/* re-bba-rb on-console test suite — GameCube * * Put the adapter in Serial Port 1, run this from Swiss. * * D-pad up/down move * A open suite / rerun the selected test * X run the selected suite * START run everything * Y clear all results * B back * Z self-update over the BBA, then relaunch (see netboot.c) */ #include #include #include #include #include #include #include "tests.h" #include "bba.h" #include "netboot.h" static void *xfb = NULL; static GXRModeObj *rmode = NULL; #define C_RESET "\x1b[37;0m" #define C_HEAD "\x1b[36;1m" #define C_SEL "\x1b[30;47m" /* black on white */ #define C_PASS "\x1b[32;1m" #define C_FAIL "\x1b[31;1m" #define C_SKIP "\x1b[33;1m" #define C_DIM "\x1b[37;0m" static const char *st_tag(tstatus_t s) { switch (s) { case T_PASS: return C_PASS "PASS" C_RESET; case T_FAIL: return C_FAIL "FAIL" C_RESET; case T_SKIP: return C_SKIP "SKIP" C_RESET; default: return C_DIM " -- " C_RESET; } } static void suite_tally(const suite_t *s, int *p, int *f, int *k) { int i; *p = *f = *k = 0; for (i = 0; i < s->ntests; i++) switch (s->tests[i].status) { case T_PASS: (*p)++; break; case T_FAIL: (*f)++; break; case T_SKIP: (*k)++; break; default: break; } } static void cls(void) { printf("\x1b[2J\x1b[1;1H"); } static void banner(void) { printf(C_HEAD " re-bba-rb -- on-console test suite" C_RESET "\n"); printf(C_DIM " GameCube Serial Port 1 (EXI0:2), clock index %u\n" C_RESET, (unsigned)bba_speed); printf(" ------------------------------------------------------------\n"); } static void draw_main(int sel, const char *status) { int i, p, f, k, tp, tf, tk, tpend; cls(); banner(); for (i = 0; i < g_nsuites; i++) { suite_tally(&g_suites[i], &p, &f, &k); printf("%s %-14s" C_RESET " %s%2d" C_RESET "/%s%2d" C_RESET "/%s%2d" C_RESET " %s%s\n", i == sel ? C_SEL : " ", g_suites[i].name, C_PASS, p, C_FAIL, f, C_SKIP, k, C_DIM, g_suites[i].what); } tests_tally(&tp, &tf, &tk, &tpend); printf(" ------------------------------------------------------------\n"); printf(" total " C_PASS "%d passed" C_RESET " " C_FAIL "%d failed" C_RESET " " C_SKIP "%d skipped" C_RESET " %d not run\n", tp, tf, tk, tpend); if (tf) { printf(C_FAIL " failing:" C_RESET); for (i = 0; i < g_nsuites; i++) { int j; for (j = 0; j < g_suites[i].ntests; j++) if (g_suites[i].tests[j].status == T_FAIL) printf(" %s/%s", g_suites[i].name, g_suites[i].tests[j].name); } printf("\n"); } printf("\n" C_DIM " A open X run suite START run all Y clear Z net-update\n" C_RESET); if (status && *status) printf("\n %s\n", status); } static void draw_suite(int si, int sel, const char *status) { suite_t *s = &g_suites[si]; int i, p, f, k; cls(); banner(); suite_tally(s, &p, &f, &k); printf(C_HEAD " %s" C_RESET " -- %s\n", s->name, s->what); printf(" " C_PASS "%d passed" C_RESET " " C_FAIL "%d failed" C_RESET " " C_SKIP "%d skipped" C_RESET " of %d\n\n", p, f, k, s->ntests); for (i = 0; i < s->ntests; i++) { printf("%s %s %-16s" C_RESET "\n", i == sel ? C_SEL : " ", st_tag(s->tests[i].status), s->tests[i].name); printf(C_DIM " %s\n" C_RESET, s->tests[i].what); if (s->tests[i].msg[0]) printf(" -> %s\n", s->tests[i].msg); } printf("\n" C_DIM " A rerun this test X run whole suite B back\n" C_RESET); if (status && *status) printf("\n %s\n", status); } /* Progress callback: netboot runs with the console as the only output, so * print straight to it rather than buffering -- a stalled transfer should show * where it stalled. */ static void nb_status(const char *msg) { printf(" %s\n", msg); } static void do_netboot(char *status, size_t nstatus) { void *buf; int len; printf("\n" C_HEAD " Self-update over the BBA" C_RESET "\n\n"); printf(" On the host: sudo ./scripts/send-dol.py \n"); printf(" B on the next screen is NOT available -- this blocks until the\n" " transfer finishes or times out (30 s idle, 5 s mid-transfer).\n\n"); /* 8 MB ceiling: far above any .dol, and it keeps a corrupt length field * from trying to allocate the whole heap. */ buf = memalign(32, 8u << 20); if (!buf) { snprintf(status, nstatus, "net-update: out of memory"); return; } len = netboot_receive(buf, 8u << 20, nb_status); if (len <= 0) { free(buf); snprintf(status, nstatus, "net-update failed (%d)", len); return; } printf("\n launching %d bytes...\n", len); /* Give the console a frame to actually show that before we disable * interrupts and overwrite ourselves. */ VIDEO_WaitVSync(); VIDEO_WaitVSync(); if (netboot_launch(buf) < 0) { free(buf); snprintf(status, nstatus, "not a valid DOL - not launching"); return; } /* netboot_launch() does not return on success. */ } int main(void) { int screen = 0; /* 0 = main, 1 = suite detail */ int sel = 0, ssel = 0, cur = 0; char status[80] = ""; VIDEO_Init(); PAD_Init(); rmode = VIDEO_GetPreferredMode(NULL); xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode)); console_init(xfb, 20, 20, rmode->fbWidth, rmode->xfbHeight, rmode->fbWidth * VI_DISPLAY_PIX_SZ); VIDEO_Configure(rmode); VIDEO_SetNextFramebuffer(xfb); VIDEO_SetBlack(FALSE); VIDEO_Flush(); VIDEO_WaitVSync(); if (rmode->viTVMode & VI_NON_INTERLACE) VIDEO_WaitVSync(); snprintf(status, sizeof(status), "ready - press START to run everything"); draw_main(sel, status); for (;;) { u16 down; VIDEO_WaitVSync(); PAD_ScanPads(); down = PAD_ButtonsDown(0); if (!down) continue; if (down & PAD_TRIGGER_Z) { /* Self-update: pull a new .dol over the BBA and launch it. * Staged in a malloc'd buffer rather than written in place -- * netboot_launch() copies sections to their load addresses, * which overlap the memory this program is running from. */ do_netboot(status, sizeof(status)); screen = 0; draw_main(sel, status); continue; } if (down & PAD_BUTTON_START) { snprintf(status, sizeof(status), "running all suites..."); if (screen) draw_suite(cur, ssel, status); else draw_main(sel, status); tests_run_all(); snprintf(status, sizeof(status), "done"); screen = 0; draw_main(sel, status); continue; } if (screen == 0) { int n = g_nsuites; if (down & PAD_BUTTON_DOWN) sel = (sel + 1) % n; if (down & PAD_BUTTON_UP) sel = (sel + n - 1) % n; if (down & PAD_BUTTON_A) { screen = 1; cur = sel; ssel = 0; status[0] = 0; draw_suite(cur, ssel, status); continue; } if (down & PAD_BUTTON_X) { snprintf(status, sizeof(status), "running %s...", g_suites[sel].name); draw_main(sel, status); tests_run_suite(&g_suites[sel]); snprintf(status, sizeof(status), "%s done", g_suites[sel].name); } if (down & PAD_BUTTON_Y) { tests_reset_status(); snprintf(status, sizeof(status), "results cleared"); } draw_main(sel, status); } else { suite_t *s = &g_suites[cur]; if (down & PAD_BUTTON_DOWN) ssel = (ssel + 1) % s->ntests; if (down & PAD_BUTTON_UP) ssel = (ssel + s->ntests - 1) % s->ntests; if (down & PAD_BUTTON_B) { screen = 0; status[0] = 0; draw_main(sel, status); continue; } if (down & PAD_BUTTON_A) { snprintf(status, sizeof(status), "running %s...", s->tests[ssel].name); draw_suite(cur, ssel, status); tests_run_test(&s->tests[ssel]); status[0] = 0; } if (down & PAD_BUTTON_X) { snprintf(status, sizeof(status), "running %s...", s->name); draw_suite(cur, ssel, status); tests_run_suite(s); status[0] = 0; } draw_suite(cur, ssel, status); } } return 0; }