Skip to content

feat(parallel): per-VU iteration counter for customParallelIterations - #1555

Open
scriptonist wants to merge 9 commits into
developfrom
feat/per-vu-variables-parallel-iterations
Open

feat(parallel): per-VU iteration counter for customParallelIterations#1555
scriptonist wants to merge 9 commits into
developfrom
feat/per-vu-variables-parallel-iterations

Conversation

@scriptonist

@scriptonist scriptonist commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

What

Per-partition (per-VU) iteration counter for customParallelIterations mode, so pm.info.iteration / pm.info.iterationCount and pm.variables behave correctly when the host (perftest) drives the parallel-iteration loop itself.

Previously every host-driven loop re-ran at cursor iteration 0, so scripts always saw pm.info.iteration === 0. Now the counter increments per loop for each VU and resets on VU recycle.

Changes

  • Per-VU iteration counterRun.isCustomParallelIterations as the mode gate; Partition.loopIteration/stopped fields; cursor cycles raised to MAX_SAFE_INTEGER in custom mode; runSinglePartition seeds cursor.iteration from the per-VU counter.
  • Loop guards — the end-of-partition early-return and the coords.cr auto-loop are gated on !isCustomParallelIterations, so the host (not the runtime) drives the next loop.
  • Full-fresh-on-reusestopSinglePartition resets loopIteration, re-clones variables, resets the cookie jar, and sets stopped = true; updatePartitionVariables drops late writes from in-flight scripts so a dead VU's mutations don't leak into the next VU on that slot.
  • Sentinel cycles + eof trigger — the cursor handed to the sandbox carries cycles: -1; the sandbox renders that as pm.info.iterationCount === Infinity (total is host-driven and unknowable). The eof iteration trigger payload carries pre-rollover coords in custom mode so the host attributes completed work to the correct iteration; runtime-managed mode is unchanged.
  • Run completion in custom mode — completion now settles the stored run callback instead of bypassing it, disarming the global run timeout that otherwise re-fired the host callback ~3 minutes after the run finished.

Temporary: vendored postman-sandbox

The -1 → Infinity transform is not yet in a published postman-sandbox release, so this PR vendors a prebuilt tarball (vendor/postman-sandbox-6.7.2-per-vu-variables.tgz, built from postman-sandbox feat/per-vu-variables-parallel-iterations @ 75d4caf) as a file: dependency.

  • vendor/ is npm-ignored — the tarball cannot ship in a published runtime.
  • test/system/repository.test.js exempts only this exact file: spec from the exact-semver rule.
  • Do not release postman-runtime from this state. Once the sandbox change ships, re-point package.json to the published version and revert per the checklist in vendor/README.md.

Tests

  • test/unit/custom-parallel-iterations.test.js — construction invariants, mode gate, counter monotonicity, reset/late-write behavior, sandbox cursor sentinel.
  • test/integration/runner-spec/customParallelIterations.test.js — end-to-end run.start()startParallelIteration()/stopParallelIteration() loops against postman-echo, including stop/restart variable isolation and a runtime-managed regression test. The pm.info.iterationCount assertion is capability-gated: exact Infinity with the vendored sandbox, exact -1 (sentinel passthrough) with a published one — no re-edit needed when the pin changes.

- Add Run.isCustomParallelIterations as the single source of truth for
  the mode gate (Change 0)
- Initialize Partition.loopIteration=0 and Partition.stopped=false
  (Change 1, 6c prep)
- Raise cursor.cycles to MAX_SAFE_INTEGER in custom mode so seek
  doesn't trip the bounds guard (Change 2)
- Increment partition.cursor.iteration on each runSinglePartition call
  in custom mode (Change 3)
- Expose Partition.resetVariables() public helper for the full-fresh-
  on-reuse contract landing in a later change (Change 6b prep)

Tests: 12 new unit cases in test/unit/custom-parallel-iterations.test.js
covering construction-time invariants, the Run gating helper, and
multi-loop counter monotonicity. All 449 unit tests pass.
- Change 4: gate the end-of-partition early-return at parallel.command:103
  on !isCustomParallelIterations. Without this, loop 2+ would short-
  circuit (coords.iteration === startIndex + partitionCycles is true
  every loop in custom mode because startIndex=0, partitionCycles=1)
  and the runtime would skip every item.
- Change 5: in the coords.cr block, after firing iteration trigger,
  early-return next() in custom mode. The host (perftest) drives the
  next loop via startParallelIteration; the runtime must not auto-loop
  in parallel. beforeIteration also no longer fires here in custom
  mode — startParallelIteration's own parallel-command invocation
  carries start:true and fires it.

Tests: 4 new unit cases on the parallel processor (with mocked
context) — Change 4 in custom + runtime-managed modes, Change 5 in
custom + runtime-managed modes. 453 passing (was 449).
- Change 6: stopSinglePartition resets partition.loopIteration to 0,
  re-clones partition.variables via the resetVariables() helper, and
  sets partition.stopped=true. Recycled partitions behave as brand-new
  VUs — pm.info.iteration === 0 with a fresh pm.variables scope. Gated
  on isCustomParallelIterations so runtime-managed mode is untouched.
- Change 6c: updatePartitionVariables drops writes when
  partition.stopped is true. Guards the late-write race where a
  script in flight at stopSinglePartition time finishes after the
  variables re-clone, otherwise leaking the dead VU's pm.variables
  mutations into the next VU's fresh scope.

