Skip to content

Commit

Permalink
refactor: organize worker
Browse files Browse the repository at this point in the history
(doesn't work, but looks nice)
  • Loading branch information
sheremet-va committed Apr 6, 2024
1 parent 40f6281 commit dbd5502
Show file tree
Hide file tree
Showing 9 changed files with 356 additions and 253 deletions.
4 changes: 0 additions & 4 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,6 @@ export class VitestFolderAPI extends VitestReporter {
await this.meta.rpc.cancelRun(this.id)
}

getCoverageConfig() {
return this.meta.rpc.getCoverageConfig(this.id)
}

waitForCoverageReport() {
return this.meta.rpc.waitForCoverageReport(this.id)
}
Expand Down
9 changes: 4 additions & 5 deletions src/api/rpc.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
import v8 from 'node:v8'
import type { ChildProcess } from 'node:child_process'
import { type BirpcReturn, createBirpc } from 'birpc'
import type { File, ResolvedCoverageOptions, TaskResultPack, UserConsoleLog } from 'vitest'
import type { File, TaskResultPack, UserConsoleLog } from 'vitest'

export interface BirpcMethods {
getFiles: (id: string) => Promise<[project: string, file: string][]>
collectTests: (id: string, testFile: string[]) => Promise<void>
cancelRun: (id: string) => Promise<void>
runTests: (id: string, files?: string[], testNamePattern?: string) => Promise<void>
isTestFile: (file: string) => Promise<boolean>
isTestFile: (file: string) => boolean

watchTests: (id: string, files?: string[], testNamePattern?: string) => Promise<void>
unwatchTests: (id: string) => Promise<void>

enableCoverage: (id: string) => void
disableCoverage: (id: string) => void
getCoverageConfig: (id: string) => Promise<ResolvedCoverageOptions>
waitForCoverageReport: (id: string) => Promise<string | null>

startInspect: (port: number) => void
stopInspect: () => void
startInspect: (id: string, port: number) => void
stopInspect: (id: string) => void
close: () => void
}

Expand Down
270 changes: 28 additions & 242 deletions src/worker/actions.ts
Original file line number Diff line number Diff line change
@@ -1,278 +1,64 @@
import { randomUUID } from 'node:crypto'
import { tmpdir } from 'node:os'
import { existsSync } from 'node:fs'
import { dirname, join } from 'pathe'
import type { CoverageProvider, ResolvedConfig, Vitest } from 'vitest'
import type { BirpcMethods } from '../api/rpc'

interface WatchState {
files: string[]
testNamePattern: string | undefined
watchEveryFile: boolean
rerunTriggered: boolean
}
import type { Vitest } from './vitest'

export function createWorkerMethods(vitestById: Record<string, Vitest>): BirpcMethods {
let debuggerEnabled = false
let _debuggerPort: number | undefined

const watchStateById: Record<string, WatchState | null> = {}
const providers = new WeakMap<Vitest, CoverageProvider>()
const coverages = new WeakMap<Vitest, unknown>()
const coverageStatuses = new WeakMap<Vitest, boolean>()

const originalConfigs = new WeakMap<Vitest, ResolvedConfig>()

const vitestEntries = Object.entries(vitestById)
vitestEntries.forEach(([id, vitest]) => {
originalConfigs.set(vitest, {
fileParallelism: vitest.config.fileParallelism,
} as any)

vitest.getCoreWorkspaceProject().provide('__vscode', {
get continuousFiles() {
const state = watchStateById[id]
return state?.files || []
},
get watchEveryFile() {
const state = watchStateById[id]
return state?.watchEveryFile ?? true
},
get rerunTriggered() {
const state = watchStateById[id]
return state?.rerunTriggered ?? false
},
})

// @ts-expect-error modifying a private property
const originalScheduleRerun = vitest.scheduleRerun.bind(vitest)
// @ts-expect-error modifying a private property
vitest.scheduleRerun = async function (files: string[]) {
// if trigger is not a test file, remove all non-continious files from this.changedTests
const triggerFile = files[0]
const isTestFileTrigger = this.changedTests.has(triggerFile)

const state = watchStateById[id]

// no continuous files for this Vitest instance, just collect tests
if (!state) {
// do not run any tests if continuous run was not enabled
if (!isTestFileTrigger) {
this.changedTests.clear()
this.invalidates.clear()
}

vitest.configOverride.testNamePattern = /$a/
return await originalScheduleRerun.call(this, files)
}

state.rerunTriggered = true

vitest.configOverride.testNamePattern = state.testNamePattern ? new RegExp(state.testNamePattern) : undefined
if (state.watchEveryFile)
return await originalScheduleRerun.call(this, files)

if (!isTestFileTrigger) {
// if souce code is changed and related tests are not continious, remove them from changedTests
const updatedTests = new Set<string>()
for (const file of this.changedTests) {
if (state.files.includes(file))
updatedTests.add(file)
}
this.changedTests = updatedTests
}

return await originalScheduleRerun.call(this, files)
}
})

async function rerunTests(vitest: Vitest, files: string[]) {
await vitest.report('onWatcherRerun', files)
await vitest.runFiles(files.flatMap(file => vitest.getProjectsByTestFile(file)), false)

await vitest.report('onWatcherStart', vitest.state.getFiles(files))
}

async function runTests(id: string, files: string[], testNamePattern?: string) {
const cwd = process.cwd()
function vitest(id: string) {
const vitest = vitestById[id]

await vitest.runningPromise

const state = watchStateById[id]
if (state)
state.rerunTriggered = false

process.chdir(dirname(id))
try {
vitest.configOverride.testNamePattern = testNamePattern ? new RegExp(testNamePattern) : undefined

if (!debuggerEnabled) {
vitest.config.inspect = false
vitest.config.fileParallelism = originalConfigs.get(vitest)!.fileParallelism

await rerunTests(vitest, files)
}
else {
// TODO: "collectTests" doesn't work with debugger paused :thinking:
vitest.config.inspect = true
vitest.config.fileParallelism = false

vitest.pool?.close?.()
vitest.pool = undefined

for (const file of files)
await rerunTests(vitest, [file])

vitest.config.inspect = false
vitest.config.fileParallelism = originalConfigs.get(vitest)!.fileParallelism
}
}
finally {
process.chdir(cwd)
}
}

async function globTestFiles(id: string, vitest: Vitest, filters?: string[]) {
const cwd = process.cwd()
process.chdir(dirname(id))
const files = await vitest.globTestFiles(filters)
process.chdir(cwd)
return files
if (!vitest)
throw new Error(`Vitest instance not found with id: ${id}`)
return vitest
}

return {
async watchTests(id: string, files, testNamePattern) {
const vitest = vitestById[id]
if (!vitest)
throw new Error(`Vitest instance not found with id: ${id}`)
watchStateById[id] = {
files: files || [],
watchEveryFile: !files,
testNamePattern,
rerunTriggered: false,
}
const { watcher } = vitest(id)
if (!files)
watcher.trackEveryFile()
else
watcher.trackTests(files, testNamePattern)
},
async unwatchTests(id) {
const vitest = vitestById[id]
if (!vitest)
throw new Error(`Vitest instance not found with id: ${id}`)
watchStateById[id] = null
vitest(id).watcher.stopTracking()
},
async collectTests(id: string, testFiles: string[]) {
const vitest = vitestById[id]
vitest.config.coverage.enabled = false
vitest.coverageProvider = undefined
try {
await runTests(id, testFiles, '$a')
}
finally {
vitest.configOverride.testNamePattern = undefined
vitest.coverageProvider = coverageStatuses.get(vitest) ? providers.get(vitest) : undefined
vitest.config.coverage.enabled = coverageStatuses.get(vitest) || false
}
return vitest(id).collectTests(testFiles)
},
async cancelRun(id: string) {
const vitest = vitestById[id]
if (!vitest)
throw new Error(`Vitest instance with id "${id}" not found.`)
await vitest.cancelCurrentRun('keyboard-input')
await vitest(id).cancelRun()
},
async runTests(id, files, testNamePattern) {
const vitest = vitestById[id]
if (!vitest)
throw new Error(`Vitest instance not found for id: ${id}`)

// @ts-expect-error private method
await vitest.initBrowserProviders()

if (testNamePattern) {
await runTests(id, files || vitest.state.getFilepaths(), testNamePattern)
}
else {
const specs = await globTestFiles(id, vitest, files)
await runTests(id, specs.map(([_, spec]) => spec))
}
return vitest(id).runTests(files, testNamePattern)
},
async getFiles(id: string) {
const vitest = vitestById[id]
const files = await globTestFiles(id, vitest)
// reset cached test files list
vitest.projects.forEach((project) => {
project.testFilesList = null
})
return files.map(([project, spec]) => [project.config.name || '', spec])
return vitest(id).getFiles()
},
async isTestFile(file: string) {
for (const [_, vitest] of vitestEntries) {
for (const project of vitest.projects) {
if (project.isTestFile(file))
return true
}
isTestFile(file: string) {
for (const id in vitestById) {
if (vitestById[id].isTestFile(file))
return true
}
return false
},
async enableCoverage(id: string) {
const vitest = vitestById[id]
coverages.delete(vitest)
vitest.config.coverage.enabled = true
coverageStatuses.set(vitest, true)

const jsonReporter = vitest.config.coverage.reporter.find(([name]) => name === 'json')
vitest.config.coverage.reporter = jsonReporter ? [jsonReporter] : [['json', {}]]
vitest.config.coverage.reportOnFailure = true
vitest.config.coverage.reportsDirectory = join(tmpdir(), `vitest-coverage-${randomUUID()}`)

try {
if (!providers.has(vitest)) {
// @ts-expect-error private method
await vitest.initCoverageProvider()
await vitest.coverageProvider?.clean(vitest.config.coverage.clean)
providers.set(vitest, vitest.coverageProvider!)
}
else {
vitest.coverageProvider = providers.get(vitest)
await vitest.coverageProvider?.clean(vitest.config.coverage.clean)
}
}
catch (err) {
coverageStatuses.set(vitest, false)
vitest.config.coverage.enabled = false
throw err
}
return vitest.coverage.enable()
},
disableCoverage(id: string) {
const vitest = vitestById[id]
coverages.delete(vitest)
coverageStatuses.set(vitest, false)
vitest.config.coverage.enabled = false
vitest.coverageProvider = undefined
},
async getCoverageConfig(id: string) {
const vitest = vitestById[id]
return vitest.config.coverage
return vitest(id).coverage.disable()
},
async waitForCoverageReport(id: string) {
const vitest = vitestById[id]
const coverage = vitest.config.coverage
if (!coverage.enabled || !vitest.coverageProvider)
return null
await vitest.runningPromise
if (existsSync(coverage.reportsDirectory))
return coverage.reportsDirectory
return null
return vitest(id).coverage.waitForCoverageReport()
},
startInspect(port) {
debuggerEnabled = true
_debuggerPort = port
startInspect(id, port) {
vitest(id).debugger.start(port)
},
stopInspect() {
debuggerEnabled = false
stopInspect(id) {
vitest(id).debugger.stop()
},
async close() {
for (const vitest of vitestEntries) {
for (const vitest in vitestById) {
try {
await vitest[1].close()
await vitestById[vitest].dispose()
}
catch {
// ignore
Expand Down

0 comments on commit dbd5502

Please sign in to comment.