From 42d4d54326bb8ae9b1ead012f98721e693e4aa84 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 4 Jun 2026 18:14:28 +0000 Subject: [PATCH 1/2] Create testProcess{Config} task that can pass command line profiler.options argument to tests --- .../datadoghq/profiler/ProfilerTestPlugin.kt | 99 +++++++++++++++++++ .../profiler/AbstractProfilerTest.java | 57 +++++++++++ 2 files changed, 156 insertions(+) diff --git a/build-logic/conventions/src/main/kotlin/com/datadoghq/profiler/ProfilerTestPlugin.kt b/build-logic/conventions/src/main/kotlin/com/datadoghq/profiler/ProfilerTestPlugin.kt index 56827cb28..ab4e8d564 100644 --- a/build-logic/conventions/src/main/kotlin/com/datadoghq/profiler/ProfilerTestPlugin.kt +++ b/build-logic/conventions/src/main/kotlin/com/datadoghq/profiler/ProfilerTestPlugin.kt @@ -315,6 +315,12 @@ class ProfilerTestPlugin : Plugin { allArgs.add("-Dtest.filter=$testsFilter") } + // Profiler options from -Pprofiler.options property + val profilerOptions = project.findProperty("profiler.options") as String? + if (profilerOptions != null) { + allArgs.add("-Dddprof.test.options=$profilerOptions") + } + // Classpath (includes custom test runner) allArgs.add("-cp") allArgs.add(testConfig.testClasspath.asPath) @@ -356,6 +362,95 @@ class ProfilerTestPlugin : Plugin { } } + /** + * Create Exec-based test task that always runs in a separate process. + * Available on all platforms. Supports -Pprofiler.options for overriding profiler settings. + * Task name: testProcess (e.g., testProcessDebug, testProcessRelease) + */ + private fun createProcessTestTask( + project: Project, + extension: ProfilerTestExtension, + testConfig: TestTaskConfiguration, + testCfg: Configuration, + sourceSets: SourceSetContainer + ) { + val taskName = "testProcess${testConfig.configName.replaceFirstChar { it.uppercase() }}" + project.tasks.register(taskName, Exec::class.java) { + val execTask = this + execTask.description = "Runs tests in separate process with ${testConfig.configName} library (supports -Pprofiler.options)" + execTask.group = "verification" + execTask.onlyIf { testConfig.isActive && !project.hasProperty("skip-tests") } + + // Dependencies + execTask.dependsOn(project.tasks.named("compileTestJava")) + execTask.dependsOn(testCfg) + execTask.dependsOn(sourceSets.getByName("test").output) + + // Configure at execution time to capture properties + execTask.doFirst { + execTask.executable = PlatformUtils.testJavaExecutable() + + val allArgs = mutableListOf() + + // JVM args + allArgs.addAll(testConfig.standardJvmArgs) + if (extension.nativeLibDir.isPresent) { + allArgs.add("-Djava.library.path=${extension.nativeLibDir.get().asFile.absolutePath}") + } + allArgs.addAll(testConfig.extraJvmArgs) + + // System properties + testConfig.systemProperties.forEach { (key, value) -> + allArgs.add("-D$key=$value") + } + + // Test filter from -Ptests property + val testsFilter = project.findProperty("tests") as String? + if (testsFilter != null) { + allArgs.add("-Dtest.filter=$testsFilter") + } + + // Profiler options from -Pprofiler.options property + val profilerOptions = project.findProperty("profiler.options") as String? + if (profilerOptions != null) { + allArgs.add("-Dddprof.test.options=$profilerOptions") + } + + // Classpath + allArgs.add("-cp") + allArgs.add(testConfig.testClasspath.asPath) + + // Use custom test runner + allArgs.add("com.datadoghq.profiler.test.ProfilerTestRunner") + + execTask.args = allArgs + } + + // Environment variables + testConfig.environmentVariables.forEach { (key, value) -> + execTask.environment(key, value) + } + + // Remove LD_LIBRARY_PATH to let RPATH work correctly + execTask.doFirst { + val currentLdLibPath = (execTask.environment["LD_LIBRARY_PATH"] as? String) ?: System.getenv("LD_LIBRARY_PATH") + if (!currentLdLibPath.isNullOrEmpty()) { + project.logger.info("Removing LD_LIBRARY_PATH to prevent cross-JDK library conflicts (was: $currentLdLibPath)") + execTask.environment.remove("LD_LIBRARY_PATH") + } + } + + // Sanitizer conditions + when (testConfig.configName) { + "asan" -> execTask.onlyIf { + PlatformUtils.locateLibasan() != null && + !PlatformUtils.isTestJvmJ9() + } + "tsan" -> execTask.onlyIf { false } + } + } + } + private fun generateMultiConfigTasks(project: Project, extension: ProfilerTestExtension) { val nativeBuildExt = project.rootProject.extensions.findByType(NativeBuildExtension::class.java) ?: return // No native build extension, nothing to generate @@ -423,6 +518,10 @@ class ProfilerTestPlugin : Plugin { createTestTask(project, extension, testConfig, testCfg, sourceSets) } + // Create process-based test task (always uses Exec, available on all platforms) + // Supports -Pprofiler.options for overriding profiler settings + createProcessTestTask(project, extension, testConfig, testCfg, sourceSets) + // Create application tasks for specified configs if (configName in applicationConfigs && appMainClass.isNotEmpty()) { // Create main configuration diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/AbstractProfilerTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/AbstractProfilerTest.java index de75c2f06..218be5700 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/AbstractProfilerTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/AbstractProfilerTest.java @@ -236,6 +236,7 @@ public void setupProfiler(TestInfo testInfo) throws Exception { String command = "start," + getAmendedProfilerCommand() + ",jfr,file=" + jfrDump.toAbsolutePath(); cpuInterval = command.contains("cpu") ? parseInterval(command, "cpu") : (command.contains("interval") ? parseInterval(command, "interval") : Duration.ZERO); wallInterval = parseInterval(command, "wall"); + // Record sanitizer log sizes before test so we can dump new errors after sanitizerLogSizesBefore.clear(); for (Path logPath : getSanitizerLogPaths()) { @@ -376,8 +377,64 @@ public final void registerCurrentThreadForWallClockProfiling() { profiler.addThread(); } + /** + * Merges two profiler command strings. Options in overrides take precedence + * over options in base for matching keys. + * + * @param base The base profiler command (from test's getProfilerCommand()) + * @param overrides The override options (from -Pprofiler.options) + * @return Merged command string with overrides taking precedence + */ + private static String mergeProfilerOptions(String base, String overrides) { + if (overrides == null || overrides.isEmpty()) { + return base; + } + + // Parse base options into ordered map + Map options = new java.util.LinkedHashMap<>(); + for (String part : base.split(",")) { + int eq = part.indexOf('='); + if (eq > 0) { + options.put(part.substring(0, eq), part.substring(eq + 1)); + } else if (!part.isEmpty()) { + options.put(part, ""); + } + } + + // Apply overrides + for (String part : overrides.split(",")) { + int eq = part.indexOf('='); + if (eq > 0) { + options.put(part.substring(0, eq), part.substring(eq + 1)); + } else if (!part.isEmpty()) { + options.put(part, ""); + } + } + + // Rebuild command + StringBuilder result = new StringBuilder(); + for (Map.Entry entry : options.entrySet()) { + if (result.length() > 0) { + result.append(","); + } + result.append(entry.getKey()); + if (!entry.getValue().isEmpty()) { + result.append("=").append(entry.getValue()); + } + } + return result.toString(); + } + private String getAmendedProfilerCommand() { String profilerCommand = getProfilerCommand(); + + // Apply user-provided options from -Pprofiler.options (override test defaults) + String userOptions = System.getProperty("ddprof.test.options"); + if (userOptions != null && !userOptions.isEmpty()) { + profilerCommand = mergeProfilerOptions(profilerCommand, userOptions); + System.out.println("[TEST] Applied profiler.options: " + userOptions); + } + String testCstack = (String)testParams.get("cstack"); if (testCstack != null) { profilerCommand += ",cstack=" + testCstack; From 6f5c7bae36395df956acfc76f48eba36f5cfe8d0 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Fri, 5 Jun 2026 14:49:41 +0000 Subject: [PATCH 2/2] Cleanup --- .../kotlin/com/datadoghq/profiler/ProfilerTestPlugin.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/build-logic/conventions/src/main/kotlin/com/datadoghq/profiler/ProfilerTestPlugin.kt b/build-logic/conventions/src/main/kotlin/com/datadoghq/profiler/ProfilerTestPlugin.kt index ab4e8d564..5e8832744 100644 --- a/build-logic/conventions/src/main/kotlin/com/datadoghq/profiler/ProfilerTestPlugin.kt +++ b/build-logic/conventions/src/main/kotlin/com/datadoghq/profiler/ProfilerTestPlugin.kt @@ -315,12 +315,6 @@ class ProfilerTestPlugin : Plugin { allArgs.add("-Dtest.filter=$testsFilter") } - // Profiler options from -Pprofiler.options property - val profilerOptions = project.findProperty("profiler.options") as String? - if (profilerOptions != null) { - allArgs.add("-Dddprof.test.options=$profilerOptions") - } - // Classpath (includes custom test runner) allArgs.add("-cp") allArgs.add(testConfig.testClasspath.asPath)