| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529 |
- import { spawn, spawnSync } from 'node:child_process'
- import { existsSync } from 'node:fs'
- import { dirname } from 'node:path'
- import { describe, expect, it, vi } from 'vitest'
- import type { SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess'
- import {
- launchLinuxScope,
- prepareLinuxTerminalScope,
- probeLinuxRunner,
- probeLinuxScope,
- probeLinuxUserManager,
- } from '../src/linux-scope.ts'
- import { spawnRunnerInvocation } from '../src/runner-launch.ts'
- function spec(argv: string[]): SubprocessSpawnSpec {
- return {
- argv,
- cwd: process.cwd(),
- stdio: { stdin: 'ignore', stdout: { maxBytes: 1024 }, stderr: { maxBytes: 1024 } },
- graceMs: 100,
- env: { LITERAL_VALUE: '$HOME ${UNCHANGED}' },
- }
- }
- function asyncQuery(runSync: typeof spawnSync) {
- return async (command: string, args: readonly string[]) => {
- const result = runSync(command, [...args], { encoding: 'utf8', timeout: 5_000 })
- return {
- status: result.status,
- stdout: typeof result.stdout === 'string' ? result.stdout : '',
- stderr: typeof result.stderr === 'string' ? result.stderr : '',
- ...result.error === undefined ? {} : { error: result.error },
- }
- }
- }
- describe.skipIf(process.platform === 'win32')('Linux systemd scope adapter', () => {
- it('separates the live manager, stable scope, and ordinary-runner probes', () => {
- const secretName = 'DSH_SCOPE_TEST_TOKEN'
- const previousSecret = process.env[secretName]
- process.env[secretName] = 'secret'
- const calls: string[][] = []
- const environments: Array<NodeJS.ProcessEnv | undefined> = []
- const runSync = vi.fn((command: string, args: readonly string[], options?: { env?: NodeJS.ProcessEnv }) => {
- calls.push([command, ...args])
- environments.push(options?.env)
- return { status: 0, error: undefined }
- }) as unknown as typeof spawnSync
- const runnerInvocation: [string, ...string[]] = ['node-runtime', 'runner-entry.js']
- try {
- expect(probeLinuxUserManager({
- spawnSync: runSync,
- systemctl: 'systemctl',
- })).toBe(true)
- expect(probeLinuxRunner({
- spawnSync: runSync,
- runnerInvocation,
- })).toBe(true)
- expect(probeLinuxScope({
- spawnSync: runSync,
- systemdRun: 'systemd-run',
- systemctl: 'systemctl',
- })).toBe(true)
- expect(calls[0]).toEqual(['systemctl', '--user', 'show-environment'])
- expect(calls[1]).toEqual([...runnerInvocation, '--mode', 'probe-node'])
- expect(calls[2]).toContain('--expand-environment=no')
- expect(calls[2]).not.toContain('--pipe')
- expect(calls[2]).not.toContain('--wait')
- const unitArg = calls[2]?.find(arg => arg.startsWith('--unit='))
- if (unitArg === undefined) throw new Error('scope probe did not publish its unit')
- const separator = calls[2]?.indexOf('--') ?? -1
- expect(calls[2]?.slice(separator + 1)).toEqual([
- 'systemctl',
- '--user',
- 'show',
- `${unitArg.slice('--unit='.length)}.scope`,
- '--property=ActiveState',
- '--value',
- ])
- expect(environments[0]?.LC_ALL).toBe('C')
- for (const environment of environments) expect(environment).not.toHaveProperty(secretName)
- } finally {
- if (previousSecret === undefined) Reflect.deleteProperty(process.env, secretName)
- else process.env[secretName] = previousSecret
- }
- const oldSystemd = vi.fn(() => ({ status: 1, error: undefined })) as unknown as typeof spawnSync
- expect(probeLinuxScope({ spawnSync: oldSystemd })).toBe(false)
- expect(probeLinuxScope({
- spawnSync: vi.fn(() => ({ status: 0, error: new Error('scope failed') })) as unknown as typeof spawnSync,
- })).toBe(false)
- const failedRunner = vi.fn(() => ({ status: 1, error: undefined })) as unknown as typeof spawnSync
- expect(probeLinuxRunner({
- spawnSync: failedRunner,
- runnerInvocation: ['node-runtime', 'runner-entry.js'],
- })).toBe(false)
- expect(failedRunner).toHaveBeenCalledOnce()
- expect(probeLinuxRunner({
- spawnSync: vi.fn(() => ({ status: 0, error: new Error('runner failed') })) as unknown as typeof spawnSync,
- runnerInvocation: ['node-runtime', 'runner-entry.js'],
- })).toBe(false)
- const managerError = new Error('missing user manager')
- expect(probeLinuxUserManager({
- spawnSync: vi.fn(() => ({ error: managerError })) as unknown as typeof spawnSync,
- })).toBe(false)
- expect(probeLinuxUserManager({
- spawnSync: vi.fn(() => ({ status: 1, error: undefined })) as unknown as typeof spawnSync,
- })).toBe(false)
- })
- it('removes private runner files when systemd-run throws synchronously', () => {
- const failure = new Error('systemd-run threw')
- let requestPath: string | undefined
- const run = vi.fn((_command: string, args: readonly string[]) => {
- const requestIndex = args.indexOf('--request')
- requestPath = args[requestIndex + 1]
- throw failure
- }) as unknown as typeof spawn
- expect(() => launchLinuxScope(spec([process.execPath, '-e', '']), {
- spawn: run,
- runnerInvocation: spawnRunnerInvocation(),
- })).toThrow(failure)
- expect(requestPath).toBeDefined()
- expect(existsSync(dirname(requestPath as string))).toBe(false)
- })
- it('wraps terminal argv literally and binds signalling and observation to the same scope', async () => {
- const signalCalls: Array<[string, readonly string[]]> = []
- const queryCalls: Array<[string, readonly string[]]> = []
- const runSync = vi.fn((command: string, args: readonly string[]) => {
- signalCalls.push([command, args])
- return { status: 0, stdout: '', stderr: '', error: undefined }
- }) as unknown as typeof spawnSync
- const query = vi.fn(async (command: string, args: readonly string[]) => {
- queryCalls.push([command, args])
- return { status: 0, stdout: 'inactive\n', stderr: '' }
- })
- const argv = ['/bin/bash', '-c', 'printf "%s" "$HOME"']
- const launch = prepareLinuxTerminalScope(argv, {
- spawnSync: runSync,
- systemdRun: '/usr/bin/systemd-run',
- systemctl: '/usr/bin/systemctl',
- systemctlQuery: query,
- })
- const unitArg = launch.args.find(arg => arg.startsWith('--unit='))
- if (unitArg === undefined) throw new Error('terminal scope did not publish its unit')
- const unit = `${unitArg.slice('--unit='.length)}.scope`
- expect(launch.command).toBe('/usr/bin/systemd-run')
- expect(launch.args.slice(0, -argv.length)).toEqual([
- '--user',
- '--scope',
- '--quiet',
- '--collect',
- '--expand-environment=no',
- unitArg,
- '--',
- ])
- expect(launch.args.slice(-argv.length)).toEqual(argv)
- const owner = launch.bindOwner(() => false)
- owner.signal('SIGTERM')
- owner.signal('SIGKILL')
- await owner.waitForExit()
- expect(signalCalls).toEqual([
- [
- '/usr/bin/systemctl',
- ['--user', 'kill', '--kill-whom=all', '--signal=SIGTERM', unit],
- ],
- [
- '/usr/bin/systemctl',
- ['--user', 'kill', '--kill-whom=all', '--signal=SIGKILL', unit],
- ],
- ])
- expect(queryCalls).toEqual([[
- '/usr/bin/systemctl',
- ['--user', 'show', unit, '--property=ActiveState', '--value'],
- ]])
- })
- it('keeps user argv out of systemd-run and reports the direct target outcome', async () => {
- let wrapper: ReturnType<typeof spawn> | undefined
- let systemdArgs: readonly string[] = []
- const run = vi.fn((_command: string, args: readonly string[], options: Parameters<typeof spawn>[2]) => {
- systemdArgs = args
- const separator = args.indexOf('--')
- const command = args[separator + 1] as string
- wrapper = spawn(command, args.slice(separator + 2), options)
- return wrapper
- }) as unknown as typeof spawn
- const runSyncMock = vi.fn((command: string, args: readonly string[]) => {
- if (command === 'systemctl' && args[1] === 'show') {
- const active = wrapper?.exitCode === null && wrapper.signalCode === null
- return { status: 0, stdout: active ? 'active\n' : 'inactive\n', stderr: '', error: undefined }
- }
- return { status: 0, stdout: '', stderr: '', error: undefined }
- })
- const runSync = runSyncMock as unknown as typeof spawnSync
- const launch = launchLinuxScope(spec([process.execPath, '-e', 'process.exit(9)', 'literal $VALUE']), {
- spawn: run,
- spawnSync: runSync,
- systemctlQuery: asyncQuery(runSync),
- runnerInvocation: spawnRunnerInvocation(),
- })
- await expect(launch.direct).resolves.toEqual({ exitCode: 9, signal: null })
- await expect(launch.owner.waitForExit()).resolves.toBeUndefined()
- await expect(launch.owner.waitForExit()).resolves.toBeUndefined()
- const callsBeforeStaleSignal = runSyncMock.mock.calls.length
- launch.owner.signal('SIGKILL')
- expect(runSyncMock).toHaveBeenCalledTimes(callsBeforeStaleSignal)
- expect(systemdArgs).toContain('--expand-environment=no')
- expect(systemdArgs).not.toContain('--pipe')
- expect(systemdArgs).not.toContain('--wait')
- expect(systemdArgs).not.toContain('literal $VALUE')
- })
- it('uses a scope KILL after the owner proves the range empty', async () => {
- let wrapper: ReturnType<typeof spawn> | undefined
- const run = vi.fn((_command: string, args: readonly string[], options: Parameters<typeof spawn>[2]) => {
- const separator = args.indexOf('--')
- const command = args[separator + 1] as string
- wrapper = spawn(command, args.slice(separator + 2), { ...options, detached: true })
- return wrapper
- }) as unknown as typeof spawn
- const runSync = vi.fn((command: string, args: readonly string[]) => {
- if (command === 'systemctl' && args[1] === 'kill') {
- if (args.includes('--signal=SIGTERM')) {
- return { status: 1, stdout: '', stderr: 'Unit could not be found', error: undefined }
- }
- if (wrapper?.pid !== undefined) process.kill(-wrapper.pid, 'SIGKILL')
- return {
- status: 1,
- stdout: '',
- stderr: 'Failed to send signal SIGKILL to auxiliary processes: Invalid argument',
- error: undefined,
- }
- }
- if (command === 'systemctl' && args[1] === 'show') {
- const active = wrapper?.exitCode === null && wrapper.signalCode === null
- return { status: 0, stdout: active ? 'active\n' : 'inactive\n', stderr: '', error: undefined }
- }
- return { status: 0, stdout: '', stderr: '', error: undefined }
- }) as unknown as typeof spawnSync
- const launch = launchLinuxScope(spec([process.execPath, '-e', 'setInterval(() => {}, 1000)']), {
- spawn: run,
- spawnSync: runSync,
- systemctlQuery: asyncQuery(runSync),
- runnerInvocation: spawnRunnerInvocation(),
- })
- launch.owner.signal('SIGTERM')
- launch.owner.signal('SIGKILL')
- await expect(launch.direct).resolves.toEqual({ exitCode: null, signal: 'SIGKILL' })
- await expect(launch.owner.waitForExit()).resolves.toBeUndefined()
- })
- it('rejects wait when the selected native owner becomes unreadable', async () => {
- const run = vi.fn((_command: string, args: readonly string[], options: Parameters<typeof spawn>[2]) => {
- const separator = args.indexOf('--')
- return spawn(args[separator + 1] as string, args.slice(separator + 2), options)
- }) as unknown as typeof spawn
- const runSync = vi.fn(() => ({
- status: 1,
- stdout: '',
- stderr: 'Failed to connect to bus: No such file or directory',
- error: undefined,
- })) as unknown as typeof spawnSync
- const launch = launchLinuxScope(spec([process.execPath, '-e', 'process.exit(0)']), {
- spawn: run,
- spawnSync: runSync,
- systemctlQuery: asyncQuery(runSync),
- runnerInvocation: spawnRunnerInvocation(),
- })
- await expect(launch.direct).resolves.toEqual({ exitCode: 0, signal: null })
- await expect(launch.owner.waitForExit()).rejects.toThrow('Failed to connect to bus')
- })
- it('propagates systemctl execution failures and unknown active states', async () => {
- const run = vi.fn((_command: string, args: readonly string[], options: Parameters<typeof spawn>[2]) => {
- const separator = args.indexOf('--')
- return spawn(args[separator + 1] as string, args.slice(separator + 2), options)
- }) as unknown as typeof spawn
- const failure = new Error('systemctl execution failed')
- const failedRead = launchLinuxScope(spec([process.execPath, '-e', 'process.exit(0)']), {
- spawn: run,
- spawnSync: vi.fn(() => ({ error: failure })) as unknown as typeof spawnSync,
- systemctlQuery: async () => ({ status: null, stdout: '', stderr: '', error: failure }),
- runnerInvocation: spawnRunnerInvocation(),
- })
- await expect(failedRead.owner.waitForExit()).rejects.toBe(failure)
- await expect(failedRead.direct).resolves.toEqual({ exitCode: 0, signal: null })
- const unknownState = launchLinuxScope(spec([process.execPath, '-e', 'process.exit(0)']), {
- spawn: run,
- spawnSync: vi.fn(() => ({ status: 0, stdout: 'reloading\n', stderr: '', error: undefined })) as unknown as typeof spawnSync,
- systemctlQuery: async () => ({ status: 0, stdout: 'reloading\n', stderr: '' }),
- runnerInvocation: spawnRunnerInvocation(),
- })
- await expect(unknownState.owner.waitForExit()).rejects.toThrow('unknown ActiveState')
- await expect(unknownState.direct).resolves.toEqual({ exitCode: 0, signal: null })
- const blankFailure = launchLinuxScope(spec([process.execPath, '-e', 'process.exit(0)']), {
- spawn: run,
- spawnSync: vi.fn(() => ({ status: 1, stdout: '', stderr: '', error: undefined })) as unknown as typeof spawnSync,
- systemctlQuery: async () => ({ status: 1, stdout: '', stderr: '' }),
- runnerInvocation: spawnRunnerInvocation(),
- })
- await expect(blankFailure.owner.waitForExit()).rejects.toThrow('exit 1')
- await expect(blankFailure.direct).resolves.toEqual({ exitCode: 0, signal: null })
- })
- it.each(['activating', 'deactivating', 'failed'])(
- 'recognizes the %s scope state',
- async (initialState) => {
- let wrapper: ReturnType<typeof spawn> | undefined
- let reads = 0
- const run = vi.fn((_command: string, args: readonly string[], options: Parameters<typeof spawn>[2]) => {
- const separator = args.indexOf('--')
- wrapper = spawn(args[separator + 1] as string, args.slice(separator + 2), options)
- return wrapper
- }) as unknown as typeof spawn
- const runSync = vi.fn(() => {
- reads += 1
- return {
- status: 0,
- stdout: reads === 1 ? `${initialState}\n` : 'inactive\n',
- stderr: '',
- error: undefined,
- }
- }) as unknown as typeof spawnSync
- const launch = launchLinuxScope(spec([process.execPath, '-e', 'process.exit(0)']), {
- spawn: run,
- spawnSync: runSync,
- systemctlQuery: asyncQuery(runSync),
- runnerInvocation: spawnRunnerInvocation(),
- })
- await expect(launch.owner.waitForExit()).resolves.toBeUndefined()
- await expect(launch.direct).resolves.toEqual({ exitCode: 0, signal: null })
- },
- )
- it('uses runner liveness when systemd has already forgotten the scope', async () => {
- const run = vi.fn((_command: string, args: readonly string[], options: Parameters<typeof spawn>[2]) => {
- const separator = args.indexOf('--')
- return spawn(args[separator + 1] as string, args.slice(separator + 2), options)
- }) as unknown as typeof spawn
- const runSyncMock = vi.fn(() => ({
- status: 1,
- stdout: '',
- stderr: 'Unit could not be found',
- error: undefined,
- }))
- const runSync = runSyncMock as unknown as typeof spawnSync
- const launch = launchLinuxScope(spec([process.execPath, '-e', 'setTimeout(() => {}, 40)']), {
- spawn: run,
- spawnSync: runSync,
- systemctlQuery: asyncQuery(runSync),
- runnerInvocation: spawnRunnerInvocation(),
- })
- await expect(launch.owner.waitForExit()).resolves.toBeUndefined()
- await expect(launch.direct).resolves.toEqual({ exitCode: 0, signal: null })
- expect(runSyncMock.mock.calls.length).toBeGreaterThan(1)
- })
- it('settles a missing scope immediately when the wrapper never started', async () => {
- const launch = launchLinuxScope(spec([process.execPath, '-e', '']), {
- systemdRun: `missing-systemd-run-${String(process.pid)}-${String(Date.now())}`,
- systemctlQuery: async () => ({
- status: 1,
- stdout: '',
- stderr: 'Unit dsh-subprocess-missing.scope could not be found',
- }),
- runnerInvocation: spawnRunnerInvocation(),
- })
- expect(launch.pid).toBeUndefined()
- await expect(launch.direct).rejects.toThrow('runner failed to start')
- await expect(launch.owner.waitForExit()).resolves.toBeUndefined()
- })
- it('does not fabricate a direct outcome after a non-forced scope signal', async () => {
- let wrapper: ReturnType<typeof spawn> | undefined
- const run = vi.fn((_command: string, args: readonly string[], options: Parameters<typeof spawn>[2]) => {
- const separator = args.indexOf('--')
- wrapper = spawn(args[separator + 1] as string, args.slice(separator + 2), { ...options, detached: true })
- return wrapper
- }) as unknown as typeof spawn
- const runSync = vi.fn((command: string, args: readonly string[]) => {
- if (command === 'systemctl' && args[1] === 'kill' && wrapper?.pid !== undefined) {
- process.kill(-wrapper.pid, 'SIGKILL')
- }
- if (command === 'systemctl' && args[1] === 'show') {
- const active = wrapper?.exitCode === null && wrapper.signalCode === null
- return { status: 0, stdout: active ? 'active\n' : 'inactive\n', stderr: '', error: undefined }
- }
- return { status: 0, stdout: '', stderr: '', error: undefined }
- }) as unknown as typeof spawnSync
- const launch = launchLinuxScope(spec([process.execPath, '-e', 'setInterval(() => {}, 1000)']), {
- spawn: run,
- spawnSync: runSync,
- systemctlQuery: asyncQuery(runSync),
- runnerInvocation: spawnRunnerInvocation(),
- })
- launch.owner.signal('SIGTERM')
- await expect(launch.direct).rejects.toThrow('exited without a direct-command result')
- await expect(launch.owner.waitForExit()).resolves.toBeUndefined()
- })
- it.each([
- [
- 'execution error',
- { status: null, stdout: '', stderr: '', error: new Error('systemctl execution failed') },
- 'systemctl execution failed',
- ],
- [
- 'stderr',
- { status: 1, stdout: '', stderr: 'Failed to connect to bus', error: undefined },
- 'Failed to connect to bus',
- ],
- [
- 'exit status',
- { status: 1, stdout: '', stderr: '', error: undefined },
- 'exit 1',
- ],
- ])('reports a failed scope KILL through the shared wait: %s', async (_label, failure, message) => {
- let wrapper: ReturnType<typeof spawn> | undefined
- const run = vi.fn((_command: string, args: readonly string[], options: Parameters<typeof spawn>[2]) => {
- const separator = args.indexOf('--')
- wrapper = spawn(args[separator + 1] as string, args.slice(separator + 2), options)
- return wrapper
- }) as unknown as typeof spawn
- const runSyncMock = vi.fn((
- command: string,
- args: readonly string[],
- _options?: { env?: NodeJS.ProcessEnv },
- ) => {
- if (command === 'systemctl' && args[1] === 'kill') {
- return failure
- }
- return { status: 0, stdout: 'active\n', stderr: '', error: undefined }
- })
- const runSync = runSyncMock as unknown as typeof spawnSync
- const launch = launchLinuxScope(spec([process.execPath, '-e', 'setInterval(() => {}, 1000)']), {
- spawn: run,
- spawnSync: runSync,
- systemctlQuery: asyncQuery(runSync),
- runnerInvocation: spawnRunnerInvocation(),
- })
- void launch.direct.catch(() => {})
- try {
- launch.owner.signal('SIGKILL')
- await expect(launch.owner.waitForExit()).rejects.toThrow(message)
- const killCall = runSyncMock.mock.calls.find(([, args]) => args.includes('--signal=SIGKILL'))
- expect(killCall?.[0]).toBe('systemctl')
- expect(killCall?.[1]).toContain('kill')
- expect(killCall?.[1]).toContain('--kill-whom=all')
- expect(killCall?.[2]?.env?.LC_ALL).toBe('C')
- } finally {
- wrapper?.kill('SIGKILL')
- }
- })
- it('uses the production command defaults when no Linux internals are supplied', async () => {
- let wrapper: ReturnType<typeof spawn> | undefined
- let queryFailure: (Error & { code?: string | number }) | undefined
- const run = vi.fn()
- const runSync = vi.fn()
- const runAsync = vi.fn()
- const queryEnvironments: Array<NodeJS.ProcessEnv | undefined> = []
- vi.resetModules()
- vi.doMock('node:child_process', async (importOriginal) => {
- const actual = await importOriginal<typeof import('node:child_process')>()
- run.mockImplementation((_command: string, args: readonly string[], options: Parameters<typeof spawn>[2]) => {
- const separator = args.indexOf('--')
- wrapper = actual.spawn(args[separator + 1] as string, args.slice(separator + 2), options)
- return wrapper
- })
- runSync.mockImplementation((_command: string, _args: readonly string[]) => {
- return { status: 0, stdout: '', stderr: '', error: undefined }
- })
- runAsync.mockImplementation((
- _command: string,
- args: readonly string[],
- options: { env?: NodeJS.ProcessEnv },
- callback: (error: Error | null, stdout: string, stderr: string) => void,
- ) => {
- queryEnvironments.push(options.env)
- if (queryFailure !== undefined) {
- callback(queryFailure, '', '')
- return
- }
- const active = wrapper?.exitCode === null && wrapper.signalCode === null
- callback(null, args[1] === 'show' && active ? 'active\n' : 'inactive\n', '')
- })
- return { ...actual, execFile: runAsync, spawn: run, spawnSync: runSync }
- })
- try {
- const defaults = await import('../src/linux-scope.ts')
- expect(defaults.probeLinuxUserManager()).toBe(true)
- expect(defaults.probeLinuxRunner()).toBe(true)
- expect(defaults.probeLinuxScope()).toBe(true)
- const terminalLaunch = defaults.prepareLinuxTerminalScope(['shell', 'literal $HOME'])
- expect(terminalLaunch.command).toBe('systemd-run')
- expect(terminalLaunch.args.slice(-3)).toEqual(['--', 'shell', 'literal $HOME'])
- const launch = defaults.launchLinuxScope(spec([process.execPath, '-e', 'process.exit(0)']))
- await expect(launch.direct).resolves.toEqual({ exitCode: 0, signal: null })
- await expect(launch.owner.waitForExit()).resolves.toBeUndefined()
- expect(run).toHaveBeenCalledWith('systemd-run', expect.any(Array), expect.any(Object))
- expect(runSync).toHaveBeenCalledWith('systemctl', expect.any(Array), expect.any(Object))
- expect(runAsync).toHaveBeenCalledWith('systemctl', expect.any(Array), expect.any(Object), expect.any(Function))
- expect(queryEnvironments[0]?.LC_ALL).toBe('C')
- queryFailure = Object.assign(new Error('numeric systemctl failure'), { code: 17 })
- const numericFailure = defaults.launchLinuxScope(spec([process.execPath, '-e', 'process.exit(0)']))
- await expect(numericFailure.owner.waitForExit()).rejects.toBe(queryFailure)
- await expect(numericFailure.direct).resolves.toEqual({ exitCode: 0, signal: null })
- queryFailure = Object.assign(new Error('named systemctl failure'), { code: 'EQUERY' })
- const namedFailure = defaults.launchLinuxScope(spec([process.execPath, '-e', 'process.exit(0)']))
- await expect(namedFailure.owner.waitForExit()).rejects.toBe(queryFailure)
- await expect(namedFailure.direct).resolves.toEqual({ exitCode: 0, signal: null })
- } finally {
- vi.doUnmock('node:child_process')
- vi.resetModules()
- }
- })
- })
|