Testing your crate C-API
Tags: rust, cargo, capi
Published: 2020-11-22 12:04:49

I wrote [previously](https://dev.to/luzero/building-crates-so-they-look-like-c-abi-libraries-1ibn) about [cargo-c](https://github.com/lu-zero/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](https://docs.rs/cargo/0.49.0/cargo/core/compiler/struct.Unit.html) 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](https://crates.io/crates/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.

``` rust
#[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()
}
```

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].

``` toml
[dev-dependencies]
inline-c = "0.1"
```

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

``` rust
#[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();
    }
}
```

And then run the tests through cargo-c:
``` sh
$ cargo ctest
```

It is available since version `0.6.17` that I just released on [crates.io](https://crates.io/crates/cargo-c).

Enjoy!