<commit_before>#[macro_use] extern crate log;
extern crate env_logger;
extern crate argparse;
extern crate quire;
extern crate lithos;
extern crate time;
extern crate scan_dir;
extern crate rustc_serialize;

use std::env;
use std::io::{BufReader, BufRead};
use std::fs::{File, remove_dir_all};
use std::path::{PathBuf, Path};
use std::process::exit;
use std::collections::HashSet;
use std::collections::BTreeMap;
use std::collections::VecDeque;

use time::{Tm, Duration, now_utc};
use quire::parse_config;
use argparse::{ArgumentParser, Parse, ParseOption, StoreTrue, StoreConst};
use argparse::{Print};
use rustc_serialize::json;

use lithos::master_config::MasterConfig;
use lithos::sandbox_config::SandboxConfig;
use lithos::child_config::ChildConfig;

#[derive(Clone, Copy, Debug)]
enum Action {
    Used,
    Unused,
    DeleteUnused,
}

fn main() {

    if env::var("RUST_LOG").is_err() {
        env::set_var("RUST_LOG", "warn");
    }
    env_logger::init().unwrap();
    let mut config_file = PathBuf::from("/etc/lithos/master.yaml");
    let mut verbose = false;
    let mut ver_min = 0;
    let mut ver_max = 1000;
    let mut action = Action::Used;
    let mut days = None::<u32>;
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("Show used/unused images and clean if needed");
        ap.refer(&mut config_file)
          .add_option(&["-C", "--config"], Parse,
            "Name of the global configuration file \
             (default /etc/lithos/master.yaml)")
          .metavar("FILE");
        ap.refer(&mut days)
          .add_option(&["-D", "--history-days"], ParseOption,
            r"Keep images that used no more than DAYS ago.
              There is no reasonable default, so you should specify this
              argument or --versions-min to get sane behavior.")
          .metavar("DAYS");
        ap.refer(&mut ver_min)
          .add_option(&["--vmin", "--versions-min"], Parse,
            r"Keep minimum NUM versions (even if they are older than DAYS).
              Default is 0 which means keep only current version.")
          .metavar("NUM");
        ap.refer(&mut ver_max)
          .add_option(&["--vmax", "--versions-max"], Parse,
            r"Keep maximum NUM versions
              (even if need to delete images more recent than DAYS).
              Default is 1000.")
          .metavar("NUM");
        ap.refer(&mut verbose)
          .add_option(&["-v", "--verbose"], StoreTrue,
            "Verbose output");
        ap.refer(&mut action)
          .add_option(&["--used"], StoreConst(Action::Used),
            "Show used images")
          .add_option(&["--unused"], StoreConst(Action::Unused),
            "Show unused images")
          .add_option(&["--delete-unused"], StoreConst(Action::DeleteUnused),
            "Delete unused images");
        ap.add_option(&["--version"],
            Print(env!("CARGO_PKG_VERSION").to_string()),
            "Show version of the lithos");
        ap.parse_args_or_exit();
    }
    let master: MasterConfig = match parse_config(&config_file,
        &MasterConfig::validator(), Default::default()) {
        Ok(cfg) => cfg,
        Err(e) => {
            error!("Can't parse config: {}", e);
            exit(1);
        }
    };
    let tm = days.map(|days| now_utc() - Duration::days(days as i64));
    let (used, dirs) = match find_used_images(&master, &config_file,
        tm, ver_min, ver_max)
    {
        Ok((used, dirs)) => (used, dirs),
        Err(e) => {
            error!("Error finding out used images: {}", e);
            exit(1);
        }
    };
    match action {
        Action::Used => {
            for i in used {
                println!("{:?}", i);
            }
        }
        Action::Unused => {
            let unused = find_unused(&used, &dirs)
                .map_err(|e| {
                    error!("Error finding unused images: {:?}", e);
                    exit(2);
                })
                .unwrap();
            for i in unused {
                println!("{:?}", i);
            }
        }
        Action::DeleteUnused => {
            let unused = find_unused(&used, &dirs)
                .map_err(|e| {
                    error!("Error finding unused images: {:?}", e);
                    exit(2);
                })
                .unwrap();
            for i in unused {
                if verbose {
                    println!("Deleting {:?}", i);
                }
                remove_dir_all(&i)
                    .map_err(|e| error!("Error removing {:?}: {}", i, e)).ok();
            }
        }
    }
}

fn find_unused(used: &HashSet<PathBuf>, dirs: &HashSet<PathBuf>)
    -> Result<Vec<PathBuf>, scan_dir::Error>
{
    let mut unused = Vec::new();
    for dir in dirs.iter() {
        try!(scan_dir::ScanDir::dirs().skip_symlinks(true).read(dir, |iter| {
            for (entry, _) in iter {
                let path = entry.path().to_path_buf();
                if !used.contains(&path) {
                    unused.push(path);
                }
            }
        }));
    }
    Ok(unused)
}

fn find_used_images(master: &MasterConfig, master_file: &Path,
    min_time: Option<Tm>, ver_min: u32, ver_max: u32)
    -> Result<(HashSet<PathBuf>, HashSet<PathBuf>), String>
{
    let config_dir = master_file.parent().unwrap().join(&master.sandboxes_dir);
    let mut images = HashSet::new();
    let mut image_dirs = HashSet::new();
    let childval = ChildConfig::mapping_validator();
    try!(try!(scan_dir::ScanDir::files().read(&config_dir, |iter| {
        let yamls = iter.filter(|&(_, ref name)| name.ends_with(".yaml"));
        for (entry, sandbox_fname) in yamls {
            let sandbox_name = &sandbox_fname[..sandbox_fname.len()-5];  // strip .yaml
            let sandbox_config: SandboxConfig = try!(parse_config(&entry.path(),
                &SandboxConfig::validator(), Default::default()));
            image_dirs.insert(sandbox_config.image_dir.clone());

            let cfg = master_file.parent().unwrap()
                .join(&master.processes_dir)
                .join(sandbox_config.config_file.as_ref().unwrap_or(
                    &PathBuf::from(&(sandbox_name.to_string() + ".yaml"))));
            let all_children: BTreeMap<String, ChildConfig>;
            all_children = try!(parse_config(&cfg, &childval, Default::default())
                .map_err(|e| format!("Can't read child config {:?}: {}",
                                     sandbox_config.config_file, e)));
            for child in all_children.values() {
                // Current are always added
                images.insert(sandbox_config.image_dir.join(&child.image));
            }

            let logname = master.config_log_dir
                .join(format!("{}.log", sandbox_name));
            // TODO(tailhook) look in log rotations
            let log = try!(File::open(&logname)
                .map_err(|e| format!("Can't read log file {:?}: {}", logname, e)));
            let mut configs;
            configs = VecDeque::<(Tm, BTreeMap<String, ChildConfig>)>::new();
            for (line_no, line) in BufReader::new(log).lines().enumerate() {
                let line = try!(line
                    .map_err(|e| format!("Readline error: {}", e)));
                let mut iter = line.splitn(2, " ");
                let (tm, cfg) = match (iter.next(), iter.next()) {
                        (Some(""), None) => continue, // last line, probably
                        (Some(date), Some(config)) => {
                            // TODO(tailhook) remove or check sandbox name
                            (try!(time::strptime(date, "%Y-%m-%dT%H:%M:%SZ")
                                 .map_err(|_| format!("Bad time at {:?}:{}",
                                    logname, line_no))),
                             try!(json::decode(config)
                                 .map_err(|_| format!("Bad config at {:?}:{}",
                                    logname, line_no))),
                            )
                        }
                        _ => {
                            return Err(format!("Bad line at {:?}:{}",
                                               logname, line_no));
                        }
                    };
                if let Some(&(_, ref ocfg)) = configs.back(){
                    if *ocfg == cfg {
                        continue;
                    }
                }
                configs.push_back((tm, cfg));
                if configs.len() >= ver_max as usize {
                    configs.pop_front();
                }
            }
            min_time.map(|min_time| {
                while configs.len() > ver_min as usize {
                    if let Some(&(tm, _)) = configs.front() {
                        if tm > min_time {
                            break;
                        }
                    } else {
                        break;
                    }
                    configs.pop_front();
                }
            });
            for &(_, ref cfg) in configs.iter() {
                for child in cfg.values() {
                    // Current are always added
                    images.insert(sandbox_config.image_dir.join(&child.image));
                }
            }
        }
        Ok(())
    }).map_err(|e| format!("Read dir error: {}", e))));
    Ok((images, image_dirs))
}
<commit_msg>Skip missing image directories in lithos_clean<commit_after>#[macro_use] extern crate log;
extern crate env_logger;
extern crate argparse;
extern crate quire;
extern crate lithos;
extern crate time;
extern crate scan_dir;
extern crate rustc_serialize;

use std::env;
use std::io::{BufReader, BufRead};
use std::fs::{File, remove_dir_all};
use std::path::{PathBuf, Path};
use std::process::exit;
use std::collections::HashSet;
use std::collections::BTreeMap;
use std::collections::VecDeque;

use time::{Tm, Duration, now_utc};
use quire::parse_config;
use argparse::{ArgumentParser, Parse, ParseOption, StoreTrue, StoreConst};
use argparse::{Print};
use rustc_serialize::json;

use lithos::master_config::MasterConfig;
use lithos::sandbox_config::SandboxConfig;
use lithos::child_config::ChildConfig;

#[derive(Clone, Copy, Debug)]
enum Action {
    Used,
    Unused,
    DeleteUnused,
}

fn main() {

    if env::var("RUST_LOG").is_err() {
        env::set_var("RUST_LOG", "warn");
    }
    env_logger::init().unwrap();
    let mut config_file = PathBuf::from("/etc/lithos/master.yaml");
    let mut verbose = false;
    let mut ver_min = 0;
    let mut ver_max = 1000;
    let mut action = Action::Used;
    let mut days = None::<u32>;
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("Show used/unused images and clean if needed");
        ap.refer(&mut config_file)
          .add_option(&["-C", "--config"], Parse,
            "Name of the global configuration file \
             (default /etc/lithos/master.yaml)")
          .metavar("FILE");
        ap.refer(&mut days)
          .add_option(&["-D", "--history-days"], ParseOption,
            r"Keep images that used no more than DAYS ago.
              There is no reasonable default, so you should specify this
              argument or --versions-min to get sane behavior.")
          .metavar("DAYS");
        ap.refer(&mut ver_min)
          .add_option(&["--vmin", "--versions-min"], Parse,
            r"Keep minimum NUM versions (even if they are older than DAYS).
              Default is 0 which means keep only current version.")
          .metavar("NUM");
        ap.refer(&mut ver_max)
          .add_option(&["--vmax", "--versions-max"], Parse,
            r"Keep maximum NUM versions
              (even if need to delete images more recent than DAYS).
              Default is 1000.")
          .metavar("NUM");
        ap.refer(&mut verbose)
          .add_option(&["-v", "--verbose"], StoreTrue,
            "Verbose output");
        ap.refer(&mut action)
          .add_option(&["--used"], StoreConst(Action::Used),
            "Show used images")
          .add_option(&["--unused"], StoreConst(Action::Unused),
            "Show unused images")
          .add_option(&["--delete-unused"], StoreConst(Action::DeleteUnused),
            "Delete unused images");
        ap.add_option(&["--version"],
            Print(env!("CARGO_PKG_VERSION").to_string()),
            "Show version of the lithos");
        ap.parse_args_or_exit();
    }
    let master: MasterConfig = match parse_config(&config_file,
        &MasterConfig::validator(), Default::default()) {
        Ok(cfg) => cfg,
        Err(e) => {
            error!("Can't parse config: {}", e);
            exit(1);
        }
    };
    let tm = days.map(|days| now_utc() - Duration::days(days as i64));
    let (used, dirs) = match find_used_images(&master, &config_file,
        tm, ver_min, ver_max)
    {
        Ok((used, dirs)) => (used, dirs),
        Err(e) => {
            error!("Error finding out used images: {}", e);
            exit(1);
        }
    };
    match action {
        Action::Used => {
            for i in used {
                println!("{:?}", i);
            }
        }
        Action::Unused => {
            let unused = find_unused(&used, &dirs)
                .map_err(|e| {
                    error!("Error finding unused images: {:?}", e);
                    exit(2);
                })
                .unwrap();
            for i in unused {
                println!("{:?}", i);
            }
        }
        Action::DeleteUnused => {
            let unused = find_unused(&used, &dirs)
                .map_err(|e| {
                    error!("Error finding unused images: {:?}", e);
                    exit(2);
                })
                .unwrap();
            for i in unused {
                if verbose {
                    println!("Deleting {:?}", i);
                }
                remove_dir_all(&i)
                    .map_err(|e| error!("Error removing {:?}: {}", i, e)).ok();
            }
        }
    }
}

fn find_unused(used: &HashSet<PathBuf>, dirs: &HashSet<PathBuf>)
    -> Result<Vec<PathBuf>, scan_dir::Error>
{
    let mut unused = Vec::new();
    for dir in dirs.iter() {
        if !dir.exists() {
            warn!("Directory {:?} does not exists", dir);
            continue;
        }
        try!(scan_dir::ScanDir::dirs().skip_symlinks(true).read(dir, |iter| {
            for (entry, _) in iter {
                let path = entry.path().to_path_buf();
                if !used.contains(&path) {
                    unused.push(path);
                }
            }
        }));
    }
    Ok(unused)
}

fn find_used_images(master: &MasterConfig, master_file: &Path,
    min_time: Option<Tm>, ver_min: u32, ver_max: u32)
    -> Result<(HashSet<PathBuf>, HashSet<PathBuf>), String>
{
    let config_dir = master_file.parent().unwrap().join(&master.sandboxes_dir);
    let mut images = HashSet::new();
    let mut image_dirs = HashSet::new();
    let childval = ChildConfig::mapping_validator();
    try!(try!(scan_dir::ScanDir::files().read(&config_dir, |iter| {
        let yamls = iter.filter(|&(_, ref name)| name.ends_with(".yaml"));
        for (entry, sandbox_fname) in yamls {
            let sandbox_name = &sandbox_fname[..sandbox_fname.len()-5];  // strip .yaml
            let sandbox_config: SandboxConfig = try!(parse_config(&entry.path(),
                &SandboxConfig::validator(), Default::default()));
            image_dirs.insert(sandbox_config.image_dir.clone());

            let cfg = master_file.parent().unwrap()
                .join(&master.processes_dir)
                .join(sandbox_config.config_file.as_ref().unwrap_or(
                    &PathBuf::from(&(sandbox_name.to_string() + ".yaml"))));
            let all_children: BTreeMap<String, ChildConfig>;
            all_children = try!(parse_config(&cfg, &childval, Default::default())
                .map_err(|e| format!("Can't read child config {:?}: {}",
                                     sandbox_config.config_file, e)));
            for child in all_children.values() {
                // Current are always added
                images.insert(sandbox_config.image_dir.join(&child.image));
            }

            let logname = master.config_log_dir
                .join(format!("{}.log", sandbox_name));
            // TODO(tailhook) look in log rotations
            let log = try!(File::open(&logname)
                .map_err(|e| format!("Can't read log file {:?}: {}", logname, e)));
            let mut configs;
            configs = VecDeque::<(Tm, BTreeMap<String, ChildConfig>)>::new();
            for (line_no, line) in BufReader::new(log).lines().enumerate() {
                let line = try!(line
                    .map_err(|e| format!("Readline error: {}", e)));
                let mut iter = line.splitn(2, " ");
                let (tm, cfg) = match (iter.next(), iter.next()) {
                        (Some(""), None) => continue, // last line, probably
                        (Some(date), Some(config)) => {
                            // TODO(tailhook) remove or check sandbox name
                            (try!(time::strptime(date, "%Y-%m-%dT%H:%M:%SZ")
                                 .map_err(|_| format!("Bad time at {:?}:{}",
                                    logname, line_no))),
                             try!(json::decode(config)
                                 .map_err(|_| format!("Bad config at {:?}:{}",
                                    logname, line_no))),
                            )
                        }
                        _ => {
                            return Err(format!("Bad line at {:?}:{}",
                                               logname, line_no));
                        }
                    };
                if let Some(&(_, ref ocfg)) = configs.back(){
                    if *ocfg == cfg {
                        continue;
                    }
                }
                configs.push_back((tm, cfg));
                if configs.len() >= ver_max as usize {
                    configs.pop_front();
                }
            }
            min_time.map(|min_time| {
                while configs.len() > ver_min as usize {
                    if let Some(&(tm, _)) = configs.front() {
                        if tm > min_time {
                            break;
                        }
                    } else {
                        break;
                    }
                    configs.pop_front();
                }
            });
            for &(_, ref cfg) in configs.iter() {
                for child in cfg.values() {
                    // Current are always added
                    images.insert(sandbox_config.image_dir.join(&child.image));
                }
            }
        }
        Ok(())
    }).map_err(|e| format!("Read dir error: {}", e))));
    Ok((images, image_dirs))
}
<|endoftext|>


<commit_before>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::fs::File;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::{fs, env};
use std::time::{SystemTime, UNIX_EPOCH};

/// A helper macro to `unwrap` a result except also print out details like:
///
/// * The file/line of the panic
/// * The expression that failed
/// * The error itself
///
/// This is currently used judiciously throughout the build system rather than
/// using a `Result` with `try!`, but this may change one day...
#[macro_export]
macro_rules! t {
    ($e:expr) => (match $e {
        Ok(e) => e,
        Err(e) => panic!("{} failed with {}", stringify!($e), e),
    })
}

pub fn run(cmd: &mut Command) {
    println!("running: {:?}", cmd);
    run_silent(cmd);
}

pub fn run_silent(cmd: &mut Command) {
    if !try_run_silent(cmd) {
        std::process::exit(1);
    }
}

pub fn try_run_silent(cmd: &mut Command) -> bool {
    let status = match cmd.status() {
        Ok(status) => status,
        Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}",
                                cmd, e)),
    };
    if !status.success() {
        println!("\n\ncommand did not execute successfully: {:?}\n\
                  expected success, got: {}\n\n",
                 cmd,
                 status);
    }
    status.success()
}

pub fn run_suppressed(cmd: &mut Command) {
    if !try_run_suppressed(cmd) {
        std::process::exit(1);
    }
}

pub fn try_run_suppressed(cmd: &mut Command) -> bool {
    let output = match cmd.output() {
        Ok(status) => status,
        Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}",
                                cmd, e)),
    };
    if !output.status.success() {
        println!("\n\ncommand did not execute successfully: {:?}\n\
                  expected success, got: {}\n\n\
                  stdout ----\n{}\n\
                  stderr ----\n{}\n\n",
                 cmd,
                 output.status,
                 String::from_utf8_lossy(&output.stdout),
                 String::from_utf8_lossy(&output.stderr));
    }
    output.status.success()
}

