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/index.js
CHANGED
|
@@ -1,49 +1,218 @@
|
|
|
1
1
|
// -----------------------------------------------------------------------------
|
|
2
|
-
//
|
|
2
|
+
// Copyright (c) 2018 Pau Sanchez
|
|
3
3
|
//
|
|
4
|
-
//
|
|
4
|
+
// MIT Licensed
|
|
5
5
|
// -----------------------------------------------------------------------------
|
|
6
|
+
const redis = require("redis");
|
|
7
|
+
const crypto = require("crypto");
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const GRANULARITY = {
|
|
10
|
+
TEST: "test",
|
|
11
|
+
SUITE: "suite",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Initialize variables from environment
|
|
15
|
+
const g_redisAddress = process.env.MOCHA_DISTRIBUTED || "";
|
|
16
|
+
const g_testExecutionId = process.env.MOCHA_DISTRIBUTED_EXECUTION_ID || "";
|
|
17
|
+
const g_expirationTime =
|
|
18
|
+
process.env.MOCHA_DISTRIBUTED_EXPIRATION_TIME || `${24 * 3600}`;
|
|
19
|
+
|
|
20
|
+
// Generate a unique random id for this runner (with almost 100% certainty
|
|
21
|
+
// to be different on any machine/environment).
|
|
22
|
+
const _randomRunnerBuf = Buffer.alloc(16);
|
|
23
|
+
const _randomRunnerId = crypto.randomFillSync(_randomRunnerBuf).toString("hex");
|
|
24
|
+
const g_runnerId = process.env.MOCHA_DISTRIBUTED_RUNNER_ID || _randomRunnerId;
|
|
25
|
+
let g_granularity =
|
|
26
|
+
process.env.MOCHA_DISTRIBUTED_GRANULARITY || GRANULARITY.TEST;
|
|
27
|
+
const g_mochaVerbose = process.env.MOCHA_DISTRIBUTED_VERBOSE === "true";
|
|
28
|
+
|
|
29
|
+
if (g_granularity !== GRANULARITY.TEST) {
|
|
30
|
+
g_granularity = GRANULARITY.SUITE;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let g_redis = null;
|
|
34
|
+
|
|
35
|
+
let g_capture = { stdout: null, stderr: null };
|
|
36
|
+
|
|
37
|
+
// -----------------------------------------------------------------------------
|
|
38
|
+
// getTestPath
|
|
12
39
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// name from the master, and will ask, before each suite or orphaned test
|
|
16
|
-
// if it needs to run it or not
|
|
40
|
+
// Returns an array with the test suites and test name from a test context
|
|
41
|
+
// as found in the hooks
|
|
17
42
|
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// Initialize mode & port from environment variable MOCHA_DISTRIBUTED
|
|
26
|
-
const DEFAULT_PORT = 12421;
|
|
27
|
-
let mode = (process.env.MOCHA_DISTRIBUTED || '').toLowerCase();
|
|
28
|
-
let port = DEFAULT_PORT;
|
|
29
|
-
|
|
30
|
-
if (mode.indexOf (':') >= 0) {
|
|
31
|
-
const splitted = mode.split(':');
|
|
32
|
-
mode = splitted[0];
|
|
33
|
-
port = parseInt (splitted[1], 10);
|
|
34
|
-
}
|
|
43
|
+
// Example:
|
|
44
|
+
//
|
|
45
|
+
// >>> getTestPath(ctxt)
|
|
46
|
+
//
|
|
47
|
+
// -----------------------------------------------------------------------------
|
|
48
|
+
function getTestPath(testContext) {
|
|
49
|
+
const path = [testContext.title];
|
|
35
50
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
while (!testContext.root && testContext.parent) {
|
|
52
|
+
testContext = testContext.parent;
|
|
53
|
+
|
|
54
|
+
if (testContext && !testContext.root) {
|
|
55
|
+
path.push(testContext.title);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return path.reverse();
|
|
42
60
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
61
|
+
|
|
62
|
+
// -----------------------------------------------------------------------------
|
|
63
|
+
// captureStream
|
|
64
|
+
// -----------------------------------------------------------------------------
|
|
65
|
+
function captureStream(stream) {
|
|
66
|
+
var oldWrite = stream.write;
|
|
67
|
+
var buf = [];
|
|
68
|
+
|
|
69
|
+
stream.write = function(chunk, encoding, callback){
|
|
70
|
+
buf.push (chunk.toString()); // chunk is a String or Buffer
|
|
71
|
+
oldWrite.apply(stream, arguments);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
unhook(){
|
|
76
|
+
stream.write = oldWrite;
|
|
77
|
+
},
|
|
78
|
+
captured(){
|
|
79
|
+
return buf;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
46
82
|
}
|
|
47
83
|
|
|
48
|
-
//
|
|
49
|
-
|
|
84
|
+
// -----------------------------------------------------------------------------
|
|
85
|
+
// Initialize redis once before the tests
|
|
86
|
+
// -----------------------------------------------------------------------------
|
|
87
|
+
exports.mochaGlobalSetup = async function () {
|
|
88
|
+
if (g_mochaVerbose) {
|
|
89
|
+
const redisNoCredentials = g_redisAddress.replace(
|
|
90
|
+
/\/\/[^@]*@/,
|
|
91
|
+
"//***:***@"
|
|
92
|
+
);
|
|
93
|
+
console.log("---------------------------------------------------");
|
|
94
|
+
console.log(" Mocha Distributed");
|
|
95
|
+
console.log(" - Runner Id :", g_runnerId);
|
|
96
|
+
console.log(" - Redis Address :", redisNoCredentials);
|
|
97
|
+
console.log(" - Execution Id :", g_testExecutionId);
|
|
98
|
+
console.log(" - Data Expiration Time :", g_expirationTime);
|
|
99
|
+
console.log(" - Test Parallel Granularity:", g_granularity);
|
|
100
|
+
console.log("---------------------------------------------------");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!g_redisAddress || !g_testExecutionId) {
|
|
104
|
+
console.log (g_redisAddress, g_testExecutionId)
|
|
105
|
+
console.error(
|
|
106
|
+
"You need to set at least the following environment variables:\n" +
|
|
107
|
+
" - MOCHA_DISTRIBUTED\n" +
|
|
108
|
+
" - MOCHA_DISTRIBUTED_EXECUTION_ID\n"
|
|
109
|
+
);
|
|
110
|
+
process.exit(-1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
g_redis = redis.createClient({ url: g_redisAddress });
|
|
114
|
+
g_redis.on("error", (err) => {
|
|
115
|
+
console.log("Redis Client Error", err);
|
|
116
|
+
console.log("Closing application!");
|
|
117
|
+
process.exit(-1);
|
|
118
|
+
});
|
|
119
|
+
await g_redis.connect();
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// -----------------------------------------------------------------------------
|
|
123
|
+
// Quit from redis
|
|
124
|
+
// -----------------------------------------------------------------------------
|
|
125
|
+
exports.mochaGlobalTeardown = async function () {
|
|
126
|
+
if (g_redis) {
|
|
127
|
+
await g_redis.quit();
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// -----------------------------------------------------------------------------
|
|
132
|
+
// Hook tests
|
|
133
|
+
//
|
|
134
|
+
// Please note that we run skip before each test if the ownership of it has
|
|
135
|
+
// already been defined by another runner.
|
|
136
|
+
// -----------------------------------------------------------------------------
|
|
137
|
+
exports.mochaHooks = {
|
|
138
|
+
beforeEach: async function () {
|
|
139
|
+
const testPath = getTestPath(this.currentTest);
|
|
140
|
+
const testKeyFullPath = `${g_testExecutionId}:${testPath.join(":")}`;
|
|
141
|
+
const testKeySuite = `${g_testExecutionId}:${testPath[0]}`;
|
|
142
|
+
|
|
143
|
+
const testKey =
|
|
144
|
+
g_granularity === GRANULARITY.TEST ? testKeyFullPath : testKeySuite;
|
|
145
|
+
|
|
146
|
+
// Atomically set/get the runner id associated to this test. Only the first
|
|
147
|
+
// runner to get there will set the value to its own runner id.
|
|
148
|
+
const [_, assignedRunnerId] = await g_redis
|
|
149
|
+
.multi()
|
|
150
|
+
.set(testKey, g_runnerId, { EX: g_expirationTime, NX: true })
|
|
151
|
+
.get(testKey)
|
|
152
|
+
.exec();
|
|
153
|
+
|
|
154
|
+
if (assignedRunnerId !== g_runnerId) {
|
|
155
|
+
this.currentTest.title += " (skipped by mocha_distributted)";
|
|
156
|
+
this.skip();
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
g_capture.stdout = captureStream(process.stdout);
|
|
160
|
+
g_capture.stderr = captureStream(process.stderr);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
afterEach(done) {
|
|
164
|
+
const SKIPPED = "pending";
|
|
165
|
+
const FAILED = "failed";
|
|
166
|
+
const PASSED = "passed";
|
|
167
|
+
|
|
168
|
+
let capturedStdout = '';
|
|
169
|
+
let capturedStderr = '';
|
|
170
|
+
if (g_capture.stdout) {
|
|
171
|
+
const stdoutArray = g_capture.stdout.captured();
|
|
172
|
+
capturedStdout = stdoutArray.join('');
|
|
173
|
+
capturedStdout = capturedStdout.replace(/\s*\u001b\[3[12]m[^\n]*\n$/g, '');
|
|
174
|
+
g_capture.stdout.unhook();
|
|
175
|
+
g_capture.stdout = null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (g_capture.stderr) {
|
|
179
|
+
capturedStderr = g_capture.stderr.captured().join('');
|
|
180
|
+
g_capture.stderr.unhook();
|
|
181
|
+
g_capture.stderr = null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Save all data in redis in a way it can be retrieved and aggregated
|
|
185
|
+
// easily for all test by an external reporter
|
|
186
|
+
if (this.currentTest.state !== SKIPPED) {
|
|
187
|
+
const stateFixed = this.currentTest.state || (this.currentTest.timedOut ? FAILED : PASSED)
|
|
188
|
+
const testResult = {
|
|
189
|
+
id: getTestPath(this.currentTest),
|
|
190
|
+
type: this.currentTest.type,
|
|
191
|
+
title: this.currentTest.title,
|
|
192
|
+
timedOut: this.currentTest.timedOut,
|
|
193
|
+
duration: this.currentTest.duration,
|
|
194
|
+
startTime: Date.now() - (this.currentTest.duration || 0),
|
|
195
|
+
endTime: Date.now(),
|
|
196
|
+
file: this.currentTest.file,
|
|
197
|
+
state: stateFixed,
|
|
198
|
+
failed: stateFixed === FAILED,
|
|
199
|
+
speed: this.currentTest.speed,
|
|
200
|
+
err: this.currentTest.err || null,
|
|
201
|
+
stdout: capturedStdout,
|
|
202
|
+
stderr: capturedStderr
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// save results as single line on purpose
|
|
206
|
+
const key = `${g_testExecutionId}:test_result`;
|
|
207
|
+
g_redis.rPush(key, JSON.stringify(testResult));
|
|
208
|
+
g_redis.expire(key, g_expirationTime);
|
|
209
|
+
|
|
210
|
+
// increment passed_count/failed_count & set expiry time
|
|
211
|
+
const countKey = `${g_testExecutionId}:${stateFixed}_count`
|
|
212
|
+
g_redis.incr(countKey);
|
|
213
|
+
g_redis.expire(countKey, g_expirationTime);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
done();
|
|
217
|
+
},
|
|
218
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// -----------------------------------------------------------------------------
|
|
2
|
+
// Copyright (c) 2018 Pau Sanchez
|
|
3
|
+
//
|
|
4
|
+
// MIT Licensed
|
|
5
|
+
// -----------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
const redis = require("redis");
|
|
8
|
+
|
|
9
|
+
async function getTestResults(redisHost = 'localhost', redisPort = 6379) {
|
|
10
|
+
const allResults = []
|
|
11
|
+
try {
|
|
12
|
+
const redisClient = redis.createClient({
|
|
13
|
+
url: `redis://${redisHost}:${redisPort}/`
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
await redisClient.connect()
|
|
17
|
+
|
|
18
|
+
// get all potential test results keys
|
|
19
|
+
const keyResults = await redisClient.keys('*:test_result')
|
|
20
|
+
|
|
21
|
+
// we can get all in parallel
|
|
22
|
+
const parallelResults = []
|
|
23
|
+
for(const key of keyResults) {
|
|
24
|
+
const rawResultList = await redisClient.lRange(key, 0, -1)
|
|
25
|
+
|
|
26
|
+
let passed = 0
|
|
27
|
+
let failed = 0
|
|
28
|
+
let aggregatedDurationMs = 0
|
|
29
|
+
let firstStartTime = null
|
|
30
|
+
let lastEndTime = null
|
|
31
|
+
const jsonResultList = []
|
|
32
|
+
for (const rawResult of rawResultList) {
|
|
33
|
+
const jsonResult = JSON.parse(rawResult)
|
|
34
|
+
jsonResultList.push(jsonResult)
|
|
35
|
+
|
|
36
|
+
// compute some extra stuff
|
|
37
|
+
if (firstStartTime === null || jsonResult.startTime < firstStartTime) {
|
|
38
|
+
firstStartTime = jsonResult.startTime
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (lastEndTime === null || jsonResult.endTime > lastEndTime) {
|
|
42
|
+
lastEndTime = jsonResult.endTime
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
passed += (jsonResult.state === "passed")
|
|
46
|
+
failed += (jsonResult.state === "failed")
|
|
47
|
+
|
|
48
|
+
aggregatedDurationMs += (jsonResult.duration || 0)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// sort all jsonResultList by the full test route, because they will
|
|
52
|
+
// be visualized better
|
|
53
|
+
jsonResultList.sort((a, b) => (a.id.join('/') > b.id.join('/')) ? 1 : -1)
|
|
54
|
+
|
|
55
|
+
allResults.push({
|
|
56
|
+
key: key,
|
|
57
|
+
start_time : firstStartTime || 0,
|
|
58
|
+
end_time : lastEndTime || 0,
|
|
59
|
+
aggregated_duration: aggregatedDurationMs,
|
|
60
|
+
real_duration: (firstStartTime && lastEndTime) ? lastEndTime - firstStartTime : aggregatedDurationMs,
|
|
61
|
+
tests_passed : passed,
|
|
62
|
+
tests_failed : failed,
|
|
63
|
+
test_results : jsonResultList
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// sort results by startTime, most recent first
|
|
68
|
+
allResults.sort((a, b) => (a.start_time < b.start_time) ? 1 : -1)
|
|
69
|
+
|
|
70
|
+
await redisClient.quit()
|
|
71
|
+
return allResults
|
|
72
|
+
}
|
|
73
|
+
catch(e) {
|
|
74
|
+
console.error ("Test Error: ", e)
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
// -----------------------------------------------------------------------------
|
|
81
|
+
// Print results as JSON output
|
|
82
|
+
// -----------------------------------------------------------------------------
|
|
83
|
+
getTestResults().then( (results) => {
|
|
84
|
+
console.log (JSON.stringify(results, null, 2))
|
|
85
|
+
})
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mocha-distributed",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Run multiple mocha suites and tests in parallel, from different processes
|
|
3
|
+
"version": "0.9.3",
|
|
4
|
+
"description": "Run multiple mocha suites and tests in parallel, from different processes and different machines. Results available on a redis database.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"integration tests",
|
|
15
15
|
"pipelines",
|
|
16
16
|
"chai",
|
|
17
|
+
"expect",
|
|
17
18
|
"kubernetes",
|
|
18
19
|
"docker",
|
|
19
20
|
"mocha-parallel-tests",
|
|
@@ -27,10 +28,10 @@
|
|
|
27
28
|
"author": "Pau Sanchez",
|
|
28
29
|
"license": "MIT",
|
|
29
30
|
"dependencies": {
|
|
30
|
-
"
|
|
31
|
+
"redis": "^4.0.2"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"chai": "^4.2.0",
|
|
34
|
-
"mocha": "^
|
|
35
|
+
"mocha": "^9.1.4"
|
|
35
36
|
}
|
|
36
37
|
}
|
package/constants.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// -----------------------------------------------------------------------------
|
|
2
|
-
// constants.js
|
|
3
|
-
//
|
|
4
|
-
// Copyright(c) 2019 Pau Sanchez - MIT License
|
|
5
|
-
// -----------------------------------------------------------------------------
|
|
6
|
-
|
|
7
|
-
const RUNNER_ID_PREFIX = 'runner-';
|
|
8
|
-
|
|
9
|
-
const TEST_STATUS_RUNNING = 'running';
|
|
10
|
-
const TEST_STATUS_SUCCESS = 'success';
|
|
11
|
-
const TEST_STATUS_FAILED = 'failed';
|
|
12
|
-
|
|
13
|
-
const TEST_PATH_SEPARATOR = '>>>';
|
|
14
|
-
|
|
15
|
-
const EVENT_FINISHED = 'finished';
|
|
16
|
-
|
|
17
|
-
const ERRORS = {
|
|
18
|
-
INVALID_REQUEST : 'INVALID_REQUEST',
|
|
19
|
-
INVALID_TEST_ID : 'INVALID_TEST_ID',
|
|
20
|
-
INVALID_REQUEST_ACTION : 'INVALID_REQUEST_ACTION',
|
|
21
|
-
INVALID_RUNNER_OWNERSHIP : 'INVALID_RUNNER_OWNERSHIP',
|
|
22
|
-
ALREADY_RUNNING : 'ALREADY_RUNNING',
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
module.exports = {
|
|
26
|
-
RUNNER_ID_PREFIX,
|
|
27
|
-
TEST_STATUS_RUNNING,
|
|
28
|
-
TEST_STATUS_SUCCESS,
|
|
29
|
-
TEST_STATUS_FAILED,
|
|
30
|
-
TEST_PATH_SEPARATOR,
|
|
31
|
-
EVENT_FINISHED,
|
|
32
|
-
ERRORS
|
|
33
|
-
};
|
package/master-mocha-bindings.js
DELETED
|
@@ -1,159 +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 constants = require('./constants.js');
|
|
12
|
-
|
|
13
|
-
let g_mochaMethods = {
|
|
14
|
-
describe : global.describe || null,
|
|
15
|
-
it : global.it || null,
|
|
16
|
-
before : global.before || null,
|
|
17
|
-
beforeEach : global.beforeEach || null,
|
|
18
|
-
after : global.after || null,
|
|
19
|
-
afterEach : global.afterEach || null
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
// contains the actual test path such as suite.title > suite.title > it.title
|
|
23
|
-
let g_testPath = [];
|
|
24
|
-
let g_testResults = new Map();
|
|
25
|
-
let g_testEventEmitter = null;
|
|
26
|
-
|
|
27
|
-
// -----------------------------------------------------------------------------
|
|
28
|
-
// setEventEmitter
|
|
29
|
-
//
|
|
30
|
-
// To communicate with the mocha bindings
|
|
31
|
-
// -----------------------------------------------------------------------------
|
|
32
|
-
function setEventEmitter (testEventEmitter) {
|
|
33
|
-
g_testEventEmitter = testEventEmitter;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// -----------------------------------------------------------------------------
|
|
37
|
-
// describe
|
|
38
|
-
//
|
|
39
|
-
// Custom version to describe a test, with same signature
|
|
40
|
-
// -----------------------------------------------------------------------------
|
|
41
|
-
async function describe (title, fn) {
|
|
42
|
-
g_testPath.push (title);
|
|
43
|
-
|
|
44
|
-
// TODO: handle timeouts!!
|
|
45
|
-
const result = g_mochaMethods.describe (title, fn);
|
|
46
|
-
g_testPath.pop();
|
|
47
|
-
|
|
48
|
-
return result;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// TODO:
|
|
52
|
-
// describe.skip = async function (title, fn) { }
|
|
53
|
-
// describe.once = async function (title, fn) { }
|
|
54
|
-
|
|
55
|
-
// -----------------------------------------------------------------------------
|
|
56
|
-
// it
|
|
57
|
-
// -----------------------------------------------------------------------------
|
|
58
|
-
async function it (title, fn) {
|
|
59
|
-
// store current path for when the 'it' function executes
|
|
60
|
-
g_testPath.push (title);
|
|
61
|
-
const testPath = g_testPath.slice(0);
|
|
62
|
-
const testId = testPath.join(constants.TEST_PATH_SEPARATOR);
|
|
63
|
-
const listenerId = constants.EVENT_FINISHED + ':' + testId;
|
|
64
|
-
g_testPath.pop();
|
|
65
|
-
|
|
66
|
-
// TODO: manage timeouts
|
|
67
|
-
|
|
68
|
-
// Two listeners are created, once beforehand, and other in runtime.
|
|
69
|
-
//
|
|
70
|
-
// Runtime one will wait the master to receive the status... but since the
|
|
71
|
-
// master node runs serially, it can also happen that another runner executes
|
|
72
|
-
// the tests before the master can initialize the second event listener, and
|
|
73
|
-
// we use this one to capture the result beforehand.
|
|
74
|
-
g_testEventEmitter.once (
|
|
75
|
-
listenerId,
|
|
76
|
-
function (test) {
|
|
77
|
-
g_testResults.set (test.id, test);
|
|
78
|
-
}
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
// define our own function hook to wait until the test finishes
|
|
82
|
-
return g_mochaMethods.it (title, async function () {
|
|
83
|
-
await new Promise(function (resolve, reject) {
|
|
84
|
-
// helper function to avoid repeating the code twice
|
|
85
|
-
function helperProcessTestResult(test) {
|
|
86
|
-
if (test.status === constants.TEST_STATUS_FAILED) {
|
|
87
|
-
reject (test.error);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
resolve();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (g_testResults.has(testId)) {
|
|
95
|
-
const test = g_testResults.get (testId);
|
|
96
|
-
helperProcessTestResult (test);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// first event-listener has not triggered, so let's install another
|
|
101
|
-
// event listener to resolve as quickly as possible
|
|
102
|
-
g_testEventEmitter.once (listenerId, helperProcessTestResult);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// -----------------------------------------------------------------------------
|
|
108
|
-
// execHookMochaMethod
|
|
109
|
-
//
|
|
110
|
-
// Method used accross all mocha hooks (before, beforeEach, after, afterEach)
|
|
111
|
-
// in order to query the master server if it needs to be executed or not.
|
|
112
|
-
// -----------------------------------------------------------------------------
|
|
113
|
-
function execHookMochaMethod (title, orgMochaMethod, fn) {
|
|
114
|
-
g_testPath.push (title);
|
|
115
|
-
const testPath = g_testPath.slice(0);
|
|
116
|
-
g_testPath.pop();
|
|
117
|
-
|
|
118
|
-
return orgMochaMethod (async function () {
|
|
119
|
-
// TODO: wait
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// -----------------------------------------------------------------------------
|
|
124
|
-
// before
|
|
125
|
-
// -----------------------------------------------------------------------------
|
|
126
|
-
async function before (fn) {
|
|
127
|
-
return execHookMochaMethod (':before', g_mochaMethods.before, fn);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// -----------------------------------------------------------------------------
|
|
131
|
-
// beforeEach
|
|
132
|
-
// -----------------------------------------------------------------------------
|
|
133
|
-
async function beforeEach (fn) {
|
|
134
|
-
return execHookMochaMethod (':beforeEach', g_mochaMethods.beforeEach, fn);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// -----------------------------------------------------------------------------
|
|
138
|
-
// after
|
|
139
|
-
// -----------------------------------------------------------------------------
|
|
140
|
-
async function after (fn) {
|
|
141
|
-
return execHookMochaMethod (':after', g_mochaMethods.after, fn);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// -----------------------------------------------------------------------------
|
|
145
|
-
// afterEach
|
|
146
|
-
// -----------------------------------------------------------------------------
|
|
147
|
-
async function afterEach (fn) {
|
|
148
|
-
return execHookMochaMethod (':afterEach', g_mochaMethods.after, fn);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
module.exports = {
|
|
152
|
-
setEventEmitter,
|
|
153
|
-
describe,
|
|
154
|
-
it,
|
|
155
|
-
before,
|
|
156
|
-
beforeEach,
|
|
157
|
-
after,
|
|
158
|
-
afterEach
|
|
159
|
-
};
|