1#!/usr/bin/env python
2#
3# Copyright 2018, 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 module_info."""
18
19import os
20import unittest
21import mock
22
23import constants
24import module_info
25import unittest_constants as uc
26
27JSON_FILE_PATH = os.path.join(uc.TEST_DATA_DIR, uc.JSON_FILE)
28EXPECTED_MOD_TARGET = 'tradefed'
29EXPECTED_MOD_TARGET_PATH = ['tf/core']
30UNEXPECTED_MOD_TARGET = 'this_should_not_be_in_module-info.json'
31MOD_NO_PATH = 'module-no-path'
32PATH_TO_MULT_MODULES = 'shared/path/to/be/used'
33MULT_MOODULES_WITH_SHARED_PATH = ['module2', 'module1']
34PATH_TO_MULT_MODULES_WITH_MULTI_ARCH = 'shared/path/to/be/used2'
35TESTABLE_MODULES_WITH_SHARED_PATH = ['multiarch1', 'multiarch2', 'multiarch3', 'multiarch3_32']
36
37ROBO_MOD_PATH = ['/shared/robo/path']
38NON_RUN_ROBO_MOD_NAME = 'robo_mod'
39RUN_ROBO_MOD_NAME = 'run_robo_mod'
40NON_RUN_ROBO_MOD = {constants.MODULE_NAME: NON_RUN_ROBO_MOD_NAME,
41                    constants.MODULE_PATH: ROBO_MOD_PATH,
42                    constants.MODULE_CLASS: ['random_class']}
43RUN_ROBO_MOD = {constants.MODULE_NAME: RUN_ROBO_MOD_NAME,
44                constants.MODULE_PATH: ROBO_MOD_PATH,
45                constants.MODULE_CLASS: [constants.MODULE_CLASS_ROBOLECTRIC]}
46MOD_PATH_INFO_DICT = {ROBO_MOD_PATH[0]: [RUN_ROBO_MOD, NON_RUN_ROBO_MOD]}
47MOD_NAME_INFO_DICT = {
48    RUN_ROBO_MOD_NAME: RUN_ROBO_MOD,
49    NON_RUN_ROBO_MOD_NAME: NON_RUN_ROBO_MOD}
50MOD_NAME1 = 'mod1'
51MOD_NAME2 = 'mod2'
52MOD_NAME3 = 'mod3'
53MOD_NAME4 = 'mod4'
54MOD_INFO_DICT = {}
55MODULE_INFO = {constants.MODULE_NAME: 'random_name',
56               constants.MODULE_PATH: 'a/b/c/path',
57               constants.MODULE_CLASS: ['random_class']}
58NAME_TO_MODULE_INFO = {'random_name' : MODULE_INFO}
59
60#pylint: disable=protected-access
61class ModuleInfoUnittests(unittest.TestCase):
62    """Unit tests for module_info.py"""
63
64    @mock.patch('json.load', return_value={})
65    @mock.patch('__builtin__.open', new_callable=mock.mock_open)
66    @mock.patch('os.path.isfile', return_value=True)
67    def test_load_mode_info_file_out_dir_handling(self, _isfile, _open, _json):
68        """Test _load_module_info_file out dir handling."""
69        # Test out default out dir is used.
70        build_top = '/path/to/top'
71        default_out_dir = os.path.join(build_top, 'out/dir/here')
72        os_environ_mock = {'ANDROID_PRODUCT_OUT': default_out_dir,
73                           constants.ANDROID_BUILD_TOP: build_top}
74        default_out_dir_mod_targ = 'out/dir/here/module-info.json'
75        # Make sure module_info_target is what we think it is.
76        with mock.patch.dict('os.environ', os_environ_mock, clear=True):
77            mod_info = module_info.ModuleInfo()
78            self.assertEqual(default_out_dir_mod_targ,
79                             mod_info.module_info_target)
80
81        # Test out custom out dir is used (OUT_DIR=dir2).
82        custom_out_dir = os.path.join(build_top, 'out2/dir/here')
83        os_environ_mock = {'ANDROID_PRODUCT_OUT': custom_out_dir,
84                           constants.ANDROID_BUILD_TOP: build_top}
85        custom_out_dir_mod_targ = 'out2/dir/here/module-info.json'
86        # Make sure module_info_target is what we think it is.
87        with mock.patch.dict('os.environ', os_environ_mock, clear=True):
88            mod_info = module_info.ModuleInfo()
89            self.assertEqual(custom_out_dir_mod_targ,
90                             mod_info.module_info_target)
91
92        # Test out custom abs out dir is used (OUT_DIR=/tmp/out/dir2).
93        abs_custom_out_dir = '/tmp/out/dir'
94        os_environ_mock = {'ANDROID_PRODUCT_OUT': abs_custom_out_dir,
95                           constants.ANDROID_BUILD_TOP: build_top}
96        custom_abs_out_dir_mod_targ = '/tmp/out/dir/module-info.json'
97        # Make sure module_info_target is what we think it is.
98        with mock.patch.dict('os.environ', os_environ_mock, clear=True):
99            mod_info = module_info.ModuleInfo()
100            self.assertEqual(custom_abs_out_dir_mod_targ,
101                             mod_info.module_info_target)
102
103    @mock.patch.object(module_info.ModuleInfo, '_load_module_info_file',)
104    def test_get_path_to_module_info(self, mock_load_module):
105        """Test that we correctly create the path to module info dict."""
106        mod_one = 'mod1'
107        mod_two = 'mod2'
108        mod_path_one = '/path/to/mod1'
109        mod_path_two = '/path/to/mod2'
110        mod_info_dict = {mod_one: {constants.MODULE_PATH: [mod_path_one],
111                                   constants.MODULE_NAME: mod_one},
112                         mod_two: {constants.MODULE_PATH: [mod_path_two],
113                                   constants.MODULE_NAME: mod_two}}
114        mock_load_module.return_value = ('mod_target', mod_info_dict)
115        path_to_mod_info = {mod_path_one: [{constants.MODULE_NAME: mod_one,
116                                            constants.MODULE_PATH: [mod_path_one]}],
117                            mod_path_two: [{constants.MODULE_NAME: mod_two,
118                                            constants.MODULE_PATH: [mod_path_two]}]}
119        mod_info = module_info.ModuleInfo()
120        self.assertDictEqual(path_to_mod_info,
121                             mod_info._get_path_to_module_info(mod_info_dict))
122
123    def test_is_module(self):
124        """Test that we get the module when it's properly loaded."""
125        # Load up the test json file and check that module is in it
126        mod_info = module_info.ModuleInfo(module_file=JSON_FILE_PATH)
127        self.assertTrue(mod_info.is_module(EXPECTED_MOD_TARGET))
128        self.assertFalse(mod_info.is_module(UNEXPECTED_MOD_TARGET))
129
130    def test_get_path(self):
131        """Test that we get the module path when it's properly loaded."""
132        # Load up the test json file and check that module is in it
133        mod_info = module_info.ModuleInfo(module_file=JSON_FILE_PATH)
134        self.assertEqual(mod_info.get_paths(EXPECTED_MOD_TARGET),
135                         EXPECTED_MOD_TARGET_PATH)
136        self.assertEqual(mod_info.get_paths(MOD_NO_PATH), [])
137
138    def test_get_module_names(self):
139        """test that we get the module name properly."""
140        mod_info = module_info.ModuleInfo(module_file=JSON_FILE_PATH)
141        self.assertEqual(mod_info.get_module_names(EXPECTED_MOD_TARGET_PATH[0]),
142                         [EXPECTED_MOD_TARGET])
143        self.assertEqual(mod_info.get_module_names(PATH_TO_MULT_MODULES),
144                         MULT_MOODULES_WITH_SHARED_PATH)
145
146    def test_path_to_mod_info(self):
147        """test that we get the module name properly."""
148        mod_info = module_info.ModuleInfo(module_file=JSON_FILE_PATH)
149        module_list = []
150        for path_to_mod_info in mod_info.path_to_module_info[PATH_TO_MULT_MODULES_WITH_MULTI_ARCH]:
151            module_list.append(path_to_mod_info.get(constants.MODULE_NAME))
152        module_list.sort()
153        TESTABLE_MODULES_WITH_SHARED_PATH.sort()
154        self.assertEqual(module_list, TESTABLE_MODULES_WITH_SHARED_PATH)
155
156    def test_is_suite_in_compatibility_suites(self):
157        """Test is_suite_in_compatibility_suites."""
158        mod_info = module_info.ModuleInfo(module_file=JSON_FILE_PATH)
159        info = {'compatibility_suites': []}
160        self.assertFalse(mod_info.is_suite_in_compatibility_suites("cts", info))
161        info2 = {'compatibility_suites': ["cts"]}
162        self.assertTrue(mod_info.is_suite_in_compatibility_suites("cts", info2))
163        self.assertFalse(mod_info.is_suite_in_compatibility_suites("vts10", info2))
164        info3 = {'compatibility_suites': ["cts", "vts10"]}
165        self.assertTrue(mod_info.is_suite_in_compatibility_suites("cts", info3))
166        self.assertTrue(mod_info.is_suite_in_compatibility_suites("vts10", info3))
167        self.assertFalse(mod_info.is_suite_in_compatibility_suites("ats", info3))
168
169    @mock.patch.object(module_info.ModuleInfo, 'is_testable_module')
170    @mock.patch.object(module_info.ModuleInfo, 'is_suite_in_compatibility_suites')
171    def test_get_testable_modules(self, mock_is_suite_exist, mock_is_testable):
172        """Test get_testable_modules."""
173        mod_info = module_info.ModuleInfo(module_file=JSON_FILE_PATH)
174        mock_is_testable.return_value = False
175        self.assertEqual(mod_info.get_testable_modules(), set())
176        mod_info.name_to_module_info = NAME_TO_MODULE_INFO
177        mock_is_testable.return_value = True
178        mock_is_suite_exist.return_value = True
179        self.assertEqual(1, len(mod_info.get_testable_modules('test_suite')))
180        mock_is_suite_exist.return_value = False
181        self.assertEqual(0, len(mod_info.get_testable_modules('test_suite')))
182        self.assertEqual(1, len(mod_info.get_testable_modules()))
183
184    @mock.patch.object(module_info.ModuleInfo, 'has_test_config')
185    @mock.patch.object(module_info.ModuleInfo, 'is_robolectric_test')
186    def test_is_testable_module(self, mock_is_robo_test, mock_has_test_config):
187        """Test is_testable_module."""
188        mod_info = module_info.ModuleInfo(module_file=JSON_FILE_PATH)
189        mock_is_robo_test.return_value = False
190        mock_has_test_config.return_value = True
191        installed_module_info = {constants.MODULE_INSTALLED:
192                                 uc.DEFAULT_INSTALL_PATH}
193        non_installed_module_info = {constants.MODULE_NAME: 'rand_name'}
194        # Empty mod_info or a non-installed module.
195        self.assertFalse(mod_info.is_testable_module(non_installed_module_info))
196        self.assertFalse(mod_info.is_testable_module({}))
197        # Testable Module or is a robo module for non-installed module.
198        self.assertTrue(mod_info.is_testable_module(installed_module_info))
199        mock_has_test_config.return_value = False
200        self.assertFalse(mod_info.is_testable_module(installed_module_info))
201        mock_is_robo_test.return_value = True
202        self.assertTrue(mod_info.is_testable_module(non_installed_module_info))
203
204    @mock.patch.object(module_info.ModuleInfo, 'is_auto_gen_test_config')
205    def test_has_test_config(self, mock_is_auto_gen):
206        """Test has_test_config."""
207        mod_info = module_info.ModuleInfo(module_file=JSON_FILE_PATH)
208        info = {constants.MODULE_PATH:[uc.TEST_DATA_DIR]}
209        mock_is_auto_gen.return_value = True
210        # Validate we see the config when it's auto-generated.
211        self.assertTrue(mod_info.has_test_config(info))
212        self.assertTrue(mod_info.has_test_config({}))
213        # Validate when actual config exists and there's no auto-generated config.
214        mock_is_auto_gen.return_value = False
215        self.assertTrue(mod_info.has_test_config(info))
216        self.assertFalse(mod_info.has_test_config({}))
217        # Validate the case mod_info MODULE_TEST_CONFIG be set
218        info2 = {constants.MODULE_PATH:[uc.TEST_CONFIG_DATA_DIR],
219                 constants.MODULE_TEST_CONFIG:[os.path.join(uc.TEST_CONFIG_DATA_DIR, "a.xml")]}
220        self.assertTrue(mod_info.has_test_config(info2))
221
222    @mock.patch.object(module_info.ModuleInfo, 'get_module_names')
223    def test_get_robolectric_test_name(self, mock_get_module_names):
224        """Test get_robolectric_test_name."""
225        # Happy path testing, make sure we get the run robo target.
226        mod_info = module_info.ModuleInfo(module_file=JSON_FILE_PATH)
227        mod_info.name_to_module_info = MOD_NAME_INFO_DICT
228        mod_info.path_to_module_info = MOD_PATH_INFO_DICT
229        mock_get_module_names.return_value = [RUN_ROBO_MOD_NAME, NON_RUN_ROBO_MOD_NAME]
230        self.assertEqual(mod_info.get_robolectric_test_name(
231            NON_RUN_ROBO_MOD_NAME), RUN_ROBO_MOD_NAME)
232        # Let's also make sure we don't return anything when we're not supposed
233        # to.
234        mock_get_module_names.return_value = [NON_RUN_ROBO_MOD_NAME]
235        self.assertEqual(mod_info.get_robolectric_test_name(
236            NON_RUN_ROBO_MOD_NAME), None)
237
238    @mock.patch.object(module_info.ModuleInfo, 'get_module_info')
239    @mock.patch.object(module_info.ModuleInfo, 'get_module_names')
240    def test_is_robolectric_test(self, mock_get_module_names, mock_get_module_info):
241        """Test is_robolectric_test."""
242        # Happy path testing, make sure we get the run robo target.
243        mod_info = module_info.ModuleInfo(module_file=JSON_FILE_PATH)
244        mod_info.name_to_module_info = MOD_NAME_INFO_DICT
245        mod_info.path_to_module_info = MOD_PATH_INFO_DICT
246        mock_get_module_names.return_value = [RUN_ROBO_MOD_NAME, NON_RUN_ROBO_MOD_NAME]
247        mock_get_module_info.return_value = RUN_ROBO_MOD
248        # Test on a run robo module.
249        self.assertTrue(mod_info.is_robolectric_test(RUN_ROBO_MOD_NAME))
250        # Test on a non-run robo module but shares with a run robo module.
251        self.assertTrue(mod_info.is_robolectric_test(NON_RUN_ROBO_MOD_NAME))
252        # Make sure we don't find robo tests where they don't exist.
253        mock_get_module_info.return_value = None
254        self.assertFalse(mod_info.is_robolectric_test('rand_mod'))
255
256    @mock.patch.object(module_info.ModuleInfo, 'is_module')
257    def test_is_auto_gen_test_config(self, mock_is_module):
258        """Test is_auto_gen_test_config correctly detects the module."""
259        mod_info = module_info.ModuleInfo(module_file=JSON_FILE_PATH)
260        mock_is_module.return_value = True
261        is_auto_test_config = {'auto_test_config': [True]}
262        is_not_auto_test_config = {'auto_test_config': [False]}
263        is_not_auto_test_config_again = {'auto_test_config': []}
264        MOD_INFO_DICT[MOD_NAME1] = is_auto_test_config
265        MOD_INFO_DICT[MOD_NAME2] = is_not_auto_test_config
266        MOD_INFO_DICT[MOD_NAME3] = is_not_auto_test_config_again
267        MOD_INFO_DICT[MOD_NAME4] = {}
268        mod_info.name_to_module_info = MOD_INFO_DICT
269        self.assertTrue(mod_info.is_auto_gen_test_config(MOD_NAME1))
270        self.assertFalse(mod_info.is_auto_gen_test_config(MOD_NAME2))
271        self.assertFalse(mod_info.is_auto_gen_test_config(MOD_NAME3))
272        self.assertFalse(mod_info.is_auto_gen_test_config(MOD_NAME4))
273
274    def test_is_robolectric_module(self):
275        """Test is_robolectric_module correctly detects the module."""
276        mod_info = module_info.ModuleInfo(module_file=JSON_FILE_PATH)
277        is_robolectric_module = {'class': ['ROBOLECTRIC']}
278        is_not_robolectric_module = {'class': ['OTHERS']}
279        MOD_INFO_DICT[MOD_NAME1] = is_robolectric_module
280        MOD_INFO_DICT[MOD_NAME2] = is_not_robolectric_module
281        mod_info.name_to_module_info = MOD_INFO_DICT
282        self.assertTrue(mod_info.is_robolectric_module(MOD_INFO_DICT[MOD_NAME1]))
283        self.assertFalse(mod_info.is_robolectric_module(MOD_INFO_DICT[MOD_NAME2]))
284
285
286if __name__ == '__main__':
287    unittest.main()
288