-
Notifications
You must be signed in to change notification settings - Fork 60
/
utils.js
157 lines (138 loc) · 5.98 KB
/
utils.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
const glob = require('@actions/glob');
const core = require('@actions/core');
const fs = require('fs');
const parser = require('xml-js');
const resolveFileAndLine = (file, classname, output, isFilenameInOutput) => {
// extract filename from classname and remove suffix
let filename;
let filenameWithPackage;
if (isFilenameInOutput) {
filename = output.split(':')[0].trim();
filenameWithPackage = filename;
} else {
filename = file ? file : classname.split('.').slice(-1)[0].split('(')[0];
filenameWithPackage = classname.replace(/\./g, '/');
}
const matches = output.match(new RegExp(`${filename}.*?:\\d+`, 'g'));
if (!matches) return {filename: filename, filenameWithPackage: filenameWithPackage, line: 1};
const [lastItem] = matches.slice(-1);
const [, line] = lastItem.split(':');
core.debug(`Resolved file ${filenameWithPackage} with name ${filename} and line ${line}`);
return {filename, filenameWithPackage, line: parseInt(line)};
};
const resolvePath = async filenameWithPackage => {
core.debug(`Resolving path for ${filenameWithPackage}`);
const globber = await glob.create([`**/${filenameWithPackage}.*`, `**/${filenameWithPackage}`].join('\n'), {followSymbolicLinks: false});
const results = await globber.glob();
core.debug(`Matched files: ${results}`);
const searchPath = globber.getSearchPaths()[0];
let path = '';
if (results.length) {
// skip various temp folders
const found = results.find(r => !r.includes('__pycache__') && !r.endsWith('.class'));
if (found) path = found.slice(searchPath.length + 1);
else path = filenameWithPackage;
} else {
path = filenameWithPackage;
}
core.debug(`Resolved path: ${path}`);
// canonicalize to make windows paths use forward slashes
const canonicalPath = path.replace(/\\/g, '/');
core.debug(`Canonical path: ${canonicalPath}`);
return canonicalPath;
};
function getTestsuites(report) {
if (report.testsuite) {
return [report.testsuite];
}
if (!report.testsuites || !report.testsuites.testsuite) {
return [];
}
if (Array.isArray(report.testsuites.testsuite)) {
return report.testsuites.testsuite;
}
return [report.testsuites.testsuite];
}
async function parseFile(file, isFilenameInStackTrace) {
core.debug(`Parsing file ${file}`);
let count = 0;
let skipped = 0;
let annotations = [];
const data = await fs.promises.readFile(file);
const report = JSON.parse(parser.xml2json(data, {compact: true}));
core.debug(`parsed report: ${JSON.stringify(report)}`);
const testsuites = getTestsuites(report);
core.debug(`test suites: ${JSON.stringify(testsuites)}`);
for (const testsuite of testsuites) {
const testcases = Array.isArray(testsuite.testcase)
? testsuite.testcase
: testsuite.testcase
? [testsuite.testcase]
: [];
for (const testcase of testcases) {
count++;
if (testcase.skipped) skipped++;
if (testcase.failure || testcase.flakyFailure || testcase.error) {
let testcaseData =
(testcase.failure && testcase.failure._cdata) ||
(testcase.failure && testcase.failure._text) ||
(testcase.flakyFailure && testcase.flakyFailure._cdata) ||
(testcase.flakyFailure && testcase.flakyFailure._text) ||
(testcase.error && testcase.error._cdata) ||
(testcase.error && testcase.error._text) ||
'';
testcaseData = Array.isArray(testcaseData) ? testcaseData : [testcaseData];
const stackTrace = (testcaseData.length ? testcaseData.join('') : '').trim();
const message = (
(testcase.failure &&
testcase.failure._attributes &&
testcase.failure._attributes.message) ||
(testcase.flakyFailure &&
testcase.flakyFailure._attributes &&
testcase.flakyFailure._attributes.message) ||
(testcase.error &&
testcase.error._attributes &&
testcase.error._attributes.message) ||
stackTrace.split('\n').slice(0, 2).join('\n') ||
testcase._attributes.name
).trim();
const {filename, filenameWithPackage, line} = resolveFileAndLine(
testcase._attributes.file,
testcase._attributes.classname,
stackTrace,
isFilenameInStackTrace
);
const path = await resolvePath(filenameWithPackage);
const title = `${filename}.${testcase._attributes.name}`;
core.info(`${path}:${line} | ${message.replace(/\n/g, ' ')}`);
annotations.push({
path,
start_line: line,
end_line: line,
start_column: 0,
end_column: 0,
annotation_level: 'failure',
title,
message,
raw_details: stackTrace
});
}
}
}
return {count, skipped, annotations};
}
const parseTestReports = async (reportPaths, isFilenameInStackTrace) => {
const globber = await glob.create(reportPaths, {followSymbolicLinks: false});
let annotations = [];
let count = 0;
let skipped = 0;
for await (const file of globber.globGenerator()) {
const {count: c, skipped: s, annotations: a} = await parseFile(file, isFilenameInStackTrace);
if (c === 0) continue;
count += c;
skipped += s;
annotations = annotations.concat(a);
}
return {count, skipped, annotations};
};
module.exports = { resolveFileAndLine, resolvePath, parseFile, parseTestReports };