pub fn gnu_target(target: &str) -> String {
    match target {
        "i686-pc-windows-msvc" => "i686-pc-win32".to_string(),
        "x86_64-pc-windows-msvc" => "x86_64-pc-win32".to_string(),
        "i686-pc-windows-gnu" => "i686-w64-mingw32".to_string(),
        "x86_64-pc-windows-gnu" => "x86_64-w64-mingw32".to_string(),
        s => s.to_string(),
    }
}

pub fn make(host: &str) -> PathBuf {
    if host.contains("bitrig") || host.contains("dragonfly") ||
        host.contains("freebsd") || host.contains("netbsd") ||
        host.contains("openbsd") {
        PathBuf::from("gmake")
    } else {
        PathBuf::from("make")
    }
}

pub fn output(cmd: &mut Command) -> String {
    let output = match cmd.stderr(Stdio::inherit()).output() {
        Ok(status) => status,
        Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}",
                                cmd, e)),
    };
    if !output.status.success() {
        panic!("command did not execute successfully: {:?}\n\
                expected success, got: {}",
               cmd,
               output.status);
    }
    String::from_utf8(output.stdout).unwrap()
}

pub fn rerun_if_changed_anything_in_dir(dir: &Path) {
    let mut stack = dir.read_dir().unwrap()
                       .map(|e| e.unwrap())
                       .filter(|e| &*e.file_name() != ".git")
                       .collect::<Vec<_>>();
    while let Some(entry) = stack.pop() {
        let path = entry.path();
        if entry.file_type().unwrap().is_dir() {
            stack.extend(path.read_dir().unwrap().map(|e| e.unwrap()));
        } else {
            println!("cargo:rerun-if-changed={}", path.display());
        }
    }
}

/// Returns the last-modified time for `path`, or zero if it doesn't exist.
pub fn mtime(path: &Path) -> SystemTime {
    fs::metadata(path).and_then(|f| f.modified()).unwrap_or(UNIX_EPOCH)
}

/// Returns whether `dst` is up to date given that the file or files in `src`
/// are used to generate it.
///
/// Uses last-modified time checks to verify this.
pub fn up_to_date(src: &Path, dst: &Path) -> bool {
    if !dst.exists() {
        return false;
    }
    let threshold = mtime(dst);
    let meta = match fs::metadata(src) {
        Ok(meta) => meta,
        Err(e) => panic!("source {:?} failed to get metadata: {}", src, e),
    };
    if meta.is_dir() {
        dir_up_to_date(src, threshold)
    } else {
        meta.modified().unwrap_or(UNIX_EPOCH) <= threshold
    }
}

