172 lines
7.1 KiB
C
172 lines
7.1 KiB
C
|
|
/**
|
||
|
|
* @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 <string.h>
|
||
|
|
|
||
|
|
#include <akbasic/error.h>
|
||
|
|
#include <akbasic/graphics.h>
|
||
|
|
#include <akbasic/runtime.h>
|
||
|
|
|
||
|
|
#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;
|
||
|
|
}
|