Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,7 @@

# Generated ACP schemas are checked byte-for-byte against formatted output.
/src/main/acp/generated/*.generated.ts linguist-generated=true text eol=lf
# Generated skill bundle artifacts are verified byte-for-byte against fresh output.
/resources/skills/*.json linguist-generated=true text eol=lf
# Vitest writes snapshots with LF, so checkout bytes must match on every host.
**/__snapshots__/*.snap text eol=lf
49 changes: 49 additions & 0 deletions config/scripts/generated-artifact-line-ending-pin.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { execFileSync } from 'node:child_process'
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { expect, it } from 'vitest'

const projectDir = resolve(import.meta.dirname, '../..')

it('keeps byte-compared artifacts and source LF in an autocrlf checkout', () => {
const root = mkdtempSync(join(tmpdir(), 'orca-line-ending-checkout-'))
const lf = 'first\nsecond\n'
const crlf = 'first\r\nsecond\r\n'
const paths = new Map([
['resources/skills/current-manifest.json', lf],
['resources/skills/snapshot-registry.json', lf],
['resources/skills/release-mapping.json', lf],
['src/main/__snapshots__/example.test.ts.snap', lf],
['src/renderer/src/example.tsx', lf],
['src/main/example.ts', lf],
['config/scripts/example.mjs', lf],
...['cmd', 'bat', 'ps1', 'nsh'].map((extension) => [`config/example.${extension}`, crlf]),
['resources/skills-extra/example.json', crlf],
['vendor/resources/skills/example.json', crlf],
['scripts/example.mjs', crlf],
['src/main/runtime/__fixtures__/example.txt', crlf],
['src/main/daemon/__fixtures__/pty-transcripts/example.txt', crlf]
])
const git = (args) => execFileSync('git', args, { cwd: root, stdio: 'pipe' })

try {
git(['init', '--quiet'])
writeFileSync(join(root, '.gitattributes'), readFileSync(join(projectDir, '.gitattributes')))
for (const path of paths.keys()) {
mkdirSync(dirname(join(root, path)), { recursive: true })
writeFileSync(join(root, path), path.includes('__fixtures__') ? crlf : lf)
}
git(['-c', 'core.autocrlf=input', 'add', '--', '.'])
for (const path of paths.keys()) {
rmSync(join(root, path))
}
git(['-c', 'core.autocrlf=true', 'checkout-index', '--all', '--force'])

for (const [path, expected] of paths) {
expect(readFileSync(join(root, path), 'utf8'), path).toBe(expected)
}
} finally {
rmSync(root, { recursive: true, force: true })
}
})
45 changes: 11 additions & 34 deletions config/scripts/run-node-server-tests.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Runs the headless-server suites under the pinned Node (design D4/D4a), not the host's.
import { join, resolve } from 'node:path'
import { randomUUID } from 'node:crypto'
import { readFileSync } from 'node:fs'
import { globSync, readFileSync } from 'node:fs'
import { ORCAD_VERSION_FILENAME } from '../../src/shared/orcad-artifacts.ts'
import { NODE_RUNTIME_PIN } from '../../src/shared/node-runtime-pin.ts'
import {
Expand All @@ -11,6 +11,7 @@ import {
import { packagedNodeRuntimePath } from './build-orcad-node.mjs'
import { ensurePinnedNodeExecutable } from './pinned-node-downloads.mjs'
import { currentTarget } from './server-build-target.mjs'
import { UNIT_INCLUDE } from './ci-unit-files.mjs'
import { describeProcessFailure, runProcessSync } from './script-child-process.mjs'
import {
CROSS_RUNTIME_TEST_PATHS,
Expand Down Expand Up @@ -84,38 +85,14 @@ run(runtimePath, [
])

function defaultTestArgs() {
// Why resolve the files: Vitest's per-project excludes override a CLI --exclude, so a directory
// selector would still run Electron probes, which need a display these containers lack.
const vitest = join(root, 'node_modules/vitest/vitest.mjs')
// Why: vitest 5 drops CLI --exclude for inline projects, so the selectors' substring matches
// are resolved to files here and filtered before vitest sees them.
// Electron probes run in desktop jobs; headless compatibility containers have no display.
const selectors = nodeServerTestPaths({ artifact, crossRuntime })
const result = runProcessSync({
program: runtimePath,
args: [
vitest,
'list',
'--config',
'config/vitest.config.ts',
...selectors,
'--filesOnly',
'--json'
],
cwd: root,
env,
timeoutMs: 120_000,
maxOutputBytes: 16 * 1024 * 1024
})
if (result.code !== 0 || result.timedOut || result.outputTruncated) {
throw new Error(`Listing node-server tests failed: ${describeProcessFailure(result)}`)
}
// A directory selector would otherwise pull cross-runtime tests into lanes that lack their inputs.
const skipped = crossRuntime ? [] : CROSS_RUNTIME_TEST_PATHS
const files = [...new Set(JSON.parse(result.stdout).map((entry) => entry.file))].filter(
(file) =>
!file.endsWith('.electron.test.ts') &&
!skipped.some((path) => file.replaceAll('\\', '/').endsWith(path))
)
if (files.length === 0) {
throw new Error('No node-server tests were selected')
}
return files
const skipped = new Set(crossRuntime ? [] : CROSS_RUNTIME_TEST_PATHS)
return globSync(UNIT_INCLUDE, { cwd: root })
.map((path) => path.replaceAll('\\', '/'))
.filter((path) => selectors.some((selector) => path.includes(selector)))
.filter((path) => !path.endsWith('.electron.test.ts') && !skipped.has(path))
.sort()
}
1 change: 1 addition & 0 deletions config/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const transforms = {
const testOptions = {
environment: 'node',
clearMocks: false,
fsModuleCache: true,
env: { ORCA_VITEST_RUNTIME: 'node' },
server: { deps: { inline: ['zod'] } },
// Node's storage globals and V8 retention checks require the existing child flags.
Expand Down
13 changes: 1 addition & 12 deletions src/main/automations/automation-dispatch-host-fence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ import { removeTreeSync } from '../../shared/windows-transient-lock-removal'
import type { Store } from '../persistence'
import { resolveAutomationRunTarget } from './run-target-resolution'
import { AutomationService } from './service'
import { installFakeAppEnvironment } from '../../../config/scripts/vitest-host-ports-setup'
import { closeTestStores, createSqliteTestStore } from '../persistence-test-harness'

const testState = { dir: '' }
import { closeTestStores, createStore, testState } from '../persistence-test-harness'

vi.mock('electron', () => ({
app: {
Expand All @@ -37,14 +34,6 @@ vi.mock('electron', () => ({
}
}))

async function createStore(): Promise<Store> {
vi.resetModules()
installFakeAppEnvironment({ getPath: () => testState.dir })
const { Store: StoreClass, initDataPath } = await import('../persistence')
initDataPath()
return createSqliteTestStore(StoreClass, { dataFile: join(testState.dir, 'orca-data.json') })
}

/** The reachable shape: a runtime-owned id is derived, not minted per lifecycle. */
const TARGET_ID = 'runtime-ssh-recipe-1'

