<commit_before>//! This file contains tests relevant to Rustup's handling of updating PATHs.
//! It depends on self-update working, so if absolutely everything here breaks,
//! check those tests as well.
pub mod mock;

use crate::mock::clitools::{self, expect_ok, Config, Scenario};
use crate::mock::{get_path, restore_path};
use lazy_static::lazy_static;
use std::path::PathBuf;
use std::sync::Mutex;

pub fn setup(f: &dyn Fn(&Config)) {
    clitools::setup(Scenario::SimpleV2, &|config| {
        // Lock protects environment variables
        lazy_static! {
            static ref LOCK: Mutex<()> = Mutex::new(());
        }
        let _g = LOCK.lock();

        // On windows these tests mess with the user's PATH. Save
        // and restore them here to keep from trashing things.
        let saved_path = get_path();
        let _g = scopeguard::guard(saved_path, restore_path);

        f(config);
    });
}

#[cfg(unix)]
mod unix {
    use super::*;
    use crate::mock::clitools::expect_err;
    use rustup::utils::raw;

    use std::fmt::Display;
    use std::fs;

    // Let's write a fake .rc which looks vaguely like a real script.
    const FAKE_RC: &str = r#"
# Sources fruity punch.
source ~/fruit/punch

# Adds apples to PATH.
export PATH="$HOME/apple/bin"
"#;

    const DEFAULT_EXPORT: &str = "export PATH=\"$HOME/.cargo/bin:$PATH\"\n";
    const POSIX_SH: &str = "env.sh";

    fn source(dir: impl Display, sh: impl Display) -> String {
        format!("source \"{dir}/{sh}\"\n", dir = dir, sh = sh)
    }

    #[test]
    fn install_creates_necessary_scripts() {
        setup(&|config| {
            // Override the test harness so that cargo home looks like
            // $HOME/.cargo by removing CARGO_HOME from the environment,
            // otherwise the literal path will be written to the file.

            let mut cmd = clitools::cmd(config, "rustup-init", &["-y"]);
            let files: Vec<PathBuf> = [".cargo/env.sh", ".profile", ".zshenv"]
                .iter()
                .map(|file| config.homedir.join(file))
                .collect();
            for file in &files {
                assert!(!file.exists());
            }
            cmd.env_remove("CARGO_HOME");
            cmd.env("SHELL", "zsh");
            assert!(cmd.output().unwrap().status.success());
            let mut rcs = files.iter();
            let env = rcs.next().unwrap();
            let envfile = fs::read_to_string(&env).unwrap();
            let (_, envfile_export) = envfile.split_at(match envfile.find("export PATH") {
                Some(idx) => idx,
                None => 0,
            });
            assert_eq!(&envfile_export[..DEFAULT_EXPORT.len()], DEFAULT_EXPORT);

            for rc in rcs {
                let expected = source("$HOME/.cargo", POSIX_SH);
                let new_profile = fs::read_to_string(&rc).unwrap();
                assert_eq!(new_profile, expected);
            }
        });
    }

    #[test]
    fn install_updates_bash_rcs() {
        setup(&|config| {
            let rcs: Vec<PathBuf> = [".bashrc", ".bash_profile", ".bash_login", ".profile"]
                .iter()
                .map(|rc| config.homedir.join(rc))
                .collect();
            for rc in &rcs {
                raw::write_file(&rc, FAKE_RC).unwrap();
            }

            expect_ok(config, &["rustup-init", "-y"]);

            let expected = FAKE_RC.to_owned() + &source(config.cargodir.display(), POSIX_SH);
            for rc in &rcs {
                let new_rc = fs::read_to_string(&rc).unwrap();
                assert_eq!(new_rc, expected);
            }
        })
    }

    #[test]
    fn install_does_not_create_bash_rcs() {
        setup(&|config| {
            let rcs: Vec<PathBuf> = [".bashrc", ".bash_profile", ".bash_login"]
                .iter()
                .map(|rc| config.homedir.join(rc))
                .collect();
            let rcs_before = rcs.iter().map(|rc| rc.exists());
            expect_ok(config, &["rustup-init", "-y"]);

            for (before, after) in rcs_before.zip(rcs.iter().map(|rc| rc.exists())) {
                assert!(before == false);
                assert_eq!(before, after);
            }
        });
    }

    #[test]
    fn install_errors_when_rc_cannot_be_updated() {
        setup(&|config| {
            let rc = config.homedir.join(".profile");
            fs::File::create(&rc).unwrap();
            let mut perms = fs::metadata(&rc).unwrap().permissions();
            perms.set_readonly(true);
            fs::set_permissions(&rc, perms).unwrap();

            expect_err(config, &["rustup-init", "-y"], "amend shell");
        });
    }

    #[test]
    fn install_with_zdotdir() {
        setup(&|config| {
            let zdotdir = tempfile::Builder::new()
                .prefix("zdotdir")
                .tempdir()
                .unwrap();
            let rc = zdotdir.path().join(".zshenv");
            raw::write_file(&rc, FAKE_RC).unwrap();

            let mut cmd = clitools::cmd(config, "rustup-init", &["-y"]);
            cmd.env("SHELL", "zsh");
            cmd.env("ZDOTDIR", zdotdir.path());
            assert!(cmd.output().unwrap().status.success());

            let new_rc = fs::read_to_string(&rc).unwrap();
            let expected = FAKE_RC.to_owned() + &source(config.cargodir.display(), POSIX_SH);
            assert_eq!(new_rc, expected);
        });
    }

    #[test]
    fn install_adds_path_to_rc_just_once() {
        setup(&|config| {
            let profile = config.homedir.join(".profile");
            raw::write_file(&profile, FAKE_RC).unwrap();
            expect_ok(config, &["rustup-init", "-y"]);
            expect_ok(config, &["rustup-init", "-y"]);

            let new_profile = fs::read_to_string(&profile).unwrap();
            let expected = FAKE_RC.to_owned() + &source(config.cargodir.display(), POSIX_SH);
            assert_eq!(new_profile, expected);
        });
    }

    #[test]
    fn uninstall_removes_source_from_rcs() {
        setup(&|config| {
            let rcs: Vec<PathBuf> = [
                ".bashrc",
                ".bash_profile",
                ".bash_login",
                ".profile",
                ".zshenv",
            ]
            .iter()
            .map(|rc| config.homedir.join(rc))
            .collect();

            for rc in &rcs {
                raw::write_file(&rc, FAKE_RC).unwrap();
            }

            expect_ok(config, &["rustup-init", "-y"]);
            expect_ok(config, &["rustup", "self", "uninstall", "-y"]);

            for rc in &rcs {
                let new_rc = fs::read_to_string(&rc).unwrap();
                assert_eq!(new_rc, FAKE_RC);
            }
        })
    }

    #[test]
    fn install_adds_sources_while_removing_legacy_paths() {
        setup(&|config| {
            let zdotdir = tempfile::Builder::new()
                .prefix("zdotdir")
                .tempdir()
                .unwrap();
            let rcs: Vec<PathBuf> = [".bash_profile", ".profile"]
                .iter()
                .map(|rc| config.homedir.join(rc))
                .collect();
            let zprofiles = vec![
                config.homedir.join(".zprofile"),
                zdotdir.path().join(".zprofile"),
            ];
            let old_rc = FAKE_RC.to_owned() + DEFAULT_EXPORT;
            for rc in rcs.iter().chain(zprofiles.iter()) {
                raw::write_file(&rc, &old_rc).unwrap();
            }

            let mut cmd = clitools::cmd(config, "rustup-init", &["-y"]);
            cmd.env("SHELL", "zsh");
            cmd.env("ZDOTDIR", zdotdir.path());
            cmd.env_remove("CARGO_HOME");
            assert!(cmd.output().unwrap().status.success());
            let fixed_rc = FAKE_RC.to_owned() + &source("$HOME/.cargo", POSIX_SH);
            for rc in &rcs {
                let new_rc = fs::read_to_string(&rc).unwrap();
                assert_eq!(new_rc, fixed_rc);
            }
            for rc in &zprofiles {
                let new_rc = fs::read_to_string(&rc).unwrap();
                assert_eq!(new_rc, FAKE_RC);
            }
        })
    }

    #[test]
    fn uninstall_cleans_up_legacy_paths() {
        setup(&|config| {
            // Install first, then overwrite.
            expect_ok(config, &["rustup-init", "-y"]);

            let zdotdir = tempfile::Builder::new()
                .prefix("zdotdir")
                .tempdir()
                .unwrap();
            let mut cmd = clitools::cmd(config, "rustup-init", &["-y"]);
            cmd.env("SHELL", "zsh");
            cmd.env("ZDOTDIR", zdotdir.path());
            cmd.env_remove("CARGO_HOME");
            assert!(cmd.output().unwrap().status.success());
            let mut rcs: Vec<PathBuf> = [".bash_profile", ".profile", ".zprofile"]
                .iter()
                .map(|rc| config.homedir.join(rc))
                .collect();
            rcs.push(zdotdir.path().join(".zprofile"));
            let old_rc = FAKE_RC.to_owned() + DEFAULT_EXPORT;
            for rc in &rcs {
                raw::write_file(&rc, &old_rc).unwrap();
            }

            let mut cmd = clitools::cmd(config, "rustup", &["self", "uninstall", "-y"]);
            cmd.env("SHELL", "zsh");
            cmd.env("ZDOTDIR", zdotdir.path());
            cmd.env_remove("CARGO_HOME");
            assert!(cmd.output().unwrap().status.success());

            for rc in &rcs {
                let new_rc = fs::read_to_string(&rc).unwrap();
                // It's not ideal, but it's OK, if we leave whitespace.
                assert_eq!(new_rc, FAKE_RC);
            }
        })
    }

    // In the default case we want to write $HOME/.cargo/bin as the path,
    // not the full path.
    #[test]
    fn when_cargo_home_is_the_default_write_path_specially() {
        setup(&|config| {
            // Override the test harness so that cargo home looks like
            // $HOME/.cargo by removing CARGO_HOME from the environment,
            // otherwise the literal path will be written to the file.

            let profile = config.homedir.join(".profile");
            raw::write_file(&profile, FAKE_RC).unwrap();
            let mut cmd = clitools::cmd(config, "rustup-init", &["-y"]);
            cmd.env_remove("CARGO_HOME");
            assert!(cmd.output().unwrap().status.success());

            let new_profile = fs::read_to_string(&profile).unwrap();
            let expected = format!("{}source \"$HOME/.cargo/env.sh\"\n", FAKE_RC);
            assert_eq!(new_profile, expected);

            let mut cmd = clitools::cmd(config, "rustup", &["self", "uninstall", "-y"]);
            cmd.env_remove("CARGO_HOME");
            assert!(cmd.output().unwrap().status.success());

            let new_profile = fs::read_to_string(&profile).unwrap();
            assert_eq!(new_profile, FAKE_RC);
        });
    }

    #[test]
    fn install_doesnt_modify_path_if_passed_no_modify_path() {
        setup(&|config| {
            let profile = config.homedir.join(".profile");
            expect_ok(config, &["rustup-init", "-y", "--no-modify-path"]);
            assert!(!profile.exists());
        });
    }
}

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

    #[test]
    #[cfg(windows)]
    /// Smoke test for end-to-end code connectivity of the installer path mgmt on windows.
    fn install_uninstall_affect_path() {
        setup(&|config| {
            let path = config.cargodir.join("bin").to_string_lossy().to_string();

            expect_ok(config, &["rustup-init", "-y"]);
            assert!(
                get_path().unwrap().contains(&path),
                format!("`{}` not in `{}`", get_path().unwrap(), &path)
            );

            expect_ok(config, &["rustup", "self", "uninstall", "-y"]);
            assert!(!get_path().unwrap().contains(&path));
        });
    }
}
<commit_msg>Remove extraneous unpacking<commit_after>//! This file contains tests relevant to Rustup's handling of updating PATHs.
//! It depends on self-update working, so if absolutely everything here breaks,
//! check those tests as well.
pub mod mock;

