mocha-distributed 0.9.1 → 0.9.5

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 CHANGED
@@ -148,16 +148,29 @@ You will see something like this on each of the items of the list:
148
148
  "type": "test",
149
149
  "title": "test-1.1-async",
150
150
  "timedOut": false,
151
+ "startTime": 1642705594300,
152
+ "endTime": 1642705594802,
151
153
  "duration": 502,
152
154
  "file": "/home/psanchez/github/mocha-distributed/example/suite-1.js",
153
155
  "state": "passed",
156
+ "failed": false,
154
157
  "speed": "slow",
155
158
  "err": 0
156
- }```
159
+ }
160
+ ```
157
161
 
158
162
  The JSON formatting will differ since it is saved in a single line.
159
163
 
160
- Apart of test_result you can also access passed_count and failed_count
164
+ Keep in mind that:
165
+
166
+ * Duration and start/end times are in milliseconds.
167
+ * Some fields are duplicated in a way, like "state" and "failed" by design
168
+ because sometimes is handy to have this when reading results back.
169
+ * You can access test_result, passed_count and failed_count in redis
170
+ * Skipped tests are never saved in redis by design, unfortunately
171
+
172
+ You might have a look at list-tests-from-redis.js for an example on how to
173
+ query redis and list all tests.
161
174
 
162
175
  ## Examples
163
176
 
@@ -243,9 +256,9 @@ or have been skipped will return 0.
243
256
  If you use jenkins, bamboo or any other build system, make sure
244
257
  one redis is installed somewhere and all runners can access to it.
245
258
 
246
- You can create as many processes processes or launch as docker or kubernetes
247
- instances as you wish, but for each of the runners that you create, make sure
248
- they have visibility to the redis.
259
+ Create as many processes, nodes, dockers, kubernetes pods as you wish,
260
+ but for each of the runners that you create, make sure each of them can connect
261
+ to the redis instance (e.g are in the same network).
249
262
 
250
263
  You can use the project name and build ID or job id as the execution ID for
251
264
  mocha-distributed. Use something unique among the builds of all your projects.
