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_file_gen."""
18
19import logging
20import os
21import shutil
22import unittest
23from unittest import mock
24
25from aidegen import unittest_constants
26from aidegen.idea import xml_gen
27from aidegen.lib import common_util
28from aidegen.lib import config
29from aidegen.lib import project_config
30from aidegen.lib import project_file_gen
31from aidegen.lib import project_info
32from aidegen.project import source_splitter
33
34
35# pylint: disable=protected-access
36# pylint: disable-msg=too-many-arguments
37class AidegenProjectFileGenUnittest(unittest.TestCase):
38    """Unit tests for project_file_gen.py."""
39
40    maxDiff = None
41    _TEST_DATA_PATH = unittest_constants.TEST_DATA_PATH
42    _ANDROID_PROJECT_PATH = unittest_constants.ANDROID_PROJECT_PATH
43    _PROJECT_PATH = os.path.join(_TEST_DATA_PATH, 'project')
44    _ANDROID_FACET_SAMPLE = os.path.join(_TEST_DATA_PATH, 'android_facet.iml')
45    _PROJECT_FACET_SAMPLE = os.path.join(_TEST_DATA_PATH, 'project_facet.iml')
46    _MODULE_DEP_SAMPLE = os.path.join(_TEST_DATA_PATH, 'module_dependency.iml')
47    _IML_SAMPLE = os.path.join(_TEST_DATA_PATH, 'test.iml')
48    _DEPENDENCIES_IML_SAMPLE = os.path.join(_TEST_DATA_PATH, 'dependencies.iml')
49    _MODULE_XML_SAMPLE = os.path.join(_TEST_DATA_PATH, 'modules.xml')
50    _MAIN_MODULE_XML_SAMPLE = os.path.join(_TEST_DATA_PATH,
51                                           'modules_only_self_module.xml')
52    _ENABLE_DEBUGGER_MODULE_SAMPLE = os.path.join(
53        _TEST_DATA_PATH, 'modules_with_enable_debugger.xml')
54    _IML_PATH = os.path.join(_ANDROID_PROJECT_PATH, 'android_project.iml')
55    _DEPENDENCIES_IML_PATH = os.path.join(_ANDROID_PROJECT_PATH,
56                                          'dependencies.iml')
57    _IDEA_PATH = os.path.join(_ANDROID_PROJECT_PATH, '.idea')
58    _MODULE_PATH = os.path.join(_IDEA_PATH, 'modules.xml')
59    _SOURCE_SAMPLE = os.path.join(_TEST_DATA_PATH, 'source.iml')
60    _SRCJAR_SAMPLE = os.path.join(_TEST_DATA_PATH, 'srcjar.iml')
61    _AOSP_FOLDER = '/aosp'
62    _TEST_SOURCE_LIST = [
63        'a/b/c/d', 'a/b/c/d/e', 'a/b/c/d/e/f', 'a/b/c/d/f', 'e/f/a', 'e/f/b/c',
64        'e/f/g/h'
65    ]
66    _ANDROID_SOURCE_RELATIVE_PATH = 'test_data/project'
67    _SAMPLE_CONTENT_LIST = ['a/b/c/d', 'e/f']
68    _SAMPLE_TRIMMED_SOURCE_LIST = ['a/b/c/d', 'e/f/a', 'e/f/b/c', 'e/f/g/h']
69
70    def _init_project_config(self, args):
71        """Initialize project configurations."""
72        self.assertIsNotNone(args)
73        pconfig = project_config.ProjectConfig(args)
74        pconfig.init_environment()
75
76    @mock.patch.object(project_config, 'ProjectConfig')
77    @mock.patch.object(project_info, 'ProjectInfo')
78    def test_generate_modules_xml(self, mock_project, mock_config):
79        """Test _generate_modules_xml."""
80        mock_config.is_launch_ide = True
81        mock_project.project_absolute_path = self._ANDROID_PROJECT_PATH
82        pfile_gen = project_file_gen.ProjectFileGenerator(mock_project)
83        # Test for main project.
84        try:
85            pfile_gen._generate_modules_xml([])
86            project_file_gen.update_enable_debugger(self._ANDROID_PROJECT_PATH)
87            test_module = common_util.read_file_content(self._MODULE_PATH)
88        finally:
89            shutil.rmtree(self._IDEA_PATH)
90        sample_module = common_util.read_file_content(self._MODULE_XML_SAMPLE)
91        self.assertEqual(test_module, sample_module)
92
93        # Test for sub projects which only has self module.
94        try:
95            pfile_gen._generate_modules_xml()
96            project_file_gen.update_enable_debugger(self._ANDROID_PROJECT_PATH)
97            test_module = common_util.read_file_content(self._MODULE_PATH)
98        finally:
99            shutil.rmtree(self._IDEA_PATH)
100        sample_module = common_util.read_file_content(
101            self._MAIN_MODULE_XML_SAMPLE)
102        self.assertEqual(test_module, sample_module)
103
104    @mock.patch.object(project_file_gen, '_get_all_git_path')
105    @mock.patch.object(xml_gen, 'write_ignore_git_dirs_file')
106    @mock.patch.object(xml_gen, 'gen_vcs_xml')
107    @mock.patch.object(common_util, 'get_android_root_dir')
108    @mock.patch.object(common_util, 'find_git_root')
109    @mock.patch.object(project_info, 'ProjectInfo')
110    def test_merge_project_vcs_xmls(self, mock_project, mock_get_git_root,
111                                    mock_get_root, mock_write, mock_ignore_git,
112                                    mock_all_git_path):
113        """Test _merge_project_vcs_xmls."""
114        mock_get_root.return_value = '/a/b'
115        mock_project.project_absolute_path = '/a/b/c'
116        mock_project.project_relative_path = 'c'
117        mock_get_git_root.return_value = '/a/b/c'
118        project_file_gen._merge_project_vcs_xmls([mock_project])
119        self.assertTrue(mock_write.called_with('/a/b/c', '/a/b/c'))
120        mock_project.project_absolute_path = '/a/b'
121        mock_project.project_relative_path = None
122        mock_get_git_root.return_value = None
123        mock_all_git_path.return_value = ['/a', '/b']
124        project_file_gen._merge_project_vcs_xmls([mock_project])
125        self.assertTrue(mock_write.called_with('/a/b', [None]))
126        self.assertTrue(mock_ignore_git.called_with('/a/b', ['/a', '/b']))
127
128    @mock.patch.object(project_info, 'ProjectInfo')
129    def test_copy_project_files(self, mock_project):
130        """Test _copy_constant_project_files."""
131        mock_project.project_absolute_path = self._ANDROID_PROJECT_PATH
132        project_file_gen.ProjectFileGenerator(
133            mock_project)._copy_constant_project_files()
134        self.assertTrue(
135            os.path.isfile(
136                os.path.join(self._IDEA_PATH,
137                             project_file_gen._CODE_STYLE_FOLDER,
138                             'codeStyleConfig.xml')))
139        self.assertTrue(
140            os.path.isfile(
141                os.path.join(self._IDEA_PATH,
142                             project_file_gen._COPYRIGHT_FOLDER,
143                             'Apache_2.xml')))
144        self.assertTrue(
145            os.path.isfile(
146                os.path.join(self._IDEA_PATH,
147                             project_file_gen._COPYRIGHT_FOLDER,
148                             'profiles_settings.xml')))
149        shutil.rmtree(self._IDEA_PATH)
150
151    @mock.patch.object(logging, 'error')
152    @mock.patch.object(os, 'symlink')
153    @mock.patch.object(os.path, 'exists')
154    def test_generate_git_ignore(self, mock_path_exist, mock_link,
155                                 mock_loggin_error):
156        """Test _generate_git_ignore."""
157        mock_path_exist.return_value = True
158        project_file_gen._generate_git_ignore(
159            common_util.get_aidegen_root_dir())
160        self.assertFalse(mock_link.called)
161
162        # Test for creating symlink exception.
163        mock_path_exist.return_value = False
164        mock_link.side_effect = OSError()
165        project_file_gen._generate_git_ignore(
166            common_util.get_aidegen_root_dir())
167        self.assertTrue(mock_loggin_error.called)
168
169    def test_filter_out_source_paths(self):
170        """Test _filter_out_source_paths."""
171        test_set = {'a/a.java', 'b/b.java', 'c/c.java'}
172        module_relpath = {'a', 'c'}
173        expected_result = {'b/b.java'}
174        result_set = project_file_gen._filter_out_source_paths(test_set,
175                                                               module_relpath)
176        self.assertEqual(result_set, expected_result)
177
178    @mock.patch.object(project_config, 'ProjectConfig')
179    @mock.patch.object(project_info, 'ProjectInfo')
180    def test_update_enable_debugger(self, mock_project, mock_config):
181        """Test update_enable_debugger."""
182        mock_config.is_launch_ide = True
183        enable_debugger_iml = '/path/to/enable_debugger/enable_debugger.iml'
184        sample_module = common_util.read_file_content(
185            self._ENABLE_DEBUGGER_MODULE_SAMPLE)
186        mock_project.project_absolute_path = self._ANDROID_PROJECT_PATH
187        pfile_gen = project_file_gen.ProjectFileGenerator(mock_project)
188        try:
189            pfile_gen._generate_modules_xml([])
190            project_file_gen.update_enable_debugger(self._ANDROID_PROJECT_PATH,
191                                                    enable_debugger_iml)
192            test_module = common_util.read_file_content(self._MODULE_PATH)
193            self.assertEqual(test_module, sample_module)
194        finally:
195            shutil.rmtree(self._IDEA_PATH)
196
197    @mock.patch.object(common_util, 'find_git_root')
198    @mock.patch.object(project_file_gen.ProjectFileGenerator,
199                       '_generate_modules_xml')
200    @mock.patch.object(project_info, 'ProjectInfo')
201    def test_generate_intellij_project_file(self, mock_project,
202                                            mock_gen_xml, mock_get_git_path):
203        """Test generate_intellij_project_file."""
204        mock_project.project_absolute_path = self._ANDROID_PROJECT_PATH
205        mock_get_git_path.return_value = 'git/path'
206        project_gen = project_file_gen.ProjectFileGenerator(mock_project)
207        project_gen.project_info.is_main_project = False
208        project_gen.generate_intellij_project_file()
209        self.assertFalse(mock_gen_xml.called)
210        project_gen.project_info.is_main_project = True
211        project_gen.generate_intellij_project_file()
212        self.assertTrue(mock_gen_xml.called)
213
214    @mock.patch.object(os, 'walk')
215    def test_get_all_git_path(self, mock_os_walk):
216        """Test _get_all_git_path."""
217        # Test .git folder exists.
218        mock_os_walk.return_value = [('/root', ['.git', 'a'], None)]
219        test_result = list(project_file_gen._get_all_git_path('/root'))
220        expected_result = ['/root']
221        self.assertEqual(test_result, expected_result)
222
223        # Test .git folder does not exist.
224        mock_os_walk.return_value = [('/root', ['a'], None)]
225        test_result = list(project_file_gen._get_all_git_path('/root'))
226        expected_result = []
227        self.assertEqual(test_result, expected_result)
228
229    @mock.patch.object(common_util, 'file_generate')
230    @mock.patch.object(os.path, 'isfile')
231    def test_generate_test_mapping_schema(self, mock_is_file,
232                                          mock_file_generate):
233        """Test _generate_test_mapping_schema."""
234        mock_is_file.return_value = False
235        project_file_gen._generate_test_mapping_schema('')
236        self.assertFalse(mock_file_generate.called)
237        mock_is_file.return_value = True
238        project_file_gen._generate_test_mapping_schema('')
239        self.assertTrue(mock_file_generate.called)
240
241    @mock.patch.object(project_file_gen, 'update_enable_debugger')
242    @mock.patch.object(config.AidegenConfig, 'create_enable_debugger_module')
243    def test_gen_enable_debugger_module(self, mock_create_module,
244                                        mock_update_module):
245        """Test gen_enable_debugger_module."""
246        android_sdk_path = None
247        project_file_gen.gen_enable_debugger_module('a', android_sdk_path)
248        self.assertFalse(mock_create_module.called)
249        mock_create_module.return_value = False
250        project_file_gen.gen_enable_debugger_module('a', 'b')
251        self.assertFalse(mock_update_module.called)
252        mock_create_module.return_value = True
253        project_file_gen.gen_enable_debugger_module('a', 'b')
254        self.assertTrue(mock_update_module.called)
255
256    @mock.patch.object(project_config.ProjectConfig, 'get_instance')
257    @mock.patch.object(project_file_gen, '_merge_project_vcs_xmls')
258    @mock.patch.object(project_file_gen.ProjectFileGenerator,
259                       'generate_intellij_project_file')
260    @mock.patch.object(source_splitter.ProjectSplitter, 'gen_projects_iml')
261    @mock.patch.object(source_splitter.ProjectSplitter,
262                       'gen_framework_srcjars_iml')
263    @mock.patch.object(source_splitter.ProjectSplitter, 'revise_source_folders')
264    @mock.patch.object(source_splitter.ProjectSplitter, 'get_dependencies')
265    @mock.patch.object(common_util, 'get_android_root_dir')
266    @mock.patch.object(project_info, 'ProjectInfo')
267    def test_generate_ide_project_files(self, mock_project, mock_get_root,
268                                        mock_get_dep, mock_revise_src,
269                                        mock_gen_framework_srcjars,
270                                        mock_gen_projects_iml, mock_gen_file,
271                                        mock_merge_vcs, mock_project_config):
272        """Test generate_ide_project_files."""
273        mock_get_root.return_value = '/aosp'
274        mock_project.project_absolute_path = '/aosp'
275        mock_project.project_relative_path = ''
276        project_cfg = mock.Mock()
277        mock_project_config.return_value = project_cfg
278        project_cfg.full_repo = True
279        gen_proj = project_file_gen.ProjectFileGenerator
280        gen_proj.generate_ide_project_files([mock_project])
281        self.assertTrue(mock_get_dep.called)
282        self.assertTrue(mock_revise_src.called)
283        self.assertTrue(mock_gen_framework_srcjars.called)
284        self.assertTrue(mock_gen_projects_iml.called)
285        self.assertTrue(mock_gen_file.called)
286        self.assertTrue(mock_merge_vcs.called)
287
288
289if __name__ == '__main__':
290    unittest.main()
291