@grest-ts/testkit 0.0.6 → 0.0.7

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 (46) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +413 -413
  3. package/dist/src/runner/isolated-loader.mjs +91 -91
  4. package/dist/src/runner/worker-loader.mjs +49 -49
  5. package/dist/tsconfig.publish.tsbuildinfo +1 -1
  6. package/package.json +12 -12
  7. package/src/GGBundleTest.ts +89 -89
  8. package/src/GGTest.ts +318 -318
  9. package/src/GGTestContext.ts +74 -74
  10. package/src/GGTestRunner.ts +308 -308
  11. package/src/GGTestRuntime.ts +265 -265
  12. package/src/GGTestRuntimeWorker.ts +159 -159
  13. package/src/GGTestSharedRef.ts +116 -116
  14. package/src/GGTestkitExtensionsDiscovery.ts +26 -26
  15. package/src/IGGLocalDiscoveryServer.ts +16 -16
  16. package/src/callOn/GGCallOnSelector.ts +61 -61
  17. package/src/callOn/GGContractClass.implement.ts +43 -43
  18. package/src/callOn/GGTestActionForLocatorOnCall.ts +134 -134
  19. package/src/callOn/TestableIPC.ts +81 -81
  20. package/src/callOn/callOn.ts +224 -224
  21. package/src/callOn/registerOnCallHandler.ts +123 -123
  22. package/src/index-node.ts +64 -64
  23. package/src/mockable/GGMockable.ts +22 -22
  24. package/src/mockable/GGMockableCall.ts +45 -45
  25. package/src/mockable/GGMockableIPC.ts +20 -20
  26. package/src/mockable/GGMockableInterceptor.ts +44 -44
  27. package/src/mockable/GGMockableInterceptorsServer.ts +69 -69
  28. package/src/mockable/mockable.ts +71 -71
  29. package/src/runner/InlineRunner.ts +47 -47
  30. package/src/runner/IsolatedRunner.ts +179 -179
  31. package/src/runner/RuntimeRunner.ts +15 -15
  32. package/src/runner/WorkerRunner.ts +179 -179
  33. package/src/runner/isolated-loader.mjs +91 -91
  34. package/src/runner/worker-loader.mjs +49 -49
  35. package/src/testers/GGCallInterceptor.ts +224 -224
  36. package/src/testers/GGMockWith.ts +92 -92
  37. package/src/testers/GGSpyWith.ts +115 -115
  38. package/src/testers/GGTestAction.ts +332 -332
  39. package/src/testers/GGTestComponent.ts +16 -16
  40. package/src/testers/GGTestSelector.ts +223 -223
  41. package/src/testers/IGGTestInterceptor.ts +10 -10
  42. package/src/testers/IGGTestWith.ts +15 -15
  43. package/src/testers/RuntimeSelector.ts +151 -151
  44. package/src/utils/GGExpectations.ts +78 -78
  45. package/src/utils/GGTestError.ts +36 -36
  46. package/src/utils/captureStack.ts +53 -53
