Developers Club geek daily blog

3 years, 1 month ago
One more way of automatic call of unit-tests in the Xi languageOn Habré already there are some articles how to develop modular tests in the Xi language. I am not going to criticize the described approaches, and only I will offer one more — what we use in Habré's project.

To whom it is interesting, I ask tackle! But I warn: there are a lot of footcloths from macroes and "linkersky" magic.

Static adjustable arrays


Let's a little plunge into question perspective. In the Xi language absence of static designers in syntax is the reason of complexity of development of modular tests. It means that you need to describe in an explicit form calls of all functions with tests which you want to execute, and it, agree extremely inconveniently.
On the other hand, when the speech comes about call of large number of functions, I have at once thoughts of pointer array. That is, to cause all required functions, it is necessary to take pointer array on these functions, to address to each its element and to cause the corresponding function. Thus, there is construction like this:
void test_func_1(void) {
}
void test_func_2(void) {
}

int main(void){
    int i;
    void (*all_tests[])(void) = {test_func_1, test_func_2};

    for(i = 0; i < sizeof(all_tests)/sizeof(all_tests[0]); i ++) {
        all_tests[i]();
    }
    return 0;
}

At once that the array is initialized manually is evident, and it is not convenient. Reflecting how to avoid it, it is possible to formulate such hotelka:
When determining this or that variable we have to have opportunity to specify that it treats this or that array.

Such mechanism in the Xi language does not exist, but let's dream up over syntax. It could look so:
<type> arr[];
<type> a(array(arr));
<type> b(array(arr));

Or, if to use the mechanism of expansions in gcc which is expressed with the help __ to attribute __
<type> arr[];
<type> a __attribute__(array_member(arr)));
<type> b __attribute__(array_member(arr)));

Now it is necessary to remember that the array in the Xi language — is the constant pointer on the first element in this array, and elements are located consistently and have the identical size. Means if we are able to specify to the compiler that it is necessary to lay certain variables in memory consistently, we will be able to organize own array. At least, we will be able to handle these variables the same as elements of the real array.

Not the compiler, and linker is responsible for placement of variables, and in linker-scripts ukazvyatsya, how exactly it has to do it. From syntax of these scripts clearly that the linker groups data on sections and if in certain section there are variables of only one type, it and will be array in fact, and it is necessary only to define array tag somehow.

When we define array, we specify type of its elements. Means, it is possible to define the first element and the reference to it it is possible to use as array. And it is even better to enter empty array of the specified type as it all the same to be necessary for correct syntax.
Something will turn out like it:
<type> arr[] __attribute__((section(“array_spread.arr”))) = {};

In order that the tag indicated the beginning of section, it is possible to use scripts from linker. By default the linker places data in random order but if to use the SORT function ("section_name"), the linker will sort characters in section in dictionary order. Thus, in order that the character of array indicated the beginning of section, it is necessary for us that the subsection name lexicographic went before other parts of array. For this purpose it is enough to attribute "0_head" by the beginning of array, and for all variables — "1_body". Of course, it would be rather simple "0" and "1", but then the text of the program would become less readable.

Thus, array declaration will look so:
<type> arr[] __attribute__((section(“array_spread.arr_0_head.rodata”))) = {};

The linker script looks as follows:
SECTIONS {
    .rodata.array_spread : {
        *(SORT(.array_spread.*.rodata))
    }
}
INSERT AFTER .rodata;

It is possible to connect it by means of gcc key - T

To specify that some variable needs to be placed in this or that section, it is necessary to add the corresponding attribute:
<type> a __attribute__((section(“array_spread.arr_1_body.rodata”)));

Thus, we will create array, but there will be one more problem: how to receive the size of this array? If with normal arrays, we simply took its size in bytes and divided into the size of the first element, in this situation the compiler does not know anything about the section size. To solve this problem, give, as well as from the beginning of array, we will add the same tag to the end, besides remembering alphabetic sorting.
So, we will receive the following:
<type> arr_tail[] __attribute__((section(“array_spread.arr_9_tail.rodata”))) = {};

Now, when we have all necessary information how to create array, let's try copy the previous example:
#include <stddef.h>
#include <stdio.h>

void test_func_1(void) {
    printf("test 1\n");
}
void test_func_2(void) {
    printf("test 2\n");
}

