mirror of
https://github.com/pevans/erc-c.git
synced 2024-12-01 00:49:46 +00:00
ac5d532a7f
This also fixes tests for disassembly so that they account for changed output. Finally this also exits if we have to perror in the setup function.
237 lines
6.4 KiB
C
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"
|
|
"0000:88 DEY #$38\n"
|
|
"0000:6C 34 12 JMP ($1234)\n");
|
|
}
|