/** * @file sink_tee.c * @brief Tests the composing sink that mirrors output to a second sink. * * This is the piece that lets an AKGL build draw BASIC output in a window and * still put it on stdout, without the interpreter carrying a second write. Two * stdio sinks over fmemopen buffers stand in for the two halves here -- the * composition is what is being tested, not what either half does with the bytes, * and using two of the same kind keeps this file free of SDL. */ #include #include #include #include #include "testutil.h" static char PRIMARY[1024]; static char MIRROR[1024]; /** @brief A sink whose every entry point fails, for asserting the pair stops. */ static akerr_ErrorContext AKERR_NOIGNORE *broken_write(akbasic_TextSink *self, const char *text) { PREPARE_ERROR(errctx); (void)self; (void)text; FAIL_RETURN(errctx, AKERR_IO, "this sink always fails"); } static akerr_ErrorContext AKERR_NOIGNORE *broken_clear(akbasic_TextSink *self) { PREPARE_ERROR(errctx); (void)self; FAIL_RETURN(errctx, AKERR_IO, "this sink always fails"); } int main(void) { akbasic_TextSink primary; akbasic_StdioSink primarystate; akbasic_TextSink mirror; akbasic_StdioSink mirrorstate; akbasic_TextSink broken; akbasic_TextSink tee; akbasic_TeeSink teestate; FILE *primaryout = NULL; FILE *mirrorout = NULL; FILE *in = NULL; char line[64]; bool eof = false; TEST_REQUIRE_OK(akbasic_error_register()); memset(PRIMARY, 0, sizeof(PRIMARY)); memset(MIRROR, 0, sizeof(MIRROR)); primaryout = fmemopen(PRIMARY, sizeof(PRIMARY), "w"); mirrorout = fmemopen(MIRROR, sizeof(MIRROR), "w"); in = fmemopen((void *)(uintptr_t)"typed\n", 6, "r"); TEST_REQUIRE(primaryout != NULL && mirrorout != NULL && in != NULL, "could not open the test buffers"); setvbuf(primaryout, NULL, _IONBF, 0); setvbuf(mirrorout, NULL, _IONBF, 0); TEST_REQUIRE_OK(akbasic_sink_init_stdio(&primary, &primarystate, primaryout, in)); TEST_REQUIRE_OK(akbasic_sink_init_stdio(&mirror, &mirrorstate, mirrorout, NULL)); /* A reader that is neither half is a wiring mistake, and is refused. */ TEST_REQUIRE_STATUS(akbasic_sink_init_tee(&tee, &teestate, &primary, &mirror, &broken), AKBASIC_ERR_VALUE); TEST_REQUIRE_STATUS(akbasic_sink_init_tee(&tee, &teestate, &primary, NULL, NULL), AKERR_NULLPOINTER); TEST_REQUIRE_STATUS(akbasic_sink_init_tee(NULL, &teestate, &primary, &mirror, NULL), AKERR_NULLPOINTER); TEST_REQUIRE_OK(akbasic_sink_init_tee(&tee, &teestate, &primary, &mirror, &primary)); /* Every write reaches both halves, byte for byte, including the newline. */ TEST_REQUIRE_OK(tee.write(&tee, "AB")); TEST_REQUIRE_OK(tee.writeln(&tee, "CD")); TEST_REQUIRE_STR(PRIMARY, "ABCD\n"); TEST_REQUIRE_STR(MIRROR, "ABCD\n"); /* The error-line double newline survives the composition unchanged. */ TEST_REQUIRE_OK(tee.writeln(&tee, "? 20 : RUNTIME ERROR something\n")); TEST_REQUIRE_STR(MIRROR, "ABCD\n? 20 : RUNTIME ERROR something\n\n"); /* clear reaches both; the stdio half has no screen, so it just succeeds. */ TEST_REQUIRE_OK(tee.clear(&tee)); /* readline comes from the designated reader only. */ TEST_REQUIRE_OK(tee.readline(&tee, line, sizeof(line), &eof)); TEST_REQUIRE(!eof, "the reader had a line to give"); TEST_REQUIRE_STR(line, "typed"); TEST_REQUIRE_OK(tee.readline(&tee, line, sizeof(line), &eof)); TEST_REQUIRE(eof, "running off the reader's end must set eof, not raise"); /* With no reader at all, readline reports end of input rather than raising. */ TEST_REQUIRE_OK(akbasic_sink_init_tee(&tee, &teestate, &primary, &mirror, NULL)); eof = false; TEST_REQUIRE_OK(tee.readline(&tee, line, sizeof(line), &eof)); TEST_REQUIRE(eof, "a tee with no reader reports EOF"); TEST_REQUIRE_STR(line, ""); TEST_REQUIRE_STATUS(tee.readline(&tee, NULL, sizeof(line), &eof), AKERR_NULLPOINTER); /* * A failing half stops the pair rather than being swallowed. A half-written * line is worse than a refused one: the golden corpus compares whole files. */ broken.self = &broken; broken.write = broken_write; broken.writeln = broken_write; broken.readline = NULL; broken.clear = broken_clear; TEST_REQUIRE_OK(akbasic_sink_init_tee(&tee, &teestate, &primary, &broken, NULL)); TEST_REQUIRE_STATUS(tee.write(&tee, "X"), AKERR_IO); TEST_REQUIRE_STATUS(tee.writeln(&tee, "X"), AKERR_IO); TEST_REQUIRE_STATUS(tee.clear(&tee), AKERR_IO); TEST_REQUIRE_OK(akbasic_sink_init_tee(&tee, &teestate, &broken, &primary, NULL)); TEST_REQUIRE_STATUS(tee.write(&tee, "X"), AKERR_IO); /* A sink whose state pointer was never set is caught rather than dereferenced. */ tee.self = NULL; TEST_REQUIRE_STATUS(tee.write(&tee, "X"), AKERR_NULLPOINTER); fclose(primaryout); fclose(mirrorout); fclose(in); return akbasic_test_failures; }