Skip to content

test(rccl): host microtests for four uncovered init.cc helpers - #10807

Open
pvallem wants to merge 2 commits into
developfrom
users/pvallem/rccl-init-microtests-four-helpers
Open

test(rccl): host microtests for four uncovered init.cc helpers#10807
pvallem wants to merge 2 commits into
developfrom
users/pvallem/rccl-init-microtests-four-helpers

Conversation

@pvallem

@pvallem pvallem commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

JIRA ID : AICOMRCCL-1685

What

Four src/init.cc helpers were at zero line coverage. This takes each to 100%, adding 26 host-only microtests. src/init.cc is untouched — test-only.

Function Lines Tests Mutants killed
setCommAbortFlags() :3884 0 → 10/10 6 17/19 (2 documented equivalents)
commCleanup() :3696 0 → 9/9 6 14/14
copyCommConfig() :3161 0 → 4/4 5 15/15
ncclGetUniqueId_impl() :358 0 → 12/12 9 17/17

init.cc functions covered: 57.97% → 62.32%.

Mutation is the acceptance gate, not coverage

Every block was proven by mutating the code it covers and confirming the tests die — 63 of 65 mutants killed. Coverage alone does not show whether a suite can see a wrong answer; earlier work on this file repeatedly hit suites sitting at 100% line and branch coverage with live mutants.

The two survivors are argued equivalent in place rather than papered over: a store reordering between two provably distinct locations with no intervening observer, and a memory_order_releaserelaxed relaxation that needs a second thread and TSan to observe.

Two fakes were hiding coverage

  • ncclParamGraphStreamOrdering() was hardcoded return 0, so envConfigOverride forced config.graphStreamOrdering = 0 on every call and the last field of ncclConfig_t was unobservable — a memcpy short by 4 bytes would have passed. Now reads through the g_loadParam seam with production's default.
  • The bootstrapGetUniqueId fake wrote only magic, leaving the rest zero, so a shortened memcpy in ncclGetUniqueId_impl was invisible. Now writes a byte-wise distinctive handle.

New seams

All follow the existing catalogue and are reset in ResetInitFakes(), with the reset verified load-bearing (deleting it makes tests fail under --gtest_shuffle): g_ncclCeFinalizeResult, g_ncclTunerPluginUnloadResult (+LastComm), g_cleanupCallOrder, g_ncclOsTopoGetStrFromSysResult (+Calls), g_ncclEnvPluginInitResult, g_bootstrapGetUniqueIdCalls, g_bootstrapHandleTemplate, and Recorder argument recording.

commCleanup's oracle is a call-order vector, since ordering (finalize → unload → free) is the thing worth testing there and a return code cannot show it.

Latent defects — pinned, not fixed

Found while covering these blocks. Current behaviour is pinned with a LATENT BUG note naming the line; src/ is unchanged.

Site Defect
:3889 childAbortFlagDev is dereferenced under a null check on a different pointer (childAbortFlag). Safe only because the two are assigned as a pair — but :2832 and :4077 both null childAbortFlag alone, and group.cc:584 repeats the shape. Retargeting the guard to the more natural-looking pointer turns ncclCommAbort into a write through a dangling device pointer.
:369 Recorder::instance().record(...)'s result is discarded, while all six sibling call sites NCCLCHECK it. A recorder failure while minting a unique id is reported as ncclSuccess, silently breaking replay for exactly the run being captured.
:3698 A transient cudaSetDevice failure returns before commFree, leaking the whole communicator (bootstrap sockets, channels, sharedRes, proxy state). The only caller, commReclaim :3812, just WARNs and moves on.
:3698 commCleanup never restores the caller's device, so ncclCommDestroy silently mutates the calling thread's current HIP device. mem_manager.cc:1094 does save/restore for the same pattern.
:3700-3701 A third-party tuner plugin returning non-success skips commFree — plugin code decides whether RCCL leaks a communicator.

Test plan

Binary Result
rccl-UnitTestsMicroInit 282/282
rccl-UnitTestsMicroInit-uncached 281/281
rccl-UnitTestsMicro 30/30
rccl-HostUnitTests 283/283
--gtest_shuffle --gtest_repeat=3, seeds 1/42/777 3/3 each
--gtest_repeat=10 10/10
--gtest_death_test_style=threadsafe pass
Hostile env (NCCL_CTA_POLICY, NCCL_HOSTID, HSA_*, ROCSHMEM_HEAP_SIZE, NCCL_DEBUG, NCCL_TOPO_DUMP_FILE) pass

No CMake or YAML change: all tests are in the existing InitMicrotest / InitMicrotestIsolated suites, already wildcarded by test_categories_micro_init.yaml. Built host-only against the hipified snapshot — no GPU, no librccl.so, no HIP runtime.

JIRA ID : AICOMRCCL-1685

Covers four functions that were at zero line coverage, each taken to 100%:

