Skip to content

Wire GitLab gtest jobs into the test quarantine mechanism [PROF-15902] - #974

Merged
jbachorik merged 21 commits into
mainfrom
gleeful-jade-caribou
Nov 11, 2026
Merged

jbachorik merged 21 commits into
mainfrom
gleeful-jade-caribou

Conversation

@jbachorik

@jbachorik jbachorik commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?:

Wires the five GitLab sanitizer gtest jobs (gtest-{asan,tsan,release}-{amd64,arm64}) into the flaky-test quarantine mechanism that already gates the Java cells and the GitLab el7 functional job. Each binary now emits JUnit XML (TEST-<binary>.xml under ddprof-lib/build/test-results/gtest/<config>/, uploaded as artifacts), every attempt runs all binaries instead of exiting at the first failure, and the job verdict comes from the shared run_tests_with_retry.sh → flake_report.py → ddprof-test/quarantine.txt path with cell names glibc-<config>-<arch>. A failing gtest only excuses the job when it is on the quarantine list for that cell.

To make gtest ids matchable, quarantine.covers() reduces gtest's decorated classnames (<inst>/<Suite>, <Suite>/<type-idx>) to the bare suite, so a class-wide Suite.* entry covers plain, value-parameterized, and typed variants; a decorated entry additionally covers its own instantiation's typed variants (a.B/0.* ⊇ a.B/0.c/1), while a.B.* stays narrower than a.B.c.*. The synthetic cell universe gains the jdk-less three-token gtest shape; flake_report.py stops counting gtest status="notrun" records as observed.

Motivation:

The gtest jobs failed hard on any test failure — no retry, no flake classification, no quarantine — so a flaky native test turned every branch-push pipeline red with no way to mute it, while the same failure in a Java cell only gated if it was not quarantined. Flaky gtests are a known recurring cost (see PROF-16577, PROF-16566).

Two design points worth calling out:

  • Signal deaths outrank plain failures. The per-attempt exit code is the first failure's status, except that a binary killed by a signal (rc > 128) takes priority: flake_report.py reads >128 as a run cut short, so a crashed binary's missing tests can never be excused behind another binary's quarantined failures.
  • find failure vs clean zero match. A failed find exits 2 with its own message; an empty match stays exit 1. Previously both collapsed to one path, and the zero-match case exited 0 (green with zero tests).

Fixes surfaced by the new runner (found because the runner now refuses to pass without evidence):

  • Exit-0 without XML is evidence loss. A binary that exits 0 without producing its XML (a test that skips via exit(0), or a results-directory creation failure) prints an ABORTED: marker and fails the attempt, so a green run with empty evidence is impossible. This is what exposed elfparser_ut.
  • elfparser_ut now resolves its test resources from the executable location. The old getcwd-based lookup missed under the GitLab runner (repo-root cwd) and the exit(0) skip path bypassed gtest XML output; Elf.readSymTable and the ElfReladyn suite have never actually run on CI. With /proc/self/exe-relative resolution and GTEST_SKIP, all 31 tests run and pass in the Linux container from both the repo root and /.
  • GTEST_ARCH and SANITIZER_LC are enum-validated (amd64|aarch64, asan|tsan|release): a runner-tag spelling (arm64) or a typo fails the job loudly instead of building a cell string that matches no quarantine entry and mutes nothing silently.
  • ReferenceChainsLifecycleTest.ChurnDuringConcurrentPassesStaysConsistent skips under abort-window starvation on optimized builds. Measured on glibc-release-amd64: 19 of 50 runs failed before the change, 0 after (35 pass / 15 skip). On fast builds the abort thread's duty cycle starves the root-enum retry (2s floor) inside the 2000-pass budget, so restarts == 0 and abort windows dominate pass reads — the test's own budget accounting, not the search under test. The skip mirrors the test's documented TSan skip and is gated on abort domination (aborted_passes >= total_passes/2), so a genuine low-abort never-terminates bug still gates. Accepted cost: a never-terminates defect whose churn also aborts at least half the passes would be skipped on every run rather than gated — disclosed for reviewer acknowledgment.

