@creative-introvert/tons-of-tests 2.1.0 → 3.0.0-rc.1
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 +143 -37
- package/dist/cjs/Classify.js +1 -2
- package/dist/cjs/Classify.js.map +1 -1
- package/dist/cjs/RepositoryError.js +42 -33
- package/dist/cjs/RepositoryError.js.map +1 -1
- package/dist/cjs/Show.js +18 -0
- package/dist/cjs/Show.js.map +1 -1
- package/dist/cjs/Test.js +40 -2
- package/dist/cjs/Test.js.map +1 -1
- package/dist/cjs/Test.repository.js +25 -22
- package/dist/cjs/Test.repository.js.map +1 -1
- package/dist/cjs/TestRun.js +28 -0
- package/dist/cjs/TestRun.js.map +1 -0
- package/dist/cjs/TestValueError.js +23 -0
- package/dist/cjs/TestValueError.js.map +1 -0
- package/dist/cjs/index.js +0 -7
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internal/Classify.js +1 -9
- package/dist/cjs/internal/Classify.js.map +1 -1
- package/dist/cjs/internal/Test.js +235 -95
- package/dist/cjs/internal/Test.js.map +1 -1
- package/dist/cjs/internal/Test.repository.sqlite.js +487 -356
- package/dist/cjs/internal/Test.repository.sqlite.js.map +1 -1
- package/dist/cjs/internal/Test.serialization.js +128 -0
- package/dist/cjs/internal/Test.serialization.js.map +1 -0
- package/dist/cjs/internal/common.js.map +1 -1
- package/dist/cjs/internal/stats.js +29 -1
- package/dist/cjs/internal/stats.js.map +1 -1
- package/dist/cjs/internal/summarize.js +143 -29
- package/dist/cjs/internal/summarize.js.map +1 -1
- package/dist/cjs/internal/test-repository-fixtures.js +31 -0
- package/dist/cjs/internal/test-repository-fixtures.js.map +1 -0
- package/dist/cjs/internal/test-repository-schema.js +98 -0
- package/dist/cjs/internal/test-repository-schema.js.map +1 -0
- package/dist/cjs/internal/test-result-stats.js +89 -0
- package/dist/cjs/internal/test-result-stats.js.map +1 -0
- package/dist/dts/Classify.d.ts +0 -1
- package/dist/dts/Classify.d.ts.map +1 -1
- package/dist/dts/RepositoryError.d.ts +47 -20
- package/dist/dts/RepositoryError.d.ts.map +1 -1
- package/dist/dts/Show.d.ts +5 -5
- package/dist/dts/Show.d.ts.map +1 -1
- package/dist/dts/Test.d.ts +136 -13
- package/dist/dts/Test.d.ts.map +1 -1
- package/dist/dts/Test.repository.d.ts +132 -9
- package/dist/dts/Test.repository.d.ts.map +1 -1
- package/dist/dts/TestRun.d.ts +22 -0
- package/dist/dts/TestRun.d.ts.map +1 -0
- package/dist/dts/TestValueError.d.ts +17 -0
- package/dist/dts/TestValueError.d.ts.map +1 -0
- package/dist/dts/index.d.ts +0 -1
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/internal/Classify.d.ts +0 -1
- package/dist/dts/internal/Classify.d.ts.map +1 -1
- package/dist/dts/internal/Test.d.ts +64 -30
- package/dist/dts/internal/Test.d.ts.map +1 -1
- package/dist/dts/internal/Test.repository.sqlite.d.ts +92 -11
- package/dist/dts/internal/Test.repository.sqlite.d.ts.map +1 -1
- package/dist/dts/internal/Test.serialization.d.ts +34 -0
- package/dist/dts/internal/Test.serialization.d.ts.map +1 -0
- package/dist/dts/internal/common.d.ts +1 -1
- package/dist/dts/internal/common.d.ts.map +1 -1
- package/dist/dts/internal/stats.d.ts +16 -1
- package/dist/dts/internal/stats.d.ts.map +1 -1
- package/dist/dts/internal/summarize.d.ts +39 -7
- package/dist/dts/internal/summarize.d.ts.map +1 -1
- package/dist/dts/internal/test-repository-fixtures.d.ts +33 -0
- package/dist/dts/internal/test-repository-fixtures.d.ts.map +1 -0
- package/dist/dts/internal/test-repository-schema.d.ts +16 -0
- package/dist/dts/internal/test-repository-schema.d.ts.map +1 -0
- package/dist/dts/internal/test-result-stats.d.ts +7 -0
- package/dist/dts/internal/test-result-stats.d.ts.map +1 -0
- package/dist/esm/Classify.js +0 -1
- package/dist/esm/Classify.js.map +1 -1
- package/dist/esm/RepositoryError.js +29 -29
- package/dist/esm/RepositoryError.js.map +1 -1
- package/dist/esm/Show.js +2 -2
- package/dist/esm/Show.js.map +1 -1
- package/dist/esm/Test.js +37 -1
- package/dist/esm/Test.js.map +1 -1
- package/dist/esm/Test.repository.js +26 -21
- package/dist/esm/Test.repository.js.map +1 -1
- package/dist/esm/TestRun.js +22 -0
- package/dist/esm/TestRun.js.map +1 -0
- package/dist/esm/TestValueError.js +16 -0
- package/dist/esm/TestValueError.js.map +1 -0
- package/dist/esm/index.js +0 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/Classify.js +0 -7
- package/dist/esm/internal/Classify.js.map +1 -1
- package/dist/esm/internal/Test.js +231 -94
- package/dist/esm/internal/Test.js.map +1 -1
- package/dist/esm/internal/Test.repository.sqlite.js +486 -356
- package/dist/esm/internal/Test.repository.sqlite.js.map +1 -1
- package/dist/esm/internal/Test.serialization.js +119 -0
- package/dist/esm/internal/Test.serialization.js.map +1 -0
- package/dist/esm/internal/common.js.map +1 -1
- package/dist/esm/internal/stats.js +27 -0
- package/dist/esm/internal/stats.js.map +1 -1
- package/dist/esm/internal/summarize.js +143 -28
- package/dist/esm/internal/summarize.js.map +1 -1
- package/dist/esm/internal/test-repository-fixtures.js +25 -0
- package/dist/esm/internal/test-repository-fixtures.js.map +1 -0
- package/dist/esm/internal/test-repository-schema.js +92 -0
- package/dist/esm/internal/test-repository-schema.js.map +1 -0
- package/dist/esm/internal/test-result-stats.js +83 -0
- package/dist/esm/internal/test-result-stats.js.map +1 -0
- package/package.json +1 -1
- package/dist/cjs/Test.repository.types.js +0 -14
- package/dist/cjs/Test.repository.types.js.map +0 -1
- package/dist/cjs/internal/lib/Schema.js +0 -14
- package/dist/cjs/internal/lib/Schema.js.map +0 -1
- package/dist/dts/Test.repository.types.d.ts +0 -62
- package/dist/dts/Test.repository.types.d.ts.map +0 -1
- package/dist/dts/internal/lib/Schema.d.ts +0 -4
- package/dist/dts/internal/lib/Schema.d.ts.map +0 -1
- package/dist/esm/Test.repository.types.js +0 -7
- package/dist/esm/Test.repository.types.js.map +0 -1
- package/dist/esm/internal/lib/Schema.js +0 -7
- package/dist/esm/internal/lib/Schema.js.map +0 -1
|
@@ -3,398 +3,529 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
|
|
6
|
+
exports.makeTestRepository = exports.TestResultStorage = exports.TestRepositoryStorage = void 0;
|
|
7
|
+
Object.defineProperty(exports, "tables", {
|
|
7
8
|
enumerable: true,
|
|
8
9
|
get: function () {
|
|
9
|
-
return
|
|
10
|
+
return _testRepositorySchema.tables;
|
|
10
11
|
}
|
|
11
12
|
});
|
|
12
|
-
|
|
13
|
-
var SqlClient = /*#__PURE__*/_interopRequireWildcard(/*#__PURE__*/require("effect/sql/SqlClient"));
|
|
13
|
+
var Sqlite = /*#__PURE__*/_interopRequireWildcard(/*#__PURE__*/require("@effect/sql-sqlite-node"));
|
|
14
14
|
var _effect = /*#__PURE__*/require("effect");
|
|
15
|
-
var
|
|
16
|
-
var
|
|
15
|
+
var _sql = /*#__PURE__*/require("effect/sql");
|
|
16
|
+
var _TestRun = /*#__PURE__*/require("../TestRun.js");
|
|
17
17
|
var _RepositoryError = /*#__PURE__*/require("../RepositoryError.js");
|
|
18
18
|
var _Classify = /*#__PURE__*/require("./Classify.js");
|
|
19
|
-
var
|
|
19
|
+
var _testRepositorySchema = /*#__PURE__*/require("./test-repository-schema.js");
|
|
20
|
+
var _testResultStats = /*#__PURE__*/require("./test-result-stats.js");
|
|
21
|
+
var _TestSerialization = /*#__PURE__*/require("./Test.serialization.js");
|
|
20
22
|
var cache;
|
|
21
23
|
function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var r, c, a = Object.defineProperty, n = { __proto__: null, default: e }; if (Object(e) !== e) return n; if (cache || "function" != typeof WeakMap || (cache = new WeakMap()), cache) { if (cache.has(e)) return cache.get(e); cache.set(e, n); } for (c in e) "default" !== c && {}.hasOwnProperty.call(e, c) && ((r = a && Object.getOwnPropertyDescriptor(e, c)) && (r.get || r.set) ? a(n, c, r) : n[c] = e[c]); return n; }
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
testRunResults: 'test-run-results'
|
|
26
|
-
};
|
|
27
|
-
const TestResultWriteSchema = /*#__PURE__*/_effect.Schema.Struct({
|
|
24
|
+
class TestRepositoryStorage extends /*#__PURE__*/_effect.Context.Service()('@creative-introvert/tons-of-tests/internal/TestRepositoryStorage') {}
|
|
25
|
+
exports.TestRepositoryStorage = TestRepositoryStorage;
|
|
26
|
+
const TestResultStorage = exports.TestResultStorage = /*#__PURE__*/_effect.Schema.Struct({
|
|
28
27
|
id: _effect.Schema.String,
|
|
29
28
|
hashTestCase: _effect.Schema.String,
|
|
30
29
|
key: _effect.Schema.String,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
expected: /*#__PURE__*/_effect.Schema.fromJsonString(_effect.Schema.Unknown),
|
|
36
|
-
label: _Classify.LabelSchema,
|
|
37
|
-
tags: /*#__PURE__*/(0, _Schema.split)(','),
|
|
38
|
-
timeMillis: _effect.Schema.Finite
|
|
30
|
+
input: /*#__PURE__*/_effect.Schema.fromJsonString(_effect.Schema.Json),
|
|
31
|
+
result: /*#__PURE__*/_effect.Schema.fromJsonString(_effect.Schema.Json),
|
|
32
|
+
expected: /*#__PURE__*/_effect.Schema.fromJsonString(_effect.Schema.Json),
|
|
33
|
+
label: _Classify.LabelSchema
|
|
39
34
|
});
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
count
|
|
46
|
-
}]) => count,
|
|
47
|
-
encode: count => [{
|
|
48
|
-
count
|
|
49
|
-
}]
|
|
50
|
-
})));
|
|
51
|
-
const TestRunsReadSchema = /*#__PURE__*/_effect.Schema.Array(_TestRepositoryTypes.TestRun);
|
|
52
|
-
const TestRunResultsSchema = /*#__PURE__*/_effect.Schema.Array(/*#__PURE__*/_effect.Schema.Struct({
|
|
53
|
-
testRun: _effect.Schema.Int,
|
|
54
|
-
testResult: _effect.Schema.String,
|
|
35
|
+
const StoredResult = /*#__PURE__*/_effect.Schema.Struct({
|
|
36
|
+
...TestResultStorage.fields,
|
|
37
|
+
input: _effect.Schema.String,
|
|
38
|
+
result: _effect.Schema.String,
|
|
39
|
+
expected: _effect.Schema.String,
|
|
55
40
|
ordering: _effect.Schema.Int,
|
|
56
41
|
resultOrdering: _effect.Schema.Int,
|
|
42
|
+
tags: /*#__PURE__*/_effect.Schema.fromJsonString(/*#__PURE__*/_effect.Schema.Array(_effect.Schema.String)),
|
|
57
43
|
timeMillis: _effect.Schema.Finite
|
|
44
|
+
});
|
|
45
|
+
const RawResult = /*#__PURE__*/StoredResult.mapFields(fields => ({
|
|
46
|
+
...fields,
|
|
47
|
+
tags: _effect.Schema.String
|
|
58
48
|
}));
|
|
59
|
-
const
|
|
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
|
-
if (!resultColumns.some(column => column.name === 'key')) {
|
|
110
|
-
yield* sql`
|
|
111
|
-
ALTER TABLE ${sql(tables.testResults)}
|
|
112
|
-
ADD COLUMN key TEXT NOT NULL DEFAULT 'result';
|
|
113
|
-
`;
|
|
114
|
-
}
|
|
115
|
-
if (!resultColumns.some(column => column.name === 'resultOrdering')) {
|
|
116
|
-
yield* sql`
|
|
117
|
-
ALTER TABLE ${sql(tables.testResults)}
|
|
118
|
-
ADD COLUMN resultOrdering INTEGER NOT NULL DEFAULT 0;
|
|
119
|
-
`;
|
|
49
|
+
const StoredRun = /*#__PURE__*/_effect.Schema.Union([/*#__PURE__*/_TestRun.TestRun.members[0].pipe(/*#__PURE__*/_effect.Schema.fieldsAssign({
|
|
50
|
+
executionId: _effect.Schema.String
|
|
51
|
+
})), /*#__PURE__*/_TestRun.TestRun.members[1].pipe(/*#__PURE__*/_effect.Schema.fieldsAssign({
|
|
52
|
+
executionId: _effect.Schema.String
|
|
53
|
+
}))]);
|
|
54
|
+
const encodeResult = /*#__PURE__*/_effect.Schema.encodeEffect(StoredResult);
|
|
55
|
+
const decodeRows = /*#__PURE__*/_effect.Schema.decodeUnknownEffect(/*#__PURE__*/_effect.Schema.Array(StoredResult));
|
|
56
|
+
const decodeRawRows = /*#__PURE__*/_effect.Schema.decodeUnknownEffect(/*#__PURE__*/_effect.Schema.Array(RawResult));
|
|
57
|
+
const decodeRuns = /*#__PURE__*/_effect.Schema.decodeUnknownEffect(/*#__PURE__*/_effect.Schema.Array(_TestRun.TestRun));
|
|
58
|
+
const decodeStoredRuns = /*#__PURE__*/_effect.Schema.decodeUnknownEffect(/*#__PURE__*/_effect.Schema.Array(StoredRun));
|
|
59
|
+
const decodeListedRuns = /*#__PURE__*/_effect.Schema.decodeUnknownEffect(/*#__PURE__*/_effect.Schema.Array(/*#__PURE__*/_TestRun.TestRun.members[1].pipe(/*#__PURE__*/_effect.Schema.fieldsAssign({
|
|
60
|
+
isBaseline: /*#__PURE__*/_effect.Schema.Literals([0, 1])
|
|
61
|
+
}))));
|
|
62
|
+
const decodeVariantBaselines = /*#__PURE__*/_effect.Schema.decodeUnknownEffect(/*#__PURE__*/_effect.Schema.Array(/*#__PURE__*/_effect.Schema.Struct({
|
|
63
|
+
variant: _effect.Schema.String,
|
|
64
|
+
id: /*#__PURE__*/_effect.Schema.NullOr(_effect.Schema.Int),
|
|
65
|
+
name: /*#__PURE__*/_effect.Schema.NullOr(_effect.Schema.String),
|
|
66
|
+
state: /*#__PURE__*/_effect.Schema.NullOr(/*#__PURE__*/_effect.Schema.Literal('Complete'))
|
|
67
|
+
})));
|
|
68
|
+
const decodeRunLimit = /*#__PURE__*/_effect.Schema.decodeUnknownEffect(/*#__PURE__*/_effect.Schema.Int.check(/*#__PURE__*/_effect.Schema.isBetween({
|
|
69
|
+
minimum: 1,
|
|
70
|
+
maximum: 100
|
|
71
|
+
})));
|
|
72
|
+
const openClient = options => _effect.Layer.build(Sqlite.SqliteClient.layer(options)).pipe(_effect.Effect.map(_effect.Context.get(_sql.SqlClient.SqlClient)));
|
|
73
|
+
const makeTestRepository = exports.makeTestRepository = /*#__PURE__*/_effect.Effect.fnUntraced(function* (options) {
|
|
74
|
+
const fs = yield* _effect.FileSystem.FileSystem;
|
|
75
|
+
const path = yield* _effect.Path.Path;
|
|
76
|
+
const crypto = yield* _effect.Crypto.Crypto;
|
|
77
|
+
const lifetime = yield* _effect.Effect.scope;
|
|
78
|
+
const memoryLock = yield* _effect.TxReentrantLock.make();
|
|
79
|
+
const snapshotLock = yield* _effect.Semaphore.make(1);
|
|
80
|
+
const snapshotMemory = yield* _effect.Ref.make(_effect.Option.none());
|
|
81
|
+
const canonicalPath = filename => fs.realPath(filename).pipe(_effect.Effect.catchTag('PlatformError', error => {
|
|
82
|
+
if (error.reason._tag !== 'NotFound') return _effect.Effect.fail(error);
|
|
83
|
+
const parent = path.dirname(filename);
|
|
84
|
+
if (parent === filename) return _effect.Effect.fail(error);
|
|
85
|
+
return _effect.Effect.gen(function* () {
|
|
86
|
+
const link = yield* fs.readLink(filename).pipe(_effect.Effect.asSome, _effect.Effect.catchReason('PlatformError', 'NotFound', () => _effect.Effect.succeedNone));
|
|
87
|
+
return _effect.Option.isSome(link) ? yield* canonicalPath(path.resolve(parent, link.value)) : path.join(yield* canonicalPath(parent), path.basename(filename));
|
|
88
|
+
});
|
|
89
|
+
}));
|
|
90
|
+
if (options.dbPath !== ':memory:' && options.cachePath !== ':memory:') {
|
|
91
|
+
const [snapshotFile, cacheFile] = yield* _effect.Effect.all([canonicalPath(options.dbPath), canonicalPath(options.cachePath)]);
|
|
92
|
+
const [snapshotInfo, cacheInfo] = yield* _effect.Effect.forEach([snapshotFile, cacheFile], filename => fs.stat(filename).pipe(_effect.Effect.asSome, _effect.Effect.catchReason('PlatformError', 'NotFound', () => _effect.Effect.succeedNone)));
|
|
93
|
+
const sameInode = _effect.Option.isSome(snapshotInfo) && _effect.Option.isSome(cacheInfo) && snapshotInfo.value.dev === cacheInfo.value.dev && _effect.Option.isSome(snapshotInfo.value.ino) && _effect.Option.isSome(cacheInfo.value.ino) && snapshotInfo.value.ino.value === cacheInfo.value.ino.value;
|
|
94
|
+
if (snapshotFile === cacheFile || sameInode) {
|
|
95
|
+
return yield* new _RepositoryError.RepositoryPathConflict({
|
|
96
|
+
snapshotPath: options.dbPath,
|
|
97
|
+
cachePath: options.cachePath
|
|
98
|
+
});
|
|
120
99
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
100
|
+
}
|
|
101
|
+
let initializeCache = options.cachePath === ':memory:';
|
|
102
|
+
if (options.cachePath !== ':memory:') {
|
|
103
|
+
yield* fs.makeDirectory(path.dirname(options.cachePath), {
|
|
104
|
+
recursive: true
|
|
105
|
+
});
|
|
106
|
+
initializeCache = !(yield* fs.exists(options.cachePath));
|
|
107
|
+
if (!initializeCache) {
|
|
108
|
+
const compatible = yield* _effect.Effect.scoped(_effect.Effect.gen(function* () {
|
|
109
|
+
const sql = yield* openClient({
|
|
110
|
+
filename: options.cachePath,
|
|
111
|
+
readonly: true,
|
|
112
|
+
disableWAL: true
|
|
113
|
+
});
|
|
114
|
+
yield* (0, _testRepositorySchema.checkFormat)(sql, 'cache', options.cachePath);
|
|
115
|
+
return true;
|
|
116
|
+
})).pipe(_effect.Effect.catchTag('RepositoryFormatError', () => _effect.Effect.succeed(false)));
|
|
117
|
+
if (!compatible) {
|
|
118
|
+
for (const suffix of ['', '-wal', '-shm']) {
|
|
119
|
+
yield* fs.remove(`${options.cachePath}${suffix}`, {
|
|
120
|
+
force: true
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
initializeCache = true;
|
|
125
124
|
}
|
|
126
|
-
yield* sql`
|
|
127
|
-
ALTER TABLE ${sql(tables.testRunResults)}
|
|
128
|
-
ADD COLUMN ${sql(column)} INTEGER NOT NULL DEFAULT 0;
|
|
129
|
-
`;
|
|
130
|
-
yield* sql`
|
|
131
|
-
UPDATE ${sql(tables.testRunResults)} AS runres
|
|
132
|
-
SET ${sql(column)} = (
|
|
133
|
-
SELECT ${sql(column)} FROM ${sql(tables.testResults)} res
|
|
134
|
-
WHERE res.id = runres.testResult
|
|
135
|
-
);
|
|
136
|
-
`;
|
|
137
125
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
126
|
+
}
|
|
127
|
+
const cache = yield* openClient({
|
|
128
|
+
filename: options.cachePath
|
|
129
|
+
});
|
|
130
|
+
yield* cache`PRAGMA foreign_keys = ON;`;
|
|
131
|
+
if (initializeCache) yield* (0, _testRepositorySchema.initializeSchema)(cache, 'cache', options.cachePath);
|
|
132
|
+
const getMemorySnapshot = snapshotLock.withPermit(_effect.Effect.gen(function* () {
|
|
133
|
+
const existing = yield* _effect.Ref.get(snapshotMemory);
|
|
134
|
+
if (_effect.Option.isSome(existing)) return existing.value;
|
|
135
|
+
const sql = yield* _effect.Scope.provide(openClient({
|
|
136
|
+
filename: ':memory:',
|
|
137
|
+
disableWAL: true
|
|
138
|
+
}), lifetime);
|
|
139
|
+
yield* sql`PRAGMA foreign_keys = ON;`;
|
|
140
|
+
yield* (0, _testRepositorySchema.initializeSchema)(sql, 'snapshot', ':memory:');
|
|
141
|
+
yield* _effect.Ref.set(snapshotMemory, _effect.Option.some(sql));
|
|
142
|
+
return sql;
|
|
144
143
|
}));
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
timeMillis: _.timeMillis
|
|
167
|
-
}));
|
|
168
|
-
for (let i = 0; i < junction.length; i += INSERT_BATCH_SIZE) {
|
|
169
|
-
const chunk = junction.slice(i, i + INSERT_BATCH_SIZE);
|
|
170
|
-
yield* sql`
|
|
171
|
-
INSERT INTO ${sql(tables.testRunResults)}
|
|
172
|
-
${sql.insert(chunk)}
|
|
173
|
-
ON CONFLICT(testRun, testResult) DO UPDATE SET
|
|
174
|
-
ordering = excluded.ordering,
|
|
175
|
-
resultOrdering = excluded.resultOrdering,
|
|
176
|
-
timeMillis = excluded.timeMillis;
|
|
177
|
-
`.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryWriteError.from('insertTestResults')));
|
|
144
|
+
const snapshotReader = _effect.Effect.gen(function* () {
|
|
145
|
+
if (options.dbPath === ':memory:') return yield* _effect.Ref.get(snapshotMemory);
|
|
146
|
+
if (!(yield* fs.exists(options.dbPath))) return _effect.Option.none();
|
|
147
|
+
const sql = yield* openClient({
|
|
148
|
+
filename: options.dbPath,
|
|
149
|
+
readonly: true,
|
|
150
|
+
disableWAL: true
|
|
151
|
+
});
|
|
152
|
+
yield* (0, _testRepositorySchema.checkFormat)(sql, 'snapshot', options.dbPath);
|
|
153
|
+
return _effect.Option.some(sql);
|
|
154
|
+
});
|
|
155
|
+
const withSnapshot = (empty, use) => _effect.Effect.scoped(_effect.Effect.gen(function* () {
|
|
156
|
+
const sql = yield* snapshotReader;
|
|
157
|
+
return _effect.Option.isSome(sql) ? yield* use(sql.value) : empty;
|
|
158
|
+
}));
|
|
159
|
+
const cacheReader = _effect.Effect.gen(function* () {
|
|
160
|
+
if (options.cachePath === ':memory:') {
|
|
161
|
+
// A memory store has one connection. Block writes to attempts while
|
|
162
|
+
// readers page through them; temporary statistics remain writable.
|
|
163
|
+
yield* _effect.TxReentrantLock.readLock(memoryLock);
|
|
164
|
+
return cache;
|
|
178
165
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const result = schemas?.result ?? _effect.Schema.Unknown;
|
|
184
|
-
const expected = schemas?.expected ?? _effect.Schema.Unknown;
|
|
185
|
-
return _effect.Schema.Struct({
|
|
186
|
-
id: _effect.Schema.String,
|
|
187
|
-
hashTestCase: _effect.Schema.String,
|
|
188
|
-
key: _effect.Schema.String,
|
|
189
|
-
ordering: _effect.Schema.Int,
|
|
190
|
-
resultOrdering: _effect.Schema.Int,
|
|
191
|
-
input: _effect.Schema.fromJsonString(input),
|
|
192
|
-
result: _effect.Schema.fromJsonString(result),
|
|
193
|
-
expected: _effect.Schema.fromJsonString(expected),
|
|
194
|
-
label: _Classify.LabelSchema,
|
|
195
|
-
tags: (0, _Schema.split)(','),
|
|
196
|
-
timeMillis: _effect.Schema.Finite
|
|
166
|
+
const sql = yield* openClient({
|
|
167
|
+
filename: options.cachePath,
|
|
168
|
+
readonly: true,
|
|
169
|
+
disableWAL: true
|
|
197
170
|
});
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
// The SQLite driver returns rows in one batch. Decode that batch once
|
|
203
|
-
// before exposing it as a stream.
|
|
204
|
-
return _effect.Stream.unwrap(_effect.Effect.gen(function* () {
|
|
205
|
-
const rows = yield* sql`
|
|
206
|
-
SELECT res.id, res.hashTestCase, res.key,
|
|
207
|
-
runres.ordering, runres.resultOrdering,
|
|
208
|
-
res.input, res.result, res.expected, res.label, res.tags,
|
|
209
|
-
runres.timeMillis
|
|
210
|
-
FROM ${sql(tables.testResults)} res
|
|
211
|
-
JOIN ${sql(tables.testRunResults)} runres ON res.id = runres.testResult
|
|
212
|
-
JOIN ${sql(tables.testRuns)} runs ON runs.id = runres.testRun
|
|
213
|
-
WHERE runs.id = ${testRun.id}
|
|
214
|
-
ORDER BY runres.ordering ASC, runres.resultOrdering ASC;
|
|
215
|
-
`.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryQueryError.fromSql('getTestResultsStream')));
|
|
216
|
-
const decoded = yield* _effect.Schema.decodeUnknownEffect(RowsSchema)(rows).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('getTestResultsStream')));
|
|
217
|
-
return _effect.Stream.fromIterable(decoded);
|
|
218
|
-
}));
|
|
219
|
-
};
|
|
220
|
-
const hasResults = _effect.Effect.fnUntraced(function* (testRun) {
|
|
221
|
-
const b = yield* sql`
|
|
222
|
-
SELECT COUNT(*) count FROM ${sql(tables.testRunResults)}
|
|
223
|
-
WHERE testRun = ${testRun.id};
|
|
224
|
-
`.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryQueryError.fromSql('hasResults')));
|
|
225
|
-
const r = yield* _effect.Schema.decodeUnknownEffect(CountSchema)(b).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('hasResults')));
|
|
226
|
-
return r > 0;
|
|
171
|
+
// Keep the selected rows stable until the scoped connection closes,
|
|
172
|
+
// including when reading is interrupted.
|
|
173
|
+
yield* sql`BEGIN;`;
|
|
174
|
+
return sql;
|
|
227
175
|
});
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const p = yield* _effect.Schema.decodeUnknownEffect(TestRunsReadSchema)(r).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('getPreviousTestRun')));
|
|
236
|
-
return _effect.Array.head(p);
|
|
176
|
+
const writeCache = effect => options.cachePath === ':memory:' ? _effect.TxReentrantLock.withWriteLock(memoryLock, cache.withTransaction(effect)) : cache.withTransaction(effect);
|
|
177
|
+
const readRun = _effect.Effect.fnUntraced(function* (sql, run) {
|
|
178
|
+
return _effect.Array.head(yield* decodeRuns(yield* sql`
|
|
179
|
+
SELECT *, ${run.store} AS store FROM ${sql(_testRepositorySchema.tables.testRuns)}
|
|
180
|
+
WHERE id = ${run.id}
|
|
181
|
+
AND ${run.store === 'cache' ? sql`snapshotIdentity = ${options.snapshotIdentity}` : sql`1 = 1`};
|
|
182
|
+
`));
|
|
237
183
|
});
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
184
|
+
const getTestRun = run => run.store === 'cache' ? readRun(cache, run) : withSnapshot(_effect.Option.none(), sql => readRun(sql, run));
|
|
185
|
+
const requireRun = _effect.Effect.fnUntraced(function* (sql, run) {
|
|
186
|
+
const found = yield* readRun(sql, run);
|
|
187
|
+
if (_effect.Option.isNone(found) || found.value.name !== run.name || found.value.variant !== run.variant) {
|
|
188
|
+
return yield* new _RepositoryError.TestRunNotFound({
|
|
189
|
+
testRunId: run.id
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return found.value;
|
|
243
193
|
});
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
194
|
+
const writeRows = _effect.Effect.fnUntraced(function* (sql, runId, rows) {
|
|
195
|
+
if (rows.length === 0) return;
|
|
196
|
+
yield* sql`
|
|
197
|
+
INSERT INTO ${sql(_testRepositorySchema.tables.testResults)} ${sql.insert(rows.map(row => ({
|
|
198
|
+
id: row.id,
|
|
199
|
+
hashTestCase: row.hashTestCase,
|
|
200
|
+
key: row.key,
|
|
201
|
+
input: row.input,
|
|
202
|
+
result: row.result,
|
|
203
|
+
expected: row.expected,
|
|
204
|
+
label: row.label
|
|
205
|
+
})))} ON CONFLICT(id) DO NOTHING;
|
|
206
|
+
`;
|
|
207
|
+
yield* sql`
|
|
208
|
+
INSERT INTO ${sql(_testRepositorySchema.tables.testRunResults)} ${sql.insert(rows.map(row => ({
|
|
209
|
+
testRun: runId,
|
|
210
|
+
testResult: row.id,
|
|
211
|
+
ordering: row.ordering,
|
|
212
|
+
resultOrdering: row.resultOrdering,
|
|
213
|
+
timeMillis: row.timeMillis,
|
|
214
|
+
tags: row.tags
|
|
215
|
+
})))} ON CONFLICT(testRun, testResult) DO UPDATE SET
|
|
216
|
+
ordering = excluded.ordering, resultOrdering = excluded.resultOrdering,
|
|
217
|
+
timeMillis = excluded.timeMillis, tags = excluded.tags;
|
|
218
|
+
`;
|
|
249
219
|
});
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
220
|
+
const insertTestResults = _effect.Effect.fnUntraced(function* (results, run, schemas) {
|
|
221
|
+
if (run.store !== 'cache') {
|
|
222
|
+
return yield* _effect.Effect.die(new Error('Only cached Running attempts accept result writes.'));
|
|
223
|
+
}
|
|
224
|
+
const codec = (0, _TestSerialization.makeResultCodec)(schemas, run.name);
|
|
225
|
+
// Fifty rows keep every statement below SQLite's 999-parameter minimum.
|
|
226
|
+
for (let offset = 0; offset < Math.max(1, results.length); offset += 50) {
|
|
227
|
+
const encoded = yield* _effect.Effect.forEach(results.slice(offset, offset + 50), _effect.Effect.fnUntraced(function* (result) {
|
|
228
|
+
const encoded = yield* codec.encode(result);
|
|
229
|
+
return yield* encodeResult({
|
|
230
|
+
...encoded,
|
|
231
|
+
input: (0, _TestSerialization.stringifyJson)(encoded.input),
|
|
232
|
+
result: (0, _TestSerialization.stringifyJson)(encoded.result),
|
|
233
|
+
expected: (0, _TestSerialization.stringifyJson)(encoded.expected)
|
|
234
|
+
});
|
|
235
|
+
}));
|
|
236
|
+
yield* writeCache(_effect.Effect.gen(function* () {
|
|
237
|
+
const saved = yield* requireRun(cache, run);
|
|
238
|
+
if (saved.state !== 'Running') {
|
|
239
|
+
return yield* _effect.Effect.die(new Error('Only Running test runs accept result writes.'));
|
|
240
|
+
}
|
|
241
|
+
yield* writeRows(cache, run.id, encoded);
|
|
242
|
+
}));
|
|
243
|
+
}
|
|
255
244
|
});
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
245
|
+
const readResultPage = (sql, runId, after, matching, limit = 100) => {
|
|
246
|
+
const filter = matching === undefined ? sql`1 = 1` : matching.length === 0 ? sql`1 = 0` : sql.or(matching.map(key => sql`res.hashTestCase = ${key.hashTestCase} AND res.key = ${key.key}`));
|
|
247
|
+
// CROSS JOIN makes matching batches use the case/key index first.
|
|
248
|
+
const from = matching === undefined ? sql`${sql(_testRepositorySchema.tables.testRunResults)} runres JOIN ${sql(_testRepositorySchema.tables.testResults)} res ON res.id = runres.testResult` : sql`${sql(_testRepositorySchema.tables.testResults)} res CROSS JOIN ${sql(_testRepositorySchema.tables.testRunResults)} runres ON res.id = runres.testResult`;
|
|
249
|
+
return sql`
|
|
250
|
+
SELECT res.id, res.hashTestCase, res.key, runres.ordering, runres.resultOrdering,
|
|
251
|
+
res.input, res.result, res.expected, res.label, runres.tags, runres.timeMillis
|
|
252
|
+
FROM ${from}
|
|
253
|
+
WHERE runres.testRun = ${runId} AND ${filter}
|
|
254
|
+
AND ${after === undefined ? sql`1 = 1` : sql`
|
|
255
|
+
(runres.ordering, runres.resultOrdering, runres.testResult) >
|
|
256
|
+
(${after.ordering}, ${after.resultOrdering}, ${after.id})`}
|
|
257
|
+
ORDER BY runres.ordering, runres.resultOrdering, runres.testResult LIMIT ${limit};
|
|
258
|
+
`;
|
|
259
|
+
};
|
|
260
|
+
const getTestResultsStream = (run, schemas, matching) => {
|
|
261
|
+
if (matching !== undefined && matching.length > 50) return _effect.Stream.die(new Error('Match at most 50 case/key pairs per query.'));
|
|
262
|
+
const codec = (0, _TestSerialization.makeResultCodec)(schemas, run.name);
|
|
263
|
+
return _effect.Stream.unwrap(_effect.Effect.gen(function* () {
|
|
264
|
+
const selected = run.store === 'cache' ? _effect.Option.some(yield* cacheReader) : yield* snapshotReader;
|
|
265
|
+
if (_effect.Option.isNone(selected)) return yield* new _RepositoryError.TestRunNotFound({
|
|
266
|
+
testRunId: run.id
|
|
267
|
+
});
|
|
268
|
+
const sql = selected.value;
|
|
269
|
+
yield* requireRun(sql, run);
|
|
270
|
+
return _effect.Stream.paginate(undefined, _effect.Effect.fnUntraced(function* (after) {
|
|
271
|
+
const rows = yield* decodeRows(yield* readResultPage(sql, run.id, after, matching));
|
|
272
|
+
const decoded = yield* _effect.Effect.forEach(rows, codec.decode);
|
|
273
|
+
return [decoded, rows.length < 100 ? _effect.Option.none() : _effect.Option.some(rows[rows.length - 1])];
|
|
274
|
+
}));
|
|
275
|
+
}));
|
|
276
|
+
};
|
|
277
|
+
const getLatestTestRun = _effect.Effect.fnUntraced(function* ({
|
|
278
|
+
name,
|
|
279
|
+
variant
|
|
280
|
+
}) {
|
|
281
|
+
return _effect.Array.head(yield* decodeRuns(yield* cache`
|
|
282
|
+
SELECT *, 'cache' AS store FROM ${cache(_testRepositorySchema.tables.testRuns)}
|
|
283
|
+
WHERE snapshotIdentity = ${options.snapshotIdentity} AND name = ${name} AND variant = ${variant}
|
|
284
|
+
ORDER BY id DESC LIMIT 1;
|
|
285
|
+
`));
|
|
266
286
|
});
|
|
267
|
-
const
|
|
268
|
-
const currentRaw = yield* sql`
|
|
269
|
-
SELECT *
|
|
270
|
-
FROM ${sql(tables.testRuns)}
|
|
271
|
-
WHERE hash IS NULL AND name = ${name}
|
|
272
|
-
ORDER BY id DESC
|
|
273
|
-
LIMIT 1;
|
|
274
|
-
`.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryQueryError.fromSql('getOrCreateCurrentTestRun')));
|
|
275
|
-
const current = yield* _effect.Schema.decodeUnknownEffect(TestRunsReadSchema)(currentRaw).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('getOrCreateCurrentTestRun')));
|
|
276
|
-
const existing = _effect.Array.head(current);
|
|
277
|
-
if (_effect.Option.isSome(existing)) {
|
|
278
|
-
return existing.value;
|
|
279
|
-
}
|
|
280
|
-
const insertedRaw = yield* sql`
|
|
281
|
-
INSERT INTO ${sql(tables.testRuns)}
|
|
282
|
-
${sql.insert({
|
|
283
|
-
name
|
|
284
|
-
})}
|
|
285
|
-
RETURNING *;
|
|
286
|
-
`.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryWriteError.from('getOrCreateCurrentTestRun')));
|
|
287
|
-
const inserted = yield* _effect.Schema.decodeUnknownEffect(TestRunsReadSchema)(insertedRaw).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('getOrCreateCurrentTestRun')));
|
|
288
|
-
return yield* _effect.Effect.fromOption(_effect.Array.head(inserted)).pipe(_effect.Effect.mapError(() => new _RepositoryError.TestRunNotCreated({
|
|
289
|
-
testSuiteName: name
|
|
290
|
-
})));
|
|
291
|
-
})).pipe(_effect.Effect.catchTag('SqlError', e => _effect.Effect.fail(_RepositoryError.RepositoryQueryError.fromSql('getOrCreateCurrentTestRun')(e))));
|
|
292
|
-
const clearStale = ({
|
|
287
|
+
const getBaselineTestRun = ({
|
|
293
288
|
name,
|
|
294
|
-
|
|
295
|
-
}) =>
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
289
|
+
variant
|
|
290
|
+
}) => withSnapshot(_effect.Option.none(), _effect.Effect.fnUntraced(function* (sql) {
|
|
291
|
+
return _effect.Array.head(yield* decodeRuns(yield* sql`
|
|
292
|
+
SELECT run.*, 'snapshot' AS store FROM ${sql(_testRepositorySchema.tables.testBaselines)} baseline
|
|
293
|
+
JOIN ${sql(_testRepositorySchema.tables.testRuns)} run ON run.id = baseline.testRun
|
|
294
|
+
WHERE baseline.name = ${name} AND baseline.variant = ${variant};
|
|
295
|
+
`));
|
|
296
|
+
}));
|
|
297
|
+
const createTestRun = _effect.Effect.fnUntraced(function* (input) {
|
|
298
|
+
const executionId = yield* crypto.randomUUIDv4();
|
|
299
|
+
return yield* writeCache(_effect.Effect.gen(function* () {
|
|
300
|
+
yield* cache`
|
|
301
|
+
DELETE FROM ${cache(_testRepositorySchema.tables.testRuns)}
|
|
302
|
+
WHERE snapshotIdentity = ${options.snapshotIdentity} AND name = ${input.name} AND variant = ${input.variant};
|
|
303
|
+
`;
|
|
304
|
+
yield* cache`
|
|
305
|
+
DELETE FROM ${cache(_testRepositorySchema.tables.testResults)} WHERE NOT EXISTS (
|
|
306
|
+
SELECT 1 FROM ${cache(_testRepositorySchema.tables.testRunResults)} links WHERE links.testResult = ${cache(_testRepositorySchema.tables.testResults)}.id
|
|
307
|
+
);
|
|
308
|
+
`;
|
|
309
|
+
const inserted = yield* decodeRuns(yield* cache`
|
|
310
|
+
INSERT INTO ${cache(_testRepositorySchema.tables.testRuns)} ${cache.insert({
|
|
311
|
+
name: input.name,
|
|
312
|
+
variant: input.variant,
|
|
313
|
+
executionId,
|
|
314
|
+
snapshotIdentity: options.snapshotIdentity,
|
|
315
|
+
state: 'Running'
|
|
316
|
+
})}
|
|
317
|
+
RETURNING *, 'cache' AS store;
|
|
318
|
+
`);
|
|
319
|
+
return yield* _effect.Effect.fromOption(_effect.Array.head(inserted)).pipe(_effect.Effect.mapError(() => new _RepositoryError.TestRunNotCreated({
|
|
320
|
+
testSuiteName: input.name
|
|
321
|
+
})));
|
|
322
|
+
}));
|
|
323
|
+
});
|
|
324
|
+
const finishTestRun = (run, state) => writeCache(_effect.Effect.gen(function* () {
|
|
325
|
+
const updated = yield* decodeRuns(yield* cache`
|
|
326
|
+
UPDATE ${cache(_testRepositorySchema.tables.testRuns)} SET state = ${state}
|
|
327
|
+
WHERE id = ${run.id} AND snapshotIdentity = ${options.snapshotIdentity} AND state = 'Running'
|
|
328
|
+
RETURNING *, 'cache' AS store;
|
|
329
|
+
`);
|
|
330
|
+
if (updated.length === 0) return yield* new _RepositoryError.TestRunNotFound({
|
|
331
|
+
testRunId: run.id
|
|
332
|
+
});
|
|
333
|
+
}));
|
|
334
|
+
const withTestRun = (input, execute) => _effect.Effect.acquireUseRelease(createTestRun(input), run => _effect.Effect.suspend(() => execute(run)).pipe(_effect.Effect.map(value => ({
|
|
335
|
+
run,
|
|
336
|
+
value
|
|
337
|
+
}))), (run, exit) => finishTestRun(run, _effect.Exit.isSuccess(exit) ? 'Complete' : 'Failed')).pipe(_effect.Effect.map(({
|
|
338
|
+
run,
|
|
339
|
+
value
|
|
340
|
+
}) => ({
|
|
341
|
+
run: _TestRun.TestRun.make({
|
|
342
|
+
...run,
|
|
343
|
+
state: 'Complete'
|
|
344
|
+
}),
|
|
345
|
+
value
|
|
346
|
+
})));
|
|
347
|
+
const snapshotWriter = _effect.Effect.gen(function* () {
|
|
348
|
+
if (options.dbPath === ':memory:') return yield* getMemorySnapshot;
|
|
349
|
+
const exists = yield* fs.exists(options.dbPath);
|
|
350
|
+
if (exists) {
|
|
351
|
+
yield* _effect.Effect.scoped(_effect.Effect.gen(function* () {
|
|
352
|
+
const sql = yield* openClient({
|
|
353
|
+
filename: options.dbPath,
|
|
354
|
+
readonly: true,
|
|
355
|
+
disableWAL: true
|
|
356
|
+
});
|
|
357
|
+
yield* (0, _testRepositorySchema.checkFormat)(sql, 'snapshot', options.dbPath);
|
|
358
|
+
}));
|
|
359
|
+
} else {
|
|
360
|
+
yield* fs.makeDirectory(path.dirname(options.dbPath), {
|
|
361
|
+
recursive: true
|
|
362
|
+
});
|
|
306
363
|
}
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
yield* sql`
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}) => sql.withTransaction(_effect.Effect.gen(function* () {
|
|
327
|
-
const currentRaw = yield* sql`
|
|
328
|
-
SELECT *
|
|
329
|
-
FROM ${sql(tables.testRuns)}
|
|
330
|
-
WHERE hash IS NULL AND name = ${name}
|
|
331
|
-
ORDER BY id DESC
|
|
332
|
-
LIMIT 1;
|
|
333
|
-
`.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryQueryError.fromSql('clearUncommitedTestResults')));
|
|
334
|
-
const current = yield* _effect.Schema.decodeUnknownEffect(TestRunsReadSchema)(currentRaw).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('clearUncommitedTestResults')));
|
|
335
|
-
const currentTestRun = _effect.Array.head(current);
|
|
336
|
-
if (_effect.Option.isNone(currentTestRun)) {
|
|
337
|
-
return;
|
|
364
|
+
const sql = yield* openClient({
|
|
365
|
+
filename: options.dbPath,
|
|
366
|
+
disableWAL: true
|
|
367
|
+
});
|
|
368
|
+
if (exists) yield* (0, _testRepositorySchema.checkFormat)(sql, 'snapshot', options.dbPath);else yield* (0, _testRepositorySchema.initializeSchema)(sql, 'snapshot', options.dbPath);
|
|
369
|
+
yield* sql`PRAGMA journal_mode = DELETE;`;
|
|
370
|
+
yield* sql`PRAGMA foreign_keys = ON;`;
|
|
371
|
+
return sql;
|
|
372
|
+
});
|
|
373
|
+
const commitTestRuns = runs => _effect.Effect.scoped(_effect.Effect.gen(function* () {
|
|
374
|
+
if (runs.length === 0) return [];
|
|
375
|
+
const selectedVariants = new Set();
|
|
376
|
+
for (const run of runs) {
|
|
377
|
+
const key = JSON.stringify([run.name, run.variant]);
|
|
378
|
+
if (selectedVariants.has(key)) return yield* new _RepositoryError.TestRunDuplicate({
|
|
379
|
+
testSuiteName: run.name,
|
|
380
|
+
variant: run.variant
|
|
381
|
+
});
|
|
382
|
+
selectedVariants.add(key);
|
|
338
383
|
}
|
|
339
|
-
yield*
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
384
|
+
const reader = runs.some(run => run.store === 'cache') ? yield* cacheReader : undefined;
|
|
385
|
+
// Check every selected run before opening a writer, so a missing or
|
|
386
|
+
// incomplete attempt cannot create a snapshot file.
|
|
387
|
+
const selected = yield* _effect.Effect.forEach(runs, run => _effect.Effect.gen(function* () {
|
|
388
|
+
const saved = run.store === 'cache' && reader !== undefined ? yield* requireRun(reader, run) : yield* getTestRun(run).pipe(_effect.Effect.flatMap(_effect.Option.match({
|
|
389
|
+
onNone: () => _effect.Effect.fail(new _RepositoryError.TestRunNotFound({
|
|
390
|
+
testRunId: run.id
|
|
391
|
+
})),
|
|
392
|
+
onSome: _effect.Effect.succeed
|
|
393
|
+
})));
|
|
394
|
+
if (saved.name !== run.name || saved.variant !== run.variant) return yield* new _RepositoryError.TestRunNotFound({
|
|
395
|
+
testRunId: run.id
|
|
396
|
+
});
|
|
397
|
+
if (saved.state !== 'Complete') return yield* new _RepositoryError.TestRunIncomplete({
|
|
398
|
+
testSuiteName: saved.name,
|
|
399
|
+
testRunId: saved.id,
|
|
400
|
+
variant: saved.variant
|
|
401
|
+
});
|
|
402
|
+
if (saved.store === 'snapshot' || reader === undefined) return {
|
|
403
|
+
run: saved,
|
|
404
|
+
executionId: undefined
|
|
405
|
+
};
|
|
406
|
+
const stored = yield* decodeStoredRuns(yield* reader`
|
|
407
|
+
SELECT *, 'cache' AS store FROM ${reader(_testRepositorySchema.tables.testRuns)}
|
|
408
|
+
WHERE id = ${saved.id} AND snapshotIdentity = ${options.snapshotIdentity};
|
|
409
|
+
`);
|
|
410
|
+
return {
|
|
411
|
+
run: saved,
|
|
412
|
+
executionId: stored[0].executionId
|
|
413
|
+
};
|
|
414
|
+
}));
|
|
415
|
+
const sql = yield* snapshotWriter;
|
|
416
|
+
return yield* sql.withTransaction(_effect.Effect.forEach(selected, ({
|
|
417
|
+
run,
|
|
418
|
+
executionId
|
|
419
|
+
}) => _effect.Effect.gen(function* () {
|
|
420
|
+
let snapshot;
|
|
421
|
+
if (run.store === 'snapshot') {
|
|
422
|
+
snapshot = yield* requireRun(sql, run);
|
|
423
|
+
} else {
|
|
424
|
+
const existing = yield* decodeRuns(yield* sql`
|
|
425
|
+
SELECT *, 'snapshot' AS store FROM ${sql(_testRepositorySchema.tables.testRuns)} WHERE executionId = ${executionId};
|
|
426
|
+
`);
|
|
427
|
+
if (existing.length > 0) {
|
|
428
|
+
snapshot = existing[0];
|
|
429
|
+
} else {
|
|
430
|
+
const inserted = yield* decodeRuns(yield* sql`
|
|
431
|
+
INSERT INTO ${sql(_testRepositorySchema.tables.testRuns)} ${sql.insert({
|
|
432
|
+
name: run.name,
|
|
433
|
+
variant: run.variant,
|
|
434
|
+
executionId,
|
|
435
|
+
snapshotIdentity: options.snapshotIdentity,
|
|
436
|
+
state: 'Complete'
|
|
437
|
+
})} RETURNING *, 'snapshot' AS store;
|
|
438
|
+
`);
|
|
439
|
+
snapshot = inserted[0];
|
|
440
|
+
let after;
|
|
441
|
+
if (reader !== undefined) {
|
|
442
|
+
while (true) {
|
|
443
|
+
const rows = yield* decodeRawRows(yield* readResultPage(reader, run.id, after, undefined, 50));
|
|
444
|
+
yield* writeRows(sql, snapshot.id, rows);
|
|
445
|
+
if (rows.length < 50) break;
|
|
446
|
+
after = rows[rows.length - 1];
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
yield* sql`
|
|
452
|
+
INSERT INTO ${sql(_testRepositorySchema.tables.testBaselines)} ${sql.insert({
|
|
453
|
+
name: snapshot.name,
|
|
454
|
+
variant: snapshot.variant,
|
|
455
|
+
testRun: snapshot.id
|
|
456
|
+
})}
|
|
457
|
+
ON CONFLICT(name, variant) DO UPDATE SET testRun = excluded.testRun;
|
|
458
|
+
`;
|
|
459
|
+
return snapshot;
|
|
460
|
+
})));
|
|
461
|
+
}));
|
|
462
|
+
const listTestRuns = _effect.Effect.fnUntraced(function* ({
|
|
351
463
|
name,
|
|
352
|
-
|
|
464
|
+
variant,
|
|
465
|
+
before,
|
|
466
|
+
limit
|
|
353
467
|
}) {
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
`.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryWriteError.from('commitCurrentTestRun')));
|
|
375
|
-
yield* sql`
|
|
376
|
-
INSERT INTO ${sql(tables.testRuns)}
|
|
377
|
-
${sql.insert({
|
|
378
|
-
name
|
|
379
|
-
})};
|
|
380
|
-
`.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryWriteError.from('commitCurrentTestRun')));
|
|
381
|
-
yield* _effect.Console.log('Commited test run.');
|
|
468
|
+
const parsedLimit = yield* decodeRunLimit(limit);
|
|
469
|
+
return yield* withSnapshot([], _effect.Effect.fnUntraced(function* (sql) {
|
|
470
|
+
const rows = yield* decodeListedRuns(yield* sql`
|
|
471
|
+
SELECT run.*, 'snapshot' AS store, EXISTS (
|
|
472
|
+
SELECT 1 FROM ${sql(_testRepositorySchema.tables.testBaselines)} baseline
|
|
473
|
+
WHERE baseline.name = run.name AND baseline.variant = run.variant AND baseline.testRun = run.id
|
|
474
|
+
) AS isBaseline FROM ${sql(_testRepositorySchema.tables.testRuns)} run
|
|
475
|
+
WHERE run.name = ${name}
|
|
476
|
+
AND ${variant === undefined ? sql`1 = 1` : sql`run.variant = ${variant}`}
|
|
477
|
+
AND ${before === undefined ? sql`1 = 1` : sql`run.id < ${before}`}
|
|
478
|
+
ORDER BY run.id DESC LIMIT ${parsedLimit};
|
|
479
|
+
`);
|
|
480
|
+
return rows.map(({
|
|
481
|
+
isBaseline,
|
|
482
|
+
...run
|
|
483
|
+
}) => ({
|
|
484
|
+
...run,
|
|
485
|
+
isBaseline: isBaseline === 1
|
|
486
|
+
}));
|
|
487
|
+
}));
|
|
382
488
|
});
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
489
|
+
const listVariantBaselines = name => withSnapshot([], _effect.Effect.fnUntraced(function* (sql) {
|
|
490
|
+
const rows = yield* decodeVariantBaselines(yield* sql`
|
|
491
|
+
SELECT variants.variant, run.id, run.name, run.state FROM (
|
|
492
|
+
SELECT DISTINCT variant FROM ${sql(_testRepositorySchema.tables.testRuns)} WHERE name = ${name}
|
|
493
|
+
) variants LEFT JOIN ${sql(_testRepositorySchema.tables.testBaselines)} baseline
|
|
494
|
+
ON baseline.name = ${name} AND baseline.variant = variants.variant
|
|
495
|
+
LEFT JOIN ${sql(_testRepositorySchema.tables.testRuns)} run ON run.id = baseline.testRun ORDER BY variants.variant;
|
|
496
|
+
`);
|
|
497
|
+
return rows.map(({
|
|
498
|
+
variant,
|
|
499
|
+
id,
|
|
500
|
+
name
|
|
501
|
+
}) => ({
|
|
502
|
+
variant,
|
|
503
|
+
baseline: id === null || name === null ? _effect.Option.none() : _effect.Option.some(_TestRun.TestRun.make({
|
|
504
|
+
id,
|
|
505
|
+
name,
|
|
506
|
+
variant,
|
|
507
|
+
store: 'snapshot',
|
|
508
|
+
state: 'Complete'
|
|
509
|
+
}))
|
|
510
|
+
}));
|
|
511
|
+
}));
|
|
512
|
+
const repository = {
|
|
513
|
+
commitTestRuns,
|
|
514
|
+
getTestRun,
|
|
515
|
+
getLatestTestRun,
|
|
516
|
+
getBaselineTestRun,
|
|
517
|
+
listTestRuns,
|
|
518
|
+
listVariantBaselines,
|
|
392
519
|
getTestResultsStream,
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
insertTestResults,
|
|
396
|
-
|
|
520
|
+
withTestRun,
|
|
521
|
+
withStats: use => (0, _testResultStats.withStats)(use).pipe(_effect.Effect.provideService(_sql.SqlClient.SqlClient, cache)),
|
|
522
|
+
insertTestResult: (result, run, schemas) => insertTestResults([result], run, schemas),
|
|
523
|
+
insertTestResults
|
|
524
|
+
};
|
|
525
|
+
return {
|
|
526
|
+
repository,
|
|
527
|
+
cache,
|
|
528
|
+
snapshot: getMemorySnapshot
|
|
397
529
|
};
|
|
398
|
-
return service;
|
|
399
530
|
});
|
|
400
531
|
//# sourceMappingURL=Test.repository.sqlite.js.map
|