prool 0.0.0 → 0.0.1

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/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/_lib/exports/index.d.ts +4 -0
  4. package/_lib/exports/index.d.ts.map +1 -0
  5. package/_lib/exports/index.js +4 -0
  6. package/_lib/exports/index.js.map +1 -0
  7. package/_lib/exports/instances.d.ts +2 -0
  8. package/_lib/exports/instances.d.ts.map +1 -0
  9. package/_lib/exports/instances.js +2 -0
  10. package/_lib/exports/instances.js.map +1 -0
  11. package/_lib/instance.d.ts +111 -0
  12. package/_lib/instance.d.ts.map +1 -0
  13. package/_lib/instance.js +141 -0
  14. package/_lib/instance.js.map +1 -0
  15. package/_lib/instances/anvil.d.ts +459 -0
  16. package/_lib/instances/anvil.d.ts.map +1 -0
  17. package/_lib/instances/anvil.js +74 -0
  18. package/_lib/instances/anvil.js.map +1 -0
  19. package/_lib/pool.d.ts +38 -0
  20. package/_lib/pool.d.ts.map +1 -0
  21. package/_lib/pool.js +134 -0
  22. package/_lib/pool.js.map +1 -0
  23. package/_lib/proxy.d.ts +21 -0
  24. package/_lib/proxy.d.ts.map +1 -0
  25. package/_lib/proxy.js +93 -0
  26. package/_lib/proxy.js.map +1 -0
  27. package/_lib/utils.d.ts +35 -0
  28. package/_lib/utils.d.ts.map +1 -0
  29. package/_lib/utils.js +71 -0
  30. package/_lib/utils.js.map +1 -0
  31. package/exports/index.test.ts +12 -0
  32. package/exports/index.ts +20 -0
  33. package/exports/instances.test.ts +10 -0
  34. package/exports/instances.ts +1 -0
  35. package/instance.test.ts +311 -0
  36. package/instance.ts +277 -0
  37. package/instances/anvil.test.ts +130 -0
  38. package/instances/anvil.ts +322 -0
  39. package/package.json +39 -8
  40. package/pool.test.ts +209 -0
  41. package/pool.ts +183 -0
  42. package/proxy.test.ts +294 -0
  43. package/proxy.ts +135 -0
  44. package/tsconfig.build.tsbuildinfo +1 -0
  45. package/utils.test.ts +44 -0
  46. package/utils.ts +92 -0