@@ -24,7 +24,7 @@ describe ('suite-1-async', async function () {
24
24
 
25
25
  describe ('suite-1-sync', function () {
26
26
  it ('test-1.1-async', async function () {
27
- await util.sleep(0.5);
27
+ await util.sleep(1.5);
28
28
  })
29
29
 
30
30
  it ('test-1.2-sync', function () {
@@ -39,3 +39,27 @@ describe ('suite-1.2-sync', function () {
39
39
  });
40
40
  });
41
41
  });
42
+
43
+ describe ('suite-1.3-io', function () {
44
+ it ('console.log', function () {
45
+ console.log ("Writing from console.log\nAnother line")
46
+ });
47
+
48
+ it ('console.error', function () {
49
+ console.error ("Writing from console.error\nAnother line")
50
+ });
51
+
52
+ it ('process.stdout', function () {
53
+ process.stdout.write("Writing from process.stdout. No newline.")
54
+ });
55
+
56
+ it ('process.stderr', function () {
57
+ process.stderr.write("Writing from process.stderr. No newline.")
58
+ });
59
+
60
+ it ('process.stdout & process.stderr', function () {
61
+ process.stdout.write("stdout output\nanother line")
62
+ process.stderr.write("stderr output\nanother line\nand yet another one.")
63
+ });
64
+
65
+ });
package/index.js CHANGED
@@ -1,3 +1,8 @@
1
+ // -----------------------------------------------------------------------------
2
+ // Copyright (c) 2018 Pau Sanchez
3
+ //
4
+ // MIT Licensed
5
+ // -----------------------------------------------------------------------------
1
6
  const redis = require("redis");
2
7
  const crypto = require("crypto");
3
8
 
@@ -27,6 +32,8 @@ if (g_granularity !== GRANULARITY.TEST) {
27
32
 
28
33
  let g_redis = null;
29
34
 
35
+ let g_capture = { stdout: null, stderr: null };
36
+
30
37
  // -----------------------------------------------------------------------------
31
38
  // getTestPath
32
39
  //
@@ -52,6 +59,28 @@ function getTestPath(testContext) {
52
59
  return path.reverse();
53
60
  }
54
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
+ };
82
+ }
83
+
55
84
  // -----------------------------------------------------------------------------
56
85
  // Initialize redis once before the tests
57
86
  // -----------------------------------------------------------------------------
@@ -72,7 +101,7 @@ exports.mochaGlobalSetup = async function () {
72
101
  }
73
102
 
74
103
  if (!g_redisAddress || !g_testExecutionId) {
75
- console.log (g_redisAddress, g_testExecutionId)
104
+ console.log(g_redisAddress, g_testExecutionId);
76
105
  console.error(
77
106
  "You need to set at least the following environment variables:\n" +
78
107
  " - MOCHA_DISTRIBUTED\n" +
@@ -125,31 +154,89 @@ exports.mochaHooks = {
125
154
  if (assignedRunnerId !== g_runnerId) {
126
155
  this.currentTest.title += " (skipped by mocha_distributted)";
127
156
  this.skip();
157
+ } else {
158
+ g_capture.stdout = captureStream(process.stdout);
159
+ g_capture.stderr = captureStream(process.stderr);
128
160
  }
129
161
  },
162
+
130
163
  afterEach(done) {
131
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(
174
+ /\s*\u001b\[3[12]m[^\n]*\n$/g,
175
+ ""
176
+ );
177
+ g_capture.stdout.unhook();
178
+ g_capture.stdout = null;
179
+ }
180
+
181
+ if (g_capture.stderr) {
182
+ capturedStderr = g_capture.stderr.captured().join("");
183
+ g_capture.stderr.unhook();
184
+ g_capture.stderr = null;
185
+ }
132
186
 
133
187
  // Save all data in redis in a way it can be retrieved and aggregated
134
188
  // easily for all test by an external reporter
135
189
  if (this.currentTest.state !== SKIPPED) {
190
+ const retryAttempt = this.currentTest._currentRetry || 0;
191
+ const retryTotal = this.currentTest._retries || 1;
192
+
193
+ // adjust state value accounting for exceptions, timeouts & retries
194
+ let stateFixed = PASSED;
195
+ if (
196
+ this.currentTest.state === FAILED ||
197
+ this.currentTest.timedOut ||
198
+ (typeof this.currentTest.state === "undefined" &&
199
+ retryAttempt < retryTotal)
200
+ ) {
201
+ stateFixed = FAILED;
202
+ }
203
+
204
+ // Error objects cannot be properly serialized with stringify, thus
205
+ // we need to use this hack to make it look like a normal object.
206
+ // Hopefully this should work as well with other sort of objects
207
+ const err = this.currentTest.err || null;
208
+ const errObj = JSON.parse(
209
+ JSON.stringify(err, Object.getOwnPropertyNames(err || {}))
210
+ );
211
+
136
212
  const testResult = {
137
213
  id: getTestPath(this.currentTest),
138
214
  type: this.currentTest.type,
139
215
  title: this.currentTest.title,
140
216
  timedOut: this.currentTest.timedOut,
141
217
  duration: this.currentTest.duration,
218
+ startTime: Date.now() - (this.currentTest.duration || 0),
219
+ endTime: Date.now(),
220
+ retryAttempt: retryAttempt,
221
+ retryTotal: retryTotal,
142
222
  file: this.currentTest.file,
143
- state: this.currentTest.state,
223
+ state: stateFixed,
224
+ failed: stateFixed === FAILED,
144
225
  speed: this.currentTest.speed,
145
- err: this.currentTest.err | null,
226
+ err: errObj,
227
+ stdout: capturedStdout,
228
+ stderr: capturedStderr,
146
229
  };
147
230
 
148
- // save as single line on purpose
231
+ // save results as single line on purpose
149
232
  const key = `${g_testExecutionId}:test_result`;
150
233
  g_redis.rPush(key, JSON.stringify(testResult));
151
234
  g_redis.expire(key, g_expirationTime);
152
- g_redis.incr(`${g_testExecutionId}:${this.currentTest.state}_count`);
235
+
236
+ // increment passed_count/failed_count & set expiry time
237
+ const countKey = `${g_testExecutionId}:${stateFixed}_count`;
238
+ g_redis.incr(countKey);
239
+ g_redis.expire(countKey, g_expirationTime);
153
240
  }
154
241
 
155
242
  done();
@@ -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.9.1",
4
- "description": "Run multiple mocha suites and tests in parallel, from different processes or different machines",
3
+ "version": "0.9.5",
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
- "redis": "^4.0.1"
31
+ "redis": "^4.0.3"
31
32
  },
32
33
  "devDependencies": {
33
34
  "chai": "^4.2.0",
34
- "mocha": "^9.1.3"
35
+ "mocha": "^9.1.4"
35
36
  }
36
37
  }