Developers Club geek daily blog

7 years, 3 months ago
Problem: to create "a friendly" environment over frejmvorkom CUnit, allowing developers/testers without additional telodvizheny to add new tests. Why in quality frejmvorka it is used CUnit? All is simple: stars so have converged.

Here I will not describe as works CUnit or how to write test cases and the test-sjuty with use given frejmvorka. All it is in the official documentation which is located to the address http://cunit.sourceforge.net/doc/index.html.


So, in the beginning we will be defined with structure of catalogues and files:
.tests
  |-- suites
  |   |-- CMakeLists.txt
  |   |-- suite1.c
  |   |-- suite2.c
  |   |-- suite3.c
  |-- main.c
  |-- utils.c
  |-- utils.h   
  |-- CMakeLists.txt


Each test-sjut will be located in a separate file in the catalogue suites. The problem of the developer or testirovshchika only to write the test-sjut and to put it in a folder suites. Others telodvizheny from the developer/tester it is not required, the test-sjut will be picked automatically up by system of assemblage for compilation, and then actually and the executed program at start of tests.

After assemblage on an exit we should receive runtests — the executed program and modules about the test-sjutami.

The agreement on the name of functions


Let's agree that test cases will have a prefix test_. That is if we test library function foo () the test case for function should be called test_foo ().

In each dynamic module the test-sjute function runSuite () which will be caused in the executed program should be exported. In danoj functions the test-sjut, should be created by means CUnit with which test cases communicate. A function prototype:

void runSuite (vod);


Template of the dynamic module — the test-sjut


suite1.c:
/* Первый тест-кейс */
static void test_foo(void) {
   /* Код тест-кейса */
}

/* Второй тест-кейс */
static void test_foo2(void) {
   /* Код тест-кейса */
}

void runSuite(void) {
    /* Код тест-сьюта */
}



As it should work


At the moment of start of the executed program runtests it loads all dynamic modules — the test-sjuty from the catalogue suites if the variable of environment TEST_MODULES_DIR is not set, and carries out function runSuite () each module. If the variable of the environment of environment TEST_MODULES_DIR modules will be loaded from the catalogue on which is specified this variable specifies

Realisation


The first has put it is realised the basic program and auxiliary function of search of dynamic modules. Functions will be realised in a file main.c:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <dlfcn.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <CUnit/Basic.h>

#include "utils.h"

int modules_alphasort(const char **a, const char **b) {
    return strcoll(*a, *b);
}

void (*runSuite)( void);

/* Функция поиска динамических модулей  */
size_t searchModulesInDir( char ***m_list, char *dir ) {
    DIR *modules_dir = NULL;
    struct dirent *ent = NULL;    
    size_t count = 0;
    char **modules_list = NULL;
    char *error = NULL;
    void *mem_module = NULL;
    void *module_handle = NULL;
    unsigned int allocated_mem = 0;
    
    errno = 0;
    
    if( !dir ) {
        return -1;
    }
    
    modules_dir = opendir( dir );
    
    if( !modules_dir ) {
        fprintf( stderr, "%s: %s\n", dir, strerror(errno));
        return -1;
    }
    
    while( ( ent = readdir( modules_dir ) ) ) {
        if( strncmp( ent->d_name, ".", 1 ) == 0 || strstr( ent->d_name, ".so" ) == NULL ) {
            continue;
        }
	
        size_t mem_len = ( strlen( ent->d_name ) + strlen( dir ) ) * sizeof( char )  + 2;
        char *module_path = malloc( mem_len );
        memset(module_path, 0,  mem_len);
	
        if( !module_path ) {
            fprintf( stderr, "%s\n", strerror(errno) );
            return -1;
        }
	
        strncat( module_path, dir, strlen( dir ) * sizeof( char ) );
        strncat( module_path, "/", 1 );
        strncat( module_path, ent->d_name, strlen( ent->d_name ) * sizeof( char ) );
	
        module_handle = dlopen ( module_path, RTLD_LAZY );
	
        if( !module_handle ) {
            fprintf( stderr, "Could not load module: '%s'\n", dlerror());
            free( module_path );
            continue;
        }
        
        dlerror();
        runSuite= dlsym( module_handle, "runSuite" );
        error = dlerror();
        if( error ) {
            fprintf( stderr, "Could not load module: %s\n", error);
            dlclose( module_handle );
            free( module_path );
            continue;
        }
	
        mem_module = realloc( modules_list, allocated_mem + strlen(module_path));
        allocated_mem += strlen(module_path);
	
        if( !mem_module ) {
            fprintf( stderr, "%s\n",  strerror(errno));
            free( module_path );
            dlclose( module_handle );
            return -1;
        } 
	
        modules_list = mem_module;
        modules_list[ count ] = module_path;
        count++;
        dlclose( module_handle ); 
    }
    
    closedir( modules_dir );
    qsort(modules_list, count, sizeof(char *), (int (*)(const void *, const void *))modules_alphasort);
    *m_list = modules_list;

    return count;
}

