1 //! This is a small server which is intended to run inside of an emulator or
2 //! on a remote test device. This server pairs with the `remote-test-client`
3 //! program in this repository. The `remote-test-client` connects to this
4 //! server over a TCP socket and performs work such as:
6 //! 1. Pushing shared libraries to the server
7 //! 2. Running tests through the server
9 //! The server supports running tests concurrently and also supports tests
10 //! themselves having support libraries. All data over the TCP sockets is in a
11 //! basically custom format suiting our needs.
14 use std::fs::Permissions;
16 use std::os::unix::prelude::*;
20 use std::fs::{self, File};
21 use std::io::prelude::*;
22 use std::io::{self, BufReader};
23 use std::net::{TcpListener, TcpStream};
24 use std::path::{Path, PathBuf};
25 use std::process::{Command, ExitStatus, Stdio};
27 use std::sync::atomic::{AtomicUsize, Ordering};
28 use std::sync::{Arc, Mutex};
35 Err(e) => panic!("{} failed with {}", stringify!($e), e),
40 static TEST: AtomicUsize = AtomicUsize::new(0);
42 #[derive(Copy, Clone)]
49 pub fn default() -> Config {
50 Config { remote: false, verbose: false }
53 pub fn parse_args() -> Config {
54 let mut config = Config::default();
56 let args = env::args().skip(1);
57 for argument in args {
63 config.verbose = true;
65 arg => panic!("unknown argument: {}", arg),
73 fn print_verbose(s: &str, conf: Config) {
80 println!("starting test server");
82 let config = Config::parse_args();
84 let bind_addr = if cfg!(target_os = "android") || cfg!(windows) || config.remote {
90 let listener = t!(TcpListener::bind(bind_addr));
91 let (work, tmp): (PathBuf, PathBuf) = if cfg!(target_os = "android") {
92 ("/data/tmp/work".into(), "/data/tmp/work/tmp".into())
94 let mut work_dir = env::temp_dir();
95 work_dir.push("work");
96 let mut tmp_dir = work_dir.clone();
100 println!("listening on {}!", bind_addr);
102 t!(fs::create_dir_all(&work));
103 t!(fs::create_dir_all(&tmp));
105 let lock = Arc::new(Mutex::new(()));
107 for socket in listener.incoming() {
108 let mut socket = t!(socket);
109 let mut buf = [0; 4];
110 if socket.read_exact(&mut buf).is_err() {
113 if &buf[..] == b"ping" {
114 print_verbose("Received ping", config);
115 t!(socket.write_all(b"pong"));
116 } else if &buf[..] == b"push" {
117 handle_push(socket, &work, config);
118 } else if &buf[..] == b"run " {
119 let lock = lock.clone();
120 let work = work.clone();
121 let tmp = tmp.clone();
122 thread::spawn(move || handle_run(socket, &work, &tmp, &lock, config));
124 panic!("unknown command {:?}", buf);
129 fn handle_push(socket: TcpStream, work: &Path, config: Config) {
130 let mut reader = BufReader::new(socket);
131 let dst = recv(&work, &mut reader);
132 print_verbose(&format!("push {:#?}", dst), config);
134 let mut socket = reader.into_inner();
135 t!(socket.write_all(b"ack "));
138 struct RemoveOnDrop<'a> {
142 impl Drop for RemoveOnDrop<'_> {
144 t!(fs::remove_dir_all(self.inner));
148 fn handle_run(socket: TcpStream, work: &Path, tmp: &Path, lock: &Mutex<()>, config: Config) {
149 let mut arg = Vec::new();
150 let mut reader = BufReader::new(socket);
152 // Allocate ourselves a directory that we'll delete when we're done to save
154 let n = TEST.fetch_add(1, Ordering::SeqCst);
155 let path = work.join(format!("test{}", n));
156 t!(fs::create_dir(&path));
157 let _a = RemoveOnDrop { inner: &path };
159 // First up we'll get a list of arguments delimited with 0 bytes. An empty
160 // argument means that we're done.
161 let mut args = Vec::new();
162 while t!(reader.read_until(0, &mut arg)) > 1 {
163 args.push(t!(str::from_utf8(&arg[..arg.len() - 1])).to_string());
167 // Next we'll get a bunch of env vars in pairs delimited by 0s as well
168 let mut env = Vec::new();
170 while t!(reader.read_until(0, &mut arg)) > 1 {
171 let key_len = arg.len() - 1;
172 let val_len = t!(reader.read_until(0, &mut arg)) - 1;
174 let key = &arg[..key_len];
175 let val = &arg[key_len + 1..][..val_len];
176 let key = t!(str::from_utf8(key)).to_string();
177 let val = t!(str::from_utf8(val)).to_string();
178 env.push((key, val));
183 // The section of code from here down to where we drop the lock is going to
184 // be a critical section for us. On Linux you can't execute a file which is
185 // open somewhere for writing, as you'll receive the error "text file busy".
186 // Now here we never have the text file open for writing when we spawn it,
187 // so why do we still need a critical section?
189 // Process spawning first involves a `fork` on Unix, which clones all file
190 // descriptors into the child process. This means that it's possible for us
191 // to open the file for writing (as we're downloading it), then some other
192 // thread forks, then we close the file and try to exec. At that point the
193 // other thread created a child process with the file open for writing, and
194 // we attempt to execute it, so we get an error.
196 // This race is resolve by ensuring that only one thread can write the file
197 // and spawn a child process at once. Kinda an unfortunate solution, but we
198 // don't have many other choices with this sort of setup!
200 // In any case the lock is acquired here, before we start writing any files.
201 // It's then dropped just after we spawn the child. That way we don't lock
202 // the execution of the child, just the creation of its files.
203 let lock = lock.lock();
205 // Next there's a list of dynamic libraries preceded by their filenames.
206 while t!(reader.fill_buf())[0] != 0 {
207 recv(&path, &mut reader);
209 assert_eq!(t!(reader.read(&mut [0])), 1);
211 // Finally we'll get the binary. The other end will tell us how big the
212 // binary is and then we'll download it all to the exe path we calculated
214 let exe = recv(&path, &mut reader);
215 print_verbose(&format!("run {:#?}", exe), config);
217 let mut cmd = Command::new(&exe);
221 // On windows, libraries are just searched in the executable directory,
222 // system directories, PWD, and PATH, in that order. PATH is the only one
223 // we can change for this.
224 let library_path = if cfg!(windows) { "PATH" } else { "LD_LIBRARY_PATH" };
226 // Support libraries were uploaded to `work` earlier, so make sure that's
227 // in `LD_LIBRARY_PATH`. Also include our own current dir which may have
228 // had some libs uploaded.
229 let mut paths = vec![work.to_owned(), path.clone()];
230 if let Some(library_path) = env::var_os(library_path) {
231 paths.extend(env::split_paths(&library_path));
233 cmd.env(library_path, env::join_paths(paths).unwrap());
235 // Some tests assume RUST_TEST_TMPDIR exists
236 cmd.env("RUST_TEST_TMPDIR", tmp.to_owned());
238 // Spawn the child and ferry over stdout/stderr to the socket in a framed
239 // fashion (poor man's style)
241 t!(cmd.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn());
243 let mut stdout = child.stdout.take().unwrap();
244 let mut stderr = child.stderr.take().unwrap();
245 let socket = Arc::new(Mutex::new(reader.into_inner()));
246 let socket2 = socket.clone();
247 let thread = thread::spawn(move || my_copy(&mut stdout, 0, &*socket2));
248 my_copy(&mut stderr, 1, &*socket);
249 thread.join().unwrap();
251 // Finally send over the exit status.
252 let status = t!(child.wait());
254 let (which, code) = get_status_code(&status);
256 t!(socket.lock().unwrap().write_all(&[
266 fn get_status_code(status: &ExitStatus) -> (u8, i32) {
267 match status.code() {
269 None => (1, status.signal().unwrap()),
274 fn get_status_code(status: &ExitStatus) -> (u8, i32) {
275 (0, status.code().unwrap())
278 fn recv<B: BufRead>(dir: &Path, io: &mut B) -> PathBuf {
279 let mut filename = Vec::new();
280 t!(io.read_until(0, &mut filename));
282 // We've got some tests with *really* long names. We try to name the test
283 // executable the same on the target as it is on the host to aid with
284 // debugging, but the targets we're emulating are often more restrictive
285 // than the hosts as well.
287 // To ensure we can run a maximum number of tests without modifications we
288 // just arbitrarily truncate the filename to 50 bytes. That should
289 // hopefully allow us to still identify what's running while staying under
290 // the filesystem limits.
291 let len = cmp::min(filename.len() - 1, 50);
292 let dst = dir.join(t!(str::from_utf8(&filename[..len])));
293 let amt = read_u32(io) as u64;
294 t!(io::copy(&mut io.take(amt), &mut t!(File::create(&dst))));
295 set_permissions(&dst);
300 fn set_permissions(path: &Path) {
301 t!(fs::set_permissions(&path, Permissions::from_mode(0o755)));
304 fn set_permissions(_path: &Path) {}
306 fn my_copy(src: &mut dyn Read, which: u8, dst: &Mutex<dyn Write>) {
307 let mut b = [0; 1024];
309 let n = t!(src.read(&mut b));
310 let mut dst = dst.lock().unwrap();
319 t!(dst.write_all(&b[..n]));
326 fn read_u32(r: &mut dyn Read) -> u32 {
327 let mut len = [0; 4];
328 t!(r.read_exact(&mut len));
329 ((len[0] as u32) << 24)
330 | ((len[1] as u32) << 16)
331 | ((len[2] as u32) << 8)
332 | ((len[3] as u32) << 0)