1#
2# Copyright (C) 2016 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17import os
18import logging
19import itertools
20
21from vts.runners.host import const
22
23from vts.testcases.kernel.ltp import ltp_configs
24from vts.testcases.kernel.ltp import ltp_enums
25from vts.testcases.kernel.ltp import test_case
26
27
28class TestCasesParser(object):
29    """Load a ltp vts testcase definition file and parse it into a generator.
30
31    Attributes:
32        _data_path: string, the vts data path on host side
33        _filter_func: function, a filter method that will emit exception if a test is filtered
34        disabled_tests: list of string
35        staging_tests: list of string
36    """
37
38    def __init__(self, data_path, filter_func, disabled_tests, staging_tests):
39        self._data_path = data_path
40        self._filter_func = filter_func
41        self._disabled_tests = disabled_tests
42        self._staging_tests = staging_tests
43
44    def ValidateDefinition(self, line):
45        """Validate a tab delimited test case definition.
46
47        Will check whether the given line of definition has three parts
48        separated by tabs.
49        It will also trim leading and ending white spaces for each part
50        in returned tuple (if valid).
51
52        Returns:
53            A tuple in format (test suite, test name, test command) if
54            definition is valid. None otherwise.
55        """
56        items = [
57            item.strip()
58            for item in line.split(ltp_enums.Delimiters.TESTCASE_DEFINITION)
59        ]
60        if not len(items) == 3 or not items:
61            return None
62        else:
63            return items
64
65    def Load(self, ltp_dir, n_bit, run_staging=False):
66        """Read the definition file and yields a TestCase generator."""
67        run_scritp = self.GenerateLtpRunScript()
68
69        for line in run_scritp:
70            items = self.ValidateDefinition(line)
71            if not items:
72                continue
73
74            testsuite, testname, command = items
75
76            # Tests failed to build will have prefix "DISABLED_"
77            if testname.startswith("DISABLED_"):
78                logging.info("[Parser] Skipping test case {}-{}. Reason: "
79                             "not built".format(testsuite, testname))
80                continue
81
82            # Some test cases contain semicolons in their commands,
83            # and we replace them with &&
84            command = command.replace(';', '&&')
85
86            testcase = test_case.TestCase(
87                testsuite=testsuite, testname=testname, command=command)
88
89            test_display_name = "{}_{}bit".format(str(testcase), n_bit)
90
91            # Check runner's base_test filtering method
92            try:
93                self._filter_func(test_display_name)
94            except:
95                logging.info("[Parser] Skipping test case %s. Reason: "
96                             "filtered" % testcase.fullname)
97                testcase.is_filtered = True
98                testcase.note = "filtered"
99
100            # For skipping tests that are not designed or ready for Android
101            if test_display_name in self._disabled_tests:
102                logging.info("[Parser] Skipping test case %s. Reason: "
103                             "disabled" % testcase.fullname)
104                continue
105
106            # For separating staging tests from stable tests
107            if test_display_name in self._staging_tests:
108                if not run_staging:
109                    # Skip staging tests in stable run
110                    continue
111                else:
112                    testcase.is_staging = True
113                    testcase.note = "staging"
114            else:
115                if run_staging:
116                    # Skip stable tests in staging run
117                    continue
118
119            logging.info("[Parser] Adding test case %s." % testcase.fullname)
120            yield testcase
121
122    def ReadCommentedTxt(self, filepath):
123        '''Read a lines of a file that are not commented by #.
124
125        Args:
126            filepath: string, path of file to read
127
128        Returns:
129            A set of string representing non-commented lines in given file
130        '''
131        if not filepath:
132            logging.error('Invalid file path')
133            return None
134
135        with open(filepath, 'r') as f:
136            lines_gen = (line.strip() for line in f)
137            return set(
138                line for line in lines_gen
139                if line and not line.startswith('#'))
140
141    def GenerateLtpTestCases(self, testsuite, disabled_tests_list):
142        '''Generate test cases for each ltp test suite.
143
144        Args:
145            testsuite: string, test suite name
146
147        Returns:
148            A list of string
149        '''
150        testsuite_script = os.path.join(self._data_path,
151                                        ltp_configs.LTP_RUNTEST_DIR, testsuite)
152
153        result = []
154        for line in open(testsuite_script, 'r'):
155            line = line.strip()
156            if not line or line.startswith('#'):
157                continue
158
159            testname = line.split()[0]
160            testname_prefix = ('DISABLED_'
161                               if testname in disabled_tests_list else '')
162            testname_modified = testname_prefix + testname
163
164            result.append("\t".join([testsuite, testname_modified, line[len(
165                testname):].strip()]))
166        return result
167
168    def GenerateLtpRunScript(self):
169        '''Given a scenario group generate test case script.
170
171        Args:
172            scenario_group: string, file path of scanerio group file
173
174        Returns:
175            A list of string
176        '''
177        disabled_tests_path = os.path.join(
178            self._data_path, ltp_configs.LTP_DISABLED_BUILD_TESTS_CONFIG_PATH)
179        disabled_tests_list = self.ReadCommentedTxt(disabled_tests_path)
180
181        result = []
182        for testsuite in ltp_configs.TEST_SUITES:
183            result.extend(
184                self.GenerateLtpTestCases(testsuite, disabled_tests_list))
185
186        #TODO(yuexima): remove duplicate
187
188        return result
189