use std::path::PathBuf;

use crate::mock::clitools::{self, expect_ok, Config, Scenario};

// Prefer omitting actually unpacking content while just testing paths.
const INIT_NONE: [&str; 4] = ["rustup-init", "-y", "--default-toolchain", "none"];

#[cfg(unix)]
mod unix {
    use std::fmt::Display;
    use std::fs;

    use rustup::utils::raw;

    use super::*;
    use crate::mock::clitools::expect_err;

    // Let's write a fake .rc which looks vaguely like a real script.
    const FAKE_RC: &str = r#"
# Sources fruity punch.
source ~/fruit/punch

# Adds apples to PATH.
export PATH="$HOME/apple/bin"
"#;
    const DEFAULT_EXPORT: &str = "export PATH=\"$HOME/.cargo/bin:$PATH\"\n";
    const POSIX_SH: &str = "env.sh";

    fn source(dir: impl Display, sh: impl Display) -> String {
        format!("source \"{dir}/{sh}\"\n", dir = dir, sh = sh)
    }

    fn setup(f: &dyn Fn(&Config)) {
        clitools::setup(Scenario::Empty, &|config| {
            f(config);
        });
    }

    #[test]
    fn install_creates_necessary_scripts() {
        setup(&|config| {
            // Override the test harness so that cargo home looks like
            // $HOME/.cargo by removing CARGO_HOME from the environment,
            // otherwise the literal path will be written to the file.

            let mut cmd = clitools::cmd(config, "rustup-init", &INIT_NONE[1..]);
            let files: Vec<PathBuf> = [".cargo/env.sh", ".profile", ".zshenv"]
                .iter()
                .map(|file| config.homedir.join(file))
                .collect();
            for file in &files {
                assert!(!file.exists());
            }
            cmd.env_remove("CARGO_HOME");
            cmd.env("SHELL", "zsh");
            assert!(cmd.output().unwrap().status.success());
            let mut rcs = files.iter();
            let env = rcs.next().unwrap();
            let envfile = fs::read_to_string(&env).unwrap();
            let (_, envfile_export) = envfile.split_at(match envfile.find("export PATH") {
                Some(idx) => idx,
                None => 0,
            });
            assert_eq!(&envfile_export[..DEFAULT_EXPORT.len()], DEFAULT_EXPORT);

            for rc in rcs {
                let expected = source("$HOME/.cargo", POSIX_SH);
                let new_profile = fs::read_to_string(&rc).unwrap();
                assert_eq!(new_profile, expected);
            }
        });
    }

    #[test]
    fn install_updates_bash_rcs() {
        setup(&|config| {
            let rcs: Vec<PathBuf> = [".bashrc", ".bash_profile", ".bash_login", ".profile"]
                .iter()
                .map(|rc| config.homedir.join(rc))
                .collect();
            for rc in &rcs {
                raw::write_file(&rc, FAKE_RC).unwrap();
            }

            expect_ok(config, &INIT_NONE);

            let expected = FAKE_RC.to_owned() + &source(config.cargodir.display(), POSIX_SH);
            for rc in &rcs {
                let new_rc = fs::read_to_string(&rc).unwrap();
                assert_eq!(new_rc, expected);
            }
        })
    }

    #[test]
    fn install_does_not_create_bash_rcs() {
        setup(&|config| {
            let rcs: Vec<PathBuf> = [".bashrc", ".bash_profile", ".bash_login"]
                .iter()
                .map(|rc| config.homedir.join(rc))
                .collect();
            let rcs_before = rcs.iter().map(|rc| rc.exists());
            expect_ok(config, &INIT_NONE);

            for (before, after) in rcs_before.zip(rcs.iter().map(|rc| rc.exists())) {
                assert!(before == false);
                assert_eq!(before, after);
            }
        });
    }

    #[test]
    fn install_errors_when_rc_cannot_be_updated() {
        setup(&|config| {
            let rc = config.homedir.join(".profile");
            fs::File::create(&rc).unwrap();
            let mut perms = fs::metadata(&rc).unwrap().permissions();
            perms.set_readonly(true);
            fs::set_permissions(&rc, perms).unwrap();

            expect_err(config, &INIT_NONE, "amend shell");
        });
    }

    #[test]
    fn install_with_zdotdir() {
        setup(&|config| {
            let zdotdir = tempfile::Builder::new()
                .prefix("zdotdir")
                .tempdir()
                .unwrap();
            let rc = zdotdir.path().join(".zshenv");
            raw::write_file(&rc, FAKE_RC).unwrap();

            let mut cmd = clitools::cmd(config, "rustup-init", &INIT_NONE[1..]);
            cmd.env("SHELL", "zsh");
            cmd.env("ZDOTDIR", zdotdir.path());
            assert!(cmd.output().unwrap().status.success());

            let new_rc = fs::read_to_string(&rc).unwrap();
            let expected = FAKE_RC.to_owned() + &source(config.cargodir.display(), POSIX_SH);
            assert_eq!(new_rc, expected);
        });
    }

    #[test]
    fn install_adds_path_to_rc_just_once() {
        setup(&|config| {
            let profile = config.homedir.join(".profile");
            raw::write_file(&profile, FAKE_RC).unwrap();
            expect_ok(config, &INIT_NONE);
            expect_ok(config, &INIT_NONE);

            let new_profile = fs::read_to_string(&profile).unwrap();
            let expected = FAKE_RC.to_owned() + &source(config.cargodir.display(), POSIX_SH);
            assert_eq!(new_profile, expected);
        });
    }

    #[test]
    fn uninstall_removes_source_from_rcs() {
        setup(&|config| {
            let rcs: Vec<PathBuf> = [
                ".bashrc",
                ".bash_profile",
                ".bash_login",
                ".profile",
                ".zshenv",
            ]
            .iter()
            .map(|rc| config.homedir.join(rc))
            .collect();

            for rc in &rcs {
                raw::write_file(&rc, FAKE_RC).unwrap();
            }

            expect_ok(config, &INIT_NONE);
            expect_ok(config, &["rustup", "self", "uninstall", "-y"]);

            for rc in &rcs {
                let new_rc = fs::read_to_string(&rc).unwrap();
                assert_eq!(new_rc, FAKE_RC);
            }
        })
    }

    #[test]
    fn install_adds_sources_while_removing_legacy_paths() {
        setup(&|config| {
            let zdotdir = tempfile::Builder::new()
                .prefix("zdotdir")
                .tempdir()
                .unwrap();
            let rcs: Vec<PathBuf> = [".bash_profile", ".profile"]
                .iter()
                .map(|rc| config.homedir.join(rc))
                .collect();
            let zprofiles = vec![
                config.homedir.join(".zprofile"),
                zdotdir.path().join(".zprofile"),
            ];
            let old_rc = FAKE_RC.to_owned() + DEFAULT_EXPORT;
            for rc in rcs.iter().chain(zprofiles.iter()) {
                raw::write_file(&rc, &old_rc).unwrap();
            }

            let mut cmd = clitools::cmd(config, "rustup-init", &INIT_NONE[1..]);
            cmd.env("SHELL", "zsh");
            cmd.env("ZDOTDIR", zdotdir.path());
            cmd.env_remove("CARGO_HOME");
            assert!(cmd.output().unwrap().status.success());
            let fixed_rc = FAKE_RC.to_owned() + &source("$HOME/.cargo", POSIX_SH);
            for rc in &rcs {
                let new_rc = fs::read_to_string(&rc).unwrap();
                assert_eq!(new_rc, fixed_rc);
            }
            for rc in &zprofiles {
                let new_rc = fs::read_to_string(&rc).unwrap();
                assert_eq!(new_rc, FAKE_RC);
            }
        })
    }

    #[test]
    fn uninstall_cleans_up_legacy_paths() {
        setup(&|config| {
            // Install first, then overwrite.
            expect_ok(config, &INIT_NONE);

            let zdotdir = tempfile::Builder::new()
                .prefix("zdotdir")
                .tempdir()
                .unwrap();
            let mut cmd = clitools::cmd(config, "rustup-init", &INIT_NONE[1..]);
            cmd.env("SHELL", "zsh");
            cmd.env("ZDOTDIR", zdotdir.path());
            cmd.env_remove("CARGO_HOME");
            assert!(cmd.output().unwrap().status.success());
            let mut rcs: Vec<PathBuf> = [".bash_profile", ".profile", ".zprofile"]
                .iter()
                .map(|rc| config.homedir.join(rc))
                .collect();
            rcs.push(zdotdir.path().join(".zprofile"));
            let old_rc = FAKE_RC.to_owned() + DEFAULT_EXPORT;
            for rc in &rcs {
                raw::write_file(&rc, &old_rc).unwrap();
            }

            let mut cmd = clitools::cmd(config, "rustup", &["self", "uninstall", "-y"]);
            cmd.env("SHELL", "zsh");
            cmd.env("ZDOTDIR", zdotdir.path());
            cmd.env_remove("CARGO_HOME");
            assert!(cmd.output().unwrap().status.success());

            for rc in &rcs {
                let new_rc = fs::read_to_string(&rc).unwrap();
                // It's not ideal, but it's OK, if we leave whitespace.
                assert_eq!(new_rc, FAKE_RC);
            }
        })
    }

    // In the default case we want to write $HOME/.cargo/bin as the path,
    // not the full path.
    #[test]
    fn when_cargo_home_is_the_default_write_path_specially() {
        setup(&|config| {
            // Override the test harness so that cargo home looks like
            // $HOME/.cargo by removing CARGO_HOME from the environment,
            // otherwise the literal path will be written to the file.

            let profile = config.homedir.join(".profile");
            raw::write_file(&profile, FAKE_RC).unwrap();
            let mut cmd = clitools::cmd(config, "rustup-init", &INIT_NONE[1..]);
            cmd.env_remove("CARGO_HOME");
            assert!(cmd.output().unwrap().status.success());

            let new_profile = fs::read_to_string(&profile).unwrap();
            let expected = format!("{}source \"$HOME/.cargo/env.sh\"\n", FAKE_RC);
            assert_eq!(new_profile, expected);

            let mut cmd = clitools::cmd(config, "rustup", &["self", "uninstall", "-y"]);
            cmd.env_remove("CARGO_HOME");
            assert!(cmd.output().unwrap().status.success());

            let new_profile = fs::read_to_string(&profile).unwrap();
            assert_eq!(new_profile, FAKE_RC);
        });
    }

    #[test]
    fn install_doesnt_modify_path_if_passed_no_modify_path() {
        clitools::setup(Scenario::SimpleV2, &|config| {
            let profile = config.homedir.join(".profile");
            expect_ok(config, &["rustup-init", "-y", "--no-modify-path"]);
            assert!(!profile.exists());
        });
    }
}

#[cfg(windows)]
mod windows {
    use std::sync::Mutex;

    use lazy_static::lazy_static;

    use super::*;
    use crate::mock::{get_path, restore_path};

    fn setup(f: &dyn Fn(&Config)) {
        clitools::setup(Scenario::Empty, &|config| {
            // Lock protects environment variables
            lazy_static! {
                static ref LOCK: Mutex<()> = Mutex::new(());
            }
            let _g = LOCK.lock();

            // On windows these tests mess with the user's PATH. Save
            // and restore them here to keep from trashing things.
            let saved_path = get_path();
            let _g = scopeguard::guard(saved_path, restore_path);

            f(config);
        });
    }

