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 importlib
18import json
19import logging
20import os
21import subprocess
22import sys
23import time
24
25from vts.runners.host import asserts
26from vts.runners.host import base_test
27from vts.runners.host import config_parser
28from vts.runners.host import records
29from vts.runners.host import test_runner
30from vts.utils.python.io import capture_printout
31from vts.utils.python.io import file_util
32
33ACTS_TEST_MODULE = 'ACTS_TEST_MODULE'
34LIST_TEST_OUTPUT_START = '==========> '
35LIST_TEST_OUTPUT_END = ' <=========='
36# Temp directory inside python log path. The name is required to be
37# 'temp' for the Java framework to skip reading contents as regular test logs.
38TEMP_DIR_NAME = 'temp'
39CONFIG_FILE_NAME = 'acts_config.txt'
40RESULT_FILE_NAME = 'test_run_summary.json'
41
42CONFIG_TEXT = '''{{
43    "_description": "VTS acts tests",
44    "testbed":
45    [
46        {{
47            "_description": "ACTS test bed",
48            "name": "{module_name}",
49            "AndroidDevice":
50            [
51              {serials}
52            ]
53        }}
54    ],
55    "logpath": "{log_path}",
56    "testpaths":
57    [
58        "{src_path}"
59    ]
60}}
61'''
62
63
64class ActsAdapter(base_test.BaseTestClass):
65    '''Template class for running acts test cases.
66
67    Attributes:
68        test_type: string, name of test type this adapter is for
69        result_path: string, test result directory for the adaptor
70        config_path: string, test config file path
71        module_name: string, ACTS module name
72        test_path: string, ACTS module source directory
73    '''
74    test_type = 'ACTS'
75
76    def setUpClass(self):
77        '''Set up result directory, generate configuration file, and list tests.'''
78        self.result_path = os.path.join(logging.log_path, TEMP_DIR_NAME,
79                                        self.test_type, str(time.time()))
80        file_util.Makedirs(self.result_path)
81        logging.debug('Result path for %s: %s' % (self.test_type,
82                                                 self.result_path))
83        self.test_path, self.module_name = self.getUserParam(
84            ACTS_TEST_MODULE).rsplit('/', 1)
85
86        self.config_path = os.path.join(self.result_path, CONFIG_FILE_NAME)
87        self.GenerateConfigFile()
88
89        testcases = self.ListTestCases()
90        logging.debug('ACTS Test cases: %s', testcases)
91
92    def tearDownClass(self):
93        '''Clear the result path.'''
94        file_util.Rmdirs(self.result_path, ignore_errors=True)
95
96    def GenerateConfigFile(self):
97        '''Generate test configuration file.'''
98        serials = []
99        for ad in self.android_devices:
100            serials.append('{"serial":"%s"}' % ad.serial)
101
102        config_text = CONFIG_TEXT.format(
103            module_name=self.module_name,
104            serials=','.join(serials),
105            log_path=self.result_path,
106            src_path=self.test_path)
107
108        with open(self.config_path, 'w') as f:
109            f.write(config_text)
110
111    def ListTestCases(self):
112        '''List test cases.
113
114        Returns:
115            List of string, test names.
116        '''
117        # TODO use ACTS runner to list test cases and add requested record.
118        # This step is optional but desired. To be implemented later
119
120    def Run(self):
121        '''Execute test cases.'''
122        # acts.py is installed to user bin by ACTS setup script.
123        # In the future, it is preferred to use the source code
124        # from repo directory.
125        bin = 'acts/bin/act.py'
126
127        cmd = '{bin} -c {config} -tb {module_name} -tc {module_name}'.format(
128            bin=bin, config=self.config_path, module_name=self.module_name)
129        logging.debug('cmd is: %s', cmd)
130
131        # Calling through subprocess is required because ACTS requires python3
132        # while VTS is currently using python2. In the future, ACTS runner
133        # can be invoked through importing when VTS upgrades to python3.
134
135        # A "hack" to call python3 outside of python2 virtualenv created by
136        # VTS framework
137        environ = {
138            key: val
139            for key, val in os.environ.iteritems() if 'virtualenv' not in val
140        }
141
142        # TODO(yuexima): disable buffer
143        p = subprocess.Popen(
144            cmd,
145            shell=True,
146            stdout=subprocess.PIPE,
147            stderr=subprocess.STDOUT,
148            env=environ)
149
150        for line in iter(p.stdout.readline, b''):
151            print line.rstrip()
152
153        p.communicate()
154        if p.returncode:
155            asserts.fail('Subprocess of ACTS command failed. Return code: %s' %
156                         p.returncode)
157
158    def ParseResults(self):
159        '''Get module run results and put in vts results.'''
160        file_path = file_util.FindFile(self.result_path, RESULT_FILE_NAME)
161
162        if file_path:
163            logging.debug('ACTS test result path: %s', file_path)
164            self.ParseJsonResults(file_path)
165        else:
166            logging.error('Cannot find result file name %s in %s',
167                          RESULT_FILE_NAME, self.result_path)
168
169    def generateAllTests(self):
170        '''Run the test module and parse results.'''
171        self.Run()
172        self.ParseResults()
173
174    def ParseJsonResults(self, result_path):
175        '''Parse test json result.
176
177        Args:
178            result_path: string, result json file path.
179        '''
180        with open(result_path, 'r') as f:
181            summary = json.load(f)
182
183        results = summary['Results']
184        for result in results:
185            logging.debug('Adding result for %s' %
186                         result[records.TestResultEnums.RECORD_NAME])
187            record = records.TestResultRecord(
188                result[records.TestResultEnums.RECORD_NAME])
189            record.test_class = result[records.TestResultEnums.RECORD_CLASS]
190            record.begin_time = result[
191                records.TestResultEnums.RECORD_BEGIN_TIME]
192            record.end_time = result[records.TestResultEnums.RECORD_END_TIME]
193            record.result = result[records.TestResultEnums.RECORD_RESULT]
194            record.uid = result[records.TestResultEnums.RECORD_UID]
195            record.extras = result[records.TestResultEnums.RECORD_EXTRAS]
196            record.details = result[records.TestResultEnums.RECORD_DETAILS]
197            record.extra_errors = result[
198                records.TestResultEnums.RECORD_EXTRA_ERRORS]
199
200            self.results.addRecord(record)
201
202        # TODO(yuexima): parse new result types
203
204if __name__ == "__main__":
205    test_runner.main()
206