spec-helpers.ts 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. import * as childProcess from 'child_process';
  2. import * as path from 'path';
  3. import * as http from 'http';
  4. import * as v8 from 'v8';
  5. import { SuiteFunction, TestFunction } from 'mocha';
  6. const addOnly = <T>(fn: Function): T => {
  7. const wrapped = (...args: any[]) => {
  8. return fn(...args);
  9. };
  10. (wrapped as any).only = wrapped;
  11. (wrapped as any).skip = wrapped;
  12. return wrapped as any;
  13. };
  14. export const ifit = (condition: boolean) => (condition ? it : addOnly<TestFunction>(it.skip));
  15. export const ifdescribe = (condition: boolean) => (condition ? describe : addOnly<SuiteFunction>(describe.skip));
  16. export const delay = (time: number = 0) => new Promise(resolve => setTimeout(resolve, time));
  17. type CleanupFunction = (() => void) | (() => Promise<void>)
  18. const cleanupFunctions: CleanupFunction[] = [];
  19. export async function runCleanupFunctions () {
  20. for (const cleanup of cleanupFunctions) {
  21. const r = cleanup();
  22. if (r instanceof Promise) { await r; }
  23. }
  24. cleanupFunctions.length = 0;
  25. }
  26. export function defer (f: CleanupFunction) {
  27. cleanupFunctions.unshift(f);
  28. }
  29. class RemoteControlApp {
  30. process: childProcess.ChildProcess;
  31. port: number;
  32. constructor (proc: childProcess.ChildProcess, port: number) {
  33. this.process = proc;
  34. this.port = port;
  35. }
  36. remoteEval = (js: string): Promise<any> => {
  37. return new Promise((resolve, reject) => {
  38. const req = http.request({
  39. host: '127.0.0.1',
  40. port: this.port,
  41. method: 'POST'
  42. }, res => {
  43. const chunks = [] as Buffer[];
  44. res.on('data', chunk => { chunks.push(chunk); });
  45. res.on('end', () => {
  46. const ret = v8.deserialize(Buffer.concat(chunks));
  47. if (Object.prototype.hasOwnProperty.call(ret, 'error')) {
  48. reject(new Error(`remote error: ${ret.error}\n\nTriggered at:`));
  49. } else {
  50. resolve(ret.result);
  51. }
  52. });
  53. });
  54. req.write(js);
  55. req.end();
  56. });
  57. }
  58. remotely = (script: Function, ...args: any[]): Promise<any> => {
  59. return this.remoteEval(`(${script})(...${JSON.stringify(args)})`);
  60. }
  61. }
  62. export async function startRemoteControlApp () {
  63. const appPath = path.join(__dirname, 'fixtures', 'apps', 'remote-control');
  64. const appProcess = childProcess.spawn(process.execPath, [appPath]);
  65. appProcess.stderr.on('data', d => {
  66. process.stderr.write(d);
  67. });
  68. const port = await new Promise<number>(resolve => {
  69. appProcess.stdout.on('data', d => {
  70. const m = /Listening: (\d+)/.exec(d.toString());
  71. if (m && m[1] != null) {
  72. resolve(Number(m[1]));
  73. }
  74. });
  75. });
  76. defer(() => { appProcess.kill('SIGINT'); });
  77. return new RemoteControlApp(appProcess, port);
  78. }