Add incremental test where cache should have same resolutions as whats in the program

This shows cache is holding onto resolutions that are no longer needed by program because either those modules arent present in file or is determined to be ambient resolution
This commit is contained in:
Sheetal Nandi
2024-09-20 12:01:38 -07:00
parent cf54ae6f91
commit 38b0c2ee41
4 changed files with 127 additions and 27 deletions
+13 -5
View File
@@ -1022,7 +1022,8 @@ export function getResolutionModeOverride(node: ImportAttributes | undefined, gr
return elem.value.text === "import" ? ModuleKind.ESNext : ModuleKind.CommonJS;
}
const emptyResolution: ResolvedModuleWithFailedLookupLocations & ResolvedTypeReferenceDirectiveWithFailedLookupLocations = {
/** @internal */
export const emptyResolution: ResolvedModuleWithFailedLookupLocations & ResolvedTypeReferenceDirectiveWithFailedLookupLocations = {
resolvedModule: undefined,
resolvedTypeReferenceDirective: undefined,
};
@@ -1201,6 +1202,13 @@ function forEachProjectReference<T>(
/** @internal */
export const inferredTypesContainingFile = "__inferred type names__.ts";
/** @internal */
export function getAutomaticTypeDirectiveContainingFile(options: CompilerOptions, currentDirectory: string): string {
// This containingFilename needs to match with the one used in managed-side
const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : currentDirectory;
return combinePaths(containingDirectory, inferredTypesContainingFile);
}
/** @internal */
export function getInferredLibraryNameResolveFrom(options: CompilerOptions, currentDirectory: string, libFileName: string): string {
const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : currentDirectory;
@@ -1874,10 +1882,10 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
automaticTypeDirectiveResolutions = createModeAwareCache();
if (automaticTypeDirectiveNames.length) {
tracing?.push(tracing.Phase.Program, "processTypeReferences", { count: automaticTypeDirectiveNames.length });
// This containingFilename needs to match with the one used in managed-side
const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : currentDirectory;
const containingFilename = combinePaths(containingDirectory, inferredTypesContainingFile);
const resolutions = resolveTypeReferenceDirectiveNamesReusingOldState(automaticTypeDirectiveNames, containingFilename);
const resolutions = resolveTypeReferenceDirectiveNamesReusingOldState(
automaticTypeDirectiveNames,
getAutomaticTypeDirectiveContainingFile(options, currentDirectory),
);
for (let i = 0; i < automaticTypeDirectiveNames.length; i++) {
// under node16/nodenext module resolution, load `types`/ata include names as cjs resolution results by passing an `undefined` mode
automaticTypeDirectiveResolutions.set(automaticTypeDirectiveNames[i], /*mode*/ undefined, resolutions[i]);
+82 -3
View File
@@ -204,6 +204,7 @@ export function verifyResolutionCache(
actualProgram: ts.Program,
resolutionHostCacheHost: ts.ResolutionCacheHost,
projectName: string,
userResolvedModuleNames?: true,
): void {
const currentDirectory = resolutionHostCacheHost.getCurrentDirectory!();
const expected = ts.createResolutionCache(resolutionHostCacheHost, actual.rootDirForResolution);
@@ -214,6 +215,12 @@ export function verifyResolutionCache(
const expectedToResolution = new Map<ExpectedResolution, ts.ResolutionWithFailedLookupLocations>();
const resolutionToExpected = new Map<ts.ResolutionWithFailedLookupLocations, ExpectedResolution>();
const resolutionToRefs = new Map<ts.ResolutionWithFailedLookupLocations, ResolutionInfo[]>();
const inferredTypesPath = resolutionHostCacheHost.toPath(
ts.getAutomaticTypeDirectiveContainingFile(
actualProgram.getCompilerOptions(),
currentDirectory,
),
);
actual.resolvedModuleNames.forEach((resolutions, path) =>
collectResolutionToRefFromCache(
"Modules",
@@ -222,6 +229,7 @@ export function verifyResolutionCache(
getResolvedModuleFileName,
/*deferWatchingNonRelativeResolution*/ true,
expected.resolvedModuleNames,
(name, mode) => actualProgram.getResolvedModule(actualProgram.getSourceFileByPath(path)!, name, mode),
)
);
actual.resolvedTypeReferenceDirectives.forEach((resolutions, path) =>
@@ -232,6 +240,10 @@ export function verifyResolutionCache(
getResolvedTypeRefFileName,
/*deferWatchingNonRelativeResolution*/ false,
expected.resolvedTypeReferenceDirectives,
(name, mode) =>
path !== inferredTypesPath ?
actualProgram.getResolvedTypeReferenceDirective(actualProgram.getSourceFileByPath(path)!, name, mode) :
actualProgram.getAutomaticTypeDirectiveResolutions().get(name, mode),
)
);
actual.resolvedLibraries.forEach((resolved, libFileName) => {
@@ -248,6 +260,39 @@ export function verifyResolutionCache(
);
expected.resolvedLibraries.set(libFileName, expectedResolution);
});
// Check for resolutions in program but not in cache to empty resolutions
if (!userResolvedModuleNames) {
actualProgram.forEachResolvedModule((resolution, name, mode, filePath) =>
verifyResolutionIsInCache(
"Modules",
actual.resolvedModuleNames.get(filePath),
resolution,
name,
mode,
filePath,
)
);
}
actualProgram.forEachResolvedTypeReferenceDirective((resolution, name, mode, filePath) =>
verifyResolutionIsInCache(
"TypeRefs",
actual.resolvedTypeReferenceDirectives.get(filePath),
resolution,
name,
mode,
filePath,
)
);
actualProgram.getAutomaticTypeDirectiveResolutions().forEach((resolution, name, mode) =>
verifyResolutionIsInCache(
"AutoTypeRefs",
actual.resolvedTypeReferenceDirectives.get(inferredTypesPath),
resolution,
name,
mode,
inferredTypesPath,
)
);
expected.finishCachingPerDirectoryResolution(actualProgram, /*oldProgram*/ undefined);
@@ -260,6 +305,10 @@ export function verifyResolutionCache(
`Expected from:: ${JSON.stringify(info, undefined, " ")}` +
`Actual from: ${resolution.files?.size}`,
);
ts.Debug.assert(
!resolution.isInvalidated,
`${projectName}:: Resolution should not be invalidated`,
);
verifySet(resolutionToExpected.get(resolution)!.files, resolution.files, `${projectName}:: Resolution files`);
});
verifyMapOfResolutionSet(expected.resolvedFileToResolution, actual.resolvedFileToResolution, `resolvedFileToResolution`);
@@ -295,24 +344,54 @@ export function verifyResolutionCache(
ts.Debug.assert(expected.countResolutionsResolvedWithGlobalCache() === 0, `${projectName}:: ResolutionsResolvedWithGlobalCache should be cleared`);
ts.Debug.assert(expected.countResolutionsResolvedWithoutGlobalCache() === 0, `${projectName}:: ResolutionsResolvedWithoutGlobalCache should be cleared`);
function verifyResolutionIsInCache<T extends ts.ResolutionWithFailedLookupLocations>(
cacheType: string,
cache: ts.ModeAwareCache<T> | undefined,
resolution: T,
name: string,
mode: ts.ResolutionMode,
fileName: string,
) {
if (resolution as unknown !== ts.emptyResolution) {
// Resolutions should match
ts.Debug.assert(
cache?.get(name, mode) === resolution,
`${projectName}:: ${cacheType}:: ${name}:: ${mode} Expected resolution in program to be in cache ${fileName}`,
);
}
else {
// EmptyResolution is place holder and shouldnt be in the cache
ts.Debug.assert(
!cache?.has(name, mode),
`${projectName}:: ${cacheType}:: ${name}:: ${mode} Ambient moduleResolution, should not be in cache or watched ${fileName}`,
);
}
}
function collectResolutionToRefFromCache<T extends ts.ResolutionWithFailedLookupLocations>(
cacheType: string,
fileName: ts.Path,
cache: ts.ModeAwareCache<T> | undefined,
getResolvedFileName: (resolution: T) => string | undefined,
deferWatchingNonRelativeResolution: boolean,
storeExpcted: Map<ts.Path, ts.ModeAwareCache<ts.ResolutionWithFailedLookupLocations>>,
storeExpected: Map<ts.Path, ts.ModeAwareCache<ts.ResolutionWithFailedLookupLocations>>,
getProgramResolutions: (name: string, mode: ts.ResolutionMode) => T | undefined,
) {
ts.Debug.assert(
actualProgram.getSourceFileByPath(fileName) || ts.endsWith(fileName, ts.inferredTypesContainingFile),
actualProgram.getSourceFileByPath(fileName) || inferredTypesPath === fileName,
`${projectName}:: ${cacheType} ${fileName} Expect cache for file in program or auto type ref`,
);
let expectedCache: ts.ModeAwareCache<ts.ResolutionWithFailedLookupLocations> | undefined;
cache?.forEach((resolved, name, mode) => {
const resolvedFileName = getResolvedFileName(resolved);
const expected = collectResolution(cacheType, fileName, resolved, resolvedFileName, name, mode, deferWatchingNonRelativeResolution);
if (!expectedCache) storeExpcted.set(fileName, expectedCache = ts.createModeAwareCache());
if (!expectedCache) storeExpected.set(fileName, expectedCache = ts.createModeAwareCache());
expectedCache.set(name, mode, expected);
// Resolution in cache should be same as that is in program
ts.Debug.assert(
resolved === getProgramResolutions(name, mode),
`${projectName}:: ${cacheType} ${fileName} ${name} ${mode} Expected resolution in cache to be matched to that in the program`,
);
});
}
+31 -19
View File
@@ -133,6 +133,7 @@ export interface RunWatchBaseline<T extends ts.BuilderProgram> extends BaselineB
getPrograms: () => readonly CommandLineProgram[];
watchOrSolution: WatchOrSolution<T>;
useSourceOfProjectReferenceRedirect?: () => boolean;
userResolvedModuleNames?: true;
}
export function runWatchBaseline<T extends ts.BuilderProgram = ts.EmitAndSemanticDiagnosticsBuilderProgram>({
scenario,
@@ -146,6 +147,7 @@ export function runWatchBaseline<T extends ts.BuilderProgram = ts.EmitAndSemanti
edits,
watchOrSolution,
useSourceOfProjectReferenceRedirect,
userResolvedModuleNames,
}: RunWatchBaseline<T>): void {
baseline.push(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}`);
let programs = watchBaseline({
@@ -171,6 +173,7 @@ export function runWatchBaseline<T extends ts.BuilderProgram = ts.EmitAndSemanti
caption,
resolutionCache: (watchOrSolution as ts.WatchOfConfigFile<T> | undefined)?.getResolutionCache?.(),
useSourceOfProjectReferenceRedirect,
userResolvedModuleNames,
});
}
}
@@ -183,6 +186,7 @@ export interface WatchBaseline extends BaselineBase, TscWatchCheckOptions {
caption?: string;
resolutionCache?: ts.ResolutionCache;
useSourceOfProjectReferenceRedirect?: () => boolean;
userResolvedModuleNames?: true;
}
export function watchBaseline({
baseline,
@@ -194,6 +198,7 @@ export function watchBaseline({
caption,
resolutionCache,
useSourceOfProjectReferenceRedirect,
userResolvedModuleNames,
}: WatchBaseline): readonly CommandLineProgram[] {
const programs = baselineAfterTscCompile(
sys,
@@ -213,6 +218,7 @@ export function watchBaseline({
programs[0][0],
resolutionCache,
useSourceOfProjectReferenceRedirect,
userResolvedModuleNames,
);
}
return programs;
@@ -222,7 +228,8 @@ function verifyProgramStructureAndResolutionCache(
sys: TscWatchSystem,
program: ts.Program,
resolutionCache: ts.ResolutionCache,
useSourceOfProjectReferenceRedirect?: () => boolean,
useSourceOfProjectReferenceRedirect: (() => boolean) | undefined,
userResolvedModuleNames: true | undefined,
) {
const options = program.getCompilerOptions();
const compilerHost = ts.createCompilerHostWorker(options, /*setParentNodes*/ undefined, sys);
@@ -239,25 +246,30 @@ function verifyProgramStructureAndResolutionCache(
program,
caption,
);
verifyResolutionCache(resolutionCache, program, {
...compilerHost,
verifyResolutionCache(
resolutionCache,
program,
{
...compilerHost,
getCompilerHost: () => compilerHost,
toPath: fileName => sys.toPath(fileName),
getCompilationSettings: () => options,
fileIsOpen: ts.returnFalse,
getCurrentProgram: () => program,
preferNonRecursiveWatch: sys.preferNonRecursiveWatch,
getCompilerHost: () => compilerHost,
toPath: fileName => sys.toPath(fileName),
getCompilationSettings: () => options,
fileIsOpen: ts.returnFalse,
getCurrentProgram: () => program,
preferNonRecursiveWatch: sys.preferNonRecursiveWatch,
watchDirectoryOfFailedLookupLocation: ts.returnNoopFileWatcher,
watchAffectingFileLocation: ts.returnNoopFileWatcher,
onInvalidatedResolution: ts.noop,
watchTypeRootsDirectory: ts.returnNoopFileWatcher,
onChangedAutomaticTypeDirectiveNames: ts.noop,
scheduleInvalidateResolutionsOfFailedLookupLocations: ts.noop,
getCachedDirectoryStructureHost: ts.returnUndefined,
writeLog: ts.noop,
}, caption);
watchDirectoryOfFailedLookupLocation: ts.returnNoopFileWatcher,
watchAffectingFileLocation: ts.returnNoopFileWatcher,
onInvalidatedResolution: ts.noop,
watchTypeRootsDirectory: ts.returnNoopFileWatcher,
onChangedAutomaticTypeDirectiveNames: ts.noop,
scheduleInvalidateResolutionsOfFailedLookupLocations: ts.noop,
getCachedDirectoryStructureHost: ts.returnUndefined,
writeLog: ts.noop,
},
caption,
userResolvedModuleNames,
);
}
export interface VerifyTscWatch extends TscWatchCompile {
baselineIncremental?: boolean;
@@ -118,6 +118,7 @@ describe("unittests:: tscWatch:: watchAPI:: tsc-watch with custom module resolut
},
],
watchOrSolution: watch,
userResolvedModuleNames: true,
});
});
}