Составьте комбинированную программу набора тестов из набора модульных тестов.

Я создаю набор модулей с модульными тестами и хотите автоматически сгенерировать комбинированную программу для выполнения всех тестов.

Потворствуя своему внутреннему фанатику контроля, я решил использовать структуру minunit.h и попробуй сделать все вручную. Итак, у меня есть два исходных файла (будет больше).

-rw-r--r-- 1 josh None 6192 Aug 21 23:15 io.c
-rw-r--r-- 1 josh None 2341 Aug 22 00:49 st.c

Каждый из них имеет встроенный модульный тест (поэтому мне легче следить за обновлениями и, возможно, на самом деле использовать его на этот раз), охраняемый #ifdef TESTMODULE. В общих чертах:

... 
//module code 
... 

#ifdef TESTMODULE 
... 
//unit tests 
... 
int main(){ 
    // call unit tests 
} 
#endif 

Затем я могу создать тестовую программу с очень коротким файлом.

$ cat > io_test.c
#define TESTMODULE
#include "io.c"
$ make io_test
cc     io_test.c   -o io_test

Я написал комбинированную программу вручную, используя тот же тип включения, но переопределяя все глобальные имена, чтобы они были уникальными.

# define main io_main 
# define tests_run io_tests_run 
# define all_tests io_all_tests 
# include "io_test.c" 
# undef main 
# undef tests_run 
# undef all_tests 
int io_test(){ 
    printf("running io_test\n"); 
    return io_main(); 
} 

# define main st_main 
# define tests_run st_tests_run 
# define all_tests st_all_tests 
# include "st_test.c" 
# undef main 
# undef tests_run 
# undef all_tests 
int st_test(){ 
    printf("running st_test\n"); 
    return st_main(); 
} 

int main(){ 
    return 
        0  || io_test()  || st_test()  ; 
} 

Теперь я хочу сгенерировать это автоматически из гораздо более короткого списка переменных частей: io и st. Я могу сделать большую часть работы с X-макросами.

#define HASH #
#define UNITS(_) \
_(io) \
_(st) \
/**/

#define gen_unit_function(unit) \
HASH define main unit##_main \
HASH define tests_run unit##_tests_run \
HASH define all_tests unit##_all_tests \
HASH include STR(unit##_test.c) \
HASH undef main \
HASH undef tests_run \
HASH undef all_tests \
int unit##_test(){ \
    printf("running " STR(unit) "_test\n"); \
    return unit##_main(); \
}

#define gen_func_call(unit) \
    || unit##_test()

UNITS(gen_unit_function)
int main(){
    return
        0 UNITS(gen_func_call) ;
}

Но, конечно, это никогда не сработает, потому что вы не можете форсировать новую строку с помощью препроцессора C. Что можно использовать для обхода этого ограничения?


person luser droog    schedule 23.08.2015    source источник


Ответы (2)


Вы можете сгенерировать исходный код с помощью m4, гораздо более мощного макропроцессора. .

Теперь m4 не будет делать X-макросы таким же образом. Вы не можете просто передать имя макроса и переоценить его в тексте расширения. Но документы gnu описывают (и распространяют) макрос foreach, который дает ту же возможность.

divert(`-1') 
# http://www.gnu.org/savannah-checkouts/gnu/m4/manual/m4-1.4.17/html_node/Foreach.html#Foreach 
# foreach(x, (item_1, item_2, ..., item_n), stmt) 
#   parenthesized list, simple version 
define(`foreach', `pushdef(`$1')_foreach($@)popdef(`$1')') 
define(`_arg1', `$1') 
define(`_foreach', `ifelse(`$2', `()', `', 
    `define(`$1', _arg1$2)$3`'$0(`$1', (shift$2), `$3')')') 

define(`UNITS', (io,st))

divert`'dnl 
`#' include <stdio.h> 
foreach(`unit', UNITS, ` 
`#' define main unit`'_main 
`#' define tests_run unit`'_tests_run 
`#' define all_tests unit`'_all_tests 
`#' include "unit`'_test.c" 
`#' undef main 
`#' undef tests_run 
`#' undef all_tests 
int unit`'_test(){ 
    printf("running unit`'_test\n"); 
    return unit`'_main(); 
} 
')dnl 

int main(){ 
    return 
        0 foreach(`unit', UNITS, ` || unit`'_test() ') ; 
} 

Затем программу можно сгенерировать, запустив m4 all_tests.m4 > all_tests.c. И это можно добавить в makefile.

all_tests.c:all_tests.m4 *_test.c
        m4 $< >$@

Но заходить в этот файл m4 для обновления списка будет некрасиво, поэтому воспользуйтесь соглашением об именах и сгенерируйте список (io,st) с помощью make (который знает, как проверять имена файлов, каталоги и прочее).

$cat makefile
testprogs= $(notdir $(wildcard ./*_test.c)) 
unitprogs= $(subst _test,,$(testprogs)) 
units= $(basename $(unitprogs)) 

test:all_tests 
        ./all_tests 
all_tests.c:all_tests.m4 makefile $(unitprogs) 
        m4 -D UNITS="$(units)" $< >$@ 

Теперь, поскольку make любит списки, разделенные пробелами, но наш файл m4 настроен на использование списка, разделенного запятыми, нам нужно предварительно обработать список перед его использованием, создав эту окончательную версию.

$cat all_tests.m4
divert(`-1') 
# http://www.gnu.org/savannah-checkouts/gnu/m4/manual/m4-1.4.17/html_node/Foreach.html#Foreach 
# foreach(x, (item_1, item_2, ..., item_n), stmt) 
#   parenthesized list, simple version 
define(`foreach', `pushdef(`$1')_foreach($@)popdef(`$1')') 
define(`_arg1', `$1') 
define(`_foreach', `ifelse(`$2', `()', `', 
    `define(`$1', _arg1$2)$3`'$0(`$1', (shift$2), `$3')')') 

define(`UNITS', (patsubst(UNITS,`\W',`,'))) 

divert`'dnl 
`#' include <stdio.h> 
foreach(`unit', UNITS, ` 
`#' define main unit`'_main 
`#' define tests_run unit`'_tests_run 
`#' define all_tests unit`'_all_tests 
`#' include "unit`'_test.c" 
`#' undef main 
`#' undef tests_run 
`#' undef all_tests 
int unit`'_test(){ 
    printf("running unit`'_test\n"); 
    return unit`'_main(); 
} 
')dnl 

int main(){ 
    return 
        0 foreach(`unit', UNITS, ` || unit`'_test() ') ; 
} 

Теперь всякий раз, когда готов новый модуль с модульным тестированием main(), защищенным TESTMODULE, его можно добавить в набор, написав тот же двухстрочный файл, описанный в вопросе, соответствующий шаблону *_test.c.

#define TESTMODULE 
#include "zz.c"

И unit-test, и all_tests будут компилироваться просто с помощью make.

[Этот материал ранее был опубликован на comp.lang.c.]

person luser droog    schedule 23.08.2015

Другой вариант — дополнить препроцессор чем-то вроде sed, чтобы обрезать строки. Вставьте токен, который может искать sed, например EOL.

#define HASH #
#define UNITS(_) \
_(io) \
_(st) \
/**/

#define gen_unit_function(unit) \
HASH define main unit##_main EOL\
HASH define tests_run unit##_tests_run EOL\
HASH define all_tests unit##_all_tests EOL\
HASH include STR(unit##_test.c) EOL\
HASH undef main EOL\
HASH undef tests_run EOL\
HASH undef all_tests EOL\
int io_test(){ EOL\
    printf("running " STR(unit) "_test\n"); EOL\
    return unit##_main(); EOL\
}

#define gen_func_call(unit) \
    || unit##_test()

UNITS(gen_unit_function)
int main(){
    return
        0 UNITS(gen_func_call) ;
}

А затем пропустите его через sed после cpp.

cpp -P test_suite.inc | sed 's/EOL */
/g'
person luser droog    schedule 09.06.2016