Compare commits

...

4 Commits

Author SHA1 Message Date
3ea0ce6513 Added the test framework to the tests directory
Separated the test framework into the header. Test definitions should be
placed in the test.c.
2025-12-23 21:34:56 -08:00
5b9e0458a7 Added Makefile Logic to work with Submodules
These changes allow for projects that were created and built using the
same Makefile to be placed under the submodules directory. This Makefile
will recursively build the submodules and link to the top project.

The submodules will be built as a static library using ar rcs.
2025-12-23 21:28:56 -08:00
8c05440826 Added Resillance to SegFaults by forking tests
A child process is created which runs the test operation. It then
returns the exit status if exited properly and termination signal if
not.

A Termination signal of (11) corresponds to a segfault so keep that in
mind.
2025-12-21 15:26:29 -08:00
2a398df027 Created Initial Attempt Testing Framework
The framework, completely contained in x_tfw4c.c, uses pre-compiler
MACROS to create an environment where it is easy to create unit tests
and run regression tests.

It iterates through a linked list of test structs that contain the test
function that should be executed and the name of the test to be
displayed.

This is a simple rework of a prior test framework that did not
dynamically add new tests at compile time.

Next steps would be to have it display more metadata and have some
resilience to crashes in the tests.
2025-12-20 12:16:12 -08:00
3 changed files with 246 additions and 16 deletions

View File

