electron/spec/node-spec.js

440 lines
14 KiB
JavaScript
Raw Normal View History

2016-06-29 16:37:10 +00:00
const ChildProcess = require('child_process')
const chai = require('chai')
const { expect } = chai
const dirtyChai = require('dirty-chai')
2016-03-25 20:03:49 +00:00
const fs = require('fs')
const path = require('path')
const os = require('os')
2018-09-13 16:10:51 +00:00
const { ipcRenderer, remote } = require('electron')
const features = process.atomBinding('features')
2016-03-25 20:03:49 +00:00
2016-06-20 02:16:17 +00:00
const isCI = remote.getGlobal('isCi')
chai.use(dirtyChai)
2016-06-20 02:16:17 +00:00
describe('node feature', () => {
const fixtures = path.join(__dirname, 'fixtures')
describe('child_process', () => {
beforeEach(function () {
if (!features.isRunAsNodeEnabled()) {
this.skip()
}
})
2018-06-18 03:09:51 +00:00
describe('child_process.fork', () => {
it('works in current process', (done) => {
const child = ChildProcess.fork(path.join(fixtures, 'module', 'ping.js'))
child.on('message', msg => {
expect(msg).to.equal('message')
2016-03-25 20:03:49 +00:00
done()
})
child.send('message')
})
it('preserves args', (done) => {
const args = ['--expose_gc', '-test', '1']
const child = ChildProcess.fork(path.join(fixtures, 'module', 'process_args.js'), args)
child.on('message', (msg) => {
expect(args).to.deep.equal(msg.slice(2))
2016-03-25 20:03:49 +00:00
done()
})
child.send('message')
})
it('works in forked process', (done) => {
const child = ChildProcess.fork(path.join(fixtures, 'module', 'fork_ping.js'))
child.on('message', (msg) => {
expect(msg).to.equal('message')
2016-03-25 20:03:49 +00:00
done()
})
child.send('message')
})
it('works in forked process when options.env is specifed', (done) => {
const child = ChildProcess.fork(path.join(fixtures, 'module', 'fork_ping.js'), [], {
2016-01-12 02:40:23 +00:00
path: process.env['PATH']
2016-03-25 20:03:49 +00:00
})
child.on('message', (msg) => {
expect(msg).to.equal('message')
2016-03-25 20:03:49 +00:00
done()
})
child.send('message')
})
it('works in browser process', (done) => {
const fork = remote.require('child_process').fork
const child = fork(path.join(fixtures, 'module', 'ping.js'))
child.on('message', (msg) => {
expect(msg).to.equal('message')
2016-03-25 20:03:49 +00:00
done()
})
child.send('message')
})
it('has String::localeCompare working in script', (done) => {
const child = ChildProcess.fork(path.join(fixtures, 'module', 'locale-compare.js'))
child.on('message', (msg) => {
expect(msg).to.deep.equal([0, -1, 1])
2016-03-25 20:03:49 +00:00
done()
})
child.send('message')
})
it('has setImmediate working in script', (done) => {
const child = ChildProcess.fork(path.join(fixtures, 'module', 'set-immediate.js'))
child.on('message', (msg) => {
expect(msg).to.equal('ok')
2016-03-25 20:03:49 +00:00
done()
})
child.send('message')
})
it('pipes stdio', (done) => {
2018-09-13 16:10:51 +00:00
const child = ChildProcess.fork(path.join(fixtures, 'module', 'process-stdout.js'), { silent: true })
let data = ''
child.stdout.on('data', (chunk) => {
data += String(chunk)
})
child.on('close', (code) => {
expect(code).to.equal(0)
expect(data).to.equal('pipes stdio')
done()
})
})
it('works when sending a message to a process forked with the --eval argument', (done) => {
2017-04-06 16:52:52 +00:00
const source = "process.on('message', (message) => { process.send(message) })"
const forked = ChildProcess.fork('--eval', [source])
2017-04-06 16:52:52 +00:00
forked.once('message', (message) => {
expect(message).to.equal('hello')
done()
})
forked.send('hello')
})
2016-03-25 20:03:49 +00:00
})
describe('child_process.spawn', () => {
let child
afterEach(() => {
if (child != null) child.kill()
})
it('supports spawning Electron as a node process via the ELECTRON_RUN_AS_NODE env var', (done) => {
child = ChildProcess.spawn(process.execPath, [path.join(__dirname, 'fixtures', 'module', 'run-as-node.js')], {
env: {
ELECTRON_RUN_AS_NODE: true
}
})
let output = ''
child.stdout.on('data', data => {
output += data
})
child.stdout.on('close', () => {
expect(JSON.parse(output)).to.deep.equal({
processLog: process.platform === 'win32' ? 'function' : 'undefined',
processType: 'undefined',
window: 'undefined'
})
done()
})
})
})
2016-03-25 20:03:49 +00:00
})
describe('contexts', () => {
describe('setTimeout in fs callback', () => {
it('does not crash', (done) => {
fs.readFile(__filename, () => {
2016-03-25 20:03:49 +00:00
setTimeout(done, 0)
})
})
})
describe('error thrown in renderer process node context', () => {
it('gets emitted as a process uncaughtException event', (done) => {
const error = new Error('boo!')
const listeners = process.listeners('uncaughtException')
2016-03-25 20:03:49 +00:00
process.removeAllListeners('uncaughtException')
process.on('uncaughtException', (thrown) => {
expect(thrown).to.equal(error)
2016-03-25 20:03:49 +00:00
process.removeAllListeners('uncaughtException')
listeners.forEach((listener) => process.on('uncaughtException', listener))
2016-03-25 20:03:49 +00:00
done()
})
fs.readFile(__filename, () => {
2016-03-25 20:03:49 +00:00
throw error
})
})
})
describe('error thrown in main process node context', () => {
it('gets emitted as a process uncaughtException event', () => {
const error = ipcRenderer.sendSync('handle-uncaught-exception', 'hello')
expect(error).to.equal('hello')
})
})
describe('promise rejection in main process node context', () => {
it('gets emitted as a process unhandledRejection event', () => {
const error = ipcRenderer.sendSync('handle-unhandled-rejection', 'hello')
expect(error).to.equal('hello')
})
})
describe('setTimeout called under Chromium event loop in browser process', () => {
it('can be scheduled in time', (done) => {
2016-03-25 20:03:49 +00:00
remote.getGlobal('setTimeout')(done, 0)
})
it('can be promisified', (done) => {
remote.getGlobal('setTimeoutPromisified')(0).then(done)
})
2016-03-25 20:03:49 +00:00
})
describe('setInterval called under Chromium event loop in browser process', () => {
it('can be scheduled in time', (done) => {
Fix some flaky tests in CI (#12153) * Guard whole InitPrefs with ScopedAllowIO Saw a crash: 0 0x7f8d2f7d918d base::debug::StackTrace::StackTrace() 1 0x7f8d2f7d755c base::debug::StackTrace::StackTrace() 2 0x7f8d2f867caa logging::LogMessage::~LogMessage() 3 0x7f8d2fa157c7 base::ThreadRestrictions::AssertIOAllowed() 4 0x7f8d2f83453a base::OpenFile() 5 0x7f8d2f82a967 base::ReadFileToStringWithMaxSize() 6 0x7f8d2f82ad44 base::ReadFileToString() 7 0x7f8d2f846f73 JSONFileValueDeserializer::ReadFileToString() 8 0x7f8d2f84738c JSONFileValueDeserializer::Deserialize() 9 0x7f8d35a5d1f6 <unknown> 10 0x7f8d35a5c217 JsonPrefStore::ReadPrefs() 11 0x7f8d35a87d3e PrefService::InitFromStorage() 12 0x7f8d35a87c60 PrefService::PrefService() 13 0x7f8d35a91a10 PrefServiceFactory::Create() 14 0x000000e86e1b brightray::BrowserContext::InitPrefs() 15 0x000000c2bd64 atom::AtomBrowserContext::AtomBrowserContext() 16 0x000000c320db atom::AtomBrowserContext::From() 17 0x000000b4b8b5 atom::api::Session::FromPartition() * Fix done being called twice in setInterval test The callback passed to browser process is called asyncly, so it is possible that multiple callbacks has already been scheduled before we can clearInternval. * Fix failing test when dir name has special chars The pdfSource is not escaped while parsedURL.search is. * Call done with Error instead of string * Fix crash caused by not removing input observer Solve crash: 0 libcontent.dylib content::RenderWidgetHostImpl::DispatchInputEventWithLatencyInfo(blink::WebInputEvent const&, ui::LatencyInfo*) + 214 1 libcontent.dylib content::RenderWidgetHostImpl::ForwardMouseEventWithLatencyInfo(blink::WebMouseEvent const&, ui::LatencyInfo const&) + 1350 2 libcontent.dylib content::RenderWidgetHostViewMac::ProcessMouseEvent(blink::WebMouseEvent const&, ui::LatencyInfo const&) + 44 3 libcontent.dylib content::RenderWidgetHostInputEventRouter::RouteMouseEvent(content::RenderWidgetHostViewBase*, blink::WebMouseEvent*, ui::LatencyInfo const&) + 1817 * Print detailed error * Run tests after server is ready
2018-03-07 05:40:27 +00:00
let interval = null
2018-03-14 05:51:47 +00:00
let clearing = false
Fix some flaky tests in CI (#12153) * Guard whole InitPrefs with ScopedAllowIO Saw a crash: 0 0x7f8d2f7d918d base::debug::StackTrace::StackTrace() 1 0x7f8d2f7d755c base::debug::StackTrace::StackTrace() 2 0x7f8d2f867caa logging::LogMessage::~LogMessage() 3 0x7f8d2fa157c7 base::ThreadRestrictions::AssertIOAllowed() 4 0x7f8d2f83453a base::OpenFile() 5 0x7f8d2f82a967 base::ReadFileToStringWithMaxSize() 6 0x7f8d2f82ad44 base::ReadFileToString() 7 0x7f8d2f846f73 JSONFileValueDeserializer::ReadFileToString() 8 0x7f8d2f84738c JSONFileValueDeserializer::Deserialize() 9 0x7f8d35a5d1f6 <unknown> 10 0x7f8d35a5c217 JsonPrefStore::ReadPrefs() 11 0x7f8d35a87d3e PrefService::InitFromStorage() 12 0x7f8d35a87c60 PrefService::PrefService() 13 0x7f8d35a91a10 PrefServiceFactory::Create() 14 0x000000e86e1b brightray::BrowserContext::InitPrefs() 15 0x000000c2bd64 atom::AtomBrowserContext::AtomBrowserContext() 16 0x000000c320db atom::AtomBrowserContext::From() 17 0x000000b4b8b5 atom::api::Session::FromPartition() * Fix done being called twice in setInterval test The callback passed to browser process is called asyncly, so it is possible that multiple callbacks has already been scheduled before we can clearInternval. * Fix failing test when dir name has special chars The pdfSource is not escaped while parsedURL.search is. * Call done with Error instead of string * Fix crash caused by not removing input observer Solve crash: 0 libcontent.dylib content::RenderWidgetHostImpl::DispatchInputEventWithLatencyInfo(blink::WebInputEvent const&, ui::LatencyInfo*) + 214 1 libcontent.dylib content::RenderWidgetHostImpl::ForwardMouseEventWithLatencyInfo(blink::WebMouseEvent const&, ui::LatencyInfo const&) + 1350 2 libcontent.dylib content::RenderWidgetHostViewMac::ProcessMouseEvent(blink::WebMouseEvent const&, ui::LatencyInfo const&) + 44 3 libcontent.dylib content::RenderWidgetHostInputEventRouter::RouteMouseEvent(content::RenderWidgetHostViewBase*, blink::WebMouseEvent*, ui::LatencyInfo const&) + 1817 * Print detailed error * Run tests after server is ready
2018-03-07 05:40:27 +00:00
const clear = () => {
if (interval === null || clearing) return
2018-03-14 05:51:47 +00:00
// interval might trigger while clearing (remote is slow sometimes)
clearing = true
2016-03-25 20:03:49 +00:00
remote.getGlobal('clearInterval')(interval)
2018-03-14 05:51:47 +00:00
clearing = false
Fix some flaky tests in CI (#12153) * Guard whole InitPrefs with ScopedAllowIO Saw a crash: 0 0x7f8d2f7d918d base::debug::StackTrace::StackTrace() 1 0x7f8d2f7d755c base::debug::StackTrace::StackTrace() 2 0x7f8d2f867caa logging::LogMessage::~LogMessage() 3 0x7f8d2fa157c7 base::ThreadRestrictions::AssertIOAllowed() 4 0x7f8d2f83453a base::OpenFile() 5 0x7f8d2f82a967 base::ReadFileToStringWithMaxSize() 6 0x7f8d2f82ad44 base::ReadFileToString() 7 0x7f8d2f846f73 JSONFileValueDeserializer::ReadFileToString() 8 0x7f8d2f84738c JSONFileValueDeserializer::Deserialize() 9 0x7f8d35a5d1f6 <unknown> 10 0x7f8d35a5c217 JsonPrefStore::ReadPrefs() 11 0x7f8d35a87d3e PrefService::InitFromStorage() 12 0x7f8d35a87c60 PrefService::PrefService() 13 0x7f8d35a91a10 PrefServiceFactory::Create() 14 0x000000e86e1b brightray::BrowserContext::InitPrefs() 15 0x000000c2bd64 atom::AtomBrowserContext::AtomBrowserContext() 16 0x000000c320db atom::AtomBrowserContext::From() 17 0x000000b4b8b5 atom::api::Session::FromPartition() * Fix done being called twice in setInterval test The callback passed to browser process is called asyncly, so it is possible that multiple callbacks has already been scheduled before we can clearInternval. * Fix failing test when dir name has special chars The pdfSource is not escaped while parsedURL.search is. * Call done with Error instead of string * Fix crash caused by not removing input observer Solve crash: 0 libcontent.dylib content::RenderWidgetHostImpl::DispatchInputEventWithLatencyInfo(blink::WebInputEvent const&, ui::LatencyInfo*) + 214 1 libcontent.dylib content::RenderWidgetHostImpl::ForwardMouseEventWithLatencyInfo(blink::WebMouseEvent const&, ui::LatencyInfo const&) + 1350 2 libcontent.dylib content::RenderWidgetHostViewMac::ProcessMouseEvent(blink::WebMouseEvent const&, ui::LatencyInfo const&) + 44 3 libcontent.dylib content::RenderWidgetHostInputEventRouter::RouteMouseEvent(content::RenderWidgetHostViewBase*, blink::WebMouseEvent*, ui::LatencyInfo const&) + 1817 * Print detailed error * Run tests after server is ready
2018-03-07 05:40:27 +00:00
interval = null
2016-03-25 20:03:49 +00:00
done()
}
interval = remote.getGlobal('setInterval')(clear, 10)
})
})
})
describe('inspector', () => {
let child = null
2017-11-06 14:25:48 +00:00
beforeEach(function () {
if (!features.isRunAsNodeEnabled()) {
this.skip()
}
})
2017-11-06 14:25:48 +00:00
afterEach(() => {
if (child !== null) child.kill()
2017-11-06 14:25:48 +00:00
})
it('supports starting the v8 inspector with --inspect/--inspect-brk', (done) => {
child = ChildProcess.spawn(process.execPath, ['--inspect-brk', path.join(__dirname, 'fixtures', 'module', 'run-as-node.js')], {
env: {
ELECTRON_RUN_AS_NODE: true
}
})
let output = ''
function cleanup () {
child.stderr.removeListener('data', errorDataListener)
child.stdout.removeListener('data', outDataHandler)
}
function errorDataListener (data) {
2017-11-06 14:25:48 +00:00
output += data
if (output.trim().startsWith('Debugger listening on ws://')) {
cleanup()
done()
}
}
function outDataHandler (data) {
cleanup()
2017-11-06 14:25:48 +00:00
done(new Error(`Unexpected output: ${data.toString()}`))
}
child.stderr.on('data', errorDataListener)
child.stdout.on('data', outDataHandler)
2017-11-06 14:25:48 +00:00
})
it('does not start the v8 inspector when --inspect is after a -- argument', (done) => {
child = ChildProcess.spawn(remote.process.execPath, [path.join(__dirname, 'fixtures', 'module', 'noop.js'), '--', '--inspect'])
let output = ''
function dataListener (data) {
output += data
}
child.stderr.on('data', dataListener)
child.stdout.on('data', dataListener)
child.on('exit', () => {
if (output.trim().startsWith('Debugger listening on ws://')) {
done(new Error('Inspector was started when it should not have been'))
} else {
done()
}
})
})
2017-11-06 14:25:48 +00:00
it('supports js binding', (done) => {
child = ChildProcess.spawn(process.execPath, ['--inspect', path.join(__dirname, 'fixtures', 'module', 'inspector-binding.js')], {
env: {
ELECTRON_RUN_AS_NODE: true
},
stdio: ['ipc']
})
child.on('message', ({ cmd, debuggerEnabled, success }) => {
2017-11-06 14:25:48 +00:00
if (cmd === 'assert') {
expect(debuggerEnabled).to.be.true()
expect(success).to.be.true()
2017-11-06 14:25:48 +00:00
done()
}
})
})
})
describe('message loop', () => {
describe('process.nextTick', () => {
it('emits the callback', (done) => process.nextTick(done))
2016-03-25 20:03:49 +00:00
it('works in nested calls', (done) => {
process.nextTick(() => {
process.nextTick(() => process.nextTick(done))
2016-03-25 20:03:49 +00:00
})
})
})
describe('setImmediate', () => {
it('emits the callback', (done) => setImmediate(done))
2016-03-25 20:03:49 +00:00
it('works in nested calls', (done) => {
setImmediate(() => {
setImmediate(() => setImmediate(done))
2016-03-25 20:03:49 +00:00
})
})
})
})
describe('net.connect', () => {
before(function () {
if (!features.isRunAsNodeEnabled() || process.platform !== 'darwin') {
this.skip()
}
})
2018-06-18 14:24:26 +00:00
it('emit error when connect to a socket path without listeners', (done) => {
const socketPath = path.join(os.tmpdir(), 'atom-shell-test.sock')
const script = path.join(fixtures, 'module', 'create_socket.js')
const child = ChildProcess.fork(script, [socketPath])
child.on('exit', (code) => {
expect(code).to.equal(0)
const client = require('net').connect(socketPath)
client.on('error', (error) => {
expect(error.code).to.equal('ECONNREFUSED')
2016-03-25 20:03:49 +00:00
done()
})
})
})
})
describe('Buffer', () => {
it('can be created from WebKit external string', () => {
const p = document.createElement('p')
2016-03-25 20:03:49 +00:00
p.innerText = '闲云潭影日悠悠,物换星移几度秋'
const b = Buffer.from(p.innerText)
expect(b.toString()).to.equal('闲云潭影日悠悠,物换星移几度秋')
expect(Buffer.byteLength(p.innerText)).to.equal(45)
2016-03-25 20:03:49 +00:00
})
it('correctly parses external one-byte UTF8 string', () => {
const p = document.createElement('p')
2016-03-25 20:03:49 +00:00
p.innerText = 'Jøhänñéß'
const b = Buffer.from(p.innerText)
expect(b.toString()).to.equal('Jøhänñéß')
expect(Buffer.byteLength(p.innerText)).to.equal(13)
2016-03-25 20:03:49 +00:00
})
2016-04-05 08:08:27 +00:00
it('does not crash when creating large Buffers', () => {
let buffer = Buffer.from(new Array(4096).join(' '))
expect(buffer.length).to.equal(4095)
buffer = Buffer.from(new Array(4097).join(' '))
expect(buffer.length).to.equal(4096)
2016-04-05 08:08:27 +00:00
})
2017-12-27 11:14:41 +00:00
it('does not crash for crypto operations', () => {
const crypto = require('crypto')
const data = 'lG9E+/g4JmRmedDAnihtBD4Dfaha/GFOjd+xUOQI05UtfVX3DjUXvrS98p7kZQwY3LNhdiFo7MY5rGft8yBuDhKuNNag9vRx/44IuClDhdQ='
const key = 'q90K9yBqhWZnAMCMTOJfPQ=='
const cipherText = '{"error_code":114,"error_message":"Tham số không hợp lệ","data":null}'
for (let i = 0; i < 10000; ++i) {
const iv = Buffer.from('0'.repeat(32), 'hex')
const input = Buffer.from(data, 'base64')
const decipher = crypto.createDecipheriv('aes-128-cbc', Buffer.from(key, 'base64'), iv)
const result = Buffer.concat([decipher.update(input), decipher.final()]).toString('utf8')
expect(cipherText).to.equal(result)
2017-12-27 11:14:41 +00:00
}
})
2016-03-25 20:03:49 +00:00
})
describe('process.stdout', () => {
it('does not throw an exception when accessed', () => {
expect(() => process.stdout).to.not.throw()
2016-03-25 20:03:49 +00:00
})
it('does not throw an exception when calling write()', () => {
expect(() => {
2016-09-07 21:40:18 +00:00
process.stdout.write('test')
}).to.not.throw()
2016-03-25 20:03:49 +00:00
})
it('should have isTTY defined on Mac and Linux', function () {
if (isCI || process.platform === 'win32') {
this.skip()
return
}
expect(process.stdout.isTTY).to.be.a('boolean')
})
2016-06-20 02:16:17 +00:00
it('should have isTTY undefined on Windows', function () {
if (isCI || process.platform !== 'win32') {
this.skip()
return
2016-09-08 20:12:00 +00:00
}
expect(process.stdout.isTTY).to.be.undefined()
2016-03-25 20:03:49 +00:00
})
})
describe('process.stdin', () => {
it('does not throw an exception when accessed', () => {
expect(() => process.stdin).to.not.throw()
2016-09-07 21:40:18 +00:00
})
it('returns null when read from', () => {
expect(process.stdin.read()).to.be.null()
2016-09-07 21:40:18 +00:00
})
})
describe('process.version', () => {
it('should not have -pre', () => {
expect(process.version.endsWith('-pre')).to.be.false()
})
})
describe('vm.runInNewContext', () => {
it('should not crash', () => {
2016-03-25 20:03:49 +00:00
require('vm').runInNewContext('')
})
})
it('includes the electron version in process.versions', () => {
expect(process.versions)
.to.have.own.property('electron')
.that.is.a('string')
.and.matches(/^\d+\.\d+\.\d+(\S*)?$/)
})
it('includes the chrome version in process.versions', () => {
expect(process.versions)
.to.have.own.property('chrome')
.that.is.a('string')
.and.matches(/^\d+\.\d+\.\d+\.\d+$/)
})
2016-03-25 20:03:49 +00:00
})