Can I write tests for invalid lifetimes? - testing

I'm writing some Rust code that manipulates raw pointers. These raw pointers are then exposed to users through structures that use ContravariantLifetime to tie the lifetime of the struct to my object.
I'd like to be able to write tests that validate that the user-facing structures cannot live longer than my object. I have code like the following:
fn element_cannot_outlive_parts() {
let mut z = {
let p = Package::new();
p.create() // returns an object that cannot live longer than p
};
}
This fails to compile, which is exactly what I want. However, I'd like to have some automated check that this behavior is true even after whatever refactoring I do to the code.
My best idea at the moment is to write one-off Rust files with this code and rig up bash scripts to attempt to compile them and look for specific error messages, which all feels pretty hacky.

The Rust project has a special set of tests called "compile-fail" tests that do exactly what you want.
The compiletest crate is an extraction of this idea that allows other libraries to do the same thing:
fn main() {
let x: (u64, bool) = (true, 42u64);
//~^ ERROR mismatched types
//~^^ ERROR mismatched types
}
One idea that gets halfway there is to use Cargo's "features".
Specify tests with a feature flag:
#[test]
#[cfg(feature = "compile_failure")]
fn bogus_test() {}
Add this to Cargo.toml:
[features]
compile_failure = []
And run tests as
cargo test --features compile_failure
The obvious thing missing from this is the automatic checking of "was it the right failure". If nothing else, this allows me to have tests that are semi-living in my codebase.

You are able to annotate a test that you expect to fail.
#[should_fail]
As such, you can write a test that attempts to breach the life time it should have, and thus fail, which would actually be a pass.
For an example of a test for 'index out of bounds' see below (pulled from the Rust guides)
#[test]
#[should_fail]
fn test_out_of_bounds_failure() {
let v: &[int] = [];
v[0];
}
I believe that this example would be a compilation error, so it would stand to reason your compile lifetime violation error would be caught by this too.

Related

Using conditionally compiled functions in rust benchmarks