    #[test]
    #[cfg(windows)]
    /// Smoke test for end-to-end code connectivity of the installer path mgmt on windows.
    fn install_uninstall_affect_path() {
        setup(&|config| {
            let path = config.cargodir.join("bin").to_string_lossy().to_string();

            expect_ok(config, &INIT_NONE);
            assert!(
                get_path().unwrap().contains(&path),
                format!("`{}` not in `{}`", get_path().unwrap(), &path)
            );

            expect_ok(config, &["rustup", "self", "uninstall", "-y"]);
            assert!(!get_path().unwrap().contains(&path));
        });
    }
}
<|endoftext|>


<commit_before>#![deny(missing_docs)]
use nix::sys::select::*;
use nix::sys::time::{TimeVal, TimeValLike};
use nix::unistd::getpid;
use std::collections::VecDeque;
use std::path::{Path, PathBuf};
use std::os::unix::io::AsRawFd;
use std::os::unix::net::UnixDatagram;
use std;
use super::Result;

const BUF_SIZE: usize = 10_240;
const PATH_DEFAULT_CLIENT: &str = "/tmp";
const PATH_DEFAULT_SERVER: &str = "/var/run/wpa_supplicant/wlan0";

/// Error type used for some library functions
#[derive(Debug, Fail, PartialEq)]
enum WpaError {
    #[fail(display = "Failed to execute the specified command")]
    Failure,
}

/// Builder object used to construct a `WpaCtrl` session
#[derive(Default)]
pub struct WpaCtrlBuilder {
    cli_path: Option<PathBuf>,
    ctrl_path: Option<PathBuf>,
}

impl WpaCtrlBuilder {
    /// A path-like object for this application's UNIX domain socket
    pub fn cli_path<I: Into<Option<P>>, P>(&mut self, cli_path: P) -> &mut Self where P: AsRef<Path> + Sized, Option<PathBuf>: From<P> {
        let new = self;
        new.cli_path = cli_path.into();
        new
    }

    /// A path-like object for the wpasupplicant / hostap UNIX domain sockets
    pub fn ctrl_path<I: Into<Option<P>>, P>(&mut self, ctrl_path: P) -> &mut Self where P: AsRef<Path> + Sized, Option<PathBuf>: From<P> {
        let new = self;
        new.ctrl_path = ctrl_path.into();
        new
    }

    /// Open a control interface to wpasupplicant.
    ///
    /// # Examples
    ///
    /// ```
    /// use wpactrl::WpaCtrl;
    /// let wpa = WpaCtrl::new().open().unwrap();
    /// ```
    pub fn open(self) -> Result<WpaCtrl> {
        let mut counter = 0;
        loop {
            counter += 1;
            let bind_filename = format!("wpa_ctrl_{}-{}", getpid(), counter);
            let bind_filepath = self.cli_path.as_ref().map(|p|p.as_path()).unwrap_or_else(||Path::new(PATH_DEFAULT_CLIENT)).join(bind_filename);
            match UnixDatagram::bind(&bind_filepath) {
                Ok(socket) => {
                    socket.connect(self.ctrl_path.unwrap_or_else(||PATH_DEFAULT_SERVER.into()))?;
                    socket.set_nonblocking(true)?;
                    return Ok(WpaCtrl(WpaCtrlInternal {
                        buffer: [0; BUF_SIZE],
                        handle: socket,
                        filepath: bind_filepath,
                    }))
                },
                Err(ref e) if counter < 2 && e.kind() == std::io::ErrorKind::AddrInUse => {
                    std::fs::remove_file(bind_filepath)?;
                    continue;
                },
                Err(e) => Err(e)?,
            };
        }
    }
}

struct WpaCtrlInternal {
    buffer: [u8; BUF_SIZE],
    handle: UnixDatagram,
    filepath: PathBuf,
}

impl WpaCtrlInternal {
    /// Check if any messages are available
    pub fn pending(&mut self) -> Result<bool> {
        let mut fd_set = FdSet::new();
        let raw_fd = self.handle.as_raw_fd();
        fd_set.insert(raw_fd);
        select(raw_fd+1, Some(&mut fd_set), None, None, Some(&mut TimeVal::seconds(0)))?;
        Ok(fd_set.contains(raw_fd))
    }

    /// Receive a message
    pub fn recv(&mut self) -> Result<Option<String>> {
        if self.pending()? {
            let buf_len = self.handle.recv(&mut self.buffer)?;
            std::str::from_utf8(&self.buffer[0..buf_len]).map(|s|Some(s.to_owned())).map_err(|e|e.into())
        } else {
            Ok(None)
        }
    }

    /// Send a command to wpasupplicant / hostapd.
    fn request<F: FnMut(&str)>(&mut self, cmd: &str, mut cb: F) -> Result<String> {
        self.handle.send(cmd.as_bytes())?;
        loop {
            let mut fd_set = FdSet::new();
            fd_set.insert(self.handle.as_raw_fd());
            select(self.handle.as_raw_fd()+1, Some(&mut fd_set), None, None, Some(&mut TimeVal::seconds(10)))?;
            match self.handle.recv(&mut self.buffer) {
                Ok(len) => {
                    let s = std::str::from_utf8(&self.buffer[0..len])?;
                    if s.starts_with('<') {
                        cb(s)
                    } else {
                        return Ok(s.to_owned());
                    }
                },
                Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
                Err(e) => return Err(e.into()),
            }
        }
    }
}

impl Drop for WpaCtrlInternal {
    fn drop(&mut self) {
        if let Err(e) = std::fs::remove_file(&self.filepath) {
            warn!("Unable to unlink {:?}", e);
        }
    }
}

/// A connection to wpasupplicant / hostap
pub struct WpaCtrl(WpaCtrlInternal);

impl WpaCtrl {
    /// Creates a builder for a wpasupplicant / hostap connection
    ///
    /// # Examples
    ///
    /// ```
    /// let wpa = wpactrl::WpaCtrl::new().open().unwrap();
    /// ```
    pub fn new() -> WpaCtrlBuilder {
        WpaCtrlBuilder::default()
    }

    /// Register as an event monitor for control interface messages
    ///
    /// # Examples
    ///
    /// ```
    /// let mut wpa = wpactrl::WpaCtrl::new().open().unwrap();
    /// let wpa_attached = wpa.attach().unwrap();
    /// ```
    pub fn attach(mut self) -> Result<WpaCtrlAttached> {
        // FIXME: None closure would be better
        if self.0.request("ATTACH", |_: &str|())? != "OK\n" {
            Err(WpaError::Failure.into())
        } else {
            Ok(WpaCtrlAttached(self.0, VecDeque::new()))
        }
    }

    /// Send a command to wpa_supplicant/hostapd.
    ///
    /// Commands are generally identical to those used in wpa_cli,
    /// except all uppercase (eg LIST_NETWORKS, SCAN, etc)
    ///
    /// # Examples
    ///
    /// ```
    /// let mut wpa = wpactrl::WpaCtrl::new().open().unwrap();
    /// assert_eq!(wpa.request("PING").unwrap(), "PONG\n");
    /// ```
    pub fn request(&mut self, cmd: &str) -> Result<String> {
        self.0.request(cmd, |_: &str|())
    }
}

/// A connection to wpasupplicant / hostap that receives status messages
pub struct WpaCtrlAttached(WpaCtrlInternal, VecDeque<String>);

impl WpaCtrlAttached {

    /// Stop listening for and discard any remaining control interface messages
    ///
    /// # Examples
    ///
    /// ```
    /// let mut wpa = wpactrl::WpaCtrl::new().open().unwrap().attach().unwrap();
    /// wpa.detach().unwrap();
    /// ```
    pub fn detach(mut self) -> Result<WpaCtrl> {
        if self.0.request("DETACH", |_: &str|())? != "OK\n" {
            Err(WpaError::Failure.into())
        } else {
            Ok(WpaCtrl(self.0))
        }
    }

    /// Receive the next control interface message.
    ///
    /// Note that multiple control interface messages can be pending;
    /// call this function repeatedly until it returns None to get all of them.
    ///
    /// # Examples
    ///
    /// ```
    /// let mut wpa = wpactrl::WpaCtrl::new().open().unwrap().attach().unwrap();
    /// assert_eq!(wpa.recv().unwrap(), None);
    /// ```
    pub fn recv(&mut self) -> Result<Option<String>> {
        if let Some(s) = self.1.pop_back() {
            Ok(Some(s))
        } else {
            self.0.recv()
        }
    }

    /// Send a command to wpa_supplicant/hostapd.
    ///
    /// Commands are generally identical to those used in wpa_cli,
    /// except all uppercase (eg LIST_NETWORKS, SCAN, etc)
    ///
    /// Control interface messages will be buffered as the command
    /// runs, and will be returned on the next call to recv.
    ///
    /// # Examples
    ///
    /// ```
    /// let mut wpa = wpactrl::WpaCtrl::new().open().unwrap();
    /// assert_eq!(wpa.request("PING").unwrap(), "PONG\n");
    /// ```
    pub fn request(&mut self, cmd: &str) -> Result<String> {
        let mut messages = VecDeque::new();
        let r = self.0.request(cmd, |s: &str|{
            messages.push_front(s.into())
        });
        self.1.extend(messages);
        r
    }
}

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

    fn wpa_ctrl() -> WpaCtrl {
        WpaCtrl::new().open().unwrap()
    }

    #[test]
    fn attach() {
        wpa_ctrl().attach().unwrap().detach().unwrap().attach().unwrap().detach().unwrap();
    }

    #[test]
    fn detach() {
        let wpa = wpa_ctrl().attach().unwrap();
        wpa.detach().unwrap();
    }

    #[test]
    fn new() {
        wpa_ctrl();
    }

    #[test]
    fn request() {
        let mut wpa = wpa_ctrl();
        assert_eq!(wpa.request("PING").unwrap(), "PONG\n");
        let mut wpa_attached = wpa.attach().unwrap();
        // FIXME: This may not trigger the callback
        assert_eq!(wpa_attached.request("PING").unwrap(), "PONG\n");
    }

    #[test]
    fn recv() {
        let mut wpa = wpa_ctrl().attach().unwrap();
        assert_eq!(wpa.recv().unwrap(), None);
        assert_eq!(wpa.request("SCAN").unwrap(), "OK\n");
        loop {
            match wpa.recv().unwrap() {
                Some(s) => {
                    assert_eq!(&s[3..], "CTRL-EVENT-SCAN-STARTED ");
                    break;
                }
                None => std::thread::sleep(std::time::Duration::from_millis(10)),
            }
        }
        wpa.detach().unwrap();
    }
}
<commit_msg>Fix ctrl_path and cli_path<commit_after>#![deny(missing_docs)]
use nix::sys::select::*;
use nix::sys::time::{TimeVal, TimeValLike};
use nix::unistd::getpid;
use std::collections::VecDeque;
use std::path::{Path, PathBuf};
use std::os::unix::io::AsRawFd;
use std::os::unix::net::UnixDatagram;
use std;
use super::Result;

const BUF_SIZE: usize = 10_240;
const PATH_DEFAULT_CLIENT: &str = "/tmp";
const PATH_DEFAULT_SERVER: &str = "/var/run/wpa_supplicant/wlan0";

/// Error type used for some library functions
#[derive(Debug, Fail, PartialEq)]
enum WpaError {
    #[fail(display = "Failed to execute the specified command")]
    Failure,
}

/// Builder object used to construct a `WpaCtrl` session
#[derive(Default)]
pub struct WpaCtrlBuilder {
    cli_path: Option<PathBuf>,
    ctrl_path: Option<PathBuf>,
}

