207 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			207 lines
		
	
	
		
			7.8 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 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();
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								});
							 |