sebastianbergmann/phpunit · error · InvalidArgumentException
Value for %s must not be negative.
Error message
Value for %s must not be negative.
What it means
HRTime is PHPUnit's immutable high-resolution timestamp value object (built on hrtime()); its constructor rejects negative seconds or nanoseconds. The Facade uses it for stopwatch starts and for SystemStopWatchWithOffset in process isolation, so the error typically comes from user code computing a negative difference or offset and feeding it to HRTime::fromSecondsAndNanoseconds().
Source
Thrown at src/Event/Value/Telemetry/HRTime.php:91
if ($seconds < 0) {
return Duration::fromSecondsAndNanoseconds(0, 0);
}
return Duration::fromSecondsAndNanoseconds(
$seconds,
$nanoseconds,
);
}
/**
* @phpstan-assert non-negative-int $value
*
* @throws InvalidArgumentException
*/
private function ensureNotNegative(int $value, string $type): void
{
if ($value < 0) {
throw new InvalidArgumentException(
sprintf(
'Value for %s must not be negative.',
$type,
),
);
}
}
/**
* @throws InvalidArgumentException
*/
private function ensureNanoSecondsInRange(int $nanoseconds): void
{
if ($nanoseconds > 999999999) {
throw new InvalidArgumentException(
'Value for nanoseconds must not be greater than 999999999.',
);
}View on GitHub (pinned to f123cdb2a2)
Solutions
- Order the operands: subtract the earlier reading from the later one and verify with $later >= $earlier before constructing.
- Use HRTime::duration($start) for differences; it borrows across the second boundary and clamps at zero instead of throwing.
- Keep seconds and nanoseconds as the split hrtime(false) pair (or split hrtime(true) totals with intdiv) rather than mixing clock types.
Example fix
// before $stamp = HRTime::fromSecondsAndNanoseconds($t0Sec - $t1Sec, $t0Nanos - $t1Nanos); // may be negative // after $stamp = HRTime::fromSecondsAndNanoseconds($t1Sec, $t1Nanos); $elapsed = $stamp->duration(HRTime::fromSecondsAndNanoseconds($t0Sec, $t0Nanos));
Defensive patterns
Strategy: validation
Validate before calling
[$seconds, $nanoseconds] = hrtime(false); // both non-negative by contract
if ($seconds < 0 || $nanoseconds < 0) {
throw new LogicException('hrtime() returned unexpected negative values');
}
$stamp = HRTime::fromSecondsAndNanoseconds($seconds, $nanoseconds); Type guard
function isNonNegativeTimeComponent(int $value): bool
{
return $value >= 0;
} Prevention
- Use HRTime::duration($start) for differences instead of manual subtraction.
- Anchor timestamps with hrtime(false) so the (seconds, nanoseconds) split is native.
- Validate that child-process offsets are computed from readings of the same monotonic clock.
When it happens
Trigger: HRTime::fromSecondsAndNanoseconds($t1 - $t0, ...) where $t1 < $t0 (swapped hrtime readings); computing an offset for a child process whose clock reading is behind the parent's; casting a negative float from hrtime(false) arithmetic to int.
Common situations: Extensions capturing their own hrtime() anchors around tests that run in isolated processes; custom stopwatch implementations; 32-bit platforms where nanosecond totals overflow.
Related errors
- Value for nanoseconds must not be greater than 999999999.
- Value for %s must not be negative.
- Value for nanoseconds must not be greater than 999999999.
- Value for %s must not be negative.
- Value for nanoseconds must not be greater than 999999999.
AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23).
Data as JSON: /api/errors/faeb5dd87f14d587.
Report an issue: GitHub.