blob: ece8a51701d6f74b460a9bd113b6ef14a8130b03 [file] [edit]
# Copyright (c) 2014 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.
#
# This module provides the test runner which is responsible for discovering
# and executing tests.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import logging
import multiprocessing
import traceback
from mtreplay import MTReplay
from mtlib import Log
from mtlib.gesture_log import GestureLog
from os import path
from test_case import TestCase
class TestRunner(object):
"""
The TestRunner provides a simple interface to execute a set of test cases.
All test results are returned in a result structure containing logs,
validation results, errors, the generated gestures and the final score.
"""
def __init__(self, tests_dir):
self.tests_dir = tests_dir
def RunTest(self, test_case, generate_original_values=False):
"""
This method executes a single test. The results are provided in the
following structure:
{
"logs": {
"evdev": "" # libevdev logs
"gestures": "", # gestures library logs
"activity": "", # generated activity log
"validation": "" # logs from the validation process
},
"error": "", # error message if applicable.
"events": "", # string representation of all events generated
"gestures": "", # string representation of all gestures generated
"result": "", # "success" the test provides a score.
# "failure" the test executed, but validation failed.
# "error" the test could not be executed.
"score": 0 # the final score of the test
}
"""
out = {
"logs": {
"evdev": "",
"gestures": "",
"activity": "",
"validation": ""
},
"error": "",
"events": "",
"description": "",
"disabled": False,
"gestures": "",
"result": "",
"score": 0
}
dbg_log = logging.getLogger(test_case.name)
if not test_case.IsComplete():
out["result"] = "incomplete"
out["disabled"] = True
return out
result = test_case.Check()
if result is not True:
out["error"] = result
out["result"] = "error"
return out
out["description"] = test_case.description
if test_case.disabled:
out["result"] = "disabled"
out["disabled"] = True
return out
try:
dbg_log.info("Loading log: %s", test_case.log_file)
events = Log(evdev=test_case.log_file)
dbg_log.info("Starting replay...")
replay = MTReplay()
replay_results = replay.Replay(events, test_case.gesture_props,
test_case.platform, dbg_log=dbg_log)
log = replay_results.gestures
out["logs"]["evdev"] = replay_results.evdev_log
out["logs"]["gestures"] = replay_results.gestures_log
out["logs"]["activity"] = replay_results.log.activity
out["events"] = "\n".join(map(lambda e: str(e), log.events))
out["gestures"] = "\n".join(map(lambda g: str(g), log.gestures))
# look for errors in the gestures library output
errors = []
for line in replay_results.gestures_log.splitlines():
if line.startswith("ERROR:"):
errors.append(line)
if errors:
dbg_log.info("Found errors in gestures log")
out["error"] = "\n".join(errors)
out["result"] = "error"
return out
# validate the replay results
dbg_log.info("Validating...")
if generate_original_values:
score, validation_log, original_values = \
test_case.module.GenerateOriginalValues(
log.raw, log.events, log.gestures)
out['original_values'] = original_values
elif test_case.original_values:
score, validation_log = test_case.module.Validate(
log.raw, log.events, log.gestures,
original_values=test_case.original_values)
else:
score, validation_log = test_case.module.Validate(
log.raw, log.events, log.gestures)
out["logs"]["validation"] = validation_log
out["result"] = "success" if score is not False else "failure"
out["score"] = score
dbg_log.info("Validation result: %s", out["result"])
except:
dbg_log.exception("Exception raised")
out["error"] = traceback.format_exc()
out["result"] = "error"
return out
def RunGDB(self, glob):
test_case = self.DiscoverTestCases(glob)[0]
replay = MTReplay()
events = Log(evdev=test_case.log_file)
replay_results = replay.Replay(events, test_case.gesture_props,
test_case.platform, gdb=True)
def RunAll(self, glob="all"):
"""
This method runs a set of test cases. Test cases can be selected using
a unix-style GLOB or "all" to execute all tests.
The result is a dictionary mapping the test name to the results structure
as described in RunTest.
"""
results = {}
for case in self.DiscoverTestCases(glob):
results[case.name] = self.RunTest(case)
return results
def DiscoverTestCases(self, glob=None):
return TestCase.DiscoverTestCases(self.tests_dir, glob)
def ParallelTestRunnerProcess(case, verbose=False):
level = logging.INFO if verbose else logging.WARNING
logging.basicConfig(level=level)
logging.info("Starting process for: %s", case.name)
runner = TestRunner(case.tests_path)
return (case, runner.RunTest(case))
def VerboseParallelTestRunnerProcess(case):
return ParallelTestRunnerProcess(case, verbose=True)
class ParallelTestRunner(TestRunner):
def RunAll(self, glob="all", verbose=False):
"""
Extends TestRunner.RunAll by multiprocessing. The test cases are distributed
to as many processes as there are cores available.
"""
pool = multiprocessing.Pool()
cases = self.DiscoverTestCases(glob)
logging.info("Starting multiprocessing pool")
if logging.getLogger().isEnabledFor(logging.INFO):
data = pool.map(VerboseParallelTestRunnerProcess, cases)
else:
data = pool.map(ParallelTestRunnerProcess, cases)
logging.info("All tests executed")
pool.terminate()
results = {}
for case, result in data:
results[case.name] = result
return results