@@ -1,179 +1,179 @@
1
- import type {RuntimeRunner} from "./RuntimeRunner";
2
- import {type ChildProcess, spawn} from "child_process";
3
- import {GGLog} from "@grest-ts/logger";
4
- import {GGTestEnvConfig} from "../GGTestRuntime";
5
-
6
- const PROCESS_READY = "IsolatedRunner:READY"
7
- const GG_ISOLATED_CONFIG = "GG_ISOLATED_CONFIG";
8
-
9
- export class IsolatedRunner implements RuntimeRunner {
10
- private process?: ChildProcess;
11
-
12
- private static isolatedLoaderPath: string | undefined;
13
-
14
- /**
15
- * Set the path to the isolated-loader.mjs file.
16
- * Called by @grest-ts/testkit-vitest to inject the path.
17
- */
18
- public static setIsolatedLoaderPath(path: string): void {
19
- this.isolatedLoaderPath = path;
20
- }
21
-
22
- constructor(private config: GGTestEnvConfig) {
23
- }
24
-
25
- async start(): Promise<void> {
26
- if (!IsolatedRunner.isolatedLoaderPath) {
27
- throw new Error(
28
- "Isolated loader path not set!\n" +
29
- "Make sure to import '@grest-ts/testkit-vitest' in your vitest setup."
30
- );
31
- }
32
-
33
- GGLog.debug(this, 'Starting isolated process for: ' + this.config.executablePath);
34
-
35
- this.process = spawn('npx', ['tsx', IsolatedRunner.isolatedLoaderPath!, this.config.executablePath], {
36
- env: {
37
- ...process.env,
38
- [GG_ISOLATED_CONFIG]: JSON.stringify(this.config)
39
- },
40
- stdio: ['pipe', 'pipe', 'pipe'], // pipe all streams so we can forward them properly
41
- shell: true // Required for Windows to find npx
42
- });
43
-
44
- // Forward stderr to console.error so vitest can capture and sequence it
45
- this.process.stderr?.setEncoding('utf8');
46
- this.process.stderr?.on('data', (data: string) => {
47
- const lines = data.split('\n');
48
- for (const line of lines) {
49
- if (line) console.error(line);
50
- }
51
- });
52
-
53
- // Wait for process to signal ready via stdout
54
- await new Promise<void>((resolve, reject) => {
55
- if (!this.process) {
56
- reject(new Error('Process failed to start'));
57
- return;
58
- }
59
-
60
- const timeout = setTimeout(() => {
61
- this.process?.kill('SIGKILL');
62
- reject(new Error(`Process did not send READY signal within 10 seconds`));
63
- }, 10000);
64
-
65
- // Watch stdout for READY signal and forward all output using console.log
66
- // so vitest can capture and sequence it properly with test output
67
- this.process.stdout?.setEncoding('utf8');
68
- let ready = false;
69
- const onData = (data: string) => {
70
- const lines = data.split('\n');
71
- for (const line of lines) {
72
- if (line) console.log(line);
73
- }
74
- if (!ready && data.includes(PROCESS_READY)) {
75
- clearTimeout(timeout);
76
- GGLog.debug(this, 'Process ready');
77
- ready = true;
78
- resolve();
79
- }
80
- };
81
- this.process.stdout?.on('data', onData);
82
-
83
- this.process.once('error', (err) => {
84
- clearTimeout(timeout);
85
- reject(err);
86
- });
87
-
88
- this.process.once('exit', (code) => {
89
- if (code !== 0 && code !== null) {
90
- clearTimeout(timeout);
91
- reject(new Error(`Process exited with code ${code} before sending READY signal`));
92
- }
93
- });
94
- });
95
- }
96
-
97
- async stopRuntime(): Promise<void> {
98
- if (!this.process) {
99
- return;
100
- }
101
-
102
- GGLog.debug(this, 'Stopping runtime in process...');
103
-
104
- const RUNTIME_STOPPED = 'IsolatedRunner:RUNTIME_STOPPED';
105
-
106
- return new Promise<void>((resolve, reject) => {
107
- if (!this.process) {
108
- resolve();
109
- return;
110
- }
111
-
112
- const timeout = setTimeout(() => {
113
- GGLog.error(this, 'Timeout waiting for runtime to stop');
114
- reject(new Error('Timeout waiting for runtime to stop'));
115
- }, 5000);
116
-
117
- // Listen for RUNTIME_STOPPED signal in stdout
118
- const onData = (data: string) => {
119
- if (data.includes(RUNTIME_STOPPED)) {
120
- clearTimeout(timeout);
121
- this.process?.stdout?.off('data', onData);
122
- GGLog.debug(this, 'Runtime stopped in process');
123
- resolve();
124
- }
125
- };
126
- this.process.stdout?.on('data', onData);
127
-
128
- // Send stop runtime command via stdin
129
- try {
130
- this.process.stdin?.write('STOP_RUNTIME\n');
131
- GGLog.debug(this, 'Sent stop runtime command via stdin');
132
- } catch (error) {
133
- clearTimeout(timeout);
134
- GGLog.error(this, 'Error sending stop runtime command', error);
135
- reject(error);
136
- }
137
- });
138
- }
139
-
140
- async shutdown(): Promise<void> {
141
- if (!this.process) {
142
- return;
143
- }
144
-
145
- GGLog.debug(this, 'Shutting down process...');
146
-
147
- return new Promise<void>((resolve) => {
148
- if (!this.process) {
149
- resolve();
150
- return;
151
- }
152
-
153
- this.process.once('exit', () => {
154
- clearTimeout(forceKillTimeout);
155
- this.process = undefined;
156
- GGLog.debug(this, 'Process shut down');
157
- resolve();
158
- });
159
-
160
- // Send shutdown message via stdin (works cross-platform, unlike SIGTERM on Windows)
161
- try {
162
- this.process.stdin?.write('SHUTDOWN\n');
163
- this.process.stdin?.end();
164
- GGLog.debug(this, 'Sent shutdown command via stdin');
165
- } catch (error) {
166
- GGLog.error(this, 'Error sending shutdown command', error);
167
- }
168
-
169
- // Force kill after 2 seconds if still running
170
- const forceKillTimeout = setTimeout(() => {
171
- if (this.process && !this.process.killed) {
172
- GGLog.debug(this, 'Process did not exit gracefully, force killing');
173
- this.process.kill('SIGKILL');
174
- this.process = undefined;
175
- }
176
- }, 2000);
177
- });
178
- }
179
- }
1
+ import type {RuntimeRunner} from "./RuntimeRunner";
2
+ import {type ChildProcess, spawn} from "child_process";
3
+ import {GGLog} from "@grest-ts/logger";
4
+ import {GGTestEnvConfig} from "../GGTestRuntime";
5
+
6
+ const PROCESS_READY = "IsolatedRunner:READY"
7
+ const GG_ISOLATED_CONFIG = "GG_ISOLATED_CONFIG";
8
+
9
+ export class IsolatedRunner implements RuntimeRunner {
10
+ private process?: ChildProcess;
11
+
12
+ private static isolatedLoaderPath: string | undefined;
13
+
14
+ /**
15
+ * Set the path to the isolated-loader.mjs file.
16
+ * Called by @grest-ts/testkit-vitest to inject the path.
17
+ */
18
+ public static setIsolatedLoaderPath(path: string): void {
19
+ this.isolatedLoaderPath = path;
20
+ }
21
+
22
+ constructor(private config: GGTestEnvConfig) {
23
+ }
24
+
25
+ async start(): Promise<void> {
26
+ if (!IsolatedRunner.isolatedLoaderPath) {
27
+ throw new Error(
28
+ "Isolated loader path not set!\n" +
29
+ "Make sure to import '@grest-ts/testkit-vitest' in your vitest setup."
30
+ );
31
+ }
32
+
33
+ GGLog.debug(this, 'Starting isolated process for: ' + this.config.executablePath);
34
+
35
+ this.process = spawn('npx', ['tsx', IsolatedRunner.isolatedLoaderPath!, this.config.executablePath], {
36
+ env: {
37
+ ...process.env,
38
+ [GG_ISOLATED_CONFIG]: JSON.stringify(this.config)
39
+ },
40
+ stdio: ['pipe', 'pipe', 'pipe'], // pipe all streams so we can forward them properly
41
+ shell: true // Required for Windows to find npx
42
+ });
43
+
44
+ // Forward stderr to console.error so vitest can capture and sequence it
45
+ this.process.stderr?.setEncoding('utf8');
46
+ this.process.stderr?.on('data', (data: string) => {
47
+ const lines = data.split('\n');
48
+ for (const line of lines) {
49
+ if (line) console.error(line);
50
+ }
51
+ });
52
+
53
+ // Wait for process to signal ready via stdout
54
+ await new Promise<void>((resolve, reject) => {
55
+ if (!this.process) {
56
+ reject(new Error('Process failed to start'));
57
+ return;
58
+ }
59
+
60
+ const timeout = setTimeout(() => {
61
+ this.process?.kill('SIGKILL');
62
+ reject(new Error(`Process did not send READY signal within 10 seconds`));
63
+ }, 10000);
64
+
65
+ // Watch stdout for READY signal and forward all output using console.log
66
+ // so vitest can capture and sequence it properly with test output
67
+ this.process.stdout?.setEncoding('utf8');
68
+ let ready = false;
69
+ const onData = (data: string) => {
70
+ const lines = data.split('\n');
71
+ for (const line of lines) {
72
+ if (line) console.log(line);
73
+ }
74
+ if (!ready && data.includes(PROCESS_READY)) {
75
+ clearTimeout(timeout);
76
+ GGLog.debug(this, 'Process ready');
77
+ ready = true;
78
+ resolve();
79
+ }
80
+ };
81
+ this.process.stdout?.on('data', onData);
82
+
83
+ this.process.once('error', (err) => {
84
+ clearTimeout(timeout);
85
+ reject(err);
86
+ });
87
+
88
+ this.process.once('exit', (code) => {
89
+ if (code !== 0 && code !== null) {
90
+ clearTimeout(timeout);
91
+ reject(new Error(`Process exited with code ${code} before sending READY signal`));
92
+ }
93
+ });
94
+ });
95
+ }
96
+
97
+ async stopRuntime(): Promise<void> {
98
+ if (!this.process) {
99
+ return;
100
+ }
101
+
102
+ GGLog.debug(this, 'Stopping runtime in process...');
103
+
104
+ const RUNTIME_STOPPED = 'IsolatedRunner:RUNTIME_STOPPED';
105
+
106
+ return new Promise<void>((resolve, reject) => {
107
+ if (!this.process) {
108
+ resolve();
109
+ return;
110
+ }
111
+
112
+ const timeout = setTimeout(() => {
113
+ GGLog.error(this, 'Timeout waiting for runtime to stop');
114
+ reject(new Error('Timeout waiting for runtime to stop'));
115
+ }, 5000);
116
+
117
+ // Listen for RUNTIME_STOPPED signal in stdout
118
+ const onData = (data: string) => {
119
+ if (data.includes(RUNTIME_STOPPED)) {
120
+ clearTimeout(timeout);
121
+ this.process?.stdout?.off('data', onData);
122
+ GGLog.debug(this, 'Runtime stopped in process');
123
+ resolve();
124
+ }
125
+ };
126
+ this.process.stdout?.on('data', onData);
127
+
128
+ // Send stop runtime command via stdin
129
+ try {
130
+ this.process.stdin?.write('STOP_RUNTIME\n');
131
+ GGLog.debug(this, 'Sent stop runtime command via stdin');
132
+ } catch (error) {
133
+ clearTimeout(timeout);
134
+ GGLog.error(this, 'Error sending stop runtime command', error);
135
+ reject(error);
136
+ }
137
+ });
138
+ }
139
+
140
+ async shutdown(): Promise<void> {
141
+ if (!this.process) {
142
+ return;
143
+ }
144
+
145
+ GGLog.debug(this, 'Shutting down process...');
146
+
147
+ return new Promise<void>((resolve) => {
148
+ if (!this.process) {
149
+ resolve();
150
+ return;
151
+ }
152
+
153
+ this.process.once('exit', () => {
154
+ clearTimeout(forceKillTimeout);
155
+ this.process = undefined;
156
+ GGLog.debug(this, 'Process shut down');
157
+ resolve();
158
+ });
159
+
160
+ // Send shutdown message via stdin (works cross-platform, unlike SIGTERM on Windows)
161
+ try {
162
+ this.process.stdin?.write('SHUTDOWN\n');
163
+ this.process.stdin?.end();
164
+ GGLog.debug(this, 'Sent shutdown command via stdin');
165
+ } catch (error) {
166
+ GGLog.error(this, 'Error sending shutdown command', error);
167
+ }
168
+
169
+ // Force kill after 2 seconds if still running
170
+ const forceKillTimeout = setTimeout(() => {
171
+ if (this.process && !this.process.killed) {
172
+ GGLog.debug(this, 'Process did not exit gracefully, force killing');
173
+ this.process.kill('SIGKILL');
174
+ this.process = undefined;
175
+ }
176
+ }, 2000);
177
+ });
178
+ }
179
+ }
@@ -1,15 +1,15 @@
1
- export interface RuntimeRunner {
2
- start(): Promise<void>;
3
-
4
- /**
5
- * Stop the GGRuntime (teardown services) but keep the worker/IPC alive.
6
- * This allows log retrieval after the runtime has stopped.
7
- */
8
- stopRuntime(): Promise<void>;
9
-
10
- /**
11
- * Fully shutdown the worker, including IPC disconnection.
12
- * After this, no commands can be sent.
13
- */
14
- shutdown(): Promise<void>;
15
- }
1
+ export interface RuntimeRunner {
2
+ start(): Promise<void>;
3
+
4
+ /**
5
+ * Stop the GGRuntime (teardown services) but keep the worker/IPC alive.
6
+ * This allows log retrieval after the runtime has stopped.
7
+ */
8
+ stopRuntime(): Promise<void>;
9
+
10
+ /**
11
+ * Fully shutdown the worker, including IPC disconnection.
12
+ * After this, no commands can be sent.
13
+ */
14
+ shutdown(): Promise<void>;
15
+ }