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"""Unittests for atest."""
18
19import datetime
20import os
21import sys
22import tempfile
23import unittest
24import mock
25
26import atest
27import constants
28import module_info
29
30from metrics import metrics_utils
31from test_finders import test_info
32
33if sys.version_info[0] == 2:
34    from StringIO import StringIO
35else:
36    from io import StringIO
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 mehod."""
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 != 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('__builtin__.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 mehod."""
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('__builtin__.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 mehod."""
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
293        reload(constants)
294        self.assertIsNotNone(date_time)
295
296
297if __name__ == '__main__':
298    unittest.main()
299