/** * @file devices.c * @brief Tests the device backend records, the palette table and the host clock. * * These are the pieces groups G, I and E are built on. Nothing here runs a BASIC * verb -- that is what the per-group suites do -- it establishes that a runtime * can be given backends, can be given none, and reports the time it was handed. */ #include #include #include #include #include "harness.h" #include "mockdevice.h" #include "testutil.h" /** * @brief A runtime with no backends attached must still come up and still print. * * This is the standalone driver's situation and the default for an embedding * host that has not opted in, so it is the case that has to keep working. */ static void test_no_devices_is_normal(void) { TEST_REQUIRE_OK(harness_start(NULL)); TEST_REQUIRE(HARNESS_RUNTIME.graphics == NULL, "graphics backend should start NULL"); TEST_REQUIRE(HARNESS_RUNTIME.audio == NULL, "audio backend should start NULL"); TEST_REQUIRE(HARNESS_RUNTIME.input == NULL, "input backend should start NULL"); TEST_REQUIRE_INT(HARNESS_RUNTIME.timems, 0); TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME, "10 PRINT \"HI\"\n")); TEST_REQUIRE_OK(akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN)); TEST_REQUIRE_OK(akbasic_runtime_run(&HARNESS_RUNTIME, 0)); TEST_REQUIRE_STR(HARNESS_OUTPUT, "HI\n"); harness_stop(); } /** @brief Attaching backends stores them; attaching NULLs takes them away again. */ static void test_set_devices(void) { TEST_REQUIRE_OK(harness_start(NULL)); mock_devices_init(); TEST_REQUIRE_OK(akbasic_runtime_set_devices(&HARNESS_RUNTIME, &MOCK_GRAPHICS, &MOCK_AUDIO, &MOCK_INPUT)); TEST_REQUIRE(HARNESS_RUNTIME.graphics == &MOCK_GRAPHICS, "graphics backend was not stored"); TEST_REQUIRE(HARNESS_RUNTIME.audio == &MOCK_AUDIO, "audio backend was not stored"); TEST_REQUIRE(HARNESS_RUNTIME.input == &MOCK_INPUT, "input backend was not stored"); /* Withholding a capability is spelled NULL, and must actually detach. */ TEST_REQUIRE_OK(akbasic_runtime_set_devices(&HARNESS_RUNTIME, &MOCK_GRAPHICS, NULL, NULL)); TEST_REQUIRE(HARNESS_RUNTIME.graphics == &MOCK_GRAPHICS, "graphics backend should have stayed"); TEST_REQUIRE(HARNESS_RUNTIME.audio == NULL, "audio backend should have been detached"); TEST_REQUIRE(HARNESS_RUNTIME.input == NULL, "input backend should have been detached"); TEST_REQUIRE_STATUS(akbasic_runtime_set_devices(NULL, NULL, NULL, NULL), AKERR_NULLPOINTER); harness_stop(); } /** @brief The clock is stored verbatim, including a value that moves backwards. */ static void test_settime(void) { TEST_REQUIRE_OK(harness_start(NULL)); TEST_REQUIRE_OK(akbasic_runtime_settime(&HARNESS_RUNTIME, 1000)); TEST_REQUIRE_INT(HARNESS_RUNTIME.timems, 1000); /* * A host driving a paused or scrubbed clock is allowed. Rejecting it would * make the interpreter an authority on the host's own time, which it is not. */ TEST_REQUIRE_OK(akbasic_runtime_settime(&HARNESS_RUNTIME, 400)); TEST_REQUIRE_INT(HARNESS_RUNTIME.timems, 400); TEST_REQUIRE_STATUS(akbasic_runtime_settime(NULL, 0), AKERR_NULLPOINTER); harness_stop(); } /** @brief The palette covers 1 to 16 and refuses everything else. */ static void test_palette(void) { akbasic_Color color; int index = 0; memset(&color, 0, sizeof(color)); for ( index = 1; index <= 16; index++ ) { TEST_REQUIRE_OK(akbasic_graphics_palette(index, &color)); TEST_REQUIRE_INT(color.a, 0xff); } /* Two entries pinned by value, so a shifted table is a failure and not a shrug. */ TEST_REQUIRE_OK(akbasic_graphics_palette(1, &color)); TEST_REQUIRE(color.r == 0x00 && color.g == 0x00 && color.b == 0x00, "color 1 should be black, got %02x%02x%02x", color.r, color.g, color.b); TEST_REQUIRE_OK(akbasic_graphics_palette(2, &color)); TEST_REQUIRE(color.r == 0xff && color.g == 0xff && color.b == 0xff, "color 2 should be white, got %02x%02x%02x", color.r, color.g, color.b); /* * BASIC counts from 1. Zero is the off-by-one somebody writes when they * forget that, and it has to be refused rather than quietly return slot 0. */ TEST_REQUIRE_STATUS(akbasic_graphics_palette(0, &color), AKBASIC_ERR_BOUNDS); TEST_REQUIRE_STATUS(akbasic_graphics_palette(17, &color), AKBASIC_ERR_BOUNDS); TEST_REQUIRE_STATUS(akbasic_graphics_palette(-1, &color), AKBASIC_ERR_BOUNDS); TEST_REQUIRE_STATUS(akbasic_graphics_palette(1, NULL), AKERR_NULLPOINTER); } /** @brief The mock backends themselves record what they were told. */ static void test_mock_records(void) { akbasic_Color red = { 0xff, 0x00, 0x00, 0xff }; bool active = false; int handle = -1; int keycode = 0; bool available = false; mock_devices_init(); TEST_REQUIRE_OK(MOCK_GRAPHICS.point(&MOCK_GRAPHICS, 10.0, 20.0, red)); TEST_REQUIRE_OK(MOCK_GRAPHICS.line(&MOCK_GRAPHICS, 0.0, 0.0, 5.0, 5.0, red)); TEST_REQUIRE_STR(MOCK.log, "point 10.0,20.0 #ff0000\nline 0.0,0.0-5.0,5.0 #ff0000\n"); /* A saved shape yields a handle, and only a handle that exists can be pasted. */ TEST_REQUIRE_OK(MOCK_GRAPHICS.save_shape(&MOCK_GRAPHICS, 0, 0, 8, 8, &handle)); TEST_REQUIRE_INT(handle, 0); TEST_REQUIRE_OK(MOCK_GRAPHICS.paste_shape(&MOCK_GRAPHICS, handle, 16.0, 16.0)); TEST_REQUIRE_STATUS(MOCK_GRAPHICS.paste_shape(&MOCK_GRAPHICS, 99, 0.0, 0.0), AKBASIC_ERR_BOUNDS); /* The exhaustion switch is what lets the PAINT test reach the partial-fill path. */ MOCK.paint_exhausts = true; TEST_REQUIRE_STATUS(MOCK_GRAPHICS.paint(&MOCK_GRAPHICS, 1, 1, red), AKERR_OUTOFBOUNDS); MOCK.paint_exhausts = false; TEST_REQUIRE_OK(MOCK_GRAPHICS.paint(&MOCK_GRAPHICS, 1, 1, red)); TEST_REQUIRE_OK(MOCK_AUDIO.tone(&MOCK_AUDIO, 0, 440.0, 250)); TEST_REQUIRE_OK(MOCK_AUDIO.voice_active(&MOCK_AUDIO, 0, &active)); TEST_REQUIRE(active, "voice 0 should be sounding after a tone"); TEST_REQUIRE_OK(MOCK_AUDIO.stop(&MOCK_AUDIO, 0)); TEST_REQUIRE_OK(MOCK_AUDIO.voice_active(&MOCK_AUDIO, 0, &active)); TEST_REQUIRE(!active, "voice 0 should be silent after a stop"); TEST_REQUIRE_STATUS(MOCK_AUDIO.tone(&MOCK_AUDIO, AKBASIC_AUDIO_VOICES, 440.0, 1), AKBASIC_ERR_BOUNDS); /* An empty key buffer is success with available false, never an error. */ TEST_REQUIRE_OK(MOCK_INPUT.poll_key(&MOCK_INPUT, &keycode, &available)); TEST_REQUIRE(!available, "an empty mock buffer should report nothing available"); mock_push_keys("AB"); TEST_REQUIRE_OK(MOCK_INPUT.poll_key(&MOCK_INPUT, &keycode, &available)); TEST_REQUIRE(available, "a primed mock buffer should report a key"); TEST_REQUIRE_INT(keycode, 'A'); TEST_REQUIRE_OK(MOCK_INPUT.poll_key(&MOCK_INPUT, &keycode, &available)); TEST_REQUIRE_INT(keycode, 'B'); TEST_REQUIRE_OK(MOCK_INPUT.poll_key(&MOCK_INPUT, &keycode, &available)); TEST_REQUIRE(!available, "the mock buffer should drain to empty"); } int main(void) { test_no_devices_is_normal(); test_set_devices(); test_settime(); test_palette(); test_mock_records(); return akbasic_test_failures; }