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    def test_set_plugin_preference(self):
309        """Test set_plugin_preference."""
310        cfg = config.AidegenConfig()
311        cfg._config[config.AidegenConfig._KEY_PLUGIN_PREFERENCE] = 'yes'
312        cfg.plugin_preference = 'no'
313        self.assertEqual(cfg._config[
314            config.AidegenConfig._KEY_PLUGIN_PREFERENCE], 'no')
315
316    def test_get_plugin_preference(self):
317        """Test get_plugin_preference."""
318        cfg = config.AidegenConfig()
319        cfg._config[config.AidegenConfig._KEY_PLUGIN_PREFERENCE] = 'yes'
320        self.assertEqual(cfg.plugin_preference, 'yes')
321
322    @mock.patch('os.makedirs')
323    @mock.patch('os.path.exists')
324    def test_gen_enable_debug_sub_dir(self, mock_file_exists, mock_makedirs):
325        """Test _gen_enable_debug_sub_dir."""
326        cfg = config.AidegenConfig()
327        mock_file_exists.return_value = True
328        cfg._gen_enable_debug_sub_dir('a')
329        self.assertFalse(mock_makedirs.called)
330        mock_file_exists.return_value = False
331        cfg._gen_enable_debug_sub_dir('a')
332        self.assertTrue(mock_makedirs.called)
333
334
335class IdeaPropertiesUnittests(unittest.TestCase):
336    """Unit tests for IdeaProperties class."""
337
338    _CONFIG_DIR = None
339
340    def setUp(self):
341        """Prepare the testdata related path."""
342        IdeaPropertiesUnittests._CONFIG_DIR = tempfile.mkdtemp()
343
344    def tearDown(self):
345        """Clear the testdata related path."""
346        shutil.rmtree(IdeaPropertiesUnittests._CONFIG_DIR)
347
348    def test_set_default_properties(self):
349        """Test creating the idea.properties with default content."""
350        cfg = config.IdeaProperties(IdeaPropertiesUnittests._CONFIG_DIR)
351        cfg._set_default_idea_properties()
352        expected_data = cfg._PROPERTIES_CONTENT.format(
353            KEY_FILE_SIZE=cfg._KEY_FILESIZE,
354            VALUE_FILE_SIZE=cfg._FILESIZE_LIMIT)
355        generated_file = os.path.join(IdeaPropertiesUnittests._CONFIG_DIR,
356                                      cfg._PROPERTIES_FILE)
357        generated_content = common_util.read_file_content(generated_file)
358        self.assertEqual(expected_data, generated_content)
359
360    @mock.patch.object(common_util, 'read_file_content')
361    def test_reset_max_file_size(self, mock_content):
362        """Test reset the file size limit when it's smaller than 100000."""
363        mock_content.return_value = ('# custom IntelliJ IDEA properties\n'
364                                     'idea.max.intellisense.filesize=5000')
365        expected_data = ('# custom IntelliJ IDEA properties\n'
366                         'idea.max.intellisense.filesize=100000')
367        cfg = config.IdeaProperties(IdeaPropertiesUnittests._CONFIG_DIR)
368        cfg._reset_max_file_size()
369        generated_file = os.path.join(IdeaPropertiesUnittests._CONFIG_DIR,
370                                      cfg._PROPERTIES_FILE)
371        with open(generated_file) as properties_file:
372            generated_content = properties_file.read()
373        self.assertEqual(expected_data, generated_content)
374
375    @mock.patch.object(common_util, 'file_generate')
376    @mock.patch.object(common_util, 'read_file_content')
377    def test_no_reset_max_file_size(self, mock_content, mock_gen_file):
378        """Test when the file size is larger than 100000."""
379        mock_content.return_value = ('# custom IntelliJ IDEA properties\n'
380                                     'idea.max.intellisense.filesize=110000')
381        cfg = config.IdeaProperties(IdeaPropertiesUnittests._CONFIG_DIR)
382        cfg._reset_max_file_size()
383        self.assertFalse(mock_gen_file.called)
384
385    @mock.patch.object(config.IdeaProperties, '_reset_max_file_size')
386    @mock.patch.object(config.IdeaProperties, '_set_default_idea_properties')
387    @mock.patch('os.path.exists')
388    def test_set_idea_properties_called(self, mock_file_exists,
389                                        mock_set_default,
390                                        mock_reset_file_size):
391        """Test _set_default_idea_properties() method is called."""
392        mock_file_exists.return_value = False
393        cfg = config.IdeaProperties(IdeaPropertiesUnittests._CONFIG_DIR)
394        cfg.set_max_file_size()
395        self.assertTrue(mock_set_default.called)
396        self.assertFalse(mock_reset_file_size.called)
397
398    @mock.patch.object(config.IdeaProperties, '_reset_max_file_size')
399    @mock.patch.object(config.IdeaProperties, '_set_default_idea_properties')
400    @mock.patch('os.path.exists')
401    def test_reset_properties_called(self, mock_file_exists, mock_set_default,
402                                     mock_reset_file_size):
403        """Test _reset_max_file_size() method is called."""
404        mock_file_exists.return_value = True
405        cfg = config.IdeaProperties(IdeaPropertiesUnittests._CONFIG_DIR)
406        cfg.set_max_file_size()
407        self.assertFalse(mock_set_default.called)
408        self.assertTrue(mock_reset_file_size.called)
409
410
411if __name__ == '__main__':
412    unittest.main()
413