#[must_use]
pub struct NativeLibBoilerplate {
    pub src_dir: PathBuf,
    pub out_dir: PathBuf,
}

impl Drop for NativeLibBoilerplate {
    fn drop(&mut self) {
        t!(File::create(self.out_dir.join("rustbuild.timestamp")));
    }
}

// Perform standard preparations for native libraries that are build only once for all stages.
// Emit rerun-if-changed and linking attributes for Cargo, check if any source files are
// updated, calculate paths used later in actual build with CMake/make or C/C++ compiler.
// If Err is returned, then everything is up-to-date and further build actions can be skipped.
// Timestamps are created automatically when the result of `native_lib_boilerplate` goes out
// of scope, so all the build actions should be completed until then.
pub fn native_lib_boilerplate(src_name: &str,
                              out_name: &str,
                              link_name: &str,
                              search_subdir: &str)
                              -> Result<NativeLibBoilerplate, ()> {
    let current_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
    let src_dir = current_dir.join("..").join(src_name);
    rerun_if_changed_anything_in_dir(&src_dir);

    let out_dir = env::var_os("RUSTBUILD_NATIVE_DIR").unwrap_or(env::var_os("OUT_DIR").unwrap());
    let out_dir = PathBuf::from(out_dir).join(out_name);
    t!(fs::create_dir_all(&out_dir));
    if link_name.contains('=') {
        println!("cargo:rustc-link-lib={}", link_name);
    } else {
        println!("cargo:rustc-link-lib=static={}", link_name);
    }
    println!("cargo:rustc-link-search=native={}", out_dir.join(search_subdir).display());

    let timestamp = out_dir.join("rustbuild.timestamp");
    if !up_to_date(Path::new("build.rs"), &timestamp) || !up_to_date(&src_dir, &timestamp) {
        Ok(NativeLibBoilerplate { src_dir: src_dir, out_dir: out_dir })
    } else {
        Err(())
    }
}

pub fn sanitizer_lib_boilerplate(sanitizer_name: &str) -> Result<NativeLibBoilerplate, ()> {
    let (link_name, search_path) = match &*env::var("TARGET").unwrap() {
        "x86_64-unknown-linux-gnu" => (
            format!("clang_rt.{}-x86_64", sanitizer_name),
            "build/lib/linux",
        ),
        "x86_64-apple-darwin" => (
            format!("dylib=clang_rt.{}_osx_dynamic", sanitizer_name),
            "build/lib/darwin",
        ),
        _ => return Err(()),
    };
    native_lib_boilerplate("libcompiler_builtins/compiler-rt",
                           sanitizer_name,
                           &link_name,
                           search_path)
}

fn dir_up_to_date(src: &Path, threshold: SystemTime) -> bool {
    t!(fs::read_dir(src)).map(|e| t!(e)).all(|e| {
        let meta = t!(e.metadata());
        if meta.is_dir() {
            dir_up_to_date(&e.path(), threshold)
        } else {
            meta.modified().unwrap_or(UNIX_EPOCH) < threshold
        }
    })
}

fn fail(s: &str) -> ! {
    println!("\n\n{}\n\n", s);
    std::process::exit(1);
}
<commit_msg>Auto merge of #49360 - topecongiro:run-rustfmt/build_helper, r=nrc<commit_after>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::fs::File;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::time::{SystemTime, UNIX_EPOCH};
use std::{env, fs};

/// A helper macro to `unwrap` a result except also print out details like:
///
/// * The file/line of the panic
/// * The expression that failed
/// * The error itself
///
/// This is currently used judiciously throughout the build system rather than
/// using a `Result` with `try!`, but this may change one day...
#[macro_export]
macro_rules! t {
    ($e:expr) => {
        match $e {
            Ok(e) => e,
            Err(e) => panic!("{} failed with {}", stringify!($e), e),
        }
    };
}

pub fn run(cmd: &mut Command) {
    println!("running: {:?}", cmd);
    run_silent(cmd);
}

pub fn run_silent(cmd: &mut Command) {
    if !try_run_silent(cmd) {
        std::process::exit(1);
    }
}

pub fn try_run_silent(cmd: &mut Command) -> bool {
    let status = match cmd.status() {
        Ok(status) => status,
        Err(e) => fail(&format!(
            "failed to execute command: {:?}\nerror: {}",
            cmd, e
        )),
    };
    if !status.success() {
        println!(
            "\n\ncommand did not execute successfully: {:?}\n\
             expected success, got: {}\n\n",
            cmd, status
        );
    }
    status.success()
}

pub fn run_suppressed(cmd: &mut Command) {
    if !try_run_suppressed(cmd) {
        std::process::exit(1);
    }
}

pub fn try_run_suppressed(cmd: &mut Command) -> bool {
    let output = match cmd.output() {
        Ok(status) => status,
        Err(e) => fail(&format!(
            "failed to execute command: {:?}\nerror: {}",
            cmd, e
        )),
    };
    if !output.status.success() {
        println!(
            "\n\ncommand did not execute successfully: {:?}\n\
             expected success, got: {}\n\n\
             stdout ----\n{}\n\
             stderr ----\n{}\n\n",
            cmd,
            output.status,
            String::from_utf8_lossy(&output.stdout),
            String::from_utf8_lossy(&output.stderr)
        );
    }
    output.status.success()
}

pub fn gnu_target(target: &str) -> String {
    match target {
        "i686-pc-windows-msvc" => "i686-pc-win32".to_string(),
        "x86_64-pc-windows-msvc" => "x86_64-pc-win32".to_string(),
        "i686-pc-windows-gnu" => "i686-w64-mingw32".to_string(),
        "x86_64-pc-windows-gnu" => "x86_64-w64-mingw32".to_string(),
        s => s.to_string(),
    }
}

pub fn make(host: &str) -> PathBuf {
    if host.contains("bitrig") || host.contains("dragonfly") || host.contains("freebsd")
        || host.contains("netbsd") || host.contains("openbsd")
    {
        PathBuf::from("gmake")
    } else {
        PathBuf::from("make")
    }
}

pub fn output(cmd: &mut Command) -> String {
    let output = match cmd.stderr(Stdio::inherit()).output() {
        Ok(status) => status,
        Err(e) => fail(&format!(
            "failed to execute command: {:?}\nerror: {}",
            cmd, e
        )),
    };
    if !output.status.success() {
        panic!(
            "command did not execute successfully: {:?}\n\
             expected success, got: {}",
            cmd, output.status
        );
    }
    String::from_utf8(output.stdout).unwrap()
}

pub fn rerun_if_changed_anything_in_dir(dir: &Path) {
    let mut stack = dir.read_dir()
        .unwrap()
        .map(|e| e.unwrap())
        .filter(|e| &*e.file_name() != ".git")
        .collect::<Vec<_>>();
    while let Some(entry) = stack.pop() {
        let path = entry.path();
        if entry.file_type().unwrap().is_dir() {
            stack.extend(path.read_dir().unwrap().map(|e| e.unwrap()));
        } else {
            println!("cargo:rerun-if-changed={}", path.display());
        }
    }
}

/// Returns the last-modified time for `path`, or zero if it doesn't exist.
pub fn mtime(path: &Path) -> SystemTime {
    fs::metadata(path)
        .and_then(|f| f.modified())
        .unwrap_or(UNIX_EPOCH)
}

/// Returns whether `dst` is up to date given that the file or files in `src`
/// are used to generate it.
///
/// Uses last-modified time checks to verify this.
pub fn up_to_date(src: &Path, dst: &Path) -> bool {
    if !dst.exists() {
        return false;
    }
    let threshold = mtime(dst);
    let meta = match fs::metadata(src) {
        Ok(meta) => meta,
        Err(e) => panic!("source {:?} failed to get metadata: {}", src, e),
    };
    if meta.is_dir() {
        dir_up_to_date(src, threshold)
    } else {
        meta.modified().unwrap_or(UNIX_EPOCH) <= threshold
    }
}

#[must_use]
pub struct NativeLibBoilerplate {
    pub src_dir: PathBuf,
    pub out_dir: PathBuf,
}

impl Drop for NativeLibBoilerplate {
    fn drop(&mut self) {
        t!(File::create(self.out_dir.join("rustbuild.timestamp")));
    }
}

// Perform standard preparations for native libraries that are build only once for all stages.
// Emit rerun-if-changed and linking attributes for Cargo, check if any source files are
// updated, calculate paths used later in actual build with CMake/make or C/C++ compiler.
// If Err is returned, then everything is up-to-date and further build actions can be skipped.
// Timestamps are created automatically when the result of `native_lib_boilerplate` goes out
// of scope, so all the build actions should be completed until then.
pub fn native_lib_boilerplate(
    src_name: &str,
    out_name: &str,
    link_name: &str,
    search_subdir: &str,
) -> Result<NativeLibBoilerplate, ()> {
    let current_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
    let src_dir = current_dir.join("..").join(src_name);
    rerun_if_changed_anything_in_dir(&src_dir);

    let out_dir = env::var_os("RUSTBUILD_NATIVE_DIR").unwrap_or(env::var_os("OUT_DIR").unwrap());
    let out_dir = PathBuf::from(out_dir).join(out_name);
    t!(fs::create_dir_all(&out_dir));
    if link_name.contains('=') {
        println!("cargo:rustc-link-lib={}", link_name);
    } else {
        println!("cargo:rustc-link-lib=static={}", link_name);
    }
    println!(
        "cargo:rustc-link-search=native={}",
        out_dir.join(search_subdir).display()
    );

    let timestamp = out_dir.join("rustbuild.timestamp");
    if !up_to_date(Path::new("build.rs"), &timestamp) || !up_to_date(&src_dir, &timestamp) {
        Ok(NativeLibBoilerplate {
            src_dir: src_dir,
            out_dir: out_dir,
        })
    } else {
        Err(())
    }
}

pub fn sanitizer_lib_boilerplate(sanitizer_name: &str) -> Result<NativeLibBoilerplate, ()> {
    let (link_name, search_path) = match &*env::var("TARGET").unwrap() {
        "x86_64-unknown-linux-gnu" => (
            format!("clang_rt.{}-x86_64", sanitizer_name),
            "build/lib/linux",
        ),
        "x86_64-apple-darwin" => (
            format!("dylib=clang_rt.{}_osx_dynamic", sanitizer_name),
            "build/lib/darwin",
        ),
        _ => return Err(()),
    };
    native_lib_boilerplate(
        "libcompiler_builtins/compiler-rt",
        sanitizer_name,
        &link_name,
        search_path,
    )
}