void (*all_tests_item_1)(void) __attribute__((section(".array_spread.all_tests_1_body.rodata"))) = test_func_1;
void (*all_tests_item_2)(void) __attribute__((section(".array_spread.all_tests_1_body.rodata"))) = test_func_2;
void (*all_tests[])(void) __attribute__((section(".array_spread.all_tests_0_head.rodata"))) = {};
void (*all_tests__tail[])(void) __attribute__((section(".array_spread.all_tests_9_tail.rodata"))) = {};

int main(void){
    int i;

    printf("%zu tests start\n", (size_t)(all_tests__tail - all_tests));

    for(i = 0; i < (size_t)(all_tests__tail - all_tests); i ++) {
        all_tests[i]();
    }
    return 0;
}

If to start this program, having specified the linker-script given above, we will receive the same result, as with normal arrays. But thus you can not only create static adjustable array in one file, but also the array distributed according to different files as the linker works at the last stage of assembly, collecting all object files in one, and it is occasionally it is very useful.

Of course, the linker does not check type and the extent of objects which you have placed in section and if you place objects of different types in one section, will be to yourself spiteful Buratino. But if to do everything accurately, you receive quite interesting mechanism of creation of static adjustable arrays in the Xi language.

Of course, this approach is not really convenient in respect of syntax therefore to be necessary to hide all magic in macroes.

For a start, we will simplify to ourselves life and we will enter couple of auxiliary macroes which enter names of arrays, sections and variables.
The first simplifies the name of sections:
#define __ARRAY_SPREAD_SECTION(array_nm, order_tag) \
    ".array_spread." #array_nm order_tag ".rodata,\"a\",%progbits;#"

The second defines the internal variable (described above array trailer label)
#define __ARRAY_SPREAD_PRIVATE(array_nm, private_nm) \
    __array_spread__##array_nm##__##private_nm

Now we will define macro which gets array.
#define ARRAY_SPREAD_DEF(element_type, name) \
        element_type volatile const name[] __attribute__ ((used,        \
        /* Some versions of GCC do not take into an account section     \
         * attribute if it appears after the definition. */             \
            section(__ARRAY_SPREAD_SECTION(name, "0_head")))) =         \
            { /* Empty anchor to the array head. */ };                  \
        element_type volatile const __ARRAY_SPREAD_PRIVATE(name,tail)[] \
        __attribute__ ((used,                                           \
        /* Some versions of GCC do not take into an account section     \
         * attribute if it appears after the definition. */             \
            section(__ARRAY_SPREAD_SECTION(name, "9_tail")))) =         \
            { /* Empty anchor at the very end of the array. */ }

Actually, it is the code used earlier enveloped in macro.
At first we enter array start tone — empty array, and we place it in the section "0_head". Then we enter one more empty array and we place it in the section "9_tail", it already the end of array. For trailer label of array it is worth inventing any cunning not used name for what the macro __ is already entered by ARRAY_SPREAD_PRIVATE. Actually, all! Now we can place elements in the correct section and address to them as to array cells.

Let's enter macro for these purposes:
#define ARRAY_SPREAD_ADD(name, item)                                       \
    static typeof(name[0]) name ## __element[]  \
        __attribute__ ((used,                                               \
                section(__ARRAY_SPREAD_SECTION(name, "1_body")))) = { item }

In the same way as with tags, we declare array and we place it in section. Difference is the subsection name "1_body" and that it not empty array, but array with the only element transferred in quality of argument. By the way, by means of easy modification it is possible to add any quantity of elements to array but not to load article, I will not bring her here. The added option can be found at in our repository.

This macro has small problem: if with its help to add to array two elements in one file, there will be problem with intersection of characters. Of course, it is possible to use the macro described above and to add all elements in the file at the same time, but, you see, it is not really convenient. Therefore we will simply use macro __ LINE __ and we will receive unique characters for variables.

So, we will enter couple of auxiliary macroes.
The macro concatenates two lines:
#define MACRO_CONCAT(m1, m2) __MACRO_CONCAT(m1, m2)
#define __MACRO_CONCAT(m1, m2) m1 ## m2

Macro _at_line_ and row number adding to the character:
#define MACRO_GUARD(symbol) __MACRO_GUARD(symbol)
#define __MACRO_GUARD(symbol) MACRO_CONCAT(symbol ## _at_line_, __LINE__)

And, at last, the macro adding to us unique name for this file, more precisely, not unique, but oooochen the rare :)
#define __ARRAY_SPREAD_GUARD(array_nm) \
    MACRO_GUARD(__ARRAY_SPREAD_PRIVATE(array_nm, element))

