michael@0: # Copyright (c) 2012 The Chromium Authors. All rights reserved. michael@0: # Use of this source code is governed by a BSD-style license that can be michael@0: # found in the LICENSE file. michael@0: michael@0: """Helper module for calling python-based tests.""" michael@0: michael@0: michael@0: import logging michael@0: import sys michael@0: import time michael@0: michael@0: from test_result import TestResults michael@0: michael@0: michael@0: def CallPythonTest(test, options): michael@0: """Invokes a test function and translates Python exceptions into test results. michael@0: michael@0: This method invokes SetUp()/TearDown() on the test. It is intended to be michael@0: resilient to exceptions in SetUp(), the test itself, and TearDown(). Any michael@0: Python exception means the test is marked as failed, and the test result will michael@0: contain information about the exception. michael@0: michael@0: If SetUp() raises an exception, the test is not run. michael@0: michael@0: If TearDown() raises an exception, the test is treated as a failure. However, michael@0: if the test itself raised an exception beforehand, that stack trace will take michael@0: precedence whether or not TearDown() also raised an exception. michael@0: michael@0: shard_index is not applicable in single-device scenarios, when test execution michael@0: is serial rather than parallel. Tests can use this to bring up servers with michael@0: unique port numbers, for example. See also python_test_sharder. michael@0: michael@0: Args: michael@0: test: an object which is ostensibly a subclass of PythonTestBase. michael@0: options: Options to use for setting up tests. michael@0: michael@0: Returns: michael@0: A TestResults object which contains any results produced by the test or, in michael@0: the case of a Python exception, the Python exception info. michael@0: """ michael@0: michael@0: start_date_ms = int(time.time()) * 1000 michael@0: failed = False michael@0: michael@0: try: michael@0: test.SetUp(options) michael@0: except Exception: michael@0: failed = True michael@0: logging.exception( michael@0: 'Caught exception while trying to run SetUp() for test: ' + michael@0: test.qualified_name) michael@0: # Tests whose SetUp() method has failed are likely to fail, or at least michael@0: # yield invalid results. michael@0: exc_info = sys.exc_info() michael@0: return TestResults.FromPythonException(test.qualified_name, start_date_ms, michael@0: exc_info) michael@0: michael@0: try: michael@0: result = test.Run() michael@0: except Exception: michael@0: # Setting this lets TearDown() avoid stomping on our stack trace from Run() michael@0: # should TearDown() also raise an exception. michael@0: failed = True michael@0: logging.exception('Caught exception while trying to run test: ' + michael@0: test.qualified_name) michael@0: exc_info = sys.exc_info() michael@0: result = TestResults.FromPythonException(test.qualified_name, start_date_ms, michael@0: exc_info) michael@0: michael@0: try: michael@0: test.TearDown() michael@0: except Exception: michael@0: logging.exception( michael@0: 'Caught exception while trying run TearDown() for test: ' + michael@0: test.qualified_name) michael@0: if not failed: michael@0: # Don't stomp the error during the test if TearDown blows up. This is a michael@0: # trade-off: if the test fails, this will mask any problem with TearDown michael@0: # until the test is fixed. michael@0: exc_info = sys.exc_info() michael@0: result = TestResults.FromPythonException(test.qualified_name, michael@0: start_date_ms, exc_info) michael@0: michael@0: return result