1 #![allow(dead_code)]
2 
3 use std::env;
4 use std::ffi::{OsStr, OsString};
5 use std::fs::{self, File};
6 use std::io;
7 use std::io::prelude::*;
8 use std::path::{Path, PathBuf};
9 
10 use cc;
11 use tempfile::{Builder, TempDir};
12 
13 pub struct Test {
14     pub td: TempDir,
15     pub gcc: PathBuf,
16     pub msvc: bool,
17 }
18 
19 pub struct Execution {
20     args: Vec<String>,
21 }
22 
23 impl Test {
new() -> Test24     pub fn new() -> Test {
25         // This is ugly: `sccache` needs to introspect the compiler it is
26         // executing, as it adjusts its behavior depending on the
27         // language/compiler. This crate's test driver uses mock compilers that
28         // are obviously not supported by sccache, so the tests fail if
29         // RUSTC_WRAPPER is set. rust doesn't build test dependencies with
30         // the `test` feature enabled, so we can't conditionally disable the
31         // usage of `sccache` if running in a test environment, at least not
32         // without setting an environment variable here and testing for it
33         // there. Explicitly deasserting RUSTC_WRAPPER here seems to be the
34         // lesser of the two evils.
35         env::remove_var("RUSTC_WRAPPER");
36 
37         let mut gcc = PathBuf::from(env::current_exe().unwrap());
38         gcc.pop();
39         if gcc.ends_with("deps") {
40             gcc.pop();
41         }
42         let td = Builder::new().prefix("gcc-test").tempdir_in(&gcc).unwrap();
43         gcc.push(format!("gcc-shim{}", env::consts::EXE_SUFFIX));
44         Test {
45             td: td,
46             gcc: gcc,
47             msvc: false,
48         }
49     }
50 
gnu() -> Test51     pub fn gnu() -> Test {
52         let t = Test::new();
53         t.shim("cc").shim("c++").shim("ar");
54         t
55     }
56 
msvc() -> Test57     pub fn msvc() -> Test {
58         let mut t = Test::new();
59         t.shim("cl").shim("lib.exe");
60         t.msvc = true;
61         t
62     }
63 
shim(&self, name: &str) -> &Test64     pub fn shim(&self, name: &str) -> &Test {
65         link_or_copy(
66             &self.gcc,
67             self.td
68                 .path()
69                 .join(&format!("{}{}", name, env::consts::EXE_SUFFIX)),
70         )
71         .unwrap();
72         self
73     }
74 
gcc(&self) -> cc::Build75     pub fn gcc(&self) -> cc::Build {
76         let mut cfg = cc::Build::new();
77         let target = if self.msvc {
78             "x86_64-pc-windows-msvc"
79         } else {
80             "x86_64-unknown-linux-gnu"
81         };
82 
83         cfg.target(target)
84             .host(target)
85             .opt_level(2)
86             .debug(false)
87             .out_dir(self.td.path())
88             .__set_env("PATH", self.path())
89             .__set_env("GCCTEST_OUT_DIR", self.td.path());
90         if self.msvc {
91             cfg.compiler(self.td.path().join("cl"));
92             cfg.archiver(self.td.path().join("lib.exe"));
93         }
94         cfg
95     }
96 
path(&self) -> OsString97     fn path(&self) -> OsString {
98         let mut path = env::split_paths(&env::var_os("PATH").unwrap()).collect::<Vec<_>>();
99         path.insert(0, self.td.path().to_owned());
100         env::join_paths(path).unwrap()
101     }
102 
cmd(&self, i: u32) -> Execution103     pub fn cmd(&self, i: u32) -> Execution {
104         let mut s = String::new();
105         File::open(self.td.path().join(format!("out{}", i)))
106             .unwrap()
107             .read_to_string(&mut s)
108             .unwrap();
109         Execution {
110             args: s.lines().map(|s| s.to_string()).collect(),
111         }
112     }
113 }
114 
115 impl Execution {
must_have<P: AsRef<OsStr>>(&self, p: P) -> &Execution116     pub fn must_have<P: AsRef<OsStr>>(&self, p: P) -> &Execution {
117         if !self.has(p.as_ref()) {
118             panic!("didn't find {:?} in {:?}", p.as_ref(), self.args);
119         } else {
120             self
121         }
122     }
123 
must_not_have<P: AsRef<OsStr>>(&self, p: P) -> &Execution124     pub fn must_not_have<P: AsRef<OsStr>>(&self, p: P) -> &Execution {
125         if self.has(p.as_ref()) {
126             panic!("found {:?}", p.as_ref());
127         } else {
128             self
129         }
130     }
131 
has(&self, p: &OsStr) -> bool132     pub fn has(&self, p: &OsStr) -> bool {
133         self.args.iter().any(|arg| OsStr::new(arg) == p)
134     }
135 
must_have_in_order(&self, before: &str, after: &str) -> &Execution136     pub fn must_have_in_order(&self, before: &str, after: &str) -> &Execution {
137         let before_position = self
138             .args
139             .iter()
140             .rposition(|x| OsStr::new(x) == OsStr::new(before));
141         let after_position = self
142             .args
143             .iter()
144             .rposition(|x| OsStr::new(x) == OsStr::new(after));
145         match (before_position, after_position) {
146             (Some(b), Some(a)) if b < a => {}
147             (b, a) => panic!(
148                 "{:?} (last position: {:?}) did not appear before {:?} (last position: {:?})",
149                 before, b, after, a
150             ),
151         };
152         self
153     }
154 }
155 
156 /// Hard link an executable or copy it if that fails.
157 ///
158 /// We first try to hard link an executable to save space. If that fails (as on Windows with
159 /// different mount points, issue #60), we copy.
160 #[cfg(not(target_os = "macos"))]
link_or_copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()>161 fn link_or_copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
162     let from = from.as_ref();
163     let to = to.as_ref();
164     fs::hard_link(from, to).or_else(|_| fs::copy(from, to).map(|_| ()))
165 }
166 
167 /// Copy an executable.
168 ///
169 /// On macOS, hard linking the executable leads to strange failures (issue #419), so we just copy.
170 #[cfg(target_os = "macos")]
link_or_copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()>171 fn link_or_copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
172     fs::copy(from, to).map(|_| ())
173 }
174