Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
!/bin/update-changelog.sh
!/bin/prepare-release.sh
!/bin/extract-release-notes.sh
!/bin/run-test-case
coverage
.buildpath
.project
Expand Down
227 changes: 227 additions & 0 deletions bin/run-test-case
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
#!/usr/bin/env 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 a single JSON Schema test suite test case
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>/<test>" — split on the first "/" only.
Exact match is tried first, then case-insensitive substring.

Options:
-v --verbose Also print the schema
-h --help Show this help

Exit codes:
0 Test found and result matches expected
1 Test found but result does not match expected
2 Usage error, file not found, or 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);
}

// No test description given — list tests in the matched group
if ($foundTest === null) {
echo "Group: {$foundGroup->description}\n";
echo "Tests:\n";
foreach ($foundGroup->tests as $test) {
$expect = $test->valid ? 'valid' : 'invalid';
echo " [{$expect}] {$test->description}\n";
}
exit(0);
}

// 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());
$validator = new Validator($factory);

try {
$validator->validate($foundTest->data, $schema, $checkMode);
} catch (\Exception $e) {
echo "Error during validation: " . $e->getMessage() . "\n";
exit(2);
}

$isValid = count($validator->getErrors()) === 0;
$passed = $isValid === $foundTest->valid;

echo "Group : {$foundGroup->description}\n";
echo "Test : {$foundTest->description}\n";
if (isset($arOptions['--verbose']) || isset($arOptions['-v'])) {
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: " . ($passed ? 'PASS' : 'FAIL') . "\n";

if (!$passed) {
if ($isValid) {
echo "\nValidator returned valid but the test case expects invalid.\n";
} else {
echo "\nValidation errors:\n";
foreach ($validator->getErrors() as $error) {
echo sprintf(" [%s] %s\n", $error['property'], $error['message']);
}
}
}

exit($passed ? 0 : 1);
Loading