Skip to content

Commit

Permalink
Merge branch 'main' into bug/673_spy_type_inference
Browse files Browse the repository at this point in the history
  • Loading branch information
Demivan committed Feb 1, 2022
2 parents 5847bf1 + 9504905 commit a94601e
Show file tree
Hide file tree
Showing 24 changed files with 164 additions and 67 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -20,7 +20,7 @@
"test": "vitest --api -r test/core",
"test:run": "vitest run -r test/core",
"test:all": "cross-env CI=true pnpm -r --stream --filter !@vitest/monorepo run test --",
"test:ci": "cross-env CI=true pnpm -r --stream --filter !@vitest/monorepo --filter !@vitest/test-fails run test --",
"test:ci": "cross-env CI=true pnpm -r --stream --filter !@vitest/monorepo --filter !test-fails run test --",
"typecheck": "tsc --noEmit",
"ui:build": "vite build packages/ui",
"ui:dev": "vite packages/ui"
Expand Down
4 changes: 4 additions & 0 deletions packages/vite-node/src/client.ts
Expand Up @@ -91,6 +91,10 @@ export class ViteNodeRunner {
},
}

// Be carefull when changing this
// changing context will change amount of code added on line :114 (vm.runInThisContext)
// this messes up sourcemaps for coverage
// adjust `offset` variable in packages/vitest/src/integrations/coverage.ts#L100 if you do change this
const context = this.prepareContext({
// esm transformed by Vite
__vite_ssr_import__: request,
Expand Down
11 changes: 7 additions & 4 deletions packages/vite-node/src/server.ts
Expand Up @@ -72,12 +72,15 @@ export class ViteNodeServer {
private async _fetchModule(id: string): Promise<FetchResult> {
let result: FetchResult

const timestamp = this.server.moduleGraph.getModuleById(id)?.lastHMRTimestamp || Date.now()
const cache = this.fetchCache.get(id)
const filePath = toFilePath(id, this.server.config.root)

const module = this.server.moduleGraph.getModuleById(id)
const timestamp = module?.lastHMRTimestamp || Date.now()
const cache = this.fetchCache.get(filePath)
if (timestamp && cache && cache.timestamp >= timestamp)
return cache.result

const externalize = await this.shouldExternalize(toFilePath(id, this.server.config.root))
const externalize = await this.shouldExternalize(filePath)
if (externalize) {
result = { externalize }
}
Expand All @@ -86,7 +89,7 @@ export class ViteNodeServer {
result = { code: r?.code, map: r?.map as unknown as RawSourceMap }
}

this.fetchCache.set(id, {
this.fetchCache.set(filePath, {
timestamp,
result,
})
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/rollup.config.js
Expand Up @@ -30,6 +30,7 @@ const external = [
...Object.keys(pkg.peerDependencies),
'worker_threads',
'inspector',
'c8',
]

export default ({ watch }) => [
Expand Down
8 changes: 4 additions & 4 deletions packages/vitest/src/integrations/chai/jest-expect.ts
Expand Up @@ -447,8 +447,8 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
utils.flag(this, 'promise', 'resolves')
const obj = utils.flag(this, 'object')
const proxy: any = new Proxy(this, {
get: (target, key, reciever) => {
const result = Reflect.get(target, key, reciever)
get: (target, key, receiver) => {
const result = Reflect.get(target, key, receiver)

if (typeof result !== 'function')
return result instanceof chai.Assertion ? proxy : result
Expand All @@ -475,8 +475,8 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
const obj = utils.flag(this, 'object')
const wrapper = typeof obj === 'function' ? obj() : obj // for jest compat
const proxy: any = new Proxy(this, {
get: (target, key, reciever) => {
const result = Reflect.get(target, key, reciever)
get: (target, key, receiver) => {
const result = Reflect.get(target, key, receiver)

if (typeof result !== 'function')
return result instanceof chai.Assertion ? proxy : result
Expand Down
85 changes: 60 additions & 25 deletions packages/vitest/src/integrations/coverage.ts
@@ -1,7 +1,10 @@
import { existsSync, promises as fs } from 'fs'
import { takeCoverage } from 'v8'
import { createRequire } from 'module'
import { pathToFileURL } from 'url'
import type { Profiler } from 'inspector'
import { resolve } from 'pathe'
import type { RawSourceMap } from 'vite-node'
import type { Vitest } from '../node'
import { toArray } from '../utils'
import type { C8Options, ResolvedC8Options } from '../types'
Expand Down Expand Up @@ -36,47 +39,79 @@ export function resolveC8Options(options: C8Options, root: string): ResolvedC8Op

resolved.reporter = toArray(resolved.reporter)
resolved.reportsDirectory = resolve(root, resolved.reportsDirectory)
resolved.tempDirectory = process.env.NODE_V8_COVERAGE || resolve(resolved.reportsDirectory, 'tmp')

return resolved as ResolvedC8Options
}

export async function cleanCoverage(options: ResolvedC8Options, clean = true) {
if (clean && existsSync(options.reportsDirectory))
await fs.rm(options.reportsDirectory, { recursive: true, force: true })

if (!existsSync(options.tempDirectory))
await fs.mkdir(options.tempDirectory, { recursive: true })
}

const require = createRequire(import.meta.url)

export async function reportCoverage(ctx: Vitest) {
// Flush coverage to disk
takeCoverage()

// eslint-disable-next-line @typescript-eslint/no-var-requires
const createReport = require('c8/lib/report')
const report = createReport(ctx.config.coverage)

report._loadReports = () => ctx.coverage

const original = report._getMergedProcessCov

report._getMergedProcessCov = () => {
const r = original.call(report)

// add source maps
Array
.from(ctx.vitenode.fetchCache.entries())
.filter(i => !i[0].includes('/node_modules/'))
.forEach(([file, { result }]) => {
const map = result.map
if (!map)
return
const url = pathToFileURL(file).href
const sources = map.sources.length
? map.sources.map(i => pathToFileURL(i).href)
: [url]
report.sourceMapCache[url] = {
data: { ...map, sources },
}
})

return r
// add source maps
const sourceMapMata: Record<string, { map: RawSourceMap; source: string | undefined }> = {}
await Promise.all(Array
.from(ctx.vitenode.fetchCache.entries())
.filter(i => !i[0].includes('/node_modules/'))
.map(async([file, { result }]) => {
const map = result.map
if (!map)
return

const url = pathToFileURL(file).href

let code: string | undefined
try {
code = (await fs.readFile(file)).toString()
}
catch {}

const sources = map.sources.length
? map.sources.map(i => pathToFileURL(i).href)
: [url]

sourceMapMata[url] = {
source: result.code,
map: {
sourcesContent: code ? [code] : undefined,
...map,
sources,
},
}
}))

// This is a magic number. It corresponds to the amount of code
// that we add in packages/vite-node/src/client.ts:114 (vm.runInThisContext)
// TODO: Include our transformations in soucemaps
const offset = 190

report._getSourceMap = (coverage: Profiler.ScriptCoverage) => {
const path = pathToFileURL(coverage.url).href
const data = sourceMapMata[path]

if (!data)
return {}

return {
sourceMap: {
sourcemap: data.map,
},
source: Array(offset).fill('.').join('') + data.source,
}
}

await report.run()
Expand Down
7 changes: 7 additions & 0 deletions packages/vitest/src/node/cli.ts
@@ -1,4 +1,5 @@
import cac from 'cac'
import { execa } from 'execa'
import type { UserConfig } from '../types'
import { version } from '../../package.json'
import { ensurePackageInstalled } from '../utils'
Expand Down Expand Up @@ -80,6 +81,12 @@ async function run(cliFilters: string[], options: UserConfig) {
if (ctx.config.coverage.enabled) {
if (!await ensurePackageInstalled('c8'))
process.exit(1)

if (!process.env.NODE_V8_COVERAGE) {
process.env.NODE_V8_COVERAGE = ctx.config.coverage.tempDirectory
const { exitCode } = await execa(process.argv0, process.argv.slice(1), { stdio: 'inherit' })
process.exit(exitCode)
}
}

if (ctx.config.environment && ctx.config.environment !== 'node') {
Expand Down
3 changes: 0 additions & 3 deletions packages/vitest/src/node/core.ts
@@ -1,5 +1,4 @@
import { existsSync } from 'fs'
import type { Profiler } from 'inspector'
import type { ViteDevServer } from 'vite'
import fg from 'fast-glob'
import mm from 'micromatch'
Expand All @@ -25,7 +24,6 @@ export class Vitest {
server: ViteDevServer = undefined!
state: StateManager = undefined!
snapshot: SnapshotManager = undefined!
coverage: Profiler.TakePreciseCoverageReturnType[] = []
reporters: Reporter[] = undefined!
console: Console
pool: WorkerPool | undefined
Expand Down Expand Up @@ -275,7 +273,6 @@ export class Vitest {
// })
// }
this.snapshot.clear()
this.coverage = []
const files = Array.from(this.changedTests)
this.changedTests.clear()

Expand Down
6 changes: 2 additions & 4 deletions packages/vitest/src/node/plugins/index.ts
Expand Up @@ -22,8 +22,9 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest())
// preliminary merge of options to be able to create server options for vite
// however to allow vitest plugins to modify vitest config values
// this is repeated in configResolved where the config is final
const preOptions = deepMerge(options, viteConfig.test || {})
const preOptions = deepMerge({}, options, viteConfig.test ?? {})
preOptions.api = resolveApiConfig(preOptions)

return {
clearScreen: false,
resolve: {
Expand All @@ -38,9 +39,6 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest())
: undefined,
preTransformRequests: false,
},
build: {
sourcemap: true,
},
// disable deps optimization
cacheDir: undefined,
}
Expand Down
3 changes: 0 additions & 3 deletions packages/vitest/src/node/pool.ts
Expand Up @@ -112,9 +112,6 @@ function createChannel(ctx: Vitest) {
snapshotSaved(snapshot) {
ctx.snapshot.add(snapshot)
},
coverageCollected(coverage) {
ctx.coverage.push(coverage)
},
async getSourceMap(id, force) {
if (force) {
const mod = ctx.server.moduleGraph.getModuleById(id)
Expand Down
7 changes: 6 additions & 1 deletion packages/vitest/src/runtime/error.ts
Expand Up @@ -62,5 +62,10 @@ export function processError(err: any) {
if (typeof err.actual !== 'string')
err.actual = stringify(err.actual)

return serializeError(err)
try {
return serializeError(err)
}
catch (e: any) {
return serializeError(new Error(`Failed to fully serialize error: ${e?.message}.\nInner error message: ${err?.message}`))
}
}
18 changes: 0 additions & 18 deletions packages/vitest/src/runtime/run.ts
@@ -1,5 +1,4 @@
import { performance } from 'perf_hooks'
import inspector from 'inspector'
import type { HookListener, ResolvedConfig, Suite, SuiteHooks, Task, TaskResult, Test } from '../types'
import { vi } from '../integrations/vi'
import { getSnapshotClient } from '../integrations/snapshot/chai'
Expand Down Expand Up @@ -189,25 +188,8 @@ export async function startTests(paths: string[], config: ResolvedConfig) {

rpc().onCollected(files)

let session!: inspector.Session
if (config.coverage.enabled) {
session = new inspector.Session()
session.connect()

session.post('Profiler.enable')
session.post('Profiler.startPreciseCoverage', { detailed: true })
}

await runSuites(files)

if (config.coverage.enabled) {
session.post('Profiler.takePreciseCoverage', (_, coverage) => {
rpc().coverageCollected(coverage)
})

session.disconnect()
}

await getSnapshotClient().saveSnap()

await sendTasksUpdate()
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/types/coverage.ts
Expand Up @@ -66,4 +66,5 @@ export interface C8Options {
}

export interface ResolvedC8Options extends Required<C8Options> {
tempDirectory: string
}
2 changes: 0 additions & 2 deletions packages/vitest/src/types/worker.ts
@@ -1,4 +1,3 @@
import type { Profiler } from 'inspector'
import type { MessagePort } from 'worker_threads'
import type { FetchFunction, RawSourceMap, ViteNodeResolveId } from 'vite-node'
import type { ResolvedConfig } from './config'
Expand Down Expand Up @@ -27,5 +26,4 @@ export interface WorkerRPC {
onTaskUpdate: (pack: TaskResultPack[]) => void

snapshotSaved: (snapshot: SnapshotResult) => void
coverageCollected: (coverage: Profiler.TakePreciseCoverageReturnType) => void
}
4 changes: 4 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions test/coverage-test/package.json
Expand Up @@ -6,6 +6,8 @@
"coverage": "vitest run --coverage"
},
"devDependencies": {
"@vitejs/plugin-vue": "^2.0.1",
"@vue/test-utils": "^2.0.0-rc.18",
"vitest": "workspace:*"
}
}
17 changes: 17 additions & 0 deletions test/coverage-test/src/Hello.vue
@@ -0,0 +1,17 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
const times = ref(2)
const props = defineProps<{ count: number }>()
const result = computed(() => props.count * times.value)
defineExpose(props)
</script>

<template>
<div>{{ count }} x {{ times }} = {{ result }}</div>
<button @click="times += 1">
x1
</button>
</template>
3 changes: 3 additions & 0 deletions test/coverage-test/src/utils.ts
Expand Up @@ -12,6 +12,9 @@ export function divide(a: number, b: number) {
}

export function sqrt(a: number) {
if (a < 0)
return Number.NaN // This should not be covered

return Math.sqrt(a)
}

Expand Down
5 changes: 5 additions & 0 deletions test/coverage-test/src/vue.shim.d.ts
@@ -0,0 +1,5 @@
declare module "*.vue" {
import type { DefineComponent } from "vue"
const component: DefineComponent<{}, {}, any>
export default component
}
6 changes: 6 additions & 0 deletions test/coverage-test/test/__snapshots__/vue.test.ts.snap
@@ -0,0 +1,6 @@
// Vitest Snapshot v1

exports[`vue 3 coverage 1`] = `
"<div>4 x 2 = 8</div>
<button> x1 </button>"
`;

0 comments on commit a94601e

Please sign in to comment.