1#!/usr/bin/env python3
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 project_info."""
18
19import logging
20import os
21import shutil
22import tempfile
23import unittest
24from unittest import mock
25
26from aidegen import constant
27from aidegen import unittest_constants
28from aidegen.lib import common_util
29from aidegen.lib import project_info
30from aidegen.lib import project_config
31from aidegen.lib import source_locator
32
33_MODULE_INFO = {
34    'm1': {
35        'class': ['JAVA_LIBRARIES'],
36        'dependencies': ['m2', 'm6'],
37        'path': ['m1']
38    },
39    'm2': {
40        'class': ['JAVA_LIBRARIES'],
41        'dependencies': ['m3', 'm4']
42    },
43    'm3': {
44        'class': ['JAVA_LIBRARIES'],
45        'dependencies': []
46    },
47    'm4': {
48        'class': ['JAVA_LIBRARIES'],
49        'dependencies': ['m6']
50    },
51    'm5': {
52        'class': ['JAVA_LIBRARIES'],
53        'dependencies': []
54    },
55    'm6': {
56        'class': ['JAVA_LIBRARIES'],
57        'dependencies': ['m2']
58    },
59}
60_EXPECT_DEPENDENT_MODULES = {
61    'm1': {
62        'class': ['JAVA_LIBRARIES'],
63        'dependencies': ['m2', 'm6'],
64        'path': ['m1'],
65        'depth': 0
66    },
67    'm2': {
68        'class': ['JAVA_LIBRARIES'],
69        'dependencies': ['m3', 'm4'],
70        'depth': 1
71    },
72    'm3': {
73        'class': ['JAVA_LIBRARIES'],
74        'dependencies': [],
75        'depth': 2
76    },
77    'm4': {
78        'class': ['JAVA_LIBRARIES'],
79        'dependencies': ['m6'],
80        'depth': 2
81    },
82    'm6': {
83        'class': ['JAVA_LIBRARIES'],
84        'dependencies': ['m2'],
85        'depth': 1
86    },
87}
88
89
90# pylint: disable=protected-access
91# pylint: disable=invalid-name
92class ProjectInfoUnittests(unittest.TestCase):
93    """Unit tests for project_info.py"""
94
95    def setUp(self):
96        """Initialize arguments for ProjectInfo."""
97        self.args = mock.MagicMock()
98        self.args.module_name = 'm1'
99        self.args.project_path = ''
100        self.args.ide = ['j']
101        self.args.no_launch = True
102        self.args.depth = 0
103        self.args.android_tree = False
104        self.args.skip_build = True
105        self.args.targets = ['m1']
106        self.args.verbose = False
107        self.args.ide_installed_path = None
108        self.args.config_reset = False
109        self.args.language = ['j']
110
111    @mock.patch('atest.module_info.ModuleInfo')
112    def test_get_dep_modules(self, mock_module_info):
113        """Test get_dep_modules recursively find dependent modules."""
114        mock_module_info.name_to_module_info = _MODULE_INFO
115        mock_module_info.is_module.return_value = True
116        mock_module_info.get_paths.return_value = ['m1']
117        mock_module_info.get_module_names.return_value = ['m1']
118        project_info.ProjectInfo.modules_info = mock_module_info
119        proj_info = project_info.ProjectInfo(self.args.module_name, False)
120        self.assertEqual(proj_info.dep_modules, _EXPECT_DEPENDENT_MODULES)
121
122    @mock.patch.object(project_info.ProjectInfo,
123                       '_get_modules_under_project_path')
124    @mock.patch.object(project_info.ProjectInfo, 'get_dep_modules')
125    def test_init(self, mock_get_deps, mock_get_sub_modules):
126        """Test init."""
127        project_info.ProjectInfo(constant.FRAMEWORK_ALL, False)
128        self.assertTrue(mock_get_deps.called)
129        self.assertFalse(mock_get_sub_modules.called)
130
131    @mock.patch.object(common_util, 'get_android_root_dir')
132    def test_get_target_name(self, mock_get_root):
133        """Test get_target_name with different conditions."""
134        mock_get_root.return_value = unittest_constants.TEST_DATA_PATH
135        self.assertEqual(
136            project_info.ProjectInfo.get_target_name(
137                unittest_constants.TEST_MODULE,
138                unittest_constants.TEST_DATA_PATH),
139            os.path.basename(unittest_constants.TEST_DATA_PATH))
140        self.assertEqual(
141            project_info.ProjectInfo.get_target_name(
142                unittest_constants.TEST_MODULE, unittest_constants.TEST_PATH),
143            unittest_constants.TEST_MODULE)
144
145    # pylint: disable=too-many-locals
146    @mock.patch('logging.info')
147    @mock.patch.object(common_util, 'get_android_root_dir')
148    @mock.patch('atest.module_info.ModuleInfo')
149    @mock.patch('atest.atest_utils.build')
150    def test_locate_source(self, mock_atest_utils_build, mock_module_info,
151                           mock_get_root, mock_info):
152        """Test locate_source handling."""
153        mock_atest_utils_build.build.return_value = True
154        test_root_path = os.path.join(tempfile.mkdtemp(), 'test')
155        shutil.copytree(unittest_constants.TEST_DATA_PATH, test_root_path)
156        mock_get_root.return_value = test_root_path
157        generated_jar = ('out/soong/.intermediates/packages/apps/test/test/'
158                         'android_common/generated.jar')
159        locate_module_info = dict(unittest_constants.MODULE_INFO)
160        locate_module_info['installed'] = [generated_jar]
161        mock_module_info.is_module.return_value = True
162        mock_module_info.get_paths.return_value = [
163            unittest_constants.MODULE_PATH
164        ]
165        mock_module_info.get_module_names.return_value = [
166            unittest_constants.TEST_MODULE
167        ]
168        project_config.ProjectConfig(self.args)
169        project_info_obj = project_info.ProjectInfo(
170            mock_module_info.get_paths()[0])
171        project_info_obj.dep_modules = {
172            unittest_constants.TEST_MODULE: locate_module_info
173        }
174        project_info_obj._init_source_path()
175        # Show warning when the jar not exists after build the module.
176        result_jar = set()
177        project_info_obj.locate_source()
178        self.assertEqual(project_info_obj.source_path['jar_path'], result_jar)
179        self.assertTrue(mock_info.called)
180
181        # Test collects source and test folders.
182        result_source = set(['packages/apps/test/src/main/java'])
183        result_test = set(['packages/apps/test/tests'])
184        self.assertEqual(project_info_obj.source_path['source_folder_path'],
185                         result_source)
186        self.assertEqual(project_info_obj.source_path['test_folder_path'],
187                         result_test)
188
189    @mock.patch.object(project_info, 'batch_build_dependencies')
190    @mock.patch.object(common_util, 'get_android_root_dir')
191    @mock.patch('atest.module_info.ModuleInfo')
192    @mock.patch('atest.atest_utils.build')
193    def test_locate_source_with_skip_build(self, mock_atest_utils_build,
194                                           mock_module_info, mock_get_root,
195                                           mock_batch):
196        """Test locate_source handling."""
197        mock_atest_utils_build.build.return_value = True
198        test_root_path = os.path.join(tempfile.mkdtemp(), 'test')
199        shutil.copytree(unittest_constants.TEST_DATA_PATH, test_root_path)
200        mock_get_root.return_value = test_root_path
201        generated_jar = ('out/soong/.intermediates/packages/apps/test/test/'
202                         'android_common/generated.jar')
203        locate_module_info = dict(unittest_constants.MODULE_INFO)
204        locate_module_info['installed'] = [generated_jar]
205        mock_module_info.is_module.return_value = True
206        mock_module_info.get_paths.return_value = [
207            unittest_constants.MODULE_PATH
208        ]
209        mock_module_info.get_module_names.return_value = [
210            unittest_constants.TEST_MODULE
211        ]
212        args = mock.MagicMock()
213        args.module_name = 'm1'
214        args.project_path = ''
215        args.ide = ['j']
216        args.no_launch = True
217        args.depth = 0
218        args.android_tree = False
219        args.skip_build = True
220        args.targets = ['m1']
221        args.verbose = False
222        args.ide_installed_path = None
223        args.config_reset = False
224        args.language = ['j']
225        project_config.ProjectConfig(args)
226        project_info_obj = project_info.ProjectInfo(
227            mock_module_info.get_paths()[0])
228        project_info_obj.dep_modules = {
229            unittest_constants.TEST_MODULE: locate_module_info
230        }
231        project_info_obj._init_source_path()
232        project_info_obj.locate_source()
233        self.assertFalse(mock_batch.called)
234
235        args.ide = ['v']
236        args.skip_build = False
237        project_config.ProjectConfig(args)
238        project_info_obj = project_info.ProjectInfo(
239            mock_module_info.get_paths()[0])
240        project_info_obj.dep_modules = {
241            unittest_constants.TEST_MODULE: locate_module_info
242        }
243        project_info_obj._init_source_path()
244        project_info_obj.locate_source()
245        self.assertFalse(mock_batch.called)
246
247    def test_separate_build_target(self):
248        """Test separate_build_target."""
249        test_list = ['1', '22', '333', '4444', '55555', '1', '7777777']
250        targets = []
251        sample = [['1', '22', '333'], ['4444'], ['55555', '1'], ['7777777']]
252        for start, end in iter(
253                project_info._separate_build_targets(test_list, 9)):
254            targets.append(test_list[start:end])
255        self.assertEqual(targets, sample)
256
257    def test_separate_build_target_with_length_short(self):
258        """Test separate_build_target with length short."""
259        test_list = ['1']
260        sample = [['1']]
261        targets = []
262        for start, end in iter(
263                project_info._separate_build_targets(test_list, 9)):
264            targets.append(test_list[start:end])
265        self.assertEqual(targets, sample)
266
267    @mock.patch.object(project_info.ProjectInfo, 'locate_source')
268    @mock.patch('atest.module_info.ModuleInfo')
269    def test_rebuild_jar_once(self, mock_module_info, mock_locate_source):
270        """Test rebuild the jar/srcjar only one time."""
271        mock_module_info.get_paths.return_value = ['m1']
272        project_info.ProjectInfo.modules_info = mock_module_info
273        proj_info = project_info.ProjectInfo(self.args.module_name, False)
274        proj_info.locate_source(build=False)
275        self.assertEqual(mock_locate_source.call_count, 1)
276        proj_info.locate_source(build=True)
277        self.assertEqual(mock_locate_source.call_count, 2)
278
279    @mock.patch('builtins.print')
280    @mock.patch('builtins.format')
281    @mock.patch('atest.atest_utils.build')
282    def test_build_target(self, mock_build, mock_format, mock_print):
283        """Test _build_target."""
284        build_argument = ['-k', 'j']
285        test_targets = ['mod_1', 'mod_2']
286        build_argument.extend(test_targets)
287        mock_build.return_value = False
288        project_info._build_target(test_targets)
289        self.assertTrue(mock_build.called_with((build_argument, True)))
290        self.assertTrue(mock_format.called_with('\n'.join(test_targets)))
291        self.assertTrue(mock_print.called)
292        mock_print.reset_mock()
293        mock_format.reset_mock()
294        mock_build.reset_mock()
295
296        mock_build.return_value = True
297        project_info._build_target(test_targets)
298        self.assertTrue(mock_build.called_with((build_argument, True)))
299        self.assertFalse(mock_format.called)
300        self.assertFalse(mock_print.called)
301        mock_print.reset_mock()
302        mock_format.reset_mock()
303        mock_build.reset_mock()
304
305    @mock.patch('builtins.print')
306    @mock.patch.object(project_info.ProjectInfo, '_search_android_make_files')
307    @mock.patch('atest.module_info.ModuleInfo')
308    def test_display_convert_make_files_message(
309            self, mock_module_info, mock_search, mock_print):
310        """Test _display_convert_make_files_message with conditions."""
311        mock_search.return_value = []
312        mock_module_info.get_paths.return_value = ['m1']
313        project_info.ProjectInfo.modules_info = mock_module_info
314        proj_info = project_info.ProjectInfo(self.args.module_name)
315        proj_info._display_convert_make_files_message()
316        self.assertFalse(mock_print.called)
317
318        mock_print.mock_reset()
319        mock_search.return_value = ['a/b/path/to/target.mk']
320        proj_info = project_info.ProjectInfo(self.args.module_name)
321        proj_info._display_convert_make_files_message()
322        self.assertTrue(mock_print.called)
323
324    @mock.patch.object(project_info, '_build_target')
325    @mock.patch.object(project_info, '_separate_build_targets')
326    @mock.patch.object(logging, 'info')
327    def test_batch_build_dependencies(self, mock_log, mock_sep, mock_build):
328        """Test batch_build_dependencies."""
329        mock_sep.return_value = [(0, 1)]
330        project_info.batch_build_dependencies({'m1', 'm2'})
331        self.assertTrue(mock_log.called)
332        self.assertTrue(mock_sep.called)
333        self.assertEqual(mock_build.call_count, 1)
334
335
336class MultiProjectsInfoUnittests(unittest.TestCase):
337    """Unit tests for MultiProjectsInfo class."""
338
339    @mock.patch.object(project_info.ProjectInfo, '__init__')
340    @mock.patch.object(project_info.ProjectInfo, 'get_dep_modules')
341    @mock.patch.object(project_info.ProjectInfo,
342                       '_get_robolectric_dep_module')
343    @mock.patch.object(project_info.ProjectInfo,
344                       '_get_modules_under_project_path')
345    @mock.patch.object(common_util, 'get_related_paths')
346    def test_collect_all_dep_modules(self, mock_relpath, mock_sub_modules_path,
347                                     mock_robo_module, mock_get_dep_modules,
348                                     mock_init):
349        """Test _collect_all_dep_modules."""
350        mock_init.return_value = None
351        mock_relpath.return_value = ('path/to/sub/module', '')
352        mock_sub_modules_path.return_value = 'sub_module'
353        mock_robo_module.return_value = 'robo_module'
354        expected = set(project_info._CORE_MODULES)
355        expected.update({'sub_module', 'robo_module'})
356        proj = project_info.MultiProjectsInfo(['a'])
357        proj.project_module_names = set('framework-all')
358        proj.collect_all_dep_modules()
359        self.assertTrue(mock_get_dep_modules.called_with(expected))
360
361    @mock.patch.object(logging, 'debug')
362    @mock.patch.object(source_locator, 'ModuleData')
363    @mock.patch.object(project_info.ProjectInfo, '__init__')
364    def test_gen_folder_base_dependencies(self, mock_init, mock_module_data,
365                                          mock_log):
366        """Test _gen_folder_base_dependencies."""
367        mock_init.return_value = None
368        proj = project_info.MultiProjectsInfo(['a'])
369        module = mock.Mock()
370        mock_module_data.return_value = module
371        mock_module_data.module_path = ''
372        proj.gen_folder_base_dependencies(mock_module_data)
373        self.assertTrue(mock_log.called)
374        mock_module_data.module_path = 'a/b'
375        mock_module_data.src_dirs = ['a/b/c']
376        mock_module_data.test_dirs = []
377        mock_module_data.r_java_paths = []
378        mock_module_data.srcjar_paths = []
379        mock_module_data.jar_files = []
380        mock_module_data.dep_paths = []
381        proj.gen_folder_base_dependencies(mock_module_data)
382        expected = {
383            'a/b': {
384                'src_dirs': ['a/b/c'],
385                'test_dirs': [],
386                'r_java_paths': [],
387                'srcjar_paths': [],
388                'jar_files': [],
389                'dep_paths': [],
390            }
391        }
392        self.assertEqual(proj.path_to_sources, expected)
393        mock_module_data.srcjar_paths = ['x/y.srcjar']
394        proj.gen_folder_base_dependencies(mock_module_data)
395        expected = {
396            'a/b': {
397                'src_dirs': ['a/b/c'],
398                'test_dirs': [],
399                'r_java_paths': [],
400                'srcjar_paths': ['x/y.srcjar'],
401                'jar_files': [],
402                'dep_paths': [],
403            }
404        }
405        self.assertEqual(proj.path_to_sources, expected)
406
407    @mock.patch.object(source_locator, 'ModuleData')
408    @mock.patch.object(project_info.ProjectInfo, '__init__')
409    def test_add_framework_base_path(self, mock_init, mock_module_data):
410        """Test _gen_folder_base_dependencies."""
411        mock_init.return_value = None
412        proj = project_info.MultiProjectsInfo(['a'])
413        module = mock.Mock()
414        mock_module_data.return_value = module
415        mock_module_data.module_path = 'frameworks/base'
416        mock_module_data.module_name = 'framework-other'
417        mock_module_data.src_dirs = ['a/b/c']
418        mock_module_data.test_dirs = []
419        mock_module_data.r_java_paths = []
420        mock_module_data.srcjar_paths = ['x/y.srcjar']
421        mock_module_data.jar_files = []
422        mock_module_data.dep_paths = []
423        proj.gen_folder_base_dependencies(mock_module_data)
424        expected = {
425            'frameworks/base': {
426                'dep_paths': [],
427                'jar_files': [],
428                'r_java_paths': [],
429                'src_dirs': ['a/b/c'],
430                'srcjar_paths': [],
431                'test_dirs': [],
432            }
433        }
434        self.assertDictEqual(proj.path_to_sources, expected)
435
436    @mock.patch.object(source_locator, 'ModuleData')
437    @mock.patch.object(project_info.ProjectInfo, '__init__')
438    def test_add_framework_srcjar_path(self, mock_init, mock_module_data):
439        """Test _gen_folder_base_dependencies."""
440        mock_init.return_value = None
441        proj = project_info.MultiProjectsInfo(['a'])
442        module = mock.Mock()
443        mock_module_data.return_value = module
444        mock_module_data.module_path = 'frameworks/base'
445        mock_module_data.module_name = 'framework-all'
446        mock_module_data.src_dirs = ['a/b/c']
447        mock_module_data.test_dirs = []
448        mock_module_data.r_java_paths = []
449        mock_module_data.srcjar_paths = ['x/y.srcjar']
450        mock_module_data.jar_files = []
451        mock_module_data.dep_paths = []
452        proj.gen_folder_base_dependencies(mock_module_data)
453        expected = {
454            'frameworks/base': {
455                'dep_paths': [],
456                'jar_files': [],
457                'r_java_paths': [],
458                'src_dirs': ['a/b/c'],
459                'srcjar_paths': [],
460                'test_dirs': [],
461            },
462            'frameworks/base/framework_srcjars': {
463                'dep_paths': ['frameworks/base'],
464                'jar_files': [],
465                'r_java_paths': [],
466                'src_dirs': [],
467                'srcjar_paths': ['x/y.srcjar'],
468                'test_dirs': [],
469            }
470        }
471        self.assertDictEqual(proj.path_to_sources, expected)
472
473
474if __name__ == '__main__':
475    unittest.main()
476