Compare commits

...

17 Commits

Author SHA1 Message Date
Aryadev Chavali
a7eeedf7fb test_stream: implement stream_test_seek 2026-02-06 04:54:23 +00:00
Aryadev Chavali
5bb83cfab7 stream: stream_seek will do clamped movement if offset is invalid
If a forward/backward offset is too big, we'll clamp to the edges of
the file rather than failing completely.  We return the number of
bytes moved so callers can still validate, but the stream API can now
deal with these situations a bit more effectively.
2026-02-06 04:53:10 +00:00
Aryadev Chavali
5a78b01a57 test_stream: don't write null terminator to mock file 2026-02-06 04:52:49 +00:00
Aryadev Chavali
2c1aebc8a5 test: seed random number generator 2026-02-06 04:52:40 +00:00
Aryadev Chavali
9452a14567 test_stream: make filename bigger, and increase the random alphabet 2026-02-06 04:34:10 +00:00
Aryadev Chavali
01fb0bf131 test_stream: randomise filename
Just to make sure it's not hardcoded or anything.
2026-02-05 20:51:45 +00:00
Aryadev Chavali
a662454ea7 test_stream: implement stream_test_peek_next 2026-02-05 20:47:28 +00:00
Aryadev Chavali
c503acb050 alisp.org: Mark off completed stream_test_file 2026-02-05 20:47:10 +00:00
Aryadev Chavali
fde3dbbf9a test_lisp_api: "cons'" -> "conses" 2026-02-05 20:36:02 +00:00
Aryadev Chavali
9e357cbc3b tests: TEST_START only logs if TEST_VERBOSE is enabled. 2026-02-05 20:34:49 +00:00
Aryadev Chavali
0b3d659f14 tests: TEST_INIT -> TEST_START, TEST_PASSED -> TEST_END 2026-02-05 20:34:29 +00:00
Aryadev Chavali
0e6a43ec5f test_stream: setup prologue and epilogue as fake tests in the suite
Standard old test functions, but they don't call TEST_INIT or
TEST_PASSED.  They're placed at the start and at the end of the test
array.

Those macros just do printing anyway, so they're not necessary.
2026-02-05 20:31:18 +00:00
Aryadev Chavali
2ddddf5774 test_stream: implement stream_test_file
We might need to setup a prelude for initialising a file in the
filesystem for testing here - not only does stream_test_file need it,
but I see later tests requiring an equivalence check for files and
strings (variants of a stream).
2026-02-05 20:22:03 +00:00
Aryadev Chavali
8d3f4f896f stream: do not initialise file streams with a non-empty vector
Because of the not_inlined trick, a 0 initialised SBO vector is
completely valid to use if required.  Future /vec_ensure/'s will deal
with it appropriately.  So there's no need to initialise the vector
ahead of time like this.
2026-02-05 18:53:13 +00:00
Aryadev Chavali
0318dcbb65 stream: Make stream name a constant cstr
We don't deal with the memory for it anyway.
2026-02-05 18:52:47 +00:00
Aryadev Chavali
f7cfe16c67 main: Put all variable declarations at start of main to ensure decl
There is a chance that /end/ is jumped to without the FILE pointer or
stream actually being declared.  This deals with that.
2026-02-05 18:39:49 +00:00
Aryadev Chavali
3612313e76 test: TEST_INIT macro as a prologue for any unit test 2026-02-05 07:37:36 +00:00
11 changed files with 298 additions and 55 deletions

View File

@@ -52,20 +52,20 @@ other state do we need to encode?
*** TODO Write the general parser
** WIP Unit tests :tests:
*** TODO Test streams
**** TODO Test file init
**** DONE Test file init
[[file:test/test_stream.c::void stream_test_file(void)]]
***** TODO Test successful init from real files
***** DONE Test successful init from real files
Ensure stream_size is 0 i.e. we don't read anything on creation.
Also ensure stream_eoc is false.
***** TODO Test failed init from fake files
**** TODO Test peeking and next
***** DONE Test failed init from fake files
**** DONE Test peeking and next
[[file:test/test_stream.c::void stream_test_peek_next(void)]]
- Peeking with bad streams ('\0' return)
- Peeking with good streams (no effect on position)
- Next with bad streams ('\0' return, no effect on position)
- Next with good streams (effects position)
- Peeking after next (should just work)
**** TODO Test seeking
**** DONE Test seeking
[[file:test/test_stream.c::void stream_test_seek(void)]]
- Seeking forward/backward on a bad stream (should stop at 0)
- Seeking forward/backward too far (should clamp)

