xref: /aosp_15_r20/tools/asuite/adevice/src/device.rs (revision c2e18aaa1096c836b086f94603d04f4eb9cf37f5)
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 regex::Regex;
10 use serde::__private::ToString;
11 use std::cmp::Ordering;
12 use std::collections::{HashMap, HashSet};
13 use std::path::PathBuf;
14 use std::process;
15 use std::sync::LazyLock;
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         // It can take 130 seconds after for a full boot.
68         // Setting timeouts to have at least 2x that.
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(75))?;
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(260));
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 // Sample output, one installed, one not:
155 // % adb exec-out pm list packages  -s -f  | grep shell
156 //   package:/product/app/Browser2/Browser2.apk=org.chromium.webview_shell
157 //   package:/data/app/~~PxHDtZDEgAeYwRyl-R3bmQ==/com.android.shell--R0z7ITsapIPKnt4BT0xkg==/base.apk=com.android.shell
158 // # capture the package name (com.android.shell)
159 static PM_LIST_PACKAGE_MATCHER: LazyLock<Regex> = LazyLock::new(|| {
160     Regex::new(r"^package:/data/app/.*/base.apk=(.+)$").expect("regex does not compile")
161 });
162 
163 /// Filter package manager output to figure out if the apk is installed in /data.
apks_from_pm_list_output(stdout: &str) -> HashSet<String>164 fn apks_from_pm_list_output(stdout: &str) -> HashSet<String> {
165     let package_match = stdout
166         .lines()
167         .filter_map(|line| PM_LIST_PACKAGE_MATCHER.captures(line).map(|x| x[1].to_string()))
168         .collect();
169     package_match
170 }
171 
172 impl RealDevice {
new(android_serial: Option<String>) -> RealDevice173     pub fn new(android_serial: Option<String>) -> RealDevice {
174         RealDevice { android_serial }
175     }
176 
177     /// Add -s DEVICE to the adb args based on global options.
adjust_adb_args(&self, args: &[String]) -> Vec<String>178     fn adjust_adb_args(&self, args: &[String]) -> Vec<String> {
179         match &self.android_serial {
180             Some(serial) => [vec!["-s".to_string(), serial.clone()], args.to_vec()].concat(),
181             None => args.to_vec(),
182         }
183     }
184 
185     /// Given "partitions" at the root of the device,
186     /// return an entry for each file found.  The entry contains the
187     /// digest of the file contents and stat-like data about the file.
188     /// Typically, dirs = ["system"]
fingerprint_device( &self, partitions: &[String], ) -> Result<HashMap<PathBuf, fingerprint::FileMetadata>>189     fn fingerprint_device(
190         &self,
191         partitions: &[String],
192     ) -> Result<HashMap<PathBuf, fingerprint::FileMetadata>> {
193         // Ensure we are root or we can't read some files.
194         // In userdebug builds, every reboot reverts back to the "shell" user.
195         self.run_raw_adb_command(&["root".to_string()])?;
196         self.run_raw_adb_command(&["wait-for-device".to_string()])?;
197         let mut adb_args = vec![
198             "shell".to_string(),
199             "/system/bin/adevice_fingerprint".to_string(),
200             "-p".to_string(),
201         ];
202         // -p system,system_ext
203         adb_args.push(partitions.join(","));
204         let fingerprint_result = self.run_raw_adb_command(&adb_args);
205         // Deal with some bootstrapping errors, like adevice_fingerprint isn't installed
206         // by printing diagnostics and exiting.
207         if let Err(problem) = fingerprint_result {
208             if problem
209                 .root_cause()
210                 .to_string()
211                 // TODO(rbraunstein): Will this work in other locales?
212                 .contains("adevice_fingerprint: inaccessible or not found")
213             {
214                 // Running as root, but adevice_fingerprint not found.
215                 // This should not happen after we tag it as an "eng" module.
216                 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`");
217             } else {
218                 // If pontis is running, add to the error message to check pontis UI
219                 let pontis_status = process::Command::new("pontis")
220                     .args(vec!["status".to_string()])
221                     .output()
222                     .context("Error checking pontis status")?;
223 
224                 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.");
225                 if pontis_status.status.success() {
226                     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.";
227                     bail!("{}{}", error_msg, pontis_error_msg);
228                 }
229                 bail!("{}", error_msg);
230             }
231         }
232 
233         let stdout = fingerprint_result.unwrap();
234 
235         let result: HashMap<String, fingerprint::FileMetadata> = match serde_json::from_str(&stdout)
236         {
237             Err(err) if err.line() == 1 && err.column() == 0 && err.is_eof() => {
238                 // This means there was no data. Print a different error, and adb
239                 // probably also just printed a line.
240                 bail!("Device didn't return any data.");
241             }
242             Err(err) => return Err(err).context("Error reading json"),
243             Ok(file_map) => file_map,
244         };
245         Ok(result.into_iter().map(|(path, metadata)| (PathBuf::from(path), metadata)).collect())
246     }
247 
248     /// Run "adb wait-for-device" ... but exit if adb doesn't return
249     /// in the `timeout` amount of time.
wait_for_adb_with_timeout(&self, args: &[String], timeout: Duration) -> Result<String>250     pub fn wait_for_adb_with_timeout(&self, args: &[String], timeout: Duration) -> Result<String> {
251         self.run_cmd_with_retry_until_timeout("adb", args, timeout)
252     }
253 
254     /// run command with retry until timeout duration is reached
run_cmd_with_retry_until_timeout( &self, cmd: &str, args: &[String], timeout: Duration, ) -> Result<String>255     pub fn run_cmd_with_retry_until_timeout(
256         &self,
257         cmd: &str,
258         args: &[String],
259         timeout: Duration,
260     ) -> Result<String> {
261         run_process_with_retry_until_timeout(cmd, args, timeout)
262     }
263 }
264 
265 // Attempts to run a command until the command is either:
266 // 1) Successful
267 // 2) The amount of retries exceeds 5
268 // 3) The timeout (total across all retries) runs out.
269 // This is used for adb wait-for-device on acloud which may return
270 // errors the first few times.
271 // Using timeout binary to simplify (not having to kill process in rust)
272 // TODO(kevindagostino): fix for windows. Use the wait_timeout crate.
273 
run_process_with_retry_until_timeout( cmd: &str, args: &[String], timeout: Duration, ) -> Result<String>274 pub fn run_process_with_retry_until_timeout(
275     cmd: &str,
276     args: &[String],
277     timeout: Duration,
278 ) -> Result<String> {
279     let start_time = Instant::now();
280     let delay = Duration::from_secs(1);
281     let max_retries = 5;
282     let mut retry_count = 0;
283 
284     while retry_count < max_retries {
285         let time_left = timeout.saturating_sub(start_time.elapsed());
286         if time_left <= Duration::ZERO {
287             break;
288         }
289         retry_count += 1;
290 
291         let mut timeout_args = vec![format!("{}", time_left.as_secs()), cmd.to_string()];
292         timeout_args.extend_from_slice(args);
293 
294         info!("       -- timeout {}", &timeout_args.join(" "));
295         let output = std::process::Command::new("timeout")
296             .args(timeout_args)
297             .output()
298             .expect("command executed");
299         if output.status.success() {
300             let msg = String::from_utf8(output.stdout)?;
301             info!("       {} {}", output.status, msg);
302             return Ok(msg);
303         }
304 
305         if retry_count > 1 {
306             let update_message = format!("retry attempt {} - {:?}", retry_count, cmd.to_string());
307             progress::update(&update_message)
308         }
309 
310         // error; log and retry if within timeout window
311         info!("       {} {:?}", output.status, String::from_utf8(output.stderr).expect("stderr"));
312         sleep(delay);
313     }
314     bail!("Command failed to execute {}", cmd.to_string());
315 }
316 
update( restart_chooser: &RestartChooser, adb_commands: &HashMap<PathBuf, AdbCommand>, profiler: &mut Profiler, device: &impl Device, should_wait: crate::cli::Wait, ) -> Result<()>317 pub fn update(
318     restart_chooser: &RestartChooser,
319     adb_commands: &HashMap<PathBuf, AdbCommand>,
320     profiler: &mut Profiler,
321     device: &impl Device,
322     should_wait: crate::cli::Wait,
323 ) -> Result<()> {
324     if adb_commands.is_empty() {
325         return Ok(());
326     }
327 
328     let installed_files =
329         adb_commands.keys().map(|p| p.clone().into_os_string().into_string().unwrap()).collect();
330 
331     progress::start("Preparing to update files");
332     prep_for_push(device, should_wait.clone())?;
333     let mut i = 1;
334     time!(
335         for command in adb_commands.values().cloned().sorted_by(&mkdir_comes_first_rm_dfs) {
336             let update_message =
337                 format!("Updating files [{}/{}] {:?}", i, adb_commands.len(), command.args());
338             progress::update(&update_message);
339             device.run_adb_command(&command)?;
340             i += 1;
341         },
342         profiler.adb_cmds
343     );
344     progress::stop();
345     println!(" * Update succeeded!");
346     println!();
347 
348     let rtype = restart_type(restart_chooser, &installed_files);
349     profiler.restart_type = format!("{:?}", rtype);
350     match rtype {
351         RestartType::Reboot => time!(device.reboot(), profiler.restart),
352         RestartType::SoftRestart => time!(device.soft_restart(), profiler.restart),
353         RestartType::None => {
354             tracing::debug!("No restart command");
355             return Ok(());
356         }
357     }?;
358 
359     if should_wait.into() {
360         device.wait(profiler)?;
361     }
362     Ok(())
363 }
364 
365 /// Common command to prepare a device to receive new files.
366 /// Always: `exec-out stop`
367 /// Always: `remount`
368 ///  # A remount may not be needed but doesn't slow things down.
369 /// If `should_wait`: Set the system property sys.boot_completed to 0.
370 ///  # A reboot would do this anyway, but it doesn't hurt if we do it too.
371 ///  # We poll for that property to be set back to 1.
372 ///  # Both reboot and exec-out start will set it back to 1 when the
373 ///  # system has booted and is ready to receive commands and run tests.
prep_for_push(device: &impl Device, should_wait: crate::cli::Wait) -> Result<()>374 fn prep_for_push(device: &impl Device, should_wait: crate::cli::Wait) -> Result<()> {
375     device.run_raw_adb_command(&split_string("exec-out stop"))?;
376     // We seem to need a remount after reboots to make the system writable.
377     device.run_raw_adb_command(&split_string("remount"))?;
378     // Set the prop to the empty string so our "-z" check in wait works.
379     if should_wait.into() {
380         device.run_raw_adb_command(&[
381             "exec-out".to_string(),
382             "setprop".to_string(),
383             "sys.boot_completed".to_string(),
384             "".to_string(),
385         ])?;
386     }
387     Ok(())
388 }
389 
390 // 1) Ensure mkdir comes before other commands.
391 // 2) Do removes as a depth-first-search so we clean children before parents.
392 // 3) Sort rm before other commands, but it shouldn't matter.
393 // 4) Remove files before dirs.
394 //    We would never remove a file or directory we are pushing to.
mkdir_comes_first_rm_dfs(a: &AdbCommand, b: &AdbCommand) -> Ordering395 fn mkdir_comes_first_rm_dfs(a: &AdbCommand, b: &AdbCommand) -> Ordering {
396     // Neither is mkdir
397     if !a.is_mkdir() && !b.is_mkdir() {
398         // Sort rm's with files before their parents.
399         let a_cmd = a.args().join(" ");
400         let b_cmd = b.args().join(" ");
401 
402         if a.is_rm() && b.is_rm() {
403             // This also sorts files before dirs because of the "-rf" added to dirs.
404             return b_cmd.cmp(&a_cmd);
405         }
406         if a.is_rm() {
407             return Ordering::Less;
408         }
409         if b.is_rm() {
410             return Ordering::Greater;
411         }
412 
413         // Sort everything by the args.
414         return a_cmd.cmp(&b_cmd);
415     }
416     // If both mkdir:
417     //  Just compare the path, parents will come before subdirs.
418     if a.is_mkdir() && b.is_mkdir() {
419         return a.device_path().cmp(b.device_path());
420     }
421     if a.is_mkdir() {
422         return Ordering::Less;
423     }
424     if b.is_mkdir() {
425         return Ordering::Greater;
426     }
427     Ordering::Equal
428 }
429 
430 #[cfg(test)]
431 mod tests {
432     use super::*;
433     use crate::commands::AdbAction;
434     use anyhow::{bail, Result};
435     use core::cmp::Ordering;
436     use std::time::Duration;
437 
438     // Igoring the tests so they don't cause delays in CI, but can still be run by hand.
439     #[ignore]
440     #[test]
timeout_returns_when_process_returns() -> Result<()>441     fn timeout_returns_when_process_returns() -> Result<()> {
442         let timeout = Duration::from_secs(5);
443         let sleep_args = &["3".to_string()];
444         let output = run_process_with_retry_until_timeout("sleep", sleep_args, timeout);
445         match output {
446             Ok(_) => Ok(()),
447             _ => bail!("Expected an ok status code"),
448         }
449     }
450 
451     #[ignore]
452     #[test]
timeout_exits_when_timeout_hit() -> Result<()>453     fn timeout_exits_when_timeout_hit() -> Result<()> {
454         let timeout = Duration::from_secs(5);
455         let sleep_args = &["7".to_string()];
456         let start_time = Instant::now();
457         let output = run_process_with_retry_until_timeout("sleep", sleep_args, timeout);
458 
459         // smoke test to make sure process ran longer then timeout.
460         let duration = start_time.elapsed();
461         assert!(
462             duration > timeout,
463             "Expected process to take longer then timeout. Elapsed: {:?}, Timeout: {:?}",
464             duration,
465             timeout
466         );
467 
468         match output {
469             Ok(_) => bail!("Expected error status code"),
470             _ => Ok(()),
471         }
472     }
473 
474     #[ignore]
475     #[test]
timeout_deals_with_process_errors() -> Result<()>476     fn timeout_deals_with_process_errors() -> Result<()> {
477         let timeout = Duration::from_secs(5);
478         let sleep_args = &["--bad-arg".to_string(), "7".to_string()];
479         // Add a bad arg so the process we run errs out.
480         let output = run_process_with_retry_until_timeout("sleep", sleep_args, timeout);
481         match output {
482             Ok(_) => bail!("Expected error status code"),
483             _ => Ok(()),
484         }
485     }
486 
487     #[ignore]
488     #[test]
reboot_wait() -> Result<()>489     fn reboot_wait() -> Result<()> {
490         let timeout = Duration::from_secs(5);
491         let sleep_args = &["--bad-arg".to_string(), "7".to_string()];
492         // Add a bad arg so the process we run errs out.
493         let output = run_process_with_retry_until_timeout("sleep", sleep_args, timeout);
494         match output {
495             Ok(_) => bail!("Expected error status code"),
496             _ => Ok(()),
497         }
498     }
499 
delete_file_cmd(file: &str) -> AdbCommand500     fn delete_file_cmd(file: &str) -> AdbCommand {
501         AdbCommand::from_action(AdbAction::DeleteFile, &PathBuf::from(file))
502     }
503 
delete_dir_cmd(dir: &str) -> AdbCommand504     fn delete_dir_cmd(dir: &str) -> AdbCommand {
505         AdbCommand::from_action(AdbAction::DeleteDir, &PathBuf::from(dir))
506     }
507 
508     #[test]
deeper_rms_come_first()509     fn deeper_rms_come_first() {
510         assert_eq!(
511             Ordering::Less,
512             mkdir_comes_first_rm_dfs(
513                 &delete_file_cmd("dir1/dir2/file1"),
514                 &delete_dir_cmd("dir1/dir2"),
515             )
516         );
517         assert_eq!(
518             Ordering::Greater,
519             mkdir_comes_first_rm_dfs(
520                 &delete_dir_cmd("dir1/dir2"),
521                 &delete_file_cmd("dir1/dir2/file1"),
522             )
523         );
524         assert_eq!(
525             Ordering::Less,
526             mkdir_comes_first_rm_dfs(
527                 &delete_dir_cmd("dir1/dir2/dir3"),
528                 &delete_dir_cmd("dir1/dir2"),
529             )
530         );
531         assert_eq!(
532             Ordering::Greater,
533             mkdir_comes_first_rm_dfs(
534                 &delete_dir_cmd("dir1/dir2"),
535                 &delete_dir_cmd("dir1/dir2/dir3"),
536             )
537         );
538     }
539     #[test]
rm_all_files_before_dirs()540     fn rm_all_files_before_dirs() {
541         assert_eq!(
542             Ordering::Less,
543             mkdir_comes_first_rm_dfs(
544                 &delete_file_cmd("system/app/FakeOemFeatures/FakeOemFeatures.apk"),
545                 &delete_dir_cmd("system/app/FakeOemFeatures"),
546             )
547         );
548         assert_eq!(
549             Ordering::Greater,
550             mkdir_comes_first_rm_dfs(
551                 &delete_dir_cmd("system/app/FakeOemFeatures"),
552                 &delete_file_cmd("system/app/FakeOemFeatures/FakeOemFeatures.apk"),
553             )
554         );
555     }
556 
557     #[test]
sort_many()558     fn sort_many() {
559         let dir = |d| AdbCommand::from_action(AdbAction::DeleteDir, &PathBuf::from(d));
560         let file = |d| AdbCommand::from_action(AdbAction::DeleteFile, &PathBuf::from(d));
561         let mut adb_commands: Vec<AdbCommand> = vec![
562             file("system/STALE_FILE"),
563             dir("system/bin/dir1/STALE_DIR"),
564             file("system/bin/dir1/STALE_DIR/stalefile1"),
565             file("system/bin/dir1/STALE_DIR/stalefile2"),
566         ];
567 
568         adb_commands.sort_by(&mkdir_comes_first_rm_dfs);
569         assert_eq!(
570             // Expected sorted order, deepest first.
571             // files before dirs.
572             vec![
573                 file("system/bin/dir1/STALE_DIR/stalefile2"),
574                 file("system/bin/dir1/STALE_DIR/stalefile1"),
575                 file("system/STALE_FILE"),
576                 dir("system/bin/dir1/STALE_DIR"),
577             ],
578             adb_commands
579         );
580     }
581 
582     #[test]
583     // NOTE: This test assumes we have adb in our path.
adb_command_success()584     fn adb_command_success() {
585         // Use real device for device tests.
586         let result = RealDevice::new(None)
587             .run_raw_adb_command(&["version".to_string()])
588             .expect("Error running command");
589         assert!(
590             result.contains("Android Debug Bridge version"),
591             "Expected a version string, but received:\n {result}"
592         );
593     }
594 
595     #[test]
adb_command_failure()596     fn adb_command_failure() {
597         let result = RealDevice::new(None).run_raw_adb_command(&["improper_cmd".to_string()]);
598         if result.is_ok() {
599             panic!("Did not expect to succeed");
600         }
601 
602         let expected_str =
603             "adb error, Exited with status code: 1  adb: unknown command improper_cmd\n";
604         assert_eq!(expected_str, format!("{:?}", result.unwrap_err()));
605     }
606 
607     #[test]
package_manager_output_parsing()608     fn package_manager_output_parsing() {
609         let actual_output = r#"
610 package:/product/app/Browser2/Browser2.apk=org.chromium.webview_shell
611 package:/apex/com.google.aosp_cf_phone.rros/overlay/cuttlefish_overlay_frameworks_base_core.apk=android.cuttlefish.overlay
612 package:/data/app/~~f_ZzeFPKma_EfXRklotqFg==/com.android.shell-hrjEOvqv3dAautKdfqeAEA==/base.apk=com.android.shell
613 package:/apex/com.google.aosp_cf_phone.rros/overlay/cuttlefish_phone_overlay_frameworks_base_core.apk=android.cuttlefish.phone.overlay
614 "#;
615         let mut expected: HashSet<String> = HashSet::new();
616         expected.insert("com.android.shell".to_string());
617         assert_eq!(expected, apks_from_pm_list_output(actual_output));
618     }
619 }
620