blob: 2c726974ed1ca15274a4060a73783a3f1a510fbf [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#[macro_use]
extern crate clap;
extern crate git2;
extern crate rayon;
extern crate reqwest;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate tempdir;
use git2::Repository;
use rayon::prelude::*;
use std::collections::HashSet;
use std::fs;
use std::io::Read;
use std::iter::FromIterator;
use std::process::Command;
use tempdir::TempDir;
// If unspecified, test this many crates
const DEFAULT_NUM: usize = 5;
#[derive(Debug, Deserialize)]
struct CrateInfo {
id: String,
name: String,
downloads: u64,
max_version: String,
description: String,
homepage: Option<String>,
repository: String,
}
#[derive(Debug, Deserialize)]
struct Crates {
crates: Vec<CrateInfo>,
}
#[derive(Debug, Eq, PartialEq)]
enum TestResult {
Success,
Failure,
Excluded,
}
#[derive(Debug)]
struct CrateResult {
id: String,
res: TestResult,
}
fn main() {
let matches = clap_app!(cratest =>
(version: "1.0")
(author: "Tim Kilbourn <tkilbourn@google.com>")
(about: "Tests the top crates from crates.io on Fuchsia")
(@arg num: -n +takes_value "Number of crates to test")
(@arg excludes: -x --exclude ... +takes_value
"Exclude crates whose name exactly match")
(@arg start: --start "Starts a Fuchsia emulator")
(@arg restart: --restart "Stop all Fuchsia emulators and start a new one")
(@arg keep: --keep "Keeps the temp dir after exiting")
(@arg verbose: -v --verbose "Print verbose output while performing commands")
).get_matches();
let num = value_t!(matches, "num", usize).unwrap_or(DEFAULT_NUM);
let verbose = matches.is_present("verbose");
let restart_emu = matches.is_present("restart");
let start_emu = matches.is_present("start");
let keep_tmp = matches.is_present("keep");
let excludes = HashSet::<String>::from_iter(
values_t!(matches, "excludes", String).unwrap_or_else(|_| Vec::new()),
);
println!(
"Running cratest on the top {} crates from crates.io...",
num
);
let crate_uri: String = [
"https://crates.io/api/v1/crates?page=1&per_page=",
&format!("{}", num),
"&sort=downloads",
].join("")
.into();
if verbose {
println!("Downloading crates from {}", crate_uri);
if !excludes.is_empty() {
println!("Excluding {} crates", excludes.len());
}
}
let mut resp = reqwest::get(&crate_uri).unwrap();
assert!(resp.status().is_success());
let mut content = String::new();
resp.read_to_string(&mut content).unwrap();
let res: Crates = serde_json::from_str(&content).unwrap();
if restart_emu {
Command::new("fargo")
.arg("restart")
.status()
.expect("failed to run fargo restart");
} else if start_emu {
Command::new("fargo")
.arg("start")
.status()
.expect("failed to run fargo start");
}
let tmpdir = TempDir::new("cratest").unwrap();
let results: Vec<CrateResult> = res
.crates
.par_iter()
.map(|cr| {
if excludes.contains(&cr.id) {
if verbose {
println!("Skipping {} (excluded)", &cr.id);
}
return CrateResult {
id: cr.id.clone(),
res: TestResult::Excluded,
};
}
let crdir = tmpdir.path().join(&cr.id);
fs::create_dir(&crdir).unwrap();
Repository::clone(&cr.repository, &crdir).unwrap();
let output = Command::new("fargo")
.arg("test")
.current_dir(&crdir)
.output()
.expect("failed to execute fargo test");
println!("crate: {}", &cr.id);
println!("status: {}", output.status);
if verbose {
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
}
CrateResult {
id: cr.id.clone(),
res: if output.status.success() {
TestResult::Success
} else {
TestResult::Failure
},
}
})
.collect();
let (succ, fail, excl) = results.into_iter().fold(
(Vec::new(), Vec::new(), Vec::new()),
|(mut s, mut f, mut e), r| {
match r.res {
TestResult::Success => s.push(r.id),
TestResult::Failure => f.push(r.id),
TestResult::Excluded => e.push(r.id),
}
(s, f, e)
},
);
for &(hdr, ref results) in &[("Successes", succ), ("Failures", fail), ("Excluded", excl)] {
if !results.is_empty() {
println!("{}({}): {:?}", hdr, results.len(), results);
}
}
if keep_tmp {
let tmppath = tmpdir.into_path();
println!("Temp output left at {}", tmppath.to_string_lossy());
}
}