autotel-vitest 0.1.0
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/README.md +144 -0
- package/dist/index.cjs +134 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/reporter.cjs +87 -0
- package/dist/reporter.cjs.map +1 -0
- package/dist/reporter.d.cts +36 -0
- package/dist/reporter.d.ts +36 -0
- package/dist/reporter.js +82 -0
- package/dist/reporter.js.map +1 -0
- package/package.json +53 -0
- package/src/index.test.ts +230 -0
- package/src/index.ts +105 -0
- package/src/reporter.test.ts +193 -0
- package/src/reporter.ts +119 -0
package/src/reporter.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional Vitest reporter that creates OTel spans for each test and suite.
|
|
3
|
+
* Runs in the runner process; ensure autotel.init() is called in globalSetup so spans are exported.
|
|
4
|
+
*
|
|
5
|
+
* Use when you want test/suite timing and hierarchy in OTLP from the runner side.
|
|
6
|
+
* For "test → instrumented code" in one trace (worker side), use the test fixture.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // vitest.config.ts
|
|
10
|
+
* import { defineConfig } from 'vitest/config';
|
|
11
|
+
*
|
|
12
|
+
* export default defineConfig({
|
|
13
|
+
* test: {
|
|
14
|
+
* reporters: ['default', 'autotel-vitest/reporter'],
|
|
15
|
+
* globalSetup: './globalSetup.ts', // must call init()
|
|
16
|
+
* },
|
|
17
|
+
* });
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { Reporter, TestCase, TestModule, TestSuite } from 'vitest/node';
|
|
21
|
+
import { getTracer, SpanStatusCode } from 'autotel';
|
|
22
|
+
|
|
23
|
+
const TRACER_NAME = 'vitest-reporter';
|
|
24
|
+
const TRACER_VERSION = '0.1.0';
|
|
25
|
+
|
|
26
|
+
type SpanEntry = {
|
|
27
|
+
span: ReturnType<ReturnType<typeof getTracer>['startSpan']>;
|
|
28
|
+
moduleId: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** Convert a vitest TestError-like object to a standard Error for OTel. */
|
|
32
|
+
function toError(testError: { message?: string; stack?: string }): Error {
|
|
33
|
+
const err = new Error(testError.message ?? 'Unknown error');
|
|
34
|
+
if (testError.stack) err.stack = testError.stack;
|
|
35
|
+
return err;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Vitest Reporter that creates one span per test and one per suite (as parents).
|
|
40
|
+
* Requires autotel.init() in globalSetup so spans are exported.
|
|
41
|
+
*/
|
|
42
|
+
class OtelReporter implements Reporter {
|
|
43
|
+
private testSpans = new Map<string, SpanEntry>();
|
|
44
|
+
private suiteSpans = new Map<string, SpanEntry>();
|
|
45
|
+
|
|
46
|
+
onTestCaseReady(testCase: TestCase): void {
|
|
47
|
+
const tracer = getTracer(TRACER_NAME, TRACER_VERSION);
|
|
48
|
+
const moduleId = testCase.module.moduleId ?? '';
|
|
49
|
+
const span = tracer.startSpan(`test:${testCase.name}`, {
|
|
50
|
+
attributes: {
|
|
51
|
+
'test.name': testCase.name,
|
|
52
|
+
'test.fullName': testCase.fullName,
|
|
53
|
+
'test.file': moduleId,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
this.testSpans.set(testCase.id, { span, moduleId });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
onTestCaseResult(testCase: TestCase): void {
|
|
60
|
+
const entry = this.testSpans.get(testCase.id);
|
|
61
|
+
if (!entry) return;
|
|
62
|
+
|
|
63
|
+
const result = testCase.result();
|
|
64
|
+
if (result.state === 'failed') {
|
|
65
|
+
entry.span.setStatus({ code: SpanStatusCode.ERROR });
|
|
66
|
+
if (result.errors && result.errors.length > 0) {
|
|
67
|
+
for (const error of result.errors) {
|
|
68
|
+
entry.span.recordException(toError(error));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
entry.span.end();
|
|
73
|
+
this.testSpans.delete(testCase.id);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
onTestSuiteReady(testSuite: TestSuite): void {
|
|
77
|
+
const tracer = getTracer(TRACER_NAME, TRACER_VERSION);
|
|
78
|
+
const moduleId = testSuite.module.moduleId ?? '';
|
|
79
|
+
const span = tracer.startSpan(`suite:${testSuite.name}`, {
|
|
80
|
+
attributes: {
|
|
81
|
+
'suite.name': testSuite.name,
|
|
82
|
+
'suite.file': moduleId,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
this.suiteSpans.set(testSuite.id, { span, moduleId });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
onTestSuiteResult(testSuite: TestSuite): void {
|
|
89
|
+
const entry = this.suiteSpans.get(testSuite.id);
|
|
90
|
+
if (!entry) return;
|
|
91
|
+
|
|
92
|
+
const state = testSuite.state();
|
|
93
|
+
if (state === 'failed') {
|
|
94
|
+
entry.span.setStatus({ code: SpanStatusCode.ERROR });
|
|
95
|
+
}
|
|
96
|
+
entry.span.end();
|
|
97
|
+
this.suiteSpans.delete(testSuite.id);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
onTestModuleEnd(testModule: TestModule): void {
|
|
101
|
+
const moduleId = testModule.moduleId;
|
|
102
|
+
// Clean up any remaining spans for this specific module
|
|
103
|
+
for (const [key, entry] of this.testSpans) {
|
|
104
|
+
if (entry.moduleId === moduleId) {
|
|
105
|
+
entry.span.end();
|
|
106
|
+
this.testSpans.delete(key);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (const [key, entry] of this.suiteSpans) {
|
|
110
|
+
if (entry.moduleId === moduleId) {
|
|
111
|
+
entry.span.end();
|
|
112
|
+
this.suiteSpans.delete(key);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export { OtelReporter };
|
|
119
|
+
export default OtelReporter;
|