attach($result); $this->result = $result; parent::run($result); } /** * Tests expansion. * * @param JsonLdTest $test the test to run. * * @group expand * @group json-ld.org * @dataProvider expandProvider */ public function testExpand($test) { $this->test = $test; $input = $test->readUrl('input'); $options = $test->createOptions(); $test->run('jsonld_expand', [$input, $options]); } /** * Tests compaction. * * @param JsonLdTest $test the test to run. * * @group compact * @group json-ld.org * @dataProvider compactProvider */ public function testCompact($test) { $this->test = $test; $input = $test->readUrl('input'); $context = $test->readProperty('context'); $options = $test->createOptions(); $test->run('jsonld_compact', [$input, $context, $options]); } /** * Tests flatten. * * @param JsonLdTest $test the test to run. * * @group flatten * @group json-ld.org * @dataProvider flattenProvider */ public function testFlatten($test) { $this->test = $test; $input = $test->readUrl('input'); $context = $test->readProperty('context'); $options = $test->createOptions(); $test->run('jsonld_flatten', [$input, $context, $options]); } /** * Tests serialization to RDF. * * @param JsonLdTest $test the test to run. * * @group toRdf * @group json-ld.org * @dataProvider toRdfProvider */ public function testToRdf($test) { $this->test = $test; $input = $test->readUrl('input'); $options = $test->createOptions(['format' => 'application/nquads']); $test->run('jsonld_to_rdf', [$input, $options]); } /** * Tests deserialization from RDF. * * @param JsonLdTest $test the test to run. * * @group fromRdf * @group json-ld.org * @dataProvider fromRdfProvider */ public function testFromRdf($test) { $this->test = $test; $input = $test->readProperty('input'); $options = $test->createOptions(['format' => 'application/nquads']); $test->run('jsonld_from_rdf', [$input, $options]); } /** * Tests framing. * * @param JsonLdTest $test the test to run. * * @group frame * @group json-ld.org * @dataProvider frameProvider */ public function testFrame($test) { $this->test = $test; $input = $test->readUrl('input'); $frame = $test->readProperty('frame'); $options = $test->createOptions(); $test->run('jsonld_frame', [$input, $frame, $options]); } /** * Tests normalization. * * @param JsonLdTest $test the test to run. * * @group normalize * @group json-ld.org * @dataProvider normalizeProvider */ public function testNormalize($test) { $this->test = $test; $input = $test->readUrl('input'); $options = $test->createOptions(['format' => 'application/nquads']); $test->run('jsonld_normalize', [$input, $options]); } /** * Tests URGNA2012 normalization. * * @param JsonLdTest $test the test to run. * * @group normalize * @group normalization * @dataProvider urgna2012Provider */ public function testUrgna2012($test) { $this->test = $test; $input = $test->readProperty('action'); $options = $test->createOptions([ 'algorithm' => 'URGNA2012', 'inputFormat' => 'application/nquads', 'format' => 'application/nquads']); $test->run('jsonld_normalize', [$input, $options]); } /** * Tests URDNA2015 normalization. * * @param JsonLdTest $test the test to run. * * @group normalize * @group normalization * @dataProvider urdna2015Provider */ public function testUrdna2015($test) { $this->test = $test; $input = $test->readProperty('action'); $options = $test->createOptions([ 'algorithm' => 'URDNA2015', 'inputFormat' => 'application/nquads', 'format' => 'application/nquads']); $test->run('jsonld_normalize', [$input, $options]); } public function expandProvider() { return new JsonLdTestIterator('jld:ExpandTest'); } public function compactProvider() { return new JsonLdTestIterator('jld:CompactTest'); } public function flattenProvider() { return new JsonLdTestIterator('jld:FlattenTest'); } public function toRdfProvider() { return new JsonLdTestIterator('jld:ToRDFTest'); } public function fromRdfProvider() { return new JsonLdTestIterator('jld:FromRDFTest'); } public function normalizeProvider() { return new JsonLdTestIterator('jld:NormalizeTest'); } public function frameProvider() { return new JsonLdTestIterator('jld:FrameTest'); } public function urgna2012Provider() { return new JsonLdTestIterator('rdfn:Urgna2012EvalTest'); } public function urdna2015Provider() { return new JsonLdTestIterator('rdfn:Urdna2015EvalTest'); } } class JsonLdManifest { public function __construct($data, $filename) { $this->data = $data; $this->filename = $filename; $this->dirname = dirname($filename); } public function load(&$tests) { $entries = array_merge( JsonLdProcessor::getValues($this->data, 'sequence'), JsonLdProcessor::getValues($this->data, 'entries')); $includes = JsonLdProcessor::getValues($this->data, 'include'); foreach ($includes as $include) { array_push($entries, $include . '.jsonld'); } foreach ($entries as $entry) { if (is_string($entry)) { $filename = join( DIRECTORY_SEPARATOR, [$this->dirname, $entry]); $entry = Util::readJson($filename); } else { $filename = $this->filename; } if (JsonLdProcessor::hasValue($entry, '@type', 'mf:Manifest') || JsonLdProcessor::hasValue($entry, 'type', 'mf:Manifest')) { // entry is another manifest $manifest = new JsonLdManifest($entry, $filename); $manifest->load($tests); } else { // assume entry is a test $test = new JsonLdTest($this, $entry, $filename); $types = array_merge( JsonLdProcessor::getValues($test->data, '@type'), JsonLdProcessor::getValues($test->data, 'type')); foreach ($types as $type) { if (!isset($tests[$type])) { $tests[$type] = []; } $tests[$type][] = $test; } } } } } class JsonLdTest { public function __construct($manifest, $data, $filename) { $this->manifest = $manifest; $this->data = $data; $this->filename = $filename; $this->dirname = dirname($filename); $this->isPositive = JsonLdProcessor::hasValue( $data, '@type', 'jld:PositiveEvaluationTest') || JsonLdProcessor::hasValue( $data, 'type', 'jld:PositiveEvaluationTest'); $this->isNegative = JsonLdProcessor::hasValue( $data, '@type', 'jld:NegativeEvaluationTest') || JsonLdProcessor::hasValue( $data, 'type', 'jld:NegativeEvaluationTest'); // generate test name if (isset($manifest->data->name)) { $manifestLabel = $manifest->data->name; } else if (isset($manifest->data->label)) { $manifestLabel = $manifest->data->label; } else { $manifestLabel = 'UNNAMED'; } if (isset($this->data->id)) { $testId = $this->data->id; } else { $testId = $this->data->{'@id'}; } if (isset($this->data->name)) { $testLabel = $this->data->name; } else if (isset($this->data->label)) { $testLabel = $this->data->label; } else { $testLabel = 'UNNAMED'; } $this->name = $manifestLabel . ' ' . $testId . ' - ' . $testLabel; // expand @id and input base if (isset($manifest->data->baseIri)) { $data->{'@id'} = ($manifest->data->baseIri . basename($manifest->filename) . $data->{'@id'}); $this->base = $manifest->data->baseIri . $data->input; } } private function _getResultProperty() { if (isset($this->data->expect)) { return 'expect'; } else if (isset($this->data->result)) { return 'result'; } else { throw new Exception('No test result property found.'); } } public function run($fn, $params) { // read expected data if ($this->isNegative) { $this->expected = $this->data->expect; } else { $this->expected = $this->readProperty($this->_getResultProperty()); } try { $this->actual = call_user_func_array($fn, $params); if ($this->isNegative) { throw new Exception('Expected an error; one was not raised.'); } PHPUnit_Framework_TestCase::assertEquals($this->expected, $this->actual); } catch (Exception $e) { // assume positive test if ($this->isNegative) { $this->actual = $this->getJsonLdErrorCode($e); PHPUnit_Framework_TestCase::assertEquals( $this->expected, $this->actual); } else { throw $e; } } } public function readUrl($property) { if (!property_exists($this->data, $property)) { return null; } return $this->manifest->data->baseIri . $this->data->{$property}; } public function readProperty($property) { $data = $this->data; if (!property_exists($data, $property)) { return null; } $filename = join( DIRECTORY_SEPARATOR, [$this->dirname, $data->{$property}]); $extension = pathinfo($filename, PATHINFO_EXTENSION); if ($extension === 'jsonld') { return Util::readJson($filename); } return Util::readFile($filename); } public function createOptions($opts = []) { $http_options = [ 'contentType', 'httpLink', 'httpStatus', 'redirectTo']; $test_options = (property_exists($this->data, 'option') ? $this->data->option : []); $options = []; foreach ($test_options as $k => $v) { if (!in_array($k, $http_options)) { $options[$k] = $v; } } $options['documentLoader'] = $this->createDocumentLoader(); $options = array_merge($options, $opts); if (isset($options['expandContext'])) { $filename = join( DIRECTORY_SEPARATOR, [$this->dirname, $options['expandContext']]); $options['expandContext'] = Util::readJson($filename); } return $options; } public function createDocumentLoader() { $base = 'http://json-ld.org/test-suite'; $test = $this; $load_locally = function($url) use ($test, $base) { $doc = (object) [ 'contextUrl' => null, 'documentUrl' => $url, 'document' => null]; $options = (property_exists($test->data, 'option') ? $test->data->option : null); if ($options and $url === $test->base) { if (property_exists($options, 'redirectTo') && property_exists($options, 'httpStatus') && $options->httpStatus >= '300' ) { $doc->documentUrl = ($test->manifest->data->baseIri . $options->redirectTo); } else if (property_exists($options, 'httpLink')) { $content_type = (property_exists($options, 'contentType') ? $options->contentType : null); $extension = pathinfo($url, PATHINFO_EXTENSION); if (!$content_type && $extension === 'jsonld') { $content_type = 'application/ld+json'; } $link_header = $options->httpLink; if (is_array($link_header)) { $link_header = join(',', $link_header); } $link_header = jsonld_parse_link_header($link_header); if (isset($link_header['http://www.w3.org/ns/json-ld#context'])) { $link_header = $link_header['http://www.w3.org/ns/json-ld#context']; } else { $link_header = null; } if ($link_header && $content_type !== 'application/ld+json') { if (is_array($link_header)) { throw new Exception('multiple context link headers'); } $doc->contextUrl = $link_header->target; } } } global $ROOT_MANIFEST_DIR; if (strpos($doc->documentUrl, ':') === false) { $filename = join( DIRECTORY_SEPARATOR, [ $ROOT_MANIFEST_DIR, $doc->documentUrl]); $doc->documentUrl = 'file://' . $filename; } else { $filename = join( DIRECTORY_SEPARATOR, [ $ROOT_MANIFEST_DIR, substr($doc->documentUrl, strlen($base))]); } try { $doc->document = Util::readJson($filename); } catch (Exception $e) { throw new Exception('loading document failed'); } return $doc; }; $local_loader = function($url) use ($test, $base, $load_locally) { // always load remote-doc and non-base tests remotely if ((strpos($url, $base) !== 0 && strpos($url, ':') !== false) || $test->manifest->data->name === 'Remote document') { return call_user_func('jsonld_default_document_loader', $url); } // attempt to load locally return call_user_func($load_locally, $url); }; return $local_loader; } public function getJsonLdErrorCode($err) { if ($err instanceof JsonLdException) { if ($err->getCode()) { return $err->getCode(); } if ($err->cause) { return $this->getJsonLdErrorCode($err->cause); } } return $err->getMessage(); } } class JsonLdTestIterator implements Iterator { /** * The current test index. */ protected $index = 0; /** * The total number of tests. */ protected $count = 0; /** * Creates a TestIterator. * * @param string $type the type of tests to iterate over. */ public function __construct($type) { global $TESTS; if (isset($TESTS[$type])) { $this->tests = $TESTS[$type]; } else { $this->tests = []; } $this->count = count($this->tests); } /** * Gets the parameters for the next test. * * @return assoc the parameters for the next test. */ public function current() { return ['test' => $this->tests[$this->index]]; } /** * Gets the current test number. * * @return int the current test number. */ public function key() { return $this->index; } /** * Proceeds to the next test. */ public function next() { $this->index += 1; } /** * Rewinds to the first test. */ public function rewind() { $this->index = 0; } /** * Returns true if there are more tests to be run. * * @return bool true if there are more tests to be run. */ public function valid() { return $this->index < $this->count; } } class EarlReport extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener { public function __construct() { $this->filename = null; $this->attached = false; $this->report = (object) [ '@context' => (object) [ '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) ['@type' => '@id'], 'doap:license' => (object) ['@type' => '@id'], 'dc:creator' => (object) ['@type' => '@id'], 'foaf:homepage' => (object) ['@type' => '@id'], 'subjectOf' => (object) ['@reverse' => 'earl:subject'], 'earl:assertedBy' => (object) ['@type' => '@id'], 'earl:mode' => (object) ['@type' => '@id'], 'earl:test' => (object) ['@type' => '@id'], 'earl:outcome' => (object) ['@type' => '@id'], 'dc:date' => (object) ['@type' => 'xsd:date'] ], '@id' => 'https://github.com/digitalbazaar/php-json-ld', '@type' => ['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) [ '@id' => 'https://github.com/dlongley', '@type' => ['foaf:Person', 'earl:Assertor'], 'foaf:name' => 'Dave Longley', 'foaf:homepage' => 'https://github.com/dlongley' ], 'dc:date' => [ '@value' => gmdate('Y-m-d'), '@type' => 'xsd:date' ], 'subjectOf' => [] ]; } /** * Attaches to the given test result, if not yet attached. * * @param PHPUnit_Framework_Test $result the result to attach to. */ public function attach(PHPUnit_Framework_TestResult $result) { if (!$this->attached && $this->filename) { $this->attached = true; $result->addListener($this); } } /** * Adds an assertion to this EARL report. * * @param JsonLdTest $test the JsonLdTest for the assertion is for. * @param bool $passed whether or not the test passed. */ public function addAssertion($test, $passed) { $this->report->{'subjectOf'}[] = (object) [ '@type' => 'earl:Assertion', 'earl:assertedBy' => $this->report->{'doap:developer'}->{'@id'}, 'earl:mode' => 'earl:automatic', 'earl:test' => $test->data->{'@id'}, 'earl:result' => (object) [ '@type' => 'earl:TestResult', 'dc:date' => gmdate(DateTime::ISO8601), 'earl:outcome' => $passed ? 'earl:passed' : 'earl:failed' ] ]; return $this; } /** * Writes this EARL report to a file. */ public function flush() { if ($this->filename) { printf("\nWriting EARL report to: %s\n", $this->filename); $fd = fopen($this->filename, 'w'); fwrite($fd, Util::jsonldEncode($this->report)); fclose($fd); } } public function endTest(PHPUnit_Framework_Test $test, $time) { $this->addAssertion($test->test, true); } public function addError( PHPUnit_Framework_Test $test, Exception $e, $time) { $this->addAssertion($test->test, false); } public function addFailure( PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { $this->addAssertion($test->test, false); if ($test->result->shouldStop()) { if (isset($test->test->name)) { $name = $test->test->name; } else if (isset($test->test->label)) { $name = $test->test->label; } else { $name = 'UNNAMED'; } printf("\n\nFAILED\n"); printf("Test: %s\n", $name); printf("Purpose: %s\n", $test->test->data->purpose); printf("EXPECTED: %s\n", Util::jsonldEncode($test->test->expected)); printf("ACTUAL: %s\n", Util::jsonldEncode($test->test->actual)); } } public function addIncompleteTest( PHPUnit_Framework_Test $test, Exception $e, $time) { $this->addAssertion($test->test, false); } public function addRiskyTest( PHPUnit_Framework_Test $test, Exception $e, $time) { } public function addSkippedTest( PHPUnit_Framework_Test $test, Exception $e, $time) { } public function startTest(PHPUnit_Framework_Test $test) { } public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { } public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { } } class Util { public static function readFile($filename) { $rval = @file_get_contents($filename); if ($rval === false) { throw new Exception('File read error: ' . $filename); } return $rval; } public static function readJson($filename) { $rval = json_decode(self::readFile($filename)); if ($rval === null) { throw new Exception('JSON parse error'); } return $rval; } public static function readNQuads($filename) { return self::readFile($filename); } public static function jsonldEncode($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; } } // tests to skip $SKIP_TESTS = []; // root manifest directory $ROOT_MANIFEST_DIR; // parsed tests; keyed by type $TESTS = []; // parsed command line options $OPTIONS = []; // parse command line options global $argv; $args = $argv; $total = count($args); $start = false; for ($i = 0; $i < $total; ++$i) { $arg = $args[$i]; if (!$start) { if (realpath($arg) === realpath(__FILE__)) { $start = true; } continue; } if ($arg[0] !== '-') { break; } $i += 1; $OPTIONS[$arg] = $args[$i]; } if (!isset($OPTIONS['-d'])) { $dvar = 'path to json-ld.org/test-suite'; $evar = 'file to write EARL report to'; echo "php-json-ld Tests\n"; echo "Usage: phpunit test.php -d <$dvar> [-e <$evar>]\n\n"; exit(0); } // EARL Report $EARL = new EarlReport(); if (isset($OPTIONS['-e'])) { $EARL->filename = $OPTIONS['-e']; } // load root manifest $ROOT_MANIFEST_DIR = realpath($OPTIONS['-d']); $filename = join( DIRECTORY_SEPARATOR, [$ROOT_MANIFEST_DIR, 'manifest.jsonld']); $root_manifest = Util::readJson($filename); $manifest = new JsonLdManifest($root_manifest, $filename); $manifest->load($TESTS); /* end of file, omit ?> */