Implement buffer interface and tests

- Implement buffer_open_file/string, buffer_read, buffer_close in v0/src/buffer.c

- Add tests in v0/tests/test_buffer.c and test_buffer.txt; register tests in v0/tests/test.c

- Update v0/include.mk and Makefile

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-24 08:07:38 +02:00
parent f6a1b290fc
commit 7ff3f76de5
7 changed files with 185 additions and 25 deletions
+3 -1
View File
@@ -3,6 +3,8 @@
all: c2 all: c2
c2: v0/bin/c2 c2: v0/bin/c2
# @copilot copy the compiler cp $< $@
test::
include v0/include.mk include v0/include.mk
+21 -10
View File
@@ -1,14 +1,25 @@
V0_SRC := # @copilot include everything in src V0_SRC := $(wildcard v0/src/*.c)
V0_TEST := # @copilot include everything in test //
V0_TEST := $(filter-out v0/tests/test_buffer.c,$(wildcard v0/tests/*.c))
V0_SRC_OBJ := # @copilot implement. exampleL v0/src/main.c -> v0/bin/src/main.o V0_SRC_OBJ := $(patsubst v0/%.c,v0/bin/%.o,$(V0_SRC))
V0_TEST_OBJ := # @copilot V0_TEST_OBJ := $(patsubst v0/%.c,v0/bin/%.o,$(V0_TEST))
v0/bin/c2: $(V0_SRC OBJ) v0/bin/c2: $(V0_SRC_OBJ)
# @copilot implement $(CC) $(CFLAGS) -o $@ $^
v0/bin/test: $(V0_SRC_TEST) V0_SRC_OBJ_NO_MAIN := $(filter-out v0/bin/src/main.o,$(V0_SRC_OBJ))
# @copilot implement
# @copilot add target that builds each .c file into a .o file. v0/bin/test: $(V0_SRC_OBJ_NO_MAIN) $(V0_TEST_OBJ)
# Ensure header dependencies are tracked correctly. $(CC) $(CFLAGS) -o $@ $^
test:: v0/bin/test
v0/bin/test
# Build each .c file into a .o file, tracking header dependencies.
v0/bin/%.o: v0/%.c
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -MMD -MP -c $< -o $@
-include $(V0_SRC_OBJ:.o=.d)
-include $(V0_TEST_OBJ:.o=.d)
+63
View File
@@ -0,0 +1,63 @@
#include "buffer.h"
#include <stdlib.h>
#include <stdio.h>
typedef enum {
BUFFER_FILE,
BUFFER_STRING
} BufferType;
struct Buffer {
BufferType type;
union {
FILE* file;
struct {
const char* data;
size_t pos;
} string;
} source;
};
Buffer* buffer_open_file(const char* path) {
FILE* f = fopen(path, "r");
if (f == NULL)
return NULL;
Buffer* buf = malloc(sizeof(Buffer));
if (buf == NULL) {
fclose(f);
return NULL;
}
buf->type = BUFFER_FILE;
buf->source.file = f;
return buf;
}
Buffer* buffer_open_string(const char* string) {
Buffer* buf = malloc(sizeof(Buffer));
if (buf == NULL)
return NULL;
buf->type = BUFFER_STRING;
buf->source.string.data = string;
buf->source.string.pos = 0;
return buf;
}
void buffer_close(Buffer* buffer) {
if (buffer->type == BUFFER_FILE)
fclose(buffer->source.file);
free(buffer);
}
char buffer_read(Buffer* buffer) {
if (buffer->type == BUFFER_FILE) {
int c = fgetc(buffer->source.file);
return c == EOF ? (char)-1 : (char)c;
} else {
char c = buffer->source.string.data[buffer->source.string.pos];
if (c == '\0')
return (char)-1;
buffer->source.string.pos++;
return c;
}
}
+53 -12
View File
@@ -1,24 +1,65 @@
#include "test.h" #include "test.h"
#include "../src/buffer.h"
#include <setjmp.h>
#include <stdio.h>
// @copilot implement fail() static jmp_buf s_testJmp;
static const char* s_failMsg;
void fail(const char* msg) {
s_failMsg = msg;
longjmp(s_testJmp, 1);
}
typedef struct {
const char* name;
Test func;
} TestCase;
#include "test_buffer.c"
static int s_totalTests; static int s_totalTests;
static int s_greenTests; static int s_greenTests;
static Test[] = { static TestCase s_tests[] = {
// @copilot add all tests {"buffer_string_reads_chars", test_buffer_string_reads_chars},
{"buffer_string_eof", test_buffer_string_eof},
{"buffer_string_eof_after_content", test_buffer_string_eof_after_content},
{"buffer_file_reads_chars", test_buffer_file_reads_chars},
{"buffer_file_open_fail", test_buffer_file_open_fail},
}; };
int main(int argc, char** argv) { int main(int argc, char** argv) {
/* (void)argc;
@copilot loop over each test. (void)argv;
If the test is good, count the test as part of green test.
Otherwise, go to the next one.
The start of the test must log the name of the test method. s_totalTests = sizeof(s_tests) / sizeof(s_tests[0]);
After the test, log either "[OK]" or "[FAIL]". s_greenTests = 0;
At the very end, log the name of each failed test and the total const char* failedTests[s_totalTests + 1];
number of green tests compared to the total. int failedCount = 0;
*/
for (int i = 0; i < s_totalTests; i++) {
printf("%s...", s_tests[i].name);
s_failMsg = NULL;
if (setjmp(s_testJmp) == 0) {
s_tests[i].func();
printf(" [OK]\n");
s_greenTests++;
} else {
printf(" [FAIL]: %s\n", s_failMsg ? s_failMsg : "");
failedTests[failedCount++] = s_tests[i].name;
}
}
if (failedCount > 0) {
printf("\nFailed tests:\n");
for (int i = 0; i < failedCount; i++) {
printf(" - %s\n", failedTests[i]);
}
}
printf("\n%d/%d tests passed.\n", s_greenTests, s_totalTests);
return failedCount > 0 ? 1 : 0;
} }
+1 -2
View File
@@ -4,8 +4,7 @@
#ifndef TEST_H #ifndef TEST_H
#define TEST_H #define TEST_H
// @copilot fix the syntax. This is a typedef for any function taking a void and returning void. typedef void (*Test)(void);
typedef Test void(void);
/** /**
* Fails a test. * Fails a test.
+43
View File
@@ -0,0 +1,43 @@
#include "test.h"
#include "../src/buffer.h"
#include <stdio.h>
// @copilot add test for reading from a file.
// Create test_buffer_xyz.txt if you need multiple,
// otherwise simply test_buffer.txt
static void test_buffer_string_reads_chars(void) {
Buffer* buf = buffer_open_string("hi");
if (buffer_read(buf) != 'h') fail("expected 'h'");
if (buffer_read(buf) != 'i') fail("expected 'i'");
buffer_close(buf);
}
static void test_buffer_string_eof(void) {
Buffer* buf = buffer_open_string("");
if (buffer_read(buf) != (char)-1) fail("expected -1 on empty string");
buffer_close(buf);
}
static void test_buffer_string_eof_after_content(void) {
Buffer* buf = buffer_open_string("a");
buffer_read(buf);
if (buffer_read(buf) != (char)-1) fail("expected -1 after end of string");
buffer_close(buf);
}
static void test_buffer_file_reads_chars(void) {
Buffer* buf = buffer_open_file("v0/tests/test_buffer.txt");
if (buf == NULL) fail("could not open file");
if (buffer_read(buf) != 'a') fail("expected 'a'");
if (buffer_read(buf) != 'b') fail("expected 'b'");
if (buffer_read(buf) != 'c') fail("expected 'c'");
if (buffer_read(buf) != '\n') fail("expected newline after content");
if (buffer_read(buf) != (char)-1) fail("expected -1 after file");
buffer_close(buf);
}
static void test_buffer_file_open_fail(void) {
Buffer* buf = buffer_open_file("v0/tests/does_not_exist.txt");
if (buf != NULL) fail("expected NULL for non-existent file");
}
+1
View File
@@ -0,0 +1 @@
abc