eyeprolog 1.3.88 → 1.3.89
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/package.json +1 -1
- package/test/run-all.mjs +1 -1
- package/test/run-examples.mjs +1 -7
- package/test/run-regression.mjs +161 -5
- package/test/test-style.mjs +2 -2
package/package.json
CHANGED
package/test/run-all.mjs
CHANGED
|
@@ -21,7 +21,7 @@ await runStandalone(async (reporter) => {
|
|
|
21
21
|
runOpenRuleBenchChecks(reporter);
|
|
22
22
|
runArchitecture(reporter);
|
|
23
23
|
runCleanup(reporter);
|
|
24
|
-
runRegression(reporter);
|
|
24
|
+
await runRegression(reporter);
|
|
25
25
|
await runPlayground(reporter);
|
|
26
26
|
await runExamples(reporter);
|
|
27
27
|
runBookExamples(reporter);
|
package/test/run-examples.mjs
CHANGED
|
@@ -87,13 +87,7 @@ export async function runExamples(reporter = new TestReporter()) {
|
|
|
87
87
|
.sort();
|
|
88
88
|
|
|
89
89
|
reporter.section('Examples');
|
|
90
|
-
|
|
91
|
-
if (deepTaxonomyIndex >= 0) {
|
|
92
|
-
await runExampleTasks(files.slice(0, deepTaxonomyIndex + 1), (name, result) => reporter.testResult(name, result), 2);
|
|
93
|
-
await runExampleTasks(files.slice(deepTaxonomyIndex + 1), (name, result) => reporter.testResult(name, result), 3);
|
|
94
|
-
} else {
|
|
95
|
-
await runExampleTasks(files, (name, result) => reporter.testResult(name, result), 3);
|
|
96
|
-
}
|
|
90
|
+
await runExampleTasks(files, (name, result) => reporter.testResult(name, result), 3);
|
|
97
91
|
reporter.sectionTotal('examples');
|
|
98
92
|
|
|
99
93
|
reporter.section('Proof examples');
|
package/test/run-regression.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import fs from 'node:fs';
|
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { Worker, parentPort, workerData } from 'node:worker_threads';
|
|
10
11
|
import { fileURLToPath } from 'node:url';
|
|
11
12
|
import publicDefaultApi from '../index.js';
|
|
12
13
|
import * as publicApi from '../src/index.js';
|
|
@@ -84,13 +85,15 @@ function sourceAtom(value) {
|
|
|
84
85
|
return `'${String(value).replaceAll('\\', '\\\\').replaceAll("'", "''")}'`;
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
const sections = [
|
|
88
|
+
const regressionSections = [
|
|
89
89
|
{ key: 'regression', name: 'Regression', cases: regressionCases },
|
|
90
90
|
{ key: 'docs', name: 'Documentation sync', cases: documentationSyncCases },
|
|
91
91
|
{ key: 'api', name: 'API', cases: apiCases },
|
|
92
92
|
{ key: 'white-box', name: 'White-box', cases: whiteBoxCases },
|
|
93
|
-
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
export async function runRegression(reporter = new TestReporter(), requestedSection = null) {
|
|
96
|
+
const sections = regressionSections;
|
|
94
97
|
const selected = requestedSection == null
|
|
95
98
|
? sections
|
|
96
99
|
: sections.filter((section) => section.key === requestedSection);
|
|
@@ -98,16 +101,167 @@ export function runRegression(reporter = new TestReporter(), requestedSection =
|
|
|
98
101
|
throw new Error(`unknown regression section: ${requestedSection}; expected ${sections.map(({ key }) => key).join(', ')}`);
|
|
99
102
|
}
|
|
100
103
|
|
|
104
|
+
const prepared = selected.map((section) => ({ ...section, tests: section.cases() }));
|
|
105
|
+
const testCount = prepared.reduce((sum, section) => sum + section.tests.length, 0);
|
|
106
|
+
const parallelism = os.availableParallelism?.() ?? os.cpus().length;
|
|
107
|
+
// Several cases start bounded-heap child processes, so leave CPU and memory
|
|
108
|
+
// headroom instead of matching the host's full logical-core count.
|
|
109
|
+
const workerCount = Math.min(testCount, Math.max(1, Math.min(4, parallelism - 1)));
|
|
110
|
+
if (workerCount === 1) {
|
|
111
|
+
withRegressionTemp(() => {
|
|
112
|
+
for (const section of prepared) runSection(reporter, section.name, section.tests);
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const results = new Map();
|
|
118
|
+
let reportSection = 0;
|
|
119
|
+
let reportTest = 0;
|
|
120
|
+
reporter.section(prepared[0].name);
|
|
121
|
+
const reportReady = () => {
|
|
122
|
+
while (reportSection < prepared.length) {
|
|
123
|
+
const section = prepared[reportSection];
|
|
124
|
+
const result = results.get(testKey(section.key, reportTest));
|
|
125
|
+
if (result == null) return;
|
|
126
|
+
reporter.testResult(section.tests[reportTest].name, result);
|
|
127
|
+
reportTest++;
|
|
128
|
+
if (reportTest !== section.tests.length) continue;
|
|
129
|
+
reporter.sectionTotal(sectionLabel(section.name), sectionElapsedMs(section, results));
|
|
130
|
+
reportSection++;
|
|
131
|
+
reportTest = 0;
|
|
132
|
+
if (reportSection < prepared.length) reporter.section(prepared[reportSection].name);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
await runRegressionWorkers(prepared, workerCount, results, reportReady);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function withRegressionTemp(runTests) {
|
|
101
139
|
tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'eyeprolog-regression.'));
|
|
102
140
|
tmpCounter = 0;
|
|
103
141
|
try {
|
|
104
|
-
|
|
142
|
+
return runTests();
|
|
105
143
|
} finally {
|
|
106
144
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
107
145
|
tmp = null;
|
|
108
146
|
}
|
|
109
147
|
}
|
|
110
148
|
|
|
149
|
+
async function runRegressionWorkers(sections, workerCount, results, onResult) {
|
|
150
|
+
const tasks = sections.flatMap((section) => section.tests.map((_, index) => ({
|
|
151
|
+
sectionKey: section.key,
|
|
152
|
+
index,
|
|
153
|
+
})));
|
|
154
|
+
let nextTask = 0;
|
|
155
|
+
let completed = 0;
|
|
156
|
+
let settled = false;
|
|
157
|
+
|
|
158
|
+
return new Promise((resolve, reject) => {
|
|
159
|
+
const workers = Array.from({ length: workerCount }, () => new Worker(new URL(import.meta.url), {
|
|
160
|
+
workerData: { regressionWorker: true },
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
const closeWorkers = () => {
|
|
164
|
+
for (const worker of workers) worker.postMessage({ close: true });
|
|
165
|
+
};
|
|
166
|
+
const fail = (error) => {
|
|
167
|
+
if (settled) return;
|
|
168
|
+
settled = true;
|
|
169
|
+
Promise.all(workers.map((worker) => worker.terminate())).finally(() => reject(error));
|
|
170
|
+
};
|
|
171
|
+
const assign = (worker) => {
|
|
172
|
+
if (settled) return;
|
|
173
|
+
const task = tasks[nextTask++];
|
|
174
|
+
if (task != null) worker.postMessage(task);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
for (const worker of workers) {
|
|
178
|
+
worker.on('message', (message) => {
|
|
179
|
+
if (settled) return;
|
|
180
|
+
if (message.ready) {
|
|
181
|
+
assign(worker);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
results.set(testKey(message.sectionKey, message.index), {
|
|
185
|
+
ms: message.ms,
|
|
186
|
+
error: deserializeWorkerError(message.error),
|
|
187
|
+
startedAt: message.startedAt,
|
|
188
|
+
finishedAt: message.finishedAt,
|
|
189
|
+
});
|
|
190
|
+
try {
|
|
191
|
+
onResult();
|
|
192
|
+
} catch (error) {
|
|
193
|
+
fail(error);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
completed++;
|
|
197
|
+
if (completed === tasks.length) {
|
|
198
|
+
settled = true;
|
|
199
|
+
const exits = Promise.all(workers.map((candidate) => new Promise((done, failed) => {
|
|
200
|
+
candidate.once('exit', done);
|
|
201
|
+
candidate.once('error', failed);
|
|
202
|
+
})));
|
|
203
|
+
closeWorkers();
|
|
204
|
+
exits.then(() => resolve(results), reject);
|
|
205
|
+
} else {
|
|
206
|
+
assign(worker);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
worker.on('error', fail);
|
|
210
|
+
worker.on('exit', (code) => {
|
|
211
|
+
if (!settled) fail(new Error(`regression worker exited unexpectedly with status ${code}`));
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function testKey(sectionKey, index) {
|
|
218
|
+
return `${sectionKey}:${index}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function sectionElapsedMs(section, results) {
|
|
222
|
+
const timings = section.tests.map((_, index) => results.get(testKey(section.key, index)));
|
|
223
|
+
return Math.max(...timings.map(({ finishedAt }) => finishedAt)) -
|
|
224
|
+
Math.min(...timings.map(({ startedAt }) => startedAt));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function deserializeWorkerError(error) {
|
|
228
|
+
if (error == null) return null;
|
|
229
|
+
return Object.assign(new Error(error.message), { stack: error.stack });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function startRegressionWorker() {
|
|
233
|
+
tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'eyeprolog-regression-worker.'));
|
|
234
|
+
tmpCounter = 0;
|
|
235
|
+
const casesBySection = new Map(regressionSections.map((section) => [section.key, section.cases()]));
|
|
236
|
+
parentPort.on('message', ({ sectionKey, index, close }) => {
|
|
237
|
+
if (close) {
|
|
238
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
239
|
+
tmp = null;
|
|
240
|
+
parentPort.close();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const testCase = casesBySection.get(sectionKey)?.[index];
|
|
244
|
+
const startedAt = Date.now();
|
|
245
|
+
const started = performance.now();
|
|
246
|
+
let error = null;
|
|
247
|
+
try {
|
|
248
|
+
if (testCase == null) throw new Error(`unknown regression test ${sectionKey}:${index}`);
|
|
249
|
+
testCase.run();
|
|
250
|
+
} catch (caught) {
|
|
251
|
+
error = { message: caught?.message ?? String(caught), stack: caught?.stack ?? String(caught) };
|
|
252
|
+
}
|
|
253
|
+
parentPort.postMessage({
|
|
254
|
+
sectionKey,
|
|
255
|
+
index,
|
|
256
|
+
ms: Math.round(performance.now() - started),
|
|
257
|
+
startedAt,
|
|
258
|
+
finishedAt: Date.now(),
|
|
259
|
+
error,
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
parentPort.postMessage({ ready: true });
|
|
263
|
+
}
|
|
264
|
+
|
|
111
265
|
function regressionCases() {
|
|
112
266
|
return [
|
|
113
267
|
{
|
|
@@ -7185,6 +7339,8 @@ function format(value) {
|
|
|
7185
7339
|
return typeof value === 'string' ? JSON.stringify(value) : String(value);
|
|
7186
7340
|
}
|
|
7187
7341
|
|
|
7188
|
-
if (
|
|
7342
|
+
if (workerData?.regressionWorker) {
|
|
7343
|
+
startRegressionWorker();
|
|
7344
|
+
} else if (isMainModule(import.meta.url)) {
|
|
7189
7345
|
await runStandalone((reporter) => runRegression(reporter, process.argv[2] ?? null));
|
|
7190
7346
|
}
|
package/test/test-style.mjs
CHANGED
|
@@ -41,12 +41,12 @@ export class TestReporter {
|
|
|
41
41
|
this.sectionCount++;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
sectionTotal(label = null) {
|
|
44
|
+
sectionTotal(label = null, elapsedMs = null) {
|
|
45
45
|
if (this.currentSection == null) return;
|
|
46
46
|
|
|
47
47
|
const ok = this.ok - this.currentSection.okAtStart;
|
|
48
48
|
const total = this.total - this.currentSection.totalAtStart;
|
|
49
|
-
const ms = nowMs() - this.currentSection.startedAt;
|
|
49
|
+
const ms = elapsedMs ?? nowMs() - this.currentSection.startedAt;
|
|
50
50
|
const suite = label ?? defaultSectionLabel(this.currentSection.name);
|
|
51
51
|
this.stdout.write(`${colors.green}OK${colors.reset} ${ok}/${total} ${suite} tests passed ${colors.dim}(${ms} ms)${colors.reset}\n`);
|
|
52
52
|
}
|