1777 lines
56 KiB
JavaScript
1777 lines
56 KiB
JavaScript
import {
|
|
test,
|
|
b4a,
|
|
Hyperdrive,
|
|
Corestore,
|
|
mkdirSync,
|
|
rmSync,
|
|
readFile,
|
|
readdir,
|
|
path,
|
|
fileURLToPath,
|
|
PassThrough,
|
|
XTERM_CLEAR_SCROLLBACK_AND_VIEWPORT,
|
|
runKernelFromSource,
|
|
runBinCommand,
|
|
createGitFsFromVfs,
|
|
runGitCli,
|
|
createStreamLineReader,
|
|
sanitizeInteractiveShellLine,
|
|
createVfs,
|
|
BARE_OS_PROC_FILE_TO_ID_HYPERCORE_PACK_HRPC_LIFECYCLE,
|
|
parseHdmsPairArgv,
|
|
hcCrypto,
|
|
idEnc,
|
|
decodeRwShareOffer,
|
|
decodeShareOffer,
|
|
HDMS_AUTOPASS_SHARE_KEY,
|
|
HDMS_AUTOPASS_SHARE_RW_KEY,
|
|
normalizeWriterSecretHex,
|
|
buildBareOsHypercorePackHrpcLifecycleProcJson,
|
|
buildBareOsPearCorestoreHrpcProcJson,
|
|
buildBareOsPearInspectLoggerTlsProcJson,
|
|
buildBareOsBareRuntimeProtoMuxProcJson,
|
|
buildBareOsBootGraphProcJson,
|
|
pathnameExpandShellWord,
|
|
createBareOsIpc,
|
|
BareOsSwarmConnectionManager,
|
|
createBareOsLifecycleStateMachine,
|
|
runTransactionalLifecycleHooks,
|
|
bareOsRegisterCorestoreSuspendResumeHooks,
|
|
bareOsInstallCorestoreSuspendResumeHooks,
|
|
bareOsFindOrphanProcessRows,
|
|
topologicalOrderKernelExtensions,
|
|
resolveKernelExtensionsFull,
|
|
createBareOsProtomuxAliasRegistry,
|
|
bareOsHttpUrlAllowed,
|
|
bareOsParseBareAclText,
|
|
bareOsAclDeniesSubject,
|
|
raceWithAbortAndTimeout,
|
|
tokenize,
|
|
expandWord,
|
|
execShellLine,
|
|
expandArgvAliases,
|
|
defaultShellAliases,
|
|
loadBarerc,
|
|
BARERC_SKELETON,
|
|
splitTokensBySemicolon,
|
|
splitTokensByAndOr,
|
|
bareOsShellAstSnapshot,
|
|
planShellRedirections,
|
|
buildShellExecutionGraph,
|
|
dispatchShellTrapSignal,
|
|
BARE_OS_EXIT_STATUS_ENV,
|
|
syncBareOsExitStatusEnv,
|
|
getBareOsPipelineLimits,
|
|
DEFAULT_PIPELINE_MAX_STAGES,
|
|
tokenizeBareShellLineDetailed,
|
|
decodeBareOsDollarQuote,
|
|
applyBareOsThemeFromEnv,
|
|
bareOsListThemeNames,
|
|
bareParseLsColors,
|
|
bareSerializeLsColors,
|
|
bareLsColorOpenSgrFromMap,
|
|
bareDefaultDircolorsDatabase,
|
|
bareParseDircolorsDatabase,
|
|
BARE_OS_CTX_API_VERSION,
|
|
buildBareOsRuntimeCaps,
|
|
bareOsBareModulesEnabled,
|
|
bareOsBareModuleManifestEmbeddedRef,
|
|
buildBareCtxObjectFromHost,
|
|
buildPearCtxObjectFromHost,
|
|
loadBareModuleManifest,
|
|
maybeMergeBareFromDrive,
|
|
maybeBareOsBuildBinManifest,
|
|
createFishReadLine,
|
|
fuzzyMatch,
|
|
stripAnsi,
|
|
parseHistoryFile,
|
|
formatHistoryFile,
|
|
dedupeConsecutiveHistory,
|
|
searchHistoryEntries,
|
|
shouldPersistShellHistoryCommand,
|
|
scrubShellHistoryEntries,
|
|
SHELL_BUILTINS,
|
|
resolveShellPromptHookSegment,
|
|
parseCompletionContext,
|
|
rankCompletionItems,
|
|
suggestGhostFromHistory,
|
|
suggestGhostFromFs,
|
|
ghostReplaceCurrentWord,
|
|
longestCommonCompletionPrefix,
|
|
completeLine,
|
|
levenshtein,
|
|
fieldMatches,
|
|
dowFieldMatches,
|
|
parseCronLine,
|
|
jobMatchesDate,
|
|
bareInitdReadinessSnapshot,
|
|
bareInitdShutdownActiveUnitsReverse,
|
|
getBareServiceRuntime,
|
|
registerBareInitdDisposer,
|
|
registerKernelShutdownHook,
|
|
runKernelShutdownHooks,
|
|
startBareInitd,
|
|
stopBareInitd,
|
|
getLastBareInitdDagSnapshotJson,
|
|
appendVarLog,
|
|
ensureBareOsVarLogTree,
|
|
KERNEL_CONSOLE_LOG,
|
|
BOOT_LOG,
|
|
bareOsKernelMetricsReset,
|
|
bareOsKernelMetricsSnapshot,
|
|
parseUnitDropInText,
|
|
bareOsHyperswarmOptsFromEnv,
|
|
bareOsBootJoinPeerHexFromEnv,
|
|
bareOsBootInitPrefetchEnabled,
|
|
bareOsBootWarmBinDownloadDiffEnabled,
|
|
bareOsProcessTableSnapshot,
|
|
buildBareOsSyscallsProcJson,
|
|
bareOsParseSendmsgScmRights,
|
|
wantPosixSocketScmRights,
|
|
parseBareOsHrpcAllowlistJson,
|
|
bareOsHrpcAllowlistDeniesRoute,
|
|
buildBareOsProtomuxExtensionsProcJson,
|
|
createBareOsDiskOsBridge,
|
|
buildBareOsReplicationLiveSketch,
|
|
evaluateBareOsPeerAdmission,
|
|
bareOsFormatPeerAdmissionAuditEvent,
|
|
bareOsPeerAdmissionAuditRateAllow,
|
|
parseDelegateAllowSet,
|
|
isDelegateKindAllowed,
|
|
bareOsAuditAppend,
|
|
bareOsAuditVerifyRows,
|
|
bareOsDrainFcntlLockWaiters,
|
|
buildBareOsReplicationOperatorSurfaceProcJson,
|
|
DEFAULT_CURL_USER_AGENT,
|
|
DEFAULT_WGET_USER_AGENT,
|
|
filenameFromContentDisposition,
|
|
encodeUstarHeader,
|
|
runTarCli,
|
|
require,
|
|
__dirname,
|
|
TAR_BLK,
|
|
concatTarParts,
|
|
headerFromInit,
|
|
testCorestoreDir,
|
|
testCtx,
|
|
personalHomeBacking,
|
|
readBuiltBin
|
|
} from './test/_helpers.js'
|
|
|
|
test('.bareos_empty marker roundtrip for vfs mkdir/rmdir on personal drive', async (t) => {
|
|
const dir = testCorestoreDir('vfs-empty-marker')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvem'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
|
|
await ctx.vfs.mkdir('/home/user/emptydir', { recursive: true })
|
|
const markerPath = personalHomeBacking('/home/user', 'emptydir/.bareos_empty')
|
|
t.ok(await personal.get(markerPath), 'mkdir writes hidden empty-dir marker')
|
|
|
|
await ctx.vfs.rmdir('/home/user/emptydir')
|
|
t.is(await personal.get(markerPath), null, 'rmdir removes empty-dir marker')
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('.bareos_empty marker roundtrip for cp -R empty directories', async (t) => {
|
|
const dir = testCorestoreDir('cp-empty-marker')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pcpem'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/cp', b4a.from(await readBuiltBin('cp')))
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = { log() {}, error() {} }
|
|
|
|
await ctx.vfs.mkdir('/home/user/src/empty', { recursive: true })
|
|
await runBinCommand(ctx, ['cp', '-R', '/home/user/src', '/home/user/dst'])
|
|
t.is(ctx.exitCode, 0)
|
|
|
|
const names = await ctx.vfs.readdir('/home/user/dst')
|
|
t.ok(names.includes('empty'))
|
|
const dstMarker = personalHomeBacking('/home/user', 'dst/empty/.bareos_empty')
|
|
t.ok(await personal.get(dstMarker), 'cp -R preserves empty directory marker')
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('git-fs-adapter rmdir roundtrip removes .bareos_empty marker', async (t) => {
|
|
const dir = testCorestoreDir('gitfs-rmdir-marker')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pgfrm'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
const fs = createGitFsFromVfs(ctx.vfs)
|
|
const empty = '/home/user/gittest-empty'
|
|
|
|
await fs.promises.mkdir(empty, { recursive: true })
|
|
const markerPath = personalHomeBacking('/home/user', 'gittest-empty/.bareos_empty')
|
|
t.ok(await personal.get(markerPath))
|
|
await fs.promises.rmdir(empty)
|
|
t.is(await personal.get(markerPath), null)
|
|
|
|
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('bare-os-ipc request/respond resolves by correlation id', async (t) => {
|
|
const ipc = createBareOsIpc()
|
|
ipc.create('rpc')
|
|
const reqP = ipc.request('rpc', { method: 'ping' }, { timeoutMs: 500 })
|
|
const req = await ipc.takeJson('rpc')
|
|
t.is(req.method, 'ping')
|
|
ipc.respond('rpc', String(req.id), { result: { ok: true } })
|
|
const res = await reqP
|
|
t.ok(res && res.result && res.result.ok === true)
|
|
})
|
|
|
|
test('swarm connection manager duplicate arbitration prefers first open', async (t) => {
|
|
const mgr = new BareOsSwarmConnectionManager(null, {})
|
|
t.ok(mgr.shouldAcceptConnection('peer-a', { initiator: true }))
|
|
mgr.markConnectionOpen('peer-a', { initiator: true })
|
|
t.absent(mgr.shouldAcceptConnection('peer-a', { initiator: false }))
|
|
mgr.markConnectionClosed('peer-a')
|
|
t.ok(mgr.shouldAcceptConnection('peer-a', { initiator: false }))
|
|
})
|
|
|
|
test('lifecycle manager state machine and transactional hook runner', async (t) => {
|
|
const sm = createBareOsLifecycleStateMachine()
|
|
t.is(sm.state, 'opening')
|
|
t.ok(sm.transition('ready'))
|
|
t.ok(sm.transition('suspending'))
|
|
t.ok(sm.transition('resuming'))
|
|
t.ok(sm.transition('ready'))
|
|
const rep = await runTransactionalLifecycleHooks([
|
|
{ name: 'ok', run: async () => {} },
|
|
{ name: 'bad', run: async () => { throw new Error('boom') } }
|
|
])
|
|
t.absent(rep.ok)
|
|
t.is(rep.rows.length, 2)
|
|
})
|
|
|
|
test('corestore+swarm suspend/resume hooks run in expected order', async (t) => {
|
|
const events = []
|
|
const store = {
|
|
async suspend() {
|
|
events.push('store.suspend')
|
|
},
|
|
async resume() {
|
|
events.push('store.resume')
|
|
}
|
|
}
|
|
const swarm = {
|
|
async suspend() {
|
|
events.push('swarm.suspend')
|
|
},
|
|
async resume() {
|
|
events.push('swarm.resume')
|
|
}
|
|
}
|
|
const suspendHooks = []
|
|
const resumeHooks = []
|
|
const ctx = {
|
|
bareOsRegisterSuspendHook(fn) {
|
|
suspendHooks.push(fn)
|
|
},
|
|
bareOsRegisterResumeHook(fn) {
|
|
resumeHooks.push(fn)
|
|
}
|
|
}
|
|
bareOsRegisterCorestoreSuspendResumeHooks(store, swarm)
|
|
bareOsInstallCorestoreSuspendResumeHooks(ctx)
|
|
t.is(suspendHooks.length, 1)
|
|
t.is(resumeHooks.length, 1)
|
|
await suspendHooks[0]()
|
|
await resumeHooks[0]()
|
|
t.alike(events, ['store.suspend', 'swarm.suspend', 'swarm.resume', 'store.resume'])
|
|
})
|
|
|
|
test('swarm connection manager grouped retry queue drains', async (t) => {
|
|
const mgr = new BareOsSwarmConnectionManager(null, {})
|
|
mgr.queueRetry('a', 'short')
|
|
mgr.queueRetry('b', 'long')
|
|
await new Promise((r) => setTimeout(r, 120))
|
|
const snap = mgr.snapshot()
|
|
t.ok(snap.groupedRetry.dequeuedTotal >= 1)
|
|
})
|
|
|
|
test('ipc request circuit breaker opens after repeated timeouts', async (t) => {
|
|
const ipc = createBareOsIpc()
|
|
ipc.create('breaker')
|
|
for (let i = 0; i < 10; i++) {
|
|
await t.exception(async () => {
|
|
await ipc.request('breaker', { method: 'slow' }, { timeoutMs: 10 })
|
|
})
|
|
}
|
|
await t.exception(async () => {
|
|
await ipc.request('breaker', { method: 'slow' }, { timeoutMs: 10 })
|
|
}, /circuit open/)
|
|
})
|
|
|
|
test('process table orphan detector finds missing parent rows', async (t) => {
|
|
const snap = bareOsProcessTableSnapshot({
|
|
shellJobs: [{ id: 1, done: false, stopped: false }]
|
|
})
|
|
const mutated = {
|
|
...snap,
|
|
processes: [...snap.processes, { pid: 9001, ppid: 8123, state: 'running' }]
|
|
}
|
|
const orphans = bareOsFindOrphanProcessRows(mutated)
|
|
t.ok(Array.isArray(orphans) && orphans.some((p) => p.pid === 9001))
|
|
})
|
|
|
|
test('bare-os-ipc enforces waiter cap and reports telemetry', async (t) => {
|
|
const ipc = createBareOsIpc({ maxWaitersPerChannel: 1 })
|
|
ipc.create('cap')
|
|
const p1 = ipc.take('cap')
|
|
await Promise.resolve()
|
|
await t.exception(async () => {
|
|
await ipc.take('cap')
|
|
}, /maxWaitersPerChannel/)
|
|
const st = ipc.stats()
|
|
t.ok(st && st.telemetry && st.telemetry.fifoTakeDeniedWaiters >= 1)
|
|
ipc.push('cap', b4a.from('x'))
|
|
t.is(b4a.toString(await p1), 'x')
|
|
})
|
|
|
|
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)))
|
|
}
|
|
})
|
|
|
|
async function countMarkdownFilesRecursive(absDir) {
|
|
let n = 0
|
|
const ents = await readdir(absDir, { withFileTypes: true })
|
|
for (const ent of ents) {
|
|
const full = path.join(absDir, ent.name)
|
|
if (ent.isDirectory()) n += await countMarkdownFilesRecursive(full)
|
|
else if (ent.isFile() && ent.name.endsWith('.md')) n++
|
|
}
|
|
return n
|
|
}
|
|
|
|
test('kernel share/man/man.json page count matches coreutils + extras + prose trees', 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 usersManualDir = path.join(__dirname, '../../users-manual')
|
|
const docsDir = path.join(__dirname, '../../docs')
|
|
const handbookMd = (await readdir(handbookDir)).filter((f) =>
|
|
f.endsWith('.md')
|
|
).length
|
|
const devguideMd = (await readdir(devguideDir)).filter((f) =>
|
|
f.endsWith('.md')
|
|
).length
|
|
const usersManualMd = (await readdir(usersManualDir)).filter((f) =>
|
|
f.endsWith('.md')
|
|
).length
|
|
const docsMd = await countMarkdownFilesRecursive(docsDir)
|
|
const expectedMin =
|
|
COREUTILS_COMMANDS.length +
|
|
MAN_EXTRA_PAGES.length +
|
|
handbookMd +
|
|
devguideMd +
|
|
usersManualMd +
|
|
docsMd
|
|
t.ok(raw.pages.length > 0)
|
|
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')
|
|
t.is(typeof raw.index['users-manual'], 'number')
|
|
t.is(typeof raw.index.docs, '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')
|
|
const um = raw.pages[raw.index['users-manual']]
|
|
t.is(um.section, 7)
|
|
t.is(um.name, 'bare-os-users-manual')
|
|
const docHub = raw.pages[raw.index.docs]
|
|
t.is(docHub.section, 7)
|
|
t.is(docHub.name, 'bare-os-docs')
|
|
})
|
|
|
|
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 = function (argv, ro) {
|
|
return runBinCommand(this, argv, ro)
|
|
}
|
|
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 = function (argv, ro) {
|
|
return runBinCommand(this, argv, ro)
|
|
}
|
|
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+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('verifyPathCapabilityEnvelope rejects invalid envelopes', async (t) => {
|
|
const { verifyPathCapabilityEnvelope } = await import(
|
|
'./lib/security/bare-os-path-capability.js'
|
|
)
|
|
t.ok(!verifyPathCapabilityEnvelope(null).ok)
|
|
t.ok(!verifyPathCapabilityEnvelope({ schema: 2 }).ok)
|
|
t.ok(!verifyPathCapabilityEnvelope({ schema: 1, payload: {} }).ok)
|
|
})
|
|
|
|
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('bareOsIpc mq_send orders by priority then FIFO', async (t) => {
|
|
const ipc = createBareOsIpc()
|
|
ipc.mqOpen('mqprio', { maxmsg: 8, maxBytes: 4096 })
|
|
ipc.mqSend('mqprio', 1, b4a.from('a', 'utf8'))
|
|
ipc.mqSend('mqprio', 9, b4a.from('b', 'utf8'))
|
|
ipc.mqSend('mqprio', 9, b4a.from('c', 'utf8'))
|
|
const x = ipc.mqReceive('mqprio')
|
|
t.is(b4a.toString(x.data, 'utf8'), 'b')
|
|
const y = ipc.mqReceive('mqprio')
|
|
t.is(b4a.toString(y.data, 'utf8'), 'c')
|
|
const z = ipc.mqReceive('mqprio')
|
|
t.is(b4a.toString(z.data, 'utf8'), 'a')
|
|
})
|
|
|
|
test('unit journal exposed under /run/bare-os/unit-journal', async (t) => {
|
|
const {
|
|
appendBareInitdJournal,
|
|
clearBareInitdJournalForTests,
|
|
getBareInitdJournalNdjson
|
|
} = await import('./lib/initd/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 })
|
|
})
|
|
|
|
test('worker_budget proc surface schema 2 exposes cpu and class map env', async (t) => {
|
|
const j = buildBareOsReplicationOperatorSurfaceProcJson('worker_budget', {
|
|
BARE_OS_BIN_WORKER_WALL_MS_MAX: '5000',
|
|
BARE_OS_BIN_WORKER_CPU_MS_MAX: '2500',
|
|
BARE_OS_KERNEL_RUNNER_CLASS_CPU_MS_MAX_JSON: '{"textproc":100}'
|
|
})
|
|
t.is(j.schema, 2)
|
|
t.is(j.wallMsMax, '5000')
|
|
t.is(j.cpuMsMax, '2500')
|
|
t.alike(j.kernelRunnerClassCpuMsMax, { textproc: 100 })
|
|
})
|
|
|
|
test('process table snapshot includes processGroups metadata', async (t) => {
|
|
const s = bareOsProcessTableSnapshot({
|
|
env: { BARE_OS_SHELL_PIPEFAIL: '1' }
|
|
})
|
|
t.ok(s.processGroups)
|
|
t.is(s.processGroups.schema, 1)
|
|
t.ok(String(s.processGroups.killpgAnalog).includes('killpg'))
|
|
t.is(s.schemaVersion, 10)
|
|
t.ok(s.jobControlSemantics && s.jobControlSemantics.pipefail === true)
|
|
const booter = s.processes.find((p) => p && p.pid === 2)
|
|
t.ok(booter && booter.parentName === 'bare-os-kernel')
|
|
const shell = s.processes.find((p) => p && p.pid === 3)
|
|
t.ok(shell && shell.parentName === 'bare-os-booter')
|
|
t.ok(s.signalRouting && s.signalRouting.schema === 1)
|
|
t.ok(s.initdBinding)
|
|
t.ok(s.signalModel && Array.isArray(s.signalModel.sigpendingAnalog))
|
|
t.ok(s.exitStatusModel && s.exitStatusModel.schema === 1)
|
|
})
|
|
|
|
test('process table lifecycle timestamps remain stable across snapshots', async (t) => {
|
|
const running = bareOsProcessTableSnapshot({
|
|
shellJobs: [{ id: 7, done: false, stopped: false, label: 'demo' }]
|
|
})
|
|
const row1 = running.processes.find((p) => p && p.jobId === 7)
|
|
t.ok(row1 && Number.isFinite(row1.createdAtMs))
|
|
const stopped = bareOsProcessTableSnapshot({
|
|
shellJobs: [{ id: 7, done: false, stopped: true, label: 'demo' }]
|
|
})
|
|
const row2 = stopped.processes.find((p) => p && p.jobId === 7)
|
|
t.is(row2.createdAtMs, row1.createdAtMs)
|
|
t.ok(row2.lastStateChangeAtMs >= row1.createdAtMs)
|
|
const done = bareOsProcessTableSnapshot({
|
|
shellJobs: [{ id: 7, done: true, stopped: false, label: 'demo' }]
|
|
})
|
|
const row3 = done.processes.find((p) => p && p.jobId === 7)
|
|
t.is(row3.createdAtMs, row1.createdAtMs)
|
|
t.ok(Number.isFinite(row3.completedAtMs))
|
|
})
|
|
|
|
test('boot log writes timeline rows into personal backing', async (t) => {
|
|
const dir = testCorestoreDir('bootlog')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pbootlog'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
await ensureBareOsVarLogTree(ctx)
|
|
await appendVarLog(
|
|
ctx,
|
|
BOOT_LOG,
|
|
'boot',
|
|
JSON.stringify({ event: 'login_prompt_ready', atMs: Date.now() })
|
|
)
|
|
const raw = b4a.toString(await ctx.vfs.readFile('/var/log/bare-os/boot.log'), 'utf8')
|
|
t.ok(raw.includes('login_prompt_ready'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('bare_acl other:: and mask interact like POSIX ACL classes', async (t) => {
|
|
const env = { UID: '1000', GID: '1000' }
|
|
const p1 = bareOsParseBareAclText('other::---\n')
|
|
t.ok(bareOsAclDeniesSubject(p1, env, '1000', '1000', 'read'))
|
|
|
|
const p2 = bareOsParseBareAclText(
|
|
'user:1000:rwx\nmask::r--\nother::rwx\n'
|
|
)
|
|
t.ok(bareOsAclDeniesSubject(p2, env, '1000', '1000', 'write'))
|
|
t.absent(bareOsAclDeniesSubject(p2, env, '1000', '1000', 'read'))
|
|
|
|
const p3 = bareOsParseBareAclText('user::r-x\n')
|
|
t.ok(bareOsAclDeniesSubject(p3, env, '1000', '1000', 'write'))
|
|
t.absent(bareOsAclDeniesSubject(p3, env, '1000', '1000', 'read'))
|
|
})
|
|
|
|
test('buildBareOsReplicationOperatorSurfaceProcJson corestore_snapshot', async (t) => {
|
|
const j = buildBareOsReplicationOperatorSurfaceProcJson('corestore_snapshot', {
|
|
BARE_OS_SEED_CORESTORE_SNAPSHOT_TAG: 'ci-tag',
|
|
BARE_OS_SEED_SNAPSHOT_HINTS_JSON: '{"ok":true}',
|
|
BARE_OS_CORESTORE_SNAPSHOT_STATE_JSON: '{"cores":3,"paused":false}',
|
|
BARE_OS_CORESTORE_SNAPSHOT_PAUSED: '1',
|
|
BARE_OS_REPLICATION_PAUSED: 'true'
|
|
})
|
|
t.is(j.schema, 2)
|
|
t.is(j.snapshotTag, 'ci-tag')
|
|
t.ok(j.snapshotHintsJson && j.snapshotHintsJson.ok === true)
|
|
t.is(j.operatorSnapshotState?.cores, 3)
|
|
t.is(j.paused, true)
|
|
t.is(j.replicationPausedEnv, true)
|
|
})
|
|
|
|
test('buildBareOsReplicationOperatorSurfaceProcJson replication_operator_panel', async (t) => {
|
|
const j = buildBareOsReplicationOperatorSurfaceProcJson(
|
|
'replication_operator_panel',
|
|
{
|
|
BARE_OS_REPLICATION_OPERATOR_PANEL_JSON: '{"treeLength":42}',
|
|
BARE_OS_HYPERCORE_LENGTHS_JSON: '{"a":1}',
|
|
BARE_OS_REPLICATION_BACKPRESSURE_JSON: '{"depth":1}',
|
|
BARE_OS_DHT_STATUS_JSON: '{"ok":true}',
|
|
BARE_OS_PEER_ALLOWLIST_HEX: 'abc'
|
|
}
|
|
)
|
|
t.is(j.schema, 1)
|
|
t.ok(j.panel && j.panel.treeLength === 42)
|
|
t.ok(j.hypercoreLengths && j.hypercoreLengths.a === 1)
|
|
t.is(j.peerAllowlistHexConfigured, true)
|
|
})
|
|
|
|
test('bare-os-protocol swarm topic matches PROTOCOL_NAME', async (t) => {
|
|
const { PROTOCOL_NAME, TOPIC_STRING, topicKey } = await import(
|
|
'bare-os-protocol'
|
|
)
|
|
t.is(PROTOCOL_NAME, TOPIC_STRING)
|
|
t.is(PROTOCOL_NAME, 'bare-os-v1')
|
|
const k = topicKey()
|
|
t.is(k.length, 32)
|
|
})
|
|
|
|
test('bare-os-protocol deterministic MBR failover ordering', async (t) => {
|
|
const { buildMbr, parseMbr, BLOCK_SIZE } = await import('bare-os-protocol')
|
|
const primary = b4a.alloc(32, 0x11)
|
|
const failoverA = b4a.alloc(32, 0x22)
|
|
const failoverB = b4a.alloc(32, 0x33)
|
|
|
|
const mbr = buildMbr(primary, [failoverA, failoverB])
|
|
t.is(mbr.byteLength, BLOCK_SIZE)
|
|
|
|
const parsed = parseMbr(mbr)
|
|
t.is(parsed.keys.length, 3)
|
|
t.alike(parsed.keys[0], primary)
|
|
t.alike(parsed.keys[1], failoverA)
|
|
t.alike(parsed.keys[2], failoverB)
|
|
|
|
const parsedAgain = parseMbr(buildMbr(primary, [failoverA, failoverB]))
|
|
t.alike(parsedAgain.keys, parsed.keys)
|
|
})
|
|
|
|
test('bareOsIpc.stats includes ipcBackpressure topChannels', async (t) => {
|
|
const ipc = createBareOsIpc()
|
|
ipc.create('q1')
|
|
const u8 = b4a.from('x')
|
|
for (let i = 0; i < 5; i++) ipc.push('q1', u8)
|
|
const st = ipc.stats()
|
|
t.ok(st.ipcBackpressure && st.ipcBackpressure.schema === 1)
|
|
t.ok(Array.isArray(st.ipcBackpressure.topChannels))
|
|
t.ok(st.ipcBackpressure.topChannels.some((r) => r.name === 'q1'))
|
|
})
|
|
|
|
test('bareOsWasmKernelCompile bounds and compile path', async (t) => {
|
|
const { bareOsWasmKernelCompile } = await import('./lib/boot/bare-os-wasm-kernel.js')
|
|
const big = new Uint8Array(600 * 1024)
|
|
const r0 = await bareOsWasmKernelCompile(big, { maxBytes: 1024 })
|
|
t.absent(r0.ok)
|
|
if (typeof WebAssembly === 'undefined' || !WebAssembly.compile) {
|
|
const r1 = await bareOsWasmKernelCompile(new Uint8Array([0x00, 0x61, 0x73, 0x6d]))
|
|
t.absent(r1.ok)
|
|
return
|
|
}
|
|
const minimal = new Uint8Array([
|
|
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00
|
|
])
|
|
const r2 = await bareOsWasmKernelCompile(minimal)
|
|
t.ok(r2.ok === true || r2.ok === false)
|
|
})
|
|
|
|
test('bareOsWasmKernelInstantiate exposes wall clock import with syscall imports', async (t) => {
|
|
const { bareOsWasmKernelInstantiate } = await import('./lib/boot/bare-os-wasm-kernel.js')
|
|
if (typeof WebAssembly === 'undefined' || !WebAssembly.compile) {
|
|
t.pass('skip: no WebAssembly')
|
|
return
|
|
}
|
|
const wasmMinimal = new Uint8Array([
|
|
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00
|
|
])
|
|
const ctx = { bareOsPathconf: () => '4096' }
|
|
const r = await bareOsWasmKernelInstantiate(wasmMinimal, ctx, {
|
|
wasmSyscallImports: true,
|
|
shellEnv: { BARE_OS_WASM_KERNEL_SYSCALL: '1' }
|
|
})
|
|
if (!r.ok) {
|
|
t.pass('instantiate may fail on minimal module; wall import path exercised when ok')
|
|
return
|
|
}
|
|
t.ok(r.wasmWallClockMs32Import)
|
|
t.ok(r.wasmPathconfImport)
|
|
})
|
|
|
|
test('bareOsWasmKernelInstantiate optional bare_os_monotonic_ms import', async (t) => {
|
|
const { bareOsWasmKernelInstantiate } = await import('./lib/boot/bare-os-wasm-kernel.js')
|
|
if (typeof WebAssembly === 'undefined' || !WebAssembly.compile) {
|
|
t.pass('skip: no WebAssembly')
|
|
return
|
|
}
|
|
const wasmMinimal = new Uint8Array([
|
|
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00
|
|
])
|
|
const ctx = { bareOsPathconf: () => '4096' }
|
|
const r = await bareOsWasmKernelInstantiate(wasmMinimal, ctx, {
|
|
wasmSyscallImports: true,
|
|
shellEnv: {
|
|
BARE_OS_WASM_KERNEL_SYSCALL: '1',
|
|
BARE_OS_WASM_KERNEL_MONOTONIC_MS: '1'
|
|
}
|
|
})
|
|
if (!r.ok) {
|
|
t.pass('instantiate may fail on minimal module')
|
|
return
|
|
}
|
|
t.ok(r.wasmMonotonicMsImport)
|
|
})
|
|
|
|
test('bareOsWasmKernelInstantiate optional bare_os_hostname_peek import flag', async (t) => {
|
|
const { bareOsWasmKernelInstantiate } = await import('./lib/boot/bare-os-wasm-kernel.js')
|
|
if (typeof WebAssembly === 'undefined' || !WebAssembly.compile) {
|
|
t.pass('skip: no WebAssembly')
|
|
return
|
|
}
|
|
const wasmMinimal = new Uint8Array([
|
|
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00
|
|
])
|
|
const ctx = {
|
|
bareOsPathconf: () => '4096',
|
|
vfs: { env: { HOSTNAME: 'guest.test' } }
|
|
}
|
|
const r = await bareOsWasmKernelInstantiate(wasmMinimal, ctx, {
|
|
wasmSyscallImports: true,
|
|
shellEnv: {
|
|
BARE_OS_WASM_KERNEL_SYSCALL: '1',
|
|
BARE_OS_WASM_KERNEL_HOSTNAME_IMPORT: '1'
|
|
}
|
|
})
|
|
if (!r.ok) {
|
|
t.pass('instantiate may fail on minimal module')
|
|
return
|
|
}
|
|
t.ok(r.wasmHostnamePeekImport)
|
|
})
|
|
|
|
test('bareOsWasmKernelInstantiate optional bare_os_posix_profile_peek import flag', async (t) => {
|
|
const { bareOsWasmKernelInstantiate } = await import('./lib/boot/bare-os-wasm-kernel.js')
|
|
if (typeof WebAssembly === 'undefined' || !WebAssembly.compile) {
|
|
t.pass('skip: no WebAssembly')
|
|
return
|
|
}
|
|
const wasmMinimal = new Uint8Array([
|
|
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00
|
|
])
|
|
const ctx = { bareOsPathconf: () => '4096' }
|
|
const r = await bareOsWasmKernelInstantiate(wasmMinimal, ctx, {
|
|
wasmSyscallImports: true,
|
|
shellEnv: {
|
|
BARE_OS_WASM_KERNEL_SYSCALL: '1',
|
|
BARE_OS_WASM_KERNEL_POSIX_PROFILE_PEEK: '1'
|
|
}
|
|
})
|
|
if (!r.ok) {
|
|
t.pass('instantiate may fail on minimal module')
|
|
return
|
|
}
|
|
t.ok(r.wasmPosixProfilePeekImport)
|
|
})
|
|
|
|
test('Protomux cap channel inbound message size bound is 65536 bytes', async (t) => {
|
|
const { BARE_OS_PROTOMUX_CAP_CHANNEL_MESSAGE_MAX_BYTES } = await import(
|
|
'./lib/p2p/swarm-disk.js'
|
|
)
|
|
t.is(BARE_OS_PROTOMUX_CAP_CHANNEL_MESSAGE_MAX_BYTES, 65536)
|
|
})
|
|
|
|
test('bareOsHyperswarmOptsFromEnv parses connection-budget envs', async (t) => {
|
|
const opts = bareOsHyperswarmOptsFromEnv({
|
|
BARE_OS_SWARM_MAX_PEERS: '777',
|
|
BARE_OS_SWARM_MAX_CLIENT_CONNECTIONS: '1234',
|
|
BARE_OS_SWARM_MAX_SERVER_CONNECTIONS: '4321',
|
|
BARE_OS_SWARM_MAX_PARALLEL: '19'
|
|
})
|
|
t.is(opts.maxPeers, 777)
|
|
t.is(opts.maxClientConnections, 1234)
|
|
t.is(opts.maxServerConnections, 4321)
|
|
t.is(opts.maxParallel, 19)
|
|
})
|
|
|
|
test('bareOsHyperswarmOptsFromEnv clamps and ignores invalid values', async (t) => {
|
|
const opts = bareOsHyperswarmOptsFromEnv({
|
|
BARE_OS_SWARM_MAX_PEERS: '999999',
|
|
BARE_OS_SWARM_MAX_CLIENT_CONNECTIONS: '0',
|
|
BARE_OS_SWARM_MAX_SERVER_CONNECTIONS: '-1',
|
|
BARE_OS_SWARM_MAX_PARALLEL: '999'
|
|
})
|
|
t.is(opts.maxPeers, 4096)
|
|
t.absent(opts.maxClientConnections)
|
|
t.absent(opts.maxServerConnections)
|
|
t.is(opts.maxParallel, 64)
|
|
})
|
|
|
|
test('bareOsBootJoinPeerHexFromEnv accepts valid 32-byte hex peer key', async (t) => {
|
|
const v = bareOsBootJoinPeerHexFromEnv({
|
|
BARE_OS_BOOT_JOIN_PEER_HEX: 'A1'.repeat(32)
|
|
})
|
|
t.is(v, 'a1'.repeat(32))
|
|
})
|
|
|
|
test('bareOsBootJoinPeerHexFromEnv rejects invalid direct peer values', async (t) => {
|
|
t.is(bareOsBootJoinPeerHexFromEnv({ BARE_OS_BOOT_JOIN_PEER_HEX: '' }), null)
|
|
t.is(bareOsBootJoinPeerHexFromEnv({ BARE_OS_BOOT_JOIN_PEER_HEX: 'zz' }), null)
|
|
t.is(bareOsBootJoinPeerHexFromEnv({ BARE_OS_BOOT_JOIN_PEER_HEX: 'ab'.repeat(31) }), null)
|
|
})
|
|
|
|
test('bareOsBootInitPrefetchEnabled recognizes boolean-ish env values', async (t) => {
|
|
t.is(bareOsBootInitPrefetchEnabled({ BARE_OS_BOOT_PREFETCH_INIT_JS: '1' }), true)
|
|
t.is(bareOsBootInitPrefetchEnabled({ BARE_OS_BOOT_PREFETCH_INIT_JS: 'true' }), true)
|
|
t.is(bareOsBootInitPrefetchEnabled({ BARE_OS_BOOT_PREFETCH_INIT_JS: 'YES' }), true)
|
|
t.is(bareOsBootInitPrefetchEnabled({ BARE_OS_BOOT_PREFETCH_INIT_JS: 'on' }), true)
|
|
t.is(bareOsBootInitPrefetchEnabled({ BARE_OS_BOOT_PREFETCH_INIT_JS: '0' }), false)
|
|
t.is(bareOsBootInitPrefetchEnabled({ BARE_OS_BOOT_PREFETCH_INIT_JS: 'false' }), false)
|
|
t.is(bareOsBootInitPrefetchEnabled({}), false)
|
|
})
|
|
|
|
test('bareOsBootWarmBinDownloadDiffEnabled recognizes boolean-ish env values', async (t) => {
|
|
t.is(
|
|
bareOsBootWarmBinDownloadDiffEnabled({
|
|
BARE_OS_BOOT_WARM_BIN_DOWNLOAD_DIFF: '1'
|
|
}),
|
|
true
|
|
)
|
|
t.is(
|
|
bareOsBootWarmBinDownloadDiffEnabled({
|
|
BARE_OS_BOOT_WARM_BIN_DOWNLOAD_DIFF: 'true'
|
|
}),
|
|
true
|
|
)
|
|
t.is(
|
|
bareOsBootWarmBinDownloadDiffEnabled({
|
|
BARE_OS_BOOT_WARM_BIN_DOWNLOAD_DIFF: 'YES'
|
|
}),
|
|
true
|
|
)
|
|
t.is(
|
|
bareOsBootWarmBinDownloadDiffEnabled({
|
|
BARE_OS_BOOT_WARM_BIN_DOWNLOAD_DIFF: 'off'
|
|
}),
|
|
false
|
|
)
|
|
t.is(bareOsBootWarmBinDownloadDiffEnabled({}), false)
|
|
})
|
|
|
|
test('SwarmDisk chat re-pair tolerates duplicate createChannel null returns', async (t) => {
|
|
const { SwarmDisk } = await import('./lib/p2p/swarm-disk.js')
|
|
const disk = new SwarmDisk()
|
|
let unpairCalls = 0
|
|
let closeCalls = 0
|
|
const peer = {
|
|
mux: {
|
|
stream: { destroyed: false },
|
|
getLastChannel() {
|
|
return { close: () => closeCalls++ }
|
|
},
|
|
unpair() {
|
|
unpairCalls++
|
|
}
|
|
},
|
|
socket: {},
|
|
chatChan: { close() {} }
|
|
}
|
|
disk.peers.add(peer)
|
|
disk.bareOsChatService = {
|
|
pairOnMux(_disk, _mux, _socket, p) {
|
|
// Simulates protocol setup path where Protomux.createChannel returned null.
|
|
p.chatChan = null
|
|
}
|
|
}
|
|
disk.pairBareOsChatExistingPeers()
|
|
t.is(peer.chatChan, null)
|
|
t.ok(closeCalls >= 1)
|
|
t.is(unpairCalls, 1)
|
|
})
|
|
|
|
test('SwarmDisk meshdrop re-pair tolerates duplicate createChannel null returns', async (t) => {
|
|
const { SwarmDisk } = await import('./lib/p2p/swarm-disk.js')
|
|
const disk = new SwarmDisk()
|
|
let unpairCalls = 0
|
|
let closeCalls = 0
|
|
const peer = {
|
|
mux: {
|
|
stream: { destroyed: false },
|
|
getLastChannel() {
|
|
return { close: () => closeCalls++ }
|
|
},
|
|
unpair() {
|
|
unpairCalls++
|
|
}
|
|
},
|
|
socket: {},
|
|
meshdropChan: { close() {} }
|
|
}
|
|
disk.peers.add(peer)
|
|
disk.bareOsMeshdropService = {
|
|
pairOnMux(_disk, _mux, _socket, p) {
|
|
// Simulates protocol setup path where Protomux.createChannel returned null.
|
|
p.meshdropChan = null
|
|
}
|
|
}
|
|
disk.pairBareOsMeshdropExistingPeers()
|
|
t.is(peer.meshdropChan, null)
|
|
t.ok(closeCalls >= 1)
|
|
t.is(unpairCalls, 1)
|
|
})
|
|
|
|
test('SwarmDisk read(0) resolves when MBR-capable peer joins after read starts', async (t) => {
|
|
const prevMbrTimeout = process.env.BARE_OS_MBR_READ_TIMEOUT_MS
|
|
process.env.BARE_OS_MBR_READ_TIMEOUT_MS = '15000'
|
|
try {
|
|
const net = await import('node:net')
|
|
const Protomux = (await import('protomux')).default
|
|
const { SwarmDisk } = await import('./lib/p2p/swarm-disk.js')
|
|
const { setupSeedChannel, buildMbr, BLOCK_SIZE } = await import(
|
|
'bare-os-protocol'
|
|
)
|
|
const crypto = await import('hypercore-crypto')
|
|
|
|
/** @returns {Promise<{ srvSock: import('net').Socket, cliSock: import('net').Socket }>} */
|
|
const tcpDuplexPair = () =>
|
|
new Promise((resolve, reject) => {
|
|
const srv = net.createServer()
|
|
srv.once('error', reject)
|
|
srv.listen(0, '127.0.0.1', () => {
|
|
const addr = srv.address()
|
|
if (!addr || typeof addr === 'string') {
|
|
reject(new Error('tcpDuplexPair: bad address'))
|
|
return
|
|
}
|
|
const cliSock = net.connect(addr.port, '127.0.0.1')
|
|
srv.once('connection', (srvSock) => {
|
|
srv.close(() => {})
|
|
resolve({ srvSock, cliSock })
|
|
})
|
|
cliSock.once('error', reject)
|
|
})
|
|
})
|
|
|
|
const kp = crypto.keyPair()
|
|
const mbr = buildMbr(kp.publicKey)
|
|
t.is(mbr.byteLength, BLOCK_SIZE)
|
|
|
|
const disk = new SwarmDisk()
|
|
|
|
const { srvSock: coldSrv, cliSock: coldCli } = await tcpDuplexPair()
|
|
const muxColdSeed = new Protomux(coldSrv)
|
|
setupSeedChannel(
|
|
muxColdSeed,
|
|
new Map(),
|
|
() => {},
|
|
{ enableBareOsChatChannel: false }
|
|
)
|
|
const muxBooterCold = new Protomux(coldCli)
|
|
disk.addPeer(muxBooterCold, coldCli)
|
|
|
|
const readP = disk.read(0)
|
|
|
|
const { srvSock: warmSrv, cliSock: warmCli } = await tcpDuplexPair()
|
|
const muxWarmSeed = new Protomux(warmSrv)
|
|
setupSeedChannel(
|
|
muxWarmSeed,
|
|
new Map([[0, mbr]]),
|
|
() => {},
|
|
{ enableBareOsChatChannel: false }
|
|
)
|
|
const muxBooterWarm = new Protomux(warmCli)
|
|
disk.addPeer(muxBooterWarm, warmCli)
|
|
|
|
const buf = await Promise.race([
|
|
readP,
|
|
new Promise((_, rej) =>
|
|
setTimeout(
|
|
() => rej(new Error('MBR read did not resolve in time')),
|
|
4000
|
|
)
|
|
)
|
|
])
|
|
t.ok(buf instanceof Uint8Array)
|
|
t.is(buf.byteLength, BLOCK_SIZE)
|
|
|
|
for (const s of [coldSrv, coldCli, warmSrv, warmCli]) {
|
|
try {
|
|
s.destroy()
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
} finally {
|
|
if (prevMbrTimeout === undefined) delete process.env.BARE_OS_MBR_READ_TIMEOUT_MS
|
|
else process.env.BARE_OS_MBR_READ_TIMEOUT_MS = prevMbrTimeout
|
|
}
|
|
})
|
|
|
|
test('bareOsAppendVaultRotationCheckpoint appends vault_save row', async (t) => {
|
|
const { bareOsAppendVaultRotationCheckpoint } = await import(
|
|
'./lib/identity/bare-os-vault-rotation-audit.js'
|
|
)
|
|
/** @type {{ p: string, buf: Uint8Array }[]} */
|
|
const puts = []
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/.bare/vault-rotation-audit.ndjson') return null
|
|
return null
|
|
},
|
|
async put(p, buf) {
|
|
puts.push({ p, buf: buf instanceof Uint8Array ? buf : b4a.from(buf) })
|
|
}
|
|
}
|
|
const ctx = { personalDrive: drive }
|
|
await bareOsAppendVaultRotationCheckpoint(ctx, { kind: 'vault_save', fileCount: 2 })
|
|
const auditPut = puts.filter((x) => x.p === '/.bare/vault-rotation-audit.ndjson')
|
|
t.is(auditPut.length, 1)
|
|
const last = JSON.parse(b4a.toString(auditPut[0].buf).trim())
|
|
t.is(last.kind, 'vault_save')
|
|
t.is(last.fileCount, 2)
|
|
})
|
|
|
|
test('protomux and hyperswarm versions match repo lock contract', async (t) => {
|
|
const { readFileSync } = await import('node:fs')
|
|
const lockPath = path.join(__dirname, '../../package-lock.json')
|
|
const lock = JSON.parse(readFileSync(lockPath, 'utf8'))
|
|
const pm = lock.packages?.['node_modules/protomux']?.version
|
|
const hs = lock.packages?.['node_modules/hyperswarm']?.version
|
|
t.ok(pm && hs)
|
|
const snapPath = path.join(__dirname, 'fixtures/protomux-hyperswarm-lock.json')
|
|
const snap = JSON.parse(readFileSync(snapPath, 'utf8'))
|
|
t.is(snap.schema, 2)
|
|
t.is(pm, snap.protomux)
|
|
t.is(hs, snap.hyperswarm)
|
|
})
|
|
|
|
test('kernel extension topological order matches guest Kahn tie-break', async (t) => {
|
|
const entries = [
|
|
{ file: 'b.json', extId: 'b', scripts: ['/x'], dependsOn: ['a'] },
|
|
{ file: 'a.json', extId: 'a', scripts: ['/y'], dependsOn: [] }
|
|
]
|
|
const topo = topologicalOrderKernelExtensions(entries)
|
|
t.ok(topo.ok)
|
|
t.is(
|
|
topo.ordered.map((e) => e.file).join(','),
|
|
'a.json,b.json',
|
|
'filename order breaks ties; dependsOn edge b→a'
|
|
)
|
|
})
|
|
|
|
test('kernel extension resolver detects dependency cycle', async (t) => {
|
|
const entries = [
|
|
{ file: 'a.json', extId: 'a', scripts: ['/x'], dependsOn: ['b'] },
|
|
{ file: 'b.json', extId: 'b', scripts: ['/y'], dependsOn: ['a'] }
|
|
]
|
|
const topo = topologicalOrderKernelExtensions(entries)
|
|
t.absent(topo.ok)
|
|
t.ok(
|
|
/** @type {{ cycleExtIds: string[] }} */ (topo).cycleExtIds.length >= 2
|
|
)
|
|
})
|
|
|
|
test('resolveKernelExtensionsFull detects provides version conflict', async (t) => {
|
|
const r = resolveKernelExtensionsFull([
|
|
{
|
|
file: 'a.json',
|
|
extId: 'ea',
|
|
scripts: ['/lib/bare-os/extensions/x.js'],
|
|
dependsOn: [],
|
|
provides: [{ name: 'bare_os.operator.svc', version: '1.0.0' }]
|
|
},
|
|
{
|
|
file: 'b.json',
|
|
extId: 'eb',
|
|
scripts: ['/lib/bare-os/extensions/y.js'],
|
|
dependsOn: [],
|
|
provides: [{ name: 'bare_os.operator.svc', version: '2.0.0' }]
|
|
}
|
|
])
|
|
t.absent(r.ok)
|
|
t.is(r.reason, 'provides_conflict')
|
|
t.ok(Array.isArray(r.conflicts) && r.conflicts.length >= 1)
|
|
})
|
|
|
|
test('protomux alias registry snapshot matches proc wire contract shape', async (t) => {
|
|
const reg = createBareOsProtomuxAliasRegistry()
|
|
reg.register('guestChannel', 'wireChannel')
|
|
const rt = reg.snapshot()
|
|
t.is(rt.schema, 2)
|
|
t.is(rt.aliases.guestChannel, 'wireChannel')
|
|
t.ok(Array.isArray(rt.changeLogTail))
|
|
t.ok(rt.reverseIndex && typeof rt.reverseIndex === 'object')
|
|
})
|
|
|
|
test('createBareOsIpc enforces maxChannels quota', async (t) => {
|
|
const ipc = createBareOsIpc({ maxChannels: 2, maxFifoBytes: 4096 })
|
|
ipc.create('a')
|
|
ipc.create('b')
|
|
let threw = false
|
|
try {
|
|
ipc.create('c')
|
|
} catch {
|
|
threw = true
|
|
}
|
|
t.ok(threw)
|
|
const st = ipc.stats()
|
|
t.is(st.maxChannels, 2)
|
|
t.ok(st.telemetry.fifoCreateDeniedQuota >= 1)
|
|
})
|
|
|
|
test('vfs union write deny + bareOsEvictWarmReadLogicalPath selective eviction', async (t) => {
|
|
const dir = testCorestoreDir('unionvfs')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('uv'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
await sys.put('/bin/u1', b4a.from('a'))
|
|
await sys.put('/bin/u2', b4a.from('b'))
|
|
const warmRef = { current: null }
|
|
const env = {
|
|
HOME: '/home/g',
|
|
PWD: '/',
|
|
PATH: '/bin',
|
|
BARE_OS_VFS_BIN_CACHE: '1'
|
|
}
|
|
const vfs = createVfs(sys, personal, env, null, {
|
|
unionReadPrefixes: ['/bin'],
|
|
unionWriteDenyPrefixes: ['/bin'],
|
|
warmReadCacheStatsRef: warmRef
|
|
})
|
|
let denied = false
|
|
try {
|
|
await vfs.writeFile('/bin/z', b4a.from('x'))
|
|
} catch {
|
|
denied = true
|
|
}
|
|
t.ok(denied, 'union write deny blocks /bin write')
|
|
await vfs.readFile('/bin/u1')
|
|
await vfs.readFile('/bin/u2')
|
|
const hits = warmRef.current?.hits ?? 0
|
|
vfs.bareOsEvictWarmReadLogicalPath('/bin/u1')
|
|
const m0 = warmRef.current?.misses ?? 0
|
|
await vfs.readFile('/bin/u1')
|
|
t.ok((warmRef.current?.misses ?? 0) > m0, 'u1 evicted')
|
|
const hitsU2 = warmRef.current?.hits ?? 0
|
|
await vfs.readFile('/bin/u2')
|
|
t.ok((warmRef.current?.hits ?? 0) > hitsU2, 'u2 still warm')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('disk.os replication_operator_sketch mirrors blind v3 and hyperblobs env JSON', async (t) => {
|
|
const dir = testCorestoreDir('diskosenv')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
await drive.ready()
|
|
const prevB = process.env.BARE_OS_BLIND_PEER_TOPOLOGY_V3_JSON
|
|
const prevH = process.env.BARE_OS_HYPERBLOBS_STATS_JSON
|
|
const prevP = process.env.BARE_OS_REPLICATION_PEER_PRIORITY_JSON
|
|
const prevC = process.env.BARE_OS_CORESTORE_STATS_JSON
|
|
process.env.BARE_OS_BLIND_PEER_TOPOLOGY_V3_JSON = JSON.stringify({
|
|
schema: 3,
|
|
hint: 't'
|
|
})
|
|
process.env.BARE_OS_HYPERBLOBS_STATS_JSON = JSON.stringify({ chunkCount: 4 })
|
|
process.env.BARE_OS_CORESTORE_STATS_JSON = JSON.stringify({
|
|
schema: 1,
|
|
namespaceCount: 2
|
|
})
|
|
process.env.BARE_OS_REPLICATION_PEER_PRIORITY_JSON = JSON.stringify({
|
|
schema: 1,
|
|
orderedPeerKeyHex: ['aa', 'bb']
|
|
})
|
|
try {
|
|
const bridge = createBareOsDiskOsBridge({
|
|
drive,
|
|
bareOsIpc: { list: () => [] },
|
|
ctxApiVersion: BARE_OS_CTX_API_VERSION,
|
|
systemRevision: null,
|
|
bootStartedMs: Date.now()
|
|
})
|
|
const op = await bridge.execRpc('bare_os', 'replication_operator_sketch', [])
|
|
const oj = JSON.parse(op)
|
|
t.is(oj.schema, 8)
|
|
t.is(oj.blindTopologySketchV3?.schema, 3)
|
|
t.is(oj.hyperblobsDedupSketch?.chunkCount, 4)
|
|
t.is(oj.corestoreOperatorSketch?.namespaceCount, 2)
|
|
t.is(oj.peerPrioritySketch?.schema, 1)
|
|
t.is(oj.peerPrioritySketch?.orderedPeerKeyHex?.length, 2)
|
|
} finally {
|
|
if (prevB === undefined) delete process.env.BARE_OS_BLIND_PEER_TOPOLOGY_V3_JSON
|
|
else process.env.BARE_OS_BLIND_PEER_TOPOLOGY_V3_JSON = prevB
|
|
if (prevH === undefined) delete process.env.BARE_OS_HYPERBLOBS_STATS_JSON
|
|
else process.env.BARE_OS_HYPERBLOBS_STATS_JSON = prevH
|
|
if (prevC === undefined) delete process.env.BARE_OS_CORESTORE_STATS_JSON
|
|
else process.env.BARE_OS_CORESTORE_STATS_JSON = prevC
|
|
if (prevP === undefined) delete process.env.BARE_OS_REPLICATION_PEER_PRIORITY_JSON
|
|
else process.env.BARE_OS_REPLICATION_PEER_PRIORITY_JSON = prevP
|
|
}
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('ctx bareOsRunCurlCli works when host delegates curl is disabled', async (t) => {
|
|
const dir = testCorestoreDir('curlctx')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('cc'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(sys, personal, {
|
|
BARE_OS_DELEGATE_ALLOW: 'git',
|
|
HOME: '/home/g',
|
|
PWD: '/',
|
|
PATH: '/bin'
|
|
})
|
|
let ran = false
|
|
ctx.bareOsRunCurlCli = async () => {
|
|
ran = true
|
|
}
|
|
const curlSrc = await readFile(
|
|
path.join(__dirname, '../../kernel/bin/curl'),
|
|
'utf8'
|
|
)
|
|
await sys.put('/bin/curl', b4a.from(curlSrc))
|
|
await runBinCommand(ctx, ['curl', 'http://example.test/x'])
|
|
t.ok(ran)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('host delegate allowlist negatives for curl/wget/git/hrpc/systemctl', async (t) => {
|
|
const allow = parseDelegateAllowSet({ BARE_OS_DELEGATE_ALLOW: 'tar' })
|
|
t.absent(isDelegateKindAllowed('curl', allow))
|
|
t.absent(isDelegateKindAllowed('wget', allow))
|
|
t.absent(isDelegateKindAllowed('git', allow))
|
|
t.absent(isDelegateKindAllowed('hrpc', allow))
|
|
t.absent(isDelegateKindAllowed('systemctl', allow))
|
|
t.ok(isDelegateKindAllowed('tar', allow))
|
|
})
|
|
|
|
test('host delegate strict zero-trust profile defaults to deny', async (t) => {
|
|
const allow = parseDelegateAllowSet({ BARE_OS_ZERO_TRUST_PROFILE: 'strict' })
|
|
t.ok(allow instanceof Set)
|
|
t.is(allow.size, 0)
|
|
t.absent(isDelegateKindAllowed('curl', allow))
|
|
})
|
|
|
|
|