package chip8 import ( "fmt" "testing" ) func slicesEqual(x, y []byte) bool { if len(x) != len(y) { return false } for i, xi := range x { if xi != y[i] { return false } } return true } // This test is kinda shit, there's so much stuff we ain't testing // Maybe fix func TestCreateCPU(t *testing.T) { prog := []byte{1, 2, 3, 4} newCPU := NewCHIP8(prog) if !slicesEqual(newCPU.memory[0x200:0x204], prog) { t.Errorf("CPU not initalized properly") } } //0x00E0 func TestClearDisplay(t *testing.T) { cpu := Chip8{opcode: 0x00E0} for i := range cpu.graphics { cpu.graphics[i] = byte(i % 255) } cpu.zeroIndexOpcodes() graphicsArray := cpu.GetGraphicsBuffer() graphicsSlice := graphicsArray[:] emptySlice := make([]byte, len(cpu.graphics)) if !slicesEqual(graphicsSlice, emptySlice) { t.Errorf("Graphics buffer not cleared properly") } } //0x00EE func TestLeaveFunction(t *testing.T) { cpu := Chip8{pc: 50, opcode: 0x00EE} cpu.stack = append(cpu.stack, 1, 2, 3, 4, 5) cpu.zeroIndexOpcodes() if cpu.pc != 4 && len(cpu.stack) != 4 { t.Errorf("TestLeaveFunction not in expected state") } } //0x1NNN func TestGoTo(t *testing.T) { cpu := Chip8{opcode: 0x3420} cpu.goTo() if cpu.pc != 0x420 { t.Errorf("Test GoTo not working as expected") } } //0x2NNN func TestCallSubroutine(t *testing.T) { cpu := Chip8{opcode: 0x3420, pc: 43} cpu.callSubroutine() if (cpu.pc != 420) && (cpu.stack[0] != 43) && (len(cpu.stack) != 1) { t.Errorf("CallSubroutine not working as expected") } } //0x3XNN func TestSkipIfRegisterEqual(t *testing.T) { var tests = []struct { register, regValue byte compValue, want uint16 }{ {0, 2, 4, 0}, {0, 2, 2, 2}, {15, 2, 2, 2}, {15, 1, 12, 0}, } for _, tt := range tests { testname := fmt.Sprintf("Reg:%d,RegVal:%d,Comp:%d", tt.register, tt.regValue, tt.compValue) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: tt.compValue + uint16(tt.register)<<8} cpu.registers[tt.register] = tt.regValue cpu.skipIfRegisterEqual() if tt.want != cpu.pc { t.Errorf("PC is %d, Wanted %d", cpu.pc, tt.want) } }) } } //0x4XNN func TestSkipIfRegisterNotEqual(t *testing.T) { var tests = []struct { register, regValue byte compValue, want uint16 }{ {0, 2, 4, 2}, {0, 2, 2, 0}, {15, 2, 2, 0}, {15, 1, 12, 2}, } for _, tt := range tests { testname := fmt.Sprintf("Reg:%d,RegVal:%d,Comp:%d", tt.register, tt.regValue, tt.compValue) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: tt.compValue + uint16(tt.register)<<8} cpu.registers[tt.register] = tt.regValue cpu.skipIfRegisterNotEqual() if tt.want != cpu.pc { t.Errorf("PC is %d, Wanted %d", cpu.pc, tt.want) } }) } } //0x5XY0 func TestSkipIfRegistersEqual(t *testing.T) { var tests = []struct { reg1, reg2, val1, val2 byte }{ {0, 2, 4, 2}, {0, 2, 2, 2}, {15, 1, 2, 0}, {15, 4, 12, 12}, } for _, tt := range tests { testname := fmt.Sprintf("Reg1:%d,Reg2:%d,val1:%d,val2:%d", tt.reg1, tt.reg2, tt.val1, tt.val2) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: uint16(tt.reg1)<<8 + uint16(tt.reg2)<<4} cpu.registers[tt.reg1] = tt.val1 cpu.registers[tt.reg2] = tt.val2 cpu.skipIfRegistersEqual() if (2 != cpu.pc) && (tt.val1 == tt.val2) { t.Errorf("PC is %d, Wanted %d", cpu.pc, 2) } else if (0 != cpu.pc) && (tt.val1 != tt.val2) { t.Errorf("PC is %d, Wanted %d", cpu.pc, 2) } }) } } //0x6XNN func TestSetRegisterTo(t *testing.T) { cpu := Chip8{opcode: 0x0824} cpu.setRegisterTo() if cpu.registers[8] != 0x24 { t.Errorf("Register 8 is %d wanted %d", cpu.registers[8], 0x24) } } //0x7XNN func TestRegisterPlusEqualsNN(t *testing.T) { var tests = []struct { register, initalVal, toAdd, expected byte }{ {0, 2, 2, 4}, {0, 254, 3, 1}, {15, 2, 0, 2}, {15, 250, 6, 0}, } for _, tt := range tests { testname := fmt.Sprintf("Register:%d,inital:%d,toAdd:%d,expected:%d", tt.register, tt.initalVal, tt.toAdd, tt.expected) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: uint16(tt.register)<<8 + uint16(tt.toAdd)} cpu.registers[tt.register] = tt.initalVal cpu.registerPlusEqualsNN() if cpu.registers[tt.register] != tt.expected { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.register], tt.expected) } }) } } // 0x8000 func TestLoadVxVy(t *testing.T) { var tests = []struct { regX, regY, xVal, yVal byte }{ {0, 2, 2, 4}, {0, 15, 3, 1}, {15, 2, 0, 2}, {15, 3, 6, 0}, } for _, tt := range tests { testname := fmt.Sprintf("RegisterX:%d,RegisterY:%d,Xval:%d,Yval:%d", tt.regX, tt.regY, tt.xVal, tt.yVal) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: 0x8000 | uint16(tt.regX)<<8 | uint16(tt.regY)<<4} cpu.registers[tt.regX] = tt.xVal cpu.registers[tt.regY] = tt.yVal cpu.bitOpsAndMath() if cpu.registers[tt.regX] != tt.yVal { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regX], tt.yVal) } }) } } // 0x8001 func TestOrVxVy(t *testing.T) { var tests = []struct { regX, regY, xVal, yVal, result byte }{ {0, 2, 0xBB, 0xCC, 0xFF}, {0, 2, 0x00, 0x00, 0x00}, {0, 2, 0x0F, 0xF0, 0xFF}, } for _, tt := range tests { testname := fmt.Sprintf("RegisterX:%d,RegisterY:%d,Xval:%d,Yval:%d,expected:%d", tt.regX, tt.regY, tt.xVal, tt.yVal, tt.result) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: 0x8001 | uint16(tt.regX)<<8 | uint16(tt.regY)<<4} cpu.registers[tt.regX] = tt.xVal cpu.registers[tt.regY] = tt.yVal cpu.bitOpsAndMath() if cpu.registers[tt.regX] != tt.result { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regX], tt.result) } if cpu.registers[tt.regY] != tt.yVal { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regY], tt.yVal) } }) } } //0x8002 func TestAndVxVy(t *testing.T) { var tests = []struct { regX, regY, xVal, yVal, result byte }{ {0, 2, 0xCC, 0xDD, 0xCC}, {0, 2, 0x00, 0x00, 0x00}, {0, 2, 0x0F, 0xF0, 0x00}, {0, 2, 0x0F, 0xF1, 0x01}, } for _, tt := range tests { testname := fmt.Sprintf("RegisterX:%d,RegisterY:%d,Xval:%d,Yval:%d,expected:%d", tt.regX, tt.regY, tt.xVal, tt.yVal, tt.result) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: 0x8002 | uint16(tt.regX)<<8 | uint16(tt.regY)<<4} cpu.registers[tt.regX] = tt.xVal cpu.registers[tt.regY] = tt.yVal cpu.bitOpsAndMath() if cpu.registers[tt.regX] != tt.result { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regX], tt.result) } if cpu.registers[tt.regY] != tt.yVal { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regY], tt.yVal) } }) } } //0x8003 func TestXORVxVy(t *testing.T) { var tests = []struct { regX, regY, xVal, yVal, result byte }{ {0, 2, 0xDD, 0xEE, 0x33}, {0, 2, 0x00, 0x00, 0x00}, {0, 4, 0x0F, 0xF0, 0xFF}, {0, 2, 0xFF, 0xFF, 0x00}, } for _, tt := range tests { testname := fmt.Sprintf("RegisterX:%d,RegisterY:%d,Xval:%d,Yval:%d,expected:%d", tt.regX, tt.regY, tt.xVal, tt.yVal, tt.result) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: 0x8003 | uint16(tt.regX)<<8 | uint16(tt.regY)<<4} cpu.registers[tt.regX] = tt.xVal cpu.registers[tt.regY] = tt.yVal cpu.bitOpsAndMath() if cpu.registers[tt.regX] != tt.result { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regX], tt.result) } if cpu.registers[tt.regY] != tt.yVal { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regY], tt.yVal) } }) } } //0x8004 func TestAddVxVyCarry(t *testing.T) { var tests = []struct { regX, regY, xVal, yVal, result, carry byte }{ {0, 2, 0x44, 0xAA, 0xEE, 0}, {0, 2, 0xAA, 0xAA, 0x54, 1}, } for _, tt := range tests { testname := fmt.Sprintf("RegisterX:%d,RegisterY:%d,Xval:%d,Yval:%d,expected:%d,carry:%d", tt.regX, tt.regY, tt.xVal, tt.yVal, tt.result, tt.carry) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: 0x8004 | uint16(tt.regX)<<8 | uint16(tt.regY)<<4} cpu.registers[tt.regX] = tt.xVal cpu.registers[tt.regY] = tt.yVal cpu.bitOpsAndMath() if cpu.registers[tt.regX] != tt.result { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regX], tt.result) } if cpu.registers[tt.regY] != tt.yVal { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regY], tt.yVal) } if cpu.registers[0x0F] != tt.carry { t.Errorf("Carry register is %d, wanted %d", cpu.registers[0x0F], tt.carry) } }) } } //0x8005 func TestSubVxVyCarry(t *testing.T) { var tests = []struct { regX, regY, xVal, yVal, result, carry byte }{ {0, 2, 0xAA, 0x22, 0x88, 1}, {0, 2, 0x22, 0xDD, 0x45, 0}, {0, 2, 0x22, 0x22, 0x00, 1}, } for _, tt := range tests { testname := fmt.Sprintf("RegisterX:%d,RegisterY:%d,Xval:%d,Yval:%d,expected:%d,carry:%d", tt.regX, tt.regY, tt.xVal, tt.yVal, tt.result, tt.carry) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: 0x8005 | uint16(tt.regX)<<8 | uint16(tt.regY)<<4} cpu.registers[tt.regX] = tt.xVal cpu.registers[tt.regY] = tt.yVal cpu.bitOpsAndMath() if cpu.registers[tt.regX] != tt.result { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regX], tt.result) } if cpu.registers[tt.regY] != tt.yVal { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regY], tt.yVal) } if cpu.registers[0x0F] != tt.carry { t.Errorf("Carry register is %d, wanted %d", cpu.registers[0x0F], tt.carry) } }) } } //0x8006 func TestSHRVxVyCarry(t *testing.T) { var tests = []struct { regX, regY, xVal, yVal, expX, expY, regF byte }{ {0, 2, 0x11, 0x44, 0x22, 0x44, 0}, {0, 2, 0x11, 0x45, 0x22, 0x45, 1}, } for _, tt := range tests { testname := fmt.Sprintf("RegisterX:%d,RegisterY:%d,Xval:%d,Yval:%d,expX:%d,expY:%d", tt.regX, tt.regY, tt.xVal, tt.yVal, tt.expX, tt.expY) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: 0x8006 | uint16(tt.regX)<<8 | uint16(tt.regY)<<4} cpu.registers[tt.regX] = tt.xVal cpu.registers[tt.regY] = tt.yVal cpu.bitOpsAndMath() if cpu.registers[tt.regX] != tt.expX { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regX], tt.expX) } if cpu.registers[tt.regY] != tt.yVal { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regY], tt.yVal) } if cpu.registers[0x0F] != tt.regF { t.Errorf("Carry register is %d, wanted %d", cpu.registers[0x0F], tt.regF) } }) } } //0x8007 func TestSUBNVxVyCarry(t *testing.T) { var tests = []struct { regX, regY, xVal, yVal, result, carry byte }{ {0, 2, 0x22, 0xAA, 0x88, 1}, {0, 2, 0xDD, 0x22, 0x45, 0}, {0, 2, 0x22, 0x22, 0x00, 1}, } for _, tt := range tests { testname := fmt.Sprintf("RegisterX:%d,RegisterY:%d,Xval:%d,Yval:%d,expected:%d,carry:%d", tt.regX, tt.regY, tt.xVal, tt.yVal, tt.result, tt.carry) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: 0x8007 | uint16(tt.regX)<<8 | uint16(tt.regY)<<4} cpu.registers[tt.regX] = tt.xVal cpu.registers[tt.regY] = tt.yVal cpu.bitOpsAndMath() if cpu.registers[tt.regX] != tt.result { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regX], tt.result) } if cpu.registers[tt.regY] != tt.yVal { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regY], tt.yVal) } if cpu.registers[0x0F] != tt.carry { t.Errorf("Carry register is %d, wanted %d", cpu.registers[0x0F], tt.carry) } }) } } //0x800E func TestSHLVxVyCarry(t *testing.T) { var tests = []struct { regX, regY, xVal, yVal, expX, expY, regF byte }{ {7, 8, 0x22, 0x44, 0x88, 0x44, 0}, {7, 8, 0x22, 0x45, 0x8A, 0x45, 0}, {7, 8, 0x22, 0xff, 0xFE, 0xFF, 1}, {7, 8, 0x22, 0x7F, 0xFE, 0x7F, 0}, } for _, tt := range tests { testname := fmt.Sprintf("RegisterX:%d,RegisterY:%d,Xval:%d,Yval:%d,expX:%d,expY:%d", tt.regX, tt.regY, tt.xVal, tt.yVal, tt.expX, tt.expY) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: 0x800E | uint16(tt.regX)<<8 | uint16(tt.regY)<<4} cpu.registers[tt.regX] = tt.xVal cpu.registers[tt.regY] = tt.yVal cpu.bitOpsAndMath() if cpu.registers[tt.regX] != tt.expX { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regX], tt.expX) } if cpu.registers[tt.regY] != tt.yVal { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regY], tt.yVal) } if cpu.registers[0x0F] != tt.regF { t.Errorf("Carry register is %d, wanted %d", cpu.registers[0x0F], tt.regF) } }) } } //0x9XY0 func TestSkipNotEqual(t *testing.T) { var tests = []struct { regX, regY, xVal, yVal byte pc_val uint16 }{ {7, 8, 0x10, 0x10, 0}, {7, 8, 0x10, 0x20, 2}, } for _, tt := range tests { testname := fmt.Sprintf("RegisterX:%d,RegisterY:%d,Xval:%d,Yval:%d,pc_val:%d", tt.regX, tt.regY, tt.xVal, tt.yVal, tt.pc_val) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: 0x9000 | uint16(tt.regX)<<8 | uint16(tt.regY)<<4} cpu.pc = 0 cpu.registers[tt.regX] = tt.xVal cpu.registers[tt.regY] = tt.yVal cpu.skipIfRegistersNotEqual() if cpu.registers[tt.regX] != tt.xVal { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regX], tt.xVal) } if cpu.registers[tt.regY] != tt.yVal { t.Errorf("Register is %d, wanted %d", cpu.registers[tt.regY], tt.yVal) } if cpu.pc != tt.pc_val { t.Errorf("pc is %d, wanted %d", cpu.registers[0x0F], tt.pc_val) } }) } } //0xANNN func TestSetAddressRegister(t *testing.T) { cpu := Chip8{opcode: 0xA420} cpu.setAddressRegister() if cpu.addressRegister != 0x420 { t.Errorf("Expected address register to be: %d, actual value %d", 0x420, cpu.addressRegister) } } //0xBnnn func TestJumpV0PlusNNN(t *testing.T) { var tests = []struct { regVal byte addr, expPc uint16 }{ {0x44, 0x555, 0x599}, } for _, tt := range tests { testname := fmt.Sprintf("TestJmpv0PlusNNN,regVal:%d,addr:%d,expected:%d", tt.regVal, tt.addr, tt.expPc) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: 0xB000 | tt.addr} cpu.registers[0] = tt.regVal cpu.jumpToV0PlusAddress() if cpu.pc != tt.expPc { t.Errorf("Expected pc to be: %d, actual value %d", tt.expPc, cpu.pc) } }) } } //0xCXNN // do something for rand but this one's probably fine idk //0xfx07 func TestSetRegisterToDelayTimer(t *testing.T) { cpu := Chip8{opcode: 0xF007 | uint16(7)<<8, delayTimer: 81} cpu.registers[7] = 17 cpu.fifteenIndexOpcodes() if cpu.registers[7] != 81 { t.Errorf("Expected register 7 to be %d, actually %d", 81, cpu.registers[7]) } } //0xfx15 func TestSetDelayTimer(t *testing.T) { cpu := Chip8{opcode: 0xF015 | uint16(7)<<8} cpu.registers[7] = 161 cpu.fifteenIndexOpcodes() if cpu.delayTimer != 161 { t.Errorf("Expected delay timer to be %d, actually %d", 161, cpu.delayTimer) } } //0xfx18 func TestSetSoundTimer(t *testing.T) { cpu := Chip8{opcode: 0xF018 | uint16(7)<<8} cpu.registers[7] = 161 cpu.fifteenIndexOpcodes() if cpu.beepTimer != 161 { t.Errorf("Expected delay timer to be %d, actually %d", 161, cpu.beepTimer) } } //0xFx1E func TestAddIVx(t *testing.T) { var tests = []struct { regX, xVal byte iVal, res uint16 }{ {0xB, 0x33, 0x22, 0x55}, {0xA, 0xFF, 0x1, 0x100}, {0xC, 0x01, 0xFFF, 0}, } for _, tt := range tests { testname := fmt.Sprintf("TestAddIVX,regX:%d,xVal:%d,iVal:%d,res:%d", tt.regX, tt.xVal, tt.iVal, tt.res) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: 0xF01E | uint16(tt.regX)<<8, addressRegister: tt.iVal} cpu.registers[tt.regX] = tt.xVal cpu.fifteenIndexOpcodes() if cpu.addressRegister != tt.res { t.Errorf("Expected address register to be %d actually %d", tt.res, cpu.addressRegister) } }) } } //0xFx29 func TestLoadSprite(t *testing.T) { var tests = []struct { regX, xVal byte iVal uint16 }{ {0xB, 0x0, 0x0}, {0xA, 0x1, 0x5}, {0xC, 0x2, 0xA}, {0xC, 0xf, 0x4B}, } for _, tt := range tests { testname := fmt.Sprintf("TestLoadSprite,regX:%d,xVal:%d,iVal:%d", tt.regX, tt.xVal, tt.iVal) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: 0xF029 | uint16(tt.regX)<<8} cpu.registers[tt.regX] = tt.xVal cpu.fifteenIndexOpcodes() if cpu.addressRegister != tt.iVal { t.Errorf("Expected address register to be %d actually %d", tt.iVal, cpu.addressRegister) } }) } } //0xFx33 func TestStoreBCD(t *testing.T) { cpu := Chip8{opcode: 0xFA33, addressRegister: 0x0AAA} cpu.registers[0xA] = 123 cpu.fifteenIndexOpcodes() if cpu.memory[0x0AAA] != 1 { t.Errorf("Store BCD Failed") } if cpu.memory[0x0AAA+1] != 2 { t.Errorf("Store BCD Failed") } if cpu.memory[0x0AAA+2] != 3 { t.Errorf("Store BCD Failed") } } //0xfx55 //0xfx65 // do the load store ones idk //0xdxyn func TestDraw(t *testing.T) { var tests = []struct { regX, regY, xVal, yVal, n, collision byte addressRegister uint16 sprite []byte set_bits [][]byte }{ {0xA, 0xB, 10, 10, 2, 0, 0x0BBB, []byte{0x3C, 0xC3}, [][]byte{[]byte{12, 10}, []byte{13, 10}, []byte{14, 10}, []byte{15, 10}, []byte{10, 11}, []byte{11, 11}, []byte{16, 11}, []byte{17, 11}}}, {0xA, 0xB, 10, 10, 2, 1, 0x0BBB, []byte{0x20}, [][]byte{}}, } for i, tt := range tests { testname := fmt.Sprintf("DrawTest%d", i) t.Run(testname, func(t *testing.T) { cpu := Chip8{opcode: 0xD000 | uint16(tt.regX)<<8 | uint16(tt.regY)<<4 | uint16(tt.n)} cpu.registers[tt.regX] = tt.xVal cpu.registers[tt.regY] = tt.yVal cpu.addressRegister = tt.addressRegister for j, val := range tt.sprite { cpu.memory[cpu.addressRegister+uint16(j)] = val } cpu.displaySprite() if tt.collision == 1 { cpu.displaySprite() // this is pretty fucking horrible but at this point i'm satisfied this function is correct so whatevs } if cpu.addressRegister != tt.addressRegister { t.Errorf("Expected address register to be %d actually %d", tt.addressRegister, cpu.addressRegister) } if cpu.registers[0xF] != tt.collision { t.Errorf("Expected reg[0xF] to be %d, %d", cpu.registers[0xF], tt.collision) } for _, val := range tt.set_bits { if cpu.graphics[int(val[0])+(int(val[1])*64)] != 1 { t.Errorf("Expected value at %d,%d to be set", val[0], val[1]) } } }) } }