Bug 1383931 - Accept base64-encoded addons in the addon install command. r=ato
authorJason Juang <juangj@gmail.com>
Mon, 24 Jul 2017 15:43:05 -0700
changeset 422316 efb8a580888afe0ed94af61f6a7843ecf520824d
parent 422315 e677ddb0596512ba640f64c0b35e9b5f0465c8d2
child 422317 6a61f3b97e514f20473b2ece3d1337bcb1631cf9
push id1517
push userjlorenzo@mozilla.com
push dateThu, 14 Sep 2017 16:50:54 +0000
treeherdermozilla-release@3b41fd564418 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersato
bugs1383931
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1383931 - Accept base64-encoded addons in the addon install command. r=ato This allows tests that use Geckodriver remotely to more easily install addons. The base64 blob is written to a temporary file before being passed on to Marionette. MozReview-Commit-ID: DnaBqoXCj5
testing/geckodriver/CHANGES.md
testing/geckodriver/src/main.rs
testing/geckodriver/src/marionette.rs
--- a/testing/geckodriver/CHANGES.md
+++ b/testing/geckodriver/CHANGES.md
@@ -1,12 +1,17 @@
 # Change log
 
 All notable changes to this program is documented in this file.
 
+## Unreleased
+
+### Changed
+- `/moz/addon/install` command accepts an `addon` parameter, in lieu of `path`, containing an add-on as a base64 string.
+
 ## 0.18.0 (2017-07-10)
 
 ### Changed
 - [`RectResponse`](https://docs.rs/webdriver/0.27.0/webdriver/response/struct.RectResponse.html) permits returning floats for `width` and `height` fields
 - New type [`CookieResponse`](https://docs.rs/webdriver/0.27.0/webdriver/response/struct.CookieResponse.html) for the [`GetNamedCookie` command](https://docs.rs/webdriver/0.27.0/webdriver/command/enum.WebDriverCommand.html#variant.GetNamedCookie) returns a single cookie, as opposed to an array of a single cookie
 - To pick up a prepared profile from the filesystem, it is now possible to pass `["-profile", "/path/to/profile"]` in the `args` array on `moz:firefoxOptions`
 - geckodriver now recommends Firefox 53 and greater
 - Version information (`--version`) contains the hash from from the commit used to build geckodriver
--- a/testing/geckodriver/src/main.rs
+++ b/testing/geckodriver/src/main.rs
@@ -9,16 +9,17 @@ extern crate mozrunner;
 extern crate mozversion;
 extern crate regex;
 extern crate rustc_serialize;
 #[macro_use]
 extern crate slog;
 extern crate slog_atomic;
 extern crate slog_stdlog;
 extern crate slog_stream;
+extern crate uuid;
 extern crate zip;
 extern crate webdriver;
 
 #[macro_use]
 extern crate log;
 
 use std::fmt;
 use std::io::Write;
--- a/testing/geckodriver/src/marionette.rs
+++ b/testing/geckodriver/src/marionette.rs
@@ -1,28 +1,32 @@
 use hyper::method::Method;
 use logging;
 use logging::LogLevel;
 use mozprofile::preferences::Pref;
 use mozprofile::profile::Profile;
 use mozrunner::runner::{Runner, FirefoxRunner};
 use regex::Captures;
+use rustc_serialize::base64::FromBase64;
 use rustc_serialize::json;
 use rustc_serialize::json::{Json, ToJson};
 use std::collections::BTreeMap;
+use std::env;
 use std::error::Error;
+use std::fs::File;
 use std::io::Error as IoError;
 use std::io::ErrorKind;
 use std::io::prelude::*;
 use std::path::PathBuf;
 use std::io::Result as IoResult;
 use std::net::{TcpListener, TcpStream};
 use std::sync::Mutex;
 use std::thread::sleep;
 use std::time::Duration;
+use uuid::Uuid;
 use webdriver::capabilities::CapabilitiesMatching;
 use webdriver::command::{WebDriverCommand, WebDriverMessage, Parameters,
                          WebDriverExtensionCommand};
 use webdriver::command::WebDriverCommand::{
     NewSession, DeleteSession, Status, Get, GetCurrentUrl,
     GoBack, GoForward, Refresh, GetTitle, GetPageSource, GetWindowHandle,
     GetWindowHandles, CloseWindow, SetWindowRect,
     GetWindowRect, MaximizeWindow, FullscreenWindow, SwitchToWindow, SwitchToFrame,
@@ -258,32 +262,55 @@ pub struct AddonInstallParameters {
 }
 
 impl Parameters for AddonInstallParameters {
     fn from_json(body: &Json) -> WebDriverResult<AddonInstallParameters> {
         let data = try!(body.as_object().ok_or(
             WebDriverError::new(ErrorStatus::InvalidArgument,
                                 "Message body was not an object")));
 
-        let path = try_opt!(
-            try_opt!(data.get("path"),
-                     ErrorStatus::InvalidArgument,
-                     "Missing 'path' parameter").as_string(),
-            ErrorStatus::InvalidArgument,
-            "'path' is not a string").to_string();
+        let base64 = match data.get("addon") {
+            Some(x) => {
+                let s = try_opt!(x.as_string(),
+                                 ErrorStatus::InvalidArgument,
+                                 "'addon' is not a string").to_string();
+
+                let addon_path = env::temp_dir().as_path()
+                    .join(format!("addon-{}.xpi", Uuid::new_v4()));
+                let mut addon_file = try!(File::create(&addon_path));
+                let addon_buf = try!(s.from_base64());
+                try!(addon_file.write(addon_buf.as_slice()));
+
+                Some(try_opt!(addon_path.to_str(),
+                              ErrorStatus::UnknownError,
+                              "could not write addon to file").to_string())
+            },
+            None => None,
+        };
+        let path = match data.get("path") {
+            Some(x) => Some(try_opt!(x.as_string(),
+                                     ErrorStatus::InvalidArgument,
+                                     "'path' is not a string").to_string()),
+            None => None,
+        };
+        if (base64.is_none() && path.is_none()) || (base64.is_some() && path.is_some()) {
+            return Err(WebDriverError::new(
+                ErrorStatus::InvalidArgument,
+                "Must specify exactly one of 'path' and 'addon'"));
+        }
 
         let temporary = match data.get("temporary") {
             Some(x) => try_opt!(x.as_boolean(),
                                 ErrorStatus::InvalidArgument,
                                 "Failed to convert 'temporary' to boolean"),
             None => false
         };
 
         return Ok(AddonInstallParameters {
-            path: path,
+            path: base64.or(path).unwrap(),
             temporary: temporary,
         })
     }
 }
 
 impl ToJson for AddonInstallParameters {
     fn to_json(&self) -> Json {
         let mut data = BTreeMap::new();
@@ -1583,8 +1610,54 @@ impl ToMarionette for FrameId {
             FrameId::Short(x) => data.insert("id".to_string(), x.to_json()),
             FrameId::Element(ref x) => data.insert("element".to_string(),
                                                    Json::Object(try!(x.to_marionette()))),
             FrameId::Null => None
         };
         Ok(data)
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use marionette::{AddonInstallParameters, Parameters};
+    use rustc_serialize::json::Json;
+    use std::io::Read;
+    use std::fs::File;
+    use webdriver::error::WebDriverResult;
+
+    #[test]
+    fn test_addon_install_params_missing_path() {
+        let json_data: Json = Json::from_str(r#"{"temporary": true}"#).unwrap();
+        let res: WebDriverResult<AddonInstallParameters> = Parameters::from_json(&json_data);
+        assert!(res.is_err());
+    }
+
+    #[test]
+    fn test_addon_install_params_with_both_path_and_base64() {
+        let json_data: Json = Json::from_str(
+            r#"{"path": "/path/to.xpi", "addon": "aGVsbG8=", "temporary": true}"#).unwrap();
+        let res: WebDriverResult<AddonInstallParameters> = Parameters::from_json(&json_data);
+        assert!(res.is_err());
+    }
+
+    #[test]
+    fn test_addon_install_params_with_path() {
+        let json_data: Json = Json::from_str(
+            r#"{"path": "/path/to.xpi", "temporary": true}"#).unwrap();
+        let parameters: AddonInstallParameters = Parameters::from_json(&json_data).unwrap();
+        assert_eq!(parameters.path, "/path/to.xpi");
+        assert_eq!(parameters.temporary, true);
+    }
+
+    #[test]
+    fn test_addon_install_params_with_base64() {
+        let json_data: Json = Json::from_str(
+            r#"{"addon": "aGVsbG8=", "temporary": true}"#).unwrap();
+        let parameters: AddonInstallParameters = Parameters::from_json(&json_data).unwrap();
+
+        assert_eq!(parameters.temporary, true);
+        let mut file = File::open(parameters.path).unwrap();
+        let mut contents = String::new();
+        file.read_to_string(&mut contents).unwrap();
+        assert_eq!("hello", contents);
+    }
+}