DEV Community

Luca Barbato
Luca Barbato

Posted on

3 3

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!

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay