DEV Community

Luca Barbato
Luca Barbato

Posted on

Testing your crate C-API

I wrote previously about cargo-c.

It is a good solution if you want to use your rust crates from C: with minimal changes you get a set of libraries, header and pkg-config that downstream application won't feel any different than a normal C library.

But an itch was left. I wanted to have proper tests and make so they won't feel much different than what we have in the cargo ecosystem.

The long-term plan was to make cargo build the C code as a stand alone Unit and link everything together in the test runner. Getting it right requires lots of time and so far I hadn't had enough.

Inline-c

Recently I stumbled upon inline-c and it felt like a great shortcut.

It let you write tests by embedding C code and then have the procedural macro invoke the compiler with the parameters pass using the usual env variables: CC, CFLAGS, LDFLAGS and so on.

#[test]
fn test_api_something() {
set_var("INLINE_C_RS_CFLAGS", "-Iinclude/something/")
set_var("INLINE_C_RS_LDFLAGS", "-Llib/path -lsomething")
(assert_c! {
        #include <something.h>

        int main() {
            SomeThing *thing = NULL;
            int ret = some_init(&thing);
            if (ret != 0)
                return -1;
            ...
            some_destroy(&thing);

            return 0;
        }
    })
    .success()
}
Enter fullscreen mode Exit fullscreen mode

It does work nicely but it requires to feed it the right include and library search path.

cargo ctest

cargo-c produces the header and library in known locations so it is fairly easy to set the env variables accordingly and makes the whole experience much nicer.

All you need to do is to add inline-c to your [dev-dependencies].

[dev-dependencies]
inline-c = "0.1"
Enter fullscreen mode Exit fullscreen mode

Write the test without having to care about where the header and the libraries are:

#[cfg(feature = "capi")]
mod capi {
    use inline_c::assert_c;

    #[test]
    fn test_capi() {
        (assert_c! {
        #include <example_project.h>
        #include <stdio.h>

        int main() {
            ExampleProjectOddCounter *counter = example_project_oddcounter_new(4);
            if (counter) {
                printf("Unexpected success\n");
                return 1;
            }
            counter = example_project_oddcounter_new(5);
            if (!counter) {
                printf("Error creating ExampleProjectOddCounter\n");
                return 1;
            }
            example_project_oddcounter_increment(counter);
            uint32_t result = example_project_oddcounter_get_current(counter);
            example_project_oddcounter_free(counter);
            if (result == 7) {
                return 0;
            } else {
                printf("Error: unexpected result: %d\n", result);
                return 1;
            }
        }
            })
        .success();
    }
}
Enter fullscreen mode Exit fullscreen mode

And then run the tests through cargo-c:

$ cargo ctest
Enter fullscreen mode Exit fullscreen mode

It is available since version 0.6.17 that I just released on crates.io.

Enjoy!

Top comments (0)