367 lines
15 KiB
C
367 lines
15 KiB
C
|
|
/**
|
||
|
|
* @file interrupts.c
|
||
|
|
* @brief Tests the interrupt machinery: arm, raise, enter a handler, RETURN.
|
||
|
|
*
|
||
|
|
* This is the part of COLLISION that has nothing to do with sprites, and it is
|
||
|
|
* tested on its own for two reasons. It is what group C's TRAP will reuse, so it
|
||
|
|
* has to be correct before anything is built on it; and driving it through the
|
||
|
|
* API rather than through a verb is the only way to fire an interrupt at an
|
||
|
|
* exactly known point in a program, which is what most of these assertions turn
|
||
|
|
* on.
|
||
|
|
*
|
||
|
|
* **Every program here is numbered 1, 2, 3, ...** One step of the runtime
|
||
|
|
* advances by one source line *index*, empty ones included -- so consecutive
|
||
|
|
* numbering is what makes a step count mean a statement count. The first step of
|
||
|
|
* any run is line 0, which is always empty, and is accounted for in each count
|
||
|
|
* below.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include "harness.h"
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief A program with a handler at 3 and a main loop that never ends.
|
||
|
|
*
|
||
|
|
* The endless loop is on purpose: every test here steps a fixed number of times
|
||
|
|
* and asserts what came out, so a regression that fails to enter or fails to
|
||
|
|
* return shows up as the wrong output rather than as a run that stops early and
|
||
|
|
* looks plausible.
|
||
|
|
*/
|
||
|
|
static const char *PROGRAM_BY_LINE =
|
||
|
|
"1 PRINT \"START\"\n"
|
||
|
|
"2 GOTO 5\n"
|
||
|
|
"3 PRINT \"HANDLER\"\n"
|
||
|
|
"4 RETURN\n"
|
||
|
|
"5 PRINT \"MAIN\"\n"
|
||
|
|
"6 GOTO 5\n";
|
||
|
|
|
||
|
|
/** @brief Load a program and put the runtime in RUN mode without stepping it. */
|
||
|
|
static akerr_ErrorContext AKERR_NOIGNORE *load_program(const char *source)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
PASS(errctx, akbasic_runtime_load(&HARNESS_RUNTIME, source));
|
||
|
|
PASS(errctx, akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief A handler named by line number runs, and RETURN resumes where it left. */
|
||
|
|
static akerr_ErrorContext AKERR_NOIGNORE *test_enter_and_return(void)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
TEST_REQUIRE_OK(harness_start(NULL));
|
||
|
|
PASS(errctx, load_program(PROGRAM_BY_LINE));
|
||
|
|
PASS(errctx, akbasic_runtime_arm_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE, 3, NULL));
|
||
|
|
|
||
|
|
/* Line 0, then 1 and 2, so the program is sitting on 5 with nothing pending. */
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 3));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "START\n");
|
||
|
|
|
||
|
|
PASS(errctx, akbasic_runtime_raise_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE));
|
||
|
|
TEST_REQUIRE(HARNESS_RUNTIME.handlerenv == NULL, "raising must not enter anything by itself");
|
||
|
|
|
||
|
|
/* One step: the handler is entered and its first line runs. */
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 1));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "START\nHANDLER\n");
|
||
|
|
TEST_REQUIRE(HARNESS_RUNTIME.handlerenv != NULL, "the runtime should know it is in a handler");
|
||
|
|
TEST_REQUIRE(!HARNESS_RUNTIME.interrupts[AKBASIC_INTERRUPT_SPRITE].pending,
|
||
|
|
"entering the handler should have consumed the event");
|
||
|
|
|
||
|
|
/* The RETURN, then the line the interrupt took the program away from. */
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 2));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "START\nHANDLER\nMAIN\n");
|
||
|
|
TEST_REQUIRE(HARNESS_RUNTIME.handlerenv == NULL, "RETURN should have left the handler");
|
||
|
|
|
||
|
|
/* Still armed: one collision handled is not a subscription cancelled. */
|
||
|
|
TEST_REQUIRE(HARNESS_RUNTIME.interrupts[AKBASIC_INTERRUPT_SPRITE].armed,
|
||
|
|
"the handler should still be armed after it returns");
|
||
|
|
|
||
|
|
harness_stop();
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief A handler named by label, on a line the program never falls into.
|
||
|
|
*
|
||
|
|
* This is the shape a handler is actually written in -- jumped over by the main
|
||
|
|
* flow, reachable only through the interrupt -- and it works only because
|
||
|
|
* akbasic_runtime_scan_labels() files LABEL before anything runs. Without the
|
||
|
|
* prescan the label does not exist when the interrupt fires and the run stops
|
||
|
|
* with AKBASIC_ERR_UNDEFINED.
|
||
|
|
*/
|
||
|
|
static akerr_ErrorContext AKERR_NOIGNORE *test_label_handler(void)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
TEST_REQUIRE_OK(harness_start(NULL));
|
||
|
|
PASS(errctx, load_program("1 GOTO 5\n"
|
||
|
|
"2 LABEL BUMPED\n"
|
||
|
|
"3 PRINT \"HANDLER\"\n"
|
||
|
|
"4 RETURN\n"
|
||
|
|
"5 PRINT \"MAIN\"\n"
|
||
|
|
"6 GOTO 5\n"));
|
||
|
|
PASS(errctx, akbasic_runtime_arm_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE,
|
||
|
|
0, "BUMPED"));
|
||
|
|
|
||
|
|
/* Line 0, the GOTO, then the main loop's PRINT. */
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 3));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "MAIN\n");
|
||
|
|
|
||
|
|
PASS(errctx, akbasic_runtime_raise_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE));
|
||
|
|
/* The LABEL line the handler is entered on, then the handler's PRINT. */
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 2));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "MAIN\nHANDLER\n");
|
||
|
|
|
||
|
|
/* RETURN, then back into the main loop where it left off. */
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 3));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "MAIN\nHANDLER\nMAIN\n");
|
||
|
|
|
||
|
|
harness_stop();
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief An interrupt does not interrupt an interrupt.
|
||
|
|
*
|
||
|
|
* Without the guard a collision that is still true while its own handler runs
|
||
|
|
* re-enters on the next line, and keeps re-entering until the environment pool
|
||
|
|
* is gone -- the failure would be AKBASIC_ERR_ENVIRONMENT from somewhere with
|
||
|
|
* nothing to do with sprites. The event raised inside the handler is not lost,
|
||
|
|
* though: it is taken as soon as the RETURN lands.
|
||
|
|
*/
|
||
|
|
static akerr_ErrorContext AKERR_NOIGNORE *test_no_reentry(void)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
TEST_REQUIRE_OK(harness_start(NULL));
|
||
|
|
PASS(errctx, load_program("1 GOTO 5\n"
|
||
|
|
"2 PRINT \"IN\"\n"
|
||
|
|
"3 PRINT \"OUT\"\n"
|
||
|
|
"4 RETURN\n"
|
||
|
|
"5 PRINT \"MAIN\"\n"
|
||
|
|
"6 GOTO 5\n"));
|
||
|
|
PASS(errctx, akbasic_runtime_arm_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE, 2, NULL));
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 3));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "MAIN\n");
|
||
|
|
|
||
|
|
PASS(errctx, akbasic_runtime_raise_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE));
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 1));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "MAIN\nIN\n");
|
||
|
|
|
||
|
|
/* Fires again while the handler is still running. */
|
||
|
|
PASS(errctx, akbasic_runtime_raise_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE));
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 1));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "MAIN\nIN\nOUT\n");
|
||
|
|
TEST_REQUIRE(HARNESS_RUNTIME.interrupts[AKBASIC_INTERRUPT_SPRITE].pending,
|
||
|
|
"an event raised inside a handler should be held, not dropped");
|
||
|
|
|
||
|
|
/* The RETURN lands, and the held event is taken on the very next step. */
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 2));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "MAIN\nIN\nOUT\nIN\n");
|
||
|
|
|
||
|
|
harness_stop();
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief A GOSUB the handler itself makes returns without re-arming interrupts. */
|
||
|
|
static akerr_ErrorContext AKERR_NOIGNORE *test_nested_gosub_keeps_the_flag(void)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
TEST_REQUIRE_OK(harness_start(NULL));
|
||
|
|
PASS(errctx, load_program("1 GOTO 5\n"
|
||
|
|
"2 GOSUB 8\n"
|
||
|
|
"3 RETURN\n"
|
||
|
|
"5 PRINT \"MAIN\"\n"
|
||
|
|
"6 GOTO 5\n"
|
||
|
|
"8 PRINT \"INNER\"\n"
|
||
|
|
"9 RETURN\n"));
|
||
|
|
PASS(errctx, akbasic_runtime_arm_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE, 2, NULL));
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 3));
|
||
|
|
PASS(errctx, akbasic_runtime_raise_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE));
|
||
|
|
|
||
|
|
/* Enter the handler, which immediately GOSUBs deeper. */
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 1));
|
||
|
|
TEST_REQUIRE(HARNESS_RUNTIME.handlerenv != NULL, "the handler should be running");
|
||
|
|
|
||
|
|
/* The inner PRINT and the inner RETURN: still inside the handler. */
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 2));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "MAIN\nINNER\n");
|
||
|
|
TEST_REQUIRE(HARNESS_RUNTIME.handlerenv != NULL,
|
||
|
|
"the inner RETURN belongs to the inner GOSUB, not to the handler");
|
||
|
|
|
||
|
|
/* The handler's own RETURN is the one that clears it. */
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 1));
|
||
|
|
TEST_REQUIRE(HARNESS_RUNTIME.handlerenv == NULL, "the handler's RETURN should have cleared it");
|
||
|
|
|
||
|
|
harness_stop();
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief Raising an unarmed source records nothing; disarming drops what is held. */
|
||
|
|
static akerr_ErrorContext AKERR_NOIGNORE *test_arm_and_disarm(void)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
TEST_REQUIRE_OK(harness_start(NULL));
|
||
|
|
PASS(errctx, load_program(PROGRAM_BY_LINE));
|
||
|
|
|
||
|
|
/* Unarmed. A backend may raise every frame without asking who is listening. */
|
||
|
|
PASS(errctx, akbasic_runtime_raise_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE));
|
||
|
|
TEST_REQUIRE(!HARNESS_RUNTIME.interrupts[AKBASIC_INTERRUPT_SPRITE].pending,
|
||
|
|
"an unarmed source should record nothing");
|
||
|
|
|
||
|
|
/* Armed, raised, then taken away again before it could be taken. */
|
||
|
|
PASS(errctx, akbasic_runtime_arm_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE, 3, NULL));
|
||
|
|
PASS(errctx, akbasic_runtime_raise_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE));
|
||
|
|
PASS(errctx, akbasic_runtime_disarm_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE));
|
||
|
|
TEST_REQUIRE(!HARNESS_RUNTIME.interrupts[AKBASIC_INTERRUPT_SPRITE].pending,
|
||
|
|
"disarming should drop a pending event rather than deferring it");
|
||
|
|
|
||
|
|
/* And the program runs straight through without ever seeing line 3. */
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 6));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "START\nMAIN\nMAIN\n");
|
||
|
|
|
||
|
|
harness_stop();
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief NEW disarms everything: the handler lines belong to a program that is gone. */
|
||
|
|
static akerr_ErrorContext AKERR_NOIGNORE *test_new_disarms(void)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
akbasic_ASTLeaf *leaf = NULL;
|
||
|
|
akbasic_Value *out = NULL;
|
||
|
|
|
||
|
|
TEST_REQUIRE_OK(harness_start(NULL));
|
||
|
|
PASS(errctx, load_program(PROGRAM_BY_LINE));
|
||
|
|
PASS(errctx, akbasic_runtime_arm_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE, 3, NULL));
|
||
|
|
|
||
|
|
PASS(errctx, akbasic_environment_zero(HARNESS_RUNTIME.environment));
|
||
|
|
PASS(errctx, harness_parse("NEW", &leaf));
|
||
|
|
PASS(errctx, akbasic_runtime_evaluate(&HARNESS_RUNTIME, leaf, &out));
|
||
|
|
|
||
|
|
TEST_REQUIRE(!HARNESS_RUNTIME.interrupts[AKBASIC_INTERRUPT_SPRITE].armed,
|
||
|
|
"NEW should have disarmed the interrupt");
|
||
|
|
|
||
|
|
harness_stop();
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief A handler label that names nothing is the program's error, not the host's.
|
||
|
|
*
|
||
|
|
* It has to be *reported* and stop the run. Letting it out of
|
||
|
|
* akbasic_runtime_step() would tear down an embedding game over a typo in a
|
||
|
|
* script, which is what goal 3 exists to prevent.
|
||
|
|
*/
|
||
|
|
static akerr_ErrorContext AKERR_NOIGNORE *test_undefined_label_is_reported(void)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
TEST_REQUIRE_OK(harness_start(NULL));
|
||
|
|
PASS(errctx, load_program("1 PRINT \"MAIN\"\n"
|
||
|
|
"2 GOTO 1\n"));
|
||
|
|
PASS(errctx, akbasic_runtime_arm_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE,
|
||
|
|
0, "NOSUCHLABEL"));
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 2));
|
||
|
|
PASS(errctx, akbasic_runtime_raise_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE));
|
||
|
|
|
||
|
|
/* Returns cleanly -- the error is on the sink, not in the return value. */
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 1));
|
||
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "NOSUCHLABEL") != NULL,
|
||
|
|
"the report should name the label that could not be found, got: %s",
|
||
|
|
HARNESS_OUTPUT);
|
||
|
|
TEST_REQUIRE_INT(HARNESS_RUNTIME.mode, AKBASIC_MODE_QUIT);
|
||
|
|
|
||
|
|
harness_stop();
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief Arming refuses a source out of range, and a target that is both or neither. */
|
||
|
|
static akerr_ErrorContext AKERR_NOIGNORE *test_arm_refusals(void)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
TEST_REQUIRE_OK(harness_start(NULL));
|
||
|
|
|
||
|
|
TEST_REQUIRE_STATUS(akbasic_runtime_arm_interrupt(&HARNESS_RUNTIME,
|
||
|
|
(akbasic_InterruptSource)AKBASIC_MAX_INTERRUPTS,
|
||
|
|
3, NULL),
|
||
|
|
AKBASIC_ERR_BOUNDS);
|
||
|
|
TEST_REQUIRE_STATUS(akbasic_runtime_arm_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE,
|
||
|
|
0, NULL),
|
||
|
|
AKBASIC_ERR_VALUE);
|
||
|
|
TEST_REQUIRE_STATUS(akbasic_runtime_arm_interrupt(&HARNESS_RUNTIME, AKBASIC_INTERRUPT_SPRITE,
|
||
|
|
3, "BUMPED"),
|
||
|
|
AKBASIC_ERR_VALUE);
|
||
|
|
TEST_REQUIRE_STATUS(akbasic_runtime_raise_interrupt(&HARNESS_RUNTIME,
|
||
|
|
(akbasic_InterruptSource)-1),
|
||
|
|
AKBASIC_ERR_BOUNDS);
|
||
|
|
TEST_REQUIRE_STATUS(akbasic_runtime_disarm_interrupt(&HARNESS_RUNTIME,
|
||
|
|
(akbasic_InterruptSource)AKBASIC_MAX_INTERRUPTS),
|
||
|
|
AKBASIC_ERR_BOUNDS);
|
||
|
|
TEST_REQUIRE_STATUS(akbasic_runtime_arm_interrupt(NULL, AKBASIC_INTERRUPT_SPRITE, 3, NULL),
|
||
|
|
AKERR_NULLPOINTER);
|
||
|
|
TEST_REQUIRE_STATUS(akbasic_runtime_service_interrupts(NULL, NULL), AKERR_NULLPOINTER);
|
||
|
|
TEST_REQUIRE_STATUS(akbasic_runtime_scan_labels(NULL), AKERR_NULLPOINTER);
|
||
|
|
|
||
|
|
harness_stop();
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief The label prescan reads LABEL where it is a statement, and nowhere else.
|
||
|
|
*
|
||
|
|
* The forward jump itself is covered by the golden case at
|
||
|
|
* tests/language/statements/label_forward.bas. What is here is the part a
|
||
|
|
* running program cannot show: that a string literal holding the word LABEL
|
||
|
|
* files nothing, and that a second statement on a line is still a statement.
|
||
|
|
*/
|
||
|
|
static akerr_ErrorContext AKERR_NOIGNORE *test_prescan_boundaries(void)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
int64_t line = 0;
|
||
|
|
|
||
|
|
TEST_REQUIRE_OK(harness_start(NULL));
|
||
|
|
PASS(errctx, load_program("1 PRINT \"LABEL DECOY\"\n"
|
||
|
|
"2 A# = 1 : LABEL SECOND\n"
|
||
|
|
"3 LABELLED# = 2\n"));
|
||
|
|
|
||
|
|
PASS(errctx, akbasic_environment_get_label(HARNESS_RUNTIME.environment, "SECOND", &line));
|
||
|
|
TEST_REQUIRE_INT(line, 2);
|
||
|
|
|
||
|
|
TEST_REQUIRE_STATUS(akbasic_environment_get_label(HARNESS_RUNTIME.environment, "DECOY", &line),
|
||
|
|
AKBASIC_ERR_UNDEFINED);
|
||
|
|
/* LABELLED# is an identifier that starts with the word, not the verb. */
|
||
|
|
TEST_REQUIRE_STATUS(akbasic_environment_get_label(HARNESS_RUNTIME.environment, "ED", &line),
|
||
|
|
AKBASIC_ERR_UNDEFINED);
|
||
|
|
|
||
|
|
harness_stop();
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
int main(void)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
ATTEMPT {
|
||
|
|
CATCH(errctx, test_enter_and_return());
|
||
|
|
CATCH(errctx, test_label_handler());
|
||
|
|
CATCH(errctx, test_no_reentry());
|
||
|
|
CATCH(errctx, test_nested_gosub_keeps_the_flag());
|
||
|
|
CATCH(errctx, test_arm_and_disarm());
|
||
|
|
CATCH(errctx, test_new_disarms());
|
||
|
|
CATCH(errctx, test_undefined_label_is_reported());
|
||
|
|
CATCH(errctx, test_arm_refusals());
|
||
|
|
CATCH(errctx, test_prescan_boundaries());
|
||
|
|
} CLEANUP {
|
||
|
|
} PROCESS(errctx) {
|
||
|
|
} HANDLE_DEFAULT(errctx) {
|
||
|
|
LOG_ERROR_WITH_MESSAGE(errctx, "interrupt test failed");
|
||
|
|
akbasic_test_failures += 1;
|
||
|
|
} FINISH_NORETURN(errctx);
|
||
|
|
|
||
|
|
return akbasic_test_failures;
|
||
|
|
}
|