Additional Notes:

  • TSan keeps GTEST_DEATH_TEST_STYLE=threadsafe, moved to job variables; the asan cell exports RETRY_ON_NO_TEST_FAILURES=1 (same ASan-init/ASLR abort as the GitHub asan matrix, google/sanitizers#856).
  • Agentic review: sphinx (interactive pass, then range reviews over each pushed increment) found 0 Critical/High. Its MEDIUM (signal-rc masking) and the LOW mutation-pin gaps are fixed here; the latent cross-binary suite-name collision was dismissed (suite names are unique per binary today). The 60m sanitizer timeout is set from the old 30m budget plus the added second attempt, not from measured cell durations — revisit if a timeout kill ever masks the quarantine classification.
  • Accepted cost: a failing quarantined-everywhere gtest still consumes the full two-attempt retry budget, doubling that cell's native-suite wall time whenever it fails. The retry is what buys flake-vs-broken evidence; skipping it for quarantined failures would need the wrapper to know the list verdict before classifying.
  • The attempt aggregation makes a quarantined failure no longer hide the rest of the suite — a behavior change from the old first-failure exit, accepted deliberately.

How to test the change?:

  • .github/scripts/tests/test_quarantine.sh (78 assertions) — gtest id-shape matching, three-token cell validation (arm64 spelling rejected), overlapping/disjoint gtest entries, decorated-variant coverage in both directions, notrun handling, and end-to-end flake_report.py/run_tests_with_retry.sh cases on gtest XML.
  • .gitlab/scripts/tests/test_run_gtest_quarantine.sh (42 assertions, hermetic) — signal-vs-plain exit precedence, find failure vs zero match, exit-0-without-XML evidence loss, arch/config enum validation, asan-only retry flag, exact cell string, unset-variable guard; wired into both the GitHub ci.yml shell checks (incl. shellcheck) and GitLab shell-unit-tests.
  • Live: bash .gitlab/scripts/tests/test_run_gtest_quarantine.sh and .github/scripts/tests/test_quarantine.sh.

For Datadog employees:

  • If this PR touches code that signs or publishes builds or packages, or handles
    credentials of any kind, I've requested a security review (run the dd:platform-security-review
    skill, or file a request via the PSEC review form).
    bewaire also runs automatically on every PR.
  • This PR doesn't touch any of that.
  • JIRA: PROF-15902

covers() reduces gtest's decorated classnames (<inst>/<Suite>, <Suite>/<idx>)
to the bare suite so a class-wide entry covers parameterized variants;
the synthetic cell universe gains the jdk-less glibc-<config>-<arch> shape.
flake_report.py stops counting gtest status=notrun records as observed.
quarantine.txt documents the gtest id and cell shapes.
Replace the first-failure shell loop with run-gtest-quarantine.sh: gtest
JUnit XML per binary, all binaries per attempt, the shared retry/quarantine
wrapper with cell glibc-<config>-<arch>, and signal-deaths (rc > 128)
outranking plain failures so a crashed binary is never read as a
quarantined pass. TSan moves GTEST_DEATH_TEST_STYLE to job variables.
test_quarantine.sh gains gtest id-shape matching, 3-token cell validation,
and end-to-end flake_report/runner cases; new hermetic
test_run_gtest_quarantine.sh pins the attempt aggregation (signal rc wins,
find failure vs clean zero match) and the asan-only retry flag, wired into
both the GitHub shell checks and GitLab shell-unit-tests.
…d/cell construction

The gtest synthetic cells list the same configs as the Java axis; reusing
the constant removes a duplicate that could drift. test_run_gtest_quarantine.sh
gains T7/T8 pinning the exact cell string passed to the retry wrapper and
the unset-variable guard.
@jbachorik jbachorik added the AI label Nov 10, 2026
@jbachorik
jbachorik marked this pull request as ready for review November 10, 2026 21:05
@jbachorik
jbachorik requested a review from a team as a code owner November 10, 2026 21:05
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-10T21:08:04.817755Z bfa63d7 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@dd-octo-sts

dd-octo-sts Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Scan-Build Report

