зеркало из https://github.com/mozilla/sccache.git
357 строки
12 KiB
Rust
357 строки
12 KiB
Rust
// System tests for compiling C code.
|
|
//
|
|
// Copyright 2016 Mozilla Foundation
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#![allow(dead_code, unused_imports)]
|
|
|
|
extern crate assert_cmd;
|
|
extern crate cc;
|
|
extern crate env_logger;
|
|
extern crate escargot;
|
|
#[macro_use]
|
|
extern crate log;
|
|
extern crate predicates;
|
|
extern crate sccache;
|
|
extern crate serde_json;
|
|
extern crate tempdir;
|
|
extern crate which;
|
|
|
|
use assert_cmd::prelude::*;
|
|
use escargot::CargoBuild;
|
|
use harness::{
|
|
sccache_command,
|
|
sccache_client_cfg,
|
|
start_local_daemon, stop_local_daemon,
|
|
write_json_cfg, write_source,
|
|
get_stats, zero_stats,
|
|
};
|
|
use log::Level::Trace;
|
|
use predicates::prelude::*;
|
|
use std::collections::HashMap;
|
|
use std::env;
|
|
use std::ffi::{OsStr,OsString};
|
|
use std::fmt;
|
|
use std::fs::{self, File};
|
|
use std::io::{self, Read, Write};
|
|
use std::path::{Path,PathBuf};
|
|
use std::process::{
|
|
Command,
|
|
Output,
|
|
Stdio,
|
|
};
|
|
use std::str;
|
|
use tempdir::TempDir;
|
|
use which::which_in;
|
|
|
|
mod harness;
|
|
|
|
#[derive(Clone)]
|
|
struct Compiler {
|
|
pub name: &'static str,
|
|
pub exe: OsString,
|
|
pub env_vars: Vec<(OsString, OsString)>,
|
|
}
|
|
|
|
// Test GCC + clang on non-OS X platforms.
|
|
#[cfg(all(unix, not(target_os="macos")))]
|
|
const COMPILERS: &'static [&'static str] = &["gcc", "clang"];
|
|
|
|
// OS X ships a `gcc` that's just a clang wrapper, so only test clang there.
|
|
#[cfg(target_os="macos")]
|
|
const COMPILERS: &'static [&'static str] = &["clang"];
|
|
|
|
//TODO: could test gcc when targeting mingw.
|
|
|
|
macro_rules! vec_from {
|
|
( $t:ty, $( $x:expr ),* ) => {
|
|
vec!($( Into::<$t>::into(&$x), )*)
|
|
};
|
|
}
|
|
|
|
fn compile_cmdline<T: AsRef<OsStr>>(compiler: &str, exe: T, input: &str, output: &str) -> Vec<OsString> {
|
|
match compiler {
|
|
"gcc" | "clang" => vec_from!(OsString, exe.as_ref(), "-c", input, "-o", output),
|
|
"cl.exe" => vec_from!(OsString, exe, "-c", input, format!("-Fo{}", output)),
|
|
_ => panic!("Unsupported compiler: {}", compiler),
|
|
}
|
|
}
|
|
|
|
const INPUT: &'static str = "test.c";
|
|
const INPUT_ERR: &'static str = "test_err.c";
|
|
const OUTPUT: &'static str = "test.o";
|
|
|
|
fn test_basic_compile(compiler: Compiler, tempdir: &Path) {
|
|
let Compiler { name, exe, env_vars } = compiler;
|
|
trace!("run_sccache_command_test: {}", name);
|
|
// Compile a source file.
|
|
// Copy the source files into the tempdir so we can compile with relative paths, since the commandline winds up in the hash key.
|
|
for f in &[INPUT, INPUT_ERR] {
|
|
let original_source_file = Path::new(file!()).parent().unwrap().join(f);
|
|
let source_file = tempdir.join(f);
|
|
trace!("fs::copy({:?}, {:?})", original_source_file, source_file);
|
|
fs::copy(&original_source_file, &source_file).unwrap();
|
|
}
|
|
|
|
let out_file = tempdir.join("test.o");
|
|
trace!("compile");
|
|
sccache_command()
|
|
.args(&compile_cmdline(name, &exe, INPUT, OUTPUT))
|
|
.current_dir(tempdir)
|
|
.envs(env_vars.clone())
|
|
.assert()
|
|
.success();
|
|
assert_eq!(true, fs::metadata(&out_file).and_then(|m| Ok(m.len() > 0)).unwrap());
|
|
trace!("request stats");
|
|
get_stats(|info| {
|
|
assert_eq!(1, info.stats.compile_requests);
|
|
assert_eq!(1, info.stats.requests_executed);
|
|
assert_eq!(0, info.stats.cache_hits.all());
|
|
assert_eq!(1, info.stats.cache_misses.all());
|
|
assert_eq!(&1, info.stats.cache_misses.get("C/C++").unwrap());
|
|
});
|
|
trace!("compile");
|
|
fs::remove_file(&out_file).unwrap();
|
|
sccache_command()
|
|
.args(&compile_cmdline(name, &exe, INPUT, OUTPUT))
|
|
.current_dir(tempdir)
|
|
.envs(env_vars.clone())
|
|
.assert()
|
|
.success();
|
|
assert_eq!(true, fs::metadata(&out_file).and_then(|m| Ok(m.len() > 0)).unwrap());
|
|
trace!("request stats");
|
|
get_stats(|info| {
|
|
assert_eq!(2, info.stats.compile_requests);
|
|
assert_eq!(2, info.stats.requests_executed);
|
|
assert_eq!(1, info.stats.cache_hits.all());
|
|
assert_eq!(1, info.stats.cache_misses.all());
|
|
assert_eq!(&1, info.stats.cache_hits.get("C/C++").unwrap());
|
|
assert_eq!(&1, info.stats.cache_misses.get("C/C++").unwrap());
|
|
});
|
|
}
|
|
|
|
fn test_noncacheable_stats(compiler: Compiler, tempdir: &Path) {
|
|
let Compiler { name, exe, env_vars } = compiler;
|
|
trace!("test_noncacheable_stats: {}", name);
|
|
// Copy the source file into the tempdir so we can compile with relative paths, since the commandline winds up in the hash key.
|
|
let original_source_file = Path::new(file!()).parent().unwrap().join(INPUT);
|
|
let source_file = tempdir.join(INPUT);
|
|
trace!("fs::copy({:?}, {:?})", original_source_file, source_file);
|
|
fs::copy(&original_source_file, &source_file).unwrap();
|
|
|
|
trace!("compile");
|
|
Command::main_binary().unwrap()
|
|
.arg(&exe)
|
|
.arg("-E")
|
|
.arg(INPUT)
|
|
.current_dir(tempdir)
|
|
.envs(env_vars.clone())
|
|
.assert()
|
|
.success();
|
|
trace!("request stats");
|
|
get_stats(|info| {
|
|
assert_eq!(1, info.stats.compile_requests);
|
|
assert_eq!(0, info.stats.requests_executed);
|
|
assert_eq!(1, info.stats.not_cached.len());
|
|
assert_eq!(Some(&1), info.stats.not_cached.get("-E"));
|
|
});
|
|
}
|
|
|
|
fn test_msvc_deps(compiler: Compiler, tempdir: &Path) {
|
|
let Compiler { name, exe, env_vars } = compiler;
|
|
// Check that -deps works.
|
|
trace!("compile with -deps");
|
|
let mut args = compile_cmdline(name, &exe, INPUT, OUTPUT);
|
|
args.push("-depstest.d".into());
|
|
sccache_command()
|
|
.args(&args)
|
|
.current_dir(tempdir)
|
|
.envs(env_vars.clone())
|
|
.assert()
|
|
.success();
|
|
// Check the contents
|
|
let mut f = File::open(tempdir.join("test.d")).expect("Failed to open dep file");
|
|
let mut buf = String::new();
|
|
// read_to_string should be safe because we're supplying all the filenames here,
|
|
// and there are no absolute paths.
|
|
f.read_to_string(&mut buf).expect("Failed to read dep file");
|
|
let lines: Vec<_> = buf.lines().map(|l| l.trim_right()).collect();
|
|
let expected = format!("{output}: {input}\n{input}:\n", output=OUTPUT, input=INPUT);
|
|
let expected_lines: Vec<_> = expected.lines().collect();
|
|
assert_eq!(lines, expected_lines);
|
|
}
|
|
|
|
fn test_gcc_mp_werror(compiler: Compiler, tempdir: &Path) {
|
|
let Compiler { name, exe, env_vars } = compiler;
|
|
trace!("test -MP with -Werror");
|
|
let mut args = compile_cmdline(name, &exe, INPUT_ERR, OUTPUT);
|
|
args.extend(vec_from!(OsString, "-MD", "-MP", "-MF", "foo.pp", "-Werror"));
|
|
// This should fail, but the error should be from the #error!
|
|
sccache_command()
|
|
.args(&args)
|
|
.current_dir(tempdir)
|
|
.envs(env_vars.clone())
|
|
.assert()
|
|
.failure()
|
|
.stderr(predicates::str::contains(
|
|
"to generate dependencies you must specify either -M or -MM").from_utf8().not());
|
|
}
|
|
|
|
fn test_gcc_fprofile_generate_source_changes(compiler: Compiler, tempdir: &Path) {
|
|
let Compiler { name, exe, env_vars } = compiler;
|
|
trace!("test -fprofile-generate with different source inputs");
|
|
zero_stats();
|
|
const SRC: &str = "source.c";
|
|
write_source(&tempdir, SRC, "/*line 1*/
|
|
#ifndef UNDEFINED
|
|
/*unused line 1*/
|
|
#endif
|
|
|
|
int main(int argc, char** argv) {
|
|
return 0;
|
|
}
|
|
");
|
|
let mut args = compile_cmdline(name, &exe, SRC, OUTPUT);
|
|
args.extend(vec_from!(OsString, "-fprofile-generate"));
|
|
trace!("compile source.c (1)");
|
|
sccache_command()
|
|
.args(&args)
|
|
.current_dir(tempdir)
|
|
.envs(env_vars.clone())
|
|
.assert()
|
|
.success();
|
|
get_stats(|info| {
|
|
assert_eq!(0, info.stats.cache_hits.all());
|
|
assert_eq!(1, info.stats.cache_misses.all());
|
|
assert_eq!(&1, info.stats.cache_misses.get("C/C++").unwrap());
|
|
});
|
|
// Compile the same source again to ensure we can get a cache hit.
|
|
trace!("compile source.c (2)");
|
|
sccache_command()
|
|
.args(&args)
|
|
.current_dir(tempdir)
|
|
.envs(env_vars.clone())
|
|
.assert()
|
|
.success();
|
|
get_stats(|info| {
|
|
assert_eq!(1, info.stats.cache_hits.all());
|
|
assert_eq!(1, info.stats.cache_misses.all());
|
|
assert_eq!(&1, info.stats.cache_hits.get("C/C++").unwrap());
|
|
assert_eq!(&1, info.stats.cache_misses.get("C/C++").unwrap());
|
|
});
|
|
// Now write out a slightly different source file that will preprocess to the same thing,
|
|
// modulo line numbers. This should not be a cache hit because line numbers are important
|
|
// with -fprofile-generate.
|
|
write_source(&tempdir, SRC, "/*line 1*/
|
|
#ifndef UNDEFINED
|
|
/*unused line 1*/
|
|
/*unused line 2*/
|
|
#endif
|
|
|
|
int main(int argc, char** argv) {
|
|
return 0;
|
|
}
|
|
");
|
|
trace!("compile source.c (3)");
|
|
sccache_command()
|
|
.args(&args)
|
|
.current_dir(tempdir)
|
|
.envs(env_vars.clone())
|
|
.assert()
|
|
.success();
|
|
get_stats(|info| {
|
|
assert_eq!(1, info.stats.cache_hits.all());
|
|
assert_eq!(2, info.stats.cache_misses.all());
|
|
assert_eq!(&1, info.stats.cache_hits.get("C/C++").unwrap());
|
|
assert_eq!(&2, info.stats.cache_misses.get("C/C++").unwrap());
|
|
});
|
|
}
|
|
|
|
fn run_sccache_command_tests(compiler: Compiler, tempdir: &Path) {
|
|
test_basic_compile(compiler.clone(), tempdir);
|
|
if compiler.name == "cl.exe" {
|
|
test_msvc_deps(compiler.clone(), tempdir);
|
|
}
|
|
if compiler.name == "gcc" {
|
|
test_gcc_mp_werror(compiler.clone(), tempdir);
|
|
test_gcc_fprofile_generate_source_changes(compiler.clone(), tempdir);
|
|
}
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
fn find_compilers() -> Vec<Compiler> {
|
|
let cwd = env::current_dir().unwrap();
|
|
COMPILERS.iter()
|
|
.filter_map(|c| {
|
|
match which_in(c, env::var_os("PATH"), &cwd) {
|
|
Ok(full_path) => match full_path.canonicalize() {
|
|
Ok(full_path_canon) => Some(Compiler {
|
|
name: *c,
|
|
exe: full_path_canon.into_os_string(),
|
|
env_vars: vec![],
|
|
}),
|
|
Err(_) => None,
|
|
},
|
|
Err(_) => None,
|
|
}
|
|
})
|
|
.collect::<Vec<_>>()
|
|
}
|
|
|
|
#[cfg(target_env="msvc")]
|
|
fn find_compilers() -> Vec<Compiler> {
|
|
let tool = cc::Build::new()
|
|
.opt_level(1)
|
|
.host("x86_64-pc-windows-msvc")
|
|
.target("x86_64-pc-windows-msvc")
|
|
.debug(false)
|
|
.get_compiler();
|
|
vec![
|
|
Compiler {
|
|
name: "cl.exe",
|
|
exe: tool.path().as_os_str().to_os_string(),
|
|
env_vars: tool.env().to_vec(),
|
|
},
|
|
]
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(unix, target_env="msvc"))]
|
|
fn test_sccache_command() {
|
|
match env_logger::try_init() {
|
|
Ok(_) => {},
|
|
Err(_) => {},
|
|
}
|
|
let tempdir = TempDir::new("sccache_system_test").unwrap();
|
|
let compilers = find_compilers();
|
|
if compilers.is_empty() {
|
|
warn!("No compilers found, skipping test");
|
|
} else {
|
|
// Ensure there's no existing sccache server running.
|
|
stop_local_daemon();
|
|
// Create the configurations
|
|
let sccache_cfg = sccache_client_cfg(tempdir.path());
|
|
write_json_cfg(tempdir.path(), "sccache-cfg.json", &sccache_cfg);
|
|
let sccache_cached_cfg_path = tempdir.path().join("sccache-cached-cfg");
|
|
// Start a server.
|
|
trace!("start server");
|
|
start_local_daemon(&tempdir.path().join("sccache-cfg.json"), &sccache_cached_cfg_path);
|
|
for compiler in compilers {
|
|
run_sccache_command_tests(compiler, tempdir.path());
|
|
zero_stats();
|
|
}
|
|
stop_local_daemon();
|
|
}
|
|
}
|