chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
import { PassThrough, Readable } from 'node:stream'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
||||
import {
|
||||
buildCommand,
|
||||
chunkBytesBySize,
|
||||
isPipedStdin,
|
||||
readStdinIfPiped,
|
||||
readStdinFrom,
|
||||
streamStdinChunks,
|
||||
shellQuote,
|
||||
} from '../../../src/commands/sandbox/exec_helpers'
|
||||
|
||||
describe('exec helpers', () => {
|
||||
test('shellQuote leaves safe args untouched', () => {
|
||||
expect(shellQuote('python3')).toBe('python3')
|
||||
expect(shellQuote('-c')).toBe('-c')
|
||||
expect(shellQuote('path/to/file.txt')).toBe('path/to/file.txt')
|
||||
})
|
||||
|
||||
test('shellQuote wraps special chars and spaces', () => {
|
||||
expect(shellQuote('print(input())')).toBe("'print(input())'")
|
||||
expect(shellQuote('hello world')).toBe("'hello world'")
|
||||
expect(shellQuote("it's ok")).toBe("'it'\"'\"'s ok'")
|
||||
expect(shellQuote('')).toBe("''")
|
||||
})
|
||||
|
||||
test('buildCommand returns a single command as-is', () => {
|
||||
const cmd = 'python3 -c "print(input())"'
|
||||
expect(buildCommand([cmd])).toBe(cmd)
|
||||
})
|
||||
|
||||
test('buildCommand quotes args that need shell escaping', () => {
|
||||
expect(buildCommand(['python3', '-c', 'print(input())'])).toBe(
|
||||
"python3 -c 'print(input())'"
|
||||
)
|
||||
expect(buildCommand(['echo', 'hello world'])).toBe("echo 'hello world'")
|
||||
expect(buildCommand(['echo', "it's ok"])).toBe("echo 'it'\"'\"'s ok'")
|
||||
})
|
||||
|
||||
test('readStdinFrom reads full input and resolves on EOF', async () => {
|
||||
const stream = Readable.from(['foo', 'bar'])
|
||||
await expect(readStdinFrom(stream)).resolves.toEqual(Buffer.from('foobar'))
|
||||
})
|
||||
|
||||
test('readStdinFrom handles EOF without trailing newline', async () => {
|
||||
const stream = Readable.from(['no-newline'])
|
||||
await expect(readStdinFrom(stream)).resolves.toEqual(
|
||||
Buffer.from('no-newline')
|
||||
)
|
||||
})
|
||||
|
||||
test('readStdinIfPiped returns undefined when stdin is not a pipe', async () => {
|
||||
const fsMock = {
|
||||
fstatSync: () => ({ isFIFO: () => false }),
|
||||
}
|
||||
const stream = new PassThrough()
|
||||
stream.end('data')
|
||||
await expect(readStdinIfPiped({ fsModule: fsMock, stream })).resolves.toBe(
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
test('readStdinIfPiped reads from provided stream when piped', async () => {
|
||||
const fsMock = {
|
||||
fstatSync: () => ({ isFIFO: () => true }),
|
||||
}
|
||||
const stream = new PassThrough()
|
||||
const promise = readStdinIfPiped({ fsModule: fsMock, stream })
|
||||
stream.write(Buffer.from([0xe2, 0x98]))
|
||||
stream.write(Buffer.from([0x83]))
|
||||
stream.end(Buffer.from([0x21]))
|
||||
await expect(promise).resolves.toEqual(Buffer.from([0xe2, 0x98, 0x83, 0x21]))
|
||||
})
|
||||
|
||||
test('isPipedStdin returns true for FIFO', () => {
|
||||
const fsMock = {
|
||||
fstatSync: () => ({ isFIFO: () => true, isCharacterDevice: () => false }),
|
||||
}
|
||||
expect(isPipedStdin(0, fsMock)).toBe(true)
|
||||
})
|
||||
|
||||
test('isPipedStdin returns true for file redirection', () => {
|
||||
const fsMock = {
|
||||
fstatSync: () => ({ isFile: () => true, isCharacterDevice: () => false }),
|
||||
}
|
||||
expect(isPipedStdin(0, fsMock)).toBe(true)
|
||||
})
|
||||
|
||||
test('isPipedStdin returns false for interactive terminal', () => {
|
||||
const fsMock = {
|
||||
fstatSync: () => ({
|
||||
isCharacterDevice: () => true,
|
||||
isFIFO: () => false,
|
||||
isFile: () => false,
|
||||
}),
|
||||
}
|
||||
expect(isPipedStdin(0, fsMock)).toBe(false)
|
||||
})
|
||||
|
||||
test('isPipedStdin returns false for non-FIFO or errors', () => {
|
||||
const fsMockFalse = {
|
||||
fstatSync: () => ({ isFIFO: () => false, isCharacterDevice: () => false }),
|
||||
}
|
||||
expect(isPipedStdin(0, fsMockFalse)).toBe(false)
|
||||
|
||||
const fsMockThrow = {
|
||||
fstatSync: () => {
|
||||
throw new Error('fail')
|
||||
},
|
||||
}
|
||||
expect(isPipedStdin(0, fsMockThrow)).toBe(false)
|
||||
})
|
||||
|
||||
test('chunkBytesBySize splits large input into byte-sized chunks', () => {
|
||||
const maxBytes = 64 * 1024
|
||||
const data = Buffer.from('a'.repeat(maxBytes * 2 + 1))
|
||||
const chunks = chunkBytesBySize(data, maxBytes)
|
||||
|
||||
expect(chunks).toHaveLength(3)
|
||||
expect(chunks[0].byteLength).toBe(maxBytes)
|
||||
expect(chunks[1].byteLength).toBe(maxBytes)
|
||||
expect(chunks[2].byteLength).toBe(1)
|
||||
expect(Buffer.concat(chunks.map((c) => Buffer.from(c)))).toEqual(data)
|
||||
})
|
||||
|
||||
test('chunkBytesBySize keeps byte content intact', () => {
|
||||
const maxBytes = 64 * 1024
|
||||
const data = Buffer.from('\u{1F600}'.repeat(20000)) // 😀 (4 bytes each)
|
||||
const chunks = chunkBytesBySize(data, maxBytes)
|
||||
|
||||
for (const chunk of chunks) {
|
||||
expect(chunk.byteLength).toBeLessThanOrEqual(maxBytes)
|
||||
}
|
||||
expect(Buffer.concat(chunks.map((c) => Buffer.from(c)))).toEqual(data)
|
||||
})
|
||||
|
||||
test('chunkBytesBySize returns empty array for empty input', () => {
|
||||
const chunks = chunkBytesBySize(Buffer.alloc(0), 64 * 1024)
|
||||
expect(chunks).toHaveLength(0)
|
||||
})
|
||||
|
||||
test('chunkBytesBySize returns single chunk for small input', () => {
|
||||
const data = Buffer.from('hello')
|
||||
const chunks = chunkBytesBySize(data, 64 * 1024)
|
||||
expect(chunks).toHaveLength(1)
|
||||
expect(Buffer.from(chunks[0])).toEqual(data)
|
||||
})
|
||||
|
||||
test('chunkBytesBySize throws on invalid maxBytes', () => {
|
||||
expect(() => chunkBytesBySize(Buffer.from('data'), 0)).toThrow()
|
||||
expect(() => chunkBytesBySize(Buffer.from('data'), -1)).toThrow()
|
||||
})
|
||||
|
||||
test('readStdinFrom resolves with empty buffer on immediate EOF', async () => {
|
||||
const stream = Readable.from([])
|
||||
await expect(readStdinFrom(stream)).resolves.toEqual(Buffer.alloc(0))
|
||||
})
|
||||
|
||||
test('streamStdinChunks delivers chunks incrementally before EOF', async () => {
|
||||
const stream = new PassThrough()
|
||||
const seen: Buffer[] = []
|
||||
let resolveFirstChunk: (() => void) | undefined
|
||||
const firstChunkSeen = new Promise<void>((resolve) => {
|
||||
resolveFirstChunk = resolve
|
||||
})
|
||||
const done = streamStdinChunks(
|
||||
stream,
|
||||
async (chunk) => {
|
||||
seen.push(Buffer.from(chunk))
|
||||
if (resolveFirstChunk) {
|
||||
resolveFirstChunk()
|
||||
resolveFirstChunk = undefined
|
||||
}
|
||||
},
|
||||
64 * 1024
|
||||
)
|
||||
|
||||
stream.write('first')
|
||||
await firstChunkSeen
|
||||
|
||||
expect(seen).toEqual([Buffer.from('first')])
|
||||
|
||||
stream.end('second')
|
||||
await done
|
||||
|
||||
expect(seen).toEqual([Buffer.from('first'), Buffer.from('second')])
|
||||
})
|
||||
|
||||
test('streamStdinChunks splits oversized stream chunks by max bytes', async () => {
|
||||
const stream = Readable.from([Buffer.from('a'.repeat(64 * 1024 + 3))])
|
||||
const chunks: Uint8Array[] = []
|
||||
|
||||
await streamStdinChunks(
|
||||
stream,
|
||||
async (chunk) => {
|
||||
chunks.push(chunk)
|
||||
},
|
||||
64 * 1024
|
||||
)
|
||||
|
||||
expect(chunks).toHaveLength(2)
|
||||
expect(chunks[0].byteLength).toBe(64 * 1024)
|
||||
expect(chunks[1].byteLength).toBe(3)
|
||||
expect(Buffer.concat(chunks.map((c) => Buffer.from(c)))).toEqual(
|
||||
Buffer.from('a'.repeat(64 * 1024 + 3))
|
||||
)
|
||||
})
|
||||
|
||||
test('streamStdinChunks stops early when onChunk returns false', async () => {
|
||||
const stream = Readable.from([Buffer.from('first'), Buffer.from('second')])
|
||||
const chunks: Uint8Array[] = []
|
||||
|
||||
await streamStdinChunks(
|
||||
stream,
|
||||
async (chunk) => {
|
||||
chunks.push(chunk)
|
||||
return false
|
||||
},
|
||||
64 * 1024
|
||||
)
|
||||
|
||||
expect(chunks).toHaveLength(1)
|
||||
expect(Buffer.from(chunks[0])).toEqual(Buffer.from('first'))
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user