Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support standalone mode #5565

Merged
merged 9 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 4 additions & 3 deletions docs/guide/cli-table.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
| `--coverage.cleanOnRerun` | Clean coverage report on watch rerun (default: true) |
| `--coverage.reportsDirectory <path>` | Directory to write coverage report to (default: ./coverage) |
| `--coverage.reporter <name>` | Coverage reporters to use. Visit [`coverage.reporter`](https://vitest.dev/config/#coverage-reporter) for more information (default: `["text", "html", "clover", "json"]`) |
| `--coverage.reportOnFailure` | Generate coverage report even when tests fail (default: false) |
| `--coverage.allowExternal` | Collect coverage of files outside the project root (default: false) |
| `--coverage.skipFull` | Do not show files with 100% statement, branch, and function coverage (default: false) |
| `--coverage.reportOnFailure` | Generate coverage report even when tests fail (default: `false`) |
| `--coverage.allowExternal` | Collect coverage of files outside the project root (default: `false`) |
| `--coverage.skipFull` | Do not show files with 100% statement, branch, and function coverage (default: `false`) |
| `--coverage.thresholds.100` | Shortcut to set all coverage thresholds to 100 (default: `false`) |
| `--coverage.thresholds.perFile` | Check thresholds per file. See `--coverage.thresholds.lines`, `--coverage.thresholds.functions`, `--coverage.thresholds.branches` and `--coverage.thresholds.statements` for the actual thresholds (default: `false`) |
| `--coverage.thresholds.autoUpdate` | Update threshold values: "lines", "functions", "branches" and "statements" to configuration file when current coverage is above the configured thresholds (default: `false`) |
Expand Down Expand Up @@ -119,3 +119,4 @@
| `--segfaultRetry <times>` | Retry the test suite if it crashes due to a segfault (default: `true`) |
| `--no-color` | Removes colors from the console output |
| `--clearScreen` | Clear terminal screen when re-running tests during watch mode (default: `true`) |
| `--standalone` | Start Vitest without running tests. File filters will be ignored, tests will be running only on change (default: `false`) |
26 changes: 25 additions & 1 deletion packages/ui/client/composables/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { WebSocketStatus } from '@vueuse/core'
import type { ErrorWithDiff, File, ResolvedConfig } from 'vitest'
import type { Ref } from 'vue'
import { reactive } from 'vue'
import { relative } from 'pathe'
import { generateHash } from '@vitest/runner/utils'
import type { RunState } from '../../../types'
import { ENTRY_URL, isReport } from '../../constants'
import { parseError } from '../error'
Expand Down Expand Up @@ -84,7 +86,29 @@ watch(
client.rpc.getConfig(),
client.rpc.getUnhandledErrors(),
])
client.state.collectFiles(files)
if (_config.standalone) {
const filenames = await client.rpc.getTestFiles()
const files = filenames.map<File>(([name, filepath]) => {
const path = relative(_config.root, filepath)
return {
filepath,
name: path,
id: /* #__PURE__ */ generateHash(`${path}${name || ''}`),
mode: 'skip',
type: 'suite',
result: {
state: 'skip',
},
meta: {},
tasks: [],
projectName: name,
}
})
client.state.collectFiles(files)
}
else {
client.state.collectFiles(files)
}
unhandledErrors.value = (errors || []).map(parseError)
config.value = _config
})
Expand Down
4 changes: 4 additions & 0 deletions packages/vitest/src/api/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ export function setup(vitestOrWorkspace: Vitest | WorkspaceProject, _server?: Vi
getProvidedContext() {
return 'ctx' in vitestOrWorkspace ? vitestOrWorkspace.getProvidedContext() : ({} as any)
},
async getTestFiles() {
const spec = await ctx.globTestFiles()
return spec.map(([project, file]) => [project.getName(), file]) as [string, string][]
},
},
{
post: msg => ws.send(msg),
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface WebSocketHandlers {
getCountOfFailedTests: () => number
sendLog: (log: UserConsoleLog) => void
getFiles: () => File[]
getTestFiles: () => Promise<[name: string, file: string][]>
getPaths: () => string[]
getConfig: () => ResolvedConfig
resolveSnapshotPath: (testPath: string) => string
Expand Down
10 changes: 8 additions & 2 deletions packages/vitest/src/node/cli/cli-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,17 @@ export async function startVitest(
})

ctx.onAfterSetServer(() => {
ctx.start(cliFilters)
if (ctx.config.standalone)
ctx.init()
else
ctx.start(cliFilters)
})

try {
await ctx.start(cliFilters)
if (ctx.config.standalone)
await ctx.init()
else
await ctx.start(cliFilters)
}
catch (e) {
process.exitCode = 1
Expand Down
9 changes: 6 additions & 3 deletions packages/vitest/src/node/cli/cli-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,13 @@ export const cliOptionsConfig: VitestCLIOptions = {
array: true,
},
reportOnFailure: {
description: 'Generate coverage report even when tests fail (default: false)',
description: 'Generate coverage report even when tests fail (default: `false`)',
},
allowExternal: {
description: 'Collect coverage of files outside the project root (default: false)',
description: 'Collect coverage of files outside the project root (default: `false`)',
},
skipFull: {
description: 'Do not show files with 100% statement, branch, and function coverage (default: false)',
description: 'Do not show files with 100% statement, branch, and function coverage (default: `false`)',
},
thresholds: {
description: null,
Expand Down Expand Up @@ -601,6 +601,9 @@ export const cliOptionsConfig: VitestCLIOptions = {
clearScreen: {
description: 'Clear terminal screen when re-running tests during watch mode (default: `true`)',
},
standalone: {
description: 'Start Vitest without running tests. File filters will be ignored, tests will be running only on change (default: `false`)',
},

// disable CLI options
cliExclude: null,
Expand Down
3 changes: 3 additions & 0 deletions packages/vitest/src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ export function resolveConfig(
resolved.shard = { index, count }
}

if (resolved.standalone && !resolved.watch)
throw new Error(`Vitest standalone mode requires --watch`)

if (resolved.maxWorkers)
resolved.maxWorkers = Number(resolved.maxWorkers)

Expand Down
45 changes: 40 additions & 5 deletions packages/vitest/src/node/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type { CancelReason, File } from '@vitest/runner'
import { ViteNodeServer } from 'vite-node/server'
import type { defineWorkspace } from 'vitest/config'
import type { ArgumentsType, CoverageProvider, OnServerRestartHandler, Reporter, ResolvedConfig, UserConfig, UserWorkspaceConfig, VitestRunMode } from '../types'
import { hasFailed, noop, slash, toArray, wildcardPatternToRegExp } from '../utils'
import { getTasks, hasFailed, noop, slash, toArray, wildcardPatternToRegExp } from '../utils'
import { getCoverageProvider } from '../integrations/coverage'
import { CONFIG_NAMES, configFiles, workspacesFiles as workspaceFiles } from '../constants'
import { rootDir } from '../paths'
Expand Down Expand Up @@ -422,6 +422,25 @@ export class Vitest {
await this.report('onWatcherStart')
}

async init() {
this._onClose = []

try {
await this.initCoverageProvider()
await this.coverageProvider?.clean(this.config.coverage.clean)
await this.initBrowserProviders()
}
finally {
await this.report('onInit', this)
}

// populate test files cache so watch mode can trigger a file rerun
await this.globTestFiles()

if (this.config.watch)
await this.report('onWatcherStart')
}

private async getTestDependencies(filepath: WorkspaceSpec, deps = new Set<string>()) {
const addImports = async ([project, filepath]: WorkspaceSpec) => {
if (deps.has(filepath))
Expand Down Expand Up @@ -599,14 +618,24 @@ export class Vitest {
if (pattern === '')
this.filenamePattern = undefined

this.configOverride.testNamePattern = pattern ? new RegExp(pattern) : undefined
const testNamePattern = pattern ? new RegExp(pattern) : undefined
this.configOverride.testNamePattern = testNamePattern
// filter only test files that have tests matching the pattern
if (testNamePattern) {
files = files.filter((filepath) => {
const files = this.state.getFiles([filepath])
return !files.length || files.some((file) => {
const tasks = getTasks(file)
return !tasks.length || tasks.some(task => testNamePattern.test(task.name))
})
})
}
await this.rerunFiles(files, trigger)
}

async changeFilenamePattern(pattern: string) {
async changeFilenamePattern(pattern: string, files: string[] = this.state.getFilepaths()) {
this.filenamePattern = pattern

const files = this.state.getFilepaths()
const trigger = this.filenamePattern ? 'change filename pattern' : 'reset filename pattern'

await this.rerunFiles(files, trigger)
Expand Down Expand Up @@ -750,8 +779,10 @@ export class Vitest {

const matchingProjects: WorkspaceProject[] = []
await Promise.all(this.projects.map(async (project) => {
if (await project.isTargetFile(id))
if (await project.isTargetFile(id)) {
matchingProjects.push(project)
project.testFilesList?.push(id)
}
}))

if (matchingProjects.length > 0) {
Expand Down Expand Up @@ -932,6 +963,10 @@ export class Vitest {
)))
}

public async getTestFilepaths() {
return this.globTestFiles().then(files => files.map(([, file]) => file))
}

public async globTestFiles(filters: string[] = []) {
const files: WorkspaceSpec[] = []
await Promise.all(this.projects.map(async (project) => {
Expand Down
5 changes: 4 additions & 1 deletion packages/vitest/src/node/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,10 @@ export class Logger {
if (this.ctx.coverageProvider)
this.log(c.dim(' Coverage enabled with ') + c.yellow(this.ctx.coverageProvider.name))

this.log()
if (this.ctx.config.standalone)
this.log(c.yellow(`\nVitest is running in standalone mode. Edit a test file to rerun tests.`))
else
this.log()
}

async printUnhandledErrors(errors: unknown[]) {
Expand Down
22 changes: 17 additions & 5 deletions packages/vitest/src/node/stdin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import readline from 'node:readline'
import type { Writable } from 'node:stream'
import c from 'picocolors'
import prompt from 'prompts'
import { relative } from 'pathe'
import { relative, resolve } from 'pathe'
import { getTests, isWindows, stdout } from '../utils'
import { toArray } from '../utils/base'
import type { Vitest } from './core'
Expand Down Expand Up @@ -73,8 +73,10 @@ export function registerConsoleShortcuts(ctx: Vitest, stdin: NodeJS.ReadStream =
if (name === 'u')
return ctx.updateSnapshot()
// rerun all tests
if (name === 'a' || name === 'return')
return ctx.changeNamePattern('')
if (name === 'a' || name === 'return') {
const files = await ctx.getTestFilepaths()
return ctx.changeNamePattern('', files, 'rerun all tests')
}
// rerun current pattern tests
if (name === 'r')
return ctx.rerunFiles()
Expand Down Expand Up @@ -113,7 +115,13 @@ export function registerConsoleShortcuts(ctx: Vitest, stdin: NodeJS.ReadStream =
})

on()
await ctx.changeNamePattern(filter?.trim() || '', undefined, 'change pattern')
const files = ctx.state.getFilepaths()
// if running in standalone mode, Vitest instance doesn't know about any test file
const cliFiles = ctx.config.standalone && !files.length
? await ctx.getTestFilepaths()
: undefined

await ctx.changeNamePattern(filter?.trim() || '', cliFiles, 'change pattern')
}

async function inputProjectName() {
Expand Down Expand Up @@ -143,8 +151,12 @@ export function registerConsoleShortcuts(ctx: Vitest, stdin: NodeJS.ReadStream =
on()

latestFilename = filter?.trim() || ''
const lastResults = watchFilter.getLastResults()

await ctx.changeFilenamePattern(latestFilename)
await ctx.changeFilenamePattern(
latestFilename,
filter && lastResults.length ? lastResults.map(i => resolve(ctx.config.root, i)) : undefined,
)
}

let rl: readline.Interface | undefined
Expand Down
4 changes: 4 additions & 0 deletions packages/vitest/src/node/watch-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,8 @@ export class WatchFilter {
// @ts-expect-error -- write() method has different signature on the union type
this.stdout.write(data)
}

public getLastResults() {
return this.results
}
}
9 changes: 9 additions & 0 deletions packages/vitest/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,15 @@ export interface UserConfig extends InlineConfig {
*/
config?: string | false | undefined

/**
* Do not run tests when Vitest starts.
*
* Vitest will only run tests if it's called programmatically or the test file changes.
*
* CLI file filters will be ignored.
*/
standalone?: boolean

/**
* Use happy-dom
*/
Expand Down
4 changes: 3 additions & 1 deletion test/reporters/tests/default.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ describe('default reporter', async () => {
// one file
vitest.write('p')
await vitest.waitForStdout('Input filename pattern')
vitest.write('a\n')
vitest.write('a')
await vitest.waitForStdout('a.test.ts')
vitest.write('\n')
await vitest.waitForStdout('Filename pattern: a')
await vitest.waitForStdout('Waiting for file changes...')
expect(vitest.stdout).contain('✓ a1 test')
Expand Down
Loading
Loading