Макрос C для определения методов тестирования с обратными вызовами - PullRequest
2 голосов
/ 20 марта 2019

Таким образом, вы можете динамически определять метод со своим собственным именем, например:

#define test(name) void name() { print("#name"); }

Затем вы можете назвать его следующим образом:

test(foo);
foo();

Интересно, можете ли высоздайте форму в стиле «обратного вызова», например:

#define test(name, body) void name() { print(#name); body(); }

, где он вызывает тело, которое определяется как своего рода «блок», например:

test(dosomething, {
  int a = add(1, 1);
  assert(a == 2);
})

Но болеекроме этого, я хотел бы передать обратный вызов для асинхронных функций, чтобы сказать, что они завершены, например:

test(dosomething, { (done)
  int a = add(1, 1);
  assert(a == 2);
  done();
})

Кроме того, я определяю их вне main, так что это будет определенов той же области, что и обычная функция.Из-за этого тесты не собираются автоматически запускаться.Они должны быть повторены по .Таким образом, их, вероятно, нужно поместить в какой-то массив.Интересно, как это можно сделать, если макросы позволяют сортировать данные в массив или создавать перечисление по одному #define за раз.

#define test(name, body) void name() { \
  print(#name); \
} \
\
TESTS[CURRENT_TEST++] = &name \ // push the test into a TESTS array.

Итак, в mainВы можете перебрать их:

int
main() {
  iterate over TESTS...
}

Подводя итог, мне интересно, как #define это на уровне тела файла (т.е. не в основном, но на уровне функций):

void afunction() { printf("Foo"); }

test(dosomething, { (done)
  int a = add(1, 1);
  assert(a == 2);
  done();
})

void anotherfunction() { printf("Bar"); }

, чтобы я мог выполнять итерации по тестам в main.

  • Это предполагает, что в макросах возможны блоки.

1 Ответ

2 голосов
/ 20 марта 2019

Похоже, вы строите какой-то каркас мини-теста, используя препроцессор C.

Есть предостережение для тел;для препроцессора C фигурные скобки и квадратные скобки являются просто токенами.Выражения в скобках распознаются (т. Е. Круглые скобки совпадают), а запятые распознаются как разделители.Так, например, этот вызов макроса:

test(dosomething, { int a = add(1, 1); assert(a == 2); })

... имеет два аргумента, несмотря на наличие двух запятых (потому что вторая запятая «обнята» в наборе в скобках), но это немного вводит в заблуждение.Этот вызов:

test(dosomething, { enum { red, green, blue }; assert(red+1==green); })

... имеет четыре аргумента: 1: dosomething, 2: { enum { red, 3: green и 4: blue }; assert(red+1==green); }.Если вы собираетесь это сделать, вы, вероятно, захотите охватить такие случаи, как это ... есть базовые стратегии: (а) обнять тело в скобках (вы можете развернуть его в расширении) или (б) использовать макросы с переменным числом аргументов.

Их нужно повторить.

Похоже на задание для x-macros (ниже я буду использовать вариант параметризованного макроса x-macros).

Но более того, я хотел бы передать обратный вызов для асинхронных функций, чтобы сказать, что они завершены, например:

... вы не можете добавить аргумент в середине, но скобки не должны быть частью этого (они все равно не помогают, так как препроцессор их игнорирует).Так что для вышеизложенного мы, вероятно, хотим выбрать вариант объятия.В результате ваши вызовы выглядят так:

test(dosomething, (int a=add(1,1); assert(a==2);), done)

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

Вот примерно так это выглядело бы, используяпараметризованная версия макроса x-macros и применение асинхронного расширения (используя семафоры, чтобы продемонстрировать, насколько это может быть произвольно):

#define APPLY_TEST_MACROS(macro) \
    macro(test_add, (int a=add(1,1); assert(a==2);  )) \
    macro(test_sub, (int a=sub(5,2); assert(a==3);  )) \
    macro(test_mul, (int a=mul(3,4); assert(a==12); ))

#define UNWRAP(...) __VA_ARGS__
#define MAKE_ASYNC_SEM(NAME_, BODY_) \
   void NAME_() { \
      sem_wait(&test_sem_ctl); print(#NAME_); sem_post(&test_sem_ctl); \
      UNWRAP BODY_ \
      sem_wait(&test_sem_ctl); \
      if (0==--tests_remaining) sem_post(&test_sem_done); \
      sem_post(&test_sem_ctl); \
   }
#define COUNT_TESTS(NAME_, BODY_) +1
sem_t test_sem_ctl;
sem_t test_sem_done;
void init_semaphores() {
   sem_init(&test_sem_ctl, 0, 1);
   sem_init(&test_sem_done, 0, 0);
}
// iterate over tests to count them
unsigned int tests_remaining = APPLY_TEST_MACROS(COUNT_TESTS);
// define the tests
APPLY_TEST_MACROS(MAKE_ASYNC_SEM)

... и так далее (я остановлюсь здесь, потому что идеяэто передать идею, а не код для вас).Макет x-macro позволяет вам выполнять итерации в препроцессоре, так что вы можете сделать что-то вроде порождения потока за тест; также можно просто использовать этот же подход для построения массива тестовых функций, если, скажем, вы хотите передать свои тесты в пул потоков.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...