| Index: build/android/pylib/linker/test_case.py
|
| diff --git a/build/android/pylib/linker/test_case.py b/build/android/pylib/linker/test_case.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..e387c0388a558da6cb96c692cb2c120953367cec
|
| --- /dev/null
|
| +++ b/build/android/pylib/linker/test_case.py
|
| @@ -0,0 +1,210 @@
|
| +# Copyright 2013 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +
|
| +"""Base class for linker-specific test cases.
|
| +
|
| + The custom dynamic linker can only be tested through a custom test case
|
| + for various technical reasons:
|
| +
|
| + - It's an 'invisible feature', i.e. it doesn't expose a new API or
|
| + behaviour, all it does is save RAM when loading native libraries.
|
| +
|
| + - Checking that it works correctly requires several things that do not
|
| + fit the existing GTest-based and instrumentation-based tests:
|
| +
|
| + - Native test code needs to be run in both the browser and renderer
|
| + process at the same time just after loading native libraries, in
|
| + a completely asynchronous way.
|
| +
|
| + - Each test case requires restarting a whole new application process
|
| + with a different command-line.
|
| +
|
| + - Enabling test support in the Linker code requires building a special
|
| + APK with a flag to activate special test-only support code in the
|
| + Linker code itself.
|
| +
|
| + Host-driven tests have also been tried, but since they're really
|
| + sub-classes of instrumentation tests, they didn't work well either.
|
| +
|
| + To build and run the linker tests, do the following:
|
| +
|
| + ninja -C out/Debug content_linker_test_apk
|
| + build/android/test_runner.py linker
|
| +
|
| + The core of the checks performed here are pretty simple:
|
| +
|
| + - Clear the logcat and start recording with an appropriate set of filters.
|
| + - Create the command-line appropriate for the test-case.
|
| + - Start the activity (always forcing a cold start).
|
| + - Every second, look at the current content of the filtered logcat lines
|
| + and look for instances of the following:
|
| +
|
| + BROWSER_LINKER_TEST: <status>
|
| + RENDERER_LINKER_TEST: <status>
|
| +
|
| + where <status> can be either FAIL or SUCCESS. These lines can appear
|
| + in any order in the logcat. Once both browser and renderer status are
|
| + found, stop the loop. Otherwise timeout after 30 seconds.
|
| +
|
| + Note that there can be other lines beginning with BROWSER_LINKER_TEST:
|
| + and RENDERER_LINKER_TEST:, but are not followed by a <status> code.
|
| +
|
| + - The test case passes if the <status> for both the browser and renderer
|
| + process are SUCCESS. Otherwise its a fail.
|
| +"""
|
| +
|
| +import logging
|
| +import os
|
| +import re
|
| +import StringIO
|
| +import subprocess
|
| +import tempfile
|
| +import time
|
| +
|
| +from pylib import android_commands
|
| +from pylib import flag_changer
|
| +from pylib.base import base_test_result
|
| +
|
| +
|
| +_PACKAGE_NAME='org.chromium.content_linker_test_apk'
|
| +_ACTIVITY_NAME='.ContentLinkerTestActivity'
|
| +_COMMAND_LINE_FILE='/data/local/tmp/content-linker-test-command-line'
|
| +
|
| +# Logcat filters used during each test. Only the 'chromium' one is really
|
| +# needed, but the logs are added to the TestResult in case of error, and
|
| +# it is handy to have the 'content_android_linker' ones as well when
|
| +# troubleshooting.
|
| +_LOGCAT_FILTERS = [ '*:s', 'chromium:v', 'content_android_linker:v' ]
|
| +
|
| +# Regular expression used to match status lines in logcat.
|
| +re_status_line = re.compile(r'(BROWSER|RENDERER)_LINKER_TEST: (FAIL|SUCCESS)')
|
| +
|
| +def _CheckLinkerTestStatus(logcat):
|
| + """Parse the content of |logcat| and checks for both a browser and
|
| + renderer status line.
|
| +
|
| + Args:
|
| + logcat: A string to parse. Can include line separators.
|
| +
|
| + Returns:
|
| + A tuple, result[0] is True if there is a complete match, then
|
| + result[1] and result[2] will be True or False to reflect the
|
| + test status for the browser and renderer processes, respectively.
|
| + """
|
| + browser_found = False
|
| + renderer_found = False
|
| + for m in re_status_line.finditer(logcat):
|
| + process_type, status = m.groups()
|
| + if process_type == 'BROWSER':
|
| + browser_found = True
|
| + browser_success = (status == 'SUCCESS')
|
| + elif process_type == 'RENDERER':
|
| + renderer_found = True
|
| + renderer_success = (status == 'SUCCESS')
|
| + else:
|
| + assert False, 'Invalid process type ' + process_type
|
| +
|
| + if browser_found and renderer_found:
|
| + return (True, browser_success, renderer_success)
|
| +
|
| + # Didn't find anything.
|
| + return (False, None, None)
|
| +
|
| +
|
| +def _CreateCommandLineFileOnDevice(adb, flags):
|
| + changer = flag_changer.FlagChanger(adb, _COMMAND_LINE_FILE)
|
| + changer.Set(flags)
|
| +
|
| +
|
| +class LinkerTestCase(object):
|
| + """Base class for linker test cases."""
|
| +
|
| + def __init__(self, test_name, is_low_memory=False):
|
| + """Create a test case initialized to run |test_name|.
|
| +
|
| + Args:
|
| + test_name: The name of the method to run as the test.
|
| + is_low_memory: True to simulate a low-memory device, False otherwise.
|
| + """
|
| + self.test_name = test_name
|
| + class_name = self.__class__.__name__
|
| + self.qualified_name = '%s.%s' % (class_name, self.test_name)
|
| + self.tagged_name = self.qualified_name
|
| + self.is_low_memory = is_low_memory
|
| +
|
| + def Run(self, device):
|
| + margin = 8
|
| + print '[ %-*s ] %s' % (margin, 'RUN', self.tagged_name)
|
| + logging.info('Running linker test: %s', self.tagged_name)
|
| + adb = android_commands.AndroidCommands(device)
|
| +
|
| + # 1. Write command-line file with appropriate options.
|
| + command_line_flags = []
|
| + if self.is_low_memory:
|
| + command_line_flags.append('--low-memory-device')
|
| + _CreateCommandLineFileOnDevice(adb, command_line_flags)
|
| +
|
| + # 2. Start recording logcat with appropriate filters.
|
| + adb.StartRecordingLogcat(clear=True, filters=_LOGCAT_FILTERS)
|
| +
|
| + try:
|
| + # 3. Force-start activity.
|
| + adb.StartActivity(package=_PACKAGE_NAME,
|
| + activity=_ACTIVITY_NAME,
|
| + force_stop=True)
|
| +
|
| + # 4. Wait up to 30 seconds until the linker test status is in the logcat.
|
| + max_tries = 30
|
| + num_tries = 0
|
| + found = False
|
| + logcat = None
|
| + while num_tries < max_tries:
|
| + time.sleep(1)
|
| + num_tries += 1
|
| + found, browser_ok, renderer_ok = _CheckLinkerTestStatus(
|
| + adb.GetCurrentRecordedLogcat())
|
| + if found:
|
| + break
|
| +
|
| + finally:
|
| + # Ensure the ADB polling process is always killed when
|
| + # the script is interrupted by the user with Ctrl-C.
|
| + logs = adb.StopRecordingLogcat()
|
| +
|
| + results = base_test_result.TestRunResults()
|
| +
|
| + if num_tries >= max_tries:
|
| + # Timeout
|
| + print '[ %*s ] %s' % (margin, 'TIMEOUT', self.tagged_name)
|
| + results.AddResult(
|
| + base_test_result.BaseTestResult(
|
| + self.test_name,
|
| + base_test_result.ResultType.TIMEOUT,
|
| + logs))
|
| + elif browser_ok and renderer_ok:
|
| + # Passed
|
| + logging.info(
|
| + 'Logcat start ---------------------------------\n%s' +
|
| + 'Logcat end -----------------------------------', logs)
|
| + print '[ %*s ] %s' % (margin, 'OK', self.tagged_name)
|
| + results.AddResult(
|
| + base_test_result.BaseTestResult(
|
| + self.test_name,
|
| + base_test_result.ResultType.PASS))
|
| + else:
|
| + print '[ %*s ] %s' % (margin, 'FAILED', self.tagged_name)
|
| + # Failed
|
| + results.AddResult(
|
| + base_test_result.BaseTestResult(
|
| + self.test_name,
|
| + base_test_result.ResultType.FAIL,
|
| + logs))
|
| +
|
| + return results
|
| +
|
| + def __str__(self):
|
| + return self.tagged_name
|
| +
|
| + def __repr__(self):
|
| + return self.tagged_name
|
|
|