1702
2044
self.assertIs(None, bzrdir_config.get_default_stack_on())
2047
class TestOldConfigHooks(tests.TestCaseWithTransport):
2050
super(TestOldConfigHooks, self).setUp()
2051
create_configs_with_file_option(self)
2053
def assertGetHook(self, conf, name, value):
2057
config.OldConfigHooks.install_named_hook('get', hook, None)
2059
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2060
self.assertLength(0, calls)
2061
actual_value = conf.get_user_option(name)
2062
self.assertEquals(value, actual_value)
2063
self.assertLength(1, calls)
2064
self.assertEquals((conf, name, value), calls[0])
2066
def test_get_hook_bazaar(self):
2067
self.assertGetHook(self.bazaar_config, 'file', 'bazaar')
2069
def test_get_hook_locations(self):
2070
self.assertGetHook(self.locations_config, 'file', 'locations')
2072
def test_get_hook_branch(self):
2073
# Since locations masks branch, we define a different option
2074
self.branch_config.set_user_option('file2', 'branch')
2075
self.assertGetHook(self.branch_config, 'file2', 'branch')
2077
def assertSetHook(self, conf, name, value):
2081
config.OldConfigHooks.install_named_hook('set', hook, None)
2083
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2084
self.assertLength(0, calls)
2085
conf.set_user_option(name, value)
2086
self.assertLength(1, calls)
2087
# We can't assert the conf object below as different configs use
2088
# different means to implement set_user_option and we care only about
2090
self.assertEquals((name, value), calls[0][1:])
2092
def test_set_hook_bazaar(self):
2093
self.assertSetHook(self.bazaar_config, 'foo', 'bazaar')
2095
def test_set_hook_locations(self):
2096
self.assertSetHook(self.locations_config, 'foo', 'locations')
2098
def test_set_hook_branch(self):
2099
self.assertSetHook(self.branch_config, 'foo', 'branch')
2101
def assertRemoveHook(self, conf, name, section_name=None):
2105
config.OldConfigHooks.install_named_hook('remove', hook, None)
2107
config.OldConfigHooks.uninstall_named_hook, 'remove', None)
2108
self.assertLength(0, calls)
2109
conf.remove_user_option(name, section_name)
2110
self.assertLength(1, calls)
2111
# We can't assert the conf object below as different configs use
2112
# different means to implement remove_user_option and we care only about
2114
self.assertEquals((name,), calls[0][1:])
2116
def test_remove_hook_bazaar(self):
2117
self.assertRemoveHook(self.bazaar_config, 'file')
2119
def test_remove_hook_locations(self):
2120
self.assertRemoveHook(self.locations_config, 'file',
2121
self.locations_config.location)
2123
def test_remove_hook_branch(self):
2124
self.assertRemoveHook(self.branch_config, 'file')
2126
def assertLoadHook(self, name, conf_class, *conf_args):
2130
config.OldConfigHooks.install_named_hook('load', hook, None)
2132
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2133
self.assertLength(0, calls)
2135
conf = conf_class(*conf_args)
2136
# Access an option to trigger a load
2137
conf.get_user_option(name)
2138
self.assertLength(1, calls)
2139
# Since we can't assert about conf, we just use the number of calls ;-/
2141
def test_load_hook_bazaar(self):
2142
self.assertLoadHook('file', config.GlobalConfig)
2144
def test_load_hook_locations(self):
2145
self.assertLoadHook('file', config.LocationConfig, self.tree.basedir)
2147
def test_load_hook_branch(self):
2148
self.assertLoadHook('file', config.BranchConfig, self.tree.branch)
2150
def assertSaveHook(self, conf):
2154
config.OldConfigHooks.install_named_hook('save', hook, None)
2156
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2157
self.assertLength(0, calls)
2158
# Setting an option triggers a save
2159
conf.set_user_option('foo', 'bar')
2160
self.assertLength(1, calls)
2161
# Since we can't assert about conf, we just use the number of calls ;-/
2163
def test_save_hook_bazaar(self):
2164
self.assertSaveHook(self.bazaar_config)
2166
def test_save_hook_locations(self):
2167
self.assertSaveHook(self.locations_config)
2169
def test_save_hook_branch(self):
2170
self.assertSaveHook(self.branch_config)
2173
class TestOldConfigHooksForRemote(tests.TestCaseWithTransport):
2174
"""Tests config hooks for remote configs.
2176
No tests for the remove hook as this is not implemented there.
2180
super(TestOldConfigHooksForRemote, self).setUp()
2181
self.transport_server = test_server.SmartTCPServer_for_testing
2182
create_configs_with_file_option(self)
2184
def assertGetHook(self, conf, name, value):
2188
config.OldConfigHooks.install_named_hook('get', hook, None)
2190
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2191
self.assertLength(0, calls)
2192
actual_value = conf.get_option(name)
2193
self.assertEquals(value, actual_value)
2194
self.assertLength(1, calls)
2195
self.assertEquals((conf, name, value), calls[0])
2197
def test_get_hook_remote_branch(self):
2198
remote_branch = branch.Branch.open(self.get_url('tree'))
2199
self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
2201
def test_get_hook_remote_bzrdir(self):
2202
remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
2203
conf = remote_bzrdir._get_config()
2204
conf.set_option('remotedir', 'file')
2205
self.assertGetHook(conf, 'file', 'remotedir')
2207
def assertSetHook(self, conf, name, value):
2211
config.OldConfigHooks.install_named_hook('set', hook, None)
2213
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2214
self.assertLength(0, calls)
2215
conf.set_option(value, name)
2216
self.assertLength(1, calls)
2217
# We can't assert the conf object below as different configs use
2218
# different means to implement set_user_option and we care only about
2220
self.assertEquals((name, value), calls[0][1:])
2222
def test_set_hook_remote_branch(self):
2223
remote_branch = branch.Branch.open(self.get_url('tree'))
2224
self.addCleanup(remote_branch.lock_write().unlock)
2225
self.assertSetHook(remote_branch._get_config(), 'file', 'remote')
2227
def test_set_hook_remote_bzrdir(self):
2228
remote_branch = branch.Branch.open(self.get_url('tree'))
2229
self.addCleanup(remote_branch.lock_write().unlock)
2230
remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
2231
self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
2233
def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
2237
config.OldConfigHooks.install_named_hook('load', hook, None)
2239
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2240
self.assertLength(0, calls)
2242
conf = conf_class(*conf_args)
2243
# Access an option to trigger a load
2244
conf.get_option(name)
2245
self.assertLength(expected_nb_calls, calls)
2246
# Since we can't assert about conf, we just use the number of calls ;-/
2248
def test_load_hook_remote_branch(self):
2249
remote_branch = branch.Branch.open(self.get_url('tree'))
2250
self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
2252
def test_load_hook_remote_bzrdir(self):
2253
remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
2254
# The config file doesn't exist, set an option to force its creation
2255
conf = remote_bzrdir._get_config()
2256
conf.set_option('remotedir', 'file')
2257
# We get one call for the server and one call for the client, this is
2258
# caused by the differences in implementations betwen
2259
# SmartServerBzrDirRequestConfigFile (in smart/bzrdir.py) and
2260
# SmartServerBranchGetConfigFile (in smart/branch.py)
2261
self.assertLoadHook(2 ,'file', remote.RemoteBzrDirConfig, remote_bzrdir)
2263
def assertSaveHook(self, conf):
2267
config.OldConfigHooks.install_named_hook('save', hook, None)
2269
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2270
self.assertLength(0, calls)
2271
# Setting an option triggers a save
2272
conf.set_option('foo', 'bar')
2273
self.assertLength(1, calls)
2274
# Since we can't assert about conf, we just use the number of calls ;-/
2276
def test_save_hook_remote_branch(self):
2277
remote_branch = branch.Branch.open(self.get_url('tree'))
2278
self.addCleanup(remote_branch.lock_write().unlock)
2279
self.assertSaveHook(remote_branch._get_config())
2281
def test_save_hook_remote_bzrdir(self):
2282
remote_branch = branch.Branch.open(self.get_url('tree'))
2283
self.addCleanup(remote_branch.lock_write().unlock)
2284
remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
2285
self.assertSaveHook(remote_bzrdir._get_config())
2288
class TestOption(tests.TestCase):
2290
def test_default_value(self):
2291
opt = config.Option('foo', default='bar')
2292
self.assertEquals('bar', opt.get_default())
2294
def test_callable_default_value(self):
2295
def bar_as_unicode():
2297
opt = config.Option('foo', default=bar_as_unicode)
2298
self.assertEquals('bar', opt.get_default())
2300
def test_default_value_from_env(self):
2301
opt = config.Option('foo', default='bar', default_from_env=['FOO'])
2302
self.overrideEnv('FOO', 'quux')
2303
# Env variable provides a default taking over the option one
2304
self.assertEquals('quux', opt.get_default())
2306
def test_first_default_value_from_env_wins(self):
2307
opt = config.Option('foo', default='bar',
2308
default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
2309
self.overrideEnv('FOO', 'foo')
2310
self.overrideEnv('BAZ', 'baz')
2311
# The first env var set wins
2312
self.assertEquals('foo', opt.get_default())
2314
def test_not_supported_list_default_value(self):
2315
self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
2317
def test_not_supported_object_default_value(self):
2318
self.assertRaises(AssertionError, config.Option, 'foo',
2321
def test_not_supported_callable_default_value_not_unicode(self):
2322
def bar_not_unicode():
2324
opt = config.Option('foo', default=bar_not_unicode)
2325
self.assertRaises(AssertionError, opt.get_default)
2328
class TestOptionConverterMixin(object):
2330
def assertConverted(self, expected, opt, value):
2331
self.assertEquals(expected, opt.convert_from_unicode(None, value))
2333
def assertWarns(self, opt, value):
2336
warnings.append(args[0] % args[1:])
2337
self.overrideAttr(trace, 'warning', warning)
2338
self.assertEquals(None, opt.convert_from_unicode(None, value))
2339
self.assertLength(1, warnings)
2341
'Value "%s" is not valid for "%s"' % (value, opt.name),
2344
def assertErrors(self, opt, value):
2345
self.assertRaises(errors.ConfigOptionValueError,
2346
opt.convert_from_unicode, None, value)
2348
def assertConvertInvalid(self, opt, invalid_value):
2350
self.assertEquals(None, opt.convert_from_unicode(None, invalid_value))
2351
opt.invalid = 'warning'
2352
self.assertWarns(opt, invalid_value)
2353
opt.invalid = 'error'
2354
self.assertErrors(opt, invalid_value)
2357
class TestOptionWithBooleanConverter(tests.TestCase, TestOptionConverterMixin):
2359
def get_option(self):
2360
return config.Option('foo', help='A boolean.',
2361
from_unicode=config.bool_from_store)
2363
def test_convert_invalid(self):
2364
opt = self.get_option()
2365
# A string that is not recognized as a boolean
2366
self.assertConvertInvalid(opt, u'invalid-boolean')
2367
# A list of strings is never recognized as a boolean
2368
self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
2370
def test_convert_valid(self):
2371
opt = self.get_option()
2372
self.assertConverted(True, opt, u'True')
2373
self.assertConverted(True, opt, u'1')
2374
self.assertConverted(False, opt, u'False')
2377
class TestOptionWithIntegerConverter(tests.TestCase, TestOptionConverterMixin):
2379
def get_option(self):
2380
return config.Option('foo', help='An integer.',
2381
from_unicode=config.int_from_store)
2383
def test_convert_invalid(self):
2384
opt = self.get_option()
2385
# A string that is not recognized as an integer
2386
self.assertConvertInvalid(opt, u'forty-two')
2387
# A list of strings is never recognized as an integer
2388
self.assertConvertInvalid(opt, [u'a', u'list'])
2390
def test_convert_valid(self):
2391
opt = self.get_option()
2392
self.assertConverted(16, opt, u'16')
2395
class TestOptionWithSIUnitConverter(tests.TestCase, TestOptionConverterMixin):
2397
def get_option(self):
2398
return config.Option('foo', help='An integer in SI units.',
2399
from_unicode=config.int_SI_from_store)
2401
def test_convert_invalid(self):
2402
opt = self.get_option()
2403
self.assertConvertInvalid(opt, u'not-a-unit')
2404
self.assertConvertInvalid(opt, u'Gb') # Forgot the int
2405
self.assertConvertInvalid(opt, u'1b') # Forgot the unit
2406
self.assertConvertInvalid(opt, u'1GG')
2407
self.assertConvertInvalid(opt, u'1Mbb')
2408
self.assertConvertInvalid(opt, u'1MM')
2410
def test_convert_valid(self):
2411
opt = self.get_option()
2412
self.assertConverted(int(5e3), opt, u'5kb')
2413
self.assertConverted(int(5e6), opt, u'5M')
2414
self.assertConverted(int(5e6), opt, u'5MB')
2415
self.assertConverted(int(5e9), opt, u'5g')
2416
self.assertConverted(int(5e9), opt, u'5gB')
2417
self.assertConverted(100, opt, u'100')
2420
class TestListOption(tests.TestCase, TestOptionConverterMixin):
2422
def get_option(self):
2423
return config.ListOption('foo', help='A list.')
2425
def test_convert_invalid(self):
2426
opt = self.get_option()
2427
# We don't even try to convert a list into a list, we only expect
2429
self.assertConvertInvalid(opt, [1])
2430
# No string is invalid as all forms can be converted to a list
2432
def test_convert_valid(self):
2433
opt = self.get_option()
2434
# An empty string is an empty list
2435
self.assertConverted([], opt, '') # Using a bare str() just in case
2436
self.assertConverted([], opt, u'')
2438
self.assertConverted([u'True'], opt, u'True')
2440
self.assertConverted([u'42'], opt, u'42')
2442
self.assertConverted([u'bar'], opt, u'bar')
2445
class TestRegistryOption(tests.TestCase, TestOptionConverterMixin):
2447
def get_option(self, registry):
2448
return config.RegistryOption('foo', registry,
2449
help='A registry option.')
2451
def test_convert_invalid(self):
2452
registry = _mod_registry.Registry()
2453
opt = self.get_option(registry)
2454
self.assertConvertInvalid(opt, [1])
2455
self.assertConvertInvalid(opt, u"notregistered")
2457
def test_convert_valid(self):
2458
registry = _mod_registry.Registry()
2459
registry.register("someval", 1234)
2460
opt = self.get_option(registry)
2461
# Using a bare str() just in case
2462
self.assertConverted(1234, opt, "someval")
2463
self.assertConverted(1234, opt, u'someval')
2464
self.assertConverted(None, opt, None)
2466
def test_help(self):
2467
registry = _mod_registry.Registry()
2468
registry.register("someval", 1234, help="some option")
2469
registry.register("dunno", 1234, help="some other option")
2470
opt = self.get_option(registry)
2472
'A registry option.\n'
2474
'The following values are supported:\n'
2475
' dunno - some other option\n'
2476
' someval - some option\n',
2479
def test_get_help_text(self):
2480
registry = _mod_registry.Registry()
2481
registry.register("someval", 1234, help="some option")
2482
registry.register("dunno", 1234, help="some other option")
2483
opt = self.get_option(registry)
2485
'A registry option.\n'
2487
'The following values are supported:\n'
2488
' dunno - some other option\n'
2489
' someval - some option\n',
2490
opt.get_help_text())
2493
class TestOptionRegistry(tests.TestCase):
2496
super(TestOptionRegistry, self).setUp()
2497
# Always start with an empty registry
2498
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2499
self.registry = config.option_registry
2501
def test_register(self):
2502
opt = config.Option('foo')
2503
self.registry.register(opt)
2504
self.assertIs(opt, self.registry.get('foo'))
2506
def test_registered_help(self):
2507
opt = config.Option('foo', help='A simple option')
2508
self.registry.register(opt)
2509
self.assertEquals('A simple option', self.registry.get_help('foo'))
2511
lazy_option = config.Option('lazy_foo', help='Lazy help')
2513
def test_register_lazy(self):
2514
self.registry.register_lazy('lazy_foo', self.__module__,
2515
'TestOptionRegistry.lazy_option')
2516
self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
2518
def test_registered_lazy_help(self):
2519
self.registry.register_lazy('lazy_foo', self.__module__,
2520
'TestOptionRegistry.lazy_option')
2521
self.assertEquals('Lazy help', self.registry.get_help('lazy_foo'))
2524
class TestRegisteredOptions(tests.TestCase):
2525
"""All registered options should verify some constraints."""
2527
scenarios = [(key, {'option_name': key, 'option': option}) for key, option
2528
in config.option_registry.iteritems()]
2531
super(TestRegisteredOptions, self).setUp()
2532
self.registry = config.option_registry
2534
def test_proper_name(self):
2535
# An option should be registered under its own name, this can't be
2536
# checked at registration time for the lazy ones.
2537
self.assertEquals(self.option_name, self.option.name)
2539
def test_help_is_set(self):
2540
option_help = self.registry.get_help(self.option_name)
2541
self.assertNotEquals(None, option_help)
2542
# Come on, think about the user, he really wants to know what the
2544
self.assertIsNot(None, option_help)
2545
self.assertNotEquals('', option_help)
2548
class TestSection(tests.TestCase):
2550
# FIXME: Parametrize so that all sections produced by Stores run these
2551
# tests -- vila 2011-04-01
2553
def test_get_a_value(self):
2554
a_dict = dict(foo='bar')
2555
section = config.Section('myID', a_dict)
2556
self.assertEquals('bar', section.get('foo'))
2558
def test_get_unknown_option(self):
2560
section = config.Section(None, a_dict)
2561
self.assertEquals('out of thin air',
2562
section.get('foo', 'out of thin air'))
2564
def test_options_is_shared(self):
2566
section = config.Section(None, a_dict)
2567
self.assertIs(a_dict, section.options)
2570
class TestMutableSection(tests.TestCase):
2572
scenarios = [('mutable',
2574
lambda opts: config.MutableSection('myID', opts)},),
2578
a_dict = dict(foo='bar')
2579
section = self.get_section(a_dict)
2580
section.set('foo', 'new_value')
2581
self.assertEquals('new_value', section.get('foo'))
2582
# The change appears in the shared section
2583
self.assertEquals('new_value', a_dict.get('foo'))
2584
# We keep track of the change
2585
self.assertTrue('foo' in section.orig)
2586
self.assertEquals('bar', section.orig.get('foo'))
2588
def test_set_preserve_original_once(self):
2589
a_dict = dict(foo='bar')
2590
section = self.get_section(a_dict)
2591
section.set('foo', 'first_value')
2592
section.set('foo', 'second_value')
2593
# We keep track of the original value
2594
self.assertTrue('foo' in section.orig)
2595
self.assertEquals('bar', section.orig.get('foo'))
2597
def test_remove(self):
2598
a_dict = dict(foo='bar')
2599
section = self.get_section(a_dict)
2600
section.remove('foo')
2601
# We get None for unknown options via the default value
2602
self.assertEquals(None, section.get('foo'))
2603
# Or we just get the default value
2604
self.assertEquals('unknown', section.get('foo', 'unknown'))
2605
self.assertFalse('foo' in section.options)
2606
# We keep track of the deletion
2607
self.assertTrue('foo' in section.orig)
2608
self.assertEquals('bar', section.orig.get('foo'))
2610
def test_remove_new_option(self):
2612
section = self.get_section(a_dict)
2613
section.set('foo', 'bar')
2614
section.remove('foo')
2615
self.assertFalse('foo' in section.options)
2616
# The option didn't exist initially so it we need to keep track of it
2617
# with a special value
2618
self.assertTrue('foo' in section.orig)
2619
self.assertEquals(config._NewlyCreatedOption, section.orig['foo'])
2622
class TestCommandLineStore(tests.TestCase):
2625
super(TestCommandLineStore, self).setUp()
2626
self.store = config.CommandLineStore()
2627
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2629
def get_section(self):
2630
"""Get the unique section for the command line overrides."""
2631
sections = list(self.store.get_sections())
2632
self.assertLength(1, sections)
2633
store, section = sections[0]
2634
self.assertEquals(self.store, store)
2637
def test_no_override(self):
2638
self.store._from_cmdline([])
2639
section = self.get_section()
2640
self.assertLength(0, list(section.iter_option_names()))
2642
def test_simple_override(self):
2643
self.store._from_cmdline(['a=b'])
2644
section = self.get_section()
2645
self.assertEqual('b', section.get('a'))
2647
def test_list_override(self):
2648
opt = config.ListOption('l')
2649
config.option_registry.register(opt)
2650
self.store._from_cmdline(['l=1,2,3'])
2651
val = self.get_section().get('l')
2652
self.assertEqual('1,2,3', val)
2653
# Reminder: lists should be registered as such explicitely, otherwise
2654
# the conversion needs to be done afterwards.
2655
self.assertEqual(['1', '2', '3'],
2656
opt.convert_from_unicode(self.store, val))
2658
def test_multiple_overrides(self):
2659
self.store._from_cmdline(['a=b', 'x=y'])
2660
section = self.get_section()
2661
self.assertEquals('b', section.get('a'))
2662
self.assertEquals('y', section.get('x'))
2664
def test_wrong_syntax(self):
2665
self.assertRaises(errors.BzrCommandError,
2666
self.store._from_cmdline, ['a=b', 'c'])
2668
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
2670
scenarios = [(key, {'get_store': builder}) for key, builder
2671
in config.test_store_builder_registry.iteritems()] + [
2672
('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
2675
store = self.get_store(self)
2676
if type(store) == config.TransportIniFileStore:
2677
raise tests.TestNotApplicable(
2678
"%s is not a concrete Store implementation"
2679
" so it doesn't need an id" % (store.__class__.__name__,))
2680
self.assertIsNot(None, store.id)
2683
class TestStore(tests.TestCaseWithTransport):
2685
def assertSectionContent(self, expected, (store, section)):
2686
"""Assert that some options have the proper values in a section."""
2687
expected_name, expected_options = expected
2688
self.assertEquals(expected_name, section.id)
2691
dict([(k, section.get(k)) for k in expected_options.keys()]))
2694
class TestReadonlyStore(TestStore):
2696
scenarios = [(key, {'get_store': builder}) for key, builder
2697
in config.test_store_builder_registry.iteritems()]
2699
def test_building_delays_load(self):
2700
store = self.get_store(self)
2701
self.assertEquals(False, store.is_loaded())
2702
store._load_from_string('')
2703
self.assertEquals(True, store.is_loaded())
2705
def test_get_no_sections_for_empty(self):
2706
store = self.get_store(self)
2707
store._load_from_string('')
2708
self.assertEquals([], list(store.get_sections()))
2710
def test_get_default_section(self):
2711
store = self.get_store(self)
2712
store._load_from_string('foo=bar')
2713
sections = list(store.get_sections())
2714
self.assertLength(1, sections)
2715
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2717
def test_get_named_section(self):
2718
store = self.get_store(self)
2719
store._load_from_string('[baz]\nfoo=bar')
2720
sections = list(store.get_sections())
2721
self.assertLength(1, sections)
2722
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2724
def test_load_from_string_fails_for_non_empty_store(self):
2725
store = self.get_store(self)
2726
store._load_from_string('foo=bar')
2727
self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
2730
class TestStoreQuoting(TestStore):
2732
scenarios = [(key, {'get_store': builder}) for key, builder
2733
in config.test_store_builder_registry.iteritems()]
2736
super(TestStoreQuoting, self).setUp()
2737
self.store = self.get_store(self)
2738
# We need a loaded store but any content will do
2739
self.store._load_from_string('')
2741
def assertIdempotent(self, s):
2742
"""Assert that quoting an unquoted string is a no-op and vice-versa.
2744
What matters here is that option values, as they appear in a store, can
2745
be safely round-tripped out of the store and back.
2747
:param s: A string, quoted if required.
2749
self.assertEquals(s, self.store.quote(self.store.unquote(s)))
2750
self.assertEquals(s, self.store.unquote(self.store.quote(s)))
2752
def test_empty_string(self):
2753
if isinstance(self.store, config.IniFileStore):
2754
# configobj._quote doesn't handle empty values
2755
self.assertRaises(AssertionError,
2756
self.assertIdempotent, '')
2758
self.assertIdempotent('')
2759
# But quoted empty strings are ok
2760
self.assertIdempotent('""')
2762
def test_embedded_spaces(self):
2763
self.assertIdempotent('" a b c "')
2765
def test_embedded_commas(self):
2766
self.assertIdempotent('" a , b c "')
2768
def test_simple_comma(self):
2769
if isinstance(self.store, config.IniFileStore):
2770
# configobj requires that lists are special-cased
2771
self.assertRaises(AssertionError,
2772
self.assertIdempotent, ',')
2774
self.assertIdempotent(',')
2775
# When a single comma is required, quoting is also required
2776
self.assertIdempotent('","')
2778
def test_list(self):
2779
if isinstance(self.store, config.IniFileStore):
2780
# configobj requires that lists are special-cased
2781
self.assertRaises(AssertionError,
2782
self.assertIdempotent, 'a,b')
2784
self.assertIdempotent('a,b')
2787
class TestDictFromStore(tests.TestCase):
2789
def test_unquote_not_string(self):
2790
conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
2791
value = conf.get('a_section')
2792
# Urgh, despite 'conf' asking for the no-name section, we get the
2793
# content of another section as a dict o_O
2794
self.assertEquals({'a': '1'}, value)
2795
unquoted = conf.store.unquote(value)
2796
# Which cannot be unquoted but shouldn't crash either (the use cases
2797
# are getting the value or displaying it. In the later case, '%s' will
2799
self.assertEquals({'a': '1'}, unquoted)
2800
self.assertEquals("{u'a': u'1'}", '%s' % (unquoted,))
2803
class TestIniFileStoreContent(tests.TestCaseWithTransport):
2804
"""Simulate loading a config store with content of various encodings.
2806
All files produced by bzr are in utf8 content.
2808
Users may modify them manually and end up with a file that can't be
2809
loaded. We need to issue proper error messages in this case.
2812
invalid_utf8_char = '\xff'
2814
def test_load_utf8(self):
2815
"""Ensure we can load an utf8-encoded file."""
2816
t = self.get_transport()
2817
# From http://pad.lv/799212
2818
unicode_user = u'b\N{Euro Sign}ar'
2819
unicode_content = u'user=%s' % (unicode_user,)
2820
utf8_content = unicode_content.encode('utf8')
2821
# Store the raw content in the config file
2822
t.put_bytes('foo.conf', utf8_content)
2823
store = config.TransportIniFileStore(t, 'foo.conf')
2825
stack = config.Stack([store.get_sections], store)
2826
self.assertEquals(unicode_user, stack.get('user'))
2828
def test_load_non_ascii(self):
2829
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2830
t = self.get_transport()
2831
t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
2832
store = config.TransportIniFileStore(t, 'foo.conf')
2833
self.assertRaises(errors.ConfigContentError, store.load)
2835
def test_load_erroneous_content(self):
2836
"""Ensure we display a proper error on content that can't be parsed."""
2837
t = self.get_transport()
2838
t.put_bytes('foo.conf', '[open_section\n')
2839
store = config.TransportIniFileStore(t, 'foo.conf')
2840
self.assertRaises(errors.ParseConfigError, store.load)
2842
def test_load_permission_denied(self):
2843
"""Ensure we get warned when trying to load an inaccessible file."""
2846
warnings.append(args[0] % args[1:])
2847
self.overrideAttr(trace, 'warning', warning)
2849
t = self.get_transport()
2851
def get_bytes(relpath):
2852
raise errors.PermissionDenied(relpath, "")
2853
t.get_bytes = get_bytes
2854
store = config.TransportIniFileStore(t, 'foo.conf')
2855
self.assertRaises(errors.PermissionDenied, store.load)
2858
[u'Permission denied while trying to load configuration store %s.'
2859
% store.external_url()])
2862
class TestIniConfigContent(tests.TestCaseWithTransport):
2863
"""Simulate loading a IniBasedConfig with content of various encodings.
2865
All files produced by bzr are in utf8 content.
2867
Users may modify them manually and end up with a file that can't be
2868
loaded. We need to issue proper error messages in this case.
2871
invalid_utf8_char = '\xff'
2873
def test_load_utf8(self):
2874
"""Ensure we can load an utf8-encoded file."""
2875
# From http://pad.lv/799212
2876
unicode_user = u'b\N{Euro Sign}ar'
2877
unicode_content = u'user=%s' % (unicode_user,)
2878
utf8_content = unicode_content.encode('utf8')
2879
# Store the raw content in the config file
2880
with open('foo.conf', 'wb') as f:
2881
f.write(utf8_content)
2882
conf = config.IniBasedConfig(file_name='foo.conf')
2883
self.assertEquals(unicode_user, conf.get_user_option('user'))
2885
def test_load_badly_encoded_content(self):
2886
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2887
with open('foo.conf', 'wb') as f:
2888
f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
2889
conf = config.IniBasedConfig(file_name='foo.conf')
2890
self.assertRaises(errors.ConfigContentError, conf._get_parser)
2892
def test_load_erroneous_content(self):
2893
"""Ensure we display a proper error on content that can't be parsed."""
2894
with open('foo.conf', 'wb') as f:
2895
f.write('[open_section\n')
2896
conf = config.IniBasedConfig(file_name='foo.conf')
2897
self.assertRaises(errors.ParseConfigError, conf._get_parser)
2900
class TestMutableStore(TestStore):
2902
scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
2903
in config.test_store_builder_registry.iteritems()]
2906
super(TestMutableStore, self).setUp()
2907
self.transport = self.get_transport()
2909
def has_store(self, store):
2910
store_basename = urlutils.relative_url(self.transport.external_url(),
2911
store.external_url())
2912
return self.transport.has(store_basename)
2914
def test_save_empty_creates_no_file(self):
2915
# FIXME: There should be a better way than relying on the test
2916
# parametrization to identify branch.conf -- vila 2011-0526
2917
if self.store_id in ('branch', 'remote_branch'):
2918
raise tests.TestNotApplicable(
2919
'branch.conf is *always* created when a branch is initialized')
2920
store = self.get_store(self)
2922
self.assertEquals(False, self.has_store(store))
2924
def test_save_emptied_succeeds(self):
2925
store = self.get_store(self)
2926
store._load_from_string('foo=bar\n')
2927
# FIXME: There should be a better way than relying on the test
2928
# parametrization to identify branch.conf -- vila 2011-0526
2929
if self.store_id in ('branch', 'remote_branch'):
2930
# branch stores requires write locked branches
2931
self.addCleanup(store.branch.lock_write().unlock)
2932
section = store.get_mutable_section(None)
2933
section.remove('foo')
2935
self.assertEquals(True, self.has_store(store))
2936
modified_store = self.get_store(self)
2937
sections = list(modified_store.get_sections())
2938
self.assertLength(0, sections)
2940
def test_save_with_content_succeeds(self):
2941
# FIXME: There should be a better way than relying on the test
2942
# parametrization to identify branch.conf -- vila 2011-0526
2943
if self.store_id in ('branch', 'remote_branch'):
2944
raise tests.TestNotApplicable(
2945
'branch.conf is *always* created when a branch is initialized')
2946
store = self.get_store(self)
2947
store._load_from_string('foo=bar\n')
2948
self.assertEquals(False, self.has_store(store))
2950
self.assertEquals(True, self.has_store(store))
2951
modified_store = self.get_store(self)
2952
sections = list(modified_store.get_sections())
2953
self.assertLength(1, sections)
2954
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2956
def test_set_option_in_empty_store(self):
2957
store = self.get_store(self)
2958
# FIXME: There should be a better way than relying on the test
2959
# parametrization to identify branch.conf -- vila 2011-0526
2960
if self.store_id in ('branch', 'remote_branch'):
2961
# branch stores requires write locked branches
2962
self.addCleanup(store.branch.lock_write().unlock)
2963
section = store.get_mutable_section(None)
2964
section.set('foo', 'bar')
2966
modified_store = self.get_store(self)
2967
sections = list(modified_store.get_sections())
2968
self.assertLength(1, sections)
2969
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2971
def test_set_option_in_default_section(self):
2972
store = self.get_store(self)
2973
store._load_from_string('')
2974
# FIXME: There should be a better way than relying on the test
2975
# parametrization to identify branch.conf -- vila 2011-0526
2976
if self.store_id in ('branch', 'remote_branch'):
2977
# branch stores requires write locked branches
2978
self.addCleanup(store.branch.lock_write().unlock)
2979
section = store.get_mutable_section(None)
2980
section.set('foo', 'bar')
2982
modified_store = self.get_store(self)
2983
sections = list(modified_store.get_sections())
2984
self.assertLength(1, sections)
2985
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2987
def test_set_option_in_named_section(self):
2988
store = self.get_store(self)
2989
store._load_from_string('')
2990
# FIXME: There should be a better way than relying on the test
2991
# parametrization to identify branch.conf -- vila 2011-0526
2992
if self.store_id in ('branch', 'remote_branch'):
2993
# branch stores requires write locked branches
2994
self.addCleanup(store.branch.lock_write().unlock)
2995
section = store.get_mutable_section('baz')
2996
section.set('foo', 'bar')
2998
modified_store = self.get_store(self)
2999
sections = list(modified_store.get_sections())
3000
self.assertLength(1, sections)
3001
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
3003
def test_load_hook(self):
3004
# First, we need to ensure that the store exists
3005
store = self.get_store(self)
3006
# FIXME: There should be a better way than relying on the test
3007
# parametrization to identify branch.conf -- vila 2011-0526
3008
if self.store_id in ('branch', 'remote_branch'):
3009
# branch stores requires write locked branches
3010
self.addCleanup(store.branch.lock_write().unlock)
3011
section = store.get_mutable_section('baz')
3012
section.set('foo', 'bar')
3014
# Now we can try to load it
3015
store = self.get_store(self)
3019
config.ConfigHooks.install_named_hook('load', hook, None)
3020
self.assertLength(0, calls)
3022
self.assertLength(1, calls)
3023
self.assertEquals((store,), calls[0])
3025
def test_save_hook(self):
3029
config.ConfigHooks.install_named_hook('save', hook, None)
3030
self.assertLength(0, calls)
3031
store = self.get_store(self)
3032
# FIXME: There should be a better way than relying on the test
3033
# parametrization to identify branch.conf -- vila 2011-0526
3034
if self.store_id in ('branch', 'remote_branch'):
3035
# branch stores requires write locked branches
3036
self.addCleanup(store.branch.lock_write().unlock)
3037
section = store.get_mutable_section('baz')
3038
section.set('foo', 'bar')
3040
self.assertLength(1, calls)
3041
self.assertEquals((store,), calls[0])
3043
def test_set_mark_dirty(self):
3044
stack = config.MemoryStack('')
3045
self.assertLength(0, stack.store.dirty_sections)
3046
stack.set('foo', 'baz')
3047
self.assertLength(1, stack.store.dirty_sections)
3048
self.assertTrue(stack.store._need_saving())
3050
def test_remove_mark_dirty(self):
3051
stack = config.MemoryStack('foo=bar')
3052
self.assertLength(0, stack.store.dirty_sections)
3054
self.assertLength(1, stack.store.dirty_sections)
3055
self.assertTrue(stack.store._need_saving())
3058
class TestStoreSaveChanges(tests.TestCaseWithTransport):
3059
"""Tests that config changes are kept in memory and saved on-demand."""
3062
super(TestStoreSaveChanges, self).setUp()
3063
self.transport = self.get_transport()
3064
# Most of the tests involve two stores pointing to the same persistent
3065
# storage to observe the effects of concurrent changes
3066
self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
3067
self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
3070
self.warnings.append(args[0] % args[1:])
3071
self.overrideAttr(trace, 'warning', warning)
3073
def has_store(self, store):
3074
store_basename = urlutils.relative_url(self.transport.external_url(),
3075
store.external_url())
3076
return self.transport.has(store_basename)
3078
def get_stack(self, store):
3079
# Any stack will do as long as it uses the right store, just a single
3080
# no-name section is enough
3081
return config.Stack([store.get_sections], store)
3083
def test_no_changes_no_save(self):
3084
s = self.get_stack(self.st1)
3085
s.store.save_changes()
3086
self.assertEquals(False, self.has_store(self.st1))
3088
def test_unrelated_concurrent_update(self):
3089
s1 = self.get_stack(self.st1)
3090
s2 = self.get_stack(self.st2)
3091
s1.set('foo', 'bar')
3092
s2.set('baz', 'quux')
3094
# Changes don't propagate magically
3095
self.assertEquals(None, s1.get('baz'))
3096
s2.store.save_changes()
3097
self.assertEquals('quux', s2.get('baz'))
3098
# Changes are acquired when saving
3099
self.assertEquals('bar', s2.get('foo'))
3100
# Since there is no overlap, no warnings are emitted
3101
self.assertLength(0, self.warnings)
3103
def test_concurrent_update_modified(self):
3104
s1 = self.get_stack(self.st1)
3105
s2 = self.get_stack(self.st2)
3106
s1.set('foo', 'bar')
3107
s2.set('foo', 'baz')
3110
s2.store.save_changes()
3111
self.assertEquals('baz', s2.get('foo'))
3112
# But the user get a warning
3113
self.assertLength(1, self.warnings)
3114
warning = self.warnings[0]
3115
self.assertStartsWith(warning, 'Option foo in section None')
3116
self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
3117
' The baz value will be saved.')
3119
def test_concurrent_deletion(self):
3120
self.st1._load_from_string('foo=bar')
3122
s1 = self.get_stack(self.st1)
3123
s2 = self.get_stack(self.st2)
3126
s1.store.save_changes()
3128
self.assertLength(0, self.warnings)
3129
s2.store.save_changes()
3131
self.assertLength(1, self.warnings)
3132
warning = self.warnings[0]
3133
self.assertStartsWith(warning, 'Option foo in section None')
3134
self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
3135
' The <DELETED> value will be saved.')
3138
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
3140
def get_store(self):
3141
return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3143
def test_get_quoted_string(self):
3144
store = self.get_store()
3145
store._load_from_string('foo= " abc "')
3146
stack = config.Stack([store.get_sections])
3147
self.assertEquals(' abc ', stack.get('foo'))
3149
def test_set_quoted_string(self):
3150
store = self.get_store()
3151
stack = config.Stack([store.get_sections], store)
3152
stack.set('foo', ' a b c ')
3154
self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
3157
class TestTransportIniFileStore(TestStore):
3159
def test_loading_unknown_file_fails(self):
3160
store = config.TransportIniFileStore(self.get_transport(),
3162
self.assertRaises(errors.NoSuchFile, store.load)
3164
def test_invalid_content(self):
3165
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3166
self.assertEquals(False, store.is_loaded())
3167
exc = self.assertRaises(
3168
errors.ParseConfigError, store._load_from_string,
3169
'this is invalid !')
3170
self.assertEndsWith(exc.filename, 'foo.conf')
3171
# And the load failed
3172
self.assertEquals(False, store.is_loaded())
3174
def test_get_embedded_sections(self):
3175
# A more complicated example (which also shows that section names and
3176
# option names share the same name space...)
3177
# FIXME: This should be fixed by forbidding dicts as values ?
3178
# -- vila 2011-04-05
3179
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3180
store._load_from_string('''
3184
foo_in_DEFAULT=foo_DEFAULT
3192
sections = list(store.get_sections())
3193
self.assertLength(4, sections)
3194
# The default section has no name.
3195
# List values are provided as strings and need to be explicitly
3196
# converted by specifying from_unicode=list_from_store at option
3198
self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
3200
self.assertSectionContent(
3201
('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
3202
self.assertSectionContent(
3203
('bar', {'foo_in_bar': 'barbar'}), sections[2])
3204
# sub sections are provided as embedded dicts.
3205
self.assertSectionContent(
3206
('baz', {'foo_in_baz': 'barbaz', 'qux': {'foo_in_qux': 'quux'}}),
3210
class TestLockableIniFileStore(TestStore):
3212
def test_create_store_in_created_dir(self):
3213
self.assertPathDoesNotExist('dir')
3214
t = self.get_transport('dir/subdir')
3215
store = config.LockableIniFileStore(t, 'foo.conf')
3216
store.get_mutable_section(None).set('foo', 'bar')
3218
self.assertPathExists('dir/subdir')
3221
class TestConcurrentStoreUpdates(TestStore):
3222
"""Test that Stores properly handle conccurent updates.
3224
New Store implementation may fail some of these tests but until such
3225
implementations exist it's hard to properly filter them from the scenarios
3226
applied here. If you encounter such a case, contact the bzr devs.
3229
scenarios = [(key, {'get_stack': builder}) for key, builder
3230
in config.test_stack_builder_registry.iteritems()]
3233
super(TestConcurrentStoreUpdates, self).setUp()
3234
self.stack = self.get_stack(self)
3235
if not isinstance(self.stack, config._CompatibleStack):
3236
raise tests.TestNotApplicable(
3237
'%s is not meant to be compatible with the old config design'
3239
self.stack.set('one', '1')
3240
self.stack.set('two', '2')
3242
self.stack.store.save()
3244
def test_simple_read_access(self):
3245
self.assertEquals('1', self.stack.get('one'))
3247
def test_simple_write_access(self):
3248
self.stack.set('one', 'one')
3249
self.assertEquals('one', self.stack.get('one'))
3251
def test_listen_to_the_last_speaker(self):
3253
c2 = self.get_stack(self)
3254
c1.set('one', 'ONE')
3255
c2.set('two', 'TWO')
3256
self.assertEquals('ONE', c1.get('one'))
3257
self.assertEquals('TWO', c2.get('two'))
3258
# The second update respect the first one
3259
self.assertEquals('ONE', c2.get('one'))
3261
def test_last_speaker_wins(self):
3262
# If the same config is not shared, the same variable modified twice
3263
# can only see a single result.
3265
c2 = self.get_stack(self)
3268
self.assertEquals('c2', c2.get('one'))
3269
# The first modification is still available until another refresh
3271
self.assertEquals('c1', c1.get('one'))
3272
c1.set('two', 'done')
3273
self.assertEquals('c2', c1.get('one'))
3275
def test_writes_are_serialized(self):
3277
c2 = self.get_stack(self)
3279
# We spawn a thread that will pause *during* the config saving.
3280
before_writing = threading.Event()
3281
after_writing = threading.Event()
3282
writing_done = threading.Event()
3283
c1_save_without_locking_orig = c1.store.save_without_locking
3284
def c1_save_without_locking():
3285
before_writing.set()
3286
c1_save_without_locking_orig()
3287
# The lock is held. We wait for the main thread to decide when to
3289
after_writing.wait()
3290
c1.store.save_without_locking = c1_save_without_locking
3294
t1 = threading.Thread(target=c1_set)
3295
# Collect the thread after the test
3296
self.addCleanup(t1.join)
3297
# Be ready to unblock the thread if the test goes wrong
3298
self.addCleanup(after_writing.set)
3300
before_writing.wait()
3301
self.assertRaises(errors.LockContention,
3302
c2.set, 'one', 'c2')
3303
self.assertEquals('c1', c1.get('one'))
3304
# Let the lock be released
3308
self.assertEquals('c2', c2.get('one'))
3310
def test_read_while_writing(self):
3312
# We spawn a thread that will pause *during* the write
3313
ready_to_write = threading.Event()
3314
do_writing = threading.Event()
3315
writing_done = threading.Event()
3316
# We override the _save implementation so we know the store is locked
3317
c1_save_without_locking_orig = c1.store.save_without_locking
3318
def c1_save_without_locking():
3319
ready_to_write.set()
3320
# The lock is held. We wait for the main thread to decide when to
3323
c1_save_without_locking_orig()
3325
c1.store.save_without_locking = c1_save_without_locking
3328
t1 = threading.Thread(target=c1_set)
3329
# Collect the thread after the test
3330
self.addCleanup(t1.join)
3331
# Be ready to unblock the thread if the test goes wrong
3332
self.addCleanup(do_writing.set)
3334
# Ensure the thread is ready to write
3335
ready_to_write.wait()
3336
self.assertEquals('c1', c1.get('one'))
3337
# If we read during the write, we get the old value
3338
c2 = self.get_stack(self)
3339
self.assertEquals('1', c2.get('one'))
3340
# Let the writing occur and ensure it occurred
3343
# Now we get the updated value
3344
c3 = self.get_stack(self)
3345
self.assertEquals('c1', c3.get('one'))
3347
# FIXME: It may be worth looking into removing the lock dir when it's not
3348
# needed anymore and look at possible fallouts for concurrent lockers. This
3349
# will matter if/when we use config files outside of bazaar directories
3350
# (.bazaar or .bzr) -- vila 20110-04-111
3353
class TestSectionMatcher(TestStore):
3355
scenarios = [('location', {'matcher': config.LocationMatcher}),
3356
('id', {'matcher': config.NameMatcher}),]
3359
super(TestSectionMatcher, self).setUp()
3360
# Any simple store is good enough
3361
self.get_store = config.test_store_builder_registry.get('configobj')
3363
def test_no_matches_for_empty_stores(self):
3364
store = self.get_store(self)
3365
store._load_from_string('')
3366
matcher = self.matcher(store, '/bar')
3367
self.assertEquals([], list(matcher.get_sections()))
3369
def test_build_doesnt_load_store(self):
3370
store = self.get_store(self)
3371
matcher = self.matcher(store, '/bar')
3372
self.assertFalse(store.is_loaded())
3375
class TestLocationSection(tests.TestCase):
3377
def get_section(self, options, extra_path):
3378
section = config.Section('foo', options)
3379
return config.LocationSection(section, extra_path)
3381
def test_simple_option(self):
3382
section = self.get_section({'foo': 'bar'}, '')
3383
self.assertEquals('bar', section.get('foo'))
3385
def test_option_with_extra_path(self):
3386
section = self.get_section({'foo': 'bar', 'foo:policy': 'appendpath'},
3388
self.assertEquals('bar/baz', section.get('foo'))
3390
def test_invalid_policy(self):
3391
section = self.get_section({'foo': 'bar', 'foo:policy': 'die'},
3393
# invalid policies are ignored
3394
self.assertEquals('bar', section.get('foo'))
3397
class TestLocationMatcher(TestStore):
3400
super(TestLocationMatcher, self).setUp()
3401
# Any simple store is good enough
3402
self.get_store = config.test_store_builder_registry.get('configobj')
3404
def test_unrelated_section_excluded(self):
3405
store = self.get_store(self)
3406
store._load_from_string('''
3414
section=/foo/bar/baz
3418
self.assertEquals(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
3420
[section.id for _, section in store.get_sections()])
3421
matcher = config.LocationMatcher(store, '/foo/bar/quux')
3422
sections = [section for _, section in matcher.get_sections()]
3423
self.assertEquals(['/foo/bar', '/foo'],
3424
[section.id for section in sections])
3425
self.assertEquals(['quux', 'bar/quux'],
3426
[section.extra_path for section in sections])
3428
def test_more_specific_sections_first(self):
3429
store = self.get_store(self)
3430
store._load_from_string('''
3436
self.assertEquals(['/foo', '/foo/bar'],
3437
[section.id for _, section in store.get_sections()])
3438
matcher = config.LocationMatcher(store, '/foo/bar/baz')
3439
sections = [section for _, section in matcher.get_sections()]
3440
self.assertEquals(['/foo/bar', '/foo'],
3441
[section.id for section in sections])
3442
self.assertEquals(['baz', 'bar/baz'],
3443
[section.extra_path for section in sections])
3445
def test_appendpath_in_no_name_section(self):
3446
# It's a bit weird to allow appendpath in a no-name section, but
3447
# someone may found a use for it
3448
store = self.get_store(self)
3449
store._load_from_string('''
3451
foo:policy = appendpath
3453
matcher = config.LocationMatcher(store, 'dir/subdir')
3454
sections = list(matcher.get_sections())
3455
self.assertLength(1, sections)
3456
self.assertEquals('bar/dir/subdir', sections[0][1].get('foo'))
3458
def test_file_urls_are_normalized(self):
3459
store = self.get_store(self)
3460
if sys.platform == 'win32':
3461
expected_url = 'file:///C:/dir/subdir'
3462
expected_location = 'C:/dir/subdir'
3464
expected_url = 'file:///dir/subdir'
3465
expected_location = '/dir/subdir'
3466
matcher = config.LocationMatcher(store, expected_url)
3467
self.assertEquals(expected_location, matcher.location)
3470
class TestStartingPathMatcher(TestStore):
3473
super(TestStartingPathMatcher, self).setUp()
3474
# Any simple store is good enough
3475
self.store = config.IniFileStore()
3477
def assertSectionIDs(self, expected, location, content):
3478
self.store._load_from_string(content)
3479
matcher = config.StartingPathMatcher(self.store, location)
3480
sections = list(matcher.get_sections())
3481
self.assertLength(len(expected), sections)
3482
self.assertEqual(expected, [section.id for _, section in sections])
3485
def test_empty(self):
3486
self.assertSectionIDs([], self.get_url(), '')
3488
def test_url_vs_local_paths(self):
3489
# The matcher location is an url and the section names are local paths
3490
sections = self.assertSectionIDs(['/foo/bar', '/foo'],
3491
'file:///foo/bar/baz', '''\
3496
def test_local_path_vs_url(self):
3497
# The matcher location is a local path and the section names are urls
3498
sections = self.assertSectionIDs(['file:///foo/bar', 'file:///foo'],
3499
'/foo/bar/baz', '''\
3505
def test_no_name_section_included_when_present(self):
3506
# Note that other tests will cover the case where the no-name section
3507
# is empty and as such, not included.
3508
sections = self.assertSectionIDs(['/foo/bar', '/foo', None],
3509
'/foo/bar/baz', '''\
3510
option = defined so the no-name section exists
3514
self.assertEquals(['baz', 'bar/baz', '/foo/bar/baz'],
3515
[s.locals['relpath'] for _, s in sections])
3517
def test_order_reversed(self):
3518
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3523
def test_unrelated_section_excluded(self):
3524
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3530
def test_glob_included(self):
3531
sections = self.assertSectionIDs(['/foo/*/baz', '/foo/b*', '/foo'],
3532
'/foo/bar/baz', '''\
3538
# Note that 'baz' as a relpath for /foo/b* is not fully correct, but
3539
# nothing really is... as far using {relpath} to append it to something
3540
# else, this seems good enough though.
3541
self.assertEquals(['', 'baz', 'bar/baz'],
3542
[s.locals['relpath'] for _, s in sections])
3544
def test_respect_order(self):
3545
self.assertSectionIDs(['/foo', '/foo/b*', '/foo/*/baz'],
3546
'/foo/bar/baz', '''\
3554
class TestNameMatcher(TestStore):
3557
super(TestNameMatcher, self).setUp()
3558
self.matcher = config.NameMatcher
3559
# Any simple store is good enough
3560
self.get_store = config.test_store_builder_registry.get('configobj')
3562
def get_matching_sections(self, name):
3563
store = self.get_store(self)
3564
store._load_from_string('''
3572
matcher = self.matcher(store, name)
3573
return list(matcher.get_sections())
3575
def test_matching(self):
3576
sections = self.get_matching_sections('foo')
3577
self.assertLength(1, sections)
3578
self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
3580
def test_not_matching(self):
3581
sections = self.get_matching_sections('baz')
3582
self.assertLength(0, sections)
3585
class TestBaseStackGet(tests.TestCase):
3588
super(TestBaseStackGet, self).setUp()
3589
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3591
def test_get_first_definition(self):
3592
store1 = config.IniFileStore()
3593
store1._load_from_string('foo=bar')
3594
store2 = config.IniFileStore()
3595
store2._load_from_string('foo=baz')
3596
conf = config.Stack([store1.get_sections, store2.get_sections])
3597
self.assertEquals('bar', conf.get('foo'))
3599
def test_get_with_registered_default_value(self):
3600
config.option_registry.register(config.Option('foo', default='bar'))
3601
conf_stack = config.Stack([])
3602
self.assertEquals('bar', conf_stack.get('foo'))
3604
def test_get_without_registered_default_value(self):
3605
config.option_registry.register(config.Option('foo'))
3606
conf_stack = config.Stack([])
3607
self.assertEquals(None, conf_stack.get('foo'))
3609
def test_get_without_default_value_for_not_registered(self):
3610
conf_stack = config.Stack([])
3611
self.assertEquals(None, conf_stack.get('foo'))
3613
def test_get_for_empty_section_callable(self):
3614
conf_stack = config.Stack([lambda : []])
3615
self.assertEquals(None, conf_stack.get('foo'))
3617
def test_get_for_broken_callable(self):
3618
# Trying to use and invalid callable raises an exception on first use
3619
conf_stack = config.Stack([object])
3620
self.assertRaises(TypeError, conf_stack.get, 'foo')
3623
class TestStackWithSimpleStore(tests.TestCase):
3626
super(TestStackWithSimpleStore, self).setUp()
3627
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3628
self.registry = config.option_registry
3630
def get_conf(self, content=None):
3631
return config.MemoryStack(content)
3633
def test_override_value_from_env(self):
3634
self.registry.register(
3635
config.Option('foo', default='bar', override_from_env=['FOO']))
3636
self.overrideEnv('FOO', 'quux')
3637
# Env variable provides a default taking over the option one
3638
conf = self.get_conf('foo=store')
3639
self.assertEquals('quux', conf.get('foo'))
3641
def test_first_override_value_from_env_wins(self):
3642
self.registry.register(
3643
config.Option('foo', default='bar',
3644
override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
3645
self.overrideEnv('FOO', 'foo')
3646
self.overrideEnv('BAZ', 'baz')
3647
# The first env var set wins
3648
conf = self.get_conf('foo=store')
3649
self.assertEquals('foo', conf.get('foo'))
3652
class TestMemoryStack(tests.TestCase):
3655
conf = config.MemoryStack('foo=bar')
3656
self.assertEquals('bar', conf.get('foo'))
3659
conf = config.MemoryStack('foo=bar')
3660
conf.set('foo', 'baz')
3661
self.assertEquals('baz', conf.get('foo'))
3663
def test_no_content(self):
3664
conf = config.MemoryStack()
3665
# No content means no loading
3666
self.assertFalse(conf.store.is_loaded())
3667
self.assertRaises(NotImplementedError, conf.get, 'foo')
3668
# But a content can still be provided
3669
conf.store._load_from_string('foo=bar')
3670
self.assertEquals('bar', conf.get('foo'))
3673
class TestStackWithTransport(tests.TestCaseWithTransport):
3675
scenarios = [(key, {'get_stack': builder}) for key, builder
3676
in config.test_stack_builder_registry.iteritems()]
3679
class TestConcreteStacks(TestStackWithTransport):
3681
def test_build_stack(self):
3682
# Just a smoke test to help debug builders
3683
stack = self.get_stack(self)
3686
class TestStackGet(TestStackWithTransport):
3689
super(TestStackGet, self).setUp()
3690
self.conf = self.get_stack(self)
3692
def test_get_for_empty_stack(self):
3693
self.assertEquals(None, self.conf.get('foo'))
3695
def test_get_hook(self):
3696
self.conf.set('foo', 'bar')
3700
config.ConfigHooks.install_named_hook('get', hook, None)
3701
self.assertLength(0, calls)
3702
value = self.conf.get('foo')
3703
self.assertEquals('bar', value)
3704
self.assertLength(1, calls)
3705
self.assertEquals((self.conf, 'foo', 'bar'), calls[0])
3708
class TestStackGetWithConverter(tests.TestCase):
3711
super(TestStackGetWithConverter, self).setUp()
3712
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3713
self.registry = config.option_registry
3715
def get_conf(self, content=None):
3716
return config.MemoryStack(content)
3718
def register_bool_option(self, name, default=None, default_from_env=None):
3719
b = config.Option(name, help='A boolean.',
3720
default=default, default_from_env=default_from_env,
3721
from_unicode=config.bool_from_store)
3722
self.registry.register(b)
3724
def test_get_default_bool_None(self):
3725
self.register_bool_option('foo')
3726
conf = self.get_conf('')
3727
self.assertEquals(None, conf.get('foo'))
3729
def test_get_default_bool_True(self):
3730
self.register_bool_option('foo', u'True')
3731
conf = self.get_conf('')
3732
self.assertEquals(True, conf.get('foo'))
3734
def test_get_default_bool_False(self):
3735
self.register_bool_option('foo', False)
3736
conf = self.get_conf('')
3737
self.assertEquals(False, conf.get('foo'))
3739
def test_get_default_bool_False_as_string(self):
3740
self.register_bool_option('foo', u'False')
3741
conf = self.get_conf('')
3742
self.assertEquals(False, conf.get('foo'))
3744
def test_get_default_bool_from_env_converted(self):
3745
self.register_bool_option('foo', u'True', default_from_env=['FOO'])
3746
self.overrideEnv('FOO', 'False')
3747
conf = self.get_conf('')
3748
self.assertEquals(False, conf.get('foo'))
3750
def test_get_default_bool_when_conversion_fails(self):
3751
self.register_bool_option('foo', default='True')
3752
conf = self.get_conf('foo=invalid boolean')
3753
self.assertEquals(True, conf.get('foo'))
3755
def register_integer_option(self, name,
3756
default=None, default_from_env=None):
3757
i = config.Option(name, help='An integer.',
3758
default=default, default_from_env=default_from_env,
3759
from_unicode=config.int_from_store)
3760
self.registry.register(i)
3762
def test_get_default_integer_None(self):
3763
self.register_integer_option('foo')
3764
conf = self.get_conf('')
3765
self.assertEquals(None, conf.get('foo'))
3767
def test_get_default_integer(self):
3768
self.register_integer_option('foo', 42)
3769
conf = self.get_conf('')
3770
self.assertEquals(42, conf.get('foo'))
3772
def test_get_default_integer_as_string(self):
3773
self.register_integer_option('foo', u'42')
3774
conf = self.get_conf('')
3775
self.assertEquals(42, conf.get('foo'))
3777
def test_get_default_integer_from_env(self):
3778
self.register_integer_option('foo', default_from_env=['FOO'])
3779
self.overrideEnv('FOO', '18')
3780
conf = self.get_conf('')
3781
self.assertEquals(18, conf.get('foo'))
3783
def test_get_default_integer_when_conversion_fails(self):
3784
self.register_integer_option('foo', default='12')
3785
conf = self.get_conf('foo=invalid integer')
3786
self.assertEquals(12, conf.get('foo'))
3788
def register_list_option(self, name, default=None, default_from_env=None):
3789
l = config.ListOption(name, help='A list.', default=default,
3790
default_from_env=default_from_env)
3791
self.registry.register(l)
3793
def test_get_default_list_None(self):
3794
self.register_list_option('foo')
3795
conf = self.get_conf('')
3796
self.assertEquals(None, conf.get('foo'))
3798
def test_get_default_list_empty(self):
3799
self.register_list_option('foo', '')
3800
conf = self.get_conf('')
3801
self.assertEquals([], conf.get('foo'))
3803
def test_get_default_list_from_env(self):
3804
self.register_list_option('foo', default_from_env=['FOO'])
3805
self.overrideEnv('FOO', '')
3806
conf = self.get_conf('')
3807
self.assertEquals([], conf.get('foo'))
3809
def test_get_with_list_converter_no_item(self):
3810
self.register_list_option('foo', None)
3811
conf = self.get_conf('foo=,')
3812
self.assertEquals([], conf.get('foo'))
3814
def test_get_with_list_converter_many_items(self):
3815
self.register_list_option('foo', None)
3816
conf = self.get_conf('foo=m,o,r,e')
3817
self.assertEquals(['m', 'o', 'r', 'e'], conf.get('foo'))
3819
def test_get_with_list_converter_embedded_spaces_many_items(self):
3820
self.register_list_option('foo', None)
3821
conf = self.get_conf('foo=" bar", "baz "')
3822
self.assertEquals([' bar', 'baz '], conf.get('foo'))
3824
def test_get_with_list_converter_stripped_spaces_many_items(self):
3825
self.register_list_option('foo', None)
3826
conf = self.get_conf('foo= bar , baz ')
3827
self.assertEquals(['bar', 'baz'], conf.get('foo'))
3830
class TestIterOptionRefs(tests.TestCase):
3831
"""iter_option_refs is a bit unusual, document some cases."""
3833
def assertRefs(self, expected, string):
3834
self.assertEquals(expected, list(config.iter_option_refs(string)))
3836
def test_empty(self):
3837
self.assertRefs([(False, '')], '')
3839
def test_no_refs(self):
3840
self.assertRefs([(False, 'foo bar')], 'foo bar')
3842
def test_single_ref(self):
3843
self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
3845
def test_broken_ref(self):
3846
self.assertRefs([(False, '{foo')], '{foo')
3848
def test_embedded_ref(self):
3849
self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
3852
def test_two_refs(self):
3853
self.assertRefs([(False, ''), (True, '{foo}'),
3854
(False, ''), (True, '{bar}'),
3858
def test_newline_in_refs_are_not_matched(self):
3859
self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
3862
class TestStackExpandOptions(tests.TestCaseWithTransport):
3865
super(TestStackExpandOptions, self).setUp()
3866
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3867
self.registry = config.option_registry
3868
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3869
self.conf = config.Stack([store.get_sections], store)
3871
def assertExpansion(self, expected, string, env=None):
3872
self.assertEquals(expected, self.conf.expand_options(string, env))
3874
def test_no_expansion(self):
3875
self.assertExpansion('foo', 'foo')
3877
def test_expand_default_value(self):
3878
self.conf.store._load_from_string('bar=baz')
3879
self.registry.register(config.Option('foo', default=u'{bar}'))
3880
self.assertEquals('baz', self.conf.get('foo', expand=True))
3882
def test_expand_default_from_env(self):
3883
self.conf.store._load_from_string('bar=baz')
3884
self.registry.register(config.Option('foo', default_from_env=['FOO']))
3885
self.overrideEnv('FOO', '{bar}')
3886
self.assertEquals('baz', self.conf.get('foo', expand=True))
3888
def test_expand_default_on_failed_conversion(self):
3889
self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
3890
self.registry.register(
3891
config.Option('foo', default=u'{bar}',
3892
from_unicode=config.int_from_store))
3893
self.assertEquals(42, self.conf.get('foo', expand=True))
3895
def test_env_adding_options(self):
3896
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3898
def test_env_overriding_options(self):
3899
self.conf.store._load_from_string('foo=baz')
3900
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3902
def test_simple_ref(self):
3903
self.conf.store._load_from_string('foo=xxx')
3904
self.assertExpansion('xxx', '{foo}')
3906
def test_unknown_ref(self):
3907
self.assertRaises(errors.ExpandingUnknownOption,
3908
self.conf.expand_options, '{foo}')
3910
def test_indirect_ref(self):
3911
self.conf.store._load_from_string('''
3915
self.assertExpansion('xxx', '{bar}')
3917
def test_embedded_ref(self):
3918
self.conf.store._load_from_string('''
3922
self.assertExpansion('xxx', '{{bar}}')
3924
def test_simple_loop(self):
3925
self.conf.store._load_from_string('foo={foo}')
3926
self.assertRaises(errors.OptionExpansionLoop,
3927
self.conf.expand_options, '{foo}')
3929
def test_indirect_loop(self):
3930
self.conf.store._load_from_string('''
3934
e = self.assertRaises(errors.OptionExpansionLoop,
3935
self.conf.expand_options, '{foo}')
3936
self.assertEquals('foo->bar->baz', e.refs)
3937
self.assertEquals('{foo}', e.string)
3939
def test_list(self):
3940
self.conf.store._load_from_string('''
3944
list={foo},{bar},{baz}
3946
self.registry.register(
3947
config.ListOption('list'))
3948
self.assertEquals(['start', 'middle', 'end'],
3949
self.conf.get('list', expand=True))
3951
def test_cascading_list(self):
3952
self.conf.store._load_from_string('''
3958
self.registry.register(
3959
config.ListOption('list'))
3960
self.assertEquals(['start', 'middle', 'end'],
3961
self.conf.get('list', expand=True))
3963
def test_pathologically_hidden_list(self):
3964
self.conf.store._load_from_string('''
3970
hidden={start}{middle}{end}
3972
# What matters is what the registration says, the conversion happens
3973
# only after all expansions have been performed
3974
self.registry.register(config.ListOption('hidden'))
3975
self.assertEquals(['bin', 'go'],
3976
self.conf.get('hidden', expand=True))
3979
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
3982
super(TestStackCrossSectionsExpand, self).setUp()
3984
def get_config(self, location, string):
3987
# Since we don't save the config we won't strictly require to inherit
3988
# from TestCaseInTempDir, but an error occurs so quickly...
3989
c = config.LocationStack(location)
3990
c.store._load_from_string(string)
3993
def test_dont_cross_unrelated_section(self):
3994
c = self.get_config('/another/branch/path','''
3999
[/another/branch/path]
4002
self.assertRaises(errors.ExpandingUnknownOption,
4003
c.get, 'bar', expand=True)
4005
def test_cross_related_sections(self):
4006
c = self.get_config('/project/branch/path','''
4010
[/project/branch/path]
4013
self.assertEquals('quux', c.get('bar', expand=True))
4016
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
4018
def test_cross_global_locations(self):
4019
l_store = config.LocationStore()
4020
l_store._load_from_string('''
4026
g_store = config.GlobalStore()
4027
g_store._load_from_string('''
4033
stack = config.LocationStack('/branch')
4034
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4035
self.assertEquals('loc-foo', stack.get('gfoo', expand=True))
4038
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
4040
def test_expand_locals_empty(self):
4041
l_store = config.LocationStore()
4042
l_store._load_from_string('''
4043
[/home/user/project]
4048
stack = config.LocationStack('/home/user/project/')
4049
self.assertEquals('', stack.get('base', expand=True))
4050
self.assertEquals('', stack.get('rel', expand=True))
4052
def test_expand_basename_locally(self):
4053
l_store = config.LocationStore()
4054
l_store._load_from_string('''
4055
[/home/user/project]
4059
stack = config.LocationStack('/home/user/project/branch')
4060
self.assertEquals('branch', stack.get('bfoo', expand=True))
4062
def test_expand_basename_locally_longer_path(self):
4063
l_store = config.LocationStore()
4064
l_store._load_from_string('''
4069
stack = config.LocationStack('/home/user/project/dir/branch')
4070
self.assertEquals('branch', stack.get('bfoo', expand=True))
4072
def test_expand_relpath_locally(self):
4073
l_store = config.LocationStore()
4074
l_store._load_from_string('''
4075
[/home/user/project]
4076
lfoo = loc-foo/{relpath}
4079
stack = config.LocationStack('/home/user/project/branch')
4080
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4082
def test_expand_relpath_unknonw_in_global(self):
4083
g_store = config.GlobalStore()
4084
g_store._load_from_string('''
4089
stack = config.LocationStack('/home/user/project/branch')
4090
self.assertRaises(errors.ExpandingUnknownOption,
4091
stack.get, 'gfoo', expand=True)
4093
def test_expand_local_option_locally(self):
4094
l_store = config.LocationStore()
4095
l_store._load_from_string('''
4096
[/home/user/project]
4097
lfoo = loc-foo/{relpath}
4101
g_store = config.GlobalStore()
4102
g_store._load_from_string('''
4108
stack = config.LocationStack('/home/user/project/branch')
4109
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4110
self.assertEquals('loc-foo/branch', stack.get('gfoo', expand=True))
4112
def test_locals_dont_leak(self):
4113
"""Make sure we chose the right local in presence of several sections.
4115
l_store = config.LocationStore()
4116
l_store._load_from_string('''
4118
lfoo = loc-foo/{relpath}
4119
[/home/user/project]
4120
lfoo = loc-foo/{relpath}
4123
stack = config.LocationStack('/home/user/project/branch')
4124
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4125
stack = config.LocationStack('/home/user/bar/baz')
4126
self.assertEquals('loc-foo/bar/baz', stack.get('lfoo', expand=True))
4130
class TestStackSet(TestStackWithTransport):
4132
def test_simple_set(self):
4133
conf = self.get_stack(self)
4134
self.assertEquals(None, conf.get('foo'))
4135
conf.set('foo', 'baz')
4136
# Did we get it back ?
4137
self.assertEquals('baz', conf.get('foo'))
4139
def test_set_creates_a_new_section(self):
4140
conf = self.get_stack(self)
4141
conf.set('foo', 'baz')
4142
self.assertEquals, 'baz', conf.get('foo')
4144
def test_set_hook(self):
4148
config.ConfigHooks.install_named_hook('set', hook, None)
4149
self.assertLength(0, calls)
4150
conf = self.get_stack(self)
4151
conf.set('foo', 'bar')
4152
self.assertLength(1, calls)
4153
self.assertEquals((conf, 'foo', 'bar'), calls[0])
4156
class TestStackRemove(TestStackWithTransport):
4158
def test_remove_existing(self):
4159
conf = self.get_stack(self)
4160
conf.set('foo', 'bar')
4161
self.assertEquals('bar', conf.get('foo'))
4163
# Did we get it back ?
4164
self.assertEquals(None, conf.get('foo'))
4166
def test_remove_unknown(self):
4167
conf = self.get_stack(self)
4168
self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
4170
def test_remove_hook(self):
4174
config.ConfigHooks.install_named_hook('remove', hook, None)
4175
self.assertLength(0, calls)
4176
conf = self.get_stack(self)
4177
conf.set('foo', 'bar')
4179
self.assertLength(1, calls)
4180
self.assertEquals((conf, 'foo'), calls[0])
1705
4183
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
1707
4185
def setUp(self):
1708
4186
super(TestConfigGetOptions, self).setUp()
1709
4187
create_configs(self)
1711
# One variable in none of the above
1712
4189
def test_no_variable(self):
1713
4190
# Using branch should query branch, locations and bazaar
1714
4191
self.assertOptions([], self.branch_config)