]> git.lizzy.rs Git - rust.git/blob - src/tools/remote-test-client/src/main.rs
Rollup merge of #63055 - Mark-Simulacrum:save-analysis-clean-2, r=Xanewok
[rust.git] / src / tools / remote-test-client / src / main.rs
1 /// This is a small client program intended to pair with `remote-test-server` in
2 /// this repository. This client connects to the server over TCP and is used to
3 /// push artifacts and run tests on the server instead of locally.
4 ///
5 /// Here is also where we bake in the support to spawn the QEMU emulator as
6 /// well.
7
8 use std::env;
9 use std::fs::{self, File};
10 use std::io::prelude::*;
11 use std::io::{self, BufWriter};
12 use std::net::TcpStream;
13 use std::path::{Path, PathBuf};
14 use std::process::{Command, Stdio};
15 use std::thread;
16 use std::time::Duration;
17
18 const REMOTE_ADDR_ENV: &str = "TEST_DEVICE_ADDR";
19
20 macro_rules! t {
21     ($e:expr) => (match $e {
22         Ok(e) => e,
23         Err(e) => panic!("{} failed with {}", stringify!($e), e),
24     })
25 }
26
27 fn main() {
28     let mut args = env::args().skip(1);
29
30     match &args.next().unwrap()[..] {
31         "spawn-emulator" => {
32             spawn_emulator(&args.next().unwrap(),
33                            Path::new(&args.next().unwrap()),
34                            Path::new(&args.next().unwrap()),
35                            args.next().map(|s| s.into()))
36         }
37         "push" => {
38             push(Path::new(&args.next().unwrap()))
39         }
40         "run" => {
41             run(args.next().unwrap(), args.collect())
42         }
43         cmd => panic!("unknown command: {}", cmd),
44     }
45 }
46
47 fn spawn_emulator(target: &str,
48                   server: &Path,
49                   tmpdir: &Path,
50                   rootfs: Option<PathBuf>) {
51     let device_address = env::var(REMOTE_ADDR_ENV).unwrap_or("127.0.0.1:12345".to_string());
52
53     if env::var(REMOTE_ADDR_ENV).is_ok() {
54         println!("Connecting to remote device {} ...", device_address);
55     } else if target.contains("android") {
56         start_android_emulator(server);
57     } else {
58         let rootfs = rootfs.as_ref().expect("need rootfs on non-android");
59         start_qemu_emulator(target, rootfs, server, tmpdir);
60     }
61
62     // Wait for the emulator to come online
63     loop {
64         let dur = Duration::from_millis(100);
65         if let Ok(mut client) = TcpStream::connect(&device_address) {
66             t!(client.set_read_timeout(Some(dur)));
67             t!(client.set_write_timeout(Some(dur)));
68             if client.write_all(b"ping").is_ok() {
69                 let mut b = [0; 4];
70                 if client.read_exact(&mut b).is_ok() {
71                     break
72                 }
73             }
74         }
75         thread::sleep(dur);
76     }
77 }
78
79 fn start_android_emulator(server: &Path) {
80     println!("waiting for device to come online");
81     let status = Command::new("adb")
82                     .arg("wait-for-device")
83                     .status()
84                     .unwrap();
85     assert!(status.success());
86
87     println!("pushing server");
88     let status = Command::new("adb")
89                     .arg("push")
90                     .arg(server)
91                     .arg("/data/tmp/testd")
92                     .status()
93                     .unwrap();
94     assert!(status.success());
95
96     println!("forwarding tcp");
97     let status = Command::new("adb")
98                     .arg("forward")
99                     .arg("tcp:12345")
100                     .arg("tcp:12345")
101                     .status()
102                     .unwrap();
103     assert!(status.success());
104
105     println!("executing server");
106     Command::new("adb")
107                     .arg("shell")
108                     .arg("/data/tmp/testd")
109                     .spawn()
110                     .unwrap();
111 }
112
113 fn start_qemu_emulator(target: &str,
114                        rootfs: &Path,
115                        server: &Path,
116                        tmpdir: &Path) {
117     // Generate a new rootfs image now that we've updated the test server
118     // executable. This is the equivalent of:
119     //
120     //      find $rootfs -print 0 | cpio --null -o --format=newc > rootfs.img
121     t!(fs::copy(server, rootfs.join("testd")));
122     let rootfs_img = tmpdir.join("rootfs.img");
123     let mut cmd = Command::new("cpio");
124     cmd.arg("--null")
125        .arg("-o")
126        .arg("--format=newc")
127        .stdin(Stdio::piped())
128        .stdout(Stdio::piped())
129        .current_dir(rootfs);
130     let mut child = t!(cmd.spawn());
131     let mut stdin = child.stdin.take().unwrap();
132     let rootfs = rootfs.to_path_buf();
133     thread::spawn(move || add_files(&mut stdin, &rootfs, &rootfs));
134     t!(io::copy(&mut child.stdout.take().unwrap(),
135                 &mut t!(File::create(&rootfs_img))));
136     assert!(t!(child.wait()).success());
137
138     // Start up the emulator, in the background
139     match target {
140         "arm-unknown-linux-gnueabihf" => {
141             let mut cmd = Command::new("qemu-system-arm");
142             cmd.arg("-M").arg("vexpress-a15")
143                .arg("-m").arg("1024")
144                .arg("-kernel").arg("/tmp/zImage")
145                .arg("-initrd").arg(&rootfs_img)
146                .arg("-dtb").arg("/tmp/vexpress-v2p-ca15-tc1.dtb")
147                .arg("-append")
148                .arg("console=ttyAMA0 root=/dev/ram rdinit=/sbin/init init=/sbin/init")
149                .arg("-nographic")
150                .arg("-redir").arg("tcp:12345::12345");
151             t!(cmd.spawn());
152         }
153         "aarch64-unknown-linux-gnu" => {
154             let mut cmd = Command::new("qemu-system-aarch64");
155             cmd.arg("-machine").arg("virt")
156                .arg("-cpu").arg("cortex-a57")
157                .arg("-m").arg("1024")
158                .arg("-kernel").arg("/tmp/Image")
159                .arg("-initrd").arg(&rootfs_img)
160                .arg("-append")
161                .arg("console=ttyAMA0 root=/dev/ram rdinit=/sbin/init init=/sbin/init")
162                .arg("-nographic")
163                .arg("-netdev").arg("user,id=net0,hostfwd=tcp::12345-:12345")
164                .arg("-device").arg("virtio-net-device,netdev=net0,mac=00:00:00:00:00:00");
165             t!(cmd.spawn());
166         }
167         _ => panic!("cannot start emulator for: {}"< target),
168     }
169
170     fn add_files(w: &mut dyn Write, root: &Path, cur: &Path) {
171         for entry in t!(cur.read_dir()) {
172             let entry = t!(entry);
173             let path = entry.path();
174             let to_print = path.strip_prefix(root).unwrap();
175             t!(write!(w, "{}\u{0}", to_print.to_str().unwrap()));
176             if t!(entry.file_type()).is_dir() {
177                 add_files(w, root, &path);
178             }
179         }
180     }
181 }
182
183 fn push(path: &Path) {
184     let device_address = env::var(REMOTE_ADDR_ENV).unwrap_or("127.0.0.1:12345".to_string());
185     let client = t!(TcpStream::connect(device_address));
186     let mut client = BufWriter::new(client);
187     t!(client.write_all(b"push"));
188     send(path, &mut client);
189     t!(client.flush());
190
191     // Wait for an acknowledgement that all the data was received. No idea
192     // why this is necessary, seems like it shouldn't be!
193     let mut client = client.into_inner().unwrap();
194     let mut buf = [0; 4];
195     t!(client.read_exact(&mut buf));
196     assert_eq!(&buf, b"ack ");
197     println!("done pushing {:?}", path);
198 }
199
200 fn run(files: String, args: Vec<String>) {
201     let device_address = env::var(REMOTE_ADDR_ENV).unwrap_or("127.0.0.1:12345".to_string());
202     let client = t!(TcpStream::connect(device_address));
203     let mut client = BufWriter::new(client);
204     t!(client.write_all(b"run "));
205
206     // Send over the args
207     for arg in args {
208         t!(client.write_all(arg.as_bytes()));
209         t!(client.write_all(&[0]));
210     }
211     t!(client.write_all(&[0]));
212
213     // Send over env vars
214     //
215     // Don't send over *everything* though as some env vars are set by and used
216     // by the client.
217     for (k, v) in env::vars() {
218         match &k[..] {
219             "PATH" |
220             "LD_LIBRARY_PATH" |
221             "PWD" => continue,
222             _ => {}
223         }
224         t!(client.write_all(k.as_bytes()));
225         t!(client.write_all(&[0]));
226         t!(client.write_all(v.as_bytes()));
227         t!(client.write_all(&[0]));
228     }
229     t!(client.write_all(&[0]));
230
231     // Send over support libraries
232     let mut files = files.split(':');
233     let exe = files.next().unwrap();
234     for file in files.map(Path::new) {
235         send(&file, &mut client);
236     }
237     t!(client.write_all(&[0]));
238
239     // Send over the client executable as the last piece
240     send(exe.as_ref(), &mut client);
241
242     println!("uploaded {:?}, waiting for result", exe);
243
244     // Ok now it's time to read all the output. We're receiving "frames"
245     // representing stdout/stderr, so we decode all that here.
246     let mut header = [0; 5];
247     let mut stderr_done = false;
248     let mut stdout_done = false;
249     let mut client = t!(client.into_inner());
250     let mut stdout = io::stdout();
251     let mut stderr = io::stderr();
252     while !stdout_done || !stderr_done {
253         t!(client.read_exact(&mut header));
254         let amt = ((header[1] as u64) << 24) |
255                   ((header[2] as u64) << 16) |
256                   ((header[3] as u64) <<  8) |
257                   ((header[4] as u64) <<  0);
258         if header[0] == 0 {
259             if amt == 0 {
260                 stdout_done = true;
261             } else {
262                 t!(io::copy(&mut (&mut client).take(amt), &mut stdout));
263                 t!(stdout.flush());
264             }
265         } else {
266             if amt == 0 {
267                 stderr_done = true;
268             } else {
269                 t!(io::copy(&mut (&mut client).take(amt), &mut stderr));
270                 t!(stderr.flush());
271             }
272         }
273     }
274
275     // Finally, read out the exit status
276     let mut status = [0; 5];
277     t!(client.read_exact(&mut status));
278     let code = ((status[1] as i32) << 24) |
279                ((status[2] as i32) << 16) |
280                ((status[3] as i32) <<  8) |
281                ((status[4] as i32) <<  0);
282     if status[0] == 0 {
283         std::process::exit(code);
284     } else {
285         println!("died due to signal {}", code);
286         std::process::exit(3);
287     }
288 }
289
290 fn send(path: &Path, dst: &mut dyn Write) {
291     t!(dst.write_all(path.file_name().unwrap().to_str().unwrap().as_bytes()));
292     t!(dst.write_all(&[0]));
293     let mut file = t!(File::open(&path));
294     let amt = t!(file.metadata()).len();
295     t!(dst.write_all(&[
296         (amt >> 24) as u8,
297         (amt >> 16) as u8,
298         (amt >>  8) as u8,
299         (amt >>  0) as u8,
300     ]));
301     t!(io::copy(&mut file, dst));
302 }