rdf-test-suite
Version:
Executes the RDF and SPARQL test suites.
296 lines (295 loc) • 15.7 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestSuiteRunner = void 0;
const rdf_data_factory_1 = require("rdf-data-factory");
const LogSymbols = require("log-symbols");
const ManifestLoader_1 = require("./ManifestLoader");
const Util_1 = require("./Util");
// tslint:disable:no-var-requires
const quad = require('rdf-quad');
const streamifyArray = require('streamify-array');
const DF = new rdf_data_factory_1.DataFactory();
/**
* TestSuiteRunner runs a certain test suite manifest.
*/
class TestSuiteRunner {
/**
* Parse an URL to file mapping string.
* @param {string} urlToFileMapping An URL to file mapping string
* @return {{url: string; path: string}[]} A parsed URL to file mapping array.
*/
fromUrlToMappingString(urlToFileMapping) {
const urlToFileMappings = [];
if (urlToFileMapping) {
const [url, path] = urlToFileMapping.split('~');
urlToFileMappings.push({ url, path });
}
return urlToFileMappings;
}
/**
* Run the manifest with the given URL.
* @param {string} manifestUrl The URL of a manifest.
* @param handler The handler to run the tests with.
* @param {string} cachePath The base directory to cache files in. If falsy, then no cache will be used.
* @param {string} specification An optional specification to scope the manifest tests by.
* @param {RegExp} testRegex An optional regex to filter test IRIs by.
* @param {any} injectArguments An optional set of arguments to pass to the handler.
* @return {Promise<ITestResult[]>} A promise resolving to an array of test results.
*/
runManifest(manifestUrl, handler, config) {
return __awaiter(this, void 0, void 0, function* () {
const { cachePath, specification, urlToFileMapping } = config;
const urlToFileMappings = this.fromUrlToMappingString(urlToFileMapping);
const manifest = yield new ManifestLoader_1.ManifestLoader().from(manifestUrl, { cachePath, urlToFileMappings });
const results = [];
// Only run the tests for the given specification if one was defined.
if (specification) {
if (!manifest.specifications || !manifest.specifications[specification]) {
return [];
}
yield this.runManifestConcrete(manifest.specifications[specification], handler, config, results);
return results;
}
yield this.runManifestConcrete(manifest, handler, config, results);
return results;
});
}
/**
* Run the given manifest.
* @param {string} manifest A manifest.
* @param handler The handler to run the tests with.
* @param {RegExp} testRegex An optional regex to filter test IRIs by.
* @param {any} injectArguments An optional set of arguments to pass to the handler.
* @param {ITestResult[]} results An array to append the test results to
* @return {Promise<void>} A promise resolving when the tests are finished.
*/
runManifestConcrete(manifest, handler, config, results) {
return __awaiter(this, void 0, void 0, function* () {
// Execute all tests in this manifest
if (manifest.testEntries) {
for (const test of manifest.testEntries) {
if ((!config.testRegex || config.testRegex.test(test.uri)) && !(config.skipRegex && config.skipRegex.test(test.uri))) {
if (!config.runRejected && test.approval === 'http://www.w3.org/ns/rdftest#Rejected') {
// Skip tests that are explicitly rejected
results.push({ test, ok: false, skipped: true, error: new Error('Rejected Test') });
}
else if (config.explicitApproval && test.approval !== 'http://www.w3.org/ns/rdftest#Approved') {
// Skip tests that are explicitly rejected
results.push({ test, ok: false, skipped: true, error: new Error('Test not explicitly approved') });
}
else {
let timeout = null;
const timeStart = process.hrtime();
let testResultOverride;
try {
yield Promise.race([
test.test(handler, config.customEngingeOptions)
.then((result) => {
if (result) {
testResultOverride = result;
}
}),
new Promise((res, rej) => {
// global. is needed because TSC may otherwise pick the browser version of setTimeout, which returns int
timeout = global.setTimeout(() => rej(new Error(`Test case '${test.uri}' timed out`)), config.timeOutDuration);
}),
]);
}
catch (error) {
clearTimeout(timeout);
results.push({ test, ok: false, error, skipped: error.skipped });
continue;
}
const timeEnd = process.hrtime(timeStart);
clearTimeout(timeout);
results.push(Object.assign({ test, ok: true, duration: (timeEnd[0] * 1000) + (timeEnd[1] / 1000000) }, (testResultOverride || {})));
}
}
}
}
// Recursively handle all sub-manifests
if (manifest.subManifests) {
for (const subManifest of manifest.subManifests) {
yield (this.runManifestConcrete(subManifest, handler, config, results));
}
}
});
}
/**
* Print the given test results to a text stream.
* @param {WriteStream} stdout The output stream to write to.
* @param {ITestResult[]} results An array of test results.
* @param {boolean} compact If the results should be printed in compact-mode.
*/
resultsToText(stdout, results, compact) {
const failedTests = [];
let success = 0;
let skipped = 0;
for (const result of results) {
if (result.ok) {
success++;
stdout.write(`${LogSymbols.success} ${result.test.name} (${result.test.uri})${` ${Util_1.Util.withColor(`${result.duration}ms`, Util_1.Util.COLOR_GRAY)}`}\n`);
}
else {
if (result.skipped) {
skipped++;
stdout.write(`${LogSymbols.info} ${result.test.name} (${result.test.uri})\n`);
}
else {
failedTests.push(result);
stdout.write(`${LogSymbols.error} ${result.test.name} (${result.test.uri})\n`);
}
}
}
if (!compact) {
for (const result of failedTests) {
stdout.write(`
${LogSymbols.error} ${Util_1.Util.withColor(result.test.name, Util_1.Util.COLOR_RED)}
${result.test.comment || ''}
${'test' in result.error ? result.error : result.error.stack}
${Util_1.Util.withColor(`More info: ${result.test.uri}`, Util_1.Util.COLOR_BLUE)}
`);
}
}
const skippedString = skipped ? ` (skipped ${skipped})` : '';
success += skipped;
if (success === results.length) {
stdout.write(`${LogSymbols.success} ${success} / ${results.length} tests succeeded!${skippedString}\n`);
}
else {
stdout.write(`${LogSymbols.error} ${success} / ${results.length} tests succeeded!${skippedString}\n`);
}
}
/**
* Convert test results to an RDF stream.
* @param {ITestResult[]} results Test results.
* @param {IEarlProperties} properties EARL properties.
* @param {Date} testDate The date at which the tests were executed.
* @return {Stream} An RDF stream of quads.
*/
resultsToEarl(results, properties, testDate) {
const p = require('./prefixes.json');
const dateRaw = testDate.toISOString();
const date = '"' + dateRaw + '"^^' + p.xsd + 'dateTime';
const quads = [];
// Describe report
const report = properties.reportUri || '';
quads.push(DF.quad(DF.namedNode(report), DF.namedNode(p.foaf + 'primaryTopic'), DF.namedNode(properties.applicationUri)));
quads.push(DF.quad(DF.namedNode(report), DF.namedNode(p.dc + 'issued'), DF.literal(testDate.toISOString(), DF.namedNode(p.xsd + 'dateTime'))));
for (const author of properties.authors) {
quads.push(DF.quad(DF.namedNode(report), DF.namedNode(p.foaf + 'maker'), DF.namedNode(author.uri)));
}
// Describe application
const app = properties.applicationUri;
quads.push(quad(app, p.rdf + 'type', p.earl + 'Software'));
quads.push(quad(app, p.rdf + 'type', p.earl + 'TestSubject'));
quads.push(quad(app, p.rdf + 'type', p.doap + 'Project'));
quads.push(quad(app, p.doap + 'name', '"' + properties.applicationNameFull + '"'));
quads.push(quad(app, p.dc + 'title', '"' + properties.applicationNameFull + '"'));
if (properties.version) {
quads.push(quad(app, p.doap + 'release', '_:b_release'));
quads.push(quad('_:b_release', p.doap + 'revision', '"' + properties.version + '"'));
}
quads.push(quad(app, p.doap + 'homepage', properties.applicationHomepageUrl));
quads.push(quad(app, p.doap + 'license', properties.licenseUri));
quads.push(quad(app, p.doap + 'programming-language', '"JavaScript"'));
for (const spec of properties.specificationUris) {
quads.push(quad(app, p.doap + 'implements', spec));
}
quads.push(quad(app, p.doap + 'category', 'http://dbpedia.org/resource/Resource_Description_Framework'));
quads.push(quad(app, p.doap + 'download-page', 'https://npmjs.org/package/' + properties.applicationNameNpm));
if (properties.applicationBugsUrl) {
quads.push(quad(app, p.doap + 'bug-database', properties.applicationBugsUrl));
}
if (properties.applicationBlogUrl) {
quads.push(quad(app, p.doap + 'blog', properties.applicationBlogUrl));
}
for (const author of properties.authors) {
quads.push(quad(app, p.doap + 'developer', author.uri));
quads.push(quad(app, p.doap + 'maintainer', author.uri));
quads.push(quad(app, p.doap + 'documenter', author.uri));
quads.push(quad(app, p.doap + 'maker', author.uri));
quads.push(quad(app, p.dc + 'creator', author.uri));
}
quads.push(quad(app, p.dc + 'description', '"' + properties.applicationDescription + '"@en'));
quads.push(quad(app, p.doap + 'description', '"' + properties.applicationDescription + '"@en'));
// Describe authors
for (const author of properties.authors) {
quads.push(quad(author.uri, p.rdf + 'type', p.foaf + 'Person'));
quads.push(quad(author.uri, p.rdf + 'type', p.earl + 'Assertor'));
if (author.name) {
quads.push(quad(author.uri, p.foaf + 'name', '"' + author.name + '"'));
}
if (author.homepage) {
quads.push(quad(author.uri, p.foaf + 'homepage', author.homepage));
}
if (author.primaryTopic) {
quads.push(quad(author.uri, p.foaf + 'primaryTopicOf', author.primaryTopic));
}
}
// Describe test results
let id = 0;
for (const result of results) {
const testUri = result.test.uri;
quads.push(quad(testUri, p.rdf + 'type', p.earl + 'TestCriterion'));
quads.push(quad(testUri, p.rdf + 'type', p.earl + 'TestCase'));
quads.push(quad(testUri, p.dc + 'title', '"' + result.test.name + '"'));
if (result.test.comment) {
quads.push(quad(testUri, p.dc + 'description', '"' + result.test.comment + '"'));
}
quads.push(quad(testUri, p.earl + 'assertions', '_:assertions' + id));
quads.push(quad('_:assertions' + id, p.rdf + 'first', '_:assertion' + id));
quads.push(quad('_:assertions' + id, p.rdf + 'rest', p.rdf + 'nil'));
quads.push(quad('_:assertion' + id, p.rdf + 'type', p.earl + 'Assertion'));
for (const author of properties.authors) {
quads.push(quad('_:assertion' + id, p.earl + 'assertedBy', author.uri));
}
quads.push(quad('_:assertion' + id, p.earl + 'test', testUri));
quads.push(quad('_:assertion' + id, p.earl + 'subject', app));
quads.push(quad('_:assertion' + id, p.earl + 'mode', p.earl + 'automatic'));
quads.push(quad('_:assertion' + id, p.earl + 'result', '_:result' + id));
quads.push(quad('_:result' + id, p.rdf + 'type', p.earl + 'TestResult'));
quads.push(quad('_:result' + id, p.earl + 'outcome', p.earl + (result.ok ? 'passed' : result.skipped ? 'inapplicable' : 'failed')));
quads.push(quad('_:result' + id, p.dc + 'date', date));
id++;
}
return streamifyArray(quads);
}
/**
* Create an {#link IEarlProperties} data object based on package.json contents.
* @param packageJson Package.json contents.
* @return {IEarlProperties} A data object.
*/
packageJsonToEarlProperties(packageJson) {
return {
applicationBugsUrl: packageJson.bugs && packageJson.bugs.url ? packageJson.bugs.url : packageJson.bugs,
applicationDescription: packageJson.description,
applicationHomepageUrl: packageJson.homepage,
applicationNameFull: packageJson.name,
applicationNameNpm: packageJson.name,
applicationUri: packageJson.name ? 'https://www.npmjs.com/package/' + packageJson.name + '/' : null,
authors: [
{
homepage: null,
name: packageJson.author,
uri: 'https://www.npmjs.com/package/' + packageJson.name + '/#author',
},
],
licenseUri: packageJson.license ? Util_1.Util.licenseToUri(packageJson.license) : null,
reportUri: null,
specificationUris: [],
version: packageJson.version,
};
}
}
exports.TestSuiteRunner = TestSuiteRunner;
//# sourceMappingURL=TestSuiteRunner.js.map