Configuration
Configure Greenlight with one greenlight.php file at the project root.
The file returns a Greenlight\Config\GreenlightConfig builder. The CLI loads
the builder and applies command-line overrides. It then creates an immutable
configuration object.
Precedence
Greenlight applies configuration in this order:
- Built-in defaults
greenlight.php- CLI flags
Later layers override earlier ones.
For example, configure automatic worker selection:
->workers('auto')
Then select one worker for a run:
greenlight run --workers=1
The CLI flag overrides the configured worker count.
GreenlightConfig
Create the builder with:
GreenlightConfig::create()
Configuration setters return $this, so you can chain their calls.
paths(array $tests): self
Default: ['tests'].
Sets the base directories that Greenlight scans.
Paths must be non-empty strings. The list itself must not be empty.
Without a suite selector, each run also scans every path from suite().
An explicit suite selector excludes these base paths. This rule prevents the
default paths(['tests']) value from scanning tests outside selected suites.
suite(string $name, callable $configurator): self
Default: no suites.
Declares a named suite. Greenlight gives a SuiteBuilder to the configurator.
The configurator must add at least one path. Greenlight ignores its return
value. Thus, you can use an arrow function.
Without a suite selector, each run includes every named suite. This behavior is compatible with configurations that use suites only to add paths.
Use --suite=<name> or --suite-tag=<tag> to select suites. Repeat either
option to select a union. Greenlight selects a suite if its name or one of its
tags matches a selector.
An explicit selection scans only paths from selected suites. It does not scan
base paths(). Test filters and sharding apply after this path selection.
A suite does not create an execution or lifecycle boundary. Coverage include paths are global and do not belong to a suite. A selected coverage run measures all configured coverage include paths.
A second declaration with the same suite name causes an error.
->suite('unit', fn ($s) => $s->in('tests/Unit'))
->suite('integration', fn ($s) => $s->in('tests/Integration')->tag('io'))
SuiteBuilder has two methods:
in(string ...$paths): selfadds directories to the suite. Required.tag(string ...$tags): selfadds tags to the suite. Optional.
Suite names and tags use case-sensitive exact matching.
workers(int|string $count = 'auto'): self
Default: 'auto' workers.
A worker executes one assignment at a time. By default, an assignment contains one complete class. Greenlight can batch small classes with the same resource requirements when previous durations are available.
Greenlight keeps this class-level schedule by default. Add #[AllowParallel]
to an independent large class to make each test or data set a separate
assignment.
Without randomization, the run gives first priority to classes that failed in the previous run. It orders classes with saved durations next, longest first. Classes without saved durations follow in discovery order.
Worker placement is load-dependent. The stable parts are:
- queue order for a given plan
- method and data-set order in the execution plan
- assignment queue order for entries from
#[AllowParallel]classes
The seed reproduces failures related to order. It does not reproduce exact worker placement or completion-event order.
$count accepts a positive integer or 'auto'. With 'auto', Greenlight uses
one worker per detected CPU core. If CPU detection fails, it uses four workers.
Install the suggested fidry/cpu-core-counter package for CPU detection that
respects cgroup limits in containers.
A worker remains active until the queue is empty or the worker fails. Greenlight does not hide memory growth or state leaks by replacing a healthy worker.
resourceLimit(string $name, int $limit = 1): self
Default: 1 for a resource used by #[RequiresResource].
Limits how many assignments in one Greenlight run can use the named resource at once.
return GreenlightConfig::create()
->resourceLimit('postgres', 3)
->resourceLimit('payments-sandbox');
Limits must be positive. Names must match [a-z0-9][a-z0-9._-]*. Configure
each name only one time.
An assignment that requires several resources waits until all of them have capacity. Greenlight claims the slots together and sends the assignment only after all slots are available.
The limit is an in-memory scheduler gate, not a distributed lock. Separate Greenlight processes, worktrees, and CI shards do not share capacity.
The scheduler does not choose a concrete resource instance or expose a lease identifier. If a test needs one of several distinct instances, the application must allocate it. Use a channel instead when every worker can have its own instance.
coverage(callable $configurator): self
Default: coverage off.
The coverage() method enables coverage collection. Greenlight gives a
CoverageBuilder to the configurator.
Repeated calls to coverage() preserve earlier settings.
CoverageBuilder methods:
include(string ...$paths): selfadds source directories to measure. Default: none.driver(string $driver): selfrestricts coverage topcovorxdebugwhen you use that value. Other non-empty values have the default behavior: Greenlight tries pcov, then Xdebug.requireDriver(bool $required = true): selffails the run when the selected coverage driver is not available. Default:false.minimumPercentage(float $percentage): selfsets the minimum total line coverage. The value must be from0through100. It can have two decimal places. Default: no minimum.maximumUncoveredLines(int $lines): selfsets the maximum number of uncovered executable lines. The value must be zero or more. Default: no maximum.export(string $format, string $target): selfadds a coverage export. Supported formats arejson,lcov,clover,cobertura, andhtml.$targetis a file path, or a directory for multi-file formats such ashtml. Repeatable.
->coverage(fn ($c) => $c
->include('src')
->driver('pcov')
->requireDriver()
->minimumPercentage(90.0)
->maximumUncoveredLines(100)
->export('lcov', 'coverage/lcov.info')
->export('html', 'coverage/html'))
When you configure coverage, the run prints the total percentage and writes each coverage export.
If no worker can collect coverage because neither pcov nor Xdebug coverage mode
is available, Greenlight warns on stderr. That warning does not fail the run by
itself. requireDriver() changes the warning to a run failure.
A configured coverage gate also requires coverage. Thus, an unavailable coverage driver fails the run when you configure a gate.
The minimum percentage gate uses the total line coverage. Greenlight rounds the calculated percentage to two decimal places before the comparison. It uses half-up rounding. A result that is equal to the minimum passes.
The uncovered-line gate counts executable lines that did not execute. A count that is equal to the maximum passes. If you configure both gates, both gates must pass.
Greenlight writes all configured coverage exports before it evaluates the
gates. Thus, a failed gate keeps the coverage evidence. A failed gate uses exit
code 1.
Reporters continue to report test results. They do not add a coverage-gate result to JUnit, JSONL, or other machine formats. Use the process exit code for the machine-readable gate result. If JSONL writes to standard output, Greenlight writes human coverage output to standard error.
The workers are not the only measured processes. In a parallel run, the
orchestrator collects its own coverage. Thus, the export includes code that
executes only in the orchestrator. A coverage run also exports
GREENLIGHT_COVERAGE_DIR and GREENLIGHT_COVERAGE_INCLUDE to each process that
it starts. A bin/greenlight process writes coverage to the shared directory
if it inherits these variables. For example, an acceptance test can start this
process to operate the real CLI.
Before export, the run adds these coverage files to the result. This collection operation does not fail the run. The configured include paths filter coverage from all processes.
watch(callable $configurator): self
Defaults: 200 ms debounce and a 100,000-file poll limit.
The configurator receives a WatchBuilder.
WatchBuilder has these methods:
debounceMilliseconds(int $milliseconds): selfsets the quiet period before a rerun starts. The value must be at least 1.paths(string ...$paths): selfadds file or directory inputs.include(string ...$patterns): selfselects files below additional directory inputs.exclude(string ...$patterns): selfremoves from all watch inputs each file that matches.maximumFiles(int $maximumFiles): selfsets the file limit for one poll.
Relative paths use the command working directory. An input that does not exist stays in the configuration and can appear later.
An additional file input does not need an include pattern. An additional directory includes all its regular files when there are no include patterns. When include patterns exist, the directory includes only files that match.
Patterns match the complete path. Paths below the working directory use a relative path. Paths outside it use an absolute path.
Pattern syntax is as follows:
/separates path segments.*matches zero or more characters except/.?matches one character except/.**matches characters across path segments.
Pattern comparison is case-sensitive. Patterns do not use negation. Use
exclude() for an exclusion. An exclusion has precedence over each path and
include pattern.
The effective test and coverage roots keep their default PHP-only behavior. Exclusion patterns also apply to those roots.
A rerun starts after the configured period has no file changes. Thus, a group of save operations starts only one run.
->watch(fn ($w) => $w
->debounceMilliseconds(500)
->paths('templates', 'config/app.yaml', 'migrations')
->include('**/*.twig', '**/*.yaml', '**/*.sql')
->exclude(
'build/**',
'coverage/**',
'var/greenlight/**',
)
->maximumFiles(25_000))
Use exclusions for files that the run writes. Examples include build output, Greenlight storage, coverage output, and configured report or artifact targets. Greenlight does not add broad default exclusions because those exclusions can hide source inputs.
failOnDeprecation(bool $enabled = true): self
Default: off.
Fails a test that otherwise passed if its captured diagnostics contain a deprecation.
The worker records the change as a result transformation. Thus, the exit code,
--bail, JUnit output, and plugins use the same final result.
The worker applies this policy after retries and afterTest() subscribers.
Also available as --fail-on-deprecation.
failOnNotice(bool $enabled = true): self
Default: off.
Fails a test that otherwise passed if its captured diagnostics contain a notice.
As with failOnDeprecation(), the worker records the change as a result
transformation. It applies the change after retries and afterTest()
subscribers.
Also available as --fail-on-notice.
failOnWarning(bool $enabled = true): self
Default: off.
Fails a test that otherwise passed if its captured diagnostics contain a warning.
The worker records the change as a result transformation. It applies the
change after retries and afterTest() subscribers.
Also available as --fail-on-warning.
ignoreDeprecationsMatching(string ...$patterns): self
Default: none.
Exempts deprecations that match a pattern from failOnDeprecation().
Greenlight compares patterns without regard to case. A plain pattern matches a
part of the message. A pattern that contains * or ? must match the complete
message.
The method is repeatable and patterns accumulate.
Use this for dependency deprecations you cannot fix yet.
failOnRisky(bool $enabled = true): self
Default: off.
A successful test is risky if it does not verify an expectation. Such a test has
no Expect calls and no mock expectations that Greenlight verifies at
teardown.
The tty and plain reporters always list risky tests after the summary. The
failOnRisky() method changes risky tests to failures.
Use #[NoExpectations] for a test that intentionally verifies no expectations.
Each eventually() or consistently() matcher counts once.
Also available as --fail-on-risky.
failOnSkipped(bool $enabled = true): self
Default: off.
Fails a run if its final summary contains one or more skipped tests.
This run policy does not change a skipped test to a failure. Reporters keep the
skipped outcome and its reason. JUnit uses a skipped element. JSONL uses the
skipped outcome. TeamCity uses testIgnored. The GitHub reporter does not
create an error annotation for a skipped test.
The policy evaluates terminal results after plugins and retries. A plugin that changes the terminal outcome changes the run-policy input.
A skipped test does not count toward --bail because its outcome is neither
failed nor errored. The policy fails the completed iteration instead. Thus,
repeat mode records that iteration as failed. --repeat-until-failure stops
after it.
Also available as --fail-on-skipped.
failOnRetriedPass(bool $enabled = true): self
Default: off.
Fails a run if one or more tests pass after retry.
The run policy does not change the passed outcome. It keeps the attempt count and attachments from failed attempts.
The tty reporter keeps each affected class in interactive output. The tty
and plain reporters list each retried pass after the summary.
JUnit uses the Surefire-compatible flakyFailure element. JSONL uses the
existing attempts field. TeamCity uses numeric test metadata. GitHub creates a
warning annotation.
A retried pass does not count toward --bail. Plugins determine the terminal
outcome before Greenlight evaluates the policy.
The policy evaluates only tests in the selected shard. Repeat mode records an
affected iteration as failed. --repeat-until-failure stops after it.
Watch mode reports the policy failure after each affected run. The watch
process continues, and q keeps its documented exit code.
A retried pass is evidence of instability. It does not prove that a test has permanent flaky behavior.
Also available as --fail-on-retried-pass.
plugins(Closure ...$plugins): self
Default: none.
Registers plugin factories. Give each factory one non-null concrete plugin class return type. Return a new plugin instance each time Greenlight calls the factory.
The method is repeatable and factories accumulate.
A ReporterProvider plugin registers custom names for --reporter. See
plugins.
artifacts(callable $configurator): self
Default: output below build/greenlight-artifacts, with failure-only retention.
Greenlight gives an ArtifactBuilder to the configurator. Repeated calls
preserve earlier settings.
->artifacts(fn ($artifacts) => $artifacts
->directory('build/test-evidence')
->maxAttachmentsPerTest(20)
->maxAttachmentSize('10M')
->maxTestSize('50M')
->maxRunAttachments(2_000)
->maxRunSize('500M')
->maxCompletedRuns(20)
->maxCompletedRunAge(604_800)
->maxRetainedSize('2G'))
Defaults are 32 attachments and 100 MiB per test. Each attachment has a 25 MiB limit. Each run has limits of 10,000 attachments and 1 GiB. Per-test limits include all retry attempts, even if retention policy discards attachments later. Greenlight releases run quota when it discards an attachment.
Completed run retention is unbounded by default. Configure a maximum count, age in seconds, or total size. Greenlight applies age, count, and size limits in that order. Each limit removes the oldest eligible completed run first.
Run greenlight artifacts:prune --dry-run to list selected directories. The
command without --dry-run applies the configured policy.
See test attachments for the runtime API and security model.
storage(callable $configurator): self
Default: state, caches, generated code, and temporary data use the system
temporary directory. Published attachments use the separate artifacts()
configuration.
The configurator receives a StorageBuilder. Repeated calls preserve earlier
settings.
Use rootDirectory() to put these four storage areas below one directory.
An area-specific directory replaces its directory below the root.
StorageBuilder has these methods:
rootDirectory(string $directory): selfsets the common storage root.stateDirectory(string $directory): selfstores prior failures and the timing cache.cacheDirectory(string $directory): selfstores discovery metadata.generatedCodeDirectory(string $directory): selfstores generated double proxy PHP.temporaryDirectory(string $directory): selfstores run-owned temporary data.
Relative directories resolve against the initial project working directory. Greenlight creates missing directories when a storage product first writes data.
Use separate areas when their retention or trust requirements differ:
->storage(fn ($storage) => $storage
->stateDirectory('build/greenlight-state')
->cacheDirectory('build/greenlight-cache')
->generatedCodeDirectory('/var/tmp/greenlight-code')
->temporaryDirectory('/var/tmp/greenlight'))
The state directory contains run-state.json for a run without suite
selectors. An explicit suite selection uses a file with a canonical suite-set
suffix. Each file has failed test IDs and class durations from the previous
matching run.
Do not share one state directory between concurrent shards. Each shard writes one complete snapshot and can replace data from another shard.
Generated proxy files contain executable PHP. Use only a private, trusted directory. Do not restore this directory from an untrusted cache.
Greenlight does not remove configured roots. It removes only temporary child directories that it creates and owns.
failFast(bool $enabled = true): self
Default: off.
Stops new work after the first failed or errored test. Active assignments can finish after this limit. Thus, the final failure count can exceed one.
randomizeOrder(?int $seed = null): self
Default: no randomization or seed. Previous failures and durations can change class order.
Randomizes class order.
Use a nonnegative integer for the seed. Zero is a valid seed.
If $seed is null, Greenlight selects one seed when it resolves the command.
Discovery and execution use this seed. Greenlight prints the seed. Use --seed
with that value to reproduce the same order.
build(): Configuration
The loader calls this method. User configuration does not call it.
build() validates the builder and returns immutable configuration values.
These values group discovery, worker, execution, and order settings.
Return the builder from greenlight.php. Do not call build().
Configuration builders throw InvalidConfiguration for invalid values or
invalid combinations. Catch this public type when an integration adds
configuration before Greenlight starts the run.
Channels and resource limits
Every worker process runs in a channel: a stable slot numbered from 1 to the worker count.
Use the channel to derive external resources that parallel tests must not share. Examples include database names, ports, virtual hosts, and temporary directories.
Use a channel when each worker can have a separate resource. Use
#[RequiresResource] when workers share a dependency with lower safe
concurrency. A resource limit controls the number of assignments that can run.
It does not assign a resource instance to an assignment.
Within one run, two concurrent tests do not share a channel. Channel numbers are from 1 through the worker count. Worker replacement does not change this range. After a worker crash, its replacement reuses the freed slot.
Separate runs and CI shards reuse the same channel numbers. Give concurrent runs separate resource prefixes when they use the same external service.
A --workers=1 run executes in-process on channel 1.
Greenlight makes the channel available in two ways:
GREENLIGHT_CHANNEL, set in each worker environment for bootstrap files and tools that usegetenv()Greenlight\Test\TestChannel, available as a harness service for constructor injection and harness providers
TestChannel->number is the numeric slot.
TestChannel->label() returns gl-<number> for resource names.
Because Greenlight uses channel slots again, channel resources can persist after a worker crash. A replacement worker on channel 2 can access resources from the previous channel-2 worker.
This design reduces the cost of one-resource-per-channel setups. For example, you can create one database schema per channel and use it for the complete run.
A test can use both. Its channel can select a private database while
#[RequiresResource('payments-sandbox')] limits access to a shared external
service.
For infrastructure that must be created and destroyed with the run, a plugin
can implement IntegrationFixtureProvider. The provider runs in the
orchestrator after discovery and sharding, creates shared or per-channel
resources, and registers teardown. Workers receive an injectable
IntegrationResources catalog with shared values plus only their own
channel overlay. See Writing plugins.
CLI reference
greenlight [command] [options]
Commands
run
Discovers and executes tests.
This is the default command if you do not give a command.
list-tests
Prints each discovered test ID on a separate line. It then prints the count.
coverage:merge
Merges two or more Greenlight coverage JSON exports.
Repeat --input for each source. Repeat --export for each required output:
greenlight coverage:merge \
--input=coverage-shard-1.json \
--input=coverage-shard-2.json \
--export=json=coverage.json \
--export=lcov=coverage.lcov \
--export=html=coverage-html
The command supports json, lcov, clover, cobertura, and html.
The merged map contains the union of all executable lines. A line has coverage if one or more inputs identify it as covered. Input order does not change the result.
Duplicate inputs and empty maps do not change the result. A file that is absent from one input remains in the result if another input contains it.
By default, absolute paths identify files. For different checkout roots, repeat
--input-root once for each input. Also set --project-root:
greenlight coverage:merge \
--input=one.json \
--input=two.json \
--input-root=/old/checkout-one \
--input-root=/old/checkout-two \
--project-root=/current/checkout \
--export=json=coverage.json
Greenlight maps each input path to the selected project root. It rejects a path outside its applicable input root. It also rejects one input that has different input roots.
The command rejects malformed documents and unsupported schema versions. It also rejects relative file paths.
Each output file uses an atomic replacement. Output files and HTML pages have deterministic content and order.
The command accepts --minimum-coverage and --maximum-uncovered-lines. These
gates apply to the merged map after Greenlight writes the outputs.
coverage:diff
Compares two coverage JSON exports.
Requires:
--baseline=<path>
--current=<path>
For exports from different checkout roots, also use both root options:
greenlight coverage:diff \
--baseline=baseline.json \
--current=current.json \
--baseline-root=/old/checkout \
--current-root=/new/checkout
Greenlight removes each explicit root from the file paths in its applicable export. It then compares the project-relative paths. Each coverage path must be below its applicable root. The command fails if a path is outside the root.
The root options do not change either coverage export. Coverage JSON version 1 continues to use absolute path keys.
The command also accepts --minimum-coverage and
--maximum-uncovered-lines. These gates apply to the current export. A failed
gate fails the command when the baseline has no regression.
Exits with code 1 if coverage across files present in both exports decreases. It also fails if the current export has a newly uncovered line. This rule also applies to added files. A coverage gain elsewhere does not hide that line.
Removed files do not cause a regression. The displayed total percentages include all files, so their difference alone does not determine the exit code.
See the coverage JSON schema for the required format and path rules.
profile:report
Renders a run profile from a saved JSONL event stream.
The command accepts JSONL version 1.
Requires:
--input=<path>
artifacts:prune
Applies the configured retention policy to completed artifact run directories.
Use --dry-run to list the directories that the command would remove. The
command reports each directory, its size, and the applicable limit.
If you configure no retention policy, the command exits successfully and does not remove a directory.
See artifacts() for the retention
settings.
ide-helper
Writes the IDE autocomplete helper for extension matchers.
Default output:
_greenlight_ide_helper.php
Override it with:
--output=<path>
Add the generated file to .gitignore. Regenerate the file after a matcher
change.
completion
Prints a shell completion script to standard output. Use the command for your shell from the project root.
For Bash:
source <(vendor/bin/greenlight completion bash)
For Zsh, initialize completion before you load the script:
autoload -Uz compinit
compinit
source <(vendor/bin/greenlight completion zsh)
For Fish, create the completion directory before you save the script:
mkdir -p ~/.config/fish/completions
vendor/bin/greenlight completion fish > ~/.config/fish/completions/greenlight.fish
Options
--config=<path>
Uses this configuration file instead of ./greenlight.php.
--workers=<n|auto>
Overrides the worker process count.
--resource-limit=<name>=<n>
Sets a resource limit for this run and overrides greenlight.php.
vendor/bin/greenlight run \
--resource-limit=postgres=2 \
--resource-limit=payments-sandbox=1
Repeat the option to set limits for different resources. Use each name only one time.
--bail[=<n>]
Stops new work after <n> failed or errored tests. Active assignments can
finish after this limit. Thus, the final failure count can exceed <n>.
Bare --bail means --bail=1.
--suite=<name>
Selects the configured suite with this exact name.
Repeatable. Greenlight creates one union from all --suite and --suite-tag
values. An unknown name is a usage error.
If you use a suite selector, Greenlight excludes base paths() from discovery.
--suite-tag=<tag>
Selects each configured suite with this exact tag.
Repeatable. An unknown tag is a usage error. Use --list-suites to see the
configured names and tags.
--group=<name>
Runs only tests in the given group.
Repeatable.
--filter=<pattern>
Runs only tests with a test ID that matches the pattern.
A test ID is Class::method with an optional data-set label.
By default, Greenlight matches substrings without regard to case. A pattern
that contains * or ? must match the complete test ID.
Repeatable. Multiple filters form a union.
--test-id=<id>
Runs only the exact test ID. Unlike --filter, this option does not compare
substrings or wildcard patterns.
Use the test IDs printed by list-tests or --list-tests. Include the data-set
label when present:
greenlight run \
'--test-id=App\Tests\OrderTest::placesOrder[card]'
Repeatable. Multiple exact test IDs and --filter patterns form a union.
Exclusions have precedence.
--exclude-group=<name>
Excludes tests in the given group.
Repeatable. Exclusions take precedence over --group and --filter.
--exclude-class=<pattern>
Excludes classes with names that match the pattern.
Greenlight matches class names with case sensitivity. A pattern without a
wildcard matches a substring. A pattern that contains * or ? must match the
complete class name.
Repeatable.
--exclude-method=<pattern>
Excludes methods with names that match the pattern.
Greenlight matches method names with case sensitivity. A pattern without a
wildcard matches a substring. A pattern that contains * or ? must match the
complete method name.
Repeatable.
--exclude-path=<prefix>
Excludes tests whose source path starts with the given prefix.
Greenlight resolves relative prefixes from the current directory. It compares
the path text without a directory-boundary check. For example,
--exclude-path=tests/Slow also matches tests/SlowExtra/ExampleTest.php.
Repeat the option to add prefixes.
--list-tests
Prints the selected test IDs. It does not run them.
Use --format=json to write the version 1 test discovery manifest. The
manifest reference defines its schema, order,
metadata, compatibility rules, and exit codes.
--format=<format>
Sets list-tests output to text or json. The default is text.
Use this option only with list-tests or run --list-tests.
--list-groups
Prints each selected group and its test count. It does not run tests.
--list-suites
Prints all configured named suites and their tags. It does not discover or run tests. Suite selectors do not hide entries from this catalog.
--repeat=<n>
Runs the selected tests in <n> fresh iterations.
The command exits with a nonzero code if an iteration fails. --repeat=1 is
equivalent to an ordinary run.
--repeat-until-failure
Repeats fresh runs until an iteration fails.
On its own, the command stops after 100 successful iterations. Use
--repeat=<n> to set a different limit. Do not use it with --watch.
Repeat modes do not support JUnit output or enabled coverage. Run a separate command for each required report.
The tty, plain, jsonl, github, and teamcity reporters support repeat
modes. The --profile output and configured reporters also remain available.
--shard=n/m
Runs the nth of m disjoint slices of the plan.
Greenlight selects shards by a stable class hash. Thus, CI machines can split a suite without coordination. Together, all shards contain the complete suite.
Only whole classes move between shards. Individual methods are not split.
Combines with --group and --filter. Greenlight divides the filtered plan
into shards.
Each shard enforces its own resource limits. If four shards each use
postgres=2, up to eight tests can use PostgreSQL across the CI job.
--failed
Reruns only tests that failed or had an error in the previous run.
Greenlight records failure state for each run in the configured state directory. By default, it uses the system temporary directory.
If no previous failure state exists, this is a usage error.
If the previous run passed completely, Greenlight reports that there is nothing to rerun and exits 0.
When failure state exists, normal runs put previously failed classes first.
--seed disables this order.
--seed=<n>
Randomizes class order with this seed.
Seeded runs do not use the timing-cache order. The seed determines the complete order.
--reporter=<name>[=<path>]
Selects the output format.
Built-in reporters:
ttyplainjunitjsonlgithubteamcity
Repeatable. Greenlight sends each event to the reporters in flag order.
ReporterProvider plugins can add names. Greenlight creates reporters in flag
order. A repeated name creates a separate reporter for each occurrence.
Without a file, the reporter writes to standard output. Add a file to keep its output separate:
vendor/bin/greenlight run --reporter=tty --reporter=junit=reports/junit.xml
Greenlight resolves a relative file path from the command working directory. It creates missing parent directories and replaces an existing file. Each file can have only one selected reporter.
Greenlight owns each supplied output. A custom reporter does not close it.
Choose unique reporter names across built-ins and plugins. A duplicate name stops the command before test execution.
Shell completions suggest built-in names. Configured names remain valid.
Default: tty on an interactive terminal, otherwise plain.
The jsonl reporter writes one complete event sequence for each repeat
iteration. When JSONL uses standard output, Greenlight writes repeat status to
standard error to keep standard output valid JSONL.
The tty reporter supports parallel work. On a terminal, it keeps one live
line for each active class. Each line has a spinner and a current count. The
reporter completes the line in place when the class finishes. Multi-worker
output does not interleave randomly. In a file, it uses append-only output.
The teamcity reporter includes IDE navigation metadata: php_qn:// location
hints for click-to-source, plus a per-class flowId to keep parallel output
separated in JetBrains tools.
--artifacts-dir=<path>
Overrides the configured artifact parent directory for this run. Greenlight creates a unique run directory below it and reports that path in human and machine-readable output.
--minimum-coverage=<percentage>
Overrides CoverageBuilder::minimumPercentage() for a run. The option also
enables coverage when the configuration file does not configure it. With
coverage:diff, the option checks the current export. With coverage:merge,
the option checks the merged map.
The value must be from 0 through 100. It can have two decimal places.
--maximum-uncovered-lines=<n>
Overrides CoverageBuilder::maximumUncoveredLines() for a run. The option also
enables coverage when the configuration file does not configure it. With
coverage:diff, the option checks the current export. With coverage:merge,
the option checks the merged map.
The value must be a nonnegative integer.
--require-coverage-driver
Requires an available coverage driver for this run. The option also enables coverage when the configuration file does not configure it.
--baseline=<path>
Sets the baseline coverage JSON file for coverage:diff.
--current=<path>
Sets the current coverage JSON file for coverage:diff.
--baseline-root=<path>
Sets the project root for baseline path normalization. Use this option with
--current-root.
--current-root=<path>
Sets the project root for current path normalization. Use this option with
--baseline-root.
--input=<path>
Sets the input stream for profile:report.
With coverage:merge, the option sets one coverage JSON input. Repeat the
option for each input.
--export=<format>=<path>
Sets one output for coverage:merge. Repeat the option for each output.
Supported formats are json, lcov, clover, cobertura, and html.
--input-root=<path>
Sets the source project root for one coverage:merge input. Repeat the option
once for each input. Use this option with --project-root.
--project-root=<path>
Sets the target project root for coverage:merge path relocation. Use this
option with --input-root.
--watch
Starts with a complete run, then reruns all selected tests after a file change.
Greenlight watches the effective test paths and all coverage include paths. An explicit suite selection limits the effective test paths to selected suites. After a change, classes that failed in the previous watch iteration run first.
The watch() builder can add templates, YAML, JSON, SQL migrations, fixtures,
and other inputs. It can also exclude generated output from every watched root.
Watch mode does not publish coverage totals or coverage exports.
In watch mode:
- Enter reruns all selected tests.
qquits with exit code 0, regardless of the last iteration result.
Signals use the exit codes in the interruption section.
Configure watch inputs
Use WatchBuilder::paths() to add files or directories. Relative paths use the
command working directory. Include patterns select files in additional
directories. Exact file inputs do not require an include pattern. Exclude
patterns apply to all watch inputs and have precedence over include patterns.
Each poll visits directory entries in a stable lexical order. It does not hash an excluded or unmatched file. A poll stops with an error when it exceeds the configured file limit.
Greenlight does not follow symlinks in watched trees.
A created file reports an addition. A removed file reports a deletion. A rename reports one deletion and one addition.
An unreadable or temporarily absent file is not in that poll. If it was in the prior poll, Greenlight reports a deletion. It reports an addition when the file becomes readable again.
Large projects increase poll time. Increase the watch debounce when frequent save events cause excess scans.
--detect-leaks
Verifies that garbage collection removes each test instance after its test.
A detected leak fails the run.
Xdebug develop mode can retain caught exceptions and report false leaks. Use
XDEBUG_MODE=off to get correct leak results.
--fail-on-deprecation
Enables the deprecation policy for this run.
--fail-on-notice
Enables the notice policy for this run.
--fail-on-warning
Enables the warning policy for this run.
--fail-on-risky
Enables the risky-test policy for this run.
--fail-on-skipped
Enables the skipped-test run policy for this run.
--fail-on-retried-pass
Enables the retried-pass run policy for this run.
--profile
Adds a run profile after the summary.
The profile includes:
- workers requested
- workers spawned
- average spawn-to-hello time
- average hello-to-ready bootstrap time
- average ready-to-first-assignment time
- total time between assignments
- idle time from the bootstrap barrier, resource capacity, and no queued work
- average time from a retirement request to observed process exit
- per-worker class count, busy time, and utilization for non-isolated workers
- workers that run isolated tests
- makespan spread between the first and last worker to finish
- the ten slowest classes
The event stream supplies all profile data. You can render a saved JSONL artifact later with:
greenlight profile:report --input=<file>
--output=<path>
Changes the file written by ide-helper.
Default: _greenlight_ide_helper.php.
--dry-run
Prints a summary of the resolved run settings without test discovery or execution. The summary includes additional watch paths, patterns, debounce, and the file limit.
--verbose
In interactive output, prints a permanent line for every completed class.
--ansi
Enables colors in help and append-only reporter output. It does not enable the live progress window.
--no-ansi
Disables colors and the live progress window. Output becomes plain and append-only.
A truthy CI environment variable has the same effect.
NO_COLOR disables colors only. NO_COLOR and --no-ansi have priority over
--ansi.
-h, —help
Shows help. Terminal output uses colors for the title, section headings,
commands, and option labels. Piped output uses plain text by default.
Use --ansi to enable colors.
NO_COLOR and --no-ansi disable help colors.
-V, —version
Shows the version.
Exit codes
Greenlight uses these exit codes:
0: success1: failure from failed tests, test errors, invalid configuration, discovery errors, coverage export errors, detected leaks, and zero discovered tests64: usage error, such as an unknown command, unknown flag, or malformed option value128 + signal number: interruption by a process signal
Greenlight treats a run with zero tests as a configuration problem. It does not treat the run as a success.
Interruption
The first Ctrl+C, SIGINT, or SIGTERM starts a graceful shutdown.
During graceful shutdown, Greenlight:
- stops new assignments
- lets workers finish their in-flight test
- drains worker output
- prints the summary for completed work
- records the failure state used by
--failed - records class durations in the timing cache
- restores the terminal when it exits watch mode
The run then exits with:
130for SIGINT143for SIGTERM
A second signal during shutdown terminates immediately.
Graceful shutdown requires ext-pcntl. Without it, PHP uses its default signal
behavior and exits immediately.
Discovery cache
Greenlight caches discovery results per file in the configured cache directory. By default, it uses the system temporary directory.
The cache identity includes the effective discovery paths. The cache key for each entry includes the file path, mtime, and size. Greenlight can reuse data for an unchanged file. If the cache data is uncertain, Greenlight parses the file again.
Watch mode benefits most because every iteration rediscovers the suite.
Interactive output
In an interactive terminal, the tty reporter shows a bounded live window:
- a progress counter
- in-flight classes
- at most ten live lines, clamped to the terminal height
The reporter prints failures and skips permanently when their class finishes.
Classes that pass only advance the counter unless you enable --verbose.
Both human reporters start with a one-line header that contains:
- Greenlight version
- PHP version
- configuration file
- seed, when randomized
- worker count
They end with a “Slowest tests” block when a test took more than 500 ms. The block lists up to five tests that exceed this threshold.
Fast suites do not print the block.
--profile extends the slowest-test list to 25 entries.