Files
bare-operating-system/packages/bare-os-coreutils/test/error-propagation.test.mjs
T
Raven Scott d5535cdb65 Fixed command substitution behavior in shell expansion:
packages/bare-os-booter/lib/shell.js
$(...)/backticks are now enabled by default unless explicitly disabled via BARE_OS_SHELL_CMDSUBST=0|false|off.
Fixed sh -c exit/status propagation:

packages/bare-os-booter/lib/shell.js
Syncs BARE_OS_EXIT_STATUS after each executed statement so later commands in the same line (like echo $?) see the immediately previous status.
packages/bare-os-coreutils/src/sh.js
In -c mode, reads final BARE_OS_EXIT_STATUS back into ctx.exitCode for consistent result propagation.
Implemented trap behavior for requested scope (EXIT, INT, TERM):

packages/bare-os-booter/lib/shell.js
exit builtin now runs EXIT trap handler before requesting booter exit.
packages/bare-os-booter/index.js
Signal delivery for shell PID now triggers trap dispatch for INT/TERM.
Hardened ulimit -f invalid diagnostics:

packages/bare-os-coreutils/src/ulimit.js
Explicit invalid-value error for malformed -f setter input, with nonzero exit.
Supported-but-unimplemented setter values still return explicit unsupported-setter error.
2026-04-27 08:41:43 -04:00

117 lines
2.9 KiB
JavaScript

import { readFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import test from 'brittle'
import b4a from 'b4a'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
async function loadBin(name) {
const runtime = await readFile(path.join(__dirname, '../lib/runtime.js'), 'utf8')
const body = await readFile(path.join(__dirname, `../src/${name}.js`), 'utf8')
return new AsyncFunction(
'ctx',
'argv',
`${runtime}\n${body}\nif (typeof run === 'function') return await run(ctx, argv)\n`
)
}
function mkCtx(vfs) {
const logs = []
const errs = []
return {
b4a,
exitCode: 0,
shellStdin: '',
console: { log: (m) => logs.push(String(m)), error: (m) => errs.push(String(m)) },
vfs,
_logs: logs,
_errs: errs
}
}
test('grep missing file exits 2', async (t) => {
const run = await loadBin('grep')
const ctx = mkCtx({
env: {},
async readFile() {
return null
}
})
await run(ctx, ['grep', 'foo', '/nonexist'])
t.is(ctx.exitCode, 2)
t.ok(ctx._errs.join('\n').includes('No such file'))
})
test('rm -f does not suppress permission errors', async (t) => {
const run = await loadBin('rm')
const ctx = mkCtx({
async rm() {
throw new Error('EACCES: denied')
},
async unlink() {
throw new Error('EACCES: denied')
},
async lstat() {
return { type: 'file' }
}
})
await run(ctx, ['rm', '-f', '/bin/sh'])
t.is(ctx.exitCode, 1)
t.ok(ctx._errs.join('\n').includes('EACCES'))
})
test('find missing root exits nonzero', async (t) => {
const run = await loadBin('find')
const ctx = mkCtx({
env: {},
resolveLogical: (p) => p,
async lstat() {
return null
}
})
await run(ctx, ['find', '/tmp/non', '-delete'])
t.is(ctx.exitCode, 1)
t.ok(ctx._errs.join('\n').includes('No such file or directory'))
})
test('ulimit -f setter reports unsupported', async (t) => {
const run = await loadBin('ulimit')
const ctx = mkCtx({
async readFile() {
return null
}
})
await run(ctx, ['ulimit', '-f', '1M'])
t.is(ctx.exitCode, 1)
t.ok(ctx._errs.join('\n').includes('not supported'))
})
test('ulimit -f invalid reports explicit diagnostic', async (t) => {
const run = await loadBin('ulimit')
const ctx = mkCtx({
async readFile() {
return null
}
})
await run(ctx, ['ulimit', '-f', 'invalid'])
t.is(ctx.exitCode, 1)
t.ok(ctx._errs.join('\n').includes('invalid file size'))
})
test('xargs -P0 maps to bounded parallel mode', async (t) => {
const run = await loadBin('xargs')
let calls = 0
const ctx = mkCtx({
env: { BARE_OS_XARGS_MAX_PROCS: '4' }
})
ctx.shellStdin = 'a b c d'
ctx.runBinCommand = async () => {
calls++
}
await run(ctx, ['xargs', '-P0', '-n1', 'echo'])
t.is(ctx.exitCode, 0)
t.is(calls, 4)
})