impl WpaCtrlBuilder {
    /// A path-like object for this application's UNIX domain socket
    ///
    /// # Examples
    ///
    /// ```
    /// use wpactrl::WpaCtrl;
    /// let wpa = WpaCtrl::new()
    ///             .cli_path("/tmp")
    ///             .open()
    ///             .unwrap();
    /// ```
    pub fn cli_path<I, P>(mut self, cli_path: I) -> Self
        where I: Into<Option<P>>, P: AsRef<Path> + Sized, PathBuf: From<P> {
        self.cli_path = cli_path.into().map(PathBuf::from);
        self
    }

    /// A path-like object for the wpasupplicant / hostap UNIX domain sockets
    ///
    /// # Examples
    ///
    /// ```
    /// use wpactrl::WpaCtrl;
    /// let wpa = WpaCtrl::new()
    ///             .ctrl_path("/var/run/wpa_supplicant/wlan0")
    ///             .open()
    ///             .unwrap();
    /// ```
    pub fn ctrl_path<I, P>(mut self, ctrl_path: I) -> Self
        where I: Into<Option<P>>, P: AsRef<Path> + Sized, PathBuf: From<P> {
        self.ctrl_path = ctrl_path.into().map(PathBuf::from);
        self
    }

    /// Open a control interface to wpasupplicant.
    ///
    /// # Examples
    ///
    /// ```
    /// use wpactrl::WpaCtrl;
    /// let wpa = WpaCtrl::new().open().unwrap();
    /// ```
    pub fn open(self) -> Result<WpaCtrl> {
        let mut counter = 0;
        loop {
            counter += 1;
            let bind_filename = format!("wpa_ctrl_{}-{}", getpid(), counter);
            let bind_filepath = self.cli_path.as_ref().map(|p|p.as_path()).unwrap_or_else(||Path::new(PATH_DEFAULT_CLIENT)).join(bind_filename);
            match UnixDatagram::bind(&bind_filepath) {
                Ok(socket) => {
                    socket.connect(self.ctrl_path.unwrap_or_else(||PATH_DEFAULT_SERVER.into()))?;
                    socket.set_nonblocking(true)?;
                    return Ok(WpaCtrl(WpaCtrlInternal {
                        buffer: [0; BUF_SIZE],
                        handle: socket,
                        filepath: bind_filepath,
                    }))
                },
                Err(ref e) if counter < 2 && e.kind() == std::io::ErrorKind::AddrInUse => {
                    std::fs::remove_file(bind_filepath)?;
                    continue;
                },
                Err(e) => Err(e)?,
            };
        }
    }
}

struct WpaCtrlInternal {
    buffer: [u8; BUF_SIZE],
    handle: UnixDatagram,
    filepath: PathBuf,
}

impl WpaCtrlInternal {
    /// Check if any messages are available
    pub fn pending(&mut self) -> Result<bool> {
        let mut fd_set = FdSet::new();
        let raw_fd = self.handle.as_raw_fd();
        fd_set.insert(raw_fd);
        select(raw_fd+1, Some(&mut fd_set), None, None, Some(&mut TimeVal::seconds(0)))?;
        Ok(fd_set.contains(raw_fd))
    }

    /// Receive a message
    pub fn recv(&mut self) -> Result<Option<String>> {
        if self.pending()? {
            let buf_len = self.handle.recv(&mut self.buffer)?;
            std::str::from_utf8(&self.buffer[0..buf_len]).map(|s|Some(s.to_owned())).map_err(|e|e.into())
        } else {
            Ok(None)
        }
    }

    /// Send a command to wpasupplicant / hostapd.
    fn request<F: FnMut(&str)>(&mut self, cmd: &str, mut cb: F) -> Result<String> {
        self.handle.send(cmd.as_bytes())?;
        loop {
            let mut fd_set = FdSet::new();
            fd_set.insert(self.handle.as_raw_fd());
            select(self.handle.as_raw_fd()+1, Some(&mut fd_set), None, None, Some(&mut TimeVal::seconds(10)))?;
            match self.handle.recv(&mut self.buffer) {
                Ok(len) => {
                    let s = std::str::from_utf8(&self.buffer[0..len])?;
                    if s.starts_with('<') {
                        cb(s)
                    } else {
                        return Ok(s.to_owned());
                    }
                },
                Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
                Err(e) => return Err(e.into()),
            }
        }
    }
}

impl Drop for WpaCtrlInternal {
    fn drop(&mut self) {
        if let Err(e) = std::fs::remove_file(&self.filepath) {
            warn!("Unable to unlink {:?}", e);
        }
    }
}

/// A connection to wpasupplicant / hostap
pub struct WpaCtrl(WpaCtrlInternal);

impl WpaCtrl {
    /// Creates a builder for a wpasupplicant / hostap connection
    ///
    /// # Examples
    ///
    /// ```
    /// let wpa = wpactrl::WpaCtrl::new().open().unwrap();
    /// ```
    pub fn new() -> WpaCtrlBuilder {
        WpaCtrlBuilder::default()
    }

    /// Register as an event monitor for control interface messages
    ///
    /// # Examples
    ///
    /// ```
    /// let mut wpa = wpactrl::WpaCtrl::new().open().unwrap();
    /// let wpa_attached = wpa.attach().unwrap();
    /// ```
    pub fn attach(mut self) -> Result<WpaCtrlAttached> {
        // FIXME: None closure would be better
        if self.0.request("ATTACH", |_: &str|())? != "OK\n" {
            Err(WpaError::Failure.into())
        } else {
            Ok(WpaCtrlAttached(self.0, VecDeque::new()))
        }
    }

    /// Send a command to wpa_supplicant/hostapd.
    ///
    /// Commands are generally identical to those used in wpa_cli,
    /// except all uppercase (eg LIST_NETWORKS, SCAN, etc)
    ///
    /// # Examples
    ///
    /// ```
    /// let mut wpa = wpactrl::WpaCtrl::new().open().unwrap();
    /// assert_eq!(wpa.request("PING").unwrap(), "PONG\n");
    /// ```
    pub fn request(&mut self, cmd: &str) -> Result<String> {
        self.0.request(cmd, |_: &str|())
    }
}

/// A connection to wpasupplicant / hostap that receives status messages
pub struct WpaCtrlAttached(WpaCtrlInternal, VecDeque<String>);

impl WpaCtrlAttached {

    /// Stop listening for and discard any remaining control interface messages
    ///
    /// # Examples
    ///
    /// ```
    /// let mut wpa = wpactrl::WpaCtrl::new().open().unwrap().attach().unwrap();
    /// wpa.detach().unwrap();
    /// ```
    pub fn detach(mut self) -> Result<WpaCtrl> {
        if self.0.request("DETACH", |_: &str|())? != "OK\n" {
            Err(WpaError::Failure.into())
        } else {
            Ok(WpaCtrl(self.0))
        }
    }

    /// Receive the next control interface message.
    ///
    /// Note that multiple control interface messages can be pending;
    /// call this function repeatedly until it returns None to get all of them.
    ///
    /// # Examples
    ///
    /// ```
    /// let mut wpa = wpactrl::WpaCtrl::new().open().unwrap().attach().unwrap();
    /// assert_eq!(wpa.recv().unwrap(), None);
    /// ```
    pub fn recv(&mut self) -> Result<Option<String>> {
        if let Some(s) = self.1.pop_back() {
            Ok(Some(s))
        } else {
            self.0.recv()
        }
    }

    /// Send a command to wpa_supplicant/hostapd.
    ///
    /// Commands are generally identical to those used in wpa_cli,
    /// except all uppercase (eg LIST_NETWORKS, SCAN, etc)
    ///
    /// Control interface messages will be buffered as the command
    /// runs, and will be returned on the next call to recv.
    ///
    /// # Examples
    ///
    /// ```
    /// let mut wpa = wpactrl::WpaCtrl::new().open().unwrap();
    /// assert_eq!(wpa.request("PING").unwrap(), "PONG\n");
    /// ```
    pub fn request(&mut self, cmd: &str) -> Result<String> {
        let mut messages = VecDeque::new();
        let r = self.0.request(cmd, |s: &str|{
            messages.push_front(s.into())
        });
        self.1.extend(messages);
        r
    }
}

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

    fn wpa_ctrl() -> WpaCtrl {
        WpaCtrl::new().open().unwrap()
    }

    #[test]
    fn attach() {
        wpa_ctrl().attach().unwrap().detach().unwrap().attach().unwrap().detach().unwrap();
    }

    #[test]
    fn detach() {
        let wpa = wpa_ctrl().attach().unwrap();
        wpa.detach().unwrap();
    }

    #[test]
    fn new() {
        wpa_ctrl();
    }

    #[test]
    fn request() {
        let mut wpa = wpa_ctrl();
        assert_eq!(wpa.request("PING").unwrap(), "PONG\n");
        let mut wpa_attached = wpa.attach().unwrap();
        // FIXME: This may not trigger the callback
        assert_eq!(wpa_attached.request("PING").unwrap(), "PONG\n");
    }

    #[test]
    fn recv() {
        let mut wpa = wpa_ctrl().attach().unwrap();
        assert_eq!(wpa.recv().unwrap(), None);
        assert_eq!(wpa.request("SCAN").unwrap(), "OK\n");
        loop {
            match wpa.recv().unwrap() {
                Some(s) => {
                    assert_eq!(&s[3..], "CTRL-EVENT-SCAN-STARTED ");
                    break;
                }
                None => std::thread::sleep(std::time::Duration::from_millis(10)),
            }
        }
        wpa.detach().unwrap();
    }
}
<|endoftext|>


<commit_before>extern crate cc;
extern crate pkg_config;
extern crate wayland_scanner;

use std::{env, fs,
          io::Write,
          path::{Path, PathBuf},
          process::Command};

const PROTOCOL_PATH: &'static str = "../protocols";

fn main() {
    dump_git_version();
    build_wayland_glib_interface();
    build_wayland_protcols();
}

/// Writes the current git hash to a file that is read by Way Cooler
///
/// If this is a release build, the file will be empty.
fn dump_git_version() {
    let out_dir = env::var("OUT_DIR").expect("Could not find out directory!");
    let dest_path = Path::new(&out_dir).join("git-version.txt");
    let mut f = fs::File::create(&dest_path).expect("Could not write git version to out directory");
    if let Some(git_version) = git_version() {
        f.write_all(git_version.as_ref()).expect("Could not write to git version file");
    }
}

/// Gets the current hash of HEAD. Returns None if for some reason
/// that could not be retrieved (e.g not in a git repository)
fn git_version() -> Option<String> {
    if !in_release_commit() {
        Command::new("git").arg("rev-parse")
                           .arg("HEAD")
                           .output()
                           .ok()
                           .map(|output| output.stdout)
                           .map(|hash| String::from_utf8_lossy(&hash).trim().into())
    } else {
        None
    }
}

/// Determines if the current HEAD is tagged with a release
fn in_release_commit() -> bool {
    let result = Command::new("git").arg("describe")
                                    .arg("--exact-match")
                                    .arg("--tags")
                                    .arg("HEAD")
                                    .output()
                                    .unwrap();
    result.status.success()
}

/// Build the wayland-glib interface as a static library
fn build_wayland_glib_interface() {
    let glib = pkg_config::probe_library("glib-2.0").unwrap();
    let dbus = pkg_config::probe_library("dbus-1").unwrap();
    let mut builder = cc::Build::new();

    let include_paths = glib.include_paths.iter().chain(dbus.include_paths.iter());

    for path in include_paths {
        builder.include(path);
    }

    builder.file("src/wayland_glib_interface.c").compile("wayland_glib_interface");
}