Let's copy macro for adding of element:
#define ARRAY_SPREAD_ADD(name, item)                                       \
    static typeof(name[0]) __ARRAY_SPREAD_GUARD(name)[]  \
        __attribute__ ((used,                                               \
                section(__ARRAY_SPREAD_SECTION(name, "1_body")))) = { item }

To receive the array size, it is necessary to take the address marker of the last element and to subtract from it marker of the beginning of array, the size of elements thus can be not considered as operation is executed in address arithmetics because the tag is defined as array of this type.
#define ARRAY_SPREAD_SIZE(array_name) \
((size_t) (__ARRAY_SPREAD_PRIVATE(array_name, tail) - array_name))

For appearance we will add syntax sugar in the form of foreach macro
#define array_spread_foreach(element, array) \
for (typeof(element) volatile const *_ptr = (array),         \
_end = _ptr + (ARRAY_SPREAD_SIZE(array));       \
(_ptr < _end) &&(((element) = *_ptr) || 1); ++_ptr)


Syntax of unit-tests


Let's return to unit-tests. At us in the project reference syntax for unit-tests is considered syntax of googletest. That in it it is important:
  • There is declaration of test sets
  • There are declarations of separate tests
  • There are functions before - and post-calls both for separate tests, and for test sets
  • There are various checks of conditions of correctness of passing of tests

Let's try formulate syntax in the Xi language taking into account the adjustable arrays described in the previous section. Test set declaration — this array declaration.
ARRAY_SPREAD_DEF(test_routine_t,all_tests);

static int test_func_1(void) {
    return 0;
}
ARRAY_SPREAD_ADD(all_tests, test_func_1);

static int test_func_2(void) {
    return 0;
}
ARRAY_SPREAD_ADD(all_tests, test_func_2);

Respectively, the call of tests can be written so:
array_spread_foreach(test, all_tests) {
        if (test()) {
            printf("error in test 0x%zu\n", (uintptr_t)test);
            return 0;
        }
        printf(".");
    }
    printf("OK\n");

Naturally, the example is strongly simplified, but already now it is visible that if in the test there will be error, the function address that not really informatively will be displaid. It is possible to pomudrit, of course, with the table of characters, times we rigidly use linker, but it will be even more pleasant if syntax of declaration of dough has appearance:
TEST_CASE(“test1 description”) {
};

It is simpler to read detailed comment, than the name of function. To make support of it, we will enter structure of the description of dough. Except function of call it has to contain and field of the description:
struct test_case_desc {
    test_routine_t routine;
    char desc[];
};

Then the call of all tests will look as follows:
    printf("%zu tests start", ARRAY_SPREAD_SIZE(all_tests));

    array_spread_foreach(test, all_tests) {
        if (test->routine()) {
            printf("error in test 0x%s\n", test->desc);
            return 0;
        }
        printf(".");
    }
    printf("OK\n");

And to enter the separate test, we will use once again macro __ LINE __.
Then the test declared on this line will declare function of dough as test_## __ LINE __, and all macro can be written so:
#define TEST_CASE(desc) \
    __TEST_CASE_NM("" desc, MACRO_GUARD(__test_case_struct), \
                    MACRO_GUARD(__test_case))

#define __TEST_CASE_NM(_desc, test_struct, test_func) \
    static int test_func(void);                      \
    static struct test_case_desc test_struct = { \
            .routine = test_func, \
            .desc = _desc,         \
    };                             \
    ARRAY_SPREAD_ADD(all_tests, &test;_struct); \
    static int test_func(void)

It turns out quite beautifully. The internal macro is entered only for increase of readability of code.

Now we will try to enter concept of test set — TEST_SUITE.

Let's go on the checked way. For each test set we will declare adjustable array in which structures with the description of tests will be stored.

Let's start now not the separate test, but test set which will cause in turn separate tests. Here we face one more problem: it is necessary to declare all arrays of the compiled tests as we need to know length of each array. Length of array can be learned and without its declaration if, for example, to use marker of the end of array as it becomes for lines.

Static adjustable arrays with the naming element


Let's return to adjustable arrays. What it is necessary to add in order that at us the option with the naming element has turned out? Let's arrive as we more than once arrived, and we will add the naming element to special subsection which we will place after array cells, but before marker of the end of array — "8_term".
That is, we will a little copy our previous macro of array declaration:
#define ARRAY_SPREAD_DEF(element_type, name) \
        ARRAY_SPREAD_TERM_DEF(element_type, name, /* empty */)

