@attest-ai/vitest 0.9.0 → 1.0.0-rc.2
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/fixtures.d.ts +54 -6
- package/dist/fixtures.d.ts.map +1 -1
- package/dist/fixtures.js +130 -31
- package/dist/fixtures.js.map +1 -1
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts +6 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +21 -5
- package/dist/plugin.js.map +1 -1
- package/dist/report.d.ts +69 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +91 -0
- package/dist/report.js.map +1 -0
- package/dist/reporter.d.ts +38 -1
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +96 -1
- package/dist/reporter.js.map +1 -1
- package/dist/test-state.d.ts +100 -0
- package/dist/test-state.d.ts.map +1 -0
- package/dist/test-state.js +187 -0
- package/dist/test-state.js.map +1 -0
- package/dist/trials.d.ts +55 -0
- package/dist/trials.d.ts.map +1 -0
- package/dist/trials.js +184 -0
- package/dist/trials.js.map +1 -0
- package/package.json +11 -6
package/dist/reporter.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Reporter, TestModule, Vitest } from "vitest/node";
|
|
1
2
|
interface ReporterContext {
|
|
2
3
|
printInfo: (message: string) => void;
|
|
3
4
|
}
|
|
@@ -12,5 +13,41 @@ interface ReporterContext {
|
|
|
12
13
|
export declare class AttestCostReporter {
|
|
13
14
|
onFinished(_files: unknown, _errors: unknown, context?: ReporterContext): void;
|
|
14
15
|
}
|
|
15
|
-
export {
|
|
16
|
+
export interface AttestReporterOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Path of the report-v2 JSON file, relative to the Vitest root. Overrides
|
|
19
|
+
* `--outputFile.attest=<path>`.
|
|
20
|
+
*/
|
|
21
|
+
outputFile?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Vitest reporter that writes a report-v2 file (proto/attest/v1/report-v2.schema.json)
|
|
25
|
+
* when the run ends: every test with its outcome, file and line, and every Attest
|
|
26
|
+
* assertion result tagged with the test that requested it. Feed the file to
|
|
27
|
+
* `attest policy evaluate --report` and the pr-annotate action.
|
|
28
|
+
*
|
|
29
|
+
* Writes only when an output file is set: `new AttestReporter({ outputFile })` in the
|
|
30
|
+
* Vitest config, or on the command line, the counterpart of pytest's
|
|
31
|
+
* `--attest-report=<path>`:
|
|
32
|
+
* `vitest run --reporter=default --reporter=@attest-ai/vitest/reporter --outputFile.attest=<path>`. Turns on `includeTaskLocation` so each test carries its
|
|
33
|
+
* line. Requires Vitest 3+.
|
|
34
|
+
*
|
|
35
|
+
* At the end of the run it prints, with or without an output file, one line per
|
|
36
|
+
* `trials()` test ("<test_id>: <passed>/<evaluated> trials passed (<n> not evaluated)")
|
|
37
|
+
* and the "attest usage: …" line when the run made or refused an attest model call.
|
|
38
|
+
*/
|
|
39
|
+
export declare class AttestReporter implements Reporter {
|
|
40
|
+
private outputFile;
|
|
41
|
+
private root;
|
|
42
|
+
private vitest;
|
|
43
|
+
private usageDir;
|
|
44
|
+
constructor(options?: AttestReporterOptions);
|
|
45
|
+
onInit(vitest: Vitest): void;
|
|
46
|
+
onTestRunStart(): void;
|
|
47
|
+
/** Sum and remove the usage files workers wrote for evaluations outside any test. */
|
|
48
|
+
private takeUnattachedUsage;
|
|
49
|
+
onTestRunEnd(testModules: ReadonlyArray<TestModule>): void;
|
|
50
|
+
}
|
|
51
|
+
/** `vitest run --reporter=@attest-ai/vitest/reporter` loads the default export. */
|
|
52
|
+
export default AttestReporter;
|
|
16
53
|
//# sourceMappingURL=reporter.d.ts.map
|
package/dist/reporter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAIhE,UAAU,eAAe;IACvB,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED;;;;;;;GAOG;AACH,qBAAa,kBAAkB;IAC7B,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI;CAwC/E;AAED,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,cAAe,YAAW,QAAQ;IAC7C,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,IAAI,CAAiB;IAC7B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,QAAQ,CAAqB;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAe5B,cAAc,IAAI,IAAI;IAItB,qFAAqF;IACrF,OAAO,CAAC,mBAAmB;IAS3B,YAAY,CAAC,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,IAAI;CAmC3D;AAED,mFAAmF;AACnF,eAAe,cAAc,CAAC"}
|
package/dist/reporter.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { isSimulationMode, parseUsage, renderDiagnostics, renderSummary, sumUsage, } from "@attest-ai/core";
|
|
5
|
+
import { buildReport, summaryLines } from "./report.js";
|
|
6
|
+
import { ROOT_ENV, USAGE_DIR_ENV } from "./test-state.js";
|
|
2
7
|
/**
|
|
3
8
|
* Vitest reporter that prints the cost report and a hierarchical
|
|
4
9
|
* diagnostic block for every failed Attest evaluation in the run.
|
|
@@ -30,4 +35,94 @@ export class AttestCostReporter {
|
|
|
30
35
|
}
|
|
31
36
|
}
|
|
32
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Vitest reporter that writes a report-v2 file (proto/attest/v1/report-v2.schema.json)
|
|
40
|
+
* when the run ends: every test with its outcome, file and line, and every Attest
|
|
41
|
+
* assertion result tagged with the test that requested it. Feed the file to
|
|
42
|
+
* `attest policy evaluate --report` and the pr-annotate action.
|
|
43
|
+
*
|
|
44
|
+
* Writes only when an output file is set: `new AttestReporter({ outputFile })` in the
|
|
45
|
+
* Vitest config, or on the command line, the counterpart of pytest's
|
|
46
|
+
* `--attest-report=<path>`:
|
|
47
|
+
* `vitest run --reporter=default --reporter=@attest-ai/vitest/reporter --outputFile.attest=<path>`. Turns on `includeTaskLocation` so each test carries its
|
|
48
|
+
* line. Requires Vitest 3+.
|
|
49
|
+
*
|
|
50
|
+
* At the end of the run it prints, with or without an output file, one line per
|
|
51
|
+
* `trials()` test ("<test_id>: <passed>/<evaluated> trials passed (<n> not evaluated)")
|
|
52
|
+
* and the "attest usage: …" line when the run made or refused an attest model call.
|
|
53
|
+
*/
|
|
54
|
+
export class AttestReporter {
|
|
55
|
+
outputFile;
|
|
56
|
+
root = process.cwd();
|
|
57
|
+
vitest;
|
|
58
|
+
usageDir;
|
|
59
|
+
constructor(options = {}) {
|
|
60
|
+
this.outputFile = options.outputFile;
|
|
61
|
+
}
|
|
62
|
+
onInit(vitest) {
|
|
63
|
+
this.vitest = vitest;
|
|
64
|
+
this.root = vitest.config.root;
|
|
65
|
+
const configured = vitest.config.outputFile;
|
|
66
|
+
if (this.outputFile === undefined && typeof configured === "object") {
|
|
67
|
+
this.outputFile = configured["attest"];
|
|
68
|
+
}
|
|
69
|
+
vitest.config.includeTaskLocation = true;
|
|
70
|
+
// Workers inherit these: test ids hash paths relative to this root, and
|
|
71
|
+
// evaluations outside any test drop their usage in usageDir.
|
|
72
|
+
this.usageDir = mkdtempSync(join(tmpdir(), "attest-usage-"));
|
|
73
|
+
process.env[ROOT_ENV] = this.root;
|
|
74
|
+
process.env[USAGE_DIR_ENV] = this.usageDir;
|
|
75
|
+
}
|
|
76
|
+
onTestRunStart() {
|
|
77
|
+
if (this.usageDir !== undefined)
|
|
78
|
+
mkdirSync(this.usageDir, { recursive: true });
|
|
79
|
+
}
|
|
80
|
+
/** Sum and remove the usage files workers wrote for evaluations outside any test. */
|
|
81
|
+
takeUnattachedUsage() {
|
|
82
|
+
const dir = this.usageDir;
|
|
83
|
+
if (dir === undefined)
|
|
84
|
+
return sumUsage([]);
|
|
85
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
86
|
+
const usage = sumUsage(files.map((f) => parseUsage(JSON.parse(readFileSync(join(dir, f), "utf8")))));
|
|
87
|
+
rmSync(dir, { recursive: true, force: true });
|
|
88
|
+
return usage;
|
|
89
|
+
}
|
|
90
|
+
onTestRunEnd(testModules) {
|
|
91
|
+
const outputFile = this.outputFile;
|
|
92
|
+
const tests = [];
|
|
93
|
+
for (const testModule of testModules) {
|
|
94
|
+
const file = relative(this.root, testModule.moduleId).split(sep).join("/");
|
|
95
|
+
for (const testCase of testModule.children.allTests()) {
|
|
96
|
+
const location = testCase.location;
|
|
97
|
+
// The location matters only for the report file; summary lines need none.
|
|
98
|
+
if (location === undefined && outputFile !== undefined) {
|
|
99
|
+
throw new Error(`AttestReporter: no source location for "${testCase.fullName}". Vitest records ` +
|
|
100
|
+
"locations only when test.includeTaskLocation is true and the test file path matches " +
|
|
101
|
+
"the root; set test.root to the real path (no symlinks) if it is set.");
|
|
102
|
+
}
|
|
103
|
+
const state = testCase.result().state;
|
|
104
|
+
tests.push({
|
|
105
|
+
testId: `${file} > ${testCase.fullName}`,
|
|
106
|
+
name: testCase.name,
|
|
107
|
+
file,
|
|
108
|
+
line: location?.line ?? 0,
|
|
109
|
+
outcome: state === "passed" || state === "failed" ? state : "skipped",
|
|
110
|
+
durationMs: testCase.diagnostic()?.duration ?? 0,
|
|
111
|
+
attest: testCase.meta().attest,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const unattached = this.takeUnattachedUsage();
|
|
116
|
+
const lines = summaryLines(tests, unattached);
|
|
117
|
+
if (lines.length > 0)
|
|
118
|
+
this.vitest?.logger.log(lines.join("\n"));
|
|
119
|
+
if (outputFile === undefined)
|
|
120
|
+
return;
|
|
121
|
+
const path = resolve(this.root, outputFile);
|
|
122
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
123
|
+
writeFileSync(path, `${JSON.stringify(buildReport(tests, isSimulationMode(), unattached), null, 2)}\n`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** `vitest run --reporter=@attest-ai/vitest/reporter` loads the default export. */
|
|
127
|
+
export default AttestReporter;
|
|
33
128
|
//# sourceMappingURL=reporter.js.map
|
package/dist/reporter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reporter.js","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"reporter.js","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-test state shared by evaluate/inspect and trials: deterministic assertion
|
|
3
|
+
* IDs, the per-test budget remainder, and the results recorded on task meta.
|
|
4
|
+
*/
|
|
5
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
|
+
import type { TestContext } from "vitest";
|
|
7
|
+
import { type AgentResult, type Assertion, type BatchBudget, type Trace, type Usage } from "@attest-ai/core";
|
|
8
|
+
import type { AttestTaskMeta } from "./report.js";
|
|
9
|
+
export type RunningTest = NonNullable<TestContext["task"]>;
|
|
10
|
+
/** Limits for the attest model calls of one test (all its evaluations and trials). */
|
|
11
|
+
export interface TestBudget {
|
|
12
|
+
/** Judge-provider requests the test may make. */
|
|
13
|
+
maxJudgeCalls?: number;
|
|
14
|
+
/** USD the test may spend: attest judge/embedding cost plus agent spend (trace `metadata.cost_usd`). */
|
|
15
|
+
maxCostUsd?: number;
|
|
16
|
+
}
|
|
17
|
+
/** The trials() call a body runs in; `trial` is undefined for a single run (N = 1). */
|
|
18
|
+
export interface TrialScope {
|
|
19
|
+
test: RunningTest;
|
|
20
|
+
trial: number | undefined;
|
|
21
|
+
budget: TestBudget | undefined;
|
|
22
|
+
/** Evaluations of the current trial. */
|
|
23
|
+
results: AgentResult[];
|
|
24
|
+
}
|
|
25
|
+
export declare const trialScope: AsyncLocalStorage<TrialScope>;
|
|
26
|
+
/** True while the code runs inside trial i of a trials() call with N > 1. */
|
|
27
|
+
export declare function inTrial(): boolean;
|
|
28
|
+
interface AttemptState {
|
|
29
|
+
attempt: number;
|
|
30
|
+
/** Assertions given an ID in this test (this trial, under trials()). */
|
|
31
|
+
ordinal: number;
|
|
32
|
+
/** Attest usage of this attempt so far. */
|
|
33
|
+
usage: Usage;
|
|
34
|
+
/** Sum of `metadata.cost_usd` of the traces evaluated in this attempt. */
|
|
35
|
+
agentSpendUsd: number;
|
|
36
|
+
traceIds: Set<string>;
|
|
37
|
+
}
|
|
38
|
+
/** State of the running attempt of `test`; a retried test starts afresh. */
|
|
39
|
+
export declare function attemptState(test: RunningTest): AttemptState;
|
|
40
|
+
/**
|
|
41
|
+
* The running test: `TestRunner.getCurrentTest()` on Vitest 4.1+, `vitest/suite`
|
|
42
|
+
* before that (loaded only there, because Vitest 4.1 warns when it is imported).
|
|
43
|
+
*/
|
|
44
|
+
export declare function runningTest(): Promise<RunningTest | undefined>;
|
|
45
|
+
/**
|
|
46
|
+
* The test an evaluation belongs to: the passed context, else the enclosing
|
|
47
|
+
* trials() call, else Vitest's running test. Under test.concurrent the running
|
|
48
|
+
* test may be another test, so outside trials() the caller must pass `context`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveTest(context: TestContext | undefined): Promise<RunningTest | undefined>;
|
|
51
|
+
/**
|
|
52
|
+
* Set by AttestReporter in the main process before workers start: the Vitest
|
|
53
|
+
* root its report paths are relative to, and the directory where workers drop
|
|
54
|
+
* the usage of evaluations that belong to no test.
|
|
55
|
+
*/
|
|
56
|
+
export declare const ROOT_ENV = "ATTEST_VITEST_ROOT";
|
|
57
|
+
export declare const USAGE_DIR_ENV = "ATTEST_VITEST_USAGE_DIR";
|
|
58
|
+
/**
|
|
59
|
+
* The report's test_id of `test`: "<file> > <suites> > <name>", the same string
|
|
60
|
+
* AttestReporter writes. The file is relative to the Vitest root AttestReporter
|
|
61
|
+
* publishes (in a multi-project setup, not the project root); without
|
|
62
|
+
* AttestReporter it is the file task's name, relative to the project root.
|
|
63
|
+
*/
|
|
64
|
+
export declare function testIdOf(test: RunningTest): string;
|
|
65
|
+
/**
|
|
66
|
+
* Hand the usage of an evaluation that belongs to no test (beforeAll, a script)
|
|
67
|
+
* to AttestReporter: one file per evaluation in its usage directory, so the
|
|
68
|
+
* session usage counts it once. Without AttestReporter there is no report to add it to.
|
|
69
|
+
*/
|
|
70
|
+
export declare function recordUnattachedUsage(usage: Usage): void;
|
|
71
|
+
/** `assert_` + the first 12 hex chars of sha256("<test_id>#<ordinal>"). */
|
|
72
|
+
export declare function deterministicAssertionId(testId: string, ordinal: number): string;
|
|
73
|
+
/** Give each assertion the next deterministic ID of the test. */
|
|
74
|
+
export declare function assignAssertionIds(test: RunningTest, assertions: readonly Assertion[]): Assertion[];
|
|
75
|
+
/** Reject a budget with unknown keys or negative/non-numeric limits. */
|
|
76
|
+
export declare function validateBudget(budget: TestBudget): TestBudget;
|
|
77
|
+
/**
|
|
78
|
+
* What is left of the test budget for the next evaluate_batch: judge calls not
|
|
79
|
+
* yet used, and USD not yet spent by attest or the agent, each floored at 0.
|
|
80
|
+
* Undefined when the budget sets no limit.
|
|
81
|
+
*/
|
|
82
|
+
export declare function remainingBudget(budget: TestBudget, usage: Usage, agentSpendUsd: number): BatchBudget | undefined;
|
|
83
|
+
/** True when attest cost plus agent spend has reached the test's cost limit. */
|
|
84
|
+
export declare function costBudgetSpent(budget: TestBudget | undefined, state: AttemptState): boolean;
|
|
85
|
+
/** Count the agent's own spend of a trace once per attempt. */
|
|
86
|
+
export declare function recordAgentSpend(test: RunningTest, trace: Trace): void;
|
|
87
|
+
/**
|
|
88
|
+
* The meta of the running attempt. Results from an earlier attempt of a retried
|
|
89
|
+
* test are dropped, so the report matches the outcome Vitest reports; their
|
|
90
|
+
* usage is kept, because those model calls were made.
|
|
91
|
+
*/
|
|
92
|
+
export declare function attemptMeta(test: RunningTest): AttestTaskMeta;
|
|
93
|
+
/**
|
|
94
|
+
* Attach the results to the test's meta so AttestReporter, in the main process,
|
|
95
|
+
* can write them with the test's file and line; rows of trial i carry `trial`.
|
|
96
|
+
*/
|
|
97
|
+
export declare function recordOnTest(test: RunningTest, result: AgentResult, trial: number | undefined): void;
|
|
98
|
+
export declare function strictFromEnv(): boolean;
|
|
99
|
+
export {};
|
|
100
|
+
//# sourceMappingURL=test-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-state.d.ts","sourceRoot":"","sources":["../src/test-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-test state shared by evaluate/inspect and trials: deterministic assertion
|
|
3
|
+
* IDs, the per-test budget remainder, and the results recorded on task meta.
|
|
4
|
+
*/
|
|
5
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
7
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { join, relative, sep } from "node:path";
|
|
9
|
+
import * as vitest from "vitest";
|
|
10
|
+
import { addUsage, zeroUsage, } from "@attest-ai/core";
|
|
11
|
+
export const trialScope = new AsyncLocalStorage();
|
|
12
|
+
/** True while the code runs inside trial i of a trials() call with N > 1. */
|
|
13
|
+
export function inTrial() {
|
|
14
|
+
return trialScope.getStore()?.trial !== undefined;
|
|
15
|
+
}
|
|
16
|
+
const attempts = new WeakMap();
|
|
17
|
+
function attemptOf(test) {
|
|
18
|
+
return test.result?.retryCount ?? 0;
|
|
19
|
+
}
|
|
20
|
+
/** State of the running attempt of `test`; a retried test starts afresh. */
|
|
21
|
+
export function attemptState(test) {
|
|
22
|
+
const attempt = attemptOf(test);
|
|
23
|
+
let state = attempts.get(test);
|
|
24
|
+
if (state === undefined || state.attempt !== attempt) {
|
|
25
|
+
state = { attempt, ordinal: 0, usage: zeroUsage(), agentSpendUsd: 0, traceIds: new Set() };
|
|
26
|
+
attempts.set(test, state);
|
|
27
|
+
}
|
|
28
|
+
return state;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The running test: `TestRunner.getCurrentTest()` on Vitest 4.1+, `vitest/suite`
|
|
32
|
+
* before that (loaded only there, because Vitest 4.1 warns when it is imported).
|
|
33
|
+
*/
|
|
34
|
+
export async function runningTest() {
|
|
35
|
+
const runner = vitest.TestRunner;
|
|
36
|
+
if (runner !== undefined)
|
|
37
|
+
return runner.getCurrentTest();
|
|
38
|
+
const suite = await import("vitest/suite");
|
|
39
|
+
return suite.getCurrentTest();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The test an evaluation belongs to: the passed context, else the enclosing
|
|
43
|
+
* trials() call, else Vitest's running test. Under test.concurrent the running
|
|
44
|
+
* test may be another test, so outside trials() the caller must pass `context`.
|
|
45
|
+
*/
|
|
46
|
+
export async function resolveTest(context) {
|
|
47
|
+
if (context !== undefined)
|
|
48
|
+
return context.task;
|
|
49
|
+
const scope = trialScope.getStore();
|
|
50
|
+
if (scope !== undefined)
|
|
51
|
+
return scope.test;
|
|
52
|
+
const test = await runningTest();
|
|
53
|
+
if (test !== undefined && test.concurrent === true) {
|
|
54
|
+
throw new Error("Attest results from a test.concurrent test — pass the test context: " +
|
|
55
|
+
"inspect(chain, { context }), evaluate(chain, { context }) or trials(body, { context })");
|
|
56
|
+
}
|
|
57
|
+
return test;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Set by AttestReporter in the main process before workers start: the Vitest
|
|
61
|
+
* root its report paths are relative to, and the directory where workers drop
|
|
62
|
+
* the usage of evaluations that belong to no test.
|
|
63
|
+
*/
|
|
64
|
+
export const ROOT_ENV = "ATTEST_VITEST_ROOT";
|
|
65
|
+
export const USAGE_DIR_ENV = "ATTEST_VITEST_USAGE_DIR";
|
|
66
|
+
/**
|
|
67
|
+
* The report's test_id of `test`: "<file> > <suites> > <name>", the same string
|
|
68
|
+
* AttestReporter writes. The file is relative to the Vitest root AttestReporter
|
|
69
|
+
* publishes (in a multi-project setup, not the project root); without
|
|
70
|
+
* AttestReporter it is the file task's name, relative to the project root.
|
|
71
|
+
*/
|
|
72
|
+
export function testIdOf(test) {
|
|
73
|
+
const names = [test.name];
|
|
74
|
+
for (let suite = test.suite; suite !== undefined && suite !== test.file; suite = suite.suite) {
|
|
75
|
+
names.unshift(suite.name);
|
|
76
|
+
}
|
|
77
|
+
const root = process.env[ROOT_ENV];
|
|
78
|
+
const file = root === undefined || test.file.filepath === undefined
|
|
79
|
+
? test.file.name
|
|
80
|
+
: relative(root, test.file.filepath).split(sep).join("/");
|
|
81
|
+
return `${file} > ${names.join(" > ")}`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Hand the usage of an evaluation that belongs to no test (beforeAll, a script)
|
|
85
|
+
* to AttestReporter: one file per evaluation in its usage directory, so the
|
|
86
|
+
* session usage counts it once. Without AttestReporter there is no report to add it to.
|
|
87
|
+
*/
|
|
88
|
+
export function recordUnattachedUsage(usage) {
|
|
89
|
+
const dir = process.env[USAGE_DIR_ENV];
|
|
90
|
+
if (dir === undefined)
|
|
91
|
+
return;
|
|
92
|
+
if (Object.values(usage).every((v) => v === 0))
|
|
93
|
+
return;
|
|
94
|
+
mkdirSync(dir, { recursive: true });
|
|
95
|
+
writeFileSync(join(dir, `${randomUUID()}.json`), JSON.stringify(usage));
|
|
96
|
+
}
|
|
97
|
+
/** `assert_` + the first 12 hex chars of sha256("<test_id>#<ordinal>"). */
|
|
98
|
+
export function deterministicAssertionId(testId, ordinal) {
|
|
99
|
+
return `assert_${createHash("sha256").update(`${testId}#${ordinal}`).digest("hex").slice(0, 12)}`;
|
|
100
|
+
}
|
|
101
|
+
/** Give each assertion the next deterministic ID of the test. */
|
|
102
|
+
export function assignAssertionIds(test, assertions) {
|
|
103
|
+
const state = attemptState(test);
|
|
104
|
+
const testId = testIdOf(test);
|
|
105
|
+
return assertions.map((a) => ({ ...a, assertion_id: deterministicAssertionId(testId, state.ordinal++) }));
|
|
106
|
+
}
|
|
107
|
+
/** Reject a budget with unknown keys or negative/non-numeric limits. */
|
|
108
|
+
export function validateBudget(budget) {
|
|
109
|
+
for (const key of Object.keys(budget)) {
|
|
110
|
+
if (key !== "maxJudgeCalls" && key !== "maxCostUsd") {
|
|
111
|
+
throw new Error(`Attest budget: unknown option "${key}" (expected maxJudgeCalls, maxCostUsd)`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const { maxJudgeCalls, maxCostUsd } = budget;
|
|
115
|
+
if (maxJudgeCalls !== undefined && !(Number.isInteger(maxJudgeCalls) && maxJudgeCalls >= 0)) {
|
|
116
|
+
throw new Error(`Attest budget: maxJudgeCalls must be an integer >= 0, got ${String(maxJudgeCalls)}`);
|
|
117
|
+
}
|
|
118
|
+
if (maxCostUsd !== undefined && !(Number.isFinite(maxCostUsd) && maxCostUsd >= 0)) {
|
|
119
|
+
throw new Error(`Attest budget: maxCostUsd must be a number >= 0, got ${String(maxCostUsd)}`);
|
|
120
|
+
}
|
|
121
|
+
return budget;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* What is left of the test budget for the next evaluate_batch: judge calls not
|
|
125
|
+
* yet used, and USD not yet spent by attest or the agent, each floored at 0.
|
|
126
|
+
* Undefined when the budget sets no limit.
|
|
127
|
+
*/
|
|
128
|
+
export function remainingBudget(budget, usage, agentSpendUsd) {
|
|
129
|
+
const wire = {};
|
|
130
|
+
if (budget.maxJudgeCalls !== undefined) {
|
|
131
|
+
wire.max_judge_calls = Math.max(0, budget.maxJudgeCalls - usage.judge_calls);
|
|
132
|
+
}
|
|
133
|
+
if (budget.maxCostUsd !== undefined) {
|
|
134
|
+
wire.max_cost_usd = Math.max(0, budget.maxCostUsd - (usage.cost_usd + agentSpendUsd));
|
|
135
|
+
}
|
|
136
|
+
return Object.keys(wire).length === 0 ? undefined : wire;
|
|
137
|
+
}
|
|
138
|
+
/** True when attest cost plus agent spend has reached the test's cost limit. */
|
|
139
|
+
export function costBudgetSpent(budget, state) {
|
|
140
|
+
return (budget?.maxCostUsd !== undefined && state.usage.cost_usd + state.agentSpendUsd >= budget.maxCostUsd);
|
|
141
|
+
}
|
|
142
|
+
/** Count the agent's own spend of a trace once per attempt. */
|
|
143
|
+
export function recordAgentSpend(test, trace) {
|
|
144
|
+
const state = attemptState(test);
|
|
145
|
+
if (state.traceIds.has(trace.trace_id))
|
|
146
|
+
return;
|
|
147
|
+
state.traceIds.add(trace.trace_id);
|
|
148
|
+
state.agentSpendUsd += trace.metadata?.cost_usd ?? 0;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The meta of the running attempt. Results from an earlier attempt of a retried
|
|
152
|
+
* test are dropped, so the report matches the outcome Vitest reports; their
|
|
153
|
+
* usage is kept, because those model calls were made.
|
|
154
|
+
*/
|
|
155
|
+
export function attemptMeta(test) {
|
|
156
|
+
const attempt = attemptOf(test);
|
|
157
|
+
let meta = test.meta.attest;
|
|
158
|
+
if (meta === undefined || meta.attempt !== attempt) {
|
|
159
|
+
meta = {
|
|
160
|
+
attempt,
|
|
161
|
+
results: [],
|
|
162
|
+
totalCost: 0,
|
|
163
|
+
totalDurationMs: 0,
|
|
164
|
+
usage: meta?.usage ?? zeroUsage(),
|
|
165
|
+
};
|
|
166
|
+
test.meta.attest = meta;
|
|
167
|
+
}
|
|
168
|
+
return meta;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Attach the results to the test's meta so AttestReporter, in the main process,
|
|
172
|
+
* can write them with the test's file and line; rows of trial i carry `trial`.
|
|
173
|
+
*/
|
|
174
|
+
export function recordOnTest(test, result, trial) {
|
|
175
|
+
const state = attemptState(test);
|
|
176
|
+
state.usage = addUsage(state.usage, result.usage);
|
|
177
|
+
const meta = attemptMeta(test);
|
|
178
|
+
meta.results.push(...result.assertionResults.map((r) => (trial === undefined ? r : { ...r, trial })));
|
|
179
|
+
meta.totalCost += result.totalCost;
|
|
180
|
+
meta.totalDurationMs += result.totalDurationMs;
|
|
181
|
+
meta.usage = addUsage(meta.usage, result.usage);
|
|
182
|
+
}
|
|
183
|
+
export function strictFromEnv() {
|
|
184
|
+
const v = (process.env["ATTEST_STRICT"] ?? "").trim().toLowerCase();
|
|
185
|
+
return v === "1" || v === "true" || v === "yes";
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=test-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-state.js","sourceRoot":"","sources":["../src/test-state.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAEjC,OAAO,EACL,QAAQ,EACR,SAAS,GAMV,MAAM,iBAAiB,CAAC;AAsBzB,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,iBAAiB,EAAc,CAAC;AAE9D,6EAA6E;AAC7E,MAAM,UAAU,OAAO;IACrB,OAAO,UAAU,CAAC,QAAQ,EAAE,EAAE,KAAK,KAAK,SAAS,CAAC;AACpD,CAAC;AAaD,MAAM,QAAQ,GAAG,IAAI,OAAO,EAA6B,CAAC;AAE1D,SAAS,SAAS,CAAC,IAAiB;IAClC,OAAO,IAAI,CAAC,MAAM,EAAE,UAAU,IAAI,CAAC,CAAC;AACtC,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAAC,IAAiB;IAC5C,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;QACrD,KAAK,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;QAC3F,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,MAAM,GAAI,MAAiC,CAAC,UAAU,CAAC;IAC7D,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,cAAc,EAAE,CAAC;IACzD,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;IAC3C,OAAO,KAAK,CAAC,cAAc,EAAE,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAgC;IAChE,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,IAAI,CAAC;IAC/C,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAC;IACpC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC;IAC3C,MAAM,IAAI,GAAG,MAAM,WAAW,EAAE,CAAC;IACjC,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;QACnD,MAAM,IAAI,KAAK,CACb,sEAAsE;YACpE,wFAAwF,CAC3F,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,oBAAoB,CAAC;AAC7C,MAAM,CAAC,MAAM,aAAa,GAAG,yBAAyB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAiB;IACxC,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;QAC7F,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,IAAI,GACR,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,KAAK,SAAS;QACpD,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;QAChB,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC9D,OAAO,GAAG,IAAI,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;AAC1C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAY;IAChD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACvC,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO;IAC9B,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO;IACvD,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,wBAAwB,CAAC,MAAc,EAAE,OAAe;IACtE,OAAO,UAAU,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;AACpG,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,kBAAkB,CAChC,IAAiB,EACjB,UAAgC;IAEhC,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,YAAY,EAAE,wBAAwB,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC5G,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,cAAc,CAAC,MAAkB;IAC/C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,IAAI,GAAG,KAAK,eAAe,IAAI,GAAG,KAAK,YAAY,EAAE,CAAC;YACpD,MAAM,IAAI,KAAK,CAAC,kCAAkC,GAAG,wCAAwC,CAAC,CAAC;QACjG,CAAC;IACH,CAAC;IACD,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;IAC7C,IAAI,aAAa,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,IAAI,aAAa,IAAI,CAAC,CAAC,EAAE,CAAC;QAC5F,MAAM,IAAI,KAAK,CAAC,6DAA6D,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;IACxG,CAAC;IACD,IAAI,UAAU,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,UAAU,IAAI,CAAC,CAAC,EAAE,CAAC;QAClF,MAAM,IAAI,KAAK,CAAC,wDAAwD,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAChG,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAkB,EAClB,KAAY,EACZ,aAAqB;IAErB,MAAM,IAAI,GAAwD,EAAE,CAAC;IACrE,IAAI,MAAM,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QACvC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,aAAa,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACpC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,CAAC,KAAK,CAAC,QAAQ,GAAG,aAAa,CAAC,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3D,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,eAAe,CAAC,MAA8B,EAAE,KAAmB;IACjF,OAAO,CACL,MAAM,EAAE,UAAU,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,aAAa,IAAI,MAAM,CAAC,UAAU,CACpG,CAAC;AACJ,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,gBAAgB,CAAC,IAAiB,EAAE,KAAY;IAC9D,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC;QAAE,OAAO;IAC/C,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACnC,KAAK,CAAC,aAAa,IAAI,KAAK,CAAC,QAAQ,EAAE,QAAQ,IAAI,CAAC,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAAiB;IAC3C,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;IAC5B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;QACnD,IAAI,GAAG;YACL,OAAO;YACP,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,CAAC;YACZ,eAAe,EAAE,CAAC;YAClB,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,SAAS,EAAE;SAClC,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAiB,EAAE,MAAmB,EAAE,KAAyB;IAC5F,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IACjC,KAAK,CAAC,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC,OAAO,CAAC,IAAI,CACf,GAAG,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CACnF,CAAC;IACF,IAAI,CAAC,SAAS,IAAI,MAAM,CAAC,SAAS,CAAC;IACnC,IAAI,CAAC,eAAe,IAAI,MAAM,CAAC,eAAe,CAAC;IAC/C,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACpE,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,KAAK,CAAC;AAClD,CAAC"}
|
package/dist/trials.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixed-N trials: the Vitest counterpart of pytest's `--attest-trials=N`.
|
|
3
|
+
*/
|
|
4
|
+
import type { TestContext } from "vitest";
|
|
5
|
+
import { type TestBudget } from "./test-state.js";
|
|
6
|
+
/** How attestGlobalSetup({ minPassRate }) reaches the workers that run trials(). */
|
|
7
|
+
export declare const MIN_PASS_RATE_ENV = "ATTEST_MIN_PASS_RATE";
|
|
8
|
+
export interface TrialsOptions {
|
|
9
|
+
/** Number of trials N; defaults to the ATTEST_TRIALS environment variable, else 1. */
|
|
10
|
+
n?: number;
|
|
11
|
+
/**
|
|
12
|
+
* The running test's context (`it("…", async (context) => …)`); required under
|
|
13
|
+
* `test.concurrent` and on Vitest before 4.1.
|
|
14
|
+
*/
|
|
15
|
+
context?: TestContext;
|
|
16
|
+
/**
|
|
17
|
+
* Limits for the test's attest model calls across all trials; see
|
|
18
|
+
* InspectOptions.testBudget. Before each trial after the first, the trials stop
|
|
19
|
+
* when attest cost plus agent spend has reached `maxCostUsd`.
|
|
20
|
+
*/
|
|
21
|
+
budget?: TestBudget;
|
|
22
|
+
/**
|
|
23
|
+
* Fail the test when passed / evaluated trials is below this rate (0..1).
|
|
24
|
+
* Overrides the run's value: attestGlobalSetup({ minPassRate }) or the
|
|
25
|
+
* ATTEST_MIN_PASS_RATE environment variable, else 1.0 (any failing trial fails).
|
|
26
|
+
*/
|
|
27
|
+
minPassRate?: number;
|
|
28
|
+
}
|
|
29
|
+
/** A pass rate in [0, 1]; anything else is an error naming where it came from. */
|
|
30
|
+
export declare function validateMinPassRate(rate: number, source: string): number;
|
|
31
|
+
/**
|
|
32
|
+
* Run `body` N times inside the current test (N = `options.n`, else the
|
|
33
|
+
* ATTEST_TRIALS environment variable, else 1) and enforce only the aggregate.
|
|
34
|
+
*
|
|
35
|
+
* With N = 1 the body runs once as a plain test body: evaluate() enforces as
|
|
36
|
+
* usual. With N > 1, during trial i (1-based): evaluate() does not enforce
|
|
37
|
+
* (it behaves as inspect()), enforceOutcome() returns its argument, every
|
|
38
|
+
* result row is reported with `trial = i`, and assertion IDs restart so each
|
|
39
|
+
* assertion keeps its ID across trials. A trial that throws counts as failed:
|
|
40
|
+
* its rows are replaced by one hard_fail row (failure_class broken_code) per
|
|
41
|
+
* assertion ID the test evaluated; when no trial evaluated anything, the first
|
|
42
|
+
* error is rethrown. A trial whose own outcome is skip counts as not evaluated,
|
|
43
|
+
* also under ATTEST_STRICT. After the last trial the test is skipped when no trial was
|
|
44
|
+
* evaluated (failed under ATTEST_STRICT), fails when passed / evaluated
|
|
45
|
+
* trials is below the minimum pass rate (default 1.0: any failing trial
|
|
46
|
+
* fails; not-evaluated trials are left out of the rate), and passes otherwise. AttestReporter prints "<test_id>: <passed>/<evaluated>
|
|
47
|
+
* trials passed (<n> not evaluated)".
|
|
48
|
+
*
|
|
49
|
+
* This is a pass/fail gate on N runs, not a statistical verdict: whether the
|
|
50
|
+
* pass rate regressed (pass / regressed / inconclusive) comes from
|
|
51
|
+
* `attest policy evaluate --baseline <tag>` with a `regression` rule over the
|
|
52
|
+
* report's per-trial rows.
|
|
53
|
+
*/
|
|
54
|
+
export declare function trials(body: (trial: number) => Promise<void> | void, options?: TrialsOptions): Promise<void>;
|
|
55
|
+
//# sourceMappingURL=trials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trials.d.ts","sourceRoot":"","sources":["../src/trials.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAS1C,OAAO,EASL,KAAK,UAAU,EAChB,MAAM,iBAAiB,CAAC;AAEzB,oFAAoF;AACpF,eAAO,MAAM,iBAAiB,yBAAyB,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,sFAAsF;IACtF,CAAC,CAAC,EAAE,MAAM,CAAC;IACX;;;OAGG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB;;;;OAIG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAoBD,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAKxE;AA0ED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,MAAM,CAC1B,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EAC7C,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,IAAI,CAAC,CAqEf"}
|