blob: df5222d46faf0d1ecfc045523ccabe30f808ec46 [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.
extern crate clap;
extern crate git2;
extern crate rayon;
extern crate reqwest;
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 {
struct CrateResult {
id: String,
res: TestResult,
fn main() {
let matches = clap_app!(cratest =>
(version: "1.0")
(author: "Tim Kilbourn <>")
(about: "Tests the top crates from 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")
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()),
"Running cratest on the top {} crates from",
let crate_uri: String = [
&format!("{}", num),
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();
let mut content = String::new();
resp.read_to_string(&mut content).unwrap();
let res: Crates = serde_json::from_str(&content).unwrap();
if restart_emu {
.expect("failed to run fargo restart");
} else if start_emu {
.expect("failed to run fargo start");
let tmpdir = TempDir::new("cratest").unwrap();
let results: Vec<CrateResult> = res.crates
.map(|cr| {
if excludes.contains(& {
if verbose {
println!("Skipping {} (excluded)", &;
return CrateResult {
res: TestResult::Excluded,
let crdir = tmpdir.path().join(&;
Repository::clone(&cr.repository, &crdir).unwrap();
let output = Command::new("fargo")
.expect("failed to execute fargo test");
println!("crate: {}", &;
println!("status: {}", output.status);
if verbose {
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
CrateResult {
res: if output.status.success() {
} else {
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(,
TestResult::Failure => f.push(,
TestResult::Excluded => e.push(,
(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());