1 use crate::adevice::{Device, Profiler};
2 use crate::commands::{restart_type, split_string, AdbCommand};
3 use crate::progress;
4 use crate::restart_chooser::{RestartChooser, RestartType};
5 use crate::{fingerprint, time};
6 
7 use anyhow::{anyhow, bail, Context, Result};
8 use itertools::Itertools;
9 use lazy_static::lazy_static;
10 use regex::Regex;
11 use serde::__private::ToString;
12 use std::cmp::Ordering;
13 use std::collections::{HashMap, HashSet};
14 use std::path::PathBuf;
15 use std::process;
16 use std::thread::sleep;
17 use std::time::Duration;
18 use std::time::Instant;
19 use tracing::{debug, info};
20 
21 pub struct RealDevice {
22     // If set, pass to all adb commands with --serial,
23     // otherwise let adb default to the only connected device or use ANDROID_SERIAL env variable.
24     android_serial: Option<String>,
25 }
26 
27 impl Device for RealDevice {
28     /// Runs `adb` with the given args.
29     /// If there is a non-zero exit code or non-empty stderr, then
30     /// creates a Result Err string with the details.
run_adb_command(&self, cmd: &AdbCommand) -> Result<String>31     fn run_adb_command(&self, cmd: &AdbCommand) -> Result<String> {
32         self.run_raw_adb_command(&cmd.args())
33     }
34 
reboot(&self) -> Result<String>35     fn reboot(&self) -> Result<String> {
36         self.run_raw_adb_command(&["reboot".to_string()])
37     }
38 
soft_restart(&self) -> Result<String>39     fn soft_restart(&self) -> Result<String> {
40         self.run_raw_adb_command(&split_string("exec-out start"))
41     }
42 
fingerprint( &self, partitions: &[String], ) -> Result<HashMap<PathBuf, fingerprint::FileMetadata>>43     fn fingerprint(
44         &self,
45         partitions: &[String],
46     ) -> Result<HashMap<PathBuf, fingerprint::FileMetadata>> {
47         self.fingerprint_device(partitions)
48     }
49 
50     /// Get the apks that are installed (i.e. with `adb install`)
51     /// Count anything in the /data partition as installed.
get_installed_apks(&self) -> Result<HashSet<String>>52     fn get_installed_apks(&self) -> Result<HashSet<String>> {
53         // TODO(rbraunstein): See if there is a better way to do this that doesn't look for /data
54         let package_manager_output = self
55             .run_raw_adb_command(&split_string("exec-out pm list packages -s -f"))
56             .context("Running pm list packages")?;
57 
58         let packages = apks_from_pm_list_output(&package_manager_output);
59         debug!("adb pm list packages found packages: {packages:?}");
60         Ok(packages)
61     }
62 
63     /// Wait for the device to be ready to use.
64     /// First ask adb to wait for the device, then poll for sys.boot_completed on the device.
wait(&self, profiler: &mut Profiler) -> Result<String>65     fn wait(&self, profiler: &mut Profiler) -> Result<String> {
66         // Typically the reboot on acloud is 25 secs
67         // And another 50 for fully booted
68         // Wait up to 3 times as long for either'
69         progress::start(" * [1/2] Waiting for device to connect.");
70         time!(
71             {
72                 let args = self.adjust_adb_args(&["wait-for-device".to_string()]);
73                 self.wait_for_adb_with_timeout(&args, Duration::from_secs(70))?;
74             },
75             profiler.wait_for_device
76         );
77 
78         progress::update(" * [2/2] Waiting for property sys.boot_completed.");
79         time!(
80             {
81                 let args = self.adjust_adb_args(&[
82                     "wait-for-device".to_string(),
83                     "shell".to_string(),
84                     "while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done".to_string(),
85                 ]);
86                 let result = self.wait_for_adb_with_timeout(&args, Duration::from_secs(100));
87                 progress::stop();
88                 result
89             },
90             profiler.wait_for_boot_completed
91         )
92     }
93 
prep_after_flash(&self, profiler: &mut Profiler) -> Result<()>94     fn prep_after_flash(&self, profiler: &mut Profiler) -> Result<()> {
95         progress::start(" * [1/2] Remounting device");
96         let timeout = Duration::from_secs(60);
97 
98         self.run_cmd_with_retry_until_timeout(
99             "adb",
100             &self.adjust_adb_args(&["root".to_string()]),
101             timeout,
102         )?;
103         // Remount and reboot; rebooting will return status code 255 so ignore error.
104         let _ = self.run_raw_adb_command(&["remount".to_string(), "-R".to_string()]);
105         progress::stop();
106         self.wait(profiler)?;
107         self.run_cmd_with_retry_until_timeout(
108             "adb",
109             &self.adjust_adb_args(&["root".to_string()]),
110             timeout,
111         )?;
112         Ok(())
113     }
114 
115     /// Runs `adb` with the given args.
116     /// If there is a non-zero exit code or non-empty stderr, then
117     /// creates a Result Err string with the details.
run_raw_adb_command(&self, cmd: &[String]) -> Result<String>118     fn run_raw_adb_command(&self, cmd: &[String]) -> Result<String> {
119         let adjusted_args = self.adjust_adb_args(cmd);
120         info!("       -- adb {adjusted_args:?}");
121         let output = process::Command::new("adb")
122             .args(adjusted_args)
123             .output()
124             .context("Error running adb commands")?;
125 
126         if output.status.success() {
127             let stdout = String::from_utf8(output.stdout)?;
128             return Ok(stdout);
129         }
130 
131         // It is some error.
132         let status = match output.status.code() {
133             Some(code) => format!("Exited with status code: {code}"),
134             None => "Process terminated by signal".to_string(),
135         };
136 
137         // Adb writes bad commands to stderr.  (adb badverb) with status 1
138         // Adb writes remount output to stderr (adb remount) but gives status 0
139         let stderr = match String::from_utf8(output.stderr) {
140             Ok(str) => str,
141             Err(e) => return Err(anyhow!("Error translating stderr {}", e)),
142         };
143 
144         // Adb writes push errors to stdout.
145         let stdout = match String::from_utf8(output.stdout) {
146             Ok(str) => str,
147             Err(e) => return Err(anyhow!("Error translating stdout {}", e)),
148         };
149 
150         Err(anyhow!("adb error, {status} {stdout} {stderr}"))
151     }
152 }
153 
154 lazy_static! {
155     // Sample output, one installed, one not:
156     // % adb exec-out pm list packages  -s -f  | grep shell
157     //   package:/product/app/Browser2/Browser2.apk=org.chromium.webview_shell
158     //   package:/data/app/~~PxHDtZDEgAeYwRyl-R3bmQ==/com.android.shell--R0z7ITsapIPKnt4BT0xkg==/base.apk=com.android.shell
159     // # capture the package name (com.android.shell)
160     static ref PM_LIST_PACKAGE_MATCHER: Regex =
161         Regex::new(r"^package:/data/app/.*/base.apk=(.+)$").expect("regex does not compile");
162 }
163 
164 /// Filter package manager output to figure out if the apk is installed in /data.
apks_from_pm_list_output(stdout: &str) -> HashSet<String>165 fn apks_from_pm_list_output(stdout: &str) -> HashSet<String> {
166     let package_match = stdout
167         .lines()
168         .filter_map(|line| PM_LIST_PACKAGE_MATCHER.captures(line).map(|x| x[1].to_string()))
169         .collect();
170     package_match
171 }
172 
173 impl RealDevice {
new(android_serial: Option<String>) -> RealDevice174     pub fn new(android_serial: Option<String>) -> RealDevice {
175         RealDevice { android_serial }
176     }
177 
178     /// Add -s DEVICE to the adb args based on global options.
adjust_adb_args(&self, args: &[String]) -> Vec<String>179     fn adjust_adb_args(&self, args: &[String]) -> Vec<String> {
180         match &self.android_serial {
181             Some(serial) => [vec!["-s".to_string(), serial.clone()], args.to_vec()].concat(),
182             None => args.to_vec(),
183         }
184     }
185 
186     /// Given "partitions" at the root of the device,
187     /// return an entry for each file found.  The entry contains the
188     /// digest of the file contents and stat-like data about the file.
189     /// Typically, dirs = ["system"]
fingerprint_device( &self, partitions: &[String], ) -> Result<HashMap<PathBuf, fingerprint::FileMetadata>>190     fn fingerprint_device(
191         &self,
192         partitions: &[String],
193     ) -> Result<HashMap<PathBuf, fingerprint::FileMetadata>> {
194         // Ensure we are root or we can't read some files.
195         // In userdebug builds, every reboot reverts back to the "shell" user.
196         self.run_raw_adb_command(&["root".to_string()])?;
197         self.run_raw_adb_command(&["wait-for-device".to_string()])?;
198         let mut adb_args = vec![
199             "shell".to_string(),
200             "/system/bin/adevice_fingerprint".to_string(),
201             "-p".to_string(),
202         ];
203         // -p system,system_ext
204         adb_args.push(partitions.join(","));
205         let fingerprint_result = self.run_raw_adb_command(&adb_args);
206         // Deal with some bootstrapping errors, like adevice_fingerprint isn't installed
207         // by printing diagnostics and exiting.
208         if let Err(problem) = fingerprint_result {
209             if problem
210                 .root_cause()
211                 .to_string()
212                 // TODO(rbraunstein): Will this work in other locales?
213                 .contains("adevice_fingerprint: inaccessible or not found")
214             {
215                 // Running as root, but adevice_fingerprint not found.
216                 // This should not happen after we tag it as an "eng" module.
217                 bail!("\n  Thank you for testing out adevice.\n  Flashing a recent image should install the needed `adevice_fingerprint` binary.\n  Otherwise, you can bootstrap by doing the following:\n\t ` adb remount; m adevice_fingerprint adevice && adb push $ANDROID_PRODUCT_OUT/system/bin/adevice_fingerprint system/bin/adevice_fingerprint`");
218             } else {
219                 // If pontis is running, add to the error message to check pontis UI
220                 let pontis_status = process::Command::new("pontis")
221                     .args(&vec!["status".to_string()])
222                     .output()
223                     .context("Error checking pontis status")?;
224 
225                 let error_msg = format!("Unknown problem running `adevice_fingerprint` on your device: {problem:?}.\n  Your device may still be in a booting state.  Try `adb get-state` to start debugging.");
226                 if pontis_status.status.success() {
227                     let pontis_error_msg = "\n  If you are using go/pontis, make sure the device appears in the Pontis browser UI and if not re-add it there.";
228                     bail!("{}{}", error_msg, pontis_error_msg);
229                 }
230                 bail!("{}", error_msg);
231             }
232         }
233 
234         let stdout = fingerprint_result.unwrap();
235 
236         let result: HashMap<String, fingerprint::FileMetadata> = match serde_json::from_str(&stdout)
237         {
238             Err(err) if err.line() == 1 && err.column() == 0 && err.is_eof() => {
239                 // This means there was no data. Print a different error, and adb
240                 // probably also just printed a line.
241                 bail!("Device didn't return any data.");
242             }
243             Err(err) => return Err(err).context("Error reading json"),
244             Ok(file_map) => file_map,
245         };
246         Ok(result.into_iter().map(|(path, metadata)| (PathBuf::from(path), metadata)).collect())
247     }
248 
249     /// Run "adb wait-for-device" ... but exit if adb doesn't return
250     /// in the `timeout` amount of time.
wait_for_adb_with_timeout(&self, args: &[String], timeout: Duration) -> Result<String>251     pub fn wait_for_adb_with_timeout(&self, args: &[String], timeout: Duration) -> Result<String> {
252         self.run_cmd_with_retry_until_timeout("adb", args, timeout)
253     }
254 
255     /// run command with retry until timeout duration is reached
run_cmd_with_retry_until_timeout( &self, cmd: &str, args: &[String], timeout: Duration, ) -> Result<String>256     pub fn run_cmd_with_retry_until_timeout(
257         &self,
258         cmd: &str,
259         args: &[String],
260         timeout: Duration,
261     ) -> Result<String> {
262         run_process_with_retry_until_timeout(cmd, args, timeout)
263     }
264 }
265 
266 // Attempts to run a command until the command is either:
267 // 1) Successful
268 // 2) The amount of retries exceeds 5
269 // 3) The timeout (total across all retries) runs out.
270 // This is used for adb wait-for-device on acloud which may return
271 // errors the first few times.
272 // Using timeout binary to simplify (not having to kill process in rust)
273 // TODO(kevindagostino): fix for windows. Use the wait_timeout crate.
274 
run_process_with_retry_until_timeout( cmd: &str, args: &[String], timeout: Duration, ) -> Result<String>275 pub fn run_process_with_retry_until_timeout(
276     cmd: &str,
277     args: &[String],
278     timeout: Duration,
279 ) -> Result<String> {
280     let start_time = Instant::now();
281     let delay = Duration::from_secs(1);
282     let max_retries = 5;
283     let mut retry_count = 0;
284 
285     while retry_count < max_retries {
286         let time_left = timeout.saturating_sub(start_time.elapsed());
287         if time_left <= Duration::ZERO {
288             break;
289         }
290         retry_count += 1;
291 
292         let mut timeout_args = vec![format!("{}", time_left.as_secs()), cmd.to_string()];
293         timeout_args.extend_from_slice(args);
294 
295         info!("       -- timeout {}", &timeout_args.join(" "));
296         let output = std::process::Command::new("timeout")
297             .args(timeout_args)
298             .output()
299             .expect("command executed");
300         if output.status.success() {
301             let msg = String::from_utf8(output.stdout)?;
302             info!("       {} {}", output.status, msg);
303             return Ok(msg);
304         }
305 
306         if retry_count > 1 {
307             let update_message = format!("retry attempt {} - {:?}", retry_count, cmd.to_string());
308             progress::update(&update_message)
309         }
310 
311         // error; log and retry if within timeout window
312         info!("       {} {:?}", output.status, String::from_utf8(output.stderr).expect("stderr"));
313         sleep(delay);
314     }
315     bail!("Command failed to execute {}", cmd.to_string());
316 }
317 
update( restart_chooser: &RestartChooser, adb_commands: &HashMap<PathBuf, AdbCommand>, profiler: &mut Profiler, device: &impl Device, should_wait: crate::cli::Wait, ) -> Result<()>318 pub fn update(
319     restart_chooser: &RestartChooser,
320     adb_commands: &HashMap<PathBuf, AdbCommand>,
321     profiler: &mut Profiler,
322     device: &impl Device,
323     should_wait: crate::cli::Wait,
324 ) -> Result<()> {
325     if adb_commands.is_empty() {
326         return Ok(());
327     }
328 
329     let installed_files =
330         adb_commands.keys().map(|p| p.clone().into_os_string().into_string().unwrap()).collect();
331 
332     progress::start("Preparing to update files");
333     prep_for_push(device, should_wait.clone())?;
334     let mut i = 1;
335     time!(
336         for command in adb_commands.values().cloned().sorted_by(&mkdir_comes_first_rm_dfs) {
337             let update_message =
338                 format!("Updating files [{}/{}] {:?}", i, adb_commands.len(), command.args());
339             progress::update(&update_message);
340             device.run_adb_command(&command)?;
341             i += 1;
342         },
343         profiler.adb_cmds
344     );
345     progress::stop();
346     println!(" * Update succeeded!");
347     println!();
348 
349     let rtype = restart_type(restart_chooser, &installed_files);
350     profiler.restart_type = format!("{:?}", rtype);
351     match rtype {
352         RestartType::Reboot => time!(device.reboot(), profiler.restart),
353         RestartType::SoftRestart => time!(device.soft_restart(), profiler.restart),
354         RestartType::None => {
355             tracing::debug!("No restart command");
356             return Ok(());
357         }
358     }?;
359 
360     if should_wait.into() {
361         device.wait(profiler)?;
362     }
363     Ok(())
364 }
365 
366 /// Common command to prepare a device to receive new files.
367 /// Always: `exec-out stop`
368 /// Always: `remount`
369 ///  # A remount may not be needed but doesn't slow things down.
370 /// If `should_wait`: Set the system property sys.boot_completed to 0.
371 ///  # A reboot would do this anyway, but it doesn't hurt if we do it too.
372 ///  # We poll for that property to be set back to 1.
373 ///  # Both reboot and exec-out start will set it back to 1 when the
374 ///  # system has booted and is ready to receive commands and run tests.
prep_for_push(device: &impl Device, should_wait: crate::cli::Wait) -> Result<()>375 fn prep_for_push(device: &impl Device, should_wait: crate::cli::Wait) -> Result<()> {
376     device.run_raw_adb_command(&split_string("exec-out stop"))?;
377     // We seem to need a remount after reboots to make the system writable.
378     device.run_raw_adb_command(&split_string("remount"))?;
379     // Set the prop to the empty string so our "-z" check in wait works.
380     if should_wait.into() {
381         device.run_raw_adb_command(&[
382             "exec-out".to_string(),
383             "setprop".to_string(),
384             "sys.boot_completed".to_string(),
385             "".to_string(),
386         ])?;
387     }
388     Ok(())
389 }
390 
391 // 1) Ensure mkdir comes before other commands.
392 // 2) Do removes as a depth-first-search so we clean children before parents.
393 // 3) Sort rm before other commands, but it shouldn't matter.
394 // 4) Remove files before dirs.
395 //    We would never remove a file or directory we are pushing to.
mkdir_comes_first_rm_dfs(a: &AdbCommand, b: &AdbCommand) -> Ordering396 fn mkdir_comes_first_rm_dfs(a: &AdbCommand, b: &AdbCommand) -> Ordering {
397     // Neither is mkdir
398     if !a.is_mkdir() && !b.is_mkdir() {
399         // Sort rm's with files before their parents.
400         let a_cmd = a.args().join(" ");
401         let b_cmd = b.args().join(" ");
402 
403         if a.is_rm() && b.is_rm() {
404             // This also sorts files before dirs because of the "-rf" added to dirs.
405             return b_cmd.cmp(&a_cmd);
406         }
407         if a.is_rm() {
408             return Ordering::Less;
409         }
410         if b.is_rm() {
411             return Ordering::Greater;
412         }
413 
414         // Sort everything by the args.
415         return a_cmd.cmp(&b_cmd);
416     }
417     // If both mkdir:
418     //  Just compare the path, parents will come before subdirs.
419     if a.is_mkdir() && b.is_mkdir() {
420         return a.device_path().cmp(b.device_path());
421     }
422     if a.is_mkdir() {
423         return Ordering::Less;
424     }
425     if b.is_mkdir() {
426         return Ordering::Greater;
427     }
428     Ordering::Equal
429 }
430 
431 #[cfg(test)]
432 mod tests {
433     use super::*;
434     use crate::commands::AdbAction;
435     use anyhow::{bail, Result};
436     use core::cmp::Ordering;
437     use std::time::Duration;
438 
439     // Igoring the tests so they don't cause delays in CI, but can still be run by hand.
440     #[ignore]
441     #[test]
timeout_returns_when_process_returns() -> Result<()>442     fn timeout_returns_when_process_returns() -> Result<()> {
443         let timeout = Duration::from_secs(5);
444         let sleep_args = &["3".to_string()];
445         let output = run_process_with_retry_until_timeout("sleep", sleep_args, timeout);
446         match output {
447             Ok(_) => Ok(()),
448             _ => bail!("Expected an ok status code"),
449         }
450     }
451 
452     #[ignore]
453     #[test]
timeout_exits_when_timeout_hit() -> Result<()>454     fn timeout_exits_when_timeout_hit() -> Result<()> {
455         let timeout = Duration::from_secs(5);
456         let sleep_args = &["7".to_string()];
457         let start_time = Instant::now();
458         let output = run_process_with_retry_until_timeout("sleep", sleep_args, timeout);
459 
460         // smoke test to make sure process ran longer then timeout.
461         let duration = start_time.elapsed();
462         assert!(
463             duration > timeout,
464             "Expected process to take longer then timeout. Elapsed: {:?}, Timeout: {:?}",
465             duration,
466             timeout
467         );
468 
469         match output {
470             Ok(_) => bail!("Expected error status code"),
471             _ => Ok(()),
472         }
473     }
474 
475     #[ignore]
476     #[test]
timeout_deals_with_process_errors() -> Result<()>477     fn timeout_deals_with_process_errors() -> Result<()> {
478         let timeout = Duration::from_secs(5);
479         let sleep_args = &["--bad-arg".to_string(), "7".to_string()];
480         // Add a bad arg so the process we run errs out.
481         let output = run_process_with_retry_until_timeout("sleep", sleep_args, timeout);
482         match output {
483             Ok(_) => bail!("Expected error status code"),
484             _ => Ok(()),
485         }
486     }
487 
488     #[ignore]
489     #[test]
reboot_wait() -> Result<()>490     fn reboot_wait() -> Result<()> {
491         let timeout = Duration::from_secs(5);
492         let sleep_args = &["--bad-arg".to_string(), "7".to_string()];
493         // Add a bad arg so the process we run errs out.
494         let output = run_process_with_retry_until_timeout("sleep", sleep_args, timeout);
495         match output {
496             Ok(_) => bail!("Expected error status code"),
497             _ => Ok(()),
498         }
499     }
500 
delete_file_cmd(file: &str) -> AdbCommand501     fn delete_file_cmd(file: &str) -> AdbCommand {
502         AdbCommand::from_action(AdbAction::DeleteFile, &PathBuf::from(file))
503     }
504 
delete_dir_cmd(dir: &str) -> AdbCommand505     fn delete_dir_cmd(dir: &str) -> AdbCommand {
506         AdbCommand::from_action(AdbAction::DeleteDir, &PathBuf::from(dir))
507     }
508 
509     #[test]
deeper_rms_come_first()510     fn deeper_rms_come_first() {
511         assert_eq!(
512             Ordering::Less,
513             mkdir_comes_first_rm_dfs(
514                 &delete_file_cmd("dir1/dir2/file1"),
515                 &delete_dir_cmd("dir1/dir2"),
516             )
517         );
518         assert_eq!(
519             Ordering::Greater,
520             mkdir_comes_first_rm_dfs(
521                 &delete_dir_cmd("dir1/dir2"),
522                 &delete_file_cmd("dir1/dir2/file1"),
523             )
524         );
525         assert_eq!(
526             Ordering::Less,
527             mkdir_comes_first_rm_dfs(
528                 &delete_dir_cmd("dir1/dir2/dir3"),
529                 &delete_dir_cmd("dir1/dir2"),
530             )
531         );
532         assert_eq!(
533             Ordering::Greater,
534             mkdir_comes_first_rm_dfs(
535                 &delete_dir_cmd("dir1/dir2"),
536                 &delete_dir_cmd("dir1/dir2/dir3"),
537             )
538         );
539     }
540     #[test]
rm_all_files_before_dirs()541     fn rm_all_files_before_dirs() {
542         assert_eq!(
543             Ordering::Less,
544             mkdir_comes_first_rm_dfs(
545                 &delete_file_cmd("system/app/FakeOemFeatures/FakeOemFeatures.apk"),
546                 &delete_dir_cmd("system/app/FakeOemFeatures"),
547             )
548         );
549         assert_eq!(
550             Ordering::Greater,
551             mkdir_comes_first_rm_dfs(
552                 &delete_dir_cmd("system/app/FakeOemFeatures"),
553                 &delete_file_cmd("system/app/FakeOemFeatures/FakeOemFeatures.apk"),
554             )
555         );
556     }
557 
558     #[test]
sort_many()559     fn sort_many() {
560         let dir = |d| AdbCommand::from_action(AdbAction::DeleteDir, &PathBuf::from(d));
561         let file = |d| AdbCommand::from_action(AdbAction::DeleteFile, &PathBuf::from(d));
562         let mut adb_commands: Vec<AdbCommand> = vec![
563             file("system/STALE_FILE"),
564             dir("system/bin/dir1/STALE_DIR"),
565             file("system/bin/dir1/STALE_DIR/stalefile1"),
566             file("system/bin/dir1/STALE_DIR/stalefile2"),
567         ];
568 
569         adb_commands.sort_by(&mkdir_comes_first_rm_dfs);
570         assert_eq!(
571             // Expected sorted order, deepest first.
572             // files before dirs.
573             vec![
574                 file("system/bin/dir1/STALE_DIR/stalefile2"),
575                 file("system/bin/dir1/STALE_DIR/stalefile1"),
576                 file("system/STALE_FILE"),
577                 dir("system/bin/dir1/STALE_DIR"),
578             ],
579             adb_commands
580         );
581     }
582 
583     #[test]
584     // NOTE: This test assumes we have adb in our path.
adb_command_success()585     fn adb_command_success() {
586         // Use real device for device tests.
587         let result = RealDevice::new(None)
588             .run_raw_adb_command(&["version".to_string()])
589             .expect("Error running command");
590         assert!(
591             result.contains("Android Debug Bridge version"),
592             "Expected a version string, but received:\n {result}"
593         );
594     }
595 
596     #[test]
adb_command_failure()597     fn adb_command_failure() {
598         let result = RealDevice::new(None).run_raw_adb_command(&["improper_cmd".to_string()]);
599         if result.is_ok() {
600             panic!("Did not expect to succeed");
601         }
602 
603         let expected_str =
604             "adb error, Exited with status code: 1  adb: unknown command improper_cmd\n";
605         assert_eq!(expected_str, format!("{:?}", result.unwrap_err()));
606     }
607 
608     #[test]
package_manager_output_parsing()609     fn package_manager_output_parsing() {
610         let actual_output = r#"
611 package:/product/app/Browser2/Browser2.apk=org.chromium.webview_shell
612 package:/apex/com.google.aosp_cf_phone.rros/overlay/cuttlefish_overlay_frameworks_base_core.apk=android.cuttlefish.overlay
613 package:/data/app/~~f_ZzeFPKma_EfXRklotqFg==/com.android.shell-hrjEOvqv3dAautKdfqeAEA==/base.apk=com.android.shell
614 package:/apex/com.google.aosp_cf_phone.rros/overlay/cuttlefish_phone_overlay_frameworks_base_core.apk=android.cuttlefish.phone.overlay
615 "#;
616         let mut expected: HashSet<String> = HashSet::new();
617         expected.insert("com.android.shell".to_string());
618         assert_eq!(expected, apks_from_pm_list_output(actual_output));
619     }
620 }
621