fn dir_up_to_date(src: &Path, threshold: SystemTime) -> bool {
    t!(fs::read_dir(src)).map(|e| t!(e)).all(|e| {
        let meta = t!(e.metadata());
        if meta.is_dir() {
            dir_up_to_date(&e.path(), threshold)
        } else {
            meta.modified().unwrap_or(UNIX_EPOCH) < threshold
        }
    })
}

fn fail(s: &str) -> ! {
    println!("\n\n{}\n\n", s);
    std::process::exit(1);
}
<|endoftext|>


<commit_before>//! Generates random trees and tree-like plants.

use prop::plant::Branch;
use math::{InnerSpace, Point3f, Vector1, Vector3f};
use rand::{Rand, Rng};
use rand::distributions::range::SampleRange;
use rand::distributions::{self, IndependentSample};
use std::ops::Range;

/// Parameters for the tree generator.
#[derive(Debug)]
struct Params {
    /// Diameter of the first branch we create (the trunk).
    trunk_diameter: f32,
    /// Trunk height. Note that branches going upward can increase plant height
    /// beyond this.
    trunk_height: f32,
    /// Trunk diameter at `trunk_height`. Should be smaller than
    /// `trunk_diameter`.
    trunk_diameter_top: f32,
    /// Trunk height at which we start creating branches.
    min_branch_height: f32,
    /// Range of subbranch diameters as a factor of the parent branch.
    branch_diameter_factor: Range<f32>,
    /// Range of subbranch angles in degrees.
    branch_angle_deg: Range<f32>,
    /// Range of angles to use for rotation of new segments.
    ///
    /// The higher the angle, the more "twisted" branches appear.
    branch_segment_angle: Range<f32>,
    /// Range of segment counts for branches.
    ///
    /// Together with `branch_segment_length`, this defines the overall branch
    /// length.
    branch_segment_count: Range<u32>,
    /// Range of segment lengths to use for branches.
    ///
    /// Each segment will have a random length in this range.
    branch_segment_length: Range<f32>,
}

/// Describes parameter ranges to generate a random tree.
///
/// We store a list of user-defined presets, one of which is selected to
/// generate a tree.
#[derive(Clone)]
struct Preset {
    trunk_diameter: Range<f32>,
    trunk_height: Range<f32>,
    trunk_diameter_top: Range<f32>,
    min_branch_height: Range<f32>,
    branch_diameter_factor: Range<f32>,
    branch_angle_deg: Range<f32>,
    branch_segment_angle: Range<f32>,
    branch_segment_count: Range<u32>,
    branch_segment_length: Range<f32>,
}

static PRESETS: &'static [Preset] = &[Preset {
                                          trunk_diameter: 0.3..0.5,
                                          trunk_height: 3.0..6.0,
                                          trunk_diameter_top: 0.2..0.4,
                                          min_branch_height: 0.4..0.6,
                                          branch_diameter_factor: 0.3..0.5,
                                          branch_angle_deg: 70.0..110.0,
                                          branch_segment_angle: 2.0..10.0,
                                          branch_segment_count: 5..20,
                                          branch_segment_length: 0.05..0.15,
                                      }];

pub struct TreeGen {
    params: Params,
    /// Buffer for branches, filled as they're created.
    branches: Vec<Branch>,
}

impl TreeGen {
    fn create_trunk<R: Rng>(&mut self, _rng: &mut R) {
        let mut points = Vec::new();

        {
            let mut add_point = |height, diam| {
                points.push((Point3f::new(0.0, height, 0.0), diam));
            };

            let diam_start = Vector1::new(self.params.trunk_diameter);
            let diam_end = Vector1::new(self.params.trunk_diameter_top);
            let mut height = 0.0;
            while height < self.params.trunk_height {
                // Current height as a fraction of the total height
                let height_frac =
                    if height == 0.0 { 0.0 } else { self.params.trunk_height / height };
                let diam = diam_start.lerp(diam_end, height_frac);

                add_point(height, diam.x);

                let segment_len = segment_dist(diam.x);
                height += segment_len;
            }

            // FIXME Do we need to create another point here?
        }

        self.branches.push(Branch {
            points: points,
            // FIXME Fixed color for now, we should use a configurable random color (or at least
            // make it brown).
            color: Vector3f::new(0.0, 0.0, 1.0),
        });
    }

    /// Generates a random tree according to the stored parameters.
    ///
    /// The tree is returned as a list of branches for now.
    pub fn generate<R: Rng>(mut self, rng: &mut R) -> Vec<Branch> {
        // Recursively create the tree and put all branches in a buffer.
        self.create_trunk(rng);
        self.branches
    }
}

impl Rand for TreeGen {
    fn rand<R: Rng>(rng: &mut R) -> Self {
        // Create a tree generator with random parameters.
        // First, select a random preset:
        let preset = rng.choose(PRESETS).unwrap().clone();

        // Copy all `Range`s, and pick a random sample for tree properties.
        let params = Params {
            trunk_diameter: range_sample(preset.trunk_diameter, rng),
            trunk_height: range_sample(preset.trunk_height, rng),
            trunk_diameter_top: range_sample(preset.trunk_diameter_top, rng),
            min_branch_height: range_sample(preset.min_branch_height, rng),
            branch_diameter_factor: preset.branch_diameter_factor,
            branch_angle_deg: preset.branch_angle_deg,
            branch_segment_angle: preset.branch_segment_angle,
            branch_segment_count: preset.branch_segment_count,
            branch_segment_length: preset.branch_segment_length,
        };

        debug!("treegen params: {:?}", params);

        TreeGen {
            params: params,
            branches: Vec::new(),
        }
    }
}

/// Samples a random element from a range.
fn range_sample<T: SampleRange + PartialOrd, R: Rng>(range: Range<T>, rng: &mut R) -> T {
    // Build a `rand` crate Range. We use `std`s Range for the cool `a..b` syntax ;)
    distributions::Range::new(range.start, range.end).ind_sample(rng)
}

/// Approximation of real-world distance of branch segments, depending on the
/// starting branch diameter.
fn segment_dist(diameter: f32) -> f32 {
    diameter * 11.25
}
<commit_msg>Branch generation and get rid of `Params`<commit_after>//! Generates random trees and tree-like plants.

use prop::plant::Branch;
use math::{InnerSpace, Point3f, Vector1, Vector3f, Euler, Basis3, Deg, Rotation};
use rand::{Rand, Rng};
use rand::distributions::range::SampleRange;
use rand::distributions::{self, IndependentSample};
use std::ops::Range;

/// Parameters for the tree generator.
#[derive(Debug)]
struct Preset {
    /// Diameter of the first branch we create (the trunk).
    trunk_diameter: Range<f32>,
    /// Trunk height. Note that branches going upward can increase plant height
    /// beyond this.
    trunk_height: Range<f32>,
    /// Trunk diameter at `trunk_height`. Should be smaller than
    /// `trunk_diameter`.
    trunk_diameter_top: Range<f32>,
    /// Trunk height at which we start creating branches.
    min_branch_height: Range<f32>,
    /// Range of subbranch diameters as a factor of the parent branch.
    branch_diameter_factor: Range<f32>,
    /// Range of subbranch angles in degrees.
    branch_angle_deg: Range<f32>,
    /// Factor by which to reduce segment diameter between consecutive points, sampled per branch.
    branch_diam_reduction: Range<f32>,
    /// Range of angles to use for rotation of new segments.
    ///
    /// The higher the angle, the more "twisted" branches appear.
    branch_segment_angle: Range<f32>,
    /// Range of segment counts for branches.
    ///
    /// Together with `branch_segment_length`, this defines the overall branch
    /// length.
    branch_segment_count: Range<u32>,
    /// Range of segment lengths to use for branches.
    ///
    /// Each segment will have a random length in this range.
    branch_segment_length: Range<f32>,  // FIXME unused
}

static PRESETS: &'static [Preset] = &[Preset {
                                          trunk_diameter: 0.3..0.5,
                                          trunk_height: 3.0..6.0,
                                          trunk_diameter_top: 0.2..0.4,
                                          min_branch_height: 0.4..0.6,
                                          branch_diameter_factor: 0.3..0.5,
                                          branch_angle_deg: 70.0..110.0,
                                          branch_diam_reduction: 0.75..0.85,
                                          branch_segment_angle: 2.0..10.0,
                                          branch_segment_count: 5..20,
                                          branch_segment_length: 0.05..0.15,
                                      }];

pub struct TreeGen {
    preset: &'static Preset,
    /// Buffer for branches, filled as they're created.
    branches: Vec<Branch>,
}

impl TreeGen {
    /// Generates a new branch at the given start point.
    ///
    /// # Parameters
    ///
    /// * `rng`: The RNG to use
    /// * `start`: The point at which the branch should start
    /// * `dir`: Direction in which the branch should grow
    /// * `depth`: Recursion depth, used to limit the maximum branch depth
    /// * `parent_diam`: Diameter of the node of the parent branch where this
    ///   branch starts
    fn create_branch<R: Rng>(&mut self,
                             rng: &mut R,
                             start: Point3f,
                             dir: Vector3f,
                             depth: u32,
                             parent_diam: f32) {
        if depth > 5 {
            // Limit recursion
            return;
        }

        // Current normalized growing direction, variated slightly as segments are generated
        let mut dir = dir.normalize();

        // Determine starting diameter of the new branch
        let mut diam = range_sample(&self.preset.branch_diameter_factor, rng) * parent_diam;
        // Determine how much segment diameter is reduced
        let diam_factor = range_sample(&self.preset.branch_diam_reduction, rng) * parent_diam;
        // How many segments should this branch get?
        let segment_count = range_sample(&self.preset.branch_segment_count, rng);

        let mut points = vec![(start, diam)];
        {
            let mut last = start;
            // Helper for adding a new point to this branch, which possibly grows a new branch.
            let mut add_point = |dist, diam| {
                // First, get a random angle by which to variate this segment.
                let x_angle = range_sample(&self.preset.branch_segment_angle, rng);
                let y_angle = range_sample(&self.preset.branch_segment_angle, rng);

                dir = Basis3::from(Euler {
                    x: Deg::new(x_angle),
                    y: Deg::new(y_angle),
                    z: Deg::new(0.0),
                }).rotate_vector(dir);

                // We need to add a point with a distance of `dist` from `last`.
                let point = last + dir * dist;
                last = point;

                // FIXME Make branch spawn chance configurable
                // 1/20 chance to spawn a branch at any point
                if rng.gen_weighted_bool(20) {
                    // Build a vector for the branch direction (Z is up)
                    let dir = self.gen_branch_direction(rng, dir);
                    self.create_branch(rng, point, dir, depth + 1, diam);
                }

                points.push((point, diam));
            };

            // In a loop, get the length of the next segment from the current diameter.
            for _ in 0..segment_count {
                let length = segment_dist(diam);
                diam *= diam_factor;
                add_point(length, diam);
            }
        }

        assert!(points.len() >= 2, "should've generated at least 2 points :(");
        self.branches.push(Branch {
            points: points,
            // FIXME Fixed color for now, we should use a configurable random color (or at least
            // make it brown).
            color: Vector3f::new(0.0, 0.0, 1.0),
        });
    }

