import { logUtils as log } from '@0x/utils' ; import { ChildProcessWithoutNullStreams , spawn } from 'child_process' ; import * as fs from 'fs' ; import * as path from 'path' ; import * as redis from 'redis' ; import { REDIS_URI } from '../../src/config' ; import { getDBConnectionAsync } from '../../src/db_connection' ; const apiRootDir = path . normalize ( path . resolve ( `${ __dirname }/../../../` ) ) ; const dockerComposeFilename = 'docker-compose-test.yml' ; export enum LogType { Console , File } param apiLogType param dependencyLogType export interface LoggingConfig { apiLogType ? : LogType ; dependencyLogType ? : LogType ; } let didTearDown = false ; param suiteName param logType export async function setupDependenciesAsync ( suiteName , logType ? ) { if ( ! didTearDown ) { await teardownDependenciesAsync ( suiteName , logType ) ; } const up = spawn ( 'docker-compose' , [ '-f' , dockerComposeFilename , 'up' ] , { cwd : apiRootDir , } ) ; directLogs ( up , suiteName , 'up' , logType ) ; didTearDown = false ; await waitForDependencyStartupAsync ( up ) ; await sleepAsync ( 10 ) ; await confirmPostgresConnectivityAsync ( ) ; await confirmRedisConnectivityAsync ( ) ; } param suiteName param logType export async function teardownDependenciesAsync ( suiteName , logType ? ) { const down = spawn ( 'docker-compose' , [ '-f' , dockerComposeFilename , 'down' ] , { cwd : apiRootDir , } ) ; directLogs ( down , suiteName , 'down' , logType ) ; const timeout = 20000 ; await waitForCloseAsync ( down , 'down' , timeout ) ; didTearDown = true ; } function directLogs ( stream , suiteName , command , logType ? ) { if ( logType === LogType . Console ) { stream . stdout . on ( 'data' , ( chunk ) => { neatlyPrintChunk ( `[${ suiteName }-${ command }]` , chunk ) ; } ) ; stream . stderr . on ( 'data' , ( chunk ) => { neatlyPrintChunk ( `[${ suiteName }-${ command } | error]` , chunk ) ; } ) ; } else if ( logType === LogType . File ) { const logStream = fs . createWriteStream ( `${ apiRootDir }/${ suiteName }_${ command }_logs` , { flags : 'a' } ) ; const errorStream = fs . createWriteStream ( `${ apiRootDir }/${ suiteName }_${ command }_errors` , { flags : 'a' } ) ; stream . stdout . pipe ( logStream ) ; stream . stderr . pipe ( errorStream ) ; } } function neatlyPrintChunk ( prefix , chunk ) { const data = chunk . toString ( ) . split ( '\n' ) ; data . filter ( ( datum ) => datum !== '' ) . map ( ( datum ) => { log . log ( prefix , datum . trim ( ) ) ; } ) ; } async function waitForCloseAsync ( stream , command , timeout ? ) { return new Promise < void > ( ( resolve , reject ) => { stream . on ( 'close' , ( ) => { resolve ( ) ; } ) ; if ( timeout !== undefined ) { setTimeout ( ( ) => { reject ( new Error ( `Timed out waiting for "${ command }" to close` ) ) ; } , timeout ) ; } } ) ; } async function waitForDependencyStartupAsync ( logStream ) { return new Promise < void > ( ( resolve , reject ) => { logStream . stdout . on ( 'data' , ( chunk ) => { const data = chunk . toString ( ) . split ( '\n' ) ; for ( const datum of data ) { if ( /.*postgres.*PostgreSQL init process complete; ready for start up./ . test ( datum ) ) { resolve ( ) ; return ; } } } ) ; setTimeout ( ( ) => { reject ( new Error ( 'Timed out waiting for dependency logs' ) ) ; } , 60000 ) ; } ) ; } async function confirmPostgresConnectivityAsync ( maxTries = 5 ) { try { await Promise . all ( [ new Promise < void > ( ( resolve ) => setTimeout ( resolve , 2000 ) ) , async ( ) => { const connection = await getDBConnectionAsync ( ) ; await connection . synchronize ( true ) ; } , ] ) ; return ; } catch ( e ) { if ( maxTries > 0 ) { await confirmPostgresConnectivityAsync ( maxTries - 1 ) ; } else { throw e ; } } } async function confirmRedisConnectivityAsync ( maxTries = 5 ) { try { await Promise . all ( [ new Promise < void > ( ( resolve ) => setTimeout ( resolve , 2000 ) ) , async ( ) => { const redisClient = redis . createClient ( { url : REDIS_URI } ) ; return new Promise ( ( resolve , reject ) => { redisClient . ping ( ( err , reply ) => { if ( err ) { reject ( err ) ; } resolve ( reply ) ; } ) ; } ) ; } , ] ) ; return ; } catch ( e ) { if ( maxTries > 0 ) { await confirmRedisConnectivityAsync ( maxTries - 1 ) ; } else { throw e ; } } } async function sleepAsync ( timeSeconds ) { return new Promise < void > ( ( resolve ) => { const secondsPerMillisecond = 1000 ; setTimeout ( resolve , timeSeconds * secondsPerMillisecond ) ; } ) ; }