@kernlang/test 3.4.2 → 3.4.3-canary.9.1.62aecb74
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.
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safety-checks.js","sourceRoot":"","sources":["../../src/generated/safety-checks.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,MAAM,CAAC,MAAM,eAAe,GAAW,4BAA4B,CAAC;AAEpE,gCAAgC;AAChC,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,OAAO,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACrC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { decompile, generateCoreNode, importTypeScript, parseDocumentWithDiagnostics, validateSchema, validateSemantics, } from '@kernlang/core';
|
|
1
|
+
import { decompile, emitNativeKernBodyTS, generateCoreNode, importTypeScript, parseDocumentWithDiagnostics, validateSchema, validateSemantics, } from '@kernlang/core';
|
|
2
2
|
import { execFileSync } from 'child_process';
|
|
3
3
|
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
4
4
|
import { dirname, join, relative, resolve } from 'path';
|
|
5
5
|
import { inspect, isDeepStrictEqual } from 'util';
|
|
6
6
|
import { createContext, Script } from 'vm';
|
|
7
|
+
import { isRuntimeBindingName } from './generated/safety-checks.js';
|
|
7
8
|
const DISCOVERY_SKIP_DIRS = new Set([
|
|
8
9
|
'.git',
|
|
9
10
|
'.next',
|
|
@@ -426,6 +427,39 @@ function handlerText(node) {
|
|
|
426
427
|
.filter(Boolean)
|
|
427
428
|
.join('\n');
|
|
428
429
|
}
|
|
430
|
+
// `handlerText` returns the verbatim `props.code` from each handler child,
|
|
431
|
+
// which is what most callers (reach analysis, length checks, regex sniffing)
|
|
432
|
+
// want. The runtime-eval path is different: a handler with `lang="kern"`
|
|
433
|
+
// stores raw KERN source in `props.code`, not JS. Feeding that to V8 yields
|
|
434
|
+
// ReferenceErrors when tests reference symbols whose body is kern-native.
|
|
435
|
+
// `runtimeHandlerSource` lowers each kern handler through the same emitter
|
|
436
|
+
// the codegen path uses (`emitNativeKernBodyTS`), giving the runner a
|
|
437
|
+
// JS body it can wrap and execute.
|
|
438
|
+
//
|
|
439
|
+
// On lowering failure we warn and inject a `throw new Error(...)` body so
|
|
440
|
+
// the binding compiles cleanly but blows up at invocation with the actual
|
|
441
|
+
// emitter error — instead of degrading to an empty body that surfaces as a
|
|
442
|
+
// misleading downstream ReferenceError.
|
|
443
|
+
function runtimeHandlerSource(node) {
|
|
444
|
+
return getChildren(node, 'handler')
|
|
445
|
+
.map((handler) => {
|
|
446
|
+
if (str(getProps(handler).lang) === 'kern') {
|
|
447
|
+
try {
|
|
448
|
+
return emitNativeKernBodyTS(handler);
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
452
|
+
const ownerName = str(getProps(node).name) || node.type;
|
|
453
|
+
console.warn(`[kern-test] kern handler lowering failed for '${ownerName}': ${message}`);
|
|
454
|
+
const literal = JSON.stringify(`kern handler lowering failed for '${ownerName}': ${message}`);
|
|
455
|
+
return `throw new Error(${literal});`;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return str(getProps(handler).code);
|
|
459
|
+
})
|
|
460
|
+
.filter(Boolean)
|
|
461
|
+
.join('\n');
|
|
462
|
+
}
|
|
429
463
|
function collectNamedHandlerBodies(root) {
|
|
430
464
|
const bodies = new Map();
|
|
431
465
|
for (const fn of collectNodes(root, 'fn')) {
|
|
@@ -2069,8 +2103,35 @@ function evaluateImportAssertion(node, context) {
|
|
|
2069
2103
|
}
|
|
2070
2104
|
return { passed: true };
|
|
2071
2105
|
}
|
|
2072
|
-
|
|
2073
|
-
const
|
|
2106
|
+
function readPositiveIntEnv(name, fallback) {
|
|
2107
|
+
const raw = process.env[name];
|
|
2108
|
+
if (raw === undefined || raw === '')
|
|
2109
|
+
return fallback;
|
|
2110
|
+
const parsed = Number.parseInt(raw, 10);
|
|
2111
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
2112
|
+
return fallback;
|
|
2113
|
+
return parsed;
|
|
2114
|
+
}
|
|
2115
|
+
// V8 enforces wall-clock time on `script.runInContext`. Under CPU contention
|
|
2116
|
+
// cold-compile + JIT can blow tight budgets — a 100ms default produced flaky
|
|
2117
|
+
// non-deterministic failures across runs. 1s is the new floor; override via
|
|
2118
|
+
// `KERN_TEST_RUNTIME_TIMEOUT_MS` for slow CI or expensive evals.
|
|
2119
|
+
const RUNTIME_EXPR_TIMEOUT_MS = readPositiveIntEnv('KERN_TEST_RUNTIME_TIMEOUT_MS', 1000);
|
|
2120
|
+
// Outer process timeout for the async eval path — must comfortably exceed
|
|
2121
|
+
// `RUNTIME_EXPR_TIMEOUT_MS` plus Node spawn cost (~200-400ms on busy machines).
|
|
2122
|
+
// We enforce a 2s safety margin even when the user overrides the env var: a
|
|
2123
|
+
// shorter outer timeout would kill the worker before V8 can report a real
|
|
2124
|
+
// inner timeout, surfacing as an opaque "process timed out" instead of the
|
|
2125
|
+
// actionable script-execution-timed-out error.
|
|
2126
|
+
const RUNTIME_ASYNC_PROCESS_FLOOR_MS = Math.max(5000, RUNTIME_EXPR_TIMEOUT_MS + 2000);
|
|
2127
|
+
const RUNTIME_ASYNC_PROCESS_TIMEOUT_MS = (() => {
|
|
2128
|
+
const requested = readPositiveIntEnv('KERN_TEST_ASYNC_PROCESS_TIMEOUT_MS', RUNTIME_ASYNC_PROCESS_FLOOR_MS);
|
|
2129
|
+
if (requested < RUNTIME_ASYNC_PROCESS_FLOOR_MS) {
|
|
2130
|
+
console.warn(`[kern-test] KERN_TEST_ASYNC_PROCESS_TIMEOUT_MS=${requested} is below the required floor (${RUNTIME_ASYNC_PROCESS_FLOOR_MS}ms = max(5000, RUNTIME_EXPR_TIMEOUT_MS+2000)); using the floor instead.`);
|
|
2131
|
+
return RUNTIME_ASYNC_PROCESS_FLOOR_MS;
|
|
2132
|
+
}
|
|
2133
|
+
return requested;
|
|
2134
|
+
})();
|
|
2074
2135
|
const RUNTIME_EXPR_UNSAFE_TOKEN = /\b(?:async|class|constructor|Date|delete|do|eval|fetch|for|Function|global|globalThis|import|process|prototype|require|setInterval|setTimeout|switch|this|throw|try|while|with|WebSocket|XMLHttpRequest|__proto__)\b/;
|
|
2075
2136
|
const RUNTIME_FN_UNSAFE_TOKEN = /\b(?:class|constructor|Date|delete|do|eval|fetch|Function|global|globalThis|import|process|prototype|require|setInterval|setTimeout|switch|this|with|WebSocket|XMLHttpRequest|__proto__)\b/;
|
|
2076
2137
|
const RUNTIME_CLASS_UNSAFE_TOKEN = /\b(?:Date|delete|do|eval|fetch|Function|global|globalThis|import|process|prototype|require|setInterval|setTimeout|switch|with|WebSocket|XMLHttpRequest|__proto__)\b/;
|
|
@@ -2122,9 +2183,9 @@ function unsafeRuntimeWorkflowReason(source) {
|
|
|
2122
2183
|
return `unsupported token '${unsafeToken}'`;
|
|
2123
2184
|
return undefined;
|
|
2124
2185
|
}
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2186
|
+
// First slice of self-hosting: `isRuntimeBindingName` lives in
|
|
2187
|
+
// `src/kern/safety-checks.kern`. Edit the .kern file, then run
|
|
2188
|
+
// `pnpm --filter @kernlang/test kern:compile` to regenerate the facade.
|
|
2128
2189
|
function transformRuntimeCodeSegments(source, transform) {
|
|
2129
2190
|
let output = '';
|
|
2130
2191
|
let segmentStart = 0;
|
|
@@ -2187,7 +2248,7 @@ function runtimeJsSource(source) {
|
|
|
2187
2248
|
.replace(/\bfunction\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)\s*:\s*[^{]+{/g, 'function $1($2) {'));
|
|
2188
2249
|
}
|
|
2189
2250
|
function runtimeConstHandlerExpr(node) {
|
|
2190
|
-
const code = runtimeJsSource(
|
|
2251
|
+
const code = runtimeJsSource(runtimeHandlerSource(node).trim());
|
|
2191
2252
|
if (!code)
|
|
2192
2253
|
return '';
|
|
2193
2254
|
if (/^\s*(?:return|const|let|var|if|for|while|try|throw)\b/.test(code) || /;\s*$/.test(code)) {
|
|
@@ -2238,7 +2299,7 @@ function runtimeParamNames(node) {
|
|
|
2238
2299
|
return parseLegacyParamNames(str(getProps(node).params));
|
|
2239
2300
|
}
|
|
2240
2301
|
function runtimeFunctionExpr(node) {
|
|
2241
|
-
const code = runtimeJsSource(
|
|
2302
|
+
const code = runtimeJsSource(runtimeHandlerSource(node));
|
|
2242
2303
|
if (!code)
|
|
2243
2304
|
return '';
|
|
2244
2305
|
const params = runtimeParamNames(node);
|
|
@@ -2249,7 +2310,7 @@ function runtimeFunctionExpr(node) {
|
|
|
2249
2310
|
}
|
|
2250
2311
|
function runtimeHandlerLines(node, spaces = 4) {
|
|
2251
2312
|
const prefix = ' '.repeat(spaces);
|
|
2252
|
-
const code = runtimeJsSource(
|
|
2313
|
+
const code = runtimeJsSource(runtimeHandlerSource(node).trim());
|
|
2253
2314
|
if (!code)
|
|
2254
2315
|
return [];
|
|
2255
2316
|
return code.split('\n').map((line) => `${prefix}${line}`);
|