deepseek-harness/packages/code-runtime/code-runtime-python/tests/protocol.spec.ts
Chinesezjc f0d669883f fix(code-runtime-python): close coverage gap and tighten the wire mirror
- Cover the log-frame `truncated` rebuild branch: assert a literal-true flag
  rides along and any other value (1, string, false) is dropped, closing the
  protocol.ts branch the coverage gate flagged.
- Correct encodeJsonPlain's JSDoc: it matches compact JSON.stringify EXCEPT on
  a beyond-safe-range integral double, where it emits the exact BigInt digits
  (`...846976`) rather than the rounded `...847000` — the divergence the
  "emits exact digits" test pins.
- Declare py/protocol.py's `global`-bearing frames (Namespace, CallMessage)
  with functional TypedDict syntax so they carry the real wire key instead of
  a `global_` attribute the wire never sends, and split optional-field messages
  (Namespace/LogMessage/DoneMessage) into a required base plus a total=False
  subclass so `type` and other required fields cannot be dropped. Widen
  HostToChild to include the boot and run frames the host sends before replies.
- Reword the mirror e2e's py/ directory assertion to describe the source-tree
  layout it actually checks.
2026-08-07 13:27:54 +08:00

251 lines
15 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, it } from 'vitest'
import { checkDoneValue, encodeJsonPlain, hasNonLosslessNumber, hasUnsafeIntegerToken, logTruncationMarker, validateChildFrame } from '../src/index.ts'
describe('logTruncationMarker', () => {
it('names the configured byte budget', () => {
expect(logTruncationMarker(65536)).toBe('[dsh-code-runtime-python] log capture truncated at 65536 bytes')
expect(logTruncationMarker(1)).toBe('[dsh-code-runtime-python] log capture truncated at 1 bytes')
})
})
describe('validateChildFrame', () => {
it('rebuilds boot-ack frames without extra fields', () => {
expect(validateChildFrame({ type: 'boot-ack' })).toEqual({ type: 'boot-ack' })
// Forged extras never ride along.
expect(validateChildFrame({ type: 'boot-ack', extra: 'x' })).toEqual({ type: 'boot-ack' })
})
it('rebuilds log frames when the text field is a string', () => {
expect(validateChildFrame({ type: 'log', text: 'hi' })).toEqual({ type: 'log', text: 'hi' })
// Non-string text drops.
expect(validateChildFrame({ type: 'log', text: 42 })).toBeUndefined()
expect(validateChildFrame({ type: 'log' })).toBeUndefined()
})
it('carries a log frame truncation flag only for the literal true', () => {
// The child's own ledger marker sets `truncated: true`; the host rebuilds
// it so it stops capturing at the same point.
expect(validateChildFrame({ type: 'log', text: 'x', truncated: true }))
.toEqual({ type: 'log', text: 'x', truncated: true })
// Any other truthy or non-boolean value is a forgery and is dropped from
// the rebuild — otherwise it would silence capture for the rest of the run.
expect(validateChildFrame({ type: 'log', text: 'x', truncated: 1 })).toEqual({ type: 'log', text: 'x' })
expect(validateChildFrame({ type: 'log', text: 'x', truncated: 'yes' })).toEqual({ type: 'log', text: 'x' })
expect(validateChildFrame({ type: 'log', text: 'x', truncated: false })).toEqual({ type: 'log', text: 'x' })
})
it('rebuilds call frames with a numeric id, string global, and string name', () => {
expect(validateChildFrame({ type: 'call', id: 1, global: 'tools', name: 'echo', args: { x: 1 } }))
.toEqual({ type: 'call', id: 1, global: 'tools', name: 'echo', args: { x: 1 } })
// A frame with NO args key drops whole: rebuilding it as `undefined`
// would invoke the binding with a non-JSON value, bypassing the
// lossless-JSON argument boundary. Any present value is JSON-plain by
// construction (frames arrive via JSON.parse), so null passes.
expect(validateChildFrame({ type: 'call', id: 2, global: 'tools', name: 'echo' })).toBeUndefined()
expect(validateChildFrame({ type: 'call', id: 2, global: 'tools', name: 'echo', args: null }))
.toEqual({ type: 'call', id: 2, global: 'tools', name: 'echo', args: null })
// A missing/mistyped required field drops.
expect(validateChildFrame({ type: 'call', id: '1', global: 'tools', name: 'echo' })).toBeUndefined()
expect(validateChildFrame({ type: 'call', id: 1, global: 7, name: 'echo' })).toBeUndefined()
expect(validateChildFrame({ type: 'call', id: 1, global: 'tools' })).toBeUndefined()
})
it('rebuilds done frames with optional value/error', () => {
expect(validateChildFrame({ type: 'done' })).toEqual({ type: 'done' })
expect(validateChildFrame({ type: 'done', value: 42 })).toEqual({ type: 'done', value: 42 })
expect(validateChildFrame({ type: 'done', error: { kind: 'exception', message: 'boom' } }))
.toEqual({ type: 'done', error: { kind: 'exception', message: 'boom' } })
expect(validateChildFrame({ type: 'done', error: { kind: 'invalid-output', message: 'lossy' } }))
.toEqual({ type: 'done', error: { kind: 'invalid-output', message: 'lossy' } })
expect(validateChildFrame({ type: 'done', error: { kind: 'output-limit', message: 'big' } }))
.toEqual({ type: 'done', error: { kind: 'output-limit', message: 'big' } })
expect(validateChildFrame({ type: 'done', value: 1, error: { kind: 'exception', message: 'boom' } }))
.toEqual({ type: 'done', value: 1, error: { kind: 'exception', message: 'boom' } })
// A `value: undefined` field is dropped (JSON never carries it, but a forged
// shape might; the rebuild coalesces to the absent case).
expect(validateChildFrame({ type: 'done', value: undefined })).toEqual({ type: 'done' })
// A missing or unrecognized kind drops the frame: the child always sends
// one of the three, so anything else is a forgery.
expect(validateChildFrame({ type: 'done', error: { message: 'boom' } })).toBeUndefined()
expect(validateChildFrame({ type: 'done', error: { kind: 'timeout', message: 'x' } })).toBeUndefined()
})
it('rejects malformed done frames', () => {
// error must be an object.
expect(validateChildFrame({ type: 'done', error: 'boom' })).toBeUndefined()
expect(validateChildFrame({ type: 'done', error: null })).toBeUndefined()
// error.message must be a string.
expect(validateChildFrame({ type: 'done', error: {} })).toBeUndefined()
expect(validateChildFrame({ type: 'done', error: { message: 42 } })).toBeUndefined()
})
it('drops non-object inputs and unknown types silently', () => {
expect(validateChildFrame(null)).toBeUndefined()
expect(validateChildFrame(undefined)).toBeUndefined()
expect(validateChildFrame(42)).toBeUndefined()
expect(validateChildFrame('str')).toBeUndefined()
expect(validateChildFrame({})).toBeUndefined()
expect(validateChildFrame({ type: 'unknown' })).toBeUndefined()
})
it('drops CALL frames whose args are non-finite or negative zero', () => {
// JSON.parse turns 1e400 into Infinity and preserves -0; the honest child
// rejects both before sending, so a call frame carrying one is forged.
expect(validateChildFrame({ type: 'call', id: 1, global: 'tools', name: 'x', args: { n: Infinity } })).toBeUndefined()
expect(validateChildFrame({ type: 'call', id: Infinity, global: 'tools', name: 'x', args: null })).toBeUndefined()
// Plain zero and ordinary floats pass.
expect(validateChildFrame({ type: 'call', id: 1, global: 'tools', name: 'x', args: [0, 1.5] }))
.toEqual({ type: 'call', id: 1, global: 'tools', name: 'x', args: [0, 1.5] })
})
it('passes DONE values through untouched — losslessness is metered later', () => {
// validateChildFrame no longer scans done.value: an unbounded scan would
// push every member of a wide forged payload before any byte cap ran. The
// done handler's checkDoneValue folds losslessness into the metered walk.
expect(validateChildFrame({ type: 'done', value: Infinity })).toEqual({ type: 'done', value: Infinity })
expect(validateChildFrame({ type: 'done', value: [{ x: -0 }] })).toEqual({ type: 'done', value: [{ x: -0 }] })
expect(validateChildFrame({ type: 'done', value: [0, 1.5] })).toEqual({ type: 'done', value: [0, 1.5] })
})
})
describe('lossless-number scan', () => {
it('finds non-finite and negative-zero numbers at any depth, iteratively', () => {
expect(hasNonLosslessNumber(Infinity)).toBe(true)
expect(hasNonLosslessNumber(-Infinity)).toBe(true)
expect(hasNonLosslessNumber(NaN)).toBe(true)
expect(hasNonLosslessNumber(-0)).toBe(true)
expect(hasNonLosslessNumber({ a: [1, { b: -0 }] })).toBe(true)
expect(hasNonLosslessNumber({ a: [0, 1.5, 'x', null, true] })).toBe(false)
// Deep nesting must not overflow the stack.
let deep: unknown = 0
for (let i = 0; i < 100000; i++) deep = [deep]
expect(hasNonLosslessNumber(deep)).toBe(false)
})
it('walks wide arrays and objects one member at a time', () => {
// `call.args` carries no seam byte cap, so a wide forged payload has no
// budget to be rejected against — the walk must hold one cursor per
// NESTING LEVEL, not one entry per member, or a flat payload just below
// the 256 MiB frame ceiling would allocate tens of millions of stack
// entries (and `Object.values` a second full-breadth copy). Observable
// through the boundary: a wide payload whose per-member cost the old shape
// would have paid still scans, and a violation ANYWHERE in it is found
// wherever it sits.
const wideArray = new Array(2_000_000).fill(0) as unknown[]
expect(hasNonLosslessNumber(wideArray)).toBe(false)
// Last element, so the cursor must run the whole breadth lazily.
wideArray[wideArray.length - 1] = -0
expect(hasNonLosslessNumber(wideArray)).toBe(true)
const wideObject: Record<string, unknown> = {}
for (let i = 0; i < 200_000; i++) wideObject[`k${i}`] = i
expect(hasNonLosslessNumber(wideObject)).toBe(false)
wideObject.last = Infinity
expect(hasNonLosslessNumber(wideObject)).toBe(true)
// Interleaved nesting: a per-level cursor must resume its parent after a
// child level ends, so a violation after a nested container is still seen.
expect(hasNonLosslessNumber([[1], { a: 2 }, NaN])).toBe(true)
})
it('scans only own enumerable properties', () => {
// The per-level cursor filters own keys (a prototype-carrying frame is
// impossible off JSON.parse, but the filter is what keeps the walk equal
// to what the encoder would serialize).
const withProto = Object.create({ inherited: -0 }) as Record<string, unknown>
withProto.own = 1
expect(hasNonLosslessNumber(withProto)).toBe(false)
})
})
describe('unsafe-integer token scan', () => {
it('flags integer tokens outside the safe range, skipping strings and float forms', () => {
expect(hasUnsafeIntegerToken('{"v":9007199254740993}')).toBe(true)
// Exact beyond-safe-range tokens are lossless and pass (2**53, 2**64).
expect(hasUnsafeIntegerToken('{"v":9007199254740992}')).toBe(false)
expect(hasUnsafeIntegerToken('{"v":18446744073709551616}')).toBe(false)
// A token that parses to Infinity is trivially lossy.
expect(hasUnsafeIntegerToken(`{"v":${'9'.repeat(400)}}`)).toBe(true)
expect(hasUnsafeIntegerToken('{"v":-9007199254740993}')).toBe(true)
expect(hasUnsafeIntegerToken('{"v":9007199254740991}')).toBe(false)
expect(hasUnsafeIntegerToken('{"v":"9007199254740993"}')).toBe(false)
expect(hasUnsafeIntegerToken(String.raw`{"v":"esc\"9007199254740993"}`)).toBe(false)
expect(hasUnsafeIntegerToken('{"v":9007199254740993.0}')).toBe(false)
expect(hasUnsafeIntegerToken('{"v":9e99}')).toBe(false)
})
})
describe('checkDoneValue', () => {
it('matches the exact encoded size and rejects one byte over', () => {
const cases: unknown[] = [null, true, false, 0, -1.5, 'a"b\\', [], {}, [1, 'x', null], { a: [1, 2], b: { c: 'd' } }]
for (const value of cases) {
const exact = Buffer.byteLength(JSON.stringify(value), 'utf8')
expect(checkDoneValue(value, exact), JSON.stringify(value)).toEqual({ ok: true, bytes: exact })
expect(checkDoneValue(value, exact - 1), JSON.stringify(value)).toEqual({ ok: false, reason: 'over-budget' })
expect(encodeJsonPlain(value)).toBe(JSON.stringify(value))
}
})
it('stops early on a huge value instead of measuring it whole', () => {
const huge = { data: 'x'.repeat(1_000_000), tail: 'y' }
expect(checkDoneValue(huge, 1024)).toEqual({ ok: false, reason: 'over-budget' })
// A forged flat array below the frame ceiling must fail BEFORE its
// elements are enqueued — the pre-enqueue bound keeps the walk O(cap).
const flat = new Array(10_000_000).fill(0)
expect(checkDoneValue(flat, 1024)).toEqual({ ok: false, reason: 'over-budget' })
// Same bound for a wide object: braces+commas fit the cap, but the
// per-entry lower bound (quoted key + colon + value) does not, so it fails
// before any key is metered or any value enqueued.
const wide: Record<string, number> = {}
for (let i = 0; i < 10; i++) wide[`k${i}`] = i
expect(checkDoneValue(wide, 12)).toEqual({ ok: false, reason: 'over-budget' })
})
it('rejects an over-budget string on its length before escaping it', () => {
// A control-heavy forged string escapes to ~6x its length; the walk must
// refuse it on the cheap `length + 2` lower bound so the escaped copy is
// never allocated. Observable through the boundary: a string whose LENGTH
// already exceeds the cap fails even though every character is 1 byte.
expect(checkDoneValue(''.repeat(4096), 1024)).toEqual({ ok: false, reason: 'over-budget' })
// The bound is a lower bound, never a false rejection: a string that fits
// exactly still passes with its exact escaped size.
expect(checkDoneValue('', 8)).toEqual({ ok: true, bytes: 8 })
expect(checkDoneValue('', 7)).toEqual({ ok: false, reason: 'over-budget' })
// Same lower bound for keys, checked before the key is escaped.
expect(checkDoneValue({ [''.repeat(4096)]: 1 }, 1024)).toEqual({ ok: false, reason: 'over-budget' })
})
it('meters only own enumerable keys', () => {
// The walk counts keys with a `for...in` + hasOwn pass rather than
// Object.keys/entries (which allocate per member before the bound). A
// prototype-carrying forgery is impossible off JSON.parse, but the own-key
// filter is what keeps the count equal to the encoder's.
const withProto = Object.create({ inherited: 'x' }) as Record<string, unknown>
withProto.own = 1
expect(checkDoneValue(withProto, 1024)).toEqual({ ok: true, bytes: Buffer.byteLength('{"own":1}', 'utf8') })
})
it('rejects non-finite and negative-zero numbers at any depth as non-lossless', () => {
expect(checkDoneValue(Infinity, 1024)).toEqual({ ok: false, reason: 'non-lossless' })
expect(checkDoneValue(-Infinity, 1024)).toEqual({ ok: false, reason: 'non-lossless' })
expect(checkDoneValue(NaN, 1024)).toEqual({ ok: false, reason: 'non-lossless' })
expect(checkDoneValue(-0, 1024)).toEqual({ ok: false, reason: 'non-lossless' })
expect(checkDoneValue({ a: [1, { b: -0 }] }, 1024)).toEqual({ ok: false, reason: 'non-lossless' })
// An ordinary finite value within budget passes with its exact byte count.
const clean = { a: [0, 1.5, 'x', null, true] }
expect(checkDoneValue(clean, 1024)).toEqual({ ok: true, bytes: Buffer.byteLength(JSON.stringify(clean), 'utf8') })
})
it('meters deep nesting iteratively without overflowing the stack', () => {
let deep: unknown = 0
for (let i = 0; i < 100_000; i++) deep = [deep]
// 100000 '[' + '0' + 100000 ']' = 200001 bytes.
expect(checkDoneValue(deep, 1_000_000)).toEqual({ ok: true, bytes: 200_001 })
})
it('emits exact digits for beyond-safe integral doubles', () => {
// String(2**60) prints the ROUNDED ...847000; echoing that to the child
// would change the integer. BigInt digits give the exact ...846976.
const v = JSON.parse('[1152921504606846976]') as unknown
expect(encodeJsonPlain(v)).toBe('[1152921504606846976]')
expect(checkDoneValue(v, 100)).toEqual({ ok: true, bytes: Buffer.byteLength('[1152921504606846976]', 'utf8') })
})
})