diff --git a/docs/framework-jacoco_agent.md b/docs/framework-jacoco_agent.md index c97d75e08a..d4c253d24b 100644 --- a/docs/framework-jacoco_agent.md +++ b/docs/framework-jacoco_agent.md @@ -21,13 +21,22 @@ Users may optionally provide their own JaCoCo service. A user-provided JaCoCo se The credential payload of the service may contain the following entries: -| Name | Description -| ---- | ----------- -| `address` | The host for the agent to connect to or listen on -| `excludes` | (Optional) A list of class names that should be excluded from execution analysis. The list entries are separated by a colon (:) and may use wildcard characters (* and ?). -| `includes` | (Optional) A list of class names that should be included in execution analysis. The list entries are separated by a colon (:) and may use wildcard characters (* and ?). -| `port` | (Optional) The port for the agent to connect to or listen on -| `output` | (Optional) The mode for the agent. Possible values are either tcpclient (default) or tcpserver. +| Name | Description +|------------------------|------------ +| `address` | The host for the agent to connect to or listen on. +| `destfile` | (Optional) The path to the file where execution data is written. Default is `jacoco.exec`. +| `sessionid` | (Optional) The identifier for the coverage session. Useful for distinguishing between different test runs. +| `append` | (Optional) If set to `true`, coverage data is appended to the existing file. Default is `false`. +| `includes` | (Optional) A list of class names to include in execution analysis. Entries are separated by a colon (`:`) and may use wildcards (`*`, `?`). +| `excludes` | (Optional) A list of class names to exclude from execution analysis. Entries are separated by a colon (`:`) and may use wildcards (`*`, `?`). +| `exclclassloader` | (Optional) A list of class loader names to exclude from instrumentation. Entries are separated by a colon (`:`). +| `inclbootstrapclasses` | (Optional) If set to `true`, includes bootstrap classes for instrumentation. Default is `false`. +| `inclnolocationclasses`| (Optional) If set to `true`, includes classes without a location for instrumentation. Default is `false`. +| `dumponexit` | (Optional) If set to `true`, coverage data is written on JVM shutdown. Default is `true`. +| `output` | (Optional) The mode for the agent. Possible values are `tcpclient` (default) or `tcpserver`. +| `port` | (Optional) The port for the agent to connect to or listen on. +| `classdumpdir` | (Optional) The directory where class files are dumped if class dumping is enabled. +| `jmx` | (Optional) If set to `true`, enables JMX control for the agent. Default is `false`. ## Configuration For general information on configuring the buildpack, including how to specify configuration values through environment variables, refer to [Configuration and Extension][]. diff --git a/lib/java_buildpack/framework/jacoco_agent.rb b/lib/java_buildpack/framework/jacoco_agent.rb index e585776b85..40f7d8fb1b 100644 --- a/lib/java_buildpack/framework/jacoco_agent.rb +++ b/lib/java_buildpack/framework/jacoco_agent.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Cloud Foundry Java Buildpack -# Copyright 2013-2020 the original author or authors. +# Copyright 2013-2025 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -31,19 +31,8 @@ def compile # (see JavaBuildpack::Component::BaseComponent#release) def release - credentials = @application.services.find_service(FILTER, ADDRESS)['credentials'] - properties = { - 'address' => credentials[ADDRESS], - 'output' => 'tcpclient', - 'sessionid' => '$CF_INSTANCE_GUID' - } - - properties['excludes'] = credentials['excludes'] if credentials.key? 'excludes' - properties['includes'] = credentials['includes'] if credentials.key? 'includes' - properties['port'] = credentials['port'] if credentials.key? 'port' - properties['output'] = credentials['output'] if credentials.key? 'output' - - @droplet.java_opts.add_javaagent_with_props(@droplet.sandbox + 'jacocoagent.jar', properties) + @droplet.java_opts.add_javaagent_with_props(@droplet.sandbox + 'jacocoagent.jar', + build_properties(service_credentials)) end protected @@ -59,6 +48,51 @@ def supports? private_constant :ADDRESS, :FILTER + OPTIONAL_PROPS = { + 'excludes' => nil, + 'includes' => nil, + 'port' => nil, + 'destfile' => nil, + 'append' => nil, + 'exclclassloader' => nil, + 'inclbootstrapclasses' => nil, + 'inclnolocationclasses' => nil, + 'dumponexit' => nil, + 'classdumpdir' => nil, + 'jmx' => nil + }.freeze + + private_constant :ADDRESS, :FILTER, :OPTIONAL_PROPS + + private + + def service_credentials + @application.services.find_service(FILTER, ADDRESS)['credentials'] + end + + def build_properties(credentials) + properties = base_properties(credentials) + properties.merge!(optional_properties(credentials)) + end + + def base_properties(credentials) + { + 'address' => credentials[ADDRESS], + 'output' => credentials['output'] || 'tcpclient', + 'sessionid' => credentials['sessionid'] || '$CF_INSTANCE_GUID' + } + end + + def optional_properties(credentials) + opts = {} + OPTIONAL_PROPS.each_key do |key| + next unless credentials.key?(key) + + opts[key] = credentials[key] + end + opts + end + end end diff --git a/spec/java_buildpack/framework/jacoco_agent_spec.rb b/spec/java_buildpack/framework/jacoco_agent_spec.rb index 05957fa272..69c4a90c7c 100644 --- a/spec/java_buildpack/framework/jacoco_agent_spec.rb +++ b/spec/java_buildpack/framework/jacoco_agent_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Cloud Foundry Java Buildpack -# Copyright 2013-2020 the original author or authors. +# Copyright 2013-2025 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -33,6 +33,42 @@ allow(services).to receive(:one_service?).with(/jacoco/, 'address').and_return(true) end + let(:extended_credentials) do + { 'address' => 'ext-address', + 'output' => 'tcpserver', + 'sessionid' => 'ext-session', + 'excludes' => 'ex.*', + 'includes' => 'in.*', + 'port' => 7654, + 'destfile' => 'custom.exec', + 'append' => 'false', + 'exclclassloader' => 'loader.*', + 'inclbootstrapclasses' => 'true', + 'inclnolocationclasses' => 'true', + 'dumponexit' => 'false', + 'classdumpdir' => 'dumpdir', + 'jmx' => 'true' } + end + + let(:extended_expected) do + [ + '-javaagent:$PWD/.java-buildpack/jacoco_agent/jacocoagent.jar=address=ext-address', + 'output=tcpserver', + 'sessionid=ext-session', + 'excludes=ex.*', + 'includes=in.*', + 'port=7654', + 'destfile=custom.exec', + 'append=false', + 'exclclassloader=loader.*', + 'inclbootstrapclasses=true', + 'inclnolocationclasses=true', + 'dumponexit=false', + 'classdumpdir=dumpdir', + 'jmx=true' + ].join(',') + end + it 'detects with jacoco service' do expect(component.detect).to eq("jacoco-agent=#{version}") end @@ -68,6 +104,12 @@ 'excludes=test-excludes,includes=test-includes,port=6300') end + it 'updates JAVA_OPTS with extended options' do + allow(services).to receive(:find_service).and_return('credentials' => extended_credentials) + component.release + expect(java_opts).to include(extended_expected) + end + end end