blob: da0d61ad6410e93f9957576166889a5bb787e7a6 [file] [log] [blame]
# Copyright (c) 2012 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 contains the TestCase class which gives access to the data files
# belonging to a test case and validation that the TestCase has all required
# information.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from mtlib.gesture_log import GestureLog
from os import path
import fnmatch
import imp
import json
import os
import traceback
class TestCase(object):
"""
The TestCase class is constructed with test name and a tests directory.
In order to automatically load properties from xorg config files, the
xorg_conf_path argument has to be set to the path of the folder containing
the chromeos xorg configuration files.
Tests have multiple files associated with them and these file paths are
determined using the convention over configuration paradigm:
The tests directory contains one folder for each platform (lumpy, cr48, etc)
as well as a common folder which is shared by all platforms. Each platform
has to contain a platform.dat containing the platforms touchpad description
(see replay tool and evemu). Additionally it can provide a platform.props file
to pass properties to the gestures library.
Each platform can organize test cases in subfolders.
A test case is defined by a log file containing the event log and the name
$(testname).log.
The gestures from the log are validated using the python module in the same
folder $(testname).py. If such a file cannot be found, the class will look for
a file $(testname).py in same subdirectory of the common folder.
Each test case can define parameters for the gestures library using a JSON
file called $(testname).props. These properties override any properties set
in platform.props.
To make sure all those files are present and the test can be executed this
class provides a method called Check which will return an error message
in case there is an error.
Tests are basically named after the path to the tests log file (including the
platform name) but excluding the .log extension. So the log file
$(tests_dir)/lumpy/categyoryA/testB.log has the name lumpy/categoryA/testB
"""
_platform_dat = "platform.dat"
_platform_props = "platform.props"
_common_folder = "common"
def __init__(self, tests_path, name):
self.tests_path = tests_path
self._ignore_properties = []
# remove extension to get test name in case a filename was provided
self.testname = path.splitext(name)[0]
self.path = self.testname.split(os.sep)
self.properties = None
self.platform = None
self.original_values = None
self._module = None
def IsComplete(self):
return path.exists(self.log_file) and (self.module is not None)
def Check(self):
"""
Returns an error message describing the configuration error, or True if
the test can be executed.
"""
if not path.exists(self.log_file):
return self.log_file + " does not exist"
# catch all errors caused by loading the module
try:
if self.module is None:
return "cannot find module file"
except:
return "unable to load module\n" + traceback.format_exc()
if "Validate" not in self.module.__dict__:
return self.module.__file__ + " does not contain a Validate() method"
return True
def __str__(self):
return self.name
def __repr__(self):
return self.name
@property
def name(self):
return "/".join(self.path)
@property
def platform_name(self):
return self.path[0]
@property
def platform_dat_file(self):
return self._PlatformFile(self._platform_dat)
@property
def log_file(self):
return self._TestCaseFile(".log")
@property
def case_module_file(self):
return self._TestCaseFile(".py")
@property
def common_module_file(self):
return self._CommonFile(".py")
@property
def case_property_file(self):
return self._TestCaseFile(".props")
@property
def device_property_file(self):
return self._PlatformFile("platform.props")
@property
def common_property_file(self):
return self._CommonFile(".props")
@property
def description(self):
module = self.module
return module.Validate.__doc__ if module.Validate.__doc__ else ""
@property
def user_description(self):
module = self.module
if hasattr(module, "UserInstructions"):
return module.UserInstructions()
return None
@property
def disabled(self):
module = self.module
return hasattr(module.Validate, "disabled") and module.Validate.disabled
@property
def disabled(self):
module = self.module
return hasattr(module.Validate, "disabled") and module.Validate.disabled
@property
def takes_original_values(self):
return hasattr(self.module, "GenerateOriginalValues")
@property
def is_virtual(self):
if self.module_file:
module_name = path.basename(path.splitext(self.module_file)[0])
log_name = path.basename(path.splitext(self.log_file)[0])
return log_name != module_name
return True
@property
def module_file(self):
def FindHierachicalModuleFile(file):
if path.exists(file + ".py"):
return file + ".py"
elif '_' in file:
return FindHierachicalModuleFile(file[:file.rfind("_")])
return None
module_file = FindHierachicalModuleFile(self._TestCaseFile(""))
if module_file:
return module_file
module_file = FindHierachicalModuleFile(self._CommonFile(""))
if module_file:
return module_file
return None
@property
def module(self):
module_file = self.module_file
if not module_file:
return None
name = os.path.splitext(os.path.basename(module_file))[0]
return imp.load_source(name, module_file)
@property
def gesture_props(self):
if self.properties is None:
self._LoadProperties()
return self.properties["gestures"]
@property
def validator_props(self):
if self.properties is None:
self._LoadProperties()
return self.properties["validator"]
@property
def ignore_properties(self):
if self.properties is None:
self._LoadProperties()
return self._ignore_properties
@property
def device_class(self):
if self.properties is None:
self._LoadProperties()
return self.properties.get("device_class")
@property
def instruct_robot(self):
if "InstructRobot" in self.module.__dict__:
return self.module.InstructRobot
return None
def ReloadProperties(self):
self.properties = None
self._LoadProperties()
def _LoadProperties(self):
self.properties = {
# The "Event Logging Enable" property is required for the gestures library
# to produce the event logs used by these tests.
"gestures": { "Event Logging Enable" : True },
"validator": {},
}
self._LoadPropertiesFile(self._PlatformFile("platform.props"))
self._LoadPropertiesFile(self._CommonFile(".props"))
self._LoadPropertiesFile(self._TestCaseFile(".props"))
self._ApplyIgnoreProperties()
def _ApplyIgnoreProperties(self):
for key in self._ignore_properties:
if key in self.properties["gestures"]:
del self.properties["gestures"][key]
def _LoadPropertiesFile(self, file):
if not path.exists(file):
return
data = json.load(open(file, "r"))
if "gestures" in data:
self.properties["gestures"].update(data["gestures"])
if "validator" in data:
self.properties["validator"].update(data["validator"])
if "ignore" in data:
self._ignore_properties.extend(data["ignore"])
if "device_class" in data:
self.properties["device_class"] = data["device_class"]
if "platform" in data:
self.platform = data["platform"]
if "original_values" in data:
self.original_values = data["original_values"]
def _TestCaseFile(self, extension):
return os.path.join(self.tests_path, *self.path) + extension
def _CommonFile(self, extension):
return os.path.join(self.tests_path, self._common_folder, *self.path[1:]) \
+ extension
def _PlatformFile(self, filename):
return os.path.join(self.tests_path, self.path[0], filename)
@staticmethod
def DiscoverTestCases(tests_dir, glob=None):
""" Returns a list of test cases that matching the glob. """
def ListDirRecursive(rootdir, sub_path=""):
result = []
target_path = path.join(rootdir, sub_path)
if not path.exists(target_path):
return []
for file in os.listdir(target_path):
if file[0] == ".":
continue
if path.isdir(path.join(rootdir, sub_path, file)):
result.extend(ListDirRecursive(rootdir, path.join(sub_path, file)))
else:
result.append(path.join(sub_path, file))
return result
def ListCases(tests_dir, platform):
files = ListDirRecursive(path.join(tests_dir, platform), "")
py_files = fnmatch.filter(files, "*.py")
log_files = fnmatch.filter(files, "*.log")
test_names = map(lambda p: path.splitext(p)[0], py_files + log_files)
return set(test_names)
common_validators = ListCases(tests_dir, "common")
all_cases = []
for platform in TestCase.DiscoverPlatforms(tests_dir):
validators = ListCases(tests_dir, platform)
device_class = TestCase(tests_dir,
path.join(platform, "dummy")).device_class
if device_class is None or device_class == "touchpad":
validators.update(common_validators)
test_cases = map(lambda f: TestCase(tests_dir, path.join(platform, f)),
validators)
all_cases.extend(test_cases)
all_cases = filter(lambda c: c.module, all_cases)
if glob is not None and glob != "all":
all_cases = filter(lambda c: fnmatch.fnmatch(c.name, glob) or
c.name.startswith(glob), all_cases)
return all_cases
@staticmethod
def DiscoverPlatforms(tests_dir):
def is_platform(platform):
return platform[0] != "." and path.isdir(path.join(tests_dir, platform))
return filter(is_platform, os.listdir(tests_dir))