| // 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()); |
| } |
| } |