Attributes
Declare test metadata with attributes from the Greenlight\Attribute
namespace.
There are no method-name conventions and no annotations. Attributes on a class apply to every test method in that class.
Test
Target: method.
Parameters:
bool $capture = true
Marks a public method as a test.
Greenlight enables output capture by default. It records output from PHP’s
output buffer, such as echo and print. It also records notices, warnings,
and deprecations from the test. Reporters can associate this output with its
test.
Direct writes to the STDOUT and STDERR stream resources bypass PHP’s output
buffer. Greenlight does not capture these writes. They can also interfere with
terminal output. Do not use these resources for test diagnostics. Use test
attachments to retain diagnostic content. See test
attachments.
Set capture: false only when a test needs to control PHP’s output buffer or
error handler itself. With capture disabled, Greenlight does not record output
or diagnostics for that test.
#[Test]
public function totalsAreRounded(): void { ... }
#[Test(capture: false)]
public function managesItsOwnOutputBuffer(): void { ... }
Before
Target: method.
No parameters.
Marks a public method to run before each test in the class.
If a class has multiple before-hooks, Greenlight runs them in declaration order.
A before-hook can throw SkipTest to skip the test. A throwable other than
SkipTest gives the test an error.
After
Target: method.
No parameters.
Marks a public method to run after each test in the class. The method also runs after a test failure or error.
If a class has multiple after-hooks, Greenlight runs them in reverse declaration order. Thus, the before-hooks and after-hooks form a stack.
Greenlight calls each after-hook, even if an earlier hook throws. If the test does not have a failure or error, the first throwable becomes its cause.
DataSet
Target: method.
Parameters:
string $provider
?string $method = null
With one argument, references a public static provider method on the test class. With two arguments, the first argument is a provider class. The second argument is its public static method:
Provider class and method names MUST NOT be empty.
#[DataSet('currencies')]
or:
#[DataSet(CurrencyDataSets::class, 'currencies')]
The provider must return an iterable of named data sets for the test method.
Providers run at discovery time before tests execute. Do not use I/O or global state in a provider.
Each provider key names a data set and appears in test IDs and reports. Each provider value is the argument list for one test invocation.
#[Test]
#[DataSet('currencies')]
public function roundsPerCurrency(Currency $currency, string $expected): void { ... }
/** @return iterable<string, array{Currency, string}> */
public static function currencies(): iterable
{
yield 'GBP rounds half-up' => [Currency::GBP, '10.01'];
yield 'JPY has no minor unit' => [Currency::JPY, '10'];
}
Use the two-argument form to share a provider between test classes:
final class CurrencyDataSets
{
/** @return iterable<string, array{Currency, string}> */
public static function currencies(): iterable
{
yield 'GBP rounds half-up' => [Currency::GBP, '10.01'];
}
}
#[Test]
#[DataSet(CurrencyDataSets::class, 'currencies')]
public function roundsSharedCurrencyCases(Currency $currency, string $expected): void { ... }
The bundled PHPStan extension validates providers without their execution. Each
provider must exist, be public and static, and return an iterable of argument
arrays. PHPStan compares visible row shapes with the test method parameters.
The array{...} return type above is one visible row shape. The extension
applies the same check to #[DataRow] literals. See static analysis with
PHPStan.
DataRow
Target: method.
Repeatable.
Parameters:
array $arguments
?string $label = null
Adds one inline data set.
$arguments contains the test arguments in parameter order. The label becomes
the data-set key in test IDs and reports. Without a label, Greenlight uses
#<position> as the key for the inline row.
Inline rows can contain only values that PHP attributes can express. Examples
include scalars, arrays, and constants. Use a #[DataSet] provider for
calculated rows, ranges, or objects.
You can use #[DataRow] and #[DataSet] on the same method. They use one
data-set key space. Thus, duplicate keys cause a discovery error.
#[Test]
#[DataRow([1, 2, 3], label: 'small')]
#[DataRow([10, 20, 30])]
public function addsUp(int $a, int $b, int $sum): void { ... }
NoExpectations
Target: method.
No parameters.
Declares that the test intentionally verifies no expectations.
Use this for tests that pass and do not throw. Risky-test detection and
--fail-on-risky ignore tests marked with this attribute.
The attribute makes the intent explicit. Thus, Greenlight does not confuse this test with a test that has a missing assertion.
An eventually() or consistently() matcher counts as one expectation.
Group
Target: method or class.
Repeatable.
Parameters:
string $name
Tags a test method, or every test in a class, with a group name.
Select groups at run time with --group=<name>. The flag is
repeatable. list-tests applies the same filter.
#[Group('slow')]
#[Group('io')]
final class ImportTest { ... }
Skip
Target: method or class.
Parameters:
string $reason
Skips the test method, or every test in the class, unconditionally.
You must give a reason. The reason appears in the report.
Greenlight does not construct skipped tests.
SkipUnless
Target: method or class.
Parameters:
string $condition
mixed ...$arguments
$condition MUST name an instantiable class that implements
Greenlight\Core\Condition.
Skips the test if the condition is false.
Greenlight passes the remaining attribute arguments to the condition
constructor. Arguments MUST be scalars or null because Greenlight sends them to
parallel workers. Another argument type causes a discovery error. The
constructor MUST only store the arguments. The isSatisfied() method MUST
evaluate the condition without side effects:
interface Condition
{
public function isSatisfied(): bool;
}
The worker evaluates the condition before it constructs the test class. If the condition is false, Greenlight does not use constructor injection or harness services.
If the condition throws, the test has an error. Greenlight does not skip it.
#[Test]
#[SkipUnless(RedisIsRunning::class)]
public function storesSessionsInRedis(): void { ... }
#[Test]
#[SkipUnless(ExtensionLoaded::class, 'redis')]
public function usesTheRedisExtension(): void { ... }
Built-in conditions
The Greenlight\Condition namespace ships conditions for the common
environment checks, so most #[SkipUnless] uses need no hand-written class:
ExtensionLoaded('redis')checks for the extension.ExtensionMissing('xdebug')checks for the absence of the extension.EnvironmentVariableSet('CI')checks thatgetenv()returns a value.EnvironmentVariableEquals('APP_ENV', 'test')checks that the variable equals the value exactly.OperatingSystemFamily('Linux')compares the value withPHP_OS_FAMILYwithout regard to case.PhpVersionAtLeast('8.5')checks thatPHP_VERSIONis at least the given version.PhpVersionLessThan('9.0')checks thatPHP_VERSIONis below the given version.FunctionAvailable('pcntl_fork')checks that the function exists.ClassAvailable(Redis::class)checks that the class exists.
The skip reason names the condition and its arguments, for example
Condition ExtensionLoaded("redis") is not satisfied.
Retry
Target: method or class.
Parameters:
int $times
?string $onlyOn = null
Retries a failed test up to $times additional attempts.
$times MUST be at least 1.
When you supply $onlyOn, it MUST be a throwable class-string. Greenlight
retries only failures with that throwable type. It does not retry other
failures.
Greenlight gives each attempt a new test instance and a new per-test scope. Thus, state does not pass between attempts.
Each retry also starts eventually() and consistently() with a new deadline
and an empty observation log. retryOnException() retries a probe within the
same test attempt, while #[Retry] starts the whole test again.
#[Test]
#[Retry(times: 2, onlyOn: NetworkException::class)]
public function fetchesRates(): void { ... }
Timeout
Target: method or class.
Parameters:
float $seconds
$seconds must be greater than zero.
Fails the test if it runs longer than the configured budget.
Greenlight enforces a timeout in two layers. The worker checks elapsed time cooperatively and fails a test that exceeds its budget. If the worker does not return, the orchestrator terminates it after the hard-kill grace period.
The orchestrator replaces the stopped worker and continues the run.
An eventually() or consistently() matcher cannot run past the current test
timeout. If the test timeout occurs first, the failure gives the requested
duration. A blocked probe remains subject to the orchestrator hard-kill grace
period.
#[Test]
#[Timeout(seconds: 5.0)]
public function convergesQuickly(): void { ... }
RequiresResource
Target: method or class. Repeatable.
Parameters:
string $name
Marks a test that requires one slot of a named resource. A name must start with a lowercase letter or digit. After the first character, the name accepts dots, underscores, and hyphens.
#[RequiresResource('postgres')]
#[RequiresResource('payments-sandbox')]
final class OrderRepositoryTest { ... }
Class-level requirements apply to each method. Greenlight combines method-level requirements with them. Multiple occurrences of the same name have no effect.
Greenlight combines requirements from all non-isolated tests in a class and holds them until the class assignment finishes. Thus, a method requirement can reduce concurrency for other non-isolated tests in the class. Each isolated test has a separate assignment with its class-level and method-level requirements.
Resources default to a limit of one. Use resourceLimit() in greenlight.php
or --resource-limit to set a larger limit.
The requirement controls the class start time. It does not select a concrete
resource instance or provide a lease identifier. Use TestChannel when every
worker can have its own instance. A smaller set of distinct instances still
needs an application-owned allocator.
Resource counts live in the current orchestrator. Other Greenlight processes, worktrees, and shards have their own counts.
Isolated
Target: method or class.
No parameters.
Runs the test method, or each test in the class, in a dedicated new worker. Greenlight discards that worker after the test.
Use this for tests that modify process-global state, such as ini settings, environment variables, or static caches.
CoverageIgnore
Target: class, method, or function.
No parameters.
Excludes the declaration from coverage. Greenlight removes ignored lines from the covered and executable totals. Thus, they do not change a percentage, export, or baseline diff.
final class Config
{
#[CoverageIgnore]
private function __construct() { ... }
}
#[CoverageIgnore]
function dumpDebugState(): void { ... }
Greenlight matches the attribute name and does not load the related code.
Therefore, it does not recognize an aliased import
(use Greenlight\Attribute\CoverageIgnore as Ignore).
The PHPUnit comment annotations work without changes. Put
@codeCoverageIgnore in a docblock before a declaration. Put
@codeCoverageIgnoreStart and @codeCoverageIgnoreEnd around a block. Put
// @codeCoverageIgnore at the end of one line.