← Back
Editing: run-test-case
#!/usr/bin/php <?php declare(strict_types=1); // Support running from git checkout $projectDirectory = dirname(__DIR__); if (is_dir($projectDirectory . DIRECTORY_SEPARATOR . 'vendor')) { set_include_path($projectDirectory . PATH_SEPARATOR . get_include_path()); } // Autoload composer classes $composerAutoload = stream_resolve_include_path('vendor/autoload.php'); if (!$composerAutoload) { echo "Cannot load json-schema library\n"; exit(1); } require $composerAutoload; use JsonSchema\Constraints\Constraint; use JsonSchema\Constraints\Factory; use JsonSchema\DraftIdentifiers; use JsonSchema\SchemaStorage; use JsonSchema\Validator; $arOptions = []; $arArgs = []; array_shift($argv); // script itself foreach ($argv as $arg) { if ($arg[0] === '-') { $arOptions[$arg] = true; } else { $arArgs[] = $arg; } } if (count($arArgs) < 2 || isset($arOptions['--help']) || isset($arOptions['-h'])) { echo <<<HLP Run JSON Schema test suite test cases Usage: run-test-case <test-file> "<group description>[/<test description>]" Arguments: test-file Path to a test JSON file. Resolved in order: 1. As given (absolute or relative to CWD) 2. Relative to the bundled test suite root description "<group>" or "<group>/<test>" — split on the first "/" only. Exact match is tried first, then case-insensitive substring. Omit the test part to run all tests in the group. Options: -v --verbose Also print the schema -h --help Show this help Exit codes: 0 All matched tests passed 1 At least one test failed 2 Usage error, file not found, or group/test not found HLP; exit(0); } $suiteRoot = $projectDirectory . '/vendor/json-schema/json-schema-test-suite/tests'; $remotesDir = $projectDirectory . '/vendor/json-schema/json-schema-test-suite/remotes'; // Resolve test file path: as-is first, then relative to suite root $filePath = $arArgs[0]; if (!file_exists($filePath)) { $filePath = $suiteRoot . '/' . $arArgs[0]; if (!file_exists($filePath)) { echo "Error: test file not found: {$arArgs[0]}\n"; exit(2); } } $filePath = (string) realpath($filePath); // Detect draft name from directory component (e.g. draft4, draft7, draft2019-09) $draft = null; if (preg_match('/(draft[^\/]+)/i', str_replace('\\', '/', $filePath), $m)) { $draft = strtolower($m[1]); } // Parse "<group>/<test>" — split on first "/" only so slashes in test descriptions are preserved $descParts = explode('/', $arArgs[1], 2); $groupDesc = $descParts[0]; $testDesc = $descParts[1] ?? null; // Load test file $contents = json_decode((string) file_get_contents($filePath), false); if (!is_array($contents)) { echo "Error: invalid test file format\n"; exit(2); } // Search: exact match first, then case-insensitive substring // $matchedGroup tracks a group that matched even when no test inside it matched $foundGroup = null; $foundTest = null; $matchedGroup = null; foreach ([true, false] as $exact) { foreach ($contents as $group) { $groupMatch = $exact ? $group->description === $groupDesc : stripos($group->description, $groupDesc) !== false; if (!$groupMatch) { continue; } if ($testDesc === null) { $foundGroup = $group; break 2; } $matchedGroup = $group; foreach ($group->tests as $test) { $testMatch = $exact ? $test->description === $testDesc : stripos($test->description, $testDesc) !== false; if ($testMatch) { $foundGroup = $group; $foundTest = $test; break 3; } } } } if ($matchedGroup === null && $foundGroup === null) { echo "Error: no matching group found for: {$groupDesc}\n"; echo "Available groups:\n"; foreach ($contents as $group) { echo " - {$group->description}\n"; } exit(2); } if ($testDesc !== null && $foundTest === null) { echo "Error: no matching test found for: {$testDesc}\n"; $listGroup = $matchedGroup ?? $foundGroup; echo "Available tests in \"{$listGroup->description}\":\n"; foreach ($listGroup->tests as $test) { $expect = $test->valid ? 'valid' : 'invalid'; echo " [{$expect}] {$test->description}\n"; } exit(2); } // Determine check mode (mirrors JsonSchemaTestSuiteTest::getCheckModeForDraft) $checkMode = in_array($draft, ['draft6', 'draft7'], true) ? Constraint::CHECK_MODE_NORMAL | Constraint::CHECK_MODE_STRICT : Constraint::CHECK_MODE_NORMAL; try { $draftIdentifier = DraftIdentifiers::fromConstraintName($draft ?? 'draft4'); } catch (\InvalidArgumentException $e) { $draftIdentifier = DraftIdentifiers::DRAFT_4(); } // Set up validator (mirrors JsonSchemaTestSuiteTest::testTestCaseValidatesCorrectly) $schema = $foundGroup->schema; $schemaId = is_object($schema) && property_exists($schema, 'id') ? $schema->id : SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI; $schemaStorage = new SchemaStorage(); $schemaStorage->addSchema($schemaId, $schema); if (is_dir($remotesDir)) { $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($remotesDir)); foreach ($iterator as $info) { if (!$info->isFile()) { continue; } $remoteId = str_replace($remotesDir, 'http://localhost:1234', $info->getPathname()); $schemaStorage->addSchema($remoteId, json_decode((string) file_get_contents($info->getPathname()), false)); } } $factory = new Factory($schemaStorage); $factory->setDefaultDialect($draftIdentifier->getValue()); /** @param object $test */ $runTest = static function (object $test) use ($factory, $schema, $checkMode): array { $validator = new Validator($factory); try { $validator->validate($test->data, $schema, $checkMode); } catch (\Exception $e) { return ['error' => $e->getMessage()]; } $isValid = count($validator->getErrors()) === 0; return [ 'passed' => $isValid === $test->valid, 'isValid' => $isValid, 'errors' => $validator->getErrors(), ]; }; $verbose = isset($arOptions['--verbose']) || isset($arOptions['-v']); // Run all tests in the group when no specific test was requested if ($foundTest === null) { echo "Group: {$foundGroup->description}\n"; if ($verbose) { echo "Schema: " . json_encode($schema, JSON_PRETTY_PRINT) . "\n"; } $passCount = 0; $failCount = 0; foreach ($foundGroup->tests as $test) { $result = $runTest($test); if (isset($result['error'])) { echo sprintf("[ERROR] %s\n", $test->description); echo " Error during validation: {$result['error']}\n"; $failCount++; continue; } $expect = $test->valid ? 'valid' : 'invalid'; $label = $result['passed'] ? 'PASS' : 'FAIL'; echo sprintf("[%s] [%s] %s\n", $label, $expect, $test->description); if (!$result['passed']) { if ($result['isValid']) { echo " Validator returned valid but the test case expects invalid.\n"; } else { foreach ($result['errors'] as $error) { echo sprintf(" [%s] %s\n", $error['property'], $error['message']); } } $failCount++; } else { $passCount++; } } echo "\n{$passCount} passed, {$failCount} failed.\n"; exit($failCount > 0 ? 1 : 0); } $result = $runTest($foundTest); if (isset($result['error'])) { echo "Error during validation: {$result['error']}\n"; exit(2); } echo "Group : {$foundGroup->description}\n"; echo "Test : {$foundTest->description}\n"; if ($verbose) { echo "Schema: " . json_encode($schema, JSON_PRETTY_PRINT) . "\n"; } echo "Data : " . json_encode($foundTest->data) . "\n"; echo "Expect: " . ($foundTest->valid ? 'valid' : 'invalid') . "\n"; echo "Result: " . ($result['passed'] ? 'PASS' : 'FAIL') . "\n"; if (!$result['passed']) { if ($result['isValid']) { echo "\nValidator returned valid but the test case expects invalid.\n"; } else { echo "\nValidation errors:\n"; foreach ($result['errors'] as $error) { echo sprintf(" [%s] %s\n", $error['property'], $error['message']); } } } exit($result['passed'] ? 0 : 1);
Save File
Cancel