// Copyright (c) 2013-2020 GitHub Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import * as childProcess from 'child_process' import * as path from 'path' import * as http from 'http' import * as v8 from 'v8' import { SuiteFunction, TestFunction } from 'mocha' const addOnly = <T>(fn: Function): T => { const wrapped = (...args: any[]) => { return fn(...args) } ;(wrapped as any).only = wrapped ;(wrapped as any).skip = wrapped return wrapped as any } export const ifit = (condition: boolean) => (condition ? it : addOnly<TestFunction>(it.skip)) export const ifdescribe = (condition: boolean) => condition ? describe : addOnly<SuiteFunction>(describe.skip) export const delay = (time: number = 0) => new Promise((resolve) => setTimeout(resolve, time)) type CleanupFunction = (() => void) | (() => Promise<void>) const cleanupFunctions: CleanupFunction[] = [] export async function runCleanupFunctions() { for (const cleanup of cleanupFunctions) { const r = cleanup() if (r instanceof Promise) { await r } } cleanupFunctions.length = 0 } export function defer(f: CleanupFunction) { cleanupFunctions.unshift(f) } class RemoteControlApp { process: childProcess.ChildProcess port: number constructor(proc: childProcess.ChildProcess, port: number) { this.process = proc this.port = port } remoteEval = (js: string): Promise<any> => { return new Promise((resolve, reject) => { const req = http.request( { host: '127.0.0.1', port: this.port, method: 'POST', }, (res) => { const chunks = [] as Buffer[] res.on('data', (chunk) => { chunks.push(chunk) }) res.on('end', () => { const ret = v8.deserialize(Buffer.concat(chunks)) if (Object.prototype.hasOwnProperty.call(ret, 'error')) { reject(new Error(`remote error: ${ret.error}\n\nTriggered at:`)) } else { resolve(ret.result) } }) } ) req.write(js) req.end() }) } remotely = (script: Function, ...args: any[]): Promise<any> => { return this.remoteEval(`(${script})(...${JSON.stringify(args)})`) } } export async function startRemoteControlApp() { const appPath = path.join(__dirname, 'fixtures', 'apps', 'remote-control') const appProcess = childProcess.spawn(process.execPath, [appPath]) appProcess.stderr.on('data', (d) => { process.stderr.write(d) }) const port = await new Promise<number>((resolve) => { appProcess.stdout.on('data', (d) => { const m = /Listening: (\d+)/.exec(d.toString()) if (m && m[1] != null) { resolve(Number(m[1])) } }) }) defer(() => { appProcess.kill('SIGINT') }) return new RemoteControlApp(appProcess, port) } export async function getFiles(directoryPath: string, { filter = null }: any = {}) { const files: string[] = [] const walker = require('walkdir').walk(directoryPath, { no_recurse: true, }) walker.on('file', (file: string) => { if (!filter || filter(file)) { files.push(file) } }) await new Promise((resolve) => walker.on('end', resolve)) return files } export const uuid = () => require('uuid').v4()