Apply SIGINT restoration to new module layout
[wifi-radar.git] / test / unit / config.py
blob1eb297087217f68cba613f65f87e53d922104a6d
1 # test.config - tests for configuration management
3 # Part of WiFi Radar: A utility for managing WiFi profiles on GNU/Linux.
5 # Copyright (C) 2014 Sean Robinson <robinson@tuxfamily.org>
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; version 2 of the License.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License in LICENSE.GPL for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to:
18 # Free Software Foundation, Inc.
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 from __future__ import unicode_literals
25 import codecs
26 from configparser import NoOptionError, NoSectionError
27 from io import StringIO
28 import unittest
30 import mock
32 from wifiradar.config import (make_section_name,
33 ConfigManager, ConfigFileManager)
34 from wifiradar.misc import PYVERSION, NoDeviceError
37 class TestConfigManager(unittest.TestCase):
38 def setUp(self):
39 self.config = ConfigManager()
41 @mock.patch('wifiradar.config.Popen')
42 def test_get_network_device(self, mock_popen):
43 """ Test get_network_device. """
44 self.config.set_opt('GENERAL', 'interface', 'wlan0')
45 self.assertEqual('wlan0', self.config.get_network_device())
47 # Test successful auto-detected interface.
48 mock_popen_instance = mock.MagicMock()
49 mock_popen_instance.stdout.__iter__.return_value = [
50 'wlan0 IEEE 802.11abgn ESSID:"WinterPalace"\n',
51 ' Mode:Managed Frequency:5.26 GHz Access Point: 00:00:00:00:00:00\n',
52 ' Tx-Power=15 dBm\n',
53 ' Retry long limit:7 RTS thr:off Fragment thr:off\n',
54 ' Power Management:off\n']
55 mock_popen.return_value = mock_popen_instance
56 self.config.set_opt('GENERAL', 'interface', 'auto_detect')
57 self.config.set_opt('GENERAL', 'iwconfig_command', 'mock_iwconfig')
58 self.assertEqual('wlan0', self.config.get_network_device())
60 # Test failed auto-detected interface.
61 mock_popen_instance = mock.MagicMock()
62 mock_popen_instance.stdout.__iter__.return_value = [
63 'lo no wireless extensions.\n',
64 '\n',
65 'eth0 no wireless extensions.\n',
66 '\n']
67 mock_popen.return_value = mock_popen_instance
68 self.config.set_opt('GENERAL', 'interface', 'auto_detect')
69 self.config.set_opt('GENERAL', 'iwconfig_command', 'mock_iwconfig')
70 self.assertRaises(NoDeviceError,
71 self.config.get_network_device)
73 def test_set_get_opt(self):
74 """ Test set_opt and get_opt methods. """
75 self.assertEqual(self.config.sections(), [])
76 self.config.set_opt('TEST_SECTION', 'str_opt', 'str_value')
77 self.assertEqual('str_value',
78 self.config.get_opt('TEST_SECTION', 'str_opt'))
80 # Check for expected exceptions in set_opt.
81 self.assertRaises(TypeError, self.config.set_opt,
82 'TEST_SECTION', 'str_opt', True)
83 self.assertRaises(TypeError, self.config.set_opt,
84 'TEST_SECTION', 'str_opt', 0)
85 self.assertRaises(TypeError, self.config.set_opt,
86 'TEST_SECTION', 'str_opt', 1.0)
88 # Check for expected exceptions in get_opt.
89 self.assertRaises(NoSectionError, self.config.get_opt,
90 'UNKNOWN_SECTION', 'str_opt')
91 self.assertRaises(NoOptionError, self.config.get_opt,
92 'TEST_SECTION', 'unknown_opt')
94 def test_set_get_bool_opt(self):
95 """ Test set_bool_opt and get_opt_as_bool methods. """
96 self.assertEqual(self.config.sections(), [])
97 # Alternate True/False tests in case set_bool_opt does not change
98 # the value in 'bool_opt'
100 # Check boolean conversion.
101 self.config.set_bool_opt('TEST_SECTION', 'bool_opt', True)
102 self.assertEqual(True,
103 self.config.get_opt_as_bool('TEST_SECTION', 'bool_opt'))
104 self.config.set_bool_opt('TEST_SECTION', 'bool_opt', False)
105 self.assertEqual(False,
106 self.config.get_opt_as_bool('TEST_SECTION', 'bool_opt'))
108 # Check successful integer conversion.
109 self.config.set_bool_opt('TEST_SECTION', 'bool_opt', 1)
110 self.assertEqual(True,
111 self.config.get_opt_as_bool('TEST_SECTION', 'bool_opt'))
112 self.config.set_bool_opt('TEST_SECTION', 'bool_opt', 0)
113 self.assertEqual(False,
114 self.config.get_opt_as_bool('TEST_SECTION', 'bool_opt'))
116 # Check string conversion.
117 self.config.set_bool_opt('TEST_SECTION', 'bool_opt', 'True')
118 self.assertEqual(True,
119 self.config.get_opt_as_bool('TEST_SECTION', 'bool_opt'))
120 self.config.set_bool_opt('TEST_SECTION', 'bool_opt', 'False')
121 self.assertEqual(False,
122 self.config.get_opt_as_bool('TEST_SECTION', 'bool_opt'))
124 # extra possible values
125 self.config.set_bool_opt('TEST_SECTION', 'bool_opt', 2)
126 self.assertEqual(True,
127 self.config.get_opt_as_bool('TEST_SECTION', 'bool_opt'))
129 # Check for expected exceptions in set_bool_opt.
130 self.assertRaises(ValueError, self.config.set_bool_opt,
131 'TEST_SECTION', 'bool_opt', -1)
132 self.assertRaises(ValueError, self.config.set_bool_opt,
133 'TEST_SECTION', 'bool_opt', 'Not False')
134 self.assertRaises(ValueError, self.config.set_bool_opt,
135 'TEST_SECTION', 'bool_opt', 1.0)
137 # Check for expected exceptions in get_opt_as_bool.
138 self.config.set_opt('TEST_SECTION', 'bool_opt', 'spam')
139 self.assertRaises(ValueError, self.config.get_opt_as_bool,
140 'TEST_SECTION', 'bool_opt')
142 def test_set_get_int_opt(self):
143 """ Test set_int_opt and get_opt_as_int methods. """
144 self.assertEqual(self.config.sections(), [])
145 self.config.set_int_opt('TEST_SECTION', 'int_opt', 42)
146 self.assertEqual(42,
147 self.config.get_opt_as_int('TEST_SECTION', 'int_opt'))
149 # Check for expected exceptions in set_int_opt.
150 # Boolean values are integers, so do not try to find an exception.
151 self.assertRaises(TypeError, self.config.set_int_opt,
152 'TEST_SECTION', 'int_opt', 'eggs')
153 self.assertRaises(TypeError, self.config.set_int_opt,
154 'TEST_SECTION', 'int_opt', 1.0)
156 # Check for expected exceptions in get_opt_as_int.
157 self.config.set_opt('TEST_SECTION', 'int_opt', 'spam')
158 self.assertRaises(ValueError, self.config.get_opt_as_int,
159 'TEST_SECTION', 'int_opt')
161 def test_set_get_float_opt(self):
162 """ Test set_float_opt and get_opt_as_float methods. """
163 self.assertEqual(self.config.sections(), [])
164 self.config.set_float_opt('TEST_SECTION', 'float_opt', 42)
165 self.assertEqual(42.0,
166 self.config.get_opt_as_float('TEST_SECTION', 'float_opt'))
167 self.config.set_float_opt('TEST_SECTION', 'float_opt', 3.0)
168 self.assertEqual(3.0,
169 self.config.get_opt_as_float('TEST_SECTION', 'float_opt'))
171 # Check for expected exceptions in set_float_opt.
172 # Boolean values are integers, so do not try to find an exception.
173 self.assertRaises(TypeError, self.config.set_float_opt,
174 'TEST_SECTION', 'float_opt', 'eggs')
176 # Check for expected exceptions in get_opt_as_float.
177 self.config.set_opt('TEST_SECTION', 'float_opt', 'spam')
178 self.assertRaises(ValueError, self.config.get_opt_as_float,
179 'TEST_SECTION', 'float_opt')
181 def test_set_section(self):
182 """ Test set_section method. """
183 self.assertEqual(self.config.sections(), [])
185 options = {'bool_opt': True, 'int_opt': 42, 'float_opt': 3.1415,
186 'str_opt': 'eggs and spam'}
187 self.config.set_section('TEST_SECTION', options)
188 self.assertEqual(True,
189 self.config.get_opt_as_bool('TEST_SECTION', 'bool_opt'))
190 self.assertEqual(42,
191 self.config.get_opt_as_int('TEST_SECTION', 'int_opt'))
192 self.assertEqual(3.1415,
193 self.config.get_opt_as_float('TEST_SECTION', 'float_opt'))
194 self.assertEqual('eggs and spam',
195 self.config.get_opt('TEST_SECTION', 'str_opt'))
197 def test_profiles(self):
198 """ Test profiles method. """
199 self.assertEqual(self.config.sections(), [])
201 self.config.add_section('TEST_SECTION')
202 self.config.add_section('TEST_AP01:00:01:02:03:04:05')
203 self.config.add_section('spam.com:AA:BB:CC:DD:EE:FF')
205 self.assertEqual(['TEST_AP01:00:01:02:03:04:05',
206 'spam.com:AA:BB:CC:DD:EE:FF'],
207 self.config.profiles())
209 def test_update(self):
210 """ Test update method. """
211 items_orig = dict((('eggs', 'spam'), ('ham', 'spam and spam')))
212 items_copy = dict((('eggs', 'spam and toast'),
213 ('ham', 'spam and spam and toast')))
214 # Set up an original ConfigManager.
215 self.config.set_section('DEFAULT', items_orig)
216 self.config.set_section('SECT01', items_orig)
217 self.config.set_section('SECT02', items_orig)
218 self.config.set_section('SECT03', items_orig)
219 self.config.set_section('WinterPalace:00:00:00:00:00:00', items_orig)
220 self.config.set_section('SummerKingdom:', items_orig)
221 # Set up a copy ConfigManager.
222 config_copy = ConfigManager(defaults=items_copy)
223 config_copy.set_section('SECT01', items_copy)
224 config_copy.set_section('SECT02', items_copy)
225 # Update the original from the copy.
226 self.config.update(config_copy)
227 # Check that sections in config_copy have changed.
228 for section in config_copy.sections():
229 self.assertEqual(self.config.items(section), items_copy)
230 # Check that the profiles and extra sections are unchanged.
231 for section in self.config.profiles():
232 self.assertEqual(self.config.items(section), items_orig)
233 self.assertEqual(self.config.items('SECT03'), items_orig)
235 def test_copy(self):
236 """ Test copy method. """
237 # Copy just the DEFAULT section.
238 defaults = [('eggs', 'spam'), ('ham', 'spam and spam')]
239 orig = ConfigManager(defaults=dict(defaults))
240 copy = orig.copy()
241 self.assertEqual(copy.items('DEFAULT'), orig.items('DEFAULT'))
242 # Copy multiple sections with profiles.
243 items = dict((('eggs', 'spam'), ('ham', 'spam and spam')))
244 orig = ConfigManager()
245 orig.set_section('SECT01', items)
246 orig.set_section('SECT02', items)
247 orig.set_section('WinterPalace:00:00:00:00:00:00', items)
248 orig.set_section('SummerKingdom:', items)
249 copy = orig.copy(profiles=True)
250 for section in orig.sections():
251 self.assertEqual(copy.items(section), orig.items(section))
252 # Copy multiple sections without profiles.
253 copy = orig.copy(profiles=False)
254 orig.remove_section('WinterPalace:00:00:00:00:00:00')
255 orig.remove_section('SummerKingdom:')
256 for section in orig.sections():
257 self.assertEqual(copy.items(section), orig.items(section))
260 class TestConfigFileManager(unittest.TestCase):
261 def setUp(self):
262 self.test_conf_file = './test/data/wifi-radar-test.conf'
263 self.GENERAL = {
264 'auto_profile_order': "[u'WinterPalace:00:09:5B:D5:03:4A']",
265 'commit_required': 'False',
266 'ifconfig_command': '/sbin/ifconfig',
267 'ifup_required': 'False',
268 'interface': 'auto_detect',
269 'iwconfig_command': '/sbin/iwconfig',
270 'iwlist_command': '/sbin/iwlist',
271 'logfile': '/var/log/wifi-radar.log',
272 'loglevel': '50',
273 'route_command': '/sbin/route',
274 'version': '0.0.0'}
275 self.DHCP = {'args': '-D -o -i dhcp_client -t ${timeout}',
276 'command': '/sbin/dhcpcd',
277 'kill_args': '-k',
278 'pidfile': '/etc/dhcpc/dhcpcd-${GENERAL:interface}.pid',
279 'timeout': '30'}
280 self.WPA = {'args': '-B -i ${GENERAL:interface} -c ${configuration} '
281 '-D ${driver} -P ${pidfile}',
282 'command': '/usr/sbin/wpa_supplicant',
283 'configuration': '/etc/wpa_supplicant.conf',
284 'driver': 'wext',
285 'kill_command': '',
286 'pidfile': '/var/run/wpa_supplicant.pid'}
287 self.WINTERPALACE = {'available': 'False',
288 'bssid': '00:09:5B:D5:03:4A',
289 'channel': 'auto',
290 'con_postscript': '',
291 'con_prescript': '',
292 'dis_postscript': '',
293 'dis_prescript': '',
294 'dns1': '',
295 'dns2': '',
296 'domain': '',
297 'encrypted': 'True',
298 'essid': 'WinterPalace',
299 'gateway': '',
300 'ip': '',
301 'key': '',
302 'known': 'True',
303 'mode': 'auto',
304 'netmask': '',
305 'protocol': 'g',
306 'roaming': 'False',
307 'security': 'none',
308 'signal': '-193',
309 'use_dhcp': 'True',
310 'wep_mode': 'none',
311 'wpa_psk': ''}
313 def test_read(self):
314 """ Test read method. """
315 self.config_file = ConfigFileManager(self.test_conf_file)
316 config = self.config_file.read()
317 self.assertEqual(config.items('GENERAL', raw=True), self.GENERAL)
318 self.assertEqual(config.items('DHCP', raw=True), self.DHCP)
319 self.assertEqual(config.items('WPA', raw=True), self.WPA)
320 self.assertEqual(config.items('WinterPalace:00:09:5B:D5:03:4A',
321 raw=True), self.WINTERPALACE)
322 self.assertEqual(config.auto_profile_order,
323 [u'WinterPalace:00:09:5B:D5:03:4A'])
325 def test_read_failures(self):
326 """ Test failure modes in the read method. """
327 # Missing file.
328 self.config_file = ConfigFileManager(
329 self.test_conf_file.replace('wifi-radar-test', 'missing_file'))
330 with self.assertRaises(IOError) as e:
331 config = self.config_file.read()
332 self.assertEqual(2, e.exception)
333 # File whose permissions do not allow reading.
334 self.config_file = ConfigFileManager(
335 self.test_conf_file.replace('-test', '-unreadable'))
336 with self.assertRaises(IOError) as e:
337 config = self.config_file.read()
338 self.assertEqual(13, e.exception)
340 def test_read_string(self):
341 """ Test read_string method. """
342 with codecs.open(self.test_conf_file, 'r', encoding='utf8') as f:
343 test_string = f.read()
344 config_file = ConfigFileManager('')
345 config = config_file.read_string(test_string)
346 self.assertEqual(config.items('GENERAL', raw=True), self.GENERAL)
347 self.assertEqual(config.items('DHCP', raw=True), self.DHCP)
348 self.assertEqual(config.items('WPA', raw=True), self.WPA)
349 self.assertEqual(config.items('WinterPalace:00:09:5B:D5:03:4A',
350 raw=True), self.WINTERPALACE)
351 self.assertEqual(config.auto_profile_order,
352 [u'WinterPalace:00:09:5B:D5:03:4A'])
354 @mock.patch('wifiradar.config.move')
355 @mock.patch('codecs.open')
356 @mock.patch('tempfile.mkstemp')
357 def test_write(self, mock_mkstemp, mock_open, mock_move):
358 """ Test write method. """
359 mock_temp_file = 'mock_file_tempXXXXXX'
360 test_file = StringIO()
361 test_file._close = test_file.close
362 test_file.close = lambda: None
363 mock_mkstemp.return_value = (1, mock_temp_file)
364 mock_open.return_value = test_file
365 # Set up a copy ConfigManager.
366 config_copy = ConfigManager()
367 config_copy.set_section('GENERAL', self.GENERAL)
368 config_copy.set_section('DHCP', self.DHCP)
369 config_copy.set_section('WPA', self.WPA)
370 config_copy.set_section('WinterPalace:00:09:5B:D5:03:4A',
371 self.WINTERPALACE)
372 config_copy.auto_profile_order = [u'WinterPalace:00:09:5B:D5:03:4A']
373 self.config_file = ConfigFileManager('mock_file')
374 self.config_file.write(config_copy)
375 with open(self.test_conf_file, 'r') as f:
376 test_data = f.read()
377 self.assertEqual(test_file.getvalue(), test_data)
378 test_file._close()
379 mock_mkstemp.assert_called_once_with(prefix='wifi-radar.conf.')
380 mock_open.assert_called_once_with(mock_temp_file, 'w', encoding='utf8')
381 mock_move.assert_called_once_with(mock_temp_file, 'mock_file')
384 class TestConfigFunctions(unittest.TestCase):
386 def test_make_section_name(self):
387 """ Test make_section_name function. """
388 # Check single AP profiles.
389 self.assertEqual('essid:bssid',
390 make_section_name('essid', 'bssid'))
391 self.assertEqual('TEST_AP01:00:01:02:03:04:05',
392 make_section_name('TEST_AP01', '00:01:02:03:04:05'))
393 self.assertEqual('spam.com:AA:BB:CC:DD:EE:FF',
394 make_section_name('spam.com', 'AA:BB:CC:DD:EE:FF'))
395 self.assertEqual('eggs_and_ham:11:BB:CC:DD:EE:FF',
396 make_section_name('eggs_and_ham', '11:BB:CC:DD:EE:FF'))
397 self.assertEqual('eggs:and:spam:22:BB:CC:DD:EE:FF',
398 make_section_name('eggs:and:spam', '22:BB:CC:DD:EE:FF'))
399 # Check roaming profiles.
400 self.assertEqual('essid:',
401 make_section_name('essid', ''))
402 self.assertEqual('TEST_AP01:',
403 make_section_name('TEST_AP01', ''))
404 self.assertEqual('spam.com:',
405 make_section_name('spam.com', ''))
406 self.assertEqual('eggs_and_ham:',
407 make_section_name('eggs_and_ham', ''))
408 self.assertEqual('eggs:and:spam:',
409 make_section_name('eggs:and:spam', ''))