fix(windows): repair what running the node-pty suites on Windows exposed

Putting these files on the Windows job turned four assertions red on the first
run. Three of them were in tests that carried `skipIf(platform !== 'win32')` and
had therefore never executed anywhere, on any branch.

- `writeFakeElectronRebuild` emitted the `windows-process-tree` addon a real
  rebuild leaves but never node-pty's, so every Windows test of the rebuild path
  ran against a tree no real rebuild can produce: node-pty "rebuilt" with nothing
  in `build/Release`. The new same-host check reads that state correctly and said
  so. The fake rebuild now writes `build/Release/conpty.node` when it was asked
  to rebuild node-pty for win32, with the marker and the target machine.
- `mkTempProject` never staged `windows-process-tree-creation-time.cjs`. The
  rebuild script reaches it through `projectRequire`, which resolves against the
  project root, so the module walker cannot follow it and must not try. Staged by
  name, with a comment saying which of the two it is. Without it the
  windows-process-tree probe failed to load its own checker and the module joined
  `modulesToRebuild`, which is the second and third red assertion.
- the two `nodePtyAddonPath` cases compared against a literal POSIX string.
  `resolve` returns a drive letter and backslashes on Windows, so they could only
  ever pass off it. Built from segments now, which still pins the `..` traversal
  that is the point of the test.

