// 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 #include #include #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; }