sebastianbergmann/phpunit · error · InvalidArgumentException
Value for %s must not be negative.
Error message
Value for %s must not be negative.
What it means
Duration is PHPUnit's immutable elapsed-time value object; its constructor rejects negative components with InvalidArgumentException ('Value for seconds must not be negative.' / '...nanoseconds...'). Note that HRTime::duration() and CpuTime::diff() clamp negative differences to Duration/CpuTime zero, so this error indicates manual subtraction passed to Duration::fromSecondsAndNanoseconds() rather than the built-in difference helpers.
Source
Thrown at src/Event/Value/Telemetry/Duration.php:147
return true;
}
if ($this->seconds < $other->seconds) {
return false;
}
return $this->nanoseconds > $other->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
- Use the provided difference APIs, e.g. HRTime::fromSecondsAndNanoseconds(...)->duration($start), which clamps at zero.
- Clamp manual results with max(0, $seconds) and max(0, $nanoseconds) before constructing.
- Ensure both readings come from the same clock (always hrtime(), never mixing time() with hrtime()) and the same process where possible.
Example fix
// before
$elapsed = Duration::fromSecondsAndNanoseconds(
$startSec - $endSec, // negative on swap
$startNanos - $endNanos,
);
// after
$elapsed = HRTime::fromSecondsAndNanoseconds($endSec, $endNanos)
->duration(HRTime::fromSecondsAndNanoseconds($startSec, $startNanos)); Defensive patterns
Strategy: validation
Validate before calling
$seconds = max(0, $endSec - $startSec); $nanoseconds = max(0, $endNanos - $startNanos); $duration = Duration::fromSecondsAndNanoseconds($seconds, $nanoseconds);
Type guard
function isNonNegativeDurationComponent(int $value): bool
{
return $value >= 0;
} Prevention
- Use HRTime::duration($start) instead of manual subtraction; it clamps at zero.
- Take both timestamps from the same clock and process.
- In reporters, treat a negative computed difference as zero elapsed time rather than constructing the object.
When it happens
Trigger: Duration::fromSecondsAndNanoseconds($endSeconds - $startSeconds, ...) with the end timestamp earlier than the start; monotonic-clock reads mixed with wall-clock reads across processes so the difference goes negative; integer underflow on 32-bit PHP.
Common situations: Extensions that report per-test durations computed from hrtime() readings captured in different processes (process isolation); building custom reporters or slow-test detectors; tests with fixture timestamps in the wrong order.
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/1a36b56909a9814b.
Report an issue: GitHub.