View File

@@ -40,7 +40,7 @@ typedef struct
typedef struct
{
stream_type_t type;
char *name;
const char *name;
u64 position;
union
{
@@ -51,9 +51,9 @@ typedef struct
#define STREAM_DEFAULT_CHUNK 64
stream_err_t stream_init_string(stream_t *, char *, sv_t);
stream_err_t stream_init_pipe(stream_t *, char *, FILE *);
stream_err_t stream_init_file(stream_t *, char *, FILE *);
stream_err_t stream_init_string(stream_t *, const char *, sv_t);
stream_err_t stream_init_pipe(stream_t *, const char *, FILE *);
stream_err_t stream_init_file(stream_t *, const char *, FILE *);
void stream_stop(stream_t *);
// End of Content (i.e. we've consumed all cached content/file)
@@ -66,9 +66,9 @@ char stream_next(stream_t *);
// Peek current character, do not push position
char stream_peek(stream_t *);
// Move forward or backward in the stream, return success of operation
bool stream_seek(stream_t *, i64);
bool stream_seek_forward(stream_t *, u64);
bool stream_seek_backward(stream_t *, u64);
u64 stream_seek(stream_t *, i64);
u64 stream_seek_forward(stream_t *, u64);
u64 stream_seek_backward(stream_t *, u64);
// Return a relative substring of a given size
sv_t stream_substr(stream_t *, u64);

View File

@@ -24,6 +24,9 @@ void usage(FILE *fp)
int main(int argc, char *argv[])
{
int ret = 0;
FILE *pipe = NULL;
stream_t stream = {0};
if (argc == 1)
{
usage(stderr);
@@ -35,8 +38,6 @@ int main(int argc, char *argv[])
TODO("alisp doesn't support multiple files currently.");
}
FILE *fp = NULL;
stream_t stream = {0};
if (strncmp(argv[1], "--", 2) == 0)
{
stream_err_t err = stream_init_pipe(&stream, "stdin", stdin);
@@ -55,8 +56,8 @@ int main(int argc, char *argv[])
}
else
{
fp = fopen(argv[1], "rb");
stream_err_t err = stream_init_file(&stream, argv[1], fp);
pipe = fopen(argv[1], "rb");
stream_err_t err = stream_init_file(&stream, argv[1], pipe);
if (err)
{
fprintf(stderr, "ERROR: %s from `%s`\n", stream_err_to_cstr(err),
@@ -68,8 +69,8 @@ int main(int argc, char *argv[])
LOG("[INFO]: Initialised stream for `%s`\n", stream.name);
end:
if (fp)
fclose(fp);
if (pipe)
fclose(pipe);
stream_stop(&stream);
return ret;
}

View File

@@ -35,7 +35,8 @@ const char *stream_err_to_cstr(stream_err_t err)
}
}
stream_err_t stream_init_string(stream_t *stream, char *name, sv_t contents)
stream_err_t stream_init_string(stream_t *stream, const char *name,
sv_t contents)
{
if (!stream)
return STREAM_ERR_INVALID_PTR;
@@ -49,7 +50,7 @@ stream_err_t stream_init_string(stream_t *stream, char *name, sv_t contents)
return STREAM_ERR_OK;
}
stream_err_t stream_init_pipe(stream_t *stream, char *name, FILE *pipe)
stream_err_t stream_init_pipe(stream_t *stream, const char *name, FILE *pipe)
{
if (!stream)
return STREAM_ERR_INVALID_PTR;
@@ -68,7 +69,7 @@ stream_err_t stream_init_pipe(stream_t *stream, char *name, FILE *pipe)
return STREAM_ERR_OK;
}
stream_err_t stream_init_file(stream_t *stream, char *name, FILE *pipe)
stream_err_t stream_init_file(stream_t *stream, const char *name, FILE *pipe)
{
if (!stream)
return STREAM_ERR_INVALID_PTR;
@@ -82,8 +83,6 @@ stream_err_t stream_init_file(stream_t *stream, char *name, FILE *pipe)
stream->name = name;
stream->pipe.file = pipe;
vec_init(&stream->pipe.cache, STREAM_DEFAULT_CHUNK);
return STREAM_ERR_OK;
}
@@ -236,7 +235,7 @@ char stream_peek(stream_t *stream)
}
}
bool stream_seek(stream_t *stream, i64 offset)
u64 stream_seek(stream_t *stream, i64 offset)
{
if (offset < 0)
return stream_seek_backward(stream, offset * -1);
@@ -247,20 +246,20 @@ bool stream_seek(stream_t *stream, i64 offset)
return true;
}
bool stream_seek_forward(stream_t *stream, u64 offset)
u64 stream_seek_forward(stream_t *stream, u64 offset)
{
if (stream_eoc(stream))
return false;
return 0;
switch (stream->type)
{
case STREAM_TYPE_STRING:
{
if (stream->position + offset >= stream->string.size)
return false;
return 0;
stream->position += offset;
return true;
return offset;
}
case STREAM_TYPE_PIPE:
case STREAM_TYPE_FILE:
@@ -271,7 +270,7 @@ bool stream_seek_forward(stream_t *stream, u64 offset)
if (stream->position + offset < stream->pipe.cache.size)
{
stream->position += offset;
return true;
return offset;
}
// Try to read chunks in till we've reached it or we're at the end of the
@@ -283,9 +282,11 @@ bool stream_seek_forward(stream_t *stream, u64 offset)
// Same principle as the stream_eoc(stream) check.
if (stream->position + offset > stream->pipe.cache.size)
return false;
{
offset = stream->pipe.cache.size - stream->position;
}
stream->position += offset;
return true;
return offset;
}
default:
FAIL("Unreachable");
@@ -293,13 +294,16 @@ bool stream_seek_forward(stream_t *stream, u64 offset)
}
}
bool stream_seek_backward(stream_t *stream, u64 offset)
u64 stream_seek_backward(stream_t *stream, u64 offset)
{
assert(stream);
if (stream->position < offset)
return false;
{
offset = stream->position;
}
stream->position -= offset;
return true;
return offset;
}
sv_t stream_substr(stream_t *stream, u64 size)

