1#
2# Copyright (C) 2017 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 sys
19
20from importlib import import_module
21from xml.etree import ElementTree
22
23
24class FuzzerType(object):
25    """Types of fuzzers."""
26    FUNC_FUZZER = 0
27    IFACE_FUZZER = 1
28
29
30class ConfigGen(object):
31    """Config generator for test/vts-testcase/fuzz.
32
33    Attributes:
34        _android_build_top: string, equal to environment variable ANDROID_BUILD_TOP.
35        _project_path: string, path to test/vts-testcase/fuzz.
36        _template_dir: string, path to directory containing templates.
37        _utils: test/vts-testcase/hal/script/build/config_gen_utils module.
38        _vts_spec_parser: tools that generates and parses vts spec with hidl-gen.
39    """
40
41    def __init__(self):
42        """ConfigGen constructor. """
43        self._android_build_top = os.environ.get('ANDROID_BUILD_TOP')
44        if not self._android_build_top:
45            print 'Run "lunch" command first.'
46            sys.exit(1)
47        self._project_path = os.path.join(self._android_build_top, 'test',
48                                          'vts-testcase', 'fuzz')
49        self._template_dir = os.path.join(self._project_path, 'script',
50                                          'config', 'template')
51        sys.path.append(
52            os.path.join(self._android_build_top, 'test', 'vts-testcase', 'hal',
53                         'script', 'build'))
54        vts_spec_parser = import_module('vts_spec_parser')
55        self._utils = import_module('build_rule_gen_utils')
56        self._vts_spec_parser = vts_spec_parser.VtsSpecParser()
57
58    def _GetPlansFromConfig(self, xml_file_path):
59        """Gets plan names from a module config.
60
61        Args:
62            xml_file_path: string, path to the XML file.
63
64        Returns:
65            list of strings, the plans that the module belongs to.
66        """
67        root = ElementTree.parse(xml_file_path).getroot()
68        plans = [e.attrib["value"] for e in root.iterfind("option") if
69                 e.get("name", "") == "config-descriptor:metadata" and
70                 e.get("key", "") == "plan" and
71                 "value" in e.attrib]
72        return plans
73
74    def UpdateFuzzerConfigs(self):
75        """Updates build rules for fuzzers.
76
77        Updates fuzzer configs for each pair of (hal_name, hal_version).
78        """
79        config_dir = os.path.join(self._project_path, 'config')
80        old_config = dict()
81
82        for base_dir, dirs, files, in os.walk(config_dir):
83            for file_name in files:
84                file_path = os.path.join(base_dir, file_name)
85                if file_name == 'AndroidTest.xml':
86                    old_config[file_path] = self._GetPlansFromConfig(file_path)
87                if file_name in ('AndroidTest.xml', 'Android.bp'):
88                    os.remove(file_path)
89
90        self.UpdateFuzzerConfigsForType(FuzzerType.IFACE_FUZZER, old_config)
91
92    def UpdateFuzzerConfigsForType(self, fuzzer_type, old_config):
93        """Updates build rules for fuzzers.
94
95        Updates fuzzer configs for given fuzzer type.
96
97        Args:
98            fuzzer_type: FuzzerType, type of fuzzer.
99            old_config: dict. The key is the path to the old XML. The value is
100                the list of the plans the module belongs to.
101        """
102        bp_template_path = os.path.join(self._template_dir, 'template.bp')
103        xml_template_path = os.path.join(self._template_dir, 'template.xml')
104        with open(bp_template_path) as template_file:
105            bp_template = str(template_file.read())
106        with open(xml_template_path) as template_file:
107            xml_template = str(template_file.read())
108
109        hal_list = self._vts_spec_parser.HalNamesAndVersions()
110        for hal_name, hal_version in hal_list:
111            if not self._IsTestable(hal_name, hal_version):
112                continue
113            fuzzer_type_subdir = self._FuzzerTypeUnderscore(fuzzer_type)
114            config_dir = os.path.join(
115                self._project_path, 'config', self._utils.HalNameDir(hal_name),
116                self._utils.HalVerDir(hal_version), fuzzer_type_subdir)
117            bp_file_path = os.path.join(config_dir, 'Android.bp')
118            xml_file_path = os.path.join(config_dir, 'AndroidTest.xml')
119
120            plan = 'vts-staging-fuzz'
121            if xml_file_path in old_config:
122                old_plans = old_config[xml_file_path]
123                if old_plans:
124                    plan = old_plans[0]
125                else:
126                    print('WARNING: No plan name in %s' % xml_file_path)
127                if len(old_plans) > 1:
128                    print('WARNING: More than one plan name in %s' %
129                          xml_file_path)
130
131            bp_string = self._FillOutTemplate(
132                hal_name, hal_version, fuzzer_type, plan, bp_template)
133
134            xml_string = self._FillOutTemplate(
135                hal_name, hal_version, fuzzer_type, plan, xml_template)
136
137            self._utils.WriteBuildRule(bp_file_path, bp_string)
138            self._utils.WriteBuildRule(xml_file_path, xml_string)
139
140    def _FuzzerTestName(self, hal_name, hal_version, fuzzer_type):
141        """Returns vts hal fuzzer test module name.
142
143        Args:
144            hal_name: string, name of the hal, e.g. 'vibrator'.
145            hal_version: string, version of the hal, e.g '7.4'
146            fuzzer_type: FuzzerType, type of fuzzer.
147
148        Returns:
149            string, test module name, e.g. VtsHalVibratorV7_4FuncFuzzer
150        """
151        test_name = 'VtsHal'
152        test_name += ''.join(map(lambda x: x.title(), hal_name.split('.')))
153        test_name += self._utils.HalVerDir(hal_version)
154        test_name += self._FuzzerTypeCamel(fuzzer_type)
155        return test_name
156
157    def _FuzzerTypeUnderscore(self, fuzzer_type):
158        """Returns vts hal fuzzer type string in underscore case.
159
160        Args:
161            fuzzer_type: FuzzerType, type of fuzzer.
162
163        Returns:
164            string, fuzzer type, e.g. "iface_fuzzer"
165        """
166        if fuzzer_type == FuzzerType.FUNC_FUZZER:
167            test_type = 'func_fuzzer'
168        else:
169            test_type = 'iface_fuzzer'
170        return test_type
171
172    def _FuzzerTypeCamel(self, fuzzer_type):
173        """Returns vts hal fuzzer type string in camel case.
174
175        Args:
176            fuzzer_type: FuzzerType, type of fuzzer.
177
178        Returns:
179            string, fuzzer type, e.g. "IfaceFuzzer"
180        """
181        if fuzzer_type == FuzzerType.FUNC_FUZZER:
182            test_type = 'FuncFuzzer'
183        else:
184            test_type = 'IfaceFuzzer'
185        return test_type
186
187    def _FillOutTemplate(self, hal_name, hal_version, fuzzer_type, plan,
188                         template):
189        """Returns build rules in string form by filling out given template.
190
191        Args:
192            hal_name: string, name of the hal, e.g. 'vibrator'.
193            hal_version: string, version of the hal, e.g '7.4'
194            fuzzer_type: FuzzerType, type of fuzzer.
195            plan: string, name of the plan, e.g. 'vts-staging-fuzz'
196            template: string, build rule template to fill out.
197
198        Returns:
199            string, complete build rule in string form.
200        """
201        config = template
202        config = config.replace('{TEST_NAME}', self._FuzzerTestName(
203            hal_name, hal_version, fuzzer_type))
204        config = config.replace('{HAL_NAME}', hal_name)
205        config = config.replace('{HAL_VERSION}', hal_version)
206        config = config.replace('{TEST_TYPE_CAMEL}',
207                                self._FuzzerTypeCamel(fuzzer_type))
208        config = config.replace('{TEST_TYPE_UNDERSCORE}',
209                                self._FuzzerTypeUnderscore(fuzzer_type))
210        config = config.replace('{PLAN}', plan)
211        return config
212
213    def _IsTestable(self, hal_name, hal_version):
214        """Returns true iff hal can be tested."""
215        if 'tests' in hal_name:
216            return False
217        return True
218