monocart-reporter 2.9.6 → 2.9.8
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/packages/monocart-reporter-vendor.js +22 -23
- 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 +512 -511
- package/lib/visitor.js +915 -915
- package/package.json +10 -10
package/lib/merge-data.js
CHANGED
|
@@ -1,405 +1,405 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const Util = require('./utils/util.js');
|
|
4
|
-
const generateReport = require('./generate-report.js');
|
|
5
|
-
const getDefaultOptions = require('./default/options.js');
|
|
6
|
-
const { calculateSummary, getTrends } = require('./common.js');
|
|
7
|
-
const { mergeGlobalCoverageReport } = require('./plugins/coverage/coverage.js');
|
|
8
|
-
const { StreamZip } = require('./packages/monocart-reporter-vendor.js');
|
|
9
|
-
|
|
10
|
-
const checkReportData = (item) => {
|
|
11
|
-
if (item && typeof item === 'object') {
|
|
12
|
-
if (item.rows && item.columns && item.summary) {
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const unzipDataFile = async (item, num, options) => {
|
|
19
|
-
|
|
20
|
-
if (!options.cacheDir) {
|
|
21
|
-
options.cacheDir = path.resolve(options.outputDir, '.cache');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const unzipDir = path.resolve(options.cacheDir, `extracted-${num}`);
|
|
25
|
-
|
|
26
|
-
fs.mkdirSync(unzipDir, {
|
|
27
|
-
recursive: true
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const zip = new StreamZip({
|
|
31
|
-
file: item
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
await zip.extract(null, unzipDir);
|
|
35
|
-
|
|
36
|
-
// Do not forget to close the file once you're done
|
|
37
|
-
await zip.close();
|
|
38
|
-
|
|
39
|
-
const filename = path.basename(item, '.zip');
|
|
40
|
-
let jsonPath = path.resolve(unzipDir, `${filename}.json`);
|
|
41
|
-
if (fs.existsSync(jsonPath)) {
|
|
42
|
-
return jsonPath;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// filename for both html and json
|
|
46
|
-
const htmlMap = {};
|
|
47
|
-
const jsonMap = {};
|
|
48
|
-
Util.forEachFile(unzipDir, (name, dir) => {
|
|
49
|
-
if (name.endsWith('.html')) {
|
|
50
|
-
htmlMap[path.basename(name, '.html')] = true;
|
|
51
|
-
} else if (name.endsWith('.json')) {
|
|
52
|
-
jsonMap[path.basename(name, '.json')] = true;
|
|
53
|
-
}
|
|
54
|
-
}, true);
|
|
55
|
-
|
|
56
|
-
const keys = Object.keys(htmlMap);
|
|
57
|
-
for (const fn of keys) {
|
|
58
|
-
if (jsonMap[fn]) {
|
|
59
|
-
jsonPath = path.resolve(unzipDir, `${fn}.json`);
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return jsonPath;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const getReportData = async (item, num, options) => {
|
|
68
|
-
if (typeof item === 'string') {
|
|
69
|
-
|
|
70
|
-
// json or zip path
|
|
71
|
-
const extname = path.extname(item);
|
|
72
|
-
if (extname === '.zip') {
|
|
73
|
-
item = await unzipDataFile(item, num, options);
|
|
74
|
-
// console.log(item);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// json path
|
|
78
|
-
const data = Util.readJSONSync(item);
|
|
79
|
-
if (!data) {
|
|
80
|
-
Util.logError(`failed to read report data file: ${item}`);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// for finding attachments
|
|
85
|
-
const jsonDir = Util.relativePath(path.dirname(item));
|
|
86
|
-
|
|
87
|
-
Util.logInfo(`report data loaded: ${Util.relativePath(item)}`);
|
|
88
|
-
return {
|
|
89
|
-
jsonDir,
|
|
90
|
-
data
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (!checkReportData(item)) {
|
|
95
|
-
Util.logError(`unmatched report data format: ${item}`);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// json
|
|
100
|
-
return {
|
|
101
|
-
jsonDir: item.outputDir,
|
|
102
|
-
data: item
|
|
103
|
-
};
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const initDataList = async (reportDataList, options) => {
|
|
107
|
-
if (!Util.isList(reportDataList)) {
|
|
108
|
-
Util.logError(`invalid report data list: ${reportDataList}`);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
let hasError = false;
|
|
113
|
-
const list = [];
|
|
114
|
-
|
|
115
|
-
let num = 1;
|
|
116
|
-
for (const item of reportDataList) {
|
|
117
|
-
|
|
118
|
-
const data = await getReportData(item, num, options);
|
|
119
|
-
if (!data) {
|
|
120
|
-
hasError = true;
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
num += 1;
|
|
124
|
-
|
|
125
|
-
list.push(data);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (hasError) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// sort list
|
|
133
|
-
|
|
134
|
-
list.sort((a, b) => {
|
|
135
|
-
if (a.jsonDir > b.jsonDir) {
|
|
136
|
-
return 1;
|
|
137
|
-
}
|
|
138
|
-
return -1;
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
// console.log(list.map((it) => it.jsonDir));
|
|
142
|
-
|
|
143
|
-
return list;
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
const copyTarget = (targetPath, fromDir, toDir) => {
|
|
147
|
-
const oldPath = path.resolve(fromDir, targetPath);
|
|
148
|
-
if (fs.existsSync(oldPath)) {
|
|
149
|
-
const newPath = path.resolve(toDir, targetPath);
|
|
150
|
-
if (oldPath !== newPath) {
|
|
151
|
-
fs.cpSync(oldPath, newPath, {
|
|
152
|
-
force: true,
|
|
153
|
-
recursive: true
|
|
154
|
-
});
|
|
155
|
-
return true;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const attachmentsHandler = (data, jsonDir, outputDir, attachmentPathHandler) => {
|
|
161
|
-
|
|
162
|
-
const extras = Util.getAttachmentPathExtras(data);
|
|
163
|
-
Util.forEach(data.rows, (row) => {
|
|
164
|
-
if (row.type !== 'case' || !row.attachments) {
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
row.attachments.forEach((attachment) => {
|
|
168
|
-
if (!attachment.path) {
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// copy attachment
|
|
173
|
-
const done = copyTarget(attachment.path, jsonDir, outputDir);
|
|
174
|
-
if (!done) {
|
|
175
|
-
Util.logError(`failed to copy: ${attachment.path}`);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (attachmentPathHandler) {
|
|
179
|
-
const newPath = attachmentPathHandler(attachment.path, extras);
|
|
180
|
-
if (newPath) {
|
|
181
|
-
attachment.path = newPath;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const mergeArtifacts = async (artifactsList, options) => {
|
|
189
|
-
|
|
190
|
-
const outputDir = options.outputDir;
|
|
191
|
-
|
|
192
|
-
const artifacts = [];
|
|
193
|
-
const coverageList = [];
|
|
194
|
-
const coverageRawList = [];
|
|
195
|
-
|
|
196
|
-
const jsonDirMap = {};
|
|
197
|
-
|
|
198
|
-
artifactsList.forEach((item) => {
|
|
199
|
-
const jsonDir = item.jsonDir;
|
|
200
|
-
let hasAssets = false;
|
|
201
|
-
item.artifacts.forEach((art) => {
|
|
202
|
-
|
|
203
|
-
// merge global coverage
|
|
204
|
-
if (art.global && art.type === 'coverage') {
|
|
205
|
-
coverageList.push({
|
|
206
|
-
jsonDir,
|
|
207
|
-
art
|
|
208
|
-
});
|
|
209
|
-
if (art.rawDir) {
|
|
210
|
-
const rawDir = path.resolve(jsonDir, art.rawDir);
|
|
211
|
-
if (fs.existsSync(rawDir)) {
|
|
212
|
-
coverageRawList.push(rawDir);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const targetDirName = path.dirname(art.path);
|
|
219
|
-
// copy all files in art dir, like network, audit
|
|
220
|
-
copyTarget(targetDirName, jsonDir, outputDir);
|
|
221
|
-
hasAssets = true;
|
|
222
|
-
|
|
223
|
-
// update path relative to cwd/root
|
|
224
|
-
art.path = Util.relativePath(path.resolve(outputDir, art.path));
|
|
225
|
-
|
|
226
|
-
artifacts.push(art);
|
|
227
|
-
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
// copy assets dir
|
|
231
|
-
if (hasAssets) {
|
|
232
|
-
copyTarget('assets', jsonDir, outputDir);
|
|
233
|
-
jsonDirMap[jsonDir] = true;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// merge global coverage raws
|
|
239
|
-
if (coverageRawList.length) {
|
|
240
|
-
const coverage = await mergeGlobalCoverageReport(coverageRawList, options);
|
|
241
|
-
if (coverage) {
|
|
242
|
-
artifacts.push(coverage);
|
|
243
|
-
}
|
|
244
|
-
} else {
|
|
245
|
-
|
|
246
|
-
// copy single one art
|
|
247
|
-
const item = coverageList.find((it) => it.art.path);
|
|
248
|
-
if (item) {
|
|
249
|
-
const { art, jsonDir } = item;
|
|
250
|
-
|
|
251
|
-
const targetDirName = path.dirname(art.path);
|
|
252
|
-
// copy all files in art dir, like network, audit
|
|
253
|
-
copyTarget(targetDirName, jsonDir, outputDir);
|
|
254
|
-
|
|
255
|
-
if (!jsonDirMap[jsonDir]) {
|
|
256
|
-
copyTarget('assets', jsonDir, outputDir);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// update path relative to cwd/root
|
|
260
|
-
art.path = Util.relativePath(path.resolve(outputDir, art.path));
|
|
261
|
-
|
|
262
|
-
artifacts.push(art);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
return artifacts;
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
const mergeDataList = async (dataList, options) => {
|
|
270
|
-
|
|
271
|
-
const { outputFile, outputDir } = options;
|
|
272
|
-
|
|
273
|
-
const trends = await getTrends(options.trend);
|
|
274
|
-
|
|
275
|
-
const attachmentPathHandler = typeof options.attachmentPath === 'function' ? options.attachmentPath : null;
|
|
276
|
-
|
|
277
|
-
const startDates = [];
|
|
278
|
-
const dateRanges = [];
|
|
279
|
-
const metadata = {};
|
|
280
|
-
const system = [];
|
|
281
|
-
const rows = [];
|
|
282
|
-
const artifactsList = [];
|
|
283
|
-
|
|
284
|
-
for (const dataItem of dataList) {
|
|
285
|
-
|
|
286
|
-
const { data, jsonDir } = dataItem;
|
|
287
|
-
|
|
288
|
-
attachmentsHandler(data, jsonDir, outputDir, attachmentPathHandler);
|
|
289
|
-
|
|
290
|
-
startDates.push(data.date);
|
|
291
|
-
dateRanges.push({
|
|
292
|
-
start: data.date,
|
|
293
|
-
end: data.date + data.duration
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// merge metadata (may collect from diff shard)
|
|
297
|
-
Object.assign(metadata, data.metadata);
|
|
298
|
-
|
|
299
|
-
// merge system
|
|
300
|
-
system.push(data.system);
|
|
301
|
-
|
|
302
|
-
// merge rows
|
|
303
|
-
rows.push({
|
|
304
|
-
// add shard level suite
|
|
305
|
-
title: data.system.hostname,
|
|
306
|
-
type: 'suite',
|
|
307
|
-
suiteType: 'shard',
|
|
308
|
-
caseNum: data.summary.tests.value,
|
|
309
|
-
subs: data.rows
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
if (data.artifacts) {
|
|
313
|
-
artifactsList.push({
|
|
314
|
-
jsonDir,
|
|
315
|
-
artifacts: data.artifacts
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const artifacts = await mergeArtifacts(artifactsList, options);
|
|
322
|
-
|
|
323
|
-
// base on first one, do not change dataList (need for onData)
|
|
324
|
-
const mergedData = {
|
|
325
|
-
... dataList[0].data
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
// merge new options
|
|
329
|
-
['traceViewerUrl', 'mermaid'].forEach((k) => {
|
|
330
|
-
if (Util.hasOwn(options, k)) {
|
|
331
|
-
mergedData[k] = options[k];
|
|
332
|
-
}
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
const reportName = options.name || mergedData.name;
|
|
336
|
-
|
|
337
|
-
const date = Math.min.apply(null, startDates);
|
|
338
|
-
const dateH = new Date(date).toLocaleString();
|
|
339
|
-
|
|
340
|
-
const duration = Util.getDuration(dateRanges, options.durationStrategy);
|
|
341
|
-
const durationH = Util.TF(duration);
|
|
342
|
-
|
|
343
|
-
Object.assign(mergedData, {
|
|
344
|
-
name: reportName,
|
|
345
|
-
|
|
346
|
-
date,
|
|
347
|
-
dateH,
|
|
348
|
-
duration,
|
|
349
|
-
durationH,
|
|
350
|
-
|
|
351
|
-
// for current merge process
|
|
352
|
-
cwd: Util.formatPath(process.cwd()),
|
|
353
|
-
outputFile: Util.relativePath(outputFile),
|
|
354
|
-
outputDir: Util.relativePath(outputDir),
|
|
355
|
-
|
|
356
|
-
metadata,
|
|
357
|
-
// system is array in shard mode, because have multiple machines
|
|
358
|
-
system,
|
|
359
|
-
trends,
|
|
360
|
-
|
|
361
|
-
rows,
|
|
362
|
-
|
|
363
|
-
artifacts
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
// tag style can be rewrite with new options tags
|
|
367
|
-
options.tags = options.tags || mergedData.tags;
|
|
368
|
-
calculateSummary(mergedData, options);
|
|
369
|
-
|
|
370
|
-
return mergedData;
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
module.exports = async (reportDataList, userOptions = {}) => {
|
|
374
|
-
|
|
375
|
-
Util.initLoggingLevel(userOptions.logging, 'merge');
|
|
376
|
-
|
|
377
|
-
Util.logInfo('merging report data ...');
|
|
378
|
-
|
|
379
|
-
const options = {
|
|
380
|
-
... getDefaultOptions(),
|
|
381
|
-
... userOptions
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
// init options
|
|
385
|
-
const outputFile = Util.resolveOutputFile(options.outputFile);
|
|
386
|
-
options.outputFile = outputFile;
|
|
387
|
-
// init outputDir
|
|
388
|
-
const outputDir = path.dirname(outputFile);
|
|
389
|
-
// clean
|
|
390
|
-
Util.initDir(outputDir);
|
|
391
|
-
// for attachment path handler
|
|
392
|
-
options.outputDir = outputDir;
|
|
393
|
-
|
|
394
|
-
const dataList = await initDataList(reportDataList, options);
|
|
395
|
-
if (!dataList) {
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const reportData = await mergeDataList(dataList, options);
|
|
400
|
-
// console.log(reportData.artifacts);
|
|
401
|
-
|
|
402
|
-
const rawData = dataList.map((it) => it.data);
|
|
403
|
-
return generateReport(reportData, options, rawData);
|
|
404
|
-
|
|
405
|
-
};
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const Util = require('./utils/util.js');
|
|
4
|
+
const generateReport = require('./generate-report.js');
|
|
5
|
+
const getDefaultOptions = require('./default/options.js');
|
|
6
|
+
const { calculateSummary, getTrends } = require('./common.js');
|
|
7
|
+
const { mergeGlobalCoverageReport } = require('./plugins/coverage/coverage.js');
|
|
8
|
+
const { StreamZip } = require('./packages/monocart-reporter-vendor.js');
|
|
9
|
+
|
|
10
|
+
const checkReportData = (item) => {
|
|
11
|
+
if (item && typeof item === 'object') {
|
|
12
|
+
if (item.rows && item.columns && item.summary) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const unzipDataFile = async (item, num, options) => {
|
|
19
|
+
|
|
20
|
+
if (!options.cacheDir) {
|
|
21
|
+
options.cacheDir = path.resolve(options.outputDir, '.cache');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const unzipDir = path.resolve(options.cacheDir, `extracted-${num}`);
|
|
25
|
+
|
|
26
|
+
fs.mkdirSync(unzipDir, {
|
|
27
|
+
recursive: true
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const zip = new StreamZip({
|
|
31
|
+
file: item
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await zip.extract(null, unzipDir);
|
|
35
|
+
|
|
36
|
+
// Do not forget to close the file once you're done
|
|
37
|
+
await zip.close();
|
|
38
|
+
|
|
39
|
+
const filename = path.basename(item, '.zip');
|
|
40
|
+
let jsonPath = path.resolve(unzipDir, `${filename}.json`);
|
|
41
|
+
if (fs.existsSync(jsonPath)) {
|
|
42
|
+
return jsonPath;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// filename for both html and json
|
|
46
|
+
const htmlMap = {};
|
|
47
|
+
const jsonMap = {};
|
|
48
|
+
Util.forEachFile(unzipDir, (name, dir) => {
|
|
49
|
+
if (name.endsWith('.html')) {
|
|
50
|
+
htmlMap[path.basename(name, '.html')] = true;
|
|
51
|
+
} else if (name.endsWith('.json')) {
|
|
52
|
+
jsonMap[path.basename(name, '.json')] = true;
|
|
53
|
+
}
|
|
54
|
+
}, true);
|
|
55
|
+
|
|
56
|
+
const keys = Object.keys(htmlMap);
|
|
57
|
+
for (const fn of keys) {
|
|
58
|
+
if (jsonMap[fn]) {
|
|
59
|
+
jsonPath = path.resolve(unzipDir, `${fn}.json`);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return jsonPath;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const getReportData = async (item, num, options) => {
|
|
68
|
+
if (typeof item === 'string') {
|
|
69
|
+
|
|
70
|
+
// json or zip path
|
|
71
|
+
const extname = path.extname(item);
|
|
72
|
+
if (extname === '.zip') {
|
|
73
|
+
item = await unzipDataFile(item, num, options);
|
|
74
|
+
// console.log(item);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// json path
|
|
78
|
+
const data = Util.readJSONSync(item);
|
|
79
|
+
if (!data) {
|
|
80
|
+
Util.logError(`failed to read report data file: ${item}`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// for finding attachments
|
|
85
|
+
const jsonDir = Util.relativePath(path.dirname(item));
|
|
86
|
+
|
|
87
|
+
Util.logInfo(`report data loaded: ${Util.relativePath(item)}`);
|
|
88
|
+
return {
|
|
89
|
+
jsonDir,
|
|
90
|
+
data
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!checkReportData(item)) {
|
|
95
|
+
Util.logError(`unmatched report data format: ${item}`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// json
|
|
100
|
+
return {
|
|
101
|
+
jsonDir: item.outputDir,
|
|
102
|
+
data: item
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const initDataList = async (reportDataList, options) => {
|
|
107
|
+
if (!Util.isList(reportDataList)) {
|
|
108
|
+
Util.logError(`invalid report data list: ${reportDataList}`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let hasError = false;
|
|
113
|
+
const list = [];
|
|
114
|
+
|
|
115
|
+
let num = 1;
|
|
116
|
+
for (const item of reportDataList) {
|
|
117
|
+
|
|
118
|
+
const data = await getReportData(item, num, options);
|
|
119
|
+
if (!data) {
|
|
120
|
+
hasError = true;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
num += 1;
|
|
124
|
+
|
|
125
|
+
list.push(data);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (hasError) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// sort list
|
|
133
|
+
|
|
134
|
+
list.sort((a, b) => {
|
|
135
|
+
if (a.jsonDir > b.jsonDir) {
|
|
136
|
+
return 1;
|
|
137
|
+
}
|
|
138
|
+
return -1;
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// console.log(list.map((it) => it.jsonDir));
|
|
142
|
+
|
|
143
|
+
return list;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const copyTarget = (targetPath, fromDir, toDir) => {
|
|
147
|
+
const oldPath = path.resolve(fromDir, targetPath);
|
|
148
|
+
if (fs.existsSync(oldPath)) {
|
|
149
|
+
const newPath = path.resolve(toDir, targetPath);
|
|
150
|
+
if (oldPath !== newPath) {
|
|
151
|
+
fs.cpSync(oldPath, newPath, {
|
|
152
|
+
force: true,
|
|
153
|
+
recursive: true
|
|
154
|
+
});
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const attachmentsHandler = (data, jsonDir, outputDir, attachmentPathHandler) => {
|
|
161
|
+
|
|
162
|
+
const extras = Util.getAttachmentPathExtras(data);
|
|
163
|
+
Util.forEach(data.rows, (row) => {
|
|
164
|
+
if (row.type !== 'case' || !row.attachments) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
row.attachments.forEach((attachment) => {
|
|
168
|
+
if (!attachment.path) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// copy attachment
|
|
173
|
+
const done = copyTarget(attachment.path, jsonDir, outputDir);
|
|
174
|
+
if (!done) {
|
|
175
|
+
Util.logError(`failed to copy: ${attachment.path}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (attachmentPathHandler) {
|
|
179
|
+
const newPath = attachmentPathHandler(attachment.path, extras);
|
|
180
|
+
if (newPath) {
|
|
181
|
+
attachment.path = newPath;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const mergeArtifacts = async (artifactsList, options) => {
|
|
189
|
+
|
|
190
|
+
const outputDir = options.outputDir;
|
|
191
|
+
|
|
192
|
+
const artifacts = [];
|
|
193
|
+
const coverageList = [];
|
|
194
|
+
const coverageRawList = [];
|
|
195
|
+
|
|
196
|
+
const jsonDirMap = {};
|
|
197
|
+
|
|
198
|
+
artifactsList.forEach((item) => {
|
|
199
|
+
const jsonDir = item.jsonDir;
|
|
200
|
+
let hasAssets = false;
|
|
201
|
+
item.artifacts.forEach((art) => {
|
|
202
|
+
|
|
203
|
+
// merge global coverage
|
|
204
|
+
if (art.global && art.type === 'coverage') {
|
|
205
|
+
coverageList.push({
|
|
206
|
+
jsonDir,
|
|
207
|
+
art
|
|
208
|
+
});
|
|
209
|
+
if (art.rawDir) {
|
|
210
|
+
const rawDir = path.resolve(jsonDir, art.rawDir);
|
|
211
|
+
if (fs.existsSync(rawDir)) {
|
|
212
|
+
coverageRawList.push(rawDir);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const targetDirName = path.dirname(art.path);
|
|
219
|
+
// copy all files in art dir, like network, audit
|
|
220
|
+
copyTarget(targetDirName, jsonDir, outputDir);
|
|
221
|
+
hasAssets = true;
|
|
222
|
+
|
|
223
|
+
// update path relative to cwd/root
|
|
224
|
+
art.path = Util.relativePath(path.resolve(outputDir, art.path));
|
|
225
|
+
|
|
226
|
+
artifacts.push(art);
|
|
227
|
+
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// copy assets dir
|
|
231
|
+
if (hasAssets) {
|
|
232
|
+
copyTarget('assets', jsonDir, outputDir);
|
|
233
|
+
jsonDirMap[jsonDir] = true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// merge global coverage raws
|
|
239
|
+
if (coverageRawList.length) {
|
|
240
|
+
const coverage = await mergeGlobalCoverageReport(coverageRawList, options);
|
|
241
|
+
if (coverage) {
|
|
242
|
+
artifacts.push(coverage);
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
|
|
246
|
+
// copy single one art
|
|
247
|
+
const item = coverageList.find((it) => it.art.path);
|
|
248
|
+
if (item) {
|
|
249
|
+
const { art, jsonDir } = item;
|
|
250
|
+
|
|
251
|
+
const targetDirName = path.dirname(art.path);
|
|
252
|
+
// copy all files in art dir, like network, audit
|
|
253
|
+
copyTarget(targetDirName, jsonDir, outputDir);
|
|
254
|
+
|
|
255
|
+
if (!jsonDirMap[jsonDir]) {
|
|
256
|
+
copyTarget('assets', jsonDir, outputDir);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// update path relative to cwd/root
|
|
260
|
+
art.path = Util.relativePath(path.resolve(outputDir, art.path));
|
|
261
|
+
|
|
262
|
+
artifacts.push(art);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return artifacts;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const mergeDataList = async (dataList, options) => {
|
|
270
|
+
|
|
271
|
+
const { outputFile, outputDir } = options;
|
|
272
|
+
|
|
273
|
+
const trends = await getTrends(options.trend);
|
|
274
|
+
|
|
275
|
+
const attachmentPathHandler = typeof options.attachmentPath === 'function' ? options.attachmentPath : null;
|
|
276
|
+
|
|
277
|
+
const startDates = [];
|
|
278
|
+
const dateRanges = [];
|
|
279
|
+
const metadata = {};
|
|
280
|
+
const system = [];
|
|
281
|
+
const rows = [];
|
|
282
|
+
const artifactsList = [];
|
|
283
|
+
|
|
284
|
+
for (const dataItem of dataList) {
|
|
285
|
+
|
|
286
|
+
const { data, jsonDir } = dataItem;
|
|
287
|
+
|
|
288
|
+
attachmentsHandler(data, jsonDir, outputDir, attachmentPathHandler);
|
|
289
|
+
|
|
290
|
+
startDates.push(data.date);
|
|
291
|
+
dateRanges.push({
|
|
292
|
+
start: data.date,
|
|
293
|
+
end: data.date + data.duration
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// merge metadata (may collect from diff shard)
|
|
297
|
+
Object.assign(metadata, data.metadata);
|
|
298
|
+
|
|
299
|
+
// merge system
|
|
300
|
+
system.push(data.system);
|
|
301
|
+
|
|
302
|
+
// merge rows
|
|
303
|
+
rows.push({
|
|
304
|
+
// add shard level suite
|
|
305
|
+
title: data.system.hostname,
|
|
306
|
+
type: 'suite',
|
|
307
|
+
suiteType: 'shard',
|
|
308
|
+
caseNum: data.summary.tests.value,
|
|
309
|
+
subs: data.rows
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
if (data.artifacts) {
|
|
313
|
+
artifactsList.push({
|
|
314
|
+
jsonDir,
|
|
315
|
+
artifacts: data.artifacts
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const artifacts = await mergeArtifacts(artifactsList, options);
|
|
322
|
+
|
|
323
|
+
// base on first one, do not change dataList (need for onData)
|
|
324
|
+
const mergedData = {
|
|
325
|
+
... dataList[0].data
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
// merge new options
|
|
329
|
+
['traceViewerUrl', 'mermaid'].forEach((k) => {
|
|
330
|
+
if (Util.hasOwn(options, k)) {
|
|
331
|
+
mergedData[k] = options[k];
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const reportName = options.name || mergedData.name;
|
|
336
|
+
|
|
337
|
+
const date = Math.min.apply(null, startDates);
|
|
338
|
+
const dateH = new Date(date).toLocaleString();
|
|
339
|
+
|
|
340
|
+
const duration = Util.getDuration(dateRanges, options.durationStrategy);
|
|
341
|
+
const durationH = Util.TF(duration);
|
|
342
|
+
|
|
343
|
+
Object.assign(mergedData, {
|
|
344
|
+
name: reportName,
|
|
345
|
+
|
|
346
|
+
date,
|
|
347
|
+
dateH,
|
|
348
|
+
duration,
|
|
349
|
+
durationH,
|
|
350
|
+
|
|
351
|
+
// for current merge process
|
|
352
|
+
cwd: Util.formatPath(process.cwd()),
|
|
353
|
+
outputFile: Util.relativePath(outputFile),
|
|
354
|
+
outputDir: Util.relativePath(outputDir),
|
|
355
|
+
|
|
356
|
+
metadata,
|
|
357
|
+
// system is array in shard mode, because have multiple machines
|
|
358
|
+
system,
|
|
359
|
+
trends,
|
|
360
|
+
|
|
361
|
+
rows,
|
|
362
|
+
|
|
363
|
+
artifacts
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// tag style can be rewrite with new options tags
|
|
367
|
+
options.tags = options.tags || mergedData.tags;
|
|
368
|
+
calculateSummary(mergedData, options);
|
|
369
|
+
|
|
370
|
+
return mergedData;
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
module.exports = async (reportDataList, userOptions = {}) => {
|
|
374
|
+
|
|
375
|
+
Util.initLoggingLevel(userOptions.logging, 'merge');
|
|
376
|
+
|
|
377
|
+
Util.logInfo('merging report data ...');
|
|
378
|
+
|
|
379
|
+
const options = {
|
|
380
|
+
... getDefaultOptions(),
|
|
381
|
+
... userOptions
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// init options
|
|
385
|
+
const outputFile = Util.resolveOutputFile(options.outputFile);
|
|
386
|
+
options.outputFile = outputFile;
|
|
387
|
+
// init outputDir
|
|
388
|
+
const outputDir = path.dirname(outputFile);
|
|
389
|
+
// clean
|
|
390
|
+
Util.initDir(outputDir);
|
|
391
|
+
// for attachment path handler
|
|
392
|
+
options.outputDir = outputDir;
|
|
393
|
+
|
|
394
|
+
const dataList = await initDataList(reportDataList, options);
|
|
395
|
+
if (!dataList) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const reportData = await mergeDataList(dataList, options);
|
|
400
|
+
// console.log(reportData.artifacts);
|
|
401
|
+
|
|
402
|
+
const rawData = dataList.map((it) => it.data);
|
|
403
|
+
return generateReport(reportData, options, rawData);
|
|
404
|
+
|
|
405
|
+
};
|