Auto merge of #72672 - seritools:remote-test-windows, r=Mark-Simulacrum
Make remote-test-client and remote-test-server compatible with windows `compiletest` and `remote-test-client`: The command line for `remote-test-client` was changed slightly to allow cross-platform compatible paths. The old way of supplying the support libs was by joining their paths with the executable path with `:`. This caused Windows-style paths to be split after the directory letter. Now, the number of support libs is provided as a parameter as well, and the support lib paths are split off from the regular args in the client. `remote-test-server`: - Marked Unix-only parts as such and implemented Windows alternatives - On Windows `LD_LIBRARY_PATH` doesn't exist. Libraries are loaded from `PATH` though, so that's the way around it. - Tiny cleanup: `Command::args`/`envs` instead of manually looping over them - The temp path for Windows has to be set via environment variable, since there isn't a global temp directory that would work on every machine (as a static string)
This commit is contained in:
commit
eeaf497b2a
4 changed files with 89 additions and 40 deletions
|
@ -1769,7 +1769,7 @@ impl Step for Crate {
|
||||||
} else if builder.remote_tested(target) {
|
} else if builder.remote_tested(target) {
|
||||||
cargo.env(
|
cargo.env(
|
||||||
format!("CARGO_TARGET_{}_RUNNER", envify(&target)),
|
format!("CARGO_TARGET_{}_RUNNER", envify(&target)),
|
||||||
format!("{} run", builder.tool_exe(Tool::RemoteTestClient).display()),
|
format!("{} run 0", builder.tool_exe(Tool::RemoteTestClient).display()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1584,29 +1584,34 @@ impl<'test> TestCx<'test> {
|
||||||
//
|
//
|
||||||
// into
|
// into
|
||||||
//
|
//
|
||||||
// remote-test-client run program:support-lib.so arg1 arg2
|
// remote-test-client run program 2 support-lib.so support-lib2.so arg1 arg2
|
||||||
//
|
//
|
||||||
// The test-client program will upload `program` to the emulator
|
// The test-client program will upload `program` to the emulator
|
||||||
// along with all other support libraries listed (in this case
|
// along with all other support libraries listed (in this case
|
||||||
// `support-lib.so`. It will then execute the program on the
|
// `support-lib.so` and `support-lib2.so`. It will then execute
|
||||||
// emulator with the arguments specified (in the environment we give
|
// the program on the emulator with the arguments specified
|
||||||
// the process) and then report back the same result.
|
// (in the environment we give the process) and then report back
|
||||||
|
// the same result.
|
||||||
_ if self.config.remote_test_client.is_some() => {
|
_ if self.config.remote_test_client.is_some() => {
|
||||||
let aux_dir = self.aux_output_dir_name();
|
let aux_dir = self.aux_output_dir_name();
|
||||||
let ProcArgs { mut prog, args } = self.make_run_args();
|
let ProcArgs { prog, args } = self.make_run_args();
|
||||||
|
let mut support_libs = Vec::new();
|
||||||
if let Ok(entries) = aux_dir.read_dir() {
|
if let Ok(entries) = aux_dir.read_dir() {
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
let entry = entry.unwrap();
|
let entry = entry.unwrap();
|
||||||
if !entry.path().is_file() {
|
if !entry.path().is_file() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
prog.push_str(":");
|
support_libs.push(entry.path());
|
||||||
prog.push_str(entry.path().to_str().unwrap());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut test_client =
|
let mut test_client =
|
||||||
Command::new(self.config.remote_test_client.as_ref().unwrap());
|
Command::new(self.config.remote_test_client.as_ref().unwrap());
|
||||||
test_client.args(&["run", &prog]).args(args).envs(env.clone());
|
test_client
|
||||||
|
.args(&["run", &support_libs.len().to_string(), &prog])
|
||||||
|
.args(support_libs)
|
||||||
|
.args(args)
|
||||||
|
.envs(env.clone());
|
||||||
self.compose_and_run(
|
self.compose_and_run(
|
||||||
test_client,
|
test_client,
|
||||||
self.config.run_lib_path.to_str().unwrap(),
|
self.config.run_lib_path.to_str().unwrap(),
|
||||||
|
|
|
@ -44,7 +44,13 @@ fn main() {
|
||||||
args.next().map(|s| s.into()),
|
args.next().map(|s| s.into()),
|
||||||
),
|
),
|
||||||
"push" => push(Path::new(&args.next().unwrap())),
|
"push" => push(Path::new(&args.next().unwrap())),
|
||||||
"run" => run(args.next().unwrap(), args.collect()),
|
"run" => run(
|
||||||
|
args.next().and_then(|count| count.parse().ok()).unwrap(),
|
||||||
|
// the last required parameter must remain the executable
|
||||||
|
// path so that the client works as a cargo runner
|
||||||
|
args.next().unwrap(),
|
||||||
|
args.collect(),
|
||||||
|
),
|
||||||
"help" | "-h" | "--help" => help(),
|
"help" | "-h" | "--help" => help(),
|
||||||
cmd => {
|
cmd => {
|
||||||
println!("unknown command: {}", cmd);
|
println!("unknown command: {}", cmd);
|
||||||
|
@ -197,12 +203,14 @@ fn push(path: &Path) {
|
||||||
println!("done pushing {:?}", path);
|
println!("done pushing {:?}", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(files: String, args: Vec<String>) {
|
fn run(support_lib_count: usize, exe: String, all_args: Vec<String>) {
|
||||||
let device_address = env::var(REMOTE_ADDR_ENV).unwrap_or(DEFAULT_ADDR.to_string());
|
let device_address = env::var(REMOTE_ADDR_ENV).unwrap_or(DEFAULT_ADDR.to_string());
|
||||||
let client = t!(TcpStream::connect(device_address));
|
let client = t!(TcpStream::connect(device_address));
|
||||||
let mut client = BufWriter::new(client);
|
let mut client = BufWriter::new(client);
|
||||||
t!(client.write_all(b"run "));
|
t!(client.write_all(b"run "));
|
||||||
|
|
||||||
|
let (support_libs, args) = all_args.split_at(support_lib_count);
|
||||||
|
|
||||||
// Send over the args
|
// Send over the args
|
||||||
for arg in args {
|
for arg in args {
|
||||||
t!(client.write_all(arg.as_bytes()));
|
t!(client.write_all(arg.as_bytes()));
|
||||||
|
@ -227,9 +235,7 @@ fn run(files: String, args: Vec<String>) {
|
||||||
t!(client.write_all(&[0]));
|
t!(client.write_all(&[0]));
|
||||||
|
|
||||||
// Send over support libraries
|
// Send over support libraries
|
||||||
let mut files = files.split(':');
|
for file in support_libs.iter().map(Path::new) {
|
||||||
let exe = files.next().unwrap();
|
|
||||||
for file in files.map(Path::new) {
|
|
||||||
send(&file, &mut client);
|
send(&file, &mut client);
|
||||||
}
|
}
|
||||||
t!(client.write_all(&[0]));
|
t!(client.write_all(&[0]));
|
||||||
|
@ -302,7 +308,8 @@ Usage: {0} <command> [<args>]
|
||||||
Sub-commands:
|
Sub-commands:
|
||||||
spawn-emulator <target> <server> <tmpdir> [rootfs] See below
|
spawn-emulator <target> <server> <tmpdir> [rootfs] See below
|
||||||
push <path> Copy <path> to emulator
|
push <path> Copy <path> to emulator
|
||||||
run <files> [args...] Run program on emulator
|
run <support_lib_count> <file> [support_libs...] [args...]
|
||||||
|
Run program on emulator
|
||||||
help Display help message
|
help Display help message
|
||||||
|
|
||||||
Spawning an emulator:
|
Spawning an emulator:
|
||||||
|
@ -321,8 +328,8 @@ specified. The file at <path> is sent to this target.
|
||||||
Executing commands on a running emulator:
|
Executing commands on a running emulator:
|
||||||
|
|
||||||
First the target emulator/adb session is connected to as for pushing files. Next
|
First the target emulator/adb session is connected to as for pushing files. Next
|
||||||
the colon separated list of <files> is pushed to the target. Finally, the first
|
the <file> and any specified support libs are pushed to the target. Finally, the
|
||||||
file in <files> is executed in the emulator, preserving the current environment.
|
<file> is executed in the emulator, preserving the current environment.
|
||||||
That command's status code is returned.
|
That command's status code is returned.
|
||||||
",
|
",
|
||||||
env::args().next().unwrap(),
|
env::args().next().unwrap(),
|
||||||
|
|
|
@ -12,15 +12,19 @@
|
||||||
|
|
||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
use std::fs::Permissions;
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
use std::os::unix::prelude::*;
|
||||||
|
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::{self, File, Permissions};
|
use std::fs::{self, File};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::io::{self, BufReader};
|
use std::io::{self, BufReader};
|
||||||
use std::net::{TcpListener, TcpStream};
|
use std::net::{TcpListener, TcpStream};
|
||||||
use std::os::unix::prelude::*;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, ExitStatus, Stdio};
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
@ -72,21 +76,23 @@ fn main() {
|
||||||
|
|
||||||
let config = Config::parse_args();
|
let config = Config::parse_args();
|
||||||
|
|
||||||
let bind_addr = if cfg!(target_os = "android") || config.remote {
|
let bind_addr = if cfg!(target_os = "android") || cfg!(windows) || config.remote {
|
||||||
"0.0.0.0:12345"
|
"0.0.0.0:12345"
|
||||||
} else {
|
} else {
|
||||||
"10.0.2.15:12345"
|
"10.0.2.15:12345"
|
||||||
};
|
};
|
||||||
|
|
||||||
let (listener, work) = if cfg!(target_os = "android") {
|
let listener = t!(TcpListener::bind(bind_addr));
|
||||||
(t!(TcpListener::bind(bind_addr)), "/data/tmp/work")
|
let work: PathBuf = if cfg!(target_os = "android") {
|
||||||
|
"/data/tmp/work".into()
|
||||||
} else {
|
} else {
|
||||||
(t!(TcpListener::bind(bind_addr)), "/tmp/work")
|
let mut temp_dir = env::temp_dir();
|
||||||
|
temp_dir.push("work");
|
||||||
|
temp_dir
|
||||||
};
|
};
|
||||||
println!("listening!");
|
println!("listening!");
|
||||||
|
|
||||||
let work = Path::new(work);
|
t!(fs::create_dir_all(&work));
|
||||||
t!(fs::create_dir_all(work));
|
|
||||||
|
|
||||||
let lock = Arc::new(Mutex::new(()));
|
let lock = Arc::new(Mutex::new(()));
|
||||||
|
|
||||||
|
@ -99,10 +105,11 @@ fn main() {
|
||||||
if &buf[..] == b"ping" {
|
if &buf[..] == b"ping" {
|
||||||
t!(socket.write_all(b"pong"));
|
t!(socket.write_all(b"pong"));
|
||||||
} else if &buf[..] == b"push" {
|
} else if &buf[..] == b"push" {
|
||||||
handle_push(socket, work);
|
handle_push(socket, &work);
|
||||||
} else if &buf[..] == b"run " {
|
} else if &buf[..] == b"run " {
|
||||||
let lock = lock.clone();
|
let lock = lock.clone();
|
||||||
thread::spawn(move || handle_run(socket, work, &lock));
|
let work = work.clone();
|
||||||
|
thread::spawn(move || handle_run(socket, &work, &lock));
|
||||||
} else {
|
} else {
|
||||||
panic!("unknown command {:?}", buf);
|
panic!("unknown command {:?}", buf);
|
||||||
}
|
}
|
||||||
|
@ -196,17 +203,28 @@ fn handle_run(socket: TcpStream, work: &Path, lock: &Mutex<()>) {
|
||||||
let exe = recv(&path, &mut reader);
|
let exe = recv(&path, &mut reader);
|
||||||
|
|
||||||
let mut cmd = Command::new(&exe);
|
let mut cmd = Command::new(&exe);
|
||||||
for arg in args {
|
cmd.args(args);
|
||||||
cmd.arg(arg);
|
cmd.envs(env);
|
||||||
}
|
|
||||||
for (k, v) in env {
|
|
||||||
cmd.env(k, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Support libraries were uploaded to `work` earlier, so make sure that's
|
// Support libraries were uploaded to `work` earlier, so make sure that's
|
||||||
// in `LD_LIBRARY_PATH`. Also include our own current dir which may have
|
// in `LD_LIBRARY_PATH`. Also include our own current dir which may have
|
||||||
// had some libs uploaded.
|
// had some libs uploaded.
|
||||||
cmd.env("LD_LIBRARY_PATH", format!("{}:{}", work.display(), path.display()));
|
if cfg!(windows) {
|
||||||
|
// On windows, libraries are just searched in the executable directory,
|
||||||
|
// system directories, PWD, and PATH, in that order. PATH is the only one
|
||||||
|
// we can change for this.
|
||||||
|
cmd.env(
|
||||||
|
"PATH",
|
||||||
|
env::join_paths(
|
||||||
|
std::iter::once(work.to_owned())
|
||||||
|
.chain(std::iter::once(path.clone()))
|
||||||
|
.chain(env::split_paths(&env::var_os("PATH").unwrap())),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
cmd.env("LD_LIBRARY_PATH", format!("{}:{}", work.display(), path.display()));
|
||||||
|
}
|
||||||
|
|
||||||
// Spawn the child and ferry over stdout/stderr to the socket in a framed
|
// Spawn the child and ferry over stdout/stderr to the socket in a framed
|
||||||
// fashion (poor man's style)
|
// fashion (poor man's style)
|
||||||
|
@ -223,10 +241,9 @@ fn handle_run(socket: TcpStream, work: &Path, lock: &Mutex<()>) {
|
||||||
|
|
||||||
// Finally send over the exit status.
|
// Finally send over the exit status.
|
||||||
let status = t!(child.wait());
|
let status = t!(child.wait());
|
||||||
let (which, code) = match status.code() {
|
|
||||||
Some(n) => (0, n),
|
let (which, code) = get_status_code(&status);
|
||||||
None => (1, status.signal().unwrap()),
|
|
||||||
};
|
|
||||||
t!(socket.lock().unwrap().write_all(&[
|
t!(socket.lock().unwrap().write_all(&[
|
||||||
which,
|
which,
|
||||||
(code >> 24) as u8,
|
(code >> 24) as u8,
|
||||||
|
@ -236,6 +253,19 @@ fn handle_run(socket: TcpStream, work: &Path, lock: &Mutex<()>) {
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
fn get_status_code(status: &ExitStatus) -> (u8, i32) {
|
||||||
|
match status.code() {
|
||||||
|
Some(n) => (0, n),
|
||||||
|
None => (1, status.signal().unwrap()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn get_status_code(status: &ExitStatus) -> (u8, i32) {
|
||||||
|
(0, status.code().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
fn recv<B: BufRead>(dir: &Path, io: &mut B) -> PathBuf {
|
fn recv<B: BufRead>(dir: &Path, io: &mut B) -> PathBuf {
|
||||||
let mut filename = Vec::new();
|
let mut filename = Vec::new();
|
||||||
t!(io.read_until(0, &mut filename));
|
t!(io.read_until(0, &mut filename));
|
||||||
|
@ -253,10 +283,17 @@ fn recv<B: BufRead>(dir: &Path, io: &mut B) -> PathBuf {
|
||||||
let dst = dir.join(t!(str::from_utf8(&filename[..len])));
|
let dst = dir.join(t!(str::from_utf8(&filename[..len])));
|
||||||
let amt = read_u32(io) as u64;
|
let amt = read_u32(io) as u64;
|
||||||
t!(io::copy(&mut io.take(amt), &mut t!(File::create(&dst))));
|
t!(io::copy(&mut io.take(amt), &mut t!(File::create(&dst))));
|
||||||
t!(fs::set_permissions(&dst, Permissions::from_mode(0o755)));
|
set_permissions(&dst);
|
||||||
dst
|
dst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
fn set_permissions(path: &Path) {
|
||||||
|
t!(fs::set_permissions(&path, Permissions::from_mode(0o755)));
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn set_permissions(_path: &Path) {}
|
||||||
|
|
||||||
fn my_copy(src: &mut dyn Read, which: u8, dst: &Mutex<dyn Write>) {
|
fn my_copy(src: &mut dyn Read, which: u8, dst: &Mutex<dyn Write>) {
|
||||||
let mut b = [0; 1024];
|
let mut b = [0; 1024];
|
||||||
loop {
|
loop {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue