eyeprolog 1.3.63 → 1.3.65
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 +108 -3
- package/test/test-style.mjs +15 -0
package/package.json
CHANGED
package/test/run-all.mjs
CHANGED
package/test/run-examples.mjs
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
// Example-output test runner.
|
|
3
3
|
// It compares examples byte-for-byte against golden output so answer and proof changes cannot silently alter results.
|
|
4
4
|
import fs from 'node:fs';
|
|
5
|
+
import os from 'node:os';
|
|
5
6
|
import path from 'node:path';
|
|
6
7
|
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads';
|
|
7
9
|
import { Program, run } from '../src/index.js';
|
|
8
10
|
import { fileURLToPath } from 'node:url';
|
|
9
11
|
import { TestReporter, isMainModule, runStandalone } from './test-style.mjs';
|
|
@@ -79,13 +81,19 @@ export const proofExamples = [
|
|
|
79
81
|
'partial-evaluator.pl',
|
|
80
82
|
];
|
|
81
83
|
|
|
82
|
-
export function runExamples(reporter = new TestReporter()) {
|
|
84
|
+
export async function runExamples(reporter = new TestReporter()) {
|
|
83
85
|
const files = fs.readdirSync(examplesDir)
|
|
84
86
|
.filter((name) => exampleIsRunnable(name))
|
|
85
87
|
.sort();
|
|
86
88
|
|
|
87
89
|
reporter.section('Examples');
|
|
88
|
-
|
|
90
|
+
const deepTaxonomyIndex = files.indexOf('deep-taxonomy-100000.pl');
|
|
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
|
+
}
|
|
89
97
|
reporter.sectionTotal('examples');
|
|
90
98
|
|
|
91
99
|
reporter.section('Proof examples');
|
|
@@ -94,6 +102,101 @@ export function runExamples(reporter = new TestReporter()) {
|
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
|
|
105
|
+
async function runExampleTasks(tasks, onResult, maxWorkers) {
|
|
106
|
+
if (tasks.length === 0) return;
|
|
107
|
+
const parallelism = os.availableParallelism?.() ?? os.cpus().length;
|
|
108
|
+
const workerCount = Math.min(tasks.length, Math.max(1, Math.min(maxWorkers, parallelism - 1)));
|
|
109
|
+
if (workerCount === 1) {
|
|
110
|
+
for (const name of tasks) {
|
|
111
|
+
const startedAt = performance.now();
|
|
112
|
+
try {
|
|
113
|
+
runExample(name);
|
|
114
|
+
onResult(name, { ms: Math.round(performance.now() - startedAt) });
|
|
115
|
+
} catch (error) {
|
|
116
|
+
onResult(name, { ms: Math.round(performance.now() - startedAt), error });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const completedResults = new Map();
|
|
123
|
+
let nextTask = 0;
|
|
124
|
+
let nextReport = 0;
|
|
125
|
+
let completed = 0;
|
|
126
|
+
let settled = false;
|
|
127
|
+
|
|
128
|
+
await new Promise((resolve, reject) => {
|
|
129
|
+
const workers = Array.from({ length: workerCount }, () => new Worker(new URL(import.meta.url), {
|
|
130
|
+
workerData: { exampleWorker: true },
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
const stopWorkers = () => Promise.all(workers.map((worker) => worker.terminate()));
|
|
134
|
+
|
|
135
|
+
const fail = (error) => {
|
|
136
|
+
if (settled) return;
|
|
137
|
+
settled = true;
|
|
138
|
+
stopWorkers().finally(() => reject(error));
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const finish = () => {
|
|
142
|
+
if (settled || completed !== tasks.length || nextReport !== tasks.length) return;
|
|
143
|
+
settled = true;
|
|
144
|
+
stopWorkers().then(() => resolve(), reject);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const reportReady = () => {
|
|
148
|
+
try {
|
|
149
|
+
while (completedResults.has(nextReport)) {
|
|
150
|
+
const result = completedResults.get(nextReport);
|
|
151
|
+
completedResults.delete(nextReport);
|
|
152
|
+
onResult(tasks[nextReport], result);
|
|
153
|
+
nextReport++;
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
fail(error);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const assign = (worker) => {
|
|
161
|
+
if (nextTask >= tasks.length || settled) return;
|
|
162
|
+
worker.postMessage({ id: nextTask, name: tasks[nextTask++] });
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
for (const worker of workers) {
|
|
166
|
+
worker.on('message', ({ id, ms, error }) => {
|
|
167
|
+
if (settled) return;
|
|
168
|
+
completedResults.set(id, {
|
|
169
|
+
ms,
|
|
170
|
+
error: error == null ? null : Object.assign(new Error(error.message), { stack: error.stack }),
|
|
171
|
+
});
|
|
172
|
+
completed++;
|
|
173
|
+
reportReady();
|
|
174
|
+
assign(worker);
|
|
175
|
+
finish();
|
|
176
|
+
});
|
|
177
|
+
worker.on('error', fail);
|
|
178
|
+
assign(worker);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function runExampleWorker() {
|
|
184
|
+
parentPort.on('message', ({ id, name }) => {
|
|
185
|
+
const startedAt = performance.now();
|
|
186
|
+
try {
|
|
187
|
+
runExample(name);
|
|
188
|
+
parentPort.postMessage({ id, ms: Math.round(performance.now() - startedAt), error: null });
|
|
189
|
+
} catch (error) {
|
|
190
|
+
parentPort.postMessage({
|
|
191
|
+
id,
|
|
192
|
+
ms: Math.round(performance.now() - startedAt),
|
|
193
|
+
error: { message: error?.message ?? String(error), stack: error?.stack ?? String(error) },
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
97
200
|
function exampleIsRunnable(name) {
|
|
98
201
|
return name.endsWith('.pl');
|
|
99
202
|
}
|
|
@@ -155,6 +258,8 @@ function diffText(expected, actualText) {
|
|
|
155
258
|
return 'outputs differ';
|
|
156
259
|
}
|
|
157
260
|
|
|
158
|
-
if (
|
|
261
|
+
if (!isMainThread && workerData?.exampleWorker) {
|
|
262
|
+
runExampleWorker();
|
|
263
|
+
} else if (isMainModule(import.meta.url)) {
|
|
159
264
|
await runStandalone(runExamples);
|
|
160
265
|
}
|
package/test/test-style.mjs
CHANGED
|
@@ -87,6 +87,21 @@ export class TestReporter {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
testResult(name, { ms = 0, error = null } = {}) {
|
|
91
|
+
this.total++;
|
|
92
|
+
const nr = String(this.total).padStart(3, '0');
|
|
93
|
+
|
|
94
|
+
if (error == null) {
|
|
95
|
+
this.ok++;
|
|
96
|
+
this.stdout.write(`${colors.green}OK${colors.reset} ${nr} ${name} ${colors.dim}(${ms} ms)${colors.reset}\n`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this.stderr.write(`${colors.red}FAIL${colors.reset} ${nr} ${name} ${colors.dim}(${ms} ms)${colors.reset}\n`);
|
|
101
|
+
this.stderr.write(`${error?.stack ?? String(error)}\n`);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
|
|
90
105
|
totalLine() {
|
|
91
106
|
const ms = nowMs() - this.startedAt;
|
|
92
107
|
this.stdout.write(`\n${colors.yellow}== Total${colors.reset}\n`);
|