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