Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve ci #221

Merged
merged 10 commits into from
May 22, 2019
2 changes: 1 addition & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ regress:nightly:
- rustup component add rustfmt-preview
- cd ci/svd2rust-regress
- rm -rf ./output
- cargo run --release -- --long-test --format --verbose
- cargo run --release -- --long-test --format --verbose --nightly
2 changes: 1 addition & 1 deletion ci/svd2rust-regress/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::path::PathBuf;
error_chain!{
errors {
ProcessFailed(command: String, stderr: Option<PathBuf>, stdout: Option<PathBuf>) {
ProcessFailed(command: String, stderr: Option<PathBuf>, stdout: Option<PathBuf>, previous_processes_stderr: Vec<PathBuf>) {
description("Process Failed")
display("Process Failed - {}", command)
}
Expand Down
123 changes: 98 additions & 25 deletions ci/svd2rust-regress/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,23 @@ struct Opt {
/// (which must be already built)
#[structopt(short = "p", long = "svd2rust-path", parse(from_os_str))]
bin_path: Option<PathBuf>,

// TODO: Consider using the same strategy cargo uses for passing args to rustc via `--`
/// Run svd2rust with `--nightly`
#[structopt(long = "nightly")]
nightly: bool,

/// Filter by chip name, case sensitive, may be combined with other filters
#[structopt(short = "c", long = "chip")]
chip: Option<String>,
#[structopt(short = "c", long = "chip", raw(validator = "validate_chips"))]
chip: Vec<String>,

/// Filter by manufacturer, case sensitive, may be combined with other filters
#[structopt(short = "m", long = "manufacturer")]
#[structopt(short = "m", long = "manufacturer", raw(validator = "validate_manufacturer"))]
mfgr: Option<String>,

/// Filter by architecture, case sensitive, may be combined with other filters
/// Options are: "CortexM", "RiscV", and "Msp430"
#[structopt(short = "a", long = "architecture")]
#[structopt(short = "a", long = "architecture", raw(validator = "validate_architecture"))]
arch: Option<String>,

/// Include tests expected to fail (will cause a non-zero return code)
Expand All @@ -54,19 +59,50 @@ struct Opt {
#[structopt(short = "f", long = "format")]
format: bool,

/// Print all available test using the specified filters
#[structopt(long = "list")]
list: bool,

/// Path to an `rustfmt` binary, relative or absolute.
/// Defaults to `$(rustup which rustfmt)`
#[structopt(long = "rustfmt_bin_path", parse(from_os_str))]
rustfmt_bin_path: Option<PathBuf>,

/// Specify what rustup toolchain to use when compiling chip(s)
#[structopt(long = "toolchain", env = "RUSTUP_TOOLCHAIN")]
rustup_toolchain: Option<String>,

/// Use verbose output
#[structopt(long = "verbose", short = "v", parse(from_occurrences))]
verbose: u8,
// TODO: Specify smaller subset of tests? Maybe with tags?
// TODO: Early fail
// TODO: Compile svd2rust?
}

fn validate_chips(s: String) -> Result<(), String>{
if tests::TESTS.iter().any(|t| t.chip == s) {
Ok(())
} else {
Err(format!("Chip `{}` is not a valid value", s))
}
}

fn validate_architecture(s: String) -> Result<(), String>{
if tests::TESTS.iter().any(|t| format!("{:?}", t.arch) == s) {
Ok(())
} else {
Err(format!("Architecture `{}` is not a valid value", s))
}
}

fn validate_manufacturer(s: String) -> Result<(), String>{
if tests::TESTS.iter().any(|t| format!("{:?}", t.mfgr) == s) {
Ok(())
} else {
Err(format!("Manufacturer `{}` is not a valid value", s))
}
}

/// Validate any assumptions made by this program
fn validate_tests(tests: &[&tests::TestCase]) {
use std::collections::HashSet;
Expand All @@ -88,6 +124,18 @@ fn validate_tests(tests: &[&tests::TestCase]) {
}
}

fn read_file(path: &PathBuf, buf: &mut String) {
if buf.is_empty() {
buf.push_str(&format!("{}\n", path.display()));
} else {
buf.push_str(&format!("\n{}\n", path.display()));
}
File::open(path)
.expect("Couldn't open file")
.read_to_string(buf)
.expect("Couldn't read file to string");
}

fn main() {
let opt = Opt::from_args();

Expand Down Expand Up @@ -138,6 +186,12 @@ fn main() {
default_rustfmt.as_ref()
}
};

// Set RUSTUP_TOOLCHAIN if needed
if let Some(toolchain) = &opt.rustup_toolchain {
::std::env::set_var("RUSTUP_TOOLCHAIN", toolchain);
}

// collect enabled tests
let tests = tests::TESTS
.iter()
Expand All @@ -161,8 +215,8 @@ fn main() {
})
// Specify chip - note: may match multiple
.filter(|t| {
if let Some(ref chip) = opt.chip {
chip == t.chip
if !opt.chip.is_empty() {
opt.chip.iter().any(|c| c == t.chip)
} else {
true
}
Expand All @@ -171,36 +225,55 @@ fn main() {
.filter(|t| opt.bad_tests || t.should_pass)
.collect::<Vec<_>>();

if opt.list {
// FIXME: Prettier output
eprintln!("{:?}", tests.iter().map(|t| t.name()).collect::<Vec<_>>());
exit(0);
}
if tests.is_empty() {
eprintln!("No tests run, you might want to use `--bad-tests` and/or `--long-test`");
}

let any_fails = AtomicBool::new(false);

// TODO: It would be more efficient to reuse directories, so we don't
// have to rebuild all the deps crates
tests.par_iter().for_each(|t| {
let start = Instant::now();

match svd_test::test(t, &bin_path, rustfmt_bin_path) {
Ok(()) => {
// TODO: If verbosity is > 1, print every logged stderr
eprintln!(
"Passed: {} - {} seconds",
t.name(),
start.elapsed().as_secs()
);
match svd_test::test(t, &bin_path, rustfmt_bin_path, opt.nightly, opt.verbose) {
Ok(s) => {
if let Some(stderrs) = s {
let mut buf = String::new();
for stderr in stderrs {
read_file(&stderr, &mut buf);
}
eprintln!(
"Passed: {} - {} seconds\n{}",
t.name(),
start.elapsed().as_secs(),
buf
);
} else {
eprintln!(
"Passed: {} - {} seconds",
t.name(),
start.elapsed().as_secs()
);
}
}
Err(e) => {
any_fails.store(true, Ordering::Release);
let additional_info = if opt.verbose > 0 {
match e.kind() {
&errors::ErrorKind::ProcessFailed(ref command, _, Some(ref stderr))
if command == "cargo check" =>
{
let mut buf = String::new();
// Unwrap is safe
File::open(stderr)
.expect("Couldn't open file")
.read_to_string(&mut buf)
.expect("Couldn't read file to string");
buf.insert_str(0, &format!("\n---{:?}---\n", stderr.as_os_str()));
&errors::ErrorKind::ProcessFailed(_, _, Some(ref stderr), ref previous_processes_stderr) => {
let mut buf = String::new();
if opt.verbose > 1 {
for stderr in previous_processes_stderr {
read_file(&stderr, &mut buf);
}
}
read_file(&stderr, &mut buf);
buf
}
_ => "".into(),
Expand Down
Loading