Verified on macOS: ensure-native-runtime-job-ownership,
verify-packaged-node-pty-job-ownership, windows-pe-machine,
script-module-dependencies, rebuild-native-deps-node-pty, rebuild-native-deps,
rebuild-native-deps-windows-process-tree, ensure-native-runtime -- 109 passed, 6
skipped. The 6 are the Windows-gated rebuild tests, which is the job this change
is aimed at; Windows CI is the arbiter.
This commit is contained in:
Neil
2026-09-15 22:51:07 -07:00
parent 2d623d704f
commit b5ee1b09df
2 changed files with 51 additions and 17 deletions
@@ -1,7 +1,7 @@
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { dirname, join, resolve } from 'node:path'
import { describe, expect, it, vi } from 'vitest'
import { peImage } from './windows-pe-image-fixture.mjs'
@@ -152,24 +152,26 @@ describe('assertNodePtyJobOwnership', () => {
})
describe('nodePtyAddonPath', () => {
// Built from segments rather than a POSIX string: on Windows `resolve` returns
// a drive letter and backslashes, so a literal only ever passed off Windows.
it('resolves the addon against node-pty lib, which is the only base callers share', () => {
expect(
nodePtyAddonPath(
'/app/node_modules/node-pty/lib/utils.js',
resolve('/app/node_modules/node-pty/lib/utils.js'),
{ dir: '../build/Release/' },
'conpty'
)
).toBe('/app/node_modules/node-pty/build/Release/conpty.node')
).toBe(join(resolve('/app/node_modules/node-pty'), 'build', 'Release', 'conpty.node'))
})
it('handles the bundled layout, where the addon sits beside lib', () => {
expect(
nodePtyAddonPath(
'/app/resources/node-pty/lib/utils.js',
resolve('/app/resources/node-pty/lib/utils.js'),
{ dir: './build/Release/' },
'conpty'
)
).toBe('/app/resources/node-pty/lib/build/Release/conpty.node')
).toBe(join(resolve('/app/resources/node-pty/lib'), 'build', 'Release', 'conpty.node'))
})
})
@@ -14,6 +14,16 @@ import { fileURLToPath } from 'node:url'
import { copyScriptWithLocalModules } from './script-module-dependencies.mjs'
import { peImage } from './windows-pe-image-fixture.mjs'
/**
* The wide literal `usesCygwinRuntime` holds, as it sits in a real addon. A
* fixture addon without it is a build that predates the MSYS breakaway denial,
* which is what these tests need to be able to represent.
*
* Taken from the gate itself: a re-typed copy agrees with a stale gate by
* construction, which is the one thing these fixtures must not do.
*/
const { CYGWIN_BREAKAWAY_MARKER } = createRequire(import.meta.url)('./node-pty-job-ownership.cjs')
const sourceScriptPath = fileURLToPath(new URL('./rebuild-native-deps.mjs', import.meta.url))
const sourceInstallScriptPath = fileURLToPath(
new URL('./install-electron-package-binary.mjs', import.meta.url)
@@ -24,6 +34,11 @@ const sourceNodePtyJobOwnershipPath = fileURLToPath(
const sourceWindowsProcessTreeGypRebuildPath = fileURLToPath(
new URL('./windows-process-tree-gyp-rebuild.mjs', import.meta.url)
)
// Reached through projectRequire, so the module walker cannot see it: that
// specifier resolves against the project root, not against the script.
const sourceWindowsProcessTreeCreationTimePath = fileURLToPath(
new URL('./windows-process-tree-creation-time.cjs', import.meta.url)
)
const sourceWindowsProcessTreePatchPath = fileURLToPath(
new URL('../patches/@vscode__windows-process-tree@0.8.0.patch', import.meta.url)
)
@@ -93,6 +108,10 @@ export function mkTempProject() {
copyFileSync(sourceScriptPath, join(projectDir, 'config', 'scripts', 'rebuild-native-deps.mjs'))
copyScriptWithLocalModules(sourceInstallScriptPath, join(projectDir, 'config', 'scripts'))
copyScriptWithLocalModules(sourceNodePtyJobOwnershipPath, join(projectDir, 'config', 'scripts'))
copyFileSync(
sourceWindowsProcessTreeCreationTimePath,
join(projectDir, 'config', 'scripts', 'windows-process-tree-creation-time.cjs')
)
copyFileSync(
sourceWindowsProcessTreeGypRebuildPath,
join(projectDir, 'config', 'scripts', 'windows-process-tree-gyp-rebuild.mjs')
@@ -224,10 +243,33 @@ const FAKE_ADDON_BYTES = {
* because "produced the upstream reader" and "produced nothing" are both real
* outcomes that assertion has to tell apart.
*/
/** What a real node-gyp Windows build leaves behind, per target arch. */
const FAKE_CONPTY_BY_ARCH = Object.fromEntries(
['x64', 'arm64'].map((arch) => [
arch,
Buffer.concat([peImage({ arch }), CYGWIN_BREAKAWAY_MARKER]).toString('base64')
])
)
export function writeFakeElectronRebuild(projectDir, { logPathEnv = null, addon = 'clean' } = {}) {
const rebuildDir = join(projectDir, 'node_modules', '@electron', 'rebuild')
mkdirSync(rebuildDir, { recursive: true })
writeFileSync(join(rebuildDir, 'package.json'), JSON.stringify({ type: 'module' }))
// A real Windows rebuild leaves conpty.node in build/Release; a fake one that
// does not makes the gates downstream see a tree that cannot happen.
const emitNodePty = `
const nodePtyDir = join('node_modules', 'node-pty')
if (
options.platform === 'win32' &&
(options.onlyModules ?? []).includes('node-pty') &&
existsSync(join(nodePtyDir, 'lib', 'utils.js'))
) {
mkdirSync(join(nodePtyDir, 'build', 'Release'), { recursive: true })
writeFileSync(
join(nodePtyDir, 'build', 'Release', 'conpty.node'),
Buffer.from(${JSON.stringify(FAKE_CONPTY_BY_ARCH)}[options.arch] ?? '', 'base64')
)
}`
const emitAddon =
addon === 'none'
? ''
@@ -239,7 +281,7 @@ export function writeFakeElectronRebuild(projectDir, { logPathEnv = null, addon
join(packageDir, 'build', 'Release', 'windows_process_tree.node'),
${JSON.stringify(FAKE_ADDON_BYTES[addon])}
)
}`
}${emitNodePty}`
const emitImports =
addon === 'none'
? ''
@@ -269,7 +311,7 @@ export async function rebuild(options) {${emitAddon}
}
`
: `${emitImports}
export async function rebuild() {${emitAddon}
export async function rebuild(options) {${emitAddon}
}
`
)
@@ -337,16 +379,6 @@ export function writeFakeNodePtyConptyPayload(
writeFileSync(join(sourceDir, 'OpenConsole.exe'), `OpenConsole.exe ${arch}`)
}
/**
* The wide literal `usesCygwinRuntime` holds, as it sits in a real addon. A
* fixture addon without it is a build that predates the MSYS breakaway denial,
* which is what these tests need to be able to represent.
*
* Taken from the gate itself: a re-typed copy agrees with a stale gate by
* construction, which is the one thing these fixtures must not do.
*/
const { CYGWIN_BREAKAWAY_MARKER } = createRequire(import.meta.url)('./node-pty-job-ownership.cjs')
function writeFakeNodePtyAddon(nodePtyDir, nativeDir, { cygwinBreakawayDenied }) {
const addonDir = resolve(join(nodePtyDir, 'lib'), nativeDir)
mkdirSync(addonDir, { recursive: true })