-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.js
132 lines (109 loc) · 2.98 KB
/
index.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
import { summary, error as annotateError } from '@actions/core'
import ErrorStackParser from 'error-stack-parser'
import parseReport from 'node-test-parser'
import url from 'url'
import StackUtils from 'stack-utils'
const workspace = process.env.GITHUB_WORKSPACE
const workspacePrefixRegex = new RegExp(`^${workspace}`)
const stackUtils = new StackUtils({
cwd: process.cwd(),
internals: StackUtils.nodeInternals()
})
export default async function* githubSummaryReporter(source) {
const report = await parseReport(source)
const tests = report.tests
const tableHeader = [
{ data: 'Passed', header: true },
{ data: 'Failed', header: true },
{ data: 'Skipped', header: true },
{ data: 'Duration', header: true }
]
const tableRow = [
`${tests.filter(s => !s.error && !s.failure && !s.skip).length}`,
`${tests.filter(s => s.error || s.failure).length}`,
`${tests.filter(s => s.skip).length}`,
`${parseInt(report.duration)}ms`
]
const reportDetails = tests
.map(test =>
formatDetails(`${statusEmoji(test)} ${test.name}`, testDetails(test))
)
.join('\n')
summary
.addHeading('Node.js Test Results', 2)
.addTable([tableHeader, tableRow])
.addHeading('Details', 3)
.addRaw(reportDetails)
.write()
yield ''
}
function testDetails(test) {
if (!test.tests.length) {
return formatMessage(test)
}
return test.tests
.map(test =>
formatDetails(`${statusEmoji(test)} ${test.name}`, testDetails(test))
)
.join('\n')
}
function formatMessage(test) {
if (test.skip) {
return 'Test skipped'
}
const error = test.error || test.failure
if (!error) {
return 'Test passed'
}
let errorMessage = error.message
if (test.diagnostic) {
errorMessage += `\n\n${test.diagnostic}`
}
if (error.cause && error.cause.stack) {
const cleanStack = stackUtils.clean(error.cause.stack)
errorMessage += `\n\nStack:\n\`\`\`\n${cleanStack}\`\`\`\n`
const errorLocation = findErrorLocation(error.cause)
if (errorLocation) {
annotateError(error, errorLocation)
}
}
return errorMessage
}
function formatDetails(heading, content) {
return `<details>
<summary>${heading}</summary>
<blockquote>
${content}
</blockquote>
</details>`
}
function statusEmoji(test) {
if (test.failure || test.error) {
return ':x:'
} else if (test.skip) {
return ':leftwards_arrow_with_hook:'
} else {
return ':white_check_mark:'
}
}
function findErrorLocation(error) {
const [firstFrame] = ErrorStackParser.parse(error)
if (!firstFrame) {
return
}
return {
file: getRelativeFilePath(firstFrame.fileName),
startLine: firstFrame.lineNumber,
startColumn: firstFrame.columnNumber
}
}
export function getSafePath(path) {
if (path.startsWith('file')) {
return path
}
return url.pathToFileURL(path).href
}
export function getRelativeFilePath(path) {
const filePath = getSafePath(path)
return new URL(filePath).pathname.replace(workspacePrefixRegex, '')
}