LexLeo 0.0.0-dev+f8e5087-dirty
Technical documentation
Loading...
Searching...
No Matches
unit_test_logger.c
Go to the documentation of this file.
1/* SPDX-License-Identifier: GPL-3.0-or-later
2 * Copyright (C) 2026 Sylvain Labopin
3 */
4
24
28
29#include "osal/mem/osal_mem.h"
31
34
35#include "lexleo_cmocka.h"
36
39//-----------------------------------------------------------------------------
40// LOCAL TEST DOUBLES
41//-----------------------------------------------------------------------------
42
43typedef struct fake_logger_backend_t {
45 int log_called;
46 int destroy_called;
47
49 void *last_backend;
50 const char *last_message;
51
53 logger_status_t log_ret;
54} fake_logger_backend_t;
55
56static void fake_logger_backend_reset(fake_logger_backend_t *b) {
57 assert_non_null(b);
58 b->log_called = 0;
59 b->destroy_called = 0;
60 b->last_backend = NULL;
61 b->last_message = NULL;
62 b->log_ret = LOGGER_STATUS_INVALID;
63}
64
65static logger_status_t fake_logger_log(void *backend, const char *message) {
66 fake_logger_backend_t *b = (fake_logger_backend_t *)backend;
67 assert_non_null(b);
68
69 b->last_backend = backend;
70 b->last_message = message;
71
72 b->log_called++;
73
74 return b->log_ret;
75}
76
77static void fake_logger_destroy(void *backend) {
78 fake_logger_backend_t *b = (fake_logger_backend_t *)backend;
79 assert_non_null(b);
80
81 b->last_backend = backend;
82
83 b->destroy_called++;
84}
85
86static const logger_vtbl_t fake_logger_vtbl = {
87 .log = fake_logger_log,
88 .destroy = fake_logger_destroy
89};
90
111static void test_logger_default_env(void **state) {
112 (void)state;
113
114 const osal_mem_ops_t dummy = {0};
115 const osal_mem_ops_t *dummy_p = &dummy;
116
117 logger_env_t ret = logger_default_env(dummy_p);
118
119 assert_ptr_equal(ret.mem, dummy_p);
120}
121
258
277typedef enum {
278 OUT_CHECK_NONE,
279 OUT_EXPECT_NULL,
280 OUT_EXPECT_NON_NULL,
281 OUT_EXPECT_UNCHANGED
282} out_expect_t;
283
291typedef struct {
292 const char *name;
293
295 size_t fail_call_idx;
296
297 logger_status_t expected_ret;
298 out_expect_t out_expect;
299} test_logger_lifecycle_case_t;
300
310typedef struct {
311 // runtime resources
312 logger_t *out;
313
314 // injection
315 logger_env_t env;
316
317 fake_logger_backend_t backend;
318
319 const test_logger_lifecycle_case_t *tc;
320} test_logger_lifecycle_fixture_t;
321
322//-----------------------------------------------------------------------------
323// FIXTURES
324//-----------------------------------------------------------------------------
325
329static int setup_logger_lifecycle(void **state)
330{
331 const test_logger_lifecycle_case_t *tc =
332 (const test_logger_lifecycle_case_t *)(*state);
333
334 test_logger_lifecycle_fixture_t *fx =
335 (test_logger_lifecycle_fixture_t *)malloc(sizeof(*fx));
336 if (!fx) return -1;
337
338 osal_memset(fx, 0, sizeof(*fx));
339 fx->tc = tc;
340
341 fake_memory_reset();
342 if (tc->scenario == LOGGER_LIFECYCLE_SCENARIO_OOM && tc->fail_call_idx > 0) {
343 fake_memory_fail_only_on_call(tc->fail_call_idx);
344 }
345
346 // DI
347 fx->env.mem = osal_mem_test_fake_ops();
348
349 fake_logger_backend_reset(&fx->backend);
350
351 *state = fx;
352 return 0;
353}
354
358static int teardown_logger_lifecycle(void **state)
359{
360 test_logger_lifecycle_fixture_t *fx =
361 (test_logger_lifecycle_fixture_t *)(*state);
362
363 if (fx->out) {
364 logger_destroy(&fx->out);
365 }
366
367 assert_true(fake_memory_no_leak());
368 assert_true(fake_memory_no_invalid_free());
369 assert_true(fake_memory_no_double_free());
370
371 free(fx);
372 return 0;
373}
374
375//-----------------------------------------------------------------------------
376// TEST
377//-----------------------------------------------------------------------------
378
382static void test_logger_lifecycle(void **state)
383{
384 test_logger_lifecycle_fixture_t *fx =
385 (test_logger_lifecycle_fixture_t *)(*state);
386 const test_logger_lifecycle_case_t *tc = fx->tc;
387
388 // ARRANGE
390
391 logger_t **out_arg = &fx->out;
392 const logger_vtbl_t *vtbl_arg = &fake_logger_vtbl;
393 logger_vtbl_t vtbl_local;
394 void *backend_arg = &fx->backend;
395 const logger_env_t *env_arg = &fx->env;
396
397 // invalid args
398 if (tc->scenario == LOGGER_LIFECYCLE_SCENARIO_OUT_NULL) out_arg = NULL;
399 if (tc->scenario == LOGGER_LIFECYCLE_SCENARIO_VTBL_NULL) vtbl_arg = NULL;
400 if (tc->scenario == LOGGER_LIFECYCLE_SCENARIO_BACKEND_NULL) backend_arg = NULL;
401 if (tc->scenario == LOGGER_LIFECYCLE_SCENARIO_ENV_NULL) env_arg = NULL;
402 if (tc->scenario == LOGGER_LIFECYCLE_SCENARIO_ENV_MEM_NULL) fx->env.mem = NULL;
403 if (tc->scenario == LOGGER_LIFECYCLE_SCENARIO_VTBL_LOG_NULL) {
404 vtbl_local = fake_logger_vtbl;
405 vtbl_local.log = NULL;
406 vtbl_arg = &vtbl_local;
407 }
409 vtbl_local = fake_logger_vtbl;
410 vtbl_local.destroy = NULL;
411 vtbl_arg = &vtbl_local;
412 }
413
414 // ensure OUT_EXPECT_UNCHANGED is meaningful
415 if (tc->out_expect == OUT_EXPECT_UNCHANGED && out_arg != NULL) {
416 fx->out = (logger_t *)(uintptr_t)0xDEADC0DEu; // sentinel
417 }
418
419 logger_t *out_arg_snapshot = fx->out;
420
421 // ACT
422 ret = logger_create(out_arg, vtbl_arg, backend_arg, env_arg);
424 assert_int_equal(ret, LOGGER_STATUS_OK);
425 assert_non_null(fx->out);
426
427 logger_destroy(&fx->out);
428 assert_null(fx->out);
429
430 logger_destroy(&fx->out);
431 assert_null(fx->out);
432 }
433
434 // ASSERT
435 assert_int_equal(ret, tc->expected_ret);
436
437 switch (tc->out_expect) {
438 case OUT_CHECK_NONE: break;
439 case OUT_EXPECT_NULL: assert_null(fx->out); break;
440 case OUT_EXPECT_NON_NULL: assert_non_null(fx->out); break;
441 case OUT_EXPECT_UNCHANGED:
442 assert_ptr_equal(out_arg_snapshot, fx->out);
443 fx->out = NULL; // prevent teardown from destroying sentinel
444 break;
445 default: fail();
446 }
447}
448
449//-----------------------------------------------------------------------------
450// CASES
451//-----------------------------------------------------------------------------
452
453static const test_logger_lifecycle_case_t CASE_LOGGER_LIFECYCLE_OUT_NULL = {
454 .name = "logger_lifecycle_out_null",
455
457 .fail_call_idx = 0,
458
459 .expected_ret = LOGGER_STATUS_INVALID,
460 .out_expect = OUT_CHECK_NONE
461};
462
463static const test_logger_lifecycle_case_t CASE_LOGGER_LIFECYCLE_VTBL_NULL = {
464 .name = "logger_lifecycle_vtbl_null",
465
467 .fail_call_idx = 0,
468
469 .expected_ret = LOGGER_STATUS_INVALID,
470 .out_expect = OUT_EXPECT_UNCHANGED
471};
472
473static const test_logger_lifecycle_case_t CASE_LOGGER_LIFECYCLE_VTBL_LOG_NULL = {
474 .name = "logger_lifecycle_vtbl_log_null",
475
477 .fail_call_idx = 0,
478
479 .expected_ret = LOGGER_STATUS_INVALID,
480 .out_expect = OUT_EXPECT_UNCHANGED
481};
482
483static const test_logger_lifecycle_case_t CASE_LOGGER_LIFECYCLE_VTBL_DESTROY_NULL = {
484 .name = "logger_lifecycle_vtbl_destroy_null",
485
487 .fail_call_idx = 0,
488
489 .expected_ret = LOGGER_STATUS_INVALID,
490 .out_expect = OUT_EXPECT_UNCHANGED
491};
492
493static const test_logger_lifecycle_case_t CASE_LOGGER_LIFECYCLE_BACKEND_NULL = {
494 .name = "logger_lifecycle_backend_null",
495
497 .fail_call_idx = 0,
498
499 .expected_ret = LOGGER_STATUS_INVALID,
500 .out_expect = OUT_EXPECT_UNCHANGED
501};
502
503static const test_logger_lifecycle_case_t CASE_LOGGER_LIFECYCLE_ENV_NULL = {
504 .name = "logger_lifecycle_env_null",
505
507 .fail_call_idx = 0,
508
509 .expected_ret = LOGGER_STATUS_INVALID,
510 .out_expect = OUT_EXPECT_UNCHANGED
511};
512
513static const test_logger_lifecycle_case_t CASE_LOGGER_LIFECYCLE_ENV_MEM_NULL = {
514 .name = "logger_lifecycle_mem_null",
515
517 .fail_call_idx = 0,
518
519 .expected_ret = LOGGER_STATUS_INVALID,
520 .out_expect = OUT_EXPECT_UNCHANGED
521};
522
523static const test_logger_lifecycle_case_t CASE_LOGGER_LIFECYCLE_OOM_1 = {
524 .name = "logger_lifecycle_oom_1",
525
527 .fail_call_idx = 1,
528
529 .expected_ret = LOGGER_STATUS_OOM,
530 .out_expect = OUT_EXPECT_UNCHANGED
531};
532
533static const test_logger_lifecycle_case_t CASE_LOGGER_LIFECYCLE_OK = {
534 .name = "logger_lifecycle_ok",
535
537 .fail_call_idx = 0,
538
539 .expected_ret = LOGGER_STATUS_OK,
540 .out_expect = OUT_EXPECT_NON_NULL
541};
542
543static const test_logger_lifecycle_case_t CASE_LOGGER_LIFECYCLE_DESTROY_IDEMPOTENT = {
544 .name = "logger_lifecycle_destroy_idempotent",
545
547 .fail_call_idx = 0,
548
549 .expected_ret = LOGGER_STATUS_OK,
550 .out_expect = OUT_EXPECT_NULL
551};
552
553//-----------------------------------------------------------------------------
554// CASES REGISTRY
555//-----------------------------------------------------------------------------
556
557#define LOGGER_LIFECYCLE_CASES(X) \
558X(CASE_LOGGER_LIFECYCLE_OUT_NULL) \
559X(CASE_LOGGER_LIFECYCLE_VTBL_NULL) \
560X(CASE_LOGGER_LIFECYCLE_VTBL_LOG_NULL) \
561X(CASE_LOGGER_LIFECYCLE_VTBL_DESTROY_NULL) \
562X(CASE_LOGGER_LIFECYCLE_BACKEND_NULL) \
563X(CASE_LOGGER_LIFECYCLE_ENV_NULL) \
564X(CASE_LOGGER_LIFECYCLE_ENV_MEM_NULL) \
565X(CASE_LOGGER_LIFECYCLE_OOM_1) \
566X(CASE_LOGGER_LIFECYCLE_OK) \
567X(CASE_LOGGER_LIFECYCLE_DESTROY_IDEMPOTENT)
568
569#define LOGGER_MAKE_LIFECYCLE_TEST(case_sym) \
570LEXLEO_MAKE_TEST(logger_lifecycle, case_sym)
571
572static const struct CMUnitTest logger_lifecycle_tests[] = {
573 LOGGER_LIFECYCLE_CASES(LOGGER_MAKE_LIFECYCLE_TEST)
574};
575
576#undef LOGGER_LIFECYCLE_CASES
577#undef LOGGER_MAKE_LIFECYCLE_TEST
578
626
632typedef struct {
633 const char *name;
634
635 logger_log_scenario_t scenario;
636
637 logger_status_t expected_ret;
638} test_logger_log_case_t;
639
649typedef struct {
650 logger_t *logger;
651 logger_env_t env;
652 fake_logger_backend_t backend;
653 const test_logger_log_case_t *tc;
654} test_logger_log_fixture_t;
655
656//-----------------------------------------------------------------------------
657// FIXTURES
658//-----------------------------------------------------------------------------
659
663static int setup_logger_log(void **state)
664{
665 const test_logger_log_case_t *tc =
666 (const test_logger_log_case_t *)(*state);
667 test_logger_log_fixture_t *fx =
668 (test_logger_log_fixture_t *)malloc(sizeof(*fx));
669 if (!fx) return -1;
670
671 osal_memset(fx, 0, sizeof(*fx));
672
673 fake_memory_reset();
674 fake_logger_backend_reset(&fx->backend);
675
676 // DI
677 fx->env.mem = osal_mem_test_fake_ops();
678
679 assert_int_equal(
680 logger_create(&fx->logger, &fake_logger_vtbl, &fx->backend, &fx->env),
682 fx->tc = tc;
683
684 *state = fx;
685
686 return 0;
687}
688
692static int teardown_logger_log(void **state)
693{
694 test_logger_log_fixture_t *fx =
695 (test_logger_log_fixture_t *)(*state);
696
697 logger_destroy(&fx->logger);
698
699 assert_true(fake_memory_no_invalid_free());
700 assert_true(fake_memory_no_double_free());
701 assert_true(fake_memory_no_leak());
702
703 free(fx);
704
705 return 0;
706}
707
708//-----------------------------------------------------------------------------
709// TEST
710//-----------------------------------------------------------------------------
711
715static void test_logger_log(void **state)
716{
717 test_logger_log_fixture_t *fx =
718 (test_logger_log_fixture_t *)(*state);
719 const test_logger_log_case_t *tc = fx->tc;
720
721 // ARRANGE
722 logger_status_t ret = (logger_status_t)-1; // poison
723 logger_t *l_arg = fx->logger;
724 const char *message_arg = "test message";
725
726 // invalid args
727 if (tc->scenario == LOGGER_LOG_SCENARIO_L_NULL) l_arg = NULL;
728 if (tc->scenario == LOGGER_LOG_SCENARIO_MESSAGE_NULL) message_arg = NULL;
729
730 // spy cfg
731 if (tc->scenario == LOGGER_LOG_SCENARIO_FORWARD_IO_ERROR_OK) {
732 fx->backend.log_ret = LOGGER_STATUS_IO_ERROR;
733 }
734
735 // ACT
736 ret = logger_log(l_arg, message_arg);
737
738 // ASSERT
739 assert_int_equal(ret, tc->expected_ret);
740
741 if (tc->scenario == LOGGER_LOG_SCENARIO_FORWARD_IO_ERROR_OK) {
742 assert_int_equal(fx->backend.log_called, 1);
743 assert_int_equal(fx->backend.destroy_called, 0);
744 assert_ptr_equal(fx->backend.last_backend, &fx->backend);
745 assert_ptr_equal(fx->backend.last_message, message_arg);
746 }
747}
748
749//-----------------------------------------------------------------------------
750// CASES
751//-----------------------------------------------------------------------------
752
753static const test_logger_log_case_t CASE_LOGGER_LOG_L_NULL = {
754 .name = "logger_log_l_null",
755
756 .scenario = LOGGER_LOG_SCENARIO_L_NULL,
757
758 .expected_ret = LOGGER_STATUS_INVALID
759};
760
761static const test_logger_log_case_t CASE_LOGGER_LOG_MESSAGE_NULL = {
762 .name = "logger_log_message_null",
763
765
766 .expected_ret = LOGGER_STATUS_INVALID
767};
768
769
770static const test_logger_log_case_t CASE_LOGGER_LOG_FORWARD_IO_ERROR_OK = {
771 .name = "logger_log_forward_io_error_ok",
772
774
775 .expected_ret = LOGGER_STATUS_IO_ERROR
776};
777
778//-----------------------------------------------------------------------------
779// CASES REGISTRY
780//-----------------------------------------------------------------------------
781
782#define LOGGER_LOG_CASES(X) \
783X(CASE_LOGGER_LOG_L_NULL) \
784X(CASE_LOGGER_LOG_MESSAGE_NULL) \
785X(CASE_LOGGER_LOG_FORWARD_IO_ERROR_OK)
786
787#define LOGGER_MAKE_LOG_TEST(case_sym) \
788LEXLEO_MAKE_TEST(logger_log, case_sym)
789
790static const struct CMUnitTest logger_log_tests[] = {
791 LOGGER_LOG_CASES(LOGGER_MAKE_LOG_TEST)
792};
793
794#undef LOGGER_LOG_CASES
795#undef LOGGER_MAKE_LOG_TEST
796
797//-----------------------------------------------------------------------------
798// MAIN
799//-----------------------------------------------------------------------------
800
801int main(void)
802{
803 static const struct CMUnitTest logger_unit_non_parametric_tests[] = {
804 cmocka_unit_test(test_logger_default_env)
805 };
806
807 int failed = 0;
808 failed += cmocka_run_group_tests(logger_unit_non_parametric_tests, NULL, NULL);
809 failed += cmocka_run_group_tests(logger_lifecycle_tests, NULL, NULL);
810 failed += cmocka_run_group_tests(logger_log_tests, NULL, NULL);
811 return failed;
812}
813
Borrower-facing runtime operations for the logger port.
logger_status_t logger_log(logger_t *l, const char *message)
Emit a log message through a logger.
Definition logger.c:77
Adapter-side API for constructing and binding logger_t objects.
logger_status_t logger_create(logger_t **out, const logger_vtbl_t *vtbl, void *backend, const logger_env_t *env)
Create a generic logger handle from adapter-provided backend bindings.
Definition logger.c:27
Composition Root helpers for the logger port.
logger_env_t logger_default_env(const osal_mem_ops_t *mem_ops)
Build a default logger_env_t from injected memory operations.
Definition logger.c:22
Lifecycle services for logger_t handles.
void logger_destroy(logger_t **l)
Destroy a logger handle.
Definition logger.c:57
logger_status_t
@ LOGGER_STATUS_OOM
@ LOGGER_STATUS_IO_ERROR
@ LOGGER_STATUS_OK
@ LOGGER_STATUS_INVALID
int main()
Definition main.c:5
void * osal_memset(void *s, int c, size_t n)
Definition osal_mem.c:30
const osal_mem_ops_t * osal_mem_test_fake_ops(void)
Runtime environment for the logger port.
Definition logger_env.h:34
const osal_mem_ops_t * mem
Memory operations used by the logger port.
Definition logger_env.h:42
Private handle structure for a logger_t.
void * backend
Adapter dispatch table bound to a logger_t instance.
logger_status_t(* log)(void *backend, const char *message)
void(* destroy)(void *backend)
logger_log_scenario_t
Scenarios for logger_log().
@ LOGGER_LOG_SCENARIO_FORWARD_IO_ERROR_OK
@ LOGGER_LOG_SCENARIO_L_NULL
@ LOGGER_LOG_SCENARIO_MESSAGE_NULL
static void test_logger_default_env(void **state)
Test logger_default_env().
logger_lifecycle_scenario_t
Scenarios for logger_create() / logger_destroy().
@ LOGGER_LIFECYCLE_SCENARIO_OK
@ LOGGER_LIFECYCLE_SCENARIO_BACKEND_NULL
@ LOGGER_LIFECYCLE_SCENARIO_ENV_NULL
@ LOGGER_LIFECYCLE_SCENARIO_OOM
@ LOGGER_LIFECYCLE_SCENARIO_VTBL_LOG_NULL
@ LOGGER_LIFECYCLE_SCENARIO_VTBL_DESTROY_NULL
@ LOGGER_LIFECYCLE_SCENARIO_DESTROY_IDEMPOTENT
@ LOGGER_LIFECYCLE_SCENARIO_ENV_MEM_NULL
@ LOGGER_LIFECYCLE_SCENARIO_OUT_NULL
@ LOGGER_LIFECYCLE_SCENARIO_VTBL_NULL