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 cli_translator."""
18
19# pylint: disable=line-too-long
20
21import unittest
22import json
23import os
24import re
25import sys
26
27from importlib import reload
28from io import StringIO
29from unittest import mock
30
31import cli_translator as cli_t
32import constants
33import test_finder_handler
34import test_mapping
35import unittest_constants as uc
36import unittest_utils
37
38from metrics import metrics
39from test_finders import module_finder
40from test_finders import test_finder_base
41
42
43# TEST_MAPPING related consts
44TEST_MAPPING_TOP_DIR = os.path.join(uc.TEST_DATA_DIR, 'test_mapping')
45TEST_MAPPING_DIR = os.path.join(TEST_MAPPING_TOP_DIR, 'folder1')
46TEST_1 = test_mapping.TestDetail({'name': 'test1', 'host': True})
47TEST_2 = test_mapping.TestDetail({'name': 'test2'})
48TEST_3 = test_mapping.TestDetail({'name': 'test3'})
49TEST_4 = test_mapping.TestDetail({'name': 'test4'})
50TEST_5 = test_mapping.TestDetail({'name': 'test5'})
51TEST_6 = test_mapping.TestDetail({'name': 'test6'})
52TEST_7 = test_mapping.TestDetail({'name': 'test7'})
53TEST_8 = test_mapping.TestDetail({'name': 'test8'})
54TEST_9 = test_mapping.TestDetail({'name': 'test9'})
55TEST_10 = test_mapping.TestDetail({'name': 'test10'})
56
57SEARCH_DIR_RE = re.compile(r'^find ([^ ]*).*$')
58
59
60#pylint: disable=unused-argument
61def gettestinfos_side_effect(test_names, test_mapping_test_details=None):
62    """Mock return values for _get_test_info."""
63    test_infos = set()
64    for test_name in test_names:
65        if test_name == uc.MODULE_NAME:
66            test_infos.add(uc.MODULE_INFO)
67        if test_name == uc.CLASS_NAME:
68            test_infos.add(uc.CLASS_INFO)
69    return test_infos
70
71
72#pylint: disable=protected-access
73#pylint: disable=no-self-use
74class CLITranslatorUnittests(unittest.TestCase):
75    """Unit tests for cli_t.py"""
76
77    def setUp(self):
78        """Run before execution of every test"""
79        self.ctr = cli_t.CLITranslator()
80
81        # Create a mock of args.
82        self.args = mock.Mock
83        self.args.tests = []
84        # Test mapping related args
85        self.args.test_mapping = False
86        self.args.include_subdirs = False
87        self.args.enable_file_patterns = False
88        # Cache finder related args
89        self.args.clear_cache = False
90        self.ctr.mod_info = mock.Mock
91        self.ctr.mod_info.name_to_module_info = {}
92
93    def tearDown(self):
94        """Run after execution of every test"""
95        reload(uc)
96
97    @mock.patch('builtins.input', return_value='n')
98    @mock.patch.object(module_finder.ModuleFinder, 'find_test_by_module_name')
99    @mock.patch.object(module_finder.ModuleFinder, 'get_fuzzy_searching_results')
100    @mock.patch.object(metrics, 'FindTestFinishEvent')
101    @mock.patch.object(test_finder_handler, 'get_find_methods_for_test')
102    # pylint: disable=too-many-locals
103    def test_get_test_infos(self, mock_getfindmethods, _metrics, mock_getfuzzyresults,
104                            mock_findtestbymodule, mock_input):
105        """Test _get_test_infos method."""
106        ctr = cli_t.CLITranslator()
107        find_method_return_module_info = lambda x, y: uc.MODULE_INFOS
108        # pylint: disable=invalid-name
109        find_method_return_module_class_info = (lambda x, test: uc.MODULE_INFOS
110                                                if test == uc.MODULE_NAME
111                                                else uc.CLASS_INFOS)
112        find_method_return_nothing = lambda x, y: None
113        one_test = [uc.MODULE_NAME]
114        mult_test = [uc.MODULE_NAME, uc.CLASS_NAME]
115
116        # Let's make sure we return what we expect.
117        expected_test_infos = {uc.MODULE_INFO}
118        mock_getfindmethods.return_value = [
119            test_finder_base.Finder(None, find_method_return_module_info, None)]
120        unittest_utils.assert_strict_equal(
121            self, ctr._get_test_infos(one_test), expected_test_infos)
122
123        # Check we receive multiple test infos.
124        expected_test_infos = {uc.MODULE_INFO, uc.CLASS_INFO}
125        mock_getfindmethods.return_value = [
126            test_finder_base.Finder(None, find_method_return_module_class_info,
127                                    None)]
128        unittest_utils.assert_strict_equal(
129            self, ctr._get_test_infos(mult_test), expected_test_infos)
130
131        # Check return null set when we have no tests found or multiple results.
132        mock_getfindmethods.return_value = [
133            test_finder_base.Finder(None, find_method_return_nothing, None)]
134        null_test_info = set()
135        mock_getfuzzyresults.return_value = []
136        self.assertEqual(null_test_info, ctr._get_test_infos(one_test))
137        self.assertEqual(null_test_info, ctr._get_test_infos(mult_test))
138
139        # Check returning test_info when the user says Yes.
140        mock_input.return_value = "Y"
141        mock_getfindmethods.return_value = [
142            test_finder_base.Finder(None, find_method_return_module_info, None)]
143        mock_getfuzzyresults.return_value = one_test
144        mock_findtestbymodule.return_value = uc.MODULE_INFO
145        unittest_utils.assert_strict_equal(
146            self, ctr._get_test_infos([uc.TYPO_MODULE_NAME]), {uc.MODULE_INFO})
147
148        # Check the method works for test mapping.
149        test_detail1 = test_mapping.TestDetail(uc.TEST_MAPPING_TEST)
150        test_detail2 = test_mapping.TestDetail(uc.TEST_MAPPING_TEST_WITH_OPTION)
151        expected_test_infos = {uc.MODULE_INFO, uc.CLASS_INFO}
152        mock_getfindmethods.return_value = [
153            test_finder_base.Finder(None, find_method_return_module_class_info,
154                                    None)]
155        test_infos = ctr._get_test_infos(
156            mult_test, [test_detail1, test_detail2])
157        unittest_utils.assert_strict_equal(
158            self, test_infos, expected_test_infos)
159        for test_info in test_infos:
160            if test_info == uc.MODULE_INFO:
161                self.assertEqual(
162                    test_detail1.options,
163                    test_info.data[constants.TI_MODULE_ARG])
164            else:
165                self.assertEqual(
166                    test_detail2.options,
167                    test_info.data[constants.TI_MODULE_ARG])
168
169    @mock.patch.object(metrics, 'FindTestFinishEvent')
170    @mock.patch.object(test_finder_handler, 'get_find_methods_for_test')
171    def test_get_test_infos_2(self, mock_getfindmethods, _metrics):
172        """Test _get_test_infos method."""
173        ctr = cli_t.CLITranslator()
174        find_method_return_module_info2 = lambda x, y: uc.MODULE_INFOS2
175        find_method_ret_mod_cls_info2 = (
176            lambda x, test: uc.MODULE_INFOS2
177            if test == uc.MODULE_NAME else uc.CLASS_INFOS2)
178        one_test = [uc.MODULE_NAME]
179        mult_test = [uc.MODULE_NAME, uc.CLASS_NAME]
180        # Let's make sure we return what we expect.
181        expected_test_infos = {uc.MODULE_INFO, uc.MODULE_INFO2}
182        mock_getfindmethods.return_value = [
183            test_finder_base.Finder(None, find_method_return_module_info2,
184                                    None)]
185        unittest_utils.assert_strict_equal(
186            self, ctr._get_test_infos(one_test), expected_test_infos)
187        # Check we receive multiple test infos.
188        expected_test_infos = {uc.MODULE_INFO, uc.CLASS_INFO, uc.MODULE_INFO2,
189                               uc.CLASS_INFO2}
190        mock_getfindmethods.return_value = [
191            test_finder_base.Finder(None, find_method_ret_mod_cls_info2,
192                                    None)]
193        unittest_utils.assert_strict_equal(
194            self, ctr._get_test_infos(mult_test), expected_test_infos)
195        # Check the method works for test mapping.
196        test_detail1 = test_mapping.TestDetail(uc.TEST_MAPPING_TEST)
197        test_detail2 = test_mapping.TestDetail(uc.TEST_MAPPING_TEST_WITH_OPTION)
198        expected_test_infos = {uc.MODULE_INFO, uc.CLASS_INFO, uc.MODULE_INFO2,
199                               uc.CLASS_INFO2}
200        mock_getfindmethods.return_value = [
201            test_finder_base.Finder(None, find_method_ret_mod_cls_info2,
202                                    None)]
203        test_infos = ctr._get_test_infos(
204            mult_test, [test_detail1, test_detail2])
205        unittest_utils.assert_strict_equal(
206            self, test_infos, expected_test_infos)
207        for test_info in test_infos:
208            if test_info in [uc.MODULE_INFO, uc.MODULE_INFO2]:
209                self.assertEqual(
210                    test_detail1.options,
211                    test_info.data[constants.TI_MODULE_ARG])
212            elif test_info in [uc.CLASS_INFO, uc.CLASS_INFO2]:
213                self.assertEqual(
214                    test_detail2.options,
215                    test_info.data[constants.TI_MODULE_ARG])
216
217    @mock.patch.object(cli_t.CLITranslator, '_get_test_infos',
218                       side_effect=gettestinfos_side_effect)
219    def test_translate_class(self, _info):
220        """Test translate method for tests by class name."""
221        # Check that we can find a class.
222        self.args.tests = [uc.CLASS_NAME]
223        targets, test_infos = self.ctr.translate(self.args)
224        unittest_utils.assert_strict_equal(
225            self, targets, uc.CLASS_BUILD_TARGETS)
226        unittest_utils.assert_strict_equal(self, test_infos, {uc.CLASS_INFO})
227
228    @mock.patch.object(cli_t.CLITranslator, '_get_test_infos',
229                       side_effect=gettestinfos_side_effect)
230    def test_translate_module(self, _info):
231        """Test translate method for tests by module or class name."""
232        # Check that we get all the build targets we expect.
233        self.args.tests = [uc.MODULE_NAME, uc.CLASS_NAME]
234        targets, test_infos = self.ctr.translate(self.args)
235        unittest_utils.assert_strict_equal(
236            self, targets, uc.MODULE_CLASS_COMBINED_BUILD_TARGETS)
237        unittest_utils.assert_strict_equal(self, test_infos, {uc.MODULE_INFO,
238                                                              uc.CLASS_INFO})
239
240    @mock.patch.object(cli_t.CLITranslator, '_find_tests_by_test_mapping')
241    @mock.patch.object(cli_t.CLITranslator, '_get_test_infos',
242                       side_effect=gettestinfos_side_effect)
243    def test_translate_test_mapping(self, _info, mock_testmapping):
244        """Test translate method for tests in test mapping."""
245        # Check that test mappings feeds into get_test_info properly.
246        test_detail1 = test_mapping.TestDetail(uc.TEST_MAPPING_TEST)
247        test_detail2 = test_mapping.TestDetail(uc.TEST_MAPPING_TEST_WITH_OPTION)
248        mock_testmapping.return_value = ([test_detail1, test_detail2], None)
249        self.args.tests = []
250        targets, test_infos = self.ctr.translate(self.args)
251        unittest_utils.assert_strict_equal(
252            self, targets, uc.MODULE_CLASS_COMBINED_BUILD_TARGETS)
253        unittest_utils.assert_strict_equal(self, test_infos, {uc.MODULE_INFO,
254                                                              uc.CLASS_INFO})
255
256    @mock.patch.object(cli_t.CLITranslator, '_find_tests_by_test_mapping')
257    @mock.patch.object(cli_t.CLITranslator, '_get_test_infos',
258                       side_effect=gettestinfos_side_effect)
259    def test_translate_test_mapping_all(self, _info, mock_testmapping):
260        """Test translate method for tests in test mapping."""
261        # Check that test mappings feeds into get_test_info properly.
262        test_detail1 = test_mapping.TestDetail(uc.TEST_MAPPING_TEST)
263        test_detail2 = test_mapping.TestDetail(uc.TEST_MAPPING_TEST_WITH_OPTION)
264        mock_testmapping.return_value = ([test_detail1, test_detail2], None)
265        self.args.tests = ['src_path:all']
266        self.args.test_mapping = True
267        targets, test_infos = self.ctr.translate(self.args)
268        unittest_utils.assert_strict_equal(
269            self, targets, uc.MODULE_CLASS_COMBINED_BUILD_TARGETS)
270        unittest_utils.assert_strict_equal(self, test_infos, {uc.MODULE_INFO,
271                                                              uc.CLASS_INFO})
272
273    def test_find_tests_by_test_mapping_presubmit(self):
274        """Test _find_tests_by_test_mapping method to locate presubmit tests."""
275        os_environ_mock = {constants.ANDROID_BUILD_TOP: uc.TEST_DATA_DIR}
276        with mock.patch.dict('os.environ', os_environ_mock, clear=True):
277            tests, all_tests = self.ctr._find_tests_by_test_mapping(
278                path=TEST_MAPPING_DIR, file_name='test_mapping_sample',
279                checked_files=set())
280        expected = set([TEST_1, TEST_2, TEST_5, TEST_7, TEST_9])
281        expected_all_tests = {'presubmit': expected,
282                              'postsubmit': set(
283                                  [TEST_3, TEST_6, TEST_8, TEST_10]),
284                              'other_group': set([TEST_4])}
285        self.assertEqual(expected, tests)
286        self.assertEqual(expected_all_tests, all_tests)
287
288    def test_find_tests_by_test_mapping_postsubmit(self):
289        """Test _find_tests_by_test_mapping method to locate postsubmit tests.
290        """
291        os_environ_mock = {constants.ANDROID_BUILD_TOP: uc.TEST_DATA_DIR}
292        with mock.patch.dict('os.environ', os_environ_mock, clear=True):
293            tests, all_tests = self.ctr._find_tests_by_test_mapping(
294                path=TEST_MAPPING_DIR,
295                test_group=constants.TEST_GROUP_POSTSUBMIT,
296                file_name='test_mapping_sample', checked_files=set())
297        expected_presubmit = set([TEST_1, TEST_2, TEST_5, TEST_7, TEST_9])
298        expected = set([TEST_3, TEST_6, TEST_8, TEST_10])
299        expected_all_tests = {'presubmit': expected_presubmit,
300                              'postsubmit': set(
301                                  [TEST_3, TEST_6, TEST_8, TEST_10]),
302                              'other_group': set([TEST_4])}
303        self.assertEqual(expected, tests)
304        self.assertEqual(expected_all_tests, all_tests)
305
306    def test_find_tests_by_test_mapping_all_group(self):
307        """Test _find_tests_by_test_mapping method to locate postsubmit tests.
308        """
309        os_environ_mock = {constants.ANDROID_BUILD_TOP: uc.TEST_DATA_DIR}
310        with mock.patch.dict('os.environ', os_environ_mock, clear=True):
311            tests, all_tests = self.ctr._find_tests_by_test_mapping(
312                path=TEST_MAPPING_DIR, test_group=constants.TEST_GROUP_ALL,
313                file_name='test_mapping_sample', checked_files=set())
314        expected_presubmit = set([TEST_1, TEST_2, TEST_5, TEST_7, TEST_9])
315        expected = set([
316            TEST_1, TEST_2, TEST_3, TEST_4, TEST_5, TEST_6, TEST_7, TEST_8,
317            TEST_9, TEST_10])
318        expected_all_tests = {'presubmit': expected_presubmit,
319                              'postsubmit': set(
320                                  [TEST_3, TEST_6, TEST_8, TEST_10]),
321                              'other_group': set([TEST_4])}
322        self.assertEqual(expected, tests)
323        self.assertEqual(expected_all_tests, all_tests)
324
325    def test_find_tests_by_test_mapping_include_subdir(self):
326        """Test _find_tests_by_test_mapping method to include sub directory."""
327        os_environ_mock = {constants.ANDROID_BUILD_TOP: uc.TEST_DATA_DIR}
328        with mock.patch.dict('os.environ', os_environ_mock, clear=True):
329            tests, all_tests = self.ctr._find_tests_by_test_mapping(
330                path=TEST_MAPPING_TOP_DIR, file_name='test_mapping_sample',
331                include_subdirs=True, checked_files=set())
332        expected = set([TEST_1, TEST_2, TEST_5, TEST_7, TEST_9])
333        expected_all_tests = {'presubmit': expected,
334                              'postsubmit': set([
335                                  TEST_3, TEST_6, TEST_8, TEST_10]),
336                              'other_group': set([TEST_4])}
337        self.assertEqual(expected, tests)
338        self.assertEqual(expected_all_tests, all_tests)
339
340    @mock.patch('builtins.input', return_value='')
341    def test_confirm_running(self, mock_input):
342        """Test _confirm_running method."""
343        self.assertTrue(self.ctr._confirm_running([TEST_1]))
344        mock_input.return_value = 'N'
345        self.assertFalse(self.ctr._confirm_running([TEST_2]))
346
347    def test_print_fuzzy_searching_results(self):
348        """Test _print_fuzzy_searching_results"""
349        modules = [uc.MODULE_NAME, uc.MODULE2_NAME]
350        capture_output = StringIO()
351        sys.stdout = capture_output
352        self.ctr._print_fuzzy_searching_results(modules)
353        sys.stdout = sys.__stdout__
354        output = 'Did you mean the following modules?\n{0}\n{1}\n'.format(
355            uc.MODULE_NAME, uc.MODULE2_NAME)
356        self.assertEqual(capture_output.getvalue(), output)
357
358    def test_filter_comments(self):
359        """Test filter_comments method"""
360        file_with_comments = os.path.join(TEST_MAPPING_TOP_DIR,
361                                          'folder6',
362                                          'test_mapping_sample_with_comments')
363        file_with_comments_golden = os.path.join(TEST_MAPPING_TOP_DIR,
364                                                 'folder6',
365                                                 'test_mapping_sample_golden')
366        test_mapping_dict = json.loads(
367            self.ctr.filter_comments(file_with_comments))
368        test_mapping_dict_gloden = None
369        with open(file_with_comments_golden) as json_file:
370            test_mapping_dict_gloden = json.load(json_file)
371
372        self.assertEqual(test_mapping_dict, test_mapping_dict_gloden)
373
374
375if __name__ == '__main__':
376    unittest.main()
377