/// Build the wayland protcols that Awesome will use to talk to Way Cooler.
fn build_wayland_protcols() {
    let protocols = fs::read_dir(PROTOCOL_PATH).expect("Protocol build path invalid.");
    let out_dir = env::var("OUT_DIR").unwrap();
    let out_dir = Path::new(&out_dir);
    for protocol_path in protocols {
        let protocol_path: fs::DirEntry = protocol_path.unwrap();
        let path: PathBuf = protocol_path.path().into();
        let mut file_name: String = protocol_path.file_name().into_string().unwrap();
        if let Some(extension) = file_name.find(".xml") {
            file_name.truncate(extension);
        }
        wayland_scanner::generate_c_code(path.clone(),
                                         out_dir.join(file_name.clone() + "_api.rs"),
                                         wayland_scanner::Side::Client);
        wayland_scanner::generate_c_interfaces(path, out_dir.join(file_name + "_interface.rs"));
    }
}
<commit_msg>awesome/build: rebuild when a wayland protocol changed or added<commit_after>extern crate cc;
extern crate pkg_config;
extern crate wayland_scanner;

use std::{env, fs,
          io::Write,
          path::{Path, PathBuf},
          process::Command};

const PROTOCOL_PATH: &'static str = "../protocols";

fn main() {
    dump_git_version();
    build_wayland_glib_interface();
    build_wayland_protcols();
}

/// Writes the current git hash to a file that is read by Way Cooler
///
/// If this is a release build, the file will be empty.
fn dump_git_version() {
    let out_dir = env::var("OUT_DIR").expect("Could not find out directory!");
    let dest_path = Path::new(&out_dir).join("git-version.txt");
    let mut f = fs::File::create(&dest_path).expect("Could not write git version to out directory");
    if let Some(git_version) = git_version() {
        f.write_all(git_version.as_ref()).expect("Could not write to git version file");
    }
}

/// Gets the current hash of HEAD. Returns None if for some reason
/// that could not be retrieved (e.g not in a git repository)
fn git_version() -> Option<String> {
    if !in_release_commit() {
        Command::new("git").arg("rev-parse")
                           .arg("HEAD")
                           .output()
                           .ok()
                           .map(|output| output.stdout)
                           .map(|hash| String::from_utf8_lossy(&hash).trim().into())
    } else {
        None
    }
}

/// Determines if the current HEAD is tagged with a release
fn in_release_commit() -> bool {
    let result = Command::new("git").arg("describe")
                                    .arg("--exact-match")
                                    .arg("--tags")
                                    .arg("HEAD")
                                    .output()
                                    .unwrap();
    result.status.success()
}

/// Build the wayland-glib interface as a static library
fn build_wayland_glib_interface() {
    let glib = pkg_config::probe_library("glib-2.0").unwrap();
    let dbus = pkg_config::probe_library("dbus-1").unwrap();
    let mut builder = cc::Build::new();

    let include_paths = glib.include_paths.iter().chain(dbus.include_paths.iter());

    for path in include_paths {
        builder.include(path);
    }

    builder.file("src/wayland_glib_interface.c").compile("wayland_glib_interface");
}

/// Build the wayland protcols that Awesome will use to talk to Way Cooler.
fn build_wayland_protcols() {
    let protocols = fs::read_dir(PROTOCOL_PATH).expect("Protocol build path invalid.");
    let out_dir = env::var("OUT_DIR").unwrap();
    println!("cargo:rerun-if-changed={}", PROTOCOL_PATH); // rebuild on protocols change
    let out_dir = Path::new(&out_dir);
    for protocol_path in protocols {
        let protocol_path: fs::DirEntry = protocol_path.unwrap();
        let path: PathBuf = protocol_path.path().into();
        let mut file_name: String = protocol_path.file_name().into_string().unwrap();
        if let Some(extension) = file_name.find(".xml") {
            file_name.truncate(extension);
        }
        wayland_scanner::generate_c_code(path.clone(),
                                         out_dir.join(file_name.clone() + "_api.rs"),
                                         wayland_scanner::Side::Client);
        wayland_scanner::generate_c_interfaces(path, out_dir.join(file_name + "_interface.rs"));
    }
}
<|endoftext|>


<commit_before>use std::fmt::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;

use colored::*;
use comments::ErrorMatch;
use crossbeam::queue::SegQueue;
use regex::Regex;

use crate::comments::Comments;

mod comments;
#[cfg(test)]
mod tests;

#[derive(Debug)]
pub struct Config {
    /// Arguments passed to the binary that is executed.
    pub args: Vec<String>,
    /// `None` to run on the host, otherwise a target triple
    pub target: Option<String>,
    /// Filters applied to stderr output before processing it
    pub stderr_filters: Filter,
    /// Filters applied to stdout output before processing it
    pub stdout_filters: Filter,
    /// The folder in which to start searching for .rs files
    pub root_dir: PathBuf,
    pub mode: Mode,
    pub program: PathBuf,
    pub output_conflict_handling: OutputConflictHandling,
    /// Only run tests with one of these strings in their path/name
    pub path_filter: Vec<String>,
}

#[derive(Debug)]
pub enum OutputConflictHandling {
    /// The default: emit a diff of the expected/actual output.
    Error,
    /// Ignore mismatches in the stderr/stdout files.
    Ignore,
    /// Instead of erroring if the stderr/stdout differs from the expected
    /// automatically replace it with the found output (after applying filters).
    Bless,
}

pub type Filter = Vec<(Regex, &'static str)>;

pub fn run_tests(config: Config) {
    eprintln!("   Compiler flags: {:?}", config.args);

    // Get the triple with which to run the tests
    let target = config.target.clone().unwrap_or_else(|| config.get_host());

    // A queue for files or folders to process
    let todo = SegQueue::new();
    todo.push(config.root_dir.clone());

    // Some statistics and failure reports.
    let failures = Mutex::new(vec![]);
    let succeeded = AtomicUsize::default();
    let ignored = AtomicUsize::default();
    let filtered = AtomicUsize::default();

    crossbeam::scope(|s| {
        for _ in 0..std::thread::available_parallelism().unwrap().get() {
            s.spawn(|_| {
                while let Some(path) = todo.pop() {
                    // Collect everything inside directories
                    if path.is_dir() {
                        for entry in std::fs::read_dir(path).unwrap() {
                            todo.push(entry.unwrap().path());
                        }
                        continue;
                    }
                    // Only look at .rs files
                    if !path.extension().map(|ext| ext == "rs").unwrap_or(false) {
                        continue;
                    }
                    if !config.path_filter.is_empty() {
                        let path_display = path.display().to_string();
                        if !config.path_filter.iter().any(|filter| path_display.contains(filter)) {
                            filtered.fetch_add(1, Ordering::Relaxed);
                            continue;
                        }
                    }
                    let comments = Comments::parse_file(&path);
                    // Ignore file if only/ignore rules do (not) apply
                    if ignore_file(&comments, &target) {
                        ignored.fetch_add(1, Ordering::Relaxed);
                        eprintln!("{} ... {}", path.display(), "ignored".yellow());
                        continue;
                    }
                    // Run the test for all revisions
                    for revision in
                        comments.revisions.clone().unwrap_or_else(|| vec![String::new()])
                    {
                        let (m, errors) = run_test(&path, &config, &target, &revision, &comments);

                        // Using `format` to prevent messages from threads from getting intermingled.
                        let mut msg = format!("{} ", path.display());
                        if !revision.is_empty() {
                            write!(msg, "(revision `{revision}`) ").unwrap();
                        }
                        write!(msg, "... ").unwrap();
                        if errors.is_empty() {
                            eprintln!("{msg}{}", "ok".green());
                            succeeded.fetch_add(1, Ordering::Relaxed);
                        } else {
                            eprintln!("{msg}{}", "FAILED".red().bold());
                            failures.lock().unwrap().push((path.clone(), m, revision, errors));
                        }
                    }
                }
            });
        }
    })
    .unwrap();

    // Print all errors in a single thread to show reliable output
    let failures = failures.into_inner().unwrap();
    let succeeded = succeeded.load(Ordering::Relaxed);
    let ignored = ignored.load(Ordering::Relaxed);
    let filtered = filtered.load(Ordering::Relaxed);
    if !failures.is_empty() {
        for (path, miri, revision, errors) in &failures {
            eprintln!();
            eprint!("{}", path.display().to_string().underline());
            if !revision.is_empty() {
                eprint!(" (revision `{}`)", revision);
            }
            eprint!(" {}", "FAILED".red());
            eprintln!();
            eprintln!("command: {:?}", miri);
            eprintln!();
            let mut dump_stderr = None;
            for error in errors {
                match error {
                    Error::ExitStatus(mode, exit_status) => eprintln!("{mode:?} got {exit_status}"),
                    Error::PatternNotFound { stderr, pattern, definition_line } => {
                        eprintln!("`{pattern}` {} in stderr output", "not found".red());
                        eprintln!(
                            "expected because of pattern here: {}:{definition_line}",
                            path.display().to_string().bold()
                        );
                        dump_stderr = Some(stderr.clone())
                    }
                    Error::NoPatternsFound =>
                        eprintln!("{}", "no error patterns found in failure test".red()),
                    Error::PatternFoundInPassTest =>
                        eprintln!("{}", "error pattern found in success test".red()),
                    Error::OutputDiffers { path, actual, expected } => {
                        dump_stderr = None;
                        eprintln!("actual output differed from expected {}", path.display());
                        eprintln!("{}", pretty_assertions::StrComparison::new(expected, actual));
                        eprintln!()
                    }
                }
                eprintln!();
            }
            if let Some(stderr) = dump_stderr {
                eprintln!("actual stderr:");
                eprintln!("{}", stderr);
                eprintln!();
            }
        }
        eprintln!(
            "test result: {}. {} tests failed, {} tests passed, {} ignored, {} filtered out",
            "FAIL".red(),
            failures.len().to_string().red().bold(),
            succeeded.to_string().green(),
            ignored.to_string().yellow(),
            filtered.to_string().yellow(),
        );
        std::process::exit(1);
    }
    eprintln!();
    eprintln!(
        "test result: {}. {} tests passed, {} ignored, {} filtered out",
        "ok".green(),
        succeeded.to_string().green(),
        ignored.to_string().yellow(),
        filtered.to_string().yellow(),
    );
    eprintln!();
}

#[derive(Debug)]
enum Error {
    /// Got an invalid exit status for the given mode.
    ExitStatus(Mode, ExitStatus),
    PatternNotFound {
        stderr: String,
        pattern: String,
        definition_line: usize,
    },
    /// A ui test checking for failure does not have any failure patterns
    NoPatternsFound,
    /// A ui test checking for success has failure patterns
    PatternFoundInPassTest,
    /// Stderr/Stdout differed from the `.stderr`/`.stdout` file present.
    OutputDiffers {
        path: PathBuf,
        actual: String,
        expected: String,
    },
}

type Errors = Vec<Error>;

fn run_test(
    path: &Path,
    config: &Config,
    target: &str,
    revision: &str,
    comments: &Comments,
) -> (Command, Errors) {
    // Run miri
    let mut miri = Command::new(&config.program);
    miri.args(config.args.iter());
    miri.arg(path);
    if !revision.is_empty() {
        miri.arg(format!("--cfg={revision}"));
    }
    for arg in &comments.compile_flags {
        miri.arg(arg);
    }
    for (k, v) in &comments.env_vars {
        miri.env(k, v);
    }
    let output = miri.output().expect("could not execute miri");
    let mut errors = config.mode.ok(output.status);
    // Check output files (if any)
    let revised = |extension: &str| {
        if revision.is_empty() {
            extension.to_string()
        } else {
            format!("{}.{}", revision, extension)
        }
    };
    // Check output files against actual output
    check_output(
        &output.stderr,
        path,
        &mut errors,
        revised("stderr"),
        target,
        &config.stderr_filters,
        &config,
        comments,
    );
    check_output(
        &output.stdout,
        path,
        &mut errors,
        revised("stdout"),
        target,
        &config.stdout_filters,
        &config,
        comments,
    );
    // Check error annotations in the source against output
    check_annotations(&output.stderr, &mut errors, config, revision, comments);
    (miri, errors)
}

fn check_annotations(
    unnormalized_stderr: &[u8],
    errors: &mut Errors,
    config: &Config,
    revision: &str,
    comments: &Comments,
) {
    let unnormalized_stderr = std::str::from_utf8(unnormalized_stderr).unwrap();
    // erase annotations from the stderr so they don't match themselves
    let annotations = Regex::new(r"\s*//~.*").unwrap();
    let unnormalized_stderr = annotations.replace(unnormalized_stderr, "");
    let mut found_annotation = false;
    if let Some((ref error_pattern, definition_line)) = comments.error_pattern {
        if !unnormalized_stderr.contains(error_pattern) {
            errors.push(Error::PatternNotFound {
                stderr: unnormalized_stderr.to_string(),
                pattern: error_pattern.to_string(),
                definition_line,
            });
        }
        found_annotation = true;
    }
    for &ErrorMatch { ref matched, revision: ref rev, definition_line } in &comments.error_matches {
        // FIXME: check that the error happens on the marked line

        if let Some(rev) = rev {
            if rev != revision {
                continue;
            }
        }

        if !unnormalized_stderr.contains(matched) {
            errors.push(Error::PatternNotFound {
                stderr: unnormalized_stderr.to_string(),
                pattern: matched.to_string(),
                definition_line,
            });
        }
        found_annotation = true;
    }
    match (config.mode, found_annotation) {
        (Mode::Pass, true) | (Mode::Panic, true) => errors.push(Error::PatternFoundInPassTest),
        (Mode::Fail, false) => errors.push(Error::NoPatternsFound),
        _ => {}
    };
}

fn check_output(
    output: &[u8],
    path: &Path,
    errors: &mut Errors,
    kind: String,
    target: &str,
    filters: &Filter,
    config: &Config,
    comments: &Comments,
) {
    let output = std::str::from_utf8(&output).unwrap();
    let output = normalize(path, output, filters, comments);
    let path = output_path(path, comments, kind, target);
    match config.output_conflict_handling {
        OutputConflictHandling::Bless =>
            if output.is_empty() {
                let _ = std::fs::remove_file(path);
            } else {
                std::fs::write(path, &output).unwrap();
            },
        OutputConflictHandling::Error => {
            let expected_output = std::fs::read_to_string(&path).unwrap_or_default();
            if output != expected_output {
                errors.push(Error::OutputDiffers {
                    path,
                    actual: output,
                    expected: expected_output,
                });
            }
        }
        OutputConflictHandling::Ignore => {}
    }
}

fn output_path(path: &Path, comments: &Comments, kind: String, target: &str) -> PathBuf {
    if comments.stderr_per_bitwidth {
        return path.with_extension(format!("{}.{kind}", get_pointer_width(target)));
    }
    path.with_extension(kind)
}

fn ignore_file(comments: &Comments, target: &str) -> bool {
    for s in &comments.ignore {
        if target.contains(s) {
            return true;
        }
        if get_pointer_width(target) == s {
            return true;
        }
    }
    for s in &comments.only {
        if !target.contains(s) {
            return true;
        }
        if get_pointer_width(target) != s {
            return true;
        }
    }
    false
}

// Taken 1:1 from compiletest-rs
fn get_pointer_width(triple: &str) -> &'static str {
    if (triple.contains("64") && !triple.ends_with("gnux32") && !triple.ends_with("gnu_ilp32"))
        || triple.starts_with("s390x")
    {
        "64bit"
    } else if triple.starts_with("avr") {
        "16bit"
    } else {
        "32bit"
    }
}

