1#!/usr/bin/env python3
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"""Unittests for atest."""
18
19# pylint: disable=line-too-long
20
21import datetime
22import os
23import sys
24import tempfile
25import unittest
26
27from importlib import reload
28from io import StringIO
29from unittest import mock
30
31import atest
32import constants
33import module_info
34
35from metrics import metrics_utils
36from test_finders import test_info
37
38#pylint: disable=protected-access
39class AtestUnittests(unittest.TestCase):
40    """Unit tests for atest.py"""
41
42    @mock.patch('os.environ.get', return_value=None)
43    def test_missing_environment_variables_uninitialized(self, _):
44        """Test _has_environment_variables when no env vars."""
45        self.assertTrue(atest._missing_environment_variables())
46
47    @mock.patch('os.environ.get', return_value='out/testcases/')
48    def test_missing_environment_variables_initialized(self, _):
49        """Test _has_environment_variables when env vars."""
50        self.assertFalse(atest._missing_environment_variables())
51
52    def test_parse_args(self):
53        """Test _parse_args parses command line args."""
54        test_one = 'test_name_one'
55        test_two = 'test_name_two'
56        custom_arg = '--custom_arg'
57        custom_arg_val = 'custom_arg_val'
58        pos_custom_arg = 'pos_custom_arg'
59
60        # Test out test and custom args are properly retrieved.
61        args = [test_one, test_two, '--', custom_arg, custom_arg_val]
62        parsed_args = atest._parse_args(args)
63        self.assertEqual(parsed_args.tests, [test_one, test_two])
64        self.assertEqual(parsed_args.custom_args, [custom_arg, custom_arg_val])
65
66        # Test out custom positional args with no test args.
67        args = ['--', pos_custom_arg, custom_arg_val]
68        parsed_args = atest._parse_args(args)
69        self.assertEqual(parsed_args.tests, [])
70        self.assertEqual(parsed_args.custom_args, [pos_custom_arg,
71                                                   custom_arg_val])
72
73    def test_has_valid_test_mapping_args(self):
74        """Test _has_valid_test_mapping_args method."""
75        # Test test mapping related args are not mixed with incompatible args.
76        options_no_tm_support = [
77            ('--generate-baseline', '5'),
78            ('--detect-regression', 'path'),
79            ('--generate-new-metrics', '5')
80        ]
81        tm_options = [
82            '--test-mapping',
83            '--include-subdirs'
84        ]
85
86        for tm_option in tm_options:
87            for no_tm_option, no_tm_option_value in options_no_tm_support:
88                args = [tm_option, no_tm_option]
89                if no_tm_option_value is not None:
90                    args.append(no_tm_option_value)
91                parsed_args = atest._parse_args(args)
92                self.assertFalse(
93                    atest._has_valid_test_mapping_args(parsed_args),
94                    'Failed to validate: %s' % args)
95
96    @mock.patch('json.load', return_value={})
97    @mock.patch('builtins.open', new_callable=mock.mock_open)
98    @mock.patch('os.path.isfile', return_value=True)
99    @mock.patch('atest_utils._has_colors', return_value=True)
100    @mock.patch.object(module_info.ModuleInfo, 'get_module_info',)
101    def test_print_module_info_from_module_name(self, mock_get_module_info,
102                                                _mock_has_colors, _isfile,
103                                                _open, _json):
104        """Test _print_module_info_from_module_name method."""
105        mod_one_name = 'mod1'
106        mod_one_path = ['src/path/mod1']
107        mod_one_installed = ['installed/path/mod1']
108        mod_one_suites = ['device_test_mod1', 'native_test_mod1']
109        mod_one = {constants.MODULE_NAME: mod_one_name,
110                   constants.MODULE_PATH: mod_one_path,
111                   constants.MODULE_INSTALLED: mod_one_installed,
112                   constants.MODULE_COMPATIBILITY_SUITES: mod_one_suites}
113
114        # Case 1: The testing_module('mod_one') can be found in module_info.
115        mock_get_module_info.return_value = mod_one
116        capture_output = StringIO()
117        sys.stdout = capture_output
118        mod_info = module_info.ModuleInfo()
119        # Check return value = True, since 'mod_one' can be found.
120        self.assertTrue(
121            atest._print_module_info_from_module_name(mod_info, mod_one_name))
122        # Assign sys.stdout back to default.
123        sys.stdout = sys.__stdout__
124        correct_output = ('\x1b[1;32mmod1\x1b[0m\n'
125                          '\x1b[1;36m\tCompatibility suite\x1b[0m\n'
126                          '\t\tdevice_test_mod1\n'
127                          '\t\tnative_test_mod1\n'
128                          '\x1b[1;36m\tSource code path\x1b[0m\n'
129                          '\t\tsrc/path/mod1\n'
130                          '\x1b[1;36m\tInstalled path\x1b[0m\n'
131                          '\t\tinstalled/path/mod1\n')
132        # Check the function correctly printed module_info in color to stdout
133        self.assertEqual(capture_output.getvalue(), correct_output)
134
135        # Case 2: The testing_module('mod_one') can NOT be found in module_info.
136        mock_get_module_info.return_value = None
137        capture_output = StringIO()
138        sys.stdout = capture_output
139        # Check return value = False, since 'mod_one' can NOT be found.
140        self.assertFalse(
141            atest._print_module_info_from_module_name(mod_info, mod_one_name))
142        # Assign sys.stdout back to default.
143        sys.stdout = sys.__stdout__
144        null_output = ''
145        # Check if no module_info, then nothing printed to screen.
146        self.assertEqual(capture_output.getvalue(), null_output)
147
148    @mock.patch('json.load', return_value={})
149    @mock.patch('builtins.open', new_callable=mock.mock_open)
150    @mock.patch('os.path.isfile', return_value=True)
151    @mock.patch('atest_utils._has_colors', return_value=True)
152    @mock.patch.object(module_info.ModuleInfo, 'get_module_info',)
153    def test_print_test_info(self, mock_get_module_info, _mock_has_colors,
154                             _isfile, _open, _json):
155        """Test _print_test_info method."""
156        mod_one_name = 'mod1'
157        mod_one = {constants.MODULE_NAME: mod_one_name,
158                   constants.MODULE_PATH: ['path/mod1'],
159                   constants.MODULE_INSTALLED: ['installed/mod1'],
160                   constants.MODULE_COMPATIBILITY_SUITES: ['suite_mod1']}
161        mod_two_name = 'mod2'
162        mod_two = {constants.MODULE_NAME: mod_two_name,
163                   constants.MODULE_PATH: ['path/mod2'],
164                   constants.MODULE_INSTALLED: ['installed/mod2'],
165                   constants.MODULE_COMPATIBILITY_SUITES: ['suite_mod2']}
166        mod_three_name = 'mod3'
167        mod_three = {constants.MODULE_NAME: mod_two_name,
168                     constants.MODULE_PATH: ['path/mod3'],
169                     constants.MODULE_INSTALLED: ['installed/mod3'],
170                     constants.MODULE_COMPATIBILITY_SUITES: ['suite_mod3']}
171        test_name = mod_one_name
172        build_targets = set([mod_one_name, mod_two_name, mod_three_name])
173        t_info = test_info.TestInfo(test_name, 'mock_runner', build_targets)
174        test_infos = set([t_info])
175
176        # The _print_test_info() will print the module_info of the test_info's
177        # test_name first. Then, print its related build targets. If the build
178        # target be printed before(e.g. build_target == test_info's test_name),
179        # it will skip it and print the next build_target.
180        # Since the build_targets of test_info are mod_one, mod_two, and
181        # mod_three, it will print mod_one first, then mod_two, and mod_three.
182        #
183        # _print_test_info() calls _print_module_info_from_module_name() to
184        # print the module_info. And _print_module_info_from_module_name()
185        # calls get_module_info() to get the module_info. So we can mock
186        # get_module_info() to achieve that.
187        mock_get_module_info.side_effect = [mod_one, mod_two, mod_three]
188
189        capture_output = StringIO()
190        sys.stdout = capture_output
191        mod_info = module_info.ModuleInfo()
192        atest._print_test_info(mod_info, test_infos)
193        # Assign sys.stdout back to default.
194        sys.stdout = sys.__stdout__
195        correct_output = ('\x1b[1;32mmod1\x1b[0m\n'
196                          '\x1b[1;36m\tCompatibility suite\x1b[0m\n'
197                          '\t\tsuite_mod1\n'
198                          '\x1b[1;36m\tSource code path\x1b[0m\n'
199                          '\t\tpath/mod1\n'
200                          '\x1b[1;36m\tInstalled path\x1b[0m\n'
201                          '\t\tinstalled/mod1\n'
202                          '\x1b[1;35m\tRelated build targets\x1b[0m\n'
203                          '\t\tmod1, mod2, mod3\n'
204                          '\x1b[1;32mmod2\x1b[0m\n'
205                          '\x1b[1;36m\tCompatibility suite\x1b[0m\n'
206                          '\t\tsuite_mod2\n'
207                          '\x1b[1;36m\tSource code path\x1b[0m\n'
208                          '\t\tpath/mod2\n'
209                          '\x1b[1;36m\tInstalled path\x1b[0m\n'
210                          '\t\tinstalled/mod2\n'
211                          '\x1b[1;32mmod3\x1b[0m\n'
212                          '\x1b[1;36m\tCompatibility suite\x1b[0m\n'
213                          '\t\tsuite_mod3\n'
214                          '\x1b[1;36m\tSource code path\x1b[0m\n'
215                          '\t\tpath/mod3\n'
216                          '\x1b[1;36m\tInstalled path\x1b[0m\n'
217                          '\t\tinstalled/mod3\n'
218                          '\x1b[1;37m\x1b[0m\n')
219        self.assertEqual(capture_output.getvalue(), correct_output)
220
221    @mock.patch.object(metrics_utils, 'send_exit_event')
222    def test_validate_exec_mode(self, _send_exit):
223        """Test _validate_exec_mode."""
224        args = []
225        parsed_args = atest._parse_args(args)
226        no_install_test_info = test_info.TestInfo(
227            'mod', '', set(), data={}, module_class=["JAVA_LIBRARIES"],
228            install_locations=set(['device']))
229        host_test_info = test_info.TestInfo(
230            'mod', '', set(), data={}, module_class=["NATIVE_TESTS"],
231            install_locations=set(['host']))
232        device_test_info = test_info.TestInfo(
233            'mod', '', set(), data={}, module_class=["NATIVE_TESTS"],
234            install_locations=set(['device']))
235        both_test_info = test_info.TestInfo(
236            'mod', '', set(), data={}, module_class=["NATIVE_TESTS"],
237            install_locations=set(['host', 'device']))
238
239        # $atest <Both-support>
240        test_infos = [host_test_info]
241        atest._validate_exec_mode(parsed_args, test_infos)
242        self.assertFalse(parsed_args.host)
243
244        # $atest <Both-support> with host_tests set to True
245        parsed_args = atest._parse_args([])
246        test_infos = [host_test_info]
247        atest._validate_exec_mode(parsed_args, test_infos, host_tests=True)
248        # Make sure the host option is not set.
249        self.assertFalse(parsed_args.host)
250
251        # $atest <Both-support> with host_tests set to False
252        parsed_args = atest._parse_args([])
253        test_infos = [host_test_info]
254        atest._validate_exec_mode(parsed_args, test_infos, host_tests=False)
255        self.assertFalse(parsed_args.host)
256
257        # $atest <device-only> with host_tests set to False
258        parsed_args = atest._parse_args([])
259        test_infos = [device_test_info]
260        atest._validate_exec_mode(parsed_args, test_infos, host_tests=False)
261        # Make sure the host option is not set.
262        self.assertFalse(parsed_args.host)
263
264        # $atest <device-only> with host_tests set to True
265        parsed_args = atest._parse_args([])
266        test_infos = [device_test_info]
267        self.assertRaises(SystemExit, atest._validate_exec_mode,
268                          parsed_args, test_infos, host_tests=True)
269
270        # $atest <Both-support>
271        parsed_args = atest._parse_args([])
272        test_infos = [both_test_info]
273        atest._validate_exec_mode(parsed_args, test_infos)
274        self.assertFalse(parsed_args.host)
275
276        # $atest <no_install_test_info>
277        parsed_args = atest._parse_args([])
278        test_infos = [no_install_test_info]
279        atest._validate_exec_mode(parsed_args, test_infos)
280        self.assertFalse(parsed_args.host)
281
282    def test_make_test_run_dir(self):
283        """Test make_test_run_dir."""
284        tmp_dir = tempfile.mkdtemp()
285        constants.ATEST_RESULT_ROOT = tmp_dir
286        date_time = None
287
288        work_dir = atest.make_test_run_dir()
289        folder_name = os.path.basename(work_dir)
290        date_time = datetime.datetime.strptime('_'.join(folder_name.split('_')[0:2]),
291                                               atest.TEST_RUN_DIR_PREFIX)
292        reload(constants)
293        self.assertTrue(date_time)
294
295
296if __name__ == '__main__':
297    unittest.main()
298