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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//! Batch Run
//! =========
//! 
//! [![Latest Version](https://img.shields.io/crates/v/batch_run.svg)](https://crates.io/crates/batch_run)
//! [![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/batch_run)
//! 
//! `batch_run` is a runner for a set of Rust source files, based on [dtolnay's `trybuild`](https://github.com/dtolnay/trybuild).
//! It can be useful when you have a bunch of Rust sources which are not complex enough to be
//! packed into dedicated crates, but which are (by their meaning) not just integration test cases.
//! It also checks for output correctness, either on compile-time (for `compile_fail` cases)
//! or at runtime (for `run_pass` cases).
//! 
//! ```toml
//! [dependencies]
//! batch_run = "1.0"
//! ```
//! 
//! *Compiler support: requires rustc 1.31+*
//! 
//! <br>
//! 
//! ## Compile-fail cases
//! 
//! A minimal batch_run setup looks like this:
//! 
//! ```rust
//! fn main() {
//!     let b = batch_run::Batch::new();
//!     b.compile_fail("batches/ui/*.rs");
//!     match b.run() {
//!         Ok(()) => {},
//!         Err(err) => println!("{:?}", err)
//!     };
//! }
//! ```
//! 
//! This program will individually compile each of the
//! source files matching the glob pattern, expect them to fail to compile, and
//! assert that the compiler's error message matches an adjacently named _*.stderr_
//! file containing the expected output (same file name as the test except with a
//! different extension). If it doesn't match, the program will print the error message
//! with expected vs actual compiler output.
//! 
//! Dependencies listed under `[dependencies]` in the project's Cargo.toml are
//! accessible from within the batch.
//! 
//! A compile\_fail case that fails to fail to compile is also a failure.
//! 
//! <br>
//! 
//! ## Run-pass cases
//! 
//! In the run_pass cases, we not only check that the code compiles, but also actually run it
//! and match the stdout/stderr output with the corresponding _*.stdout_/_*.stderr_ files.
//! 
//! You can mix compile_fail and run_pass cases in one batch:
//! 
//! ```rust
//! fn main() {
//!     let t = batch_run::Batch::new();
//!     t.run_pass("batches/01-parse-header.rs");
//!     t.run_pass("batches/02-parse-body.rs");
//!     t.compile_fail("batches/03-expand-four-errors.rs");
//!     t.run_pass("batches/04-paste-ident.rs");
//!     t.run_pass("batches/05-repeat-section.rs");
//! }
//! ```
//! 
//! <br>
//! 
//! ## Details
//! 
//! That's the entire API for now.
//! 
//! <br>
//! 
//! ## Workflow
//! 
//! (TODO)
//! 


#[macro_use]
mod term;

mod binary;
mod cargo_rustc;
mod dependencies;
mod env;
mod error;
mod features;
mod message;
mod normalize;
mod run;
mod rustflags;

use std::cell::RefCell;
use std::path::{Path, PathBuf};
// use std::thread;

#[derive(Debug)]
pub struct Batch {
    runner: RefCell<Runner>,
}

#[derive(Debug)]
struct Runner {
    entries: Vec<Entry>,
}

#[derive(Clone, Debug)]
struct Entry {
    path: PathBuf,
    expected: Expected,
}

#[derive(Copy, Clone, Debug)]
enum Expected {
    RunPass,
    CompileFail,
}

impl Expected {
    pub fn is_run_pass(&self) -> bool {
        use Expected::*;
        match self {
            RunPass => true,
            CompileFail => false,
        }
    }
}

impl Batch {
    #[allow(clippy::new_without_default)]
    pub fn new() -> Self {
        Batch {
            runner: RefCell::new(Runner {
                entries: Vec::new(),
            }),
        }
    }

    pub fn run_pass<P: AsRef<Path>>(&self, path: P) {
        self.runner.borrow_mut().entries.push(Entry {
            path: path.as_ref().to_owned(),
            expected: Expected::RunPass,
        });
    }

    pub fn compile_fail<P: AsRef<Path>>(&self, path: P) {
        self.runner.borrow_mut().entries.push(Entry {
            path: path.as_ref().to_owned(),
            expected: Expected::CompileFail,
        });
    }

    // TODO error type
    pub fn run(self) -> Result<(), error::Error> {
        self.runner.borrow_mut().run()
    }
}

// #[doc(hidden)]
// impl Drop for Batch {
//     fn drop(&mut self) {
//         if !thread::panicking() {
//             self.runner
//                 .borrow_mut()
//                 .run()
//                 .unwrap_or_else(|err| println!("{}", err));
//         }
//     }
// }