@creative-introvert/tons-of-tests 2.0.0 → 3.0.0-rc.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.
Files changed (120) hide show
  1. package/README.md +263 -134
  2. package/dist/cjs/Classify.js +6 -2
  3. package/dist/cjs/Classify.js.map +1 -1
  4. package/dist/cjs/RepositoryError.js +24 -33
  5. package/dist/cjs/RepositoryError.js.map +1 -1
  6. package/dist/cjs/Show.js +18 -0
  7. package/dist/cjs/Show.js.map +1 -1
  8. package/dist/cjs/Test.js +41 -2
  9. package/dist/cjs/Test.js.map +1 -1
  10. package/dist/cjs/Test.repository.js +8 -17
  11. package/dist/cjs/Test.repository.js.map +1 -1
  12. package/dist/cjs/TestRun.js +20 -0
  13. package/dist/cjs/TestRun.js.map +1 -0
  14. package/dist/cjs/TestValueError.js +23 -0
  15. package/dist/cjs/TestValueError.js.map +1 -0
  16. package/dist/cjs/index.js +0 -7
  17. package/dist/cjs/index.js.map +1 -1
  18. package/dist/cjs/internal/Classify.js +1 -9
  19. package/dist/cjs/internal/Classify.js.map +1 -1
  20. package/dist/cjs/internal/Test.js +305 -109
  21. package/dist/cjs/internal/Test.js.map +1 -1
  22. package/dist/cjs/internal/Test.repository.sqlite.js +266 -304
  23. package/dist/cjs/internal/Test.repository.sqlite.js.map +1 -1
  24. package/dist/cjs/internal/Test.serialization.js +128 -0
  25. package/dist/cjs/internal/Test.serialization.js.map +1 -0
  26. package/dist/cjs/internal/common.js.map +1 -1
  27. package/dist/cjs/internal/stats.js +37 -3
  28. package/dist/cjs/internal/stats.js.map +1 -1
  29. package/dist/cjs/internal/summarize.js +155 -36
  30. package/dist/cjs/internal/summarize.js.map +1 -1
  31. package/dist/cjs/internal/test-repository-fixtures.js +31 -0
  32. package/dist/cjs/internal/test-repository-fixtures.js.map +1 -0
  33. package/dist/cjs/internal/test-repository-migrations.js +68 -0
  34. package/dist/cjs/internal/test-repository-migrations.js.map +1 -0
  35. package/dist/cjs/internal/test-result-stats.js +89 -0
  36. package/dist/cjs/internal/test-result-stats.js.map +1 -0
  37. package/dist/dts/Classify.d.ts +4 -1
  38. package/dist/dts/Classify.d.ts.map +1 -1
  39. package/dist/dts/RepositoryError.d.ts +19 -20
  40. package/dist/dts/RepositoryError.d.ts.map +1 -1
  41. package/dist/dts/Show.d.ts +8 -8
  42. package/dist/dts/Show.d.ts.map +1 -1
  43. package/dist/dts/Test.d.ts +155 -11
  44. package/dist/dts/Test.d.ts.map +1 -1
  45. package/dist/dts/Test.repository.d.ts +94 -8
  46. package/dist/dts/Test.repository.d.ts.map +1 -1
  47. package/dist/dts/TestRun.d.ts +18 -0
  48. package/dist/dts/TestRun.d.ts.map +1 -0
  49. package/dist/dts/TestValueError.d.ts +17 -0
  50. package/dist/dts/TestValueError.d.ts.map +1 -0
  51. package/dist/dts/index.d.ts +0 -1
  52. package/dist/dts/index.d.ts.map +1 -1
  53. package/dist/dts/internal/Classify.d.ts +0 -1
  54. package/dist/dts/internal/Classify.d.ts.map +1 -1
  55. package/dist/dts/internal/Test.d.ts +58 -31
  56. package/dist/dts/internal/Test.d.ts.map +1 -1
  57. package/dist/dts/internal/Test.repository.sqlite.d.ts +66 -11
  58. package/dist/dts/internal/Test.repository.sqlite.d.ts.map +1 -1
  59. package/dist/dts/internal/Test.serialization.d.ts +34 -0
  60. package/dist/dts/internal/Test.serialization.d.ts.map +1 -0
  61. package/dist/dts/internal/common.d.ts +1 -1
  62. package/dist/dts/internal/common.d.ts.map +1 -1
  63. package/dist/dts/internal/stats.d.ts +15 -1
  64. package/dist/dts/internal/stats.d.ts.map +1 -1
  65. package/dist/dts/internal/summarize.d.ts +39 -7
  66. package/dist/dts/internal/summarize.d.ts.map +1 -1
  67. package/dist/dts/internal/test-repository-fixtures.d.ts +22 -0
  68. package/dist/dts/internal/test-repository-fixtures.d.ts.map +1 -0
  69. package/dist/dts/internal/test-repository-migrations.d.ts +11 -0
  70. package/dist/dts/internal/test-repository-migrations.d.ts.map +1 -0
  71. package/dist/dts/internal/test-result-stats.d.ts +7 -0
  72. package/dist/dts/internal/test-result-stats.d.ts.map +1 -0
  73. package/dist/esm/Classify.js +5 -1
  74. package/dist/esm/Classify.js.map +1 -1
  75. package/dist/esm/RepositoryError.js +14 -29
  76. package/dist/esm/RepositoryError.js.map +1 -1
  77. package/dist/esm/Show.js +2 -2
  78. package/dist/esm/Show.js.map +1 -1
  79. package/dist/esm/Test.js +38 -1
  80. package/dist/esm/Test.js.map +1 -1
  81. package/dist/esm/Test.repository.js +7 -16
  82. package/dist/esm/Test.repository.js.map +1 -1
  83. package/dist/esm/TestRun.js +13 -0
  84. package/dist/esm/TestRun.js.map +1 -0
  85. package/dist/esm/TestValueError.js +16 -0
  86. package/dist/esm/TestValueError.js.map +1 -0
  87. package/dist/esm/index.js +0 -1
  88. package/dist/esm/index.js.map +1 -1
  89. package/dist/esm/internal/Classify.js +0 -7
  90. package/dist/esm/internal/Classify.js.map +1 -1
  91. package/dist/esm/internal/Test.js +299 -108
  92. package/dist/esm/internal/Test.js.map +1 -1
  93. package/dist/esm/internal/Test.repository.sqlite.js +265 -301
  94. package/dist/esm/internal/Test.repository.sqlite.js.map +1 -1
  95. package/dist/esm/internal/Test.serialization.js +119 -0
  96. package/dist/esm/internal/Test.serialization.js.map +1 -0
  97. package/dist/esm/internal/common.js.map +1 -1
  98. package/dist/esm/internal/stats.js +35 -2
  99. package/dist/esm/internal/stats.js.map +1 -1
  100. package/dist/esm/internal/summarize.js +155 -35
  101. package/dist/esm/internal/summarize.js.map +1 -1
  102. package/dist/esm/internal/test-repository-fixtures.js +25 -0
  103. package/dist/esm/internal/test-repository-fixtures.js.map +1 -0
  104. package/dist/esm/internal/test-repository-migrations.js +62 -0
  105. package/dist/esm/internal/test-repository-migrations.js.map +1 -0
  106. package/dist/esm/internal/test-result-stats.js +83 -0
  107. package/dist/esm/internal/test-result-stats.js.map +1 -0
  108. package/package.json +1 -1
  109. package/dist/cjs/Test.repository.types.js +0 -14
  110. package/dist/cjs/Test.repository.types.js.map +0 -1
  111. package/dist/cjs/internal/lib/Schema.js +0 -14
  112. package/dist/cjs/internal/lib/Schema.js.map +0 -1
  113. package/dist/dts/Test.repository.types.d.ts +0 -56
  114. package/dist/dts/Test.repository.types.d.ts.map +0 -1
  115. package/dist/dts/internal/lib/Schema.d.ts +0 -4
  116. package/dist/dts/internal/lib/Schema.d.ts.map +0 -1
  117. package/dist/esm/Test.repository.types.js +0 -7
  118. package/dist/esm/Test.repository.types.js.map +0 -1
  119. package/dist/esm/internal/lib/Schema.js +0 -7
  120. package/dist/esm/internal/lib/Schema.js.map +0 -1
