Bug 1318644 - Auto-detect application type if binary has been specified. draft
authorHenrik Skupin <mail@hskupin.info>
Fri, 25 Nov 2016 10:41:17 +0100
changeset 443871 bd0f616c465d397d591c150b4a542e565a7b8771
parent 443602 bad312aefb42982f492ad2cf36f4c6c3d698f4f7
child 538169 19d4dadbbec1d6a928e52fd7f824e9b0ad162688
push id37122
push userbmo:hskupin@gmail.com
push dateFri, 25 Nov 2016 09:42:29 +0000
bugs1318644
milestone53.0a1
Bug 1318644 - Auto-detect application type if binary has been specified. MozReview-Commit-ID: 15etyogBI54
testing/marionette/client/marionette_driver/geckoinstance.py
testing/marionette/client/marionette_driver/marionette.py
testing/marionette/client/requirements.txt
testing/marionette/harness/marionette/runner/base.py
testing/marionette/harness/marionette/tests/unit/test_geckoinstance.py
testing/marionette/harness/marionette/tests/unit/unit-tests.ini
--- a/testing/marionette/client/marionette_driver/geckoinstance.py
+++ b/testing/marionette/client/marionette_driver/geckoinstance.py
@@ -4,16 +4,18 @@
 
 import os
 import sys
 import tempfile
 import time
 
 from copy import deepcopy
 
+import mozversion
+
 from mozprofile import Profile
 from mozrunner import Runner, FennecEmulatorRunner
 
 
 class GeckoInstance(object):
     required_prefs = {
         "browser.sessionstore.resume_from_crash": False,
         "browser.shell.checkDefaultBrowser": False,
@@ -42,17 +44,17 @@ class GeckoInstance(object):
         "startup.homepage_welcome_url": "",
         "startup.homepage_welcome_url.additional": "",
         "toolkit.telemetry.enabled": False,
         # Until Bug 1238095 is fixed, we have to enable CPOWs in order
         # for Marionette tests to work properly.
         "dom.ipc.cpows.forbid-unsafe-from-browser": False,
     }
 
-    def __init__(self, host, port, bin, profile=None, addons=None,
+    def __init__(self, host=None, port=None, bin=None, profile=None, addons=None,
                  app_args=None, symbols_path=None, gecko_log=None, prefs=None,
                  workspace=None, verbose=0):
         self.runner_class = Runner
         self.app_args = app_args or []
         self.runner = None
         self.symbols_path = symbols_path
         self.binary = bin
 
@@ -128,16 +130,30 @@ class GeckoInstance(object):
                     os.path.basename(self.profile_path),
                     time.time()
                 )
                 if self.workspace:
                     profile_args["path_to"] = os.path.join(self.workspace,
                                                            profile_name)
                 self.profile = Profile.clone(**profile_args)
 
