Rust: Move from binary to library and Add Documentation Examples that are tested.
Tags: rust, crate, test, documentation
Published: 2021-05-11 05:32:35


In the previous article in this series. I went through some basics of documenting your rust code. I had hoped to be able to take advantage of another nice feature of rust. That being the fact that code examples are actually tested by rust. I will go through the changes I had to make for this to work.

## TL;DR:
1. Document testing only works if you are writing a crate. This means it works against a `lib.rs` file and its related modules.
2. The documentation is tested when you either run `cargo test` or `cargo test --doc` if you only want to test the documentation examples.

### Example:
```zsh

   Doc-tests decon-spf

running 3 tests
test src/spf/mod.rs - spf::Spf::new (line 32) ... ok
test src/spf/mechanism.rs - spf::mechanism::SpfMechanism<IpNetwork>::as_string (line 166) ... ok
test src/spf/mechanism.rs - spf::mechanism::SpfMechanism<IpNetwork>::as_mechanism (line 145) ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.85s
```

## How we get the results above
Initially when I started trying to test my documentation I found that no matter what I did. The tests did not appear to be getting run.  
It turns out **binary** projects are really not expected to have documentation with testable examples, makes sense really.  
So the answer is to have your modules as **libraries**. In my case I went for the fastest approach, which is fairly straight forward. I converted my project into a mixed **library** and **binary**

I will give the main details. But if you really want to look at the gory details, take a look at [this diff](https://github.com/Bas-Man/learning-rust-trust-dns-resolver/compare/Doc-Example...Doc-Example2)   

There were a large number of changes. I took this opportunity to rename my project and to move the modules around as I suspect **mx** and **soa** will not be used.

## The Steps

### Update Cargo.toml
This step is not really needed. But I wanted to rename the project more in line with what it was doing. So I changed the `name` from `trust-dns` to `decon-spf` which is short for **"deconstruct spf"**.

### Create a lib.rs file
Next I created a lib.rs file under `src`.
This file eventually became simply
```rust
pub mod spf;
```

### Update main.rs
Here I removed the `mod dns` and `use crate::dns::...` and replaced them with a single line of `use decon_spf::spf::Spf;`  
When you are creating a library in this way, you no longer make use of the `use crate::` within the source of the binary application. You actually call the crate by name. You still make use of the `use crate::` within the module code itself though. So be aware of that.

### Move spf up a directory level in line with dns
I moved the `spf` directory up one level along side `dns` given the new directory structure you see here.
```zsh
src
├── dns
│   ├── mod.rs
│   ├── mx.rs
│   └── soa.rs
├── lib.rs
├── main.rs
└── spf
    ├── kinds.rs
    ├── mechanism.rs
    ├── mod.rs
    └── tests
        ├── mod.rs
        ├── spf_a_mechanism_test.rs
        ├── spf_mx_mechanism_test.rs
        └── spf_test.rs
```
### Update dns/mod.rs
I removed the reference for for `pub mod spf` from within `dns/mod.rs`

### Update all .rs files below the spf directory
Within all these files we replace any reference to `use crate::dns:spf::...` with `use crate::spf::...`.  
As **spf** no longer lives below **dns**.

## Adding some documentation examples which are tested.

### Spf::new()
I added my first example in `spf/mod.rs`
**Note: I am using single quote marks in the following examples. Dev.to does not seem to allow me to escape backpacks. Resulting in incorrectly formatted contents.**
~~~rust
impl Spf {
    /// Create a new Spf with the provided `str`
    ///
    /// # Example
    ///
    /// ```
    /// use decon_spf::spf::Spf;
    /// let source_str = "v=spf1 redirect=_spf.example.com";
    /// let spf = Spf::new(&source_str.to_string());
    /// ```
    ///
    pub fn new(str: &String) -> Self {
~~~

Anything between three backticks is assumed to be rust code, unless a type other than **rust** is given.  
For example:

```
'''
I am rust.
'''
```

```
'''rust
I am also rust.
'''
```

```
'''text
 I am not rust.
'''
```

If you look in the result section below you will see that the example code was tested
```zsh
test src/spf/mod.rs - spf::Spf::new (line 32) ... ok
```
This tells us that the code in the file **mod.rs** with the three backticks starting on line 32 was tested and passed.

### SpfMechanism<IpNetwork>::as_mechanism() 
Next I updated `spf/mechanism.rs`.  

~~~rust
/// Returns the mechanism string representation of an IP4/6 mechanism.
/// # Example
///
/// ```
/// use ipnetwork::IpNetwork;
/// use decon_spf::spf::mechanism::SpfMechanism;
/// let ip: IpNetwork = "192.168.11.0/24".parse().unwrap();
/// let ip_mechanism = SpfMechanism::new_ip4('+', ip);
/// assert_eq!(ip_mechanism.as_mechanism(), "ip4:192.168.11.0/24");
/// ```
///
pub fn as_mechanism(&self) -> String {
    let mut txt = String::new();
    if self.qualifier != '+' {
        txt.push(self.qualifier);
    };
    txt.push_str(self.mechanism_prefix_from_kind().as_str());
    txt.push_str(self.mechanism.to_string().as_str());
    txt
}
~~~

## The Result

With these changes I was able to achieve my main goal, of having my documentation examples tested. But I also moved forward in making **spf** more of an independent crate that could, with an extreme amount of more work, be made available as a crate for others to use.  

Testing this now becomes a matter of running `cargo test` which results in the following output.

```zsh
Finished test [unoptimized + debuginfo] target(s) in 0.34s
  Running target/debug/deps/decon_spf-66ff691605dc6b68

running 39 tests
test spf::mechanism::SpfMechanismIpNetwork::test_ip4_neutral ... ok
test spf::mechanism::SpfMechanismIpNetwork::test_ip6_fail ... ok
test spf::mechanism::SpfMechanismIpNetwork::test_ip4_fail ... ok
--snip--
test spf::tests::spf_test::test_spf::test_netblocks2_google_com ... ok
test spf::tests::spf_test::test_spf::test_hotmail ... ok

test result: ok. 39 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s

     Running target/debug/deps/decon_spf-6020a1334ed1a1df

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests decon-spf

running 3 tests
test src/spf/mod.rs - spf::Spf::new (line 32) ... ok
test src/spf/mechanism.rs - spf::mechanism::SpfMechanism<IpNetwork>::as_mechanism (line 145) ... ok
test src/spf/mechanism.rs - spf::mechanism::SpfMechanism<IpNetwork>::as_string (line 166) ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.60s
```

Or `cargo test --doc` which results in the shorter output as it only tests the documentation.
```zsh
Finished test [unoptimized + debuginfo] target(s) in 0.25s
Doc-tests decon-spf

running 3 tests
test src/spf/mod.rs - spf::Spf::new (line 32) ... ok
test src/spf/mechanism.rs - spf::mechanism::SpfMechanism<IpNetwork>::as_string (line 166) ... ok
test src/spf/mechanism.rs - spf::mechanism::SpfMechanism<IpNetwork>::as_mechanism (line 145) ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.54s
``` 

That pretty much wraps things up I think for this article.