packages/requirements/tests/Unit/Test/ProcessRunnerTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Test;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\Medium;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use Requirements\Test\JUnit;
12use Requirements\Test\ProcessRunner;
13use Requirements\Test\RunnerConfig;
14use Requirements\Test\TestResult;
15use Tests\Fake\ProjectDirectory;
16
17#[CoversClass(ProcessRunner::class)]
18#[UsesClass(RunnerConfig::class)]
19#[UsesClass(JUnit::class)]
20#[UsesClass(TestResult::class)]
21#[Medium]
22final class ProcessRunnerTest extends TestCase
23{
24    public function testRunFailsOnTimeoutAndMissingReport(): void
25    {
26        $project = new ProjectDirectory();
27        $runner = new ProcessRunner();
28        $config = new RunnerConfig('custom', [PHP_BINARY, '-r', 'usleep(500000);'], $project->directory, 0.05);
29        self::assertSame('error', $runner->run($config, static fn (string $directory): array => [])->status);
30        $config = new RunnerConfig('custom', [PHP_BINARY, '-r', 'exit(0);'], $project->directory);
31        self::assertSame('error', $runner->run($config, static fn (string $directory): array => [])->status);
32    }
33
34    public function testRunReportsTheTimeoutMessage(): void
35    {
36        $project = new ProjectDirectory();
37        $config = new RunnerConfig('custom', [PHP_BINARY, '-r', 'usleep(500000);'], $project->directory, 0.05);
38        $result = (new ProcessRunner())->run($config, static fn (string $directory): array => []);
39        self::assertSame(0, $result->tests);
40        self::assertStringContainsString('exceeded the timeout of 0.05 seconds', $result->message);
41    }
42
43    public function testRunReportsTheOutputOfARunWithoutReports(): void
44    {
45        $project = new ProjectDirectory();
46        $config = new RunnerConfig('custom', [PHP_BINARY, '-r', 'echo "to stdout\n"; fwrite(STDERR, "to stderr\n");'], $project->directory);
47        self::assertSame(['status' => 'error', 'tests' => 0, 'message' => "No executed tests in fresh JUnit reports. to stdout\nto stderr"], (new ProcessRunner())->run($config, static fn (string $directory): array => [])->toArray());
48    }
49
50    public function testRunReadsTheReportWrittenToTheFreshDirectory(): void
51    {
52        $project = new ProjectDirectory();
53        $config = new RunnerConfig('custom', [PHP_BINARY, '-r', 'file_put_contents($argv[1], "<testsuite><testcase name=\"a\"/><testcase name=\"b\"/></testsuite>");'], $project->directory);
54        $result = (new ProcessRunner())->run($config, static fn (string $directory): array => [$directory . '/report.xml']);
55        self::assertSame(['status' => 'passed', 'tests' => 2, 'message' => ''], $result->toArray());
56    }
57
58    public function testRunAppendsTheArgumentsToTheCommand(): void
59    {
60        $project = new ProjectDirectory();
61        $config = new RunnerConfig('custom', [PHP_BINARY, '-r', 'file_put_contents($argv[2], "<testsuite><testcase name=\"" . $argv[1] . "\"/></testsuite>"); echo $argv[1];', 'fixed'], $project->directory);
62        $result = (new ProcessRunner())->run($config, static fn (string $directory): array => [$directory . '/report.xml']);
63        self::assertSame('passed', $result->status);
64    }
65
66    public function testRunRunsInTheConfiguredDirectory(): void
67    {
68        $project = new ProjectDirectory();
69        $config = new RunnerConfig('custom', [PHP_BINARY, '-r', 'file_put_contents("cwd.txt", "here"); file_put_contents($argv[1], "<testsuite><testcase/></testsuite>");'], $project->directory);
70        (new ProcessRunner())->run($config, static fn (string $directory): array => [$directory . '/report.xml']);
71        self::assertSame('here', $project->read('cwd.txt'));
72    }
73
74    public function testRunReadsEveryXmlReportAndIgnoresOtherFiles(): void
75    {
76        $project = new ProjectDirectory();
77        $config = new RunnerConfig('custom', [PHP_BINARY, '-r', 'file_put_contents($argv[1] . "/a.xml", "<testsuite><testcase/></testsuite>"); file_put_contents($argv[1] . "/b.xml", "<testsuite><testcase/></testsuite>"); file_put_contents($argv[1] . "/notes.txt", "<testsuite><testcase><failure/></testcase></testsuite>");'], $project->directory);
78        $result = (new ProcessRunner())->run($config, static fn (string $directory): array => [$directory]);
79        self::assertSame(['status' => 'passed', 'tests' => 2, 'message' => ''], $result->toArray());
80    }
81
82    public function testRunFailsWithTheOutputOfAFailingExit(): void
83    {
84        $project = new ProjectDirectory();
85        $config = new RunnerConfig('custom', [PHP_BINARY, '-r', 'file_put_contents($argv[1], "<testsuite><testcase/></testsuite>"); echo "broken\n"; exit(3);'], $project->directory);
86        $result = (new ProcessRunner())->run($config, static fn (string $directory): array => [$directory . '/report.xml']);
87        self::assertSame(['status' => 'failed', 'tests' => 1, 'message' => 'broken'], $result->toArray());
88    }
89
90    public function testRunRemovesTheReportDirectory(): void
91    {
92        $project = new ProjectDirectory();
93        $config = new RunnerConfig('custom', [PHP_BINARY, '-r', 'file_put_contents($argv[2] . "/report.xml", "<testsuite><testcase/></testsuite>"); file_put_contents($argv[2] . "/log.txt", "x"); file_put_contents($argv[1], $argv[2]);', $project->path('seen.txt')], $project->directory);
94        (new ProcessRunner())->run($config, static fn (string $directory): array => [$directory]);
95        self::assertStringStartsWith(sys_get_temp_dir() . '/requirements-', $project->read('seen.txt'));
96        self::assertDirectoryDoesNotExist($project->read('seen.txt'));
97    }
98
99    public function testRunReportsACommandThatCannotStart(): void
100    {
101        $project = new ProjectDirectory();
102        $config = new RunnerConfig('custom', [PHP_BINARY, '-r', 'exit(0);'], $project->path('missing'));
103        $result = (new ProcessRunner())->run($config, static fn (string $directory): array => []);
104        self::assertSame('error', $result->status);
105        self::assertSame(0, $result->tests);
106        self::assertStringContainsString('cwd', $result->message);
107    }
108}
109