Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- NEVER hardcode things: examples include function names, builtins, etc.
- Actively improve touched code and design docs when the opportunity is clear: if you find a stale or contradictory design note in the area you are changing, update the doc to match the source/tests instead of preserving the contradiction.
- Never use `as unknown as ...`. It is a red flag for a type-system escape hatch hiding a bad runtime contract; return the actual runtime type or fix the owner type signature so the checker catches mismatches.
- NEVER collect a byte stream with one-byte `Reader` calls when the `Reader` accepts a caller-sized buffer. Read in bounded chunks (32 KiB by default for runtime overrides), then parse bytes from the buffer. Cover bulk-input paths with a regression test that caps `Reader` calls.
- Go standard library sources are located at "go env GOROOT" (shell command)
- Leverage adding more tests (e.g., `compiler/analysis_test.go`) instead of debug logging for diagnosing issues. If the new test case is temporary, add a `tmp_test.go` file to keep things separated.
- AVOID type arguments unless necessary (prefer type inference)
Expand Down
32 changes: 32 additions & 0 deletions gs/compress/gzip/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,38 @@ describe('compress/gzip override', () => {
expect($.bytesToString(out)).toBe('hello gzip world')
})

test('reader collects bulk input in chunks', async () => {
const input = $.makeSlice<number>(128 * 1024, undefined, 'byte')
let state = 0x12345678
for (let idx = 0; idx < input.length; idx++) {
state ^= state << 13
state ^= state >>> 17
state ^= state << 5
input[idx] = state & 0xff
}

const compressed = $.markAsStructValue(new bytes.Buffer())
const writer = NewWriter(compressed)
expect(writer.Write(input)[1]).toBeNull()
expect(await writer.Close()).toBeNull()

const source = bytes.NewReader(compressed.Bytes())
let readCalls = 0
const observedReader = {
Read(p: $.Bytes): [number, $.GoError] {
readCalls++
return source.Read(p)
},
}

const [reader, readerErr] = NewReader(observedReader as io.Reader)
expect(readerErr).toBeNull()
const [out, readErr] = await io.ReadAll(reader!)
expect(readErr).toBeNull()
expect($.bytesToUint8Array(out)).toEqual($.bytesToUint8Array(input))
expect(readCalls).toBeLessThanOrEqual(6)
})

test('reader reset accepts async generated readers', async () => {
const compressed = $.markAsStructValue(new bytes.Buffer())
const writer = NewWriter(compressed)
Expand Down
20 changes: 10 additions & 10 deletions gs/compress/gzip/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ type compressionRuntime = {
gunzipSync?: (data: Uint8Array) => Uint8Array
}

const readerBufferSize = 32 * 1024

export const NoCompression = 0
export const BestSpeed = 1
export const BestCompression = 9
Expand Down Expand Up @@ -174,9 +176,7 @@ async function gzipBytes(data: Uint8Array, level: number): Promise<Uint8Array> {
return streamTransform(data, new CompressionStreamCtor('gzip'))
}

function gunzipBytes(
data: Uint8Array,
): Uint8Array | Promise<Uint8Array> {
function gunzipBytes(data: Uint8Array): Uint8Array | Promise<Uint8Array> {
const runtime = nodeCompressionRuntime()
if (runtime?.gunzipSync != null) {
return runtime.gunzipSync(data)
Expand Down Expand Up @@ -207,7 +207,7 @@ function readGunzipped(
| { data: Uint8Array | null; err: $.GoError }
| Promise<{ data: Uint8Array | null; err: $.GoError }> {
const chunks: Uint8Array[] = []
const buf = $.makeSlice<number>(1, undefined, 'byte')
const buf = $.makeSlice<number>(readerBufferSize, undefined, 'byte')
while (true) {
const read = r.Read(buf)
if (read instanceof Promise) {
Expand Down Expand Up @@ -249,12 +249,12 @@ function recordChunk(chunks: Uint8Array[], buf: $.Bytes, n: number): void {
}
}

function inflateRecorded(
chunks: Uint8Array[],
): { data: Uint8Array | null; err: $.GoError } | Promise<{
data: Uint8Array | null
err: $.GoError
}> {
function inflateRecorded(chunks: Uint8Array[]):
| { data: Uint8Array | null; err: $.GoError }
| Promise<{
data: Uint8Array | null
err: $.GoError
}> {
try {
const inflated = gunzipBytes(concat(chunks))
if (inflated instanceof Promise) {
Expand Down
Loading