use crate::cache::disk::DiskCache;
use crate::client::connect_to_server;
use crate::commands::{do_compile, request_shutdown, request_stats};
use crate::jobserver::Client;
use crate::mock_command::*;
use crate::server::{DistClientContainer, SccacheServer, ServerMessage};
use crate::test::utils::*;
use futures::channel::oneshot::{self, Sender};
use std::fs::File;
use std::io::{Cursor, Write};
#[cfg(not(target_os = "macos"))]
use std::net::TcpListener;
use std::path::Path;
#[cfg(not(target_os = "macos"))]
use std::process::Command;
use std::sync::{mpsc, Arc, Mutex};
use std::thread;
use std::time::Duration;
use std::u64;
use tokio::runtime::Runtime;
#[derive(Default)]
struct ServerOptions {
idle_timeout: Option<u64>,
cache_size: Option<u64>,
}
fn run_server_thread<T>(
cache_dir: &Path,
options: T,
) -> (
u16,
Sender<ServerMessage>,
Arc<Mutex<MockCommandCreator>>,
thread::JoinHandle<()>,
)
where
T: Into<Option<ServerOptions>> + Send + 'static,
{
let options = options.into();
let cache_dir = cache_dir.to_path_buf();
let cache_size = options
.as_ref()
.and_then(|o| o.cache_size.as_ref())
.copied()
.unwrap_or(u64::MAX);
let (tx, rx) = mpsc::channel();
let (shutdown_tx, shutdown_rx) = oneshot::channel();
let handle = thread::spawn(move || {
let runtime = Runtime::new().unwrap();
let dist_client = DistClientContainer::new_disabled();
let storage = Arc::new(DiskCache::new(&cache_dir, cache_size, runtime.handle()));
let client = unsafe { Client::new() };
let srv = SccacheServer::new(0, runtime, client, dist_client, storage).unwrap();
let mut srv: SccacheServer<Arc<Mutex<MockCommandCreator>>> = srv;
assert!(srv.port() > 0);
if let Some(options) = options {
if let Some(timeout) = options.idle_timeout {
srv.set_idle_timeout(Duration::from_millis(timeout));
}
}
let port = srv.port();
let creator = srv.command_creator().clone();
tx.send((port, creator)).unwrap();
srv.run(shutdown_rx).unwrap();
});
let (port, creator) = rx.recv().unwrap();
(port, shutdown_tx, creator, handle)
}
#[test]
fn test_server_shutdown() {
let f = TestFixture::new();
let (port, _sender, _storage, child) = run_server_thread(f.tempdir.path(), None);
let conn = connect_to_server(port).unwrap();
request_shutdown(conn).unwrap();
child.join().unwrap();
}
#[test]
fn test_server_shutdown_no_idle() {
let f = TestFixture::new();
let (port, _sender, _storage, child) = run_server_thread(
f.tempdir.path(),
ServerOptions {
idle_timeout: Some(0),
..Default::default()
},
);
let conn = connect_to_server(port).unwrap();
request_shutdown(conn).unwrap();
child.join().unwrap();
}
#[test]
fn test_server_idle_timeout() {
let f = TestFixture::new();
let (_port, _sender, _storage, child) = run_server_thread(
f.tempdir.path(),
ServerOptions {
idle_timeout: Some(1),
..Default::default()
},
);
child.join().unwrap();
}
#[test]
fn test_server_stats() {
let f = TestFixture::new();
let (port, sender, _storage, child) = run_server_thread(f.tempdir.path(), None);
let conn = connect_to_server(port).unwrap();
let info = request_stats(conn).unwrap();
assert_eq!(0, info.stats.compile_requests);
sender.send(ServerMessage::Shutdown).ok().unwrap();
child.join().unwrap();
}
#[test]
fn test_server_unsupported_compiler() {
let f = TestFixture::new();
let (port, sender, server_creator, child) = run_server_thread(f.tempdir.path(), None);
let conn = connect_to_server(port).unwrap();
{
let mut c = server_creator.lock().unwrap();
c.next_command_spawns(Ok(MockChild::new(exit_status(0), "hello", "error")));
}
let exe = &f.bins[0];
let cmdline = vec!["-c".into(), "file.c".into(), "-o".into(), "file.o".into()];
let cwd = f.tempdir.path();
let client_creator = new_creator();
let mut stdout = Cursor::new(Vec::new());
let mut stderr = Cursor::new(Vec::new());
let path = Some(f.paths);
let mut runtime = Runtime::new().unwrap();
let res = do_compile(
client_creator,
&mut runtime,
conn,
exe,
cmdline,
cwd,
path,
vec![],
&mut stdout,
&mut stderr,
);
match res {
Ok(_) => panic!("do_compile should have failed!"),
Err(e) => assert_eq!("Compiler not supported: \"error\"", e.to_string()),
}
assert_eq!(0, server_creator.lock().unwrap().children.len());
sender.send(ServerMessage::Shutdown).ok().unwrap();
child.join().unwrap();
}
#[test]
fn test_server_compile() {
let _ = env_logger::try_init();
let f = TestFixture::new();
let (port, sender, server_creator, child) = run_server_thread(f.tempdir.path(), None);
const PREPROCESSOR_STDOUT: &[u8] = b"preprocessor stdout";
const PREPROCESSOR_STDERR: &[u8] = b"preprocessor stderr";
const STDOUT: &[u8] = b"some stdout";
const STDERR: &[u8] = b"some stderr";
let conn = connect_to_server(port).unwrap();
{
let mut c = server_creator.lock().unwrap();
c.next_command_spawns(Ok(MockChild::new(exit_status(0), "gcc", "")));
c.next_command_spawns(Ok(MockChild::new(
exit_status(0),
PREPROCESSOR_STDOUT,
PREPROCESSOR_STDERR,
)));
let obj = f.tempdir.path().join("file.o");
c.next_command_calls(move |_| {
let mut f = File::create(&obj)?;
f.write_all(b"file contents")?;
Ok(MockChild::new(exit_status(0), STDOUT, STDERR))
});
}
let exe = &f.bins[0];
let cmdline = vec!["-c".into(), "file.c".into(), "-o".into(), "file.o".into()];
let cwd = f.tempdir.path();
let client_creator = new_creator();
let mut stdout = Cursor::new(Vec::new());
let mut stderr = Cursor::new(Vec::new());
let path = Some(f.paths);
let mut runtime = Runtime::new().unwrap();
assert_eq!(
0,
do_compile(
client_creator,
&mut runtime,
conn,
exe,
cmdline,
cwd,
path,
vec![],
&mut stdout,
&mut stderr
)
.unwrap()
);
assert_eq!(0, server_creator.lock().unwrap().children.len());
assert_eq!(STDOUT, stdout.into_inner().as_slice());
assert_eq!(STDERR, stderr.into_inner().as_slice());
sender.send(ServerMessage::Shutdown).ok().unwrap();
child.join().unwrap();
}
#[test]
#[cfg(not(target_os = "macos"))]
fn test_server_port_in_use() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let sccache = find_sccache_binary();
let output = Command::new(&sccache)
.arg("--start-server")
.env(
"SCCACHE_SERVER_PORT",
listener.local_addr().unwrap().port().to_string(),
)
.output()
.unwrap();
assert!(!output.status.success());
let s = String::from_utf8_lossy(&output.stderr);
const MSG: &str = "Server startup failed:";
assert!(
s.contains(MSG),
"Output did not contain '{}':\n========\n{}\n========",
MSG,
s
);
}