#define ARRAY_SPREAD_TERM_DEF(element_type, name, _term) \
        element_type volatile const name[] __attribute__ ((used,        \
        /* Some versions of GCC do not take into an account section     \
         * attribute if it appears after the definition. */             \
            section(__ARRAY_SPREAD_SECTION(name, "0_head")))) =         \
            { /* Empty anchor to the array head. */ };                  \
        element_type volatile const __ARRAY_SPREAD_PRIVATE(name,term)[] \
        __attribute__ ((used,                                           \
        /* Some versions of GCC do not take into an account section     \
         * attribute if it appears after the definition. */             \
            section(__ARRAY_SPREAD_SECTION(name, "8_term")))) =         \
            { _term };                                                    \
        element_type volatile const __ARRAY_SPREAD_PRIVATE(name,tail)[] \
        __attribute__ ((used,                                           \
        /* Some versions of GCC do not take into an account section     \
         * attribute if it appears after the definition. */             \
            section(__ARRAY_SPREAD_SECTION(name, "9_tail")))) =         \
            { /* Empty anchor at the very end of the array. */ }

Let's add foreach macro () for the arrays named nulyom
#define array_spread_nullterm_foreach(element, array) \
    __array_spread_nullterm_foreach_nm(element, array,  \
            MACRO_GUARD(__ptr))

#define __array_spread_nullterm_foreach_nm(element, array, _ptr) \
    for (typeof(element) volatile const *_ptr = (array);         \
            ((element) = *_ptr); ++_ptr) 

Test sets


Now it is possible to return to test sets.
They too the very simple. Let's enter structure for test set:
struct test_suite_desc {
    const struct test_case_desc *volatile const *test_cases;
    char desc[];
};

As a matter of fact, we need only text descriptor and the pointer on array of tests.
Let's enter macro for test set declaration.
#define TEST_SUITE(_desc) \
    ARRAY_SPREAD_TERM_DEF(static const struct test_case_desc *, \
            __TEST_CASES_ARRAY, NULL /* */);                         \
    static struct test_suite_desc test_suite = {                  \
                    .test_cases = __TEST_CASES_ARRAY,                \
                    .desc = ""_desc,                             \
            };                                                   \
    ARRAY_SPREAD_ADD(all_tests, &test;_suite)

It defines adjustable array for separate tests. With this array there was discrepancy — the array has to have unique name, after all it cannot be static though we also thought of adding of possibility of static array declaration. In the project we use own assembly system, and for each module we generate unique identifier with his full name. Straight off the problem did not manage to be solved and therefore for declaration of test set it is necessary to set unique name of its array with tests.
#define __TEST_CASES_ARRAY test_case_array_1
TEST_SUITE("first test suite");

For the rest, declaration of set of texts looks decently.
Except array the structure of this set is defined and initialized, and the pointer on this structure is located in global array of test sets.

Let's a little change maskros for test case declaration:
#define TEST_CASE(desc) \
    __TEST_CASE_NM("" desc, MACRO_GUARD(__test_case_struct), \
                    MACRO_GUARD(__test_case))

#define __TEST_CASE_NM(_desc, test_struct, test_func)  \
    static int test_func(void);                        \
    static struct test_case_desc test_struct = {       \
            .routine = test_func,                      \
            .desc = _desc,                             \
    };                                                 \
    ARRAY_SPREAD_ADD(__TEST_CASES_ARRAY, &test;_struct); \
    static int test_func(void)

In fact, only the array in which our test is brought changes.

It was necessary to replace call of tests:
    array_spread_foreach(test_suite, all_tests) {
        printf("%s", test_suite->desc);
        array_spread_nullterm_foreach(test_case, test_suite->test_cases) {
            if (test_case->routine()) {
                printf("error in test 0x%s\n", test_case->desc);
                return 0;
            }
            printf(".");
        }
        printf("OK\n");
    }


We still had many unconsidered aspects, but I want to complete article on this moment, as the main idea considered. If it is interesting to readers I will continue already in the following article.
I will provide screenshot of that in the conclusion that has turned out:
One more way of automatic call of unit-tests in the Xi language
The code given in article lies at us in separate repository. We have thought that the solution has turned out interesting and it can be demanded as separate framework not only at us in the project therefore began to take out it. Well and at the same time have written article, I hope the interesting.

P.S. The author of original idea is abusalimov.

This article is a translation of the original post at habrahabr.ru/post/265461/
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