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

fix(workspace): set CWD to config directory, allow overriding local .env #5476

Merged
merged 4 commits into from Apr 3, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
50 changes: 39 additions & 11 deletions packages/vitest/src/node/core.ts
@@ -1,4 +1,5 @@
import { existsSync, promises as fs } from 'node:fs'
import { isMainThread } from 'node:worker_threads'
import type { ViteDevServer } from 'vite'
import { mergeConfig } from 'vite'
import { basename, dirname, join, normalize, relative, resolve } from 'pathe'
Expand Down Expand Up @@ -312,23 +313,50 @@ export class Vitest {
return acc
}, {} as UserConfig)

const projects = filteredWorkspaces.map(async (workspacePath) => {
// don't start a new server, but reuse existing one
if (
this.server.config.configFile === workspacePath
)
return this.createCoreProject()
return initializeProject(workspacePath, this, { workspaceConfigPath, test: cliOverrides })
})
const cwd = process.cwd()

const projects: (() => Promise<WorkspaceProject>)[] = []

try {
// we have to resolve them one by one because CWD should depend on the project
for (const filepath of filteredWorkspaces) {
if (this.server.config.configFile === filepath) {
const project = await this.createCoreProject()
projects.push(() => Promise.resolve(project))
continue
}
const dir = filepath.endsWith('/') ? filepath.slice(0, -1) : dirname(filepath)
if (isMainThread)
process.chdir(dir)
// this just resolves the config, later we also wait when the server is resolved,
// but we can do that in parallel because it doesn't depend on process.cwd()
// this is strictly a performance optimization so we don't need to wait for server to start
projects.push(await initializeProject(filepath, this, { workspaceConfigPath, test: cliOverrides }))
}
}
finally {
if (isMainThread)
process.chdir(cwd)
}

const projectPromises: Promise<() => Promise<WorkspaceProject>>[] = []

projectsOptions.forEach((options, index) => {
projects.push(initializeProject(index, this, mergeConfig(options, { workspaceConfigPath, test: cliOverrides }) as any))
// we can resolve these in parallel because process.cwd() is not changed
projectPromises.push(initializeProject(index, this, mergeConfig(options, { workspaceConfigPath, test: cliOverrides }) as any))
})

if (!projects.length)
if (!projects.length && !projectPromises.length)
return [await this.createCoreProject()]

const resolvedProjects = await Promise.all(projects)
const resolvedProjectsReceivers = [
...projects,
...await Promise.all(projectPromises),
]
// we need to wait when the server is resolved, we can do that in parallel
const resolvedProjects = await Promise.all(
resolvedProjectsReceivers.map(receiver => receiver()),
)
const names = new Set<string>()

for (const project of resolvedProjects) {
Expand Down
51 changes: 37 additions & 14 deletions packages/vitest/src/node/workspace.ts
Expand Up @@ -12,6 +12,7 @@ import { deepMerge } from '../utils'
import type { Typechecker } from '../typecheck/typechecker'
import type { BrowserProvider } from '../types/browser'
import { getBrowserProvider } from '../integrations/browser'
import { createDefer } from '../public/utils'
import { isBrowserEnabled, resolveConfig } from './config'
import { WorkspaceVitestPlugin } from './plugins/workspace'
import { createViteServer } from './vite'
Expand Down Expand Up @@ -39,22 +40,40 @@ export async function initializeProject(workspacePath: string | number, ctx: Vit
: workspacePath.endsWith('/') ? workspacePath : dirname(workspacePath)
)

const config: ViteInlineConfig = {
...options,
root,
logLevel: 'error',
configFile,
// this will make "mode": "test" | "benchmark" inside defineConfig
mode: options.test?.mode || options.mode || ctx.config.mode,
plugins: [
...options.plugins || [],
WorkspaceVitestPlugin(project, { ...options, root, workspacePath }),
],
}
return new Promise<() => Promise<WorkspaceProject>>((resolve, reject) => {
const resolution = createDefer<WorkspaceProject>()
let configResolved = false
const config: ViteInlineConfig = {
...options,
root,
logLevel: 'error',
configFile,
// this will make "mode": "test" | "benchmark" inside defineConfig
mode: options.test?.mode || options.mode || ctx.config.mode,
plugins: [
{
name: 'vitest:workspace:resolve',
configResolved() {
configResolved = true
resolve(() => resolution)
},
},
...options.plugins || [],
WorkspaceVitestPlugin(project, { ...options, root, workspacePath }),
],
}

await createViteServer(config)
createViteServer(config)
.then(() => resolution.resolve(project))
.catch((err) => {
if (configResolved)
resolution.reject(err)
else
reject(err)
})

return project
return project
})
}

