1#!/usr/bin/env python3
2#
3# Copyright 2019, 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 AidegenConfig class."""
18
19import os
20import shutil
21import tempfile
22import unittest
23from unittest import mock
24
25from aidegen import constant
26
27from aidegen.lib import common_util
28from aidegen.lib import config
29
30
31# pylint: disable=protected-access
32class AidegenConfigUnittests(unittest.TestCase):
33    """Unit tests for config.py"""
34
35    _TMP_DIR = None
36
37    def setUp(self):
38        """Prepare the testdata related path."""
39        AidegenConfigUnittests._TMP_DIR = tempfile.mkdtemp()
40        config.AidegenConfig._CONFIG_DIR = os.path.join(
41            AidegenConfigUnittests._TMP_DIR, '.config', 'asuite', 'aidegen')
42        config.AidegenConfig._CONFIG_FILE_PATH = os.path.join(
43            config.AidegenConfig._CONFIG_DIR,
44            config.AidegenConfig._DEFAULT_CONFIG_FILE)
45        config.AidegenConfig._ENABLE_DEBUG_DIR = os.path.join(
46            config.AidegenConfig._CONFIG_DIR,
47            config.AidegenConfig._ENABLE_DEBUG_CONFIG_DIR)
48        config.AidegenConfig.DEBUG_ENABLED_FILE_PATH = os.path.join(
49            config.AidegenConfig._CONFIG_DIR,
50            config.AidegenConfig._ENABLE_DEBUG_CONFIG_FILE)
51
52    def tearDown(self):
53        """Clear the testdata related path."""
54        shutil.rmtree(AidegenConfigUnittests._TMP_DIR)
55
56    @mock.patch('json.load')
57    @mock.patch('builtins.open')
58    @mock.patch('os.path.exists')
59    def test_load_aidegen_config(self, mock_file_exists, mock_file_open,
60                                 mock_json_load):
61        """Test loading aidegen config."""
62        mock_file_exists.return_value = False
63        cfg = config.AidegenConfig()
64        cfg._load_aidegen_config()
65        self.assertFalse(mock_file_open.called)
66        self.assertFalse(mock_json_load.called)
67        mock_file_exists.return_value = True
68        cfg = config.AidegenConfig()
69        cfg._load_aidegen_config()
70        self.assertTrue(mock_file_open.called)
71        self.assertTrue(mock_json_load.called)
72
73    @mock.patch('logging.info')
74    @mock.patch('logging.error')
75    @mock.patch('builtins.open')
76    @mock.patch('os.path.exists')
77    def test_error_load_aidegen_config(self, mock_file_exists, mock_file_open,
78                                       mock_error, mock_info):
79        """Test loading aidegen config with errors."""
80        mock_file_exists.return_value = True
81        cfg = config.AidegenConfig()
82        mock_file_open.side_effect = IOError()
83        with self.assertRaises(IOError):
84            cfg._load_aidegen_config()
85            self.assertTrue(mock_error.called)
86            self.assertFalse(mock_info.called)
87        mock_file_open.reset()
88        mock_file_open.side_effect = ValueError()
89        cfg._load_aidegen_config()
90        self.assertTrue(mock_info.called)
91
92    @mock.patch('json.dump')
93    @mock.patch('builtins.open')
94    @mock.patch.object(config.AidegenConfig, '_is_config_modified')
95    def test_aidegen_config_no_changed(self, mock_is_config_modified,
96                                       mock_file_open, mock_json_dump):
97        """Skip saving aidegen config when no configuration data is modified."""
98        mock_is_config_modified.return_value = False
99        cfg = config.AidegenConfig()
100        cfg._save_aidegen_config()
101        self.assertFalse(mock_file_open.called)
102        self.assertFalse(mock_json_dump.called)
103
104    @mock.patch('json.dump')
105    @mock.patch('builtins.open')
106    @mock.patch.object(config.AidegenConfig, '_is_config_modified')
107    def test_update_aidegen_config(self, mock_is_config_modified,
108                                   mock_file_open, mock_json_dump):
109        """Save the aidegen config once any configuration data is modified."""
110        mock_is_config_modified.return_value = True
111        cfg = config.AidegenConfig()
112        cfg._save_aidegen_config()
113        self.assertTrue(mock_file_open.called)
114        self.assertTrue(mock_json_dump.called)
115
116    @mock.patch('logging.warning')
117    @mock.patch.object(config.AidegenConfig, '_gen_enable_debugger_config')
118    @mock.patch.object(config.AidegenConfig, '_gen_androidmanifest')
119    @mock.patch.object(config.AidegenConfig, '_gen_enable_debug_sub_dir')
120    def test_create_enable_debugger(self, mock_debug, mock_androidmanifest,
121                                    mock_enable, mock_warning):
122        """Test create_enable_debugger_module."""
123        cfg = config.AidegenConfig()
124        self.assertTrue(cfg.create_enable_debugger_module(''))
125        mock_debug.side_effect = IOError()
126        self.assertFalse(cfg.create_enable_debugger_module(''))
127        self.assertTrue(mock_warning.called)
128        mock_debug.side_effect = OSError()
129        self.assertFalse(cfg.create_enable_debugger_module(''))
130        self.assertTrue(mock_warning.called)
131        mock_androidmanifest.side_effect = IOError()
132        self.assertFalse(cfg.create_enable_debugger_module(''))
133        self.assertTrue(mock_warning.called)
134        mock_androidmanifest.side_effect = OSError()
135        self.assertFalse(cfg.create_enable_debugger_module(''))
136        self.assertTrue(mock_warning.called)
137        mock_enable.side_effect = IOError()
138        self.assertFalse(cfg.create_enable_debugger_module(''))
139        self.assertTrue(mock_warning.called)
140        mock_enable.side_effect = OSError()
141        self.assertFalse(cfg.create_enable_debugger_module(''))
142        self.assertTrue(mock_warning.called)
143
144    @mock.patch.object(common_util, 'file_generate')
145    @mock.patch('os.path.exists')
146    def test_gen_debugger_config(self, mock_file_exists, mock_file_generate):
147        """Test generating the enable debugger config."""
148        cfg = config.AidegenConfig()
149        android_sdk_version = ''
150        mock_file_exists.return_value = False
151        cfg._gen_enable_debugger_config(android_sdk_version)
152        self.assertTrue(mock_file_generate.called)
153
154    @mock.patch('os.stat')
155    @mock.patch.object(common_util, 'file_generate')
156    @mock.patch('os.path.exists')
157    def test_androidmanifest_no_changed(self, mock_file_exists,
158                                        mock_file_generate, mock_file_stat):
159        """No generate the AndroidManifest.xml when it exists and size > 0."""
160        cfg = config.AidegenConfig()
161        mock_file_exists.return_value = True
162        mock_file_stat.return_value.st_size = 1
163        cfg._gen_androidmanifest()
164        self.assertFalse(mock_file_generate.called)
165
166    @mock.patch('os.stat')
167    @mock.patch.object(common_util, 'file_generate')
168    @mock.patch('os.path.exists')
169    def test_override_androidmanifest(self, mock_file_exists,
170                                      mock_file_generate, mock_file_stat):
171        """Override the AndroidManifest.xml when the file size is zero."""
172        cfg = config.AidegenConfig()
173        mock_file_exists.return_value = True
174        mock_file_stat.return_value.st_size = 0
175        cfg._gen_androidmanifest()
176        self.assertTrue(mock_file_generate.called)
177
178    @mock.patch.object(common_util, 'file_generate')
179    @mock.patch('os.path.exists')
180    def test_gen_androidmanifest(self, mock_file_exists, mock_file_generate):
181        """Generate the AndroidManifest.xml when it doesn't exist."""
182        cfg = config.AidegenConfig()
183        mock_file_exists.return_value = False
184        cfg._gen_androidmanifest()
185        self.assertTrue(mock_file_generate.called)
186
187    @mock.patch('os.makedirs')
188    @mock.patch('os.path.exists')
189    def test_config_folder_exists(self, mock_folder_exists, mock_makedirs):
190        """Skipping create the config folder once it exists."""
191        mock_folder_exists.return_value = True
192        config.AidegenConfig()
193        self.assertFalse(mock_makedirs.called)
194
195    @mock.patch('os.makedirs')
196    @mock.patch('os.path.exists')
197    def test_create_config_folder(self, mock_folder_exists, mock_makedirs):
198        """Create the config folder when it doesn't exist."""
199        mock_folder_exists.return_value = False
200        config.AidegenConfig()
201        self.assertTrue(mock_makedirs.called)
202
203    @mock.patch('os.path.isfile')
204    @mock.patch('builtins.open', create=True)
205    def test_deprecated_intellij_version(self, mock_open, mock_isfile):
206        """Test deprecated_intellij_version."""
207        # Test the idea.sh file contains the deprecated string.
208        cfg = config.AidegenConfig()
209        expacted_data = ('#!/bin/sh\n\n'
210                         'SUMMARY="This version of IntelliJ Community Edition '
211                         'is no longer supported."\n')
212        mock_open.side_effect = [
213            mock.mock_open(read_data=expacted_data).return_value
214        ]
215        mock_isfile.return_value = True
216        self.assertTrue(cfg.deprecated_intellij_version(0))
217
218        # Test the idea.sh file doesn't contains the deprecated string.
219        expacted_data = ('#!/bin/sh\n\n'
220                         'JAVA_BIN="$JDK/bin/java"\n'
221                         '"$JAVA_BIN" \\n')
222        mock_open.side_effect = [
223            mock.mock_open(read_data=expacted_data).return_value
224        ]
225        self.assertFalse(cfg.deprecated_intellij_version(0))
226
227    @mock.patch.object(config.AidegenConfig, 'deprecated_studio_version')
228    @mock.patch.object(config.AidegenConfig, 'deprecated_intellij_version')
229    def test_deprecated_version(self, mock_inj, mock_studio):
230        """Test deprecated_version."""
231        cfg = config.AidegenConfig()
232        ide_name = constant.IDE_INTELLIJ
233        test_path = ''
234        cfg.deprecated_version(ide_name, test_path)
235        self.assertTrue(mock_inj.called)
236
237        ide_name = constant.IDE_ANDROID_STUDIO
238        cfg.deprecated_version(ide_name, test_path)
239        self.assertTrue(mock_studio.called)
240        test_ide = ''
241        self.assertFalse(cfg.deprecated_version(test_ide, test_path))
242
243    @mock.patch.object(os.path, 'isfile')
244    def test_deprecated_studio_version(self, mock_is_file):
245        """Test deprecated_studio_version."""
246        test_sh_name = 'test.sh'
247        # temp_dir/test
248        temp_base = os.path.join(self._TMP_DIR, 'test')
249        os.mkdir(temp_base)
250        # temp_dir/test/studio/bin
251        os.mkdir(os.path.join(temp_base, 'studio'))
252        test_bin_path = os.path.join(self._TMP_DIR, 'test', 'studio', 'bin')
253        os.mkdir(test_bin_path)
254        # /temp_dir/test/studio/bin/test.sh
255        test_sh_path = os.path.join(self._TMP_DIR, 'test', 'studio', 'bin',
256                                    test_sh_name)
257        # Real test.sh doesn't exist.
258        cfg = config.AidegenConfig()
259        self.assertTrue(cfg.deprecated_studio_version(test_sh_path))
260        # The /temp_dir/test/studio/lib doesn't exist case.
261        mock_is_file.return_value = True
262        self.assertTrue(cfg.deprecated_studio_version(test_sh_path))
263        # The /temp_dir/test/studio/lib exists.
264        test_lib_path = os.path.join(self._TMP_DIR, 'test', 'studio', 'lib')
265        os.mkdir(test_lib_path)
266        self.assertFalse(cfg.deprecated_studio_version(test_sh_path))
267        shutil.rmtree(temp_base)
268
269    @mock.patch('os.path.isfile')
270    def test_idea_path_not_file(self, mock_isfile):
271        """Test deprecated_intellij_version."""
272        # Test the idea_path is not a file.
273        cfg = config.AidegenConfig()
274        mock_isfile.return_value = False
275        self.assertFalse(cfg.deprecated_intellij_version(0))
276
277    @mock.patch.object(config.AidegenConfig, 'deprecated_version')
278    @mock.patch.object(config.AidegenConfig, 'deprecated_intellij_version')
279    def test_preferred_version(self, mock_deprecated_intj, mock_deprecated):
280        """Test get preferred IntelliJ version."""
281        cfg = config.AidegenConfig()
282        cfg._config['preferred_version'] = ''
283        self.assertEqual(cfg.preferred_version(), None)
284
285        result = 'test_intellij'
286        cfg._config['IntelliJ_preferred_version'] = result
287        mock_deprecated.return_value = False
288        self.assertEqual(cfg.preferred_version(constant.IDE_INTELLIJ), result)
289        self.assertEqual(cfg.preferred_version(result), None)
290        mock_deprecated.return_value = True
291        self.assertEqual(cfg.preferred_version(constant.IDE_INTELLIJ), None)
292
293        mock_deprecated_intj.return_value = False
294        cfg._config['preferred_version'] = 'a'
295        self.assertEqual(cfg.preferred_version(), 'a')
296        mock_deprecated_intj.return_value = True
297        self.assertEqual(cfg.preferred_version(), None)
298
299    def test_set_preferred_version(self):
300        """Test set_preferred_version."""
301        cfg = config.AidegenConfig()
302        cfg._config[config.AidegenConfig._KEY_APPEND] = 'Yes'
303        cfg.set_preferred_version('test', None)
304        self.assertEqual(cfg._config[config.AidegenConfig._KEY_APPEND], 'test')
305        cfg.set_preferred_version('test', constant.IDE_INTELLIJ)
306        self.assertEqual(cfg._config['IntelliJ_preferred_version'], 'test')
307
308    @mock.patch('os.makedirs')
309    @mock.patch('os.path.exists')
310    def test_gen_enable_debug_sub_dir(self, mock_file_exists, mock_makedirs):
311        """Test _gen_enable_debug_sub_dir."""
312        cfg = config.AidegenConfig()
313        mock_file_exists.return_value = True
314        cfg._gen_enable_debug_sub_dir('a')
315        self.assertFalse(mock_makedirs.called)
316        mock_file_exists.return_value = False
317        cfg._gen_enable_debug_sub_dir('a')
318        self.assertTrue(mock_makedirs.called)
319
320
321class IdeaPropertiesUnittests(unittest.TestCase):
322    """Unit tests for IdeaProperties class."""
323
324    _CONFIG_DIR = None
325
326    def setUp(self):
327        """Prepare the testdata related path."""
328        IdeaPropertiesUnittests._CONFIG_DIR = tempfile.mkdtemp()
329
330    def tearDown(self):
331        """Clear the testdata related path."""
332        shutil.rmtree(IdeaPropertiesUnittests._CONFIG_DIR)
333
334    def test_set_default_properties(self):
335        """Test creating the idea.properties with default content."""
336        cfg = config.IdeaProperties(IdeaPropertiesUnittests._CONFIG_DIR)
337        cfg._set_default_idea_properties()
338        expected_data = cfg._PROPERTIES_CONTENT.format(
339            KEY_FILE_SIZE=cfg._KEY_FILESIZE,
340            VALUE_FILE_SIZE=cfg._FILESIZE_LIMIT)
341        generated_file = os.path.join(IdeaPropertiesUnittests._CONFIG_DIR,
342                                      cfg._PROPERTIES_FILE)
343        generated_content = common_util.read_file_content(generated_file)
344        self.assertEqual(expected_data, generated_content)
345
346    @mock.patch.object(common_util, 'read_file_content')
347    def test_reset_max_file_size(self, mock_content):
348        """Test reset the file size limit when it's smaller than 100000."""
349        mock_content.return_value = ('# custom IntelliJ IDEA properties\n'
350                                     'idea.max.intellisense.filesize=5000')
351        expected_data = ('# custom IntelliJ IDEA properties\n'
352                         'idea.max.intellisense.filesize=100000')
353        cfg = config.IdeaProperties(IdeaPropertiesUnittests._CONFIG_DIR)
354        cfg._reset_max_file_size()
355        generated_file = os.path.join(IdeaPropertiesUnittests._CONFIG_DIR,
356                                      cfg._PROPERTIES_FILE)
357        with open(generated_file) as properties_file:
358            generated_content = properties_file.read()
359        self.assertEqual(expected_data, generated_content)
360
361    @mock.patch.object(common_util, 'file_generate')
362    @mock.patch.object(common_util, 'read_file_content')
363    def test_no_reset_max_file_size(self, mock_content, mock_gen_file):
364        """Test when the file size is larger than 100000."""
365        mock_content.return_value = ('# custom IntelliJ IDEA properties\n'
366                                     'idea.max.intellisense.filesize=110000')
367        cfg = config.IdeaProperties(IdeaPropertiesUnittests._CONFIG_DIR)
368        cfg._reset_max_file_size()
369        self.assertFalse(mock_gen_file.called)
370
371    @mock.patch.object(config.IdeaProperties, '_reset_max_file_size')
372    @mock.patch.object(config.IdeaProperties, '_set_default_idea_properties')
373    @mock.patch('os.path.exists')
374    def test_set_idea_properties_called(self, mock_file_exists,
375                                        mock_set_default,
376                                        mock_reset_file_size):
377        """Test _set_default_idea_properties() method is called."""
378        mock_file_exists.return_value = False
379        cfg = config.IdeaProperties(IdeaPropertiesUnittests._CONFIG_DIR)
380        cfg.set_max_file_size()
381        self.assertTrue(mock_set_default.called)
382        self.assertFalse(mock_reset_file_size.called)
383
384    @mock.patch.object(config.IdeaProperties, '_reset_max_file_size')
385    @mock.patch.object(config.IdeaProperties, '_set_default_idea_properties')
386    @mock.patch('os.path.exists')
387    def test_reset_properties_called(self, mock_file_exists, mock_set_default,
388                                     mock_reset_file_size):
389        """Test _reset_max_file_size() method is called."""
390        mock_file_exists.return_value = True
391        cfg = config.IdeaProperties(IdeaPropertiesUnittests._CONFIG_DIR)
392        cfg.set_max_file_size()
393        self.assertFalse(mock_set_default.called)
394        self.assertTrue(mock_reset_file_size.called)
395
396
397if __name__ == '__main__':
398    unittest.main()
399