1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! docmatic:
//!
//! `docmatic` runs `rustdoc` on your documentation files.
//!
//! ## Writing code blocks
//!
//! See ["Documentation tests"](https://doc.rust-lang.org/beta/rustdoc/documentation-tests.html)
//! for how to customize your code blocks being run as tests.
//!
//! ## Example
//!
//! First, add this to your `Cargo.toml`:
//!
//! ```toml
//! [dev-dependencies]
//! docmatic = "0.1"
//! ```
//!
//! Next, in your test file:
//!
//! ```rust
//! extern crate docmatic;
//!
//! fn test_readme() {
//!     docmatic::assert_file("README.md");
//! }
//! ```

extern crate which;

use std::path;
use std::ffi::OsStr;

/// A specialized process builder managing a `rustdoc` test session.
///
/// # Example
///
/// The following code will test the crate README with the `docmatic`
/// configuration set and a default library path:
///
/// ```rust
/// extern crate docmatic;
///
/// use std::default::Default;
///
/// fn test_readme() {
///     docmatic::Assert::default()
///         .cfg("docmatic")
///         .test_file("README.md")
/// }
/// ```
pub struct Assert(std::process::Command);

impl Assert {
    /// Construct a new `Assert` with no flags set.
    ///
    /// Will likely fail if you don't provide at least one library path
    /// containing the tested crate. Instead, you should probably use
    /// [`Assert::default`]
    ///
    /// [`Assert::default`]: #tymethod.default
    pub fn new() -> Self {
        let executable = which::which("rustdoc").expect("rustdoc not found");
        Assert(std::process::Command::new(executable))
    }

    /// Add a path to the library paths passed to `rustdoc`.
    pub fn library_path<S>(&mut self, path: S) -> &mut Self
    where
        S: AsRef<OsStr>,
    {
        self.0.arg("--library-path").arg(path);
        self
    }

    /// Add a *cfg* to the configuration passed to `rustdoc`.
    pub fn cfg<S>(&mut self, cfg: S) -> &mut Self
    where
        S: AsRef<OsStr>,
    {
        self.0.arg("--cfg").arg(cfg);
        self
    }

    /// Test the given file, and panics on failure.
    pub fn test_file<P>(&mut self, path: P)
    where
        P: AsRef<path::Path>,
    {
        let process = self.0.arg("--test").arg(path.as_ref()).spawn();

        let result = process
            .expect("rustdoc is runnable")
            .wait()
            .expect("rustdoc can run");

        assert!(
            result.success(),
            format!("Failed to run rustdoc tests on '{:?}'", path.as_ref())
        );
    }
}

impl Default for Assert {
    /// Create an `Assert` instance with the following default parameters:
    ///
    /// * `--library-path` set to the current *deps* directory (`target/debug/deps` or
    ///   `target/release/deps` depending on the test compilation mode).
    ///
    fn default() -> Self {
        let mut assert = Self::new();
        let current_exe = std::env::current_exe()
            .and_then(|p| p.canonicalize())
            .expect("could not get path to test executable");
        assert.library_path(current_exe.parent().expect("parent exists"));
        assert
    }
}

/// Test a single file with default parameters.
pub fn assert_file<P>(documentation: P)
where
    P: AsRef<path::Path>,
{
    Assert::default().test_file(documentation);
}