mgba-ps3/src/core/test/scripting.c

875 lines
21 KiB
C

/* Copyright (c) 2013-2022 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "util/test/suite.h"
#include <mgba/core/core.h>
#include <mgba/core/log.h>
#include <mgba/core/scripting.h>
#include <mgba/internal/script/lua.h>
#include <mgba/script.h>
#include "script/test.h"
#ifdef M_CORE_GBA
#include <mgba/internal/gba/memory.h>
#define TEST_PLATFORM mPLATFORM_GBA
#define RAM_BASE GBA_BASE_IWRAM
#elif defined(M_CORE_GB)
#include <mgba/internal/gb/memory.h>
#define TEST_PLATFORM mPLATFORM_GB
#define RAM_BASE GB_BASE_WORKING_RAM_BANK0
#else
#error "Need a valid platform for testing"
#endif
struct mScriptTestLogger {
struct mLogger d;
char* log;
char* warn;
char* error;
};
static const uint8_t _fakeGBROM[0x4000] = {
[0x100] = 0x18, // Loop forever
[0x101] = 0xFE, // jr, $-2
[0x102] = 0xCE, // Enough of the header to fool the core
[0x103] = 0xED,
[0x104] = 0x66,
[0x105] = 0x66,
};
#define SETUP_LUA \
struct mScriptContext context; \
mScriptContextInit(&context); \
struct mScriptEngineContext* lua = mScriptContextRegisterEngine(&context, mSCRIPT_ENGINE_LUA)
#define CREATE_CORE \
struct mCore* core = mCoreCreate(TEST_PLATFORM); \
assert_non_null(core); \
assert_true(core->init(core)); \
switch (core->platform(core)) { \
case mPLATFORM_GBA: \
core->busWrite32(core, 0x020000C0, 0xEAFFFFFE); \
break; \
case mPLATFORM_GB: \
assert_true(core->loadROM(core, VFileFromConstMemory(_fakeGBROM, sizeof(_fakeGBROM)))); \
break; \
case mPLATFORM_NONE: \
break; \
} \
mCoreInitConfig(core, NULL); \
mScriptContextAttachCore(&context, core)
#define TEARDOWN_CORE \
mCoreConfigDeinit(&core->config); \
core->deinit(core)
static void _mScriptTestLog(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args) {
UNUSED(category);
struct mScriptTestLogger* logger = (struct mScriptTestLogger*) log;
char* message;
#ifdef HAVE_VASPRINTF
vasprintf(&message, format, args);
#else
char messageBuf[64];
vsnprintf(messageBuf, format, args);
message = strdup(messageBuf);
#endif
switch (level) {
case mLOG_INFO:
if (logger->log) {
free(logger->log);
}
logger->log = message;
break;
case mLOG_WARN:
if (logger->warn) {
free(logger->warn);
}
logger->warn = message;
break;
case mLOG_ERROR:
if (logger->error) {
free(logger->error);
}
logger->error = message;
break;
default:
free(message);
}
}
static void mScriptTestLoggerInit(struct mScriptTestLogger* logger) {
memset(logger, 0, sizeof(*logger));
logger->d.log = _mScriptTestLog;
}
static void mScriptTestLoggerDeinit(struct mScriptTestLogger* logger) {
if (logger->log) {
free(logger->log);
}
if (logger->warn) {
free(logger->warn);
}
if (logger->error) {
free(logger->error);
}
}
M_TEST_SUITE_SETUP(mScriptCore) {
if (mSCRIPT_ENGINE_LUA->init) {
mSCRIPT_ENGINE_LUA->init(mSCRIPT_ENGINE_LUA);
}
return 0;
}
M_TEST_SUITE_TEARDOWN(mScriptCore) {
if (mSCRIPT_ENGINE_LUA->deinit) {
mSCRIPT_ENGINE_LUA->deinit(mSCRIPT_ENGINE_LUA);
}
return 0;
}
M_TEST_DEFINE(globals) {
SETUP_LUA;
CREATE_CORE;
core->reset(core);
LOAD_PROGRAM("assert(emu)");
assert_true(lua->run(lua));
mScriptContextDeinit(&context);
TEARDOWN_CORE;
}
M_TEST_DEFINE(infoFuncs) {
SETUP_LUA;
CREATE_CORE;
core->reset(core);
LOAD_PROGRAM(
"frequency = emu:frequency()\n"
"frameCycles = emu:frameCycles()\n"
);
assert_true(lua->run(lua));
TEST_VALUE(S32, "frequency", core->frequency(core));
TEST_VALUE(S32, "frameCycles", core->frameCycles(core));
mScriptContextDeinit(&context);
TEARDOWN_CORE;
}
M_TEST_DEFINE(detach) {
SETUP_LUA;
CREATE_CORE;
core->reset(core);
LOAD_PROGRAM(
"assert(emu)\n"
"assert(emu.memory)\n"
"a = emu\n"
"b = emu.memory\n"
);
assert_true(lua->run(lua));
mScriptContextDetachCore(&context);
LOAD_PROGRAM(
"assert(not emu)\n"
);
assert_true(lua->run(lua));
LOAD_PROGRAM(
"a:frequency()\n"
);
assert_false(lua->run(lua));
LOAD_PROGRAM(
"assert(memory.cart0)\n"
);
assert_false(lua->run(lua));
mScriptContextDeinit(&context);
TEARDOWN_CORE;
}
M_TEST_DEFINE(runFrame) {
SETUP_LUA;
CREATE_CORE;
core->reset(core);
LOAD_PROGRAM(
"frame = emu:currentFrame()\n"
"emu:runFrame()\n"
);
int i;
for (i = 0; i < 5; ++i) {
assert_true(lua->run(lua));
TEST_VALUE(S32, "frame", i);
}
mScriptContextDeinit(&context);
TEARDOWN_CORE;
}
M_TEST_DEFINE(memoryRead) {
SETUP_LUA;
CREATE_CORE;
core->reset(core);
LOAD_PROGRAM(
"a8 = emu:read8(base + 0)\n"
"b8 = emu:read8(base + 1)\n"
"c8 = emu:read8(base + 2)\n"
"d8 = emu:read8(base + 3)\n"
"a16 = emu:read16(base + 4)\n"
"b16 = emu:read16(base + 6)\n"
"a32 = emu:read32(base + 8)\n"
);
int i;
for (i = 0; i < 12; ++i) {
core->busWrite8(core, RAM_BASE + i, i + 1);
}
struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE);
lua->setGlobal(lua, "base", &base);
assert_true(lua->run(lua));
TEST_VALUE(S32, "a8", 1);
TEST_VALUE(S32, "b8", 2);
TEST_VALUE(S32, "c8", 3);
TEST_VALUE(S32, "d8", 4);
TEST_VALUE(S32, "a16", 0x0605);
TEST_VALUE(S32, "b16", 0x0807);
TEST_VALUE(S32, "a32", 0x0C0B0A09);
mScriptContextDeinit(&context);
TEARDOWN_CORE;
}
M_TEST_DEFINE(memoryWrite) {
SETUP_LUA;
CREATE_CORE;
core->reset(core);
LOAD_PROGRAM(
"emu:write8(base + 0, 1)\n"
"emu:write8(base + 1, 2)\n"
"emu:write8(base + 2, 3)\n"
"emu:write8(base + 3, 4)\n"
"emu:write16(base + 4, 0x0605)\n"
"emu:write16(base + 6, 0x0807)\n"
"emu:write32(base + 8, 0x0C0B0A09)\n"
);
struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE);
lua->setGlobal(lua, "base", &base);
assert_true(lua->run(lua));
int i;
for (i = 0; i < 12; ++i) {
assert_int_equal(core->busRead8(core, RAM_BASE + i), i + 1);
}
mScriptContextDeinit(&context);
TEARDOWN_CORE;
}
M_TEST_DEFINE(logging) {
SETUP_LUA;
struct mScriptTestLogger logger;
mScriptTestLoggerInit(&logger);
mScriptContextAttachLogger(&context, &logger.d);
LOAD_PROGRAM(
"assert(console)\n"
"console:log(\"log\")\n"
"console:warn(\"warn\")\n"
"console:error(\"error\")\n"
"a = console\n"
);
assert_true(lua->run(lua));
assert_non_null(logger.log);
assert_non_null(logger.warn);
assert_non_null(logger.error);
assert_string_equal(logger.log, "log");
assert_string_equal(logger.warn, "warn");
assert_string_equal(logger.error, "error");
mScriptContextDetachLogger(&context);
mScriptTestLoggerDeinit(&logger);
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(screenshot) {
SETUP_LUA;
CREATE_CORE;
mColor* buffer = malloc(240 * 160 * sizeof(mColor));
core->setVideoBuffer(core, buffer, 240);
core->reset(core);
core->runFrame(core);
TEST_PROGRAM("im = emu:screenshotToImage()");
TEST_PROGRAM("assert(im)");
TEST_PROGRAM("assert(im.width >= 160)");
TEST_PROGRAM("assert(im.height >= 144)");
free(buffer);
mScriptContextDeinit(&context);
TEARDOWN_CORE;
}
#ifdef ENABLE_DEBUGGERS
void _setupBp(struct mCore* core) {
switch (core->platform(core)) {
#ifdef M_CORE_GBA
case mPLATFORM_GBA:
core->busWrite32(core, 0x020000C0, 0xE0000000); // nop
core->busWrite32(core, 0x020000C4, 0xE0000000); // nop
core->busWrite32(core, 0x020000C8, 0xEAFFFFFD); // b 0x020000C4
break;
#endif
#ifdef M_CORE_GB
case mPLATFORM_GB:
core->rawWrite8(core, 0x101, 0, 0xEE); // Jump to 0xF0
core->rawWrite8(core, 0xF0, 0, 0x00); // nop
core->rawWrite8(core, 0xF1, 0, 0x18); // Loop forecer
core->rawWrite8(core, 0xF2, 0, 0xFD); // jr $-3
break;
#endif
}
}
#ifdef M_CORE_GBA
M_TEST_DEFINE(basicBreakpointGBA) {
SETUP_LUA;
struct mCore* core = mCoreCreate(mPLATFORM_GBA);
struct mDebugger debugger;
assert_non_null(core);
assert_true(core->init(core));
mCoreInitConfig(core, NULL);
core->reset(core);
_setupBp(core);
mScriptContextAttachCore(&context, core);
mDebuggerInit(&debugger);
mDebuggerAttach(&debugger, core);
TEST_PROGRAM(
"hit = 0\n"
"address = nil\n"
"function bkpt(info)\n"
" hit = hit + 1\n"
" address = info.address\n"
"end"
);
TEST_PROGRAM("cbid = emu:setBreakpoint(bkpt, 0x020000C4)");
TEST_PROGRAM("assert(cbid == 1)");
int i;
for (i = 0; i < 20; ++i) {
mDebuggerRun(&debugger);
}
assert_int_equal(debugger.state, DEBUGGER_RUNNING);
TEST_PROGRAM("assert(hit >= 1)");
TEST_PROGRAM("assert(address == 0x020000C4)");
mScriptContextDeinit(&context);
TEARDOWN_CORE;
mDebuggerDeinit(&debugger);
}
#endif
#ifdef M_CORE_GB
M_TEST_DEFINE(basicBreakpointGB) {
SETUP_LUA;
struct mCore* core = mCoreCreate(mPLATFORM_GB);
struct mDebugger debugger;
assert_non_null(core);
assert_true(core->init(core));
mCoreInitConfig(core, NULL);
assert_true(core->loadROM(core, VFileFromConstMemory(_fakeGBROM, sizeof(_fakeGBROM))));
core->reset(core);
_setupBp(core);
mScriptContextAttachCore(&context, core);
mDebuggerInit(&debugger);
mDebuggerAttach(&debugger, core);
TEST_PROGRAM(
"hit = 0\n"
"address = nil\n"
"function bkpt(info)\n"
" hit = hit + 1\n"
" address = info.address\n"
"end"
);
TEST_PROGRAM("cbid = emu:setBreakpoint(bkpt, 0xF0)");
TEST_PROGRAM("assert(cbid == 1)");
int i;
for (i = 0; i < 20; ++i) {
mDebuggerRun(&debugger);
}
assert_int_equal(debugger.state, DEBUGGER_RUNNING);
TEST_PROGRAM("assert(hit >= 1)");
TEST_PROGRAM("assert(address == 0xF0)");
mScriptContextDeinit(&context);
TEARDOWN_CORE;
mDebuggerDeinit(&debugger);
}
#endif
M_TEST_DEFINE(multipleBreakpoint) {
SETUP_LUA;
struct mCore* core = mCoreCreate(TEST_PLATFORM);
struct mDebugger debugger;
assert_non_null(core);
assert_true(core->init(core));
mCoreInitConfig(core, NULL);
core->reset(core);
_setupBp(core);
mScriptContextAttachCore(&context, core);
mDebuggerInit(&debugger);
mDebuggerAttach(&debugger, core);
TEST_PROGRAM(
"hit = 0\n"
"address = nil\n"
"function bkpt1(info)\n"
" hit = hit + 1\n"
" if address then\n"
" address = (address + info.address) / 2\n"
" else\n"
" address = info.address\n"
" end\n"
"end\n"
"function bkpt2(info)\n"
" hit = hit + 100\n"
" if address then\n"
" address = (address + info.address) / 2\n"
" else\n"
" address = info.address\n"
" end\n"
"end"
);
#ifdef M_CORE_GBA
TEST_PROGRAM("cbid1 = emu:setBreakpoint(bkpt1, 0x020000C4)");
TEST_PROGRAM("cbid2 = emu:setBreakpoint(bkpt2, 0x020000C8)");
#else
TEST_PROGRAM("cbid1 = emu:setBreakpoint(bkpt1, 0xF0)");
TEST_PROGRAM("cbid2 = emu:setBreakpoint(bkpt2, 0xF1)");
#endif
TEST_PROGRAM("assert(cbid1 == 1)");
TEST_PROGRAM("assert(cbid2 == 2)");
int i;
for (i = 0; i < 20; ++i) {
mDebuggerRun(&debugger);
}
assert_int_equal(debugger.state, DEBUGGER_RUNNING);
TEST_PROGRAM("assert(hit >= 101)");
#ifdef M_CORE_GBA
TEST_PROGRAM("assert(address >= 0x020000C4)");
TEST_PROGRAM("assert(address <= 0x020000C8)");
#else
TEST_PROGRAM("assert(address >= 0xF0)");
TEST_PROGRAM("assert(address <= 0xF1)");
#endif
mScriptContextDeinit(&context);
TEARDOWN_CORE;
mDebuggerDeinit(&debugger);
}
M_TEST_DEFINE(basicWatchpoint) {
SETUP_LUA;
mScriptContextAttachStdlib(&context);
CREATE_CORE;
struct mDebugger debugger;
core->reset(core);
mScriptContextAttachCore(&context, core);
int i;
for (i = 0; i < 4; ++i) {
core->busWrite8(core, RAM_BASE + i, i + 1);
}
mDebuggerInit(&debugger);
mDebuggerAttach(&debugger, core);
TEST_PROGRAM(
"hit = 0\n"
"address = nil\n"
"width = nil\n"
"oldValue = nil\n"
"newValue = nil\n"
"accessType = nil\n"
"function bkpt(info)\n"
" hit = hit + 1\n"
" address = info.address\n"
" width = info.width\n"
" oldValue = info.oldValue\n"
" newValue = info.newValue\n"
" accessType = info.accessType\n"
"end"
);
struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE);
lua->setGlobal(lua, "base", &base);
TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.READ))");
TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base + 1, C.WATCHPOINT_TYPE.WRITE))");
TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base + 2, C.WATCHPOINT_TYPE.RW))");
TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base + 3, C.WATCHPOINT_TYPE.WRITE_CHANGE))");
TEST_PROGRAM("assert(hit == 0)");
uint8_t value;
// Read
TEST_PROGRAM(
"hit = 0\n"
"address = nil\n"
"width = nil\n"
"oldValue = nil\n"
"newValue = nil\n"
"accessType = nil\n");
value = core->rawRead8(core, RAM_BASE, -1);
TEST_PROGRAM("assert(hit == 0)");
core->busRead8(core, RAM_BASE);
TEST_PROGRAM("assert(hit == 1)");
core->busWrite8(core, RAM_BASE, value);
TEST_PROGRAM("assert(hit == 1)");
core->busWrite8(core, RAM_BASE, ~value);
TEST_PROGRAM("assert(hit == 1)");
TEST_PROGRAM("assert(address == base)");
TEST_PROGRAM("assert(width == 1)");
TEST_PROGRAM("assert(oldValue == 1)");
TEST_PROGRAM("assert(newValue == nil)");
TEST_PROGRAM("assert(accessType == C.WATCHPOINT_TYPE.READ)");
// Write
TEST_PROGRAM(
"hit = 0\n"
"address = nil\n"
"width = nil\n"
"oldValue = nil\n"
"newValue = nil\n"
"accessType = nil\n");
value = core->rawRead8(core, RAM_BASE + 1, -1);
TEST_PROGRAM("assert(hit == 0)");
core->busRead8(core, RAM_BASE + 1);
TEST_PROGRAM("assert(hit == 0)");
core->busWrite8(core, RAM_BASE + 1, value);
TEST_PROGRAM("assert(hit == 1)");
TEST_PROGRAM("assert(oldValue == 2)");
TEST_PROGRAM("assert(newValue == 2)");
core->busWrite8(core, RAM_BASE + 1, ~value);
TEST_PROGRAM("assert(hit == 2)");
TEST_PROGRAM("assert(address == base + 1)");
TEST_PROGRAM("assert(width == 1)");
TEST_PROGRAM("assert(oldValue == 2)");
TEST_PROGRAM("assert(newValue == -3)");
TEST_PROGRAM("assert(accessType == C.WATCHPOINT_TYPE.WRITE)");
// RW
TEST_PROGRAM(
"hit = 0\n"
"address = nil\n"
"width = nil\n"
"oldValue = nil\n"
"newValue = nil\n"
"accessType = nil\n");
value = core->rawRead8(core, RAM_BASE + 2, -1);
TEST_PROGRAM("assert(hit == 0)");
core->busRead8(core, RAM_BASE + 2);
TEST_PROGRAM("assert(accessType == C.WATCHPOINT_TYPE.READ)");
TEST_PROGRAM("assert(hit == 1)");
TEST_PROGRAM("assert(oldValue == 3)");
TEST_PROGRAM("assert(newValue == nil)");
core->busWrite8(core, RAM_BASE + 2, value);
TEST_PROGRAM("assert(hit == 2)");
TEST_PROGRAM("assert(oldValue == 3)");
TEST_PROGRAM("assert(newValue == 3)");
core->busWrite8(core, RAM_BASE + 2, ~value);
TEST_PROGRAM("assert(hit == 3)");
TEST_PROGRAM("assert(address == base + 2)");
TEST_PROGRAM("assert(width == 1)");
TEST_PROGRAM("assert(oldValue == 3)");
TEST_PROGRAM("assert(newValue == -4)");
TEST_PROGRAM("assert(accessType == C.WATCHPOINT_TYPE.WRITE)");
// Change
TEST_PROGRAM(
"hit = 0\n"
"address = nil\n"
"width = nil\n"
"oldValue = nil\n"
"newValue = nil\n"
"accessType = nil\n");
value = core->rawRead8(core, RAM_BASE + 3, -1);
TEST_PROGRAM("assert(hit == 0)");
core->busRead8(core, RAM_BASE + 3);
TEST_PROGRAM("assert(hit == 0)");
core->busWrite8(core, RAM_BASE + 3, value);
TEST_PROGRAM("assert(hit == 0)");
core->busWrite8(core, RAM_BASE + 3, ~value);
TEST_PROGRAM("assert(hit == 1)");
TEST_PROGRAM("assert(address == base + 3)");
TEST_PROGRAM("assert(width == 1)");
TEST_PROGRAM("assert(oldValue == 4)");
TEST_PROGRAM("assert(newValue == -5)");
TEST_PROGRAM("assert(accessType == C.WATCHPOINT_TYPE.WRITE)");
mScriptContextDeinit(&context);
TEARDOWN_CORE;
mDebuggerDeinit(&debugger);
}
M_TEST_DEFINE(watchpointReentrant) {
SETUP_LUA;
mScriptContextAttachStdlib(&context);
CREATE_CORE;
struct mDebugger debugger;
core->reset(core);
mScriptContextAttachCore(&context, core);
mDebuggerInit(&debugger);
mDebuggerAttach(&debugger, core);
TEST_PROGRAM(
"hit = 0\n"
"function bkpt()\n"
" hit = hit + 1\n"
"end"
);
struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE);
lua->setGlobal(lua, "base", &base);
TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.READ))");
TEST_PROGRAM("hit = 0");
core->busRead8(core, RAM_BASE);
TEST_PROGRAM("assert(hit == 1)");
TEST_PROGRAM("emu:read8(base)");
TEST_PROGRAM("assert(hit == 1)");
core->busRead8(core, RAM_BASE);
TEST_PROGRAM("assert(hit == 2)");
mScriptContextDeinit(&context);
TEARDOWN_CORE;
mDebuggerDeinit(&debugger);
}
M_TEST_DEFINE(removeBreakpoint) {
SETUP_LUA;
mScriptContextAttachStdlib(&context);
CREATE_CORE;
struct mDebugger debugger;
core->reset(core);
mScriptContextAttachCore(&context, core);
mDebuggerInit(&debugger);
mDebuggerAttach(&debugger, core);
TEST_PROGRAM(
"hit = 0\n"
"function bkpt()\n"
" hit = hit + 1\n"
"end"
);
struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE);
lua->setGlobal(lua, "base", &base);
TEST_PROGRAM("cbid = emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.READ)");
TEST_PROGRAM("assert(hit == 0)");
core->busRead8(core, RAM_BASE);
TEST_PROGRAM("assert(hit == 1)");
core->busRead8(core, RAM_BASE);
TEST_PROGRAM("assert(hit == 2)");
TEST_PROGRAM("assert(emu:clearBreakpoint(cbid))");
core->busRead8(core, RAM_BASE);
TEST_PROGRAM("assert(hit == 2)");
mScriptContextDeinit(&context);
TEARDOWN_CORE;
mDebuggerDeinit(&debugger);
}
M_TEST_DEFINE(overlappingBreakpoint) {
SETUP_LUA;
struct mCore* core = mCoreCreate(TEST_PLATFORM);
struct mDebugger debugger;
assert_non_null(core);
assert_true(core->init(core));
mCoreInitConfig(core, NULL);
core->reset(core);
_setupBp(core);
mScriptContextAttachCore(&context, core);
mDebuggerInit(&debugger);
mDebuggerAttach(&debugger, core);
TEST_PROGRAM(
"hit = 0\n"
"function bkpt1()\n"
" hit = hit + 1\n"
"end\n"
"function bkpt2()\n"
" hit = hit + 100\n"
"end"
);
#ifdef M_CORE_GBA
TEST_PROGRAM("cbid1 = emu:setBreakpoint(bkpt1, 0x020000C4)");
TEST_PROGRAM("cbid2 = emu:setBreakpoint(bkpt2, 0x020000C4)");
#else
TEST_PROGRAM("cbid1 = emu:setBreakpoint(bkpt1, 0xF0)");
TEST_PROGRAM("cbid2 = emu:setBreakpoint(bkpt2, 0xF0)");
#endif
TEST_PROGRAM("assert(cbid1 == 1)");
TEST_PROGRAM("assert(cbid2 == 2)");
int i;
for (i = 0; i < 20; ++i) {
mDebuggerRun(&debugger);
}
assert_int_equal(debugger.state, DEBUGGER_RUNNING);
TEST_PROGRAM("assert(hit >= 101)");
TEST_PROGRAM("oldHit = hit");
TEST_PROGRAM("assert(emu:clearBreakpoint(cbid2))");
for (i = 0; i < 10; ++i) {
mDebuggerRun(&debugger);
}
TEST_PROGRAM("assert(hit - oldHit > 0)");
TEST_PROGRAM("assert(hit - oldHit < 100)");
mScriptContextDeinit(&context);
TEARDOWN_CORE;
mDebuggerDeinit(&debugger);
}
M_TEST_DEFINE(overlappingWatchpoint) {
SETUP_LUA;
mScriptContextAttachStdlib(&context);
CREATE_CORE;
struct mDebugger debugger;
core->reset(core);
mScriptContextAttachCore(&context, core);
mDebuggerInit(&debugger);
mDebuggerAttach(&debugger, core);
TEST_PROGRAM(
"hit = 0\n"
"function bkpt()\n"
" hit = hit + 1\n"
"end"
);
struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE);
lua->setGlobal(lua, "base", &base);
TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.READ))");
TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.WRITE))");
TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.RW))");
TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.WRITE_CHANGE))");
TEST_PROGRAM("assert(hit == 0)");
uint8_t value;
// Read
TEST_PROGRAM("hit = 0");
value = core->rawRead8(core, RAM_BASE, -1);
TEST_PROGRAM("assert(hit == 0)");
core->busRead8(core, RAM_BASE);
TEST_PROGRAM("assert(hit == 2)"); // Read, RW
core->busWrite8(core, RAM_BASE, value);
TEST_PROGRAM("assert(hit == 4)"); // Write, RW
core->busWrite8(core, RAM_BASE, ~value);
TEST_PROGRAM("assert(hit == 7)"); // Write, RW, change
mScriptContextDeinit(&context);
TEARDOWN_CORE;
mDebuggerDeinit(&debugger);
}
M_TEST_DEFINE(rangeWatchpoint) {
SETUP_LUA;
mScriptContextAttachStdlib(&context);
CREATE_CORE;
struct mDebugger debugger;
core->reset(core);
mScriptContextAttachCore(&context, core);
mDebuggerInit(&debugger);
mDebuggerAttach(&debugger, core);
TEST_PROGRAM(
"hit = 0\n"
"address = nil\n"
"function bkpt(info)\n"
" hit = hit + 1\n"
" address = info.address\n"
"end"
);
struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE);
lua->setGlobal(lua, "base", &base);
TEST_PROGRAM("assert(0 < emu:setRangeWatchpoint(bkpt, base, base + 2, C.WATCHPOINT_TYPE.READ))");
TEST_PROGRAM("assert(0 < emu:setRangeWatchpoint(bkpt, base + 1, base + 3, C.WATCHPOINT_TYPE.READ))");
// Read
TEST_PROGRAM("assert(hit == 0)");
core->busRead8(core, RAM_BASE);
TEST_PROGRAM("assert(hit == 1)");
TEST_PROGRAM("assert(address == base)");
core->busRead8(core, RAM_BASE + 1);
TEST_PROGRAM("assert(hit == 3)");
TEST_PROGRAM("assert(address == base + 1)");
core->busRead8(core, RAM_BASE + 2);
TEST_PROGRAM("assert(hit == 4)");
TEST_PROGRAM("assert(address == base + 2)");
mScriptContextDeinit(&context);
TEARDOWN_CORE;
mDebuggerDeinit(&debugger);
}
#endif
M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptCore,
cmocka_unit_test(globals),
cmocka_unit_test(infoFuncs),
cmocka_unit_test(detach),
cmocka_unit_test(runFrame),
cmocka_unit_test(memoryRead),
cmocka_unit_test(memoryWrite),
cmocka_unit_test(logging),
cmocka_unit_test(screenshot),
#ifdef ENABLE_DEBUGGERS
#ifdef M_CORE_GBA
cmocka_unit_test(basicBreakpointGBA),
#endif
#ifdef M_CORE_GB
cmocka_unit_test(basicBreakpointGB),
#endif
cmocka_unit_test(multipleBreakpoint),
cmocka_unit_test(basicWatchpoint),
cmocka_unit_test(watchpointReentrant),
cmocka_unit_test(removeBreakpoint),
cmocka_unit_test(overlappingBreakpoint),
cmocka_unit_test(overlappingWatchpoint),
cmocka_unit_test(rangeWatchpoint),
#endif
)