196 lines
6.4 KiB
C
196 lines
6.4 KiB
C
|
|
/**
|
||
|
|
* @file trap_verbs.c
|
||
|
|
* @brief Tests the group C verbs: TRAP, RESUME and ERR().
|
||
|
|
*
|
||
|
|
* The interesting half of error trapping is what *stops* happening: an armed
|
||
|
|
* TRAP suppresses the "? line : CLASS message" report and stops the run from
|
||
|
|
* ending, and neither is visible unless a test says the output is empty of one
|
||
|
|
* and the program kept going.
|
||
|
|
*
|
||
|
|
* `ER` and `EL` are `ER#` and `EL#` here -- ordinary globals, because this
|
||
|
|
* dialect has no bare variable names. See TODO.md section 5.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#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));
|
||
|
|
/* Bounded: a bare RESUME that never fixes anything is an infinite retry. */
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 4000));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief An armed TRAP swallows the report and enters the handler instead. */
|
||
|
|
static void test_trap_catches(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(run_program("10 TRAP 100\n"
|
||
|
|
"20 DIM Q#(2)\n"
|
||
|
|
"30 PRINT Q#(9)\n"
|
||
|
|
"40 PRINT \"AFTER\"\n"
|
||
|
|
"50 END\n"
|
||
|
|
"100 PRINT \"TRAPPED \" + ER# + \" ON LINE \" + EL#\n"
|
||
|
|
"110 RESUME NEXT\n"));
|
||
|
|
/*
|
||
|
|
* AKBASIC_ERR_BOUNDS is 515: base 512 plus SYNTAX, TYPE, UNDEFINED. Spelled
|
||
|
|
* out rather than referenced so that a renumbering of the error band shows up
|
||
|
|
* here as a failure rather than as a silently different program.
|
||
|
|
*/
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "TRAPPED 515 ON LINE 30\nAFTER\n");
|
||
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "RUNTIME ERROR") == NULL,
|
||
|
|
"a trapped error must not also be reported, got \"%s\"", HARNESS_OUTPUT);
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief With no TRAP the error is reported and the program stops, as before. */
|
||
|
|
static void test_untrapped_still_reports(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(run_program("10 DIM Q#(2)\n"
|
||
|
|
"20 PRINT Q#(9)\n"
|
||
|
|
"30 PRINT \"UNREACHED\"\n"));
|
||
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "RUNTIME ERROR") != NULL,
|
||
|
|
"an untrapped error should be reported, got \"%s\"", HARNESS_OUTPUT);
|
||
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "UNREACHED") == NULL,
|
||
|
|
"an untrapped error should stop the program, got \"%s\"", HARNESS_OUTPUT);
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief Bare TRAP disarms, and the next error is reported normally again. */
|
||
|
|
static void test_trap_disarms(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(run_program("10 TRAP 100\n"
|
||
|
|
"20 TRAP\n"
|
||
|
|
"30 DIM Q#(2)\n"
|
||
|
|
"40 PRINT Q#(9)\n"
|
||
|
|
"50 PRINT \"UNREACHED\"\n"
|
||
|
|
"100 PRINT \"NOT SEEN\"\n"
|
||
|
|
"110 RESUME NEXT\n"));
|
||
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "RUNTIME ERROR") != NULL,
|
||
|
|
"a disarmed TRAP should let the error through, got \"%s\"", HARNESS_OUTPUT);
|
||
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "NOT SEEN") == NULL,
|
||
|
|
"the handler should not have run, got \"%s\"", HARNESS_OUTPUT);
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief A handler may be named by label, which is where a handler usually sits. */
|
||
|
|
static void test_trap_by_label(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(run_program("10 TRAP HANDLER\n"
|
||
|
|
"20 DIM Q#(2)\n"
|
||
|
|
"30 PRINT Q#(9)\n"
|
||
|
|
"40 PRINT \"AFTER\"\n"
|
||
|
|
"50 END\n"
|
||
|
|
"100 LABEL HANDLER\n"
|
||
|
|
"110 PRINT \"CAUGHT\"\n"
|
||
|
|
"120 RESUME NEXT\n"));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "CAUGHT\nAFTER\n");
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief RESUME's three forms land on three different lines. */
|
||
|
|
static void test_resume_forms(void)
|
||
|
|
{
|
||
|
|
/* RESUME <line> goes where it is told. */
|
||
|
|
TEST_REQUIRE_OK(run_program("10 TRAP 100\n"
|
||
|
|
"20 DIM Q#(2)\n"
|
||
|
|
"30 PRINT Q#(9)\n"
|
||
|
|
"40 PRINT \"NOT HERE\"\n"
|
||
|
|
"50 PRINT \"HERE\"\n"
|
||
|
|
"60 END\n"
|
||
|
|
"100 RESUME 50\n"));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "HERE\n");
|
||
|
|
harness_stop();
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Bare RESUME retries the failing line. The handler fixes the array first,
|
||
|
|
* so the retry succeeds -- which is the only way a bare RESUME terminates,
|
||
|
|
* and the reason the verb exists.
|
||
|
|
*/
|
||
|
|
TEST_REQUIRE_OK(run_program("10 TRAP 100\n"
|
||
|
|
"20 DIM Q#(2)\n"
|
||
|
|
"30 PRINT Q#(N#)\n"
|
||
|
|
"40 PRINT \"AFTER\"\n"
|
||
|
|
"50 END\n"
|
||
|
|
"100 N# = 1\n"
|
||
|
|
"110 RESUME\n"));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "0\nAFTER\n");
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief RESUME outside a handler is refused rather than popping something else. */
|
||
|
|
static void test_resume_outside_handler(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(run_program("10 RESUME NEXT\n"));
|
||
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "outside the context of a TRAP handler") != NULL,
|
||
|
|
"a bare RESUME should be refused, got \"%s\"", HARNESS_OUTPUT);
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief An error inside a handler is reported rather than re-entering it.
|
||
|
|
*
|
||
|
|
* Otherwise a broken handler traps its own failure forever and the program can
|
||
|
|
* never be stopped. A C128 behaves the same way.
|
||
|
|
*/
|
||
|
|
static void test_error_inside_handler(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(run_program("10 TRAP 100\n"
|
||
|
|
"20 DIM Q#(2)\n"
|
||
|
|
"30 PRINT Q#(9)\n"
|
||
|
|
"40 END\n"
|
||
|
|
"100 PRINT \"IN HANDLER\"\n"
|
||
|
|
"110 PRINT Q#(9)\n"
|
||
|
|
"120 RESUME NEXT\n"));
|
||
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "IN HANDLER") != NULL,
|
||
|
|
"the handler should have run once, got \"%s\"", HARNESS_OUTPUT);
|
||
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "RUNTIME ERROR") != NULL,
|
||
|
|
"an error inside the handler should be reported, got \"%s\"", HARNESS_OUTPUT);
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief ERR() names a status code, using the registry every code is named in. */
|
||
|
|
static void test_err_function(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(run_program("10 TRAP 100\n"
|
||
|
|
"20 DIM Q#(2)\n"
|
||
|
|
"30 PRINT Q#(9)\n"
|
||
|
|
"40 END\n"
|
||
|
|
"100 PRINT ERR(ER#)\n"
|
||
|
|
"110 RESUME NEXT\n"));
|
||
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "Out Of Bounds") != NULL,
|
||
|
|
"ERR should name the status, got \"%s\"", HARNESS_OUTPUT);
|
||
|
|
harness_stop();
|
||
|
|
|
||
|
|
/*
|
||
|
|
* A code nobody reserved reads back as libakerror's own "Unknown Error",
|
||
|
|
* which is the same string a stack trace would print for it.
|
||
|
|
*/
|
||
|
|
TEST_REQUIRE_OK(run_program("10 PRINT ERR(30000)\n"));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "Unknown Error\n");
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
int main(void)
|
||
|
|
{
|
||
|
|
test_trap_catches();
|
||
|
|
test_untrapped_still_reports();
|
||
|
|
test_trap_disarms();
|
||
|
|
test_trap_by_label();
|
||
|
|
test_resume_forms();
|
||
|
|
test_resume_outside_handler();
|
||
|
|
test_error_inside_handler();
|
||
|
|
test_err_function();
|
||
|
|
return akbasic_test_failures;
|
||
|
|
}
|