int main() {
    char *modules_dir = NULL;
    char *env_modules_dir = NULL;
    struct stat dir_info;
    size_t modules_total = 0;
    char **modules = NULL;
    size_t i = 0;
    void *module_handle = NULL;

    env_modules_dir = getenv( "TEST_SUITES_DIR" );
    modules_dir = ( env_modules_dir ) ? env_modules_dir : "./suites";
    
    if( stat( modules_dir, &dir;_info ) < 0 ) {
       fprintf( stderr, "%s: %s\n", modules_dir, strerror(errno));
       return 1;
    }
    
    if( !S_ISDIR( dir_info.st_mode ) ) {
        fprintf( stderr, "'%s' is not a directory\n", modules_dir);
        return 1;
    } 
    
    if( access( modules_dir, R_OK | X_OK ) != 0 ) {
        fprintf( stderr, "Directory '%s' is not accessible\n", modules_dir );
        return 1;
    } 
    
    modules_total = searchModulesInDir( &modules;, modules_dir);
    if(modules_total <= 0) {
        fprintf( stderr, "No test suites\n");
        return 0;
    }

    CUnitInitialize();
    
    for( i = 0; i < modules_total; i++ ) {
        module_handle = dlopen ( modules[i], RTLD_LAZY );	
        if( !module_handle ) {
            fprintf( stderr, "Module '%s'\n", dlerror());
            continue;
        }

        runSuite = dlsym( module_handle, "runSuite" );
        runSuite();
    }
            
    CU_basic_set_mode(CU_BRM_VERBOSE);
    CU_basic_run_tests();
    CUnitUInitialize();
    return CU_get_error();
}



Auxiliary functions and environment macroes


Constantly manually not to write a prefix at test cases or than that is worse if in the subsequent the prefix is changed, not to rename all test cases we will write auxiliary macro TEST_FUNCT:

#define TEST_FUNCT(name) \
        static void test_##name() 


Now instead of writing:

static void test_foo() {
    /* Some code */
}


We write:

TEST_FUNCT(foo) {
    /* Some code */
}


