<commit_before>//! Support for future-incompatible warning reporting.

use crate::core::compiler::BuildContext;
use crate::core::{Dependency, PackageId, Workspace};
use crate::sources::SourceConfigMap;
use crate::util::{iter_join, CargoResult, Config};
use anyhow::{bail, format_err, Context};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::fmt::Write as _;
use std::io::{Read, Write};

pub const REPORT_PREAMBLE: &str = "\
The following warnings were discovered during the build. These warnings are an
indication that the packages contain code that will become an error in a
future release of Rust. These warnings typically cover changes to close
soundness problems, unintended or undocumented behavior, or critical problems
that cannot be fixed in a backwards-compatible fashion, and are not expected
to be in wide use.

Each warning should contain a link for more information on what the warning
means and how to resolve it.
";

/// Current version of the on-disk format.
const ON_DISK_VERSION: u32 = 0;

/// The future incompatibility report, emitted by the compiler as a JSON message.
#[derive(serde::Deserialize)]
pub struct FutureIncompatReport {
    pub future_incompat_report: Vec<FutureBreakageItem>,
}

/// Structure used for collecting reports in-memory.
pub struct FutureIncompatReportPackage {
    pub package_id: PackageId,
    pub items: Vec<FutureBreakageItem>,
}

/// A single future-incompatible warning emitted by rustc.
#[derive(Serialize, Deserialize)]
pub struct FutureBreakageItem {
    /// The date at which this lint will become an error.
    /// Currently unused
    pub future_breakage_date: Option<String>,
    /// The original diagnostic emitted by the compiler
    pub diagnostic: Diagnostic,
}

/// A diagnostic emitted by the compiler as a JSON message.
/// We only care about the 'rendered' field
#[derive(Serialize, Deserialize)]
pub struct Diagnostic {
    pub rendered: String,
    pub level: String,
}

/// The filename in the top-level `target` directory where we store
/// the report
const FUTURE_INCOMPAT_FILE: &str = ".future-incompat-report.json";
/// Max number of reports to save on disk.
const MAX_REPORTS: usize = 5;

/// The structure saved to disk containing the reports.
#[derive(Serialize, Deserialize)]
pub struct OnDiskReports {
    /// A schema version number, to handle older cargo's from trying to read
    /// something that they don't understand.
    version: u32,
    /// The report ID to use for the next report to save.
    next_id: u32,
    /// Available reports.
    reports: Vec<OnDiskReport>,
}

/// A single report for a given compilation session.
#[derive(Serialize, Deserialize)]
struct OnDiskReport {
    /// Unique reference to the report for the `--id` CLI flag.
    id: u32,
    /// A message describing suggestions for fixing the
    /// reported issues
    suggestion_message: String,
    /// Report, suitable for printing to the console.
    /// Maps package names to the corresponding report
    /// We use a `BTreeMap` so that the iteration order
    /// is stable across multiple runs of `cargo`
    per_package: BTreeMap<String, String>,
}

impl Default for OnDiskReports {
    fn default() -> OnDiskReports {
        OnDiskReports {
            version: ON_DISK_VERSION,
            next_id: 1,
            reports: Vec::new(),
        }
    }
}

impl OnDiskReports {
    /// Saves a new report.
    pub fn save_report(
        mut self,
        ws: &Workspace<'_>,
        suggestion_message: String,
        per_package_reports: &[FutureIncompatReportPackage],
    ) {
        let report = OnDiskReport {
            id: self.next_id,
            suggestion_message,
            per_package: render_report(per_package_reports),
        };
        self.next_id += 1;
        self.reports.push(report);
        if self.reports.len() > MAX_REPORTS {
            self.reports.remove(0);
        }
        let on_disk = serde_json::to_vec(&self).unwrap();
        if let Err(e) = ws
            .target_dir()
            .open_rw(
                FUTURE_INCOMPAT_FILE,
                ws.config(),
                "Future incompatibility report",
            )
            .and_then(|file| {
                let mut file = file.file();
                file.set_len(0)?;
                file.write_all(&on_disk)?;
                Ok(())
            })
        {
            crate::display_warning_with_error(
                "failed to write on-disk future incompatible report",
                &e,
                &mut ws.config().shell(),
            );
        }
    }

    /// Loads the on-disk reports.
    pub fn load(ws: &Workspace<'_>) -> CargoResult<OnDiskReports> {
        let report_file = match ws.target_dir().open_ro(
            FUTURE_INCOMPAT_FILE,
            ws.config(),
            "Future incompatible report",
        ) {
            Ok(r) => r,
            Err(e) => {
                if let Some(io_err) = e.downcast_ref::<std::io::Error>() {
                    if io_err.kind() == std::io::ErrorKind::NotFound {
                        bail!("no reports are currently available");
                    }
                }
                return Err(e);
            }
        };

        let mut file_contents = String::new();
        report_file
            .file()
            .read_to_string(&mut file_contents)
            .with_context(|| "failed to read report")?;
        let on_disk_reports: OnDiskReports =
            serde_json::from_str(&file_contents).with_context(|| "failed to load report")?;
        if on_disk_reports.version != ON_DISK_VERSION {
            bail!("unable to read reports; reports were saved from a future version of Cargo");
        }
        Ok(on_disk_reports)
    }

    /// Returns the most recent report ID.
    pub fn last_id(&self) -> u32 {
        self.reports.last().map(|r| r.id).unwrap()
    }

    pub fn get_report(
        &self,
        id: u32,
        config: &Config,
        package: Option<&str>,
    ) -> CargoResult<String> {
        let report = self.reports.iter().find(|r| r.id == id).ok_or_else(|| {
            let available = iter_join(self.reports.iter().map(|r| r.id.to_string()), ", ");
            format_err!(
                "could not find report with ID {}\n\
                 Available IDs are: {}",
                id,
                available
            )
        })?;

        let mut to_display = report.suggestion_message.clone();
        to_display += "\n";

        let package_report = if let Some(package) = package {
            report
                .per_package
                .get(package)
                .ok_or_else(|| {
                    format_err!(
                        "could not find package with ID `{}`\n
                Available packages are: {}\n
                Omit the `--package` flag to display a report for all packages",
                        package,
                        iter_join(report.per_package.keys(), ", ")
                    )
                })?
                .to_string()
        } else {
            report
                .per_package
                .values()
                .cloned()
                .collect::<Vec<_>>()
                .join("\n")
        };
        to_display += &package_report;

        let shell = config.shell();

        let to_display = if shell.err_supports_color() && shell.out_supports_color() {
            to_display
        } else {
            strip_ansi_escapes::strip(&to_display)
                .map(|v| String::from_utf8(v).expect("utf8"))
                .expect("strip should never fail")
        };
        Ok(to_display)
    }
}

fn render_report(per_package_reports: &[FutureIncompatReportPackage]) -> BTreeMap<String, String> {
    let mut report: BTreeMap<String, String> = BTreeMap::new();
    for per_package in per_package_reports {
        let package_spec = format!(
            "{}:{}",
            per_package.package_id.name(),
            per_package.package_id.version()
        );
        let rendered = report.entry(package_spec).or_default();
        rendered.push_str(&format!(
            "The package `{}` currently triggers the following future incompatibility lints:\n",
            per_package.package_id
        ));
        for item in &per_package.items {
            rendered.extend(
                item.diagnostic
                    .rendered
                    .lines()
                    .map(|l| format!("> {}\n", l)),
            );
        }
    }
    report
}

/// Returns a user-readable message explaining which of
/// the packages in `package_ids` have updates available.
/// This is best-effort - if an error occurs, `None` will be returned.
fn get_updates(ws: &Workspace<'_>, package_ids: &BTreeSet<PackageId>) -> Option<String> {
    // This in general ignores all errors since this is opportunistic.
    let _lock = ws.config().acquire_package_cache_lock().ok()?;
    // Create a set of updated registry sources.
    let map = SourceConfigMap::new(ws.config()).ok()?;
    let package_ids: BTreeSet<_> = package_ids
        .iter()
        .filter(|pkg_id| pkg_id.source_id().is_registry())
        .collect();
    let source_ids: HashSet<_> = package_ids
        .iter()
        .map(|pkg_id| pkg_id.source_id())
        .collect();
    let mut sources: HashMap<_, _> = source_ids
        .into_iter()
        .filter_map(|sid| {
            let source = map.load(sid, &HashSet::new()).ok()?;
            Some((sid, source))
        })
        .collect();
    // Query the sources for new versions.
    let mut updates = String::new();
    for pkg_id in package_ids {
        let source = match sources.get_mut(&pkg_id.source_id()) {
            Some(s) => s,
            None => continue,
        };
        let dep = Dependency::parse(pkg_id.name(), None, pkg_id.source_id()).ok()?;
        let summaries = source.query_vec(&dep).ok()?;
        let mut updated_versions: Vec<_> = summaries
            .iter()
            .map(|summary| summary.version())
            .filter(|version| *version > pkg_id.version())
            .collect();
        updated_versions.sort();

        let updated_versions = iter_join(
            updated_versions
                .into_iter()
                .map(|version| version.to_string()),
            ", ",
        );

        if !updated_versions.is_empty() {
            writeln!(
                updates,
                "{} has the following newer versions available: {}",
                pkg_id, updated_versions
            )
            .unwrap();
        }
    }
    Some(updates)
}

/// Writes a future-incompat report to disk, using the per-package
/// reports gathered during the build. If requested by the user,
/// a message is also displayed in the build output.
pub fn save_and_display_report(
    bcx: &BuildContext<'_, '_>,
    per_package_future_incompat_reports: &[FutureIncompatReportPackage],
) {
    let should_display_message = match bcx.config.future_incompat_config() {
        Ok(config) => config.should_display_message(),
        Err(e) => {
            crate::display_warning_with_error(
                "failed to read future-incompat config from disk",
                &e,
                &mut bcx.config.shell(),
            );
            true
        }
    };

    if per_package_future_incompat_reports.is_empty() {
        // Explicitly passing a command-line flag overrides
        // `should_display_message` from the config file
        if bcx.build_config.future_incompat_report {
            drop(
                bcx.config
                    .shell()
                    .note("0 dependencies had future-incompatible warnings"),
            );
        }
        return;
    }

    let current_reports = match OnDiskReports::load(bcx.ws) {
        Ok(r) => r,
        Err(e) => {
            log::debug!(
                "saving future-incompatible reports failed to load current reports: {:?}",
                e
            );
            OnDiskReports::default()
        }
    };
    let report_id = current_reports.next_id;

    // Get a list of unique and sorted package name/versions.
    let package_ids: BTreeSet<_> = per_package_future_incompat_reports
        .iter()
        .map(|r| r.package_id)
        .collect();
    let package_vers: Vec<_> = package_ids.iter().map(|pid| pid.to_string()).collect();

    if should_display_message || bcx.build_config.future_incompat_report {
        drop(bcx.config.shell().warn(&format!(
            "the following packages contain code that will be rejected by a future \
             version of Rust: {}",
            package_vers.join(", ")
        )));
    }

    let updated_versions = get_updates(bcx.ws, &package_ids).unwrap_or(String::new());

    let update_message = if !updated_versions.is_empty() {
        format!(
            "
- Some affected dependencies have newer versions available.
You may want to consider updating them to a newer version to see if the issue has been fixed.

{updated_versions}\n",
            updated_versions = updated_versions
        )
    } else {
        String::new()
    };

    let upstream_info = package_ids
        .iter()
        .map(|package_id| {
            let manifest = bcx.packages.get_one(*package_id).unwrap().manifest();
            format!(
                "
  - {name}
  - Repository: {url}
  - Detailed warning command: `cargo report future-incompatibilities --id {id} --package {name}`",
                name = format!("{}:{}", package_id.name(), package_id.version()),
                url = manifest
                    .metadata()
                    .repository
                    .as_deref()
                    .unwrap_or("<not found>"),
                id = report_id,
            )
        })
        .collect::<Vec<_>>()
        .join("\n");

    let suggestion_message = format!(
        "
To solve this problem, you can try the following approaches:

{update_message}
- If the issue is not solved by updating the dependencies, a fix has to be
implemented by those dependencies. You can help with that by notifying the
maintainers of this problem (e.g. by creating a bug report) or by proposing a
fix to the maintainers (e.g. by creating a pull request):
{upstream_info}

- If waiting for an upstream fix is not an option, you can use the `[patch]`
section in `Cargo.toml` to use your own version of the dependency. For more
information, see:
https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html#the-patch-section
        ",
        upstream_info = upstream_info,
        update_message = update_message,
    );

    current_reports.save_report(
        bcx.ws,
        suggestion_message.clone(),
        per_package_future_incompat_reports,
    );

    if bcx.build_config.future_incompat_report {
        drop(bcx.config.shell().note(&suggestion_message));
        drop(bcx.config.shell().note(&format!(
            "this report can be shown with `cargo report \
             future-incompatibilities -Z future-incompat-report --id {}`",
            report_id
        )));
    } else if should_display_message {
        drop(bcx.config.shell().note(&format!(
            "to see what the problems were, use the option \
             `--future-incompat-report`, or run `cargo report \
             future-incompatibilities --id {}`",
            report_id
        )));
    }
}
<commit_msg>Remove `-Z future-incompat-report` from message displayed to user<commit_after>//! Support for future-incompatible warning reporting.

