inline-c
Have you ever written a C API for your Rust program? Have you ever dreamed of running your C API directly in your Rust implementation, for example to unit test it? No? Because I did. Bah, I'm probably not the only one. Right? Please tell me I'm not the only one!
The inline-c
crate is a really immature project that allows you to
write C code within Rust directly, to compile it and to run some
assertions.
Please, do not use this crate for evil purposes. Stay reasonable. The
main idea is to test a C API, for instance of a Rust program that is
automatically generated with cbindgen
.
Example
assert_c!
and assert_cxx!
macros
The Basic usage of the assert_c!
(or assert_cxx!
) macro. In the
following example a simple Hello, World! C program is compiled and
executed. It is then asserted than the exit code and the outputs are
correct. The next example asserts than the C program correctly returns
an error.
#[test]
fn test_successful() {
(assert_c! {
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
})
.success()
.stdout("Hello, World!\n")
.no_stderr();
}
#[test]
fn test_badly() {
(assert_c! {
int main() {
int x = 1;
int y = 2;
return x + y;
}
})
.failure()
.code(3)
.no_stdout()
.no_stderr();
}
Define environment variables
Now, let's enter the real reason to live of this project. Let's way we want a C program to link against a specific shared library.
Note: The
CFLAGS
,CXXFLAGS
,CPPFLAGS
andLDFLAGS
are supported environment variables.
Great! We may want to define a value for the CFLAGS
and the
LDFLAGS
environment variables. First way is to use the
#inline_c_rs
C directive with the following syntax:
#include_c_rs <variable_name>: "<variable_value>"
Please note the double quotes around the variable value.
Let's see a concrete example. We declare 3 environment variables,
resp. FOO
, CFLAGS
and LDFLAGS
. The C program prints their
corresponding values, and exit accordingly.
#[test]
fn test_c_macro_with_env_vars_inlined() {
(assert_c! {
#inline_c_rs FOO: "bar baz qux"
#inline_c_rs CFLAGs: "-Ixyz/include -Lzyx/lib"
#inline_c_rs LDFLAGS: "-lfoo"
#include <stdio.h>
#include <stdlib.h>
int main() {
const char* foo = getenv("FOO");
if (NULL == foo) {
return 1;
}
printf("FOO is set to `%s`\n", foo);
return 0;
}
})
.success()
.stdout("FOO is set to `bar baz qux`\n")
.no_stderr();
}
This is cool isn't it? But it can be repetitive. What if we can define
environment variables globally, for all the C program written in
assert_c!
or assert_cxx!
?
It is possible with meta environment variables, with the following syntax:
INLINE_C_RS_<variable_name>=<variable_value>
Let's see it in action. We set 2 environments variables,
resp. INLINE_C_RS_FOO
, INLINE_C_RS_CFLAGS
and
INLINE_C_RS_LDFLAGS
, that will create FOO
, CFLAGS
and LDFLAGS
for this C program specifically:
#[test]
fn test_c_macro_with_env_vars_from_env_vars() {
set_var("INLINE_C_RS_FOO", "bar baz qux");
set_var("INLINE_C_RS_CFLAGS", "-Ixyz/include -Lxyz/lib");
set_var("INLINE_C_RS_LDFLAGS", "-lfoo");
(assert_c! {
#include <stdio.h>
#include <stdlib.h>
int main() {
const char* foo = getenv("FOO");
if (NULL == foo) {
return 1;
}
printf("FOO is set to `%s`\n", foo);
return 0;
}
})
.success()
.stdout("FOO is set to `bar baz qux`\n")
.no_stderr();
remove_var("INLINE_C_RS_FOO");
remove_var("INLINE_C_RS_CFLAGS");
remove_var("INLINE_C_RS_LDFLAGS");
}
Note that we have use
set_var
and
remove_var
to set or remove the environment variables. That's for the sake of
simplicity: It is possible to set those variables before running your
tests or anything.
License
BSD-3-Clause
, see LICENSE.md
.