Files
emuz80go/test_helpers.go

118 lines
2.9 KiB
Go

package z80
import (
"fmt"
"strings"
"testing"
"time"
)
// mockMemory is a simple 64K RAM that satisfies Memory interface.
type mockMemory struct {
data [65536]byte
}
func (m *mockMemory) ReadByte(address uint16) byte { return m.data[address] }
func (m *mockMemory) WriteByte(address uint16, value byte) { m.data[address] = value }
func (m *mockMemory) ReadWord(address uint16) uint16 {
lo := m.data[address]
hi := m.data[address+1]
return (uint16(hi) << 8) | uint16(lo)
}
func (m *mockMemory) WriteWord(address uint16, value uint16) {
m.data[address] = byte(value)
m.data[address+1] = byte(value >> 8)
}
// mockIO is a trivial port device.
type mockIO struct {
lastOut map[uint16]byte
inVals map[uint16]byte
interrupt bool
}
func newMockIO() *mockIO {
return &mockIO{lastOut: make(map[uint16]byte), inVals: make(map[uint16]byte), interrupt: false}
}
func (io *mockIO) ReadPort(port uint16) byte { return io.inVals[port] }
func (io *mockIO) WritePort(port uint16, value byte) { io.lastOut[port] = value }
func (io *mockIO) CheckInterrupt() bool { return io.interrupt }
// testCPU creates a CPU with empty RAM/IO and PC=0, SP=0xFFFF.
func testCPU() (*CPU, *mockMemory, *mockIO) {
mem := &mockMemory{}
io := newMockIO()
cpu := New(mem, io)
cpu.SP = 0xFFFF
cpu.PC = 0x0000
return cpu, mem, io
}
// loadProgram writes bytes at address and sets PC to that address.
func loadProgram(cpu *CPU, mem *mockMemory, addr uint16, bytes ...byte) {
for i, b := range bytes {
mem.WriteByte(addr+uint16(i), b)
}
cpu.PC = addr
}
// mustStep runs one instruction and logs an error if cycles <= 0.
// Returns cycles consumed.
func mustStep(t *testing.T, cpu *CPU) int {
c := cpu.ExecuteOneInstruction()
if c <= 0 {
t.Errorf("ExecuteOneInstruction returned invalid cycles: %d", c)
}
return c
}
// assert helper shortcuts (non-fatal).
func assertEq[T comparable](t *testing.T, got, want T, msg string) {
t.Helper()
if got != want {
t.Errorf("%s: got %v, want %v", msg, got, want)
}
}
func assertFlag(t *testing.T, cpu *CPU, flag byte, want bool, msg string) {
t.Helper()
if cpu.GetFlag(flag) != want {
t.Errorf("%s: flag 0x%02X got %v, want %v (F=%02X)", msg, flag, cpu.GetFlag(flag), want, cpu.F)
}
}
// hexdump prints a compact hex + ascii line dump suitable for test logs.
func hexdump(p []byte, width int) string {
if width <= 0 {
width = 16
}
var b strings.Builder
for i := 0; i < len(p); i += width {
end := i + width
if end > len(p) {
end = len(p)
}
b.WriteString(fmt.Sprintf("%08X ", i))
for j := i; j < end; j++ {
b.WriteString(fmt.Sprintf("%02x ", p[j]))
}
for j := end; j < i+width; j++ {
b.WriteString(" ")
}
b.WriteString(" |")
for j := i; j < end; j++ {
c := p[j]
if c < 32 || c > 126 {
c = '.'
}
b.WriteByte(c)
}
b.WriteString("|\n")
}
return b.String()
}
// small helper to timestamp messages consistently
func ts() string { return time.Now().Format("15:04:05.000") }