@@ -0,0 +1,311 @@
1
+ import { expect, test } from 'vitest'
2
+ import { defineInstance } from './instance.js'
3
+
4
+ test('default', async () => {
5
+ let started = false
6
+ const foo = defineInstance(() => {
7
+ return {
8
+ name: 'foo',
9
+ host: 'localhost',
10
+ port: 3000,
11
+ async start() {
12
+ started = true
13
+ },
14
+ async stop() {
15
+ started = false
16
+ },
17
+ }
18
+ })
19
+
20
+ const instance = foo()
21
+
22
+ expect(started).toEqual(false)
23
+ await instance.start()
24
+ expect(started).toEqual(true)
25
+ await instance.stop()
26
+ expect(started).toEqual(false)
27
+ })
28
+
29
+ test('behavior: parameters', async () => {
30
+ let started = [false, {}]
31
+ const foo = defineInstance((parameters: { bar: string }) => {
32
+ return {
33
+ name: 'foo',
34
+ host: 'localhost',
35
+ port: 3000,
36
+ async start() {
37
+ started = [true, parameters]
38
+ },
39
+ async stop() {
40
+ started = [false, {}]
41
+ },
42
+ }
43
+ })
44
+
45
+ const instance = foo({ bar: 'baz' })
46
+
47
+ expect(started).toEqual([false, {}])
48
+ await instance.start()
49
+ expect(started).toEqual([true, { bar: 'baz' }])
50
+ await instance.stop()
51
+ expect(started).toEqual([false, {}])
52
+ })
53
+
54
+ test('behavior: start', async () => {
55
+ let count = 0
56
+ const foo = defineInstance(() => {
57
+ return {
58
+ name: 'foo',
59
+ host: 'localhost',
60
+ port: 3000,
61
+ async start() {
62
+ count++
63
+ },
64
+ async stop() {},
65
+ }
66
+ })
67
+
68
+ const instance = foo()
69
+
70
+ expect(instance.status).toEqual('idle')
71
+
72
+ const promise_1 = instance.start()
73
+ expect(instance.status).toEqual('starting')
74
+
75
+ const promise_2 = instance.start()
76
+ expect(instance.status).toEqual('starting')
77
+
78
+ expect(promise_1).toStrictEqual(promise_2)
79
+ expect(count).toEqual(1)
80
+
81
+ await promise_1
82
+
83
+ expect(instance.status).toEqual('started')
84
+
85
+ instance.stop()
86
+ expect(instance.status).toEqual('stopping')
87
+
88
+ expect(() => instance.start()).rejects.toThrowErrorMatchingInlineSnapshot(
89
+ `[Error: Instance "foo" is not in an idle or stopped state. Status: stopping]`,
90
+ )
91
+ })
92
+
93
+ test('behavior: start (error)', async () => {
94
+ const foo = defineInstance(() => {
95
+ return {
96
+ name: 'foo',
97
+ host: 'localhost',
98
+ port: 3000,
99
+ async start() {
100
+ throw new Error('oh no')
101
+ },
102
+ async stop() {},
103
+ }
104
+ })
105
+
106
+ const instance = foo()
107
+
108
+ expect(instance.status).toEqual('idle')
109
+ await expect(instance.start()).rejects.toThrowErrorMatchingInlineSnapshot(
110
+ '[Error: oh no]',
111
+ )
112
+ expect(instance.status).toEqual('idle')
113
+ })
114
+
115
+ test('behavior: stop', async () => {
116
+ let count = 0
117
+ const foo = defineInstance(() => {
118
+ return {
119
+ name: 'foo',
120
+ host: 'localhost',
121
+ port: 3000,
122
+ async start() {},
123
+ async stop() {
124
+ count++
125
+ },
126
+ }
127
+ })
128
+
129
+ const instance = foo()
130
+ await instance.start()
131
+
132
+ const promise_1 = instance.stop()
133
+ expect(instance.status).toEqual('stopping')
134
+ const promise_2 = instance.stop()
135
+ expect(instance.status).toEqual('stopping')
136
+
137
+ expect(promise_1).toStrictEqual(promise_2)
138
+ expect(count).toEqual(1)
139
+
140
+ await promise_1
141
+
142
+ expect(instance.status).toEqual('stopped')
143
+
144
+ instance.start()
145
+ expect(instance.status).toEqual('starting')
146
+
147
+ expect(() => instance.stop()).rejects.toThrowErrorMatchingInlineSnapshot(
148
+ `[Error: Instance "foo" is starting.]`,
149
+ )
150
+ })
151
+
152
+ test('behavior: stop (error)', async () => {
153
+ const foo = defineInstance(() => {
154
+ return {
155
+ name: 'foo',
156
+ host: 'localhost',
157
+ port: 3000,
158
+ async start() {},
159
+ async stop() {
160
+ throw new Error('oh no')
161
+ },
162
+ }
163
+ })
164
+
165
+ const instance = foo()
166
+
167
+ await instance.start()
168
+ expect(instance.status).toEqual('started')
169
+
170
+ await expect(instance.stop()).rejects.toThrowErrorMatchingInlineSnapshot(
171
+ '[Error: oh no]',
172
+ )
173
+ expect(instance.status).toEqual('started')
174
+ })
175
+
176
+ test('behavior: events', async () => {
177
+ const foo = defineInstance(() => {
178
+ let count = 0
179
+ return {
180
+ name: 'foo',
181
+ host: 'localhost',
182
+ port: 3000,
183
+ async start({ emitter }) {
184
+ emitter.emit('message', count.toString())
185
+ emitter.emit('listening')
186
+ if (count > 0) emitter.emit('stderr', 'stderr')
187
+ else emitter.emit('stdout', 'stdout')
188
+ count++
189
+ },
190
+ async stop({ emitter }) {
191
+ emitter.emit('exit', 0, 'SIGTERM')
192
+ emitter.emit('message', 'goodbye')
193
+ },
194
+ }
195
+ })
196
+
197
+ const listening = Promise.withResolvers<void>()
198
+ const message_1 = Promise.withResolvers<string>()
199
+ const stdout = Promise.withResolvers<string>()
200
+ const stderr = Promise.withResolvers<string>()
201
+ const exit = Promise.withResolvers<unknown>()
202
+
203
+ const instance = foo()
204
+ instance.once('listening', listening.resolve)
205
+ instance.once('message', message_1.resolve)
206
+ instance.once('stdout', stdout.resolve)
207
+ instance.once('stderr', stderr.resolve)
208
+ instance.once('exit', exit.resolve)
209
+
210
+ await instance.start()
211
+
212
+ await listening.promise
213
+ expect(await message_1.promise).toEqual('0')
214
+ expect(await stdout.promise).toEqual('stdout')
215
+
216
+ const message_2 = Promise.withResolvers()
217
+ instance.once('message', message_2.resolve)
218
+
219
+ await instance.stop()
220
+
221
+ expect(await message_2.promise).toEqual('goodbye')
222
+ await exit.promise
223
+
224
+ const message_3 = Promise.withResolvers()
225
+ instance.once('message', message_3.resolve)
226
+
227
+ await instance.start()
228
+
229
+ expect(await message_3.promise).toEqual('1')
230
+ expect(await stderr.promise).toEqual('stderr')
231
+ })
232
+
233
+ test('behavior: messages', async () => {
234
+ const foo = defineInstance(() => {
235
+ return {
236
+ name: 'foo',
237
+ host: 'localhost',
238
+ port: 3000,
239
+ async start({ emitter }) {
240
+ for (let i = 0; i < 50; i++) emitter.emit('message', i.toString())
241
+ },
242
+ async stop() {},
243
+ }
244
+ })
245
+
246
+ const instance = foo()
247
+ expect(instance.messages.get()).toEqual([])
248
+
249
+ await instance.start()
250
+ expect(instance.messages.get()).toMatchInlineSnapshot(`
251
+ [
252
+ "30",
253
+ "31",
254
+ "32",
255
+ "33",
256
+ "34",
257
+ "35",
258
+ "36",
259
+ "37",
260
+ "38",
261
+ "39",
262
+ "40",
263
+ "41",
264
+ "42",
265
+ "43",
266
+ "44",
267
+ "45",
268
+ "46",
269
+ "47",
270
+ "48",
271
+ "49",
272
+ ]
273
+ `)
274
+ })
275
+
276
+ test('options: timeout', async () => {
277
+ const foo = defineInstance(() => {
278
+ return {
279
+ name: 'foo',
280
+ host: 'localhost',
281
+ port: 3000,
282
+ async start() {
283
+ await new Promise((resolve) => setTimeout(resolve, 200))
284
+ },
285
+ async stop() {},
286
+ }
287
+ })
288
+
289
+ const instance_1 = foo({ timeout: 100 })
290
+ await expect(() => instance_1.start()).rejects.toThrow(
291
+ 'Instance "foo" failed to start in time',
292
+ )
293
+
294
+ const bar = defineInstance(() => {
295
+ return {
296
+ name: 'bar',
297
+ host: 'localhost',
298
+ port: 3000,
299
+ async start() {},
300
+ async stop() {
301
+ await new Promise((resolve) => setTimeout(resolve, 200))
302
+ },
303
+ }
304
+ })
305
+
306
+ const instance_2 = bar({ timeout: 100 })
307
+ await instance_2.start()
308
+ await expect(() => instance_2.stop()).rejects.toThrow(
309
+ 'Instance "bar" failed to stop in time',
310
+ )
311
+ })
package/instance.ts ADDED
@@ -0,0 +1,277 @@
1
+ import { EventEmitter } from 'eventemitter3'
2
+
3
+ type EventTypes = {
4
+ exit: [code: number | null, signal: NodeJS.Signals | null]
5
+ listening: []
6
+ message: [message: string]
7
+ stderr: [message: string]
8
+ stdout: [message: string]
9
+ }
10
+
11
+ type InstanceStartOptions_internal = {
12
+ emitter: EventEmitter<EventTypes>
13
+ status: Instance['status']
14
+ }
15
+ type InstanceStopOptions_internal = {
16
+ emitter: EventEmitter<EventTypes>
17
+ status: Instance['status']
18
+ }
19
+
20
+ export type InstanceStartOptions = {
21
+ /**
22
+ * Port to start the instance on.
23
+ */
24
+ port?: number | undefined
25
+ }
26
+
27
+ export type DefineInstanceFn<
28
+ parameters,
29
+ _internal extends object | undefined = object | undefined,
30
+ > = (parameters: parameters) => Pick<Instance, 'host' | 'name' | 'port'> & {
31
+ _internal?: _internal | undefined
32
+ start(
33
+ options: InstanceStartOptions & InstanceStartOptions_internal,
34
+ ): Promise<void>
35
+ stop(options: InstanceStopOptions_internal): Promise<void>
36
+ }
37
+
38
+ export type Instance<
39
+ _internal extends object | undefined = object | undefined,
40
+ > = Pick<
41
+ EventEmitter<EventTypes>,
42
+ | 'addListener'
43
+ | 'off'
44
+ | 'on'
45
+ | 'once'
46
+ | 'removeAllListeners'
47
+ | 'removeListener'
48
+ > & {
49
+ _internal: _internal
50
+ /**
51
+ * Creates an instance.
52
+ */
53
+ create(
54
+ parameters?: { port?: number | undefined } | undefined,
55
+ ): Omit<Instance<_internal>, 'create'>
56
+ /**
57
+ * Host the instance is running on.
58
+ */
59
+ host: string
60
+ /**
61
+ * Name of the instance.
62
+ *
63
+ * @example "anvil"
64
+ */
65
+ name: string
66
+ /**
67
+ * Port the instance is running on.
68
+ */
69
+ port: number
70
+ /**
71
+ * Set of messages emitted from the `"message"` event stored in-memory,
72
+ * with length {@link InstanceOptions`messageBuffer`}.
73
+ * Useful for debugging.
74
+ *
75
+ * @example ["Listening on http://127.0.0.1", "Started successfully."]
76
+ */
77
+ messages: { clear(): void; get(): string[] }
78
+ /**
79
+ * Status of the instance.
80
+ *
81
+ * @default "idle"
82
+ */
83
+ status: 'idle' | 'stopped' | 'starting' | 'started' | 'stopping'
84
+ /**
85
+ * Starts the instance.
86
+ *
87
+ * @param options - Options for starting the instance.
88
+ * @returns A function to stop the instance.
89
+ */
90
+ start(): Promise<() => void>
91
+ /**
92
+ * Stops the instance.
93
+ */
94
+ stop(): Promise<void>
95
+ }
96
+
97
+ export type InstanceOptions = {
98
+ /** Number of messages to store in-memory. @default 20 */
99
+ messageBuffer?: number
100
+ /** Timeout (in milliseconds) for starting and stopping the instance. @default 10_000 */
101
+ timeout?: number
102
+ }
103
+
104
+ export type DefineInstanceReturnType<
105
+ _internal extends object | undefined = object | undefined,
106
+ parameters = undefined,
107
+ > = (
108
+ ...parameters: parameters extends undefined
109
+ ? [options?: InstanceOptions]
110
+ : [parameters: parameters, options?: InstanceOptions]
111
+ ) => Instance<_internal>
112
+
113
+ /**
114
+ * Creates an instance definition.
115
+ *
116
+ * @param fn - Function to define the instance.
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * const foo = defineInstance((parameters: FooParameters) => {
121
+ * return {
122
+ * name: 'foo',
123
+ * host: 'localhost',
124
+ * port: 3000,
125
+ * async start() {
126
+ * // ...
127
+ * },
128
+ * async stop() {
129
+ * // ...
130
+ * },
131
+ * }
132
+ * })
133
+ * ```
134
+ */
135
+ export function defineInstance<
136
+ _internal extends object | undefined,
137
+ parameters = undefined,
138
+ >(
139
+ fn: DefineInstanceFn<parameters, _internal>,
140
+ ): DefineInstanceReturnType<_internal, parameters> {
141
+ return (...[parametersOrOptions, options_]) => {
142
+ function create(createParameters: Parameters<Instance['create']>[0] = {}) {
143
+ const parameters = parametersOrOptions as parameters
144
+ const options = options_ || parametersOrOptions || {}
145
+
146
+ const { _internal, host, name, port, start, stop } = {
147
+ ...fn(parameters),
148
+ ...createParameters,
149
+ }
150
+ const { messageBuffer = 20, timeout = 10_000 } = options
151
+
152
+ let startResolver = Promise.withResolvers<() => void>()
153
+ let stopResolver = Promise.withResolvers<void>()
154
+
155
+ const emitter = new EventEmitter<EventTypes>()
156
+
157
+ let messages: string[] = []
158
+ let status: Instance['status'] = 'idle'
159
+
160
+ function onExit() {
161
+ status = 'stopped'
162
+ }
163
+ function onListening() {
164
+ status = 'started'
165
+ }
166
+ function onMessage(message: string) {
167
+ messages.push(message)
168
+ if (messages.length > messageBuffer) messages.shift()
169
+ }
170
+
171
+ return {
172
+ _internal: _internal as _internal,
173
+ messages: {
174
+ clear() {
175
+ messages = []
176
+ },
177
+ get() {
178
+ return messages
179
+ },
180
+ },
181
+ host,
182
+ name,
183
+ port,
184
+ get status() {
185
+ return status
186
+ },
187
+ async start() {
188
+ if (status === 'starting') return startResolver.promise
189
+ if (status !== 'idle' && status !== 'stopped')
190
+ throw new Error(
191
+ `Instance "${name}" is not in an idle or stopped state. Status: ${status}`,
192
+ )
193
+
194
+ if (typeof timeout === 'number') {
195
+ const timer = setTimeout(() => {
196
+ clearTimeout(timer)
197
+ startResolver.reject(
198
+ new Error(`Instance "${name}" failed to start in time.`),
199
+ )
200
+ }, timeout)
201
+ }
202
+
203
+ emitter.on('message', onMessage)
204
+ emitter.on('listening', onListening)
205
+ emitter.on('exit', onExit)
206
+
207
+ status = 'starting'
208
+ start({
209
+ emitter,
210
+ port,
211
+ status: this.status,
212
+ })
213
+ .then(() => {
214
+ status = 'started'
215
+
216
+ stopResolver = Promise.withResolvers<void>()
217
+ startResolver.resolve(this.stop)
218
+ })
219
+ .catch((error) => {
220
+ status = 'idle'
221
+ this.messages.clear()
222
+ emitter.off('message', onMessage)
223
+ startResolver.reject(error)
224
+ })
225
+
226
+ return startResolver.promise
227
+ },
228
+ async stop() {
229
+ if (status === 'stopping') return stopResolver.promise
230
+ if (status === 'starting')
231
+ throw new Error(`Instance "${name}" is starting.`)
232
+
233
+ if (typeof timeout === 'number') {
234
+ const timer = setTimeout(() => {
235
+ clearTimeout(timer)
236
+ stopResolver.reject(
237
+ new Error(`Instance "${name}" failed to stop in time.`),
238
+ )
239
+ }, timeout)
240
+ }
241
+
242
+ status = 'stopping'
243
+ stop({
244
+ emitter,
245
+ status: this.status,
246
+ })
247
+ .then((...args) => {
248
+ status = 'stopped'
249
+ this.messages.clear()
250
+
251
+ emitter.off('message', onMessage)
252
+ emitter.off('listening', onListening)
253
+ emitter.off('exit', onExit)
254
+
255
+ startResolver = Promise.withResolvers<() => void>()
256
+ stopResolver.resolve(...args)
257
+ })
258
+ .catch((error) => {
259
+ status = 'started'
260
+ stopResolver.reject(error)
261
+ })
262
+
263
+ return stopResolver.promise
264
+ },
265
+
266
+ addListener: emitter.addListener.bind(emitter),
267
+ off: emitter.off.bind(emitter),
268
+ on: emitter.on.bind(emitter),
269
+ once: emitter.once.bind(emitter),
270
+ removeListener: emitter.removeListener.bind(emitter),
271
+ removeAllListeners: emitter.removeAllListeners.bind(emitter),
272
+ }
273
+ }
274
+
275
+ return Object.assign(create(), { create })
276
+ }
277
+ }
@@ -0,0 +1,130 @@
1
+ import { afterEach, expect, test } from 'vitest'
2
+ import type { Instance } from '../instance.js'
3
+ import { type AnvilParameters, anvil } from './anvil.js'
4
+
5
+ const instances: Instance[] = []
6
+ const timestamp = 1717114065
7
+
8
+ const defineInstance = (parameters: AnvilParameters = {}) => {
9
+ const instance = anvil(parameters)
10
+ instances.push(instance)
11
+ return instance
12
+ }
13
+
14
+ afterEach(async () => {
15
+ for (const instance of instances) await instance.stop().catch(() => {})
16
+ })
17
+
18
+ test('default', async () => {
19
+ const messages: string[] = []
20
+ const stdouts: string[] = []
21
+
22
+ const instance = defineInstance({ timestamp })
23
+
24
+ instance.on('message', (m) => messages.push(m))
25
+ instance.on('stdout', (m) => stdouts.push(m))
26
+
27
+ expect(instance.messages.get()).toMatchInlineSnapshot('[]')
28
+
29
+ await instance.start()
30
+ expect(instance.status).toEqual('started')
31
+
32
+ expect(messages.join('')).toBeDefined()
33
+ expect(stdouts.join('')).toBeDefined()
34
+ expect(instance.messages.get().join('')).toBeDefined()
35
+
36
+ await instance.stop()
37
+ expect(instance.status).toEqual('stopped')
38
+
39
+ expect(messages.join('')).toBeDefined()
40
+ expect(stdouts.join('')).toBeDefined()
41
+ expect(instance.messages.get()).toMatchInlineSnapshot('[]')
42
+ })
43
+
44
+ test('behavior: instance errored (duplicate ports)', async () => {
45
+ const instance_1 = defineInstance({ timestamp, port: 8545 })
46
+ const instance_2 = defineInstance({ timestamp, port: 8545 })
47
+
48
+ await instance_1.start()
49
+ await expect(() => instance_2.start()).rejects.toThrowError(
50
+ 'Failed to start anvil',
51
+ )
52
+ })
53
+
54
+ test('behavior: start and stop multiple times', async () => {
55
+ const instance = defineInstance()
56
+
57
+ await instance.start()
58
+ await instance.stop()
59
+ await instance.start()
60
+ await instance.stop()
61
+ await instance.start()
62
+ await instance.stop()
63
+ await instance.start()
64
+ await instance.stop()
65
+ })
66
+
67
+ test('behavior: can subscribe to stdout', async () => {
68
+ const messages: string[] = []
69
+ const instance = defineInstance({ timestamp })
70
+ instance.on('stdout', (message) => messages.push(message))
71
+
72
+ await instance.start()
73
+ expect(messages.length).toBeGreaterThanOrEqual(1)
74
+ })
75
+
76
+ test('behavior: can subscribe to stderr', async () => {
77
+ const messages: string[] = []
78
+
79
+ const instance_1 = defineInstance({ timestamp, port: 8545 })
80
+ const instance_2 = defineInstance({ timestamp, port: 8545 })
81
+
82
+ await instance_1.start()
83
+ instance_2.on('stderr', (message) => messages.push(message))
84
+ await expect(instance_2.start()).rejects.toThrow('Failed to start anvil')
85
+
86
+ expect(messages.length).toBeGreaterThanOrEqual(1)
87
+ expect(messages.join('')).toContain('Address already in use')
88
+ })
89
+
90
+ test('behavior: starts anvil with custom options', async () => {
91
+ const instance = defineInstance({
92
+ timestamp,
93
+ chainId: 123,
94
+ forkBlockNumber: 69420,
95
+ forkUrl: 'https://cloudflare-eth.com',
96
+ })
97
+
98
+ await instance.start()
99
+ })
100
+
101
+ test('behavior: exit', async () => {
102
+ const instance = defineInstance({ timestamp })
103
+
104
+ let exitCode: number | null | undefined = undefined
105
+ instance.on('exit', (code) => {
106
+ exitCode = code
107
+ })
108
+
109
+ await instance.start()
110
+ expect(instance.status).toEqual('started')
111
+
112
+ instance._internal.process.kill()
113
+
114
+ await new Promise<void>((res) => setTimeout(res, 100))
115
+ expect(instance.status).toEqual('stopped')
116
+ expect(typeof exitCode !== 'undefined').toBeTruthy()
117
+ })
118
+
119
+ test('behavior: exit when status is starting', async () => {
120
+ const instance = defineInstance({ timestamp })
121
+
122
+ const promise = instance.start()
123
+ expect(instance.status).toEqual('starting')
124
+
125
+ instance._internal.process.kill()
126
+
127
+ await expect(promise).rejects.toThrowErrorMatchingInlineSnapshot(
128
+ '[Error: Failed to start anvil: exited.]',
129
+ )
130
+ })