]> git.lizzy.rs Git - rust.git/blob - src/tools/remote-test-client/src/main.rs
Rollup merge of #52769 - sinkuu:stray_test, r=alexcrichton
[rust.git] / src / tools / remote-test-client / src / main.rs
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.
4 //
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.
10
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.
14 ///
15 /// Here is also where we bake in the support to spawn the QEMU emulator as
16 /// well.
17
18 use std::env;
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};
25 use std::thread;
26 use std::time::Duration;
27
28 const REMOTE_ADDR_ENV: &'static str = "TEST_DEVICE_ADDR";
29
30 macro_rules! t {
31     ($e:expr) => (match $e {
32         Ok(e) => e,
33         Err(e) => panic!("{} failed with {}", stringify!($e), e),
34     })
35 }
36
37 fn main() {
38     let mut args = env::args().skip(1);
39
40     match &args.next().unwrap()[..] {
41         "spawn-emulator" => {
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()))
46         }
47         "push" => {
48             push(Path::new(&args.next().unwrap()))
49         }
50         "run" => {
51             run(args.next().unwrap(), args.collect())
52         }
53         cmd => panic!("unknown command: {}", cmd),
54     }
55 }
56
57 fn spawn_emulator(target: &str,
58                   server: &Path,
59                   tmpdir: &Path,
60                   rootfs: Option<PathBuf>) {
61     let device_address = env::var(REMOTE_ADDR_ENV).unwrap_or("127.0.0.1:12345".to_string());
62
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);
67     } else {
68         let rootfs = rootfs.as_ref().expect("need rootfs on non-android");
69         start_qemu_emulator(target, rootfs, server, tmpdir);
70     }
71
72     // Wait for the emulator to come online
73     loop {
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() {
79                 let mut b = [0; 4];
80                 if client.read_exact(&mut b).is_ok() {
81                     break
82                 }
83             }
84         }
85         thread::sleep(dur);
86     }
87 }
88
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")
93                     .status()
94                     .unwrap();
95     assert!(status.success());
96
97     println!("pushing server");
98     let status = Command::new("adb")
99                     .arg("push")
100                     .arg(server)
101                     .arg("/data/tmp/testd")
102                     .status()
103                     .unwrap();
104     assert!(status.success());
105
106     println!("forwarding tcp");
107     let status = Command::new("adb")
108                     .arg("forward")
109                     .arg("tcp:12345")
110                     .arg("tcp:12345")
111                     .status()
112                     .unwrap();
113     assert!(status.success());
114
115     println!("executing server");
116     Command::new("adb")
117                     .arg("shell")
118                     .arg("/data/tmp/testd")
119                     .spawn()
120                     .unwrap();
121 }
122
123 fn start_qemu_emulator(target: &str,
124                        rootfs: &Path,
125                        server: &Path,
126                        tmpdir: &Path) {
127     // Generate a new rootfs image now that we've updated the test server
128     // executable. This is the equivalent of:
129     //
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");
134     cmd.arg("--null")
135        .arg("-o")
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());
147
148     // Start up the emulator, in the background
149     match target {
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")
157                .arg("-append")
158                .arg("console=ttyAMA0 root=/dev/ram rdinit=/sbin/init init=/sbin/init")
159                .arg("-nographic")
160                .arg("-redir").arg("tcp:12345::12345");
161             t!(cmd.spawn());
162         }
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)
170                .arg("-append")
171                .arg("console=ttyAMA0 root=/dev/ram rdinit=/sbin/init init=/sbin/init")
172                .arg("-nographic")
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");
175             t!(cmd.spawn());
176         }
177         _ => panic!("cannot start emulator for: {}"< target),
178     }
179
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);
188             }
189         }
190     }
191 }
192
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);
199     t!(client.flush());
200
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);
208 }
209
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 "));
215
216     // Send over the args
217     for arg in args {
218         t!(client.write_all(arg.as_bytes()));
219         t!(client.write_all(&[0]));
220     }
221     t!(client.write_all(&[0]));
222
223     // Send over env vars
224     //
225     // Don't send over *everything* though as some env vars are set by and used
226     // by the client.
227     for (k, v) in env::vars() {
228         match &k[..] {
229             "PATH" |
230             "LD_LIBRARY_PATH" |
231             "PWD" => continue,
232             _ => {}
233         }
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]));
238     }
239     t!(client.write_all(&[0]));
240
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);
246     }
247     t!(client.write_all(&[0]));
248
249     // Send over the client executable as the last piece
250     send(exe.as_ref(), &mut client);
251
252     println!("uploaded {:?}, waiting for result", exe);
253
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);
268         if header[0] == 0 {
269             if amt == 0 {
270                 stdout_done = true;
271             } else {
272                 t!(io::copy(&mut (&mut client).take(amt), &mut stdout));
273                 t!(stdout.flush());
274             }
275         } else {
276             if amt == 0 {
277                 stderr_done = true;
278             } else {
279                 t!(io::copy(&mut (&mut client).take(amt), &mut stderr));
280                 t!(stderr.flush());
281             }
282         }
283     }
284
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);
292     if status[0] == 0 {
293         std::process::exit(code);
294     } else {
295         println!("died due to signal {}", code);
296         std::process::exit(3);
297     }
298 }
299
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();
305     t!(dst.write_all(&[
306         (amt >> 24) as u8,
307         (amt >> 16) as u8,
308         (amt >>  8) as u8,
309         (amt >>  0) as u8,
310     ]));
311     t!(io::copy(&mut file, dst));
312 }