    /// Given the growing direction of the parent branch, calculates a growing direction to use for
    /// a new child branch.
    fn gen_branch_direction<R: Rng>(&self, rng: &mut R, parent_dir: Vector3f) -> Vector3f {
        let x_angle = range_sample(&self.preset.branch_angle_deg, rng);
        let y_angle = range_sample(&self.preset.branch_angle_deg, rng);

        Basis3::from(Euler {
            x: Deg::new(x_angle),
            y: Deg::new(y_angle),
            z: Deg::new(0.0),
        }).rotate_vector(parent_dir)
    }

    fn create_trunk<R: Rng>(&mut self, rng: &mut R) {
        let trunk_diameter = range_sample(&self.preset.trunk_diameter, rng);
        let trunk_height = range_sample(&self.preset.trunk_height, rng);
        let trunk_diameter_top = range_sample(&self.preset.trunk_diameter_top, rng);
        let min_branch_height = range_sample(&self.preset.min_branch_height, rng);

        let mut points = Vec::new();

        {
            let mut add_point = |height, diam| {
                let point = Point3f::new(0.0, height, 0.0);
                if height >= min_branch_height {
                    // FIXME Make branch spawn chance configurable
                    // 1/5 chance to spawn a branch at any point
                    if rng.gen_weighted_bool(5) {
                        // Build a vector for the branch direction (Z is up)
                        let dir = self.gen_branch_direction(rng, Vector3f::new(0.0, 0.0, 1.0));
                        self.create_branch(rng, point, dir, 1, diam);
                    }
                }

                points.push((point, diam));
            };

            let diam_start = Vector1::new(trunk_diameter);
            let diam_end = Vector1::new(trunk_diameter_top);
            let mut height = 0.0;
            while height < trunk_height {
                // Current height as a fraction of the total height
                let height_frac = if height == 0.0 { 0.0 } else { trunk_height / height };
                let diam = diam_start.lerp(diam_end, height_frac);

                add_point(height, diam.x);

                let segment_len = segment_dist(diam.x);
                height += segment_len;
            }

            // FIXME Do we need to create another point here?
        }

        assert!(points.len() >= 2, "should've generated at least 2 points :(");
        self.branches.push(Branch {
            points: points,
            // FIXME Fixed color for now, we should use a configurable random color (or at least
            // make it brown).
            color: Vector3f::new(0.0, 0.0, 1.0),
        });

        debug!("generated tree with {} branches", self.branches.len());
    }

    /// Generates a random tree according to the stored parameters.
    ///
    /// The tree is returned as a list of branches for now.
    pub fn generate<R: Rng>(mut self, rng: &mut R) -> Vec<Branch> {
        info!("treegen activated!");    // deleteme
        // Recursively create the tree and put all branches in a buffer.
        self.create_trunk(rng);
        self.branches
    }
}

impl Rand for TreeGen {
    fn rand<R: Rng>(rng: &mut R) -> Self {
        // Create a tree generator with random parameters.
        // First, select a random preset:
        let preset = rng.choose(PRESETS).unwrap().clone();

        TreeGen {
            preset: preset,
            branches: Vec::new(),
        }
    }
}

/// Samples a random element from a range.
fn range_sample<T: SampleRange + PartialOrd + Copy, R: Rng>(range: &Range<T>, rng: &mut R) -> T {
    // Build a `rand` crate Range. We use `std`s Range for the cool `a..b` syntax ;)
    distributions::Range::new(range.start, range.end).ind_sample(rng)
}

/// Approximation of real-world distance of branch segments, depending on the
/// starting branch diameter.
fn segment_dist(diameter: f32) -> f32 {
    diameter * 11.25
}
<|endoftext|>


<commit_before>use super::Compat;
use futures::Stream as Stream01;

impl<St: Stream01> Stream01CompatExt for St {}

/// Extension trait for futures 0.1 [`Stream`][Stream01]
pub trait Stream01CompatExt: Stream01 {
    /// Converts a futures 0.1 [`Stream<Item = T, Error = E>`][Stream01] into a
    /// futures 0.3 [`Stream<Item = Result<T, E>>`][Stream03].
    fn compat(self) -> Compat<Self, ()>
    where
        Self: Sized,
    {
        Compat {
            inner: self,
            executor: None,
        }
    }
}
<commit_msg>Fix doc links in futures-util::compat<commit_after>use super::Compat;
use futures::Stream as Stream01;

impl<St: Stream01> Stream01CompatExt for St {}

/// Extension trait for futures 0.1 [`Stream`][futures::Stream]
pub trait Stream01CompatExt: Stream01 {
    /// Converts a futures 0.1 [`Stream<Item = T, Error = E>`][futures::Stream]
    /// into a futures 0.3 [`Stream<Item = Result<T,
    /// E>>`][futures_core::Stream].
    fn compat(self) -> Compat<Self, ()>
    where
        Self: Sized,
    {
        Compat {
            inner: self,
            executor: None,
        }
    }
}
<|endoftext|>


<commit_before>/*
  This code finds a solution to the NPR puzzle presented on Oct 21, 2018.

  A (paraphrased) statement of the problem:
    Given the letters in the word 'beermouth', contruct a three by three matrix of words where all
    the verticals, horizontals, and diagonals form three letter words.
*/

use std::collections::{HashMap, HashSet};
use std::error::Error;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::vec::Vec;

// Gets the nth char from `word`.
fn get_char(word: &str, index: usize) -> char {
    word.chars().nth(index).unwrap()
}

// Convert the word to a map from charaters to the number of times that character appears.
fn construct_letter_map(word: &str) -> HashMap<char, u8> {
    let mut map = HashMap::new();
    for letter in word.chars() {
        *map.entry(letter).or_insert(0) += 1
    }
    map
}

// Compare if `left` is a subset of `right`.
fn letter_map_less_than(left: &HashMap<char, u8>, right: &HashMap<char, u8>) -> bool {
    for (letter, l_count) in left {
        match right.get(letter) {
            Some(r_count) => if l_count > r_count {
                return false;
            },
            _ => return false,
        }
    }
    true
}

// The middle letter has to be the middle letter of at least four words. Construct lists of
// possible sets of words.
fn shared_middle_letters(words: &[[char; 3]]) -> Vec<Vec<[char; 3]>> {
    let mut map = HashMap::new();
    for word in words {
        map.entry(word[1]).or_insert(Vec::new()).push(*word)
    }
    let mut out = Vec::new();
    for (_, val) in map.drain() {
        if val.len() >= 4 {
            out.push(val);
        }
    }
    out
}

// Checks that the first and last letters of `word` are still in `right`.
fn two_letter_less_than(word: &[char; 3], right: &HashMap<char, u8>) -> bool {
    for letter in [word[0], word[2]].iter() {
        match right.get(letter) {
            Some(count) => if *count <= 0 {
                return false;
            },
            _ => return false,
        }
    }
    true
}

// Checks if the word is a subset of the letters in `letter_map`. If so, it decrements the counts
// in `letter_map` by the first and third letters of the word. Ignores the middle letter since that
// is shared.
// Returns whether or not the word was a subset.
fn clear_two_letters(word: &[char; 3], letter_map: &mut HashMap<char, u8>) -> bool {
    if !two_letter_less_than(word, letter_map) {
        return false;
    }
    *letter_map.get_mut(&word[0]).unwrap() -= 1;
    *letter_map.get_mut(&word[2]).unwrap() -= 1;
    true
}

// If the word is a candidate, append it. Otherwise, do nothing. Returns whether or not the word
// was appended.
fn append_word_if<'a>(
    word: &[char; 3],
    letter_map: &mut HashMap<char, u8>,
    words: &mut Vec<[char; 3]>,
) -> bool {
    if !clear_two_letters(word, letter_map) {
        return false;
    }
    words.push(*word);
    true
}

fn four_word_product<'a>(words: &[[char; 3]], base_map: &HashMap<char, u8>) -> Vec<Vec<[char; 3]>> {
    let mut out = Vec::new();
    let middle_letter = words[0][1];
    for ii in 0..words.len() {
        let mut map = base_map.clone();
        let mut four_words = Vec::new();
        *map.get_mut(&middle_letter).unwrap() -= 1;
        if !append_word_if(&words[ii], &mut map, &mut four_words) {
            continue;
        }
        assert_eq!(four_words.len(), 1);
        for jj in ii + 1..words.len() {
            // Truncate here so that we only get one word from this pass.
            four_words.truncate(1);
            // Create a new map per iteration.
            let mut map = map.clone();
            if !append_word_if(&words[jj], &mut map, &mut four_words) {
                continue;
            }
            assert_eq!(four_words.len(), 2);
            for kk in jj + 1..words.len() {
                four_words.truncate(2);
                let mut map = map.clone();
                if !append_word_if(&words[kk], &mut map, &mut four_words) {
                    continue;
                }
                assert_eq!(four_words.len(), 3);
                for ll in kk + 1..words.len() {
                    four_words.truncate(3);
                    let mut map = map.clone();
                    if append_word_if(&words[ll], &mut map, &mut four_words) {
                        assert_eq!(four_words.len(), 4);
                        out.push(four_words.clone());
                    }
                }
            }
        }
    }
    out
}

// Find all of the combinations of four words that share a middle letter and the combined letters
// of all four words are still a subset of 'base_map'.
fn four_word_candidates<'a>(
    shared_middle: &[Vec<[char; 3]>],
    base_map: &HashMap<char, u8>,
) -> Vec<Vec<[char; 3]>> {
    let mut out = Vec::new();
    for words in shared_middle {
        out.append(&mut four_word_product(words, base_map));
    }
    out
}

// For four indices between zero and 4, return the missing index.
fn missing_index(ii: usize, jj: usize, kk: usize) -> usize {
    assert!(ii < 4);
    assert!(jj < 4);
    assert!(kk < 4);
    for ll in 0..4 {
        if ll != ii && ll != jj && ll != kk {
            return ll;
        }
    }
    unreachable!()
}

