Skip to content
Source
Sign inSign up

profile-startup.test.ts

149 lines5.9 KB

1// @effect-diagnostics nodeBuiltinImport:off -- Runs the profiling preload in a real Node subprocess.
2import * as NodeChildProcess from "node:child_process";
3import * as NodeFS from "node:fs";
4import * as NodeOS from "node:os";
5import * as NodePath from "node:path";
6import * as NodeURL from "node:url";
7import * as Schema from "effect/Schema";
8import { expect, it } from "vite-plus/test";
9​
10const decodeTrace = Schema.decodeUnknownSync(
11 Schema.fromJsonString(
12 Schema.Struct({
13 traceEvents: Schema.Array(
14 Schema.Struct({
15 name: Schema.String,
16 ts: Schema.Number,
17 dur: Schema.Number,
18 args: Schema.optional(
19 Schema.Struct({
20 stage: Schema.optional(Schema.String),
21 url: Schema.optional(Schema.String),
22 parentURL: Schema.optional(Schema.String),
23 importedBy: Schema.optional(Schema.String),
24 installed: Schema.optional(Schema.Boolean),
25 }),
26 ),
27 }),
28 ),
29 }),
30 ),
31);
32​
33const decodeRunner = Schema.decodeUnknownSync(
34 Schema.fromJsonString(Schema.Struct({ pid: Schema.Number, args: Schema.Array(Schema.String) })),
35);
36​
37it("attributes ESM and CommonJS imports without changing module execution", () => {
38 const directory = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-startup-profile-"));
39 try {
40 NodeFS.writeFileSync(NodePath.join(directory, "package.json"), '{"type":"module"}');
41 const fixtures = {
42 "root.mjs": `
43 import { value } from './dependency.mjs';
44 import strict from './strict.cjs';
45 import loose from './loose.cjs';
46 import interop from './interop.cjs';
47 if (value !== 42 || strict !== undefined || loose !== true || interop !== 9) {
48 throw Error('Profiling changed module semantics');
49 }
50 `,
51 "dependency.mjs": "export const value = 42;",
52 "strict.cjs": `/* retain the directive */\n"use strict"; module.exports = (function () { return this; })();`,
53 "loose.cjs": "module.exports = (function () { return this; })() === globalThis;",
54 "interop.cjs": "module.exports = require('./required.js').value;",
55 "required.js": "export const value = 9;",
56 };
57 for (const [name, source] of Object.entries(fixtures)) {
58 NodeFS.writeFileSync(NodePath.join(directory, name), source);
59 }
60 const output = NodePath.join(directory, "capture");
61 const root = NodeURL.pathToFileURL(
62 NodeFS.realpathSync(NodePath.join(directory, "root.mjs")),
63 ).href;
64 NodeChildProcess.execFileSync(
65 process.execPath,
66 [
67 "--no-node-snapshot",
68 "--import",
69 NodeURL.pathToFileURL(NodePath.join(import.meta.dirname, "profile-startup.mjs")).href,
70 "--input-type=module",
71 "-e",
72 `await import(${JSON.stringify(root)})`,
73 ],
74 { env: { ...process.env, NODE_OPTIONS: "", T3CODE_STARTUP_PROFILE_DIR: output } },
75 );
76 const spans = NodeFS.readdirSync(output)
77 .filter((name) => name.endsWith(".modules.json"))
78 .flatMap(
79 (name) => decodeTrace(NodeFS.readFileSync(NodePath.join(output, name), "utf8")).traceEvents,
80 );
81 expect(spans.some((span) => span.args?.installed === true)).toBe(true);
82 for (const name of Object.keys(fixtures)) {
83 const url = NodeURL.pathToFileURL(NodeFS.realpathSync(NodePath.join(directory, name))).href;
84 const stages = spans.filter((span) => span.args?.url === url).map((span) => span.args?.stage);
85 expect(stages, name).toEqual(
86 expect.arrayContaining(
87 name === "required.js" ? ["link", "require-esm"] : ["prepare", "evaluate"],
88 ),
89 );
90 }
91 const prepared = spans.find(
92 (span) => span.args?.url === root && span.args?.stage === "prepare",
93 );
94 const linked = spans.find((span) => span.args?.url === root && span.args?.stage === "link");
95 if (!prepared || !linked) throw Error("Missing root module preparation/link boundaries");
96 expect(prepared.ts + prepared.dur).toBeLessThanOrEqual(linked.ts);
97 const dependency = NodeURL.pathToFileURL(
98 NodeFS.realpathSync(NodePath.join(directory, "dependency.mjs")),
99 ).href;
100 expect(
101 spans.find((span) => span.args?.url === dependency && span.args?.stage === "link")?.args
102 ?.importedBy,
103 ).toBe(root);
104 } finally {
105 NodeFS.rmSync(directory, { recursive: true, force: true });
106 }
107});
108​
109it("captures only the dev runner and preserves its exit status", () => {
110 const directory = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-startup-launcher-"));
111 try {
112 NodeFS.mkdirSync(NodePath.join(directory, "scripts"));
113 NodeFS.writeFileSync(
114 NodePath.join(directory, "scripts/dev-runner.ts"),
115 "console.log(JSON.stringify({ pid: process.pid, args: process.argv.slice(2) }));",
116 );
117 const output = NodePath.join(directory, "capture");
118 const stdout = NodeChildProcess.execFileSync(
119 process.execPath,
120 [NodePath.join(import.meta.dirname, "profile-startup.mjs"), "--no-seed"],
121 {
122 cwd: directory,
123 env: { ...process.env, NODE_OPTIONS: "", T3CODE_STARTUP_PROFILE_DIR: output },
124 encoding: "utf8",
125 },
126 );
127 const runner = decodeRunner(stdout.trim().split("\n").at(-1));
128 expect(runner.args).toEqual(["dev", "--no-seed"]);
129 expect(NodeFS.readdirSync(output).sort()).toEqual(
130 ["cpuprofile", "modules.json", "process.json"].map((suffix) => `${runner.pid}-0.${suffix}`),
131 );
132 NodeFS.writeFileSync(
133 NodePath.join(directory, "scripts/dev-runner.ts"),
134 "process.kill(process.pid, 'SIGTERM');",
135 );
136 const interrupted = NodeChildProcess.spawnSync(
137 process.execPath,
138 [NodePath.join(import.meta.dirname, "profile-startup.mjs")],
139 {
140 cwd: directory,
141 env: { ...process.env, NODE_OPTIONS: "", T3CODE_STARTUP_PROFILE_DIR: output },
142 },
143 );
144 expect(interrupted.error).toBeUndefined();
145 expect(interrupted.status).toBe(1);
146 } finally {
147 NodeFS.rmSync(directory, { recursive: true, force: true });
148 }
149});