mirror of https://gitee.com/bigwinds/arangodb
650 lines
20 KiB
JavaScript
650 lines
20 KiB
JavaScript
/* jshint strict: false, sub: true */
|
|
/* global print db */
|
|
'use strict';
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / DISCLAIMER
|
|
// /
|
|
// / Copyright 2016 ArangoDB GmbH, Cologne, Germany
|
|
// / Copyright 2014 triagens GmbH, Cologne, Germany
|
|
// /
|
|
// / Licensed under the Apache License, Version 2.0 (the "License")
|
|
// / you may not use this file except in compliance with the License.
|
|
// / You may obtain a copy of the License at
|
|
// /
|
|
// / http://www.apache.org/licenses/LICENSE-2.0
|
|
// /
|
|
// / Unless required by applicable law or agreed to in writing, software
|
|
// / distributed under the License is distributed on an "AS IS" BASIS,
|
|
// / WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// / See the License for the specific language governing permissions and
|
|
// / limitations under the License.
|
|
// /
|
|
// / Copyright holder is ArangoDB GmbH, Cologne, Germany
|
|
// /
|
|
// / @author Max Neunhoeffer
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
/* Modules: */
|
|
const _ = require('lodash');
|
|
const fs = require('fs');
|
|
const pu = require('@arangodb/process-utils');
|
|
const yaml = require('js-yaml');
|
|
|
|
const toArgv = require('internal').toArgv;
|
|
const time = require('internal').time;
|
|
const sleep = require('internal').sleep;
|
|
const download = require('internal').download;
|
|
|
|
/* Constants: */
|
|
// const BLUE = require('internal').COLORS.COLOR_BLUE;
|
|
// const CYAN = require('internal').COLORS.COLOR_CYAN;
|
|
// const GREEN = require('internal').COLORS.COLOR_GREEN;
|
|
const RED = require('internal').COLORS.COLOR_RED;
|
|
const RESET = require('internal').COLORS.COLOR_RESET;
|
|
// const YELLOW = require('internal').COLORS.COLOR_YELLOW;
|
|
|
|
let didSplitBuckets = false;
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief get the items uniq to arr1 or arr2
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function diffArray (arr1, arr2) {
|
|
return arr1.concat(arr2).filter(function (val) {
|
|
if (!(arr1.includes(val) && arr2.includes(val))) {
|
|
return val;
|
|
}
|
|
});
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief build a unix path
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function makePathUnix (path) {
|
|
return fs.join.apply(null, path.split('/'));
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief build a generic path
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function makePathGeneric (path) {
|
|
return fs.join.apply(null, path.split(fs.pathSeparator));
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief runs a list of tests
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function performTests (options, testList, testname, runFn, serverOptions, startStopHandlers) {
|
|
if (options.testBuckets && !didSplitBuckets) {
|
|
throw new Error("You parametrized to split buckets, but this testsuite doesn't support it!!!");
|
|
}
|
|
|
|
if (testList.length === 0) {
|
|
print('Testsuite is empty!');
|
|
|
|
return {
|
|
'EMPTY TESTSUITE': {
|
|
status: false,
|
|
message: 'no testsuites found!'
|
|
}
|
|
};
|
|
}
|
|
|
|
if (serverOptions === undefined) {
|
|
serverOptions = {};
|
|
}
|
|
|
|
let env = {};
|
|
let customInstanceInfos = {};
|
|
|
|
if (startStopHandlers !== undefined && startStopHandlers.hasOwnProperty('preStart')) {
|
|
customInstanceInfos['preStart'] = startStopHandlers.preStart(options,
|
|
serverOptions,
|
|
customInstanceInfos,
|
|
startStopHandlers);
|
|
if (customInstanceInfos.preStart.state === false) {
|
|
return {
|
|
setup: {
|
|
status: false,
|
|
message: 'custom preStart failed!' + customInstanceInfos.preStart.message
|
|
}
|
|
};
|
|
}
|
|
_.defaults(env, customInstanceInfos.preStart.env);
|
|
}
|
|
|
|
let instanceInfo = pu.startInstance('tcp', options, serverOptions, testname);
|
|
|
|
if (instanceInfo === false) {
|
|
if (startStopHandlers !== undefined && startStopHandlers.hasOwnProperty('startFailed')) {
|
|
customInstanceInfos['startFailed'] = startStopHandlers.startFailed(options,
|
|
serverOptions,
|
|
customInstanceInfos,
|
|
startStopHandlers);
|
|
}
|
|
return {
|
|
setup: {
|
|
status: false,
|
|
message: 'failed to start server!'
|
|
}
|
|
};
|
|
}
|
|
|
|
if (startStopHandlers !== undefined && startStopHandlers.hasOwnProperty('postStart')) {
|
|
customInstanceInfos['postStart'] = startStopHandlers.postStart(options,
|
|
serverOptions,
|
|
instanceInfo,
|
|
customInstanceInfos,
|
|
startStopHandlers);
|
|
if (customInstanceInfos.postStart.state === false) {
|
|
pu.shutdownInstance(instanceInfo, options);
|
|
return {
|
|
setup: {
|
|
status: false,
|
|
message: 'custom postStart failed: ' + customInstanceInfos.postStart.message
|
|
}
|
|
};
|
|
}
|
|
_.defaults(env, customInstanceInfos.postStart.env);
|
|
}
|
|
|
|
let results = {};
|
|
let continueTesting = true;
|
|
let count = 0;
|
|
let forceTerminate = false;
|
|
let graphCount = 0;
|
|
|
|
for (let i = 0; i < testList.length; i++) {
|
|
let te = testList[i];
|
|
let filtered = {};
|
|
|
|
if (filterTestcaseByOptions(te, options, filtered)) {
|
|
let first = true;
|
|
let loopCount = 0;
|
|
count += 1;
|
|
|
|
let collectionsBefore = [];
|
|
db._collections().forEach(collection => {
|
|
collectionsBefore.push(collection._name);
|
|
});
|
|
while (first || options.loopEternal) {
|
|
if (!continueTesting) {
|
|
print('oops! Skipping, ' + te + ' server is gone.');
|
|
|
|
results[te] = {
|
|
status: false,
|
|
message: instanceInfo.exitStatus
|
|
};
|
|
|
|
instanceInfo.exitStatus = 'server is gone.';
|
|
|
|
break;
|
|
}
|
|
|
|
print('\n' + Date() + ' ' + runFn.info + ': Trying', te, '...');
|
|
let reply = runFn(options, instanceInfo, te, env);
|
|
|
|
if (reply.hasOwnProperty('forceTerminate')) {
|
|
continueTesting = false;
|
|
forceTerminate = true;
|
|
continue;
|
|
} else if (reply.hasOwnProperty('status')) {
|
|
results[te] = reply;
|
|
|
|
if (results[te].status === false) {
|
|
options.cleanup = false;
|
|
}
|
|
|
|
if (!reply.status && !options.force) {
|
|
break;
|
|
}
|
|
} else {
|
|
results[te] = {
|
|
status: false,
|
|
message: reply
|
|
};
|
|
|
|
if (!options.force) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
continueTesting = pu.arangod.check.instanceAlive(instanceInfo, options);
|
|
|
|
// Check whether some collections were left behind, and if mark test as failed.
|
|
let collectionsAfter = [];
|
|
db._collections().forEach(collection => {
|
|
collectionsAfter.push(collection._name);
|
|
});
|
|
let delta = diffArray(collectionsBefore, collectionsAfter).filter(function(name) {
|
|
return (name[0] !== '_'); // exclude system collections from the comparison
|
|
});
|
|
print(delta);
|
|
if (delta.length !== 0) {
|
|
results[te] = {
|
|
status: false,
|
|
message: 'Cleanup missing - test left over collections: ' + delta + '. Original test status: ' + JSON.stringify(results[te])
|
|
};
|
|
collectionsBefore = [];
|
|
db._collections().forEach(collection => {
|
|
collectionsBefore.push(collection._name);
|
|
});
|
|
}
|
|
|
|
let graphs = db._collection('_graphs');
|
|
if (graphs && graphs.count() !== graphCount) {
|
|
results[te] = {
|
|
status: false,
|
|
message: 'Cleanup of graphs missing - found graph definitions: [ ' +
|
|
JSON.stringify(graphs.toArray()) +
|
|
' ] - Original test status: ' +
|
|
JSON.stringify(results[te])
|
|
};
|
|
graphCount = graphs.count();
|
|
}
|
|
|
|
if (startStopHandlers !== undefined && startStopHandlers.hasOwnProperty('alive')) {
|
|
customInstanceInfos['alive'] = startStopHandlers.alive(options,
|
|
serverOptions,
|
|
instanceInfo,
|
|
customInstanceInfos,
|
|
startStopHandlers);
|
|
if (customInstanceInfos.alive.state === false) {
|
|
continueTesting = false;
|
|
results.setup.message = 'custom preStop failed!';
|
|
}
|
|
collectionsBefore = [];
|
|
db._collections().forEach(collection => {
|
|
collectionsBefore.push(collection._name);
|
|
});
|
|
}
|
|
|
|
first = false;
|
|
|
|
if (options.loopEternal) {
|
|
if (loopCount % options.loopSleepWhen === 0) {
|
|
print('sleeping...');
|
|
sleep(options.loopSleepSec);
|
|
print('continuing.');
|
|
}
|
|
|
|
++loopCount;
|
|
}
|
|
}
|
|
} else {
|
|
if (options.extremeVerbosity) {
|
|
print('Skipped ' + te + ' because of ' + filtered.filter);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count === 0) {
|
|
results['ALLTESTS'] = {
|
|
status: false,
|
|
skipped: true
|
|
};
|
|
results.status = false;
|
|
print(RED + 'No testcase matched the filter.' + RESET);
|
|
}
|
|
|
|
print('Shutting down...');
|
|
if (startStopHandlers !== undefined && startStopHandlers.hasOwnProperty('preStop')) {
|
|
customInstanceInfos['preStop'] = startStopHandlers.preStop(options,
|
|
serverOptions,
|
|
instanceInfo,
|
|
customInstanceInfos,
|
|
startStopHandlers);
|
|
if (customInstanceInfos.preStop.state === false) {
|
|
results.setup.status = false;
|
|
results.setup.message = 'custom preStop failed!';
|
|
}
|
|
}
|
|
|
|
// pass on JWT secret
|
|
let clonedOpts = _.clone(options);
|
|
if (serverOptions['server.jwt-secret'] && !clonedOpts['server.jwt-secret']) {
|
|
clonedOpts['server.jwt-secret'] = serverOptions['server.jwt-secret'];
|
|
}
|
|
pu.shutdownInstance(instanceInfo, clonedOpts, forceTerminate);
|
|
|
|
if (startStopHandlers !== undefined && startStopHandlers.hasOwnProperty('postStop')) {
|
|
customInstanceInfos['postStop'] = startStopHandlers.postStop(options,
|
|
serverOptions,
|
|
instanceInfo,
|
|
customInstanceInfos,
|
|
startStopHandlers);
|
|
if (customInstanceInfos.postStop.state === false) {
|
|
results.setup.status = false;
|
|
results.setup.message = 'custom postStop failed!';
|
|
}
|
|
}
|
|
print('done.');
|
|
|
|
return results;
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief filter test-cases according to options
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function filterTestcaseByOptions (testname, options, whichFilter) {
|
|
// These filters require a proper setup, Even if we filter by testcase:
|
|
if ((testname.indexOf('-mmfiles') !== -1) && options.storageEngine === 'rocksdb') {
|
|
whichFilter.filter = 'skip when running as rocksdb';
|
|
return false;
|
|
}
|
|
|
|
if ((testname.indexOf('-rocksdb') !== -1) && options.storageEngine === 'mmfiles') {
|
|
whichFilter.filter = 'skip when running as mmfiles';
|
|
return false;
|
|
}
|
|
|
|
if (options.replication) {
|
|
whichFilter.filter = 'replication';
|
|
|
|
if (options.hasOwnProperty('test') && (typeof (options.test) !== 'undefined')) {
|
|
whichFilter.filter = 'testcase';
|
|
return ((testname.search(options.test) >= 0) &&
|
|
(testname.indexOf('replication') !== -1));
|
|
} else {
|
|
return testname.indexOf('replication') !== -1;
|
|
}
|
|
} else if (testname.indexOf('replication') !== -1) {
|
|
whichFilter.filter = 'replication';
|
|
return false;
|
|
}
|
|
|
|
if ((testname.indexOf('-cluster') !== -1) && !options.cluster) {
|
|
whichFilter.filter = 'noncluster';
|
|
return false;
|
|
}
|
|
|
|
if (testname.indexOf('-noncluster') !== -1 && options.cluster) {
|
|
whichFilter.filter = 'cluster';
|
|
return false;
|
|
}
|
|
|
|
if (testname.indexOf('-arangosearch') !== -1 && !options.arangosearch) {
|
|
whichFilter.filter = 'arangosearch';
|
|
return false;
|
|
}
|
|
|
|
// if we filter, we don't care about the other filters below:
|
|
if (options.hasOwnProperty('test') && (typeof (options.test) !== 'undefined')) {
|
|
whichFilter.filter = 'testcase';
|
|
return testname.search(options.test) >= 0;
|
|
}
|
|
|
|
if (testname.indexOf('-timecritical') !== -1 && options.skipTimeCritical) {
|
|
whichFilter.filter = 'timecritical';
|
|
return false;
|
|
}
|
|
|
|
if (testname.indexOf('-nightly') !== -1 && options.skipNightly && !options.onlyNightly) {
|
|
whichFilter.filter = 'skip nightly';
|
|
return false;
|
|
}
|
|
|
|
if (testname.indexOf('-geo') !== -1 && options.skipGeo) {
|
|
whichFilter.filter = 'geo';
|
|
return false;
|
|
}
|
|
|
|
if (testname.indexOf('-nondeterministic') !== -1 && options.skipNondeterministic) {
|
|
whichFilter.filter = 'nondeterministic';
|
|
return false;
|
|
}
|
|
|
|
if (testname.indexOf('-graph') !== -1 && options.skipGraph) {
|
|
whichFilter.filter = 'graph';
|
|
return false;
|
|
}
|
|
|
|
if (testname.indexOf('-disabled') !== -1) {
|
|
whichFilter.filter = 'disabled';
|
|
return false;
|
|
}
|
|
|
|
if ((testname.indexOf('-memoryintense') !== -1) && options.skipMemoryIntense) {
|
|
whichFilter.filter = 'memoryintense';
|
|
return false;
|
|
}
|
|
|
|
if (testname.indexOf('-nightly') === -1 && options.onlyNightly) {
|
|
whichFilter.filter = 'only nightly';
|
|
return false;
|
|
}
|
|
|
|
if ((testname.indexOf('-novalgrind') !== -1) && options.valgrind) {
|
|
whichFilter.filter = 'skip in valgrind';
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief split into buckets
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function splitBuckets (options, cases) {
|
|
if (!options.testBuckets || cases.length === 0) {
|
|
return cases;
|
|
}
|
|
|
|
didSplitBuckets = true;
|
|
let m = cases.length;
|
|
let n = options.testBuckets.split('/');
|
|
let r = parseInt(n[0]);
|
|
let s = parseInt(n[1]);
|
|
|
|
if (r < 1) {
|
|
r = 1;
|
|
}
|
|
|
|
if (r === 1) {
|
|
return cases;
|
|
}
|
|
|
|
if (s < 0) {
|
|
s = 0;
|
|
}
|
|
if (r <= s) {
|
|
s = r - 1;
|
|
}
|
|
|
|
let result = [];
|
|
|
|
for (let i = s % m; i < cases.length; i = i + r) {
|
|
result.push(cases[i]);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function doOnePathInner (path) {
|
|
return _.filter(fs.list(makePathUnix(path)),
|
|
function (p) {
|
|
return (p.substr(-3) === '.js');
|
|
})
|
|
.map(function (x) {
|
|
return fs.join(makePathUnix(path), x);
|
|
}).sort();
|
|
}
|
|
|
|
function scanTestPath (path) {
|
|
var community = doOnePathInner(path);
|
|
if (global.ARANGODB_CLIENT_VERSION(true)['enterprise-version']) {
|
|
return community.concat(doOnePathInner('enterprise/' + path));
|
|
} else {
|
|
return community;
|
|
}
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief runs a remote unittest file using /_admin/execute
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function runThere (options, instanceInfo, file) {
|
|
try {
|
|
let testCode;
|
|
let mochaGrep = options.mochaGrep ? ', ' + JSON.stringify(options.mochaGrep) : '';
|
|
if (file.indexOf('-spec') === -1) {
|
|
testCode = 'const runTest = require("jsunity").runTest; ' +
|
|
'return runTest(' + JSON.stringify(file) + ', true);';
|
|
} else {
|
|
testCode = 'const runTest = require("@arangodb/mocha-runner"); ' +
|
|
'return runTest(' + JSON.stringify(file) + ', true' + mochaGrep + ');';
|
|
}
|
|
|
|
testCode = 'global.instanceInfo = ' + JSON.stringify(instanceInfo) + ';\n' + testCode;
|
|
|
|
let httpOptions = pu.makeAuthorizationHeaders(options);
|
|
httpOptions.method = 'POST';
|
|
httpOptions.timeout = 1800;
|
|
|
|
if (options.valgrind) {
|
|
httpOptions.timeout *= 2;
|
|
}
|
|
|
|
httpOptions.returnBodyOnError = true;
|
|
|
|
const reply = download(instanceInfo.url + '/_admin/execute?returnAsJSON=true',
|
|
testCode,
|
|
httpOptions);
|
|
|
|
if (!reply.error && reply.code === 200) {
|
|
return JSON.parse(reply.body);
|
|
} else {
|
|
if ((reply.code === 500) &&
|
|
reply.hasOwnProperty('message') &&
|
|
(reply.message === 'Request timeout reached')) {
|
|
return {
|
|
status: false,
|
|
message: reply.message,
|
|
forceTerminate: true
|
|
};
|
|
} else if (reply.hasOwnProperty('body')) {
|
|
return {
|
|
status: false,
|
|
message: reply.body
|
|
};
|
|
} else {
|
|
return {
|
|
status: false,
|
|
message: yaml.safeDump(reply)
|
|
};
|
|
}
|
|
}
|
|
} catch (ex) {
|
|
return {
|
|
status: false,
|
|
message: ex.message || String(ex),
|
|
stack: ex.stack
|
|
};
|
|
}
|
|
}
|
|
runThere.info = 'runThere';
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief runs a local unittest file using arangosh
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function runInArangosh (options, instanceInfo, file, addArgs) {
|
|
let args = pu.makeArgs.arangosh(options);
|
|
args['server.endpoint'] = instanceInfo.endpoint;
|
|
|
|
args['javascript.unit-tests'] = fs.join(pu.TOP_DIR, file);
|
|
|
|
if (!options.verbose) {
|
|
args['log.level'] = 'warning';
|
|
}
|
|
|
|
if (addArgs !== undefined) {
|
|
args = Object.assign(args, addArgs);
|
|
}
|
|
require('internal').env.INSTANCEINFO = JSON.stringify(instanceInfo);
|
|
let rc = pu.executeAndWait(pu.ARANGOSH_BIN, toArgv(args), options, 'arangosh', instanceInfo.rootDir);
|
|
let result;
|
|
try {
|
|
result = JSON.parse(fs.read(instanceInfo.rootDir + '/testresult.json'));
|
|
|
|
fs.remove(instanceInfo.rootDir + '/testresult.json');
|
|
} catch (x) {
|
|
if (options.extremeVerbosity) {
|
|
print('failed to read ' + instanceInfo.rootDir + '/testresult.json');
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
if ((typeof result[0] === 'object') &&
|
|
result[0].hasOwnProperty('status')) {
|
|
return result[0];
|
|
} else {
|
|
rc.failed = rc.status ? 0 : 1;
|
|
return rc;
|
|
}
|
|
}
|
|
runInArangosh.info = 'arangosh';
|
|
|
|
function makeResults (testname, instanceInfo) {
|
|
const startTime = time();
|
|
|
|
return function (status, message) {
|
|
let duration = time() - startTime;
|
|
let results;
|
|
|
|
if (status) {
|
|
let result;
|
|
|
|
try {
|
|
result = JSON.parse(fs.read(instanceInfo.rootDir + '/testresult.json'));
|
|
|
|
if ((typeof result[0] === 'object') &&
|
|
result[0].hasOwnProperty('status')) {
|
|
results = result[0];
|
|
}
|
|
} catch (x) {}
|
|
}
|
|
|
|
if (results === undefined) {
|
|
results = {
|
|
status: status,
|
|
duration: duration,
|
|
total: 1,
|
|
failed: status ? 0 : 1,
|
|
'testing.js': {
|
|
status: status,
|
|
duration: duration
|
|
}
|
|
};
|
|
|
|
if (message) {
|
|
results['testing.js'].message = message;
|
|
}
|
|
}
|
|
|
|
let full = {};
|
|
full[testname] = results;
|
|
|
|
return full;
|
|
};
|
|
}
|
|
|
|
exports.runThere = runThere;
|
|
exports.runInArangosh = runInArangosh;
|
|
|
|
exports.makePathUnix = makePathUnix;
|
|
exports.makePathGeneric = makePathGeneric;
|
|
exports.performTests = performTests;
|
|
exports.filterTestcaseByOptions = filterTestcaseByOptions;
|
|
exports.splitBuckets = splitBuckets;
|
|
exports.doOnePathInner = doOnePathInner;
|
|
exports.scanTestPath = scanTestPath;
|
|
exports.makeResults = makeResults;
|
|
exports.diffArray = diffArray;
|