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