// Takes the candidates and finds words that make a proper word in the top row. The words are
// returned such that the first word is the left to right diagonal (1, 5, 9 on a phone), the second
// word is the middle vertical, the third is the right to left diagonal (3, 5, 7) and the remaining
// word is last.
fn top_row_candidates<'a>(
    four_cands: &Vec<Vec<[char; 3]>>,
    possible_words: &HashSet<[char; 3]>,
) -> Vec<Vec<[char; 3]>> {
    let mut out = Vec::new();
    for cand in four_cands {
        assert!(cand.len() == 4);
        for ii in 0..cand.len() {
            let first = cand[ii];
            for jj in 0..cand.len() {
                if jj == ii {
                    continue;
                }
                let second = cand[jj];
                for kk in 0..cand.len() {
                    if kk == jj || kk == ii {
                        continue;
                    }
                    let third = cand[kk];
                    let word = [first[0], second[0], third[0]];
                    if possible_words.contains(&word) {
                        let mut top_cand = vec![first, second, third];
                        top_cand.push(cand[missing_index(ii, jj, kk)]);
                        out.push(top_cand);
                    }
                }
            }
        }
    }
    out
}

// Filter out sets of four where the bottom generated from the top three words isn't a word.
fn filter_by_bottom<'a>(
    top_cands: Vec<Vec<[char; 3]>>,
    possible_words: &HashSet<[char; 3]>,
) -> Vec<Vec<[char; 3]>> {
    let mut out = Vec::new();
    for cand in top_cands {
        assert!(cand.len() == 4);
        let first = cand[0][2];
        let second = cand[1][2];
        let third = cand[2][2];
        let word = [first, second, third];
        if possible_words.contains(&word) {
            out.push(cand);
            continue;
        }
    }
    out
}

fn filter_by_rest<'a>(
    cands: Vec<Vec<[char; 3]>>,
    possible_words: &HashSet<[char; 3]>,
) -> Vec<Vec<[char; 3]>> {
    let mut out = Vec::new();
    for cand in cands {
        // The left vertical word is made up of the first letter of the first top word (left to
        // right diagnol), the first letter of the remaining word, and the last letter of the
        // third top word (right to left diagonal).
        let left_v_word = [cand[0][0], cand[3][0], cand[2][2]];
        if !possible_words.contains(&left_v_word) {
            continue;
        }
        // The right vertical word is from the first letter of the third top word, the third letter
        // of the remaining word and the third letter of the first top word.
        let right_v_word = [cand[2][0], cand[3][2], cand[0][2]];
        if !possible_words.contains(&right_v_word) {
            continue;
        }

        out.push(cand);
    }
    out
}

// Returns the 3x3 grid of letters in row major form.
fn presentation_format(cand: &[[char; 3]]) -> Vec<Vec<char>> {
    let mut out = Vec::new();
    // Fill in the vectors with x's just as a placeholder.
    for _ in 0..3 {
        out.push(vec!['x', 'x', 'x']);
    }
    // The first word is the left to right diagonal.
    out[0][0] = cand[0][0];
    out[1][1] = cand[0][1];
    out[2][2] = cand[0][2];
    // The second word is the middle vertical.
    out[0][1] = cand[1][0];
    out[2][1] = cand[1][2];
    // The third word is the left diagonal.
    out[0][2] = cand[2][0];
    out[2][0] = cand[2][2];
    // The last word is the middle horizontal.
    out[1][0] = cand[3][0];
    out[1][2] = cand[3][2];
    out
}

fn main() {
    let f = match File::open("dictionary.txt") {
        Ok(f) => f,
        Err(why) => panic!("Failed to open links.txt: {}", Error::description(&why)),
    };

    // Construct a vector of three letter words that are a subset of beermouth.
    let source_word = construct_letter_map("beermouth");
    let words: Vec<[char; 3]> = BufReader::new(f)
        .lines()
        .map(|s| s.unwrap())
        .filter(|s| s.len() == 3)
        .filter(|s| letter_map_less_than(&construct_letter_map(s), &source_word))
        .map(|s| [get_char(&s, 0), get_char(&s, 1), get_char(&s, 2)])
        .collect();

    // Find sets of words that share a middle letter.
    let middle_candidates = shared_middle_letters(&words);
    // Find sets of four words that share a middle letter and could make up the solution.
    let four_cands = four_word_candidates(&middle_candidates, &source_word);

    // Find possible top rows.
    let word_set = {
        let mut word_set = HashSet::with_capacity(words.len());
        for word in words.iter() {
            word_set.insert(word.clone());
        }
        word_set
    };
    let top_cands = top_row_candidates(&four_cands, &word_set);
    let bottom_cands = filter_by_bottom(top_cands, &word_set);
    let last_cands = filter_by_rest(bottom_cands, &word_set);

    for last_cand in last_cands {
        println!("Last candidate words:");
        for word in last_cand.iter() {
            let word_str: String = word.iter().collect();
            print!("{} ", word_str);
        }
        println!("\nIn grid format:");
        let present = presentation_format(&last_cand);
        for line in present {
            for ch in line {
                print!(" {} ", ch);
            }
            print!("\n");
        }
        print!("\n");
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_letter_map() {
        let mut expected = HashMap::new();
        expected.insert('b', 1);
        expected.insert('e', 2);
        expected.insert('r', 1);
        expected.insert('m', 1);
        expected.insert('o', 1);
        expected.insert('u', 1);
        expected.insert('t', 1);
        expected.insert('h', 1);
        assert_eq!(expected, construct_letter_map("beermouth"));
    }

    #[test]
    fn test_map_less_than() {
        let source_word = construct_letter_map("beermouth");
        assert!(letter_map_less_than(
            &construct_letter_map("bee"),
            &source_word
        ));
        assert!(letter_map_less_than(
            &construct_letter_map("bum"),
            &source_word
        ));
        assert!(letter_map_less_than(
            &construct_letter_map("but"),
            &source_word
        ));
        assert!(letter_map_less_than(
            &construct_letter_map("out"),
            &source_word
        ));
        assert!(!letter_map_less_than(
            &construct_letter_map("boo"),
            &source_word
        ));
        assert!(!letter_map_less_than(
            &construct_letter_map("oom"),
            &source_word
        ));
        assert!(!letter_map_less_than(
            &construct_letter_map("ouf"),
            &source_word
        ));
    }

    #[test]
    fn test_shared_middle() {
        let mut words = Vec::new();
        words.push(['b', 'e', 'e']);
        words.push(['b', 'e', 't']);
        words.push(['m', 'e', 't']);
        words.push(['b', 'u', 'm']);
        words.push(['b', 'u', 't']);
        words.push(['o', 'u', 't']);
        words.push(['o', 'u', 'f']);
        words.push(['b', 'o', 't']);
        words.push(['h', 'o', 't']);
        words.push(['m', 'o', 't']);
        words.push(['t', 'o', 'm']);
        words.push(['r', 'o', 't']);
        let mut expected = Vec::new();
        {
            let mut group = Vec::new();
            for ii in 3..7 {
                group.push(words[ii]);
            }
            group.sort();
            expected.push(group);
        }
        {
            let mut group = Vec::new();
            for ii in 7..12 {
                group.push(words[ii]);
            }
            group.sort();
            expected.push(group);
        }
        let mut input_words = words.clone();
        input_words.sort();
        let mut shared = shared_middle_letters(&input_words);
        // Sort by the lengths of the sub-Vecs to prevent test flakes.
        shared.sort_by(|l, r| l.len().cmp(&r.len()));
        assert_eq!(expected, shared);
    }
}
<commit_msg>Make the filters into filters<commit_after>/*
  This code finds a solution to the NPR puzzle presented on Oct 21, 2018.

  A (paraphrased) statement of the problem:
    Given the letters in the word 'beermouth', contruct a three by three matrix of words where all
    the verticals, horizontals, and diagonals form three letter words.
*/

use std::collections::{HashMap, HashSet};
use std::error::Error;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::vec::Vec;

// Gets the nth char from `word`.
fn get_char(word: &str, index: usize) -> char {
    word.chars().nth(index).unwrap()
}

// Convert the word to a map from charaters to the number of times that character appears.
fn construct_letter_map(word: &str) -> HashMap<char, u8> {
    let mut map = HashMap::new();
    for letter in word.chars() {
        *map.entry(letter).or_insert(0) += 1
    }
    map
}

// Compare if `left` is a subset of `right`.
fn letter_map_less_than(left: &HashMap<char, u8>, right: &HashMap<char, u8>) -> bool {
    for (letter, l_count) in left {
        match right.get(letter) {
            Some(r_count) => if l_count > r_count {
                return false;
            },
            _ => return false,
        }
    }
    true
}

// The middle letter has to be the middle letter of at least four words. Construct lists of
// possible sets of words.
fn shared_middle_letters(words: &[[char; 3]]) -> Vec<Vec<[char; 3]>> {
    let mut map = HashMap::new();
    for word in words {
        map.entry(word[1]).or_insert(Vec::new()).push(*word)
    }
    let mut out = Vec::new();
    for (_, val) in map.drain() {
        if val.len() >= 4 {
            out.push(val);
        }
    }
    out
}

// Checks that the first and last letters of `word` are still in `right`.
fn two_letter_less_than(word: &[char; 3], right: &HashMap<char, u8>) -> bool {
    for letter in [word[0], word[2]].iter() {
        match right.get(letter) {
            Some(count) => if *count <= 0 {
                return false;
            },
            _ => return false,
        }
    }
    true
}

// Checks if the word is a subset of the letters in `letter_map`. If so, it decrements the counts
// in `letter_map` by the first and third letters of the word. Ignores the middle letter since that
// is shared.
// Returns whether or not the word was a subset.
fn clear_two_letters(word: &[char; 3], letter_map: &mut HashMap<char, u8>) -> bool {
    if !two_letter_less_than(word, letter_map) {
        return false;
    }
    *letter_map.get_mut(&word[0]).unwrap() -= 1;
    *letter_map.get_mut(&word[2]).unwrap() -= 1;
    true
}

// If the word is a candidate, append it. Otherwise, do nothing. Returns whether or not the word
// was appended.
fn append_word_if<'a>(
    word: &[char; 3],
    letter_map: &mut HashMap<char, u8>,
    words: &mut Vec<[char; 3]>,
) -> bool {
    if !clear_two_letters(word, letter_map) {
        return false;
    }
    words.push(*word);
    true
}

