From f0d16030f0a99925a60f56d30e0b4fe4f90bcf3b Mon Sep 17 00:00:00 2001 From: William Briggs <37094383+billy-briggs-dev@users.noreply.github.com> Date: Thu, 21 Aug 2025 00:49:46 +0000 Subject: [PATCH] Adding Support for JaCoCo Agent Configuration Options This change introduces support for additional configuration options for the JaCoCo agent, enhancing its flexibility and usability. These changes were made to be backwards compatible, ensuring that existing configurations continue to work without modification. Added optional configuration options: - destfile - append - exclclassloader - inclbootstrapclasses - inclnolocationclasses - sessionid (defaulted to instance uuid) - dumponexit - classdumpdir - jmx Issue: #1116 --- docs/framework-jacoco_agent.md | 23 ++++--- lib/java_buildpack/framework/jacoco_agent.rb | 62 ++++++++++++++----- .../framework/jacoco_agent_spec.rb | 44 ++++++++++++- 3 files changed, 107 insertions(+), 22 deletions(-) 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