mirror of
https://github.com/rh-hideout/pokeemerald-expansion.git
synced 2026-04-26 02:14:22 -05:00
assertf: Formatted asserts
assertf's behavior depends on the build: - In release builds it executes recovery code. - In debug builds it shows a crash screen. When start is pressed it resumes and executes the recovery code. - In test builds it fails the test with an INVALID result.
This commit is contained in:
parent
0a5da344e3
commit
cc8c8bd668
|
|
@ -191,6 +191,20 @@ else
|
|||
}
|
||||
```
|
||||
|
||||
The exception is `assertf` which should always use braces if it has a recovery path, even for one line of code.
|
||||
|
||||
```c
|
||||
assertf(true); // correct
|
||||
|
||||
assertf(true) // correct
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assertf(true) // incorrect
|
||||
return NULL;
|
||||
```
|
||||
|
||||
### Control Structures
|
||||
|
||||
When comparing whether or not a value equals `0`, don't be explicit unless the
|
||||
|
|
|
|||
BIN
graphics/crash_screen/font.png
Normal file
BIN
graphics/crash_screen/font.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 275 B |
5
graphics/crash_screen/palette.pal
Normal file
5
graphics/crash_screen/palette.pal
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
JASC-PAL
|
||||
0100
|
||||
2
|
||||
0 0 255
|
||||
255 255 255
|
||||
62
include/assertf.h
Normal file
62
include/assertf.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#ifndef ASSERTF_H
|
||||
#define ASSERTF_H
|
||||
|
||||
/* Formatted assert.
|
||||
*
|
||||
* Asserts are a way to catch programmer errors at run-time. They should
|
||||
* be used when both of the following are true:
|
||||
* 1. It's impossible to catch the error at compile-time.
|
||||
* 2. The error is caused only by the programmer.
|
||||
* For example:
|
||||
* - removeobject for a local ID that isn't in the object event
|
||||
* templates is a programmer error because the object could never be
|
||||
* spawned.
|
||||
* - removeobject for a local ID that isn't spawned is not (necessarily)
|
||||
* a programmer error because the object could have been despawned by
|
||||
* the player.
|
||||
* - Trying to choose a move from the Fight menu when it's disabled is
|
||||
* not a programmer error because the player is able to try to choose
|
||||
* the move.
|
||||
* - The battle engine receiving a disabled move as the chosen move is
|
||||
* a programmer error because it should have been rejected by the UI.
|
||||
*
|
||||
* When possible, prefer to catch errors at compile-time with things
|
||||
* like STATIC_ASSERT rather than at run-time with assertf.
|
||||
*
|
||||
* assertf(cond);
|
||||
* assertf(cond) { recovery... }
|
||||
* assertf(cond, fmt, ...);
|
||||
* assertf(cond, fmt, ...) { recovery... }
|
||||
*
|
||||
* If cond is FALSE:
|
||||
* - In a release build: executes the recovery code (if any).
|
||||
* - In a debug build: shows a resumable crash screen and executes the
|
||||
* recovery code (if any).
|
||||
* - In a test build: causes the test to be INVALID.
|
||||
*
|
||||
* Usually the recovery code makes the function do nothing, for example:
|
||||
* - warp to a map that doesn't exist shouldn't warp anywhere.
|
||||
* - addobject of a local ID that doesn't exist shouldn't add anything.
|
||||
*
|
||||
* But sometimes the function has to return something, in which case the
|
||||
* recovery code does something that seems "reasonable", for example:
|
||||
* - CreateMonWithGenderNatureLetter should ignore an illegal gender or
|
||||
* letter. */
|
||||
#define assertf(cond, ...) CAT(_ASSERTF, FIRST(__VA_OPT__(_FMT,) _COND))(cond __VA_OPT__(,) __VA_ARGS__)
|
||||
|
||||
#define _ASSERTF_COND(cond) for (bool32 _recover = !(cond); _recover && (_ASSERTF_HANDLE("%s:%d: %s", __FILE__, __LINE__, STR(cond)), TRUE); _recover = FALSE)
|
||||
|
||||
#define _ASSERTF_FMT(cond, fmt, ...) for (bool32 _recover = !(cond); _recover && (_ASSERTF_HANDLE("%s:%d: " fmt, __FILE__, __LINE__ __VA_OPT__(,) __VA_ARGS__), TRUE); _recover = FALSE)
|
||||
|
||||
#if RELEASE
|
||||
#define _ASSERTF_HANDLE(...) 0
|
||||
#elif TESTING
|
||||
#include "test_result.h"
|
||||
#define _ASSERTF_HANDLE(fmt, ...) Test_ExitWithResult(TEST_RESULT_INVALID, 0, fmt, __VA_ARGS__)
|
||||
#else
|
||||
#define _ASSERTF_HANDLE(fmt, ...) AssertfCrashScreen(__builtin_return_address(0), fmt, __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
void AssertfCrashScreen(const void *return0, const char *fmt, ...);
|
||||
|
||||
#endif
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
#include <limits.h>
|
||||
#include "config/general.h" // we need to define config before gba headers as print stuff needs the functions nulled before defines.
|
||||
#include "gba/gba.h"
|
||||
#include "assertf.h"
|
||||
#include "gametypes.h"
|
||||
#include "siirtc.h"
|
||||
#include "fpmath.h"
|
||||
|
|
|
|||
|
|
@ -1,25 +1,13 @@
|
|||
#ifndef GUARD_TEST_H
|
||||
#define GUARD_TEST_H
|
||||
|
||||
#include "test_result.h"
|
||||
#include "test_runner.h"
|
||||
#include "random.h"
|
||||
|
||||
#define MAX_PROCESSES 32 // See also tools/mgba-rom-test-hydra/main.c
|
||||
#define RIGGED_RNG_COUNT 8
|
||||
|
||||
enum TestResult
|
||||
{
|
||||
TEST_RESULT_FAIL,
|
||||
TEST_RESULT_PASS,
|
||||
TEST_RESULT_ASSUMPTION_FAIL,
|
||||
TEST_RESULT_INVALID,
|
||||
TEST_RESULT_ERROR,
|
||||
TEST_RESULT_TIMEOUT,
|
||||
TEST_RESULT_CRASH,
|
||||
TEST_RESULT_TODO,
|
||||
TEST_RESULT_KNOWN_FAIL,
|
||||
};
|
||||
|
||||
struct TestRunner
|
||||
{
|
||||
u32 (*estimateCost)(void *);
|
||||
|
|
@ -106,7 +94,6 @@ void CB2_TestRunner(void);
|
|||
void Test_ExpectedResult(enum TestResult);
|
||||
void Test_ExpectLeaks(bool32);
|
||||
void Test_ExpectCrash(bool32);
|
||||
void Test_ExitWithResult(enum TestResult, u32 stopLine, const char *fmt, ...);
|
||||
u32 SourceLine(u32 sourceLineOffset);
|
||||
u32 SourceLineOffset(u32 sourceLine);
|
||||
void SetupRiggedRng(u32 sourceLine, enum RandomTag randomTag, u32 value);
|
||||
|
|
|
|||
20
include/test_result.h
Normal file
20
include/test_result.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef GUARD_TEST_RESULT_H
|
||||
#define GUARD_TEST_RESULT_H
|
||||
|
||||
enum TestResult
|
||||
{
|
||||
TEST_RESULT_FAIL,
|
||||
TEST_RESULT_PASS,
|
||||
TEST_RESULT_ASSUMPTION_FAIL,
|
||||
TEST_RESULT_INVALID,
|
||||
TEST_RESULT_ERROR,
|
||||
TEST_RESULT_TIMEOUT,
|
||||
TEST_RESULT_CRASH,
|
||||
TEST_RESULT_TODO,
|
||||
TEST_RESULT_KNOWN_FAIL,
|
||||
};
|
||||
|
||||
void Test_ExitWithResult_(enum TestResult, u32 stopLine, const void *return0, const char *fmt, ...);
|
||||
#define Test_ExitWithResult(result, stopLine, ...) Test_ExitWithResult_(result, stopLine, __builtin_return_address(0), __VA_ARGS__)
|
||||
|
||||
#endif
|
||||
401
src/assertf.c
Normal file
401
src/assertf.c
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
#include <alloca.h>
|
||||
#include <stdarg.h>
|
||||
#include "global.h"
|
||||
#include "bg.h"
|
||||
#include "main.h"
|
||||
#include "malloc.h"
|
||||
#include "m4a.h"
|
||||
#include "constants/characters.h"
|
||||
#include "constants/rgb.h"
|
||||
|
||||
struct BitUnPackArgs
|
||||
{
|
||||
u16 compressedSize;
|
||||
u8 compressedBits;
|
||||
u8 decompressedBits;
|
||||
u32 dataOffset:31;
|
||||
u32 offsetZeros:1;
|
||||
};
|
||||
|
||||
extern void BitUnPack(const void *src, void *dest, const struct BitUnPackArgs *);
|
||||
|
||||
static const u16 sPltt[2] = INCBIN_U16("graphics/crash_screen/palette.gbapal");
|
||||
static const u32 sGlyphs1BPP[] = INCBIN_U32("graphics/crash_screen/font.1bpp");
|
||||
|
||||
enum
|
||||
{
|
||||
GLYPH_SPACE,
|
||||
GLYPH_UNDERSCORE,
|
||||
GLYPH_PERIOD,
|
||||
GLYPH_COLON,
|
||||
GLYPH_SLASH,
|
||||
GLYPH_A,
|
||||
GLYPH_0 = GLYPH_A + 26,
|
||||
};
|
||||
|
||||
static const struct BitUnPackArgs sBitUnPack1BPP =
|
||||
{
|
||||
.compressedSize = sizeof(sGlyphs1BPP),
|
||||
.compressedBits = 1,
|
||||
.decompressedBits = 4,
|
||||
.dataOffset = 0,
|
||||
.offsetZeros = FALSE,
|
||||
};
|
||||
|
||||
#define TILE0_OFFSET ((32 * 20 * sizeof(u16)) / TILE_SIZE_4BPP)
|
||||
|
||||
static bool32 Putc(u32 *x, u32 *y, char c)
|
||||
{
|
||||
if (c == '\n')
|
||||
goto newline;
|
||||
|
||||
u32 glyph;
|
||||
if ('a' <= c && c <= 'z')
|
||||
glyph = GLYPH_A + c - 'a';
|
||||
else if ('A' <= c && c <= 'Z')
|
||||
glyph = GLYPH_A + c - 'A';
|
||||
else if ('0' <= c && c <= '9')
|
||||
glyph = GLYPH_0 + c - '0';
|
||||
else if (c == ' ')
|
||||
glyph = GLYPH_SPACE;
|
||||
else if (c == '.')
|
||||
glyph = GLYPH_PERIOD;
|
||||
else if (c == ':')
|
||||
glyph = GLYPH_COLON;
|
||||
else if (c == '/')
|
||||
glyph = GLYPH_SLASH;
|
||||
else
|
||||
glyph = GLYPH_UNDERSCORE;
|
||||
((vu16 *)VRAM)[*x + *y * 32] = TILE0_OFFSET + glyph;
|
||||
|
||||
*x += 1;
|
||||
if (*x == 30)
|
||||
{
|
||||
newline:
|
||||
*x = 0;
|
||||
*y += 1;
|
||||
}
|
||||
|
||||
return *y < 18;
|
||||
}
|
||||
|
||||
static bool32 Puti(u32 *x, u32 *y, s32 i)
|
||||
{
|
||||
if (i < 0)
|
||||
{
|
||||
if (!Putc(x, y, '-'))
|
||||
return FALSE;
|
||||
return Puti(x, y, -i);
|
||||
}
|
||||
else if (i == 0)
|
||||
{
|
||||
return Putc(x, y, '0');
|
||||
}
|
||||
|
||||
u8 digits[9]; // floor(log10(INT_MAX))
|
||||
|
||||
u32 n = 0;
|
||||
while (i)
|
||||
{
|
||||
digits[n++] = i % 10;
|
||||
i /= 10;
|
||||
}
|
||||
|
||||
while (n > 0)
|
||||
{
|
||||
n--;
|
||||
if (!Putc(x, y, '0' + digits[n]))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static bool32 Putp(u32 *x, u32 *y, const void *p)
|
||||
{
|
||||
uintptr_t address = (uintptr_t)p;
|
||||
u8 digits[8];
|
||||
|
||||
for (u32 n = 0; n < 8; n++)
|
||||
{
|
||||
digits[n] = address % 16;
|
||||
address /= 16;
|
||||
}
|
||||
|
||||
for (u32 n = 0; n < 8; n++)
|
||||
{
|
||||
u32 d = digits[7 - n];
|
||||
|
||||
// Most addresses are 7-digit, so elide the 8th if it's zero.
|
||||
if (n == 0 && d == 0)
|
||||
continue;
|
||||
|
||||
if (0 <= d && d <= 9)
|
||||
{
|
||||
if (!Putc(x, y, '0' + d))
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Putc(x, y, 'A' + d - 10))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static bool32 Puts(u32 *x, u32 *y, const char *s)
|
||||
{
|
||||
while (*s != '\0')
|
||||
{
|
||||
if (!Putc(x, y, *s++))
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static bool32 PutS(u32 *x, u32 *y, const u8 *s)
|
||||
{
|
||||
while (*s != EOS)
|
||||
{
|
||||
char c;
|
||||
if (CHAR_a <= *s && *s <= CHAR_z)
|
||||
c = 'A' + *s - CHAR_a;
|
||||
else if (CHAR_A <= *s && *s <= CHAR_Z)
|
||||
c = 'A' + *s - CHAR_A;
|
||||
else if (CHAR_0 <= *s && *s <= CHAR_9)
|
||||
c = '0' + *s - CHAR_0;
|
||||
else if (*s == ' ')
|
||||
c = ' ';
|
||||
else if (*s == '.')
|
||||
c = '.';
|
||||
else if (*s == ':')
|
||||
c = ':';
|
||||
else
|
||||
c = '_';
|
||||
|
||||
if (!Putc(x, y, c))
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static bool32 Putx(u32 *x, u32 *y, unsigned u)
|
||||
{
|
||||
u8 digits[8];
|
||||
|
||||
u32 n = 0;
|
||||
while (u)
|
||||
{
|
||||
digits[n++] = u % 16;
|
||||
u /= 16;
|
||||
}
|
||||
|
||||
while (n > 0)
|
||||
{
|
||||
n--;
|
||||
if (0 <= digits[n] && digits[n] <= 9)
|
||||
{
|
||||
if (!Putc(x, y, '0' + digits[n]))
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Putc(x, y, 'A' + digits[n] - 10))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// This printf renders directly into VRAM rather than into a buffer.
|
||||
static void Vprintf(const void *return1, const void *return0, const char *fmt, va_list va)
|
||||
{
|
||||
u32 x, y;
|
||||
|
||||
x = 3;
|
||||
y = 19;
|
||||
static const char footer[] = "Press START to continue.";
|
||||
for (u32 i = 0; i < sizeof(footer) - 1; i++)
|
||||
Putc(&x, &y, footer[i]);
|
||||
|
||||
x = 0;
|
||||
y = 0;
|
||||
while (TRUE)
|
||||
{
|
||||
char c = *fmt++;
|
||||
if (c == '\0')
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (c == '%')
|
||||
{
|
||||
char f = *fmt++;
|
||||
switch (f)
|
||||
{
|
||||
case 'd':
|
||||
if (!Puti(&x, &y, va_arg(va, int)))
|
||||
return;
|
||||
break;
|
||||
case 'p':
|
||||
if (!Putp(&x, &y, va_arg(va, const void *)))
|
||||
return;
|
||||
break;
|
||||
case 's':
|
||||
if (!Puts(&x, &y, va_arg(va, const char *)))
|
||||
return;
|
||||
break;
|
||||
case 'S':
|
||||
if (!PutS(&x, &y, va_arg(va, const u8 *)))
|
||||
return;
|
||||
break;
|
||||
case 'x':
|
||||
if (!Putx(&x, &y, va_arg(va, unsigned)))
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Putc(&x, &y, c))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Puts(&x, &y, "\n in: "))
|
||||
return;
|
||||
if (!Putp(&x, &y, return1))
|
||||
return;
|
||||
if (!Puts(&x, &y, "\n in: "))
|
||||
return;
|
||||
if (!Putp(&x, &y, return0))
|
||||
return;
|
||||
}
|
||||
|
||||
static void BusyWaitForVBlank(void)
|
||||
{
|
||||
// Interrupts are disabled so we have to busy loop to wait for
|
||||
// v-blanks.
|
||||
while (REG_VCOUNT < 160)
|
||||
;
|
||||
}
|
||||
|
||||
struct Backup
|
||||
{
|
||||
bool8 onHeap;
|
||||
u8 ime;
|
||||
u16 soundcnt_l;
|
||||
u16 soundcnt_h;
|
||||
u16 dispcnt;
|
||||
u16 bg0cnt;
|
||||
u16 bgPltt[2];
|
||||
u8 vram[32 * 20 * sizeof(u16) + TILE_SIZE_4BPP + 4 * sizeof(sGlyphs1BPP)];
|
||||
};
|
||||
|
||||
/* Blue Screen of Death style screen that displays the error message and
|
||||
* hijacks the main loop until the start button is pressed. */
|
||||
void AssertfCrashScreen(const void *return1, const char *fmt, ...)
|
||||
{
|
||||
// Backup and override hardware state.
|
||||
struct Backup *backup = NULL;
|
||||
|
||||
// Allocate on heap if possible.
|
||||
if (!backup)
|
||||
{
|
||||
backup = Alloc(sizeof(*backup));
|
||||
if (backup)
|
||||
backup->onHeap = TRUE;
|
||||
}
|
||||
|
||||
// Allocate on stack if possible.
|
||||
if (!backup)
|
||||
{
|
||||
extern char __iwram_end[];
|
||||
size_t stack_free = (char *)__builtin_frame_address(0) - __iwram_end;
|
||||
if (stack_free > sizeof(*backup) + 128)
|
||||
{
|
||||
backup = alloca(sizeof(*backup));
|
||||
backup->onHeap = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!backup)
|
||||
{
|
||||
// TODO: What to do?
|
||||
return;
|
||||
}
|
||||
|
||||
backup->ime = REG_IME;
|
||||
REG_IME = 0;
|
||||
m4aMPlayStop(&gMPlayInfo_BGM);
|
||||
m4aMPlayStop(&gMPlayInfo_SE1);
|
||||
m4aMPlayStop(&gMPlayInfo_SE2);
|
||||
m4aMPlayStop(&gMPlayInfo_SE3);
|
||||
backup->soundcnt_l = REG_SOUNDCNT_L;
|
||||
REG_SOUNDCNT_L = 0;
|
||||
backup->soundcnt_h = REG_SOUNDCNT_H;
|
||||
REG_SOUNDCNT_H = REG_SOUNDCNT_H & ~(SOUND_A_RIGHT_OUTPUT | SOUND_A_LEFT_OUTPUT | SOUND_B_RIGHT_OUTPUT | SOUND_B_LEFT_OUTPUT);
|
||||
backup->dispcnt = REG_DISPCNT;
|
||||
REG_DISPCNT = 0;
|
||||
backup->bg0cnt = REG_BG0CNT;
|
||||
REG_BG0CNT = BGCNT_CHARBASE(0) | BGCNT_16COLOR | BGCNT_SCREENBASE(0) | BGCNT_TXT256x256;
|
||||
REG_BG0HOFS = 0; // NOTE: REG_BG0HOFS is write-only.
|
||||
REG_BG0VOFS = 0; // NOTE: REG_BG0VOFS is write-only.
|
||||
memcpy(backup->bgPltt, (void *)BG_PLTT, sizeof(backup->bgPltt));
|
||||
memcpy((void *)BG_PLTT, sPltt, sizeof(backup->bgPltt));
|
||||
CpuFastCopy((void *)VRAM, &backup->vram, sizeof(backup->vram));
|
||||
for (u32 i = 0; i < 32 * 20; i++)
|
||||
((vu16 *)VRAM)[i] = TILE0_OFFSET;
|
||||
BitUnPack(sGlyphs1BPP, (void *)(VRAM + TILE_OFFSET_4BPP(TILE0_OFFSET)), &sBitUnPack1BPP);
|
||||
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
Vprintf(return1, __builtin_return_address(0), fmt, va);
|
||||
va_end(va);
|
||||
|
||||
BusyWaitForVBlank();
|
||||
REG_DISPCNT = DISPCNT_MODE_0 | DISPCNT_BG0_ON;
|
||||
|
||||
u16 prevKeyinput = ~REG_KEYINPUT;
|
||||
enum { WAIT_FOR_PRESS, WAIT_FOR_RELEASE } state = WAIT_FOR_PRESS;
|
||||
while (TRUE)
|
||||
{
|
||||
BusyWaitForVBlank();
|
||||
|
||||
// Exit when start is pressed.
|
||||
u16 keyinput = ~REG_KEYINPUT;
|
||||
if (state == WAIT_FOR_PRESS)
|
||||
{
|
||||
if (!(prevKeyinput & START_BUTTON) && (keyinput & START_BUTTON))
|
||||
state = WAIT_FOR_RELEASE;
|
||||
}
|
||||
else if (state == WAIT_FOR_RELEASE)
|
||||
{
|
||||
if ((prevKeyinput & START_BUTTON) && !(keyinput & START_BUTTON))
|
||||
break;
|
||||
}
|
||||
prevKeyinput = keyinput;
|
||||
}
|
||||
|
||||
// Restore backup.
|
||||
REG_DISPCNT = 0;
|
||||
CpuFastCopy(&backup->vram, (void *)VRAM, sizeof(backup->vram));
|
||||
memcpy((void *)BG_PLTT, backup->bgPltt, sizeof(backup->bgPltt));
|
||||
// Best-effort, restore BG0HOFS/BG0VOFS from one of GF's caches.
|
||||
REG_BG0VOFS = GetBgY(0);
|
||||
REG_BG0HOFS = GetBgX(0);
|
||||
REG_BG0CNT = backup->bg0cnt;
|
||||
REG_DISPCNT = backup->dispcnt;
|
||||
REG_SOUNDCNT_H = backup->soundcnt_h;
|
||||
REG_SOUNDCNT_L = backup->soundcnt_l;
|
||||
m4aMPlayContinue(&gMPlayInfo_SE3);
|
||||
m4aMPlayContinue(&gMPlayInfo_SE2);
|
||||
m4aMPlayContinue(&gMPlayInfo_SE1);
|
||||
m4aMPlayContinue(&gMPlayInfo_BGM);
|
||||
REG_IME = backup->ime;
|
||||
|
||||
if (backup->onHeap)
|
||||
Free(backup);
|
||||
}
|
||||
|
|
@ -654,7 +654,7 @@ static void Intr_Timer2(void)
|
|||
}
|
||||
}
|
||||
|
||||
void Test_ExitWithResult(enum TestResult result, u32 stopLine, const char *fmt, ...)
|
||||
void Test_ExitWithResult_(enum TestResult result, u32 stopLine, const void *return1, const char *fmt, ...)
|
||||
{
|
||||
gTestRunnerState.result = result;
|
||||
gTestRunnerState.failedAssumptionsBlockLine = stopLine;
|
||||
|
|
@ -665,6 +665,11 @@ void Test_ExitWithResult(enum TestResult result, u32 stopLine, const char *fmt,
|
|||
if (!gTestRunnerState.test->runner->handleExitWithResult
|
||||
|| !gTestRunnerState.test->runner->handleExitWithResult(gTestRunnerState.test->data, result))
|
||||
{
|
||||
if (result == TEST_RESULT_INVALID)
|
||||
{
|
||||
const void *return0 = __builtin_return_address(0);
|
||||
Test_MgbaPrintf("in %p\nin %p", return1, return0);
|
||||
}
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
MgbaVPrintf_(fmt, va);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user