<commit_before>// Copyright (c) 2020 DDN. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

use crate::{
    api_utils::{delete, first, get, graphql, post, wait_for_cmd_display},
    display_utils::{wrap_fut, DisplayType, IntoDisplayType as _},
    error::{
        DurationParseError, ImlManagerCliError, RunStratagemCommandResult,
        RunStratagemValidationError,
    },
};
use console::Term;
use iml_graphql_queries::stratagem as stratagem_queries;
use iml_manager_client::ImlManagerClientError;
use iml_wire_types::{ApiList, CmdWrapper, EndpointName, Filesystem, StratagemConfiguration};
use structopt::{clap::arg_enum, StructOpt};

#[derive(Debug, StructOpt)]
pub enum StratagemCommand {
    /// Kickoff a Stratagem scan
    #[structopt(name = "scan")]
    Scan(StratagemScanData),
    /// Configure Stratagem scanning interval
    #[structopt(name = "interval")]
    Interval(IntervalCommand),
    /// Kickoff a Stratagem Filesync
    #[structopt(name = "filesync")]
    Filesync(StratagemFilesyncData),
    /// Kickoff a Stratagem Cloudsync
    #[structopt(name = "cloudsync")]
    Cloudsync(StratagemCloudsyncData),
    /// Work with Stratagem reports
    #[structopt(name = "report")]
    Report {
        #[structopt(subcommand)]
        command: Option<ReportCommand>,
    },
}

#[derive(Debug, StructOpt)]
pub enum ReportCommand {
    /// List all existing Stratagem reports (default)
    #[structopt(name = "list")]
    List {
        /// Display type: json, yaml, tabular
        #[structopt(short = "d", long = "display", default_value = "tabular")]
        display_type: DisplayType,
    },
    /// Delete Stratagem reports
    #[structopt(name = "remove")]
    Delete {
        /// Report name to delete
        #[structopt(required = true, min_values = 1)]
        name: Vec<String>,
    },
}

#[derive(Debug, StructOpt)]
pub enum IntervalCommand {
    /// List all existing Stratagem intervals
    #[structopt(name = "list")]
    List {
        /// Display type: json, yaml, tabular
        #[structopt(short = "d", long = "display", default_value = "tabular")]
        display_type: DisplayType,
    },
    /// Create Stratagem scan interval
    #[structopt(name = "create")]
    Create(IntervalCommandConfig),
    /// Remove a Stratagem interval
    #[structopt(name = "remove")]
    Remove(StratagemRemoveData),
}

#[derive(Debug, StructOpt, serde::Serialize)]
pub struct IntervalCommandConfig {
    /// Filesystem to configure
    filesystem: String,
    /// Interval to scan
    #[structopt(parse(try_from_str = parse_duration))]
    interval: u64,
    /// The report duration
    #[structopt(short = "r", long = "report", parse(try_from_str = parse_duration))]
    report_duration: Option<u64>,
    /// The purge duration
    #[structopt(short = "p", long = "purge", parse(try_from_str = parse_duration))]
    purge_duration: Option<u64>,
}

#[derive(Debug, StructOpt, serde::Serialize)]
pub struct StratagemRemoveData {
    /// Filesystem to unconfigure
    filesystem: String,
}

#[derive(serde::Serialize, StructOpt, Debug)]
pub struct StratagemScanData {
    /// The name of the filesystem to scan
    filesystem: String,
    /// The report duration
    #[structopt(short = "r", long = "report", parse(try_from_str = parse_duration))]
    report_duration: Option<u64>,
    /// The purge duration
    #[structopt(short = "p", long = "purge", parse(try_from_str = parse_duration))]
    purge_duration: Option<u64>,
}

arg_enum! {
    #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)]
    #[serde(rename_all = "lowercase")]
    pub enum FilesyncAction {
    Push,
    }
}

arg_enum! {
    #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)]
    #[serde(rename_all = "lowercase")]
    pub enum CloudsyncAction {
    Push,
    Pull,
    }
}

#[derive(serde::Serialize, StructOpt, Debug)]
pub struct StratagemFilesyncData {
    /// action, 'push' is supported
    #[structopt()]
    action: FilesyncAction,
    /// The name of the filesystem to scan
    #[structopt(short = "f", long = "filesystem")]
    filesystem: String,
    /// The remote filesystem
    #[structopt(short = "r", long = "remote")]
    remote: String,
    /// Match expression
    #[structopt(short = "e", long = "expression")]
    expression: String,
    #[structopt(skip = true)]
    filesync: bool,
}

#[derive(serde::Serialize, StructOpt, Debug)]
pub struct StratagemCloudsyncData {
    /// action, either push or pull
    #[structopt()]
    action: CloudsyncAction,
    /// The name of the filesystem to scan
    #[structopt(short = "f", long = "filesystem")]
    filesystem: String,
    /// The s3 instance
    #[structopt(short = "r", long = "remote")]
    remote: String,
    /// Match expression
    #[structopt(short = "e", long = "expression")]
    expression: String,
    policy: String,
    #[structopt(skip = true)]
    cloudsync: bool,
}

fn parse_duration(src: &str) -> Result<u64, ImlManagerCliError> {
    if src.len() < 2 {
        return Err(DurationParseError::InvalidValue.into());
    }

    let mut val = String::from(src);
    let unit = val.pop();

    let val = val.parse::<u64>()?;

    match unit {
        Some('h') => Ok(val * 3_600_000),
        Some('d') => Ok(val * 86_400_000),
        Some('m') => Ok(val * 60_000),
        Some('s') => Ok(val * 1_000),
        Some('1'..='9') => Err(DurationParseError::NoUnit.into()),
        _ => Err(DurationParseError::InvalidUnit.into()),
    }
}

/// Given some params, does a fetch for the item in the API
async fn fetch_one<T: EndpointName + std::fmt::Debug + serde::de::DeserializeOwned>(
    params: impl serde::Serialize,
) -> Result<T, ImlManagerCliError> {
    let x = get(T::endpoint_name(), params).await?;

    first(x)
}

async fn get_stratagem_config_by_fs_name(
    name: &str,
) -> Result<StratagemConfiguration, ImlManagerCliError> {
    let fs: Filesystem = fetch_one(serde_json::json!({
        "name": name,
        "dehydrate__mgt": false
    }))
    .await?;

    fetch_one(serde_json::json!({ "filesystem": fs.id })).await
}

async fn handle_cmd_resp(
    resp: iml_manager_client::Response,
) -> Result<CmdWrapper, ImlManagerCliError> {
    let status = resp.status();

    let body = resp.text().await.map_err(ImlManagerClientError::from)?;

    if status.is_success() {
        Ok(serde_json::from_str::<CmdWrapper>(&body)?)
    } else if status.is_client_error() {
        tracing::debug!("body: {:?}", body);

        serde_json::from_str::<RunStratagemValidationError>(&body)
            .map(std::convert::Into::into)
            .map(Err)?
    } else if status.is_server_error() {
        Err(RunStratagemValidationError {
            code: RunStratagemCommandResult::ServerError,
            message: "Internal server error.".into(),
        }
        .into())
    } else {
        Err(RunStratagemValidationError {
            code: RunStratagemCommandResult::UnknownError,
            message: "Unknown error.".into(),
        }
        .into())
    }
}

fn list_stratagem_configurations(
    stratagem_configs: Vec<StratagemConfiguration>,
    display_type: DisplayType,
) {
    let term = Term::stdout();

    tracing::debug!("Stratagem Configurations: {:?}", stratagem_configs);

    let x = stratagem_configs.into_display_type(display_type);

    term.write_line(&x).unwrap();
}

async fn report_cli(cmd: ReportCommand) -> Result<(), ImlManagerCliError> {
    match cmd {
        ReportCommand::List { display_type } => {
            let query = stratagem_queries::list_reports::build();

            let resp: iml_graphql_queries::Response<stratagem_queries::list_reports::Resp> =
                graphql(query).await?;
            let reports = Result::from(resp)?.data.stratagem_reports;

            let x = reports.into_display_type(display_type);

            let term = Term::stdout();
            term.write_line(&x).unwrap();

            Ok(())
        }
        ReportCommand::Delete { name } => {
            for n in name {
                let query = stratagem_queries::delete_report::build(n);

                let _resp: iml_graphql_queries::Response<stratagem_queries::delete_report::Resp> =
                    graphql(query).await?;
            }
            Ok(())
        }
    }
}

async fn interval_cli(cmd: IntervalCommand) -> Result<(), ImlManagerCliError> {
    match cmd {
        IntervalCommand::List { display_type } => {
            let r: ApiList<StratagemConfiguration> = wrap_fut(
                "Finding existing intervals...",
                get(
                    StratagemConfiguration::endpoint_name(),
                    serde_json::json!({ "limit": 0 }),
                ),
            )
            .await?;

            if r.objects.is_empty() {
                println!("No Stratagem intervals found");
            } else {
                list_stratagem_configurations(r.objects, display_type);
            }
            Ok(())
        }
        IntervalCommand::Create(c) => {
            let r = post(StratagemConfiguration::endpoint_name(), c).await?;

            let CmdWrapper { command } = handle_cmd_resp(r).await?;

            wait_for_cmd_display(command).await?;
            Ok(())
        }
        IntervalCommand::Remove(StratagemRemoveData { filesystem }) => {
            let x = get_stratagem_config_by_fs_name(&filesystem).await?;

            let r = delete(
                &format!("{}/{}", StratagemConfiguration::endpoint_name(), x.id),
                Vec::<(String, String)>::new(),
            )
            .await?;

            let CmdWrapper { command } = handle_cmd_resp(r).await?;

            wait_for_cmd_display(command).await?;
            Ok(())
        }
    }
}

