1#!/usr/bin/env python
2#
3# Copyright 2017 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18# Create the configuration files for a hidl hal test.
19# This script generates Android.bp and AndroidTest.xml files under
20# test/vts-testcases/hal/ based on the hal package name.
21
22import datetime
23import os
24import sys
25
26from build.vts_spec_parser import VtsSpecParser
27from xml.dom import minidom
28from xml.etree import cElementTree as ET
29from xml.sax.saxutils import unescape
30from utils.const import Constant
31
32ANDROID_BP_FILE_NAME = 'Android.bp'
33ANDROID_TEST_XML_FILE_NAME = 'AndroidTest.xml'
34
35
36class TestCaseCreator(object):
37    """Init a test case directory with helloworld test case.
38
39    Attributes:
40        hal_package_name: string, package name of the testing hal. e.g. android.hardware.nfc@1.0.
41        hal_name: name of the testing hal, derived from hal_package_name. e.g. nfc.
42        hal_version: version of the testing hal, derived from hal_package_name.
43        test_type: string, type of the test, currently support host and target.
44        package_root: String, prefix of the hal package, e.g. android.hardware.
45        path_root: String, root path that stores the hal definition, e.g. hardware/interfaces
46        test_binary_file: String, test binary name for target-side hal test.
47        test_script_file: String, test script name for host-side hal test.
48        test_config_dir: String, directory path to store the configure files.
49        test_name_prefix: prefix of generated test name. e.g. android.hardware.nfc@1.0-test-target.
50        test_name: test name generated. e.g. android.hardware.nfc@1.0-test-target-profiling.
51        test_plan: string, the plan that the test belongs to.
52        test_dir: string, test case absolute directory.
53        time_out: string, timeout of the test, default is 1m.
54        stop_runtime: boolean whether to stop framework before the test.
55        build_top: string, equal to environment variable ANDROID_BUILD_TOP.
56        vts_spec_parser: tools that generates and parses vts spec with hidl-gen.
57        current_year: current year.
58    """
59
60    def __init__(self, vts_spec_parser, hal_package_name):
61        '''Initialize class attributes.'''
62        self._hal_package_name = hal_package_name
63
64        build_top = os.getenv('ANDROID_BUILD_TOP')
65        if not build_top:
66            print('Error: Missing ANDROID_BUILD_TOP env variable. Please run '
67                  '\'. build/envsetup.sh; lunch <build target>\' Exiting...')
68            sys.exit(1)
69        self._build_top = build_top
70        self._vts_spec_parser = vts_spec_parser
71
72        self._current_year = datetime.datetime.now().year
73
74    def LaunchTestCase(self,
75                       test_type,
76                       time_out='1m',
77                       is_replay=False,
78                       stop_runtime=False,
79                       update_only=False,
80                       mapping_dir_path="",
81                       test_binary_file=None,
82                       test_script_file=None,
83                       test_config_dir=Constant.VTS_HAL_TEST_CASE_PATH,
84                       package_root=Constant.HAL_PACKAGE_PREFIX,
85                       path_root=Constant.HAL_INTERFACE_PATH,
86                       is_profiling=False):
87        """Create the necessary configuration files to launch a test case.
88
89        Args:
90          test_type: type of the test.
91          time_out: timeout of the test.
92          stop_runtime: whether to stop framework before the test.
93          update_only: flag to only update existing test configure.
94          mapping_dir_path: directory that stores the cts_hal_mapping files.
95                            Used for adapter test only.
96
97        Returns:
98          boolean, whether created/updated a test case successfully.
99        """
100        self._test_type = test_type
101        self._time_out = time_out
102        self._is_replay = is_replay
103        self._stop_runtime = stop_runtime
104        self._mapping_dir_path = mapping_dir_path
105        self._test_binary_file = test_binary_file
106        self._test_script_file = test_script_file
107        self._test_config_dir = test_config_dir
108        self._package_root = package_root
109        self._path_root = path_root
110
111        [package, version] = self._hal_package_name.split('@')
112        self._hal_name = package[len(self._package_root) + 1:]
113        self._hal_version = version
114
115        self._test_module_name = self.GetVtsHalTestModuleName()
116        self._test_name = self._test_module_name
117        self._test_plan = 'vts-staging-default'
118        if is_replay:
119            self._test_name = self._test_module_name + 'Replay'
120            self._test_plan = 'vts-hal-replay'
121        if self._test_type == 'adapter':
122            self._test_plan = 'vts-hal-adapter'
123
124        self._test_dir = self.GetHalTestCasePath()
125        # Check whether the host side test script and target test binary is available.
126        if self._test_type == 'host':
127            if not self._test_script_file:
128                test_script_file = self.GetVtsHostTestScriptFileName()
129                if not os.path.exists(test_script_file):
130                    print('Could not find the host side test script: %s.' %
131                          test_script_file)
132                    return False
133                self._test_script_file = os.path.basename(test_script_file)
134        elif self._test_type == 'target':
135            if not self._test_binary_file:
136                test_binary_file = self.GetVtsTargetTestSourceFileName()
137                if not os.path.exists(test_binary_file):
138                    print('Could not find the target side test binary: %s.' %
139                          test_binary_file)
140                    return False
141                self._test_binary_file = os.path.basename(test_binary_file)
142
143        if os.path.exists(self._test_dir):
144            print 'WARNING: Test directory already exists. Continuing...'
145        elif not update_only:
146            try:
147                os.makedirs(self._test_dir)
148            except:
149                print('Error: Failed to create test directory at %s. '
150                      'Exiting...' % self._test_dir)
151                return False
152        else:
153            print('WARNING: Test directory does not exists, stop updating.')
154            return True
155
156        self.CreateAndroidBp()
157        self.CreateAndroidTestXml()
158        return True
159
160    def GetVtsTargetTestSourceFileName(self):
161        """Get the name of target side test source file ."""
162        test_binary_name = self._test_module_name + 'Test.cpp'
163        return os.path.join(self.GetHalInterfacePath(), 'vts/functional',
164                            test_binary_name)
165
166    def GetVtsHostTestScriptFileName(self):
167        """Get the name of host side test script file ."""
168        test_script_name = self._test_module_name + 'Test.py'
169        return os.path.join(
170            self.GetHalTestCasePath(), test_script_name)
171
172    def GetVtsHalTestModuleName(self):
173        """Get the test model name with format VtsHalHalNameVersionTestType."""
174        sub_names = self._hal_name.split('.')
175        hal_name_upper_camel = ''.join(x.title() for x in sub_names)
176        return 'VtsHal' + hal_name_upper_camel + self.GetHalVersionToken(
177        ) + self._test_type.title()
178
179    def GetVtsHalReplayTraceFiles(self):
180        """Get the trace files for replay test."""
181        trace_files = []
182        for filename in os.listdir(self.GetHalTracePath()):
183            if filename.endswith(".trace"):
184                trace_files.append(filename)
185        return trace_files
186
187    def GetHalPath(self):
188        """Get the hal path based on hal name."""
189        return self._hal_name.replace('.', '/')
190
191    def GetHalVersionToken(self):
192        """Get a string of the hal version."""
193        return 'V' + self._hal_version.replace('.', '_')
194
195    def GetHalInterfacePath(self):
196        """Get the directory that stores the .hal files."""
197        return os.path.join(self._build_top, self._path_root,
198                            self.GetHalPath(), self._hal_version)
199
200    def GetHalTestCasePath(self):
201        """Get the directory that stores the test case."""
202        test_dir = self._test_type
203        if self._is_replay:
204            test_dir = test_dir + '_replay'
205        return os.path.join(self._build_top, self._test_config_dir,
206                            self.GetHalPath(), self.GetHalVersionToken(),
207                            test_dir)
208
209    def GetHalTracePath(self):
210        """Get the directory that stores the hal trace files."""
211        return os.path.join(self._build_top, Constant.HAL_TRACE_PATH,
212                            self.GetHalPath(), self.GetHalVersionToken())
213
214    def CreateAndroidBp(self):
215        """Create Android.bp."""
216        target = os.path.join(self._test_dir, ANDROID_BP_FILE_NAME)
217        with open(target, 'w') as f:
218            print 'Creating %s' % target
219            f.write(ANDROID_BP_TEMPLATE.format(test_name=self._test_name, year=self._current_year))
220
221    def CreateAndroidTestXml(self):
222        """Create AndroidTest.xml."""
223        VTS_FILE_PUSHER = 'com.android.compatibility.common.tradefed.targetprep.VtsFilePusher'
224        VTS_TEST_CLASS = 'com.android.tradefed.testtype.VtsMultiDeviceTest'
225
226        configuration = ET.Element('configuration', {
227            'description':
228            'Config for VTS ' + self._test_name + ' test cases'
229        })
230
231        ET.SubElement(
232            configuration, 'option', {
233                'name': 'config-descriptor:metadata',
234                'key': 'plan',
235                'value': self._test_plan
236            })
237
238        if self._test_type == 'adapter':
239            self.CreateAndroidTestXmlForAdapterTest(configuration)
240        else:
241            file_pusher = ET.SubElement(configuration, 'target_preparer',
242                                        {'class': VTS_FILE_PUSHER})
243
244            self.GeneratePushFileConfigure(file_pusher)
245            test = ET.SubElement(configuration, 'test',
246                                 {'class': VTS_TEST_CLASS})
247
248            self.GenerateTestOptionConfigure(test)
249
250        target = os.path.join(self._test_dir, ANDROID_TEST_XML_FILE_NAME)
251        with open(target, 'w') as f:
252            print 'Creating %s' % target
253            f.write(XML_HEADER)
254            f.write(LICENSE_STATEMENT_XML.format(year=self._current_year))
255            f.write(self.Prettify(configuration))
256
257    def CreateAndroidTestXmlForAdapterTest(self, configuration):
258        """Create the test configuration within AndroidTest.xml for adapter test.
259
260        Args:
261          configuration: parent xml element for test configure.
262        """
263
264        # Configure VtsHalAdapterPreparer.
265        adapter_module_controller = ET.SubElement(configuration, 'object',
266                                         {'type': 'module_controller',
267                                          'class': VTA_HAL_ADAPTER_MODULE_CONTROLLER})
268        ET.SubElement(adapter_module_controller, 'option', {
269            'name': 'hal-package-name',
270            'value': self._hal_package_name
271        })
272        adapter_preparer = ET.SubElement(configuration, 'target_preparer',
273                                         {'class': VTA_HAL_ADAPTER_PREPARER})
274        (major_version, minor_version) = self._hal_version.split('.')
275        adapter_version = major_version + '.' + str(int(minor_version) - 1)
276        ET.SubElement(
277            adapter_preparer, 'option', {
278                'name':
279                'adapter-binary-name',
280                'value':
281                Constant.HAL_PACKAGE_PREFIX + '.' + self._hal_name + '@' +
282                adapter_version + '-adapter'
283            })
284        ET.SubElement(adapter_preparer, 'option', {
285            'name': 'hal-package-name',
286            'value': self._hal_package_name
287        })
288        # Configure device health tests.
289        test = ET.SubElement(configuration, 'test',
290                             {'class': ANDROID_JUNIT_TEST})
291        ET.SubElement(test, 'option', {
292            'name': 'package',
293            'value': 'com.android.devicehealth.tests'
294        })
295        ET.SubElement(
296            test, 'option', {
297                'name': 'runner',
298                'value': 'androidx.test.runner.AndroidJUnitRunner'
299            })
300
301        # Configure CTS tests.
302        list_of_files = os.listdir(self._mapping_dir_path)
303        # Use the latest mapping file.
304        latest_file = max(
305            [
306                os.path.join(self._mapping_dir_path, basename)
307                for basename in list_of_files
308            ],
309            key=os.path.getctime)
310
311        with open(latest_file, 'r') as cts_hal_map_file:
312            for line in cts_hal_map_file.readlines():
313                if line.startswith(Constant.HAL_PACKAGE_PREFIX + '.' +
314                                   self._hal_name + '@' + adapter_version):
315                    cts_tests = line.split(':')[1].split(',')
316                    for cts_test in cts_tests:
317                        test_config_name = cts_test[0:cts_test.find(
318                            '(')] + '.config'
319                        ET.SubElement(configuration, 'include',
320                                      {'name': test_config_name})
321
322    def GeneratePushFileConfigure(self, file_pusher):
323        """Create the push file configuration within AndroidTest.xml
324
325        Args:
326          file_pusher: parent xml element for push file configure.
327        """
328        ET.SubElement(file_pusher, 'option', {
329            'name': 'abort-on-push-failure',
330            'value': 'false'
331        })
332
333        if self._test_type == 'target':
334            if self._is_replay:
335                ET.SubElement(file_pusher, 'option', {
336                    'name': 'push-group',
337                    'value': 'HalHidlHostTest.push'
338                })
339            else:
340                ET.SubElement(file_pusher, 'option', {
341                    'name': 'push-group',
342                    'value': 'HalHidlTargetTest.push'
343                })
344        else:
345            ET.SubElement(file_pusher, 'option', {
346                'name': 'push-group',
347                'value': 'HalHidlHostTest.push'
348            })
349
350        imported_package_lists = self._vts_spec_parser.ImportedPackagesList(
351            self._hal_name, self._hal_version)
352        imported_package_lists.append(self._hal_package_name)
353        # Generate additional push files e.g driver/profiler/vts_spec
354        if self._test_type == 'host' or self._is_replay:
355            ET.SubElement(file_pusher, 'option', {
356                'name': 'cleanup',
357                'value': 'true'
358            })
359            for imported_package in imported_package_lists:
360                imported_package_str, imported_package_version = imported_package.split(
361                    '@')
362                imported_package_name = imported_package_str[
363                    len(self._package_root) + 1:]
364                imported_vts_spec_lists = self._vts_spec_parser.VtsSpecNames(
365                    imported_package_name, imported_package_version)
366                for vts_spec in imported_vts_spec_lists:
367                    push_spec = VTS_SPEC_PUSH_TEMPLATE.format(
368                        hal_path=imported_package_name.replace('.', '/'),
369                        hal_version=imported_package_version,
370                        package_path=imported_package_str.replace('.', '/'),
371                        vts_file=vts_spec)
372                    ET.SubElement(file_pusher, 'option', {
373                        'name': 'push',
374                        'value': push_spec
375                    })
376
377                dirver_package_name = imported_package + '-vts.driver.so'
378                push_driver = VTS_LIB_PUSH_TEMPLATE_32.format(
379                    lib_name=dirver_package_name)
380                ET.SubElement(file_pusher, 'option', {
381                    'name': 'push',
382                    'value': push_driver
383                })
384                push_driver = VTS_LIB_PUSH_TEMPLATE_64.format(
385                    lib_name=dirver_package_name)
386                ET.SubElement(file_pusher, 'option', {
387                    'name': 'push',
388                    'value': push_driver
389                })
390
391    def GenerateTestOptionConfigure(self, test):
392        """Create the test option configuration within AndroidTest.xml
393
394        Args:
395          test: parent xml element for test option configure.
396        """
397        ET.SubElement(test, 'option', {
398            'name': 'test-module-name',
399            'value': self._test_name
400        })
401
402        if self._test_type == 'target':
403            if self._is_replay:
404                ET.SubElement(test, 'option', {
405                    'name': 'binary-test-type',
406                    'value': 'hal_hidl_replay_test'
407                })
408                for trace in self.GetVtsHalReplayTraceFiles():
409                    ET.SubElement(
410                        test, 'option', {
411                            'name':
412                            'hal-hidl-replay-test-trace-path',
413                            'value':
414                            TEST_TRACE_TEMPLATE.format(
415                                hal_path=self.GetHalPath(),
416                                hal_version=self.GetHalVersionToken(),
417                                trace_file=trace)
418                        })
419                ET.SubElement(
420                    test, 'option', {
421                        'name': 'hal-hidl-package-name',
422                        'value': self._hal_package_name
423                    })
424            else:
425                test_binary_file = TEST_BINEARY_TEMPLATE_32.format(
426                    test_binary=self._test_binary_file[:-len('.cpp')])
427                ET.SubElement(test, 'option', {
428                    'name': 'binary-test-source',
429                    'value': test_binary_file
430                })
431                test_binary_file = TEST_BINEARY_TEMPLATE_64.format(
432                    test_binary=self._test_binary_file[:-len('.cpp')])
433                ET.SubElement(test, 'option', {
434                    'name': 'binary-test-source',
435                    'value': test_binary_file
436                })
437                ET.SubElement(test, 'option', {
438                    'name': 'binary-test-type',
439                    'value': 'hal_hidl_gtest'
440                })
441                if self._stop_runtime:
442                    ET.SubElement(test, 'option', {
443                        'name': 'binary-test-disable-framework',
444                        'value': 'true'
445                    })
446                    ET.SubElement(test, 'option', {
447                        'name': 'binary-test-stop-native-servers',
448                        'value': 'true'
449                    })
450        else:
451            test_script_file = TEST_SCRIPT_TEMPLATE.format(
452                hal_path=self.GetHalPath(),
453                hal_version=self.GetHalVersionToken(),
454                test_script=self._test_script_file[:-len('.py')])
455            ET.SubElement(test, 'option', {
456                'name': 'test-case-path',
457                'value': test_script_file
458            })
459
460        ET.SubElement(test, 'option', {
461            'name': 'test-timeout',
462            'value': self._time_out
463        })
464
465    def Prettify(self, elem):
466        """Create a pretty-printed XML string for the Element.
467
468        Args:
469          elem: a xml element.
470
471        Regurns:
472          A pretty-printed XML string for the Element.
473        """
474        if elem:
475            doc = minidom.Document()
476            declaration = doc.toxml()
477            rough_string = ET.tostring(elem, 'utf-8')
478            reparsed = minidom.parseString(rough_string)
479            return unescape(
480                reparsed.toprettyxml(indent="    ")[len(declaration) + 1:])
481
482
483LICENSE_STATEMENT_XML = """<!-- Copyright (C) {year} The Android Open Source Project
484
485     Licensed under the Apache License, Version 2.0 (the "License");
486     you may not use this file except in compliance with the License.
487     You may obtain a copy of the License at
488
489          http://www.apache.org/licenses/LICENSE-2.0
490
491     Unless required by applicable law or agreed to in writing, software
492     distributed under the License is distributed on an "AS IS" BASIS,
493     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
494     See the License for the specific language governing permissions and
495     limitations under the License.
496-->
497"""
498
499ANDROID_BP_TEMPLATE = """// Copyright (C) {year} The Android Open Source Project
500//
501// Licensed under the Apache License, Version 2.0 (the "License");
502// you may not use this file except in compliance with the License.
503// You may obtain a copy of the License at
504//
505//      http://www.apache.org/licenses/LICENSE-2.0
506//
507// Unless required by applicable law or agreed to in writing, software
508// distributed under the License is distributed on an "AS IS" BASIS,
509// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
510// See the License for the specific language governing permissions and
511// limitations under the License.
512//
513// This file is autogenerated by test/vts-testcase/hal/script/test_case_creator.py
514// DO NOT EDIT
515
516vts_config {{
517    name: "{test_name}",
518}}
519
520"""
521
522XML_HEADER = """<?xml version="1.0" encoding="utf-8"?>
523"""
524
525TEST_BINEARY_TEMPLATE_32 = '_32bit::DATA/nativetest/{test_binary}/{test_binary}'
526TEST_BINEARY_TEMPLATE_64 = '_64bit::DATA/nativetest64/{test_binary}/{test_binary}'
527
528TEST_SCRIPT_TEMPLATE = 'vts/testcases/hal/{hal_path}/{hal_version}/host/{test_script}'
529TEST_TRACE_TEMPLATE = 'test/vts-testcase/hal-trace/{hal_path}/{hal_version}/{trace_file}'
530VTS_SPEC_PUSH_TEMPLATE = (
531    'spec/hardware/interfaces/{hal_path}/{hal_version}/vts/{vts_file}->'
532    '/data/local/tmp/spec/{package_path}/{hal_version}/{vts_file}')
533VTS_LIB_PUSH_TEMPLATE_32 = 'DATA/lib/{lib_name}->/data/local/tmp/32/{lib_name}'
534VTS_LIB_PUSH_TEMPLATE_64 = 'DATA/lib64/{lib_name}->/data/local/tmp/64/{lib_name}'
535
536VTA_HAL_ADAPTER_MODULE_CONTROLLER = 'com.android.tradefed.module.VtsHalAdapterModuleController'
537VTA_HAL_ADAPTER_PREPARER = 'com.android.tradefed.targetprep.VtsHalAdapterPreparer'
538ANDROID_JUNIT_TEST = 'com.android.tradefed.testtype.AndroidJUnitTest'
539