diff --git a/AGENTS.md b/AGENTS.md index 7358b25..ca48772 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -91,6 +91,8 @@ to the custom scripts instead of system-installed packages. - Use conventional commit prefixes (`feat:`, `fix:`, `docs:`, `chore:`, etc.) - Separate subject from body with a blank line +- Do **not** add yourself as a co-author (`Co-Authored-By:` trailers are + forbidden) Example: diff --git a/CMakeLists.txt b/CMakeLists.txt index 25c4b2f..9153b43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ add_subdirectory(ctdd) add_executable(main main.c) target_include_directories(main PRIVATE .) target_compile_features(main PRIVATE c_std_23) -target_link_libraries(main PRIVATE ctdd_str ctdd_report ctdd_logger) +target_link_libraries(main PRIVATE ctdd_str ctdd_report ctdd_logger ctdd_log_write) # Testing enable_testing() diff --git a/ctdd/CMakeLists.txt b/ctdd/CMakeLists.txt index 23dd818..9977380 100644 --- a/ctdd/CMakeLists.txt +++ b/ctdd/CMakeLists.txt @@ -2,12 +2,17 @@ add_library(ctdd_str STATIC str.c) target_include_directories(ctdd_str PUBLIC "${CMAKE_SOURCE_DIR}") target_compile_features(ctdd_str PRIVATE c_std_23) -# Reporter — calls log_message(); symbol resolved by the final binary +# Reporter — calls log_info(); symbol resolved by the final binary add_library(ctdd_report STATIC report.c) target_include_directories(ctdd_report PUBLIC "${CMAKE_SOURCE_DIR}") target_compile_features(ctdd_report PRIVATE c_std_23) -# Real log_message implementation — linked into production binaries only +# Level-filtering logger — calls log_write(); symbol resolved by the final binary add_library(ctdd_logger STATIC logger.c) target_include_directories(ctdd_logger PUBLIC "${CMAKE_SOURCE_DIR}") target_compile_features(ctdd_logger PRIVATE c_std_23) + +# Real log_write implementation — linked into production binaries only +add_library(ctdd_log_write STATIC log_write.c) +target_include_directories(ctdd_log_write PUBLIC "${CMAKE_SOURCE_DIR}") +target_compile_features(ctdd_log_write PRIVATE c_std_23) diff --git a/ctdd/log_write.c b/ctdd/log_write.c new file mode 100644 index 0000000..3558d81 --- /dev/null +++ b/ctdd/log_write.c @@ -0,0 +1,6 @@ +#include "ctdd/log_write.h" +#include + +void log_write(char const* msg) { + printf("%s\n", msg); +} diff --git a/ctdd/log_write.h b/ctdd/log_write.h new file mode 100644 index 0000000..87ea5ce --- /dev/null +++ b/ctdd/log_write.h @@ -0,0 +1,3 @@ +#pragma once + +void log_write(char const* msg); diff --git a/ctdd/logger.c b/ctdd/logger.c index 5ec1106..13cbee6 100644 --- a/ctdd/logger.c +++ b/ctdd/logger.c @@ -1,6 +1,18 @@ #include "ctdd/logger.h" +#include "ctdd/log_write.h" #include -void log_message(char const* msg) { - printf("[LOG] %s\n", msg); +static log_level s_level = LOG_DEBUG; + +void logger_set_level(log_level level) { s_level = level; } + +static void emit(char const* prefix, char const* msg) { + char buf[512]; + snprintf(buf, sizeof(buf), "[%s] %s", prefix, msg); + log_write(buf); } + +void log_debug(char const* msg) { if (s_level <= LOG_DEBUG) emit("DEBUG", msg); } +void log_info(char const* msg) { if (s_level <= LOG_INFO) emit("INFO", msg); } +void log_warn(char const* msg) { if (s_level <= LOG_WARN) emit("WARN", msg); } +void log_err(char const* msg) { if (s_level <= LOG_ERROR) emit("ERROR", msg); } diff --git a/ctdd/logger.h b/ctdd/logger.h index 919a6e2..10127bc 100644 --- a/ctdd/logger.h +++ b/ctdd/logger.h @@ -1,3 +1,9 @@ #pragma once -void log_message(char const* msg); +typedef enum { LOG_DEBUG = 0, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_NONE } log_level; + +void logger_set_level(log_level level); +void log_debug(char const* msg); +void log_info(char const* msg); +void log_warn(char const* msg); +void log_err(char const* msg); diff --git a/ctdd/report.c b/ctdd/report.c index 4232430..f33a7db 100644 --- a/ctdd/report.c +++ b/ctdd/report.c @@ -5,5 +5,5 @@ void report_value(char const* label, int value) { char buf[256]; snprintf(buf, sizeof(buf), "%s: %d", label, value); - log_message(buf); + log_info(buf); } diff --git a/main.c b/main.c index 528b65c..03f1a18 100644 --- a/main.c +++ b/main.c @@ -13,7 +13,7 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char const* argv[]) { char upper[32]; str_upper(upper, "hello, tdd", sizeof(upper)); - log_message(upper); + log_info(upper); return 0; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 87e5851..d61e1d6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -25,8 +25,6 @@ function(cmock_generate_mock target header) VERBATIM ) target_sources("${target}" PRIVATE "${mock_src}") - # MOCK_GEN_DIR for the generated header; header_dir so the generated - # #include "logger.h" resolves without the ctdd/ prefix target_include_directories("${target}" PRIVATE "${MOCK_GEN_DIR}" "${header_dir}") endfunction() @@ -37,10 +35,18 @@ target_link_libraries(test_str PRIVATE ctdd_str Unity::Unity) target_compile_features(test_str PRIVATE c_std_23) add_test(NAME test_str COMMAND test_str) -# report tests — CMock-generated mock for log_message +# report tests — mocks logger.h so log_info calls are intercepted add_executable(test_report test_report.c) target_include_directories(test_report PRIVATE "${CMAKE_SOURCE_DIR}") target_link_libraries(test_report PRIVATE ctdd_report Unity::Unity CMock::CMock) target_compile_features(test_report PRIVATE c_std_23) cmock_generate_mock(test_report "${CMAKE_SOURCE_DIR}/ctdd/logger.h") add_test(NAME test_report COMMAND test_report) + +# logger tests — mocks log_write.h so output calls are intercepted +add_executable(test_logger test_logger.c) +target_include_directories(test_logger PRIVATE "${CMAKE_SOURCE_DIR}") +target_link_libraries(test_logger PRIVATE ctdd_logger Unity::Unity CMock::CMock) +target_compile_features(test_logger PRIVATE c_std_23) +cmock_generate_mock(test_logger "${CMAKE_SOURCE_DIR}/ctdd/log_write.h") +add_test(NAME test_logger COMMAND test_logger) diff --git a/tests/test_logger.c b/tests/test_logger.c new file mode 100644 index 0000000..0dee2a8 --- /dev/null +++ b/tests/test_logger.c @@ -0,0 +1,73 @@ +#include "unity.h" +#include "ctdd/logger.h" +#include "Mocklog_write.h" + +void setUp(void) { Mocklog_write_Init(); logger_set_level(LOG_DEBUG); } +void tearDown(void) { Mocklog_write_Verify(); Mocklog_write_Destroy(); } + +void test_log_debug_emits_at_debug_level(void) { + log_write_Expect("[DEBUG] hello"); + log_debug("hello"); +} + +void test_log_info_emits_at_debug_level(void) { + log_write_Expect("[INFO] world"); + log_info("world"); +} + +void test_log_warn_emits_at_warn_level(void) { + logger_set_level(LOG_WARN); + log_write_Expect("[WARN] alert"); + log_warn("alert"); +} + +void test_log_err_emits_at_warn_level(void) { + logger_set_level(LOG_WARN); + log_write_Expect("[ERROR] fatal"); + log_err("fatal"); +} + +void test_log_debug_suppressed_at_info_level(void) { + logger_set_level(LOG_INFO); + log_debug("silent"); +} + +void test_log_info_suppressed_at_warn_level(void) { + logger_set_level(LOG_WARN); + log_info("silent"); +} + +void test_log_warn_suppressed_at_error_level(void) { + logger_set_level(LOG_ERROR); + log_warn("silent"); +} + +void test_log_none_suppresses_all(void) { + logger_set_level(LOG_NONE); + log_debug("silent"); + log_info("silent"); + log_warn("silent"); + log_err("silent"); +} + +void test_level_can_be_raised_then_lowered(void) { + logger_set_level(LOG_ERROR); + log_info("silent"); + logger_set_level(LOG_DEBUG); + log_write_Expect("[INFO] now visible"); + log_info("now visible"); +} + +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_log_debug_emits_at_debug_level); + RUN_TEST(test_log_info_emits_at_debug_level); + RUN_TEST(test_log_warn_emits_at_warn_level); + RUN_TEST(test_log_err_emits_at_warn_level); + RUN_TEST(test_log_debug_suppressed_at_info_level); + RUN_TEST(test_log_info_suppressed_at_warn_level); + RUN_TEST(test_log_warn_suppressed_at_error_level); + RUN_TEST(test_log_none_suppresses_all); + RUN_TEST(test_level_can_be_raised_then_lowered); + return UNITY_END(); +} diff --git a/tests/test_report.c b/tests/test_report.c index 6f9a0a9..bdb936a 100644 --- a/tests/test_report.c +++ b/tests/test_report.c @@ -6,22 +6,22 @@ void setUp(void) { Mocklogger_Init(); } void tearDown(void) { Mocklogger_Verify(); Mocklogger_Destroy(); } void test_report_formats_label_and_value(void) { - log_message_Expect("count: 42"); + log_info_Expect("count: 42"); report_value("count", 42); } void test_report_negative_value(void) { - log_message_Expect("score: -5"); + log_info_Expect("score: -5"); report_value("score", -5); } void test_report_zero(void) { - log_message_Expect("total: 0"); + log_info_Expect("total: 0"); report_value("total", 0); } void test_report_calls_log_exactly_once(void) { - log_message_Expect("x: 1"); + log_info_Expect("x: 1"); report_value("x", 1); }