diff options
Diffstat (limited to '')
-rw-r--r-- | testing/mozbase/rust/mozdevice/src/test.rs | 760 |
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() + ); +} |