'; // 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: , sequence: [{ 'name': , '@type': ["test:TestCase", "jld:"], 'input': , 'context': , 'frame': , 'expect': , }] } */ 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 ?> */