Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 126 additions & 24 deletions buildSrc/src/test/kotlin/datadog/gradle/plugin/GradleFixture.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.UnexpectedBuildResultException
import org.intellij.lang.annotations.Language
import org.junit.jupiter.api.io.TempDir
import org.w3c.dom.Document
import java.io.File
import java.nio.file.Files
Expand All @@ -13,7 +14,10 @@ import javax.xml.parsers.DocumentBuilderFactory
* Base fixture for Gradle plugin integration tests.
* Provides common functionality for setting up test projects and running Gradle builds.
*/
internal open class GradleFixture(protected val projectDir: File) {
open class GradleFixture {
@TempDir
protected lateinit var projectDir: File

// Each fixture gets its own testkit dir in the system temp directory (NOT under
// projectDir) so that JUnit's @TempDir cleanup doesn't race with daemon file locks.
// See https://github.com/gradle/gradle/issues/12535
Expand All @@ -28,6 +32,27 @@ internal open class GradleFixture(protected val projectDir: File) {
}
}

// Configure Gradle to use as few resources as possible:
// - Xms64m -Xmx256m: consume minimum amount of RAM.
// - workers.max=1: don't let the daemon fan out into multiple Worker JVMs.
// - parallel=false: serialize task execution within the fixture build.
// Re-applied if missing.
private fun applyResourceLimits() {
val gradleProperties = file("gradle.properties")
if (gradleProperties.exists() && gradleProperties.readText().contains("org.gradle.jvmargs=-Xms64m -Xmx256m")) {
return
}

writeFile("gradle.properties",
"""
org.gradle.jvmargs=-Xms64m -Xmx256m
org.gradle.workers.max=1
org.gradle.parallel=false
""",
append = true,
)
}
Comment thread
AlexeyKuznetsov-DD marked this conversation as resolved.
Outdated

/**
* Runs Gradle with the specified arguments.
*
Expand All @@ -38,16 +63,30 @@ internal open class GradleFixture(protected val projectDir: File) {
* @param args Gradle task names and arguments
* @param expectFailure Whether the build is expected to fail
* @param env Environment variables to set (merged with system environment)
* @param forwardOutput Forward the build's stdout/stderr to the test's output
* @param projectDir Override the project directory used by Gradle (useful for git worktree
* tests); when null, defaults to the fixture's project directory.
* @return The build result
*/
fun run(vararg args: String, expectFailure: Boolean = false, env: Map<String, String> = emptyMap()): BuildResult {
fun run(
vararg args: String,
expectFailure: Boolean = false,
env: Map<String, String> = emptyMap(),
forwardOutput: Boolean = false,
projectDir: File? = null,
Comment thread
AlexeyKuznetsov-DD marked this conversation as resolved.
Outdated
): BuildResult {
applyResourceLimits()

val runner = GradleRunner.create()
.withTestKitDir(testKitDir)
.withPluginClasspath()
.withProjectDir(projectDir)
.withProjectDir(projectDir ?: this.projectDir)
// Using withDebug prevents starting a daemon, but it doesn't work with withEnvironment
.withEnvironment(System.getenv() + env)
.withArguments(*args)
if (forwardOutput) {
runner.forwardOutput()
}
return try {
if (expectFailure) runner.buildAndFail() else runner.build()
} catch (e: UnexpectedBuildResultException) {
Expand Down Expand Up @@ -112,33 +151,82 @@ internal open class GradleFixture(protected val projectDir: File) {
}

/**
* Adds a subproject to the build.
* Updates settings.gradle and creates the build script for the subproject.
* Writes a file under the project directory, creating parent dirs as needed.
*
* @param path Path relative to the project directory
* @param content File contents; passed through [String.trimIndent] before writing,
* and a trailing newline is appended.
* @param append If true, appends to any existing file instead of overwriting it.
* Safe to call repeatedly to build content up across steps.
*/
fun writeFile(path: String, content: String, append: Boolean = false): File =
file(path).also {
it.parentFile?.mkdirs()
val text = content.trimIndent() + "\n"
if (append) it.appendText(text) else it.writeText(text)
}

/**
* Adds a subproject to the build by appending an `include` line to settings.gradle
* and writing the subproject's build.gradle.
*
* @param projectPath The project path (e.g., "dd-java-agent:instrumentation:other")
* @param buildScript The build script content for the subproject
*/
fun addSubproject(projectPath: String, @Language("Groovy") buildScript: String) {
// Add to settings.gradle
val settingsFile = file("settings.gradle")
if (settingsFile.exists()) {
settingsFile.appendText("\ninclude ':$projectPath'")
} else {
settingsFile.writeText("include ':$projectPath'")
}
writeFile("settings.gradle", "include ':$projectPath'", append = true)
writeFile("${projectPath.replace(':', '/')}/build.gradle", buildScript)
Comment thread
AlexeyKuznetsov-DD marked this conversation as resolved.
Outdated
}

file("${projectPath.replace(':', '/')}/build.gradle")
.writeText(buildScript.trimIndent())
/**
* Writes a Java source file under src/<sourceSet>/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 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 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("Groovy") buildScript: String, append: Boolean = false): File =
writeFile("build.gradle", buildScript, append)

/**
* Writes the root project's settings.gradle file.
*
* @param settingsScript The settings script content
* @param append If true, appends to any existing file instead of overwriting
*/
fun writeSettings(@Language("Groovy") settingsScript: String, append: Boolean = false): File =
writeFile("settings.gradle", settingsScript, append)

/**
* Parses an XML file into a DOM Document.
Expand All @@ -149,12 +237,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)
}
Original file line number Diff line number Diff line change
@@ -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)

Expand Down Expand Up @@ -76,9 +74,9 @@ class ParseV2SupportedConfigurationsTest {
assertTrue(content.contains("""reversePropertyKeysMapping.put("property.key", "DD_ACTION_EXECUTION_ID")"""))
}

private fun runGradleTask(projectDir: File): Pair<BuildResult, File> {
val jsonFile = file(projectDir, "test-supported-configurations.json")
jsonFile.writeText(
private fun runGradleTask(): Pair<BuildResult, File> {
writeFile(
"test-supported-configurations.json",
"""
{
"supportedConfigurations": {
Expand All @@ -88,7 +86,7 @@ class ParseV2SupportedConfigurationsTest {
"type": "string",
"default": null,
"aliases": [],
"propertyKeys": ["property.key"]
"propertyKeys": ["property.key"]
}
],
"DD_AGENTLESS_LOG_SUBMISSION_ENABLED": [
Expand All @@ -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'
"""
rootProject.name = "test-config-project"
""".trimIndent()
)

file(projectDir, "build.gradle.kts").writeText(
writeRootProject(
Comment thread
bric3 marked this conversation as resolved.
"""
plugins {
id("java")
id("dd-trace-java.supported-config-generator")
id 'java'
id 'dd-trace-java.supported-config-generator'
}
group = "datadog.config.test"

group = 'datadog.config.test'

supportedTracerConfigurations {
jsonFile.set(file("test-supported-configurations.json"))
destinationDirectory.set(file("build/generated/supportedConfigurations"))
className.set("datadog.test.TestGeneratedSupportedConfigurations")
jsonFile.set(file('test-supported-configurations.json'))
destinationDirectory.set(file('build/generated/supportedConfigurations'))
className.set('datadog.test.TestGeneratedSupportedConfigurations')
}
""".trimIndent()
"""
)
}

private fun file(projectDir: File, vararg parts: String, makeDirectory: Boolean = false): File {
val f = Paths.get(projectDir.absolutePath, *parts).toFile()

if (makeDirectory) {
f.parentFile.mkdirs()
}

return f
}

private fun assertContainsSupportedConfig(
content: String,
key: String,
Expand Down
Loading
Loading