@@ -3,341 +3,303 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- Object.defineProperty(exports, "TestRepository", {
6
+ exports.makeTestRepository = exports.TestResultStorage = void 0;
7
+ Object.defineProperty(exports, "tables", {
7
8
  enumerable: true,
8
9
  get: function () {
9
- return _TestRepository.TestRepository;
10
+ return _testRepositoryMigrations.tables;
10
11
  }
11
12
  });
12
- exports.tables = exports.makeTestRepository = void 0;
13
- var SqlClient = /*#__PURE__*/_interopRequireWildcard(/*#__PURE__*/require("effect/sql/SqlClient"));
14
13
  var _effect = /*#__PURE__*/require("effect");
15
- var _TestRepository = /*#__PURE__*/require("../Test.repository.js");
16
- var _TestRepositoryTypes = /*#__PURE__*/require("../Test.repository.types.js");
14
+ var _sql = /*#__PURE__*/require("effect/sql");
15
+ var _TestRun = /*#__PURE__*/require("../TestRun.js");
17
16
  var _RepositoryError = /*#__PURE__*/require("../RepositoryError.js");
18
17
  var _Classify = /*#__PURE__*/require("./Classify.js");
19
- var _Schema = /*#__PURE__*/require("./lib/Schema.js");
20
- var cache;
21
- 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
- const tables = exports.tables = {
23
- testResults: 'test-results',
24
- testRuns: 'test-runs',
25
- testRunResults: 'test-run-results'
26
- };
27
- const TestResultWriteSchema = /*#__PURE__*/_effect.Schema.Struct({
18
+ var _testRepositoryMigrations = /*#__PURE__*/require("./test-repository-migrations.js");
19
+ var _testResultStats = /*#__PURE__*/require("./test-result-stats.js");
20
+ var _TestSerialization = /*#__PURE__*/require("./Test.serialization.js");
21
+ const TestResultStorage = exports.TestResultStorage = /*#__PURE__*/_effect.Schema.Struct({
28
22
  id: _effect.Schema.String,
29
23
  hashTestCase: _effect.Schema.String,
24
+ key: _effect.Schema.String,
25
+ input: /*#__PURE__*/_effect.Schema.fromJsonString(_effect.Schema.Json),
26
+ result: /*#__PURE__*/_effect.Schema.fromJsonString(_effect.Schema.Json),
27
+ expected: /*#__PURE__*/_effect.Schema.fromJsonString(_effect.Schema.Json),
28
+ label: _Classify.LabelSchema
29
+ });
30
+ const StoredResult = /*#__PURE__*/_effect.Schema.Struct({
31
+ ...TestResultStorage.fields,
32
+ input: _effect.Schema.String,
33
+ result: _effect.Schema.String,
34
+ expected: _effect.Schema.String,
30
35
  ordering: _effect.Schema.Int,
31
- input: /*#__PURE__*/_effect.Schema.fromJsonString(_effect.Schema.Unknown),
32
- result: /*#__PURE__*/_effect.Schema.fromJsonString(_effect.Schema.Unknown),
33
- expected: /*#__PURE__*/_effect.Schema.fromJsonString(_effect.Schema.Unknown),
34
- label: _Classify.LabelSchema,
35
- tags: /*#__PURE__*/(0, _Schema.split)(','),
36
+ resultOrdering: _effect.Schema.Int,
37
+ tags: /*#__PURE__*/_effect.Schema.fromJsonString(/*#__PURE__*/_effect.Schema.Array(_effect.Schema.String)),
36
38
  timeMillis: _effect.Schema.Finite
37
39
  });
38
- const TestResultsSchema = /*#__PURE__*/_effect.Schema.Array(TestResultWriteSchema);
39
- const CountSchema = /*#__PURE__*/_effect.Schema.Tuple([_effect.Schema.Struct({
40
- count: _effect.Schema.Int
41
- })]).pipe(/*#__PURE__*/_effect.Schema.decodeTo(_effect.Schema.Int, /*#__PURE__*/_effect.SchemaTransformation.transform({
42
- decode: ([{
43
- count
44
- }]) => count,
45
- encode: count => [{
46
- count
47
- }]
40
+ const encodeResult = /*#__PURE__*/_effect.Schema.encodeEffect(StoredResult);
41
+ const decodeRows = /*#__PURE__*/_effect.Schema.decodeUnknownEffect(/*#__PURE__*/_effect.Schema.Array(StoredResult));
42
+ const decodeRuns = /*#__PURE__*/_effect.Schema.decodeUnknownEffect(/*#__PURE__*/_effect.Schema.Array(_TestRun.TestRun));
43
+ const decodeListedRuns = /*#__PURE__*/_effect.Schema.decodeUnknownEffect(/*#__PURE__*/_effect.Schema.Array(/*#__PURE__*/_effect.Schema.Struct({
44
+ ..._TestRun.TestRun.fields,
45
+ isBaseline: /*#__PURE__*/_effect.Schema.Literals([0, 1])
46
+ })));
47
+ const decodeVariantBaselines = /*#__PURE__*/_effect.Schema.decodeUnknownEffect(/*#__PURE__*/_effect.Schema.Array(/*#__PURE__*/_effect.Schema.Struct({
48
+ variant: _effect.Schema.String,
49
+ id: /*#__PURE__*/_effect.Schema.NullOr(_TestRun.TestRun.fields.id),
50
+ name: /*#__PURE__*/_effect.Schema.NullOr(_TestRun.TestRun.fields.name),
51
+ state: /*#__PURE__*/_effect.Schema.NullOr(_TestRun.TestRun.fields.state)
52
+ })));
53
+ const decodeRunLimit = /*#__PURE__*/_effect.Schema.decodeUnknownEffect(/*#__PURE__*/_effect.Schema.Int.check(/*#__PURE__*/_effect.Schema.isBetween({
54
+ minimum: 1,
55
+ maximum: 100
48
56
  })));
49
- const TestRunsReadSchema = /*#__PURE__*/_effect.Schema.Array(_TestRepositoryTypes.TestRun);
50
- const TestRunResultsSchema = /*#__PURE__*/_effect.Schema.Array(/*#__PURE__*/_effect.Schema.Struct({
51
- testRun: _effect.Schema.Int,
52
- testResult: _effect.Schema.String
53
- }));
54
57
  const makeTestRepository = exports.makeTestRepository = /*#__PURE__*/_effect.Effect.gen(function* () {
55
- const sql = yield* SqlClient.SqlClient;
58
+ const sql = yield* _sql.SqlClient.SqlClient;
56
59
  yield* sql`PRAGMA auto_vacuum = FULL;`;
57
- yield* sql`
58
- CREATE TABLE IF NOT EXISTS ${sql(tables.testRuns)}(
59
- id INTEGER PRIMARY KEY AUTOINCREMENT,
60
- name TEXT NOT NULL,
61
- hash TEXT
62
- );
63
- `;
64
- yield* sql`CREATE INDEX IF NOT EXISTS idx_name_hash ON ${sql(tables.testRuns)}(name, hash)`;
65
- // At most one "current" (hash IS NULL) row per suite name. Partial index
66
- // so committed rows are unaffected.
67
- yield* sql`
68
- CREATE UNIQUE INDEX IF NOT EXISTS idx_current_per_name
69
- ON ${sql(tables.testRuns)}(name)
70
- WHERE hash IS NULL;
71
- `;
72
- yield* sql`
73
- CREATE TABLE IF NOT EXISTS ${sql(tables.testResults)}(
74
- id TEXT PRIMARY KEY,
75
- hashTestCase TEXT NOT NULL,
76
- ordering INTEGER NOT NULL,
77
- input TEXT,
78
- result TEXT,
79
- expected TEXT,
80
- label TEXT NOT NULL,
81
- tags TEXT NOT NULL,
82
- timeMillis INTEGER NOT NULL
83
- );
84
- `;
85
- yield* sql`
86
- CREATE TABLE IF NOT EXISTS ${sql(tables.testRunResults)}(
87
- testRun INTEGER NOT NULL,
88
- testResult TEXT NOT NULL,
89
- FOREIGN KEY (testRun) REFERENCES ${sql(tables.testRuns)}(id),
90
- FOREIGN KEY (testResult) REFERENCES ${sql(tables.testResults)}(id),
91
- UNIQUE (testRun, testResult)
92
- );
93
- `;
94
- // Lookup by the junction's `testResult` column: the existing
95
- // UNIQUE(testRun, testResult) index is keyed on testRun first, so orphan
96
- // cleanup (`... NOT IN (SELECT testResult ...)`) and stream joins
97
- // (`res.id = runres.testResult`) would otherwise full-scan the junction.
98
- yield* sql`CREATE INDEX IF NOT EXISTS idx_trr_testresult ON ${sql(tables.testRunResults)}(testResult)`;
99
- yield* sql`CREATE INDEX IF NOT EXISTS idx_tr_hashtestcase ON ${sql(tables.testResults)}(hashTestCase)`;
100
- // SQLite's default SQLITE_MAX_VARIABLE_NUMBER is 999; testResults has 9
101
- // columns and junction rows 2, so 50 rows stays well under the limit.
102
- const INSERT_BATCH_SIZE = 50;
103
- const insertTestResults = (testResults, testRun) => sql.withTransaction(_effect.Effect.gen(function* () {
104
- if (testResults.length === 0) return;
105
- const encoded = yield* _effect.Effect.forEach(testResults, _ => _effect.Schema.encodeEffect(TestResultWriteSchema)(_).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('insertTestResults'))));
106
- for (let i = 0; i < encoded.length; i += INSERT_BATCH_SIZE) {
107
- const chunk = encoded.slice(i, i + INSERT_BATCH_SIZE);
108
- yield* sql`
109
- INSERT INTO ${sql(tables.testResults)}
110
- ${sql.insert(chunk)}
60
+ yield* _testRepositoryMigrations.migrate;
61
+ const getTestRun = _effect.Effect.fnUntraced(function* (id) {
62
+ return _effect.Array.head(yield* decodeRuns(yield* sql`SELECT * FROM ${sql(_testRepositoryMigrations.tables.testRuns)} WHERE id = ${id};`));
63
+ });
64
+ const insertTestResults = _effect.Effect.fnUntraced(function* (results, run, schemas) {
65
+ const codec = (0, _TestSerialization.makeResultCodec)(schemas, run.name);
66
+ // Each statement stays below SQLite's lowest default limit of 999 parameters.
67
+ for (let offset = 0; offset < Math.max(1, results.length); offset += 50) {
68
+ const encoded = yield* _effect.Effect.forEach(results.slice(offset, offset + 50), _effect.Effect.fnUntraced(function* (result) {
69
+ const encoded = yield* codec.encode(result);
70
+ return yield* encodeResult({
71
+ ...encoded,
72
+ input: (0, _TestSerialization.stringifyJson)(encoded.input),
73
+ result: (0, _TestSerialization.stringifyJson)(encoded.result),
74
+ expected: (0, _TestSerialization.stringifyJson)(encoded.expected)
75
+ });
76
+ }));
77
+ yield* sql.withTransaction(_effect.Effect.gen(function* () {
78
+ const saved = yield* getTestRun(run.id);
79
+ if (_effect.Option.isNone(saved)) {
80
+ return yield* new _RepositoryError.TestRunNotFound({
81
+ testRunId: run.id
82
+ });
83
+ }
84
+ if (saved.value.state !== 'Running') {
85
+ return yield* _effect.Effect.die(new Error('Only Running test runs accept result writes.'));
86
+ }
87
+ if (encoded.length === 0) return;
88
+ const content = encoded.map(result => ({
89
+ id: result.id,
90
+ hashTestCase: result.hashTestCase,
91
+ key: result.key,
92
+ input: result.input,
93
+ result: result.result,
94
+ expected: result.expected,
95
+ label: result.label
96
+ }));
97
+ yield* sql`
98
+ INSERT INTO ${sql(_testRepositoryMigrations.tables.testResults)} ${sql.insert(content)}
111
99
  ON CONFLICT(id) DO NOTHING;
112
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryWriteError.from('insertTestResults')));
100
+ `;
101
+ const links = encoded.map(result => ({
102
+ testRun: run.id,
103
+ testResult: result.id,
104
+ ordering: result.ordering,
105
+ resultOrdering: result.resultOrdering,
106
+ timeMillis: result.timeMillis,
107
+ tags: result.tags
108
+ }));
109
+ yield* sql`
110
+ INSERT INTO ${sql(_testRepositoryMigrations.tables.testRunResults)} ${sql.insert(links)}
111
+ ON CONFLICT(testRun, testResult) DO UPDATE SET
112
+ ordering = excluded.ordering,
113
+ resultOrdering = excluded.resultOrdering,
114
+ timeMillis = excluded.timeMillis,
115
+ tags = excluded.tags;
116
+ `;
117
+ }));
113
118
  }
114
- // Junction rows reference the resolved testRun.id directly,
115
- // so no correlated subquery.
116
- const junction = encoded.map(_ => ({
117
- testRun: testRun.id,
118
- testResult: _.id
119
- }));
120
- for (let i = 0; i < junction.length; i += INSERT_BATCH_SIZE) {
121
- const chunk = junction.slice(i, i + INSERT_BATCH_SIZE);
122
- yield* sql`
123
- INSERT INTO ${sql(tables.testRunResults)}
124
- ${sql.insert(chunk)}
125
- ON CONFLICT(testRun, testResult) DO NOTHING;
126
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryWriteError.from('insertTestResults')));
119
+ });
120
+ const getTestResultsStream = (run, schemas, matching) => {
121
+ if (matching !== undefined && matching.length > 50) {
122
+ return _effect.Stream.die(new Error('Match at most 50 case/key pairs per query.'));
127
123
  }
128
- })).pipe(_effect.Effect.catchTag('SqlError', e => _effect.Effect.fail(_RepositoryError.RepositoryWriteError.from('insertTestResults')(e))));
129
- const insertTestResult = (testResult, testRun) => insertTestResults([testResult], testRun);
130
- const makeTestResultSchema = schemas => {
131
- const input = schemas?.input ?? _effect.Schema.Unknown;
132
- const result = schemas?.result ?? _effect.Schema.Unknown;
133
- const expected = schemas?.expected ?? _effect.Schema.Unknown;
134
- return _effect.Schema.Struct({
135
- id: _effect.Schema.String,
136
- hashTestCase: _effect.Schema.String,
137
- ordering: _effect.Schema.Int,
138
- input: _effect.Schema.fromJsonString(input),
139
- result: _effect.Schema.fromJsonString(result),
140
- expected: _effect.Schema.fromJsonString(expected),
141
- label: _Classify.LabelSchema,
142
- tags: (0, _Schema.split)(','),
143
- timeMillis: _effect.Schema.Finite
144
- });
145
- };
146
- const getTestResultsStream = (testRun, schemas) => {
147
- const RowSchema = makeTestResultSchema(schemas);
148
- const RowsSchema = _effect.Schema.Array(RowSchema);
149
- // The SQLite driver returns rows in one batch. Decode that batch once
150
- // before exposing it as a stream.
151
- return _effect.Stream.unwrap(_effect.Effect.gen(function* () {
152
- const rows = yield* sql`
153
- SELECT res.* FROM ${sql(tables.testResults)} res
154
- JOIN ${sql(tables.testRunResults)} runres ON res.id = runres.testResult
155
- JOIN ${sql(tables.testRuns)} runs ON runs.id = runres.testRun
156
- WHERE runs.id = ${testRun.id}
157
- ORDER BY ordering ASC;
158
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryQueryError.fromSql('getTestResultsStream')));
159
- const decoded = yield* _effect.Schema.decodeUnknownEffect(RowsSchema)(rows).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('getTestResultsStream')));
160
- return _effect.Stream.fromIterable(decoded);
124
+ const codec = (0, _TestSerialization.makeResultCodec)(schemas, run.name);
125
+ 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}`));
126
+ // Match case/key pairs first, so each batch avoids scanning the whole prior run.
127
+ // CROSS JOIN keeps SQLite from choosing the run-order index for that lookup.
128
+ const from = matching === undefined ? sql`${sql(_testRepositoryMigrations.tables.testRunResults)} runres
129
+ JOIN ${sql(_testRepositoryMigrations.tables.testResults)} res ON res.id = runres.testResult` : sql`${sql(_testRepositoryMigrations.tables.testResults)} res
130
+ CROSS JOIN ${sql(_testRepositoryMigrations.tables.testRunResults)} runres ON res.id = runres.testResult`;
131
+ // The driver cannot stream a query. Keyset pages keep decoded rows bounded.
132
+ return _effect.Stream.paginate(undefined, _effect.Effect.fnUntraced(function* (after) {
133
+ const rows = yield* decodeRows(yield* sql`
134
+ SELECT res.id, res.hashTestCase, res.key,
135
+ runres.ordering, runres.resultOrdering, res.input, res.result,
136
+ res.expected, res.label, runres.tags, runres.timeMillis
137
+ FROM ${from}
138
+ WHERE runres.testRun = ${run.id} AND ${filter}
139
+ AND ${after === undefined ? sql`1 = 1` : sql`
140
+ (runres.ordering, runres.resultOrdering, runres.testResult) >
141
+ (${after.ordering}, ${after.resultOrdering}, ${after.id})
142
+ `}
143
+ ORDER BY runres.ordering, runres.resultOrdering, runres.testResult
144
+ LIMIT 100;
145
+ `);
146
+ const last = rows[rows.length - 1];
147
+ const decoded = yield* _effect.Effect.forEach(rows, codec.decode);
148
+ return [decoded, rows.length < 100 ? _effect.Option.none() : _effect.Option.some({
149
+ ordering: last.ordering,
150
+ resultOrdering: last.resultOrdering,
151
+ id: last.id
152
+ })];
161
153
  }));
162
154
  };
163
- const hasResults = _effect.Effect.fnUntraced(function* (testRun) {
164
- const b = yield* sql`
165
- SELECT COUNT(*) count FROM ${sql(tables.testRunResults)}
166
- WHERE testRun = ${testRun.id};
167
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryQueryError.fromSql('hasResults')));
168
- const r = yield* _effect.Schema.decodeUnknownEffect(CountSchema)(b).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('hasResults')));
169
- return r > 0;
170
- });
171
- const getPreviousTestRun = _effect.Effect.fnUntraced(function* (name) {
172
- const r = yield* sql`
173
- SELECT * FROM ${sql(tables.testRuns)}
174
- WHERE hash IS NOT NULL AND name = ${name}
175
- ORDER BY id DESC
176
- LIMIT 1;
177
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryQueryError.fromSql('getPreviousTestRun')));
178
- const p = yield* _effect.Schema.decodeUnknownEffect(TestRunsReadSchema)(r).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('getPreviousTestRun')));
179
- return _effect.Array.head(p);
180
- });
181
- const getAllTestRuns = _effect.Effect.gen(function* () {
182
- const results = yield* sql`
183
- SELECT * from ${sql(tables.testRuns)};
184
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryQueryError.fromSql('getAllTestRuns')));
185
- return yield* _effect.Schema.decodeUnknownEffect(TestRunsReadSchema)(results).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('getAllTestRuns')));
186
- });
187
- const getAllTestRunResults = _effect.Effect.gen(function* () {
188
- const results = yield* sql`
189
- SELECT * from ${sql(tables.testRunResults)};
190
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryQueryError.fromSql('getAllTestRunResults')));
191
- return yield* _effect.Schema.decodeUnknownEffect(TestRunResultsSchema)(results).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('getAllTestRunResults')));
192
- });
193
- const getAllTestResults = _effect.Effect.gen(function* () {
194
- const results = yield* sql`
195
- SELECT * from ${sql(tables.testResults)};
196
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryQueryError.fromSql('getAllTestResults')));
197
- return yield* _effect.Schema.decodeUnknownEffect(TestResultsSchema)(results).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('getAllTestResults')));
155
+ const getLatestTestRun = _effect.Effect.fnUntraced(function* ({
156
+ name,
157
+ variant
158
+ }) {
159
+ return _effect.Array.head(yield* decodeRuns(yield* sql`
160
+ SELECT * FROM ${sql(_testRepositoryMigrations.tables.testRuns)}
161
+ WHERE name = ${name} AND variant = ${variant}
162
+ ORDER BY id DESC LIMIT 1;
163
+ `));
198
164
  });
199
- const getLastTestRunHash = _effect.Effect.fnUntraced(function* (name) {
200
- const rows = yield* sql`
201
- SELECT *
202
- FROM ${sql(tables.testRuns)}
203
- WHERE hash IS NOT NULL AND name = ${name}
204
- ORDER BY id DESC
205
- LIMIT 1;
206
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryQueryError.fromSql('getLastTestRunHash')));
207
- const runs = yield* _effect.Schema.decodeUnknownEffect(TestRunsReadSchema)(rows).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('getLastTestRunHash')));
208
- return _effect.Array.head(runs).pipe(_effect.Option.flatMap(run => _effect.Option.fromNullishOr(run.hash)));
165
+ const getBaselineTestRun = _effect.Effect.fnUntraced(function* ({
166
+ name,
167
+ variant
168
+ }) {
169
+ return _effect.Array.head(yield* decodeRuns(yield* sql`
170
+ SELECT run.* FROM ${sql(_testRepositoryMigrations.tables.testBaselines)} baseline
171
+ JOIN ${sql(_testRepositoryMigrations.tables.testRuns)} run ON run.id = baseline.testRun
172
+ WHERE baseline.name = ${name} AND baseline.variant = ${variant};
173
+ `));
209
174
  });
