1
0
mirror of https://github.com/pevans/erc-c.git synced 2024-12-22 14:30:45 +00:00
erc-c/tests/mos6502.dis.c
2018-03-01 21:37:10 -06:00

237 lines
6.4 KiB
C

#include <criterion/criterion.h>
#include <unistd.h>
#include "mos6502.h"
#include "mos6502.dis.h"
#include "mos6502.enums.h"
/*
* BUFSIZ is the normal block-buffer size that a FILE stream would use
* (possibly amongst other things).
*/
static char buf[BUFSIZ];
/*
* This is the file stream we will be using to write our disassembly
* code into.
*/
static FILE *stream = NULL;
static mos6502 *cpu = NULL;
static vm_segment *mem = NULL;
static void
setup()
{
// Ok, so...there's some...trickery going on here. As you might
// guess by the file path being /dev/null.
stream = fopen("/dev/null", "w");
if (stream == NULL) {
perror("Could not open temporary file for mos6502 disassembly tests");
exit(1);
}
// The C standard library allows us to set an arbitrary buffer for a
// file stream. It also allows us to fully buffer the file stream,
// which means nothing is written to file until an fflush() or an
// fclose() is called (or something else I can't think of). So we
// can use the FILE abstraction to write our disassembly results
// into, but use an underlying string buffer that we can easily
// check with Criterion. Uh, unless we blow out the buffer size...
// don't do that :D
setvbuf(stream, buf, _IOFBF, BUFSIZ);
mem = vm_segment_create(MOS6502_MEMSIZE);
cpu = mos6502_create(mem, mem);
}
static void
teardown()
{
fclose(stream);
mos6502_free(cpu);
vm_segment_free(mem);
}
static void
assert_buf(const char *str)
{
// This will set the cursor position in the file back to the start
// of the file stream.
rewind(stream);
// Our actual assertion. The downside to doing it this way is that
// when Criterion flags an assertion failure, it'll highlight _this_
// line in the file, not in the test. It might be worth macroifying
// this code.
cr_assert_str_eq(buf, str);
// We're not sure what previous tests may have run, and where NUL
// characters were set therein, so to be safe we wipe out the full
// contents of the test buffer after every test.
memset(buf, 0, BUFSIZ);
}
TestSuite(mos6502_dis, .init = setup, .fini = teardown);
Test(mos6502_dis, operand)
{
mos6502_dis_operand(cpu, buf, sizeof(buf), 0, ABS, 0x1234);
assert_buf("$1234");
mos6502_dis_operand(cpu, buf, sizeof(buf), 0, ABX, 0x1234);
assert_buf("$1234,X");
mos6502_dis_operand(cpu, buf, sizeof(buf), 0, ABY, 0x1234);
assert_buf("$1234,Y");
mos6502_dis_operand(cpu, buf, sizeof(buf), 0, IMM, 0x12);
assert_buf("#$12");
mos6502_set(cpu, 0x1234, 0x48);
mos6502_set(cpu, 0x1235, 0x34);
// For JMPs and JSRs (and BRKs), this should be a label and not a
// literal value. So we need to test both the literal and
// jump-labeled version.
mos6502_dis_operand(cpu, buf, sizeof(buf), 0, IND, 0x1234);
assert_buf("($1234)");
mos6502_dis_operand(cpu, buf, sizeof(buf), 0, IDX, 0x12);
assert_buf("($12,X)");
mos6502_dis_operand(cpu, buf, sizeof(buf), 0, IDY, 0x34);
assert_buf("($34),Y");
mos6502_dis_operand(cpu, buf, sizeof(buf), 0, ZPG, 0x34);
assert_buf("$34");
mos6502_dis_operand(cpu, buf, sizeof(buf), 0, ZPX, 0x34);
assert_buf("$34,X");
mos6502_dis_operand(cpu, buf, sizeof(buf), 0, ZPY, 0x34);
assert_buf("$34,Y");
// These should both end up printing nothing
mos6502_dis_operand(cpu, buf, sizeof(buf), 0, ACC, 0);
assert_buf("");
mos6502_dis_operand(cpu, buf, sizeof(buf), 0, IMP, 0);
assert_buf("");
// Test a forward jump (operand < 128)
mos6502_dis_operand(cpu, buf, sizeof(buf), 500, REL, 52);
assert_buf("<0228>");
// Test a backward jump (operand >= 128)
mos6502_dis_operand(cpu, buf, sizeof(buf), 500, REL, 152);
assert_buf("<018c>");
}
Test(mos6502_dis, instruction)
{
#define TEST_INST(x) \
mos6502_dis_instruction(buf, sizeof(buf) - 1, x); \
assert_buf(#x)
TEST_INST(ADC);
TEST_INST(AND);
TEST_INST(ASL);
TEST_INST(BCC);
TEST_INST(BCS);
TEST_INST(BEQ);
TEST_INST(BIT);
TEST_INST(BMI);
TEST_INST(BNE);
TEST_INST(BPL);
TEST_INST(BRK);
TEST_INST(BVC);
TEST_INST(BVS);
TEST_INST(CLC);
TEST_INST(CLD);
TEST_INST(CLI);
TEST_INST(CLV);
TEST_INST(CMP);
TEST_INST(CPX);
TEST_INST(CPY);
TEST_INST(DEC);
TEST_INST(DEX);
TEST_INST(DEY);
TEST_INST(EOR);
TEST_INST(INC);
TEST_INST(INX);
TEST_INST(INY);
TEST_INST(JMP);
TEST_INST(JSR);
TEST_INST(LDA);
TEST_INST(LDX);
TEST_INST(LDY);
TEST_INST(LSR);
TEST_INST(NOP);
TEST_INST(ORA);
TEST_INST(PHA);
TEST_INST(PHP);
TEST_INST(PLA);
TEST_INST(PLP);
TEST_INST(ROL);
TEST_INST(ROR);
TEST_INST(RTI);
TEST_INST(RTS);
TEST_INST(SBC);
TEST_INST(SEC);
TEST_INST(SED);
TEST_INST(SEI);
TEST_INST(STA);
TEST_INST(STX);
TEST_INST(STY);
TEST_INST(TAX);
TEST_INST(TAY);
TEST_INST(TSX);
TEST_INST(TXA);
TEST_INST(TXS);
TEST_INST(TYA);
}
Test(mos6502_dis, expected_bytes)
{
#define TEST_BYTES(x, y) \
cr_assert_eq(mos6502_dis_expected_bytes(x), y)
TEST_BYTES(ACC, 0);
TEST_BYTES(ABS, 2);
TEST_BYTES(ABX, 2);
TEST_BYTES(ABY, 2);
TEST_BYTES(IMM, 1);
TEST_BYTES(IMP, 0);
TEST_BYTES(IND, 2);
TEST_BYTES(IDX, 1);
TEST_BYTES(IDY, 1);
TEST_BYTES(REL, 1);
TEST_BYTES(ZPG, 1);
TEST_BYTES(ZPX, 1);
TEST_BYTES(ZPY, 1);
}
Test(mos6502_dis, opcode)
{
int bytes;
mos6502_set(cpu, 0, 0x29); // AND (imm)
mos6502_set(cpu, 1, 0x38);
bytes = mos6502_dis_opcode(cpu, stream, 0);
assert_buf("0000:29 38 AND #$38\n");
cr_assert_eq(bytes, 2);
}
Test(mos6502_dis, scan)
{
mos6502_set(cpu, 0, 0x29); // AND (imm)
mos6502_set(cpu, 1, 0x38);
mos6502_set(cpu, 2, 0x88); // DEY (imp)
mos6502_set(cpu, 3, 0x6C); // JMP (ind)
mos6502_set(cpu, 4, 0x34);
mos6502_set(cpu, 5, 0x12);
mos6502_dis_scan(cpu, stream, 0, 6);
// FIXME: scan does not currently advance the PC byte; _should_ it?
// I'm honestly not sure. There are definitely times (e.g. during
// runtime operation) when you don't want it to, but as a standalone
// disassembler, it feels less useful when PC isn't emulated.
assert_buf("0000:29 38 AND #$38\n"
"0002:88 DEY \n"
"0003:6C 34 12 JMP ($1234)\n");
}