Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(87)

Unified Diff: build/android/pylib/linker/test_case.py

Issue 25525003: Add new Android test runner command to handle linker tests. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Trivial rebase + reupload Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..a4b861a150f9ef8d5963277f7562487522893bc9
--- /dev/null
+++ b/build/android/pylib/linker/test_case.py
@@ -0,0 +1,240 @@
+# 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 StringIO
+import subprocess
+import tempfile
+import time
+
+from pylib import android_commands
+from pylib.base import base_test_result
+
+# aka the parent of com.google.android
+BASE_ROOT = 'src' + os.sep
bulach 2013/10/02 11:16:50 is this equivalent of: from pylib import constants
digit1 2013/10/02 14:01:52 Actually, this came from pylib/host_driven/test_ca
+
+_LINKER_TAG = 'ContentLinkerTest'
+_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' ]
+
+#_LOGCAT_FILTERS = [ '*:v' ] ## DEBUG
+
+
+def _CheckPrefixedTestStatus(logcat, prefix):
+ """Parse the content of |logcat| for some text that begins
+ with |prefix| and is followed by 'FAIL' or 'SUCCESS'.
+
+ Args:
bulach 2013/10/02 11:16:50 truly nit: the Args: and Returns: are normally ali
digit1 2013/10/02 14:01:52 Oops, I've fixed this. Thanks.
+ logcat: A string to parse. Can include line separators.
+ prefix: The status prefix
+
+ Returns:
+ A tuple, result[0] is True is a line was found, then
+ result[1] will be True for 'SUCCESS' and False for 'Fail'
+ """
+ start = 0
+ while True:
+ n = logcat.find(prefix, start)
+ if n < 0:
+ return (False, None)
+
+ n += len(prefix)
+ if logcat.find('FAIL', n) == n:
+ return (True, False)
+ if logcat.find('SUCCESS', n) == n:
+ return (True, True)
+
+ start = n
+
+
+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 is there is a complete match, then
bulach 2013/10/02 11:16:50 nit: s/is there is/if there is/
digit1 2013/10/02 14:01:52 Done.
+ result[1] and result[2] will be True or False to reflect the
+ test status for the browser and renderer processes, respectively.
+ """
+ browser_found, browser_success = _CheckPrefixedTestStatus(
+ logcat, 'BROWSER_LINKER_TEST: ')
+ renderer_found, renderer_success = _CheckPrefixedTestStatus(
+ logcat, 'RENDERER_LINKER_TEST: ')
+
+ if browser_found and renderer_found:
+ return (True, browser_success, renderer_success)
+ return (False, None, None)
+
+ # Didn't find anything.
+ return (False, None)
bulach 2013/10/02 11:16:50 nit: , None so it always returns a 3-ary tuple?
digit1 2013/10/02 14:01:52 Done.
+
+
+def _CreateCommandLineFileOnDevice(adb, cmd_line):
+ """Create a command-line file on the device.
+ Args:
+ adb: An AndroidCommands instance to communicate with the device.
+ cmd_line: The command-line as a string.
+ """
+ command_line_file = tempfile.NamedTemporaryFile()
+ command_line_file.write(cmd_line)
+ command_line_file.flush()
+ adb.PushIfNeeded(command_line_file.name, _COMMAND_LINE_FILE)
+
+
+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)
+ # Use tagged_name when creating results, so that we can identify linker
+ # tests in the overall results.
+ self.tagged_name = '%s_%s' % (_LINKER_TAG, self.test_name)
+ self.is_low_memory = is_low_memory
+
+ def Run(self, device):
+ margin = 8
+ print "[ %-*s ] %s" % (margin, "RUN", self.tagged_name)
bulach 2013/10/02 11:16:50 nit: s/"/'/ everywhere
digit1 2013/10/02 14:01:52 Done.
+ logging.info('Running linker test: %s', self.tagged_name)
+ adb = android_commands.AndroidCommands(device)
+
+ # 1. Write command-line file with appropriate options.
+ command_line = ''
+ if self.is_low_memory:
+ command_line = '--low-memory-device'
+ _CreateCommandLineFileOnDevice(adb, command_line)
+
+ # 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())
bulach 2013/10/02 11:16:50 hmm, would adb.StartMonitoringLogCat() (before Sta
digit1 2013/10/02 14:01:52 adb.WaitForLogMatch() isn't a good match here beca
+ 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

Powered by Google App Engine
This is Rietveld 408576698