fn normalize(path: &Path, text: &str, filters: &Filter, comments: &Comments) -> String {
    // Useless paths
    let mut text = text.replace(&path.parent().unwrap().display().to_string(), "$DIR");
    if let Some(lib_path) = option_env!("RUSTC_LIB_PATH") {
        text = text.replace(lib_path, "RUSTLIB");
    }

    for (regex, replacement) in filters.iter() {
        text = regex.replace_all(&text, *replacement).to_string();
    }

    for (from, to) in &comments.normalize_stderr {
        text = from.replace_all(&text, to).to_string();
    }
    text
}

impl Config {
    fn get_host(&self) -> String {
        rustc_version::VersionMeta::for_command(std::process::Command::new(&self.program))
            .expect("failed to parse rustc version info")
            .host
    }
}

#[derive(Copy, Clone, Debug)]
pub enum Mode {
    // The test passes a full execution of the rustc driver
    Pass,
    // The rustc driver panicked
    Panic,
    // The rustc driver emitted an error
    Fail,
}

impl Mode {
    fn ok(self, status: ExitStatus) -> Errors {
        match (status.code().unwrap(), self) {
            (1, Mode::Fail) | (101, Mode::Panic) | (0, Mode::Pass) => vec![],
            _ => vec![Error::ExitStatus(self, status)],
        }
    }
}
<commit_msg>print reason for ignoring<commit_after>use std::fmt::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;

use colored::*;
use comments::ErrorMatch;
use crossbeam::queue::SegQueue;
use regex::Regex;

use crate::comments::Comments;

mod comments;
#[cfg(test)]
mod tests;

#[derive(Debug)]
pub struct Config {
    /// Arguments passed to the binary that is executed.
    pub args: Vec<String>,
    /// `None` to run on the host, otherwise a target triple
    pub target: Option<String>,
    /// Filters applied to stderr output before processing it
    pub stderr_filters: Filter,
    /// Filters applied to stdout output before processing it
    pub stdout_filters: Filter,
    /// The folder in which to start searching for .rs files
    pub root_dir: PathBuf,
    pub mode: Mode,
    pub program: PathBuf,
    pub output_conflict_handling: OutputConflictHandling,
    /// Only run tests with one of these strings in their path/name
    pub path_filter: Vec<String>,
}

#[derive(Debug)]
pub enum OutputConflictHandling {
    /// The default: emit a diff of the expected/actual output.
    Error,
    /// Ignore mismatches in the stderr/stdout files.
    Ignore,
    /// Instead of erroring if the stderr/stdout differs from the expected
    /// automatically replace it with the found output (after applying filters).
    Bless,
}

pub type Filter = Vec<(Regex, &'static str)>;

pub fn run_tests(config: Config) {
    eprintln!("   Compiler flags: {:?}", config.args);

    // Get the triple with which to run the tests
    let target = config.target.clone().unwrap_or_else(|| config.get_host());

    // A queue for files or folders to process
    let todo = SegQueue::new();
    todo.push(config.root_dir.clone());

    // Some statistics and failure reports.
    let failures = Mutex::new(vec![]);
    let succeeded = AtomicUsize::default();
    let ignored = AtomicUsize::default();
    let filtered = AtomicUsize::default();

    crossbeam::scope(|s| {
        for _ in 0..std::thread::available_parallelism().unwrap().get() {
            s.spawn(|_| {
                while let Some(path) = todo.pop() {
                    // Collect everything inside directories
                    if path.is_dir() {
                        for entry in std::fs::read_dir(path).unwrap() {
                            todo.push(entry.unwrap().path());
                        }
                        continue;
                    }
                    // Only look at .rs files
                    if !path.extension().map(|ext| ext == "rs").unwrap_or(false) {
                        continue;
                    }
                    if !config.path_filter.is_empty() {
                        let path_display = path.display().to_string();
                        if !config.path_filter.iter().any(|filter| path_display.contains(filter)) {
                            filtered.fetch_add(1, Ordering::Relaxed);
                            continue;
                        }
                    }
                    let comments = Comments::parse_file(&path);
                    // Ignore file if only/ignore rules do (not) apply
                    if ignore_file(&comments, &target) {
                        ignored.fetch_add(1, Ordering::Relaxed);
                        eprintln!("{} ... {}", path.display(), "ignored (in-test comment)".yellow());
                        continue;
                    }
                    // Run the test for all revisions
                    for revision in
                        comments.revisions.clone().unwrap_or_else(|| vec![String::new()])
                    {
                        let (m, errors) = run_test(&path, &config, &target, &revision, &comments);

                        // Using `format` to prevent messages from threads from getting intermingled.
                        let mut msg = format!("{} ", path.display());
                        if !revision.is_empty() {
                            write!(msg, "(revision `{revision}`) ").unwrap();
                        }
                        write!(msg, "... ").unwrap();
                        if errors.is_empty() {
                            eprintln!("{msg}{}", "ok".green());
                            succeeded.fetch_add(1, Ordering::Relaxed);
                        } else {
                            eprintln!("{msg}{}", "FAILED".red().bold());
                            failures.lock().unwrap().push((path.clone(), m, revision, errors));
                        }
                    }
                }
            });
        }
    })
    .unwrap();

    // Print all errors in a single thread to show reliable output
    let failures = failures.into_inner().unwrap();
    let succeeded = succeeded.load(Ordering::Relaxed);
    let ignored = ignored.load(Ordering::Relaxed);
    let filtered = filtered.load(Ordering::Relaxed);
    if !failures.is_empty() {
        for (path, miri, revision, errors) in &failures {
            eprintln!();
            eprint!("{}", path.display().to_string().underline());
            if !revision.is_empty() {
                eprint!(" (revision `{}`)", revision);
            }
            eprint!(" {}", "FAILED".red());
            eprintln!();
            eprintln!("command: {:?}", miri);
            eprintln!();
            let mut dump_stderr = None;
            for error in errors {
                match error {
                    Error::ExitStatus(mode, exit_status) => eprintln!("{mode:?} got {exit_status}"),
                    Error::PatternNotFound { stderr, pattern, definition_line } => {
                        eprintln!("`{pattern}` {} in stderr output", "not found".red());
                        eprintln!(
                            "expected because of pattern here: {}:{definition_line}",
                            path.display().to_string().bold()
                        );
                        dump_stderr = Some(stderr.clone())
                    }
                    Error::NoPatternsFound =>
                        eprintln!("{}", "no error patterns found in failure test".red()),
                    Error::PatternFoundInPassTest =>
                        eprintln!("{}", "error pattern found in success test".red()),
                    Error::OutputDiffers { path, actual, expected } => {
                        dump_stderr = None;
                        eprintln!("actual output differed from expected {}", path.display());
                        eprintln!("{}", pretty_assertions::StrComparison::new(expected, actual));
                        eprintln!()
                    }
                }
                eprintln!();
            }
            if let Some(stderr) = dump_stderr {
                eprintln!("actual stderr:");
                eprintln!("{}", stderr);
                eprintln!();
            }
        }
        eprintln!(
            "test result: {}. {} tests failed, {} tests passed, {} ignored, {} filtered out",
            "FAIL".red(),
            failures.len().to_string().red().bold(),
            succeeded.to_string().green(),
            ignored.to_string().yellow(),
            filtered.to_string().yellow(),
        );
        std::process::exit(1);
    }
    eprintln!();
    eprintln!(
        "test result: {}. {} tests passed, {} ignored, {} filtered out",
        "ok".green(),
        succeeded.to_string().green(),
        ignored.to_string().yellow(),
        filtered.to_string().yellow(),
    );
    eprintln!();
}

#[derive(Debug)]
enum Error {
    /// Got an invalid exit status for the given mode.
    ExitStatus(Mode, ExitStatus),
    PatternNotFound {
        stderr: String,
        pattern: String,
        definition_line: usize,
    },
    /// A ui test checking for failure does not have any failure patterns
    NoPatternsFound,
    /// A ui test checking for success has failure patterns
    PatternFoundInPassTest,
    /// Stderr/Stdout differed from the `.stderr`/`.stdout` file present.
    OutputDiffers {
        path: PathBuf,
        actual: String,
        expected: String,
    },
}

type Errors = Vec<Error>;

fn run_test(
    path: &Path,
    config: &Config,
    target: &str,
    revision: &str,
    comments: &Comments,
) -> (Command, Errors) {
    // Run miri
    let mut miri = Command::new(&config.program);
    miri.args(config.args.iter());
    miri.arg(path);
    if !revision.is_empty() {
        miri.arg(format!("--cfg={revision}"));
    }
    for arg in &comments.compile_flags {
        miri.arg(arg);
    }
    for (k, v) in &comments.env_vars {
        miri.env(k, v);
    }
    let output = miri.output().expect("could not execute miri");
    let mut errors = config.mode.ok(output.status);
    // Check output files (if any)
    let revised = |extension: &str| {
        if revision.is_empty() {
            extension.to_string()
        } else {
            format!("{}.{}", revision, extension)
        }
    };
    // Check output files against actual output
    check_output(
        &output.stderr,
        path,
        &mut errors,
        revised("stderr"),
        target,
        &config.stderr_filters,
        &config,
        comments,
    );
    check_output(
        &output.stdout,
        path,
        &mut errors,
        revised("stdout"),
        target,
        &config.stdout_filters,
        &config,
        comments,
    );
    // Check error annotations in the source against output
    check_annotations(&output.stderr, &mut errors, config, revision, comments);
    (miri, errors)
}

fn check_annotations(
    unnormalized_stderr: &[u8],
    errors: &mut Errors,
    config: &Config,
    revision: &str,
    comments: &Comments,
) {
    let unnormalized_stderr = std::str::from_utf8(unnormalized_stderr).unwrap();
    // erase annotations from the stderr so they don't match themselves
    let annotations = Regex::new(r"\s*//~.*").unwrap();
    let unnormalized_stderr = annotations.replace(unnormalized_stderr, "");
    let mut found_annotation = false;
    if let Some((ref error_pattern, definition_line)) = comments.error_pattern {
        if !unnormalized_stderr.contains(error_pattern) {
            errors.push(Error::PatternNotFound {
                stderr: unnormalized_stderr.to_string(),
                pattern: error_pattern.to_string(),
                definition_line,
            });
        }
        found_annotation = true;
    }
    for &ErrorMatch { ref matched, revision: ref rev, definition_line } in &comments.error_matches {
        // FIXME: check that the error happens on the marked line

        if let Some(rev) = rev {
            if rev != revision {
                continue;
            }
        }

        if !unnormalized_stderr.contains(matched) {
            errors.push(Error::PatternNotFound {
                stderr: unnormalized_stderr.to_string(),
                pattern: matched.to_string(),
                definition_line,
            });
        }
        found_annotation = true;
    }
    match (config.mode, found_annotation) {
        (Mode::Pass, true) | (Mode::Panic, true) => errors.push(Error::PatternFoundInPassTest),
        (Mode::Fail, false) => errors.push(Error::NoPatternsFound),
        _ => {}
    };
}

fn check_output(
    output: &[u8],
    path: &Path,
    errors: &mut Errors,
    kind: String,
    target: &str,
    filters: &Filter,
    config: &Config,
    comments: &Comments,
) {
    let output = std::str::from_utf8(&output).unwrap();
    let output = normalize(path, output, filters, comments);
    let path = output_path(path, comments, kind, target);
    match config.output_conflict_handling {
        OutputConflictHandling::Bless =>
            if output.is_empty() {
                let _ = std::fs::remove_file(path);
            } else {
                std::fs::write(path, &output).unwrap();
            },
        OutputConflictHandling::Error => {
            let expected_output = std::fs::read_to_string(&path).unwrap_or_default();
            if output != expected_output {
                errors.push(Error::OutputDiffers {
                    path,
                    actual: output,
                    expected: expected_output,
                });
            }
        }
        OutputConflictHandling::Ignore => {}
    }
}

fn output_path(path: &Path, comments: &Comments, kind: String, target: &str) -> PathBuf {
    if comments.stderr_per_bitwidth {
        return path.with_extension(format!("{}.{kind}", get_pointer_width(target)));
    }
    path.with_extension(kind)
}

fn ignore_file(comments: &Comments, target: &str) -> bool {
    for s in &comments.ignore {
        if target.contains(s) {
            return true;
        }
        if get_pointer_width(target) == s {
            return true;
        }
    }
    for s in &comments.only {
        if !target.contains(s) {
            return true;
        }
        if get_pointer_width(target) != s {
            return true;
        }
    }
    false
}

// Taken 1:1 from compiletest-rs
fn get_pointer_width(triple: &str) -> &'static str {
    if (triple.contains("64") && !triple.ends_with("gnux32") && !triple.ends_with("gnu_ilp32"))
        || triple.starts_with("s390x")
    {
        "64bit"
    } else if triple.starts_with("avr") {
        "16bit"
    } else {
        "32bit"
    }
}

