Skip to content

test(coverage): instrument every test subprocess implicitly#9073

Closed
BridgeAR wants to merge 5 commits into
masterfrom
BridgeAR/2026-06-26-coverage-subprocess-chokepoint
Closed

test(coverage): instrument every test subprocess implicitly#9073
BridgeAR wants to merge 5 commits into
masterfrom
BridgeAR/2026-06-26-coverage-subprocess-chokepoint

Conversation

@BridgeAR

Copy link
Copy Markdown
Member

Summary

The integration coverage harness is meant to instrument every Node descendant a
test spawns, but two gaps let subprocess coverage slip through silently:

  1. The harness re-injected its NYC bootstrap by wrapping the public child_process
    methods. A caller that captured fork/spawn before the patch installed, or that
    overwrote NODE_OPTIONS to add its own loader (every ESM fixture), bypassed the
    wrappers and ran the child uninstrumented. Injection now happens at
    ChildProcess.prototype.spawn — the internal point every async spawn funnels
    through after the env is normalized into options.envPairs — so nothing a
    developer writes can escape it. The public-method wrappers remain for the
    synchronous spawns (which do not traverse the chokepoint) and as the fallback
    when a future Node reshapes the internal.
  2. test:plugins:ci ran under plain nyc, which never instruments a forked sandbox
    whose NODE_OPTIONS was replaced. A branch reached only from a spawned child —
    graphql's ESM @graphql-tools/executor wrap is the case that surfaced this —
    never appeared in coverage even though a test exercised it. test:plugins:ci now
    runs through the harness, with a new --instrument-main mode that also
    instruments the mocha process (plugin unit suites run product code there) and
    finalizes it like a sandbox. Both streams merge into the same
    coverage/node-<version> report the upload step already consumes, so no workflow
    changes are needed.

Why

The harness is implicit by design so a developer spawning a subprocess never has to
think about coverage. A "remember to call a helper at every spawn site" fix would be
forgotten the first time someone adds a site; the chokepoint enforces the invariant
structurally instead.

