Skip to content

Commit

Permalink
Support reporters (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
mcollina committed Jan 31, 2024
1 parent 60d95a1 commit 9499d36
Show file tree
Hide file tree
Showing 8 changed files with 413 additions and 13 deletions.
16 changes: 13 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,27 @@ jobs:
node-version: [18.x, 20.x, 21.x]
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Use Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

- name: Install
run: |
npm install
- name: Lint
run: |
npm run lint
- name: Run tests
run: |
npm run test
npm run unit -- --reporter spec --reporter md:report.md --reporter gh
- name: Upload report
shell: bash
if: success() || failure()
run: |
cat report.md >> "$GITHUB_STEP_SUMMARY"
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,70 @@ Note the use of `incremental: true`, which speed up compilation massively.
* `--ignore` or `-i`, ignore a glob pattern, and not look for tests there
* `--expose-gc`, exposes the gc() function to tests
* `--pattern` or `-p`, run tests matching the given glob pattern
* `--reporter` or `-r`, set up a reporter, use a colon to set a file destination. Default: `spec`.

## Reporters

Here are the available reporters:

* `md`: creates a markdown table, useful for setting up a Summary in your GitHub Action
* `gh`: emits `::error` workflow commands for GitHub Actions to show inlined error. Enabled by default when running on GHA.
* `tap`: outputs the test results in the TAP format.
* `spec`: outputs the test results in a human-readable format.
* `dot`: outputs the test results in a compact format, where each passing test is represented by a ., and each failing test is represented by a X.
* `junit`: outputs test results in a jUnit XML format

## GitHub Action Summary

The following will automatically show the summary of the test run in the summary page of GitHub Actions.

```yaml
name: ci

on:
push:
paths-ignore:
- 'docs/**'
- '*.md'
pull_request:
paths-ignore:
- 'docs/**'
- '*.md'

jobs:
test:
runs-on: ${{matrix.os}}

strategy:
matrix:
node-version: [18.x, 20.x, 21.x]
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v4

- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

- name: Install
run: |
npm install
- name: Lint
run: |
npm run lint
- name: Run tests
run: |
npm run unit -- --reporter spec --reporter md:report.md
- name: Upload report
shell: bash
if: success() || failure()
run: |
cat report.md >> "$GITHUB_STEP_SUMMARY"
```

## License

Expand Down
56 changes: 46 additions & 10 deletions borp.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
#! /usr/bin/env node

import { parseArgs } from 'node:util'
import { tap, spec } from 'node:test/reporters'
import Reporters from 'node:test/reporters'
import { mkdtemp, rm, readFile } from 'node:fs/promises'
import { createWriteStream } from 'node:fs'
import { finished } from 'node:stream/promises'
import { join, relative } from 'node:path'
import posix from 'node:path/posix'
import runWithTypeScript from './lib/run.js'
import { MarkdownReporter, GithubWorkflowFailuresReporter } from './lib/reporters.js'
import { Report } from 'c8'
import os from 'node:os'
import { execa } from 'execa'

let reporter
/* c8 ignore next 4 */
if (process.stdout.isTTY) {
/* eslint new-cap: "off" */
reporter = new spec()
} else {
reporter = tap
}
process.on('unhandledRejection', (err) => {
console.error(err)
process.exit(1)
})

const args = parseArgs({
args: process.argv.slice(2),
Expand All @@ -32,7 +31,13 @@ const args = parseArgs({
'coverage-exclude': { type: 'string', short: 'X', multiple: true },
ignore: { type: 'string', short: 'i', multiple: true },
'expose-gc': { type: 'boolean' },
help: { type: 'boolean', short: 'h' }
help: { type: 'boolean', short: 'h' },
reporter: {
type: 'string',
short: 'r',
default: ['spec'],
multiple: true
}
},
allowPositionals: true
})
Expand Down Expand Up @@ -79,13 +84,44 @@ const config = {
}

try {
const pipes = []

const reporters = {
...Reporters,
md: new MarkdownReporter(config),
gh: new GithubWorkflowFailuresReporter(config),
/* eslint new-cap: "off" */
spec: new Reporters.spec()
}

// If we're running in a GitHub action, adds the gh reporter
// by default so that we can report failures to GitHub
if (process.env.GITHUB_ACTION) {
args.values.reporter.push('gh')
}

for (const input of args.values.reporter) {
const [name, dest] = input.split(':')
const reporter = reporters[name]
if (!reporter) {
throw new Error(`Unknown reporter: ${name}`)
}
let output = process.stdout
if (dest) {
output = createWriteStream(dest)
}
pipes.push([reporter, output])
}

const stream = await runWithTypeScript(config)

stream.on('test:fail', () => {
process.exitCode = 1
})

stream.compose(reporter).pipe(process.stdout)
for (const [reporter, output] of pipes) {
stream.compose(reporter).pipe(output)
}

await finished(stream)

Expand Down
114 changes: 114 additions & 0 deletions lib/reporters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { Transform } from 'node:stream'
import { fileURLToPath } from 'node:url'

function normalizeFile (file, cwd) {
let res = file
if (file.startsWith('file://')) {
try {
res = fileURLToPath(new URL(file))
} catch (err) {
if (err.code === 'ERR_INVALID_FILE_URL_PATH') {
res = fileURLToPath(new URL(file.replace('file:///', 'file://')))
}
}
}
res = res.replace(cwd, '')
if (res.startsWith('/') || res.startsWith('\\')) {
res = res.slice(1)
}
return res
}

function eventToLine (event) {
return `* __${event.data.name}__, duration ${event.data.details.duration_ms}ms, line ${event.data.line}\n`
}

export class MarkdownReporter extends Transform {
constructor (opts) {
super({
...opts,
objectMode: true
})

this._files = {}
this._cwd = opts?.cwd
}

getFile (path) {
const file = this._files[path] || {
pass: [],
fail: []
}
this._files[path] = file
return file
}

_transform (event, encoding, callback) {
if (!event.data.file) {
callback()
return
}

const path = normalizeFile(event.data.file, this._cwd)
const file = this.getFile(path)
switch (event.type) {
case 'test:pass':
file.pass.push(event)
break
case 'test:fail':
file.fail.push(event)
break
}

callback()
}

_flush (callback) {
this.push('# Summary\n')
for (const [path, file] of Object.entries(this._files)) {
this.push(`## ${path}\n`)
if (file.pass.length > 0) {
this.push('### :white_check_mark: Pass\n')
for (const event of file.pass) {
this.push(eventToLine(event))
}
}
if (file.fail.length > 0) {
this.push('### :x: Fail\n')
for (const event of file.fail) {
this.push(eventToLine(event))
}
}
}
this.push(null)
callback()
}
}

export class GithubWorkflowFailuresReporter extends Transform {
constructor (opts) {
super({
...opts,
objectMode: true
})

this._files = {}
this._cwd = opts?.cwd
}

_transform (event, encoding, callback) {
if (!event.data.file) {
callback()
return
}

const path = normalizeFile(event.data.file, this._cwd)
switch (event.type) {
case 'test:fail':
this.push(`::error file=${path},line=${event.data.line}::${event.data.name}\n`)
break
}

callback()
}
}
1 change: 1 addition & 0 deletions lib/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export default async function runWithTypeScript (config) {
}
config.prefix = prefix
config.setup = (test) => {
/* c8 ignore next 12 */
if (test.reporter) {
for (const chunk of pushable) {
test.reporter.push(chunk)
Expand Down
2 changes: 2 additions & 0 deletions test/cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { rejects } from 'node:assert'

const borp = join(import.meta.url, '..', 'borp.js')

delete process.env.GITHUB_ACTION

test('limit concurrency', async () => {
await execa('node', [
borp,
Expand Down
1 change: 1 addition & 0 deletions test/coverage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { match, doesNotMatch } from 'node:assert'
import { execa } from 'execa'
import { join } from 'desm'

delete process.env.GITHUB_ACTION
const borp = join(import.meta.url, '..', 'borp.js')

test('coverage', async () => {
Expand Down

0 comments on commit 9499d36

Please sign in to comment.