User:runner@runnervmwuk1x
Working Directory:/home/runner/work/java-profiler/java-profiler/ddprof-lib/src/test/make
Command Line:make -j4 all
Clang Version:Ubuntu clang version 21.1.8 (6ubuntu1)
Date:Wed Nov 11 17:31:41 2026

Bug Summary

Bug TypeQuantityDisplay?
All Bugs2
Logic error
Dereference of null pointer1
Unused code
Dead initialization1

Filter Results by File

Reports

Bug Group Bug Type ▾ File Function/Method Line Path Length
Unused codeDead initializationreferenceChains.cppapplyOomRamp4971
Logic errorDereference of null pointerperfEvents_linux.cppunregisterThread72026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bfa63d7300

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .gitlab/scripts/run-gtest-quarantine.sh Resolved
Comment thread .gitlab/sanitizer-tests/.gitlab-ci.yml Resolved
gtest suite ln (nativeThreadNameRace_ut) races thread death in the name
scanner and fails ~daily on quiet CI runners. Quarantined everywhere: the
failure is runner-load dependent, not config- or arch-specific. The entry
uses the bare suite id the XML carries, not the Jira title.

@datadog-prod-us1-3 datadog-prod-us1-3 Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bits Code Review: FAIL

Quarantined assertion failures can mask an independent ASan or TSan failure, turning a sanitizer job green despite memory errors or data races. The aggregate signal check does not protect against ordinary sanitizer exit codes.

Open Bits AI session

🤖 Bits Code Review · Commit bfa63d7

Comment thread .gitlab/scripts/run-gtest-quarantine.sh Resolved

@datadog-prod-us1-3 datadog-prod-us1-3 Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bits Code Review: FAIL

The PR routes GitLab gtests through retry/quarantine handling and adds a thread-name race quarantine, but its new ln suite ID never matches the actual test. The earlier defect remains unfixed: quarantined assertions can still mask independent ASan/TSan failures and turn sanitizer jobs green.

Open Bits AI session

🤖 Bits Code Review · Commit 46c4353

Comment thread ddprof-test/quarantine.txt Resolved
@dd-octo-sts

dd-octo-sts Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

CI Test Results

Run: #43033415602 | Commit: f01fddc | Duration: 20m 29s (longest job)

✅ All 32 test jobs passed

Status Overview

JDK glibc-aarch64/debug glibc-amd64/debug musl-aarch64/debug musl-amd64/debug
8 - ✅ - -
8-ibm - ✅ - -
8-j9 ✅ ✅ - -
8-librca - - ✅ ✅
8-orcl - ✅ - -
11 - ✅ - -
11-j9 ✅ ✅ - -
11-librca - - ✅ ✅
17 ✅ ✅ - -
17-graal ✅ ✅ - -
17-j9 ✅ ✅ - -
17-librca - - ✅ ✅
21 ✅ ✅ - -
21-graal ✅ ✅ - -
21-librca - - ✅ ✅
25 ✅ ✅ - -
25-graal ✅ ✅ - -
25-librca - - ✅ ✅

Legend: ✅ passed | ❌ failed | ⚪ skipped | 🚫 cancelled

Summary: Total: 32 | Passed: 32 | Failed: 0


Updated: 2026-11-11 17:41:58 UTC

@dd-octo-sts

dd-octo-sts Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

✅ All 40 integration tests passed

📊 Dashboard · 👷 Pipeline · 📦 5bad328a

…the gating

The quarantine entry named the ticket title's suite; the declaration is
TEST(NativeThreadNameRaceTest, ...), so the entry matched nothing -- pinned
by a test deriving the id from the source. A gtest binary aborted by a
sanitizer diagnostic (ASan 1, TSan 66) leaves no XML and no signal exit;
the runner now prints an ABORTED marker that flake_report.py reads as a
cut-short run, so another binary's quarantined failures cannot excuse it.
Tier 1 doc and AGENTS.md updated for the quarantine-gated contract.

@datadog-prod-us1-3 datadog-prod-us1-3 Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bits Code Review: FAIL

