tests: refactor testing and implement a bunch of tests
* tests: split of symtable testing into its own suite makes sense to be there, not in the lisp API * tests: Added string view suite sv_copy is the only function, but we may have others later. * tests: Meaningful and pretty logging for tests * tests: slight cleanliness * tests: c23 allows you to inline stack allocated arrays in struct decls * test: Added definition to make default testing less verbose TEST_VERBOSE is a preprocesser directive which TEST is dependent on. By default it is 0, in which case TEST simply fails if the condition is not true. Otherwise, a full log (as done previously) is made. * Makefile: added mode flag for full logs MODE=full will initialise a debug build with all logs, including test logs. Otherwise, MODE=debug just sets up standard debug build with main logs but no testing logs. MODE=release optimises and strips all logs. * tests: fix size of LISP_API_SUITE tests * test_lisp_api: int_test -> smi_test, added smi_oob_test * test_lisp_api: sym_test -> sym_fresh_test * test_lisp_api: added sym_unique_test * alisp.org: Added some tasks * symtable: sym_table_cleanup -> sym_table_free * lisp: split off lisp_free as it's own function lisp_free will do a shallow clean of any object, freeing its associated memory. It won't recur through any containers, nor will it freakout if you give it something that is constant (symbols, small integers, NIL, etc). * test_lisp_api: added sys_test * test_stream: basic skeleton * test_stream: implement stream_test_string * test_stream: Enable only stream_test_string * tests: enable STREAM_SUITE * sv: fix possible runtime issue with NULL SV's in sv_copy * alisp.org: add TODOs for all the tests required for streams * tests: Better suite creation While the previous method of in-lining a stack allocated array of tests into the suite struct declaration was nice, we had to update size manually. This macro will allow us to just append new tests to the suite without having to care for that. It generates a uniquely named variable for the test array, then uses that test array in the suite declaration. Nice and easy. * test: TEST_INIT macro as a prologue for any unit test * 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. * stream: Make stream name a constant cstr We don't deal with the memory for it anyway. * 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. * 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). * 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. * tests: TEST_INIT -> TEST_START, TEST_PASSED -> TEST_END * tests: TEST_START only logs if TEST_VERBOSE is enabled. * test_lisp_api: "cons'" -> "conses" * alisp.org: Mark off completed stream_test_file * test_stream: implement stream_test_peek_next * test_stream: randomise filename Just to make sure it's not hardcoded or anything. * test_stream: make filename bigger, and increase the random alphabet * test: seed random number generator * test_stream: don't write null terminator to mock file * 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. * test_stream: implement stream_test_seek * stream: ensure stream_stop resets the FILE pointer if STREAM_TYPE_FILE * stream: stream_substr's call to stream_seek_forward refactored Following stream_seek_forward's own refactor, where we get offsets back instead of just a boolean, we should verify that offset. * main: put stream_stop before FILE pointer close As stream_stop requires a valid FILE pointer (fseek), we need to do it before we close the pipe. * test_vec: vec_test_substr -> vec_test_gen_substr * test_stream: implement stream_test_substr * alisp: add TODO for sv_t
This commit is contained in:
47
src/lisp.c
47
src/lisp.c
@@ -26,7 +26,7 @@ void sys_free(sys_t *sys)
|
||||
{
|
||||
static_assert(NUM_TAGS == 5);
|
||||
|
||||
sym_table_cleanup(&sys->symtable);
|
||||
sym_table_free(&sys->symtable);
|
||||
if (sys->memory.size == 0)
|
||||
return;
|
||||
|
||||
@@ -34,26 +34,7 @@ void sys_free(sys_t *sys)
|
||||
for (size_t i = 0; i < VEC_SIZE(&sys->memory, lisp_t **); ++i)
|
||||
{
|
||||
lisp_t *allocated = VEC_GET(&sys->memory, i, lisp_t *);
|
||||
switch (get_tag(allocated))
|
||||
{
|
||||
case TAG_CONS:
|
||||
// Delete the cons
|
||||
free(as_cons(allocated));
|
||||
break;
|
||||
case TAG_VEC:
|
||||
{
|
||||
vec_t *vec = as_vec(allocated);
|
||||
vec_free(vec);
|
||||
free(vec);
|
||||
break;
|
||||
}
|
||||
case TAG_NIL:
|
||||
case TAG_INT:
|
||||
case TAG_SYM:
|
||||
case NUM_TAGS:
|
||||
// shouldn't be dealt with (either constant or dealt with elsewhere)
|
||||
break;
|
||||
}
|
||||
lisp_free(allocated);
|
||||
}
|
||||
|
||||
// Free the container
|
||||
@@ -110,6 +91,30 @@ lisp_t *cdr(lisp_t *lsp)
|
||||
return CDR(lsp);
|
||||
}
|
||||
|
||||
void lisp_free(lisp_t *item)
|
||||
{
|
||||
switch (get_tag(item))
|
||||
{
|
||||
case TAG_CONS:
|
||||
// Delete the cons
|
||||
free(as_cons(item));
|
||||
break;
|
||||
case TAG_VEC:
|
||||
{
|
||||
vec_t *vec = as_vec(item);
|
||||
vec_free(vec);
|
||||
free(vec);
|
||||
break;
|
||||
}
|
||||
case TAG_NIL:
|
||||
case TAG_INT:
|
||||
case TAG_SYM:
|
||||
case NUM_TAGS:
|
||||
// shouldn't be dealt with (either constant or dealt with elsewhere)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Copyright (C) 2025, 2026 Aryadev Chavali
|
||||
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
|
||||
15
src/main.c
15
src/main.c
@@ -23,7 +23,10 @@ void usage(FILE *fp)
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int ret = 0;
|
||||
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,9 +69,9 @@ int main(int argc, char *argv[])
|
||||
|
||||
LOG("[INFO]: Initialised stream for `%s`\n", stream.name);
|
||||
end:
|
||||
if (fp)
|
||||
fclose(fp);
|
||||
stream_stop(&stream);
|
||||
if (pipe)
|
||||
fclose(pipe);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
46
src/stream.c
46
src/stream.c
@@ -5,6 +5,7 @@
|
||||
* Commentary:
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -35,7 +36,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 +51,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 +70,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 +84,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;
|
||||
}
|
||||
|
||||
@@ -96,8 +96,11 @@ void stream_stop(stream_t *stream)
|
||||
case STREAM_TYPE_STRING:
|
||||
free(stream->string.data);
|
||||
break;
|
||||
case STREAM_TYPE_PIPE:
|
||||
case STREAM_TYPE_FILE:
|
||||
// ensure we reset the FILE pointer to the start
|
||||
fseek(stream->pipe.file, 0, SEEK_SET);
|
||||
// fallthrough
|
||||
case STREAM_TYPE_PIPE:
|
||||
// Must cleanup vector
|
||||
vec_free(&stream->pipe.cache);
|
||||
break;
|
||||
@@ -236,7 +239,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 +250,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 +274,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 +286,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 +298,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)
|
||||
@@ -309,11 +317,11 @@ sv_t stream_substr(stream_t *stream, u64 size)
|
||||
|
||||
// See if I can go forward enough to make this substring
|
||||
u64 current_position = stream->position;
|
||||
bool successful = stream_seek_forward(stream, size);
|
||||
u64 successful = stream_seek_forward(stream, size);
|
||||
// Reset the position in either situation
|
||||
stream->position = current_position;
|
||||
|
||||
if (!successful)
|
||||
if (successful != size)
|
||||
return SV(NULL, 0);
|
||||
|
||||
char *ptr = NULL;
|
||||
|
||||
4
src/sv.c
4
src/sv.c
@@ -12,6 +12,10 @@
|
||||
|
||||
sv_t sv_copy(sv_t old)
|
||||
{
|
||||
if (old.size == 0)
|
||||
return SV(old.data, 0);
|
||||
else if (old.data == NULL)
|
||||
return SV(NULL, old.size);
|
||||
char *newstr = calloc(1, (old.size + 1) * sizeof(*newstr));
|
||||
memcpy(newstr, old.data, old.size);
|
||||
newstr[old.size] = '\0';
|
||||
|
||||
@@ -54,7 +54,7 @@ char *sym_table_find(sym_table_t *table, sv_t sv)
|
||||
return ENTRY_GET(table, index).data;
|
||||
}
|
||||
|
||||
void sym_table_cleanup(sym_table_t *table)
|
||||
void sym_table_free(sym_table_t *table)
|
||||
{
|
||||
// Iterate through the strings and free each of them.
|
||||
sv_t current = {0};
|
||||
|
||||
Reference in New Issue
Block a user