소스 검색

test: drop two host-paced assumptions the hosted image exposes

The ACP plugin-config case drove a real dispose with 150ms graces. On the hosted
image the SIGKILL escalation landed while the managed scope could not take the
signal — systemctl failed the kill ("Failed to send signal SIGKILL to auxiliary
processes: Invalid argument") — so the run reported a teardown failure the
configuration never asked for. The case asserts that config graces reach the
real run, so the values only need to clear the host's scope handling. Its mock
refuses stdin EOF and SIGTERM by design, so the case waits out both graces and
carries its own budget for that fixed cost.

Both illegal-UTF-8 residual cases relied on a plain sched_yield leaving each
write as its own read chunk. A starved reader coalesces hundreds of bytes into
one chunk, which inflates the measured peak for correct accounting too: the
hosted image measured 2563 against the 2048 bound. Pace with a real sleep so
each byte is its own chunk everywhere, and keep each payload above the bound
(3200 bytes; 1100 CESU-8 sequences) so a raw-byte undercount still flushes past
it instead of only at EOF.
Chinesezjc 3 일 전
부모
커밋
f45ca2d9ac
2개의 변경된 파일36개의 추가작업 그리고 13개의 파일을 삭제
  1. 21 8
      packages/experimental/code-runtime-python/tests/runtime.spec.ts
  2. 15 5
      packages/subagent/subagent-acp/tests/subagent-acp.spec.ts

+ 21 - 8
packages/experimental/code-runtime-python/tests/runtime.spec.ts

@@ -1517,10 +1517,14 @@ describe('PythonCodeRuntime — programs and bindings', () => {
       const { runtime } = await setup({ maxLogBytes: 3072, maxWallMs: 30_000 })
       result = await runtime.run({
         program: [
-          'import os',
-          'for _ in range(6000):',
+          'import os, time',
+          // One byte per chunk on every host: a plain yield lets a loaded
+          // reader coalesce, and the coalesced chunk is what the bound below
+          // measures. The payload stays above the 2048 discriminator, so a
+          // raw-byte undercount still flushes the whole residual at EOF.
+          'for _ in range(3200):',
           '    os.write(1, b"\\xff")',
-          '    os.sched_yield()',
+          '    time.sleep(0.001)',
           'return None',
         ].join('\n'),
         bindings: [],
@@ -1534,7 +1538,10 @@ describe('PythonCodeRuntime — programs and bindings', () => {
     // merged buffer stays well under 2048. A raw-byte undercount would let it
     // reach ~3072 before flushing, so 2048 discriminates.
     expect(maxConcat).toBeLessThan(2048)
-  })
+    // The paced payload costs ~3.2s deterministically, which is above the
+    // 5000ms default the local unit entry grants, so the case carries its own
+    // bound instead of relying on the lane to widen it.
+  }, 20_000)
 
   it('charges a structurally-valid but illegal UTF-8 sequence its U+FFFD-decoded cost', async () => {
     // A CESU-8 lone surrogate `ED A0 80` is structurally well-formed (a 3-byte
@@ -1559,12 +1566,15 @@ describe('PythonCodeRuntime — programs and bindings', () => {
       const { runtime } = await setup({ maxLogBytes: 3072, maxWallMs: 30_000 })
       result = await runtime.run({
         program: [
-          'import os',
+          'import os, time',
           'seq = (0xed, 0xa0, 0x80)',
-          'for _ in range(2000):',
+          // 1100 sequences are 3300 raw bytes, past the 3072-byte budget a
+          // raw-byte undercount reaches, so the undercount flushes above the
+          // 2048 discriminator instead of only at EOF.
+          'for _ in range(1100):',
           '    for b in seq:',
           '        os.write(1, bytes((b,)))',
-          '        os.sched_yield()',
+          '        time.sleep(0.001)',
           'return None',
         ].join('\n'),
         bindings: [],
@@ -1579,7 +1589,10 @@ describe('PythonCodeRuntime — programs and bindings', () => {
     // largest merged buffer stays well under 2048. Charging the structural width
     // 3 would need ~1024 raw bytes, tripling the peak past 2048.
     expect(maxConcat).toBeLessThan(2048)
-  })
+    // The paced payload costs ~3.3s deterministically, which is above the
+    // 5000ms default the local unit entry grants, so the case carries its own
+    // bound instead of relying on the lane to widen it.
+  }, 20_000)
 
   it('charges a lone surrogate its full six escaped bytes, not three', async () => {
     // A forged `log` frame carrying `\ud800` escapes materializes lone

+ 15 - 5
packages/subagent/subagent-acp/tests/subagent-acp.spec.ts

@@ -1412,17 +1412,27 @@ describe('dsh-subagent-acp', () => {
       args: [mockServer],
       permission: 'reject',
       env: { MOCK_TRAP_SIGTERM: '1', MOCK_TEXT: 'x', MOCK_READY_FILE: ready },
-      disposeEofGraceMs: 150,
-      disposeGraceMs: 150,
+      // The graces are wall-clock budgets for the managed scope's teardown on a
+      // shared host. At 150ms the hosted image escalated while the scope could
+      // not take the signal and systemctl failed the kill ("Failed to send
+      // signal SIGKILL to auxiliary processes: Invalid argument"), surfacing as
+      // a teardown failure the configuration never asked for. The case asserts
+      // that config graces reach the real run, so the value only has to clear
+      // the host's scope handling.
+      disposeEofGraceMs: 5_000,
+      disposeGraceMs: 5_000,
     })
     const run = await ctx.subagents.start('acp', request())
     expect(start).toHaveBeenCalledExactlyOnceWith(expect.anything(), expect.objectContaining({
-      disposeEofGraceMs: 150,
-      disposeGraceMs: 150,
+      disposeEofGraceMs: 5_000,
+      disposeGraceMs: 5_000,
     }))
     await waitForFile(ready, task.timeout)
     await expect(run.dispose()).resolves.toBeUndefined()
-  })
+    // The trapped child refuses stdin EOF, so the ladder waits out the EOF
+    // grace and then the SIGTERM grace (10s) before the SIGKILL settles it.
+    // That fixed cost is above the 5000ms default the local unit entry grants.
+  }, 30_000)
 
   it('rejects a dispose grace outside the Node timer range at load', async () => {
     for (const bad of [