Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 97 additions & 14 deletions scripts/build-npm-launcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,15 @@ const run = Effect.fn("test.run")(function* (
return { stdout, stderr, exitCode };
}, Effect.scoped);

const serveFakeRelease = Effect.fn("test.serveFakeRelease")(function* (root: string) {
const serveFakeRelease = Effect.fn("test.serveFakeRelease")(function* (
root: string,
executable = `#!/bin/sh
if [ -n "$T3CODE_TEST_CALLS" ]; then echo "$*|$T3CODE_NPM_LAUNCHER_PATH" >> "$T3CODE_TEST_CALLS"; fi
if [ "$1" = "--version" ]; then echo "${VERSION}"; exit 0; fi
echo "stub $* launcher=$T3CODE_NPM_LAUNCHER_PATH"
exit 7
`,
) {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const key = `${yield* HostProcess.Platform}-${yield* HostProcess.Architecture}`;
Expand All @@ -56,10 +64,7 @@ const serveFakeRelease = Effect.fn("test.serveFakeRelease")(function* (root: str
yield* fs.makeDirectory(path.join(root, "release", stem, "node_modules/native"), {
recursive: true,
});
yield* fs.writeFileString(
path.join(root, "release", stem, "t3"),
'#!/bin/sh\necho "stub $* launcher=$T3CODE_NPM_LAUNCHER_PATH"\nexit 7\n',
);
yield* fs.writeFileString(path.join(root, "release", stem, "t3"), executable);
yield* fs.chmod(path.join(root, "release", stem, "t3"), 0o755);
const tar = yield* run("tar", ["-czf", archiveName, stem], {
cwd: path.join(root, "release"),
Expand Down Expand Up @@ -136,6 +141,7 @@ it.layer(NodeServices.layer)("build-npm-launcher", (it) => {
const env = {
PATH: process.env.PATH,
T3CODE_HOME: home,
T3CODE_TEST_CALLS: path.join(root, "calls"),
T3CODE_RELEASE_BASE_URL: release.baseUrl,
};
const launcher = path.join(packageDir, "bin/t3.mjs");
Expand All @@ -145,6 +151,10 @@ it.layer(NodeServices.layer)("build-npm-launcher", (it) => {
});
assert.equal(first.stdout.trim(), `stub serve a b launcher=${launcher}`, first.stderr);
assert.equal(first.exitCode, 7);
assert.deepStrictEqual(
(yield* fs.readFileString(env.T3CODE_TEST_CALLS)).trim().split("\n"),
["--version|", `serve a b|${launcher}`],
);
const versionDir = path.join(home, "runtime/versions", VERSION);
assert.equal(
(yield* fs.readFileString(path.join(versionDir, ".install-complete"))).trim(),
Expand All @@ -158,8 +168,24 @@ it.layer(NodeServices.layer)("build-npm-launcher", (it) => {

const requestsAfterFirst = release.requests.length;
const second = yield* run(process.execPath, [launcher, "--version"], { cwd: root, env });
assert.equal(second.exitCode, 7);
assert.equal(second.exitCode, 0);
assert.equal(release.requests.length, requestsAfterFirst);
assert.deepStrictEqual(
(yield* fs.readFileString(env.T3CODE_TEST_CALLS)).trim().split("\n"),
["--version|", `serve a b|${launcher}`, `--version|${launcher}`],
);

yield* fs.writeFileString(path.join(versionDir, "t3"), "#!/t3-test-missing-loader\n");
for (const _attempt of [1, 2]) {
const cached = yield* run(process.execPath, [launcher, "serve"], { cwd: root, env });
assert.equal(cached.exitCode, 1, cached.stderr);
assert.include(cached.stderr, "could not run");
assert.equal(release.requests.length, requestsAfterFirst);
assert.equal(
yield* fs.readFileString(path.join(versionDir, ".install-complete")),
`${VERSION}\n`,
);
}

const ipcServer = path.join(home, "runtime/versions", VERSION, "t3");
yield* fs.writeFileString(
Expand Down Expand Up @@ -189,8 +215,8 @@ it.layer(NodeServices.layer)("build-npm-launcher", (it) => {
env: { ...env, T3CODE_HOME: raceHome },
});
const [one, two] = yield* Effect.all([launch, launch], { concurrency: "unbounded" });
assert.equal(one.exitCode, 7, one.stderr);
assert.equal(two.exitCode, 7, two.stderr);
assert.equal(one.exitCode, 0, one.stderr);
assert.equal(two.exitCode, 0, two.stderr);
assert.deepStrictEqual(yield* fs.readDirectory(path.join(raceHome, "runtime/versions")), [
VERSION,
]);
Expand Down Expand Up @@ -296,12 +322,8 @@ fs.readFileSync = (file, ...rest) => {
cwd: root,
env: { ...env, T3CODE_HOME: staleOnlyHome },
});
assert.equal(
replaced.stdout.trim(),
`stub --version launcher=${launcher}`,
replaced.stderr,
);
assert.equal(replaced.exitCode, 7);
assert.equal(replaced.stdout.trim(), VERSION, replaced.stderr);
assert.equal(replaced.exitCode, 0);
assert.deepStrictEqual(
yield* fs.readDirectory(path.join(staleOnlyHome, "runtime/versions")),
[VERSION],
Expand All @@ -320,4 +342,65 @@ fs.readFileSync = (file, ...rest) => {
);
}),
);
it.effect.skipIf(HostProcess.Platform.defaultValue() === "win32")(
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
"does not publish an unusable release",
() =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
for (const executable of [
"#!/t3-test-missing-loader\n",
'#!/bin/sh\necho "runtime loader failed" >&2\nexit 42\n',
"#!/bin/sh\nkill -TERM $$\n",
'#!/bin/sh\ntrap \'echo caught > "$T3CODE_TEST_TERM"; exit 42\' TERM\necho ready > "$T3CODE_TEST_READY"\nwhile :; do :; done\n',
]) {
const root = yield* fs.makeTempDirectoryScoped({ prefix: "t3-npm-validation-test-" });
const output = yield* buildNpmLauncher({
version: VERSION,
outputDir: path.join(root, "out"),
});
const release = yield* serveFakeRelease(root, executable);
const home = path.join(root, "home");
const dir = path.join(home, "runtime/versions", VERSION);
const probeTimeoutHook = path.join(root, "probe-timeout.mjs");
yield* fs.writeFileString(
probeTimeoutHook,
`import childProcess from "node:child_process";
import { syncBuiltinESMExports } from "node:module";
const { spawnSync } = childProcess;
childProcess.spawnSync = (command, args, options) => spawnSync(command, args,
options?.timeout === undefined ? options : { ...options, timeout: 500 });
syncBuiltinESMExports();
`,
);
const env = {
PATH: process.env.PATH,
T3CODE_HOME: home,
T3CODE_RELEASE_BASE_URL: release.baseUrl,
T3CODE_TEST_READY: path.join(root, "probe-ready"),
T3CODE_TEST_TERM: path.join(root, "probe-term"),
};
const launcher = path.join(output.packageDir, "bin/t3.mjs");
const failed = yield* run(
process.execPath,
["--import", probeTimeoutHook, launcher, "serve"],
{ cwd: root, env },
);
assert.equal(failed.exitCode, 1, failed.stderr);
assert.deepStrictEqual(yield* fs.readDirectory(path.dirname(dir)), [], failed.stderr);
assert.include(failed.stderr, "could not run");
if (executable.includes("runtime loader failed")) {
assert.include(failed.stderr, "runtime loader failed");
}
if (executable.includes("trap")) {
assert.isTrue(yield* fs.exists(env.T3CODE_TEST_READY));
assert.isFalse(yield* fs.exists(env.T3CODE_TEST_TERM));
}
if ((yield* HostProcess.Platform) === "linux") {
assert.include(failed.stderr, "glibc");
assert.include(failed.stderr, "musl");
}
}
}),
);
});
29 changes: 28 additions & 1 deletion scripts/npm-launcher/t3.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,24 @@ async function install(dir, key) {
});
if (extract.status !== 0) throw new Error(`could not extract ${archive}`);
fs.rmSync(file);
const validate = spawnSync(
path.join(staging, process.platform === "win32" ? "t3.exe" : "t3"),
["--version"],
{
stdio: ["ignore", "ignore", "pipe"],
encoding: "utf8",
timeout: 30_000,
killSignal: "SIGKILL",
},
);
if (validate.status !== 0) {
throw executableError(
key,
validate.error?.message ||
validate.stderr?.trim() ||
`--version exited with ${validate.signal || validate.status}`,
);
}
fs.writeFileSync(path.join(staging, ".install-complete"), `${version}\n`);
publish(staging, dir);
} finally {
Expand Down Expand Up @@ -172,12 +190,21 @@ async function main() {
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
process.on(signal, () => (ipc || signal !== "SIGINT") && child.kill(signal));
}
child.on("error", (error) => fail(error));
child.on("error", (error) => fail(executableError(key, error.message)));
child.on("exit", (code, signal) =>
process.exit(code ?? 128 + (os.constants.signals[signal] || 1)),
);
}

function executableError(key, detail) {
return new Error(
`could not run T3 Code ${version} for ${key}: ${detail}` +
(process.platform === "linux"
? "\nThe Linux release requires glibc; musl-based systems such as Alpine are not supported. Use a glibc-based Linux environment."
: ""),
);
}

function fail(error) {
process.stderr.write(`t3: ${error.message}\n`);
process.exit(1);
Expand Down
Loading