428 lines
15 KiB
C
428 lines
15 KiB
C
// testMyBasic.c -- tests for the my-basic adapter against the broker core.
|
|
//
|
|
// Exercises: a broker native called from BASIC, scalar/string marshalling, LIST
|
|
// and DICT collections in both directions, and a BASIC routine exported and
|
|
// invoked from C through a CalogFnT (using the live argument cursor of the
|
|
// enclosing native call).
|
|
|
|
#include "calogInternal.h"
|
|
#include "mybasicAdapter.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define CHECK(cond, msg) checkImpl((cond), (msg), __FILE__, __LINE__)
|
|
|
|
#define LIST_ELEM_BASE 10
|
|
#define LIST_ELEMS 3
|
|
|
|
static CalogT *broker = NULL;
|
|
static CalogMyBasicT *basic = NULL;
|
|
static CalogValueT recorded;
|
|
static int32_t testsRun = 0;
|
|
static int32_t testsFailed = 0;
|
|
|
|
static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
|
|
static int32_t nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t nativeBoom(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t nativeCallEcho(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t nativeCallExport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t nativeMakeDict(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t nativeMakeList(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t nativeMakeNested(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t nativeMakeStr(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t nativeRecord(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t runProgram(const char *source);
|
|
static void testDictRoundTrip(void);
|
|
static void testListFromNative(void);
|
|
static void testListToNative(void);
|
|
static void testNativeError(void);
|
|
static void testNativeFromBasic(void);
|
|
static void testNestedCollection(void);
|
|
static void testRoutineExport(void);
|
|
static void testRoutineStringArg(void);
|
|
static void testStringMarshal(void);
|
|
static void testStringReturn(void);
|
|
|
|
|
|
static void checkImpl(bool condition, const char *message, const char *file, int32_t line) {
|
|
testsRun++;
|
|
if (!condition) {
|
|
testsFailed++;
|
|
printf("FAIL %s:%d %s\n", file, line, message);
|
|
}
|
|
}
|
|
|
|
|
|
static int32_t nativeAdd(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
(void)userData;
|
|
if (argCount != 2 || args[0].type != calogIntE || args[1].type != calogIntE) {
|
|
return calogFail(result, calogErrArgE, "add expects two integers");
|
|
}
|
|
calogValueInt(result, args[0].as.i + args[1].as.i);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t nativeBoom(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
(void)args;
|
|
(void)argCount;
|
|
(void)userData;
|
|
return calogFail(result, calogErrArgE, "boom always fails");
|
|
}
|
|
|
|
|
|
static int32_t nativeCallEcho(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
CalogMyBasicT *context;
|
|
CalogFnT *routine;
|
|
int32_t status;
|
|
|
|
// Lowercase name on purpose: calogMyBasicExportRoutine must uppercase it to match
|
|
// BASIC's "def echo". Exercises a routine invoked with a STRING argument.
|
|
context = (CalogMyBasicT *)userData;
|
|
status = calogMyBasicExportRoutine(context, "echo", &routine);
|
|
if (status != calogOkE) {
|
|
return calogFail(result, status, "failed to export routine echo");
|
|
}
|
|
status = calogFnInvoke(routine, args, argCount, result);
|
|
calogFnRelease(routine);
|
|
return status;
|
|
}
|
|
|
|
|
|
static int32_t nativeCallExport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
CalogMyBasicT *context;
|
|
CalogFnT *routine;
|
|
int32_t status;
|
|
|
|
context = (CalogMyBasicT *)userData;
|
|
status = calogMyBasicExportRoutine(context, "D", &routine);
|
|
if (status != calogOkE) {
|
|
return calogFail(result, status, "failed to export routine D");
|
|
}
|
|
status = calogFnInvoke(routine, args, argCount, result);
|
|
calogFnRelease(routine);
|
|
return status;
|
|
}
|
|
|
|
|
|
static int32_t nativeMakeDict(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
CalogAggT *aggregate;
|
|
CalogValueT key;
|
|
CalogValueT value;
|
|
int32_t status;
|
|
|
|
(void)args;
|
|
(void)argCount;
|
|
(void)userData;
|
|
status = calogAggCreate(&aggregate, calogMapE);
|
|
if (status != calogOkE) {
|
|
return calogFail(result, status, "makeDict out of memory");
|
|
}
|
|
calogValueString(&key, "a", 1);
|
|
calogValueInt(&value, 1);
|
|
calogAggSet(aggregate, &key, &value);
|
|
calogValueString(&key, "b", 1);
|
|
calogValueInt(&value, 2);
|
|
calogAggSet(aggregate, &key, &value);
|
|
calogValueAgg(result, aggregate);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t nativeMakeList(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
CalogAggT *aggregate;
|
|
CalogValueT element;
|
|
int32_t status;
|
|
int32_t index;
|
|
|
|
(void)args;
|
|
(void)argCount;
|
|
(void)userData;
|
|
status = calogAggCreate(&aggregate, calogListE);
|
|
if (status != calogOkE) {
|
|
return calogFail(result, status, "makeList out of memory");
|
|
}
|
|
for (index = 0; index < LIST_ELEMS; index++) {
|
|
calogValueInt(&element, (int64_t)(index + 1) * LIST_ELEM_BASE);
|
|
status = calogAggPush(aggregate, &element);
|
|
if (status != calogOkE) {
|
|
calogValueFree(&element);
|
|
calogAggFree(aggregate);
|
|
return calogFail(result, status, "makeList push failed");
|
|
}
|
|
}
|
|
calogValueAgg(result, aggregate);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t nativeMakeNested(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
CalogAggT *outer;
|
|
int32_t status;
|
|
int32_t i;
|
|
int32_t j;
|
|
|
|
(void)args;
|
|
(void)argCount;
|
|
(void)userData;
|
|
status = calogAggCreate(&outer, calogListE);
|
|
if (status != calogOkE) {
|
|
return calogFail(result, status, "makeNested out of memory");
|
|
}
|
|
for (i = 0; i < 2; i++) {
|
|
CalogAggT *inner;
|
|
CalogValueT innerValue;
|
|
status = calogAggCreate(&inner, calogListE);
|
|
if (status != calogOkE) {
|
|
calogAggFree(outer);
|
|
return calogFail(result, status, "makeNested inner create");
|
|
}
|
|
for (j = 0; j < 2; j++) {
|
|
CalogValueT element;
|
|
calogValueInt(&element, (int64_t)(i * 2 + j + 1));
|
|
status = calogAggPush(inner, &element);
|
|
if (status != calogOkE) {
|
|
calogValueFree(&element);
|
|
calogAggFree(inner);
|
|
calogAggFree(outer);
|
|
return calogFail(result, status, "makeNested push");
|
|
}
|
|
}
|
|
calogValueAgg(&innerValue, inner);
|
|
status = calogAggPush(outer, &innerValue);
|
|
if (status != calogOkE) {
|
|
calogValueFree(&innerValue);
|
|
calogAggFree(outer);
|
|
return calogFail(result, status, "makeNested push outer");
|
|
}
|
|
}
|
|
calogValueAgg(result, outer);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t nativeMakeStr(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
int32_t status;
|
|
|
|
(void)args;
|
|
(void)argCount;
|
|
(void)userData;
|
|
status = calogValueString(result, "hi", 2);
|
|
if (status != calogOkE) {
|
|
return calogFail(result, status, "makeStr out of memory");
|
|
}
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t nativeRecord(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
int32_t status;
|
|
|
|
(void)userData;
|
|
calogValueNil(result);
|
|
calogValueFree(&recorded);
|
|
if (argCount < 1) {
|
|
return calogOkE;
|
|
}
|
|
status = calogValueCopy(&recorded, &args[0]);
|
|
if (status != calogOkE) {
|
|
return calogFail(result, status, "record copy failed");
|
|
}
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
// Each program runs on a fresh interpreter: a single my-basic context is meant
|
|
// to host one program, and disposing native-pushed collection intermediates
|
|
// does not survive a reset-and-reload cleanly. callExport carries its context as
|
|
// userData, so it is re-registered for each fresh context.
|
|
static int32_t runProgram(const char *source) {
|
|
int32_t status;
|
|
|
|
if (basic != NULL) {
|
|
calogMyBasicDestroy(basic);
|
|
basic = NULL;
|
|
}
|
|
status = calogMyBasicCreate(&basic, broker, 1);
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
calogRegister(broker, "callExport", nativeCallExport, basic);
|
|
calogRegister(broker, "callEcho", nativeCallEcho, basic);
|
|
calogMyBasicExpose(basic, "add");
|
|
calogMyBasicExpose(basic, "record");
|
|
calogMyBasicExpose(basic, "makeList");
|
|
calogMyBasicExpose(basic, "makeDict");
|
|
calogMyBasicExpose(basic, "makeNested");
|
|
calogMyBasicExpose(basic, "makeStr");
|
|
calogMyBasicExpose(basic, "boom");
|
|
calogMyBasicExpose(basic, "callExport");
|
|
calogMyBasicExpose(basic, "callEcho");
|
|
return calogMyBasicRun(basic, source);
|
|
}
|
|
|
|
|
|
static void testDictRoundTrip(void) {
|
|
CalogValueT key;
|
|
CalogValueT *found;
|
|
int32_t status;
|
|
|
|
status = runProgram("record(makedict())");
|
|
CHECK(status == calogOkE, "dict round trip run");
|
|
CHECK(recorded.type == calogAggE, "dict marshalled to aggregate");
|
|
CHECK(recorded.as.agg->pairCount == 2, "dict pair count");
|
|
|
|
calogValueString(&key, "a", 1);
|
|
found = calogAggGet(recorded.as.agg, &key);
|
|
calogValueFree(&key);
|
|
CHECK(found != NULL && found->type == calogIntE && found->as.i == 1, "dict key a");
|
|
|
|
calogValueString(&key, "b", 1);
|
|
found = calogAggGet(recorded.as.agg, &key);
|
|
calogValueFree(&key);
|
|
CHECK(found != NULL && found->type == calogIntE && found->as.i == 2, "dict key b");
|
|
}
|
|
|
|
|
|
static void testListFromNative(void) {
|
|
int32_t status;
|
|
|
|
status = runProgram("record(makelist())");
|
|
CHECK(status == calogOkE, "list from native run");
|
|
CHECK(recorded.type == calogAggE, "aggregate result round-tripped through BASIC LIST");
|
|
CHECK(recorded.as.agg->arrayCount == LIST_ELEMS, "round-tripped list count");
|
|
CHECK(recorded.as.agg->array[0].as.i == 10 && recorded.as.agg->array[2].as.i == 30, "round-tripped list values");
|
|
}
|
|
|
|
|
|
static void testListToNative(void) {
|
|
int32_t status;
|
|
|
|
status = runProgram("record(list(10, 20, 30))");
|
|
CHECK(status == calogOkE, "list to native run");
|
|
CHECK(recorded.type == calogAggE, "BASIC list marshalled to aggregate");
|
|
CHECK(recorded.as.agg->arrayCount == LIST_ELEMS, "list array count");
|
|
CHECK(recorded.as.agg->array[0].as.i == 10 && recorded.as.agg->array[1].as.i == 20 && recorded.as.agg->array[2].as.i == 30, "list array values");
|
|
}
|
|
|
|
|
|
static void testNativeError(void) {
|
|
int32_t status;
|
|
|
|
status = runProgram("boom()");
|
|
CHECK(status != calogOkE, "failing native surfaces as a run error");
|
|
}
|
|
|
|
|
|
static void testNativeFromBasic(void) {
|
|
int32_t status;
|
|
|
|
status = runProgram("record(add(3, 4))");
|
|
CHECK(status == calogOkE, "native from BASIC run");
|
|
CHECK(recorded.type == calogIntE && recorded.as.i == 7, "native add result recorded");
|
|
}
|
|
|
|
|
|
static void testNestedCollection(void) {
|
|
CalogAggT *inner0;
|
|
CalogAggT *inner1;
|
|
int32_t status;
|
|
|
|
// A list-of-lists round-trips aggregate -> BASIC LIST -> aggregate, exercising
|
|
// nested-collection ownership in both directions.
|
|
status = runProgram("record(makenested())");
|
|
CHECK(status == calogOkE, "nested collection run");
|
|
CHECK(recorded.type == calogAggE && recorded.as.agg->arrayCount == 2, "nested outer list count");
|
|
CHECK(recorded.as.agg->array[0].type == calogAggE && recorded.as.agg->array[1].type == calogAggE, "nested elements are lists");
|
|
inner0 = recorded.as.agg->array[0].as.agg;
|
|
inner1 = recorded.as.agg->array[1].as.agg;
|
|
CHECK(inner0->arrayCount == 2 && inner0->array[0].as.i == 1 && inner0->array[1].as.i == 2, "nested inner list 0");
|
|
CHECK(inner1->arrayCount == 2 && inner1->array[0].as.i == 3 && inner1->array[1].as.i == 4, "nested inner list 1");
|
|
}
|
|
|
|
|
|
static void testRoutineExport(void) {
|
|
int32_t status;
|
|
|
|
status = runProgram("def d(n)\nreturn n * 2\nenddef\nrecord(callexport(21))");
|
|
CHECK(status == calogOkE, "routine export run");
|
|
CHECK(recorded.type == calogIntE && recorded.as.i == 42, "exported BASIC routine invoked from C");
|
|
}
|
|
|
|
|
|
static void testRoutineStringArg(void) {
|
|
int32_t status;
|
|
|
|
// Invokes an exported routine with a STRING argument (marshalled into
|
|
// mb_eval_routine, which borrows it) and returns a string back.
|
|
status = runProgram("def echo(s)\nreturn s\nenddef\nrecord(callecho(\"hello\"))");
|
|
CHECK(status == calogOkE, "routine string-arg run");
|
|
CHECK(recorded.type == calogStringE && recorded.as.s.length == 5, "routine string-arg result length");
|
|
CHECK(memcmp(recorded.as.s.bytes, "hello", 5) == 0, "routine invoked with a string argument");
|
|
}
|
|
|
|
|
|
static void testStringMarshal(void) {
|
|
int32_t status;
|
|
|
|
status = runProgram("record(\"hello\")");
|
|
CHECK(status == calogOkE, "string marshal run");
|
|
CHECK(recorded.type == calogStringE && recorded.as.s.length == 5, "BASIC string marshalled length");
|
|
CHECK(memcmp(recorded.as.s.bytes, "hello", 5) == 0, "BASIC string marshalled content");
|
|
}
|
|
|
|
|
|
static void testStringReturn(void) {
|
|
int32_t status;
|
|
|
|
// A native returning a STRING into BASIC must push via mb_push_string (lazy
|
|
// destroy) rather than mb_push_value (which borrows).
|
|
status = runProgram("record(makestr())");
|
|
CHECK(status == calogOkE, "string return run");
|
|
CHECK(recorded.type == calogStringE && recorded.as.s.length == 2, "native string return length");
|
|
CHECK(memcmp(recorded.as.s.bytes, "hi", 2) == 0, "native string returned into BASIC");
|
|
}
|
|
|
|
|
|
int main(void) {
|
|
calogValueNil(&recorded);
|
|
broker = calogBrokerCreate();
|
|
if (broker == NULL) {
|
|
printf("broker create failed\n");
|
|
return 1;
|
|
}
|
|
|
|
calogRegister(broker, "add", nativeAdd, NULL);
|
|
calogRegister(broker, "record", nativeRecord, NULL);
|
|
calogRegister(broker, "makeList", nativeMakeList, NULL);
|
|
calogRegister(broker, "makeDict", nativeMakeDict, NULL);
|
|
calogRegister(broker, "makeNested", nativeMakeNested, NULL);
|
|
calogRegister(broker, "makeStr", nativeMakeStr, NULL);
|
|
calogRegister(broker, "boom", nativeBoom, NULL);
|
|
|
|
testNativeFromBasic();
|
|
testStringMarshal();
|
|
testStringReturn();
|
|
testListToNative();
|
|
testListFromNative();
|
|
testDictRoundTrip();
|
|
testNestedCollection();
|
|
testRoutineExport();
|
|
testRoutineStringArg();
|
|
testNativeError();
|
|
|
|
calogValueFree(&recorded);
|
|
if (basic != NULL) {
|
|
calogMyBasicDestroy(basic);
|
|
}
|
|
calogBrokerDestroy(broker);
|
|
|
|
printf("\n%d checks, %d failed\n", testsRun, testsFailed);
|
|
fflush(stdout);
|
|
if (testsFailed != 0) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|