use crate::core::compiler::BuildContext;
use crate::core::{Dependency, PackageId, Workspace};
use crate::sources::SourceConfigMap;
use crate::util::{iter_join, CargoResult, Config};
use anyhow::{bail, format_err, Context};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::fmt::Write as _;
use std::io::{Read, Write};

pub const REPORT_PREAMBLE: &str = "\
The following warnings were discovered during the build. These warnings are an
indication that the packages contain code that will become an error in a
future release of Rust. These warnings typically cover changes to close
soundness problems, unintended or undocumented behavior, or critical problems
that cannot be fixed in a backwards-compatible fashion, and are not expected
to be in wide use.

Each warning should contain a link for more information on what the warning
means and how to resolve it.
";

/// Current version of the on-disk format.
const ON_DISK_VERSION: u32 = 0;

/// The future incompatibility report, emitted by the compiler as a JSON message.
#[derive(serde::Deserialize)]
pub struct FutureIncompatReport {
    pub future_incompat_report: Vec<FutureBreakageItem>,
}

/// Structure used for collecting reports in-memory.
pub struct FutureIncompatReportPackage {
    pub package_id: PackageId,
    pub items: Vec<FutureBreakageItem>,
}

/// A single future-incompatible warning emitted by rustc.
#[derive(Serialize, Deserialize)]
pub struct FutureBreakageItem {
    /// The date at which this lint will become an error.
    /// Currently unused
    pub future_breakage_date: Option<String>,
    /// The original diagnostic emitted by the compiler
    pub diagnostic: Diagnostic,
}

/// A diagnostic emitted by the compiler as a JSON message.
/// We only care about the 'rendered' field
#[derive(Serialize, Deserialize)]
pub struct Diagnostic {
    pub rendered: String,
    pub level: String,
}

/// The filename in the top-level `target` directory where we store
/// the report
const FUTURE_INCOMPAT_FILE: &str = ".future-incompat-report.json";
/// Max number of reports to save on disk.
const MAX_REPORTS: usize = 5;

/// The structure saved to disk containing the reports.
#[derive(Serialize, Deserialize)]
pub struct OnDiskReports {
    /// A schema version number, to handle older cargo's from trying to read
    /// something that they don't understand.
    version: u32,
    /// The report ID to use for the next report to save.
    next_id: u32,
    /// Available reports.
    reports: Vec<OnDiskReport>,
}

/// A single report for a given compilation session.
#[derive(Serialize, Deserialize)]
struct OnDiskReport {
    /// Unique reference to the report for the `--id` CLI flag.
    id: u32,
    /// A message describing suggestions for fixing the
    /// reported issues
    suggestion_message: String,
    /// Report, suitable for printing to the console.
    /// Maps package names to the corresponding report
    /// We use a `BTreeMap` so that the iteration order
    /// is stable across multiple runs of `cargo`
    per_package: BTreeMap<String, String>,
}

impl Default for OnDiskReports {
    fn default() -> OnDiskReports {
        OnDiskReports {
            version: ON_DISK_VERSION,
            next_id: 1,
            reports: Vec::new(),
        }
    }
}

impl OnDiskReports {
    /// Saves a new report.
    pub fn save_report(
        mut self,
        ws: &Workspace<'_>,
        suggestion_message: String,
        per_package_reports: &[FutureIncompatReportPackage],
    ) {
        let report = OnDiskReport {
            id: self.next_id,
            suggestion_message,
            per_package: render_report(per_package_reports),
        };
        self.next_id += 1;
        self.reports.push(report);
        if self.reports.len() > MAX_REPORTS {
            self.reports.remove(0);
        }
        let on_disk = serde_json::to_vec(&self).unwrap();
        if let Err(e) = ws
            .target_dir()
            .open_rw(
                FUTURE_INCOMPAT_FILE,
                ws.config(),
                "Future incompatibility report",
            )
            .and_then(|file| {
                let mut file = file.file();
                file.set_len(0)?;
                file.write_all(&on_disk)?;
                Ok(())
            })
        {
            crate::display_warning_with_error(
                "failed to write on-disk future incompatible report",
                &e,
                &mut ws.config().shell(),
            );
        }
    }

    /// Loads the on-disk reports.
    pub fn load(ws: &Workspace<'_>) -> CargoResult<OnDiskReports> {
        let report_file = match ws.target_dir().open_ro(
            FUTURE_INCOMPAT_FILE,
            ws.config(),
            "Future incompatible report",
        ) {
            Ok(r) => r,
            Err(e) => {
                if let Some(io_err) = e.downcast_ref::<std::io::Error>() {
                    if io_err.kind() == std::io::ErrorKind::NotFound {
                        bail!("no reports are currently available");
                    }
                }
                return Err(e);
            }
        };

        let mut file_contents = String::new();
        report_file
            .file()
            .read_to_string(&mut file_contents)
            .with_context(|| "failed to read report")?;
        let on_disk_reports: OnDiskReports =
            serde_json::from_str(&file_contents).with_context(|| "failed to load report")?;
        if on_disk_reports.version != ON_DISK_VERSION {
            bail!("unable to read reports; reports were saved from a future version of Cargo");
        }
        Ok(on_disk_reports)
    }

    /// Returns the most recent report ID.
    pub fn last_id(&self) -> u32 {
        self.reports.last().map(|r| r.id).unwrap()
    }

    pub fn get_report(
        &self,
        id: u32,
        config: &Config,
        package: Option<&str>,
    ) -> CargoResult<String> {
        let report = self.reports.iter().find(|r| r.id == id).ok_or_else(|| {
            let available = iter_join(self.reports.iter().map(|r| r.id.to_string()), ", ");
            format_err!(
                "could not find report with ID {}\n\
                 Available IDs are: {}",
                id,
                available
            )
        })?;

        let mut to_display = report.suggestion_message.clone();
        to_display += "\n";

        let package_report = if let Some(package) = package {
            report
                .per_package
                .get(package)
                .ok_or_else(|| {
                    format_err!(
                        "could not find package with ID `{}`\n
                Available packages are: {}\n
                Omit the `--package` flag to display a report for all packages",
                        package,
                        iter_join(report.per_package.keys(), ", ")
                    )
                })?
                .to_string()
        } else {
            report
                .per_package
                .values()
                .cloned()
                .collect::<Vec<_>>()
                .join("\n")
        };
        to_display += &package_report;

        let shell = config.shell();

        let to_display = if shell.err_supports_color() && shell.out_supports_color() {
            to_display
        } else {
            strip_ansi_escapes::strip(&to_display)
                .map(|v| String::from_utf8(v).expect("utf8"))
                .expect("strip should never fail")
        };
        Ok(to_display)
    }
}

fn render_report(per_package_reports: &[FutureIncompatReportPackage]) -> BTreeMap<String, String> {
    let mut report: BTreeMap<String, String> = BTreeMap::new();
    for per_package in per_package_reports {
        let package_spec = format!(
            "{}:{}",
            per_package.package_id.name(),
            per_package.package_id.version()
        );
        let rendered = report.entry(package_spec).or_default();
        rendered.push_str(&format!(
            "The package `{}` currently triggers the following future incompatibility lints:\n",
            per_package.package_id
        ));
        for item in &per_package.items {
            rendered.extend(
                item.diagnostic
                    .rendered
                    .lines()
                    .map(|l| format!("> {}\n", l)),
            );
        }
    }
    report
}

/// Returns a user-readable message explaining which of
/// the packages in `package_ids` have updates available.
/// This is best-effort - if an error occurs, `None` will be returned.
fn get_updates(ws: &Workspace<'_>, package_ids: &BTreeSet<PackageId>) -> Option<String> {
    // This in general ignores all errors since this is opportunistic.
    let _lock = ws.config().acquire_package_cache_lock().ok()?;
    // Create a set of updated registry sources.
    let map = SourceConfigMap::new(ws.config()).ok()?;
    let package_ids: BTreeSet<_> = package_ids
        .iter()
        .filter(|pkg_id| pkg_id.source_id().is_registry())
        .collect();
    let source_ids: HashSet<_> = package_ids
        .iter()
        .map(|pkg_id| pkg_id.source_id())
        .collect();
    let mut sources: HashMap<_, _> = source_ids
        .into_iter()
        .filter_map(|sid| {
            let source = map.load(sid, &HashSet::new()).ok()?;
            Some((sid, source))
        })
        .collect();
    // Query the sources for new versions.
    let mut updates = String::new();
    for pkg_id in package_ids {
        let source = match sources.get_mut(&pkg_id.source_id()) {
            Some(s) => s,
            None => continue,
        };
        let dep = Dependency::parse(pkg_id.name(), None, pkg_id.source_id()).ok()?;
        let summaries = source.query_vec(&dep).ok()?;
        let mut updated_versions: Vec<_> = summaries
            .iter()
            .map(|summary| summary.version())
            .filter(|version| *version > pkg_id.version())
            .collect();
        updated_versions.sort();

        let updated_versions = iter_join(
            updated_versions
                .into_iter()
                .map(|version| version.to_string()),
            ", ",
        );

        if !updated_versions.is_empty() {
            writeln!(
                updates,
                "{} has the following newer versions available: {}",
                pkg_id, updated_versions
            )
            .unwrap();
        }
    }
    Some(updates)
}