fn normalize(path: &Path, text: &str, filters: &Filter, comments: &Comments) -> String {
    // Useless paths
    let mut text = text.replace(&path.parent().unwrap().display().to_string(), "$DIR");
    if let Some(lib_path) = option_env!("RUSTC_LIB_PATH") {
        text = text.replace(lib_path, "RUSTLIB");
    }

    for (regex, replacement) in filters.iter() {
        text = regex.replace_all(&text, *replacement).to_string();
    }

    for (from, to) in &comments.normalize_stderr {
        text = from.replace_all(&text, to).to_string();
    }
    text
}

impl Config {
    fn get_host(&self) -> String {
        rustc_version::VersionMeta::for_command(std::process::Command::new(&self.program))
            .expect("failed to parse rustc version info")
            .host
    }
}

#[derive(Copy, Clone, Debug)]
pub enum Mode {
    // The test passes a full execution of the rustc driver
    Pass,
    // The rustc driver panicked
    Panic,
    // The rustc driver emitted an error
    Fail,
}

impl Mode {
    fn ok(self, status: ExitStatus) -> Errors {
        match (status.code().unwrap(), self) {
            (1, Mode::Fail) | (101, Mode::Panic) | (0, Mode::Pass) => vec![],
            _ => vec![Error::ExitStatus(self, status)],
        }
    }
}
<|endoftext|>


<commit_before>use crate::rustc::hir;
use crate::rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use crate::rustc::{declare_tool_lint, lint_array};
use if_chain::if_chain;
use crate::rustc::ty;
use crate::rustc_errors::Applicability;
use crate::syntax::source_map::Span;
use crate::utils::{in_macro, iter_input_pats, match_type, method_chain_args, snippet, span_lint_and_then};
use crate::utils::paths;

#[derive(Clone)]
pub struct Pass;

/// **What it does:** Checks for usage of `option.map(f)` where f is a function
/// or closure that returns the unit type.
///
/// **Why is this bad?** Readability, this can be written more clearly with
/// an if let statement
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// let x: Option<&str> = do_stuff();
/// x.map(log_err_msg);
/// x.map(|msg| log_err_msg(format_msg(msg)))
/// ```
///
/// The correct use would be:
///
/// ```rust
/// let x: Option<&str> = do_stuff();
/// if let Some(msg) = x {
///     log_err_msg(msg)
/// }
/// if let Some(msg) = x {
///     log_err_msg(format_msg(msg))
/// }
/// ```
declare_clippy_lint! {
    pub OPTION_MAP_UNIT_FN,
    complexity,
    "using `option.map(f)`, where f is a function or closure that returns ()"
}

/// **What it does:** Checks for usage of `result.map(f)` where f is a function
/// or closure that returns the unit type.
///
/// **Why is this bad?** Readability, this can be written more clearly with
/// an if let statement
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// let x: Result<&str, &str> = do_stuff();
/// x.map(log_err_msg);
/// x.map(|msg| log_err_msg(format_msg(msg)))
/// ```
///
/// The correct use would be:
///
/// ```rust
/// let x: Result<&str, &str> = do_stuff();
/// if let Ok(msg) = x {
///     log_err_msg(msg)
/// }
/// if let Ok(msg) = x {
///     log_err_msg(format_msg(msg))
/// }
/// ```
declare_clippy_lint! {
    pub RESULT_MAP_UNIT_FN,
    complexity,
    "using `result.map(f)`, where f is a function or closure that returns ()"
}

impl LintPass for Pass {
    fn get_lints(&self) -> LintArray {
        lint_array!(OPTION_MAP_UNIT_FN, RESULT_MAP_UNIT_FN)
    }
}

fn is_unit_type(ty: ty::Ty<'_>) -> bool {
    match ty.sty {
        ty::Tuple(slice) => slice.is_empty(),
        ty::Never => true,
        _ => false,
    }
}

fn is_unit_function(cx: &LateContext<'_, '_>, expr: &hir::Expr) -> bool {
    let ty = cx.tables.expr_ty(expr);

    if let ty::FnDef(id, _) = ty.sty {
        if let Some(fn_type) = cx.tcx.fn_sig(id).no_late_bound_regions() {
            return is_unit_type(fn_type.output());
        }
    }
    false
}

fn is_unit_expression(cx: &LateContext<'_, '_>, expr: &hir::Expr) -> bool {
    is_unit_type(cx.tables.expr_ty(expr))
}

/// The expression inside a closure may or may not have surrounding braces and
/// semicolons, which causes problems when generating a suggestion. Given an
/// expression that evaluates to '()' or '!', recursively remove useless braces
/// and semi-colons until is suitable for including in the suggestion template
fn reduce_unit_expression<'a>(cx: &LateContext<'_, '_>, expr: &'a hir::Expr) -> Option<Span> {
    if !is_unit_expression(cx, expr) {
        return None;
    }

    match expr.node {
        hir::ExprKind::Call(_, _) |
        hir::ExprKind::MethodCall(_, _, _) => {
            // Calls can't be reduced any more
            Some(expr.span)
        },
        hir::ExprKind::Block(ref block, _) => {
            match (&block.stmts[..], block.expr.as_ref()) {
                (&[], Some(inner_expr)) => {
                    // If block only contains an expression,
                    // reduce `{ X }` to `X`
                    reduce_unit_expression(cx, inner_expr)
                },
                (&[ref inner_stmt], None) => {
                    // If block only contains statements,
                    // reduce `{ X; }` to `X` or `X;`
                    match inner_stmt.node {
                        hir::StmtKind::Decl(ref d, _) => Some(d.span),
                        hir::StmtKind::Expr(ref e, _) => Some(e.span),
                        hir::StmtKind::Semi(_, _) => Some(inner_stmt.span),
                    }
                },
                _ => {
                    // For closures that contain multiple statements
                    // it's difficult to get a correct suggestion span
                    // for all cases (multi-line closures specifically)
                    //
                    // We do not attempt to build a suggestion for those right now.
                    None
                }
            }
        },
        _ => None,
    }
}

fn unit_closure<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'a hir::Expr) -> Option<(&'tcx hir::Arg, &'a hir::Expr)> {
    if let hir::ExprKind::Closure(_, ref decl, inner_expr_id, _, _) = expr.node {
        let body = cx.tcx.hir.body(inner_expr_id);
        let body_expr = &body.value;

        if_chain! {
            if decl.inputs.len() == 1;
            if is_unit_expression(cx, body_expr);
            if let Some(binding) = iter_input_pats(&decl, body).next();
            then {
                return Some((binding, body_expr));
            }
        }
    }
    None
}

/// Builds a name for the let binding variable (var_arg)
///
/// `x.field` => `x_field`
/// `y` => `_y`
///
/// Anything else will return `_`.
fn let_binding_name(cx: &LateContext<'_, '_>, var_arg: &hir::Expr) -> String {
    match &var_arg.node {
        hir::ExprKind::Field(_, _) => snippet(cx, var_arg.span, "_").replace(".", "_"),
        hir::ExprKind::Path(_) => format!("_{}", snippet(cx, var_arg.span, "")),
        _ => "_".to_string()
    }
}

