mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 08:23:59 +01:00
test(windows): name the B2, B4, B5 and B12 tests in the curated Windows lane
The runtime-root bundle tests lived in browse/test/config.test.ts and the Copilot glossary test in test/host-config.test.ts, both excluded from the Windows lane, so the two Windows-only fixes were never checked on Windows. Move each into its own Windows-safe file (unchanged assertions) and list it, the piped-stdin chain test and the /cso docker.exe discovery test as named Windows coverage in scripts/lib/windows-curation.ts.
This commit is contained in:
1 parent
981798f9bc
commit
9de8be3df1
5 files changed
+112
-90
No files matched your search
@@ -297,86 +297,6 @@ describe('resolveNodeServerScript', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#3026: a Windows runtime root without node_modules runs the source checkout bundle', () => {
|
||||
const { resolveNodeServerScript } = require('../src/cli');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
|
||||
function layout(opts: { runtimeModules: boolean; sourcePath: boolean; sourceModules: boolean }) {
|
||||
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'browse-runtime-root-'));
|
||||
const source = path.join(base, 'gstack');
|
||||
const runtime = path.join(base, 'home', '.codex', 'skills', 'gstack');
|
||||
for (const dir of [source, runtime]) {
|
||||
fs.mkdirSync(path.join(dir, 'browse', 'dist'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'browse', 'dist', 'server-node.mjs'), '');
|
||||
}
|
||||
const playwright = (root: string) => {
|
||||
fs.mkdirSync(path.join(root, 'node_modules', 'playwright'), { recursive: true });
|
||||
fs.writeFileSync(path.join(root, 'node_modules', 'playwright', 'package.json'), '{}');
|
||||
};
|
||||
if (opts.sourceModules) playwright(source);
|
||||
if (opts.runtimeModules) playwright(runtime);
|
||||
if (opts.sourcePath) fs.writeFileSync(path.join(runtime, '.source-path'), `${source}\n`);
|
||||
return { base, source, runtime, exe: path.join(runtime, 'browse', 'dist', 'browse.exe') };
|
||||
}
|
||||
|
||||
test('no reachable playwright beside the copy -> the checkout bundle named by .source-path', () => {
|
||||
const l = layout({ runtimeModules: false, sourcePath: true, sourceModules: true });
|
||||
try { expect(resolveNodeServerScript('/$bunfs/root', l.exe)).toBe(path.join(l.source, 'browse', 'dist', 'server-node.mjs')); }
|
||||
finally { fs.rmSync(l.base, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('a runtime root that reaches playwright keeps its own bundle', () => {
|
||||
const l = layout({ runtimeModules: true, sourcePath: true, sourceModules: true });
|
||||
try { expect(resolveNodeServerScript('/$bunfs/root', l.exe)).toBe(path.join(l.runtime, 'browse', 'dist', 'server-node.mjs')); }
|
||||
finally { fs.rmSync(l.base, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('without .source-path, or a checkout without node_modules, the adjacent bundle is unchanged', () => {
|
||||
for (const opts of [{ runtimeModules: false, sourcePath: false, sourceModules: true }, { runtimeModules: false, sourcePath: true, sourceModules: false }]) {
|
||||
const l = layout(opts);
|
||||
try { expect(resolveNodeServerScript('/$bunfs/root', l.exe)).toBe(path.join(l.runtime, 'browse', 'dist', 'server-node.mjs')); }
|
||||
finally { fs.rmSync(l.base, { recursive: true, force: true }); }
|
||||
}
|
||||
});
|
||||
|
||||
test('same build on both sides -> the checkout bundle; different builds -> refusal with the ./setup fix and anchor', () => {
|
||||
const { BROWSE_VERSION_SKEW_ANCHOR } = require('../src/cli');
|
||||
const l = layout({ runtimeModules: false, sourcePath: true, sourceModules: true });
|
||||
const version = (root: string, v: string) => fs.writeFileSync(path.join(root, 'browse', 'dist', '.version'), `${v}\n`);
|
||||
try {
|
||||
version(l.runtime, 'a'.repeat(40));
|
||||
version(l.source, 'a'.repeat(40));
|
||||
expect(resolveNodeServerScript('/$bunfs/root', l.exe)).toBe(path.join(l.source, 'browse', 'dist', 'server-node.mjs'));
|
||||
version(l.source, 'b'.repeat(40));
|
||||
let message = '';
|
||||
try { resolveNodeServerScript('/$bunfs/root', l.exe); } catch (err: any) { message = err.message; }
|
||||
expect(message).toContain(`build ${'a'.repeat(12)}`);
|
||||
expect(message).toContain(`build ${'b'.repeat(12)}`);
|
||||
expect(message).toContain('are from different builds, so the server was not started');
|
||||
expect(message).toContain(`Fix: cd "${l.source}" && ./setup`);
|
||||
expect(message).toContain(BROWSE_VERSION_SKEW_ANCHOR);
|
||||
expect(BROWSE_VERSION_SKEW_ANCHOR).toBe('https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#browse-runtime-version-skew');
|
||||
// resolveServerLaunch surfaces the same refusal on Windows.
|
||||
expect(() => require('../src/cli').resolveServerLaunch('win32', {}, '/$bunfs/root', l.exe)).toThrow('different builds');
|
||||
} finally { fs.rmSync(l.base, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('an unversioned build on either side is not called skew', () => {
|
||||
const l = layout({ runtimeModules: false, sourcePath: true, sourceModules: true });
|
||||
try {
|
||||
fs.writeFileSync(path.join(l.runtime, 'browse', 'dist', '.version'), 'abc\n');
|
||||
expect(resolveNodeServerScript('/$bunfs/root', l.exe)).toBe(path.join(l.source, 'browse', 'dist', 'server-node.mjs'));
|
||||
} finally { fs.rmSync(l.base, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('setup records the source checkout in every runtime root it links', () => {
|
||||
const setup = fs.readFileSync(path.resolve(__dirname, '../../setup'), 'utf8');
|
||||
const fn = setup.slice(setup.indexOf('_link_runtime_dists() {'), setup.indexOf('\n}\n', setup.indexOf('_link_runtime_dists() {')));
|
||||
expect(fn).toContain(`printf '%s\\n' "$1" > "$2/.source-path"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('version mismatch detection', () => {
|
||||
test('detects when versions differ', () => {
|
||||
const stateVersion = 'abc123';
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* A Windows host runtime root (~/.codex/skills/gstack and the other env-var
|
||||
* hosts) holds a copy of browse/dist with no node_modules, so its CLI starts
|
||||
* the source checkout's Node server bundle named by .source-path, and refuses
|
||||
* when the two come from different builds. Windows-safe (os.tmpdir paths
|
||||
* only), so the curated Windows lane runs it natively.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import * as path from 'path';
|
||||
|
||||
describe('#3026: a Windows runtime root without node_modules runs the source checkout bundle', () => {
|
||||
const { resolveNodeServerScript } = require('../src/cli');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
|
||||
function layout(opts: { runtimeModules: boolean; sourcePath: boolean; sourceModules: boolean }) {
|
||||
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'browse-runtime-root-'));
|
||||
const source = path.join(base, 'gstack');
|
||||
const runtime = path.join(base, 'home', '.codex', 'skills', 'gstack');
|
||||
for (const dir of [source, runtime]) {
|
||||
fs.mkdirSync(path.join(dir, 'browse', 'dist'), { recursive: true });
|
||||
fs.writeFileSync(path.join(dir, 'browse', 'dist', 'server-node.mjs'), '');
|
||||
}
|
||||
const playwright = (root: string) => {
|
||||
fs.mkdirSync(path.join(root, 'node_modules', 'playwright'), { recursive: true });
|
||||
fs.writeFileSync(path.join(root, 'node_modules', 'playwright', 'package.json'), '{}');
|
||||
};
|
||||
if (opts.sourceModules) playwright(source);
|
||||
if (opts.runtimeModules) playwright(runtime);
|
||||
if (opts.sourcePath) fs.writeFileSync(path.join(runtime, '.source-path'), `${source}\n`);
|
||||
return { base, source, runtime, exe: path.join(runtime, 'browse', 'dist', 'browse.exe') };
|
||||
}
|
||||
|
||||
test('no reachable playwright beside the copy -> the checkout bundle named by .source-path', () => {
|
||||
const l = layout({ runtimeModules: false, sourcePath: true, sourceModules: true });
|
||||
try { expect(resolveNodeServerScript('/$bunfs/root', l.exe)).toBe(path.join(l.source, 'browse', 'dist', 'server-node.mjs')); }
|
||||
finally { fs.rmSync(l.base, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('a runtime root that reaches playwright keeps its own bundle', () => {
|
||||
const l = layout({ runtimeModules: true, sourcePath: true, sourceModules: true });
|
||||
try { expect(resolveNodeServerScript('/$bunfs/root', l.exe)).toBe(path.join(l.runtime, 'browse', 'dist', 'server-node.mjs')); }
|
||||
finally { fs.rmSync(l.base, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('without .source-path, or a checkout without node_modules, the adjacent bundle is unchanged', () => {
|
||||
for (const opts of [{ runtimeModules: false, sourcePath: false, sourceModules: true }, { runtimeModules: false, sourcePath: true, sourceModules: false }]) {
|
||||
const l = layout(opts);
|
||||
try { expect(resolveNodeServerScript('/$bunfs/root', l.exe)).toBe(path.join(l.runtime, 'browse', 'dist', 'server-node.mjs')); }
|
||||
finally { fs.rmSync(l.base, { recursive: true, force: true }); }
|
||||
}
|
||||
});
|
||||
|
||||
test('same build on both sides -> the checkout bundle; different builds -> refusal with the ./setup fix and anchor', () => {
|
||||
const { BROWSE_VERSION_SKEW_ANCHOR } = require('../src/cli');
|
||||
const l = layout({ runtimeModules: false, sourcePath: true, sourceModules: true });
|
||||
const version = (root: string, v: string) => fs.writeFileSync(path.join(root, 'browse', 'dist', '.version'), `${v}\n`);
|
||||
try {
|
||||
version(l.runtime, 'a'.repeat(40));
|
||||
version(l.source, 'a'.repeat(40));
|
||||
expect(resolveNodeServerScript('/$bunfs/root', l.exe)).toBe(path.join(l.source, 'browse', 'dist', 'server-node.mjs'));
|
||||
version(l.source, 'b'.repeat(40));
|
||||
let message = '';
|
||||
try { resolveNodeServerScript('/$bunfs/root', l.exe); } catch (err: any) { message = err.message; }
|
||||
expect(message).toContain(`build ${'a'.repeat(12)}`);
|
||||
expect(message).toContain(`build ${'b'.repeat(12)}`);
|
||||
expect(message).toContain('are from different builds, so the server was not started');
|
||||
expect(message).toContain(`Fix: cd "${l.source}" && ./setup`);
|
||||
expect(message).toContain(BROWSE_VERSION_SKEW_ANCHOR);
|
||||
expect(BROWSE_VERSION_SKEW_ANCHOR).toBe('https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#browse-runtime-version-skew');
|
||||
// resolveServerLaunch surfaces the same refusal on Windows.
|
||||
expect(() => require('../src/cli').resolveServerLaunch('win32', {}, '/$bunfs/root', l.exe)).toThrow('different builds');
|
||||
} finally { fs.rmSync(l.base, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('an unversioned build on either side is not called skew', () => {
|
||||
const l = layout({ runtimeModules: false, sourcePath: true, sourceModules: true });
|
||||
try {
|
||||
fs.writeFileSync(path.join(l.runtime, 'browse', 'dist', '.version'), 'abc\n');
|
||||
expect(resolveNodeServerScript('/$bunfs/root', l.exe)).toBe(path.join(l.source, 'browse', 'dist', 'server-node.mjs'));
|
||||
} finally { fs.rmSync(l.base, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('setup records the source checkout in every runtime root it links', () => {
|
||||
const setup = fs.readFileSync(path.resolve(__dirname, '../../setup'), 'utf8');
|
||||
const fn = setup.slice(setup.indexOf('_link_runtime_dists() {'), setup.indexOf('\n}\n', setup.indexOf('_link_runtime_dists() {')));
|
||||
expect(fn).toContain(`printf '%s\\n' "$1" > "$2/.source-path"`);
|
||||
});
|
||||
});
|
||||
@@ -225,6 +225,12 @@ export const KNOWN_WINDOWS_INCOMPATIBLE: Array<{ file: string; reason: string }>
|
||||
// pattern hit is a false positive — the point of these files is Windows
|
||||
// coverage, so auto-excluding them defeats the regression tests they carry.
|
||||
export const KNOWN_WINDOWS_SAFE: Array<{ file: string; reason: string }> = [
|
||||
// Named Windows coverage for fixes whose bug only bites on Windows: kept in
|
||||
// the curated lane even if a future edit trips a content pattern.
|
||||
{ file: 'browse/test/cli-chain-stdin.test.ts', reason: 'piped-stdin browse chain against a stub daemon, and no-flow usage errors before any daemon boots; spawns the CLI through Bun argv' },
|
||||
{ file: 'browse/test/runtime-root-server-bundle.test.ts', reason: 'a runtime root copy without node_modules resolves the checkout bundle via .source-path and refuses on version skew; os.tmpdir layouts only' },
|
||||
{ file: 'test/cso-windows-docker.test.ts', reason: '/cso docker.exe discovery under known-folder roots (real path, no reparse points) and the native-transport-unsupported outcome; pure win32 path logic' },
|
||||
{ file: 'test/copilot-windows-bash.test.ts', reason: 'the Copilot glossary line that runs bash blocks in Git for Windows Bash; reads host config only' },
|
||||
{ file: 'test/ship-hook-windows-paths.test.ts', reason: 'runs bin/ helpers through explicit bash and Bun argv with forward-slash paths; never executes a shebang; the path-spelling simulation is skipIf win32' },
|
||||
{ file: 'test/state-root-parity.test.ts', reason: 'runs the bash twin and lib/state-root.ts over an env table with PATH empty; no shebang execution, raw-string comparison is platform-neutral' },
|
||||
{ file: 'test/generator-eexist.test.ts',
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copilot CLI on Windows ran gstack's extensionless helpers straight from
|
||||
* PowerShell. The Copilot tool glossary (rendered into every Copilot skill)
|
||||
* names Git for Windows Bash by full path. Kept out of host-config.test.ts,
|
||||
* which the curated Windows lane excludes, so Windows runs this check.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
|
||||
describe('#3047: Copilot on Windows runs bash blocks in Git for Windows Bash', () => {
|
||||
test('the Copilot tool glossary names Git Bash by full path and forbids PowerShell translation', async () => {
|
||||
const { default: copilot } = await import('../hosts/copilot');
|
||||
const glossary = JSON.stringify(copilot.toolRewrites);
|
||||
expect(glossary).toContain('Git for Windows Bash by its full path');
|
||||
expect(glossary).toContain('bare `bash` can start WSL');
|
||||
expect(glossary).toContain('Never translate a block into PowerShell');
|
||||
});
|
||||
});
|
||||
@@ -790,13 +790,3 @@ describe('host renders name the host\'s own tools and identities (#2626, #2015,
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('#3047: Copilot on Windows runs bash blocks in Git for Windows Bash', () => {
|
||||
test('the Copilot tool glossary names Git Bash by full path and forbids PowerShell translation', async () => {
|
||||
const { default: copilot } = await import('../hosts/copilot');
|
||||
const glossary = JSON.stringify(copilot.toolRewrites);
|
||||
expect(glossary).toContain('Git for Windows Bash by its full path');
|
||||
expect(glossary).toContain('bare `bash` can start WSL');
|
||||
expect(glossary).toContain('Never translate a block into PowerShell');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user