export class WorkspaceProject {
Expand Down Expand Up @@ -393,6 +412,10 @@ export class WorkspaceProject {
inspectBrk: this.ctx.config.inspectBrk,
alias: [],
includeTaskLocation: this.config.includeTaskLocation ?? this.ctx.config.includeTaskLocation,
env: {
...this.server?.config.env,
...this.config.env,
},
}, this.ctx.configOverride || {} as any) as ResolvedConfig
}

Expand Down
8 changes: 8 additions & 0 deletions packages/vitest/src/runtime/setup-common.ts
Expand Up @@ -10,6 +10,7 @@ let globalSetup = false
export async function setupCommonEnv(config: ResolvedConfig) {
resetRunOnceCounter()
setupDefines(config.defines)
setupEnv(config.env)

if (globalSetup)
return
Expand All @@ -26,6 +27,13 @@ function setupDefines(defines: Record<string, any>) {
(globalThis as any)[key] = defines[key]
}

function setupEnv(env: Record<string, any>) {
if (typeof process === 'undefined')
return
for (const key in env)
process.env[key] = env[key]
}

export async function loadDiffConfig(config: ResolvedConfig, executor: VitestExecutor) {
if (typeof config.diff !== 'string')
return
Expand Down
4 changes: 4 additions & 0 deletions test/workspaces/.env.local
@@ -0,0 +1,4 @@
VITE_MY_TEST_VARIABLE=core
VITE_CORE_VARIABLE=core
CUSTOM_ROOT=custom
ROOT_VARIABLE=root
4 changes: 2 additions & 2 deletions test/workspaces/globalTest.ts
Expand Up @@ -33,8 +33,8 @@ export async function teardown() {
try {
assert.ok(results.success)
assert.equal(results.numTotalTestSuites, 28)
assert.equal(results.numTotalTests, 29)
assert.equal(results.numPassedTests, 29)
assert.equal(results.numTotalTests, 30)
assert.equal(results.numPassedTests, 30)

const shared = results.testResults.filter((r: any) => r.name.includes('space_shared/test.spec.ts'))

Expand Down
2 changes: 2 additions & 0 deletions test/workspaces/space_1/.env.local
@@ -0,0 +1,2 @@
VITE_MY_TEST_VARIABLE=local
CUSTOM_MY_TEST_VARIABLE=custom
16 changes: 16 additions & 0 deletions test/workspaces/space_1/test/env-injected.spec.ts
Expand Up @@ -7,3 +7,19 @@ declare global {
test('dev is injected', () => {
expect(__DEV__).toBe(true)
})

test('env variable is assigned', () => {
// we override it with "local" in .env.local, but dotenv prefers the root .env
// this is consistent with how Vite works
expect(import.meta.env.VITE_MY_TEST_VARIABLE).toBe('core')
expect(process.env.VITE_MY_TEST_VARIABLE).toBe('core')
expect(import.meta.env.CUSTOM_MY_TEST_VARIABLE).toBe('custom')
expect(process.env.CUSTOM_MY_TEST_VARIABLE).toBe('custom')

expect(process.env.VITE_CORE_VARIABLE).toBe('core')
expect(process.env.CUSTOM_ROOT).toBe('custom')
expect(process.env.ROOT_VARIABLE).toBe('root')
expect(process.env.CONFIG_VAR).toBe('root')
expect(process.env.CONFIG_LOCAL).toBe('local')
expect(process.env.CONFIG_OVERRIDE).toBe('local')
})
5 changes: 5 additions & 0 deletions test/workspaces/space_1/vite.config.ts
@@ -1,11 +1,16 @@
import { defineProject } from 'vitest/config'

export default defineProject({
envPrefix: ['VITE_', 'CUSTOM_'],
define: {
__DEV__: 'true',
},
test: {
name: 'space_1',
environment: 'happy-dom',
env: {
CONFIG_LOCAL: 'local',
CONFIG_OVERRIDE: 'local',
},
},
})
5 changes: 5 additions & 0 deletions test/workspaces/vitest.config.ts
Expand Up @@ -7,6 +7,7 @@ if (process.env.TEST_WATCH) {
}

export default defineConfig({
envPrefix: ['VITE_', 'CUSTOM_', 'ROOT_'],
test: {
coverage: {
enabled: true,
Expand All @@ -15,5 +16,9 @@ export default defineConfig({
reporters: ['default', 'json'],
outputFile: './results.json',
globalSetup: './globalTest.ts',
env: {
CONFIG_VAR: 'root',
CONFIG_OVERRIDE: 'root',
},
},
})