• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 chromium_linker_test_apk
33     build/android/test_runner.py linker
34
35"""
36# pylint: disable=R0201
37
38import logging
39import re
40
41from devil.android import device_errors
42from devil.android.sdk import intent
43from pylib.base import base_test_result
44
45
46ResultType = base_test_result.ResultType
47
48_PACKAGE_NAME = 'org.chromium.chromium_linker_test_apk'
49_ACTIVITY_NAME = '.ChromiumLinkerTestActivity'
50_COMMAND_LINE_FILE = '/data/local/tmp/chromium-linker-test-command-line'
51
52# Logcat filters used during each test. Only the 'chromium' one is really
53# needed, but the logs are added to the TestResult in case of error, and
54# it is handy to have others as well when troubleshooting.
55_LOGCAT_FILTERS = ['*:s', 'chromium:v', 'cr_chromium:v',
56                   'cr_ChromiumAndroidLinker:v', 'cr_LibraryLoader:v',
57                   'cr_LinkerTest:v']
58#_LOGCAT_FILTERS = ['*:v']  ## DEBUG
59
60# Regular expression used to match status lines in logcat.
61_RE_BROWSER_STATUS_LINE = re.compile(r' BROWSER_LINKER_TEST: (FAIL|SUCCESS)$')
62_RE_RENDERER_STATUS_LINE = re.compile(r' RENDERER_LINKER_TEST: (FAIL|SUCCESS)$')
63
64def _StartActivityAndWaitForLinkerTestStatus(device, timeout):
65  """Force-start an activity and wait up to |timeout| seconds until the full
66     linker test status lines appear in the logcat, recorded through |device|.
67  Args:
68    device: A DeviceUtils instance.
69    timeout: Timeout in seconds
70  Returns:
71    A (status, logs) tuple, where status is a ResultType constant, and logs
72    if the final logcat output as a string.
73  """
74
75  # 1. Start recording logcat with appropriate filters.
76  with device.GetLogcatMonitor(filter_specs=_LOGCAT_FILTERS) as logmon:
77
78    # 2. Force-start activity.
79    device.StartActivity(
80        intent.Intent(package=_PACKAGE_NAME, activity=_ACTIVITY_NAME),
81        force_stop=True)
82
83    # 3. Wait up to |timeout| seconds until the test status is in the logcat.
84    result = ResultType.PASS
85    try:
86      browser_match = logmon.WaitFor(_RE_BROWSER_STATUS_LINE, timeout=timeout)
87      logging.debug('Found browser match: %s', browser_match.group(0))
88      renderer_match = logmon.WaitFor(_RE_RENDERER_STATUS_LINE,
89                                      timeout=timeout)
90      logging.debug('Found renderer match: %s', renderer_match.group(0))
91      if (browser_match.group(1) != 'SUCCESS'
92          or renderer_match.group(1) != 'SUCCESS'):
93        result = ResultType.FAIL
94    except device_errors.CommandTimeoutError:
95      result = ResultType.TIMEOUT
96
97    return result, '\n'.join(device.adb.Logcat(dump=True))
98
99
100class LibraryLoadMap(dict):
101  """A helper class to pretty-print a map of library names to load addresses."""
102  def __str__(self):
103    items = ['\'%s\': 0x%x' % (name, address) for \
104        (name, address) in self.iteritems()]
105    return '{%s}' % (', '.join(items))
106
107  def __repr__(self):
108    return 'LibraryLoadMap(%s)' % self.__str__()
109
110
111class AddressList(list):
112  """A helper class to pretty-print a list of load addresses."""
113  def __str__(self):
114    items = ['0x%x' % address for address in self]
115    return '[%s]' % (', '.join(items))
116
117  def __repr__(self):
118    return 'AddressList(%s)' % self.__str__()
119
120
121class LinkerTestCaseBase(object):
122  """Base class for linker test cases."""
123
124  def __init__(self, is_modern_linker=False, is_low_memory=False):
125    """Create a test case.
126    Args:
127      is_modern_linker: True to test ModernLinker, False to test LegacyLinker.
128      is_low_memory: True to simulate a low-memory device, False otherwise.
129    """
130    self.is_modern_linker = is_modern_linker
131    if is_modern_linker:
132      test_suffix = 'ForModernLinker'
133    else:
134      test_suffix = 'ForLegacyLinker'
135    self.is_low_memory = is_low_memory
136    if is_low_memory:
137      test_suffix += 'LowMemoryDevice'
138    else:
139      test_suffix += 'RegularDevice'
140    class_name = self.__class__.__name__
141    self.qualified_name = '%s.%s' % (class_name, test_suffix)
142    self.tagged_name = self.qualified_name
143
144  def _RunTest(self, _device):
145    """Run the test, must be overriden.
146    Args:
147      _device: A DeviceUtils interface.
148    Returns:
149      A (status, log) tuple, where <status> is a ResultType constant, and <log>
150      is the logcat output captured during the test in case of error, or None
151      in case of success.
152    """
153    return ResultType.FAIL, 'Unimplemented _RunTest() method!'
154
155  def Run(self, device):
156    """Run the test on a given device.
157    Args:
158      device: Name of target device where to run the test.
159    Returns:
160      A base_test_result.TestRunResult() instance.
161    """
162    margin = 8
163    print '[ %-*s ] %s' % (margin, 'RUN', self.tagged_name)
164    logging.info('Running linker test: %s', self.tagged_name)
165
166    # Create command-line file on device.
167    if self.is_modern_linker:
168      command_line_flags = '--use-linker=modern'
169    else:
170      command_line_flags = '--use-linker=legacy'
171    if self.is_low_memory:
172      command_line_flags += ' --low-memory-device'
173    device.WriteFile(_COMMAND_LINE_FILE, command_line_flags)
174
175    # Run the test.
176    status, logs = self._RunTest(device)
177
178    result_text = 'OK'
179    if status == ResultType.FAIL:
180      result_text = 'FAILED'
181    elif status == ResultType.TIMEOUT:
182      result_text = 'TIMEOUT'
183    print '[ %*s ] %s' % (margin, result_text, self.tagged_name)
184
185    results = base_test_result.TestRunResults()
186    results.AddResult(
187        base_test_result.BaseTestResult(
188            self.tagged_name,
189            status,
190            log=logs))
191
192    return results
193
194  def __str__(self):
195    return self.tagged_name
196
197  def __repr__(self):
198    return self.tagged_name
199
200
201class LinkerSharedRelroTest(LinkerTestCaseBase):
202  """A linker test case to check the status of shared RELRO sections.
203
204    The core of the checks performed here are pretty simple:
205
206      - Clear the logcat and start recording with an appropriate set of filters.
207      - Create the command-line appropriate for the test-case.
208      - Start the activity (always forcing a cold start).
209      - Every second, look at the current content of the filtered logcat lines
210        and look for instances of the following:
211
212            BROWSER_LINKER_TEST: <status>
213            RENDERER_LINKER_TEST: <status>
214
215        where <status> can be either FAIL or SUCCESS. These lines can appear
216        in any order in the logcat. Once both browser and renderer status are
217        found, stop the loop. Otherwise timeout after 30 seconds.
218
219        Note that there can be other lines beginning with BROWSER_LINKER_TEST:
220        and RENDERER_LINKER_TEST:, but are not followed by a <status> code.
221
222      - The test case passes if the <status> for both the browser and renderer
223        process are SUCCESS. Otherwise its a fail.
224  """
225  def _RunTest(self, device):
226    # Wait up to 30 seconds until the linker test status is in the logcat.
227    return _StartActivityAndWaitForLinkerTestStatus(device, timeout=30)
228