History

test(server): skip posix executable fixtures on a Windows host (#9565)

Julius Marminge committed 2 months ago

Commit 47010411 parent cc607534 files · +391 −347Browse files

Unified · Split

4 files
  1. apps/server/src/process/externalLauncher.test.ts +314 −282
  2. apps/server/src/provider/AntigravityInstallation.test.ts +63 −59
  3. apps/server/src/provider/providerMaintenance.test.ts +8 −4
  4. apps/server/src/resourceTelemetry/ResourceMonitorBinary.test.ts +6 −2
apps/server/src/process/externalLauncher.test.ts+314−282
@@ -20,6 +20,12 @@ import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; · 19 unmodified lines
2020 import { SpawnExecutableResolution } from "@t3tools/shared/shell";
2121 import * as ExternalLauncher from "./externalLauncher.ts";
2222
23+// Tests below write `#!/bin/sh` stubs into a real temp dir and hand that
24+// directory to a posix-mocked resolver as PATH. On a Windows host the temp
25+// path carries a drive letter, so the posix `:` split shatters it; there is
26+// no posix executable to find there anyway.
27+const windowsHost = HostProcessPlatform.defaultValue() === "win32";
28+
2329 interface MockSpawnResult {
2430 readonly exitCode?: number;
2531 readonly stdout?: string;
@@ -153,7 +159,7 @@ it.effect("launches an installed editor with platform-safe arguments", () => · 127 unmodified lines
153159 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
154160 );
155161
156−it.effect("reveals a file in Finder with open -R on macOS", () =>
162+it.effect.skipIf(windowsHost)("reveals a file in Finder with open -R on macOS", () =>
157163 Effect.gen(function* () {
158164 const fileSystem = yield* FileSystem.FileSystem;
159165 const path = yield* Path.Path;
@@ -336,196 +342,208 @@ it.effect("does not advertise reveal on Windows when PowerShell is missing", () · 176 unmodified lines
336342 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
337343 );
338344
339−it.effect("reveals a WSL file in Windows File Explorer through its UNC path", () =>
340− Effect.gen(function* () {
341− const fileSystem = yield* FileSystem.FileSystem;
342− const path = yield* Path.Path;
343− const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
344− for (const name of ["explorer.exe", "powershell.exe", "xdg-open"]) {
345− const filePath = path.join(binDir, name);
346− yield* fileSystem.writeFileString(filePath, "#!/bin/sh\n");
347− yield* fileSystem.chmod(filePath, 0o755);
348− }
345+it.effect.skipIf(windowsHost)(
346+ "reveals a WSL file in Windows File Explorer through its UNC path",
347+ () =>
348+ Effect.gen(function* () {
349+ const fileSystem = yield* FileSystem.FileSystem;
350+ const path = yield* Path.Path;
351+ const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
352+ for (const name of ["explorer.exe", "powershell.exe", "xdg-open"]) {
353+ const filePath = path.join(binDir, name);
354+ yield* fileSystem.writeFileString(filePath, "#!/bin/sh\n");
355+ yield* fileSystem.chmod(filePath, 0o755);
356+ }
349357
350− let spawned: ChildProcess.StandardCommand | undefined;
351− const result = yield* Effect.gen(function* () {
352− const launcher = yield* ExternalLauncher.ExternalLauncher;
353− const kind = yield* launcher.resolveFileManagerRevealKind();
354− const editors = yield* launcher.resolveAvailableEditors();
355− yield* launcher.launchEditor({
356− editor: "file-manager",
357− cwd: "/home/t3/workspace/media/clip.mp4",
358− reveal: true,
359− });
360− return { kind, editors };
361− }).pipe(
362− Effect.provide(
363− testLayer({
364− platform: "linux",
365− env: {
366− PATH: binDir,
367− WSL_DISTRO_NAME: "Ubuntu-24.04",
368− WSL_INTEROP: "/run/WSL/1_interop",
369− },
370− onSpawn: (command) => {
371− spawned = command;
372− },
373− }),
374− ),
375− );
358+ let spawned: ChildProcess.StandardCommand | undefined;
359+ const result = yield* Effect.gen(function* () {
360+ const launcher = yield* ExternalLauncher.ExternalLauncher;
361+ const kind = yield* launcher.resolveFileManagerRevealKind();
362+ const editors = yield* launcher.resolveAvailableEditors();
363+ yield* launcher.launchEditor({
364+ editor: "file-manager",
365+ cwd: "/home/t3/workspace/media/clip.mp4",
366+ reveal: true,
367+ });
368+ return { kind, editors };
369+ }).pipe(
370+ Effect.provide(
371+ testLayer({
372+ platform: "linux",
373+ env: {
374+ PATH: binDir,
375+ WSL_DISTRO_NAME: "Ubuntu-24.04",
376+ WSL_INTEROP: "/run/WSL/1_interop",
377+ },
378+ onSpawn: (command) => {
379+ spawned = command;
380+ },
381+ }),
382+ ),
383+ );
376384
377− assert.equal(result.kind, "file-explorer");
378− assert.equal(result.editors.includes("file-manager"), true);
379− assert.ok(spawned);
380− // The reveal routes through interop PowerShell so Explorer receives its
381− // raw `/select,"<path>"` switch even for spaced paths.
382− assert.equal(spawned.command, "powershell.exe");
383− const encodedCommand = spawned.args[spawned.args.length - 1] ?? "";
384− const decodedCommand = Buffer.from(encodedCommand, "base64").toString("utf16le");
385− assert.equal(
386− decodedCommand,
387− "$ProgressPreference = 'SilentlyContinue'; Start-Process 'explorer.exe' -ArgumentList ('/select,\"' + '\\\\wsl.localhost\\Ubuntu-24.04\\home\\t3\\workspace\\media\\clip.mp4' + '\"')",
388− );
389− }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
385+ assert.equal(result.kind, "file-explorer");
386+ assert.equal(result.editors.includes("file-manager"), true);
387+ assert.ok(spawned);
388+ // The reveal routes through interop PowerShell so Explorer receives its
389+ // raw `/select,"<path>"` switch even for spaced paths.
390+ assert.equal(spawned.command, "powershell.exe");
391+ const encodedCommand = spawned.args[spawned.args.length - 1] ?? "";
392+ const decodedCommand = Buffer.from(encodedCommand, "base64").toString("utf16le");
393+ assert.equal(
394+ decodedCommand,
395+ "$ProgressPreference = 'SilentlyContinue'; Start-Process 'explorer.exe' -ArgumentList ('/select,\"' + '\\\\wsl.localhost\\Ubuntu-24.04\\home\\t3\\workspace\\media\\clip.mp4' + '\"')",
396+ );
397+ }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
390398 );
391399
392−it.effect("does not advertise reveal from WSL when interop PowerShell is missing", () =>
393− Effect.gen(function* () {
394− const fileSystem = yield* FileSystem.FileSystem;
395− const path = yield* Path.Path;
396− const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
397− const explorerPath = path.join(binDir, "explorer.exe");
398− yield* fileSystem.writeFileString(explorerPath, "");
399− yield* fileSystem.chmod(explorerPath, 0o755);
400+it.effect.skipIf(windowsHost)(
401+ "does not advertise reveal from WSL when interop PowerShell is missing",
402+ () =>
403+ Effect.gen(function* () {
404+ const fileSystem = yield* FileSystem.FileSystem;
405+ const path = yield* Path.Path;
406+ const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
407+ const explorerPath = path.join(binDir, "explorer.exe");
408+ yield* fileSystem.writeFileString(explorerPath, "");
409+ yield* fileSystem.chmod(explorerPath, 0o755);
400410
401− const result = yield* Effect.gen(function* () {
402− const launcher = yield* ExternalLauncher.ExternalLauncher;
403− return {
404− kind: yield* launcher.resolveFileManagerRevealKind(),
405− editors: yield* launcher.resolveAvailableEditors(),
406− };
407− }).pipe(
408− Effect.provide(
409− testLayer({
410− platform: "linux",
411− env: {
412− PATH: binDir,
413− WSL_DISTRO_NAME: "Ubuntu-24.04",
414− WSL_INTEROP: "/run/WSL/1_interop",
415− },
416− }),
417− ),
418− );
411+ const result = yield* Effect.gen(function* () {
412+ const launcher = yield* ExternalLauncher.ExternalLauncher;
413+ return {
414+ kind: yield* launcher.resolveFileManagerRevealKind(),
415+ editors: yield* launcher.resolveAvailableEditors(),
416+ };
417+ }).pipe(
418+ Effect.provide(
419+ testLayer({
420+ platform: "linux",
421+ env: {
422+ PATH: binDir,
423+ WSL_DISTRO_NAME: "Ubuntu-24.04",
424+ WSL_INTEROP: "/run/WSL/1_interop",
425+ },
426+ }),
427+ ),
428+ );
419429
420− assert.equal(result.editors.includes("file-manager"), true);
421− assert.isUndefined(result.kind);
422− }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
430+ assert.equal(result.editors.includes("file-manager"), true);
431+ assert.isUndefined(result.kind);
432+ }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
423433 );
424434
425435 // When interop PowerShell is missing the capability advertises the Linux
426436 // "files" kind (or nothing), so the reveal must open the Linux file manager
427437 // the label promised even though plain open still prefers File Explorer.
428−it.effect("reveals through the Linux file manager when WSL lacks interop PowerShell", () =>
429− Effect.gen(function* () {
430− const fileSystem = yield* FileSystem.FileSystem;
431− const path = yield* Path.Path;
432− const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
433− for (const name of ["explorer.exe", "xdg-open", "xdg-mime"]) {
434− const filePath = path.join(binDir, name);
435− yield* fileSystem.writeFileString(filePath, "#!/bin/sh\n");
436− yield* fileSystem.chmod(filePath, 0o755);
437− }
438+it.effect.skipIf(windowsHost)(
439+ "reveals through the Linux file manager when WSL lacks interop PowerShell",
440+ () =>
441+ Effect.gen(function* () {
442+ const fileSystem = yield* FileSystem.FileSystem;
443+ const path = yield* Path.Path;
444+ const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
445+ for (const name of ["explorer.exe", "xdg-open", "xdg-mime"]) {
446+ const filePath = path.join(binDir, name);
447+ yield* fileSystem.writeFileString(filePath, "#!/bin/sh\n");
448+ yield* fileSystem.chmod(filePath, 0o755);
449+ }
438450
439− const spawnedCommands: ChildProcess.StandardCommand[] = [];
440− const kind = yield* Effect.gen(function* () {
441− const launcher = yield* ExternalLauncher.ExternalLauncher;
442− const revealKind = yield* launcher.resolveFileManagerRevealKind();
443− yield* launcher.launchEditor({
444− editor: "file-manager",
445− cwd: "/home/t3/workspace/media/clip.mp4",
446− reveal: true,
447− });
448− return revealKind;
449− }).pipe(
450− Effect.provide(
451− testLayer({
452− platform: "linux",
453− env: {
454− PATH: binDir,
455− WSL_DISTRO_NAME: "Ubuntu-24.04",
456− WSL_INTEROP: "/run/WSL/1_interop",
457− DISPLAY: ":0",
458− },
459− onSpawn: (command) => {
460− spawnedCommands.push(command);
461− },
462− spawnResult: (command) =>
463− command.command === "xdg-mime" ? { stdout: "org.gnome.Nautilus.desktop\n" } : undefined,
464− }),
465− ),
466− );
451+ const spawnedCommands: ChildProcess.StandardCommand[] = [];
452+ const kind = yield* Effect.gen(function* () {
453+ const launcher = yield* ExternalLauncher.ExternalLauncher;
454+ const revealKind = yield* launcher.resolveFileManagerRevealKind();
455+ yield* launcher.launchEditor({
456+ editor: "file-manager",
457+ cwd: "/home/t3/workspace/media/clip.mp4",
458+ reveal: true,
459+ });
460+ return revealKind;
461+ }).pipe(
462+ Effect.provide(
463+ testLayer({
464+ platform: "linux",
465+ env: {
466+ PATH: binDir,
467+ WSL_DISTRO_NAME: "Ubuntu-24.04",
468+ WSL_INTEROP: "/run/WSL/1_interop",
469+ DISPLAY: ":0",
470+ },
471+ onSpawn: (command) => {
472+ spawnedCommands.push(command);
473+ },
474+ spawnResult: (command) =>
475+ command.command === "xdg-mime"
476+ ? { stdout: "org.gnome.Nautilus.desktop\n" }
477+ : undefined,
478+ }),
479+ ),
480+ );
467481
468− assert.equal(kind, "files");
469− const launch = spawnedCommands.find((command) => command.command === "xdg-open");
470− assert.ok(launch);
471− assert.deepEqual(launch.args, ["/home/t3/workspace/media"]);
472− assert.isUndefined(spawnedCommands.find((command) => command.command === "explorer.exe"));
473− }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
482+ assert.equal(kind, "files");
483+ const launch = spawnedCommands.find((command) => command.command === "xdg-open");
484+ assert.ok(launch);
485+ assert.deepEqual(launch.args, ["/home/t3/workspace/media"]);
486+ assert.isUndefined(spawnedCommands.find((command) => command.command === "explorer.exe"));
487+ }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
474488 );
475489
476490 // Interop can exist without `explorer.exe` on PATH (appendWindowsPath=false)
477491 // while WSLg still provides a working Linux file manager; the host must keep
478492 // the Linux open/reveal path instead of losing the editor entirely.
479−it.effect("falls back to the Linux file manager when WSL lacks the Explorer bridge", () =>
480− Effect.gen(function* () {
481− const fileSystem = yield* FileSystem.FileSystem;
482− const path = yield* Path.Path;
483− const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
484− for (const name of ["xdg-open", "xdg-mime"]) {
485− const filePath = path.join(binDir, name);
486− yield* fileSystem.writeFileString(filePath, "#!/bin/sh\n");
487− yield* fileSystem.chmod(filePath, 0o755);
488− }
493+it.effect.skipIf(windowsHost)(
494+ "falls back to the Linux file manager when WSL lacks the Explorer bridge",
495+ () =>
496+ Effect.gen(function* () {
497+ const fileSystem = yield* FileSystem.FileSystem;
498+ const path = yield* Path.Path;
499+ const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
500+ for (const name of ["xdg-open", "xdg-mime"]) {
501+ const filePath = path.join(binDir, name);
502+ yield* fileSystem.writeFileString(filePath, "#!/bin/sh\n");
503+ yield* fileSystem.chmod(filePath, 0o755);
504+ }
489505
490− const spawnedCommands: ChildProcess.StandardCommand[] = [];
491− const result = yield* Effect.gen(function* () {
492− const launcher = yield* ExternalLauncher.ExternalLauncher;
493− const editors = yield* launcher.resolveAvailableEditors();
494− const kind = yield* launcher.resolveFileManagerRevealKind();
495− yield* launcher.launchEditor({
496− editor: "file-manager",
497− cwd: "/home/t3/workspace/media/clip.mp4",
498− reveal: true,
499− });
500− return { editors, kind };
501− }).pipe(
502− Effect.provide(
503− testLayer({
504− platform: "linux",
505− env: {
506− PATH: binDir,
507− WSL_DISTRO_NAME: "Ubuntu-24.04",
508− WSL_INTEROP: "/run/WSL/1_interop",
509− DISPLAY: ":0",
510− },
511− onSpawn: (command) => {
512− spawnedCommands.push(command);
513− },
514− spawnResult: (command) =>
515− command.command === "xdg-mime" ? { stdout: "org.gnome.Nautilus.desktop\n" } : undefined,
516− }),
517− ),
518− );
506+ const spawnedCommands: ChildProcess.StandardCommand[] = [];
507+ const result = yield* Effect.gen(function* () {
508+ const launcher = yield* ExternalLauncher.ExternalLauncher;
509+ const editors = yield* launcher.resolveAvailableEditors();
510+ const kind = yield* launcher.resolveFileManagerRevealKind();
511+ yield* launcher.launchEditor({
512+ editor: "file-manager",
513+ cwd: "/home/t3/workspace/media/clip.mp4",
514+ reveal: true,
515+ });
516+ return { editors, kind };
517+ }).pipe(
518+ Effect.provide(
519+ testLayer({
520+ platform: "linux",
521+ env: {
522+ PATH: binDir,
523+ WSL_DISTRO_NAME: "Ubuntu-24.04",
524+ WSL_INTEROP: "/run/WSL/1_interop",
525+ DISPLAY: ":0",
526+ },
527+ onSpawn: (command) => {
528+ spawnedCommands.push(command);
529+ },
530+ spawnResult: (command) =>
531+ command.command === "xdg-mime"
532+ ? { stdout: "org.gnome.Nautilus.desktop\n" }
533+ : undefined,
534+ }),
535+ ),
536+ );
519537
520− assert.equal(result.editors.includes("file-manager"), true);
521− assert.equal(result.kind, "files");
522− const launch = spawnedCommands.find((command) => command.command === "xdg-open");
523− assert.ok(launch);
524− assert.deepEqual(launch.args, ["/home/t3/workspace/media"]);
525− }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
538+ assert.equal(result.editors.includes("file-manager"), true);
539+ assert.equal(result.kind, "files");
540+ const launch = spawnedCommands.find((command) => command.command === "xdg-open");
541+ assert.ok(launch);
542+ assert.deepEqual(launch.args, ["/home/t3/workspace/media"]);
543+ }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
526544 );
527545
528−it.effect(
546+it.effect.skipIf(windowsHost)(
529547 "falls back to opening the containing directory for WSL paths Explorer cannot select",
530548 () =>
531549 Effect.gen(function* () {
@@ -570,7 +588,7 @@ it.effect( · 38 unmodified lines
570588 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
571589 );
572590
573−it.effect("reveals by opening the containing directory on Linux", () =>
591+it.effect.skipIf(windowsHost)("reveals by opening the containing directory on Linux", () =>
574592 Effect.gen(function* () {
575593 const fileSystem = yield* FileSystem.FileSystem;
576594 const path = yield* Path.Path;
@@ -609,127 +627,137 @@ it.effect("reveals by opening the containing directory on Linux", () => · 32 unmodified lines
609627 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
610628 );
611629
612−it.effect("does not advertise a Linux file manager without a graphical session", () =>
613− Effect.gen(function* () {
614− const fileSystem = yield* FileSystem.FileSystem;
615− const path = yield* Path.Path;
616− const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
617− const xdgOpenPath = path.join(binDir, "xdg-open");
618− yield* fileSystem.writeFileString(xdgOpenPath, "#!/bin/sh\n");
619− yield* fileSystem.chmod(xdgOpenPath, 0o755);
630+it.effect.skipIf(windowsHost)(
631+ "does not advertise a Linux file manager without a graphical session",
632+ () =>
633+ Effect.gen(function* () {
634+ const fileSystem = yield* FileSystem.FileSystem;
635+ const path = yield* Path.Path;
636+ const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
637+ const xdgOpenPath = path.join(binDir, "xdg-open");
638+ yield* fileSystem.writeFileString(xdgOpenPath, "#!/bin/sh\n");
639+ yield* fileSystem.chmod(xdgOpenPath, 0o755);
620640
621− const editors = yield* Effect.gen(function* () {
622− const launcher = yield* ExternalLauncher.ExternalLauncher;
623− return yield* launcher.resolveAvailableEditors();
624− }).pipe(Effect.provide(testLayer({ platform: "linux", env: { PATH: binDir } })));
641+ const editors = yield* Effect.gen(function* () {
642+ const launcher = yield* ExternalLauncher.ExternalLauncher;
643+ return yield* launcher.resolveAvailableEditors();
644+ }).pipe(Effect.provide(testLayer({ platform: "linux", env: { PATH: binDir } })));
625645
626− assert.equal(editors.includes("file-manager"), false);
627− }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
646+ assert.equal(editors.includes("file-manager"), false);
647+ }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
628648 );
629649
630−it.effect("advertises a Linux file manager when a directory handler is installed", () =>
631− Effect.gen(function* () {
632− const fileSystem = yield* FileSystem.FileSystem;
633− const path = yield* Path.Path;
634− const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
635− for (const name of ["xdg-open", "xdg-mime"]) {
636− const filePath = path.join(binDir, name);
637− yield* fileSystem.writeFileString(filePath, "#!/bin/sh\n");
638− yield* fileSystem.chmod(filePath, 0o755);
639− }
650+it.effect.skipIf(windowsHost)(
651+ "advertises a Linux file manager when a directory handler is installed",
652+ () =>
653+ Effect.gen(function* () {
654+ const fileSystem = yield* FileSystem.FileSystem;
655+ const path = yield* Path.Path;
656+ const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
657+ for (const name of ["xdg-open", "xdg-mime"]) {
658+ const filePath = path.join(binDir, name);
659+ yield* fileSystem.writeFileString(filePath, "#!/bin/sh\n");
660+ yield* fileSystem.chmod(filePath, 0o755);
661+ }
640662
641− let probe: ChildProcess.StandardCommand | undefined;
642− const editors = yield* Effect.gen(function* () {
643− const launcher = yield* ExternalLauncher.ExternalLauncher;
644− return yield* launcher.resolveAvailableEditors();
645− }).pipe(
646− Effect.provide(
647− testLayer({
648− platform: "linux",
649− env: { PATH: binDir, DISPLAY: ":0" },
650− onSpawn: (command) => {
651− probe = command;
652− },
653− spawnResult: (command) =>
654− command.command === "xdg-mime" ? { stdout: "org.gnome.Nautilus.desktop\n" } : undefined,
655− }),
656− ),
657− );
663+ let probe: ChildProcess.StandardCommand | undefined;
664+ const editors = yield* Effect.gen(function* () {
665+ const launcher = yield* ExternalLauncher.ExternalLauncher;
666+ return yield* launcher.resolveAvailableEditors();
667+ }).pipe(
668+ Effect.provide(
669+ testLayer({
670+ platform: "linux",
671+ env: { PATH: binDir, DISPLAY: ":0" },
672+ onSpawn: (command) => {
673+ probe = command;
674+ },
675+ spawnResult: (command) =>
676+ command.command === "xdg-mime"
677+ ? { stdout: "org.gnome.Nautilus.desktop\n" }
678+ : undefined,
679+ }),
680+ ),
681+ );
658682
659− assert.equal(editors.includes("file-manager"), true);
660− assert.ok(probe);
661− assert.equal(probe.command, "xdg-mime");
662− assert.deepEqual(probe.args, ["query", "default", "inode/directory"]);
663− }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
683+ assert.equal(editors.includes("file-manager"), true);
684+ assert.ok(probe);
685+ assert.equal(probe.command, "xdg-mime");
686+ assert.deepEqual(probe.args, ["query", "default", "inode/directory"]);
687+ }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
664688 );
665689
666690 // `xdg-open` with a display variable but no `inode/directory` handler exits
667691 // nonzero after the launch has already detached: without this gate the server
668692 // advertises a reveal that is a silent no-op.
669−it.effect("does not advertise a Linux file manager without a directory handler", () =>
670− Effect.gen(function* () {
671− const fileSystem = yield* FileSystem.FileSystem;
672− const path = yield* Path.Path;
673− const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
674− for (const name of ["xdg-open", "xdg-mime"]) {
675− const filePath = path.join(binDir, name);
676− yield* fileSystem.writeFileString(filePath, "#!/bin/sh\n");
677− yield* fileSystem.chmod(filePath, 0o755);
678− }
693+it.effect.skipIf(windowsHost)(
694+ "does not advertise a Linux file manager without a directory handler",
695+ () =>
696+ Effect.gen(function* () {
697+ const fileSystem = yield* FileSystem.FileSystem;
698+ const path = yield* Path.Path;
699+ const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
700+ for (const name of ["xdg-open", "xdg-mime"]) {
701+ const filePath = path.join(binDir, name);
702+ yield* fileSystem.writeFileString(filePath, "#!/bin/sh\n");
703+ yield* fileSystem.chmod(filePath, 0o755);
704+ }
679705
680− const editors = yield* Effect.gen(function* () {
681− const launcher = yield* ExternalLauncher.ExternalLauncher;
682− return yield* launcher.resolveAvailableEditors();
683− }).pipe(
684− Effect.provide(
685− testLayer({
686− platform: "linux",
687− env: { PATH: binDir, DISPLAY: ":0" },
688− spawnResult: (command) => (command.command === "xdg-mime" ? { stdout: "" } : undefined),
689− }),
690− ),
691− );
706+ const editors = yield* Effect.gen(function* () {
707+ const launcher = yield* ExternalLauncher.ExternalLauncher;
708+ return yield* launcher.resolveAvailableEditors();
709+ }).pipe(
710+ Effect.provide(
711+ testLayer({
712+ platform: "linux",
713+ env: { PATH: binDir, DISPLAY: ":0" },
714+ spawnResult: (command) => (command.command === "xdg-mime" ? { stdout: "" } : undefined),
715+ }),
716+ ),
717+ );
692718
693− assert.equal(editors.includes("file-manager"), false);
694− }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
719+ assert.equal(editors.includes("file-manager"), false);
720+ }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
695721 );
696722
697−it.effect("does not advertise a Linux file manager when the handler query fails", () =>
698− Effect.gen(function* () {
699− const fileSystem = yield* FileSystem.FileSystem;
700− const path = yield* Path.Path;
701− const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
702− for (const name of ["xdg-open", "xdg-mime"]) {
703− const filePath = path.join(binDir, name);
704− yield* fileSystem.writeFileString(filePath, "#!/bin/sh\n");
705− yield* fileSystem.chmod(filePath, 0o755);
706− }
723+it.effect.skipIf(windowsHost)(
724+ "does not advertise a Linux file manager when the handler query fails",
725+ () =>
726+ Effect.gen(function* () {
727+ const fileSystem = yield* FileSystem.FileSystem;
728+ const path = yield* Path.Path;
729+ const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
730+ for (const name of ["xdg-open", "xdg-mime"]) {
731+ const filePath = path.join(binDir, name);
732+ yield* fileSystem.writeFileString(filePath, "#!/bin/sh\n");
733+ yield* fileSystem.chmod(filePath, 0o755);
734+ }
707735
708− const editors = yield* Effect.gen(function* () {
709− const launcher = yield* ExternalLauncher.ExternalLauncher;
710− return yield* launcher.resolveAvailableEditors();
711− }).pipe(
712− Effect.provide(
713− testLayer({
714− platform: "linux",
715− env: { PATH: binDir, DISPLAY: ":0" },
716− spawnResult: (command) =>
717− command.command === "xdg-mime"
718− ? { exitCode: 47, stdout: "org.gnome.Nautilus.desktop\n" }
719− : undefined,
720− }),
721− ),
722− );
736+ const editors = yield* Effect.gen(function* () {
737+ const launcher = yield* ExternalLauncher.ExternalLauncher;
738+ return yield* launcher.resolveAvailableEditors();
739+ }).pipe(
740+ Effect.provide(
741+ testLayer({
742+ platform: "linux",
743+ env: { PATH: binDir, DISPLAY: ":0" },
744+ spawnResult: (command) =>
745+ command.command === "xdg-mime"
746+ ? { exitCode: 47, stdout: "org.gnome.Nautilus.desktop\n" }
747+ : undefined,
748+ }),
749+ ),
750+ );
723751
724− assert.equal(editors.includes("file-manager"), false);
725− }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
752+ assert.equal(editors.includes("file-manager"), false);
753+ }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
726754 );
727755
728756 // The handler probe carries its own timeout because the editor scan's outer
729757 // timeout in server.getConfig degrades to an EMPTY editor list: a wedged
730758 // xdg-mime must cost only the file manager, never the other editors. Runs on
731759 // the live clock so the probe's real timeout fires.
732−it.live("a stalled handler probe drops only the file manager", () =>
760+it.live.skipIf(windowsHost)("a stalled handler probe drops only the file manager", () =>
733761 Effect.gen(function* () {
734762 const fileSystem = yield* FileSystem.FileSystem;
735763 const path = yield* Path.Path;
@@ -758,22 +786,26 @@ it.live("a stalled handler probe drops only the file manager", () => · 22 unmodified lines
758786 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
759787 );
760788
761−it.effect("does not advertise a Linux file manager when xdg-mime is missing", () =>
762− Effect.gen(function* () {
763− const fileSystem = yield* FileSystem.FileSystem;
764− const path = yield* Path.Path;
765− const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
766− const xdgOpenPath = path.join(binDir, "xdg-open");
767− yield* fileSystem.writeFileString(xdgOpenPath, "#!/bin/sh\n");
768− yield* fileSystem.chmod(xdgOpenPath, 0o755);
789+it.effect.skipIf(windowsHost)(
790+ "does not advertise a Linux file manager when xdg-mime is missing",
791+ () =>
792+ Effect.gen(function* () {
793+ const fileSystem = yield* FileSystem.FileSystem;
794+ const path = yield* Path.Path;
795+ const binDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-editors-" });
796+ const xdgOpenPath = path.join(binDir, "xdg-open");
797+ yield* fileSystem.writeFileString(xdgOpenPath, "#!/bin/sh\n");
798+ yield* fileSystem.chmod(xdgOpenPath, 0o755);
769799
770− const editors = yield* Effect.gen(function* () {
771− const launcher = yield* ExternalLauncher.ExternalLauncher;
772− return yield* launcher.resolveAvailableEditors();
773− }).pipe(Effect.provide(testLayer({ platform: "linux", env: { PATH: binDir, DISPLAY: ":0" } })));
800+ const editors = yield* Effect.gen(function* () {
801+ const launcher = yield* ExternalLauncher.ExternalLauncher;
802+ return yield* launcher.resolveAvailableEditors();
803+ }).pipe(
804+ Effect.provide(testLayer({ platform: "linux", env: { PATH: binDir, DISPLAY: ":0" } })),
805+ );
774806
775− assert.equal(editors.includes("file-manager"), false);
776− }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
807+ assert.equal(editors.includes("file-manager"), false);
808+ }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
777809 );
778810
779811 it.effect("discovers editors through the service API", () =>
apps/server/src/provider/AntigravityInstallation.test.ts+63−59
@@ -681,65 +681,69 @@ it.layer(NodeServices.layer)("Antigravity installation", (it) => { · 680 unmodified lines
681681 }),
682682 );
683683
684− it.effect("honors explicit paths and reports invalid overrides without falling back", () =>
685− Effect.gen(function* () {
686− const fs = yield* FileSystem.FileSystem;
687− const path = yield* Path.Path;
688− const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-agy-path-test-" });
689− const externalDirectory = path.join(baseDir, "external");
690− const externalExecutable = path.join(externalDirectory, "agy_acp_server.par");
691− const externalHarness = path.join(externalDirectory, "localharness_external");
692− yield* fs.makeDirectory(externalDirectory);
693− yield* fs.writeFileString(externalExecutable, "external server", { mode: 0o755 });
694− yield* fs.writeFileString(externalHarness, "external harness", { mode: 0o755 });
695− const { installation } = yield* makeHarness({
696− baseDir,
697− path: externalDirectory,
698− previous: true,
699− });
700− yield* expectPreviousRelease(installation);
701− expect(yield* installation.resolve(undefined, { PATH: externalDirectory })).toMatchObject({
702− source: "managed",
703− version: previousVersion,
704− });
705− expect(yield* installation.resolve(externalExecutable)).toMatchObject({
706− executablePath: externalExecutable,
707− source: "override",
708− managedVersionDirectory: null,
709− });
710− expect(yield* installation.resolve("agy_acp_server.par")).toMatchObject({
711− source: "override",
712− });
713− yield* fs.remove(externalHarness);
714− expect(yield* installation.resolve(externalExecutable).pipe(Effect.flip)).toMatchObject({
715− operation: "resolve",
716− });
717− expect(
718− yield* installation.resolve(path.join(baseDir, "missing")).pipe(Effect.flip),
719− ).toMatchObject({
720− operation: "resolve",
721− });
722− yield* expectPreviousRelease(installation);
723− yield* fs.writeFileString(externalHarness, "external harness", { mode: 0o755 });
724− yield* installation.remove();
725− expect(yield* installation.resolve()).toMatchObject({
726− source: "path",
727− executablePath: externalExecutable,
728− });
729− const isolated = yield* makeHarness({ baseDir });
730− expect(yield* isolated.installation.resolve().pipe(Effect.flip)).toMatchObject({
731− operation: "resolve",
732− });
733− expect(
734− yield* isolated.installation.resolve(undefined, { PATH: externalDirectory }),
735− ).toMatchObject({
736− source: "path",
737− executablePath: externalExecutable,
738− });
739− expect(
740− yield* isolated.installation.resolve("agy_acp_server.par", { PATH: externalDirectory }),
741− ).toMatchObject({ source: "override", executablePath: externalExecutable });
742− }),
684+ // Real posix executables in a real temp dir, resolved by a linux-mocked
685+ // PATH walk; a Windows temp path cannot be split on `:`.
686+ it.effect.skipIf(HostProcessPlatform.defaultValue() === "win32")(
687+ "honors explicit paths and reports invalid overrides without falling back",
688+ () =>
689+ Effect.gen(function* () {
690+ const fs = yield* FileSystem.FileSystem;
691+ const path = yield* Path.Path;
692+ const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-agy-path-test-" });
693+ const externalDirectory = path.join(baseDir, "external");
694+ const externalExecutable = path.join(externalDirectory, "agy_acp_server.par");
695+ const externalHarness = path.join(externalDirectory, "localharness_external");
696+ yield* fs.makeDirectory(externalDirectory);
697+ yield* fs.writeFileString(externalExecutable, "external server", { mode: 0o755 });
698+ yield* fs.writeFileString(externalHarness, "external harness", { mode: 0o755 });
699+ const { installation } = yield* makeHarness({
700+ baseDir,
701+ path: externalDirectory,
702+ previous: true,
703+ });
704+ yield* expectPreviousRelease(installation);
705+ expect(yield* installation.resolve(undefined, { PATH: externalDirectory })).toMatchObject({
706+ source: "managed",
707+ version: previousVersion,
708+ });
709+ expect(yield* installation.resolve(externalExecutable)).toMatchObject({
710+ executablePath: externalExecutable,
711+ source: "override",
712+ managedVersionDirectory: null,
713+ });
714+ expect(yield* installation.resolve("agy_acp_server.par")).toMatchObject({
715+ source: "override",
716+ });
717+ yield* fs.remove(externalHarness);
718+ expect(yield* installation.resolve(externalExecutable).pipe(Effect.flip)).toMatchObject({
719+ operation: "resolve",
720+ });
721+ expect(
722+ yield* installation.resolve(path.join(baseDir, "missing")).pipe(Effect.flip),
723+ ).toMatchObject({
724+ operation: "resolve",
725+ });
726+ yield* expectPreviousRelease(installation);
727+ yield* fs.writeFileString(externalHarness, "external harness", { mode: 0o755 });
728+ yield* installation.remove();
729+ expect(yield* installation.resolve()).toMatchObject({
730+ source: "path",
731+ executablePath: externalExecutable,
732+ });
733+ const isolated = yield* makeHarness({ baseDir });
734+ expect(yield* isolated.installation.resolve().pipe(Effect.flip)).toMatchObject({
735+ operation: "resolve",
736+ });
737+ expect(
738+ yield* isolated.installation.resolve(undefined, { PATH: externalDirectory }),
739+ ).toMatchObject({
740+ source: "path",
741+ executablePath: externalExecutable,
742+ });
743+ expect(
744+ yield* isolated.installation.resolve("agy_acp_server.par", { PATH: externalDirectory }),
745+ ).toMatchObject({ source: "override", executablePath: externalExecutable });
746+ }),
743747 );
744748
745749 it.effect("keeps leased releases available while new sessions resolve the new release", () =>
apps/server/src/provider/providerMaintenance.test.ts+8−4
@@ -22,6 +22,10 @@ import { · 21 unmodified lines
2222 } from "./providerMaintenance.ts";
2323
2424 const driver = (value: string) => ProviderDriverKind.make(value);
25+// These write `#!/bin/sh` stubs and resolve them through a darwin-mocked
26+// PATH walk; a Windows temp path cannot be split on `:`.
27+const windowsHost = HostProcessPlatform.defaultValue() === "win32";
28+
2529 const makeTempDir = (name: string) =>
2630 Crypto.Crypto.pipe(
2731 Effect.flatMap((crypto) => crypto.randomUUIDv4),
@@ -199,7 +203,7 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => { · 171 unmodified lines
199203 });
200204 });
201205
202− it.effect(
206+ it.effect.skipIf(windowsHost)(
203207 "switches package-managed providers to vite-plus updates when the resolved binary lives in vite-plus global bin",
204208 () =>
205209 Effect.gen(function* () {
@@ -272,7 +276,7 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => { · 66 unmodified lines
272276 }),
273277 );
274278
275− it.effect(
279+ it.effect.skipIf(windowsHost)(
276280 "switches package-managed providers to pnpm updates when the resolved binary lives in pnpm's global bin",
277281 () =>
278282 Effect.gen(function* () {
@@ -332,7 +336,7 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => { · 53 unmodified lines
332336 });
333337 });
334338
335− it.effect(
339+ it.effect.skipIf(windowsHost)(
336340 "switches native-package-tool to native updates when the binary resolves through the native installer",
337341 () =>
338342 Effect.gen(function* () {
@@ -369,7 +373,7 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => { · 30 unmodified lines
369373 }),
370374 );
371375
372− it.effect(
376+ it.effect.skipIf(windowsHost)(
373377 "switches scoped-package-tool to native upgrades when the binary resolves through the standalone installer",
374378 () =>
375379 Effect.gen(function* () {
apps/server/src/resourceTelemetry/ResourceMonitorBinary.test.ts+6−2
@@ -11,6 +11,10 @@ import * as FileSystem from "effect/FileSystem"; · 10 unmodified lines
1111 import { ServerConfig } from "../config.ts";
1212 import * as ResourceMonitorBinary from "./ResourceMonitorBinary.ts";
1313
14+// The override checks POSIX exec bits on a real file under a linux platform
15+// mock; NTFS never reports those bits, so the check cannot be satisfied there.
16+const windowsHost = HostProcessPlatform.defaultValue() === "win32";
17+
1418 describe("ResourceMonitorBinary", () => {
1519 afterEach(() => {
1620 vi.restoreAllMocks();
@@ -42,7 +46,7 @@ describe("ResourceMonitorBinary", () => { · 25 unmodified lines
4246 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
4347 );
4448
45− it.effect("resolves an executable override", () =>
49+ it.effect.skipIf(windowsHost)("resolves an executable override", () =>
4650 Effect.gen(function* () {
4751 const fileSystem = yield* FileSystem.FileSystem;
4852 const baseDir = yield* fileSystem.makeTempDirectoryScoped({
@@ -66,7 +70,7 @@ describe("ResourceMonitorBinary", () => { · 17 unmodified lines
6670 }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
6771 );
6872
69− it.effect("resolves an executable override on an unsupported platform", () =>
73+ it.effect.skipIf(windowsHost)("resolves an executable override on an unsupported platform", () =>
7074 Effect.gen(function* () {
7175 const fileSystem = yield* FileSystem.FileSystem;
7276 const baseDir = yield* fileSystem.makeTempDirectoryScoped({