@efffrida/vitest-pool 0.0.18 → 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/frida/agent.ts +192 -0
  2. package/package.json +6 -7
@@ -0,0 +1,192 @@
1
+ import "@efffrida/polyfills";
2
+
3
+ // QuickJS returns Error.stack as a boxed String object, not a primitive. This
4
+ // causes @vitest/utils serializeValue to walk it as a char-indexed object
5
+ // {"0":"A","1":"s",...}. Adding toJSON makes serializeValue call valueOf()
6
+ // instead, returning the primitive string.
7
+ if (!("toJSON" in String.prototype)) {
8
+ (String.prototype as any).toJSON = String.prototype.valueOf;
9
+ }
10
+
11
+ import type { CancelReason, VitestRunner } from "@vitest/runner";
12
+ import type { ContextRPC, RunnerRPC, RuntimeRPC, WorkerGlobalState } from "vitest";
13
+ import type { WorkerRequest, WorkerResponse } from "vitest/node";
14
+
15
+ import { collectTests, startTests } from "@vitest/runner";
16
+ import { serializeError } from "@vitest/utils/error";
17
+ import { createStackString, parseStacktrace } from "@vitest/utils/source-map";
18
+ import { createBirpc } from "birpc";
19
+ import { stringify as flattedStringify } from "flatted";
20
+ import { EvaluatedModules } from "vitest";
21
+
22
+ // There should only ever be one test running at a time in a worker and these
23
+ // need to be shared across multiple rpc calls anyways so they will live up here
24
+ // in the module scope.
25
+ let runPromise: Promise<unknown> | undefined;
26
+ let setupContext!: Omit<ContextRPC, "files" | "providedContext" | "invalidates" | "workerId">;
27
+
28
+ // Collections of listeners
29
+ const cleanupListeners = new Set<() => unknown>();
30
+ const cancelListeners = new Set<(reason: CancelReason) => unknown>();
31
+ const messageListeners = new Set<(data: any, ...extras: Array<any>) => void>();
32
+
33
+ // Bidirectional rpc
34
+ const birpc = createBirpc<RuntimeRPC, RunnerRPC>(
35
+ {
36
+ async onCancel(reason: CancelReason) {
37
+ await Promise.allSettled([...cancelListeners.values()].map((listener) => listener(reason)));
38
+ },
39
+ },
40
+ {
41
+ // How to send and receive messages.
42
+ on: (rpcListener) => messageListeners.add(rpcListener),
43
+ off: (rpcListener) => messageListeners.delete(rpcListener),
44
+ post: (message) => (typeof message === "string" ? send(message) : send(flattedStringify(message))),
45
+
46
+ // Names of remote functions that do not need response.
47
+ // These are fire-and-forget messages to the vitest pool coordinator.
48
+ eventNames: ["onUserConsoleLog", "onCollected", "onCancel"],
49
+
50
+ // Maximum timeout for waiting for response, in milliseconds.
51
+ timeout: -1,
52
+ }
53
+ );
54
+
55
+ /** @see https://github.com/vitest-dev/vitest/blob/8508296e9adcd2d9859e8073ac76c0bcb7d78f50/packages/vitest/src/runtime/utils.ts#L23-L32 */
56
+ function provideWorkerState(context: unknown, state: WorkerGlobalState): WorkerGlobalState {
57
+ const NAME_WORKER_STATE = "__vitest_worker__";
58
+ Object.defineProperty(context, NAME_WORKER_STATE, {
59
+ value: state,
60
+ configurable: true,
61
+ writable: true,
62
+ enumerable: false,
63
+ });
64
+ return state;
65
+ }
66
+
67
+ /** @see https://github.com/vitest-dev/vitest/blob/9ca74cfb2060d8bc1c7a319ba3cba1578517adb0/packages/vitest/src/runtime/runners/index.ts#L65-L139 */
68
+ function patchTestRunner(testRunner: VitestRunner): VitestRunner {
69
+ const originalOnTaskUpdate = testRunner.onTaskUpdate;
70
+ testRunner.onTaskUpdate = async (task, events) => {
71
+ await birpc.onTaskUpdate(task, events);
72
+ await originalOnTaskUpdate?.call(testRunner, task, events);
73
+ };
74
+
75
+ const originalOnCollectStart = testRunner.onCollectStart;
76
+ testRunner.onCollectStart = async (file) => {
77
+ await birpc.onQueued(file);
78
+ await originalOnCollectStart?.call(testRunner, file);
79
+ };
80
+
81
+ const originalOnCollected = testRunner.onCollected;
82
+ testRunner.onCollected = async (files) => {
83
+ await birpc.onCollected(files);
84
+ await originalOnCollected?.call(testRunner, files);
85
+ };
86
+
87
+ return testRunner;
88
+ }
89
+
90
+ /** @see https://github.com/vitest-dev/vitest/blob/4f58c77147796d48bf70579222a577df977300f8/packages/vitest/src/runtime/workers/init.ts#L20-L235 */
91
+ rpc.exports["onMessage"] = async (message: unknown): Promise<WorkerResponse | void> => {
92
+ // Predicate to check if a message is a worker request
93
+ const isWorkerRequest = (u: unknown): u is WorkerRequest =>
94
+ typeof u === "object" && u !== null && "__vitest_worker_request__" in u && u.__vitest_worker_request__ === true;
95
+
96
+ // Handle non-worker messages
97
+ if (!isWorkerRequest(message)) {
98
+ return messageListeners.forEach((listener) => {
99
+ listener(message);
100
+ });
101
+ }
102
+
103
+ // Handle worker messages
104
+ switch (message.type) {
105
+ case "start": {
106
+ process.env.VITEST_POOL_ID = String(message.poolId);
107
+ process.env.VITEST_WORKER_ID = String(message.workerId);
108
+ const { config, environment, pool } = message.context;
109
+ setupContext = { environment, config, pool, rpc: birpc, projectName: config.name ?? "" };
110
+ return send({ type: "started", __vitest_worker_response__: true });
111
+ }
112
+
113
+ case "stop": {
114
+ if (runPromise !== undefined) await runPromise;
115
+ await Promise.allSettled(
116
+ setupContext.rpc.$rejectPendingCalls(({ method, reject }) => {
117
+ reject(`Closing rpc while ${method} was pending`);
118
+ })
119
+ );
120
+
121
+ await Promise.allSettled([...cleanupListeners.values()].map((listener) => listener()));
122
+ cancelListeners.clear();
123
+ cleanupListeners.clear();
124
+
125
+ return send({
126
+ type: "stopped",
127
+ __vitest_worker_response__: true,
128
+ });
129
+ }
130
+
131
+ case "run":
132
+ case "collect": {
133
+ if (runPromise !== undefined) {
134
+ return send({
135
+ type: "testfileFinished",
136
+ __vitest_worker_response__: true,
137
+ error: serializeError("Worker is already running tests"),
138
+ });
139
+ }
140
+
141
+ /** @see https://github.com/vitest-dev/vitest/blob/4f58c77147796d48bf70579222a577df977300f8/packages/vitest/src/runtime/worker.ts#L28-L49 */
142
+ provideWorkerState(globalThis, {
143
+ rpc: birpc,
144
+ environment: null!,
145
+ config: setupContext.config,
146
+ durations: { environment: 0, prepare: 0 },
147
+ ctx: { ...setupContext, ...message.context },
148
+ providedContext: message.context.providedContext,
149
+ metaEnv: process.env as WorkerGlobalState["metaEnv"],
150
+
151
+ resolvingModules: new Set(), // TODO: share this between runs? https://github.com/vitest-dev/vitest/blob/8508296e9adcd2d9859e8073ac76c0bcb7d78f50/packages/vitest/src/runtime/worker.ts#L9
152
+ moduleExecutionInfo: new Map(), // TODO: share this between runs? https://github.com/vitest-dev/vitest/blob/4f58c77147796d48bf70579222a577df977300f8/packages/vitest/src/runtime/workers/base.ts#L18-L19
153
+ evaluatedModules: new EvaluatedModules(), // TODO: share this between runs? https://github.com/vitest-dev/vitest/blob/4f58c77147796d48bf70579222a577df977300f8/packages/vitest/src/runtime/workers/base.ts#L18-L19
154
+
155
+ onCancel: (listener) => cancelListeners.add(listener),
156
+ onCleanup: (listener) => cleanupListeners.add(listener),
157
+ onFilterStackTrace: (stack) => createStackString(parseStacktrace(stack)),
158
+ } satisfies WorkerGlobalState);
159
+
160
+ // Create a minimal runner without snapshot support
161
+ const entrypoint = message.type === "run" ? startTests : collectTests;
162
+ const testRunner: VitestRunner = {
163
+ config: setupContext.config as VitestRunner["config"],
164
+ importFile: async (_file: string): Promise<void> => {
165
+ // @efffrida/vitest-pool/agent/file-map
166
+
167
+ return Promise.reject("importFile is not supported in the frida pool agent");
168
+ },
169
+ };
170
+
171
+ try {
172
+ for (const file of message.context.files) {
173
+ runPromise = entrypoint([file], patchTestRunner(testRunner));
174
+ await runPromise;
175
+ }
176
+
177
+ return send({
178
+ type: "testfileFinished",
179
+ __vitest_worker_response__: true,
180
+ });
181
+ } catch (error: unknown) {
182
+ return send({
183
+ type: "testfileFinished",
184
+ __vitest_worker_response__: true,
185
+ error: serializeError(error),
186
+ });
187
+ } finally {
188
+ runPromise = undefined;
189
+ }
190
+ }
191
+ }
192
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@efffrida/vitest-pool",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "description": "effect frida vitest-pool",
5
5
  "keywords": [
6
6
  "effect-ts",
@@ -17,6 +17,7 @@
17
17
  },
18
18
  "files": [
19
19
  "src/**/*.ts",
20
+ "dist/**/*.ts",
20
21
  "dist/**/*.js",
21
22
  "dist/**/*.js.map",
22
23
  "dist/**/*.d.ts",
@@ -35,6 +36,8 @@
35
36
  "provenance": true
36
37
  },
37
38
  "dependencies": {
39
+ "flatted": "3.4.2",
40
+ "frida": "17.9.11",
38
41
  "ioredis": "5.11.0",
39
42
  "@efffrida/frida-tools": "0.0.31"
40
43
  },
@@ -46,23 +49,19 @@
46
49
  "@vitest/utils": "4.1.7",
47
50
  "birpc": "4.0.0",
48
51
  "effect": "4.0.0-beta.69",
49
- "flatted": "3.4.2",
50
- "frida": "17.9.11",
51
52
  "vite": "8.0.14",
52
53
  "vitest": "4.1.7",
53
54
  "@efffrida/polyfills": "0.0.10"
54
55
  },
55
56
  "peerDependencies": {
56
57
  "@effect/platform-node": "4.0.0-beta.69",
57
- "effect": "4.0.0-beta.69",
58
- "vitest": "4.1.7"
59
- },
60
- "optionalDependencies": {
61
58
  "@vitest/runner": "4.1.7",
62
59
  "@vitest/utils": "4.1.7",
63
60
  "birpc": "4.0.0",
61
+ "effect": "4.0.0-beta.69",
64
62
  "flatted": "3.4.2",
65
63
  "vite": "8.0.14",
64
+ "vitest": "4.1.7",
66
65
  "@efffrida/polyfills": "0.0.10"
67
66
  },
68
67
  "tags": [