mirror of
https://github.com/Ninjdai1/pokeemerald.git
synced 2025-01-13 15:13:42 +01:00
Merge branch 'RHH/master' into RHH/upcoming
This commit is contained in:
commit
786bf7752f
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -4,9 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- battle_engine
|
||||
- pokemon_expansion
|
||||
- item_expansion
|
||||
- upcoming
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
@ -45,6 +45,13 @@ SECTIONS {
|
||||
test/*.o(COMMON);
|
||||
*libc.a:sbrkr.o(COMMON);
|
||||
end = .;
|
||||
|
||||
/* .persistent starts at 0x3007F00 */
|
||||
/* WARNING: This is the end of the IRQ stack, if there's too
|
||||
* much data it WILL be overwritten. */
|
||||
. = 0x7F00;
|
||||
test/*.o(.persistent);
|
||||
|
||||
. = 0x8000;
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ enum TestResult
|
||||
TEST_RESULT_INVALID,
|
||||
TEST_RESULT_ERROR,
|
||||
TEST_RESULT_TIMEOUT,
|
||||
TEST_RESULT_CRASH,
|
||||
TEST_RESULT_TODO,
|
||||
};
|
||||
|
||||
@ -38,8 +39,6 @@ struct TestRunnerState
|
||||
{
|
||||
u8 state;
|
||||
u8 exitCode;
|
||||
s32 tests;
|
||||
s32 passes;
|
||||
const char *skipFilename;
|
||||
const struct Test *test;
|
||||
u32 processCosts[MAX_PROCESSES];
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* Embedded DSL for automated black-box testing of battle mechanics.
|
||||
*
|
||||
* To run all the tests use:
|
||||
* make check
|
||||
* make check -j
|
||||
* To run specific tests, e.g. Spikes ones, use:
|
||||
* make check TESTS='Spikes'
|
||||
* To build a ROM (pokemerald-test.elf) that can be opened in mgba to
|
||||
|
@ -15,6 +15,16 @@ void CB2_TestRunner(void);
|
||||
EWRAM_DATA struct TestRunnerState gTestRunnerState;
|
||||
EWRAM_DATA struct FunctionTestRunnerState *gFunctionTestRunnerState;
|
||||
|
||||
enum {
|
||||
CURRENT_TEST_STATE_ESTIMATE,
|
||||
CURRENT_TEST_STATE_RUN,
|
||||
};
|
||||
|
||||
__attribute__((section(".persistent"))) static struct {
|
||||
u32 address:28;
|
||||
u32 state:1;
|
||||
} sCurrentTest = {0};
|
||||
|
||||
void TestRunner_Battle(const struct Test *);
|
||||
|
||||
static bool32 MgbaOpen_(void);
|
||||
@ -51,6 +61,47 @@ enum
|
||||
STATE_EXIT,
|
||||
};
|
||||
|
||||
static u32 MinCostProcess(void)
|
||||
{
|
||||
u32 i;
|
||||
u32 minCost, minCostProcess;
|
||||
|
||||
minCost = gTestRunnerState.processCosts[0];
|
||||
minCostProcess = 0;
|
||||
for (i = 1; i < gTestRunnerN; i++)
|
||||
{
|
||||
if (gTestRunnerState.processCosts[i] < minCost)
|
||||
{
|
||||
minCost = gTestRunnerState.processCosts[i];
|
||||
minCostProcess = i;
|
||||
}
|
||||
}
|
||||
|
||||
return minCostProcess;
|
||||
}
|
||||
|
||||
// Greedily assign tests to processes based on estimated cost.
|
||||
// TODO: Make processCosts a min heap.
|
||||
static u32 AssignCostToRunner(void)
|
||||
{
|
||||
u32 minCostProcess;
|
||||
|
||||
if (gTestRunnerState.test->runner == &gAssumptionsRunner)
|
||||
return TRUE;
|
||||
|
||||
minCostProcess = MinCostProcess();
|
||||
|
||||
// XXX: If estimateCost returns only on some processes, or
|
||||
// returns inconsistent results then processCosts will be
|
||||
// inconsistent and some tests may not run.
|
||||
if (gTestRunnerState.test->runner->estimateCost)
|
||||
gTestRunnerState.processCosts[minCostProcess] += gTestRunnerState.test->runner->estimateCost(gTestRunnerState.test->data);
|
||||
else
|
||||
gTestRunnerState.processCosts[minCostProcess] += 1;
|
||||
|
||||
return minCostProcess;
|
||||
}
|
||||
|
||||
void CB2_TestRunner(void)
|
||||
{
|
||||
switch (gTestRunnerState.state)
|
||||
@ -65,12 +116,43 @@ void CB2_TestRunner(void)
|
||||
|
||||
gIntrTable[7] = Intr_Timer2;
|
||||
|
||||
// The current test restarted the ROM (e.g. by jumping to NULL).
|
||||
if (sCurrentTest.address != 0)
|
||||
{
|
||||
gTestRunnerState.test = __start_tests;
|
||||
while ((uintptr_t)gTestRunnerState.test != sCurrentTest.address)
|
||||
{
|
||||
AssignCostToRunner();
|
||||
gTestRunnerState.test++;
|
||||
}
|
||||
if (sCurrentTest.state == CURRENT_TEST_STATE_ESTIMATE)
|
||||
{
|
||||
u32 runner = MinCostProcess();
|
||||
gTestRunnerState.processCosts[runner] += 1;
|
||||
if (runner == gTestRunnerI)
|
||||
{
|
||||
gTestRunnerState.state = STATE_REPORT_RESULT;
|
||||
gTestRunnerState.result = TEST_RESULT_CRASH;
|
||||
}
|
||||
else
|
||||
{
|
||||
gTestRunnerState.state = STATE_NEXT_TEST;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gTestRunnerState.state = STATE_REPORT_RESULT;
|
||||
gTestRunnerState.result = TEST_RESULT_CRASH;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gTestRunnerState.state = STATE_NEXT_TEST;
|
||||
gTestRunnerState.exitCode = 0;
|
||||
gTestRunnerState.tests = 0;
|
||||
gTestRunnerState.passes = 0;
|
||||
gTestRunnerState.skipFilename = NULL;
|
||||
gTestRunnerState.test = __start_tests - 1;
|
||||
}
|
||||
gTestRunnerState.exitCode = 0;
|
||||
gTestRunnerState.skipFilename = NULL;
|
||||
|
||||
break;
|
||||
|
||||
case STATE_NEXT_TEST:
|
||||
@ -108,40 +190,19 @@ void CB2_TestRunner(void)
|
||||
return;
|
||||
}
|
||||
|
||||
// Greedily assign tests to processes based on estimated cost.
|
||||
// TODO: Make processCosts a min heap.
|
||||
if (gTestRunnerState.test->runner != &gAssumptionsRunner)
|
||||
{
|
||||
u32 i;
|
||||
u32 minCost, minCostProcess;
|
||||
minCost = gTestRunnerState.processCosts[0];
|
||||
minCostProcess = 0;
|
||||
for (i = 1; i < gTestRunnerN; i++)
|
||||
{
|
||||
if (gTestRunnerState.processCosts[i] < minCost)
|
||||
{
|
||||
minCost = gTestRunnerState.processCosts[i];
|
||||
minCostProcess = i;
|
||||
}
|
||||
}
|
||||
sCurrentTest.address = (uintptr_t)gTestRunnerState.test;
|
||||
sCurrentTest.state = CURRENT_TEST_STATE_ESTIMATE;
|
||||
|
||||
if (minCostProcess == gTestRunnerI)
|
||||
if (AssignCostToRunner() == gTestRunnerI)
|
||||
gTestRunnerState.state = STATE_RUN_TEST;
|
||||
else
|
||||
gTestRunnerState.state = STATE_NEXT_TEST;
|
||||
|
||||
// XXX: If estimateCost exits only on some processes then
|
||||
// processCosts will be inconsistent.
|
||||
if (gTestRunnerState.test->runner->estimateCost)
|
||||
gTestRunnerState.processCosts[minCostProcess] += gTestRunnerState.test->runner->estimateCost(gTestRunnerState.test->data);
|
||||
else
|
||||
gTestRunnerState.processCosts[minCostProcess] += 1;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case STATE_RUN_TEST:
|
||||
gTestRunnerState.state = STATE_REPORT_RESULT;
|
||||
sCurrentTest.state = CURRENT_TEST_STATE_RUN;
|
||||
if (gTestRunnerState.test->runner->setUp)
|
||||
gTestRunnerState.test->runner->setUp(gTestRunnerState.test->data);
|
||||
gTestRunnerState.test->runner->run(gTestRunnerState.test->data);
|
||||
@ -162,6 +223,15 @@ void CB2_TestRunner(void)
|
||||
const struct MemBlock *block = head;
|
||||
do
|
||||
{
|
||||
if (block->magic != MALLOC_SYSTEM_ID
|
||||
|| !(EWRAM_START <= (uintptr_t)block->next && (uintptr_t)block->next < EWRAM_END)
|
||||
|| (block->next <= block && block->next != head))
|
||||
{
|
||||
MgbaPrintf_("gHeap corrupted block at 0x%p", block);
|
||||
gTestRunnerState.result = TEST_RESULT_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (block->allocated)
|
||||
{
|
||||
const char *location = MemBlockLocation(block);
|
||||
@ -186,11 +256,8 @@ void CB2_TestRunner(void)
|
||||
const char *color;
|
||||
const char *result;
|
||||
|
||||
gTestRunnerState.tests++;
|
||||
|
||||
if (gTestRunnerState.result == gTestRunnerState.expectedResult)
|
||||
{
|
||||
gTestRunnerState.passes++;
|
||||
color = "\e[32m";
|
||||
MgbaPrintf_(":N%s", gTestRunnerState.test->name);
|
||||
}
|
||||
@ -243,6 +310,9 @@ void CB2_TestRunner(void)
|
||||
case TEST_RESULT_TIMEOUT:
|
||||
result = "TIMEOUT";
|
||||
break;
|
||||
case TEST_RESULT_CRASH:
|
||||
result = "CRASH";
|
||||
break;
|
||||
default:
|
||||
result = "UNKNOWN";
|
||||
break;
|
||||
@ -434,6 +504,7 @@ static s32 MgbaVPrintf_(const char *fmt, va_list va)
|
||||
{
|
||||
s32 i = 0;
|
||||
s32 c, d;
|
||||
u32 p;
|
||||
const char *s;
|
||||
while (*fmt)
|
||||
{
|
||||
@ -467,6 +538,20 @@ static s32 MgbaVPrintf_(const char *fmt, va_list va)
|
||||
i = MgbaPutchar_(i, buffer[--n]);
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
p = va_arg(va, unsigned);
|
||||
{
|
||||
s32 n;
|
||||
for (n = 0; n < 7; n++)
|
||||
{
|
||||
unsigned nybble = (p >> (24 - (4*n))) & 0xF;
|
||||
if (nybble <= 9)
|
||||
i = MgbaPutchar_(i, '0' + nybble);
|
||||
else
|
||||
i = MgbaPutchar_(i, 'a' + nybble - 10);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'q':
|
||||
d = va_arg(va, int);
|
||||
{
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <fcntl.h>
|
||||
#include <math.h>
|
||||
#include <poll.h>
|
||||
#include <regex.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
@ -37,6 +38,8 @@
|
||||
#define MAX_FAILED_TESTS_TO_LIST 100
|
||||
#define MAX_TEST_LIST_BUFFER_LENGTH 256
|
||||
|
||||
#define ARRAY_COUNT(arr) (sizeof((arr)) / sizeof((arr)[0]))
|
||||
|
||||
struct Runner
|
||||
{
|
||||
pid_t pid;
|
||||
@ -248,7 +251,29 @@ int main(int argc, char *argv[])
|
||||
exit(2);
|
||||
}
|
||||
|
||||
nrunners = 1;
|
||||
const char *makeflags = getenv("MAKEFLAGS");
|
||||
if (makeflags)
|
||||
{
|
||||
int e;
|
||||
regex_t preg;
|
||||
regmatch_t pmatch[4];
|
||||
if ((e = regcomp(&preg, "(^| )-j([0-9]*)($| )", REG_EXTENDED)) != 0)
|
||||
{
|
||||
char errbuf[256];
|
||||
regerror(e, &preg, errbuf, sizeof(errbuf));
|
||||
fprintf(stderr, "regcomp failed: '%s'\n", errbuf);
|
||||
exit(2);
|
||||
}
|
||||
if (regexec(&preg, makeflags, ARRAY_COUNT(pmatch), pmatch, 0) != REG_NOMATCH)
|
||||
{
|
||||
if (pmatch[2].rm_so == pmatch[2].rm_eo)
|
||||
nrunners = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
else
|
||||
sscanf(makeflags + pmatch[2].rm_so, "%d", &nrunners);
|
||||
}
|
||||
regfree(&preg);
|
||||
}
|
||||
if (nrunners > MAX_PROCESSES)
|
||||
nrunners = MAX_PROCESSES;
|
||||
runners_digits = ceil(log10(nrunners));
|
||||
|
Loading…
x
Reference in New Issue
Block a user