Source code
Revision control
Copy as Markdown
Other Tools
/* 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
// 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()
);
}