425 lines
13 KiB
C
425 lines
13 KiB
C
/* test_stream.c: Stream tests
|
|
* Created: 2026-02-05
|
|
* Author: Aryadev Chavali
|
|
* License: See end of file
|
|
* Commentary:
|
|
*/
|
|
|
|
#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("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_AUTO("hello, world!"),
|
|
SV_AUTO("another string"),
|
|
sv_truncate(SV_AUTO(text), ARRSIZE(text) / 2),
|
|
};
|
|
|
|
for (u64 i = 0; i < ARRSIZE(test_strings); ++i)
|
|
{
|
|
sv_t copy = sv_copy(test_strings[i]);
|
|
|
|
stream_t stream = {0};
|
|
stream_err_t err = stream_init_string(&stream, NULL, test_strings[i]);
|
|
TEST(err == STREAM_ERR_OK, "Stream initialising did not fail: %s",
|
|
stream_err_to_cstr(err));
|
|
TEST(stream_size(&stream) == test_strings[i].size,
|
|
"Stream size is always string size (%lu == %lu)", stream_size(&stream),
|
|
test_strings[i].size);
|
|
TEST(!stream_eoc(&stream), "Not end of content already");
|
|
|
|
stream_free(&stream);
|
|
TEST(strncmp(copy.data, test_strings[i].data, copy.size) == 0,
|
|
"Freeing a stream does not free the underlying memory it was derived "
|
|
"from");
|
|
|
|
// NOTE: Okay to free since we own copy.
|
|
free((void *)copy.data);
|
|
}
|
|
|
|
stream_t stream = {0};
|
|
stream_err_t err = stream_init_string(&stream, NULL, SV(NULL, 0));
|
|
TEST(err == STREAM_ERR_OK, "NULL stream initialising did not fail: %s",
|
|
stream_err_to_cstr(err));
|
|
TEST(stream_size(&stream) == 0, "NULL stream size is 0");
|
|
TEST(stream_eoc(&stream), "NULL stream is always at end of content");
|
|
stream_free(&stream);
|
|
|
|
TEST_END();
|
|
}
|
|
|
|
void stream_test_file(void)
|
|
{
|
|
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_eoc(&stream), "Stream should not be at the EoC from init.");
|
|
stream_free(&stream);
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
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_free(&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_free(&stream);
|
|
}
|
|
TEST_END();
|
|
}
|
|
|
|
void stream_test_seek(void)
|
|
{
|
|
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_free(&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_free(&stream);
|
|
}
|
|
|
|
TEST_END();
|
|
}
|
|
|
|
void stream_test_substr(void)
|
|
{
|
|
TEST_START();
|
|
u64 size = rand() % (ARRSIZE(words_text) - 1);
|
|
u64 position = ARRSIZE(words_text) - size - 1;
|
|
|
|
// Taking substrings of invalid streams
|
|
{
|
|
stream_t stream = {0};
|
|
stream_init_file(&stream, NULL, invalid_fp);
|
|
|
|
// Relative
|
|
{
|
|
sv_t result = stream_substr(&stream, size);
|
|
TEST(result.data == NULL && result.size == 0,
|
|
"Relative substring with size %lu on invalid stream should be NULL",
|
|
size);
|
|
}
|
|
|
|
// Absolute
|
|
{
|
|
sv_t result = stream_substr_abs(&stream, position, size);
|
|
TEST(result.data == NULL && result.size == 0,
|
|
"Absolute substring @%lu with size %lu on invalid stream should be "
|
|
"NULL",
|
|
position, size);
|
|
}
|
|
|
|
stream_free(&stream);
|
|
}
|
|
|
|
// Taking substrings of valid streams
|
|
{
|
|
stream_t stream = {0};
|
|
stream_init_file(&stream, valid_filename, valid_fp);
|
|
|
|
// Absolute
|
|
{
|
|
sv_t result = stream_substr_abs(&stream, position, size);
|
|
TEST(result.data && result.size,
|
|
"Absolute substring @%lu with size %lu on valid stream should be "
|
|
"nonzero",
|
|
position, size);
|
|
|
|
TEST(result.size == size, "Substring has right size (%lu)", result.size);
|
|
|
|
sv_t expected = sv_substr(SV_AUTO(words_text), position, size);
|
|
TEST(strncmp(result.data, expected.data, result.size) == 0,
|
|
"Expect the substring to be the same as the data we put in");
|
|
}
|
|
|
|
// Relative
|
|
{
|
|
sv_t result = stream_substr(&stream, size);
|
|
TEST(result.data && result.size,
|
|
"Relative substring with size %lu should be nonzero", size);
|
|
|
|
TEST(result.size == size, "Substring has right size (%lu)", result.size);
|
|
|
|
sv_t expected = sv_truncate(SV_AUTO(words_text), size);
|
|
TEST(strncmp(result.data, expected.data, result.size) == 0,
|
|
"Expect the substring to be the same as the data we put in");
|
|
}
|
|
|
|
// Relative substring after seeking
|
|
{
|
|
// Shift forward to a random position
|
|
assert(stream_seek_forward(&stream, position)); // not a test
|
|
sv_t result = stream_substr(&stream, size);
|
|
TEST(result.data && result.size,
|
|
"Relative substring with size %lu after seeking %lu bytes should be "
|
|
"nonzero",
|
|
size, position);
|
|
|
|
TEST(result.size == size, "Substring has right size (%lu)", result.size);
|
|
|
|
sv_t expected = sv_substr(SV_AUTO(words_text), position, size);
|
|
TEST(strncmp(result.data, expected.data, result.size) == 0,
|
|
"Expect the substring to be the same as the data we put in");
|
|
|
|
// Shift back to the original position.
|
|
assert(stream_seek_backward(&stream, position)); // not a test
|
|
}
|
|
|
|
// Bad substrings
|
|
{
|
|
{
|
|
sv_t result = stream_substr_abs(&stream, stream_size(&stream), 100);
|
|
TEST(!result.data && !result.size,
|
|
"Absolute substring at %lu of 100 bytes is invalid",
|
|
stream_size(&stream));
|
|
}
|
|
|
|
assert(stream_seek_forward(&stream, stream_size(&stream))); // not a test
|
|
{
|
|
sv_t result = stream_substr(&stream, 100);
|
|
TEST(!result.data && !result.size,
|
|
"Relative substring with size 100 after seeking %lu bytes is "
|
|
"invalid",
|
|
stream.position);
|
|
}
|
|
}
|
|
|
|
stream_free(&stream);
|
|
}
|
|
TEST_END();
|
|
}
|
|
|
|
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_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_substr),
|
|
MAKE_TEST_FN(stream_test_epilogue), );
|
|
|
|
/* 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/>.
|
|
|
|
*/
|