Let's add one more macro ADD_SUITE_TEST for addition of test cases to the test-sjutu:
#define ADD_SUITE_TEST(suite, name) \
    if ((NULL == CU_add_test(suite, #name, (CU_TestFunc)test_##name))) {\
        CU_cleanup_registry();\
        return;\
    }\


Well and the last that is necessary for us is an auxiliary function for creation test-sjuta CUnitCreateSuite ()

Macroes and prtotipy auxiliary functions are located in a file utils.h:

#ifndef __UTILS_H__
#define __UTILS_H__

#include <stdio.h>
#include <stdlib.h>
#include <CUnit/Basic.h>

#define TEST_FUNCT(name) \
        static void test_##name() 

#define ADD_SUITE_TEST(suite, name) \
    if ((NULL == CU_add_test(suite, #name, (CU_TestFunc)test_##name))) {\
        CU_cleanup_registry();\
        return;\
    }\

CU_pSuite CUnitCreateSuite(const char* title);
void CUnitInitialize(void);
void CUnitUInitialize(void);

#endif 


In a file utils.c we realise auxiliary functions:

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <CUnit/Basic.h>

#include "utils.h"

void CUnitUInitialize(void)
{
    CU_cleanup_registry();
}

void CUnitInitialize(void)
{
    if (CU_initialize_registry() != CUE_SUCCESS) {
        fprintf(stderr, "Failed to initialize the CUnit registry: %d\n", CU_get_error());
        exit(1);
    }
}

static int initSuite(void) {
    return 0;
}

static int cleanSuite(void) {
    return 0;
}

CU_pSuite CUnitCreateSuite(const char* title)
{
    CU_pSuite suite = NULL;
    suite = CU_add_suite(title, initSuite, cleanSuite);
    if (suite == NULL) {
        CU_cleanup_registry();
        return NULL;
    }

    return suite;
}


Now we will write the test-sjut:
#include <stdio.h>
#include <stdlib.h>
#include <CUnit/Basic.h>

#include "utils.h"

TEST_FUNCT(foo) {
     /* Фейковый код */
    CU_ASSERT_EQUAL(0, 1);
}
TEST_FUNCT(foo2) {
    /* Фейковый код */
    CU_ASSERT_EQUAL(1, 1);
}

void runSuite(void) {
    CU_pSuite suite = CUnitCreateSuite("Suite1");
    if (suite) {
        ADD_SUITE_TEST(suite, foo)
        ADD_SUITE_TEST(suite, foo2)
    }
}


Assemblage


File suites/CMakeLists.txt:
MACRO(ADD_MODULE file)
    ADD_LIBRARY( ${file} MODULE ${file}.c ../utils.c )
    TARGET_LINK_LIBRARIES( ${file} cunit )
    SET_TARGET_PROPERTIES( ${file} PROPERTIES
            PREFIX ""
            LIBRARY_OUTPUT_DIRECTORY "."
    )   
ENDMACRO(ADD_MODULE file)

FILE(GLOB C_FILES RELATIVE "${CMAKE_SOURCE_DIR}/suites" "${CMAKE_SOURCE_DIR}/suites/*.c")

INCLUDE_DIRECTORIES ( "${CMAKE_SOURCE_DIR}" )

FOREACH ( module ${C_FILES} )
    STRING( REGEX REPLACE ".c$" "" module "${module}" )
    MESSAGE(STATUS "Found test suite: ${module}")
    ADD_MODULE(${module})
ENDFOREACH ( module ${MODULES} )


File CMakeLists.txt:
CMAKE_MINIMUM_REQUIRED (VERSION 2.6)
SET(CMAKE_VERBOSE_MAKEFILE ON)

PROJECT("runtest")

SET(CMAKE_C_FLAGS " -std=c99 -O3 -Wall -Wextra -Wimplicit")

INCLUDE_DIRECTORIES ( "/usr/include"  )
ADD_EXECUTABLE(runtests main.c utils.c)
TARGET_LINK_LIBRARIES(runtests cunit dl)

ADD_CUSTOM_TARGET(test "./runtests" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" VERBATIM)
ADD_SUBDIRECTORY(suites)


antonio:tests antonio$ cmake .
antonio:tests antonio$ make
antonio:tests antonio$ ./runtests

CUnit: Automatic testing with dynamic loading of tests


This article is a translation of the original post at habrahabr.ru/post/123344/
If you have any questions regarding the material covered in the article above, please, contact the original author of the post.
If you have any complaints about this article or you want this article to be deleted, please, drop an email here: sysmagazine.com@gmail.com.

We believe that the knowledge, which is available at the most popular Russian IT blog habrahabr.ru, should be accessed by everyone, even though it is poorly translated.
Shared knowledge makes the world better.
Best wishes.

comments powered by Disqus