Port iOS test runner from chromium/tools/build

BUG=608537

Review-Url: https://codereview.chromium.org/1949123004
Cr-Commit-Position: refs/heads/master@{#392215}
diff --git a/ios/build/bots/scripts/test_runner_test.py b/ios/build/bots/scripts/test_runner_test.py
new file mode 100755
index 0000000..0d7c8b1
--- /dev/null
+++ b/ios/build/bots/scripts/test_runner_test.py
@@ -0,0 +1,289 @@
+#!/usr/bin/python
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Unittests for test_runner.py."""
+
+import collections
+import json
+import os
+import sys
+import unittest
+
+import test_runner
+
+
+class TestCase(unittest.TestCase):
+  """Test case which supports installing mocks. Uninstalls on tear down."""
+
+  def __init__(self, *args, **kwargs):
+    """Initializes a new instance of this class."""
+    super(TestCase, self).__init__(*args, **kwargs)
+
+    # Maps object to a dict which maps names of mocked members to their
+    # original values.
+    self._mocks = collections.OrderedDict()
+
+  def mock(self, obj, member, mock):
+    """Installs mock in place of the named member of the given obj.
+
+    Args:
+      obj: Any object.
+      member: String naming the attribute of the object to mock.
+      mock: The mock to install.
+    """
+    self._mocks.setdefault(obj, collections.OrderedDict()).setdefault(
+        member, getattr(obj, member))
+    setattr(obj, member, mock)
+
+  def tearDown(self, *args, **kwargs):
+    """Uninstalls mocks."""
+    super(TestCase, self).tearDown(*args, **kwargs)
+
+    for obj in self._mocks:
+      for member, original_value in self._mocks[obj].iteritems():
+        setattr(obj, member, original_value)
+
+
+class GetKIFTestFilterTest(TestCase):
+  """Tests for test_runner.get_kif_test_filter."""
+
+  def test_correct(self):
+    """Ensures correctness of filter."""
+    tests = [
+      'KIF.test1',
+      'KIF.test2',
+    ]
+    expected = 'NAME:test1|test2'
+
+    self.assertEqual(test_runner.get_kif_test_filter(tests), expected)
+
+  def test_correct_inverted(self):
+    """Ensures correctness of inverted filter."""
+    tests = [
+      'KIF.test1',
+      'KIF.test2',
+    ]
+    expected = '-NAME:test1|test2'
+
+    self.assertEqual(
+        test_runner.get_kif_test_filter(tests, invert=True), expected)
+
+
+class GetGTestFilterTest(TestCase):
+  """Tests for test_runner.get_gtest_filter."""
+
+  def test_correct(self):
+    """Ensures correctness of filter."""
+    tests = [
+      'test.1',
+      'test.2',
+    ]
+    expected = 'test.1:test.2'
+
+    self.assertEqual(test_runner.get_gtest_filter(tests), expected)
+
+  def test_correct_inverted(self):
+    """Ensures correctness of inverted filter."""
+    tests = [
+      'test.1',
+      'test.2',
+    ]
+    expected = '-test.1:test.2'
+
+    self.assertEqual(
+        test_runner.get_gtest_filter(tests, invert=True), expected)
+
+
+class SimulatorTestRunnerTest(TestCase):
+  """Tests for test_runner.SimulatorTestRunner."""
+
+  def test_app_not_found(self):
+    """Ensures AppNotFoundError is raised."""
+    def exists(path):
+      if path == 'fake-app':
+        return False
+      return True
+
+    def find_xcode(version):
+      return {'found': True}
+
+    def check_output(command):
+      return 'fake-bundle-id'
+
+    self.mock(test_runner.os.path, 'exists', exists)
+    self.mock(test_runner.find_xcode, 'find_xcode', find_xcode)
+    self.mock(test_runner.subprocess, 'check_output', check_output)
+
+    self.assertRaises(
+        test_runner.AppNotFoundError,
+        test_runner.SimulatorTestRunner,
+        'fake-app',
+        'fake-iossim',
+        'platform',
+        'os',
+        'xcode-version',
+        'out-dir',
+    )
+
+  def test_iossim_not_found(self):
+    """Ensures SimulatorNotFoundError is raised."""
+    def exists(path):
+      if path == 'fake-iossim':
+        return False
+      return True
+
+    def find_xcode(version):
+      return {'found': True}
+
+    def check_output(command):
+      return 'fake-bundle-id'
+
+    self.mock(test_runner.os.path, 'exists', exists)
+    self.mock(test_runner.find_xcode, 'find_xcode', find_xcode)
+    self.mock(test_runner.subprocess, 'check_output', check_output)
+
+    self.assertRaises(
+        test_runner.SimulatorNotFoundError,
+        test_runner.SimulatorTestRunner,
+        'fake-app',
+        'fake-iossim',
+        'platform',
+        'os',
+        'xcode-version',
+        'out-dir',
+    )
+
+  def test_init(self):
+    """Ensures instance is created."""
+    def exists(path):
+      return True
+
+    def find_xcode(version):
+      return {'found': True}
+
+    def check_output(command):
+      return 'fake-bundle-id'
+
+    self.mock(test_runner.os.path, 'exists', exists)
+    self.mock(test_runner.find_xcode, 'find_xcode', find_xcode)
+    self.mock(test_runner.subprocess, 'check_output', check_output)
+
+    tr = test_runner.SimulatorTestRunner(
+        'fake-app',
+        'fake-iossim',
+        'platform',
+        'os',
+        'xcode-version',
+        'out-dir',
+    )
+
+    self.failUnless(tr)
+
+  def test_startup_crash(self):
+    """Ensures test is relaunched once on startup crash."""
+    def exists(path):
+      return True
+
+    def find_xcode(version):
+      return {'found': True}
+
+    def check_output(command):
+      return 'fake-bundle-id'
+
+    def set_up(self):
+      return
+
+    @staticmethod
+    def _run(command):
+      return collections.namedtuple('result', ['crashed', 'crashed_test'])(
+          crashed=True, crashed_test=None)
+
+    def tear_down(self):
+      return
+
+    self.mock(test_runner.os.path, 'exists', exists)
+    self.mock(test_runner.find_xcode, 'find_xcode', find_xcode)
+    self.mock(test_runner.subprocess, 'check_output', check_output)
+    self.mock(test_runner.SimulatorTestRunner, 'set_up', set_up)
+    self.mock(test_runner.TestRunner, '_run', _run)
+    self.mock(test_runner.SimulatorTestRunner, 'tear_down', tear_down)
+
+    tr = test_runner.SimulatorTestRunner(
+        'fake-app',
+        'fake-iossim',
+        'platform',
+        'os',
+        'xcode-version',
+        'out-dir',
+    )
+    self.assertRaises(test_runner.AppLaunchError, tr.launch)
+
+  def test_relaunch(self):
+    """Ensures test is relaunched on test crash until tests complete."""
+    def exists(path):
+      return True
+
+    def find_xcode(version):
+      return {'found': True}
+
+    def check_output(command):
+      return 'fake-bundle-id'
+
+    def set_up(self):
+      return
+
+    @staticmethod
+    def _run(command):
+      result = collections.namedtuple(
+          'result', [
+              'crashed',
+              'crashed_test',
+              'failed_tests',
+              'flaked_tests',
+              'passed_tests',
+          ],
+      )
+      if '-e' not in command:
+        # First run, has no test filter supplied. Mock a crash.
+        return result(
+            crashed=True,
+            crashed_test='c',
+            failed_tests={'b': ['b-out'], 'c': ['Did not complete.']},
+            flaked_tests={'d': ['d-out']},
+            passed_tests=['a'],
+        )
+      else:
+        return result(
+            crashed=False,
+            crashed_test=None,
+            failed_tests={},
+            flaked_tests={},
+            passed_tests=[],
+        )
+
+    def tear_down(self):
+      return
+
+    self.mock(test_runner.os.path, 'exists', exists)
+    self.mock(test_runner.find_xcode, 'find_xcode', find_xcode)
+    self.mock(test_runner.subprocess, 'check_output', check_output)
+    self.mock(test_runner.SimulatorTestRunner, 'set_up', set_up)
+    self.mock(test_runner.TestRunner, '_run', _run)
+    self.mock(test_runner.SimulatorTestRunner, 'tear_down', tear_down)
+
+    tr = test_runner.SimulatorTestRunner(
+        'fake-app',
+        'fake-iossim',
+        'platform',
+        'os',
+        'xcode-version',
+        'out-dir',
+    )
+    tr.launch()
+    self.failUnless(tr.summary['logs'])
+
+
+if __name__ == '__main__':
+  unittest.main()