fix(ngcc): correctly invalidate cache when moving/removing files/directories (#35106)

One particular scenario where this was causing problems was when the
[BackupFileCleaner][1] restored a file (such as a `.d.ts` file) by
[moving the backup file][2] to its original location, but the modified
content was kept in the cache.

[1]: https://github.com/angular/angular/blob/4d36b2f6e/packages/compiler-cli/ngcc/src/writing/cleaning/cleaning_strategies.ts#L54
[2]: https://github.com/angular/angular/blob/4d36b2f6e/packages/compiler-cli/ngcc/src/writing/cleaning/cleaning_strategies.ts#L61

Fixes #35095

PR Close #35106
This commit is contained in:
George Kalpakas
2020-02-02 16:03:34 +02:00
committed by Miško Hevery
parent 3c30474417
commit 523c785e8f
5 changed files with 267 additions and 46 deletions

View File

@ -72,12 +72,16 @@ export class CachedFileSystem implements FileSystem {
moveFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void {
this.delegate.moveFile(from, to);
this.existsCache.set(from, false);
this.existsCache.set(to, true);
if (this.readFileCache.has(from)) {
this.readFileCache.set(to, this.readFileCache.get(from));
this.readFileCache.delete(from);
} else {
this.readFileCache.delete(to);
}
this.existsCache.set(to, true);
}
ensureDir(path: AbsoluteFsPath): void {
@ -90,10 +94,18 @@ export class CachedFileSystem implements FileSystem {
removeDeep(path: AbsoluteFsPath): void {
this.delegate.removeDeep(path);
// Clear out all children of this directory from the exists cache.
// Clear out this directory and all its children from the `exists` cache.
for (const p of this.existsCache.keys()) {
if (p.startsWith(path)) {
this.existsCache.set(path, false);
this.existsCache.set(p, false);
}
}
// Clear out this directory and all its children from the `readFile` cache.
for (const p of this.readFileCache.keys()) {
if (p.startsWith(path)) {
this.readFileCache.delete(p);
}
}
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CachedFileSystem} from '../src/cached_file_system';
import {absoluteFrom, setFileSystem} from '../src/helpers';
import {absoluteFrom, join, setFileSystem} from '../src/helpers';
import {NodeJSFileSystem} from '../src/node_js_file_system';
import {AbsoluteFsPath, FileSystem} from '../src/types';
@ -236,7 +236,7 @@ describe('CachedFileSystem', () => {
expect(readFileSpy).toHaveBeenCalledWith(abcPath);
});
it('should update the `to` "readFile" cache', () => {
it('should update the `to` "readFile" cache (if `from` was cached)', () => {
spyOn(delegate, 'moveFile');
const readFileSpy = spyOn(delegate, 'readFile');
@ -252,6 +252,24 @@ describe('CachedFileSystem', () => {
expect(fs.readFile(xyzPath)).toEqual('abc content');
expect(readFileSpy).not.toHaveBeenCalled();
});
it('should delete the `to` "readFile" cache (if `from` was not cached)', () => {
spyOn(delegate, 'moveFile');
const readFileSpy = spyOn(delegate, 'readFile');
// Fill the xyz "readFile" cache
readFileSpy.and.returnValue('xyz content');
fs.readFile(xyzPath);
readFileSpy.calls.reset();
// Move the file
fs.moveFile(abcPath, xyzPath);
// Show that the cache was not hit for the xyz file
readFileSpy.and.returnValue('abc content');
expect(fs.readFile(xyzPath)).toBe('abc content');
expect(readFileSpy).toHaveBeenCalledWith(xyzPath);
});
});
describe('ensureDir()', () => {
@ -281,14 +299,41 @@ describe('CachedFileSystem', () => {
});
it('should update the exists cache', () => {
spyOn(delegate, 'writeFile');
spyOn(delegate, 'removeDeep');
const existsSpy = spyOn(delegate, 'exists').and.returnValue(true);
expect(fs.exists(abcPath)).toBe(true);
existsSpy.calls.reset();
// Create a file inside `/a/b/c`.
const abcdPath = join(abcPath, 'd');
fs.writeFile(abcdPath, 'content');
expect(fs.exists(abcdPath)).toBe(true);
expect(existsSpy).not.toHaveBeenCalled();
// Remove the `/a/b/c` directory and ensure it is removed from cache (along with its content).
fs.removeDeep(abcPath);
expect(fs.exists(abcPath)).toBeFalsy();
expect(fs.exists(abcdPath)).toBeFalsy();
expect(existsSpy).not.toHaveBeenCalled();
});
it('should update the readFile cache', () => {
spyOn(delegate, 'writeFile');
spyOn(delegate, 'removeDeep');
spyOn(delegate, 'lstat').and.throwError('ENOENT: no such file or directory');
const readFileSpy = spyOn(delegate, 'readFile');
// Create a file inside `/a/b/c`.
const abcdPath = join(abcPath, 'd');
fs.writeFile(abcdPath, 'content from cache');
expect(fs.readFile(abcdPath)).toBe('content from cache');
expect(readFileSpy).not.toHaveBeenCalled();
// Remove the `/a/b/c` directory and ensure it is removed from cache (along with its content).
fs.removeDeep(abcPath);
expect(() => fs.readFile(abcdPath)).toThrowError('ENOENT: no such file or directory');
expect(() => fs.readFile(abcPath)).toThrowError('ENOENT: no such file or directory');
});
});
});