Auto merge of #41575 - alexcrichton:android-qemu-server, r=TimNN
travis: Parallelize tests on Android Currently our slowest test suite on android, run-pass, takes over 5 times longer than the x86_64 component (~400 -> ~2200s). Typically QEMU emulation does indeed add overhead, but not 5x for this kind of workload. One of the slowest parts of the Android process is that *compilation* happens serially. Tests themselves need to run single-threaded on the emulator (due to how the test harness works) and this forces the compiles themselves to be single threaded. Now Travis gives us more than one core per machine, so it'd be much better if we could take advantage of them! The emulator itself is still fundamentally single-threaded, but we should see a nice speedup by sending binaries for it to run much more quickly. It turns out that we've already got all the toos to do this in-tree. The qemu-test-{server,client} that are in use for the ARM Linux testing are a perfect match for the Android emulator. This commit migrates the custom adb management code in compiletest/rustbuild to the same qemu-test-{server,client} implementation that ARM Linux uses. This allows us to lift the parallelism restriction on the compiletest test suites, namely run-pass. Consequently although we'll still basically run the tests themselves in single threaded mode we'll be able to compile all of them in parallel, keeping the pipeline much more full hopefully and using more cores for the work at hand. Additionally the architecture here should be a bit speedier as it should have less overhead than adb which is a whole new process on both the host and the emulator! Locally on an 8 core machine I've seen the run-pass test suite speed up from taking nearly an hour to only taking 5 minutes. I don't think we'll see quite a drastic speedup on Travis but I'm hoping this change can place the Android tests well below 2 hours instead of just above 2 hours. Because the client/server here are now repurposed for more than just QEMU, they've been renamed to `remote-test-{server,client}`. Note that this PR does not currently modify how debuginfo tests are executed on Android. While parallelizable it wouldn't be quite as easy, so that's left to another day. Thankfull that test suite is much smaller than the run-pass test suite.
This commit is contained in:
commit
ad1461efb9
14 changed files with 243 additions and 467 deletions
16
src/Cargo.lock
generated
16
src/Cargo.lock
generated
|
@ -366,14 +366,6 @@ dependencies = [
|
||||||
"bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "qemu-test-client"
|
|
||||||
version = "0.1.0"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "qemu-test-server"
|
|
||||||
version = "0.1.0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-error"
|
name = "quick-error"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -403,6 +395,14 @@ name = "regex-syntax"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "remote-test-client"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "remote-test-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rls-data"
|
name = "rls-data"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -11,8 +11,8 @@ members = [
|
||||||
"tools/rustbook",
|
"tools/rustbook",
|
||||||
"tools/tidy",
|
"tools/tidy",
|
||||||
"tools/build-manifest",
|
"tools/build-manifest",
|
||||||
"tools/qemu-test-client",
|
"tools/remote-test-client",
|
||||||
"tools/qemu-test-server",
|
"tools/remote-test-server",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Curiously, compiletest will segfault if compiled with opt-level=3 on 64-bit
|
# Curiously, compiletest will segfault if compiled with opt-level=3 on 64-bit
|
||||||
|
|
|
@ -28,7 +28,7 @@ use {Build, Compiler, Mode};
|
||||||
use dist;
|
use dist;
|
||||||
use util::{self, dylib_path, dylib_path_var, exe};
|
use util::{self, dylib_path, dylib_path_var, exe};
|
||||||
|
|
||||||
const ADB_TEST_DIR: &'static str = "/data/tmp";
|
const ADB_TEST_DIR: &'static str = "/data/tmp/work";
|
||||||
|
|
||||||
/// The two modes of the test runner; tests or benchmarks.
|
/// The two modes of the test runner; tests or benchmarks.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
|
@ -243,10 +243,10 @@ pub fn compiletest(build: &Build,
|
||||||
.arg("--llvm-cxxflags").arg("");
|
.arg("--llvm-cxxflags").arg("");
|
||||||
}
|
}
|
||||||
|
|
||||||
if build.qemu_rootfs(target).is_some() {
|
if build.remote_tested(target) {
|
||||||
cmd.arg("--qemu-test-client")
|
cmd.arg("--remote-test-client")
|
||||||
.arg(build.tool(&Compiler::new(0, &build.config.build),
|
.arg(build.tool(&Compiler::new(0, &build.config.build),
|
||||||
"qemu-test-client"));
|
"remote-test-client"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Running a C compiler on MSVC requires a few env vars to be set, to be
|
// Running a C compiler on MSVC requires a few env vars to be set, to be
|
||||||
|
@ -445,9 +445,7 @@ pub fn krate(build: &Build,
|
||||||
dylib_path.insert(0, build.sysroot_libdir(&compiler, target));
|
dylib_path.insert(0, build.sysroot_libdir(&compiler, target));
|
||||||
cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
|
cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
|
||||||
|
|
||||||
if target.contains("android") ||
|
if target.contains("emscripten") || build.remote_tested(target) {
|
||||||
target.contains("emscripten") ||
|
|
||||||
build.qemu_rootfs(target).is_some() {
|
|
||||||
cargo.arg("--no-run");
|
cargo.arg("--no-run");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,75 +457,24 @@ pub fn krate(build: &Build,
|
||||||
|
|
||||||
let _time = util::timeit();
|
let _time = util::timeit();
|
||||||
|
|
||||||
if target.contains("android") {
|
if target.contains("emscripten") {
|
||||||
build.run(&mut cargo);
|
|
||||||
krate_android(build, &compiler, target, mode);
|
|
||||||
} else if target.contains("emscripten") {
|
|
||||||
build.run(&mut cargo);
|
build.run(&mut cargo);
|
||||||
krate_emscripten(build, &compiler, target, mode);
|
krate_emscripten(build, &compiler, target, mode);
|
||||||
} else if build.qemu_rootfs(target).is_some() {
|
} else if build.remote_tested(target) {
|
||||||
build.run(&mut cargo);
|
build.run(&mut cargo);
|
||||||
krate_qemu(build, &compiler, target, mode);
|
krate_remote(build, &compiler, target, mode);
|
||||||
} else {
|
} else {
|
||||||
cargo.args(&build.flags.cmd.test_args());
|
cargo.args(&build.flags.cmd.test_args());
|
||||||
build.run(&mut cargo);
|
build.run(&mut cargo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn krate_android(build: &Build,
|
|
||||||
compiler: &Compiler,
|
|
||||||
target: &str,
|
|
||||||
mode: Mode) {
|
|
||||||
let mut tests = Vec::new();
|
|
||||||
let out_dir = build.cargo_out(compiler, mode, target);
|
|
||||||
find_tests(&out_dir, target, &mut tests);
|
|
||||||
find_tests(&out_dir.join("deps"), target, &mut tests);
|
|
||||||
|
|
||||||
for test in tests {
|
|
||||||
build.run(Command::new("adb").arg("push").arg(&test).arg(ADB_TEST_DIR));
|
|
||||||
|
|
||||||
let test_file_name = test.file_name().unwrap().to_string_lossy();
|
|
||||||
let log = format!("{}/check-stage{}-T-{}-H-{}-{}.log",
|
|
||||||
ADB_TEST_DIR,
|
|
||||||
compiler.stage,
|
|
||||||
target,
|
|
||||||
compiler.host,
|
|
||||||
test_file_name);
|
|
||||||
let quiet = if build.config.quiet_tests { "--quiet" } else { "" };
|
|
||||||
let program = format!("(cd {dir}; \
|
|
||||||
LD_LIBRARY_PATH=./{target} ./{test} \
|
|
||||||
--logfile {log} \
|
|
||||||
{quiet} \
|
|
||||||
{args})",
|
|
||||||
dir = ADB_TEST_DIR,
|
|
||||||
target = target,
|
|
||||||
test = test_file_name,
|
|
||||||
log = log,
|
|
||||||
quiet = quiet,
|
|
||||||
args = build.flags.cmd.test_args().join(" "));
|
|
||||||
|
|
||||||
let output = output(Command::new("adb").arg("shell").arg(&program));
|
|
||||||
println!("{}", output);
|
|
||||||
|
|
||||||
t!(fs::create_dir_all(build.out.join("tmp")));
|
|
||||||
build.run(Command::new("adb")
|
|
||||||
.arg("pull")
|
|
||||||
.arg(&log)
|
|
||||||
.arg(build.out.join("tmp")));
|
|
||||||
build.run(Command::new("adb").arg("shell").arg("rm").arg(&log));
|
|
||||||
if !output.contains("result: ok") {
|
|
||||||
panic!("some tests failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn krate_emscripten(build: &Build,
|
fn krate_emscripten(build: &Build,
|
||||||
compiler: &Compiler,
|
compiler: &Compiler,
|
||||||
target: &str,
|
target: &str,
|
||||||
mode: Mode) {
|
mode: Mode) {
|
||||||
let mut tests = Vec::new();
|
let mut tests = Vec::new();
|
||||||
let out_dir = build.cargo_out(compiler, mode, target);
|
let out_dir = build.cargo_out(compiler, mode, target);
|
||||||
find_tests(&out_dir, target, &mut tests);
|
|
||||||
find_tests(&out_dir.join("deps"), target, &mut tests);
|
find_tests(&out_dir.join("deps"), target, &mut tests);
|
||||||
|
|
||||||
for test in tests {
|
for test in tests {
|
||||||
|
@ -543,17 +490,16 @@ fn krate_emscripten(build: &Build,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn krate_qemu(build: &Build,
|
fn krate_remote(build: &Build,
|
||||||
compiler: &Compiler,
|
compiler: &Compiler,
|
||||||
target: &str,
|
target: &str,
|
||||||
mode: Mode) {
|
mode: Mode) {
|
||||||
let mut tests = Vec::new();
|
let mut tests = Vec::new();
|
||||||
let out_dir = build.cargo_out(compiler, mode, target);
|
let out_dir = build.cargo_out(compiler, mode, target);
|
||||||
find_tests(&out_dir, target, &mut tests);
|
|
||||||
find_tests(&out_dir.join("deps"), target, &mut tests);
|
find_tests(&out_dir.join("deps"), target, &mut tests);
|
||||||
|
|
||||||
let tool = build.tool(&Compiler::new(0, &build.config.build),
|
let tool = build.tool(&Compiler::new(0, &build.config.build),
|
||||||
"qemu-test-client");
|
"remote-test-client");
|
||||||
for test in tests {
|
for test in tests {
|
||||||
let mut cmd = Command::new(&tool);
|
let mut cmd = Command::new(&tool);
|
||||||
cmd.arg("run")
|
cmd.arg("run")
|
||||||
|
@ -566,7 +512,6 @@ fn krate_qemu(build: &Build,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn find_tests(dir: &Path,
|
fn find_tests(dir: &Path,
|
||||||
target: &str,
|
target: &str,
|
||||||
dst: &mut Vec<PathBuf>) {
|
dst: &mut Vec<PathBuf>) {
|
||||||
|
@ -585,59 +530,28 @@ fn find_tests(dir: &Path,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn emulator_copy_libs(build: &Build, compiler: &Compiler, target: &str) {
|
pub fn emulator_copy_libs(build: &Build, compiler: &Compiler, target: &str) {
|
||||||
if target.contains("android") {
|
if !build.remote_tested(target) {
|
||||||
android_copy_libs(build, compiler, target)
|
return
|
||||||
} else if let Some(s) = build.qemu_rootfs(target) {
|
|
||||||
qemu_copy_libs(build, compiler, target, s)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn android_copy_libs(build: &Build, compiler: &Compiler, target: &str) {
|
println!("REMOTE copy libs to emulator ({})", target);
|
||||||
println!("Android copy libs to emulator ({})", target);
|
|
||||||
build.run(Command::new("adb").arg("wait-for-device"));
|
|
||||||
build.run(Command::new("adb").arg("remount"));
|
|
||||||
build.run(Command::new("adb").args(&["shell", "rm", "-r", ADB_TEST_DIR]));
|
|
||||||
build.run(Command::new("adb").args(&["shell", "mkdir", ADB_TEST_DIR]));
|
|
||||||
build.run(Command::new("adb")
|
|
||||||
.arg("push")
|
|
||||||
.arg(build.src.join("src/etc/adb_run_wrapper.sh"))
|
|
||||||
.arg(ADB_TEST_DIR));
|
|
||||||
|
|
||||||
let target_dir = format!("{}/{}", ADB_TEST_DIR, target);
|
|
||||||
build.run(Command::new("adb").args(&["shell", "mkdir", &target_dir]));
|
|
||||||
|
|
||||||
for f in t!(build.sysroot_libdir(compiler, target).read_dir()) {
|
|
||||||
let f = t!(f);
|
|
||||||
let name = f.file_name().into_string().unwrap();
|
|
||||||
if util::is_dylib(&name) {
|
|
||||||
build.run(Command::new("adb")
|
|
||||||
.arg("push")
|
|
||||||
.arg(f.path())
|
|
||||||
.arg(&target_dir));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn qemu_copy_libs(build: &Build,
|
|
||||||
compiler: &Compiler,
|
|
||||||
target: &str,
|
|
||||||
rootfs: &Path) {
|
|
||||||
println!("QEMU copy libs to emulator ({})", target);
|
|
||||||
assert!(target.starts_with("arm"), "only works with arm for now");
|
|
||||||
t!(fs::create_dir_all(build.out.join("tmp")));
|
t!(fs::create_dir_all(build.out.join("tmp")));
|
||||||
|
|
||||||
// Copy our freshly compiled test server over to the rootfs
|
|
||||||
let server = build.cargo_out(compiler, Mode::Tool, target)
|
let server = build.cargo_out(compiler, Mode::Tool, target)
|
||||||
.join(exe("qemu-test-server", target));
|
.join(exe("remote-test-server", target));
|
||||||
t!(fs::copy(&server, rootfs.join("testd")));
|
|
||||||
|
|
||||||
// Spawn the emulator and wait for it to come online
|
// Spawn the emulator and wait for it to come online
|
||||||
let tool = build.tool(&Compiler::new(0, &build.config.build),
|
let tool = build.tool(&Compiler::new(0, &build.config.build),
|
||||||
"qemu-test-client");
|
"remote-test-client");
|
||||||
build.run(Command::new(&tool)
|
let mut cmd = Command::new(&tool);
|
||||||
.arg("spawn-emulator")
|
cmd.arg("spawn-emulator")
|
||||||
.arg(rootfs)
|
.arg(target)
|
||||||
.arg(build.out.join("tmp")));
|
.arg(&server)
|
||||||
|
.arg(build.out.join("tmp"));
|
||||||
|
if let Some(rootfs) = build.qemu_rootfs(target) {
|
||||||
|
cmd.arg(rootfs);
|
||||||
|
}
|
||||||
|
build.run(&mut cmd);
|
||||||
|
|
||||||
// Push all our dylibs to the emulator
|
// Push all our dylibs to the emulator
|
||||||
for f in t!(build.sysroot_libdir(compiler, target).read_dir()) {
|
for f in t!(build.sysroot_libdir(compiler, target).read_dir()) {
|
||||||
|
|
|
@ -945,6 +945,12 @@ impl Build {
|
||||||
.map(|p| &**p)
|
.map(|p| &**p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether the target will be tested using the `remote-test-client`
|
||||||
|
/// and `remote-test-server` binaries.
|
||||||
|
fn remote_tested(&self, target: &str) -> bool {
|
||||||
|
self.qemu_rootfs(target).is_some() || target.contains("android")
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the root of the "rootfs" image that this target will be using,
|
/// Returns the root of the "rootfs" image that this target will be using,
|
||||||
/// if one was configured.
|
/// if one was configured.
|
||||||
///
|
///
|
||||||
|
|
|
@ -513,15 +513,15 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
|
||||||
rules.test("emulator-copy-libs", "path/to/nowhere")
|
rules.test("emulator-copy-libs", "path/to/nowhere")
|
||||||
.dep(|s| s.name("libtest"))
|
.dep(|s| s.name("libtest"))
|
||||||
.dep(move |s| {
|
.dep(move |s| {
|
||||||
if build.qemu_rootfs(s.target).is_some() {
|
if build.remote_tested(s.target) {
|
||||||
s.name("tool-qemu-test-client").target(s.host).stage(0)
|
s.name("tool-remote-test-client").target(s.host).stage(0)
|
||||||
} else {
|
} else {
|
||||||
Step::noop()
|
Step::noop()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.dep(move |s| {
|
.dep(move |s| {
|
||||||
if build.qemu_rootfs(s.target).is_some() {
|
if build.remote_tested(s.target) {
|
||||||
s.name("tool-qemu-test-server")
|
s.name("tool-remote-test-server")
|
||||||
} else {
|
} else {
|
||||||
Step::noop()
|
Step::noop()
|
||||||
}
|
}
|
||||||
|
@ -566,14 +566,14 @@ pub fn build_rules<'a>(build: &'a Build) -> Rules {
|
||||||
.dep(|s| s.name("maybe-clean-tools"))
|
.dep(|s| s.name("maybe-clean-tools"))
|
||||||
.dep(|s| s.name("libstd-tool"))
|
.dep(|s| s.name("libstd-tool"))
|
||||||
.run(move |s| compile::tool(build, s.stage, s.target, "build-manifest"));
|
.run(move |s| compile::tool(build, s.stage, s.target, "build-manifest"));
|
||||||
rules.build("tool-qemu-test-server", "src/tools/qemu-test-server")
|
rules.build("tool-remote-test-server", "src/tools/remote-test-server")
|
||||||
.dep(|s| s.name("maybe-clean-tools"))
|
.dep(|s| s.name("maybe-clean-tools"))
|
||||||
.dep(|s| s.name("libstd-tool"))
|
.dep(|s| s.name("libstd-tool"))
|
||||||
.run(move |s| compile::tool(build, s.stage, s.target, "qemu-test-server"));
|
.run(move |s| compile::tool(build, s.stage, s.target, "remote-test-server"));
|
||||||
rules.build("tool-qemu-test-client", "src/tools/qemu-test-client")
|
rules.build("tool-remote-test-client", "src/tools/remote-test-client")
|
||||||
.dep(|s| s.name("maybe-clean-tools"))
|
.dep(|s| s.name("maybe-clean-tools"))
|
||||||
.dep(|s| s.name("libstd-tool"))
|
.dep(|s| s.name("libstd-tool"))
|
||||||
.run(move |s| compile::tool(build, s.stage, s.target, "qemu-test-client"));
|
.run(move |s| compile::tool(build, s.stage, s.target, "remote-test-client"));
|
||||||
rules.build("tool-cargo", "cargo")
|
rules.build("tool-cargo", "cargo")
|
||||||
.dep(|s| s.name("maybe-clean-tools"))
|
.dep(|s| s.name("maybe-clean-tools"))
|
||||||
.dep(|s| s.name("libstd-tool"))
|
.dep(|s| s.name("libstd-tool"))
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
# Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
|
||||||
# file at the top-level directory of this distribution and at
|
|
||||||
# http://rust-lang.org/COPYRIGHT.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
||||||
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
||||||
# option. This file may not be copied, modified, or distributed
|
|
||||||
# except according to those terms.
|
|
||||||
#
|
|
||||||
# ignore-tidy-linelength
|
|
||||||
#
|
|
||||||
# usage : adb_run_wrapper [test dir - where test executables exist] [test executable]
|
|
||||||
#
|
|
||||||
|
|
||||||
TEST_PATH=$1
|
|
||||||
BIN_PATH=/system/bin
|
|
||||||
if [ -d "$TEST_PATH" ]
|
|
||||||
then
|
|
||||||
shift
|
|
||||||
RUN=$1
|
|
||||||
|
|
||||||
if [ ! -z "$RUN" ]
|
|
||||||
then
|
|
||||||
shift
|
|
||||||
|
|
||||||
# The length of binary path (i.e. ./$RUN) should be shorter than 128 characters.
|
|
||||||
cd $TEST_PATH
|
|
||||||
TEST_EXEC_ENV=22 LD_LIBRARY_PATH=$TEST_PATH PATH=$BIN_PATH:$TEST_PATH ./$RUN $@ 1>$TEST_PATH/$RUN.stdout 2>$TEST_PATH/$RUN.stderr
|
|
||||||
L_RET=$?
|
|
||||||
|
|
||||||
echo $L_RET > $TEST_PATH/$RUN.exitcode
|
|
||||||
|
|
||||||
fi
|
|
||||||
fi
|
|
|
@ -13,9 +13,11 @@
|
||||||
#![feature(rand)]
|
#![feature(rand)]
|
||||||
#![feature(const_fn)]
|
#![feature(const_fn)]
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
||||||
use std::__rand::{thread_rng, Rng};
|
use std::__rand::{thread_rng, Rng};
|
||||||
|
use std::panic;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
const MAX_LEN: usize = 80;
|
const MAX_LEN: usize = 80;
|
||||||
|
|
||||||
|
@ -76,6 +78,7 @@ fn test(input: &[DropCounter]) {
|
||||||
let mut panic_countdown = panic_countdown;
|
let mut panic_countdown = panic_countdown;
|
||||||
v.sort_by(|a, b| {
|
v.sort_by(|a, b| {
|
||||||
if panic_countdown == 0 {
|
if panic_countdown == 0 {
|
||||||
|
SILENCE_PANIC.with(|s| s.set(true));
|
||||||
panic!();
|
panic!();
|
||||||
}
|
}
|
||||||
panic_countdown -= 1;
|
panic_countdown -= 1;
|
||||||
|
@ -94,7 +97,15 @@ fn test(input: &[DropCounter]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thread_local!(static SILENCE_PANIC: Cell<bool> = Cell::new(false));
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
let prev = panic::take_hook();
|
||||||
|
panic::set_hook(Box::new(move |info| {
|
||||||
|
if !SILENCE_PANIC.with(|s| s.get()) {
|
||||||
|
prev(info);
|
||||||
|
}
|
||||||
|
}));
|
||||||
for len in (1..20).chain(70..MAX_LEN) {
|
for len in (1..20).chain(70..MAX_LEN) {
|
||||||
// Test on a random array.
|
// Test on a random array.
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|
|
@ -185,8 +185,8 @@ pub struct Config {
|
||||||
// Print one character per test instead of one line
|
// Print one character per test instead of one line
|
||||||
pub quiet: bool,
|
pub quiet: bool,
|
||||||
|
|
||||||
// where to find the qemu test client process, if we're using it
|
// where to find the remote test client process, if we're using it
|
||||||
pub qemu_test_client: Option<PathBuf>,
|
pub remote_test_client: Option<PathBuf>,
|
||||||
|
|
||||||
// Configuration for various run-make tests frobbing things like C compilers
|
// Configuration for various run-make tests frobbing things like C compilers
|
||||||
// or querying about various LLVM component information.
|
// or querying about various LLVM component information.
|
||||||
|
|
|
@ -106,7 +106,7 @@ pub fn parse_config(args: Vec<String> ) -> Config {
|
||||||
reqopt("", "llvm-components", "list of LLVM components built in", "LIST"),
|
reqopt("", "llvm-components", "list of LLVM components built in", "LIST"),
|
||||||
reqopt("", "llvm-cxxflags", "C++ flags for LLVM", "FLAGS"),
|
reqopt("", "llvm-cxxflags", "C++ flags for LLVM", "FLAGS"),
|
||||||
optopt("", "nodejs", "the name of nodejs", "PATH"),
|
optopt("", "nodejs", "the name of nodejs", "PATH"),
|
||||||
optopt("", "qemu-test-client", "path to the qemu test client", "PATH"),
|
optopt("", "remote-test-client", "path to the remote test client", "PATH"),
|
||||||
optflag("h", "help", "show this message")];
|
optflag("h", "help", "show this message")];
|
||||||
|
|
||||||
let (argv0, args_) = args.split_first().unwrap();
|
let (argv0, args_) = args.split_first().unwrap();
|
||||||
|
@ -177,9 +177,7 @@ pub fn parse_config(args: Vec<String> ) -> Config {
|
||||||
llvm_version: matches.opt_str("llvm-version"),
|
llvm_version: matches.opt_str("llvm-version"),
|
||||||
android_cross_path: opt_path(matches, "android-cross-path"),
|
android_cross_path: opt_path(matches, "android-cross-path"),
|
||||||
adb_path: opt_str2(matches.opt_str("adb-path")),
|
adb_path: opt_str2(matches.opt_str("adb-path")),
|
||||||
adb_test_dir: format!("{}/{}",
|
adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")),
|
||||||
opt_str2(matches.opt_str("adb-test-dir")),
|
|
||||||
opt_str2(matches.opt_str("target"))),
|
|
||||||
adb_device_status:
|
adb_device_status:
|
||||||
opt_str2(matches.opt_str("target")).contains("android") &&
|
opt_str2(matches.opt_str("target")).contains("android") &&
|
||||||
"(none)" != opt_str2(matches.opt_str("adb-test-dir")) &&
|
"(none)" != opt_str2(matches.opt_str("adb-test-dir")) &&
|
||||||
|
@ -187,7 +185,7 @@ pub fn parse_config(args: Vec<String> ) -> Config {
|
||||||
lldb_python_dir: matches.opt_str("lldb-python-dir"),
|
lldb_python_dir: matches.opt_str("lldb-python-dir"),
|
||||||
verbose: matches.opt_present("verbose"),
|
verbose: matches.opt_present("verbose"),
|
||||||
quiet: matches.opt_present("quiet"),
|
quiet: matches.opt_present("quiet"),
|
||||||
qemu_test_client: matches.opt_str("qemu-test-client").map(PathBuf::from),
|
remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
|
||||||
|
|
||||||
cc: matches.opt_str("cc").unwrap(),
|
cc: matches.opt_str("cc").unwrap(),
|
||||||
cxx: matches.opt_str("cxx").unwrap(),
|
cxx: matches.opt_str("cxx").unwrap(),
|
||||||
|
@ -252,27 +250,14 @@ pub fn run_tests(config: &Config) {
|
||||||
if let DebugInfoGdb = config.mode {
|
if let DebugInfoGdb = config.mode {
|
||||||
println!("{} debug-info test uses tcp 5039 port.\
|
println!("{} debug-info test uses tcp 5039 port.\
|
||||||
please reserve it", config.target);
|
please reserve it", config.target);
|
||||||
}
|
|
||||||
|
|
||||||
// android debug-info test uses remote debugger
|
|
||||||
// so, we test 1 thread at once.
|
|
||||||
// also trying to isolate problems with adb_run_wrapper.sh ilooping
|
|
||||||
match config.mode {
|
|
||||||
// These tests don't actually run code or don't run for android, so
|
|
||||||
// we don't need to limit ourselves there
|
|
||||||
Mode::Ui |
|
|
||||||
Mode::CompileFail |
|
|
||||||
Mode::ParseFail |
|
|
||||||
Mode::RunMake |
|
|
||||||
Mode::Codegen |
|
|
||||||
Mode::CodegenUnits |
|
|
||||||
Mode::Pretty |
|
|
||||||
Mode::Rustdoc => {}
|
|
||||||
|
|
||||||
_ => {
|
|
||||||
env::set_var("RUST_TEST_THREADS", "1");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// android debug-info test uses remote debugger so, we test 1 thread
|
||||||
|
// at once as they're all sharing the same TCP port to communicate
|
||||||
|
// over.
|
||||||
|
//
|
||||||
|
// we should figure out how to lift this restriction! (run them all
|
||||||
|
// on different ports allocated dynamically).
|
||||||
|
env::set_var("RUST_TEST_THREADS", "1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,9 +281,10 @@ pub fn run_tests(config: &Config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugInfoGdb => {
|
DebugInfoGdb => {
|
||||||
if config.qemu_test_client.is_some() {
|
if config.remote_test_client.is_some() &&
|
||||||
|
!config.target.contains("android"){
|
||||||
println!("WARNING: debuginfo tests are not available when \
|
println!("WARNING: debuginfo tests are not available when \
|
||||||
testing with QEMU");
|
testing with remote");
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ use util::logv;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fmt;
|
|
||||||
use std::fs::{self, File, create_dir_all};
|
use std::fs::{self, File, create_dir_all};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::io::{self, BufReader};
|
use std::io::{self, BufReader};
|
||||||
|
@ -468,7 +467,9 @@ actual:\n\
|
||||||
|
|
||||||
let debugger_run_result;
|
let debugger_run_result;
|
||||||
match &*self.config.target {
|
match &*self.config.target {
|
||||||
"arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" => {
|
"arm-linux-androideabi" |
|
||||||
|
"armv7-linux-androideabi" |
|
||||||
|
"aarch64-linux-android" => {
|
||||||
|
|
||||||
cmds = cmds.replace("run", "continue");
|
cmds = cmds.replace("run", "continue");
|
||||||
|
|
||||||
|
@ -533,6 +534,7 @@ actual:\n\
|
||||||
exe_file.file_name().unwrap().to_str()
|
exe_file.file_name().unwrap().to_str()
|
||||||
.unwrap());
|
.unwrap());
|
||||||
|
|
||||||
|
debug!("adb arg: {}", adb_arg);
|
||||||
let mut process = procsrv::run_background("",
|
let mut process = procsrv::run_background("",
|
||||||
&self.config.adb_path
|
&self.config.adb_path
|
||||||
,
|
,
|
||||||
|
@ -589,7 +591,7 @@ actual:\n\
|
||||||
};
|
};
|
||||||
|
|
||||||
debugger_run_result = ProcRes {
|
debugger_run_result = ProcRes {
|
||||||
status: Status::Normal(status),
|
status: status,
|
||||||
stdout: out,
|
stdout: out,
|
||||||
stderr: err,
|
stderr: err,
|
||||||
cmdline: cmdline
|
cmdline: cmdline
|
||||||
|
@ -840,7 +842,7 @@ actual:\n\
|
||||||
|
|
||||||
self.dump_output(&out, &err);
|
self.dump_output(&out, &err);
|
||||||
ProcRes {
|
ProcRes {
|
||||||
status: Status::Normal(status),
|
status: status,
|
||||||
stdout: out,
|
stdout: out,
|
||||||
stderr: err,
|
stderr: err,
|
||||||
cmdline: format!("{:?}", cmd)
|
cmdline: format!("{:?}", cmd)
|
||||||
|
@ -1191,25 +1193,20 @@ actual:\n\
|
||||||
let env = self.props.exec_env.clone();
|
let env = self.props.exec_env.clone();
|
||||||
|
|
||||||
match &*self.config.target {
|
match &*self.config.target {
|
||||||
|
|
||||||
"arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" => {
|
|
||||||
self._arm_exec_compiled_test(env)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is pretty similar to below, we're transforming:
|
// This is pretty similar to below, we're transforming:
|
||||||
//
|
//
|
||||||
// program arg1 arg2
|
// program arg1 arg2
|
||||||
//
|
//
|
||||||
// into
|
// into
|
||||||
//
|
//
|
||||||
// qemu-test-client run program:support-lib.so arg1 arg2
|
// remote-test-client run program:support-lib.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`. It will then execute the program on the
|
||||||
// emulator with the arguments specified (in the environment we give
|
// emulator with the arguments specified (in the environment we give
|
||||||
// the process) and then report back the same result.
|
// the process) and then report back the same result.
|
||||||
_ if self.config.qemu_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 mut args = self.make_run_args();
|
let mut args = self.make_run_args();
|
||||||
let mut program = args.prog.clone();
|
let mut program = args.prog.clone();
|
||||||
|
@ -1225,7 +1222,7 @@ actual:\n\
|
||||||
}
|
}
|
||||||
args.args.insert(0, program);
|
args.args.insert(0, program);
|
||||||
args.args.insert(0, "run".to_string());
|
args.args.insert(0, "run".to_string());
|
||||||
args.prog = self.config.qemu_test_client.clone().unwrap()
|
args.prog = self.config.remote_test_client.clone().unwrap()
|
||||||
.into_os_string().into_string().unwrap();
|
.into_os_string().into_string().unwrap();
|
||||||
self.compose_and_run(args,
|
self.compose_and_run(args,
|
||||||
env,
|
env,
|
||||||
|
@ -1327,13 +1324,6 @@ actual:\n\
|
||||||
aux_testpaths.file.display()),
|
aux_testpaths.file.display()),
|
||||||
&auxres);
|
&auxres);
|
||||||
}
|
}
|
||||||
|
|
||||||
match &*self.config.target {
|
|
||||||
"arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" => {
|
|
||||||
self._arm_push_aux_shared_library();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.compose_and_run(args,
|
self.compose_and_run(args,
|
||||||
|
@ -1567,7 +1557,7 @@ actual:\n\
|
||||||
input).expect(&format!("failed to exec `{}`", prog));
|
input).expect(&format!("failed to exec `{}`", prog));
|
||||||
self.dump_output(&out, &err);
|
self.dump_output(&out, &err);
|
||||||
return ProcRes {
|
return ProcRes {
|
||||||
status: Status::Normal(status),
|
status: status,
|
||||||
stdout: out,
|
stdout: out,
|
||||||
stderr: err,
|
stderr: err,
|
||||||
cmdline: cmdline,
|
cmdline: cmdline,
|
||||||
|
@ -1701,157 +1691,6 @@ actual:\n\
|
||||||
println!("---------------------------------------------------");
|
println!("---------------------------------------------------");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _arm_exec_compiled_test(&self, env: Vec<(String, String)>) -> ProcRes {
|
|
||||||
let args = self.make_run_args();
|
|
||||||
let cmdline = self.make_cmdline("", &args.prog, &args.args);
|
|
||||||
|
|
||||||
// get bare program string
|
|
||||||
let mut tvec: Vec<String> = args.prog
|
|
||||||
.split('/')
|
|
||||||
.map(str::to_owned)
|
|
||||||
.collect();
|
|
||||||
let prog_short = tvec.pop().unwrap();
|
|
||||||
|
|
||||||
// copy to target
|
|
||||||
let copy_result = procsrv::run("",
|
|
||||||
&self.config.adb_path,
|
|
||||||
None,
|
|
||||||
&[
|
|
||||||
"push".to_owned(),
|
|
||||||
args.prog.clone(),
|
|
||||||
self.config.adb_test_dir.clone()
|
|
||||||
],
|
|
||||||
vec![("".to_owned(), "".to_owned())],
|
|
||||||
Some("".to_owned()))
|
|
||||||
.expect(&format!("failed to exec `{}`", self.config.adb_path));
|
|
||||||
|
|
||||||
if self.config.verbose {
|
|
||||||
println!("push ({}) {} {} {}",
|
|
||||||
self.config.target,
|
|
||||||
args.prog,
|
|
||||||
copy_result.out,
|
|
||||||
copy_result.err);
|
|
||||||
}
|
|
||||||
|
|
||||||
logv(self.config, format!("executing ({}) {}", self.config.target, cmdline));
|
|
||||||
|
|
||||||
let mut runargs = Vec::new();
|
|
||||||
|
|
||||||
// run test via adb_run_wrapper
|
|
||||||
runargs.push("shell".to_owned());
|
|
||||||
for (key, val) in env {
|
|
||||||
runargs.push(format!("{}={}", key, val));
|
|
||||||
}
|
|
||||||
runargs.push(format!("{}/../adb_run_wrapper.sh", self.config.adb_test_dir));
|
|
||||||
runargs.push(format!("{}", self.config.adb_test_dir));
|
|
||||||
runargs.push(format!("{}", prog_short));
|
|
||||||
|
|
||||||
for tv in &args.args {
|
|
||||||
runargs.push(tv.to_owned());
|
|
||||||
}
|
|
||||||
procsrv::run("",
|
|
||||||
&self.config.adb_path,
|
|
||||||
None,
|
|
||||||
&runargs,
|
|
||||||
vec![("".to_owned(), "".to_owned())], Some("".to_owned()))
|
|
||||||
.expect(&format!("failed to exec `{}`", self.config.adb_path));
|
|
||||||
|
|
||||||
// get exitcode of result
|
|
||||||
runargs = Vec::new();
|
|
||||||
runargs.push("shell".to_owned());
|
|
||||||
runargs.push("cat".to_owned());
|
|
||||||
runargs.push(format!("{}/{}.exitcode", self.config.adb_test_dir, prog_short));
|
|
||||||
|
|
||||||
let procsrv::Result{ out: exitcode_out, err: _, status: _ } =
|
|
||||||
procsrv::run("",
|
|
||||||
&self.config.adb_path,
|
|
||||||
None,
|
|
||||||
&runargs,
|
|
||||||
vec![("".to_owned(), "".to_owned())],
|
|
||||||
Some("".to_owned()))
|
|
||||||
.expect(&format!("failed to exec `{}`", self.config.adb_path));
|
|
||||||
|
|
||||||
let mut exitcode: i32 = 0;
|
|
||||||
for c in exitcode_out.chars() {
|
|
||||||
if !c.is_numeric() { break; }
|
|
||||||
exitcode = exitcode * 10 + match c {
|
|
||||||
'0' ... '9' => c as i32 - ('0' as i32),
|
|
||||||
_ => 101,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get stdout of result
|
|
||||||
runargs = Vec::new();
|
|
||||||
runargs.push("shell".to_owned());
|
|
||||||
runargs.push("cat".to_owned());
|
|
||||||
runargs.push(format!("{}/{}.stdout", self.config.adb_test_dir, prog_short));
|
|
||||||
|
|
||||||
let procsrv::Result{ out: stdout_out, err: _, status: _ } =
|
|
||||||
procsrv::run("",
|
|
||||||
&self.config.adb_path,
|
|
||||||
None,
|
|
||||||
&runargs,
|
|
||||||
vec![("".to_owned(), "".to_owned())],
|
|
||||||
Some("".to_owned()))
|
|
||||||
.expect(&format!("failed to exec `{}`", self.config.adb_path));
|
|
||||||
|
|
||||||
// get stderr of result
|
|
||||||
runargs = Vec::new();
|
|
||||||
runargs.push("shell".to_owned());
|
|
||||||
runargs.push("cat".to_owned());
|
|
||||||
runargs.push(format!("{}/{}.stderr", self.config.adb_test_dir, prog_short));
|
|
||||||
|
|
||||||
let procsrv::Result{ out: stderr_out, err: _, status: _ } =
|
|
||||||
procsrv::run("",
|
|
||||||
&self.config.adb_path,
|
|
||||||
None,
|
|
||||||
&runargs,
|
|
||||||
vec![("".to_owned(), "".to_owned())],
|
|
||||||
Some("".to_owned()))
|
|
||||||
.expect(&format!("failed to exec `{}`", self.config.adb_path));
|
|
||||||
|
|
||||||
self.dump_output(&stdout_out, &stderr_out);
|
|
||||||
|
|
||||||
ProcRes {
|
|
||||||
status: Status::Parsed(exitcode),
|
|
||||||
stdout: stdout_out,
|
|
||||||
stderr: stderr_out,
|
|
||||||
cmdline: cmdline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn _arm_push_aux_shared_library(&self) {
|
|
||||||
let tdir = self.aux_output_dir_name();
|
|
||||||
|
|
||||||
let dirs = fs::read_dir(&tdir).unwrap();
|
|
||||||
for file in dirs {
|
|
||||||
let file = file.unwrap().path();
|
|
||||||
if file.extension().and_then(|s| s.to_str()) == Some("so") {
|
|
||||||
// FIXME (#9639): This needs to handle non-utf8 paths
|
|
||||||
let copy_result = procsrv::run("",
|
|
||||||
&self.config.adb_path,
|
|
||||||
None,
|
|
||||||
&[
|
|
||||||
"push".to_owned(),
|
|
||||||
file.to_str()
|
|
||||||
.unwrap()
|
|
||||||
.to_owned(),
|
|
||||||
self.config.adb_test_dir.to_owned(),
|
|
||||||
],
|
|
||||||
vec![("".to_owned(),
|
|
||||||
"".to_owned())],
|
|
||||||
Some("".to_owned()))
|
|
||||||
.expect(&format!("failed to exec `{}`", self.config.adb_path));
|
|
||||||
|
|
||||||
if self.config.verbose {
|
|
||||||
println!("push ({}) {:?} {} {}",
|
|
||||||
self.config.target, file.display(),
|
|
||||||
copy_result.out, copy_result.err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// codegen tests (using FileCheck)
|
// codegen tests (using FileCheck)
|
||||||
|
|
||||||
fn compile_test_and_save_ir(&self) -> ProcRes {
|
fn compile_test_and_save_ir(&self) -> ProcRes {
|
||||||
|
@ -2350,7 +2189,7 @@ actual:\n\
|
||||||
let output = cmd.output().expect("failed to spawn `make`");
|
let output = cmd.output().expect("failed to spawn `make`");
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
let res = ProcRes {
|
let res = ProcRes {
|
||||||
status: Status::Normal(output.status),
|
status: output.status,
|
||||||
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
|
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
|
||||||
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
|
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
|
||||||
cmdline: format!("{:?}", cmd),
|
cmdline: format!("{:?}", cmd),
|
||||||
|
@ -2597,17 +2436,12 @@ struct ProcArgs {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ProcRes {
|
pub struct ProcRes {
|
||||||
status: Status,
|
status: ExitStatus,
|
||||||
stdout: String,
|
stdout: String,
|
||||||
stderr: String,
|
stderr: String,
|
||||||
cmdline: String,
|
cmdline: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Status {
|
|
||||||
Parsed(i32),
|
|
||||||
Normal(ExitStatus),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProcRes {
|
impl ProcRes {
|
||||||
pub fn fatal(&self, err: Option<&str>) -> ! {
|
pub fn fatal(&self, err: Option<&str>) -> ! {
|
||||||
if let Some(e) = err {
|
if let Some(e) = err {
|
||||||
|
@ -2631,31 +2465,6 @@ impl ProcRes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Status {
|
|
||||||
fn code(&self) -> Option<i32> {
|
|
||||||
match *self {
|
|
||||||
Status::Parsed(i) => Some(i),
|
|
||||||
Status::Normal(ref e) => e.code(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn success(&self) -> bool {
|
|
||||||
match *self {
|
|
||||||
Status::Parsed(i) => i == 0,
|
|
||||||
Status::Normal(ref e) => e.success(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Status {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match *self {
|
|
||||||
Status::Parsed(i) => write!(f, "exit code: {}", i),
|
|
||||||
Status::Normal(ref e) => e.fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum TargetLocation {
|
enum TargetLocation {
|
||||||
ThisFile(PathBuf),
|
ThisFile(PathBuf),
|
||||||
ThisDirectory(PathBuf),
|
ThisDirectory(PathBuf),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "qemu-test-client"
|
name = "remote-test-client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["The Rust Project Developers"]
|
authors = ["The Rust Project Developers"]
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
/// This is a small client program intended to pair with `qemu-test-server` in
|
/// This is a small client program intended to pair with `remote-test-server` in
|
||||||
/// this repository. This client connects to the server over TCP and is used to
|
/// this repository. This client connects to the server over TCP and is used to
|
||||||
/// push artifacts and run tests on the server instead of locally.
|
/// push artifacts and run tests on the server instead of locally.
|
||||||
///
|
///
|
||||||
|
@ -16,11 +16,11 @@
|
||||||
/// well.
|
/// well.
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::File;
|
use std::fs::{self, File};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::io::{self, BufWriter};
|
use std::io::{self, BufWriter};
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
@ -37,8 +37,10 @@ fn main() {
|
||||||
|
|
||||||
match &args.next().unwrap()[..] {
|
match &args.next().unwrap()[..] {
|
||||||
"spawn-emulator" => {
|
"spawn-emulator" => {
|
||||||
spawn_emulator(Path::new(&args.next().unwrap()),
|
spawn_emulator(&args.next().unwrap(),
|
||||||
Path::new(&args.next().unwrap()))
|
Path::new(&args.next().unwrap()),
|
||||||
|
Path::new(&args.next().unwrap()),
|
||||||
|
args.next().map(|s| s.into()))
|
||||||
}
|
}
|
||||||
"push" => {
|
"push" => {
|
||||||
push(Path::new(&args.next().unwrap()))
|
push(Path::new(&args.next().unwrap()))
|
||||||
|
@ -50,11 +52,74 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_emulator(rootfs: &Path, tmpdir: &Path) {
|
fn spawn_emulator(target: &str,
|
||||||
|
server: &Path,
|
||||||
|
tmpdir: &Path,
|
||||||
|
rootfs: Option<PathBuf>) {
|
||||||
|
if target.contains("android") {
|
||||||
|
start_android_emulator(server);
|
||||||
|
} else {
|
||||||
|
let rootfs = rootfs.as_ref().expect("need rootfs on non-android");
|
||||||
|
start_qemu_emulator(rootfs, server, tmpdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the emulator to come online
|
||||||
|
loop {
|
||||||
|
let dur = Duration::from_millis(100);
|
||||||
|
if let Ok(mut client) = TcpStream::connect("127.0.0.1:12345") {
|
||||||
|
t!(client.set_read_timeout(Some(dur)));
|
||||||
|
t!(client.set_write_timeout(Some(dur)));
|
||||||
|
if client.write_all(b"ping").is_ok() {
|
||||||
|
let mut b = [0; 4];
|
||||||
|
if client.read_exact(&mut b).is_ok() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thread::sleep(dur);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_android_emulator(server: &Path) {
|
||||||
|
println!("waiting for device to come online");
|
||||||
|
let status = Command::new("adb")
|
||||||
|
.arg("wait-for-device")
|
||||||
|
.status()
|
||||||
|
.unwrap();
|
||||||
|
assert!(status.success());
|
||||||
|
|
||||||
|
println!("pushing server");
|
||||||
|
let status = Command::new("adb")
|
||||||
|
.arg("push")
|
||||||
|
.arg(server)
|
||||||
|
.arg("/data/tmp/testd")
|
||||||
|
.status()
|
||||||
|
.unwrap();
|
||||||
|
assert!(status.success());
|
||||||
|
|
||||||
|
println!("forwarding tcp");
|
||||||
|
let status = Command::new("adb")
|
||||||
|
.arg("forward")
|
||||||
|
.arg("tcp:12345")
|
||||||
|
.arg("tcp:12345")
|
||||||
|
.status()
|
||||||
|
.unwrap();
|
||||||
|
assert!(status.success());
|
||||||
|
|
||||||
|
println!("executing server");
|
||||||
|
Command::new("adb")
|
||||||
|
.arg("shell")
|
||||||
|
.arg("/data/tmp/testd")
|
||||||
|
.spawn()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_qemu_emulator(rootfs: &Path, server: &Path, tmpdir: &Path) {
|
||||||
// Generate a new rootfs image now that we've updated the test server
|
// Generate a new rootfs image now that we've updated the test server
|
||||||
// executable. This is the equivalent of:
|
// executable. This is the equivalent of:
|
||||||
//
|
//
|
||||||
// find $rootfs -print 0 | cpio --null -o --format=newc > rootfs.img
|
// find $rootfs -print 0 | cpio --null -o --format=newc > rootfs.img
|
||||||
|
t!(fs::copy(server, rootfs.join("testd")));
|
||||||
let rootfs_img = tmpdir.join("rootfs.img");
|
let rootfs_img = tmpdir.join("rootfs.img");
|
||||||
let mut cmd = Command::new("cpio");
|
let mut cmd = Command::new("cpio");
|
||||||
cmd.arg("--null")
|
cmd.arg("--null")
|
||||||
|
@ -83,22 +148,6 @@ fn spawn_emulator(rootfs: &Path, tmpdir: &Path) {
|
||||||
.arg("-redir").arg("tcp:12345::12345");
|
.arg("-redir").arg("tcp:12345::12345");
|
||||||
t!(cmd.spawn());
|
t!(cmd.spawn());
|
||||||
|
|
||||||
// Wait for the emulator to come online
|
|
||||||
loop {
|
|
||||||
let dur = Duration::from_millis(100);
|
|
||||||
if let Ok(mut client) = TcpStream::connect("127.0.0.1:12345") {
|
|
||||||
t!(client.set_read_timeout(Some(dur)));
|
|
||||||
t!(client.set_write_timeout(Some(dur)));
|
|
||||||
if client.write_all(b"ping").is_ok() {
|
|
||||||
let mut b = [0; 4];
|
|
||||||
if client.read_exact(&mut b).is_ok() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
thread::sleep(dur);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_files(w: &mut Write, root: &Path, cur: &Path) {
|
fn add_files(w: &mut Write, root: &Path, cur: &Path) {
|
||||||
for entry in t!(cur.read_dir()) {
|
for entry in t!(cur.read_dir()) {
|
||||||
let entry = t!(entry);
|
let entry = t!(entry);
|
||||||
|
@ -116,11 +165,15 @@ fn push(path: &Path) {
|
||||||
let client = t!(TcpStream::connect("127.0.0.1:12345"));
|
let client = t!(TcpStream::connect("127.0.0.1:12345"));
|
||||||
let mut client = BufWriter::new(client);
|
let mut client = BufWriter::new(client);
|
||||||
t!(client.write_all(b"push"));
|
t!(client.write_all(b"push"));
|
||||||
t!(client.write_all(path.file_name().unwrap().to_str().unwrap().as_bytes()));
|
send(path, &mut client);
|
||||||
t!(client.write_all(&[0]));
|
|
||||||
let mut file = t!(File::open(path));
|
|
||||||
t!(io::copy(&mut file, &mut client));
|
|
||||||
t!(client.flush());
|
t!(client.flush());
|
||||||
|
|
||||||
|
// Wait for an acknowledgement that all the data was received. No idea
|
||||||
|
// why this is necessary, seems like it shouldn't be!
|
||||||
|
let mut client = client.into_inner().unwrap();
|
||||||
|
let mut buf = [0; 4];
|
||||||
|
t!(client.read_exact(&mut buf));
|
||||||
|
assert_eq!(&buf, b"ack ");
|
||||||
println!("done pushing {:?}", path);
|
println!("done pushing {:?}", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,13 +190,20 @@ fn run(files: String, args: Vec<String>) {
|
||||||
t!(client.write_all(&[0]));
|
t!(client.write_all(&[0]));
|
||||||
|
|
||||||
// Send over env vars
|
// Send over env vars
|
||||||
|
//
|
||||||
|
// Don't send over *everything* though as some env vars are set by and used
|
||||||
|
// by the client.
|
||||||
for (k, v) in env::vars() {
|
for (k, v) in env::vars() {
|
||||||
if k != "PATH" && k != "LD_LIBRARY_PATH" {
|
match &k[..] {
|
||||||
t!(client.write_all(k.as_bytes()));
|
"PATH" |
|
||||||
t!(client.write_all(&[0]));
|
"LD_LIBRARY_PATH" |
|
||||||
t!(client.write_all(v.as_bytes()));
|
"PWD" => continue,
|
||||||
t!(client.write_all(&[0]));
|
_ => {}
|
||||||
}
|
}
|
||||||
|
t!(client.write_all(k.as_bytes()));
|
||||||
|
t!(client.write_all(&[0]));
|
||||||
|
t!(client.write_all(v.as_bytes()));
|
||||||
|
t!(client.write_all(&[0]));
|
||||||
}
|
}
|
||||||
t!(client.write_all(&[0]));
|
t!(client.write_all(&[0]));
|
||||||
|
|
||||||
|
@ -151,8 +211,6 @@ fn run(files: String, args: Vec<String>) {
|
||||||
let mut files = files.split(':');
|
let mut files = files.split(':');
|
||||||
let exe = files.next().unwrap();
|
let exe = files.next().unwrap();
|
||||||
for file in files.map(Path::new) {
|
for file in files.map(Path::new) {
|
||||||
t!(client.write_all(file.file_name().unwrap().to_str().unwrap().as_bytes()));
|
|
||||||
t!(client.write_all(&[0]));
|
|
||||||
send(&file, &mut client);
|
send(&file, &mut client);
|
||||||
}
|
}
|
||||||
t!(client.write_all(&[0]));
|
t!(client.write_all(&[0]));
|
||||||
|
@ -209,6 +267,8 @@ fn run(files: String, args: Vec<String>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send(path: &Path, dst: &mut Write) {
|
fn send(path: &Path, dst: &mut Write) {
|
||||||
|
t!(dst.write_all(path.file_name().unwrap().to_str().unwrap().as_bytes()));
|
||||||
|
t!(dst.write_all(&[0]));
|
||||||
let mut file = t!(File::open(&path));
|
let mut file = t!(File::open(&path));
|
||||||
let amt = t!(file.metadata()).len();
|
let amt = t!(file.metadata()).len();
|
||||||
t!(dst.write_all(&[
|
t!(dst.write_all(&[
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "qemu-test-server"
|
name = "remote-test-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["The Rust Project Developers"]
|
authors = ["The Rust Project Developers"]
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
/// This is a small server which is intended to run inside of an emulator. This
|
/// This is a small server which is intended to run inside of an emulator. This
|
||||||
/// server pairs with the `qemu-test-client` program in this repository. The
|
/// server pairs with the `remote-test-client` program in this repository. The
|
||||||
/// `qemu-test-client` connects to this server over a TCP socket and performs
|
/// `remote-test-client` connects to this server over a TCP socket and performs
|
||||||
/// work such as:
|
/// work such as:
|
||||||
///
|
///
|
||||||
/// 1. Pushing shared libraries to the server
|
/// 1. Pushing shared libraries to the server
|
||||||
|
@ -20,17 +20,18 @@
|
||||||
/// themselves having support libraries. All data over the TCP sockets is in a
|
/// themselves having support libraries. All data over the TCP sockets is in a
|
||||||
/// basically custom format suiting our needs.
|
/// basically custom format suiting our needs.
|
||||||
|
|
||||||
|
use std::cmp;
|
||||||
use std::fs::{self, File, Permissions};
|
use std::fs::{self, File, Permissions};
|
||||||
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::os::unix::prelude::*;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::path::{Path, PathBuf};
|
||||||
use std::path::Path;
|
use std::process::{Command, Stdio};
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
|
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::process::{Command, Stdio};
|
|
||||||
|
|
||||||
macro_rules! t {
|
macro_rules! t {
|
||||||
($e:expr) => (match $e {
|
($e:expr) => (match $e {
|
||||||
|
@ -43,10 +44,14 @@ static TEST: AtomicUsize = ATOMIC_USIZE_INIT;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("starting test server");
|
println!("starting test server");
|
||||||
let listener = t!(TcpListener::bind("10.0.2.15:12345"));
|
let (listener, work) = if cfg!(target_os = "android") {
|
||||||
|
(t!(TcpListener::bind("0.0.0.0:12345")), "/data/tmp/work")
|
||||||
|
} else {
|
||||||
|
(t!(TcpListener::bind("10.0.2.15:12345")), "/tmp/work")
|
||||||
|
};
|
||||||
println!("listening!");
|
println!("listening!");
|
||||||
|
|
||||||
let work = Path::new("/tmp/work");
|
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(()));
|
||||||
|
@ -54,7 +59,9 @@ fn main() {
|
||||||
for socket in listener.incoming() {
|
for socket in listener.incoming() {
|
||||||
let mut socket = t!(socket);
|
let mut socket = t!(socket);
|
||||||
let mut buf = [0; 4];
|
let mut buf = [0; 4];
|
||||||
t!(socket.read_exact(&mut buf));
|
if socket.read_exact(&mut buf).is_err() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
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" {
|
||||||
|
@ -70,14 +77,10 @@ fn main() {
|
||||||
|
|
||||||
fn handle_push(socket: TcpStream, work: &Path) {
|
fn handle_push(socket: TcpStream, work: &Path) {
|
||||||
let mut reader = BufReader::new(socket);
|
let mut reader = BufReader::new(socket);
|
||||||
let mut filename = Vec::new();
|
recv(&work, &mut reader);
|
||||||
t!(reader.read_until(0, &mut filename));
|
|
||||||
filename.pop(); // chop off the 0
|
|
||||||
let filename = t!(str::from_utf8(&filename));
|
|
||||||
|
|
||||||
let path = work.join(filename);
|
let mut socket = reader.into_inner();
|
||||||
t!(io::copy(&mut reader, &mut t!(File::create(&path))));
|
t!(socket.write_all(b"ack "));
|
||||||
t!(fs::set_permissions(&path, Permissions::from_mode(0o755)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RemoveOnDrop<'a> {
|
struct RemoveOnDrop<'a> {
|
||||||
|
@ -98,19 +101,19 @@ fn handle_run(socket: TcpStream, work: &Path, lock: &Mutex<()>) {
|
||||||
// space.
|
// space.
|
||||||
let n = TEST.fetch_add(1, Ordering::SeqCst);
|
let n = TEST.fetch_add(1, Ordering::SeqCst);
|
||||||
let path = work.join(format!("test{}", n));
|
let path = work.join(format!("test{}", n));
|
||||||
let exe = path.join("exe");
|
|
||||||
t!(fs::create_dir(&path));
|
t!(fs::create_dir(&path));
|
||||||
let _a = RemoveOnDrop { inner: &path };
|
let _a = RemoveOnDrop { inner: &path };
|
||||||
|
|
||||||
// First up we'll get a list of arguments delimited with 0 bytes. An empty
|
// First up we'll get a list of arguments delimited with 0 bytes. An empty
|
||||||
// argument means that we're done.
|
// argument means that we're done.
|
||||||
let mut cmd = Command::new(&exe);
|
let mut args = Vec::new();
|
||||||
while t!(reader.read_until(0, &mut arg)) > 1 {
|
while t!(reader.read_until(0, &mut arg)) > 1 {
|
||||||
cmd.arg(t!(str::from_utf8(&arg[..arg.len() - 1])));
|
args.push(t!(str::from_utf8(&arg[..arg.len() - 1])).to_string());
|
||||||
arg.truncate(0);
|
arg.truncate(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next we'll get a bunch of env vars in pairs delimited by 0s as well
|
// Next we'll get a bunch of env vars in pairs delimited by 0s as well
|
||||||
|
let mut env = Vec::new();
|
||||||
arg.truncate(0);
|
arg.truncate(0);
|
||||||
while t!(reader.read_until(0, &mut arg)) > 1 {
|
while t!(reader.read_until(0, &mut arg)) > 1 {
|
||||||
let key_len = arg.len() - 1;
|
let key_len = arg.len() - 1;
|
||||||
|
@ -118,9 +121,9 @@ fn handle_run(socket: TcpStream, work: &Path, lock: &Mutex<()>) {
|
||||||
{
|
{
|
||||||
let key = &arg[..key_len];
|
let key = &arg[..key_len];
|
||||||
let val = &arg[key_len + 1..][..val_len];
|
let val = &arg[key_len + 1..][..val_len];
|
||||||
let key = t!(str::from_utf8(key));
|
let key = t!(str::from_utf8(key)).to_string();
|
||||||
let val = t!(str::from_utf8(val));
|
let val = t!(str::from_utf8(val)).to_string();
|
||||||
cmd.env(key, val);
|
env.push((key, val));
|
||||||
}
|
}
|
||||||
arg.truncate(0);
|
arg.truncate(0);
|
||||||
}
|
}
|
||||||
|
@ -148,23 +151,23 @@ fn handle_run(socket: TcpStream, work: &Path, lock: &Mutex<()>) {
|
||||||
let lock = lock.lock();
|
let lock = lock.lock();
|
||||||
|
|
||||||
// Next there's a list of dynamic libraries preceded by their filenames.
|
// Next there's a list of dynamic libraries preceded by their filenames.
|
||||||
arg.truncate(0);
|
while t!(reader.fill_buf())[0] != 0 {
|
||||||
while t!(reader.read_until(0, &mut arg)) > 1 {
|
recv(&path, &mut reader);
|
||||||
let dst = path.join(t!(str::from_utf8(&arg[..arg.len() - 1])));
|
|
||||||
let amt = read_u32(&mut reader) as u64;
|
|
||||||
t!(io::copy(&mut reader.by_ref().take(amt),
|
|
||||||
&mut t!(File::create(&dst))));
|
|
||||||
t!(fs::set_permissions(&dst, Permissions::from_mode(0o755)));
|
|
||||||
arg.truncate(0);
|
|
||||||
}
|
}
|
||||||
|
assert_eq!(t!(reader.read(&mut [0])), 1);
|
||||||
|
|
||||||
// Finally we'll get the binary. The other end will tell us how big the
|
// Finally we'll get the binary. The other end will tell us how big the
|
||||||
// binary is and then we'll download it all to the exe path we calculated
|
// binary is and then we'll download it all to the exe path we calculated
|
||||||
// earlier.
|
// earlier.
|
||||||
let amt = read_u32(&mut reader) as u64;
|
let exe = recv(&path, &mut reader);
|
||||||
t!(io::copy(&mut reader.by_ref().take(amt),
|
|
||||||
&mut t!(File::create(&exe))));
|
let mut cmd = Command::new(&exe);
|
||||||
t!(fs::set_permissions(&exe, Permissions::from_mode(0o755)));
|
for arg in args {
|
||||||
|
cmd.arg(arg);
|
||||||
|
}
|
||||||
|
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
|
||||||
|
@ -202,6 +205,28 @@ fn handle_run(socket: TcpStream, work: &Path, lock: &Mutex<()>) {
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn recv<B: BufRead>(dir: &Path, io: &mut B) -> PathBuf {
|
||||||
|
let mut filename = Vec::new();
|
||||||
|
t!(io.read_until(0, &mut filename));
|
||||||
|
|
||||||
|
// We've got some tests with *really* long names. We try to name the test
|
||||||
|
// executable the same on the target as it is on the host to aid with
|
||||||
|
// debugging, but the targets we're emulating are often more restrictive
|
||||||
|
// than the hosts as well.
|
||||||
|
//
|
||||||
|
// To ensure we can run a maximum number of tests without modifications we
|
||||||
|
// just arbitrarily truncate the filename to 50 bytes. That should
|
||||||
|
// hopefully allow us to still identify what's running while staying under
|
||||||
|
// the filesystem limits.
|
||||||
|
let len = cmp::min(filename.len() - 1, 50);
|
||||||
|
let dst = dir.join(t!(str::from_utf8(&filename[..len])));
|
||||||
|
let amt = read_u32(io) as u64;
|
||||||
|
t!(io::copy(&mut io.take(amt),
|
||||||
|
&mut t!(File::create(&dst))));
|
||||||
|
t!(fs::set_permissions(&dst, Permissions::from_mode(0o755)));
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
fn my_copy(src: &mut Read, which: u8, dst: &Mutex<Write>) {
|
fn my_copy(src: &mut Read, which: u8, dst: &Mutex<Write>) {
|
||||||
let mut b = [0; 1024];
|
let mut b = [0; 1024];
|
||||||
loop {
|
loop {
|
Loading…
Add table
Add a link
Reference in a new issue