1979
1238
self.assertIs(None, bzrdir_config.get_default_stack_on())
1982
class TestOldConfigHooks(tests.TestCaseWithTransport):
1985
super(TestOldConfigHooks, self).setUp()
1986
create_configs_with_file_option(self)
1988
def assertGetHook(self, conf, name, value):
1992
config.OldConfigHooks.install_named_hook('get', hook, None)
1994
config.OldConfigHooks.uninstall_named_hook, 'get', None)
1995
self.assertLength(0, calls)
1996
actual_value = conf.get_user_option(name)
1997
self.assertEquals(value, actual_value)
1998
self.assertLength(1, calls)
1999
self.assertEquals((conf, name, value), calls[0])
2001
def test_get_hook_bazaar(self):
2002
self.assertGetHook(self.bazaar_config, 'file', 'bazaar')
2004
def test_get_hook_locations(self):
2005
self.assertGetHook(self.locations_config, 'file', 'locations')
2007
def test_get_hook_branch(self):
2008
# Since locations masks branch, we define a different option
2009
self.branch_config.set_user_option('file2', 'branch')
2010
self.assertGetHook(self.branch_config, 'file2', 'branch')
2012
def assertSetHook(self, conf, name, value):
2016
config.OldConfigHooks.install_named_hook('set', hook, None)
2018
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2019
self.assertLength(0, calls)
2020
conf.set_user_option(name, value)
2021
self.assertLength(1, calls)
2022
# We can't assert the conf object below as different configs use
2023
# different means to implement set_user_option and we care only about
2025
self.assertEquals((name, value), calls[0][1:])
2027
def test_set_hook_bazaar(self):
2028
self.assertSetHook(self.bazaar_config, 'foo', 'bazaar')
2030
def test_set_hook_locations(self):
2031
self.assertSetHook(self.locations_config, 'foo', 'locations')
2033
def test_set_hook_branch(self):
2034
self.assertSetHook(self.branch_config, 'foo', 'branch')
2036
def assertRemoveHook(self, conf, name, section_name=None):
2040
config.OldConfigHooks.install_named_hook('remove', hook, None)
2042
config.OldConfigHooks.uninstall_named_hook, 'remove', None)
2043
self.assertLength(0, calls)
2044
conf.remove_user_option(name, section_name)
2045
self.assertLength(1, calls)
2046
# We can't assert the conf object below as different configs use
2047
# different means to implement remove_user_option and we care only about
2049
self.assertEquals((name,), calls[0][1:])
2051
def test_remove_hook_bazaar(self):
2052
self.assertRemoveHook(self.bazaar_config, 'file')
2054
def test_remove_hook_locations(self):
2055
self.assertRemoveHook(self.locations_config, 'file',
2056
self.locations_config.location)
2058
def test_remove_hook_branch(self):
2059
self.assertRemoveHook(self.branch_config, 'file')
2061
def assertLoadHook(self, name, conf_class, *conf_args):
2065
config.OldConfigHooks.install_named_hook('load', hook, None)
2067
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2068
self.assertLength(0, calls)
2070
conf = conf_class(*conf_args)
2071
# Access an option to trigger a load
2072
conf.get_user_option(name)
2073
self.assertLength(1, calls)
2074
# Since we can't assert about conf, we just use the number of calls ;-/
2076
def test_load_hook_bazaar(self):
2077
self.assertLoadHook('file', config.GlobalConfig)
2079
def test_load_hook_locations(self):
2080
self.assertLoadHook('file', config.LocationConfig, self.tree.basedir)
2082
def test_load_hook_branch(self):
2083
self.assertLoadHook('file', config.BranchConfig, self.tree.branch)
2085
def assertSaveHook(self, conf):
2089
config.OldConfigHooks.install_named_hook('save', hook, None)
2091
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2092
self.assertLength(0, calls)
2093
# Setting an option triggers a save
2094
conf.set_user_option('foo', 'bar')
2095
self.assertLength(1, calls)
2096
# Since we can't assert about conf, we just use the number of calls ;-/
2098
def test_save_hook_bazaar(self):
2099
self.assertSaveHook(self.bazaar_config)
2101
def test_save_hook_locations(self):
2102
self.assertSaveHook(self.locations_config)
2104
def test_save_hook_branch(self):
2105
self.assertSaveHook(self.branch_config)
2108
class TestOldConfigHooksForRemote(tests.TestCaseWithTransport):
2109
"""Tests config hooks for remote configs.
2111
No tests for the remove hook as this is not implemented there.
2115
super(TestOldConfigHooksForRemote, self).setUp()
2116
self.transport_server = test_server.SmartTCPServer_for_testing
2117
create_configs_with_file_option(self)
2119
def assertGetHook(self, conf, name, value):
2123
config.OldConfigHooks.install_named_hook('get', hook, None)
2125
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2126
self.assertLength(0, calls)
2127
actual_value = conf.get_option(name)
2128
self.assertEquals(value, actual_value)
2129
self.assertLength(1, calls)
2130
self.assertEquals((conf, name, value), calls[0])
2132
def test_get_hook_remote_branch(self):
2133
remote_branch = branch.Branch.open(self.get_url('tree'))
2134
self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
2136
def test_get_hook_remote_bzrdir(self):
2137
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2138
conf = remote_bzrdir._get_config()
2139
conf.set_option('remotedir', 'file')
2140
self.assertGetHook(conf, 'file', 'remotedir')
2142
def assertSetHook(self, conf, name, value):
2146
config.OldConfigHooks.install_named_hook('set', hook, None)
2148
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2149
self.assertLength(0, calls)
2150
conf.set_option(value, name)
2151
self.assertLength(1, calls)
2152
# We can't assert the conf object below as different configs use
2153
# different means to implement set_user_option and we care only about
2155
self.assertEquals((name, value), calls[0][1:])
2157
def test_set_hook_remote_branch(self):
2158
remote_branch = branch.Branch.open(self.get_url('tree'))
2159
self.addCleanup(remote_branch.lock_write().unlock)
2160
self.assertSetHook(remote_branch._get_config(), 'file', 'remote')
2162
def test_set_hook_remote_bzrdir(self):
2163
remote_branch = branch.Branch.open(self.get_url('tree'))
2164
self.addCleanup(remote_branch.lock_write().unlock)
2165
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2166
self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
2168
def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
2172
config.OldConfigHooks.install_named_hook('load', hook, None)
2174
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2175
self.assertLength(0, calls)
2177
conf = conf_class(*conf_args)
2178
# Access an option to trigger a load
2179
conf.get_option(name)
2180
self.assertLength(expected_nb_calls, calls)
2181
# Since we can't assert about conf, we just use the number of calls ;-/
2183
def test_load_hook_remote_branch(self):
2184
remote_branch = branch.Branch.open(self.get_url('tree'))
2185
self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
2187
def test_load_hook_remote_bzrdir(self):
2188
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2189
# The config file doesn't exist, set an option to force its creation
2190
conf = remote_bzrdir._get_config()
2191
conf.set_option('remotedir', 'file')
2192
# We get one call for the server and one call for the client, this is
2193
# caused by the differences in implementations betwen
2194
# SmartServerBzrDirRequestConfigFile (in smart/bzrdir.py) and
2195
# SmartServerBranchGetConfigFile (in smart/branch.py)
2196
self.assertLoadHook(2 ,'file', remote.RemoteBzrDirConfig, remote_bzrdir)
2198
def assertSaveHook(self, conf):
2202
config.OldConfigHooks.install_named_hook('save', hook, None)
2204
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2205
self.assertLength(0, calls)
2206
# Setting an option triggers a save
2207
conf.set_option('foo', 'bar')
2208
self.assertLength(1, calls)
2209
# Since we can't assert about conf, we just use the number of calls ;-/
2211
def test_save_hook_remote_branch(self):
2212
remote_branch = branch.Branch.open(self.get_url('tree'))
2213
self.addCleanup(remote_branch.lock_write().unlock)
2214
self.assertSaveHook(remote_branch._get_config())
2216
def test_save_hook_remote_bzrdir(self):
2217
remote_branch = branch.Branch.open(self.get_url('tree'))
2218
self.addCleanup(remote_branch.lock_write().unlock)
2219
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2220
self.assertSaveHook(remote_bzrdir._get_config())
2223
class TestOption(tests.TestCase):
2225
def test_default_value(self):
2226
opt = config.Option('foo', default='bar')
2227
self.assertEquals('bar', opt.get_default())
2229
def test_callable_default_value(self):
2230
def bar_as_unicode():
2232
opt = config.Option('foo', default=bar_as_unicode)
2233
self.assertEquals('bar', opt.get_default())
2235
def test_default_value_from_env(self):
2236
opt = config.Option('foo', default='bar', default_from_env=['FOO'])
2237
self.overrideEnv('FOO', 'quux')
2238
# Env variable provides a default taking over the option one
2239
self.assertEquals('quux', opt.get_default())
2241
def test_first_default_value_from_env_wins(self):
2242
opt = config.Option('foo', default='bar',
2243
default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
2244
self.overrideEnv('FOO', 'foo')
2245
self.overrideEnv('BAZ', 'baz')
2246
# The first env var set wins
2247
self.assertEquals('foo', opt.get_default())
2249
def test_not_supported_list_default_value(self):
2250
self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
2252
def test_not_supported_object_default_value(self):
2253
self.assertRaises(AssertionError, config.Option, 'foo',
2256
def test_not_supported_callable_default_value_not_unicode(self):
2257
def bar_not_unicode():
2259
opt = config.Option('foo', default=bar_not_unicode)
2260
self.assertRaises(AssertionError, opt.get_default)
2262
def test_get_help_topic(self):
2263
opt = config.Option('foo')
2264
self.assertEquals('foo', opt.get_help_topic())
2267
class TestOptionConverterMixin(object):
2269
def assertConverted(self, expected, opt, value):
2270
self.assertEquals(expected, opt.convert_from_unicode(None, value))
2272
def assertWarns(self, opt, value):
2275
warnings.append(args[0] % args[1:])
2276
self.overrideAttr(trace, 'warning', warning)
2277
self.assertEquals(None, opt.convert_from_unicode(None, value))
2278
self.assertLength(1, warnings)
2280
'Value "%s" is not valid for "%s"' % (value, opt.name),
2283
def assertErrors(self, opt, value):
2284
self.assertRaises(errors.ConfigOptionValueError,
2285
opt.convert_from_unicode, None, value)
2287
def assertConvertInvalid(self, opt, invalid_value):
2289
self.assertEquals(None, opt.convert_from_unicode(None, invalid_value))
2290
opt.invalid = 'warning'
2291
self.assertWarns(opt, invalid_value)
2292
opt.invalid = 'error'
2293
self.assertErrors(opt, invalid_value)
2296
class TestOptionWithBooleanConverter(tests.TestCase, TestOptionConverterMixin):
2298
def get_option(self):
2299
return config.Option('foo', help='A boolean.',
2300
from_unicode=config.bool_from_store)
2302
def test_convert_invalid(self):
2303
opt = self.get_option()
2304
# A string that is not recognized as a boolean
2305
self.assertConvertInvalid(opt, u'invalid-boolean')
2306
# A list of strings is never recognized as a boolean
2307
self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
2309
def test_convert_valid(self):
2310
opt = self.get_option()
2311
self.assertConverted(True, opt, u'True')
2312
self.assertConverted(True, opt, u'1')
2313
self.assertConverted(False, opt, u'False')
2316
class TestOptionWithIntegerConverter(tests.TestCase, TestOptionConverterMixin):
2318
def get_option(self):
2319
return config.Option('foo', help='An integer.',
2320
from_unicode=config.int_from_store)
2322
def test_convert_invalid(self):
2323
opt = self.get_option()
2324
# A string that is not recognized as an integer
2325
self.assertConvertInvalid(opt, u'forty-two')
2326
# A list of strings is never recognized as an integer
2327
self.assertConvertInvalid(opt, [u'a', u'list'])
2329
def test_convert_valid(self):
2330
opt = self.get_option()
2331
self.assertConverted(16, opt, u'16')
2334
class TestOptionWithSIUnitConverter(tests.TestCase, TestOptionConverterMixin):
2336
def get_option(self):
2337
return config.Option('foo', help='An integer in SI units.',
2338
from_unicode=config.int_SI_from_store)
2340
def test_convert_invalid(self):
2341
opt = self.get_option()
2342
self.assertConvertInvalid(opt, u'not-a-unit')
2343
self.assertConvertInvalid(opt, u'Gb') # Forgot the int
2344
self.assertConvertInvalid(opt, u'1b') # Forgot the unit
2345
self.assertConvertInvalid(opt, u'1GG')
2346
self.assertConvertInvalid(opt, u'1Mbb')
2347
self.assertConvertInvalid(opt, u'1MM')
2349
def test_convert_valid(self):
2350
opt = self.get_option()
2351
self.assertConverted(int(5e3), opt, u'5kb')
2352
self.assertConverted(int(5e6), opt, u'5M')
2353
self.assertConverted(int(5e6), opt, u'5MB')
2354
self.assertConverted(int(5e9), opt, u'5g')
2355
self.assertConverted(int(5e9), opt, u'5gB')
2356
self.assertConverted(100, opt, u'100')
2359
class TestListOption(tests.TestCase, TestOptionConverterMixin):
2361
def get_option(self):
2362
return config.ListOption('foo', help='A list.')
2364
def test_convert_invalid(self):
2365
opt = self.get_option()
2366
# We don't even try to convert a list into a list, we only expect
2368
self.assertConvertInvalid(opt, [1])
2369
# No string is invalid as all forms can be converted to a list
2371
def test_convert_valid(self):
2372
opt = self.get_option()
2373
# An empty string is an empty list
2374
self.assertConverted([], opt, '') # Using a bare str() just in case
2375
self.assertConverted([], opt, u'')
2377
self.assertConverted([u'True'], opt, u'True')
2379
self.assertConverted([u'42'], opt, u'42')
2381
self.assertConverted([u'bar'], opt, u'bar')
2384
class TestRegistryOption(tests.TestCase, TestOptionConverterMixin):
2386
def get_option(self, registry):
2387
return config.RegistryOption('foo', registry,
2388
help='A registry option.')
2390
def test_convert_invalid(self):
2391
registry = _mod_registry.Registry()
2392
opt = self.get_option(registry)
2393
self.assertConvertInvalid(opt, [1])
2394
self.assertConvertInvalid(opt, u"notregistered")
2396
def test_convert_valid(self):
2397
registry = _mod_registry.Registry()
2398
registry.register("someval", 1234)
2399
opt = self.get_option(registry)
2400
# Using a bare str() just in case
2401
self.assertConverted(1234, opt, "someval")
2402
self.assertConverted(1234, opt, u'someval')
2403
self.assertConverted(None, opt, None)
2405
def test_help(self):
2406
registry = _mod_registry.Registry()
2407
registry.register("someval", 1234, help="some option")
2408
registry.register("dunno", 1234, help="some other option")
2409
opt = self.get_option(registry)
2411
'A registry option.\n'
2413
'The following values are supported:\n'
2414
' dunno - some other option\n'
2415
' someval - some option\n',
2418
def test_get_help_text(self):
2419
registry = _mod_registry.Registry()
2420
registry.register("someval", 1234, help="some option")
2421
registry.register("dunno", 1234, help="some other option")
2422
opt = self.get_option(registry)
2424
'A registry option.\n'
2426
'The following values are supported:\n'
2427
' dunno - some other option\n'
2428
' someval - some option\n',
2429
opt.get_help_text())
2432
class TestOptionRegistry(tests.TestCase):
2435
super(TestOptionRegistry, self).setUp()
2436
# Always start with an empty registry
2437
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2438
self.registry = config.option_registry
2440
def test_register(self):
2441
opt = config.Option('foo')
2442
self.registry.register(opt)
2443
self.assertIs(opt, self.registry.get('foo'))
2445
def test_registered_help(self):
2446
opt = config.Option('foo', help='A simple option')
2447
self.registry.register(opt)
2448
self.assertEquals('A simple option', self.registry.get_help('foo'))
2450
lazy_option = config.Option('lazy_foo', help='Lazy help')
2452
def test_register_lazy(self):
2453
self.registry.register_lazy('lazy_foo', self.__module__,
2454
'TestOptionRegistry.lazy_option')
2455
self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
2457
def test_registered_lazy_help(self):
2458
self.registry.register_lazy('lazy_foo', self.__module__,
2459
'TestOptionRegistry.lazy_option')
2460
self.assertEquals('Lazy help', self.registry.get_help('lazy_foo'))
2463
class TestRegisteredOptions(tests.TestCase):
2464
"""All registered options should verify some constraints."""
2466
scenarios = [(key, {'option_name': key, 'option': option}) for key, option
2467
in config.option_registry.iteritems()]
2470
super(TestRegisteredOptions, self).setUp()
2471
self.registry = config.option_registry
2473
def test_proper_name(self):
2474
# An option should be registered under its own name, this can't be
2475
# checked at registration time for the lazy ones.
2476
self.assertEquals(self.option_name, self.option.name)
2478
def test_help_is_set(self):
2479
option_help = self.registry.get_help(self.option_name)
2480
self.assertNotEquals(None, option_help)
2481
# Come on, think about the user, he really wants to know what the
2483
self.assertIsNot(None, option_help)
2484
self.assertNotEquals('', option_help)
2487
class TestSection(tests.TestCase):
2489
# FIXME: Parametrize so that all sections produced by Stores run these
2490
# tests -- vila 2011-04-01
2492
def test_get_a_value(self):
2493
a_dict = dict(foo='bar')
2494
section = config.Section('myID', a_dict)
2495
self.assertEquals('bar', section.get('foo'))
2497
def test_get_unknown_option(self):
2499
section = config.Section(None, a_dict)
2500
self.assertEquals('out of thin air',
2501
section.get('foo', 'out of thin air'))
2503
def test_options_is_shared(self):
2505
section = config.Section(None, a_dict)
2506
self.assertIs(a_dict, section.options)
2509
class TestMutableSection(tests.TestCase):
2511
scenarios = [('mutable',
2513
lambda opts: config.MutableSection('myID', opts)},),
2517
a_dict = dict(foo='bar')
2518
section = self.get_section(a_dict)
2519
section.set('foo', 'new_value')
2520
self.assertEquals('new_value', section.get('foo'))
2521
# The change appears in the shared section
2522
self.assertEquals('new_value', a_dict.get('foo'))
2523
# We keep track of the change
2524
self.assertTrue('foo' in section.orig)
2525
self.assertEquals('bar', section.orig.get('foo'))
2527
def test_set_preserve_original_once(self):
2528
a_dict = dict(foo='bar')
2529
section = self.get_section(a_dict)
2530
section.set('foo', 'first_value')
2531
section.set('foo', 'second_value')
2532
# We keep track of the original value
2533
self.assertTrue('foo' in section.orig)
2534
self.assertEquals('bar', section.orig.get('foo'))
2536
def test_remove(self):
2537
a_dict = dict(foo='bar')
2538
section = self.get_section(a_dict)
2539
section.remove('foo')
2540
# We get None for unknown options via the default value
2541
self.assertEquals(None, section.get('foo'))
2542
# Or we just get the default value
2543
self.assertEquals('unknown', section.get('foo', 'unknown'))
2544
self.assertFalse('foo' in section.options)
2545
# We keep track of the deletion
2546
self.assertTrue('foo' in section.orig)
2547
self.assertEquals('bar', section.orig.get('foo'))
2549
def test_remove_new_option(self):
2551
section = self.get_section(a_dict)
2552
section.set('foo', 'bar')
2553
section.remove('foo')
2554
self.assertFalse('foo' in section.options)
2555
# The option didn't exist initially so it we need to keep track of it
2556
# with a special value
2557
self.assertTrue('foo' in section.orig)
2558
self.assertEquals(config._NewlyCreatedOption, section.orig['foo'])
2561
class TestCommandLineStore(tests.TestCase):
2564
super(TestCommandLineStore, self).setUp()
2565
self.store = config.CommandLineStore()
2566
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2568
def get_section(self):
2569
"""Get the unique section for the command line overrides."""
2570
sections = list(self.store.get_sections())
2571
self.assertLength(1, sections)
2572
store, section = sections[0]
2573
self.assertEquals(self.store, store)
2576
def test_no_override(self):
2577
self.store._from_cmdline([])
2578
section = self.get_section()
2579
self.assertLength(0, list(section.iter_option_names()))
2581
def test_simple_override(self):
2582
self.store._from_cmdline(['a=b'])
2583
section = self.get_section()
2584
self.assertEqual('b', section.get('a'))
2586
def test_list_override(self):
2587
opt = config.ListOption('l')
2588
config.option_registry.register(opt)
2589
self.store._from_cmdline(['l=1,2,3'])
2590
val = self.get_section().get('l')
2591
self.assertEqual('1,2,3', val)
2592
# Reminder: lists should be registered as such explicitely, otherwise
2593
# the conversion needs to be done afterwards.
2594
self.assertEqual(['1', '2', '3'],
2595
opt.convert_from_unicode(self.store, val))
2597
def test_multiple_overrides(self):
2598
self.store._from_cmdline(['a=b', 'x=y'])
2599
section = self.get_section()
2600
self.assertEquals('b', section.get('a'))
2601
self.assertEquals('y', section.get('x'))
2603
def test_wrong_syntax(self):
2604
self.assertRaises(errors.BzrCommandError,
2605
self.store._from_cmdline, ['a=b', 'c'])
2607
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
2609
scenarios = [(key, {'get_store': builder}) for key, builder
2610
in config.test_store_builder_registry.iteritems()] + [
2611
('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
2614
store = self.get_store(self)
2615
if type(store) == config.TransportIniFileStore:
2616
raise tests.TestNotApplicable(
2617
"%s is not a concrete Store implementation"
2618
" so it doesn't need an id" % (store.__class__.__name__,))
2619
self.assertIsNot(None, store.id)
2622
class TestStore(tests.TestCaseWithTransport):
2624
def assertSectionContent(self, expected, (store, section)):
2625
"""Assert that some options have the proper values in a section."""
2626
expected_name, expected_options = expected
2627
self.assertEquals(expected_name, section.id)
2630
dict([(k, section.get(k)) for k in expected_options.keys()]))
2633
class TestReadonlyStore(TestStore):
2635
scenarios = [(key, {'get_store': builder}) for key, builder
2636
in config.test_store_builder_registry.iteritems()]
2638
def test_building_delays_load(self):
2639
store = self.get_store(self)
2640
self.assertEquals(False, store.is_loaded())
2641
store._load_from_string('')
2642
self.assertEquals(True, store.is_loaded())
2644
def test_get_no_sections_for_empty(self):
2645
store = self.get_store(self)
2646
store._load_from_string('')
2647
self.assertEquals([], list(store.get_sections()))
2649
def test_get_default_section(self):
2650
store = self.get_store(self)
2651
store._load_from_string('foo=bar')
2652
sections = list(store.get_sections())
2653
self.assertLength(1, sections)
2654
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2656
def test_get_named_section(self):
2657
store = self.get_store(self)
2658
store._load_from_string('[baz]\nfoo=bar')
2659
sections = list(store.get_sections())
2660
self.assertLength(1, sections)
2661
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2663
def test_load_from_string_fails_for_non_empty_store(self):
2664
store = self.get_store(self)
2665
store._load_from_string('foo=bar')
2666
self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
2669
class TestStoreQuoting(TestStore):
2671
scenarios = [(key, {'get_store': builder}) for key, builder
2672
in config.test_store_builder_registry.iteritems()]
2675
super(TestStoreQuoting, self).setUp()
2676
self.store = self.get_store(self)
2677
# We need a loaded store but any content will do
2678
self.store._load_from_string('')
2680
def assertIdempotent(self, s):
2681
"""Assert that quoting an unquoted string is a no-op and vice-versa.
2683
What matters here is that option values, as they appear in a store, can
2684
be safely round-tripped out of the store and back.
2686
:param s: A string, quoted if required.
2688
self.assertEquals(s, self.store.quote(self.store.unquote(s)))
2689
self.assertEquals(s, self.store.unquote(self.store.quote(s)))
2691
def test_empty_string(self):
2692
if isinstance(self.store, config.IniFileStore):
2693
# configobj._quote doesn't handle empty values
2694
self.assertRaises(AssertionError,
2695
self.assertIdempotent, '')
2697
self.assertIdempotent('')
2698
# But quoted empty strings are ok
2699
self.assertIdempotent('""')
2701
def test_embedded_spaces(self):
2702
self.assertIdempotent('" a b c "')
2704
def test_embedded_commas(self):
2705
self.assertIdempotent('" a , b c "')
2707
def test_simple_comma(self):
2708
if isinstance(self.store, config.IniFileStore):
2709
# configobj requires that lists are special-cased
2710
self.assertRaises(AssertionError,
2711
self.assertIdempotent, ',')
2713
self.assertIdempotent(',')
2714
# When a single comma is required, quoting is also required
2715
self.assertIdempotent('","')
2717
def test_list(self):
2718
if isinstance(self.store, config.IniFileStore):
2719
# configobj requires that lists are special-cased
2720
self.assertRaises(AssertionError,
2721
self.assertIdempotent, 'a,b')
2723
self.assertIdempotent('a,b')
2726
class TestDictFromStore(tests.TestCase):
2728
def test_unquote_not_string(self):
2729
conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
2730
value = conf.get('a_section')
2731
# Urgh, despite 'conf' asking for the no-name section, we get the
2732
# content of another section as a dict o_O
2733
self.assertEquals({'a': '1'}, value)
2734
unquoted = conf.store.unquote(value)
2735
# Which cannot be unquoted but shouldn't crash either (the use cases
2736
# are getting the value or displaying it. In the later case, '%s' will
2738
self.assertEquals({'a': '1'}, unquoted)
2739
self.assertEquals("{u'a': u'1'}", '%s' % (unquoted,))
2742
class TestIniFileStoreContent(tests.TestCaseWithTransport):
2743
"""Simulate loading a config store with content of various encodings.
2745
All files produced by bzr are in utf8 content.
2747
Users may modify them manually and end up with a file that can't be
2748
loaded. We need to issue proper error messages in this case.
2751
invalid_utf8_char = '\xff'
2753
def test_load_utf8(self):
2754
"""Ensure we can load an utf8-encoded file."""
2755
t = self.get_transport()
2756
# From http://pad.lv/799212
2757
unicode_user = u'b\N{Euro Sign}ar'
2758
unicode_content = u'user=%s' % (unicode_user,)
2759
utf8_content = unicode_content.encode('utf8')
2760
# Store the raw content in the config file
2761
t.put_bytes('foo.conf', utf8_content)
2762
store = config.TransportIniFileStore(t, 'foo.conf')
2764
stack = config.Stack([store.get_sections], store)
2765
self.assertEquals(unicode_user, stack.get('user'))
2767
def test_load_non_ascii(self):
2768
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2769
t = self.get_transport()
2770
t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
2771
store = config.TransportIniFileStore(t, 'foo.conf')
2772
self.assertRaises(errors.ConfigContentError, store.load)
2774
def test_load_erroneous_content(self):
2775
"""Ensure we display a proper error on content that can't be parsed."""
2776
t = self.get_transport()
2777
t.put_bytes('foo.conf', '[open_section\n')
2778
store = config.TransportIniFileStore(t, 'foo.conf')
2779
self.assertRaises(errors.ParseConfigError, store.load)
2781
def test_load_permission_denied(self):
2782
"""Ensure we get warned when trying to load an inaccessible file."""
2785
warnings.append(args[0] % args[1:])
2786
self.overrideAttr(trace, 'warning', warning)
2788
t = self.get_transport()
2790
def get_bytes(relpath):
2791
raise errors.PermissionDenied(relpath, "")
2792
t.get_bytes = get_bytes
2793
store = config.TransportIniFileStore(t, 'foo.conf')
2794
self.assertRaises(errors.PermissionDenied, store.load)
2797
[u'Permission denied while trying to load configuration store %s.'
2798
% store.external_url()])
2801
class TestIniConfigContent(tests.TestCaseWithTransport):
2802
"""Simulate loading a IniBasedConfig with content of various encodings.
2804
All files produced by bzr are in utf8 content.
2806
Users may modify them manually and end up with a file that can't be
2807
loaded. We need to issue proper error messages in this case.
2810
invalid_utf8_char = '\xff'
2812
def test_load_utf8(self):
2813
"""Ensure we can load an utf8-encoded file."""
2814
# From http://pad.lv/799212
2815
unicode_user = u'b\N{Euro Sign}ar'
2816
unicode_content = u'user=%s' % (unicode_user,)
2817
utf8_content = unicode_content.encode('utf8')
2818
# Store the raw content in the config file
2819
with open('foo.conf', 'wb') as f:
2820
f.write(utf8_content)
2821
conf = config.IniBasedConfig(file_name='foo.conf')
2822
self.assertEquals(unicode_user, conf.get_user_option('user'))
2824
def test_load_badly_encoded_content(self):
2825
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2826
with open('foo.conf', 'wb') as f:
2827
f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
2828
conf = config.IniBasedConfig(file_name='foo.conf')
2829
self.assertRaises(errors.ConfigContentError, conf._get_parser)
2831
def test_load_erroneous_content(self):
2832
"""Ensure we display a proper error on content that can't be parsed."""
2833
with open('foo.conf', 'wb') as f:
2834
f.write('[open_section\n')
2835
conf = config.IniBasedConfig(file_name='foo.conf')
2836
self.assertRaises(errors.ParseConfigError, conf._get_parser)
2839
class TestMutableStore(TestStore):
2841
scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
2842
in config.test_store_builder_registry.iteritems()]
2845
super(TestMutableStore, self).setUp()
2846
self.transport = self.get_transport()
2848
def has_store(self, store):
2849
store_basename = urlutils.relative_url(self.transport.external_url(),
2850
store.external_url())
2851
return self.transport.has(store_basename)
2853
def test_save_empty_creates_no_file(self):
2854
# FIXME: There should be a better way than relying on the test
2855
# parametrization to identify branch.conf -- vila 2011-0526
2856
if self.store_id in ('branch', 'remote_branch'):
2857
raise tests.TestNotApplicable(
2858
'branch.conf is *always* created when a branch is initialized')
2859
store = self.get_store(self)
2861
self.assertEquals(False, self.has_store(store))
2863
def test_mutable_section_shared(self):
2864
store = self.get_store(self)
2865
store._load_from_string('foo=bar\n')
2866
# FIXME: There should be a better way than relying on the test
2867
# parametrization to identify branch.conf -- vila 2011-0526
2868
if self.store_id in ('branch', 'remote_branch'):
2869
# branch stores requires write locked branches
2870
self.addCleanup(store.branch.lock_write().unlock)
2871
section1 = store.get_mutable_section(None)
2872
section2 = store.get_mutable_section(None)
2873
# If we get different sections, different callers won't share the
2875
self.assertIs(section1, section2)
2877
def test_save_emptied_succeeds(self):
2878
store = self.get_store(self)
2879
store._load_from_string('foo=bar\n')
2880
# FIXME: There should be a better way than relying on the test
2881
# parametrization to identify branch.conf -- vila 2011-0526
2882
if self.store_id in ('branch', 'remote_branch'):
2883
# branch stores requires write locked branches
2884
self.addCleanup(store.branch.lock_write().unlock)
2885
section = store.get_mutable_section(None)
2886
section.remove('foo')
2888
self.assertEquals(True, self.has_store(store))
2889
modified_store = self.get_store(self)
2890
sections = list(modified_store.get_sections())
2891
self.assertLength(0, sections)
2893
def test_save_with_content_succeeds(self):
2894
# FIXME: There should be a better way than relying on the test
2895
# parametrization to identify branch.conf -- vila 2011-0526
2896
if self.store_id in ('branch', 'remote_branch'):
2897
raise tests.TestNotApplicable(
2898
'branch.conf is *always* created when a branch is initialized')
2899
store = self.get_store(self)
2900
store._load_from_string('foo=bar\n')
2901
self.assertEquals(False, self.has_store(store))
2903
self.assertEquals(True, self.has_store(store))
2904
modified_store = self.get_store(self)
2905
sections = list(modified_store.get_sections())
2906
self.assertLength(1, sections)
2907
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2909
def test_set_option_in_empty_store(self):
2910
store = self.get_store(self)
2911
# FIXME: There should be a better way than relying on the test
2912
# parametrization to identify branch.conf -- vila 2011-0526
2913
if self.store_id in ('branch', 'remote_branch'):
2914
# branch stores requires write locked branches
2915
self.addCleanup(store.branch.lock_write().unlock)
2916
section = store.get_mutable_section(None)
2917
section.set('foo', 'bar')
2919
modified_store = self.get_store(self)
2920
sections = list(modified_store.get_sections())
2921
self.assertLength(1, sections)
2922
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2924
def test_set_option_in_default_section(self):
2925
store = self.get_store(self)
2926
store._load_from_string('')
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.set('foo', 'bar')
2935
modified_store = self.get_store(self)
2936
sections = list(modified_store.get_sections())
2937
self.assertLength(1, sections)
2938
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2940
def test_set_option_in_named_section(self):
2941
store = self.get_store(self)
2942
store._load_from_string('')
2943
# FIXME: There should be a better way than relying on the test
2944
# parametrization to identify branch.conf -- vila 2011-0526
2945
if self.store_id in ('branch', 'remote_branch'):
2946
# branch stores requires write locked branches
2947
self.addCleanup(store.branch.lock_write().unlock)
2948
section = store.get_mutable_section('baz')
2949
section.set('foo', 'bar')
2951
modified_store = self.get_store(self)
2952
sections = list(modified_store.get_sections())
2953
self.assertLength(1, sections)
2954
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2956
def test_load_hook(self):
2957
# First, we need to ensure that the store exists
2958
store = self.get_store(self)
2959
# FIXME: There should be a better way than relying on the test
2960
# parametrization to identify branch.conf -- vila 2011-0526
2961
if self.store_id in ('branch', 'remote_branch'):
2962
# branch stores requires write locked branches
2963
self.addCleanup(store.branch.lock_write().unlock)
2964
section = store.get_mutable_section('baz')
2965
section.set('foo', 'bar')
2967
# Now we can try to load it
2968
store = self.get_store(self)
2972
config.ConfigHooks.install_named_hook('load', hook, None)
2973
self.assertLength(0, calls)
2975
self.assertLength(1, calls)
2976
self.assertEquals((store,), calls[0])
2978
def test_save_hook(self):
2982
config.ConfigHooks.install_named_hook('save', hook, None)
2983
self.assertLength(0, calls)
2984
store = self.get_store(self)
2985
# FIXME: There should be a better way than relying on the test
2986
# parametrization to identify branch.conf -- vila 2011-0526
2987
if self.store_id in ('branch', 'remote_branch'):
2988
# branch stores requires write locked branches
2989
self.addCleanup(store.branch.lock_write().unlock)
2990
section = store.get_mutable_section('baz')
2991
section.set('foo', 'bar')
2993
self.assertLength(1, calls)
2994
self.assertEquals((store,), calls[0])
2996
def test_set_mark_dirty(self):
2997
stack = config.MemoryStack('')
2998
self.assertLength(0, stack.store.dirty_sections)
2999
stack.set('foo', 'baz')
3000
self.assertLength(1, stack.store.dirty_sections)
3001
self.assertTrue(stack.store._need_saving())
3003
def test_remove_mark_dirty(self):
3004
stack = config.MemoryStack('foo=bar')
3005
self.assertLength(0, stack.store.dirty_sections)
3007
self.assertLength(1, stack.store.dirty_sections)
3008
self.assertTrue(stack.store._need_saving())
3011
class TestStoreSaveChanges(tests.TestCaseWithTransport):
3012
"""Tests that config changes are kept in memory and saved on-demand."""
3015
super(TestStoreSaveChanges, self).setUp()
3016
self.transport = self.get_transport()
3017
# Most of the tests involve two stores pointing to the same persistent
3018
# storage to observe the effects of concurrent changes
3019
self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
3020
self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
3023
self.warnings.append(args[0] % args[1:])
3024
self.overrideAttr(trace, 'warning', warning)
3026
def has_store(self, store):
3027
store_basename = urlutils.relative_url(self.transport.external_url(),
3028
store.external_url())
3029
return self.transport.has(store_basename)
3031
def get_stack(self, store):
3032
# Any stack will do as long as it uses the right store, just a single
3033
# no-name section is enough
3034
return config.Stack([store.get_sections], store)
3036
def test_no_changes_no_save(self):
3037
s = self.get_stack(self.st1)
3038
s.store.save_changes()
3039
self.assertEquals(False, self.has_store(self.st1))
3041
def test_unrelated_concurrent_update(self):
3042
s1 = self.get_stack(self.st1)
3043
s2 = self.get_stack(self.st2)
3044
s1.set('foo', 'bar')
3045
s2.set('baz', 'quux')
3047
# Changes don't propagate magically
3048
self.assertEquals(None, s1.get('baz'))
3049
s2.store.save_changes()
3050
self.assertEquals('quux', s2.get('baz'))
3051
# Changes are acquired when saving
3052
self.assertEquals('bar', s2.get('foo'))
3053
# Since there is no overlap, no warnings are emitted
3054
self.assertLength(0, self.warnings)
3056
def test_concurrent_update_modified(self):
3057
s1 = self.get_stack(self.st1)
3058
s2 = self.get_stack(self.st2)
3059
s1.set('foo', 'bar')
3060
s2.set('foo', 'baz')
3063
s2.store.save_changes()
3064
self.assertEquals('baz', s2.get('foo'))
3065
# But the user get a warning
3066
self.assertLength(1, self.warnings)
3067
warning = self.warnings[0]
3068
self.assertStartsWith(warning, 'Option foo in section None')
3069
self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
3070
' The baz value will be saved.')
3072
def test_concurrent_deletion(self):
3073
self.st1._load_from_string('foo=bar')
3075
s1 = self.get_stack(self.st1)
3076
s2 = self.get_stack(self.st2)
3079
s1.store.save_changes()
3081
self.assertLength(0, self.warnings)
3082
s2.store.save_changes()
3084
self.assertLength(1, self.warnings)
3085
warning = self.warnings[0]
3086
self.assertStartsWith(warning, 'Option foo in section None')
3087
self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
3088
' The <DELETED> value will be saved.')
3091
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
3093
def get_store(self):
3094
return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3096
def test_get_quoted_string(self):
3097
store = self.get_store()
3098
store._load_from_string('foo= " abc "')
3099
stack = config.Stack([store.get_sections])
3100
self.assertEquals(' abc ', stack.get('foo'))
3102
def test_set_quoted_string(self):
3103
store = self.get_store()
3104
stack = config.Stack([store.get_sections], store)
3105
stack.set('foo', ' a b c ')
3107
self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
3110
class TestTransportIniFileStore(TestStore):
3112
def test_loading_unknown_file_fails(self):
3113
store = config.TransportIniFileStore(self.get_transport(),
3115
self.assertRaises(errors.NoSuchFile, store.load)
3117
def test_invalid_content(self):
3118
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3119
self.assertEquals(False, store.is_loaded())
3120
exc = self.assertRaises(
3121
errors.ParseConfigError, store._load_from_string,
3122
'this is invalid !')
3123
self.assertEndsWith(exc.filename, 'foo.conf')
3124
# And the load failed
3125
self.assertEquals(False, store.is_loaded())
3127
def test_get_embedded_sections(self):
3128
# A more complicated example (which also shows that section names and
3129
# option names share the same name space...)
3130
# FIXME: This should be fixed by forbidding dicts as values ?
3131
# -- vila 2011-04-05
3132
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3133
store._load_from_string('''
3137
foo_in_DEFAULT=foo_DEFAULT
3145
sections = list(store.get_sections())
3146
self.assertLength(4, sections)
3147
# The default section has no name.
3148
# List values are provided as strings and need to be explicitly
3149
# converted by specifying from_unicode=list_from_store at option
3151
self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
3153
self.assertSectionContent(
3154
('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
3155
self.assertSectionContent(
3156
('bar', {'foo_in_bar': 'barbar'}), sections[2])
3157
# sub sections are provided as embedded dicts.
3158
self.assertSectionContent(
3159
('baz', {'foo_in_baz': 'barbaz', 'qux': {'foo_in_qux': 'quux'}}),
3163
class TestLockableIniFileStore(TestStore):
3165
def test_create_store_in_created_dir(self):
3166
self.assertPathDoesNotExist('dir')
3167
t = self.get_transport('dir/subdir')
3168
store = config.LockableIniFileStore(t, 'foo.conf')
3169
store.get_mutable_section(None).set('foo', 'bar')
3171
self.assertPathExists('dir/subdir')
3174
class TestConcurrentStoreUpdates(TestStore):
3175
"""Test that Stores properly handle conccurent updates.
3177
New Store implementation may fail some of these tests but until such
3178
implementations exist it's hard to properly filter them from the scenarios
3179
applied here. If you encounter such a case, contact the bzr devs.
3182
scenarios = [(key, {'get_stack': builder}) for key, builder
3183
in config.test_stack_builder_registry.iteritems()]
3186
super(TestConcurrentStoreUpdates, self).setUp()
3187
self.stack = self.get_stack(self)
3188
if not isinstance(self.stack, config._CompatibleStack):
3189
raise tests.TestNotApplicable(
3190
'%s is not meant to be compatible with the old config design'
3192
self.stack.set('one', '1')
3193
self.stack.set('two', '2')
3195
self.stack.store.save()
3197
def test_simple_read_access(self):
3198
self.assertEquals('1', self.stack.get('one'))
3200
def test_simple_write_access(self):
3201
self.stack.set('one', 'one')
3202
self.assertEquals('one', self.stack.get('one'))
3204
def test_listen_to_the_last_speaker(self):
3206
c2 = self.get_stack(self)
3207
c1.set('one', 'ONE')
3208
c2.set('two', 'TWO')
3209
self.assertEquals('ONE', c1.get('one'))
3210
self.assertEquals('TWO', c2.get('two'))
3211
# The second update respect the first one
3212
self.assertEquals('ONE', c2.get('one'))
3214
def test_last_speaker_wins(self):
3215
# If the same config is not shared, the same variable modified twice
3216
# can only see a single result.
3218
c2 = self.get_stack(self)
3221
self.assertEquals('c2', c2.get('one'))
3222
# The first modification is still available until another refresh
3224
self.assertEquals('c1', c1.get('one'))
3225
c1.set('two', 'done')
3226
self.assertEquals('c2', c1.get('one'))
3228
def test_writes_are_serialized(self):
3230
c2 = self.get_stack(self)
3232
# We spawn a thread that will pause *during* the config saving.
3233
before_writing = threading.Event()
3234
after_writing = threading.Event()
3235
writing_done = threading.Event()
3236
c1_save_without_locking_orig = c1.store.save_without_locking
3237
def c1_save_without_locking():
3238
before_writing.set()
3239
c1_save_without_locking_orig()
3240
# The lock is held. We wait for the main thread to decide when to
3242
after_writing.wait()
3243
c1.store.save_without_locking = c1_save_without_locking
3247
t1 = threading.Thread(target=c1_set)
3248
# Collect the thread after the test
3249
self.addCleanup(t1.join)
3250
# Be ready to unblock the thread if the test goes wrong
3251
self.addCleanup(after_writing.set)
3253
before_writing.wait()
3254
self.assertRaises(errors.LockContention,
3255
c2.set, 'one', 'c2')
3256
self.assertEquals('c1', c1.get('one'))
3257
# Let the lock be released
3261
self.assertEquals('c2', c2.get('one'))
3263
def test_read_while_writing(self):
3265
# We spawn a thread that will pause *during* the write
3266
ready_to_write = threading.Event()
3267
do_writing = threading.Event()
3268
writing_done = threading.Event()
3269
# We override the _save implementation so we know the store is locked
3270
c1_save_without_locking_orig = c1.store.save_without_locking
3271
def c1_save_without_locking():
3272
ready_to_write.set()
3273
# The lock is held. We wait for the main thread to decide when to
3276
c1_save_without_locking_orig()
3278
c1.store.save_without_locking = c1_save_without_locking
3281
t1 = threading.Thread(target=c1_set)
3282
# Collect the thread after the test
3283
self.addCleanup(t1.join)
3284
# Be ready to unblock the thread if the test goes wrong
3285
self.addCleanup(do_writing.set)
3287
# Ensure the thread is ready to write
3288
ready_to_write.wait()
3289
self.assertEquals('c1', c1.get('one'))
3290
# If we read during the write, we get the old value
3291
c2 = self.get_stack(self)
3292
self.assertEquals('1', c2.get('one'))
3293
# Let the writing occur and ensure it occurred
3296
# Now we get the updated value
3297
c3 = self.get_stack(self)
3298
self.assertEquals('c1', c3.get('one'))
3300
# FIXME: It may be worth looking into removing the lock dir when it's not
3301
# needed anymore and look at possible fallouts for concurrent lockers. This
3302
# will matter if/when we use config files outside of bazaar directories
3303
# (.bazaar or .bzr) -- vila 20110-04-111
3306
class TestSectionMatcher(TestStore):
3308
scenarios = [('location', {'matcher': config.LocationMatcher}),
3309
('id', {'matcher': config.NameMatcher}),]
3312
super(TestSectionMatcher, self).setUp()
3313
# Any simple store is good enough
3314
self.get_store = config.test_store_builder_registry.get('configobj')
3316
def test_no_matches_for_empty_stores(self):
3317
store = self.get_store(self)
3318
store._load_from_string('')
3319
matcher = self.matcher(store, '/bar')
3320
self.assertEquals([], list(matcher.get_sections()))
3322
def test_build_doesnt_load_store(self):
3323
store = self.get_store(self)
3324
matcher = self.matcher(store, '/bar')
3325
self.assertFalse(store.is_loaded())
3328
class TestLocationSection(tests.TestCase):
3330
def get_section(self, options, extra_path):
3331
section = config.Section('foo', options)
3332
return config.LocationSection(section, extra_path)
3334
def test_simple_option(self):
3335
section = self.get_section({'foo': 'bar'}, '')
3336
self.assertEquals('bar', section.get('foo'))
3338
def test_option_with_extra_path(self):
3339
section = self.get_section({'foo': 'bar', 'foo:policy': 'appendpath'},
3341
self.assertEquals('bar/baz', section.get('foo'))
3343
def test_invalid_policy(self):
3344
section = self.get_section({'foo': 'bar', 'foo:policy': 'die'},
3346
# invalid policies are ignored
3347
self.assertEquals('bar', section.get('foo'))
3350
class TestLocationMatcher(TestStore):
3353
super(TestLocationMatcher, self).setUp()
3354
# Any simple store is good enough
3355
self.get_store = config.test_store_builder_registry.get('configobj')
3357
def test_unrelated_section_excluded(self):
3358
store = self.get_store(self)
3359
store._load_from_string('''
3367
section=/foo/bar/baz
3371
self.assertEquals(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
3373
[section.id for _, section in store.get_sections()])
3374
matcher = config.LocationMatcher(store, '/foo/bar/quux')
3375
sections = [section for _, section in matcher.get_sections()]
3376
self.assertEquals(['/foo/bar', '/foo'],
3377
[section.id for section in sections])
3378
self.assertEquals(['quux', 'bar/quux'],
3379
[section.extra_path for section in sections])
3381
def test_more_specific_sections_first(self):
3382
store = self.get_store(self)
3383
store._load_from_string('''
3389
self.assertEquals(['/foo', '/foo/bar'],
3390
[section.id for _, section in store.get_sections()])
3391
matcher = config.LocationMatcher(store, '/foo/bar/baz')
3392
sections = [section for _, section in matcher.get_sections()]
3393
self.assertEquals(['/foo/bar', '/foo'],
3394
[section.id for section in sections])
3395
self.assertEquals(['baz', 'bar/baz'],
3396
[section.extra_path for section in sections])
3398
def test_appendpath_in_no_name_section(self):
3399
# It's a bit weird to allow appendpath in a no-name section, but
3400
# someone may found a use for it
3401
store = self.get_store(self)
3402
store._load_from_string('''
3404
foo:policy = appendpath
3406
matcher = config.LocationMatcher(store, 'dir/subdir')
3407
sections = list(matcher.get_sections())
3408
self.assertLength(1, sections)
3409
self.assertEquals('bar/dir/subdir', sections[0][1].get('foo'))
3411
def test_file_urls_are_normalized(self):
3412
store = self.get_store(self)
3413
if sys.platform == 'win32':
3414
expected_url = 'file:///C:/dir/subdir'
3415
expected_location = 'C:/dir/subdir'
3417
expected_url = 'file:///dir/subdir'
3418
expected_location = '/dir/subdir'
3419
matcher = config.LocationMatcher(store, expected_url)
3420
self.assertEquals(expected_location, matcher.location)
3423
class TestStartingPathMatcher(TestStore):
3426
super(TestStartingPathMatcher, self).setUp()
3427
# Any simple store is good enough
3428
self.store = config.IniFileStore()
3430
def assertSectionIDs(self, expected, location, content):
3431
self.store._load_from_string(content)
3432
matcher = config.StartingPathMatcher(self.store, location)
3433
sections = list(matcher.get_sections())
3434
self.assertLength(len(expected), sections)
3435
self.assertEqual(expected, [section.id for _, section in sections])
3438
def test_empty(self):
3439
self.assertSectionIDs([], self.get_url(), '')
3441
def test_url_vs_local_paths(self):
3442
# The matcher location is an url and the section names are local paths
3443
sections = self.assertSectionIDs(['/foo/bar', '/foo'],
3444
'file:///foo/bar/baz', '''\
3449
def test_local_path_vs_url(self):
3450
# The matcher location is a local path and the section names are urls
3451
sections = self.assertSectionIDs(['file:///foo/bar', 'file:///foo'],
3452
'/foo/bar/baz', '''\
3458
def test_no_name_section_included_when_present(self):
3459
# Note that other tests will cover the case where the no-name section
3460
# is empty and as such, not included.
3461
sections = self.assertSectionIDs(['/foo/bar', '/foo', None],
3462
'/foo/bar/baz', '''\
3463
option = defined so the no-name section exists
3467
self.assertEquals(['baz', 'bar/baz', '/foo/bar/baz'],
3468
[s.locals['relpath'] for _, s in sections])
3470
def test_order_reversed(self):
3471
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3476
def test_unrelated_section_excluded(self):
3477
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3483
def test_glob_included(self):
3484
sections = self.assertSectionIDs(['/foo/*/baz', '/foo/b*', '/foo'],
3485
'/foo/bar/baz', '''\
3491
# Note that 'baz' as a relpath for /foo/b* is not fully correct, but
3492
# nothing really is... as far using {relpath} to append it to something
3493
# else, this seems good enough though.
3494
self.assertEquals(['', 'baz', 'bar/baz'],
3495
[s.locals['relpath'] for _, s in sections])
3497
def test_respect_order(self):
3498
self.assertSectionIDs(['/foo', '/foo/b*', '/foo/*/baz'],
3499
'/foo/bar/baz', '''\
3507
class TestNameMatcher(TestStore):
3510
super(TestNameMatcher, self).setUp()
3511
self.matcher = config.NameMatcher
3512
# Any simple store is good enough
3513
self.get_store = config.test_store_builder_registry.get('configobj')
3515
def get_matching_sections(self, name):
3516
store = self.get_store(self)
3517
store._load_from_string('''
3525
matcher = self.matcher(store, name)
3526
return list(matcher.get_sections())
3528
def test_matching(self):
3529
sections = self.get_matching_sections('foo')
3530
self.assertLength(1, sections)
3531
self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
3533
def test_not_matching(self):
3534
sections = self.get_matching_sections('baz')
3535
self.assertLength(0, sections)
3538
class TestBaseStackGet(tests.TestCase):
3541
super(TestBaseStackGet, self).setUp()
3542
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3544
def test_get_first_definition(self):
3545
store1 = config.IniFileStore()
3546
store1._load_from_string('foo=bar')
3547
store2 = config.IniFileStore()
3548
store2._load_from_string('foo=baz')
3549
conf = config.Stack([store1.get_sections, store2.get_sections])
3550
self.assertEquals('bar', conf.get('foo'))
3552
def test_get_with_registered_default_value(self):
3553
config.option_registry.register(config.Option('foo', default='bar'))
3554
conf_stack = config.Stack([])
3555
self.assertEquals('bar', conf_stack.get('foo'))
3557
def test_get_without_registered_default_value(self):
3558
config.option_registry.register(config.Option('foo'))
3559
conf_stack = config.Stack([])
3560
self.assertEquals(None, conf_stack.get('foo'))
3562
def test_get_without_default_value_for_not_registered(self):
3563
conf_stack = config.Stack([])
3564
self.assertEquals(None, conf_stack.get('foo'))
3566
def test_get_for_empty_section_callable(self):
3567
conf_stack = config.Stack([lambda : []])
3568
self.assertEquals(None, conf_stack.get('foo'))
3570
def test_get_for_broken_callable(self):
3571
# Trying to use and invalid callable raises an exception on first use
3572
conf_stack = config.Stack([object])
3573
self.assertRaises(TypeError, conf_stack.get, 'foo')
3576
class TestStackWithSimpleStore(tests.TestCase):
3579
super(TestStackWithSimpleStore, self).setUp()
3580
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3581
self.registry = config.option_registry
3583
def get_conf(self, content=None):
3584
return config.MemoryStack(content)
3586
def test_override_value_from_env(self):
3587
self.registry.register(
3588
config.Option('foo', default='bar', override_from_env=['FOO']))
3589
self.overrideEnv('FOO', 'quux')
3590
# Env variable provides a default taking over the option one
3591
conf = self.get_conf('foo=store')
3592
self.assertEquals('quux', conf.get('foo'))
3594
def test_first_override_value_from_env_wins(self):
3595
self.registry.register(
3596
config.Option('foo', default='bar',
3597
override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
3598
self.overrideEnv('FOO', 'foo')
3599
self.overrideEnv('BAZ', 'baz')
3600
# The first env var set wins
3601
conf = self.get_conf('foo=store')
3602
self.assertEquals('foo', conf.get('foo'))
3605
class TestMemoryStack(tests.TestCase):
3608
conf = config.MemoryStack('foo=bar')
3609
self.assertEquals('bar', conf.get('foo'))
3612
conf = config.MemoryStack('foo=bar')
3613
conf.set('foo', 'baz')
3614
self.assertEquals('baz', conf.get('foo'))
3616
def test_no_content(self):
3617
conf = config.MemoryStack()
3618
# No content means no loading
3619
self.assertFalse(conf.store.is_loaded())
3620
self.assertRaises(NotImplementedError, conf.get, 'foo')
3621
# But a content can still be provided
3622
conf.store._load_from_string('foo=bar')
3623
self.assertEquals('bar', conf.get('foo'))
3626
class TestStackIterSections(tests.TestCase):
3628
def test_empty_stack(self):
3629
conf = config.Stack([])
3630
sections = list(conf.iter_sections())
3631
self.assertLength(0, sections)
3633
def test_empty_store(self):
3634
store = config.IniFileStore()
3635
store._load_from_string('')
3636
conf = config.Stack([store.get_sections])
3637
sections = list(conf.iter_sections())
3638
self.assertLength(0, sections)
3640
def test_simple_store(self):
3641
store = config.IniFileStore()
3642
store._load_from_string('foo=bar')
3643
conf = config.Stack([store.get_sections])
3644
tuples = list(conf.iter_sections())
3645
self.assertLength(1, tuples)
3646
(found_store, found_section) = tuples[0]
3647
self.assertIs(store, found_store)
3649
def test_two_stores(self):
3650
store1 = config.IniFileStore()
3651
store1._load_from_string('foo=bar')
3652
store2 = config.IniFileStore()
3653
store2._load_from_string('bar=qux')
3654
conf = config.Stack([store1.get_sections, store2.get_sections])
3655
tuples = list(conf.iter_sections())
3656
self.assertLength(2, tuples)
3657
self.assertIs(store1, tuples[0][0])
3658
self.assertIs(store2, tuples[1][0])
3661
class TestStackWithTransport(tests.TestCaseWithTransport):
3663
scenarios = [(key, {'get_stack': builder}) for key, builder
3664
in config.test_stack_builder_registry.iteritems()]
3667
class TestConcreteStacks(TestStackWithTransport):
3669
def test_build_stack(self):
3670
# Just a smoke test to help debug builders
3671
stack = self.get_stack(self)
3674
class TestStackGet(TestStackWithTransport):
3677
super(TestStackGet, self).setUp()
3678
self.conf = self.get_stack(self)
3680
def test_get_for_empty_stack(self):
3681
self.assertEquals(None, self.conf.get('foo'))
3683
def test_get_hook(self):
3684
self.conf.set('foo', 'bar')
3688
config.ConfigHooks.install_named_hook('get', hook, None)
3689
self.assertLength(0, calls)
3690
value = self.conf.get('foo')
3691
self.assertEquals('bar', value)
3692
self.assertLength(1, calls)
3693
self.assertEquals((self.conf, 'foo', 'bar'), calls[0])
3696
class TestStackGetWithConverter(tests.TestCase):
3699
super(TestStackGetWithConverter, self).setUp()
3700
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3701
self.registry = config.option_registry
3703
def get_conf(self, content=None):
3704
return config.MemoryStack(content)
3706
def register_bool_option(self, name, default=None, default_from_env=None):
3707
b = config.Option(name, help='A boolean.',
3708
default=default, default_from_env=default_from_env,
3709
from_unicode=config.bool_from_store)
3710
self.registry.register(b)
3712
def test_get_default_bool_None(self):
3713
self.register_bool_option('foo')
3714
conf = self.get_conf('')
3715
self.assertEquals(None, conf.get('foo'))
3717
def test_get_default_bool_True(self):
3718
self.register_bool_option('foo', u'True')
3719
conf = self.get_conf('')
3720
self.assertEquals(True, conf.get('foo'))
3722
def test_get_default_bool_False(self):
3723
self.register_bool_option('foo', False)
3724
conf = self.get_conf('')
3725
self.assertEquals(False, conf.get('foo'))
3727
def test_get_default_bool_False_as_string(self):
3728
self.register_bool_option('foo', u'False')
3729
conf = self.get_conf('')
3730
self.assertEquals(False, conf.get('foo'))
3732
def test_get_default_bool_from_env_converted(self):
3733
self.register_bool_option('foo', u'True', default_from_env=['FOO'])
3734
self.overrideEnv('FOO', 'False')
3735
conf = self.get_conf('')
3736
self.assertEquals(False, conf.get('foo'))
3738
def test_get_default_bool_when_conversion_fails(self):
3739
self.register_bool_option('foo', default='True')
3740
conf = self.get_conf('foo=invalid boolean')
3741
self.assertEquals(True, conf.get('foo'))
3743
def register_integer_option(self, name,
3744
default=None, default_from_env=None):
3745
i = config.Option(name, help='An integer.',
3746
default=default, default_from_env=default_from_env,
3747
from_unicode=config.int_from_store)
3748
self.registry.register(i)
3750
def test_get_default_integer_None(self):
3751
self.register_integer_option('foo')
3752
conf = self.get_conf('')
3753
self.assertEquals(None, conf.get('foo'))
3755
def test_get_default_integer(self):
3756
self.register_integer_option('foo', 42)
3757
conf = self.get_conf('')
3758
self.assertEquals(42, conf.get('foo'))
3760
def test_get_default_integer_as_string(self):
3761
self.register_integer_option('foo', u'42')
3762
conf = self.get_conf('')
3763
self.assertEquals(42, conf.get('foo'))
3765
def test_get_default_integer_from_env(self):
3766
self.register_integer_option('foo', default_from_env=['FOO'])
3767
self.overrideEnv('FOO', '18')
3768
conf = self.get_conf('')
3769
self.assertEquals(18, conf.get('foo'))
3771
def test_get_default_integer_when_conversion_fails(self):
3772
self.register_integer_option('foo', default='12')
3773
conf = self.get_conf('foo=invalid integer')
3774
self.assertEquals(12, conf.get('foo'))
3776
def register_list_option(self, name, default=None, default_from_env=None):
3777
l = config.ListOption(name, help='A list.', default=default,
3778
default_from_env=default_from_env)
3779
self.registry.register(l)
3781
def test_get_default_list_None(self):
3782
self.register_list_option('foo')
3783
conf = self.get_conf('')
3784
self.assertEquals(None, conf.get('foo'))
3786
def test_get_default_list_empty(self):
3787
self.register_list_option('foo', '')
3788
conf = self.get_conf('')
3789
self.assertEquals([], conf.get('foo'))
3791
def test_get_default_list_from_env(self):
3792
self.register_list_option('foo', default_from_env=['FOO'])
3793
self.overrideEnv('FOO', '')
3794
conf = self.get_conf('')
3795
self.assertEquals([], conf.get('foo'))
3797
def test_get_with_list_converter_no_item(self):
3798
self.register_list_option('foo', None)
3799
conf = self.get_conf('foo=,')
3800
self.assertEquals([], conf.get('foo'))
3802
def test_get_with_list_converter_many_items(self):
3803
self.register_list_option('foo', None)
3804
conf = self.get_conf('foo=m,o,r,e')
3805
self.assertEquals(['m', 'o', 'r', 'e'], conf.get('foo'))
3807
def test_get_with_list_converter_embedded_spaces_many_items(self):
3808
self.register_list_option('foo', None)
3809
conf = self.get_conf('foo=" bar", "baz "')
3810
self.assertEquals([' bar', 'baz '], conf.get('foo'))
3812
def test_get_with_list_converter_stripped_spaces_many_items(self):
3813
self.register_list_option('foo', None)
3814
conf = self.get_conf('foo= bar , baz ')
3815
self.assertEquals(['bar', 'baz'], conf.get('foo'))
3818
class TestIterOptionRefs(tests.TestCase):
3819
"""iter_option_refs is a bit unusual, document some cases."""
3821
def assertRefs(self, expected, string):
3822
self.assertEquals(expected, list(config.iter_option_refs(string)))
3824
def test_empty(self):
3825
self.assertRefs([(False, '')], '')
3827
def test_no_refs(self):
3828
self.assertRefs([(False, 'foo bar')], 'foo bar')
3830
def test_single_ref(self):
3831
self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
3833
def test_broken_ref(self):
3834
self.assertRefs([(False, '{foo')], '{foo')
3836
def test_embedded_ref(self):
3837
self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
3840
def test_two_refs(self):
3841
self.assertRefs([(False, ''), (True, '{foo}'),
3842
(False, ''), (True, '{bar}'),
3846
def test_newline_in_refs_are_not_matched(self):
3847
self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
3850
class TestStackExpandOptions(tests.TestCaseWithTransport):
3853
super(TestStackExpandOptions, self).setUp()
3854
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3855
self.registry = config.option_registry
3856
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3857
self.conf = config.Stack([store.get_sections], store)
3859
def assertExpansion(self, expected, string, env=None):
3860
self.assertEquals(expected, self.conf.expand_options(string, env))
3862
def test_no_expansion(self):
3863
self.assertExpansion('foo', 'foo')
3865
def test_expand_default_value(self):
3866
self.conf.store._load_from_string('bar=baz')
3867
self.registry.register(config.Option('foo', default=u'{bar}'))
3868
self.assertEquals('baz', self.conf.get('foo', expand=True))
3870
def test_expand_default_from_env(self):
3871
self.conf.store._load_from_string('bar=baz')
3872
self.registry.register(config.Option('foo', default_from_env=['FOO']))
3873
self.overrideEnv('FOO', '{bar}')
3874
self.assertEquals('baz', self.conf.get('foo', expand=True))
3876
def test_expand_default_on_failed_conversion(self):
3877
self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
3878
self.registry.register(
3879
config.Option('foo', default=u'{bar}',
3880
from_unicode=config.int_from_store))
3881
self.assertEquals(42, self.conf.get('foo', expand=True))
3883
def test_env_adding_options(self):
3884
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3886
def test_env_overriding_options(self):
3887
self.conf.store._load_from_string('foo=baz')
3888
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3890
def test_simple_ref(self):
3891
self.conf.store._load_from_string('foo=xxx')
3892
self.assertExpansion('xxx', '{foo}')
3894
def test_unknown_ref(self):
3895
self.assertRaises(errors.ExpandingUnknownOption,
3896
self.conf.expand_options, '{foo}')
3898
def test_indirect_ref(self):
3899
self.conf.store._load_from_string('''
3903
self.assertExpansion('xxx', '{bar}')
3905
def test_embedded_ref(self):
3906
self.conf.store._load_from_string('''
3910
self.assertExpansion('xxx', '{{bar}}')
3912
def test_simple_loop(self):
3913
self.conf.store._load_from_string('foo={foo}')
3914
self.assertRaises(errors.OptionExpansionLoop,
3915
self.conf.expand_options, '{foo}')
3917
def test_indirect_loop(self):
3918
self.conf.store._load_from_string('''
3922
e = self.assertRaises(errors.OptionExpansionLoop,
3923
self.conf.expand_options, '{foo}')
3924
self.assertEquals('foo->bar->baz', e.refs)
3925
self.assertEquals('{foo}', e.string)
3927
def test_list(self):
3928
self.conf.store._load_from_string('''
3932
list={foo},{bar},{baz}
3934
self.registry.register(
3935
config.ListOption('list'))
3936
self.assertEquals(['start', 'middle', 'end'],
3937
self.conf.get('list', expand=True))
3939
def test_cascading_list(self):
3940
self.conf.store._load_from_string('''
3946
self.registry.register(config.ListOption('list'))
3947
# Register an intermediate option as a list to ensure no conversion
3948
# happen while expanding. Conversion should only occur for the original
3949
# option ('list' here).
3950
self.registry.register(config.ListOption('baz'))
3951
self.assertEquals(['start', 'middle', 'end'],
3952
self.conf.get('list', expand=True))
3954
def test_pathologically_hidden_list(self):
3955
self.conf.store._load_from_string('''
3961
hidden={start}{middle}{end}
3963
# What matters is what the registration says, the conversion happens
3964
# only after all expansions have been performed
3965
self.registry.register(config.ListOption('hidden'))
3966
self.assertEquals(['bin', 'go'],
3967
self.conf.get('hidden', expand=True))
3970
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
3973
super(TestStackCrossSectionsExpand, self).setUp()
3975
def get_config(self, location, string):
3978
# Since we don't save the config we won't strictly require to inherit
3979
# from TestCaseInTempDir, but an error occurs so quickly...
3980
c = config.LocationStack(location)
3981
c.store._load_from_string(string)
3984
def test_dont_cross_unrelated_section(self):
3985
c = self.get_config('/another/branch/path','''
3990
[/another/branch/path]
3993
self.assertRaises(errors.ExpandingUnknownOption,
3994
c.get, 'bar', expand=True)
3996
def test_cross_related_sections(self):
3997
c = self.get_config('/project/branch/path','''
4001
[/project/branch/path]
4004
self.assertEquals('quux', c.get('bar', expand=True))
4007
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
4009
def test_cross_global_locations(self):
4010
l_store = config.LocationStore()
4011
l_store._load_from_string('''
4017
g_store = config.GlobalStore()
4018
g_store._load_from_string('''
4024
stack = config.LocationStack('/branch')
4025
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4026
self.assertEquals('loc-foo', stack.get('gfoo', expand=True))
4029
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
4031
def test_expand_locals_empty(self):
4032
l_store = config.LocationStore()
4033
l_store._load_from_string('''
4034
[/home/user/project]
4039
stack = config.LocationStack('/home/user/project/')
4040
self.assertEquals('', stack.get('base', expand=True))
4041
self.assertEquals('', stack.get('rel', expand=True))
4043
def test_expand_basename_locally(self):
4044
l_store = config.LocationStore()
4045
l_store._load_from_string('''
4046
[/home/user/project]
4050
stack = config.LocationStack('/home/user/project/branch')
4051
self.assertEquals('branch', stack.get('bfoo', expand=True))
4053
def test_expand_basename_locally_longer_path(self):
4054
l_store = config.LocationStore()
4055
l_store._load_from_string('''
4060
stack = config.LocationStack('/home/user/project/dir/branch')
4061
self.assertEquals('branch', stack.get('bfoo', expand=True))
4063
def test_expand_relpath_locally(self):
4064
l_store = config.LocationStore()
4065
l_store._load_from_string('''
4066
[/home/user/project]
4067
lfoo = loc-foo/{relpath}
4070
stack = config.LocationStack('/home/user/project/branch')
4071
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4073
def test_expand_relpath_unknonw_in_global(self):
4074
g_store = config.GlobalStore()
4075
g_store._load_from_string('''
4080
stack = config.LocationStack('/home/user/project/branch')
4081
self.assertRaises(errors.ExpandingUnknownOption,
4082
stack.get, 'gfoo', expand=True)
4084
def test_expand_local_option_locally(self):
4085
l_store = config.LocationStore()
4086
l_store._load_from_string('''
4087
[/home/user/project]
4088
lfoo = loc-foo/{relpath}
4092
g_store = config.GlobalStore()
4093
g_store._load_from_string('''
4099
stack = config.LocationStack('/home/user/project/branch')
4100
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4101
self.assertEquals('loc-foo/branch', stack.get('gfoo', expand=True))
4103
def test_locals_dont_leak(self):
4104
"""Make sure we chose the right local in presence of several sections.
4106
l_store = config.LocationStore()
4107
l_store._load_from_string('''
4109
lfoo = loc-foo/{relpath}
4110
[/home/user/project]
4111
lfoo = loc-foo/{relpath}
4114
stack = config.LocationStack('/home/user/project/branch')
4115
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4116
stack = config.LocationStack('/home/user/bar/baz')
4117
self.assertEquals('loc-foo/bar/baz', stack.get('lfoo', expand=True))
4121
class TestStackSet(TestStackWithTransport):
4123
def test_simple_set(self):
4124
conf = self.get_stack(self)
4125
self.assertEquals(None, conf.get('foo'))
4126
conf.set('foo', 'baz')
4127
# Did we get it back ?
4128
self.assertEquals('baz', conf.get('foo'))
4130
def test_set_creates_a_new_section(self):
4131
conf = self.get_stack(self)
4132
conf.set('foo', 'baz')
4133
self.assertEquals, 'baz', conf.get('foo')
4135
def test_set_hook(self):
4139
config.ConfigHooks.install_named_hook('set', hook, None)
4140
self.assertLength(0, calls)
4141
conf = self.get_stack(self)
4142
conf.set('foo', 'bar')
4143
self.assertLength(1, calls)
4144
self.assertEquals((conf, 'foo', 'bar'), calls[0])
4147
class TestStackRemove(TestStackWithTransport):
4149
def test_remove_existing(self):
4150
conf = self.get_stack(self)
4151
conf.set('foo', 'bar')
4152
self.assertEquals('bar', conf.get('foo'))
4154
# Did we get it back ?
4155
self.assertEquals(None, conf.get('foo'))
4157
def test_remove_unknown(self):
4158
conf = self.get_stack(self)
4159
self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
4161
def test_remove_hook(self):
4165
config.ConfigHooks.install_named_hook('remove', hook, None)
4166
self.assertLength(0, calls)
4167
conf = self.get_stack(self)
4168
conf.set('foo', 'bar')
4170
self.assertLength(1, calls)
4171
self.assertEquals((conf, 'foo'), calls[0])
4174
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
4177
super(TestConfigGetOptions, self).setUp()
4178
create_configs(self)
4180
def test_no_variable(self):
4181
# Using branch should query branch, locations and bazaar
4182
self.assertOptions([], self.branch_config)
4184
def test_option_in_bazaar(self):
4185
self.bazaar_config.set_user_option('file', 'bazaar')
4186
self.assertOptions([('file', 'bazaar', 'DEFAULT', 'bazaar')],
4189
def test_option_in_locations(self):
4190
self.locations_config.set_user_option('file', 'locations')
4192
[('file', 'locations', self.tree.basedir, 'locations')],
4193
self.locations_config)
4195
def test_option_in_branch(self):
4196
self.branch_config.set_user_option('file', 'branch')
4197
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch')],
4200
def test_option_in_bazaar_and_branch(self):
4201
self.bazaar_config.set_user_option('file', 'bazaar')
4202
self.branch_config.set_user_option('file', 'branch')
4203
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch'),
4204
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4207
def test_option_in_branch_and_locations(self):
4208
# Hmm, locations override branch :-/
4209
self.locations_config.set_user_option('file', 'locations')
4210
self.branch_config.set_user_option('file', 'branch')
4212
[('file', 'locations', self.tree.basedir, 'locations'),
4213
('file', 'branch', 'DEFAULT', 'branch'),],
4216
def test_option_in_bazaar_locations_and_branch(self):
4217
self.bazaar_config.set_user_option('file', 'bazaar')
4218
self.locations_config.set_user_option('file', 'locations')
4219
self.branch_config.set_user_option('file', 'branch')
4221
[('file', 'locations', self.tree.basedir, 'locations'),
4222
('file', 'branch', 'DEFAULT', 'branch'),
4223
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4227
class TestConfigRemoveOption(tests.TestCaseWithTransport, TestOptionsMixin):
4230
super(TestConfigRemoveOption, self).setUp()
4231
create_configs_with_file_option(self)
4233
def test_remove_in_locations(self):
4234
self.locations_config.remove_user_option('file', self.tree.basedir)
4236
[('file', 'branch', 'DEFAULT', 'branch'),
4237
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4240
def test_remove_in_branch(self):
4241
self.branch_config.remove_user_option('file')
4243
[('file', 'locations', self.tree.basedir, 'locations'),
4244
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4247
def test_remove_in_bazaar(self):
4248
self.bazaar_config.remove_user_option('file')
4250
[('file', 'locations', self.tree.basedir, 'locations'),
4251
('file', 'branch', 'DEFAULT', 'branch'),],
4255
class TestConfigGetSections(tests.TestCaseWithTransport):
4258
super(TestConfigGetSections, self).setUp()
4259
create_configs(self)
4261
def assertSectionNames(self, expected, conf, name=None):
4262
"""Check which sections are returned for a given config.
4264
If fallback configurations exist their sections can be included.
4266
:param expected: A list of section names.
4268
:param conf: The configuration that will be queried.
4270
:param name: An optional section name that will be passed to
4273
sections = list(conf._get_sections(name))
4274
self.assertLength(len(expected), sections)
4275
self.assertEqual(expected, [name for name, _, _ in sections])
4277
def test_bazaar_default_section(self):
4278
self.assertSectionNames(['DEFAULT'], self.bazaar_config)
4280
def test_locations_default_section(self):
4281
# No sections are defined in an empty file
4282
self.assertSectionNames([], self.locations_config)
4284
def test_locations_named_section(self):
4285
self.locations_config.set_user_option('file', 'locations')
4286
self.assertSectionNames([self.tree.basedir], self.locations_config)
4288
def test_locations_matching_sections(self):
4289
loc_config = self.locations_config
4290
loc_config.set_user_option('file', 'locations')
4291
# We need to cheat a bit here to create an option in sections above and
4292
# below the 'location' one.
4293
parser = loc_config._get_parser()
4294
# locations.cong deals with '/' ignoring native os.sep
4295
location_names = self.tree.basedir.split('/')
4296
parent = '/'.join(location_names[:-1])
4297
child = '/'.join(location_names + ['child'])
4299
parser[parent]['file'] = 'parent'
4301
parser[child]['file'] = 'child'
4302
self.assertSectionNames([self.tree.basedir, parent], loc_config)
4304
def test_branch_data_default_section(self):
4305
self.assertSectionNames([None],
4306
self.branch_config._get_branch_data_config())
4308
def test_branch_default_sections(self):
4309
# No sections are defined in an empty locations file
4310
self.assertSectionNames([None, 'DEFAULT'],
4312
# Unless we define an option
4313
self.branch_config._get_location_config().set_user_option(
4314
'file', 'locations')
4315
self.assertSectionNames([self.tree.basedir, None, 'DEFAULT'],
4318
def test_bazaar_named_section(self):
4319
# We need to cheat as the API doesn't give direct access to sections
4320
# other than DEFAULT.
4321
self.bazaar_config.set_alias('bazaar', 'bzr')
4322
self.assertSectionNames(['ALIASES'], self.bazaar_config, 'ALIASES')
4325
1241
class TestAuthenticationConfigFile(tests.TestCase):
4326
1242
"""Test the authentication.conf file matching"""