node-gdb
Version:
Control GDB from node.js
412 lines (359 loc) • 12.7 kB
text/coffeescript
GDB = require '../src'
sinon = require 'sinon'
assert = require 'assert'
child_process = require 'child_process'
{CompositeDisposable} = require 'event-kit'
testfile = (gdb, srcfile) ->
new Promise (resolve, reject) ->
binfile = 'test/bin/' + srcfile.slice(0, srcfile.lastIndexOf('.'))
srcfile = 'test/src/' + srcfile
child_process.exec "cc -g -O0 -o #{binfile} #{srcfile}", (err) ->
if err? then reject(err) else resolve(binfile)
.then (binfile) ->
gdb.setFile binfile
waitStop = (gdb, cmd) ->
new Promise (resolve, reject) ->
x = new CompositeDisposable
x.add gdb.exec.onRunning ->
x.add gdb.exec.onExited ->
x.dispose()
reject new Error 'Target exited'
x.add gdb.exec.onStopped (result) ->
x.dispose()
resolve result.frame
cmd()
describe 'GDB core', ->
gdb = null
beforeEach ->
gdb = new GDB()
afterEach -> gdb?.destroy()
describe 'Connecting and disconnecting', ->
it 'emits connected when connecting', ->
spy = sinon.spy()
gdb.onConnect spy
gdb.connect().then ->
assert spy.called
it 'emits disconnected when disconnecting', (done) ->
gdb.onDisconnect done
gdb.connect()
.then ->
gdb.disconnect()
return
it 'emits disconnected then connected when reconnecting', ->
disconnect = sinon.spy()
connect = sinon.spy ->
assert(disconnect.called)
gdb.connect()
.then ->
gdb.onDisconnect disconnect
gdb.onConnect connect
gdb.connect()
.then ->
assert(connect.called)
it "rejects promise if can't spawn child", ->
gdb.connect("unlikely-to-exist")
.then ->
assert(not "Shouldn't get here")
.catch (err) ->
assert(err.constructor is Error)
describe 'When connected', ->
beforeEach ->
gdb.connect()
it 'accepts cli commands and emits console-output event', ->
spy = sinon.spy()
gdb.onConsoleOutput spy
gdb.send_cli 'show version'
.then ->
assert spy.called
it 'ignore comment lines on cli', ->
gdb.send_cli ' #some comment'
it 'rejects bad cli command with exception', ->
gdb.send_cli 'badcommand'
.then ->
assert(not "Shouldn't get here")
.catch (err) ->
assert(err.constructor is Error)
it 'accepts cwd', ->
gdb.setCwd('.')
it 'rejects invalid cwd', ->
gdb.setCwd('./unlikely-to-exist')
.then ->
assert(not "Shouldn't get here")
.catch (err) ->
assert(err.constructor is Error)
it 'allows setting internal variables', ->
gdb.set('confirm', 'off')
.then ->
gdb.show('confirm')
.then (foo) ->
assert(foo == 'off')
it 'allow setting target binary', ->
testfile(gdb, 'simple.c')
it 'rejects non-existant binary', ->
gdb.setFile('garbage')
.then ->
assert(false)
.catch (err) ->
assert(err.constructor is Error)
describe 'GDB Execution State', ->
gdb = null
beforeEach ->
gdb = new GDB()
gdb.connect()
.then -> testfile(gdb, 'simple.c')
it 'can start the target program', ->
waitStop gdb, -> gdb.exec.start()
.then (frame) ->
assert frame.file.match /.*simple\.c$/
assert frame.func == 'main'
beforeEach ->
waitStop gdb, => gdb.exec.start()
it 'can resume execution', (done) ->
gdb.exec.onExited ->
done()
gdb.exec.continue()
return
it 'can step over function calls', ->
waitStop gdb, -> gdb.exec.next()
.then (frame) ->
assert frame.func == 'main'
it 'can step into function calls', ->
waitStop gdb, -> gdb.exec.step()
.then (frame) ->
assert frame.func == 'func1'
it 'can step out of function calls', ->
waitStop gdb, -> gdb.exec.step()
.then -> waitStop gdb, -> gdb.exec.finish()
.then (frame) ->
assert frame.func == 'main'
it 'notifies observers of state changes', ->
stateSequence = []
x = new CompositeDisposable
x.add gdb.exec.onExited -> stateSequence.push 'exit'
x.add gdb.exec.onRunning -> stateSequence.push 'run'
x.add gdb.exec.onStopped -> stateSequence.push 'stop'
waitStop gdb, -> gdb.exec.next()
.then -> waitStop(gdb, -> gdb.exec.continue())
.catch (err) ->
x.dispose()
assert stateSequence.length == 4
assert stateSequence[0] == 'run'
assert stateSequence[1] == 'stop'
assert stateSequence[2] == 'run'
assert stateSequence[3] == 'exit'
it 'can read the list of threads', ->
gdb.exec.getThreads()
it 'can read a stack backtrace', ->
waitStop gdb, -> gdb.exec.step()
.then -> waitStop gdb, -> gdb.exec.step()
.then -> gdb.exec.getFrames()
.then (frames) ->
assert frames.length == 3
assert frames[0].func == 'func2'
assert frames[1].func == 'func1'
assert frames[2].func == 'main'
it 'can examine local variables', ->
waitStop gdb, -> gdb.exec.step()
.then -> waitStop gdb, -> gdb.exec.step()
.then -> gdb.exec.getLocals(0)
.then (locals) ->
assert locals[0].name == 'b1' and locals[0].value == '2'
assert locals[1].name == 'b2' and locals[1].value == '1'
.then -> gdb.exec.getLocals(1)
.then (locals) ->
assert locals[0].name == 'a' and locals[0].value == '1'
it 'can interrupt a running target', ->
gdb.send_cli 'set spin = 1'
.then -> waitStop gdb, ->
gdb.exec.continue()
gdb.exec.interrupt()
it 'can interrupt target run from cli', ->
gdb.send_cli 'set spin = 1'
.then -> waitStop gdb, ->
gdb.send_cli 'cont'
gdb.exec.interrupt()
describe 'GDB Remote target', ->
gdb = null
beforeEach ->
gdb = new GDB()
gdb.connect()
.then -> testfile(gdb, 'simple.c')
.then -> gdb.send_cli 'target remote | gdbserver - test/bin/simple'
it 'can interrupt a running target', ->
gdb.send_cli 'set spin = 1'
.then ->waitStop gdb, ->
gdb.exec.continue()
gdb.exec.interrupt()
it 'can interrupt target run from cli', ->
gdb.send_cli 'set spin = 1'
.then -> waitStop gdb, ->
gdb.send_cli 'cont'
gdb.exec.interrupt()
describe 'GDB Breakpoint Manager', ->
gdb = null
bkpt = null
before ->
gdb = new GDB()
gdb.connect()
.then -> testfile(gdb, 'simple.c')
it 'can set a breakpoint', ->
bpObserver = sinon.spy()
gdb.breaks.observe bpObserver
gdb.breaks.insert 'func2'
.then (b) ->
bkpt = b
assert bpObserver.calledWith('1', bkpt)
it 'can hit a breakpoint', ->
bpChanged = sinon.spy()
bkpt.onChanged bpChanged
waitStop gdb, -> gdb.exec.continue()
.then (frame) ->
assert frame.func == 'func2'
assert bpChanged.called
assert bkpt.times == '1'
it 'can remove a breakpoint', (done) ->
bpDeleted = sinon.spy()
bkpt.onDeleted bpDeleted
bkpt.remove()
.then ->
assert bpDeleted.called
x = gdb.exec.onExited ->
x.dispose()
done()
gdb.exec.continue()
return
it 'notifies osbervers on cli created breakpoints', ->
bpObserver = sinon.spy()
gdb.breaks.observe bpObserver
gdb.send_cli 'break main'
.then ->
assert bpObserver.called
bkpt = bpObserver.args[0][1]
assert bkpt.func == 'main'
it 'notifies observers of cli deleted breakpoints', (done) ->
bpDeleted = sinon.spy()
bkpt.onDeleted bpDeleted
gdb.send_cli "delete #{bkpt.number}"
.then ->
assert bpDeleted.called
x = gdb.exec.onExited ->
x.dispose()
done()
gdb.exec.continue()
return
describe 'GDB Variable Manager', ->
gdb = null
beforeEach ->
gdb = new GDB()
gdb.connect()
.then -> testfile(gdb, 'struct.c')
.then -> waitStop gdb, -> gdb.exec.start()
it 'can add a variable object', ->
spy = sinon.spy()
gdb.vars.observe spy
gdb.vars.add('astruct')
.then (v) ->
assert(spy.args[0][0] == v)
assert(+v.numchild == 3)
assert(v.exp == 'astruct')
assert(v.nest == 0)
it "can enumerate variable's children", ->
spy = sinon.spy()
parent = null
gdb.vars.observe spy
gdb.vars.add('astruct')
.then (v) ->
parent = v
parent.addChildren()
.then (children) ->
assert parent.children == children
assert children.length == 3
for i in [0..2]
assert spy.args[i+1][0] == children[i]
assert children[i].parent == parent
assert children[0].exp == 'inner1'
assert children[1].exp == 'inner2'
assert children[2].exp == 'c'
it "can remove a variable object", ->
spy = sinon.spy()
gdb.vars.add('astruct')
.then (v) ->
v.onDeleted spy
v.remove()
.then ->
assert spy.called
it "reports changes in variable objects", (done) ->
changed = (v) ->
assert v.exp == 'c'
assert v.value = '20'
done()
gdb.vars.add('astruct')
.then (v) ->
v.addChildren()
.then (children) ->
children[2].onChanged changed.bind(null, children[2])
.then ->
gdb.exec.next()
return
it "can assign a value to a variable", ->
gdb.vars.add('astruct')
.then (v) ->
v.addChildren()
.then (children) ->
children[2].assign "42"
.then (val) ->
assert val == "42"
it "can set a watchpoint on a variable", ->
changed = sinon.spy()
gdb.vars.add('astruct.c')
.then (v) ->
v.onChanged changed
v.setWatch()
.then ->
assert changed.called
wpt = changed.args[0][0].watchpoint
assert wpt.number?
assert wpt.times?
it "can clear a watchpoint on a variable", ->
changed = sinon.spy()
v = null
gdb.vars.add('astruct.c')
.then (view) ->
v = view
v.setWatch()
.then ->
assert v.watchpoint?
v.onChanged changed
v.clearWatch()
.then ->
assert not v.watchpoint?
assert changed.called
it "automatically creates variables on new watchpoints", (done) ->
gdb.vars.observe (v) -> v.onChanged (v) ->
assert v.watchpoint?
done()
gdb.send_cli 'watch astruct.c'
return
it "can evaluate an arbitrary expression", ->
gdb.vars.evalExpression "astruct.c"
.then (val) ->
assert val == '0'
it "removes child when parent is removed", ->
spy = sinon.spy()
parent = null
gdb.vars.add('astruct')
.then (v) ->
parent = v
parent.addChildren()
.then (children) ->
children[0].onDeleted spy
parent.remove()
.then ->
assert spy.called