Skip to content

Commit dc89f91

Browse files
authored
test: test against the errors generated by publiccode-parser-go CLI (#12)
Use the upstream testdata to generate golden files with the reference Go implementation to test for actual error messages and line numbers, key names as well.
1 parent a995a9e commit dc89f91

File tree

2 files changed

+216
-6
lines changed

2 files changed

+216
-6
lines changed

.github/workflows/test.yml

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,22 @@ jobs:
2929
- name: Install dependencies
3030
run: composer install --prefer-dist --no-interaction
3131

32-
- run: |
33-
git clone --depth 1 https://github.com/italia/publiccode-parser-go.git /tmp/publiccode-parser-go
34-
mv /tmp/publiccode-parser-go/testdata/* tests/fixtures/testdata/
35-
36-
- run: composer run test
37-
3832
- name: Run static analysis
3933
run: composer run phpstan
4034

4135
- name: Run linter
4236
run: composer run cs
37+
38+
- name: Build publiccode-parser CLI (pinned by go-src/go.mod)
39+
run: |
40+
mkdir -p "$GITHUB_WORKSPACE/bin"
41+
go build -C go-src -o "$GITHUB_WORKSPACE/bin/publiccode-parser" \
42+
github.com/italia/publiccode-parser-go/v4/publiccode-parser
43+
44+
- run: echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH
45+
46+
- run: |
47+
git clone --depth 1 https://github.com/italia/publiccode-parser-go.git /tmp/publiccode-parser-go
48+
mv /tmp/publiccode-parser-go/testdata/* tests/fixtures/testdata/
49+
50+
- run: composer run test

tests/ParserGoldenFilesTest.php

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
<?php
2+
3+
/**
4+
* Check the actual errors/warnings from the Go implementation, using
5+
* `publiccode-parser --json` as a reference so an check that the binding
6+
* behaves exactly like it should without having to duplicate the Go
7+
* testcases here.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace Bfabio\PublicCodeParser\Tests;
13+
14+
use Bfabio\PublicCodeParser\Exception\ValidationException;
15+
use Bfabio\PublicCodeParser\Parser;
16+
use Bfabio\PublicCodeParser\ParserConfig;
17+
use PHPUnit\Framework\TestCase;
18+
19+
final class ParserGoldenFilesTest extends TestCase
20+
{
21+
private const ROOT = __DIR__ . '/fixtures/testdata/v0';
22+
private const GOLDEN_DIR = self::ROOT . '.golden';
23+
24+
private Parser $parser;
25+
private Parser $parserNoNetwork;
26+
27+
protected function setUp(): void
28+
{
29+
if (!is_dir(self::GOLDEN_DIR)) {
30+
mkdir(self::GOLDEN_DIR, 0755, true);
31+
}
32+
33+
$this->parser = new Parser();
34+
35+
$opts = new ParserConfig();
36+
$opts->setDisableNetwork(true);
37+
$this->parserNoNetwork = new Parser($opts);
38+
}
39+
40+
/** @dataProvider validFilesWithWarningsProvider */
41+
/* public function testGenerateGoldenValidWithWarnings(string $yamlPath): void */
42+
/* { */
43+
/* $this->generateGolden($yamlPath); */
44+
/**/
45+
/* $pc = $this->parser->parseFile($yamlPath); */
46+
/**/
47+
/* $pc->getWarnings() */
48+
/* } */
49+
50+
/** @dataProvider validFilesWithWarningsNoNetworkProvider */
51+
/* public function testGenerateGoldenValidWithWarningsNoNetwork(string $yamlPath): void */
52+
/* { */
53+
/* $this->generateGolden($yamlPath, true); */
54+
/* $this->assertFileExists($this->goldenPath($yamlPath)); */
55+
/* } */
56+
57+
/** @dataProvider invalidFilesProvider */
58+
public function testGenerateGoldenInvalid(string $yamlPath): void
59+
{
60+
$goldenPath = $this->generateGolden($yamlPath);
61+
62+
try {
63+
$this->parser->parseFile($yamlPath);
64+
65+
$this->fail('Expected ValidationException was not thrown');
66+
} catch (ValidationException $e) {
67+
$this->assertErrorsMatchGolden($e, $goldenPath);
68+
}
69+
}
70+
71+
/** @dataProvider invalidFilesNoNetworkProvider */
72+
public function testGenerateGoldenInvalidNoNetwork(string $yamlPath): void
73+
{
74+
$goldenPath = $this->generateGolden($yamlPath, true);
75+
76+
try {
77+
$this->parserNoNetwork->parseFile($yamlPath);
78+
79+
$this->fail('Expected ValidationException was not thrown');
80+
} catch (ValidationException $e) {
81+
$this->assertErrorsMatchGolden($e, $goldenPath);
82+
}
83+
}
84+
85+
/** @return non-empty-array<string, array{string}> */
86+
// Enable when https://github.com/bfabio/publiccode-parser-php/issues/11 is fixed
87+
/* public static function validFilesWithWarningsProvider(): array */
88+
/* { */
89+
/* // no 'valid/' directory here because those files don't have any errors or warnings */
90+
/* return self::scanTestdata(['valid_with_warnings']); */
91+
/* } */
92+
93+
/** @return non-empty-array<string, array{string}> */
94+
// Enable when https://github.com/bfabio/publiccode-parser-php/issues/11 is fixed
95+
/* public static function validFilesWithWarningsNoNetworkProvider(): array */
96+
/* { */
97+
/* // no 'valid/no-network' directory here because those files don't have any errors or warnings */
98+
/* return self::scanTestdata(['valid/no-network', 'valid_with_warnings/no-network']); */
99+
/* } */
100+
101+
/** @return non-empty-array<string, array{string}> */
102+
public static function invalidFilesProvider(): array
103+
{
104+
return self::scanTestdata(['invalid']);
105+
}
106+
107+
/** @return non-empty-array<string, array{string}> */
108+
public static function invalidFilesNoNetworkProvider(): array
109+
{
110+
return self::scanTestdata(['invalid/no-network']);
111+
}
112+
113+
/**
114+
* @param list<non-empty-string> $paths
115+
* @return non-empty-array<string, array{string}>
116+
*/
117+
private static function scanTestdata(array $paths): array
118+
{
119+
$root = __DIR__ . '/fixtures/testdata';
120+
$out = [];
121+
122+
foreach (['v0'] as $v) {
123+
foreach ($paths as $path) {
124+
foreach (glob("$root/$v/$path/*.yml") ?: [] as $file) {
125+
$out[$file] = [$file];
126+
}
127+
}
128+
}
129+
if (!$out) {
130+
self::fail("Nessun file trovato in $root");
131+
}
132+
133+
return $out;
134+
}
135+
136+
private function goldenPath(string $yamlPath): string
137+
{
138+
$relYaml = substr($yamlPath, strlen(self::ROOT) + 1);
139+
$outFile = self::GOLDEN_DIR . "/$relYaml.json";
140+
141+
$outDir = dirname($outFile);
142+
if (!is_dir($outDir)) {
143+
mkdir($outDir, 0755, true);
144+
}
145+
146+
return $outFile;
147+
}
148+
149+
private function generateGolden(string $yamlPath, bool $noNetwork = false): string
150+
{
151+
$outFile = $this->goldenPath($yamlPath);
152+
153+
$cmd = sprintf(
154+
"publiccode-parser --json %s %s > $outFile 2>&1",
155+
$noNetwork ? '--no-network' : '',
156+
escapeshellarg($yamlPath),
157+
);
158+
159+
exec($cmd, $output, $exitCode);
160+
161+
if ($exitCode !== 0) {
162+
throw new \RuntimeException("publiccode-parser failed on $yamlPath (exit $exitCode)");
163+
}
164+
165+
return $outFile;
166+
}
167+
168+
private function assertErrorsMatchGolden(
169+
ValidationException $e,
170+
string $goldenPath,
171+
): void {
172+
$content = file_get_contents($goldenPath);
173+
if ($content === false) {
174+
throw new \RuntimeException("Cannot read file: $goldenPath");
175+
}
176+
177+
$golden = json_decode($content, true);
178+
179+
$goldenMessages = array_map(
180+
fn (array $g) =>
181+
sprintf(
182+
'publiccode.yml:%d:%d: %s: %s%s',
183+
$g['line'],
184+
$g['column'],
185+
$g['type'],
186+
$g['key'] !== '' ? $g['key'] . ': ' : '',
187+
$g['description'],
188+
),
189+
array_filter($golden, fn (array $g) => $g['type'] === 'error'),
190+
);
191+
$phpErrors = $e->getErrors();
192+
193+
sort($goldenMessages);
194+
sort($phpErrors);
195+
196+
$this->assertSame(
197+
$goldenMessages,
198+
$phpErrors,
199+
"Mismatch between golden file and PHP binding for $goldenPath",
200+
);
201+
}
202+
}

0 commit comments

Comments
 (0)