@ -11,8 +11,6 @@
# Change date file was created to today # Change date file was created to today
# run make dirs to create project hierarchy # run make dirs to create project hierarchy
# Place headers in include and source files in src # Place headers in include and source files in src
# Delete this text
.POSIX: .POSIX:
# DIRECTORIES # DIRECTORIES
@ -20,48 +18,89 @@ SRC_DIR := src
INCLUDE_DIR := include INCLUDE_DIR := include
OBJ_DIR := obj OBJ_DIR := obj
TESTS_DIR := tests TESTS_DIR := tests
SUBMODULES_DIR := submodules
# Final Binary Name
PROJECT_NAME := placeholder
PROJECT_OBJ := $(OBJ_DIR)/$(PROJECT_NAME).o
# SUBMODULE VARS FOR GENERATING STATIC LIBS
SUBMODULES := $(wildcard $(SUBMODULES_DIR)/*)
SUBBARE := $(SUBMODULES:$(SUBMODULES_DIR)/%=%)
SUBMODULE_SLIB := $(foreach m,$(SUBBARE),$(SUBMODULES_DIR)/$(m)/$(m).a)
STATIC_LIBS = $(shell find ./ -name "*.a")
# C compiler settings # C compiler settings
CC := gcc CC := gcc
CFLAGS := -ggdb -I$(INCLUDE_DIR) -Wall -Wextra -MMD -MP INCLUDE_LIST := $(foreach m,$(SUBMODULES),$(m)/$(INCLUDE_DIR)) $(INCLUDE_DIR)
INCLUDES := $(addprefix -I,$(INCLUDE_LIST))
CFLAGS := -ggdb $(INCLUDES) -Wall -Wextra -MMD -MP
DFLAGS := DFLAGS :=
# Main Executable Filename
MAIN := main.c
# Files # Files
SRC := $(wildcard $(SRC_DIR)/*.c) SRC := $(filter-out $(SRC_DIR)/$(MAIN), $(wildcard $(SRC_DIR)/*.c))
OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o) OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
TESTS := $(wildcard $(TESTS_DIR)/*.c) TESTS := $(wildcard $(TESTS_DIR)/*.c)
# Get Dependencies from gcc using -MMD and -MP # Get Dependencies from gcc using -MMD and -MP
DEPENDENCIES := $(OBJ:%.o=%.d) DEPENDENCIES := $(OBJ:%.o=%.d)
# Final Binary Name
PROJECT_NAME := placeholder
all: $(PROJECT_NAME) all: $(PROJECT_NAME)
# CREATE STATIC LIBRARY OF MODULE
$(PROJECT_NAME).a: $(PROJECT_OBJ) check_submods
ar rcs $(@) $(<)
$(TESTS:$(TESTS_DIR)/%.c=%): $(OBJ) # CREATE TEST EXECUTABLES
$(CC) $(CFLAGS) $(DFLAGS) -o $(@) $(^) $(TESTS:$(TESTS_DIR)/%.c=%): %: $(OBJ_DIR)/%.o $(OBJ) check_submods
$(CC) $(CFLAGS) $(DFLAGS) -o $(@) $(<) $(OBJ) $(STATIC_LIBS)
# CREATE TEST OBJS
$(OBJ_DIR)/%.o: $(TESTS_DIR)/%.c $(OBJ)
$(CC) $(CFLAGS) $(DFLAGS) -c -o $(@) $(^)
$(PROJECT_NAME): $(OBJ) # CREATE MAIN OBJ
$(CC) $(CFLAGS) $(DFLAGS) -o $(@) $(^) $(PROJECT_NAME): $(MAIN:%.c=$(OBJ_DIR)/%.o) $(OBJ) check_submods
$(CC) $(CFLAGS) $(DFLAGS) -o $(@) $(<) $(OBJ) $(STATIC_LIBS)
# CREATE OBJS
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
$(CC) $(CFLAGS) $(DFLAGS) -c -o $(@) $(<) $(CC) $(CFLAGS) $(DFLAGS) -c -o $(@) $(<)
# RECURSE THROUGH SUBMODULES AND RUN MAKE *.a
$(SUBMODULE_SLIB):
$(MAKE) -C $(dir $(@)) $(notdir $(@))
check_submods: $(SUBMODULE_SLIB)
# RECURSE AND CLEAN ALL FILES
clean: clean:
rm -f $(PROJECT_NAME) $(OBJ_DIR)/* $(TESTS:$(TESTS_DIR)/%.c=%) @for sm in $(SUBMODULES) ; do \
$(MAKE) -C $$sm clean ; \
done
rm -f $(PROJECT_NAME) $(OBJ_DIR)/* $(TESTS:$(TESTS_DIR)/%.c=%) ./*.a
-include $(DEPENDENCIES) -include $(DEPENDENCIES)
.PHONY: clean all help:
@echo "SUBMODULES: $(SUBMODULES)"
@echo "SUBMODULE_SLIB: $(SUBMODULE_SLIB)"
@echo "STATIC_LIBS: $(STATIC_LIBS)"
.PHONY: clean all check_submods help
# Create Project Hierarchy # Create Project Hierarchy
dirs: $(SRC_DIR) $(OBJ_DIR) $(TESTS_DIR) $(INCLUDE_DIR) dirs: $(SRC_DIR) $(OBJ_DIR) $(TESTS_DIR) $(INCLUDE_DIR) $(SUBMODULES_DIR) $(SRC_DIR)/$(MAIN)
$(SRC_DIR) $(OBJ_DIR) $(TESTS_DIR) $(INCLUDE_DIR): $(SRC_DIR) $(OBJ_DIR) $(TESTS_DIR) $(INCLUDE_DIR) $(SUBMODULES_DIR):
mkdir -p $(@) mkdir -p $(@)
$(SRC_DIR)/$(MAIN):
touch $(SRC_DIR)/$(MAIN)

View File

@ -0,0 +1,162 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <assert.h>
// printf Formating
#define X_RED "\x1B[31m"
#define X_GREEN "\x1B[32m"
#define X_YELLOW "\x1B[33m"
#define X_RST "\x1B[0m"
// TEST FRAMEWORK STRUCT
typedef struct testnode{
char* name;
int (*op)(void);
struct testnode* next;
}testnode;
// LL nodes
static testnode* head = NULL;
static testnode* tail = NULL;
static testnode* ptr = NULL;
// TEST FRAMEWORK MACRO
// create a new node and pass in the name of the test. Then make a new function
// that adds the test to the LL. __attribute__((constructor)) has the function
// run before main
#define TEST(UNIT)\
static int UNIT(void);\
static testnode UNIT##node = {#UNIT, UNIT, NULL};\
__attribute__((constructor)) \
static void add##UNIT(void){ \
if (head == NULL){\
head = &UNIT##node;\
tail = head;\
return; \
}\
ptr = tail;\
tail = &UNIT##node;\
ptr->next = tail;\
}\
static int UNIT(void)
// TEST HELPER FUNCTIONS and STRUCTS
typedef enum {START, END} header_opt;
static void ptest_headers(header_opt option){
switch (option){
case START:
printf("\n-------------------\n");
printf(" Starting Tests\n");
printf("-------------------\n");
break;
case END:
printf("-------------------\n");
printf(" ENDING TESTS\n");
printf("-------------------\n\n");
break;
default:
printf("error: Wrong option in ptest_headers()\n");
exit(-1);
}
}
typedef enum {PASS, FAIL}rc_opt;
static void ppass_fail(rc_opt rc){
switch (rc){
case PASS:
printf("%s[PASS]%s ", X_GREEN, X_RST);
printf("%s", ptr->name);
break;
case FAIL:
printf("%s[FAIL]%s ", X_RED, X_RST);
printf("%s ", ptr->name);
break;
default:
printf("error: Unknown return code");
exit(-1);
}
}
static int presult(int status){
if (WIFEXITED(status)){
int rc = WEXITSTATUS(status);
if (rc == 0) {
ppass_fail(PASS);
return 0;
}
else{
ppass_fail(FAIL);
printf("- Exit Code %s(%d)%s",
X_YELLOW, rc, X_RST);
return 1;
}
}
else if (WIFSIGNALED(status)){
ppass_fail(FAIL);
printf("- Termination Signal %s(%d)%s",
X_YELLOW, WTERMSIG(status), X_RST);
return 1;
}
else{
printf("%s[FAIL]%s ", X_RED, X_RST);
printf("%s ", ptr->name);
printf("- Unknown Error ");
return 1;
}
}
static int run_child(){
int status;
pid_t cpid = fork();
if (cpid < 0){
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0){
int rc = ptr->op();
_exit(rc);
}else{
if (waitpid(cpid, &status, 0) < 0){
perror("waitpid");
exit(EXIT_FAILURE);
}
}
return status;
}
static int run_tests(){
int total_tests = 0;
int errors = 0;
ptest_headers(START);
ptr = head;
while (ptr != NULL){
total_tests++;
if(presult(run_child())){
errors++;
}
printf("\n");
ptr = ptr->next;
}
printf("\nTests completed with %s%d PASS%s", X_GREEN, total_tests - errors, X_RST);
printf(" and %s%d FAIL%s\n", X_RED, errors, X_RST);
ptest_headers(END);
if (errors > 0){
return 1;
}
return 0;
}

View File

@ -0,0 +1,29 @@
#include "test.h"
// INCLUDE PROJECT HEADERS
#include "hello_world.h"
// DEFINE TESTS
TEST(success){
return 0;
}
TEST(fail){
int i = 1;
assert(i == 0);
printf("after assert\n");
return i;
}
TEST(segfault){
int* x = NULL;
int y = *x;
return y;
}
// END OF TEST DEFINITIONS
int main(void){
return run_tests();
}