Files
caprover/tests/DockerBuildOutput.test.ts
T

158 lines
4.6 KiB
TypeScript

import {
DockerBuildOutputDecoder,
DockerJsonStreamParser,
} from '../src/docker/DockerBuildOutput'
function varint(value: number): Buffer {
const bytes: number[] = []
do {
const remaining = Math.floor(value / 128)
bytes.push(value % 128 | (remaining ? 0x80 : 0))
value = remaining
} while (value)
return Buffer.from(bytes)
}
function bytesField(fieldNumber: number, value: string | Buffer): Buffer {
const data = Buffer.isBuffer(value) ? value : Buffer.from(value)
return Buffer.concat([
varint(fieldNumber * 8 + 2),
varint(data.length),
data,
])
}
test('buffers split JSON messages and parses multiple messages', () => {
const parser = new DockerJsonStreamParser()
expect(parser.push('{"stream":"hel')).toEqual([])
expect(parser.push('lo"}\n{"error":"failed"}\n')).toEqual([
{ stream: 'hello' },
{ error: 'failed' },
])
})
test('keeps buffers isolated and flushes the final message', () => {
const first = new DockerJsonStreamParser()
const second = new DockerJsonStreamParser()
first.push('{"stream":"first"')
second.push('{"stream":"second"}')
expect(first.push('}\n')).toEqual([{ stream: 'first' }])
expect(second.flush()).toEqual([{ stream: 'second' }])
})
test('reports malformed JSON without interrupting later messages', () => {
const parser = new DockerJsonStreamParser()
expect(parser.push('not-json\n{"stream":"still running"}\n')).toEqual([
{ stream: 'Cannot parse not-json' },
{ stream: 'still running' },
])
})
test('preserves traditional builder output', () => {
const decoder = new DockerBuildOutputDecoder()
const message = { stream: 'Step 1/1\n' }
expect(decoder.decode(message)).toEqual([message])
})
test('decodes BuildKit vertices, logs, and warnings', () => {
const vertex = Buffer.concat([
bytesField(1, 'sha256:vertex'),
bytesField(3, '[1/1] RUN npm test'),
])
const log = Buffer.concat([
bytesField(1, 'sha256:vertex'),
bytesField(4, 'tests passed\n'),
])
const warning = bytesField(3, 'deprecated instruction')
const status = Buffer.concat([
bytesField(1, vertex),
bytesField(3, log),
bytesField(4, warning),
])
const decoder = new DockerBuildOutputDecoder()
expect(
decoder.decode({
id: 'moby.buildkit.trace',
aux: status.toString('base64'),
})
).toEqual([
{ stream: '[1/1] RUN npm test\n' },
{ stream: 'tests passed\n' },
{ stream: 'WARNING: deprecated instruction\n' },
])
expect(
decoder.decode({
id: 'moby.buildkit.trace',
aux: bytesField(1, vertex).toString('base64'),
})
).toEqual([])
})
test('decodes BuildKit vertex errors once with error details', () => {
const vertex = Buffer.concat([
bytesField(1, 'sha256:failed-vertex'),
bytesField(3, '[1/1] RUN exit 1'),
bytesField(7, 'process exited with code 1'),
])
const message = {
id: 'moby.buildkit.trace',
aux: bytesField(1, vertex).toString('base64'),
}
const decoder = new DockerBuildOutputDecoder()
expect(decoder.decode(message)).toEqual([
{ stream: '[1/1] RUN exit 1\n' },
{
error: 'process exited with code 1',
errorDetail: { message: 'process exited with code 1' },
},
])
expect(decoder.decode(message)).toEqual([])
})
test('reports malformed BuildKit protobuf output', () => {
const decoder = new DockerBuildOutputDecoder()
expect(
decoder.decode({
id: 'moby.buildkit.trace',
aux: Buffer.from([0x0a, 0x05, 0x01]).toString('base64'),
})
).toEqual([{ stream: 'Cannot parse BuildKit build output\n' }])
})
test('ignores unrelated aux messages', () => {
const decoder = new DockerBuildOutputDecoder()
expect(
decoder.decode({ id: 'other.message', aux: 'not-protobuf' })
).toEqual([])
})
test('skips 10-byte protobuf varints without dropping later output', () => {
const fullWidthVarint = Buffer.from([
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01,
])
const log = Buffer.concat([
Buffer.from([0x08]),
fullWidthVarint,
bytesField(4, 'still visible\n'),
])
const status = bytesField(3, log)
const decoder = new DockerBuildOutputDecoder()
expect(
decoder.decode({
id: 'moby.buildkit.trace',
aux: status.toString('base64'),
})
).toEqual([{ stream: 'still visible\n' }])
})