node-spec.js 12 KB

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