Bug 1429511 - Move Rust port of mozrunner to central. r?ahal
This moves the Rust crate mozrunner into central from GitHub.
The old repository will be graveyarded:
https://github.com/jgraham/rust_mozrunner
The git history is not considered important, hence this does not
overlay that onto central like we did for testing/geckodriver and
testing/webdriver.
MozReview-Commit-ID: J4ZYdow2Lkw
--- a/.gitignore
+++ b/.gitignore
@@ -99,16 +99,20 @@ testing/web-platform/products/
# Android Gradle artifacts.
mobile/android/gradle/.gradle
# XCode project cruft
/*.xcodeproj/
embedding/ios/GeckoEmbed/GeckoEmbed.xcodeproj/project.xcworkspace/xcuserdata
embedding/ios/GeckoEmbed/GeckoEmbed.xcodeproj/xcuserdata
+# Rust port of mozbase are libraries
+testing/mozbase/rust/*/target
+testing/mozbase/rust/*/Cargo.lock
+
# Ignore mozharness execution files
testing/mozharness/.tox/
testing/mozharness/build/
testing/mozharness/logs/
testing/mozharness/.coverage
testing/mozharness/nosetests.xml
# Ignore ESLint node_modules
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/rust/mozrunner/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "mozrunner"
+version = "0.5.0"
+authors = ["Mozilla Tools and Automation <auto-tools@mozilla.com>"]
+description = "Library for starting Firefox binaries."
+repository = "https://github.com/jgraham/rust_mozrunner"
+license = "MPL-2.0"
+
+[dependencies]
+log = "0.3"
+mozprofile = "0.3"
+
+[target.'cfg(target_os = "windows")'.dependencies]
+winreg = "0.3.5"
+
+[[bin]]
+name = "firefox-default-path"
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/rust/mozrunner/moz.build
@@ -0,0 +1,6 @@
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Testing", "Mozbase Rust")
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/rust/mozrunner/src/bin/firefox-default-path.rs
@@ -0,0 +1,17 @@
+extern crate mozrunner;
+
+use mozrunner::runner::platform;
+use std::io::Write;
+
+fn main() {
+ let (path, code) = platform::firefox_default_path()
+ .map(|x| (x.to_string_lossy().into_owned(), 0))
+ .unwrap_or(("Firefox binary not found".to_owned(), 1));
+
+ let mut writer: Box<Write> = match code {
+ 0 => Box::new(std::io::stdout()),
+ _ => Box::new(std::io::stderr())
+ };
+ writeln!(&mut writer, "{}", &*path).unwrap();
+ std::process::exit(code);
+}
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/rust/mozrunner/src/lib.rs
@@ -0,0 +1,8 @@
+#[macro_use] extern crate log;
+extern crate mozprofile;
+#[cfg(target_os = "windows")]
+extern crate winreg;
+
+pub mod runner;
+
+pub use runner::platform::firefox_default_path;
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/rust/mozrunner/src/runner.rs
@@ -0,0 +1,387 @@
+use mozprofile::prefreader::PrefReaderError;
+use mozprofile::profile::Profile;
+use std::ascii::AsciiExt;
+use std::collections::HashMap;
+use std::convert::From;
+use std::env;
+use std::error::Error;
+use std::fmt;
+use std::io::{Result as IoResult, Error as IoError, ErrorKind};
+use std::path::{Path, PathBuf};
+use std::process;
+use std::process::{Command, Stdio};
+
+pub trait Runner {
+ fn args(&mut self) -> &mut Vec<String>;
+ fn build_command(&self, &mut Command);
+ fn envs(&mut self) -> &mut HashMap<String, String>;
+ fn is_running(&mut self) -> bool;
+ fn start(&mut self) -> Result<(), RunnerError>;
+ fn status(&mut self) -> IoResult<Option<process::ExitStatus>>;
+ fn stop(&mut self) -> IoResult<Option<process::ExitStatus>>;
+}
+
+#[derive(Debug)]
+pub enum RunnerError {
+ Io(IoError),
+ PrefReader(PrefReaderError),
+}
+
+impl fmt::Display for RunnerError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.description().fmt(f)
+ }
+}
+
+impl Error for RunnerError {
+ fn description(&self) -> &str {
+ match *self {
+ RunnerError::Io(ref err) => {
+ match err.kind() {
+ ErrorKind::NotFound => "no such file or directory",
+ _ => err.description(),
+ }
+ }
+ RunnerError::PrefReader(ref err) => err.description(),
+ }
+ }
+
+ fn cause(&self) -> Option<&Error> {
+ Some(match *self {
+ RunnerError::Io(ref err) => err as &Error,
+ RunnerError::PrefReader(ref err) => err as &Error,
+ })
+ }
+}
+
+impl From<IoError> for RunnerError {
+ fn from(value: IoError) -> RunnerError {
+ RunnerError::Io(value)
+ }
+}
+
+impl From<PrefReaderError> for RunnerError {
+ fn from(value: PrefReaderError) -> RunnerError {
+ RunnerError::PrefReader(value)
+ }
+}
+
+#[derive(Debug)]
+pub struct FirefoxRunner {
+ pub binary: PathBuf,
+ args: Vec<String>,
+ envs: HashMap<String, String>,
+ process: Option<process::Child>,
+ pub profile: Profile
+}
+
+impl FirefoxRunner {
+ pub fn new(binary: &Path, profile: Option<Profile>) -> IoResult<FirefoxRunner> {
+ let prof = match profile {
+ Some(p) => p,
+ None => try!(Profile::new(None))
+ };
+
+ let mut envs = HashMap::new();
+ envs.insert("MOZ_NO_REMOTE".to_string(), "1".to_string());
+ envs.insert("NO_EM_RESTART".to_string(), "1".to_string());
+
+ Ok(FirefoxRunner {
+ binary: binary.to_path_buf(),
+ process: None,
+ args: Vec::new(),
+ envs: envs,
+ profile: prof
+ })
+ }
+}
+
+impl Runner for FirefoxRunner {
+ fn args(&mut self) -> &mut Vec<String> {
+ &mut self.args
+ }
+
+ fn build_command(&self, command: &mut Command) {
+ command
+ .args(&self.args[..])
+ .envs(&self.envs);
+
+ if !self.args.iter().any(|x| is_profile_arg(x)) {
+ command.arg("-profile").arg(&self.profile.path);
+ }
+ command.stdout(Stdio::inherit())
+ .stderr(Stdio::inherit());
+ }
+
+ fn envs(&mut self) -> &mut HashMap<String, String> {
+ &mut self.envs
+ }
+
+ fn is_running(&mut self) -> bool {
+ self.process.is_some() && self.status().unwrap().is_none()
+ }
+
+ fn start(&mut self) -> Result<(), RunnerError> {
+ let mut cmd = Command::new(&self.binary);
+ self.build_command(&mut cmd);
+
+ let prefs = try!(self.profile.user_prefs());
+ try!(prefs.write());
+
+ info!("Running command: {:?}", cmd);
+ let process = try!(cmd.spawn());
+ self.process = Some(process);
+ Ok(())
+ }
+
+ fn status(&mut self) -> IoResult<Option<process::ExitStatus>> {
+ self.process.as_mut().map(|p| p.try_wait()).unwrap_or(Ok(None))
+ }
+
+ fn stop(&mut self) -> IoResult<Option<process::ExitStatus>> {
+ let mut retval = None;
+
+ if let Some(ref mut p) = self.process {
+ try!(p.kill());
+ retval = Some(try!(p.wait()));
+ };
+ Ok(retval)
+ }
+}
+
+fn parse_arg_name(arg: &str) -> Option<&str> {
+ let mut start = 0;
+ let mut end = 0;
+
+ for (i, c) in arg.chars().enumerate() {
+ if i == 0 {
+ if !platform::arg_prefix_char(c) {
+ break;
+ }
+ } else if i == 1 {
+ if name_end_char(c) {
+ break;
+ } else if c != '-' {
+ start = i;
+ end = start + 1;
+ } else {
+ start = i + 1;
+ end = start;
+ }
+ } else {
+ end += 1;
+ if name_end_char(c) {
+ end -= 1;
+ break;
+ }
+ }
+ }
+
+ if start > 0 && end > start {
+ Some(&arg[start..end])
+ } else {
+ None
+ }
+}
+
+fn name_end_char(c: char) -> bool {
+ c == ' ' || c == '='
+}
+
+/// Check if an argument string affects the Firefox profile
+///
+/// Returns a boolean indicating whether a given string
+/// contains one of the `-P`, `-Profile` or `-ProfileManager`
+/// arguments, respecting the various platform-specific conventions.
+pub fn is_profile_arg(arg: &str) -> bool {
+ if let Some(name) = parse_arg_name(arg) {
+ name.eq_ignore_ascii_case("profile") ||
+ name.eq_ignore_ascii_case("p") ||
+ name.eq_ignore_ascii_case("profilemanager")
+ } else {
+ false
+ }
+}
+
+fn find_binary(name: &str) -> Option<PathBuf> {
+ env::var("PATH")
+ .ok()
+ .and_then(|path_env| {
+ for mut path in env::split_paths(&*path_env) {
+ path.push(name);
+ if path.exists() {
+ return Some(path)
+ }
+ }
+ None
+ })
+}
+
+#[cfg(target_os = "linux")]
+pub mod platform {
+ use super::find_binary;
+ use std::path::PathBuf;
+
+ pub fn firefox_default_path() -> Option<PathBuf> {
+ find_binary("firefox")
+ }
+
+ pub fn arg_prefix_char(c: char) -> bool {
+ c == '-'
+ }
+}
+
+#[cfg(target_os = "macos")]
+pub mod platform {
+ use super::find_binary;
+ use std::env;
+ use std::path::PathBuf;
+
+ pub fn firefox_default_path() -> Option<PathBuf> {
+ if let Some(path) = find_binary("firefox-bin") {
+ return Some(path)
+ }
+ let home = env::home_dir();
+ for &(prefix_home, trial_path) in [
+ (false, "/Applications/Firefox.app/Contents/MacOS/firefox-bin"),
+ (true, "Applications/Firefox.app/Contents/MacOS/firefox-bin")].iter() {
+ let path = match (home.as_ref(), prefix_home) {
+ (Some(ref home_dir), true) => home_dir.join(trial_path),
+ (None, true) => continue,
+ (_, false) => PathBuf::from(trial_path)
+ };
+ if path.exists() {
+ return Some(path)
+ }
+ }
+ None
+ }
+
+ pub fn arg_prefix_char(c: char) -> bool {
+ c == '-'
+ }
+}
+
+#[cfg(target_os = "windows")]
+pub mod platform {
+ use super::find_binary;
+ use std::io::Error;
+ use std::path::PathBuf;
+ use winreg::RegKey;
+ use winreg::enums::*;
+
+ pub fn firefox_default_path() -> Option<PathBuf> {
+ let opt_path = firefox_registry_path().unwrap_or(None);
+ if let Some(path) = opt_path {
+ if path.exists() {
+ return Some(path)
+ }
+ };
+ find_binary("firefox.exe")
+ }
+
+ fn firefox_registry_path() -> Result<Option<PathBuf>, Error> {
+ let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
+ for subtree_key in ["SOFTWARE", "SOFTWARE\\WOW6432Node"].iter() {
+ let subtree = try!(hklm.open_subkey_with_flags(subtree_key, KEY_READ));
+ let mozilla_org = match subtree.open_subkey_with_flags("mozilla.org\\Mozilla", KEY_READ) {
+ Ok(val) => val,
+ Err(_) => continue
+ };
+ let current_version: String = try!(mozilla_org.get_value("CurrentVersion"));
+ let mozilla = try!(subtree.open_subkey_with_flags("Mozilla", KEY_READ));
+ for key_res in mozilla.enum_keys() {
+ let key = try!(key_res);
+ let section_data = try!(mozilla.open_subkey_with_flags(&key, KEY_READ));
+ let version: Result<String, _> = section_data.get_value("GeckoVer");
+ if let Ok(ver) = version {
+ if ver == current_version {
+ let mut bin_key = key.to_owned();
+ bin_key.push_str("\\bin");
+ if let Ok(bin_subtree) = mozilla.open_subkey_with_flags(bin_key, KEY_READ) {
+ let path: Result<String, _> = bin_subtree.get_value("PathToExe");
+ if let Ok(path) = path {
+ return Ok(Some(PathBuf::from(path)))
+ }
+ }
+ }
+ }
+ }
+ }
+ Ok(None)
+ }
+
+ pub fn arg_prefix_char(c: char) -> bool {
+ c == '/' || c == '-'
+ }
+}
+
+#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
+pub mod platform {
+ use std::path::PathBuf;
+
+ pub fn firefox_default_path() -> Option<PathBuf> {
+ None
+ }
+
+ pub fn arg_prefix_char(c: char) -> bool {
+ c == '-'
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{parse_arg_name, is_profile_arg};
+
+ fn parse(arg: &str, name: Option<&str>) {
+ let result = parse_arg_name(arg);
+ assert_eq!(result, name);
+ }
+
+ #[test]
+ fn test_parse_arg_name() {
+ parse("-p", Some("p"));
+ parse("--p", Some("p"));
+ parse("--profile foo", Some("profile"));
+ parse("--profile", Some("profile"));
+ parse("--", None);
+ parse("", None);
+ parse("-=", None);
+ parse("--=", None);
+ parse("-- foo", None);
+ parse("foo", None);
+ parse("/ foo", None);
+ parse("/- foo", None);
+ parse("/=foo", None);
+ parse("foo", None);
+ parse("-profile", Some("profile"));
+ parse("-profile=foo", Some("profile"));
+ parse("-profile = foo", Some("profile"));
+ parse("-profile abc", Some("profile"));
+ }
+
+ #[cfg(target_os = "windows")]
+ #[test]
+ fn test_parse_arg_name_windows() {
+ parse("/profile", Some("profile"));
+ }
+
+ #[cfg(not(target_os = "windows"))]
+ #[test]
+ fn test_parse_arg_name_non_windows() {
+ parse("/profile", None);
+ }
+
+ #[test]
+ fn test_is_profile_arg() {
+ assert!(is_profile_arg("--profile"));
+ assert!(is_profile_arg("-p"));
+ assert!(is_profile_arg("-PROFILEMANAGER"));
+ assert!(is_profile_arg("-ProfileMANAGER"));
+ assert!(!is_profile_arg("-- profile"));
+ assert!(!is_profile_arg("-profiled"));
+ assert!(!is_profile_arg("-p1"));
+ assert!(is_profile_arg("-p test"));
+ assert!(is_profile_arg("-profile /foo"));
+ }
+}