116 lines
3.8 KiB
C
116 lines
3.8 KiB
C
|
|
/**
|
||
|
|
* @file machine_verbs.c
|
||
|
|
* @brief Tests the group J verbs: FETCH, STASH and SYS.
|
||
|
|
*
|
||
|
|
* These operate on real process memory, exactly as POKE, PEEK and POINTER
|
||
|
|
* already do, so the tests give them real addresses -- their own buffers, taken
|
||
|
|
* with POINTER the way a BASIC program would have to.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
#include <akbasic/error.h>
|
||
|
|
#include <akbasic/runtime.h>
|
||
|
|
|
||
|
|
#include "harness.h"
|
||
|
|
#include "testutil.h"
|
||
|
|
|
||
|
|
/** @brief Run a program to completion in RUN mode, from a string. */
|
||
|
|
static akerr_ErrorContext AKERR_NOIGNORE *run_program(const char *source)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
PASS(errctx, harness_start(NULL));
|
||
|
|
PASS(errctx, akbasic_runtime_load(&HARNESS_RUNTIME, source));
|
||
|
|
PASS(errctx, akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 0));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief STASH copies bytes, and FETCH copies them back.
|
||
|
|
*
|
||
|
|
* Addresses come from POINTER, which is the only way a BASIC program can name a
|
||
|
|
* real one. Two string variables give two buffers to copy between.
|
||
|
|
*/
|
||
|
|
static void test_copy(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(run_program("10 A$ = \"SOURCE\"\n"
|
||
|
|
"20 B$ = \"XXXXXX\"\n"
|
||
|
|
"30 STASH 7, POINTER(A$), POINTER(B$)\n"
|
||
|
|
"40 PRINT B$\n"));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "SOURCE\n");
|
||
|
|
harness_stop();
|
||
|
|
|
||
|
|
/* FETCH is the same copy; there is no expansion RAM to distinguish them. */
|
||
|
|
TEST_REQUIRE_OK(run_program("10 A$ = \"HELLO!\"\n"
|
||
|
|
"20 B$ = \"......\"\n"
|
||
|
|
"30 FETCH 7, POINTER(A$), POINTER(B$)\n"
|
||
|
|
"40 PRINT B$\n"));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "HELLO!\n");
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief A zero-length copy does nothing, and a negative one is refused. */
|
||
|
|
static void test_copy_bounds(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(run_program("10 A$ = \"ABC\"\n"
|
||
|
|
"20 B$ = \"XYZ\"\n"
|
||
|
|
"30 STASH 0, POINTER(A$), POINTER(B$)\n"
|
||
|
|
"40 PRINT B$\n"));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "XYZ\n");
|
||
|
|
harness_stop();
|
||
|
|
|
||
|
|
TEST_REQUIRE_OK(run_program("10 A$ = \"ABC\"\n"
|
||
|
|
"20 STASH -1, POINTER(A$), POINTER(A$)\n"));
|
||
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "negative number of bytes") != NULL,
|
||
|
|
"a negative count should be refused, got \"%s\"", HARNESS_OUTPUT);
|
||
|
|
harness_stop();
|
||
|
|
|
||
|
|
/* Address zero is refused rather than dereferenced. */
|
||
|
|
TEST_REQUIRE_OK(run_program("10 A$ = \"ABC\"\n"
|
||
|
|
"20 STASH 3, 0, POINTER(A$)\n"));
|
||
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "address zero") != NULL,
|
||
|
|
"address zero should be refused, got \"%s\"", HARNESS_OUTPUT);
|
||
|
|
harness_stop();
|
||
|
|
|
||
|
|
/* Too few arguments is a syntax error naming the form. */
|
||
|
|
TEST_REQUIRE_OK(run_program("10 STASH 3\n"));
|
||
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "source address") != NULL,
|
||
|
|
"an incomplete STASH should name the form, got \"%s\"", HARNESS_OUTPUT);
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief SYS parses and is then refused by name.
|
||
|
|
*
|
||
|
|
* Parsing first is what keeps a listing containing SYS loadable and listable; it
|
||
|
|
* is only running it that has no meaning.
|
||
|
|
*/
|
||
|
|
static void test_sys_refused(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(run_program("10 SYS 65490\n"));
|
||
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "no 6502") != NULL,
|
||
|
|
"SYS should be refused with a reason, got \"%s\"", HARNESS_OUTPUT);
|
||
|
|
harness_stop();
|
||
|
|
|
||
|
|
/*
|
||
|
|
* A SYS with no address is a syntax error, not the device refusal -- and it
|
||
|
|
* names the verb. It used to fail deep in the expression parser with
|
||
|
|
* "peek() returned nil token!", which every verb taking an argument list
|
||
|
|
* shared.
|
||
|
|
*/
|
||
|
|
TEST_REQUIRE_OK(run_program("10 SYS\n"));
|
||
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "SYS expected at least one argument") != NULL,
|
||
|
|
"a bare SYS should name the verb, got \"%s\"", HARNESS_OUTPUT);
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
int main(void)
|
||
|
|
{
|
||
|
|
test_copy();
|
||
|
|
test_copy_bounds();
|
||
|
|
test_sys_refused();
|
||
|
|
return akbasic_test_failures;
|
||
|
|
}
|