Tests: 9 new unit cases — loopIteration reset, stopped flag toggle,
variables re-clone, flag clear on next runSinglePartition, full-cycle
regression (stop→start→counter=0), runtime-managed-mode non-mutation,
late-write drop happy + race + missing-partition paths. 462 unit tests
passing (was 453).
…7, 9)

- Change 7: at the host.execute call site in event.command.js,
  transform cursor.cycles to -1 when isCustomParallelIterations is
  true. -1 is the wire sentinel that survives JSON/structured-clone
  encoding (Infinity would JSON.stringify to null,
  Number.MAX_SAFE_INTEGER would leak to pm.info.iterationCount).
  Sandbox-side rendering of -1 -> Infinity is the matching change in
  postman-sandbox/lib/sandbox/pmapi.js. Helper factored out as
  applySandboxCursorSentinel and exposed for unit tests.
- Change 9: in the eof branch of parallel.command, fire the iteration
  trigger with payload.coords ('loop just completed') in custom mode
  rather than the post-rollover snapshot. Gated to custom mode to
  preserve existing Newman/desktop semantics — flipping the global
  contract risks silent downstream regressions we can't audit from
  inside postman-runtime.

Tests: 5 new unit cases — sentinel transform happy + non-mutation +
no-op paths, eof trigger custom + runtime-managed payloads.
467 unit tests passing (was 462).
…ions

Self-contained driver that exercises the perftest invocation pattern:
runner.run() -> run.start() -> startParallelIteration() loop until
maxLoops -> abort. Verifies the full chain: runtime drives the loop ->
host.execute hands the (sentinel-transformed) cursor to the sandbox ->
script reads pm.info.iteration + pm.info.iterationCount.

Tests cover:
- T1: first loop sees iteration=0 (via runtime trigger cursor)
- T3: monotonic iteration across 3 loops ([0, 1, 2])
- T2/loop-2-runs-all-items: no skip-everything regression
- T4/T5 trigger counts: iteration + beforeIteration fire once per loop
- T6/T6b: stop+restart resets cursor.iteration to 0 AND re-clones
  pm.variables (verified via in-script marker)
- T10 regression: maxConcurrency=2 mode still completes normally
- T12: pm.info.iterationCount === Infinity end-to-end (cross-repo
  wire contract verified)

Note: T12 requires the matching postman-sandbox change (cycles===-1
rendered as Infinity in pmapi.js). Until the sandbox version pinned
in package.json is bumped, T12 fails locally — run
'node npm/cache.js' in the local postman-sandbox repo and copy
.cache/bootcode.js into node_modules/postman-sandbox/.cache/ to
verify locally before release.
Lint: use object spread instead of Object.assign, repo noop convention
for empty test callbacks, and wrap/format long lines per style rules.

The per-partition cookie jar unit test mocked only the raw
customParallelIterations option, so after stopSinglePartition migrated
to the derived isCustomParallelIterations flag the custom-mode branch
never executed. Mock now mirrors real Run construction; assertions
unchanged.
Custom-mode runs completed via triggerStopAction -> triggers(null)
without settling the stored _process completion callback, leaving the
3-minute global timeout armed, which later re-fired the host callback
with a spurious timeout error. triggerStopAction now routes completion
through the stored process callback (clearing it first) so the
timeout-settling wrapper runs; runtime-managed mode is unaffected.

Integration tests: gate the pm.info.iterationCount assertion on
sandbox capability - exact -1 (sentinel passthrough) with current
postman-sandbox, exact Infinity once the sandbox-side transform ships;
add once-guards around mocha callbacks. Add unit coverage for the
custom-mode completion callback path.
Temporarily bundle postman-sandbox 6.7.2-per-vu.0 (branch
feat/per-vu-variables-parallel-iterations, commit 75d4caf) as a file:
dependency so CI exercises the cycles -1 -> pm.info.iterationCount
Infinity transform before it ships upstream. The capability-gated
integration assertion now runs its Infinity branch.

Re-point to the published postman-sandbox release and drop vendor/
once the sandbox change ships (see vendor/README.md).
vendor/ is npm-ignored so the temporary vendored postman-sandbox
tarball can never ship in a published runtime, and the repository
system test exempts only the exact vendored file: spec from the
exact-semver dependency rule. Both changes are temporary and listed
in the vendor/README.md removal checklist for when the sandbox change
ships upstream.
@scriptonist
scriptonist marked this pull request as ready for review July 22, 2026 13:56
@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.73684% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.38%. Comparing base (7073999) to head (8d802fd).

Files with missing lines Patch % Lines
lib/runner/partition-manager.js 92.10% 2 Missing and 1 partial ⚠️

❌ Your patch check has failed because the patch coverage (94.73%) is below the target coverage (100.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #1555      +/-   ##
===========================================
+ Coverage    76.19%   76.38%   +0.18%     
===========================================
  Files           50       50              
  Lines         4020     4052      +32     
  Branches      1172     1181       +9     
===========================================
+ Hits          3063     3095      +32     
  Misses         721      721              
  Partials       236      236              
Flag Coverage Δ
integration 67.39% <78.94%> (+0.16%) ⬆️
legacy 35.46% <7.01%> (-0.19%) ⬇️
unit 44.94% <82.45%> (+0.93%) ⬆️

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant