Skip to content

Commit 56f3a9d

Browse files
committed
PHPC-714: Script to convert BSON corpus tests
1 parent 84dbfe0 commit 56f3a9d

File tree

2 files changed

+229
-0
lines changed

2 files changed

+229
-0
lines changed

scripts/convert-bson-corpus-tests.php

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
<?php
2+
3+
require_once __DIR__ . '/../tests/utils/tools.php';
4+
5+
$outputPath = realpath(__DIR__ . '/../tests') . '/bson-corpus/';
6+
7+
if ( ! is_dir($outputPath) && ! mkdir($outputPath, 0755, true)) {
8+
printf("Error creating output path: %s\n", $outputPath);
9+
}
10+
11+
foreach (array_slice($argv, 1) as $inputFile) {
12+
if ( ! is_readable($inputFile) || ! is_file($inputFile)) {
13+
printf("Error reading %s\n", $inputFile);
14+
continue;
15+
}
16+
17+
$test = json_decode(file_get_contents($inputFile), true);
18+
19+
if (json_last_error() !== JSON_ERROR_NONE) {
20+
printf("Error decoding %s: %s\n", $inputFile, json_last_error_msg());
21+
continue;
22+
}
23+
24+
if ( ! empty($test['deprecated'])) {
25+
printf("Skipping deprecated test file: %s\n", $inputFile);
26+
continue;
27+
}
28+
29+
if ( ! empty($test['valid'])) {
30+
foreach ($test['valid'] as $i => $case) {
31+
$outputFile = sprintf('%s-valid-%03d.phpt', pathinfo($inputFile, PATHINFO_FILENAME), $i + 1);
32+
$output = renderPhpt(getParamsForValid($test, $case));
33+
34+
if (false === file_put_contents($outputPath . '/' . $outputFile, $output)) {
35+
printf("Error writing valid[%d] in %s\n", $i, $inputFile);
36+
continue;
37+
}
38+
}
39+
}
40+
41+
if ( ! empty($test['decodeErrors'])) {
42+
foreach ($test['decodeErrors'] as $i => $case) {
43+
$outputFile = sprintf('%s-decodeError-%03d.phpt', pathinfo($inputFile, PATHINFO_FILENAME), $i + 1);
44+
$output = renderPhpt(getParamsForDecodeError($test, $case));
45+
46+
if (false === file_put_contents($outputPath . '/' . $outputFile, $output)) {
47+
printf("Error writing decodeErrors[%d] in %s\n", $i, $inputFile);
48+
continue;
49+
}
50+
}
51+
}
52+
53+
if ( ! empty($test['parseErrors'])) {
54+
foreach ($test['parseErrors'] as $i => $case) {
55+
$outputFile = sprintf('%s-parseError-%03d.phpt', pathinfo($inputFile, PATHINFO_FILENAME), $i + 1);
56+
try {
57+
$output = renderPhpt(getParamsForParseError($test, $case));
58+
} catch (UnexpectedValueException $e) {
59+
printf("Parse errors not supported for BSON type: %s\n", $test['bson_type']);
60+
continue;
61+
}
62+
63+
if (false === file_put_contents($outputPath . '/' . $outputFile, $output)) {
64+
printf("Error writing parseErrors[%d] in %s\n", $i, $inputFile);
65+
continue;
66+
}
67+
}
68+
}
69+
}
70+
71+
function getParamsForValid(array $test, array $case)
72+
{
73+
$code = '';
74+
$expect = '';
75+
76+
$bson = $case['bson'];
77+
$canonicalBson = isset($case['canonical_bson']) ? $case['canonical_bson'] : $bson;
78+
$expectedCanonicalBson = strtolower($canonicalBson);
79+
80+
$code .= sprintf('$bson = hex2bin(%s);', var_export($bson, true)) . "\n";
81+
82+
$code .= "\n// BSON to Canonical BSON\n";
83+
$code .= 'echo bin2hex(fromPHP(toPHP($bson))), "\n";' . "\n";
84+
$expect .= $expectedCanonicalBson . "\n";
85+
86+
if ($bson !== $canonicalBson) {
87+
$code .= "\n" . sprintf('$canonicalBson = hex2bin(%s);', var_export($canonicalBson, true)) . "\n";
88+
$code .= "\n// Canonical BSON to Canonical BSON\n";
89+
$code .= 'echo bin2hex(fromPHP(toPHP($canonicalBson))), "\n";' . "\n";
90+
$expect .= $expectedCanonicalBson . "\n";
91+
}
92+
93+
if (isset($case['extjson'])) {
94+
$json = $case['extjson'];
95+
$canonicalJson = isset($case['canonical_extjson']) ? $case['canonical_extjson'] : $json;
96+
$expectedCanonicalJson = json_canonicalize($canonicalJson);
97+
$lossy = isset($case['lossy']) ? (boolean) $case['lossy'] : false;
98+
99+
$code .= "\n// BSON to Canonical extJSON\n";
100+
$code .= 'echo json_canonicalize(toJSON($bson)), "\n";' . "\n";;
101+
$expect .= $expectedCanonicalJson . "\n";
102+
103+
$code .= "\n" . sprintf('$json = %s;', var_export($json, true)) . "\n";
104+
$code .= "\n// extJSON to Canonical extJSON\n";
105+
$code .= 'echo json_canonicalize(toJSON(fromJSON($json))), "\n";' . "\n";;
106+
$expect .= $expectedCanonicalJson . "\n";
107+
108+
if ($bson !== $canonicalBson) {
109+
$code .= "\n// Canonical BSON to Canonical extJSON\n";
110+
$code .= 'echo json_canonicalize(toJSON($canonicalBson)), "\n";' . "\n";;
111+
$expect .= $expectedCanonicalJson . "\n";
112+
}
113+
114+
if ($json !== $canonicalJson) {
115+
$code .= "\n" . sprintf('$canonicalJson = %s;', var_export($canonicalJson, true)) . "\n";
116+
$code .= "\n// Canonical extJSON to Canonical extJSON\n";
117+
$code .= 'echo json_canonicalize(toJSON(fromJSON($canonicalJson))), "\n";' . "\n";;
118+
$expect .= $expectedCanonicalJson . "\n";
119+
}
120+
121+
if ( ! $lossy) {
122+
$code .= "\n// extJSON to Canonical BSON\n";
123+
$code .= 'echo bin2hex(fromJSON($json)), "\n";' . "\n";;
124+
$expect .= $expectedCanonicalBson . "\n";
125+
126+
if ($json !== $canonicalJson) {
127+
$code .= "\n// Canonical extJSON to Canonical BSON\n";
128+
$code .= 'echo bin2hex(fromJSON($canonicalJson)), "\n";' . "\n";;
129+
$expect .= $expectedCanonicalBson . "\n";
130+
}
131+
}
132+
}
133+
134+
return [
135+
'%NAME%' => sprintf('%s: %s', trim($test['description']), trim($case['description'])),
136+
'%CODE%' => trim($code),
137+
'%EXPECT%' => trim($expect),
138+
];
139+
}
140+
141+
function getParamsForDecodeError(array $test, array $case)
142+
{
143+
$code = sprintf('$bson = hex2bin(%s);', var_export($case['bson'], true)) . "\n\n";
144+
$code .= "throws(function() use (\$bson) {\n";
145+
$code .= " var_dump(toPHP(\$bson));\n";
146+
$code .= "}, 'MongoDB\Driver\Exception\UnexpectedValueException');";
147+
148+
/* We do not test for the exception message, since that may differ based on
149+
* the nature of the decoding error. */
150+
$expect = "OK: Got MongoDB\Driver\Exception\UnexpectedValueException";
151+
152+
return [
153+
'%NAME%' => sprintf('%s: %s', trim($test['description']), trim($case['description'])),
154+
'%CODE%' => trim($code),
155+
'%EXPECT%' => trim($expect),
156+
];
157+
}
158+
159+
function getParamsForParseError(array $test, array $case)
160+
{
161+
$code = '';
162+
$expect = '';
163+
164+
switch ($test['bson_type']) {
165+
case '0x13': // Decimal128
166+
$code = "throws(function() {\n";
167+
$code .= sprintf(" new MongoDB\BSON\Decimal128(%s);\n", var_export($case['string'], true));
168+
$code .= "}, 'MongoDB\Driver\Exception\InvalidArgumentException');";
169+
170+
/* We do not test for the exception message, since that may differ
171+
* based on the nature of the parse error. */
172+
$expect = "OK: Got MongoDB\Driver\Exception\InvalidArgumentException";
173+
break;
174+
175+
default:
176+
throw new UnexpectedValueException;
177+
}
178+
179+
return [
180+
'%NAME%' => sprintf('%s: %s', trim($test['description']), trim($case['description'])),
181+
'%CODE%' => trim($code),
182+
'%EXPECT%' => trim($expect),
183+
];
184+
}
185+
186+
function renderPhpt(array $params)
187+
{
188+
$template = <<< 'TEMPLATE'
189+
--TEST--
190+
%NAME%
191+
--DESCRIPTION--
192+
Generated by scripts/convert-bson-corpus-tests.php
193+
194+
DO NOT EDIT THIS FILE
195+
--FILE--
196+
<?php
197+
198+
require_once __DIR__ . '/../utils/tools.php';
199+
200+
%CODE%
201+
202+
?>
203+
===DONE===
204+
<?php exit(0); ?>
205+
--EXPECT--
206+
%EXPECT%
207+
===DONE===
208+
TEMPLATE;
209+
210+
return str_replace(array_keys($params), array_values($params), $template);
211+
}

tests/utils/tools.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
23
/**
34
* Prints a traditional hex dump of byte values and printable characters.
45
*
@@ -31,6 +32,23 @@ function hex_dump($data, $width = 16)
3132
}
3233
}
3334

35+
/**
36+
* Canonicalizes a JSON string.
37+
*
38+
* @param string $json
39+
* @return string
40+
*/
41+
function json_canonicalize($json)
42+
{
43+
$json = json_encode(json_decode($json));
44+
45+
/* Versions of PHP before 7.1 replace empty JSON keys with "_empty_" when
46+
* decoding to a stdClass (see: https://bugs.php.net/bug.php?id=46600). Work
47+
* around this by replacing "_empty_" keys before returning.
48+
*/
49+
return str_replace('"_empty_":', '"":', $json);
50+
}
51+
3452
/**
3553
* Return a collection name to use for the test file.
3654
*

0 commit comments

Comments
 (0)