| # 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)) |