Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
262e0fe
feat: rewrite pools without `tinypool`
AriPerkkio Oct 13, 2025
e15e5ae
fix: use `addEventListener` on message channel port
AriPerkkio Oct 14, 2025
96c7587
fix: use `on` on message channel port instead
AriPerkkio Oct 14, 2025
9281338
fix: support browser pool
sheremet-va Oct 14, 2025
fff205a
Merge branch 'main' of github.com:vitest-dev/vitest into pr/AriPerkki…
sheremet-va Oct 14, 2025
ddb9480
fix: check environment
sheremet-va Oct 14, 2025
56de2f2
chore: lint test files
sheremet-va Oct 14, 2025
3c2a670
feat: support custom pool
sheremet-va Oct 14, 2025
d400dfa
fix: order -Infinity
sheremet-va Oct 14, 2025
4f830da
Merge branch 'main' of github.com:vitest-dev/vitest into pr/AriPerkki…
sheremet-va Oct 16, 2025
2a02ccf
fix: gracefully reject errors during deserialization
sheremet-va Oct 16, 2025
9f4d543
test: fix network test
sheremet-va Oct 16, 2025
a1b7701
docs: update cli docs
sheremet-va Oct 16, 2025
69dccdf
fix: import entry runtime as soon as possible
sheremet-va Oct 16, 2025
3a0b0c2
fix: gracefully cancel test run, make sure the "run" is finished befo…
sheremet-va Oct 16, 2025
98799b7
chore: rely on teardownTimeout instead of 1s instead
sheremet-va Oct 16, 2025
6f48b76
fix: return 1 sec to sigkill
sheremet-va Oct 16, 2025
153f8e1
chore: cancel task only after runtime is stopped
sheremet-va Oct 16, 2025
c122950
fix: use eventEmitter instead of custom listeners
sheremet-va Oct 16, 2025
6d58936
chore: fix cusotm pool test
sheremet-va Oct 16, 2025
9526a32
chore: update exports
sheremet-va Oct 16, 2025
5f15db5
fix: reuse browser pool
sheremet-va Oct 17, 2025
2f90c4e
fix: externalize events
sheremet-va Oct 17, 2025
b563f84
fix: handle potential errors during response
sheremet-va Oct 17, 2025
ad19f8f
fix: remove worker warmup for now, let's add it later as optimization…
AriPerkkio Oct 18, 2025
2cd9f0b
chore: add error handling to ts
sheremet-va Oct 18, 2025
cc9ee55
test: normalize lines between CI and local
AriPerkkio Oct 18, 2025
2973017
test: retry watch-mode test
AriPerkkio Oct 18, 2025
e5e9302
fix: pass `fileParallelism` to workers, fix workspace tests
AriPerkkio Oct 18, 2025
b5d1755
test: dont pass browser options as cli args without projects
AriPerkkio Oct 18, 2025
4542e77
test: vitest-in-vitest tests to use threads pool to avoid multiple pr…
AriPerkkio Oct 18, 2025
619d8ec
feat: rework custom pool API, update docs
AriPerkkio Oct 19, 2025
94a86fa
docs: migration guide
AriPerkkio Oct 20, 2025
d482a80
test: make `write-file-dynamic-import.test.ts` resilient to parallelism
AriPerkkio Oct 20, 2025
fa813fd
docs: updates
AriPerkkio Oct 20, 2025
5b69c04
test: add `pool.test.ts`
AriPerkkio Oct 20, 2025
5e43b7c
chore: remove commented question
AriPerkkio Oct 20, 2025
6ccb069
chore: add comment for SIGKILL
AriPerkkio Oct 20, 2025
827742e
Revert "test: vitest-in-vitest tests to use threads pool to avoid mul…
AriPerkkio Oct 20, 2025
1353d03
fix(pool): cache worker termination
sheremet-va Oct 20, 2025
eba28f7
test: browser test slow on CI
AriPerkkio Oct 20, 2025
91fd933
test: show a log if coverage test fails
sheremet-va Oct 20, 2025
9814128
Merge branch 'main' into fix/remove-tinypool
sheremet-va Oct 20, 2025
655671b
chore: remove closer
sheremet-va Oct 20, 2025
0502075
fix: shared runtime check
AriPerkkio Oct 20, 2025
49ff36c
chore: remove todo, fix local test
AriPerkkio Oct 20, 2025
6aa0155
fix: prevent calling `runtime.stop` twice
AriPerkkio Oct 20, 2025
103ac02
refactor: introduce PoolRuntimeWorker (name TBD)
sheremet-va Oct 20, 2025
41dad88
fix: workers to exit gracefully, simplify `init` arguments
AriPerkkio Oct 20, 2025
0a0a137
refactor: renaming
sheremet-va Oct 20, 2025
9c7468d
fix: update custom pool
sheremet-va Oct 20, 2025
65dfea9
chore: update exports
sheremet-va Oct 20, 2025
aeedd44
chore: license
sheremet-va Oct 20, 2025
d798468
refactor: `worker.removeAllListeners` -> `worker.teardown`
AriPerkkio Oct 21, 2025
cbdeac0
fix: check exitCode instead of `killed` to see if the process exited
sheremet-va Oct 21, 2025
cf0fd58
fix: await the schedule so try/catch can catch it
sheremet-va Oct 21, 2025
996a967
chore: cleanup
sheremet-va Oct 21, 2025
361e810
test: timings
sheremet-va Oct 21, 2025
dcc72aa
chore: remove await
sheremet-va Oct 21, 2025
bf2eae5
fix: rename `isTerminating` check, move it when everything is done
sheremet-va Oct 21, 2025
d30f06a
fix: bind emitWorkerError
AriPerkkio Oct 21, 2025
c23c3dd
chore: add guards against race conditions
sheremet-va Oct 21, 2025
efc6a7a
fix: add 'off' method to workers
AriPerkkio Oct 21, 2025
24d6df1
Merge branch 'main' into fix/remove-tinypool
AriPerkkio Oct 21, 2025
713b9f4
docs: custom pool off
AriPerkkio Oct 21, 2025
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
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ Avoid deps that has large transitive dependencies that results in bloated size c