The PR adds retry/quarantine handling for GitLab gtests; the incorrect quarantine ID and handling of sanitizer exits without XML are fixed. One issue remains: TSan can write XML before exiting 66, allowing quarantined assertions in another binary to mask a real data race and turn the job green.

Open Bits AI session

🤖 Bits Code Review · Commit 7fd66d6

Comment thread .gitlab/scripts/run-gtest-quarantine.sh Resolved
…as a pass

TSan's defaults (halt_on_error=0, exitcode=66) let gtest finish and write a
passing JUnit XML before exiting 66 at shutdown -- a data race then bypasses
both the ABORTED marker and the >128 check. The tsan cell now runs with
halt_on_error=1: the first race kills the binary before the XML is written
and the ABORTED path gates it. Config-specific env setup hoisted above the
--attempt branch so the re-executed attempt sets it too.
… reject decorated suite entries

A leftover TEST-*.xml from a reused workspace would satisfy the -s check and
suppress the ABORTED marker; the attempt now clears the results dir first.
The worst case is build + two full passes, so the job timeout moves to 45m.
validate() rejects a decorated gtest suite name in a class-wide entry --
covers() reduces ids to the bare suite, so such an entry matches nothing.
The bare substring grep swept a release_helper binary parked in an asan_*
directory into the release cell and silently dropped an unprefixed binary
from the right one. The hermetic fakes now use the real
bin/gtest/<config>_<target>/<binary> layout.

@datadog-prod-us1-3 datadog-prod-us1-3 Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bits Code Review: PASS

More details

GitLab gtests now use shared retry/quarantine handling with per-binary XML. The quarantine ID is corrected, and TSan halts on the first race to route failures through abort detection; both previously reported defects are resolved, with no open findings.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Bits Code Review · Commit 0962e93

@datadog-prod-us1-3

datadog-prod-us1-3 Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Pipelines

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 5bad328 | Docs | View more details | Give us feedback!

…s budget

On optimized builds the passes run fast enough that the abort windows cover
nearly every pass read (measured 19/50 failures on release, 0/30 on debug):
the root-enum retry is starved for the whole kMaxPasses budget and no search
reaches a terminal outcome. Generalize the documented TSan skip to that case,
gated on abort domination (aborted_passes >= passes/2) so a genuine
never-terminates bug still gates. Failure asserts now carry the counters.
…ntry complaint; pin the covers boundary

gtest still runs and can exit 0 when it cannot write its XML, so an unchecked
mkdir yields a green-looking empty-evidence run (exit 3, like the rm -rf).
validate() claimed a decorated class-wide entry matches nothing - covers()
matches it directly and it is the narrower form, so both forms stay valid.
Pins: 'a.B.*' must not cover the nested id a.B.c.d.
…cover their typed variants

A binary that exits 0 without flushing its JUnit XML left no evidence and no
signal: the ABORTED path now fires on a missing XML regardless of the exit
code, and a zero exit is hardened to non-zero so the cut-short gate fires.
covers() now matches a decorated class-wide entry against its own
instantiation's typed variants (MyInst/WobblySuite.* covers
MyInst/WobblySuite/0.case). The hermetic fakes honor --gtest_output, pinning
the runner's XML-path and argument against renames.
The cwd-based lookup misses under the GitLab runner (repo-root cwd) and the
exit(0) skip path bypassed gtest XML output, tripping the runner's
exit-0-without-XML evidence-loss guard on every sanitizer cell.
…al caches

loadSymbolTable aliased mapped bytes through an ElfSymbol*; a crafted
sh_offset puts entries at addresses violating Elf64_Sym's 8-byte alignment,
which UBSan halts on (exposed by the elfparser_ut tests this PR's runner
made reachable). Copy each entry instead. The test's throwaway
CodeCacheArrays leaked every parsed library; drain them explicitly.
@jbachorik
jbachorik merged commit fb82a00 into main Nov 11, 2026
186 of 192 checks passed
@jbachorik
jbachorik deleted the gleeful-jade-caribou branch November 11, 2026 17:43
@github-actions github-actions Bot added this to the 1.53.0 milestone Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant