1 // Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 /// This is a small client program intended to pair with `remote-test-server` in
12 /// this repository. This client connects to the server over TCP and is used to
13 /// push artifacts and run tests on the server instead of locally.
15 /// Here is also where we bake in the support to spawn the QEMU emulator as
19 use std::fs::{self, File};
20 use std::io::prelude::*;
21 use std::io::{self, BufWriter};
22 use std::net::TcpStream;
23 use std::path::{Path, PathBuf};
24 use std::process::{Command, Stdio};
26 use std::time::Duration;
28 const REMOTE_ADDR_ENV: &'static str = "TEST_DEVICE_ADDR";
31 ($e:expr) => (match $e {
33 Err(e) => panic!("{} failed with {}", stringify!($e), e),
38 let mut args = env::args().skip(1);
40 match &args.next().unwrap()[..] {
42 spawn_emulator(&args.next().unwrap(),
43 Path::new(&args.next().unwrap()),
44 Path::new(&args.next().unwrap()),
45 args.next().map(|s| s.into()))
48 push(Path::new(&args.next().unwrap()))
51 run(args.next().unwrap(), args.collect())
53 cmd => panic!("unknown command: {}", cmd),
57 fn spawn_emulator(target: &str,
60 rootfs: Option<PathBuf>) {
61 let device_address = env::var(REMOTE_ADDR_ENV).unwrap_or("127.0.0.1:12345".to_string());
63 if env::var(REMOTE_ADDR_ENV).is_ok() {
64 println!("Connecting to remote device {} ...", device_address);
65 } else if target.contains("android") {
66 start_android_emulator(server);
68 let rootfs = rootfs.as_ref().expect("need rootfs on non-android");
69 start_qemu_emulator(target, rootfs, server, tmpdir);
72 // Wait for the emulator to come online
74 let dur = Duration::from_millis(100);
75 if let Ok(mut client) = TcpStream::connect(&device_address) {
76 t!(client.set_read_timeout(Some(dur)));
77 t!(client.set_write_timeout(Some(dur)));
78 if client.write_all(b"ping").is_ok() {
80 if client.read_exact(&mut b).is_ok() {
89 fn start_android_emulator(server: &Path) {
90 println!("waiting for device to come online");
91 let status = Command::new("adb")
92 .arg("wait-for-device")
95 assert!(status.success());
97 println!("pushing server");
98 let status = Command::new("adb")
101 .arg("/data/tmp/testd")
104 assert!(status.success());
106 println!("forwarding tcp");
107 let status = Command::new("adb")
113 assert!(status.success());
115 println!("executing server");
118 .arg("/data/tmp/testd")
123 fn start_qemu_emulator(target: &str,
127 // Generate a new rootfs image now that we've updated the test server
128 // executable. This is the equivalent of:
130 // find $rootfs -print 0 | cpio --null -o --format=newc > rootfs.img
131 t!(fs::copy(server, rootfs.join("testd")));
132 let rootfs_img = tmpdir.join("rootfs.img");
133 let mut cmd = Command::new("cpio");
136 .arg("--format=newc")
137 .stdin(Stdio::piped())
138 .stdout(Stdio::piped())
139 .current_dir(rootfs);
140 let mut child = t!(cmd.spawn());
141 let mut stdin = child.stdin.take().unwrap();
142 let rootfs = rootfs.to_path_buf();
143 thread::spawn(move || add_files(&mut stdin, &rootfs, &rootfs));
144 t!(io::copy(&mut child.stdout.take().unwrap(),
145 &mut t!(File::create(&rootfs_img))));
146 assert!(t!(child.wait()).success());
148 // Start up the emulator, in the background
150 "arm-unknown-linux-gnueabihf" => {
151 let mut cmd = Command::new("qemu-system-arm");
152 cmd.arg("-M").arg("vexpress-a15")
153 .arg("-m").arg("1024")
154 .arg("-kernel").arg("/tmp/zImage")
155 .arg("-initrd").arg(&rootfs_img)
156 .arg("-dtb").arg("/tmp/vexpress-v2p-ca15-tc1.dtb")
158 .arg("console=ttyAMA0 root=/dev/ram rdinit=/sbin/init init=/sbin/init")
160 .arg("-redir").arg("tcp:12345::12345");
163 "aarch64-unknown-linux-gnu" => {
164 let mut cmd = Command::new("qemu-system-aarch64");
165 cmd.arg("-machine").arg("virt")
166 .arg("-cpu").arg("cortex-a57")
167 .arg("-m").arg("1024")
168 .arg("-kernel").arg("/tmp/Image")
169 .arg("-initrd").arg(&rootfs_img)
171 .arg("console=ttyAMA0 root=/dev/ram rdinit=/sbin/init init=/sbin/init")
173 .arg("-netdev").arg("user,id=net0,hostfwd=tcp::12345-:12345")
174 .arg("-device").arg("virtio-net-device,netdev=net0,mac=00:00:00:00:00:00");
177 _ => panic!("cannot start emulator for: {}"< target),
180 fn add_files(w: &mut dyn Write, root: &Path, cur: &Path) {
181 for entry in t!(cur.read_dir()) {
182 let entry = t!(entry);
183 let path = entry.path();
184 let to_print = path.strip_prefix(root).unwrap();
185 t!(write!(w, "{}\u{0}", to_print.to_str().unwrap()));
186 if t!(entry.file_type()).is_dir() {
187 add_files(w, root, &path);
193 fn push(path: &Path) {
194 let device_address = env::var(REMOTE_ADDR_ENV).unwrap_or("127.0.0.1:12345".to_string());
195 let client = t!(TcpStream::connect(device_address));
196 let mut client = BufWriter::new(client);
197 t!(client.write_all(b"push"));
198 send(path, &mut client);
201 // Wait for an acknowledgement that all the data was received. No idea
202 // why this is necessary, seems like it shouldn't be!
203 let mut client = client.into_inner().unwrap();
204 let mut buf = [0; 4];
205 t!(client.read_exact(&mut buf));
206 assert_eq!(&buf, b"ack ");
207 println!("done pushing {:?}", path);
210 fn run(files: String, args: Vec<String>) {
211 let device_address = env::var(REMOTE_ADDR_ENV).unwrap_or("127.0.0.1:12345".to_string());
212 let client = t!(TcpStream::connect(device_address));
213 let mut client = BufWriter::new(client);
214 t!(client.write_all(b"run "));
216 // Send over the args
218 t!(client.write_all(arg.as_bytes()));
219 t!(client.write_all(&[0]));
221 t!(client.write_all(&[0]));
223 // Send over env vars
225 // Don't send over *everything* though as some env vars are set by and used
227 for (k, v) in env::vars() {
234 t!(client.write_all(k.as_bytes()));
235 t!(client.write_all(&[0]));
236 t!(client.write_all(v.as_bytes()));
237 t!(client.write_all(&[0]));
239 t!(client.write_all(&[0]));
241 // Send over support libraries
242 let mut files = files.split(':');
243 let exe = files.next().unwrap();
244 for file in files.map(Path::new) {
245 send(&file, &mut client);
247 t!(client.write_all(&[0]));
249 // Send over the client executable as the last piece
250 send(exe.as_ref(), &mut client);
252 println!("uploaded {:?}, waiting for result", exe);
254 // Ok now it's time to read all the output. We're receiving "frames"
255 // representing stdout/stderr, so we decode all that here.
256 let mut header = [0; 5];
257 let mut stderr_done = false;
258 let mut stdout_done = false;
259 let mut client = t!(client.into_inner());
260 let mut stdout = io::stdout();
261 let mut stderr = io::stderr();
262 while !stdout_done || !stderr_done {
263 t!(client.read_exact(&mut header));
264 let amt = ((header[1] as u64) << 24) |
265 ((header[2] as u64) << 16) |
266 ((header[3] as u64) << 8) |
267 ((header[4] as u64) << 0);
272 t!(io::copy(&mut (&mut client).take(amt), &mut stdout));
279 t!(io::copy(&mut (&mut client).take(amt), &mut stderr));
285 // Finally, read out the exit status
286 let mut status = [0; 5];
287 t!(client.read_exact(&mut status));
288 let code = ((status[1] as i32) << 24) |
289 ((status[2] as i32) << 16) |
290 ((status[3] as i32) << 8) |
291 ((status[4] as i32) << 0);
293 std::process::exit(code);
295 println!("died due to signal {}", code);
296 std::process::exit(3);
300 fn send(path: &Path, dst: &mut dyn Write) {
301 t!(dst.write_all(path.file_name().unwrap().to_str().unwrap().as_bytes()));
302 t!(dst.write_all(&[0]));
303 let mut file = t!(File::open(&path));
304 let amt = t!(file.metadata()).len();
311 t!(io::copy(&mut file, dst));