210
- const getOrCreateCurrentTestRun = name => sql.withTransaction(_effect.Effect.gen(function* () {
211
- const currentRaw = yield* sql`
212
- SELECT *
213
- FROM ${sql(tables.testRuns)}
214
- WHERE hash IS NULL AND name = ${name}
215
- ORDER BY id DESC
216
- LIMIT 1;
217
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryQueryError.fromSql('getOrCreateCurrentTestRun')));
218
- const current = yield* _effect.Schema.decodeUnknownEffect(TestRunsReadSchema)(currentRaw).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('getOrCreateCurrentTestRun')));
219
- const existing = _effect.Array.head(current);
220
- if (_effect.Option.isSome(existing)) {
221
- return existing.value;
222
- }
223
- const insertedRaw = yield* sql`
224
- INSERT INTO ${sql(tables.testRuns)}
225
- ${sql.insert({
226
- name
227
- })}
228
- RETURNING *;
229
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryWriteError.from('getOrCreateCurrentTestRun')));
230
- const inserted = yield* _effect.Schema.decodeUnknownEffect(TestRunsReadSchema)(insertedRaw).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('getOrCreateCurrentTestRun')));
175
+ const createTestRun = _effect.Effect.fnUntraced(function* (input) {
176
+ const inserted = yield* decodeRuns(yield* sql`
177
+ INSERT INTO ${sql(_testRepositoryMigrations.tables.testRuns)}
178
+ ${sql.insert({
179
+ ...input,
180
+ state: 'Running'
181
+ })} RETURNING *;
182
+ `);
231
183
  return yield* _effect.Effect.fromOption(_effect.Array.head(inserted)).pipe(_effect.Effect.mapError(() => new _RepositoryError.TestRunNotCreated({
232
- testSuiteName: name
184
+ testSuiteName: input.name
233
185
  })));
234
- })).pipe(_effect.Effect.catchTag('SqlError', e => _effect.Effect.fail(_RepositoryError.RepositoryQueryError.fromSql('getOrCreateCurrentTestRun')(e))));
235
- const clearStale = ({
236
- name,
237
- keep = 1
238
- }) => sql.withTransaction(_effect.Effect.gen(function* () {
239
- const raw = yield* sql`
240
- SELECT *
241
- FROM ${sql(tables.testRuns)}
242
- WHERE hash IS NOT NULL AND name = ${name}
243
- ORDER BY id DESC
244
- LIMIT -1 OFFSET ${keep};
245
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryQueryError.fromSql('clearStale')));
246
- const staleTestRuns = yield* _effect.Schema.decodeUnknownEffect(TestRunsReadSchema)(raw).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('clearStale')));
247
- if (staleTestRuns.length === 0) {
248
- return;
186
+ }, sql.withTransaction);
187
+ const finishTestRun = _effect.Effect.fnUntraced(function* (run, state) {
188
+ const updated = _effect.Array.head(yield* decodeRuns(yield* sql`
189
+ UPDATE ${sql(_testRepositoryMigrations.tables.testRuns)} SET state = ${state}
190
+ WHERE id = ${run.id} AND state = 'Running' RETURNING *;
191
+ `));
192
+ if (_effect.Option.isNone(updated)) {
193
+ return yield* new _RepositoryError.TestRunNotFound({
194
+ testRunId: run.id
195
+ });
249
196
  }
250
- const ids = staleTestRuns.map(r => r.id);
251
- yield* sql`
252
- DELETE FROM ${sql(tables.testRunResults)}
253
- WHERE testRun IN ${sql.in(ids)};
254
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryWriteError.from('clearStale')));
255
- yield* sql`
256
- DELETE FROM ${sql(tables.testRuns)}
257
- WHERE id IN ${sql.in(ids)};
258
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryWriteError.from('clearStale')));
259
- yield* sql`
260
- DELETE FROM ${sql(tables.testResults)}
261
- WHERE id NOT IN (
262
- SELECT DISTINCT testResult FROM ${sql(tables.testRunResults)}
263
- );
264
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryWriteError.from('clearStale')));
265
- yield* _effect.Console.log('Cleared stale test runs.');
266
- })).pipe(_effect.Effect.catchTag('SqlError', e => _effect.Effect.fail(_RepositoryError.RepositoryWriteError.from('clearStale')(e))));
267
- const clearUncommitedTestResults = ({
268
- name
269
- }) => sql.withTransaction(_effect.Effect.gen(function* () {
270
- const currentRaw = yield* sql`
271
- SELECT *
272
- FROM ${sql(tables.testRuns)}
273
- WHERE hash IS NULL AND name = ${name}
274
- ORDER BY id DESC
275
- LIMIT 1;
276
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryQueryError.fromSql('clearUncommitedTestResults')));
277
- const current = yield* _effect.Schema.decodeUnknownEffect(TestRunsReadSchema)(currentRaw).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('clearUncommitedTestResults')));
278
- const currentTestRun = _effect.Array.head(current);
279
- if (_effect.Option.isNone(currentTestRun)) {
280
- return;
197
+ }, sql.withTransaction);
198
+ const withTestRun = (input, execute) => _effect.Effect.acquireUseRelease(createTestRun(input), run => _effect.Effect.suspend(() => execute(run)).pipe(_effect.Effect.map(value => ({
199
+ run,
200
+ value
201
+ }))), (run, exit) => finishTestRun(run, _effect.Exit.isSuccess(exit) ? 'Complete' : 'Failed')).pipe(_effect.Effect.map(({
202
+ run,
203
+ value
204
+ }) => ({
205
+ run: new _TestRun.TestRun({
206
+ id: run.id,
207
+ name: run.name,
208
+ variant: run.variant,
209
+ state: 'Complete'
210
+ }),
211
+ value
212
+ })));
213
+ const commitTestRun = _effect.Effect.fnUntraced(function* (id) {
214
+ const found = yield* getTestRun(id);
215
+ if (_effect.Option.isNone(found)) {
216
+ return yield* new _RepositoryError.TestRunNotFound({
217
+ testRunId: id
218
+ });
219
+ }
220
+ const run = found.value;
221
+ if (run.state !== 'Complete') {
222
+ return yield* new _RepositoryError.TestRunIncomplete({
223
+ testSuiteName: run.name,
224
+ testRunId: run.id,
225
+ variant: run.variant
226
+ });
281
227
  }
282
228
  yield* sql`
283
- DELETE FROM ${sql(tables.testRunResults)}
284
- WHERE testRun = ${currentTestRun.value.id};
285
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryWriteError.from('clearUncommitedTestResults')));
286
- yield* sql`
287
- DELETE FROM ${sql(tables.testResults)}
288
- WHERE id NOT IN (
289
- SELECT DISTINCT testResult FROM ${sql(tables.testRunResults)}
290
- );
291
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryWriteError.from('clearUncommitedTestResults')));
292
- })).pipe(_effect.Effect.catchTag('SqlError', e => _effect.Effect.fail(_RepositoryError.RepositoryWriteError.from('clearUncommitedTestResults')(e))));
293
- const commitCurrentTestRun = _effect.Effect.fnUntraced(function* ({
229
+ INSERT INTO ${sql(_testRepositoryMigrations.tables.testBaselines)}
230
+ ${sql.insert({
231
+ name: run.name,
232
+ variant: run.variant,
233
+ testRun: run.id
234
+ })}
235
+ ON CONFLICT(name, variant) DO UPDATE SET testRun = excluded.testRun;
236
+ `;
237
+ }, sql.withTransaction);
238
+ const listTestRuns = _effect.Effect.fnUntraced(function* ({
294
239
  name,
295
- hash
240
+ variant,
241
+ before,
242
+ limit
296
243
  }) {
297
- const currentTestRunResults = yield* _effect.Schema.decodeUnknownEffect(CountSchema)(yield* sql`
298
- SELECT COUNT(*) count FROM ${sql(tables.testRunResults)}
299
- WHERE testRun = (
300
- SELECT id
301
- FROM ${sql(tables.testRuns)}
302
- WHERE hash IS NULL AND name = ${name}
303
- );
304
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryQueryError.fromSql('commitCurrentTestRun')))).pipe(_effect.Effect.mapError(_RepositoryError.RepositoryDecodeError.from('commitCurrentTestRun')));
305
- if (currentTestRunResults === 0) {
306
- yield* _effect.Console.log('Current run has no test results, not commiting.');
307
- return;
308
- }
309
- yield* sql`
310
- UPDATE ${sql(tables.testRuns)}
311
- SET hash = ${hash}
312
- WHERE id = (
313
- SELECT id
314
- FROM ${sql(tables.testRuns)}
315
- WHERE hash IS NULL AND name = ${name}
316
- );
317
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryWriteError.from('commitCurrentTestRun')));
318
- yield* sql`
319
- INSERT INTO ${sql(tables.testRuns)}
320
- ${sql.insert({
321
- name
322
- })};
323
- `.pipe(_effect.Effect.mapError(_RepositoryError.RepositoryWriteError.from('commitCurrentTestRun')));
324
- yield* _effect.Console.log('Commited test run.');
244
+ const parsedLimit = yield* decodeRunLimit(limit);
245
+ const runs = yield* decodeListedRuns(yield* sql`
246
+ SELECT run.*, EXISTS(
247
+ SELECT 1 FROM ${sql(_testRepositoryMigrations.tables.testBaselines)} baseline
248
+ WHERE baseline.name = run.name AND baseline.variant = run.variant
249
+ AND baseline.testRun = run.id
250
+ ) AS isBaseline
251
+ FROM ${sql(_testRepositoryMigrations.tables.testRuns)} run
252
+ WHERE run.name = ${name}
253
+ AND ${variant === undefined ? sql`1 = 1` : sql`run.variant = ${variant}`}
254
+ AND ${before === undefined ? sql`1 = 1` : sql`run.id < ${before}`}
255
+ ORDER BY run.id DESC LIMIT ${parsedLimit};
256
+ `);
257
+ return runs.map(({
258
+ isBaseline,
259
+ ...run
260
+ }) => Object.assign(new _TestRun.TestRun(run), {
261
+ isBaseline: isBaseline === 1
262
+ }));
263
+ });
264
+ const listVariantBaselines = _effect.Effect.fnUntraced(function* (name) {
265
+ const variants = yield* decodeVariantBaselines(yield* sql`
266
+ SELECT variants.variant, run.id, run.name, run.state
267
+ FROM (
268
+ SELECT DISTINCT variant FROM ${sql(_testRepositoryMigrations.tables.testRuns)}
269
+ WHERE name = ${name}
270
+ ) variants
271
+ LEFT JOIN ${sql(_testRepositoryMigrations.tables.testBaselines)} baseline
272
+ ON baseline.name = ${name} AND baseline.variant = variants.variant
273
+ LEFT JOIN ${sql(_testRepositoryMigrations.tables.testRuns)} run ON run.id = baseline.testRun
274
+ ORDER BY variants.variant;
275
+ `);
276
+ return variants.map(({
277
+ variant,
278
+ id,
279
+ name,
280
+ state
281
+ }) => ({
282
+ variant,
283
+ baseline: id === null || name === null || state === null ? _effect.Option.none() : _effect.Option.some(new _TestRun.TestRun({
284
+ id,
285
+ name,
286
+ variant,
287
+ state
288
+ }))
289
+ }));
325
290
  });
326
- const service = {
327
- clearStale,
328
- clearUncommitedTestResults,
329
- commitCurrentTestRun,
330
- getAllTestResults,
331
- getAllTestRuns,
332
- getOrCreateCurrentTestRun,
333
- getLastTestRunHash,
334
- getPreviousTestRun,
291
+ return {
292
+ commitTestRun,
293
+ getTestRun,
294
+ getLatestTestRun,
295
+ getBaselineTestRun,
296
+ listTestRuns,
297
+ listVariantBaselines,
335
298
  getTestResultsStream,
336
- hasResults,
337
- insertTestResult,
338
- insertTestResults,
339
- getAllTestRunResults
299
+ withTestRun,
300
+ withStats: use => (0, _testResultStats.withStats)(use).pipe(_effect.Effect.provideService(_sql.SqlClient.SqlClient, sql)),
301
+ insertTestResult: (result, run, schemas) => insertTestResults([result], run, schemas),
302
+ insertTestResults
340
303
  };
341
- return service;
342
304
  });
343
305
  //# sourceMappingURL=Test.repository.sqlite.js.map