node-spec.ts 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. import { expect } from 'chai';
  2. import * as childProcess from 'child_process';
  3. import * as path from 'path';
  4. import * as util from 'util';
  5. import { emittedOnce } from './events-helpers';
  6. import { ifdescribe, ifit } from './spec-helpers';
  7. import { webContents, WebContents } from 'electron';
  8. const features = process.electronBinding('features');
  9. describe('node feature', () => {
  10. const fixtures = path.join(__dirname, '..', 'spec', 'fixtures');
  11. describe('child_process', () => {
  12. describe('child_process.fork', () => {
  13. it('works in browser process', (done) => {
  14. const child = childProcess.fork(path.join(fixtures, 'module', 'ping.js'));
  15. child.on('message', (msg) => {
  16. expect(msg).to.equal('message');
  17. done();
  18. });
  19. child.send('message');
  20. });
  21. });
  22. });
  23. describe('contexts', () => {
  24. describe('setTimeout called under Chromium event loop in browser process', () => {
  25. it('can be scheduled in time', (done) => {
  26. setTimeout(done, 0);
  27. });
  28. it('can be promisified', (done) => {
  29. util.promisify(setTimeout)(0).then(done);
  30. });
  31. });
  32. describe('setInterval called under Chromium event loop in browser process', () => {
  33. it('can be scheduled in time', (done) => {
  34. let interval: any = null;
  35. let clearing = false;
  36. const clear = () => {
  37. if (interval === null || clearing) return;
  38. // interval might trigger while clearing (remote is slow sometimes)
  39. clearing = true;
  40. clearInterval(interval);
  41. clearing = false;
  42. interval = null;
  43. done();
  44. };
  45. interval = setInterval(clear, 10);
  46. });
  47. });
  48. });
  49. describe('NODE_OPTIONS', () => {
  50. let child: childProcess.ChildProcessWithoutNullStreams;
  51. let exitPromise: Promise<any[]>;
  52. afterEach(async () => {
  53. if (child && exitPromise) {
  54. const [code, signal] = await exitPromise;
  55. expect(signal).to.equal(null);
  56. expect(code).to.equal(0);
  57. } else if (child) {
  58. child.kill();
  59. }
  60. });
  61. it('fails for options disallowed by Node.js itself', (done) => {
  62. const env = Object.assign({}, process.env, { NODE_OPTIONS: '--v8-options' });
  63. child = childProcess.spawn(process.execPath, { env });
  64. function cleanup () {
  65. child.stderr.removeListener('data', listener);
  66. child.stdout.removeListener('data', listener);
  67. }
  68. let output = '';
  69. function listener (data: Buffer) {
  70. output += data;
  71. if (/electron: --v8-options is not allowed in NODE_OPTIONS/m.test(output)) {
  72. cleanup();
  73. done();
  74. }
  75. }
  76. child.stderr.on('data', listener);
  77. child.stdout.on('data', listener);
  78. });
  79. it('disallows crypto-related options', (done) => {
  80. const env = Object.assign({}, process.env, { NODE_OPTIONS: '--use-openssl-ca' });
  81. child = childProcess.spawn(process.execPath, ['--enable-logging'], { env });
  82. function cleanup () {
  83. child.stderr.removeListener('data', listener);
  84. child.stdout.removeListener('data', listener);
  85. }
  86. let output = '';
  87. function listener (data: Buffer) {
  88. output += data;
  89. if (/The NODE_OPTION --use-openssl-ca is not supported in Electron/m.test(output)) {
  90. cleanup();
  91. done();
  92. }
  93. }
  94. child.stderr.on('data', listener);
  95. child.stdout.on('data', listener);
  96. });
  97. });
  98. ifdescribe(features.isRunAsNodeEnabled())('inspector', () => {
  99. let child: childProcess.ChildProcessWithoutNullStreams;
  100. let exitPromise: Promise<any[]>;
  101. afterEach(async () => {
  102. if (child && exitPromise) {
  103. const [code, signal] = await exitPromise;
  104. expect(signal).to.equal(null);
  105. expect(code).to.equal(0);
  106. } else if (child) {
  107. child.kill();
  108. }
  109. });
  110. it('supports starting the v8 inspector with --inspect/--inspect-brk', (done) => {
  111. child = childProcess.spawn(process.execPath, ['--inspect-brk', path.join(fixtures, 'module', 'run-as-node.js')], {
  112. env: {
  113. ELECTRON_RUN_AS_NODE: 'true'
  114. }
  115. });
  116. let output = '';
  117. function cleanup () {
  118. child.stderr.removeListener('data', errorDataListener);
  119. child.stdout.removeListener('data', outDataHandler);
  120. }
  121. function errorDataListener (data: Buffer) {
  122. output += data;
  123. if (/^Debugger listening on ws:/m.test(output)) {
  124. cleanup();
  125. done();
  126. }
  127. }
  128. function outDataHandler (data: Buffer) {
  129. cleanup();
  130. done(new Error(`Unexpected output: ${data.toString()}`));
  131. }
  132. child.stderr.on('data', errorDataListener);
  133. child.stdout.on('data', outDataHandler);
  134. });
  135. it('supports starting the v8 inspector with --inspect and a provided port', (done) => {
  136. child = childProcess.spawn(process.execPath, ['--inspect=17364', path.join(fixtures, 'module', 'run-as-node.js')], {
  137. env: {
  138. ELECTRON_RUN_AS_NODE: 'true'
  139. }
  140. });
  141. exitPromise = emittedOnce(child, 'exit');
  142. let output = '';
  143. function cleanup () {
  144. child.stderr.removeListener('data', errorDataListener);
  145. child.stdout.removeListener('data', outDataHandler);
  146. }
  147. function errorDataListener (data: Buffer) {
  148. output += data;
  149. if (/^Debugger listening on ws:/m.test(output)) {
  150. expect(output.trim()).to.contain(':17364', 'should be listening on port 17364');
  151. cleanup();
  152. done();
  153. }
  154. }
  155. function outDataHandler (data: Buffer) {
  156. cleanup();
  157. done(new Error(`Unexpected output: ${data.toString()}`));
  158. }
  159. child.stderr.on('data', errorDataListener);
  160. child.stdout.on('data', outDataHandler);
  161. });
  162. it('does not start the v8 inspector when --inspect is after a -- argument', (done) => {
  163. child = childProcess.spawn(process.execPath, [path.join(fixtures, 'module', 'noop.js'), '--', '--inspect']);
  164. exitPromise = emittedOnce(child, 'exit');
  165. let output = '';
  166. function dataListener (data: Buffer) {
  167. output += data;
  168. }
  169. child.stderr.on('data', dataListener);
  170. child.stdout.on('data', dataListener);
  171. child.on('exit', () => {
  172. if (output.trim().startsWith('Debugger listening on ws://')) {
  173. done(new Error('Inspector was started when it should not have been'));
  174. } else {
  175. done();
  176. }
  177. });
  178. });
  179. // IPC Electron child process not supported on Windows
  180. ifit(process.platform !== 'win32')('does does not crash when quitting with the inspector connected', function (done) {
  181. child = childProcess.spawn(process.execPath, [path.join(fixtures, 'module', 'delay-exit'), '--inspect=0'], {
  182. stdio: ['ipc']
  183. }) as childProcess.ChildProcessWithoutNullStreams;
  184. exitPromise = emittedOnce(child, 'exit');
  185. let output = '';
  186. function dataListener (data: Buffer) {
  187. output += data;
  188. if (output.trim().indexOf('Debugger listening on ws://') > -1 && output.indexOf('\n') > -1) {
  189. const socketMatch = output.trim().match(/(ws:\/\/.+:[0-9]+\/.+?)\n/gm);
  190. if (socketMatch && socketMatch[0]) {
  191. child.stderr.removeListener('data', dataListener);
  192. child.stdout.removeListener('data', dataListener);
  193. const w = (webContents as any).create({}) as WebContents;
  194. w.loadURL('about:blank')
  195. .then(() => w.executeJavaScript(`new Promise(resolve => {
  196. const connection = new WebSocket(${JSON.stringify(socketMatch[0])})
  197. connection.onopen = () => {
  198. resolve()
  199. connection.close()
  200. }
  201. })`))
  202. .then(() => {
  203. (w as any).destroy();
  204. child.send('plz-quit');
  205. done();
  206. });
  207. }
  208. }
  209. }
  210. child.stderr.on('data', dataListener);
  211. child.stdout.on('data', dataListener);
  212. });
  213. it('supports js binding', (done) => {
  214. child = childProcess.spawn(process.execPath, ['--inspect', path.join(fixtures, 'module', 'inspector-binding.js')], {
  215. env: {
  216. ELECTRON_RUN_AS_NODE: 'true'
  217. },
  218. stdio: ['ipc']
  219. }) as childProcess.ChildProcessWithoutNullStreams;
  220. exitPromise = emittedOnce(child, 'exit');
  221. child.on('message', ({ cmd, debuggerEnabled, success }) => {
  222. if (cmd === 'assert') {
  223. expect(debuggerEnabled).to.be.true();
  224. expect(success).to.be.true();
  225. done();
  226. }
  227. });
  228. });
  229. });
  230. it('can find a module using a package.json main field', () => {
  231. const result = childProcess.spawnSync(process.execPath, [path.resolve(fixtures, 'api', 'electron-main-module', 'app.asar')]);
  232. expect(result.status).to.equal(0);
  233. });
  234. ifit(features.isRunAsNodeEnabled())('handles Promise timeouts correctly', (done) => {
  235. const scriptPath = path.join(fixtures, 'module', 'node-promise-timer.js');
  236. const child = childProcess.spawn(process.execPath, [scriptPath], {
  237. env: { ELECTRON_RUN_AS_NODE: 'true' }
  238. });
  239. emittedOnce(child, 'exit').then(([code, signal]) => {
  240. expect(code).to.equal(0);
  241. expect(signal).to.equal(null);
  242. child.kill();
  243. done();
  244. });
  245. });
  246. it('performs microtask checkpoint correctly', (done) => {
  247. const f3 = async () => {
  248. return new Promise((resolve, reject) => {
  249. reject(new Error('oops'));
  250. });
  251. };
  252. process.once('unhandledRejection', () => done('catch block is delayed to next tick'));
  253. setTimeout(() => {
  254. f3().catch(() => done());
  255. });
  256. });
  257. });