diff options
author | initial.commit@chromium.org <initial.commit@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-11-02 02:14:31 +0000 |
---|---|---|
committer | initial.commit@chromium.org <initial.commit@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-11-02 02:14:31 +0000 |
commit | 5a7bdf208c28c210b39cff63d1cf91302b02821b (patch) | |
tree | 5ff484561562f78b29d2670168a9e3b40b48dcab /ceee/tools | |
parent | 26a54a5e0f4603c8fda2fe51789d331125993c9a (diff) | |
download | chromium_src-5a7bdf208c28c210b39cff63d1cf91302b02821b.zip chromium_src-5a7bdf208c28c210b39cff63d1cf91302b02821b.tar.gz chromium_src-5a7bdf208c28c210b39cff63d1cf91302b02821b.tar.bz2 |
Checking in the initial version of CEEE (Chrome Extensions Execution
Environment), an optional feature of Chrome Frame that acts as an
adapter layer for a subset of the Chrome Extension APIs. This enables
extensions that stick to the supported subset of APIs to work in the
context of Chrome Frame with minimal or sometimes no changes.
See http://www.chromium.org/developers/design-documents/ceee for an
overview of the design of CEEE.
TEST=unit tests (run ceee/smoke_tests.bat as an administrator on Windows)
BUG=none
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@64712 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ceee/tools')
-rw-r--r-- | ceee/tools/smoke_test.py | 212 | ||||
-rw-r--r-- | ceee/tools/verifier.py | 303 |
2 files changed, 515 insertions, 0 deletions
diff --git a/ceee/tools/smoke_test.py b/ceee/tools/smoke_test.py new file mode 100644 index 0000000..1ad5e3f --- /dev/null +++ b/ceee/tools/smoke_test.py @@ -0,0 +1,212 @@ +#!python +# Copyright (c) 2010 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. +'''This scripts builds and runs the CEEE unittests and medium tests that +make our smoke tests. + +It exits with a non-zero exit status on error. +''' +try: + import ctypes + import pythoncom + import win32com.client +except ImportError: + print "This script requires Python 2.6 with PyWin32 installed." + raise + +import datetime +import os.path +import subprocess +import sys +import time +import verifier + +# The top-level source directory. +# All other paths in this file are relative to this directory. +_SRC_DIR = os.path.abspath(os.path.join( + os.path.dirname(__file__), '../..')) + + +def _AbsolutePath(path): + '''Convert path to an absolute path. + + Args: + path: a path relative to _SRC_DIR. + + Returns: Path as an absolute, normalized path. + ''' + return os.path.abspath(os.path.join(_SRC_DIR, path)) + + +_CHROME_SOLUTION = _AbsolutePath('chrome/chrome.sln') + + +# List of project files to build. +_PROJECTS_TO_BUILD = [ + # Common unittest + _AbsolutePath('ceee/common/ceee_common_unittests.vcproj'), + + # IE unittests and medium tests. + _AbsolutePath('ceee/ie/ie_unittests.vcproj'), + _AbsolutePath('ceee/ie/mediumtest_ie.vcproj'), +] + +# List of test executables to run. +_TESTS_TO_RUN = [ + 'ceee_common_unittests', + 'ie_unittests', + 'mediumtest_ie', +] + +# A list of configurations we build and run. +_CONFIGURATIONS = [ + 'Debug', + 'Release', +] + +class TestRunner(object): + def __init__(self, solution, projects, tests): + self._projects = projects + self._tests = tests + self._saved_autohides = None + self._solution = win32com.client.GetObject(solution) + self._dte = self._GetDte(self._solution) + + self._EnsureVisible() + + def __del__(self): + self._RestoreVisibility() + + def _EnsureVisible(self): + dte = self._dte + # Make sure the UI is visible. + dte.MainWindow.Visible = True + + # Get the output window, disable its autohide and give it focus. + self._output = dte.Windows['Output'] + self._saved_autohides = self._output.AutoHides + self._output.AutoHides = False + self._output.SetFocus() + + def _RestoreVisibility(self): + if self._saved_autohides != None: + self._output.AutoHides = self._saved_autohides + + def _GetDte(self, solution): + '''Sometimes invoking solution.DTE will fail with an exception during + Visual Studio initialization. To work around this, we try a couple of + times with an intervening sleep to give VS time to settle.''' + # Attempt ten times under a try block. + for i in range(0, 10): + try: + return solution.DTE + except pythoncom.com_error, e: + # Sleep for 2 seconds + print 'Exception from solution.DTE "%s", retrying: ' % str(e) + time.sleep(2.0) + + # Final try, pass the exception to the caller on failure here. + return solution.DTE + + def BuildConfig(self, config): + '''Builds all projects for a given config. + + Args: + config: the name of the build configuration to build projecs for. + + Returns: + The number of failures. + ''' + builder = self._solution.SolutionBuild + for project in self._projects: + print 'Building project "%s" in "%s" configuration' % (project, config) + wait_for_build = True + # See http://goo.gl/Dy8x for documentation on this method. + builder.BuildProject(config, project, wait_for_build) + errors = builder.LastBuildInfo + if errors != 0: + return errors + + return 0 + + def RunConfigTests(self, config): + '''Runs all tests for a given config and writes success files. + + For each succeeding test '<path>/foo.exe', this function writes a + corresponding success file '<path>/foo.success'. If the corresponding + success file is newer than the test executable, the test executable will + not run again. + If all tests succeed, this function writes an overall success file + named 'ceee.success'. + + Args: + config: the name of the build configuration to run tests for. + + Returns: + The number of failures. + ''' + failures = 0 + for test in self._tests: + test_exe = os.path.join(_SRC_DIR, + 'chrome', + config, + test) + '.exe' + success_file = os.path.join(_SRC_DIR, + 'chrome', + config, + test) + '.success' + if (os.path.exists(success_file) and + os.stat(test_exe).st_mtime <= os.stat(success_file).st_mtime): + print '"%s" is unchanged since last successful run' % test_exe + else: + if verifier.HasAppVerifier(): + (exit, errors) = verifier.RunTestWithVerifier(test_exe) + else: + exit = subprocess.call(test_exe) + + # Create a success file if the test succeded. + if exit == 0: + open(success_file, 'w').write(str(datetime.datetime.now())) + else: + failures = failures + 1 + print '"%s" failed with exit status %d' % (test_exe, exit) + + # Create the overall success file if everything succeded. + if failures == 0: + success_file = os.path.join(_SRC_DIR, + 'chrome', + config, + 'ceee') + '.success' + open(success_file, 'w').write(str(datetime.datetime.now())) + + return failures + + +def Main(): + if not ctypes.windll.shell32.IsUserAnAdmin(): + print ("Please run the smoke tests in an admin prompt " + "(or AppVerifier won't work).") + return 1 + + runner = TestRunner(_CHROME_SOLUTION, _PROJECTS_TO_BUILD, _TESTS_TO_RUN) + + failures = 0 + for config in _CONFIGURATIONS: + failures += runner.BuildConfig(config) + + if failures != 0: + print '%d build errors' % failures + return failures + + for config in _CONFIGURATIONS: + failures += runner.RunConfigTests(config) + + if failures != 0: + print '%d unittests failed' % failures + + return failures + + +if __name__ == '__main__': + sys.exit(Main()) diff --git a/ceee/tools/verifier.py b/ceee/tools/verifier.py new file mode 100644 index 0000000..31fc6d2 --- /dev/null +++ b/ceee/tools/verifier.py @@ -0,0 +1,303 @@ +# Copyright (c) 2010 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. +'''Utilities to automate running tests under app verifier.''' + +import os +import os.path +import re +import subprocess +import sys +import tempfile +import win32com.client.gencache as gencache +import xml.sax +import xml.sax.handler + + +TRACE_RE_ = re.compile('(?P<module>\w+)' # Module + '(?:' + '!' # Separator + '(?P<symbol>[^\+]+)' # Symbol + '(?:\+(?P<offset>[0-9a-f]+))?' # Optional hex offset + ')?' # Sometimes we only get a module + '(?:\s+\(' # Optional file and line + '(?P<file>[^@]*)' # file + '\s+@\s*' + '(?P<line>\d+)' # line + '\))?' + , re.I) + + +# This is application verifier's automation library id. +LIBID_AppVerifier = '{DAB52BCB-6990-464A-AC61-F60C8EF60E24}' +try: + VerifierLib = gencache.EnsureModule(LIBID_AppVerifier, 0, 1, 0) +except: + VerifierLib = None + + +class LogTrace: + def __init__(self, text): + d = TRACE_RE_.match(text).groupdict() + self.module = d['module'] + self.symbol = d['symbol'] + self.offset = d['offset'] + self.file = d['file'] + self.line = d['line'] + + def __str__(self): + ret = '' + if self.file and self.line: + ret = '%s (%s): AppVerifier warning C1: ' % (self.file, self.line) + + ret = '%s%s!%s' % (ret, self.module, self.symbol) + + if self.offset: + ret = "%s+%s" % (ret, self.offset) + + return ret + + +class LogEntry: + def __init__(self, stopcode, layer, severity): + self.stopcode = stopcode + self.layer = layer + self.severity = severity + self.message = '' + self.trace = [] + + def __str__(self): + return "%s: %s\n\t%s" % (self.severity, + self.message, + '\n\t'.join(map(str,self.trace))) + + +class VerifierSaxHandler(xml.sax.handler.ContentHandler): + '''Sax event handler to parse the verifier log file. + + The XML generated by verifier appears not to be standards compliant, so + e.g. xml.dom.minidom falls down while parsing it, hence this tedious + implementation. + ''' + def __init__(self): + self.log_entry = None + self.stack = [] + self.element = None + self.errors = 0 + self.text = '' + self.log_entries = [] + + def startElement(self, name, attrs): + self.stack.append((self.element, self.text)) + self.element = name + self.text = [] + + if name == 'avrf:logEntry': + self.log_entry = LogEntry(attrs.getValue('StopCode'), + attrs.getValue('LayerName'), + attrs.getValue('Severity')) + self.errors += 1 + + def endElement(self, name): + if name == 'avrf:logEntry': + self.log_entries.append(self.log_entry) + self.log_entry = None + elif name == 'avrf:message': + self.log_entry.message = ''.join(self.text) + elif name == 'avrf:trace': + self.log_entry.trace.append(LogTrace(''.join(self.text))) + + (self.element, self.text) = self.stack.pop() + + def characters(self, content): + self.text.append(content) + + +class AppverifierTestRunner: + '''Encapsulates logic to run an executable under app verifier. + + Interacts with application verifiers automation library to set default + verifier settings for a given target, process logs etc. + ''' + # Checks we want enabled. + default_checks_ = [ + # Basics group + "Core", + "Exceptions", + "Handles", + "Heaps", + "InputOutput", + "Locks", + "Memory", + "TLS", + + # Misc group + "DangerousAPIs", + "DirtyStacks", + "TimeRollOver", + ] + + def __init__(self, break_on_stop): + self.manager = VerifierLib.AppVerifierManager() + self.break_on_stop = break_on_stop + + def SetStopBreaks(self, check): + '''Configures all the stops for check to log only. + + Arguments: + check: an app verifier check + ''' + error_reporting = VerifierLib.constants.VerifierErrorReportingNoBreak + error_flags = (VerifierLib.constants.VerifierErrorFlagLogToFile | + VerifierLib.constants.VerifierErrorFlagLogStackTrace) + if self.break_on_stop: + error_reporting = VerifierLib.constants.VerifierErrorReportingBreakpoint + + try: + for stop in check.Stops: + stop.ErrorReporting = error_reporting + stop.ErrorFlags = error_flags + except: + # Accessing or enumerating Stops fails for some checks. + print 'Exception setting options for check', check.Name + + def ResetImage(self, image_name): + '''Removes all verifier settings for image_name. + ''' + # Reset the settings for our image + try: + self.manager.Images.Remove(image_name) + except: + # this fails if verifier had no settings for the image + pass + + def SetImageDefaults(self, image_name): + '''Configures a default set of tests for image_name + + Arguments: + image_name: the basename of a test, e.g. 'common_unittest.exe' + ''' + self.ResetImage(image_name) + + image = self.manager.Images.Add(image_name) + for check in image.Checks: + if check.Name in self.default_checks_: + check.Enabled = True + self.SetStopBreaks(check) + + def ClearImageLogs(self, image_name): + '''Deletes all app verifier logs for image_name.''' + logs = self.manager.Logs(image_name) + if logs: + while logs.Count: + logs.Remove(0) + + def ProcessLog(self, log_path): + '''Process the verifier log at log_path. + + Returns: a list of the log entries in the log. + ''' + handler = VerifierSaxHandler() + doc = xml.sax.parse(open(log_path, 'rb'), handler) + + return handler.log_entries + + def SaveLog(self, log): + '''Saves log to an XML file and returns the path to the file. + ''' + (fd, path) = tempfile.mkstemp('.xml', 'verifier_log') + os.close(fd) + try: + log.SaveAsXML(path, '') + except: + os.remove(path) + return None + + return path + + def ProcessLogs(self, test_name): + '''Processes all logs for image test_name. + + Arguments: + test_name: the base name of the test executable. + + Returns: A list of LogEntry instances for each error logged. + ''' + logs = self.manager.Logs(test_name) + if not logs or not logs.Count: + return 0 + + errors = [] + for log in logs: + path = self.SaveLog(log) + if path: + errors.extend(self.ProcessLog(path)) + os.remove(path) + + return errors + + def RunTestWithVerifier(self, test_path): + '''Run a single test under verifier. + + Arguments: + test_path: full or relative path to the test to run. + + Returns: + A tuple with the test exit code and a list of verifier errors, + example: + (0, [error1, error2, ...]) + ''' + test_name = os.path.basename(test_path) + + # Set up the verifier configuration + self.SetImageDefaults(test_name) + self.ClearImageLogs(test_name) + + # run the test. + exit = subprocess.call(test_path) + + # Clear the verifier settings for the image. + self.ResetImage(test_name) + + # And process the logs. + errors = self.ProcessLogs(test_name) + return (exit, errors) + + def DumpImages_(self): + for image in self.manager.Images: + print image.Name + for check in image.Checks: + print '\t', check.Name, check.Enabled + + def DumpLogs_(self, image_name): + for l in self.manager.Logs(image_name): + print l + + +def RunTestWithVerifier(test_path, break_on_stop = False): + '''Runs test_path under app verifier. + + Returns: a tuple of the exit code and list of errors + ''' + runner = AppverifierTestRunner(break_on_stop) + return runner.RunTestWithVerifier(test_path) + + +def HasAppVerifier(): + '''Returns true iff application verifier is installed.''' + if VerifierLib: + return True + + return False + + +def Main(): + '''Runs all tests on command line under verifier with stops disabled.''' + for test in sys.argv[1:]: + (exit_code, errors) = RunTestWithVerifier(test) + for error in errors: + print error + + +if __name__ == '__main__': + Main() |