feat(ngcc): lock ngcc when processing (#34722)

Previously, it was possible for multiple instance of ngcc to be running
at the same time, but this is not supported and can cause confusing and
flakey errors at build time.

Now, only one instance of ngcc can run at a time. If a second instance
tries to execute it fails with an appropriate error message.

See https://github.com/angular/angular/issues/32431#issuecomment-571825781

PR Close #34722
This commit is contained in:
Pete Bacon Darwin
2020-01-10 09:54:58 +00:00
committed by Andrew Kushnir
parent d243d851a5
commit 6dd51f1cc2
9 changed files with 553 additions and 33 deletions

View File

@ -14,6 +14,7 @@ import {ClusterExecutor} from '../../../src/execution/cluster/executor';
import {ClusterMaster} from '../../../src/execution/cluster/master';
import {ClusterWorker} from '../../../src/execution/cluster/worker';
import {PackageJsonUpdater} from '../../../src/writing/package_json_updater';
import {MockLockFile} from '../../helpers/mock_lock_file';
import {MockLogger} from '../../helpers/mock_logger';
import {mockProperty} from '../../helpers/spy_utils';
@ -23,14 +24,19 @@ describe('ClusterExecutor', () => {
let masterRunSpy: jasmine.Spy;
let workerRunSpy: jasmine.Spy;
let mockLogger: MockLogger;
let mockLockFile: MockLockFile;
let executor: ClusterExecutor;
beforeEach(() => {
masterRunSpy = spyOn(ClusterMaster.prototype, 'run');
workerRunSpy = spyOn(ClusterWorker.prototype, 'run');
masterRunSpy = spyOn(ClusterMaster.prototype, 'run')
.and.returnValue(Promise.resolve('CusterMaster#run()'));
workerRunSpy = spyOn(ClusterWorker.prototype, 'run')
.and.returnValue(Promise.resolve('CusterWorker#run()'));
mockLogger = new MockLogger();
executor = new ClusterExecutor(42, mockLogger, null as unknown as PackageJsonUpdater);
mockLockFile = new MockLockFile();
executor =
new ClusterExecutor(42, mockLogger, null as unknown as PackageJsonUpdater, mockLockFile);
});
describe('execute()', () => {
@ -47,7 +53,6 @@ describe('ClusterExecutor', () => {
});
it('should delegate to `ClusterMaster#run()`', async() => {
masterRunSpy.and.returnValue('CusterMaster#run()');
const analyzeEntryPointsSpy = jasmine.createSpy('analyzeEntryPoints');
const createCompilerFnSpy = jasmine.createSpy('createCompilerFn');
@ -60,6 +65,58 @@ describe('ClusterExecutor', () => {
expect(analyzeEntryPointsSpy).toHaveBeenCalledWith();
expect(createCompilerFnSpy).not.toHaveBeenCalled();
});
it('should call LockFile.create() and LockFile.remove() if master runner completes successfully',
async() => {
const anyFn: () => any = () => undefined;
await executor.execute(anyFn, anyFn);
expect(mockLockFile.log).toEqual(['create()', 'remove()']);
});
it('should call LockFile.create() and LockFile.remove() if master runner fails', async() => {
const anyFn: () => any = () => undefined;
masterRunSpy.and.returnValue(Promise.reject(new Error('master runner error')));
let error = '';
try {
await executor.execute(anyFn, anyFn);
} catch (e) {
error = e.message;
}
expect(error).toEqual('master runner error');
expect(mockLockFile.log).toEqual(['create()', 'remove()']);
});
it('should not call master runner if Lockfile.create() fails', async() => {
const anyFn: () => any = () => undefined;
const lockFile = new MockLockFile({throwOnCreate: true});
executor =
new ClusterExecutor(42, mockLogger, null as unknown as PackageJsonUpdater, lockFile);
let error = '';
try {
await executor.execute(anyFn, anyFn);
} catch (e) {
error = e.message;
}
expect(error).toEqual('LockFile.create() error');
expect(lockFile.log).toEqual(['create()']);
expect(masterRunSpy).not.toHaveBeenCalled();
});
it('should fail if Lockfile.remove() fails', async() => {
const anyFn: () => any = () => undefined;
const lockFile = new MockLockFile({throwOnRemove: true});
executor =
new ClusterExecutor(42, mockLogger, null as unknown as PackageJsonUpdater, lockFile);
let error = '';
try {
await executor.execute(anyFn, anyFn);
} catch (e) {
error = e.message;
}
expect(error).toEqual('LockFile.remove() error');
expect(lockFile.log).toEqual(['create()', 'remove()']);
expect(masterRunSpy).toHaveBeenCalled();
});
});
describe('(on cluster worker)', () => {
@ -73,7 +130,6 @@ describe('ClusterExecutor', () => {
});
it('should delegate to `ClusterWorker#run()`', async() => {
workerRunSpy.and.returnValue('CusterWorker#run()');
const analyzeEntryPointsSpy = jasmine.createSpy('analyzeEntryPoints');
const createCompilerFnSpy = jasmine.createSpy('createCompilerFn');
@ -86,6 +142,12 @@ describe('ClusterExecutor', () => {
expect(analyzeEntryPointsSpy).not.toHaveBeenCalled();
expect(createCompilerFnSpy).toHaveBeenCalledWith(jasmine.any(Function));
});
it('should not call LockFile.create() or LockFile.remove()', async() => {
const anyFn: () => any = () => undefined;
await executor.execute(anyFn, anyFn);
expect(mockLockFile.log).toEqual([]);
});
});
});
});