Files
alisp/test/test_lisp_api.c
oreodave a65964e2f7 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
2026-02-06 06:08:13 +00:00

213 lines
5.7 KiB
C

/* test_lisp_api.c: Testing of constructors/destructors of Lisp expressions
* Created: 2026-02-04
* Author: Aryadev Chavali
* License: See end of file
* Commentary:
*/
#include "./data.h"
#include "./test.h"
#include <alisp/lisp.h>
void smi_test(void)
{
TEST_START();
// Standard old testing, checking both sides of the number line and our set
// bounds.
i64 ints[] = {
1, -1, (1 << 10) - 1, (-1) * ((1 << 10) - 1), INT_MIN, INT_MAX,
};
for (u64 i = 0; i < ARRSIZE(ints); ++i)
{
i64 in = ints[i];
lisp_t *lisp = make_int(in);
i64 out = as_int(lisp);
TEST(in == out, "%ld == %ld", in, out);
}
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[] = {
INT_MIN - 1,
INT_MAX + 1,
INT64_MIN,
INT64_MAX,
};
for (u64 i = 0; i < ARRSIZE(ints); ++i)
{
i64 in = ints[i];
lisp_t *lisp = make_int(in);
i64 out = as_int(lisp);
TEST(in != out, "%ld != %ld", in, out);
}
TEST_END();
}
void sym_fresh_test(void)
{
TEST_START();
sys_t system = {0};
sys_init(&system);
// We expect every interned symbol to get a fresh allocation, but still be a
// valid representation of the original symbol.
for (u64 i = 0; i < ARRSIZE(words); ++i)
{
const char *in = words[i];
lisp_t *lisp = intern(&system, SV((char *)in, strlen(in)));
char *out = as_sym(lisp);
TEST(in != out, "%p != %p", in, out);
TEST(strlen(in) == strlen(out), "%zu == %zu", strlen(in), strlen(out));
TEST(strncmp(in, out, strlen(in)) == 0, "`%s` == `%s`", in, out);
}
sys_free(&system);
TEST_END();
}
void sym_unique_test(void)
{
TEST_START();
sys_t system = {0};
sys_init(&system);
sv_t symbols[] = {
SV("hello", 5),
SV("goodbye", 7),
SV("display", 7),
SV("@xs'a_sh;d::a-h]", 16),
};
lisp_t *ptrs[ARRSIZE(symbols)];
for (u64 i = 0; i < ARRSIZE(symbols); ++i)
{
ptrs[i] = intern(&system, symbols[i]);
TEST(ptrs[i] != 0, "%p (derived from `" PR_SV "`) is not NIL",
(void *)ptrs[i], SV_FMT(symbols[i]));
}
for (u64 i = 0; i < ARRSIZE(symbols); ++i)
{
lisp_t *newptr = intern(&system, symbols[i]);
TEST(newptr == ptrs[i], "interning again (%p) gives us the same (%p)",
(void *)newptr, (void *)ptrs[i]);
}
sys_free(&system);
TEST_END();
}
void cons_test(void)
{
TEST_START();
sys_t system = {0};
sys_init(&system);
// Let's make a list of words using `cons`
lisp_t *lisp = NIL;
for (u64 i = 0; i < ARRSIZE(words); ++i)
{
const char *word = words[i];
lisp_t *lword = intern(&system, SV((char *)word, strlen(word)));
lisp = cons(&system, lword, lisp);
}
/*
As we've cons'd each word, we'd expect the order to be reversed. This test
will allow us to verify:
1) words have actually been added to the linked list.
2) words are in the order we expect.
in one go.
*/
u64 i = ARRSIZE(words);
for (lisp_t *iter = lisp; iter; iter = cdr(iter), --i)
{
const char *expected = words[i - 1];
lisp_t *item = car(iter);
char *got = as_sym(item);
size_t size = MIN(strlen(expected), strlen(got));
TEST(strncmp(expected, got, size) == 0, "%s == %s", expected, got);
}
sys_free(&system);
TEST_END();
}
void sys_test(void)
{
TEST_START();
sys_t sys = {0};
sys_init(&sys);
u64 old_memory_size = sys.memory.size;
// Creating integers doesn't affect memory size
(void)make_int(2000);
TEST(sys.memory.size == old_memory_size,
"Making integers doesn't affect system memory size");
// Creating symbols won't affect memory size, but does affect the symbol table
(void)intern(&sys, SV("hello world!", 12));
TEST(sys.memory.size == old_memory_size,
"Interning doesn't affect system memory size");
TEST(sys.symtable.count > 0, "Interning affects symbol table");
// Creating conses do affect memory size
(void)cons(&sys, make_int(1), make_int(2));
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 conses back to back affects memory size");
old_memory_size = sys.memory.size;
// Creating vectors does affect memory size
(void)make_vec(&sys, 8);
TEST(sys.memory.size > old_memory_size,
"Creating vectors (size 8) affects memory size");
old_memory_size = sys.memory.size;
(void)make_vec(&sys, 1000);
TEST(sys.memory.size > old_memory_size,
"Creating vectors (size 1000) affects memory size");
old_memory_size = sys.memory.size;
sys_free(&sys);
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_END();
}
MAKE_TEST_SUITE(LISP_API_SUITE, "LISP API Tests",
MAKE_TEST_FN(smi_test), MAKE_TEST_FN(smi_oob_test),
MAKE_TEST_FN(sym_fresh_test), MAKE_TEST_FN(sym_unique_test),
MAKE_TEST_FN(cons_test), MAKE_TEST_FN(sys_test), );
/* Copyright (C) 2026 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License Version 2 for
* details.
* You may distribute and modify this code under the terms of the GNU General *
* Public License Version 2, which you should have received a copy of along with
* this program. If not, please go to <https://www.gnu.org/licenses/>.
*/