yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
emulator_test_suite.h
Go to the documentation of this file.
1#ifndef YAZE_APP_TEST_EMULATOR_TEST_SUITE_H
2#define YAZE_APP_TEST_EMULATOR_TEST_SUITE_H
3
4#include <chrono>
5#include <memory>
6
7#include "app/emu/audio/apu.h"
10#include "app/emu/cpu/cpu.h"
14#include "app/emu/snes.h"
15#include "app/gui/core/icons.h"
17#include "util/log.h"
18
19namespace yaze {
20namespace test {
21
31 public:
32 EmulatorTestSuite() = default;
33 ~EmulatorTestSuite() override = default;
34
35 std::string GetName() const override { return "Emulator Core Tests"; }
36 TestCategory GetCategory() const override { return TestCategory::kUnit; }
37
38 absl::Status RunTests(TestResults& results) override {
40 RunApuHandshakeTest(results);
48 RunAudioBackendTest(results);
49
50 return absl::OkStatus();
51 }
52
53 void DrawConfiguration() override {
54 ImGui::Text("%s Emulator Core Test Configuration", ICON_MD_GAMEPAD);
55 ImGui::Separator();
56 ImGui::Checkbox("Test APU Handshake Protocol", &test_apu_handshake_);
57 ImGui::Checkbox("Test SPC700 Cycle Accuracy", &test_spc700_cycles_);
58 ImGui::Checkbox("Test Breakpoint Manager", &test_breakpoint_manager_);
59 ImGui::Checkbox("Test Watchpoint Manager", &test_watchpoint_manager_);
60 ImGui::Checkbox("Test Audio Backend", &test_audio_backend_);
61 }
62
63 private:
64 // Configuration flags
70
81 auto start_time = std::chrono::steady_clock::now();
82 TestResult result;
83 result.name = "APU_Handshake_Protocol";
84 result.suite_name = GetName();
85 result.category = GetCategory();
86 result.timestamp = start_time;
87
88 try {
89 // Setup a mock SNES environment
90 emu::Snes snes;
91 std::vector<uint8_t> rom_data(0x8000, 0); // Minimal ROM
92 snes.Init(rom_data);
93
94 auto& apu = snes.apu();
95 auto& tracker = snes.apu_handshake_tracker();
96
97 // 1. Reset APU to start the IPL ROM boot sequence.
98 apu.Reset();
99 tracker.Reset();
100
101 // 2. Run APU for enough cycles to complete its internal initialization.
102 // The SPC700 should write $AA to port $F4 and $BB to $F5.
103 for (int i = 0; i < 10000; ++i) {
104 apu.RunCycles(i * 24); // Simulate passing master cycles
105 if (tracker.GetPhase() ==
107 break;
108 }
109 }
110
111 // 3. Verify the APU has signaled it is ready.
112 if (tracker.GetPhase() !=
114 throw std::runtime_error(
115 "APU did not signal ready ($BBAA). Current phase: " +
116 tracker.GetPhaseString());
117 }
118
119 // 4. Simulate CPU writing $CC to initiate the transfer.
120 snes.Write(0x2140, 0xCC);
121
122 // 5. Run APU for a few more cycles to process the $CC command.
123 apu.RunCycles(snes.mutable_cycles() + 1000);
124
125 // 6. Verify the handshake is acknowledged.
126 if (tracker.IsHandshakeComplete()) {
128 result.error_message =
129 "APU handshake successful. Ready signal and CPU ack verified.";
130 } else {
131 throw std::runtime_error(
132 "CPU handshake ($CC) was not acknowledged by APU.");
133 }
134
135 } catch (const std::exception& e) {
137 result.error_message =
138 std::string("APU handshake test exception: ") + e.what();
139 }
140
141 result.duration = std::chrono::duration_cast<std::chrono::milliseconds>(
142 std::chrono::steady_clock::now() - start_time);
143 results.AddResult(result);
144 }
145
157 auto start_time = std::chrono::steady_clock::now();
158 TestResult result;
159 result.name = "SPC700_Cycle_Accuracy";
160 result.suite_name = GetName();
161 result.category = GetCategory();
162 result.timestamp = start_time;
163
164 try {
165 // Dummy callbacks for SPC700 instantiation
166 emu::ApuCallbacks callbacks;
167 callbacks.read = [](uint16_t) {
168 return 0;
169 };
170 callbacks.write = [](uint16_t, uint8_t) {
171 };
172 callbacks.idle = [](bool) {
173 };
174
175 emu::Spc700 spc(callbacks);
176 spc.Reset(true);
177
178 // Test a sample of opcodes against the cycle table
179 // Opcode 0x00 (NOP) should take 2 cycles
180 spc.PC = 0; // Set PC to a known state
181 spc.RunOpcode(); // This will read opcode at PC=0 and prepare to execute
182 spc.RunOpcode(); // This executes the opcode
183
184 if (spc.GetLastOpcodeCycles() != 2) {
185 throw std::runtime_error(
186 absl::StrFormat("NOP (0x00) should be 2 cycles, was %d",
187 spc.GetLastOpcodeCycles()));
188 }
189
190 // Opcode 0x2F (BRA) should take 4 cycles
191 spc.PC = 0;
192 spc.RunOpcode();
193 spc.RunOpcode();
194
195 // Note: This is a simplified check. A full implementation would need to
196 // mock memory to provide the opcodes to the SPC700.
197
199 result.error_message = "Basic SPC700 cycle counts appear correct.";
200
201 } catch (const std::exception& e) {
203 result.error_message =
204 std::string("SPC700 cycle test exception: ") + e.what();
205 }
206
207 result.duration = std::chrono::duration_cast<std::chrono::milliseconds>(
208 std::chrono::steady_clock::now() - start_time);
209 results.AddResult(result);
210 }
211
221 auto start_time = std::chrono::steady_clock::now();
222 TestResult result;
223 result.name = "BreakpointManager_Core";
224 result.suite_name = GetName();
225 result.category = GetCategory();
226 result.timestamp = start_time;
227
228 try {
230
231 // 1. Add an execution breakpoint
232 uint32_t bp_id =
235 if (bpm.GetAllBreakpoints().size() != 1) {
236 throw std::runtime_error("Failed to add breakpoint.");
237 }
238
239 // 2. Test hit detection
240 if (!bpm.ShouldBreakOnExecute(
242 throw std::runtime_error("Execution breakpoint was not hit.");
243 }
244 if (bpm.ShouldBreakOnExecute(
246 throw std::runtime_error("Breakpoint hit at incorrect address.");
247 }
248
249 // 3. Test removal
250 bpm.RemoveBreakpoint(bp_id);
251 if (bpm.GetAllBreakpoints().size() != 0) {
252 throw std::runtime_error("Failed to remove breakpoint.");
253 }
254 if (bpm.ShouldBreakOnExecute(
256 throw std::runtime_error("Breakpoint was hit after being removed.");
257 }
258
260 result.error_message =
261 "BreakpointManager add, hit, and remove tests passed.";
262
263 } catch (const std::exception& e) {
265 result.error_message =
266 std::string("BreakpointManager test exception: ") + e.what();
267 }
268
269 result.duration = std::chrono::duration_cast<std::chrono::milliseconds>(
270 std::chrono::steady_clock::now() - start_time);
271 results.AddResult(result);
272 }
273
282 auto start_time = std::chrono::steady_clock::now();
283 TestResult result;
284 result.name = "WatchpointManager_Core";
285 result.suite_name = GetName();
286 result.category = GetCategory();
287 result.timestamp = start_time;
288
289 try {
291
292 // 1. Add a write watchpoint on address $7E0010 with break enabled.
293 uint32_t wp_id =
294 wpm.AddWatchpoint(0x7E0010, 0x7E0010, false, true, true, "Link HP");
295
296 // 2. Simulate a write access and check if it breaks.
297 bool should_break =
298 wpm.OnMemoryAccess(0x8000, 0x7E0010, true, 0x05, 0x06, 12345);
299 if (!should_break) {
300 throw std::runtime_error("Write watchpoint did not trigger a break.");
301 }
302
303 // 3. Simulate a read access, which should not break.
304 should_break =
305 wpm.OnMemoryAccess(0x8001, 0x7E0010, false, 0x06, 0x06, 12350);
306 if (should_break) {
307 throw std::runtime_error(
308 "Read access incorrectly triggered a write-only watchpoint.");
309 }
310
311 // 4. Verify the write access was logged.
312 auto history = wpm.GetHistory(0x7E0010);
313 if (history.size() != 1) {
314 throw std::runtime_error(
315 "Memory access was not logged to watchpoint history.");
316 }
317 if (history[0].new_value != 0x06 || !history[0].is_write) {
318 throw std::runtime_error("Logged access data is incorrect.");
319 }
320
322 result.error_message =
323 "WatchpointManager logging and break-on-write tests passed.";
324
325 } catch (const std::exception& e) {
327 result.error_message =
328 std::string("WatchpointManager test exception: ") + e.what();
329 }
330
331 result.duration = std::chrono::duration_cast<std::chrono::milliseconds>(
332 std::chrono::steady_clock::now() - start_time);
333 results.AddResult(result);
334 }
335
344 auto start_time = std::chrono::steady_clock::now();
345 TestResult result;
346 result.name = "Audio_Backend_Initialization";
347 result.suite_name = GetName();
348 result.category = GetCategory();
349 result.timestamp = start_time;
350
351 try {
354
355 // 1. Test initialization
357 if (!backend->Initialize(config)) {
358 throw std::runtime_error("Audio backend failed to initialize.");
359 }
360 if (!backend->IsInitialized()) {
361 throw std::runtime_error(
362 "IsInitialized() returned false after successful initialization.");
363 }
364
365 // 2. Test state changes
366 backend->Play();
367 if (!backend->GetStatus().is_playing) {
368 throw std::runtime_error(
369 "Backend is not playing after Play() was called.");
370 }
371
372 backend->Pause();
373 if (backend->GetStatus().is_playing) {
374 throw std::runtime_error(
375 "Backend is still playing after Pause() was called.");
376 }
377
378 // 3. Test shutdown
379 backend->Shutdown();
380 if (backend->IsInitialized()) {
381 throw std::runtime_error(
382 "IsInitialized() returned true after Shutdown().");
383 }
384
386 result.error_message =
387 "Audio backend Initialize, Play, Pause, and Shutdown states work "
388 "correctly.";
389
390 } catch (const std::exception& e) {
392 result.error_message =
393 std::string("Audio backend test exception: ") + e.what();
394 }
395
396 result.duration = std::chrono::duration_cast<std::chrono::milliseconds>(
397 std::chrono::steady_clock::now() - start_time);
398 results.AddResult(result);
399 }
400};
401
402} // namespace test
403} // namespace yaze
404
405#endif // YAZE_APP_TEST_EMULATOR_TEST_SUITE_H
Manages CPU and SPC700 breakpoints for debugging.
bool ShouldBreakOnExecute(uint32_t pc, CpuType cpu)
Check if execution should break at this address.
void RemoveBreakpoint(uint32_t id)
Remove a breakpoint by ID.
std::vector< Breakpoint > GetAllBreakpoints() const
Get all breakpoints.
uint32_t AddBreakpoint(uint32_t address, Type type, CpuType cpu, const std::string &condition="", const std::string &description="")
Add a new breakpoint.
auto mutable_cycles() -> uint64_t &
Definition snes.h:90
auto apu_handshake_tracker() -> debug::ApuHandshakeTracker &
Definition snes.h:111
auto apu() -> Apu &
Definition snes.h:86
void Write(uint32_t adr, uint8_t val)
Definition snes.cc:770
void Init(const std::vector< uint8_t > &rom_data)
Definition snes.cc:162
The Spc700 class represents the SPC700 processor.
Definition spc700.h:69
void Reset(bool hard=false)
Definition spc700.cc:16
int GetLastOpcodeCycles() const
Definition spc700.h:151
uint16_t PC
Definition spc700.h:106
void RunOpcode()
Definition spc700.cc:75
Manages memory watchpoints for debugging.
uint32_t AddWatchpoint(uint32_t start_address, uint32_t end_address, bool track_reads, bool track_writes, bool break_on_access=false, const std::string &description="")
Add a memory watchpoint.
std::vector< AccessLog > GetHistory(uint32_t address, int max_entries=100) const
Get access history for a specific address.
bool OnMemoryAccess(uint32_t pc, uint32_t address, bool is_write, uint8_t old_value, uint8_t new_value, uint64_t cycle_count)
Check if memory access should break/log.
static std::unique_ptr< IAudioBackend > Create(BackendType type)
Test suite for core emulator components.
absl::Status RunTests(TestResults &results) override
void RunBreakpointManagerTest(TestResults &results)
Tests the core functionality of the BreakpointManager.
void RunApuHandshakeTest(TestResults &results)
Verifies the CPU-APU handshake protocol.
~EmulatorTestSuite() override=default
std::string GetName() const override
void RunAudioBackendTest(TestResults &results)
Tests the audio backend abstraction layer.
TestCategory GetCategory() const override
void RunWatchpointManagerTest(TestResults &results)
Tests the memory WatchpointManager.
void RunSpc700CycleAccuracyTest(TestResults &results)
Validates the cycle counting for SPC700 opcodes.
#define ICON_MD_GAMEPAD
Definition icons.h:866
std::function< void(uint16_t, uint8_t)> write
Definition spc700.h:53
std::function< void(bool)> idle
Definition spc700.h:55
std::function< uint8_t(uint16_t)> read
Definition spc700.h:54
std::chrono::milliseconds duration
std::string error_message
std::chrono::time_point< std::chrono::steady_clock > timestamp
void AddResult(const TestResult &result)