autotel-vitest 0.3.5 → 0.4.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/dist/index.cjs +31 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -3
- package/dist/index.d.ts +1 -3
- package/dist/index.js +31 -30
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/fixture.ts +70 -0
- package/src/index.test.ts +19 -52
- package/src/index.ts +3 -61
- package/src/wiring.test.ts +59 -0
package/dist/index.cjs
CHANGED
|
@@ -22,37 +22,38 @@ function ensureCollector() {
|
|
|
22
22
|
}
|
|
23
23
|
return collector;
|
|
24
24
|
}
|
|
25
|
-
var
|
|
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
|
-
task.meta.otelSpans = spans;
|
|
51
|
-
}
|
|
25
|
+
var otelTestSpanFixture = [
|
|
26
|
+
async ({ task }, use) => {
|
|
27
|
+
ensureCollector();
|
|
28
|
+
const tracer = autotel.getTracer(TRACER_NAME, TRACER_VERSION);
|
|
29
|
+
const span = tracer.startSpan(`test:${task.name}`, {
|
|
30
|
+
attributes: {
|
|
31
|
+
"test.name": task.name,
|
|
32
|
+
"test.file": task.file?.name ?? "",
|
|
33
|
+
"test.suite": task.suite?.name ?? ""
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
const ctx = autotel.otelTrace.setSpan(autotel.context.active(), span);
|
|
37
|
+
try {
|
|
38
|
+
await autotel.context.with(ctx, () => use(span));
|
|
39
|
+
} catch (error) {
|
|
40
|
+
span.setStatus({ code: autotel.SpanStatusCode.ERROR });
|
|
41
|
+
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
42
|
+
throw error;
|
|
43
|
+
} finally {
|
|
44
|
+
span.end();
|
|
45
|
+
const traceId = span.spanContext().traceId;
|
|
46
|
+
const rootSpanId = span.spanContext().spanId;
|
|
47
|
+
const spans = collector.drainTrace(traceId, rootSpanId);
|
|
48
|
+
if (spans.length > 0) {
|
|
49
|
+
task.meta.otelSpans = spans;
|
|
52
50
|
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{ auto: true }
|
|
54
|
+
];
|
|
55
|
+
var test = vitest.test.extend({
|
|
56
|
+
_otelTestSpan: otelTestSpanFixture
|
|
56
57
|
});
|
|
57
58
|
|
|
58
59
|
Object.defineProperty(exports, "afterAll", {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["TestSpanCollector","getAutotelTracerProvider","SimpleSpanProcessor","
|
|
1
|
+
{"version":3,"sources":["../src/fixture.ts","../src/index.ts"],"names":["TestSpanCollector","getAutotelTracerProvider","SimpleSpanProcessor","getTracer","otelTrace","otelContext","SpanStatusCode","base"],"mappings":";;;;;;;;;AAUA,IAAM,WAAA,GAAc,cAAA;AACpB,IAAM,cAAA,GAAiB,OAAA;AAEvB,IAAI,SAAA,GAAsC,IAAA;AAM1C,SAAS,eAAA,GAAqC;AAC5C,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,SAAA,GAAY,IAAIA,mCAAA,EAAkB;AAClC,IAAA,MAAM,WAAWC,gCAAA,EAAyB;AAC1C,IAAA,IAAI,sBAAsB,QAAA,EAAU;AAClC,MAAC,QAAA,CAAyC,gBAAA;AAAA,QACxC,IAAIC,+BAAoB,SAAS;AAAA,OACnC;AAAA,IACF;AAAA,EACF;AACA,EAAA,OAAO,SAAA;AACT;AAOO,IAAM,mBAAA,GAAuD;AAAA,EAClE,OACE,EAAE,IAAA,EAAK,EACP,GAAA,KACG;AACH,IAAA,eAAA,EAAgB;AAChB,IAAA,MAAM,MAAA,GAASC,iBAAA,CAAU,WAAA,EAAa,cAAc,CAAA;AACpD,IAAA,MAAM,OAAO,MAAA,CAAO,SAAA,CAAU,CAAA,KAAA,EAAQ,IAAA,CAAK,IAAI,CAAA,CAAA,EAAI;AAAA,MACjD,UAAA,EAAY;AAAA,QACV,aAAa,IAAA,CAAK,IAAA;AAAA,QAClB,WAAA,EAAa,IAAA,CAAK,IAAA,EAAM,IAAA,IAAQ,EAAA;AAAA,QAChC,YAAA,EAAc,IAAA,CAAK,KAAA,EAAO,IAAA,IAAQ;AAAA;AACpC,KACD,CAAA;AACD,IAAA,MAAM,MAAMC,iBAAA,CAAU,OAAA,CAAQC,eAAA,CAAY,MAAA,IAAU,IAAI,CAAA;AACxD,IAAA,IAAI;AACF,MAAA,MAAMA,gBAAY,IAAA,CAAK,GAAA,EAAK,MAAM,GAAA,CAAI,IAAI,CAAC,CAAA;AAAA,IAC7C,SAAS,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAMC,sBAAA,CAAe,OAAO,CAAA;AAC7C,MAAA,IAAA,CAAK,eAAA,CAAgB,iBAAiB,KAAA,GAAQ,KAAA,GAAQ,IAAI,KAAA,CAAM,MAAA,CAAO,KAAK,CAAC,CAAC,CAAA;AAC9E,MAAA,MAAM,KAAA;AAAA,IACR,CAAA,SAAE;AACA,MAAA,IAAA,CAAK,GAAA,EAAI;AACT,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,WAAA,EAAY,CAAE,OAAA;AACnC,MAAA,MAAM,UAAA,GAAa,IAAA,CAAK,WAAA,EAAY,CAAE,MAAA;AACtC,MAAA,MAAM,KAAA,GAAQ,SAAA,CAAW,UAAA,CAAW,OAAA,EAAS,UAAU,CAAA;AACvD,MAAA,IAAI,KAAA,CAAM,SAAS,CAAA,EAAG;AACpB,QAAC,IAAA,CAAK,KAAiC,SAAA,GAAY,KAAA;AAAA,MACrD;AAAA,IACF;AAAA,EACF,CAAA;AAAA,EACA,EAAE,MAAM,IAAA;AACV,CAAA;AChDO,IAAM,IAAA,GAAOC,YAAK,MAAA,CAAO;AAAA,EAC9B,aAAA,EAAe;AACjB,CAAC","file":"index.cjs","sourcesContent":["import {\n getTracer,\n getAutotelTracerProvider,\n context as otelContext,\n otelTrace,\n SpanStatusCode,\n} from 'autotel';\nimport { TestSpanCollector } from 'autotel/test-span-collector';\nimport { SimpleSpanProcessor } from 'autotel/processors';\n\nconst TRACER_NAME = 'vitest-tests';\nconst TRACER_VERSION = '0.1.0';\n\nlet collector: TestSpanCollector | null = null;\n\ninterface TracerProviderWithProcessor {\n addSpanProcessor(processor: unknown): void;\n}\n\nfunction ensureCollector(): TestSpanCollector {\n if (!collector) {\n collector = new TestSpanCollector();\n const provider = getAutotelTracerProvider();\n if ('addSpanProcessor' in provider) {\n (provider as TracerProviderWithProcessor).addSpanProcessor(\n new SimpleSpanProcessor(collector),\n );\n }\n }\n return collector;\n}\n\nexport type OtelFixtureFn = (\n args: { task: { name: string; file?: { name: string }; suite?: { name: string }; meta: Record<string, unknown> } },\n use: (span: unknown) => Promise<void>,\n) => Promise<void>;\n\nexport const otelTestSpanFixture: [OtelFixtureFn, { auto: true }] = [\n async (\n { task }: { task: { name: string; file?: { name: string }; suite?: { name: string }; meta: Record<string, unknown> } },\n use: (span: unknown) => Promise<void>,\n ) => {\n ensureCollector();\n const tracer = getTracer(TRACER_NAME, TRACER_VERSION);\n const span = tracer.startSpan(`test:${task.name}`, {\n attributes: {\n 'test.name': task.name,\n 'test.file': task.file?.name ?? '',\n 'test.suite': task.suite?.name ?? '',\n },\n });\n const ctx = otelTrace.setSpan(otelContext.active(), span);\n try {\n await otelContext.with(ctx, () => use(span));\n } catch (error) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.recordException(error instanceof Error ? error : new Error(String(error)));\n throw error;\n } finally {\n span.end();\n const traceId = span.spanContext().traceId;\n const rootSpanId = span.spanContext().spanId;\n const spans = collector!.drainTrace(traceId, rootSpanId);\n if (spans.length > 0) {\n (task.meta as Record<string, unknown>).otelSpans = spans;\n }\n }\n },\n { auto: true },\n];\n","/**\n * autotel-vitest\n *\n * Vitest fixture that creates one OTel span per test so all autotel-instrumented\n * code executed during a test automatically creates child spans under it;\n * making every test run filterable in your OTLP backend.\n *\n * @example\n * // vitest.config.ts: globalSetup calls init({ service: 'unit-tests' })\n * // In spec:\n * import { test, expect } from 'autotel-vitest';\n * test('creates user', async () => {\n * await userService.createUser({ email: 'test@example.com' });\n * // All trace()/span() calls become children of the test span\n * });\n */\n\nimport { test as base } from 'vitest';\nimport { otelTestSpanFixture } from './fixture';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const test = base.extend({\n _otelTestSpan: otelTestSpanFixture as any,\n});\n\nexport { expect, describe, beforeEach, afterEach, beforeAll, afterAll } from 'vitest';\n\n// Re-export all autotel/testing utilities\nexport {\n createTraceCollector,\n assertTraceCreated,\n assertTraceSucceeded,\n assertTraceFailed,\n assertNoErrors,\n assertTraceDuration,\n waitForTrace,\n getTraceDuration,\n createMockLogger,\n type TraceCollector,\n type TestSpan,\n type LogCollector,\n type LogEntry,\n} from 'autotel/testing';\n\n// Re-export trace context helpers for DX convenience\nexport {\n getTraceContext,\n resolveTraceUrl,\n isTracing,\n enrichWithTraceContext,\n} from 'autotel';\n\nexport type { OtelTraceContext } from 'autotel';\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -19,8 +19,6 @@ export { OtelTraceContext, enrichWithTraceContext, getTraceContext, isTracing, r
|
|
|
19
19
|
* // All trace()/span() calls become children of the test span
|
|
20
20
|
* });
|
|
21
21
|
*/
|
|
22
|
-
declare const test: vitest.TestAPI<
|
|
23
|
-
_otelTestSpan: unknown;
|
|
24
|
-
}>;
|
|
22
|
+
declare const test: vitest.TestAPI<Record<string, any> & object>;
|
|
25
23
|
|
|
26
24
|
export { test };
|
package/dist/index.d.ts
CHANGED
|
@@ -19,8 +19,6 @@ export { OtelTraceContext, enrichWithTraceContext, getTraceContext, isTracing, r
|
|
|
19
19
|
* // All trace()/span() calls become children of the test span
|
|
20
20
|
* });
|
|
21
21
|
*/
|
|
22
|
-
declare const test: vitest.TestAPI<
|
|
23
|
-
_otelTestSpan: unknown;
|
|
24
|
-
}>;
|
|
22
|
+
declare const test: vitest.TestAPI<Record<string, any> & object>;
|
|
25
23
|
|
|
26
24
|
export { test };
|
package/dist/index.js
CHANGED
|
@@ -22,37 +22,38 @@ function ensureCollector() {
|
|
|
22
22
|
}
|
|
23
23
|
return collector;
|
|
24
24
|
}
|
|
25
|
-
var
|
|
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
|
-
task.meta.otelSpans = spans;
|
|
51
|
-
}
|
|
25
|
+
var otelTestSpanFixture = [
|
|
26
|
+
async ({ task }, use) => {
|
|
27
|
+
ensureCollector();
|
|
28
|
+
const tracer = getTracer(TRACER_NAME, TRACER_VERSION);
|
|
29
|
+
const span = tracer.startSpan(`test:${task.name}`, {
|
|
30
|
+
attributes: {
|
|
31
|
+
"test.name": task.name,
|
|
32
|
+
"test.file": task.file?.name ?? "",
|
|
33
|
+
"test.suite": task.suite?.name ?? ""
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
const ctx = otelTrace.setSpan(context.active(), span);
|
|
37
|
+
try {
|
|
38
|
+
await context.with(ctx, () => use(span));
|
|
39
|
+
} catch (error) {
|
|
40
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
41
|
+
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
42
|
+
throw error;
|
|
43
|
+
} finally {
|
|
44
|
+
span.end();
|
|
45
|
+
const traceId = span.spanContext().traceId;
|
|
46
|
+
const rootSpanId = span.spanContext().spanId;
|
|
47
|
+
const spans = collector.drainTrace(traceId, rootSpanId);
|
|
48
|
+
if (spans.length > 0) {
|
|
49
|
+
task.meta.otelSpans = spans;
|
|
52
50
|
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{ auto: true }
|
|
54
|
+
];
|
|
55
|
+
var test = test$1.extend({
|
|
56
|
+
_otelTestSpan: otelTestSpanFixture
|
|
56
57
|
});
|
|
57
58
|
|
|
58
59
|
export { test };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["
|
|
1
|
+
{"version":3,"sources":["../src/fixture.ts","../src/index.ts"],"names":["otelContext","base"],"mappings":";;;;;;;;;AAUA,IAAM,WAAA,GAAc,cAAA;AACpB,IAAM,cAAA,GAAiB,OAAA;AAEvB,IAAI,SAAA,GAAsC,IAAA;AAM1C,SAAS,eAAA,GAAqC;AAC5C,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,SAAA,GAAY,IAAI,iBAAA,EAAkB;AAClC,IAAA,MAAM,WAAW,wBAAA,EAAyB;AAC1C,IAAA,IAAI,sBAAsB,QAAA,EAAU;AAClC,MAAC,QAAA,CAAyC,gBAAA;AAAA,QACxC,IAAI,oBAAoB,SAAS;AAAA,OACnC;AAAA,IACF;AAAA,EACF;AACA,EAAA,OAAO,SAAA;AACT;AAOO,IAAM,mBAAA,GAAuD;AAAA,EAClE,OACE,EAAE,IAAA,EAAK,EACP,GAAA,KACG;AACH,IAAA,eAAA,EAAgB;AAChB,IAAA,MAAM,MAAA,GAAS,SAAA,CAAU,WAAA,EAAa,cAAc,CAAA;AACpD,IAAA,MAAM,OAAO,MAAA,CAAO,SAAA,CAAU,CAAA,KAAA,EAAQ,IAAA,CAAK,IAAI,CAAA,CAAA,EAAI;AAAA,MACjD,UAAA,EAAY;AAAA,QACV,aAAa,IAAA,CAAK,IAAA;AAAA,QAClB,WAAA,EAAa,IAAA,CAAK,IAAA,EAAM,IAAA,IAAQ,EAAA;AAAA,QAChC,YAAA,EAAc,IAAA,CAAK,KAAA,EAAO,IAAA,IAAQ;AAAA;AACpC,KACD,CAAA;AACD,IAAA,MAAM,MAAM,SAAA,CAAU,OAAA,CAAQA,OAAA,CAAY,MAAA,IAAU,IAAI,CAAA;AACxD,IAAA,IAAI;AACF,MAAA,MAAMA,QAAY,IAAA,CAAK,GAAA,EAAK,MAAM,GAAA,CAAI,IAAI,CAAC,CAAA;AAAA,IAC7C,SAAS,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAM,cAAA,CAAe,OAAO,CAAA;AAC7C,MAAA,IAAA,CAAK,eAAA,CAAgB,iBAAiB,KAAA,GAAQ,KAAA,GAAQ,IAAI,KAAA,CAAM,MAAA,CAAO,KAAK,CAAC,CAAC,CAAA;AAC9E,MAAA,MAAM,KAAA;AAAA,IACR,CAAA,SAAE;AACA,MAAA,IAAA,CAAK,GAAA,EAAI;AACT,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,WAAA,EAAY,CAAE,OAAA;AACnC,MAAA,MAAM,UAAA,GAAa,IAAA,CAAK,WAAA,EAAY,CAAE,MAAA;AACtC,MAAA,MAAM,KAAA,GAAQ,SAAA,CAAW,UAAA,CAAW,OAAA,EAAS,UAAU,CAAA;AACvD,MAAA,IAAI,KAAA,CAAM,SAAS,CAAA,EAAG;AACpB,QAAC,IAAA,CAAK,KAAiC,SAAA,GAAY,KAAA;AAAA,MACrD;AAAA,IACF;AAAA,EACF,CAAA;AAAA,EACA,EAAE,MAAM,IAAA;AACV,CAAA;AChDO,IAAM,IAAA,GAAOC,OAAK,MAAA,CAAO;AAAA,EAC9B,aAAA,EAAe;AACjB,CAAC","file":"index.js","sourcesContent":["import {\n getTracer,\n getAutotelTracerProvider,\n context as otelContext,\n otelTrace,\n SpanStatusCode,\n} from 'autotel';\nimport { TestSpanCollector } from 'autotel/test-span-collector';\nimport { SimpleSpanProcessor } from 'autotel/processors';\n\nconst TRACER_NAME = 'vitest-tests';\nconst TRACER_VERSION = '0.1.0';\n\nlet collector: TestSpanCollector | null = null;\n\ninterface TracerProviderWithProcessor {\n addSpanProcessor(processor: unknown): void;\n}\n\nfunction ensureCollector(): TestSpanCollector {\n if (!collector) {\n collector = new TestSpanCollector();\n const provider = getAutotelTracerProvider();\n if ('addSpanProcessor' in provider) {\n (provider as TracerProviderWithProcessor).addSpanProcessor(\n new SimpleSpanProcessor(collector),\n );\n }\n }\n return collector;\n}\n\nexport type OtelFixtureFn = (\n args: { task: { name: string; file?: { name: string }; suite?: { name: string }; meta: Record<string, unknown> } },\n use: (span: unknown) => Promise<void>,\n) => Promise<void>;\n\nexport const otelTestSpanFixture: [OtelFixtureFn, { auto: true }] = [\n async (\n { task }: { task: { name: string; file?: { name: string }; suite?: { name: string }; meta: Record<string, unknown> } },\n use: (span: unknown) => Promise<void>,\n ) => {\n ensureCollector();\n const tracer = getTracer(TRACER_NAME, TRACER_VERSION);\n const span = tracer.startSpan(`test:${task.name}`, {\n attributes: {\n 'test.name': task.name,\n 'test.file': task.file?.name ?? '',\n 'test.suite': task.suite?.name ?? '',\n },\n });\n const ctx = otelTrace.setSpan(otelContext.active(), span);\n try {\n await otelContext.with(ctx, () => use(span));\n } catch (error) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.recordException(error instanceof Error ? error : new Error(String(error)));\n throw error;\n } finally {\n span.end();\n const traceId = span.spanContext().traceId;\n const rootSpanId = span.spanContext().spanId;\n const spans = collector!.drainTrace(traceId, rootSpanId);\n if (spans.length > 0) {\n (task.meta as Record<string, unknown>).otelSpans = spans;\n }\n }\n },\n { auto: true },\n];\n","/**\n * autotel-vitest\n *\n * Vitest fixture that creates one OTel span per test so all autotel-instrumented\n * code executed during a test automatically creates child spans under it;\n * making every test run filterable in your OTLP backend.\n *\n * @example\n * // vitest.config.ts: globalSetup calls init({ service: 'unit-tests' })\n * // In spec:\n * import { test, expect } from 'autotel-vitest';\n * test('creates user', async () => {\n * await userService.createUser({ email: 'test@example.com' });\n * // All trace()/span() calls become children of the test span\n * });\n */\n\nimport { test as base } from 'vitest';\nimport { otelTestSpanFixture } from './fixture';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const test = base.extend({\n _otelTestSpan: otelTestSpanFixture as any,\n});\n\nexport { expect, describe, beforeEach, afterEach, beforeAll, afterAll } from 'vitest';\n\n// Re-export all autotel/testing utilities\nexport {\n createTraceCollector,\n assertTraceCreated,\n assertTraceSucceeded,\n assertTraceFailed,\n assertNoErrors,\n assertTraceDuration,\n waitForTrace,\n getTraceDuration,\n createMockLogger,\n type TraceCollector,\n type TestSpan,\n type LogCollector,\n type LogEntry,\n} from 'autotel/testing';\n\n// Re-export trace context helpers for DX convenience\nexport {\n getTraceContext,\n resolveTraceUrl,\n isTracing,\n enrichWithTraceContext,\n} from 'autotel';\n\nexport type { OtelTraceContext } from 'autotel';\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autotel-vitest",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Vitest fixture for OpenTelemetry: one span per test so all instrumented code is filterable by test run",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"README.md"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"autotel": "2.
|
|
27
|
+
"autotel": "2.25.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"vitest": ">=4.1.0"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"tsup": "^8.5.1",
|
|
39
39
|
"typescript": "^5.9.3",
|
|
40
|
-
"vitest": "^4.0
|
|
40
|
+
"vitest": "^4.1.0"
|
|
41
41
|
},
|
|
42
42
|
"repository": {
|
|
43
43
|
"type": "git",
|
package/src/fixture.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getTracer,
|
|
3
|
+
getAutotelTracerProvider,
|
|
4
|
+
context as otelContext,
|
|
5
|
+
otelTrace,
|
|
6
|
+
SpanStatusCode,
|
|
7
|
+
} from 'autotel';
|
|
8
|
+
import { TestSpanCollector } from 'autotel/test-span-collector';
|
|
9
|
+
import { SimpleSpanProcessor } from 'autotel/processors';
|
|
10
|
+
|
|
11
|
+
const TRACER_NAME = 'vitest-tests';
|
|
12
|
+
const TRACER_VERSION = '0.1.0';
|
|
13
|
+
|
|
14
|
+
let collector: TestSpanCollector | null = null;
|
|
15
|
+
|
|
16
|
+
interface TracerProviderWithProcessor {
|
|
17
|
+
addSpanProcessor(processor: unknown): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function ensureCollector(): TestSpanCollector {
|
|
21
|
+
if (!collector) {
|
|
22
|
+
collector = new TestSpanCollector();
|
|
23
|
+
const provider = getAutotelTracerProvider();
|
|
24
|
+
if ('addSpanProcessor' in provider) {
|
|
25
|
+
(provider as TracerProviderWithProcessor).addSpanProcessor(
|
|
26
|
+
new SimpleSpanProcessor(collector),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return collector;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type OtelFixtureFn = (
|
|
34
|
+
args: { task: { name: string; file?: { name: string }; suite?: { name: string }; meta: Record<string, unknown> } },
|
|
35
|
+
use: (span: unknown) => Promise<void>,
|
|
36
|
+
) => Promise<void>;
|
|
37
|
+
|
|
38
|
+
export const otelTestSpanFixture: [OtelFixtureFn, { auto: true }] = [
|
|
39
|
+
async (
|
|
40
|
+
{ task }: { task: { name: string; file?: { name: string }; suite?: { name: string }; meta: Record<string, unknown> } },
|
|
41
|
+
use: (span: unknown) => Promise<void>,
|
|
42
|
+
) => {
|
|
43
|
+
ensureCollector();
|
|
44
|
+
const tracer = getTracer(TRACER_NAME, TRACER_VERSION);
|
|
45
|
+
const span = tracer.startSpan(`test:${task.name}`, {
|
|
46
|
+
attributes: {
|
|
47
|
+
'test.name': task.name,
|
|
48
|
+
'test.file': task.file?.name ?? '',
|
|
49
|
+
'test.suite': task.suite?.name ?? '',
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
const ctx = otelTrace.setSpan(otelContext.active(), span);
|
|
53
|
+
try {
|
|
54
|
+
await otelContext.with(ctx, () => use(span));
|
|
55
|
+
} catch (error) {
|
|
56
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
57
|
+
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
58
|
+
throw error;
|
|
59
|
+
} finally {
|
|
60
|
+
span.end();
|
|
61
|
+
const traceId = span.spanContext().traceId;
|
|
62
|
+
const rootSpanId = span.spanContext().spanId;
|
|
63
|
+
const spans = collector!.drainTrace(traceId, rootSpanId);
|
|
64
|
+
if (spans.length > 0) {
|
|
65
|
+
(task.meta as Record<string, unknown>).otelSpans = spans;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{ auto: true },
|
|
70
|
+
];
|
package/src/index.test.ts
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
|
|
3
|
-
type FixtureFn = (
|
|
4
|
-
args: { task: { name: string; file?: { name: string }; suite?: { name: string }; meta: Record<string, unknown> } },
|
|
5
|
-
use: (span: unknown) => Promise<void>,
|
|
6
|
-
) => Promise<void>;
|
|
7
|
-
|
|
8
|
-
type Fixtures = {
|
|
9
|
-
_otelTestSpan?: FixtureFn | [FixtureFn, { auto: true }];
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
const state: { fixtures?: Fixtures } = {};
|
|
13
3
|
let spanIdCounter = 0;
|
|
14
4
|
const createdSpans: Array<{
|
|
15
5
|
end: ReturnType<typeof vi.fn>;
|
|
@@ -18,20 +8,6 @@ const createdSpans: Array<{
|
|
|
18
8
|
spanContext: () => { traceId: string; spanId: string };
|
|
19
9
|
}> = [];
|
|
20
10
|
|
|
21
|
-
vi.mock('vitest', async () => {
|
|
22
|
-
const actual = await vi.importActual<typeof import('vitest')>('vitest');
|
|
23
|
-
return {
|
|
24
|
-
...actual,
|
|
25
|
-
test: {
|
|
26
|
-
...actual.test,
|
|
27
|
-
extend: (fixtures: Fixtures) => {
|
|
28
|
-
state.fixtures = fixtures;
|
|
29
|
-
return fixtures;
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
});
|
|
34
|
-
|
|
35
11
|
vi.mock('autotel', () => ({
|
|
36
12
|
SpanStatusCode: { UNSET: 0, OK: 1, ERROR: 2 },
|
|
37
13
|
context: {
|
|
@@ -79,21 +55,23 @@ vi.mock('autotel/processors', () => ({
|
|
|
79
55
|
|
|
80
56
|
describe('autotel-vitest fixture', () => {
|
|
81
57
|
afterEach(() => {
|
|
82
|
-
state.fixtures = undefined;
|
|
83
58
|
createdSpans.length = 0;
|
|
84
59
|
spanIdCounter = 0;
|
|
85
60
|
mockDrainResult = [];
|
|
86
61
|
vi.resetModules();
|
|
87
62
|
});
|
|
88
63
|
|
|
89
|
-
|
|
90
|
-
await import('./
|
|
64
|
+
async function getFixture() {
|
|
65
|
+
const { otelTestSpanFixture } = await import('./fixture');
|
|
66
|
+
const [fixtureFn, options] = otelTestSpanFixture;
|
|
67
|
+
return { fixtureFn, options, fixture: otelTestSpanFixture };
|
|
68
|
+
}
|
|
91
69
|
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
expect(
|
|
70
|
+
it('creates a span for each test via the _otelTestSpan fixture', async () => {
|
|
71
|
+
const { fixtureFn } = await getFixture();
|
|
72
|
+
expect(fixtureFn).toBeTypeOf('function');
|
|
95
73
|
|
|
96
|
-
await
|
|
74
|
+
await fixtureFn(
|
|
97
75
|
{
|
|
98
76
|
task: {
|
|
99
77
|
name: 'creates user',
|
|
@@ -110,14 +88,11 @@ describe('autotel-vitest fixture', () => {
|
|
|
110
88
|
});
|
|
111
89
|
|
|
112
90
|
it('ends the span after the test completes', async () => {
|
|
113
|
-
await
|
|
114
|
-
|
|
115
|
-
const spanFixture = state.fixtures?._otelTestSpan;
|
|
116
|
-
const spanFixtureFn = Array.isArray(spanFixture) ? spanFixture[0] : spanFixture;
|
|
91
|
+
const { fixtureFn } = await getFixture();
|
|
117
92
|
|
|
118
93
|
let spanDuringTest: unknown;
|
|
119
94
|
|
|
120
|
-
await
|
|
95
|
+
await fixtureFn(
|
|
121
96
|
{
|
|
122
97
|
task: {
|
|
123
98
|
name: 'test end timing',
|
|
@@ -139,15 +114,12 @@ describe('autotel-vitest fixture', () => {
|
|
|
139
114
|
});
|
|
140
115
|
|
|
141
116
|
it('sets error status when the test throws', async () => {
|
|
142
|
-
await
|
|
143
|
-
|
|
144
|
-
const spanFixture = state.fixtures?._otelTestSpan;
|
|
145
|
-
const spanFixtureFn = Array.isArray(spanFixture) ? spanFixture[0] : spanFixture;
|
|
117
|
+
const { fixtureFn } = await getFixture();
|
|
146
118
|
|
|
147
119
|
const err = new Error('test failure');
|
|
148
120
|
|
|
149
121
|
await expect(
|
|
150
|
-
|
|
122
|
+
fixtureFn(
|
|
151
123
|
{
|
|
152
124
|
task: {
|
|
153
125
|
name: 'failing test',
|
|
@@ -173,13 +145,10 @@ describe('autotel-vitest fixture', () => {
|
|
|
173
145
|
{ spanId: 'span-1', name: 'test:my-test', startTimeMs: 1000, durationMs: 100, status: 'ok' },
|
|
174
146
|
];
|
|
175
147
|
|
|
176
|
-
await
|
|
177
|
-
|
|
178
|
-
const spanFixture = state.fixtures?._otelTestSpan;
|
|
179
|
-
const spanFixtureFn = Array.isArray(spanFixture) ? spanFixture[0] : spanFixture;
|
|
148
|
+
const { fixtureFn } = await getFixture();
|
|
180
149
|
|
|
181
150
|
const meta: Record<string, unknown> = {};
|
|
182
|
-
await
|
|
151
|
+
await fixtureFn(
|
|
183
152
|
{
|
|
184
153
|
task: {
|
|
185
154
|
name: 'my-test',
|
|
@@ -197,12 +166,10 @@ describe('autotel-vitest fixture', () => {
|
|
|
197
166
|
});
|
|
198
167
|
|
|
199
168
|
it('uses auto: true to activate for every test', async () => {
|
|
200
|
-
await
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (Array.isArray(spanFixture)) {
|
|
205
|
-
expect(spanFixture[1]).toEqual({ auto: true });
|
|
169
|
+
const { fixture } = await getFixture();
|
|
170
|
+
expect(Array.isArray(fixture)).toBe(true);
|
|
171
|
+
if (Array.isArray(fixture)) {
|
|
172
|
+
expect(fixture[1]).toEqual({ auto: true });
|
|
206
173
|
}
|
|
207
174
|
});
|
|
208
175
|
});
|
package/src/index.ts
CHANGED
|
@@ -16,69 +16,11 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { test as base } from 'vitest';
|
|
19
|
-
import {
|
|
20
|
-
getTracer,
|
|
21
|
-
getAutotelTracerProvider,
|
|
22
|
-
context as otelContext,
|
|
23
|
-
otelTrace,
|
|
24
|
-
SpanStatusCode,
|
|
25
|
-
} from 'autotel';
|
|
26
|
-
import { TestSpanCollector } from 'autotel/test-span-collector';
|
|
27
|
-
import { SimpleSpanProcessor } from 'autotel/processors';
|
|
28
|
-
|
|
29
|
-
const TRACER_NAME = 'vitest-tests';
|
|
30
|
-
const TRACER_VERSION = '0.1.0';
|
|
31
|
-
|
|
32
|
-
let collector: TestSpanCollector | null = null;
|
|
33
|
-
|
|
34
|
-
interface TracerProviderWithProcessor {
|
|
35
|
-
addSpanProcessor(processor: unknown): void;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function ensureCollector(): TestSpanCollector {
|
|
39
|
-
if (!collector) {
|
|
40
|
-
collector = new TestSpanCollector();
|
|
41
|
-
const provider = getAutotelTracerProvider();
|
|
42
|
-
if ('addSpanProcessor' in provider) {
|
|
43
|
-
(provider as TracerProviderWithProcessor).addSpanProcessor(
|
|
44
|
-
new SimpleSpanProcessor(collector),
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return collector;
|
|
49
|
-
}
|
|
19
|
+
import { otelTestSpanFixture } from './fixture';
|
|
50
20
|
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
22
|
export const test = base.extend({
|
|
52
|
-
_otelTestSpan:
|
|
53
|
-
async ({ task }, use) => {
|
|
54
|
-
ensureCollector();
|
|
55
|
-
const tracer = getTracer(TRACER_NAME, TRACER_VERSION);
|
|
56
|
-
const span = tracer.startSpan(`test:${task.name}`, {
|
|
57
|
-
attributes: {
|
|
58
|
-
'test.name': task.name,
|
|
59
|
-
'test.file': task.file?.name ?? '',
|
|
60
|
-
'test.suite': task.suite?.name ?? '',
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
const ctx = otelTrace.setSpan(otelContext.active(), span);
|
|
64
|
-
try {
|
|
65
|
-
await otelContext.with(ctx, () => use(span));
|
|
66
|
-
} catch (error) {
|
|
67
|
-
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
68
|
-
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
69
|
-
throw error;
|
|
70
|
-
} finally {
|
|
71
|
-
span.end();
|
|
72
|
-
const traceId = span.spanContext().traceId;
|
|
73
|
-
const rootSpanId = span.spanContext().spanId;
|
|
74
|
-
const spans = collector!.drainTrace(traceId, rootSpanId);
|
|
75
|
-
if (spans.length > 0) {
|
|
76
|
-
(task.meta as Record<string, unknown>).otelSpans = spans;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
{ auto: true },
|
|
81
|
-
],
|
|
23
|
+
_otelTestSpan: otelTestSpanFixture as any,
|
|
82
24
|
});
|
|
83
25
|
|
|
84
26
|
export { expect, describe, beforeEach, afterEach, beforeAll, afterAll } from 'vitest';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wiring test: verifies the public `test` export from index.ts
|
|
3
|
+
* actually registers the _otelTestSpan fixture with auto: true.
|
|
4
|
+
*
|
|
5
|
+
* Uses the exported `test` (not vitest's base) to define tests,
|
|
6
|
+
* so if index.ts stops calling base.extend() or drops the fixture,
|
|
7
|
+
* these tests will fail.
|
|
8
|
+
*/
|
|
9
|
+
import { expect, vi } from 'vitest';
|
|
10
|
+
|
|
11
|
+
vi.mock('autotel', () => ({
|
|
12
|
+
SpanStatusCode: { UNSET: 0, OK: 1, ERROR: 2 },
|
|
13
|
+
context: {
|
|
14
|
+
active: () => ({}),
|
|
15
|
+
with: (_ctx: unknown, fn: () => Promise<unknown>) => fn(),
|
|
16
|
+
},
|
|
17
|
+
getTracer: () => ({
|
|
18
|
+
startSpan: () => ({
|
|
19
|
+
end: vi.fn(),
|
|
20
|
+
recordException: vi.fn(),
|
|
21
|
+
setStatus: vi.fn(),
|
|
22
|
+
spanContext: () => ({ traceId: 'wiring-trace', spanId: 'wiring-span' }),
|
|
23
|
+
}),
|
|
24
|
+
}),
|
|
25
|
+
otelTrace: {
|
|
26
|
+
setSpan: () => ({}),
|
|
27
|
+
},
|
|
28
|
+
getAutotelTracerProvider: vi.fn(() => ({})),
|
|
29
|
+
getTraceContext: vi.fn(() => null),
|
|
30
|
+
resolveTraceUrl: vi.fn(() => undefined),
|
|
31
|
+
isTracing: vi.fn(() => false),
|
|
32
|
+
enrichWithTraceContext: vi.fn((obj: unknown) => obj),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
vi.mock('autotel/test-span-collector', () => ({
|
|
36
|
+
TestSpanCollector: class {
|
|
37
|
+
export = vi.fn();
|
|
38
|
+
drainTrace = vi.fn(() => []);
|
|
39
|
+
shutdown = vi.fn(() => Promise.resolve());
|
|
40
|
+
forceFlush = vi.fn(() => Promise.resolve());
|
|
41
|
+
},
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
vi.mock('autotel/processors', () => ({
|
|
45
|
+
SimpleSpanProcessor: class {
|
|
46
|
+
constructor() {}
|
|
47
|
+
},
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
// Import the PUBLIC test — not vitest's base test.
|
|
51
|
+
// If index.ts doesn't wire otelTestSpanFixture via base.extend(),
|
|
52
|
+
// _otelTestSpan won't be available and these tests fail.
|
|
53
|
+
import { test } from './index';
|
|
54
|
+
|
|
55
|
+
test('_otelTestSpan fixture is registered and auto-activates', ({ _otelTestSpan }) => {
|
|
56
|
+
// auto: true means the fixture injects automatically.
|
|
57
|
+
// If the fixture key is missing from base.extend(), this will be undefined.
|
|
58
|
+
expect(_otelTestSpan).toBeDefined();
|
|
59
|
+
});
|