aboutsummaryrefslogtreecommitdiffgithub
diff options
context:
space:
mode:
authorAustin Adams <git@austinjadams.com>2019-01-25 21:46:44 -0600
committerAustin Adams <git@austinjadams.com>2019-01-25 21:46:44 -0600
commit2ab75fbfe4688e2ca2c95241a8b846c06621e2f1 (patch)
tree436dd8af0f413e62814a7c38e42b19578782dc38
parentbd36031b3e1d023022bcf55fa20b71d0df604ee6 (diff)
downloadnovice-2ab75fbfe4688e2ca2c95241a8b846c06621e2f1.tar.gz
novice-2ab75fbfe4688e2ca2c95241a8b846c06621e2f1.tar.xz
Work on cli debugger tests
-rw-r--r--novice/simulator/cli-debugger.test.ts335
-rw-r--r--novice/simulator/cli-debugger.ts25
-rw-r--r--novice/simulator/debugger.test.ts2
3 files changed, 356 insertions, 6 deletions
diff --git a/novice/simulator/cli-debugger.test.ts b/novice/simulator/cli-debugger.test.ts
index 630c814..4f20881 100644
--- a/novice/simulator/cli-debugger.test.ts
+++ b/novice/simulator/cli-debugger.test.ts
@@ -39,7 +39,6 @@ describe('cli debugger', () => {
// @ts-ignore
mockInterface = {
- //close: () => {stdin.finish(); stdout.finish();},
close: jest.fn(),
on: jest.fn(),
question: jest.fn(),
@@ -49,21 +48,48 @@ describe('cli debugger', () => {
afterEach(() => {
// @ts-ignore
readline.createInterface.mockReset();
+ // @ts-ignore
+ mockInterface.close.mockReset();
+ // @ts-ignore
+ mockInterface.on.mockReset();
+ // @ts-ignore
+ mockInterface.question.mockReset();
});
function runCmd(cmd: string): void {
// @ts-ignore
mockInterface.question.mockImplementationOnce((q, cb) => {
+ expect(q).toEqual('(novice) ');
cb(cmd);
});
}
+ function sendInputChar(c: string): void {
+ // @ts-ignore
+ mockInterface.question.mockImplementationOnce((q, cb) => {
+ expect(q).toEqual('');
+ cb(c);
+ });
+ }
+
describe('lc3 debugging', () => {
beforeEach(() => {
dbg = new CliDebugger(getIsa('lc3'), stdin, stdout);
});
it('quits', () => {
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ // @ts-ignore
+ expect(mockInterface.question.mock.calls.length).toEqual(1);
+ // @ts-ignore
+ expect(mockInterface.question.mock.calls[0][0]).toEqual('(novice) ');
+ expect(stdoutActual).toMatch('==> 0x3000');
+ });
+ });
+
+ it('displays help', () => {
runCmd('h');
runCmd('q');
@@ -73,5 +99,312 @@ describe('cli debugger', () => {
expect(stdoutActual).toMatch(/q\[uit\]\s+escape this foul debugger/);
});
});
+
+ it('re-runs last command on empty input', () => {
+ runCmd('h');
+ runCmd('');
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ expect(stdoutActual).toMatch(/novice debugger usage[^]+novice debugger usage/);
+ });
+ });
+
+ it('does not step past halt', () => {
+ dbg.store(0x3000, 0xf025);
+ runCmd('s');
+ runCmd('s');
+ runCmd('s');
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ expect(stdoutActual).toMatch(/==> 0x3000[^]+==> 0x3000[^]+==> 0x3000/);
+ });
+ });
+
+ it('blows up with wrong number of operands', () => {
+ runCmd('s 1');
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ expect(stdoutActual).toMatch('command step expects 0 operands but got 1');
+ });
+ });
+
+ it('catches exceptions thrown by command', () => {
+ runCmd('u');
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ expect(stdoutActual).toMatch('error: already at the beginning of time');
+ });
+ });
+
+ it('handles invalid command', () => {
+ runCmd('bogus');
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ expect(stdoutActual).toMatch('unknown command `bogus\'');
+ });
+ });
+
+ it('closes readline on close()', () => {
+ dbg.close();
+ // @ts-ignore
+ expect(mockInterface.close.mock.calls).toEqual([[]]);
+ });
+
+ it('errors on label breakpoint', () => {
+ runCmd('b asdf');
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ expect(stdoutActual).toMatch('not yet implemented');
+ // Don't print state again after an error
+ expect(stdoutActual).not.toMatch(/==>[^]+==>/);
+ });
+ });
+
+ it('steps work', () => {
+ dbg.store(0x3000, 0x5020); // and r0, r0, 0
+ dbg.store(0x3001, 0x1023); // add r0, r0, 3
+ dbg.store(0x3002, 0x5220); // and r1, r0, 0
+ dbg.store(0x3003, 0x1264); // add r1, r1, 4
+ dbg.store(0x3004, 0xf025); // halt
+
+ for (let i = 0x3000; i <= 0x3004; i++) runCmd('s');
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ expect(stdoutActual).toMatch(/==> 0x3000[^]+==> 0x3001[^]+==> 0x3002[^]+==> 0x3003[^]+==> 0x3004[^]+==> 0x3004/);
+ expect(stdoutActual).toMatch(/r0: 0x0000[^]+r0: 0x0000[^]+r0: 0x0003[^]+r0: 0x0003[^]+r0: 0x0003[^]+r0: 0x0003/);
+ expect(stdoutActual).toMatch(/r1: 0x0000[^]+r1: 0x0000[^]+r1: 0x0000[^]+r1: 0x0000[^]+r1: 0x0004[^]+r1: 0x0004/);
+ });
+ });
+
+ it('breakpoints, continue work', () => {
+ dbg.store(0x3000, 0x5020); // and r0, r0, 0
+ dbg.store(0x3001, 0x1023); // add r0, r0, 3
+ dbg.store(0x3002, 0x5220); // and r1, r0, 0
+ dbg.store(0x3003, 0x1264); // add r1, r1, 4
+ dbg.store(0x3004, 0xf025); // halt
+
+ runCmd('b 0x3002');
+ runCmd('c');
+ runCmd('c');
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ expect(stdoutActual).toMatch('breakpoint set at 0x3002');
+ expect(stdoutActual).toMatch(/==> 0x3000[^]+==> 0x3002[^]+==> 0x3004/);
+ expect(stdoutActual).toMatch(/r0: 0x0000[^]+r0: 0x0003[^]+r0: 0x0003/);
+ expect(stdoutActual).toMatch(/r1: 0x0000[^]+r1: 0x0000[^]+r1: 0x0004/);
+ });
+ });
+
+ it('puts works', () => {
+ dbg.store(0x3000, 0xe002); // lea r0, message
+ dbg.store(0x3001, 0xf022); // puts
+ dbg.store(0x3002, 0xf025); // halt
+ dbg.store(0x3003, 0x0068); // message .stringz "hello dad\n"
+ dbg.store(0x3004, 0x0065);
+ dbg.store(0x3005, 0x006c);
+ dbg.store(0x3006, 0x006c);
+ dbg.store(0x3007, 0x006f);
+ dbg.store(0x3008, 0x0020);
+ dbg.store(0x3009, 0x0064);
+ dbg.store(0x300a, 0x0061);
+ dbg.store(0x300b, 0x0064);
+ dbg.store(0x300c, 0x000a);
+ dbg.store(0x300d, 0x0000);
+
+ runCmd('c');
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ expect(stdoutActual).toMatch('hello dad\n');
+ });
+ });
+
+ it('puts works but puts ↲ after fake linebreak', () => {
+ dbg.store(0x3000, 0xe002); // lea r0, message
+ dbg.store(0x3001, 0xf022); // puts
+ dbg.store(0x3002, 0xf025); // halt
+ dbg.store(0x3003, 0x0064); // message .stringz "dad"
+ dbg.store(0x3004, 0x0061);
+ dbg.store(0x3005, 0x0064);
+ dbg.store(0x3006, 0x0000);
+
+ runCmd('c');
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ expect(stdoutActual).toMatch(/^dad↲$/m);
+ });
+ });
+
+ it('getc works', () => {
+ // outputs stdin to stdout until it hits a character which is not a lowercase
+ // letter a-z
+ dbg.store(0x3000, 0x56e0); // and r3, r3, 0 ; r3 <- 0
+ dbg.store(0x3001, 0xf020); // loop getc ; r0 <- input
+ dbg.store(0x3002, 0x2411); // ld r2, ascii_a
+ dbg.store(0x3003, 0x94bf); // not r2, r2
+ dbg.store(0x3004, 0x14a1); // add r2, r2, 1 ; r2 <- -'a'
+ dbg.store(0x3005, 0x1480); // add r2, r2, r0 ; r2 <- c - 'a'
+ dbg.store(0x3006, 0x0808); // brn done
+ dbg.store(0x3007, 0x240d); // ld r2, ascii_z
+ dbg.store(0x3008, 0x94bf); // not r2, r2
+ dbg.store(0x3009, 0x14a1); // add r2, r2, 1 ; r2 <- -'z'
+ dbg.store(0x300a, 0x1480); // add r2, r2, r0 ; r2 <- c - 'z'
+ dbg.store(0x300b, 0x0203); // brp done
+ dbg.store(0x300c, 0xf021); // out
+ dbg.store(0x300d, 0x1620); // add r3, r0, 0
+ dbg.store(0x300e, 0x0ff2); // br loop
+ dbg.store(0x300f, 0x16e0); // done add r3, r3, 0
+ dbg.store(0x3010, 0x0402); // brz die
+ dbg.store(0x3011, 0x2004); // ld r0, ascii_lf
+ dbg.store(0x3012, 0xf021); // out
+ dbg.store(0x3013, 0xf025); // die halt
+ dbg.store(0x3014, 0x0061); // ascii_a .fill 'a'
+ dbg.store(0x3015, 0x007a); // ascii_z .fill 'z'
+ dbg.store(0x3016, 0x000a); // ascii_lf .fill '\n'
+
+ runCmd('c');
+ 'ilikebigstringsandicannotlie '.split('').forEach(sendInputChar);
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ expect(stdoutActual).toMatch(/^ilikebigstringsandicannotlie$/m);
+ });
+ });
+
+ it('complains if you type more than 1 char for getc', () => {
+ dbg.store(0x3000, 0xf020); // getc
+ dbg.store(0x3001, 0xf025); // halt
+
+ runCmd('c');
+ sendInputChar('whoopsie');
+ sendInputChar('x');
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ expect(stdoutActual).toMatch('please type exactly 1 char');
+ });
+ });
+
+ it('prints memory with range including pc', () => {
+ dbg.store(0x3000, 0xe002); // lea r0, message
+ dbg.store(0x3001, 0xf022); // puts
+ dbg.store(0x3002, 0xf025); // halt
+ dbg.store(0x3003, 0x0068); // message .stringz "hello dad\n"
+ dbg.store(0x3004, 0x0065);
+ dbg.store(0x3005, 0x006c);
+ dbg.store(0x3006, 0x006c);
+ dbg.store(0x3007, 0x006f);
+ dbg.store(0x3008, 0x0020);
+ dbg.store(0x3009, 0x0064);
+ dbg.store(0x300a, 0x0061);
+ dbg.store(0x300b, 0x0064);
+ dbg.store(0x300c, 0x000a);
+ dbg.store(0x300d, 0x0000);
+
+ runCmd('p 0x3000-0x3004');
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ expect(stdoutActual).toMatch(/0x3000[^]+^==> 0x3000: 0xe002 -8190 lea r0, 2$/m);
+ expect(stdoutActual).toMatch(/0x3001[^]+^ 0x3001: 0xf022 -4062 puts$/m);
+ expect(stdoutActual).toMatch(/0x3002[^]+^ 0x3002: 0xf025 -4059 halt$/m);
+ expect(stdoutActual).toMatch(/0x3003[^]+^ 0x3003: 0x0068 104\s*$/m);
+ expect(stdoutActual).toMatch(/0x3004[^]+^ 0x3004: 0x0065 101\s*$/m);
+ });
+ });
+
+ it('prints memory with range not including pc', () => {
+ dbg.store(0x3000, 0xe002); // lea r0, message
+ dbg.store(0x3001, 0xf022); // puts
+ dbg.store(0x3002, 0xf025); // halt
+ dbg.store(0x3003, 0x0068); // message .stringz "hello dad\n"
+ dbg.store(0x3004, 0x0065);
+ dbg.store(0x3005, 0x006c);
+ dbg.store(0x3006, 0x006c);
+ dbg.store(0x3007, 0x006f);
+ dbg.store(0x3008, 0x0020);
+ dbg.store(0x3009, 0x0064);
+ dbg.store(0x300a, 0x0061);
+ dbg.store(0x300b, 0x0064);
+ dbg.store(0x300c, 0x000a);
+ dbg.store(0x300d, 0x0000);
+
+ runCmd('p 0x3001-0x3004');
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ expect(stdoutActual).toMatch(/0x3001[^]+^0x3001: 0xf022 -4062 puts$/m);
+ expect(stdoutActual).toMatch(/0x3002[^]+^0x3002: 0xf025 -4059 halt$/m);
+ expect(stdoutActual).toMatch(/0x3003[^]+^0x3003: 0x0068 104\s*$/m);
+ expect(stdoutActual).toMatch(/0x3004[^]+^0x3004: 0x0065 101\s*$/m);
+ });
+ });
+
+ it('prints memory with decimal range', () => {
+ dbg.store(0x3000, 0xe002); // lea r0, message
+ dbg.store(0x3001, 0xf022); // puts
+ dbg.store(0x3002, 0xf025); // halt
+ dbg.store(0x3003, 0x0068); // message .stringz "hello dad\n"
+ dbg.store(0x3004, 0x0065);
+ dbg.store(0x3005, 0x006c);
+ dbg.store(0x3006, 0x006c);
+ dbg.store(0x3007, 0x006f);
+ dbg.store(0x3008, 0x0020);
+ dbg.store(0x3009, 0x0064);
+ dbg.store(0x300a, 0x0061);
+ dbg.store(0x300b, 0x0064);
+ dbg.store(0x300c, 0x000a);
+ dbg.store(0x300d, 0x0000);
+
+ runCmd('p 12289-0x3002');
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ expect(stdoutActual).toMatch(/0x3001[^]+^0x3001: 0xf022 -4062 puts$/m);
+ expect(stdoutActual).toMatch(/0x3002[^]+^0x3002: 0xf025 -4059 halt$/m);
+ });
+ });
+
+ it('prints single address', () => {
+ dbg.store(0x3000, 0xe002); // lea r0, message
+ dbg.store(0x3001, 0xf022); // puts
+ dbg.store(0x3002, 0xf025); // halt
+ dbg.store(0x3003, 0x0068); // message .stringz "hello dad\n"
+ dbg.store(0x3004, 0x0065);
+ dbg.store(0x3005, 0x006c);
+ dbg.store(0x3006, 0x006c);
+ dbg.store(0x3007, 0x006f);
+ dbg.store(0x3008, 0x0020);
+ dbg.store(0x3009, 0x0064);
+ dbg.store(0x300a, 0x0061);
+ dbg.store(0x300b, 0x0064);
+ dbg.store(0x300c, 0x000a);
+ dbg.store(0x300d, 0x0000);
+
+ runCmd('p 0x3001');
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ expect(stdoutActual).toMatch(/0x3001[^]+^0x3001: 0xf022 -4062 puts$/m);
+ });
+ });
+
+ it('errors when weird operand is passed to print', () => {
+ runCmd('p 0x3000-0x3001-0x3002');
+ runCmd('q');
+
+ return dbg.run().then(() => {
+ expect(stdoutActual).toMatch('error: expected range');
+ });
+ });
});
});
diff --git a/novice/simulator/cli-debugger.ts b/novice/simulator/cli-debugger.ts
index 167e23d..0ff86cd 100644
--- a/novice/simulator/cli-debugger.ts
+++ b/novice/simulator/cli-debugger.ts
@@ -15,6 +15,11 @@ interface Command {
class PromptIO implements IO {
public rl!: readline.Interface;
public stdout!: Writable;
+ public buf: string;
+
+ public constructor() {
+ this.buf = '';
+ }
public async getc(): Promise<number> {
let bad = true;
@@ -36,7 +41,9 @@ class PromptIO implements IO {
}
public putc(c: number): void {
- this.stdout.write(String.fromCharCode(c));
+ const character = String.fromCharCode(c);
+ this.buf += character;
+ this.stdout.write(character);
}
}
@@ -44,6 +51,7 @@ class CliDebugger extends Debugger {
private stdin: Readable;
private stdout: Writable;
private rl: readline.Interface;
+ private promptIo: PromptIO;
private exit: boolean;
private commands: Command[];
@@ -59,8 +67,9 @@ class CliDebugger extends Debugger {
output: this.stdout,
});
this.rl.on('SIGINT', this.onInterrupt.bind(this));
- io.rl = this.rl;
- io.stdout = this.stdout;
+ this.promptIo = io;
+ this.promptIo.rl = this.rl;
+ this.promptIo.stdout = this.stdout;
this.exit = false;
this.commands = [
@@ -136,9 +145,17 @@ class CliDebugger extends Debugger {
`${operands.length}`);
}
- last = [cmd, operands];
+ this.promptIo.buf = '';
+
await cmd.method.bind(this)(operands);
showState = cmd.showState;
+ last = [cmd, operands];
+
+ // To avoid breaking the prompt
+ if (this.promptIo.buf &&
+ this.promptIo.buf[this.promptIo.buf.length - 1] != '\n') {
+ this.stdout.write('↲\n');
+ }
} catch (err) {
this.stdout.write(`error: ${err.message}\n`);
showState = false;
diff --git a/novice/simulator/debugger.test.ts b/novice/simulator/debugger.test.ts
index d729cbc..2b5d992 100644
--- a/novice/simulator/debugger.test.ts
+++ b/novice/simulator/debugger.test.ts
@@ -3,7 +3,7 @@ import { getIsa } from '../isa';
import { FakeIO } from './helpers.test';
// for programs with ONE section, generate store() calls with
-// storify() { ./novice.js asm "$1"; xxd -p "${1%.asm}.obj" | awk '{ print substr($1,9) }' | sed 's/\(.\{4\}\)/\1\n/g' | head -n -1 | awk '{ printf "dbg.store(0x%04x, 0x%s);\n", 0x3000 + NR - 1, $1 }'; }; storify meme.asm
+// storify() { ./novice.js asm "$1"; xxd -p "${1%.asm}.obj" | tr -d '\n' | awk '{ print substr($1,9) }' | sed 's/\(.\{4\}\)/\1\n/g' | head -n -1 | awk '{ printf "dbg.store(0x%04x, 0x%s);\n", 0x3000 + NR - 1, $1 }'; }; storify meme.asm
describe('debugger', () => {
let io: FakeIO;