• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2013 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Base class for host-driven test cases.
6
7This test case is intended to serve as the base class for any host-driven
8test cases. It is similar to the Python unitttest module in that test cases
9inherit from this class and add methods which will be run as tests.
10
11When a HostDrivenTestCase object is instantiated, its purpose is to run only one
12test method in the derived class. The test runner gives it the name of the test
13method the instance will run. The test runner calls SetUp with the device ID
14which the test method will run against. The test runner runs the test method
15itself, collecting the result, and calls TearDown.
16
17Tests can perform arbitrary Python commands and asserts in test methods. Tests
18that run instrumentation tests can make use of the _RunJavaTestFilters helper
19function to trigger Java tests and convert results into a single host-driven
20test result.
21"""
22
23import logging
24import os
25import time
26
27from pylib import android_commands
28from pylib import constants
29from pylib import forwarder
30from pylib import valgrind_tools
31from pylib.base import base_test_result
32from pylib.instrumentation import test_package
33from pylib.instrumentation import test_result
34from pylib.instrumentation import test_runner
35
36# aka the parent of com.google.android
37BASE_ROOT = 'src' + os.sep
38
39
40class HostDrivenTestCase(object):
41  """Base class for host-driven test cases."""
42
43  _HOST_DRIVEN_TAG = 'HostDriven'
44
45  def __init__(self, test_name, instrumentation_options=None):
46    """Create a test case initialized to run |test_name|.
47
48    Args:
49      test_name: The name of the method to run as the test.
50      instrumentation_options: An InstrumentationOptions object.
51    """
52    self.test_name = test_name
53    class_name = self.__class__.__name__
54    self.qualified_name = '%s.%s' % (class_name, self.test_name)
55    # Use tagged_name when creating results, so that we can identify host-driven
56    # tests in the overall results.
57    self.tagged_name = '%s_%s' % (self._HOST_DRIVEN_TAG, self.qualified_name)
58
59    self.instrumentation_options = instrumentation_options
60    self.ports_to_forward = []
61    self.has_forwarded_ports = False
62
63  # TODO(bulach): make ports_to_forward not optional and move the Forwarder
64  # mapping here.
65  def SetUp(self, device, shard_index, push_deps,
66            cleanup_test_files, ports_to_forward=[]):
67    self.device_id = device
68    self.shard_index = shard_index
69    self.adb = android_commands.AndroidCommands(self.device_id)
70    self.push_deps = push_deps
71    self.cleanup_test_files = cleanup_test_files
72    if ports_to_forward:
73      self.ports_to_forward = ports_to_forward
74
75  def TearDown(self):
76    pass
77
78  # TODO(craigdh): Remove GetOutDir once references have been removed
79  # downstream.
80  def GetOutDir(self):
81    return constants.GetOutDirectory()
82
83  def Run(self):
84    logging.info('Running host-driven test: %s', self.tagged_name)
85    # Get the test method on the derived class and execute it
86    return getattr(self, self.test_name)()
87
88  def __GetHostForwarderLog(self):
89    return ('-- Begin Full HostForwarder log\n'
90            '%s\n'
91            '--End Full HostForwarder log\n' % forwarder.Forwarder.GetHostLog())
92
93  def __StartForwarder(self):
94    logging.warning('Forwarding %s %s', self.ports_to_forward,
95                    self.has_forwarded_ports)
96    if self.ports_to_forward and not self.has_forwarded_ports:
97      self.has_forwarded_ports = True
98      tool = valgrind_tools.CreateTool(None, self.adb)
99      forwarder.Forwarder.Map([(port, port) for port in self.ports_to_forward],
100                              self.adb, tool)
101
102  def __RunJavaTest(self, test, test_pkg, additional_flags=None):
103    """Runs a single Java test in a Java TestRunner.
104
105    Args:
106      test: Fully qualified test name (ex. foo.bar.TestClass#testMethod)
107      test_pkg: TestPackage object.
108      additional_flags: A list of additional flags to add to the command line.
109
110    Returns:
111      TestRunResults object with a single test result.
112    """
113    # TODO(bulach): move this to SetUp() stage.
114    self.__StartForwarder()
115
116    java_test_runner = test_runner.TestRunner(self.instrumentation_options,
117                                              self.device_id,
118                                              self.shard_index, test_pkg,
119                                              additional_flags=additional_flags)
120    try:
121      java_test_runner.SetUp()
122      return java_test_runner.RunTest(test)[0]
123    finally:
124      java_test_runner.TearDown()
125
126  def _RunJavaTestFilters(self, test_filters, additional_flags=None):
127    """Calls a list of tests and stops at the first test failure.
128
129    This method iterates until either it encounters a non-passing test or it
130    exhausts the list of tests. Then it returns the appropriate overall result.
131
132    Test cases may make use of this method internally to assist in running
133    instrumentation tests. This function relies on instrumentation_options
134    being defined.
135
136    Args:
137      test_filters: A list of Java test filters.
138      additional_flags: A list of addition flags to add to the command line.
139
140    Returns:
141      A TestRunResults object containing an overall result for this set of Java
142      tests. If any Java tests do not pass, this is a fail overall.
143    """
144    test_type = base_test_result.ResultType.PASS
145    log = ''
146
147    test_pkg = test_package.TestPackage(
148        self.instrumentation_options.test_apk_path,
149        self.instrumentation_options.test_apk_jar_path)
150
151    start_ms = int(time.time()) * 1000
152    done = False
153    for test_filter in test_filters:
154      tests = test_pkg._GetAllMatchingTests(None, None, test_filter)
155      # Filters should always result in >= 1 test.
156      if len(tests) == 0:
157        raise Exception('Java test filter "%s" returned no tests.'
158                        % test_filter)
159      for test in tests:
160        # We're only running one test at a time, so this TestRunResults object
161        # will hold only one result.
162        java_result = self.__RunJavaTest(test, test_pkg, additional_flags)
163        assert len(java_result.GetAll()) == 1
164        if not java_result.DidRunPass():
165          result = java_result.GetNotPass().pop()
166          log = result.GetLog()
167          log += self.__GetHostForwarderLog()
168          test_type = result.GetType()
169          done = True
170          break
171      if done:
172        break
173    duration_ms = int(time.time()) * 1000 - start_ms
174
175    overall_result = base_test_result.TestRunResults()
176    overall_result.AddResult(
177        test_result.InstrumentationTestResult(
178            self.tagged_name, test_type, start_ms, duration_ms, log=log))
179    return overall_result
180
181  def __str__(self):
182    return self.tagged_name
183
184  def __repr__(self):
185    return self.tagged_name
186