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
31 from wifiradar
.config
import (make_section_name
,
32 ConfigManager
, ConfigFileManager
)
33 from wifiradar
.misc
import PYVERSION
, NoDeviceError
36 class TestConfigManager(unittest
.TestCase
):
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',
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',
64 'eth0 no wireless extensions.\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)
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'))
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
)
235 """ Test copy method. """
236 # Copy just the DEFAULT section.
237 defaults
= [('eggs', 'spam'), ('ham', 'spam and spam')]
238 orig
= ConfigManager(defaults
=dict(defaults
))
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
):
261 self
.test_conf_file
= './test/data/wifi-radar-test.conf'
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',
272 'route_command': '/sbin/route',
274 self
.DHCP
= {'args': '-D -o -i dhcp_client -t ${timeout}',
275 'command': '/sbin/dhcpcd',
277 'pidfile': '/etc/dhcpc/dhcpcd-${GENERAL:interface}.pid',
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',
285 'pidfile': '/var/run/wpa_supplicant.pid'}
286 self
.WINTERPALACE
= {'available': 'False',
287 'bssid': '00:09:5B:D5:03:4A',
289 'con_postscript': '',
291 'dis_postscript': '',
297 'essid': 'WinterPalace',
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 @mock.patch('wifiradar.config.move')
325 @mock.patch('codecs.open')
326 @mock.patch('tempfile.mkstemp')
327 def test_write(self
, mock_mkstemp
, mock_open
, mock_move
):
328 """ Test write method. """
329 mock_temp_file
= 'mock_file_tempXXXXXX'
330 test_file
= StringIO()
331 test_file
._close
= test_file
.close
332 test_file
.close
= lambda: None
333 mock_mkstemp
.return_value
= (1, mock_temp_file
)
334 mock_open
.return_value
= test_file
335 # Set up a copy ConfigManager.
336 config_copy
= ConfigManager()
337 config_copy
.set_section('GENERAL', self
.GENERAL
)
338 config_copy
.set_section('DHCP', self
.DHCP
)
339 config_copy
.set_section('WPA', self
.WPA
)
340 config_copy
.set_section('WinterPalace:00:09:5B:D5:03:4A',
342 config_copy
.auto_profile_order
= [u
'WinterPalace:00:09:5B:D5:03:4A']
343 self
.config_file
= ConfigFileManager('mock_file')
344 self
.config_file
.write(config_copy
)
345 with
open(self
.test_conf_file
, 'r') as f
:
347 self
.assertEqual(test_file
.getvalue(), test_data
)
349 mock_mkstemp
.assert_called_once_with(prefix
='wifi-radar.conf.')
350 mock_open
.assert_called_once_with(mock_temp_file
, 'w', encoding
='utf8')
351 mock_move
.assert_called_once_with(mock_temp_file
, 'mock_file')
354 class TestConfigFunctions(unittest
.TestCase
):
356 def test_make_section_name(self
):
357 """ Test make_section_name function. """
358 # Check single AP profiles.
359 self
.assertEqual('essid:bssid',
360 make_section_name('essid', 'bssid'))
361 self
.assertEqual('TEST_AP01:00:01:02:03:04:05',
362 make_section_name('TEST_AP01', '00:01:02:03:04:05'))
363 self
.assertEqual('spam.com:AA:BB:CC:DD:EE:FF',
364 make_section_name('spam.com', 'AA:BB:CC:DD:EE:FF'))
365 self
.assertEqual('eggs_and_ham:11:BB:CC:DD:EE:FF',
366 make_section_name('eggs_and_ham', '11:BB:CC:DD:EE:FF'))
367 self
.assertEqual('eggs:and:spam:22:BB:CC:DD:EE:FF',
368 make_section_name('eggs:and:spam', '22:BB:CC:DD:EE:FF'))
369 # Check roaming profiles.
370 self
.assertEqual('essid:',
371 make_section_name('essid', ''))
372 self
.assertEqual('TEST_AP01:',
373 make_section_name('TEST_AP01', ''))
374 self
.assertEqual('spam.com:',
375 make_section_name('spam.com', ''))
376 self
.assertEqual('eggs_and_ham:',
377 make_section_name('eggs_and_ham', ''))
378 self
.assertEqual('eggs:and:spam:',
379 make_section_name('eggs:and:spam', ''))