Test plan

  • coverage-child-process.spec.js passes on Node 18/20/22/24/26 (validated locally).
  • A plugin *:coverage run shows a sandbox-only branch (e.g. graphql's ESM executor wrap) covered in the merged report.

@dd-octo-sts

dd-octo-sts Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Overall package size

Self size: 6.37 MB
Deduped: 7.44 MB
No deduping: 7.44 MB

Dependency sizes | name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.2.0 | 104.26 kB | 843.44 kB | | opentracing | 0.14.7 | 194.81 kB | 194.81 kB | | dc-polyfill | 0.1.11 | 25.74 kB | 25.74 kB |

🤖 This report was automatically generated by heaviest-objects-in-the-universe

@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.79%. Comparing base (42b241d) to head (74f7568).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9073      +/-   ##
==========================================
+ Coverage   93.66%   93.79%   +0.12%     
==========================================
  Files         889      889              
  Lines       50841    50841              
  Branches    11799    11799              
==========================================
+ Hits        47621    47684      +63     
+ Misses       3220     3157      -63     
Flag Coverage Δ
aiguard 34.87% <ø> (-0.08%) ⬇️
aiguard-integration 41.82% <ø> (+0.03%) ⬆️
apm-bucket-0 38.37% <ø> (+3.46%) ⬆️
apm-bucket-1 44.10% <ø> (+3.70%) ⬆️
apm-bucket-2 40.90% <ø> (+3.49%) ⬆️
apm-capabilities-tracing 48.57% <ø> (ø)
apm-integrations-aerospike 32.88% <ø> (-0.31%) ⬇️
apm-integrations-confluentinc-kafka-javascript 43.15% <ø> (+3.04%) ⬆️
apm-integrations-couchbase 36.72% <ø> (+3.25%) ⬆️
apm-integrations-http 45.03% <ø> (+2.93%) ⬆️
apm-integrations-kafkajs 43.37% <ø> (+3.04%) ⬆️
apm-integrations-next 42.77% <ø> (+13.20%) ⬆️
apm-integrations-prisma 39.22% <ø> (+4.12%) ⬆️
apm-integrations-tedious 33.68% <ø> (-0.29%) ⬇️
appsec 57.36% <ø> (-0.08%) ⬇️
appsec-express_fastify_graphql 53.76% <ø> (-0.06%) ⬇️
appsec-integration 36.18% <ø> (+0.01%) ⬆️
appsec-kafka_ldapjs_lodash 43.55% <ø> (-0.06%) ⬇️
appsec-mongodb-core_mongoose_mysql 48.75% <ø> (-0.07%) ⬇️
appsec-next 27.99% <ø> (-0.07%) ⬇️
appsec-node-serialize_passport_postgres 47.99% <ø> (-0.06%) ⬇️
appsec-sourcing_stripe_template 45.55% <ø> (-0.06%) ⬇️
debugger 44.34% <ø> (+0.03%) ⬆️
instrumentations-bucket-0 28.14% <ø> (-0.08%) ⬇️
instrumentations-bucket-1 37.40% <ø> (-0.07%) ⬇️
instrumentations-bucket-10 40.42% <ø> (-0.07%) ⬇️
instrumentations-bucket-11 27.94% <ø> (-0.08%) ⬇️
instrumentations-bucket-12 28.66% <ø> (-0.08%) ⬇️
instrumentations-bucket-13 27.77% <ø> (-0.08%) ⬇️
instrumentations-bucket-2 30.22% <ø> (-0.07%) ⬇️
instrumentations-bucket-3 35.90% <ø> (-0.07%) ⬇️
instrumentations-bucket-4 28.06% <ø> (-0.08%) ⬇️
instrumentations-bucket-5 36.27% <ø> (-0.07%) ⬇️
instrumentations-bucket-6 38.25% <ø> (-0.08%) ⬇️
instrumentations-bucket-7 35.91% <ø> (-0.07%) ⬇️
instrumentations-bucket-8 36.95% <ø> (-0.07%) ⬇️
instrumentations-bucket-9 39.52% <ø> (-0.07%) ⬇️
instrumentations-instrumentation-couchbase 46.40% <ø> (ø)
instrumentations-integration-esbuild 24.80% <ø> (+0.05%) ⬆️
llmobs-ai_anthropic_bedrock 43.80% <ø> (+4.20%) ⬆️
llmobs-google-genai_langchain_vertex-ai 41.89% <ø> (+4.98%) ⬆️
llmobs-openai 43.99% <ø> (+4.35%) ⬆️
llmobs-sdk 43.59% <ø> (-0.08%) ⬇️
master-coverage 93.79% <ø> (?)
openfeature 37.79% <ø> (+0.03%) ⬆️
openfeature-unit 50.35% <ø> (ø)
platform-core_esbuild_instrumentations-misc 23.03% <ø> (ø)
platform-integration 47.44% <ø> (+0.03%) ⬆️
platform-shimmer_unit-guardrails_webpack 18.80% <ø> (ø)
plugins-bucket-0 38.32% <ø> (+1.93%) ⬆️
plugins-bucket-1 39.63% <ø> (+0.03%) ⬆️
plugins-bucket-11 42.14% <ø> (+3.61%) ⬆️
plugins-bucket-17 43.35% <ø> (+4.12%) ⬆️
plugins-bucket-18 44.69% <ø> (+2.53%) ⬆️
plugins-bucket-19 42.92% <ø> (+3.30%) ⬆️
plugins-bucket-20 46.59% <ø> (+3.47%) ⬆️
plugins-bucket-4 39.04% <ø> (+1.26%) ⬆️
plugins-bullmq_cassandra_cookie 46.29% <ø> (+6.46%) ⬆️
plugins-cookie-parser_crypto_dd-trace-api 38.39% <ø> (+5.09%) ⬆️
plugins-fetch_fs_generic-pool 43.49% <ø> (+7.37%) ⬆️
plugins-google-cloud-pubsub_grpc_handlebars 48.18% <ø> (+5.01%) ⬆️
plugins-hapi_hono_ioredis 41.50% <ø> (+3.64%) ⬆️
plugins-jest_knex_langgraph 37.22% <ø> (+4.54%) ⬆️
plugins-ldapjs_light-my-request_limitd-client 40.01% <ø> (+12.09%) ⬆️
plugins-lodash_mariadb_memcached 41.47% <ø> (+6.22%) ⬆️
plugins-mongodb_mongodb-core_mongoose 42.85% <ø> (+6.47%) ⬆️
plugins-multer_mysql_mysql2 41.72% <ø> (+6.55%) ⬆️
plugins-nats_node-serialize_opensearch 43.62% <ø> (+6.61%) ⬆️
plugins-passport-http_pino_postgres 42.47% <ø> (+6.87%) ⬆️
plugins-process_pug_redis 41.07% <ø> (+6.74%) ⬆️
plugins-undici_url_valkey 43.19% <ø> (+7.23%) ⬆️
plugins-vm_winston_ws 43.81% <ø> (+6.18%) ⬆️
profiling 43.63% <ø> (-0.05%) ⬇️
serverless-aws-sdk-aws-sdk 33.05% <ø> (-0.30%) ⬇️
serverless-aws-sdk-bedrockruntime 31.95% <ø> (-0.28%) ⬇️
serverless-aws-sdk-client 34.06% <ø> (-3.03%) ⬇️
serverless-aws-sdk-dynamodb 33.89% <ø> (-0.32%) ⬇️
serverless-aws-sdk-eventbridge 26.94% <ø> (-0.37%) ⬇️
serverless-aws-sdk-kinesis 37.17% <ø> (-0.30%) ⬇️
serverless-aws-sdk-lambda 34.34% <ø> (-0.32%) ⬇️
serverless-aws-sdk-s3 32.33% <ø> (-0.33%) ⬇️
serverless-aws-sdk-serverless-peer-service 39.35% <ø> (-0.19%) ⬇️
serverless-aws-sdk-sns 38.03% <ø> (-0.29%) ⬇️
serverless-aws-sdk-sqs 39.48% <ø> (+1.42%) ⬆️
serverless-aws-sdk-stepfunctions 32.98% <ø> (-0.27%) ⬇️
serverless-aws-sdk-util 47.82% <ø> (ø)
serverless-bucket-0 39.49% <ø> (+0.03%) ⬆️
serverless-lambda 34.32% <ø> (-0.09%) ⬇️
test-optimization-cucumber 52.71% <ø> (+0.31%) ⬆️
test-optimization-cypress 49.76% <ø> (+0.21%) ⬆️
test-optimization-jest 55.80% <ø> (+0.21%) ⬆️
test-optimization-mocha 53.68% <ø> (+0.11%) ⬆️
test-optimization-playwright-playwright-atr 43.62% <ø> (+0.12%) ⬆️
test-optimization-playwright-playwright-efd 43.91% <ø> (+0.12%) ⬆️
test-optimization-playwright-playwright-final-status 43.96% <ø> (+0.12%) ⬆️
test-optimization-playwright-playwright-impacted-tests 43.44% <ø> (+0.03%) ⬆️
test-optimization-playwright-playwright-reporting 43.53% <ø> (+0.12%) ⬆️
test-optimization-playwright-playwright-test-management 44.96% <ø> (+0.12%) ⬆️
test-optimization-playwright-playwright-test-span 44.81% <ø> (+0.11%) ⬆️
test-optimization-selenium 45.62% <ø> (+0.16%) ⬆️
test-optimization-testopt 48.30% <ø> (+0.12%) ⬆️
test-optimization-vitest 50.98% <ø> (+0.15%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@datadog-prod-us1-5

datadog-prod-us1-5 Bot commented Jun 25, 2026

Copy link
Copy Markdown

Tests

Fix all issues with BitsAI

⚠️ Warnings

🧪 1 Test failed in 1 job

All Green | all-green   GitHub Actions

esm with oracledb &gt;=5 (6.10.0) is instrumented destructure from with oracledb &gt;=5 (6.10.0)   View in Datadog
Timeout of 20000ms exceeded. For async tests and hooks, ensure &#34;done()&#34; is called; if returning a Promise, ensure it resolves. (/home/runner/work/dd-trace-js/dd-trace-js/packages/datadog-plugin-oracledb/test/integration-test/client.spec.js)

Error: Timeout of 20000ms exceeded. For async tests and hooks, ensure &#34;done()&#34; is called; if returning a Promise, ensure it resolves. (/home/runner/work/dd-trace-js/dd-trace-js/packages/datadog-plugin-oracledb/test/integration-test/client.spec.js)
    at listOnTimeout (node:internal/timers:559:17)
    at processTimers (node:internal/timers:502:7)

ℹ️ Info

No other issues found (see more)

❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 88.12% (+0.16%)

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 74f7568 | Docs | Datadog PR Page | Give us feedback!

@pr-commenter

pr-commenter Bot commented Jun 25, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-06-26 21:15:59

Comparing candidate commit 74f7568 in PR branch BridgeAR/2026-06-26-coverage-subprocess-chokepoint with baseline commit 42b241d in branch master.

📊 Benchmarking dashboard

Found 0 performance improvements and 0 performance regressions! Performance is the same for 2250 metrics, 36 unstable metrics.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

Unstable benchmarks

These benchmarks have a confidence interval too wide to call a change; treat them as noise rather than signal.

scenario:appsec-appsec-enabled-20

  • unstable execution_time [-199.723ms; +188.147ms] or [-5.384%; +5.072%]

scenario:appsec-appsec-enabled-24

  • unstable execution_time [-176.884ms; +185.140ms] or [-6.711%; +7.024%]

scenario:appsec-appsec-enabled-26

  • unstable execution_time [-207.255ms; +210.232ms] or [-8.280%; +8.399%]

scenario:appsec-appsec-enabled-with-attacks-26

  • unstable execution_time [-156403.516µs; +154955.049µs] or [-5.370%; +5.320%]

scenario:appsec-control-20

  • unstable execution_time [-162.539ms; +160.285ms] or [-9.295%; +9.166%]

scenario:appsec-control-24

  • unstable execution_time [-107.557ms; +109.940ms] or [-8.852%; +9.048%]

scenario:appsec-control-26

  • unstable execution_time [-100.366ms; +122.029ms] or [-8.383%; +10.192%]

scenario:debugger-line-probe-with-snapshot-default-24

  • unstable cpu_user_time [-3216.767ms; +3307.586ms] or [-33.531%; +34.478%]
  • unstable execution_time [-3239.480ms; +3326.640ms] or [-31.462%; +32.309%]
  • unstable instructions [-27611.5M instructions; +28466.1M instructions] or [-35.057%; +36.142%]
  • unstable max_rss_usage [-11863.916KB; +12347.916KB] or [-7.226%; +7.521%]
  • unstable throughput [-965.682op/s; +952.843op/s] or [-28.942%; +28.558%]

scenario:debugger-line-probe-with-snapshot-default-26

  • unstable cpu_user_time [-2969.880ms; +2880.938ms] or [-29.696%; +28.806%]
  • unstable execution_time [-2989.577ms; +2915.754ms] or [-27.933%; +27.243%]
  • unstable instructions [-26425.9M instructions; +25843.2M instructions] or [-31.743%; +31.043%]
  • unstable max_rss_usage [-9129.988KB; +9295.588KB] or [-5.678%; +5.781%]
  • unstable throughput [-632.355op/s; +660.327op/s] or [-20.243%; +21.139%]

scenario:debugger-line-probe-with-snapshot-minimal-26

  • unstable cpu_user_time [-2070.009ms; +3318.964ms] or [-22.106%; +35.443%]
  • unstable execution_time [-2110.825ms; +3348.961ms] or [-21.033%; +33.370%]
  • unstable instructions [-18.5G instructions; +29.5G instructions] or [-23.684%; +37.787%]
  • unstable max_rss_usage [-6.644MB; +10.381MB] or [-4.186%; +6.540%]
  • unstable throughput [-730.802op/s; +461.485op/s] or [-22.338%; +14.106%]

scenario:debugger-line-probe-without-snapshot-26

  • unstable cpu_user_time [-2137.319ms; +3390.077ms] or [-22.888%; +36.303%]
  • unstable execution_time [-2170.860ms; +3407.707ms] or [-21.643%; +33.975%]
  • unstable instructions [-18.9G instructions; +29.8G instructions] or [-24.317%; +38.400%]
  • unstable max_rss_usage [-6.683MB; +10.430MB] or [-4.205%; +6.563%]
  • unstable throughput [-745.374op/s; +480.212op/s] or [-22.730%; +14.644%]

scenario:dogstatsd-with-tags-20

  • unstable cpu_user_time [-446.635ms; +267.140ms] or [-9.133%; +5.462%]
  • unstable execution_time [-441.434ms; +266.763ms] or [-8.891%; +5.373%]
  • unstable throughput [-90466.988op/s; +151939.599op/s] or [-5.349%; +8.984%]

scenario:plugin-graphql-long-with-depth-and-collapse-off-24

  • unstable cpu_user_time [-277.472ms; +305.919ms] or [-6.119%; +6.746%]
  • unstable execution_time [-303.769ms; +335.168ms] or [-6.299%; +6.950%]
  • unstable max_rss_usage [-36.398MB; +44.868MB] or [-6.700%; +8.259%]
  • unstable throughput [-3.278op/s; +2.810op/s] or [-7.393%; +6.337%]

scenario:plugin-graphql-long-with-depth-off-20

  • unstable max_rss_usage [-6.096MB; +14.139MB] or [-3.992%; +9.258%]

scenario:test-optimization-large-suite-20

  • unstable max_rss_usage [-5093.936KB; +3570.269KB] or [-6.342%; +4.445%]

…tion

The coverage harness re-injects its NYC bootstrap into spawned children by
wrapping the public `child_process` methods. A caller that captured `fork` or
`spawn` before the patch installed, or one that overwrites `NODE_OPTIONS` to add
its own loader, slipped past those wrappers, so the child ran uninstrumented and
its coverage was dropped. Inject at `ChildProcess.prototype.spawn` instead, the
shared junction every async spawn passes through after the env is normalized into
`options.envPairs`, so no captured reference or env overwrite escapes it. The
public wrappers stay for the synchronous spawns, which do not pass through the
junction, and as the fallback for a future Node that reshapes the internal. The
envPairs merge matches `NODE_OPTIONS` case-insensitively so a Windows
`Node_Options` entry is rewritten in place rather than duplicated.

The wrappers go through `shimmer.wrap`, which copies the original's own properties
and symbols onto each wrapper. That keeps `util.promisify.custom` on `exec` and
`execFile`, the symbol `util.promisify` prefers over the callback form. Without it
a promisified call resolves the raw stdout string instead of `{ stdout, stderr }`
and uses the global Promise, which breaks the child_process plugin's Bluebird
tests.
@BridgeAR
BridgeAR force-pushed the BridgeAR/2026-06-26-coverage-subprocess-chokepoint branch 2 times, most recently from 06a9cca to 7eedb9c Compare June 26, 2026 01:03
`test:plugins:ci` ran under plain `nyc`, which only instruments the main process
and the sandboxes nyc spawn-wraps. Plugin tests that fork a sandbox after
overwriting `NODE_OPTIONS` (every ESM fixture) left their children uninstrumented,
so a branch reached only from a spawned child, like graphql's ESM
`@graphql-tools/executor` wrap, never showed up in coverage even though a test
exercised it.

Route `test:plugins:ci` through the coverage harness and add an `--instrument-main`
mode to `run-suite.js` that also instruments the mocha process (plugin unit suites
run product code there, not only in sandboxes) and finalizes its coverage like a
sandbox. Both streams merge into the same `coverage/node-<version>` report the
upload step already reads, so no workflow changes are needed.

child_process keeps the classic `nyc` flow instead: its tests spawn real OS
subprocesses and assert spans under tight per-test timeouts, and nyc's runtime
require-hook over the mocha process slows it enough to time them out. It has no
sandbox specs, so the harness would add nothing there anyway.
@BridgeAR
BridgeAR force-pushed the BridgeAR/2026-06-26-coverage-subprocess-chokepoint branch from 7eedb9c to 65bc8b8 Compare June 26, 2026 01:22
@BridgeAR
BridgeAR marked this pull request as ready for review June 26, 2026 14:47
@BridgeAR
BridgeAR requested a review from a team as a code owner June 26, 2026 14:47

@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: 0d532fc329

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread integration-tests/coverage/run-suite.js
BridgeAR and others added 2 commits June 26, 2026 20:09
The `--instrument-main` flow finalizes the repo root as if it were a sandbox,
and `finalizeSandbox` merges every JSON under `<repo>/.nyc_output/integration-tests`.
That directory is persistent, not a throwaway sandbox temp dir, so a second
`test:plugins:ci` run in the same checkout and Node version would fold the
previous run's raw coverage into the new report. Clear it before the spawn,
matching how `resetCollectorRoot` already wipes the collector dir.
@BridgeAR
BridgeAR marked this pull request as draft July 7, 2026 18:06
@BridgeAR

BridgeAR commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

Superseded with the V8 coverage switch

@BridgeAR BridgeAR closed this Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant