You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
506 lines
14 KiB
506 lines
14 KiB
<?php |
|
/** |
|
* PHP unit tests for JSON-LD. |
|
* |
|
* @author Dave Longley |
|
* |
|
* Copyright (c) 2011-2013 Digital Bazaar, Inc. All rights reserved. |
|
*/ |
|
require_once('jsonld.php'); |
|
|
|
// send errors to stdout |
|
ini_set('display_errors', 'stdout'); |
|
|
|
// determine EOL for output based on command line php or webpage php |
|
$isCli = defined('STDIN'); |
|
$eol = $isCli ? "\n" : '<br/>'; |
|
|
|
// EARL report |
|
$earl = (object)array( |
|
'@context' => (object)array( |
|
'doap' => 'http://usefulinc.com/ns/doap#', |
|
'foaf' => 'http://xmlns.com/foaf/0.1/', |
|
'dc' => 'http://purl.org/dc/terms/', |
|
'earl' => 'http://www.w3.org/ns/earl#', |
|
'xsd' => 'http://www.w3.org/2001/XMLSchema#', |
|
'doap:homepage' => (object)array('@type' => '@id'), |
|
'doap:license' => (object)array('@type' => '@id'), |
|
'dc:creator' => (object)array('@type' => '@id'), |
|
'foaf:homepage' => (object)array('@type' => '@id'), |
|
'subjectOf' => (object)array('@reverse' => 'earl:subject'), |
|
'earl:assertedBy' => (object)array('@type' => '@id'), |
|
'earl:mode' => (object)array('@type' => '@id'), |
|
'earl:test' => (object)array('@type' => '@id'), |
|
'earl:outcome' => (object)array('@type' => '@id'), |
|
'dc:date' => (object)array('@type' => 'xsd:date') |
|
), |
|
'@id' => 'https://github.com/digitalbazaar/php-json-ld', |
|
'@type' => array('doap:Project', 'earl:TestSubject', 'earl:Software'), |
|
'doap:name' => 'php-json-ld', |
|
'dc:title' => 'php-json-ld', |
|
'doap:homepage' => 'https://github.com/digitalbazaar/php-json-ld', |
|
'doap:license' => 'https://github.com/digitalbazaar/php-json-ld/blob/master/LICENSE', |
|
'doap:description' => 'A JSON-LD processor for PHP', |
|
'doap:programming-language' => 'PHP', |
|
'dc:creator' => 'https://github.com/dlongley', |
|
'doap:developer' => (object)array( |
|
'@id' => 'https://github.com/dlongley', |
|
'@type' => array('foaf:Person', 'earl:Assertor'), |
|
'foaf:name' => 'Dave Longley', |
|
'foaf:homepage' => 'https://github.com/dlongley' |
|
), |
|
'dc:date' => array( |
|
'@value' => gmdate('Y-m-d'), |
|
'@type' => 'xsd:date' |
|
), |
|
'subjectOf' => array() |
|
); |
|
|
|
function error_handler($errno, $errstr, $errfile, $errline) { |
|
global $eol; |
|
echo "$eol$errstr$eol"; |
|
array_walk( |
|
debug_backtrace(), |
|
create_function( |
|
'$a,$b', |
|
'echo "{$a[\'function\']}()' . |
|
'(".basename($a[\'file\']).":{$a[\'line\']}); ' . $eol . '";')); |
|
throw new Exception(); |
|
return false; |
|
} |
|
if(!$isCli) { |
|
set_error_handler('error_handler'); |
|
} |
|
|
|
function deep_compare($expect, $result) { |
|
if(is_array($expect)) { |
|
if(!is_array($result)) { |
|
return false; |
|
} |
|
if(count($expect) !== count($result)) { |
|
return false; |
|
} |
|
foreach($expect as $i => $v) { |
|
if(!deep_compare($v, $result[$i])) { |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
if(is_object($expect)) { |
|
if(!is_object($result)) { |
|
return false; |
|
} |
|
if(count(get_object_vars($expect)) !== count(get_object_vars($result))) { |
|
return false; |
|
} |
|
foreach($expect as $k => $v) { |
|
if(!property_exists($result, $k) || !deep_compare($v, $result->{$k})) { |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
return $expect === $result; |
|
} |
|
|
|
function expanded_compare($x, $y, $is_list=false) { |
|
if($x === $y) { |
|
return true; |
|
} |
|
if(gettype($x) !== gettype($y)) { |
|
return false; |
|
} |
|
if(is_array($x)) { |
|
if(!is_array($y) || count($x) !== count($y)) { |
|
return false; |
|
} |
|
$rval = true; |
|
if($is_list) { |
|
// compare in order |
|
for($i = 0; $rval && $i < count($x); ++$i) { |
|
$rval = expanded_compare($x[$i], $y[$i], false); |
|
} |
|
} |
|
else { |
|
// compare in any order |
|
$iso = array(); |
|
for($i = 0; $rval && $i < count($x); ++$i) { |
|
$rval = false; |
|
for($j = 0; !$rval && $j < count($y); ++$j) { |
|
if(!isset($iso[$j])) { |
|
if(expanded_compare($x[$i], $y[$j], false)) { |
|
$iso[$j] = $i; |
|
$rval = true; |
|
} |
|
} |
|
} |
|
} |
|
$rval = $rval && (count($iso) === count($x)); |
|
} |
|
return $rval; |
|
} |
|
if(is_object($x)) { |
|
$x_keys = array_keys((array)$x); |
|
$y_keys = array_keys((array)$y); |
|
if(count($x_keys) !== count($y_keys)) { |
|
return false; |
|
} |
|
foreach($x_keys as $key) { |
|
if(!property_exists($y, $key)) { |
|
return false; |
|
} |
|
if(!expanded_compare($x->{$key}, $y->{$key}, $key === '@list')) { |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
/** |
|
* Reads test JSON files. |
|
* |
|
* @param string $file the file to read. |
|
* @param string $filepath the test filepath. |
|
* |
|
* @return string the read JSON. |
|
*/ |
|
function read_test_json($file, $filepath) { |
|
global $eol; |
|
|
|
try { |
|
$file = $filepath . '/' . $file; |
|
return json_decode(file_get_contents($file)); |
|
} |
|
catch(Exception $e) { |
|
echo "Exception while parsing file: '$file'$eol"; |
|
throw $e; |
|
} |
|
} |
|
|
|
/** |
|
* Reads test N-Quads files. |
|
* |
|
* @param string $file the file to read. |
|
* @param string $filepath the test filepath. |
|
* |
|
* @return string the read N-Quads. |
|
*/ |
|
function read_test_nquads($file, $filepath) { |
|
global $eol; |
|
|
|
try { |
|
$file = $filepath . '/' . $file; |
|
return file_get_contents($file); |
|
} |
|
catch(Exception $e) { |
|
echo "Exception while parsing file: '$file'$eol"; |
|
throw $e; |
|
} |
|
} |
|
|
|
/** |
|
* JSON-encodes the given input (does not escape slashes). |
|
* |
|
* @param mixed $input the input to encode. |
|
* |
|
* @return the encoded input. |
|
*/ |
|
function jsonld_encode($input) { |
|
// newer PHP has a flag to avoid escaped '/' |
|
if(defined('JSON_UNESCAPED_SLASHES')) { |
|
$options = JSON_UNESCAPED_SLASHES; |
|
if(defined('JSON_PRETTY_PRINT')) { |
|
$options |= JSON_PRETTY_PRINT; |
|
} |
|
$json = json_encode($input, $options); |
|
} |
|
else { |
|
// use a simple string replacement of '\/' to '/'. |
|
$json = str_replace('\\/', '/', json_encode($input)); |
|
} |
|
|
|
return $json; |
|
} |
|
|
|
class TestRunner { |
|
public function __construct() { |
|
// set up groups, add root group |
|
$this->groups = array(); |
|
$this->group(''); |
|
|
|
$this->passed = 0; |
|
$this->failed = 0; |
|
$this->total = 0; |
|
} |
|
|
|
public function group($name) { |
|
$this->groups[] = (object)array( |
|
'name' => $name, |
|
'tests' => array(), |
|
'count' => 1); |
|
} |
|
|
|
public function ungroup() { |
|
array_pop($this->groups); |
|
} |
|
|
|
public function test($name) { |
|
$this->groups[count($this->groups) - 1]->tests[] = $name; |
|
$this->total += 1; |
|
|
|
$line = ''; |
|
foreach($this->groups as $g) { |
|
$line .= ($line === '') ? $g->name : ('/' . $g->name); |
|
} |
|
|
|
$g = $this->groups[count($this->groups) - 1]; |
|
if($g->name !== '') { |
|
$count = '' . $g->count; |
|
$end = 4 - strlen($count); |
|
for($i = 0; $i < $end; ++$i) { |
|
$count = '0' . $count; |
|
} |
|
$line .= ' ' . $count; |
|
$g->count += 1; |
|
} |
|
$line .= '/' . array_pop($g->tests) . '... '; |
|
echo $line; |
|
} |
|
|
|
public function check($test, $expect, $result, $type) { |
|
global $eol; |
|
|
|
$compare_json = !in_array('jld:ToRDFTest', $type); |
|
$relabel = in_array('jld:FlattenTest', $type); |
|
$expanded = $relabel || in_array('jld:ExpandTest', $type); |
|
if($relabel) { |
|
$expect = jsonld_relabel_blank_nodes($expect); |
|
$result = jsonld_relabel_blank_nodes($result); |
|
} |
|
|
|
$pass = false; |
|
if($compare_json) { |
|
$pass = (json_encode($expect) === json_encode($result)); |
|
} |
|
if(!$pass) { |
|
$pass = deep_compare($expect, $result) || |
|
($expanded && expanded_compare($expect, $result, $relabel)); |
|
} |
|
if(!$pass && $relabel) { |
|
echo "WARN tried normalization..."; |
|
$expect_normalized = jsonld_normalize( |
|
$expect, array('format' => 'application/nquads')); |
|
$result_normalized = jsonld_normalize( |
|
$result, array('format' => 'application/nquads')); |
|
$pass = ($expect_normalized === $result_normalized); |
|
} |
|
|
|
if($pass) { |
|
$this->passed += 1; |
|
echo "PASS$eol"; |
|
} |
|
else { |
|
$this->failed += 1; |
|
echo "FAIL$eol"; |
|
echo 'Expect: ' . jsonld_encode($expect) . $eol; |
|
echo 'Result: ' . jsonld_encode($result) . $eol; |
|
} |
|
|
|
return $pass; |
|
} |
|
|
|
public function load($filepath) { |
|
global $eol; |
|
$manifests = array(); |
|
|
|
// get full path |
|
$filepath = realpath($filepath); |
|
echo "Reading manifest files from: '$filepath'$eol"; |
|
|
|
// read each test file from the directory |
|
$files = array(); |
|
$handle = opendir($filepath); |
|
if($handle) { |
|
while(($file = readdir($handle)) !== false) { |
|
if($file !== '..' and $file !== '.') { |
|
$files[] = $filepath . '/' . $file; |
|
} |
|
} |
|
closedir($handle); |
|
} |
|
else { |
|
throw new Exception('Could not open directory.'); |
|
} |
|
|
|
foreach($files as $file) { |
|
$info = pathinfo($file); |
|
// FIXME: hackish, manifests are now JSON-LD |
|
if(strstr($info['basename'], 'manifest') !== false && |
|
$info['extension'] == 'jsonld') { |
|
echo "Reading manifest file: '$file'$eol"; |
|
|
|
try { |
|
$manifest = json_decode(file_get_contents($file)); |
|
} |
|
catch(Exception $e) { |
|
echo "Exception while parsing file: '$file'$eol"; |
|
throw $e; |
|
} |
|
|
|
$manifest->filename = $file; |
|
$manifest->filepath = $filepath; |
|
$manifests[] = $manifest; |
|
} |
|
} |
|
|
|
echo count($manifests) . " manifest file(s) read.$eol"; |
|
return $manifests; |
|
} |
|
|
|
public function run($manifests) { |
|
/* Manifest format: { |
|
name: <optional manifest name>, |
|
sequence: [{ |
|
'name': <test name>, |
|
'@type': ["test:TestCase", "jld:<type of test>"], |
|
'input': <input file for test>, |
|
'context': <context file for add context test type>, |
|
'frame': <frame file for frame test type>, |
|
'expect': <expected result file>, |
|
}] |
|
} |
|
*/ |
|
global $eol; |
|
global $earl; |
|
foreach($manifests as $manifest) { |
|
if(property_exists($manifest, 'name')) { |
|
$this->group($manifest->name); |
|
} |
|
$filepath = $manifest->filepath; |
|
$idBase = 'http://json-ld.org/test-suite/tests/' . |
|
basename($manifest->filename); |
|
foreach($manifest->sequence as $test) { |
|
// read test input files |
|
$type = $test->{'@type'}; |
|
$options = array( |
|
'base' => 'http://json-ld.org/test-suite/tests/' . $test->input, |
|
'useNativeTypes' => true); |
|
|
|
$pass = false; |
|
try { |
|
if(in_array('jld:ApiErrorTest', $type)) { |
|
echo "Skipping test \"{$test->name}\" of type: " . |
|
json_encode($type) . $eol; |
|
continue; |
|
} |
|
else if(in_array('jld:NormalizeTest', $type)) { |
|
$this->test($test->name); |
|
$input = read_test_json($test->input, $filepath); |
|
$test->expect = read_test_nquads($test->expect, $filepath); |
|
$options['format'] = 'application/nquads'; |
|
$result = jsonld_normalize($input, $options); |
|
} |
|
else if(in_array('jld:ExpandTest', $type)) { |
|
$this->test($test->name); |
|
$input = read_test_json($test->input, $filepath); |
|
$test->expect = read_test_json($test->expect, $filepath); |
|
$result = jsonld_expand($input, $options); |
|
} |
|
else if(in_array('jld:CompactTest', $type)) { |
|
$this->test($test->name); |
|
$input = read_test_json($test->input, $filepath); |
|
$test->context = read_test_json($test->context, $filepath); |
|
$test->expect = read_test_json($test->expect, $filepath); |
|
$result = jsonld_compact($input, $test->context, $options); |
|
} |
|
else if(in_array('jld:FlattenTest', $type)) { |
|
$this->test($test->name); |
|
$input = read_test_json($test->input, $filepath); |
|
$test->expect = read_test_json($test->expect, $filepath); |
|
$result = jsonld_flatten($input, null, $options); |
|
} |
|
else if(in_array('jld:FrameTest', $type)) { |
|
$this->test($test->name); |
|
$input = read_test_json($test->input, $filepath); |
|
$test->frame = read_test_json($test->frame, $filepath); |
|
$test->expect = read_test_json($test->expect, $filepath); |
|
$result = jsonld_frame($input, $test->frame, $options); |
|
} |
|
else if(in_array('jld:FromRDFTest', $type)) { |
|
$this->test($test->name); |
|
$input = read_test_nquads($test->input, $filepath); |
|
$test->expect = read_test_json($test->expect, $filepath); |
|
$result = jsonld_from_rdf($input, $options); |
|
} |
|
else if(in_array('jld:ToRDFTest', $type)) { |
|
$this->test($test->name); |
|
$input = read_test_json($test->input, $filepath); |
|
$test->expect = read_test_nquads($test->expect, $filepath); |
|
$options['format'] = 'application/nquads'; |
|
$result = jsonld_to_rdf($input, $options); |
|
} |
|
else { |
|
echo "Skipping test \"{$test->name}\" of type: " . |
|
json_encode($type) . $eol; |
|
continue; |
|
} |
|
|
|
// check results |
|
$pass = $this->check($test, $test->expect, $result, $type); |
|
} |
|
catch(JsonLdException $e) { |
|
echo $eol . $e; |
|
$this->failed += 1; |
|
echo "FAIL$eol"; |
|
} |
|
|
|
$earl->subjectOf[] = (object)array( |
|
'@type' => 'earl:Assertion', |
|
'earl:assertedBy' => $earl->{'doap:developer'}->{'@id'}, |
|
'earl:mode' => 'earl:automatic', |
|
'earl:test' => $idBase . (property_exists($test, '@id') ? |
|
$test->{'@id'} : ''), |
|
'earl:result' => (object)array( |
|
'@type' => 'earl:TestResult', |
|
'dc:date' => gmdate(DateTime::ISO8601), |
|
'earl:outcome' => 'earl:' . ($pass ? 'passed' : 'failed'), |
|
) |
|
); |
|
} |
|
if(property_exists($manifest, 'name')) { |
|
$this->ungroup(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// get command line options |
|
$options = getopt('d:e:'); |
|
if($options === false || !array_key_exists('d', $options)) { |
|
$dvar = 'path to json-ld.org/test-suite/tests'; |
|
$evar = 'file to write EARL report to'; |
|
echo "Usage: php jsonld-tests.php -d <$dvar> [-e <$evar>]$eol"; |
|
exit(0); |
|
} |
|
|
|
// load and run tests |
|
$tr = new TestRunner(); |
|
$tr->group('JSON-LD'); |
|
$tr->run($tr->load($options['d'])); |
|
$tr->ungroup(); |
|
echo "Done. Total:{$tr->total} Passed:{$tr->passed} Failed:{$tr->failed}$eol"; |
|
|
|
// write out EARL report |
|
if(array_key_exists('e', $options)) { |
|
$fd = fopen($options['e'], 'w'); |
|
fwrite($fd, jsonld_encode($earl)); |
|
fclose($fd); |
|
} |
|
|
|
/* end of file, omit ?> */
|
|
|