Expand Down
14 changes: 2 additions & 12 deletions src/main/automations/automation-skip-coalescing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@ import { removeTreeSync } from '../../shared/windows-transient-lock-removal'
import type { Store } from '../persistence'
import {
closeTestStores,
createSqliteTestStore,
createStore,
readPersistedStateJson,
testState,
writePersistedStateJson
} from '../persistence-test-harness'
import { AutomationService } from './service'
import { installFakeAppEnvironment } from '../../../config/scripts/vitest-host-ports-setup'

const testState = { dir: '' }

vi.mock('electron', () => ({
app: {
Expand All @@ -35,14 +33,6 @@ vi.mock('electron', () => ({
}
}))

async function createStore(): Promise<Store> {
vi.resetModules()
installFakeAppEnvironment({ getPath: () => testState.dir })
const { Store: StoreClass, initDataPath } = await import('../persistence')
initDataPath()
return createSqliteTestStore(StoreClass, { dataFile: join(testState.dir, 'orca-data.json') })
}

const makeRepo = (): Repo => ({
id: 'r1',
path: '/repo',
Expand Down
13 changes: 1 addition & 12 deletions src/main/automations/automation-zero-grace-tick-latency.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { closeTestStores, createSqliteTestStore } from '../persistence-test-harness'
import { closeTestStores, createStore, testState } from '../persistence-test-harness'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { mkdtempSync, rmSync } from 'node:fs'
import { join } from 'node:path'
import { tmpdir } from 'node:os'
import type { Repo } from '../../shared/repo-types'
import { AutomationService } from './service'
import { installFakeAppEnvironment } from '../../../config/scripts/vitest-host-ports-setup'

const testState = { dir: '' }

vi.mock('electron', () => ({
app: {
Expand All @@ -20,14 +17,6 @@ vi.mock('electron', () => ({
}
}))

async function createStore() {
vi.resetModules()
installFakeAppEnvironment({ getPath: () => testState.dir })
const { Store, initDataPath } = await import('../persistence')
initDataPath()
return createSqliteTestStore(Store, { dataFile: join(testState.dir, 'orca-data.json') })
}

const makeRepo = (overrides: Partial<Repo> = {}): Repo => ({
id: 'r1',
path: '/repo',
Expand Down
13 changes: 1 addition & 12 deletions src/main/automations/retained-run-reconciliation.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { closeTestStores, createSqliteTestStore } from '../persistence-test-harness'
import { closeTestStores, createStore, testState } from '../persistence-test-harness'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { mkdtempSync, rmSync } from 'node:fs'
import { join } from 'node:path'
Expand All @@ -14,9 +14,6 @@ import type {
AutomationRunCompletionObservation,
AutomationRunTerminalObserver
} from './run-completion-watcher'
import { installFakeAppEnvironment } from '../../../config/scripts/vitest-host-ports-setup'

const testState = { dir: '' }

vi.mock('electron', () => ({
app: { getPath: () => testState.dir },
Expand All @@ -28,14 +25,6 @@ vi.mock('electron', () => ({
}
}))

async function createStore() {
vi.resetModules()
installFakeAppEnvironment({ getPath: () => testState.dir })
const { Store, initDataPath } = await import('../persistence')
initDataPath()
return createSqliteTestStore(Store, { dataFile: join(testState.dir, 'orca-data.json') })
}

type TestStore = Awaited<ReturnType<typeof createStore>>

const makeRepo = (): Repo => ({
Expand Down
12 changes: 1 addition & 11 deletions src/main/automations/run-completion-watcher.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { closeTestStores, createSqliteTestStore } from '../persistence-test-harness'
import { closeTestStores, createStore, testState } from '../persistence-test-harness'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { mkdtempSync, rmSync } from 'node:fs'
import { join } from 'node:path'
Expand All @@ -11,9 +11,7 @@ import type {
AutomationRunCompletionObservation,
AutomationRunTerminalObserver
} from './run-completion-watcher'
import { installFakeAppEnvironment } from '../../../config/scripts/vitest-host-ports-setup'

const testState = { dir: '' }
const ipcHandlers = new Map<string, (event: unknown, args: unknown) => unknown>()

vi.mock('electron', () => ({
Expand All @@ -32,14 +30,6 @@ vi.mock('electron', () => ({
}
}))

async function createStore() {
vi.resetModules()
installFakeAppEnvironment({ getPath: () => testState.dir })
const { Store, initDataPath } = await import('../persistence')
initDataPath()
return createSqliteTestStore(Store, { dataFile: join(testState.dir, 'orca-data.json') })
}

const makeRepo = (overrides: Partial<Repo> = {}): Repo => ({
id: 'r1',
path: '/repo',
Expand Down
15 changes: 2 additions & 13 deletions src/main/automations/service.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
closeTestStores,
createSqliteTestStore,
createStore,
testState,
readPersistedStateJson,
writePersistedStateJson
} from '../persistence-test-harness'
Expand All @@ -11,9 +12,6 @@ import { tmpdir } from 'node:os'
import type { Repo } from '../../shared/repo-types'
import { toRuntimeExecutionHostId } from '../../shared/execution-host'
import { AutomationService } from './service'
import { installFakeAppEnvironment } from '../../../config/scripts/vitest-host-ports-setup'

const testState = { dir: '' }

vi.mock('electron', () => ({
app: {
Expand All @@ -26,15 +24,6 @@ vi.mock('electron', () => ({
}
}))

async function createStore() {
vi.resetModules()
// Why: userData resolves through AppEnvironment; point it at this file's temp dir.
installFakeAppEnvironment({ getPath: () => testState.dir })
const { Store, initDataPath } = await import('../persistence')
initDataPath()
return createSqliteTestStore(Store, { dataFile: join(testState.dir, 'orca-data.json') })
}

/** Simulate registry drift after a record was stored; the create path derives contexts itself. */
function mutateDataFile(mutate: (state: { automations: Record<string, unknown>[] }) => void): void {
const file = join(testState.dir, 'orca-data.json')
Expand Down
40 changes: 21 additions & 19 deletions src/main/runtime/orca-runtime-create-managed-worktree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,26 +161,27 @@ export class OrcaRuntimeWithCreateManagedWorktree extends OrcaRuntimeWithGetWork
: {})
}
}
const localCreate = await createRuntimeLocalManagedWorktree({
request: args,
repo,
store: this.requireStore(),
createdWithAgent: effectiveCreatedWithAgent,
hostedReviewExecutionContext: this.getHostedReviewExecutionOptions(repo),
resolveRemoteTrackingBase: (path, base, ...options) =>
this.resolveRemoteTrackingBase(path, base, ...options),
hasRemoteTrackingRef: (path, base, ...options) =>
this.hasRemoteTrackingRef(path, base, ...options),
refreshRemoteTrackingBase: (path, base, ...options) =>
this.getOrStartRemoteTrackingBaseRefresh(path, base, ...options),
fetchRemote: (path, remote, ...options) =>
this.fetchRemoteWithCache(path, remote, ...options),
onWorktreeMetadataPersisted: (persistedWorktree) =>
this.recordCreatedWorktreeLineage(persistedWorktree, lineageResolution),
rearm,
timing
})
const { worktree, worktreePath, includeCopyWarning, created, addResult, metadataResult } =
await createRuntimeLocalManagedWorktree({
request: args,
repo,
store: this.requireStore(),
createdWithAgent: effectiveCreatedWithAgent,
hostedReviewExecutionContext: this.getHostedReviewExecutionOptions(repo),
resolveRemoteTrackingBase: (path, base, ...options) =>
this.resolveRemoteTrackingBase(path, base, ...options),
hasRemoteTrackingRef: (path, base, ...options) =>
this.hasRemoteTrackingRef(path, base, ...options),
refreshRemoteTrackingBase: (path, base, ...options) =>
this.getOrStartRemoteTrackingBaseRefresh(path, base, ...options),
fetchRemote: (path, remote, ...options) =>
this.fetchRemoteWithCache(path, remote, ...options),
onWorktreeMetadataPersisted: (persistedWorktree) =>
this.recordCreatedWorktreeLineage(persistedWorktree, lineageResolution),
rearm,
timing
})
localCreate
const settings = createSettings
const { lineage, workspaceLineage, warnings: lineageWarnings } = metadataResult

Expand Down Expand Up @@ -299,6 +300,7 @@ export class OrcaRuntimeWithCreateManagedWorktree extends OrcaRuntimeWithGetWork
: {}),
...(defaultTabs ? { defaultTabs } : {}),
...(warning ? { warning } : {}),
...(localCreate.baseFallback ? { baseFallback: localCreate.baseFallback } : {}),
...(addResult.localBaseRefRefresh
? { localBaseRefRefresh: addResult.localBaseRefRefresh }
: {}),
Expand Down
Loading
Loading