blob: 09e0afa863d6d005f1743cf5aac77e0de6dab88d [file] [log] [blame]
/**
* @license
* Copyright 2017 The Bazel Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'jasmine';
import * as ts from 'typescript';
import {CachedFileLoader, FileCache, ProgramAndFileCache} from './cache';
import {invalidateFileCache, writeTempFile} from './test_support';
function fauxDebug(...args: [any?, ...any[]]) {
console.error.apply(console, args);
}
describe('Cache', () => {
let fileCache: FileCache;
let fileLoader: CachedFileLoader;
function loadFile(name: string, fn: string) {
return fileLoader.loadFile(name, fn, ts.ScriptTarget.ES5);
}
function setCurrentFiles(...fileName: string[]) {
// Give all files the same digest, so that the file cache allows reading
// them, but does not evict them.
const digests =
new Map(fileName.map((fn): [string, string] => [fn, 'digest']));
fileCache.updateCache(digests);
}
function expectFileCacheKeys() {
// Strip the long /tmp paths created by writeTempFile.
return expect(
fileCache.getFileCacheKeysForTest().map(fn => fn.replace(/.*\./, '')));
}
describe('FileCache', () => {
beforeEach(() => {
fileCache = new FileCache(fauxDebug);
fileLoader = new CachedFileLoader(fileCache);
});
it('caches files', () => {
const fn = writeTempFile('file_cache_test', 'let x: number = 12;\n');
invalidateFileCache(fileCache, fn);
// Caches.
const sourceFile =
fileLoader.loadFile('fileName', fn, ts.ScriptTarget.ES5);
let sourceFile2 =
fileLoader.loadFile('fileName', fn, ts.ScriptTarget.ES5);
expect(sourceFile).toBe(sourceFile2); // i.e., identical w/ ===
// Invalidate the file.
invalidateFileCache(fileCache, fn);
sourceFile2 = fileLoader.loadFile('fileName', fn, ts.ScriptTarget.ES5);
// New file after write/mtime change.
expect(sourceFile).not.toBe(sourceFile2);
});
it('caches in LRU order', () => {
let free = false;
fileCache.shouldFreeMemory = () => free;
const fn1 = writeTempFile('file_cache_test1', 'let x: number = 1;\n');
const fn2 = writeTempFile('file_cache_test2', 'let x: number = 2;\n');
const fn3 = writeTempFile('file_cache_test3', 'let x: number = 3;\n');
const fn4 = writeTempFile('file_cache_test4', 'let x: number = 4;\n');
const fn5 = writeTempFile('file_cache_test5', 'let x: number = 5;\n');
setCurrentFiles(fn1, fn2, fn3, fn4, fn5);
// Populate the cache.
const f1 = loadFile('f1', fn1);
const f2 = loadFile('f2', fn2);
const f3 = loadFile('f3', fn3);
const f4 = loadFile('f4', fn4);
expectFileCacheKeys().toEqual([
'file_cache_test1',
'file_cache_test2',
'file_cache_test3',
'file_cache_test4',
]);
// Load f1 from cache again. Now f1 is the most recently used file.
expect(loadFile('f1', fn1)).toBe(f1, 'f1 should be cached');
expectFileCacheKeys().toEqual([
'file_cache_test2',
'file_cache_test3',
'file_cache_test4',
'file_cache_test1',
]);
// Now load f5 and pretend memory must be freed.
// length / 2 == 2 files must be cleared.
// f2 + f5 are pinned because they are part of the compilation unit.
// f1, f3, f4 are eligible for eviction, and f1 is more recently used than
// the others, so f1 shoud be retained, f3 + f4 dropped.
setCurrentFiles(fn2, fn5);
free = true;
const f5 = loadFile('f5', fn5);
expectFileCacheKeys().toEqual([
'file_cache_test2',
'file_cache_test1',
'file_cache_test5',
]);
setCurrentFiles(fn1, fn2, fn3, fn4, fn5);
expect(loadFile('f1', fn1))
.toBe(f1, 'f1 should not be dropped, it was recently used');
expect(loadFile('f2', fn2)).toBe(f2, 'f2 should be pinned');
expect(loadFile('f3', fn3)).not.toBe(f3, 'f3 should have been dropped');
expect(loadFile('f4', fn4)).not.toBe(f4, 'f4 should have been dropped');
expect(loadFile('f5', fn5)).toBe(f5, 'f5 should be pinned');
});
it('degenerates to cannotEvict mode', () => {
// Pretend to always be out of memory.
fileCache.shouldFreeMemory = () => true;
const fn1 = writeTempFile('file_cache_test1', 'let x: number = 1;\n');
const fn2 = writeTempFile('file_cache_test2', 'let x: number = 2;\n');
const fn3 = writeTempFile('file_cache_test3', 'let x: number = 3;\n');
const fn4 = writeTempFile('file_cache_test4', 'let x: number = 4;\n');
setCurrentFiles(fn1, fn2, fn3);
loadFile('fn1', fn1), loadFile('fn2', fn2);
loadFile('fn3', fn3);
expect(fileCache['cannotEvict']).toBe(true, 'all files are pinned');
expectFileCacheKeys().toEqual([
'file_cache_test1',
'file_cache_test2',
'file_cache_test3',
]);
setCurrentFiles(fn1, fn4);
expect(fileCache['cannotEvict']).toBe(false, 'pinned files reset');
loadFile('fn4', fn4);
expectFileCacheKeys().toEqual([
'file_cache_test1',
'file_cache_test4',
]);
});
});
describe('ProgramAndFileCache', () => {
let cache: ProgramAndFileCache;
function loadProgram(name: string): ts.Program {
const p = {} as ts.Program;
cache.putProgram(name, p);
return p;
}
function expectProgramCacheKeys() {
return expect(cache.getProgramCacheKeysForTest());
}
beforeEach(() => {
fileCache = new ProgramAndFileCache(fauxDebug);
fileLoader = new CachedFileLoader(fileCache);
cache = fileCache as ProgramAndFileCache;
});
it('caches programs', () => {
const name = 'fauxprogram';
const program = loadProgram(name);
expect(cache.getProgram(name)).toBe(program);
});
it('caches programs in LRU order', () => {
let free = false;
cache.shouldFreeMemory = () => free;
// Populate the cache.
const p1 = loadProgram('p1');
loadProgram('p2');
loadProgram('p3');
loadProgram('p4');
expectProgramCacheKeys().toEqual([
'p1',
'p2',
'p3',
'p4',
]);
// Load p1 from cache again. Now p1 is the most recently used program.
expect(cache.getProgram('p1')).toBe(p1);
expectProgramCacheKeys().toEqual([
'p2',
'p3',
'p4',
'p1',
]);
// Now load p5 and pretend memory must be freed.
// length / 2 == 2 files must be cleared.
free = true;
loadProgram('p5');
expectProgramCacheKeys().toEqual([
'p4',
'p1',
'p5',
]);
});
it('evicts programs before files', () => {
let free = false;
cache.shouldFreeMemory = () => free;
// Populate the cache.
loadProgram('p1');
const fn1 = writeTempFile('file_cache_test1', 'let x: number = 1;\n');
const fn2 = writeTempFile('file_cache_test2', 'let x: number = 2;\n');
setCurrentFiles(fn1);
loadFile('fn1', fn1);
expectProgramCacheKeys().toEqual(['p1']);
expectFileCacheKeys().toEqual(['file_cache_test1']);
free = true;
setCurrentFiles(fn2);
loadFile('fn2', fn2);
expectProgramCacheKeys().toEqual([]);
expectFileCacheKeys().toEqual(['file_cache_test1', 'file_cache_test2']);
});
});
});