Before I begin, let me note that I think there have been many related questions and answers, all of which proved useful to me. However, I couldn't find anyone who thoroughly described a method to do everything I wanted, so I thought I would document the problem and my solution and ask if there were better approaches.
Let's suppose that I have some slow--but definitely correct--code to perform a certain task that I keep in my project to test a faster implementation. For concreteness, define:
pub fn fast(x: T) -> U {
// does stuff and eventually returns a value `out`
// ...
debug_assert_eq!(out, slow(x))
out
}
#[cfg(any(test, debug_assertions))]
pub fn slow(x: T) -> U { ... }
This is all fine and good. However, now suppose that I would like to add some benchmarks to demonstrate how good my fast implementation is...
Attempt 1: Criterion
I think that a standard way to set up benchmarking is to put a benches/ directory in the project, add a [[bench]] to Cargo.toml with the harness disabled, and use the criterion crate. However, if I understand correctly, if we then run cargo bench, the benchmark will have to take the position of a user that cannot access crate features defined only during testing. Thus, slow will not be resolved and the command will fail.
A Quick Aside: Another thing that derailed me for a while is that I kept wanting to use bench as a cfg flag but couldn't find anything about this. As it turns out, I think that test also covers the benching case. (I think all the seasoned rustaceans will be laughing at me, but this seems like a useful thing to note for anyone in a similar situation).
Attempt 2: The Nightly Test Crate
Since the previous method didn't seem fruitful, another popular option seemed to be to use the unstable test crate. This results in a project structure that looks like:
Cargo.toml
src/
lib.rs
bench.rs
Our original file is then revised to be:
// Include the unstable feature
#![feature(test)]
pub fn fast(x: T) -> U { ... }
#[cfg(any(test, debug_assertions))]
pub fn slow(x: T) -> U { ... }
#[cfg(test)]
mod bench;
And then bench.rs should look something like:
extern crate test;
use test::Bencher;
#[bench]
fn bench_it(b: &mut Bencher) {
b.iter(|| {}) // gotta go fast
}
This seemed to do everything I wanted upon running cargo +nightly bench. However, it is also super desirable for the project to be compilable outside of testing without the use of nightly or extra feature flags. That is, I still want to be able to run cargo build and cargo test and not get yelled at for requesting unstable features on a stable channel.
Attempt 2.5: Enter Build Scripts
(Once again, each of the parts is well-documented in other questions, I'm just collecting everything here for fun). Using a bunch of other posts, I learned that we can check for nightly and conditionally enable features by way of a build script. Our project now looks like this:
Cargo.toml
build.rs
src/
lib.rs
bench.rs
And we need to add rustc_version to our [build-dependencies] in Cargo.toml. We then add the following build script:
use rustc_version::{version_meta, Channel};
fn main() {
// Set feature flags based on the detected compiler version
match version_meta().unwrap().channel {
Channel::Stable => {
println!("cargo:rustc-cfg=RUSTC_IS_STABLE");
}
Channel::Beta => {
println!("cargo:rustc-cfg=RUSTC_IS_BETA");
}
Channel::Nightly => {
println!("cargo:rustc-cfg=RUSTC_IS_NIGHTLY");
}
Channel::Dev => {
println!("cargo:rustc-cfg=RUSTC_IS_DEV");
}
}
}
Finally, if we update lib.rs to be the following:
// Include the unstable feature
#![cfg_attr(RUSTC_IS_NIGHTLY, feature(test))] // <-- Note the change here!
pub fn fast(x: T) -> U { ... }
#[cfg(any(test, debug_assertions))]
pub fn slow(x: T) -> U { ... }
#[cfg(all(RUSTC_IS_NIGHTLY, test))] // <-- Note the change here!
mod bench;
I think we get everything we want.
So... thanks for joining me on this adventure. Would appreciate commentary on whether or not this was the right approach. Also, you might ask "why keep the benchmark around once we know it's slower?" I suppose this might be fair, but perhaps the test could be changed or I'd like to prove the new implementation is faster to a third party that won't just trust me.

Does Rust have hooks for early return on errors?

panic! allows the setting of a custom (albeit global) hook. Is there anything comparable for early returns with the ? operator? I have a function that needs to close some resources in a special way before exiting. I could write a function ok_or_close() that closes the resources before returning the error:
fn opens_resources() -> Result<(), MyError> {
//Opens some stuff.
//Now a bunch of functions that might raise errors.
ok_or_close(foo(), local variables)?;
ok_or_close(bar(), local variables)?;
ok_or_close(baz(), local variables)?;
ok_or_close(Ok(()), local variables)
}
But that seems verbose. What I'd really like to do is this:
fn opens_resources() -> Result<(), MyError> {
//Opens some stuff.
//Now a bunch of functions that might raise errors.
foo()?;
bar()?;
baz()?;
on_err:
//Closes some stuff. Would prefer not to make
// this a function, uses many local variables.
Ok(())
}
Is there a way to do this or a pattern of programming that gets around this?
The closest thing to this would be the Try trait which allows you to implement how ? affect a specific type, but sadly it is still a nightly experiment as stated here
If you're interested in this features I'd recommend you give a +1 at this issue

Alternative to the try (?) operator suited to iterator mapping

In the process of learning Rust, I am getting acquainted with error propagation and the choice between unwrap and the ? operator. After writing some prototype code that only uses unwrap(), I would like to remove unwrap from reusable parts, where panicking on every error is inappropriate.
How would one avoid the use of unwrap in a closure, like in this example?
// todo is VecDeque<PathBuf>
let dir = fs::read_dir(&filename).unwrap();
todo.extend(dir.map(|dirent| dirent.unwrap().path()));
The first unwrap can be easily changed to ?, as long as the containing function returns Result<(), io::Error> or similar. However, the second unwrap, the one in dirent.unwrap().path(), cannot be changed to dirent?.path() because the closure must return a PathBuf, not a Result<PathBuf, io::Error>.
One option is to change extend to an explicit loop:
let dir = fs::read_dir(&filename)?;
for dirent in dir {
todo.push_back(dirent?.path());
}
But that feels wrong - the original extend was elegant and clearly reflected the intention of the code. (It might also have been more efficient than a sequence of push_backs.) How would an experienced Rust developer express error checking in such code?
How would one avoid the use of unwrap in a closure, like in this example?
Well, it really depends on what you wish to do upon failure.
should failure be reported to the user or be silent
if reported, should one failure be reported or all?
if a failure occur, should it interrupt processing?
For example, you could perfectly decide to silently ignore all failures and just skip the entries that fail. In this case, the Iterator::filter_map combined with Result::ok is exactly what you are asking for.
let dir = fs::read_dir(&filename)?;
let todos.extend(dir.filter_map(Result::ok));
The Iterator interface is full of goodies, it's definitely worth perusing when looking for tidier code.
Here is a solution based on filter_map suggested by Matthieu. It calls Result::map_err to ensure the error is "caught" and logged, sending it further to Result::ok and filter_map to remove it from iteration:
fn log_error(e: io::Error) {
eprintln!("{}", e);
}
(|| {
let dir = fs::read_dir(&filename)?;
todo.extend(dir
.filter_map(|res| res.map_err(log_error).ok()))
.map(|dirent| dirent.path()));
})().unwrap_or_else(log_error)

