asmimproved-dbgmits
Version:
Provides the ability to control GDB and LLDB programmatically via GDB/MI.
424 lines (375 loc) • 18.9 kB
text/typescript
// Copyright (c) 2015 Vadim Macagon
// MIT License, see LICENSE file for full terms.
require('source-map-support').install();
import * as chai from 'chai';
import chaiAsPromised = require('chai-as-promised');
import * as bunyan from 'bunyan';
import * as dbgmits from '../lib/index';
import {
beforeEachTestWithLogger, logSuite as log, startDebugSession, runToFunc
} from './test_utils';
chai.use(chaiAsPromised);
// aliases
var expect = chai.expect;
import DebugSession = dbgmits.DebugSession;
// the directory in which Gruntfile.js resides is also Mocha's working directory,
// so any relative paths will be relative to that directory
var localTargetExe: string = './build/Debug/stack_tests_target';
log(describe("Debug Session", () => {
describe("Stack Inspection", () => {
var debugSession: DebugSession;
beforeEachTestWithLogger((logger: bunyan.Logger) => {
debugSession = startDebugSession(logger);
return debugSession.setExecutableFile(localTargetExe);
});
afterEach(() => {
return debugSession.end();
});
describe("#getStackFrame", () => {
it("gets the current stack frame", () => {
return runToFunc(debugSession, 'funcAtFrameLevel0', () => {
return debugSession.getStackFrame()
.then((info: dbgmits.IStackFrameInfo) => {
expect(info).to.have.property('func');
expect(info.func).match(/^funcAtFrameLevel0/);
});
});
});
// FIXME: re-enable on LLDB when it's fixed to handle --thread and --frame arguments
it("gets an outer stack frame @skipOnLLDB", () => {
return runToFunc(debugSession, 'funcAtFrameLevel0', () => {
return debugSession.getStackFrame({ threadId: 1, frameLevel: 1 })
.then((info: dbgmits.IStackFrameInfo) => {
expect(info).to.have.property('func');
expect(info.func).match(/^funcAtFrameLevel1/);
});
});
});
}); // #getStackFrame
it("#getStackDepth", () => {
var initialStackDepth = -1;
// GDB and LLDB report stack depth a bit differently, LLDB adds a couple of frames from
// libc to the count, but GDB does not. So instead of checking the absolute stack depth
// we check the relative stack depth (starting from main()).
var onBreakpointCheckStackDepth = new Promise<void>((resolve, reject) => {
debugSession.on(dbgmits.EVENT_BREAKPOINT_HIT,
(breakNotify: dbgmits.IBreakpointHitEvent) => {
switch (breakNotify.breakpointId) {
case 1: // breakpoint in main()
debugSession.getStackDepth()
.then((stackDepth: number) => { initialStackDepth = stackDepth; })
.then(() => { return debugSession.addBreakpoint('funcAtFrameLevel0'); })
.then(() => { return debugSession.resumeInferior(); })
.catch(reject);
break;
case 2: // breakpoint in funcAtFrameLevel0()
debugSession.getStackDepth()
.then((stackDepth: number) => {
// the stack should be 2 levels deep counting from main():
// funcAtFrameLevel1()->funcAtFrameLevel0()
expect(stackDepth - initialStackDepth).to.equal(2);
})
.then(resolve)
.catch(reject);
break;
}
}
);
});
return debugSession.addBreakpoint('main')
.then(() => {
return Promise.all([
onBreakpointCheckStackDepth,
debugSession.startInferior()
]);
});
}); // #getStackDepth
describe("#getStackFrames", () => {
it("gets a list of stack frames", () => {
return runToFunc(debugSession, 'funcAtFrameLevel0', () => {
let expectedStackDepth = -1;
return debugSession.getStackDepth()
.then((stackDepth: number) => { expectedStackDepth = stackDepth; })
.then(() => { return debugSession.getStackFrames(); })
.then((frames: dbgmits.IStackFrameInfo[]) => {
expect(frames.length).to.equal(expectedStackDepth);
for (let i = 0; i < frames.length; ++i) {
expect(frames[i].level).to.equal(i);
}
expect(frames[0].func).match(/^funcAtFrameLevel0/);
expect(frames[1].func).match(/^funcAtFrameLevel1/);
expect(frames[2].func).match(/^main/);
});
});
});
it("gets a list of stack frames within a certain level range", () => {
return runToFunc(debugSession, 'funcAtFrameLevel0', () => {
return debugSession.getStackFrames({ lowFrame: 0, highFrame: 1 })
.then((frames: dbgmits.IStackFrameInfo[]) => {
expect(frames.length).to.equal(2);
for (let i = 0; i < frames.length; ++i) {
expect(frames[i].level).to.equal(i);
}
expect(frames[0].func).match(/^funcAtFrameLevel0/);
expect(frames[1].func).match(/^funcAtFrameLevel1/);
});
});
});
it("gets a stack frame for a specific level", () => {
return runToFunc(debugSession, 'funcAtFrameLevel0', () => {
return debugSession.getStackFrames({ highFrame: 1 })
.then((frames: dbgmits.IStackFrameInfo[]) => {
expect(frames.length).to.equal(1);
expect(frames[0].level).to.equal(1);
expect(frames[0].func).match(/^funcAtFrameLevel1/);
});
});
});
}); // #getStackFrames
describe("#getStackFrameArgs", () => {
it("gets frame arguments for a number of frames", () => {
return runToFunc(debugSession, 'funcWithNoArgs', () => {
// FIXME: should switch to simple detail level so we get type information,
// but the LLDB MI driver needs to be fixed to support that detail level
// first
return debugSession.getStackFrameArgs(dbgmits.VariableDetailLevel.All, { lowFrame: 0, highFrame: 3 })
.then((frames: dbgmits.IStackFrameArgsInfo[]) => {
expect(frames).to.have.property('length', 4);
expect(frames[0]).to.have.property('level', 0);
expect(frames[0]).to.have.property('args').that.has.property('length', 0);
expect(frames[1]).to.have.property('level', 1);
expect(frames[1]).to.have.property('args').that.has.property('length', 1);
expect(frames[1].args[0]).to.have.property('name', 'a');
expect(frames[1].args[0]).to.have.property('value', '5');
expect(frames[2].level).to.equal(2);
expect(frames[2].args.length).to.equal(2);
expect(frames[2].args[0].name).to.equal('b');
expect(frames[2].args[0].value).to.equal('7');
expect(frames[2].args[1].name).to.equal('c');
expect(frames[2].args[1]).to.have.property('value');
expect(frames[3].level).to.equal(3);
expect(frames[3].args.length).to.equal(3);
expect(frames[3].args[0].name).to.equal('d');
expect(frames[3].args[0].value).to.equal('300');
expect(frames[3].args[1].name).to.equal('e');
expect(frames[3].args[1]).to.have.property('value');
expect(frames[3].args[2].name).to.equal('f');
expect(frames[3].args[2]).to.have.property('value');
});
});
});
}); // #getStackFrameArgs
describe("#getStackFrameVariables", () => {
it("gets a single simple local variable (name only) for a frame", () => {
return runToFunc(debugSession, 'funcWithOneSimpleLocalVariable_Inner', () => {
// get the variables for the previous frame
return debugSession.getStackFrameVariables(
dbgmits.VariableDetailLevel.None, { threadId: 1, frameLevel: 1 }
)
.then((variables: dbgmits.IStackFrameVariablesInfo) => {
const locals = variables.locals;
expect(locals.length).to.equal(1);
expect(locals[0]).to.have.property('name', 'a');
expect(locals[0]).to.have.property('value').that.is.undefined;
expect(locals[0]).to.have.property('type').that.is.undefined;
});
});
});
// FIXME: re-enable on LLDB when it correctly supports 'simple' mode (as in outputs types)
it("gets a single simple local variable (name, value, and type) for a frame @skipOnLLDB", () => {
return runToFunc(debugSession, 'funcWithOneSimpleLocalVariable_Inner', () => {
// get the locals for the previous frame
return debugSession.getStackFrameVariables(
dbgmits.VariableDetailLevel.Simple, { threadId: 1, frameLevel: 1 }
)
.then((variables: dbgmits.IStackFrameVariablesInfo) => {
const locals = variables.locals;
expect(locals.length).to.equal(1);
expect(locals[0]).to.have.property('name', 'a');
expect(locals[0]).to.have.property('value', '5');
expect(locals[0]).to.have.property('type', 'int');
});
});
});
it("gets a single simple local variable (name and value) for a frame", () => {
return runToFunc(debugSession, 'funcWithOneSimpleLocalVariable_Inner', () => {
// get the locals for the previous frame
return debugSession.getStackFrameVariables(
dbgmits.VariableDetailLevel.All, { threadId: 1, frameLevel: 1 }
)
.then((variables: dbgmits.IStackFrameVariablesInfo) => {
const locals = variables.locals;
expect(locals.length).to.equal(1);
expect(locals[0]).to.have.property('name', 'a');
expect(locals[0]).to.have.property('value', '5');
expect(locals[0]).to.have.property('type').that.is.undefined;
});
});
});
// FIXME: re-enable on LLDB when it correctly supports 'simple' mode (as in outputs types)
it("gets a single complex local variable (name and type) for the current frame @skipOnLLDB", () => {
return runToFunc(debugSession, 'funcWithOneComplexLocalVariable_Inner', () => {
// get the locals for the current frame
return debugSession.getStackFrameVariables(
dbgmits.VariableDetailLevel.Simple, { threadId: 1, frameLevel: 1 }
)
.then((variables: dbgmits.IStackFrameVariablesInfo) => {
const locals = variables.locals;
expect(locals.length).to.equal(1);
expect(locals[0]).to.have.property('name', 'b');
expect(locals[0]).to.have.property('value').that.is.undefined;
expect(locals[0]).to.have.property('type', 'int [3]');
});
});
});
// FIXME: re-enable on LLDB when it correctly supports 'simple' mode (as in outputs types)
it("gets two local variables (name, value, and type) for a frame @skipOnLLDB", () => {
return runToFunc(debugSession, 'funcWithTwoLocalVariables_Inner', () => {
// get the locals for the current frame
return debugSession.getStackFrameVariables(
dbgmits.VariableDetailLevel.Simple, { threadId: 1, frameLevel: 1 }
)
.then((variables: dbgmits.IStackFrameVariablesInfo) => {
const locals = variables.locals;
expect(locals.length).to.equal(2);
expect(locals[0]).to.have.property('name', 'c');
expect(locals[0]).to.have.property('value', 'true');
expect(locals[0]).to.have.property('type', 'bool');
expect(locals[1]).to.have.property('name', 'd');
expect(locals[1]).to.have.property('value').that.is.undefined;
expect(locals[1]).to.have.property('type', 'const char *[3]');
});
});
});
it("gets two local variables (name and value) for a frame", () => {
return runToFunc(debugSession, 'funcWithTwoLocalVariables_Inner', () => {
// get the locals for the current frame
return debugSession.getStackFrameVariables(
dbgmits.VariableDetailLevel.All, { threadId: 1, frameLevel: 1 }
)
.then((variables: dbgmits.IStackFrameVariablesInfo) => {
const locals = variables.locals;
expect(locals.length).to.equal(2);
expect(locals[0]).to.have.property('name', 'c');
expect(locals[0]).to.have.property('value', 'true');
expect(locals[0]).to.have.property('type').that.is.undefined;
expect(locals[1]).to.have.property('name', 'd');
expect(locals[1]).to.have.property('value');
expect(locals[1]).to.have.property('type').that.is.undefined;
});
});
});
// FIXME: re-enable on LLDB when it correctly supports 'simple' mode (as in outputs types)
it("gets three local variables (name, value, and type) for a frame @skipOnLLDB", () => {
return runToFunc(debugSession, 'funcWithThreeLocalVariables_Inner', () => {
// get the locals for the current frame
return debugSession.getStackFrameVariables(
dbgmits.VariableDetailLevel.Simple, { threadId: 1, frameLevel: 1 }
)
.then((variables: dbgmits.IStackFrameVariablesInfo) => {
const locals = variables.locals;
expect(locals.length).to.equal(3);
expect(locals[0]).to.have.property('name', 'e');
expect(locals[0]).to.have.property('value');
expect(locals[0]).to.have.property('type', 'Point');
expect(locals[1]).to.have.property('name', 'f');
expect(locals[1]).to.have.property('value', '9.5');
expect(locals[1]).to.have.property('type', 'float');
expect(locals[2]).to.have.property('name', 'g');
expect(locals[2]).to.have.property('value', '300');
expect(locals[2]).to.have.property('type', 'long');
});
});
});
it("gets three local variables (name and value) for a frame", () => {
return runToFunc(debugSession, 'funcWithThreeLocalVariables_Inner', () => {
// get the locals for the current frame
return debugSession.getStackFrameVariables(
dbgmits.VariableDetailLevel.All, { threadId: 1, frameLevel: 1 }
)
.then((variables: dbgmits.IStackFrameVariablesInfo) => {
const locals = variables.locals;
expect(locals.length).to.equal(3);
expect(locals[0]).to.have.property('name', 'e');
expect(locals[0]).to.have.property('value');
expect(locals[0]).to.have.property('type').that.is.undefined;
expect(locals[1]).to.have.property('name', 'f');
expect(locals[1]).to.have.property('value', '9.5');
expect(locals[1]).to.have.property('type').that.is.undefined;
expect(locals[2]).to.have.property('name', 'g');
expect(locals[2]).to.have.property('value', '300');
expect(locals[2]).to.have.property('type').that.is.undefined;
});
});
});
it("gets no frame arguments for a function with no arguments", () => {
return runToFunc(debugSession, 'funcWithNoArgs', () => {
return debugSession.getStackFrameVariables(dbgmits.VariableDetailLevel.None)
.then((variables: dbgmits.IStackFrameVariablesInfo) => {
const args = variables.args;
expect(args.length).to.equal(0);
});
});
});
// FIXME: re-enable on LLDB when it correctly supports 'simple' mode (as in outputs types)
it("gets one simple function argument (name, value, and type) @skipOnLLDB", () => {
return runToFunc(debugSession, 'funcWithOneSimpleArg', () => {
return debugSession.getStackFrameVariables(dbgmits.VariableDetailLevel.Simple)
.then((variables: dbgmits.IStackFrameVariablesInfo) => {
const args = variables.args;
expect(args.length).to.equal(1);
expect(args[0]).to.have.property('name', 'a');
expect(args[0]).to.have.property('value', '5');
expect(args[0]).to.have.property('type', 'int');
});
});
});
it("gets one simple function argument (name and value)", () => {
return runToFunc(debugSession, 'funcWithOneSimpleArg', () => {
return debugSession.getStackFrameVariables(dbgmits.VariableDetailLevel.All)
.then((variables: dbgmits.IStackFrameVariablesInfo) => {
const args = variables.args;
expect(args.length).to.equal(1);
expect(args[0]).to.have.property('name', 'a');
expect(args[0]).to.have.property('value', '5');
expect(args[0]).to.have.property('type').that.is.undefined;
});
});
});
// FIXME: re-enable on LLDB when it correctly supports 'simple' mode (as in outputs types)
it("gets two function arguments (name, value, and type) @skipOnLLDB", () => {
return runToFunc(debugSession, 'funcWithTwoArgs', () => {
return debugSession.getStackFrameVariables(dbgmits.VariableDetailLevel.Simple)
.then((variables: dbgmits.IStackFrameVariablesInfo) => {
const args = variables.args;
expect(args.length).to.equal(2);
expect(args[0]).to.have.property('name', 'b');
expect(args[0]).to.have.property('value', '7');
expect(args[0]).to.have.property('type', 'float');
expect(args[1]).to.have.property('name', 'c');
expect(args[1]).to.have.property('value').that.is.undefined;
expect(args[1]).to.have.property('type', 'Point');
});
});
});
// FIXME: re-enable on LLDB when it correctly supports 'simple' mode (as in outputs types)
it("gets three function arguments (name, value, and type) @skipOnLLDB", () => {
return runToFunc(debugSession, 'funcWithThreeArgs', () => {
return debugSession.getStackFrameVariables(dbgmits.VariableDetailLevel.Simple)
.then((variables: dbgmits.IStackFrameVariablesInfo) => {
const args = variables.args;
expect(args.length).to.equal(3);
expect(args[0]).to.have.property('name', 'd');
expect(args[0]).to.have.property('value', '300');
expect(args[0]).to.have.property('type', 'long');
expect(args[1]).to.have.property('name', 'e');
expect(args[1]).to.have.property('value');
expect(args[1]).to.have.property('type', 'const char *');
expect(args[2]).to.have.property('name', 'f');
expect(args[2]).to.have.property('type', 'int *');
});
});
});
}); // #getStackFrameVariables
});
}));