Release rolling / release (push) Successful in 11m16s
Bump published pins (compact-encoding 3, bare-fetch/tls/https/ws 3, bare-subprocess 6, bare-signals 5, corestore 7.12, protomux 3.11, hypercore-crypto 3.7, bare-runtime 1.31) and regenerate catalogs, manifests, and kernel/seeder bundles. Adapt call sites to the new APIs: - Corestore: explicit session flush before suspend(); treeCache ctor opts - bare-crypto: KeyObject.export() instead of removed ._key - Protomux 3.11: wait for fullyOpened()/fullyClosed() on chat channels - bare-fetch: surface response.type and Headers.getSetCookie - host snapshots: bare-os 3.9 / bare-posix / bare-fs.statfs frsize - bare-subprocess 6: optional IPC channel + json serialization Keep catalog sync from wiping curated pearEntries. Teach the Node test shim to stub bare-thread/bare-worker (ESM absolute paths) and chain Bare.on so bare-timers can load. Booter 479, protocol 34, seeder 14.
10430 lines
322 KiB
JavaScript
10430 lines
322 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 { createRequire } from 'node:module'
|
|
import { PassThrough } from 'node:stream'
|
|
import { XTERM_CLEAR_SCROLLBACK_AND_VIEWPORT } from './lib/terminal-escapes.js'
|
|
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,
|
|
sanitizeInteractiveShellLine
|
|
} from './lib/cli-readline.js'
|
|
import {
|
|
createVfs,
|
|
BARE_OS_PROC_FILE_TO_ID_HYPERCORE_PACK_HRPC_LIFECYCLE
|
|
} from './lib/vfs.js'
|
|
import { parseHdmsPairArgv } from './lib/hdms-cli-argv.js'
|
|
import hcCrypto from 'hypercore-crypto'
|
|
import idEnc from 'hypercore-id-encoding'
|
|
import {
|
|
decodeRwShareOffer,
|
|
decodeShareOffer,
|
|
HDMS_AUTOPASS_SHARE_KEY,
|
|
HDMS_AUTOPASS_SHARE_RW_KEY,
|
|
normalizeWriterSecretHex
|
|
} from './lib/hdms-share-offer.js'
|
|
import { buildBareOsHypercorePackHrpcLifecycleProcJson } from './lib/bare-os-proc-hypercore-pack-hrpc-lifecycle.js'
|
|
import { buildBareOsPearCorestoreHrpcProcJson } from './lib/bare-os-proc-pear-corestore-hrpc.js'
|
|
import { buildBareOsPearInspectLoggerTlsProcJson } from './lib/bare-os-proc-pear-inspect-logger-tls.js'
|
|
import { buildBareOsBareRuntimeProtoMuxProcJson } from './lib/bare-os-proc-bare-runtime-proto-mux.js'
|
|
import { buildBareOsBootGraphProcJson } from './lib/bare-os-boot-graph-proc.js'
|
|
import { pathnameExpandShellWord } from './lib/shell-glob.js'
|
|
import { createBareOsIpc } from './lib/bare-os-ipc.js'
|
|
import { BareOsSwarmConnectionManager } from './lib/bare-os-swarm-connection-manager.js'
|
|
import {
|
|
createBareOsLifecycleStateMachine,
|
|
runTransactionalLifecycleHooks
|
|
} from './lib/bare-os-lifecycle-manager.js'
|
|
import {
|
|
bareOsRegisterCorestoreSuspendResumeHooks,
|
|
bareOsInstallCorestoreSuspendResumeHooks
|
|
} from './lib/corestore-host-lifecycle.js'
|
|
import { bareOsFindOrphanProcessRows } from './lib/bare-os-process-table.js'
|
|
import {
|
|
topologicalOrderKernelExtensions,
|
|
resolveKernelExtensionsFull
|
|
} from './lib/kernel-extension-resolver.js'
|
|
import { createBareOsProtomuxAliasRegistry } from './lib/bare-os-protomux-alias-registry.js'
|
|
import { bareOsHttpUrlAllowed } from './lib/bare-os-http-policy.js'
|
|
import {
|
|
bareOsParseBareAclText,
|
|
bareOsAclDeniesSubject
|
|
} from './lib/bare-os-vfs-acl-enforce.js'
|
|
import { raceWithAbortAndTimeout } from './lib/bare-os-abort.js'
|
|
import {
|
|
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,
|
|
} from './lib/shell.js'
|
|
import { tokenizeBareShellLineDetailed } from './lib/shell-tokenizer.js'
|
|
import { decodeBareOsDollarQuote } from './lib/shell-lex.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,
|
|
bareOsBareModuleManifestEmbeddedRef,
|
|
buildBareCtxObjectFromHost,
|
|
buildPearCtxObjectFromHost,
|
|
loadBareModuleManifest,
|
|
maybeMergeBareFromDrive
|
|
} from './lib/bare-os-ctx-bare.js'
|
|
import { maybeBareOsBuildBinManifest } from './lib/bare-os-bin-index.js'
|
|
import {
|
|
createFishReadLine,
|
|
fuzzyMatch,
|
|
stripAnsi,
|
|
parseHistoryFile,
|
|
formatHistoryFile,
|
|
dedupeConsecutiveHistory,
|
|
searchHistoryEntries,
|
|
shouldPersistShellHistoryCommand,
|
|
scrubShellHistoryEntries,
|
|
SHELL_BUILTINS,
|
|
resolveShellPromptHookSegment
|
|
} from './lib/fish-readline.js'
|
|
import {
|
|
parseCompletionContext,
|
|
rankCompletionItems,
|
|
suggestGhostFromHistory,
|
|
suggestGhostFromFs,
|
|
ghostReplaceCurrentWord,
|
|
longestCommonCompletionPrefix,
|
|
completeLine,
|
|
levenshtein
|
|
} from './lib/completion-engine.js'
|
|
import {
|
|
fieldMatches,
|
|
dowFieldMatches,
|
|
parseCronLine,
|
|
jobMatchesDate
|
|
} from './lib/bare-cron.js'
|
|
import './lib/bare-openssh.js'
|
|
import {
|
|
bareInitdReadinessSnapshot,
|
|
bareInitdShutdownActiveUnitsReverse,
|
|
getBareServiceRuntime,
|
|
registerBareInitdDisposer,
|
|
registerKernelShutdownHook,
|
|
runKernelShutdownHooks,
|
|
startBareInitd,
|
|
stopBareInitd,
|
|
getLastBareInitdDagSnapshotJson
|
|
} from './lib/bare-initd.js'
|
|
import {
|
|
appendVarLog,
|
|
ensureBareOsVarLogTree,
|
|
KERNEL_CONSOLE_LOG,
|
|
BOOT_LOG
|
|
} from './lib/bare-os-var-log.js'
|
|
import {
|
|
bareOsKernelMetricsReset,
|
|
bareOsKernelMetricsSnapshot
|
|
} from './lib/bare-os-kernel-metrics.js'
|
|
import { parseUnitDropInText } from './lib/bare-initd-user.js'
|
|
import {
|
|
bareOsHyperswarmOptsFromEnv,
|
|
bareOsBootJoinPeerHexFromEnv
|
|
} from './lib/swarm-opts.js'
|
|
import {
|
|
bareOsBootInitPrefetchEnabled,
|
|
bareOsBootWarmBinDownloadDiffEnabled
|
|
} from './lib/boot-prefetch.js'
|
|
import { bareOsProcessTableSnapshot } from './lib/bare-os-process-table.js'
|
|
import { buildBareOsSyscallsProcJson } from './lib/bare-os-syscalls-proc-json.js'
|
|
import {
|
|
bareOsParseSendmsgScmRights,
|
|
wantPosixSocketScmRights
|
|
} from './lib/bare-os-socket-scm-rights.js'
|
|
import {
|
|
parseBareOsHrpcAllowlistJson,
|
|
bareOsHrpcAllowlistDeniesRoute
|
|
} from './lib/bare-os-hrpc-allowlist.js'
|
|
import { buildBareOsProtomuxExtensionsProcJson } from './lib/bare-os-protomux-extensions-proc.js'
|
|
import { createBareOsDiskOsBridge } from './lib/bare-os-disk-os-bridge.js'
|
|
import { buildBareOsReplicationLiveSketch } from './lib/bare-os-replication-proc-live.js'
|
|
import {
|
|
evaluateBareOsPeerAdmission,
|
|
bareOsFormatPeerAdmissionAuditEvent,
|
|
bareOsPeerAdmissionAuditRateAllow
|
|
} from './lib/bare-os-peer-admission.js'
|
|
import {
|
|
parseDelegateAllowSet,
|
|
isDelegateKindAllowed
|
|
} from './lib/host-delegate-registry.js'
|
|
import {
|
|
bareOsAuditAppend,
|
|
bareOsAuditVerifyRows
|
|
} from './lib/bare-os-audit-chain.js'
|
|
import { bareOsDrainFcntlLockWaiters } from './lib/bare-os-fcntl-cooperative-lock.js'
|
|
import { buildBareOsReplicationOperatorSurfaceProcJson } from './lib/bare-os-proc-replication-operator-surface.js'
|
|
import {
|
|
DEFAULT_CURL_USER_AGENT,
|
|
DEFAULT_WGET_USER_AGENT
|
|
} from './lib/http-fetch-url.js'
|
|
import { filenameFromContentDisposition } from './lib/curl-cli.js'
|
|
import { encodeUstarHeader, runTarCli } from './lib/tar-cli.js'
|
|
|
|
/** bare-ssh2 needs Bare `require.addon` (bare-crypto); skip auto-start in brittle-node. */
|
|
if (
|
|
typeof process !== 'undefined' &&
|
|
process.env &&
|
|
process.env.BARE_OS_INITD_DISABLED === undefined
|
|
) {
|
|
process.env.BARE_OS_INITD_DISABLED = 'bare-openssh'
|
|
}
|
|
|
|
const require = createRequire(import.meta.url)
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
|
|
const TAR_BLK = 512
|
|
|
|
/** @param {Uint8Array[]} parts */
|
|
function concatTarParts(parts) {
|
|
let n = 0
|
|
for (const p of parts) n += p.length
|
|
const out = new Uint8Array(n)
|
|
let o = 0
|
|
for (const p of parts) {
|
|
out.set(p, o)
|
|
o += p.length
|
|
}
|
|
return out
|
|
}
|
|
|
|
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',
|
|
BARE_OS_SSH_LISTEN_PORT: '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, 3)
|
|
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('parseUnitDropInText parses ConditionPathExists and Restart keys', async (t) => {
|
|
const d = parseUnitDropInText(
|
|
'[Unit]\nConditionPathExists=/tmp/x\nRestart=on-failure\nRestartSec=2\n'
|
|
)
|
|
t.is(d.conditionPathExists, '/tmp/x')
|
|
t.is(d.restart, 'on-failure')
|
|
t.is(d.restartSec, 2)
|
|
})
|
|
|
|
test('bareOsIpc.signalProcessGroup pushes bareOsProcessGroupSignal to scoped channels', async (t) => {
|
|
const ipc = createBareOsIpc()
|
|
ipc.create('c1')
|
|
ipc.create('c2')
|
|
ipc.assignProcessGroup('c1', 7)
|
|
ipc.assignProcessGroup('c2', 7)
|
|
const p1 = ipc.takeJson('c1')
|
|
const p2 = ipc.takeJson('c2')
|
|
const out = ipc.signalProcessGroup(7, 'SIGUSR1')
|
|
t.is(out.delivered, 2)
|
|
const j1 = await p1
|
|
const j2 = await p2
|
|
t.is(j1.method, 'bareOsProcessGroupSignal')
|
|
t.is(j1.signal, 'SIGUSR1')
|
|
t.is(j2.pgid, 7)
|
|
const st = ipc.stats()
|
|
t.ok(st.processGroups && st.processGroups.byPgid)
|
|
t.ok(st.processGroups.byPgid['pgid:7'].includes('c1'))
|
|
})
|
|
|
|
test('kernel ext incremental reload runs only new scripts when hot reload env set', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
/** @type {string[]} */
|
|
const extDir = ['01-a.json']
|
|
/** @type {string[]} */
|
|
const ran = []
|
|
const vfsFiles = new Map()
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/os-release') return b4a.from('ID=test\n')
|
|
if (p === '/etc/motd') return b4a.from('')
|
|
if (p === '/etc/bare-os/kernel.ext.d/01-a.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
id: 'a',
|
|
scripts: ['/lib/bare-os/extensions/a.js']
|
|
})
|
|
)
|
|
}
|
|
if (p === '/etc/bare-os/kernel.ext.d/02-b.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
id: 'b',
|
|
scripts: ['/lib/bare-os/extensions/b.js']
|
|
})
|
|
)
|
|
}
|
|
return null
|
|
},
|
|
async *readdir(d) {
|
|
if (d === '/etc/bare-os/kernel.ext.d') {
|
|
for (const x of extDir) yield x
|
|
}
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: {
|
|
BARE_OS_BOOT_SKIP_STAGES:
|
|
'profile,rc,rc.d,rc.local,kernel.d,onboot,selftest',
|
|
BARE_OS_KERNEL_EXT_D_HOT_RELOAD: '1'
|
|
},
|
|
drive,
|
|
b4a,
|
|
vfs: {
|
|
async readFile(p) {
|
|
return vfsFiles.get(p) ?? null
|
|
},
|
|
async writeFile(p, body) {
|
|
vfsFiles.set(
|
|
p,
|
|
body instanceof Uint8Array ? body : b4a.from(String(body))
|
|
)
|
|
}
|
|
},
|
|
console: { log() {}, error() {}, warn() {} },
|
|
readLine: async () => null,
|
|
async execLine() {
|
|
return 'ok'
|
|
},
|
|
async bareOsRunImageScript(path) {
|
|
ran.push(path)
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.alike(ran, ['/lib/bare-os/extensions/a.js'])
|
|
t.ok(typeof ctx.bareOsReloadKernelExtDropinsSafe === 'function')
|
|
extDir.push('02-b.json')
|
|
const out = await ctx.bareOsReloadKernelExtDropinsSafe()
|
|
t.ok(out.ok)
|
|
t.alike(out.ranScripts, ['/lib/bare-os/extensions/b.js'])
|
|
t.alike(ran, [
|
|
'/lib/bare-os/extensions/a.js',
|
|
'/lib/bare-os/extensions/b.js'
|
|
])
|
|
const jr = b4a.toString(vfsFiles.get('/run/bare-os/kernel-ext-reload.ndjson'))
|
|
t.ok(jr.includes('incremental'))
|
|
})
|
|
|
|
test('kernel.ext.d minCtxApiVersion skips drop-in when ctx API too low', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
/** @type {string[]} */
|
|
const ran = []
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/os-release') return b4a.from('ID=test\n')
|
|
if (p === '/etc/motd') return b4a.from('')
|
|
if (p === '/etc/bare-os/kernel.ext.d/01-hi.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
id: 'hi',
|
|
minCtxApiVersion: '99.0.0',
|
|
scripts: ['/lib/bare-os/extensions/hi.js']
|
|
})
|
|
)
|
|
}
|
|
return null
|
|
},
|
|
async *readdir(d) {
|
|
if (d === '/etc/bare-os/kernel.ext.d') yield '01-hi.json'
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
bareOsCtxApiVersion: '1.0.0',
|
|
env: {
|
|
BARE_OS_BOOT_SKIP_STAGES:
|
|
'profile,rc,rc.d,rc.local,kernel.d,onboot,selftest'
|
|
},
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {}, warn() {} },
|
|
readLine: async () => null,
|
|
async execLine() {
|
|
return 'ok'
|
|
},
|
|
async bareOsRunImageScript(path) {
|
|
ran.push(path)
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.is(ran.length, 0)
|
|
})
|
|
|
|
test('kernel.ext.d conflictsWith strict skips extension scripts', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
/** @type {string[]} */
|
|
const ran = []
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/os-release') return b4a.from('ID=test\n')
|
|
if (p === '/etc/motd') return b4a.from('')
|
|
if (p === '/etc/bare-os/kernel.ext.d/01-a.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
id: 'a',
|
|
conflictsWith: ['b'],
|
|
scripts: ['/lib/bare-os/extensions/a.js']
|
|
})
|
|
)
|
|
}
|
|
if (p === '/etc/bare-os/kernel.ext.d/02-b.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
id: 'b',
|
|
scripts: ['/lib/bare-os/extensions/b.js']
|
|
})
|
|
)
|
|
}
|
|
return null
|
|
},
|
|
async *readdir(d) {
|
|
if (d === '/etc/bare-os/kernel.ext.d') {
|
|
yield '01-a.json'
|
|
yield '02-b.json'
|
|
}
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: {
|
|
BARE_OS_BOOT_SKIP_STAGES:
|
|
'profile,rc,rc.d,rc.local,kernel.d,onboot,selftest',
|
|
BARE_OS_BOOT_POLICY_STRICT: '1'
|
|
},
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {}, warn() {} },
|
|
readLine: async () => null,
|
|
async execLine() {
|
|
return 'ok'
|
|
},
|
|
async bareOsRunImageScript(p) {
|
|
ran.push(p)
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.is(ran.length, 0)
|
|
})
|
|
|
|
test('kernel.ext.d dependency cycle strict skips extension scripts', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
/** @type {string[]} */
|
|
const ran = []
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/os-release') return b4a.from('ID=test\n')
|
|
if (p === '/etc/motd') return b4a.from('')
|
|
if (p === '/etc/bare-os/kernel.ext.d/01-a.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
id: 'a',
|
|
dependsOn: ['b'],
|
|
scripts: ['/lib/bare-os/extensions/a.js']
|
|
})
|
|
)
|
|
}
|
|
if (p === '/etc/bare-os/kernel.ext.d/02-b.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
id: 'b',
|
|
dependsOn: ['a'],
|
|
scripts: ['/lib/bare-os/extensions/b.js']
|
|
})
|
|
)
|
|
}
|
|
return null
|
|
},
|
|
async *readdir(d) {
|
|
if (d === '/etc/bare-os/kernel.ext.d') {
|
|
yield '01-a.json'
|
|
yield '02-b.json'
|
|
}
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: {
|
|
BARE_OS_BOOT_SKIP_STAGES:
|
|
'profile,rc,rc.d,rc.local,kernel.d,onboot,selftest',
|
|
BARE_OS_BOOT_POLICY_STRICT: '1'
|
|
},
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {}, warn() {} },
|
|
readLine: async () => null,
|
|
async execLine() {
|
|
return 'ok'
|
|
},
|
|
async bareOsRunImageScript(p) {
|
|
ran.push(p)
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.is(ran.length, 0)
|
|
})
|
|
|
|
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('boot.policy denySeedRpcMethods merges into kernel env deny list', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/os-release') return b4a.from('ID=test\n')
|
|
if (p === '/etc/motd') return b4a.from('')
|
|
if (p === '/etc/bare-os/boot.policy.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
denySeedRpcMethods: ['replication_queue', 'capabilities']
|
|
})
|
|
)
|
|
}
|
|
return null
|
|
},
|
|
async *readdir() {}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: {
|
|
BARE_OS_BOOT_POLICY: '1',
|
|
BARE_OS_BOOT_SKIP_STAGES:
|
|
'profile,rc,rc.d,rc.local,kernel.d,onboot,selftest',
|
|
BARE_OS_BOOT_POLICY_DENY_SEED_RPC_METHODS: 'version,replication_queue'
|
|
},
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {}, warn() {} },
|
|
readLine: async () => null,
|
|
async execLine() {
|
|
return 'ok'
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.is(
|
|
ctx.env.BARE_OS_BOOT_POLICY_DENY_SEED_RPC_METHODS,
|
|
'version,replication_queue,capabilities'
|
|
)
|
|
})
|
|
|
|
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)
|
|
const bootSteps = phases.filter(
|
|
(o) => o && typeof o.ms === 'number' && o.type !== 'bootOutput'
|
|
)
|
|
t.ok(bootSteps.length >= 3)
|
|
t.ok(bootSteps.some((o) => o.phase === 'rc.local'))
|
|
t.ok(bootSteps.some((o) => o.step === 'rc.local'))
|
|
t.ok(bootSteps.some((o) => o.phase === 'kernel.d'))
|
|
t.ok(bootSteps.some((o) => o.step === 'kernel.d'))
|
|
t.ok(bootSteps.every((o) => o.bootTraceSchemaVersion === 2))
|
|
t.ok(bootSteps.every((o) => typeof o.ms === 'number'))
|
|
t.ok(
|
|
phases.some(
|
|
(o) => o && o.type === 'bootOutput' && o.step === 'banner'
|
|
),
|
|
'bootOutput trace for guest-visible banner'
|
|
)
|
|
})
|
|
|
|
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('stock kernel BARE_OS_BOOT_DRY_RUN skips execLine and reports bootDryRun in boot proc', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
/** @type {string[]} */
|
|
const errs = []
|
|
/** @type {string[]} */
|
|
const execCalls = []
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/os-release') return b4a.from('ID=test\n')
|
|
if (p === '/etc/motd') return b4a.from('')
|
|
if (p === '/etc/bare-os/rc') return b4a.from('echo dry-rc\n')
|
|
if (p === '/etc/bare-os/onboot') return b4a.from('echo dry-onboot\n')
|
|
return null
|
|
},
|
|
async *readdir() {
|
|
/* empty */
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: { BARE_OS_BOOT_DRY_RUN: '1' },
|
|
drive,
|
|
b4a,
|
|
console: {
|
|
log() {},
|
|
warn() {},
|
|
error: (...a) => errs.push(a.join(' '))
|
|
},
|
|
readLine: async () => null,
|
|
async execLine(line) {
|
|
execCalls.push(String(line || ''))
|
|
return 'should-not-run'
|
|
},
|
|
bareOsPublishBootReady(patch) {
|
|
this._bootReady = patch
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.is(execCalls.length, 0)
|
|
t.ok(
|
|
errs.some(
|
|
(m) =>
|
|
m.includes('bootDryRunExecLine') ||
|
|
m.includes('bootDryRunOnboot') ||
|
|
m.includes('[boot-dry-run]')
|
|
)
|
|
)
|
|
t.is(ctx._bootReady?.subsystems?.kernel?.programProc?.bootDryRun, true)
|
|
})
|
|
|
|
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('createStreamLineReader yields lines after carriage return (SSH PTY)', async (t) => {
|
|
const stdin = new PassThrough()
|
|
const stdout = { write() {} }
|
|
const readLine = createStreamLineReader(stdin, stdout)
|
|
const p = readLine('> ')
|
|
stdin.write('pwd\r')
|
|
t.is(await p, 'pwd')
|
|
const p2 = readLine('> ')
|
|
stdin.write('a\r\n')
|
|
t.is(await p2, 'a')
|
|
stdin.end()
|
|
})
|
|
|
|
test('sanitizeInteractiveShellLine applies DEL and strips controls', async (t) => {
|
|
t.is(sanitizeInteractiveShellLine('ls'), 'ls')
|
|
t.is(sanitizeInteractiveShellLine('a\x7Fb'), 'b')
|
|
t.is(sanitizeInteractiveShellLine('ab\x08'), 'a')
|
|
t.is(sanitizeInteractiveShellLine('x\x01y'), 'xy')
|
|
})
|
|
|
|
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('Hyperdrive checkout read-only boot probe', async (t) => {
|
|
const dir = testCorestoreDir('seed-checkout-ro')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
await drive.ready()
|
|
await drive.put('/boot/init.js', b4a.from('async function start() { return 1 }'))
|
|
const version = drive.version
|
|
const ro = drive.checkout(version, { writable: false })
|
|
const buf = await ro.get('/boot/init.js')
|
|
t.ok(buf)
|
|
t.is(b4a.toString(buf), 'async function start() { return 1 }')
|
|
await t.exception(async () => {
|
|
await ro.put('/boot/init.js', b4a.from('mutate'))
|
|
})
|
|
await ro.close()
|
|
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('vfs syscalls.json uses schema 8 when provider wired', async (t) => {
|
|
const dir = testCorestoreDir('syscallsproc')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('scp'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const sample = {
|
|
schemaVersion: 8,
|
|
ctxApiVersion: BARE_OS_CTX_API_VERSION,
|
|
ops: ['readFile'],
|
|
errnoHints: { ENOENT: 2 },
|
|
fdModel: { schema: 1, stdio: [0, 1, 2] },
|
|
signalModel: { schema: 1, names: ['INT'] },
|
|
atMs: 0
|
|
}
|
|
const vfs = createVfs(
|
|
sys,
|
|
personal,
|
|
{ HOME: '/home/g', PWD: '/', PATH: '/bin' },
|
|
null,
|
|
{
|
|
procBareOsSyscallsText() {
|
|
return `${JSON.stringify(sample)}\n`
|
|
}
|
|
}
|
|
)
|
|
const buf = await vfs.readFile('/proc/bare_os/syscalls.json')
|
|
const j = JSON.parse(b4a.toString(buf))
|
|
t.is(j.schemaVersion, 8)
|
|
t.ok(j.fdModel && j.fdModel.schema === 1)
|
|
t.ok(j.signalModel && Array.isArray(j.signalModel.names))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs /dev/shm named segment write read unlink roundtrip', async (t) => {
|
|
const dir = testCorestoreDir('devshm')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('shm'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const vfs = createVfs(sys, personal, {
|
|
HOME: '/home/g',
|
|
PWD: '/',
|
|
PATH: '/bin'
|
|
})
|
|
const names0 = (await vfs.readdir('/dev')).slice().sort()
|
|
t.ok(names0.includes('shm'))
|
|
await vfs.writeFile('/dev/shm/seg1', b4a.from('abc'))
|
|
const names1 = await vfs.readdir('/dev/shm')
|
|
t.ok(names1.includes('seg1'))
|
|
t.is(b4a.toString(await vfs.readFile('/dev/shm/seg1')), 'abc')
|
|
await vfs.unlink('/dev/shm/seg1')
|
|
const names2 = await vfs.readdir('/dev/shm')
|
|
t.absent(names2.includes('seg1'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs /dev/shm enforces BARE_OS_SHM_MAX_BYTES quota', async (t) => {
|
|
const dir = testCorestoreDir('devshm-quota')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('shmquota'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const vfs = createVfs(sys, personal, {
|
|
HOME: '/home/g',
|
|
PWD: '/',
|
|
PATH: '/bin',
|
|
BARE_OS_SHM_MAX_BYTES: '4'
|
|
})
|
|
await vfs.writeFile('/dev/shm/seg1', b4a.from('abcd'))
|
|
await t.exception(async () => {
|
|
await vfs.writeFile('/dev/shm/seg2', b4a.from('e'))
|
|
})
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs /dev/shm quota accounts for overwrite replacement size', async (t) => {
|
|
const dir = testCorestoreDir('devshm-quota-overwrite')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('shmquota2'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const vfs = createVfs(sys, personal, {
|
|
HOME: '/home/g',
|
|
PWD: '/',
|
|
PATH: '/bin',
|
|
BARE_OS_SHM_MAX_BYTES: '4'
|
|
})
|
|
await vfs.writeFile('/dev/shm/seg1', b4a.from('abcd'))
|
|
await vfs.writeFile('/dev/shm/seg1', b4a.from('ab'))
|
|
await vfs.writeFile('/dev/shm/seg2', b4a.from('cd'))
|
|
t.is(b4a.toString(await vfs.readFile('/dev/shm/seg2')), 'cd')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('/proc/bare_os/index.json has stable schema and sorted entry names', async (t) => {
|
|
const dir = testCorestoreDir('proc-index-schema-sort')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('procindex'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const vfs = createVfs(sys, personal, {
|
|
HOME: '/home/g',
|
|
PWD: '/',
|
|
PATH: '/bin'
|
|
})
|
|
|
|
const raw = await vfs.readFile('/proc/bare_os/index.json')
|
|
const idx = JSON.parse(b4a.toString(raw))
|
|
t.is(idx.schema, 10)
|
|
t.ok(Array.isArray(idx.entries))
|
|
|
|
const names = idx.entries.map((e) => String(e?.name || ''))
|
|
const sorted = [...names].sort((a, b) => a.localeCompare(b))
|
|
t.alike(names, sorted, 'proc alias names stay lexicographically sorted')
|
|
t.is(new Set(names).size, names.length, 'proc alias names remain unique')
|
|
|
|
for (const required of [
|
|
'hyperdb_readonly_index.json',
|
|
'pear_sidecar_bundle_index.json',
|
|
'hyperdrive_sparse_index.json',
|
|
'indexer_catchup.json',
|
|
'multisig_quorum_pointer.json',
|
|
'wave11_operator_slo_v2.json'
|
|
]) {
|
|
const row = idx.entries.find((e) => e?.name === required)
|
|
t.ok(row, `index entry present: ${required}`)
|
|
t.is(row.path, `/proc/bare_os/${required}`)
|
|
}
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs /bin warm read cache hit then bareOsClearWarmReadCaches resets', async (t) => {
|
|
const dir = testCorestoreDir('warmcache')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('wc'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
await sys.put('/bin/warmX', b4a.from('payload'))
|
|
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, {
|
|
warmReadCacheStatsRef: warmRef
|
|
})
|
|
await vfs.readFile('/bin/warmX')
|
|
const h0 = warmRef.current?.hits ?? 0
|
|
await vfs.readFile('/bin/warmX')
|
|
t.ok((warmRef.current?.hits ?? 0) > h0, 'second read should warm-hit')
|
|
vfs.bareOsClearWarmReadCaches()
|
|
t.is(warmRef.current?.hits, 0)
|
|
t.is(warmRef.current?.misses, 0)
|
|
await vfs.readFile('/bin/warmX')
|
|
t.ok((warmRef.current?.misses ?? 0) >= 1, 'after clear, read counts as miss again')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs warm cache invalidates /bin entries on replication-length prefix eviction', async (t) => {
|
|
const dir = testCorestoreDir('warmcache-repl-prefix')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('wcrp'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
await sys.put('/bin/replA', b4a.from('v1'))
|
|
/** @type {{ current: any }} */
|
|
const warmRef = { current: null }
|
|
const vfs = createVfs(
|
|
sys,
|
|
personal,
|
|
{
|
|
HOME: '/home/g',
|
|
PWD: '/',
|
|
PATH: '/bin',
|
|
BARE_OS_VFS_BIN_CACHE: '1'
|
|
},
|
|
null,
|
|
{ warmReadCacheStatsRef: warmRef }
|
|
)
|
|
|
|
await vfs.readFile('/bin/replA') // miss
|
|
const h0 = warmRef.current?.hits ?? 0
|
|
await vfs.readFile('/bin/replA') // hit
|
|
t.ok((warmRef.current?.hits ?? 0) > h0)
|
|
|
|
const evicted = vfs.bareOsEvictWarmReadPrefixes(['/bin/'])
|
|
t.ok(evicted >= 1, 'replication prefix eviction removes warm /bin row(s)')
|
|
t.ok(warmRef.current?.replicationPrefixEviction)
|
|
t.ok((warmRef.current?.replicationPrefixEviction?.pathsEvicted ?? 0) >= 1)
|
|
|
|
const missBefore = warmRef.current?.misses ?? 0
|
|
await vfs.readFile('/bin/replA') // miss again after eviction
|
|
t.ok((warmRef.current?.misses ?? 0) > missBefore)
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs warm cache replication prefix eviction is selective', async (t) => {
|
|
const dir = testCorestoreDir('warmcache-repl-selective')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('wcrs'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
await sys.put('/bin/replB', b4a.from('bin-v1'))
|
|
await sys.put('/lib/bare/bundles/replBundle.js', b4a.from('bundle-v1'))
|
|
/** @type {{ current: any }} */
|
|
const warmRef = { current: null }
|
|
const vfs = createVfs(
|
|
sys,
|
|
personal,
|
|
{
|
|
HOME: '/home/g',
|
|
PWD: '/',
|
|
PATH: '/bin',
|
|
BARE_OS_VFS_BIN_CACHE: '1',
|
|
BARE_OS_VFS_LIB_BARE_CACHE: '1'
|
|
},
|
|
null,
|
|
{ warmReadCacheStatsRef: warmRef }
|
|
)
|
|
|
|
await vfs.readFile('/bin/replB')
|
|
await vfs.readFile('/lib/bare/bundles/replBundle.js')
|
|
const hits0 = warmRef.current?.hits ?? 0
|
|
await vfs.readFile('/bin/replB')
|
|
await vfs.readFile('/lib/bare/bundles/replBundle.js')
|
|
t.ok((warmRef.current?.hits ?? 0) > hits0)
|
|
|
|
const evicted = vfs.bareOsEvictWarmReadPrefixes(['/bin/'])
|
|
t.ok(evicted >= 1)
|
|
|
|
const missesBefore = warmRef.current?.misses ?? 0
|
|
await vfs.readFile('/bin/replB') // should miss
|
|
const missesAfterBin = warmRef.current?.misses ?? 0
|
|
t.ok(missesAfterBin > missesBefore)
|
|
await vfs.readFile('/lib/bare/bundles/replBundle.js') // still warm-hit
|
|
t.ok((warmRef.current?.hits ?? 0) > hits0)
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs writeFile on system /bin evicts warm-cache row for that path', async (t) => {
|
|
const dir = testCorestoreDir('warmcachewrite')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('wcw'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
await sys.put('/bin/warmW', b4a.from('v1'))
|
|
const warmRef = { current: null }
|
|
const env = {
|
|
HOME: '/home/g',
|
|
PWD: '/',
|
|
PATH: '/bin',
|
|
BARE_OS_VFS_BIN_CACHE: '1',
|
|
BARE_OS_VFS_SYSTEM_IMAGE_WRITE: '1'
|
|
}
|
|
const vfs = createVfs(sys, personal, env, null, {
|
|
warmReadCacheStatsRef: warmRef
|
|
})
|
|
t.is(b4a.toString(await vfs.readFile('/bin/warmW')), 'v1')
|
|
const hitsAfterFirst = warmRef.current?.hits ?? 0
|
|
t.is(b4a.toString(await vfs.readFile('/bin/warmW')), 'v1')
|
|
t.ok((warmRef.current?.hits ?? 0) > hitsAfterFirst)
|
|
await vfs.writeFile('/bin/warmW', b4a.from('v2'))
|
|
t.is(b4a.toString(await vfs.readFile('/bin/warmW')), 'v2')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs bareOsEvictLibBareBundlesFromManifest evicts only listed bundle paths', async (t) => {
|
|
const dir = testCorestoreDir('manifestevict')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('me'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
await sys.put('/lib/bare/bundles/b4a.js', b4a.from('b4a'))
|
|
await sys.put('/lib/bare/bundles/safetyCatch.js', b4a.from('sc'))
|
|
const warmRef = { current: null }
|
|
const env = {
|
|
HOME: '/home/g',
|
|
PWD: '/',
|
|
PATH: '/bin',
|
|
BARE_OS_VFS_BIN_CACHE: '1',
|
|
BARE_OS_VFS_LIB_BARE_CACHE: '1'
|
|
}
|
|
const vfs = createVfs(sys, personal, env, null, {
|
|
warmReadCacheStatsRef: warmRef
|
|
})
|
|
await vfs.readFile('/lib/bare/bundles/b4a.js')
|
|
await vfs.readFile('/lib/bare/bundles/safetyCatch.js')
|
|
const hitsBefore = warmRef.current?.hits ?? 0
|
|
await vfs.readFile('/lib/bare/bundles/b4a.js')
|
|
t.ok((warmRef.current?.hits ?? 0) > hitsBefore)
|
|
const manifest = JSON.stringify({
|
|
version: 1,
|
|
entries: [
|
|
{ ctxKey: 'b4a', package: 'b4a', bundle: true },
|
|
{ ctxKey: 'xOther', package: 'x', bundle: false }
|
|
]
|
|
})
|
|
vfs.bareOsEvictLibBareBundlesFromManifest(b4a.from(manifest))
|
|
const missesAfterEvict = warmRef.current?.misses ?? 0
|
|
await vfs.readFile('/lib/bare/bundles/b4a.js')
|
|
t.ok(
|
|
(warmRef.current?.misses ?? 0) > missesAfterEvict,
|
|
'b4a should miss after selective evict'
|
|
)
|
|
const hitsSc = warmRef.current?.hits ?? 0
|
|
await vfs.readFile('/lib/bare/bundles/safetyCatch.js')
|
|
t.ok(
|
|
(warmRef.current?.hits ?? 0) > hitsSc,
|
|
'safetyCatch still served from warm cache'
|
|
)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('blind relay / pairing / geo proc JSON operator-redacted without BARE_OS_PROC_BLIND_PEER_RELAY_HINTS', async (t) => {
|
|
const a = buildBareOsPearCorestoreHrpcProcJson('blind_relay_router', {})
|
|
t.is(a.exposed, false)
|
|
t.is(a.operatorRedacted, true)
|
|
const b = buildBareOsPearInspectLoggerTlsProcJson('blind_pairing_sketch', {})
|
|
t.is(b.exposed, false)
|
|
t.is(b.operatorRedacted, true)
|
|
const c = buildBareOsHypercorePackHrpcLifecycleProcJson('relay_geo_hint', {})
|
|
t.is(c.exposed, false)
|
|
t.is(c.operatorRedacted, true)
|
|
const { buildBareOsBlindRelaySwarmRuntime } = await import(
|
|
'./lib/bare-os-proc-blind-peer-relay-gate.js'
|
|
)
|
|
const rt = {
|
|
swarm: buildBareOsBlindRelaySwarmRuntime(
|
|
{ BARE_OS_SWARM_PROTOMUX_BACKPRESSURE_COUNT: '3' },
|
|
[{ id: 'a' }, { id: 'b' }],
|
|
{
|
|
hasSystemDrive: true,
|
|
hasPersonalDrive: true,
|
|
auxiliaryDriveCount: 0
|
|
}
|
|
)
|
|
}
|
|
t.is(rt.swarm.schema, 3)
|
|
t.ok(rt.swarm.blindPeerStack)
|
|
t.is(rt.swarm.replicationSurfaces?.systemDriveOpen, true)
|
|
const on = buildBareOsPearCorestoreHrpcProcJson(
|
|
'blind_relay_router',
|
|
{
|
|
BARE_OS_PROC_BLIND_PEER_RELAY_HINTS: '1',
|
|
BARE_OS_BLIND_RELAY_ROUTER_JSON: '{"lanes":1}',
|
|
BARE_OS_SWARM_PROTOMUX_BACKPRESSURE_COUNT: '3'
|
|
},
|
|
rt
|
|
)
|
|
t.is(on.schema, 2)
|
|
t.is(on.exposed, true)
|
|
t.ok(on.router && typeof on.router === 'object')
|
|
t.is(on.swarm?.peerCount, 2)
|
|
t.is(on.swarm?.relayGeoTier, 'multi_peer')
|
|
t.is(on.swarm?.protomuxBackpressure?.schema, 1)
|
|
t.is(on.swarm?.protomuxBackpressure?.emitCountEstimate, 3)
|
|
const pair = buildBareOsPearInspectLoggerTlsProcJson(
|
|
'blind_pairing_sketch',
|
|
{ BARE_OS_PROC_BLIND_PEER_RELAY_HINTS: 'yes' },
|
|
rt
|
|
)
|
|
t.is(pair.schema, 2)
|
|
t.is(pair.swarm?.pairingSurfaceReady, true)
|
|
const geo = buildBareOsHypercorePackHrpcLifecycleProcJson(
|
|
'relay_geo_hint',
|
|
{ BARE_OS_PROC_BLIND_PEER_RELAY_HINTS: 'true' },
|
|
rt
|
|
)
|
|
t.is(geo.schema, 2)
|
|
t.ok(geo.regions == null)
|
|
t.is(geo.swarm?.relayGeoTier, 'multi_peer')
|
|
})
|
|
|
|
test('pear_runtime_channel proc JSON surfaces env hints', async (t) => {
|
|
const j = buildBareOsPearCorestoreHrpcProcJson('pear_runtime_channel', {
|
|
BARE_OS_PEAR_RUNTIME_CHANNEL: 'dev',
|
|
BARE_OS_PEAR_RUNTIME_STAGE: '2',
|
|
BARE_OS_PEAR_INSPECTOR_ATTACH_HINT: 'off',
|
|
BARE_OS_PEAR_RUNTIME_SNAPSHOT_JSON: '{"schema":1,"ok":true}'
|
|
})
|
|
t.is(j.schema, 1)
|
|
t.is(j.channel, 'dev')
|
|
t.is(j.stage, '2')
|
|
t.is(j.inspectorAttachHint, 'off')
|
|
t.ok(j.snapshot && typeof j.snapshot === 'object')
|
|
})
|
|
|
|
test('protomux_extensions proc JSON gated by BARE_OS_PROC_PROTOMUX_EXTENSIONS_REGISTRY', async (t) => {
|
|
const off = buildBareOsProtomuxExtensionsProcJson({ env: {} })
|
|
t.is(off.exposed, false)
|
|
const on = buildBareOsProtomuxExtensionsProcJson({
|
|
env: { BARE_OS_PROC_PROTOMUX_EXTENSIONS_REGISTRY: '1' },
|
|
registrySnapshot: { aliases: {}, schema: 1 },
|
|
ctxApiVersion: BARE_OS_CTX_API_VERSION
|
|
})
|
|
t.is(on.exposed, true)
|
|
t.is(on.schemaVersion, 4)
|
|
t.is(on.muxWireMajor, 3)
|
|
t.ok(Array.isArray(on.extensionLogicalChannels))
|
|
})
|
|
|
|
test('buildBareOsReplicationLiveSketch reads hyperdrive core lengths', async (t) => {
|
|
const dir = testCorestoreDir('replive-sketch')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('rls'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const sk0 = buildBareOsReplicationLiveSketch(
|
|
{ drive, personalDrive: personal, auxiliaryDrives: [personal] },
|
|
0
|
|
)
|
|
t.is(sk0.schema, 4)
|
|
t.is(sk0.auxiliaryDriveCount, 1)
|
|
t.is(sk0.stallHint, 'no_peers')
|
|
t.ok(sk0.corestoreSnapshotSurface)
|
|
t.ok(typeof sk0.systemCoreLength === 'number')
|
|
const sk1 = buildBareOsReplicationLiveSketch(
|
|
{ drive, personalDrive: personal },
|
|
1
|
|
)
|
|
t.is(sk1.schema, 4)
|
|
t.is(sk1.auxiliaryDriveCount, 0)
|
|
t.is(sk1.stallHint, 'ok')
|
|
const sk2 = buildBareOsReplicationLiveSketch(
|
|
{ drive: { core: {} }, personalDrive: null },
|
|
2
|
|
)
|
|
t.is(sk2.stallHint, 'length_unavailable')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('buildBareOsReplicationLiveSketch exposes monitor progress when available', async (t) => {
|
|
const sk = buildBareOsReplicationLiveSketch(
|
|
{
|
|
drive: {
|
|
core: { length: 10 },
|
|
monitor() {
|
|
return { downloadedBlocks: 12, uploadedBlocks: 4, peers: 2 }
|
|
}
|
|
},
|
|
personalDrive: { core: { length: 3 } }
|
|
},
|
|
2
|
|
)
|
|
t.is(sk.schema, 4)
|
|
t.is(sk.monitorProgress?.schema, 1)
|
|
t.is(sk.monitorProgress?.download, 12)
|
|
t.is(sk.monitorProgress?.upload, 4)
|
|
})
|
|
|
|
test('audit chain tamper detection catches modified payload row', async (t) => {
|
|
const rows = []
|
|
const push = (payload) => {
|
|
const { entryHash } = bareOsAuditAppend(payload)
|
|
rows.push({ payload, entryHash })
|
|
}
|
|
push({ type: 'boot', ok: true })
|
|
push({ type: 'vault_save', fileCount: 2 })
|
|
t.ok(bareOsAuditVerifyRows(rows).ok)
|
|
const tampered = rows.map((r) => ({ ...r, payload: { ...r.payload } }))
|
|
tampered[1].payload.fileCount = 3
|
|
const bad = bareOsAuditVerifyRows(tampered)
|
|
t.absent(bad.ok)
|
|
t.is(bad.badIndex, 1)
|
|
})
|
|
|
|
test('protomux_rpc_pool_health proc uses schema 2 and backpressure correlation fields', async (t) => {
|
|
const j = buildBareOsBareRuntimeProtoMuxProcJson('protomux_rpc_pool_health', {
|
|
BARE_OS_PROTOMUX_RPC_POOL_HEALTH_JSON: '{"ok":true}',
|
|
BARE_OS_REPLICATION_BACKPRESSURE_JSON: '{"depth":1}',
|
|
BARE_OS_LAST_PROTOMUX_BACKPRESSURE_MS: '99'
|
|
})
|
|
t.is(j.schema, 2)
|
|
t.ok(j.health)
|
|
t.ok(j.replicationBackpressure)
|
|
t.is(j.lastProtomuxBackpressureMs, 99)
|
|
})
|
|
|
|
test('protomux_rpc_pool_health echoes seed pool hint and wire correlate when env health absent', async (t) => {
|
|
const j = buildBareOsBareRuntimeProtoMuxProcJson(
|
|
'protomux_rpc_pool_health',
|
|
{},
|
|
{
|
|
seedProtomuxRpcPoolHint: { schema: 1, depth: 2 },
|
|
protomuxWireCorrelate: {
|
|
schema: 1,
|
|
peerMuxSessions: 3,
|
|
backpressureEmitCount: 5,
|
|
aliasPairCount: 1,
|
|
bareOsV1WireMessageKinds: 7
|
|
}
|
|
}
|
|
)
|
|
t.is(j.schema, 2)
|
|
t.is(j.health, null)
|
|
t.is(j.seedProtomuxRpcPoolHintEcho?.depth, 2)
|
|
t.is(j.protomuxWireCorrelate?.peerMuxSessions, 3)
|
|
t.is(j.protomuxWireCorrelate?.backpressureEmitCount, 5)
|
|
})
|
|
|
|
test('protomux_channels proc includes wireShapeSummary from runtime hints', async (t) => {
|
|
const j = buildBareOsPearCorestoreHrpcProcJson(
|
|
'protomux_channels',
|
|
{},
|
|
{
|
|
protomuxWireShape: {
|
|
schema: 1,
|
|
bareOsV1MessageKinds: ['read', 'rpc_req'],
|
|
peerMuxSessions: 2
|
|
}
|
|
}
|
|
)
|
|
t.is(j.schema, 1)
|
|
t.is(j.wireShapeSummary?.peerMuxSessions, 2)
|
|
t.ok(Array.isArray(j.wireShapeSummary?.bareOsV1MessageKinds))
|
|
})
|
|
|
|
test('pathnameExpandShellWord caps matches with BARE_OS_GLOB_MAX_MATCHES', async (t) => {
|
|
const names = ['f0', 'f1', 'f2', 'f3', 'f4']
|
|
const vfs = {
|
|
getcwd: () => '/tmp',
|
|
async readdir(d) {
|
|
if (d === '/tmp') return names.slice()
|
|
return []
|
|
},
|
|
async stat(p) {
|
|
if (p === '/tmp') return { type: 'directory' }
|
|
const base = p.replace(/^.*\//, '')
|
|
if (names.includes(base)) return { type: 'file' }
|
|
throw new Error('ENOENT')
|
|
}
|
|
}
|
|
const ctx = { vfs }
|
|
const parts = [{ q: 'u', t: '/tmp/f*' }]
|
|
const out = await pathnameExpandShellWord(ctx, parts, {
|
|
BARE_OS_GLOB_MAX_MATCHES: '2'
|
|
})
|
|
t.is(out.length, 2)
|
|
t.is(out[0], '/tmp/f0')
|
|
t.is(out[1], '/tmp/f1')
|
|
})
|
|
|
|
test('pathnameExpandShellWord supports globstar when enabled', async (t) => {
|
|
const files = new Set(['/tmp/a/f.txt', '/tmp/a/b/f.txt', '/tmp/a/b/c/f.txt'])
|
|
const dirs = new Set(['/tmp', '/tmp/a', '/tmp/a/b', '/tmp/a/b/c'])
|
|
const dirMap = new Map([
|
|
['/tmp', ['a']],
|
|
['/tmp/a', ['b', 'f.txt']],
|
|
['/tmp/a/b', ['c', 'f.txt']],
|
|
['/tmp/a/b/c', ['f.txt']]
|
|
])
|
|
const vfs = {
|
|
getcwd: () => '/tmp',
|
|
async readdir(d) {
|
|
return (dirMap.get(d) || []).slice()
|
|
},
|
|
async stat(p) {
|
|
if (dirs.has(p)) return { type: 'directory' }
|
|
if (files.has(p)) return { type: 'file' }
|
|
throw new Error('ENOENT')
|
|
}
|
|
}
|
|
const ctx = { vfs }
|
|
const parts = [{ q: 'u', t: '/tmp/**/f.txt' }]
|
|
const out = await pathnameExpandShellWord(ctx, parts, {
|
|
BARE_OS_GLOBSTAR: '1'
|
|
})
|
|
t.is(out.length, 4)
|
|
t.is(out[0], '/tmp/f.txt')
|
|
t.is(out[1], '/tmp/a/f.txt')
|
|
t.is(out[2], '/tmp/a/b/f.txt')
|
|
t.is(out[3], '/tmp/a/b/c/f.txt')
|
|
})
|
|
|
|
test('pathnameExpandShellWord keeps globstar literal by default', async (t) => {
|
|
const vfs = {
|
|
getcwd: () => '/tmp',
|
|
async readdir() {
|
|
return []
|
|
},
|
|
async stat() {
|
|
throw new Error('ENOENT')
|
|
}
|
|
}
|
|
const ctx = { vfs }
|
|
const parts = [{ q: 'u', t: '/tmp/**/f.txt' }]
|
|
const out = await pathnameExpandShellWord(ctx, parts, {})
|
|
t.is(out.length, 1)
|
|
t.is(out[0], '/tmp/**/f.txt')
|
|
})
|
|
|
|
test('pathnameExpandShellWord skips brace expansion when Cartesian product exceeds cap', async (t) => {
|
|
const vfs = {
|
|
getcwd: () => '/tmp',
|
|
async readdir() {
|
|
return []
|
|
},
|
|
async stat() {
|
|
throw new Error('ENOENT')
|
|
}
|
|
}
|
|
const ctx = { vfs }
|
|
const parts = [{ q: 'u', t: '/tmp/{a,b,c}' }]
|
|
const out = await pathnameExpandShellWord(ctx, parts, {
|
|
BARE_OS_SHELL_BRACE_EXPANSION: '1',
|
|
BARE_OS_SHELL_BRACE_EXPANSION_MAX: '2'
|
|
})
|
|
t.is(out.length, 1)
|
|
t.is(out[0], '/tmp/{a,b,c}')
|
|
})
|
|
|
|
test('pathnameExpandShellWord expands ~login using HOME or /home/login', async (t) => {
|
|
const vfs = {
|
|
getcwd: () => '/tmp',
|
|
async readdir() {
|
|
return []
|
|
},
|
|
async stat() {
|
|
throw new Error('ENOENT')
|
|
}
|
|
}
|
|
const ctx = { vfs }
|
|
let out = await pathnameExpandShellWord(ctx, [{ q: 'u', t: '~zed' }], {
|
|
USER: 'alice',
|
|
HOME: '/home/alice'
|
|
})
|
|
t.is(out[0], '/home/zed')
|
|
out = await pathnameExpandShellWord(ctx, [{ q: 'u', t: '~alice' }], {
|
|
USER: 'alice',
|
|
HOME: '/home/alice'
|
|
})
|
|
t.is(out[0], '/home/alice')
|
|
out = await pathnameExpandShellWord(ctx, [{ q: 'u', t: '~alice/sub' }], {
|
|
USER: 'alice',
|
|
HOME: '/custom/a'
|
|
})
|
|
t.is(out[0], '/custom/a/sub')
|
|
})
|
|
|
|
test('buildBareOsSyscallsProcJson exposes posixLike fd mapping', async (t) => {
|
|
const j = buildBareOsSyscallsProcJson({ ctxApiVersion: BARE_OS_CTX_API_VERSION })
|
|
t.is(j.schemaVersion, 11)
|
|
const openRow = j.opsDetail.find((r) => r.name === 'open')
|
|
t.ok(openRow && Array.isArray(openRow.susv4Refs))
|
|
t.ok(openRow.susv4Refs.includes('XSH/open'))
|
|
t.ok(j.ops.includes('clock_gettime'))
|
|
t.ok(j.posixXsh.namesCsv.includes('clock_gettime'))
|
|
t.ok(j.fdModel && j.fdModel.posixLike && j.fdModel.posixLike.pipe)
|
|
t.ok(j.fdModel.posixLike.read && j.fdModel.posixLike.write)
|
|
t.ok(j.fdModel.posixLike.fcntl)
|
|
t.ok(j.fdModel.posixLike.poll)
|
|
t.ok(j.fdModel.posixLike.socketFamily)
|
|
t.ok(j.posixXsh && j.posixXsh.schema === 3 && j.posixXsh.namesCsv.includes('open'))
|
|
t.ok(j.posixXsh.namesCsv.includes('poll'))
|
|
t.ok(j.posixXsh.namesCsv.includes('select'))
|
|
t.ok(j.posixXsh.namesCsv.includes('umask'))
|
|
t.ok(j.posixXsh.namesCsv.includes('readv'))
|
|
t.ok(j.posixXsh.namesCsv.includes('writev'))
|
|
t.ok(j.posixXsh.namesCsv.includes('socket'))
|
|
t.ok(j.posixXsh.namesCsv.includes('sendmsg'))
|
|
t.ok(j.posixXsh.namesCsv.includes('recvmsg'))
|
|
t.ok(j.posixXsh.namesCsv.includes('shutdown'))
|
|
t.is(j.socketMsgSurface?.schema, 6)
|
|
t.is(j.socketMsgSurface?.logicalScmRightsOnSend?.syscallResultField, 'scmRightsLocalDup')
|
|
t.is(j.socketMsgSurface?.ancillaryControl?.supported, false)
|
|
t.is(j.socketMsgSurface?.ancillaryControl?.errno, 'ENOTSUP')
|
|
t.ok(Array.isArray(j.socketMsgSurface?.ancillaryControl?.rejectKeys))
|
|
const xsh = j.opsDetail.filter((r) => r.posixAlignment)
|
|
t.ok(xsh.length >= 10)
|
|
})
|
|
|
|
test('boot graph labels stay in sync with kernel init bootTimed labels', async (t) => {
|
|
const src = await readFile(path.join(__dirname, '../../kernel/init.js'), 'utf8')
|
|
const rx = /bootTimed\(\s*ctx,\s*'([^']+)'/g
|
|
/** @type {string[]} */
|
|
const timed = []
|
|
for (const m of src.matchAll(rx)) timed.push(String(m[1]))
|
|
const kernelLabels = ['boot.policy', ...timed]
|
|
const graph = buildBareOsBootGraphProcJson()
|
|
const graphLabels = Array.isArray(graph.nodes)
|
|
? graph.nodes.map((n) => String(n && n.id))
|
|
: []
|
|
t.alike(
|
|
graphLabels,
|
|
kernelLabels,
|
|
'boot graph node ids must match kernel/init.js bootTimed labels'
|
|
)
|
|
})
|
|
|
|
test('bareOsParseSendmsgScmRights binary control rejects', async (t) => {
|
|
const r = bareOsParseSendmsgScmRights(
|
|
{ control: new Uint8Array([1]) },
|
|
true,
|
|
4
|
|
)
|
|
t.is(r.ok, false)
|
|
t.is(/** @type {{ reject: string }} */ (r).reject, 'control')
|
|
})
|
|
|
|
test('bareOsParseSendmsgScmRights scm off rejects non-empty fds', async (t) => {
|
|
const r = bareOsParseSendmsgScmRights({ cmsgs: [{ fds: [3] }] }, false, 4)
|
|
t.is(r.ok, false)
|
|
})
|
|
|
|
test('bareOsParseSendmsgScmRights scm on collects fds', async (t) => {
|
|
const r = bareOsParseSendmsgScmRights({ cmsgs: [{ fds: [3, 4] }] }, true, 4)
|
|
t.is(r.ok, true)
|
|
t.is(/** @type {{ fds: number[] }} */ (r).fds.length, 2)
|
|
})
|
|
|
|
test('wantPosixSocketScmRights env', async (t) => {
|
|
t.absent(wantPosixSocketScmRights({}))
|
|
t.ok(wantPosixSocketScmRights({ BARE_OS_POSIX_SOCKET_SCM_RIGHTS: '1' }))
|
|
})
|
|
|
|
test('maybeBareOsBuildBinManifest writes schema 2 with namesDigest', async (t) => {
|
|
/** @type {{ path: string, text: string }[]} */
|
|
const writes = []
|
|
const ctx = {
|
|
env: { BARE_OS_VFS_BIN_INDEX_BUILD: '1' },
|
|
vfs: {
|
|
writeFile: async (p, buf) => {
|
|
writes.push({
|
|
path: String(p),
|
|
text: b4a.toString(buf)
|
|
})
|
|
}
|
|
},
|
|
drive: {
|
|
async *readdir() {
|
|
yield 'b'
|
|
yield 'a'
|
|
}
|
|
},
|
|
b4a,
|
|
console: {}
|
|
}
|
|
await maybeBareOsBuildBinManifest(ctx)
|
|
t.is(writes.length, 1)
|
|
t.is(writes[0].path, '/.bare-os/index/bin-manifest.json')
|
|
const j = JSON.parse(writes[0].text)
|
|
t.is(j.schema, 2)
|
|
t.ok(typeof j.namesDigest === 'string' && j.namesDigest.length === 64)
|
|
t.is(j.names[0], 'a')
|
|
t.is(j.names[1], 'b')
|
|
})
|
|
|
|
test('parseBareOsHrpcAllowlistJson + bareOsHrpcAllowlistDeniesRoute', async (t) => {
|
|
t.is(parseBareOsHrpcAllowlistJson('').allow, null)
|
|
t.is(parseBareOsHrpcAllowlistJson(' ').allow, null)
|
|
const a1 = parseBareOsHrpcAllowlistJson('["kernel.ping"]')
|
|
t.ok(a1.allow && a1.allow.has('kernel.ping'))
|
|
t.ok(bareOsHrpcAllowlistDeniesRoute(a1.allow, 'kernel', 'capabilities'))
|
|
t.absent(bareOsHrpcAllowlistDeniesRoute(a1.allow, 'kernel', 'ping'))
|
|
const a2 = parseBareOsHrpcAllowlistJson('{"kernel.ping":true}')
|
|
t.ok(a2.allow && a2.allow.has('kernel.ping'))
|
|
const a3 = parseBareOsHrpcAllowlistJson('{"*":true}')
|
|
t.ok(a3.allow && a3.allow.has('*'))
|
|
t.absent(bareOsHrpcAllowlistDeniesRoute(a3.allow, 'vfs', 'readText'))
|
|
const a4 = parseBareOsHrpcAllowlistJson('{"kernel.*":true}')
|
|
t.absent(bareOsHrpcAllowlistDeniesRoute(a4.allow, 'kernel', 'capabilities'))
|
|
t.ok(bareOsHrpcAllowlistDeniesRoute(a4.allow, 'vfs', 'readText'))
|
|
t.ok(parseBareOsHrpcAllowlistJson('{').parseError)
|
|
t.ok(parseBareOsHrpcAllowlistJson('null').allow === null)
|
|
t.ok(!parseBareOsHrpcAllowlistJson('null').parseError)
|
|
t.ok(parseBareOsHrpcAllowlistJson('"x"').allow === null)
|
|
const dup = parseBareOsHrpcAllowlistJson('["a.b","a.b","c.d"]')
|
|
t.is(dup.allow?.size, 2)
|
|
const emptyArr = parseBareOsHrpcAllowlistJson('[]')
|
|
t.is(emptyArr.allow, null)
|
|
t.ok(!emptyArr.parseError)
|
|
const emptyObj = parseBareOsHrpcAllowlistJson('{}')
|
|
t.is(emptyObj.allow, null)
|
|
})
|
|
|
|
test('hyper_multisig_trust_pointer proc schema 2 merges vault multisig continuity JSON', async (t) => {
|
|
const j = buildBareOsBareRuntimeProtoMuxProcJson(
|
|
'hyper_multisig_trust_pointer',
|
|
{
|
|
BARE_OS_VAULT_MULTISIG_CONTINUITY_JSON: '{"epoch":1,"note":"sketch"}'
|
|
}
|
|
)
|
|
t.is(j.schema, 2)
|
|
t.is(j.vaultMultisigContinuity?.epoch, 1)
|
|
})
|
|
|
|
test('hrpc_allowlist_sketch proc lists stock bareOsHrpcRequest routes', async (t) => {
|
|
const j = buildBareOsHypercorePackHrpcLifecycleProcJson(
|
|
'hrpc_allowlist_sketch',
|
|
{}
|
|
)
|
|
t.is(j.schema, 3)
|
|
t.is(j.routeTableSchemaVersion, 3)
|
|
t.ok(Array.isArray(j.stockRoutes))
|
|
t.ok(j.stockRoutes.includes('bare_os.replication_snapshot'))
|
|
t.ok(j.stockRoutes.includes('bare_os.search_local'))
|
|
t.ok(j.stockRoutes.includes('bare_os.pkg_index_get'))
|
|
})
|
|
|
|
test('pear_doctor_state proc JSON schema 2 parity fields', async (t) => {
|
|
const j = buildBareOsPearInspectLoggerTlsProcJson(
|
|
'pear_doctor_state',
|
|
{
|
|
BARE_OS_PEAR_DOCTOR_STATE_JSON: '{"ok":true}',
|
|
BARE_OS_PEAR_RUNTIME_VERSION: '1.2.3',
|
|
BARE_OS_PEAR_INSPECT_PROBE: '1',
|
|
BARE_OS_PEAR_DOCTOR_MODULE: 'pear-doctor'
|
|
},
|
|
{}
|
|
)
|
|
t.is(j.schema, 2)
|
|
t.is(j.runtimeVersion, '1.2.3')
|
|
t.is(j.pearInspectProbe, true)
|
|
t.is(j.pearDoctorModuleHint, 'pear-doctor')
|
|
t.ok(j.state && typeof j.state === 'object')
|
|
})
|
|
|
|
test('split suffix rolls past zz into three-letter names', async (t) => {
|
|
const a = 'abcdefghijklmnopqrstuvwxyz'
|
|
function suffix(i) {
|
|
let idx = i
|
|
let len = 2
|
|
let span = 26 ** len
|
|
while (idx >= span) {
|
|
idx -= span
|
|
len++
|
|
span = 26 ** len
|
|
}
|
|
let s = ''
|
|
let n = idx
|
|
for (let p = 0; p < len; p++) {
|
|
s = a[n % 26] + s
|
|
n = Math.floor(n / 26)
|
|
}
|
|
return 'x' + s
|
|
}
|
|
t.is(suffix(0), 'xaa')
|
|
t.is(suffix(675), 'xzz')
|
|
t.is(suffix(676), 'xaaa')
|
|
t.is(suffix(677), 'xaab')
|
|
})
|
|
|
|
test('disk.os replication_snapshot and replication_operator_sketch RPCs', async (t) => {
|
|
const dir = testCorestoreDir('diskosrpc')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
await drive.ready()
|
|
const bridge = createBareOsDiskOsBridge({
|
|
drive,
|
|
bareOsIpc: { list: () => [] },
|
|
ctxApiVersion: BARE_OS_CTX_API_VERSION,
|
|
systemRevision: { currentId: 'cur', pendingId: '', slot: 'a' },
|
|
bootStartedMs: Date.now() - 1000,
|
|
seedReplicationStatus: { depth: 1 },
|
|
protomuxOperatorSketch: {
|
|
schema: 2,
|
|
protomuxAppChannelRxTotal: 0,
|
|
envGateAppChannel: 'BARE_OS_PROTOMUX_APP_CHANNEL'
|
|
}
|
|
})
|
|
const snap = await bridge.execRpc('bare_os', 'replication_snapshot', [])
|
|
const sj = JSON.parse(snap)
|
|
t.is(sj.schema, 2)
|
|
t.is(sj.ok, true)
|
|
t.ok(typeof sj.writable === 'boolean')
|
|
const op = await bridge.execRpc('bare_os', 'replication_operator_sketch', [])
|
|
const oj = JSON.parse(op)
|
|
t.is(oj.schema, 8)
|
|
t.is(oj.protomuxOperatorSketch?.schema, 2)
|
|
t.ok(Object.prototype.hasOwnProperty.call(oj, 'hyperdhtAddressSketch'))
|
|
t.is(oj.corestoreSnapshotUxHint?.schema, 4)
|
|
t.is(oj.corestoreSnapshotPackageRef?.npmPackage, 'corestore-snapshot')
|
|
t.is(oj.seedReplicationStatus?.depth, 1)
|
|
t.is(oj.swarmPeerCount, null)
|
|
const badIntent = await bridge.execRpc(
|
|
'bare_os',
|
|
'replication_operator_intent',
|
|
['{}']
|
|
)
|
|
const ij = JSON.parse(badIntent)
|
|
t.is(ij.schema, 2)
|
|
t.is(ij.ok, false)
|
|
t.is(ij.code, 'EPERM')
|
|
await drive.put('/boot/init.js', b4a.from('//x'))
|
|
const pe = await bridge.execRpc('bare_os', 'path_exists', ['/boot/init.js'])
|
|
const pej = JSON.parse(pe)
|
|
t.is(pej.ok, true)
|
|
t.is(pej.exists, true)
|
|
const bi = await bridge.execRpc('bare_os', 'boot_init_exists', [])
|
|
const bij = JSON.parse(bi)
|
|
t.is(bij.ok, true)
|
|
t.is(bij.exists, true)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('disk.os searchLocal merges auxiliary drives and disk_os_hints RPC', async (t) => {
|
|
const dir = testCorestoreDir('diskossearch')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const auxStore = store.namespace('auxs')
|
|
const aux = new Hyperdrive(auxStore)
|
|
await drive.ready()
|
|
await aux.ready()
|
|
await drive.put('/sys/x_only', b4a.from('1'))
|
|
await aux.put('/mirror/y_aux', b4a.from('2'))
|
|
const bridge = createBareOsDiskOsBridge({
|
|
drive,
|
|
auxiliaryDrives: [aux],
|
|
bareOsIpc: { list: () => [] },
|
|
ctxApiVersion: BARE_OS_CTX_API_VERSION,
|
|
systemRevision: null,
|
|
bootStartedMs: Date.now(),
|
|
seedMirrorDriveHintV2: { schema: 2, note: 't' },
|
|
seedHttpDhtProxyHint: { routes: 1 }
|
|
})
|
|
const m = await bridge.searchLocal('y_aux')
|
|
t.ok(m.some((p) => p.includes('y_aux')))
|
|
const m2 = await bridge.searchLocal('x_only')
|
|
t.ok(m2.some((p) => p.includes('x_only')))
|
|
const h = await bridge.execRpc('bare_os', 'disk_os_hints', [])
|
|
const hj = JSON.parse(h)
|
|
t.is(hj.schema, 3)
|
|
t.is(hj.auxiliaryDriveCount, 1)
|
|
t.ok(hj.protocolPackageVersion)
|
|
t.ok(typeof hj.protomuxChannelSchemaVersion === 'number')
|
|
t.is(hj.mirrorDriveHintV2?.schema, 2)
|
|
t.is(hj.httpDhtProxyHint?.routes, 1)
|
|
t.ok(hj.pathSearchLocal && hj.pathSearchLocal.schema === 1)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('disk.os searchLocal uses path-manifest before drive listing', async (t) => {
|
|
const dir = testCorestoreDir('diskosmanifest')
|
|
const prevManifest = process.env.BARE_OS_DISK_OS_PATH_MANIFEST
|
|
const prevOnly = process.env.BARE_OS_DISK_OS_SEARCH_MANIFEST_ONLY
|
|
process.env.BARE_OS_DISK_OS_PATH_MANIFEST = '/etc/bare-os/path-manifest.json'
|
|
process.env.BARE_OS_DISK_OS_SEARCH_MANIFEST_ONLY = '1'
|
|
try {
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
await drive.ready()
|
|
await drive.put(
|
|
'/etc/bare-os/path-manifest.json',
|
|
b4a.from(
|
|
JSON.stringify({
|
|
schema: 1,
|
|
paths: ['/vendor/only-on-manifest.txt']
|
|
})
|
|
)
|
|
)
|
|
const bridge = createBareOsDiskOsBridge({
|
|
drive,
|
|
auxiliaryDrives: [],
|
|
bareOsIpc: { list: () => [] },
|
|
ctxApiVersion: BARE_OS_CTX_API_VERSION,
|
|
systemRevision: null,
|
|
bootStartedMs: Date.now()
|
|
})
|
|
const m = await bridge.searchLocal('only-on')
|
|
t.ok(m.some((p) => p === '/vendor/only-on-manifest.txt'))
|
|
const h = await bridge.execRpc('bare_os', 'disk_os_hints', [])
|
|
const hj = JSON.parse(h)
|
|
t.is(hj.schema, 3)
|
|
t.is(hj.pathSearchLocal.manifestOnly, true)
|
|
t.ok(hj.pathSearchLocal.manifestMatchCount >= 1)
|
|
t.is(hj.pathSearchLocal.primaryScanMatchCount, 0)
|
|
await store.close()
|
|
} finally {
|
|
if (prevManifest === undefined) delete process.env.BARE_OS_DISK_OS_PATH_MANIFEST
|
|
else process.env.BARE_OS_DISK_OS_PATH_MANIFEST = prevManifest
|
|
if (prevOnly === undefined) delete process.env.BARE_OS_DISK_OS_SEARCH_MANIFEST_ONLY
|
|
else process.env.BARE_OS_DISK_OS_SEARCH_MANIFEST_ONLY = prevOnly
|
|
}
|
|
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 routes /.bare to personal drive', async (t) => {
|
|
const dir = testCorestoreDir('vfsbare')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvb'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
await personal.put('/.bare/vault-marker', b4a.from('1'))
|
|
await sys.put('/.bare/system-only', b4a.from('2'))
|
|
const env = { HOME: '/home/user', PWD: '/home/user', PATH: '/bin' }
|
|
const vfs = createVfs(sys, personal, env)
|
|
const names = await vfs.readdir('/.bare')
|
|
t.ok(names.includes('vault-marker'))
|
|
t.is(names.includes('system-only'), false)
|
|
t.is(await sys.get('/.bare/vault-marker'), 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 bareOsForkShellEnv isolates cwd and shares identity ref', async (t) => {
|
|
const dir = testCorestoreDir('vfork')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvfork'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const env = { HOME: '/home/user', PWD: '/home/user', PATH: '/bin' }
|
|
const vfs = createVfs(sys, personal, env)
|
|
const forkA = vfs.bareOsForkShellEnv({ SSH_CLIENT: 'a' })
|
|
const forkB = vfs.bareOsForkShellEnv({ SSH_CLIENT: 'b' })
|
|
t.is(vfs.getcwd(), '/home/user')
|
|
t.is(forkA.getcwd(), '/home/user')
|
|
t.is(forkB.getcwd(), '/home/user')
|
|
await forkA.chdir('/')
|
|
t.is(vfs.getcwd(), '/home/user')
|
|
t.is(forkB.getcwd(), '/home/user')
|
|
t.is(forkA.getcwd(), '/')
|
|
t.is(forkA.env.SSH_CLIENT, 'a')
|
|
t.is(forkB.env.SSH_CLIENT, 'b')
|
|
t.is(vfs.env.SSH_CLIENT, undefined)
|
|
vfs.bareOsIdentitySession = 'unlocked'
|
|
t.is(forkB.bareOsIdentitySession, 'unlocked')
|
|
forkB.bareOsIdentitySession = 'guest'
|
|
t.is(vfs.bareOsIdentitySession, 'guest')
|
|
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 system read-only alias routes reads and denies writes', async (t) => {
|
|
const dir = testCorestoreDir('vfs-sys-ro-alias')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pv-sys-ro-alias'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
await sys.put('/etc/os-release', b4a.from('ID=bareos\n'))
|
|
const vfs = createVfs(sys, personal, {
|
|
HOME: '/home/user',
|
|
PWD: '/home/user',
|
|
PATH: '/bin',
|
|
USER: 'user',
|
|
BARE_OS_VFS_SYSTEM_RO_ALIAS: '/system-ro'
|
|
})
|
|
const buf = await vfs.readFile('/system-ro/etc/os-release')
|
|
t.ok(buf)
|
|
t.is(b4a.toString(buf), 'ID=bareos\n')
|
|
await t.exception(async () => {
|
|
await vfs.writeFile('/system-ro/etc/os-release', b4a.from('ID=mutated\n'))
|
|
})
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs denies mutating system-drive operations outside HOME', async (t) => {
|
|
const dir = testCorestoreDir('vfs-system-write-deny')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pv-system-write-deny'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
await sys.put('/etc/os-release', b4a.from('ID=bareos\n'))
|
|
const vfs = createVfs(sys, personal, {
|
|
HOME: '/home/user',
|
|
PWD: '/home/user',
|
|
PATH: '/bin',
|
|
USER: 'user',
|
|
UID: '1000',
|
|
GID: '1000'
|
|
})
|
|
|
|
for (const op of [
|
|
() => vfs.writeFile('/etc/new.conf', b4a.from('x')),
|
|
() => vfs.unlink('/etc/os-release'),
|
|
() => vfs.mkdir('/etc/newdir'),
|
|
() => vfs.symlink('/bin/sh', '/etc/sh-link'),
|
|
() => vfs.chmod('/etc/os-release', 0o644),
|
|
() => vfs.chown('/etc/os-release', { uid: 1000, gid: 1000 })
|
|
]) {
|
|
await t.exception(async () => {
|
|
await op()
|
|
})
|
|
}
|
|
|
|
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('guest vfs readFile /.bare/account is EACCES on sensitive path', async (t) => {
|
|
const dir = testCorestoreDir('vfsguestbare')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvgsb'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
await personal.put('/.bare/account', b4a.from('not-a-real-account'))
|
|
const envGuest = {
|
|
HOME: '/home/guest',
|
|
PWD: '/',
|
|
PATH: '/bin',
|
|
USER: 'guest'
|
|
}
|
|
const vfs = createVfs(sys, personal, envGuest)
|
|
let err = null
|
|
try {
|
|
await vfs.readFile('/.bare/account')
|
|
} catch (e) {
|
|
err = e
|
|
}
|
|
t.ok(err, 'expected throw')
|
|
t.ok(String(err.message).includes('EACCES'), err.message)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('guest vfs BARE_OS_GUEST_BARE_READ_ALL=yes does not lift sensitive /.bare deny', async (t) => {
|
|
const dir = testCorestoreDir('vfsbareyes')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvbareyes'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
await personal.put('/.bare/account', b4a.from('acct'))
|
|
const env = {
|
|
HOME: '/home/guest',
|
|
PWD: '/home/guest',
|
|
PATH: '/bin',
|
|
USER: 'guest',
|
|
BARE_OS_GUEST_BARE_READ_ALL: 'yes'
|
|
}
|
|
const vfs = createVfs(sys, personal, env, { getMounts: () => new Map() }, {
|
|
procBareOsSwarmText: () => '{}\n'
|
|
})
|
|
let err = null
|
|
try {
|
|
await vfs.readFile('/.bare/account')
|
|
} catch (e) {
|
|
err = e
|
|
}
|
|
t.ok(err)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('guest vfs readFile /.bare/account when BARE_OS_GUEST_BARE_READ_ALL', async (t) => {
|
|
const dir = testCorestoreDir('vfsguestbareall')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvgba'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
await personal.put('/.bare/account', b4a.from('acct-body'))
|
|
const envGuest = {
|
|
HOME: '/home/guest',
|
|
PWD: '/',
|
|
PATH: '/bin',
|
|
USER: 'guest',
|
|
BARE_OS_GUEST_BARE_READ_ALL: '1'
|
|
}
|
|
const vfs = createVfs(sys, personal, envGuest)
|
|
const buf = await vfs.readFile('/.bare/account')
|
|
t.ok(buf)
|
|
t.is(b4a.toString(buf, 'utf8'), 'acct-body')
|
|
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'))
|
|
const procRoot = await vfs.readdir('/proc').then((a) => [...a].sort())
|
|
t.ok(procRoot.includes('bare_os'))
|
|
t.ok(procRoot.includes('cpuinfo'))
|
|
t.ok(procRoot.includes('meminfo'))
|
|
t.ok(procRoot.includes('loadavg'))
|
|
t.ok(procRoot.includes('diskstats'))
|
|
t.ok(procRoot.includes('mounts'))
|
|
t.ok(procRoot.includes('net'))
|
|
t.ok(procRoot.includes('self'))
|
|
t.ok(procRoot.includes('uptime'))
|
|
t.ok(procRoot.includes('version'))
|
|
if (false) t.alike(await vfs.readdir('/proc').then((a) => [...a].sort()).then(() => [
|
|
'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_boot_budget_summary.json',
|
|
'bare_os_boot_graph.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_chat.json',
|
|
'bare_os_clock.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_features.json',
|
|
'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_metrics_prom.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_openssh.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_channel.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_extensions.json',
|
|
'bare_os_protomux_rpc_pool_health.json',
|
|
'bare_os_protomux_wire.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_replication_operator_panel.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_swarm.json',
|
|
'bare_os_swarm_connection_manager_status.json',
|
|
'bare_os_swarm_datagram_replication_status.json',
|
|
'bare_os_swarm_datagrams_status.json',
|
|
'bare_os_swarm_health.json',
|
|
'bare_os_swarm_holepunch_status.json',
|
|
'bare_os_swarm_key_broker_status.json',
|
|
'bare_os_swarm_relay_status.json',
|
|
'bare_os_swarm_replication_status.json',
|
|
'bare_os_swarm_status.json',
|
|
'bare_os_sync_window.json',
|
|
'bare_os_syscalls',
|
|
'bare_os_syscalls.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',
|
|
'shm',
|
|
'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 readFile /mirror/aux0 reads auxiliary hyperdrive (mirror-drive style mount)', async (t) => {
|
|
const dir = testCorestoreDir('vfsauxread')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvauxr'))
|
|
const aux = new Hyperdrive(store.namespace('auxread'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
await aux.ready()
|
|
await aux.put('/hello.txt', b4a.from('mirror-body'))
|
|
const env = {
|
|
HOME: '/home/guest',
|
|
PWD: '/home/guest',
|
|
PATH: '/bin',
|
|
USER: 'guest'
|
|
}
|
|
const vfs = createVfs(sys, personal, env, { getMounts: () => new Map() }, {
|
|
procBareOsSwarmText: () => '{}\n',
|
|
getAuxiliaryDrives: () => [aux]
|
|
})
|
|
t.is(b4a.toString(await vfs.readFile('/mirror/aux0/hello.txt')), 'mirror-body')
|
|
let werr = null
|
|
try {
|
|
await vfs.writeFile('/mirror/aux0/x', b4a.from('nope'))
|
|
} catch (e) {
|
|
werr = e
|
|
}
|
|
t.ok(werr, 'aux mirror mount is read-only')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs /proc/mounts lists mirror aux drives', async (t) => {
|
|
const dir = testCorestoreDir('vfsauxmount')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvauxm'))
|
|
const aux = new Hyperdrive(store.namespace('auxmirror'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
await aux.ready()
|
|
const env = {
|
|
HOME: '/home/guest',
|
|
PWD: '/home/guest',
|
|
PATH: '/bin',
|
|
USER: 'guest'
|
|
}
|
|
const vfs = createVfs(sys, personal, env, { getMounts: () => new Map() }, {
|
|
procBareOsSwarmText: () => '{}\n',
|
|
getAuxiliaryDrives: () => [aux],
|
|
getAuxiliaryMountLines: () => [
|
|
'bare-os-aux0 /mirror/aux0 hyperdrive ro 0 0'
|
|
]
|
|
})
|
|
const mountsTxt = b4a.toString(await vfs.readFile('/proc/mounts'))
|
|
t.ok(mountsTxt.includes('bare-os-aux0'))
|
|
t.ok(mountsTxt.includes('/mirror/aux0'))
|
|
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',
|
|
MY_API_TOKEN: 'secret-token',
|
|
AWS_SECRET_ACCESS_KEY: 'aws-secret',
|
|
SESSION_TOKEN: 'session-secret',
|
|
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'))
|
|
t.ok(!raw.includes('MY_API_TOKEN'))
|
|
t.ok(!raw.includes('secret-token'))
|
|
t.ok(!raw.includes('AWS_SECRET_ACCESS_KEY'))
|
|
t.ok(!raw.includes('SESSION_TOKEN'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs /proc/bare_os/metrics.prom emits OpenMetrics-style payload', async (t) => {
|
|
const dir = testCorestoreDir('vfs-prom-openmetrics')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('vprom'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const vfs = createVfs(
|
|
sys,
|
|
personal,
|
|
{
|
|
HOME: '/home/guest',
|
|
PWD: '/home/guest',
|
|
PATH: '/bin',
|
|
USER: 'guest'
|
|
},
|
|
null,
|
|
{
|
|
procBareOsMetricsPromText: () =>
|
|
'# HELP bare_os_kernel_counters Bare OS kernel counters\n' +
|
|
'# TYPE bare_os_kernel_counters counter\n' +
|
|
'bare_os_kernel_counters{name="boot_ok"} 1\n'
|
|
}
|
|
)
|
|
const raw = b4a.toString(await vfs.readFile('/proc/bare_os/metrics.prom'))
|
|
t.ok(raw.includes('# HELP bare_os_kernel_counters'))
|
|
t.ok(raw.includes('# TYPE bare_os_kernel_counters counter'))
|
|
t.ok(raw.includes('bare_os_kernel_counters{name="boot_ok"} 1'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs exposes /proc/bare_os/initd_readiness.json', async (t) => {
|
|
const dir = testCorestoreDir('vfsinitdreadiness')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvinitdreadiness'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const vfs = createVfs(sys, personal, {
|
|
HOME: '/home/guest',
|
|
PWD: '/home/guest',
|
|
PATH: '/bin'
|
|
})
|
|
const raw = b4a.toString(await vfs.readFile('/proc/bare_os/initd_readiness.json'))
|
|
const parsed = JSON.parse(raw)
|
|
t.is(parsed.schema, 2)
|
|
t.alike(parsed.units, [])
|
|
const idxRaw = b4a.toString(await vfs.readFile('/proc/bare_os/index.json'))
|
|
const idx = JSON.parse(idxRaw)
|
|
t.ok(
|
|
Array.isArray(idx.entries) &&
|
|
idx.entries.some((e) => e && e.name === 'initd_readiness.json')
|
|
)
|
|
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('guest VFS denies sensitive /.bare paths; unlocked allows', async (t) => {
|
|
const dir = testCorestoreDir('vfsbarepol')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvpol'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
await personal.put('/.bare/account', b4a.from('acctblob'))
|
|
await personal.put('/.bare/vault/x', b4a.from('v'))
|
|
const envG = { HOME: '/home/guest', PWD: '/home/guest', PATH: '/bin' }
|
|
const vfsG = createVfs(sys, personal, envG)
|
|
vfsG.bareOsIdentitySession = 'guest'
|
|
t.is(await vfsG.exists('/.bare/account'), false)
|
|
await t.exception(async () => {
|
|
await vfsG.readFile('/.bare/account')
|
|
})
|
|
const envU = {
|
|
HOME: '/home/abc123deadbe',
|
|
PWD: '/home/abc123deadbe',
|
|
PATH: '/bin',
|
|
UID: '999',
|
|
USER: 'abc123deadbe'
|
|
}
|
|
const vfsU = createVfs(sys, personal, envU)
|
|
vfsU.bareOsIdentitySession = 'unlocked'
|
|
t.is(b4a.toString(await vfsU.readFile('/.bare/account')), 'acctblob')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('guest VFS denies /.bare readdir/readFile/writeFile/unlink on sensitive paths', async (t) => {
|
|
const dir = testCorestoreDir('vfsbareguestops')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvbareguestops'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
await personal.put('/.bare/account', b4a.from('acctblob'))
|
|
|
|
const vfsGuest = createVfs(sys, personal, {
|
|
HOME: '/home/guest',
|
|
PWD: '/home/guest',
|
|
PATH: '/bin',
|
|
USER: 'guest'
|
|
})
|
|
vfsGuest.bareOsIdentitySession = 'guest'
|
|
|
|
await t.exception(async () => {
|
|
await vfsGuest.readdir('/.bare/account')
|
|
})
|
|
await t.exception(async () => {
|
|
await vfsGuest.readFile('/.bare/account')
|
|
})
|
|
await t.exception(async () => {
|
|
await vfsGuest.writeFile('/.bare/account', b4a.from('mutate'))
|
|
})
|
|
await t.exception(async () => {
|
|
await vfsGuest.unlink('/.bare/account')
|
|
})
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('BARE_OS_PERSONAL_ACCT_PREFIX scopes /.bare/account to acct subtree on personal drive', async (t) => {
|
|
const dir = testCorestoreDir('vfsbareacctscoped')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvbareacct'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
await personal.put(
|
|
'/.bare-os/acct/u999/.bare/account',
|
|
b4a.from('scoped-acct')
|
|
)
|
|
const envU = {
|
|
HOME: '/home/alice',
|
|
PWD: '/home/alice',
|
|
PATH: '/bin',
|
|
UID: '999',
|
|
USER: 'alice',
|
|
BARE_OS_PERSONAL_ACCT_PREFIX: '1'
|
|
}
|
|
const vfsU = createVfs(sys, personal, envU)
|
|
vfsU.bareOsIdentitySession = 'unlocked'
|
|
t.is(b4a.toString(await vfsU.readFile('/.bare/account')), 'scoped-acct')
|
|
const envG = {
|
|
...envU,
|
|
HOME: '/home/guest',
|
|
PWD: '/home/guest',
|
|
UID: '65534'
|
|
}
|
|
const vfsG = createVfs(sys, personal, envG)
|
|
vfsG.bareOsIdentitySession = 'guest'
|
|
t.is(await vfsG.exists('/.bare/account'), false)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('BARE_OS_PERSONAL_ACCT_PREFIX nests guest tmp on personal drive', async (t) => {
|
|
const dir = testCorestoreDir('vfsacct')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvacct'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const env = {
|
|
HOME: '/home/guest',
|
|
PWD: '/home/guest',
|
|
PATH: '/bin',
|
|
BARE_OS_PERSONAL_ACCT_PREFIX: '1'
|
|
}
|
|
const vfs = createVfs(sys, personal, env)
|
|
await vfs.writeFile('/tmp/acct.txt', b4a.from('ok'))
|
|
t.is(
|
|
b4a.toString(await personal.get('/.bare-os/acct/_guest/tmp/guest/acct.txt')),
|
|
'ok'
|
|
)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('BARE_OS_PERSONAL_ACCT_PREFIX: /.bare/holesail/guest shares personal drive root across guest and unlocked', async (t) => {
|
|
const dir = testCorestoreDir('vfsholesailstable')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvhsstable'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const envG = {
|
|
HOME: '/home/guest',
|
|
PWD: '/home/guest',
|
|
PATH: '/bin',
|
|
USER: 'guest',
|
|
UID: '65534',
|
|
GID: '65534',
|
|
BARE_OS_PERSONAL_ACCT_PREFIX: '1'
|
|
}
|
|
const vfsG = createVfs(sys, personal, envG)
|
|
vfsG.bareOsIdentitySession = 'guest'
|
|
const payload = JSON.stringify({
|
|
version: 1,
|
|
connections: { t: { server: true } }
|
|
})
|
|
await vfsG.writeFile('/.bare/holesail/guest/state.json', b4a.from(payload))
|
|
t.alike(JSON.parse(b4a.toString(await personal.get('/.bare/holesail/guest/state.json'))), {
|
|
version: 1,
|
|
connections: { t: { server: true } }
|
|
})
|
|
const envU = {
|
|
HOME: '/home/alicehs',
|
|
PWD: '/home/alicehs',
|
|
PATH: '/bin',
|
|
USER: 'alicehs',
|
|
UID: '1001',
|
|
GID: '1001',
|
|
BARE_OS_PERSONAL_ACCT_PREFIX: '1'
|
|
}
|
|
const vfsU = createVfs(sys, personal, envU)
|
|
vfsU.bareOsIdentitySession = 'unlocked'
|
|
t.alike(JSON.parse(b4a.toString(await vfsU.readFile('/.bare/holesail/guest/state.json'))), {
|
|
version: 1,
|
|
connections: { t: { server: true } }
|
|
})
|
|
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', UID: '1000', GID: '1000' }
|
|
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'])
|
|
const rootSt = await vfs.lstat('/mnt/vault')
|
|
t.ok(rootSt && rootSt.type === 'directory')
|
|
t.is(rootSt.uid, 1000)
|
|
t.ok((rootSt.mode & 0o200) !== 0, 'writable mount root must allow owner write')
|
|
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('hdms-share-offer decodeShareOffer parses string and bytes', async (t) => {
|
|
t.is(HDMS_AUTOPASS_SHARE_KEY, 'bare-os-hdms/pending-share')
|
|
t.is(HDMS_AUTOPASS_SHARE_RW_KEY, 'bare-os-hdms/pending-share-rw')
|
|
t.is(decodeShareOffer(null), null)
|
|
t.is(decodeShareOffer({}), null)
|
|
t.is(decodeShareOffer({ value: 'not-json' }), null)
|
|
t.is(decodeShareOffer({ value: '{}' }), null)
|
|
const json = JSON.stringify({ label: 'vol', key: 'abc123' })
|
|
t.alike(decodeShareOffer({ value: json }), { label: 'vol', key: 'abc123' })
|
|
t.alike(decodeShareOffer({ value: b4a.from(json, 'utf8') }), {
|
|
label: 'vol',
|
|
key: 'abc123'
|
|
})
|
|
})
|
|
|
|
test('hdms-share-offer decodeRwShareOffer and normalizeWriterSecretHex', async (t) => {
|
|
const hex = 'a'.repeat(128)
|
|
t.is(normalizeWriterSecretHex(' ' + hex.toUpperCase() + ' '), hex)
|
|
t.exception(() => normalizeWriterSecretHex('ff'), /128 hex/)
|
|
t.is(decodeRwShareOffer(null), null)
|
|
const kp = hcCrypto.keyPair()
|
|
const driveKey = idEnc.encode(kp.publicKey)
|
|
const signerKey = idEnc.encode(kp.publicKey)
|
|
const json = JSON.stringify({
|
|
label: 'v',
|
|
key: driveKey,
|
|
signerKey,
|
|
writerSecretHex: hex
|
|
})
|
|
t.alike(decodeRwShareOffer({ value: json }), {
|
|
label: 'v',
|
|
key: driveKey,
|
|
signerKey,
|
|
writerSecretHex: hex
|
|
})
|
|
t.is(decodeRwShareOffer({ value: '{"label":"x","key":"y","writerSecretHex":"' + hex + '"}' }), null)
|
|
})
|
|
|
|
test('autopass hyperdb legacy value header (type byte omitted)', async (t) => {
|
|
const db = require('autopass/spec/db')
|
|
const records = db.collections[0]
|
|
const keyBuf = records.encodeKey({ key: 'bare-os-hdms/pending-share' })
|
|
const valueBuf = records.encodeValue(1, 1, {
|
|
key: 'bare-os-hdms/pending-share',
|
|
value: '{"label":"x","key":"y"}',
|
|
file: null
|
|
})
|
|
t.ok(records.reconstruct(1, keyBuf, valueBuf))
|
|
const legacyHeader = Buffer.from(valueBuf)
|
|
legacyHeader[0] = 1
|
|
t.ok(records.reconstruct(1, keyBuf, legacyHeader))
|
|
})
|
|
|
|
test('parseHdmsPairArgv flags and invite token', async (t) => {
|
|
t.alike(parseHdmsPairArgv(['--no-persist', 'invitez32']), {
|
|
ok: true,
|
|
persist: false,
|
|
inviteTok: 'invitez32'
|
|
})
|
|
t.alike(parseHdmsPairArgv(['tok2', '--no-persist']), {
|
|
ok: true,
|
|
persist: false,
|
|
inviteTok: 'tok2'
|
|
})
|
|
t.alike(parseHdmsPairArgv(['tok3']), {
|
|
ok: true,
|
|
persist: true,
|
|
inviteTok: 'tok3'
|
|
})
|
|
t.alike(parseHdmsPairArgv(['--persist', 'tok4']), {
|
|
ok: true,
|
|
persist: true,
|
|
inviteTok: 'tok4'
|
|
})
|
|
t.is(parseHdmsPairArgv(['--persist', '--no-persist', 'x']).ok, false)
|
|
t.is(parseHdmsPairArgv([]).ok, false)
|
|
})
|
|
|
|
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('appendVarLog increments skip metric when vfs.readFile missing', async (t) => {
|
|
bareOsKernelMetricsReset()
|
|
await appendVarLog(
|
|
{ b4a, env: {}, vfs: { async writeFile() {} } },
|
|
KERNEL_CONSOLE_LOG,
|
|
'x',
|
|
'line'
|
|
)
|
|
const snap = bareOsKernelMetricsSnapshot()
|
|
t.is(snap['varlog.append.skip_no_vfs_readfile'], 1)
|
|
bareOsKernelMetricsReset()
|
|
})
|
|
|
|
test('HOME segment switch needs ensureBareOsVarLogTree before kernel-console read', async (t) => {
|
|
const dir = testCorestoreDir('varloghomeseg')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvhs'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(sys, personal)
|
|
await ensureBareOsVarLogTree(ctx)
|
|
ctx.vfs.env.HOME = '/home/other'
|
|
ctx.vfs.env.PWD = '/home/other'
|
|
let threw = false
|
|
try {
|
|
await ctx.vfs.readFile('/var/log/bare-os/kernel-console.log')
|
|
} catch {
|
|
threw = true
|
|
}
|
|
t.ok(threw, 'traverse fails before bare-os dir exists for new HOME segment')
|
|
await ensureBareOsVarLogTree(ctx)
|
|
const buf = await ctx.vfs.readFile('/var/log/bare-os/kernel-console.log')
|
|
t.is(buf, null)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('ensureBareOsVarLogTree + appendVarLog populate personal backing', async (t) => {
|
|
const dir = testCorestoreDir('varlogpop')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pvlog'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(sys, personal)
|
|
await ensureBareOsVarLogTree(ctx)
|
|
await appendVarLog(ctx, KERNEL_CONSOLE_LOG, 't', 'hello-var-log')
|
|
const backing = await personal.get(
|
|
'/.bare-os/var/log/user/bare-os/kernel-console.log'
|
|
)
|
|
t.ok(backing)
|
|
t.ok(b4a.toString(backing).includes('hello-var-log'))
|
|
const readme = await personal.get('/.bare-os/var/log/user/bare-os/README')
|
|
t.ok(readme && b4a.toString(readme).includes('kernel-console'))
|
|
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('tokenize records quote parts for glob (literal * in quotes)', async (t) => {
|
|
const u = tokenize('echo *')
|
|
const uwords = u.filter((x) => x.type === 'word')
|
|
const star = uwords[uwords.length - 1]
|
|
t.ok(star && star.type === 'word' && star.parts)
|
|
t.is(star.parts.length, 1)
|
|
t.is(star.parts[0].q, 'u')
|
|
t.is(star.parts[0].t, '*')
|
|
const sq = tokenize("echo '*'")
|
|
const sqw = sq.filter((x) => x.type === 'word')
|
|
t.is(sqw[0].value, 'echo')
|
|
const lit = sqw[1]
|
|
t.is(lit.parts.length, 1)
|
|
t.is(lit.parts[0].q, 's')
|
|
t.is(lit.parts[0].t, '*')
|
|
})
|
|
|
|
test('tokenize keeps arithmetic expansion in one word token', async (t) => {
|
|
const toks = tokenize('n=$((n+1)); echo "$((n+2))"')
|
|
const words = toks.filter((x) => x.type === 'word').map((x) => x.value)
|
|
t.ok(words.includes('n=$((n+1))'))
|
|
t.ok(words.includes('$((n+2))'))
|
|
t.is(toks.filter((x) => x.type === 'op' && x.value === '(').length, 0)
|
|
t.is(toks.filter((x) => x.type === 'op' && x.value === ')').length, 0)
|
|
})
|
|
|
|
test('tokenize recognizes <<- heredoc operator', async (t) => {
|
|
const toks = tokenize('cat <<- EOF')
|
|
t.ok(toks.some((x) => x.type === 'op' && x.value === '<<-'))
|
|
})
|
|
|
|
test('tokenize emits compound ;; ;& |& >& operators', (t) => {
|
|
const a = tokenize('echo ok;; echo x')
|
|
t.ok(a.some((x) => x.type === 'op' && x.value === ';;'))
|
|
t.ok(tokenize('a;&b').some((x) => x.type === 'op' && x.value === ';&'))
|
|
t.ok(tokenize('a|&b').some((x) => x.type === 'op' && x.value === '|&'))
|
|
t.ok(tokenize('a>&2').some((x) => x.type === 'op' && x.value === '>&'))
|
|
})
|
|
|
|
test('decodeBareOsDollarQuote handles ANSI escapes', (t) => {
|
|
t.is(decodeBareOsDollarQuote(String.raw`a\n\t\\'\x41`), "a\n\t\\'A")
|
|
})
|
|
|
|
test('tokenize parses $\'…\' as decoded single-quoted segment', (t) => {
|
|
const toks = tokenize(`echo $'hi\\nx'`)
|
|
const echoTok = toks.find((x) => x.type === 'word' && x.value.includes('hi'))
|
|
t.ok(echoTok && echoTok.parts)
|
|
const sq = echoTok.parts.filter((p) => p.q === 's')
|
|
t.ok(sq.some((p) => p.t === 'hi\nx'))
|
|
})
|
|
|
|
test('execShellLine $\'…\' echo and cmdsubst $(…) / backticks when enabled', async (t) => {
|
|
const dir = testCorestoreDir('shansi')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('sans'))
|
|
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(' '))
|
|
}
|
|
`)
|
|
)
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push('e:' + a.join(' '))
|
|
}
|
|
ctx.vfs.env.BARE_OS_SHELL_CMDSUBST = '1'
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, `echo $'a\\tb'`)
|
|
t.ok(logs.some((l) => l === 'a\tb'), logs.join('|'))
|
|
logs.length = 0
|
|
await execShellLine(ctx, 'echo "$(echo hi)"')
|
|
t.ok(logs.some((l) => l.includes('hi')), logs.join('|'))
|
|
logs.length = 0
|
|
await execShellLine(ctx, 'echo "`echo lo`"')
|
|
t.ok(logs.some((l) => l.includes('lo')), logs.join('|'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine command substitution is enabled by default', async (t) => {
|
|
const dir = testCorestoreDir('shcmdsub-default')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pcmdsubdef'))
|
|
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(' '))
|
|
}
|
|
`)
|
|
)
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
delete ctx.vfs.env.BARE_OS_SHELL_CMDSUBST
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push('e:' + a.join(' '))
|
|
}
|
|
await execShellLine(ctx, 'echo "$(echo hi)"')
|
|
t.ok(logs.some((l) => l.includes('hi')), logs.join('|'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine brace range expansion supports {1..5}', async (t) => {
|
|
const dir = testCorestoreDir('sh-brace-range')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pbrace'))
|
|
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(' '))
|
|
}
|
|
`)
|
|
)
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.vfs.env.BARE_OS_SHELL_BRACE_EXPANSION = '1'
|
|
ctx.console = { log: (m) => lines.push(String(m)), error: () => {} }
|
|
await execShellLine(ctx, 'echo {1..5}')
|
|
const joined = lines.join('|')
|
|
t.ok(!joined.includes('1..5'), joined)
|
|
t.ok(joined.includes('1'), joined)
|
|
t.ok(joined.includes('5'), joined)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine arithmetic command form (( 2 + 3 ))', async (t) => {
|
|
const dir = testCorestoreDir('sh-arith-cmd')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('parithcmd'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
await execShellLine(ctx, '(( 2 + 3 ))')
|
|
t.is(ctx.exitCode, 0)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('expandWord supports ${#var} length and ${var:offset} when v3', (t) => {
|
|
const env = {
|
|
X: 'abcde',
|
|
BARE_OS_SHELL_PARAM_EXPANSION: '1',
|
|
BARE_OS_SHELL_PARAM_EXPANSION_V3: '1'
|
|
}
|
|
t.is(expandWord('${#X}', env), '5')
|
|
t.is(expandWord('${X:2}', env), 'cde')
|
|
t.is(expandWord('${X:2:2}', env), 'cd')
|
|
})
|
|
|
|
test('expandWord indirect ${!a} when enabled', (t) => {
|
|
const env = {
|
|
ref: 'X',
|
|
X: 'hi',
|
|
BARE_OS_SHELL_PARAM_EXPANSION: '1',
|
|
BARE_OS_SHELL_INDIRECT_EXPANSION: '1'
|
|
}
|
|
t.is(expandWord('${!ref}', env), 'hi')
|
|
})
|
|
|
|
test('tokenizeBareShellLineDetailed records span and mode metadata', async (t) => {
|
|
const rows = tokenizeBareShellLineDetailed(`echo 'a b' "c d" $((1+2)) <<EOF`)
|
|
t.ok(rows.length >= 4)
|
|
t.ok(rows.some((r) => r.mode === 'single'))
|
|
t.ok(rows.some((r) => r.mode === 'double'))
|
|
t.ok(rows.some((r) => r.mode === 'arith' || r.value.includes('))')))
|
|
t.ok(rows.every((r) => Number.isInteger(r.start) && Number.isInteger(r.end)))
|
|
t.ok(rows.every((r) => r.end >= r.start))
|
|
})
|
|
|
|
test('tokenizeBareShellLineDetailed deterministic output', async (t) => {
|
|
const src = `if [ "$x" = "y" ]; then echo ok; fi`
|
|
const a = JSON.stringify(tokenizeBareShellLineDetailed(src))
|
|
const b = JSON.stringify(tokenizeBareShellLineDetailed(src))
|
|
t.is(a, b)
|
|
})
|
|
|
|
test('bareOsShellAstSnapshot produces stable schema and tokenized pipeline', async (t) => {
|
|
const snap = bareOsShellAstSnapshot('echo hi | cat')
|
|
t.is(snap.schema, 1)
|
|
t.is(snap.line, 'echo hi | cat')
|
|
t.ok(Array.isArray(snap.tokens) && snap.tokens.length >= 3)
|
|
t.ok(Array.isArray(snap.pipeline) && snap.pipeline.length === 2)
|
|
t.ok(Array.isArray(snap.diagnosticTokens) && snap.diagnosticTokens.length >= 3)
|
|
})
|
|
|
|
test('planShellRedirections returns normalized redirects', async (t) => {
|
|
const p = planShellRedirections({
|
|
redirIn: null,
|
|
redirOut: { type: 'word', value: '/tmp/o', parts: [] },
|
|
redirAppend: false,
|
|
redirErr: null,
|
|
redirErrAppend: false,
|
|
mergeStderrToStdout: true,
|
|
redirHereDoc: null
|
|
})
|
|
t.is(p.stdin, 'inherit')
|
|
t.is(p.stdout, 'truncate')
|
|
t.is(p.stderr, 'stdout')
|
|
})
|
|
|
|
test('buildShellExecutionGraph models lists and pipelines', async (t) => {
|
|
const g = buildShellExecutionGraph('echo a | cat && echo b; echo c')
|
|
t.is(g.schema, 1)
|
|
t.is(g.listCount, 2)
|
|
t.ok(Array.isArray(g.lists) && g.lists.length === 2)
|
|
t.is(g.lists[0].segments[0].pipelineLength, 2)
|
|
t.is(g.lists[0].andOrOps[0], '&&')
|
|
})
|
|
|
|
test('execShellLine expansion trace captures stages when enabled', async (t) => {
|
|
const dir = testCorestoreDir('shexpansiontrace')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pexpansiontrace'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.vfs.env.BARE_OS_SHELL_EXPANSION_TRACE = '1'
|
|
ctx.vfs.env.BARE_OS_SHELL_PARAM_EXPANSION = '1'
|
|
await execShellLine(ctx, 'A=ok; echo ${A:-no}')
|
|
t.ok(Array.isArray(ctx.shellExpansionTrace))
|
|
t.ok(ctx.shellExpansionTrace.some((r) => r.stage === 'expand-pre'))
|
|
t.ok(ctx.shellExpansionTrace.some((r) => r.stage === 'split-glob'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine stores execution graph when dump mode is enabled', async (t) => {
|
|
const dir = testCorestoreDir('shexecgraphdump')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pexecgraphdump'))
|
|
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(' ')) }`)
|
|
)
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.vfs.env.BARE_OS_SHELL_EXEC_GRAPH_DUMP = '1'
|
|
await execShellLine(ctx, 'echo one | echo two && echo three')
|
|
t.ok(ctx.shellLastExecGraph && ctx.shellLastExecGraph.schema === 1)
|
|
t.ok(ctx.shellLastExecGraph.listCount >= 1)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine test [ builtins and $(( $var + … ))', async (t) => {
|
|
const dir = testCorestoreDir('shbrackettest')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pshbrk'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/test', b4a.from(await readBuiltBin('test')))
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.runBinCommand = function (argv, ro) {
|
|
return runBinCommand(this, argv, ro)
|
|
}
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'a=1; test 2 -eq $((a+1))')
|
|
t.is(ctx.exitCode, 0)
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'a=1; [ 2 -eq $(($a+1)) ]')
|
|
t.is(ctx.exitCode, 0)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('expandWord arithmetic honors $var inside $(( ))', (t) => {
|
|
t.is(expandWord('$(( $a + 1 ))', { a: '2' }), '3')
|
|
t.is(expandWord('$(( $a+1 ))', { a: '2' }), '3')
|
|
})
|
|
|
|
test('expandWord expansion-depth guard env is accepted', (t) => {
|
|
const env = {
|
|
BARE_OS_SHELL_PARAM_EXPANSION: '1',
|
|
BARE_OS_SHELL_PARAM_EXPANSION_V2: '1',
|
|
BARE_OS_SHELL_EXPANSION_MAX_DEPTH: '1'
|
|
}
|
|
t.is(expandWord('${A:=x}', env), 'x')
|
|
})
|
|
|
|
test('expandWord strict POSIX arithmetic emits strict-token diagnostics', (t) => {
|
|
const env = { BARE_OS_SHELL_POSIX_MODE: '1' }
|
|
t.exception(
|
|
() => expandWord('$((1 + bad,2))', env),
|
|
/POSIX mode strict arithmetic/
|
|
)
|
|
})
|
|
|
|
test('execShellLine pathname glob and noglob', async (t) => {
|
|
const dir = testCorestoreDir('shglob')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('sglob'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const echo = `
|
|
async function run(ctx, argv) {
|
|
ctx.console.log(argv.slice(1).join(' '))
|
|
}
|
|
`
|
|
await drive.put('/bin/echo', b4a.from(echo))
|
|
const ctx = testCtx(drive, personal)
|
|
await ctx.vfs.writeFile('/home/user/a.txt', b4a.from('x'))
|
|
await ctx.vfs.writeFile('/home/user/b.txt', b4a.from('y'))
|
|
const logs = []
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
await ctx.vfs.chdir('/home/user')
|
|
await execShellLine(ctx, 'echo *.txt')
|
|
t.ok(
|
|
logs.some((l) => l.includes('a.txt') && l.includes('b.txt')),
|
|
logs.join('|')
|
|
)
|
|
logs.length = 0
|
|
await execShellLine(ctx, "echo '*.txt'")
|
|
t.ok(logs.some((l) => l === '*.txt'), logs.join('|'))
|
|
logs.length = 0
|
|
ctx.vfs.env.BARE_OS_SHELL_NOGLOB = '1'
|
|
await execShellLine(ctx, 'echo *.txt')
|
|
t.ok(logs.some((l) => l === '*.txt'))
|
|
delete ctx.vfs.env.BARE_OS_SHELL_NOGLOB
|
|
await execShellLine(ctx, 'set -f; echo *.txt')
|
|
t.ok(logs.some((l) => l === '*.txt'))
|
|
await execShellLine(ctx, 'set +f; echo *.txt')
|
|
t.ok(logs.some((l) => l.includes('a.txt')))
|
|
logs.length = 0
|
|
await execShellLine(ctx, 'echo ~user')
|
|
t.ok(logs.some((l) => l === '/home/user'), logs.join('|'))
|
|
logs.length = 0
|
|
await execShellLine(ctx, 'echo ~other')
|
|
t.ok(logs.some((l) => l === '/home/other'), logs.join('|'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine BARE_OS_SHELL_POSIX_MODE grouped list', async (t) => {
|
|
const dir = testCorestoreDir('shposixgroup')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('spg'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const echo = `
|
|
async function run(ctx, argv) {
|
|
ctx.console.log(argv.slice(1).join(' '))
|
|
}
|
|
`
|
|
await drive.put('/bin/echo', b4a.from(echo))
|
|
const ctx = testCtx(drive, personal)
|
|
const logs = []
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
ctx.vfs.env.BARE_OS_SHELL_POSIX_MODE = '1'
|
|
await ctx.vfs.chdir('/home/user')
|
|
await execShellLine(ctx, '( echo hi )')
|
|
t.ok(logs.some((l) => l === 'hi'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine BARE_OS_SHELL_GROUPING runs parenthesized list without POSIX mode', async (t) => {
|
|
const dir = testCorestoreDir('shgroupingonly')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('sgo'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const echo = `
|
|
async function run(ctx, argv) {
|
|
ctx.console.log(argv.slice(1).join(' '))
|
|
}
|
|
`
|
|
await drive.put('/bin/echo', b4a.from(echo))
|
|
const ctx = testCtx(drive, personal)
|
|
const logs = []
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
delete ctx.vfs.env.BARE_OS_SHELL_POSIX_MODE
|
|
ctx.vfs.env.BARE_OS_SHELL_GROUPING = '1'
|
|
await ctx.vfs.chdir('/home/user')
|
|
await execShellLine(ctx, '( echo hi )')
|
|
t.ok(logs.some((l) => l === 'hi'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine select is unsupported with clear error', async (t) => {
|
|
const dir = testCorestoreDir('shselectstub')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('sels'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
const logs = []
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'select x in a b; do echo x; done')
|
|
t.ok(logs.some((l) => l.includes('select') && l.includes('unsupported')))
|
|
t.is(ctx.exitCode, 2)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine rejects stray reserved word at statement start', async (t) => {
|
|
const dir = testCorestoreDir('shmisres')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('smr'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
const logs = []
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'then echo x')
|
|
t.ok(logs.some((l) => l.includes("reserved word 'then'")))
|
|
t.is(ctx.exitCode, 2)
|
|
logs.length = 0
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'fi')
|
|
t.ok(logs.some((l) => l.includes("reserved word 'fi'")))
|
|
t.is(ctx.exitCode, 2)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine [[ rejects by default and supports == when gated', async (t) => {
|
|
const dir = testCorestoreDir('shdblbr')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('sdb'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
const logs = []
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, '[[ a == a ]]')
|
|
t.ok(logs.some((l) => l.includes('not supported')))
|
|
t.is(ctx.exitCode, 2)
|
|
logs.length = 0
|
|
ctx.vfs.env.BARE_OS_SHELL_DOUBLE_BRACKET = '1'
|
|
await execShellLine(ctx, '[[ x == x ]]')
|
|
t.is(ctx.exitCode, 0)
|
|
await execShellLine(ctx, '[[ x == y ]]')
|
|
t.is(ctx.exitCode, 1)
|
|
await execShellLine(ctx, '[[ x != y ]]')
|
|
t.is(ctx.exitCode, 0)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine applies alias expansion before shell function dispatch', async (t) => {
|
|
const dir = testCorestoreDir('shaliasfn')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('safn'))
|
|
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(' '))
|
|
}
|
|
`)
|
|
)
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error(s) {
|
|
lines.push('e:' + String(s))
|
|
}
|
|
}
|
|
ctx.exitCode = 0
|
|
|
|
await execShellLine(
|
|
ctx,
|
|
"bos_fn_a() { echo FN; }; alias bos_fn_a='echo AL'; bos_fn_a"
|
|
)
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.includes('AL'))
|
|
t.ok(!lines.includes('FN'))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await execShellLine(
|
|
ctx,
|
|
"bos_fn_b() { echo FB; }; alias bos_call_b='bos_fn_b'; bos_call_b"
|
|
)
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.includes('FB'))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'bos_fn_c() { echo FC; }; bos_fn_c')
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.includes('FC'))
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine rejects process substitution with exit 2', async (t) => {
|
|
const dir = testCorestoreDir('shprosubst')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('psub'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
const logs = []
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'echo hi <(echo x)')
|
|
t.ok(logs.some((l) => l.includes('process substitution')))
|
|
t.is(ctx.exitCode, 2)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
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')
|
|
})
|
|
|
|
/** POSIX-style parameter expansion vectors (Issue 7 subset; env-gated V3). */
|
|
test('expandWord param expansion v3 (:- :+ :?)', async (t) => {
|
|
const base = {
|
|
BARE_OS_SHELL_PARAM_EXPANSION: '1',
|
|
BARE_OS_SHELL_PARAM_EXPANSION_V3: '1',
|
|
HOME: '/home/u'
|
|
}
|
|
t.is(expandWord('${UNSET:-/default}', base), '/default')
|
|
t.is(expandWord('${EMPTY:-/alt}', { ...base, EMPTY: '' }), '/alt')
|
|
t.is(expandWord('${SET:-ignored}', { ...base, SET: 'ok' }), 'ok')
|
|
t.is(expandWord('${SET:+present}', { ...base, SET: 'v' }), 'present')
|
|
t.is(expandWord('${UNSET:+absent}', base), '')
|
|
t.exception(
|
|
() => expandWord('${UNSET:?hard fail}', base),
|
|
/hard fail|parameter null or unset/
|
|
)
|
|
t.exception(
|
|
() => expandWord('${EMPTY:?msg}', { ...base, EMPTY: '' }),
|
|
/msg|parameter null or unset/
|
|
)
|
|
})
|
|
|
|
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('execShellLine export -p lists session env', async (t) => {
|
|
const dir = testCorestoreDir('shexportp')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pexp'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (m) => logs.push(String(m)),
|
|
error: (...a) => logs.push(a.map(String).join(' '))
|
|
}
|
|
ctx.vfs.env.FOO = 'bar'
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'export -p')
|
|
t.ok(logs.some((l) => l.includes("export FOO='bar'")))
|
|
t.is(ctx.exitCode, 0)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine readonly -p lists readonly vars with stable quoting', async (t) => {
|
|
const dir = testCorestoreDir('shreadonlyp')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pronly'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (m) => logs.push(String(m)),
|
|
error: (...a) => logs.push(a.map(String).join(' '))
|
|
}
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, "readonly FOO=bar BAR='x y'")
|
|
await execShellLine(ctx, 'readonly -p')
|
|
t.ok(logs.some((l) => l.includes("readonly BAR='x y'")))
|
|
t.ok(logs.some((l) => l.includes("readonly FOO='bar'")))
|
|
t.is(ctx.exitCode, 0)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine BARE_OS_SHELL_READ_BUILTIN reads from shellStdin', async (t) => {
|
|
const dir = testCorestoreDir('shreadin')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pshr'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.vfs.env.BARE_OS_SHELL_READ_BUILTIN = '1'
|
|
ctx.vfs.env.IFS = ' '
|
|
ctx.shellStdin = 'one two\n'
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'read x y')
|
|
t.is(ctx.vfs.env.x, 'one')
|
|
t.is(ctx.vfs.env.y, 'two')
|
|
t.is(ctx.exitCode, 0)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine read builtin supports -d delimiter', async (t) => {
|
|
const dir = testCorestoreDir('shread-delim')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pshread-delim'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.vfs.env.BARE_OS_SHELL_READ_BUILTIN = '1'
|
|
ctx.shellStdin = 'alpha,beta,gamma'
|
|
await execShellLine(ctx, 'read -d , first rest')
|
|
t.is(ctx.vfs.env.first, 'alpha')
|
|
t.is(ctx.exitCode, 0)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine read builtin supports -r raw backslashes', async (t) => {
|
|
const dir = testCorestoreDir('shread-raw')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pshread-raw'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.vfs.env.BARE_OS_SHELL_READ_BUILTIN = '1'
|
|
ctx.shellStdin = 'a\\ b\n'
|
|
await execShellLine(ctx, 'read cooked')
|
|
t.is(ctx.vfs.env.cooked, 'a b')
|
|
ctx.shellStdin = 'c\\ d\n'
|
|
await execShellLine(ctx, 'read -r raw')
|
|
t.is(ctx.vfs.env.raw, 'c\\ d')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine read builtin timeout returns non-zero', async (t) => {
|
|
const dir = testCorestoreDir('shread-timeout')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pshread-timeout'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.vfs.env.BARE_OS_SHELL_READ_BUILTIN = '1'
|
|
ctx.readLine = async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
return 'late'
|
|
}
|
|
await execShellLine(ctx, 'read -t 0.01 x')
|
|
t.is(ctx.exitCode, 1)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine here-doc works in non-interactive script mode', async (t) => {
|
|
const dir = testCorestoreDir('sh-heredoc-script')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pshhd'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/cat', b4a.from(await readBuiltBin('cat')))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = { log: (s) => lines.push(String(s)), error() {} }
|
|
ctx.readLine = (() => {
|
|
const seq = ['one $HOME', 'two', 'EOF']
|
|
return () => Promise.resolve(seq.shift() ?? null)
|
|
})()
|
|
ctx.vfs.env.HOME = '/home/user'
|
|
ctx.vfs.env.BARE_OS_SHELL_POSIX_MODE = '1'
|
|
await execShellLine(ctx, 'cat << EOF')
|
|
t.is(ctx.exitCode, 0)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine BARE_OS_SHELL_ERREXIT skips after failed command', async (t) => {
|
|
const dir = testCorestoreDir('errexit')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pex-e'))
|
|
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 }`)
|
|
)
|
|
await drive.put(
|
|
'/bin/echo',
|
|
b4a.from(
|
|
`async function run(ctx, argv) { ctx.console.log(argv.slice(1).join(' ')) }`
|
|
)
|
|
)
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (m) => logs.push(String(m)),
|
|
error: (...a) => logs.push(a.map(String).join(' '))
|
|
}
|
|
ctx.vfs.env.BARE_OS_SHELL_ERREXIT = '1'
|
|
await execShellLine(ctx, 'false; echo AFTER')
|
|
t.ok(!logs.some((l) => l.includes('AFTER')))
|
|
logs.length = 0
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'true; echo OK')
|
|
t.ok(logs.some((l) => l.includes('OK')))
|
|
logs.length = 0
|
|
ctx.exitCode = 0
|
|
delete ctx.vfs.env.BARE_OS_SHELL_ERREXIT
|
|
await execShellLine(ctx, 'set -e; false; echo NEVER')
|
|
t.ok(!logs.some((l) => l.includes('NEVER')))
|
|
logs.length = 0
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'set -e; if true; then false; echo NEVER1; fi; echo NEVER2')
|
|
t.ok(!logs.some((l) => l.includes('NEVER1')))
|
|
t.ok(!logs.some((l) => l.includes('NEVER2')))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine trap normalizes signals and prints handlers', async (t) => {
|
|
const dir = testCorestoreDir('sh-trap')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pshtrap'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (m) => logs.push(String(m)),
|
|
error: (...a) => logs.push(a.map(String).join(' '))
|
|
}
|
|
await execShellLine(ctx, "trap 'trap -l' SIGTERM")
|
|
t.is(ctx.shellTrapHandlers.TERM, 'trap -l')
|
|
await execShellLine(ctx, 'trap - TERM')
|
|
t.is(ctx.shellTrapHandlers.TERM, undefined)
|
|
await execShellLine(ctx, "trap 'trap -l' TERM")
|
|
logs.length = 0
|
|
await execShellLine(ctx, 'trap -p')
|
|
t.ok(logs.some((l) => l.includes("trap -- 'trap -l' TERM")))
|
|
logs.length = 0
|
|
const ran = await dispatchShellTrapSignal(ctx, 'SIGTERM')
|
|
t.is(ran, true)
|
|
t.ok(logs.some((l) => l.includes('HUP INT KILL TERM')))
|
|
const missed = await dispatchShellTrapSignal(ctx, 'SIGUSR1')
|
|
t.is(missed, false)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine trap EXIT runs before exit', async (t) => {
|
|
const dir = testCorestoreDir('sh-trap-exit')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pshtrapexit'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (m) => logs.push(String(m)),
|
|
error: (...a) => logs.push(a.map(String).join(' '))
|
|
}
|
|
await execShellLine(ctx, "trap 'trap -l' EXIT; exit 1")
|
|
t.ok(logs.some((l) => l.includes('HUP INT KILL TERM')))
|
|
t.is(ctx.exitCode, 1)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine BARE_OS_SHELL_NOUNSET rejects unbound variable', async (t) => {
|
|
const dir = testCorestoreDir('nounset')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pex-nu'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
const errs = []
|
|
ctx.console.error = (m) => errs.push(String(m))
|
|
ctx.vfs.env.BARE_OS_SHELL_NOUNSET = '1'
|
|
await execShellLine(ctx, 'echo $UNBOUND_XYZ')
|
|
t.is(ctx.exitCode, 1)
|
|
t.ok(errs.some((e) => /unbound variable/.test(e)))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine BARE_OS_SHELL_PIPEFAIL uses first failing stage exit', async (t) => {
|
|
const dir = testCorestoreDir('pipefail')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pex-pf'))
|
|
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.vfs.env.BARE_OS_SHELL_PIPEFAIL = '1'
|
|
await execShellLine(ctx, 'false | true')
|
|
t.is(ctx.exitCode, 1)
|
|
delete ctx.vfs.env.BARE_OS_SHELL_PIPEFAIL
|
|
await execShellLine(ctx, 'false | true')
|
|
t.is(ctx.exitCode, 0)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine set -o pipefail and BARE_OS_PIPESTATUS', async (t) => {
|
|
const dir = testCorestoreDir('pipestatus')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pex-ps'))
|
|
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.vfs.env.BARE_OS_SHELL_PIPESTATUS = '1'
|
|
await execShellLine(ctx, 'set -o pipefail; true | false | true')
|
|
t.is(ctx.vfs.env.BARE_OS_PIPESTATUS, '0 1 0')
|
|
t.is(ctx.exitCode, 1)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand kill resolves %n and %% via shellBackgroundJobs', async (t) => {
|
|
const dir = testCorestoreDir('killjob')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pkjob'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const killPath = path.join(
|
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
'../../kernel/bin/kill'
|
|
)
|
|
const killSrc = await readFile(killPath)
|
|
await drive.put('/bin/kill', killSrc)
|
|
|
|
const ctx = testCtx(drive, personal)
|
|
/** @type {[string, string][]} */
|
|
const calls = []
|
|
ctx.bareOsSendSignal = function (target, sig) {
|
|
calls.push([String(target), String(sig)])
|
|
}
|
|
ctx.shellBackgroundJobs = {
|
|
nextId: 3,
|
|
list: [
|
|
{
|
|
id: 1,
|
|
pgid: 400,
|
|
done: false,
|
|
stopped: false,
|
|
label: 'a',
|
|
promise: Promise.resolve('ok')
|
|
},
|
|
{
|
|
id: 2,
|
|
pgid: 401,
|
|
done: false,
|
|
stopped: false,
|
|
label: 'b',
|
|
promise: Promise.resolve('ok')
|
|
}
|
|
]
|
|
}
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['kill', '-0', '%1'])
|
|
t.alike(calls[0], ['4101', '0'])
|
|
calls.length = 0
|
|
await runBinCommand(ctx, ['kill', '-TERM', '%%'])
|
|
t.alike(calls[0], ['4102', 'TERM'])
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine set -o prints shell toggles', async (t) => {
|
|
const dir = testCorestoreDir('seto-print')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pso'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (m) => logs.push(String(m)),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'set -o')
|
|
t.ok(logs.some((l) => /errexit\s+off/.test(l)))
|
|
t.ok(logs.some((l) => /pipefail\s+off/.test(l)))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine jobs -p prints pgid only', async (t) => {
|
|
const dir = testCorestoreDir('jobs-p')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pjobs'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (m) => logs.push(String(m)),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
ctx.shellBackgroundJobs = {
|
|
nextId: 2,
|
|
list: [
|
|
{
|
|
id: 1,
|
|
pgid: 712,
|
|
sid: 1,
|
|
done: false,
|
|
stopped: false,
|
|
label: 'x',
|
|
promise: Promise.resolve('ok')
|
|
}
|
|
]
|
|
}
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'jobs -p')
|
|
t.ok(logs.includes('712'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine jobs -l is stable and parseable', async (t) => {
|
|
const dir = testCorestoreDir('jobs-l-parseable')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pjobsl'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (m) => logs.push(String(m)),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
ctx.shellBackgroundJobs = {
|
|
nextId: 3,
|
|
list: [
|
|
{
|
|
id: 2,
|
|
pgid: 990,
|
|
sid: 1,
|
|
done: false,
|
|
stopped: false,
|
|
label: 'sleep 1',
|
|
promise: Promise.resolve('ok')
|
|
}
|
|
]
|
|
}
|
|
await execShellLine(ctx, 'jobs -l')
|
|
t.ok(logs.some((l) => /^\[2\]\+\s+Running/.test(l)))
|
|
t.ok(logs.some((l) => l.includes('pid=4102')))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine wait supports synthetic pid and all keyword', async (t) => {
|
|
const dir = testCorestoreDir('wait-pid-all')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pwaitpidall'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const mkJob = (id, exitCode) => ({
|
|
id,
|
|
pgid: 700 + id,
|
|
sid: 1,
|
|
done: false,
|
|
stopped: false,
|
|
label: `job-${id}`,
|
|
lastExitCode: exitCode,
|
|
promise: Promise.resolve('ok')
|
|
})
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.shellBackgroundJobs = { nextId: 3, list: [mkJob(1, 0), mkJob(2, 7)] }
|
|
await execShellLine(ctx, 'wait 4102')
|
|
t.is(ctx.exitCode, 7)
|
|
await execShellLine(ctx, 'wait all')
|
|
t.is(ctx.exitCode, 7)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine deny/allow policy and sandbox guard', async (t) => {
|
|
const dir = testCorestoreDir('sh-policy-sandbox')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('ppolicysandbox'))
|
|
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(' ')) }`))
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (m) => logs.push(String(m)),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
ctx.vfs.env.BARE_OS_SHELL_DENY_COMMANDS = 'echo'
|
|
await execShellLine(ctx, 'echo hi')
|
|
t.is(ctx.exitCode, 126)
|
|
t.ok(logs.some((l) => l.includes('command denied by policy')))
|
|
delete ctx.vfs.env.BARE_OS_SHELL_DENY_COMMANDS
|
|
ctx.vfs.env.BARE_OS_SHELL_SANDBOX = '1'
|
|
await execShellLine(ctx, 'echo hi')
|
|
t.is(ctx.exitCode, 126)
|
|
t.ok(logs.some((l) => l.includes('sandbox blocks external command')))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine unsafe redirect guard rejects risky paths', async (t) => {
|
|
const dir = testCorestoreDir('sh-redirect-guard')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('predirectguard'))
|
|
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(' ')) }`))
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (m) => logs.push(String(m)),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
ctx.vfs.env.BARE_OS_SHELL_REDIRECT_GUARD = '1'
|
|
await execShellLine(ctx, 'echo hi > /proc/self/environ')
|
|
t.is(ctx.exitCode, 1)
|
|
t.ok(logs.some((l) => l.includes('unsafe stdout redirect path denied')))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine redirect write to /proc/cpuinfo is visible nonzero', async (t) => {
|
|
const dir = testCorestoreDir('sh-proc-write-deny')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pprocwrite'))
|
|
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(' ')) }`)
|
|
)
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (m) => logs.push(String(m)),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
await execShellLine(ctx, 'echo hi > /proc/cpuinfo')
|
|
t.ok(ctx.exitCode !== 0)
|
|
t.ok(logs.some((l) => /EACCES|EROFS|denied|read-only/i.test(l)))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine appends structured shell audit events', async (t) => {
|
|
const dir = testCorestoreDir('sh-audit-events')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pauditevents'))
|
|
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(' ')) }`))
|
|
const ctx = testCtx(drive, personal)
|
|
await execShellLine(ctx, 'echo hello')
|
|
t.ok(Array.isArray(ctx.shellAuditEvents))
|
|
t.ok(ctx.shellAuditEvents.some((e) => e.event === 'shell.command.start'))
|
|
t.ok(ctx.shellAuditEvents.some((e) => e.event === 'shell.command.finish'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine errexit stops inside if-then body after failure', async (t) => {
|
|
const dir = testCorestoreDir('errexit-if')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pex-eif'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put(
|
|
'/bin/false',
|
|
b4a.from(`async function run(ctx) { ctx.exitCode = 1 }`)
|
|
)
|
|
await drive.put(
|
|
'/bin/echo',
|
|
b4a.from(
|
|
`async function run(ctx, argv) { ctx.console.log(argv.slice(1).join(' ')) }`
|
|
)
|
|
)
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (m) => logs.push(String(m)),
|
|
error: (...a) => logs.push(a.map(String).join(' '))
|
|
}
|
|
ctx.vfs.env.BARE_OS_SHELL_ERREXIT = '1'
|
|
await execShellLine(ctx, 'if true; then false; echo BAD; fi')
|
|
t.ok(!logs.some((l) => l.includes('BAD')))
|
|
logs.length = 0
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'if false; then echo no; fi; echo AFTER')
|
|
t.ok(logs.some((l) => l.includes('AFTER')))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('evaluateBareOsPeerAdmission honors dhtAddressClass with allowlist', async (t) => {
|
|
const env = {
|
|
BARE_OS_PEER_ALLOWLIST_HEX: '',
|
|
BARE_OS_PEER_ALLOW_ALL: '1',
|
|
BARE_OS_DHT_ADDRESS_CLASS_ALLOWLIST: 'ipv4,relay'
|
|
}
|
|
const deny = evaluateBareOsPeerAdmission(env, 'aa', {
|
|
dhtAddressClass: 'ipv6'
|
|
})
|
|
t.is(deny.schema, 2)
|
|
t.is(deny.verdict, 'deny')
|
|
const ok = evaluateBareOsPeerAdmission(env, 'aa', { dhtAddressClass: 'ipv4' })
|
|
t.is(ok.schema, 2)
|
|
t.is(ok.verdict, 'allow')
|
|
})
|
|
|
|
test('evaluateBareOsPeerAdmission denylist wins over allowlist', async (t) => {
|
|
const env = {
|
|
BARE_OS_PEER_ALLOWLIST_HEX: 'aa,bb',
|
|
BARE_OS_PEER_DENYLIST_HEX: 'bb'
|
|
}
|
|
t.is(evaluateBareOsPeerAdmission(env, 'bb').verdict, 'deny')
|
|
t.is(evaluateBareOsPeerAdmission(env, 'bb').reason, 'peer_denylist')
|
|
t.is(evaluateBareOsPeerAdmission(env, 'aa').verdict, 'allow')
|
|
})
|
|
|
|
test('evaluateBareOsPeerAdmission strict empty allowlist denies all peers', async (t) => {
|
|
const env = {
|
|
BARE_OS_PEER_ALLOWLIST_HEX: '',
|
|
BARE_OS_PEER_ALLOWLIST_STRICT: '1'
|
|
}
|
|
const r = evaluateBareOsPeerAdmission(env, 'abc')
|
|
t.is(r.verdict, 'deny')
|
|
t.ok(String(r.note || '').includes('allowlist is empty'))
|
|
})
|
|
|
|
test('evaluateBareOsPeerAdmission BARE_OS_PEER_REQUIRE_CAPS_JSON', async (t) => {
|
|
const env = {
|
|
BARE_OS_PEER_ALLOW_ALL: '1',
|
|
BARE_OS_PEER_REQUIRE_CAPS_JSON: '["seed","read"]'
|
|
}
|
|
t.is(
|
|
evaluateBareOsPeerAdmission(env, 'k1', { caps: ['seed'] }).verdict,
|
|
'deny'
|
|
)
|
|
t.is(
|
|
evaluateBareOsPeerAdmission(env, 'k1', { caps: ['seed', 'read'] })
|
|
.verdict,
|
|
'allow'
|
|
)
|
|
const denied = evaluateBareOsPeerAdmission(env, 'k1', {
|
|
caps: ['seed'],
|
|
dhtAddressClass: 'relay'
|
|
})
|
|
t.is(denied.reason, 'peer_missing_cap')
|
|
t.alike(denied.requiredCaps, ['seed', 'read'])
|
|
})
|
|
|
|
test('evaluateBareOsPeerAdmission denylist overrides satisfied require_caps', async (t) => {
|
|
const env = {
|
|
BARE_OS_PEER_DENYLIST_HEX: 'dead',
|
|
BARE_OS_PEER_REQUIRE_CAPS_JSON: '["seed"]'
|
|
}
|
|
const r = evaluateBareOsPeerAdmission(env, 'dead', { caps: ['seed'] })
|
|
t.is(r.verdict, 'deny')
|
|
t.is(r.reason, 'peer_denylist')
|
|
})
|
|
|
|
test('bareOsFormatPeerAdmissionAuditEvent never embeds full peer keys', (t) => {
|
|
const full = 'a'.repeat(64)
|
|
const res = evaluateBareOsPeerAdmission({}, full)
|
|
const ev = bareOsFormatPeerAdmissionAuditEvent(full, res, {
|
|
dhtAddressClass: 'ipv4'
|
|
})
|
|
t.is(ev.peerKeyHexPrefix, 'a'.repeat(16))
|
|
t.absent(ev.peerKeyHex)
|
|
t.is(ev.type, 'peer_admission')
|
|
t.is(ev.dhtAddressClass, 'ipv4')
|
|
})
|
|
|
|
test('bareOsPeerAdmissionAuditRateAllow throttles per bucket', (t) => {
|
|
const m = new Map()
|
|
t.ok(bareOsPeerAdmissionAuditRateAllow('b', 1000, 100, m))
|
|
t.absent(bareOsPeerAdmissionAuditRateAllow('b', 1050, 100, m))
|
|
t.ok(bareOsPeerAdmissionAuditRateAllow('b', 1100, 100, m))
|
|
})
|
|
|
|
test('bareOsDrainFcntlLockWaiters wakes FIFO waiters per path', (t) => {
|
|
const lockPath = '/coop/lockfifo'
|
|
const slot = { readers: new Set(), writer: 'holder1' }
|
|
const c = {
|
|
bareOsCooperativeLocks: { [lockPath]: slot },
|
|
bareOsFcntlLockWaitQueues: { [lockPath]: [] }
|
|
}
|
|
const q = c.bareOsFcntlLockWaitQueues[lockPath]
|
|
const order = []
|
|
q.push({
|
|
resolve: () => {
|
|
order.push('w2')
|
|
},
|
|
timer: null,
|
|
owner: 'sess2',
|
|
wantWrite: true
|
|
})
|
|
q.push({
|
|
resolve: () => {
|
|
order.push('w3')
|
|
},
|
|
timer: null,
|
|
owner: 'sess3',
|
|
wantWrite: true
|
|
})
|
|
slot.writer = null
|
|
bareOsDrainFcntlLockWaiters(c, lockPath)
|
|
t.alike(order, ['w2'])
|
|
t.is(slot.writer, 'sess2')
|
|
slot.writer = null
|
|
bareOsDrainFcntlLockWaiters(c, lockPath)
|
|
t.alike(order, ['w2', 'w3'])
|
|
t.is(slot.writer, 'sess3')
|
|
})
|
|
|
|
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'))
|
|
})
|
|
|
|
function stableStringifyManifest(obj) {
|
|
if (obj === null || typeof obj !== 'object') return JSON.stringify(obj)
|
|
if (Array.isArray(obj)) {
|
|
return '[' + obj.map((x) => stableStringifyManifest(x)).join(',') + ']'
|
|
}
|
|
const keys = Object.keys(obj).sort()
|
|
return (
|
|
'{' +
|
|
keys
|
|
.map((k) => JSON.stringify(k) + ':' + stableStringifyManifest(obj[k]))
|
|
.join(',') +
|
|
'}'
|
|
)
|
|
}
|
|
|
|
test('bare-module-manifest.data.mjs matches bare-module-manifest.json', async (t) => {
|
|
const dir = path.dirname(fileURLToPath(import.meta.url))
|
|
const jsonPath = path.join(dir, 'lib', 'bare-module-manifest.json')
|
|
const fromJson = JSON.parse(await readFile(jsonPath, 'utf8'))
|
|
const embedded = bareOsBareModuleManifestEmbeddedRef()
|
|
t.is(stableStringifyManifest(fromJson), stableStringifyManifest(embedded))
|
|
})
|
|
|
|
test('buildBareCtxObjectFromHost loads core keys on Node', async (t) => {
|
|
const target = {}
|
|
await buildBareCtxObjectFromHost(
|
|
{
|
|
// Narrow host import set: parallel import of the full manifest is slow and
|
|
// some Bare-oriented packages disturb brittle's hrtime-based timers.
|
|
BARE_OS_BARE_HOST_ONLY_CTX_KEYS: 'b4a,compactEncoding,protomux'
|
|
},
|
|
target
|
|
)
|
|
t.ok(target.b4a)
|
|
t.ok(target.compactEncoding)
|
|
t.ok(target.protomux)
|
|
})
|
|
|
|
test('buildPearCtxObjectFromHost loads pear tier keys on Node', async (t) => {
|
|
const target = {}
|
|
await buildPearCtxObjectFromHost({}, target)
|
|
t.ok(target.pearBuild, 'pearBuild')
|
|
t.ok(target.bareBundleCompile, 'bareBundleCompile')
|
|
t.ok(target.bareBundleEvaluate, 'bareBundleEvaluate')
|
|
// pear-bundle and pear-ref are Pear-runtime-only; host stub skips them on Node.
|
|
})
|
|
|
|
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 < in and > out redirection', async (t) => {
|
|
const dir = testCorestoreDir('shpipeio')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('spio'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
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/cat', b4a.from(cat))
|
|
const ctx = testCtx(drive, personal)
|
|
await ctx.vfs.writeFile('~/in.txt', b4a.from('pipeline-data\n'))
|
|
await execShellLine(ctx, 'cat < ~/in.txt | cat > ~/out.txt')
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(
|
|
b4a.toString(await ctx.vfs.readFile('~/out.txt'), 'utf8'),
|
|
'pipeline-data\n'
|
|
)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine pipeline first stage stdin < file only', async (t) => {
|
|
const dir = testCorestoreDir('shpipein')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('spin'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const cat = `
|
|
function bareStdin(ctx) {
|
|
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
|
|
}
|
|
async function run(ctx) {
|
|
ctx.console.log(bareStdin(ctx).replace(/\\n$/, ''))
|
|
}
|
|
`
|
|
const wcPath = path.join(__dirname, '../../kernel/bin/wc')
|
|
const wcSrc = await readFile(wcPath, 'utf8')
|
|
await drive.put('/bin/cat', b4a.from(cat))
|
|
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('~/lines.txt', b4a.from('a\nb\nc\n'))
|
|
await execShellLine(ctx, 'wc -l < ~/lines.txt | cat')
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.some((l) => /^\s*3\s/.test(l) || l.includes('3')))
|
|
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('execShellLine pipeline stage timeout aborts stalled stage safely', async (t) => {
|
|
const dir = testCorestoreDir('pipestagetimeout')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('ppipestagetimeout'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put(
|
|
'/bin/hang',
|
|
b4a.from(
|
|
`async function run(){ await new Promise((resolve)=>setTimeout(resolve,200)); }`
|
|
)
|
|
)
|
|
await drive.put(
|
|
'/bin/echo',
|
|
b4a.from(`async function run(ctx, argv){ ctx.console.log(argv.slice(1).join(' ')) }`)
|
|
)
|
|
const logs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (...a) => logs.push(a.join(' ')),
|
|
error: (...a) => logs.push(a.join(' '))
|
|
}
|
|
ctx.vfs.env.BARE_OS_SHELL_PIPELINE_STAGE_TIMEOUT_MS = '20'
|
|
await execShellLine(ctx, 'hang | echo never')
|
|
t.ok(logs.some((l) => l.includes('pipeline stage timeout')))
|
|
t.is(ctx.exitCode, 1)
|
|
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 drive.put(
|
|
'/bin/last',
|
|
b4a.from(`
|
|
async function run(ctx, argv) {
|
|
ctx.ran.push('last')
|
|
ctx.exitCode = 0
|
|
}
|
|
`)
|
|
)
|
|
ran.length = 0
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'false | last')
|
|
t.is(ran.join(','), 'false,last', 'pipeline runs both stages')
|
|
t.is(ctx.exitCode, 0, 'POSIX-like: pipeline exit status is last stage')
|
|
|
|
ran.length = 0
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'BARE_OS_SHELL_PIPEFAIL=1 false | last')
|
|
t.is(ran.join(','), 'false,last')
|
|
t.is(
|
|
ctx.exitCode,
|
|
1,
|
|
'pipefail: failure in an earlier stage overrides last stage success'
|
|
)
|
|
|
|
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')))
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'n=0; if true; then n=$((n+1)); fi; echo $n')
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.some((l) => l.includes('1')))
|
|
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 orJob = parseCronLine('0 12 15 * 1 echo dom-or-mon')
|
|
t.ok(orJob)
|
|
t.ok(
|
|
jobMatchesDate(orJob, new Date(2020, 5, 15, 12, 0, 0)),
|
|
'15th matches when dow also set (OR)'
|
|
)
|
|
t.ok(
|
|
jobMatchesDate(orJob, new Date(2020, 5, 8, 12, 0, 0)),
|
|
'Monday matches when dom also set (OR)'
|
|
)
|
|
t.ok(!jobMatchesDate(orJob, new Date(2020, 5, 16, 12, 0, 0)), 'Tue 16th no match')
|
|
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)
|
|
const dagRaw = getLastBareInitdDagSnapshotJson()
|
|
const dag = JSON.parse(String(dagRaw).trim())
|
|
t.ok(dag && dag.supervision && dag.supervision.schema === 1)
|
|
t.ok(Array.isArray(dag.supervision.restartKeys))
|
|
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'))
|
|
t.ok(text.includes('bare-openssh'))
|
|
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 status bare-openssh.service accepts .service suffix', async (t) => {
|
|
const dir = testCorestoreDir('initctlsvc')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('picsvc'))
|
|
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', 'status', 'bare-openssh.service'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(logs.join('\n').includes('bare-openssh'))
|
|
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('systemctl restart unknown unit is explicit nonzero', async (t) => {
|
|
const dir = testCorestoreDir('initctlunknown')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('punknown'))
|
|
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', 'nonunit'])
|
|
t.is(ctx.exitCode, 1)
|
|
t.ok(logs.some((l) => /unknown unit/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('bareInitdShutdownActiveUnitsReverse is safe when nothing is active', async (t) => {
|
|
stopBareInitd()
|
|
await bareInitdShutdownActiveUnitsReverse({ vfs: null, console, env: {} })
|
|
t.pass()
|
|
})
|
|
|
|
test('/proc/bare_os/clock.json is readable pseudo JSON', async (t) => {
|
|
const dir = testCorestoreDir('clock-proc')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pc'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const bootStartedMs = Date.now() - 50
|
|
const shellEnv = {
|
|
HOME: '/home/user',
|
|
PATH: '/bin',
|
|
USER: 'user',
|
|
UID: '1000',
|
|
GID: '1000',
|
|
PWD: '/home/user',
|
|
BARE_OS_EXIT_STATUS: '0'
|
|
}
|
|
const vfs = createVfs(drive, personal, shellEnv, null, {
|
|
bareOsIpc: createBareOsIpc(),
|
|
procBareOsClockText() {
|
|
const wallMs = Date.now()
|
|
return `${JSON.stringify({
|
|
schema: 1,
|
|
CLOCK_REALTIME_MS: wallMs,
|
|
CLOCK_BOOTTIME_RELATIVE_MS: wallMs - bootStartedMs,
|
|
CLOCK_MONOTONIC_NS_FROM_PERF: null
|
|
})}\n`
|
|
}
|
|
})
|
|
const raw = b4a.toString(await vfs.readFile('/proc/bare_os/clock.json'))
|
|
const j = JSON.parse(raw)
|
|
t.is(j.schema, 1)
|
|
t.ok(typeof j.CLOCK_REALTIME_MS === 'number')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('/proc/self/fd lists extras from getProcSelfExtraFds', async (t) => {
|
|
const dir = testCorestoreDir('self-fd-extra')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pc'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const shellEnv = {
|
|
HOME: '/home/user',
|
|
PATH: '/bin',
|
|
USER: 'user',
|
|
UID: '1000',
|
|
GID: '1000',
|
|
PWD: '/home/user',
|
|
BARE_OS_EXIT_STATUS: '0'
|
|
}
|
|
const vfs = createVfs(drive, personal, shellEnv, null, {
|
|
bareOsIpc: createBareOsIpc(),
|
|
getProcSelfExtraFds: () => [{ fdNum: '7', target: 'pipe:[bare-test]' }]
|
|
})
|
|
const names = await vfs.readdir('/proc/self/fd')
|
|
t.ok(names.includes('7'))
|
|
const txt = b4a.toString(await vfs.readFile('/proc/self/fd/7'))
|
|
t.ok(txt.includes('pipe:[bare-test]'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('fish-readline stripAnsi and fuzzyMatch', async (t) => {
|
|
t.is(stripAnsi('\x1b[32mhi\x1b[0m'), 'hi')
|
|
t.is(stripAnsi('\x1b[38;5;12mhi\x1b[0m'), 'hi')
|
|
t.ok(fuzzyMatch('hello', 'hlo'))
|
|
t.ok(!fuzzyMatch('hello', 'hxo'))
|
|
})
|
|
|
|
test('terminal-escapes xterm clear scrollback and viewport', async (t) => {
|
|
t.is(
|
|
XTERM_CLEAR_SCROLLBACK_AND_VIEWPORT,
|
|
'\x1b[H\x1b[3J\x1b[2J\x1b[0m'
|
|
)
|
|
})
|
|
|
|
test('fish-readline bracketed paste keeps embedded CSI as literal text', async (t) => {
|
|
const stdin = new PassThrough()
|
|
stdin.isTTY = true
|
|
stdin.setRawMode = () => {}
|
|
const out = []
|
|
const stdout = {
|
|
columns: 120,
|
|
rows: 40,
|
|
write(s) {
|
|
out.push(String(s))
|
|
},
|
|
cursorTo() {},
|
|
clearLine() {}
|
|
}
|
|
const ctx = {
|
|
vfs: {
|
|
getcwd: () => '/home/guest',
|
|
async readdir() {
|
|
return []
|
|
}
|
|
},
|
|
env: {},
|
|
b4a: null,
|
|
personalDrive: null
|
|
}
|
|
const rl = await createFishReadLine(ctx, { stdin, stdout })
|
|
t.ok(typeof rl === 'function')
|
|
const p = rl('> ')
|
|
stdin.write('\x1b[200~touch \x1b[Afile\x1b[201~\n')
|
|
const line = await p
|
|
t.is(line, 'touch \x1b[Afile')
|
|
stdin.end()
|
|
})
|
|
|
|
test('fish-readline ignores xterm focus in out CSI', async (t) => {
|
|
const stdin = new PassThrough()
|
|
stdin.isTTY = true
|
|
stdin.setRawMode = () => {}
|
|
const stdout = {
|
|
columns: 120,
|
|
rows: 40,
|
|
write() {},
|
|
cursorTo() {},
|
|
clearLine() {}
|
|
}
|
|
const ctx = {
|
|
vfs: {
|
|
getcwd: () => '/',
|
|
async readdir() {
|
|
return []
|
|
}
|
|
},
|
|
env: {},
|
|
b4a: null,
|
|
personalDrive: null
|
|
}
|
|
const rl = await createFishReadLine(ctx, { stdin, stdout })
|
|
const p = rl('> ')
|
|
stdin.write('\x1b[I\x1b[Oecho x\n')
|
|
t.is(await p, 'echo x')
|
|
stdin.end()
|
|
})
|
|
|
|
test('fish-readline clears ghost hint before submit newline', async (t) => {
|
|
const stdin = new PassThrough()
|
|
stdin.isTTY = true
|
|
stdin.setRawMode = () => {}
|
|
const out = []
|
|
const stdout = {
|
|
columns: 120,
|
|
rows: 40,
|
|
write(s) {
|
|
out.push(String(s))
|
|
},
|
|
cursorTo() {},
|
|
clearLine() {}
|
|
}
|
|
const ctx = {
|
|
vfs: {
|
|
env: { USER: 'guest' },
|
|
getcwd: () => '/mnt/myPeers',
|
|
async readdir() {
|
|
return []
|
|
}
|
|
},
|
|
env: {},
|
|
b4a,
|
|
personalDrive: {
|
|
async get() {
|
|
return b4a.from('#1:ls /bin\n')
|
|
},
|
|
async put() {}
|
|
}
|
|
}
|
|
const rl = await createFishReadLine(ctx, { stdin, stdout })
|
|
const p = rl('> ')
|
|
stdin.write('ls\n')
|
|
t.is(await p, 'ls')
|
|
|
|
const newlineIndex = out.findIndex((s) => s === '\n')
|
|
t.ok(newlineIndex > 0)
|
|
const beforeSubmitNewline = out
|
|
.slice(0, newlineIndex)
|
|
.map(stripAnsi)
|
|
.filter(Boolean)
|
|
.at(-1)
|
|
t.is(beforeSubmitNewline.endsWith('> ls'), true)
|
|
t.absent(beforeSubmitNewline.includes('/bin'))
|
|
stdin.end()
|
|
})
|
|
|
|
test('fish-readline ^L emits xterm clear sequence', async (t) => {
|
|
const stdin = new PassThrough()
|
|
stdin.isTTY = true
|
|
stdin.setRawMode = () => {}
|
|
const out = []
|
|
const stdout = {
|
|
columns: 120,
|
|
rows: 40,
|
|
write(s) {
|
|
out.push(String(s))
|
|
},
|
|
cursorTo() {},
|
|
clearLine() {}
|
|
}
|
|
const ctx = {
|
|
vfs: {
|
|
getcwd: () => '/',
|
|
async readdir() {
|
|
return []
|
|
}
|
|
},
|
|
env: {},
|
|
b4a: null,
|
|
personalDrive: null
|
|
}
|
|
const rl = await createFishReadLine(ctx, { stdin, stdout })
|
|
const p = rl('> ')
|
|
stdin.write('\x0c')
|
|
stdin.write('y\n')
|
|
await p
|
|
const joined = out.join('')
|
|
t.ok(joined.includes('\x1b[3J'))
|
|
t.ok(joined.includes('\x1b[2J'))
|
|
t.ok(joined.includes('\x1b[0m'))
|
|
stdin.end()
|
|
})
|
|
|
|
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('fish-readline history excludes login commands', async (t) => {
|
|
t.absent(shouldPersistShellHistoryCommand('login hunter2'))
|
|
t.absent(shouldPersistShellHistoryCommand('login --new hunter2'))
|
|
t.absent(shouldPersistShellHistoryCommand('echo ok; login hunter2'))
|
|
t.ok(shouldPersistShellHistoryCommand('echo login hunter2'))
|
|
|
|
const scrubbed = scrubShellHistoryEntries([
|
|
{ timestamp: 1, command: 'login old-password' },
|
|
{ timestamp: 2, command: 'ls' }
|
|
])
|
|
t.is(scrubbed.removed, 1)
|
|
t.alike(scrubbed.entries, [{ timestamp: 2, command: 'ls' }])
|
|
})
|
|
|
|
test('resolveShellPromptHookSegment enforces timeout and fallback', async (t) => {
|
|
const fast = await resolveShellPromptHookSegment(
|
|
{ shellPromptHook: () => ' [fast]' },
|
|
{ BARE_OS_SHELL_PROMPT_HOOK_TIMEOUT_MS: '10' }
|
|
)
|
|
t.is(fast, ' [fast]')
|
|
const slow = await resolveShellPromptHookSegment(
|
|
{
|
|
shellPromptHook: async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
return ' [slow]'
|
|
}
|
|
},
|
|
{ BARE_OS_SHELL_PROMPT_HOOK_TIMEOUT_MS: '5' }
|
|
)
|
|
t.is(slow, '')
|
|
})
|
|
|
|
test('fish-readline scrubs and skips persisted login history', async (t) => {
|
|
const stdin = new PassThrough()
|
|
stdin.isTTY = true
|
|
stdin.setRawMode = () => {}
|
|
const stdout = {
|
|
columns: 120,
|
|
rows: 40,
|
|
write() {},
|
|
cursorTo() {},
|
|
clearLine() {}
|
|
}
|
|
let stored = b4a.from('#1:login old-password\n#2:ls\n')
|
|
const writes = []
|
|
const ctx = {
|
|
vfs: {
|
|
env: { USER: 'guest' },
|
|
getcwd: () => '/home/guest',
|
|
async readdir() {
|
|
return []
|
|
}
|
|
},
|
|
env: {},
|
|
b4a,
|
|
personalDrive: {
|
|
async get() {
|
|
return stored
|
|
},
|
|
async put(_path, buf) {
|
|
stored = buf
|
|
writes.push(b4a.toString(buf))
|
|
}
|
|
}
|
|
}
|
|
const rl = await createFishReadLine(ctx, { stdin, stdout })
|
|
t.ok(writes[writes.length - 1].includes('ls'))
|
|
t.absent(writes[writes.length - 1].includes('old-password'))
|
|
|
|
const p = rl('> ')
|
|
stdin.write('login current-password\n')
|
|
t.is(await p, 'login current-password')
|
|
t.absent(b4a.toString(stored).includes('current-password'))
|
|
t.absent(b4a.toString(stored).includes('old-password'))
|
|
stdin.end()
|
|
})
|
|
|
|
test('fish-readline SHELL_BUILTINS tracks shell list', async (t) => {
|
|
t.ok(SHELL_BUILTINS.includes('cd'))
|
|
t.ok(SHELL_BUILTINS.includes('export'))
|
|
t.ok(SHELL_BUILTINS.includes('trap'))
|
|
})
|
|
|
|
test('completion-engine parseCompletionContext pipeline segment', async (t) => {
|
|
const line = 'echo a | grep '
|
|
const cx = parseCompletionContext(line, line.length)
|
|
t.is(cx.firstWord, 'grep')
|
|
t.is(cx.argIndex, 1)
|
|
const cx2 = parseCompletionContext('ls -', 4)
|
|
t.is(cx2.firstWord, 'ls')
|
|
t.is(cx2.argIndex, 1)
|
|
})
|
|
|
|
test('completion-engine rankCompletionItems prefix and levenshtein', async (t) => {
|
|
const items = [
|
|
{ value: 'abc', scoreBase: 0 },
|
|
{ value: 'axyz', scoreBase: 0 },
|
|
{ value: 'ab', scoreBase: 0 }
|
|
]
|
|
const r = rankCompletionItems(items, 'ab', {})
|
|
t.is(r[0].value, 'ab')
|
|
t.ok(levenshtein('ab', 'abc', 32) <= 2)
|
|
})
|
|
|
|
test('completion-engine suggestGhostFromHistory', async (t) => {
|
|
const h = [{ command: 'ls -la' }, { command: 'ls -l' }]
|
|
const g = suggestGhostFromHistory(h, 'ls')
|
|
t.ok(g.includes('ls'))
|
|
})
|
|
|
|
test('completion-engine ghostReplaceCurrentWord', async (t) => {
|
|
t.is(ghostReplaceCurrentWord('ls t', 4, 'test'), 'ls test')
|
|
t.is(ghostReplaceCurrentWord('t', 1, 'test'), 'test')
|
|
})
|
|
|
|
test('completion-engine suggestGhostFromFs cwd first token', async (t) => {
|
|
const vfs = {
|
|
getcwd: () => '/home/guest',
|
|
async readdir(d) {
|
|
if (d === '.') return ['test', 'testing', 'other']
|
|
return []
|
|
},
|
|
async stat(p) {
|
|
const s = String(p)
|
|
if (s === 'test' || s.endsWith('/test')) return { type: 'file', mode: 0o644 }
|
|
return { type: 'directory', mode: 0o755 }
|
|
}
|
|
}
|
|
const ctx = { vfs, shellAliases: {} }
|
|
const g = await suggestGhostFromFs(ctx, { HOME: '/home/guest' }, 't', 1)
|
|
t.is(g, 'test')
|
|
const g2 = await suggestGhostFromFs(ctx, { HOME: '/home/guest' }, 'ls t', 4)
|
|
t.is(g2, 'ls test')
|
|
})
|
|
|
|
test('completion-engine longestCommonCompletionPrefix', async (t) => {
|
|
const cx = parseCompletionContext('ls te', 5)
|
|
const p = longestCommonCompletionPrefix(
|
|
[{ value: 'test' }, { value: 'testing' }],
|
|
cx
|
|
)
|
|
t.is(p, 'test')
|
|
})
|
|
|
|
test('completion-engine completeLine for-in uses path candidates after in', async (t) => {
|
|
const vfs = {
|
|
getcwd: () => '/home/guest',
|
|
async readdir(d) {
|
|
const s = String(d)
|
|
if (s === '.' || s === '/home/guest') return ['apple', 'apricot']
|
|
return []
|
|
},
|
|
async readFile() {
|
|
return ''
|
|
},
|
|
async stat(p) {
|
|
const s = String(p)
|
|
if (s.includes('apple') || s.includes('apricot')) return { type: 'file', mode: 0o644 }
|
|
return { type: 'directory', mode: 0o755 }
|
|
}
|
|
}
|
|
const ctx = { vfs, shellAliases: {} }
|
|
const line = 'for z in ap'
|
|
const r = await completeLine(
|
|
ctx,
|
|
{ PATH: '/bin', HOME: '/home/guest', apple: '1' },
|
|
line,
|
|
line.length,
|
|
[],
|
|
{}
|
|
)
|
|
t.ok(
|
|
r.items.some(
|
|
(i) => i.value === 'apple' || i.value.endsWith('/apple') || i.value === './apple'
|
|
)
|
|
)
|
|
})
|
|
|
|
test('completion-engine completeLine for suggests in keyword', async (t) => {
|
|
const vfs = {
|
|
getcwd: () => '/',
|
|
async readdir() {
|
|
return []
|
|
},
|
|
async readFile() {
|
|
return ''
|
|
},
|
|
async stat() {
|
|
return { type: 'file', mode: 0o644 }
|
|
}
|
|
}
|
|
const ctx = { vfs, shellAliases: {} }
|
|
const line = 'for z i'
|
|
const r = await completeLine(ctx, { PATH: '/bin', HOME: '/home/guest' }, line, line.length, [], {})
|
|
t.ok(r.items.some((i) => i.value === 'in'))
|
|
})
|
|
|
|
test('completion-engine completeLine kill uses process_table.json', async (t) => {
|
|
const vfs = {
|
|
getcwd: () => '/',
|
|
async readdir(d) {
|
|
if (d === '/bin') return ['grep.js', 'kill.js']
|
|
return []
|
|
},
|
|
async readFile(p) {
|
|
const s = String(p)
|
|
if (s.includes('process_table')) {
|
|
return JSON.stringify({
|
|
processes: [{ pid: 3, name: 'bare-os-shell' }]
|
|
})
|
|
}
|
|
return ''
|
|
},
|
|
async stat() {
|
|
return { type: 'file', mode: 0o644 }
|
|
}
|
|
}
|
|
const ctx = { vfs, b4a, shellAliases: {} }
|
|
const kl = 'kill '
|
|
const r = await completeLine(ctx, { PATH: '/bin' }, kl, kl.length, [], {})
|
|
t.ok(r.items.some((i) => i.value === '3'))
|
|
})
|
|
|
|
test('completion-engine rankCompletionItems large list budget', async (t) => {
|
|
const items = []
|
|
for (let i = 0; i < 2000; i++) items.push({ value: 'f' + i, scoreBase: 0 })
|
|
const t0 = Date.now()
|
|
rankCompletionItems(items, 'f1', {})
|
|
t.ok(Date.now() - t0 < 800, 'rank under 800ms')
|
|
})
|
|
|
|
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, ['grep', '-E', 'needle\\s+line$', 'w.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.some((l) => l.includes('needle line')))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['grep', '-G', 'needle', 'w.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.some((l) => l.includes('needle line')))
|
|
|
|
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')
|
|
|
|
lines.length = 0
|
|
errs.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['sort', '-M', 'ord.txt'])
|
|
t.is(ctx.exitCode, 1)
|
|
t.ok(errs.length > 0)
|
|
|
|
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('tier-1 shuf deterministic seed option', async (t) => {
|
|
const dir = testCorestoreDir('shuf-seed')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pshufseed'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/shuf', b4a.from(await readBuiltBin('shuf')))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = { log: (s) => lines.push(String(s)), error() {} }
|
|
await ctx.vfs.writeFile('s.txt', b4a.from('a\nb\nc\nd\n'))
|
|
ctx.vfs.env.BARE_OS_SHUF_SEED = 'seed-1'
|
|
await runBinCommand(ctx, ['shuf', 's.txt'])
|
|
const first = lines.join('\n')
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['shuf', 's.txt'])
|
|
const second = lines.join('\n')
|
|
t.is(first, second)
|
|
ctx.vfs.env.BARE_OS_SHUF_SEED = 'seed-2'
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['shuf', 's.txt'])
|
|
t.unlike(lines.join('\n'), first)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 env -S and --env-file under Bare runtime', async (t) => {
|
|
const dir = testCorestoreDir('env-dashs')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('penvds'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/env', b4a.from(await readBuiltBin('env')))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = { log: (s) => lines.push(String(s)), error: (s) => lines.push(String(s)) }
|
|
ctx.vfs.env.BARE_OS_ENV_DASH_S = '1'
|
|
|
|
await runBinCommand(ctx, ['env', '-S', 'A=1 B=2'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.includes('A=1'))
|
|
t.ok(lines.includes('B=2'))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await ctx.vfs.writeFile(
|
|
'/home/user/.env.test',
|
|
b4a.from('X=left\nY=right\n', 'utf8')
|
|
)
|
|
await runBinCommand(ctx, ['env', '--env-file', '/home/user/.env.test'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.includes('X=left'))
|
|
t.ok(lines.includes('Y=right'))
|
|
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('vfs union write deny rejects chmod and symlink targets', async (t) => {
|
|
const dir = testCorestoreDir('union-deny')
|
|
const store = new Corestore(dir)
|
|
const sys = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pu'))
|
|
await sys.ready()
|
|
await personal.ready()
|
|
const shellEnv = {
|
|
HOME: '/home/guest',
|
|
PWD: '/home/guest',
|
|
PATH: '/bin',
|
|
USER: 'guest',
|
|
UID: '0',
|
|
GID: '0'
|
|
}
|
|
const vfs = createVfs(sys, personal, shellEnv, null, {
|
|
bareOsIpc: createBareOsIpc(),
|
|
unionReadPrefixes: ['/etc'],
|
|
unionWriteDenyPrefixes: ['/etc']
|
|
})
|
|
let w = false
|
|
try {
|
|
await vfs.writeFile('/etc/union-blocked', b4a.from('x'))
|
|
} catch (e) {
|
|
w = true
|
|
t.ok(String(e.message).includes('union write denied'))
|
|
}
|
|
t.ok(w)
|
|
let c = false
|
|
try {
|
|
await vfs.chmod('/etc/passwd', 0o644)
|
|
} catch (e) {
|
|
c = true
|
|
t.ok(String(e.message).includes('union write denied'))
|
|
}
|
|
t.ok(c)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs symlink relative and absolute targets under personal', async (t) => {
|
|
const dir = testCorestoreDir('symlink-personal')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('slp'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const ctx = testCtx(drive, personal)
|
|
await ctx.vfs.writeFile('~/target.txt', b4a.from('hello'))
|
|
await ctx.vfs.symlink('target.txt', '~/via_rel.txt')
|
|
t.is(await ctx.vfs.readlink('~/via_rel.txt'), 'target.txt')
|
|
const st = await ctx.vfs.lstat('~/via_rel.txt')
|
|
t.is(st.type, 'symlink')
|
|
t.is(b4a.toString(await ctx.vfs.readFile('~/via_rel.txt'), 'utf8'), 'hello')
|
|
|
|
await ctx.vfs.symlink('/home/user/target.txt', '~/via_abs.txt')
|
|
t.is(await ctx.vfs.readlink('~/via_abs.txt'), '/home/user/target.txt')
|
|
t.is(b4a.toString(await ctx.vfs.readFile('~/via_abs.txt'), 'utf8'), 'hello')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('vfs symlink on system drive /var follows within same drive', async (t) => {
|
|
const dir = testCorestoreDir('symlink-system-var')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('slsv'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const dataPath = '/var/bare-os-symlink-test/data.txt'
|
|
const aliasPath = '/var/bare-os-symlink-test/alias.txt'
|
|
await drive.put(dataPath, b4a.from('sysblob'))
|
|
await drive.symlink(aliasPath, dataPath, { metadata: {} })
|
|
const ctx = testCtx(drive, personal)
|
|
t.is(await ctx.vfs.readlink(aliasPath), dataPath)
|
|
t.is(b4a.toString(await ctx.vfs.readFile(aliasPath), 'utf8'), 'sysblob')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('HyperDHT local testnet: bootstrap node and ephemeral peer fullyBootstrapped', async (t) => {
|
|
const createTestnet = require('hyperdht/testnet.js')
|
|
const DHT = require('hyperdht')
|
|
const testnet = await createTestnet(1)
|
|
try {
|
|
t.ok(testnet.bootstrap.length >= 1, 'testnet exposes bootstrap address')
|
|
const addr = testnet.nodes[0].address()
|
|
t.ok(addr && addr.port > 0, 'bootstrap listens on a UDP port')
|
|
const client = new DHT({
|
|
ephemeral: true,
|
|
bootstrap: testnet.bootstrap,
|
|
host: '127.0.0.1'
|
|
})
|
|
try {
|
|
await client.fullyBootstrapped()
|
|
t.pass('ephemeral HyperDHT node reaches fullyBootstrapped against local testnet')
|
|
} finally {
|
|
await client.destroy()
|
|
}
|
|
} finally {
|
|
await testnet.destroy()
|
|
}
|
|
})
|
|
|
|
test('tier-1 mv renames file in home', async (t) => {
|
|
const dir = testCorestoreDir('mv-home')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('mvhm'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/mv', b4a.from(await readBuiltBin('mv')))
|
|
const ctx = testCtx(drive, personal)
|
|
await ctx.vfs.writeFile('a.txt', b4a.from('ok'))
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['mv', 'a.txt', 'b.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(b4a.toString(await ctx.vfs.readFile('b.txt'), 'utf8'), 'ok')
|
|
t.ok(
|
|
(await ctx.vfs.readFile('a.txt')) == null,
|
|
'mv removed source path in home'
|
|
)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 cmp silent and kernel-boot-diff line set', async (t) => {
|
|
const dir = testCorestoreDir('cmp-kbd')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('cmpkbd'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const cmpSrc = await readFile(
|
|
path.join(__dirname, '../../kernel/bin/cmp'),
|
|
'utf8'
|
|
)
|
|
const kbdSrc = await readFile(
|
|
path.join(__dirname, '../../kernel/bin/kernel-boot-diff'),
|
|
'utf8'
|
|
)
|
|
await drive.put('/bin/cmp', b4a.from(cmpSrc))
|
|
await drive.put('/bin/kernel-boot-diff', b4a.from(kbdSrc))
|
|
const ctx = testCtx(drive, personal)
|
|
await ctx.vfs.writeFile('same1.txt', b4a.from('x'))
|
|
await ctx.vfs.writeFile('same2.txt', b4a.from('x'))
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['cmp', '-s', 'same1.txt', 'same2.txt'])
|
|
t.is(ctx.exitCode, 0, 'cmp -s identical')
|
|
await ctx.vfs.writeFile('d2.txt', b4a.from('y'))
|
|
ctx.exitCode = 0
|
|
const errs = []
|
|
ctx.console = {
|
|
log() {},
|
|
error(s) {
|
|
errs.push(String(s))
|
|
}
|
|
}
|
|
await runBinCommand(ctx, ['cmp', '-s', 'same1.txt', 'd2.txt'])
|
|
t.is(ctx.exitCode, 1, 'cmp -s differs')
|
|
await ctx.vfs.writeFile('boot-a.txt', b4a.from('line1\nline2\n'))
|
|
await ctx.vfs.writeFile('boot-b.txt', b4a.from('line1\nline3\n'))
|
|
const lines = []
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error(s) {
|
|
errs.push(String(s))
|
|
}
|
|
}
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['kernel-boot-diff', 'boot-a.txt', 'boot-b.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.some((l) => l.startsWith('+ ') && l.includes('line3')))
|
|
t.ok(lines.some((l) => l.startsWith('- ') && l.includes('line2')))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 diff -u and patch apply stdin hunk', async (t) => {
|
|
const dir = testCorestoreDir('diffpatch')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('dp'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/diff', b4a.from(await readBuiltBin('diff')))
|
|
await drive.put('/bin/patch', b4a.from(await readBuiltBin('patch')))
|
|
const ctx = testCtx(drive, personal)
|
|
await ctx.vfs.writeFile('a.txt', b4a.from('alpha\n'))
|
|
await ctx.vfs.writeFile('b.txt', b4a.from('beta\n'))
|
|
const out = []
|
|
ctx.console = {
|
|
log(s) {
|
|
out.push(String(s))
|
|
},
|
|
error() {}
|
|
}
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['diff', '-u', 'a.txt', 'b.txt'])
|
|
t.is(ctx.exitCode, 1)
|
|
t.ok(out.join('\n').includes('+++ b.txt'))
|
|
await ctx.vfs.writeFile('fix.txt', b4a.from('alpha\n'))
|
|
ctx.shellStdin = out.join('\n') + '\n'
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['patch', '-p0', 'fix.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(ctx.b4a.toString(await ctx.vfs.readFile('fix.txt')), 'beta\n')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('bareInitdReadinessSnapshot exposes unit table', async (t) => {
|
|
const s = bareInitdReadinessSnapshot()
|
|
t.is(s.schema, 2)
|
|
t.ok(Array.isArray(s.units))
|
|
t.ok(s.supervisionTelemetry && s.supervisionTelemetry.schema === 1)
|
|
})
|
|
|
|
test('tier-1 test -r -w -x -s predicates', async (t) => {
|
|
const dir = testCorestoreDir('test-pred')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('tpred'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/test', b4a.from(await readBuiltBin('test')))
|
|
const ctx = testCtx(drive, personal)
|
|
await ctx.vfs.writeFile('ro.txt', b4a.from('data'))
|
|
await ctx.vfs.chmod('ro.txt', 0o400)
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['test', '-r', 'ro.txt'])
|
|
t.is(ctx.exitCode, 0, '-r read-only file')
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['test', '-w', 'ro.txt'])
|
|
t.is(ctx.exitCode, 1, '-w false when not writable')
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['test', '-s', 'ro.txt'])
|
|
t.is(ctx.exitCode, 0, '-s nonempty file')
|
|
await ctx.vfs.writeFile('empty.txt', b4a.from(''))
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['test', '-s', 'empty.txt'])
|
|
t.is(ctx.exitCode, 1, '-s empty file')
|
|
await ctx.vfs.writeFile('run.sh', b4a.from('#!'))
|
|
await ctx.vfs.chmod('run.sh', 0o700)
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['test', '-x', 'run.sh'])
|
|
t.is(ctx.exitCode, 0, '-x executable')
|
|
await ctx.vfs.chmod('run.sh', 0o600)
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['test', '-x', 'run.sh'])
|
|
t.is(ctx.exitCode, 1, '-x false without exec bit')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 find -perm filters by mode', async (t) => {
|
|
const dir = testCorestoreDir('find-perm')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pfp'))
|
|
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.writeFile('rw.txt', b4a.from(''))
|
|
await ctx.vfs.chmod('rw.txt', 0o644)
|
|
await ctx.vfs.writeFile('x.txt', b4a.from(''))
|
|
await ctx.vfs.chmod('x.txt', 0o600)
|
|
await runBinCommand(ctx, ['find', '.', '-perm', '600', '-type', 'f'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(
|
|
lines.some((p) => String(p).replace(/\\/g, '/').endsWith('/x.txt'))
|
|
)
|
|
t.ok(
|
|
!lines.some((p) => String(p).replace(/\\/g, '/').endsWith('/rw.txt'))
|
|
)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 find -mtime -1 matches recent files', async (t) => {
|
|
const dir = testCorestoreDir('find-mtime')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('fmt'))
|
|
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.writeFile('recent.txt', b4a.from('x'))
|
|
await runBinCommand(ctx, ['find', '.', '-type', 'f', '-mtime', '-1'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(
|
|
lines.some((p) => String(p).replace(/\\/g, '/').endsWith('/recent.txt'))
|
|
)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 find -xdev skips other volume under /', async (t) => {
|
|
const dir = testCorestoreDir('find-xdev')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('fxd'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/onlyroot.txt', b4a.from('sys'))
|
|
await drive.put('/bin/find', b4a.from(await readBuiltBin('find')))
|
|
const ctx = testCtx(drive, personal, { PWD: '/', HOME: '/home/user' })
|
|
ctx.exitCode = 0
|
|
const lines = []
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error() {}
|
|
}
|
|
await ctx.vfs.writeFile('/home/user/only_in_home.txt', b4a.from('p'))
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['find', '/', '-xdev', '-name', 'only_in_home.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines.length, 0, '-xdev from / must not descend into /home')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['find', '/', '-name', 'only_in_home.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(
|
|
lines.some((p) =>
|
|
String(p).replace(/\\/g, '/').endsWith('/only_in_home.txt')
|
|
),
|
|
'without -xdev, file under /home is visible from /'
|
|
)
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['find', '/', '-xdev', '-name', 'onlyroot.txt'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(
|
|
lines.some((p) =>
|
|
String(p).replace(/\\/g, '/').endsWith('/onlyroot.txt')
|
|
),
|
|
'-xdev still searches system tree under /'
|
|
)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tar-cli hard link extract copies unless BARE_OS_VFS_STRICT_HARDLINK', async (t) => {
|
|
const dir = testCorestoreDir('tar-hardlink')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('tarhl'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
const body = b4a.from('hi', 'utf8')
|
|
const hdrA = encodeUstarHeader({
|
|
name: 'a.txt',
|
|
size: body.length,
|
|
mode: 0o644,
|
|
mtime: 1,
|
|
typeflag: '0'
|
|
})
|
|
const padA = new Uint8Array((TAR_BLK - (body.length % TAR_BLK)) % TAR_BLK)
|
|
const hdrB = encodeUstarHeader({
|
|
name: 'b.txt',
|
|
size: 0,
|
|
mode: 0o644,
|
|
mtime: 1,
|
|
typeflag: '1',
|
|
linkname: 'a.txt'
|
|
})
|
|
const archive = concatTarParts([
|
|
hdrA,
|
|
body,
|
|
padA,
|
|
hdrB,
|
|
new Uint8Array(TAR_BLK * 2)
|
|
])
|
|
|
|
const errs = []
|
|
const ctx = testCtx(drive, personal)
|
|
await ctx.vfs.writeFile('hl.tar', b4a.from(archive))
|
|
ctx.exitCode = 0
|
|
ctx.console = {
|
|
log() {},
|
|
error(s) {
|
|
errs.push(String(s))
|
|
}
|
|
}
|
|
await runTarCli(ctx, ['tar', '-xf', 'hl.tar'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(b4a.toString(await ctx.vfs.readFile('b.txt'), 'utf8'), 'hi')
|
|
|
|
await ctx.vfs.rm('a.txt', { recursive: true, force: true })
|
|
await ctx.vfs.rm('b.txt', { recursive: true, force: true })
|
|
|
|
errs.length = 0
|
|
ctx.exitCode = 0
|
|
await ctx.vfs.writeFile('hl.tar', b4a.from(archive))
|
|
const ctxStrict = testCtx(drive, personal, {
|
|
BARE_OS_VFS_STRICT_HARDLINK: '1'
|
|
})
|
|
await ctxStrict.vfs.writeFile('hl.tar', b4a.from(archive))
|
|
ctxStrict.exitCode = 0
|
|
ctxStrict.console = {
|
|
log() {},
|
|
error(s) {
|
|
errs.push(String(s))
|
|
}
|
|
}
|
|
await runTarCli(ctxStrict, ['tar', '-xf', 'hl.tar'])
|
|
t.is(ctxStrict.exitCode, 1)
|
|
t.ok(
|
|
errs.some((e) => /hard link entries not supported/i.test(e)),
|
|
'strict mode rejects hard link members'
|
|
)
|
|
|
|
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 = function (argv, ro) {
|
|
return runBinCommand(this, argv, ro)
|
|
}
|
|
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', '_SC_NPROCESSORS_ONLN'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines.pop(), '4')
|
|
|
|
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
|
|
ctx.bareOsPathconf = (p, n) => {
|
|
void p
|
|
if (n === '_PC_NAME_MAX') return 255
|
|
throw new Error('getconf pathconf test: unknown name')
|
|
}
|
|
await runBinCommand(ctx, ['getconf', '_PC_NAME_MAX', '/home/user'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(lines.pop(), '255')
|
|
|
|
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 = 'a\nb\n'
|
|
await runBinCommand(ctx, ['xargs', '-P2', '-n1', 'echolog'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(new Set(lines).size, 2)
|
|
t.ok(lines.includes('a'))
|
|
t.ok(lines.includes('b'))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
ctx.vfs.env.BARE_OS_XARGS_MAX_PROCS = '2'
|
|
ctx.shellStdin = 'x\ny\n'
|
|
await runBinCommand(ctx, ['xargs', '-P8', '-n1', 'echolog'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.some((s) => String(s).includes('exceeds cap 2')))
|
|
|
|
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-1 sh shebang strip and symlink script path', async (t) => {
|
|
const dir = testCorestoreDir('shshe')
|
|
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/sh', b4a.from(await readBuiltBin('sh')))
|
|
await drive.put(
|
|
'/bin/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.execLine = (line, opts) => execShellLine(ctx, line)
|
|
ctx.exitCode = 0
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error(s) {
|
|
lines.push(String(s))
|
|
}
|
|
}
|
|
await ctx.vfs.chdir('/home/user')
|
|
await ctx.vfs.writeFile(
|
|
'real.sh',
|
|
b4a.from('#!/bin/sh\necho shebang-ok\n')
|
|
)
|
|
await ctx.vfs.symlink('real.sh', 'via.sh')
|
|
await runBinCommand(ctx, ['sh', 'via.sh'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.some((l) => String(l).includes('shebang-ok')))
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 sh script mode handles multiline compound blocks', async (t) => {
|
|
const dir = testCorestoreDir('shmulti')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pshm'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/sh', b4a.from(await readBuiltBin('sh')))
|
|
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
|
|
}
|
|
`)
|
|
)
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.runBinCommand = function (argv, ro) {
|
|
return runBinCommand(this, argv, ro)
|
|
}
|
|
ctx.execLine = (line) => execShellLine(ctx, line)
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error(s) {
|
|
lines.push('e:' + String(s))
|
|
}
|
|
}
|
|
ctx.exitCode = 0
|
|
await ctx.vfs.chdir('/home/user')
|
|
await ctx.vfs.writeFile(
|
|
'm.sh',
|
|
b4a.from(
|
|
[
|
|
'#!/bin/sh',
|
|
'n=0',
|
|
'for x in a b',
|
|
'do',
|
|
' if true',
|
|
' then',
|
|
' n=$((n+1))',
|
|
' fi',
|
|
'done',
|
|
'echo $n'
|
|
].join('\n')
|
|
)
|
|
)
|
|
await runBinCommand(ctx, ['sh', 'm.sh'])
|
|
t.ok(typeof ctx.exitCode === 'number')
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 direct .sh command runs via /bin/sh', async (t) => {
|
|
const dir = testCorestoreDir('shdirect')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pshd'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/sh', b4a.from(await readBuiltBin('sh')))
|
|
await drive.put(
|
|
'/bin/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.execLine = (line) => execShellLine(ctx, line)
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error(s) {
|
|
lines.push('e:' + String(s))
|
|
}
|
|
}
|
|
ctx.exitCode = 0
|
|
await ctx.vfs.chdir('/home/user')
|
|
await ctx.vfs.writeFile('./test.sh', b4a.from('#!/bin/sh\necho via-sh\n'))
|
|
await runBinCommand(ctx, ['./test.sh'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.includes('via-sh'))
|
|
t.ok(!lines.some((l) => l.startsWith('e:Unexpected string')))
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('tier-1 sh -c executes command and sets positional args', async (t) => {
|
|
const dir = testCorestoreDir('shdashc')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pshc'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/sh', b4a.from(await readBuiltBin('sh')))
|
|
await drive.put(
|
|
'/bin/echo',
|
|
b4a.from(`async function run(ctx, argv) {
|
|
ctx.console.log(argv.slice(1).join(' '))
|
|
}
|
|
`)
|
|
)
|
|
await drive.put(
|
|
'/bin/false',
|
|
b4a.from(`async function run(ctx) {
|
|
ctx.exitCode = 1
|
|
}
|
|
`)
|
|
)
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.runBinCommand = function (argv, ro) {
|
|
return runBinCommand(this, argv, ro)
|
|
}
|
|
ctx.execLine = (line) => execShellLine(ctx, line)
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error(s) {
|
|
lines.push('e:' + String(s))
|
|
}
|
|
}
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['sh', '-c', 'echo $0 $1 $2 $#', 'myshell', 'aa', 'bb'])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(!lines.some((l) => l.startsWith('e:')))
|
|
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['sh', '-c', 'false'])
|
|
t.is(ctx.exitCode, 1)
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, [
|
|
'sh',
|
|
'-c',
|
|
'n=0; if true; then n=$((n+1)); fi; echo $n'
|
|
])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(!lines.some((l) => l.startsWith('e:')))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['sh', '-c', 'false; echo $?'])
|
|
t.ok(lines.some((l) => String(l).trim() === '1'), lines.join('|'))
|
|
t.is(ctx.exitCode, 0)
|
|
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('execShellLine supports shell functions, export NAME, and until by default', async (t) => {
|
|
const dir = testCorestoreDir('shfunc')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pshf'))
|
|
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
|
|
}
|
|
`)
|
|
)
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log(s) {
|
|
lines.push(String(s))
|
|
},
|
|
error(s) {
|
|
lines.push('e:' + String(s))
|
|
}
|
|
}
|
|
ctx.exitCode = 0
|
|
|
|
await execShellLine(ctx, 'hello() { echo $1; }; hello world')
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.includes('world'))
|
|
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'export DEMO')
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(ctx.vfs.env.DEMO, '')
|
|
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'export BAD-NAME=1')
|
|
t.is(ctx.exitCode, 1)
|
|
|
|
ctx.exitCode = 0
|
|
await execShellLine(ctx, 'until false; do break; done')
|
|
t.is(ctx.exitCode, 0)
|
|
|
|
ctx.exitCode = 0
|
|
await execShellLine(
|
|
ctx,
|
|
'n=0; for x in a b; do if true; then n=$((n+1)); fi; done; echo $n'
|
|
)
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(lines.includes('2'))
|
|
|
|
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
|
|
ctx.httpFetch = async () =>
|
|
new Response('wfmt', {
|
|
status: 201,
|
|
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
|
})
|
|
await runBinCommand(ctx, [
|
|
'curl',
|
|
'-s',
|
|
'-w',
|
|
'%{response_code}|%{content_type}|%{method}|%{scheme}|%{host}|%{path}',
|
|
'-o',
|
|
'w.out',
|
|
'https://stub.example/api/v1?q=1'
|
|
])
|
|
t.is(ctx.exitCode, 0)
|
|
t.is(
|
|
lines[lines.length - 1],
|
|
'201|application/json; charset=utf-8|GET|https|stub.example|/api/v1?q=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, certPem?: string, keyPem?: 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')
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await ctx.vfs.writeFile(
|
|
'/home/user/client-cert.pem',
|
|
b4a.from('-----BEGIN CERTIFICATE-----\nCLIENT\n-----END CERTIFICATE-----\n')
|
|
)
|
|
await ctx.vfs.writeFile(
|
|
'/home/user/client-key.pem',
|
|
b4a.from('-----BEGIN PRIVATE KEY-----\nKEY\n-----END PRIVATE KEY-----\n')
|
|
)
|
|
let mtlsInit = null
|
|
ctx.httpFetch = async (_url, init) => {
|
|
mtlsInit = init && init.bareOsCurlTls
|
|
return new Response('mtls-ok', { status: 200 })
|
|
}
|
|
await runBinCommand(ctx, [
|
|
'curl',
|
|
'--cert',
|
|
'/home/user/client-cert.pem',
|
|
'--key',
|
|
'/home/user/client-key.pem',
|
|
'https://stub.example/mtls'
|
|
])
|
|
t.is(ctx.exitCode, 0)
|
|
t.ok(mtlsInit && String(mtlsInit.certPem || '').includes('CLIENT'))
|
|
t.ok(mtlsInit && String(mtlsInit.keyPem || '').includes('PRIVATE KEY'))
|
|
t.is(lines[0], 'mtls-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 runBinCommand(ctx, ['wget', '-N', 'https://stub.example/n-ts'])
|
|
t.is(ctx.exitCode, 2)
|
|
t.ok(lines.some((l) => l.includes('--timestamping')))
|
|
|
|
lines.length = 0
|
|
ctx.exitCode = 0
|
|
await runBinCommand(ctx, ['wget', '--timestamping', 'https://stub.example/n-ts2'])
|
|
t.is(ctx.exitCode, 2)
|
|
t.ok(lines.some((l) => l.includes('--timestamping')))
|
|
|
|
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('.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+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('verifyPathCapabilityEnvelope rejects invalid envelopes', async (t) => {
|
|
const { verifyPathCapabilityEnvelope } = await import(
|
|
'./lib/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/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/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/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/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/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/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/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/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/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/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/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))
|
|
})
|
|
|
|
async function readBuiltBin(name) {
|
|
const fs = await import('node:fs/promises')
|
|
const p = path.join(__dirname, '../../kernel/bin', name)
|
|
return fs.readFile(p, 'utf8')
|
|
}
|