1

I'm having trouble compiling any kind of reasonable structure for unit-testing a module's helper/static functions. Nearly all of this module is static functions and it has many of them, so I'm trying not to put all my tests in the same file. The specific (large number of) error is: /usr/bin/ld: /usr/lib/debug/usr/lib/x86_64-linux-gnu/crt1.o(.debug_info): relocation 0 has invalid symbol index 11

But I'm interested in a general approach that will compile.

From the command line, first install Cunit:

# Install cunit
sudo apt-get install libcunit1 libcunit1-doc libcunit1-dev

In module_a.c:

#include <stdio.h>

int main(void)
{
  // Do the real thing
  printf("The number 42: %d\n", get_42());
  printf("The number 0: %d\n", get_0());

  return 0;
}

static int32_t get_42(void)
{
  return 42;
}

static int32_t get_0(void)
{
  return 42;
}

In module_a_tests.c:

#define UNIT_TEST
#include "module_a.c"
#include "CUnit/Basic.h"
#ifdef UNIT_TEST

int set_up(void)
{
  return 0;
}

int tear_down(void)
{
  return 0;
}

void run_good_fn(void)
{
  CU_ASSERT(42 == get_42());
}

void run_bad_fn(void)
{
  CU_ASSERT(0 == get_0());
}

int main(void)
{
  CU_pSuite p_suite = NULL;

  // Initialize
  if (CU_initialize_registry() != CUE_SUCCESS) {
    return CU_get_error();
  }

  p_suite = CU_add_suite("First Suite", set_up, tear_down);
  if (p_suite == NULL) {
    goto exit;
  }
  CU_basic_set_mode(CU_BRM_VERBOSE);

  // Add tests
  if (CU_add_test(p_suite, "Testing run_good_fn", run_good_fn) == NULL) {
    goto exit;
  }

  if (CU_add_test(p_suite, "Testing run_bad_fn", run_bad_fn) == NULL) {
    goto exit;
  }

  // Run the tests
  CU_basic_run_tests();

  exit:
    CU_cleanup_registry();

  return CU_get_error();
}
#endif

Related:

How to test a static function

Community
  • 1
  • 1
tarabyte
  • 17,837
  • 15
  • 76
  • 117

1 Answers1

1

It's a bit hacky, but one way to approach this is to use a #include as a raw text substitution in the right location (after the forward declaration of all your static functions). There's a dependency on the location, but if you follow a convention it can be easy to understand:

In module_a.c:

#include <stdio.h>

// Comment this macro in and out to enable/disable unit testing
#define UNIT_TEST

static int32_t get_42(void);
static int32_t get_0(void);

#ifndef UNIT_TEST
int main(void)
{
  // Do the real thing
  printf("The number 42: %d\n", get_42());
  printf("The number 0: %d\n", get_0());

  return 0;
}
#else
#include "module_a_tests.c"
#endif

static int32_t get_42(void)
{
  return 42;
}

static int32_t get_0(void)
{
  return 42;
}

In module_a_tests.c:

// Add a #include guard
#ifndef MODULE_A_TESTS_C
#define MODULE_A_TESTS_C

#include "CUnit/Basic.h"

int set_up(void)
{
  return 0;
}

int tear_down(void)
{
  return 0;
}

void run_good_fn(void)
{
  CU_ASSERT(42 == get_42());
}

void run_bad_fn(void)
{
  CU_ASSERT(0 == get_0());
}

int main(void)
{
  CU_pSuite p_suite = NULL;

  // Initialize
  if (CU_initialize_registry() != CUE_SUCCESS) {
    return CU_get_error();
  }

  p_suite = CU_add_suite("First Suite", set_up, tear_down);
  if (p_suite == NULL) {
    goto exit;
  }
  CU_basic_set_mode(CU_BRM_VERBOSE);

  // Add tests
  if (CU_add_test(p_suite, "run_good_fn", run_good_fn) == NULL) {
    goto exit;
  }

  if (CU_add_test(p_suite, "run_bad_fn", run_bad_fn) == NULL) {
    goto exit;
  }

  // Run the tests
  CU_basic_run_tests();

  exit:
    CU_cleanup_registry();

  return CU_get_error();
}
#endif
tarabyte
  • 17,837
  • 15
  • 76
  • 117
  • As goto is forbidden in many places.. In module_a_tests.c, could you replace each "goto exit;" with, say "return cleanExit();" and have a helper "int cleanExit()" that simply has the two lines of code after your exit: label? Or would this somehow not work because of the specific test framework? Sorry about the lack of markup, never managed to figure it out. – Razzle Dec 29 '20 at 12:42