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 _LINKER_TAG = 'ContentLinkerTest' | |
68 _PACKAGE_NAME='org.chromium.content_linker_test_apk' | |
69 _ACTIVITY_NAME='.ContentLinkerTestActivity' | |
70 _COMMAND_LINE_FILE='/data/local/tmp/content-linker-test-command-line' | |
71 | |
72 # Logcat filters used during each test. Only the 'chromium' one is really | |
73 # needed, but the logs are added to the TestResult in case of error, and | |
74 # it is handy to have the 'content_android_linker' ones as well when | |
75 # troubleshooting. | |
76 _LOGCAT_FILTERS = [ '*:s', 'chromium:v', 'content_android_linker:v' ] | |
77 | |
78 | |
79 def _CheckPrefixedTestStatus(logcat, prefix): | |
frankf
2013/10/02 18:16:02
Are you not using regex in case logcat is very lar
digit1
2013/10/03 09:16:00
The logcat won't be very large, due to the filters
| |
80 """Parse the content of |logcat| for some text that begins | |
81 with |prefix| and is followed by 'FAIL' or 'SUCCESS'. | |
82 | |
83 Args: | |
84 logcat: A string to parse. Can include line separators. | |
85 prefix: The status prefix | |
86 | |
87 Returns: | |
88 A tuple, result[0] is True is a line was found, then | |
frankf
2013/10/02 18:16:02
is -> if
digit1
2013/10/03 09:16:00
Done.
| |
89 result[1] will be True for 'SUCCESS' and False for 'Fail' | |
90 """ | |
91 start = 0 | |
92 while True: | |
93 n = logcat.find(prefix, start) | |
94 if n < 0: | |
95 return (False, None) | |
96 | |
97 n += len(prefix) | |
98 if logcat.find('FAIL', n) == n: | |
99 return (True, False) | |
100 if logcat.find('SUCCESS', n) == n: | |
101 return (True, True) | |
102 | |
103 start = n | |
104 | |
105 | |
106 def _CheckLinkerTestStatus(logcat): | |
107 """Parse the content of |logcat| and checks for both a browser and | |
108 renderer status line. | |
109 | |
110 Args: | |
111 logcat: A string to parse. Can include line separators. | |
112 | |
113 Returns: | |
114 A tuple, result[0] is True if there is a complete match, then | |
115 result[1] and result[2] will be True or False to reflect the | |
116 test status for the browser and renderer processes, respectively. | |
117 """ | |
118 browser_found, browser_success = _CheckPrefixedTestStatus( | |
119 logcat, 'BROWSER_LINKER_TEST: ') | |
120 renderer_found, renderer_success = _CheckPrefixedTestStatus( | |
121 logcat, 'RENDERER_LINKER_TEST: ') | |
122 | |
123 if browser_found and renderer_found: | |
124 return (True, browser_success, renderer_success) | |
125 return (False, None, None) | |
frankf
2013/10/02 18:16:02
duplicate of line 128
digit1
2013/10/03 09:16:00
Done.
| |
126 | |
127 # Didn't find anything. | |
128 return (False, None, None) | |
129 | |
130 | |
131 def _CreateCommandLineFileOnDevice(adb, cmd_line): | |
frankf
2013/10/02 18:16:02
you can use flag_changer.py
digit1
2013/10/03 09:16:00
Done.
| |
132 """Create a command-line file on the device. | |
133 Args: | |
134 adb: An AndroidCommands instance to communicate with the device. | |
135 cmd_line: The command-line as a string. | |
136 """ | |
137 command_line_file = tempfile.NamedTemporaryFile() | |
138 command_line_file.write(cmd_line) | |
139 command_line_file.flush() | |
140 adb.PushIfNeeded(command_line_file.name, _COMMAND_LINE_FILE) | |
141 | |
142 | |
143 class LinkerTestCase(object): | |
144 """Base class for linker test cases.""" | |
145 | |
146 def __init__(self, test_name, is_low_memory=False): | |
147 """Create a test case initialized to run |test_name|. | |
148 | |
149 Args: | |
150 test_name: The name of the method to run as the test. | |
151 is_low_memory: True to simulate a low-memory device, False otherwise. | |
152 """ | |
153 self.test_name = test_name | |
154 class_name = self.__class__.__name__ | |
155 self.qualified_name = '%s.%s' % (class_name, self.test_name) | |
156 # Use tagged_name when creating results, so that we can identify linker | |
157 # tests in the overall results. | |
frankf
2013/10/02 18:16:02
isn't class_name sufficient for this. Note the lin
digit1
2013/10/03 09:16:00
The class name itself won't be sufficient, but sel
| |
158 self.tagged_name = '%s_%s' % (_LINKER_TAG, self.test_name) | |
159 self.is_low_memory = is_low_memory | |
160 | |
161 def Run(self, device): | |
162 margin = 8 | |
163 print "[ %-*s ] %s" % (margin, 'RUN', self.tagged_name) | |
frankf
2013/10/02 18:16:02
single quotes for consistancy
digit1
2013/10/03 09:16:00
Done.
| |
164 logging.info('Running linker test: %s', self.tagged_name) | |
165 adb = android_commands.AndroidCommands(device) | |
166 | |
167 # 1. Write command-line file with appropriate options. | |
168 command_line = '' | |
169 if self.is_low_memory: | |
170 command_line = '--low-memory-device' | |
171 _CreateCommandLineFileOnDevice(adb, command_line) | |
172 | |
173 # 2. Start recording logcat with appropriate filters. | |
174 adb.StartRecordingLogcat(clear=True, filters=_LOGCAT_FILTERS) | |
175 | |
176 try: | |
177 # 3. Force-start activity. | |
178 adb.StartActivity(package=_PACKAGE_NAME, | |
179 activity=_ACTIVITY_NAME, | |
180 force_stop=True) | |
181 | |
182 # 4. Wait up to 30 seconds until the linker test status is in the logcat. | |
183 max_tries = 30 | |
184 num_tries = 0 | |
185 found = False | |
186 logcat = None | |
187 while num_tries < max_tries: | |
188 time.sleep(1) | |
189 num_tries += 1 | |
190 found, browser_ok, renderer_ok = _CheckLinkerTestStatus( | |
191 adb.GetCurrentRecordedLogcat()) | |
192 if found: | |
193 break | |
194 | |
195 finally: | |
196 # Ensure the ADB polling process is always killed when | |
197 # the script is interrupted by the user with Ctrl-C. | |
198 logs = adb.StopRecordingLogcat() | |
199 | |
200 results = base_test_result.TestRunResults() | |
201 | |
202 if num_tries >= max_tries: | |
203 # Timeout | |
204 print '[ %*s ] %s' % (margin, 'TIMEOUT', self.tagged_name) | |
205 results.AddResult( | |
206 base_test_result.BaseTestResult( | |
207 self.test_name, | |
208 base_test_result.ResultType.TIMEOUT, | |
209 logs)) | |
210 elif browser_ok and renderer_ok: | |
211 # Passed | |
212 logging.info( | |
213 'Logcat start ---------------------------------\n%s' + | |
214 'Logcat end -----------------------------------', logs) | |
215 print '[ %*s ] %s' % (margin, 'OK', self.tagged_name) | |
216 results.AddResult( | |
217 base_test_result.BaseTestResult( | |
218 self.test_name, | |
219 base_test_result.ResultType.PASS)) | |
220 else: | |
221 print '[ %*s ] %s' % (margin, 'FAILED', self.tagged_name) | |
222 # Failed | |
223 results.AddResult( | |
224 base_test_result.BaseTestResult( | |
225 self.test_name, | |
226 base_test_result.ResultType.FAIL, | |
227 logs)) | |
228 | |
229 return results | |
230 | |
231 def __str__(self): | |
232 return self.tagged_name | |
233 | |
234 def __repr__(self): | |
235 return self.tagged_name | |
OLD | NEW |