test_r_core/worker.rs
1//! Worker-process metadata exposed to user-defined dependency factories.
2//!
3//! The test runner spawns one worker subprocess per test thread when output
4//! capturing is on (see [`crate::sync`]/[`crate::tokio`]). Each spawned
5//! worker is assigned a zero-based **worker index** by the parent. `PerWorker`
6//! dependency constructors can read that index via [`worker_index`] to seed
7//! per-worker state (for example, a unique-id counter that must not collide
8//! with other workers).
9//!
10//! When the test harness runs without spawning workers (the parent process
11//! itself runs every test, e.g. under `--nocapture`, when no `Shared` deps
12//! force serial execution, or when nothing requires capture in the first
13//! place), [`worker_index`] returns `0`.
14
15use std::sync::OnceLock;
16
17static WORKER_INDEX: OnceLock<usize> = OnceLock::new();
18
19/// Sets the worker index for the current process.
20///
21/// Called by the test runner entry points (`test_runner_sync` /
22/// `test_runner_tokio`) at startup when the parent passed
23/// `--worker-index <N>` on the command line. May be called at most once per
24/// process; subsequent calls are silently ignored so the first observed value
25/// wins (this matches `OnceLock` semantics and protects against accidental
26/// re-initialisation from multiple test harnesses linked into the same
27/// binary).
28///
29/// Crate-private: user code must not call this. It is only meaningful when
30/// invoked by the test runner before any `PerWorker` constructor runs.
31pub(crate) fn set_worker_index(idx: usize) {
32 // Ignore double-init: keep the first observed value (matches OnceLock).
33 let _ = WORKER_INDEX.set(idx);
34}
35
36/// Returns the zero-based worker index assigned to this OS process.
37///
38/// This is a **process-level** identifier, not a per-test-thread identifier.
39/// Each spawned worker subprocess has at most one index; the top-level
40/// parent and any "no spawn workers" execution path (e.g. `--nocapture`,
41/// no captured-output requirement, or `Shared` deps forcing single-thread
42/// execution) observes `0`.
43///
44/// # When this is useful
45///
46/// Only `PerWorker` constructors and the tests they feed see meaningful
47/// per-worker values, because they are the only constructors that run
48/// inside spawned worker subprocesses. Parent-only scopes — `Shared`,
49/// `Cloneable`, `Hosted`, and `HostedRpc` — always run in the top-level
50/// parent and therefore always observe `0`. Do not use `worker_index()`
51/// to partition state inside those constructors; the answer is always 0.
52///
53/// # Use with `PerWorker` dependencies
54///
55/// Combine with `#[test_dep(scope = PerWorker)]` to seed per-worker state.
56/// For example, a unique-id counter that partitions its id space by worker
57/// so that two parallel workers cannot mint the same id:
58///
59/// ```ignore
60/// use std::sync::atomic::AtomicU16;
61/// use test_r::test_dep;
62///
63/// pub struct LastUniqueId {
64/// pub id: AtomicU16,
65/// }
66///
67/// #[test_dep(scope = PerWorker)]
68/// fn last_unique_id() -> LastUniqueId {
69/// // Reserve the high 8 bits for the worker index, leaving 8 bits per
70/// // worker for the local sequence. Adjust the shift to whatever fits
71/// // the underlying integer width.
72/// LastUniqueId {
73/// id: AtomicU16::new((test_r::worker_index() as u16) << 8),
74/// }
75/// }
76/// ```
77pub fn worker_index() -> usize {
78 *WORKER_INDEX.get().unwrap_or(&0)
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn defaults_to_zero_when_unset() {
87 // This test runs in the test-r-core unit test binary, where no
88 // worker subprocess is in play. `worker_index` must default to 0
89 // even when `set_worker_index` was never called.
90 //
91 // Note: because `WORKER_INDEX` is a process-global OnceLock,
92 // calling `set_worker_index` in another test would poison this
93 // observation. We deliberately do not call the setter from any
94 // unit test in this module.
95 assert_eq!(worker_index(), 0);
96 }
97}