first public release
This commit is contained in:
117
test_helpers.go
Normal file
117
test_helpers.go
Normal file
@@ -0,0 +1,117 @@
|
||||
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") }
|
||||
Reference in New Issue
Block a user