pub async fn stratagem_cli(command: StratagemCommand) -> Result<(), ImlManagerCliError> {
    match command {
        StratagemCommand::Scan(data) => {
            let r = post("run_stratagem", data).await?;

            tracing::debug!("resp {:?}", r);

            let CmdWrapper { command } = handle_cmd_resp(r).await?;

            wait_for_cmd_display(command).await?;
        }
        StratagemCommand::Filesync(data) => {
            let r = post("run_stratagem", data).await?;
            let CmdWrapper { command } = handle_cmd_resp(r).await?;

            wait_for_cmd_display(command).await?;
        }
        StratagemCommand::Cloudsync(data) => {
            let r = post("run_stratagem", data).await?;

            tracing::debug!("run_cloudsync: {:?}", r);

            let CmdWrapper { command } = handle_cmd_resp(r).await?;

            tracing::debug!("run_cloudsync: {:?}", command);

            wait_for_cmd_display(command).await?;
        }
        StratagemCommand::Interval(cmd) => interval_cli(cmd).await?,
        StratagemCommand::Report { command } => {
            report_cli(command.unwrap_or(ReportCommand::List {
                display_type: DisplayType::Tabular,
            }))
            .await?
        }
    };

    Ok(())
}

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

    #[test]
    fn test_parse_duration_with_days() {
        match parse_duration("273d") {
            Ok(x) => assert_eq!(x, 23_587_200_000),
            _ => panic!("Duration parser should not have errored!"),
        }
    }

    #[test]
    fn test_parse_duration_with_hours() {
        match parse_duration("273h") {
            Ok(x) => assert_eq!(x, 982_800_000),
            _ => panic!("Duration parser should not have errored!"),
        }
    }

    #[test]
    fn test_parse_duration_with_minutes() {
        match parse_duration("273m") {
            Ok(x) => assert_eq!(x, 16_380_000),
            _ => panic!("Duration parser should not have errored!"),
        }
    }

    #[test]
    fn test_parse_duration_with_seconds() {
        match parse_duration("273s") {
            Ok(x) => assert_eq!(x, 273_000),
            _ => panic!("Duration parser should not have errored!"),
        }
    }

    #[test]
    fn test_parse_duration_with_invalid_unit() {
        match parse_duration("273x") {
            Ok(_) => panic!("Duration should have Errored with InvalidUnit"),
            Err(e) => assert_eq!(
                e.to_string(),
                "Invalid unit. Valid units include 'h' and 'd'."
            ),
        }
    }

    #[test]
    fn test_parse_duration_without_unit() {
        match parse_duration("273") {
            Ok(_) => panic!("Duration should have Errored with NoUnit"),
            Err(e) => assert_eq!(e.to_string(), "No unit specified."),
        }
    }

    #[test]
    fn test_parse_duration_with_insufficient_data() {
        match parse_duration("2") {
            Ok(_) => panic!("Duration should have Errored with InvalidValue"),
            Err(e) => assert_eq!(
                e.to_string(),
                "Invalid value specified. Must be a valid integer."
            ),
        }
    }

    #[test]
    fn test_parse_duration_with_invalid_data() {
        match parse_duration("abch") {
            Ok(_) => panic!("Duration should have Errored with invalid digit"),
            Err(e) => assert_eq!(e.to_string(), "invalid digit found in string"),
        }
    }
}
<commit_msg>EX-1932 cloudsync: remove unused option from command line (#2327)<commit_after>// Copyright (c) 2020 DDN. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

use crate::{
    api_utils::{delete, first, get, graphql, post, wait_for_cmd_display},
    display_utils::{wrap_fut, DisplayType, IntoDisplayType as _},
    error::{
        DurationParseError, ImlManagerCliError, RunStratagemCommandResult,
        RunStratagemValidationError,
    },
};
use console::Term;
use iml_graphql_queries::stratagem as stratagem_queries;
use iml_manager_client::ImlManagerClientError;
use iml_wire_types::{ApiList, CmdWrapper, EndpointName, Filesystem, StratagemConfiguration};
use structopt::{clap::arg_enum, StructOpt};

#[derive(Debug, StructOpt)]
pub enum StratagemCommand {
    /// Kickoff a Stratagem scan
    #[structopt(name = "scan")]
    Scan(StratagemScanData),
    /// Configure Stratagem scanning interval
    #[structopt(name = "interval")]
    Interval(IntervalCommand),
    /// Kickoff a Stratagem Filesync
    #[structopt(name = "filesync")]
    Filesync(StratagemFilesyncData),
    /// Kickoff a Stratagem Cloudsync
    #[structopt(name = "cloudsync")]
    Cloudsync(StratagemCloudsyncData),
    /// Work with Stratagem reports
    #[structopt(name = "report")]
    Report {
        #[structopt(subcommand)]
        command: Option<ReportCommand>,
    },
}

#[derive(Debug, StructOpt)]
pub enum ReportCommand {
    /// List all existing Stratagem reports (default)
    #[structopt(name = "list")]
    List {
        /// Display type: json, yaml, tabular
        #[structopt(short = "d", long = "display", default_value = "tabular")]
        display_type: DisplayType,
    },
    /// Delete Stratagem reports
    #[structopt(name = "remove")]
    Delete {
        /// Report name to delete
        #[structopt(required = true, min_values = 1)]
        name: Vec<String>,
    },
}

#[derive(Debug, StructOpt)]
pub enum IntervalCommand {
    /// List all existing Stratagem intervals
    #[structopt(name = "list")]
    List {
        /// Display type: json, yaml, tabular
        #[structopt(short = "d", long = "display", default_value = "tabular")]
        display_type: DisplayType,
    },
    /// Create Stratagem scan interval
    #[structopt(name = "create")]
    Create(IntervalCommandConfig),
    /// Remove a Stratagem interval
    #[structopt(name = "remove")]
    Remove(StratagemRemoveData),
}

#[derive(Debug, StructOpt, serde::Serialize)]
pub struct IntervalCommandConfig {
    /// Filesystem to configure
    filesystem: String,
    /// Interval to scan
    #[structopt(parse(try_from_str = parse_duration))]
    interval: u64,
    /// The report duration
    #[structopt(short = "r", long = "report", parse(try_from_str = parse_duration))]
    report_duration: Option<u64>,
    /// The purge duration
    #[structopt(short = "p", long = "purge", parse(try_from_str = parse_duration))]
    purge_duration: Option<u64>,
}

#[derive(Debug, StructOpt, serde::Serialize)]
pub struct StratagemRemoveData {
    /// Filesystem to unconfigure
    filesystem: String,
}

#[derive(serde::Serialize, StructOpt, Debug)]
pub struct StratagemScanData {
    /// The name of the filesystem to scan
    filesystem: String,
    /// The report duration
    #[structopt(short = "r", long = "report", parse(try_from_str = parse_duration))]
    report_duration: Option<u64>,
    /// The purge duration
    #[structopt(short = "p", long = "purge", parse(try_from_str = parse_duration))]
    purge_duration: Option<u64>,
}

arg_enum! {
    #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)]
    #[serde(rename_all = "lowercase")]
    pub enum FilesyncAction {
    Push,
    }
}

arg_enum! {
    #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)]
    #[serde(rename_all = "lowercase")]
    pub enum CloudsyncAction {
    Push,
    Pull,
    }
}

#[derive(serde::Serialize, StructOpt, Debug)]
pub struct StratagemFilesyncData {
    /// action, 'push' is supported
    #[structopt()]
    action: FilesyncAction,
    /// The name of the filesystem to scan
    #[structopt(short = "f", long = "filesystem")]
    filesystem: String,
    /// The remote filesystem
    #[structopt(short = "r", long = "remote")]
    remote: String,
    /// Match expression
    #[structopt(short = "e", long = "expression")]
    expression: String,
    #[structopt(skip = true)]
    filesync: bool,
}

#[derive(serde::Serialize, StructOpt, Debug)]
pub struct StratagemCloudsyncData {
    /// action, either push or pull
    #[structopt()]
    action: CloudsyncAction,
    /// The name of the filesystem to scan
    #[structopt(short = "f", long = "filesystem")]
    filesystem: String,
    /// The s3 instance
    #[structopt(short = "r", long = "remote")]
    remote: String,
    /// Match expression
    #[structopt(short = "e", long = "expression")]
    expression: String,
    #[structopt(skip = true)]
    cloudsync: bool,
}

fn parse_duration(src: &str) -> Result<u64, ImlManagerCliError> {
    if src.len() < 2 {
        return Err(DurationParseError::InvalidValue.into());
    }

    let mut val = String::from(src);
    let unit = val.pop();

    let val = val.parse::<u64>()?;

    match unit {
        Some('h') => Ok(val * 3_600_000),
        Some('d') => Ok(val * 86_400_000),
        Some('m') => Ok(val * 60_000),
        Some('s') => Ok(val * 1_000),
        Some('1'..='9') => Err(DurationParseError::NoUnit.into()),
        _ => Err(DurationParseError::InvalidUnit.into()),
    }
}

/// Given some params, does a fetch for the item in the API
async fn fetch_one<T: EndpointName + std::fmt::Debug + serde::de::DeserializeOwned>(
    params: impl serde::Serialize,
) -> Result<T, ImlManagerCliError> {
    let x = get(T::endpoint_name(), params).await?;

    first(x)
}

async fn get_stratagem_config_by_fs_name(
    name: &str,
) -> Result<StratagemConfiguration, ImlManagerCliError> {
    let fs: Filesystem = fetch_one(serde_json::json!({
        "name": name,
        "dehydrate__mgt": false
    }))
    .await?;

    fetch_one(serde_json::json!({ "filesystem": fs.id })).await
}

async fn handle_cmd_resp(
    resp: iml_manager_client::Response,
) -> Result<CmdWrapper, ImlManagerCliError> {
    let status = resp.status();

    let body = resp.text().await.map_err(ImlManagerClientError::from)?;

    if status.is_success() {
        Ok(serde_json::from_str::<CmdWrapper>(&body)?)
    } else if status.is_client_error() {
        tracing::debug!("body: {:?}", body);

        serde_json::from_str::<RunStratagemValidationError>(&body)
            .map(std::convert::Into::into)
            .map(Err)?
    } else if status.is_server_error() {
        Err(RunStratagemValidationError {
            code: RunStratagemCommandResult::ServerError,
            message: "Internal server error.".into(),
        }
        .into())
    } else {
        Err(RunStratagemValidationError {
            code: RunStratagemCommandResult::UnknownError,
            message: "Unknown error.".into(),
        }
        .into())
    }
}

fn list_stratagem_configurations(
    stratagem_configs: Vec<StratagemConfiguration>,
    display_type: DisplayType,
) {
    let term = Term::stdout();

    tracing::debug!("Stratagem Configurations: {:?}", stratagem_configs);

    let x = stratagem_configs.into_display_type(display_type);

    term.write_line(&x).unwrap();
}

async fn report_cli(cmd: ReportCommand) -> Result<(), ImlManagerCliError> {
    match cmd {
        ReportCommand::List { display_type } => {
            let query = stratagem_queries::list_reports::build();

            let resp: iml_graphql_queries::Response<stratagem_queries::list_reports::Resp> =
                graphql(query).await?;
            let reports = Result::from(resp)?.data.stratagem_reports;

            let x = reports.into_display_type(display_type);

            let term = Term::stdout();
            term.write_line(&x).unwrap();

            Ok(())
        }
        ReportCommand::Delete { name } => {
            for n in name {
                let query = stratagem_queries::delete_report::build(n);

                let _resp: iml_graphql_queries::Response<stratagem_queries::delete_report::Resp> =
                    graphql(query).await?;
            }
            Ok(())
        }
    }
}

async fn interval_cli(cmd: IntervalCommand) -> Result<(), ImlManagerCliError> {
    match cmd {
        IntervalCommand::List { display_type } => {
            let r: ApiList<StratagemConfiguration> = wrap_fut(
                "Finding existing intervals...",
                get(
                    StratagemConfiguration::endpoint_name(),
                    serde_json::json!({ "limit": 0 }),
                ),
            )
            .await?;

            if r.objects.is_empty() {
                println!("No Stratagem intervals found");
            } else {
                list_stratagem_configurations(r.objects, display_type);
            }
            Ok(())
        }
        IntervalCommand::Create(c) => {
            let r = post(StratagemConfiguration::endpoint_name(), c).await?;

            let CmdWrapper { command } = handle_cmd_resp(r).await?;

            wait_for_cmd_display(command).await?;
            Ok(())
        }
        IntervalCommand::Remove(StratagemRemoveData { filesystem }) => {
            let x = get_stratagem_config_by_fs_name(&filesystem).await?;

            let r = delete(
                &format!("{}/{}", StratagemConfiguration::endpoint_name(), x.id),
                Vec::<(String, String)>::new(),
            )
            .await?;

            let CmdWrapper { command } = handle_cmd_resp(r).await?;

            wait_for_cmd_display(command).await?;
            Ok(())
        }
    }
}

pub async fn stratagem_cli(command: StratagemCommand) -> Result<(), ImlManagerCliError> {
    match command {
        StratagemCommand::Scan(data) => {
            let r = post("run_stratagem", data).await?;

            tracing::debug!("resp {:?}", r);

            let CmdWrapper { command } = handle_cmd_resp(r).await?;

            wait_for_cmd_display(command).await?;
        }
        StratagemCommand::Filesync(data) => {
            let r = post("run_stratagem", data).await?;
            let CmdWrapper { command } = handle_cmd_resp(r).await?;

            wait_for_cmd_display(command).await?;
        }
        StratagemCommand::Cloudsync(data) => {
            let r = post("run_stratagem", data).await?;

            tracing::debug!("run_cloudsync: {:?}", r);

            let CmdWrapper { command } = handle_cmd_resp(r).await?;

            tracing::debug!("run_cloudsync: {:?}", command);

            wait_for_cmd_display(command).await?;
        }
        StratagemCommand::Interval(cmd) => interval_cli(cmd).await?,
        StratagemCommand::Report { command } => {
            report_cli(command.unwrap_or(ReportCommand::List {
                display_type: DisplayType::Tabular,
            }))
            .await?
        }
    };

    Ok(())
}

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

    #[test]
    fn test_parse_duration_with_days() {
        match parse_duration("273d") {
            Ok(x) => assert_eq!(x, 23_587_200_000),
            _ => panic!("Duration parser should not have errored!"),
        }
    }

    #[test]
    fn test_parse_duration_with_hours() {
        match parse_duration("273h") {
            Ok(x) => assert_eq!(x, 982_800_000),
            _ => panic!("Duration parser should not have errored!"),
        }
    }

    #[test]
    fn test_parse_duration_with_minutes() {
        match parse_duration("273m") {
            Ok(x) => assert_eq!(x, 16_380_000),
            _ => panic!("Duration parser should not have errored!"),
        }
    }

    #[test]
    fn test_parse_duration_with_seconds() {
        match parse_duration("273s") {
            Ok(x) => assert_eq!(x, 273_000),
            _ => panic!("Duration parser should not have errored!"),
        }
    }

    #[test]
    fn test_parse_duration_with_invalid_unit() {
        match parse_duration("273x") {
            Ok(_) => panic!("Duration should have Errored with InvalidUnit"),
            Err(e) => assert_eq!(
                e.to_string(),
                "Invalid unit. Valid units include 'h' and 'd'."
            ),
        }
    }

    #[test]
    fn test_parse_duration_without_unit() {
        match parse_duration("273") {
            Ok(_) => panic!("Duration should have Errored with NoUnit"),
            Err(e) => assert_eq!(e.to_string(), "No unit specified."),
        }
    }

    #[test]
    fn test_parse_duration_with_insufficient_data() {
        match parse_duration("2") {
            Ok(_) => panic!("Duration should have Errored with InvalidValue"),
            Err(e) => assert_eq!(
                e.to_string(),
                "Invalid value specified. Must be a valid integer."
            ),
        }
    }

    #[test]
    fn test_parse_duration_with_invalid_data() {
        match parse_duration("abch") {
            Ok(_) => panic!("Duration should have Errored with invalid digit"),
            Err(e) => assert_eq!(e.to_string(), "invalid digit found in string"),
        }
    }
}
<|endoftext|>


