OLD | NEW |
1 # Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """Base class for linker-specific test cases. | 5 """Base class for linker-specific test cases. |
6 | 6 |
7 The custom dynamic linker can only be tested through a custom test case | 7 The custom dynamic linker can only be tested through a custom test case |
8 for various technical reasons: | 8 for various technical reasons: |
9 | 9 |
10 - It's an 'invisible feature', i.e. it doesn't expose a new API or | 10 - It's an 'invisible feature', i.e. it doesn't expose a new API or |
(...skipping 24 matching lines...) Expand all Loading... |
35 """ | 35 """ |
36 # pylint: disable=R0201 | 36 # pylint: disable=R0201 |
37 | 37 |
38 import logging | 38 import logging |
39 import os | 39 import os |
40 import re | 40 import re |
41 import time | 41 import time |
42 | 42 |
43 from pylib import constants | 43 from pylib import constants |
44 from pylib.base import base_test_result | 44 from pylib.base import base_test_result |
| 45 from pylib.device import device_errors |
45 from pylib.device import intent | 46 from pylib.device import intent |
46 | 47 |
47 | 48 |
48 ResultType = base_test_result.ResultType | 49 ResultType = base_test_result.ResultType |
49 | 50 |
50 _PACKAGE_NAME = 'org.chromium.chromium_linker_test_apk' | 51 _PACKAGE_NAME = 'org.chromium.chromium_linker_test_apk' |
51 _ACTIVITY_NAME = '.ChromiumLinkerTestActivity' | 52 _ACTIVITY_NAME = '.ChromiumLinkerTestActivity' |
52 _COMMAND_LINE_FILE = '/data/local/tmp/chromium-linker-test-command-line' | 53 _COMMAND_LINE_FILE = '/data/local/tmp/chromium-linker-test-command-line' |
53 | 54 |
54 # Path to the Linker.java source file. | 55 # Path to the Linker.java source file. |
55 _LINKER_JAVA_SOURCE_PATH = ( | 56 _LINKER_JAVA_SOURCE_PATH = ( |
56 'base/android/java/src/org/chromium/base/library_loader/Linker.java') | 57 'base/android/java/src/org/chromium/base/library_loader/Linker.java') |
57 | 58 |
58 # A regular expression used to extract the browser shared RELRO configuration | 59 # A regular expression used to extract the browser shared RELRO configuration |
59 # from the Java source file above. | 60 # from the Java source file above. |
60 _RE_LINKER_BROWSER_CONFIG = re.compile( | 61 _RE_LINKER_BROWSER_CONFIG = re.compile( |
61 r'.*BROWSER_SHARED_RELRO_CONFIG\s+=\s+' + | 62 r'.*BROWSER_SHARED_RELRO_CONFIG\s+=\s+' + |
62 r'BROWSER_SHARED_RELRO_CONFIG_(\S+)\s*;.*', | 63 r'BROWSER_SHARED_RELRO_CONFIG_(\S+)\s*;.*', |
63 re.MULTILINE | re.DOTALL) | 64 re.MULTILINE | re.DOTALL) |
64 | 65 |
65 # Logcat filters used during each test. Only the 'chromium' one is really | 66 # Logcat filters used during each test. Only the 'chromium' one is really |
66 # needed, but the logs are added to the TestResult in case of error, and | 67 # needed, but the logs are added to the TestResult in case of error, and |
67 # it is handy to have the 'chromium_android_linker' ones as well when | 68 # it is handy to have the 'chromium_android_linker' ones as well when |
68 # troubleshooting. | 69 # troubleshooting. |
69 _LOGCAT_FILTERS = ['*:s', 'chromium:v', 'chromium_android_linker:v'] | 70 _LOGCAT_FILTERS = ['*:s', 'chromium:v', 'chromium_android_linker:v'] |
70 #_LOGCAT_FILTERS = ['*:v'] ## DEBUG | 71 #_LOGCAT_FILTERS = ['*:v'] ## DEBUG |
71 | 72 |
72 # Regular expression used to match status lines in logcat. | 73 # Regular expression used to match status lines in logcat. |
73 re_status_line = re.compile(r'(BROWSER|RENDERER)_LINKER_TEST: (FAIL|SUCCESS)') | 74 _RE_BROWSER_STATUS_LINE = re.compile(r' BROWSER_LINKER_TEST: (FAIL|SUCCESS)$') |
| 75 _RE_RENDERER_STATUS_LINE = re.compile(r' RENDERER_LINKER_TEST: (FAIL|SUCCESS)$') |
74 | 76 |
75 # Regular expression used to mach library load addresses in logcat. | 77 # Regular expression used to mach library load addresses in logcat. |
76 re_library_address = re.compile( | 78 _RE_LIBRARY_ADDRESS = re.compile( |
77 r'(BROWSER|RENDERER)_LIBRARY_ADDRESS: (\S+) ([0-9A-Fa-f]+)') | 79 r'(BROWSER|RENDERER)_LIBRARY_ADDRESS: (\S+) ([0-9A-Fa-f]+)') |
78 | 80 |
79 | 81 |
80 def _GetBrowserSharedRelroConfig(): | 82 def _GetBrowserSharedRelroConfig(): |
81 """Returns a string corresponding to the Linker's configuration of shared | 83 """Returns a string corresponding to the Linker's configuration of shared |
82 RELRO sections in the browser process. This parses the Java linker source | 84 RELRO sections in the browser process. This parses the Java linker source |
83 file to get the appropriate information. | 85 file to get the appropriate information. |
84 Return: | 86 Return: |
85 None in case of error (e.g. could not locate the source file). | 87 None in case of error (e.g. could not locate the source file). |
86 'NEVER' if the browser process shall never use shared RELROs. | 88 'NEVER' if the browser process shall never use shared RELROs. |
(...skipping 15 matching lines...) Expand all Loading... |
102 return None | 104 return None |
103 | 105 |
104 if configs[0] not in ['NEVER', 'LOW_RAM_ONLY', 'ALWAYS']: | 106 if configs[0] not in ['NEVER', 'LOW_RAM_ONLY', 'ALWAYS']: |
105 logging.error('Unexpected browser config value: ' + configs[0]) | 107 logging.error('Unexpected browser config value: ' + configs[0]) |
106 return None | 108 return None |
107 | 109 |
108 logging.info('Found linker browser shared RELRO config: ' + configs[0]) | 110 logging.info('Found linker browser shared RELRO config: ' + configs[0]) |
109 return configs[0] | 111 return configs[0] |
110 | 112 |
111 | 113 |
112 def _WriteCommandLineFile(device, command_line, command_line_file): | |
113 """Create a command-line file on the device. This does not use FlagChanger | |
114 because its implementation assumes the device has 'su', and thus does | |
115 not work at all with production devices.""" | |
116 device.RunShellCommand( | |
117 'echo "%s" > %s' % (command_line, command_line_file)) | |
118 | |
119 | |
120 def _CheckLinkerTestStatus(logcat): | |
121 """Parse the content of |logcat| and checks for both a browser and | |
122 renderer status line. | |
123 | |
124 Args: | |
125 logcat: A string to parse. Can include line separators. | |
126 | |
127 Returns: | |
128 A tuple, result[0] is True if there is a complete match, then | |
129 result[1] and result[2] will be True or False to reflect the | |
130 test status for the browser and renderer processes, respectively. | |
131 """ | |
132 browser_found = False | |
133 renderer_found = False | |
134 for m in re_status_line.finditer(logcat): | |
135 process_type, status = m.groups() | |
136 if process_type == 'BROWSER': | |
137 browser_found = True | |
138 browser_success = (status == 'SUCCESS') | |
139 elif process_type == 'RENDERER': | |
140 renderer_found = True | |
141 renderer_success = (status == 'SUCCESS') | |
142 else: | |
143 assert False, 'Invalid process type ' + process_type | |
144 | |
145 if browser_found and renderer_found: | |
146 return (True, browser_success, renderer_success) | |
147 | |
148 # Didn't find anything. | |
149 return (False, None, None) | |
150 | |
151 | |
152 def _StartActivityAndWaitForLinkerTestStatus(device, timeout): | 114 def _StartActivityAndWaitForLinkerTestStatus(device, timeout): |
153 """Force-start an activity and wait up to |timeout| seconds until the full | 115 """Force-start an activity and wait up to |timeout| seconds until the full |
154 linker test status lines appear in the logcat, recorded through |device|. | 116 linker test status lines appear in the logcat, recorded through |device|. |
155 Args: | 117 Args: |
156 device: A DeviceUtils instance. | 118 device: A DeviceUtils instance. |
157 timeout: Timeout in seconds | 119 timeout: Timeout in seconds |
158 Returns: | 120 Returns: |
159 A (status, logs) tuple, where status is a ResultType constant, and logs | 121 A (status, logs) tuple, where status is a ResultType constant, and logs |
160 if the final logcat output as a string. | 122 if the final logcat output as a string. |
161 """ | 123 """ |
| 124 |
162 # 1. Start recording logcat with appropriate filters. | 125 # 1. Start recording logcat with appropriate filters. |
163 device.old_interface.StartRecordingLogcat( | 126 with device.GetLogcatMonitor(filters=_LOGCAT_FILTERS) as logmon: |
164 clear=True, filters=_LOGCAT_FILTERS) | |
165 | 127 |
166 try: | |
167 # 2. Force-start activity. | 128 # 2. Force-start activity. |
168 device.StartActivity( | 129 device.StartActivity( |
169 intent.Intent(package=_PACKAGE_NAME, activity=_ACTIVITY_NAME), | 130 intent.Intent(package=_PACKAGE_NAME, activity=_ACTIVITY_NAME), |
170 force_stop=True) | 131 force_stop=True) |
171 | 132 |
172 # 3. Wait up to |timeout| seconds until the test status is in the logcat. | 133 # 3. Wait up to |timeout| seconds until the test status is in the logcat. |
173 num_tries = 0 | 134 result = ResultType.PASS |
174 max_tries = timeout | 135 try: |
175 found = False | 136 browser_match = logmon.WaitFor(_RE_BROWSER_STATUS_LINE, timeout=timeout) |
176 while num_tries < max_tries: | 137 logging.debug('Found browser match: %s', browser_match.group(0)) |
177 time.sleep(1) | 138 renderer_match = logmon.WaitFor(_RE_RENDERER_STATUS_LINE, |
178 num_tries += 1 | 139 timeout=timeout) |
179 found, browser_ok, renderer_ok = _CheckLinkerTestStatus( | 140 logging.debug('Found renderer match: %s', renderer_match.group(0)) |
180 device.old_interface.GetCurrentRecordedLogcat()) | 141 if (browser_match.group(1) != 'SUCCESS' |
181 if found: | 142 or renderer_match.group(1) != 'SUCCESS'): |
182 break | 143 result = ResultType.FAIL |
| 144 except device_errors.CommandTimeoutError: |
| 145 result = ResultType.TIMEOUT |
183 | 146 |
184 finally: | 147 return result, '\n'.join(device.adb.Logcat(dump=True)) |
185 logs = device.old_interface.StopRecordingLogcat() | |
186 | |
187 if num_tries >= max_tries: | |
188 return ResultType.TIMEOUT, logs | |
189 | |
190 if browser_ok and renderer_ok: | |
191 return ResultType.PASS, logs | |
192 | |
193 return ResultType.FAIL, logs | |
194 | 148 |
195 | 149 |
196 class LibraryLoadMap(dict): | 150 class LibraryLoadMap(dict): |
197 """A helper class to pretty-print a map of library names to load addresses.""" | 151 """A helper class to pretty-print a map of library names to load addresses.""" |
198 def __str__(self): | 152 def __str__(self): |
199 items = ['\'%s\': 0x%x' % (name, address) for \ | 153 items = ['\'%s\': 0x%x' % (name, address) for \ |
200 (name, address) in self.iteritems()] | 154 (name, address) in self.iteritems()] |
201 return '{%s}' % (', '.join(items)) | 155 return '{%s}' % (', '.join(items)) |
202 | 156 |
203 def __repr__(self): | 157 def __repr__(self): |
(...skipping 15 matching lines...) Expand all Loading... |
219 browser and renderer processes. | 173 browser and renderer processes. |
220 Args: | 174 Args: |
221 logs: A string containing logcat output. | 175 logs: A string containing logcat output. |
222 Returns: | 176 Returns: |
223 A tuple (browser_libs, renderer_libs), where each item is a map of | 177 A tuple (browser_libs, renderer_libs), where each item is a map of |
224 library names (strings) to library load addresses (ints), for the | 178 library names (strings) to library load addresses (ints), for the |
225 browser and renderer processes, respectively. | 179 browser and renderer processes, respectively. |
226 """ | 180 """ |
227 browser_libs = LibraryLoadMap() | 181 browser_libs = LibraryLoadMap() |
228 renderer_libs = LibraryLoadMap() | 182 renderer_libs = LibraryLoadMap() |
229 for m in re_library_address.finditer(logs): | 183 for m in _RE_LIBRARY_ADDRESS.finditer(logs): |
230 process_type, lib_name, lib_address = m.groups() | 184 process_type, lib_name, lib_address = m.groups() |
231 lib_address = int(lib_address, 16) | 185 lib_address = int(lib_address, 16) |
232 if process_type == 'BROWSER': | 186 if process_type == 'BROWSER': |
233 browser_libs[lib_name] = lib_address | 187 browser_libs[lib_name] = lib_address |
234 elif process_type == 'RENDERER': | 188 elif process_type == 'RENDERER': |
235 renderer_libs[lib_name] = lib_address | 189 renderer_libs[lib_name] = lib_address |
236 else: | 190 else: |
237 assert False, 'Invalid process type' | 191 assert False, 'Invalid process type' |
238 | 192 |
239 return browser_libs, renderer_libs | 193 return browser_libs, renderer_libs |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
316 A base_test_result.TestRunResult() instance. | 270 A base_test_result.TestRunResult() instance. |
317 """ | 271 """ |
318 margin = 8 | 272 margin = 8 |
319 print '[ %-*s ] %s' % (margin, 'RUN', self.tagged_name) | 273 print '[ %-*s ] %s' % (margin, 'RUN', self.tagged_name) |
320 logging.info('Running linker test: %s', self.tagged_name) | 274 logging.info('Running linker test: %s', self.tagged_name) |
321 | 275 |
322 # Create command-line file on device. | 276 # Create command-line file on device. |
323 command_line_flags = '' | 277 command_line_flags = '' |
324 if self.is_low_memory: | 278 if self.is_low_memory: |
325 command_line_flags = '--low-memory-device' | 279 command_line_flags = '--low-memory-device' |
326 _WriteCommandLineFile(device, command_line_flags, _COMMAND_LINE_FILE) | 280 device.WriteFile(_COMMAND_LINE_FILE, command_line_flags) |
327 | 281 |
328 # Run the test. | 282 # Run the test. |
329 status, logs = self._RunTest(device) | 283 status, logs = self._RunTest(device) |
330 | 284 |
331 result_text = 'OK' | 285 result_text = 'OK' |
332 if status == ResultType.FAIL: | 286 if status == ResultType.FAIL: |
333 result_text = 'FAILED' | 287 result_text = 'FAILED' |
334 elif status == ResultType.TIMEOUT: | 288 elif status == ResultType.TIMEOUT: |
335 result_text = 'TIMEOUT' | 289 result_text = 'TIMEOUT' |
336 print '[ %*s ] %s' % (margin, result_text, self.tagged_name) | 290 print '[ %*s ] %s' % (margin, result_text, self.tagged_name) |
(...skipping 196 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
533 # Note that this behaviour doesn't seem to happen when starting an | 487 # Note that this behaviour doesn't seem to happen when starting an |
534 # application 'normally', i.e. when using the application launcher to | 488 # application 'normally', i.e. when using the application launcher to |
535 # start the activity. | 489 # start the activity. |
536 logging.info('Ignoring system\'s low randomization of browser libraries' + | 490 logging.info('Ignoring system\'s low randomization of browser libraries' + |
537 ' for regular devices') | 491 ' for regular devices') |
538 | 492 |
539 if not renderer_status: | 493 if not renderer_status: |
540 return ResultType.FAIL, renderer_logs | 494 return ResultType.FAIL, renderer_logs |
541 | 495 |
542 return ResultType.PASS, logs | 496 return ResultType.PASS, logs |
OLD | NEW |