cnfparse.c

Small library to make config parsing easier
git clone git://git.finwo.net/lib/cnfparse.c
Log | Files | Refs | README | LICENSE

commit 6d9c129a2ee42c448076194d825e7a4c8a26a465
parent a20c931ff50909a4655bd746aa65b460d61c7d74
Author: finwo <finwo@pm.me>
Date:   Thu, 27 Aug 2026 00:00:00 +0200

Add basic tests

Diffstat:
MMakefile | 21+++++++++++++++++----
Rtest/main.c -> example.c | 0
Msrc/cnfparse.c | 1-
Atest/.gitignore | 1+
Atest/0000-basics.c | 253+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 271 insertions(+), 5 deletions(-)

diff --git a/Makefile b/Makefile @@ -11,7 +11,7 @@ CFLAGS?=-Wall -s -O2 include lib/.dep/config.mk SRC+=src/cnfparse.c -SRC+=test/main.c +EXAMPLE_SRC:=example.c OBJ:=$(SRC:.c=.o) OBJ:=$(OBJ:.cc=.o) @@ -20,18 +20,31 @@ CFLAGS+=$(INCLUDES) LDFLAGS+=$(CFLAGS) +TESTS:= +TESTS+=$(patsubst %.c,%,$(wildcard test/*.c)) + .PHONY: default default: $(BIN) README.md .c.o: $(CC) $(CFLAGS) -c -o $@ $< -$(BIN): $(OBJ) - $(CC) $(LDFLAGS) $(OBJ) -o $@ +$(BIN): $(OBJ) $(EXAMPLE_SRC:.c=.o) + $(CC) $(CFLAGS) $^ -o $@ + +# Test targets — each file is compiled independently and run +$(TESTS): $(OBJ) $(TESTS:=.c) + $(CC) $@.c $(SRC) -I test $(CFLAGS) -o $@ + +.PHONY: tests +tests: $(TESTS) $(OBJ) + for test in $(TESTS); do \ + $$test ; \ + done .PHONY: clean clean: - rm -rf $(OBJ) + rm -rf $(OBJ) $(TESTS) README.md: src/cnfparse.h stddoc < src/cnfparse.h > README.md diff --git a/test/main.c b/example.c diff --git a/src/cnfparse.c b/src/cnfparse.c @@ -148,7 +148,6 @@ struct cnf_directive * cnf_directive_read(FILE *fd) { exit(1); } - printf("char: %c\n", c); } cnf_directive_read_finalize: diff --git a/test/.gitignore b/test/.gitignore @@ -0,0 +1 @@ +/0000-basics diff --git a/test/0000-basics.c b/test/0000-basics.c @@ -0,0 +1,253 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "assert.h" +#include "cnfparse.h" + +static FILE *open_string(const char *text) { + return fmemopen((void *)text, strlen(text), "r"); +} + +void test_empty_file() { + FILE *fd = open_string(""); + ASSERT("fmemopen succeeds", fd != NULL); + struct cnf_directive *dir = cnf_directive_read(fd); + ASSERT("empty file returns NULL", dir == NULL); + fclose(fd); +} + +void test_blank_lines_only() { + FILE *fd = open_string("\n\n \t\r\n"); + ASSERT("fmemopen succeeds", fd != NULL); + struct cnf_directive *dir = cnf_directive_read(fd); + ASSERT("blank lines return NULL", dir == NULL); + fclose(fd); +} + +void test_comment_only_line() { + FILE *fd = open_string("# this is a comment line\n"); + ASSERT("fmemopen succeeds", fd != NULL); + struct cnf_directive *dir = cnf_directive_read(fd); + ASSERT("comment-only line returns NULL", dir == NULL); + fclose(fd); +} + +void test_single_word_no_args() { + FILE *fd = open_string("directive\n"); + ASSERT("fmemopen succeeds", fd != NULL); + struct cnf_directive *dir = cnf_directive_read(fd); + ASSERT("non-empty directive returned", dir != NULL); + ASSERT_STRING_EQUALS("directive", dir->name); + ASSERT_EQUALS(0, dir->argc); + cnf_directive_free(dir); + fclose(fd); +} + +void test_leading_whitespace() { + FILE *fd = open_string(" \t leading_spaces\n"); + ASSERT("fmemopen succeeds", fd != NULL); + struct cnf_directive *dir = cnf_directive_read(fd); + ASSERT("directive with leading whitespace returned", dir != NULL); + ASSERT_STRING_EQUALS("leading_spaces", dir->name); + ASSERT_EQUALS(0, dir->argc); + cnf_directive_free(dir); + fclose(fd); +} + +void test_trailing_whitespace() { + FILE *fd = open_string("trailing \t\n"); + ASSERT("fmemopen succeeds", fd != NULL); + struct cnf_directive *dir = cnf_directive_read(fd); + ASSERT("directive with trailing whitespace returned", dir != NULL); + ASSERT_STRING_EQUALS("trailing", dir->name); + ASSERT_EQUALS(0, dir->argc); + cnf_directive_free(dir); + fclose(fd); +} + +void test_multiple_space_separated_args() { + FILE *fd = open_string("add 10.0.0.0/8 via 192.168.1.1\n"); + ASSERT("fmemopen succeeds", fd != NULL); + struct cnf_directive *dir = cnf_directive_read(fd); + ASSERT("directive with args returned", dir != NULL); + ASSERT_STRING_EQUALS("add", dir->name); + ASSERT_EQUALS(3, dir->argc); + ASSERT_STRING_EQUALS("10.0.0.0/8", dir->argv[0]); + ASSERT_STRING_EQUALS("via", dir->argv[1]); + ASSERT_STRING_EQUALS("192.168.1.1", dir->argv[2]); + cnf_directive_free(dir); + fclose(fd); +} + +void test_multiple_spaces_between_args() { + FILE *fd = open_string(" del 10.1.0.0/16\n"); + ASSERT("fmemopen succeeds", fd != NULL); + struct cnf_directive *dir = cnf_directive_read(fd); + ASSERT("directive with extra spaces returned", dir != NULL); + ASSERT_STRING_EQUALS("del", dir->name); + ASSERT_EQUALS(1, dir->argc); + ASSERT_STRING_EQUALS("10.1.0.0/16", dir->argv[0]); + cnf_directive_free(dir); + fclose(fd); +} + +void test_quoted_double_args() { + FILE *fd = open_string("set note \"hello world\" and \"foo bar\"\n"); + ASSERT("fmemopen succeeds", fd != NULL); + struct cnf_directive *dir = cnf_directive_read(fd); + ASSERT("directive with quoted args returned", dir != NULL); + ASSERT_STRING_EQUALS("set", dir->name); + ASSERT_EQUALS(4, dir->argc); + ASSERT_STRING_EQUALS("note", dir->argv[0]); + ASSERT_STRING_EQUALS("hello world", dir->argv[1]); + ASSERT_STRING_EQUALS("and", dir->argv[2]); + ASSERT_STRING_EQUALS("foo bar", dir->argv[3]); + fclose(fd); +} + +void test_quoted_single_args() { + FILE *fd = open_string("op 'single quoted'\n"); + ASSERT("fmemopen succeeds", fd != NULL); + struct cnf_directive *dir = cnf_directive_read(fd); + ASSERT("directive with single-quoted arg returned", dir != NULL); + ASSERT_STRING_EQUALS("op", dir->name); + ASSERT_EQUALS(1, dir->argc); + ASSERT_STRING_EQUALS("single quoted", dir->argv[0]); + cnf_directive_free(dir); + fclose(fd); +} + +void test_escaped_quote_in_double() { + FILE *fd = open_string("op arg \"a \\\" b\"\n"); + ASSERT("fmemopen succeeds", fd != NULL); + struct cnf_directive *dir = cnf_directive_read(fd); + ASSERT("directive with escaped quote returned", dir != NULL); + ASSERT_STRING_EQUALS("op", dir->name); + ASSERT_EQUALS(2, dir->argc); + ASSERT_STRING_EQUALS("arg", dir->argv[0]); + ASSERT_STRING_EQUALS("a \" b", dir->argv[1]); + cnf_directive_free(dir); + fclose(fd); +} + +void test_hash_in_quoted_string() { + FILE *fd = open_string("set note \"a # b\"\n"); + ASSERT("fmemopen succeeds", fd != NULL); + struct cnf_directive *dir = cnf_directive_read(fd); + ASSERT("directive with hash in quotes returned", dir != NULL); + ASSERT_STRING_EQUALS("set", dir->name); + ASSERT_EQUALS(2, dir->argc); + ASSERT_STRING_EQUALS("note", dir->argv[0]); + ASSERT_STRING_EQUALS("a # b", dir->argv[1]); + cnf_directive_free(dir); + fclose(fd); +} + +void test_trailing_comment() { + FILE *fd = open_string("op arg # trailing comment\n"); + ASSERT("fmemopen succeeds", fd != NULL); + struct cnf_directive *dir = cnf_directive_read(fd); + ASSERT("directive before trailing comment returned", dir != NULL); + ASSERT_STRING_EQUALS("op", dir->name); + ASSERT_EQUALS(1, dir->argc); + ASSERT_STRING_EQUALS("arg", dir->argv[0]); + cnf_directive_free(dir); + fclose(fd); +} + +void test_comment_no_space() { + FILE *fd = open_string("route 172.16.0.0/12 #comment with no space\n"); + ASSERT("fmemopen succeeds", fd != NULL); + struct cnf_directive *dir = cnf_directive_read(fd); + ASSERT("directive before tight comment returned", dir != NULL); + ASSERT_STRING_EQUALS("route", dir->name); + ASSERT_EQUALS(1, dir->argc); + ASSERT_STRING_EQUALS("172.16.0.0/12", dir->argv[0]); + cnf_directive_free(dir); + fclose(fd); +} + +void test_multiple_directives_sequence() { + FILE *fd = open_string("first one\nsecond two three\n"); + ASSERT("fmemopen succeeds", fd != NULL); + + struct cnf_directive *d1 = cnf_directive_read(fd); + ASSERT("first directive returned", d1 != NULL); + ASSERT_STRING_EQUALS("first", d1->name); + ASSERT_EQUALS(1, d1->argc); + ASSERT_STRING_EQUALS("one", d1->argv[0]); + cnf_directive_free(d1); + + struct cnf_directive *d2 = cnf_directive_read(fd); + ASSERT("second directive returned", d2 != NULL); + ASSERT_STRING_EQUALS("second", d2->name); + ASSERT_EQUALS(2, d2->argc); + ASSERT_STRING_EQUALS("two", d2->argv[0]); + ASSERT_STRING_EQUALS("three", d2->argv[1]); + cnf_directive_free(d2); + + struct cnf_directive *d3 = cnf_directive_read(fd); + ASSERT("exhausted file returns NULL", d3 == NULL); + fclose(fd); +} + +void test_blank_lines_between_directives() { + FILE *fd = open_string("\n\nfirst arg\n\n\nsecond\n"); + ASSERT("fmemopen succeeds", fd != NULL); + + struct cnf_directive *d1 = cnf_directive_read(fd); + ASSERT("directive after blank lines returned", d1 != NULL); + ASSERT_STRING_EQUALS("first", d1->name); + ASSERT_EQUALS(1, d1->argc); + cnf_directive_free(d1); + + struct cnf_directive *d2 = cnf_directive_read(fd); + ASSERT("directive after more blank lines returned", d2 != NULL); + ASSERT_STRING_EQUALS("second", d2->name); + ASSERT_EQUALS(0, d2->argc); + cnf_directive_free(d2); + + fclose(fd); +} + +void test_no_trailing_newline() { + FILE *fd = open_string("final one two three"); + ASSERT("fmemopen succeeds", fd != NULL); + struct cnf_directive *dir = cnf_directive_read(fd); + ASSERT("directive without trailing newline returned", dir != NULL); + ASSERT_STRING_EQUALS("final", dir->name); + ASSERT_EQUALS(3, dir->argc); + ASSERT_STRING_EQUALS("one", dir->argv[0]); + ASSERT_STRING_EQUALS("two", dir->argv[1]); + ASSERT_STRING_EQUALS("three", dir->argv[2]); + cnf_directive_free(dir); + fclose(fd); +} + +void test_directive_free_null() { + cnf_directive_free(NULL); + ASSERT("free NULL does not crash", 1); +} + +int main(int argc, const char *argv[]) { + RUN(test_empty_file); + RUN(test_blank_lines_only); + RUN(test_comment_only_line); + RUN(test_single_word_no_args); + RUN(test_leading_whitespace); + RUN(test_trailing_whitespace); + RUN(test_multiple_space_separated_args); + RUN(test_multiple_spaces_between_args); + RUN(test_quoted_double_args); + RUN(test_quoted_single_args); + RUN(test_escaped_quote_in_double); + RUN(test_hash_in_quoted_string); + RUN(test_trailing_comment); + RUN(test_comment_no_space); + RUN(test_multiple_directives_sequence); + RUN(test_blank_lines_between_directives); + RUN(test_no_trailing_newline); + RUN(test_directive_free_null); + return TEST_REPORT(); +}