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 constants 28from pylib import forwarder 29from pylib import valgrind_tools 30from pylib.base import base_test_result 31from pylib.device import device_utils 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 class_name = self.__class__.__name__ 53 self.adb = None 54 self.cleanup_test_files = False 55 self.device = None 56 self.device_id = '' 57 self.has_forwarded_ports = False 58 self.instrumentation_options = instrumentation_options 59 self.ports_to_forward = [] 60 self.push_deps = False 61 self.shard_index = 0 62 63 # Use tagged_name when creating results, so that we can identify host-driven 64 # tests in the overall results. 65 self.test_name = test_name 66 self.qualified_name = '%s.%s' % (class_name, self.test_name) 67 self.tagged_name = '%s_%s' % (self._HOST_DRIVEN_TAG, self.qualified_name) 68 69 # TODO(bulach): make ports_to_forward not optional and move the Forwarder 70 # mapping here. 71 def SetUp(self, device, shard_index, push_deps, 72 cleanup_test_files, ports_to_forward=None): 73 if not ports_to_forward: 74 ports_to_forward = [] 75 self.device_id = device 76 self.shard_index = shard_index 77 self.device = device_utils.DeviceUtils(self.device_id) 78 self.adb = self.device.old_interface 79 self.push_deps = push_deps 80 self.cleanup_test_files = cleanup_test_files 81 if ports_to_forward: 82 self.ports_to_forward = ports_to_forward 83 84 def TearDown(self): 85 pass 86 87 # TODO(craigdh): Remove GetOutDir once references have been removed 88 # downstream. 89 @staticmethod 90 def GetOutDir(): 91 return constants.GetOutDirectory() 92 93 def Run(self): 94 logging.info('Running host-driven test: %s', self.tagged_name) 95 # Get the test method on the derived class and execute it 96 return getattr(self, self.test_name)() 97 98 @staticmethod 99 def __GetHostForwarderLog(): 100 return ('-- Begin Full HostForwarder log\n' 101 '%s\n' 102 '--End Full HostForwarder log\n' % forwarder.Forwarder.GetHostLog()) 103 104 def __StartForwarder(self): 105 logging.warning('Forwarding %s %s', self.ports_to_forward, 106 self.has_forwarded_ports) 107 if self.ports_to_forward and not self.has_forwarded_ports: 108 self.has_forwarded_ports = True 109 tool = valgrind_tools.CreateTool(None, self.device) 110 forwarder.Forwarder.Map([(port, port) for port in self.ports_to_forward], 111 self.device, tool) 112 113 def __RunJavaTest(self, test, test_pkg, additional_flags=None): 114 """Runs a single Java test in a Java TestRunner. 115 116 Args: 117 test: Fully qualified test name (ex. foo.bar.TestClass#testMethod) 118 test_pkg: TestPackage object. 119 additional_flags: A list of additional flags to add to the command line. 120 121 Returns: 122 TestRunResults object with a single test result. 123 """ 124 # TODO(bulach): move this to SetUp() stage. 125 self.__StartForwarder() 126 127 java_test_runner = test_runner.TestRunner(self.instrumentation_options, 128 self.device_id, 129 self.shard_index, test_pkg, 130 additional_flags=additional_flags) 131 try: 132 java_test_runner.SetUp() 133 return java_test_runner.RunTest(test)[0] 134 finally: 135 java_test_runner.TearDown() 136 137 def _RunJavaTestFilters(self, test_filters, additional_flags=None): 138 """Calls a list of tests and stops at the first test failure. 139 140 This method iterates until either it encounters a non-passing test or it 141 exhausts the list of tests. Then it returns the appropriate overall result. 142 143 Test cases may make use of this method internally to assist in running 144 instrumentation tests. This function relies on instrumentation_options 145 being defined. 146 147 Args: 148 test_filters: A list of Java test filters. 149 additional_flags: A list of addition flags to add to the command line. 150 151 Returns: 152 A TestRunResults object containing an overall result for this set of Java 153 tests. If any Java tests do not pass, this is a fail overall. 154 """ 155 test_type = base_test_result.ResultType.PASS 156 log = '' 157 158 test_pkg = test_package.TestPackage( 159 self.instrumentation_options.test_apk_path, 160 self.instrumentation_options.test_apk_jar_path, 161 self.instrumentation_options.test_support_apk_path) 162 163 start_ms = int(time.time()) * 1000 164 done = False 165 for test_filter in test_filters: 166 tests = test_pkg.GetAllMatchingTests(None, None, test_filter) 167 # Filters should always result in >= 1 test. 168 if len(tests) == 0: 169 raise Exception('Java test filter "%s" returned no tests.' 170 % test_filter) 171 for test in tests: 172 # We're only running one test at a time, so this TestRunResults object 173 # will hold only one result. 174 java_result = self.__RunJavaTest(test, test_pkg, additional_flags) 175 assert len(java_result.GetAll()) == 1 176 if not java_result.DidRunPass(): 177 result = java_result.GetNotPass().pop() 178 log = result.GetLog() 179 log += self.__GetHostForwarderLog() 180 test_type = result.GetType() 181 done = True 182 break 183 if done: 184 break 185 duration_ms = int(time.time()) * 1000 - start_ms 186 187 overall_result = base_test_result.TestRunResults() 188 overall_result.AddResult( 189 test_result.InstrumentationTestResult( 190 self.tagged_name, test_type, start_ms, duration_ms, log=log)) 191 return overall_result 192 193 def __str__(self): 194 return self.tagged_name 195 196 def __repr__(self): 197 return self.tagged_name 198