Как выполнить модульное тестирование функций языка C PostgreSQL - PullRequest
1 голос
/ 02 апреля 2019

Я разрабатываю пользовательскую функцию для PostgreSQL на C. Эта функция будет использоваться как часть пользовательского агрегата. Я следую документации по Определяемым пользователем агрегатам и Функции языка C . Мне известна инфраструктура построения расширений, которую предоставляет установка PostgreSQL, задокументированная здесь , которая также предоставляет средства для тестирования расширения. Однако, чтобы облегчить разработку расширений PostgreSQL, мне было интересно, есть ли способ протестировать пользовательскую функцию, используя обычные методы модульного тестирования на Си.

Я пытаюсь использовать CMocka для моего модульного тестирования. Это осложняется макросами PG_FUNCTION_ARGS и PG_FUNCTION_INFO_V1 из fmgr.h, которые необходимы для функций C, которые будут взаимодействовать с PostgreSQL. Эти макросы имеют тенденцию абстрагировать некоторые параметры обработки и в результате запутывают эту функциональность. В результате я испытываю трудности с вызовом моей пользовательской функции PostgreSQL в модульном тесте. У меня возникают ошибки, связанные с ожидаемыми типами параметров. Я не уверен, как вручную построить список параметров с помощью макросов для моей функции. Ниже приведены ошибки GCC, которые я получаю при попытке запустить мой модульный тест.

    gcc -fprofile-arcs -ftest-coverage -I '/usr/include/postgresql/10/server' ./tests/test_max_pos_min_neg.c -o ./build/tests/test_max_pos_min_neg
./tests/test_max_pos_min_neg.c: In function ‘test_get_max_pos_min_neg’:
./tests/test_max_pos_min_neg.c:21:25: warning: passing argument 1 of ‘get_max_pos_min_neg’ from incompatible pointer type [-Wincompatible-pointer-types]
     get_max_pos_min_neg((float4 *) init_cond, 10.0, -2.5, 7.5);
                         ^
In file included from ./tests/test_max_pos_min_neg.c:6:0:
./tests/../src/max_pos_min_neg.h:8:7: note: expected ‘FunctionCallInfo {aka struct FunctionCallInfoData *}’ but argument is of type ‘float4 * {aka float *}’
 Datum get_max_pos_min_neg(PG_FUNCTION_ARGS);
       ^~~~~~~~~~~~~~~~~~~
./tests/test_max_pos_min_neg.c:21:5: error: too many arguments to function ‘get_max_pos_min_neg’
     get_max_pos_min_neg((float4 *) init_cond, 10.0, -2.5, 7.5);
     ^~~~~~~~~~~~~~~~~~~
In file included from ./tests/test_max_pos_min_neg.c:6:0:
./tests/../src/max_pos_min_neg.h:8:7: note: declared here
 Datum get_max_pos_min_neg(PG_FUNCTION_ARGS);
       ^~~~~~~~~~~~~~~~~~~
./tests/test_max_pos_min_neg.c:24:5: warning: implicit declaration of function ‘assert_float_equal’; did you mean ‘assert_int_equal’? [-Wimplicit-function-declaration]
     assert_float_equal(init_cond[0], 10.0, EPSILON);
     ^~~~~~~~~~~~~~~~~~
     assert_int_equal
Makefile:59: recipe for target 'build/tests/test_max_pos_min_neg' failed
make: *** [build/tests/test_max_pos_min_neg] Error 1

Вот мой исходный код:

#include "postgres.h"
#include "fmgr.h"
#include "utils/array.h"

PG_MODULE_MAGIC;

Datum get_max_pos_min_neg(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(get_max_pos_min_neg);

// Computes the maximum positive and mininum negative sentiment scores.
Datum
get_max_pos_min_neg(PG_FUNCTION_ARGS)
{
    ArrayType *state_array;
    float4 *state;
    float4 pos, neg, score;

    state_array = PG_GETARG_ARRAYTYPE_P(0);
    pos = PG_GETARG_FLOAT4(1);
    neg = PG_GETARG_FLOAT4(2);
    score = PG_GETARG_FLOAT4(3);

    state = (float4 *) ARR_DATA_PTR(state_array);

    if (state[2] < score)
    {
        state[0] = pos;
        state[1] = neg;
        state[2] = score;
    }
    if (score < state[5])
    {
        state[3] = pos;
        state[4] = neg;
        state[5] = score;
    }
    PG_RETURN_ARRAYTYPE_P(state);
}

Мой заголовочный файл:

#include "postgres.h"
#include "fmgr.h"
#include "utils/array.h"

Datum get_max_pos_min_neg(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(get_max_pos_min_neg);

Мой юнит-тест:

#include "../src/max_pos_min_neg.h"
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include "cmocka.h"

const float4 EPSILON = 0.001;

void 
test_get_max_pos_min_neg(void **state)
{
    (void) state; /* unused */
    // This is the initial condition used by the SQL stored procedure
    float4 init_cond[] = {0, 0, -1, 0, 0, 100};

    get_max_pos_min_neg(init_cond, 10.0, -2.5, 7.5);

    // init_cond should now be {10.0, -2.5, 7.5, 10.0, -2.5, 7.5}
    assert_float_equal(init_cond[0], 10.0, EPSILON);
    assert_float_equal(init_cond[1], -2.5, EPSILON);
    assert_float_equal(init_cond[2], 7.5, EPSILON);
    assert_float_equal(init_cond[3], 10.0, EPSILON);
    assert_float_equal(init_cond[4], -2.5, EPSILON);
    assert_float_equal(init_cond[5], 7.5, EPSILON);
}

const struct CMUnitTest tests[] = { 
    cmocka_unit_test(test_get_max_pos_min_neg)
};

int 
main(void)
{
    return cmocka_run_group_tests(tests, NULL, NULL);
}

Будем весьма благодарны за любую помощь, которую вы можете оказать, чтобы протестировать функции языка C PostgreSQL вне PostgreSQL. Спасибо!

1 Ответ

2 голосов
/ 02 апреля 2019

Как говорит ошибка, аргумент на самом деле должен быть FunctionCallInfo (который скрыт за макросом).

Чтобы протестировать функцию вне PostgreSQL, вам необходимо создать макет для сервера PostgreSQL (интерфейс вызова функции, но также и любую серверную функцию, которую вы используете в своем коде).

Мало того, что это будет очень сложно, вам придется обновлять свой макет с каждой новой версией сервера (внутренние API имеют тенденцию иногда меняться), но вы также рискуете внести некоторые свежие ошибки. в ваш тестовый код, что сделало бы сомнительным значение такого теста.

Мой совет не идти по этому пути.

PostgreSQL поддерживает однопользовательский режим (postgres --single -D /data/directory dbname), который вы можете встроить в свою тестовую среду. Вы можете использовать каналы для связи с сервером, и как только вы закроете стандартный ввод PostgreSQL, сервер отключится.

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