Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
8 changes: 7 additions & 1 deletion .github/scripts/test_alpine_aarch64.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export LIBRARY="musl"
export CONFIG="${3}"
export JAVA_HOME="${4}"
export JAVA_TEST_HOME="${5}"
SLOW_TESTS="${6:-false}"

export PATH="${JAVA_HOME}/bin":${PATH}

Expand All @@ -33,4 +34,9 @@ apk update && apk add curl moreutils wget hexdump linux-headers bash make g++ cl
# Install debug symbols for musl libc
apk add musl-dbg

./gradlew -PCI -PkeepJFRs :ddprof-test:test${CONFIG} --no-daemon --parallel --build-cache --no-watch-fs
TASK_PREFIX="test"
if [ "${SLOW_TESTS}" = "true" ]; then
TASK_PREFIX="testSlow"
fi

./gradlew -PCI -PkeepJFRs :ddprof-test:${TASK_PREFIX}${CONFIG} --no-daemon --parallel --build-cache --no-watch-fs
8 changes: 7 additions & 1 deletion .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ jobs:
# C++ gtests (ASan + TSan) run on every PR via native-sanitizer-tests in ci.yml.
# Skip them here so the nightly focuses on Java functional tests under ASan.
skip_gtest: true
run-slow-test:
uses: ./.github/workflows/test_workflow.yml
with:
configuration: '["debug"]'
skip_gtest: true
slow_tests: true
fuzz:
runs-on: ubuntu-latest
continue-on-error: true
Expand Down Expand Up @@ -52,7 +58,7 @@ jobs:
path: ddprof-lib/fuzz/build/fuzz-crashes/
report-failures:
runs-on: ubuntu-latest
needs: run-test
needs: [run-test, run-slow-test]
if: failure()
steps:
- name: Download all failure artifacts
Expand Down
14 changes: 11 additions & 3 deletions .github/workflows/release-validated.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,17 +169,25 @@ jobs:
with:
configuration: '["debug", "asan"]'

pre-release-slow-tests:
needs: validate-inputs
if: ${{ inputs.dry_run != true && inputs.skip_tests != true && inputs.release_type != 'retag' }}
uses: ./.github/workflows/test_workflow.yml
with:
configuration: '["debug"]'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep slow regressions in ASan runs

Configuring the slow suite only for debug means the newly tagged tests are excluded from both nightly and pre-release ASan tasks, while no testSlowAsan invocation replaces that coverage. This is particularly harmful for WriteStackTracesAfterClassUnloadTest, whose own documentation says non-sanitized runs can stay green on the use-after-free and that ASan/UBSan is the authoritative signal; include asan in a slow-suite workflow so this regression remains detectable.

AGENTS.md reference: AGENTS.md:L324-L328

Useful? React with 👍 / 👎.

slow_tests: true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Namespace artifacts by test suite

For every normal release validation, this call and pre-release-tests both instantiate the full debug matrix, but test_workflow.yml names uploaded artifacts only by platform, JDK, and configuration (for example, line 170), without distinguishing regular from slow tests. Since actions/upload-artifact artifacts are immutable and names must be unique within the workflow run, the second debug job to upload each name fails, causing one of these required reusable-workflow jobs to fail and blocking create-release; include the suite in every artifact name.

Useful? React with 👍 / 👎.


create-release:
needs: [validate-inputs, pre-release-tests]
if: always() && needs.validate-inputs.result == 'success' && (needs.pre-release-tests.result == 'success' || needs.pre-release-tests.result == 'skipped')
needs: [validate-inputs, pre-release-tests, pre-release-slow-tests]
if: always() && needs.validate-inputs.result == 'success' && (needs.pre-release-tests.result == 'success' || needs.pre-release-tests.result == 'skipped') && (needs.pre-release-slow-tests.result == 'success' || needs.pre-release-slow-tests.result == 'skipped')
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
id-token: write # Needed to federate a token for the bump-PR create-release step
steps:
- name: Check test results
if: ${{ inputs.dry_run != true && inputs.skip_tests != true && inputs.release_type != 'retag' && needs.pre-release-tests.result != 'success' }}
if: ${{ inputs.dry_run != true && inputs.skip_tests != true && inputs.release_type != 'retag' && (needs.pre-release-tests.result != 'success' || needs.pre-release-slow-tests.result != 'success') }}
run: |
echo "::error::Pre-release tests failed. Cannot proceed with release."
exit 1
Expand Down
20 changes: 15 additions & 5 deletions .github/workflows/test_workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ on:
required: false
type: boolean
default: false
slow_tests:
description: "Run the slow/e2e test suite (testSlow<Config>) instead of the regular suite"
required: false
type: boolean
default: false