/// Writes a future-incompat report to disk, using the per-package
/// reports gathered during the build. If requested by the user,
/// a message is also displayed in the build output.
pub fn save_and_display_report(
    bcx: &BuildContext<'_, '_>,
    per_package_future_incompat_reports: &[FutureIncompatReportPackage],
) {
    let should_display_message = match bcx.config.future_incompat_config() {
        Ok(config) => config.should_display_message(),
        Err(e) => {
            crate::display_warning_with_error(
                "failed to read future-incompat config from disk",
                &e,
                &mut bcx.config.shell(),
            );
            true
        }
    };

    if per_package_future_incompat_reports.is_empty() {
        // Explicitly passing a command-line flag overrides
        // `should_display_message` from the config file
        if bcx.build_config.future_incompat_report {
            drop(
                bcx.config
                    .shell()
                    .note("0 dependencies had future-incompatible warnings"),
            );
        }
        return;
    }

    let current_reports = match OnDiskReports::load(bcx.ws) {
        Ok(r) => r,
        Err(e) => {
            log::debug!(
                "saving future-incompatible reports failed to load current reports: {:?}",
                e
            );
            OnDiskReports::default()
        }
    };
    let report_id = current_reports.next_id;

    // Get a list of unique and sorted package name/versions.
    let package_ids: BTreeSet<_> = per_package_future_incompat_reports
        .iter()
        .map(|r| r.package_id)
        .collect();
    let package_vers: Vec<_> = package_ids.iter().map(|pid| pid.to_string()).collect();

    if should_display_message || bcx.build_config.future_incompat_report {
        drop(bcx.config.shell().warn(&format!(
            "the following packages contain code that will be rejected by a future \
             version of Rust: {}",
            package_vers.join(", ")
        )));
    }

    let updated_versions = get_updates(bcx.ws, &package_ids).unwrap_or(String::new());

    let update_message = if !updated_versions.is_empty() {
        format!(
            "
- Some affected dependencies have newer versions available.
You may want to consider updating them to a newer version to see if the issue has been fixed.

{updated_versions}\n",
            updated_versions = updated_versions
        )
    } else {
        String::new()
    };

    let upstream_info = package_ids
        .iter()
        .map(|package_id| {
            let manifest = bcx.packages.get_one(*package_id).unwrap().manifest();
            format!(
                "
  - {name}
  - Repository: {url}
  - Detailed warning command: `cargo report future-incompatibilities --id {id} --package {name}`",
                name = format!("{}:{}", package_id.name(), package_id.version()),
                url = manifest
                    .metadata()
                    .repository
                    .as_deref()
                    .unwrap_or("<not found>"),
                id = report_id,
            )
        })
        .collect::<Vec<_>>()
        .join("\n");

    let suggestion_message = format!(
        "
To solve this problem, you can try the following approaches:

{update_message}
- If the issue is not solved by updating the dependencies, a fix has to be
implemented by those dependencies. You can help with that by notifying the
maintainers of this problem (e.g. by creating a bug report) or by proposing a
fix to the maintainers (e.g. by creating a pull request):
{upstream_info}

- If waiting for an upstream fix is not an option, you can use the `[patch]`
section in `Cargo.toml` to use your own version of the dependency. For more
information, see:
https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html#the-patch-section
        ",
        upstream_info = upstream_info,
        update_message = update_message,
    );

    current_reports.save_report(
        bcx.ws,
        suggestion_message.clone(),
        per_package_future_incompat_reports,
    );

    if bcx.build_config.future_incompat_report {
        drop(bcx.config.shell().note(&suggestion_message));
        drop(bcx.config.shell().note(&format!(
            "this report can be shown with `cargo report \
             future-incompatibilities --id {}`",
            report_id
        )));
    } else if should_display_message {
        drop(bcx.config.shell().note(&format!(
            "to see what the problems were, use the option \
             `--future-incompat-report`, or run `cargo report \
             future-incompatibilities --id {}`",
            report_id
        )));
    }
}
<|endoftext|>


<commit_before>extern crate clap;
extern crate clap_test;

use clap::{App, Arg, ArgGroup, ErrorKind};

