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:
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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.
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
abc
|
||||||
Reference in New Issue
Block a user