-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvalidate.ts
122 lines (116 loc) · 3.63 KB
/
validate.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { Effect, Option } from "effect";
import { FileSystem } from "@effect/platform/FileSystem";
import { type Schema } from "@effect/schema/Schema";
import * as ts from "typescript";
import { mapError } from "effect/Effect";
import _ from "lodash";
import { toTimeLimitedRunnable } from "./generate";
import type { PlatformError } from "@effect/platform/Error";
// type check and tests
export const validate = <Input extends unknown[], Output>(
fileName: string,
outputSchema: Schema<Output>,
tests: Array<{
input: Input;
output: Output;
}> = []
) =>
Effect.gen(function* () {
const fs = yield* FileSystem;
const generatedCode = yield* fs.readFileString(fileName);
const program = ts.createProgram([fileName], {});
let emitResult = program.emit();
let allDiagnostics = ts
.getPreEmitDiagnostics(program)
.concat(emitResult.diagnostics)
.flatMap((diagnostic) => {
if (diagnostic.file) {
return diagnostic.file.fileName === fileName
? diagnostic.messageText
: [];
}
return [];
});
if (allDiagnostics.length > 0) {
return Option.some(
`Type check failed: ${allDiagnostics
.map((d) => ts.flattenDiagnosticMessageText(d, "\n"))
.join("\n")}`
);
}
const jsFileName = fileName.replace(".ts", ".js");
ts.transpile(generatedCode, undefined, jsFileName);
const runnable = toTimeLimitedRunnable(jsFileName, outputSchema, 3000);
const failed = [];
for (const test of tests) {
console.log("Running test", fileName);
try {
const actual: Output = yield* Effect.promise(() =>
runnable(...test.input)
);
if (!_.isEqual(test.output, actual)) {
failed.push({ input: test.input, expected: test.output, actual });
}
} catch (e) {
failed.push({ input: test.input, expected: test.output, actual: e });
}
}
if (failed.length > 0) {
return Option.some(
`${failed.length}\/${
tests.length
} tests failed. Failed test cases: ${JSON.stringify(failed)}`
);
}
return Option.none();
});
export const validateCachedFunction = <Input extends unknown[], Output>(
fileName: string,
outputSchema: Schema<Output>,
tests: Array<{
input: Input;
output: Output;
}> = []
) =>
validate(fileName, outputSchema, tests).pipe(
mapError((e) => {
if (typeof e === "string") {
return (
"Cached function is outdated. Need to regenerate one. See error: " + e
);
} else {
return e;
}
})
);
export interface PreviousAttempt {
response: string;
verdict: Option.Option<string>;
}
export const validatePreviousAttempts = <Input extends unknown[], Output>(
directoryName: string,
outputSchema: Schema<Output>,
tests: Array<{
input: Input;
output: Output;
}> = []
): Effect.Effect<PreviousAttempt[], PlatformError, FileSystem> =>
Effect.gen(function* () {
const fs = yield* FileSystem;
const files = yield* fs.readDirectory(directoryName);
// TODO: Maybe it can be done faster to find the passing one.
return yield* Effect.all(
files
.filter((f) => f.includes(".ts"))
.map((file) =>
Effect.gen(function* () {
const fileName = `${directoryName}/${file}`;
const response = yield* fs.readFileString(fileName);
const verdict = yield* validate(fileName, outputSchema, tests);
yield* Effect.log("Validated ", fileName, verdict);
return { response, verdict };
})
),
{ concurrency: "unbounded" }
);
});