permissions:
contents: read
Expand Down Expand Up @@ -133,7 +138,7 @@ jobs:

for attempt in $(seq 1 $MAX_ATTEMPTS); do
mkdir -p build/logs
${GRADLEW_PREFIX} ./gradlew -PCI -PkeepJFRs ${{ inputs.skip_gtest == true && '-Pskip-gtest' || '' }} :ddprof-test:test${{ matrix.config }} --no-daemon --parallel --build-cache --no-watch-fs 2>&1 \
${GRADLEW_PREFIX} ./gradlew -PCI -PkeepJFRs ${{ inputs.skip_gtest == true && '-Pskip-gtest' || '' }} :ddprof-test:test${{ inputs.slow_tests && 'Slow' || '' }}${{ matrix.config }} --no-daemon --parallel --build-cache --no-watch-fs 2>&1 \
| tee -a build/test-raw.log \
| python3 -u .github/scripts/filter_gradle_log.py
EXIT_CODE=${PIPESTATUS[0]}
Expand Down Expand Up @@ -273,7 +278,7 @@ jobs:
echo "JAVA_VERSION=${JAVA_VERSION}"

mkdir -p build/logs
./gradlew -PCI -PkeepJFRs :ddprof-test:test${{ matrix.config }} --no-daemon --parallel --build-cache --no-watch-fs 2>&1 \
./gradlew -PCI -PkeepJFRs :ddprof-test:test${{ inputs.slow_tests && 'Slow' || '' }}${{ matrix.config }} --no-daemon --parallel --build-cache --no-watch-fs 2>&1 \
| tee -a build/test-raw.log \
| python3 -u .github/scripts/filter_gradle_log.py
EXIT_CODE=${PIPESTATUS[0]}
Expand Down Expand Up @@ -387,10 +392,14 @@ jobs:
- name: Setup OS
if: steps.set_enabled.outputs.enabled == 'true'
run: |
# ports.ubuntu.com's IPv6 route is intermittently unreachable from GH-hosted
# aarch64 runners; force IPv4 so apt doesn't stall/fail on a dead IPv6 path,
# and retry on top in case of other transient connection blips.
printf 'Acquire::ForceIPv4 "true";\nAcquire::Retries "5";\n' | sudo tee /etc/apt/apt.conf.d/99force-ipv4 >/dev/null
sudo apt update -y
sudo apt remove -y g++
sudo apt autoremove -y
sudo apt install -y curl zip unzip clang make build-essential binutils gdb
sudo apt install -y curl zip unzip clang make build-essential binutils gdb libgtest-dev libgmock-dev
# Install debug symbols for system libraries
sudo apt install -y libc6-dbg
if [[ ${{ matrix.java_version }} =~ "-zing" ]]; then
Expand Down Expand Up @@ -448,7 +457,7 @@ jobs:

for attempt in $(seq 1 $MAX_ATTEMPTS); do
mkdir -p build/logs
${GRADLEW_PREFIX} ./gradlew -PCI -PkeepJFRs ${{ inputs.skip_gtest == true && '-Pskip-gtest' || '' }} :ddprof-test:test${{ matrix.config }} --no-daemon --parallel --build-cache --no-watch-fs 2>&1 \
${GRADLEW_PREFIX} ./gradlew -PCI -PkeepJFRs ${{ inputs.skip_gtest == true && '-Pskip-gtest' || '' }} :ddprof-test:test${{ inputs.slow_tests && 'Slow' || '' }}${{ matrix.config }} --no-daemon --parallel --build-cache --no-watch-fs 2>&1 \
| tee -a build/test-raw.log \
| python3 -u .github/scripts/filter_gradle_log.py
EXIT_CODE=${PIPESTATUS[0]}
Expand Down Expand Up @@ -565,7 +574,8 @@ jobs:
docker run --cpus 4 --rm -v /tmp:/tmp -v "${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}" -w "${GITHUB_WORKSPACE}" alpine:3.21 /bin/sh -c "
\"$GITHUB_WORKSPACE/.github/scripts/test_alpine_aarch64.sh\" \
\"${{ github.sha }}\" \"musl/${{ matrix.java_version }}-${{ matrix.config }}-aarch64\" \
\"${{ matrix.config }}\" \"${{ env.JAVA_HOME }}\" \"${{ env.JAVA_TEST_HOME }}\"
\"${{ matrix.config }}\" \"${{ env.JAVA_HOME }}\" \"${{ env.JAVA_TEST_HOME }}\" \
\"${{ inputs.slow_tests }}\"
" 2>&1 \
| tee -a build/test-raw.log \
| python3 -u .github/scripts/filter_gradle_log.py
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,23 @@ class ProfilerTestPlugin : Plugin<Project> {
)
}

