From 57a8918abfda5638f316eca7db5eb4d03fdc4ad4 Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Wed, 13 May 2026 17:08:00 -0400 Subject: [PATCH 1/5] Improved GradleFixture usage. Moved common code in base class. --- .../datadog/gradle/plugin/GradleFixture.kt | 68 ++++++++++++-- .../ParseV2SupportedConfigurationsTest.kt | 63 ++++++------- .../csi/CallSiteInstrumentationPluginTest.kt | 81 +++++++---------- .../dump/DumpHangedTestIntegrationTest.kt | 90 ++++++++----------- .../BuildTimeInstrumentationPluginTest.kt | 81 +++++++---------- .../muzzle/MuzzlePluginFunctionalTest.kt | 15 ++-- .../plugin/muzzle/MuzzlePluginTestFixture.kt | 5 +- .../version/TracerVersionIntegrationTest.kt | 55 +++++------- .../plugin/version/VersionPluginsFixture.kt | 6 -- .../version/WriteVersionFilePluginTest.kt | 56 ++++++------ 10 files changed, 251 insertions(+), 269 deletions(-) diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt index c0fd6077ded..017a9d5e640 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt @@ -13,7 +13,7 @@ import javax.xml.parsers.DocumentBuilderFactory * Base fixture for Gradle plugin integration tests. * Provides common functionality for setting up test projects and running Gradle builds. */ -internal open class GradleFixture(protected val projectDir: File) { +internal open class GradleFixture(val projectDir: File) { // Each fixture gets its own testkit dir in the system temp directory (NOT under // projectDir) so that JUnit's @TempDir cleanup doesn't race with daemon file locks. // See https://github.com/gradle/gradle/issues/12535 @@ -24,6 +24,17 @@ internal open class GradleFixture(protected val projectDir: File) { // have been stopped), so file locks are guaranteed to be released by then. private val testKitDir: File by lazy { Files.createTempDirectory("gradle-testkit-").toFile().also { dir -> + // Configure Gradle to use as less resources as possible: + // - Xms64m -Xmx256m: consume minimum amount of RAM. + // - workers.max=1: don't let the daemon fan out into multiple Worker JVMs. + // - parallel=false: serialize task execution within the fixture build. + appendTo("gradle.properties", + """ + org.gradle.jvmargs= -Xms64m -Xmx256m + org.gradle.workers.max=1 + org.gradle.parallel=false + """ + ) Runtime.getRuntime().addShutdownHook(Thread { dir.deleteRecursively() }) } } @@ -38,9 +49,17 @@ internal open class GradleFixture(protected val projectDir: File) { * @param args Gradle task names and arguments * @param expectFailure Whether the build is expected to fail * @param env Environment variables to set (merged with system environment) + * @param forwardOutput Forward the build's stdout/stderr to the test's output + * @param projectDir Override the project directory used by Gradle (useful for git worktree tests) * @return The build result */ - fun run(vararg args: String, expectFailure: Boolean = false, env: Map = emptyMap()): BuildResult { + fun run( + vararg args: String, + expectFailure: Boolean = false, + env: Map = emptyMap(), + forwardOutput: Boolean = false, + projectDir: File = this.projectDir, + ): BuildResult { val runner = GradleRunner.create() .withTestKitDir(testKitDir) .withPluginClasspath() @@ -48,6 +67,9 @@ internal open class GradleFixture(protected val projectDir: File) { // Using withDebug prevents starting a daemon, but it doesn't work with withEnvironment .withEnvironment(System.getenv() + env) .withArguments(*args) + if (forwardOutput) { + runner.forwardOutput() + } return try { if (expectFailure) runner.buildAndFail() else runner.build() } catch (e: UnexpectedBuildResultException) { @@ -131,13 +153,49 @@ internal open class GradleFixture(protected val projectDir: File) { .writeText(buildScript.trimIndent()) } + /** + * Writes a Java test source file under src/test/java. + * + * @param testName Simple class name, fully qualified class name, or source path + * @param testCode The Java test source content + */ + fun writeTest(testName: String, @Language("JAVA") testCode: String) { + val sourcePath = testName.removeSuffix(".java").replace('.', '/') + ".java" + appendTo("src/test/java/$sourcePath", testCode) + } + + /** + * Writes an arbitrary text file under the project directory, creating parent dirs. + * + * @param path Path relative to the project directory + * @param content File contents; leading/trailing whitespace is preserved (no trimIndent) + */ + fun appendTo(path: String, content: String): File = + file(path).also { + it.appendText(content.trimIndent()) + it.appendText("\n") + } + + fun gradleProperties(content: String) { + appendTo("gradle.properties", content) + } + /** * Writes the root project's build.gradle file. * * @param buildScript The build script content for the root project */ - fun writeRootProject(@Language("Groovy") buildScript: String) { - file("build.gradle").writeText(buildScript.trimIndent()) + fun rootProject(@Language("Groovy") buildScript: String) { + appendTo("build.gradle", buildScript) + } + + /** + * Writes the root project's settings.gradle file. + * + * @param settingsScript The settings script content + */ + fun settings(@Language("Groovy") settingsScript: String) { + appendTo("settings.gradle", settingsScript) } /** @@ -151,7 +209,7 @@ internal open class GradleFixture(protected val projectDir: File) { /** * Creates or gets a file in the project directory, ensuring parent directories exist. */ - protected fun file(path: String, mkdirs: Boolean = true): File = + fun file(path: String, mkdirs: Boolean = true): File = File(projectDir, path).also { file -> if (mkdirs) { file.parentFile?.mkdirs() diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt index bb6a04d52f1..b81e7a3d722 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt @@ -1,14 +1,13 @@ package datadog.gradle.plugin.config +import datadog.gradle.plugin.GradleFixture import org.gradle.testkit.runner.BuildResult -import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.io.File -import java.nio.file.Paths class ParseV2SupportedConfigurationsTest { @Test @@ -77,8 +76,10 @@ class ParseV2SupportedConfigurationsTest { } private fun runGradleTask(projectDir: File): Pair { - val jsonFile = file(projectDir, "test-supported-configurations.json") - jsonFile.writeText( + val fixture = GradleFixture(projectDir) + + fixture.appendTo( + "test-supported-configurations.json", """ { "supportedConfigurations": { @@ -88,7 +89,7 @@ class ParseV2SupportedConfigurationsTest { "type": "string", "default": null, "aliases": [], - "propertyKeys": ["property.key"] + "propertyKeys": ["property.key"] } ], "DD_AGENTLESS_LOG_SUBMISSION_ENABLED": [ @@ -114,54 +115,42 @@ class ParseV2SupportedConfigurationsTest { """.trimIndent() ) - setupGradleProject(projectDir) + setupGradleProject(fixture) - val buildResult = GradleRunner.create() - .forwardOutput() - .withPluginClasspath() - .withArguments("generateSupportedConfigurations") - .withProjectDir(projectDir) - .build() + val buildResult = fixture.run( + "generateSupportedConfigurations", + forwardOutput = true + ) - val generatedFile = file(projectDir, "build", "generated", "supportedConfigurations", "datadog", "test", "TestGeneratedSupportedConfigurations.java") + val generatedFile = fixture.file("build/generated/supportedConfigurations/datadog/test/TestGeneratedSupportedConfigurations.java", mkdirs = false) return Pair(buildResult, generatedFile) } - private fun setupGradleProject(projectDir: File) { - file(projectDir, "settings.gradle.kts").writeText( + private fun setupGradleProject(fixture: GradleFixture) { + fixture.settings( + """ + rootProject.name = 'test-config-project' """ - rootProject.name = "test-config-project" - """.trimIndent() ) - file(projectDir, "build.gradle.kts").writeText( + fixture.rootProject( """ plugins { - id("java") - id("dd-trace-java.supported-config-generator") + id 'java' + id 'dd-trace-java.supported-config-generator' } - - group = "datadog.config.test" - + + group = 'datadog.config.test' + supportedTracerConfigurations { - jsonFile.set(file("test-supported-configurations.json")) - destinationDirectory.set(file("build/generated/supportedConfigurations")) - className.set("datadog.test.TestGeneratedSupportedConfigurations") + jsonFile.set(file('test-supported-configurations.json')) + destinationDirectory.set(file('build/generated/supportedConfigurations')) + className.set('datadog.test.TestGeneratedSupportedConfigurations') } - """.trimIndent() + """ ) } - private fun file(projectDir: File, vararg parts: String, makeDirectory: Boolean = false): File { - val f = Paths.get(projectDir.absolutePath, *parts).toFile() - - if (makeDirectory) { - f.parentFile.mkdirs() - } - - return f - } - private fun assertContainsSupportedConfig( content: String, key: String, diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPluginTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPluginTest.kt index deba44a3a08..2444d1447c3 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPluginTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPluginTest.kt @@ -1,10 +1,8 @@ package datadog.gradle.plugin.csi +import datadog.gradle.plugin.GradleFixture import org.gradle.testkit.runner.BuildResult -import org.gradle.testkit.runner.GradleRunner -import org.gradle.testkit.runner.UnexpectedBuildFailure import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir @@ -28,11 +26,11 @@ class CallSiteInstrumentationPluginTest { targetFolder = project.layout.buildDirectory.dir('csi') rootFolder = file('__ROOT_FOLDER__') } - + repositories { mavenCentral() } - + dependencies { implementation group: 'net.bytebuddy', name: 'byte-buddy', version: '1.18.8' implementation group: 'com.google.auto.service', name: 'auto-service-annotations', version: '1.1.1' @@ -44,11 +42,11 @@ class CallSiteInstrumentationPluginTest { @Test fun `test call site instrumentation plugin`() { - createGradleProject( - buildDir, buildGradle, + val fixture = createGradleProject( + buildGradle, """ import datadog.trace.agent.tooling.csi.*; - + @CallSite(spi = CallSites.class) public class BeforeAdviceCallSite { @CallSite.Before("java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String)") @@ -58,9 +56,9 @@ class CallSiteInstrumentationPluginTest { """.trimIndent() ) - val result = buildGradleProject(buildDir) + val result = buildGradleProject(fixture) - val generated = resolve(buildDir, "build", "csi", "BeforeAdviceCallSites.java") + val generated = File(buildDir, "build/csi/BeforeAdviceCallSites.java") assertTrue(generated.exists()) val output = result.output @@ -70,11 +68,11 @@ class CallSiteInstrumentationPluginTest { @Test fun `test call site instrumentation plugin with error`() { - createGradleProject( - buildDir, buildGradle, + val fixture = createGradleProject( + buildGradle, """ import datadog.trace.agent.tooling.csi.*; - + @CallSite(spi = CallSites.class) public class BeforeAdviceCallSite { @CallSite.Before("java.lang.StringBuilder java.lang.StringBuilder.append(java.lang.String)") @@ -84,22 +82,22 @@ class CallSiteInstrumentationPluginTest { """.trimIndent() ) - val error = assertThrows(UnexpectedBuildFailure::class.java) { - buildGradleProject(buildDir) - } + val result = fixture.run("build", "--info", "--stacktrace", forwardOutput = true, expectFailure = true) - val generated = resolve(buildDir, "build", "csi", "BeforeAdviceCallSites.java") + val generated = File(buildDir, "build/csi/BeforeAdviceCallSites.java") assertFalse(generated.exists()) - val output = error.message ?: "" + val output = result.output assertFalse(output.contains("[✓]")) assertTrue(output.contains("ADVICE_METHOD_NOT_STATIC_AND_PUBLIC")) } - private fun createGradleProject(buildDir: File, gradleFile: String, advice: String) { + private fun createGradleProject(gradleFile: String, advice: String): GradleFixture { + val fixture = GradleFixture(buildDir) val projectFolder = File(System.getProperty("user.dir")).parentFile - val callSiteJar = resolve(projectFolder, "buildSrc", "call-site-instrumentation-plugin", "build", "libs", "call-site-instrumentation-plugin-all.jar") - val testCallSiteJarDir = resolve(buildDir, "buildSrc", "call-site-instrumentation-plugin", "build", "libs", makeDirs = true) + val callSiteJar = File(projectFolder, "buildSrc/call-site-instrumentation-plugin/build/libs/call-site-instrumentation-plugin-all.jar") + val testCallSiteJarDir = fixture.file("buildSrc/call-site-instrumentation-plugin/build/libs") + testCallSiteJarDir.mkdirs() Files.copy( callSiteJar.toPath(), @@ -107,31 +105,26 @@ class CallSiteInstrumentationPluginTest { ) val gradleFileContent = gradleFile.replace("__ROOT_FOLDER__", projectFolder.toString().replace("\\", "\\\\")) - writeText(resolve(buildDir, "build.gradle"), gradleFileContent) + fixture.rootProject(gradleFileContent) - val javaFolder = resolve(buildDir, "src", "main", "java", makeDirs = true) val advicePackage = parsePackage(advice) val adviceClassName = parseClassName(advice) - val adviceFolder = resolve(javaFolder, *advicePackage.split("\\.").toTypedArray(), makeDirs = true) - writeText(resolve(adviceFolder, "$adviceClassName.java"), advice) + val advicePath = if (advicePackage.isEmpty()) { + "src/main/java/$adviceClassName.java" + } else { + "src/main/java/${advicePackage.replace('.', '/')}/$adviceClassName.java" + } + fixture.appendTo(advicePath, advice) - val csiSource = resolve(projectFolder, "dd-java-agent", "agent-tooling", "src", "main", "java", "datadog", "trace", "agent", "tooling", "csi") - val csiTarget = resolve(javaFolder, "datadog", "trace", "agent", "tooling", "csi", makeDirs = true) - csiSource.listFiles()?.forEach { - writeText(File(csiTarget, it.name), it.readText()) + val csiSource = File(projectFolder, "dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/csi") + csiSource.listFiles()?.forEach { src -> + fixture.appendTo("src/main/java/datadog/trace/agent/tooling/csi/${src.name}", src.readText()) } + return fixture } - private fun buildGradleProject(buildDir: File): BuildResult { - return GradleRunner.create() - .withTestKitDir(File(buildDir, ".gradle-test-kit")) // workaround in case the global test-kit cache becomes corrupted - .withDebug(true) // avoids starting daemon which can leave undeleted files post-cleanup - .withProjectDir(buildDir) - .withArguments("build", "--info", "--stacktrace") - .withPluginClasspath() - .forwardOutput() - .build() - } + private fun buildGradleProject(fixture: GradleFixture): BuildResult = + fixture.run("build", "--info", "--stacktrace", forwardOutput = true) private fun parsePackage(advice: String): String { val regex = Regex("package\\s+([\\w.]+)\\s*;", RegexOption.DOT_MATCHES_ALL) @@ -144,14 +137,4 @@ class CallSiteInstrumentationPluginTest { val match = regex.find(advice) return match?.groupValues?.getOrNull(1) ?: "" } - - private fun resolve(parent: File, vararg path: String, makeDirs: Boolean = false): File { - return path.fold(parent) { acc, next -> File(acc, next) }.apply { - if (makeDirs) { - mkdirs() - } - } - } - - private fun writeText(file: File, content: String) = file.writeText(content) } diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/dump/DumpHangedTestIntegrationTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/dump/DumpHangedTestIntegrationTest.kt index d0a8279c85d..a0f60bfd607 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/dump/DumpHangedTestIntegrationTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/dump/DumpHangedTestIntegrationTest.kt @@ -1,39 +1,37 @@ package datadog.gradle.plugin.dump -import org.gradle.testkit.runner.GradleRunner -import org.gradle.testkit.runner.UnexpectedBuildFailure +import datadog.gradle.plugin.GradleFixture import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertNotNull import org.junit.jupiter.api.io.TempDir import java.io.File -import java.nio.file.Paths class DumpHangedTestIntegrationTest { @Test fun `should not take dumps`(@TempDir projectDir: File) { - val output = runGradleTest(projectDir, testSleep = 1000) + val output = runGradleTest(projectDir, testSleepMillis = 1000) // Assert Gradle output has no evidence of taking dumps. assertFalse(output.contains("Taking dumps after 15 seconds delay for :test")) assertFalse(output.contains("Requesting stop of task ':test' as it has exceeded its configured timeout of 20s.")) - assertTrue(file(projectDir, "build").exists()) // Assert build happened. - assertFalse(file(projectDir, "build", "dumps").exists()) // Assert no dumps created. + assertTrue(File(projectDir, "build").exists()) // Assert build happened. + assertFalse(File(projectDir, "build/dumps").exists()) // Assert no dumps created. } @Test fun `should take dumps`(@TempDir projectDir: File) { - val output = runGradleTest(projectDir, testSleep = 25_0000) + val output = runGradleTest(projectDir, testSleepMillis = 25_0000) // Assert Gradle output has evidence of taking dumps. assertTrue(output.contains("Taking dumps after 15 seconds delay for :test")) assertTrue(output.contains("Requesting stop of task ':test' as it has exceeded its configured timeout of 20s.")) - assertTrue(file(projectDir, "build").exists()) // Assert build happened. + assertTrue(File(projectDir, "build").exists()) // Assert build happened. - val dumps = file(projectDir, "build", "dumps") + val dumps = File(projectDir, "build/dumps") assertTrue(dumps.exists()) // Assert dumps created. // Assert actual dumps created. @@ -42,82 +40,64 @@ class DumpHangedTestIntegrationTest { assertNotNull(dumpFiles.find { it.startsWith("all-thread-dumps") }) } - private fun runGradleTest(projectDir: File, testSleep: Long): List { - file(projectDir, "settings.gradle.kts").writeText( + private fun runGradleTest(projectDir: File, testSleepMillis: Long): List { + val fixture = GradleFixture(projectDir) + + fixture.settings( + """ + rootProject.name = 'test-project' """ - rootProject.name = "test-project" - """.trimIndent() ) - file(projectDir, "build.gradle.kts").writeText( + fixture.rootProject( """ import java.time.Duration - + plugins { - id("java") - id("dd-trace-java.dump-hanged-test") + id 'java' + id 'dd-trace-java.dump-hanged-test' } - - group = "datadog.dump.test" - + + group = 'datadog.dump.test' + repositories { mavenCentral() } - + dependencies { - testImplementation(platform("org.junit:junit-bom:5.10.0")) - testImplementation("org.junit.jupiter:junit-jupiter") - testRuntimeOnly("org.junit.platform:junit-platform-launcher") + testImplementation platform('org.junit:junit-bom:5.10.0') + testImplementation 'org.junit.jupiter:junit-jupiter' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } - + dumpHangedTest { // Set the dump offset for 5 seconds to trigger taking dumps after 15 seconds. dumpOffset.set(5) } - - tasks.withType().configureEach { + + tasks.withType(Test).configureEach { // Set test timeout after 20 seconds. timeout.set(Duration.ofSeconds(20)) - + useJUnitPlatform() } - """.trimIndent() + """ ) - file(projectDir, "src", "test", "java", "SimpleTest.java", makeDirectory = true).writeText( + fixture.writeTest( + "SimpleTest", """ import org.junit.jupiter.api.Test; - + public class SimpleTest { @Test public void test() throws InterruptedException { - Thread.sleep($testSleep); + Thread.sleep($testSleepMillis); } } - """.trimIndent() + """ ) - try { - val buildResult = GradleRunner.create() - .forwardOutput() - .withPluginClasspath() - .withArguments("test") - .withProjectDir(projectDir) - .build() - - return buildResult.output.lines() - } catch (e: UnexpectedBuildFailure) { - return e.buildResult.output.lines() - } - } - - private fun file(projectDir: File, vararg parts: String, makeDirectory: Boolean = false): File { - val f = Paths.get(projectDir.absolutePath, *parts).toFile() - - if (makeDirectory) { - f.parentFile.mkdirs() - } - - return f + return fixture.run("test", forwardOutput = true).output.lines() } } diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/BuildTimeInstrumentationPluginTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/BuildTimeInstrumentationPluginTest.kt index 017f6e6041d..288f0f3375a 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/BuildTimeInstrumentationPluginTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/BuildTimeInstrumentationPluginTest.kt @@ -1,7 +1,7 @@ package datadog.gradle.plugin.instrument +import datadog.gradle.plugin.GradleFixture import net.bytebuddy.utility.OpenedClassReader -import org.gradle.testkit.runner.GradleRunner import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir @@ -37,7 +37,7 @@ class BuildTimeInstrumentationPluginTest { buildTimeInstrumentation.plugins = [ 'TestPlugin' ] - """.trimIndent() + """ private val exampleCode = """ package example; @@ -49,30 +49,22 @@ class BuildTimeInstrumentationPluginTest { @Test fun `test instrument plugin`() { - val buildFile = File(buildDir, "build.gradle") - buildFile.writeText(buildGradle) - - val srcMainJava = testPlugin("src/main/java", "ExampleCode") + val fixture = GradleFixture(buildDir) + fixture.rootProject(buildGradle) - val examplePackageDir = File(srcMainJava, "example").apply { mkdirs() } - File(examplePackageDir, "ExampleCode.java").writeText(exampleCode) + writeTestPlugin(fixture, "ExampleCode") + fixture.appendTo("src/main/java/example/ExampleCode.java", exampleCode) - // Run Gradle build with TestKit - GradleRunner.create().withTestKitDir(File(buildDir, ".gradle-test-kit")) // workaround in case the global test-kit cache becomes corrupted - .withDebug(true) // avoids starting daemon which can leave undeleted files post-cleanup - .withProjectDir(buildDir) - .withArguments("build", "--stacktrace") - .withPluginClasspath() - .forwardOutput() - .build() + fixture.run("build", "--stacktrace", forwardOutput = true) assertInstrumented(File(buildDir, "build/classes/java/main/example/ExampleCode.class")) } @Test fun `test instrument plugin processes includeClassDirectories`() { - val buildFile = File(buildDir, "build.gradle") - buildFile.writeText(""" + val fixture = GradleFixture(buildDir) + fixture.rootProject( + """ plugins { id 'java' id 'dd-trace-java.build-time-instrumentation' @@ -95,22 +87,16 @@ class BuildTimeInstrumentationPluginTest { plugins = ['TestPlugin'] includeClassDirectories.from(file('external-classes')) } - """.trimIndent()) + """ + ) - testPlugin("src/main/java", "ExternalCode") + writeTestPlugin(fixture, "ExternalCode") // Pre-compile ExternalCode using ASM and place it in the external-classes directory - val externalClassesDir = File(buildDir, "external-classes").apply { mkdirs() } + val externalClassesDir = fixture.file("external-classes").apply { mkdirs() } precompiledClass("ExternalCode", externalClassesDir) - GradleRunner.create() - .withTestKitDir(File(buildDir, ".gradle-test-kit")) - .withDebug(true) - .withProjectDir(buildDir) - .withArguments("build", "--stacktrace") - .withPluginClasspath() - .forwardOutput() - .build() + fixture.run("build", "--stacktrace", forwardOutput = true) // ExternalCode.class should have been copied from external-classes, instrumented, and placed in the output assertInstrumented(File(buildDir, "build/classes/java/main/ExternalCode.class")) @@ -118,8 +104,9 @@ class BuildTimeInstrumentationPluginTest { @Test fun `test rerun-tasks does not lose includeClassDirectories classes`() { - val buildFile = File(buildDir, "build.gradle") - buildFile.writeText(""" + val fixture = GradleFixture(buildDir) + fixture.rootProject( + """ plugins { id 'java' id 'dd-trace-java.build-time-instrumentation' @@ -142,37 +129,31 @@ class BuildTimeInstrumentationPluginTest { plugins = ['TestPlugin'] includeClassDirectories.from(file('external-classes')) } - """.trimIndent()) + """ + ) - val srcMainJava = testPlugin("src/main/java", "ExampleCode", "ExternalCode") - val examplePackageDir = File(srcMainJava, "example").apply { mkdirs() } - File(examplePackageDir, "ExampleCode.java").writeText("package example; public class ExampleCode {}") + writeTestPlugin(fixture, "ExampleCode", "ExternalCode") + fixture.appendTo("src/main/java/example/ExampleCode.java", "package example; public class ExampleCode {}") - val externalClassesDir = File(buildDir, "external-classes").apply { mkdirs() } + val externalClassesDir = fixture.file("external-classes").apply { mkdirs() } precompiledClass("ExternalCode", externalClassesDir) - val runner = GradleRunner.create() - .withTestKitDir(File(buildDir, ".gradle-test-kit")) - .withDebug(true) - .withProjectDir(buildDir) - .withPluginClasspath() - .forwardOutput() - // First build - runner.withArguments("build", "--stacktrace").build() + fixture.run("build", "--stacktrace", forwardOutput = true) // Second build with --rerun-tasks: compileJava wipes classesDirectory, so without // the fix InstrumentAction would only sync freshly-compiled classes and lose ExternalCode.class - runner.withArguments("build", "--rerun-tasks", "--stacktrace").build() + fixture.run("build", "--rerun-tasks", "--stacktrace", forwardOutput = true) assertInstrumented(File(buildDir, "build/classes/java/main/example/ExampleCode.class")) assertInstrumented(File(buildDir, "build/classes/java/main/ExternalCode.class")) } - private fun testPlugin(srcDir: String, vararg classNames: String): File { - val dir = File(buildDir, srcDir).apply { mkdirs() } + private fun writeTestPlugin(fixture: GradleFixture, vararg classNames: String) { val conditions = classNames.joinToString(" || ") { "\"$it\".equals(name)" } - File(dir, "TestPlugin.java").writeText(""" + fixture.appendTo( + "src/main/java/TestPlugin.java", + """ import java.io.File; import java.io.IOException; import net.bytebuddy.build.Plugin; @@ -206,8 +187,8 @@ class BuildTimeInstrumentationPluginTest { // no-op } } - """.trimIndent()) - return dir + """.trimIndent() + ) } private fun precompiledClass(className: String, targetDir: File) { diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt index 00eb2514db0..63ac678395a 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt @@ -36,7 +36,7 @@ class MuzzlePluginFunctionalTest { ) // Add runMuzzle aggregator task at root level (like in dd-trace-java.ci-jobs.gradle.kts) - fixture.writeRootProject( + fixture.rootProject( """ tasks.register('runMuzzle') { dependsOn(':dd-java-agent:instrumentation:demo:muzzle') @@ -731,15 +731,18 @@ class MuzzlePluginFunctionalTest { @Test fun `missing dd-java-agent projects error handling`(@TempDir projectDir: File) { + val fixture = GradleFixture(projectDir) + // Create a minimal settings.gradle without the dd-java-agent structure - File(projectDir, "settings.gradle").also { it.parentFile?.mkdirs() }.writeText( + fixture.settings( """ rootProject.name = 'muzzle-test' include ':instrumentation:demo' - """.trimIndent() + """ ) - File(projectDir, "instrumentation/demo/build.gradle").also { it.parentFile?.mkdirs() }.writeText( + fixture.addSubproject( + "instrumentation:demo", """ plugins { id 'java' @@ -751,13 +754,13 @@ class MuzzlePluginFunctionalTest { coreJdk() } } - """.trimIndent() + """ ) // No need to create MuzzleVersionScanPlugin - the error happens during configuration // phase before any task execution, so the scan plugin is never invoked - val result = GradleFixture(projectDir).run( + val result = fixture.run( ":instrumentation:demo:tasks", "--stacktrace" ) diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginTestFixture.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginTestFixture.kt index 026ef5b0d9d..4f76dcf3d89 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginTestFixture.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginTestFixture.kt @@ -15,11 +15,10 @@ internal class MuzzlePluginTestFixture(projectDir: File) : GradleFixture(project * Creates a multi-project build with agent-bootstrap, agent-tooling, and instrumentation modules. */ fun writeProject(@Language("Groovy") instrumentationBuildScript: String) { - file("settings.gradle").writeText( - // language=Groovy + settings( """ rootProject.name = 'muzzle-e2e' - """.trimIndent() + """ ) addSubproject("dd-java-agent:agent-bootstrap", diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt index 4f253b41633..dc6783a7934 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt @@ -1,7 +1,6 @@ package datadog.gradle.plugin.version import org.assertj.core.api.Assertions.assertThat -import org.gradle.testkit.runner.GradleRunner import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.io.File @@ -32,7 +31,7 @@ class TracerVersionIntegrationTest { expectedVersion = "0.1.0-SNAPSHOT", beforeGradle = { fixture.initGitRepo() - settingsFile.appendText("\n// uncommitted change this file, ") + fixture.settings("\n// uncommitted change this file, ") }, ) } @@ -67,10 +66,10 @@ class TracerVersionIntegrationTest { fixture.assertTracerVersion( expectedVersion = "1.53.0-SNAPSHOT-DIRTY", beforeGradle = { - gradlePropertiesFile.writeText("tracerVersion.dirtiness=true") + fixture.gradleProperties("tracerVersion.dirtiness=true") fixture.initGitRepo() fixture.exec("git", "tag", "v1.52.0", "-m", "") - settingsFile.appendText("\n// uncommitted change this file, ") + fixture.settings("\n// uncommitted change this file, ") }, ) } @@ -83,7 +82,7 @@ class TracerVersionIntegrationTest { beforeGradle = { fixture.initGitRepo() fixture.exec("git", "tag", "v1.52.0", "-m", "") - settingsFile.appendText("\n// Committed change this file, ") + fixture.settings("\n// Committed change this file, ") fixture.exec("git", "commit", "-am", "Another commit") }, ) @@ -95,13 +94,12 @@ class TracerVersionIntegrationTest { fixture.assertTracerVersion( expectedVersion = "1.53.0-SNAPSHOT-DIRTY", beforeGradle = { - gradlePropertiesFile.writeText("tracerVersion.dirtiness=true") + fixture.gradleProperties("tracerVersion.dirtiness=true") fixture.initGitRepo() fixture.exec("git", "tag", "v1.52.0", "-m", "") - val settings = settingsFile - settings.appendText("\n// uncommitted change ") + fixture.settings("\n// uncommitted change ") fixture.exec("git", "commit", "-am", "Another commit") - settings.appendText("\n// An uncommitted modification") + fixture.settings("\n// An uncommitted modification") }, ) } @@ -114,7 +112,7 @@ class TracerVersionIntegrationTest { beforeGradle = { fixture.initGitRepo() fixture.exec("git", "tag", "v1.52.0", "-m", "") - settingsFile.appendText("\n// Committed change ") + fixture.settings("\n// Committed change ") fixture.exec("git", "commit", "-am", "Another commit") fixture.exec("git", "switch", "-c", "release/v1.52.x") }, @@ -130,11 +128,10 @@ class TracerVersionIntegrationTest { fixture.initGitRepo() fixture.exec("git", "tag", "v1.52.0", "-m", "") fixture.exec("git", "switch", "-c", "release/v1.52.x") - val settings = settingsFile - settings.appendText("\n// Committed change ") + fixture.settings("\n// Committed change ") fixture.exec("git", "commit", "-am", "Another commit") fixture.exec("git", "tag", "v1.52.1", "-m", "") - settings.appendText("\n// Another committed change ") + fixture.settings("\n// Another committed change ") fixture.exec("git", "commit", "-am", "Another commit") }, ) @@ -151,7 +148,7 @@ class TracerVersionIntegrationTest { fixture.exec("git", "tag", "v1.52.0", "-m", "") fixture.exec("git", "commit", "-m", "Initial commit", "--allow-empty") fixture.exec("git", "worktree", "add", workTreeDir.absolutePath) - File(workTreeDir, "settings.gradle.kts").appendText("\n// Committed change this file, ") + File(workTreeDir, "settings.gradle").appendText("\n// Committed change this file, ") fixture.exec(workTreeDir, "git", "commit", "-am", "Another commit") }, ) @@ -162,35 +159,29 @@ class TracerVersionIntegrationTest { workingDirectory: File? = null, beforeGradle: VersionPluginsFixture.() -> Unit = {}, ) { - settingsFile.writeText("""rootProject.name = "test-project"""") - projectBuildFile.writeText( + settings( + """ + rootProject.name = 'test-project' + """ + ) + + rootProject( """ plugins { - id("dd-trace-java.tracer-version") + id 'dd-trace-java.tracer-version' } - tasks.register("printVersion") { + tasks.register('printVersion') { logger.quiet(project.version.toString()) } - group = "datadog.tracer.version.test" - """.trimIndent() + group = 'datadog.tracer.version.test' + """ ) beforeGradle() - val buildResult = if (workingDirectory == null) { - run("printVersion", "--quiet") - } else { - // Worktree: Gradle must run from a directory other than projectDir, - // so use GradleRunner directly instead of GradleFixture.run(). - GradleRunner.create() - .forwardOutput() - .withPluginClasspath() - .withArguments("printVersion", "--quiet") - .withProjectDir(workingDirectory) - .build() - } + val buildResult = run("printVersion", "--quiet", projectDir = workingDirectory ?: projectDir) assertThat(buildResult.output.lines().first()).isEqualTo(expectedVersion) } diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/VersionPluginsFixture.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/VersionPluginsFixture.kt index aba9e9fb35b..2ffee2a3a91 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/VersionPluginsFixture.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/VersionPluginsFixture.kt @@ -28,12 +28,6 @@ internal class VersionPluginsFixture(projectDir: File) : GradleFixture(projectDi exec(workingDirectory, "git", "commit", "-m", "A commit") } - val projectBuildFile = file("build.gradle.kts") - - val gradlePropertiesFile = file("gradle.properties") - - val settingsFile = file("settings.gradle.kts") - val generatedVersionFile = file("build/generated/version/my-lib.version", false) val builtResourceVersionFile = file("build/resources/main/my-lib.version", false) diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/WriteVersionFilePluginTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/WriteVersionFilePluginTest.kt index 8aa49a86ae8..3dff7989ee9 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/WriteVersionFilePluginTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/WriteVersionFilePluginTest.kt @@ -26,14 +26,14 @@ class WriteVersionFilePluginTest { fixture.assertVersionFile( expectedContentRegex = "9.9.9~deadbeef", beforeGradle = { - projectBuildFile.appendText( + rootProject( """ - tasks.named("writeVersionNumberFile").configure { - version.set("9.9.9") - gitHash.set("deadbeef") + tasks.named('writeVersionNumberFile', datadog.gradle.plugin.version.WriteVersionFile).configure { + version.set('9.9.9') + gitHash.set('deadbeef') } - """.trimIndent() + """ ) }, ) @@ -45,13 +45,13 @@ class WriteVersionFilePluginTest { fixture.assertVersionFile( expectedContentRegex = "1.2.3~abc12345", beforeGradle = { - projectBuildFile.appendText( + rootProject( """ - tasks.named("writeVersionNumberFile").configure { - gitHash.set("abc12345") + tasks.named('writeVersionNumberFile', datadog.gradle.plugin.version.WriteVersionFile).configure { + gitHash.set('abc12345') } - """.trimIndent() + """ ) generatedVersionFile.run { parentFile.mkdirs() @@ -68,13 +68,13 @@ class WriteVersionFilePluginTest { expectedContentRegex = "1.2.3~abc12345", task = "processResources", beforeGradle = { - projectBuildFile.appendText( + rootProject( """ - tasks.named("writeVersionNumberFile").configure { - gitHash.set("abc12345") + tasks.named('writeVersionNumberFile', datadog.gradle.plugin.version.WriteVersionFile).configure { + gitHash.set('abc12345') } - """.trimIndent() + """ ) }, ) @@ -88,13 +88,13 @@ class WriteVersionFilePluginTest { fixture.assertVersionFile( expectedContentRegex = "1.2.3~abc12345", beforeGradle = { - projectBuildFile.appendText( + rootProject( """ - tasks.named("writeVersionNumberFile").configure { - gitHash.set("abc12345") + tasks.named('writeVersionNumberFile', datadog.gradle.plugin.version.WriteVersionFile).configure { + gitHash.set('abc12345') } - """.trimIndent() + """ ) }, ) @@ -110,13 +110,13 @@ class WriteVersionFilePluginTest { fixture.assertVersionFile( expectedContentRegex = "1.2.3~abc12345", beforeGradle = { - projectBuildFile.appendText( + rootProject( """ - tasks.named("writeVersionNumberFile").configure { - gitHash.set("abc12345") + tasks.named('writeVersionNumberFile', datadog.gradle.plugin.version.WriteVersionFile).configure { + gitHash.set('abc12345') } - """.trimIndent() + """ ) }, ) @@ -133,15 +133,19 @@ class WriteVersionFilePluginTest { task: String = ":writeVersionNumberFile", beforeGradle: VersionPluginsFixture.() -> Unit = {}, ): BuildResult { - settingsFile.writeText("""rootProject.name = "my-lib"""") - projectBuildFile.writeText( + settings( + """ + rootProject.name = 'my-lib' + """ + ) + rootProject( """ plugins { - id("dd-trace-java.version-file") + id 'dd-trace-java.version-file' } - version = "1.2.3" - """.trimIndent() + version = '1.2.3' + """ ) beforeGradle() From f76959e3caeb7096712eaa82aa8bff2e08bd085c Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Wed, 13 May 2026 20:03:26 -0400 Subject: [PATCH 2/5] Improved GradleFixture usage. Moved common code in base class. --- .../datadog/gradle/plugin/GradleFixture.kt | 160 +++++++++----- .../ParseV2SupportedConfigurationsTest.kt | 27 +-- .../csi/CallSiteInstrumentationPluginTest.kt | 47 ++-- .../dump/DumpHangedTestIntegrationTest.kt | 40 ++-- .../BuildTimeInstrumentationPluginTest.kt | 62 +++--- .../muzzle/MuzzlePluginFunctionalTest.kt | 205 ++++++++---------- .../muzzle/MuzzlePluginPerformanceTest.kt | 105 ++++----- .../plugin/muzzle/MuzzlePluginTestFixture.kt | 39 ++-- .../version/TracerVersionIntegrationTest.kt | 144 ++++++------ .../plugin/version/VersionPluginsFixture.kt | 7 +- .../version/WriteVersionFilePluginTest.kt | 73 +++---- 11 files changed, 435 insertions(+), 474 deletions(-) diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt index 017a9d5e640..45d39933371 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt @@ -4,6 +4,7 @@ import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.UnexpectedBuildResultException import org.intellij.lang.annotations.Language +import org.junit.jupiter.api.io.TempDir import org.w3c.dom.Document import java.io.File import java.nio.file.Files @@ -13,7 +14,10 @@ import javax.xml.parsers.DocumentBuilderFactory * Base fixture for Gradle plugin integration tests. * Provides common functionality for setting up test projects and running Gradle builds. */ -internal open class GradleFixture(val projectDir: File) { +open class GradleFixture { + @TempDir + protected lateinit var projectDir: File + // Each fixture gets its own testkit dir in the system temp directory (NOT under // projectDir) so that JUnit's @TempDir cleanup doesn't race with daemon file locks. // See https://github.com/gradle/gradle/issues/12535 @@ -24,21 +28,31 @@ internal open class GradleFixture(val projectDir: File) { // have been stopped), so file locks are guaranteed to be released by then. private val testKitDir: File by lazy { Files.createTempDirectory("gradle-testkit-").toFile().also { dir -> - // Configure Gradle to use as less resources as possible: - // - Xms64m -Xmx256m: consume minimum amount of RAM. - // - workers.max=1: don't let the daemon fan out into multiple Worker JVMs. - // - parallel=false: serialize task execution within the fixture build. - appendTo("gradle.properties", - """ - org.gradle.jvmargs= -Xms64m -Xmx256m - org.gradle.workers.max=1 - org.gradle.parallel=false - """ - ) Runtime.getRuntime().addShutdownHook(Thread { dir.deleteRecursively() }) } } + // Configure Gradle to use as few resources as possible: + // - Xms64m -Xmx256m: consume minimum amount of RAM. + // - workers.max=1: don't let the daemon fan out into multiple Worker JVMs. + // - parallel=false: serialize task execution within the fixture build. + // Re-applied if missing. + private fun applyResourceLimits() { + val gradleProperties = file("gradle.properties") + if (gradleProperties.exists() && gradleProperties.readText().contains("org.gradle.jvmargs=-Xms64m -Xmx256m")) { + return + } + + writeFile("gradle.properties", + """ + org.gradle.jvmargs=-Xms64m -Xmx256m + org.gradle.workers.max=1 + org.gradle.parallel=false + """, + append = true, + ) + } + /** * Runs Gradle with the specified arguments. * @@ -50,7 +64,8 @@ internal open class GradleFixture(val projectDir: File) { * @param expectFailure Whether the build is expected to fail * @param env Environment variables to set (merged with system environment) * @param forwardOutput Forward the build's stdout/stderr to the test's output - * @param projectDir Override the project directory used by Gradle (useful for git worktree tests) + * @param projectDir Override the project directory used by Gradle (useful for git worktree + * tests); when null, defaults to the fixture's project directory. * @return The build result */ fun run( @@ -58,12 +73,14 @@ internal open class GradleFixture(val projectDir: File) { expectFailure: Boolean = false, env: Map = emptyMap(), forwardOutput: Boolean = false, - projectDir: File = this.projectDir, + projectDir: File? = null, ): BuildResult { + applyResourceLimits() + val runner = GradleRunner.create() .withTestKitDir(testKitDir) .withPluginClasspath() - .withProjectDir(projectDir) + .withProjectDir(projectDir ?: this.projectDir) // Using withDebug prevents starting a daemon, but it doesn't work with withEnvironment .withEnvironment(System.getenv() + env) .withArguments(*args) @@ -134,69 +151,82 @@ internal open class GradleFixture(val projectDir: File) { } /** - * Adds a subproject to the build. - * Updates settings.gradle and creates the build script for the subproject. + * Writes a file under the project directory, creating parent dirs as needed. + * + * @param path Path relative to the project directory + * @param content File contents; passed through [String.trimIndent] before writing, + * and a trailing newline is appended. + * @param append If true, appends to any existing file instead of overwriting it. + * Safe to call repeatedly to build content up across steps. + */ + fun writeFile(path: String, content: String, append: Boolean = false): File = + file(path).also { + it.parentFile?.mkdirs() + val text = content.trimIndent() + "\n" + if (append) it.appendText(text) else it.writeText(text) + } + + /** + * Adds a subproject to the build by appending an `include` line to settings.gradle + * and writing the subproject's build.gradle. * * @param projectPath The project path (e.g., "dd-java-agent:instrumentation:other") * @param buildScript The build script content for the subproject */ fun addSubproject(projectPath: String, @Language("Groovy") buildScript: String) { - // Add to settings.gradle - val settingsFile = file("settings.gradle") - if (settingsFile.exists()) { - settingsFile.appendText("\ninclude ':$projectPath'") - } else { - settingsFile.writeText("include ':$projectPath'") - } - - file("${projectPath.replace(':', '/')}/build.gradle") - .writeText(buildScript.trimIndent()) + writeFile("settings.gradle", "include ':$projectPath'", append = true) + writeFile("${projectPath.replace(':', '/')}/build.gradle", buildScript) } /** - * Writes a Java test source file under src/test/java. + * Writes a Java source file under src//java. * - * @param testName Simple class name, fully qualified class name, or source path - * @param testCode The Java test source content + * @param classNameOrPath Simple class name, fully qualified class name, or source path + * @param sourceCode The Java source content + * @param sourceSet The Gradle source set to write to + * @param projectPath Optional Gradle project path; defaults to the root project */ - fun writeTest(testName: String, @Language("JAVA") testCode: String) { - val sourcePath = testName.removeSuffix(".java").replace('.', '/') + ".java" - appendTo("src/test/java/$sourcePath", testCode) + fun writeJavaSource( + classNameOrPath: String, + @Language("JAVA") sourceCode: String, + sourceSet: String = "main", + projectPath: String? = null, + ) { + val sourcePath = classNameOrPath.removeSuffix(".java").replace('.', '/') + ".java" + val projectPrefix = projectPath + ?.removePrefix(":") + ?.replace(':', '/') + ?.let { "$it/" } + .orEmpty() + writeFile("${projectPrefix}src/$sourceSet/java/$sourcePath", sourceCode) } /** - * Writes an arbitrary text file under the project directory, creating parent dirs. + * Writes gradle.properties at the project root. * - * @param path Path relative to the project directory - * @param content File contents; leading/trailing whitespace is preserved (no trimIndent) + * @param content Properties content (trimIndent applied, trailing newline added) + * @param append If true, appends to any existing file instead of overwriting */ - fun appendTo(path: String, content: String): File = - file(path).also { - it.appendText(content.trimIndent()) - it.appendText("\n") - } - - fun gradleProperties(content: String) { - appendTo("gradle.properties", content) - } + fun writeGradleProperties(content: String, append: Boolean = false): File = + writeFile("gradle.properties", content, append) /** * Writes the root project's build.gradle file. * * @param buildScript The build script content for the root project + * @param append If true, appends to any existing file instead of overwriting */ - fun rootProject(@Language("Groovy") buildScript: String) { - appendTo("build.gradle", buildScript) - } + fun writeRootProject(@Language("Groovy") buildScript: String, append: Boolean = false): File = + writeFile("build.gradle", buildScript, append) /** * Writes the root project's settings.gradle file. * * @param settingsScript The settings script content + * @param append If true, appends to any existing file instead of overwriting */ - fun settings(@Language("Groovy") settingsScript: String) { - appendTo("settings.gradle", settingsScript) - } + fun writeSettings(@Language("Groovy") settingsScript: String, append: Boolean = false): File = + writeFile("settings.gradle", settingsScript, append) /** * Parses an XML file into a DOM Document. @@ -207,12 +237,26 @@ internal open class GradleFixture(val projectDir: File) { } /** - * Creates or gets a file in the project directory, ensuring parent directories exist. + * Returns a File handle under the project directory. + * Does not touch the filesystem. */ - fun file(path: String, mkdirs: Boolean = true): File = - File(projectDir, path).also { file -> - if (mkdirs) { - file.parentFile?.mkdirs() - } - } + fun file(path: String): File = File(projectDir, path) + + /** + * Creates a directory under the project directory (including any missing parents) + * and returns it. + */ + fun dir(path: String): File = file(path).also { it.mkdirs() } + + /** + * The Gradle build output directory (`projectDir/build`). Not created — Gradle + * produces it during a build. + */ + val buildDir: File get() = File(projectDir, "build") + + /** + * Returns a File under the Gradle build output directory (`projectDir/build/...`). + * Does NOT create parent dirs — these paths are read after a Gradle build produces them. + */ + fun buildFile(path: String): File = File(buildDir, path) } diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt index b81e7a3d722..5c437666d4b 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt @@ -6,13 +6,12 @@ import org.gradle.testkit.runner.TaskOutcome import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test -import org.junit.jupiter.api.io.TempDir import java.io.File -class ParseV2SupportedConfigurationsTest { +class ParseV2SupportedConfigurationsTest : GradleFixture() { @Test - fun `should generate Java file from JSON configuration`(@TempDir projectDir: File) { - val (buildResult, generatedFile) = runGradleTask(projectDir) + fun `should generate Java file from JSON configuration`() { + val (buildResult, generatedFile) = runGradleTask() assertEquals(TaskOutcome.SUCCESS, buildResult.task(":generateSupportedConfigurations")?.outcome) @@ -75,10 +74,8 @@ class ParseV2SupportedConfigurationsTest { assertTrue(content.contains("""reversePropertyKeysMapping.put("property.key", "DD_ACTION_EXECUTION_ID")""")) } - private fun runGradleTask(projectDir: File): Pair { - val fixture = GradleFixture(projectDir) - - fixture.appendTo( + private fun runGradleTask(): Pair { + writeFile( "test-supported-configurations.json", """ { @@ -112,28 +109,28 @@ class ParseV2SupportedConfigurationsTest { "legacy.setting": "No longer supported" } } - """.trimIndent() + """ ) - setupGradleProject(fixture) + setupGradleProject() - val buildResult = fixture.run( + val buildResult = run( "generateSupportedConfigurations", forwardOutput = true ) - val generatedFile = fixture.file("build/generated/supportedConfigurations/datadog/test/TestGeneratedSupportedConfigurations.java", mkdirs = false) + val generatedFile = file("build/generated/supportedConfigurations/datadog/test/TestGeneratedSupportedConfigurations.java") return Pair(buildResult, generatedFile) } - private fun setupGradleProject(fixture: GradleFixture) { - fixture.settings( + private fun setupGradleProject() { + writeSettings( """ rootProject.name = 'test-config-project' """ ) - fixture.rootProject( + writeRootProject( """ plugins { id 'java' diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPluginTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPluginTest.kt index 2444d1447c3..d1a8716e122 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPluginTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPluginTest.kt @@ -5,11 +5,10 @@ import org.gradle.testkit.runner.BuildResult import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test -import org.junit.jupiter.api.io.TempDir import java.io.File import java.nio.file.Files -class CallSiteInstrumentationPluginTest { +class CallSiteInstrumentationPluginTest : GradleFixture() { private val buildGradle = """ plugins { id 'java' @@ -35,14 +34,11 @@ class CallSiteInstrumentationPluginTest { implementation group: 'net.bytebuddy', name: 'byte-buddy', version: '1.18.8' implementation group: 'com.google.auto.service', name: 'auto-service-annotations', version: '1.1.1' } - """.trimIndent() - - @TempDir - lateinit var buildDir: File + """ @Test fun `test call site instrumentation plugin`() { - val fixture = createGradleProject( + createGradleProject( buildGradle, """ import datadog.trace.agent.tooling.csi.*; @@ -53,12 +49,12 @@ class CallSiteInstrumentationPluginTest { public static void beforeAppend(@CallSite.This final StringBuilder self, @CallSite.Argument final String param) { } } - """.trimIndent() + """ ) - val result = buildGradleProject(fixture) + val result = buildGradleProject() - val generated = File(buildDir, "build/csi/BeforeAdviceCallSites.java") + val generated = buildFile("csi/BeforeAdviceCallSites.java") assertTrue(generated.exists()) val output = result.output @@ -68,7 +64,7 @@ class CallSiteInstrumentationPluginTest { @Test fun `test call site instrumentation plugin with error`() { - val fixture = createGradleProject( + createGradleProject( buildGradle, """ import datadog.trace.agent.tooling.csi.*; @@ -79,12 +75,12 @@ class CallSiteInstrumentationPluginTest { private void beforeAppend(@CallSite.This final StringBuilder self, @CallSite.Argument final String param) { } } - """.trimIndent() + """ ) - val result = fixture.run("build", "--info", "--stacktrace", forwardOutput = true, expectFailure = true) + val result = run("build", "--info", "--stacktrace", forwardOutput = true, expectFailure = true) - val generated = File(buildDir, "build/csi/BeforeAdviceCallSites.java") + val generated = buildFile("csi/BeforeAdviceCallSites.java") assertFalse(generated.exists()) val output = result.output @@ -92,12 +88,10 @@ class CallSiteInstrumentationPluginTest { assertTrue(output.contains("ADVICE_METHOD_NOT_STATIC_AND_PUBLIC")) } - private fun createGradleProject(gradleFile: String, advice: String): GradleFixture { - val fixture = GradleFixture(buildDir) + private fun createGradleProject(gradleFile: String, advice: String) { val projectFolder = File(System.getProperty("user.dir")).parentFile val callSiteJar = File(projectFolder, "buildSrc/call-site-instrumentation-plugin/build/libs/call-site-instrumentation-plugin-all.jar") - val testCallSiteJarDir = fixture.file("buildSrc/call-site-instrumentation-plugin/build/libs") - testCallSiteJarDir.mkdirs() + val testCallSiteJarDir = dir("buildSrc/call-site-instrumentation-plugin/build/libs") Files.copy( callSiteJar.toPath(), @@ -105,26 +99,25 @@ class CallSiteInstrumentationPluginTest { ) val gradleFileContent = gradleFile.replace("__ROOT_FOLDER__", projectFolder.toString().replace("\\", "\\\\")) - fixture.rootProject(gradleFileContent) + writeRootProject(gradleFileContent) val advicePackage = parsePackage(advice) val adviceClassName = parseClassName(advice) - val advicePath = if (advicePackage.isEmpty()) { - "src/main/java/$adviceClassName.java" + val adviceSourceName = if (advicePackage.isEmpty()) { + adviceClassName } else { - "src/main/java/${advicePackage.replace('.', '/')}/$adviceClassName.java" + "$advicePackage.$adviceClassName" } - fixture.appendTo(advicePath, advice) + writeJavaSource(adviceSourceName, advice) val csiSource = File(projectFolder, "dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/csi") csiSource.listFiles()?.forEach { src -> - fixture.appendTo("src/main/java/datadog/trace/agent/tooling/csi/${src.name}", src.readText()) + writeJavaSource("datadog.trace.agent.tooling.csi.${src.nameWithoutExtension}", src.readText()) } - return fixture } - private fun buildGradleProject(fixture: GradleFixture): BuildResult = - fixture.run("build", "--info", "--stacktrace", forwardOutput = true) + private fun buildGradleProject(): BuildResult = + run("build", "--info", "--stacktrace", forwardOutput = true) private fun parsePackage(advice: String): String { val regex = Regex("package\\s+([\\w.]+)\\s*;", RegexOption.DOT_MATCHES_ALL) diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/dump/DumpHangedTestIntegrationTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/dump/DumpHangedTestIntegrationTest.kt index a0f60bfd607..066cd13dc34 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/dump/DumpHangedTestIntegrationTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/dump/DumpHangedTestIntegrationTest.kt @@ -5,33 +5,30 @@ import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertNotNull -import org.junit.jupiter.api.io.TempDir -import java.io.File -class DumpHangedTestIntegrationTest { +class DumpHangedTestIntegrationTest : GradleFixture() { @Test - fun `should not take dumps`(@TempDir projectDir: File) { - val output = runGradleTest(projectDir, testSleepMillis = 1000) + fun `should not take dumps`() { + val output = runGradleTest(testSleepMillis = 1000) // Assert Gradle output has no evidence of taking dumps. assertFalse(output.contains("Taking dumps after 15 seconds delay for :test")) assertFalse(output.contains("Requesting stop of task ':test' as it has exceeded its configured timeout of 20s.")) - assertTrue(File(projectDir, "build").exists()) // Assert build happened. - assertFalse(File(projectDir, "build/dumps").exists()) // Assert no dumps created. + assertTrue(buildDir.exists()) // Assert build happened. + assertFalse(buildFile("dumps").exists()) // Assert no dumps created. } @Test - fun `should take dumps`(@TempDir projectDir: File) { - val output = runGradleTest(projectDir, testSleepMillis = 25_0000) + fun `should take dumps`() { + val output = runGradleTest(testSleepMillis = 25_0000) // Assert Gradle output has evidence of taking dumps. assertTrue(output.contains("Taking dumps after 15 seconds delay for :test")) assertTrue(output.contains("Requesting stop of task ':test' as it has exceeded its configured timeout of 20s.")) + assertTrue(buildDir.exists()) // Assert build happened. - assertTrue(File(projectDir, "build").exists()) // Assert build happened. - - val dumps = File(projectDir, "build/dumps") + val dumps = buildFile("dumps") assertTrue(dumps.exists()) // Assert dumps created. // Assert actual dumps created. @@ -40,16 +37,10 @@ class DumpHangedTestIntegrationTest { assertNotNull(dumpFiles.find { it.startsWith("all-thread-dumps") }) } - private fun runGradleTest(projectDir: File, testSleepMillis: Long): List { - val fixture = GradleFixture(projectDir) - - fixture.settings( - """ - rootProject.name = 'test-project' - """ - ) + private fun runGradleTest(testSleepMillis: Long): List { + writeSettings("""rootProject.name = 'test-project'""") - fixture.rootProject( + writeRootProject( """ import java.time.Duration @@ -84,7 +75,7 @@ class DumpHangedTestIntegrationTest { """ ) - fixture.writeTest( + writeJavaSource( "SimpleTest", """ import org.junit.jupiter.api.Test; @@ -95,9 +86,10 @@ class DumpHangedTestIntegrationTest { Thread.sleep($testSleepMillis); } } - """ + """, + sourceSet = "test" ) - return fixture.run("test", forwardOutput = true).output.lines() + return run("test", forwardOutput = true).output.lines() } } diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/BuildTimeInstrumentationPluginTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/BuildTimeInstrumentationPluginTest.kt index 288f0f3375a..9502c6a4f20 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/BuildTimeInstrumentationPluginTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/BuildTimeInstrumentationPluginTest.kt @@ -4,7 +4,6 @@ import datadog.gradle.plugin.GradleFixture import net.bytebuddy.utility.OpenedClassReader import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test -import org.junit.jupiter.api.io.TempDir import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassVisitor import org.objectweb.asm.ClassWriter @@ -13,7 +12,7 @@ import org.objectweb.asm.Opcodes import java.io.File import java.io.FileInputStream -class BuildTimeInstrumentationPluginTest { +class BuildTimeInstrumentationPluginTest : GradleFixture() { private val buildGradle = """ plugins { @@ -42,28 +41,22 @@ class BuildTimeInstrumentationPluginTest { private val exampleCode = """ package example; public class ExampleCode {} - """.trimIndent() - - @TempDir - lateinit var buildDir: File + """ @Test fun `test instrument plugin`() { - val fixture = GradleFixture(buildDir) - fixture.rootProject(buildGradle) - - writeTestPlugin(fixture, "ExampleCode") - fixture.appendTo("src/main/java/example/ExampleCode.java", exampleCode) + writeRootProject(buildGradle) + writeTestPlugin("ExampleCode") + writeJavaSource("example.ExampleCode", exampleCode) - fixture.run("build", "--stacktrace", forwardOutput = true) + run("build", "--stacktrace", forwardOutput = true) - assertInstrumented(File(buildDir, "build/classes/java/main/example/ExampleCode.class")) + assertInstrumented(buildFile("classes/java/main/example/ExampleCode.class")) } @Test fun `test instrument plugin processes includeClassDirectories`() { - val fixture = GradleFixture(buildDir) - fixture.rootProject( + writeRootProject( """ plugins { id 'java' @@ -90,22 +83,21 @@ class BuildTimeInstrumentationPluginTest { """ ) - writeTestPlugin(fixture, "ExternalCode") + writeTestPlugin("ExternalCode") // Pre-compile ExternalCode using ASM and place it in the external-classes directory - val externalClassesDir = fixture.file("external-classes").apply { mkdirs() } + val externalClassesDir = dir("external-classes") precompiledClass("ExternalCode", externalClassesDir) - fixture.run("build", "--stacktrace", forwardOutput = true) + run("build", "--stacktrace", forwardOutput = true) // ExternalCode.class should have been copied from external-classes, instrumented, and placed in the output - assertInstrumented(File(buildDir, "build/classes/java/main/ExternalCode.class")) + assertInstrumented(buildFile("classes/java/main/ExternalCode.class")) } @Test fun `test rerun-tasks does not lose includeClassDirectories classes`() { - val fixture = GradleFixture(buildDir) - fixture.rootProject( + writeRootProject( """ plugins { id 'java' @@ -132,27 +124,27 @@ class BuildTimeInstrumentationPluginTest { """ ) - writeTestPlugin(fixture, "ExampleCode", "ExternalCode") - fixture.appendTo("src/main/java/example/ExampleCode.java", "package example; public class ExampleCode {}") + writeTestPlugin("ExampleCode", "ExternalCode") + writeJavaSource("example.ExampleCode", "package example; public class ExampleCode {}") - val externalClassesDir = fixture.file("external-classes").apply { mkdirs() } + val externalClassesDir = dir("external-classes") precompiledClass("ExternalCode", externalClassesDir) // First build - fixture.run("build", "--stacktrace", forwardOutput = true) + run("build", "--stacktrace", forwardOutput = true) // Second build with --rerun-tasks: compileJava wipes classesDirectory, so without // the fix InstrumentAction would only sync freshly-compiled classes and lose ExternalCode.class - fixture.run("build", "--rerun-tasks", "--stacktrace", forwardOutput = true) + run("build", "--rerun-tasks", "--stacktrace", forwardOutput = true) - assertInstrumented(File(buildDir, "build/classes/java/main/example/ExampleCode.class")) - assertInstrumented(File(buildDir, "build/classes/java/main/ExternalCode.class")) + assertInstrumented(buildFile("classes/java/main/example/ExampleCode.class")) + assertInstrumented(buildFile("classes/java/main/ExternalCode.class")) } - private fun writeTestPlugin(fixture: GradleFixture, vararg classNames: String) { + private fun writeTestPlugin(vararg classNames: String) { val conditions = classNames.joinToString(" || ") { "\"$it\".equals(name)" } - fixture.appendTo( - "src/main/java/TestPlugin.java", + writeJavaSource( + "TestPlugin", """ import java.io.File; import java.io.IOException; @@ -183,11 +175,11 @@ class BuildTimeInstrumentationPluginTest { } @Override - public void close() throws IOException { - // no-op - } + public void close() throws IOException { + // no-op } - """.trimIndent() + } + """ ) } diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt index 63ac678395a..3b6f50b1b34 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt @@ -1,26 +1,17 @@ package datadog.gradle.plugin.muzzle -import datadog.gradle.plugin.GradleFixture -import datadog.gradle.plugin.MavenRepoFixture import org.assertj.core.api.Assertions.assertThat import org.gradle.testkit.runner.TaskOutcome.SUCCESS import org.junit.jupiter.api.Test -import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource -import java.io.File import kotlin.io.path.readText -class MuzzlePluginFunctionalTest { +class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { @ParameterizedTest @ValueSource(strings = ["muzzle", ":dd-java-agent:instrumentation:demo:muzzle", "runMuzzle"]) - fun `detects muzzle invocation with various task names`( - taskName: String, - @TempDir projectDir: File - ) { - val fixture = MuzzlePluginTestFixture(projectDir) - - fixture.writeProject( + fun `detects muzzle invocation with various task names`(taskName: String) { + writeProject( """ plugins { id 'java' @@ -36,7 +27,7 @@ class MuzzlePluginFunctionalTest { ) // Add runMuzzle aggregator task at root level (like in dd-trace-java.ci-jobs.gradle.kts) - fixture.rootProject( + writeRootProject( """ tasks.register('runMuzzle') { dependsOn(':dd-java-agent:instrumentation:demo:muzzle') @@ -44,9 +35,9 @@ class MuzzlePluginFunctionalTest { """ ) - fixture.writeNoopScanPlugin() + writeNoopScanPlugin() - val result = fixture.run(taskName, "--stacktrace") + val result = run(taskName, "--stacktrace") assertThat(result.tasks) .withFailMessage("Should create muzzle tasks when '$taskName' is requested") @@ -59,9 +50,8 @@ class MuzzlePluginFunctionalTest { } @Test - fun `muzzle with pass directive writes junit report`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - fixture.writeProject( + fun `muzzle with pass directive writes junit report`() { + writeProject( """ plugins { id 'java' @@ -76,7 +66,7 @@ class MuzzlePluginFunctionalTest { } """ ) - fixture.writeScanPlugin( + writeScanPlugin( """ if (!assertPass) { throw new IllegalStateException("unexpected fail assertion for " + muzzleDirective); @@ -84,14 +74,14 @@ class MuzzlePluginFunctionalTest { """ ) - val buildResult = fixture.run(":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace") + val buildResult = run(":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace") assertThat(buildResult.task(":dd-java-agent:instrumentation:demo:muzzle")?.outcome).isEqualTo(SUCCESS) assertThat(buildResult.task(":dd-java-agent:instrumentation:demo:muzzle-AssertPass-core-jdk")?.outcome) .isEqualTo(SUCCESS) assertThat(buildResult.task(":dd-java-agent:instrumentation:demo:muzzle-end")?.outcome).isEqualTo(SUCCESS) - val reportFile = fixture.findSingleMuzzleJUnitReport() - val report = fixture.parseXml(reportFile) + val reportFile = findSingleMuzzleJUnitReport() + val report = parseXml(reportFile) val suite = report.documentElement assertThat(suite.tagName).isEqualTo("testsuite") assertThat(suite.getAttribute("name")).isEqualTo(":dd-java-agent:instrumentation:demo") @@ -103,9 +93,8 @@ class MuzzlePluginFunctionalTest { } @Test - fun `muzzle without directives writes default junit report`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - fixture.writeProject( + fun `muzzle without directives writes default junit report`() { + writeProject( """ plugins { id 'java' @@ -113,7 +102,7 @@ class MuzzlePluginFunctionalTest { } """ ) - fixture.writeScanPlugin( + writeScanPlugin( """ if (!assertPass) { throw new IllegalStateException("unexpected fail assertion for " + muzzleDirective); @@ -121,11 +110,11 @@ class MuzzlePluginFunctionalTest { """ ) - val result = fixture.run(":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace") + val result = run(":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace") assertThat(result.task(":dd-java-agent:instrumentation:demo:muzzle")?.outcome).isEqualTo(SUCCESS) - val reportFile = fixture.findSingleMuzzleJUnitReport() - val report = fixture.parseXml(reportFile) + val reportFile = findSingleMuzzleJUnitReport() + val report = parseXml(reportFile) val suite = report.documentElement assertThat(suite.getAttribute("name")).isEqualTo(":dd-java-agent:instrumentation:demo") assertThat(suite.getAttribute("tests")).isEqualTo("1") @@ -136,9 +125,8 @@ class MuzzlePluginFunctionalTest { } @Test - fun `non muzzle invocation does not register muzzle end task`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - fixture.writeProject( + fun `non muzzle invocation does not register muzzle end task`() { + writeProject( """ plugins { id 'java' @@ -153,15 +141,14 @@ class MuzzlePluginFunctionalTest { """ ) - val buildResult = fixture.run(":dd-java-agent:instrumentation:demo:tasks", "--all") + val buildResult = run(":dd-java-agent:instrumentation:demo:tasks", "--all") assertThat(buildResult.output).doesNotContain("muzzle-end") } @Test - fun `muzzle plugin wires bootstrap and tooling project classpaths`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - fixture.writeProject( + fun `muzzle plugin wires bootstrap and tooling project classpaths`() { + writeProject( """ plugins { id 'java' @@ -170,14 +157,14 @@ class MuzzlePluginFunctionalTest { """ ) - val bootstrapDependencies = fixture.run( + val bootstrapDependencies = run( ":dd-java-agent:instrumentation:demo:dependencies", "--configuration", "muzzleBootstrap" ) assertThat(bootstrapDependencies.output).contains("project :dd-java-agent:agent-bootstrap") - val toolingDependencies = fixture.run( + val toolingDependencies = run( ":dd-java-agent:instrumentation:demo:dependencies", "--configuration", "muzzleTooling" @@ -186,9 +173,8 @@ class MuzzlePluginFunctionalTest { } @Test - fun `muzzle executes exactly planned core-jdk tasks and writes task results`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - fixture.writeProject( + fun `muzzle executes exactly planned core-jdk tasks and writes task results`() { + writeProject( """ plugins { id 'java' @@ -201,13 +187,13 @@ class MuzzlePluginFunctionalTest { } """ ) - fixture.writeScanPlugin( + writeScanPlugin( """ // pass """ ) - val result = fixture.run(":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace") + val result = run(":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace") val muzzleTaskPath = ":dd-java-agent:instrumentation:demo:muzzle" val passDirectiveTaskPath = ":dd-java-agent:instrumentation:demo:muzzle-AssertPass-core-jdk" val failDirectiveTaskPath = ":dd-java-agent:instrumentation:demo:muzzle-AssertFail-core-jdk" @@ -229,8 +215,8 @@ class MuzzlePluginFunctionalTest { assertThat(muzzleChainInOrder) .containsExactly(muzzleTaskPath, passDirectiveTaskPath, failDirectiveTaskPath, endTaskPath) - val passDirectiveResult = fixture.resultFile("muzzle-AssertPass-core-jdk") - val failDirectiveResult = fixture.resultFile("muzzle-AssertFail-core-jdk") + val passDirectiveResult = resultFile("muzzle-AssertPass-core-jdk") + val failDirectiveResult = resultFile("muzzle-AssertFail-core-jdk") assertThat(passDirectiveResult).isRegularFile() assertThat(failDirectiveResult).isRegularFile() assertThat(passDirectiveResult.readText()).isEqualTo("PASSING") @@ -238,16 +224,15 @@ class MuzzlePluginFunctionalTest { } @Test - fun `artifact directive resolves multiple versions from version range`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - val mavenRepoFixture = MavenRepoFixture(projectDir) + fun `artifact directive resolves multiple versions from version range`() { + val mavenRepoFixture = createMavenRepoFixture() mavenRepoFixture.publishVersions( group = "com.example.test", module = "demo-lib", versions = listOf("1.0.0", "1.1.0", "1.2.0", "2.0.0") ) - fixture.writeProject( + writeProject( """ plugins { id 'java' @@ -275,10 +260,10 @@ class MuzzlePluginFunctionalTest { } """ ) - fixture.writeNoopScanPlugin() + writeNoopScanPlugin() // Leveraging MAVEN_REPOSITORY_PROXY to point to our fake repo over maven central - val result = fixture.run( + val result = run( ":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace", env = mapOf("MAVEN_REPOSITORY_PROXY" to mavenRepoFixture.repoUrl) @@ -301,8 +286,8 @@ class MuzzlePluginFunctionalTest { .withFailMessage("Should not check against test-demo-lib:2.0.0") .isNull() - val reportFile = fixture.findSingleMuzzleJUnitReport() - val report = fixture.parseXml(reportFile) + val reportFile = findSingleMuzzleJUnitReport() + val report = parseXml(reportFile) val suite = report.documentElement val testCount = suite.getAttribute("tests").toInt() assertThat(testCount) @@ -318,9 +303,8 @@ class MuzzlePluginFunctionalTest { } @Test - fun `named directive is passed to scan plugin`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - fixture.writeProject( + fun `named directive is passed to scan plugin`() { + writeProject( """ plugins { id 'java' @@ -337,7 +321,7 @@ class MuzzlePluginFunctionalTest { ) // The real MuzzleVersionScanPlugin uses the directive name to filter InstrumenterModules - fixture.writeScanPlugin( + writeScanPlugin( """ if (!"my-custom-check".equals(muzzleDirective)) { throw new IllegalStateException( @@ -349,7 +333,7 @@ class MuzzlePluginFunctionalTest { """ ) - val result = fixture.run(":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace") + val result = run(":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace") assertThat(result.task(":dd-java-agent:instrumentation:demo:muzzle-AssertPass-core-jdk")?.outcome) .isEqualTo(SUCCESS) @@ -358,9 +342,8 @@ class MuzzlePluginFunctionalTest { } @Test - fun `non-existent artifact fails with clear error message`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - fixture.writeProject( + fun `non-existent artifact fails with clear error message`() { + writeProject( """ plugins { id 'java' @@ -376,9 +359,9 @@ class MuzzlePluginFunctionalTest { } """ ) - fixture.writeNoopScanPlugin() + writeNoopScanPlugin() - val result = fixture.run( + val result = run( ":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace", env = mapOf("MAVEN_REPOSITORY_PROXY" to "https://repo1.maven.org/maven2/") @@ -395,9 +378,8 @@ class MuzzlePluginFunctionalTest { } @Test - fun `pass directive that fails validation causes build failure`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - fixture.writeProject( + fun `pass directive that fails validation causes build failure`() { + writeProject( """ plugins { id 'java' @@ -413,7 +395,7 @@ class MuzzlePluginFunctionalTest { ) // Real implementation throws RuntimeException when !passed && assertPass (line 70 of MuzzleVersionScanPlugin) - fixture.writeScanPlugin( + writeScanPlugin( """ if (assertPass) { System.err.println("FAILED MUZZLE VALIDATION: mismatches:"); @@ -423,7 +405,7 @@ class MuzzlePluginFunctionalTest { """ ) - val result = fixture.run( + val result = run( ":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace" ) @@ -435,9 +417,8 @@ class MuzzlePluginFunctionalTest { } @Test - fun `fail directive that passes validation causes build failure`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - fixture.writeProject( + fun `fail directive that passes validation causes build failure`() { + writeProject( """ plugins { id 'java' @@ -454,7 +435,7 @@ class MuzzlePluginFunctionalTest { // Scan plugin simulates successful validation when it should fail // Real MuzzleVersionScanPlugin throws RuntimeException when passed && !assertPass - fixture.writeScanPlugin( + writeScanPlugin( """ if (!assertPass) { System.err.println("MUZZLE PASSED BUT FAILURE WAS EXPECTED"); @@ -463,7 +444,7 @@ class MuzzlePluginFunctionalTest { """ ) - val result = fixture.run( + val result = run( ":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace" ) @@ -477,9 +458,8 @@ class MuzzlePluginFunctionalTest { } @Test - fun `additional dependencies are added to muzzle test classpath`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - val mavenRepoFixture = MavenRepoFixture(projectDir) + fun `additional dependencies are added to muzzle test classpath`() { + val mavenRepoFixture = createMavenRepoFixture() // Create a fake Maven repo with a fake additional dependency // The JAR will automatically include standard Maven metadata @@ -488,7 +468,7 @@ class MuzzlePluginFunctionalTest { module = "extra-lib", versions = listOf("1.0.0") ) - fixture.writeProject( + writeProject( """ plugins { id 'java' @@ -515,7 +495,7 @@ class MuzzlePluginFunctionalTest { ) // Scan plugin verifies that the additional dependency JAR is in the classpath - fixture.writeScanPlugin( + writeScanPlugin( """ java.io.InputStream resource = testApplicationClassLoader.getResourceAsStream("META-INF/maven/com.example.extra/extra-lib/pom.properties"); if (resource != null) { @@ -531,7 +511,7 @@ class MuzzlePluginFunctionalTest { """ ) - val result = fixture.run( + val result = run( ":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace", env = mapOf("MAVEN_REPOSITORY_PROXY" to mavenRepoFixture.repoUrl) @@ -548,9 +528,8 @@ class MuzzlePluginFunctionalTest { } @Test - fun `excluded dependencies are removed from muzzle test classpath`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - val mavenRepoFixture = MavenRepoFixture(projectDir) + fun `excluded dependencies are removed from muzzle test classpath`() { + val mavenRepoFixture = createMavenRepoFixture() // Create a fake repo with an artifact that has transitive dependencies mavenRepoFixture.publishVersions( @@ -560,6 +539,7 @@ class MuzzlePluginFunctionalTest { ) // Manually create a POM with a transitive dependency + // Write into MavenRepoFixture's repoDir, not GradleFixture's projectDir. val pomFile = mavenRepoFixture.repoDir.resolve("com/example/test/with-transitive/1.0.0/with-transitive-1.0.0.pom") pomFile.writeText( """ @@ -579,7 +559,7 @@ class MuzzlePluginFunctionalTest { """.trimIndent() ) - fixture.writeProject( + writeProject( """ plugins { id 'java' @@ -609,7 +589,7 @@ class MuzzlePluginFunctionalTest { ) // Scan plugin verifies that guava is NOT in the classpath (it was excluded) - fixture.writeScanPlugin( + writeScanPlugin( """ try { testApplicationClassLoader.loadClass("com.google.common.collect.ImmutableList"); @@ -620,7 +600,7 @@ class MuzzlePluginFunctionalTest { """ ) - val result = fixture.run( + val result = run( ":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace", env = mapOf("MAVEN_REPOSITORY_PROXY" to mavenRepoFixture.repoUrl) @@ -635,10 +615,8 @@ class MuzzlePluginFunctionalTest { } @Test - fun `java plugin applied after muzzle plugin`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - - fixture.writeProject( + fun `java plugin applied after muzzle plugin`() { + writeProject( """ plugins { id 'dd-trace-java.muzzle' @@ -654,9 +632,9 @@ class MuzzlePluginFunctionalTest { } """ ) - fixture.writeNoopScanPlugin() + writeNoopScanPlugin() - val result = fixture.run( + val result = run( ":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace" ) @@ -667,10 +645,8 @@ class MuzzlePluginFunctionalTest { } @Test - fun `java plugin applied before muzzle plugin`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - - fixture.writeProject( + fun `java plugin applied before muzzle plugin`() { + writeProject( """ plugins { id 'java' @@ -687,9 +663,9 @@ class MuzzlePluginFunctionalTest { } """ ) - fixture.writeNoopScanPlugin() + writeNoopScanPlugin() - val result = fixture.run( + val result = run( ":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace" ) @@ -700,10 +676,8 @@ class MuzzlePluginFunctionalTest { } @Test - fun `plugin behavior without java plugin should no-op`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - - fixture.writeProject( + fun `plugin behavior without java plugin should no-op`() { + writeProject( """ plugins { id 'dd-trace-java.muzzle' @@ -717,9 +691,9 @@ class MuzzlePluginFunctionalTest { } """ ) - fixture.writeNoopScanPlugin() + writeNoopScanPlugin() - val result = fixture.run( + val result = run( ":dd-java-agent:instrumentation:demo:tasks", "--all" ) @@ -730,18 +704,16 @@ class MuzzlePluginFunctionalTest { } @Test - fun `missing dd-java-agent projects error handling`(@TempDir projectDir: File) { - val fixture = GradleFixture(projectDir) - + fun `missing dd-java-agent projects error handling`() { // Create a minimal settings.gradle without the dd-java-agent structure - fixture.settings( + writeSettings( """ rootProject.name = 'muzzle-test' include ':instrumentation:demo' """ ) - fixture.addSubproject( + addSubproject( "instrumentation:demo", """ plugins { @@ -760,7 +732,7 @@ class MuzzlePluginFunctionalTest { // No need to create MuzzleVersionScanPlugin - the error happens during configuration // phase before any task execution, so the scan plugin is never invoked - val result = fixture.run( + val result = run( ":instrumentation:demo:tasks", "--stacktrace" ) @@ -774,16 +746,15 @@ class MuzzlePluginFunctionalTest { } @Test - fun `assertInverse creates pass and fail tasks for in-range and out-of-range versions`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - val mavenRepoFixture = MavenRepoFixture(projectDir) + fun `assertInverse creates pass and fail tasks for in-range and out-of-range versions`() { + val mavenRepoFixture = createMavenRepoFixture() mavenRepoFixture.publishVersions( group = "com.example.test", module = "inverse-lib", versions = listOf("1.0.0", "2.0.0", "3.0.0", "4.0.0") ) - fixture.writeProject( + writeProject( """ plugins { id 'java' @@ -811,13 +782,13 @@ class MuzzlePluginFunctionalTest { } """ ) - fixture.writeScanPlugin( + writeScanPlugin( """ System.out.println("MUZZLE_CHECK assertPass=" + assertPass); """ ) - val result = fixture.run( + val result = run( ":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace", env = mapOf("MAVEN_REPOSITORY_PROXY" to mavenRepoFixture.repoUrl) @@ -851,8 +822,8 @@ class MuzzlePluginFunctionalTest { .contains("MUZZLE_CHECK assertPass=false") // Verify JUnit report contains all 4 test cases with no failures - val reportFile = fixture.findSingleMuzzleJUnitReport() - val report = fixture.parseXml(reportFile) + val reportFile = findSingleMuzzleJUnitReport() + val report = parseXml(reportFile) val suite = report.documentElement assertThat(suite.getAttribute("tests")) .withFailMessage("Should have 4 test cases (2 pass + 2 inverse fail)") diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginPerformanceTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginPerformanceTest.kt index 79aaf409b7c..30b17879f55 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginPerformanceTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginPerformanceTest.kt @@ -1,20 +1,15 @@ package datadog.gradle.plugin.muzzle -import datadog.gradle.plugin.MavenRepoFixture import org.gradle.testkit.runner.TaskOutcome.SUCCESS import org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE import org.junit.jupiter.api.Test -import org.junit.jupiter.api.io.TempDir -import java.io.File import org.assertj.core.api.Assertions.assertThat -class MuzzlePluginPerformanceTest { +class MuzzlePluginPerformanceTest : MuzzlePluginTestFixture() { @Test - fun `task graph does not include muzzle tasks when not requested`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - - fixture.writeProject( + fun `task graph does not include muzzle tasks when not requested`() { + writeProject( """ plugins { id 'java' @@ -30,9 +25,9 @@ class MuzzlePluginPerformanceTest { } """ ) - fixture.writeNoopScanPlugin() + writeNoopScanPlugin() - val result = fixture.run( + val result = run( ":dd-java-agent:instrumentation:demo:tasks", "--all", "--info" @@ -49,10 +44,8 @@ class MuzzlePluginPerformanceTest { } @Test - fun `does not configure muzzle when other project muzzle task is requested`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - - fixture.writeProject( + fun `does not configure muzzle when other project muzzle task is requested`() { + writeProject( """ plugins { id 'java' @@ -63,8 +56,8 @@ class MuzzlePluginPerformanceTest { } """ ) - fixture.writeNoopScanPlugin() - fixture.addSubproject("dd-java-agent:instrumentation:other", + writeNoopScanPlugin() + addSubproject("dd-java-agent:instrumentation:other", """ plugins { id 'java' @@ -76,7 +69,7 @@ class MuzzlePluginPerformanceTest { """ ) - val result = fixture.run( + val result = run( ":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace", "--info" @@ -94,9 +87,8 @@ class MuzzlePluginPerformanceTest { } @Test - fun `muzzle tasks are up-to-date when nothing changes`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - val mavenRepoFixture = MavenRepoFixture(projectDir) + fun `muzzle tasks are up-to-date when nothing changes`() { + val mavenRepoFixture = createMavenRepoFixture() mavenRepoFixture.publishVersions( group = "com.example.test", @@ -104,7 +96,7 @@ class MuzzlePluginPerformanceTest { versions = listOf("1.0.0", "1.1.0") ) - fixture.writeProject( + writeProject( """ plugins { id 'java' @@ -130,11 +122,11 @@ class MuzzlePluginPerformanceTest { } """ ) - fixture.writeNoopScanPlugin() + writeNoopScanPlugin() // First run - should execute the tasks run { - val firstRun = fixture.run( + val firstRun = run( ":dd-java-agent:instrumentation:demo:muzzle", env = mapOf("MAVEN_REPOSITORY_PROXY" to mavenRepoFixture.repoUrl) ) @@ -153,7 +145,7 @@ class MuzzlePluginPerformanceTest { // Second run without changes - assertion tasks should be up-to-date run { - val secondRun = fixture.run( + val secondRun = run( ":dd-java-agent:instrumentation:demo:muzzle", env = mapOf("MAVEN_REPOSITORY_PROXY" to mavenRepoFixture.repoUrl) ) @@ -181,7 +173,7 @@ class MuzzlePluginPerformanceTest { versions = listOf("1.2.0") ) - val thirdRun = fixture.run( + val thirdRun = run( ":dd-java-agent:instrumentation:demo:muzzle", env = mapOf("MAVEN_REPOSITORY_PROXY" to mavenRepoFixture.repoUrl) ) @@ -205,10 +197,8 @@ class MuzzlePluginPerformanceTest { } @Test - fun `muzzle tasks invalidated when instrumentation code changes`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - - fixture.writeProject( + fun `muzzle tasks invalidated when instrumentation code changes`() { + writeProject( """ plugins { id 'java' @@ -220,11 +210,11 @@ class MuzzlePluginPerformanceTest { } """ ) - fixture.writeNoopScanPlugin() + writeNoopScanPlugin() // First run - should execute the tasks run { - val firstRun = fixture.run(":dd-java-agent:instrumentation:demo:muzzle") + val firstRun = run(":dd-java-agent:instrumentation:demo:muzzle") assertThat(firstRun.task(":dd-java-agent:instrumentation:demo:muzzle")?.outcome) .withFailMessage("First run should execute muzzle task") @@ -236,7 +226,7 @@ class MuzzlePluginPerformanceTest { // Second run without changes - assertion tasks should be up-to-date run { - val secondRun = fixture.run(":dd-java-agent:instrumentation:demo:muzzle") + val secondRun = run(":dd-java-agent:instrumentation:demo:muzzle") assertThat(secondRun.task(":dd-java-agent:instrumentation:demo:muzzle")?.outcome) .withFailMessage("Second run should be up-to-date") @@ -248,19 +238,18 @@ class MuzzlePluginPerformanceTest { // Third run after changing instrumentation code - should be invalidated run { - val demoSourceDir = File(projectDir, "dd-java-agent/instrumentation/demo/src/main/java/com/example") - demoSourceDir.mkdirs() - File(demoSourceDir, "Demo.java").writeText( + writeFile( + "dd-java-agent/instrumentation/demo/src/main/java/com/example/Demo.java", """ package com.example; public class Demo { public void doSomething() {} } - """.trimIndent() + """ ) - val thirdRun = fixture.run(":dd-java-agent:instrumentation:demo:muzzle") + val thirdRun = run(":dd-java-agent:instrumentation:demo:muzzle") assertThat(thirdRun.task(":dd-java-agent:instrumentation:demo:muzzle")?.outcome) .withFailMessage("Third run should execute after instrumentation code change") @@ -272,10 +261,8 @@ class MuzzlePluginPerformanceTest { } @Test - fun `muzzle tasks invalidated when tooling classpath changes`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - - fixture.writeProject( + fun `muzzle tasks invalidated when tooling classpath changes`() { + writeProject( """ plugins { id 'java' @@ -287,11 +274,11 @@ class MuzzlePluginPerformanceTest { } """ ) - fixture.writeNoopScanPlugin() + writeNoopScanPlugin() // First run - should execute the tasks run { - val firstRun = fixture.run(":dd-java-agent:instrumentation:demo:muzzle") + val firstRun = run(":dd-java-agent:instrumentation:demo:muzzle") assertThat(firstRun.task(":dd-java-agent:instrumentation:demo:muzzle")?.outcome) .withFailMessage("First run should execute muzzle task") @@ -303,7 +290,7 @@ class MuzzlePluginPerformanceTest { // Second run without changes - assertion tasks should be up-to-date run { - val secondRun = fixture.run(":dd-java-agent:instrumentation:demo:muzzle") + val secondRun = run(":dd-java-agent:instrumentation:demo:muzzle") assertThat(secondRun.task(":dd-java-agent:instrumentation:demo:muzzle")?.outcome) .withFailMessage("Second run should be up-to-date") @@ -315,19 +302,18 @@ class MuzzlePluginPerformanceTest { // Third run after changing agent-tooling code - should be invalidated run { - val toolingSourceDir = File(projectDir, "dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling") - toolingSourceDir.mkdirs() - File(toolingSourceDir, "Extra.java").writeText( + writeFile( + "dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/Extra.java", """ package datadog.trace.agent.tooling; public class Extra { public void extraMethod() {} } - """.trimIndent() + """ ) - val thirdRun = fixture.run(":dd-java-agent:instrumentation:demo:muzzle") + val thirdRun = run(":dd-java-agent:instrumentation:demo:muzzle") assertThat(thirdRun.task(":dd-java-agent:instrumentation:demo:muzzle")?.outcome) .withFailMessage("Third run should execute after tooling classpath change") @@ -339,10 +325,8 @@ class MuzzlePluginPerformanceTest { } @Test - fun `muzzle tasks invalidated when bootstrap classpath changes`(@TempDir projectDir: File) { - val fixture = MuzzlePluginTestFixture(projectDir) - - fixture.writeProject( + fun `muzzle tasks invalidated when bootstrap classpath changes`() { + writeProject( """ plugins { id 'java' @@ -354,11 +338,11 @@ class MuzzlePluginPerformanceTest { } """ ) - fixture.writeNoopScanPlugin() + writeNoopScanPlugin() // First run - should execute the tasks run { - val firstRun = fixture.run(":dd-java-agent:instrumentation:demo:muzzle") + val firstRun = run(":dd-java-agent:instrumentation:demo:muzzle") assertThat(firstRun.task(":dd-java-agent:instrumentation:demo:muzzle")?.outcome) .withFailMessage("First run should execute muzzle task") @@ -370,7 +354,7 @@ class MuzzlePluginPerformanceTest { // Second run without changes - assertion tasks should be up-to-date run { - val secondRun = fixture.run(":dd-java-agent:instrumentation:demo:muzzle") + val secondRun = run(":dd-java-agent:instrumentation:demo:muzzle") assertThat(secondRun.task(":dd-java-agent:instrumentation:demo:muzzle")?.outcome) .withFailMessage("Second run should be up-to-date") @@ -382,19 +366,18 @@ class MuzzlePluginPerformanceTest { // Third run after changing agent-bootstrap code - should be invalidated run { - val bootstrapSourceDir = File(projectDir, "dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap") - bootstrapSourceDir.mkdirs() - File(bootstrapSourceDir, "Helper.java").writeText( + writeFile( + "dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Helper.java", """ package datadog.trace.bootstrap; public class Helper { public void help() {} } - """.trimIndent() + """ ) - val thirdRun = fixture.run(":dd-java-agent:instrumentation:demo:muzzle") + val thirdRun = run(":dd-java-agent:instrumentation:demo:muzzle") assertThat(thirdRun.task(":dd-java-agent:instrumentation:demo:muzzle")?.outcome) .withFailMessage("Third run should execute after bootstrap classpath change") diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginTestFixture.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginTestFixture.kt index 4f76dcf3d89..646dfa9f46a 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginTestFixture.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginTestFixture.kt @@ -1,6 +1,7 @@ package datadog.gradle.plugin.muzzle import datadog.gradle.plugin.GradleFixture +import datadog.gradle.plugin.MavenRepoFixture import org.intellij.lang.annotations.Language import java.io.File @@ -8,14 +9,15 @@ import java.io.File * Test fixture for muzzle plugin integration tests. * Extends GradleFixture with muzzle-specific functionality. */ -internal class MuzzlePluginTestFixture(projectDir: File) : GradleFixture(projectDir) { +open class MuzzlePluginTestFixture : GradleFixture() { + fun createMavenRepoFixture(): MavenRepoFixture = MavenRepoFixture(projectDir) /** * Writes the basic Gradle project structure for muzzle testing. * Creates a multi-project build with agent-bootstrap, agent-tooling, and instrumentation modules. */ fun writeProject(@Language("Groovy") instrumentationBuildScript: String) { - settings( + writeSettings( """ rootProject.name = 'muzzle-e2e' """ @@ -56,25 +58,26 @@ internal class MuzzlePluginTestFixture(projectDir: File) : GradleFixture(project * @param assertionBody Java code to execute in the assertion method */ fun writeScanPlugin(@Language("JAVA") assertionBody: String) { - file("dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/MuzzleVersionScanPlugin.java") - .writeText( - // language=JAVA - """ - package datadog.trace.agent.tooling.muzzle; + writeJavaSource( + "datadog.trace.agent.tooling.muzzle.MuzzleVersionScanPlugin", + // language=JAVA + """ + package datadog.trace.agent.tooling.muzzle; - public final class MuzzleVersionScanPlugin { - private MuzzleVersionScanPlugin() {} + public final class MuzzleVersionScanPlugin { + private MuzzleVersionScanPlugin() {} - public static void assertInstrumentationMuzzled( - ClassLoader instrumentationClassLoader, - ClassLoader testApplicationClassLoader, - boolean assertPass, - String muzzleDirective) { - $assertionBody - } + public static void assertInstrumentationMuzzled( + ClassLoader instrumentationClassLoader, + ClassLoader testApplicationClassLoader, + boolean assertPass, + String muzzleDirective) { + $assertionBody } - """.trimIndent() - ) + } + """, + projectPath = "dd-java-agent:agent-tooling", + ) } /** diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt index dc6783a7934..ddce497159c 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt @@ -5,167 +5,157 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.io.File -class TracerVersionIntegrationTest { +class TracerVersionIntegrationTest : VersionPluginsFixture() { @Test - fun `should use default version when not under a git clone`(@TempDir projectDir: File) { - val fixture = VersionPluginsFixture(projectDir) - fixture.assertTracerVersion(expectedVersion = "0.1.0-SNAPSHOT") + fun `should use default version when not under a git clone`() { + assertTracerVersion(expectedVersion = "0.1.0-SNAPSHOT") } @Test - fun `should use default version when no git tags`(@TempDir projectDir: File) { - val fixture = VersionPluginsFixture(projectDir) - fixture.assertTracerVersion( + fun `should use default version when no git tags`() { + assertTracerVersion( expectedVersion = "0.1.0-SNAPSHOT", beforeGradle = { - fixture.initGitRepo() + initGitRepo() }, ) } @Test - fun `should ignore dirtiness when no git tags`(@TempDir projectDir: File) { - val fixture = VersionPluginsFixture(projectDir) - fixture.assertTracerVersion( + fun `should ignore dirtiness when no git tags`() { + assertTracerVersion( expectedVersion = "0.1.0-SNAPSHOT", beforeGradle = { - fixture.initGitRepo() - fixture.settings("\n// uncommitted change this file, ") + initGitRepo() + writeSettings("// uncommitted change this file, ", append = true) }, ) } @Test - fun `should use default version when unmatching git tags`(@TempDir projectDir: File) { - val fixture = VersionPluginsFixture(projectDir) - fixture.assertTracerVersion( + fun `should use default version when unmatching git tags`() { + assertTracerVersion( expectedVersion = "0.1.0-SNAPSHOT", beforeGradle = { - fixture.initGitRepo() - fixture.exec("git", "tag", "something1.40.1", "-m", "Not our tag") + initGitRepo() + exec("git", "tag", "something1.40.1", "-m", "Not our tag") }, ) } @Test - fun `should use exact version when on tag`(@TempDir projectDir: File) { - val fixture = VersionPluginsFixture(projectDir) - fixture.assertTracerVersion( + fun `should use exact version when on tag`() { + assertTracerVersion( expectedVersion = "1.52.0", beforeGradle = { - fixture.initGitRepo() - fixture.exec("git", "tag", "v1.52.0", "-m", "") + initGitRepo() + exec("git", "tag", "v1.52.0", "-m", "") }, ) } @Test - fun `should increment minor and mark dirtiness`(@TempDir projectDir: File) { - val fixture = VersionPluginsFixture(projectDir) - fixture.assertTracerVersion( + fun `should increment minor and mark dirtiness`() { + assertTracerVersion( expectedVersion = "1.53.0-SNAPSHOT-DIRTY", beforeGradle = { - fixture.gradleProperties("tracerVersion.dirtiness=true") - fixture.initGitRepo() - fixture.exec("git", "tag", "v1.52.0", "-m", "") - fixture.settings("\n// uncommitted change this file, ") + writeGradleProperties("tracerVersion.dirtiness=true") + initGitRepo() + exec("git", "tag", "v1.52.0", "-m", "") + writeSettings("// uncommitted change this file, ", append = true) }, ) } @Test - fun `should increment minor with added commits after version tag`(@TempDir projectDir: File) { - val fixture = VersionPluginsFixture(projectDir) - fixture.assertTracerVersion( + fun `should increment minor with added commits after version tag`() { + assertTracerVersion( expectedVersion = "1.53.0-SNAPSHOT", beforeGradle = { - fixture.initGitRepo() - fixture.exec("git", "tag", "v1.52.0", "-m", "") - fixture.settings("\n// Committed change this file, ") - fixture.exec("git", "commit", "-am", "Another commit") + initGitRepo() + exec("git", "tag", "v1.52.0", "-m", "") + writeSettings("// Committed change this file, ", append = true) + exec("git", "commit", "-am", "Another commit") }, ) } @Test - fun `should increment minor with snapshot and dirtiness with added commits after version tag and dirty`(@TempDir projectDir: File) { - val fixture = VersionPluginsFixture(projectDir) - fixture.assertTracerVersion( + fun `should increment minor with snapshot and dirtiness with added commits after version tag and dirty`() { + assertTracerVersion( expectedVersion = "1.53.0-SNAPSHOT-DIRTY", beforeGradle = { - fixture.gradleProperties("tracerVersion.dirtiness=true") - fixture.initGitRepo() - fixture.exec("git", "tag", "v1.52.0", "-m", "") - fixture.settings("\n// uncommitted change ") - fixture.exec("git", "commit", "-am", "Another commit") - fixture.settings("\n// An uncommitted modification") + writeGradleProperties("tracerVersion.dirtiness=true") + initGitRepo() + exec("git", "tag", "v1.52.0", "-m", "") + writeSettings("// uncommitted change ", append = true) + exec("git", "commit", "-am", "Another commit") + writeSettings("// An uncommitted modification", append = true) }, ) } @Test - fun `should increment patch on release branch and no patch release tag`(@TempDir projectDir: File) { - val fixture = VersionPluginsFixture(projectDir) - fixture.assertTracerVersion( + fun `should increment patch on release branch and no patch release tag`() { + assertTracerVersion( expectedVersion = "1.52.1-SNAPSHOT", beforeGradle = { - fixture.initGitRepo() - fixture.exec("git", "tag", "v1.52.0", "-m", "") - fixture.settings("\n// Committed change ") - fixture.exec("git", "commit", "-am", "Another commit") - fixture.exec("git", "switch", "-c", "release/v1.52.x") + initGitRepo() + exec("git", "tag", "v1.52.0", "-m", "") + writeSettings("// Committed change ", append = true) + exec("git", "commit", "-am", "Another commit") + exec("git", "switch", "-c", "release/v1.52.x") }, ) } @Test - fun `should increment patch on release branch and with previous patch release tag`(@TempDir projectDir: File) { - val fixture = VersionPluginsFixture(projectDir) - fixture.assertTracerVersion( + fun `should increment patch on release branch and with previous patch release tag`() { + assertTracerVersion( expectedVersion = "1.52.2-SNAPSHOT", beforeGradle = { - fixture.initGitRepo() - fixture.exec("git", "tag", "v1.52.0", "-m", "") - fixture.exec("git", "switch", "-c", "release/v1.52.x") - fixture.settings("\n// Committed change ") - fixture.exec("git", "commit", "-am", "Another commit") - fixture.exec("git", "tag", "v1.52.1", "-m", "") - fixture.settings("\n// Another committed change ") - fixture.exec("git", "commit", "-am", "Another commit") + initGitRepo() + exec("git", "tag", "v1.52.0", "-m", "") + exec("git", "switch", "-c", "release/v1.52.x") + writeSettings("// Committed change ", append = true) + exec("git", "commit", "-am", "Another commit") + exec("git", "tag", "v1.52.1", "-m", "") + writeSettings("// Another committed change ", append = true) + exec("git", "commit", "-am", "Another commit") }, ) } @Test - fun `should compute version on worktrees`(@TempDir projectDir: File, @TempDir workTreeDir: File) { - val fixture = VersionPluginsFixture(projectDir) - fixture.assertTracerVersion( + fun `should compute version on worktrees`(@TempDir workTreeDir: File) { + assertTracerVersion( expectedVersion = "1.53.0-SNAPSHOT", workingDirectory = workTreeDir, beforeGradle = { - fixture.initGitRepo() - fixture.exec("git", "tag", "v1.52.0", "-m", "") - fixture.exec("git", "commit", "-m", "Initial commit", "--allow-empty") - fixture.exec("git", "worktree", "add", workTreeDir.absolutePath) + initGitRepo() + exec("git", "tag", "v1.52.0", "-m", "") + exec("git", "commit", "-m", "Initial commit", "--allow-empty") + exec("git", "worktree", "add", workTreeDir.absolutePath) + // Write into workTreeDir, not projectDir, so the next commit has changes to pick up. File(workTreeDir, "settings.gradle").appendText("\n// Committed change this file, ") - fixture.exec(workTreeDir, "git", "commit", "-am", "Another commit") + exec(workTreeDir, "git", "commit", "-am", "Another commit") }, ) } - private fun VersionPluginsFixture.assertTracerVersion( + private fun assertTracerVersion( expectedVersion: String, workingDirectory: File? = null, beforeGradle: VersionPluginsFixture.() -> Unit = {}, ) { - settings( + writeSettings( """ rootProject.name = 'test-project' """ ) - rootProject( + writeRootProject( """ plugins { id 'dd-trace-java.tracer-version' @@ -181,7 +171,7 @@ class TracerVersionIntegrationTest { beforeGradle() - val buildResult = run("printVersion", "--quiet", projectDir = workingDirectory ?: projectDir) + val buildResult = run("printVersion", "--quiet", projectDir = workingDirectory) assertThat(buildResult.output.lines().first()).isEqualTo(expectedVersion) } diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/VersionPluginsFixture.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/VersionPluginsFixture.kt index 2ffee2a3a91..e33bc75cf40 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/VersionPluginsFixture.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/VersionPluginsFixture.kt @@ -4,8 +4,7 @@ import datadog.gradle.plugin.GradleFixture import java.io.File import java.io.IOException -internal class VersionPluginsFixture(projectDir: File) : GradleFixture(projectDir) { - +open class VersionPluginsFixture : GradleFixture() { fun exec(workingDirectory: File, vararg args: String) { val exitCode = ProcessBuilder() .command(*args) @@ -28,7 +27,7 @@ internal class VersionPluginsFixture(projectDir: File) : GradleFixture(projectDi exec(workingDirectory, "git", "commit", "-m", "A commit") } - val generatedVersionFile = file("build/generated/version/my-lib.version", false) + val generatedVersionFile: File get() = file("build/generated/version/my-lib.version") - val builtResourceVersionFile = file("build/resources/main/my-lib.version", false) + val builtResourceVersionFile: File get() = file("build/resources/main/my-lib.version") } diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/WriteVersionFilePluginTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/WriteVersionFilePluginTest.kt index 3dff7989ee9..1cf21c016ba 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/WriteVersionFilePluginTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/WriteVersionFilePluginTest.kt @@ -4,15 +4,12 @@ import org.assertj.core.api.Assertions.assertThat import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.TaskOutcome import org.junit.jupiter.api.Test -import org.junit.jupiter.api.io.TempDir -import java.io.File -class WriteVersionFilePluginTest { +class WriteVersionFilePluginTest : VersionPluginsFixture() { @Test - fun `writes version file in version~hash format`(@TempDir projectDir: File) { - val fixture = VersionPluginsFixture(projectDir) - fixture.assertVersionFile( + fun `writes version file in version~hash format`() { + assertVersionFile( expectedContentRegex = "1\\.2\\.3~[0-9a-f]+", beforeGradle = { initGitRepo() @@ -21,37 +18,37 @@ class WriteVersionFilePluginTest { } @Test - fun `version and gitHash properties can be overridden`(@TempDir projectDir: File) { - val fixture = VersionPluginsFixture(projectDir) - fixture.assertVersionFile( + fun `version and gitHash properties can be overridden`() { + assertVersionFile( expectedContentRegex = "9.9.9~deadbeef", beforeGradle = { - rootProject( + writeRootProject( """ tasks.named('writeVersionNumberFile', datadog.gradle.plugin.version.WriteVersionFile).configure { version.set('9.9.9') gitHash.set('deadbeef') } - """ + """, + append = true, ) }, ) } @Test - fun `task overwrites existing version file`(@TempDir projectDir: File) { - val fixture = VersionPluginsFixture(projectDir) - fixture.assertVersionFile( + fun `task overwrites existing version file`() { + assertVersionFile( expectedContentRegex = "1.2.3~abc12345", beforeGradle = { - rootProject( + writeRootProject( """ tasks.named('writeVersionNumberFile', datadog.gradle.plugin.version.WriteVersionFile).configure { gitHash.set('abc12345') } - """ + """, + append = true, ) generatedVersionFile.run { parentFile.mkdirs() @@ -62,83 +59,83 @@ class WriteVersionFilePluginTest { } @Test - fun `version file generation is wired into main resources`(@TempDir projectDir: File) { - val fixture = VersionPluginsFixture(projectDir) - fixture.assertVersionFile( + fun `version file generation is wired into main resources`() { + assertVersionFile( expectedContentRegex = "1.2.3~abc12345", task = "processResources", beforeGradle = { - rootProject( + writeRootProject( """ tasks.named('writeVersionNumberFile', datadog.gradle.plugin.version.WriteVersionFile).configure { gitHash.set('abc12345') } - """ + """, + append = true, ) }, ) - assertThat(fixture.builtResourceVersionFile).exists() + assertThat(builtResourceVersionFile).exists() } @Test - fun `task is up-to-date on second run`(@TempDir projectDir: File) { - val fixture = VersionPluginsFixture(projectDir) - fixture.assertVersionFile( + fun `task is up-to-date on second run`() { + assertVersionFile( expectedContentRegex = "1.2.3~abc12345", beforeGradle = { - rootProject( + writeRootProject( """ tasks.named('writeVersionNumberFile', datadog.gradle.plugin.version.WriteVersionFile).configure { gitHash.set('abc12345') } - """ + """, + append = true, ) }, ) - val result = fixture.run("writeVersionNumberFile") + val result = run("writeVersionNumberFile") assertThat(result.task(":writeVersionNumberFile")?.outcome).isEqualTo(TaskOutcome.UP_TO_DATE) } @Test - fun `clean deletes version file`(@TempDir projectDir: File) { - val fixture = VersionPluginsFixture(projectDir) - fixture.assertVersionFile( + fun `clean deletes version file`() { + assertVersionFile( expectedContentRegex = "1.2.3~abc12345", beforeGradle = { - rootProject( + writeRootProject( """ tasks.named('writeVersionNumberFile', datadog.gradle.plugin.version.WriteVersionFile).configure { gitHash.set('abc12345') } - """ + """, + append = true, ) }, ) - val versionFile = fixture.generatedVersionFile + val versionFile = generatedVersionFile - val result = fixture.run("clean") + val result = run("clean") assertThat(result.task(":clean")?.outcome).isEqualTo(TaskOutcome.SUCCESS) assertThat(versionFile).doesNotExist() } - private fun VersionPluginsFixture.assertVersionFile( + private fun assertVersionFile( expectedContentRegex: String, task: String = ":writeVersionNumberFile", beforeGradle: VersionPluginsFixture.() -> Unit = {}, ): BuildResult { - settings( + writeSettings( """ rootProject.name = 'my-lib' """ ) - rootProject( + writeRootProject( """ plugins { id 'dd-trace-java.version-file' From b2fcbd9b0ab01ccea44b0407ab6fec4232306fb6 Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Thu, 14 May 2026 14:25:13 -0400 Subject: [PATCH 3/5] Refactored to use Kotlin DSL in tests scripts. --- .../datadog/gradle/plugin/GradleFixture.kt | 45 ++----- .../ParseV2SupportedConfigurationsTest.kt | 14 +- .../csi/CallSiteInstrumentationPluginTest.kt | 16 +-- .../dump/DumpHangedTestIntegrationTest.kt | 17 +-- .../BuildTimeInstrumentationPluginTest.kt | 30 ++--- .../muzzle/MuzzlePluginFunctionalTest.kt | 124 +++++++++--------- .../muzzle/MuzzlePluginPerformanceTest.kt | 42 +++--- .../plugin/muzzle/MuzzlePluginTestFixture.kt | 10 +- .../version/TracerVersionIntegrationTest.kt | 10 +- .../version/WriteVersionFilePluginTest.kt | 28 ++-- 10 files changed, 155 insertions(+), 181 deletions(-) diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt index 45d39933371..fb69e36db8d 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt @@ -32,27 +32,6 @@ open class GradleFixture { } } - // Configure Gradle to use as few resources as possible: - // - Xms64m -Xmx256m: consume minimum amount of RAM. - // - workers.max=1: don't let the daemon fan out into multiple Worker JVMs. - // - parallel=false: serialize task execution within the fixture build. - // Re-applied if missing. - private fun applyResourceLimits() { - val gradleProperties = file("gradle.properties") - if (gradleProperties.exists() && gradleProperties.readText().contains("org.gradle.jvmargs=-Xms64m -Xmx256m")) { - return - } - - writeFile("gradle.properties", - """ - org.gradle.jvmargs=-Xms64m -Xmx256m - org.gradle.workers.max=1 - org.gradle.parallel=false - """, - append = true, - ) - } - /** * Runs Gradle with the specified arguments. * @@ -75,8 +54,6 @@ open class GradleFixture { forwardOutput: Boolean = false, projectDir: File? = null, ): BuildResult { - applyResourceLimits() - val runner = GradleRunner.create() .withTestKitDir(testKitDir) .withPluginClasspath() @@ -167,15 +144,15 @@ open class GradleFixture { } /** - * Adds a subproject to the build by appending an `include` line to settings.gradle - * and writing the subproject's build.gradle. + * Adds a subproject to the build by appending an `include` line to settings.gradle.kts + * and writing the subproject's build.gradle.kts. * * @param projectPath The project path (e.g., "dd-java-agent:instrumentation:other") * @param buildScript The build script content for the subproject */ - fun addSubproject(projectPath: String, @Language("Groovy") buildScript: String) { - writeFile("settings.gradle", "include ':$projectPath'", append = true) - writeFile("${projectPath.replace(':', '/')}/build.gradle", buildScript) + fun addSubproject(projectPath: String, @Language("kotlin") buildScript: String) { + writeFile("settings.gradle.kts", """include(":$projectPath")""", append = true) + writeFile("${projectPath.replace(':', '/')}/build.gradle.kts", buildScript) } /** @@ -211,22 +188,22 @@ open class GradleFixture { writeFile("gradle.properties", content, append) /** - * Writes the root project's build.gradle file. + * Writes the root project's build.gradle.kts file. * * @param buildScript The build script content for the root project * @param append If true, appends to any existing file instead of overwriting */ - fun writeRootProject(@Language("Groovy") buildScript: String, append: Boolean = false): File = - writeFile("build.gradle", buildScript, append) + fun writeRootProject(@Language("kotlin") buildScript: String, append: Boolean = false): File = + writeFile("build.gradle.kts", buildScript, append) /** - * Writes the root project's settings.gradle file. + * Writes the root project's settings.gradle.kts file. * * @param settingsScript The settings script content * @param append If true, appends to any existing file instead of overwriting */ - fun writeSettings(@Language("Groovy") settingsScript: String, append: Boolean = false): File = - writeFile("settings.gradle", settingsScript, append) + fun writeSettings(@Language("kotlin") settingsScript: String, append: Boolean = false): File = + writeFile("settings.gradle.kts", settingsScript, append) /** * Parses an XML file into a DOM Document. diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt index 5c437666d4b..b5f46e15932 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt @@ -126,23 +126,23 @@ class ParseV2SupportedConfigurationsTest : GradleFixture() { private fun setupGradleProject() { writeSettings( """ - rootProject.name = 'test-config-project' + rootProject.name = "test-config-project" """ ) writeRootProject( """ plugins { - id 'java' - id 'dd-trace-java.supported-config-generator' + id("java") + id("dd-trace-java.supported-config-generator") } - group = 'datadog.config.test' + group = "datadog.config.test" supportedTracerConfigurations { - jsonFile.set(file('test-supported-configurations.json')) - destinationDirectory.set(file('build/generated/supportedConfigurations')) - className.set('datadog.test.TestGeneratedSupportedConfigurations') + jsonFile.set(file("test-supported-configurations.json")) + destinationDirectory.set(file("build/generated/supportedConfigurations")) + className.set("datadog.test.TestGeneratedSupportedConfigurations") } """ ) diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPluginTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPluginTest.kt index d1a8716e122..c65742f2c83 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPluginTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPluginTest.kt @@ -11,8 +11,8 @@ import java.nio.file.Files class CallSiteInstrumentationPluginTest : GradleFixture() { private val buildGradle = """ plugins { - id 'java' - id 'dd-trace-java.call-site-instrumentation' + id("java") + id("dd-trace-java.call-site-instrumentation") } java { @@ -21,9 +21,8 @@ class CallSiteInstrumentationPluginTest : GradleFixture() { } csi { - suffix = 'CallSite' - targetFolder = project.layout.buildDirectory.dir('csi') - rootFolder = file('__ROOT_FOLDER__') + suffix.set("CallSite") + targetFolder.set(project.layout.buildDirectory.dir("csi")) } repositories { @@ -31,8 +30,8 @@ class CallSiteInstrumentationPluginTest : GradleFixture() { } dependencies { - implementation group: 'net.bytebuddy', name: 'byte-buddy', version: '1.18.8' - implementation group: 'com.google.auto.service', name: 'auto-service-annotations', version: '1.1.1' + implementation("net.bytebuddy:byte-buddy:1.18.8") + implementation("com.google.auto.service:auto-service-annotations:1.1.1") } """ @@ -98,8 +97,7 @@ class CallSiteInstrumentationPluginTest : GradleFixture() { testCallSiteJarDir.toPath().resolve(callSiteJar.name) ) - val gradleFileContent = gradleFile.replace("__ROOT_FOLDER__", projectFolder.toString().replace("\\", "\\\\")) - writeRootProject(gradleFileContent) + writeRootProject(gradleFile) val advicePackage = parsePackage(advice) val adviceClassName = parseClassName(advice) diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/dump/DumpHangedTestIntegrationTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/dump/DumpHangedTestIntegrationTest.kt index 066cd13dc34..78279142b40 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/dump/DumpHangedTestIntegrationTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/dump/DumpHangedTestIntegrationTest.kt @@ -38,27 +38,28 @@ class DumpHangedTestIntegrationTest : GradleFixture() { } private fun runGradleTest(testSleepMillis: Long): List { - writeSettings("""rootProject.name = 'test-project'""") + writeSettings("""rootProject.name = "test-project"""") writeRootProject( """ import java.time.Duration + import org.gradle.api.tasks.testing.Test plugins { - id 'java' - id 'dd-trace-java.dump-hanged-test' + id("java") + id("dd-trace-java.dump-hanged-test") } - group = 'datadog.dump.test' + group = "datadog.dump.test" repositories { mavenCentral() } dependencies { - testImplementation platform('org.junit:junit-bom:5.10.0') - testImplementation 'org.junit.jupiter:junit-jupiter' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") } dumpHangedTest { @@ -66,7 +67,7 @@ class DumpHangedTestIntegrationTest : GradleFixture() { dumpOffset.set(5) } - tasks.withType(Test).configureEach { + tasks.withType().configureEach { // Set test timeout after 20 seconds. timeout.set(Duration.ofSeconds(20)) diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/BuildTimeInstrumentationPluginTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/BuildTimeInstrumentationPluginTest.kt index 9502c6a4f20..9bbcb7bf386 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/BuildTimeInstrumentationPluginTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/BuildTimeInstrumentationPluginTest.kt @@ -16,8 +16,8 @@ class BuildTimeInstrumentationPluginTest : GradleFixture() { private val buildGradle = """ plugins { - id 'java' - id 'dd-trace-java.build-time-instrumentation' + id("java") + id("dd-trace-java.build-time-instrumentation") } java { @@ -30,12 +30,10 @@ class BuildTimeInstrumentationPluginTest : GradleFixture() { } dependencies { - compileOnly group: 'net.bytebuddy', name: 'byte-buddy', version: '1.18.8' // just to build TestPlugin + compileOnly("net.bytebuddy:byte-buddy:1.18.8") // just to build TestPlugin } - buildTimeInstrumentation.plugins = [ - 'TestPlugin' - ] + buildTimeInstrumentation.plugins.set(listOf("TestPlugin")) """ private val exampleCode = """ @@ -59,8 +57,8 @@ class BuildTimeInstrumentationPluginTest : GradleFixture() { writeRootProject( """ plugins { - id 'java' - id 'dd-trace-java.build-time-instrumentation' + id("java") + id("dd-trace-java.build-time-instrumentation") } java { @@ -73,12 +71,12 @@ class BuildTimeInstrumentationPluginTest : GradleFixture() { } dependencies { - compileOnly group: 'net.bytebuddy', name: 'byte-buddy', version: '1.18.8' + compileOnly("net.bytebuddy:byte-buddy:1.18.8") } buildTimeInstrumentation { - plugins = ['TestPlugin'] - includeClassDirectories.from(file('external-classes')) + plugins.set(listOf("TestPlugin")) + includeClassDirectories.from(file("external-classes")) } """ ) @@ -100,8 +98,8 @@ class BuildTimeInstrumentationPluginTest : GradleFixture() { writeRootProject( """ plugins { - id 'java' - id 'dd-trace-java.build-time-instrumentation' + id("java") + id("dd-trace-java.build-time-instrumentation") } java { @@ -114,12 +112,12 @@ class BuildTimeInstrumentationPluginTest : GradleFixture() { } dependencies { - compileOnly group: 'net.bytebuddy', name: 'byte-buddy', version: '1.18.8' + compileOnly("net.bytebuddy:byte-buddy:1.18.8") } buildTimeInstrumentation { - plugins = ['TestPlugin'] - includeClassDirectories.from(file('external-classes')) + plugins.set(listOf("TestPlugin")) + includeClassDirectories.from(file("external-classes")) } """ ) diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt index 3b6f50b1b34..b09bca95c1e 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt @@ -14,8 +14,8 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { @@ -29,8 +29,8 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { // Add runMuzzle aggregator task at root level (like in dd-trace-java.ci-jobs.gradle.kts) writeRootProject( """ - tasks.register('runMuzzle') { - dependsOn(':dd-java-agent:instrumentation:demo:muzzle') + tasks.register("runMuzzle") { + dependsOn(":dd-java-agent:instrumentation:demo:muzzle") } """ ) @@ -54,13 +54,13 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { pass { - name = 'expected-pass' + name = "expected-pass" coreJdk() } } @@ -97,8 +97,8 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } """ ) @@ -129,8 +129,8 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { @@ -151,8 +151,8 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } """ ) @@ -177,8 +177,8 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { @@ -235,14 +235,14 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } // Gradle repositories for artifact download repositories { maven { - url = uri('${mavenRepoFixture.repoUrl}') + url = uri("${mavenRepoFixture.repoUrl}") metadataSources { mavenPom() artifact() @@ -253,9 +253,9 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { muzzle { pass { - group = 'com.example.test' - module = 'demo-lib' - versions = '[1.0.0,2.0.0)' // Should resolve 1.0.0, 1.1.0, 1.2.0 but NOT 2.0.0 + group = "com.example.test" + module = "demo-lib" + versions = "[1.0.0,2.0.0)" // Should resolve 1.0.0, 1.1.0, 1.2.0 but NOT 2.0.0 } } """ @@ -307,13 +307,13 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { pass { - name = 'my-custom-check' + name = "my-custom-check" coreJdk() } } @@ -346,15 +346,15 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { pass { - group = 'com.example.nonexistent' - module = 'does-not-exist' - versions = '[1.0.0,2.0.0)' + group = "com.example.nonexistent" + module = "does-not-exist" + versions = "[1.0.0,2.0.0)" } } """ @@ -382,8 +382,8 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { @@ -421,8 +421,8 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { @@ -471,13 +471,13 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } repositories { maven { - url = uri('${mavenRepoFixture.repoUrl}') + url = uri("${mavenRepoFixture.repoUrl}") metadataSources { mavenPom() artifact() @@ -488,7 +488,7 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { muzzle { pass { coreJdk() - extraDependency('com.example.extra:extra-lib:1.0.0') + extraDependency("com.example.extra:extra-lib:1.0.0") } } """ @@ -562,13 +562,13 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } repositories { maven { - url = uri('${mavenRepoFixture.repoUrl}') + url = uri("${mavenRepoFixture.repoUrl}") metadataSources { mavenPom() artifact() @@ -579,10 +579,10 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { muzzle { pass { - group = 'com.example.test' - module = 'with-transitive' - versions = '1.0.0' - excludeDependency('com.google.guava:guava') + group = "com.example.test" + module = "with-transitive" + versions = "1.0.0" + excludeDependency("com.google.guava:guava") } } """ @@ -619,11 +619,11 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'dd-trace-java.muzzle' + id("dd-trace-java.muzzle") } // applied after muzzle plugin - apply plugin: 'java' + apply(plugin = "java") muzzle { pass { @@ -649,14 +649,14 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' apply false // Declared but not applied + id("java") + id("dd-trace-java.muzzle") apply false // Declared but not applied } // Apply muzzle plugin after java using imperative syntax - apply plugin: 'dd-trace-java.muzzle' + apply(plugin = "dd-trace-java.muzzle") - muzzle { + extensions.configure("muzzle") { pass { coreJdk() } @@ -680,7 +680,7 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'dd-trace-java.muzzle' + id("dd-trace-java.muzzle") // NO java plugin applied } @@ -705,11 +705,11 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { @Test fun `missing dd-java-agent projects error handling`() { - // Create a minimal settings.gradle without the dd-java-agent structure + // Create a minimal settings.gradle.kts without the dd-java-agent structure writeSettings( """ - rootProject.name = 'muzzle-test' - include ':instrumentation:demo' + rootProject.name = "muzzle-test" + include(":instrumentation:demo") """ ) @@ -717,8 +717,8 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { "instrumentation:demo", """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { @@ -757,14 +757,14 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } // Gradle repositories for artifact download repositories { maven { - url = uri('${mavenRepoFixture.repoUrl}') + url = uri("${mavenRepoFixture.repoUrl}") metadataSources { mavenPom() artifact() @@ -774,9 +774,9 @@ class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { muzzle { pass { - group = 'com.example.test' - module = 'inverse-lib' - versions = '[2.0.0,3.0.0]' + group = "com.example.test" + module = "inverse-lib" + versions = "[2.0.0,3.0.0]" assertInverse = true } } diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginPerformanceTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginPerformanceTest.kt index 30b17879f55..2e43b0b3d38 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginPerformanceTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginPerformanceTest.kt @@ -12,15 +12,15 @@ class MuzzlePluginPerformanceTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { pass { - group = 'com.example.test' - module = 'some-lib' - versions = '[1.0.0,2.0.0)' + group = "com.example.test" + module = "some-lib" + versions = "[1.0.0,2.0.0)" } } """ @@ -48,8 +48,8 @@ class MuzzlePluginPerformanceTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { pass { coreJdk() } @@ -60,8 +60,8 @@ class MuzzlePluginPerformanceTest : MuzzlePluginTestFixture() { addSubproject("dd-java-agent:instrumentation:other", """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { pass { coreJdk() } @@ -99,13 +99,13 @@ class MuzzlePluginPerformanceTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } repositories { maven { - url = uri('${mavenRepoFixture.repoUrl}') + url = uri("${mavenRepoFixture.repoUrl}") metadataSources { mavenPom() artifact() @@ -115,9 +115,9 @@ class MuzzlePluginPerformanceTest : MuzzlePluginTestFixture() { muzzle { pass { - group = 'com.example.test' - module = 'example-lib' - versions = '[1.0.0,2.0.0)' + group = "com.example.test" + module = "example-lib" + versions = "[1.0.0,2.0.0)" } } """ @@ -201,8 +201,8 @@ class MuzzlePluginPerformanceTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { @@ -265,8 +265,8 @@ class MuzzlePluginPerformanceTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { @@ -329,8 +329,8 @@ class MuzzlePluginPerformanceTest : MuzzlePluginTestFixture() { writeProject( """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginTestFixture.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginTestFixture.kt index 646dfa9f46a..ba9945cbe4a 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginTestFixture.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginTestFixture.kt @@ -16,27 +16,27 @@ open class MuzzlePluginTestFixture : GradleFixture() { * Writes the basic Gradle project structure for muzzle testing. * Creates a multi-project build with agent-bootstrap, agent-tooling, and instrumentation modules. */ - fun writeProject(@Language("Groovy") instrumentationBuildScript: String) { + fun writeProject(@Language("kotlin") instrumentationBuildScript: String) { writeSettings( """ - rootProject.name = 'muzzle-e2e' + rootProject.name = "muzzle-e2e" """ ) addSubproject("dd-java-agent:agent-bootstrap", """ plugins { - id 'java' + id("java") } - tasks.register('compileMain_java11Java') + tasks.register("compileMain_java11Java") """ ) addSubproject("dd-java-agent:agent-tooling", """ plugins { - id 'java' + id("java") } """ ) diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt index ddce497159c..ebcc7f36e12 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt @@ -138,7 +138,7 @@ class TracerVersionIntegrationTest : VersionPluginsFixture() { exec("git", "commit", "-m", "Initial commit", "--allow-empty") exec("git", "worktree", "add", workTreeDir.absolutePath) // Write into workTreeDir, not projectDir, so the next commit has changes to pick up. - File(workTreeDir, "settings.gradle").appendText("\n// Committed change this file, ") + File(workTreeDir, "settings.gradle.kts").appendText("\n// Committed change this file, ") exec(workTreeDir, "git", "commit", "-am", "Another commit") }, ) @@ -151,21 +151,21 @@ class TracerVersionIntegrationTest : VersionPluginsFixture() { ) { writeSettings( """ - rootProject.name = 'test-project' + rootProject.name = "test-project" """ ) writeRootProject( """ plugins { - id 'dd-trace-java.tracer-version' + id("dd-trace-java.tracer-version") } - tasks.register('printVersion') { + tasks.register("printVersion") { logger.quiet(project.version.toString()) } - group = 'datadog.tracer.version.test' + group = "datadog.tracer.version.test" """ ) diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/WriteVersionFilePluginTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/WriteVersionFilePluginTest.kt index 1cf21c016ba..f1bb1395b16 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/WriteVersionFilePluginTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/WriteVersionFilePluginTest.kt @@ -25,9 +25,9 @@ class WriteVersionFilePluginTest : VersionPluginsFixture() { writeRootProject( """ - tasks.named('writeVersionNumberFile', datadog.gradle.plugin.version.WriteVersionFile).configure { - version.set('9.9.9') - gitHash.set('deadbeef') + tasks.named("writeVersionNumberFile") { + version.set("9.9.9") + gitHash.set("deadbeef") } """, append = true, @@ -44,8 +44,8 @@ class WriteVersionFilePluginTest : VersionPluginsFixture() { writeRootProject( """ - tasks.named('writeVersionNumberFile', datadog.gradle.plugin.version.WriteVersionFile).configure { - gitHash.set('abc12345') + tasks.named("writeVersionNumberFile") { + gitHash.set("abc12345") } """, append = true, @@ -67,8 +67,8 @@ class WriteVersionFilePluginTest : VersionPluginsFixture() { writeRootProject( """ - tasks.named('writeVersionNumberFile', datadog.gradle.plugin.version.WriteVersionFile).configure { - gitHash.set('abc12345') + tasks.named("writeVersionNumberFile") { + gitHash.set("abc12345") } """, append = true, @@ -87,8 +87,8 @@ class WriteVersionFilePluginTest : VersionPluginsFixture() { writeRootProject( """ - tasks.named('writeVersionNumberFile', datadog.gradle.plugin.version.WriteVersionFile).configure { - gitHash.set('abc12345') + tasks.named("writeVersionNumberFile") { + gitHash.set("abc12345") } """, append = true, @@ -109,8 +109,8 @@ class WriteVersionFilePluginTest : VersionPluginsFixture() { writeRootProject( """ - tasks.named('writeVersionNumberFile', datadog.gradle.plugin.version.WriteVersionFile).configure { - gitHash.set('abc12345') + tasks.named("writeVersionNumberFile") { + gitHash.set("abc12345") } """, append = true, @@ -132,16 +132,16 @@ class WriteVersionFilePluginTest : VersionPluginsFixture() { ): BuildResult { writeSettings( """ - rootProject.name = 'my-lib' + rootProject.name = "my-lib" """ ) writeRootProject( """ plugins { - id 'dd-trace-java.version-file' + id("dd-trace-java.version-file") } - version = '1.2.3' + version = "1.2.3" """ ) beforeGradle() From 461f6160db0a1a06aeeccec87144d800d110c8be Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Thu, 14 May 2026 18:04:41 -0400 Subject: [PATCH 4/5] Share TestKit daemon across GradleFixture tests. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Groovy→Kotlin DSL conversion of test scripts pushed :buildSrc:test from 6 min to ~11 min because every test method killed its TestKit daemon, forcing kotlinc to recompile every .gradle.kts in the next test. Move testKitDir into a JVM-wide companion-object lazy val, remove the per-test stopDaemons() call, and reap daemons once from the shutdown hook. Shared daemons cut :buildSrc:test from 10 min 40 s down to 1 min 36 s on Kotlin DSL (and ~3.8× faster than the original Groovy DSL runtime). Fix MuzzleMavenRepoUtils.MUZZLE_REPOS, which cached MAVEN_REPOSITORY_PROXY in a daemon-static `by lazy` val. With per-test daemons the cache was harmless; with daemon reuse it leaked a stale repo URL into the next test's resolution and caused version-range queries to fail. Replace it with defaultMuzzleRepos(), a function that reads the env on each call. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../plugin/muzzle/MuzzleMavenRepoUtils.kt | 13 +- .../datadog/gradle/plugin/GradleFixture.kt | 151 ++++++++++-------- .../gradle/plugin/muzzle/RangeQueryTest.kt | 2 +- 3 files changed, 90 insertions(+), 76 deletions(-) diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleMavenRepoUtils.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleMavenRepoUtils.kt index 998e0357b18..af25b681f80 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleMavenRepoUtils.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleMavenRepoUtils.kt @@ -20,13 +20,16 @@ import java.nio.file.Files internal object MuzzleMavenRepoUtils { /** - * Remote repositories used to query version ranges and fetch dependencies + * Remote repositories used to query version ranges and fetch dependencies. + * + * This intentionally reads the environment on each access: Gradle daemons can + * be reused across builds with different MAVEN_REPOSITORY_PROXY values. */ @JvmStatic - val MUZZLE_REPOS: List by lazy { + fun defaultMuzzleRepos(): List { val central = RemoteRepository.Builder("central", "default", "https://repo1.maven.org/maven2/").build() val mavenProxyUrl = System.getenv("MAVEN_REPOSITORY_PROXY") - if (mavenProxyUrl == null) { + return if (mavenProxyUrl == null) { listOf(central) } else { val proxy = RemoteRepository.Builder("central-proxy", "default", mavenProxyUrl).build() @@ -70,7 +73,7 @@ internal object MuzzleMavenRepoUtils { muzzleDirective: MuzzleDirective, system: RepositorySystem, session: RepositorySystemSession, - defaultRepos: List = MUZZLE_REPOS + defaultRepos: List = defaultMuzzleRepos() ): Set { val allVersionsArtifact = DefaultArtifact( muzzleDirective.group, @@ -124,7 +127,7 @@ internal object MuzzleMavenRepoUtils { muzzleDirective: MuzzleDirective, system: RepositorySystem, session: RepositorySystemSession, - defaultRepos: List = MUZZLE_REPOS + defaultRepos: List = defaultMuzzleRepos() ): VersionRangeResult { val directiveArtifact: Artifact = DefaultArtifact( muzzleDirective.group, diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt index fb69e36db8d..6e8a6bb805a 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt @@ -18,26 +18,93 @@ open class GradleFixture { @TempDir protected lateinit var projectDir: File - // Each fixture gets its own testkit dir in the system temp directory (NOT under - // projectDir) so that JUnit's @TempDir cleanup doesn't race with daemon file locks. - // See https://github.com/gradle/gradle/issues/12535 - // A fresh daemon is started per test — ensuring withEnvironment() vars (e.g. - // MAVEN_REPOSITORY_PROXY) are correctly set on the daemon JVM and not inherited - // from a previously-started daemon with a different test's environment. - // A JVM shutdown hook removes the directory after all tests have run (and daemons - // have been stopped), so file locks are guaranteed to be released by then. - private val testKitDir: File by lazy { - Files.createTempDirectory("gradle-testkit-").toFile().also { dir -> - Runtime.getRuntime().addShutdownHook(Thread { dir.deleteRecursively() }) + private val testKitDir: File get() = sharedTestKitDir + + companion object { + // JVM-wide testkit dir shared across all GradleFixture instances. One daemon + // pool serves every test method, so kotlinc work on .gradle.kts scripts is + // amortized instead of re-paid per test (recovers the +77 % wall-time + // regression introduced by the Groovy→Kotlin DSL conversion). + // + // Lives outside any @TempDir so JUnit cleanup never races with daemon file + // locks. See https://github.com/gradle/gradle/issues/12535 + // + // TestKit may reuse the same daemon for builds with different withEnvironment() + // values, so build logic must not cache environment-derived state in daemon- + // static fields. + private val sharedTestKitDir: File by lazy { + Files.createTempDirectory("gradle-testkit-").toFile().also { dir -> + Runtime.getRuntime().addShutdownHook(Thread { + stopDaemonsIn(dir) + dir.deleteRecursively() + }) + } + } + + /** + * Kills Gradle daemons started by TestKit under the given testkit dir. + * + * The Gradle Tooling API (used by [GradleRunner]) always spawns a daemon and + * provides no public API to stop it (https://github.com/gradle/gradle/issues/12535). + * We replicate the strategy Gradle uses in its own integration tests + * ([DaemonLogsAnalyzer.killAll()][1]): + * + * 1. Scan `/daemon//` for log files matching + * `DaemonLogConstants.DAEMON_LOG_PREFIX + pid + DaemonLogConstants.DAEMON_LOG_SUFFIX`, + * i.e. `daemon-.out.log`. + * 2. Extract the PID from the filename and kill the process. + * + * Trade-offs of the PID-from-filename approach: + * - **PID recycling**: between the build finishing and `kill` being sent, the OS + * could theoretically recycle the PID. Now that this only runs at JVM exit + * (no longer per-test), the window is short — when called from the shutdown + * hook all daemons we own are still alive — so the risk remains negligible. + * - **Filename convention is internal**: Gradle's `DaemonLogConstants.DAEMON_LOG_PREFIX` + * (`"daemon-"`) / `DAEMON_LOG_SUFFIX` (`".out.log"`) are not public API; a future + * Gradle version could change them. The `toLongOrNull()` guard safely skips entries + * that don't parse as a PID (including the UUID fallback Gradle uses when the PID + * is unavailable). + * - **Java 8 compatible**: uses `kill`/`taskkill` via [ProcessBuilder] instead of + * `ProcessHandle` (Java 9+) because build logic targets JVM 1.8. + * + * [1]: https://github.com/gradle/gradle/blob/43b381d88/testing/internal-distribution-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonLogsAnalyzer.groovy + */ + private fun stopDaemonsIn(testKitDir: File) { + val daemonDir = File(testKitDir, "daemon") + if (!daemonDir.exists()) return + + daemonDir.walkTopDown() + .filter { it.isFile && it.name.endsWith(".out.log") && !it.name.startsWith("hs_err") } + .forEach { logFile -> + val pid = logFile.nameWithoutExtension // daemon-12345.out + .removeSuffix(".out") // daemon-12345 + .removePrefix("daemon-") // 12345 + .toLongOrNull() ?: return@forEach // skip UUIDs / unparseable names + + val isWindows = System.getProperty("os.name").lowercase().contains("win") + val killProcess = if (isWindows) { + ProcessBuilder("taskkill", "/F", "/PID", pid.toString()) + } else { + ProcessBuilder("kill", pid.toString()) + } + try { + val process = killProcess.redirectErrorStream(true).start() + process.waitFor(5, java.util.concurrent.TimeUnit.SECONDS) + } catch (_: Exception) { + // best effort — daemon may already be stopped + } + } } } /** * Runs Gradle with the specified arguments. * - * After the build completes, any Gradle daemons started by TestKit are killed - * so their file locks on the testkit cache are released before JUnit `@TempDir` - * cleanup. See https://github.com/gradle/gradle/issues/12535 + * The TestKit daemon spawned by the first call is reused for every subsequent + * call in the JVM (shared [testKitDir]) so kotlinc compilation of + * `.gradle.kts` scripts amortizes across tests instead of being re-paid per + * test. Daemons are reaped at JVM shutdown by the hook registered when + * [sharedTestKitDir] is created. * * @param args Gradle task names and arguments * @param expectFailure Whether the build is expected to fail @@ -68,65 +135,9 @@ open class GradleFixture { if (expectFailure) runner.buildAndFail() else runner.build() } catch (e: UnexpectedBuildResultException) { e.buildResult - } finally { - stopDaemons() } } - /** - * Kills Gradle daemons started by TestKit for this fixture's testkit dir. - * - * The Gradle Tooling API (used by [GradleRunner]) always spawns a daemon and - * provides no public API to stop it (https://github.com/gradle/gradle/issues/12535). - * We replicate the strategy Gradle uses in its own integration tests - * ([DaemonLogsAnalyzer.killAll()][1]): - * - * 1. Scan `/daemon//` for log files matching - * `DaemonLogConstants.DAEMON_LOG_PREFIX + pid + DaemonLogConstants.DAEMON_LOG_SUFFIX`, - * i.e. `daemon-.out.log`. - * 2. Extract the PID from the filename and kill the process. - * - * Trade-offs of the PID-from-filename approach: - * - **PID recycling**: between the build finishing and `kill` being sent, the OS - * could theoretically recycle the PID. In practice the window is short - * (the `finally` block runs immediately after the build) so the risk is negligible. - * - **Filename convention is internal**: Gradle's `DaemonLogConstants.DAEMON_LOG_PREFIX` - * (`"daemon-"`) / `DAEMON_LOG_SUFFIX` (`".out.log"`) are not public API; a future - * Gradle version could change them. The `toLongOrNull()` guard safely skips entries - * that don't parse as a PID (including the UUID fallback Gradle uses when the PID - * is unavailable). - * - **Java 8 compatible**: uses `kill`/`taskkill` via [ProcessBuilder] instead of - * `ProcessHandle` (Java 9+) because build logic targets JVM 1.8. - * - * [1]: https://github.com/gradle/gradle/blob/43b381d88/testing/internal-distribution-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/DaemonLogsAnalyzer.groovy - */ - private fun stopDaemons() { - val daemonDir = File(testKitDir, "daemon") - if (!daemonDir.exists()) return - - daemonDir.walkTopDown() - .filter { it.isFile && it.name.endsWith(".out.log") && !it.name.startsWith("hs_err") } - .forEach { logFile -> - val pid = logFile.nameWithoutExtension // daemon-12345.out - .removeSuffix(".out") // daemon-12345 - .removePrefix("daemon-") // 12345 - .toLongOrNull() ?: return@forEach // skip UUIDs / unparseable names - - val isWindows = System.getProperty("os.name").lowercase().contains("win") - val killProcess = if (isWindows) { - ProcessBuilder("taskkill", "/F", "/PID", pid.toString()) - } else { - ProcessBuilder("kill", pid.toString()) - } - try { - val process = killProcess.redirectErrorStream(true).start() - process.waitFor(5, java.util.concurrent.TimeUnit.SECONDS) - } catch (_: Exception) { - // best effort — daemon may already be stopped - } - } - } - /** * Writes a file under the project directory, creating parent dirs as needed. * diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/RangeQueryTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/RangeQueryTest.kt index 5ced9ed1032..2c47645bdc7 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/RangeQueryTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/RangeQueryTest.kt @@ -15,7 +15,7 @@ class RangeQueryTest { // compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.5.0', ext: 'pom' val directiveArtifact: Artifact = DefaultArtifact("org.codehaus.groovy", "groovy-all", "jar", "[2.5.0,2.5.8)") val rangeRequest = VersionRangeRequest().apply { - repositories = MuzzleMavenRepoUtils.MUZZLE_REPOS + repositories = MuzzleMavenRepoUtils.defaultMuzzleRepos() artifact = directiveArtifact } From 0e5f1ec29903417339dbe759fab6f306edc334c2 Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Thu, 14 May 2026 20:52:14 -0400 Subject: [PATCH 5/5] Fixed review comments --- .../datadog/gradle/plugin/GradleFixture.kt | 21 ++++++++++--------- .../version/TracerVersionIntegrationTest.kt | 4 ++-- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt index 6e8a6bb805a..b89ee5937a9 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt @@ -1,5 +1,6 @@ package datadog.gradle.plugin +import datadog.gradle.plugin.GradleFixture.Companion.sharedTestKitDir import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.UnexpectedBuildResultException @@ -30,8 +31,8 @@ open class GradleFixture { // locks. See https://github.com/gradle/gradle/issues/12535 // // TestKit may reuse the same daemon for builds with different withEnvironment() - // values, so build logic must not cache environment-derived state in daemon- - // static fields. + // values, so build logic must not cache environment-derived state in daemon-static + // fields. private val sharedTestKitDir: File by lazy { Files.createTempDirectory("gradle-testkit-").toFile().also { dir -> Runtime.getRuntime().addShutdownHook(Thread { @@ -100,18 +101,18 @@ open class GradleFixture { /** * Runs Gradle with the specified arguments. * - * The TestKit daemon spawned by the first call is reused for every subsequent - * call in the JVM (shared [testKitDir]) so kotlinc compilation of - * `.gradle.kts` scripts amortizes across tests instead of being re-paid per - * test. Daemons are reaped at JVM shutdown by the hook registered when + * The TestKit daemon spawned by the first call and reused for every subsequent + * call in the JVM (shared [testKitDir]) so Kotlin compilation of `.gradle.kts` + * scripts amortizes across tests instead of being re-paid per test. + * Daemons are reaped at JVM shutdown by the hook registered when * [sharedTestKitDir] is created. * * @param args Gradle task names and arguments * @param expectFailure Whether the build is expected to fail * @param env Environment variables to set (merged with system environment) * @param forwardOutput Forward the build's stdout/stderr to the test's output - * @param projectDir Override the project directory used by Gradle (useful for git worktree - * tests); when null, defaults to the fixture's project directory. + * @param gradleProjectDir Override the project directory used by Gradle (useful for git worktree tests); + * defaults to the fixture's project directory. * @return The build result */ fun run( @@ -119,12 +120,12 @@ open class GradleFixture { expectFailure: Boolean = false, env: Map = emptyMap(), forwardOutput: Boolean = false, - projectDir: File? = null, + gradleProjectDir: File = projectDir, ): BuildResult { val runner = GradleRunner.create() .withTestKitDir(testKitDir) .withPluginClasspath() - .withProjectDir(projectDir ?: this.projectDir) + .withProjectDir(gradleProjectDir) // Using withDebug prevents starting a daemon, but it doesn't work with withEnvironment .withEnvironment(System.getenv() + env) .withArguments(*args) diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt index ebcc7f36e12..aa25c46c757 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt @@ -146,7 +146,7 @@ class TracerVersionIntegrationTest : VersionPluginsFixture() { private fun assertTracerVersion( expectedVersion: String, - workingDirectory: File? = null, + workingDirectory: File = projectDir, beforeGradle: VersionPluginsFixture.() -> Unit = {}, ) { writeSettings( @@ -171,7 +171,7 @@ class TracerVersionIntegrationTest : VersionPluginsFixture() { beforeGradle() - val buildResult = run("printVersion", "--quiet", projectDir = workingDirectory) + val buildResult = run("printVersion", "--quiet", gradleProjectDir = workingDirectory) assertThat(buildResult.output.lines().first()).isEqualTo(expectedVersion) }