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";
1212import { ChildProcessSpawner } from "effect/unstable/process";
1313
1414import {
15 buildWslNodeEnvPreamble,
1615 buildWslRuntimeInstallScript,
1716 buildWslRuntimeInvalidateScript,
1817 buildWslRuntimePruneScript,
1918 DesktopWslDistroListError,
2019 formatMissingToolsReason,
21 formatNodePtyProbeFailureReason,
22 formatWslShellTransportFailureReason,
2320 parseNodePath,
2421 parseNodeVersion,
2522 parseResolvedPath,
2623 parseToolchainReport,
2724 parseWslRuntimeRoot,
2825 probeWslDistros,
29 sanitizeWslRuntimeId,
3026} from "./DesktopWslEnvironment.ts";
3127
3228const encoder = new TextEncoder();
......@@ -144,46 +140,19 @@ describe("probeWslDistros", () => {
144140 });
145141});
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
184143describe("WSL runtime cache", () => {
185 it("sanitizes cache ids before interpolating them into Linux paths", () => {
186 expect(sanitizeWslRuntimeId("1.2.3/x64; touch /tmp/nope")).toBe("1.2.3_x64__touch__tmp_nope");
144 it.each([
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);
187156 });
188157
189158 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 = {
147147 transportFailure: "timeout",
148148};
149149
150export const formatWslShellTransportFailureReason = (
150const formatWslShellTransportFailureReason = (
151151 failure: ShellResult["transportFailure"],
152152): string | null => {
153153 switch (failure) {
......@@ -165,7 +165,7 @@ export const formatWslShellTransportFailureReason = (
165165// Reuse the SSH remote resolver so WSL and SSH discover version-managed Node
166166// the same way. Passing the engine range lets the resolver fall through to
167167// version managers like nvm when a system node exists but is too old.
168export const buildWslNodeEnvPreamble = (
168const buildWslNodeEnvPreamble = (
169169 nodeEngineRange?: string | null,
170170): string => `${buildRemoteNodeEnvScript({ nodeEngineRange: nodeEngineRange ?? null })}
171171ensure_remote_node_path || true
......@@ -263,8 +263,7 @@ const WSL_RUNTIME_READY_MARKER = ".t3code-wsl-runtime-ready";
263263const WSL_RUNTIME_SELECTED_MARKER = ".t3code-wsl-runtime-selected";
264264const WSL_RUNTIME_SELECTION_GRACE_MINUTES = 5;
265265
266export const sanitizeWslRuntimeId = (value: string): string =>
267 value.replace(/[^A-Za-z0-9._-]/g, "_");
266const sanitizeWslRuntimeId = (value: string): string => value.replace(/[^A-Za-z0-9._-]/g, "_");
268267
269268// `archiveSha256` is the digest the build recorded alongside the archive. The
270269// install verifies the bytes before extracting, so an archive can never be
......@@ -491,7 +490,7 @@ export const parseWslRuntimeRoot = (stdout: string): string | null => {
491490
492491const NODE_PTY_PREBUILD_MISSING_EXIT_CODE = 4;
493492
494export const formatNodePtyProbeFailureReason = (exitCode: number): string | null =>
493const formatNodePtyProbeFailureReason = (exitCode: number): string | null =>
495494 exitCode === NODE_PTY_PREBUILD_MISSING_EXIT_CODE
496495 ? "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."
497496 : null;