(staged as /lib/init/init-main.js); point bundle-kernel-init and verify scripts at the new path. Wire curl, wget, openssl, ssh-keygen, and tar through coreutils and booter host delegates with booter-side CLI helpers; refresh related bins, bare manifest, shell completion, and man DB (kernel + seeder). Add booter support modules for ACL evaluation, audit chain, secret handles, peer admission, replication priority, process table, swarm lifecycle, boot-graph proc, metrics, monotonic time, protomux alias registry, and swarm peer policy; extend extension resolver, VFS, swarm connection managers, IPC, identity-account, and initd. Harden bare-os-bare-libs build on esbuild failure; add verify scripts for extension manifest schema and runtime incomplete markers; extend ctx API typings, gen-ctx-client-stub, and verify-ctx-dts. Update boot hook fragment, bundled init.js, handbook and reference docs (incl. kernel security and VFS path classes).
4324 lines
130 KiB
JavaScript
4324 lines
130 KiB
JavaScript
import test from 'brittle'
|
|
import b4a from 'b4a'
|
|
import Hyperdrive from 'hyperdrive'
|
|
import Corestore from 'corestore'
|
|
import { mkdirSync, rmSync } from 'node:fs'
|
|
import { readFile, readdir } from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { PassThrough } from 'node:stream'
|
|
import { runKernelFromSource, runBinCommand } from './lib/kernel-runner.js'
|
|
import { createGitFsFromVfs } from './lib/git-fs-adapter.js'
|
|
import { runGitCli } from './lib/git-cli.js'
|
|
import { createStreamLineReader } from './lib/cli-readline.js'
|
|
import {
|
|
createVfs,
|
|
BARE_OS_PROC_FILE_TO_ID_HYPERCORE_PACK_HRPC_LIFECYCLE
|
|
} from './lib/vfs.js'
|
|
import { buildBareOsHypercorePackHrpcLifecycleProcJson } from './lib/bare-os-proc-hypercore-pack-hrpc-lifecycle.js'
|
|
import { createBareOsIpc } from './lib/bare-os-ipc.js'
|
|
import { bareOsHttpUrlAllowed } from './lib/bare-os-http-policy.js'
|
|
import { raceWithAbortAndTimeout } from './lib/bare-os-abort.js'
|
|
import {
|
|
tokenize,
|
|
expandWord,
|
|
execShellLine,
|
|
expandArgvAliases,
|
|
defaultShellAliases,
|
|
loadBarerc,
|
|
BARERC_SKELETON,
|
|
splitTokensBySemicolon,
|
|
splitTokensByAndOr,
|
|
BARE_OS_EXIT_STATUS_ENV,
|
|
syncBareOsExitStatusEnv,
|
|
getBareOsPipelineLimits,
|
|
DEFAULT_PIPELINE_MAX_STAGES
|
|
} from './lib/shell.js'
|
|
import {
|
|
applyBareOsThemeFromEnv,
|
|
bareOsListThemeNames
|
|
} from './lib/bare-os-theme-presets.js'
|
|
import {
|
|
bareParseLsColors,
|
|
bareSerializeLsColors,
|
|
bareLsColorOpenSgrFromMap,
|
|
bareDefaultDircolorsDatabase,
|
|
bareParseDircolorsDatabase
|
|
} from 'bare-os-lscolors'
|
|
import { BARE_OS_CTX_API_VERSION } from './lib/bare-os-ctx-api.js'
|
|
import { buildBareOsRuntimeCaps } from './lib/bare-os-runtime-caps.js'
|
|
import {
|
|
bareOsBareModulesEnabled,
|
|
buildBareCtxObjectFromHost,
|
|
loadBareModuleManifest,
|
|
maybeMergeBareFromDrive
|
|
} from './lib/bare-os-ctx-bare.js'
|
|
import {
|
|
fuzzyMatch,
|
|
stripAnsi,
|
|
parseHistoryFile,
|
|
formatHistoryFile,
|
|
dedupeConsecutiveHistory,
|
|
searchHistoryEntries
|
|
} from './lib/fish-readline.js'
|
|
import {
|
|
fieldMatches,
|
|
dowFieldMatches,
|
|
parseCronLine,
|
|
jobMatchesDate
|
|
} from './lib/bare-cron.js'
|
|
import {
|
|
getBareServiceRuntime,
|
|
registerBareInitdDisposer,
|
|
registerKernelShutdownHook,
|
|
runKernelShutdownHooks,
|
|
startBareInitd,
|
|
stopBareInitd
|
|
} from './lib/bare-initd.js'
|
|
import {
|
|
DEFAULT_CURL_USER_AGENT,
|
|
DEFAULT_WGET_USER_AGENT
|
|
} from './lib/http-fetch-url.js'
|
|
import { filenameFromContentDisposition } from './lib/curl-cli.js'
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
|
|
function headerFromInit(init, name) {
|
|
if (!init?.headers) return undefined
|
|
const h = init.headers
|
|
if (typeof h.get === 'function') return h.get(name)
|
|
return undefined
|
|
}
|
|
|
|
function testCorestoreDir(name) {
|
|
const dir = path.join(
|
|
__dirname,
|
|
'.test-data',
|
|
name + '-' + process.pid + '-' + Math.random().toString(36).slice(2)
|
|
)
|
|
mkdirSync(path.dirname(dir), { recursive: true })
|
|
return dir
|
|
}
|
|
|
|
function testCtx(drive, personal, env) {
|
|
const shellEnv = {
|
|
HOME: '/home/user',
|
|
PATH: '/bin',
|
|
USER: 'user',
|
|
UID: '1000',
|
|
GID: '1000',
|
|
PWD: '/home/user',
|
|
BARE_OS_EXIT_STATUS: '0',
|
|
...env
|
|
}
|
|
const bareOsIpc = createBareOsIpc()
|
|
const vfs = createVfs(drive, personal, shellEnv, null, { bareOsIpc })
|
|
const ctx = {
|
|
drive,
|
|
personalDrive: personal,
|
|
vfs,
|
|
bareOsIpc,
|
|
env: shellEnv,
|
|
console,
|
|
b4a,
|
|
async bareOsApplyTheme() {
|
|
return applyBareOsThemeFromEnv(this)
|
|
},
|
|
bareOsListThemes() {
|
|
return bareOsListThemeNames()
|
|
}
|
|
}
|
|
return ctx
|
|
}
|
|
|
|
/** Personal-drive path backing logical `/home/<seg>/…` (matches `vfs.js`). */
|
|
function personalHomeBacking(homePath, rel = '') {
|
|
const seg = homePath.replace(/^\/home\//, '').split('/')[0] || 'user'
|
|
const r = String(rel).replace(/^\//, '')
|
|
return r ? `/.bare-os/home/${seg}/${r}` : `/.bare-os/home/${seg}`
|
|
}
|
|
|
|
test('kernel_program proc JSON includes program metadata', async (t) => {
|
|
const o = buildBareOsHypercorePackHrpcLifecycleProcJson(
|
|
'kernel_program',
|
|
{ BARE_OS_BOOT_SAFE_MODE: '1' }
|
|
)
|
|
t.is(o.schema, 2)
|
|
t.is(o.program, 'bare-os-kernel-program')
|
|
t.ok(o.operatorSketches && typeof o.operatorSketches === 'object')
|
|
t.ok(o.hooks && o.hooks.bootSafeMode === true)
|
|
})
|
|
|
|
test('giant_phase_program proc id returns same builder as kernel_program', async (t) => {
|
|
const a = buildBareOsHypercorePackHrpcLifecycleProcJson('kernel_program', {})
|
|
const b = buildBareOsHypercorePackHrpcLifecycleProcJson('giant_phase_program', {})
|
|
t.is(a.program, b.program)
|
|
t.is(a.schema, b.schema)
|
|
})
|
|
|
|
test('stock kernel BARE_OS_BOOT_SAFE_MODE skips kernel.ext.d scripts', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
let extRan = false
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/bare-os/kernel.ext.d/01-a.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
id: 'a',
|
|
scripts: ['/lib/bare-os/extensions/x.js']
|
|
})
|
|
)
|
|
}
|
|
return null
|
|
},
|
|
async *readdir(d) {
|
|
if (d === '/etc/bare-os/kernel.ext.d') yield '01-a.json'
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: { BARE_OS_BOOT_SAFE_MODE: '1' },
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {} },
|
|
readLine: async () => null,
|
|
async execLine() {
|
|
return 'ok'
|
|
},
|
|
async bareOsRunImageScript() {
|
|
extRan = true
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.is(extRan, false)
|
|
})
|
|
|
|
test('runKernelFromSource invokes start(ctx)', async (t) => {
|
|
const calls = []
|
|
const source = `
|
|
async function start(ctx) {
|
|
ctx.calls.push('ok')
|
|
}
|
|
`
|
|
await runKernelFromSource(source, { calls })
|
|
t.is(calls[0], 'ok')
|
|
})
|
|
|
|
test('stock kernel init.js onboot runs multiple BARE_OS_ONBOOT lines when SKIP_REPL', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
const execLines = []
|
|
const drive = {
|
|
async get() {
|
|
return null
|
|
},
|
|
async *readdir() {
|
|
/* empty */
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: { BARE_OS_ONBOOT: 'echo one\n#c\n echo two' },
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {} },
|
|
readLine: async () => null,
|
|
async execLine(line) {
|
|
execLines.push(String(line).trim())
|
|
return 'ok'
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.alike(execLines, ['echo one', 'echo two'])
|
|
})
|
|
|
|
test('stock kernel init.js runs rc.local before onboot', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
const execLines = []
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/bare-os/rc.local') return b4a.from('echo rc-local')
|
|
return null
|
|
},
|
|
async *readdir() {
|
|
/* empty */
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: { BARE_OS_ONBOOT: 'echo onboot-line' },
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {} },
|
|
readLine: async () => null,
|
|
async execLine(line) {
|
|
execLines.push(String(line).trim())
|
|
return 'ok'
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.is(execLines[0], 'echo rc-local')
|
|
t.is(execLines[1], 'echo onboot-line')
|
|
})
|
|
|
|
test('stock kernel init.js runs kernel.d after rc.local before onboot', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
const execLines = []
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/bare-os/rc.local') return b4a.from('echo rc-local')
|
|
if (p === '/etc/bare-os/kernel.d/05-k.mod') return b4a.from('echo kmod')
|
|
return null
|
|
},
|
|
async *readdir(dir) {
|
|
if (dir === '/etc/bare-os/kernel.d') yield '05-k.mod'
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: { BARE_OS_ONBOOT: 'echo onboot-line' },
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {} },
|
|
readLine: async () => null,
|
|
async execLine(line) {
|
|
execLines.push(String(line).trim())
|
|
return 'ok'
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.is(execLines[0], 'echo rc-local')
|
|
t.is(execLines[1], 'echo kmod')
|
|
t.is(execLines[2], 'echo onboot-line')
|
|
})
|
|
|
|
test('stock kernel init.js BARE_OS_BOOT_TRACE=json emits boot trace JSON on stderr', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
const stderr = []
|
|
const drive = {
|
|
async get() {
|
|
return null
|
|
},
|
|
async *readdir() {
|
|
/* empty */
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: false,
|
|
env: { BARE_OS_BOOT_TRACE: 'json' },
|
|
drive,
|
|
b4a,
|
|
console: {
|
|
log() {},
|
|
error(...a) {
|
|
stderr.push(a.join(' '))
|
|
}
|
|
},
|
|
readLine: async () => null,
|
|
async execLine() {
|
|
return 'ok'
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
const phases = stderr
|
|
.map((l) => {
|
|
try {
|
|
return JSON.parse(l)
|
|
} catch {
|
|
return null
|
|
}
|
|
})
|
|
.filter(Boolean)
|
|
t.ok(phases.length >= 3)
|
|
t.ok(phases.some((o) => o.phase === 'rc.local'))
|
|
t.ok(phases.some((o) => o.step === 'rc.local'))
|
|
t.ok(phases.some((o) => o.phase === 'kernel.d'))
|
|
t.ok(phases.some((o) => o.step === 'kernel.d'))
|
|
t.ok(phases.every((o) => o.bootTraceSchemaVersion === 2))
|
|
t.ok(phases.every((o) => typeof o.ms === 'number'))
|
|
})
|
|
|
|
test('stock kernel BARE_OS_RC_D_SKIP skips matching rc.d snippets', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
const execLines = []
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/bare-os/rc.d/05-skip.me')
|
|
return b4a.from('echo skipped-should-not-run')
|
|
if (p === '/etc/bare-os/rc.d/10-keep') return b4a.from('echo kept')
|
|
return null
|
|
},
|
|
async *readdir(dir) {
|
|
if (dir === '/etc/bare-os/rc.d') {
|
|
yield '05-skip.me'
|
|
yield '10-keep'
|
|
}
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: { BARE_OS_ONBOOT: 'echo tail', BARE_OS_RC_D_SKIP: '05-*' },
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {} },
|
|
readLine: async () => null,
|
|
async execLine(line) {
|
|
execLines.push(String(line).trim())
|
|
return 'ok'
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.ok(!execLines.some((l) => l.includes('skipped')))
|
|
t.is(execLines[0], 'echo kept')
|
|
t.is(execLines[1], 'echo tail')
|
|
})
|
|
|
|
test('stock kernel BARE_OS_BOOT_STRICT stops boot after execLine throw', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
const execLines = []
|
|
let exitArg = null
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/bare-os/rc')
|
|
return b4a.from('echo first\n__throw__\necho third')
|
|
return null
|
|
},
|
|
async *readdir() {
|
|
/* empty */
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: { BARE_OS_BOOT_STRICT: '1' },
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {} },
|
|
readLine: async () => null,
|
|
requestBooterExit(code) {
|
|
exitArg = code
|
|
},
|
|
async execLine(line) {
|
|
const t0 = String(line).trim()
|
|
if (t0 === '__throw__') throw new Error('boot-fail')
|
|
execLines.push(t0)
|
|
return 'ok'
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.alike(execLines, ['echo first'])
|
|
t.is(exitArg, 1)
|
|
})
|
|
|
|
test('runBinCommand runs /bin helper', async (t) => {
|
|
const dir = testCorestoreDir('bin')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('p'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put(
|
|
'/bin/hello',
|
|
b4a.from(`
|
|
async function run(ctx, argv) {
|
|
ctx.out.push(argv.join(' '))
|
|
}
|
|
`)
|
|
)
|
|
const out = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.out = out
|
|
await runBinCommand(ctx, ['hello', 'a', 'b'])
|
|
t.is(out[0], 'hello a b')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand runs bare hello.js from cwd on personal drive', async (t) => {
|
|
const dir = testCorestoreDir('barejs')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pj'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', 'hello.js'),
|
|
b4a.from(`
|
|
async function run(ctx, argv) {
|
|
ctx.out.push(argv.join(' '))
|
|
}
|
|
`)
|
|
)
|
|
const out = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.out = out
|
|
await runBinCommand(ctx, ['hello.js', 'x', 'y'])
|
|
t.is(out[0], 'hello.js x y')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand strips shebang from user script', async (t) => {
|
|
const dir = testCorestoreDir('shebang')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('psh'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', 'x.js'),
|
|
b4a.from(`#!/usr/bin/env bare
|
|
async function run(ctx) { ctx.out.push('ok') }
|
|
`)
|
|
)
|
|
const out = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.out = out
|
|
await runBinCommand(ctx, ['./x.js'])
|
|
t.is(out[0], 'ok')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand runs top-level user script without run()', async (t) => {
|
|
const dir = testCorestoreDir('norun')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pnr'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', 'plain.js'),
|
|
b4a.from(`ctx.out.push('only-top')`)
|
|
)
|
|
const out = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.out = out
|
|
await runBinCommand(ctx, ['./plain.js'])
|
|
t.is(out[0], 'only-top')
|
|
t.is(ctx.exitCode, 0)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand runs top-level then run() when both present', async (t) => {
|
|
const dir = testCorestoreDir('bothrun')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pbr'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', 'hybrid.js'),
|
|
b4a.from(`ctx.out.push('first')
|
|
async function run(ctx) { ctx.out.push('second') }
|
|
`)
|
|
)
|
|
const out = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.out = out
|
|
await runBinCommand(ctx, ['./hybrid.js'])
|
|
t.alike(out, ['first', 'second'])
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand user script error is caught and logged', async (t) => {
|
|
const dir = testCorestoreDir('throwjs')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('ptj'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', 'bad.js'),
|
|
b4a.from(`async function run() { test() }`)
|
|
)
|
|
const errs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: () => {},
|
|
error: (...a) => {
|
|
errs.push(a.join(' '))
|
|
}
|
|
}
|
|
await runBinCommand(ctx, ['./bad.js'])
|
|
t.ok(errs.length > 0)
|
|
t.ok(errs.some((e) => /test|not defined|ReferenceError/i.test(e)))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('ls hides dotfiles unless -a', async (t) => {
|
|
const lsPath = path.join(__dirname, '../../kernel/bin/ls')
|
|
const lsSrc = await readFile(lsPath, 'utf8')
|
|
const dir = testCorestoreDir('lsdot')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pls'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/ls', b4a.from(lsSrc))
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', 'shown.txt'),
|
|
b4a.from('')
|
|
)
|
|
await personal.put(personalHomeBacking('/home/user', '.hidden'), b4a.from(''))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (s) => lines.push(String(s)),
|
|
error: (...a) => lines.push(a.join(' '))
|
|
}
|
|
await runBinCommand(ctx, ['ls'])
|
|
const flat = lines.join('\n')
|
|
t.ok(flat.includes('shown.txt'))
|
|
t.ok(!flat.includes('hidden'))
|
|
lines.length = 0
|
|
await runBinCommand(ctx, ['ls', '-a'])
|
|
const flatA = lines.join('\n')
|
|
t.ok(flatA.includes('hidden'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('createStreamLineReader yields lines after newline', async (t) => {
|
|
const stdin = new PassThrough()
|
|
const chunks = []
|
|
const stdout = {
|
|
write(s) {
|
|
chunks.push(String(s))
|
|
}
|
|
}
|
|
const readLine = createStreamLineReader(stdin, stdout)
|
|
const p = readLine('> ')
|
|
stdin.write('help\n')
|
|
t.is(await p, 'help')
|
|
t.ok(chunks.join('').includes('> '))
|
|
stdin.end()
|
|
})
|
|
|
|
test('Hyperdrive roundtrips /boot/init.js on Corestore', async (t) => {
|
|
const dir = testCorestoreDir('seed')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
await drive.ready()
|
|
await drive.put('/boot/init.js', b4a.from('async function start() {}'))
|
|
const buf = await drive.get('/boot/init.js')
|
|
t.ok(buf)
|
|
t.is(b4a.toString(buf), 'async function start() {}')
|
|
await drive.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs lstat personal file includes mode uid user mtime', async (t) => {
|
|
const dir = testCorestoreDir('lstatposix')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('lsp'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const vfs = createVfs(sys, personal, {
|
|
HOME: '/home/user',
|
|
PWD: '/home/user',
|
|
PATH: '/bin',
|
|
USER: 'alice',
|
|
UID: '4242',
|
|
GID: '4242'
|
|
})
|
|
await vfs.writeFile('f', b4a.from('x'))
|
|
const st = await vfs.lstat('/home/user/f')
|
|
t.ok(st)
|
|
t.is(st.type, 'file')
|
|
t.is(st.user, 'alice')
|
|
t.is(st.uid, 4242)
|
|
t.ok((st.mode & 0o777) <= 0o777)
|
|
t.ok((st.mode & 0o100000) === 0o100000)
|
|
t.ok(typeof st.mtimeMs === 'number')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('ls -l long listing uses session user and regular file mode', async (t) => {
|
|
const lsPath = path.join(__dirname, '../../kernel/bin/ls')
|
|
const lsSrc = await readFile(lsPath, 'utf8')
|
|
const dir = testCorestoreDir('lslong')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pll'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/ls', b4a.from(lsSrc))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal, {
|
|
USER: 'carol',
|
|
UID: '9001',
|
|
GID: '9001'
|
|
})
|
|
ctx.console = {
|
|
log: (s) => lines.push(String(s)),
|
|
error: (...a) => lines.push(a.join(' '))
|
|
}
|
|
await ctx.vfs.writeFile('shown.txt', b4a.from(''))
|
|
await runBinCommand(ctx, ['ls', '-l', 'shown.txt'])
|
|
const longLine = lines.find((l) => l.includes('shown.txt'))
|
|
t.ok(longLine)
|
|
t.ok(longLine.includes('carol'))
|
|
t.ok(/^-rw/.test(longLine), 'expected regular file mode prefix')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('ls --color=always colors directory blue and executable green', async (t) => {
|
|
const lsPath = path.join(__dirname, '../../kernel/bin/ls')
|
|
const lsSrc = await readFile(lsPath, 'utf8')
|
|
const dir = testCorestoreDir('lscolor')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('plsc'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/ls', b4a.from(lsSrc))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (s) => lines.push(String(s)),
|
|
error: (...a) => lines.push(a.join(' '))
|
|
}
|
|
await ctx.vfs.mkdir('subdir', { recursive: true })
|
|
await ctx.vfs.writeFile('xfile', b4a.from(''))
|
|
await ctx.vfs.chmod('xfile', 0o755)
|
|
await runBinCommand(ctx, ['ls', '--color=always'])
|
|
const shortLine = lines.find(
|
|
(l) => l.includes('subdir') && l.includes('xfile')
|
|
)
|
|
t.ok(shortLine)
|
|
t.ok(
|
|
/\x1b\[[0-9;]*msubdir\x1b\[0m/.test(shortLine),
|
|
'directory uses LS_COLORS di SGR'
|
|
)
|
|
t.ok(
|
|
/\x1b\[[0-9;]*mxfile\x1b\[0m/.test(shortLine),
|
|
'executable uses LS_COLORS ex SGR'
|
|
)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('ls honors NO_COLOR over --color=always', async (t) => {
|
|
const lsPath = path.join(__dirname, '../../kernel/bin/ls')
|
|
const lsSrc = await readFile(lsPath, 'utf8')
|
|
const dir = testCorestoreDir('lscolornc')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('plsn'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/ls', b4a.from(lsSrc))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal, { NO_COLOR: '1' })
|
|
ctx.console = {
|
|
log: (s) => lines.push(String(s)),
|
|
error: (...a) => lines.push(a.join(' '))
|
|
}
|
|
await ctx.vfs.mkdir('onlydir', { recursive: true })
|
|
await runBinCommand(ctx, ['ls', '--color=always'])
|
|
const shortLine = lines.find((l) => l.includes('onlydir'))
|
|
t.ok(shortLine)
|
|
t.ok(!shortLine.includes('\x1b['), 'NO_COLOR strips ANSI')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('ls rejects unknown long option', async (t) => {
|
|
const lsPath = path.join(__dirname, '../../kernel/bin/ls')
|
|
const lsSrc = await readFile(lsPath, 'utf8')
|
|
const dir = testCorestoreDir('lsbadopt')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('plbo'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/ls', b4a.from(lsSrc))
|
|
const errs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: () => {},
|
|
error: (...a) => errs.push(a.join(' '))
|
|
}
|
|
await runBinCommand(ctx, ['ls', '--not-a-real-option'])
|
|
t.is(ctx.exitCode, 2)
|
|
t.ok(errs.some((e) => e.includes('unrecognized')))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs readFile missing path in home does not throw (touch pattern)', async (t) => {
|
|
const dir = testCorestoreDir('vfsreadmiss')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvrm'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const vfs = createVfs(sys, personal, {
|
|
HOME: '/home/zed',
|
|
PWD: '/home/zed',
|
|
PATH: '/bin',
|
|
USER: 'zed',
|
|
UID: '7000',
|
|
GID: '7000'
|
|
})
|
|
const missing = await vfs.readFile('newfile')
|
|
t.is(missing, null)
|
|
await vfs.writeFile('newfile', b4a.from('ok'))
|
|
t.is(b4a.toString(await vfs.readFile('newfile')), 'ok')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs routes HOME to personal drive', async (t) => {
|
|
const dir = testCorestoreDir('vfs')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pv'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const env = { HOME: '/home/user', PWD: '/home/user', PATH: '/bin' }
|
|
const vfs = createVfs(sys, personal, env)
|
|
await vfs.writeFile('f.txt', b4a.from('hi'))
|
|
const buf = await personal.get(personalHomeBacking('/home/user', 'f.txt'))
|
|
t.ok(buf)
|
|
t.is(b4a.toString(buf), 'hi')
|
|
const sysTry = await sys.get('/home/user/f.txt')
|
|
t.is(sysTry, null)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs chdir / and stat $HOME root (Hyperdrive rejects entry("/"))', async (t) => {
|
|
const dir = testCorestoreDir('vfsroot')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pv2'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const env = { HOME: '/home/user', PWD: '/home/user', PATH: '/bin' }
|
|
const vfs = createVfs(sys, personal, env)
|
|
await vfs.chdir('/')
|
|
t.is(vfs.getcwd(), '/')
|
|
const st = await vfs.stat('/home/user')
|
|
t.is(st.type, 'directory')
|
|
await vfs.chdir('/home/user')
|
|
t.is(vfs.getcwd(), '/home/user')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs expands ~ and ~/ to $HOME (not read-only /~)', async (t) => {
|
|
const dir = testCorestoreDir('vfstilde')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pv3'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const env = { HOME: '/home/zuser', PWD: '/home/zuser', PATH: '/bin' }
|
|
const vfs = createVfs(sys, personal, env)
|
|
await vfs.chdir('/')
|
|
await vfs.chdir('~')
|
|
t.is(vfs.getcwd(), '/home/zuser')
|
|
await vfs.writeFile('~/tilde.txt', b4a.from('ok'))
|
|
const buf = await personal.get(
|
|
personalHomeBacking('/home/zuser', 'tilde.txt')
|
|
)
|
|
t.ok(buf)
|
|
t.is(b4a.toString(buf), 'ok')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs lists virtual /home at root; /home shows only active session dir', async (t) => {
|
|
const dir = testCorestoreDir('vfshomevirt')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvh'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const envGuest = { HOME: '/home/guest', PWD: '/home/guest', PATH: '/bin' }
|
|
const vfsG = createVfs(sys, personal, envGuest)
|
|
const rootG = await vfsG.readdir('/')
|
|
t.ok(rootG.includes('home'))
|
|
t.ok(rootG.includes('bin') || rootG.includes('boot') || rootG.length >= 1)
|
|
t.alike(await vfsG.readdir('/home'), ['guest'])
|
|
const envUser = {
|
|
HOME: '/home/eeb18de988e9',
|
|
PWD: '/home/eeb18de988e9',
|
|
PATH: '/bin'
|
|
}
|
|
const vfsU = createVfs(sys, personal, envUser)
|
|
t.alike(await vfsU.readdir('/home'), ['eeb18de988e9'])
|
|
t.is(await vfsU.stat('/home/guest'), null)
|
|
await vfsU.writeFile('/home/eeb18de988e9/x', b4a.from('1'))
|
|
const buf = await personal.get(personalHomeBacking('/home/eeb18de988e9', 'x'))
|
|
t.ok(buf)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs /proc /sys read-only pseudo files; write rejected', async (t) => {
|
|
const dir = testCorestoreDir('vfsproc')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvproc'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const env = {
|
|
HOME: '/home/guest',
|
|
PWD: '/home/guest',
|
|
PATH: '/bin',
|
|
USER: 'guest',
|
|
BARE_OS_CTX_API_VERSION: '9.9.9-test'
|
|
}
|
|
const mntMap = new Map()
|
|
const bareOsIpc = createBareOsIpc()
|
|
const vfs = createVfs(
|
|
sys,
|
|
personal,
|
|
env,
|
|
{ getMounts: () => mntMap },
|
|
{
|
|
procSnapshot: { version: '1.2.3-test', cmdline: 'unit-test' },
|
|
bootStartedMs: Date.now() - 4000,
|
|
bootProfileText: () => 'mini\n',
|
|
sessionText: () => 'test-session-id\n',
|
|
initdRunText: () => 'demo-unit\tactive\t1\tdemo\n',
|
|
procBareOsSwarmText: () => '{}\n',
|
|
bareOsIpc
|
|
}
|
|
)
|
|
const root = await vfs.readdir('/')
|
|
t.ok(root.includes('proc'))
|
|
t.ok(root.includes('sys'))
|
|
t.ok(root.includes('tmp'))
|
|
t.ok(root.includes('run'))
|
|
t.ok(root.includes('dev'))
|
|
t.alike(await vfs.readdir('/proc').then((a) => [...a].sort()), [
|
|
'bare_os',
|
|
'bare_os_activity_queue_depth.json',
|
|
'bare_os_async_hooks_lag.json',
|
|
'bare_os_autobase_writer_hint.json',
|
|
'bare_os_autopass_rotation_sketch.json',
|
|
'bare_os_autopass_session_sketch.json',
|
|
'bare_os_bare_addon_policy.json',
|
|
'bare_os_bare_boot_phase_map.json',
|
|
'bare_os_bare_crypto_policy.json',
|
|
'bare_os_bare_daemon_hooks.json',
|
|
'bare_os_bare_diagnostics_channel.json',
|
|
'bare_os_bare_inspect_policy.json',
|
|
'bare_os_bare_ipc_bridge.json',
|
|
'bare_os_bare_kit_bridge.json',
|
|
'bare_os_bare_logger_policy.json',
|
|
'bare_os_bare_module_resolution.json',
|
|
'bare_os_bare_net_interfaces.json',
|
|
'bare_os_bare_pack_cache.json',
|
|
'bare_os_bare_performance_counters.json',
|
|
'bare_os_bare_rpc_registry_sketch.json',
|
|
'bare_os_bare_signals_mask.json',
|
|
'bare_os_bare_signals_profile.json',
|
|
'bare_os_bare_storage_quota.json',
|
|
'bare_os_bare_stream_backpressure.json',
|
|
'bare_os_bare_thread_pool.json',
|
|
'bare_os_bare_timers_budget.json',
|
|
'bare_os_bare_timers_histogram.json',
|
|
'bare_os_bare_tls_session_hint.json',
|
|
'bare_os_bare_vm_sandbox_sketch.json',
|
|
'bare_os_bare_worker_pool.json',
|
|
'bare_os_bare_ws_gateway_sketch.json',
|
|
'bare_os_blind_pairing_sketch.json',
|
|
'bare_os_blind_relay_router.json',
|
|
'bare_os_bootstrap',
|
|
'bare_os_brittle_snapshot_ci.json',
|
|
'bare_os_broadcast_encryption_hint.json',
|
|
'bare_os_build_attestation_pointer.json',
|
|
'bare_os_bundle_preload_hint.json',
|
|
'bare_os_capabilities',
|
|
'bare_os_capabilities.json',
|
|
'bare_os_cellery_sidecar_hint.json',
|
|
'bare_os_compact_encoding_profile.json',
|
|
'bare_os_corestore_gc_hint.json',
|
|
'bare_os_debug.json',
|
|
'bare_os_delegate_red.json',
|
|
'bare_os_dht_status.json',
|
|
'bare_os_dns_map_active.json',
|
|
'bare_os_drive_resolve_cache.json',
|
|
'bare_os_drive_version_graph.json',
|
|
'bare_os_extensions.json',
|
|
'bare_os_features',
|
|
'bare_os_form_data_delegate_limits.json',
|
|
'bare_os_form_data_delegate_limits_v2.json',
|
|
'bare_os_giant_phase_program.json',
|
|
'bare_os_gip_transport_sketch.json',
|
|
'bare_os_git_delegate_stats.json',
|
|
'bare_os_git_lfs_budget.json',
|
|
'bare_os_git_lfs_pointer_stats.json',
|
|
'bare_os_hdms_health.json',
|
|
'bare_os_hdms_hints.json',
|
|
'bare_os_host_os.json',
|
|
'bare_os_hrpc_allowlist_sketch.json',
|
|
'bare_os_hrpc_bridge_health.json',
|
|
'bare_os_http_dht_proxy_route.json',
|
|
'bare_os_hyper_multisig_trust_pointer.json',
|
|
'bare_os_hypercore_lengths.json',
|
|
'bare_os_hypercore_repair_hint.json',
|
|
'bare_os_hypercore_replicate_budget.json',
|
|
'bare_os_hypercore_signing_status.json',
|
|
'bare_os_hyperdb_readonly_index.json',
|
|
'bare_os_hyperdrive_sparse_index.json',
|
|
'bare_os_hypermininet_topology.json',
|
|
'bare_os_indexer_catchup.json',
|
|
'bare_os_initd_dag.json',
|
|
'bare_os_initd_graph.json',
|
|
'bare_os_ipc_backpressure.json',
|
|
'bare_os_kernel_program.json',
|
|
'bare_os_libmqjs_queue_depth.json',
|
|
'bare_os_locale.json',
|
|
'bare_os_manifest_hints',
|
|
'bare_os_metrics_live.json',
|
|
'bare_os_multisig_quorum_pointer.json',
|
|
'bare_os_net_qos_class.json',
|
|
'bare_os_net_summary.json',
|
|
'bare_os_oidc_publishing_pointer.json',
|
|
'bare_os_pear_api_allowlist_sketch.json',
|
|
'bare_os_pear_appling_manifest.json',
|
|
'bare_os_pear_build_fingerprint.json',
|
|
'bare_os_pear_doctor_state.json',
|
|
'bare_os_pear_drop_events.json',
|
|
'bare_os_pear_ipc_health.json',
|
|
'bare_os_pear_radio_state.json',
|
|
'bare_os_pear_rti_pointer.json',
|
|
'bare_os_pear_runtime_matrix.json',
|
|
'bare_os_pear_sidecar_bundle_index.json',
|
|
'bare_os_pear_stage_pointer.json',
|
|
'bare_os_pear_trust.json',
|
|
'bare_os_pear_updater_state.json',
|
|
'bare_os_pear_user_dirs_map.json',
|
|
'bare_os_pear_wakeups_schedule.json',
|
|
'bare_os_pear_workshop_flags.json',
|
|
'bare_os_peer_health',
|
|
'bare_os_protomux_backpressure.json',
|
|
'bare_os_protomux_channel_alias_v2.json',
|
|
'bare_os_protomux_channels.json',
|
|
'bare_os_protomux_rpc_pool_health.json',
|
|
'bare_os_provenance',
|
|
'bare_os_quotas',
|
|
'bare_os_react_native_bare_kit.json',
|
|
'bare_os_relay_geo_hint.json',
|
|
'bare_os_replication',
|
|
'bare_os_replication_backpressure.json',
|
|
'bare_os_resources',
|
|
'bare_os_rlimits.json',
|
|
'bare_os_rocksdb_pointer.json',
|
|
'bare_os_safe_sodium_buffer_policy.json',
|
|
'bare_os_sandbox_profile.json',
|
|
'bare_os_sandbox_worker_queue.json',
|
|
'bare_os_security_context.json',
|
|
'bare_os_security_posture.json',
|
|
'bare_os_seed_handshake',
|
|
'bare_os_session_stats',
|
|
'bare_os_sidecar_resource_cap.json',
|
|
'bare_os_slo_hints.json',
|
|
'bare_os_snapshot_hints',
|
|
'bare_os_snapshot_hints.json',
|
|
'bare_os_staging_slot',
|
|
'bare_os_storage_tier_hint.json',
|
|
'bare_os_structured_clone_budget_v2.json',
|
|
'bare_os_structured_clone_profile.json',
|
|
'bare_os_swarm',
|
|
'bare_os_sync_window.json',
|
|
'bare_os_udx_extended.json',
|
|
'bare_os_union',
|
|
'bare_os_updater_download_state.json',
|
|
'bare_os_version',
|
|
'bare_os_virtual_registry',
|
|
'bare_os_wave11_operator_slo_v2.json',
|
|
'bare_os_wave11_peer_qos_sketch.json',
|
|
'bare_os_worker_budget.json',
|
|
'cpuinfo',
|
|
'diskstats',
|
|
'loadavg',
|
|
'meminfo',
|
|
'mounts',
|
|
'net',
|
|
'self',
|
|
'uptime',
|
|
'version'
|
|
])
|
|
t.alike(await vfs.readdir('/proc/self').then((a) => [...a].sort()), [
|
|
'cgroups',
|
|
'cmdline',
|
|
'environ',
|
|
'exe',
|
|
'fd',
|
|
'limits'
|
|
])
|
|
const ver = b4a.toString(await vfs.readFile('/proc/version'))
|
|
t.ok(ver.includes('Bare OS'))
|
|
t.ok(ver.includes('bare_os_ctx_api_version=1.2.3-test'))
|
|
const sysVer = b4a.toString(await vfs.readFile('/sys/fs/bare_os/version'))
|
|
t.ok(sysVer.includes('1.2.3-test'))
|
|
const cmd = b4a.toString(await vfs.readFile('/proc/self/cmdline'))
|
|
t.ok(cmd.includes('unit-test'))
|
|
const mem = b4a.toString(await vfs.readFile('/proc/meminfo'))
|
|
t.ok(mem.includes('MemTotal:'))
|
|
const up = b4a.toString(await vfs.readFile('/proc/uptime'))
|
|
t.ok(/^\d+\.\d+\s+\d+\.\d+/.test(up.trim()))
|
|
const cpu = b4a.toString(await vfs.readFile('/proc/cpuinfo'))
|
|
t.ok(cpu.includes('Bare OS pseudo CPU'))
|
|
const la = b4a.toString(await vfs.readFile('/proc/loadavg'))
|
|
t.ok(/^\d+\.\d+\s+\d+\.\d+\s+\d+\.\d+\s+/.test(la))
|
|
const exe = b4a.toString(await vfs.readFile('/proc/self/exe'))
|
|
t.ok(exe.includes('pseudo inode'))
|
|
const cg = b4a.toString(await vfs.readFile('/proc/self/cgroups'))
|
|
t.ok(cg.includes('bare-os'))
|
|
t.alike(await vfs.readdir('/proc/net').then((a) => [...a].sort()), [
|
|
'dev',
|
|
'tcp',
|
|
'udp'
|
|
])
|
|
const tcp = b4a.toString(await vfs.readFile('/proc/net/tcp'))
|
|
t.ok(tcp.includes('local_address'))
|
|
const udp = b4a.toString(await vfs.readFile('/proc/net/udp'))
|
|
t.ok(udp.includes('sl'))
|
|
let mountsTxt = b4a.toString(await vfs.readFile('/proc/mounts'))
|
|
t.ok(mountsTxt.includes('bare-os-root'))
|
|
t.ok(mountsTxt.includes(' / hyperdrive '))
|
|
mntMap.set('vol', { drive: sys, writable: true })
|
|
mountsTxt = b4a.toString(await vfs.readFile('/proc/mounts'))
|
|
t.ok(mountsTxt.includes('/mnt/vol'))
|
|
t.ok(mountsTxt.includes('bare-os-vol'))
|
|
t.alike(await vfs.readdir('/dev').then((a) => [...a].sort()), [
|
|
'null',
|
|
'urandom',
|
|
'zero'
|
|
])
|
|
const ur = await vfs.readFile('/dev/urandom')
|
|
t.ok(ur && ur.byteLength === 4096)
|
|
await vfs.writeFile('/dev/null', b4a.from('gone'))
|
|
t.is((await vfs.readFile('/dev/null'))?.byteLength ?? 0, 0)
|
|
const z = await vfs.readFile('/dev/zero')
|
|
t.ok(z && z.byteLength === 65536)
|
|
t.alike(await vfs.readdir('/run/bare-os').then((a) => [...a].sort()), [
|
|
'boot.json',
|
|
'boot_profile',
|
|
'ipc',
|
|
'ready',
|
|
'session',
|
|
'unit-journal',
|
|
'units',
|
|
'virtual'
|
|
])
|
|
t.alike(await vfs.readdir('/run/bare-os/ipc'), [])
|
|
bareOsIpc.create('q1')
|
|
t.alike(await vfs.readdir('/run/bare-os/ipc'), ['q1'])
|
|
const readP = vfs.readFile('/run/bare-os/ipc/q1')
|
|
const t0 = Date.now()
|
|
await vfs.writeFile('/run/bare-os/ipc/q1', b4a.from('ping'))
|
|
t.is(b4a.toString(await readP), 'ping')
|
|
t.ok(Date.now() - t0 < 60000, 'kernel ipc ping-pong completes')
|
|
await vfs.unlink('/run/bare-os/ipc/q1')
|
|
t.alike(await vfs.readdir('/run/bare-os/ipc'), [])
|
|
t.is(b4a.toString(await vfs.readFile('/run/bare-os/boot_profile')), 'mini\n')
|
|
t.is(
|
|
b4a.toString(await vfs.readFile('/run/bare-os/session')),
|
|
'test-session-id\n'
|
|
)
|
|
const units = b4a.toString(await vfs.readFile('/run/bare-os/units'))
|
|
t.ok(units.includes('demo-unit'))
|
|
let writeErr = null
|
|
try {
|
|
await vfs.writeFile('/proc/version', b4a.from('x'))
|
|
} catch (e) {
|
|
writeErr = e
|
|
}
|
|
t.ok(writeErr)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs BARE_OS_HIDE_PROC_HYPERCORE_PACK_HRPC_LIFECYCLE=0 hides wave11 proc nodes', async (t) => {
|
|
const dir = testCorestoreDir('vfsprocw11')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvprocw11'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const env = {
|
|
HOME: '/home/guest',
|
|
PWD: '/home/guest',
|
|
PATH: '/bin',
|
|
USER: 'guest',
|
|
BARE_OS_HIDE_PROC_HYPERCORE_PACK_HRPC_LIFECYCLE: '0'
|
|
}
|
|
const mntMap = new Map()
|
|
const vfs = createVfs(sys, personal, env, { getMounts: () => mntMap }, {
|
|
procBareOsSwarmText: () => '{}\n'
|
|
})
|
|
const flatW11 = Object.keys(BARE_OS_PROC_FILE_TO_ID_HYPERCORE_PACK_HRPC_LIFECYCLE).map((k) =>
|
|
k.endsWith('.json') ? k : `${k}.json`
|
|
)
|
|
const bareOsW11 = Object.keys(BARE_OS_PROC_FILE_TO_ID_HYPERCORE_PACK_HRPC_LIFECYCLE).map((k) => {
|
|
const base = k.startsWith('bare_os_') ? k.slice('bare_os_'.length) : k
|
|
return base.endsWith('.json') ? base : `${base}.json`
|
|
})
|
|
const procRoot = await vfs.readdir('/proc').then((a) => [...a])
|
|
for (const n of flatW11) {
|
|
t.ok(!procRoot.includes(n), `flat ${n} should be hidden`)
|
|
}
|
|
const bo = await vfs.readdir('/proc/bare_os').then((a) => [...a])
|
|
for (const n of bareOsW11) {
|
|
t.ok(!bo.includes(n), `bare_os/${n} should be hidden`)
|
|
}
|
|
t.is(
|
|
await vfs.readFile('/proc/bare_os_hypercore_replicate_budget.json'),
|
|
null
|
|
)
|
|
const idxRaw = b4a.toString(await vfs.readFile('/proc/bare_os/index.json'))
|
|
const idx = JSON.parse(idxRaw)
|
|
t.ok(Array.isArray(idx.entries))
|
|
const names = new Set(idx.entries.map((e) => e.name))
|
|
t.ok(!names.has('hypercore_replicate_budget.json'))
|
|
t.ok(!names.has('wave11_peer_qos_sketch.json'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs /proc/self/environ omits secret-like env keys', async (t) => {
|
|
const dir = testCorestoreDir('vfsprocenv')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvpe'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const env = {
|
|
HOME: '/home/guest',
|
|
PWD: '/home/guest',
|
|
PATH: '/bin',
|
|
USER: 'guest',
|
|
MY_PASSWORD: 'hunter2',
|
|
BARE_OS_SAFE: 'visible'
|
|
}
|
|
const vfs = createVfs(sys, personal, env)
|
|
const raw = b4a.toString(await vfs.readFile('/proc/self/environ'))
|
|
t.ok(raw.includes('HOME='))
|
|
t.ok(raw.includes('BARE_OS_SAFE='))
|
|
t.ok(!raw.includes('MY_PASSWORD'))
|
|
t.ok(!raw.includes('hunter2'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs /tmp maps to personal drive per HOME basename', async (t) => {
|
|
const dir = testCorestoreDir('vfstmp')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvtmp'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const vfsGuest = createVfs(sys, personal, {
|
|
HOME: '/home/guest',
|
|
PWD: '/home/guest',
|
|
PATH: '/bin'
|
|
})
|
|
await vfsGuest.writeFile('/tmp/session.dat', b4a.from('g'))
|
|
t.is(b4a.toString(await personal.get('/.bare-os/tmp/guest/session.dat')), 'g')
|
|
const vfsUser = createVfs(sys, personal, {
|
|
HOME: '/home/hexuserdead',
|
|
PWD: '/home/hexuserdead',
|
|
PATH: '/bin'
|
|
})
|
|
t.is(await vfsUser.readFile('/tmp/session.dat'), null)
|
|
await vfsUser.writeFile('/tmp/session.dat', b4a.from('u'))
|
|
t.is(
|
|
b4a.toString(await personal.get('/.bare-os/tmp/hexuserdead/session.dat')),
|
|
'u'
|
|
)
|
|
t.is(b4a.toString(await personal.get('/.bare-os/tmp/guest/session.dat')), 'g')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs isolates home and /var/log per HOME basename on personal drive', async (t) => {
|
|
const dir = testCorestoreDir('vfsisol')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvis'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const vfsGuest = createVfs(sys, personal, {
|
|
HOME: '/home/guest',
|
|
PWD: '/home/guest',
|
|
PATH: '/bin'
|
|
})
|
|
await vfsGuest.writeFile('shared-name.txt', b4a.from('guest-data'))
|
|
await vfsGuest.writeFile('/var/log/bare-os/g.log', b4a.from('glog'))
|
|
const vfsUser = createVfs(sys, personal, {
|
|
HOME: '/home/alice12hex',
|
|
PWD: '/home/alice12hex',
|
|
PATH: '/bin'
|
|
})
|
|
t.is(await vfsUser.readFile('/home/alice12hex/shared-name.txt'), null)
|
|
await vfsUser.writeFile('shared-name.txt', b4a.from('user-data'))
|
|
t.is(
|
|
b4a.toString(
|
|
await personal.get(personalHomeBacking('/home/guest', 'shared-name.txt'))
|
|
),
|
|
'guest-data'
|
|
)
|
|
t.is(
|
|
b4a.toString(
|
|
await personal.get(
|
|
personalHomeBacking('/home/alice12hex', 'shared-name.txt')
|
|
)
|
|
),
|
|
'user-data'
|
|
)
|
|
t.is(
|
|
b4a.toString(await personal.get('/.bare-os/var/log/guest/bare-os/g.log')),
|
|
'glog'
|
|
)
|
|
await vfsUser.writeFile('/var/log/bare-os/u.log', b4a.from('ulog'))
|
|
t.is(
|
|
b4a.toString(
|
|
await personal.get('/.bare-os/var/log/alice12hex/bare-os/u.log')
|
|
),
|
|
'ulog'
|
|
)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs /mnt lists HDMS mounts and allows writable put', async (t) => {
|
|
const dir = testCorestoreDir('vfsmnt')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvm'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const env = { HOME: '/home/u', PWD: '/home/u', PATH: '/bin' }
|
|
const extra = new Hyperdrive(store.namespace('exm', { writable: true }))
|
|
await extra.ready()
|
|
const mntRef = {
|
|
getMounts: () => new Map([['vault', { drive: extra, writable: true }]])
|
|
}
|
|
const vfs = createVfs(sys, personal, env, mntRef)
|
|
const root = await vfs.readdir('/')
|
|
t.ok(root.includes('mnt'))
|
|
t.alike(await vfs.readdir('/mnt'), ['vault'])
|
|
await vfs.writeFile('/mnt/vault/x.txt', b4a.from('ok'))
|
|
const buf = await extra.get('/x.txt')
|
|
t.ok(buf)
|
|
t.is(b4a.toString(buf), 'ok')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs /var in root readdir; /var/log empty lstat; writes map to personal', async (t) => {
|
|
const dir = testCorestoreDir('vfsvar')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvvar'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const env = { HOME: '/home/u', PWD: '/home/u', PATH: '/bin' }
|
|
const vfs = createVfs(sys, personal, env)
|
|
const root = await vfs.readdir('/')
|
|
t.ok(root.includes('var'))
|
|
t.alike(await vfs.readdir('/var'), ['log'])
|
|
const stLog = await vfs.lstat('/var/log')
|
|
t.ok(stLog)
|
|
t.is(stLog.type, 'directory')
|
|
await vfs.mkdir('/var/log/bare-os', { recursive: true })
|
|
await vfs.writeFile('/var/log/bare-os/test-vfs.log', b4a.from('hello'))
|
|
const buf = await personal.get('/.bare-os/var/log/u/bare-os/test-vfs.log')
|
|
t.ok(buf)
|
|
t.is(b4a.toString(buf), 'hello')
|
|
const sysTry = await sys.get('/.bare-os/var/log/u/bare-os/test-vfs.log')
|
|
t.is(sysTry, null)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs chdir /var/log from home', async (t) => {
|
|
const dir = testCorestoreDir('vfsvarcd')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvcv'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const vfs = createVfs(sys, personal, {
|
|
HOME: '/home/u',
|
|
PWD: '/home/u',
|
|
PATH: '/bin'
|
|
})
|
|
await vfs.chdir('/var/log')
|
|
t.is(vfs.getcwd(), '/var/log')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tokenize handles quotes and ops', async (t) => {
|
|
const tok = tokenize('ls -la | cat > out')
|
|
t.ok(tok.some((x) => x.type === 'op' && x.value === '|'))
|
|
t.ok(tok.some((x) => x.type === 'op' && x.value === '>'))
|
|
const w = tokenize("echo 'a b'")
|
|
const words = w.filter((x) => x.type === 'word').map((x) => x.value)
|
|
t.is(words.join(','), 'echo,a b')
|
|
})
|
|
|
|
test('expandWord reads env', async (t) => {
|
|
t.is(expandWord('x${HOME}y', { HOME: '/h' }), 'x/hy')
|
|
})
|
|
|
|
test('expandWord $? and ${?} use BARE_OS_EXIT_STATUS', async (t) => {
|
|
const env = { [BARE_OS_EXIT_STATUS_ENV]: '7', HOME: '/h' }
|
|
t.is(expandWord('code=$?', env), 'code=7')
|
|
t.is(expandWord('c=${?}', env), 'c=7')
|
|
t.is(expandWord('missing=$?', {}), 'missing=0')
|
|
})
|
|
|
|
test('expandWord param expansion v2 (## %% :=)', async (t) => {
|
|
const base = {
|
|
BARE_OS_SHELL_PARAM_EXPANSION: '1',
|
|
BARE_OS_SHELL_PARAM_EXPANSION_V2: '1',
|
|
P: '/usr/bin/foo',
|
|
Q: 'caba'
|
|
}
|
|
t.is(expandWord('${P##*/}', base), 'foo')
|
|
t.is(expandWord('${P#*/}', base), 'usr/bin/foo')
|
|
t.is(expandWord('${Q%%ba}', base), 'ca')
|
|
t.is(expandWord('${Q%ba}', base), 'ca')
|
|
const assign = { ...base, EMPTY: '' }
|
|
t.is(expandWord('${EMPTY:=set}', assign), 'set')
|
|
t.is(assign.EMPTY, 'set')
|
|
})
|
|
|
|
test('syncBareOsExitStatusEnv and execShellLine update env', async (t) => {
|
|
const dir = testCorestoreDir('exstat')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pex'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put(
|
|
'/bin/false',
|
|
b4a.from(`async function run(ctx) { ctx.exitCode = 1 }`)
|
|
)
|
|
await drive.put(
|
|
'/bin/true',
|
|
b4a.from(`async function run(ctx) { ctx.exitCode = 0 }`)
|
|
)
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.exitCode = 0
|
|
syncBareOsExitStatusEnv(ctx)
|
|
t.is(ctx.vfs.env[BARE_OS_EXIT_STATUS_ENV], '0')
|
|
await execShellLine(ctx, 'false')
|
|
t.is(ctx.vfs.env[BARE_OS_EXIT_STATUS_ENV], '1')
|
|
await execShellLine(ctx, 'true')
|
|
t.is(ctx.vfs.env[BARE_OS_EXIT_STATUS_ENV], '0')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('BARE_OS_CTX_API_VERSION is semver-shaped', async (t) => {
|
|
t.ok(/^\d+\.\d+\.\d+$/.test(BARE_OS_CTX_API_VERSION))
|
|
})
|
|
|
|
test('getBareOsPipelineLimits reads BARE_OS_PIPELINE_* from env', async (t) => {
|
|
const def = getBareOsPipelineLimits({})
|
|
t.is(def.maxStages, DEFAULT_PIPELINE_MAX_STAGES)
|
|
const custom = getBareOsPipelineLimits({
|
|
BARE_OS_PIPELINE_MAX_STAGES: '4',
|
|
BARE_OS_PIPELINE_MAX_BYTES: '100',
|
|
BARE_OS_PIPELINE_MAX_LINES: '20'
|
|
})
|
|
t.is(custom.maxStages, 4)
|
|
t.is(custom.maxBytes, 100)
|
|
t.is(custom.maxLines, 20)
|
|
})
|
|
|
|
test('buildBareOsRuntimeCaps matches ctx API version and pipeline env', async (t) => {
|
|
const caps = buildBareOsRuntimeCaps({
|
|
BARE_OS_PIPELINE_MAX_STAGES: '8',
|
|
BARE_OS_CTX_API_VERSION
|
|
})
|
|
t.is(caps.ctxApiVersion, BARE_OS_CTX_API_VERSION)
|
|
t.is(caps.pipeline.maxStages, 8)
|
|
t.ok(Array.isArray(caps.pseudoFsPaths))
|
|
t.ok(caps.pseudoFsPaths.includes('/proc/version'))
|
|
t.ok(caps.pseudoFsPaths.includes('/run/bare-os/boot_profile'))
|
|
t.ok(caps.pseudoFsPaths.includes('/run/bare-os/ipc'))
|
|
t.ok(caps.pseudoFsPaths.includes('/run/bare-os/session'))
|
|
t.ok(caps.pseudoFsPaths.includes('/proc/mounts'))
|
|
t.ok(caps.pseudoFsPaths.includes('/proc/bare_os_resources'))
|
|
t.ok(caps.pseudoFsPaths.includes('/run/bare-os/virtual'))
|
|
t.ok(caps.pseudoFsPaths.includes('/run/bare-os/unit-journal'))
|
|
t.ok(caps.pseudoFsPaths.includes('/snapshots'))
|
|
t.ok(caps.pseudoFsPaths.includes('/snapshots/system'))
|
|
t.is(caps.features.simulatedPipelines, true)
|
|
t.is(caps.features.httpDelegate, true)
|
|
t.is(caps.features.gitDelegate, true)
|
|
t.is(caps.features.systemctlDelegate, true)
|
|
t.is(caps.features.bareCtxModules, true)
|
|
t.is(caps.features.bareDriveBundles, true)
|
|
t.is(caps.features.bareHostImportsForCtx, true)
|
|
const capsOff = buildBareOsRuntimeCaps({ BARE_OS_BARE_MODULES: '0' })
|
|
t.is(capsOff.features.bareCtxModules, false)
|
|
t.is(capsOff.features.bareDriveBundles, false)
|
|
t.is(capsOff.features.bareHostImportsForCtx, false)
|
|
const capsIso = buildBareOsRuntimeCaps({ BARE_OS_BARE_HOST_IMPORTS: '0' })
|
|
t.is(capsIso.features.bareHostImportsForCtx, false)
|
|
t.is(capsIso.features.bareCtxModules, true)
|
|
})
|
|
|
|
test('bareOsBareModulesEnabled respects BARE_OS_BARE_MODULES', async (t) => {
|
|
t.ok(bareOsBareModulesEnabled({}))
|
|
t.ok(!bareOsBareModulesEnabled({ BARE_OS_BARE_MODULES: '0' }))
|
|
t.ok(!bareOsBareModulesEnabled({ BARE_OS_BARE_MODULES: 'false' }))
|
|
})
|
|
|
|
test('loadBareModuleManifest has entries', async (t) => {
|
|
const m = loadBareModuleManifest()
|
|
t.ok(m.version >= 1)
|
|
t.ok(Array.isArray(m.entries))
|
|
t.ok(m.entries.some((e) => e.ctxKey === 'b4a'))
|
|
})
|
|
|
|
test('buildBareCtxObjectFromHost loads core keys on Node', async (t) => {
|
|
const target = {}
|
|
await buildBareCtxObjectFromHost({}, target)
|
|
t.ok(target.b4a)
|
|
t.ok(target.compactEncoding)
|
|
t.ok(target.protomux)
|
|
})
|
|
|
|
test('maybeMergeBareFromDrive fills missing keys from bundle (mock vfs)', async (t) => {
|
|
const repoRoot = path.join(
|
|
fileURLToPath(new URL('.', import.meta.url)),
|
|
'..',
|
|
'..'
|
|
)
|
|
const bundleAbs = path.join(repoRoot, 'kernel/lib/bare/bundles/b4a.js')
|
|
const bundleSrc = await readFile(bundleAbs)
|
|
const manifest = {
|
|
version: 1,
|
|
bundles: [{ path: '/lib/bare/bundles/b4a.js', keys: ['b4a'] }]
|
|
}
|
|
const target = {}
|
|
const vfs = {
|
|
async readFile(p) {
|
|
if (p === '/lib/bare/manifest.json')
|
|
return b4a.from(JSON.stringify(manifest))
|
|
if (p === '/lib/bare/bundles/b4a.js') return new Uint8Array(bundleSrc)
|
|
return null
|
|
}
|
|
}
|
|
await maybeMergeBareFromDrive({}, vfs, target)
|
|
t.ok(target.b4a)
|
|
})
|
|
|
|
test('expandArgvAliases expands first word and keeps trailing argv', async (t) => {
|
|
t.alike(expandArgvAliases(['ll', 'z'], defaultShellAliases()), [
|
|
'ls',
|
|
'-la',
|
|
'z'
|
|
])
|
|
})
|
|
|
|
test('expandArgvAliases leaves sed unchanged', async (t) => {
|
|
t.alike(
|
|
expandArgvAliases(['sed', 's/a/b/', 'x.txt'], defaultShellAliases()),
|
|
['sed', 's/a/b/', 'x.txt']
|
|
)
|
|
})
|
|
|
|
test('defaultShellAliases does not remap sed', async (t) => {
|
|
t.is(defaultShellAliases().sed, undefined)
|
|
})
|
|
|
|
test('defaultShellAliases top maps to baretop', async (t) => {
|
|
t.is(defaultShellAliases().top, 'baretop')
|
|
})
|
|
|
|
test('defaultShellAliases btop maps to baretop', async (t) => {
|
|
t.is(defaultShellAliases().btop, 'baretop')
|
|
})
|
|
|
|
test('defaultShellAliases nano maps to edit', async (t) => {
|
|
t.is(defaultShellAliases().nano, 'edit')
|
|
t.alike(expandArgvAliases(['nano', 'x'], defaultShellAliases()), [
|
|
'edit',
|
|
'x'
|
|
])
|
|
})
|
|
|
|
test('expandArgvAliases throws on cyclic alias chain', async (t) => {
|
|
const cyclic = { a: 'b', b: 'a' }
|
|
t.exception(
|
|
() => expandArgvAliases(['a'], cyclic),
|
|
/alias: expansion nested too deeply/
|
|
)
|
|
})
|
|
|
|
test('loadBarerc applies export and alias from personal ~/.barerc', async (t) => {
|
|
const dir = testCorestoreDir('barerc')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('brc'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', '.barerc'),
|
|
b4a.from('export MYRC=1\nalias dog=echo woof\n')
|
|
)
|
|
const ctx = testCtx(drive, personal)
|
|
await loadBarerc(ctx)
|
|
t.is(ctx.vfs.env.MYRC, '1')
|
|
t.is(ctx.shellAliases.dog, 'echo woof')
|
|
t.is(ctx.shellAliases.ll, 'ls -la')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('loadBarerc createSkeletonIfMissing writes ~/.barerc when absent', async (t) => {
|
|
const dir = testCorestoreDir('barercskel')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('brcsk'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
await loadBarerc(ctx, { createSkeletonIfMissing: true })
|
|
const back = await ctx.vfs.readFile('~/.barerc')
|
|
t.ok(back)
|
|
t.is(ctx.b4a.toString(back), BARERC_SKELETON)
|
|
t.is(ctx.shellAliases.ll, 'ls -la')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('loadBarerc theme nord sets REPL color env and LS_COLORS', async (t) => {
|
|
const dir = testCorestoreDir('barerctheme')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('brcth'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', '.barerc'),
|
|
b4a.from('theme nord\n')
|
|
)
|
|
const ctx = testCtx(drive, personal)
|
|
await loadBarerc(ctx)
|
|
t.is(ctx.vfs.env.BARE_OS_THEME, 'nord')
|
|
t.ok(
|
|
String(ctx.vfs.env.BARE_OS_COLOR_PROMPT || '').includes('38;2;'),
|
|
'nord preset uses truecolor prompt'
|
|
)
|
|
t.ok(
|
|
String(ctx.vfs.env.LS_COLORS || '').includes('di='),
|
|
'LS_COLORS set from preset'
|
|
)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine barerc reload reapplies barerc', async (t) => {
|
|
const dir = testCorestoreDir('barercrel')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('brcrel'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', '.barerc'),
|
|
b4a.from('export MARK=before\n')
|
|
)
|
|
const ctx = testCtx(drive, personal)
|
|
await loadBarerc(ctx)
|
|
t.is(ctx.vfs.env.MARK, 'before')
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', '.barerc'),
|
|
b4a.from('export MARK=after\n')
|
|
)
|
|
await execShellLine(ctx, 'barerc reload')
|
|
t.is(ctx.vfs.env.MARK, 'after')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('bareParseLsColors roundtrip', async (t) => {
|
|
const s = 'di=01;34:ln=36:ex=32:*.tar=01;31'
|
|
const m = bareParseLsColors(s)
|
|
t.is(m.di, '01;34')
|
|
t.is(m.ln, '36')
|
|
t.alike(bareParseLsColors(bareSerializeLsColors(m)), m)
|
|
})
|
|
|
|
test('bareLsColorOpenSgrFromMap directory and glob', async (t) => {
|
|
const map = bareParseLsColors('di=01;34:*.md=00;32')
|
|
const d = bareLsColorOpenSgrFromMap(
|
|
{ type: 'directory', mode: 0o040755 },
|
|
'foo',
|
|
map
|
|
)
|
|
t.ok(d.startsWith('\x1b['), 'directory colored')
|
|
const md = bareLsColorOpenSgrFromMap(
|
|
{ type: 'file', mode: 0o100644 },
|
|
'README.md',
|
|
map
|
|
)
|
|
t.ok(md.includes('32'), 'markdown glob')
|
|
})
|
|
|
|
test('bareLsColorOpenSgrFromMap mh for multi-link regular file', async (t) => {
|
|
const map = bareParseLsColors('mh=01;44:fi=40;31')
|
|
const one = bareLsColorOpenSgrFromMap(
|
|
{ type: 'file', mode: 0o100644, nlink: 1 },
|
|
'a',
|
|
map
|
|
)
|
|
const two = bareLsColorOpenSgrFromMap(
|
|
{ type: 'file', mode: 0o100644, nlink: 2 },
|
|
'a',
|
|
map
|
|
)
|
|
t.ok(two.includes('44'), 'nlink>1 uses mh SGR')
|
|
t.ok(one.includes('31'), 'nlink 1 uses fi')
|
|
})
|
|
|
|
test('bareLsColorOpenSgrFromMap ca when stat has capabilities', async (t) => {
|
|
const map = bareParseLsColors('ca=30;41:fi=00')
|
|
const sgr = bareLsColorOpenSgrFromMap(
|
|
{ type: 'file', mode: 0o100644, nlink: 1, capabilities: true },
|
|
'cap',
|
|
map
|
|
)
|
|
t.ok(sgr.includes('41'), 'capabilities use ca')
|
|
})
|
|
|
|
test('applyBareOsThemeFromEnv BARE_OS_COLOR_DEPTH=256 drops truecolor', async (t) => {
|
|
const ctx = {
|
|
vfs: { env: { BARE_OS_THEME: 'nord', BARE_OS_COLOR_DEPTH: '256' } }
|
|
}
|
|
await applyBareOsThemeFromEnv(ctx)
|
|
const p = String(ctx.vfs.env.BARE_OS_COLOR_PROMPT || '')
|
|
t.ok(p.includes('38;5;'), '256-color palette index SGR')
|
|
t.ok(!p.includes('38;2;'), 'no RGB truecolor')
|
|
})
|
|
|
|
test('dircolors -p includes TERM and di', async (t) => {
|
|
const db = bareDefaultDircolorsDatabase()
|
|
t.ok(db.includes('TERM'))
|
|
t.ok(db.includes('di '))
|
|
})
|
|
|
|
test('bareParseDircolorsDatabase TERM block', async (t) => {
|
|
const text = 'TERM xterm\ndi 01;34\nTERM none\nfi 00\nTERM *\nln 01;36\n'
|
|
const m = bareParseDircolorsDatabase(text, 'xterm')
|
|
t.is(m.di, '01;34')
|
|
t.is(m.ln, '01;36')
|
|
})
|
|
|
|
test('runBinCommand theme set writes barerc and updates env', async (t) => {
|
|
const themePath = path.join(__dirname, '../../kernel/bin/theme')
|
|
const themeSrc = await readFile(themePath, 'utf8')
|
|
const dir = testCorestoreDir('themecmd')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('thcmd'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/theme', b4a.from(themeSrc))
|
|
const ctx = testCtx(drive, personal)
|
|
await loadBarerc(ctx)
|
|
await runBinCommand(ctx, ['theme', 'set', 'dracula'])
|
|
t.is(ctx.vfs.env.BARE_OS_THEME, 'dracula')
|
|
const barc = ctx.b4a.toString(await ctx.vfs.readFile('~/.barerc'))
|
|
t.ok(barc.includes('theme dracula'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand dircolors -p prints database', async (t) => {
|
|
const dcPath = path.join(__dirname, '../../kernel/bin/dircolors')
|
|
const dcSrc = await readFile(dcPath, 'utf8')
|
|
const dir = testCorestoreDir('dircolp')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('dcp'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/dircolors', b4a.from(dcSrc))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (s) => lines.push(String(s)),
|
|
error: (...a) => lines.push(a.join(' '))
|
|
}
|
|
await runBinCommand(ctx, ['dircolors', '-p'])
|
|
const out = lines.join('\n')
|
|
t.ok(out.includes('TERM'))
|
|
t.ok(out.includes('di'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand edit --help prints usage', async (t) => {
|
|
const editPath = path.join(__dirname, '../../kernel/bin/edit')
|
|
const editSrc = await readFile(editPath, 'utf8')
|
|
const dir = testCorestoreDir('edithlp')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('edh'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/edit', b4a.from(editSrc))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (s) => lines.push(String(s)),
|
|
error: (...a) => lines.push(a.join(' '))
|
|
}
|
|
await runBinCommand(ctx, ['edit', '--help'])
|
|
const out = lines.join('\n')
|
|
t.ok(out.includes('usage:'))
|
|
t.ok(out.includes('TTY'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand edit requires TTY', async (t) => {
|
|
const editPath = path.join(__dirname, '../../kernel/bin/edit')
|
|
const editSrc = await readFile(editPath, 'utf8')
|
|
const dir = testCorestoreDir('ednotty')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('ednt'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/edit', b4a.from(editSrc))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.replStdin = { isTTY: false }
|
|
ctx.console = {
|
|
log: (s) => lines.push(String(s)),
|
|
error: (...a) => lines.push(a.join(' '))
|
|
}
|
|
await runBinCommand(ctx, ['edit', 'x.txt'])
|
|
t.is(ctx.exitCode, 1)
|
|
t.ok(lines.some((l) => l.includes('TTY')))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand baretop --help prints usage', async (t) => {
|
|
const binPath = path.join(__dirname, '../../kernel/bin/baretop')
|
|
const src = await readFile(binPath, 'utf8')
|
|
const dir = testCorestoreDir('baretophlp')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('bth'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/baretop', b4a.from(src))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (s) => lines.push(String(s)),
|
|
error: (...a) => lines.push(a.join(' '))
|
|
}
|
|
await runBinCommand(ctx, ['baretop', '--help'])
|
|
const out = lines.join('\n')
|
|
t.ok(out.includes('usage:'))
|
|
t.ok(out.includes('TTY'))
|
|
t.ok(out.toLowerCase().includes('dashboard'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand baretop requires TTY', async (t) => {
|
|
const binPath = path.join(__dirname, '../../kernel/bin/baretop')
|
|
const src = await readFile(binPath, 'utf8')
|
|
const dir = testCorestoreDir('baretopntty')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('btnt'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/baretop', b4a.from(src))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.replStdin = { isTTY: false }
|
|
ctx.console = {
|
|
log: (s) => lines.push(String(s)),
|
|
error: (...a) => lines.push(a.join(' '))
|
|
}
|
|
await runBinCommand(ctx, ['baretop'])
|
|
t.is(ctx.exitCode, 1)
|
|
t.ok(lines.some((l) => l.includes('TTY')))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand nano --help matches edit bundle', async (t) => {
|
|
const nanoPath = path.join(__dirname, '../../kernel/bin/nano')
|
|
const nanoSrc = await readFile(nanoPath, 'utf8')
|
|
const dir = testCorestoreDir('nanohlp')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('nanh'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/nano', b4a.from(nanoSrc))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (s) => lines.push(String(s)),
|
|
error: (...a) => lines.push(a.join(' '))
|
|
}
|
|
await runBinCommand(ctx, ['nano', '-h'])
|
|
const out = lines.join('\n')
|
|
t.ok(out.includes('usage:'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tokenize leaves echo 2 > file as stdout redirect not 2>', async (t) => {
|
|
const toks = tokenize('echo 2 > /tmp/x')
|
|
const words = toks.filter((x) => x.type === 'word').map((x) => x.value)
|
|
t.ok(words.includes('2'))
|
|
t.ok(toks.some((x) => x.type === 'op' && x.value === '>'))
|
|
t.ok(!toks.some((x) => x.type === 'op' && x.value === '2>'))
|
|
})
|
|
|
|
test('execShellLine stderr 2> and 2>&1 in pipeline', async (t) => {
|
|
const dir = testCorestoreDir('sherr')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('serr'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const emit = `
|
|
async function run(ctx) {
|
|
ctx.console.log('OUT')
|
|
ctx.console.error('ERR')
|
|
}
|
|
`
|
|
const cat = `
|
|
function bareStdin(ctx) {
|
|
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
|
|
}
|
|
async function run(ctx) {
|
|
ctx.console.log(bareStdin(ctx).replace(/\\n$/, ''))
|
|
}
|
|
`
|
|
await drive.put('/bin/emit', b4a.from(emit))
|
|
await drive.put('/bin/cat', b4a.from(cat))
|
|
const ctx = testCtx(drive, personal)
|
|
await execShellLine(ctx, 'emit 2>~/e.out')
|
|
const eb = await ctx.vfs.readFile('~/e.out')
|
|
t.ok(eb && ctx.b4a.toString(eb).includes('ERR'))
|
|
t.ok(!ctx.b4a.toString(eb).includes('OUT'))
|
|
await ctx.vfs.unlink('~/e.out')
|
|
const logs = []
|
|
ctx.console = {
|
|
log: (...a) => logs.push(['out', ...a]),
|
|
error: (...a) => logs.push(['err', ...a])
|
|
}
|
|
await execShellLine(ctx, 'emit 2>&1 | cat')
|
|
const piped = logs
|
|
.filter((x) => x[0] === 'out')
|
|
.map((x) => x.slice(1).join(' '))
|
|
t.ok(piped.some((l) => l.includes('OUT') && l.includes('ERR')))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine pipeline stage and byte limits', async (t) => {
|
|
const dir = testCorestoreDir('shpipe')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('spipe'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const echo = `
|
|
async function run(ctx, argv) {
|
|
ctx.console.log(argv.slice(1).join(' '))
|
|
}
|
|
`
|
|
const cat = `
|
|
function bareStdin(ctx) {
|
|
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
|
|
}
|
|
async function run(ctx) {
|
|
ctx.console.log(bareStdin(ctx).replace(/\\n$/, ''))
|
|
}
|
|
`
|
|
await drive.put('/bin/echo', b4a.from(echo))
|
|
await drive.put('/bin/cat', b4a.from(cat))
|
|
const spam = `
|
|
async function run(ctx) {
|
|
ctx.console.log('z'.repeat(200))
|
|
}
|
|
`
|
|
await drive.put('/bin/spam', b4a.from(spam))
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
ctx.env.BARE_OS_PIPELINE_MAX_STAGES = '2'
|
|
await execShellLine(ctx, 'echo a | echo b | echo c')
|
|
t.is(ctx.exitCode, 1)
|
|
t.ok(logs.some((l) => l.includes('BARE_OS_PIPELINE_MAX_STAGES')))
|
|
logs.length = 0
|
|
ctx.env.BARE_OS_PIPELINE_MAX_STAGES = '32'
|
|
ctx.env.BARE_OS_PIPELINE_MAX_BYTES = '80'
|
|
await execShellLine(ctx, 'spam | cat')
|
|
t.is(ctx.exitCode, 1)
|
|
t.ok(logs.some((l) => l.includes('BARE_OS_PIPELINE_MAX_BYTES')))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('ls bareOsStdoutCaptured lists one name per line', async (t) => {
|
|
const lsPath = path.join(__dirname, '../../kernel/bin/ls')
|
|
const lsSrc = await readFile(lsPath, 'utf8')
|
|
const dir = testCorestoreDir('lscap')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('plcap'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/ls', b4a.from(lsSrc))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (s) => lines.push(String(s)),
|
|
error: (...a) => lines.push(a.join(' '))
|
|
}
|
|
await ctx.vfs.writeFile('aaa', b4a.from(''))
|
|
await ctx.vfs.writeFile('lib', b4a.from(''))
|
|
await ctx.vfs.writeFile('zzz', b4a.from(''))
|
|
await runBinCommand(Object.assign({}, ctx, { bareOsStdoutCaptured: true }), [
|
|
'ls'
|
|
])
|
|
t.is(lines.length, 3)
|
|
t.ok(lines.includes('aaa') && lines.includes('lib') && lines.includes('zzz'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine ls pipe grep prints only matching entry line', async (t) => {
|
|
const lsPath = path.join(__dirname, '../../kernel/bin/ls')
|
|
const grepPath = path.join(__dirname, '../../kernel/bin/grep')
|
|
const lsSrc = await readFile(lsPath, 'utf8')
|
|
const grepSrc = await readFile(grepPath, 'utf8')
|
|
const dir = testCorestoreDir('lspipegrep')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('plpg'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/ls', b4a.from(lsSrc))
|
|
await drive.put('/bin/grep', b4a.from(grepSrc))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (s) => lines.push(String(s)),
|
|
error: (...a) => lines.push(a.join(' '))
|
|
}
|
|
await ctx.vfs.writeFile('aaa', b4a.from(''))
|
|
await ctx.vfs.writeFile('lib', b4a.from(''))
|
|
await ctx.vfs.writeFile('zzz', b4a.from(''))
|
|
await execShellLine(ctx, 'ls | grep -F lib')
|
|
t.is(ctx.exitCode, 0)
|
|
t.alike(lines, ['lib'])
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine ls pipe wc -l counts lines', async (t) => {
|
|
const lsPath = path.join(__dirname, '../../kernel/bin/ls')
|
|
const wcPath = path.join(__dirname, '../../kernel/bin/wc')
|
|
const lsSrc = await readFile(lsPath, 'utf8')
|
|
const wcSrc = await readFile(wcPath, 'utf8')
|
|
const dir = testCorestoreDir('lspipewc')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('plwc'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/ls', b4a.from(lsSrc))
|
|
await drive.put('/bin/wc', b4a.from(wcSrc))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (s) => lines.push(String(s)),
|
|
error: (...a) => lines.push(a.join(' '))
|
|
}
|
|
await ctx.vfs.writeFile('a', b4a.from(''))
|
|
await ctx.vfs.writeFile('b', b4a.from(''))
|
|
await ctx.vfs.writeFile('c', b4a.from(''))
|
|
await execShellLine(ctx, 'ls | wc -l')
|
|
t.is(ctx.exitCode, 0)
|
|
const out = lines.join('\n').trim()
|
|
t.is(out, '3', 'three directory entries => three lines')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine runs cd and external', async (t) => {
|
|
const dir = testCorestoreDir('sh')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('psh'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put(
|
|
'/bin/xy',
|
|
b4a.from(`
|
|
async function run(ctx, argv) {
|
|
ctx.got.push(argv.join(' '))
|
|
}
|
|
`)
|
|
)
|
|
const got = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.got = got
|
|
await execShellLine(ctx, 'cd /bin')
|
|
t.is(ctx.vfs.getcwd(), '/bin')
|
|
await execShellLine(ctx, 'xy one two')
|
|
t.is(got[0], 'xy one two')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine expands default ll to ls -la', async (t) => {
|
|
const dir = testCorestoreDir('llalias')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('lla'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put(
|
|
'/bin/ls',
|
|
b4a.from(`
|
|
async function run(ctx, argv) {
|
|
ctx.got.push(argv.join(' '))
|
|
}
|
|
`)
|
|
)
|
|
const got = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.got = got
|
|
await execShellLine(ctx, 'll one')
|
|
t.is(got[0], 'ls -la one')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tokenize && || ; and split helpers', async (t) => {
|
|
const toks = tokenize('a&&b||c;d')
|
|
t.is(toks.map((x) => x.value).join(' '), 'a && b || c ; d')
|
|
const lists = splitTokensBySemicolon(toks)
|
|
t.is(lists.length, 2)
|
|
const { segments, ops } = splitTokensByAndOr(lists[0])
|
|
t.is(segments.length, 3)
|
|
t.is(ops.join(' '), '&& ||')
|
|
})
|
|
|
|
test('execShellLine && || ; short-circuit and exitCode', async (t) => {
|
|
const dir = testCorestoreDir('shandor')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('sand'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const stub = `
|
|
async function run(ctx, argv) {
|
|
const c = argv[0]
|
|
ctx.ran.push(c)
|
|
ctx.exitCode = c === 'false' ? 1 : 0
|
|
}
|
|
`
|
|
await drive.put('/bin/true', b4a.from(stub))
|
|
await drive.put('/bin/false', b4a.from(stub))
|
|
await drive.put(
|
|
'/bin/rec',
|
|
b4a.from(`
|
|
async function run(ctx, argv) {
|
|
ctx.ran.push('rec')
|
|
ctx.exitCode = 0
|
|
}
|
|
`)
|
|
)
|
|
const ran = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.ran = ran
|
|
ctx.exitCode = 0
|
|
|
|
await execShellLine(ctx, 'false && rec')
|
|
t.is(ran.join(','), 'false')
|
|
t.is(ctx.exitCode, 1)
|
|
|
|
ran.length = 0
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'false || rec')
|
|
t.is(ran.join(','), 'false,rec')
|
|
t.is(ctx.exitCode, 0)
|
|
|
|
ran.length = 0
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'true && rec')
|
|
t.is(ran.join(','), 'true,rec')
|
|
t.is(ctx.exitCode, 0)
|
|
|
|
ran.length = 0
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'true || rec')
|
|
t.is(ran.join(','), 'true')
|
|
t.is(ctx.exitCode, 0)
|
|
|
|
ran.length = 0
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'false || false || rec')
|
|
t.is(ran.join(','), 'false,false,rec')
|
|
t.is(ctx.exitCode, 0)
|
|
|
|
ran.length = 0
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'true && false || rec')
|
|
t.is(ran.join(','), 'true,false,rec')
|
|
t.is(ctx.exitCode, 0)
|
|
|
|
ran.length = 0
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'true; rec')
|
|
t.is(ran.join(','), 'true,rec')
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine if then else fi', async (t) => {
|
|
const dir = testCorestoreDir('shif')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('sif'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put(
|
|
'/bin/echo',
|
|
b4a.from(`
|
|
async function run(ctx, argv) {
|
|
ctx.console.log(argv.slice(1).join(' '))
|
|
}
|
|
`)
|
|
)
|
|
await drive.put(
|
|
'/bin/true',
|
|
b4a.from(`
|
|
async function run(ctx) {
|
|
ctx.exitCode = 0
|
|
}
|
|
`)
|
|
)
|
|
await drive.put(
|
|
'/bin/false',
|
|
b4a.from(`
|
|
async function run(ctx) {
|
|
ctx.exitCode = 1
|
|
}
|
|
`)
|
|
)
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (s) => lines.push(String(s)),
|
|
error: (...a) => lines.push(a.join(' '))
|
|
}
|
|
await execShellLine(ctx, 'if false; then echo no; else echo yes; fi')
|
|
t.ok(lines.some((l) => l.includes('yes')))
|
|
lines.length = 0
|
|
await execShellLine(ctx, 'if true; then echo ok; fi')
|
|
t.ok(lines.some((l) => l.includes('ok')))
|
|
lines.length = 0
|
|
await execShellLine(ctx, 'if false; then echo x; fi')
|
|
t.ok(!lines.some((l) => l.includes('x')))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('filenameFromContentDisposition parses attachment names', async (t) => {
|
|
t.is(
|
|
filenameFromContentDisposition('attachment; filename="a b.txt"'),
|
|
'a b.txt'
|
|
)
|
|
t.is(
|
|
filenameFromContentDisposition("attachment; filename*=UTF-8''x%20y.bin"),
|
|
'x y.bin'
|
|
)
|
|
t.is(filenameFromContentDisposition(null), null)
|
|
})
|
|
|
|
test('cron fieldMatches and dowFieldMatches', async (t) => {
|
|
t.ok(fieldMatches('*', 0, 0, 59))
|
|
t.ok(fieldMatches('*/5', 10, 0, 59))
|
|
t.ok(!fieldMatches('*/5', 11, 0, 59))
|
|
t.ok(fieldMatches('1-3', 2, 0, 59))
|
|
t.ok(!fieldMatches('1-3', 4, 0, 59))
|
|
t.ok(fieldMatches('1,4', 1, 0, 59))
|
|
t.ok(fieldMatches('1,4', 4, 0, 59))
|
|
t.ok(fieldMatches('1-10/2', 3, 0, 59))
|
|
t.ok(!fieldMatches('1-10/2', 4, 0, 59))
|
|
t.ok(dowFieldMatches('7', 0))
|
|
t.ok(!dowFieldMatches('7', 1))
|
|
t.ok(dowFieldMatches('0', 0))
|
|
t.ok(dowFieldMatches('1-5', 3))
|
|
})
|
|
|
|
test('cron parseCronLine and jobMatchesDate', async (t) => {
|
|
t.absent(parseCronLine(''))
|
|
t.absent(parseCronLine('# comment'))
|
|
t.absent(parseCronLine('0 0 * *'))
|
|
const j = parseCronLine('30 14 15 6 * echo hello world')
|
|
t.ok(j)
|
|
t.is(j.command, 'echo hello world')
|
|
const when = new Date(2020, 5, 15, 14, 30, 0)
|
|
t.ok(jobMatchesDate(j, when))
|
|
t.ok(!jobMatchesDate(j, new Date(2020, 5, 15, 14, 31, 0)))
|
|
const reboot = parseCronLine('@reboot echo hi')
|
|
t.ok(reboot)
|
|
t.is(reboot.minute, '@reboot')
|
|
t.is(reboot.command, 'echo hi')
|
|
t.ok(!jobMatchesDate(reboot, when))
|
|
const jit = parseCronLine('@reboot JitterSec=12 /bin/true')
|
|
t.ok(jit)
|
|
t.is(jit.jitterSec, 12)
|
|
t.is(jit.command, '/bin/true')
|
|
const jl = parseCronLine('* * * * * JitterSec=3 echo x')
|
|
t.ok(jl)
|
|
t.is(jl.jitterSec, 3)
|
|
t.is(jl.command, 'echo x')
|
|
})
|
|
|
|
test('systemctl list after startBareInitd shows kernel-logger and bare-cron', async (t) => {
|
|
const dir = testCorestoreDir('initctl')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pinit'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const logs = []
|
|
const ctx = testCtx(sys, personal)
|
|
ctx.execLine = async () => {}
|
|
await startBareInitd(ctx)
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['systemctl', 'list'])
|
|
t.is(ctx.exitCode, 0)
|
|
const text = logs.join('\n')
|
|
t.ok(text.includes('kernel-logger'))
|
|
t.ok(text.includes('bare-cron'))
|
|
stopBareInitd()
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('systemctl list-units delegates to same backend', async (t) => {
|
|
const dir = testCorestoreDir('initctlsys')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pics'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const logs = []
|
|
const ctx = testCtx(sys, personal)
|
|
ctx.execLine = async () => {}
|
|
await startBareInitd(ctx)
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['systemctl', 'list-units'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(logs.join('\n').includes('bare-cron'))
|
|
stopBareInitd()
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('systemctl is-active matches bare-initd runtime', async (t) => {
|
|
const dir = testCorestoreDir('initctlactive')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pica'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const logs = []
|
|
const ctx = testCtx(sys, personal)
|
|
ctx.execLine = async () => {}
|
|
await startBareInitd(ctx)
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['systemctl', 'is-active', 'kernel-logger'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(logs.join('\n').includes('active'))
|
|
logs.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['systemctl', 'is-active', 'nonexistent-unit'])
|
|
t.is(ctx.exitCode, 3)
|
|
stopBareInitd()
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('systemctl restart bare-cron succeeds', async (t) => {
|
|
const dir = testCorestoreDir('initctlrst')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('picr'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const logs = []
|
|
const ctx = testCtx(sys, personal)
|
|
ctx.execLine = async () => {}
|
|
await startBareInitd(ctx)
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['systemctl', 'restart', 'bare-cron'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(logs.some((l) => /restarted bare-cron/i.test(l)))
|
|
stopBareInitd()
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('initd disabled.txt skips unit on boot; systemctl enable undoes', async (t) => {
|
|
const dir = testCorestoreDir('initdis')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pdis'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(sys, personal)
|
|
ctx.execLine = async () => {}
|
|
await ctx.vfs.mkdir('/home/user/.config/bare-os/initd', { recursive: true })
|
|
await ctx.vfs.writeFile(
|
|
'/home/user/.config/bare-os/initd/disabled.txt',
|
|
b4a.from('bare-cron\n')
|
|
)
|
|
await startBareInitd(ctx)
|
|
t.is(getBareServiceRuntime('bare-cron'), undefined)
|
|
t.is(getBareServiceRuntime('kernel-logger')?.phase, 'active')
|
|
stopBareInitd()
|
|
ctx.exitCode = 0
|
|
ctx.console = {
|
|
log: () => {},
|
|
error: () => {}
|
|
}
|
|
await runBinCommand(ctx, ['systemctl', 'enable', 'bare-cron'])
|
|
t.is(ctx.exitCode, 0)
|
|
await startBareInitd(ctx)
|
|
t.is(getBareServiceRuntime('bare-cron')?.phase, 'active')
|
|
stopBareInitd()
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('stopBareInitd runs registered disposers', async (t) => {
|
|
let n = 0
|
|
registerBareInitdDisposer(() => {
|
|
n++
|
|
})
|
|
stopBareInitd()
|
|
t.is(n, 1)
|
|
stopBareInitd()
|
|
t.is(n, 2)
|
|
})
|
|
|
|
test('runKernelShutdownHooks runs LIFO once', async (t) => {
|
|
const o = []
|
|
registerKernelShutdownHook(async () => {
|
|
o.push('a')
|
|
})
|
|
registerKernelShutdownHook(async () => {
|
|
o.push('b')
|
|
})
|
|
await runKernelShutdownHooks()
|
|
t.is(o.join(','), 'b,a')
|
|
await runKernelShutdownHooks()
|
|
t.is(o.join(','), 'b,a')
|
|
})
|
|
|
|
test('fish-readline stripAnsi and fuzzyMatch', async (t) => {
|
|
t.is(stripAnsi('\x1b[32mhi\x1b[0m'), 'hi')
|
|
t.ok(fuzzyMatch('hello', 'hlo'))
|
|
t.ok(!fuzzyMatch('hello', 'hxo'))
|
|
})
|
|
|
|
test('fish-readline history parse format dedupe search', async (t) => {
|
|
const parsed = parseHistoryFile('#1:a\n#2:b\n')
|
|
t.is(parsed.length, 2)
|
|
t.is(parsed[0].command, 'a')
|
|
const round = parseHistoryFile(formatHistoryFile(parsed))
|
|
t.is(round[1].command, 'b')
|
|
const deduped = dedupeConsecutiveHistory([
|
|
{ timestamp: 1, command: 'x' },
|
|
{ timestamp: 2, command: 'x' },
|
|
{ timestamp: 3, command: 'y' }
|
|
])
|
|
t.is(deduped.length, 2)
|
|
t.is(deduped[1].command, 'y')
|
|
const hist = [{ command: 'aa' }, { command: 'ba' }]
|
|
const found = searchHistoryEntries(hist, 'a')
|
|
t.ok(found.includes('ba'))
|
|
t.ok(found.includes('aa'))
|
|
})
|
|
|
|
test('tier-1 cat from system drive', async (t) => {
|
|
const dir = testCorestoreDir('cat')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pc'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/cat', b4a.from(await readBuiltBin('cat')))
|
|
await drive.put('/etc/x', b4a.from('hello'))
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log() {},
|
|
error() {}
|
|
}
|
|
const catOut = []
|
|
const prevCatWrite = process.stdout.write
|
|
process.stdout.write = (chunk) => {
|
|
catOut.push(
|
|
typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8')
|
|
)
|
|
return true
|
|
}
|
|
try {
|
|
await runBinCommand(ctx, ['cat', '/etc/x'])
|
|
} finally {
|
|
process.stdout.write = prevCatWrite
|
|
}
|
|
t.is(catOut.join(''), 'hello')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 tail head options and tail -f poll', async (t) => {
|
|
const dir = testCorestoreDir('tail-head')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pth'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/tail', b4a.from(await readBuiltBin('tail')))
|
|
await drive.put('/bin/head', b4a.from(await readBuiltBin('head')))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal, {
|
|
BARE_OS_TAIL_F_POLL_MS: '25',
|
|
BARE_OS_TAIL_F_MAX_ROUNDS: '8'
|
|
})
|
|
ctx.exitCode = 0
|
|
ctx.bareOsRuntimeCaps = buildBareOsRuntimeCaps(ctx.env)
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error() {}
|
|
}
|
|
await ctx.vfs.writeFile('t.txt', b4a.from('one\ntwo\nthree\n'))
|
|
await runBinCommand(ctx, ['tail', '-n', '2', 't.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines.join('\n'), 'two\nthree')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['tail', '-n', '+2', 't.txt'])
|
|
t.is(lines.join('\n'), 'two\nthree')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['head', '-c', '4', 't.txt'])
|
|
t.is(lines[0], 'one\n')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await ctx.vfs.writeFile('f.log', b4a.from('a\n'))
|
|
const tailP = runBinCommand(ctx, ['tail', '-f', 'f.log'])
|
|
await new Promise((r) => setTimeout(r, 15))
|
|
const cur = b4a.toString(await ctx.vfs.readFile('f.log'))
|
|
await ctx.vfs.writeFile('f.log', b4a.from(cur + 'b\n'))
|
|
await tailP
|
|
t.ok(lines.includes('a'))
|
|
t.ok(lines.includes('b'))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
ctx.shellStdin = 'x\ny\n'
|
|
await runBinCommand(ctx, ['tail', '-f'])
|
|
t.is(ctx.exitCode, 1)
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 grep and mkfifo from system drive', async (t) => {
|
|
const dir = testCorestoreDir('grep')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pg'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/grep', b4a.from(await readBuiltBin('grep')))
|
|
await drive.put('/bin/mkfifo', b4a.from(await readBuiltBin('mkfifo')))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.exitCode = 0
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error() {}
|
|
}
|
|
await ctx.vfs.writeFile(
|
|
'w.txt',
|
|
b4a.from('aaa\nneedle line\nbbb\nneedle line two')
|
|
)
|
|
await runBinCommand(ctx, ['grep', '-F', 'needle', 'w.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines.length, 2)
|
|
t.ok(lines[0].includes('needle line'))
|
|
t.ok(lines[1].includes('needle line two'))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['grep', '-F', 'nomatch', 'w.txt'])
|
|
t.is(ctx.exitCode, 1)
|
|
t.is(lines.length, 0)
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['grep', '-v', '-F', 'needle', 'w.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines.join('\n'), 'aaa\nbbb')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['grep', '-c', '-F', 'needle', 'w.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines[0], '2')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['grep', '-n', '-F', 'bbb', 'w.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines[0], '3:bbb')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
ctx.shellStdin = 'alpha\nbeta\n'
|
|
await runBinCommand(ctx, ['grep', '-F', 'beta'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines[0], 'beta')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['grep', '-x', '-F', 'aaa', 'w.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines[0], 'aaa')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['grep', '-m', '1', '-F', 'needle', 'w.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines.length, 1)
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['grep', '-o', 'ee', 'w.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.includes('ee'))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['mkfifo', '/run/bare-os/ipc/t1'])
|
|
t.is(ctx.exitCode, 0)
|
|
const readP = ctx.vfs.readFile('/run/bare-os/ipc/t1')
|
|
await ctx.vfs.writeFile('/run/bare-os/ipc/t1', b4a.from('ok'))
|
|
t.is(b4a.toString(await readP), 'ok')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['grep', '-F', '-C', '1', 'needle', 'w.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.some((l) => l === 'aaa'))
|
|
t.ok(lines.some((l) => l.includes('needle line')))
|
|
t.ok(lines.some((l) => l === 'bbb'))
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 sort and wc flags', async (t) => {
|
|
const dir = testCorestoreDir('sort-wc')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('psw'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/sort', b4a.from(await readBuiltBin('sort')))
|
|
await drive.put('/bin/wc', b4a.from(await readBuiltBin('wc')))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.exitCode = 0
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error() {}
|
|
}
|
|
await ctx.vfs.writeFile('nums.txt', b4a.from('10\n2\n1\n'))
|
|
await runBinCommand(ctx, ['sort', '-n', 'nums.txt'])
|
|
t.is(lines.join('\n'), '1\n2\n10')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['sort', '-nru', 'nums.txt'])
|
|
t.is(lines.join('\n'), '10\n2\n1')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['wc', '-l', 'nums.txt'])
|
|
t.is(lines[0].trim(), '3 nums.txt')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['wc', '-c', 'nums.txt'])
|
|
t.is(lines[0], ' 7 nums.txt')
|
|
|
|
const errs = []
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error(...a) {
|
|
errs.push(a.join(' '))
|
|
}
|
|
}
|
|
await ctx.vfs.writeFile('ord.txt', b4a.from('a\nb\nc\n'))
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['sort', '-c', 'ord.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(errs.length, 0)
|
|
|
|
await ctx.vfs.writeFile('bad.txt', b4a.from('b\na\n'))
|
|
lines.length = 0
|
|
errs.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['sort', '-c', 'bad.txt'])
|
|
t.is(ctx.exitCode, 1)
|
|
t.ok(errs.some((e) => e.includes('disorder')))
|
|
|
|
errs.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['sort', '-C', 'bad.txt'])
|
|
t.is(ctx.exitCode, 1)
|
|
t.is(errs.length, 0)
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['sort', '-o', 'sorted.txt', '-n', 'nums.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
const sortedBuf = await ctx.vfs.readFile('sorted.txt')
|
|
t.is(ctx.b4a.toString(sortedBuf), '1\n2\n10\n')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['sort', '-s', 'ord.txt'])
|
|
t.is(lines.join('\n'), 'a\nb\nc')
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 find du basename options', async (t) => {
|
|
const dir = testCorestoreDir('fdu-base')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pfdb'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/find', b4a.from(await readBuiltBin('find')))
|
|
await drive.put('/bin/du', b4a.from(await readBuiltBin('du')))
|
|
await drive.put('/bin/basename', b4a.from(await readBuiltBin('basename')))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.exitCode = 0
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error() {}
|
|
}
|
|
await ctx.vfs.writeFile('Lo.txt', b4a.from('ab'))
|
|
await runBinCommand(ctx, ['find', '.', '-iname', 'lo.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.some((p) => /Lo\.txt$/.test(String(p).replace(/\\/g, '/'))))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['du', '-h', 'Lo.txt'])
|
|
t.ok(/^2\tLo\.txt$/.test(lines[0]) || /^3\tLo\.txt$/.test(lines[0]))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['basename', '-a', '/x/a', '/y/b'])
|
|
t.is(lines.join('\n'), 'a\nb')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['basename', 'z.h', '.h'])
|
|
t.is(lines[0], 'z')
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine find pipes to wc -l', async (t) => {
|
|
const dir = testCorestoreDir('findpipewc')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('fpwc'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/find', b4a.from(await readBuiltBin('find')))
|
|
await drive.put('/bin/wc', b4a.from(await readBuiltBin('wc')))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.exitCode = 0
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error() {}
|
|
}
|
|
await ctx.vfs.writeFile('p.txt', b4a.from(''))
|
|
await execShellLine(ctx, 'find . -maxdepth 1 | wc -l')
|
|
t.is(ctx.exitCode, 0)
|
|
const n = Number.parseInt(String(lines[0] || '').trim(), 10)
|
|
t.ok(Number.isFinite(n) && n >= 1)
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 date format and test numeric', async (t) => {
|
|
const dir = testCorestoreDir('date-test')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pdt'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/date', b4a.from(await readBuiltBin('date')))
|
|
await drive.put('/bin/test', b4a.from(await readBuiltBin('test')))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.exitCode = 0
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error() {}
|
|
}
|
|
await runBinCommand(ctx, ['test', '2', '-lt', '5'])
|
|
t.is(ctx.exitCode, 0)
|
|
await runBinCommand(ctx, ['test', '5', '-lt', '2'])
|
|
t.is(ctx.exitCode, 1)
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['date', '+%Y'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(/^\d{4}$/.test(lines[0]))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 find -mindepth skips shallow paths', async (t) => {
|
|
const dir = testCorestoreDir('find-mindepth')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pfindmd'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/find', b4a.from(await readBuiltBin('find')))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.exitCode = 0
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error() {}
|
|
}
|
|
await ctx.vfs.mkdir('nest', { recursive: true })
|
|
await ctx.vfs.mkdir('nest/sub', { recursive: true })
|
|
await ctx.vfs.writeFile('nest/a.txt', b4a.from('a'))
|
|
await ctx.vfs.writeFile('nest/sub/b.txt', b4a.from('b'))
|
|
await runBinCommand(ctx, ['find', '.', '-mindepth', '2', '-type', 'f'])
|
|
t.is(ctx.exitCode, 0)
|
|
const norm = lines.map((p) => p.replace(/\\/g, '/')).sort()
|
|
t.ok(norm.some((p) => p.endsWith('/nest/a.txt')))
|
|
t.ok(norm.some((p) => p.endsWith('/nest/sub/b.txt')))
|
|
lines.length = 0
|
|
await runBinCommand(ctx, ['find', 'nest', '-mindepth', '2', '-type', 'f'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(
|
|
lines.some((p) => String(p).replace(/\\/g, '/').endsWith('/nest/sub/b.txt'))
|
|
)
|
|
t.ok(
|
|
!lines.some((p) => String(p).replace(/\\/g, '/').endsWith('/nest/a.txt'))
|
|
)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 jq from system drive', async (t) => {
|
|
const dir = testCorestoreDir('jq')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pjq'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/jq', b4a.from(await readBuiltBin('jq')))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.exitCode = 0
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error() {}
|
|
}
|
|
await ctx.vfs.writeFile('data.json', b4a.from('{"x":42,"name":"hi"}'))
|
|
await runBinCommand(ctx, ['jq', '.x', 'data.json'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines[0], '42')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['jq', '-c', '.', 'data.json'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines[0], '{"x":42,"name":"hi"}')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
ctx.shellStdin = '{"a":1}'
|
|
await runBinCommand(ctx, ['jq', '.a'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines[0], '1')
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 getconf and xargs from system drive', async (t) => {
|
|
const dir = testCorestoreDir('gxc')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pgx'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/getconf', b4a.from(await readBuiltBin('getconf')))
|
|
await drive.put(
|
|
'/bin/echolog',
|
|
b4a.from(`
|
|
async function run(ctx, argv) {
|
|
ctx.console.log(argv.slice(1).join(' '))
|
|
}
|
|
`)
|
|
)
|
|
await drive.put('/bin/xargs', b4a.from(await readBuiltBin('xargs')))
|
|
await drive.put('/bin/false', b4a.from(await readBuiltBin('false')))
|
|
await drive.put('/bin/printf', b4a.from(await readBuiltBin('printf')))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.runBinCommand = (argv) => runBinCommand(ctx, argv)
|
|
ctx.exitCode = 0
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error(s) {
|
|
lines.push(String(s))
|
|
}
|
|
}
|
|
await runBinCommand(ctx, ['getconf', 'PATH_MAX'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines.pop(), '4096')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['getconf', 'BARE_OS_YES_MAX_LINES'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines.pop(), '100000')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['getconf', 'NOT_A_REAL_CONF_NAME'])
|
|
t.is(ctx.exitCode, 1)
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
ctx.shellStdin = 'hello\tworld\n'
|
|
await runBinCommand(ctx, ['xargs', 'echolog'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines.join('\n'), 'hello world')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
ctx.shellStdin = 'a\0b\0'
|
|
await runBinCommand(ctx, ['xargs', '-0', '-n1', 'echolog'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines.join('\n'), 'a\nb')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
ctx.shellStdin = 'one\ntwo\n'
|
|
await runBinCommand(ctx, ['xargs', '-I', '{}', 'echolog', 'x', '{}', 'y'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines.join('\n'), 'x one y\nx two y')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
ctx.shellStdin = ''
|
|
await execShellLine(ctx, 'false || printf recovered')
|
|
t.is(lines.pop(), 'recovered')
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-2 cat env touch mkdir', async (t) => {
|
|
const dir = testCorestoreDir('tier2tem')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pt2'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
for (const name of ['cat', 'env', 'touch', 'mkdir', 'printf']) {
|
|
await drive.put('/bin/' + name, b4a.from(await readBuiltBin(name)))
|
|
}
|
|
const catOut = []
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal, { MARK: 'parent', HELLO: 'world' })
|
|
ctx.exitCode = 0
|
|
ctx.runBinCommand = function (argv, o) {
|
|
return runBinCommand(this, argv, o)
|
|
}
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error(s) {
|
|
lines.push('e:' + String(s))
|
|
}
|
|
}
|
|
await ctx.vfs.chdir('/home/user')
|
|
await ctx.vfs.writeFile('plain.txt', b4a.from('a\nb\n'))
|
|
|
|
const captureCat = async (argv) => {
|
|
catOut.length = 0
|
|
const prevWrite = process.stdout.write
|
|
process.stdout.write = (chunk) => {
|
|
catOut.push(
|
|
typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8')
|
|
)
|
|
return true
|
|
}
|
|
try {
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, argv)
|
|
} finally {
|
|
process.stdout.write = prevWrite
|
|
}
|
|
}
|
|
|
|
await captureCat(['cat', '-n', 'plain.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(catOut.join(''), ' 1\ta\n 2\tb\n')
|
|
|
|
await captureCat(['cat', '-A', 'plain.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(catOut.join(''), 'a$\nb$\n')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, [
|
|
'env',
|
|
'-i',
|
|
'PATH=/bin',
|
|
'HELLO=there',
|
|
'printf',
|
|
'%s',
|
|
'ok'
|
|
])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines.join('\n'), 'ok')
|
|
t.is(ctx.vfs.env.MARK, 'parent')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['touch', '-d', '@3600', 'ts.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
const stTs = await ctx.vfs.lstat('ts.txt')
|
|
t.is(stTs && stTs.mtimeMs, 3600000)
|
|
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['touch', 'ref.txt'])
|
|
const stRef = await ctx.vfs.lstat('ref.txt')
|
|
t.ok(stRef && typeof stRef.mtimeMs === 'number')
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['touch', '-r', 'ref.txt', 'ts2.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
const st2 = await ctx.vfs.lstat('ts2.txt')
|
|
t.is(st2 && st2.mtimeMs, stRef.mtimeMs)
|
|
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['mkdir', '-m', '700', 'secret_dir'])
|
|
t.is(ctx.exitCode, 0)
|
|
const stD = await ctx.vfs.lstat('secret_dir')
|
|
t.ok(stD && (stD.mode & 0o777) === 0o700)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('printenv -0 uses ctx.bareOsBinWrite when stdout is unavailable', async (t) => {
|
|
const dir = testCorestoreDir('binw')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pbin'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/printenv', b4a.from(await readBuiltBin('printenv')))
|
|
const raw = []
|
|
const ctx = testCtx(drive, personal, { ZZ: 'z', AA: 'a' })
|
|
ctx.exitCode = 0
|
|
ctx.runBinCommand = function (argv, o) {
|
|
return runBinCommand(this, argv, o)
|
|
}
|
|
ctx.bareOsBinWrite = (u) => {
|
|
raw.push(b4a.toString(u instanceof Uint8Array ? u : new Uint8Array(u)))
|
|
}
|
|
ctx.console = { log() {}, error() {} }
|
|
await runBinCommand(ctx, ['printenv', '-0', 'AA', 'ZZ'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(raw.join(''), 'a\0z\0')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('sed and awk golden one-liners', async (t) => {
|
|
const dir = testCorestoreDir('sawk')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('psawk'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/sed', b4a.from(await readBuiltBin('sed')))
|
|
await drive.put('/bin/awk', b4a.from(await readBuiltBin('awk')))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.exitCode = 0
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error() {}
|
|
}
|
|
await ctx.vfs.chdir('/home/user')
|
|
await ctx.vfs.writeFile('f.txt', b4a.from('abc\ndef\n'))
|
|
await runBinCommand(ctx, ['sed', '-e', 's/a/X/', 'f.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines.join('\n'), 'Xbc\ndef')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['awk', '{ print NF }', 'f.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines.join('\n'), '1\n1')
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('curl delegated from booter with stub fetch', async (t) => {
|
|
const dir = testCorestoreDir('curl')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pcurl'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.exitCode = 0
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error(s) {
|
|
lines.push('e:' + String(s))
|
|
}
|
|
}
|
|
/** @type {RequestInit | undefined} */
|
|
let curlInitHello
|
|
ctx.httpFetch = async (_url, init) => {
|
|
curlInitHello = init
|
|
return new Response('hello', {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'text/plain' }
|
|
})
|
|
}
|
|
await runBinCommand(ctx, ['curl', 'https://stub.example/x'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines[0], 'hello')
|
|
t.is(headerFromInit(curlInitHello, 'User-Agent'), DEFAULT_CURL_USER_AGENT)
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
ctx.httpFetch = async () => new Response('', { status: 404 })
|
|
await runBinCommand(ctx, ['curl', '-f', '-s', 'https://stub.example/missing'])
|
|
t.is(ctx.exitCode, 22)
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
let curlSeenUrl = ''
|
|
/** @type {RequestInit | undefined} */
|
|
let curlSeenInit
|
|
ctx.httpFetch = async (url, init) => {
|
|
curlSeenUrl = String(url)
|
|
curlSeenInit = init
|
|
return new Response('', { status: 200 })
|
|
}
|
|
await runBinCommand(ctx, ['curl', 'example.com', '-IL'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(curlSeenUrl, 'http://example.com')
|
|
t.is(curlSeenInit && curlSeenInit.method, 'HEAD')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
/** @type {{ url: string, method: string }[]} */
|
|
const curlHopCalls = []
|
|
ctx.httpFetch = async (url, init) => {
|
|
const u = String(url)
|
|
const method = String(init?.method || 'GET')
|
|
curlHopCalls.push({ url: u, method })
|
|
if (method === 'HEAD' && u === 'http://hop.example/start') {
|
|
return new Response('', {
|
|
status: 302,
|
|
headers: { Location: 'https://hop.example/done' }
|
|
})
|
|
}
|
|
if (method === 'GET' && u === 'https://hop.example/done') {
|
|
return new Response('secret-body', {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'text/plain' }
|
|
})
|
|
}
|
|
return new Response('unexpected', { status: 500 })
|
|
}
|
|
await runBinCommand(ctx, ['curl', '-IL', 'http://hop.example/start'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(curlHopCalls.length, 2)
|
|
t.is(curlHopCalls[0].method, 'HEAD')
|
|
t.is(curlHopCalls[1].method, 'GET')
|
|
t.ok(lines[0].includes('200'))
|
|
t.ok(!lines[0].includes('secret-body'))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
ctx.httpFetch = async (url) => {
|
|
curlSeenUrl = String(url)
|
|
return new Response('', { status: 200 })
|
|
}
|
|
await runBinCommand(ctx, ['curl', '-I', '//stub.example/p'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(curlSeenUrl, 'https://stub.example/p')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
/** @type {RequestInit | undefined} */
|
|
let curlInitUa
|
|
ctx.httpFetch = async (_url, init) => {
|
|
curlInitUa = init
|
|
return new Response('x', { status: 200 })
|
|
}
|
|
await runBinCommand(ctx, [
|
|
'curl',
|
|
'-A',
|
|
'CustomCurlUA/9',
|
|
'https://stub.example/ua'
|
|
])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(headerFromInit(curlInitUa, 'User-Agent'), 'CustomCurlUA/9')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
ctx.httpFetch = async (_url, init) => {
|
|
curlInitUa = init
|
|
return new Response('x', { status: 200 })
|
|
}
|
|
await runBinCommand(ctx, [
|
|
'curl',
|
|
'-H',
|
|
'User-Agent: from-header',
|
|
'-A',
|
|
'from-flag',
|
|
'https://stub.example/ua2'
|
|
])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(headerFromInit(curlInitUa, 'User-Agent'), 'from-flag')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
ctx.httpFetch = async (_url, init) => {
|
|
curlInitUa = init
|
|
return new Response('x', { status: 200 })
|
|
}
|
|
await runBinCommand(ctx, [
|
|
'curl',
|
|
'-A',
|
|
'from-flag',
|
|
'-H',
|
|
'User-Agent: from-header',
|
|
'https://stub.example/ua3'
|
|
])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(headerFromInit(curlInitUa, 'User-Agent'), 'from-header')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
ctx.httpFetch = async () =>
|
|
new Response('body-o', {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'text/plain' }
|
|
})
|
|
await runBinCommand(ctx, [
|
|
'curl',
|
|
'-sO',
|
|
'https://stub.example/dir/named.txt'
|
|
])
|
|
t.is(ctx.exitCode, 0)
|
|
const named = await ctx.vfs.readFile('named.txt')
|
|
t.is(ctx.b4a.toString(named), 'body-o')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['curl', '-o', 'x', '-O', 'https://stub.example/y'])
|
|
t.is(ctx.exitCode, 2)
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
/** @type {{ url: string, method: string }[]} */
|
|
const curlRedirectCountCalls = []
|
|
ctx.httpFetch = async (url, init) => {
|
|
const u = String(url)
|
|
const method = String(init?.method || 'GET')
|
|
curlRedirectCountCalls.push({ url: u, method })
|
|
if (method === 'HEAD' && u === 'http://rc.example/a') {
|
|
return new Response('', {
|
|
status: 302,
|
|
headers: { Location: 'https://rc.example/b' }
|
|
})
|
|
}
|
|
if (method === 'GET' && u === 'https://rc.example/b') {
|
|
return new Response('', { status: 200 })
|
|
}
|
|
return new Response('bad', { status: 500 })
|
|
}
|
|
await runBinCommand(ctx, [
|
|
'curl',
|
|
'-s',
|
|
'-w',
|
|
'%{num_redirects}',
|
|
'-o',
|
|
'hdr.dump',
|
|
'-IL',
|
|
'http://rc.example/a'
|
|
])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines[lines.length - 1], '1')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
/** @type {RequestInit | undefined} */
|
|
let curlCookieInit
|
|
ctx.httpFetch = async (_url, init) => {
|
|
curlCookieInit = init
|
|
return new Response('c', { status: 200 })
|
|
}
|
|
await runBinCommand(ctx, [
|
|
'curl',
|
|
'-b',
|
|
'sid=abc',
|
|
'https://stub.example/cookie-inline'
|
|
])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(headerFromInit(curlCookieInit, 'Cookie'), 'sid=abc')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await ctx.vfs.writeFile(
|
|
'/home/user/cookies.json',
|
|
b4a.from(JSON.stringify({ 'stub.example': { x: 'y' } }), 'utf8')
|
|
)
|
|
ctx.httpFetch = async (_url, init) => {
|
|
curlCookieInit = init
|
|
return new Response('c', { status: 200 })
|
|
}
|
|
await runBinCommand(ctx, [
|
|
'curl',
|
|
'-b',
|
|
'/home/user/cookies.json',
|
|
'https://stub.example/cookie-file'
|
|
])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(String(headerFromInit(curlCookieInit, 'Cookie')).includes('x=y'))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
ctx.httpFetch = async () =>
|
|
new Response('z', {
|
|
status: 200,
|
|
headers: { 'Set-Cookie': 'sess=hello; Path=/' }
|
|
})
|
|
await runBinCommand(ctx, [
|
|
'curl',
|
|
'-c',
|
|
'/home/user/jar.json',
|
|
'https://stub.example/jar'
|
|
])
|
|
t.is(ctx.exitCode, 0)
|
|
const jraw = await ctx.vfs.readFile('/home/user/jar.json')
|
|
t.ok(jraw && ctx.b4a.toString(jraw).includes('sess'))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
/** @type {{ insecure?: boolean, caPem?: string } | null} */
|
|
let curlTlsSeen = null
|
|
ctx.httpFetch = async (_url, init) => {
|
|
curlTlsSeen = init && init.bareOsCurlTls
|
|
return new Response('z', { status: 200 })
|
|
}
|
|
await runBinCommand(ctx, ['curl', '-k', 'https://stub.example/insecure'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(curlTlsSeen && curlTlsSeen.insecure === true)
|
|
t.is(lines[0], 'z')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await ctx.vfs.writeFile(
|
|
'/home/user/ca-test.pem',
|
|
b4a.from('-----BEGIN CERTIFICATE-----\nTEST\n-----END CERTIFICATE-----\n')
|
|
)
|
|
let cacertInit = null
|
|
ctx.httpFetch = async (_url, init) => {
|
|
cacertInit = init && init.bareOsCurlTls
|
|
return new Response('pem-ok', { status: 200 })
|
|
}
|
|
await runBinCommand(ctx, [
|
|
'curl',
|
|
'--cacert',
|
|
'/home/user/ca-test.pem',
|
|
'https://stub.example/ca'
|
|
])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(
|
|
cacertInit &&
|
|
typeof cacertInit.caPem === 'string' &&
|
|
cacertInit.caPem.includes('BEGIN CERTIFICATE')
|
|
)
|
|
t.is(lines[0], 'pem-ok')
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('curl --connect-timeout aborts hanging stub fetch', async (t) => {
|
|
const dir = testCorestoreDir('curlcto')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pccto'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.exitCode = 0
|
|
ctx.console = { log() {}, error() {} }
|
|
ctx.httpFetch = async (_url, init) =>
|
|
new Promise((_resolve, reject) => {
|
|
const sig = init && init.signal
|
|
if (sig && sig.aborted) {
|
|
reject(new Error('The operation was aborted'))
|
|
return
|
|
}
|
|
if (sig) {
|
|
sig.addEventListener('abort', () =>
|
|
reject(new Error('The operation was aborted'))
|
|
)
|
|
}
|
|
})
|
|
await runBinCommand(ctx, [
|
|
'curl',
|
|
'--connect-timeout',
|
|
'1',
|
|
'https://stub.example/hang'
|
|
])
|
|
t.is(ctx.exitCode, 7)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('wget delegated from booter with stub fetch', async (t) => {
|
|
const dir = testCorestoreDir('wget')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pwget'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.exitCode = 0
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error(s) {
|
|
lines.push('e:' + String(s))
|
|
}
|
|
}
|
|
/** @type {RequestInit | undefined} */
|
|
let wgetInitPayload
|
|
ctx.httpFetch = async (_url, init) => {
|
|
wgetInitPayload = init
|
|
return new Response('payload', {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'text/plain' }
|
|
})
|
|
}
|
|
await runBinCommand(ctx, [
|
|
'wget',
|
|
'-O',
|
|
'saved.txt',
|
|
'-q',
|
|
'https://stub.example/a'
|
|
])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(headerFromInit(wgetInitPayload, 'User-Agent'), DEFAULT_WGET_USER_AGENT)
|
|
const out = await ctx.vfs.readFile('saved.txt')
|
|
t.ok(out)
|
|
t.is(ctx.b4a.toString(out), 'payload')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
ctx.httpFetch = async () => new Response('', { status: 500 })
|
|
await runBinCommand(ctx, ['wget', '-q', 'https://stub.example/err'])
|
|
t.is(ctx.exitCode, 8)
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
ctx.httpFetch = async () => new Response('stdout-body', { status: 200 })
|
|
await runBinCommand(ctx, ['wget', '-O', '-', '-q', 'https://stub.example/b'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines[0], 'stdout-body')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
let wgetSeenUrl = ''
|
|
ctx.httpFetch = async (url) => {
|
|
wgetSeenUrl = String(url)
|
|
return new Response('p2', {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'text/plain' }
|
|
})
|
|
}
|
|
await runBinCommand(ctx, ['wget', 'stub.example/z', '-qO', 'w2.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(wgetSeenUrl, 'http://stub.example/z')
|
|
const w2 = await ctx.vfs.readFile('w2.txt')
|
|
t.ok(w2)
|
|
t.is(ctx.b4a.toString(w2), 'p2')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
/** @type {RequestInit | undefined} */
|
|
let wgetInitU
|
|
ctx.httpFetch = async (_url, init) => {
|
|
wgetInitU = init
|
|
return new Response('u', { status: 200 })
|
|
}
|
|
await runBinCommand(ctx, [
|
|
'wget',
|
|
'-q',
|
|
'-U',
|
|
'WgetCustom/1',
|
|
'-O',
|
|
'u.txt',
|
|
'https://stub.example/u'
|
|
])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(headerFromInit(wgetInitU, 'User-Agent'), 'WgetCustom/1')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['wget', '-c', '-O', '-', 'https://stub.example/n'])
|
|
t.is(ctx.exitCode, 2)
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await ctx.vfs.writeFile('partial.bin', b4a.from('ab'))
|
|
ctx.httpFetch = async (_url, init) => {
|
|
t.is(headerFromInit(init, 'Range'), 'bytes=2-')
|
|
return new Response('cde', {
|
|
status: 206,
|
|
headers: { 'Content-Type': 'application/octet-stream' }
|
|
})
|
|
}
|
|
await runBinCommand(ctx, [
|
|
'wget',
|
|
'-q',
|
|
'-c',
|
|
'-O',
|
|
'partial.bin',
|
|
'https://stub.example/resume'
|
|
])
|
|
t.is(ctx.exitCode, 0)
|
|
const merged = await ctx.vfs.readFile('partial.bin')
|
|
t.is(ctx.b4a.toString(merged), 'abcde')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await ctx.vfs.writeFile('part.txt', b4a.from('ab'))
|
|
ctx.httpFetch = async (_url, init) => {
|
|
t.is(headerFromInit(init, 'Range'), 'bytes=2-')
|
|
return new Response('cd', { status: 206 })
|
|
}
|
|
await runBinCommand(ctx, ['wget', '-qc', 'http://stub.example/blob/part.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
const partMerged = await ctx.vfs.readFile('part.txt')
|
|
t.is(ctx.b4a.toString(partMerged), 'abcd')
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 rm -rf removes directory tree on personal drive', async (t) => {
|
|
const dir = testCorestoreDir('rmrf')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('prm'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/rm', b4a.from(await readBuiltBin('rm')))
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.exitCode = 0
|
|
ctx.console = { log() {}, error() {} }
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', 'nest/leaf/x.txt'),
|
|
b4a.from('x')
|
|
)
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', 'nest/other/y.txt'),
|
|
b4a.from('y')
|
|
)
|
|
t.ok((await ctx.vfs.readdir('nest')).includes('leaf'))
|
|
await runBinCommand(ctx, ['rm', '-rf', 'nest'])
|
|
t.is(ctx.exitCode, 0)
|
|
const after = await ctx.vfs.readdir('.')
|
|
t.ok(!after.includes('nest'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('git-fs-adapter mkdir recursive and readdir hides .bareos_empty', async (t) => {
|
|
const dir = testCorestoreDir('gitfs')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pgf'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
const fs = createGitFsFromVfs(ctx.vfs)
|
|
const base = '/home/user/gittest'
|
|
await fs.promises.mkdir(path.posix.join(base, 'a', 'b'), { recursive: true })
|
|
const inA = await fs.promises.readdir(path.posix.join(base, 'a'))
|
|
t.is(inA.indexOf('.bareos_empty'), -1)
|
|
t.ok(inA.includes('b'))
|
|
const top = await fs.promises.readdir(base)
|
|
t.ok(top.includes('a'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand delegates git to booter (ignores /bin/git script body)', async (t) => {
|
|
const dir = testCorestoreDir('gitdel')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pgd'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put(
|
|
'/bin/git',
|
|
b4a.from(
|
|
`async function run() { throw new Error('eval git should not run') }`
|
|
)
|
|
)
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
await runBinCommand(ctx, ['git', 'version'])
|
|
t.ok(logs.some((l) => /isomorphic-git/i.test(l)))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand runs ./git from cwd instead of booter delegate', async (t) => {
|
|
const dir = testCorestoreDir('gitlocal')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pgl'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', 'git'),
|
|
b4a.from(`
|
|
async function run(ctx) {
|
|
ctx.out.push('local-git-script')
|
|
}
|
|
`)
|
|
)
|
|
const out = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.out = out
|
|
await runBinCommand(ctx, ['./git'])
|
|
t.is(out[0], 'local-git-script')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runGitCli init and status on personal drive', async (t) => {
|
|
const dir = testCorestoreDir('gitcmd')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pgc'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
await runGitCli(ctx, ['git', 'init', '-C', '/home/user/myrepo'])
|
|
t.ok(logs.some((l) => /initialized|git repository/i.test(l)))
|
|
logs.length = 0
|
|
await runGitCli(ctx, ['git', '-C', '/home/user/myrepo', 'status'])
|
|
t.ok(logs.length > 0)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('git clean -fd removes untracked files only', async (t) => {
|
|
const dir = testCorestoreDir('gitclean')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pgcl'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = { log() {}, error() {} }
|
|
await runGitCli(ctx, ['git', 'init', '-C', '/home/user/clrepo'])
|
|
await ctx.vfs.writeFile('/home/user/clrepo/a.txt', b4a.from('a'))
|
|
await runGitCli(ctx, ['git', '-C', '/home/user/clrepo', 'add', 'a.txt'])
|
|
await runGitCli(ctx, [
|
|
'git',
|
|
'-C',
|
|
'/home/user/clrepo',
|
|
'commit',
|
|
'-m',
|
|
'init'
|
|
])
|
|
await ctx.vfs.writeFile('/home/user/clrepo/junk.txt', b4a.from('j'))
|
|
await runGitCli(ctx, ['git', '-C', '/home/user/clrepo', 'clean', '-fd'])
|
|
t.is(await ctx.vfs.readFile('/home/user/clrepo/junk.txt'), null)
|
|
t.ok(await ctx.vfs.readFile('/home/user/clrepo/a.txt'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('curl -O -J saves Content-Disposition filename', async (t) => {
|
|
const dir = testCorestoreDir('curlj')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pcj'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.exitCode = 0
|
|
ctx.httpFetch = async () =>
|
|
new Response(b4a.from('payload'), {
|
|
status: 200,
|
|
headers: {
|
|
'Content-Disposition': 'attachment; filename="from-server.bin"'
|
|
}
|
|
})
|
|
await runBinCommand(ctx, [
|
|
'curl',
|
|
'-s',
|
|
'-O',
|
|
'-J',
|
|
'https://stub.example/blob'
|
|
])
|
|
t.is(ctx.exitCode, 0)
|
|
const out = await ctx.vfs.readFile('/home/user/from-server.bin')
|
|
t.ok(out)
|
|
t.is(ctx.b4a.toString(out), 'payload')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('bare-os-ipc fanout publishes to subscribers', async (t) => {
|
|
const ipc = createBareOsIpc({ maxFifoBytes: 65536 })
|
|
const a = ipc.fanoutSubscribe('news')
|
|
const b = ipc.fanoutSubscribe('news')
|
|
ipc.fanoutPublish('news', b4a.from('x', 'utf8'))
|
|
t.is(b4a.toString(await a.take()), 'x')
|
|
t.is(b4a.toString(await b.take()), 'x')
|
|
a.dispose()
|
|
b.dispose()
|
|
})
|
|
|
|
test('bare-os-ipc pushJson requires token when configured', async (t) => {
|
|
const ipc = createBareOsIpc({ ipcRpcToken: 'secret' })
|
|
ipc.create('q')
|
|
t.exception(() => ipc.pushJson('q', { x: 1 }), /token/)
|
|
ipc.pushJson('q', { x: 1, bareOsIpcToken: 'secret' })
|
|
const j = await ipc.takeJson('q')
|
|
t.is(j.x, 1)
|
|
})
|
|
|
|
test('bareOsHttpUrlAllowed allowlist and denylist', async (t) => {
|
|
t.ok(bareOsHttpUrlAllowed('https://a.example/x', { allow: [], deny: [] }).ok)
|
|
t.ok(
|
|
!bareOsHttpUrlAllowed('https://evil.com/', {
|
|
allow: ['a.example'],
|
|
deny: []
|
|
}).ok
|
|
)
|
|
t.ok(
|
|
bareOsHttpUrlAllowed('https://a.example/', {
|
|
allow: ['a.example'],
|
|
deny: []
|
|
}).ok
|
|
)
|
|
t.ok(
|
|
!bareOsHttpUrlAllowed('https://a.example/', {
|
|
allow: ['a.example'],
|
|
deny: ['a.example']
|
|
}).ok
|
|
)
|
|
})
|
|
|
|
test('raceWithAbortAndTimeout rejects on timeout', async (t) => {
|
|
try {
|
|
await raceWithAbortAndTimeout(
|
|
new Promise(() => {}),
|
|
{ timeoutMs: 30 },
|
|
'hang'
|
|
)
|
|
t.ok(false, 'expected timeout')
|
|
} catch (e) {
|
|
t.ok(/timed out/.test(String(e && e.message)))
|
|
}
|
|
})
|
|
|
|
test('kernel share/man/man.json page count matches coreutils + extras + handbook + devguide', async (t) => {
|
|
const manPath = path.join(__dirname, '../../kernel/share/man/man.json')
|
|
const raw = JSON.parse(await readFile(manPath, 'utf8'))
|
|
const { COREUTILS_COMMANDS, MAN_EXTRA_PAGES } =
|
|
await import('../bare-os-coreutils/lib/commands.mjs')
|
|
const handbookDir = path.join(__dirname, '../../handbook')
|
|
const devguideDir = path.join(__dirname, '../../developer-guide')
|
|
const handbookMd = (await readdir(handbookDir)).filter((f) =>
|
|
f.endsWith('.md')
|
|
).length
|
|
const devguideMd = (await readdir(devguideDir)).filter((f) =>
|
|
f.endsWith('.md')
|
|
).length
|
|
t.is(
|
|
raw.pages.length,
|
|
COREUTILS_COMMANDS.length + MAN_EXTRA_PAGES.length + handbookMd + devguideMd
|
|
)
|
|
t.ok(Array.isArray(raw.apropos) && raw.apropos.length > 0)
|
|
t.is(typeof raw.index.ls, 'number')
|
|
t.is(typeof raw.index.handbook, 'number')
|
|
t.is(typeof raw.index.devguide, 'number')
|
|
const hb = raw.pages[raw.index.handbook]
|
|
t.is(hb.section, 7)
|
|
t.is(hb.name, 'bare-os-handbook')
|
|
const dg = raw.pages[raw.index.devguide]
|
|
t.is(dg.section, 7)
|
|
t.is(dg.name, 'bare-os-developer-guide')
|
|
})
|
|
|
|
test('runBinCommand man ls prints manual text', async (t) => {
|
|
const dir = testCorestoreDir('mann')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pmn'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const manPath = path.join(__dirname, '../../kernel/share/man/man.json')
|
|
await drive.put(
|
|
'/share/man/man.json',
|
|
b4a.from(await readFile(manPath, 'utf8'))
|
|
)
|
|
await drive.put('/bin/man', b4a.from(await readBuiltBin('man')))
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['man', 'ls'])
|
|
t.is(ctx.exitCode, 0)
|
|
const text = logs.join('\n')
|
|
t.ok(text.includes('SYNOPSIS'))
|
|
t.ok(text.includes('EXAMPLES'))
|
|
t.ok(text.includes('ls'))
|
|
logs.length = 0
|
|
await runBinCommand(ctx, ['man', '-w'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(logs.join('\n').trim(), '/share/man/man.json')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('coreutils sed and awk fixture corpus', async (t) => {
|
|
const corpusPath = path.join(
|
|
__dirname,
|
|
'../bare-os-coreutils/fixtures/sed-awk-corpus.json'
|
|
)
|
|
const corpus = JSON.parse(await readFile(corpusPath, 'utf8'))
|
|
const dir = testCorestoreDir('sedawkcorpus')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('psac'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/sed', b4a.from(await readBuiltBin('sed')))
|
|
await drive.put('/bin/awk', b4a.from(await readBuiltBin('awk')))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.exitCode = 0
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error(...a) {
|
|
lines.push(a.join(' '))
|
|
}
|
|
}
|
|
function corpusNormLines(logs) {
|
|
return logs
|
|
.flatMap((l) => String(l).split('\n'))
|
|
.filter((x) => x.length > 0)
|
|
}
|
|
for (const c of corpus.sed) {
|
|
await ctx.vfs.writeFile(c.file, b4a.from(c.content))
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, c.argv)
|
|
t.is(ctx.exitCode, 0, 'sed ' + c.name)
|
|
t.alike(corpusNormLines(lines), c.lines, 'sed ' + c.name)
|
|
}
|
|
for (const c of corpus.awk) {
|
|
if (c.progFile) {
|
|
await ctx.vfs.writeFile(c.progFile, b4a.from(c.progContent))
|
|
}
|
|
if (c.file) await ctx.vfs.writeFile(c.file, b4a.from(c.content))
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, c.argv)
|
|
t.is(ctx.exitCode, 0, 'awk ' + c.name)
|
|
t.alike(corpusNormLines(lines), c.lines, 'awk ' + c.name)
|
|
}
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('coreutils matrix: cp find sort printf uniq realpath sha256sum base64 rm -d', async (t) => {
|
|
const dir = testCorestoreDir('corematrix')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pcm'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const bins = [
|
|
'cp',
|
|
'find',
|
|
'sort',
|
|
'printf',
|
|
'uniq',
|
|
'realpath',
|
|
'sha256sum',
|
|
'base64',
|
|
'rm',
|
|
'mkdir',
|
|
'touch'
|
|
]
|
|
for (const b of bins) {
|
|
await drive.put('/bin/' + b, b4a.from(await readBuiltBin(b)))
|
|
}
|
|
await drive.put(
|
|
'/bin/_corpus_echo',
|
|
b4a.from(`async function run(ctx, argv) {
|
|
ctx.console.log(argv.slice(1).join(' '))
|
|
}
|
|
`)
|
|
)
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.runBinCommand = (argv) => runBinCommand(ctx, argv)
|
|
ctx.exitCode = 0
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error(...a) {
|
|
lines.push(a.join(' '))
|
|
}
|
|
}
|
|
|
|
await ctx.vfs.mkdir('tree', { recursive: true })
|
|
await ctx.vfs.writeFile('tree/a.txt', b4a.from('newdata'))
|
|
await ctx.vfs.writeFile('dest.txt', b4a.from('stale'))
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['touch', '-d', '@1', 'dest.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['touch', '-d', '@100000', 'tree/a.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['cp', '-u', 'tree/a.txt', 'dest.txt'])
|
|
t.is(ctx.exitCode, 0, 'cp -u')
|
|
t.is(ctx.b4a.toString(await ctx.vfs.readFile('dest.txt')), 'newdata')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['find', '.', '-type', 'f', '-regex', '.*\\.txt$'])
|
|
t.is(ctx.exitCode, 0, 'find -regex')
|
|
t.ok(lines.some((l) => String(l).includes('a.txt')))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['find', 'tree', '-exec', '_corpus_echo', '{}', ';'])
|
|
t.is(ctx.exitCode, 0, 'find -exec')
|
|
t.ok(lines.some((l) => /a\.txt/.test(String(l))))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
ctx.shellStdin = '3\n1\n2\n'
|
|
await runBinCommand(ctx, ['sort', '-n'])
|
|
t.is(lines.join('\n'), '1\n2\n3')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['printf', '%s-%d', 'n', '7'])
|
|
t.is(lines[0], 'n-7')
|
|
|
|
await ctx.vfs.writeFile('u.txt', b4a.from('a\na\nb\n'))
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['uniq', '-c', 'u.txt'])
|
|
t.ok(lines.some((l) => /^\s*2\s+a$/.test(String(l))))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['realpath', 'tree/a.txt'])
|
|
t.ok(/a\.txt$/.test(lines[0]))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['sha256sum', 'u.txt'])
|
|
t.is(lines.length, 1)
|
|
t.ok(/^[a-f0-9]{64}\s+/.test(lines[0]))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['base64', '-w', '0', 'u.txt'])
|
|
const dec = Buffer.from(
|
|
lines.join('').replace(/\s+/g, ''),
|
|
'base64'
|
|
).toString()
|
|
t.is(dec, 'a\na\nb\n')
|
|
|
|
await ctx.vfs.mkdir('emptydir', { recursive: true })
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['rm', '-d', 'emptydir'])
|
|
t.is(ctx.exitCode, 0, 'rm -d')
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('coreutils gnu-gap batch: paste tac rev md5sum expr tsort numfmt truncate install comm join', async (t) => {
|
|
const dir = testCorestoreDir('gnugap')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('ngg'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const bins = [
|
|
'paste',
|
|
'tac',
|
|
'rev',
|
|
'md5sum',
|
|
'expr',
|
|
'tsort',
|
|
'numfmt',
|
|
'sync',
|
|
'truncate',
|
|
'install',
|
|
'unlink',
|
|
'comm',
|
|
'join',
|
|
'yes',
|
|
'cp',
|
|
'touch',
|
|
'mkdir',
|
|
'ls'
|
|
]
|
|
for (const b of bins) {
|
|
await drive.put('/bin/' + b, b4a.from(await readBuiltBin(b)))
|
|
}
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.runBinCommand = (argv) => runBinCommand(ctx, argv)
|
|
ctx.exitCode = 0
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error(...a) {
|
|
lines.push(a.join(' '))
|
|
}
|
|
}
|
|
ctx.vfs.env.BARE_OS_YES_MAX_LINES = '2'
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['yes', 'ok'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines.filter((l) => l === 'ok').length, 2)
|
|
|
|
await ctx.vfs.writeFile('nums.txt', b4a.from('1\n2\n3\n'))
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['tac', 'nums.txt'])
|
|
t.is(lines.join('\n'), '3\n2\n1')
|
|
|
|
await ctx.vfs.writeFile('rv.txt', b4a.from('ab\ncd\n'))
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['rev', 'rv.txt'])
|
|
t.is(lines.join('\n'), 'ba\ndc')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['md5sum', 'nums.txt'])
|
|
t.ok(/^[a-f0-9]{32}\s/.test(lines[0]))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['expr', '1', '+', '2', '*', '3'])
|
|
t.is(lines[0], '7')
|
|
|
|
await ctx.vfs.writeFile('ts.txt', b4a.from('a b\nb c\n'))
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['tsort', 'ts.txt'])
|
|
t.is(lines.join('\n'), 'a\nb\nc')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['numfmt', '--to=iec', '1024'])
|
|
t.is(lines[0], '1K')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['sync'])
|
|
t.is(ctx.exitCode, 0)
|
|
|
|
await ctx.vfs.writeFile('tr.txt', b4a.from('hello'))
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['truncate', '-s', '2', 'tr.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(ctx.b4a.toString(await ctx.vfs.readFile('tr.txt')), 'he')
|
|
|
|
await ctx.vfs.writeFile('src.txt', b4a.from('data'))
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['install', '-m', '600', 'src.txt', 'dest.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(ctx.b4a.toString(await ctx.vfs.readFile('dest.txt')), 'data')
|
|
|
|
await ctx.vfs.writeFile('s1.txt', b4a.from('a\nb\n'))
|
|
await ctx.vfs.writeFile('s2.txt', b4a.from('a\nc\n'))
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['comm', 's1.txt', 's2.txt'])
|
|
t.ok(lines.some((l) => l.includes('\t\ta')))
|
|
|
|
await ctx.vfs.writeFile('j1.txt', b4a.from('1 x\n2 y\n'))
|
|
await ctx.vfs.writeFile('j2.txt', b4a.from('1 p\n2 q\n'))
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['join', 'j1.txt', 'j2.txt'])
|
|
t.ok(lines.some((l) => /1\s+x\s+1\s+p/.test(String(l))))
|
|
|
|
await ctx.vfs.writeFile('u.txt', b4a.from('x'))
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['unlink', 'u.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(await ctx.vfs.readFile('u.txt'), null)
|
|
|
|
await ctx.vfs.writeFile('p1.txt', b4a.from('a\nb\n'))
|
|
await ctx.vfs.writeFile('p2.txt', b4a.from('1\n2\n'))
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['paste', 'p1.txt', 'p2.txt'])
|
|
t.is(lines[0], 'a\t1')
|
|
t.is(lines[1], 'b\t2')
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('verifyBootManifestEd25519 rejects invalid inputs', async (t) => {
|
|
const { verifyBootManifestEd25519 } =
|
|
await import('#bare-os-boot-manifest-sig')
|
|
const msg = b4a.from('manifest-bytes', 'utf8')
|
|
t.absent(verifyBootManifestEd25519(msg, null, ''))
|
|
t.absent(verifyBootManifestEd25519(msg, msg, '00ff'))
|
|
const z64 = '0'.repeat(64)
|
|
t.absent(verifyBootManifestEd25519(msg, new Uint8Array(64), z64))
|
|
})
|
|
|
|
test('bareOsIpc.duplexJsonRoundTrip', async (t) => {
|
|
const ipc = createBareOsIpc()
|
|
const { left, right } = ipc.createDuplexBridge('dupjx')
|
|
const respP = ipc.duplexJsonRoundTrip(right, { id: 7, method: 'ping' })
|
|
const u8 = await left.take()
|
|
const req = JSON.parse(b4a.toString(u8, 'utf8'))
|
|
t.is(req.method, 'ping')
|
|
left.push(
|
|
b4a.from(
|
|
JSON.stringify({ bareOsRpc: '2', id: 7, result: 'pong' }) + '\n',
|
|
'utf8'
|
|
)
|
|
)
|
|
const out = await respP
|
|
t.is(out.result, 'pong')
|
|
})
|
|
|
|
test('unit journal exposed under /run/bare-os/unit-journal', async (t) => {
|
|
const {
|
|
appendBareInitdJournal,
|
|
clearBareInitdJournalForTests,
|
|
getBareInitdJournalNdjson
|
|
} = await import('./lib/bare-initd-journal.js')
|
|
clearBareInitdJournalForTests()
|
|
appendBareInitdJournal('demo', { event: 'unit_test' })
|
|
const dir = testCorestoreDir('vj')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvj'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const env = {
|
|
HOME: '/home/guest',
|
|
PWD: '/home/guest',
|
|
PATH: '/bin',
|
|
USER: 'guest'
|
|
}
|
|
const vfs = createVfs(sys, personal, env, null, {
|
|
getUnitJournalNdjson: (u) => getBareInitdJournalNdjson(u)
|
|
})
|
|
t.ok((await vfs.readdir('/run/bare-os/unit-journal')).includes('demo.ndjson'))
|
|
const j = b4a.toString(
|
|
await vfs.readFile('/run/bare-os/unit-journal/demo.ndjson')
|
|
)
|
|
t.ok(j.includes('unit_test'))
|
|
clearBareInitdJournalForTests()
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
async function readBuiltBin(name) {
|
|
const fs = await import('node:fs/promises')
|
|
const p = path.join(__dirname, '../../kernel/bin', name)
|
|
return fs.readFile(p, 'utf8')
|
|
}
|