The message now gives concrete advice to developers who experience the error due to running multiple simultaneous builds via webpack. Fixes #35000 PR Close #35001
		
			
				
	
	
		
			209 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			209 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
/**
 | 
						|
 * @license
 | 
						|
 * Copyright Google Inc. All Rights Reserved.
 | 
						|
 *
 | 
						|
 * Use of this source code is governed by an MIT-style license that can be
 | 
						|
 * found in the LICENSE file at https://angular.io/license
 | 
						|
 */
 | 
						|
import * as process from 'process';
 | 
						|
import {FileSystem, getFileSystem} from '../../../src/ngtsc/file_system';
 | 
						|
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
 | 
						|
import {LockFile} from '../../src/execution/lock_file';
 | 
						|
 | 
						|
/**
 | 
						|
 * This class allows us to test the protected methods of LockFile directly,
 | 
						|
 * which are normally hidden as "protected".
 | 
						|
 *
 | 
						|
 * We also add logging in here to track what is being called and in what order.
 | 
						|
 *
 | 
						|
 * Finally this class stubs out the `exit()` method to prevent unit tests from exiting the process.
 | 
						|
 */
 | 
						|
class LockFileUnderTest extends LockFile {
 | 
						|
  log: string[] = [];
 | 
						|
  constructor(fs: FileSystem, private handleSignals = false) {
 | 
						|
    super(fs);
 | 
						|
    fs.ensureDir(fs.dirname(this.lockFilePath));
 | 
						|
  }
 | 
						|
  create() {
 | 
						|
    this.log.push('create()');
 | 
						|
    super.create();
 | 
						|
  }
 | 
						|
  remove() {
 | 
						|
    this.log.push('remove()');
 | 
						|
    super.remove();
 | 
						|
  }
 | 
						|
  addSignalHandlers() {
 | 
						|
    if (this.handleSignals) {
 | 
						|
      super.addSignalHandlers();
 | 
						|
    }
 | 
						|
  }
 | 
						|
  removeSignalHandlers() { super.removeSignalHandlers(); }
 | 
						|
  exit(code: number) { this.log.push(`exit(${code})`); }
 | 
						|
}
 | 
						|
 | 
						|
