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 common_util."""
18
19import logging
20import os
21import unittest
22from unittest import mock
23from xml.etree import ElementTree
24
25from aidegen import constant
26from aidegen import unittest_constants
27from aidegen.lib import common_util
28from aidegen.lib import errors
29
30from atest import module_info
31
32
33# pylint: disable=unused-argument
34# pylint: disable=protected-access
35class AidegenCommonUtilUnittests(unittest.TestCase):
36    """Unit tests for common_util.py"""
37
38    _TEST_XML_CONTENT = """\
39<application><component name="ProjectJdkTable">
40
41    <jdk version="2">     <name value="JDK_OTHER" />
42      <type value="JavaSDK" />    </jdk>  </component>
43</application>
44"""
45    _SAMPLE_XML_CONTENT = """\
46<application>
47  <component name="ProjectJdkTable">
48    <jdk version="2">
49      <name value="JDK_OTHER"/>
50      <type value="JavaSDK"/>
51    </jdk>
52  </component>
53</application>"""
54
55    @mock.patch('os.getcwd')
56    @mock.patch('os.path.isabs')
57    @mock.patch.object(common_util, 'get_android_root_dir')
58    def test_get_related_paths(self, mock_get_root, mock_is_abspath, mock_cwd):
59        """Test get_related_paths with different conditions."""
60        mod_info = mock.MagicMock()
61        mod_info.is_module.return_value = True
62        mod_info.get_paths.return_value = {}
63        mock_is_abspath.return_value = False
64        self.assertEqual((None, None),
65                         common_util.get_related_paths(
66                             mod_info, unittest_constants.TEST_MODULE))
67        mock_get_root.return_value = unittest_constants.TEST_PATH
68        mod_info.get_paths.return_value = [unittest_constants.TEST_MODULE]
69        expected = (unittest_constants.TEST_MODULE, os.path.join(
70            unittest_constants.TEST_PATH, unittest_constants.TEST_MODULE))
71        self.assertEqual(
72            expected, common_util.get_related_paths(
73                mod_info, unittest_constants.TEST_MODULE))
74        mod_info.is_module.return_value = False
75        mod_info.get_module_names.return_value = True
76        self.assertEqual(expected, common_util.get_related_paths(
77            mod_info, unittest_constants.TEST_MODULE))
78        self.assertEqual(('', unittest_constants.TEST_PATH),
79                         common_util.get_related_paths(
80                             mod_info, constant.WHOLE_ANDROID_TREE_TARGET))
81
82        mod_info.is_module.return_value = False
83        mod_info.get_module_names.return_value = False
84        mock_is_abspath.return_value = True
85        mock_get_root.return_value = '/a'
86        self.assertEqual(('b/c', '/a/b/c'),
87                         common_util.get_related_paths(mod_info, '/a/b/c'))
88
89        mock_is_abspath.return_value = False
90        mock_cwd.return_value = '/a'
91        mock_get_root.return_value = '/a'
92        self.assertEqual(('b/c', '/a/b/c'),
93                         common_util.get_related_paths(mod_info, 'b/c'))
94
95    @mock.patch('os.getcwd')
96    @mock.patch.object(common_util, 'is_android_root')
97    @mock.patch.object(common_util, 'get_android_root_dir')
98    def test_get_related_paths_2(
99            self, mock_get_root, mock_is_root, mock_getcwd):
100        """Test get_related_paths with different conditions."""
101
102        mock_get_root.return_value = '/a'
103        mod_info = mock.MagicMock()
104
105        # Test get_module_names returns False, user inputs a relative path of
106        # current directory.
107        mod_info.is_mod.return_value = False
108        rel_path = 'b/c/d'
109        abs_path = '/a/b/c/d'
110        mod_info.get_paths.return_value = [rel_path]
111        mod_info.get_module_names.return_value = False
112        mock_getcwd.return_value = '/a/b/c'
113        input_target = 'd'
114        # expected tuple: (rel_path, abs_path)
115        expected = (rel_path, abs_path)
116        result = common_util.get_related_paths(mod_info, input_target)
117        self.assertEqual(expected, result)
118
119        # Test user doesn't input target and current working directory is the
120        # android root folder.
121        mock_getcwd.return_value = '/a'
122        mock_is_root.return_value = True
123        expected = ('', '/a')
124        result = common_util.get_related_paths(mod_info, target=None)
125        self.assertEqual(expected, result)
126
127        # Test user doesn't input target and current working directory is not
128        # android root folder.
129        mock_getcwd.return_value = '/a/b'
130        mock_is_root.return_value = False
131        expected = ('b', '/a/b')
132        result = common_util.get_related_paths(mod_info, target=None)
133        self.assertEqual(expected, result)
134        result = common_util.get_related_paths(mod_info, target='.')
135        self.assertEqual(expected, result)
136
137    @mock.patch.object(common_util, 'is_android_root')
138    @mock.patch.object(common_util, 'get_related_paths')
139    def test_is_target_android_root(self, mock_get_rel, mock_get_root):
140        """Test is_target_android_root with different conditions."""
141        mod_info = mock.MagicMock()
142        mock_get_rel.return_value = None, unittest_constants.TEST_PATH
143        mock_get_root.return_value = True
144        self.assertTrue(
145            common_util.is_target_android_root(
146                mod_info, [unittest_constants.TEST_MODULE]))
147        mock_get_rel.return_value = None, ''
148        mock_get_root.return_value = False
149        self.assertFalse(
150            common_util.is_target_android_root(
151                mod_info, [unittest_constants.TEST_MODULE]))
152
153    @mock.patch.object(common_util, 'get_android_root_dir')
154    @mock.patch.object(common_util, 'has_build_target')
155    @mock.patch('os.path.isdir')
156    @mock.patch.object(common_util, 'get_related_paths')
157    def test_check_module(self, mock_get, mock_isdir, mock_has_target,
158                          mock_get_root):
159        """Test if _check_module raises errors with different conditions."""
160        mod_info = mock.MagicMock()
161        mock_get.return_value = None, None
162        with self.assertRaises(errors.FakeModuleError) as ctx:
163            common_util.check_module(mod_info, unittest_constants.TEST_MODULE)
164            expected = common_util.FAKE_MODULE_ERROR.format(
165                unittest_constants.TEST_MODULE)
166            self.assertEqual(expected, str(ctx.exception))
167        mock_get_root.return_value = unittest_constants.TEST_PATH
168        mock_get.return_value = None, unittest_constants.TEST_MODULE
169        with self.assertRaises(errors.ProjectOutsideAndroidRootError) as ctx:
170            common_util.check_module(mod_info, unittest_constants.TEST_MODULE)
171            expected = common_util.OUTSIDE_ROOT_ERROR.format(
172                unittest_constants.TEST_MODULE)
173            self.assertEqual(expected, str(ctx.exception))
174        mock_get.return_value = None, unittest_constants.TEST_PATH
175        mock_isdir.return_value = False
176        with self.assertRaises(errors.ProjectPathNotExistError) as ctx:
177            common_util.check_module(mod_info, unittest_constants.TEST_MODULE)
178            expected = common_util.PATH_NOT_EXISTS_ERROR.format(
179                unittest_constants.TEST_MODULE)
180            self.assertEqual(expected, str(ctx.exception))
181        mock_isdir.return_value = True
182        mock_has_target.return_value = False
183        mock_get.return_value = None, os.path.join(unittest_constants.TEST_PATH,
184                                                   'test.jar')
185        with self.assertRaises(errors.NoModuleDefinedInModuleInfoError) as ctx:
186            common_util.check_module(mod_info, unittest_constants.TEST_MODULE)
187            expected = common_util.NO_MODULE_DEFINED_ERROR.format(
188                unittest_constants.TEST_MODULE)
189            self.assertEqual(expected, str(ctx.exception))
190        self.assertFalse(common_util.check_module(mod_info, '', False))
191        self.assertFalse(common_util.check_module(mod_info, 'nothing', False))
192
193    @mock.patch.object(common_util, 'check_module')
194    def test_check_modules(self, mock_check):
195        """Test _check_modules with different module lists."""
196        mod_info = mock.MagicMock()
197        common_util._check_modules(mod_info, [])
198        self.assertEqual(mock_check.call_count, 0)
199        common_util._check_modules(mod_info, ['module1', 'module2'])
200        self.assertEqual(mock_check.call_count, 2)
201        target = 'nothing'
202        mock_check.return_value = False
203        self.assertFalse(common_util._check_modules(mod_info, [target], False))
204
205    @mock.patch.object(common_util, 'get_android_root_dir')
206    def test_get_abs_path(self, mock_get_root):
207        """Test get_abs_path handling."""
208        mock_get_root.return_value = unittest_constants.TEST_DATA_PATH
209        self.assertEqual(unittest_constants.TEST_DATA_PATH,
210                         common_util.get_abs_path(''))
211        test_path = os.path.join(unittest_constants.TEST_DATA_PATH, 'test.jar')
212        self.assertEqual(test_path, common_util.get_abs_path(test_path))
213        self.assertEqual(test_path, common_util.get_abs_path('test.jar'))
214
215    def test_is_target(self):
216        """Test is_target handling."""
217        self.assertTrue(
218            common_util.is_target('packages/apps/tests/test.a', ['.so', '.a']))
219        self.assertTrue(
220            common_util.is_target('packages/apps/tests/test.so', ['.so', '.a']))
221        self.assertFalse(
222            common_util.is_target(
223                'packages/apps/tests/test.jar', ['.so', '.a']))
224
225    @mock.patch.object(logging, 'basicConfig')
226    def test_configure_logging(self, mock_log_config):
227        """Test configure_logging with different arguments."""
228        common_util.configure_logging(True)
229        log_format = common_util._LOG_FORMAT
230        datefmt = common_util._DATE_FORMAT
231        level = common_util.logging.DEBUG
232        self.assertTrue(
233            mock_log_config.called_with(
234                level=level, format=log_format, datefmt=datefmt))
235        common_util.configure_logging(False)
236        level = common_util.logging.INFO
237        self.assertTrue(
238            mock_log_config.called_with(
239                level=level, format=log_format, datefmt=datefmt))
240
241    @mock.patch.object(common_util, '_check_modules')
242    @mock.patch.object(module_info, 'ModuleInfo')
243    def test_get_atest_module_info(self, mock_modinfo, mock_check_modules):
244        """Test get_atest_module_info handling."""
245        common_util.get_atest_module_info()
246        self.assertEqual(mock_modinfo.call_count, 1)
247        mock_modinfo.reset_mock()
248        mock_check_modules.return_value = False
249        common_util.get_atest_module_info(['nothing'])
250        self.assertEqual(mock_modinfo.call_count, 2)
251
252    @mock.patch('builtins.open', create=True)
253    def test_read_file_content(self, mock_open):
254        """Test read_file_content handling."""
255        expected_data1 = 'Data1'
256        file_a = 'fileA'
257        mock_open.side_effect = [
258            mock.mock_open(read_data=expected_data1).return_value
259        ]
260        self.assertEqual(expected_data1, common_util.read_file_content(file_a))
261        mock_open.assert_called_once_with(file_a, 'r', encoding='utf8')
262
263    @mock.patch('os.getenv')
264    @mock.patch.object(common_util, 'get_android_root_dir')
265    def test_get_android_out_dir(self, mock_get_android_root_dir, mock_getenv):
266        """Test get_android_out_dir handling."""
267        root = 'my/path-to-root/master'
268        default_root = 'out'
269        android_out_root = 'eng_out'
270        mock_get_android_root_dir.return_value = root
271        mock_getenv.side_effect = ['', '']
272        self.assertEqual(default_root, common_util.get_android_out_dir())
273        mock_getenv.side_effect = [android_out_root, '']
274        self.assertEqual(android_out_root, common_util.get_android_out_dir())
275        mock_getenv.side_effect = ['', default_root]
276        self.assertEqual(os.path.join(default_root, os.path.basename(root)),
277                         common_util.get_android_out_dir())
278        mock_getenv.side_effect = [android_out_root, default_root]
279        self.assertEqual(android_out_root, common_util.get_android_out_dir())
280
281    def test_has_build_target(self):
282        """Test has_build_target handling."""
283        mod_info = mock.MagicMock()
284        mod_info.path_to_module_info = {'a/b/c': {}}
285        rel_path = 'a/b'
286        self.assertTrue(common_util.has_build_target(mod_info, rel_path))
287        rel_path = 'd/e'
288        self.assertFalse(common_util.has_build_target(mod_info, rel_path))
289
290    @mock.patch('os.path.expanduser')
291    def test_remove_user_home_path(self, mock_expanduser):
292        """ Test replace the user home path to a constant string."""
293        mock_expanduser.return_value = '/usr/home/a'
294        test_string = '/usr/home/a/test/dir'
295        expected_string = '$USER_HOME$/test/dir'
296        result_path = common_util.remove_user_home_path(test_string)
297        self.assertEqual(result_path, expected_string)
298
299    def test_io_error_handle(self):
300        """Test io_error_handle handling."""
301        err = "It's an IO error."
302
303        def some_io_error_func():
304            raise IOError(err)
305        with self.assertRaises(IOError) as context:
306            decorator = common_util.io_error_handle(some_io_error_func)
307            decorator()
308            self.assertTrue(err in context.exception)
309
310    @mock.patch.object(common_util, '_show_env_setup_msg_and_exit')
311    @mock.patch('os.environ.get')
312    def test_get_android_root_dir(self, mock_get_env, mock_show_msg):
313        """Test get_android_root_dir handling."""
314        root = 'root'
315        mock_get_env.return_value = root
316        expected = common_util.get_android_root_dir()
317        self.assertEqual(root, expected)
318        root = ''
319        mock_get_env.return_value = root
320        common_util.get_android_root_dir()
321        self.assertTrue(mock_show_msg.called)
322
323    # pylint: disable=no-value-for-parameter
324    def test_check_args(self):
325        """Test check_args handling."""
326        with self.assertRaises(TypeError):
327            decorator = common_util.check_args(name=str, text=str)
328            decorator(parse_rule(None, 'text'))
329        with self.assertRaises(TypeError):
330            decorator = common_util.check_args(name=str, text=str)
331            decorator(parse_rule('Paul', ''))
332        with self.assertRaises(TypeError):
333            decorator = common_util.check_args(name=str, text=str)
334            decorator(parse_rule(1, 2))
335
336    @mock.patch.object(common_util, 'get_blueprint_json_path')
337    @mock.patch.object(common_util, 'get_android_out_dir')
338    @mock.patch.object(common_util, 'get_android_root_dir')
339    def test_get_blueprint_json_files_relative_dict(
340            self, mock_get_root, mock_get_out, mock_get_path):
341        """Test get_blueprint_json_files_relative_dict function,"""
342        mock_get_root.return_value = 'a/b'
343        mock_get_out.return_value = 'out'
344        mock_get_path.return_value = 'out/soong/bp_java_file'
345        path_compdb = os.path.join('a/b', 'out', 'soong',
346                                   constant.RELATIVE_COMPDB_PATH,
347                                   constant.COMPDB_JSONFILE_NAME)
348        data = {
349            constant.GEN_JAVA_DEPS: 'a/b/out/soong/bp_java_file',
350            constant.GEN_CC_DEPS: 'a/b/out/soong/bp_java_file',
351            constant.GEN_COMPDB: path_compdb,
352            constant.GEN_RUST: 'a/b/out/soong/bp_java_file'
353        }
354        self.assertEqual(
355            data, common_util.get_blueprint_json_files_relative_dict())
356
357    @mock.patch('os.environ.get')
358    def test_get_lunch_target(self, mock_get_env):
359        """Test get_lunch_target."""
360        mock_get_env.return_value = "test"
361        self.assertEqual(
362            common_util.get_lunch_target(), '{"lunch target": "test-test"}')
363
364    def test_to_pretty_xml(self):
365        """Test to_pretty_xml."""
366        root = ElementTree.fromstring(self._TEST_XML_CONTENT)
367        pretty_xml = common_util.to_pretty_xml(root)
368        self.assertEqual(pretty_xml, self._SAMPLE_XML_CONTENT)
369
370    def test_to_to_boolean(self):
371        """Test to_boolean function with conditions."""
372        self.assertTrue(common_util.to_boolean('True'))
373        self.assertTrue(common_util.to_boolean('true'))
374        self.assertTrue(common_util.to_boolean('T'))
375        self.assertTrue(common_util.to_boolean('t'))
376        self.assertTrue(common_util.to_boolean('1'))
377        self.assertFalse(common_util.to_boolean('False'))
378        self.assertFalse(common_util.to_boolean('false'))
379        self.assertFalse(common_util.to_boolean('F'))
380        self.assertFalse(common_util.to_boolean('f'))
381        self.assertFalse(common_util.to_boolean('0'))
382        self.assertFalse(common_util.to_boolean(''))
383
384    @mock.patch.object(os.path, 'exists')
385    @mock.patch.object(common_util, 'get_android_root_dir')
386    def test_find_git_root(self, mock_get_root, mock_exist):
387        """Test find_git_root."""
388        mock_get_root.return_value = '/a/b'
389        mock_exist.return_value = True
390        self.assertEqual(common_util.find_git_root('c/d'), '/a/b/c/d')
391        mock_exist.return_value = False
392        self.assertEqual(common_util.find_git_root('c/d'), None)
393
394    def test_determine_language_ide(self):
395        """Test determine_language_ide function."""
396        ide = 'u'
397        lang = 'u'
398        self.assertEqual((constant.JAVA, constant.IDE_INTELLIJ),
399                         common_util.determine_language_ide(lang, ide))
400        self.assertEqual((constant.JAVA, constant.IDE_INTELLIJ),
401                         common_util.determine_language_ide(
402                             lang, ide, ['some_module']))
403        self.assertEqual((constant.C_CPP, constant.IDE_CLION),
404                         common_util.determine_language_ide(
405                             lang, ide, None, ['some_module']))
406        self.assertEqual((constant.RUST, constant.IDE_VSCODE),
407                         common_util.determine_language_ide(
408                             lang, ide, None, None, ['some_module']))
409        lang = 'j'
410        self.assertEqual((constant.JAVA, constant.IDE_INTELLIJ),
411                         common_util.determine_language_ide(lang, ide))
412        ide = 'c'
413        self.assertEqual((constant.C_CPP, constant.IDE_CLION),
414                         common_util.determine_language_ide(lang, ide))
415        ide = 'j'
416        lang = 'u'
417        self.assertEqual((constant.JAVA, constant.IDE_INTELLIJ),
418                         common_util.determine_language_ide(lang, ide))
419        lang = 'j'
420        self.assertEqual((constant.JAVA, constant.IDE_INTELLIJ),
421                         common_util.determine_language_ide(lang, ide))
422        ide = 'c'
423        self.assertEqual((constant.C_CPP, constant.IDE_CLION),
424                         common_util.determine_language_ide(lang, ide))
425        lang = 'c'
426        ide = 'u'
427        self.assertEqual((constant.C_CPP, constant.IDE_CLION),
428                         common_util.determine_language_ide(lang, ide))
429        ide = 'j'
430        self.assertEqual((constant.JAVA, constant.IDE_INTELLIJ),
431                         common_util.determine_language_ide(lang, ide))
432
433    @mock.patch('zipfile.ZipFile.extractall')
434    @mock.patch('zipfile.ZipFile')
435    def test_unzip_file(self, mock_zipfile, mock_extract):
436        """Test unzip_file function."""
437        src = 'a/b/c.zip'
438        dest = 'a/b/d'
439        common_util.unzip_file(src, dest)
440        mock_zipfile.assert_called_with(src, 'r')
441        self.assertFalse(mock_extract.called)
442
443    @mock.patch('os.walk')
444    def test_check_java_or_kotlin_file_exists(self, mock_walk):
445        """Test check_java_or_kotlin_file_exists with conditions."""
446        root_dir = 'a/path/to/dir'
447        folder = 'path/to/dir'
448        target = 'test.java'
449        abs_path = os.path.join(root_dir, folder)
450        mock_walk.return_value = [(root_dir, [folder], [target])]
451        self.assertTrue(common_util.check_java_or_kotlin_file_exists(abs_path))
452        target = 'test.kt'
453        abs_path = os.path.join(root_dir, folder)
454        mock_walk.return_value = [(root_dir, [folder], [target])]
455        self.assertTrue(common_util.check_java_or_kotlin_file_exists(abs_path))
456        target = 'test.cpp'
457        mock_walk.return_value = [(root_dir, [folder], [target])]
458        self.assertFalse(common_util.check_java_or_kotlin_file_exists(abs_path))
459
460        # Only VS Code IDE supports Rust projects right now.
461        lang = 'r'
462        ide = 'u'
463        self.assertEqual((constant.RUST, constant.IDE_VSCODE),
464                         common_util.determine_language_ide(lang, ide))
465        lang = 'r'
466        ide = 'v'
467        self.assertEqual((constant.RUST, constant.IDE_VSCODE),
468                         common_util.determine_language_ide(lang, ide))
469        lang = 'r'
470        ide = 'j'
471        self.assertEqual((constant.RUST, constant.IDE_VSCODE),
472                         common_util.determine_language_ide(lang, ide))
473        lang = 'r'
474        ide = 'c'
475        self.assertEqual((constant.RUST, constant.IDE_VSCODE),
476                         common_util.determine_language_ide(lang, ide))
477
478
479def parse_rule(self, name, text):
480    """A test function for test_check_args."""
481
482
483if __name__ == '__main__':
484    unittest.main()
485