/** * @file sink_stdio.c * @brief Tests the stdio sink, byte for byte. * * The double newline on an error line is the assertion that matters: the caller's * message already ends in \n and writeln adds another, and * tests/language/array_outofbounds.txt ends in 0a 0a because of it. See TODO.md * section 1.8. */ #include #include #include #include #include "testutil.h" static char OUTPUT[1024]; int main(void) { akbasic_TextSink sink; akbasic_StdioSink state; FILE *out = NULL; FILE *in = NULL; char line[64]; bool eof = false; TEST_REQUIRE_OK(akbasic_error_register()); memset(OUTPUT, 0, sizeof(OUTPUT)); out = fmemopen(OUTPUT, sizeof(OUTPUT), "w"); TEST_REQUIRE(out != NULL, "could not open the output buffer"); setvbuf(out, NULL, _IONBF, 0); in = fmemopen((void *)(uintptr_t)"first\nsecond\n", 13, "r"); TEST_REQUIRE(in != NULL, "could not open the input buffer"); TEST_REQUIRE_OK(akbasic_sink_init_stdio(&sink, &state, out, in)); /* write adds nothing; writeln adds exactly one newline. */ TEST_REQUIRE_OK(sink.write(&sink, "AB")); TEST_REQUIRE_OK(sink.write(&sink, "CD")); TEST_REQUIRE_OK(sink.writeln(&sink, "EF")); TEST_REQUIRE_STR(OUTPUT, "ABCDEF\n"); /* * The error-line shape: a message that already ends in \n, handed to writeln, * produces two. Reproducing this exactly is what makes the golden corpus * pass. */ memset(OUTPUT, 0, sizeof(OUTPUT)); rewind(out); TEST_REQUIRE_OK(sink.writeln(&sink, "? 20 : RUNTIME ERROR something\n")); TEST_REQUIRE_STR(OUTPUT, "? 20 : RUNTIME ERROR something\n\n"); TEST_REQUIRE_INT(strlen(OUTPUT), 32); TEST_REQUIRE_INT(OUTPUT[30], '\n'); TEST_REQUIRE_INT(OUTPUT[31], '\n'); /* readline strips the terminator and reports EOF without raising. */ TEST_REQUIRE_OK(sink.readline(&sink, line, sizeof(line), &eof)); TEST_REQUIRE(!eof, "the first line is not EOF"); TEST_REQUIRE_STR(line, "first"); TEST_REQUIRE_OK(sink.readline(&sink, line, sizeof(line), &eof)); TEST_REQUIRE_STR(line, "second"); TEST_REQUIRE_OK(sink.readline(&sink, line, sizeof(line), &eof)); TEST_REQUIRE(eof, "running off the end must set eof, not raise"); /* Argument validation. */ TEST_REQUIRE_STATUS(sink.write(&sink, NULL), AKERR_NULLPOINTER); TEST_REQUIRE_STATUS(sink.readline(&sink, line, 1, &eof), AKBASIC_ERR_BOUNDS); TEST_REQUIRE_STATUS(akbasic_sink_init_stdio(NULL, &state, out, in), AKERR_NULLPOINTER); TEST_REQUIRE_STATUS(akbasic_sink_init_stdio(&sink, NULL, out, in), AKERR_NULLPOINTER); fclose(out); fclose(in); return akbasic_test_failures; }