Am I forced to create my own Error type?

I want to write a get_members function that returns members from a GitHub team.
pub fn get_members(group_id: &str) -> Result<Vec<User>, Error> {
let client = Client::new();
let query = format!("https://api.github.com/teams/{}/members?access_token={}",
group_id,
config::get_env(config::ENV_TOKEN));
println!("{}", query);
let mut res = try!(client
.get(&query)
.header(UserAgent("my/app".to_owned()))
.send());
let mut body = String::new();
try!(res.read_to_string(&mut body));
try!(json::decode(&body));
}
There are two different types of errors into play. One is hyper::error::Error and the other is rustc_serialize::json::DecoderError.
I thought I could just use implement From<::hyper::error::Error> for Error and From<rustc_serialize::json::DecoderError>. But since neither io::Error nor one of the other two errors is in my crate I'm not allowed to follow that approach.
I wonder what's the way to go here. Do I need to come up with my own AppError Type and then implement the From<> trait for that? Is that the way to go?
Usually yes, using your own error type is the way to go. There are even several crates (of which I was able to find only this one now) which help you to remove the boilerplate. This approach should also be used when you're writing a library, as opposed to an application.
There is an option, however, of using Box<Error> trait object as your error type. Lots of error types in Rust and in third-party libraries implement this trait; therefore, using Result<..., Box<Error>> as a return type should work almost always.

Reuse the description of an existing Error when creating a new Error

I have the following code in Rust, which does not compile, but shows the intent of what I'd like to do.
pub fn parse(cursor: &mut io::Cursor<&[u8]>) -> io::Result<Ack> {
use self::byteorder::{BigEndian, ReadBytesExt};
use self::core::error::Error;
match cursor.read_u16::<BigEndian>() {
Err(byteorder::Error::Io(error)) => Err(error),
Err(error) =>
Err(io::Error::new(io::ErrorKind::Other, error.description(),
None)),
Ok(value) => Ok(Ack { block_number: value })
}
}
Essentially, I want to take the error description of an error returned by the byteorder library and use it to create the description of an error I'll pass back to the user of my library. This fails with packets.rs:166:58: 166:63 error:errordoes not live long enough, and I understand why.
The byteorder library solves this issue by wrapping an std::io::Result in the byteorder::Error::Io constructor. However, I don't want to take this route because I'd have to define my own error type that wraps either an std::io::Error or a byteorder::Error. It seems to me that my users shouldn't know or care that I use the byteorder library, and it shouldn't be part of my interface.
I'm a Rust newbie and don't yet know the idioms and best practices of the language and design. What are my options for dealing with this?
Your problem is in fact in that io::Error::new()'s second parameter is &'static str, while byteorder::Error::description() returns a &'a str where 'a is lifetime of the error object itself which is less than 'static. Hence you can't use it for io::Error's description.
The simplest fix would be moving byteorder::Error description to detail field of io::Error:
Err(error) =>
Err(io::Error::new(
io::ErrorKind::Other,
"byteorder error",
Some(error.description().to_string())
)),
However, you should seriously consider making a custom wrapper error type which encapsulates all "downstream" errors. With properly written FromError instances you should be able to write something like
try!(cursor.read_u16::<BigEndian>()
.map(|value| Ack { block_number: value }))
instead of your whole match. Custom error wrappers will also help you when your program grows and more "downstream" error sources appear - you could just add new enum variants and/or FromError implementations to support these new errors.
I cannot test your code so I can't be sure. Isn't the ref keyword enough?
Err(byteorder::Error::Io(ref error)) => Err(error),