HS E36: RPC timeout tests (10, 11, 14) — 16/16 complete
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 16s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 16s
All 16 socket tests now green. Fake synchronous setTimeout queue (__hsFlushTimers) lets the synchronous test harness drive RPC timeout tests without real async waiting: - default timeout: flush timers → wrapper.pending emptied (rejected) - noTimeout: flush timers → wrapper.pending still has entry (not rejected) - timeout(n): flush timers → 50ms timer fires → pending emptied _rpcDispatch handles "noTimeout"/"timeout" method names, returning new proxy or timeout-factory function respectively. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,28 @@ const K = globalThis.SxKernel;
|
||||
// awaits RPC promises; rejections from timed-out or unresolved calls are expected.
|
||||
process.on('unhandledRejection', () => {});
|
||||
|
||||
// ─── Fake timer (for RPC timeout tests) ────────────────────────────────────
|
||||
// socket timeout tests need setTimeout to fire synchronously on demand.
|
||||
// Replace global setTimeout with a queue; __hsFlushTimers fires all pending.
|
||||
let _fakeTimers = [];
|
||||
let _fakeTimerIdCtr = 0;
|
||||
const _realSetTimeout = globalThis.setTimeout;
|
||||
globalThis.setTimeout = function(cb, _delay) {
|
||||
const id = ++_fakeTimerIdCtr;
|
||||
_fakeTimers.push({ id, cb });
|
||||
return id;
|
||||
};
|
||||
globalThis.clearTimeout = function(id) {
|
||||
const idx = _fakeTimers.findIndex(t => t.id === id);
|
||||
if (idx >= 0) _fakeTimers.splice(idx, 1);
|
||||
};
|
||||
// __hsFlushTimers — drain all pending timers synchronously.
|
||||
// Exposed as a plain object so host-call o "call" works.
|
||||
globalThis.__hsFlushTimers = { call: function() {
|
||||
const batch = _fakeTimers.splice(0);
|
||||
for (const { cb } of batch) { try { cb(); } catch (_) {} }
|
||||
}};
|
||||
|
||||
// Step limit API — exposed from OCaml kernel
|
||||
const STEP_LIMIT = parseInt(process.env.HS_STEP_LIMIT || '200000');
|
||||
|
||||
@@ -760,6 +782,7 @@ for(let i=startTest;i<Math.min(endTest,testCount);i++){
|
||||
globalThis.__hsMutationRegistry.length = 0;
|
||||
globalThis.__hsMutationActive = false;
|
||||
globalThis.__currentHsTestName = name;
|
||||
_fakeTimers = []; // reset timer queue between tests
|
||||
|
||||
// Enable step limit for timeout protection
|
||||
setStepLimit(STEP_LIMIT);
|
||||
|
||||
@@ -2096,6 +2096,65 @@ def generate_eval_only_test(test, idx):
|
||||
f' (assert= (host-get (host-global "__hs_ws_created") "_len") 2)))))'
|
||||
)
|
||||
|
||||
# Test 10: rpc proxy default timeout rejects the promise.
|
||||
# With a socket created using `with timeout 50`, calling rpc.neverReplies()
|
||||
# enqueues a fake setTimeout. After flushing timers, wrapper.pending should
|
||||
# be empty (the timeout callback deleted the entry and rejected the promise).
|
||||
if test['name'] == 'rpc proxy default timeout rejects the promise':
|
||||
return (
|
||||
f' (deftest "{safe_name}"\n'
|
||||
f' (hs-cleanup!)\n'
|
||||
f' (eval-hs "socket DefTOSocket ws://localhost/ws with timeout 50 end")\n'
|
||||
f' (let ((wrapper (host-get (host-global "window") "DefTOSocket")))\n'
|
||||
f' (let ((rpc (host-get wrapper "rpc")))\n'
|
||||
f' (do\n'
|
||||
f' (host-call rpc "neverReplies")\n'
|
||||
f' (let ((keys-before (host-call (host-global "Object") "keys" (host-get wrapper "pending"))))\n'
|
||||
f' (assert= (host-get keys-before "length") 1))\n'
|
||||
f' (host-call (host-global "__hsFlushTimers") "call")\n'
|
||||
f' (let ((keys-after (host-call (host-global "Object") "keys" (host-get wrapper "pending"))))\n'
|
||||
f' (assert= (host-get keys-after "length") 0))))))'
|
||||
)
|
||||
|
||||
# Test 11: rpc proxy noTimeout avoids timeout rejection.
|
||||
# rpc.noTimeout returns a proxy with timeout=Infinity; no setTimeout is
|
||||
# registered so flushing timers leaves the pending entry intact.
|
||||
if test['name'] == 'rpc proxy noTimeout avoids timeout rejection':
|
||||
return (
|
||||
f' (deftest "{safe_name}"\n'
|
||||
f' (hs-cleanup!)\n'
|
||||
f' (eval-hs "socket NoTOSocket ws://localhost/ws with timeout 20 end")\n'
|
||||
f' (let ((wrapper (host-get (host-global "window") "NoTOSocket")))\n'
|
||||
f' (let ((rpc (host-get wrapper "rpc")))\n'
|
||||
f' (do\n'
|
||||
f' (let ((no-timeout (host-call rpc "noTimeout")))\n'
|
||||
f' (host-call no-timeout "slowCall" "x"))\n'
|
||||
f' (host-call (host-global "__hsFlushTimers") "call")\n'
|
||||
f' (let ((keys-after (host-call (host-global "Object") "keys" (host-get wrapper "pending"))))\n'
|
||||
f' (assert= (host-get keys-after "length") 1))))))'
|
||||
)
|
||||
|
||||
# Test 14: rpc proxy timeout(n) rejects after a custom window.
|
||||
# rpc.timeout(50) returns a proxy with overrideTimeout=50; calling a method
|
||||
# on it enqueues a 50ms fake timer. After flushing, pending is empty.
|
||||
if test['name'] == 'rpc proxy timeout(n) rejects after a custom window':
|
||||
return (
|
||||
f' (deftest "{safe_name}"\n'
|
||||
f' (hs-cleanup!)\n'
|
||||
f' (eval-hs "socket CustomTOSocket ws://localhost/ws with timeout 60000 end")\n'
|
||||
f' (let ((wrapper (host-get (host-global "window") "CustomTOSocket")))\n'
|
||||
f' (let ((rpc (host-get wrapper "rpc")))\n'
|
||||
f' (do\n'
|
||||
f' (let ((timeout-fn (host-call rpc "timeout"))\n'
|
||||
f' (custom-proxy (host-call-fn timeout-fn (list 50))))\n'
|
||||
f' (host-call custom-proxy "willTimeOut"))\n'
|
||||
f' (let ((keys-before (host-call (host-global "Object") "keys" (host-get wrapper "pending"))))\n'
|
||||
f' (assert= (host-get keys-before "length") 1))\n'
|
||||
f' (host-call (host-global "__hsFlushTimers") "call")\n'
|
||||
f' (let ((keys-after (host-call (host-global "Object") "keys" (host-get wrapper "pending"))))\n'
|
||||
f' (assert= (host-get keys-after "length") 0))))))'
|
||||
)
|
||||
|
||||
# Special case: cluster-29 init events. The two tractable tests both attach
|
||||
# listeners to a wa container, set its innerHTML to a hyperscript fragment,
|
||||
# then call `_hyperscript.processNode(wa)`. Hand-roll deftests using
|
||||
|
||||
Reference in New Issue
Block a user