Only run the rest of WiFi Radar if the configuration was created
[wifi-radar.git] / test / unit / config.py
blob5f934e12adbfb6958de00319af7fcf6e9599707d
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 from configparser import NoOptionError, NoSectionError
26 from io import StringIO
27 import unittest
29 import mock
31 from wifiradar.config import (make_section_name,
32 ConfigManager, ConfigFileManager)
33 from wifiradar.misc import PYVERSION, NoDeviceError
36 class TestConfigManager(unittest.TestCase):
37 def setUp(self):
38 self.config = ConfigManager()
40 @mock.patch('wifiradar.config.Popen')
41 def test_get_network_device(self, mock_popen):
42 """ Test get_network_device. """
43 self.config.set_opt('GENERAL', 'interface', 'wlan0')
44 self.assertEqual('wlan0', self.config.get_network_device())
46 # Test successful auto-detected interface.
47 mock_popen_instance = mock.MagicMock()
48 mock_popen_instance.stdout.__iter__.return_value = [
49 'wlan0 IEEE 802.11abgn ESSID:"WinterPalace"\n',
50 ' Mode:Managed Frequency:5.26 GHz Access Point: 00:00:00:00:00:00\n',
51 ' Tx-Power=15 dBm\n',
52 ' Retry long limit:7 RTS thr:off Fragment thr:off\n',
53 ' Power Management:off\n']
54 mock_popen.return_value = mock_popen_instance
55 self.config.set_opt('GENERAL', 'interface', 'auto_detect')
56 self.config.set_opt('GENERAL', 'iwconfig_command', 'mock_iwconfig')
57 self.assertEqual('wlan0', self.config.get_network_device())
59 # Test failed auto-detected interface.
60 mock_popen_instance = mock.MagicMock()
61 mock_popen_instance.stdout.__iter__.return_value = [
62 'lo no wireless extensions.\n',
63 '\n',
64 'eth0 no wireless extensions.\n',
65 '\n']
66 mock_popen.return_value = mock_popen_instance
67 self.config.set_opt('GENERAL', 'interface', 'auto_detect')
68 self.config.set_opt('GENERAL', 'iwconfig_command', 'mock_iwconfig')
69 self.assertRaises(NoDeviceError,
70 self.config.get_network_device)
72 def test_set_get_opt(self):
73 """ Test set_opt and get_opt methods. """
74 self.assertEqual(self.config.sections(), [])
75 self.config.set_opt('TEST_SECTION', 'str_opt', 'str_value')
76 self.assertEqual('str_value',
77 self.config.get_opt('TEST_SECTION', 'str_opt'))
79 # Check for expected exceptions in set_opt.
80 self.assertRaises(TypeError, self.config.set_opt,
81 'TEST_SECTION', 'str_opt', True)
82 self.assertRaises(TypeError, self.config.set_opt,
83 'TEST_SECTION', 'str_opt', 0)
84 self.assertRaises(TypeError, self.config.set_opt,
85 'TEST_SECTION', 'str_opt', 1.0)
87 # Check for expected exceptions in get_opt.
88 self.assertRaises(NoSectionError, self.config.get_opt,
89 'UNKNOWN_SECTION', 'str_opt')
90 self.assertRaises(NoOptionError, self.config.get_opt,
91 'TEST_SECTION', 'unknown_opt')
93 def test_set_get_bool_opt(self):
94 """ Test set_bool_opt and get_opt_as_bool methods. """
95 self.assertEqual(self.config.sections(), [])
96 # Alternate True/False tests in case set_bool_opt does not change
97 # the value in 'bool_opt'
99 # Check boolean conversion.
100 self.config.set_bool_opt('TEST_SECTION', 'bool_opt', True)
101 self.assertEqual(True,
102 self.config.get_opt_as_bool('TEST_SECTION', 'bool_opt'))
103 self.config.set_bool_opt('TEST_SECTION', 'bool_opt', False)
104 self.assertEqual(False,
105 self.config.get_opt_as_bool('TEST_SECTION', 'bool_opt'))
107 # Check successful integer conversion.
108 self.config.set_bool_opt('TEST_SECTION', 'bool_opt', 1)
109 self.assertEqual(True,
110 self.config.get_opt_as_bool('TEST_SECTION', 'bool_opt'))
111 self.config.set_bool_opt('TEST_SECTION', 'bool_opt', 0)
112 self.assertEqual(False,
113 self.config.get_opt_as_bool('TEST_SECTION', 'bool_opt'))
115 # Check string conversion.
116 self.config.set_bool_opt('TEST_SECTION', 'bool_opt', 'True')
117 self.assertEqual(True,
118 self.config.get_opt_as_bool('TEST_SECTION', 'bool_opt'))
119 self.config.set_bool_opt('TEST_SECTION', 'bool_opt', 'False')
120 self.assertEqual(False,
121 self.config.get_opt_as_bool('TEST_SECTION', 'bool_opt'))
123 # extra possible values
124 self.config.set_bool_opt('TEST_SECTION', 'bool_opt', 2)
125 self.assertEqual(True,
126 self.config.get_opt_as_bool('TEST_SECTION', 'bool_opt'))
128 # Check for expected exceptions in set_bool_opt.
129 self.assertRaises(ValueError, self.config.set_bool_opt,
130 'TEST_SECTION', 'bool_opt', -1)
131 self.assertRaises(ValueError, self.config.set_bool_opt,
132 'TEST_SECTION', 'bool_opt', 'Not False')
133 self.assertRaises(ValueError, self.config.set_bool_opt,
134 'TEST_SECTION', 'bool_opt', 1.0)
136 # Check for expected exceptions in get_opt_as_bool.
137 self.config.set_opt('TEST_SECTION', 'bool_opt', 'spam')
138 self.assertRaises(ValueError, self.config.get_opt_as_bool,
139 'TEST_SECTION', 'bool_opt')
141 def test_set_get_int_opt(self):
142 """ Test set_int_opt and get_opt_as_int methods. """
143 self.assertEqual(self.config.sections(), [])
144 self.config.set_int_opt('TEST_SECTION', 'int_opt', 42)
145 self.assertEqual(42,
146 self.config.get_opt_as_int('TEST_SECTION', 'int_opt'))
148 # Check for expected exceptions in set_int_opt.
149 # Boolean values are integers, so do not try to find an exception.
150 self.assertRaises(TypeError, self.config.set_int_opt,
151 'TEST_SECTION', 'int_opt', 'eggs')
152 self.assertRaises(TypeError, self.config.set_int_opt,
153 'TEST_SECTION', 'int_opt', 1.0)
155 # Check for expected exceptions in get_opt_as_int.
156 self.config.set_opt('TEST_SECTION', 'int_opt', 'spam')
157 self.assertRaises(ValueError, self.config.get_opt_as_int,
158 'TEST_SECTION', 'int_opt')
160 def test_set_get_float_opt(self):
161 """ Test set_float_opt and get_opt_as_float methods. """
162 self.assertEqual(self.config.sections(), [])
163 self.config.set_float_opt('TEST_SECTION', 'float_opt', 42)
164 self.assertEqual(42.0,
165 self.config.get_opt_as_float('TEST_SECTION', 'float_opt'))
166 self.config.set_float_opt('TEST_SECTION', 'float_opt', 3.0)
167 self.assertEqual(3.0,
168 self.config.get_opt_as_float('TEST_SECTION', 'float_opt'))
170 # Check for expected exceptions in set_float_opt.
171 # Boolean values are integers, so do not try to find an exception.
172 self.assertRaises(TypeError, self.config.set_float_opt,
173 'TEST_SECTION', 'float_opt', 'eggs')
175 # Check for expected exceptions in get_opt_as_float.
176 self.config.set_opt('TEST_SECTION', 'float_opt', 'spam')
177 self.assertRaises(ValueError, self.config.get_opt_as_float,
178 'TEST_SECTION', 'float_opt')
180 def test_set_section(self):
181 """ Test set_section method. """
182 self.assertEqual(self.config.sections(), [])
184 options = {'bool_opt': True, 'int_opt': 42, 'float_opt': 3.1415,
185 'str_opt': 'eggs and spam'}
186 self.config.set_section('TEST_SECTION', options)
187 self.assertEqual(True,
188 self.config.get_opt_as_bool('TEST_SECTION', 'bool_opt'))
189 self.assertEqual(42,
190 self.config.get_opt_as_int('TEST_SECTION', 'int_opt'))
191 self.assertEqual(3.1415,
192 self.config.get_opt_as_float('TEST_SECTION', 'float_opt'))
193 self.assertEqual('eggs and spam',
194 self.config.get_opt('TEST_SECTION', 'str_opt'))
196 def test_profiles(self):
197 """ Test profiles method. """
198 self.assertEqual(self.config.sections(), [])
200 self.config.add_section('TEST_SECTION')
201 self.config.add_section('TEST_AP01:00:01:02:03:04:05')
202 self.config.add_section('spam.com:AA:BB:CC:DD:EE:FF')
204 self.assertEqual(['TEST_AP01:00:01:02:03:04:05',
205 'spam.com:AA:BB:CC:DD:EE:FF'],
206 self.config.profiles())
208 def test_update(self):
209 """ Test update method. """
210 items_orig = dict((('eggs', 'spam'), ('ham', 'spam and spam')))
211 items_copy = dict((('eggs', 'spam and toast'),
212 ('ham', 'spam and spam and toast')))
213 # Set up an original ConfigManager.
214 self.config.set_section('DEFAULT', items_orig)
215 self.config.set_section('SECT01', items_orig)
216 self.config.set_section('SECT02', items_orig)
217 self.config.set_section('SECT03', items_orig)
218 self.config.set_section('WinterPalace:00:00:00:00:00:00', items_orig)
219 self.config.set_section('SummerKingdom:', items_orig)
220 # Set up a copy ConfigManager.
221 config_copy = ConfigManager(defaults=items_copy)
222 config_copy.set_section('SECT01', items_copy)
223 config_copy.set_section('SECT02', items_copy)
224 # Update the original from the copy.
225 self.config.update(config_copy)
226 # Check that sections in config_copy have changed.
227 for section in config_copy.sections():
228 self.assertEqual(self.config.items(section), items_copy)
229 # Check that the profiles and extra sections are unchanged.
230 for section in self.config.profiles():
231 self.assertEqual(self.config.items(section), items_orig)
232 self.assertEqual(self.config.items('SECT03'), items_orig)
234 def test_copy(self):
235 """ Test copy method. """
236 # Copy just the DEFAULT section.
237 defaults = [('eggs', 'spam'), ('ham', 'spam and spam')]
238 orig = ConfigManager(defaults=dict(defaults))
239 copy = orig.copy()
240 self.assertEqual(copy.items('DEFAULT'), orig.items('DEFAULT'))
241 # Copy multiple sections with profiles.
242 items = dict((('eggs', 'spam'), ('ham', 'spam and spam')))
243 orig = ConfigManager()
244 orig.set_section('SECT01', items)
245 orig.set_section('SECT02', items)
246 orig.set_section('WinterPalace:00:00:00:00:00:00', items)
247 orig.set_section('SummerKingdom:', items)
248 copy = orig.copy(profiles=True)
249 for section in orig.sections():
250 self.assertEqual(copy.items(section), orig.items(section))
251 # Copy multiple sections without profiles.
252 copy = orig.copy(profiles=False)
253 orig.remove_section('WinterPalace:00:00:00:00:00:00')
254 orig.remove_section('SummerKingdom:')
255 for section in orig.sections():
256 self.assertEqual(copy.items(section), orig.items(section))
259 class TestConfigFileManager(unittest.TestCase):
260 def setUp(self):
261 self.test_conf_file = './test/data/wifi-radar-test.conf'
262 self.GENERAL = {
263 'auto_profile_order': "[u'WinterPalace:00:09:5B:D5:03:4A']",
264 'commit_required': 'False',
265 'ifconfig_command': '/sbin/ifconfig',
266 'ifup_required': 'False',
267 'interface': 'auto_detect',
268 'iwconfig_command': '/sbin/iwconfig',
269 'iwlist_command': '/sbin/iwlist',
270 'logfile': '/var/log/wifi-radar.log',
271 'loglevel': '50',
272 'route_command': '/sbin/route',
273 'version': '0.0.0'}
274 self.DHCP = {'args': '-D -o -i dhcp_client -t ${timeout}',
275 'command': '/sbin/dhcpcd',
276 'kill_args': '-k',
277 'pidfile': '/etc/dhcpc/dhcpcd-${GENERAL:interface}.pid',
278 'timeout': '30'}
279 self.WPA = {'args': '-B -i ${GENERAL:interface} -c ${configuration} '
280 '-D ${driver} -P ${pidfile}',
281 'command': '/usr/sbin/wpa_supplicant',
282 'configuration': '/etc/wpa_supplicant.conf',
283 'driver': 'wext',
284 'kill_command': '',
285 'pidfile': '/var/run/wpa_supplicant.pid'}
286 self.WINTERPALACE = {'available': 'False',
287 'bssid': '00:09:5B:D5:03:4A',
288 'channel': 'auto',
289 'con_postscript': '',
290 'con_prescript': '',
291 'dis_postscript': '',
292 'dis_prescript': '',
293 'dns1': '',
294 'dns2': '',
295 'domain': '',
296 'encrypted': 'True',
297 'essid': 'WinterPalace',
298 'gateway': '',
299 'ip': '',
300 'key': '',
301 'known': 'True',
302 'mode': 'auto',
303 'netmask': '',
304 'protocol': 'g',
305 'roaming': 'False',
306 'security': 'none',
307 'signal': '-193',
308 'use_dhcp': 'True',
309 'wep_mode': 'none',
310 'wpa_psk': ''}
312 def test_read(self):
313 """ Test read method. """
314 self.config_file = ConfigFileManager(self.test_conf_file)
315 config = self.config_file.read()
316 self.assertEqual(config.items('GENERAL', raw=True), self.GENERAL)
317 self.assertEqual(config.items('DHCP', raw=True), self.DHCP)
318 self.assertEqual(config.items('WPA', raw=True), self.WPA)
319 self.assertEqual(config.items('WinterPalace:00:09:5B:D5:03:4A',
320 raw=True), self.WINTERPALACE)
321 self.assertEqual(config.auto_profile_order,
322 [u'WinterPalace:00:09:5B:D5:03:4A'])
324 def test_read_failures(self):
325 """ Test failure modes in the read method. """
326 # Missing file.
327 self.config_file = ConfigFileManager(
328 self.test_conf_file.replace('wifi-radar-test', 'missing_file'))
329 with self.assertRaises(IOError) as e:
330 config = self.config_file.read()
331 self.assertEqual(2, e.exception)
332 # File whose permissions do not allow reading.
333 self.config_file = ConfigFileManager(
334 self.test_conf_file.replace('-test', '-unreadable'))
335 with self.assertRaises(IOError) as e:
336 config = self.config_file.read()
337 self.assertEqual(13, e.exception)
339 @mock.patch('wifiradar.config.move')
340 @mock.patch('codecs.open')
341 @mock.patch('tempfile.mkstemp')
342 def test_write(self, mock_mkstemp, mock_open, mock_move):
343 """ Test write method. """
344 mock_temp_file = 'mock_file_tempXXXXXX'
345 test_file = StringIO()
346 test_file._close = test_file.close
347 test_file.close = lambda: None
348 mock_mkstemp.return_value = (1, mock_temp_file)
349 mock_open.return_value = test_file
350 # Set up a copy ConfigManager.
351 config_copy = ConfigManager()
352 config_copy.set_section('GENERAL', self.GENERAL)
353 config_copy.set_section('DHCP', self.DHCP)
354 config_copy.set_section('WPA', self.WPA)
355 config_copy.set_section('WinterPalace:00:09:5B:D5:03:4A',
356 self.WINTERPALACE)
357 config_copy.auto_profile_order = [u'WinterPalace:00:09:5B:D5:03:4A']
358 self.config_file = ConfigFileManager('mock_file')
359 self.config_file.write(config_copy)
360 with open(self.test_conf_file, 'r') as f:
361 test_data = f.read()
362 self.assertEqual(test_file.getvalue(), test_data)
363 test_file._close()
364 mock_mkstemp.assert_called_once_with(prefix='wifi-radar.conf.')
365 mock_open.assert_called_once_with(mock_temp_file, 'w', encoding='utf8')
366 mock_move.assert_called_once_with(mock_temp_file, 'mock_file')
369 class TestConfigFunctions(unittest.TestCase):
371 def test_make_section_name(self):
372 """ Test make_section_name function. """
373 # Check single AP profiles.
374 self.assertEqual('essid:bssid',
375 make_section_name('essid', 'bssid'))
376 self.assertEqual('TEST_AP01:00:01:02:03:04:05',
377 make_section_name('TEST_AP01', '00:01:02:03:04:05'))
378 self.assertEqual('spam.com:AA:BB:CC:DD:EE:FF',
379 make_section_name('spam.com', 'AA:BB:CC:DD:EE:FF'))
380 self.assertEqual('eggs_and_ham:11:BB:CC:DD:EE:FF',
381 make_section_name('eggs_and_ham', '11:BB:CC:DD:EE:FF'))
382 self.assertEqual('eggs:and:spam:22:BB:CC:DD:EE:FF',
383 make_section_name('eggs:and:spam', '22:BB:CC:DD:EE:FF'))
384 # Check roaming profiles.
385 self.assertEqual('essid:',
386 make_section_name('essid', ''))
387 self.assertEqual('TEST_AP01:',
388 make_section_name('TEST_AP01', ''))
389 self.assertEqual('spam.com:',
390 make_section_name('spam.com', ''))
391 self.assertEqual('eggs_and_ham:',
392 make_section_name('eggs_and_ham', ''))
393 self.assertEqual('eggs:and:spam:',
394 make_section_name('eggs:and:spam', ''))