+    @classmethod
+    def create(cls, app=None, *args, **kwargs):
+        try:
+            if not app:
+                app_id = mozversion.get_version(binary=kwargs.get('bin'))['application_id']
+                app = app_ids[app_id]
+
+            instance_class = apps[app]
+        except KeyError:
+            msg = 'Application "{0}" unknown (should be one of {1})'
+            raise NotImplementedError(msg.format(app, apps.keys()))
+
+        return instance_class(*args, **kwargs)
+
     def start(self):
         self._update_profile()
         self.runner = self.runner_class(**self._get_runner_args())
         self.runner.start()
 
     def _get_runner_args(self):
         process_args = {
             'processOutputLine': [NullOutput()],
@@ -313,11 +329,16 @@ class DesktopInstance(GeckoInstance):
 
 
 class NullOutput(object):
     def __call__(self, line):
         pass
 
 
 apps = {
+    'fennec': FennecInstance,
     'fxdesktop': DesktopInstance,
-    'fennec': FennecInstance,
 }
+
+app_ids = {
+    '{aa3c5121-dab2-40e2-81ca-7ea25febc110}': 'fennec',
+    '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'fxdesktop',
+}
--- a/testing/marionette/client/marionette_driver/marionette.py
+++ b/testing/marionette/client/marionette_driver/marionette.py
@@ -1,33 +1,31 @@
 # 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/.
 
-import ConfigParser
 import base64
 import datetime
 import json
 import os
 import socket
 import sys
 import time
 import traceback
 import warnings
 
 from contextlib import contextmanager
 
-from decorators import do_process_check
-from keys import Keys
+import errors
+import transport
 
-import errors
-import geckoinstance
-import transport
-from timeout import Timeouts
-
+from .decorators import do_process_check
+from .geckoinstance import GeckoInstance
+from .keys import Keys
+from .timeout import Timeouts
 
 WEBELEMENT_KEY = "ELEMENT"
 W3C_WEBELEMENT_KEY = "element-6066-11e4-a52e-4f735466cecf"
 
 
 class HTMLElement(object):
     """Represents a DOM Element."""
 
@@ -579,50 +577,27 @@ class Marionette(object):
         self.chrome_window = None
         self.baseurl = baseurl
         self._test_name = None
         self.socket_timeout = socket_timeout
         self.crashed = 0
 
         startup_timeout = startup_timeout or self.DEFAULT_STARTUP_TIMEOUT
         if self.bin:
-            self.instance = self._create_instance(app, instance_args)
+            if not Marionette.is_port_available(self.port, host=self.host):
+                ex_msg = "{0}:{1} is unavailable.".format(self.host, self.port)
+                raise errors.MarionetteException(message=ex_msg)
+
+            self.instance = GeckoInstance.create(
+                app, host=self.host, port=self.port, bin=self.bin, **instance_args)
             self.instance.start()
             self.raise_for_port(timeout=startup_timeout)
 
         self.timeout = Timeouts(self)
 
-    def _create_instance(self, app, instance_args):
-        if not Marionette.is_port_available(self.port, host=self.host):
-            ex_msg = "{0}:{1} is unavailable.".format(self.host, self.port)
-            raise errors.MarionetteException(message=ex_msg)
-        if app:
-            # select instance class for the given app
-            try:
-                instance_class = geckoinstance.apps[app]
-            except KeyError:
-                msg = 'Application "{0}" unknown (should be one of {1})'
-                raise NotImplementedError(
-                    msg.format(app, geckoinstance.apps.keys()))
-        else:
-            try:
-                if not isinstance(self.bin, basestring):
-                    raise TypeError("bin must be a string if app is not specified")
-                config = ConfigParser.RawConfigParser()
-                config.read(os.path.join(os.path.dirname(self.bin),
-                                         'application.ini'))
-                app = config.get('App', 'Name')
-                instance_class = geckoinstance.apps[app.lower()]
-            except (ConfigParser.NoOptionError,
-                    ConfigParser.NoSectionError,
-                    KeyError):
-                instance_class = geckoinstance.GeckoInstance
-        return instance_class(host=self.host, port=self.port, bin=self.bin,
-                              **instance_args)
-
     @property
     def profile_path(self):
         if self.instance and self.instance.profile:
             return self.instance.profile.profile
 
     def cleanup(self):
         if self.session:
             try:
--- a/testing/marionette/client/requirements.txt
+++ b/testing/marionette/client/requirements.txt
@@ -1,1 +1,2 @@
 mozrunner >= 6.13
+mozversion >= 1.1
--- a/testing/marionette/harness/marionette/runner/base.py
+++ b/testing/marionette/harness/marionette/runner/base.py
@@ -1,36 +1,36 @@
 # 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/.
 
-from argparse import ArgumentParser
-
-from copy import deepcopy
 import json
-import mozinfo
-import moznetwork
 import os
 import random
 import re
 import socket
 import sys
 import time
 import traceback
 import unittest
 import warnings
+
+from argparse import ArgumentParser
+from copy import deepcopy
+
+import mozinfo
+import moznetwork
 import mozprofile
-
+import mozversion
 
 from manifestparser import TestManifest
 from manifestparser.filters import tags
 from marionette_driver.marionette import Marionette
 from moztest.adapters.unit import StructuredTestRunner, StructuredTestResult
 from moztest.results import TestResultCollection, TestResult, relevant_line
-import mozversion
 
 import httpd
 
 
 here = os.path.abspath(os.path.dirname(__file__))
 
 
 def update_mozinfo(path=None):
@@ -501,35 +501,39 @@ class BaseMarionetteTestRunner(object):
                  symbols_path=None,
                  shuffle=False, shuffle_seed=random.randint(0, sys.maxint), this_chunk=1,
                  total_chunks=1,
                  server_root=None, gecko_log=None, result_callbacks=None,
                  prefs=None, test_tags=None,
                  socket_timeout=BaseMarionetteArguments.socket_timeout_default,
                  startup_timeout=None, addons=None, workspace=None,
                  verbose=0, e10s=True, emulator=False, **kwargs):
