# # Copyright (C) 2016 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import os import logging import itertools from vts.runners.host import const from vts.testcases.kernel.ltp import ltp_configs from vts.testcases.kernel.ltp import ltp_enums from vts.testcases.kernel.ltp import test_case from vts.testcases.kernel.ltp.configs import stable_tests from vts.testcases.kernel.ltp.configs import disabled_tests from vts.utils.python.common import filter_utils class TestCasesParser(object): """Load a ltp vts testcase definition file and parse it into a generator. Attributes: _data_path: string, the vts data path on host side _filter_func: function, a filter method that will emit exception if a test is filtered _ltp_tests_filter: list of string, filter for tests that are stable and disabled """ def __init__(self, data_path, filter_func): self._data_path = data_path self._filter_func = filter_func self._ltp_tests_filter = filter_utils.Filter( [ i[0] for i in stable_tests.STABLE_TESTS ], disabled_tests.DISABLED_TESTS, enable_regex=True) self._ltp_tests_filter.ExpandBitness() def ValidateDefinition(self, line): """Validate a tab delimited test case definition. Will check whether the given line of definition has three parts separated by tabs. It will also trim leading and ending white spaces for each part in returned tuple (if valid). Returns: A tuple in format (test suite, test name, test command) if definition is valid. None otherwise. """ items = [ item.strip() for item in line.split(ltp_enums.Delimiters.TESTCASE_DEFINITION) ] if not len(items) == 3 or not items: return None else: return items def Load(self, ltp_dir, n_bit, test_filter, run_staging=False, is_low_mem=False, is_hwasan=False): """Read the definition file and yields a TestCase generator. Args: ltp_dir: string, directory that contains ltp binaries and scripts n_bit: int, bitness test_filter: Filter object, test name filter from base_test run_staging: bool, whether to use staging configuration is_low_mem: bool, whether to use low memory device configuration """ scenario_groups = (ltp_configs.TEST_SUITES_LOW_MEM if is_low_mem else ltp_configs.TEST_SUITES) logging.info('LTP scenario groups: %s', scenario_groups) run_scritp = self.GenerateLtpRunScript(scenario_groups, is_hwasan=is_hwasan) for line in run_scritp: items = self.ValidateDefinition(line) if not items: continue testsuite, testname, command = items if is_low_mem and testsuite.endswith( ltp_configs.LOW_MEMORY_SCENARIO_GROUP_SUFFIX): testsuite = testsuite[:-len( ltp_configs.LOW_MEMORY_SCENARIO_GROUP_SUFFIX)] # Tests failed to build will have prefix "DISABLED_" if testname.startswith("DISABLED_"): logging.info("[Parser] Skipping test case {}-{}. Reason: " "not built".format(testsuite, testname)) continue # Some test cases contain semicolons in their commands, # and we replace them with && command = command.replace(';', '&&') # Some test cases have hardcoded "/tmp" in the command # we replace that with ltp_configs.TMPDIR command = command.replace('/tmp', ltp_configs.TMPDIR) testcase = test_case.TestCase( testsuite=testsuite, testname=testname, command=command) test_display_name = "{}_{}bit".format(str(testcase), n_bit) # Check runner's base_test filtering method try: self._filter_func(test_display_name) except: logging.info("[Parser] Skipping test case %s. Reason: " "filtered" % testcase.fullname) testcase.is_filtered = True testcase.note = "filtered" # For skipping tests that are not designed or ready for Android, # check for bit specific test in disabled list as well as non-bit specific if ((self._ltp_tests_filter.IsInExcludeFilter(str(testcase)) or self._ltp_tests_filter.IsInExcludeFilter(test_display_name)) and not test_filter.IsInIncludeFilter(test_display_name)): logging.info("[Parser] Skipping test case %s. Reason: " "disabled" % testcase.fullname) continue # For separating staging tests from stable tests if not self._ltp_tests_filter.IsInIncludeFilter(test_display_name): if not run_staging and not test_filter.IsInIncludeFilter( test_display_name): # Skip staging tests in stable run continue else: testcase.is_staging = True testcase.note = "staging" else: if run_staging: # Skip stable tests in staging run continue if not testcase.is_staging: for x in stable_tests.STABLE_TESTS: if x[0] == test_display_name and x[1]: testcase.is_mandatory = True break logging.info("[Parser] Adding test case %s." % testcase.fullname) yield testcase def ReadCommentedTxt(self, filepath): '''Read a lines of a file that are not commented by #. Args: filepath: string, path of file to read Returns: A set of string representing non-commented lines in given file ''' if not filepath: logging.error('Invalid file path') return None with open(filepath, 'r') as f: lines_gen = (line.strip() for line in f) return set( line for line in lines_gen if line and not line.startswith('#')) def GenerateLtpTestCases(self, testsuite, disabled_tests_list): '''Generate test cases for each ltp test suite. Args: testsuite: string, test suite name Returns: A list of string ''' testsuite_script = os.path.join(self._data_path, ltp_configs.LTP_RUNTEST_DIR, testsuite) result = [] for line in open(testsuite_script, 'r'): line = line.strip() if not line or line.startswith('#'): continue testname = line.split()[0] testname_prefix = ('DISABLED_' if testname in disabled_tests_list else '') testname_modified = testname_prefix + testname result.append("\t".join( [testsuite, testname_modified, line[len(testname):].strip()])) return result def GenerateLtpRunScript(self, scenario_groups, is_hwasan=False): '''Given a scenario group generate test case script. Args: scenario_groups: list of string, name of test scenario groups to use Returns: A list of string ''' disabled_tests_path = os.path.join( self._data_path, ltp_configs.LTP_DISABLED_BUILD_TESTS_CONFIG_PATH) disabled_tests_list = self.ReadCommentedTxt(disabled_tests_path) if is_hwasan: disabled_tests_list = disabled_tests_list.union(disabled_tests.DISABLED_TESTS_HWASAN) result = [] for testsuite in scenario_groups: result.extend( self.GenerateLtpTestCases(testsuite, disabled_tests_list)) #TODO(yuexima): remove duplicate return result