| Property | Value |
|---|---|
| Category | Reproducibility (JustDummies.Reproducibility) |
| Severity | 🔴 Error |
| Enabled by default | Yes |
Both seeding entry points return something the caller must keep, and both are silent when the result is thrown away.
Any.UseSeed(seed) returns the handle that closes the scope it opened. Dropping it means the scope can never be closed: as the method’s own documentation puts it, “failing to dispose leaves the seed pinned for whatever runs next in the same execution context”. Every later test flowing from that context silently stops being arbitrary — it replays one fixed sequence, and the tests become coupled to each other through draw order, so adding or reordering a test changes values in unrelated ones.
Any.WithSeed(seed) returns an isolated context and pins nothing at all. A discarded call is dead code at a site that reads as if the run had been seeded: the ambient Any.* entry points keep drawing unseeded, exactly as if the line were not there.
Noncompliant
Any.UseSeed(1234); // JD004: the scope is never closed
string reference = Any.String().Generate();
Any.WithSeed(1234); // JD004: pins nothing — the draw below is still unseeded
int quantity = Any.Int32().Positive().Generate();
Compliant
using IDisposable scope = Any.UseSeed(1234);
string reference = Any.String().Generate();
AnyContext context = Any.WithSeed(1234);
int quantity = context.Int32().Positive().Generate();
Inside a test body, prefer Any.Reproducibly(() => { ... }): it also reports the seed when the body fails, which a raw scope does not.
What it does not flag
- A handle held by a
usingstatement or ausingdeclaration. - A context captured in a variable, a field or a parameter.
- A test asserting that the call rejects its argument —
Expect.Throws<ArgumentNullException>(() => Any.UseSeed(seed, null!)). No scope is opened there, so there is nothing to leak.