fn four_word_product<'a>(words: &[[char; 3]], base_map: &HashMap<char, u8>) -> Vec<Vec<[char; 3]>> {
    let mut out = Vec::new();
    let middle_letter = words[0][1];
    for ii in 0..words.len() {
        let mut map = base_map.clone();
        let mut four_words = Vec::new();
        *map.get_mut(&middle_letter).unwrap() -= 1;
        if !append_word_if(&words[ii], &mut map, &mut four_words) {
            continue;
        }
        assert_eq!(four_words.len(), 1);
        for jj in ii + 1..words.len() {
            // Truncate here so that we only get one word from this pass.
            four_words.truncate(1);
            // Create a new map per iteration.
            let mut map = map.clone();
            if !append_word_if(&words[jj], &mut map, &mut four_words) {
                continue;
            }
            assert_eq!(four_words.len(), 2);
            for kk in jj + 1..words.len() {
                four_words.truncate(2);
                let mut map = map.clone();
                if !append_word_if(&words[kk], &mut map, &mut four_words) {
                    continue;
                }
                assert_eq!(four_words.len(), 3);
                for ll in kk + 1..words.len() {
                    four_words.truncate(3);
                    let mut map = map.clone();
                    if append_word_if(&words[ll], &mut map, &mut four_words) {
                        assert_eq!(four_words.len(), 4);
                        out.push(four_words.clone());
                    }
                }
            }
        }
    }
    out
}

// Find all of the combinations of four words that share a middle letter and the combined letters
// of all four words are still a subset of 'base_map'.
fn four_word_candidates<'a>(
    shared_middle: &[Vec<[char; 3]>],
    base_map: &HashMap<char, u8>,
) -> Vec<Vec<[char; 3]>> {
    let mut out = Vec::new();
    for words in shared_middle {
        out.append(&mut four_word_product(words, base_map));
    }
    out
}

// For four indices between zero and 4, return the missing index.
fn missing_index(ii: usize, jj: usize, kk: usize) -> usize {
    assert!(ii < 4);
    assert!(jj < 4);
    assert!(kk < 4);
    for ll in 0..4 {
        if ll != ii && ll != jj && ll != kk {
            return ll;
        }
    }
    unreachable!()
}

// Takes the candidates and finds words that make a proper word in the top row. The words are
// returned such that the first word is the left to right diagonal (1, 5, 9 on a phone), the second
// word is the middle vertical, the third is the right to left diagonal (3, 5, 7) and the remaining
// word is last.
fn top_row_candidates<'a>(
    four_cands: &Vec<Vec<[char; 3]>>,
    possible_words: &HashSet<[char; 3]>,
) -> Vec<Vec<[char; 3]>> {
    let mut out = Vec::new();
    for cand in four_cands {
        assert!(cand.len() == 4);
        for ii in 0..cand.len() {
            let first = cand[ii];
            for jj in 0..cand.len() {
                if jj == ii {
                    continue;
                }
                let second = cand[jj];
                for kk in 0..cand.len() {
                    if kk == jj || kk == ii {
                        continue;
                    }
                    let third = cand[kk];
                    let word = [first[0], second[0], third[0]];
                    if possible_words.contains(&word) {
                        let mut top_cand = vec![first, second, third];
                        top_cand.push(cand[missing_index(ii, jj, kk)]);
                        out.push(top_cand);
                    }
                }
            }
        }
    }
    out
}

// Filter out sets of four where the bottom generated from the top three words isn't a word.
fn filter_by_bottom<'a>(cand: &Vec<[char; 3]>, possible_words: &HashSet<[char; 3]>) -> bool {
    assert!(cand.len() == 4);
    let first = cand[0][2];
    let second = cand[1][2];
    let third = cand[2][2];
    let word = [first, second, third];
    possible_words.contains(&word)
}

fn filter_by_rest<'a>(cand: &Vec<[char; 3]>, possible_words: &HashSet<[char; 3]>) -> bool {
    // The left vertical word is made up of the first letter of the first top word (left to
    // right diagnol), the first letter of the remaining word, and the last letter of the
    // third top word (right to left diagonal).
    let left_v_word = [cand[0][0], cand[3][0], cand[2][2]];
    if !possible_words.contains(&left_v_word) {
        return false;
    }
    // The right vertical word is from the first letter of the third top word, the third letter
    // of the remaining word and the third letter of the first top word.
    let right_v_word = [cand[2][0], cand[3][2], cand[0][2]];
    possible_words.contains(&right_v_word)
}

// Returns the 3x3 grid of letters in row major form.
fn presentation_format(cand: &[[char; 3]]) -> Vec<Vec<char>> {
    let mut out = Vec::new();
    // Fill in the vectors with x's just as a placeholder.
    for _ in 0..3 {
        out.push(vec!['x', 'x', 'x']);
    }
    // The first word is the left to right diagonal.
    out[0][0] = cand[0][0];
    out[1][1] = cand[0][1];
    out[2][2] = cand[0][2];
    // The second word is the middle vertical.
    out[0][1] = cand[1][0];
    out[2][1] = cand[1][2];
    // The third word is the left diagonal.
    out[0][2] = cand[2][0];
    out[2][0] = cand[2][2];
    // The last word is the middle horizontal.
    out[1][0] = cand[3][0];
    out[1][2] = cand[3][2];
    out
}

fn main() {
    let f = match File::open("dictionary.txt") {
        Ok(f) => f,
        Err(why) => panic!("Failed to open links.txt: {}", Error::description(&why)),
    };

    // Construct a vector of three letter words that are a subset of beermouth.
    let source_word = construct_letter_map("beermouth");
    let words: Vec<[char; 3]> = BufReader::new(f)
        .lines()
        .map(|s| s.unwrap())
        .filter(|s| s.len() == 3)
        .filter(|s| letter_map_less_than(&construct_letter_map(s), &source_word))
        .map(|s| [get_char(&s, 0), get_char(&s, 1), get_char(&s, 2)])
        .collect();

    // Find sets of words that share a middle letter.
    let middle_candidates = shared_middle_letters(&words);
    // Find sets of four words that share a middle letter and could make up the solution.
    let four_cands = four_word_candidates(&middle_candidates, &source_word);

    // Find possible top rows.
    let word_set = {
        let mut word_set = HashSet::with_capacity(words.len());
        for word in words.iter() {
            word_set.insert(word.clone());
        }
        word_set
    };
    let top_cands = top_row_candidates(&four_cands, &word_set);
    let last_cands: Vec<&Vec<[char; 3]>> = top_cands
        .iter()
        .filter(|cand| filter_by_bottom(cand, &word_set))
        .filter(|cand| filter_by_rest(cand, &word_set))
        .collect();

    for last_cand in last_cands {
        println!("Last candidate words:");
        for word in last_cand.iter() {
            let word_str: String = word.iter().collect();
            print!("{} ", word_str);
        }
        println!("\nIn grid format:");
        let present = presentation_format(&last_cand);
        for line in present {
            for ch in line {
                print!(" {} ", ch);
            }
            print!("\n");
        }
        print!("\n");
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_letter_map() {
        let mut expected = HashMap::new();
        expected.insert('b', 1);
        expected.insert('e', 2);
        expected.insert('r', 1);
        expected.insert('m', 1);
        expected.insert('o', 1);
        expected.insert('u', 1);
        expected.insert('t', 1);
        expected.insert('h', 1);
        assert_eq!(expected, construct_letter_map("beermouth"));
    }

    #[test]
    fn test_map_less_than() {
        let source_word = construct_letter_map("beermouth");
        assert!(letter_map_less_than(
            &construct_letter_map("bee"),
            &source_word
        ));
        assert!(letter_map_less_than(
            &construct_letter_map("bum"),
            &source_word
        ));
        assert!(letter_map_less_than(
            &construct_letter_map("but"),
            &source_word
        ));
        assert!(letter_map_less_than(
            &construct_letter_map("out"),
            &source_word
        ));
        assert!(!letter_map_less_than(
            &construct_letter_map("boo"),
            &source_word
        ));
        assert!(!letter_map_less_than(
            &construct_letter_map("oom"),
            &source_word
        ));
        assert!(!letter_map_less_than(
            &construct_letter_map("ouf"),
            &source_word
        ));
    }

    #[test]
    fn test_shared_middle() {
        let mut words = Vec::new();
        words.push(['b', 'e', 'e']);
        words.push(['b', 'e', 't']);
        words.push(['m', 'e', 't']);
        words.push(['b', 'u', 'm']);
        words.push(['b', 'u', 't']);
        words.push(['o', 'u', 't']);
        words.push(['o', 'u', 'f']);
        words.push(['b', 'o', 't']);
        words.push(['h', 'o', 't']);
        words.push(['m', 'o', 't']);
        words.push(['t', 'o', 'm']);
        words.push(['r', 'o', 't']);
        let mut expected = Vec::new();
        {
            let mut group = Vec::new();
            for ii in 3..7 {
                group.push(words[ii]);
            }
            group.sort();
            expected.push(group);
        }
        {
            let mut group = Vec::new();
            for ii in 7..12 {
                group.push(words[ii]);
            }
            group.sort();
            expected.push(group);
        }
        let mut input_words = words.clone();
        input_words.sort();
        let mut shared = shared_middle_letters(&input_words);
        // Sort by the lengths of the sub-Vecs to prevent test flakes.
        shared.sort_by(|l, r| l.len().cmp(&r.len()));
        assert_eq!(expected, shared);
    }
}
<|endoftext|>


<commit_before>extern crate cargotest;
extern crate hamcrest;

use cargotest::ChannelChanger;
use cargotest::support::registry::{self, Package};
use cargotest::support::{project, execs};
use hamcrest::assert_that;

#[test]
fn is_feature_gated() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            version = "0.0.1"
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    Package::new("bar", "0.0.1").alternative(true).publish();

    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(101)
                .with_stderr_contains("  feature `alternative-registries` is required"));
}

#[test]
fn depend_on_alt_registry() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            cargo-features = ["alternative-registries"]

            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            version = "0.0.1"
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    Package::new("bar", "0.0.1").alternative(true).publish();

    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(0).with_stderr(&format!("\
[UPDATING] registry `{reg}`
[DOWNLOADING] bar v0.0.1 (registry `file://[..]`)
[COMPILING] bar v0.0.1 (registry `file://[..]`)
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs
",
        dir = p.url(),
        reg = registry::alt_registry())));

    assert_that(p.cargo("clean").masquerade_as_nightly_cargo(), execs().with_status(0));

    // Don't download a second time
    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(0).with_stderr(&format!("\
[COMPILING] bar v0.0.1 (registry `file://[..]`)
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs
",
        dir = p.url())));
}

#[test]
fn registry_incompatible_with_path() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            cargo-features = ["alternative-registries"]

            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            path = ""
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(101)
                .with_stderr_contains("  dependency (bar) specification is ambiguous. Only one of `path` or `registry` is allowed."));
}

#[test]
fn registry_incompatible_with_git() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            cargo-features = ["alternative-registries"]

            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            git = ""
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(101)
                .with_stderr_contains("  dependency (bar) specification is ambiguous. Only one of `git` or `registry` is allowed."));
}

