GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build darwin arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux x64 Agent (push) Canceled after 0s
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
Account for Electron-as-Node when probing the current executable and update the Linux workspace registry scenario to validate the writable handle behavior shipped with remote file management.
275 lines
7.8 KiB
TypeScript
275 lines
7.8 KiB
TypeScript
import { EventEmitter } from 'node:events'
|
|
import { realpath } from 'node:fs/promises'
|
|
import { basename, dirname, resolve } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import {
|
|
detectAgentRuntimes,
|
|
detectRuntimeBinary,
|
|
validateRuntimeVersion,
|
|
type RuntimeVersionProcess
|
|
} from './runtime-discovery'
|
|
|
|
const originalPath = process.env.PATH
|
|
const originalPathCase = process.env.Path
|
|
|
|
afterEach(() => {
|
|
if (originalPath === undefined) {
|
|
delete process.env.PATH
|
|
} else {
|
|
process.env.PATH = originalPath
|
|
}
|
|
if (originalPathCase === undefined) {
|
|
delete process.env.Path
|
|
} else {
|
|
process.env.Path = originalPathCase
|
|
}
|
|
})
|
|
|
|
describe('runtime discovery', () => {
|
|
it('launches a detached POSIX version probe and awaits bounded tree cleanup', async () => {
|
|
const child =
|
|
new EventEmitter() as RuntimeVersionProcess & EventEmitter
|
|
child.exitCode = null
|
|
child.pid = 2718
|
|
child.kill = vi.fn()
|
|
child.stdout = new EventEmitter()
|
|
child.stderr = new EventEmitter()
|
|
const spawnProcess = vi.fn(() => child)
|
|
let releaseCleanup!: () => void
|
|
const cleanupBlocked = new Promise<void>((resolve) => {
|
|
releaseCleanup = resolve
|
|
})
|
|
const terminateProcessTree = vi.fn(async () => {
|
|
await cleanupBlocked
|
|
})
|
|
|
|
const validation = validateRuntimeVersion('runtime', {
|
|
platform: 'linux',
|
|
spawnProcess,
|
|
terminateProcessTree,
|
|
outputLimit: 4,
|
|
terminationWaitMs: 25
|
|
})
|
|
;(child.stdout as EventEmitter).emit('data', '12345')
|
|
let completed = false
|
|
void validation.then(() => {
|
|
completed = true
|
|
})
|
|
await Promise.resolve()
|
|
|
|
expect(completed).toBe(false)
|
|
expect(spawnProcess).toHaveBeenCalledWith(
|
|
'runtime',
|
|
['--version'],
|
|
expect.objectContaining({ detached: true })
|
|
)
|
|
expect(terminateProcessTree).toHaveBeenCalledWith(child, {
|
|
platform: 'linux',
|
|
processGroup: true,
|
|
signal: 'SIGKILL',
|
|
waitMs: 25
|
|
})
|
|
|
|
releaseCleanup()
|
|
await expect(validation).resolves.toEqual({ valid: false })
|
|
})
|
|
|
|
it('canonicalizes and validates a configured ordinary file first', async () => {
|
|
process.env.PATH = ''
|
|
process.env.Path = ''
|
|
|
|
const detection = await detectRuntimeBinary({
|
|
binaryPath: process.execPath,
|
|
binaryNames: ['binary-that-does-not-exist'],
|
|
label: 'Test CLI'
|
|
})
|
|
|
|
expect(detection).toMatchObject({
|
|
available: true,
|
|
path: await realpath(process.execPath),
|
|
source: 'configured'
|
|
})
|
|
if (process.versions.electron) {
|
|
expect(detection.version).toBeUndefined()
|
|
} else {
|
|
expect(detection.version).toMatch(/^\d+\.\d+\.\d+/u)
|
|
}
|
|
})
|
|
|
|
it('trusts a configured execution path without reading or launching it', async () => {
|
|
process.env.PATH = ''
|
|
process.env.Path = ''
|
|
const candidate = resolve(
|
|
dirname(fileURLToPath(import.meta.url)),
|
|
'runtime-that-does-not-exist'
|
|
)
|
|
|
|
const detection = await detectRuntimeBinary({
|
|
binaryPath: candidate,
|
|
validation: 'path-only',
|
|
binaryNames: ['binary-that-does-not-exist'],
|
|
label: 'Managed Runtime'
|
|
})
|
|
|
|
expect(detection).toEqual({
|
|
available: true,
|
|
path: candidate,
|
|
source: 'configured',
|
|
detail: 'Managed Runtime 已就绪'
|
|
})
|
|
})
|
|
|
|
it('rejects relative configured paths without resolving them from cwd', async () => {
|
|
process.env.PATH = ''
|
|
process.env.Path = ''
|
|
|
|
await expect(
|
|
detectRuntimeBinary({
|
|
binaryPath: 'relative/runtime',
|
|
binaryNames: ['goodbuddy-runtime-that-does-not-exist'],
|
|
label: 'Test CLI'
|
|
})
|
|
).resolves.toEqual({
|
|
available: false,
|
|
detail: expect.stringContaining('必须为绝对路径')
|
|
})
|
|
})
|
|
|
|
it('finds executable names from absolute PATH directories', async () => {
|
|
process.env.PATH = dirname(process.execPath)
|
|
process.env.Path = dirname(process.execPath)
|
|
|
|
const detection = await detectRuntimeBinary({
|
|
binaryPath: '',
|
|
binaryNames: [basename(process.execPath)],
|
|
label: 'Test CLI'
|
|
})
|
|
|
|
expect(detection).toMatchObject({
|
|
available: true,
|
|
path: await realpath(process.execPath),
|
|
source: 'automatic'
|
|
})
|
|
})
|
|
|
|
it('prefers a configured binary over the bundled runtime', async () => {
|
|
const detection = await detectRuntimeBinary({
|
|
binaryPath: process.execPath,
|
|
bundledPath: process.execPath,
|
|
binaryNames: ['goodbuddy-runtime-that-does-not-exist'],
|
|
label: 'Test CLI'
|
|
})
|
|
|
|
expect(detection).toMatchObject({
|
|
available: true,
|
|
path: await realpath(process.execPath),
|
|
source: 'configured'
|
|
})
|
|
expect(detection.detail).not.toContain('内置')
|
|
})
|
|
|
|
it('prefers a bundled runtime over PATH discovery', async () => {
|
|
process.env.PATH = dirname(process.execPath)
|
|
process.env.Path = dirname(process.execPath)
|
|
|
|
const detection = await detectRuntimeBinary({
|
|
binaryPath: '',
|
|
bundledPath: process.execPath,
|
|
binaryNames: [basename(process.execPath)],
|
|
label: 'Test CLI'
|
|
})
|
|
|
|
expect(detection).toMatchObject({
|
|
available: true,
|
|
path: await realpath(process.execPath),
|
|
source: 'bundled'
|
|
})
|
|
expect(detection.detail).toContain('内置')
|
|
})
|
|
|
|
it('allows a bundled script to defer execution validation to its host adapter', async () => {
|
|
process.env.PATH = ''
|
|
process.env.Path = ''
|
|
const bundledScript = fileURLToPath(import.meta.url)
|
|
|
|
const detection = await detectRuntimeBinary({
|
|
binaryPath: '',
|
|
bundledPath: bundledScript,
|
|
bundledValidation: 'canonical-file',
|
|
bundledVersion: '1.5.47',
|
|
binaryNames: ['goodbuddy-runtime-that-does-not-exist'],
|
|
label: 'Script Runtime'
|
|
})
|
|
|
|
expect(detection).toMatchObject({
|
|
available: true,
|
|
path: await realpath(bundledScript),
|
|
version: '1.5.47',
|
|
source: 'bundled'
|
|
})
|
|
expect(detection.detail).toBe(
|
|
'内置 Script Runtime 1.5.47 已就绪'
|
|
)
|
|
})
|
|
|
|
it('accepts a controlled bundled harness when no custom host is configured', async () => {
|
|
const bundledScript = fileURLToPath(import.meta.url)
|
|
const detection = await detectRuntimeBinary({
|
|
binaryPath: '',
|
|
bundledPath: bundledScript,
|
|
bundledValidation: 'canonical-file',
|
|
bundledVersion: '0.1.2-rc.1',
|
|
binaryNames: [],
|
|
label: 'GoodBuddy DeepSeek Harness Host'
|
|
})
|
|
|
|
expect(detection).toMatchObject({
|
|
available: true,
|
|
path: await realpath(bundledScript),
|
|
version: '0.1.2-rc.1',
|
|
source: 'bundled'
|
|
})
|
|
expect(detection.detail).toContain('内置')
|
|
})
|
|
|
|
it('does not discover arbitrary DeepSeek Harness hosts from PATH', async () => {
|
|
process.env.PATH = dirname(process.execPath)
|
|
process.env.Path = dirname(process.execPath)
|
|
|
|
await expect(
|
|
detectRuntimeBinary({
|
|
binaryPath: '',
|
|
allowAutomaticDiscovery: false,
|
|
binaryNames: [basename(process.execPath)],
|
|
label: 'GoodBuddy DeepSeek Harness Host'
|
|
})
|
|
).resolves.toEqual({
|
|
available: false,
|
|
detail: expect.stringContaining('未自动检测到')
|
|
})
|
|
})
|
|
|
|
it('returns both runtime detections without exposing PATH contents', async () => {
|
|
const privatePathValue = `${dirname(process.execPath)}-private-path-value`
|
|
process.env.PATH = privatePathValue
|
|
process.env.Path = privatePathValue
|
|
|
|
const result = await detectAgentRuntimes({
|
|
opencodeBinaryPath: process.execPath,
|
|
continueBinaryPath: process.execPath
|
|
})
|
|
|
|
expect(result.opencode).toMatchObject({
|
|
available: true,
|
|
path: await realpath(process.execPath)
|
|
})
|
|
expect(result.continue).toMatchObject({
|
|
available: true,
|
|
path: await realpath(process.execPath)
|
|
})
|
|
expect(result.deepseekHarness.available).toBe(false)
|
|
expect(JSON.stringify(result)).not.toContain(privatePathValue)
|
|
})
|
|
})
|