2020-01-10 04:54:58 -05:00
/ * *
* @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 ` +
2020-01-28 05:13:08 -05:00
` 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 ` +
2020-01-10 04:54:58 -05:00
` (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 ( ) ;
} ) ;
} ) ;
} ) ;
} ) ;