fn suggestion_msg(function_type: &str, map_type: &str) -> String {
    format!(
        "called `map(f)` on an {0} value where `f` is a unit {1}",
        map_type,
        function_type
    )
}

fn lint_map_unit_fn(cx: &LateContext<'_, '_>, stmt: &hir::Stmt, expr: &hir::Expr, map_args: &[hir::Expr]) {
    let var_arg = &map_args[0];
    let fn_arg = &map_args[1];

    let (map_type, variant, lint) =
        if match_type(cx, cx.tables.expr_ty(var_arg), &paths::OPTION) {
            ("Option", "Some", OPTION_MAP_UNIT_FN)
        } else if match_type(cx, cx.tables.expr_ty(var_arg), &paths::RESULT) {
            ("Result", "Ok", RESULT_MAP_UNIT_FN)
        } else {
            return
        };

    if is_unit_function(cx, fn_arg) {
        let msg = suggestion_msg("function", map_type);
        let suggestion = format!("if let {0}({1}) = {2} {{ {3}(...) }}",
                                 variant,
                                 let_binding_name(cx, var_arg),
                                 snippet(cx, var_arg.span, "_"),
                                 snippet(cx, fn_arg.span, "_"));

        span_lint_and_then(cx, lint, expr.span, &msg, |db| {
            db.span_suggestion_with_applicability(stmt.span,
                                                  "try this",
                                                  suggestion,
                                                  Applicability::Unspecified);
        });
    } else if let Some((binding, closure_expr)) = unit_closure(cx, fn_arg) {
        let msg = suggestion_msg("closure", map_type);

        span_lint_and_then(cx, lint, expr.span, &msg, |db| {
            if let Some(reduced_expr_span) = reduce_unit_expression(cx, closure_expr) {
                let suggestion = format!("if let {0}({1}) = {2} {{ {3} }}",
                                         variant,
                                         snippet(cx, binding.pat.span, "_"),
                                         snippet(cx, var_arg.span, "_"),
                                         snippet(cx, reduced_expr_span, "_"));
                db.span_suggestion(stmt.span, "try this", suggestion);
            } else {
                let suggestion = format!("if let {0}({1}) = {2} {{ ... }}",
                                         variant,
                                         snippet(cx, binding.pat.span, "_"),
                                         snippet(cx, var_arg.span, "_"));
                db.span_suggestion_with_applicability(stmt.span,
                                                      "try this",
                                                      suggestion,
                                                      Applicability::Unspecified);
            }
        });
    }
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
    fn check_stmt(&mut self, cx: &LateContext<'_, '_>, stmt: &hir::Stmt) {
        if in_macro(stmt.span) {
            return;
        }

        if let hir::StmtKind::Semi(ref expr, _) = stmt.node {
            if let hir::ExprKind::MethodCall(_, _, _) = expr.node {
                if let Some(arglists) = method_chain_args(expr, &["map"]) {
                    lint_map_unit_fn(cx, stmt, expr, arglists[0]);
                }
            }
        }
    }
}
<commit_msg>Remove unneeded check for method call<commit_after>use crate::rustc::hir;
use crate::rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use crate::rustc::{declare_tool_lint, lint_array};
use if_chain::if_chain;
use crate::rustc::ty;
use crate::rustc_errors::Applicability;
use crate::syntax::source_map::Span;
use crate::utils::{in_macro, iter_input_pats, match_type, method_chain_args, snippet, span_lint_and_then};
use crate::utils::paths;

#[derive(Clone)]
pub struct Pass;

/// **What it does:** Checks for usage of `option.map(f)` where f is a function
/// or closure that returns the unit type.
///
/// **Why is this bad?** Readability, this can be written more clearly with
/// an if let statement
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// let x: Option<&str> = do_stuff();
/// x.map(log_err_msg);
/// x.map(|msg| log_err_msg(format_msg(msg)))
/// ```
///
/// The correct use would be:
///
/// ```rust
/// let x: Option<&str> = do_stuff();
/// if let Some(msg) = x {
///     log_err_msg(msg)
/// }
/// if let Some(msg) = x {
///     log_err_msg(format_msg(msg))
/// }
/// ```
declare_clippy_lint! {
    pub OPTION_MAP_UNIT_FN,
    complexity,
    "using `option.map(f)`, where f is a function or closure that returns ()"
}

/// **What it does:** Checks for usage of `result.map(f)` where f is a function
/// or closure that returns the unit type.
///
/// **Why is this bad?** Readability, this can be written more clearly with
/// an if let statement
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// let x: Result<&str, &str> = do_stuff();
/// x.map(log_err_msg);
/// x.map(|msg| log_err_msg(format_msg(msg)))
/// ```
///
/// The correct use would be:
///
/// ```rust
/// let x: Result<&str, &str> = do_stuff();
/// if let Ok(msg) = x {
///     log_err_msg(msg)
/// }
/// if let Ok(msg) = x {
///     log_err_msg(format_msg(msg))
/// }
/// ```
declare_clippy_lint! {
    pub RESULT_MAP_UNIT_FN,
    complexity,
    "using `result.map(f)`, where f is a function or closure that returns ()"
}

impl LintPass for Pass {
    fn get_lints(&self) -> LintArray {
        lint_array!(OPTION_MAP_UNIT_FN, RESULT_MAP_UNIT_FN)
    }
}

fn is_unit_type(ty: ty::Ty<'_>) -> bool {
    match ty.sty {
        ty::Tuple(slice) => slice.is_empty(),
        ty::Never => true,
        _ => false,
    }
}

fn is_unit_function(cx: &LateContext<'_, '_>, expr: &hir::Expr) -> bool {
    let ty = cx.tables.expr_ty(expr);

    if let ty::FnDef(id, _) = ty.sty {
        if let Some(fn_type) = cx.tcx.fn_sig(id).no_late_bound_regions() {
            return is_unit_type(fn_type.output());
        }
    }
    false
}

fn is_unit_expression(cx: &LateContext<'_, '_>, expr: &hir::Expr) -> bool {
    is_unit_type(cx.tables.expr_ty(expr))
}

/// The expression inside a closure may or may not have surrounding braces and
/// semicolons, which causes problems when generating a suggestion. Given an
/// expression that evaluates to '()' or '!', recursively remove useless braces
/// and semi-colons until is suitable for including in the suggestion template
fn reduce_unit_expression<'a>(cx: &LateContext<'_, '_>, expr: &'a hir::Expr) -> Option<Span> {
    if !is_unit_expression(cx, expr) {
        return None;
    }

    match expr.node {
        hir::ExprKind::Call(_, _) |
        hir::ExprKind::MethodCall(_, _, _) => {
            // Calls can't be reduced any more
            Some(expr.span)
        },
        hir::ExprKind::Block(ref block, _) => {
            match (&block.stmts[..], block.expr.as_ref()) {
                (&[], Some(inner_expr)) => {
                    // If block only contains an expression,
                    // reduce `{ X }` to `X`
                    reduce_unit_expression(cx, inner_expr)
                },
                (&[ref inner_stmt], None) => {
                    // If block only contains statements,
                    // reduce `{ X; }` to `X` or `X;`
                    match inner_stmt.node {
                        hir::StmtKind::Decl(ref d, _) => Some(d.span),
                        hir::StmtKind::Expr(ref e, _) => Some(e.span),
                        hir::StmtKind::Semi(_, _) => Some(inner_stmt.span),
                    }
                },
                _ => {
                    // For closures that contain multiple statements
                    // it's difficult to get a correct suggestion span
                    // for all cases (multi-line closures specifically)
                    //
                    // We do not attempt to build a suggestion for those right now.
                    None
                }
            }
        },
        _ => None,
    }
}

fn unit_closure<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'a hir::Expr) -> Option<(&'tcx hir::Arg, &'a hir::Expr)> {
    if let hir::ExprKind::Closure(_, ref decl, inner_expr_id, _, _) = expr.node {
        let body = cx.tcx.hir.body(inner_expr_id);
        let body_expr = &body.value;

        if_chain! {
            if decl.inputs.len() == 1;
            if is_unit_expression(cx, body_expr);
            if let Some(binding) = iter_input_pats(&decl, body).next();
            then {
                return Some((binding, body_expr));
            }
        }
    }
    None
}

/// Builds a name for the let binding variable (var_arg)
///
/// `x.field` => `x_field`
/// `y` => `_y`
///
/// Anything else will return `_`.
fn let_binding_name(cx: &LateContext<'_, '_>, var_arg: &hir::Expr) -> String {
    match &var_arg.node {
        hir::ExprKind::Field(_, _) => snippet(cx, var_arg.span, "_").replace(".", "_"),
        hir::ExprKind::Path(_) => format!("_{}", snippet(cx, var_arg.span, "")),
        _ => "_".to_string()
    }
}

fn suggestion_msg(function_type: &str, map_type: &str) -> String {
    format!(
        "called `map(f)` on an {0} value where `f` is a unit {1}",
        map_type,
        function_type
    )
}

fn lint_map_unit_fn(cx: &LateContext<'_, '_>, stmt: &hir::Stmt, expr: &hir::Expr, map_args: &[hir::Expr]) {
    let var_arg = &map_args[0];
    let fn_arg = &map_args[1];

    let (map_type, variant, lint) =
        if match_type(cx, cx.tables.expr_ty(var_arg), &paths::OPTION) {
            ("Option", "Some", OPTION_MAP_UNIT_FN)
        } else if match_type(cx, cx.tables.expr_ty(var_arg), &paths::RESULT) {
            ("Result", "Ok", RESULT_MAP_UNIT_FN)
        } else {
            return
        };

    if is_unit_function(cx, fn_arg) {
        let msg = suggestion_msg("function", map_type);
        let suggestion = format!("if let {0}({1}) = {2} {{ {3}(...) }}",
                                 variant,
                                 let_binding_name(cx, var_arg),
                                 snippet(cx, var_arg.span, "_"),
                                 snippet(cx, fn_arg.span, "_"));

        span_lint_and_then(cx, lint, expr.span, &msg, |db| {
            db.span_suggestion_with_applicability(stmt.span,
                                                  "try this",
                                                  suggestion,
                                                  Applicability::Unspecified);
        });
    } else if let Some((binding, closure_expr)) = unit_closure(cx, fn_arg) {
        let msg = suggestion_msg("closure", map_type);

        span_lint_and_then(cx, lint, expr.span, &msg, |db| {
            if let Some(reduced_expr_span) = reduce_unit_expression(cx, closure_expr) {
                let suggestion = format!("if let {0}({1}) = {2} {{ {3} }}",
                                         variant,
                                         snippet(cx, binding.pat.span, "_"),
                                         snippet(cx, var_arg.span, "_"),
                                         snippet(cx, reduced_expr_span, "_"));
                db.span_suggestion(stmt.span, "try this", suggestion);
            } else {
                let suggestion = format!("if let {0}({1}) = {2} {{ ... }}",
                                         variant,
                                         snippet(cx, binding.pat.span, "_"),
                                         snippet(cx, var_arg.span, "_"));
                db.span_suggestion_with_applicability(stmt.span,
                                                      "try this",
                                                      suggestion,
                                                      Applicability::Unspecified);
            }
        });
    }
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
    fn check_stmt(&mut self, cx: &LateContext<'_, '_>, stmt: &hir::Stmt) {
        if in_macro(stmt.span) {
            return;
        }

        if let hir::StmtKind::Semi(ref expr, _) = stmt.node {
            if let Some(arglists) = method_chain_args(expr, &["map"]) {
                lint_map_unit_fn(cx, stmt, expr, arglists[0]);
            }
        }
    }
}
<|endoftext|>