Bug 1438679 - [mozrunner] DeviceRunner has to override returncode and wait() to check for remote process status. draft
authorHenrik Skupin <mail@hskupin.info>
Thu, 22 Feb 2018 23:08:49 +0100
changeset 760261 95c43e760befa7c4556661a825184b0d44b86b28
parent 760185 580d833df9c44acec686a9fb88b5f27e9d29f68d
push id100595
push userbmo:hskupin@gmail.com
push dateTue, 27 Feb 2018 09:05:47 +0000
bugs1438679
milestone60.0a1
Bug 1438679 - [mozrunner] DeviceRunner has to override returncode and wait() to check for remote process status. Without returncode and wait() being overridden the default implementation of the Runner class takes precedence and will run the check for the adb command but not the remote process. This always returns 0 because adb runs or forks itself as daemon. Instead the remote process has to be checked for existence. MozReview-Commit-ID: GvuAaMSxBT2
testing/mozbase/mozrunner/mozrunner/base/device.py
--- a/testing/mozbase/mozrunner/mozrunner/base/device.py
+++ b/testing/mozbase/mozrunner/mozrunner/base/device.py
@@ -77,57 +77,73 @@ class DeviceRunner(BaseRunner):
         self.device.wait_for_net()
 
         if not self.device.wait_for_net():
             raise Exception("Network did not come up when starting device")
 
         pid = BaseRunner.start(self, *args, **kwargs)
 
         timeout = 10  # seconds
-        starttime = datetime.datetime.now()
-        while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout):
-            if self.is_running():
-                break
-            time.sleep(1)
+        end_time = datetime.datetime.now() + datetime.timedelta(seconds=timeout)
+        while not self.is_running() and datetime.datetime.now() < end_time:
+            time.sleep(.1)
         else:
             print("timed out waiting for '%s' process to start" % self.app_ctx.remote_process)
 
         if not self.device.wait_for_net():
             raise Exception("Failed to get a network connection")
         return pid
 
     def stop(self, sig=None):
-        def _wait_for_shutdown(pid, timeout=10):
-            start_time = datetime.datetime.now()
-            end_time = datetime.timedelta(seconds=timeout)
-            while datetime.datetime.now() - start_time < end_time:
-                if self.is_running() != pid:
-                    return True
-                time.sleep(1)
-            return False
+        if self.is_running():
+            timeout = 10
 
-        remote_pid = self.is_running()
-        if remote_pid:
-            self.app_ctx.dm.killProcess(
-                self.app_ctx.remote_process, sig=sig)
-            if not _wait_for_shutdown(remote_pid) and sig is not None:
+            self.app_ctx.dm.killProcess(self.app_ctx.remote_process, sig=sig)
+            if self.wait(timeout) is None and sig is not None:
                 print("timed out waiting for '%s' process to exit, trying "
                       "without signal {}".format(
                           self.app_ctx.remote_process, sig))
 
             # need to call adb stop otherwise the system will attempt to
             # restart the process
-            remote_pid = self.is_running() or remote_pid
             self.app_ctx.stop_application()
-            if not _wait_for_shutdown(remote_pid):
+            if self.wait(timeout) is None:
                 print("timed out waiting for '%s' process to exit".format(
                     self.app_ctx.remote_process))
 
-    def is_running(self):
-        return self.app_ctx.dm.processExist(self.app_ctx.remote_process)
+    @property
+    def returncode(self):
+        """The returncode of the remote process.
+
+        A value of None indicates the process is still running. Otherwise 0 is
+        returned, because there is no known way yet to retrieve the real exit code.
+        """
+        if self.app_ctx.dm.processExist(self.app_ctx.remote_process) is None:
+            return 0
+
+        return None
+
+    def wait(self, timeout=None):
+        """Wait for the remote process to exit.
+
+        :param timeout: if not None, will return after timeout seconds.
+
+        :returns: the process return code or None if timeout was reached
+                  and the process is still running.
+        """
+        end_time = None
+        if timeout is not None:
+            end_time = datetime.datetime.now() + datetime.timedelta(seconds=timeout)
+
+        while self.is_running():
+            if end_time is not None and datetime.datetime.now() > end_time:
+                break
+            time.sleep(.1)
+
+        return self.returncode
 
     def on_output(self, line):
         match = re.findall(r"TEST-START \| ([^\s]*)", line)
         if match:
             self.last_test = match[-1]
 
     def on_timeout(self):
         self.stop(sig=signal.SIGABRT)