runInEachFileSystem(() => {
 | 
						|
  describe('LockFile', () => {
 | 
						|
    describe('lock() - synchronous', () => {
 | 
						|
      it('should guard the `fn()` with calls to `create()` and `remove()`', () => {
 | 
						|
        const fs = getFileSystem();
 | 
						|
        const lockFile = new LockFileUnderTest(fs);
 | 
						|
 | 
						|
        lockFile.lock(() => lockFile.log.push('fn()'));
 | 
						|
        expect(lockFile.log).toEqual(['create()', 'fn()', 'remove()']);
 | 
						|
      });
 | 
						|
 | 
						|
      it('should guard the `fn()` with calls to `create()` and `remove()`, even if it throws',
 | 
						|
         () => {
 | 
						|
           let error: string = '';
 | 
						|
           const fs = getFileSystem();
 | 
						|
           const lockFile = new LockFileUnderTest(fs);
 | 
						|
 | 
						|
           try {
 | 
						|
             lockFile.lock(() => {
 | 
						|
               lockFile.log.push('fn()');
 | 
						|
               throw new Error('ERROR');
 | 
						|
             });
 | 
						|
           } catch (e) {
 | 
						|
             error = e.message;
 | 
						|
           }
 | 
						|
           expect(error).toEqual('ERROR');
 | 
						|
           expect(lockFile.log).toEqual(['create()', 'fn()', 'remove()']);
 | 
						|
         });
 | 
						|
 | 
						|
      it('should remove the lockfile if CTRL-C is triggered', () => {
 | 
						|
        const fs = getFileSystem();
 | 
						|
        const lockFile = new LockFileUnderTest(fs, /* handleSignals */ true);
 | 
						|
 | 
						|
        lockFile.lock(() => {
 | 
						|
          lockFile.log.push('SIGINT');
 | 
						|
          process.emit('SIGINT', 'SIGINT');
 | 
						|
        });
 | 
						|
        // Since the test does not actually exit process, the `remove()` is called one more time.
 | 
						|
        expect(lockFile.log).toEqual(['create()', 'SIGINT', 'remove()', 'exit(1)', 'remove()']);
 | 
						|
        // Clean up the signal handlers. In practice this is not needed since the process would have
 | 
						|
        // been terminated already.
 | 
						|
        lockFile.removeSignalHandlers();
 | 
						|
      });
 | 
						|
 | 
						|
      it('should remove the lockfile if terminal is closed', () => {
 | 
						|
        const fs = getFileSystem();
 | 
						|
        const lockFile = new LockFileUnderTest(fs, /* handleSignals */ true);
 | 
						|
 | 
						|
        lockFile.lock(() => {
 | 
						|
          lockFile.log.push('SIGHUP');
 | 
						|
          process.emit('SIGHUP', 'SIGHUP');
 | 
						|
        });
 | 
						|
        // Since this does not actually exit process, the `remove()` is called one more time.
 | 
						|
        expect(lockFile.log).toEqual(['create()', 'SIGHUP', 'remove()', 'exit(1)', 'remove()']);
 | 
						|
        // Clean up the signal handlers. In practice this is not needed since the process would have
 | 
						|
        // been terminated already.
 | 
						|
        lockFile.removeSignalHandlers();
 | 
						|
      });
 | 
						|
    });
 | 
						|
 | 
						|
    describe('lock() - asynchronous', () => {
 | 
						|
      it('should guard the `fn()` with calls to `create()` and `remove()`', async() => {
 | 
						|
        const fs = getFileSystem();
 | 
						|
        const lockFile = new LockFileUnderTest(fs);
 | 
						|
 | 
						|
        await lockFile.lock(async() => {
 | 
						|
          lockFile.log.push('fn() - before');
 | 
						|
          // This promise forces node to do a tick in this function, ensuring that we are truly
 | 
						|
          // testing an async scenario.
 | 
						|
          await Promise.resolve();
 | 
						|
          lockFile.log.push('fn() - after');
 | 
						|
        });
 | 
						|
        expect(lockFile.log).toEqual(['create()', 'fn() - before', 'fn() - after', 'remove()']);
 | 
						|
      });
 | 
						|
 | 
						|
      it('should guard the `fn()` with calls to `create()` and `remove()`, even if it throws',
 | 
						|
         async() => {
 | 
						|
           let error: string = '';
 | 
						|
           const fs = getFileSystem();
 | 
						|
           const lockFile = new LockFileUnderTest(fs);
 | 
						|
           lockFile.create = () => lockFile.log.push('create()');
 | 
						|
           lockFile.remove = () => lockFile.log.push('remove()');
 | 
						|
 | 
						|
           try {
 | 
						|
             await lockFile.lock(async() => {
 | 
						|
               lockFile.log.push('fn()');
 | 
						|
               throw new Error('ERROR');
 | 
						|
             });
 | 
						|
           } catch (e) {
 | 
						|
             error = e.message;
 | 
						|
           }
 | 
						|
           expect(error).toEqual('ERROR');
 | 
						|
           expect(lockFile.log).toEqual(['create()', 'fn()', 'remove()']);
 | 
						|
         });
 | 
						|
 | 
						|
      it('should remove the lockfile if CTRL-C is triggered', async() => {
 | 
						|
        const fs = getFileSystem();
 | 
						|
        const lockFile = new LockFileUnderTest(fs, /* handleSignals */ true);
 | 
						|
 | 
						|
        await lockFile.lock(async() => {
 | 
						|
          lockFile.log.push('SIGINT');
 | 
						|
          process.emit('SIGINT', 'SIGINT');
 | 
						|
        });
 | 
						|
        // Since the test does not actually exit process, the `remove()` is called one more time.
 | 
						|
        expect(lockFile.log).toEqual(['create()', 'SIGINT', 'remove()', 'exit(1)', 'remove()']);
 | 
						|
        // Clean up the signal handlers. In practice this is not needed since the process would have
 | 
						|
        // been terminated already.
 | 
						|
        lockFile.removeSignalHandlers();
 | 
						|
      });
 | 
						|
 | 
						|
      it('should remove the lockfile if terminal is closed', async() => {
 | 
						|
        const fs = getFileSystem();
 | 
						|
        const lockFile = new LockFileUnderTest(fs, /* handleSignals */ true);
 | 
						|
 | 
						|
        await lockFile.lock(async() => {
 | 
						|
          lockFile.log.push('SIGHUP');
 | 
						|
          process.emit('SIGHUP', 'SIGHUP');
 | 
						|
        });
 | 
						|
        // Since this does not actually exit process, the `remove()` is called one more time.
 | 
						|
        expect(lockFile.log).toEqual(['create()', 'SIGHUP', 'remove()', 'exit(1)', 'remove()']);
 | 
						|
        // Clean up the signal handlers. In practice this is not needed since the process would have
 | 
						|
        // been terminated already.
 | 
						|
        lockFile.removeSignalHandlers();
 | 
						|
      });
 | 
						|
    });
 | 
						|
 | 
						|
    describe('create()', () => {
 | 
						|
      it('should write a lock file to the file-system', () => {
 | 
						|
        const fs = getFileSystem();
 | 
						|
        const lockFile = new LockFileUnderTest(fs);
 | 
						|
        expect(fs.exists(lockFile.lockFilePath)).toBe(false);
 | 
						|
        lockFile.create();
 | 
						|
        expect(fs.exists(lockFile.lockFilePath)).toBe(true);
 | 
						|
      });
 | 
						|
 | 
						|
      it('should error if a lock file already exists', () => {
 | 
						|
        const fs = getFileSystem();
 | 
						|
        const lockFile = new LockFileUnderTest(fs);
 | 
						|
        fs.writeFile(lockFile.lockFilePath, '188');
 | 
						|
        expect(() => lockFile.create())
 | 
						|
            .toThrowError(
 | 
						|
                `ngcc is already running at process with id 188.\n` +
 | 
						|
                `If you are running multiple builds in parallel then you should pre-process your node_modules via the command line ngcc tool before starting the builds;\n` +
 | 
						|
                `See https://v9.angular.io/guide/ivy#speeding-up-ngcc-compilation.\n` +
 | 
						|
                `(If you are sure no ngcc process is running then you should delete the lockfile at ${lockFile.lockFilePath}.)`);
 | 
						|
      });
 | 
						|
    });
 | 
						|
 | 
						|
    describe('remove()', () => {
 | 
						|
      it('should remove the lock file from the file-system', () => {
 | 
						|
        const fs = getFileSystem();
 | 
						|
        const lockFile = new LockFileUnderTest(fs);
 | 
						|
        fs.writeFile(lockFile.lockFilePath, '188');
 | 
						|
        lockFile.remove();
 | 
						|
        expect(fs.exists(lockFile.lockFilePath)).toBe(false);
 | 
						|
      });
 | 
						|
 | 
						|
      it('should not error if the lock file does not exist', () => {
 | 
						|
        const fs = getFileSystem();
 | 
						|
        const lockFile = new LockFileUnderTest(fs);
 | 
						|
        expect(() => lockFile.remove()).not.toThrow();
 | 
						|
      });
 | 
						|
    });
 | 
						|
  });
 | 
						|
});
 |