eyeprolog 1.3.62 → 1.3.63
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-regression.mjs +115 -33
package/package.json
CHANGED
package/test/run-regression.mjs
CHANGED
|
@@ -2221,22 +2221,16 @@ c4 ?- call((!;1)).
|
|
|
2221
2221
|
{
|
|
2222
2222
|
name: 'REPL term input is on demand in conjunctions and Ctrl-D does not exit the top level',
|
|
2223
2223
|
run: () => {
|
|
2224
|
-
if (
|
|
2225
|
-
const
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
`script -qefc ${shellQuote(command)} /dev/null`;
|
|
2235
|
-
const result = spawnSync('sh', ['-c', scriptCommand], {
|
|
2236
|
-
cwd: packageRoot,
|
|
2237
|
-
encoding: 'utf8',
|
|
2238
|
-
timeout: 5000,
|
|
2239
|
-
});
|
|
2224
|
+
if (!hasUtilLinuxScript()) return;
|
|
2225
|
+
const result = runScriptedRepl([
|
|
2226
|
+
{ waitFor: '?- ', send: 'read(X), read(Y).\n' },
|
|
2227
|
+
{ waitFor: ' |: ', send: 'foo.\n' },
|
|
2228
|
+
{ waitFor: '|: ', send: 'bar.\n' },
|
|
2229
|
+
{ waitFor: 'X = foo, Y = bar.', send: 'read(Z).\n' },
|
|
2230
|
+
{ waitFor: ' |: ', send: '\u0004' },
|
|
2231
|
+
{ waitFor: 'Z = end_of_file.', send: 'true.\n' },
|
|
2232
|
+
{ waitFor: ' true.', send: 'halt.\n' },
|
|
2233
|
+
]);
|
|
2240
2234
|
assertEqual(result.error?.code, undefined, 'interactive read timeout');
|
|
2241
2235
|
assertEqual(result.status, 0, 'exit status');
|
|
2242
2236
|
assertIncludes(result.stdout, 'X = foo, Y = bar.', 'conjunction reads');
|
|
@@ -2248,23 +2242,18 @@ c4 ?- call((!;1)).
|
|
|
2248
2242
|
{
|
|
2249
2243
|
name: 'REPL character input is on demand and Ctrl-D stays local (issue #55)',
|
|
2250
2244
|
run: () => {
|
|
2251
|
-
if (
|
|
2252
|
-
const
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
const result = spawnSync('sh', ['-c', scriptCommand], {
|
|
2264
|
-
cwd: packageRoot,
|
|
2265
|
-
encoding: 'utf8',
|
|
2266
|
-
timeout: 5000,
|
|
2267
|
-
});
|
|
2245
|
+
if (!hasUtilLinuxScript()) return;
|
|
2246
|
+
const result = runScriptedRepl([
|
|
2247
|
+
{ waitFor: '?- ', send: 'peek_char(P), get_char(C), get_code(K).\n' },
|
|
2248
|
+
{ waitFor: ' |: ', send: 'ab\n' },
|
|
2249
|
+
{ waitFor: 'P = a, C = a, K = 98.', send: 'get_char(N).\n' },
|
|
2250
|
+
{ waitFor: ' |: ', send: 'z\n' },
|
|
2251
|
+
{ waitFor: 'N = z.', send: 'get_char(E).\n' },
|
|
2252
|
+
{ waitFor: ' |: ', send: '\u0004' },
|
|
2253
|
+
{ waitFor: 'E = end_of_file.', send: 'get_char(D).\n' },
|
|
2254
|
+
{ waitFor: ' |: ', send: 'q\n' },
|
|
2255
|
+
{ waitFor: 'D = q.', send: 'halt.\n' },
|
|
2256
|
+
]);
|
|
2268
2257
|
assertEqual(result.error?.code, undefined, 'interactive character input timeout');
|
|
2269
2258
|
assertEqual(result.status, 0, 'exit status');
|
|
2270
2259
|
assertIncludes(result.stdout, 'P = a, C = a, K = 98.', 'peek/get char and code input');
|
|
@@ -6112,6 +6101,99 @@ function shellQuote(value) {
|
|
|
6112
6101
|
return `'${String(value).replaceAll("'", "'\"'\"'")}'`;
|
|
6113
6102
|
}
|
|
6114
6103
|
|
|
6104
|
+
let utilLinuxScriptAvailable = null;
|
|
6105
|
+
|
|
6106
|
+
function hasUtilLinuxScript() {
|
|
6107
|
+
if (process.platform === 'win32') return false;
|
|
6108
|
+
if (utilLinuxScriptAvailable == null) {
|
|
6109
|
+
const available = spawnSync('sh', ['-c',
|
|
6110
|
+
'command -v script >/dev/null 2>&1 && script --version 2>/dev/null | grep -qi util-linux']);
|
|
6111
|
+
utilLinuxScriptAvailable = available.status === 0;
|
|
6112
|
+
}
|
|
6113
|
+
return utilLinuxScriptAvailable;
|
|
6114
|
+
}
|
|
6115
|
+
|
|
6116
|
+
function runScriptedRepl(steps, { timeout = 5000 } = {}) {
|
|
6117
|
+
const command = `${shellQuote(process.execPath)} ${shellQuote(bin)}`;
|
|
6118
|
+
const payload = Buffer.from(JSON.stringify({
|
|
6119
|
+
command,
|
|
6120
|
+
cwd: packageRoot,
|
|
6121
|
+
steps,
|
|
6122
|
+
timeout,
|
|
6123
|
+
})).toString('base64');
|
|
6124
|
+
const helper = String.raw`
|
|
6125
|
+
const { spawn } = require('node:child_process');
|
|
6126
|
+
const config = JSON.parse(Buffer.from(process.env.EYEPROLOG_REPL_SCRIPT, 'base64').toString('utf8'));
|
|
6127
|
+
const child = spawn('script', ['-qefc', config.command, '/dev/null'], {
|
|
6128
|
+
cwd: config.cwd,
|
|
6129
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
6130
|
+
});
|
|
6131
|
+
let stdout = '';
|
|
6132
|
+
let stderr = '';
|
|
6133
|
+
let cursor = 0;
|
|
6134
|
+
let step = 0;
|
|
6135
|
+
let finished = false;
|
|
6136
|
+
|
|
6137
|
+
function advance() {
|
|
6138
|
+
while (step < config.steps.length) {
|
|
6139
|
+
const current = config.steps[step];
|
|
6140
|
+
const index = stdout.indexOf(current.waitFor, cursor);
|
|
6141
|
+
if (index === -1) return;
|
|
6142
|
+
cursor = index + current.waitFor.length;
|
|
6143
|
+
child.stdin.write(current.send);
|
|
6144
|
+
step++;
|
|
6145
|
+
}
|
|
6146
|
+
}
|
|
6147
|
+
|
|
6148
|
+
child.stdout.on('data', (chunk) => {
|
|
6149
|
+
stdout += chunk;
|
|
6150
|
+
advance();
|
|
6151
|
+
});
|
|
6152
|
+
child.stderr.on('data', (chunk) => {
|
|
6153
|
+
stderr += chunk;
|
|
6154
|
+
});
|
|
6155
|
+
|
|
6156
|
+
const timer = setTimeout(() => {
|
|
6157
|
+
if (finished) return;
|
|
6158
|
+
finished = true;
|
|
6159
|
+
child.kill('SIGKILL');
|
|
6160
|
+
process.stdout.write(JSON.stringify({ status: null, stdout, stderr, timedOut: true, step }));
|
|
6161
|
+
}, config.timeout);
|
|
6162
|
+
|
|
6163
|
+
child.on('close', (status, signal) => {
|
|
6164
|
+
if (finished) return;
|
|
6165
|
+
finished = true;
|
|
6166
|
+
clearTimeout(timer);
|
|
6167
|
+
process.stdout.write(JSON.stringify({ status, signal, stdout, stderr, timedOut: false, step }));
|
|
6168
|
+
});
|
|
6169
|
+
`;
|
|
6170
|
+
const helperResult = spawnSync(process.execPath, ['-e', helper], {
|
|
6171
|
+
cwd: packageRoot,
|
|
6172
|
+
encoding: 'utf8',
|
|
6173
|
+
env: { ...process.env, EYEPROLOG_REPL_SCRIPT: payload },
|
|
6174
|
+
timeout: timeout + 1000,
|
|
6175
|
+
});
|
|
6176
|
+
if (helperResult.error) return helperResult;
|
|
6177
|
+
if (helperResult.status !== 0) return helperResult;
|
|
6178
|
+
let result;
|
|
6179
|
+
try {
|
|
6180
|
+
result = JSON.parse(helperResult.stdout);
|
|
6181
|
+
} catch (error) {
|
|
6182
|
+
return {
|
|
6183
|
+
...helperResult,
|
|
6184
|
+
error: new Error(`interactive REPL helper returned invalid JSON: ${error.message}`),
|
|
6185
|
+
};
|
|
6186
|
+
}
|
|
6187
|
+
return {
|
|
6188
|
+
status: result.status,
|
|
6189
|
+
stdout: result.stdout,
|
|
6190
|
+
stderr: `${result.stderr ?? ''}${helperResult.stderr ?? ''}`,
|
|
6191
|
+
error: result.timedOut
|
|
6192
|
+
? Object.assign(new Error(`interactive REPL timed out after step ${result.step}`), { code: 'ETIMEDOUT' })
|
|
6193
|
+
: undefined,
|
|
6194
|
+
};
|
|
6195
|
+
}
|
|
6196
|
+
|
|
6115
6197
|
function runCli(args, options = {}) {
|
|
6116
6198
|
return spawnSync(process.execPath, [bin, ...args], {
|
|
6117
6199
|
cwd: packageRoot,
|