If there are libraries that are needed and don't comply with our size
requirements, a fork can be tried to reduce its size while we work with them to
upstream our changes (see [tinypool](https://github.com/tinylibs/tinypool) for example)
upstream our changes.

### Think before adding yet another option

Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ Next generation testing framework powered by Vite.
- [JSDOM](https://github.com/jsdom/jsdom) and [happy-dom](https://github.com/capricorn86/happy-dom) for DOM and browser API mocking
- [Browser Mode](https://vitest.dev/guide/browser/) for running component tests in the browser
- Components testing ([Vue](https://github.com/vitest-tests/browser-examples/tree/main/examples/vue), [React](https://github.com/vitest-tests/browser-examples/tree/main/examples/react), [Svelte](https://github.com/vitest-tests/browser-examples/tree/main/examples/svelte), [Lit](./examples/lit), [Marko](https://github.com/marko-js/examples/tree/master/examples/library-ts))
- Workers multi-threading via [Tinypool](https://github.com/tinylibs/tinypool) (a lightweight fork of [Piscina](https://github.com/piscinajs/piscina))
- Benchmarking support with [Tinybench](https://github.com/tinylibs/tinybench)
- [Projects](https://vitest.dev/guide/projects) support
- [expect-type](https://github.com/mmkal/expect-type) for type-level testing
Expand Down
1 change: 0 additions & 1 deletion docs/.vitepress/components/FeaturesList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
<ListItem>Component testing for Vue, React, Svelte, Lit, Marko and more</ListItem>
<ListItem>Out-of-the-box TypeScript / JSX support</ListItem>
<ListItem>ESM first, top level await</ListItem>
<ListItem>Workers multi-threading via <a target="_blank" href="https://github.com/tinylibs/tinypool" rel="noopener noreferrer">Tinypool</a></ListItem>
<ListItem>Benchmarking support with <a target="_blank" href="https://github.com/tinylibs/tinybench" rel="noopener noreferrer">Tinybench</a></ListItem>
<ListItem>Filtering, timeouts, concurrent for suite and tests</ListItem>
<ListItem><a href="/guide/projects">Projects</a> support</ListItem>
Expand Down
6 changes: 5 additions & 1 deletion docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default ({ mode }: { mode: string }) => {
['link', { rel: 'icon', href: '/favicon.ico', sizes: '48x48' }],
['link', { rel: 'icon', href: '/logo.svg', sizes: 'any', type: 'image/svg+xml' }],
['meta', { name: 'author', content: `${teamMembers.map(c => c.name).join(', ')} and ${vitestName} contributors` }],
['meta', { name: 'keywords', content: 'vitest, vite, test, coverage, snapshot, react, vue, preact, svelte, solid, lit, marko, ruby, cypress, puppeteer, jsdom, happy-dom, test-runner, jest, typescript, esm, tinypool, tinyspy, node' }],
['meta', { name: 'keywords', content: 'vitest, vite, test, coverage, snapshot, react, vue, preact, svelte, solid, lit, marko, ruby, cypress, puppeteer, jsdom, happy-dom, test-runner, jest, typescript, esm, tinyspy, node' }],
['meta', { property: 'og:title', content: vitestName }],
['meta', { property: 'og:description', content: vitestDescription }],
['meta', { property: 'og:url', content: ogUrl }],
Expand Down Expand Up @@ -628,6 +628,10 @@ function guide(): DefaultTheme.SidebarItem[] {
},
],
},
{
text: 'Recipes',
link: '/guide/recipes',
},
]
}

Expand Down
129 changes: 90 additions & 39 deletions docs/advanced/pool.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,44 @@
# Custom Pool

::: warning
This is an advanced and very low-level API. If you just want to [run tests](/guide/), you probably don't need this. It is primarily used by library authors.
This is an advanced, experimental and very low-level API. If you just want to [run tests](/guide/), you probably don't need this. It is primarily used by library authors.
:::

Vitest runs tests in pools. By default, there are several pools:
Vitest runs tests in a pool. By default, there are several pool runners:

- `threads` to run tests using `node:worker_threads` (isolation is provided with a new worker context)
- `forks` to run tests using `node:child_process` (isolation is provided with a new `child_process.fork` process)
- `vmThreads` to run tests using `node:worker_threads` (but isolation is provided with `vm` module instead of a new worker context)
- `browser` to run tests using browser providers
- `typescript` to run typechecking on tests

You can provide your own pool by specifying a file path:
::: tip
See [`vitest-pool-example`](https://www.npmjs.com/package/vitest-pool-example) for example of a custom pool runner implementation.
:::

## Usage

You can provide your own pool runner by a function that returns `PoolRunnerInitializer`.

```ts [vitest.config.ts]
import { defineConfig } from 'vitest/config'
import customPool from './my-custom-pool.ts'

export default defineConfig({
test: {
// will run every file with a custom pool by default
pool: './my-custom-pool.ts',
// you can provide options using `poolOptions` object
poolOptions: {
myCustomPool: {
customProperty: true,
},
},
pool: customPool({
customProperty: true,
})
},
})
```

If you need to run tests in different pools, use the [`projects`](/guide/projects) feature:

```ts [vitest.config.ts]
import customPool from './my-custom-pool.ts'

export default defineConfig({
test: {
projects: [
Expand All @@ -43,54 +48,100 @@ export default defineConfig({
pool: 'threads',
},
},
{
extends: true,
test: {
pool: customPool({
customProperty: true,
})
}
}
],
},
})
```

## API

The file specified in `pool` option should export a function (can be async) that accepts `Vitest` interface as its first option. This function needs to return an object matching `ProcessPool` interface:
The `pool` option accepts a `PoolRunnerInitializer` that can be used for custom pool runners. The `name` property should indicate name of the custom pool runner. It should be identical with your worker's `name` property.

```ts
import type { ProcessPool, TestSpecification } from 'vitest/node'
```ts [my-custom-pool.ts]
import type { PoolRunnerInitializer } from 'vitest/node'

export interface ProcessPool {
name: string
runTests: (files: TestSpecification[], invalidates?: string[]) => Promise<void>
collectTests: (files: TestSpecification[], invalidates?: string[]) => Promise<void>
close?: () => Promise<void>
export function customPool(customOptions: CustomOptions): PoolRunnerInitializer {
return {
name: 'custom-pool',
createPoolWorker: options => new CustomPoolWorker(options, customOptions),
}
}
```

The function is called only once (unless the server config was updated), and it's generally a good idea to initialize everything you need for tests inside that function and reuse it when `runTests` is called.
In your `CustomPoolWorker` you need to define all required methods:

```ts [my-custom-pool.ts]
import type { PoolOptions, PoolWorker, WorkerRequest } from 'vitest/node'

Vitest calls `runTest` when new tests are scheduled to run. It will not call it if `files` is empty. The first argument is an array of [TestSpecifications](/advanced/api/test-specification). Files are sorted using [`sequencer`](/config/#sequence-sequencer) before `runTests` is called. It's possible (but unlikely) to have the same file twice, but it will always have a different project - this is implemented via [`projects`](/guide/projects) configuration.
class CustomPoolWorker implements PoolWorker {
name = 'custom-pool'
private customOptions: CustomOptions

Vitest will wait until `runTests` is executed before finishing a run (i.e., it will emit [`onTestRunEnd`](/advanced/reporters) only after `runTests` is resolved).
constructor(options: PoolOptions, customOptions: CustomOptions) {
this.customOptions = customOptions
}

If you are using a custom pool, you will have to provide test files and their results yourself - you can reference [`vitest.state`](https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/node/state.ts) for that (most important are `collectFiles` and `updateTasks`). Vitest uses `startTests` function from `@vitest/runner` package to do that.
send(message: WorkerRequest): void {
// Provide way to send your worker a message
}

Vitest will call `collectTests` if `vitest.collect` is called or `vitest list` is invoked via a CLI command. It works the same way as `runTests`, but you don't have to run test callbacks, only report their tasks by calling `vitest.state.collectFiles(files)`.
on(event: string, callback: (arg: any) => void): void {
// Provide way to listen to your workers events, e.g. message, error, exit
}

To communicate between different processes, you can create methods object using `createMethodsRPC` from `vitest/node`, and use any form of communication that you prefer. For example, to use WebSockets with `birpc` you can write something like this:
off(event: string, callback: (arg: any) => void): void {
// Provide way to unsubscribe `on` listeners
}

```ts
import { createBirpc } from 'birpc'
import { parse, stringify } from 'flatted'
import { createMethodsRPC, TestProject } from 'vitest/node'
async start() {
// do something when the worker is started
}

function createRpc(project: TestProject, wss: WebSocketServer) {
return createBirpc(
createMethodsRPC(project),
{
post: msg => wss.send(msg),
on: fn => wss.on('message', fn),
serialize: stringify,
deserialize: parse,
},
)
async stop() {
// cleanup the state
}

deserialize(data) {
return data
}
}
```

You can see a simple example of a pool made from scratch that doesn't run tests but marks them as collected in [pool/custom-pool.ts](https://github.com/vitest-dev/vitest/blob/main/test/cli/fixtures/custom-pool/pool/custom-pool.ts).
Your `CustomPoolRunner` will be controlling how your custom test runner worker life cycles and communication channel works. For example, your `CustomPoolRunner` could launch a `node:worker_threads` `Worker`, and provide communication via `Worker.postMessage` and `parentPort`.

In your worker file, you can import helper utilities from `vitest/worker`:

```ts [my-worker.ts]
import { init, runBaseTests } from 'vitest/worker'

init({
post: (response) => {
// Provide way to send this message to CustomPoolRunner's onWorker as message event
},
on: (callback) => {
// Provide a way to listen CustomPoolRunner's "postMessage" calls
},
off: (callback) => {
// Optional, provide a way to remove listeners added by "on" calls
},
teardown: () => {
// Optional, provide a way to teardown worker, e.g. unsubscribe all the `on` listeners
},
serialize: (value) => {
// Optional, provide custom serializer for `post` calls
},
deserialize: (value) => {
// Optional, provide custom deserializer for `on` callbacks
},
runTests: state => runBaseTests('run', state),
collectTests: state => runBaseTests('collect', state),
})
```
Loading
Loading