<commit_before>
/// A priority queue implemented with a binary heap
use core::cmp::Ord;

pub struct PriorityQueue <T: Copy Ord>{
    priv data: ~[T],
}

impl <T: Copy Ord> PriorityQueue<T> {
    /// Returns the greatest item in the queue - fails if empty
    pure fn top(&self) -> T { self.data[0] }

    /// Returns the greatest item in the queue - None if empty
    pure fn maybe_top(&self) -> Option<T> {
        if self.is_empty() { None } else { Some(self.top()) }
    }

    /// Returns the length of the queue
    pure fn len(&self) -> uint { self.data.len() }

    /// Returns true if a queue contains no elements
    pure fn is_empty(&self) -> bool { self.data.is_empty() }

    /// Returns true if a queue contains some elements
    pure fn is_not_empty(&self) -> bool { self.data.is_not_empty() }

    /// Returns the number of elements the queue can hold without reallocating
    pure fn capacity(&self) -> uint { vec::capacity(&self.data) }

    fn reserve(&mut self, n: uint) { vec::reserve(&mut self.data, n) }

    fn reserve_at_least(&mut self, n: uint) {
        vec::reserve_at_least(&mut self.data, n)
    }

    /// Drop all items from the queue
    fn clear(&mut self) { self.data.truncate(0) }

    /// Pop the greatest item from the queue - fails if empty
    fn pop(&mut self) -> T {
        let mut item = self.data.pop();
        if self.is_not_empty() { item <-> self.data[0]; self.siftup(0); }
        item
    }

    /// Pop the greatest item from the queue - None if empty
    fn maybe_pop(&mut self) -> Option<T> {
        if self.is_empty() { None } else { Some(self.pop()) }
    }

    /// Push an item onto the queue
    fn push(&mut self, item: T) {
        self.data.push(item);
        self.siftdown(0, self.len() - 1);
    }

    /// Optimized version of a push followed by a pop
    fn push_pop(&mut self, item: T) -> T {
        let mut item = item;
        if self.is_not_empty() && self.data[0] > item {
            item <-> self.data[0];
            self.siftup(0);
        }
        item
    }

    /// Optimized version of a pop followed by a push - fails if empty
    fn replace(&mut self, item: T) -> T {
        let mut item = item;
        item <-> self.data[0];
        self.siftup(0);
        item
    }

    /// Consume the PriorityQueue and return the underlying vector
    pure fn to_vec(self) -> ~[T] { let PriorityQueue{data: v} = self; v }

    /// Consume the PriorityQueue and return a vector in sorted (ascending) order
    pure fn to_sorted_vec(self) -> ~[T] {
        let mut q = self;
        let mut end = q.len() - 1;
        while end > 0 {
            q.data[end] <-> q.data[0];
            end -= 1;
            unsafe { q.siftup_range(0, end) } // purity-checking workaround
        }
        q.to_vec()
    }

    static pub pure fn from_vec(xs: ~[T]) -> PriorityQueue<T> {
        let mut q = PriorityQueue{data: xs,};
        let mut n = q.len() / 2;
        while n > 0 {
            n -= 1;
            unsafe { q.siftup(n) }; // purity-checking workaround
        }
        q
    }

    priv fn siftdown(&mut self, startpos: uint, pos: uint) {
        let mut pos = pos;
        let newitem = self.data[pos];

        while pos > startpos {
            let parentpos = (pos - 1) >> 1;
            let parent = self.data[parentpos];
            if newitem > parent {
                self.data[pos] = parent;
                pos = parentpos;
                loop
            }
            break
        }
        self.data[pos] = newitem;
    }

    priv fn siftup_range(&mut self, pos: uint, endpos: uint) {
        let mut pos = pos;
        let startpos = pos;
        let newitem = self.data[pos];

        let mut childpos = 2 * pos + 1;
        while childpos < endpos {
            let rightpos = childpos + 1;
            if rightpos < endpos &&
                   !(self.data[childpos] > self.data[rightpos]) {
                childpos = rightpos;
            }
            self.data[pos] = self.data[childpos];
            pos = childpos;
            childpos = 2 * pos + 1;
        }
        self.data[pos] = newitem;
        self.siftdown(startpos, pos);
    }

    priv fn siftup(&mut self, pos: uint) {
        self.siftup_range(pos, self.len());
    }
}

#[cfg(test)]
mod tests {
    use sort::merge_sort;
    use core::cmp::le;
    use PriorityQueue::from_vec;

    #[test]
    fn test_top_and_pop() {
        let data = ~[2, 4, 6, 2, 1, 8, 10, 3, 5, 7, 0, 9, 1];
        let mut sorted = merge_sort(data, le);
        let mut heap = from_vec(data);
        while heap.is_not_empty() {
            assert heap.top() == sorted.last();
            assert heap.pop() == sorted.pop();
        }
    }

    #[test]
    fn test_push() {
        let mut heap = from_vec(~[2, 4, 9]);
        assert heap.len() == 3;
        assert heap.top() == 9;
        heap.push(11);
        assert heap.len() == 4;
        assert heap.top() == 11;
        heap.push(5);
        assert heap.len() == 5;
        assert heap.top() == 11;
        heap.push(27);
        assert heap.len() == 6;
        assert heap.top() == 27;
        heap.push(3);
        assert heap.len() == 7;
        assert heap.top() == 27;
        heap.push(103);
        assert heap.len() == 8;
        assert heap.top() == 103;
    }

    #[test]
    fn test_push_pop() {
        let mut heap = from_vec(~[5, 5, 2, 1, 3]);
        assert heap.len() == 5;
        assert heap.push_pop(6) == 6;
        assert heap.len() == 5;
        assert heap.push_pop(0) == 5;
        assert heap.len() == 5;
        assert heap.push_pop(4) == 5;
        assert heap.len() == 5;
        assert heap.push_pop(1) == 4;
        assert heap.len() == 5;
    }

    #[test]
    fn test_replace() {
        let mut heap = from_vec(~[5, 5, 2, 1, 3]);
        assert heap.len() == 5;
        assert heap.replace(6) == 5;
        assert heap.len() == 5;
        assert heap.replace(0) == 6;
        assert heap.len() == 5;
        assert heap.replace(4) == 5;
        assert heap.len() == 5;
        assert heap.replace(1) == 4;
        assert heap.len() == 5;
    }

    #[test]
    fn test_to_sorted_vec() {
        let data = ~[2, 4, 6, 2, 1, 8, 10, 3, 5, 7, 0, 9, 1];
        assert from_vec(data).to_sorted_vec() == merge_sort(data, le);
    }

    #[test]
    #[should_fail]
    fn test_empty_pop() { let mut heap = from_vec::<int>(~[]); heap.pop(); }

    #[test]
    fn test_empty_maybe_pop() {
        let mut heap = from_vec::<int>(~[]);
        assert heap.maybe_pop().is_none();
    }

    #[test]
    #[should_fail]
    fn test_empty_top() { from_vec::<int>(~[]).top(); }

    #[test]
    fn test_empty_maybe_top() {
        assert from_vec::<int>(~[]).maybe_top().is_none();
    }

    #[test]
    #[should_fail]
    fn test_empty_replace() {
        let mut heap = from_vec::<int>(~[]);
        heap.replace(5);
    }

    #[test]
    fn test_to_vec() {
        let data = ~[1, 3, 5, 7, 9, 2, 4, 6, 8, 0];
        let heap = from_vec(copy data);
        assert merge_sort(heap.to_vec(), le) == merge_sort(data, le);
    }
}
<commit_msg>priority_queue: fix to_sorted_vec off-by-one error<commit_after>
/// A priority queue implemented with a binary heap
use core::cmp::Ord;

pub struct PriorityQueue <T: Copy Ord>{
    priv data: ~[T],
}

impl <T: Copy Ord> PriorityQueue<T> {
    /// Returns the greatest item in the queue - fails if empty
    pure fn top(&self) -> T { self.data[0] }

    /// Returns the greatest item in the queue - None if empty
    pure fn maybe_top(&self) -> Option<T> {
        if self.is_empty() { None } else { Some(self.top()) }
    }

    /// Returns the length of the queue
    pure fn len(&self) -> uint { self.data.len() }

    /// Returns true if a queue contains no elements
    pure fn is_empty(&self) -> bool { self.data.is_empty() }

    /// Returns true if a queue contains some elements
    pure fn is_not_empty(&self) -> bool { self.data.is_not_empty() }

    /// Returns the number of elements the queue can hold without reallocating
    pure fn capacity(&self) -> uint { vec::capacity(&self.data) }

    fn reserve(&mut self, n: uint) { vec::reserve(&mut self.data, n) }

    fn reserve_at_least(&mut self, n: uint) {
        vec::reserve_at_least(&mut self.data, n)
    }

    /// Drop all items from the queue
    fn clear(&mut self) { self.data.truncate(0) }

    /// Pop the greatest item from the queue - fails if empty
    fn pop(&mut self) -> T {
        let mut item = self.data.pop();
        if self.is_not_empty() { item <-> self.data[0]; self.siftup(0); }
        item
    }

    /// Pop the greatest item from the queue - None if empty
    fn maybe_pop(&mut self) -> Option<T> {
        if self.is_empty() { None } else { Some(self.pop()) }
    }

    /// Push an item onto the queue
    fn push(&mut self, item: T) {
        self.data.push(item);
        self.siftdown(0, self.len() - 1);
    }

    /// Optimized version of a push followed by a pop
    fn push_pop(&mut self, item: T) -> T {
        let mut item = item;
        if self.is_not_empty() && self.data[0] > item {
            item <-> self.data[0];
            self.siftup(0);
        }
        item
    }

    /// Optimized version of a pop followed by a push - fails if empty
    fn replace(&mut self, item: T) -> T {
        let mut item = item;
        item <-> self.data[0];
        self.siftup(0);
        item
    }

    /// Consume the PriorityQueue and return the underlying vector
    pure fn to_vec(self) -> ~[T] { let PriorityQueue{data: v} = self; v }

