简体   繁体   中英

How To Unit Test PostgreSQL C-Language Functions

I am developing a user defined function for PostgreSQL in C. This function will be used as part of a user defined aggregate. I am following the documentation on User-defined Aggregates and C-Language Funtions . I am aware of the extension building infrastructure that a PostgreSQL installation provides, documented here , which also provides a means of testing the extension. However, to ease the development of PostgreSQL extensions, I was wondering if there is any way to test the user function using regular C unit testing techniques.

I am trying to use CMocka for my unit testing. This is complicated by the PG_FUNCTION_ARGS and PG_FUNCTION_INFO_V1 macros from fmgr.h that are needed for C functions that will be interfacing with PostgreSQL. These macros tend to abstract some of the parameter handling and as a result obfuscate this functionality. As a result, I am having difficulty calling my PostgreSQL user function in a unit test. The errors I am having are related to the expected parameter types. I'm not sure how to manually construct a parameter list via the macros for my function. Below are the GCC errors I get when I try to run my unit test.

    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

Here is my source code:

#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);
}

My header file:

#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);

My unit test:

#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);
}

Any help you can provide on how to unit test PostgreSQL C-Language functions outside of PostgreSQL would be much appreciated. Thank you!

As the error tells you, the argument actually has to be a FunctionCallInfo (which is hidden behind a macro).

To test the function outside PostgreSQL, you would have to build a mock-up for the PostgreSQL server (the function call interface, but also any server function you happen to use in your code).

Not only would this be very difficult, and you would have to update your mock-up with every new server version (internal APIs tend to change occasionally), but you are also running the non-neglectible risk of introducing some fresh bugs into your test code, which would make the value of such a test questionable.

My advice is not to go down that road.

PostgreSQL supports a single-user mode ( postgres --single -D /data/directory dbname ) which you can embed into your test framework. You could use pipes to communicate with the server, and as soon as you close PostgreSQL's standard input, the server will shut down.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM