authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-05 22:30:43-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-05 22:30:43-07:00
logb4040d9bf38d57cce715c9e602de8de4b0f4a523
tree06f2576e67348ce110175a92deaa25962b8bd4ca
parent0c200c5f83c039f85da0cb1b8d1611db3e708c92
signature Signed by PGP key B5690EEEBB952194

test(desktop): exercise WSL cache safety through public scripts (#10301)


2 files changed, 16 insertions(+), 48 deletions(-)

apps/desktop/src/wsl/DesktopWslEnvironment.test.ts+12-43
...@@ -12,21 +12,17 @@ import * as TestClock from "effect/testing/TestClock";...@@ -12,21 +12,17 @@ import * as TestClock from "effect/testing/TestClock";
12import { ChildProcessSpawner } from "effect/unstable/process";12import { ChildProcessSpawner } from "effect/unstable/process";
1313
14import {14import {
15 buildWslNodeEnvPreamble,
16 buildWslRuntimeInstallScript,15 buildWslRuntimeInstallScript,
17 buildWslRuntimeInvalidateScript,16 buildWslRuntimeInvalidateScript,
18 buildWslRuntimePruneScript,17 buildWslRuntimePruneScript,
19 DesktopWslDistroListError,18 DesktopWslDistroListError,
20 formatMissingToolsReason,19 formatMissingToolsReason,
21 formatNodePtyProbeFailureReason,
22 formatWslShellTransportFailureReason,
23 parseNodePath,20 parseNodePath,
24 parseNodeVersion,21 parseNodeVersion,
25 parseResolvedPath,22 parseResolvedPath,
26 parseToolchainReport,23 parseToolchainReport,
27 parseWslRuntimeRoot,24 parseWslRuntimeRoot,
28 probeWslDistros,25 probeWslDistros,
29 sanitizeWslRuntimeId,
30} from "./DesktopWslEnvironment.ts";26} from "./DesktopWslEnvironment.ts";
3127
32const encoder = new TextEncoder();28const encoder = new TextEncoder();
...@@ -144,46 +140,19 @@ describe("probeWslDistros", () => {...@@ -144,46 +140,19 @@ describe("probeWslDistros", () => {
144 });140 });
145});141});
146142
147describe("formatNodePtyProbeFailureReason", () => {
148 it("identifies a packaged build that omitted the Linux node-pty prebuild", () => {
149 const reason = formatNodePtyProbeFailureReason(4);
150
151 expect(reason).toContain("packaged Linux node-pty binary was not included");
152 expect(reason).toContain("--wsl-prebuild");
153 });
154
155 it("leaves other node-pty load failures to the compatibility diagnostic", () => {
156 expect(formatNodePtyProbeFailureReason(1)).toBeNull();
157 });
158});
159
160describe("formatWslShellTransportFailureReason", () => {
161 it("distinguishes timeouts and spawn failures from normal shell exit codes", () => {
162 expect(formatWslShellTransportFailureReason("timeout")).toContain("timed out");
163 expect(formatWslShellTransportFailureReason("spawn")).toContain("could not start wsl.exe");
164 expect(formatWslShellTransportFailureReason("process")).toContain("lost communication");
165 expect(formatWslShellTransportFailureReason(null)).toBeNull();
166 });
167});
168
169describe("buildWslNodeEnvPreamble", () => {
170 it("passes the required Node engine range into the shared resolver", () => {
171 const preamble = buildWslNodeEnvPreamble("^22.16 || ^23.11 || >=24.10");
172
173 expect(preamble).toContain("T3_NODE_ENGINE_RANGE='^22.16 || ^23.11 || >=24.10'");
174 expect(preamble.indexOf("T3_NODE_ENGINE_RANGE=")).toBeLessThan(
175 preamble.lastIndexOf("ensure_remote_node_path || true"),
176 );
177 });
178
179 it("keeps the shared resolver permissive when no Node engine range is provided", () => {
180 expect(buildWslNodeEnvPreamble()).toContain("T3_NODE_ENGINE_RANGE=''");
181 });
182});
183
184describe("WSL runtime cache", () => {143describe("WSL runtime cache", () => {
185 it("sanitizes cache ids before interpolating them into Linux paths", () => {144 it.each([
186 expect(sanitizeWslRuntimeId("1.2.3/x64; touch /tmp/nope")).toBe("1.2.3_x64__touch__tmp_nope");145 [
146 "install",
147 (id: string) => buildWslRuntimeInstallScript("/runtime.tar.gz", id, "b".repeat(64)),
148 ],
149 ["prune", buildWslRuntimePruneScript],
150 ["invalidate", buildWslRuntimeInvalidateScript],
151 ] as const)("sanitizes cache ids in the %s script", (_, buildScript) => {
152 const runtimeId = "1.2.3/x64; touch /tmp/nope";
153 const script = buildScript(runtimeId);
154 expect(script).toContain("/1.2.3_x64__touch__tmp_nope");
155 expect(script).not.toContain(runtimeId);
187 });156 });
188157
189 it("installs through a temporary directory and only reuses valid completed caches", () => {158 it("installs through a temporary directory and only reuses valid completed caches", () => {
apps/desktop/src/wsl/DesktopWslEnvironment.ts+4-5
...@@ -147,7 +147,7 @@ const TIMEOUT_RESULT: ShellResult = {...@@ -147,7 +147,7 @@ const TIMEOUT_RESULT: ShellResult = {
147 transportFailure: "timeout",147 transportFailure: "timeout",
148};148};
149149
150export const formatWslShellTransportFailureReason = (150const formatWslShellTransportFailureReason = (
151 failure: ShellResult["transportFailure"],151 failure: ShellResult["transportFailure"],
152): string | null => {152): string | null => {
153 switch (failure) {153 switch (failure) {
...@@ -165,7 +165,7 @@ export const formatWslShellTransportFailureReason = (...@@ -165,7 +165,7 @@ export const formatWslShellTransportFailureReason = (
165// Reuse the SSH remote resolver so WSL and SSH discover version-managed Node165// Reuse the SSH remote resolver so WSL and SSH discover version-managed Node
166// the same way. Passing the engine range lets the resolver fall through to166// the same way. Passing the engine range lets the resolver fall through to
167// version managers like nvm when a system node exists but is too old.167// version managers like nvm when a system node exists but is too old.
168export const buildWslNodeEnvPreamble = (168const buildWslNodeEnvPreamble = (
169 nodeEngineRange?: string | null,169 nodeEngineRange?: string | null,
170): string => `${buildRemoteNodeEnvScript({ nodeEngineRange: nodeEngineRange ?? null })}170): string => `${buildRemoteNodeEnvScript({ nodeEngineRange: nodeEngineRange ?? null })}
171ensure_remote_node_path || true171ensure_remote_node_path || true
...@@ -263,8 +263,7 @@ const WSL_RUNTIME_READY_MARKER = ".t3code-wsl-runtime-ready";...@@ -263,8 +263,7 @@ const WSL_RUNTIME_READY_MARKER = ".t3code-wsl-runtime-ready";
263const WSL_RUNTIME_SELECTED_MARKER = ".t3code-wsl-runtime-selected";263const WSL_RUNTIME_SELECTED_MARKER = ".t3code-wsl-runtime-selected";
264const WSL_RUNTIME_SELECTION_GRACE_MINUTES = 5;264const WSL_RUNTIME_SELECTION_GRACE_MINUTES = 5;
265265
266export const sanitizeWslRuntimeId = (value: string): string =>266const sanitizeWslRuntimeId = (value: string): string => value.replace(/[^A-Za-z0-9._-]/g, "_");
267 value.replace(/[^A-Za-z0-9._-]/g, "_");
268267
269// `archiveSha256` is the digest the build recorded alongside the archive. The268// `archiveSha256` is the digest the build recorded alongside the archive. The
270// install verifies the bytes before extracting, so an archive can never be269// install verifies the bytes before extracting, so an archive can never be
...@@ -491,7 +490,7 @@ export const parseWslRuntimeRoot = (stdout: string): string | null => {...@@ -491,7 +490,7 @@ export const parseWslRuntimeRoot = (stdout: string): string | null => {
491490
492const NODE_PTY_PREBUILD_MISSING_EXIT_CODE = 4;491const NODE_PTY_PREBUILD_MISSING_EXIT_CODE = 4;
493492
494export const formatNodePtyProbeFailureReason = (exitCode: number): string | null =>493const formatNodePtyProbeFailureReason = (exitCode: number): string | null =>
495 exitCode === NODE_PTY_PREBUILD_MISSING_EXIT_CODE494 exitCode === NODE_PTY_PREBUILD_MISSING_EXIT_CODE
496 ? "WSL support is missing from this T3 Code build: the packaged Linux node-pty binary was not included. Rebuild the Windows artifact with `--wsl-prebuild <path-to-linux-pty.node>` or install a build that includes WSL support."495 ? "WSL support is missing from this T3 Code build: the packaged Linux node-pty binary was not included. Rebuild the Windows artifact with `--wsl-prebuild <path-to-linux-pty.node>` or install a build that includes WSL support."
497 : null;496 : null;