    /// Consume the PriorityQueue and return a vector in sorted (ascending) order
    pure fn to_sorted_vec(self) -> ~[T] {
        let mut q = self;
        let mut end = q.len();
        while end > 1 {
            end -= 1;
            q.data[end] <-> q.data[0];
            unsafe { q.siftup_range(0, end) } // purity-checking workaround
        }
        q.to_vec()
    }

    static pub pure fn from_vec(xs: ~[T]) -> PriorityQueue<T> {
        let mut q = PriorityQueue{data: xs,};
        let mut n = q.len() / 2;
        while n > 0 {
            n -= 1;
            unsafe { q.siftup(n) }; // purity-checking workaround
        }
        q
    }

    priv fn siftdown(&mut self, startpos: uint, pos: uint) {
        let mut pos = pos;
        let newitem = self.data[pos];

        while pos > startpos {
            let parentpos = (pos - 1) >> 1;
            let parent = self.data[parentpos];
            if newitem > parent {
                self.data[pos] = parent;
                pos = parentpos;
                loop
            }
            break
        }
        self.data[pos] = newitem;
    }

    priv fn siftup_range(&mut self, pos: uint, endpos: uint) {
        let mut pos = pos;
        let startpos = pos;
        let newitem = self.data[pos];

        let mut childpos = 2 * pos + 1;
        while childpos < endpos {
            let rightpos = childpos + 1;
            if rightpos < endpos &&
                   !(self.data[childpos] > self.data[rightpos]) {
                childpos = rightpos;
            }
            self.data[pos] = self.data[childpos];
            pos = childpos;
            childpos = 2 * pos + 1;
        }
        self.data[pos] = newitem;
        self.siftdown(startpos, pos);
    }

    priv fn siftup(&mut self, pos: uint) {
        self.siftup_range(pos, self.len());
    }
}

#[cfg(test)]
mod tests {
    use sort::merge_sort;
    use core::cmp::le;
    use PriorityQueue::from_vec;

    #[test]
    fn test_top_and_pop() {
        let data = ~[2, 4, 6, 2, 1, 8, 10, 3, 5, 7, 0, 9, 1];
        let mut sorted = merge_sort(data, le);
        let mut heap = from_vec(data);
        while heap.is_not_empty() {
            assert heap.top() == sorted.last();
            assert heap.pop() == sorted.pop();
        }
    }

    #[test]
    fn test_push() {
        let mut heap = from_vec(~[2, 4, 9]);
        assert heap.len() == 3;
        assert heap.top() == 9;
        heap.push(11);
        assert heap.len() == 4;
        assert heap.top() == 11;
        heap.push(5);
        assert heap.len() == 5;
        assert heap.top() == 11;
        heap.push(27);
        assert heap.len() == 6;
        assert heap.top() == 27;
        heap.push(3);
        assert heap.len() == 7;
        assert heap.top() == 27;
        heap.push(103);
        assert heap.len() == 8;
        assert heap.top() == 103;
    }

    #[test]
    fn test_push_pop() {
        let mut heap = from_vec(~[5, 5, 2, 1, 3]);
        assert heap.len() == 5;
        assert heap.push_pop(6) == 6;
        assert heap.len() == 5;
        assert heap.push_pop(0) == 5;
        assert heap.len() == 5;
        assert heap.push_pop(4) == 5;
        assert heap.len() == 5;
        assert heap.push_pop(1) == 4;
        assert heap.len() == 5;
    }

    #[test]
    fn test_replace() {
        let mut heap = from_vec(~[5, 5, 2, 1, 3]);
        assert heap.len() == 5;
        assert heap.replace(6) == 5;
        assert heap.len() == 5;
        assert heap.replace(0) == 6;
        assert heap.len() == 5;
        assert heap.replace(4) == 5;
        assert heap.len() == 5;
        assert heap.replace(1) == 4;
        assert heap.len() == 5;
    }

    fn check_to_vec(data: ~[int]) {
        let heap = from_vec(data);
        assert merge_sort(heap.to_vec(), le) == merge_sort(data, le);
        assert heap.to_sorted_vec() == merge_sort(data, le);
    }

    #[test]
    fn test_to_vec() {
        check_to_vec(~[]);
        check_to_vec(~[5]);
        check_to_vec(~[3, 2]);
        check_to_vec(~[2, 3]);
        check_to_vec(~[5, 1, 2]);
        check_to_vec(~[1, 100, 2, 3]);
        check_to_vec(~[1, 3, 5, 7, 9, 2, 4, 6, 8, 0]);
        check_to_vec(~[2, 4, 6, 2, 1, 8, 10, 3, 5, 7, 0, 9, 1]);
        check_to_vec(~[9, 11, 9, 9, 9, 9, 11, 2, 3, 4, 11, 9, 0, 0, 0, 0]);
        check_to_vec(~[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
        check_to_vec(~[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]);
        check_to_vec(~[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 1, 2]);
        check_to_vec(~[5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 3, 2, 1]);
    }

    #[test]
    #[should_fail]
    fn test_empty_pop() { let mut heap = from_vec::<int>(~[]); heap.pop(); }

    #[test]
    fn test_empty_maybe_pop() {
        let mut heap = from_vec::<int>(~[]);
        assert heap.maybe_pop().is_none();
    }

    #[test]
    #[should_fail]
    fn test_empty_top() { from_vec::<int>(~[]).top(); }

    #[test]
    fn test_empty_maybe_top() {
        assert from_vec::<int>(~[]).maybe_top().is_none();
    }

    #[test]
    #[should_fail]
    fn test_empty_replace() {
        let mut heap = from_vec::<int>(~[]);
        heap.replace(5);
    }
}
<|endoftext|>


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

//! Abstraction of a thread pool for basic parallelism.

#![unstable(feature = "std_misc",
            reason = "the semantics of a failing task and whether a thread is \
                      re-attached to a thread pool are somewhat unclear, and the \
                      utility of this type in `std::sync` is questionable with \
                      respect to the jobs of other primitives")]

use core::prelude::*;

use sync::{Arc, Mutex};
use sync::mpsc::{channel, Sender, Receiver};
use thread;
use thunk::Thunk;

struct Sentinel<'a> {
    jobs: &'a Arc<Mutex<Receiver<Thunk<'static>>>>,
    active: bool
}

impl<'a> Sentinel<'a> {
    fn new(jobs: &'a Arc<Mutex<Receiver<Thunk<'static>>>>) -> Sentinel<'a> {
        Sentinel {
            jobs: jobs,
            active: true
        }
    }

    // Cancel and destroy this sentinel.
    fn cancel(mut self) {
        self.active = false;
    }
}

#[unsafe_destructor]
impl<'a> Drop for Sentinel<'a> {
    fn drop(&mut self) {
        if self.active {
            spawn_in_pool(self.jobs.clone())
        }
    }
}

/// A thread pool used to execute functions in parallel.
///
/// Spawns `n` worker threads and replenishes the pool if any worker threads
/// panic.
///
/// # Example
///
/// ```rust
/// use std::sync::TaskPool;
/// use std::iter::AdditiveIterator;
/// use std::sync::mpsc::channel;
///
/// let pool = TaskPool::new(4);
///
/// let (tx, rx) = channel();
/// for _ in 0..8 {
///     let tx = tx.clone();
///     pool.execute(move|| {
///         tx.send(1_u32).unwrap();
///     });
/// }
///
/// assert_eq!(rx.iter().take(8).sum(), 8);
/// ```
pub struct TaskPool {
    // How the threadpool communicates with subthreads.
    //
    // This is the only such Sender, so when it is dropped all subthreads will
    // quit.
    jobs: Sender<Thunk<'static>>
}

impl TaskPool {
    /// Spawns a new thread pool with `threads` threads.
    ///
    /// # Panics
    ///
    /// This function will panic if `threads` is 0.
    pub fn new(threads: usize) -> TaskPool {
        assert!(threads >= 1);

        let (tx, rx) = channel::<Thunk>();
        let rx = Arc::new(Mutex::new(rx));

        // Threadpool threads
        for _ in 0..threads {
            spawn_in_pool(rx.clone());
        }

        TaskPool { jobs: tx }
    }

    /// Executes the function `job` on a thread in the pool.
    pub fn execute<F>(&self, job: F)
        where F : FnOnce(), F : Send + 'static
    {
        self.jobs.send(Thunk::new(job)).unwrap();
    }
}

fn spawn_in_pool(jobs: Arc<Mutex<Receiver<Thunk<'static>>>>) {
    thread::spawn(move || {
        // Will spawn a new thread on panic unless it is cancelled.
        let sentinel = Sentinel::new(&jobs);

        loop {
            let message = {
                // Only lock jobs for the time it takes
                // to get a job, not run it.
                let lock = jobs.lock().unwrap();
                lock.recv()
            };

            match message {
                Ok(job) => job.invoke(()),

                // The Taskpool was dropped.
                Err(..) => break
            }
        }

        sentinel.cancel();
    });
}

#[cfg(test)]
mod test {
    use prelude::v1::*;
    use super::*;
    use sync::mpsc::channel;

    const TEST_TASKS: usize = 4;

    #[test]
    fn test_works() {
        use iter::AdditiveIterator;

        let pool = TaskPool::new(TEST_TASKS);

        let (tx, rx) = channel();
        for _ in 0..TEST_TASKS {
            let tx = tx.clone();
            pool.execute(move|| {
                tx.send(1).unwrap();
            });
        }

        assert_eq!(rx.iter().take(TEST_TASKS).sum(), TEST_TASKS);
    }

    #[test]
    #[should_fail]
    fn test_zero_tasks_panic() {
        TaskPool::new(0);
    }

    #[test]
    fn test_recovery_from_subtask_panic() {
        use iter::AdditiveIterator;

        let pool = TaskPool::new(TEST_TASKS);

        // Panic all the existing threads.
        for _ in 0..TEST_TASKS {
            pool.execute(move|| -> () { panic!() });
        }

        // Ensure new threads were spawned to compensate.
        let (tx, rx) = channel();
        for _ in 0..TEST_TASKS {
            let tx = tx.clone();
            pool.execute(move|| {
                tx.send(1).unwrap();
            });
        }

        assert_eq!(rx.iter().take(TEST_TASKS).sum(), TEST_TASKS);
    }

    #[test]
    fn test_should_not_panic_on_drop_if_subtasks_panic_after_drop() {
        use sync::{Arc, Barrier};

        let pool = TaskPool::new(TEST_TASKS);
        let waiter = Arc::new(Barrier::new(TEST_TASKS + 1));

        // Panic all the existing threads in a bit.
        for _ in 0..TEST_TASKS {
            let waiter = waiter.clone();
            pool.execute(move|| {
                waiter.wait();
                panic!();
            });
        }

        drop(pool);

        // Kick off the failure.
        waiter.wait();
    }
}
<commit_msg>Rollup merge of #22783 - alexcrichton:deprecate-taskpool, r=alexcrichton<commit_after>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Abstraction of a thread pool for basic parallelism.

#![deprecated(since = "1.0.0",
              reason = "This kind of API needs some time to bake in \
                        crates.io. This functionality is available through \
                        https://crates.io/crates/threadpool")]
#![unstable(feature = "std_misc")]

use core::prelude::*;

use sync::{Arc, Mutex};
use sync::mpsc::{channel, Sender, Receiver};
use thread;
use thunk::Thunk;

struct Sentinel<'a> {
    jobs: &'a Arc<Mutex<Receiver<Thunk<'static>>>>,
    active: bool
}

impl<'a> Sentinel<'a> {
    fn new(jobs: &'a Arc<Mutex<Receiver<Thunk<'static>>>>) -> Sentinel<'a> {
        Sentinel {
            jobs: jobs,
            active: true
        }
    }

    // Cancel and destroy this sentinel.
    fn cancel(mut self) {
        self.active = false;
    }
}

#[unsafe_destructor]
impl<'a> Drop for Sentinel<'a> {
    fn drop(&mut self) {
        if self.active {
            spawn_in_pool(self.jobs.clone())
        }
    }
}