/**
* Task name for a given config, shared between the glibc Test task and the musl Exec task
* so both platforms expose the same testXxx/testSlowXxx naming.
*/
private fun testTaskName(configName: String, slow: Boolean): String {
val capitalized = configName.replaceFirstChar { it.uppercase() }
return if (slow) "testSlow$capitalized" else "test$capitalized"
}

private fun testTaskDescription(configName: String, slow: Boolean, platformSuffix: String = ""): String {
return if (slow) {
"Runs the slow/e2e test suite with the $configName library variant$platformSuffix"
} else {
"Runs unit tests with the $configName library variant$platformSuffix"
}
}

/**
* Create native Test task for glibc/macOS (normal path).
* Uses Gradle's Test task with -Ptests property support.
Expand All @@ -253,11 +270,13 @@ class ProfilerTestPlugin : Plugin<Project> {
extension: ProfilerTestExtension,
testConfig: TestTaskConfiguration,
testCfg: Configuration,
sourceSets: SourceSetContainer
sourceSets: SourceSetContainer,
slow: Boolean = false
) {
project.tasks.register("test${testConfig.configName.replaceFirstChar { it.uppercase() }}", Test::class.java) {
val taskName = testTaskName(testConfig.configName, slow)
project.tasks.register(taskName, Test::class.java) {
val testTask = this
testTask.description = "Runs unit tests with the ${testConfig.configName} library variant"
testTask.description = testTaskDescription(testConfig.configName, slow)
testTask.group = "verification"
testTask.onlyIf { testConfig.isActive && !project.hasProperty("skip-tests") }

Expand All @@ -271,7 +290,14 @@ class ProfilerTestPlugin : Plugin<Project> {
testTask.classpath = testConfig.testClasspath

// Use JUnit Platform
testTask.useJUnitPlatform()
testTask.useJUnitPlatform {
val platformOptions = this
if (slow) {
platformOptions.includeTags("slow")
} else {
platformOptions.excludeTags("slow")
}
}

// Configure Java executable - bypasses toolchain system
testTask.setExecutable(PlatformUtils.testJavaExecutable())
Expand Down Expand Up @@ -365,11 +391,13 @@ class ProfilerTestPlugin : Plugin<Project> {
extension: ProfilerTestExtension,
testConfig: TestTaskConfiguration,
testCfg: Configuration,
sourceSets: SourceSetContainer
sourceSets: SourceSetContainer,
slow: Boolean = false
) {
project.tasks.register("test${testConfig.configName.replaceFirstChar { it.uppercase() }}", Exec::class.java) {
val taskName = testTaskName(testConfig.configName, slow)
project.tasks.register(taskName, Exec::class.java) {
val execTask = this
execTask.description = "Runs unit tests with the ${testConfig.configName} library variant (musl workaround)"
execTask.description = testTaskDescription(testConfig.configName, slow, " (musl workaround)")
execTask.group = "verification"
execTask.onlyIf { testConfig.isActive && !project.hasProperty("skip-tests") }

Expand Down Expand Up @@ -405,6 +433,14 @@ class ProfilerTestPlugin : Plugin<Project> {
allArgs.add("-Dtest.filter=$testsFilter")
}

// Carve out the "slow" suite the same way the glibc Test task does via
// useJUnitPlatform { includeTags/excludeTags }
if (slow) {
allArgs.add("-Dtest.tags.include=slow")
} else {
allArgs.add("-Dtest.tags.exclude=slow")
}

// Classpath (includes custom test runner)
allArgs.add("-cp")
allArgs.add(testConfig.testClasspath.asPath)
Expand Down Expand Up @@ -508,9 +544,11 @@ class ProfilerTestPlugin : Plugin<Project> {
if (isMuslSystem) {
project.logger.info("Creating Exec task for $configName (musl workaround, LIBC=${System.getenv("LIBC")})")
createExecTestTask(project, extension, testConfig, testCfg, sourceSets)
createExecTestTask(project, extension, testConfig, testCfg, sourceSets, slow = true)
} else {
project.logger.info("Creating Test task for $configName (glibc/macOS, LIBC=${System.getenv("LIBC")})")
createTestTask(project, extension, testConfig, testCfg, sourceSets)
createTestTask(project, extension, testConfig, testCfg, sourceSets, slow = true)
}

// Create application tasks for specified configs
Expand Down Expand Up @@ -654,22 +692,25 @@ class ProfilerTestPlugin : Plugin<Project> {
}
}

// Wire up gtest -> test dependencies (C++ tests run before Java tests)
// Wire up gtest -> test dependencies (C++ tests run before Java tests).
// Both the regular and slow/e2e test tasks get the same gtest dependency: each is an
// independent Gradle invocation (e.g. a separate CI job), so gtest coverage from a
// "sibling" regular test job doesn't carry over to a testSlow-only invocation.
project.gradle.projectsEvaluated {
configNames.forEach { cfgName ->
val capitalizedCfgName = cfgName.replaceFirstChar { it.uppercaseChar() }
val testTaskName = "test$capitalizedCfgName"
val testTask = project.tasks.findByName(testTaskName)
val profilerLibProject = project.rootProject.findProject(profilerLibProjectPath)

if (profilerLibProject != null && testTask != null) {
// gtest runs before test (C++ unit tests run before Java integration tests)
val gtestTaskName = "gtest${capitalizedCfgName}"
try {
val gtestTask = profilerLibProject.tasks.named(gtestTaskName)
testTask.dependsOn(gtestTask)
} catch (e: org.gradle.api.UnknownTaskException) {
project.logger.info("Task $gtestTaskName not found in $profilerLibProjectPath - gtest may not be available")
val gtestTaskName = "gtest${capitalizedCfgName}"

listOf("test$capitalizedCfgName", "testSlow$capitalizedCfgName").forEach { taskName ->
val testTask = project.tasks.findByName(taskName)
if (profilerLibProject != null && testTask != null) {
try {
val gtestTask = profilerLibProject.tasks.named(gtestTaskName)
testTask.dependsOn(gtestTask)
} catch (e: org.gradle.api.UnknownTaskException) {
project.logger.info("Task $gtestTaskName not found in $profilerLibProjectPath - gtest may not be available")
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.datadoghq.profiler.junit.RetryTest;

import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.params.provider.ValueSource;
Expand All @@ -27,6 +28,7 @@
* Exercises the race by churning short-lived threads while calling dump() repeatedly.
* A SIGSEGV would abort the JVM and fail the test with a non-zero exit code.
*/
@Tag("slow")
public class DumpWhileChurningThreadsTest extends CStackAwareAbstractProfilerTest {

private static final int TEST_DURATION_SECS = 10;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import com.datadoghq.profiler.Platform;

import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.api.TestTemplate;

import com.datadoghq.profiler.junit.CStack;
import com.datadoghq.profiler.junit.RetryTest;

@Tag("slow")
public class ObjectSampleDumpSmokeTest extends JfrDumpTest {
public ObjectSampleDumpSmokeTest(@CStack String cstack) {
super(cstack);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.datadoghq.profiler.memleak;

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;

Expand Down Expand Up @@ -53,6 +54,7 @@
* <li>cleanupUnreferencedMethods() must not SIGSEGV when freeing the line number table</li>
* </ol>
*/
@Tag("slow")
public class CleanupAfterClassUnloadTest extends AbstractDynamicClassTest {

// AGE_THRESHOLD in C++ is 3; run 4 dumps to ensure cleanup fires
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.datadoghq.profiler.memleak;

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

import java.io.IOException;
Expand Down Expand Up @@ -49,6 +50,7 @@
* <li>Combined cleanup: method_map cleanup + class unloading</li>
* </ul>
*/
@Tag("slow")
public class GetLineNumberTableLeakTest extends AbstractDynamicClassTest {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.datadoghq.profiler.memleak;

import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.objectweb.asm.ClassWriter;
Expand Down Expand Up @@ -86,6 +87,7 @@
* is evidence of nothing more than "no crash was hit this time" for whatever code paths happened
* to be exercised.
*/
@Tag("slow")
public class JMethodIDInvalidationStressTest extends AbstractDynamicClassTest {

private static final int CHURN_THREADS = 4;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/
package com.datadoghq.profiler.memleak;

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;

Expand Down Expand Up @@ -63,6 +64,7 @@
* covered by {@code CleanupAfterClassUnloadTest}.</li>
* </ul>
*/
@Tag("slow")
public class WriteStackTracesAfterClassUnloadTest extends AbstractDynamicClassTest {

@Override
Expand Down
Loading
Loading