diff --git a/packages/vitest/src/node/cache/fsModuleCache.ts b/packages/vitest/src/node/cache/fsModuleCache.ts index 2349f27c9..44677d2f4 100644 --- a/packages/vitest/src/node/cache/fsModuleCache.ts +++ b/packages/vitest/src/node/cache/fsModuleCache.ts @@ -45,7 +45,7 @@ export class FileSystemModuleCache { private version = '1.0.0-beta.8' private fsCacheRoots = new WeakMap() private fsEnvironmentHashMap = new WeakMap() - private fsCacheKeyGenerators = new Set() + private fsCacheKeyGenerators = new WeakMap>() private warnedDeprecatedIgnore = new Set() // this exists only to avoid the perf. cost of reading a file and generating a hash again // surprisingly, on some machines this has negligible effect @@ -62,8 +62,13 @@ export class FileSystemModuleCache { this.metadataFilePath = join(this.rootCache, METADATA_FILE) } - public defineCacheKeyGenerator(callback: CacheKeyIdGenerator): void { - this.fsCacheKeyGenerators.add(callback) + public defineCacheKeyGenerator(config: ResolvedConfig, callback: CacheKeyIdGenerator): void { + let generators = this.fsCacheKeyGenerators.get(config) + if (!generators) { + generators = new Set() + this.fsCacheKeyGenerators.set(config, generators) + } + generators.add(callback) } // A plugin can exclude itself from the cache key via `api.vitest.ignoreFsModuleCache`. @@ -224,7 +229,7 @@ export class FileSystemModuleCache { let hashString = '' - for (const generator of this.fsCacheKeyGenerators) { + for (const generator of this.fsCacheKeyGenerators.get(vitestConfig) || []) { const result = generator({ environment, id, sourceCode: fileContent }) if (typeof result === 'string') { hashString += result diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index f78a55280..4f2659f5f 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -396,7 +396,8 @@ export class Vitest { project, vitest: this, injectTestProjects: this.injectTestProject, - defineCacheKeyGenerator: (callback) => this._fsCache.defineCacheKeyGenerator(callback), + defineCacheKeyGenerator: (callback) => + this._fsCache.defineCacheKeyGenerator(project.config, callback), /** * @deprecated Use `defineCacheKeyGenerator` instead. */ @@ -407,7 +408,7 @@ export class Vitest { '`experimental_defineCacheKeyGenerator` is deprecated. Use `defineCacheKeyGenerator` instead.', ) } - this._fsCache.defineCacheKeyGenerator(callback) + this._fsCache.defineCacheKeyGenerator(project.config, callback) }, }), ) diff --git a/packages/vitest/src/node/types/plugin.ts b/packages/vitest/src/node/types/plugin.ts index ef5353a69..aeadf74bf 100644 --- a/packages/vitest/src/node/types/plugin.ts +++ b/packages/vitest/src/node/types/plugin.ts @@ -10,7 +10,7 @@ export interface VitestPluginContext { config: TestProjectConfiguration | TestProjectConfiguration[], ) => Promise /** - * Define a generator that will be applied before hashing the cache key. + * Define a generator that will be applied before hashing this project's cache key. * * Use this to make sure Vitest generates correct hash. It is a good idea * to define this function if your plugin can be registered with different options. diff --git a/test/e2e/test/caching.test.ts b/test/e2e/test/caching.test.ts index a590e5d98..fd65ef882 100644 --- a/test/e2e/test/caching.test.ts +++ b/test/e2e/test/caching.test.ts @@ -1,5 +1,5 @@ import { expect, test } from 'vitest' -import { runVitest, useFS } from '../../test-utils' +import { runInlineTests, runVitest, useFS } from '../../test-utils' test("if file has import.meta.glob, it's not cached", async () => { const { createFile } = useFS( @@ -138,6 +138,62 @@ test('if cache key generator is defined, the hash is valid', async () => { `) }) +test.each([ + ['foo', 'bar'], + ['bar', 'foo'], +])('cache key generators are scoped to projects (%s, %s)', async (first, second) => { + const cold = await runInlineTests({ + 'vitest.config.js': ` + import { defineConfig } from 'vitest/config' + export default defineConfig({ + test: { + fsModuleCache: true, + fsModuleCachePath: './node_modules/.vitest-fs-cache', + projects: ${JSON.stringify([first, second])}.map(name => ({ + plugins: [{ + name: 'replacer', + configureVitest({ defineCacheKeyGenerator }) { + defineCacheKeyGenerator(() => name) + }, + transform(code, id) { + if (id.endsWith('/common.js')) { + return code.replace('PLACEHOLDER', name) + } + }, + }], + test: { name, include: [name + '.test.js'] }, + })), + }, + }) + `, + 'common.js': `export const value = 'PLACEHOLDER'`, + 'foo.test.js': ` + import { expect, test } from 'vitest' + import { value } from './common.js' + test('project value', () => expect(value).toBe('foo')) + `, + 'bar.test.js': ` + import { expect, test } from 'vitest' + import { value } from './common.js' + test('project value', () => expect(value).toBe('bar')) + `, + }) + const warm = await runVitest({ root: cold.root }) + for (const run of [cold, warm]) { + expect(run.stderr).toBe('') + expect(run.errorTree()).toMatchInlineSnapshot(` + { + "bar.test.js": { + "project value": "passed", + }, + "foo.test.js": { + "project value": "passed", + }, + } + `) + } +}) + test('if cache key generator bails out, the file is not cached', async () => { process.env.REPLACED = 'value1'