Skill update round trip / roundtrip (false, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, latest) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
108 lines
4.5 KiB
TypeScript
108 lines
4.5 KiB
TypeScript
/**
|
||
* Real-zsh proof that a wrapper dir holding files from two Orca builds still
|
||
* loads the user's own zsh config.
|
||
*
|
||
* A shared dir used to mean a shell could read one build's `.zshenv` and
|
||
* another's `.zprofile`/`.zshrc`/`.zlogin`, which is why every generated file
|
||
* redefined the helpers it called. #15285 removed the hazard for the desktop and
|
||
* daemon trees by naming each one after a hash of its contents, so two builds
|
||
* never write the same directory.
|
||
*
|
||
* The relay is the one writer left on a fixed path — `~/.orca-relay/shell-ready`
|
||
* — so this is where the scenario is still reachable, and it is now much smaller:
|
||
* Orca writes one file, and that file hands ZDOTDIR back before anything else
|
||
* runs. Both halves are pinned here:
|
||
*
|
||
* 1. Files an older build left beside the hook are inert — zsh reads .zprofile,
|
||
* .zshrc and .zlogin from the user's own directory and never from here.
|
||
* 2. Generation does not delete them. An older build checks for all four before
|
||
* calling its tree complete; deleting them makes it rewrite its own `.zshenv`
|
||
* over ours, and an older `.zshenv` points ZDOTDIR at a directory that would
|
||
* then hold no `.zshrc` at all — the user losing their entire config, which is
|
||
* the failure this file exists to prevent.
|
||
*/
|
||
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||
import { tmpdir } from 'node:os'
|
||
import { join } from 'node:path'
|
||
import { describe, expect, it } from 'vitest'
|
||
import { ensureOverlayRestoreWrappers } from '../relay/pty-shell-overlay-wrappers'
|
||
import { hasZsh, makeZshHome, runZshPty } from './zsh-startup-hook-pty-harness'
|
||
|
||
const itWithZsh = hasZsh ? it : it.skip
|
||
|
||
/** The three files an older build wrote alongside its own `.zshenv`. */
|
||
const OLDER_BUILD_FILES = {
|
||
'.zprofile': 'export ORCA_TEST_STALE_ZPROFILE=1\n',
|
||
'.zshrc': 'export ORCA_TEST_STALE_ZSHRC=1\n',
|
||
'.zlogin': 'export ORCA_TEST_STALE_ZLOGIN=1\n'
|
||
}
|
||
|
||
describe.skipIf(process.platform === 'win32')('zsh wrapper dir written by mixed builds', () => {
|
||
itWithZsh('ignores an older build’s files and loads the user’s config instead', async () => {
|
||
const root = mkdtempSync(join(tmpdir(), 'orca-wrapper-mismatch-'))
|
||
const home = makeZshHome({
|
||
'.zshenv': 'export ORCA_TEST_USER_ZSHENV=1\n',
|
||
'.zprofile': 'export ORCA_TEST_USER_ZPROFILE=1\n',
|
||
'.zshrc': 'export ORCA_TEST_USER_ZSHRC=1\n'
|
||
})
|
||
try {
|
||
expect(ensureOverlayRestoreWrappers(root)).toBe(true)
|
||
const zshDir = join(root, 'zsh')
|
||
for (const [name, content] of Object.entries(OLDER_BUILD_FILES)) {
|
||
writeFileSync(join(zshDir, name), content)
|
||
}
|
||
|
||
const { values } = await runZshPty({
|
||
env: {
|
||
PATH: '/usr/bin:/bin',
|
||
HOME: home,
|
||
ZDOTDIR: zshDir,
|
||
ORCA_ORIG_ZDOTDIR: home,
|
||
ORCA_SHELL_FEATURES: 'history',
|
||
ORCA_HISTFILE: join(home, 'scoped_history')
|
||
},
|
||
report: [
|
||
'ORCA_TEST_USER_ZPROFILE',
|
||
'ORCA_TEST_USER_ZSHRC',
|
||
'ORCA_TEST_STALE_ZPROFILE',
|
||
'ORCA_TEST_STALE_ZSHRC',
|
||
'ORCA_TEST_STALE_ZLOGIN',
|
||
'HISTFILE'
|
||
]
|
||
})
|
||
|
||
// The user's own files loaded; the older build's leftovers did not.
|
||
expect(values.ORCA_TEST_USER_ZPROFILE).toBe('1')
|
||
expect(values.ORCA_TEST_USER_ZSHRC).toBe('1')
|
||
expect(values.ORCA_TEST_STALE_ZPROFILE).toBe('UNSET')
|
||
expect(values.ORCA_TEST_STALE_ZSHRC).toBe('UNSET')
|
||
expect(values.ORCA_TEST_STALE_ZLOGIN).toBe('UNSET')
|
||
expect(values.HISTFILE).toBe(join(home, 'scoped_history'))
|
||
} finally {
|
||
rmSync(root, { recursive: true, force: true })
|
||
rmSync(home, { recursive: true, force: true })
|
||
}
|
||
})
|
||
|
||
it('leaves an older build’s files in place so that build can still use them', () => {
|
||
const root = mkdtempSync(join(tmpdir(), 'orca-wrapper-mismatch-keep-'))
|
||
try {
|
||
expect(ensureOverlayRestoreWrappers(root)).toBe(true)
|
||
const zshDir = join(root, 'zsh')
|
||
for (const [name, content] of Object.entries(OLDER_BUILD_FILES)) {
|
||
writeFileSync(join(zshDir, name), content)
|
||
}
|
||
|
||
// A second generation pass is what an older build's launch would race.
|
||
expect(ensureOverlayRestoreWrappers(root)).toBe(true)
|
||
|
||
for (const [name, content] of Object.entries(OLDER_BUILD_FILES)) {
|
||
expect(existsSync(join(zshDir, name))).toBe(true)
|
||
expect(readFileSync(join(zshDir, name), 'utf8')).toBe(content)
|
||
}
|
||
} finally {
|
||
rmSync(root, { recursive: true, force: true })
|
||
}
|
||
})
|
||
})
|