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 c0fd6077ded..b89ee5937a9 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt @@ -1,9 +1,11 @@ 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 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,132 +15,207 @@ 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) { - // 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() }) +open class GradleFixture { + @TempDir + protected lateinit var projectDir: File + + 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 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 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(vararg args: String, expectFailure: Boolean = false, env: Map = emptyMap()): BuildResult { + fun run( + vararg args: String, + expectFailure: Boolean = false, + env: Map = emptyMap(), + forwardOutput: Boolean = false, + gradleProjectDir: File = projectDir, + ): BuildResult { val runner = GradleRunner.create() .withTestKitDir(testKitDir) .withPluginClasspath() - .withProjectDir(projectDir) + .withProjectDir(gradleProjectDir) // 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) { 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. + * Writes a file under the project directory, creating parent dirs as needed. * - * 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 + * @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. */ - 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 - } - } - } + 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. - * Updates settings.gradle and creates the build script for the subproject. + * 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) { - // Add to settings.gradle - val settingsFile = file("settings.gradle") - if (settingsFile.exists()) { - settingsFile.appendText("\ninclude ':$projectPath'") - } else { - settingsFile.writeText("include ':$projectPath'") - } + fun addSubproject(projectPath: String, @Language("kotlin") buildScript: String) { + writeFile("settings.gradle.kts", """include(":$projectPath")""", append = true) + writeFile("${projectPath.replace(':', '/')}/build.gradle.kts", buildScript) + } - file("${projectPath.replace(':', '/')}/build.gradle") - .writeText(buildScript.trimIndent()) + /** + * Writes a Java source file under src//java. + * + * @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 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 the root project's build.gradle file. + * Writes gradle.properties at the project root. + * + * @param content Properties content (trimIndent applied, trailing newline added) + * @param append If true, appends to any existing file instead of overwriting + */ + fun writeGradleProperties(content: String, append: Boolean = false): File = + writeFile("gradle.properties", content, append) + + /** + * 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) { - file("build.gradle").writeText(buildScript.trimIndent()) - } + fun writeRootProject(@Language("kotlin") buildScript: String, append: Boolean = false): File = + writeFile("build.gradle.kts", buildScript, append) + + /** + * 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("kotlin") settingsScript: String, append: Boolean = false): File = + writeFile("settings.gradle.kts", settingsScript, append) /** * Parses an XML file into a DOM Document. @@ -149,12 +226,26 @@ internal open class GradleFixture(protected 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. */ - protected 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 bb6a04d52f1..b5f46e15932 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt @@ -1,19 +1,17 @@ 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 { +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) @@ -76,9 +74,9 @@ class ParseV2SupportedConfigurationsTest { assertTrue(content.contains("""reversePropertyKeysMapping.put("property.key", "DD_ACTION_EXECUTION_ID")""")) } - private fun runGradleTask(projectDir: File): Pair { - val jsonFile = file(projectDir, "test-supported-configurations.json") - jsonFile.writeText( + private fun runGradleTask(): Pair { + writeFile( + "test-supported-configurations.json", """ { "supportedConfigurations": { @@ -88,7 +86,7 @@ class ParseV2SupportedConfigurationsTest { "type": "string", "default": null, "aliases": [], - "propertyKeys": ["property.key"] + "propertyKeys": ["property.key"] } ], "DD_AGENTLESS_LOG_SUBMISSION_ENABLED": [ @@ -111,57 +109,45 @@ class ParseV2SupportedConfigurationsTest { "legacy.setting": "No longer supported" } } - """.trimIndent() + """ ) - setupGradleProject(projectDir) + setupGradleProject() - val buildResult = GradleRunner.create() - .forwardOutput() - .withPluginClasspath() - .withArguments("generateSupportedConfigurations") - .withProjectDir(projectDir) - .build() + val buildResult = run( + "generateSupportedConfigurations", + forwardOutput = true + ) - val generatedFile = file(projectDir, "build", "generated", "supportedConfigurations", "datadog", "test", "TestGeneratedSupportedConfigurations.java") + val generatedFile = file("build/generated/supportedConfigurations/datadog/test/TestGeneratedSupportedConfigurations.java") return Pair(buildResult, generatedFile) } - private fun setupGradleProject(projectDir: File) { - file(projectDir, "settings.gradle.kts").writeText( + private fun setupGradleProject() { + writeSettings( """ rootProject.name = "test-config-project" - """.trimIndent() + """ ) - file(projectDir, "build.gradle.kts").writeText( + writeRootProject( """ plugins { id("java") id("dd-trace-java.supported-config-generator") } - + group = "datadog.config.test" - + supportedTracerConfigurations { 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..c65742f2c83 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPluginTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPluginTest.kt @@ -1,21 +1,18 @@ 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 import java.io.File import java.nio.file.Files -class CallSiteInstrumentationPluginTest { +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 { @@ -24,43 +21,39 @@ class CallSiteInstrumentationPluginTest { } csi { - suffix = 'CallSite' - targetFolder = project.layout.buildDirectory.dir('csi') - rootFolder = file('__ROOT_FOLDER__') + suffix.set("CallSite") + targetFolder.set(project.layout.buildDirectory.dir("csi")) } - + 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' + implementation("net.bytebuddy:byte-buddy:1.18.8") + implementation("com.google.auto.service:auto-service-annotations:1.1.1") } - """.trimIndent() - - @TempDir - lateinit var buildDir: File + """ @Test fun `test call site instrumentation plugin`() { createGradleProject( - buildDir, buildGradle, + 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)") public static void beforeAppend(@CallSite.This final StringBuilder self, @CallSite.Argument final String param) { } } - """.trimIndent() + """ ) - val result = buildGradleProject(buildDir) + val result = buildGradleProject() - val generated = resolve(buildDir, "build", "csi", "BeforeAdviceCallSites.java") + val generated = buildFile("csi/BeforeAdviceCallSites.java") assertTrue(generated.exists()) val output = result.output @@ -71,67 +64,58 @@ class CallSiteInstrumentationPluginTest { @Test fun `test call site instrumentation plugin with error`() { createGradleProject( - buildDir, buildGradle, + 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)") private void beforeAppend(@CallSite.This final StringBuilder self, @CallSite.Argument final String param) { } } - """.trimIndent() + """ ) - val error = assertThrows(UnexpectedBuildFailure::class.java) { - buildGradleProject(buildDir) - } + val result = run("build", "--info", "--stacktrace", forwardOutput = true, expectFailure = true) - val generated = resolve(buildDir, "build", "csi", "BeforeAdviceCallSites.java") + val generated = buildFile("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) { 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 = dir("buildSrc/call-site-instrumentation-plugin/build/libs") Files.copy( callSiteJar.toPath(), testCallSiteJarDir.toPath().resolve(callSiteJar.name) ) - val gradleFileContent = gradleFile.replace("__ROOT_FOLDER__", projectFolder.toString().replace("\\", "\\\\")) - writeText(resolve(buildDir, "build.gradle"), gradleFileContent) + writeRootProject(gradleFile) - 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 adviceSourceName = if (advicePackage.isEmpty()) { + adviceClassName + } else { + "$advicePackage.$adviceClassName" + } + writeJavaSource(adviceSourceName, 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 -> + writeJavaSource("datadog.trace.agent.tooling.csi.${src.nameWithoutExtension}", src.readText()) } } - 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(): BuildResult = + 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 +128,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..78279142b40 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,34 @@ 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 { +class DumpHangedTestIntegrationTest : GradleFixture() { @Test - fun `should not take dumps`(@TempDir projectDir: File) { - val output = runGradleTest(projectDir, testSleep = 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, testSleep = 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. @@ -42,82 +37,60 @@ class DumpHangedTestIntegrationTest { assertNotNull(dumpFiles.find { it.startsWith("all-thread-dumps") }) } - private fun runGradleTest(projectDir: File, testSleep: Long): List { - file(projectDir, "settings.gradle.kts").writeText( - """ - rootProject.name = "test-project" - """.trimIndent() - ) + private fun runGradleTest(testSleepMillis: Long): List { + writeSettings("""rootProject.name = "test-project"""") - file(projectDir, "build.gradle.kts").writeText( + writeRootProject( """ import java.time.Duration - + import org.gradle.api.tasks.testing.Test + plugins { id("java") id("dd-trace-java.dump-hanged-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") } - + dumpHangedTest { // Set the dump offset for 5 seconds to trigger taking dumps after 15 seconds. dumpOffset.set(5) } - + tasks.withType().configureEach { // Set test timeout after 20 seconds. timeout.set(Duration.ofSeconds(20)) - + useJUnitPlatform() } - """.trimIndent() + """ ) - file(projectDir, "src", "test", "java", "SimpleTest.java", makeDirectory = true).writeText( + writeJavaSource( + "SimpleTest", """ import org.junit.jupiter.api.Test; - + public class SimpleTest { @Test public void test() throws InterruptedException { - Thread.sleep($testSleep); + Thread.sleep($testSleepMillis); } } - """.trimIndent() + """, + sourceSet = "test" ) - 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 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..9bbcb7bf386 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/BuildTimeInstrumentationPluginTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/BuildTimeInstrumentationPluginTest.kt @@ -1,10 +1,9 @@ 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 import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassVisitor import org.objectweb.asm.ClassWriter @@ -13,12 +12,12 @@ import org.objectweb.asm.Opcodes import java.io.File import java.io.FileInputStream -class BuildTimeInstrumentationPluginTest { +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 { @@ -31,51 +30,35 @@ class BuildTimeInstrumentationPluginTest { } 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' - ] - """.trimIndent() + buildTimeInstrumentation.plugins.set(listOf("TestPlugin")) + """ private val exampleCode = """ package example; public class ExampleCode {} - """.trimIndent() - - @TempDir - lateinit var buildDir: File + """ @Test fun `test instrument plugin`() { - val buildFile = File(buildDir, "build.gradle") - buildFile.writeText(buildGradle) - - val srcMainJava = testPlugin("src/main/java", "ExampleCode") - - val examplePackageDir = File(srcMainJava, "example").apply { mkdirs() } - File(examplePackageDir, "ExampleCode.java").writeText(exampleCode) + writeRootProject(buildGradle) + writeTestPlugin("ExampleCode") + writeJavaSource("example.ExampleCode", 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() + 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 buildFile = File(buildDir, "build.gradle") - buildFile.writeText(""" + writeRootProject( + """ plugins { - id 'java' - id 'dd-trace-java.build-time-instrumentation' + id("java") + id("dd-trace-java.build-time-instrumentation") } java { @@ -88,41 +71,35 @@ class BuildTimeInstrumentationPluginTest { } 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")) } - """.trimIndent()) + """ + ) - testPlugin("src/main/java", "ExternalCode") + writeTestPlugin("ExternalCode") // Pre-compile ExternalCode using ASM and place it in the external-classes directory - val externalClassesDir = File(buildDir, "external-classes").apply { mkdirs() } + val externalClassesDir = dir("external-classes") precompiledClass("ExternalCode", externalClassesDir) - GradleRunner.create() - .withTestKitDir(File(buildDir, ".gradle-test-kit")) - .withDebug(true) - .withProjectDir(buildDir) - .withArguments("build", "--stacktrace") - .withPluginClasspath() - .forwardOutput() - .build() + 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 buildFile = File(buildDir, "build.gradle") - buildFile.writeText(""" + writeRootProject( + """ plugins { - id 'java' - id 'dd-trace-java.build-time-instrumentation' + id("java") + id("dd-trace-java.build-time-instrumentation") } java { @@ -135,44 +112,38 @@ class BuildTimeInstrumentationPluginTest { } 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")) } - """.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("ExampleCode", "ExternalCode") + writeJavaSource("example.ExampleCode", "package example; public class ExampleCode {}") - val externalClassesDir = File(buildDir, "external-classes").apply { mkdirs() } + val externalClassesDir = dir("external-classes") 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() + 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() + 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 testPlugin(srcDir: String, vararg classNames: String): File { - val dir = File(buildDir, srcDir).apply { mkdirs() } + private fun writeTestPlugin(vararg classNames: String) { val conditions = classNames.joinToString(" || ") { "\"$it\".equals(name)" } - File(dir, "TestPlugin.java").writeText(""" + writeJavaSource( + "TestPlugin", + """ import java.io.File; import java.io.IOException; import net.bytebuddy.build.Plugin; @@ -202,12 +173,12 @@ class BuildTimeInstrumentationPluginTest { } @Override - public void close() throws IOException { - // no-op - } + public void close() throws IOException { + // no-op } - """.trimIndent()) - return dir + } + """ + ) } 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..b09bca95c1e 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt @@ -1,30 +1,21 @@ 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' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { @@ -36,17 +27,17 @@ class MuzzlePluginFunctionalTest { ) // Add runMuzzle aggregator task at root level (like in dd-trace-java.ci-jobs.gradle.kts) - fixture.writeRootProject( + writeRootProject( """ - tasks.register('runMuzzle') { - dependsOn(':dd-java-agent:instrumentation:demo:muzzle') + tasks.register("runMuzzle") { + dependsOn(":dd-java-agent:instrumentation:demo:muzzle") } """ ) - 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,24 +50,23 @@ 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' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { pass { - name = 'expected-pass' + name = "expected-pass" coreJdk() } } """ ) - 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,17 +93,16 @@ 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' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } """ ) - 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,13 +125,12 @@ 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' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { @@ -153,31 +141,30 @@ 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' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } """ ) - 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,13 +173,12 @@ 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' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { @@ -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,26 +224,25 @@ 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' - 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() @@ -268,17 +253,17 @@ class MuzzlePluginFunctionalTest { 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 } } """ ) - 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,18 +303,17 @@ 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' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { pass { - name = 'my-custom-check' + name = "my-custom-check" coreJdk() } } @@ -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,27 +342,26 @@ 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' - 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)" } } """ ) - 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,13 +378,12 @@ 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' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { @@ -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,13 +417,12 @@ 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' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { @@ -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,16 +468,16 @@ class MuzzlePluginFunctionalTest { module = "extra-lib", versions = listOf("1.0.0") ) - fixture.writeProject( + 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() @@ -508,14 +488,14 @@ class MuzzlePluginFunctionalTest { muzzle { pass { coreJdk() - extraDependency('com.example.extra:extra-lib:1.0.0') + extraDependency("com.example.extra:extra-lib:1.0.0") } } """ ) // 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,16 +559,16 @@ class MuzzlePluginFunctionalTest { """.trimIndent() ) - fixture.writeProject( + 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() @@ -599,17 +579,17 @@ class MuzzlePluginFunctionalTest { 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") } } """ ) // 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,17 +615,15 @@ 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' + id("dd-trace-java.muzzle") } // applied after muzzle plugin - apply plugin: 'java' + apply(plugin = "java") muzzle { pass { @@ -654,9 +632,9 @@ class MuzzlePluginFunctionalTest { } """ ) - fixture.writeNoopScanPlugin() + writeNoopScanPlugin() - val result = fixture.run( + val result = run( ":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace" ) @@ -667,29 +645,27 @@ 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' - 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() } } """ ) - fixture.writeNoopScanPlugin() + writeNoopScanPlugin() - val result = fixture.run( + val result = run( ":dd-java-agent:instrumentation:demo:muzzle", "--stacktrace" ) @@ -700,13 +676,11 @@ 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' + id("dd-trace-java.muzzle") // NO java plugin applied } @@ -717,9 +691,9 @@ class MuzzlePluginFunctionalTest { } """ ) - fixture.writeNoopScanPlugin() + writeNoopScanPlugin() - val result = fixture.run( + val result = run( ":dd-java-agent:instrumentation:demo:tasks", "--all" ) @@ -730,20 +704,21 @@ class MuzzlePluginFunctionalTest { } @Test - fun `missing dd-java-agent projects error handling`(@TempDir projectDir: File) { - // Create a minimal settings.gradle without the dd-java-agent structure - File(projectDir, "settings.gradle").also { it.parentFile?.mkdirs() }.writeText( + fun `missing dd-java-agent projects error handling`() { + // 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' - """.trimIndent() ) - File(projectDir, "instrumentation/demo/build.gradle").also { it.parentFile?.mkdirs() }.writeText( + addSubproject( + "instrumentation:demo", """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { @@ -751,13 +726,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 = run( ":instrumentation:demo:tasks", "--stacktrace" ) @@ -771,26 +746,25 @@ 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' - 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() @@ -800,21 +774,21 @@ class MuzzlePluginFunctionalTest { 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 } } """ ) - 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) @@ -848,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..2e43b0b3d38 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginPerformanceTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginPerformanceTest.kt @@ -1,38 +1,33 @@ 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' - 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)" } } """ ) - fixture.writeNoopScanPlugin() + writeNoopScanPlugin() - val result = fixture.run( + val result = run( ":dd-java-agent:instrumentation:demo:tasks", "--all", "--info" @@ -49,26 +44,24 @@ 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' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { pass { coreJdk() } } """ ) - fixture.writeNoopScanPlugin() - fixture.addSubproject("dd-java-agent:instrumentation:other", + writeNoopScanPlugin() + addSubproject("dd-java-agent:instrumentation:other", """ plugins { - id 'java' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { pass { coreJdk() } @@ -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,16 +96,16 @@ class MuzzlePluginPerformanceTest { versions = listOf("1.0.0", "1.1.0") ) - fixture.writeProject( + 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() @@ -123,18 +115,18 @@ class MuzzlePluginPerformanceTest { 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)" } } """ ) - 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,14 +197,12 @@ 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' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { @@ -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,14 +261,12 @@ 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' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { @@ -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,14 +325,12 @@ 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' - id 'dd-trace-java.muzzle' + id("java") + id("dd-trace-java.muzzle") } muzzle { @@ -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 026ef5b0d9d..ba9945cbe4a 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,34 +9,34 @@ 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) { - file("settings.gradle").writeText( - // language=Groovy + fun writeProject(@Language("kotlin") instrumentationBuildScript: String) { + writeSettings( + """ + rootProject.name = "muzzle-e2e" """ - rootProject.name = 'muzzle-e2e' - """.trimIndent() ) 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") } """ ) @@ -57,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/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 } 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..aa25c46c757 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/version/TracerVersionIntegrationTest.kt @@ -1,169 +1,161 @@ 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 -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() - settingsFile.appendText("\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 = { - gradlePropertiesFile.writeText("tracerVersion.dirtiness=true") - fixture.initGitRepo() - fixture.exec("git", "tag", "v1.52.0", "-m", "") - settingsFile.appendText("\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", "") - settingsFile.appendText("\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 = { - gradlePropertiesFile.writeText("tracerVersion.dirtiness=true") - fixture.initGitRepo() - fixture.exec("git", "tag", "v1.52.0", "-m", "") - val settings = settingsFile - settings.appendText("\n// uncommitted change ") - fixture.exec("git", "commit", "-am", "Another commit") - settings.appendText("\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", "") - settingsFile.appendText("\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") - val settings = settingsFile - settings.appendText("\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.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.kts").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, + workingDirectory: File = projectDir, beforeGradle: VersionPluginsFixture.() -> Unit = {}, ) { - settingsFile.writeText("""rootProject.name = "test-project"""") - projectBuildFile.writeText( + writeSettings( + """ + rootProject.name = "test-project" + """ + ) + + writeRootProject( """ plugins { id("dd-trace-java.tracer-version") @@ -174,23 +166,12 @@ class TracerVersionIntegrationTest { } group = "datadog.tracer.version.test" - """.trimIndent() + """ ) 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", gradleProjectDir = 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 aba9e9fb35b..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,13 +27,7 @@ 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 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 8aa49a86ae8..f1bb1395b16 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 = { - projectBuildFile.appendText( + writeRootProject( """ - tasks.named("writeVersionNumberFile").configure { + tasks.named("writeVersionNumberFile") { version.set("9.9.9") gitHash.set("deadbeef") } - """.trimIndent() + """, + 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 = { - projectBuildFile.appendText( + writeRootProject( """ - tasks.named("writeVersionNumberFile").configure { + tasks.named("writeVersionNumberFile") { gitHash.set("abc12345") } - """.trimIndent() + """, + append = true, ) generatedVersionFile.run { parentFile.mkdirs() @@ -62,86 +59,90 @@ 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 = { - projectBuildFile.appendText( + writeRootProject( """ - tasks.named("writeVersionNumberFile").configure { + tasks.named("writeVersionNumberFile") { gitHash.set("abc12345") } - """.trimIndent() + """, + 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 = { - projectBuildFile.appendText( + writeRootProject( """ - tasks.named("writeVersionNumberFile").configure { + tasks.named("writeVersionNumberFile") { gitHash.set("abc12345") } - """.trimIndent() + """, + 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 = { - projectBuildFile.appendText( + writeRootProject( """ - tasks.named("writeVersionNumberFile").configure { + tasks.named("writeVersionNumberFile") { gitHash.set("abc12345") } - """.trimIndent() + """, + 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 { - settingsFile.writeText("""rootProject.name = "my-lib"""") - projectBuildFile.writeText( + writeSettings( + """ + rootProject.name = "my-lib" + """ + ) + writeRootProject( """ plugins { id("dd-trace-java.version-file") } version = "1.2.3" - """.trimIndent() + """ ) beforeGradle()