diff --git a/dd-java-agent/build.gradle b/dd-java-agent/build.gradle index ae78df4410c..e086e3f054f 100644 --- a/dd-java-agent/build.gradle +++ b/dd-java-agent/build.gradle @@ -530,7 +530,7 @@ tasks.register('verifyAgentJarContents') { // Sanity check on the minimum number of classes; update as needed. Set to about 98% of that number. def classCount = entries.keySet().count { it.endsWith('.class') || it.endsWith('.classdata') } - def classFloor = 17_000 // a bit moe than 98% of 17,279 at time of writing + def classFloor = 17_000 // a bit more than 98% of 17,279 at time of writing if (classCount < classFloor) { failures << "Class count ${classCount} is below floor ${classFloor}" } @@ -577,6 +577,85 @@ tasks.register('verifyAgentJarContents') { } } +def integrationsGoldenFile = project.file('expected-integrations.txt') + +tasks.register('verifyAgentJarIntegrations', JavaExec) { + group = LifecycleBasePlugin.VERIFICATION_GROUP + description = 'Verify the agent jar lists exactly the integrations in expected-integrations.txt' + + def jarProvider = tasks.named('shadowJar', ShadowJar).flatMap { it.archiveFile } + inputs.file(jarProvider) + inputs.file(integrationsGoldenFile) + outputs.file(project.layout.buildDirectory.file("tmp/${it.name}/.verified")) + + // Run the assembled agent jar directly — this exercises dd-java-agent.index, + // inst/instrumenter.index, and instrumentation class loading end-to-end. + mainClass = 'datadog.trace.bootstrap.AgentBootstrap' + classpath = objects.fileCollection().from(jarProvider) + args = ['--list-integrations'] + + // Capture both stdout and stderr: InstrumenterIndex.buildModule() logs ERROR and returns null when a module + // fails to load, while the process exits with status 0. + def capturedOutput = new ByteArrayOutputStream() + def capturedError = new ByteArrayOutputStream() + standardOutput = capturedOutput + errorOutput = capturedError + + doLast { + def stderr = capturedError.toString() + if (!stderr.isBlank()) { + throw new GradleException( + "--list-integrations produced unexpected stderr output " + + "(likely a module load failure; see InstrumenterIndex.buildModule):\n${stderr}") + } + + if (!integrationsGoldenFile.exists()) { + throw new GradleException( + "${integrationsGoldenFile.name} not found. " + + "Run './gradlew :dd-java-agent:updateAgentJarIntegrationsGolden' to create it.") + } + + def actual = capturedOutput.toString().readLines().findAll { !it.isBlank() }.toSorted() + def expected = integrationsGoldenFile.readLines().findAll { !it.isBlank() }.toSorted() + def added = actual - expected + def removed = expected - actual + + if (added || removed) { + def msg = new StringBuilder("Integration list differs from ${integrationsGoldenFile.name}.") + msg.append(" Run './gradlew :dd-java-agent:updateAgentJarIntegrationsGolden' to update it.\n") + added.each { msg.append(" + ${it}\n") } + removed.each { msg.append(" - ${it}\n") } + throw new GradleException(msg.toString()) + } + + def marker = outputs.files.singleFile + marker.parentFile.mkdirs() + marker.text = 'verified' + } +} + +// Manual run after adding/removing integrations to update expected-integrations.txt, then add with the new integration. +tasks.register('updateAgentJarIntegrationsGolden', JavaExec) { + group = LifecycleBasePlugin.VERIFICATION_GROUP + description = 'Regenerate expected-integrations.txt from the current agent jar' + + def jarProvider = tasks.named('shadowJar', ShadowJar).flatMap { it.archiveFile } + inputs.file(jarProvider) + + mainClass = 'datadog.trace.bootstrap.AgentBootstrap' + classpath = objects.fileCollection().from(jarProvider) + args = ['--list-integrations'] + + def capturedOutput = new ByteArrayOutputStream() + standardOutput = capturedOutput + + doLast { + def integrations = capturedOutput.toString().readLines().findAll { !it.isBlank() }.toSorted() + integrationsGoldenFile.text = integrations.join('\n') + '\n' + logger.lifecycle("Updated ${integrationsGoldenFile.name} with ${integrations.size()} integrations") + } +} + tasks.named('check') { - dependsOn 'verifyAgentJarContents' + dependsOn 'verifyAgentJarContents', 'verifyAgentJarIntegrations' } diff --git a/dd-java-agent/expected-integrations.txt b/dd-java-agent/expected-integrations.txt new file mode 100644 index 00000000000..dc0d8bd7e7b --- /dev/null +++ b/dd-java-agent/expected-integrations.txt @@ -0,0 +1,208 @@ +IastInstrumentation +aerospike +akka-http +akka-http2 +akka_actor_mailbox +akka_actor_receive +akka_actor_send +akka_concurrent +allocatedirect +amqp +apache-httpclient +armeria-grpc-client +armeria-grpc-server +armeria-jetty +avro +aws-lambda +aws-sdk +axis2 +axway-api +azure-functions +caffeine +cassandra +ci-visibility +cics +classloading +commons-fileupload +commons-http-client +confluent-schema-registry +couchbase +cucumber +cxf +datanucleus +defineclass +dropwizard +dynamodb +elasticsearch +emr-aws-sdk +eventbridge +ffm-native-tracing +finatra +freemarker +gax +glassfish +google-http-client +google-pubsub +gradle +graphql-java +grizzly +grizzly-client +grizzly-filterchain +grpc +gson +guava +hazelcast +hazelcast_legacy +hibernate +httpasyncclient +httpasyncclient5 +httpclient +httpclient5 +httpcore +httpcore-5 +httpurlconnection +hystrix +ignite +inputStream +jackson +jackson-core +jacoco +jakarta-jms +jakarta-mail +jakarta-rs +jakarta-websocket +jakarta-ws +java-http-client +java-lang +java-lang-appsec +java-lang-management +java-module +java-net +java_completablefuture +java_concurrent +java_timer +javax-mail +javax-websocket +jax-rs +jax-ws +jboss-logmanager +jdbc +jdbc-datasource +jedis +jersey +jetty +jetty-client +jetty-concurrent +jms +jni +jsp +jwt +kafka +kotlin_coroutine +lettuce +liberty +log4j +logback +maven +micronaut +mmap +mongo +mule +native-image +netty +netty-concurrent +netty-promise +not-not-trace +ognl +okhttp +openai-java +opensearch +opentelemetry-annotations +opentelemetry-beta +opentelemetry-logs +opentelemetry-metrics +opentelemetry.experimental +opentracing +org-json +pekko-http +pekko-http2 +pekko_actor_mailbox +pekko_actor_receive +pekko_actor_send +play +play-ws +protobuf +quartz +ratpack +ratpack-request-body +reactive-streams +reactor-core +reactor-netty +rediscala +redisson +renaissance +resilience4j +resilience4j-reactor +resteasy +restlet-http +rmi +rxjava +s3 +scala_concurrent +servicetalk +servlet +servlet-filter +servlet-request-body +servlet-service +sfn +shutdown +slick +snakeyaml +sns +socket +sofarpc +spark +spark-executor +spark-exit +spark-launcher +spark-openlineage +sparkjava +spray-http +spring-async +spring-beans +spring-boot +spring-boot-span-origin +spring-cloud-zuul +spring-core +spring-data +spring-jms +spring-messaging +spring-rabbit +spring-scheduling +spring-security +spring-web +spring-web-code-origin +spring-webflux +spring-ws +spymemcached +sqs +sslsocket +synapse3-client +synapse3-server +testng +throwables +thymeleaf +tibco +tinylog +tomcat +trace +twilio-sdk +undertow +urlconnection +valkey +velocity +vertx +wallclock +websphere-jmx +wildfly +zio.experimental