test(jsonl): cover reusable decoder branches

This commit is contained in:
imccyu
2026-08-06 01:31:13 +08:00
parent 456d4319bf
commit 03bb663966
5 changed files with 147 additions and 20 deletions

View File

@@ -280,6 +280,7 @@ export class SessionPersistenceJsonl extends SessionPersistence implements Persi
signal?.throwIfAborted()
const headerFrame = decodedFrames.next()
signal?.throwIfAborted()
/* v8 ignore next -- a non-empty structural frame list makes the decoder yield its first frame or throw. */
if (headerFrame.done) throw new Error('empty or header-less Zstandard session log')
assertZstdHeaderFrame(headerFrame.value)
const scanner = new SessionLogScanner(headerFrame.value)

View File

@@ -41,6 +41,7 @@ function privateZstdStream(
const errorKey = Reflect.ownKeys(stream).find((key): key is symbol => (
typeof key === 'symbol' && key.description === 'kError'
))
/* v8 ignore next -- one test runtime exposes one Node-private shape; the Node 22/24/26 matrix checks compatibility. */
if (
typeof handle !== 'object' || handle === null
|| typeof (handle as { writeSync?: unknown }).writeSync !== 'function'
@@ -48,6 +49,7 @@ function privateZstdStream(
|| candidate._writeState.length < 2
|| typeof candidate._defaultFlushFlag !== 'number'
|| errorKey === undefined
|| candidate[errorKey] !== null
) return undefined
return { stream: stream as NodeZstdPrivateStream, errorKey }
}
@@ -81,10 +83,13 @@ export class NodePrivateZstdFrameDecoder implements ZstdFrameDecoder {
static create(): NodePrivateZstdFrameDecoder | undefined {
const stream = createZstdDecompress({ chunkSize: DECODE_CHUNK_SIZE })
const privateAccess = privateZstdStream(stream)
/* v8 ignore next -- reached only when a supported Node release changes its private stream shape. */
if (privateAccess !== undefined) {
return new NodePrivateZstdFrameDecoder(privateAccess.stream, privateAccess.errorKey)
}
/* v8 ignore next -- the active Node runtime passed the private-shape probe above. */
stream.close()
/* v8 ignore next -- the active Node runtime passed the private-shape probe above. */
return undefined
}
@@ -111,6 +116,7 @@ export class NodePrivateZstdFrameDecoder implements ZstdFrameDecoder {
/** Decode one frame; its returned scratch view remains valid until the next call. */
private decodeFrame(input: Buffer): Buffer {
const handle = this.stream._handle
/* v8 ignore next -- decode() rejects closed instances before entering this private frame operation. */
if (this.closed || handle === null) throw new Error('cannot decode with a closed Zstandard frame decoder')
let inputOffset = 0
@@ -125,11 +131,11 @@ export class NodePrivateZstdFrameDecoder implements ZstdFrameDecoder {
inputRemaining,
this.output,
0,
DECODE_CHUNK_SIZE,
this.output.length,
)
if (this.decoderError !== undefined) throw this.decoderError
const internalError = this.stream[this.errorKey]
if (internalError !== undefined && internalError !== null) {
if (internalError !== null) {
if (internalError instanceof Error) throw internalError
throw new Error('Zstandard decoder exposed a non-Error internal failure')
}
@@ -137,21 +143,23 @@ export class NodePrivateZstdFrameDecoder implements ZstdFrameDecoder {
const outputAfter = this.stream._writeState[0]
const inputAfter = this.stream._writeState[1]
const consumed = inputRemaining - inputAfter
const produced = DECODE_CHUNK_SIZE - outputAfter
const produced = this.output.length - outputAfter
if (produced > 0) {
outputBytes += produced
/* v8 ignore next -- Buffer cannot materialize a frame beyond its own process-wide maximum length. */
if (outputBytes > bufferConstants.MAX_LENGTH) {
throw new Error(`Zstandard frame output exceeds ${bufferConstants.MAX_LENGTH} bytes`)
}
}
if (outputAfter !== 0) {
/* v8 ignore next -- structurally scanned ranges contain exactly one complete frame and no trailing bytes. */
if (inputAfter !== 0) throw new Error('Zstandard frame decoder left trailing input')
const finalChunk = this.output.subarray(0, produced)
if (fullChunks.length === 0) return finalChunk
if (produced > 0) fullChunks.push(Buffer.from(finalChunk))
const [onlyChunk] = fullChunks
return fullChunks.length === 1 && onlyChunk !== undefined
const onlyChunk = fullChunks[0] as Buffer
return fullChunks.length === 1
? onlyChunk
: Buffer.concat(fullChunks, outputBytes)
}

View File

@@ -6,7 +6,7 @@
*/
import {
constants, zstdCompress, zstdDecompress, zstdDecompressSync, type ZstdOptions,
constants, zstdCompress, zstdDecompress, type ZstdOptions,
} from 'node:zlib'
import { promisify } from 'node:util'
import { NodePrivateZstdFrameDecoder } from './zstd-private-decoder.ts'
@@ -121,17 +121,6 @@ export async function decompressZstdFrame(input: Buffer): Promise<Buffer> {
return zstdDecompressAsync(input)
}
/**
* Synchronously decompress one complete frame and validate its checksum.
* Complete-log readers time-slice repeated calls so the event loop regains
* control without paying one asynchronous native dispatch per frame.
* @param input - one structurally complete Zstandard frame.
* @returns the frame plaintext.
*/
export function decompressZstdFrameSync(input: Buffer): Buffer {
return zstdDecompressSync(input)
}
/** Common lifecycle for interchangeable synchronous multi-frame decoders. */
export interface ZstdFrameDecoder {
/**