blob: c679772d0ddbcb02aacf200f3708ef0a8ed1c914 [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2015 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Standalone test driver.
Note that this is tested indirectly by make_par_unittest.py.
"""
from __future__ import print_function
import argparse
import ast
import logging
import os
import pickle
import sys
import unittest
import factory_common # pylint: disable=unused-import
from cros.factory.device import device_utils
from cros.factory.test.utils import pytest_utils
from cros.factory.utils.arg_utils import Args
def _GetTestCase(pytest):
"""Returns the first test case class found in a given pytest."""
return pytest_utils.LoadPytest(pytest)
def _FormatErrorMessage(trace):
"""Formats a trace so that the actual error message is in the last
line.
"""
# The actual error is in the last line.
trace, _, error_msg = trace.strip().rpartition('\n')
error_msg = error_msg.replace('TestFailure: ', '')
return error_msg + '\n' + trace
def RunPytest(pytest, args, dut_options, use_goofy=False):
fn = _RunPytestGoofy if use_goofy else _RunPytestRaw
return fn(pytest, args, dut_options)
def _RunPytestGoofy(pytest, args, dut_options):
"""Runs a pytest.
Args:
pytest: The name of the test within the pytests module (e.g.,
"thermal_slope").
args: The argument dictionary.
Returns:
A tuple (success, error_message), where:
- success is a boolean representing the test success/failure
- error_message is None on success and an error message string on failure
"""
# Unless this function is called, we would like to avoid these extra
# dependencies.
from cros.factory.utils import file_utils
from cros.factory.goofy import invocation
from cros.factory.test import state
with file_utils.UnopenedTemporaryFile(prefix='results') as results:
info = invocation.PytestInfo(None, None, pytest, args, results,
dut_options=dut_options)
invocation.RunPytest(info)
status, error_msg = pickle.load(open(results))
return (status == state.TestState.PASSED, error_msg)
def _RunPytestRaw(pytest, args, dut_options):
"""Runs a pytest with minimal goofy dependencies.
Args:
pytest: The name of the test within the pytests module, or the
unittest.TestCase object to run.
args: The unverified argument dictionary.
dut_options: Any dut_options to be passed to the test.
Returns:
A tuple (status, error_msg), where:
- status is a boolean representing the test success/failure
- error_msg is None on success and an error message string on failure
"""
# Create a test case instance.
if isinstance(pytest, str):
test = _GetTestCase(pytest)
else:
test = pytest()
# Setup DUT_OPTIONS environment.
if dut_options:
os.environ.update({device_utils.ENV_DUT_OPTIONS: str(dut_options)})
# Set self.args of the test case.
try:
arg_spec = getattr(test, 'ARGS', None)
setattr(test, 'args', Args(*arg_spec).Parse(args if args else {}))
except Exception as e:
return (False, e.message)
# Run the test.
result = unittest.TestResult()
test.run(result)
# Collect errors and return them.
all_failures = result.failures + result.errors
error_msg = None
if all_failures:
error_msg = '\n'.join(_FormatErrorMessage(trace)
for test_name, trace in all_failures)
return (error_msg is None, error_msg)
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'pytest', metavar='TEST_NAME',
help='Name of the pytest within the current directory, e.g., '
'"thermal_slope"')
parser.add_argument(
'--args',
help='''Dictionary of arguments, e.g., "{'foo': 'bar'}"''')
parser.add_argument(
'--dut-options',
help='''DUT options, e.g., "{'link_class': 'ADBLink'}"''')
parser.add_argument(
'--verbose', '-v',
action='store_true')
parser.add_argument(
'--no-use-goofy',
dest='use_goofy',
help='Run test with minimal goofy dependencies',
action='store_false')
cli_args = parser.parse_args()
# Set logging level.
logging_level = logging.DEBUG if cli_args.verbose else logging.INFO
logging.basicConfig(level=logging_level)
# Run the test.
args = (ast.literal_eval(cli_args.args)
if cli_args.args else {})
dut_options = (ast.literal_eval(cli_args.dut_options)
if cli_args.dut_options else {})
_, error_msg = RunPytest(pytest=cli_args.pytest,
args=args,
dut_options=dut_options,
use_goofy=cli_args.use_goofy)
# Exit code and error message.
sys.exit(error_msg or None)
if __name__ == '__main__':
main()