monocart-reporter 2.9.6 → 2.9.7
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/LICENSE +21 -21
- package/README.md +1180 -1180
- package/lib/cli.js +372 -372
- package/lib/common.js +244 -244
- package/lib/default/columns.js +79 -79
- package/lib/default/options.js +95 -95
- package/lib/default/summary.js +80 -80
- package/lib/default/template.html +47 -47
- package/lib/generate-data.js +174 -174
- package/lib/generate-report.js +360 -360
- package/lib/index.d.ts +268 -268
- package/lib/index.js +253 -253
- package/lib/index.mjs +19 -19
- package/lib/merge-data.js +405 -405
- package/lib/packages/monocart-reporter-assets.js +3 -3
- package/lib/platform/concurrency.js +74 -74
- package/lib/platform/share.js +369 -369
- package/lib/plugins/audit/audit.js +119 -119
- package/lib/plugins/comments.js +124 -124
- package/lib/plugins/coverage/coverage.js +169 -169
- package/lib/plugins/email.js +76 -76
- package/lib/plugins/metadata/metadata.js +25 -25
- package/lib/plugins/network/network.js +186 -186
- package/lib/plugins/state/client.js +152 -152
- package/lib/plugins/state/state.js +194 -194
- package/lib/utils/pie.js +148 -148
- package/lib/utils/system.js +145 -145
- package/lib/utils/util.js +511 -511
- package/lib/visitor.js +915 -915
- package/package.json +89 -89
package/lib/utils/util.js
CHANGED
|
@@ -1,511 +1,511 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const { writeFile, readFile } = require('fs/promises');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const os = require('os');
|
|
5
|
-
const crypto = require('crypto');
|
|
6
|
-
const EC = require('eight-colors');
|
|
7
|
-
const CG = require('console-grid');
|
|
8
|
-
const Share = require('../platform/share.js');
|
|
9
|
-
|
|
10
|
-
const getDefaultOptions = require('../default/options.js');
|
|
11
|
-
|
|
12
|
-
const Util = {
|
|
13
|
-
... Share,
|
|
14
|
-
|
|
15
|
-
EC,
|
|
16
|
-
CG,
|
|
17
|
-
|
|
18
|
-
root: process.cwd(),
|
|
19
|
-
|
|
20
|
-
relativePath: function(p, root) {
|
|
21
|
-
p = `${p}`;
|
|
22
|
-
root = `${root || Util.root}`;
|
|
23
|
-
let rp = path.relative(root, p);
|
|
24
|
-
rp = Util.formatPath(rp);
|
|
25
|
-
return rp;
|
|
26
|
-
},
|
|
27
|
-
|
|
28
|
-
replace: function(str, obj, defaultValue) {
|
|
29
|
-
str = `${str}`;
|
|
30
|
-
if (!obj) {
|
|
31
|
-
return str;
|
|
32
|
-
}
|
|
33
|
-
str = str.replace(/\{([^}{]+)\}/g, function(match, key) {
|
|
34
|
-
if (!Util.hasOwn(obj, key)) {
|
|
35
|
-
if (typeof (defaultValue) !== 'undefined') {
|
|
36
|
-
return defaultValue;
|
|
37
|
-
}
|
|
38
|
-
return match;
|
|
39
|
-
}
|
|
40
|
-
let val = obj[key];
|
|
41
|
-
if (typeof (val) === 'function') {
|
|
42
|
-
val = val(obj, key);
|
|
43
|
-
}
|
|
44
|
-
if (typeof (val) === 'undefined') {
|
|
45
|
-
val = '';
|
|
46
|
-
}
|
|
47
|
-
return val;
|
|
48
|
-
});
|
|
49
|
-
return str;
|
|
50
|
-
},
|
|
51
|
-
|
|
52
|
-
calculateSha1(buffer) {
|
|
53
|
-
const hash = crypto.createHash('sha1');
|
|
54
|
-
hash.update(buffer);
|
|
55
|
-
return hash.digest('hex');
|
|
56
|
-
},
|
|
57
|
-
|
|
58
|
-
calculateId: (id) => {
|
|
59
|
-
if (id) {
|
|
60
|
-
return Util.calculateSha1(id).slice(0, 20);
|
|
61
|
-
}
|
|
62
|
-
return Util.uid();
|
|
63
|
-
},
|
|
64
|
-
|
|
65
|
-
parseComments: (input) => {
|
|
66
|
-
const str = `${input}`;
|
|
67
|
-
// starts with @ , ends without @ encodeURIComponent("@") %40
|
|
68
|
-
const reg = /@(\w+)\s+([^@]+)/g;
|
|
69
|
-
const matches = str.matchAll(reg);
|
|
70
|
-
const parsed = {};
|
|
71
|
-
for (const match of matches) {
|
|
72
|
-
// 0 is whole matched, and remove ends * or */
|
|
73
|
-
parsed[match[1]] = match[2].trim().replace(/(\*+|\*+\/)$/g, '').trim();
|
|
74
|
-
}
|
|
75
|
-
// console.log(parsed);
|
|
76
|
-
return parsed;
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
resolveOutputDir: (testInfo) => {
|
|
80
|
-
const reporterOptions = Util.resolveReporterOptions(testInfo);
|
|
81
|
-
const outputFile = Util.resolveOutputFile(reporterOptions.outputFile);
|
|
82
|
-
const outputDir = path.dirname(outputFile);
|
|
83
|
-
return outputDir;
|
|
84
|
-
},
|
|
85
|
-
|
|
86
|
-
resolveOutputFile: (outputFile) => {
|
|
87
|
-
|
|
88
|
-
// then check string
|
|
89
|
-
if (!outputFile || typeof outputFile !== 'string') {
|
|
90
|
-
outputFile = getDefaultOptions().outputFile;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// end with html
|
|
94
|
-
if (!outputFile.endsWith('.html')) {
|
|
95
|
-
outputFile = path.join(outputFile, 'index.html');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return path.resolve(outputFile);
|
|
99
|
-
},
|
|
100
|
-
|
|
101
|
-
resolveLogging: (testInfo, options) => {
|
|
102
|
-
if (options && options.logging) {
|
|
103
|
-
return options.logging;
|
|
104
|
-
}
|
|
105
|
-
const reporterOptions = Util.resolveReporterOptions(testInfo);
|
|
106
|
-
return reporterOptions.logging;
|
|
107
|
-
},
|
|
108
|
-
|
|
109
|
-
// eslint-disable-next-line complexity
|
|
110
|
-
resolveReporterOptions: (testInfo) => {
|
|
111
|
-
if (Util.reporterOptions) {
|
|
112
|
-
return Util.reporterOptions;
|
|
113
|
-
}
|
|
114
|
-
if (!testInfo) {
|
|
115
|
-
return {};
|
|
116
|
-
}
|
|
117
|
-
const configReporters = testInfo.config.reporter;
|
|
118
|
-
if (Array.isArray(configReporters)) {
|
|
119
|
-
for (const item of configReporters) {
|
|
120
|
-
if (Array.isArray(item)) {
|
|
121
|
-
const [name, options] = item;
|
|
122
|
-
if (name && name.indexOf('monocart-reporter') !== -1) {
|
|
123
|
-
Util.reporterOptions = options;
|
|
124
|
-
return options || {};
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return {};
|
|
130
|
-
},
|
|
131
|
-
|
|
132
|
-
resolveTestIdWithRetry: (testInfo) => {
|
|
133
|
-
const id = Util.calculateId(testInfo.testId);
|
|
134
|
-
const retry = testInfo.retry;
|
|
135
|
-
if (retry > 0) {
|
|
136
|
-
return `${id}-retry${retry}`;
|
|
137
|
-
}
|
|
138
|
-
return id;
|
|
139
|
-
},
|
|
140
|
-
|
|
141
|
-
resolveArtifactSourcePath: (artifactsDir, id) => {
|
|
142
|
-
const filename = `source-${id}.json`;
|
|
143
|
-
const sourcePath = path.resolve(artifactsDir, filename);
|
|
144
|
-
return sourcePath;
|
|
145
|
-
},
|
|
146
|
-
|
|
147
|
-
// empty or create dir
|
|
148
|
-
initDir: (dirPath) => {
|
|
149
|
-
if (fs.existsSync(dirPath)) {
|
|
150
|
-
Util.rmSync(dirPath);
|
|
151
|
-
}
|
|
152
|
-
fs.mkdirSync(dirPath, {
|
|
153
|
-
recursive: true
|
|
154
|
-
});
|
|
155
|
-
},
|
|
156
|
-
|
|
157
|
-
getEOL: function(content) {
|
|
158
|
-
if (!content) {
|
|
159
|
-
return os.EOL;
|
|
160
|
-
}
|
|
161
|
-
const nIndex = content.lastIndexOf('\n');
|
|
162
|
-
if (nIndex === -1) {
|
|
163
|
-
return os.EOL;
|
|
164
|
-
}
|
|
165
|
-
if (content.substr(nIndex - 1, 1) === '\r') {
|
|
166
|
-
return '\r\n';
|
|
167
|
-
}
|
|
168
|
-
return '\n';
|
|
169
|
-
},
|
|
170
|
-
|
|
171
|
-
getAttachmentPathExtras: function(d) {
|
|
172
|
-
return {
|
|
173
|
-
name: d.name,
|
|
174
|
-
cwd: d.cwd,
|
|
175
|
-
outputDir: d.outputDir
|
|
176
|
-
};
|
|
177
|
-
},
|
|
178
|
-
|
|
179
|
-
readJSONSync: function(filePath) {
|
|
180
|
-
// do NOT use require, it has cache
|
|
181
|
-
const content = Util.readFileSync(filePath);
|
|
182
|
-
if (content) {
|
|
183
|
-
return JSON.parse(content);
|
|
184
|
-
}
|
|
185
|
-
},
|
|
186
|
-
|
|
187
|
-
writeJSONSync: function(filePath, json) {
|
|
188
|
-
let content = Util.jsonString(json);
|
|
189
|
-
if (!content) {
|
|
190
|
-
Util.logError('invalid JSON object');
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
// end of line
|
|
194
|
-
const EOL = Util.getEOL();
|
|
195
|
-
content = content.replace(/\r|\n/g, EOL);
|
|
196
|
-
content += EOL;
|
|
197
|
-
Util.writeFileSync(filePath, content);
|
|
198
|
-
return true;
|
|
199
|
-
},
|
|
200
|
-
|
|
201
|
-
jsonString: function(obj, spaces) {
|
|
202
|
-
|
|
203
|
-
if (typeof obj === 'string') {
|
|
204
|
-
return obj;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (!spaces) {
|
|
208
|
-
spaces = 4;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
let str = '';
|
|
212
|
-
try {
|
|
213
|
-
str = JSON.stringify(obj, null, spaces);
|
|
214
|
-
} catch (e) {
|
|
215
|
-
console.log(e);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return str;
|
|
219
|
-
},
|
|
220
|
-
|
|
221
|
-
readFileSync: function(filePath) {
|
|
222
|
-
if (fs.existsSync(filePath)) {
|
|
223
|
-
// Returns: <string> | <Buffer>
|
|
224
|
-
const buf = fs.readFileSync(filePath);
|
|
225
|
-
if (Buffer.isBuffer(buf)) {
|
|
226
|
-
return buf.toString('utf8');
|
|
227
|
-
}
|
|
228
|
-
return buf;
|
|
229
|
-
}
|
|
230
|
-
},
|
|
231
|
-
|
|
232
|
-
readFile: async (filePath) => {
|
|
233
|
-
if (fs.existsSync(filePath)) {
|
|
234
|
-
const buf = await readFile(filePath).catch((e) => {
|
|
235
|
-
Util.logError(`read file: ${filePath} ${e.message || e}`);
|
|
236
|
-
});
|
|
237
|
-
if (Buffer.isBuffer(buf)) {
|
|
238
|
-
return buf.toString('utf8');
|
|
239
|
-
}
|
|
240
|
-
return buf;
|
|
241
|
-
}
|
|
242
|
-
},
|
|
243
|
-
|
|
244
|
-
writeFileSync: function(filePath, content) {
|
|
245
|
-
if (!fs.existsSync(filePath)) {
|
|
246
|
-
const p = path.dirname(filePath);
|
|
247
|
-
if (!fs.existsSync(p)) {
|
|
248
|
-
fs.mkdirSync(p, {
|
|
249
|
-
recursive: true
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
fs.writeFileSync(filePath, content);
|
|
254
|
-
},
|
|
255
|
-
|
|
256
|
-
writeFile: async (filePath, content) => {
|
|
257
|
-
if (!fs.existsSync(filePath)) {
|
|
258
|
-
const p = path.dirname(filePath);
|
|
259
|
-
if (!fs.existsSync(p)) {
|
|
260
|
-
fs.mkdirSync(p, {
|
|
261
|
-
recursive: true
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
await writeFile(filePath, content).catch((e) => {
|
|
266
|
-
Util.logError(`write file: ${filePath} ${e.message || e}`);
|
|
267
|
-
});
|
|
268
|
-
},
|
|
269
|
-
|
|
270
|
-
rmSync: (p) => {
|
|
271
|
-
if (fs.existsSync(p)) {
|
|
272
|
-
fs.rmSync(p, {
|
|
273
|
-
recursive: true,
|
|
274
|
-
force: true,
|
|
275
|
-
maxRetries: 10
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
},
|
|
279
|
-
|
|
280
|
-
// eslint-disable-next-line complexity
|
|
281
|
-
forEachFile: (dir, callback, shallow) => {
|
|
282
|
-
if (!fs.existsSync(dir)) {
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const isBreak = (res) => {
|
|
287
|
-
return res === 'break' || res === false;
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
const dirs = [];
|
|
291
|
-
const list = fs.readdirSync(dir, {
|
|
292
|
-
withFileTypes: true
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
for (const item of list) {
|
|
296
|
-
|
|
297
|
-
if (item.isFile()) {
|
|
298
|
-
const res = callback(item.name, dir);
|
|
299
|
-
if (isBreak(res)) {
|
|
300
|
-
return res;
|
|
301
|
-
}
|
|
302
|
-
continue;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (item.isDirectory()) {
|
|
306
|
-
dirs.push(path.resolve(dir, item.name));
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (shallow) {
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
for (const subDir of dirs) {
|
|
316
|
-
const res = Util.forEachFile(subDir, callback, shallow);
|
|
317
|
-
if (isBreak(res)) {
|
|
318
|
-
return res;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
},
|
|
323
|
-
|
|
324
|
-
getTemplate: function(templatePath) {
|
|
325
|
-
if (!Util.templateCache) {
|
|
326
|
-
Util.templateCache = {};
|
|
327
|
-
}
|
|
328
|
-
let template = Util.templateCache[templatePath];
|
|
329
|
-
if (!template) {
|
|
330
|
-
template = Util.readFileSync(templatePath);
|
|
331
|
-
if (template) {
|
|
332
|
-
Util.templateCache[templatePath] = template;
|
|
333
|
-
} else {
|
|
334
|
-
Util.logError(`not found template: ${templatePath}`);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
return template;
|
|
338
|
-
},
|
|
339
|
-
|
|
340
|
-
getDuration: (dateRanges, durationStrategy) => {
|
|
341
|
-
dateRanges.sort((a, b) => {
|
|
342
|
-
if (a.start === b.start) {
|
|
343
|
-
return a.end - b.end;
|
|
344
|
-
}
|
|
345
|
-
return a.start - b.start;
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
if (durationStrategy === 'exclude-idle') {
|
|
349
|
-
|
|
350
|
-
dateRanges.reduce((prevRange, range) => {
|
|
351
|
-
// same start
|
|
352
|
-
if (range.start === prevRange.start) {
|
|
353
|
-
range.dedupe = true;
|
|
354
|
-
// equal prev
|
|
355
|
-
if (range.end === prevRange.end) {
|
|
356
|
-
return prevRange;
|
|
357
|
-
}
|
|
358
|
-
// great than the prev end, update the end
|
|
359
|
-
prevRange.end = range.end;
|
|
360
|
-
return prevRange;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// already in the range
|
|
364
|
-
if (range.end <= prevRange.end) {
|
|
365
|
-
range.dedupe = true;
|
|
366
|
-
return prevRange;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// collected, update the end
|
|
370
|
-
if (range.start <= prevRange.end) {
|
|
371
|
-
range.dedupe = true;
|
|
372
|
-
prevRange.end = range.end;
|
|
373
|
-
return prevRange;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
return range;
|
|
377
|
-
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
const ranges = dateRanges.filter((it) => !it.dedupe);
|
|
381
|
-
// console.log(ranges);
|
|
382
|
-
let duration = 0;
|
|
383
|
-
ranges.forEach((item) => {
|
|
384
|
-
duration += item.end - item.start;
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
return duration;
|
|
388
|
-
}
|
|
389
|
-
// normal
|
|
390
|
-
const dateStart = dateRanges[0].start;
|
|
391
|
-
const endDate = dateRanges[dateRanges.length - 1].end;
|
|
392
|
-
const duration = endDate - dateStart;
|
|
393
|
-
return duration;
|
|
394
|
-
},
|
|
395
|
-
|
|
396
|
-
mergeOption: function(... args) {
|
|
397
|
-
const option = {};
|
|
398
|
-
args.forEach((item) => {
|
|
399
|
-
if (!item) {
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
Object.keys(item).forEach((k) => {
|
|
403
|
-
const nv = item[k];
|
|
404
|
-
if (Util.hasOwn(option, k)) {
|
|
405
|
-
const ov = option[k];
|
|
406
|
-
if (ov && typeof ov === 'object') {
|
|
407
|
-
if (nv && typeof nv === 'object' && !Array.isArray(nv)) {
|
|
408
|
-
option[k] = Util.mergeOption(ov, nv);
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
option[k] = nv;
|
|
414
|
-
});
|
|
415
|
-
});
|
|
416
|
-
return option;
|
|
417
|
-
},
|
|
418
|
-
|
|
419
|
-
cmpVersion: (v1, v2) => {
|
|
420
|
-
const [strMajor1, strMinor1, strPatch1] = `${v1}`.split('.');
|
|
421
|
-
const [strMajor2, strMinor2, strPatch2] = `${v2}`.split('.');
|
|
422
|
-
const strList = [strMajor1, strMinor1, strPatch1, strMajor2, strMinor2, strPatch2];
|
|
423
|
-
const list = strList.map((str) => Util.toNum(parseInt(str)));
|
|
424
|
-
const [major1, minor1, patch1, major2, minor2, patch2] = list;
|
|
425
|
-
if (major1 === major2) {
|
|
426
|
-
if (minor1 === minor2) {
|
|
427
|
-
return patch1 - patch2;
|
|
428
|
-
}
|
|
429
|
-
return minor1 - minor2;
|
|
430
|
-
}
|
|
431
|
-
return major1 - major2;
|
|
432
|
-
},
|
|
433
|
-
|
|
434
|
-
// ==========================================================================================
|
|
435
|
-
|
|
436
|
-
loggingLevels: {
|
|
437
|
-
off: 0,
|
|
438
|
-
error: 10,
|
|
439
|
-
info: 20,
|
|
440
|
-
debug: 30
|
|
441
|
-
},
|
|
442
|
-
|
|
443
|
-
initLoggingLevel: (level, from = '') => {
|
|
444
|
-
if (!level && Util.loggingType) {
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
const types = {
|
|
448
|
-
off: 'off',
|
|
449
|
-
error: 'error',
|
|
450
|
-
info: 'info',
|
|
451
|
-
debug: 'debug'
|
|
452
|
-
};
|
|
453
|
-
const type = types[level] || types.info;
|
|
454
|
-
Util.loggingType = type;
|
|
455
|
-
Util.loggingLevel = Util.loggingLevels[type];
|
|
456
|
-
|
|
457
|
-
// console.log('=========================================');
|
|
458
|
-
// console.log(from, Util.loggingType, Util.loggingLevel);
|
|
459
|
-
},
|
|
460
|
-
|
|
461
|
-
logError: (message) => {
|
|
462
|
-
if (Util.loggingLevel < Util.loggingLevels.error) {
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
EC.logRed(`[MR] ${message}`);
|
|
466
|
-
},
|
|
467
|
-
|
|
468
|
-
logInfo: (message) => {
|
|
469
|
-
if (Util.loggingLevel < Util.loggingLevels.info) {
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
console.log(`[MR] ${message}`);
|
|
473
|
-
},
|
|
474
|
-
|
|
475
|
-
// grid is info level
|
|
476
|
-
logGrid: (gridData) => {
|
|
477
|
-
if (Util.loggingLevel < Util.loggingLevels.info) {
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
480
|
-
CG(gridData);
|
|
481
|
-
},
|
|
482
|
-
|
|
483
|
-
logDebug: (message) => {
|
|
484
|
-
if (Util.loggingLevel < Util.loggingLevels.debug) {
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
console.log(`[MR] ${message}`);
|
|
488
|
-
},
|
|
489
|
-
|
|
490
|
-
// time is debug level
|
|
491
|
-
logTime: (message, time_start) => {
|
|
492
|
-
if (Util.loggingLevel < Util.loggingLevels.debug) {
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
const duration = Date.now() - time_start;
|
|
496
|
-
const durationH = Util.TSF(duration);
|
|
497
|
-
const ls = [`[MR] ${message}`, ' ('];
|
|
498
|
-
if (duration <= 100) {
|
|
499
|
-
ls.push(EC.green(durationH));
|
|
500
|
-
} else if (duration < 500) {
|
|
501
|
-
ls.push(EC.yellow(durationH));
|
|
502
|
-
} else {
|
|
503
|
-
ls.push(EC.red(durationH));
|
|
504
|
-
}
|
|
505
|
-
ls.push(')');
|
|
506
|
-
console.log(ls.join(''));
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
module.exports = Util;
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const { writeFile, readFile } = require('fs/promises');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const EC = require('eight-colors');
|
|
7
|
+
const CG = require('console-grid');
|
|
8
|
+
const Share = require('../platform/share.js');
|
|
9
|
+
|
|
10
|
+
const getDefaultOptions = require('../default/options.js');
|
|
11
|
+
|
|
12
|
+
const Util = {
|
|
13
|
+
... Share,
|
|
14
|
+
|
|
15
|
+
EC,
|
|
16
|
+
CG,
|
|
17
|
+
|
|
18
|
+
root: process.cwd(),
|
|
19
|
+
|
|
20
|
+
relativePath: function(p, root) {
|
|
21
|
+
p = `${p}`;
|
|
22
|
+
root = `${root || Util.root}`;
|
|
23
|
+
let rp = path.relative(root, p);
|
|
24
|
+
rp = Util.formatPath(rp);
|
|
25
|
+
return rp;
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
replace: function(str, obj, defaultValue) {
|
|
29
|
+
str = `${str}`;
|
|
30
|
+
if (!obj) {
|
|
31
|
+
return str;
|
|
32
|
+
}
|
|
33
|
+
str = str.replace(/\{([^}{]+)\}/g, function(match, key) {
|
|
34
|
+
if (!Util.hasOwn(obj, key)) {
|
|
35
|
+
if (typeof (defaultValue) !== 'undefined') {
|
|
36
|
+
return defaultValue;
|
|
37
|
+
}
|
|
38
|
+
return match;
|
|
39
|
+
}
|
|
40
|
+
let val = obj[key];
|
|
41
|
+
if (typeof (val) === 'function') {
|
|
42
|
+
val = val(obj, key);
|
|
43
|
+
}
|
|
44
|
+
if (typeof (val) === 'undefined') {
|
|
45
|
+
val = '';
|
|
46
|
+
}
|
|
47
|
+
return val;
|
|
48
|
+
});
|
|
49
|
+
return str;
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
calculateSha1(buffer) {
|
|
53
|
+
const hash = crypto.createHash('sha1');
|
|
54
|
+
hash.update(buffer);
|
|
55
|
+
return hash.digest('hex');
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
calculateId: (id) => {
|
|
59
|
+
if (id) {
|
|
60
|
+
return Util.calculateSha1(id).slice(0, 20);
|
|
61
|
+
}
|
|
62
|
+
return Util.uid();
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
parseComments: (input) => {
|
|
66
|
+
const str = `${input}`;
|
|
67
|
+
// starts with @ , ends without @ encodeURIComponent("@") %40
|
|
68
|
+
const reg = /@(\w+)\s+([^@]+)/g;
|
|
69
|
+
const matches = str.matchAll(reg);
|
|
70
|
+
const parsed = {};
|
|
71
|
+
for (const match of matches) {
|
|
72
|
+
// 0 is whole matched, and remove ends * or */
|
|
73
|
+
parsed[match[1]] = match[2].trim().replace(/(\*+|\*+\/)$/g, '').trim();
|
|
74
|
+
}
|
|
75
|
+
// console.log(parsed);
|
|
76
|
+
return parsed;
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
resolveOutputDir: (testInfo) => {
|
|
80
|
+
const reporterOptions = Util.resolveReporterOptions(testInfo);
|
|
81
|
+
const outputFile = Util.resolveOutputFile(reporterOptions.outputFile);
|
|
82
|
+
const outputDir = path.dirname(outputFile);
|
|
83
|
+
return outputDir;
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
resolveOutputFile: (outputFile) => {
|
|
87
|
+
|
|
88
|
+
// then check string
|
|
89
|
+
if (!outputFile || typeof outputFile !== 'string') {
|
|
90
|
+
outputFile = getDefaultOptions().outputFile;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// end with html
|
|
94
|
+
if (!outputFile.endsWith('.html')) {
|
|
95
|
+
outputFile = path.join(outputFile, 'index.html');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return path.resolve(outputFile);
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
resolveLogging: (testInfo, options) => {
|
|
102
|
+
if (options && options.logging) {
|
|
103
|
+
return options.logging;
|
|
104
|
+
}
|
|
105
|
+
const reporterOptions = Util.resolveReporterOptions(testInfo);
|
|
106
|
+
return reporterOptions.logging;
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
// eslint-disable-next-line complexity
|
|
110
|
+
resolveReporterOptions: (testInfo) => {
|
|
111
|
+
if (Util.reporterOptions) {
|
|
112
|
+
return Util.reporterOptions;
|
|
113
|
+
}
|
|
114
|
+
if (!testInfo) {
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
117
|
+
const configReporters = testInfo.config.reporter;
|
|
118
|
+
if (Array.isArray(configReporters)) {
|
|
119
|
+
for (const item of configReporters) {
|
|
120
|
+
if (Array.isArray(item)) {
|
|
121
|
+
const [name, options] = item;
|
|
122
|
+
if (name && name.indexOf('monocart-reporter') !== -1) {
|
|
123
|
+
Util.reporterOptions = options;
|
|
124
|
+
return options || {};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return {};
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
resolveTestIdWithRetry: (testInfo) => {
|
|
133
|
+
const id = Util.calculateId(testInfo.testId);
|
|
134
|
+
const retry = testInfo.retry;
|
|
135
|
+
if (retry > 0) {
|
|
136
|
+
return `${id}-retry${retry}`;
|
|
137
|
+
}
|
|
138
|
+
return id;
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
resolveArtifactSourcePath: (artifactsDir, id) => {
|
|
142
|
+
const filename = `source-${id}.json`;
|
|
143
|
+
const sourcePath = path.resolve(artifactsDir, filename);
|
|
144
|
+
return sourcePath;
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
// empty or create dir
|
|
148
|
+
initDir: (dirPath) => {
|
|
149
|
+
if (fs.existsSync(dirPath)) {
|
|
150
|
+
Util.rmSync(dirPath);
|
|
151
|
+
}
|
|
152
|
+
fs.mkdirSync(dirPath, {
|
|
153
|
+
recursive: true
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
getEOL: function(content) {
|
|
158
|
+
if (!content) {
|
|
159
|
+
return os.EOL;
|
|
160
|
+
}
|
|
161
|
+
const nIndex = content.lastIndexOf('\n');
|
|
162
|
+
if (nIndex === -1) {
|
|
163
|
+
return os.EOL;
|
|
164
|
+
}
|
|
165
|
+
if (content.substr(nIndex - 1, 1) === '\r') {
|
|
166
|
+
return '\r\n';
|
|
167
|
+
}
|
|
168
|
+
return '\n';
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
getAttachmentPathExtras: function(d) {
|
|
172
|
+
return {
|
|
173
|
+
name: d.name,
|
|
174
|
+
cwd: d.cwd,
|
|
175
|
+
outputDir: d.outputDir
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
readJSONSync: function(filePath) {
|
|
180
|
+
// do NOT use require, it has cache
|
|
181
|
+
const content = Util.readFileSync(filePath);
|
|
182
|
+
if (content) {
|
|
183
|
+
return JSON.parse(content);
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
writeJSONSync: function(filePath, json) {
|
|
188
|
+
let content = Util.jsonString(json);
|
|
189
|
+
if (!content) {
|
|
190
|
+
Util.logError('invalid JSON object');
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
// end of line
|
|
194
|
+
const EOL = Util.getEOL();
|
|
195
|
+
content = content.replace(/\r|\n/g, EOL);
|
|
196
|
+
content += EOL;
|
|
197
|
+
Util.writeFileSync(filePath, content);
|
|
198
|
+
return true;
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
jsonString: function(obj, spaces) {
|
|
202
|
+
|
|
203
|
+
if (typeof obj === 'string') {
|
|
204
|
+
return obj;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!spaces) {
|
|
208
|
+
spaces = 4;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let str = '';
|
|
212
|
+
try {
|
|
213
|
+
str = JSON.stringify(obj, null, spaces);
|
|
214
|
+
} catch (e) {
|
|
215
|
+
console.log(e);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return str;
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
readFileSync: function(filePath) {
|
|
222
|
+
if (fs.existsSync(filePath)) {
|
|
223
|
+
// Returns: <string> | <Buffer>
|
|
224
|
+
const buf = fs.readFileSync(filePath);
|
|
225
|
+
if (Buffer.isBuffer(buf)) {
|
|
226
|
+
return buf.toString('utf8');
|
|
227
|
+
}
|
|
228
|
+
return buf;
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
readFile: async (filePath) => {
|
|
233
|
+
if (fs.existsSync(filePath)) {
|
|
234
|
+
const buf = await readFile(filePath).catch((e) => {
|
|
235
|
+
Util.logError(`read file: ${filePath} ${e.message || e}`);
|
|
236
|
+
});
|
|
237
|
+
if (Buffer.isBuffer(buf)) {
|
|
238
|
+
return buf.toString('utf8');
|
|
239
|
+
}
|
|
240
|
+
return buf;
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
writeFileSync: function(filePath, content) {
|
|
245
|
+
if (!fs.existsSync(filePath)) {
|
|
246
|
+
const p = path.dirname(filePath);
|
|
247
|
+
if (!fs.existsSync(p)) {
|
|
248
|
+
fs.mkdirSync(p, {
|
|
249
|
+
recursive: true
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
fs.writeFileSync(filePath, content);
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
writeFile: async (filePath, content) => {
|
|
257
|
+
if (!fs.existsSync(filePath)) {
|
|
258
|
+
const p = path.dirname(filePath);
|
|
259
|
+
if (!fs.existsSync(p)) {
|
|
260
|
+
fs.mkdirSync(p, {
|
|
261
|
+
recursive: true
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
await writeFile(filePath, content).catch((e) => {
|
|
266
|
+
Util.logError(`write file: ${filePath} ${e.message || e}`);
|
|
267
|
+
});
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
rmSync: (p) => {
|
|
271
|
+
if (fs.existsSync(p)) {
|
|
272
|
+
fs.rmSync(p, {
|
|
273
|
+
recursive: true,
|
|
274
|
+
force: true,
|
|
275
|
+
maxRetries: 10
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
// eslint-disable-next-line complexity
|
|
281
|
+
forEachFile: (dir, callback, shallow) => {
|
|
282
|
+
if (!fs.existsSync(dir)) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const isBreak = (res) => {
|
|
287
|
+
return res === 'break' || res === false;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const dirs = [];
|
|
291
|
+
const list = fs.readdirSync(dir, {
|
|
292
|
+
withFileTypes: true
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
for (const item of list) {
|
|
296
|
+
|
|
297
|
+
if (item.isFile()) {
|
|
298
|
+
const res = callback(item.name, dir);
|
|
299
|
+
if (isBreak(res)) {
|
|
300
|
+
return res;
|
|
301
|
+
}
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (item.isDirectory()) {
|
|
306
|
+
dirs.push(path.resolve(dir, item.name));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (shallow) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
for (const subDir of dirs) {
|
|
316
|
+
const res = Util.forEachFile(subDir, callback, shallow);
|
|
317
|
+
if (isBreak(res)) {
|
|
318
|
+
return res;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
getTemplate: function(templatePath) {
|
|
325
|
+
if (!Util.templateCache) {
|
|
326
|
+
Util.templateCache = {};
|
|
327
|
+
}
|
|
328
|
+
let template = Util.templateCache[templatePath];
|
|
329
|
+
if (!template) {
|
|
330
|
+
template = Util.readFileSync(templatePath);
|
|
331
|
+
if (template) {
|
|
332
|
+
Util.templateCache[templatePath] = template;
|
|
333
|
+
} else {
|
|
334
|
+
Util.logError(`not found template: ${templatePath}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return template;
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
getDuration: (dateRanges, durationStrategy) => {
|
|
341
|
+
dateRanges.sort((a, b) => {
|
|
342
|
+
if (a.start === b.start) {
|
|
343
|
+
return a.end - b.end;
|
|
344
|
+
}
|
|
345
|
+
return a.start - b.start;
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
if (durationStrategy === 'exclude-idle') {
|
|
349
|
+
|
|
350
|
+
dateRanges.reduce((prevRange, range) => {
|
|
351
|
+
// same start
|
|
352
|
+
if (range.start === prevRange.start) {
|
|
353
|
+
range.dedupe = true;
|
|
354
|
+
// equal prev
|
|
355
|
+
if (range.end === prevRange.end) {
|
|
356
|
+
return prevRange;
|
|
357
|
+
}
|
|
358
|
+
// great than the prev end, update the end
|
|
359
|
+
prevRange.end = range.end;
|
|
360
|
+
return prevRange;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// already in the range
|
|
364
|
+
if (range.end <= prevRange.end) {
|
|
365
|
+
range.dedupe = true;
|
|
366
|
+
return prevRange;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// collected, update the end
|
|
370
|
+
if (range.start <= prevRange.end) {
|
|
371
|
+
range.dedupe = true;
|
|
372
|
+
prevRange.end = range.end;
|
|
373
|
+
return prevRange;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return range;
|
|
377
|
+
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
const ranges = dateRanges.filter((it) => !it.dedupe);
|
|
381
|
+
// console.log(ranges);
|
|
382
|
+
let duration = 0;
|
|
383
|
+
ranges.forEach((item) => {
|
|
384
|
+
duration += item.end - item.start;
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
return duration;
|
|
388
|
+
}
|
|
389
|
+
// normal
|
|
390
|
+
const dateStart = dateRanges[0].start;
|
|
391
|
+
const endDate = dateRanges[dateRanges.length - 1].end;
|
|
392
|
+
const duration = endDate - dateStart;
|
|
393
|
+
return duration;
|
|
394
|
+
},
|
|
395
|
+
|
|
396
|
+
mergeOption: function(... args) {
|
|
397
|
+
const option = {};
|
|
398
|
+
args.forEach((item) => {
|
|
399
|
+
if (!item) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
Object.keys(item).forEach((k) => {
|
|
403
|
+
const nv = item[k];
|
|
404
|
+
if (Util.hasOwn(option, k)) {
|
|
405
|
+
const ov = option[k];
|
|
406
|
+
if (ov && typeof ov === 'object') {
|
|
407
|
+
if (nv && typeof nv === 'object' && !Array.isArray(nv)) {
|
|
408
|
+
option[k] = Util.mergeOption(ov, nv);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
option[k] = nv;
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
return option;
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
cmpVersion: (v1, v2) => {
|
|
420
|
+
const [strMajor1, strMinor1, strPatch1] = `${v1}`.split('.');
|
|
421
|
+
const [strMajor2, strMinor2, strPatch2] = `${v2}`.split('.');
|
|
422
|
+
const strList = [strMajor1, strMinor1, strPatch1, strMajor2, strMinor2, strPatch2];
|
|
423
|
+
const list = strList.map((str) => Util.toNum(parseInt(str)));
|
|
424
|
+
const [major1, minor1, patch1, major2, minor2, patch2] = list;
|
|
425
|
+
if (major1 === major2) {
|
|
426
|
+
if (minor1 === minor2) {
|
|
427
|
+
return patch1 - patch2;
|
|
428
|
+
}
|
|
429
|
+
return minor1 - minor2;
|
|
430
|
+
}
|
|
431
|
+
return major1 - major2;
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
// ==========================================================================================
|
|
435
|
+
|
|
436
|
+
loggingLevels: {
|
|
437
|
+
off: 0,
|
|
438
|
+
error: 10,
|
|
439
|
+
info: 20,
|
|
440
|
+
debug: 30
|
|
441
|
+
},
|
|
442
|
+
|
|
443
|
+
initLoggingLevel: (level, from = '') => {
|
|
444
|
+
if (!level && Util.loggingType) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
const types = {
|
|
448
|
+
off: 'off',
|
|
449
|
+
error: 'error',
|
|
450
|
+
info: 'info',
|
|
451
|
+
debug: 'debug'
|
|
452
|
+
};
|
|
453
|
+
const type = types[level] || types.info;
|
|
454
|
+
Util.loggingType = type;
|
|
455
|
+
Util.loggingLevel = Util.loggingLevels[type];
|
|
456
|
+
|
|
457
|
+
// console.log('=========================================');
|
|
458
|
+
// console.log(from, Util.loggingType, Util.loggingLevel);
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
logError: (message) => {
|
|
462
|
+
if (Util.loggingLevel < Util.loggingLevels.error) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
EC.logRed(`[MR] ${message}`);
|
|
466
|
+
},
|
|
467
|
+
|
|
468
|
+
logInfo: (message) => {
|
|
469
|
+
if (Util.loggingLevel < Util.loggingLevels.info) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
console.log(`[MR] ${message}`);
|
|
473
|
+
},
|
|
474
|
+
|
|
475
|
+
// grid is info level
|
|
476
|
+
logGrid: (gridData) => {
|
|
477
|
+
if (Util.loggingLevel < Util.loggingLevels.info) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
CG(gridData);
|
|
481
|
+
},
|
|
482
|
+
|
|
483
|
+
logDebug: (message) => {
|
|
484
|
+
if (Util.loggingLevel < Util.loggingLevels.debug) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
console.log(`[MR] ${message}`);
|
|
488
|
+
},
|
|
489
|
+
|
|
490
|
+
// time is debug level
|
|
491
|
+
logTime: (message, time_start) => {
|
|
492
|
+
if (Util.loggingLevel < Util.loggingLevels.debug) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
const duration = Date.now() - time_start;
|
|
496
|
+
const durationH = Util.TSF(duration);
|
|
497
|
+
const ls = [`[MR] ${message}`, ' ('];
|
|
498
|
+
if (duration <= 100) {
|
|
499
|
+
ls.push(EC.green(durationH));
|
|
500
|
+
} else if (duration < 500) {
|
|
501
|
+
ls.push(EC.yellow(durationH));
|
|
502
|
+
} else {
|
|
503
|
+
ls.push(EC.red(durationH));
|
|
504
|
+
}
|
|
505
|
+
ls.push(')');
|
|
506
|
+
console.log(ls.join(''));
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
module.exports = Util;
|