/// A thread pool used to execute functions in parallel.
///
/// Spawns `n` worker threads and replenishes the pool if any worker threads
/// panic.
///
/// # Example
///
/// ```rust
/// use std::sync::TaskPool;
/// use std::iter::AdditiveIterator;
/// use std::sync::mpsc::channel;
///
/// let pool = TaskPool::new(4);
///
/// let (tx, rx) = channel();
/// for _ in 0..8 {
///     let tx = tx.clone();
///     pool.execute(move|| {
///         tx.send(1_u32).unwrap();
///     });
/// }
///
/// assert_eq!(rx.iter().take(8).sum(), 8);
/// ```
pub struct TaskPool {
    // How the threadpool communicates with subthreads.
    //
    // This is the only such Sender, so when it is dropped all subthreads will
    // quit.
    jobs: Sender<Thunk<'static>>
}

impl TaskPool {
    /// Spawns a new thread pool with `threads` threads.
    ///
    /// # Panics
    ///
    /// This function will panic if `threads` is 0.
    pub fn new(threads: usize) -> TaskPool {
        assert!(threads >= 1);

        let (tx, rx) = channel::<Thunk>();
        let rx = Arc::new(Mutex::new(rx));

        // Threadpool threads
        for _ in 0..threads {
            spawn_in_pool(rx.clone());
        }

        TaskPool { jobs: tx }
    }

    /// Executes the function `job` on a thread in the pool.
    pub fn execute<F>(&self, job: F)
        where F : FnOnce(), F : Send + 'static
    {
        self.jobs.send(Thunk::new(job)).unwrap();
    }
}

fn spawn_in_pool(jobs: Arc<Mutex<Receiver<Thunk<'static>>>>) {
    thread::spawn(move || {
        // Will spawn a new thread on panic unless it is cancelled.
        let sentinel = Sentinel::new(&jobs);

        loop {
            let message = {
                // Only lock jobs for the time it takes
                // to get a job, not run it.
                let lock = jobs.lock().unwrap();
                lock.recv()
            };

            match message {
                Ok(job) => job.invoke(()),

                // The Taskpool was dropped.
                Err(..) => break
            }
        }

        sentinel.cancel();
    });
}

#[cfg(test)]
mod test {
    use prelude::v1::*;
    use super::*;
    use sync::mpsc::channel;

    const TEST_TASKS: usize = 4;

    #[test]
    fn test_works() {
        use iter::AdditiveIterator;

        let pool = TaskPool::new(TEST_TASKS);

        let (tx, rx) = channel();
        for _ in 0..TEST_TASKS {
            let tx = tx.clone();
            pool.execute(move|| {
                tx.send(1).unwrap();
            });
        }

        assert_eq!(rx.iter().take(TEST_TASKS).sum(), TEST_TASKS);
    }

    #[test]
    #[should_fail]
    fn test_zero_tasks_panic() {
        TaskPool::new(0);
    }

    #[test]
    fn test_recovery_from_subtask_panic() {
        use iter::AdditiveIterator;

        let pool = TaskPool::new(TEST_TASKS);

        // Panic all the existing threads.
        for _ in 0..TEST_TASKS {
            pool.execute(move|| -> () { panic!() });
        }

        // Ensure new threads were spawned to compensate.
        let (tx, rx) = channel();
        for _ in 0..TEST_TASKS {
            let tx = tx.clone();
            pool.execute(move|| {
                tx.send(1).unwrap();
            });
        }

        assert_eq!(rx.iter().take(TEST_TASKS).sum(), TEST_TASKS);
    }

    #[test]
    fn test_should_not_panic_on_drop_if_subtasks_panic_after_drop() {
        use sync::{Arc, Barrier};

        let pool = TaskPool::new(TEST_TASKS);
        let waiter = Arc::new(Barrier::new(TEST_TASKS + 1));

        // Panic all the existing threads in a bit.
        for _ in 0..TEST_TASKS {
            let waiter = waiter.clone();
            pool.execute(move|| {
                waiter.wait();
                panic!();
            });
        }

        drop(pool);

        // Kick off the failure.
        waiter.wait();
    }
}
<|endoftext|>


<commit_before>use std::fs::{self, File};
use std::io::prelude::*;

use support::{project, execs, path2url};
use support::COMPILING;
use support::paths::CargoPathExt;
use hamcrest::{assert_that, existing_file};

fn setup() {}