View File

@@ -6,7 +6,9 @@
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "./data.h"
#include "./test.h"
@@ -18,18 +20,19 @@
#include "./test_vec.c"
test_suite_t SUITES[] = {
VEC_SUITE, SV_SUITE, SYMTABLE_SUITE, STREAM_SUITE, LISP_API_SUITE,
SV_SUITE, VEC_SUITE, SYMTABLE_SUITE, STREAM_SUITE, LISP_API_SUITE,
};
int main(void)
{
// Seed the pseudorandom gen for subsequent tests.
srand(time(NULL));
for (u64 i = 0; i < ARRSIZE(SUITES); ++i)
{
test_suite_t suite = SUITES[i];
printf("Suite [%s]\n", suite.name);
for (u64 j = 0; j < suite.size; ++j)
{
printf("\t[%s]: Running...\n", suite.tests[j].name);
suite.tests[j].fn();
}
}

View File

@@ -14,8 +14,16 @@
#define TEST_VERBOSE 0
#endif
#define TEST_PASSED() printf("\t[%s]: Passed\n", __func__)
#define TEST_END() printf("\t[%s]: Passed\n", __func__)
#define TEST_INFO(...) \
do \
{ \
printf("\tINFO: "); \
printf(__VA_ARGS__); \
} while (0);
#if TEST_VERBOSE
#define TEST_START() printf("\t[%s]: Running...\n", __func__)
#define TEST(COND, ...) \
do \
{ \
@@ -36,6 +44,7 @@
} \
} while (0)
#else
#define TEST_START()
#define TEST(COND, ...) \
do \
{ \

View File

@@ -12,6 +12,7 @@
void smi_test(void)
{
TEST_START();
// Standard old testing, checking both sides of the number line and our set
// bounds.
i64 ints[] = {
@@ -27,11 +28,12 @@ void smi_test(void)
TEST(in == out, "%ld == %ld", in, out);
}
TEST_PASSED();
TEST_END();
}
void smi_oob_test(void)
{
TEST_START();
// These are integers that are completely out of the bounds of our standard
// tagging system due to their size. We need to use big integers for this.
i64 ints[] = {
@@ -50,11 +52,12 @@ void smi_oob_test(void)
TEST(in != out, "%ld != %ld", in, out);
}
TEST_PASSED();
TEST_END();
}
void sym_fresh_test(void)
{
TEST_START();
sys_t system = {0};
sys_init(&system);
@@ -71,11 +74,12 @@ void sym_fresh_test(void)
}
sys_free(&system);
TEST_PASSED();
TEST_END();
}
void sym_unique_test(void)
{
TEST_START();
sys_t system = {0};
sys_init(&system);
@@ -102,11 +106,12 @@ void sym_unique_test(void)
}
sys_free(&system);
TEST_PASSED();
TEST_END();
}
void cons_test(void)
{
TEST_START();
sys_t system = {0};
sys_init(&system);
@@ -138,11 +143,12 @@ void cons_test(void)
}
sys_free(&system);
TEST_PASSED();
TEST_END();
}
void sys_test(void)
{
TEST_START();
sys_t sys = {0};
sys_init(&sys);
u64 old_memory_size = sys.memory.size;
@@ -158,14 +164,14 @@ void sys_test(void)
"Interning doesn't affect system memory size");
TEST(sys.symtable.count > 0, "Interning affects symbol table");
// Creating cons' do affect memory size
// Creating conses do affect memory size
(void)cons(&sys, make_int(1), make_int(2));
TEST(sys.memory.size > 0, "Creating cons' affects memory size");
TEST(sys.memory.size > 0, "Creating conses affects memory size");
old_memory_size = sys.memory.size;
(void)cons(&sys, intern(&sys, SV("test", 4)), NIL);
TEST(sys.memory.size > old_memory_size,
"Creating cons' back to back affects memory size");
"Creating conses back to back affects memory size");
old_memory_size = sys.memory.size;
// Creating vectors does affect memory size
@@ -183,7 +189,7 @@ void sys_test(void)
TEST(sys.memory.size == 0, "sys_free cleans up memory (shallow check)");
TEST(sys.symtable.count == 0, "sys_free cleans up symtable (shallow check)");
TEST_PASSED();
TEST_END();
}
MAKE_TEST_SUITE(LISP_API_SUITE, "LISP API Tests",

View File

@@ -6,14 +6,63 @@
*/
#include <malloc.h>
#include <stdio.h>
#include "./data.h"
#include "./test.h"
#include <alisp/stream.h>
#include <string.h>
char valid_filename[50];
FILE *valid_fp = NULL;
FILE *invalid_fp = NULL;
void stream_test_prologue(void)
{
const char filename_prefix[] = "build/stream_test_";
valid_filename[ARRSIZE(valid_filename) - 1] = '\0';
memcpy(valid_filename, filename_prefix, ARRSIZE(filename_prefix) - 1);
for (u64 i = ARRSIZE(filename_prefix) - 1; i < ARRSIZE(valid_filename) - 1;
++i)
{
u8 num = (rand() % 36);
if (num < 26)
{
valid_filename[i] = num + 'a';
}
else
{
valid_filename[i] = num + '0';
}
}
TEST_INFO("Creating file named `%.*s`\n", (int)ARRSIZE(valid_filename),
valid_filename);
valid_fp = fopen(valid_filename, "wb");
// This should do a few things for us
// 1) Create a file, or clear the contents of it if it exists already.
// 2) Write some content to it.
assert(valid_fp);
fwrite(words_text, ARRSIZE(words_text) - 1, 1, valid_fp);
fclose(valid_fp);
valid_fp = fopen(valid_filename, "rb");
assert(valid_fp);
invalid_fp = NULL;
}
void stream_test_epilogue(void)
{
TEST_INFO("Freeing resources and deleting file `%s`\n", valid_filename);
assert(valid_fp);
fclose(valid_fp);
remove(valid_filename);
}
void stream_test_string(void)
{
TEST_START();
sv_t test_strings[] = {
SV("hello, world!", 13),
SV("another string", 14),
@@ -49,47 +98,214 @@ void stream_test_string(void)
TEST(stream_eoc(&stream), "NULL stream is always at end of content");
stream_stop(&stream);
TEST_PASSED();
TEST_END();
}
void stream_test_file(void)
{
TODO("Not implemented");
TEST_START();
// Test that initialising works correctly
{
stream_t stream = {0};
{
stream_err_t err = stream_init_file(&stream, valid_filename, valid_fp);
TEST(err == STREAM_ERR_OK, "Expected initialisating to be okay: %s",
stream_err_to_cstr(err));
}
TEST(stream_size(&stream) == 0, "Stream doesn't read on init: size = %lu",
stream_size(&stream));
TEST(!stream_eoc(&stream), "Stream should not be at the EoC from init.");
}
// try to initialise the stream again but against a nonexistent file - we're
// expecting an error.
{
stream_t stream = {0};
{
stream_err_t err = stream_init_file(&stream, NULL, invalid_fp);
TEST(err != STREAM_ERR_OK, "Expected initialisating to not be okay: %s",
stream_err_to_cstr(err));
}
}
TEST_END();
}
void stream_test_peek_next(void)
{
TODO("Not implemented");
TEST_START();
// Valid streams
{
stream_t stream = {0};
stream_init_file(&stream, valid_filename, valid_fp);
u64 old_position = stream.position;
char c1 = stream_peek(&stream);
TEST(c1 != '\0', "Peek should provide a normal character (%c)", c1);
TEST(stream.position == old_position,
"Peek should not shift the position (%lu -> %lu)", old_position,
stream.position);
char c2 = stream_next(&stream);
TEST(c2 != '\0', "Next should provide a normal character (%c)", c2);
TEST(stream.position > old_position,
"Next should shift the position (%lu -> %lu)", old_position,
stream.position);
TEST(c2 != c1,
"Next should yield a different character (%c) to the previous peek "
"(%c)",
c2, c1);
char c3 = stream_peek(&stream);
TEST(c3 == c2,
"Peeking should yield the same character (%c) as the previous next "
"(%c)",
c3, c2);
stream_stop(&stream);
}
// Invalid streams
{
stream_t stream = {0};
stream_init_file(&stream, NULL, invalid_fp);
char c = stream_peek(&stream);
TEST(c == '\0', "Invalid streams should have an invalid peek (%c)", c);
u64 old_position = stream.position;
c = stream_next(&stream);
TEST(c == '\0', "Invalid streams should have an invalid next (%c)", c);
TEST(old_position == stream.position,
"Next on an invalid stream should not affect position (%lu -> %lu)",
old_position, stream.position);
stream_stop(&stream);
}
TEST_END();
}
void stream_test_seek(void)
{
TODO("Not implemented");
TEST_START();
// Seeking on invalid streams
{
stream_t stream = {0};
stream_init_file(&stream, NULL, invalid_fp);
u64 old_position = stream.position;
TEST(!stream_seek_forward(&stream, 1),
"Shouldn't be possible to seek forward on an invalid stream.");
TEST(old_position == stream.position,
"Position shouldn't be affected when seeking forward on an invalid "
"stream"
"(%lu -> %lu)",
old_position, stream.position);
TEST(!stream_seek_backward(&stream, 1),
"Shouldn't be possible to seek backward on an invalid stream.");
TEST(old_position == stream.position,
"Position shouldn't be affected when seeking backward on an invalid "
"stream (%lu -> %lu)",
old_position, stream.position);
stream_stop(&stream);
}
// Valid streams
{
stream_t stream = {0};
stream_init_file(&stream, valid_filename, valid_fp);
u64 old_position = stream.position;
TEST(stream_seek_forward(&stream, 1),
"Okay to seek forward on a valid stream.");
TEST(old_position < stream.position,
"Position should be greater than before when seeking forward on a "
"valid stream (%lu -> %lu)",
old_position, stream.position);
TEST(stream_seek_backward(&stream, 1),
"Okay to seek backward on a valid stream.");
TEST(old_position == stream.position,
"stream_seek_forward and stream_seek_backward are inverse operations");
u64 forward_offset = stream_seek_forward(&stream, ARRSIZE(words_text) * 2);
TEST(forward_offset < ARRSIZE(words_text) * 2,
"Forward seeking by offsets greater than file size clamps (%lu "
"clamps to %lu)",
ARRSIZE(words_text) * 2, forward_offset);
u64 file_size = stream_size(&stream);
u64 backward_offset = stream_seek_backward(&stream, file_size + 1);
TEST(backward_offset == file_size,
"Backward seeking by offsets greater than file size clamps (%lu "
"clamps to %lu)",
file_size + 1, backward_offset);
TEST(stream.position == 0,
"Clamped forward and clamped backward seeking "
"leads to start of stream (position=%lu)",
stream.position);
i64 r_forward_offset = (rand() % (file_size - 1)) + 1;
i64 r_backward_offset = (rand() % (file_size - 1)) + 1;
while (r_backward_offset >= r_forward_offset)
r_backward_offset = (rand() % (file_size - 1)) + 1;
TEST(stream_seek(&stream, r_forward_offset) == (u64)r_forward_offset,
"Seeking by a random positive offset (%lu) is valid",
r_forward_offset);
TEST(stream_seek(&stream, -r_backward_offset) == (u64)r_backward_offset,
"Seeking backward by a random negative offset (%lu) is valid",
r_backward_offset);
TEST(
(i64)stream.position == r_forward_offset - r_backward_offset,
"Stream position (%lu) is exactly shifted by seeking offsets described "
"above.",
stream.position);
stream_stop(&stream);
}
TEST_END();
}
void stream_test_substr(void)
{
TEST_START();
TODO("Not implemented");
}
void stream_test_till(void)
{
TEST_START();
TODO("Not implemented");
}
void stream_test_while(void)
{
TEST_START();
TODO("Not implemented");
}
void stream_test_line_col(void)
{
TEST_START();
TODO("Not implemented");
}
MAKE_TEST_SUITE(STREAM_SUITE, "Stream Tests",
MAKE_TEST_FN(stream_test_string), );
MAKE_TEST_FN(stream_test_prologue),
MAKE_TEST_FN(stream_test_string),
MAKE_TEST_FN(stream_test_file),
MAKE_TEST_FN(stream_test_peek_next),
MAKE_TEST_FN(stream_test_seek),
MAKE_TEST_FN(stream_test_epilogue), );
/* Copyright (C) 2026 Aryadev Chavali

View File

@@ -13,6 +13,7 @@
void sv_copy_test(void)
{
TEST_START();
static_assert(ARRSIZE(unique_words) > 3, "Expected at least 3 unique words");
for (u64 i = 0; i < 3; ++i)
{

View File

@@ -10,6 +10,7 @@
void symtable_test(void)
{
TEST_START();
sym_table_t table = {0};
sym_table_init(&table);
for (u64 i = 0; i < ARRSIZE(words); ++i)
@@ -19,7 +20,7 @@ void symtable_test(void)
ARRSIZE(unique_words));
sym_table_free(&table);
TEST_PASSED();
TEST_END();
}
MAKE_TEST_SUITE(SYMTABLE_SUITE, "Symbol Table Tests",

View File

@@ -10,6 +10,7 @@
void vec_test_concat(void)
{
TEST_START();
sys_t system = {0};
sys_init(&system);
@@ -33,11 +34,12 @@ void vec_test_concat(void)
strlen(words_text));
sys_free(&system);
TEST_PASSED();
TEST_END();
}
void vec_test_substr(void)
{
TEST_START();
sys_t system = {0};
sys_init(&system);
// Generating substrings
@@ -66,7 +68,7 @@ void vec_test_substr(void)
}
sys_free(&system);
TEST_PASSED();
TEST_END();
}
MAKE_TEST_SUITE(VEC_SUITE, "Vector Tests",