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 re |
| 60 import StringIO |
| 61 import subprocess |
| 62 import tempfile |
| 63 import time |
| 64 |
| 65 from pylib import android_commands |
| 66 from pylib import flag_changer |
| 67 from pylib.base import base_test_result |
| 68 |
| 69 |
| 70 _PACKAGE_NAME='org.chromium.content_linker_test_apk' |
| 71 _ACTIVITY_NAME='.ContentLinkerTestActivity' |
| 72 _COMMAND_LINE_FILE='/data/local/tmp/content-linker-test-command-line' |
| 73 |
| 74 # Logcat filters used during each test. Only the 'chromium' one is really |
| 75 # needed, but the logs are added to the TestResult in case of error, and |
| 76 # it is handy to have the 'content_android_linker' ones as well when |
| 77 # troubleshooting. |
| 78 _LOGCAT_FILTERS = [ '*:s', 'chromium:v', 'content_android_linker:v' ] |
| 79 |
| 80 # Regular expression used to match status lines in logcat. |
| 81 re_status_line = re.compile(r'(BROWSER|RENDERER)_LINKER_TEST: (FAIL|SUCCESS)') |
| 82 |
| 83 def _CheckLinkerTestStatus(logcat): |
| 84 """Parse the content of |logcat| and checks for both a browser and |
| 85 renderer status line. |
| 86 |
| 87 Args: |
| 88 logcat: A string to parse. Can include line separators. |
| 89 |
| 90 Returns: |
| 91 A tuple, result[0] is True if there is a complete match, then |
| 92 result[1] and result[2] will be True or False to reflect the |
| 93 test status for the browser and renderer processes, respectively. |
| 94 """ |
| 95 browser_found = False |
| 96 renderer_found = False |
| 97 for m in re_status_line.finditer(logcat): |
| 98 process_type, status = m.groups() |
| 99 if process_type == 'BROWSER': |
| 100 browser_found = True |
| 101 browser_success = (status == 'SUCCESS') |
| 102 elif process_type == 'RENDERER': |
| 103 renderer_found = True |
| 104 renderer_success = (status == 'SUCCESS') |
| 105 else: |
| 106 assert False, 'Invalid process type ' + process_type |
| 107 |
| 108 if browser_found and renderer_found: |
| 109 return (True, browser_success, renderer_success) |
| 110 |
| 111 # Didn't find anything. |
| 112 return (False, None, None) |
| 113 |
| 114 |
| 115 def _CreateCommandLineFileOnDevice(adb, flags): |
| 116 changer = flag_changer.FlagChanger(adb, _COMMAND_LINE_FILE) |
| 117 changer.Set(flags) |
| 118 |
| 119 |
| 120 class LinkerTestCase(object): |
| 121 """Base class for linker test cases.""" |
| 122 |
| 123 def __init__(self, test_name, is_low_memory=False): |
| 124 """Create a test case initialized to run |test_name|. |
| 125 |
| 126 Args: |
| 127 test_name: The name of the method to run as the test. |
| 128 is_low_memory: True to simulate a low-memory device, False otherwise. |
| 129 """ |
| 130 self.test_name = test_name |
| 131 class_name = self.__class__.__name__ |
| 132 self.qualified_name = '%s.%s' % (class_name, self.test_name) |
| 133 self.tagged_name = self.qualified_name |
| 134 self.is_low_memory = is_low_memory |
| 135 |
| 136 def Run(self, device): |
| 137 margin = 8 |
| 138 print '[ %-*s ] %s' % (margin, 'RUN', self.tagged_name) |
| 139 logging.info('Running linker test: %s', self.tagged_name) |
| 140 adb = android_commands.AndroidCommands(device) |
| 141 |
| 142 # 1. Write command-line file with appropriate options. |
| 143 command_line_flags = [] |
| 144 if self.is_low_memory: |
| 145 command_line_flags.append('--low-memory-device') |
| 146 _CreateCommandLineFileOnDevice(adb, command_line_flags) |
| 147 |
| 148 # 2. Start recording logcat with appropriate filters. |
| 149 adb.StartRecordingLogcat(clear=True, filters=_LOGCAT_FILTERS) |
| 150 |
| 151 try: |
| 152 # 3. Force-start activity. |
| 153 adb.StartActivity(package=_PACKAGE_NAME, |
| 154 activity=_ACTIVITY_NAME, |
| 155 force_stop=True) |
| 156 |
| 157 # 4. Wait up to 30 seconds until the linker test status is in the logcat. |
| 158 max_tries = 30 |
| 159 num_tries = 0 |
| 160 found = False |
| 161 logcat = None |
| 162 while num_tries < max_tries: |
| 163 time.sleep(1) |
| 164 num_tries += 1 |
| 165 found, browser_ok, renderer_ok = _CheckLinkerTestStatus( |
| 166 adb.GetCurrentRecordedLogcat()) |
| 167 if found: |
| 168 break |
| 169 |
| 170 finally: |
| 171 # Ensure the ADB polling process is always killed when |
| 172 # the script is interrupted by the user with Ctrl-C. |
| 173 logs = adb.StopRecordingLogcat() |
| 174 |
| 175 results = base_test_result.TestRunResults() |
| 176 |
| 177 if num_tries >= max_tries: |
| 178 # Timeout |
| 179 print '[ %*s ] %s' % (margin, 'TIMEOUT', self.tagged_name) |
| 180 results.AddResult( |
| 181 base_test_result.BaseTestResult( |
| 182 self.test_name, |
| 183 base_test_result.ResultType.TIMEOUT, |
| 184 logs)) |
| 185 elif browser_ok and renderer_ok: |
| 186 # Passed |
| 187 logging.info( |
| 188 'Logcat start ---------------------------------\n%s' + |
| 189 'Logcat end -----------------------------------', logs) |
| 190 print '[ %*s ] %s' % (margin, 'OK', self.tagged_name) |
| 191 results.AddResult( |
| 192 base_test_result.BaseTestResult( |
| 193 self.test_name, |
| 194 base_test_result.ResultType.PASS)) |
| 195 else: |
| 196 print '[ %*s ] %s' % (margin, 'FAILED', self.tagged_name) |
| 197 # Failed |
| 198 results.AddResult( |
| 199 base_test_result.BaseTestResult( |
| 200 self.test_name, |
| 201 base_test_result.ResultType.FAIL, |
| 202 logs)) |
| 203 |
| 204 return results |
| 205 |
| 206 def __str__(self): |
| 207 return self.tagged_name |
| 208 |
| 209 def __repr__(self): |
| 210 return self.tagged_name |
OLD | NEW |