Close GH-2795: Rewrite cucumber test caching (and support logic). Fixes #2745

This commit is contained in:
Patrick Niklaus
2016-09-19 14:13:44 -07:00
committed by Dane Springmeyer
parent 8522cddd61
commit 7d124ce54d
60 changed files with 871 additions and 1144 deletions
+31
View File
@@ -0,0 +1,31 @@
'use strict';
const fs = require('fs');
const crypto = require('crypto');
const d3 = require('d3-queue');
module.exports = {
hashOfFiles: (paths, cb) => {
let queue = d3.queue();
for (let i = 0; i < paths.length; ++i) {
queue.defer(fs.readFile, paths[i]);
}
queue.awaitAll((err, results) => {
if (err) return cb(err);
let checksum = crypto.createHash('md5');
for (let i = 0; i < results.length; ++i) {
checksum.update(results[i]);
}
cb(null, checksum.digest('hex'));
});
},
hashOfFile: (path, cb) => {
fs.readFile(path, (err, result) => {
if (err) return cb(err);
let checksum = crypto.createHash('md5');
checksum.update(result);
cb(null, checksum.digest('hex'));
});
}
};
+161
View File
@@ -0,0 +1,161 @@
'use strict';
const builder = require('xmlbuilder');
const ensureDecimal = require('./utils').ensureDecimal;
class DB {
constructor () {
this.nodes = new Array();
this.ways = new Array();
this.relations = new Array();
}
addNode (node) {
this.nodes.push(node);
}
addWay (way) {
this.ways.push(way);
}
addRelation (relation) {
this.relations.push(relation);
}
clear () {
this.nodes = [];
this.ways = [];
this.relations = [];
}
toXML (callback) {
var xml = builder.create('osm', {'encoding':'UTF-8'});
xml.att('generator', 'osrm-test')
.att('version', '0.6');
this.nodes.forEach((n) => {
var node = xml.ele('node', {
id: n.id,
version: 1,
uid: n.OSM_UID,
user: n.OSM_USER,
timestamp: n.OSM_TIMESTAMP,
lon: ensureDecimal(n.lon),
lat: ensureDecimal(n.lat)
});
for (var k in n.tags) {
node.ele('tag')
.att('k', k)
.att('v', n.tags[k]);
}
});
this.ways.forEach((w) => {
var way = xml.ele('way', {
id: w.id,
version: 1,
uid: w.OSM_UID,
user: w.OSM_USER,
timestamp: w.OSM_TIMESTAMP
});
w.nodes.forEach((k) => {
way.ele('nd')
.att('ref', k.id);
});
for (var k in w.tags) {
way.ele('tag')
.att('k', k)
.att('v', w.tags[k]);
}
});
this.relations.forEach((r) => {
var relation = xml.ele('relation', {
id: r.id,
user: r.OSM_USER,
timestamp: r.OSM_TIMESTAMP,
uid: r.OSM_UID
});
r.members.forEach((m) => {
relation.ele('member', {
type: m.type,
ref: m.id,
role: m.role
});
});
for (var k in r.tags) {
relation.ele('tag')
.att('k', k)
.att('v', r.tags[k]);
}
});
callback(xml.end({ pretty: true, indent: ' ' }));
}
}
class Node {
constructor (id, OSM_USER, OSM_TIMESTAMP, OSM_UID, lon, lat, tags) {
this.id = id;
this.OSM_USER = OSM_USER;
this.OSM_TIMESTAMP = OSM_TIMESTAMP;
this.OSM_UID = OSM_UID;
this.lon = lon;
this.lat = lat;
this.tags = tags;
}
addTag (k, v) {
this.tags[k] = v;
}
}
class Way {
constructor (id, OSM_USER, OSM_TIMESTAMP, OSM_UID) {
this.id = id;
this.OSM_USER = OSM_USER;
this.OSM_TIMESTAMP = OSM_TIMESTAMP;
this.OSM_UID = OSM_UID;
this.tags = {};
this.nodes = [];
}
addNode (node) {
this.nodes.push(node);
}
setTags (tags) {
this.tags = tags;
}
}
class Relation {
constructor (id, OSM_USER, OSM_TIMESTAMP, OSM_UID) {
this.id = id;
this.OSM_USER = OSM_USER;
this.OSM_TIMESTAMP = OSM_TIMESTAMP;
this.OSM_UID = OSM_UID;
this.members = [];
this.tags = {};
}
addMember (memberType, id, role) {
this.members.push({type: memberType, id: id, role: role});
}
addTag (k, v) {
this.tags[k] = v;
}
}
module.exports = {
DB: DB,
Node: Node,
Way: Way,
Relation: Relation
};
+169
View File
@@ -0,0 +1,169 @@
'use strict';
const fs = require('fs');
const util = require('util');
const Timeout = require('node-timeout');
const tryConnect = require('../lib/try_connect');
const errorReason = require('./utils').errorReason;
class OSRMBaseLoader{
constructor (scope) {
this.scope = scope;
this.child = null;
}
launch (callback) {
var limit = Timeout(this.scope.TIMEOUT, { err: new Error('*** Launching osrm-routed timed out.') });
var runLaunch = (cb) => {
this.osrmUp(() => { this.waitForConnection(cb); });
};
runLaunch(limit((e) => { if (e) callback(e); else callback(); }));
}
shutdown (callback) {
if (!this.osrmIsRunning()) return callback();
var limit = Timeout(this.scope.TIMEOUT, { err: new Error('*** Shutting down osrm-routed timed out.')});
this.osrmDown(limit(callback));
}
osrmIsRunning () {
return this.child && !this.child.killed;
}
osrmDown (callback) {
if (this.osrmIsRunning()) {
this.child.on('exit', (code, signal) => {callback();});
this.child.kill();
} else callback();
}
waitForConnection (callback) {
var retryCount = 0;
let retry = (err) => {
if (err) {
if (retryCount < 10) {
retryCount++;
setTimeout(() => { tryConnect(this.scope.OSRM_PORT, retry); }, 10);
} else {
callback(new Error("Could not connect to osrm-routed after ten retries."));
}
}
else
{
callback();
}
};
tryConnect(this.scope.OSRM_PORT, retry);
}
};
class OSRMDirectLoader extends OSRMBaseLoader {
constructor (scope) {
super(scope);
}
load (inputFile, callback) {
this.inputFile = inputFile;
this.shutdown(() => {
this.launch(callback);
});
}
osrmUp (callback) {
if (this.osrmIsRunning()) return callback(new Error("osrm-routed already running!"));
this.child = this.scope.runBin('osrm-routed', util.format("%s -p %d", this.inputFile, this.scope.OSRM_PORT), this.scope.environment, (err) => {
if (err) {
throw new Error(util.format('osrm-routed %s: %s', errorReason(err), err.cmd));
}
});
callback();
}
};
class OSRMDatastoreLoader extends OSRMBaseLoader {
constructor (scope) {
super(scope);
}
load (inputFile, callback) {
this.inputFile = inputFile;
this.loadData((err) => {
if (err) return callback(err);
if (!this.osrmIsRunning()) this.launch(callback);
else {
this.scope.setupOutputLog(this.child, fs.createWriteStream(this.scope.scenarioLogFile, {'flags': 'a'}));
callback();
}
});
}
loadData (callback) {
this.scope.runBin('osrm-datastore', this.inputFile, this.scope.environment, (err) => {
if (err) return callback(new Error('*** osrm-datastore exited with ' + err.code + ': ' + err));
callback();
});
}
osrmUp (callback) {
if (this.osrmIsRunning()) return callback();
this.child = this.scope.runBin('osrm-routed', util.format('--shared-memory=1 -p %d', this.scope.OSRM_PORT), this.scope.environment, (err) => {
if (err) {
throw new Error(util.format('osrm-routed %s: %s', errorReason(err), err.cmd));
}
});
// we call the callback here, becuase we don't want to wait for the child process to finish
callback();
}
};
class OSRMLoader {
constructor (scope) {
this.scope = scope;
this.sharedLoader = new OSRMDatastoreLoader(this.scope);
this.directLoader = new OSRMDirectLoader(this.scope);
this.method = scope.DEFAULT_LOAD_METHOD;
}
load (inputFile, callback) {
if (this.method === 'datastore') {
this.directLoader.shutdown((err) => {
if (err) return callback(err);
this.loader = this.sharedLoader;
this.sharedLoader.load(inputFile, callback);
});
} else if (this.method === 'directly') {
this.sharedLoader.shutdown((err) => {
if (err) return callback(err);
this.loader = this.directLoader;
this.directLoader.load(inputFile, callback);
});
} else {
callback(new Error('*** Unknown load method ' + method));
}
}
setLoadMethod (method) {
this.method = method;
}
shutdown (callback) {
if (!this.loader) return callback();
this.loader.shutdown(callback);
}
up () {
return this.loader ? this.loader.osrmIsRunning() : false;
}
};
module.exports = OSRMLoader;
+54
View File
@@ -0,0 +1,54 @@
'use strict';
var util = require('util');
var path = require('path');
var fs = require('fs');
var chalk = require('chalk');
var unescapeStr = (str) => str.replace(/\\\|/g, '\|').replace(/\\\\/g, '\\');
module.exports = function (expected, actual) {
let headers = expected.raw()[0];
let expected_keys = expected.hashes();
let diff = [];
let hasErrors = false;
var good = 0, bad = 0;
expected_keys.forEach((row, i) => {
var rowError = false;
for (var j in row) {
if (unescapeStr(row[j]) != actual[i][j]) {
rowError = true;
hasErrors = true;
break;
}
}
if (rowError) {
bad++;
diff.push(Object.assign({}, row, {c_status: 'undefined'}));
diff.push(Object.assign({}, actual[i], {c_status: 'comment'}));
} else {
good++;
diff.push(row);
}
});
if (!hasErrors) return null;
var s = ['Tables were not identical:'];
s.push(headers.map(key => ' ' + key).join(' | '));
diff.forEach((row) => {
var rowString = '| ';
headers.forEach((header) => {
if (!row.c_status) rowString += chalk.green(' ' + row[header] + ' | ');
else if (row.c_status === 'undefined') rowString += chalk.yellow('(-) ' + row[header] + ' | ');
else rowString += chalk.red('(+) ' + row[header] + ' | ');
});
s.push(rowString);
});
return s.join('\n') + '\nTODO this is a temp workaround waiting for https://github.com/cucumber/cucumber-js/issues/534';
};
+13
View File
@@ -0,0 +1,13 @@
'use strict';
const net = require('net');
const Timeout = require('node-timeout');
module.exports = function tryConnect(port, callback) {
net.connect({ port: port, host: '127.0.0.1' })
.on('connect', () => { callback(); })
.on('error', () => {
callback(new Error('Could not connect.'));
});
}
+17
View File
@@ -0,0 +1,17 @@
'use strict';
const util = require('util');
module.exports = {
ensureDecimal: (i) => {
if (parseInt(i) === i) return i.toFixed(1);
else return i;
},
errorReason: (err) => {
return err.signal ?
util.format('killed by signal %s', err.signal) :
util.format('exited with code %d', err.code);
}
};