#[test]
fn cannot_publish_with_registry_dependency() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            cargo-features = ["alternative-registries"]

            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            version = "0.0.1"
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    Package::new("bar", "0.0.1").alternative(true).publish();

    assert_that(p.cargo("publish").masquerade_as_nightly_cargo()
                 .arg("--index").arg(registry::alt_registry().to_string()),
                execs().with_status(101));
}

#[test]
fn alt_registry_and_crates_io_deps() {

    let p = project("foo")
        .file("Cargo.toml", r#"
            cargo-features = ["alternative-registries"]

            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies]
            crates_io_dep = "0.0.1"

            [dependencies.alt_reg_dep]
            version = "0.1.0"
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    Package::new("crates_io_dep", "0.0.1").publish();
    Package::new("alt_reg_dep", "0.1.0").alternative(true).publish();

    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(0)
                       .with_stderr_contains(format!("\
[UPDATING] registry `{}`", registry::alt_registry()))
                       .with_stderr_contains(&format!("\
[UPDATING] registry `{}`", registry::registry()))
                       .with_stderr_contains("\
[DOWNLOADING] crates_io_dep v0.0.1 (registry `file://[..]`)")
                       .with_stderr_contains("\
[DOWNLOADING] alt_reg_dep v0.1.0 (registry `file://[..]`)")
                       .with_stderr_contains("\
[COMPILING] alt_reg_dep v0.1.0 (registry `file://[..]`)")
                       .with_stderr_contains("\
[COMPILING] crates_io_dep v0.0.1")
                       .with_stderr_contains(&format!("\
[COMPILING] foo v0.0.1 ({})", p.url()))
                       .with_stderr_contains("\
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs"))

}

#[test]
fn alt_registry_dep_with_crates_io_dep() {

    let p = project("foo")
        .file("Cargo.toml", r#"
            cargo-features = ["alternative-registries"]

            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.alt_reg_dep]
            version = "0.1.1"
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    Package::new("crates_io_dep", "0.0.2").publish();
    Package::new("alt_reg_dep", "0.1.1").alternative(true).registry_dep("crates_io_dep", "0.0.2", registry::registry().as_str()).publish();

    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(0).with_stderr(&format!("\
[UPDATING] registry `{alt_reg}`
[UPDATING] registry `{crates_io_reg}`
[DOWNLOADING] alt_reg_dep v0.1.1 (registry `file://[..]`)
[DOWNLOADING] crates_io_dep v0.0.2 (registry `file://[..]`)
[COMPILING] crates_io_dep v0.0.2 (registry `file://[..]`)
[COMPILING] alt_reg_dep v0.1.1 (registry `file://[..]`)
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs
",
        dir = p.url(),
        crates_io_reg = registry::registry(),
        alt_reg = registry::alt_registry())));
}

#[test]
fn alt_reg_dep_with_alt_reg_dep() {

    let p = project("foo")
        .file("Cargo.toml", r#"
            cargo-features = ["alternative-registries"]

            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            version = "0.1.1"
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    Package::new("baz", "0.0.2").alternative(true).publish();
    Package::new("bar", "0.1.1").alternative(true).registry_dep("baz", "0.0.2", registry::alt_registry().as_str()).publish();

    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(0).with_stderr(&format!("\
[UPDATING] registry `{alt_reg}`
[DOWNLOADING] bar v0.1.1 (registry `file://[..]`)
[DOWNLOADING] baz v0.0.2 (registry `file://[..]`)
[COMPILING] baz v0.0.2 (registry `file://[..]`)
[COMPILING] bar v0.1.1 (registry `file://[..]`)
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs
",
        dir = p.url(),
        alt_reg = registry::alt_registry())));
}
<commit_msg>Add tests of transitive dependencies.<commit_after>extern crate cargotest;
extern crate hamcrest;

use cargotest::ChannelChanger;
use cargotest::support::registry::{self, Package};
use cargotest::support::{project, execs};
use hamcrest::assert_that;

#[test]
fn is_feature_gated() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            version = "0.0.1"
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    Package::new("bar", "0.0.1").alternative(true).publish();

    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(101)
                .with_stderr_contains("  feature `alternative-registries` is required"));
}

#[test]
fn depend_on_alt_registry() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            cargo-features = ["alternative-registries"]

            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            version = "0.0.1"
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    Package::new("bar", "0.0.1").alternative(true).publish();

    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(0).with_stderr(&format!("\
[UPDATING] registry `{reg}`
[DOWNLOADING] bar v0.0.1 (registry `file://[..]`)
[COMPILING] bar v0.0.1 (registry `file://[..]`)
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs
",
        dir = p.url(),
        reg = registry::alt_registry())));

    assert_that(p.cargo("clean").masquerade_as_nightly_cargo(), execs().with_status(0));

    // Don't download a second time
    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(0).with_stderr(&format!("\
[COMPILING] bar v0.0.1 (registry `file://[..]`)
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs
",
        dir = p.url())));
}

#[test]
fn depend_on_alt_registry_depends_on_same_registry() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            cargo-features = ["alternative-registries"]

            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            version = "0.0.1"
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    Package::new("baz", "0.0.1").alternative(true).publish();
    Package::new("bar", "0.0.1").dep("baz", "0.0.1").alternative(true).publish();

    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(0).with_stderr(&format!("\
[UPDATING] registry `{reg}`
[DOWNLOADING] [..] v0.0.1 (registry `file://[..]`)
[DOWNLOADING] [..] v0.0.1 (registry `file://[..]`)
[COMPILING] baz v0.0.1 (registry `file://[..]`)
[COMPILING] bar v0.0.1 (registry `file://[..]`)
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs
",
        dir = p.url(),
        reg = registry::alt_registry())));
}

#[test]
fn depend_on_alt_registry_depends_on_crates_io() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            cargo-features = ["alternative-registries"]

            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            version = "0.0.1"
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    Package::new("baz", "0.0.1").publish();
    Package::new("bar", "0.0.1").dep("baz", "0.0.1").alternative(true).publish();

    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(0).with_stderr(&format!("\
[UPDATING] registry `{reg}`
[DOWNLOADING] [..] v0.0.1 (registry `file://[..]`)
[DOWNLOADING] [..] v0.0.1 (registry `file://[..]`)
[COMPILING] baz v0.0.1
[COMPILING] bar v0.0.1
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs
",
        dir = p.url(),
        reg = registry::alt_registry())));
}

#[test]
fn registry_incompatible_with_path() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            cargo-features = ["alternative-registries"]

            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            path = ""
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(101)
                .with_stderr_contains("  dependency (bar) specification is ambiguous. Only one of `path` or `registry` is allowed."));
}

#[test]
fn registry_incompatible_with_git() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            cargo-features = ["alternative-registries"]

            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            git = ""
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(101)
                .with_stderr_contains("  dependency (bar) specification is ambiguous. Only one of `git` or `registry` is allowed."));
}

#[test]
fn cannot_publish_with_registry_dependency() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            cargo-features = ["alternative-registries"]

            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            version = "0.0.1"
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    Package::new("bar", "0.0.1").alternative(true).publish();

    assert_that(p.cargo("publish").masquerade_as_nightly_cargo()
                 .arg("--index").arg(registry::alt_registry().to_string()),
                execs().with_status(101));
}

#[test]
fn alt_registry_and_crates_io_deps() {

    let p = project("foo")
        .file("Cargo.toml", r#"
            cargo-features = ["alternative-registries"]

            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies]
            crates_io_dep = "0.0.1"

            [dependencies.alt_reg_dep]
            version = "0.1.0"
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    Package::new("crates_io_dep", "0.0.1").publish();
    Package::new("alt_reg_dep", "0.1.0").alternative(true).publish();

    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(0)
                       .with_stderr_contains(format!("\
[UPDATING] registry `{}`", registry::alt_registry()))
                       .with_stderr_contains(&format!("\
[UPDATING] registry `{}`", registry::registry()))
                       .with_stderr_contains("\
[DOWNLOADING] crates_io_dep v0.0.1 (registry `file://[..]`)")
                       .with_stderr_contains("\
[DOWNLOADING] alt_reg_dep v0.1.0 (registry `file://[..]`)")
                       .with_stderr_contains("\
[COMPILING] alt_reg_dep v0.1.0 (registry `file://[..]`)")
                       .with_stderr_contains("\
[COMPILING] crates_io_dep v0.0.1")
                       .with_stderr_contains(&format!("\
[COMPILING] foo v0.0.1 ({})", p.url()))
                       .with_stderr_contains("\
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs"))

}

#[test]
fn alt_registry_dep_with_crates_io_dep() {

    let p = project("foo")
        .file("Cargo.toml", r#"
            cargo-features = ["alternative-registries"]

            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.alt_reg_dep]
            version = "0.1.1"
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    Package::new("crates_io_dep", "0.0.2").publish();
    Package::new("alt_reg_dep", "0.1.1").alternative(true).registry_dep("crates_io_dep", "0.0.2", registry::registry().as_str()).publish();

    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(0).with_stderr(&format!("\
[UPDATING] registry `{alt_reg}`
[UPDATING] registry `{crates_io_reg}`
[DOWNLOADING] alt_reg_dep v0.1.1 (registry `file://[..]`)
[DOWNLOADING] crates_io_dep v0.0.2 (registry `file://[..]`)
[COMPILING] crates_io_dep v0.0.2 (registry `file://[..]`)
[COMPILING] alt_reg_dep v0.1.1 (registry `file://[..]`)
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs
",
        dir = p.url(),
        crates_io_reg = registry::registry(),
        alt_reg = registry::alt_registry())));
}

#[test]
fn alt_reg_dep_with_alt_reg_dep() {

    let p = project("foo")
        .file("Cargo.toml", r#"
            cargo-features = ["alternative-registries"]

            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            version = "0.1.1"
            registry = "alternative"
        "#)
        .file("src/main.rs", "fn main() {}");
    p.build();

    Package::new("baz", "0.0.2").alternative(true).publish();
    Package::new("bar", "0.1.1").alternative(true).registry_dep("baz", "0.0.2", registry::alt_registry().as_str()).publish();

    assert_that(p.cargo("build").masquerade_as_nightly_cargo(),
                execs().with_status(0).with_stderr(&format!("\
[UPDATING] registry `{alt_reg}`
[DOWNLOADING] bar v0.1.1 (registry `file://[..]`)
[DOWNLOADING] baz v0.0.2 (registry `file://[..]`)
[COMPILING] baz v0.0.2 (registry `file://[..]`)
[COMPILING] bar v0.1.1 (registry `file://[..]`)
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] secs
",
        dir = p.url(),
        alt_reg = registry::alt_registry())));
}
<|endoftext|>