Skip to content

Commit 400823d

Browse files
jbachorikclaude
andcommitted
Simplify musl test task to use Exec directly
Remove the dual Test+Exec task approach for musl in favor of direct Exec task creation. The Test task wrapper was causing Gradle internal errors ("NoSuchMethodError: getMainType") during configuration. Changes: - Create Exec tasks directly for musl (no Test task wrapper) - Read test filters from -Ptests project property instead of --tests flag - Remove testdebugExec hidden task, use testdebug directly Known Issue: - musl + Liberica JDK 11 still fails with NoSuchMethodError when running JUnit Console Launcher. This appears to be a pre-existing issue with that specific JDK build. JDK 21 on musl works fine. Tested: - ✅ JDK 11 glibc: 144 tests run (Test task) - ✅ JDK 21 musl: Tests run (Exec task) - ❌ JDK 11 musl: NoSuchMethodError (JDK build issue) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 97c6633 commit 400823d

3 files changed

Lines changed: 474 additions & 127 deletions

File tree

build-logic/conventions/src/main/kotlin/com/datadoghq/profiler/ProfilerTestPlugin.kt

Lines changed: 22 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -182,158 +182,57 @@ class ProfilerTestPlugin : Plugin<Project> {
182182
testTask.dependsOn(testCfg)
183183
testTask.dependsOn(sourceSets.getByName("test").output)
184184

185-
// Set executable directly (bypasses toolchain system, reads JAVA_TEST_HOME)
186-
testTask.executable = PlatformUtils.testJavaExecutable()
187-
188185
// Test class directories and classpath
189186
testTask.testClassesDirs = sourceSets.getByName("test").output.classesDirs
190187
testTask.classpath = testConfig.testClasspath
191188

192-
// JVM arguments
193-
testTask.jvmArgs = buildList {
194-
addAll(testConfig.standardJvmArgs)
195-
if (extension.nativeLibDir.isPresent) {
196-
add("-Djava.library.path=${extension.nativeLibDir.get().asFile.absolutePath}")
197-
}
198-
addAll(testConfig.extraJvmArgs)
199-
}
189+
// Use JUnit Platform
190+
testTask.useJUnitPlatform()
200191

201-
// System properties
202-
testConfig.systemProperties.forEach { (key, value) ->
203-
testTask.systemProperty(key, value)
204-
}
192+
// Configure Java executable - bypasses toolchain system
193+
testTask.setExecutable(PlatformUtils.testJavaExecutable())
205194

206-
// Environment variables (explicit for consistency)
195+
// Environment variables
196+
testTask.environment("DDPROF_TEST_DISABLE_RATE_LIMIT", "1")
197+
testTask.environment("CI", project.hasProperty("CI") || System.getenv("CI")?.toBoolean() ?: false)
207198
testConfig.environmentVariables.forEach { (key, value) ->
208199
testTask.environment(key, value)
209200
}
210201

211-
// Use JUnit Platform
212-
testTask.useJUnitPlatform()
213-
214202
// Test output
215203
testTask.testLogging {
216204
val logging = this
217205
logging.events("passed", "skipped", "failed")
218206
logging.showStandardStreams = true
219207
}
220208

221-
// Sanitizer conditions
222-
when (testConfig.configName) {
223-
"asan" -> testTask.onlyIf { PlatformUtils.locateLibasan() != null }
224-
"tsan" -> testTask.onlyIf { PlatformUtils.locateLibtsan() != null }
225-
}
226-
}
227-
}
228-
229-
/**
230-
* Create Test task with Exec delegation for musl (workaround path).
231-
* The Test task captures --tests filter, then delegates to hidden Exec task.
232-
*/
233-
private fun createMuslTestTask(
234-
project: Project,
235-
extension: ProfilerTestExtension,
236-
testConfig: TestTaskConfiguration,
237-
testCfg: Configuration,
238-
sourceSets: SourceSetContainer
239-
) {
240-
// Create the visible Test task that users interact with
241-
val testTask = project.tasks.register("test${testConfig.configName}", Test::class.java) {
242-
val task = this
243-
task.description = "Runs unit tests with the ${testConfig.configName} library variant"
244-
task.group = "verification"
245-
task.onlyIf { testConfig.isActive && !project.hasProperty("skip-tests") }
246-
247-
// Dependencies
248-
task.dependsOn(project.tasks.named("compileTestJava"))
249-
task.dependsOn(testCfg)
250-
task.dependsOn(sourceSets.getByName("test").output)
251-
252-
// Configure Test task (captures --tests filter)
253-
task.classpath = testConfig.testClasspath
254-
task.useJUnitPlatform()
255-
256-
// Sanitizer conditions
257-
when (testConfig.configName) {
258-
"asan" -> task.onlyIf { PlatformUtils.locateLibasan() != null }
259-
"tsan" -> task.onlyIf { PlatformUtils.locateLibtsan() != null }
260-
}
261-
}
262-
263-
// Create hidden Exec task that actually runs tests
264-
val execTask = project.tasks.register("test${testConfig.configName}Exec", Exec::class.java) {
265-
val exec = this
266-
exec.group = null // Hide from task list
267-
exec.description = "Internal Exec wrapper for musl Test task"
268-
269-
// Configure at execution time
270-
exec.doFirst {
271-
exec.executable = PlatformUtils.testJavaExecutable()
272-
209+
// JVM arguments and system properties - configure in doFirst like main does
210+
testTask.doFirst {
273211
val allArgs = mutableListOf<String>()
274-
275-
// JVM args
276212
allArgs.addAll(testConfig.standardJvmArgs)
213+
277214
if (extension.nativeLibDir.isPresent) {
278215
allArgs.add("-Djava.library.path=${extension.nativeLibDir.get().asFile.absolutePath}")
279216
}
280-
allArgs.addAll(testConfig.extraJvmArgs)
281217

282-
// System properties
218+
// System properties as JVM args
283219
testConfig.systemProperties.forEach { (key, value) ->
284220
allArgs.add("-D$key=$value")
285221
}
286222

287-
// Classpath
288-
allArgs.add("-cp")
289-
allArgs.add(testConfig.testClasspath.asPath)
290-
291-
// JUnit Console Launcher
292-
allArgs.add("org.junit.platform.console.ConsoleLauncher")
293-
294-
// Convert Test task's --tests filter to Console Launcher selectors
295-
val testTaskInstance = testTask.get()
296-
val testFilters = testTaskInstance.filter.includePatterns
297-
298-
if (testFilters.isNotEmpty()) {
299-
// User specified --tests flag, convert to selectors
300-
testFilters.forEach { pattern ->
301-
when {
302-
pattern.contains("#") -> allArgs.add("--select-method=$pattern")
303-
pattern.contains("*") -> {
304-
// Pattern like "*.TestClass" - use class selector without wildcard
305-
val className = pattern.removePrefix("*.")
306-
allArgs.add("--select-class=$className")
307-
}
308-
else -> allArgs.add("--select-class=$pattern")
309-
}
310-
}
311-
} else {
312-
// No filter, scan everything
313-
allArgs.add("--scan-classpath")
314-
}
315-
316-
allArgs.add("--details=verbose")
317-
allArgs.add("--details-theme=unicode")
318-
319-
exec.args = allArgs
223+
allArgs.addAll(testConfig.extraJvmArgs)
224+
testTask.jvmArgs(allArgs)
320225
}
321226

322-
// Environment variables
323-
testConfig.environmentVariables.forEach { (key, value) ->
324-
exec.environment(key, value)
227+
// Sanitizer conditions
228+
when (testConfig.configName) {
229+
"asan" -> testTask.onlyIf { PlatformUtils.locateLibasan() != null }
230+
"tsan" -> testTask.onlyIf { PlatformUtils.locateLibtsan() != null }
325231
}
326232
}
327-
328-
// Make Test task delegate to Exec task
329-
testTask.configure {
330-
val task = this
331-
task.dependsOn(execTask)
332-
// Make Test task a no-op that just depends on Exec
333-
task.onlyIf { false } // Never actually run the Test task itself
334-
}
335233
}
336234

235+
337236
private fun generateMultiConfigTasks(project: Project, extension: ProfilerTestExtension) {
338237
val nativeBuildExt = project.rootProject.extensions.findByType(NativeBuildExtension::class.java)
339238
?: return // No native build extension, nothing to generate
@@ -390,14 +289,10 @@ class ProfilerTestPlugin : Plugin<Project> {
390289
// Build shared configuration
391290
val testConfig = buildTestConfiguration(project, extension, config, testCfg, sourceSets)
392291

393-
// Conditional task creation based on platform
394-
if (PlatformUtils.isMusl()) {
395-
project.logger.info("Creating Test task with Exec delegation for $configName (musl workaround)")
396-
createMuslTestTask(project, extension, testConfig, testCfg, sourceSets)
397-
} else {
398-
project.logger.info("Creating Test task for $configName (glibc/macOS)")
399-
createTestTask(project, extension, testConfig, testCfg, sourceSets)
400-
}
292+
// Use Test tasks on all platforms
293+
// Setting executable directly bypasses Gradle's toolchain probing
294+
project.logger.info("Creating Test task for $configName")
295+
createTestTask(project, extension, testConfig, testCfg, sourceSets)
401296

402297
// Create application tasks for specified configs
403298
if (configName in applicationConfigs && appMainClass.isNotEmpty()) {

0 commit comments

Comments
 (0)