2022-12-24 05:13:42 +00:00
|
|
|
#ifndef GUARD_TEST_H
|
|
|
|
#define GUARD_TEST_H
|
|
|
|
|
|
|
|
#include "test_runner.h"
|
|
|
|
|
|
|
|
#define MAX_PROCESSES 32 // See also tools/mgba-rom-test-hydra/main.c
|
|
|
|
|
|
|
|
enum TestResult
|
|
|
|
{
|
|
|
|
TEST_RESULT_FAIL,
|
|
|
|
TEST_RESULT_PASS,
|
2023-03-08 11:26:33 +00:00
|
|
|
TEST_RESULT_ASSUMPTION_FAIL,
|
2022-12-24 05:13:42 +00:00
|
|
|
TEST_RESULT_INVALID,
|
|
|
|
TEST_RESULT_ERROR,
|
|
|
|
TEST_RESULT_TIMEOUT,
|
2023-07-14 09:50:55 +01:00
|
|
|
TEST_RESULT_CRASH,
|
2023-03-24 01:34:08 +00:00
|
|
|
TEST_RESULT_TODO,
|
2022-12-24 05:13:42 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct TestRunner
|
|
|
|
{
|
|
|
|
u32 (*estimateCost)(void *);
|
|
|
|
void (*setUp)(void *);
|
|
|
|
void (*run)(void *);
|
|
|
|
void (*tearDown)(void *);
|
|
|
|
bool32 (*checkProgress)(void *);
|
|
|
|
bool32 (*handleExitWithResult)(void *, enum TestResult);
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Test
|
|
|
|
{
|
|
|
|
const char *name;
|
|
|
|
const char *filename;
|
|
|
|
const struct TestRunner *runner;
|
|
|
|
void *data;
|
2024-07-20 17:22:40 +01:00
|
|
|
u16 sourceLine;
|
2022-12-24 05:13:42 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct TestRunnerState
|
|
|
|
{
|
|
|
|
u8 state;
|
|
|
|
u8 exitCode;
|
|
|
|
const char *skipFilename;
|
2024-07-20 17:22:40 +01:00
|
|
|
u32 failedAssumptionsBlockLine;
|
2022-12-24 05:13:42 +00:00
|
|
|
const struct Test *test;
|
|
|
|
u32 processCosts[MAX_PROCESSES];
|
|
|
|
|
|
|
|
u8 result;
|
|
|
|
u8 expectedResult;
|
2023-04-20 20:35:22 +01:00
|
|
|
bool8 expectLeaks:1;
|
2023-07-24 12:51:43 +01:00
|
|
|
bool8 inBenchmark:1;
|
2023-10-23 06:57:57 +01:00
|
|
|
bool8 tearDown:1;
|
2022-12-24 05:13:42 +00:00
|
|
|
u32 timeoutSeconds;
|
|
|
|
};
|
|
|
|
|
|
|
|
extern const u8 gTestRunnerN;
|
|
|
|
extern const u8 gTestRunnerI;
|
|
|
|
extern const char gTestRunnerArgv[256];
|
|
|
|
|
|
|
|
extern const struct TestRunner gAssumptionsRunner;
|
2023-02-03 11:21:08 +00:00
|
|
|
|
|
|
|
struct FunctionTestRunnerState
|
|
|
|
{
|
2023-12-27 16:48:17 +00:00
|
|
|
u16 parameters;
|
|
|
|
u16 runParameter;
|
|
|
|
u16 checkProgressParameter;
|
2023-02-03 11:21:08 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
extern const struct TestRunner gFunctionTestRunner;
|
|
|
|
extern struct FunctionTestRunnerState *gFunctionTestRunnerState;
|
|
|
|
|
2022-12-24 05:13:42 +00:00
|
|
|
extern struct TestRunnerState gTestRunnerState;
|
|
|
|
|
|
|
|
void CB2_TestRunner(void);
|
|
|
|
|
|
|
|
void Test_ExpectedResult(enum TestResult);
|
2023-04-20 20:35:22 +01:00
|
|
|
void Test_ExpectLeaks(bool32);
|
2024-07-20 17:22:40 +01:00
|
|
|
void Test_ExitWithResult(enum TestResult, u32 stopLine, const char *fmt, ...);
|
|
|
|
u32 SourceLine(u32 sourceLineOffset);
|
|
|
|
u32 SourceLineOffset(u32 sourceLine);
|
2022-12-24 05:13:42 +00:00
|
|
|
|
2024-05-31 16:54:25 +01:00
|
|
|
s32 Test_MgbaPrintf(const char *fmt, ...);
|
2022-12-24 05:13:42 +00:00
|
|
|
|
2023-02-03 11:21:08 +00:00
|
|
|
#define TEST(_name) \
|
|
|
|
static void CAT(Test, __LINE__)(void); \
|
2024-10-25 12:05:19 +01:00
|
|
|
__attribute__((section(".tests"), used)) static const struct Test CAT(sTest, __LINE__) = \
|
2023-02-03 11:21:08 +00:00
|
|
|
{ \
|
|
|
|
.name = _name, \
|
|
|
|
.filename = __FILE__, \
|
|
|
|
.runner = &gFunctionTestRunner, \
|
2024-07-20 17:22:40 +01:00
|
|
|
.sourceLine = __LINE__, \
|
2023-02-03 11:21:08 +00:00
|
|
|
.data = (void *)CAT(Test, __LINE__), \
|
|
|
|
}; \
|
|
|
|
static void CAT(Test, __LINE__)(void)
|
|
|
|
|
2022-12-24 05:13:42 +00:00
|
|
|
#define ASSUMPTIONS \
|
|
|
|
static void Assumptions(void); \
|
2024-10-25 12:05:19 +01:00
|
|
|
__attribute__((section(".tests"), used)) static const struct Test sAssumptions = \
|
2022-12-24 05:13:42 +00:00
|
|
|
{ \
|
|
|
|
.name = "ASSUMPTIONS: " __FILE__, \
|
|
|
|
.filename = __FILE__, \
|
|
|
|
.runner = &gAssumptionsRunner, \
|
2024-07-20 17:22:40 +01:00
|
|
|
.sourceLine = __LINE__, \
|
2022-12-24 05:13:42 +00:00
|
|
|
.data = Assumptions, \
|
|
|
|
}; \
|
|
|
|
static void Assumptions(void)
|
|
|
|
|
|
|
|
#define ASSUME(c) \
|
|
|
|
do \
|
|
|
|
{ \
|
|
|
|
if (!(c)) \
|
2024-07-20 17:22:40 +01:00
|
|
|
Test_ExitWithResult(TEST_RESULT_ASSUMPTION_FAIL, __LINE__, ":L%s:%d: ASSUME failed", gTestRunnerState.test->filename, __LINE__); \
|
2022-12-24 05:13:42 +00:00
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define EXPECT(c) \
|
|
|
|
do \
|
|
|
|
{ \
|
|
|
|
if (!(c)) \
|
2024-07-20 17:22:40 +01:00
|
|
|
Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT failed", gTestRunnerState.test->filename, __LINE__); \
|
2022-12-24 05:13:42 +00:00
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define EXPECT_EQ(a, b) \
|
|
|
|
do \
|
|
|
|
{ \
|
|
|
|
typeof(a) _a = (a), _b = (b); \
|
|
|
|
if (_a != _b) \
|
2024-07-20 17:22:40 +01:00
|
|
|
Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT_EQ(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \
|
2022-12-24 05:13:42 +00:00
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define EXPECT_NE(a, b) \
|
|
|
|
do \
|
|
|
|
{ \
|
|
|
|
typeof(a) _a = (a), _b = (b); \
|
|
|
|
if (_a == _b) \
|
2024-07-20 17:22:40 +01:00
|
|
|
Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT_NE(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \
|
2022-12-24 05:13:42 +00:00
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define EXPECT_LT(a, b) \
|
|
|
|
do \
|
|
|
|
{ \
|
|
|
|
typeof(a) _a = (a), _b = (b); \
|
|
|
|
if (_a >= _b) \
|
2024-07-20 17:22:40 +01:00
|
|
|
Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT_LT(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \
|
2022-12-24 05:13:42 +00:00
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define EXPECT_LE(a, b) \
|
|
|
|
do \
|
|
|
|
{ \
|
|
|
|
typeof(a) _a = (a), _b = (b); \
|
|
|
|
if (_a > _b) \
|
2024-07-20 17:22:40 +01:00
|
|
|
Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT_LE(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \
|
2022-12-24 05:13:42 +00:00
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define EXPECT_GT(a, b) \
|
|
|
|
do \
|
|
|
|
{ \
|
|
|
|
typeof(a) _a = (a), _b = (b); \
|
|
|
|
if (_a <= _b) \
|
2024-07-20 17:22:40 +01:00
|
|
|
Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT_GT(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \
|
2022-12-24 05:13:42 +00:00
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define EXPECT_GE(a, b) \
|
|
|
|
do \
|
|
|
|
{ \
|
|
|
|
typeof(a) _a = (a), _b = (b); \
|
|
|
|
if (_a < _b) \
|
2024-07-20 17:22:40 +01:00
|
|
|
Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT_GE(%d, %d) failed", gTestRunnerState.test->filename, __LINE__, _a, _b); \
|
2022-12-24 05:13:42 +00:00
|
|
|
} while (0)
|
|
|
|
|
2023-07-25 07:40:51 +01:00
|
|
|
struct Benchmark { s32 ticks; };
|
2023-07-24 12:51:43 +01:00
|
|
|
|
|
|
|
static inline void BenchmarkStart(void)
|
|
|
|
{
|
|
|
|
gTestRunnerState.inBenchmark = TRUE;
|
2023-12-30 10:10:40 +00:00
|
|
|
// Wait for a v-blank so that comparing two benchmarks is not affected
|
|
|
|
// by the v-count (different numbers of IRQs may run).
|
|
|
|
VBlankIntrWait();
|
2023-07-24 12:51:43 +01:00
|
|
|
REG_TM3CNT = (TIMER_ENABLE | TIMER_64CLK) << 16;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline struct Benchmark BenchmarkStop(void)
|
|
|
|
{
|
|
|
|
REG_TM3CNT_H = 0;
|
|
|
|
gTestRunnerState.inBenchmark = FALSE;
|
|
|
|
return (struct Benchmark) { REG_TM3CNT_L };
|
|
|
|
}
|
|
|
|
|
|
|
|
#define BENCHMARK(id) \
|
|
|
|
for (BenchmarkStart(); gTestRunnerState.inBenchmark; *(id) = BenchmarkStop())
|
|
|
|
|
|
|
|
// An approximation of how much overhead benchmarks introduce.
|
|
|
|
#define BENCHMARK_ABS 2
|
|
|
|
|
|
|
|
// An approximation for what percentage faster a benchmark has to be for
|
|
|
|
// us to be confident that it's faster than another.
|
|
|
|
#define BENCHMARK_REL 95
|
|
|
|
|
|
|
|
#define EXPECT_FASTER(a, b) \
|
|
|
|
do \
|
|
|
|
{ \
|
|
|
|
u32 a_ = (a).ticks; u32 b_ = (b).ticks; \
|
2024-05-31 16:54:25 +01:00
|
|
|
Test_MgbaPrintf(#a ": %d ticks, " #b ": %d ticks", a_, b_); \
|
2023-07-24 12:51:43 +01:00
|
|
|
if (((a_ - BENCHMARK_ABS) * BENCHMARK_REL) >= (b_ * 100)) \
|
2024-07-20 17:22:40 +01:00
|
|
|
Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT_FASTER(" #a ", " #b ") failed", gTestRunnerState.test->filename, __LINE__); \
|
2023-07-24 12:51:43 +01:00
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define EXPECT_SLOWER(a, b) \
|
|
|
|
do \
|
|
|
|
{ \
|
|
|
|
u32 a_ = (a).ticks; u32 b_ = (b).ticks; \
|
2024-05-31 16:54:25 +01:00
|
|
|
Test_MgbaPrintf(#a ": %d ticks, " #b ": %d ticks", a_, b_); \
|
2023-07-24 12:51:43 +01:00
|
|
|
if ((a_ * 100) <= ((b_ - BENCHMARK_ABS) * BENCHMARK_REL)) \
|
2024-07-20 17:22:40 +01:00
|
|
|
Test_ExitWithResult(TEST_RESULT_FAIL, __LINE__, ":L%s:%d: EXPECT_SLOWER(" #a ", " #b ") failed", gTestRunnerState.test->filename, __LINE__); \
|
2023-07-24 12:51:43 +01:00
|
|
|
} while (0)
|
|
|
|
|
2022-12-24 05:13:42 +00:00
|
|
|
#define KNOWN_FAILING \
|
|
|
|
Test_ExpectedResult(TEST_RESULT_FAIL)
|
|
|
|
|
2023-04-20 20:35:22 +01:00
|
|
|
#define KNOWN_LEAKING \
|
|
|
|
Test_ExpectLeaks(TRUE)
|
|
|
|
|
2023-02-03 11:21:08 +00:00
|
|
|
#define PARAMETRIZE if (gFunctionTestRunnerState->parameters++ == gFunctionTestRunnerState->runParameter)
|
|
|
|
|
2024-05-31 16:54:25 +01:00
|
|
|
#define PARAMETRIZE_LABEL(f, label) if (gFunctionTestRunnerState->parameters++ == gFunctionTestRunnerState->runParameter && (Test_MgbaPrintf(":N%s: " f " (%d/%d)", gTestRunnerState.test->name, label, gFunctionTestRunnerState->runParameter + 1, gFunctionTestRunnerState->parameters), 1))
|
2024-03-05 13:57:39 +00:00
|
|
|
|
2023-03-24 01:34:08 +00:00
|
|
|
#define TO_DO \
|
2023-06-22 15:09:16 +01:00
|
|
|
do { \
|
|
|
|
Test_ExpectedResult(TEST_RESULT_TODO); \
|
2024-07-20 17:22:40 +01:00
|
|
|
Test_ExitWithResult(TEST_RESULT_TODO, __LINE__, ":L%s:%d: EXPECT_TO_DO", gTestRunnerState.test->filename, __LINE__); \
|
2023-06-22 15:09:16 +01:00
|
|
|
} while (0)
|
2023-03-24 01:34:08 +00:00
|
|
|
|
2022-12-24 05:13:42 +00:00
|
|
|
#endif
|