diff --git a/CMakeLists.txt b/CMakeLists.txt index c5534a4..73b319d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,14 +6,14 @@ project(c_traceback DESCRIPTION "Colorful, lightweight tracebacks in C" ) -# --- Dependencies & Modules --- +### Dependencies & Modules ### include(GNUInstallDirs) -# --- Options --- +### Options ### option(ENABLE_SANITIZERS "Enable address and undefined sanitizers" OFF) option(BUILD_EXAMPLES "Build example executables" OFF) -# --- Library --- +### Library ### add_library(c_traceback STATIC src/error.c src/error_codes.c @@ -25,14 +25,14 @@ add_library(c_traceback STATIC ) add_library(c_traceback::c_traceback ALIAS c_traceback) -# --- Standard (c99) --- +### Standard (c99) ### set_target_properties(c_traceback PROPERTIES C_STANDARD 99 C_STANDARD_REQUIRED ON POSITION_INDEPENDENT_CODE ON ) -# --- Includes --- +### Includes ### target_include_directories(c_traceback PUBLIC $ @@ -41,7 +41,7 @@ target_include_directories(c_traceback ${CMAKE_CURRENT_SOURCE_DIR}/src/internal ) -# --- Warnings --- +### Warnings ### if(MSVC) target_compile_options(c_traceback PRIVATE /W4) else() @@ -51,7 +51,7 @@ else() ) endif() -# --- Sanitizers --- +### Sanitizers ### if(ENABLE_SANITIZERS) if(NOT MSVC) target_compile_options(c_traceback PRIVATE -fsanitize=address,undefined) @@ -59,10 +59,10 @@ if(ENABLE_SANITIZERS) endif() endif() -# --- Definitions --- +### Definitions ### target_compile_definitions(c_traceback PRIVATE CTB_VERSION="${PROJECT_VERSION}") -# --- Installation --- +### Installation ### if(PROJECT_IS_TOP_LEVEL) include(CMakePackageConfigHelpers) @@ -78,7 +78,7 @@ if(PROJECT_IS_TOP_LEVEL) DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) - # --- Package config & version --- + ### Package config & version ### write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/c_tracebackConfigVersion.cmake" VERSION ${PROJECT_VERSION} @@ -97,7 +97,7 @@ if(PROJECT_IS_TOP_LEVEL) INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/c_traceback" ) - # --- Export targets / install cmake files --- + ### Export targets / install cmake files ### install(EXPORT c_tracebackTargets FILE c_tracebackTargets.cmake NAMESPACE c_traceback:: @@ -110,13 +110,17 @@ if(PROJECT_IS_TOP_LEVEL) DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/c_traceback ) - # --- Build Examples --- + ### Build Examples ### if(BUILD_EXAMPLES) - enable_testing() - if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/examples/CMakeLists.txt") add_subdirectory(examples) endif() endif() -endif() \ No newline at end of file +endif() + +### Testing ### +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/CMakeLists.txt") + enable_testing() + add_subdirectory(tests) +endif() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index bca823d..cf7e9dc 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -3,21 +3,11 @@ file(GLOB EXAMPLE_SOURCES CONFIGURE_DEPENDS "example*.c") foreach(SOURCE_FILE ${EXAMPLE_SOURCES}) get_filename_component(TARGET_NAME ${SOURCE_FILE} NAME_WE) - add_executable(${TARGET_NAME} ${SOURCE_FILE}) target_link_libraries(${TARGET_NAME} PRIVATE c_traceback::c_traceback) - if(ENABLE_SANITIZERS AND NOT MSVC) target_compile_options(${TARGET_NAME} PRIVATE -fsanitize=address,undefined) target_link_options(${TARGET_NAME} PRIVATE -fsanitize=address,undefined) endif() - # Test - add_test( - NAME ${TARGET_NAME} - COMMAND ${CMAKE_COMMAND} - -DTEST_PROG=$ - -P ${CMAKE_CURRENT_SOURCE_DIR}/check_test.cmake - ) - -endforeach() +endforeach() \ No newline at end of file diff --git a/examples/example_keyboard_interrupt.c b/examples/example_keyboard_interrupt.c new file mode 100644 index 0000000..9be65bf --- /dev/null +++ b/examples/example_keyboard_interrupt.c @@ -0,0 +1,17 @@ +#include + +#include "c_traceback.h" + +int main(void) +{ + ctb_clear_context(); + ctb_install_signal_handler(); + + printf("Press Ctrl+C to trigger a keyboard interrupt...\n"); + while (1) + { + /* Do nothing */ + } + + return 0; +} \ No newline at end of file diff --git a/examples/example_multi_error.c b/examples/example_multi_error.c index 67106a0..3256f55 100644 --- a/examples/example_multi_error.c +++ b/examples/example_multi_error.c @@ -4,29 +4,8 @@ #define FILE_PATH1 "../test1.txt" #define FILE_PATH2 "../test2.txt" -void open_file(const char *file_name) -{ - FILE *file = fopen(file_name, "r"); - if (!file) - { - THROW_FMT(CTB_FILE_NOT_FOUND_ERROR, "Failed to open file: \"%s\"", file_name); - return; - } - /* Do something */ - fclose(file); -} - -void do_something_risky() -{ - int x = 0; - if (x == 0) - { - THROW(CTB_RUNTIME_ERROR, "Division by zero attempted"); - return; - } - int y = 10 / x; - (void)y; -} +void open_file(const char *file_name); +void do_something_risky(); int main(void) { @@ -50,3 +29,27 @@ int main(void) ctb_dump_traceback(); return 1; } + +void open_file(const char *file_name) +{ + FILE *file = fopen(file_name, "r"); + if (!file) + { + THROW_FMT(CTB_FILE_NOT_FOUND_ERROR, "Failed to open file: \"%s\"", file_name); + return; + } + /* Do something */ + fclose(file); +} + +void do_something_risky() +{ + int x = 0; + if (x == 0) + { + THROW(CTB_RUNTIME_ERROR, "Division by zero attempted"); + return; + } + int y = 10 / x; + (void)y; +} \ No newline at end of file diff --git a/examples/example_open_file.c b/examples/example_open_file.c index e68c52b..36f094a 100644 --- a/examples/example_open_file.c +++ b/examples/example_open_file.c @@ -4,17 +4,7 @@ #define FILE_PATH "../test.txt" -void open_file(const char *file_name) -{ - FILE *file = fopen(file_name, "r"); - if (!file) - { - THROW_FMT(CTB_FILE_NOT_FOUND_ERROR, "Failed to open file: \"%s\"", file_name); - return; - } - /* Do something */ - fclose(file); -} +void open_file(const char *file_name); int main(void) { @@ -29,4 +19,16 @@ int main(void) error: ctb_dump_traceback(); return 1; +} + +void open_file(const char *file_name) +{ + FILE *file = fopen(file_name, "r"); + if (!file) + { + THROW_FMT(CTB_FILE_NOT_FOUND_ERROR, "Failed to open file: \"%s\"", file_name); + return; + } + /* Do something */ + fclose(file); } \ No newline at end of file diff --git a/examples/example_recursion.c b/examples/example_recursion.c index 1b184ad..a011710 100644 --- a/examples/example_recursion.c +++ b/examples/example_recursion.c @@ -4,16 +4,7 @@ #define N 100 -void recursion(int count) -{ - if (count >= N) - { - THROW_FMT(CTB_RUNTIME_ERROR, "Oh no, some error occurred at depth %d", count); - return; - } - - TRACE(recursion(count + 1)); -} +void recursion(int count); int main(void) { @@ -23,7 +14,20 @@ int main(void) TRY_GOTO(recursion(0), error); printf("This shouldn't be printed if there is error"); + return 0; + error: ctb_dump_traceback(); // Log traceback and reset error stack - return 0; + return 1; } + +void recursion(int count) +{ + if (count >= N) + { + THROW_FMT(CTB_RUNTIME_ERROR, "Oh no, some error occurred at depth %d", count); + return; + } + + TRACE(recursion(count + 1)); +} \ No newline at end of file diff --git a/examples/example_seg_fault.c b/examples/example_seg_fault.c index 6ab1090..8809853 100644 --- a/examples/example_seg_fault.c +++ b/examples/example_seg_fault.c @@ -13,7 +13,7 @@ int main(void) { ctb_clear_context(); ctb_install_signal_handler(); - THROW(CTB_BUFFER_ERROR, "Hello! This is a test error before segfault."); + THROW(CTB_BUFFER_ERROR, "Hello! This is an error before segfault."); TRACE(some_function()); diff --git a/examples/example_stack_overflow.c b/examples/example_stack_overflow.c new file mode 100644 index 0000000..39d39a3 --- /dev/null +++ b/examples/example_stack_overflow.c @@ -0,0 +1,23 @@ +#include +#include + +#include "c_traceback.h" + +void stack_overflow(int depth) +{ + char buffer[10240]; // Allocate some stack space + memset(buffer, 0, sizeof(buffer)); + printf("Recursion depth: %d\n", depth); + stack_overflow(depth + 1); +} + +int main(void) +{ + ctb_clear_context(); + ctb_install_signal_handler(); + + // Stack overflow + TRACE(stack_overflow(1)); + + return 0; +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..0abe3b4 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,23 @@ +file(GLOB TEST_SOURCES CONFIGURE_DEPENDS "test*.c") + +foreach(SOURCE_FILE ${TEST_SOURCES}) + + get_filename_component(TARGET_NAME ${SOURCE_FILE} NAME_WE) + + add_executable(${TARGET_NAME} ${SOURCE_FILE}) + target_link_libraries(${TARGET_NAME} PRIVATE c_traceback::c_traceback) + + if(ENABLE_SANITIZERS AND NOT MSVC) + target_compile_options(${TARGET_NAME} PRIVATE -fsanitize=address,undefined) + target_link_options(${TARGET_NAME} PRIVATE -fsanitize=address,undefined) + endif() + + # Test + add_test( + NAME ${TARGET_NAME} + COMMAND ${CMAKE_COMMAND} + -DTEST_PROG=$ + -P ${CMAKE_CURRENT_SOURCE_DIR}/check_test.cmake + ) + +endforeach() diff --git a/examples/check_test.cmake b/tests/check_test.cmake similarity index 100% rename from examples/check_test.cmake rename to tests/check_test.cmake diff --git a/tests/test_logging.c b/tests/test_logging.c new file mode 100644 index 0000000..2f18355 --- /dev/null +++ b/tests/test_logging.c @@ -0,0 +1,91 @@ +/** + * \file example_logging.c + * + * \brief Example usage of logging functions from c_traceback library + */ + +#include +#include + +#include "c_traceback.h" + +#define MESSAGE_BUFFER_SIZE 256 + +void log_error(int i); +void log_error_level2(int i); +void log_warning(int i); +void log_warning_level2(int i); +void log_message(int i); +void log_message_level2(int i); + +int main(void) +{ + ctb_clear_context(); + ctb_install_signal_handler(); + + log_error(1); + log_warning(3); + log_message(5); + + return 0; +} + +void log_error(int i) +{ + LOG_ERROR_FMT( + CTB_MATH_ERROR, + "(Test %d) This should be formatted error level 1 with math error", + i + ); + log_error_level2(i + 1); +} + +void log_error_level2(int i) +{ + char message[MESSAGE_BUFFER_SIZE]; + snprintf( + message, + MESSAGE_BUFFER_SIZE, + "(Test %d) This should be error level 2 with buffer error", + i + ); + LOG_ERROR(CTB_BUFFER_ERROR, message); +} + +void log_warning(int i) +{ + LOG_WARNING_FMT( + CTB_DEPRECATION_WARNING, + "(Test %d) This should be formatted warning level 1 with deprecation " + "warning", + i + ); + log_warning_level2(i + 1); +} + +void log_warning_level2(int i) +{ + char message[MESSAGE_BUFFER_SIZE]; + snprintf( + message, + MESSAGE_BUFFER_SIZE, + "(Test %d) This should be warning level 2 with user warning", + i + ); + LOG_WARNING(CTB_USER_WARNING, message); +} + +void log_message(int i) +{ + LOG_MESSAGE_FMT("(Test %d) This should be formatted message level 1", i); + log_message_level2(i + 1); +} + +void log_message_level2(int i) +{ + char message[MESSAGE_BUFFER_SIZE]; + snprintf( + message, MESSAGE_BUFFER_SIZE, "(Test %d) This should be message level 2", i + ); + LOG_MESSAGE(message); +} diff --git a/tests/test_multi_error.c b/tests/test_multi_error.c new file mode 100644 index 0000000..67106a0 --- /dev/null +++ b/tests/test_multi_error.c @@ -0,0 +1,52 @@ +#include "c_traceback.h" +#include + +#define FILE_PATH1 "../test1.txt" +#define FILE_PATH2 "../test2.txt" + +void open_file(const char *file_name) +{ + FILE *file = fopen(file_name, "r"); + if (!file) + { + THROW_FMT(CTB_FILE_NOT_FOUND_ERROR, "Failed to open file: \"%s\"", file_name); + return; + } + /* Do something */ + fclose(file); +} + +void do_something_risky() +{ + int x = 0; + if (x == 0) + { + THROW(CTB_RUNTIME_ERROR, "Division by zero attempted"); + return; + } + int y = 10 / x; + (void)y; +} + +int main(void) +{ + ctb_clear_context(); + ctb_install_signal_handler(); + + TRACE(open_file(FILE_PATH1)); + TRACE(open_file(FILE_PATH2)); + + // Throw 100 errors for testing + for (int i = 0; i < 100; ++i) + { + TRACE(do_something_risky()); + } + + TRY_GOTO(do_something_risky(), error); + /* Do something */ + return 0; + +error: + ctb_dump_traceback(); + return 1; +} diff --git a/tests/test_open_file.c b/tests/test_open_file.c new file mode 100644 index 0000000..e68c52b --- /dev/null +++ b/tests/test_open_file.c @@ -0,0 +1,32 @@ +#include + +#include "c_traceback.h" + +#define FILE_PATH "../test.txt" + +void open_file(const char *file_name) +{ + FILE *file = fopen(file_name, "r"); + if (!file) + { + THROW_FMT(CTB_FILE_NOT_FOUND_ERROR, "Failed to open file: \"%s\"", file_name); + return; + } + /* Do something */ + fclose(file); +} + +int main(void) +{ + ctb_clear_context(); + ctb_install_signal_handler(); + + TRY_GOTO(open_file(FILE_PATH), error); + /* Do something */ + + return 0; + +error: + ctb_dump_traceback(); + return 1; +} \ No newline at end of file diff --git a/tests/test_print_compilation_info.c b/tests/test_print_compilation_info.c new file mode 100644 index 0000000..2cdda98 --- /dev/null +++ b/tests/test_print_compilation_info.c @@ -0,0 +1,13 @@ +#include + +#include "c_traceback.h" + +int main(void) +{ + ctb_clear_context(); + ctb_install_signal_handler(); + + ctb_print_compilation_info(); + + return 0; +} \ No newline at end of file diff --git a/tests/test_recursion.c b/tests/test_recursion.c new file mode 100644 index 0000000..1b184ad --- /dev/null +++ b/tests/test_recursion.c @@ -0,0 +1,29 @@ +#include + +#include "c_traceback.h" + +#define N 100 + +void recursion(int count) +{ + if (count >= N) + { + THROW_FMT(CTB_RUNTIME_ERROR, "Oh no, some error occurred at depth %d", count); + return; + } + + TRACE(recursion(count + 1)); +} + +int main(void) +{ + ctb_clear_context(); + ctb_install_signal_handler(); + + TRY_GOTO(recursion(0), error); + printf("This shouldn't be printed if there is error"); + +error: + ctb_dump_traceback(); // Log traceback and reset error stack + return 0; +} diff --git a/tests/test_seg_fault.c b/tests/test_seg_fault.c new file mode 100644 index 0000000..6ab1090 --- /dev/null +++ b/tests/test_seg_fault.c @@ -0,0 +1,21 @@ +#include + +#include "c_traceback.h" + +void some_function(void) +{ + int *ptr = NULL; + THROW(CTB_BLOCKING_IO_ERROR, "Hello! This is another test error before segfault."); + printf("value: %d\n", *ptr); // This will cause a segmentation fault +} + +int main(void) +{ + ctb_clear_context(); + ctb_install_signal_handler(); + THROW(CTB_BUFFER_ERROR, "Hello! This is a test error before segfault."); + + TRACE(some_function()); + + return 0; +} diff --git a/tests/test_simple.c b/tests/test_simple.c new file mode 100644 index 0000000..6c07bd9 --- /dev/null +++ b/tests/test_simple.c @@ -0,0 +1,75 @@ +#include +#include + +#include "c_traceback.h" + +#define N 10 + +static void do_calculation(double *vec); +static void division_vec(double *vec, double denominator); + +int main(void) +{ + ctb_clear_context(); + ctb_install_signal_handler(); + + LOG_WARNING( + CTB_DEPRECATION_WARNING, + "This function is deprecated and will be replaced in the next version" + ); + LOG_MESSAGE("Hello, world!"); + + double *vec = malloc(N * sizeof(double)); + if (!vec) + { + THROW(CTB_MEMORY_ERROR, "Failed to allocate memory"); + goto error; + } + + TRY_GOTO(do_calculation(vec), error); + printf("This shouldn't be printed if there is error"); + + free(vec); + return 0; + +error: + free(vec); + ctb_dump_traceback(); // Log traceback and reset error stack + return 0; +} + +static void do_calculation(double *vec) +{ + // Initialize array + for (int i = 0; i < N; i++) + { + vec[i] = 0; + } + int success = TRY(division_vec(vec, 0)); // Throw error + if (!success) + { + return; + } + + // Further calculations... + for (int i = 0; i < N; i++) + { + vec[i] += 10; + } +} + +static void division_vec(double *vec, double denominator) +{ + if (denominator == 0) + { + THROW_FMT( + CTB_VALUE_ERROR, "Denominator must be nonzero! Received: %lf", denominator + ); + return; + } + + for (int i = 0; i < N; i++) + { + vec[i] /= denominator; + } +}