diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 08332857..b0e6f903 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -8,9 +8,9 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
- node-version: [18.x]
+ node-version: [20.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
@@ -23,9 +23,9 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
- node-version: [14.x, 16.x, 18.x, 20.x]
+ node-version: [18.x, 20.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
@@ -41,10 +41,10 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
- node-version: [16.x]
+ node-version: [20.x]
bundler: [webpack, browserify]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
@@ -62,9 +62,9 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
- node-version: [16.x]
+ node-version: [20.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
@@ -78,9 +78,9 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
- node-version: [18.x]
+ node-version: [20.x]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7c0926a8..11908faf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,25 @@
# jsonld ChangeLog
+## 9.0.0 - 2023-xx-xx
+
+### Changed
+- **BREAKING**: Drop support for Node.js < 18.
+- **BREAKING**: Upgrade dependencies.
+ - `@digitalbazaar/http-client@4`.
+ - `canonicalize@2`.
+ - `rdf-canonize@4`: See the [rdf-canonize][] 4.0.0 changelog for
+ **important** changes and upgrade notes. Of note:
+ - The `URDNA2015` default algorithm has been changed to `RDFC-1.0` from
+ [rdf-canon][].
+ - Complexity control defaults `maxWorkFactor` or `maxDeepIterations` may
+ need to be adjusted to process graphs with certain blank node constructs.
+ - A `signal` option is available to use an `AbortSignal` to limit resource
+ usage.
+ - The internal digest algorithm can be changed.
+
+### Removed
+- **BREAKING**: Remove `application/nquads` alias for `application/n-quads`.
+
## 8.3.2 - 2023-12-06
### Fixed
diff --git a/lib/fromRdf.js b/lib/fromRdf.js
index 01098353..795ec9af 100644
--- a/lib/fromRdf.js
+++ b/lib/fromRdf.js
@@ -89,7 +89,7 @@ api.fromRDF = async (
const nodeMap = graphMap[name];
// get subject, predicate, object
- const s = quad.subject.value;
+ const s = _nodeId(quad.subject);
const p = quad.predicate.value;
const o = quad.object;
@@ -98,13 +98,14 @@ api.fromRDF = async (
}
const node = nodeMap[s];
- const objectIsNode = o.termType.endsWith('Node');
- if(objectIsNode && !(o.value in nodeMap)) {
- nodeMap[o.value] = {'@id': o.value};
+ const objectNodeId = _nodeId(o);
+ const objectIsNode = !!objectNodeId;
+ if(objectIsNode && !(objectNodeId in nodeMap)) {
+ nodeMap[objectNodeId] = {'@id': objectNodeId};
}
if(p === RDF_TYPE && !useRdfType && objectIsNode) {
- _addValue(node, '@type', o.value, {propertyIsArray: true});
+ _addValue(node, '@type', objectNodeId, {propertyIsArray: true});
continue;
}
@@ -114,9 +115,9 @@ api.fromRDF = async (
// object may be an RDF list/partial list node but we can't know easily
// until all triples are read
if(objectIsNode) {
- if(o.value === RDF_NIL) {
+ if(objectNodeId === RDF_NIL) {
// track rdf:nil uniquely per graph
- const object = nodeMap[o.value];
+ const object = nodeMap[objectNodeId];
if(!('usages' in object)) {
object.usages = [];
}
@@ -125,12 +126,12 @@ api.fromRDF = async (
property: p,
value
});
- } else if(o.value in referencedOnce) {
+ } else if(objectNodeId in referencedOnce) {
// object referenced more than once
- referencedOnce[o.value] = false;
+ referencedOnce[objectNodeId] = false;
} else {
// keep track of single reference
- referencedOnce[o.value] = {
+ referencedOnce[objectNodeId] = {
node,
property: p,
value
@@ -303,8 +304,9 @@ api.fromRDF = async (
*/
function _RDFToObject(o, useNativeTypes, rdfDirection, options) {
// convert NamedNode/BlankNode object to JSON-LD
- if(o.termType.endsWith('Node')) {
- return {'@id': o.value};
+ const nodeId = _nodeId(o);
+ if(nodeId) {
+ return {'@id': nodeId};
}
// convert literal to JSON-LD
@@ -397,3 +399,20 @@ function _RDFToObject(o, useNativeTypes, rdfDirection, options) {
return rval;
}
+
+/**
+ * Return id for a term. Handles BlankNodes and NamedNodes. Adds a '_:' prefix
+ * for BlanksNodes.
+ *
+ * @param term a term object.
+ *
+ * @return the Node term id or null.
+ */
+function _nodeId(term) {
+ if(term.termType === 'NamedNode') {
+ return term.value;
+ } else if(term.termType === 'BlankNode') {
+ return '_:' + term.value;
+ }
+ return null;
+}
diff --git a/lib/jsonld.js b/lib/jsonld.js
index c6931aeb..0a001b72 100644
--- a/lib/jsonld.js
+++ b/lib/jsonld.js
@@ -523,7 +523,7 @@ jsonld.link = async function(input, ctx, options) {
/**
* Performs RDF dataset normalization on the given input. The input is JSON-LD
* unless the 'inputFormat' option is used. The output is an RDF dataset
- * unless the 'format' option is used.
+ * unless a non-null 'format' option is used.
*
* Note: Canonicalization sets `safe` to `true` and `base` to `null` by
* default in order to produce safe outputs and "fail closed" by default. This
@@ -531,25 +531,31 @@ jsonld.link = async function(input, ctx, options) {
* allow unsafe defaults (for cryptographic usage) in order to comply with the
* JSON-LD 1.1 specification.
*
- * @param input the input to normalize as JSON-LD or as a format specified by
- * the 'inputFormat' option.
+ * @param input the input to normalize as JSON-LD given as an RDF dataset or as
+ * a format specified by the 'inputFormat' option.
* @param [options] the options to use:
- * [algorithm] the normalization algorithm to use, `URDNA2015` or
- * `URGNA2012` (default: `URDNA2015`).
* [base] the base IRI to use (default: `null`).
* [expandContext] a context to expand with.
* [skipExpansion] true to assume the input is expanded and skip
* expansion, false not to, defaults to false. Some well-formed
* and safe-mode checks may be omitted.
- * [inputFormat] the format if input is not JSON-LD:
- * 'application/n-quads' for N-Quads.
- * [format] the format if output is a string:
- * 'application/n-quads' for N-Quads.
+ * [inputFormat] the input format. null for a JSON-LD object,
+ * 'application/n-quads' for N-Quads. (default: null)
+ * [format] the output format. null for an RDF dataset,
+ * 'application/n-quads' for an N-Quads string. (default: N-Quads)
* [documentLoader(url, options)] the document loader.
- * [useNative] true to use a native canonize algorithm
* [rdfDirection] null or 'i18n-datatype' to support RDF
* transformation of @direction (default: null).
* [safe] true to use safe mode. (default: true).
+ * [canonizeOptions] options to pass to rdf-canonize canonize(). See
+ * rdf-canonize for more details. Commonly used options, and their
+ * defaults, are:
+ * algorithm="RDFC-1.0",
+ * messageDigestAlgorithm="sha256",
+ * canonicalIdMap,
+ * maxWorkFactor=1,
+ * maxDeepIterations=-1,
+ * and signal=null.
* [contextResolver] internal use only.
*
* @return a Promise that resolves to the normalized output.
@@ -559,18 +565,21 @@ jsonld.normalize = jsonld.canonize = async function(input, options) {
throw new TypeError('Could not canonize, too few arguments.');
}
- // set default options
+ // set toRDF options
options = _setDefaults(options, {
- base: _isString(input) ? input : null,
- algorithm: 'URDNA2015',
skipExpansion: false,
safe: true,
contextResolver: new ContextResolver(
{sharedCache: _resolvedContextCache})
});
+
+ // set canonize options
+ const canonizeOptions = Object.assign({}, {
+ algorithm: 'RDFC-1.0'
+ }, options.canonizeOptions || null);
+
if('inputFormat' in options) {
- if(options.inputFormat !== 'application/n-quads' &&
- options.inputFormat !== 'application/nquads') {
+ if(options.inputFormat !== 'application/n-quads') {
throw new JsonLdError(
'Unknown canonicalization input format.',
'jsonld.CanonizeError');
@@ -579,17 +588,18 @@ jsonld.normalize = jsonld.canonize = async function(input, options) {
const parsedInput = NQuads.parse(input);
// do canonicalization
- return canonize.canonize(parsedInput, options);
+ return canonize.canonize(parsedInput, canonizeOptions);
}
// convert to RDF dataset then do normalization
const opts = {...options};
delete opts.format;
+ delete opts.canonizeOptions;
opts.produceGeneralizedRdf = false;
const dataset = await jsonld.toRDF(input, opts);
// do canonicalization
- return canonize.canonize(dataset, options);
+ return canonize.canonize(dataset, canonizeOptions);
};
/**
@@ -653,8 +663,8 @@ jsonld.fromRDF = async function(dataset, options) {
* [skipExpansion] true to assume the input is expanded and skip
* expansion, false not to, defaults to false. Some well-formed
* and safe-mode checks may be omitted.
- * [format] the format to use to output a string:
- * 'application/n-quads' for N-Quads.
+ * [format] the output format. null for an RDF dataset,
+ * 'application/n-quads' for an N-Quads string. (default: null)
* [produceGeneralizedRdf] true to output generalized RDF, false
* to produce only standard RDF (default: false).
* [documentLoader(url, options)] the document loader.
@@ -672,7 +682,6 @@ jsonld.toRDF = async function(input, options) {
// set default options
options = _setDefaults(options, {
- base: _isString(input) ? input : '',
skipExpansion: false,
contextResolver: new ContextResolver(
{sharedCache: _resolvedContextCache})
@@ -690,8 +699,7 @@ jsonld.toRDF = async function(input, options) {
// output RDF dataset
const dataset = _toRDF(expanded, options);
if(options.format) {
- if(options.format === 'application/n-quads' ||
- options.format === 'application/nquads') {
+ if(options.format === 'application/n-quads') {
return NQuads.serialize(dataset);
}
throw new JsonLdError(
@@ -997,7 +1005,6 @@ jsonld.unregisterRDFParser = function(contentType) {
// register the N-Quads RDF parser
jsonld.registerRDFParser('application/n-quads', NQuads.parse);
-jsonld.registerRDFParser('application/nquads', NQuads.parse);
/* URL API */
jsonld.url = require('./url');
diff --git a/lib/toRdf.js b/lib/toRdf.js
index 53f20af4..e8a54844 100644
--- a/lib/toRdf.js
+++ b/lib/toRdf.js
@@ -63,12 +63,7 @@ api.toRDF = (input, options) => {
if(graphName === '@default') {
graphTerm = {termType: 'DefaultGraph', value: ''};
} else if(_isAbsoluteIri(graphName)) {
- if(graphName.startsWith('_:')) {
- graphTerm = {termType: 'BlankNode'};
- } else {
- graphTerm = {termType: 'NamedNode'};
- }
- graphTerm.value = graphName;
+ graphTerm = _makeTerm(graphName);
} else {
// skip relative IRIs (not valid RDF)
if(options.eventHandler) {
@@ -119,10 +114,7 @@ function _graphToRDF(dataset, graph, graphTerm, issuer, options) {
for(const item of items) {
// RDF subject
- const subject = {
- termType: id.startsWith('_:') ? 'BlankNode' : 'NamedNode',
- value: id
- };
+ const subject = _makeTerm(id);
// skip relative IRI subjects (not valid RDF)
if(!_isAbsoluteIri(id)) {
@@ -144,10 +136,7 @@ function _graphToRDF(dataset, graph, graphTerm, issuer, options) {
}
// RDF predicate
- const predicate = {
- termType: property.startsWith('_:') ? 'BlankNode' : 'NamedNode',
- value: property
- };
+ const predicate = _makeTerm(property);
// skip relative IRI predicates (not valid RDF)
if(!_isAbsoluteIri(property)) {
@@ -226,13 +215,16 @@ function _listToRDF(list, issuer, dataset, graphTerm, rdfDirection, options) {
const last = list.pop();
// Result is the head of the list
- const result = last ? {termType: 'BlankNode', value: issuer.getId()} : nil;
+ const result = last ? {
+ termType: 'BlankNode',
+ value: issuer.getId().slice(2)
+ } : nil;
let subject = result;
for(const item of list) {
const object = _objectToRDF(
item, issuer, dataset, graphTerm, rdfDirection, options);
- const next = {termType: 'BlankNode', value: issuer.getId()};
+ const next = {termType: 'BlankNode', value: issuer.getId().slice(2)};
dataset.push({
subject,
predicate: first,
@@ -284,14 +276,16 @@ function _listToRDF(list, issuer, dataset, graphTerm, rdfDirection, options) {
function _objectToRDF(
item, issuer, dataset, graphTerm, rdfDirection, options
) {
- const object = {};
+ let object;
// convert value object to RDF
if(graphTypes.isValue(item)) {
- object.termType = 'Literal';
- object.value = undefined;
- object.datatype = {
- termType: 'NamedNode'
+ object = {
+ termType: 'Literal',
+ value: undefined,
+ datatype: {
+ termType: 'NamedNode'
+ }
};
let value = item['@value'];
const datatype = item['@type'] || null;
@@ -374,13 +368,14 @@ function _objectToRDF(
} else if(graphTypes.isList(item)) {
const _list = _listToRDF(
item['@list'], issuer, dataset, graphTerm, rdfDirection, options);
- object.termType = _list.termType;
- object.value = _list.value;
+ object = {
+ termType: _list.termType,
+ value: _list.value
+ };
} else {
// convert string/node object to RDF
const id = types.isObject(item) ? item['@id'] : item;
- object.termType = id.startsWith('_:') ? 'BlankNode' : 'NamedNode';
- object.value = id;
+ object = _makeTerm(id);
}
// skip relative IRIs, not valid RDF
@@ -404,3 +399,24 @@ function _objectToRDF(
return object;
}
+
+/**
+ * Make a term from an id. Handles BlankNodes and NamedNodes based on a
+ * possible '_:' id prefix. The prefix is removed for BlankNodes.
+ *
+ * @param id a term id.
+ *
+ * @return a term object.
+ */
+function _makeTerm(id) {
+ if(id.startsWith('_:')) {
+ return {
+ termType: 'BlankNode',
+ value: id.slice(2)
+ };
+ }
+ return {
+ termType: 'NamedNode',
+ value: id
+ };
+}
diff --git a/package.json b/package.json
index d1e84e37..488fc4b8 100644
--- a/package.json
+++ b/package.json
@@ -29,10 +29,10 @@
"lib/**/*.js"
],
"dependencies": {
- "@digitalbazaar/http-client": "^3.4.1",
- "canonicalize": "^1.0.1",
+ "@digitalbazaar/http-client": "^4.0.0",
+ "canonicalize": "^2.0.0",
"lru-cache": "^6.0.0",
- "rdf-canonize": "^3.4.0"
+ "rdf-canonize": "^4.0.1"
},
"devDependencies": {
"@babel/core": "^7.21.8",
@@ -79,7 +79,7 @@
"webpack-merge": "^5.8.0"
},
"engines": {
- "node": ">=14"
+ "node": ">=18"
},
"keywords": [
"JSON",
diff --git a/tests/misc.js b/tests/misc.js
index 908052cd..b9ebbbc3 100644
--- a/tests/misc.js
+++ b/tests/misc.js
@@ -143,19 +143,18 @@ describe('other toRDF tests', () => {
});
});
- it('should handle deprecated N-Quads format', done => {
+ it('should fail for deprecated N-Quads format', done => {
const doc = {
"@id": "https://example.com/",
"https://example.com/test": "test"
};
const p = jsonld.toRDF(doc, {format: 'application/nquads'});
assert(p instanceof Promise);
- p.catch(e => {
- assert.ifError(e);
- }).then(output => {
- assert.equal(
- output,
- ' "test" .\n');
+ p.then(() => {
+ assert.fail();
+ }).catch(e => {
+ assert(e);
+ assert.equal(e.name, 'jsonld.UnknownFormat');
done();
});
});
@@ -232,21 +231,15 @@ describe('other fromRDF tests', () => {
});
});
- it('should handle deprecated N-Quads format', done => {
+ it('should fail for deprecated N-Quads format', done => {
const nq = ' "test" .\n';
const p = jsonld.fromRDF(nq, {format: 'application/nquads'});
assert(p instanceof Promise);
- p.catch(e => {
- assert.ifError(e);
- }).then(output => {
- assert.deepEqual(
- output,
- [{
- "@id": "https://example.com/",
- "https://example.com/test": [{
- "@value": "test"
- }]
- }]);
+ p.then(() => {
+ assert.fail();
+ }).catch(e => {
+ assert(e);
+ assert.equal(e.name, 'jsonld.UnknownFormat');
done();
});
});
@@ -4030,7 +4023,7 @@ _:b0 "v" .
]
;
const nq = `\
-_:b0 <_:b1> "v" .
+_:b0 _:b1 "v" .
`;
await _test({
diff --git a/tests/test.js b/tests/test.js
index a0af0eba..e9aa9f51 100644
--- a/tests/test.js
+++ b/tests/test.js
@@ -349,38 +349,73 @@ const TEST_TYPES = {
],
compare: compareCanonizedExpectedNQuads
},
- 'rdfc:Urgna2012EvalTest': {
- fn: 'normalize',
+ 'rdfc:RDFC10EvalTest': {
+ skip: {
+ // NOTE: idRegex format:
+ // /manifest-urdna2015#testNNN$/,
+ // FIXME
+ idRegex: [
+ // Unsupported U escape
+ // /manifest-urdna2015#test060/
+ ]
+ },
+ fn: 'canonize',
params: [
readTestNQuads('action'),
createTestOptions({
- algorithm: 'URGNA2012',
+ algorithm: 'RDFC-1.0',
inputFormat: 'application/n-quads',
format: 'application/n-quads'
})
],
compare: compareExpectedNQuads
},
- 'rdfc:Urdna2015EvalTest': {
+ 'rdfc:RDFC10NegativeEvalTest': {
skip: {
// NOTE: idRegex format:
- // /manifest-urdna2015#testNNN$/,
- // FIXME
- idRegex: [
- // Unsupported U escape
- /manifest-urdna2015#test060/
- ]
+ // /manifest-rdfc10#testNNN$/,
+ idRegex: []
},
fn: 'canonize',
params: [
readTestNQuads('action'),
createTestOptions({
- algorithm: 'URDNA2015',
+ algorithm: 'RDFC-1.0',
+ inputFormat: 'application/n-quads',
+ format: 'application/n-quads'
+ })
+ ]
+ },
+ 'rdfc:RDFC10MapTest': {
+ skip: {
+ // NOTE: idRegex format:
+ // /manifest-rdfc10#testNNN$/,
+ idRegex: []
+ },
+ fn: 'canonize',
+ params: [
+ readTestNQuads('action'),
+ createTestOptions({
+ algorithm: 'RDFC-1.0',
inputFormat: 'application/n-quads',
format: 'application/n-quads'
})
],
- compare: compareExpectedNQuads
+ preRunAdjustParams: ({params, extra}) => {
+ // add canonicalIdMap
+ const m = new Map();
+ extra.canonicalIdMap = m;
+ params[1].canonizeOptions = params[1].canonizeOptions || {};
+ params[1].canonizeOptions.canonicalIdMap = m;
+ return params;
+ },
+ postRunAdjustParams: ({params}) => {
+ // restore output param to empty map
+ const m = new Map();
+ params[1].canonizeOptions = params[1].canonizeOptions || {};
+ params[1].canonizeOptions.canonicalIdMap = m;
+ },
+ compare: compareExpectedCanonicalIdMap
}
};
@@ -429,8 +464,6 @@ if(options.earl && options.earl.filename) {
}
}
-return new Promise(resolve => {
-
// async generated tests
// _tests => [{suite}, ...]
// suite => {
@@ -440,21 +473,20 @@ return new Promise(resolve => {
// }
const _tests = [];
-return addManifest(manifest, _tests)
- .then(() => {
- return _testsToMocha(_tests);
- }).then(result => {
- if(options.earl.report) {
- describe('Writing EARL report to: ' + options.earl.filename, function() {
- // print out EARL even if .only was used
- const _it = result.hadOnly ? it.only : it;
- _it('should print the earl report', function() {
- return options.writeFile(
- options.earl.filename, options.earl.report.reportJson());
- });
- });
- }
- }).then(() => resolve());
+await addManifest(manifest, _tests);
+const result = _testsToMocha(_tests);
+if(options.earl.report) {
+ describe('Writing EARL report to: ' + options.earl.filename, function() {
+ // print out EARL even if .only was used
+ const _it = result.hadOnly ? it.only : it;
+ _it('should print the earl report', function() {
+ return options.writeFile(
+ options.earl.filename, options.earl.report.reportJson());
+ });
+ });
+}
+
+return;
// build mocha tests from local test structure
function _testsToMocha(tests) {
@@ -485,89 +517,102 @@ function _testsToMocha(tests) {
};
}
-});
-
/**
* Adds the tests for all entries in the given manifest.
*
- * @param manifest {Object} the manifest.
- * @param parent {Object} the parent test structure
- * @return {Promise}
+ * @param {object} manifest - The manifest.
+ * @param {object} parent - The parent test structure.
+ * @returns {Promise} - A promise with no value.
*/
-function addManifest(manifest, parent) {
- return new Promise((resolve, reject) => {
- // create test structure
- const suite = {
- title: manifest.name || manifest.label,
- tests: [],
- suites: [],
- imports: []
- };
- parent.push(suite);
-
- // get entries and sequence (alias for entries)
- const entries = [].concat(
- getJsonLdValues(manifest, 'entries'),
- getJsonLdValues(manifest, 'sequence')
- );
-
- const includes = getJsonLdValues(manifest, 'include');
- // add includes to sequence as jsonld files
- for(let i = 0; i < includes.length; ++i) {
- entries.push(includes[i] + '.jsonld');
+async function addManifest(manifest, parent) {
+ // create test structure
+ const suite = {
+ title: manifest.name || manifest.label,
+ tests: [],
+ suites: [],
+ imports: []
+ };
+ parent.push(suite);
+
+ // get entries and sequence (alias for entries)
+ const entries = [].concat(
+ getJsonLdValues(manifest, 'entries'),
+ getJsonLdValues(manifest, 'sequence')
+ );
+
+ const includes = getJsonLdValues(manifest, 'include');
+ // add includes to sequence as jsonld files
+ for(let i = 0; i < includes.length; ++i) {
+ entries.push(includes[i] + '.jsonld');
+ }
+
+ // resolve all entry promises and process
+ for await (const entry of await Promise.all(entries)) {
+ if(typeof entry === 'string' && entry.endsWith('js')) {
+ // process later as a plain JavaScript file
+ suite.imports.push(entry);
+ continue;
+ } else if(typeof entry === 'function') {
+ // process as a function that returns a promise
+ const childSuite = await entry(options);
+ if(suite) {
+ suite.suites.push(childSuite);
+ }
+ continue;
+ }
+ const manifestEntry = await readManifestEntry(manifest, entry);
+ if(isJsonLdType(manifestEntry, '__SKIP__')) {
+ // special local skip logic
+ suite.tests.push(manifestEntry);
+ } else if(isJsonLdType(manifestEntry, 'mf:Manifest')) {
+ // entry is another manifest
+ await addManifest(manifestEntry, suite.suites);
+ } else {
+ // assume entry is a test
+ await addTest(manifest, manifestEntry, suite.tests);
}
+ }
+}
- // resolve all entry promises and process
- Promise.all(entries).then(entries => {
- let p = Promise.resolve();
- entries.forEach(entry => {
- if(typeof entry === 'string' && entry.endsWith('js')) {
- // process later as a plain JavaScript file
- suite.imports.push(entry);
- return;
- } else if(typeof entry === 'function') {
- // process as a function that returns a promise
- p = p.then(() => {
- return entry(options);
- }).then(childSuite => {
- if(suite) {
- suite.suites.push(childSuite);
- }
- });
- return;
- }
- p = p.then(() => {
- return readManifestEntry(manifest, entry);
- }).then(entry => {
- if(isJsonLdType(entry, '__SKIP__')) {
- // special local skip logic
- suite.tests.push(entry);
- } else if(isJsonLdType(entry, 'mf:Manifest')) {
- // entry is another manifest
- return addManifest(entry, suite.suites);
- } else {
- // assume entry is a test
- return addTest(manifest, entry, suite.tests);
- }
- });
- });
- return p;
- }).then(() => {
- resolve();
- }).catch(err => {
- console.error(err);
- reject(err);
- });
- });
+/**
+ * Common adjust params helper.
+ *
+ * @param {object} params - The param to adjust.
+ * @param {object} test - The test.
+ */
+function _commonAdjustParams(params, test) {
+ if(isJsonLdType(test, 'rdfc:RDFC10EvalTest') ||
+ isJsonLdType(test, 'rdfc:RDFC10MapTest') ||
+ isJsonLdType(test, 'rdfc:RDFC10NegativeEvalTest')) {
+ if(test.hashAlgorithm) {
+ params.canonizeOptions = params.canonizeOptions || {};
+ params.canonizeOptions.messageDigestAlgorithm = test.hashAlgorithm;
+ }
+ if(test.computationalComplexity === 'low') {
+ // simple test cases
+ params.canonizeOptions = params.canonizeOptions || {};
+ params.canonizeOptions.maxWorkFactor = 0;
+ }
+ if(test.computationalComplexity === 'medium') {
+ // tests between O(n) and O(n^2)
+ params.canonizeOptions = params.canonizeOptions || {};
+ params.canonizeOptions.maxWorkFactor = 2;
+ }
+ if(test.computationalComplexity === 'high') {
+ // poison tests between O(n^2) and O(n^3)
+ params.canonizeOptions = params.canonizeOptions || {};
+ params.canonizeOptions.maxWorkFactor = 3;
+ }
+ }
}
/**
* Adds a test.
*
- * @param manifest {Object} the manifest.
- * @param test {Object} the test.
- * @param tests {Array} the list of tests to add to.
- * @return {Promise}
+ * @param {object} manifest - The manifest.
+ * @param {object} test - The test.
+ * @param {Array} tests - The list of tests to add to.
+ * @returns {Promise} - A promise with no value.
*/
async function addTest(manifest, test, tests) {
// expand @id and input base
@@ -597,6 +642,10 @@ async function addTest(manifest, test, tests) {
title: description + ` (jobs=${jobs})`,
f: makeFn({
test,
+ adjustParams: params => {
+ _commonAdjustParams(params[1], test);
+ return params;
+ },
run: ({/*test, */testInfo, params}) => {
// skip Promise.all
if(jobs === 1 && fast1) {
@@ -770,7 +819,14 @@ function makeFn({
});
});
- const params = adjustParams(testInfo.params.map(param => param(test)));
+ let params = testInfo.params.map(param => param(test));
+ const extra = {};
+ // type specific pre run adjustments
+ if(testInfo.preRunAdjustParams) {
+ params = testInfo.preRunAdjustParams({params, extra});
+ }
+ // general adjustments
+ params = adjustParams(params);
// resolve test data
const values = await Promise.all(params);
// copy used to check inputs do not change
@@ -780,6 +836,10 @@ function makeFn({
// run and capture errors and results
try {
result = await run({test, testInfo, params: values});
+ // type specific post run adjustments
+ if(testInfo.postRunAdjustParams) {
+ testInfo.postRunAdjustParams({params: values, extra});
+ }
// check input not changed
assert.deepStrictEqual(valuesOrig, values);
} catch(e) {
@@ -789,21 +849,25 @@ function makeFn({
try {
if(isJsonLdType(test, 'jld:NegativeEvaluationTest')) {
if(!isBenchmark) {
- await compareExpectedError(test, err);
+ await compareExpectedError({test, err});
+ }
+ } else if(isJsonLdType(test, 'rdfc:RDFC10NegativeEvalTest')) {
+ if(!isBenchmark) {
+ await checkError({test, err});
}
} else if(isJsonLdType(test, 'jld:PositiveEvaluationTest') ||
- isJsonLdType(test, 'rdfc:Urgna2012EvalTest') ||
- isJsonLdType(test, 'rdfc:Urdna2015EvalTest')) {
+ isJsonLdType(test, 'rdfc:RDFC10EvalTest') ||
+ isJsonLdType(test, 'rdfc:RDFC10MapTest')) {
if(err) {
throw err;
}
if(!isBenchmark) {
- await testInfo.compare(test, result);
+ await testInfo.compare({test, result, extra});
}
} else if(isJsonLdType(test, 'jld:PositiveSyntaxTest')) {
// no checks
} else {
- throw Error('Unknown test type: ' + test.type);
+ throw new Error(`Unknown test type: "${test.type}"`);
}
let benchmarkResult = null;
@@ -1013,11 +1077,11 @@ function _getExpectProperty(test) {
} else if('result' in test) {
return 'result';
} else {
- throw Error('No expected output property found');
+ throw new Error('No expected output property found');
}
}
-async function compareExpectedJson(test, result) {
+async function compareExpectedJson({test, result}) {
let expect;
try {
expect = await readTestJson(_getExpectProperty(test))(test);
@@ -1032,7 +1096,7 @@ async function compareExpectedJson(test, result) {
}
}
-async function compareExpectedNQuads(test, result) {
+async function compareExpectedNQuads({test, result}) {
let expect;
try {
expect = await readTestNQuads(_getExpectProperty(test))(test);
@@ -1047,11 +1111,15 @@ async function compareExpectedNQuads(test, result) {
}
}
-async function compareCanonizedExpectedNQuads(test, result) {
+async function compareCanonizedExpectedNQuads({test, result}) {
let expect;
try {
expect = await readTestNQuads(_getExpectProperty(test))(test);
- const opts = {algorithm: 'URDNA2015'};
+ const opts = {
+ algorithm: 'RDFC-1.0',
+ // some tests need this: expand 0027 and 0062
+ maxWorkFactor: 2
+ };
const expectDataset = rdfCanonize.NQuads.parse(expect);
const expectCmp = await rdfCanonize.canonize(expectDataset, opts);
const resultDataset = rdfCanonize.NQuads.parse(result);
@@ -1067,7 +1135,35 @@ async function compareCanonizedExpectedNQuads(test, result) {
}
}
-async function compareExpectedError(test, err) {
+async function compareExpectedCanonicalIdMap({test, result, extra}) {
+ let expect;
+ try {
+ expect = await readTestJson(_getExpectProperty(test))(test);
+ const expectMap = new Map(Object.entries(expect));
+ assert.deepStrictEqual(extra.canonicalIdMap, expectMap);
+ } catch(err) {
+ if(options.bailOnError) {
+ console.log('\nTEST FAILED\n');
+ console.log('EXPECTED:\n ' + JSON.stringify(expect, null, 2));
+ console.log('ACTUAL:\n' + JSON.stringify(result, null, 2));
+ }
+ throw err;
+ }
+}
+
+async function checkError({/*test,*/ err}) {
+ try {
+ assert.ok(err, 'no error present');
+ } catch(_err) {
+ if(options.bailOnError) {
+ console.log('\nTEST FAILED\n');
+ console.log('EXPECTED ERROR');
+ }
+ throw _err;
+ }
+}
+
+async function compareExpectedError({test, err}) {
let expect;
let result;
try {
@@ -1088,10 +1184,7 @@ async function compareExpectedError(test, err) {
}
function isJsonLdType(node, type) {
- const nodeType = [].concat(
- getJsonLdValues(node, '@type'),
- getJsonLdValues(node, 'type')
- );
+ const nodeType = getJsonLdType(node);
type = Array.isArray(type) ? type : [type];
for(let i = 0; i < type.length; ++i) {
if(nodeType.indexOf(type[i]) !== -1) {
@@ -1101,13 +1194,17 @@ function isJsonLdType(node, type) {
return false;
}
+function getJsonLdType(node) {
+ return [].concat(
+ getJsonLdValues(node, '@type'),
+ getJsonLdValues(node, 'type')
+ );
+}
+
function getJsonLdValues(node, property) {
let rval = [];
if(property in node) {
- rval = node[property];
- if(!Array.isArray(rval)) {
- rval = [rval];
- }
+ rval = [].concat(node[property]);
}
return rval;
}