137 lines
4.3 KiB
TypeScript
137 lines
4.3 KiB
TypeScript
// 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 'node:child_process'
|
|
import * as nodeCrypto from 'node:crypto'
|
|
import * as path from 'node:path'
|
|
import * as http from 'node: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 = () => nodeCrypto.randomUUID()
|