#build-script #configuration #cargo

conf_test

Run configuration tests from build.rs and set available features

8 releases (4 breaking)

0.5.0 May 4, 2023
0.4.0 May 3, 2023
0.3.2 May 3, 2023
0.3.1 Dec 22, 2022
0.1.2 Apr 28, 2021

#651 in Configuration


Used in 2 crates (via openat_ct)

MIT/Apache

19KB
258 lines

Rust conf_test

Run configuration tests from build.rs and set available features, similar to autotools configure scripts.

Description

ConfTest::run() called from 'build.rs' parses 'Cargo.toml'. Then for each [feature] defined, it checks if that feature was not set manually (with --features) and a test in 'conf_tests/' exists. This test is then compiled and build. When that succeeds the feature becomes enabled automatically.


lib.rs:

Run configuration tests from build.rs and set available features, similar to autotools configure scripts.

Description

ConfTest::run() called from 'build.rs' parses 'Cargo.toml'. Then for each [feature] defined, it checks if that feature was not set manually (with --features) and a test in 'conf_tests/' exists. This test is then compiled and build. When that succeeds the feature becomes enabled automatically.

Special case for 'docs.rs'

When a packages is build for documentation on 'docs.rs' then conf_test detects and checks if a docs_rs = [] features is available in 'Cargo.toml', if so, then this becomes enabled. When not, then nothing is probed.

Rationale

Compiler versions and Operating Systems have sometimes subtle differences in features and standards conformance. Sometimes non-standard features are added for improved performance. These differences make it hard to write portable software that still offer optimal performance for different Operating Systems. Moreover often a developer doesn't even know what featureset other Operating Systems may provide or this may be changed by kernel or userland version or configuration. Probing the presence of such features at build time can solve these problems.

Further it becomes possible to test for rust stdlib functionality such as if nightly features are available or became stabilized.

How To

Checking OS features

When present 'cargo' (builds and) executes a build.rs script while building crate. This is the place where ConfTest is hooked in at first:

fn main() {
    conf_test::ConfTest::run();
    // any other build.rs steps follow below
}

Further one has to define a set of features and dependencies in the 'Cargo.toml'. Note that 'build.rs' will be run before the '[dependencies]' are build. Thus all dependencies needed for the tests must go into '[build-dependencies]' as well. For Example:

[dependencies]
libc = "0.2.34"

[build-dependencies]
libc = "0.2.34"
conf_test = "0.4"

[features]
default = []
o_path = []

And as final step the crate directory 'conf_tests/' need to be created which contain rust files named after the features to be probed. Containing a single fn main() which shall probe one single thing.

// This goes into conf_tests/o_path.rs
extern crate libc;

fn main() {
    unsafe {
        let conf_tests = std::ffi::CString::new("conf_tests").unwrap();
        // Compilation will fail when the libc does not define O_PATH
        libc::open(conf_tests.as_ptr(), libc::O_PATH);
    }
}

Later in the crate implementation source code one uses conditional compilation as usual with #[cfg(feature = "o_path")].

Test depending on other Features

Tests may depend on features that are discovered by other tests or set manually. For simplicity there is no dependency resolver about this but tests are run in sort order of the feature name. Every subsequent test is compiled with the the feature flags already discovered so far. To leverage this functionality one rarely needs to change the feature names. For example when 'bar' depends on 'foo' it is required to enforce the sort order by renaming these features to 'aa_foo' and 'bb_bar'. Only features that get discovered are used for the test compilations features set by printing cargo instructions from the test scripts are not used.

Detailed Control

Tests can emit special instructions to cargo on stdout. These become only effective when the test exits successful. See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script

One can control ConfTest by setting the environment variable CONF_TEST_INHIBIT to one of the following:

  • skip Will not execute any conf_tests but proceed with 'build.rs'.
  • stop Exits 'build.rs' sucessfully, not executing any tests.
  • fail Exits 'build.rs' with an failure, not executing any tests.

Any other value will make the script panic.

Limitations

  • The tests running on the machine where the software is build, using the build-dependencies. This will be a problem when Software gets cross-compiled. For cross compilation set 'CONF_TEST_INHIBIT=skip' and set the desired features manually with the '--features' option.

  • Features can only be set, not unset. This is deliberate and not a limitation. Do only positive tests checking for the presence of a feature.

Good Practices

  • Only use ConfTest when other things (like factoring out OS specific thing into their own crates) are not applicable.

  • Provide a baseline implementation which is portable with no features enabled. This may not perform as well or lack some special features but should compile nevertheless.

Dependencies

~1–1.9MB
~39K SLoC