packages/requirements/tests/Unit/Test/BehatRunnerTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Test;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\DataProvider;
9use PHPUnit\Framework\Attributes\Medium;
10use PHPUnit\Framework\Attributes\UsesClass;
11use PHPUnit\Framework\TestCase;
12use Requirements\Test\BehatRunner;
13use Requirements\Test\JUnit;
14use Requirements\Test\ProcessRunner;
15use Requirements\Test\RunnerConfig;
16use Requirements\Test\TestResult;
17use Tests\Fake\BehatSuite;
18use Tests\Fake\ProjectDirectory;
19
20#[CoversClass(BehatRunner::class)]
21#[UsesClass(ProcessRunner::class)]
22#[UsesClass(RunnerConfig::class)]
23#[UsesClass(JUnit::class)]
24#[UsesClass(TestResult::class)]
25#[Medium]
26final class BehatRunnerTest extends TestCase
27{
28 #[DataProvider('providerRunRunsScenarioAndOutlineAndRejectsUndefinedOrMissingTests')]
29 public function testRunRunsScenarioAndOutlineAndRejectsUndefinedOrMissingTests(int $line, string $status, int $tests): void
30 {
31 $project = new ProjectDirectory();
32 $command = BehatSuite::write($project->directory);
33 self::assertNotEmpty($command);
34 $config = new RunnerConfig('behat', $command, $project->directory);
35 $result = (new BehatRunner())->run($config, 'example.feature:' . $line);
36 self::assertSame($status, $result->status, $result->message);
37 self::assertSame($tests, $result->tests, $result->message);
38 }
39
40 /**
41 * @return list<array{int, string, int}>
42 */
43 public static function providerRunRunsScenarioAndOutlineAndRejectsUndefinedOrMissingTests(): array
44 {
45 return [[2, 'passed', 1], [4, 'failed', 1], [6, 'failed', 1], [8, 'passed', 2], [3, 'error', 0], [99, 'error', 0]];
46 }
47
48 public function testRunAcceptsAnAbsoluteFeaturePath(): void
49 {
50 $project = new ProjectDirectory();
51 $command = BehatSuite::write($project->directory);
52 self::assertNotEmpty($command);
53 $config = new RunnerConfig('behat', $command, $project->directory);
54 $result = (new BehatRunner())->run($config, $project->path('example.feature') . ':2');
55 self::assertSame('passed', $result->status, $result->message);
56 self::assertSame(1, $result->tests, $result->message);
57 }
58
59 #[DataProvider('providerRunRejectsMalformedTargets')]
60 public function testRunRejectsMalformedTargets(string $target): void
61 {
62 $project = new ProjectDirectory();
63 $project->put('-example.feature', "Feature: x\n Scenario: y\n");
64 $config = new RunnerConfig('behat', [PHP_BINARY, '-r', 'file_put_contents("ran.txt", "ran");'], $project->directory);
65 self::assertSame(['status' => 'error', 'tests' => 0, 'message' => 'Behat targets must select one scenario by file.feature:line.'], (new BehatRunner())->run($config, $target)->toArray());
66 self::assertFileDoesNotExist($project->path('ran.txt'));
67 }
68
69 /**
70 * @return array<string, array{string}>
71 */
72 public static function providerRunRejectsMalformedTargets(): array
73 {
74 return [
75 'no line' => ['example.feature'],
76 'empty line' => ['example.feature:'],
77 'line zero' => ['example.feature:0'],
78 'leading zero' => ['example.feature:02'],
79 'not a feature' => ['example.txt:2'],
80 'no file name' => [':2'],
81 'option' => ['-example.feature:2'],
82 'trailing newline' => ["example.feature:2\n"],
83 'line range' => ['example.feature:2-4'],
84 ];
85 }
86
87 /**
88 * @param array<string, string> $files
89 */
90 #[DataProvider('providerRunRejectsLinesWithoutAScenarioHeader')]
91 public function testRunRejectsLinesWithoutAScenarioHeader(array $files, string $target): void
92 {
93 $project = new ProjectDirectory();
94 array_map($project->put(...), array_keys($files), $files);
95 $config = new RunnerConfig('behat', [PHP_BINARY, '-r', 'file_put_contents("ran.txt", "ran");'], $project->directory);
96 self::assertSame(['status' => 'error', 'tests' => 0, 'message' => 'The target line must be an English Scenario or Scenario Outline header.'], (new BehatRunner())->run($config, $target)->toArray());
97 self::assertFileDoesNotExist($project->path('ran.txt'));
98 }
99
100 /**
101 * @return array<string, array{array<string, string>, string}>
102 */
103 public static function providerRunRejectsLinesWithoutAScenarioHeader(): array
104 {
105 $feature = "Feature: Headers\n Scenario:\n Scenario: \n Background: setup\n Scenarios: x\n # Scenario: commented\n Szenario: German\n";
106 return [
107 'missing file' => [[], 'missing.feature:1'],
108 'directory' => [['example.feature/inner.txt' => 'x'], 'example.feature:1'],
109 'feature line' => [['example.feature' => $feature], 'example.feature:1'],
110 'untitled scenario' => [['example.feature' => $feature], 'example.feature:2'],
111 'blank title' => [['example.feature' => $feature], 'example.feature:3'],
112 'background' => [['example.feature' => $feature], 'example.feature:4'],
113 'plural keyword' => [['example.feature' => $feature], 'example.feature:5'],
114 'comment' => [['example.feature' => $feature], 'example.feature:6'],
115 'other language' => [['example.feature' => $feature], 'example.feature:7'],
116 'after the last line' => [['example.feature' => $feature], 'example.feature:8'],
117 ];
118 }
119
120 public function testRunReadsTheHeaderAtTheExactLine(): void
121 {
122 $project = new ProjectDirectory();
123 $project->put('example.feature', "Feature: Offsets\nScenario: first\n Given a step\n");
124 $project->put('report.xml', '<testsuite><testcase/></testsuite>');
125 $config = new RunnerConfig('behat', [PHP_BINARY, '-r', 'copy("report.xml", $argv[array_search("--out", $argv, true) + 1] . "/report.xml");', '--'], $project->directory);
126 self::assertSame(['status' => 'passed', 'tests' => 1, 'message' => ''], (new BehatRunner())->run($config, 'example.feature:2')->toArray());
127 }
128
129 public function testRunPassesTheTargetAfterTheOptions(): void
130 {
131 $project = new ProjectDirectory();
132 $project->put('example.feature', "Feature: Arguments\n Scenario Outline: examples\n");
133 $config = new RunnerConfig('behat', [PHP_BINARY, '-r', 'file_put_contents("args.txt", implode(" ", array_slice($argv, 1)));', '--'], $project->directory);
134 (new BehatRunner())->run($config, 'example.feature:2');
135 self::assertMatchesRegularExpression('~^--strict --no-interaction --no-snippets --format junit --out /\S+ -- example\.feature:2$~D', $project->read('args.txt'));
136 }
137}
138