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:
+21
-10
@@ -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)
|
||||
|
||||
@@ -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 "../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
@@ -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.
|
||||
|
||||
@@ -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