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
c2: v0/bin/c2
# @copilot copy the compiler
cp $< $@
test::
include v0/include.mk
+21 -10
View File
@@ -1,14 +1,25 @@
V0_SRC := # @copilot include everything in src
V0_TEST := # @copilot include everything in test
V0_SRC := $(wildcard v0/src/*.c)
//
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_TEST_OBJ := # @copilot
V0_SRC_OBJ := $(patsubst v0/%.c,v0/bin/%.o,$(V0_SRC))
V0_TEST_OBJ := $(patsubst v0/%.c,v0/bin/%.o,$(V0_TEST))
v0/bin/c2: $(V0_SRC OBJ)
# @copilot implement
v0/bin/c2: $(V0_SRC_OBJ)
$(CC) $(CFLAGS) -o $@ $^
v0/bin/test: $(V0_SRC_TEST)
# @copilot implement
V0_SRC_OBJ_NO_MAIN := $(filter-out v0/bin/src/main.o,$(V0_SRC_OBJ))
# @copilot add target that builds each .c file into a .o file.
# Ensure header dependencies are tracked correctly.
v0/bin/test: $(V0_SRC_OBJ_NO_MAIN) $(V0_TEST_OBJ)
$(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 "../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_greenTests;
static Test[] = {
// @copilot add all tests
static TestCase s_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) {
/*
@copilot loop over each test.
If the test is good, count the test as part of green test.
Otherwise, go to the next one.
(void)argc;
(void)argv;
The start of the test must log the name of the test method.
After the test, log either "[OK]" or "[FAIL]".
s_totalTests = sizeof(s_tests) / sizeof(s_tests[0]);
s_greenTests = 0;
At the very end, log the name of each failed test and the total
number of green tests compared to the total.
*/
const char* failedTests[s_totalTests + 1];
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
#define TEST_H
// @copilot fix the syntax. This is a typedef for any function taking a void and returning void.
typedef Test void(void);
typedef void (*Test)(void);
/**
* 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