1312
2048
self.assertIs(None, bzrdir_config.get_default_stack_on())
2051
class TestOldConfigHooks(tests.TestCaseWithTransport):
2054
super(TestOldConfigHooks, self).setUp()
2055
create_configs_with_file_option(self)
2057
def assertGetHook(self, conf, name, value):
2061
config.OldConfigHooks.install_named_hook('get', hook, None)
2063
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2064
self.assertLength(0, calls)
2065
actual_value = conf.get_user_option(name)
2066
self.assertEquals(value, actual_value)
2067
self.assertLength(1, calls)
2068
self.assertEquals((conf, name, value), calls[0])
2070
def test_get_hook_bazaar(self):
2071
self.assertGetHook(self.bazaar_config, 'file', 'bazaar')
2073
def test_get_hook_locations(self):
2074
self.assertGetHook(self.locations_config, 'file', 'locations')
2076
def test_get_hook_branch(self):
2077
# Since locations masks branch, we define a different option
2078
self.branch_config.set_user_option('file2', 'branch')
2079
self.assertGetHook(self.branch_config, 'file2', 'branch')
2081
def assertSetHook(self, conf, name, value):
2085
config.OldConfigHooks.install_named_hook('set', hook, None)
2087
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2088
self.assertLength(0, calls)
2089
conf.set_user_option(name, value)
2090
self.assertLength(1, calls)
2091
# We can't assert the conf object below as different configs use
2092
# different means to implement set_user_option and we care only about
2094
self.assertEquals((name, value), calls[0][1:])
2096
def test_set_hook_bazaar(self):
2097
self.assertSetHook(self.bazaar_config, 'foo', 'bazaar')
2099
def test_set_hook_locations(self):
2100
self.assertSetHook(self.locations_config, 'foo', 'locations')
2102
def test_set_hook_branch(self):
2103
self.assertSetHook(self.branch_config, 'foo', 'branch')
2105
def assertRemoveHook(self, conf, name, section_name=None):
2109
config.OldConfigHooks.install_named_hook('remove', hook, None)
2111
config.OldConfigHooks.uninstall_named_hook, 'remove', None)
2112
self.assertLength(0, calls)
2113
conf.remove_user_option(name, section_name)
2114
self.assertLength(1, calls)
2115
# We can't assert the conf object below as different configs use
2116
# different means to implement remove_user_option and we care only about
2118
self.assertEquals((name,), calls[0][1:])
2120
def test_remove_hook_bazaar(self):
2121
self.assertRemoveHook(self.bazaar_config, 'file')
2123
def test_remove_hook_locations(self):
2124
self.assertRemoveHook(self.locations_config, 'file',
2125
self.locations_config.location)
2127
def test_remove_hook_branch(self):
2128
self.assertRemoveHook(self.branch_config, 'file')
2130
def assertLoadHook(self, name, conf_class, *conf_args):
2134
config.OldConfigHooks.install_named_hook('load', hook, None)
2136
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2137
self.assertLength(0, calls)
2139
conf = conf_class(*conf_args)
2140
# Access an option to trigger a load
2141
conf.get_user_option(name)
2142
self.assertLength(1, calls)
2143
# Since we can't assert about conf, we just use the number of calls ;-/
2145
def test_load_hook_bazaar(self):
2146
self.assertLoadHook('file', config.GlobalConfig)
2148
def test_load_hook_locations(self):
2149
self.assertLoadHook('file', config.LocationConfig, self.tree.basedir)
2151
def test_load_hook_branch(self):
2152
self.assertLoadHook('file', config.BranchConfig, self.tree.branch)
2154
def assertSaveHook(self, conf):
2158
config.OldConfigHooks.install_named_hook('save', hook, None)
2160
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2161
self.assertLength(0, calls)
2162
# Setting an option triggers a save
2163
conf.set_user_option('foo', 'bar')
2164
self.assertLength(1, calls)
2165
# Since we can't assert about conf, we just use the number of calls ;-/
2167
def test_save_hook_bazaar(self):
2168
self.assertSaveHook(self.bazaar_config)
2170
def test_save_hook_locations(self):
2171
self.assertSaveHook(self.locations_config)
2173
def test_save_hook_branch(self):
2174
self.assertSaveHook(self.branch_config)
2177
class TestOldConfigHooksForRemote(tests.TestCaseWithTransport):
2178
"""Tests config hooks for remote configs.
2180
No tests for the remove hook as this is not implemented there.
2184
super(TestOldConfigHooksForRemote, self).setUp()
2185
self.transport_server = test_server.SmartTCPServer_for_testing
2186
create_configs_with_file_option(self)
2188
def assertGetHook(self, conf, name, value):
2192
config.OldConfigHooks.install_named_hook('get', hook, None)
2194
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2195
self.assertLength(0, calls)
2196
actual_value = conf.get_option(name)
2197
self.assertEquals(value, actual_value)
2198
self.assertLength(1, calls)
2199
self.assertEquals((conf, name, value), calls[0])
2201
def test_get_hook_remote_branch(self):
2202
remote_branch = branch.Branch.open(self.get_url('tree'))
2203
self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
2205
def test_get_hook_remote_bzrdir(self):
2206
remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
2207
conf = remote_bzrdir._get_config()
2208
conf.set_option('remotedir', 'file')
2209
self.assertGetHook(conf, 'file', 'remotedir')
2211
def assertSetHook(self, conf, name, value):
2215
config.OldConfigHooks.install_named_hook('set', hook, None)
2217
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2218
self.assertLength(0, calls)
2219
conf.set_option(value, name)
2220
self.assertLength(1, calls)
2221
# We can't assert the conf object below as different configs use
2222
# different means to implement set_user_option and we care only about
2224
self.assertEquals((name, value), calls[0][1:])
2226
def test_set_hook_remote_branch(self):
2227
remote_branch = branch.Branch.open(self.get_url('tree'))
2228
self.addCleanup(remote_branch.lock_write().unlock)
2229
self.assertSetHook(remote_branch._get_config(), 'file', 'remote')
2231
def test_set_hook_remote_bzrdir(self):
2232
remote_branch = branch.Branch.open(self.get_url('tree'))
2233
self.addCleanup(remote_branch.lock_write().unlock)
2234
remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
2235
self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
2237
def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
2241
config.OldConfigHooks.install_named_hook('load', hook, None)
2243
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2244
self.assertLength(0, calls)
2246
conf = conf_class(*conf_args)
2247
# Access an option to trigger a load
2248
conf.get_option(name)
2249
self.assertLength(expected_nb_calls, calls)
2250
# Since we can't assert about conf, we just use the number of calls ;-/
2252
def test_load_hook_remote_branch(self):
2253
remote_branch = branch.Branch.open(self.get_url('tree'))
2254
self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
2256
def test_load_hook_remote_bzrdir(self):
2257
remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
2258
# The config file doesn't exist, set an option to force its creation
2259
conf = remote_bzrdir._get_config()
2260
conf.set_option('remotedir', 'file')
2261
# We get one call for the server and one call for the client, this is
2262
# caused by the differences in implementations betwen
2263
# SmartServerBzrDirRequestConfigFile (in smart/bzrdir.py) and
2264
# SmartServerBranchGetConfigFile (in smart/branch.py)
2265
self.assertLoadHook(2 ,'file', remote.RemoteBzrDirConfig, remote_bzrdir)
2267
def assertSaveHook(self, conf):
2271
config.OldConfigHooks.install_named_hook('save', hook, None)
2273
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2274
self.assertLength(0, calls)
2275
# Setting an option triggers a save
2276
conf.set_option('foo', 'bar')
2277
self.assertLength(1, calls)
2278
# Since we can't assert about conf, we just use the number of calls ;-/
2280
def test_save_hook_remote_branch(self):
2281
remote_branch = branch.Branch.open(self.get_url('tree'))
2282
self.addCleanup(remote_branch.lock_write().unlock)
2283
self.assertSaveHook(remote_branch._get_config())
2285
def test_save_hook_remote_bzrdir(self):
2286
remote_branch = branch.Branch.open(self.get_url('tree'))
2287
self.addCleanup(remote_branch.lock_write().unlock)
2288
remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
2289
self.assertSaveHook(remote_bzrdir._get_config())
2292
class TestOption(tests.TestCase):
2294
def test_default_value(self):
2295
opt = config.Option('foo', default='bar')
2296
self.assertEquals('bar', opt.get_default())
2298
def test_callable_default_value(self):
2299
def bar_as_unicode():
2301
opt = config.Option('foo', default=bar_as_unicode)
2302
self.assertEquals('bar', opt.get_default())
2304
def test_default_value_from_env(self):
2305
opt = config.Option('foo', default='bar', default_from_env=['FOO'])
2306
self.overrideEnv('FOO', 'quux')
2307
# Env variable provides a default taking over the option one
2308
self.assertEquals('quux', opt.get_default())
2310
def test_first_default_value_from_env_wins(self):
2311
opt = config.Option('foo', default='bar',
2312
default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
2313
self.overrideEnv('FOO', 'foo')
2314
self.overrideEnv('BAZ', 'baz')
2315
# The first env var set wins
2316
self.assertEquals('foo', opt.get_default())
2318
def test_not_supported_list_default_value(self):
2319
self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
2321
def test_not_supported_object_default_value(self):
2322
self.assertRaises(AssertionError, config.Option, 'foo',
2325
def test_not_supported_callable_default_value_not_unicode(self):
2326
def bar_not_unicode():
2328
opt = config.Option('foo', default=bar_not_unicode)
2329
self.assertRaises(AssertionError, opt.get_default)
2332
class TestOptionConverterMixin(object):
2334
def assertConverted(self, expected, opt, value):
2335
self.assertEquals(expected, opt.convert_from_unicode(None, value))
2337
def assertWarns(self, opt, value):
2340
warnings.append(args[0] % args[1:])
2341
self.overrideAttr(trace, 'warning', warning)
2342
self.assertEquals(None, opt.convert_from_unicode(None, value))
2343
self.assertLength(1, warnings)
2345
'Value "%s" is not valid for "%s"' % (value, opt.name),
2348
def assertErrors(self, opt, value):
2349
self.assertRaises(errors.ConfigOptionValueError,
2350
opt.convert_from_unicode, None, value)
2352
def assertConvertInvalid(self, opt, invalid_value):
2354
self.assertEquals(None, opt.convert_from_unicode(None, invalid_value))
2355
opt.invalid = 'warning'
2356
self.assertWarns(opt, invalid_value)
2357
opt.invalid = 'error'
2358
self.assertErrors(opt, invalid_value)
2361
class TestOptionWithBooleanConverter(tests.TestCase, TestOptionConverterMixin):
2363
def get_option(self):
2364
return config.Option('foo', help='A boolean.',
2365
from_unicode=config.bool_from_store)
2367
def test_convert_invalid(self):
2368
opt = self.get_option()
2369
# A string that is not recognized as a boolean
2370
self.assertConvertInvalid(opt, u'invalid-boolean')
2371
# A list of strings is never recognized as a boolean
2372
self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
2374
def test_convert_valid(self):
2375
opt = self.get_option()
2376
self.assertConverted(True, opt, u'True')
2377
self.assertConverted(True, opt, u'1')
2378
self.assertConverted(False, opt, u'False')
2381
class TestOptionWithIntegerConverter(tests.TestCase, TestOptionConverterMixin):
2383
def get_option(self):
2384
return config.Option('foo', help='An integer.',
2385
from_unicode=config.int_from_store)
2387
def test_convert_invalid(self):
2388
opt = self.get_option()
2389
# A string that is not recognized as an integer
2390
self.assertConvertInvalid(opt, u'forty-two')
2391
# A list of strings is never recognized as an integer
2392
self.assertConvertInvalid(opt, [u'a', u'list'])
2394
def test_convert_valid(self):
2395
opt = self.get_option()
2396
self.assertConverted(16, opt, u'16')
2399
class TestOptionWithSIUnitConverter(tests.TestCase, TestOptionConverterMixin):
2401
def get_option(self):
2402
return config.Option('foo', help='An integer in SI units.',
2403
from_unicode=config.int_SI_from_store)
2405
def test_convert_invalid(self):
2406
opt = self.get_option()
2407
self.assertConvertInvalid(opt, u'not-a-unit')
2408
self.assertConvertInvalid(opt, u'Gb') # Forgot the int
2409
self.assertConvertInvalid(opt, u'1b') # Forgot the unit
2410
self.assertConvertInvalid(opt, u'1GG')
2411
self.assertConvertInvalid(opt, u'1Mbb')
2412
self.assertConvertInvalid(opt, u'1MM')
2414
def test_convert_valid(self):
2415
opt = self.get_option()
2416
self.assertConverted(int(5e3), opt, u'5kb')
2417
self.assertConverted(int(5e6), opt, u'5M')
2418
self.assertConverted(int(5e6), opt, u'5MB')
2419
self.assertConverted(int(5e9), opt, u'5g')
2420
self.assertConverted(int(5e9), opt, u'5gB')
2421
self.assertConverted(100, opt, u'100')
2424
class TestListOption(tests.TestCase, TestOptionConverterMixin):
2426
def get_option(self):
2427
return config.ListOption('foo', help='A list.')
2429
def test_convert_invalid(self):
2430
opt = self.get_option()
2431
# We don't even try to convert a list into a list, we only expect
2433
self.assertConvertInvalid(opt, [1])
2434
# No string is invalid as all forms can be converted to a list
2436
def test_convert_valid(self):
2437
opt = self.get_option()
2438
# An empty string is an empty list
2439
self.assertConverted([], opt, '') # Using a bare str() just in case
2440
self.assertConverted([], opt, u'')
2442
self.assertConverted([u'True'], opt, u'True')
2444
self.assertConverted([u'42'], opt, u'42')
2446
self.assertConverted([u'bar'], opt, u'bar')
2449
class TestRegistryOption(tests.TestCase, TestOptionConverterMixin):
2451
def get_option(self, registry):
2452
return config.RegistryOption('foo', registry,
2453
help='A registry option.')
2455
def test_convert_invalid(self):
2456
registry = _mod_registry.Registry()
2457
opt = self.get_option(registry)
2458
self.assertConvertInvalid(opt, [1])
2459
self.assertConvertInvalid(opt, u"notregistered")
2461
def test_convert_valid(self):
2462
registry = _mod_registry.Registry()
2463
registry.register("someval", 1234)
2464
opt = self.get_option(registry)
2465
# Using a bare str() just in case
2466
self.assertConverted(1234, opt, "someval")
2467
self.assertConverted(1234, opt, u'someval')
2468
self.assertConverted(None, opt, None)
2470
def test_help(self):
2471
registry = _mod_registry.Registry()
2472
registry.register("someval", 1234, help="some option")
2473
registry.register("dunno", 1234, help="some other option")
2474
opt = self.get_option(registry)
2476
'A registry option.\n'
2478
'The following values are supported:\n'
2479
' dunno - some other option\n'
2480
' someval - some option\n',
2483
def test_get_help_text(self):
2484
registry = _mod_registry.Registry()
2485
registry.register("someval", 1234, help="some option")
2486
registry.register("dunno", 1234, help="some other option")
2487
opt = self.get_option(registry)
2489
'A registry option.\n'
2491
'The following values are supported:\n'
2492
' dunno - some other option\n'
2493
' someval - some option\n',
2494
opt.get_help_text())
2497
class TestOptionRegistry(tests.TestCase):
2500
super(TestOptionRegistry, self).setUp()
2501
# Always start with an empty registry
2502
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2503
self.registry = config.option_registry
2505
def test_register(self):
2506
opt = config.Option('foo')
2507
self.registry.register(opt)
2508
self.assertIs(opt, self.registry.get('foo'))
2510
def test_registered_help(self):
2511
opt = config.Option('foo', help='A simple option')
2512
self.registry.register(opt)
2513
self.assertEquals('A simple option', self.registry.get_help('foo'))
2515
lazy_option = config.Option('lazy_foo', help='Lazy help')
2517
def test_register_lazy(self):
2518
self.registry.register_lazy('lazy_foo', self.__module__,
2519
'TestOptionRegistry.lazy_option')
2520
self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
2522
def test_registered_lazy_help(self):
2523
self.registry.register_lazy('lazy_foo', self.__module__,
2524
'TestOptionRegistry.lazy_option')
2525
self.assertEquals('Lazy help', self.registry.get_help('lazy_foo'))
2528
class TestRegisteredOptions(tests.TestCase):
2529
"""All registered options should verify some constraints."""
2531
scenarios = [(key, {'option_name': key, 'option': option}) for key, option
2532
in config.option_registry.iteritems()]
2535
super(TestRegisteredOptions, self).setUp()
2536
self.registry = config.option_registry
2538
def test_proper_name(self):
2539
# An option should be registered under its own name, this can't be
2540
# checked at registration time for the lazy ones.
2541
self.assertEquals(self.option_name, self.option.name)
2543
def test_help_is_set(self):
2544
option_help = self.registry.get_help(self.option_name)
2545
self.assertNotEquals(None, option_help)
2546
# Come on, think about the user, he really wants to know what the
2548
self.assertIsNot(None, option_help)
2549
self.assertNotEquals('', option_help)
2552
class TestSection(tests.TestCase):
2554
# FIXME: Parametrize so that all sections produced by Stores run these
2555
# tests -- vila 2011-04-01
2557
def test_get_a_value(self):
2558
a_dict = dict(foo='bar')
2559
section = config.Section('myID', a_dict)
2560
self.assertEquals('bar', section.get('foo'))
2562
def test_get_unknown_option(self):
2564
section = config.Section(None, a_dict)
2565
self.assertEquals('out of thin air',
2566
section.get('foo', 'out of thin air'))
2568
def test_options_is_shared(self):
2570
section = config.Section(None, a_dict)
2571
self.assertIs(a_dict, section.options)
2574
class TestMutableSection(tests.TestCase):
2576
scenarios = [('mutable',
2578
lambda opts: config.MutableSection('myID', opts)},),
2582
a_dict = dict(foo='bar')
2583
section = self.get_section(a_dict)
2584
section.set('foo', 'new_value')
2585
self.assertEquals('new_value', section.get('foo'))
2586
# The change appears in the shared section
2587
self.assertEquals('new_value', a_dict.get('foo'))
2588
# We keep track of the change
2589
self.assertTrue('foo' in section.orig)
2590
self.assertEquals('bar', section.orig.get('foo'))
2592
def test_set_preserve_original_once(self):
2593
a_dict = dict(foo='bar')
2594
section = self.get_section(a_dict)
2595
section.set('foo', 'first_value')
2596
section.set('foo', 'second_value')
2597
# We keep track of the original value
2598
self.assertTrue('foo' in section.orig)
2599
self.assertEquals('bar', section.orig.get('foo'))
2601
def test_remove(self):
2602
a_dict = dict(foo='bar')
2603
section = self.get_section(a_dict)
2604
section.remove('foo')
2605
# We get None for unknown options via the default value
2606
self.assertEquals(None, section.get('foo'))
2607
# Or we just get the default value
2608
self.assertEquals('unknown', section.get('foo', 'unknown'))
2609
self.assertFalse('foo' in section.options)
2610
# We keep track of the deletion
2611
self.assertTrue('foo' in section.orig)
2612
self.assertEquals('bar', section.orig.get('foo'))
2614
def test_remove_new_option(self):
2616
section = self.get_section(a_dict)
2617
section.set('foo', 'bar')
2618
section.remove('foo')
2619
self.assertFalse('foo' in section.options)
2620
# The option didn't exist initially so it we need to keep track of it
2621
# with a special value
2622
self.assertTrue('foo' in section.orig)
2623
self.assertEquals(config._NewlyCreatedOption, section.orig['foo'])
2626
class TestCommandLineStore(tests.TestCase):
2629
super(TestCommandLineStore, self).setUp()
2630
self.store = config.CommandLineStore()
2631
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2633
def get_section(self):
2634
"""Get the unique section for the command line overrides."""
2635
sections = list(self.store.get_sections())
2636
self.assertLength(1, sections)
2637
store, section = sections[0]
2638
self.assertEquals(self.store, store)
2641
def test_no_override(self):
2642
self.store._from_cmdline([])
2643
section = self.get_section()
2644
self.assertLength(0, list(section.iter_option_names()))
2646
def test_simple_override(self):
2647
self.store._from_cmdline(['a=b'])
2648
section = self.get_section()
2649
self.assertEqual('b', section.get('a'))
2651
def test_list_override(self):
2652
opt = config.ListOption('l')
2653
config.option_registry.register(opt)
2654
self.store._from_cmdline(['l=1,2,3'])
2655
val = self.get_section().get('l')
2656
self.assertEqual('1,2,3', val)
2657
# Reminder: lists should be registered as such explicitely, otherwise
2658
# the conversion needs to be done afterwards.
2659
self.assertEqual(['1', '2', '3'],
2660
opt.convert_from_unicode(self.store, val))
2662
def test_multiple_overrides(self):
2663
self.store._from_cmdline(['a=b', 'x=y'])
2664
section = self.get_section()
2665
self.assertEquals('b', section.get('a'))
2666
self.assertEquals('y', section.get('x'))
2668
def test_wrong_syntax(self):
2669
self.assertRaises(errors.BzrCommandError,
2670
self.store._from_cmdline, ['a=b', 'c'])
2672
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
2674
scenarios = [(key, {'get_store': builder}) for key, builder
2675
in config.test_store_builder_registry.iteritems()] + [
2676
('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
2679
store = self.get_store(self)
2680
if type(store) == config.TransportIniFileStore:
2681
raise tests.TestNotApplicable(
2682
"%s is not a concrete Store implementation"
2683
" so it doesn't need an id" % (store.__class__.__name__,))
2684
self.assertIsNot(None, store.id)
2687
class TestStore(tests.TestCaseWithTransport):
2689
def assertSectionContent(self, expected, (store, section)):
2690
"""Assert that some options have the proper values in a section."""
2691
expected_name, expected_options = expected
2692
self.assertEquals(expected_name, section.id)
2695
dict([(k, section.get(k)) for k in expected_options.keys()]))
2698
class TestReadonlyStore(TestStore):
2700
scenarios = [(key, {'get_store': builder}) for key, builder
2701
in config.test_store_builder_registry.iteritems()]
2703
def test_building_delays_load(self):
2704
store = self.get_store(self)
2705
self.assertEquals(False, store.is_loaded())
2706
store._load_from_string('')
2707
self.assertEquals(True, store.is_loaded())
2709
def test_get_no_sections_for_empty(self):
2710
store = self.get_store(self)
2711
store._load_from_string('')
2712
self.assertEquals([], list(store.get_sections()))
2714
def test_get_default_section(self):
2715
store = self.get_store(self)
2716
store._load_from_string('foo=bar')
2717
sections = list(store.get_sections())
2718
self.assertLength(1, sections)
2719
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2721
def test_get_named_section(self):
2722
store = self.get_store(self)
2723
store._load_from_string('[baz]\nfoo=bar')
2724
sections = list(store.get_sections())
2725
self.assertLength(1, sections)
2726
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2728
def test_load_from_string_fails_for_non_empty_store(self):
2729
store = self.get_store(self)
2730
store._load_from_string('foo=bar')
2731
self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
2734
class TestStoreQuoting(TestStore):
2736
scenarios = [(key, {'get_store': builder}) for key, builder
2737
in config.test_store_builder_registry.iteritems()]
2740
super(TestStoreQuoting, self).setUp()
2741
self.store = self.get_store(self)
2742
# We need a loaded store but any content will do
2743
self.store._load_from_string('')
2745
def assertIdempotent(self, s):
2746
"""Assert that quoting an unquoted string is a no-op and vice-versa.
2748
What matters here is that option values, as they appear in a store, can
2749
be safely round-tripped out of the store and back.
2751
:param s: A string, quoted if required.
2753
self.assertEquals(s, self.store.quote(self.store.unquote(s)))
2754
self.assertEquals(s, self.store.unquote(self.store.quote(s)))
2756
def test_empty_string(self):
2757
if isinstance(self.store, config.IniFileStore):
2758
# configobj._quote doesn't handle empty values
2759
self.assertRaises(AssertionError,
2760
self.assertIdempotent, '')
2762
self.assertIdempotent('')
2763
# But quoted empty strings are ok
2764
self.assertIdempotent('""')
2766
def test_embedded_spaces(self):
2767
self.assertIdempotent('" a b c "')
2769
def test_embedded_commas(self):
2770
self.assertIdempotent('" a , b c "')
2772
def test_simple_comma(self):
2773
if isinstance(self.store, config.IniFileStore):
2774
# configobj requires that lists are special-cased
2775
self.assertRaises(AssertionError,
2776
self.assertIdempotent, ',')
2778
self.assertIdempotent(',')
2779
# When a single comma is required, quoting is also required
2780
self.assertIdempotent('","')
2782
def test_list(self):
2783
if isinstance(self.store, config.IniFileStore):
2784
# configobj requires that lists are special-cased
2785
self.assertRaises(AssertionError,
2786
self.assertIdempotent, 'a,b')
2788
self.assertIdempotent('a,b')
2791
class TestDictFromStore(tests.TestCase):
2793
def test_unquote_not_string(self):
2794
conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
2795
value = conf.get('a_section')
2796
# Urgh, despite 'conf' asking for the no-name section, we get the
2797
# content of another section as a dict o_O
2798
self.assertEquals({'a': '1'}, value)
2799
unquoted = conf.store.unquote(value)
2800
# Which cannot be unquoted but shouldn't crash either (the use cases
2801
# are getting the value or displaying it. In the later case, '%s' will
2803
self.assertEquals({'a': '1'}, unquoted)
2804
self.assertEquals("{u'a': u'1'}", '%s' % (unquoted,))
2807
class TestIniFileStoreContent(tests.TestCaseWithTransport):
2808
"""Simulate loading a config store with content of various encodings.
2810
All files produced by bzr are in utf8 content.
2812
Users may modify them manually and end up with a file that can't be
2813
loaded. We need to issue proper error messages in this case.
2816
invalid_utf8_char = '\xff'
2818
def test_load_utf8(self):
2819
"""Ensure we can load an utf8-encoded file."""
2820
t = self.get_transport()
2821
# From http://pad.lv/799212
2822
unicode_user = u'b\N{Euro Sign}ar'
2823
unicode_content = u'user=%s' % (unicode_user,)
2824
utf8_content = unicode_content.encode('utf8')
2825
# Store the raw content in the config file
2826
t.put_bytes('foo.conf', utf8_content)
2827
store = config.TransportIniFileStore(t, 'foo.conf')
2829
stack = config.Stack([store.get_sections], store)
2830
self.assertEquals(unicode_user, stack.get('user'))
2832
def test_load_non_ascii(self):
2833
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2834
t = self.get_transport()
2835
t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
2836
store = config.TransportIniFileStore(t, 'foo.conf')
2837
self.assertRaises(errors.ConfigContentError, store.load)
2839
def test_load_erroneous_content(self):
2840
"""Ensure we display a proper error on content that can't be parsed."""
2841
t = self.get_transport()
2842
t.put_bytes('foo.conf', '[open_section\n')
2843
store = config.TransportIniFileStore(t, 'foo.conf')
2844
self.assertRaises(errors.ParseConfigError, store.load)
2846
def test_load_permission_denied(self):
2847
"""Ensure we get warned when trying to load an inaccessible file."""
2850
warnings.append(args[0] % args[1:])
2851
self.overrideAttr(trace, 'warning', warning)
2853
t = self.get_transport()
2855
def get_bytes(relpath):
2856
raise errors.PermissionDenied(relpath, "")
2857
t.get_bytes = get_bytes
2858
store = config.TransportIniFileStore(t, 'foo.conf')
2859
self.assertRaises(errors.PermissionDenied, store.load)
2862
[u'Permission denied while trying to load configuration store %s.'
2863
% store.external_url()])
2866
class TestIniConfigContent(tests.TestCaseWithTransport):
2867
"""Simulate loading a IniBasedConfig with content of various encodings.
2869
All files produced by bzr are in utf8 content.
2871
Users may modify them manually and end up with a file that can't be
2872
loaded. We need to issue proper error messages in this case.
2875
invalid_utf8_char = '\xff'
2877
def test_load_utf8(self):
2878
"""Ensure we can load an utf8-encoded file."""
2879
# From http://pad.lv/799212
2880
unicode_user = u'b\N{Euro Sign}ar'
2881
unicode_content = u'user=%s' % (unicode_user,)
2882
utf8_content = unicode_content.encode('utf8')
2883
# Store the raw content in the config file
2884
with open('foo.conf', 'wb') as f:
2885
f.write(utf8_content)
2886
conf = config.IniBasedConfig(file_name='foo.conf')
2887
self.assertEquals(unicode_user, conf.get_user_option('user'))
2889
def test_load_badly_encoded_content(self):
2890
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2891
with open('foo.conf', 'wb') as f:
2892
f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
2893
conf = config.IniBasedConfig(file_name='foo.conf')
2894
self.assertRaises(errors.ConfigContentError, conf._get_parser)
2896
def test_load_erroneous_content(self):
2897
"""Ensure we display a proper error on content that can't be parsed."""
2898
with open('foo.conf', 'wb') as f:
2899
f.write('[open_section\n')
2900
conf = config.IniBasedConfig(file_name='foo.conf')
2901
self.assertRaises(errors.ParseConfigError, conf._get_parser)
2904
class TestMutableStore(TestStore):
2906
scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
2907
in config.test_store_builder_registry.iteritems()]
2910
super(TestMutableStore, self).setUp()
2911
self.transport = self.get_transport()
2913
def has_store(self, store):
2914
store_basename = urlutils.relative_url(self.transport.external_url(),
2915
store.external_url())
2916
return self.transport.has(store_basename)
2918
def test_save_empty_creates_no_file(self):
2919
# FIXME: There should be a better way than relying on the test
2920
# parametrization to identify branch.conf -- vila 2011-0526
2921
if self.store_id in ('branch', 'remote_branch'):
2922
raise tests.TestNotApplicable(
2923
'branch.conf is *always* created when a branch is initialized')
2924
store = self.get_store(self)
2926
self.assertEquals(False, self.has_store(store))
2928
def test_save_emptied_succeeds(self):
2929
store = self.get_store(self)
2930
store._load_from_string('foo=bar\n')
2931
# FIXME: There should be a better way than relying on the test
2932
# parametrization to identify branch.conf -- vila 2011-0526
2933
if self.store_id in ('branch', 'remote_branch'):
2934
# branch stores requires write locked branches
2935
self.addCleanup(store.branch.lock_write().unlock)
2936
section = store.get_mutable_section(None)
2937
section.remove('foo')
2939
self.assertEquals(True, self.has_store(store))
2940
modified_store = self.get_store(self)
2941
sections = list(modified_store.get_sections())
2942
self.assertLength(0, sections)
2944
def test_save_with_content_succeeds(self):
2945
# FIXME: There should be a better way than relying on the test
2946
# parametrization to identify branch.conf -- vila 2011-0526
2947
if self.store_id in ('branch', 'remote_branch'):
2948
raise tests.TestNotApplicable(
2949
'branch.conf is *always* created when a branch is initialized')
2950
store = self.get_store(self)
2951
store._load_from_string('foo=bar\n')
2952
self.assertEquals(False, self.has_store(store))
2954
self.assertEquals(True, self.has_store(store))
2955
modified_store = self.get_store(self)
2956
sections = list(modified_store.get_sections())
2957
self.assertLength(1, sections)
2958
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2960
def test_set_option_in_empty_store(self):
2961
store = self.get_store(self)
2962
# FIXME: There should be a better way than relying on the test
2963
# parametrization to identify branch.conf -- vila 2011-0526
2964
if self.store_id in ('branch', 'remote_branch'):
2965
# branch stores requires write locked branches
2966
self.addCleanup(store.branch.lock_write().unlock)
2967
section = store.get_mutable_section(None)
2968
section.set('foo', 'bar')
2970
modified_store = self.get_store(self)
2971
sections = list(modified_store.get_sections())
2972
self.assertLength(1, sections)
2973
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2975
def test_set_option_in_default_section(self):
2976
store = self.get_store(self)
2977
store._load_from_string('')
2978
# FIXME: There should be a better way than relying on the test
2979
# parametrization to identify branch.conf -- vila 2011-0526
2980
if self.store_id in ('branch', 'remote_branch'):
2981
# branch stores requires write locked branches
2982
self.addCleanup(store.branch.lock_write().unlock)
2983
section = store.get_mutable_section(None)
2984
section.set('foo', 'bar')
2986
modified_store = self.get_store(self)
2987
sections = list(modified_store.get_sections())
2988
self.assertLength(1, sections)
2989
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2991
def test_set_option_in_named_section(self):
2992
store = self.get_store(self)
2993
store._load_from_string('')
2994
# FIXME: There should be a better way than relying on the test
2995
# parametrization to identify branch.conf -- vila 2011-0526
2996
if self.store_id in ('branch', 'remote_branch'):
2997
# branch stores requires write locked branches
2998
self.addCleanup(store.branch.lock_write().unlock)
2999
section = store.get_mutable_section('baz')
3000
section.set('foo', 'bar')
3002
modified_store = self.get_store(self)
3003
sections = list(modified_store.get_sections())
3004
self.assertLength(1, sections)
3005
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
3007
def test_load_hook(self):
3008
# First, we need to ensure that the store exists
3009
store = self.get_store(self)
3010
# FIXME: There should be a better way than relying on the test
3011
# parametrization to identify branch.conf -- vila 2011-0526
3012
if self.store_id in ('branch', 'remote_branch'):
3013
# branch stores requires write locked branches
3014
self.addCleanup(store.branch.lock_write().unlock)
3015
section = store.get_mutable_section('baz')
3016
section.set('foo', 'bar')
3018
# Now we can try to load it
3019
store = self.get_store(self)
3023
config.ConfigHooks.install_named_hook('load', hook, None)
3024
self.assertLength(0, calls)
3026
self.assertLength(1, calls)
3027
self.assertEquals((store,), calls[0])
3029
def test_save_hook(self):
3033
config.ConfigHooks.install_named_hook('save', hook, None)
3034
self.assertLength(0, calls)
3035
store = self.get_store(self)
3036
# FIXME: There should be a better way than relying on the test
3037
# parametrization to identify branch.conf -- vila 2011-0526
3038
if self.store_id in ('branch', 'remote_branch'):
3039
# branch stores requires write locked branches
3040
self.addCleanup(store.branch.lock_write().unlock)
3041
section = store.get_mutable_section('baz')
3042
section.set('foo', 'bar')
3044
self.assertLength(1, calls)
3045
self.assertEquals((store,), calls[0])
3047
def test_set_mark_dirty(self):
3048
stack = config.MemoryStack('')
3049
self.assertLength(0, stack.store.dirty_sections)
3050
stack.set('foo', 'baz')
3051
self.assertLength(1, stack.store.dirty_sections)
3052
self.assertTrue(stack.store._need_saving())
3054
def test_remove_mark_dirty(self):
3055
stack = config.MemoryStack('foo=bar')
3056
self.assertLength(0, stack.store.dirty_sections)
3058
self.assertLength(1, stack.store.dirty_sections)
3059
self.assertTrue(stack.store._need_saving())
3062
class TestStoreSaveChanges(tests.TestCaseWithTransport):
3063
"""Tests that config changes are kept in memory and saved on-demand."""
3066
super(TestStoreSaveChanges, self).setUp()
3067
self.transport = self.get_transport()
3068
# Most of the tests involve two stores pointing to the same persistent
3069
# storage to observe the effects of concurrent changes
3070
self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
3071
self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
3074
self.warnings.append(args[0] % args[1:])
3075
self.overrideAttr(trace, 'warning', warning)
3077
def has_store(self, store):
3078
store_basename = urlutils.relative_url(self.transport.external_url(),
3079
store.external_url())
3080
return self.transport.has(store_basename)
3082
def get_stack(self, store):
3083
# Any stack will do as long as it uses the right store, just a single
3084
# no-name section is enough
3085
return config.Stack([store.get_sections], store)
3087
def test_no_changes_no_save(self):
3088
s = self.get_stack(self.st1)
3089
s.store.save_changes()
3090
self.assertEquals(False, self.has_store(self.st1))
3092
def test_unrelated_concurrent_update(self):
3093
s1 = self.get_stack(self.st1)
3094
s2 = self.get_stack(self.st2)
3095
s1.set('foo', 'bar')
3096
s2.set('baz', 'quux')
3098
# Changes don't propagate magically
3099
self.assertEquals(None, s1.get('baz'))
3100
s2.store.save_changes()
3101
self.assertEquals('quux', s2.get('baz'))
3102
# Changes are acquired when saving
3103
self.assertEquals('bar', s2.get('foo'))
3104
# Since there is no overlap, no warnings are emitted
3105
self.assertLength(0, self.warnings)
3107
def test_concurrent_update_modified(self):
3108
s1 = self.get_stack(self.st1)
3109
s2 = self.get_stack(self.st2)
3110
s1.set('foo', 'bar')
3111
s2.set('foo', 'baz')
3114
s2.store.save_changes()
3115
self.assertEquals('baz', s2.get('foo'))
3116
# But the user get a warning
3117
self.assertLength(1, self.warnings)
3118
warning = self.warnings[0]
3119
self.assertStartsWith(warning, 'Option foo in section None')
3120
self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
3121
' The baz value will be saved.')
3123
def test_concurrent_deletion(self):
3124
self.st1._load_from_string('foo=bar')
3126
s1 = self.get_stack(self.st1)
3127
s2 = self.get_stack(self.st2)
3130
s1.store.save_changes()
3132
self.assertLength(0, self.warnings)
3133
s2.store.save_changes()
3135
self.assertLength(1, self.warnings)
3136
warning = self.warnings[0]
3137
self.assertStartsWith(warning, 'Option foo in section None')
3138
self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
3139
' The <DELETED> value will be saved.')
3142
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
3144
def get_store(self):
3145
return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3147
def test_get_quoted_string(self):
3148
store = self.get_store()
3149
store._load_from_string('foo= " abc "')
3150
stack = config.Stack([store.get_sections])
3151
self.assertEquals(' abc ', stack.get('foo'))
3153
def test_set_quoted_string(self):
3154
store = self.get_store()
3155
stack = config.Stack([store.get_sections], store)
3156
stack.set('foo', ' a b c ')
3158
self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
3161
class TestTransportIniFileStore(TestStore):
3163
def test_loading_unknown_file_fails(self):
3164
store = config.TransportIniFileStore(self.get_transport(),
3166
self.assertRaises(errors.NoSuchFile, store.load)
3168
def test_invalid_content(self):
3169
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3170
self.assertEquals(False, store.is_loaded())
3171
exc = self.assertRaises(
3172
errors.ParseConfigError, store._load_from_string,
3173
'this is invalid !')
3174
self.assertEndsWith(exc.filename, 'foo.conf')
3175
# And the load failed
3176
self.assertEquals(False, store.is_loaded())
3178
def test_get_embedded_sections(self):
3179
# A more complicated example (which also shows that section names and
3180
# option names share the same name space...)
3181
# FIXME: This should be fixed by forbidding dicts as values ?
3182
# -- vila 2011-04-05
3183
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3184
store._load_from_string('''
3188
foo_in_DEFAULT=foo_DEFAULT
3196
sections = list(store.get_sections())
3197
self.assertLength(4, sections)
3198
# The default section has no name.
3199
# List values are provided as strings and need to be explicitly
3200
# converted by specifying from_unicode=list_from_store at option
3202
self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
3204
self.assertSectionContent(
3205
('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
3206
self.assertSectionContent(
3207
('bar', {'foo_in_bar': 'barbar'}), sections[2])
3208
# sub sections are provided as embedded dicts.
3209
self.assertSectionContent(
3210
('baz', {'foo_in_baz': 'barbaz', 'qux': {'foo_in_qux': 'quux'}}),
3214
class TestLockableIniFileStore(TestStore):
3216
def test_create_store_in_created_dir(self):
3217
self.assertPathDoesNotExist('dir')
3218
t = self.get_transport('dir/subdir')
3219
store = config.LockableIniFileStore(t, 'foo.conf')
3220
store.get_mutable_section(None).set('foo', 'bar')
3222
self.assertPathExists('dir/subdir')
3225
class TestConcurrentStoreUpdates(TestStore):
3226
"""Test that Stores properly handle conccurent updates.
3228
New Store implementation may fail some of these tests but until such
3229
implementations exist it's hard to properly filter them from the scenarios
3230
applied here. If you encounter such a case, contact the bzr devs.
3233
scenarios = [(key, {'get_stack': builder}) for key, builder
3234
in config.test_stack_builder_registry.iteritems()]
3237
super(TestConcurrentStoreUpdates, self).setUp()
3238
self.stack = self.get_stack(self)
3239
if not isinstance(self.stack, config._CompatibleStack):
3240
raise tests.TestNotApplicable(
3241
'%s is not meant to be compatible with the old config design'
3243
self.stack.set('one', '1')
3244
self.stack.set('two', '2')
3246
self.stack.store.save()
3248
def test_simple_read_access(self):
3249
self.assertEquals('1', self.stack.get('one'))
3251
def test_simple_write_access(self):
3252
self.stack.set('one', 'one')
3253
self.assertEquals('one', self.stack.get('one'))
3255
def test_listen_to_the_last_speaker(self):
3257
c2 = self.get_stack(self)
3258
c1.set('one', 'ONE')
3259
c2.set('two', 'TWO')
3260
self.assertEquals('ONE', c1.get('one'))
3261
self.assertEquals('TWO', c2.get('two'))
3262
# The second update respect the first one
3263
self.assertEquals('ONE', c2.get('one'))
3265
def test_last_speaker_wins(self):
3266
# If the same config is not shared, the same variable modified twice
3267
# can only see a single result.
3269
c2 = self.get_stack(self)
3272
self.assertEquals('c2', c2.get('one'))
3273
# The first modification is still available until another refresh
3275
self.assertEquals('c1', c1.get('one'))
3276
c1.set('two', 'done')
3277
self.assertEquals('c2', c1.get('one'))
3279
def test_writes_are_serialized(self):
3281
c2 = self.get_stack(self)
3283
# We spawn a thread that will pause *during* the config saving.
3284
before_writing = threading.Event()
3285
after_writing = threading.Event()
3286
writing_done = threading.Event()
3287
c1_save_without_locking_orig = c1.store.save_without_locking
3288
def c1_save_without_locking():
3289
before_writing.set()
3290
c1_save_without_locking_orig()
3291
# The lock is held. We wait for the main thread to decide when to
3293
after_writing.wait()
3294
c1.store.save_without_locking = c1_save_without_locking
3298
t1 = threading.Thread(target=c1_set)
3299
# Collect the thread after the test
3300
self.addCleanup(t1.join)
3301
# Be ready to unblock the thread if the test goes wrong
3302
self.addCleanup(after_writing.set)
3304
before_writing.wait()
3305
self.assertRaises(errors.LockContention,
3306
c2.set, 'one', 'c2')
3307
self.assertEquals('c1', c1.get('one'))
3308
# Let the lock be released
3312
self.assertEquals('c2', c2.get('one'))
3314
def test_read_while_writing(self):
3316
# We spawn a thread that will pause *during* the write
3317
ready_to_write = threading.Event()
3318
do_writing = threading.Event()
3319
writing_done = threading.Event()
3320
# We override the _save implementation so we know the store is locked
3321
c1_save_without_locking_orig = c1.store.save_without_locking
3322
def c1_save_without_locking():
3323
ready_to_write.set()
3324
# The lock is held. We wait for the main thread to decide when to
3327
c1_save_without_locking_orig()
3329
c1.store.save_without_locking = c1_save_without_locking
3332
t1 = threading.Thread(target=c1_set)
3333
# Collect the thread after the test
3334
self.addCleanup(t1.join)
3335
# Be ready to unblock the thread if the test goes wrong
3336
self.addCleanup(do_writing.set)
3338
# Ensure the thread is ready to write
3339
ready_to_write.wait()
3340
self.assertEquals('c1', c1.get('one'))
3341
# If we read during the write, we get the old value
3342
c2 = self.get_stack(self)
3343
self.assertEquals('1', c2.get('one'))
3344
# Let the writing occur and ensure it occurred
3347
# Now we get the updated value
3348
c3 = self.get_stack(self)
3349
self.assertEquals('c1', c3.get('one'))
3351
# FIXME: It may be worth looking into removing the lock dir when it's not
3352
# needed anymore and look at possible fallouts for concurrent lockers. This
3353
# will matter if/when we use config files outside of bazaar directories
3354
# (.bazaar or .bzr) -- vila 20110-04-111
3357
class TestSectionMatcher(TestStore):
3359
scenarios = [('location', {'matcher': config.LocationMatcher}),
3360
('id', {'matcher': config.NameMatcher}),]
3363
super(TestSectionMatcher, self).setUp()
3364
# Any simple store is good enough
3365
self.get_store = config.test_store_builder_registry.get('configobj')
3367
def test_no_matches_for_empty_stores(self):
3368
store = self.get_store(self)
3369
store._load_from_string('')
3370
matcher = self.matcher(store, '/bar')
3371
self.assertEquals([], list(matcher.get_sections()))
3373
def test_build_doesnt_load_store(self):
3374
store = self.get_store(self)
3375
matcher = self.matcher(store, '/bar')
3376
self.assertFalse(store.is_loaded())
3379
class TestLocationSection(tests.TestCase):
3381
def get_section(self, options, extra_path):
3382
section = config.Section('foo', options)
3383
return config.LocationSection(section, extra_path)
3385
def test_simple_option(self):
3386
section = self.get_section({'foo': 'bar'}, '')
3387
self.assertEquals('bar', section.get('foo'))
3389
def test_option_with_extra_path(self):
3390
section = self.get_section({'foo': 'bar', 'foo:policy': 'appendpath'},
3392
self.assertEquals('bar/baz', section.get('foo'))
3394
def test_invalid_policy(self):
3395
section = self.get_section({'foo': 'bar', 'foo:policy': 'die'},
3397
# invalid policies are ignored
3398
self.assertEquals('bar', section.get('foo'))
3401
class TestLocationMatcher(TestStore):
3404
super(TestLocationMatcher, self).setUp()
3405
# Any simple store is good enough
3406
self.get_store = config.test_store_builder_registry.get('configobj')
3408
def test_unrelated_section_excluded(self):
3409
store = self.get_store(self)
3410
store._load_from_string('''
3418
section=/foo/bar/baz
3422
self.assertEquals(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
3424
[section.id for _, section in store.get_sections()])
3425
matcher = config.LocationMatcher(store, '/foo/bar/quux')
3426
sections = [section for _, section in matcher.get_sections()]
3427
self.assertEquals(['/foo/bar', '/foo'],
3428
[section.id for section in sections])
3429
self.assertEquals(['quux', 'bar/quux'],
3430
[section.extra_path for section in sections])
3432
def test_more_specific_sections_first(self):
3433
store = self.get_store(self)
3434
store._load_from_string('''
3440
self.assertEquals(['/foo', '/foo/bar'],
3441
[section.id for _, section in store.get_sections()])
3442
matcher = config.LocationMatcher(store, '/foo/bar/baz')
3443
sections = [section for _, section in matcher.get_sections()]
3444
self.assertEquals(['/foo/bar', '/foo'],
3445
[section.id for section in sections])
3446
self.assertEquals(['baz', 'bar/baz'],
3447
[section.extra_path for section in sections])
3449
def test_appendpath_in_no_name_section(self):
3450
# It's a bit weird to allow appendpath in a no-name section, but
3451
# someone may found a use for it
3452
store = self.get_store(self)
3453
store._load_from_string('''
3455
foo:policy = appendpath
3457
matcher = config.LocationMatcher(store, 'dir/subdir')
3458
sections = list(matcher.get_sections())
3459
self.assertLength(1, sections)
3460
self.assertEquals('bar/dir/subdir', sections[0][1].get('foo'))
3462
def test_file_urls_are_normalized(self):
3463
store = self.get_store(self)
3464
if sys.platform == 'win32':
3465
expected_url = 'file:///C:/dir/subdir'
3466
expected_location = 'C:/dir/subdir'
3468
expected_url = 'file:///dir/subdir'
3469
expected_location = '/dir/subdir'
3470
matcher = config.LocationMatcher(store, expected_url)
3471
self.assertEquals(expected_location, matcher.location)
3474
class TestStartingPathMatcher(TestStore):
3477
super(TestStartingPathMatcher, self).setUp()
3478
# Any simple store is good enough
3479
self.store = config.IniFileStore()
3481
def assertSectionIDs(self, expected, location, content):
3482
self.store._load_from_string(content)
3483
matcher = config.StartingPathMatcher(self.store, location)
3484
sections = list(matcher.get_sections())
3485
self.assertLength(len(expected), sections)
3486
self.assertEqual(expected, [section.id for _, section in sections])
3489
def test_empty(self):
3490
self.assertSectionIDs([], self.get_url(), '')
3492
def test_url_vs_local_paths(self):
3493
# The matcher location is an url and the section names are local paths
3494
sections = self.assertSectionIDs(['/foo/bar', '/foo'],
3495
'file:///foo/bar/baz', '''\
3500
def test_local_path_vs_url(self):
3501
# The matcher location is a local path and the section names are urls
3502
sections = self.assertSectionIDs(['file:///foo/bar', 'file:///foo'],
3503
'/foo/bar/baz', '''\
3509
def test_no_name_section_included_when_present(self):
3510
# Note that other tests will cover the case where the no-name section
3511
# is empty and as such, not included.
3512
sections = self.assertSectionIDs(['/foo/bar', '/foo', None],
3513
'/foo/bar/baz', '''\
3514
option = defined so the no-name section exists
3518
self.assertEquals(['baz', 'bar/baz', '/foo/bar/baz'],
3519
[s.locals['relpath'] for _, s in sections])
3521
def test_order_reversed(self):
3522
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3527
def test_unrelated_section_excluded(self):
3528
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3534
def test_glob_included(self):
3535
sections = self.assertSectionIDs(['/foo/*/baz', '/foo/b*', '/foo'],
3536
'/foo/bar/baz', '''\
3542
# Note that 'baz' as a relpath for /foo/b* is not fully correct, but
3543
# nothing really is... as far using {relpath} to append it to something
3544
# else, this seems good enough though.
3545
self.assertEquals(['', 'baz', 'bar/baz'],
3546
[s.locals['relpath'] for _, s in sections])
3548
def test_respect_order(self):
3549
self.assertSectionIDs(['/foo', '/foo/b*', '/foo/*/baz'],
3550
'/foo/bar/baz', '''\
3558
class TestNameMatcher(TestStore):
3561
super(TestNameMatcher, self).setUp()
3562
self.matcher = config.NameMatcher
3563
# Any simple store is good enough
3564
self.get_store = config.test_store_builder_registry.get('configobj')
3566
def get_matching_sections(self, name):
3567
store = self.get_store(self)
3568
store._load_from_string('''
3576
matcher = self.matcher(store, name)
3577
return list(matcher.get_sections())
3579
def test_matching(self):
3580
sections = self.get_matching_sections('foo')
3581
self.assertLength(1, sections)
3582
self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
3584
def test_not_matching(self):
3585
sections = self.get_matching_sections('baz')
3586
self.assertLength(0, sections)
3589
class TestBaseStackGet(tests.TestCase):
3592
super(TestBaseStackGet, self).setUp()
3593
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3595
def test_get_first_definition(self):
3596
store1 = config.IniFileStore()
3597
store1._load_from_string('foo=bar')
3598
store2 = config.IniFileStore()
3599
store2._load_from_string('foo=baz')
3600
conf = config.Stack([store1.get_sections, store2.get_sections])
3601
self.assertEquals('bar', conf.get('foo'))
3603
def test_get_with_registered_default_value(self):
3604
config.option_registry.register(config.Option('foo', default='bar'))
3605
conf_stack = config.Stack([])
3606
self.assertEquals('bar', conf_stack.get('foo'))
3608
def test_get_without_registered_default_value(self):
3609
config.option_registry.register(config.Option('foo'))
3610
conf_stack = config.Stack([])
3611
self.assertEquals(None, conf_stack.get('foo'))
3613
def test_get_without_default_value_for_not_registered(self):
3614
conf_stack = config.Stack([])
3615
self.assertEquals(None, conf_stack.get('foo'))
3617
def test_get_for_empty_section_callable(self):
3618
conf_stack = config.Stack([lambda : []])
3619
self.assertEquals(None, conf_stack.get('foo'))
3621
def test_get_for_broken_callable(self):
3622
# Trying to use and invalid callable raises an exception on first use
3623
conf_stack = config.Stack([object])
3624
self.assertRaises(TypeError, conf_stack.get, 'foo')
3627
class TestStackWithSimpleStore(tests.TestCase):
3630
super(TestStackWithSimpleStore, self).setUp()
3631
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3632
self.registry = config.option_registry
3634
def get_conf(self, content=None):
3635
return config.MemoryStack(content)
3637
def test_override_value_from_env(self):
3638
self.registry.register(
3639
config.Option('foo', default='bar', override_from_env=['FOO']))
3640
self.overrideEnv('FOO', 'quux')
3641
# Env variable provides a default taking over the option one
3642
conf = self.get_conf('foo=store')
3643
self.assertEquals('quux', conf.get('foo'))
3645
def test_first_override_value_from_env_wins(self):
3646
self.registry.register(
3647
config.Option('foo', default='bar',
3648
override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
3649
self.overrideEnv('FOO', 'foo')
3650
self.overrideEnv('BAZ', 'baz')
3651
# The first env var set wins
3652
conf = self.get_conf('foo=store')
3653
self.assertEquals('foo', conf.get('foo'))
3656
class TestMemoryStack(tests.TestCase):
3659
conf = config.MemoryStack('foo=bar')
3660
self.assertEquals('bar', conf.get('foo'))
3663
conf = config.MemoryStack('foo=bar')
3664
conf.set('foo', 'baz')
3665
self.assertEquals('baz', conf.get('foo'))
3667
def test_no_content(self):
3668
conf = config.MemoryStack()
3669
# No content means no loading
3670
self.assertFalse(conf.store.is_loaded())
3671
self.assertRaises(NotImplementedError, conf.get, 'foo')
3672
# But a content can still be provided
3673
conf.store._load_from_string('foo=bar')
3674
self.assertEquals('bar', conf.get('foo'))
3677
class TestStackIterSections(tests.TestCase):
3679
def test_empty_stack(self):
3680
conf = config.Stack([])
3681
sections = list(conf.iter_sections())
3682
self.assertLength(0, sections)
3684
def test_empty_store(self):
3685
store = config.IniFileStore()
3686
store._load_from_string('')
3687
conf = config.Stack([store.get_sections])
3688
sections = list(conf.iter_sections())
3689
self.assertLength(0, sections)
3691
def test_simple_store(self):
3692
store = config.IniFileStore()
3693
store._load_from_string('foo=bar')
3694
conf = config.Stack([store.get_sections])
3695
tuples = list(conf.iter_sections())
3696
self.assertLength(1, tuples)
3697
(found_store, found_section) = tuples[0]
3698
self.assertIs(store, found_store)
3700
def test_two_stores(self):
3701
store1 = config.IniFileStore()
3702
store1._load_from_string('foo=bar')
3703
store2 = config.IniFileStore()
3704
store2._load_from_string('bar=qux')
3705
conf = config.Stack([store1.get_sections, store2.get_sections])
3706
tuples = list(conf.iter_sections())
3707
self.assertLength(2, tuples)
3708
self.assertIs(store1, tuples[0][0])
3709
self.assertIs(store2, tuples[1][0])
3712
class TestStackWithTransport(tests.TestCaseWithTransport):
3714
scenarios = [(key, {'get_stack': builder}) for key, builder
3715
in config.test_stack_builder_registry.iteritems()]
3718
class TestConcreteStacks(TestStackWithTransport):
3720
def test_build_stack(self):
3721
# Just a smoke test to help debug builders
3722
stack = self.get_stack(self)
3725
class TestStackGet(TestStackWithTransport):
3728
super(TestStackGet, self).setUp()
3729
self.conf = self.get_stack(self)
3731
def test_get_for_empty_stack(self):
3732
self.assertEquals(None, self.conf.get('foo'))
3734
def test_get_hook(self):
3735
self.conf.set('foo', 'bar')
3739
config.ConfigHooks.install_named_hook('get', hook, None)
3740
self.assertLength(0, calls)
3741
value = self.conf.get('foo')
3742
self.assertEquals('bar', value)
3743
self.assertLength(1, calls)
3744
self.assertEquals((self.conf, 'foo', 'bar'), calls[0])
3747
class TestStackGetWithConverter(tests.TestCase):
3750
super(TestStackGetWithConverter, self).setUp()
3751
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3752
self.registry = config.option_registry
3754
def get_conf(self, content=None):
3755
return config.MemoryStack(content)
3757
def register_bool_option(self, name, default=None, default_from_env=None):
3758
b = config.Option(name, help='A boolean.',
3759
default=default, default_from_env=default_from_env,
3760
from_unicode=config.bool_from_store)
3761
self.registry.register(b)
3763
def test_get_default_bool_None(self):
3764
self.register_bool_option('foo')
3765
conf = self.get_conf('')
3766
self.assertEquals(None, conf.get('foo'))
3768
def test_get_default_bool_True(self):
3769
self.register_bool_option('foo', u'True')
3770
conf = self.get_conf('')
3771
self.assertEquals(True, conf.get('foo'))
3773
def test_get_default_bool_False(self):
3774
self.register_bool_option('foo', False)
3775
conf = self.get_conf('')
3776
self.assertEquals(False, conf.get('foo'))
3778
def test_get_default_bool_False_as_string(self):
3779
self.register_bool_option('foo', u'False')
3780
conf = self.get_conf('')
3781
self.assertEquals(False, conf.get('foo'))
3783
def test_get_default_bool_from_env_converted(self):
3784
self.register_bool_option('foo', u'True', default_from_env=['FOO'])
3785
self.overrideEnv('FOO', 'False')
3786
conf = self.get_conf('')
3787
self.assertEquals(False, conf.get('foo'))
3789
def test_get_default_bool_when_conversion_fails(self):
3790
self.register_bool_option('foo', default='True')
3791
conf = self.get_conf('foo=invalid boolean')
3792
self.assertEquals(True, conf.get('foo'))
3794
def register_integer_option(self, name,
3795
default=None, default_from_env=None):
3796
i = config.Option(name, help='An integer.',
3797
default=default, default_from_env=default_from_env,
3798
from_unicode=config.int_from_store)
3799
self.registry.register(i)
3801
def test_get_default_integer_None(self):
3802
self.register_integer_option('foo')
3803
conf = self.get_conf('')
3804
self.assertEquals(None, conf.get('foo'))
3806
def test_get_default_integer(self):
3807
self.register_integer_option('foo', 42)
3808
conf = self.get_conf('')
3809
self.assertEquals(42, conf.get('foo'))
3811
def test_get_default_integer_as_string(self):
3812
self.register_integer_option('foo', u'42')
3813
conf = self.get_conf('')
3814
self.assertEquals(42, conf.get('foo'))
3816
def test_get_default_integer_from_env(self):
3817
self.register_integer_option('foo', default_from_env=['FOO'])
3818
self.overrideEnv('FOO', '18')
3819
conf = self.get_conf('')
3820
self.assertEquals(18, conf.get('foo'))
3822
def test_get_default_integer_when_conversion_fails(self):
3823
self.register_integer_option('foo', default='12')
3824
conf = self.get_conf('foo=invalid integer')
3825
self.assertEquals(12, conf.get('foo'))
3827
def register_list_option(self, name, default=None, default_from_env=None):
3828
l = config.ListOption(name, help='A list.', default=default,
3829
default_from_env=default_from_env)
3830
self.registry.register(l)
3832
def test_get_default_list_None(self):
3833
self.register_list_option('foo')
3834
conf = self.get_conf('')
3835
self.assertEquals(None, conf.get('foo'))
3837
def test_get_default_list_empty(self):
3838
self.register_list_option('foo', '')
3839
conf = self.get_conf('')
3840
self.assertEquals([], conf.get('foo'))
3842
def test_get_default_list_from_env(self):
3843
self.register_list_option('foo', default_from_env=['FOO'])
3844
self.overrideEnv('FOO', '')
3845
conf = self.get_conf('')
3846
self.assertEquals([], conf.get('foo'))
3848
def test_get_with_list_converter_no_item(self):
3849
self.register_list_option('foo', None)
3850
conf = self.get_conf('foo=,')
3851
self.assertEquals([], conf.get('foo'))
3853
def test_get_with_list_converter_many_items(self):
3854
self.register_list_option('foo', None)
3855
conf = self.get_conf('foo=m,o,r,e')
3856
self.assertEquals(['m', 'o', 'r', 'e'], conf.get('foo'))
3858
def test_get_with_list_converter_embedded_spaces_many_items(self):
3859
self.register_list_option('foo', None)
3860
conf = self.get_conf('foo=" bar", "baz "')
3861
self.assertEquals([' bar', 'baz '], conf.get('foo'))
3863
def test_get_with_list_converter_stripped_spaces_many_items(self):
3864
self.register_list_option('foo', None)
3865
conf = self.get_conf('foo= bar , baz ')
3866
self.assertEquals(['bar', 'baz'], conf.get('foo'))
3869
class TestIterOptionRefs(tests.TestCase):
3870
"""iter_option_refs is a bit unusual, document some cases."""
3872
def assertRefs(self, expected, string):
3873
self.assertEquals(expected, list(config.iter_option_refs(string)))
3875
def test_empty(self):
3876
self.assertRefs([(False, '')], '')
3878
def test_no_refs(self):
3879
self.assertRefs([(False, 'foo bar')], 'foo bar')
3881
def test_single_ref(self):
3882
self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
3884
def test_broken_ref(self):
3885
self.assertRefs([(False, '{foo')], '{foo')
3887
def test_embedded_ref(self):
3888
self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
3891
def test_two_refs(self):
3892
self.assertRefs([(False, ''), (True, '{foo}'),
3893
(False, ''), (True, '{bar}'),
3897
def test_newline_in_refs_are_not_matched(self):
3898
self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
3901
class TestStackExpandOptions(tests.TestCaseWithTransport):
3904
super(TestStackExpandOptions, self).setUp()
3905
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3906
self.registry = config.option_registry
3907
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3908
self.conf = config.Stack([store.get_sections], store)
3910
def assertExpansion(self, expected, string, env=None):
3911
self.assertEquals(expected, self.conf.expand_options(string, env))
3913
def test_no_expansion(self):
3914
self.assertExpansion('foo', 'foo')
3916
def test_expand_default_value(self):
3917
self.conf.store._load_from_string('bar=baz')
3918
self.registry.register(config.Option('foo', default=u'{bar}'))
3919
self.assertEquals('baz', self.conf.get('foo', expand=True))
3921
def test_expand_default_from_env(self):
3922
self.conf.store._load_from_string('bar=baz')
3923
self.registry.register(config.Option('foo', default_from_env=['FOO']))
3924
self.overrideEnv('FOO', '{bar}')
3925
self.assertEquals('baz', self.conf.get('foo', expand=True))
3927
def test_expand_default_on_failed_conversion(self):
3928
self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
3929
self.registry.register(
3930
config.Option('foo', default=u'{bar}',
3931
from_unicode=config.int_from_store))
3932
self.assertEquals(42, self.conf.get('foo', expand=True))
3934
def test_env_adding_options(self):
3935
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3937
def test_env_overriding_options(self):
3938
self.conf.store._load_from_string('foo=baz')
3939
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3941
def test_simple_ref(self):
3942
self.conf.store._load_from_string('foo=xxx')
3943
self.assertExpansion('xxx', '{foo}')
3945
def test_unknown_ref(self):
3946
self.assertRaises(errors.ExpandingUnknownOption,
3947
self.conf.expand_options, '{foo}')
3949
def test_indirect_ref(self):
3950
self.conf.store._load_from_string('''
3954
self.assertExpansion('xxx', '{bar}')
3956
def test_embedded_ref(self):
3957
self.conf.store._load_from_string('''
3961
self.assertExpansion('xxx', '{{bar}}')
3963
def test_simple_loop(self):
3964
self.conf.store._load_from_string('foo={foo}')
3965
self.assertRaises(errors.OptionExpansionLoop,
3966
self.conf.expand_options, '{foo}')
3968
def test_indirect_loop(self):
3969
self.conf.store._load_from_string('''
3973
e = self.assertRaises(errors.OptionExpansionLoop,
3974
self.conf.expand_options, '{foo}')
3975
self.assertEquals('foo->bar->baz', e.refs)
3976
self.assertEquals('{foo}', e.string)
3978
def test_list(self):
3979
self.conf.store._load_from_string('''
3983
list={foo},{bar},{baz}
3985
self.registry.register(
3986
config.ListOption('list'))
3987
self.assertEquals(['start', 'middle', 'end'],
3988
self.conf.get('list', expand=True))
3990
def test_cascading_list(self):
3991
self.conf.store._load_from_string('''
3997
self.registry.register(config.ListOption('list'))
3998
# Register an intermediate option as a list to ensure no conversion
3999
# happen while expanding. Conversion should only occur for the original
4000
# option ('list' here).
4001
self.registry.register(config.ListOption('baz'))
4002
self.assertEquals(['start', 'middle', 'end'],
4003
self.conf.get('list', expand=True))
4005
def test_pathologically_hidden_list(self):
4006
self.conf.store._load_from_string('''
4012
hidden={start}{middle}{end}
4014
# What matters is what the registration says, the conversion happens
4015
# only after all expansions have been performed
4016
self.registry.register(config.ListOption('hidden'))
4017
self.assertEquals(['bin', 'go'],
4018
self.conf.get('hidden', expand=True))
4021
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
4024
super(TestStackCrossSectionsExpand, self).setUp()
4026
def get_config(self, location, string):
4029
# Since we don't save the config we won't strictly require to inherit
4030
# from TestCaseInTempDir, but an error occurs so quickly...
4031
c = config.LocationStack(location)
4032
c.store._load_from_string(string)
4035
def test_dont_cross_unrelated_section(self):
4036
c = self.get_config('/another/branch/path','''
4041
[/another/branch/path]
4044
self.assertRaises(errors.ExpandingUnknownOption,
4045
c.get, 'bar', expand=True)
4047
def test_cross_related_sections(self):
4048
c = self.get_config('/project/branch/path','''
4052
[/project/branch/path]
4055
self.assertEquals('quux', c.get('bar', expand=True))
4058
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
4060
def test_cross_global_locations(self):
4061
l_store = config.LocationStore()
4062
l_store._load_from_string('''
4068
g_store = config.GlobalStore()
4069
g_store._load_from_string('''
4075
stack = config.LocationStack('/branch')
4076
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4077
self.assertEquals('loc-foo', stack.get('gfoo', expand=True))
4080
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
4082
def test_expand_locals_empty(self):
4083
l_store = config.LocationStore()
4084
l_store._load_from_string('''
4085
[/home/user/project]
4090
stack = config.LocationStack('/home/user/project/')
4091
self.assertEquals('', stack.get('base', expand=True))
4092
self.assertEquals('', stack.get('rel', expand=True))
4094
def test_expand_basename_locally(self):
4095
l_store = config.LocationStore()
4096
l_store._load_from_string('''
4097
[/home/user/project]
4101
stack = config.LocationStack('/home/user/project/branch')
4102
self.assertEquals('branch', stack.get('bfoo', expand=True))
4104
def test_expand_basename_locally_longer_path(self):
4105
l_store = config.LocationStore()
4106
l_store._load_from_string('''
4111
stack = config.LocationStack('/home/user/project/dir/branch')
4112
self.assertEquals('branch', stack.get('bfoo', expand=True))
4114
def test_expand_relpath_locally(self):
4115
l_store = config.LocationStore()
4116
l_store._load_from_string('''
4117
[/home/user/project]
4118
lfoo = loc-foo/{relpath}
4121
stack = config.LocationStack('/home/user/project/branch')
4122
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4124
def test_expand_relpath_unknonw_in_global(self):
4125
g_store = config.GlobalStore()
4126
g_store._load_from_string('''
4131
stack = config.LocationStack('/home/user/project/branch')
4132
self.assertRaises(errors.ExpandingUnknownOption,
4133
stack.get, 'gfoo', expand=True)
4135
def test_expand_local_option_locally(self):
4136
l_store = config.LocationStore()
4137
l_store._load_from_string('''
4138
[/home/user/project]
4139
lfoo = loc-foo/{relpath}
4143
g_store = config.GlobalStore()
4144
g_store._load_from_string('''
4150
stack = config.LocationStack('/home/user/project/branch')
4151
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4152
self.assertEquals('loc-foo/branch', stack.get('gfoo', expand=True))
4154
def test_locals_dont_leak(self):
4155
"""Make sure we chose the right local in presence of several sections.
4157
l_store = config.LocationStore()
4158
l_store._load_from_string('''
4160
lfoo = loc-foo/{relpath}
4161
[/home/user/project]
4162
lfoo = loc-foo/{relpath}
4165
stack = config.LocationStack('/home/user/project/branch')
4166
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4167
stack = config.LocationStack('/home/user/bar/baz')
4168
self.assertEquals('loc-foo/bar/baz', stack.get('lfoo', expand=True))
4172
class TestStackSet(TestStackWithTransport):
4174
def test_simple_set(self):
4175
conf = self.get_stack(self)
4176
self.assertEquals(None, conf.get('foo'))
4177
conf.set('foo', 'baz')
4178
# Did we get it back ?
4179
self.assertEquals('baz', conf.get('foo'))
4181
def test_set_creates_a_new_section(self):
4182
conf = self.get_stack(self)
4183
conf.set('foo', 'baz')
4184
self.assertEquals, 'baz', conf.get('foo')
4186
def test_set_hook(self):
4190
config.ConfigHooks.install_named_hook('set', hook, None)
4191
self.assertLength(0, calls)
4192
conf = self.get_stack(self)
4193
conf.set('foo', 'bar')
4194
self.assertLength(1, calls)
4195
self.assertEquals((conf, 'foo', 'bar'), calls[0])
4198
class TestStackRemove(TestStackWithTransport):
4200
def test_remove_existing(self):
4201
conf = self.get_stack(self)
4202
conf.set('foo', 'bar')
4203
self.assertEquals('bar', conf.get('foo'))
4205
# Did we get it back ?
4206
self.assertEquals(None, conf.get('foo'))
4208
def test_remove_unknown(self):
4209
conf = self.get_stack(self)
4210
self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
4212
def test_remove_hook(self):
4216
config.ConfigHooks.install_named_hook('remove', hook, None)
4217
self.assertLength(0, calls)
4218
conf = self.get_stack(self)
4219
conf.set('foo', 'bar')
4221
self.assertLength(1, calls)
4222
self.assertEquals((conf, 'foo'), calls[0])
4225
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
4228
super(TestConfigGetOptions, self).setUp()
4229
create_configs(self)
4231
def test_no_variable(self):
4232
# Using branch should query branch, locations and bazaar
4233
self.assertOptions([], self.branch_config)
4235
def test_option_in_bazaar(self):
4236
self.bazaar_config.set_user_option('file', 'bazaar')
4237
self.assertOptions([('file', 'bazaar', 'DEFAULT', 'bazaar')],
4240
def test_option_in_locations(self):
4241
self.locations_config.set_user_option('file', 'locations')
4243
[('file', 'locations', self.tree.basedir, 'locations')],
4244
self.locations_config)
4246
def test_option_in_branch(self):
4247
self.branch_config.set_user_option('file', 'branch')
4248
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch')],
4251
def test_option_in_bazaar_and_branch(self):
4252
self.bazaar_config.set_user_option('file', 'bazaar')
4253
self.branch_config.set_user_option('file', 'branch')
4254
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch'),
4255
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4258
def test_option_in_branch_and_locations(self):
4259
# Hmm, locations override branch :-/
4260
self.locations_config.set_user_option('file', 'locations')
4261
self.branch_config.set_user_option('file', 'branch')
4263
[('file', 'locations', self.tree.basedir, 'locations'),
4264
('file', 'branch', 'DEFAULT', 'branch'),],
4267
def test_option_in_bazaar_locations_and_branch(self):
4268
self.bazaar_config.set_user_option('file', 'bazaar')
4269
self.locations_config.set_user_option('file', 'locations')
4270
self.branch_config.set_user_option('file', 'branch')
4272
[('file', 'locations', self.tree.basedir, 'locations'),
4273
('file', 'branch', 'DEFAULT', 'branch'),
4274
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4278
class TestConfigRemoveOption(tests.TestCaseWithTransport, TestOptionsMixin):
4281
super(TestConfigRemoveOption, self).setUp()
4282
create_configs_with_file_option(self)
4284
def test_remove_in_locations(self):
4285
self.locations_config.remove_user_option('file', self.tree.basedir)
4287
[('file', 'branch', 'DEFAULT', 'branch'),
4288
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4291
def test_remove_in_branch(self):
4292
self.branch_config.remove_user_option('file')
4294
[('file', 'locations', self.tree.basedir, 'locations'),
4295
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4298
def test_remove_in_bazaar(self):
4299
self.bazaar_config.remove_user_option('file')
4301
[('file', 'locations', self.tree.basedir, 'locations'),
4302
('file', 'branch', 'DEFAULT', 'branch'),],
4306
class TestConfigGetSections(tests.TestCaseWithTransport):
4309
super(TestConfigGetSections, self).setUp()
4310
create_configs(self)
4312
def assertSectionNames(self, expected, conf, name=None):
4313
"""Check which sections are returned for a given config.
4315
If fallback configurations exist their sections can be included.
4317
:param expected: A list of section names.
4319
:param conf: The configuration that will be queried.
4321
:param name: An optional section name that will be passed to
4324
sections = list(conf._get_sections(name))
4325
self.assertLength(len(expected), sections)
4326
self.assertEqual(expected, [name for name, _, _ in sections])
4328
def test_bazaar_default_section(self):
4329
self.assertSectionNames(['DEFAULT'], self.bazaar_config)
4331
def test_locations_default_section(self):
4332
# No sections are defined in an empty file
4333
self.assertSectionNames([], self.locations_config)
4335
def test_locations_named_section(self):
4336
self.locations_config.set_user_option('file', 'locations')
4337
self.assertSectionNames([self.tree.basedir], self.locations_config)
4339
def test_locations_matching_sections(self):
4340
loc_config = self.locations_config
4341
loc_config.set_user_option('file', 'locations')
4342
# We need to cheat a bit here to create an option in sections above and
4343
# below the 'location' one.
4344
parser = loc_config._get_parser()
4345
# locations.cong deals with '/' ignoring native os.sep
4346
location_names = self.tree.basedir.split('/')
4347
parent = '/'.join(location_names[:-1])
4348
child = '/'.join(location_names + ['child'])
4350
parser[parent]['file'] = 'parent'
4352
parser[child]['file'] = 'child'
4353
self.assertSectionNames([self.tree.basedir, parent], loc_config)
4355
def test_branch_data_default_section(self):
4356
self.assertSectionNames([None],
4357
self.branch_config._get_branch_data_config())
4359
def test_branch_default_sections(self):
4360
# No sections are defined in an empty locations file
4361
self.assertSectionNames([None, 'DEFAULT'],
4363
# Unless we define an option
4364
self.branch_config._get_location_config().set_user_option(
4365
'file', 'locations')
4366
self.assertSectionNames([self.tree.basedir, None, 'DEFAULT'],
4369
def test_bazaar_named_section(self):
4370
# We need to cheat as the API doesn't give direct access to sections
4371
# other than DEFAULT.
4372
self.bazaar_config.set_alias('bazaar', 'bzr')
4373
self.assertSectionNames(['ALIASES'], self.bazaar_config, 'ALIASES')
1315
4376
class TestAuthenticationConfigFile(tests.TestCase):
1316
4377
"""Test the authentication.conf file matching"""