-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Experimentally expose internal events for custom reporters
Add a new `observeRunsFromConfig` experiment, which allows a test run to be observed by a function installed through an `ava.config.*` file. The function has access to AVA's internal events, which can then be used to report to a file. AVA's internal event structure is not currently covered by any SemVer guarantees, which is why this feature requires the experimental opt-in. Does not currently support watch mode. Only the first run is observed.
- Loading branch information
1 parent
6790d50
commit adbfcde
Showing
11 changed files
with
229 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import type {StateChangeEvent} from '../types/state-change-events.d'; | ||
|
||
export type Event = StateChangeEvent; | ||
|
||
export type ObservedRun = { | ||
events: AsyncIterableIterator<Event>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export async function * asyncEventIteratorFromApi(api) { | ||
// TODO: support multiple runs (watch mode) | ||
const {value: plan} = await api.events('run').next(); | ||
|
||
for await (const stateChange of plan.status.events('stateChange')) { | ||
yield stateChange; | ||
|
||
if (stateChange.type === 'end' || stateChange.type === 'interrupt') { | ||
break; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
internal-events.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import fs from 'node:fs/promises'; | ||
|
||
const internalEvents = []; | ||
|
||
export default { | ||
files: [ | ||
'test.js', | ||
], | ||
nonSemVerExperiments: { | ||
observeRunsFromConfig: true, | ||
}, | ||
async observeRun(run) { | ||
for await (const event of run.events) { | ||
internalEvents.push(event); | ||
} | ||
|
||
await fs.writeFile('internal-events.json', JSON.stringify(internalEvents)); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"type": "module" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import test from 'ava'; | ||
|
||
test('placeholder', t => { | ||
t.pass(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import fs from 'node:fs/promises'; | ||
import {fileURLToPath} from 'node:url'; | ||
|
||
import test from '@ava/test'; | ||
|
||
import {fixture} from '../helpers/exec.js'; | ||
|
||
test('internal events are emitted', async t => { | ||
await fixture(); | ||
|
||
const result = JSON.parse(await fs.readFile(fileURLToPath(new URL('fixtures/internal-events.json', import.meta.url)))); | ||
|
||
t.like(result[0], { | ||
type: 'starting', | ||
testFile: fileURLToPath(new URL('fixtures/test.js', import.meta.url)), | ||
}); | ||
|
||
const testPassedEvent = result.find(event => event.type === 'test-passed'); | ||
t.like(testPassedEvent, { | ||
type: 'test-passed', | ||
title: 'placeholder', | ||
testFile: fileURLToPath(new URL('fixtures/test.js', import.meta.url)), | ||
}); | ||
|
||
t.like(result.at(-1), { | ||
type: 'end', | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
type ErrorSource = { | ||
isDependency: boolean; | ||
isWithinProject: boolean; | ||
file: string; | ||
line: number; | ||
}; | ||
|
||
type SerializedErrorBase = { | ||
message: string; | ||
name: string; | ||
originalError: unknown; | ||
stack: string; | ||
}; | ||
|
||
type AggregateSerializedError = SerializedErrorBase & { | ||
type: 'aggregate'; | ||
errors: SerializedError[]; | ||
}; | ||
|
||
type NativeSerializedError = SerializedErrorBase & { | ||
type: 'native'; | ||
source: ErrorSource | undefined; | ||
}; | ||
|
||
type AvaSerializedError = SerializedErrorBase & { | ||
type: 'ava'; | ||
assertion: string; | ||
improperUsage: unknown | undefined; | ||
formattedCause: unknown | undefined; | ||
formattedDetails: unknown | unknown[]; | ||
source: ErrorSource | undefined; | ||
}; | ||
|
||
type SerializedError = AggregateSerializedError | NativeSerializedError | AvaSerializedError; | ||
|
||
export type StateChangeEvent = { | ||
type: 'starting'; | ||
testFile: string; | ||
} | { | ||
type: 'stats'; | ||
stats: { | ||
byFile: Map<string, { | ||
declaredTests: number; | ||
failedHooks: number; | ||
failedTests: number; | ||
internalErrors: number; | ||
remainingTests: number; | ||
passedKnownFailingTests: number; | ||
passedTests: number; | ||
selectedTests: number; | ||
selectingLines: boolean; | ||
skippedTests: number; | ||
todoTests: number; | ||
uncaughtExceptions: number; | ||
unhandledRejections: number; | ||
}>; | ||
declaredTests: number; | ||
failedHooks: number; | ||
failedTests: number; | ||
failedWorkers: number; | ||
files: number; | ||
parallelRuns: { | ||
currentIndex: number; | ||
totalRuns: number; | ||
} | undefined; | ||
finishedWorkers: number; | ||
internalErrors: number; | ||
remainingTests: number; | ||
passedKnownFailingTests: number; | ||
passedTests: number; | ||
selectedTests: number; | ||
sharedWorkerErrors: number; | ||
skippedTests: number; | ||
timedOutTests: number; | ||
timeouts: number; | ||
todoTests: number; | ||
uncaughtExceptions: number; | ||
unhandledRejections: number; | ||
}; | ||
} | { | ||
type: 'declared-test'; | ||
title: string; | ||
knownFailing: boolean; | ||
todo: boolean; | ||
testFile: string; | ||
} | { | ||
type: 'selected-test'; | ||
title: string; | ||
knownFailing: boolean; | ||
skip: boolean; | ||
todo: boolean; | ||
testFile: string; | ||
} | { | ||
type: 'test-register-log-reference'; | ||
title: string; | ||
logs: string[]; | ||
testFile: string; | ||
} | { | ||
type: 'test-passed'; | ||
title: string; | ||
duration: number; | ||
knownFailing: boolean; | ||
logs: string[]; | ||
testFile: string; | ||
} | { | ||
type: 'test-failed'; | ||
title: string; | ||
err: SerializedError; | ||
duration: number; | ||
knownFailing: boolean; | ||
logs: string[]; | ||
testFile: string; | ||
} | { | ||
type: 'worker-finished'; | ||
forcedExit: boolean; | ||
testFile: string; | ||
} | { | ||
type: 'worker-failed'; | ||
nonZeroExitCode?: boolean; | ||
signal?: string; | ||
err?: SerializedError; | ||
} | { | ||
type: 'touched-files'; | ||
files: { | ||
changedFiles: string[]; | ||
temporaryFiles: string[]; | ||
}; | ||
} | { | ||
type: 'worker-stdout'; | ||
chunk: Uint8Array; | ||
testFile: string; | ||
} | { | ||
type: 'worker-stderr'; | ||
chunk: Uint8Array; | ||
testFile: string; | ||
} | { | ||
type: 'timeout'; | ||
period: number; | ||
pendingTests: Map<string, Set<string>>; | ||
} | ||
| { | ||
type: 'end'; | ||
}; |