packages/requirements/tests/Integration/Console/SpecPolicyTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Integration\Console;
6
7use JsonException;
8use PHPUnit\Framework\Attributes\CoversClass;
9use PHPUnit\Framework\Attributes\DataProvider;
10use PHPUnit\Framework\Attributes\Large;
11use PHPUnit\Framework\Attributes\UsesClass;
12use PHPUnit\Framework\TestCase;
13use Requirements\Console\Application;
14use Requirements\Input\Fields;
15use Tests\Fake\CommandLine as Cli;
16use Tests\Fake\CountingRunner;
17use Tests\Fake\ProjectDirectory;
18
19#[CoversClass(Application::class)]
20#[UsesClass(Fields::class)]
21#[Large]
22final class SpecPolicyTest extends TestCase
23{
24 /**
25 * @param list<array<string, mixed>> $items
26 * @param list<string> $options
27 * @param array<string, string> $statuses
28 * @throws JsonException
29 */
30 #[DataProvider('providerPolicies')]
31 public function testPoliciesControlExitCodesAndExecutedTargets(array $items, array $options, int $exitCode, array $statuses, string $executed): void
32 {
33 $project = new ProjectDirectory();
34 $project->write('requirements.yaml', [
35 'version' => 1,
36 'definitions' => ['definition.yaml'],
37 'extensions' => ['runners' => ['counting' => CountingRunner::class]],
38 'runners' => ['unit' => ['extension' => 'counting', 'command' => ['unused']]],
39 ]);
40 $project->write('definition.yaml', ['version' => 1, 'source' => ['id' => 'manual', 'uri' => 'source.html', 'format' => 'html', 'selector' => 'main p'], 'items' => $items]);
41 $process = Cli::run(['spec', '--json', ...$options], $project->directory);
42 self::assertSame($exitCode, $process->getExitCode(), $process->getOutput() . $process->getErrorOutput());
43 $report = Fields::mapping(json_decode($process->getOutput(), true, 512, JSON_THROW_ON_ERROR), 'report');
44 self::assertSame($exitCode === 0, $report['passed']);
45 self::assertSame($statuses, array_column(Fields::mapping($report['specifications'], 'rows'), 'status', 'id'));
46 self::assertSame(in_array('--all', $options, true), $report['all']);
47 self::assertSame(in_array('--strict', $options, true), $report['strict']);
48 self::assertSame($executed, $project->read('executions.txt'));
49 self::assertSame($executed !== '', is_file($project->path('executions.txt')));
50 }
51
52 /**
53 * @return array<string, array{list<array<string, mixed>>, list<string>, int, array<string, string>, string}>
54 */
55 public static function providerPolicies(): array
56 {
57 $base = ProjectDirectory::item();
58 $unlinked = [...$base, 'id' => 'UNLINKED'];
59 $unit = ['runner' => 'unit', 'target' => 'fast'];
60 $manual = [...$unit, 'run' => 'manual'];
61 $automatic = [...$base, 'id' => 'AUTO', 'tests' => [$unit, $manual]];
62 $deferred = [...$base, 'id' => 'MANUAL', 'tests' => [$manual, $manual]];
63 $mixed = [...$base, 'id' => 'MIXED', 'tests' => [$unit, ['runner' => 'unit', 'target' => 'empty', 'run' => 'manual']]];
64 $failure = [...$base, 'id' => 'FAILURE', 'tests' => [['runner' => 'unit', 'target' => 'empty'], $manual]];
65 $unsupported = [...$base, 'id' => 'UNSUPPORTED', 'status' => 'unsupported', 'reason' => 'Not implemented.', 'tests' => [['runner' => 'unit', 'target' => 'never', 'run' => 'manual']]];
66 $requirement = [...$base, 'id' => 'REQ', 'kind' => 'requirement'];
67 return [
68 'unlinked allowed' => [[$unlinked], [], 0, ['UNLINKED' => 'unverified'], ''],
69 'strict unlinked' => [[$unlinked], ['--strict'], 1, ['UNLINKED' => 'unverified'], ''],
70 'all does not invent links' => [[$unlinked], ['--all'], 0, ['UNLINKED' => 'unverified'], ''],
71 'all strict still needs links' => [[$unlinked], ['--all', '--strict'], 1, ['UNLINKED' => 'unverified'], ''],
72 'no test unlinked' => [[$unlinked], ['--no-test'], 0, ['UNLINKED' => 'not-run'], ''],
73 'no test strict' => [[$unlinked], ['--no-test', '--strict'], 1, ['UNLINKED' => 'not-run'], ''],
74 'empty selection' => [[$unlinked], ['--id=MISSING'], 0, [], ''],
75 'strict empty selection' => [[$unlinked], ['--id=MISSING', '--strict'], 1, [], ''],
76 'manual only' => [[$deferred], [], 0, ['MANUAL' => 'deferred'], ''],
77 'strict permits manual' => [[$deferred], ['--strict'], 0, ['MANUAL' => 'deferred'], ''],
78 'all executes manual once' => [[$deferred], ['--all', '--strict'], 0, ['MANUAL' => 'passed'], "fast\n"],
79 'mixed deferred' => [[$mixed], [], 0, ['MIXED' => 'deferred'], "fast\n"],
80 'all exposes failure' => [[$mixed], ['--all'], 1, ['MIXED' => 'failed'], "fast\nempty\n"],
81 'failure beats deferred' => [[$failure], [], 1, ['FAILURE' => 'failed'], "empty\n"],
82 'no test beats all' => [[$mixed], ['--no-test', '--all', '--strict'], 0, ['MIXED' => 'not-run'], ''],
83 'shared manual first' => [[$deferred, $automatic], [], 0, ['MANUAL' => 'passed', 'AUTO' => 'passed'], "fast\n"],
84 'shared automatic first' => [[$automatic, $deferred], [], 0, ['AUTO' => 'passed', 'MANUAL' => 'passed'], "fast\n"],
85 'filter excludes automatic reference' => [[$automatic, $deferred], ['--id=MANUAL'], 0, ['MANUAL' => 'deferred'], ''],
86 'all respects filter' => [[$failure, $deferred], ['--id=MANUAL', '--all'], 0, ['MANUAL' => 'passed'], "fast\n"],
87 'all respects support' => [[$unsupported, $requirement], ['--all', '--strict'], 0, ['UNSUPPORTED' => 'unsupported', 'REQ' => 'not-applicable'], ''],
88 ];
89 }
90}
91