node-spec.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. const assert = require('assert')
  2. const ChildProcess = require('child_process')
  3. const { expect } = require('chai')
  4. const fs = require('fs')
  5. const path = require('path')
  6. const os = require('os')
  7. const { ipcRenderer, remote } = require('electron')
  8. const features = process.atomBinding('features')
  9. const isCI = remote.getGlobal('isCi')
  10. describe('node feature', () => {
  11. const fixtures = path.join(__dirname, 'fixtures')
  12. describe('child_process', () => {
  13. beforeEach(function () {
  14. if (!features.isRunAsNodeEnabled()) {
  15. this.skip()
  16. }
  17. })
  18. describe('child_process.fork', () => {
  19. it('works in current process', (done) => {
  20. const child = ChildProcess.fork(path.join(fixtures, 'module', 'ping.js'))
  21. child.on('message', (msg) => {
  22. assert.strictEqual(msg, 'message')
  23. done()
  24. })
  25. child.send('message')
  26. })
  27. it('preserves args', (done) => {
  28. const args = ['--expose_gc', '-test', '1']
  29. const child = ChildProcess.fork(path.join(fixtures, 'module', 'process_args.js'), args)
  30. child.on('message', (msg) => {
  31. assert.deepStrictEqual(args, msg.slice(2))
  32. done()
  33. })
  34. child.send('message')
  35. })
  36. it('works in forked process', (done) => {
  37. const child = ChildProcess.fork(path.join(fixtures, 'module', 'fork_ping.js'))
  38. child.on('message', (msg) => {
  39. assert.strictEqual(msg, 'message')
  40. done()
  41. })
  42. child.send('message')
  43. })
  44. it('works in forked process when options.env is specifed', (done) => {
  45. const child = ChildProcess.fork(path.join(fixtures, 'module', 'fork_ping.js'), [], {
  46. path: process.env['PATH']
  47. })
  48. child.on('message', (msg) => {
  49. assert.strictEqual(msg, 'message')
  50. done()
  51. })
  52. child.send('message')
  53. })
  54. it('works in browser process', (done) => {
  55. const fork = remote.require('child_process').fork
  56. const child = fork(path.join(fixtures, 'module', 'ping.js'))
  57. child.on('message', (msg) => {
  58. assert.strictEqual(msg, 'message')
  59. done()
  60. })
  61. child.send('message')
  62. })
  63. it('has String::localeCompare working in script', (done) => {
  64. const child = ChildProcess.fork(path.join(fixtures, 'module', 'locale-compare.js'))
  65. child.on('message', (msg) => {
  66. assert.deepStrictEqual(msg, [0, -1, 1])
  67. done()
  68. })
  69. child.send('message')
  70. })
  71. it('has setImmediate working in script', (done) => {
  72. const child = ChildProcess.fork(path.join(fixtures, 'module', 'set-immediate.js'))
  73. child.on('message', (msg) => {
  74. assert.strictEqual(msg, 'ok')
  75. done()
  76. })
  77. child.send('message')
  78. })
  79. it('pipes stdio', (done) => {
  80. const child = ChildProcess.fork(path.join(fixtures, 'module', 'process-stdout.js'), { silent: true })
  81. let data = ''
  82. child.stdout.on('data', (chunk) => {
  83. data += String(chunk)
  84. })
  85. child.on('close', (code) => {
  86. assert.strictEqual(code, 0)
  87. assert.strictEqual(data, 'pipes stdio')
  88. done()
  89. })
  90. })
  91. it('works when sending a message to a process forked with the --eval argument', (done) => {
  92. const source = "process.on('message', (message) => { process.send(message) })"
  93. const forked = ChildProcess.fork('--eval', [source])
  94. forked.once('message', (message) => {
  95. assert.strictEqual(message, 'hello')
  96. done()
  97. })
  98. forked.send('hello')
  99. })
  100. })
  101. describe('child_process.spawn', () => {
  102. let child
  103. afterEach(() => {
  104. if (child != null) child.kill()
  105. })
  106. it('supports spawning Electron as a node process via the ELECTRON_RUN_AS_NODE env var', (done) => {
  107. child = ChildProcess.spawn(process.execPath, [path.join(__dirname, 'fixtures', 'module', 'run-as-node.js')], {
  108. env: {
  109. ELECTRON_RUN_AS_NODE: true
  110. }
  111. })
  112. let output = ''
  113. child.stdout.on('data', (data) => {
  114. output += data
  115. })
  116. child.stdout.on('close', () => {
  117. assert.deepStrictEqual(JSON.parse(output), {
  118. processLog: process.platform === 'win32' ? 'function' : 'undefined',
  119. processType: 'undefined',
  120. window: 'undefined'
  121. })
  122. done()
  123. })
  124. })
  125. })
  126. })
  127. describe('contexts', () => {
  128. describe('setTimeout in fs callback', () => {
  129. it('does not crash', (done) => {
  130. fs.readFile(__filename, () => {
  131. setTimeout(done, 0)
  132. })
  133. })
  134. })
  135. describe('error thrown in renderer process node context', () => {
  136. it('gets emitted as a process uncaughtException event', (done) => {
  137. const error = new Error('boo!')
  138. const listeners = process.listeners('uncaughtException')
  139. process.removeAllListeners('uncaughtException')
  140. process.on('uncaughtException', (thrown) => {
  141. assert.strictEqual(thrown, error)
  142. process.removeAllListeners('uncaughtException')
  143. listeners.forEach((listener) => {
  144. process.on('uncaughtException', listener)
  145. })
  146. done()
  147. })
  148. fs.readFile(__filename, () => {
  149. throw error
  150. })
  151. })
  152. })
  153. describe('error thrown in main process node context', () => {
  154. it('gets emitted as a process uncaughtException event', () => {
  155. const error = ipcRenderer.sendSync('handle-uncaught-exception', 'hello')
  156. assert.strictEqual(error, 'hello')
  157. })
  158. })
  159. describe('promise rejection in main process node context', () => {
  160. it('gets emitted as a process unhandledRejection event', () => {
  161. const error = ipcRenderer.sendSync('handle-unhandled-rejection', 'hello')
  162. assert.strictEqual(error, 'hello')
  163. })
  164. })
  165. describe('setTimeout called under Chromium event loop in browser process', () => {
  166. it('can be scheduled in time', (done) => {
  167. remote.getGlobal('setTimeout')(done, 0)
  168. })
  169. it('can be promisified', (done) => {
  170. remote.getGlobal('setTimeoutPromisified')(0).then(done)
  171. })
  172. })
  173. describe('setInterval called under Chromium event loop in browser process', () => {
  174. it('can be scheduled in time', (done) => {
  175. let interval = null
  176. let clearing = false
  177. const clear = () => {
  178. if (interval === null || clearing) {
  179. return
  180. }
  181. // interval might trigger while clearing (remote is slow sometimes)
  182. clearing = true
  183. remote.getGlobal('clearInterval')(interval)
  184. clearing = false
  185. interval = null
  186. done()
  187. }
  188. interval = remote.getGlobal('setInterval')(clear, 10)
  189. })
  190. })
  191. })
  192. describe('inspector', () => {
  193. let child = null
  194. beforeEach(function () {
  195. if (!features.isRunAsNodeEnabled()) {
  196. this.skip()
  197. }
  198. })
  199. afterEach(() => {
  200. if (child !== null) child.kill()
  201. })
  202. it('supports starting the v8 inspector with --inspect/--inspect-brk', (done) => {
  203. child = ChildProcess.spawn(process.execPath, ['--inspect-brk', path.join(__dirname, 'fixtures', 'module', 'run-as-node.js')], {
  204. env: {
  205. ELECTRON_RUN_AS_NODE: true
  206. }
  207. })
  208. let output = ''
  209. function cleanup () {
  210. child.stderr.removeListener('data', errorDataListener)
  211. child.stdout.removeListener('data', outDataHandler)
  212. }
  213. function errorDataListener (data) {
  214. output += data
  215. if (output.trim().startsWith('Debugger listening on ws://')) {
  216. cleanup()
  217. done()
  218. }
  219. }
  220. function outDataHandler (data) {
  221. cleanup()
  222. done(new Error(`Unexpected output: ${data.toString()}`))
  223. }
  224. child.stderr.on('data', errorDataListener)
  225. child.stdout.on('data', outDataHandler)
  226. })
  227. it('supports js binding', (done) => {
  228. child = ChildProcess.spawn(process.execPath, ['--inspect', path.join(__dirname, 'fixtures', 'module', 'inspector-binding.js')], {
  229. env: {
  230. ELECTRON_RUN_AS_NODE: true
  231. },
  232. stdio: ['ipc']
  233. })
  234. child.on('message', ({ cmd, debuggerEnabled, secondSessionOpened, success }) => {
  235. if (cmd === 'assert') {
  236. assert.strictEqual(debuggerEnabled, true)
  237. assert.strictEqual(secondSessionOpened, true)
  238. assert.strictEqual(success, true)
  239. done()
  240. }
  241. })
  242. })
  243. })
  244. describe('message loop', () => {
  245. describe('process.nextTick', () => {
  246. it('emits the callback', (done) => {
  247. process.nextTick(done)
  248. })
  249. it('works in nested calls', (done) => {
  250. process.nextTick(() => {
  251. process.nextTick(() => {
  252. process.nextTick(done)
  253. })
  254. })
  255. })
  256. })
  257. describe('setImmediate', () => {
  258. it('emits the callback', (done) => {
  259. setImmediate(done)
  260. })
  261. it('works in nested calls', (done) => {
  262. setImmediate(() => {
  263. setImmediate(() => {
  264. setImmediate(done)
  265. })
  266. })
  267. })
  268. })
  269. })
  270. describe('net.connect', () => {
  271. before(function () {
  272. if (!features.isRunAsNodeEnabled() || process.platform !== 'darwin') {
  273. this.skip()
  274. }
  275. })
  276. it('emit error when connect to a socket path without listeners', (done) => {
  277. const socketPath = path.join(os.tmpdir(), 'atom-shell-test.sock')
  278. const script = path.join(fixtures, 'module', 'create_socket.js')
  279. const child = ChildProcess.fork(script, [socketPath])
  280. child.on('exit', (code) => {
  281. assert.strictEqual(code, 0)
  282. const client = require('net').connect(socketPath)
  283. client.on('error', (error) => {
  284. assert.strictEqual(error.code, 'ECONNREFUSED')
  285. done()
  286. })
  287. })
  288. })
  289. })
  290. describe('Buffer', () => {
  291. it('can be created from WebKit external string', () => {
  292. const p = document.createElement('p')
  293. p.innerText = '闲云潭影日悠悠,物换星移几度秋'
  294. const b = Buffer.from(p.innerText)
  295. assert.strictEqual(b.toString(), '闲云潭影日悠悠,物换星移几度秋')
  296. assert.strictEqual(Buffer.byteLength(p.innerText), 45)
  297. })
  298. it('correctly parses external one-byte UTF8 string', () => {
  299. const p = document.createElement('p')
  300. p.innerText = 'Jøhänñéß'
  301. const b = Buffer.from(p.innerText)
  302. assert.strictEqual(b.toString(), 'Jøhänñéß')
  303. assert.strictEqual(Buffer.byteLength(p.innerText), 13)
  304. })
  305. it('does not crash when creating large Buffers', () => {
  306. let buffer = Buffer.from(new Array(4096).join(' '))
  307. assert.strictEqual(buffer.length, 4095)
  308. buffer = Buffer.from(new Array(4097).join(' '))
  309. assert.strictEqual(buffer.length, 4096)
  310. })
  311. it('does not crash for crypto operations', () => {
  312. const crypto = require('crypto')
  313. const data = 'lG9E+/g4JmRmedDAnihtBD4Dfaha/GFOjd+xUOQI05UtfVX3DjUXvrS98p7kZQwY3LNhdiFo7MY5rGft8yBuDhKuNNag9vRx/44IuClDhdQ='
  314. const key = 'q90K9yBqhWZnAMCMTOJfPQ=='
  315. const cipherText = '{"error_code":114,"error_message":"Tham số không hợp lệ","data":null}'
  316. for (let i = 0; i < 10000; ++i) {
  317. const iv = Buffer.from('0'.repeat(32), 'hex')
  318. const input = Buffer.from(data, 'base64')
  319. const decipher = crypto.createDecipheriv('aes-128-cbc', Buffer.from(key, 'base64'), iv)
  320. const result = Buffer.concat([decipher.update(input), decipher.final()]).toString('utf8')
  321. assert.strictEqual(cipherText, result)
  322. }
  323. })
  324. })
  325. describe('process.stdout', () => {
  326. it('does not throw an exception when accessed', () => {
  327. assert.doesNotThrow(() => {
  328. // eslint-disable-next-line
  329. process.stdout
  330. })
  331. })
  332. it('does not throw an exception when calling write()', () => {
  333. assert.doesNotThrow(() => {
  334. process.stdout.write('test')
  335. })
  336. })
  337. it('should have isTTY defined on Mac and Linux', function () {
  338. if (isCI || process.platform === 'win32') {
  339. this.skip()
  340. return
  341. }
  342. assert.strictEqual(typeof process.stdout.isTTY, 'boolean')
  343. })
  344. it('should have isTTY undefined on Windows', function () {
  345. if (isCI || process.platform !== 'win32') {
  346. this.skip()
  347. return
  348. }
  349. assert.strictEqual(process.stdout.isTTY, undefined)
  350. })
  351. })
  352. describe('process.stdin', () => {
  353. it('does not throw an exception when accessed', () => {
  354. assert.doesNotThrow(() => {
  355. process.stdin // eslint-disable-line
  356. })
  357. })
  358. it('returns null when read from', () => {
  359. assert.strictEqual(process.stdin.read(), null)
  360. })
  361. })
  362. describe('process.version', () => {
  363. it('should not have -pre', () => {
  364. assert(!process.version.endsWith('-pre'))
  365. })
  366. })
  367. describe('vm.runInNewContext', () => {
  368. it('should not crash', () => {
  369. require('vm').runInNewContext('')
  370. })
  371. })
  372. describe('crypto', () => {
  373. it('should list the ripemd160 hash in getHashes', () => {
  374. expect(require('crypto').getHashes()).to.include('ripemd160')
  375. })
  376. it('should be able to create a ripemd160 hash and use it', () => {
  377. const hash = require('crypto').createHash('ripemd160')
  378. hash.update('electron-ripemd160')
  379. expect(hash.digest('hex')).to.equal('fa7fec13c624009ab126ebb99eda6525583395fe')
  380. })
  381. it('should list aes-{128,256}-cfb in getCiphers', () => {
  382. expect(require('crypto').getCiphers()).to.include.members(['aes-128-cfb', 'aes-256-cfb'])
  383. })
  384. it('should be able to create an aes-128-cfb cipher', () => {
  385. require('crypto').createCipheriv('aes-128-cfb', '0123456789abcdef', '0123456789abcdef')
  386. })
  387. it('should be able to create an aes-256-cfb cipher', () => {
  388. require('crypto').createCipheriv('aes-256-cfb', '0123456789abcdef0123456789abcdef', '0123456789abcdef')
  389. })
  390. it('should list des-ede-cbc in getCiphers', () => {
  391. expect(require('crypto').getCiphers()).to.include('des-ede-cbc')
  392. })
  393. it('should be able to create an des-ede-cbc cipher', () => {
  394. const key = Buffer.from('0123456789abcdeff1e0d3c2b5a49786', 'hex')
  395. const iv = Buffer.from('fedcba9876543210', 'hex')
  396. require('crypto').createCipheriv('des-ede-cbc', key, iv)
  397. })
  398. it('should not crash when getting an ECDH key', () => {
  399. const ecdh = require('crypto').createECDH('prime256v1')
  400. expect(ecdh.generateKeys()).to.be.an.instanceof(Buffer)
  401. expect(ecdh.getPrivateKey()).to.be.an.instanceof(Buffer)
  402. })
  403. it('should not crash when generating DH keys or fetching DH fields', () => {
  404. const dh = require('crypto').createDiffieHellman('modp15')
  405. expect(dh.generateKeys()).to.be.an.instanceof(Buffer)
  406. expect(dh.getPublicKey()).to.be.an.instanceof(Buffer)
  407. expect(dh.getPrivateKey()).to.be.an.instanceof(Buffer)
  408. expect(dh.getPrime()).to.be.an.instanceof(Buffer)
  409. expect(dh.getGenerator()).to.be.an.instanceof(Buffer)
  410. })
  411. it('should not crash when creating an ECDH cipher', () => {
  412. const crypto = require('crypto')
  413. const dh = crypto.createECDH('prime256v1')
  414. dh.generateKeys()
  415. dh.setPrivateKey(dh.getPrivateKey())
  416. })
  417. })
  418. it('includes the electron version in process.versions', () => {
  419. expect(process.versions)
  420. .to.have.own.property('electron')
  421. .that.is.a('string')
  422. .and.matches(/^\d+\.\d+\.\d+(\S*)?$/)
  423. })
  424. it('includes the chrome version in process.versions', () => {
  425. expect(process.versions)
  426. .to.have.own.property('chrome')
  427. .that.is.a('string')
  428. .and.matches(/^\d+\.\d+\.\d+\.\d+$/)
  429. })
  430. })