#[test]
fn required_group_missing_arg() {
    let result = App::new("group")
        .args_from_usage("-f, --flag 'some flag'
                          -c, --color 'some other flag'")
        .group(ArgGroup::with_name("req")
            .args(&["flag", "color"])
            .required(true))
        .get_matches_from_safe(vec![""]);
    assert!(result.is_err());
    let err = result.err().unwrap();
    assert_eq!(err.kind, ErrorKind::MissingRequiredArgument);
}

#[test]
fn group_single_value() {
    let m = App::new("group")
        .args_from_usage("-f, --flag 'some flag'
                          -c, --color [color] 'some option'")
        .group(ArgGroup::with_name("grp")
            .args(&["flag", "color"]))
        .get_matches_from(vec!["", "-c", "blue"]);
    assert!(m.is_present("grp"));
    assert_eq!(m.value_of("grp").unwrap(), "blue");
}

#[test]
fn group_single_flag() {
    let m = App::new("group")
        .args_from_usage("-f, --flag 'some flag'
                          -c, --color [color] 'some option'")
        .group(ArgGroup::with_name("grp")
            .args(&["flag", "color"]))
        .get_matches_from(vec!["", "-f"]);
    assert!(m.is_present("grp"));
    assert!(m.value_of("grp").is_none());
}

#[test]
fn group_empty() {
    let m = App::new("group")
        .args_from_usage("-f, --flag 'some flag'
                          -c, --color [color] 'some option'")
        .group(ArgGroup::with_name("grp")
            .args(&["flag", "color"]))
        .get_matches_from(vec![""]);
    assert!(!m.is_present("grp"));
    assert!(m.value_of("grp").is_none());
}

#[test]
fn group_reqired_flags_empty() {
    let result = App::new("group")
        .args_from_usage("-f, --flag 'some flag'
                          -c, --color 'some option'")
        .group(ArgGroup::with_name("grp")
            .required(true)
            .args(&["flag", "color"]))
        .get_matches_from_safe(vec![""]);
    assert!(result.is_err());
    let err = result.err().unwrap();
    assert_eq!(err.kind, ErrorKind::MissingRequiredArgument);
}

#[test]
fn group_multi_value_single_arg() {
    let m = App::new("group")
        .args_from_usage("-f, --flag 'some flag'
                          -c, --color [color]... 'some option'")
        .group(ArgGroup::with_name("grp")
            .args(&["flag", "color"]))
        .get_matches_from(vec!["", "-c", "blue", "red", "green"]);
    assert!(m.is_present("grp"));
    assert_eq!(&*m.values_of("grp").unwrap().collect::<Vec<_>>(), &["blue", "red", "green"]);
}

#[test]
fn empty_group() {
    let r = App::new("empty_group")
        .arg(Arg::from_usage("-f, --flag 'some flag'"))
        .group(ArgGroup::with_name("vers")
            .required(true))
        .get_matches_from_safe(vec!["empty_prog"]);
    assert!(r.is_err());
    let err = r.err().unwrap();
    assert_eq!(err.kind, ErrorKind::MissingRequiredArgument);
}

#[test]
fn req_group_usage_string() {
    let app = App::new("req_group")
        .args_from_usage("[base] 'Base commit'
                          -d, --delete 'Remove the base commit information'")
        .group(ArgGroup::with_name("base_or_delete")
            .args(&["base", "delete"])
            .required(true));

    clap_test::check_err_output(app, "clap-test",
"error: The following required arguments were not provided:
    <base|--delete>

USAGE:
    clap-test [FLAGS] <base|--delete> [ARGS]

For more information try --help", true);

}
<commit_msg>test: adds test for new group deduping<commit_after>extern crate clap;
extern crate clap_test;

use clap::{App, Arg, ArgGroup, ErrorKind};

#[test]
fn required_group_missing_arg() {
    let result = App::new("group")
        .args_from_usage("-f, --flag 'some flag'
                          -c, --color 'some other flag'")
        .group(ArgGroup::with_name("req")
            .args(&["flag", "color"])
            .required(true))
        .get_matches_from_safe(vec![""]);
    assert!(result.is_err());
    let err = result.err().unwrap();
    assert_eq!(err.kind, ErrorKind::MissingRequiredArgument);
}

#[test]
fn group_single_value() {
    let m = App::new("group")
        .args_from_usage("-f, --flag 'some flag'
                          -c, --color [color] 'some option'")
        .group(ArgGroup::with_name("grp")
            .args(&["flag", "color"]))
        .get_matches_from(vec!["", "-c", "blue"]);
    assert!(m.is_present("grp"));
    assert_eq!(m.value_of("grp").unwrap(), "blue");
}

#[test]
fn group_single_flag() {
    let m = App::new("group")
        .args_from_usage("-f, --flag 'some flag'
                          -c, --color [color] 'some option'")
        .group(ArgGroup::with_name("grp")
            .args(&["flag", "color"]))
        .get_matches_from(vec!["", "-f"]);
    assert!(m.is_present("grp"));
    assert!(m.value_of("grp").is_none());
}

#[test]
fn group_empty() {
    let m = App::new("group")
        .args_from_usage("-f, --flag 'some flag'
                          -c, --color [color] 'some option'")
        .group(ArgGroup::with_name("grp")
            .args(&["flag", "color"]))
        .get_matches_from(vec![""]);
    assert!(!m.is_present("grp"));
    assert!(m.value_of("grp").is_none());
}

#[test]
fn group_reqired_flags_empty() {
    let result = App::new("group")
        .args_from_usage("-f, --flag 'some flag'
                          -c, --color 'some option'")
        .group(ArgGroup::with_name("grp")
            .required(true)
            .args(&["flag", "color"]))
        .get_matches_from_safe(vec![""]);
    assert!(result.is_err());
    let err = result.err().unwrap();
    assert_eq!(err.kind, ErrorKind::MissingRequiredArgument);
}

#[test]
fn group_multi_value_single_arg() {
    let m = App::new("group")
        .args_from_usage("-f, --flag 'some flag'
                          -c, --color [color]... 'some option'")
        .group(ArgGroup::with_name("grp")
            .args(&["flag", "color"]))
        .get_matches_from(vec!["", "-c", "blue", "red", "green"]);
    assert!(m.is_present("grp"));
    assert_eq!(&*m.values_of("grp").unwrap().collect::<Vec<_>>(), &["blue", "red", "green"]);
}

#[test]
fn empty_group() {
    let r = App::new("empty_group")
        .arg(Arg::from_usage("-f, --flag 'some flag'"))
        .group(ArgGroup::with_name("vers")
            .required(true))
        .get_matches_from_safe(vec!["empty_prog"]);
    assert!(r.is_err());
    let err = r.err().unwrap();
    assert_eq!(err.kind, ErrorKind::MissingRequiredArgument);
}

#[test]
fn req_group_usage_string() {
    let app = App::new("req_group")
        .args_from_usage("[base] 'Base commit'
                          -d, --delete 'Remove the base commit information'")
        .group(ArgGroup::with_name("base_or_delete")
            .args(&["base", "delete"])
            .required(true));

    clap_test::check_err_output(app, "clap-test",
"error: The following required arguments were not provided:
    <base|--delete>

USAGE:
    clap-test [FLAGS] <base|--delete> [ARGS]

For more information try --help", true);

}

#[test]
fn req_group_with_conflict_usage_string() {
    let app = App::new("req_group")
        .arg(Arg::from_usage("[base] 'Base commit'").conflicts_with("delete"))
        .arg(Arg::from_usage("-d, --delete 'Remove the base commit information'"))
        .group(ArgGroup::with_name("base_or_delete")
            .args(&["base", "delete"])
            .required(true));

    clap_test::check_err_output(app, "clap-test --delete base",
"error: The argument '--delete' cannot be used with 'base'

USAGE:
    clap-test <base|--delete>

For more information try --help", true);

}

#[test]
fn req_group_with_conflict_rev_usage_string() {
    let app = App::new("req_group")
        .arg(Arg::from_usage("[base] 'Base commit'").conflicts_with("delete"))
        .arg(Arg::from_usage("-d, --delete 'Remove the base commit information'"))
        .group(ArgGroup::with_name("base_or_delete")
            .args(&["base", "delete"])
            .required(true));

    clap_test::check_err_output(app, "clap-test --delete base",
"error: The argument '--delete' cannot be used with 'base'

USAGE:
    clap-test <base|--delete>

For more information try --help", true);

}
<|endoftext|>


<commit_before>#![cfg(not(feature = "preserve_order"))]

extern crate serde;
extern crate serde_json;

use serde_json::{Deserializer, Map, Value};

#[test]
fn test_json_stream_newlines() {
    let data = "{\"x\":39} {\"x\":40}{\"x\":41}\n{\"x\":42}";
    let mut parsed = Deserializer::from_str(data).into_iter::<Value>();

    assert_eq!(parsed.next().unwrap().ok().unwrap().pointer("/x").unwrap(), 39);
    assert_eq!(parsed.next().unwrap().ok().unwrap().pointer("/x").unwrap(), 40);
    assert_eq!(parsed.next().unwrap().ok().unwrap().pointer("/x").unwrap(), 41);
    assert_eq!(parsed.next().unwrap().ok().unwrap().pointer("/x").unwrap(), 42);
    assert!(parsed.next().is_none());
}

#[test]
fn test_json_stream_trailing_whitespaces() {
    let data = "{\"x\":42} \t\n";
    let mut parsed = Deserializer::from_str(data).into_iter::<Value>();

    assert_eq!(parsed.next().unwrap().ok().unwrap().pointer("/x").unwrap(), 42);
    assert!(parsed.next().is_none());
}

#[test]
fn test_json_stream_truncated() {
    let data = "{\"x\":40}\n{\"x\":";
    let mut parsed = Deserializer::from_str(data).into_iter::<Value>();

    assert_eq!(parsed.next().unwrap().ok().unwrap().pointer("/x").unwrap(), 40);
    assert!(parsed.next().unwrap().is_err());
    assert!(parsed.next().is_none());
}

#[test]
fn test_json_stream_empty() {
    let data = "";
    let mut parsed = Deserializer::from_str(data).into_iter::<Value>();

    assert!(parsed.next().is_none());
}

#[test]
fn test_json_stream_primitive() {
    let data = "{} true";
    let mut parsed = Deserializer::from_str(data).into_iter::<Value>();

    let first = parsed.next().unwrap().unwrap();
    assert_eq!(first, Value::Object(Map::new()));

    let second = parsed.next().unwrap().unwrap_err();
    assert_eq!(second.to_string(), "expected `{` or `[` at line 1 column 4");
}
<commit_msg>Run the existing tests against every type of stream<commit_after>#![cfg(not(feature = "preserve_order"))]

extern crate serde;
extern crate serde_json;

use serde_json::{Deserializer, Map, Value};

macro_rules! test_stream {
    ($data:expr, $ty:ty, |$stream:ident| $test:block) => {
        {
            let de = Deserializer::from_str($data);
            let mut $stream = de.into_iter::<$ty>();
            $test
        }
        {
            let de = Deserializer::from_slice($data.as_bytes());
            let mut $stream = de.into_iter::<$ty>();
            $test
        }
        {
            let de = Deserializer::from_iter($data.bytes().map(Ok));
            let mut $stream = de.into_iter::<$ty>();
            $test
        }
        {
            let mut bytes = $data.as_bytes();
            let de = Deserializer::from_reader(&mut bytes);
            let mut $stream = de.into_iter::<$ty>();
            $test
        }
    }
}

#[test]
fn test_json_stream_newlines() {
    let data = "{\"x\":39} {\"x\":40}{\"x\":41}\n{\"x\":42}";

    test_stream!(data, Value, |stream| {
        assert_eq!(stream.next().unwrap().ok().unwrap().pointer("/x").unwrap(), 39);
        assert_eq!(stream.next().unwrap().ok().unwrap().pointer("/x").unwrap(), 40);
        assert_eq!(stream.next().unwrap().ok().unwrap().pointer("/x").unwrap(), 41);
        assert_eq!(stream.next().unwrap().ok().unwrap().pointer("/x").unwrap(), 42);
        assert!(stream.next().is_none());
    });
}

#[test]
fn test_json_stream_trailing_whitespaces() {
    let data = "{\"x\":42} \t\n";

    test_stream!(data, Value, |stream| {
        assert_eq!(stream.next().unwrap().ok().unwrap().pointer("/x").unwrap(), 42);
        assert!(stream.next().is_none());
    });
}

#[test]
fn test_json_stream_truncated() {
    let data = "{\"x\":40}\n{\"x\":";

    test_stream!(data, Value, |stream| {
        assert_eq!(stream.next().unwrap().ok().unwrap().pointer("/x").unwrap(), 40);
        assert!(stream.next().unwrap().is_err());
        assert!(stream.next().is_none());
    });
}

#[test]
fn test_json_stream_empty() {
    let data = "";

    test_stream!(data, Value, |stream| {
        assert!(stream.next().is_none());
    });
}

#[test]
fn test_json_stream_primitive() {
    let data = "{} true";

    test_stream!(data, Value, |stream| {
        let first = stream.next().unwrap().unwrap();
        assert_eq!(first, Value::Object(Map::new()));

        let second = stream.next().unwrap().unwrap_err();
        assert_eq!(second.to_string(), "expected `{` or `[` at line 1 column 4");
    });
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Timing functions.

use std_time::precise_time_ns;
use collections::treemap::TreeMap;
use std::comm::{Sender, channel, Receiver};
use std::f64;
use std::iter::AdditiveIterator;
use std::io::timer::sleep;
use task::{spawn_named};

// front-end representation of the profiler used to communicate with the profiler
#[deriving(Clone)]
pub struct ProfilerChan(pub Sender<ProfilerMsg>);

impl ProfilerChan {
    pub fn send(&self, msg: ProfilerMsg) {
        let ProfilerChan(ref c) = *self;
        c.send(msg);
    }
}

pub enum ProfilerMsg {
    /// Normal message used for reporting time
    TimeMsg(ProfilerCategory, f64),
    /// Message used to force print the profiling metrics
    PrintMsg,
    /// Tells the profiler to shut down.
    ExitMsg,
}

#[repr(u32)]
#[deriving(Eq, Clone, Ord, TotalEq, TotalOrd)]
pub enum ProfilerCategory {
    CompositingCategory,
    LayoutQueryCategory,
    LayoutPerformCategory,
    LayoutStyleRecalcCategory,
    LayoutSelectorMatchCategory,
    LayoutTreeBuilderCategory,
    LayoutDamagePropagateCategory,
    LayoutMainCategory,
    LayoutParallelWarmupCategory,
    LayoutShapingCategory,
    LayoutDispListBuildCategory,
    GfxRegenAvailableFontsCategory,
    RenderingDrawingCategory,
    RenderingPrepBuffCategory,
    RenderingCategory,
    // FIXME(rust#8803): workaround for lack of CTFE function on enum types to return length
    NumBuckets,
}

impl ProfilerCategory {
    // convenience function to not have to cast every time
    pub fn num_buckets() -> uint {
        NumBuckets as uint
    }

    // enumeration of all ProfilerCategory types
    fn empty_buckets() -> ProfilerBuckets {
        let mut buckets = TreeMap::new();
        buckets.insert(CompositingCategory, vec!());
        buckets.insert(LayoutQueryCategory, vec!());
        buckets.insert(LayoutPerformCategory, vec!());
        buckets.insert(LayoutStyleRecalcCategory, vec!());
        buckets.insert(LayoutSelectorMatchCategory, vec!());
        buckets.insert(LayoutTreeBuilderCategory, vec!());
        buckets.insert(LayoutMainCategory, vec!());
        buckets.insert(LayoutParallelWarmupCategory, vec!());
        buckets.insert(LayoutShapingCategory, vec!());
        buckets.insert(LayoutDamagePropagateCategory, vec!());
        buckets.insert(LayoutDispListBuildCategory, vec!());
        buckets.insert(GfxRegenAvailableFontsCategory, vec!());
        buckets.insert(RenderingDrawingCategory, vec!());
        buckets.insert(RenderingPrepBuffCategory, vec!());
        buckets.insert(RenderingCategory, vec!());

        buckets
    }

    // some categories are subcategories of LayoutPerformCategory
    // and should be printed to indicate this
    pub fn format(self) -> ~str {
        let padding = match self {
            LayoutStyleRecalcCategory |
            LayoutMainCategory |
            LayoutDispListBuildCategory |
            LayoutShapingCategory |
            LayoutDamagePropagateCategory => "+ ",
            LayoutParallelWarmupCategory |
            LayoutSelectorMatchCategory |
            LayoutTreeBuilderCategory => "| + ",
            _ => ""
        };
        format!("{:s}{:?}", padding, self)
    }
}

type ProfilerBuckets = TreeMap<ProfilerCategory, Vec<f64>>;

// back end of the profiler that handles data aggregation and performance metrics
pub struct Profiler {
    pub port: Receiver<ProfilerMsg>,
    buckets: ProfilerBuckets,
    pub last_msg: Option<ProfilerMsg>,
}

impl Profiler {
    pub fn create(period: Option<f64>) -> ProfilerChan {
        let (chan, port) = channel();
        match period {
            Some(period) => {
                let period = (period * 1000f64) as u64;
                let chan = chan.clone();
                spawn_named("Profiler timer", proc() {
                    loop {
                        sleep(period);
                        if !chan.try_send(PrintMsg) {
                            break;
                        }
                    }
                });
                // Spawn the profiler
                spawn_named("Profiler", proc() {
                    let mut profiler = Profiler::new(port);
                    profiler.start();
                });
            }
            None => {
                // no-op to handle profiler messages when the profiler is inactive
                spawn_named("Profiler", proc() {
                    loop {
                        match port.recv_opt() {
                            None | Some(ExitMsg) => break,
                            _ => {}
                        }
                    }
                });
            }
        }

        ProfilerChan(chan)
    }

    pub fn new(port: Receiver<ProfilerMsg>) -> Profiler {
        Profiler {
            port: port,
            buckets: ProfilerCategory::empty_buckets(),
            last_msg: None,
        }
    }

    pub fn start(&mut self) {
        loop {
            let msg = self.port.recv_opt();
            match msg {
               Some(msg) => {
                   if !self.handle_msg(msg) {
                       break
                   }
               }
               None => break
            }
        }
    }

    fn handle_msg(&mut self, msg: ProfilerMsg) -> bool {
        match msg {
            TimeMsg(category, t) => self.buckets.find_mut(&category).unwrap().push(t),
            PrintMsg => match self.last_msg {
                // only print if more data has arrived since the last printout
                Some(TimeMsg(..)) => self.print_buckets(),
                _ => ()
            },
            ExitMsg => return false,
        };
        self.last_msg = Some(msg);
        true
    }

    fn print_buckets(&mut self) {
        println!("{:39s} {:15s} {:15s} {:15s} {:15s} {:15s}",
                 "_category_", "_mean (ms)_", "_median (ms)_",
                 "_min (ms)_", "_max (ms)_", "_bucket size_");
        for (category, data) in self.buckets.iter() {
            // FIXME(XXX): TreeMap currently lacks mut_iter()
            let mut data = data.clone();
            data.sort_by(|a, b| {
                if a < b {
                    Less
                } else {
                    Greater
                }
            });
            let data_len = data.len();
            if data_len > 0 {
                let (mean, median, min, max) =
                    (data.iter().map(|&x|x).sum() / (data_len as f64),
                     *data.get(data_len / 2),
                     data.iter().fold(f64::INFINITY, |a, &b| a.min(b)),
                     data.iter().fold(-f64::INFINITY, |a, &b| a.max(b)));
                println!("{:-35s}: {:15.4f} {:15.4f} {:15.4f} {:15.4f} {:15u}",
                         category.format(), mean, median, min, max, data_len);
            }
        }
        println!("");
    }
}

pub fn profile<T>(category: ProfilerCategory,
                  profiler_chan: ProfilerChan,
                  callback: || -> T)
                  -> T {
    let start_time = precise_time_ns();
    let val = callback();
    let end_time = precise_time_ns();
    let ms = (end_time - start_time) as f64 / 1000000f64;
    profiler_chan.send(TimeMsg(category, ms));
    return val;
}

pub fn time<T>(msg: &str, callback: || -> T) -> T{
    let start_time = precise_time_ns();
    let val = callback();
    let end_time = precise_time_ns();
    let ms = (end_time - start_time) as f64 / 1000000f64;
    if ms >= 5f64 {
        debug!("{:s} took {} ms", msg, ms);
    }
    return val;
}

// ensure that the order of the buckets matches the order of the enum categories
#[test]
fn check_order() {
    let buckets = ProfilerCategory::empty_buckets();
    assert!(buckets.len() == NumBuckets as uint);
}
<commit_msg>auto merge of #2459 : zwarich/servo/tree-map-rev-iter, r=jdm<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Timing functions.

use std_time::precise_time_ns;
use collections::treemap::TreeMap;
use std::comm::{Sender, channel, Receiver};
use std::f64;
use std::iter::AdditiveIterator;
use std::io::timer::sleep;
use task::{spawn_named};

// front-end representation of the profiler used to communicate with the profiler
#[deriving(Clone)]
pub struct ProfilerChan(pub Sender<ProfilerMsg>);

impl ProfilerChan {
    pub fn send(&self, msg: ProfilerMsg) {
        let ProfilerChan(ref c) = *self;
        c.send(msg);
    }
}

pub enum ProfilerMsg {
    /// Normal message used for reporting time
    TimeMsg(ProfilerCategory, f64),
    /// Message used to force print the profiling metrics
    PrintMsg,
    /// Tells the profiler to shut down.
    ExitMsg,
}

#[repr(u32)]
#[deriving(Eq, Clone, Ord, TotalEq, TotalOrd)]
pub enum ProfilerCategory {
    CompositingCategory,
    LayoutQueryCategory,
    LayoutPerformCategory,
    LayoutStyleRecalcCategory,
    LayoutSelectorMatchCategory,
    LayoutTreeBuilderCategory,
    LayoutDamagePropagateCategory,
    LayoutMainCategory,
    LayoutParallelWarmupCategory,
    LayoutShapingCategory,
    LayoutDispListBuildCategory,
    GfxRegenAvailableFontsCategory,
    RenderingDrawingCategory,
    RenderingPrepBuffCategory,
    RenderingCategory,
    // FIXME(rust#8803): workaround for lack of CTFE function on enum types to return length
    NumBuckets,
}

impl ProfilerCategory {
    // convenience function to not have to cast every time
    pub fn num_buckets() -> uint {
        NumBuckets as uint
    }

    // enumeration of all ProfilerCategory types
    fn empty_buckets() -> ProfilerBuckets {
        let mut buckets = TreeMap::new();
        buckets.insert(CompositingCategory, vec!());
        buckets.insert(LayoutQueryCategory, vec!());
        buckets.insert(LayoutPerformCategory, vec!());
        buckets.insert(LayoutStyleRecalcCategory, vec!());
        buckets.insert(LayoutSelectorMatchCategory, vec!());
        buckets.insert(LayoutTreeBuilderCategory, vec!());
        buckets.insert(LayoutMainCategory, vec!());
        buckets.insert(LayoutParallelWarmupCategory, vec!());
        buckets.insert(LayoutShapingCategory, vec!());
        buckets.insert(LayoutDamagePropagateCategory, vec!());
        buckets.insert(LayoutDispListBuildCategory, vec!());
        buckets.insert(GfxRegenAvailableFontsCategory, vec!());
        buckets.insert(RenderingDrawingCategory, vec!());
        buckets.insert(RenderingPrepBuffCategory, vec!());
        buckets.insert(RenderingCategory, vec!());

        buckets
    }

    // some categories are subcategories of LayoutPerformCategory
    // and should be printed to indicate this
    pub fn format(self) -> ~str {
        let padding = match self {
            LayoutStyleRecalcCategory |
            LayoutMainCategory |
            LayoutDispListBuildCategory |
            LayoutShapingCategory |
            LayoutDamagePropagateCategory => "+ ",
            LayoutParallelWarmupCategory |
            LayoutSelectorMatchCategory |
            LayoutTreeBuilderCategory => "| + ",
            _ => ""
        };
        format!("{:s}{:?}", padding, self)
    }
}

type ProfilerBuckets = TreeMap<ProfilerCategory, Vec<f64>>;

// back end of the profiler that handles data aggregation and performance metrics
pub struct Profiler {
    pub port: Receiver<ProfilerMsg>,
    buckets: ProfilerBuckets,
    pub last_msg: Option<ProfilerMsg>,
}

impl Profiler {
    pub fn create(period: Option<f64>) -> ProfilerChan {
        let (chan, port) = channel();
        match period {
            Some(period) => {
                let period = (period * 1000f64) as u64;
                let chan = chan.clone();
                spawn_named("Profiler timer", proc() {
                    loop {
                        sleep(period);
                        if !chan.try_send(PrintMsg) {
                            break;
                        }
                    }
                });
                // Spawn the profiler
                spawn_named("Profiler", proc() {
                    let mut profiler = Profiler::new(port);
                    profiler.start();
                });
            }
            None => {
                // no-op to handle profiler messages when the profiler is inactive
                spawn_named("Profiler", proc() {
                    loop {
                        match port.recv_opt() {
                            None | Some(ExitMsg) => break,
                            _ => {}
                        }
                    }
                });
            }
        }

        ProfilerChan(chan)
    }

    pub fn new(port: Receiver<ProfilerMsg>) -> Profiler {
        Profiler {
            port: port,
            buckets: ProfilerCategory::empty_buckets(),
            last_msg: None,
        }
    }

    pub fn start(&mut self) {
        loop {
            let msg = self.port.recv_opt();
            match msg {
               Some(msg) => {
                   if !self.handle_msg(msg) {
                       break
                   }
               }
               None => break
            }
        }
    }

    fn handle_msg(&mut self, msg: ProfilerMsg) -> bool {
        match msg {
            TimeMsg(category, t) => self.buckets.find_mut(&category).unwrap().push(t),
            PrintMsg => match self.last_msg {
                // only print if more data has arrived since the last printout
                Some(TimeMsg(..)) => self.print_buckets(),
                _ => ()
            },
            ExitMsg => return false,
        };
        self.last_msg = Some(msg);
        true
    }

    fn print_buckets(&mut self) {
        println!("{:39s} {:15s} {:15s} {:15s} {:15s} {:15s}",
                 "_category_", "_mean (ms)_", "_median (ms)_",
                 "_min (ms)_", "_max (ms)_", "_bucket size_");
        for (category, data) in self.buckets.mut_iter() {
            data.sort_by(|a, b| {
                if a < b {
                    Less
                } else {
                    Greater
                }
            });
            let data_len = data.len();
            if data_len > 0 {
                let (mean, median, min, max) =
                    (data.iter().map(|&x|x).sum() / (data_len as f64),
                     *data.get(data_len / 2),
                     data.iter().fold(f64::INFINITY, |a, &b| a.min(b)),
                     data.iter().fold(-f64::INFINITY, |a, &b| a.max(b)));
                println!("{:-35s}: {:15.4f} {:15.4f} {:15.4f} {:15.4f} {:15u}",
                         category.format(), mean, median, min, max, data_len);
            }
        }
        println!("");
    }
}

pub fn profile<T>(category: ProfilerCategory,
                  profiler_chan: ProfilerChan,
                  callback: || -> T)
                  -> T {
    let start_time = precise_time_ns();
    let val = callback();
    let end_time = precise_time_ns();
    let ms = (end_time - start_time) as f64 / 1000000f64;
    profiler_chan.send(TimeMsg(category, ms));
    return val;
}

pub fn time<T>(msg: &str, callback: || -> T) -> T{
    let start_time = precise_time_ns();
    let val = callback();
    let end_time = precise_time_ns();
    let ms = (end_time - start_time) as f64 / 1000000f64;
    if ms >= 5f64 {
        debug!("{:s} took {} ms", msg, ms);
    }
    return val;
}

// ensure that the order of the buckets matches the order of the enum categories
#[test]
fn check_order() {
    let buckets = ProfilerCategory::empty_buckets();
    assert!(buckets.len() == NumBuckets as uint);
}
<|endoftext|>


<commit_before>extern crate rust_libindy_wrapper as indy;
#[macro_use] extern crate serde_json;
#[macro_use] extern crate serde_derive;
extern crate rmp_serde;
extern crate byteorder;

use indy::did::Did;
use indy::wallet::Wallet;

use indy::ErrorCode;

use std::path::Path;
use std::panic;
use std::sync::mpsc::channel;
use std::time::Duration;

mod utils;

use utils::{export_config_json, export_path};
use utils::constants::DEFAULT_CREDENTIALS;
use utils::file::TempDir;
use utils::rand;

const VALID_TIMEOUT: Duration = Duration::from_secs(5);
const INVALID_TIMEOUT: Duration = Duration::from_micros(1);

mod wallet_config {
    use super::*;

    #[inline]
    pub fn new() -> String {
        json!({
            "id": rand::random_string(20)
        }).to_string()
    }

    #[inline]
    pub fn with_storage(storage: &str) -> String {
        json!({
            "id": rand::random_string(20),
            "storage_type": storage,
        }).to_string()
    }

    #[inline]
    pub fn with_custom_path<P: AsRef<Path>>(path: P) -> String {
        json!({
            "id": rand::random_string(20),
            "storage_type": "default",
            "storage_config": {
                "path": path.as_ref().to_str()
            }
        }).to_string()
    }
}

#[cfg(test)]
mod wallet_register {

}

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

    #[test]
    fn create_delete_wallet_works() {
        let wallet_name = r#"{"id":"create_delete_wallet_works"}"#;
        match Wallet::create(wallet_name, DEFAULT_CREDENTIALS) {
            Ok(..) => assert!(Wallet::delete(wallet_name, DEFAULT_CREDENTIALS).is_ok()),
            Err(e) => match e {
                ErrorCode::WalletAlreadyExistsError => {
                    //This is ok, just delete
                    assert!(Wallet::delete(wallet_name, DEFAULT_CREDENTIALS).is_ok())
                }
                _ => {
                    panic!("{:#?}", e)
                }
            }
        }
    }

    #[test]
    fn open_close_wallet_works() {
        let wallet_name = r#"{"id":"open_wallet_works"}"#;
        let open_closure = || {
            match Wallet::open(wallet_name, DEFAULT_CREDENTIALS) {
                Ok(handle) => {
                    Wallet::close(handle).unwrap();
                    Wallet::delete(wallet_name, DEFAULT_CREDENTIALS).unwrap();
                },
                Err(e) => {
                    Wallet::delete(wallet_name, DEFAULT_CREDENTIALS).unwrap();
                    panic!("{:#?}", e);
                }
            }
        };

        match Wallet::create(wallet_name, DEFAULT_CREDENTIALS) {
            Err(e) => match e {
                ErrorCode::WalletAlreadyExistsError => {
                    open_closure()
                }
                _ => panic!("{:#?}", e)
            }
            _ => open_closure()
        };
    }

    #[test]
    fn export_import_wallet_works() {
        let wallet_name = r#"{"id":"export_import_wallet_works"}"#;

        let open_closure = || {
            match Wallet::open(wallet_name, DEFAULT_CREDENTIALS) {
                Ok(handle) => {
                    Did::new(handle, "{}").unwrap();

                    Wallet::export(handle, &export_config_json(wallet_name)).unwrap();

                    assert!(Path::new(&export_path(wallet_name)).exists());

                    Wallet::close(handle).unwrap();
                    Wallet::delete(wallet_name, DEFAULT_CREDENTIALS).unwrap();
                },
                Err(e) => {
                    Wallet::delete(wallet_name, DEFAULT_CREDENTIALS).unwrap();
                    panic!("{:#?}", e);
                }
            }
        };

        match Wallet::create(wallet_name, DEFAULT_CREDENTIALS) {
            Err(e) => match e {
                ErrorCode::WalletAlreadyExistsError => {
                    open_closure()
                }
                _ => panic!("{:#?}", e)
            }
            _ => open_closure()
        };
    }
}

#[cfg(test)]
mod test_wallet_create {
    use super::*;
    const CREDENTIALS: &str = r#"{"key":"9DXvkIMD7iSgD&RT$XYjHo0t"}"#;

    #[test]
    fn create_default_wallet() {
        let config = wallet_config::with_storage("default");

        let result = Wallet::create(&config, CREDENTIALS);

        assert_eq!((), result.unwrap());

        Wallet::delete(&config, CREDENTIALS).unwrap();
    }

    #[test]
    fn create_default_wallet_custom_path() {
        let dir = TempDir::new(None).unwrap();
        let config = wallet_config::with_custom_path(&dir);

        let result = Wallet::create(&config, CREDENTIALS);

        assert_eq!((), result.unwrap());

        Wallet::delete(&config, CREDENTIALS).unwrap();
    }

    // #[test]
    // fn create_wallet_custom_storage_type() {
    //     unimplemented!();
    // }

    #[test]
    fn create_wallet_unknown_storage_type() {
        let config = wallet_config::with_storage("unknown");

        let result = Wallet::create(&config, CREDENTIALS);

        assert_eq!(ErrorCode::WalletUnknownTypeError, result.unwrap_err());
    }

    #[test]
    fn create_wallet_empty_storage_type() {
        let config = wallet_config::new();

        let result = Wallet::create(&config, CREDENTIALS);

        assert_eq!((), result.unwrap());

        Wallet::delete(&config, CREDENTIALS).unwrap();
    }

    #[test]
    fn create_wallet_without_key() {
        let config = wallet_config::new();
        let credentials = "{}";

        let result = Wallet::create(&config, credentials);

        assert_eq!(ErrorCode::CommonInvalidStructure, result.unwrap_err());
    }

    #[test]
    fn create_wallet_without_encryption() {
        let config = wallet_config::new();
        let credentials = json!({"key": ""}).to_string();

        let result = Wallet::create(&config, &credentials);

        assert_eq!((), result.unwrap());

        Wallet::delete(&config, &credentials).unwrap();
    }

    #[test]
    fn create_default_wallet_async() {
        let (sender, receiver) = channel();
        let config = wallet_config::with_storage("default");

        Wallet::create_async(
            &config,
            CREDENTIALS,
            move |ec| sender.send(ec).unwrap()
        );

        let ec = receiver.recv_timeout(VALID_TIMEOUT).unwrap();

        assert_eq!(ErrorCode::Success, ec);

        Wallet::delete(&config, CREDENTIALS).unwrap();
    }

    #[test]
    fn create_wallet_unknown_storage_type_async() {
        let (sender, receiver) = channel();
        let config = wallet_config::with_storage("unknown");

        Wallet::create_async(
            &config,
            CREDENTIALS,
            move |ec| sender.send(ec).unwrap()
        );

        let ec = receiver.recv_timeout(VALID_TIMEOUT).unwrap();

        assert_eq!(ErrorCode::WalletUnknownTypeError, ec);
    }

    #[test]
    fn create_default_wallet_timeout() {
        let config = wallet_config::with_storage("default");

        let result = Wallet::create_timeout(
            &config,
            CREDENTIALS,
            VALID_TIMEOUT
        );

        assert_eq!((), result.unwrap());

        Wallet::delete(&config, CREDENTIALS).unwrap();
    }

    #[test]
    fn create_wallet_unknown_storage_type_timeout() {
        let config = wallet_config::with_storage("unknown");

        let result = Wallet::create_timeout(
            &config,
            CREDENTIALS,
            VALID_TIMEOUT
        );

        assert_eq!(ErrorCode::WalletUnknownTypeError, result.unwrap_err());
    }

    #[test]
    fn create_wallet_timeout_timeouts() {
        let config = wallet_config::with_storage("unknown");

        let result = Wallet::create_timeout(
            &config,
            CREDENTIALS,
            INVALID_TIMEOUT
        );

        assert_eq!(ErrorCode::CommonIOError, result.unwrap_err());
    }
}

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

    #[inline]
    fn assert_wallet_deleted(config: &str, credentials: &str) {
        let result = Wallet::open(config, credentials);
        assert_eq!(ErrorCode::WalletNotFoundError, result.unwrap_err());
    }

    #[test]
    fn delete_wallet() {
        let config = wallet_config::new();
        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();

        let result = Wallet::delete(&config, DEFAULT_CREDENTIALS);

        assert_eq!((), result.unwrap());
        assert_wallet_deleted(&config, DEFAULT_CREDENTIALS);
    }

    #[test]
    fn delete_wallet_custom_path() {
        let dir = TempDir::new(None).unwrap();
        let config = wallet_config::with_custom_path(&dir);
        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();

        let result = Wallet::delete(&config, DEFAULT_CREDENTIALS);

        assert_eq!((), result.unwrap());
        assert_wallet_deleted(&config, DEFAULT_CREDENTIALS);
    }

    #[test]
    fn delete_wallet_closed() {
        let config = wallet_config::new();

        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();
        let handle = Wallet::open(&config, DEFAULT_CREDENTIALS).unwrap();
        Wallet::close(handle).unwrap();

        let result = Wallet::delete(&config, DEFAULT_CREDENTIALS);

        assert_eq!((), result.unwrap());
        assert_wallet_deleted(&config, DEFAULT_CREDENTIALS);
    }

    #[test]
    fn delete_wallet_opened() {
        let config = wallet_config::new();

        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();
        let handle = Wallet::open(&config, DEFAULT_CREDENTIALS).unwrap();

        let result = Wallet::delete(&config, DEFAULT_CREDENTIALS);

        assert_eq!(ErrorCode::CommonInvalidState, result.unwrap_err());

        Wallet::close(handle).unwrap();
        Wallet::delete(&config, DEFAULT_CREDENTIALS).unwrap();
    }

    // #[test]
    // fn delete_registered_wallet() {
    //     unimplemented!();
    // }

    #[test]
    fn delete_wallet_repeated_command() {
        let config = wallet_config::new();
        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();
        Wallet::delete(&config, DEFAULT_CREDENTIALS).unwrap();

        let result = Wallet::delete(&config, DEFAULT_CREDENTIALS);

        assert_eq!(ErrorCode::WalletNotFoundError, result.unwrap_err());
    }

    #[test]
    fn delete_wallet_invalid_credentials() {
        let config = wallet_config::new();
        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();

        let result = Wallet::delete(&config, r#"{"key": "badkey"}"#);

        assert_eq!(ErrorCode::WalletAccessFailed, result.unwrap_err());

        Wallet::delete(&config, DEFAULT_CREDENTIALS).unwrap();
    }

    #[test]
    fn delete_wallet_uncreated() {
        let config = wallet_config::new();

        let result = Wallet::delete(&config, DEFAULT_CREDENTIALS);

        assert_eq!(ErrorCode::WalletNotFoundError, result.unwrap_err());
    }

    #[test]
    fn delete_wallet_async() {
        let (sender, receiver) = channel();
        let config = wallet_config::new();
        Wallet::create(&config, DEFAULT_CREDENTIALS);

        Wallet::delete_async(
            &config,
            DEFAULT_CREDENTIALS,
            move |ec| sender.send(ec).unwrap()
        );

        let ec = receiver.recv().unwrap();

        assert_eq!(ErrorCode::Success, ec);
        assert_wallet_deleted(&config, DEFAULT_CREDENTIALS);
    }

    #[test]
    fn delete_wallet_uncreated_async() {
        let (sender, receiver) = channel();
        let config = wallet_config::new();

        Wallet::delete_async(
            &config,
            DEFAULT_CREDENTIALS,
            move |ec| sender.send(ec).unwrap()
        );

        let ec = receiver.recv().unwrap();

        assert_eq!(ErrorCode::WalletNotFoundError, ec);
    }

    #[test]
    fn delete_wallet_timeout() {
        let config = wallet_config::new();
        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();

        let result = Wallet::delete_timeout(
            &config,
            DEFAULT_CREDENTIALS,
            VALID_TIMEOUT,
        );

        assert_eq!((), result.unwrap());
        assert_wallet_deleted(&config, DEFAULT_CREDENTIALS);
    }

    #[test]
    fn delete_wallet_uncreated_timeout() {
        let config = wallet_config::new();

        let result = Wallet::delete_timeout(
            &config,
            DEFAULT_CREDENTIALS,
            VALID_TIMEOUT,
        );

        assert_eq!(ErrorCode::WalletNotFoundError, result.unwrap_err());
    }

    #[test]
    fn delete_wallet_timeout_timeouts() {
        let config = wallet_config::new();

        let result = Wallet::delete_timeout(
            &config,
            DEFAULT_CREDENTIALS,
            INVALID_TIMEOUT,
        );

        assert_eq!(ErrorCode::CommonIOError, result.unwrap_err());
    }
}<commit_msg>TOK-424 Add Wallet::open tests.<commit_after>extern crate rust_libindy_wrapper as indy;
#[macro_use] extern crate serde_json;
#[macro_use] extern crate serde_derive;
extern crate rmp_serde;
extern crate byteorder;

use indy::did::Did;
use indy::wallet::Wallet;

use indy::ErrorCode;

use std::path::Path;
use std::panic;
use std::sync::mpsc::channel;
use std::time::Duration;

mod utils;

use utils::{export_config_json, export_path};
use utils::constants::DEFAULT_CREDENTIALS;
use utils::file::TempDir;
use utils::rand;

const VALID_TIMEOUT: Duration = Duration::from_secs(5);
const INVALID_TIMEOUT: Duration = Duration::from_micros(1);

mod wallet_config {
    use super::*;

    #[inline]
    pub fn new() -> String {
        json!({
            "id": rand::random_string(20)
        }).to_string()
    }

    #[inline]
    pub fn with_storage(storage: &str) -> String {
        json!({
            "id": rand::random_string(20),
            "storage_type": storage,
        }).to_string()
    }

    #[inline]
    pub fn with_custom_path<P: AsRef<Path>>(path: P) -> String {
        json!({
            "id": rand::random_string(20),
            "storage_type": "default",
            "storage_config": {
                "path": path.as_ref().to_str()
            }
        }).to_string()
    }
}

#[cfg(test)]
mod wallet_register {

}

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

    #[test]
    fn create_delete_wallet_works() {
        let wallet_name = r#"{"id":"create_delete_wallet_works"}"#;
        match Wallet::create(wallet_name, DEFAULT_CREDENTIALS) {
            Ok(..) => assert!(Wallet::delete(wallet_name, DEFAULT_CREDENTIALS).is_ok()),
            Err(e) => match e {
                ErrorCode::WalletAlreadyExistsError => {
                    //This is ok, just delete
                    assert!(Wallet::delete(wallet_name, DEFAULT_CREDENTIALS).is_ok())
                }
                _ => {
                    panic!("{:#?}", e)
                }
            }
        }
    }

    #[test]
    fn open_close_wallet_works() {
        let wallet_name = r#"{"id":"open_wallet_works"}"#;
        let open_closure = || {
            match Wallet::open(wallet_name, DEFAULT_CREDENTIALS) {
                Ok(handle) => {
                    Wallet::close(handle).unwrap();
                    Wallet::delete(wallet_name, DEFAULT_CREDENTIALS).unwrap();
                },
                Err(e) => {
                    Wallet::delete(wallet_name, DEFAULT_CREDENTIALS).unwrap();
                    panic!("{:#?}", e);
                }
            }
        };

        match Wallet::create(wallet_name, DEFAULT_CREDENTIALS) {
            Err(e) => match e {
                ErrorCode::WalletAlreadyExistsError => {
                    open_closure()
                }
                _ => panic!("{:#?}", e)
            }
            _ => open_closure()
        };
    }

    #[test]
    fn export_import_wallet_works() {
        let wallet_name = r#"{"id":"export_import_wallet_works"}"#;

        let open_closure = || {
            match Wallet::open(wallet_name, DEFAULT_CREDENTIALS) {
                Ok(handle) => {
                    Did::new(handle, "{}").unwrap();

                    Wallet::export(handle, &export_config_json(wallet_name)).unwrap();

                    assert!(Path::new(&export_path(wallet_name)).exists());

                    Wallet::close(handle).unwrap();
                    Wallet::delete(wallet_name, DEFAULT_CREDENTIALS).unwrap();
                },
                Err(e) => {
                    Wallet::delete(wallet_name, DEFAULT_CREDENTIALS).unwrap();
                    panic!("{:#?}", e);
                }
            }
        };

        match Wallet::create(wallet_name, DEFAULT_CREDENTIALS) {
            Err(e) => match e {
                ErrorCode::WalletAlreadyExistsError => {
                    open_closure()
                }
                _ => panic!("{:#?}", e)
            }
            _ => open_closure()
        };
    }
}

#[cfg(test)]
mod test_wallet_create {
    use super::*;
    const CREDENTIALS: &str = r#"{"key":"9DXvkIMD7iSgD&RT$XYjHo0t"}"#;

    #[test]
    fn create_default_wallet() {
        let config = wallet_config::with_storage("default");

        let result = Wallet::create(&config, CREDENTIALS);

        assert_eq!((), result.unwrap());

        Wallet::delete(&config, CREDENTIALS).unwrap();
    }

    #[test]
    fn create_default_wallet_custom_path() {
        let dir = TempDir::new(None).unwrap();
        let config = wallet_config::with_custom_path(&dir);

        let result = Wallet::create(&config, CREDENTIALS);

        assert_eq!((), result.unwrap());

        Wallet::delete(&config, CREDENTIALS).unwrap();
    }

    // #[test]
    // fn create_wallet_custom_storage_type() {
    //     unimplemented!();
    // }

    #[test]
    fn create_wallet_unknown_storage_type() {
        let config = wallet_config::with_storage("unknown");

        let result = Wallet::create(&config, CREDENTIALS);

        assert_eq!(ErrorCode::WalletUnknownTypeError, result.unwrap_err());
    }

    #[test]
    fn create_wallet_empty_storage_type() {
        let config = wallet_config::new();

        let result = Wallet::create(&config, CREDENTIALS);

        assert_eq!((), result.unwrap());

        Wallet::delete(&config, CREDENTIALS).unwrap();
    }

    #[test]
    fn create_wallet_without_key() {
        let config = wallet_config::new();
        let credentials = "{}";

        let result = Wallet::create(&config, credentials);

        assert_eq!(ErrorCode::CommonInvalidStructure, result.unwrap_err());
    }

    #[test]
    fn create_wallet_without_encryption() {
        let config = wallet_config::new();
        let credentials = json!({"key": ""}).to_string();

        let result = Wallet::create(&config, &credentials);

        assert_eq!((), result.unwrap());

        Wallet::delete(&config, &credentials).unwrap();
    }

    #[test]
    fn create_default_wallet_async() {
        let (sender, receiver) = channel();
        let config = wallet_config::with_storage("default");

        Wallet::create_async(
            &config,
            CREDENTIALS,
            move |ec| sender.send(ec).unwrap()
        );

        let ec = receiver.recv_timeout(VALID_TIMEOUT).unwrap();

        assert_eq!(ErrorCode::Success, ec);

        Wallet::delete(&config, CREDENTIALS).unwrap();
    }

    #[test]
    fn create_wallet_unknown_storage_type_async() {
        let (sender, receiver) = channel();
        let config = wallet_config::with_storage("unknown");

        Wallet::create_async(
            &config,
            CREDENTIALS,
            move |ec| sender.send(ec).unwrap()
        );

        let ec = receiver.recv_timeout(VALID_TIMEOUT).unwrap();

        assert_eq!(ErrorCode::WalletUnknownTypeError, ec);
    }

    #[test]
    fn create_default_wallet_timeout() {
        let config = wallet_config::with_storage("default");

        let result = Wallet::create_timeout(
            &config,
            CREDENTIALS,
            VALID_TIMEOUT
        );

        assert_eq!((), result.unwrap());

        Wallet::delete(&config, CREDENTIALS).unwrap();
    }

    #[test]
    fn create_wallet_unknown_storage_type_timeout() {
        let config = wallet_config::with_storage("unknown");

        let result = Wallet::create_timeout(
            &config,
            CREDENTIALS,
            VALID_TIMEOUT
        );

        assert_eq!(ErrorCode::WalletUnknownTypeError, result.unwrap_err());
    }

    #[test]
    fn create_wallet_timeout_timeouts() {
        let config = wallet_config::with_storage("unknown");

        let result = Wallet::create_timeout(
            &config,
            CREDENTIALS,
            INVALID_TIMEOUT
        );

        assert_eq!(ErrorCode::CommonIOError, result.unwrap_err());
    }
}

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

    #[inline]
    fn assert_wallet_deleted(config: &str, credentials: &str) {
        let result = Wallet::open(config, credentials);
        assert_eq!(ErrorCode::WalletNotFoundError, result.unwrap_err());
    }

    #[test]
    fn delete_wallet() {
        let config = wallet_config::new();
        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();

        let result = Wallet::delete(&config, DEFAULT_CREDENTIALS);

        assert_eq!((), result.unwrap());
        assert_wallet_deleted(&config, DEFAULT_CREDENTIALS);
    }

    #[test]
    fn delete_wallet_custom_path() {
        let dir = TempDir::new(None).unwrap();
        let config = wallet_config::with_custom_path(&dir);
        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();

        let result = Wallet::delete(&config, DEFAULT_CREDENTIALS);

        assert_eq!((), result.unwrap());
        assert_wallet_deleted(&config, DEFAULT_CREDENTIALS);
    }

    #[test]
    fn delete_wallet_closed() {
        let config = wallet_config::new();

        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();
        let handle = Wallet::open(&config, DEFAULT_CREDENTIALS).unwrap();
        Wallet::close(handle).unwrap();

        let result = Wallet::delete(&config, DEFAULT_CREDENTIALS);

        assert_eq!((), result.unwrap());
        assert_wallet_deleted(&config, DEFAULT_CREDENTIALS);
    }

    #[test]
    fn delete_wallet_opened() {
        let config = wallet_config::new();

        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();
        let handle = Wallet::open(&config, DEFAULT_CREDENTIALS).unwrap();

        let result = Wallet::delete(&config, DEFAULT_CREDENTIALS);

        assert_eq!(ErrorCode::CommonInvalidState, result.unwrap_err());

        Wallet::close(handle).unwrap();
        Wallet::delete(&config, DEFAULT_CREDENTIALS).unwrap();
    }

    // #[test]
    // fn delete_registered_wallet() {
    //     unimplemented!();
    // }

    #[test]
    fn delete_wallet_repeated_command() {
        let config = wallet_config::new();
        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();
        Wallet::delete(&config, DEFAULT_CREDENTIALS).unwrap();

        let result = Wallet::delete(&config, DEFAULT_CREDENTIALS);

        assert_eq!(ErrorCode::WalletNotFoundError, result.unwrap_err());
    }

    #[test]
    fn delete_wallet_invalid_credentials() {
        let config = wallet_config::new();
        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();

        let result = Wallet::delete(&config, r#"{"key": "badkey"}"#);

        assert_eq!(ErrorCode::WalletAccessFailed, result.unwrap_err());

        Wallet::delete(&config, DEFAULT_CREDENTIALS).unwrap();
    }

    #[test]
    fn delete_wallet_uncreated() {
        let config = wallet_config::new();

        let result = Wallet::delete(&config, DEFAULT_CREDENTIALS);

        assert_eq!(ErrorCode::WalletNotFoundError, result.unwrap_err());
    }

    #[test]
    fn delete_wallet_async() {
        let (sender, receiver) = channel();
        let config = wallet_config::new();
        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();

        Wallet::delete_async(
            &config,
            DEFAULT_CREDENTIALS,
            move |ec| sender.send(ec).unwrap()
        );

        let ec = receiver.recv().unwrap();

        assert_eq!(ErrorCode::Success, ec);
        assert_wallet_deleted(&config, DEFAULT_CREDENTIALS);
    }

    #[test]
    fn delete_wallet_uncreated_async() {
        let (sender, receiver) = channel();
        let config = wallet_config::new();

        Wallet::delete_async(
            &config,
            DEFAULT_CREDENTIALS,
            move |ec| sender.send(ec).unwrap()
        );

        let ec = receiver.recv().unwrap();

        assert_eq!(ErrorCode::WalletNotFoundError, ec);
    }

    #[test]
    fn delete_wallet_timeout() {
        let config = wallet_config::new();
        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();

        let result = Wallet::delete_timeout(
            &config,
            DEFAULT_CREDENTIALS,
            VALID_TIMEOUT,
        );

        assert_eq!((), result.unwrap());
        assert_wallet_deleted(&config, DEFAULT_CREDENTIALS);
    }

    #[test]
    fn delete_wallet_uncreated_timeout() {
        let config = wallet_config::new();

        let result = Wallet::delete_timeout(
            &config,
            DEFAULT_CREDENTIALS,
            VALID_TIMEOUT,
        );

        assert_eq!(ErrorCode::WalletNotFoundError, result.unwrap_err());
    }

    #[test]
    fn delete_wallet_timeout_timeouts() {
        let config = wallet_config::new();

        let result = Wallet::delete_timeout(
            &config,
            DEFAULT_CREDENTIALS,
            INVALID_TIMEOUT,
        );

        assert_eq!(ErrorCode::CommonIOError, result.unwrap_err());
    }
}

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

    #[test]
    fn open_wallet() {
        let config = wallet_config::new();
        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();

        let handle = Wallet::open(&config, DEFAULT_CREDENTIALS).unwrap();

        Wallet::close(handle).unwrap();
        Wallet::delete(&config, DEFAULT_CREDENTIALS).unwrap();
    }

    #[test]
    fn open_wallet_custom_path() {
        let dir = TempDir::new(None).unwrap();
        let config = wallet_config::with_custom_path(&dir);

        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();

        let handle = Wallet::open(&config, DEFAULT_CREDENTIALS).unwrap();

        Wallet::close(handle).unwrap();
        Wallet::delete(&config, DEFAULT_CREDENTIALS).unwrap();
    }

    // #[test]
    // fn open_wallet_registered() {
    //     unimplemented!();
    // }

    #[test]
    fn open_wallet_not_created() {
        let config = wallet_config::new();

        let result = Wallet::open(&config, DEFAULT_CREDENTIALS);

        assert_eq!(ErrorCode::WalletNotFoundError, result.unwrap_err());
    }

    #[test]
    fn open_wallet_repeated_command() {
        let config = wallet_config::new();
        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();

        let handle = Wallet::open(&config, DEFAULT_CREDENTIALS).unwrap();

        let result = Wallet::open(&config, DEFAULT_CREDENTIALS);

        assert_eq!(ErrorCode::WalletAlreadyOpenedError, result.unwrap_err());

        Wallet::close(handle).unwrap();
        Wallet::delete(&config, DEFAULT_CREDENTIALS).unwrap();
    }

    #[test]
    fn open_wallet_two_same_time() {
        let config1 = wallet_config::new();
        let config2 = wallet_config::new();

        Wallet::create(&config1, DEFAULT_CREDENTIALS).unwrap();
        Wallet::create(&config2, DEFAULT_CREDENTIALS).unwrap();

        let handle1 = Wallet::open(&config1, DEFAULT_CREDENTIALS).unwrap();
        let handle2 = Wallet::open(&config2, DEFAULT_CREDENTIALS).unwrap();

        Wallet::close(handle1).unwrap();
        Wallet::close(handle2).unwrap();
        Wallet::delete(&config1, DEFAULT_CREDENTIALS).unwrap();
        Wallet::delete(&config2, DEFAULT_CREDENTIALS).unwrap();
    }

    #[test]
    fn open_wallet_invalid_credentials() {
        let config = wallet_config::new();
        let credentials = json!({"key": "xylophone rat"}).to_string();

        Wallet::create(&config, &credentials).unwrap();

        let result = Wallet::open(&config, DEFAULT_CREDENTIALS);

        assert_eq!(ErrorCode::WalletAccessFailed, result.unwrap_err());

        Wallet::delete(&config, &credentials).unwrap();
    }

    #[test]
    fn open_wallet_change_credentials() {
        let config = wallet_config::new();
        let credentials1 = json!({"key": "key_1"}).to_string();
        let credentials2 = json!({"key": "key_2"}).to_string();
        let rekey = json!({"key": "key_1", "rekey": "key_2"}).to_string();

        Wallet::create(&config, &credentials1).unwrap();

        let handle = Wallet::open(&config, &rekey).unwrap();
        Wallet::close(handle).unwrap();

        let result = Wallet::open(&config, &credentials1);
        assert_eq!(ErrorCode::WalletAccessFailed, result.unwrap_err());

        let handle = Wallet::open(&config, &credentials2).unwrap();
        Wallet::close(handle).unwrap();

        Wallet::delete(&config, &credentials2).unwrap();
    }

    #[test]
    fn open_wallet_invalid_config() {
        let config = wallet_config::new();
        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();

        let result = Wallet::open("{}", DEFAULT_CREDENTIALS);

        assert_eq!(ErrorCode::CommonInvalidStructure, result.unwrap_err());

        Wallet::delete(&config, DEFAULT_CREDENTIALS).unwrap();
    }

    #[test]
    fn open_wallet_async() {
        let (sender, receiver) = channel();
        let config = wallet_config::new();
        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();

        Wallet::open_async(
            &config,
            DEFAULT_CREDENTIALS,
            move |ec, handle| sender.send((ec, handle)).unwrap()
        );

        let (ec, handle) = receiver.recv_timeout(VALID_TIMEOUT).unwrap();

        assert_eq!(ErrorCode::Success, ec);

        Wallet::close(handle).unwrap();
        Wallet::delete(&config, DEFAULT_CREDENTIALS).unwrap();
    }

    #[test]
    fn open_wallet_async_not_created() {
        let (sender, receiver) = channel();
        let config = wallet_config::new();

        Wallet::open_async(
            &config,
            DEFAULT_CREDENTIALS,
            move |ec, handle| sender.send((ec, handle)).unwrap()
        );

        let (ec, handle) = receiver.recv_timeout(VALID_TIMEOUT).unwrap();

        assert_eq!(ErrorCode::WalletNotFoundError, ec);
        assert_eq!(0, handle);
    }

    #[test]
    fn open_wallet_timeout() {
        let config = wallet_config::new();
        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();

        let handle = Wallet::open_timeout(
            &config,
            DEFAULT_CREDENTIALS,
            VALID_TIMEOUT
        ).unwrap();

        Wallet::close(handle).unwrap();
        Wallet::delete(&config, DEFAULT_CREDENTIALS).unwrap();
    }

    #[test]
    fn open_wallet_timeout_not_created() {
        let config = wallet_config::new();

        let result = Wallet::open_timeout(
            &config,
            DEFAULT_CREDENTIALS,
            VALID_TIMEOUT
        );

        assert_eq!(ErrorCode::WalletNotFoundError, result.unwrap_err());
    }

    #[test]
    fn open_wallet_timeout_timeouts() {
        let config = wallet_config::new();
        Wallet::create(&config, DEFAULT_CREDENTIALS).unwrap();

        let result = Wallet::open_timeout(
            &config,
            DEFAULT_CREDENTIALS,
            INVALID_TIMEOUT
        );

        assert_eq!(ErrorCode::CommonIOError, result.unwrap_err());
    }
}<|endoftext|>


<commit_before>extern crate structopt;
#[macro_use]
extern crate structopt_derive;

use structopt::StructOpt;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;

struct OutOfBounds;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Position {
    // positions are (y, x). y increases downward, x increases right
    current: (usize, usize),
    bounds: (usize, usize),
}

impl Position {
    fn go(&self, dir: Direction) -> Result<(usize, usize), OutOfBounds> {
        use Direction::*;
        match dir {
            Up => if self.current.0 == 0 {
                Err(OutOfBounds)
            } else {
                Ok((self.current.0 - 1, self.current.1))
            },
            Down => if self.current.0 == self.bounds.0 {
                Err(OutOfBounds)
            } else {
                Ok((self.current.0 + 1, self.current.1))
            },
            Left => if self.current.1 == 0 {
                Err(OutOfBounds)
            } else {
                Ok((self.current.0, self.current.1 - 1))
            },
            Right => if self.current.1 == self.bounds.1 {
                Err(OutOfBounds)
            } else {
                Ok((self.current.0, self.current.1 + 1))
            },
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Direction {
    Up,
    Down,
    Left,
    Right,
}

impl Direction {
    fn reverse(&self) -> Direction {
        use Direction::*;
        match *self {
            Up => Down,
            Down => Up,
            Left => Right,
            Right => Left,
        }
    }
}

fn traverse(input: &str) -> (String, usize) {
    use Direction::*;
    let map: Vec<Vec<char>> =
        input.lines().map(|l| l.chars().collect()).collect();
    let mut position = Position {
        current: (0, map[0].iter().position(|&ch| ch == '|').unwrap()),
        bounds: (map.len(), map[0].len()),
    };
    let mut direction = Down;
    let mut letters = String::new();
    let mut steps = 1;
    while let Ok(pos) = position.go(direction) {
        steps += 1;
        position.current = pos;
        match map[pos.0][pos.1] {
            '|' | '-' => {}
            ' ' => panic!(format!(
                "ruh roh! ({}, {})",
                position.current.0, position.current.1
            )),
            ch => {
                if ch != '+' {
                    letters.push(ch)
                }
                match vec![Up, Down, Left, Right]
                    .into_iter()
                    .filter(|&d| d != direction.reverse())
                    .filter(|&d| {
                        if let Ok(p) = position.go(d) {
                            map[p.0][p.1] != ' '
                        } else {
                            false
                        }
                    })
                    .next()
                {
                    Some(dir) => direction = dir,
                    None => break,
                }
            }
        }
    }
    (letters, steps)
}

fn main() {
    let opt = Opt::from_args();
    let mut contents = String::new();
    if opt.input.to_str() == Some("-") {
        std::io::stdin()
            .read_to_string(&mut contents)
            .expect("could not read stdin");
    } else {
        let mut file = File::open(&opt.input)
            .expect(&format!("file {} not found", opt.input.display()));
        file.read_to_string(&mut contents)
            .expect(&format!("could not read file {}", opt.input.display()));
    }
    let (letters, steps) = traverse(&contents);
    println!("Part 1: {}", letters);
    println!("Part 2: {}", steps);
}

#[derive(StructOpt, Debug)]
#[structopt(name = "day13", about = "Advent of code 2017 day 13")]
struct Opt {
    #[structopt(help = "Input file", parse(from_os_str))] input: PathBuf,
}

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

    #[test]
    fn traverse_test() {
        let input = concat!(
            "     |          \n",
            "     |  +--+    \n",
            "     A  |  C    \n",
            " F---|----E|--+ \n",
            "     |  |  |  D \n",
            "     +B-+  +--+ \n",
            "                ",
        );
        assert_eq!(traverse(input), ("ABCDEF".into(), 38));
    }
}
<commit_msg>2017 day 19 style<commit_after>extern crate structopt;
#[macro_use]
extern crate structopt_derive;

use structopt::StructOpt;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;

struct OutOfBounds;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Position {
    // positions are (y, x). y increases downward, x increases right
    current: (usize, usize),
    bounds: (usize, usize),
}

impl Position {
    fn go(&self, dir: Direction) -> Result<(usize, usize), OutOfBounds> {
        use Direction::*;
        match dir {
            Up => if self.current.0 == 0 {
                Err(OutOfBounds)
            } else {
                Ok((self.current.0 - 1, self.current.1))
            },
            Down => if self.current.0 == self.bounds.0 {
                Err(OutOfBounds)
            } else {
                Ok((self.current.0 + 1, self.current.1))
            },
            Left => if self.current.1 == 0 {
                Err(OutOfBounds)
            } else {
                Ok((self.current.0, self.current.1 - 1))
            },
            Right => if self.current.1 == self.bounds.1 {
                Err(OutOfBounds)
            } else {
                Ok((self.current.0, self.current.1 + 1))
            },
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Direction {
    Up,
    Down,
    Left,
    Right,
}

impl Direction {
    fn reverse(&self) -> Direction {
        use Direction::*;
        match *self {
            Up => Down,
            Down => Up,
            Left => Right,
            Right => Left,
        }
    }
}

fn traverse(input: &str) -> (String, usize) {
    use Direction::*;
    let map: Vec<Vec<char>> =
        input.lines().map(|l| l.chars().collect()).collect();
    let mut position = Position {
        current: (0, map[0].iter().position(|&ch| ch == '|').unwrap()),
        bounds: (map.len(), map[0].len()),
    };
    let mut direction = Down;
    let mut letters = String::new();
    let mut steps = 1;
    while let Ok(pos) = position.go(direction) {
        steps += 1;
        position.current = pos;
        match map[pos.0][pos.1] {
            '|' | '-' => {}
            ' ' => panic!(format!(
                "ruh roh! ({}, {})",
                position.current.0, position.current.1
            )),
            ch => {
                if ch != '+' {
                    letters.push(ch)
                }
                match vec![Up, Down, Left, Right].into_iter().find(|&d| {
                    d != direction.reverse()
                        && position
                            .go(d)
                            .map(|p| map[p.0][p.1] != ' ')
                            .unwrap_or(false)
                }) {
                    Some(dir) => direction = dir,
                    None => break,
                }
            }
        }
    }
    (letters, steps)
}

fn main() {
    let opt = Opt::from_args();
    let mut contents = String::new();
    if opt.input.to_str() == Some("-") {
        std::io::stdin()
            .read_to_string(&mut contents)
            .expect("could not read stdin");
    } else {
        let mut file = File::open(&opt.input)
            .expect(&format!("file {} not found", opt.input.display()));
        file.read_to_string(&mut contents)
            .expect(&format!("could not read file {}", opt.input.display()));
    }
    let (letters, steps) = traverse(&contents);
    println!("Part 1: {}", letters);
    println!("Part 2: {}", steps);
}

#[derive(StructOpt, Debug)]
#[structopt(name = "day13", about = "Advent of code 2017 day 13")]
struct Opt {
    #[structopt(help = "Input file", parse(from_os_str))] input: PathBuf,
}

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

    #[test]
    fn traverse_test() {
        let input = concat!(
            "     |          \n",
            "     |  +--+    \n",
            "     A  |  C    \n",
            " F---|----E|--+ \n",
            "     |  |  |  D \n",
            "     +B-+  +--+ \n",
            "                ",
        );
        assert_eq!(traverse(input), ("ABCDEF".into(), 38));
    }
}
<|endoftext|>