+
+        self._appinfo = None
+        self._appName = None
+        self._capabilities = None
+        self._filename_pattern = None
+        self._version_info = {}
+
         self.extra_kwargs = kwargs
         self.test_kwargs = deepcopy(kwargs)
         self.address = address
         self.app = app
         self.app_args = app_args or []
         self.bin = binary
         self.emulator = emulator
         self.profile = profile
         self.addons = addons
         self.logger = logger
         self.httpd = None
         self.marionette = None
         self.logdir = logdir
         self.repeat = repeat
         self.symbols_path = symbols_path
         self.socket_timeout = socket_timeout
-        self._capabilities = None
-        self._appinfo = None
-        self._appName = None
         self.shuffle = shuffle
         self.shuffle_seed = shuffle_seed
         self.server_root = server_root
         self.this_chunk = this_chunk
         self.total_chunks = total_chunks
         self.mixin_run_tests = []
         self.manifest_skipped_tests = []
         self.tests = []
@@ -538,17 +542,16 @@ class BaseMarionetteTestRunner(object):
         self.test_tags = test_tags
         self.startup_timeout = startup_timeout
         self.workspace = workspace
         # If no workspace is set, default location for gecko.log is .
         # and default location for profile is TMP
         self.workspace_path = workspace or os.getcwd()
         self.verbose = verbose
         self.e10s = e10s
-        self._filename_pattern = None
 
         def gather_debug(test, status):
             rv = {}
             marionette = test._marionette_weakref()
 
             # In the event we're gathering debug without starting a session,
             # skip marionette commands
             if marionette.session is not None:
@@ -671,16 +674,27 @@ class BaseMarionetteTestRunner(object):
         """Set binary and reset parts of runner accordingly.
 
         Intended use: to change binary between calls to run_tests
         """
         self._bin = path
         self.tests = []
         self.cleanup()
 
+    @property
+    def version_info(self):
+        if not self._version_info:
+            try:
+                # TODO: Get version_info in Fennec case
+                self._version_info = mozversion.get_version(binary=self.bin)
+            except Exception:
+                self.logger.warning("Failed to retrieve version information for {}".format(
+                    self.bin))
+        return self._version_info
+
     def reset_test_stats(self):
         self.passed = 0
         self.failed = 0
         self.crashed = 0
         self.unexpected_successes = 0
         self.todo = 0
         self.skipped = 0
         self.failures = []
@@ -820,25 +834,20 @@ class BaseMarionetteTestRunner(object):
 
         device_info = None
         if self.marionette.instance and self.emulator:
             try:
                 device_info = self.marionette.instance.runner.device.dm.getInfo()
             except Exception:
                 self.logger.warning('Could not get device info.')
 
-        # TODO: Get version_info in Fennec case
-        version_info = None
-        if self.bin:
-            version_info = mozversion.get_version(binary=self.bin)
-
         self.logger.info("running with e10s: {}".format(self.e10s))
 
         self.logger.suite_start(self.tests,
-                                version_info=version_info,
+                                version_info=self.version_info,
                                 device_info=device_info)
 
         self._log_skipped_tests()
 
         interrupted = None
         try:
             counter = self.repeat
             while counter >= 0:
new file mode 100644
--- /dev/null
+++ b/testing/marionette/harness/marionette/tests/unit/test_geckoinstance.py
@@ -0,0 +1,27 @@
+# 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/.
+
+from marionette.marionette_test import MarionetteTestCase
+from marionette_driver.geckoinstance import apps, GeckoInstance
+
+
+class TestGeckoInstance(MarionetteTestCase):
+
+    def test_create(self):
+        """Test that the correct gecko instance is determined."""
+        for app in apps:
+            # If app has been specified we directly return the appropriate instance class
+            self.assertEqual(type(GeckoInstance.create(app=app, bin="n/a")),
+                             apps[app])
+
+        # When using mozversion to detect the instance class, it should never return the
+        # base GeckoInstance class.
+        self.assertNotEqual(GeckoInstance.create(bin=self.marionette.bin),
+                            GeckoInstance)
+
+        # Unknown applications and binaries should fail
+        self.assertRaises(NotImplementedError, GeckoInstance.create,
+                          app="n/a", binary=self.marionette.bin)
+        self.assertRaises(IOError, GeckoInstance.create,
+                          bin="n/a")
--- a/testing/marionette/harness/marionette/tests/unit/unit-tests.ini
+++ b/testing/marionette/harness/marionette/tests/unit/unit-tests.ini
@@ -1,9 +1,10 @@
 [test_marionette.py]
+[test_geckoinstance.py]
 [test_data_driven.py]
 [test_session.py]
 [test_capabilities.py]
 [test_accessibility.py]
 [test_expectedfail.py]
 expected = fail
 [test_import_script.py]
 [test_click.py]