Greenlight

Documentation

Use Greenlight

On this page

Test attachments

Attachments keep diagnostic data with the related test result. They do not write this data to captured stdout. Tests and worker-side plugins can attach JSON values, text, bytes, or files that already exist.

Attachment creation

Ask for Greenlight\Artifact\Attachments through constructor injection:

use Greenlight\Attribute\NoExpectations;
use Greenlight\Attribute\Test;
use Greenlight\Artifact\Attachments;
use Greenlight\Sandbox\TemporaryDirectory;

final readonly class DiagnosticAttachmentsTest
{
    public function __construct(
        private Attachments $attachments,
        private TemporaryDirectory $temporary,
    ) {}

    #[Test]
    #[NoExpectations]
    public function recordsDiagnosticData(): void
    {
        $this->attachments->value('response.json', [
            'status' => 202,
            'requestId' => 'request-123',
        ]);
        $this->attachments->text('application.log', 'Order accepted.');
        $this->attachments->bytes('trace.bin', "\x00\x01");

        $source = $this->temporary->path() . '/export.csv';
        \file_put_contents($source, "id,status\n123,accepted\n");
        $this->attachments->file('export.csv', $source);
    }
}

This example uses fixed diagnostic values. Replace them with values from the system under test. The test has no expectations because it only demonstrates attachment creation. The default retention policy discards these attachments when the test passes.

value() encodes its value as JSON. text() and bytes() accept an optional media type. file() copies a regular file and detects its media type when possible.

Media types use type/subtype form with optional parameters. Greenlight rejects malformed values and control characters.

Greenlight copies the content before the method returns. You can remove a temporary source file after file() returns. Later changes to a value or file do not change the attachment.

By default, Greenlight retains attachments when the final result fails or has an error. It also retains them when the transformation log contains an earlier failed or errored outcome. A change to passed or skipped therefore preserves failure evidence.

To retain an attachment from a result without failure evidence, set its retention to AttachmentRetention::Always:

use Greenlight\Artifact\AttachmentRetention;

$attachments->text(
    'timing.txt',
    $timing,
    retention: AttachmentRetention::Always,
);

Each retry has separate attachments. Attachments from failed attempts stay on the final result even if a later attempt passes. The attempt field identifies the source attempt for each attachment. For the final attempt, the retention rules above apply to the final result and its transformation log.

Output directory

By default, Greenlight writes retained attachments to a unique run directory below build/greenlight-artifacts. A run with no retained attachments does not create an empty directory. Change the parent directory in greenlight.php:

use Greenlight\Config\ArtifactBuilder;
use Greenlight\Config\GreenlightConfig;

return GreenlightConfig::create()
    ->artifacts(fn (ArtifactBuilder $artifacts) => $artifacts
        ->directory('build/test-evidence'));

Use --artifacts-dir to override it for one run:

vendor/bin/greenlight run --artifacts-dir=build/ci-evidence

By default, Greenlight does not remove completed run directories. Configure one or more limits to remove old directories after a run completes:

use Greenlight\Config\ArtifactBuilder;
use Greenlight\Config\GreenlightConfig;

return GreenlightConfig::create()
    ->artifacts(fn (ArtifactBuilder $artifacts) => $artifacts
        ->maxCompletedRuns(20)
        ->maxCompletedRunAge(7 * 24 * 60 * 60)
        ->maxRetainedSize('2G'));

The age value is in seconds. Greenlight applies age, count, and byte limits in that order. Each limit removes the oldest eligible completed run first.

Use greenlight artifacts:prune --dry-run to examine the configured policy. Use the command without --dry-run to apply the policy.

Metadata and names

Each attachment records its name, kind, media type, byte size, SHA-256 digest, attempt number, retention policy, and published path. The metadata does not include the original source path or the attachment content.

Use a non-empty label for each attachment name. Use valid UTF-8 and no more than 120 bytes. Names cannot contain directory separators or control characters. They cannot equal . or ... Repeated names within one attempt receive -2, -3, and later suffixes in their published filenames. Their logical names remain unchanged in the result metadata.

Greenlight converts test IDs to slugs and hashes before it uses them in paths.

File safety

Source files must be regular files, not symlinks. Greenlight verifies that a source does not change during the copy operation. Published paths stay in the configured run directory. Greenlight creates artifact files with private permissions on supported platforms.

Each run directory contains versioned ownership metadata and a lifecycle lock. Greenlight prunes only completed directories with an exact content manifest. It does not prune active, incomplete, changed, unknown, or symbolic-link directories. Cleanup claims use atomic directory renames and a parent lock.

Greenlight does not inspect or redact attachment content. Before you attach a value or file, remove secrets and personal data. Apply the policy for sensitive CI artifacts to the output directory.

Limits

The defaults are:

Configure the limits with maxAttachmentsPerTest(), maxAttachmentSize(), maxTestSize(), maxRunAttachments(), and maxRunSize() on ArtifactBuilder. Size methods accept values such as 10M and 2G. A limit violation fails the active test with an attachment error. Greenlight does not truncate attachment content.

Greenlight coordinates run limits through private shared staging. Thus, they apply across parallel workers. Per-test limits include all attempts, even when Greenlight discards some attachments later. Greenlight releases run quota when it discards an attachment.

Completed run limits are independent of attachment limits. The completed run defaults are unbounded. Retention failures do not fail a test run.

Plugins

$context->attachments gives the same attempt-owned object to BeforeTestSubscriber::beforeTest() and AfterTestSubscriber::afterTest(). A plugin can attach data before the test or after it examines the result.

A retry decider receives attachment metadata on the TestResult, but it cannot read the content through the result. See writing plugins for the plugin interfaces.

Reporters and CI

The tty and plain reporters print the paths of retained attachments. JUnit adds [[ATTACHMENT|path]] markers to the test case’s system-out. GitHub annotations include attachment paths and an artifact directory notice. TeamCity emits artifact metadata and publishes the run directory.

JSONL includes attachment metadata and reports the run directory in run-started.artifactsDirectory. See the JSONL schema.

For other CI systems, use a post-test step that always runs. Upload the reported run directory from this step.

CI platform retention remains authoritative after upload. Greenlight retention controls only the runner filesystem.