test!(modifying_and_moving {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [package]
            name = "foo"
            authors = []
            version = "0.0.1"
        "#)
        .file("src/main.rs", r#"
            mod a; fn main() {}
        "#)
        .file("src/a.rs", "");

    assert_that(p.cargo_process("build"),
                execs().with_status(0).with_stdout(format!("\
{compiling} foo v0.0.1 ({dir})
", compiling = COMPILING, dir = path2url(p.root()))));

    assert_that(p.cargo("build"),
                execs().with_status(0).with_stdout(""));
    p.root().move_into_the_past().unwrap();
    p.root().join("target").move_into_the_past().unwrap();

    File::create(&p.root().join("src/a.rs")).unwrap()
         .write_all(b"fn main() {}").unwrap();
    assert_that(p.cargo("build"),
                execs().with_status(0).with_stdout(format!("\
{compiling} foo v0.0.1 ({dir})
", compiling = COMPILING, dir = path2url(p.root()))));

    fs::rename(&p.root().join("src/a.rs"), &p.root().join("src/b.rs")).unwrap();
    assert_that(p.cargo("build"),
                execs().with_status(101));
});

test!(modify_only_some_files {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [package]
            name = "foo"
            authors = []
            version = "0.0.1"
        "#)
        .file("src/lib.rs", "mod a;")
        .file("src/a.rs", "")
        .file("src/main.rs", r#"
            mod b;
            fn main() {}
        "#)
        .file("src/b.rs", "")
        .file("tests/test.rs", "");

    assert_that(p.cargo_process("build"),
                execs().with_status(0).with_stdout(format!("\
{compiling} foo v0.0.1 ({dir})
", compiling = COMPILING, dir = path2url(p.root()))));
    assert_that(p.cargo("test"),
                execs().with_status(0));

    assert_that(&p.bin("foo"), existing_file());

    let lib = p.root().join("src/lib.rs");
    let bin = p.root().join("src/b.rs");

    File::create(&lib).unwrap().write_all(b"invalid rust code").unwrap();
    lib.move_into_the_past().unwrap();
    p.root().move_into_the_past().unwrap();

    File::create(&bin).unwrap().write_all(b"fn foo() {}").unwrap();
    // p.root().move_into_the_past().unwrap();
    // p.root().join("target").move_into_the_past().unwrap();

    // Make sure the binary is rebuilt, not the lib
    assert_that(p.cargo("build")
                 .env("RUST_LOG", "cargo::ops::cargo_rustc::fingerprint"),
                execs().with_status(0).with_stdout(format!("\
{compiling} foo v0.0.1 ({dir})
", compiling = COMPILING, dir = path2url(p.root()))));
    assert_that(&p.bin("foo"), existing_file());
});

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

            [dependencies.a]
            path = "a"
            [dependencies.b]
            path = "b"
        "#)
        .file("src/lib.rs", "extern crate a; extern crate b;")
        .file("a/Cargo.toml", r#"
            [package]
            name = "a"
            authors = []
            version = "0.0.1"
            [dependencies.b]
            path = "../b"
        "#)
        .file("a/src/lib.rs", "extern crate b;")
        .file("b/Cargo.toml", r#"
            [package]
            name = "b"
            authors = []
            version = "0.0.1"
        "#)
        .file("b/src/lib.rs", "");

    assert_that(p.cargo_process("build"),
                execs().with_status(0));

    File::create(&p.root().join("b/src/lib.rs")).unwrap().write_all(br#"
        pub fn b() {}
    "#).unwrap();

    assert_that(p.cargo("build").arg("-pb"),
                execs().with_status(0));

    File::create(&p.root().join("src/lib.rs")).unwrap().write_all(br#"
        extern crate a;
        extern crate b;
        pub fn toplevel() {}
    "#).unwrap();

    assert_that(p.cargo("build"),
                execs().with_status(0));
});

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

            [features]
            foo = []
        "#)
        .file("src/lib.rs", "");

    assert_that(p.cargo_process("build"),
                execs().with_status(0)
                       .with_stdout("\
[..]Compiling foo v0.0.1 ([..])
"));

    assert_that(p.cargo("build").arg("--features").arg("foo"),
                execs().with_status(0)
                       .with_stdout("\
[..]Compiling foo v0.0.1 ([..])
"));

    assert_that(p.cargo("build"),
                execs().with_status(0)
                       .with_stdout("\
[..]Compiling foo v0.0.1 ([..])
"));

    assert_that(p.cargo("build"),
                execs().with_status(0)
                       .with_stdout(""));
});

test!(rebuild_tests_if_lib_changes {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [package]
            name = "foo"
            version = "0.0.1"
            authors = []
        "#)
        .file("src/lib.rs", "pub fn foo() {}")
        .file("tests/foo.rs", r#"
            extern crate foo;
            #[test]
            fn test() { foo::foo(); }
        "#);

    assert_that(p.cargo_process("build"),
                execs().with_status(0));
    assert_that(p.cargo("test"),
                execs().with_status(0));

    File::create(&p.root().join("src/lib.rs")).unwrap();
    p.root().move_into_the_past().unwrap();
    p.root().join("target").move_into_the_past().unwrap();

    assert_that(p.cargo("build"),
                execs().with_status(0));
    assert_that(p.cargo("test").arg("-v"),
                execs().with_status(101));
});
<commit_msg>Auto merge of #1671 - alexcrichton:osx-more-robust, r=huonw<commit_after>use std::fs::{self, File};
use std::io::prelude::*;
use std::thread;

use support::{project, execs, path2url};
use support::COMPILING;
use support::paths::CargoPathExt;
use hamcrest::{assert_that, existing_file};

fn setup() {}

test!(modifying_and_moving {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [package]
            name = "foo"
            authors = []
            version = "0.0.1"
        "#)
        .file("src/main.rs", r#"
            mod a; fn main() {}
        "#)
        .file("src/a.rs", "");

    assert_that(p.cargo_process("build"),
                execs().with_status(0).with_stdout(format!("\
{compiling} foo v0.0.1 ({dir})
", compiling = COMPILING, dir = path2url(p.root()))));

    assert_that(p.cargo("build"),
                execs().with_status(0).with_stdout(""));
    p.root().move_into_the_past().unwrap();
    p.root().join("target").move_into_the_past().unwrap();

    File::create(&p.root().join("src/a.rs")).unwrap()
         .write_all(b"fn main() {}").unwrap();
    assert_that(p.cargo("build"),
                execs().with_status(0).with_stdout(format!("\
{compiling} foo v0.0.1 ({dir})
", compiling = COMPILING, dir = path2url(p.root()))));

    fs::rename(&p.root().join("src/a.rs"), &p.root().join("src/b.rs")).unwrap();
    assert_that(p.cargo("build"),
                execs().with_status(101));
});

test!(modify_only_some_files {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [package]
            name = "foo"
            authors = []
            version = "0.0.1"
        "#)
        .file("src/lib.rs", "mod a;")
        .file("src/a.rs", "")
        .file("src/main.rs", r#"
            mod b;
            fn main() {}
        "#)
        .file("src/b.rs", "")
        .file("tests/test.rs", "");

    assert_that(p.cargo_process("build"),
                execs().with_status(0).with_stdout(format!("\
{compiling} foo v0.0.1 ({dir})
", compiling = COMPILING, dir = path2url(p.root()))));
    assert_that(p.cargo("test"),
                execs().with_status(0));
    thread::sleep_ms(1000);

    assert_that(&p.bin("foo"), existing_file());

    let lib = p.root().join("src/lib.rs");
    let bin = p.root().join("src/b.rs");

    File::create(&lib).unwrap().write_all(b"invalid rust code").unwrap();
    File::create(&bin).unwrap().write_all(b"fn foo() {}").unwrap();
    lib.move_into_the_past().unwrap();

    // Make sure the binary is rebuilt, not the lib
    assert_that(p.cargo("build")
                 .env("RUST_LOG", "cargo::ops::cargo_rustc::fingerprint"),
                execs().with_status(0).with_stdout(format!("\
{compiling} foo v0.0.1 ({dir})
", compiling = COMPILING, dir = path2url(p.root()))));
    assert_that(&p.bin("foo"), existing_file());
});

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

            [dependencies.a]
            path = "a"
            [dependencies.b]
            path = "b"
        "#)
        .file("src/lib.rs", "extern crate a; extern crate b;")
        .file("a/Cargo.toml", r#"
            [package]
            name = "a"
            authors = []
            version = "0.0.1"
            [dependencies.b]
            path = "../b"
        "#)
        .file("a/src/lib.rs", "extern crate b;")
        .file("b/Cargo.toml", r#"
            [package]
            name = "b"
            authors = []
            version = "0.0.1"
        "#)
        .file("b/src/lib.rs", "");

    assert_that(p.cargo_process("build"),
                execs().with_status(0));

    File::create(&p.root().join("b/src/lib.rs")).unwrap().write_all(br#"
        pub fn b() {}
    "#).unwrap();

    assert_that(p.cargo("build").arg("-pb"),
                execs().with_status(0));

    File::create(&p.root().join("src/lib.rs")).unwrap().write_all(br#"
        extern crate a;
        extern crate b;
        pub fn toplevel() {}
    "#).unwrap();

    assert_that(p.cargo("build"),
                execs().with_status(0));
});

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

            [features]
            foo = []
        "#)
        .file("src/lib.rs", "");

    assert_that(p.cargo_process("build"),
                execs().with_status(0)
                       .with_stdout("\
[..]Compiling foo v0.0.1 ([..])
"));

    assert_that(p.cargo("build").arg("--features").arg("foo"),
                execs().with_status(0)
                       .with_stdout("\
[..]Compiling foo v0.0.1 ([..])
"));

    assert_that(p.cargo("build"),
                execs().with_status(0)
                       .with_stdout("\
[..]Compiling foo v0.0.1 ([..])
"));

    assert_that(p.cargo("build"),
                execs().with_status(0)
                       .with_stdout(""));
});

test!(rebuild_tests_if_lib_changes {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [package]
            name = "foo"
            version = "0.0.1"
            authors = []
        "#)
        .file("src/lib.rs", "pub fn foo() {}")
        .file("tests/foo.rs", r#"
            extern crate foo;
            #[test]
            fn test() { foo::foo(); }
        "#);

    assert_that(p.cargo_process("build"),
                execs().with_status(0));
    assert_that(p.cargo("test"),
                execs().with_status(0));

    File::create(&p.root().join("src/lib.rs")).unwrap();
    p.root().move_into_the_past().unwrap();
    p.root().join("target").move_into_the_past().unwrap();

    assert_that(p.cargo("build"),
                execs().with_status(0));
    assert_that(p.cargo("test").arg("-v"),
                execs().with_status(101));
});
<|endoftext|>


<commit_before>//! Tests for configuration values that point to programs.

use cargo_test_support::{basic_lib_manifest, no_such_file_err_msg, project, rustc_host};

#[cargo_test]
fn pathless_tools() {
    let target = rustc_host();

    let foo = project()
        .file("Cargo.toml", &basic_lib_manifest("foo"))
        .file("src/lib.rs", "")
        .file(
            ".cargo/config",
            &format!(
                r#"
                    [target.{}]
                    linker = "nonexistent-linker"
                "#,
                target
            ),
        )
        .build();

    foo.cargo("build --verbose")
        .with_stderr(
            "\
[COMPILING] foo v0.5.0 ([CWD])
[RUNNING] `rustc [..] -C linker=nonexistent-linker [..]`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
        )
        .run();
}

#[cargo_test]
fn absolute_tools() {
    let target = rustc_host();

    // Escaped as they appear within a TOML config file
    let linker = if cfg!(windows) {
        r#"C:\\bogus\\nonexistent-linker"#
    } else {
        r#"/bogus/nonexistent-linker"#
    };

    let foo = project()
        .file("Cargo.toml", &basic_lib_manifest("foo"))
        .file("src/lib.rs", "")
        .file(
            ".cargo/config",
            &format!(
                r#"
                    [target.{target}]
                    linker = "{linker}"
                "#,
                target = target,
                linker = linker
            ),
        )
        .build();

    foo.cargo("build --verbose")
        .with_stderr(
            "\
[COMPILING] foo v0.5.0 ([CWD])
[RUNNING] `rustc [..] -C linker=[..]bogus/nonexistent-linker [..]`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
        )
        .run();
}

#[cargo_test]
fn relative_tools() {
    let target = rustc_host();

    // Escaped as they appear within a TOML config file
    let linker = if cfg!(windows) {
        r#".\\tools\\nonexistent-linker"#
    } else {
        r#"./tools/nonexistent-linker"#
    };

    // Funky directory structure to test that relative tool paths are made absolute
    // by reference to the `.cargo/..` directory and not to (for example) the CWD.
    let p = project()
        .no_manifest()
        .file("bar/Cargo.toml", &basic_lib_manifest("bar"))
        .file("bar/src/lib.rs", "")
        .file(
            ".cargo/config",
            &format!(
                r#"
                    [target.{target}]
                    linker = "{linker}"
                "#,
                target = target,
                linker = linker
            ),
        )
        .build();

    let prefix = p.root().into_os_string().into_string().unwrap();

    p.cargo("build --verbose")
        .cwd("bar")
        .with_stderr(&format!(
            "\
[COMPILING] bar v0.5.0 ([CWD])
[RUNNING] `rustc [..] -C linker={prefix}/./tools/nonexistent-linker [..]`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
            prefix = prefix,
        ))
        .run();
}

#[cargo_test]
fn custom_runner() {
    let target = rustc_host();

    let p = project()
        .file("src/main.rs", "fn main() {}")
        .file("tests/test.rs", "")
        .file("benches/bench.rs", "")
        .file(
            ".cargo/config",
            &format!(
                r#"
                    [target.{}]
                    runner = "nonexistent-runner -r"
                "#,
                target
            ),
        )
        .build();

    p.cargo("run -- --param")
        .with_status(101)
        .with_stderr_contains(
            "\
[COMPILING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
[RUNNING] `nonexistent-runner -r target/debug/foo[EXE] --param`
",
        )
        .run();

    p.cargo("test --test test --verbose -- --param")
        .with_status(101)
        .with_stderr_contains(
            "\
[COMPILING] foo v0.0.1 ([CWD])
[RUNNING] `rustc [..]`
[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
[RUNNING] `nonexistent-runner -r [..]/target/debug/deps/test-[..][EXE] --param`
",
        )
        .run();

    p.cargo("bench --bench bench --verbose -- --param")
        .with_status(101)
        .with_stderr_contains(
            "\
[COMPILING] foo v0.0.1 ([CWD])
[RUNNING] `rustc [..]`
[RUNNING] `rustc [..]`
[FINISHED] bench [optimized] target(s) in [..]
[RUNNING] `nonexistent-runner -r [..]/target/release/deps/bench-[..][EXE] --param --bench`
",
        )
        .run();
}

// can set a custom runner via `target.'cfg(..)'.runner`
#[cargo_test]
fn custom_runner_cfg() {
    let p = project()
        .file("src/main.rs", "fn main() {}")
        .file(
            ".cargo/config",
            r#"
            [target.'cfg(not(target_os = "none"))']
            runner = "nonexistent-runner -r"
            "#,
        )
        .build();

    p.cargo("run -- --param")
        .with_status(101)
        .with_stderr_contains(
            "\
[COMPILING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
[RUNNING] `nonexistent-runner -r target/debug/foo[EXE] --param`
",
        )
        .run();
}

// custom runner set via `target.$triple.runner` have precedence over `target.'cfg(..)'.runner`
#[cargo_test]
fn custom_runner_cfg_precedence() {
    let target = rustc_host();

    let p = project()
        .file("src/main.rs", "fn main() {}")
        .file(
            ".cargo/config",
            &format!(
                r#"
                    [target.'cfg(not(target_os = "none"))']
                    runner = "ignored-runner"

                    [target.{}]
                    runner = "nonexistent-runner -r"
                "#,
                target
            ),
        )
        .build();

    p.cargo("run -- --param")
        .with_status(101)
        .with_stderr_contains(
            "\
[COMPILING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
[RUNNING] `nonexistent-runner -r target/debug/foo[EXE] --param`
",
        )
        .run();
}

#[cargo_test]
fn custom_runner_cfg_collision() {
    let p = project()
        .file("src/main.rs", "fn main() {}")
        .file(
            ".cargo/config",
            r#"
            [target.'cfg(not(target_arch = "avr"))']
            runner = "true"

            [target.'cfg(not(target_os = "none"))']
            runner = "false"
            "#,
        )
        .build();

    p.cargo("run -- --param")
        .with_status(101)
        .with_stderr(
            "\
[ERROR] several matching instances of `target.'cfg(..)'.runner` in `.cargo/config`
first match `cfg(not(target_arch = \"avr\"))` located in [..]/foo/.cargo/config
second match `cfg(not(target_os = \"none\"))` located in [..]/foo/.cargo/config
",
        )
        .run();
}

#[cargo_test]
fn custom_runner_env() {
    let target = rustc_host();
    let p = project().file("src/main.rs", "fn main() {}").build();

    let key = format!(
        "CARGO_TARGET_{}_RUNNER",
        target.to_uppercase().replace('-', "_")
    );

    p.cargo("run")
        .env(&key, "nonexistent-runner --foo")
        .with_status(101)
        .with_stderr(&format!(
            "\
[COMPILING] foo [..]
[FINISHED] dev [..]
[RUNNING] `nonexistent-runner --foo target/debug/foo[EXE]`
[ERROR] could not execute process `nonexistent-runner --foo target/debug/foo[EXE]` (never executed)

Caused by:
  {}
",
            no_such_file_err_msg()
        ))
        .run();
}

#[cargo_test]
fn custom_runner_env_overrides_config() {
    let target = rustc_host();
    let p = project()
        .file("src/main.rs", "fn main() {}")
        .file(
            ".cargo/config.toml",
            &format!(
                r#"
                    [target.{}]
                    runner = "should-not-run -r"
                "#,
                target
            ),
        )
        .build();

    let key = format!(
        "CARGO_TARGET_{}_RUNNER",
        target.to_uppercase().replace('-', "_")
    );

    p.cargo("run")
        .env(&key, "should-run --foo")
        .with_status(101)
        .with_stderr_contains("[RUNNING] `should-run --foo target/debug/foo[EXE]`")
        .run();
}

#[cargo_test]
#[cfg(unix)] // Assumes `true` is in PATH.
fn custom_runner_env_true() {
    // Check for a bug where "true" was interpreted as a boolean instead of
    // the executable.
    let target = rustc_host();
    let p = project().file("src/main.rs", "fn main() {}").build();

    let key = format!(
        "CARGO_TARGET_{}_RUNNER",
        target.to_uppercase().replace('-', "_")
    );

    p.cargo("run")
        .env(&key, "true")
        .with_stderr_contains("[RUNNING] `true target/debug/foo[EXE]`")
        .run();
}

#[cargo_test]
fn custom_linker_env() {
    let target = rustc_host();
    let p = project().file("src/main.rs", "fn main() {}").build();

    let key = format!(
        "CARGO_TARGET_{}_LINKER",
        target.to_uppercase().replace('-', "_")
    );

    p.cargo("build -v")
        .env(&key, "nonexistent-linker")
        .with_status(101)
        .with_stderr_contains("[RUNNING] `rustc [..]-C linker=nonexistent-linker [..]")
        .run();
}

#[cargo_test]
fn target_in_environment_contains_lower_case() {
    let p = project().file("src/main.rs", "fn main() {}").build();

    let target_keys = [
        "CARGO_TARGET_X86_64_UNKNOWN_LINUX_musl_LINKER",
        "CARGO_TARGET_x86_64_unknown_linux_musl_LINKER",
    ];

    for target_key in &target_keys {
        let mut execs = p.cargo("build -v --target x86_64-unknown-linux-musl");
        execs.env(target_key, "nonexistent-linker").with_status(101);
        if cfg!(windows) {
            execs.with_stderr_does_not_contain("warning:[..]");
        } else {
            execs.with_stderr_contains(format!(
                "warning: Environment variables are expected to use uppercase letters and underscores, \
                the variable `{}` will be ignored and have no effect",
                target_key
            ));
        }
        execs.run();
    }
}

#[cargo_test]
fn cfg_ignored_fields() {
    // Test for some ignored fields in [target.'cfg()'] tables.
    let p = project()
        .file(
            ".cargo/config",
            r#"
            # Try some empty tables.
            [target.'cfg(not(foo))']
            [target.'cfg(not(bar))'.somelib]

            # A bunch of unused fields.
            [target.'cfg(not(target_os = "none"))']
            linker = 'false'
            ar = 'false'
            foo = {rustc-flags = "-l foo"}
            invalid = 1
            runner = 'false'
            rustflags = ''
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("check")
        .with_stderr(
            "\
[WARNING] unused key `somelib` in [target] config table `cfg(not(bar))`
[WARNING] unused key `ar` in [target] config table `cfg(not(target_os = \"none\"))`
[WARNING] unused key `foo` in [target] config table `cfg(not(target_os = \"none\"))`
[WARNING] unused key `invalid` in [target] config table `cfg(not(target_os = \"none\"))`
[WARNING] unused key `linker` in [target] config table `cfg(not(target_os = \"none\"))`
[CHECKING] foo v0.0.1 ([..])
[FINISHED] [..]
",
        )
        .run();
}
<commit_msg>Auto merge of #9203 - ehuss:fix-target_in_environment_contains_lower_case, r=alexcrichton<commit_after>//! Tests for configuration values that point to programs.

use cargo_test_support::{basic_lib_manifest, no_such_file_err_msg, project, rustc_host};

#[cargo_test]
fn pathless_tools() {
    let target = rustc_host();

    let foo = project()
        .file("Cargo.toml", &basic_lib_manifest("foo"))
        .file("src/lib.rs", "")
        .file(
            ".cargo/config",
            &format!(
                r#"
                    [target.{}]
                    linker = "nonexistent-linker"
                "#,
                target
            ),
        )
        .build();

    foo.cargo("build --verbose")
        .with_stderr(
            "\
[COMPILING] foo v0.5.0 ([CWD])
[RUNNING] `rustc [..] -C linker=nonexistent-linker [..]`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
        )
        .run();
}

#[cargo_test]
fn absolute_tools() {
    let target = rustc_host();

    // Escaped as they appear within a TOML config file
    let linker = if cfg!(windows) {
        r#"C:\\bogus\\nonexistent-linker"#
    } else {
        r#"/bogus/nonexistent-linker"#
    };

    let foo = project()
        .file("Cargo.toml", &basic_lib_manifest("foo"))
        .file("src/lib.rs", "")
        .file(
            ".cargo/config",
            &format!(
                r#"
                    [target.{target}]
                    linker = "{linker}"
                "#,
                target = target,
                linker = linker
            ),
        )
        .build();

    foo.cargo("build --verbose")
        .with_stderr(
            "\
[COMPILING] foo v0.5.0 ([CWD])
[RUNNING] `rustc [..] -C linker=[..]bogus/nonexistent-linker [..]`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
        )
        .run();
}

#[cargo_test]
fn relative_tools() {
    let target = rustc_host();

    // Escaped as they appear within a TOML config file
    let linker = if cfg!(windows) {
        r#".\\tools\\nonexistent-linker"#
    } else {
        r#"./tools/nonexistent-linker"#
    };

    // Funky directory structure to test that relative tool paths are made absolute
    // by reference to the `.cargo/..` directory and not to (for example) the CWD.
    let p = project()
        .no_manifest()
        .file("bar/Cargo.toml", &basic_lib_manifest("bar"))
        .file("bar/src/lib.rs", "")
        .file(
            ".cargo/config",
            &format!(
                r#"
                    [target.{target}]
                    linker = "{linker}"
                "#,
                target = target,
                linker = linker
            ),
        )
        .build();

    let prefix = p.root().into_os_string().into_string().unwrap();

    p.cargo("build --verbose")
        .cwd("bar")
        .with_stderr(&format!(
            "\
[COMPILING] bar v0.5.0 ([CWD])
[RUNNING] `rustc [..] -C linker={prefix}/./tools/nonexistent-linker [..]`
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
            prefix = prefix,
        ))
        .run();
}

#[cargo_test]
fn custom_runner() {
    let target = rustc_host();

    let p = project()
        .file("src/main.rs", "fn main() {}")
        .file("tests/test.rs", "")
        .file("benches/bench.rs", "")
        .file(
            ".cargo/config",
            &format!(
                r#"
                    [target.{}]
                    runner = "nonexistent-runner -r"
                "#,
                target
            ),
        )
        .build();

    p.cargo("run -- --param")
        .with_status(101)
        .with_stderr_contains(
            "\
[COMPILING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
[RUNNING] `nonexistent-runner -r target/debug/foo[EXE] --param`
",
        )
        .run();

    p.cargo("test --test test --verbose -- --param")
        .with_status(101)
        .with_stderr_contains(
            "\
[COMPILING] foo v0.0.1 ([CWD])
[RUNNING] `rustc [..]`
[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
[RUNNING] `nonexistent-runner -r [..]/target/debug/deps/test-[..][EXE] --param`
",
        )
        .run();

    p.cargo("bench --bench bench --verbose -- --param")
        .with_status(101)
        .with_stderr_contains(
            "\
[COMPILING] foo v0.0.1 ([CWD])
[RUNNING] `rustc [..]`
[RUNNING] `rustc [..]`
[FINISHED] bench [optimized] target(s) in [..]
[RUNNING] `nonexistent-runner -r [..]/target/release/deps/bench-[..][EXE] --param --bench`
",
        )
        .run();
}

// can set a custom runner via `target.'cfg(..)'.runner`
#[cargo_test]
fn custom_runner_cfg() {
    let p = project()
        .file("src/main.rs", "fn main() {}")
        .file(
            ".cargo/config",
            r#"
            [target.'cfg(not(target_os = "none"))']
            runner = "nonexistent-runner -r"
            "#,
        )
        .build();

    p.cargo("run -- --param")
        .with_status(101)
        .with_stderr_contains(
            "\
[COMPILING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
[RUNNING] `nonexistent-runner -r target/debug/foo[EXE] --param`
",
        )
        .run();
}

// custom runner set via `target.$triple.runner` have precedence over `target.'cfg(..)'.runner`
#[cargo_test]
fn custom_runner_cfg_precedence() {
    let target = rustc_host();

    let p = project()
        .file("src/main.rs", "fn main() {}")
        .file(
            ".cargo/config",
            &format!(
                r#"
                    [target.'cfg(not(target_os = "none"))']
                    runner = "ignored-runner"

                    [target.{}]
                    runner = "nonexistent-runner -r"
                "#,
                target
            ),
        )
        .build();

    p.cargo("run -- --param")
        .with_status(101)
        .with_stderr_contains(
            "\
[COMPILING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
[RUNNING] `nonexistent-runner -r target/debug/foo[EXE] --param`
",
        )
        .run();
}

#[cargo_test]
fn custom_runner_cfg_collision() {
    let p = project()
        .file("src/main.rs", "fn main() {}")
        .file(
            ".cargo/config",
            r#"
            [target.'cfg(not(target_arch = "avr"))']
            runner = "true"

            [target.'cfg(not(target_os = "none"))']
            runner = "false"
            "#,
        )
        .build();

    p.cargo("run -- --param")
        .with_status(101)
        .with_stderr(
            "\
[ERROR] several matching instances of `target.'cfg(..)'.runner` in `.cargo/config`
first match `cfg(not(target_arch = \"avr\"))` located in [..]/foo/.cargo/config
second match `cfg(not(target_os = \"none\"))` located in [..]/foo/.cargo/config
",
        )
        .run();
}

#[cargo_test]
fn custom_runner_env() {
    let target = rustc_host();
    let p = project().file("src/main.rs", "fn main() {}").build();

    let key = format!(
        "CARGO_TARGET_{}_RUNNER",
        target.to_uppercase().replace('-', "_")
    );

    p.cargo("run")
        .env(&key, "nonexistent-runner --foo")
        .with_status(101)
        .with_stderr(&format!(
            "\
[COMPILING] foo [..]
[FINISHED] dev [..]
[RUNNING] `nonexistent-runner --foo target/debug/foo[EXE]`
[ERROR] could not execute process `nonexistent-runner --foo target/debug/foo[EXE]` (never executed)

Caused by:
  {}
",
            no_such_file_err_msg()
        ))
        .run();
}

#[cargo_test]
fn custom_runner_env_overrides_config() {
    let target = rustc_host();
    let p = project()
        .file("src/main.rs", "fn main() {}")
        .file(
            ".cargo/config.toml",
            &format!(
                r#"
                    [target.{}]
                    runner = "should-not-run -r"
                "#,
                target
            ),
        )
        .build();

    let key = format!(
        "CARGO_TARGET_{}_RUNNER",
        target.to_uppercase().replace('-', "_")
    );

    p.cargo("run")
        .env(&key, "should-run --foo")
        .with_status(101)
        .with_stderr_contains("[RUNNING] `should-run --foo target/debug/foo[EXE]`")
        .run();
}

#[cargo_test]
#[cfg(unix)] // Assumes `true` is in PATH.
fn custom_runner_env_true() {
    // Check for a bug where "true" was interpreted as a boolean instead of
    // the executable.
    let target = rustc_host();
    let p = project().file("src/main.rs", "fn main() {}").build();

    let key = format!(
        "CARGO_TARGET_{}_RUNNER",
        target.to_uppercase().replace('-', "_")
    );

    p.cargo("run")
        .env(&key, "true")
        .with_stderr_contains("[RUNNING] `true target/debug/foo[EXE]`")
        .run();
}

#[cargo_test]
fn custom_linker_env() {
    let target = rustc_host();
    let p = project().file("src/main.rs", "fn main() {}").build();

    let key = format!(
        "CARGO_TARGET_{}_LINKER",
        target.to_uppercase().replace('-', "_")
    );

    p.cargo("build -v")
        .env(&key, "nonexistent-linker")
        .with_status(101)
        .with_stderr_contains("[RUNNING] `rustc [..]-C linker=nonexistent-linker [..]")
        .run();
}

#[cargo_test]
fn target_in_environment_contains_lower_case() {
    let p = project().file("src/main.rs", "fn main() {}").build();

    let target = rustc_host();
    let env_key = format!(
        "CARGO_TARGET_{}_LINKER",
        target.to_lowercase().replace('-', "_")
    );

    let mut execs = p.cargo("build -v --target");
    execs.arg(target).env(&env_key, "nonexistent-linker");
    if cfg!(windows) {
        // Windows env keys are case insensitive, so no warning, but it will
        // fail due to the missing linker.
        execs
            .with_stderr_does_not_contain("warning:[..]")
            .with_status(101);
    } else {
        execs.with_stderr_contains(format!(
            "warning: Environment variables are expected to use uppercase letters and underscores, \
            the variable `{}` will be ignored and have no effect",
            env_key
        ));
    }
    execs.run();
}

#[cargo_test]
fn cfg_ignored_fields() {
    // Test for some ignored fields in [target.'cfg()'] tables.
    let p = project()
        .file(
            ".cargo/config",
            r#"
            # Try some empty tables.
            [target.'cfg(not(foo))']
            [target.'cfg(not(bar))'.somelib]

            # A bunch of unused fields.
            [target.'cfg(not(target_os = "none"))']
            linker = 'false'
            ar = 'false'
            foo = {rustc-flags = "-l foo"}
            invalid = 1
            runner = 'false'
            rustflags = ''
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("check")
        .with_stderr(
            "\
[WARNING] unused key `somelib` in [target] config table `cfg(not(bar))`
[WARNING] unused key `ar` in [target] config table `cfg(not(target_os = \"none\"))`
[WARNING] unused key `foo` in [target] config table `cfg(not(target_os = \"none\"))`
[WARNING] unused key `invalid` in [target] config table `cfg(not(target_os = \"none\"))`
[WARNING] unused key `linker` in [target] config table `cfg(not(target_os = \"none\"))`
[CHECKING] foo v0.0.1 ([..])
[FINISHED] [..]
",
        )
        .run();
}
<|endoftext|>


<commit_before>//
//  SOS: the Stupid Operating System
//  by Eliza Weisman (hi@hawkweisman.me)
//
//  Copyright (c) 2016-2017 Eliza Weisman
//  Released under the terms of the MIT license. See `LICENSE` in the root
//  directory of this repository for more information.
//
//! Initial 32-bit bootloader for x86_64
#![crate_name = "boot"]
// #![crate_type = "staticlib"]
#![feature(asm)]
#![feature(lang_items)]
#![feature(naked_functions)]
#![feature(static_recursion)]
#![feature(linkage)]
#![feature(struct_field_attributes)]
#![feature(stmt_expr_attributes)]
#![no_std]
// #![no_main]

const TABLE_LENGTH: usize = 512;

type Table = [u64; TABLE_LENGTH];

use core::ptr;
use core::convert;

macro_rules! set_flags {
    (%$register:ident $( |= $body:expr);+ ) => {
        let mut $register: usize;
        asm!( concat!("mov $0, ", stringify!($register))
            : "=r"($register)
            ::: "intel");
        $($register |= $body;)+
        asm!( concat!("mov ", stringify!($register), ", $0")
            :: "r"($register)
            :: "intel");
    }
}

#[repr(C, packed)]
pub struct Gdt { _null: u64
               , code: u64
               }

#[repr(C, packed)]
pub struct GdtPointer { /// the length of the GDT
                        pub limit: u16
                      , /// pointer to the GDT
                        pub base: &'static Gdt
                      }

impl GdtPointer {
    #[cold] #[inline(always)]
    unsafe fn load (&self) {
        asm!("lgdt ($0)" :: "r"(self) : "memory");
    }
}

impl convert::From<&'static Gdt> for GdtPointer {
    #[cold] #[inline(always)]
    fn from(gdt: &'static Gdt) -> Self {
        use core::mem::size_of_val;
        GdtPointer { limit: size_of_val(gdt) as u16 - 1
                   , base: gdt }
    }
}

#[link_name = ".gdt64"]
#[link_section = ".gdt"]
#[no_mangle]
pub static GDT: Gdt
    = Gdt { _null: 0
          , code: (1<<44) | (1<<47) | (1<<41) | (1<<43) | (1<<53)
        //   , data: (1 << 44) | (1 << 47) | (1 << 41)
          };

#[cfg(feature = "log")]
#[inline(always)]
#[naked]
fn boot_write(s: &[u8]) {
	// use core::ptr;
    let mut offset = 0;
    unsafe {
        let vga_buf: *mut u16 = 0xb8000 as *mut u16;
    	for c in s {
    		ptr::write_volatile( vga_buf.offset(offset)
                               , 0x0200 + *c as u16);
    		offset += 1;
    	}

    }

}

#[cfg(not(feature = "log"))]
fn boot_write(_s: &[u8]) { }

extern "C" {
    static mut pml4_table: Table;
    static mut pdp_table: Table;
    static mut pd_table: Table;
}

#[cold]
#[inline(always)]
#[naked]
unsafe fn create_page_tables() {
    // 3. if everything is okay, create the page tables and start long mode
    const HUGE_PAGE_SIZE: u64 = 2 * 1024 * 1024; // 2 MiB

    //-- map the PML4 and PDP tables -----------------------------------------
    // recursive map last PML4 entry
    pml4_table[511] = (&pml4_table as *const _ as u64) | 0b11;
    // map first PML4 entry to PDP table
    pml4_table[0] = (&pdp_table as *const _ as u64) | 0b11;
    // map first PDPT entry to PD table
    pdp_table[0] = (&pd_table as *const _ as u64) | 0b11;

    boot_write(b"3.1");

    //-- map the PD table ----------------------------------------------------
    for (number, entry) in pd_table.iter_mut().enumerate() {
        // set each PD entry equal to the start address of the page (the page
        // number times the page's size)
        let addr = number as u64 * HUGE_PAGE_SIZE;
        // with the appropriate flags (present + writable + huge)
        // TODO: do we want to do this using bitflags, or is that too
        //       heavyweight for the boot module?
        //          - eliza, 1/23/2017
        *entry = addr | 0b10000011;
    }

    boot_write(b"3.2");
}
#[cold]
#[inline(always)]
#[naked]
unsafe fn set_long_mode() {

    // load PML4 addr to cr3
    asm!("mov cr3, $0" :: "r"(&pml4_table) :: "intel");
    boot_write(b"3.3");

    // // enable PAE flag in cr4
    set_flags!(%cr4 |= 1 << 5 );
    boot_write(b"3.4");

    // set the long mode bit in EFER MSR (model specific register)
    asm!( "mov   ecx, 0xC0000080
           rdmsr
           or    eax, 1 << 8
           wrmsr"
        :::: "intel");
    boot_write(b"3.5");

    // enable paging in cr0
    set_flags!(%cr0 |= 1 << 31;
                    |= 1 << 16 );
}

#[cold]
#[no_mangle]
#[naked]
pub unsafe extern "C" fn _start() {
    boot_write(b"0");
    asm!("cli");

    // 1. Move Multiboot info pointer to edi
    asm!("mov edi, ebx" :::: "intel");
    boot_write(b"1");

    // 2. make sure the system supports SOS
    // TODO: port this from boot.asm
    boot_write(b"2");

    create_page_tables();
    set_long_mode();

    // 4. load the 64-bit GDT
    GdtPointer::from(&GDT).load();

    boot_write(b"4");

    // // 5. update selectors
    // asm!("mov ax, 0x10" :::: "intel");
    // boot_write(b"5.1");
    // // stack selector
    // asm!("mov ss, ax" :::: "intel");
    // boot_write(b"5.2");
    // // data selector
    // asm!("mov ds, ax" :::: "intel");
    // boot_write(b"5.3");
    // // extra selector
    // asm!("mov es, ax" :::: "intel");
    // boot_write(b"5.4");
    // loop { }
    // 6. jump to the 64-bit boot subroutine.
    // 0. Move the stack pointer to the top of the stack.
    asm!("ljmpl $$8, $$long_mode_init");

}
<commit_msg>[boot] clean up boot module a little<commit_after>//
//  SOS: the Stupid Operating System
//  by Eliza Weisman (hi@hawkweisman.me)
//
//  Copyright (c) 2016-2017 Eliza Weisman
//  Released under the terms of the MIT license. See `LICENSE` in the root
//  directory of this repository for more information.
//
//! Initial 32-bit bootloader for x86_64
#![crate_name = "boot"]
#![feature(asm)]
#![feature(lang_items)]
#![feature(naked_functions)]
#![feature(static_recursion)]
#![feature(linkage)]
#![feature(struct_field_attributes)]
#![feature(stmt_expr_attributes)]
#![no_std]

const TABLE_LENGTH: usize = 512;

type Table = [u64; TABLE_LENGTH];

use core::convert;

macro_rules! set_flags {
    (%$register:ident $( |= $body:expr);+ ) => {
        let mut $register: usize;
        asm!( concat!("mov $0, ", stringify!($register))
            : "=r"($register)
            ::: "intel");
        $($register |= $body;)+
        asm!( concat!("mov ", stringify!($register), ", $0")
            :: "r"($register)
            :: "intel");
    }
}

#[repr(C, packed)]
pub struct Gdt { _null: u64
               , code: u64
               }

#[repr(C, packed)]
pub struct GdtPointer { /// the length of the GDT
                        pub limit: u16
                      , /// pointer to the GDT
                        pub base: &'static Gdt
                      }

impl GdtPointer {
    #[cold] #[inline(always)]
    unsafe fn load (&self) {
        asm!("lgdt ($0)" :: "r"(self) : "memory");
    }
}

impl convert::From<&'static Gdt> for GdtPointer {
    #[cold] #[inline(always)]
    fn from(gdt: &'static Gdt) -> Self {
        use core::mem::size_of_val;
        GdtPointer { limit: size_of_val(gdt) as u16 - 1
                   , base: gdt }
    }
}

#[link_name = ".gdt64"]
#[link_section = ".gdt"]
#[no_mangle]
pub static GDT: Gdt
    = Gdt { _null: 0
          , code: (1<<44) | (1<<47) | (1<<41) | (1<<43) | (1<<53)
        //   , data: (1 << 44) | (1 << 47) | (1 << 41)
          };

#[cfg(feature = "log")]
#[inline(always)]
#[naked]
fn boot_write(s: &[u8]) {
	use core::ptr;
    let mut offset = 0;
    unsafe {
        let vga_buf: *mut u16 = 0xb8000 as *mut u16;
    	for c in s {
    		ptr::write_volatile( vga_buf.offset(offset)
                               , 0x0200 + *c as u16);
    		offset += 1;
    	}

    }

}

#[cfg(not(feature = "log"))]
fn boot_write(_s: &[u8]) { }

extern "C" {
    static mut pml4_table: Table;
    static mut pdp_table: Table;
    static mut pd_table: Table;
}

#[cold]
#[inline(always)]
#[naked]
unsafe fn create_page_tables() {
    // 3. if everything is okay, create the page tables and start long mode
    const HUGE_PAGE_SIZE: u64 = 2 * 1024 * 1024; // 2 MiB

    //-- map the PML4 and PDP tables -----------------------------------------
    // recursive map last PML4 entry
    pml4_table[511] = (&pml4_table as *const _ as u64) | 0b11;
    // map first PML4 entry to PDP table
    pml4_table[0] = (&pdp_table as *const _ as u64) | 0b11;
    // map first PDPT entry to PD table
    pdp_table[0] = (&pd_table as *const _ as u64) | 0b11;

    boot_write(b"3.1");

    //-- map the PD table ----------------------------------------------------
    for (number, entry) in pd_table.iter_mut().enumerate() {
        // set each PD entry equal to the start address of the page (the page
        // number times the page's size)
        let addr = number as u64 * HUGE_PAGE_SIZE;
        // with the appropriate flags (present + writable + huge)
        // TODO: do we want to do this using bitflags, or is that too
        //       heavyweight for the boot module?
        //          - eliza, 1/23/2017
        *entry = addr | 0b10000011;
    }

    boot_write(b"3.2");
}
#[cold]
#[inline(always)]
#[naked]
unsafe fn set_long_mode() {
    // load PML4 addr to cr3
    asm!("mov cr3, $0" :: "r"(&pml4_table) :: "intel");
    boot_write(b"3.3");

    // // enable PAE flag in cr4
    set_flags!(%cr4 |= 1 << 5 );
    boot_write(b"3.4");

    // set the long mode bit in EFER MSR (model specific register)
    asm!( "mov   ecx, 0xC0000080
           rdmsr
           or    eax, 1 << 8
           wrmsr"
        :::: "intel");
    boot_write(b"3.5");

    // enable paging in cr0
    set_flags!(%cr0 |= 1 << 31;
                    |= 1 << 16 );
}

#[cold]
#[no_mangle]
#[naked]
pub unsafe extern "C" fn _start() {
    boot_write(b"0");
    asm!("cli");

    // 1. Move Multiboot info pointer to edi
    asm!("mov edi, ebx" :::: "intel");
    boot_write(b"1");

    // 2. make sure the system supports SOS
    // TODO: port this from boot.asm
    boot_write(b"2");

    create_page_tables();
    set_long_mode();

    // 4. load the 64-bit GDT
    GdtPointer::from(&GDT).load();
    boot_write(b"4");

    // 6. jump to the 64-bit boot subroutine.
    asm!("ljmpl $$8, $$long_mode_init");

}
<|endoftext|>