Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 linker-specific test cases. | |
| 6 | |
| 7 The custom dynamic linker can only be tested through a custom test case | |
| 8 for various technical reasons: | |
| 9 | |
| 10 - It's an 'invisible feature', i.e. it doesn't expose a new API or | |
| 11 behaviour, all it does is save RAM when loading native libraries. | |
| 12 | |
| 13 - Checking that it works correctly requires several things that do not | |
| 14 fit the existing GTest-based and instrumentation-based tests: | |
| 15 | |
| 16 - Native test code needs to be run in both the browser and renderer | |
| 17 process at the same time just after loading native libraries, in | |
| 18 a completely asynchronous way. | |
| 19 | |
| 20 - Each test case requires restarting a whole new application process | |
| 21 with a different command-line. | |
| 22 | |
| 23 - Enabling test support in the Linker code requires building a special | |
| 24 APK with a flag to activate special test-only support code in the | |
| 25 Linker code itself. | |
| 26 | |
| 27 Host-driven tests have also been tried, but since they're really | |
| 28 sub-classes of instrumentation tests, they didn't work well either. | |
| 29 | |
| 30 To build and run the linker tests, do the following: | |
| 31 | |
| 32 ninja -C out/Debug content_linker_test_apk | |
| 33 build/android/test_runner.py linker | |
| 34 | |
| 35 The core of the checks performed here are pretty simple: | |
| 36 | |
| 37 - Clear the logcat and start recording with an appropriate set of filters. | |
| 38 - Create the command-line appropriate for the test-case. | |
| 39 - Start the activity (always forcing a cold start). | |
| 40 - Every second, look at the current content of the filtered logcat lines | |
| 41 and look for instances of the following: | |
| 42 | |
| 43 BROWSER_LINKER_TEST: <status> | |
| 44 RENDERER_LINKER_TEST: <status> | |
| 45 | |
| 46 where <status> can be either FAIL or SUCCESS. These lines can appear | |
| 47 in any order in the logcat. Once both browser and renderer status are | |
| 48 found, stop the loop. Otherwise timeout after 30 seconds. | |
| 49 | |
| 50 Note that there can be other lines beginning with BROWSER_LINKER_TEST: | |
| 51 and RENDERER_LINKER_TEST:, but are not followed by a <status> code. | |
| 52 | |
| 53 - The test case passes if the <status> for both the browser and renderer | |
| 54 process are SUCCESS. Otherwise its a fail. | |
| 55 """ | |
| 56 | |
| 57 import logging | |
| 58 import os | |
| 59 import StringIO | |
| 60 import subprocess | |
| 61 import tempfile | |
| 62 import time | |
| 63 | |
| 64 from pylib import android_commands | |
| 65 from pylib.base import base_test_result | |
| 66 | |
| 67 # aka the parent of com.google.android | |
| 68 BASE_ROOT = 'src' + os.sep | |
|
bulach
2013/10/02 11:16:50
is this equivalent of:
from pylib import constants
digit1
2013/10/02 14:01:52
Actually, this came from pylib/host_driven/test_ca
| |
| 69 | |
| 70 _LINKER_TAG = 'ContentLinkerTest' | |
| 71 _PACKAGE_NAME='org.chromium.content_linker_test_apk' | |
| 72 _ACTIVITY_NAME='.ContentLinkerTestActivity' | |
| 73 _COMMAND_LINE_FILE='/data/local/tmp/content-linker-test-command-line' | |
| 74 | |
| 75 # Logcat filters used during each test. Only the 'chromium' one is really | |
| 76 # needed, but the logs are added to the TestResult in case of error, and | |
| 77 # it is handy to have the 'content_android_linker' ones as well when | |
| 78 # troubleshooting. | |
| 79 _LOGCAT_FILTERS = [ '*:s', 'chromium:v', 'content_android_linker:v' ] | |
| 80 | |
| 81 #_LOGCAT_FILTERS = [ '*:v' ] ## DEBUG | |
| 82 | |
| 83 | |
| 84 def _CheckPrefixedTestStatus(logcat, prefix): | |
| 85 """Parse the content of |logcat| for some text that begins | |
| 86 with |prefix| and is followed by 'FAIL' or 'SUCCESS'. | |
| 87 | |
| 88 Args: | |
|
bulach
2013/10/02 11:16:50
truly nit: the Args: and Returns: are normally ali
digit1
2013/10/02 14:01:52
Oops, I've fixed this. Thanks.
| |
| 89 logcat: A string to parse. Can include line separators. | |
| 90 prefix: The status prefix | |
| 91 | |
| 92 Returns: | |
| 93 A tuple, result[0] is True is a line was found, then | |
| 94 result[1] will be True for 'SUCCESS' and False for 'Fail' | |
| 95 """ | |
| 96 start = 0 | |
| 97 while True: | |
| 98 n = logcat.find(prefix, start) | |
| 99 if n < 0: | |
| 100 return (False, None) | |
| 101 | |
| 102 n += len(prefix) | |
| 103 if logcat.find('FAIL', n) == n: | |
| 104 return (True, False) | |
| 105 if logcat.find('SUCCESS', n) == n: | |
| 106 return (True, True) | |
| 107 | |
| 108 start = n | |
| 109 | |
| 110 | |
| 111 def _CheckLinkerTestStatus(logcat): | |
| 112 """Parse the content of |logcat| and checks for both a browser and | |
| 113 renderer status line. | |
| 114 | |
| 115 Args: | |
| 116 logcat: A string to parse. Can include line separators. | |
| 117 | |
| 118 Returns: | |
| 119 A tuple, result[0] is True is there is a complete match, then | |
|
bulach
2013/10/02 11:16:50
nit: s/is there is/if there is/
digit1
2013/10/02 14:01:52
Done.
| |
| 120 result[1] and result[2] will be True or False to reflect the | |
| 121 test status for the browser and renderer processes, respectively. | |
| 122 """ | |
| 123 browser_found, browser_success = _CheckPrefixedTestStatus( | |
| 124 logcat, 'BROWSER_LINKER_TEST: ') | |
| 125 renderer_found, renderer_success = _CheckPrefixedTestStatus( | |
| 126 logcat, 'RENDERER_LINKER_TEST: ') | |
| 127 | |
| 128 if browser_found and renderer_found: | |
| 129 return (True, browser_success, renderer_success) | |
| 130 return (False, None, None) | |
| 131 | |
| 132 # Didn't find anything. | |
| 133 return (False, None) | |
|
bulach
2013/10/02 11:16:50
nit: , None so it always returns a 3-ary tuple?
digit1
2013/10/02 14:01:52
Done.
| |
| 134 | |
| 135 | |
| 136 def _CreateCommandLineFileOnDevice(adb, cmd_line): | |
| 137 """Create a command-line file on the device. | |
| 138 Args: | |
| 139 adb: An AndroidCommands instance to communicate with the device. | |
| 140 cmd_line: The command-line as a string. | |
| 141 """ | |
| 142 command_line_file = tempfile.NamedTemporaryFile() | |
| 143 command_line_file.write(cmd_line) | |
| 144 command_line_file.flush() | |
| 145 adb.PushIfNeeded(command_line_file.name, _COMMAND_LINE_FILE) | |
| 146 | |
| 147 | |
| 148 class LinkerTestCase(object): | |
| 149 """Base class for linker test cases.""" | |
| 150 | |
| 151 def __init__(self, test_name, is_low_memory=False): | |
| 152 """Create a test case initialized to run |test_name|. | |
| 153 | |
| 154 Args: | |
| 155 test_name: The name of the method to run as the test. | |
| 156 is_low_memory: True to simulate a low-memory device, False otherwise. | |
| 157 """ | |
| 158 self.test_name = test_name | |
| 159 class_name = self.__class__.__name__ | |
| 160 self.qualified_name = '%s.%s' % (class_name, self.test_name) | |
| 161 # Use tagged_name when creating results, so that we can identify linker | |
| 162 # tests in the overall results. | |
| 163 self.tagged_name = '%s_%s' % (_LINKER_TAG, self.test_name) | |
| 164 self.is_low_memory = is_low_memory | |
| 165 | |
| 166 def Run(self, device): | |
| 167 margin = 8 | |
| 168 print "[ %-*s ] %s" % (margin, "RUN", self.tagged_name) | |
|
bulach
2013/10/02 11:16:50
nit: s/"/'/ everywhere
digit1
2013/10/02 14:01:52
Done.
| |
| 169 logging.info('Running linker test: %s', self.tagged_name) | |
| 170 adb = android_commands.AndroidCommands(device) | |
| 171 | |
| 172 # 1. Write command-line file with appropriate options. | |
| 173 command_line = '' | |
| 174 if self.is_low_memory: | |
| 175 command_line = '--low-memory-device' | |
| 176 _CreateCommandLineFileOnDevice(adb, command_line) | |
| 177 | |
| 178 # 2. Start recording logcat with appropriate filters. | |
| 179 adb.StartRecordingLogcat(clear=True, filters=_LOGCAT_FILTERS) | |
| 180 | |
| 181 try: | |
| 182 # 3. Force-start activity. | |
| 183 adb.StartActivity(package=_PACKAGE_NAME, | |
| 184 activity=_ACTIVITY_NAME, | |
| 185 force_stop=True) | |
| 186 | |
| 187 # 4. Wait up to 30 seconds until the linker test status is in the logcat. | |
| 188 max_tries = 30 | |
| 189 num_tries = 0 | |
| 190 found = False | |
| 191 logcat = None | |
| 192 while num_tries < max_tries: | |
| 193 time.sleep(1) | |
| 194 num_tries += 1 | |
| 195 found, browser_ok, renderer_ok = _CheckLinkerTestStatus( | |
| 196 adb.GetCurrentRecordedLogcat()) | |
|
bulach
2013/10/02 11:16:50
hmm, would adb.StartMonitoringLogCat() (before Sta
digit1
2013/10/02 14:01:52
adb.WaitForLogMatch() isn't a good match here beca
| |
| 197 if found: | |
| 198 break | |
| 199 | |
| 200 finally: | |
| 201 # Ensure the ADB polling process is always killed when | |
| 202 # the script is interrupted by the user with Ctrl-C. | |
| 203 logs = adb.StopRecordingLogcat() | |
| 204 | |
| 205 results = base_test_result.TestRunResults() | |
| 206 | |
| 207 if num_tries >= max_tries: | |
| 208 # Timeout | |
| 209 print "[ %*s ] %s" % (margin, "TIMEOUT", self.tagged_name) | |
| 210 results.AddResult( | |
| 211 base_test_result.BaseTestResult( | |
| 212 self.test_name, | |
| 213 base_test_result.ResultType.TIMEOUT, | |
| 214 logs)) | |
| 215 elif browser_ok and renderer_ok: | |
| 216 # Passed | |
| 217 logging.info( | |
| 218 "Logcat start ---------------------------------\n%s" + \ | |
| 219 "Logcat end -----------------------------------", logs) | |
| 220 print "[ %*s ] %s" % (margin, "OK", self.tagged_name) | |
| 221 results.AddResult( | |
| 222 base_test_result.BaseTestResult( | |
| 223 self.test_name, | |
| 224 base_test_result.ResultType.PASS)) | |
| 225 else: | |
| 226 print "[ %*s ] %s" % (margin, "FAILED", self.tagged_name) | |
| 227 # Failed | |
| 228 results.AddResult( | |
| 229 base_test_result.BaseTestResult( | |
| 230 self.test_name, | |
| 231 base_test_result.ResultType.FAIL, | |
| 232 logs)) | |
| 233 | |
| 234 return results | |
| 235 | |
| 236 def __str__(self): | |
| 237 return self.tagged_name | |
| 238 | |
| 239 def __repr__(self): | |
| 240 return self.tagged_name | |
| OLD | NEW |