summaryrefslogtreecommitdiffstats
path: root/testing/mozbase/rust/mozdevice/src/test.rs
diff options
context:
space:
mode:
Diffstat (limited to 'testing/mozbase/rust/mozdevice/src/test.rs')
-rw-r--r--testing/mozbase/rust/mozdevice/src/test.rs760
1 files changed, 760 insertions, 0 deletions
diff --git a/testing/mozbase/rust/mozdevice/src/test.rs b/testing/mozbase/rust/mozdevice/src/test.rs
new file mode 100644
index 0000000000..b4173e3649
--- /dev/null
+++ b/testing/mozbase/rust/mozdevice/src/test.rs
@@ -0,0 +1,760 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Currently the mozdevice API is not safe for multiple requests at the same
+// time. It is recommended to run each of the unit tests on its own. Also adb
+// specific tests cannot be run in CI yet. To check those locally, also run
+// the ignored tests.
+//
+// Use the following command to accomplish that:
+//
+// $ cargo test -- --ignored --test-threads=1
+
+use crate::*;
+
+use std::collections::BTreeSet;
+use std::panic;
+use std::path::PathBuf;
+use tempfile::{tempdir, TempDir};
+
+#[test]
+fn read_length_from_valid_string() {
+ fn test(message: &str) -> Result<usize> {
+ read_length(&mut io::BufReader::new(message.as_bytes()))
+ }
+
+ assert_eq!(test("0000").unwrap(), 0);
+ assert_eq!(test("0001").unwrap(), 1);
+ assert_eq!(test("000F").unwrap(), 15);
+ assert_eq!(test("00FF").unwrap(), 255);
+ assert_eq!(test("0FFF").unwrap(), 4095);
+ assert_eq!(test("FFFF").unwrap(), 65535);
+
+ assert_eq!(test("FFFF0").unwrap(), 65535);
+}
+
+#[test]
+fn read_length_from_invalid_string() {
+ fn test(message: &str) -> Result<usize> {
+ read_length(&mut io::BufReader::new(message.as_bytes()))
+ }
+
+ test("").expect_err("empty string");
+ test("G").expect_err("invalid hex character");
+ test("-1").expect_err("negative number");
+ test("000").expect_err("shorter than 4 bytes");
+}
+
+#[test]
+fn encode_message_with_valid_string() {
+ assert_eq!(encode_message("").unwrap(), "0000".to_string());
+ assert_eq!(encode_message("a").unwrap(), "0001a".to_string());
+ assert_eq!(
+ encode_message(&"a".repeat(15)).unwrap(),
+ format!("000F{}", "a".repeat(15))
+ );
+ assert_eq!(
+ encode_message(&"a".repeat(255)).unwrap(),
+ format!("00FF{}", "a".repeat(255))
+ );
+ assert_eq!(
+ encode_message(&"a".repeat(4095)).unwrap(),
+ format!("0FFF{}", "a".repeat(4095))
+ );
+ assert_eq!(
+ encode_message(&"a".repeat(65535)).unwrap(),
+ format!("FFFF{}", "a".repeat(65535))
+ );
+}
+
+#[test]
+fn encode_message_with_invalid_string() {
+ encode_message(&"a".repeat(65536)).expect_err("string lengths exceeds 4 bytes");
+}
+
+fn run_device_test<F>(test: F)
+where
+ F: FnOnce(&Device, &TempDir, &UnixPath) + panic::UnwindSafe,
+{
+ let host = Host {
+ ..Default::default()
+ };
+ let device = host
+ .device_or_default::<String>(None, AndroidStorageInput::Auto)
+ .expect("device_or_default");
+
+ let tmp_dir = tempdir().expect("create temp dir");
+ let response = device
+ .execute_host_shell_command("echo $EXTERNAL_STORAGE")
+ .unwrap();
+ let mut test_root = UnixPathBuf::from(response.trim_end_matches('\n'));
+ test_root.push("mozdevice");
+
+ let _ = device.remove(&test_root);
+
+ let result = panic::catch_unwind(|| test(&device, &tmp_dir, &test_root));
+
+ let _ = device.kill_forward_all_ports();
+ // let _ = device.kill_reverse_all_ports();
+
+ assert!(result.is_ok())
+}
+
+#[test]
+#[ignore]
+fn host_features() {
+ let host = Host {
+ ..Default::default()
+ };
+
+ let set = host.features::<BTreeSet<_>>().expect("to query features");
+ assert!(set.contains("cmd"));
+ assert!(set.contains("shell_v2"));
+}
+
+#[test]
+#[ignore]
+fn host_devices() {
+ let host = Host {
+ ..Default::default()
+ };
+
+ let set: BTreeSet<_> = host.devices().expect("to query devices");
+ assert_eq!(1, set.len());
+}
+
+#[test]
+#[ignore]
+fn host_device_or_default() {
+ let host = Host {
+ ..Default::default()
+ };
+
+ let devices: Vec<_> = host.devices().expect("to query devices");
+ let expected_device = devices.first().expect("found a device");
+
+ let device = host
+ .device_or_default::<String>(Some(&expected_device.serial), AndroidStorageInput::App)
+ .expect("connected device with serial");
+ assert_eq!(device.run_as_package, None);
+ assert_eq!(device.serial, expected_device.serial);
+ assert!(device.tempfile.starts_with("/data/local/tmp"));
+}
+
+#[test]
+#[ignore]
+fn host_device_or_default_invalid_serial() {
+ let host = Host {
+ ..Default::default()
+ };
+
+ host.device_or_default::<String>(Some(&"foobar".to_owned()), AndroidStorageInput::Auto)
+ .expect_err("invalid serial");
+}
+
+#[test]
+#[ignore]
+fn host_device_or_default_no_serial() {
+ let host = Host {
+ ..Default::default()
+ };
+
+ let devices: Vec<_> = host.devices().expect("to query devices");
+ let expected_device = devices.first().expect("found a device");
+
+ let device = host
+ .device_or_default::<String>(None, AndroidStorageInput::Auto)
+ .expect("connected device with serial");
+ assert_eq!(device.serial, expected_device.serial);
+}
+
+#[test]
+#[ignore]
+fn host_device_or_default_storage_as_app() {
+ let host = Host {
+ ..Default::default()
+ };
+
+ let device = host
+ .device_or_default::<String>(None, AndroidStorageInput::App)
+ .expect("connected device");
+ assert_eq!(device.storage, AndroidStorage::App);
+}
+
+#[test]
+#[ignore]
+fn host_device_or_default_storage_as_auto() {
+ let host = Host {
+ ..Default::default()
+ };
+
+ let device = host
+ .device_or_default::<String>(None, AndroidStorageInput::Auto)
+ .expect("connected device");
+ assert_eq!(device.storage, AndroidStorage::Sdcard);
+}
+
+#[test]
+#[ignore]
+fn host_device_or_default_storage_as_internal() {
+ let host = Host {
+ ..Default::default()
+ };
+
+ let device = host
+ .device_or_default::<String>(None, AndroidStorageInput::Internal)
+ .expect("connected device");
+ assert_eq!(device.storage, AndroidStorage::Internal);
+}
+
+#[test]
+#[ignore]
+fn host_device_or_default_storage_as_sdcard() {
+ let host = Host {
+ ..Default::default()
+ };
+
+ let device = host
+ .device_or_default::<String>(None, AndroidStorageInput::Sdcard)
+ .expect("connected device");
+ assert_eq!(device.storage, AndroidStorage::Sdcard);
+}
+
+#[test]
+#[ignore]
+fn device_shell_command() {
+ run_device_test(|device: &Device, _: &TempDir, _: &UnixPath| {
+ assert_eq!(
+ "Linux\n",
+ device
+ .execute_host_shell_command("uname")
+ .expect("to have shell output")
+ );
+ });
+}
+
+#[test]
+#[ignore]
+fn device_forward_port_hardcoded() {
+ run_device_test(|device: &Device, _: &TempDir, _: &UnixPath| {
+ assert_eq!(
+ 3035,
+ device
+ .forward_port(3035, 3036)
+ .expect("forwarded local port")
+ );
+ // TODO: check with forward --list
+ });
+}
+
+// #[test]
+// #[ignore]
+// TODO: "adb server response to `forward tcp:0 ...` was not a u16: \"000559464\"")
+// fn device_forward_port_system_allocated() {
+// run_device_test(|device: &Device, _: &TempDir, _: &UnixPath| {
+// let local_port = device.forward_port(0, 3037).expect("local_port");
+// assert_ne!(local_port, 0);
+// // TODO: check with forward --list
+// });
+// }
+
+#[test]
+#[ignore]
+fn device_kill_forward_port_no_forwarded_port() {
+ run_device_test(|device: &Device, _: &TempDir, _: &UnixPath| {
+ device
+ .kill_forward_port(3038)
+ .expect_err("adb error: listener 'tcp:3038' ");
+ });
+}
+
+#[test]
+#[ignore]
+fn device_kill_forward_port_twice() {
+ run_device_test(|device: &Device, _: &TempDir, _: &UnixPath| {
+ let local_port = device
+ .forward_port(3039, 3040)
+ .expect("forwarded local port");
+ assert_eq!(local_port, 3039);
+ // TODO: check with forward --list
+ device
+ .kill_forward_port(local_port)
+ .expect("to remove forwarded port");
+ device
+ .kill_forward_port(local_port)
+ .expect_err("adb error: listener 'tcp:3039' ");
+ });
+}
+
+#[test]
+#[ignore]
+fn device_kill_forward_all_ports_no_forwarded_port() {
+ run_device_test(|device: &Device, _: &TempDir, _: &UnixPath| {
+ device
+ .kill_forward_all_ports()
+ .expect("to not fail for no forwarded ports");
+ });
+}
+
+#[test]
+#[ignore]
+fn device_kill_forward_all_ports_twice() {
+ run_device_test(|device: &Device, _: &TempDir, _: &UnixPath| {
+ let local_port1 = device
+ .forward_port(3039, 3040)
+ .expect("forwarded local port");
+ assert_eq!(local_port1, 3039);
+ let local_port2 = device
+ .forward_port(3041, 3042)
+ .expect("forwarded local port");
+ assert_eq!(local_port2, 3041);
+ // TODO: check with forward --list
+ device
+ .kill_forward_all_ports()
+ .expect("to remove all forwarded ports");
+ device
+ .kill_forward_all_ports()
+ .expect("to not fail for no forwarded ports");
+ });
+}
+
+#[test]
+#[ignore]
+fn device_reverse_port_hardcoded() {
+ run_device_test(|device: &Device, _: &TempDir, _: &UnixPath| {
+ assert_eq!(4035, device.reverse_port(4035, 4036).expect("remote_port"));
+ // TODO: check with reverse --list
+ });
+}
+
+// #[test]
+// #[ignore]
+// TODO: No adb response: ParseInt(ParseIntError { kind: Empty })
+// fn device_reverse_port_system_allocated() {
+// run_device_test(|device: &Device, _: &TempDir, _: &UnixPath| {
+// let reverse_port = device.reverse_port(0, 4037).expect("remote port");
+// assert_ne!(reverse_port, 0);
+// // TODO: check with reverse --list
+// });
+// }
+
+#[test]
+#[ignore]
+fn device_kill_reverse_port_no_reverse_port() {
+ run_device_test(|device: &Device, _: &TempDir, _: &UnixPath| {
+ device
+ .kill_reverse_port(4038)
+ .expect_err("listener 'tcp:4038' not found");
+ });
+}
+
+// #[test]
+// #[ignore]
+// TODO: "adb error: adb server response did not contain expected hexstring length: \"\""
+// fn device_kill_reverse_port_twice() {
+// run_device_test(|device: &Device, _: &TempDir, _: &UnixPath| {
+// let remote_port = device
+// .reverse_port(4039, 4040)
+// .expect("reversed local port");
+// assert_eq!(remote_port, 4039);
+// // TODO: check with reverse --list
+// device
+// .kill_reverse_port(remote_port)
+// .expect("to remove reverse port");
+// device
+// .kill_reverse_port(remote_port)
+// .expect_err("listener 'tcp:4039' not found");
+// });
+// }
+
+#[test]
+#[ignore]
+fn device_kill_reverse_all_ports_no_reversed_port() {
+ run_device_test(|device: &Device, _: &TempDir, _: &UnixPath| {
+ device
+ .kill_reverse_all_ports()
+ .expect("to not fail for no reversed ports");
+ });
+}
+
+#[test]
+#[ignore]
+fn device_kill_reverse_all_ports_twice() {
+ run_device_test(|device: &Device, _: &TempDir, _: &UnixPath| {
+ let local_port1 = device
+ .forward_port(4039, 4040)
+ .expect("forwarded local port");
+ assert_eq!(local_port1, 4039);
+ let local_port2 = device
+ .forward_port(4041, 4042)
+ .expect("forwarded local port");
+ assert_eq!(local_port2, 4041);
+ // TODO: check with reverse --list
+ device
+ .kill_reverse_all_ports()
+ .expect("to remove all reversed ports");
+ device
+ .kill_reverse_all_ports()
+ .expect("to not fail for no reversed ports");
+ });
+}
+
+#[test]
+#[ignore]
+fn device_push_pull_text_file() {
+ run_device_test(
+ |device: &Device, _: &TempDir, remote_root_path: &UnixPath| {
+ let content = "test";
+ let remote_path = remote_root_path.join("foo.txt");
+
+ device
+ .push(
+ &mut io::BufReader::new(content.as_bytes()),
+ &remote_path,
+ 0o777,
+ )
+ .expect("file has been pushed");
+
+ let file_content = device
+ .execute_host_shell_command(&format!("cat {}", remote_path.display()))
+ .expect("host shell command for 'cat' to succeed");
+
+ assert_eq!(file_content, content);
+
+ // And as second step pull it off the device.
+ let mut buffer = Vec::new();
+ device
+ .pull(&remote_path, &mut buffer)
+ .expect("file has been pulled");
+ assert_eq!(buffer, content.as_bytes());
+ },
+ );
+}
+
+#[test]
+#[ignore]
+fn device_push_pull_large_binary_file() {
+ run_device_test(
+ |device: &Device, _: &TempDir, remote_root_path: &UnixPath| {
+ let remote_path = remote_root_path.join("foo.binary");
+
+ let mut content = Vec::new();
+
+ // Needs to be larger than 64kB to test multiple chunks.
+ for i in 0..100000u32 {
+ content.push('0' as u8 + (i % 10) as u8);
+ }
+
+ device
+ .push(
+ &mut std::io::Cursor::new(content.clone()),
+ &remote_path,
+ 0o777,
+ )
+ .expect("large file has been pushed");
+
+ let output = device
+ .execute_host_shell_command(&format!("ls -l {}", remote_path.display()))
+ .expect("host shell command for 'ls' to succeed");
+
+ assert!(output.contains(remote_path.to_str().unwrap()));
+
+ let mut buffer = Vec::new();
+
+ device
+ .pull(&remote_path, &mut buffer)
+ .expect("large binary file has been pulled");
+ assert_eq!(buffer, content);
+ },
+ );
+}
+
+#[test]
+#[ignore]
+fn device_push_permission() {
+ run_device_test(
+ |device: &Device, _: &TempDir, remote_root_path: &UnixPath| {
+ fn adjust_mode(mode: u32) -> u32 {
+ // Adjust the mode by copying the user permissions to
+ // group and other as indicated in
+ // [send_impl](https://android.googlesource.com/platform/system/core/+/master/adb/daemon/file_sync_service.cpp#516).
+ // This ensures that group and other can both access a
+ // file if the user can access it.
+ let mut m = mode & 0o777;
+ m |= (m >> 3) & 0o070;
+ m |= (m >> 3) & 0o007;
+ m
+ }
+
+ fn get_permissions(mode: u32) -> String {
+ // Convert the mode integer into the string representation
+ // of the mode returned by `ls`. This assumes the object is
+ // a file and not a directory.
+ let mut perms = vec!["-", "r", "w", "x", "r", "w", "x", "r", "w", "x"];
+ let mut bit_pos = 0;
+ while bit_pos < 9 {
+ if (1 << bit_pos) & mode == 0 {
+ perms[9 - bit_pos] = "-"
+ }
+ bit_pos += 1;
+ }
+ perms.concat()
+ }
+ let content = "test";
+ let remote_path = remote_root_path.join("foo.bar");
+
+ // First push the file to the device
+ let modes = vec![0o421, 0o644, 0o666, 0o777];
+ for mode in modes {
+ let adjusted_mode = adjust_mode(mode);
+ let adjusted_perms = get_permissions(adjusted_mode);
+ device
+ .push(
+ &mut io::BufReader::new(content.as_bytes()),
+ &remote_path,
+ mode,
+ )
+ .expect("file has been pushed");
+
+ let output = device
+ .execute_host_shell_command(&format!("ls -l {}", remote_path.display()))
+ .expect("host shell command for 'ls' to succeed");
+
+ assert!(output.contains(remote_path.to_str().unwrap()));
+ assert!(output.starts_with(&adjusted_perms));
+ }
+
+ let output = device
+ .execute_host_shell_command(&format!("ls -ld {}", remote_root_path.display()))
+ .expect("host shell command for 'ls parent' to succeed");
+
+ assert!(output.contains(remote_root_path.to_str().unwrap()));
+ assert!(output.starts_with("drwxrwxrwx"));
+ },
+ );
+}
+
+#[test]
+#[ignore]
+fn device_pull_fails_for_missing_file() {
+ run_device_test(
+ |device: &Device, _: &TempDir, remote_root_path: &UnixPath| {
+ let mut buffer = Vec::new();
+
+ device
+ .pull(&remote_root_path.join("missing"), &mut buffer)
+ .expect_err("missing file should not be pulled");
+ },
+ );
+}
+
+#[test]
+#[ignore]
+fn device_push_and_list_dir() {
+ run_device_test(
+ |device: &Device, tmp_dir: &TempDir, remote_root_path: &UnixPath| {
+ let files = ["foo1.bar", "foo2.bar", "bar/foo3.bar", "bar/more/foo3.bar"];
+
+ for file in files.iter() {
+ let path = tmp_dir.path().join(Path::new(file));
+ let _ = std::fs::create_dir_all(path.parent().unwrap());
+
+ let f = File::create(path).expect("to create file");
+ let mut f = io::BufWriter::new(f);
+ f.write_all(file.as_bytes()).expect("to write data");
+ }
+
+ device
+ .push_dir(tmp_dir.path(), &remote_root_path, 0o777)
+ .expect("to push_dir");
+
+ for file in files.iter() {
+ let path = append_components(remote_root_path, Path::new(file)).unwrap();
+ let output = device
+ .execute_host_shell_command(&format!("ls {}", path.display()))
+ .expect("host shell command for 'ls' to succeed");
+
+ assert!(output.contains(path.to_str().unwrap()));
+ }
+
+ let mut listings = device.list_dir(&remote_root_path).expect("to list_dir");
+ listings.sort();
+ assert_eq!(
+ listings,
+ vec![
+ RemoteDirEntry {
+ depth: 0,
+ name: "foo1.bar".to_string(),
+ metadata: RemoteMetadata::RemoteFile(RemoteFileMetadata {
+ mode: 0b110110000,
+ size: 8
+ })
+ },
+ RemoteDirEntry {
+ depth: 0,
+ name: "foo2.bar".to_string(),
+ metadata: RemoteMetadata::RemoteFile(RemoteFileMetadata {
+ mode: 0b110110000,
+ size: 8
+ })
+ },
+ RemoteDirEntry {
+ depth: 0,
+ name: "bar".to_string(),
+ metadata: RemoteMetadata::RemoteDir
+ },
+ RemoteDirEntry {
+ depth: 1,
+ name: "bar/foo3.bar".to_string(),
+ metadata: RemoteMetadata::RemoteFile(RemoteFileMetadata {
+ mode: 0b110110000,
+ size: 12
+ })
+ },
+ RemoteDirEntry {
+ depth: 1,
+ name: "bar/more".to_string(),
+ metadata: RemoteMetadata::RemoteDir
+ },
+ RemoteDirEntry {
+ depth: 2,
+ name: "bar/more/foo3.bar".to_string(),
+ metadata: RemoteMetadata::RemoteFile(RemoteFileMetadata {
+ mode: 0b110110000,
+ size: 17
+ })
+ }
+ ]
+ );
+ },
+ );
+}
+
+#[test]
+#[ignore]
+fn device_push_and_pull_dir() {
+ run_device_test(
+ |device: &Device, tmp_dir: &TempDir, remote_root_path: &UnixPath| {
+ let files = ["foo1.bar", "foo2.bar", "bar/foo3.bar", "bar/more/foo3.bar"];
+
+ let src_dir = tmp_dir.path().join(Path::new("src"));
+ let dest_dir = tmp_dir.path().join(Path::new("src"));
+
+ for file in files.iter() {
+ let path = src_dir.join(Path::new(file));
+ let _ = std::fs::create_dir_all(path.parent().unwrap());
+
+ let f = File::create(path).expect("to create file");
+ let mut f = io::BufWriter::new(f);
+ f.write_all(file.as_bytes()).expect("to write data");
+ }
+
+ device
+ .push_dir(&src_dir, &remote_root_path, 0o777)
+ .expect("to push_dir");
+
+ device
+ .pull_dir(remote_root_path, &dest_dir)
+ .expect("to pull_dir");
+
+ for file in files.iter() {
+ let path = dest_dir.join(Path::new(file));
+ let mut f = File::open(path).expect("to open file");
+ let mut buf = String::new();
+ f.read_to_string(&mut buf).expect("to read content");
+ assert_eq!(buf, *file);
+ }
+ },
+ )
+}
+
+#[test]
+#[ignore]
+fn device_push_and_list_dir_flat() {
+ run_device_test(
+ |device: &Device, tmp_dir: &TempDir, remote_root_path: &UnixPath| {
+ let content = "test";
+
+ let files = [
+ PathBuf::from("foo1.bar"),
+ PathBuf::from("foo2.bar"),
+ PathBuf::from("bar").join("foo3.bar"),
+ ];
+
+ for file in files.iter() {
+ let path = tmp_dir.path().join(&file);
+ let _ = std::fs::create_dir_all(path.parent().unwrap());
+
+ let f = File::create(path).expect("to create file");
+ let mut f = io::BufWriter::new(f);
+ f.write_all(content.as_bytes()).expect("to write data");
+ }
+
+ device
+ .push_dir(tmp_dir.path(), &remote_root_path, 0o777)
+ .expect("to push_dir");
+
+ for file in files.iter() {
+ let path = append_components(remote_root_path, file).unwrap();
+ let output = device
+ .execute_host_shell_command(&format!("ls {}", path.display()))
+ .expect("host shell command for 'ls' to succeed");
+
+ assert!(output.contains(path.to_str().unwrap()));
+ }
+
+ let mut listings = device
+ .list_dir_flat(&remote_root_path, 7, "prefix".to_string())
+ .expect("to list_dir_flat");
+ listings.sort();
+ assert_eq!(
+ listings,
+ vec![
+ RemoteDirEntry {
+ depth: 7,
+ metadata: RemoteMetadata::RemoteFile(RemoteFileMetadata {
+ mode: 0b110110000,
+ size: 4
+ }),
+ name: "prefix/foo1.bar".to_string(),
+ },
+ RemoteDirEntry {
+ depth: 7,
+ metadata: RemoteMetadata::RemoteFile(RemoteFileMetadata {
+ mode: 0b110110000,
+ size: 4
+ }),
+ name: "prefix/foo2.bar".to_string(),
+ },
+ RemoteDirEntry {
+ depth: 7,
+ metadata: RemoteMetadata::RemoteDir,
+ name: "prefix/bar".to_string(),
+ },
+ ]
+ );
+ },
+ );
+}
+
+#[test]
+fn format_own_device_error_types() {
+ assert_eq!(
+ format!("{}", DeviceError::InvalidStorage),
+ "Invalid storage".to_string()
+ );
+ assert_eq!(
+ format!("{}", DeviceError::MissingPackage),
+ "Missing package".to_string()
+ );
+ assert_eq!(
+ format!("{}", DeviceError::MultipleDevices),
+ "Multiple Android devices online".to_string()
+ );
+
+ assert_eq!(
+ format!("{}", DeviceError::Adb("foo".to_string())),
+ "foo".to_string()
+ );
+}