mocha-distributed 0.8.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +199 -92
- package/docker-compose.yml +18 -0
- package/example/suite-1.js +25 -1
- package/index.js +208 -39
- package/list-tests-from-redis.js +85 -0
- package/package.json +5 -4
- package/constants.js +0 -33
- package/master-mocha-bindings.js +0 -159
- package/master-server.js +0 -194
- package/master.js +0 -47
- package/runner-mocha-bindings.js +0 -212
- package/runner.js +0 -20
package/master-server.js
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
// -----------------------------------------------------------------------------
|
|
2
|
-
// master-mocha-bindings.js
|
|
3
|
-
//
|
|
4
|
-
// Copyright(c) 2019 Pau Sanchez - MIT License
|
|
5
|
-
// -----------------------------------------------------------------------------
|
|
6
|
-
const url = require('url');
|
|
7
|
-
const querystring = require('querystring');
|
|
8
|
-
const constants = require('./constants.js');
|
|
9
|
-
|
|
10
|
-
let g_testsPerRunner = new Map();
|
|
11
|
-
let g_testEventEmitter = null;
|
|
12
|
-
|
|
13
|
-
// -----------------------------------------------------------------------------
|
|
14
|
-
// setEventEmitter
|
|
15
|
-
//
|
|
16
|
-
// To communicate with the mocha bindings
|
|
17
|
-
// -----------------------------------------------------------------------------
|
|
18
|
-
function setEventEmitter (testEventEmitter) {
|
|
19
|
-
g_testEventEmitter = testEventEmitter;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// -----------------------------------------------------------------------------
|
|
23
|
-
// handleRunnerShouldRun
|
|
24
|
-
//
|
|
25
|
-
// REST: /runner/<runner-id>/should-run?test=xxxxx
|
|
26
|
-
//
|
|
27
|
-
// Asks whether the client should run given test
|
|
28
|
-
// -----------------------------------------------------------------------------
|
|
29
|
-
function handleRunnerShouldRun (req, res) {
|
|
30
|
-
const queryObj = querystring.parse(req.route.query);
|
|
31
|
-
const splitted = req.route.pathname.split('/');
|
|
32
|
-
const runnerId = splitted[2];
|
|
33
|
-
const action = splitted[3];
|
|
34
|
-
|
|
35
|
-
const testId = queryObj.test || null;
|
|
36
|
-
|
|
37
|
-
// is this test already assigned to somebody else?
|
|
38
|
-
if (!testId) {
|
|
39
|
-
res.write (
|
|
40
|
-
JSON.stringify ({
|
|
41
|
-
answer : 'skip',
|
|
42
|
-
reason : constants.ERRORS.INVALID_TEST_ID
|
|
43
|
-
})
|
|
44
|
-
)
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (g_testsPerRunner.has(testId)) {
|
|
49
|
-
const runner = g_testsPerRunner.get (testId).runner;
|
|
50
|
-
|
|
51
|
-
// is it a retry? e.g. same runner...
|
|
52
|
-
if (runnerId === runner) {
|
|
53
|
-
g_testsPerRunner.get (testId).retries ++;
|
|
54
|
-
res.write (JSON.stringify ({ answer : 'run' }));
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// otherwise, it is already running :)
|
|
59
|
-
res.write (
|
|
60
|
-
JSON.stringify ({
|
|
61
|
-
answer : 'skip',
|
|
62
|
-
reason : constants.ERRORS.ALREADY_RUNNING,
|
|
63
|
-
runner : runner
|
|
64
|
-
})
|
|
65
|
-
);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// make this runner the owner of running this test
|
|
70
|
-
g_testsPerRunner.set (testId, {
|
|
71
|
-
id : testId,
|
|
72
|
-
runner : runnerId,
|
|
73
|
-
status : constants.TEST_STATUS_RUNNING,
|
|
74
|
-
retries: 0,
|
|
75
|
-
start : Date.now(),
|
|
76
|
-
end : false,
|
|
77
|
-
error : null
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
res.write (
|
|
81
|
-
JSON.stringify ({
|
|
82
|
-
answer : 'run'
|
|
83
|
-
})
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// -----------------------------------------------------------------------------
|
|
88
|
-
// handleRunnerResult
|
|
89
|
-
//
|
|
90
|
-
// REST: /runner/<runner-id>/result?test=xxxxx&status=success|error
|
|
91
|
-
//
|
|
92
|
-
// Informs the result of running given test
|
|
93
|
-
// -----------------------------------------------------------------------------
|
|
94
|
-
function handleRunnerResult (req, res) {
|
|
95
|
-
const queryObj = querystring.parse(req.route.query);
|
|
96
|
-
const splitted = req.route.pathname.split('/');
|
|
97
|
-
const runnerId = splitted[2];
|
|
98
|
-
const action = splitted[3];
|
|
99
|
-
|
|
100
|
-
const testId = queryObj.test || null;
|
|
101
|
-
if (!testId || !g_testsPerRunner.has(testId)) {
|
|
102
|
-
res.write (
|
|
103
|
-
JSON.stringify ({
|
|
104
|
-
error : constants.ERRORS.INVALID_TEST_ID
|
|
105
|
-
})
|
|
106
|
-
)
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const test = g_testsPerRunner.get (testId);
|
|
111
|
-
if (test.runner !== runnerId) {
|
|
112
|
-
res.write (
|
|
113
|
-
JSON.stringify ({
|
|
114
|
-
error : constants.ERRORS.INVALID_RUNNER_OWNERSHIP
|
|
115
|
-
})
|
|
116
|
-
)
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
let status = queryObj.status || constants.TEST_STATUS_FAILED;
|
|
121
|
-
if (status !== constants.TEST_STATUS_SUCCESS) {
|
|
122
|
-
status = constants.TEST_STATUS_FAILED;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
test.status = status;
|
|
126
|
-
test.error = JSON.parse(querystring.unescape (queryObj.error || 'null'));
|
|
127
|
-
|
|
128
|
-
res.write ( JSON.stringify ({ 'status' : status }) );
|
|
129
|
-
|
|
130
|
-
// Emit an event-finished for given test ID, we use the ID because that
|
|
131
|
-
// should be unique.
|
|
132
|
-
g_testEventEmitter.emit (constants.EVENT_FINISHED + ':' + testId, test);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// -----------------------------------------------------------------------------
|
|
136
|
-
// handleRunners
|
|
137
|
-
//
|
|
138
|
-
// REST: /runner/<runner-id>/*
|
|
139
|
-
//
|
|
140
|
-
// Handles all requests from all runners (registration, queries, ...)
|
|
141
|
-
// -----------------------------------------------------------------------------
|
|
142
|
-
function handleRunners (req, res) {
|
|
143
|
-
const queryObj = querystring.parse(req.route.query);
|
|
144
|
-
const splitted = req.route.pathname.split('/');
|
|
145
|
-
const runnerId = splitted[2];
|
|
146
|
-
const action = splitted[3];
|
|
147
|
-
|
|
148
|
-
// TODO: check that runner is registered
|
|
149
|
-
if (!runnerId || !action) {
|
|
150
|
-
res.end();
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
switch (action) {
|
|
155
|
-
case 'should-run': handleRunnerShouldRun (req, res); break;
|
|
156
|
-
case 'result': handleRunnerResult(req, res); break;
|
|
157
|
-
default:
|
|
158
|
-
console.error ("Invalid action: " + action + "(" + req.url + ")");
|
|
159
|
-
res.write (
|
|
160
|
-
JSON.stringify ({ error : constants.ERRORS.INVALID_REQUEST_ACTION })
|
|
161
|
-
)
|
|
162
|
-
break;
|
|
163
|
-
}
|
|
164
|
-
res.end();
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// -----------------------------------------------------------------------------
|
|
168
|
-
// mainServerHandler
|
|
169
|
-
//
|
|
170
|
-
// REST: /*
|
|
171
|
-
//
|
|
172
|
-
// Handles all requests to this master server
|
|
173
|
-
// -----------------------------------------------------------------------------
|
|
174
|
-
function mainServerHandler (req, res) {
|
|
175
|
-
res.writeHead(200, {'Content-Type': 'application/javascript'});
|
|
176
|
-
req.route = url.parse(req.url);
|
|
177
|
-
|
|
178
|
-
// REST: /runner/<runner-id>/*
|
|
179
|
-
//
|
|
180
|
-
// execute actions from given runner
|
|
181
|
-
if (req.route.pathname.startsWith (`/runner/`)) {
|
|
182
|
-
handleRunners (req, res);
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
res.write(JSON.stringify ({ error : constants.ERRORS.INVALID_REQUEST }));
|
|
186
|
-
res.end();
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
module.exports = {
|
|
192
|
-
setEventEmitter,
|
|
193
|
-
mainServerHandler
|
|
194
|
-
};
|
package/master.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// -----------------------------------------------------------------------------
|
|
2
|
-
// master.js
|
|
3
|
-
//
|
|
4
|
-
// Copyright(c) 2019 Pau Sanchez - MIT License
|
|
5
|
-
// -----------------------------------------------------------------------------
|
|
6
|
-
const http = require('http');
|
|
7
|
-
const EventEmitter = require('events');
|
|
8
|
-
const masterServer = require ('./master-server.js');
|
|
9
|
-
const masterMochaBindings = require ('./master-mocha-bindings.js');
|
|
10
|
-
|
|
11
|
-
// used to notify master that a test has been executed
|
|
12
|
-
class TestEmitter extends EventEmitter {};
|
|
13
|
-
|
|
14
|
-
let g_server = null;
|
|
15
|
-
let g_eventEmitter = null;
|
|
16
|
-
|
|
17
|
-
// -----------------------------------------------------------------------------
|
|
18
|
-
// master
|
|
19
|
-
//
|
|
20
|
-
// Initializes the master server, redefines variables, etc...
|
|
21
|
-
// -----------------------------------------------------------------------------
|
|
22
|
-
function master (port) {
|
|
23
|
-
|
|
24
|
-
// Checking server because this master function might be called once
|
|
25
|
-
// per every mocha file, and we only want to initialize once.
|
|
26
|
-
if (g_server === null) {
|
|
27
|
-
g_eventEmitter = new TestEmitter();
|
|
28
|
-
|
|
29
|
-
masterServer.setEventEmitter(g_eventEmitter);
|
|
30
|
-
masterMochaBindings.setEventEmitter (g_eventEmitter);
|
|
31
|
-
|
|
32
|
-
g_server = http.createServer(masterServer.mainServerHandler);
|
|
33
|
-
g_server.listen(port, function() {
|
|
34
|
-
console.log("# Mocha distributed master listening at port:", port, "\n");
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// hook all mocha methods
|
|
39
|
-
global.describe = masterMochaBindings.describe;
|
|
40
|
-
global.it = masterMochaBindings.it;
|
|
41
|
-
global.before = masterMochaBindings.before;
|
|
42
|
-
global.beforeEach = masterMochaBindings.beforeEach;
|
|
43
|
-
global.after = masterMochaBindings.after;
|
|
44
|
-
global.afterEach = masterMochaBindings.afterEach;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
module.exports = master;
|
package/runner-mocha-bindings.js
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
// -----------------------------------------------------------------------------
|
|
2
|
-
// runner-mocha-bindings.js
|
|
3
|
-
//
|
|
4
|
-
// NOTE: mocha first does a first pass executing all describe/it/... and then
|
|
5
|
-
// when it has gathered all information, it runs the functions associated
|
|
6
|
-
// to each. What we do is we hook our method in the middle and call
|
|
7
|
-
// or not, the original test method.
|
|
8
|
-
//
|
|
9
|
-
// Copyright(c) 2019 Pau Sanchez - MIT License
|
|
10
|
-
// -----------------------------------------------------------------------------
|
|
11
|
-
const axios = require('axios');
|
|
12
|
-
const querystring = require('querystring');
|
|
13
|
-
const crypto = require('crypto');
|
|
14
|
-
|
|
15
|
-
const constants = require('./constants.js');
|
|
16
|
-
|
|
17
|
-
let g_masterAddress = null;
|
|
18
|
-
|
|
19
|
-
// Generate a unique random id for this runner (with almost 100% certainty
|
|
20
|
-
// to be different on any machine/environment).
|
|
21
|
-
const g_runnerIdBuffer = Buffer.alloc(16);
|
|
22
|
-
const g_runnerId = crypto.randomFillSync(g_runnerIdBuffer).toString('hex');
|
|
23
|
-
|
|
24
|
-
let g_mochaMethods = {
|
|
25
|
-
describe : global.describe || null,
|
|
26
|
-
it : global.it || null,
|
|
27
|
-
before : global.before || null,
|
|
28
|
-
beforeEach : global.beforeEach || null,
|
|
29
|
-
after : global.after || null,
|
|
30
|
-
afterEach : global.afterEach || null
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// contains the actual test path such as suite.title > suite.title > it.title
|
|
35
|
-
let g_testPath = [];
|
|
36
|
-
|
|
37
|
-
// -----------------------------------------------------------------------------
|
|
38
|
-
// askMasterIfShouldRun
|
|
39
|
-
//
|
|
40
|
-
// Asks master server if the current runner should run given suite.
|
|
41
|
-
//
|
|
42
|
-
// Right now we only ask for suites because it seems the safest approach.
|
|
43
|
-
//
|
|
44
|
-
// Returns:
|
|
45
|
-
// - true: the caller should run the test
|
|
46
|
-
// - false: the caller should not run the test
|
|
47
|
-
// - null: connectivity issue with the master, it depends on the caller to
|
|
48
|
-
// decide what to do
|
|
49
|
-
// -----------------------------------------------------------------------------
|
|
50
|
-
async function askMasterIfShouldRun (testPath) {
|
|
51
|
-
const testSuite = querystring.escape(testPath[0]);
|
|
52
|
-
try {
|
|
53
|
-
const r = await axios.get (`http://${g_masterAddress}/runner/${g_runnerId}/should-run?test=${testSuite}`);
|
|
54
|
-
const ok = (r.data.answer === 'run');
|
|
55
|
-
|
|
56
|
-
// we ask for given test, so we can save the result properly
|
|
57
|
-
// TODO: this can be removed if the server accepts runners
|
|
58
|
-
if (ok) {
|
|
59
|
-
const path = querystring.escape(testPath.join(constants.TEST_PATH_SEPARATOR));
|
|
60
|
-
const r = await axios.get (`http://${g_masterAddress}/runner/${g_runnerId}/should-run?test=${path}`);
|
|
61
|
-
}
|
|
62
|
-
return ok;
|
|
63
|
-
}
|
|
64
|
-
catch (e) {
|
|
65
|
-
// ignore connection errors
|
|
66
|
-
// the master could have died because it already finished
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// skip (should not run) if there is any kind of error with master
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// -----------------------------------------------------------------------------
|
|
74
|
-
// sendTestResultToMaster
|
|
75
|
-
// -----------------------------------------------------------------------------
|
|
76
|
-
async function sendTestResultToMaster (testPath, status, error = null) {
|
|
77
|
-
// TODO: use POST
|
|
78
|
-
const path = querystring.escape(testPath.join(constants.TEST_PATH_SEPARATOR));
|
|
79
|
-
let serror = querystring.escape(JSON.stringify(error || null));
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
const r = await axios.get (
|
|
83
|
-
`http://${g_masterAddress}/runner/${g_runnerId}/result?test=${path}&status=${status}&error=${serror}`
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
catch (e) {
|
|
87
|
-
// ignore connection errors
|
|
88
|
-
// the master could have died because it already finished
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// -----------------------------------------------------------------------------
|
|
93
|
-
// init
|
|
94
|
-
//
|
|
95
|
-
// Initialize this module
|
|
96
|
-
// -----------------------------------------------------------------------------
|
|
97
|
-
function init (masterAddress){
|
|
98
|
-
g_masterAddress = masterAddress;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// -----------------------------------------------------------------------------
|
|
102
|
-
// describe
|
|
103
|
-
//
|
|
104
|
-
// Custom version to describe a test, with same signature
|
|
105
|
-
// -----------------------------------------------------------------------------
|
|
106
|
-
async function describe (title, fn) {
|
|
107
|
-
g_testPath.push (title);
|
|
108
|
-
const result = g_mochaMethods.describe (title, fn);
|
|
109
|
-
g_testPath.pop();
|
|
110
|
-
|
|
111
|
-
return result;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// TODO:
|
|
115
|
-
// describe.skip = async function (title, fn) { }
|
|
116
|
-
// describe.once = async function (title, fn) { }
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// -----------------------------------------------------------------------------
|
|
120
|
-
// it
|
|
121
|
-
// -----------------------------------------------------------------------------
|
|
122
|
-
async function it (title, fn) {
|
|
123
|
-
// store current path for when the 'it' function executes
|
|
124
|
-
g_testPath.push (title);
|
|
125
|
-
const testPath = g_testPath.slice(0);
|
|
126
|
-
g_testPath.pop();
|
|
127
|
-
|
|
128
|
-
// define our own function hook
|
|
129
|
-
return g_mochaMethods.it (title, async function () {
|
|
130
|
-
if (!await askMasterIfShouldRun (testPath)) {
|
|
131
|
-
this.skip();
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
const testResult = await fn();
|
|
137
|
-
await sendTestResultToMaster (testPath, constants.TEST_STATUS_SUCCESS);
|
|
138
|
-
return testResult;
|
|
139
|
-
}
|
|
140
|
-
catch (e) {
|
|
141
|
-
await sendTestResultToMaster (testPath, constants.TEST_STATUS_FAILED, e);
|
|
142
|
-
throw e;
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// -----------------------------------------------------------------------------
|
|
148
|
-
// execHookMochaMethod
|
|
149
|
-
//
|
|
150
|
-
// Method used accross all mocha hooks (before, beforeEach, after, afterEach)
|
|
151
|
-
// in order to query the master server if it needs to be executed or not.
|
|
152
|
-
// -----------------------------------------------------------------------------
|
|
153
|
-
function execHookMochaMethod (title, orgMochaMethod, fn) {
|
|
154
|
-
g_testPath.push (title);
|
|
155
|
-
const testPath = g_testPath.slice(0);
|
|
156
|
-
g_testPath.pop();
|
|
157
|
-
|
|
158
|
-
return orgMochaMethod (async function () {
|
|
159
|
-
if (!await askMasterIfShouldRun (testPath)) {
|
|
160
|
-
this.skip();
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
const testResult = await fn();
|
|
166
|
-
await sendTestResultToMaster (testPath, constants.TEST_STATUS_SUCCESS);
|
|
167
|
-
return testResult;
|
|
168
|
-
}
|
|
169
|
-
catch (e) {
|
|
170
|
-
await sendTestResultToMaster (testPath, constants.TEST_STATUS_FAILED, e);
|
|
171
|
-
throw e;
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// -----------------------------------------------------------------------------
|
|
177
|
-
// before
|
|
178
|
-
// -----------------------------------------------------------------------------
|
|
179
|
-
async function before (fn) {
|
|
180
|
-
return execHookMochaMethod (':before', g_mochaMethods.before, fn);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// -----------------------------------------------------------------------------
|
|
184
|
-
// beforeEach
|
|
185
|
-
// -----------------------------------------------------------------------------
|
|
186
|
-
async function beforeEach (fn) {
|
|
187
|
-
return execHookMochaMethod (':beforeEach', g_mochaMethods.beforeEach, fn);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// -----------------------------------------------------------------------------
|
|
191
|
-
// after
|
|
192
|
-
// -----------------------------------------------------------------------------
|
|
193
|
-
async function after (fn) {
|
|
194
|
-
return execHookMochaMethod (':after', g_mochaMethods.after, fn);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// -----------------------------------------------------------------------------
|
|
198
|
-
// afterEach
|
|
199
|
-
// -----------------------------------------------------------------------------
|
|
200
|
-
async function afterEach (fn) {
|
|
201
|
-
return execHookMochaMethod (':afterEach', g_mochaMethods.after, fn);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
module.exports = {
|
|
205
|
-
init,
|
|
206
|
-
describe,
|
|
207
|
-
it,
|
|
208
|
-
before,
|
|
209
|
-
beforeEach,
|
|
210
|
-
after,
|
|
211
|
-
afterEach
|
|
212
|
-
};
|
package/runner.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
// -----------------------------------------------------------------------------
|
|
2
|
-
// runner.js
|
|
3
|
-
//
|
|
4
|
-
// Copyright(c) 2019 Pau Sanchez - MIT License
|
|
5
|
-
// -----------------------------------------------------------------------------
|
|
6
|
-
const runnerMochaBindings = require ('./runner-mocha-bindings.js');
|
|
7
|
-
|
|
8
|
-
function runner (masterAddress, port) {
|
|
9
|
-
runnerMochaBindings.init (masterAddress + ':' + port);
|
|
10
|
-
|
|
11
|
-
// hook all mocha methods
|
|
12
|
-
global.describe = runnerMochaBindings.describe;
|
|
13
|
-
global.it = runnerMochaBindings.it;
|
|
14
|
-
global.before = runnerMochaBindings.before;
|
|
15
|
-
global.beforeEach = runnerMochaBindings.beforeEach;
|
|
16
|
-
global.after = runnerMochaBindings.after;
|
|
17
|
-
global.afterEach = runnerMochaBindings.afterEach;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
module.exports = runner;
|