79 lines
2.7 KiB
C
79 lines
2.7 KiB
C
|
|
/**
|
||
|
|
* @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 <stdio.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
#include <akbasic/error.h>
|
||
|
|
#include <akbasic/sink.h>
|
||
|
|
|
||
|
|
#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;
|
||
|
|
}
|