| function | tests | mutants killed |
|---|---|---|
| setCommAbortFlags (init.cc:3884)   | 6 | 17/19 (2 documented equivalents) |
| commCleanup (init.cc:3696)         | 6 | 14/14 |
| copyCommConfig (init.cc:3161)      | 5 | 15/15 |
| ncclGetUniqueId_impl (init.cc:358) | 9 | 17/17 |

Mutation, not coverage, is the acceptance gate: each block was proven by
mutating the code it covers and confirming the tests die. 63 of 65 mutants
killed; the two survivors are argued equivalent in place (a store reordering
between provably distinct locations, and a memory_order relaxation that needs
a second thread to observe).

New seams follow the existing catalogue and are reset in ResetInitFakes():
g_ncclCeFinalizeResult, g_ncclTunerPluginUnloadResult (+LastComm),
g_cleanupCallOrder, g_ncclOsTopoGetStrFromSysResult (+Calls),
g_ncclEnvPluginInitResult, g_bootstrapGetUniqueIdCalls, g_bootstrapHandleTemplate,
and Recorder argument recording.

Two fixes to fakes that were hiding coverage:
- ncclParamGraphStreamOrdering() was hardcoded to 0, so envConfigOverride
  forced config.graphStreamOrdering = 0 on every call and the last field of
  ncclConfig_t was unobservable -- a memcpy short by 4 bytes would have passed.
  It now reads through the g_loadParam seam with production's default.
- The bootstrapGetUniqueId fake wrote only `magic`, leaving the rest zero, so a
  shortened memcpy in ncclGetUniqueId_impl was invisible. It now writes a
  byte-wise distinctive handle.

src/init.cc is untouched. Latent defects found while covering these blocks are
pinned as current behaviour with a LATENT BUG note naming the line, not fixed:
init.cc:3889 (childAbortFlagDev guarded by childAbortFlag's null check),
init.cc:369 (Recorder::record return discarded where six sibling call sites
NCCLCHECK it), init.cc:3698 (a cudaSetDevice failure aborts teardown before
commFree, leaking the communicator; and the caller's current device is never
restored), init.cc:3700 (a tuner plugin returning non-success leaks the comm).

Verified: 283/283 and 282/282 uncached, rccl-UnitTestsMicro 30/30,
rccl-HostUnitTests 282/282, clean under --gtest_shuffle --gtest_repeat=3 across
three seeds, --gtest_death_test_style=threadsafe, and a hostile environment.
@pvallem
pvallem requested review from a team August 27, 2026 00:41
@therock-pr-bot

Copy link
Copy Markdown

✅ All Policy Checks Passed

Check Status Details
📝 PR Description ✅ Pass
Forbidden Files ✅ Pass
🧪 Unit Test ⚠️ Warning Error: Source/code files changed without an accompanying unit test.
Expected: add at least one test file named like test_<name>.py / test_<name>.cpp (or <name>_test.*).
Current: code file(s) changed: projects/rccl/test/host/fakes/bootstrap_stubs.cc, projects/rccl/test/host/fakes/init_fakes.cc, projects/rccl/test/host/fakes/init_fakes.h, projects/rccl/test/host/fakes/nccl_stubs.cc, projects/rccl/test/host/init-test.cc; no test file found
🚫 Draft PR 🔜 To Be Enabled
🚩 Feature Flag 🔜 To Be Enabled
📊 Code Coverage 🔜 To Be Enabled

🎉 All policy checks passed!

📖 Need help? See the Policy FAQ for details on every check and how to fix failures.

🙋 Wish to Override Policy?

…elpers

Conflict in test/host/init-test.cc: develop (#10808) lifted the inline NVTX
redirector block into fakes/nvtx_redirect.h, and this branch added the
MicroMalloc and tuner-finalize seams immediately above it. Kept both: the new
seams stay, the inline block is replaced by the shared header include.
@github-actions

Copy link
Copy Markdown
Contributor

RCCL Perf-Regression Gate: ⚠️ NO VERDICT (not measured)

This run did not produce a usable answer. It is neither a PASS nor a regression — treat the perf gate as not run for this change.

Why:

  • this report predates the trustworthiness flag, or a group could not be scored

Mode: detect (reference vs candidate)
Thresholds: small 17.2% · mid 12.3% · large 12.9%
Keys compared: 0 · Confirmed regressions: 0 · Inconclusive: 0
Provenance: n/a

Per-collective breakdown
group keys regressions inconclusive
all_gather_perf-d=bfloat16-default 0 0 0
all_gather_perf-d=float-default 0 0 0
all_reduce_perf-d=bfloat16-default 0 0 0
all_reduce_perf-d=float-default 0 0 0
broadcast_perf-d=bfloat16-default 0 0 0
broadcast_perf-d=float-default 0 0 0
reduce_scatter_perf-d=bfloat16-default 0 0 0
reduce_scatter_perf-d=float-default 0 0 0

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.

2 participants