1305
1991
self.assertIs(None, bzrdir_config.get_default_stack_on())
1994
class TestOldConfigHooks(tests.TestCaseWithTransport):
1997
super(TestOldConfigHooks, self).setUp()
1998
create_configs_with_file_option(self)
2000
def assertGetHook(self, conf, name, value):
2004
config.OldConfigHooks.install_named_hook('get', hook, None)
2006
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2007
self.assertLength(0, calls)
2008
actual_value = conf.get_user_option(name)
2009
self.assertEquals(value, actual_value)
2010
self.assertLength(1, calls)
2011
self.assertEquals((conf, name, value), calls[0])
2013
def test_get_hook_bazaar(self):
2014
self.assertGetHook(self.bazaar_config, 'file', 'bazaar')
2016
def test_get_hook_locations(self):
2017
self.assertGetHook(self.locations_config, 'file', 'locations')
2019
def test_get_hook_branch(self):
2020
# Since locations masks branch, we define a different option
2021
self.branch_config.set_user_option('file2', 'branch')
2022
self.assertGetHook(self.branch_config, 'file2', 'branch')
2024
def assertSetHook(self, conf, name, value):
2028
config.OldConfigHooks.install_named_hook('set', hook, None)
2030
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2031
self.assertLength(0, calls)
2032
conf.set_user_option(name, value)
2033
self.assertLength(1, calls)
2034
# We can't assert the conf object below as different configs use
2035
# different means to implement set_user_option and we care only about
2037
self.assertEquals((name, value), calls[0][1:])
2039
def test_set_hook_bazaar(self):
2040
self.assertSetHook(self.bazaar_config, 'foo', 'bazaar')
2042
def test_set_hook_locations(self):
2043
self.assertSetHook(self.locations_config, 'foo', 'locations')
2045
def test_set_hook_branch(self):
2046
self.assertSetHook(self.branch_config, 'foo', 'branch')
2048
def assertRemoveHook(self, conf, name, section_name=None):
2052
config.OldConfigHooks.install_named_hook('remove', hook, None)
2054
config.OldConfigHooks.uninstall_named_hook, 'remove', None)
2055
self.assertLength(0, calls)
2056
conf.remove_user_option(name, section_name)
2057
self.assertLength(1, calls)
2058
# We can't assert the conf object below as different configs use
2059
# different means to implement remove_user_option and we care only about
2061
self.assertEquals((name,), calls[0][1:])
2063
def test_remove_hook_bazaar(self):
2064
self.assertRemoveHook(self.bazaar_config, 'file')
2066
def test_remove_hook_locations(self):
2067
self.assertRemoveHook(self.locations_config, 'file',
2068
self.locations_config.location)
2070
def test_remove_hook_branch(self):
2071
self.assertRemoveHook(self.branch_config, 'file')
2073
def assertLoadHook(self, name, conf_class, *conf_args):
2077
config.OldConfigHooks.install_named_hook('load', hook, None)
2079
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2080
self.assertLength(0, calls)
2082
conf = conf_class(*conf_args)
2083
# Access an option to trigger a load
2084
conf.get_user_option(name)
2085
self.assertLength(1, calls)
2086
# Since we can't assert about conf, we just use the number of calls ;-/
2088
def test_load_hook_bazaar(self):
2089
self.assertLoadHook('file', config.GlobalConfig)
2091
def test_load_hook_locations(self):
2092
self.assertLoadHook('file', config.LocationConfig, self.tree.basedir)
2094
def test_load_hook_branch(self):
2095
self.assertLoadHook('file', config.BranchConfig, self.tree.branch)
2097
def assertSaveHook(self, conf):
2101
config.OldConfigHooks.install_named_hook('save', hook, None)
2103
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2104
self.assertLength(0, calls)
2105
# Setting an option triggers a save
2106
conf.set_user_option('foo', 'bar')
2107
self.assertLength(1, calls)
2108
# Since we can't assert about conf, we just use the number of calls ;-/
2110
def test_save_hook_bazaar(self):
2111
self.assertSaveHook(self.bazaar_config)
2113
def test_save_hook_locations(self):
2114
self.assertSaveHook(self.locations_config)
2116
def test_save_hook_branch(self):
2117
self.assertSaveHook(self.branch_config)
2120
class TestOldConfigHooksForRemote(tests.TestCaseWithTransport):
2121
"""Tests config hooks for remote configs.
2123
No tests for the remove hook as this is not implemented there.
2127
super(TestOldConfigHooksForRemote, self).setUp()
2128
self.transport_server = test_server.SmartTCPServer_for_testing
2129
create_configs_with_file_option(self)
2131
def assertGetHook(self, conf, name, value):
2135
config.OldConfigHooks.install_named_hook('get', hook, None)
2137
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2138
self.assertLength(0, calls)
2139
actual_value = conf.get_option(name)
2140
self.assertEquals(value, actual_value)
2141
self.assertLength(1, calls)
2142
self.assertEquals((conf, name, value), calls[0])
2144
def test_get_hook_remote_branch(self):
2145
remote_branch = branch.Branch.open(self.get_url('tree'))
2146
self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
2148
def test_get_hook_remote_bzrdir(self):
2149
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2150
conf = remote_bzrdir._get_config()
2151
conf.set_option('remotedir', 'file')
2152
self.assertGetHook(conf, 'file', 'remotedir')
2154
def assertSetHook(self, conf, name, value):
2158
config.OldConfigHooks.install_named_hook('set', hook, None)
2160
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2161
self.assertLength(0, calls)
2162
conf.set_option(value, name)
2163
self.assertLength(1, calls)
2164
# We can't assert the conf object below as different configs use
2165
# different means to implement set_user_option and we care only about
2167
self.assertEquals((name, value), calls[0][1:])
2169
def test_set_hook_remote_branch(self):
2170
remote_branch = branch.Branch.open(self.get_url('tree'))
2171
self.addCleanup(remote_branch.lock_write().unlock)
2172
self.assertSetHook(remote_branch._get_config(), 'file', 'remote')
2174
def test_set_hook_remote_bzrdir(self):
2175
remote_branch = branch.Branch.open(self.get_url('tree'))
2176
self.addCleanup(remote_branch.lock_write().unlock)
2177
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2178
self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
2180
def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
2184
config.OldConfigHooks.install_named_hook('load', hook, None)
2186
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2187
self.assertLength(0, calls)
2189
conf = conf_class(*conf_args)
2190
# Access an option to trigger a load
2191
conf.get_option(name)
2192
self.assertLength(expected_nb_calls, calls)
2193
# Since we can't assert about conf, we just use the number of calls ;-/
2195
def test_load_hook_remote_branch(self):
2196
remote_branch = branch.Branch.open(self.get_url('tree'))
2197
self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
2199
def test_load_hook_remote_bzrdir(self):
2200
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2201
# The config file doesn't exist, set an option to force its creation
2202
conf = remote_bzrdir._get_config()
2203
conf.set_option('remotedir', 'file')
2204
# We get one call for the server and one call for the client, this is
2205
# caused by the differences in implementations betwen
2206
# SmartServerBzrDirRequestConfigFile (in smart/bzrdir.py) and
2207
# SmartServerBranchGetConfigFile (in smart/branch.py)
2208
self.assertLoadHook(2 ,'file', remote.RemoteBzrDirConfig, remote_bzrdir)
2210
def assertSaveHook(self, conf):
2214
config.OldConfigHooks.install_named_hook('save', hook, None)
2216
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2217
self.assertLength(0, calls)
2218
# Setting an option triggers a save
2219
conf.set_option('foo', 'bar')
2220
self.assertLength(1, calls)
2221
# Since we can't assert about conf, we just use the number of calls ;-/
2223
def test_save_hook_remote_branch(self):
2224
remote_branch = branch.Branch.open(self.get_url('tree'))
2225
self.addCleanup(remote_branch.lock_write().unlock)
2226
self.assertSaveHook(remote_branch._get_config())
2228
def test_save_hook_remote_bzrdir(self):
2229
remote_branch = branch.Branch.open(self.get_url('tree'))
2230
self.addCleanup(remote_branch.lock_write().unlock)
2231
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2232
self.assertSaveHook(remote_bzrdir._get_config())
2235
class TestOption(tests.TestCase):
2237
def test_default_value(self):
2238
opt = config.Option('foo', default='bar')
2239
self.assertEquals('bar', opt.get_default())
2241
def test_callable_default_value(self):
2242
def bar_as_unicode():
2244
opt = config.Option('foo', default=bar_as_unicode)
2245
self.assertEquals('bar', opt.get_default())
2247
def test_default_value_from_env(self):
2248
opt = config.Option('foo', default='bar', default_from_env=['FOO'])
2249
self.overrideEnv('FOO', 'quux')
2250
# Env variable provides a default taking over the option one
2251
self.assertEquals('quux', opt.get_default())
2253
def test_first_default_value_from_env_wins(self):
2254
opt = config.Option('foo', default='bar',
2255
default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
2256
self.overrideEnv('FOO', 'foo')
2257
self.overrideEnv('BAZ', 'baz')
2258
# The first env var set wins
2259
self.assertEquals('foo', opt.get_default())
2261
def test_not_supported_list_default_value(self):
2262
self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
2264
def test_not_supported_object_default_value(self):
2265
self.assertRaises(AssertionError, config.Option, 'foo',
2268
def test_not_supported_callable_default_value_not_unicode(self):
2269
def bar_not_unicode():
2271
opt = config.Option('foo', default=bar_not_unicode)
2272
self.assertRaises(AssertionError, opt.get_default)
2274
def test_get_help_topic(self):
2275
opt = config.Option('foo')
2276
self.assertEquals('foo', opt.get_help_topic())
2279
class TestOptionConverterMixin(object):
2281
def assertConverted(self, expected, opt, value):
2282
self.assertEquals(expected, opt.convert_from_unicode(None, value))
2284
def assertWarns(self, opt, value):
2287
warnings.append(args[0] % args[1:])
2288
self.overrideAttr(trace, 'warning', warning)
2289
self.assertEquals(None, opt.convert_from_unicode(None, value))
2290
self.assertLength(1, warnings)
2292
'Value "%s" is not valid for "%s"' % (value, opt.name),
2295
def assertErrors(self, opt, value):
2296
self.assertRaises(errors.ConfigOptionValueError,
2297
opt.convert_from_unicode, None, value)
2299
def assertConvertInvalid(self, opt, invalid_value):
2301
self.assertEquals(None, opt.convert_from_unicode(None, invalid_value))
2302
opt.invalid = 'warning'
2303
self.assertWarns(opt, invalid_value)
2304
opt.invalid = 'error'
2305
self.assertErrors(opt, invalid_value)
2308
class TestOptionWithBooleanConverter(tests.TestCase, TestOptionConverterMixin):
2310
def get_option(self):
2311
return config.Option('foo', help='A boolean.',
2312
from_unicode=config.bool_from_store)
2314
def test_convert_invalid(self):
2315
opt = self.get_option()
2316
# A string that is not recognized as a boolean
2317
self.assertConvertInvalid(opt, u'invalid-boolean')
2318
# A list of strings is never recognized as a boolean
2319
self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
2321
def test_convert_valid(self):
2322
opt = self.get_option()
2323
self.assertConverted(True, opt, u'True')
2324
self.assertConverted(True, opt, u'1')
2325
self.assertConverted(False, opt, u'False')
2328
class TestOptionWithIntegerConverter(tests.TestCase, TestOptionConverterMixin):
2330
def get_option(self):
2331
return config.Option('foo', help='An integer.',
2332
from_unicode=config.int_from_store)
2334
def test_convert_invalid(self):
2335
opt = self.get_option()
2336
# A string that is not recognized as an integer
2337
self.assertConvertInvalid(opt, u'forty-two')
2338
# A list of strings is never recognized as an integer
2339
self.assertConvertInvalid(opt, [u'a', u'list'])
2341
def test_convert_valid(self):
2342
opt = self.get_option()
2343
self.assertConverted(16, opt, u'16')
2346
class TestOptionWithSIUnitConverter(tests.TestCase, TestOptionConverterMixin):
2348
def get_option(self):
2349
return config.Option('foo', help='An integer in SI units.',
2350
from_unicode=config.int_SI_from_store)
2352
def test_convert_invalid(self):
2353
opt = self.get_option()
2354
self.assertConvertInvalid(opt, u'not-a-unit')
2355
self.assertConvertInvalid(opt, u'Gb') # Forgot the int
2356
self.assertConvertInvalid(opt, u'1b') # Forgot the unit
2357
self.assertConvertInvalid(opt, u'1GG')
2358
self.assertConvertInvalid(opt, u'1Mbb')
2359
self.assertConvertInvalid(opt, u'1MM')
2361
def test_convert_valid(self):
2362
opt = self.get_option()
2363
self.assertConverted(int(5e3), opt, u'5kb')
2364
self.assertConverted(int(5e6), opt, u'5M')
2365
self.assertConverted(int(5e6), opt, u'5MB')
2366
self.assertConverted(int(5e9), opt, u'5g')
2367
self.assertConverted(int(5e9), opt, u'5gB')
2368
self.assertConverted(100, opt, u'100')
2371
class TestListOption(tests.TestCase, TestOptionConverterMixin):
2373
def get_option(self):
2374
return config.ListOption('foo', help='A list.')
2376
def test_convert_invalid(self):
2377
opt = self.get_option()
2378
# We don't even try to convert a list into a list, we only expect
2380
self.assertConvertInvalid(opt, [1])
2381
# No string is invalid as all forms can be converted to a list
2383
def test_convert_valid(self):
2384
opt = self.get_option()
2385
# An empty string is an empty list
2386
self.assertConverted([], opt, '') # Using a bare str() just in case
2387
self.assertConverted([], opt, u'')
2389
self.assertConverted([u'True'], opt, u'True')
2391
self.assertConverted([u'42'], opt, u'42')
2393
self.assertConverted([u'bar'], opt, u'bar')
2396
class TestRegistryOption(tests.TestCase, TestOptionConverterMixin):
2398
def get_option(self, registry):
2399
return config.RegistryOption('foo', registry,
2400
help='A registry option.')
2402
def test_convert_invalid(self):
2403
registry = _mod_registry.Registry()
2404
opt = self.get_option(registry)
2405
self.assertConvertInvalid(opt, [1])
2406
self.assertConvertInvalid(opt, u"notregistered")
2408
def test_convert_valid(self):
2409
registry = _mod_registry.Registry()
2410
registry.register("someval", 1234)
2411
opt = self.get_option(registry)
2412
# Using a bare str() just in case
2413
self.assertConverted(1234, opt, "someval")
2414
self.assertConverted(1234, opt, u'someval')
2415
self.assertConverted(None, opt, None)
2417
def test_help(self):
2418
registry = _mod_registry.Registry()
2419
registry.register("someval", 1234, help="some option")
2420
registry.register("dunno", 1234, help="some other option")
2421
opt = self.get_option(registry)
2423
'A registry option.\n'
2425
'The following values are supported:\n'
2426
' dunno - some other option\n'
2427
' someval - some option\n',
2430
def test_get_help_text(self):
2431
registry = _mod_registry.Registry()
2432
registry.register("someval", 1234, help="some option")
2433
registry.register("dunno", 1234, help="some other option")
2434
opt = self.get_option(registry)
2436
'A registry option.\n'
2438
'The following values are supported:\n'
2439
' dunno - some other option\n'
2440
' someval - some option\n',
2441
opt.get_help_text())
2444
class TestOptionRegistry(tests.TestCase):
2447
super(TestOptionRegistry, self).setUp()
2448
# Always start with an empty registry
2449
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2450
self.registry = config.option_registry
2452
def test_register(self):
2453
opt = config.Option('foo')
2454
self.registry.register(opt)
2455
self.assertIs(opt, self.registry.get('foo'))
2457
def test_registered_help(self):
2458
opt = config.Option('foo', help='A simple option')
2459
self.registry.register(opt)
2460
self.assertEquals('A simple option', self.registry.get_help('foo'))
2462
lazy_option = config.Option('lazy_foo', help='Lazy help')
2464
def test_register_lazy(self):
2465
self.registry.register_lazy('lazy_foo', self.__module__,
2466
'TestOptionRegistry.lazy_option')
2467
self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
2469
def test_registered_lazy_help(self):
2470
self.registry.register_lazy('lazy_foo', self.__module__,
2471
'TestOptionRegistry.lazy_option')
2472
self.assertEquals('Lazy help', self.registry.get_help('lazy_foo'))
2475
class TestRegisteredOptions(tests.TestCase):
2476
"""All registered options should verify some constraints."""
2478
scenarios = [(key, {'option_name': key, 'option': option}) for key, option
2479
in config.option_registry.iteritems()]
2482
super(TestRegisteredOptions, self).setUp()
2483
self.registry = config.option_registry
2485
def test_proper_name(self):
2486
# An option should be registered under its own name, this can't be
2487
# checked at registration time for the lazy ones.
2488
self.assertEquals(self.option_name, self.option.name)
2490
def test_help_is_set(self):
2491
option_help = self.registry.get_help(self.option_name)
2492
self.assertNotEquals(None, option_help)
2493
# Come on, think about the user, he really wants to know what the
2495
self.assertIsNot(None, option_help)
2496
self.assertNotEquals('', option_help)
2499
class TestSection(tests.TestCase):
2501
# FIXME: Parametrize so that all sections produced by Stores run these
2502
# tests -- vila 2011-04-01
2504
def test_get_a_value(self):
2505
a_dict = dict(foo='bar')
2506
section = config.Section('myID', a_dict)
2507
self.assertEquals('bar', section.get('foo'))
2509
def test_get_unknown_option(self):
2511
section = config.Section(None, a_dict)
2512
self.assertEquals('out of thin air',
2513
section.get('foo', 'out of thin air'))
2515
def test_options_is_shared(self):
2517
section = config.Section(None, a_dict)
2518
self.assertIs(a_dict, section.options)
2521
class TestMutableSection(tests.TestCase):
2523
scenarios = [('mutable',
2525
lambda opts: config.MutableSection('myID', opts)},),
2529
a_dict = dict(foo='bar')
2530
section = self.get_section(a_dict)
2531
section.set('foo', 'new_value')
2532
self.assertEquals('new_value', section.get('foo'))
2533
# The change appears in the shared section
2534
self.assertEquals('new_value', a_dict.get('foo'))
2535
# We keep track of the change
2536
self.assertTrue('foo' in section.orig)
2537
self.assertEquals('bar', section.orig.get('foo'))
2539
def test_set_preserve_original_once(self):
2540
a_dict = dict(foo='bar')
2541
section = self.get_section(a_dict)
2542
section.set('foo', 'first_value')
2543
section.set('foo', 'second_value')
2544
# We keep track of the original value
2545
self.assertTrue('foo' in section.orig)
2546
self.assertEquals('bar', section.orig.get('foo'))
2548
def test_remove(self):
2549
a_dict = dict(foo='bar')
2550
section = self.get_section(a_dict)
2551
section.remove('foo')
2552
# We get None for unknown options via the default value
2553
self.assertEquals(None, section.get('foo'))
2554
# Or we just get the default value
2555
self.assertEquals('unknown', section.get('foo', 'unknown'))
2556
self.assertFalse('foo' in section.options)
2557
# We keep track of the deletion
2558
self.assertTrue('foo' in section.orig)
2559
self.assertEquals('bar', section.orig.get('foo'))
2561
def test_remove_new_option(self):
2563
section = self.get_section(a_dict)
2564
section.set('foo', 'bar')
2565
section.remove('foo')
2566
self.assertFalse('foo' in section.options)
2567
# The option didn't exist initially so it we need to keep track of it
2568
# with a special value
2569
self.assertTrue('foo' in section.orig)
2570
self.assertEquals(config._NewlyCreatedOption, section.orig['foo'])
2573
class TestCommandLineStore(tests.TestCase):
2576
super(TestCommandLineStore, self).setUp()
2577
self.store = config.CommandLineStore()
2578
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2580
def get_section(self):
2581
"""Get the unique section for the command line overrides."""
2582
sections = list(self.store.get_sections())
2583
self.assertLength(1, sections)
2584
store, section = sections[0]
2585
self.assertEquals(self.store, store)
2588
def test_no_override(self):
2589
self.store._from_cmdline([])
2590
section = self.get_section()
2591
self.assertLength(0, list(section.iter_option_names()))
2593
def test_simple_override(self):
2594
self.store._from_cmdline(['a=b'])
2595
section = self.get_section()
2596
self.assertEqual('b', section.get('a'))
2598
def test_list_override(self):
2599
opt = config.ListOption('l')
2600
config.option_registry.register(opt)
2601
self.store._from_cmdline(['l=1,2,3'])
2602
val = self.get_section().get('l')
2603
self.assertEqual('1,2,3', val)
2604
# Reminder: lists should be registered as such explicitely, otherwise
2605
# the conversion needs to be done afterwards.
2606
self.assertEqual(['1', '2', '3'],
2607
opt.convert_from_unicode(self.store, val))
2609
def test_multiple_overrides(self):
2610
self.store._from_cmdline(['a=b', 'x=y'])
2611
section = self.get_section()
2612
self.assertEquals('b', section.get('a'))
2613
self.assertEquals('y', section.get('x'))
2615
def test_wrong_syntax(self):
2616
self.assertRaises(errors.BzrCommandError,
2617
self.store._from_cmdline, ['a=b', 'c'])
2619
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
2621
scenarios = [(key, {'get_store': builder}) for key, builder
2622
in config.test_store_builder_registry.iteritems()] + [
2623
('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
2626
store = self.get_store(self)
2627
if type(store) == config.TransportIniFileStore:
2628
raise tests.TestNotApplicable(
2629
"%s is not a concrete Store implementation"
2630
" so it doesn't need an id" % (store.__class__.__name__,))
2631
self.assertIsNot(None, store.id)
2634
class TestStore(tests.TestCaseWithTransport):
2636
def assertSectionContent(self, expected, (store, section)):
2637
"""Assert that some options have the proper values in a section."""
2638
expected_name, expected_options = expected
2639
self.assertEquals(expected_name, section.id)
2642
dict([(k, section.get(k)) for k in expected_options.keys()]))
2645
class TestReadonlyStore(TestStore):
2647
scenarios = [(key, {'get_store': builder}) for key, builder
2648
in config.test_store_builder_registry.iteritems()]
2650
def test_building_delays_load(self):
2651
store = self.get_store(self)
2652
self.assertEquals(False, store.is_loaded())
2653
store._load_from_string('')
2654
self.assertEquals(True, store.is_loaded())
2656
def test_get_no_sections_for_empty(self):
2657
store = self.get_store(self)
2658
store._load_from_string('')
2659
self.assertEquals([], list(store.get_sections()))
2661
def test_get_default_section(self):
2662
store = self.get_store(self)
2663
store._load_from_string('foo=bar')
2664
sections = list(store.get_sections())
2665
self.assertLength(1, sections)
2666
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2668
def test_get_named_section(self):
2669
store = self.get_store(self)
2670
store._load_from_string('[baz]\nfoo=bar')
2671
sections = list(store.get_sections())
2672
self.assertLength(1, sections)
2673
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2675
def test_load_from_string_fails_for_non_empty_store(self):
2676
store = self.get_store(self)
2677
store._load_from_string('foo=bar')
2678
self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
2681
class TestStoreQuoting(TestStore):
2683
scenarios = [(key, {'get_store': builder}) for key, builder
2684
in config.test_store_builder_registry.iteritems()]
2687
super(TestStoreQuoting, self).setUp()
2688
self.store = self.get_store(self)
2689
# We need a loaded store but any content will do
2690
self.store._load_from_string('')
2692
def assertIdempotent(self, s):
2693
"""Assert that quoting an unquoted string is a no-op and vice-versa.
2695
What matters here is that option values, as they appear in a store, can
2696
be safely round-tripped out of the store and back.
2698
:param s: A string, quoted if required.
2700
self.assertEquals(s, self.store.quote(self.store.unquote(s)))
2701
self.assertEquals(s, self.store.unquote(self.store.quote(s)))
2703
def test_empty_string(self):
2704
if isinstance(self.store, config.IniFileStore):
2705
# configobj._quote doesn't handle empty values
2706
self.assertRaises(AssertionError,
2707
self.assertIdempotent, '')
2709
self.assertIdempotent('')
2710
# But quoted empty strings are ok
2711
self.assertIdempotent('""')
2713
def test_embedded_spaces(self):
2714
self.assertIdempotent('" a b c "')
2716
def test_embedded_commas(self):
2717
self.assertIdempotent('" a , b c "')
2719
def test_simple_comma(self):
2720
if isinstance(self.store, config.IniFileStore):
2721
# configobj requires that lists are special-cased
2722
self.assertRaises(AssertionError,
2723
self.assertIdempotent, ',')
2725
self.assertIdempotent(',')
2726
# When a single comma is required, quoting is also required
2727
self.assertIdempotent('","')
2729
def test_list(self):
2730
if isinstance(self.store, config.IniFileStore):
2731
# configobj requires that lists are special-cased
2732
self.assertRaises(AssertionError,
2733
self.assertIdempotent, 'a,b')
2735
self.assertIdempotent('a,b')
2738
class TestDictFromStore(tests.TestCase):
2740
def test_unquote_not_string(self):
2741
conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
2742
value = conf.get('a_section')
2743
# Urgh, despite 'conf' asking for the no-name section, we get the
2744
# content of another section as a dict o_O
2745
self.assertEquals({'a': '1'}, value)
2746
unquoted = conf.store.unquote(value)
2747
# Which cannot be unquoted but shouldn't crash either (the use cases
2748
# are getting the value or displaying it. In the later case, '%s' will
2750
self.assertEquals({'a': '1'}, unquoted)
2751
self.assertEquals("{u'a': u'1'}", '%s' % (unquoted,))
2754
class TestIniFileStoreContent(tests.TestCaseWithTransport):
2755
"""Simulate loading a config store with content of various encodings.
2757
All files produced by bzr are in utf8 content.
2759
Users may modify them manually and end up with a file that can't be
2760
loaded. We need to issue proper error messages in this case.
2763
invalid_utf8_char = '\xff'
2765
def test_load_utf8(self):
2766
"""Ensure we can load an utf8-encoded file."""
2767
t = self.get_transport()
2768
# From http://pad.lv/799212
2769
unicode_user = u'b\N{Euro Sign}ar'
2770
unicode_content = u'user=%s' % (unicode_user,)
2771
utf8_content = unicode_content.encode('utf8')
2772
# Store the raw content in the config file
2773
t.put_bytes('foo.conf', utf8_content)
2774
store = config.TransportIniFileStore(t, 'foo.conf')
2776
stack = config.Stack([store.get_sections], store)
2777
self.assertEquals(unicode_user, stack.get('user'))
2779
def test_load_non_ascii(self):
2780
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2781
t = self.get_transport()
2782
t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
2783
store = config.TransportIniFileStore(t, 'foo.conf')
2784
self.assertRaises(errors.ConfigContentError, store.load)
2786
def test_load_erroneous_content(self):
2787
"""Ensure we display a proper error on content that can't be parsed."""
2788
t = self.get_transport()
2789
t.put_bytes('foo.conf', '[open_section\n')
2790
store = config.TransportIniFileStore(t, 'foo.conf')
2791
self.assertRaises(errors.ParseConfigError, store.load)
2793
def test_load_permission_denied(self):
2794
"""Ensure we get warned when trying to load an inaccessible file."""
2797
warnings.append(args[0] % args[1:])
2798
self.overrideAttr(trace, 'warning', warning)
2800
t = self.get_transport()
2802
def get_bytes(relpath):
2803
raise errors.PermissionDenied(relpath, "")
2804
t.get_bytes = get_bytes
2805
store = config.TransportIniFileStore(t, 'foo.conf')
2806
self.assertRaises(errors.PermissionDenied, store.load)
2809
[u'Permission denied while trying to load configuration store %s.'
2810
% store.external_url()])
2813
class TestIniConfigContent(tests.TestCaseWithTransport):
2814
"""Simulate loading a IniBasedConfig with content of various encodings.
2816
All files produced by bzr are in utf8 content.
2818
Users may modify them manually and end up with a file that can't be
2819
loaded. We need to issue proper error messages in this case.
2822
invalid_utf8_char = '\xff'
2824
def test_load_utf8(self):
2825
"""Ensure we can load an utf8-encoded file."""
2826
# From http://pad.lv/799212
2827
unicode_user = u'b\N{Euro Sign}ar'
2828
unicode_content = u'user=%s' % (unicode_user,)
2829
utf8_content = unicode_content.encode('utf8')
2830
# Store the raw content in the config file
2831
with open('foo.conf', 'wb') as f:
2832
f.write(utf8_content)
2833
conf = config.IniBasedConfig(file_name='foo.conf')
2834
self.assertEquals(unicode_user, conf.get_user_option('user'))
2836
def test_load_badly_encoded_content(self):
2837
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2838
with open('foo.conf', 'wb') as f:
2839
f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
2840
conf = config.IniBasedConfig(file_name='foo.conf')
2841
self.assertRaises(errors.ConfigContentError, conf._get_parser)
2843
def test_load_erroneous_content(self):
2844
"""Ensure we display a proper error on content that can't be parsed."""
2845
with open('foo.conf', 'wb') as f:
2846
f.write('[open_section\n')
2847
conf = config.IniBasedConfig(file_name='foo.conf')
2848
self.assertRaises(errors.ParseConfigError, conf._get_parser)
2851
class TestMutableStore(TestStore):
2853
scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
2854
in config.test_store_builder_registry.iteritems()]
2857
super(TestMutableStore, self).setUp()
2858
self.transport = self.get_transport()
2860
def has_store(self, store):
2861
store_basename = urlutils.relative_url(self.transport.external_url(),
2862
store.external_url())
2863
return self.transport.has(store_basename)
2865
def test_save_empty_creates_no_file(self):
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
raise tests.TestNotApplicable(
2870
'branch.conf is *always* created when a branch is initialized')
2871
store = self.get_store(self)
2873
self.assertEquals(False, self.has_store(store))
2875
def test_mutable_section_shared(self):
2876
store = self.get_store(self)
2877
store._load_from_string('foo=bar\n')
2878
# FIXME: There should be a better way than relying on the test
2879
# parametrization to identify branch.conf -- vila 2011-0526
2880
if self.store_id in ('branch', 'remote_branch'):
2881
# branch stores requires write locked branches
2882
self.addCleanup(store.branch.lock_write().unlock)
2883
section1 = store.get_mutable_section(None)
2884
section2 = store.get_mutable_section(None)
2885
# If we get different sections, different callers won't share the
2887
self.assertIs(section1, section2)
2889
def test_save_emptied_succeeds(self):
2890
store = self.get_store(self)
2891
store._load_from_string('foo=bar\n')
2892
# FIXME: There should be a better way than relying on the test
2893
# parametrization to identify branch.conf -- vila 2011-0526
2894
if self.store_id in ('branch', 'remote_branch'):
2895
# branch stores requires write locked branches
2896
self.addCleanup(store.branch.lock_write().unlock)
2897
section = store.get_mutable_section(None)
2898
section.remove('foo')
2900
self.assertEquals(True, self.has_store(store))
2901
modified_store = self.get_store(self)
2902
sections = list(modified_store.get_sections())
2903
self.assertLength(0, sections)
2905
def test_save_with_content_succeeds(self):
2906
# FIXME: There should be a better way than relying on the test
2907
# parametrization to identify branch.conf -- vila 2011-0526
2908
if self.store_id in ('branch', 'remote_branch'):
2909
raise tests.TestNotApplicable(
2910
'branch.conf is *always* created when a branch is initialized')
2911
store = self.get_store(self)
2912
store._load_from_string('foo=bar\n')
2913
self.assertEquals(False, self.has_store(store))
2915
self.assertEquals(True, self.has_store(store))
2916
modified_store = self.get_store(self)
2917
sections = list(modified_store.get_sections())
2918
self.assertLength(1, sections)
2919
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2921
def test_set_option_in_empty_store(self):
2922
store = self.get_store(self)
2923
# FIXME: There should be a better way than relying on the test
2924
# parametrization to identify branch.conf -- vila 2011-0526
2925
if self.store_id in ('branch', 'remote_branch'):
2926
# branch stores requires write locked branches
2927
self.addCleanup(store.branch.lock_write().unlock)
2928
section = store.get_mutable_section(None)
2929
section.set('foo', 'bar')
2931
modified_store = self.get_store(self)
2932
sections = list(modified_store.get_sections())
2933
self.assertLength(1, sections)
2934
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2936
def test_set_option_in_default_section(self):
2937
store = self.get_store(self)
2938
store._load_from_string('')
2939
# FIXME: There should be a better way than relying on the test
2940
# parametrization to identify branch.conf -- vila 2011-0526
2941
if self.store_id in ('branch', 'remote_branch'):
2942
# branch stores requires write locked branches
2943
self.addCleanup(store.branch.lock_write().unlock)
2944
section = store.get_mutable_section(None)
2945
section.set('foo', 'bar')
2947
modified_store = self.get_store(self)
2948
sections = list(modified_store.get_sections())
2949
self.assertLength(1, sections)
2950
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2952
def test_set_option_in_named_section(self):
2953
store = self.get_store(self)
2954
store._load_from_string('')
2955
# FIXME: There should be a better way than relying on the test
2956
# parametrization to identify branch.conf -- vila 2011-0526
2957
if self.store_id in ('branch', 'remote_branch'):
2958
# branch stores requires write locked branches
2959
self.addCleanup(store.branch.lock_write().unlock)
2960
section = store.get_mutable_section('baz')
2961
section.set('foo', 'bar')
2963
modified_store = self.get_store(self)
2964
sections = list(modified_store.get_sections())
2965
self.assertLength(1, sections)
2966
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2968
def test_load_hook(self):
2969
# First, we need to ensure that the store exists
2970
store = self.get_store(self)
2971
# FIXME: There should be a better way than relying on the test
2972
# parametrization to identify branch.conf -- vila 2011-0526
2973
if self.store_id in ('branch', 'remote_branch'):
2974
# branch stores requires write locked branches
2975
self.addCleanup(store.branch.lock_write().unlock)
2976
section = store.get_mutable_section('baz')
2977
section.set('foo', 'bar')
2979
# Now we can try to load it
2980
store = self.get_store(self)
2984
config.ConfigHooks.install_named_hook('load', hook, None)
2985
self.assertLength(0, calls)
2987
self.assertLength(1, calls)
2988
self.assertEquals((store,), calls[0])
2990
def test_save_hook(self):
2994
config.ConfigHooks.install_named_hook('save', hook, None)
2995
self.assertLength(0, calls)
2996
store = self.get_store(self)
2997
# FIXME: There should be a better way than relying on the test
2998
# parametrization to identify branch.conf -- vila 2011-0526
2999
if self.store_id in ('branch', 'remote_branch'):
3000
# branch stores requires write locked branches
3001
self.addCleanup(store.branch.lock_write().unlock)
3002
section = store.get_mutable_section('baz')
3003
section.set('foo', 'bar')
3005
self.assertLength(1, calls)
3006
self.assertEquals((store,), calls[0])
3008
def test_set_mark_dirty(self):
3009
stack = config.MemoryStack('')
3010
self.assertLength(0, stack.store.dirty_sections)
3011
stack.set('foo', 'baz')
3012
self.assertLength(1, stack.store.dirty_sections)
3013
self.assertTrue(stack.store._need_saving())
3015
def test_remove_mark_dirty(self):
3016
stack = config.MemoryStack('foo=bar')
3017
self.assertLength(0, stack.store.dirty_sections)
3019
self.assertLength(1, stack.store.dirty_sections)
3020
self.assertTrue(stack.store._need_saving())
3023
class TestStoreSaveChanges(tests.TestCaseWithTransport):
3024
"""Tests that config changes are kept in memory and saved on-demand."""
3027
super(TestStoreSaveChanges, self).setUp()
3028
self.transport = self.get_transport()
3029
# Most of the tests involve two stores pointing to the same persistent
3030
# storage to observe the effects of concurrent changes
3031
self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
3032
self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
3035
self.warnings.append(args[0] % args[1:])
3036
self.overrideAttr(trace, 'warning', warning)
3038
def has_store(self, store):
3039
store_basename = urlutils.relative_url(self.transport.external_url(),
3040
store.external_url())
3041
return self.transport.has(store_basename)
3043
def get_stack(self, store):
3044
# Any stack will do as long as it uses the right store, just a single
3045
# no-name section is enough
3046
return config.Stack([store.get_sections], store)
3048
def test_no_changes_no_save(self):
3049
s = self.get_stack(self.st1)
3050
s.store.save_changes()
3051
self.assertEquals(False, self.has_store(self.st1))
3053
def test_unrelated_concurrent_update(self):
3054
s1 = self.get_stack(self.st1)
3055
s2 = self.get_stack(self.st2)
3056
s1.set('foo', 'bar')
3057
s2.set('baz', 'quux')
3059
# Changes don't propagate magically
3060
self.assertEquals(None, s1.get('baz'))
3061
s2.store.save_changes()
3062
self.assertEquals('quux', s2.get('baz'))
3063
# Changes are acquired when saving
3064
self.assertEquals('bar', s2.get('foo'))
3065
# Since there is no overlap, no warnings are emitted
3066
self.assertLength(0, self.warnings)
3068
def test_concurrent_update_modified(self):
3069
s1 = self.get_stack(self.st1)
3070
s2 = self.get_stack(self.st2)
3071
s1.set('foo', 'bar')
3072
s2.set('foo', 'baz')
3075
s2.store.save_changes()
3076
self.assertEquals('baz', s2.get('foo'))
3077
# But the user get a warning
3078
self.assertLength(1, self.warnings)
3079
warning = self.warnings[0]
3080
self.assertStartsWith(warning, 'Option foo in section None')
3081
self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
3082
' The baz value will be saved.')
3084
def test_concurrent_deletion(self):
3085
self.st1._load_from_string('foo=bar')
3087
s1 = self.get_stack(self.st1)
3088
s2 = self.get_stack(self.st2)
3091
s1.store.save_changes()
3093
self.assertLength(0, self.warnings)
3094
s2.store.save_changes()
3096
self.assertLength(1, self.warnings)
3097
warning = self.warnings[0]
3098
self.assertStartsWith(warning, 'Option foo in section None')
3099
self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
3100
' The <DELETED> value will be saved.')
3103
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
3105
def get_store(self):
3106
return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3108
def test_get_quoted_string(self):
3109
store = self.get_store()
3110
store._load_from_string('foo= " abc "')
3111
stack = config.Stack([store.get_sections])
3112
self.assertEquals(' abc ', stack.get('foo'))
3114
def test_set_quoted_string(self):
3115
store = self.get_store()
3116
stack = config.Stack([store.get_sections], store)
3117
stack.set('foo', ' a b c ')
3119
self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
3122
class TestTransportIniFileStore(TestStore):
3124
def test_loading_unknown_file_fails(self):
3125
store = config.TransportIniFileStore(self.get_transport(),
3127
self.assertRaises(errors.NoSuchFile, store.load)
3129
def test_invalid_content(self):
3130
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3131
self.assertEquals(False, store.is_loaded())
3132
exc = self.assertRaises(
3133
errors.ParseConfigError, store._load_from_string,
3134
'this is invalid !')
3135
self.assertEndsWith(exc.filename, 'foo.conf')
3136
# And the load failed
3137
self.assertEquals(False, store.is_loaded())
3139
def test_get_embedded_sections(self):
3140
# A more complicated example (which also shows that section names and
3141
# option names share the same name space...)
3142
# FIXME: This should be fixed by forbidding dicts as values ?
3143
# -- vila 2011-04-05
3144
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3145
store._load_from_string('''
3149
foo_in_DEFAULT=foo_DEFAULT
3157
sections = list(store.get_sections())
3158
self.assertLength(4, sections)
3159
# The default section has no name.
3160
# List values are provided as strings and need to be explicitly
3161
# converted by specifying from_unicode=list_from_store at option
3163
self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
3165
self.assertSectionContent(
3166
('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
3167
self.assertSectionContent(
3168
('bar', {'foo_in_bar': 'barbar'}), sections[2])
3169
# sub sections are provided as embedded dicts.
3170
self.assertSectionContent(
3171
('baz', {'foo_in_baz': 'barbaz', 'qux': {'foo_in_qux': 'quux'}}),
3175
class TestLockableIniFileStore(TestStore):
3177
def test_create_store_in_created_dir(self):
3178
self.assertPathDoesNotExist('dir')
3179
t = self.get_transport('dir/subdir')
3180
store = config.LockableIniFileStore(t, 'foo.conf')
3181
store.get_mutable_section(None).set('foo', 'bar')
3183
self.assertPathExists('dir/subdir')
3186
class TestConcurrentStoreUpdates(TestStore):
3187
"""Test that Stores properly handle conccurent updates.
3189
New Store implementation may fail some of these tests but until such
3190
implementations exist it's hard to properly filter them from the scenarios
3191
applied here. If you encounter such a case, contact the bzr devs.
3194
scenarios = [(key, {'get_stack': builder}) for key, builder
3195
in config.test_stack_builder_registry.iteritems()]
3198
super(TestConcurrentStoreUpdates, self).setUp()
3199
self.stack = self.get_stack(self)
3200
if not isinstance(self.stack, config._CompatibleStack):
3201
raise tests.TestNotApplicable(
3202
'%s is not meant to be compatible with the old config design'
3204
self.stack.set('one', '1')
3205
self.stack.set('two', '2')
3207
self.stack.store.save()
3209
def test_simple_read_access(self):
3210
self.assertEquals('1', self.stack.get('one'))
3212
def test_simple_write_access(self):
3213
self.stack.set('one', 'one')
3214
self.assertEquals('one', self.stack.get('one'))
3216
def test_listen_to_the_last_speaker(self):
3218
c2 = self.get_stack(self)
3219
c1.set('one', 'ONE')
3220
c2.set('two', 'TWO')
3221
self.assertEquals('ONE', c1.get('one'))
3222
self.assertEquals('TWO', c2.get('two'))
3223
# The second update respect the first one
3224
self.assertEquals('ONE', c2.get('one'))
3226
def test_last_speaker_wins(self):
3227
# If the same config is not shared, the same variable modified twice
3228
# can only see a single result.
3230
c2 = self.get_stack(self)
3233
self.assertEquals('c2', c2.get('one'))
3234
# The first modification is still available until another refresh
3236
self.assertEquals('c1', c1.get('one'))
3237
c1.set('two', 'done')
3238
self.assertEquals('c2', c1.get('one'))
3240
def test_writes_are_serialized(self):
3242
c2 = self.get_stack(self)
3244
# We spawn a thread that will pause *during* the config saving.
3245
before_writing = threading.Event()
3246
after_writing = threading.Event()
3247
writing_done = threading.Event()
3248
c1_save_without_locking_orig = c1.store.save_without_locking
3249
def c1_save_without_locking():
3250
before_writing.set()
3251
c1_save_without_locking_orig()
3252
# The lock is held. We wait for the main thread to decide when to
3254
after_writing.wait()
3255
c1.store.save_without_locking = c1_save_without_locking
3259
t1 = threading.Thread(target=c1_set)
3260
# Collect the thread after the test
3261
self.addCleanup(t1.join)
3262
# Be ready to unblock the thread if the test goes wrong
3263
self.addCleanup(after_writing.set)
3265
before_writing.wait()
3266
self.assertRaises(errors.LockContention,
3267
c2.set, 'one', 'c2')
3268
self.assertEquals('c1', c1.get('one'))
3269
# Let the lock be released
3273
self.assertEquals('c2', c2.get('one'))
3275
def test_read_while_writing(self):
3277
# We spawn a thread that will pause *during* the write
3278
ready_to_write = threading.Event()
3279
do_writing = threading.Event()
3280
writing_done = threading.Event()
3281
# We override the _save implementation so we know the store is locked
3282
c1_save_without_locking_orig = c1.store.save_without_locking
3283
def c1_save_without_locking():
3284
ready_to_write.set()
3285
# The lock is held. We wait for the main thread to decide when to
3288
c1_save_without_locking_orig()
3290
c1.store.save_without_locking = c1_save_without_locking
3293
t1 = threading.Thread(target=c1_set)
3294
# Collect the thread after the test
3295
self.addCleanup(t1.join)
3296
# Be ready to unblock the thread if the test goes wrong
3297
self.addCleanup(do_writing.set)
3299
# Ensure the thread is ready to write
3300
ready_to_write.wait()
3301
self.assertEquals('c1', c1.get('one'))
3302
# If we read during the write, we get the old value
3303
c2 = self.get_stack(self)
3304
self.assertEquals('1', c2.get('one'))
3305
# Let the writing occur and ensure it occurred
3308
# Now we get the updated value
3309
c3 = self.get_stack(self)
3310
self.assertEquals('c1', c3.get('one'))
3312
# FIXME: It may be worth looking into removing the lock dir when it's not
3313
# needed anymore and look at possible fallouts for concurrent lockers. This
3314
# will matter if/when we use config files outside of bazaar directories
3315
# (.bazaar or .bzr) -- vila 20110-04-111
3318
class TestSectionMatcher(TestStore):
3320
scenarios = [('location', {'matcher': config.LocationMatcher}),
3321
('id', {'matcher': config.NameMatcher}),]
3324
super(TestSectionMatcher, self).setUp()
3325
# Any simple store is good enough
3326
self.get_store = config.test_store_builder_registry.get('configobj')
3328
def test_no_matches_for_empty_stores(self):
3329
store = self.get_store(self)
3330
store._load_from_string('')
3331
matcher = self.matcher(store, '/bar')
3332
self.assertEquals([], list(matcher.get_sections()))
3334
def test_build_doesnt_load_store(self):
3335
store = self.get_store(self)
3336
matcher = self.matcher(store, '/bar')
3337
self.assertFalse(store.is_loaded())
3340
class TestLocationSection(tests.TestCase):
3342
def get_section(self, options, extra_path):
3343
section = config.Section('foo', options)
3344
return config.LocationSection(section, extra_path)
3346
def test_simple_option(self):
3347
section = self.get_section({'foo': 'bar'}, '')
3348
self.assertEquals('bar', section.get('foo'))
3350
def test_option_with_extra_path(self):
3351
section = self.get_section({'foo': 'bar', 'foo:policy': 'appendpath'},
3353
self.assertEquals('bar/baz', section.get('foo'))
3355
def test_invalid_policy(self):
3356
section = self.get_section({'foo': 'bar', 'foo:policy': 'die'},
3358
# invalid policies are ignored
3359
self.assertEquals('bar', section.get('foo'))
3362
class TestLocationMatcher(TestStore):
3365
super(TestLocationMatcher, self).setUp()
3366
# Any simple store is good enough
3367
self.get_store = config.test_store_builder_registry.get('configobj')
3369
def test_unrelated_section_excluded(self):
3370
store = self.get_store(self)
3371
store._load_from_string('''
3379
section=/foo/bar/baz
3383
self.assertEquals(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
3385
[section.id for _, section in store.get_sections()])
3386
matcher = config.LocationMatcher(store, '/foo/bar/quux')
3387
sections = [section for _, section in matcher.get_sections()]
3388
self.assertEquals(['/foo/bar', '/foo'],
3389
[section.id for section in sections])
3390
self.assertEquals(['quux', 'bar/quux'],
3391
[section.extra_path for section in sections])
3393
def test_more_specific_sections_first(self):
3394
store = self.get_store(self)
3395
store._load_from_string('''
3401
self.assertEquals(['/foo', '/foo/bar'],
3402
[section.id for _, section in store.get_sections()])
3403
matcher = config.LocationMatcher(store, '/foo/bar/baz')
3404
sections = [section for _, section in matcher.get_sections()]
3405
self.assertEquals(['/foo/bar', '/foo'],
3406
[section.id for section in sections])
3407
self.assertEquals(['baz', 'bar/baz'],
3408
[section.extra_path for section in sections])
3410
def test_appendpath_in_no_name_section(self):
3411
# It's a bit weird to allow appendpath in a no-name section, but
3412
# someone may found a use for it
3413
store = self.get_store(self)
3414
store._load_from_string('''
3416
foo:policy = appendpath
3418
matcher = config.LocationMatcher(store, 'dir/subdir')
3419
sections = list(matcher.get_sections())
3420
self.assertLength(1, sections)
3421
self.assertEquals('bar/dir/subdir', sections[0][1].get('foo'))
3423
def test_file_urls_are_normalized(self):
3424
store = self.get_store(self)
3425
if sys.platform == 'win32':
3426
expected_url = 'file:///C:/dir/subdir'
3427
expected_location = 'C:/dir/subdir'
3429
expected_url = 'file:///dir/subdir'
3430
expected_location = '/dir/subdir'
3431
matcher = config.LocationMatcher(store, expected_url)
3432
self.assertEquals(expected_location, matcher.location)
3435
class TestStartingPathMatcher(TestStore):
3438
super(TestStartingPathMatcher, self).setUp()
3439
# Any simple store is good enough
3440
self.store = config.IniFileStore()
3442
def assertSectionIDs(self, expected, location, content):
3443
self.store._load_from_string(content)
3444
matcher = config.StartingPathMatcher(self.store, location)
3445
sections = list(matcher.get_sections())
3446
self.assertLength(len(expected), sections)
3447
self.assertEqual(expected, [section.id for _, section in sections])
3450
def test_empty(self):
3451
self.assertSectionIDs([], self.get_url(), '')
3453
def test_url_vs_local_paths(self):
3454
# The matcher location is an url and the section names are local paths
3455
sections = self.assertSectionIDs(['/foo/bar', '/foo'],
3456
'file:///foo/bar/baz', '''\
3461
def test_local_path_vs_url(self):
3462
# The matcher location is a local path and the section names are urls
3463
sections = self.assertSectionIDs(['file:///foo/bar', 'file:///foo'],
3464
'/foo/bar/baz', '''\
3470
def test_no_name_section_included_when_present(self):
3471
# Note that other tests will cover the case where the no-name section
3472
# is empty and as such, not included.
3473
sections = self.assertSectionIDs(['/foo/bar', '/foo', None],
3474
'/foo/bar/baz', '''\
3475
option = defined so the no-name section exists
3479
self.assertEquals(['baz', 'bar/baz', '/foo/bar/baz'],
3480
[s.locals['relpath'] for _, s in sections])
3482
def test_order_reversed(self):
3483
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3488
def test_unrelated_section_excluded(self):
3489
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3495
def test_glob_included(self):
3496
sections = self.assertSectionIDs(['/foo/*/baz', '/foo/b*', '/foo'],
3497
'/foo/bar/baz', '''\
3503
# Note that 'baz' as a relpath for /foo/b* is not fully correct, but
3504
# nothing really is... as far using {relpath} to append it to something
3505
# else, this seems good enough though.
3506
self.assertEquals(['', 'baz', 'bar/baz'],
3507
[s.locals['relpath'] for _, s in sections])
3509
def test_respect_order(self):
3510
self.assertSectionIDs(['/foo', '/foo/b*', '/foo/*/baz'],
3511
'/foo/bar/baz', '''\
3519
class TestNameMatcher(TestStore):
3522
super(TestNameMatcher, self).setUp()
3523
self.matcher = config.NameMatcher
3524
# Any simple store is good enough
3525
self.get_store = config.test_store_builder_registry.get('configobj')
3527
def get_matching_sections(self, name):
3528
store = self.get_store(self)
3529
store._load_from_string('''
3537
matcher = self.matcher(store, name)
3538
return list(matcher.get_sections())
3540
def test_matching(self):
3541
sections = self.get_matching_sections('foo')
3542
self.assertLength(1, sections)
3543
self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
3545
def test_not_matching(self):
3546
sections = self.get_matching_sections('baz')
3547
self.assertLength(0, sections)
3550
class TestBaseStackGet(tests.TestCase):
3553
super(TestBaseStackGet, self).setUp()
3554
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3556
def test_get_first_definition(self):
3557
store1 = config.IniFileStore()
3558
store1._load_from_string('foo=bar')
3559
store2 = config.IniFileStore()
3560
store2._load_from_string('foo=baz')
3561
conf = config.Stack([store1.get_sections, store2.get_sections])
3562
self.assertEquals('bar', conf.get('foo'))
3564
def test_get_with_registered_default_value(self):
3565
config.option_registry.register(config.Option('foo', default='bar'))
3566
conf_stack = config.Stack([])
3567
self.assertEquals('bar', conf_stack.get('foo'))
3569
def test_get_without_registered_default_value(self):
3570
config.option_registry.register(config.Option('foo'))
3571
conf_stack = config.Stack([])
3572
self.assertEquals(None, conf_stack.get('foo'))
3574
def test_get_without_default_value_for_not_registered(self):
3575
conf_stack = config.Stack([])
3576
self.assertEquals(None, conf_stack.get('foo'))
3578
def test_get_for_empty_section_callable(self):
3579
conf_stack = config.Stack([lambda : []])
3580
self.assertEquals(None, conf_stack.get('foo'))
3582
def test_get_for_broken_callable(self):
3583
# Trying to use and invalid callable raises an exception on first use
3584
conf_stack = config.Stack([object])
3585
self.assertRaises(TypeError, conf_stack.get, 'foo')
3588
class TestStackWithSimpleStore(tests.TestCase):
3591
super(TestStackWithSimpleStore, self).setUp()
3592
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3593
self.registry = config.option_registry
3595
def get_conf(self, content=None):
3596
return config.MemoryStack(content)
3598
def test_override_value_from_env(self):
3599
self.registry.register(
3600
config.Option('foo', default='bar', override_from_env=['FOO']))
3601
self.overrideEnv('FOO', 'quux')
3602
# Env variable provides a default taking over the option one
3603
conf = self.get_conf('foo=store')
3604
self.assertEquals('quux', conf.get('foo'))
3606
def test_first_override_value_from_env_wins(self):
3607
self.registry.register(
3608
config.Option('foo', default='bar',
3609
override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
3610
self.overrideEnv('FOO', 'foo')
3611
self.overrideEnv('BAZ', 'baz')
3612
# The first env var set wins
3613
conf = self.get_conf('foo=store')
3614
self.assertEquals('foo', conf.get('foo'))
3617
class TestMemoryStack(tests.TestCase):
3620
conf = config.MemoryStack('foo=bar')
3621
self.assertEquals('bar', conf.get('foo'))
3624
conf = config.MemoryStack('foo=bar')
3625
conf.set('foo', 'baz')
3626
self.assertEquals('baz', conf.get('foo'))
3628
def test_no_content(self):
3629
conf = config.MemoryStack()
3630
# No content means no loading
3631
self.assertFalse(conf.store.is_loaded())
3632
self.assertRaises(NotImplementedError, conf.get, 'foo')
3633
# But a content can still be provided
3634
conf.store._load_from_string('foo=bar')
3635
self.assertEquals('bar', conf.get('foo'))
3638
class TestStackIterSections(tests.TestCase):
3640
def test_empty_stack(self):
3641
conf = config.Stack([])
3642
sections = list(conf.iter_sections())
3643
self.assertLength(0, sections)
3645
def test_empty_store(self):
3646
store = config.IniFileStore()
3647
store._load_from_string('')
3648
conf = config.Stack([store.get_sections])
3649
sections = list(conf.iter_sections())
3650
self.assertLength(0, sections)
3652
def test_simple_store(self):
3653
store = config.IniFileStore()
3654
store._load_from_string('foo=bar')
3655
conf = config.Stack([store.get_sections])
3656
tuples = list(conf.iter_sections())
3657
self.assertLength(1, tuples)
3658
(found_store, found_section) = tuples[0]
3659
self.assertIs(store, found_store)
3661
def test_two_stores(self):
3662
store1 = config.IniFileStore()
3663
store1._load_from_string('foo=bar')
3664
store2 = config.IniFileStore()
3665
store2._load_from_string('bar=qux')
3666
conf = config.Stack([store1.get_sections, store2.get_sections])
3667
tuples = list(conf.iter_sections())
3668
self.assertLength(2, tuples)
3669
self.assertIs(store1, tuples[0][0])
3670
self.assertIs(store2, tuples[1][0])
3673
class TestStackWithTransport(tests.TestCaseWithTransport):
3675
scenarios = [(key, {'get_stack': builder}) for key, builder
3676
in config.test_stack_builder_registry.iteritems()]
3679
class TestConcreteStacks(TestStackWithTransport):
3681
def test_build_stack(self):
3682
# Just a smoke test to help debug builders
3683
stack = self.get_stack(self)
3686
class TestStackGet(TestStackWithTransport):
3689
super(TestStackGet, self).setUp()
3690
self.conf = self.get_stack(self)
3692
def test_get_for_empty_stack(self):
3693
self.assertEquals(None, self.conf.get('foo'))
3695
def test_get_hook(self):
3696
self.conf.set('foo', 'bar')
3700
config.ConfigHooks.install_named_hook('get', hook, None)
3701
self.assertLength(0, calls)
3702
value = self.conf.get('foo')
3703
self.assertEquals('bar', value)
3704
self.assertLength(1, calls)
3705
self.assertEquals((self.conf, 'foo', 'bar'), calls[0])
3708
class TestStackGetWithConverter(tests.TestCase):
3711
super(TestStackGetWithConverter, self).setUp()
3712
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3713
self.registry = config.option_registry
3715
def get_conf(self, content=None):
3716
return config.MemoryStack(content)
3718
def register_bool_option(self, name, default=None, default_from_env=None):
3719
b = config.Option(name, help='A boolean.',
3720
default=default, default_from_env=default_from_env,
3721
from_unicode=config.bool_from_store)
3722
self.registry.register(b)
3724
def test_get_default_bool_None(self):
3725
self.register_bool_option('foo')
3726
conf = self.get_conf('')
3727
self.assertEquals(None, conf.get('foo'))
3729
def test_get_default_bool_True(self):
3730
self.register_bool_option('foo', u'True')
3731
conf = self.get_conf('')
3732
self.assertEquals(True, conf.get('foo'))
3734
def test_get_default_bool_False(self):
3735
self.register_bool_option('foo', False)
3736
conf = self.get_conf('')
3737
self.assertEquals(False, conf.get('foo'))
3739
def test_get_default_bool_False_as_string(self):
3740
self.register_bool_option('foo', u'False')
3741
conf = self.get_conf('')
3742
self.assertEquals(False, conf.get('foo'))
3744
def test_get_default_bool_from_env_converted(self):
3745
self.register_bool_option('foo', u'True', default_from_env=['FOO'])
3746
self.overrideEnv('FOO', 'False')
3747
conf = self.get_conf('')
3748
self.assertEquals(False, conf.get('foo'))
3750
def test_get_default_bool_when_conversion_fails(self):
3751
self.register_bool_option('foo', default='True')
3752
conf = self.get_conf('foo=invalid boolean')
3753
self.assertEquals(True, conf.get('foo'))
3755
def register_integer_option(self, name,
3756
default=None, default_from_env=None):
3757
i = config.Option(name, help='An integer.',
3758
default=default, default_from_env=default_from_env,
3759
from_unicode=config.int_from_store)
3760
self.registry.register(i)
3762
def test_get_default_integer_None(self):
3763
self.register_integer_option('foo')
3764
conf = self.get_conf('')
3765
self.assertEquals(None, conf.get('foo'))
3767
def test_get_default_integer(self):
3768
self.register_integer_option('foo', 42)
3769
conf = self.get_conf('')
3770
self.assertEquals(42, conf.get('foo'))
3772
def test_get_default_integer_as_string(self):
3773
self.register_integer_option('foo', u'42')
3774
conf = self.get_conf('')
3775
self.assertEquals(42, conf.get('foo'))
3777
def test_get_default_integer_from_env(self):
3778
self.register_integer_option('foo', default_from_env=['FOO'])
3779
self.overrideEnv('FOO', '18')
3780
conf = self.get_conf('')
3781
self.assertEquals(18, conf.get('foo'))
3783
def test_get_default_integer_when_conversion_fails(self):
3784
self.register_integer_option('foo', default='12')
3785
conf = self.get_conf('foo=invalid integer')
3786
self.assertEquals(12, conf.get('foo'))
3788
def register_list_option(self, name, default=None, default_from_env=None):
3789
l = config.ListOption(name, help='A list.', default=default,
3790
default_from_env=default_from_env)
3791
self.registry.register(l)
3793
def test_get_default_list_None(self):
3794
self.register_list_option('foo')
3795
conf = self.get_conf('')
3796
self.assertEquals(None, conf.get('foo'))
3798
def test_get_default_list_empty(self):
3799
self.register_list_option('foo', '')
3800
conf = self.get_conf('')
3801
self.assertEquals([], conf.get('foo'))
3803
def test_get_default_list_from_env(self):
3804
self.register_list_option('foo', default_from_env=['FOO'])
3805
self.overrideEnv('FOO', '')
3806
conf = self.get_conf('')
3807
self.assertEquals([], conf.get('foo'))
3809
def test_get_with_list_converter_no_item(self):
3810
self.register_list_option('foo', None)
3811
conf = self.get_conf('foo=,')
3812
self.assertEquals([], conf.get('foo'))
3814
def test_get_with_list_converter_many_items(self):
3815
self.register_list_option('foo', None)
3816
conf = self.get_conf('foo=m,o,r,e')
3817
self.assertEquals(['m', 'o', 'r', 'e'], conf.get('foo'))
3819
def test_get_with_list_converter_embedded_spaces_many_items(self):
3820
self.register_list_option('foo', None)
3821
conf = self.get_conf('foo=" bar", "baz "')
3822
self.assertEquals([' bar', 'baz '], conf.get('foo'))
3824
def test_get_with_list_converter_stripped_spaces_many_items(self):
3825
self.register_list_option('foo', None)
3826
conf = self.get_conf('foo= bar , baz ')
3827
self.assertEquals(['bar', 'baz'], conf.get('foo'))
3830
class TestIterOptionRefs(tests.TestCase):
3831
"""iter_option_refs is a bit unusual, document some cases."""
3833
def assertRefs(self, expected, string):
3834
self.assertEquals(expected, list(config.iter_option_refs(string)))
3836
def test_empty(self):
3837
self.assertRefs([(False, '')], '')
3839
def test_no_refs(self):
3840
self.assertRefs([(False, 'foo bar')], 'foo bar')
3842
def test_single_ref(self):
3843
self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
3845
def test_broken_ref(self):
3846
self.assertRefs([(False, '{foo')], '{foo')
3848
def test_embedded_ref(self):
3849
self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
3852
def test_two_refs(self):
3853
self.assertRefs([(False, ''), (True, '{foo}'),
3854
(False, ''), (True, '{bar}'),
3858
def test_newline_in_refs_are_not_matched(self):
3859
self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
3862
class TestStackExpandOptions(tests.TestCaseWithTransport):
3865
super(TestStackExpandOptions, self).setUp()
3866
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3867
self.registry = config.option_registry
3868
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3869
self.conf = config.Stack([store.get_sections], store)
3871
def assertExpansion(self, expected, string, env=None):
3872
self.assertEquals(expected, self.conf.expand_options(string, env))
3874
def test_no_expansion(self):
3875
self.assertExpansion('foo', 'foo')
3877
def test_expand_default_value(self):
3878
self.conf.store._load_from_string('bar=baz')
3879
self.registry.register(config.Option('foo', default=u'{bar}'))
3880
self.assertEquals('baz', self.conf.get('foo', expand=True))
3882
def test_expand_default_from_env(self):
3883
self.conf.store._load_from_string('bar=baz')
3884
self.registry.register(config.Option('foo', default_from_env=['FOO']))
3885
self.overrideEnv('FOO', '{bar}')
3886
self.assertEquals('baz', self.conf.get('foo', expand=True))
3888
def test_expand_default_on_failed_conversion(self):
3889
self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
3890
self.registry.register(
3891
config.Option('foo', default=u'{bar}',
3892
from_unicode=config.int_from_store))
3893
self.assertEquals(42, self.conf.get('foo', expand=True))
3895
def test_env_adding_options(self):
3896
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3898
def test_env_overriding_options(self):
3899
self.conf.store._load_from_string('foo=baz')
3900
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3902
def test_simple_ref(self):
3903
self.conf.store._load_from_string('foo=xxx')
3904
self.assertExpansion('xxx', '{foo}')
3906
def test_unknown_ref(self):
3907
self.assertRaises(errors.ExpandingUnknownOption,
3908
self.conf.expand_options, '{foo}')
3910
def test_indirect_ref(self):
3911
self.conf.store._load_from_string('''
3915
self.assertExpansion('xxx', '{bar}')
3917
def test_embedded_ref(self):
3918
self.conf.store._load_from_string('''
3922
self.assertExpansion('xxx', '{{bar}}')
3924
def test_simple_loop(self):
3925
self.conf.store._load_from_string('foo={foo}')
3926
self.assertRaises(errors.OptionExpansionLoop,
3927
self.conf.expand_options, '{foo}')
3929
def test_indirect_loop(self):
3930
self.conf.store._load_from_string('''
3934
e = self.assertRaises(errors.OptionExpansionLoop,
3935
self.conf.expand_options, '{foo}')
3936
self.assertEquals('foo->bar->baz', e.refs)
3937
self.assertEquals('{foo}', e.string)
3939
def test_list(self):
3940
self.conf.store._load_from_string('''
3944
list={foo},{bar},{baz}
3946
self.registry.register(
3947
config.ListOption('list'))
3948
self.assertEquals(['start', 'middle', 'end'],
3949
self.conf.get('list', expand=True))
3951
def test_cascading_list(self):
3952
self.conf.store._load_from_string('''
3958
self.registry.register(config.ListOption('list'))
3959
# Register an intermediate option as a list to ensure no conversion
3960
# happen while expanding. Conversion should only occur for the original
3961
# option ('list' here).
3962
self.registry.register(config.ListOption('baz'))
3963
self.assertEquals(['start', 'middle', 'end'],
3964
self.conf.get('list', expand=True))
3966
def test_pathologically_hidden_list(self):
3967
self.conf.store._load_from_string('''
3973
hidden={start}{middle}{end}
3975
# What matters is what the registration says, the conversion happens
3976
# only after all expansions have been performed
3977
self.registry.register(config.ListOption('hidden'))
3978
self.assertEquals(['bin', 'go'],
3979
self.conf.get('hidden', expand=True))
3982
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
3985
super(TestStackCrossSectionsExpand, self).setUp()
3987
def get_config(self, location, string):
3990
# Since we don't save the config we won't strictly require to inherit
3991
# from TestCaseInTempDir, but an error occurs so quickly...
3992
c = config.LocationStack(location)
3993
c.store._load_from_string(string)
3996
def test_dont_cross_unrelated_section(self):
3997
c = self.get_config('/another/branch/path','''
4002
[/another/branch/path]
4005
self.assertRaises(errors.ExpandingUnknownOption,
4006
c.get, 'bar', expand=True)
4008
def test_cross_related_sections(self):
4009
c = self.get_config('/project/branch/path','''
4013
[/project/branch/path]
4016
self.assertEquals('quux', c.get('bar', expand=True))
4019
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
4021
def test_cross_global_locations(self):
4022
l_store = config.LocationStore()
4023
l_store._load_from_string('''
4029
g_store = config.GlobalStore()
4030
g_store._load_from_string('''
4036
stack = config.LocationStack('/branch')
4037
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4038
self.assertEquals('loc-foo', stack.get('gfoo', expand=True))
4041
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
4043
def test_expand_locals_empty(self):
4044
l_store = config.LocationStore()
4045
l_store._load_from_string('''
4046
[/home/user/project]
4051
stack = config.LocationStack('/home/user/project/')
4052
self.assertEquals('', stack.get('base', expand=True))
4053
self.assertEquals('', stack.get('rel', expand=True))
4055
def test_expand_basename_locally(self):
4056
l_store = config.LocationStore()
4057
l_store._load_from_string('''
4058
[/home/user/project]
4062
stack = config.LocationStack('/home/user/project/branch')
4063
self.assertEquals('branch', stack.get('bfoo', expand=True))
4065
def test_expand_basename_locally_longer_path(self):
4066
l_store = config.LocationStore()
4067
l_store._load_from_string('''
4072
stack = config.LocationStack('/home/user/project/dir/branch')
4073
self.assertEquals('branch', stack.get('bfoo', expand=True))
4075
def test_expand_relpath_locally(self):
4076
l_store = config.LocationStore()
4077
l_store._load_from_string('''
4078
[/home/user/project]
4079
lfoo = loc-foo/{relpath}
4082
stack = config.LocationStack('/home/user/project/branch')
4083
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4085
def test_expand_relpath_unknonw_in_global(self):
4086
g_store = config.GlobalStore()
4087
g_store._load_from_string('''
4092
stack = config.LocationStack('/home/user/project/branch')
4093
self.assertRaises(errors.ExpandingUnknownOption,
4094
stack.get, 'gfoo', expand=True)
4096
def test_expand_local_option_locally(self):
4097
l_store = config.LocationStore()
4098
l_store._load_from_string('''
4099
[/home/user/project]
4100
lfoo = loc-foo/{relpath}
4104
g_store = config.GlobalStore()
4105
g_store._load_from_string('''
4111
stack = config.LocationStack('/home/user/project/branch')
4112
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4113
self.assertEquals('loc-foo/branch', stack.get('gfoo', expand=True))
4115
def test_locals_dont_leak(self):
4116
"""Make sure we chose the right local in presence of several sections.
4118
l_store = config.LocationStore()
4119
l_store._load_from_string('''
4121
lfoo = loc-foo/{relpath}
4122
[/home/user/project]
4123
lfoo = loc-foo/{relpath}
4126
stack = config.LocationStack('/home/user/project/branch')
4127
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4128
stack = config.LocationStack('/home/user/bar/baz')
4129
self.assertEquals('loc-foo/bar/baz', stack.get('lfoo', expand=True))
4133
class TestStackSet(TestStackWithTransport):
4135
def test_simple_set(self):
4136
conf = self.get_stack(self)
4137
self.assertEquals(None, conf.get('foo'))
4138
conf.set('foo', 'baz')
4139
# Did we get it back ?
4140
self.assertEquals('baz', conf.get('foo'))
4142
def test_set_creates_a_new_section(self):
4143
conf = self.get_stack(self)
4144
conf.set('foo', 'baz')
4145
self.assertEquals, 'baz', conf.get('foo')
4147
def test_set_hook(self):
4151
config.ConfigHooks.install_named_hook('set', hook, None)
4152
self.assertLength(0, calls)
4153
conf = self.get_stack(self)
4154
conf.set('foo', 'bar')
4155
self.assertLength(1, calls)
4156
self.assertEquals((conf, 'foo', 'bar'), calls[0])
4159
class TestStackRemove(TestStackWithTransport):
4161
def test_remove_existing(self):
4162
conf = self.get_stack(self)
4163
conf.set('foo', 'bar')
4164
self.assertEquals('bar', conf.get('foo'))
4166
# Did we get it back ?
4167
self.assertEquals(None, conf.get('foo'))
4169
def test_remove_unknown(self):
4170
conf = self.get_stack(self)
4171
self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
4173
def test_remove_hook(self):
4177
config.ConfigHooks.install_named_hook('remove', hook, None)
4178
self.assertLength(0, calls)
4179
conf = self.get_stack(self)
4180
conf.set('foo', 'bar')
4182
self.assertLength(1, calls)
4183
self.assertEquals((conf, 'foo'), calls[0])
4186
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
4189
super(TestConfigGetOptions, self).setUp()
4190
create_configs(self)
4192
def test_no_variable(self):
4193
# Using branch should query branch, locations and bazaar
4194
self.assertOptions([], self.branch_config)
4196
def test_option_in_bazaar(self):
4197
self.bazaar_config.set_user_option('file', 'bazaar')
4198
self.assertOptions([('file', 'bazaar', 'DEFAULT', 'bazaar')],
4201
def test_option_in_locations(self):
4202
self.locations_config.set_user_option('file', 'locations')
4204
[('file', 'locations', self.tree.basedir, 'locations')],
4205
self.locations_config)
4207
def test_option_in_branch(self):
4208
self.branch_config.set_user_option('file', 'branch')
4209
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch')],
4212
def test_option_in_bazaar_and_branch(self):
4213
self.bazaar_config.set_user_option('file', 'bazaar')
4214
self.branch_config.set_user_option('file', 'branch')
4215
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch'),
4216
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4219
def test_option_in_branch_and_locations(self):
4220
# Hmm, locations override branch :-/
4221
self.locations_config.set_user_option('file', 'locations')
4222
self.branch_config.set_user_option('file', 'branch')
4224
[('file', 'locations', self.tree.basedir, 'locations'),
4225
('file', 'branch', 'DEFAULT', 'branch'),],
4228
def test_option_in_bazaar_locations_and_branch(self):
4229
self.bazaar_config.set_user_option('file', 'bazaar')
4230
self.locations_config.set_user_option('file', 'locations')
4231
self.branch_config.set_user_option('file', 'branch')
4233
[('file', 'locations', self.tree.basedir, 'locations'),
4234
('file', 'branch', 'DEFAULT', 'branch'),
4235
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4239
class TestConfigRemoveOption(tests.TestCaseWithTransport, TestOptionsMixin):
4242
super(TestConfigRemoveOption, self).setUp()
4243
create_configs_with_file_option(self)
4245
def test_remove_in_locations(self):
4246
self.locations_config.remove_user_option('file', self.tree.basedir)
4248
[('file', 'branch', 'DEFAULT', 'branch'),
4249
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4252
def test_remove_in_branch(self):
4253
self.branch_config.remove_user_option('file')
4255
[('file', 'locations', self.tree.basedir, 'locations'),
4256
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4259
def test_remove_in_bazaar(self):
4260
self.bazaar_config.remove_user_option('file')
4262
[('file', 'locations', self.tree.basedir, 'locations'),
4263
('file', 'branch', 'DEFAULT', 'branch'),],
4267
class TestConfigGetSections(tests.TestCaseWithTransport):
4270
super(TestConfigGetSections, self).setUp()
4271
create_configs(self)
4273
def assertSectionNames(self, expected, conf, name=None):
4274
"""Check which sections are returned for a given config.
4276
If fallback configurations exist their sections can be included.
4278
:param expected: A list of section names.
4280
:param conf: The configuration that will be queried.
4282
:param name: An optional section name that will be passed to
4285
sections = list(conf._get_sections(name))
4286
self.assertLength(len(expected), sections)
4287
self.assertEqual(expected, [name for name, _, _ in sections])
4289
def test_bazaar_default_section(self):
4290
self.assertSectionNames(['DEFAULT'], self.bazaar_config)
4292
def test_locations_default_section(self):
4293
# No sections are defined in an empty file
4294
self.assertSectionNames([], self.locations_config)
4296
def test_locations_named_section(self):
4297
self.locations_config.set_user_option('file', 'locations')
4298
self.assertSectionNames([self.tree.basedir], self.locations_config)
4300
def test_locations_matching_sections(self):
4301
loc_config = self.locations_config
4302
loc_config.set_user_option('file', 'locations')
4303
# We need to cheat a bit here to create an option in sections above and
4304
# below the 'location' one.
4305
parser = loc_config._get_parser()
4306
# locations.cong deals with '/' ignoring native os.sep
4307
location_names = self.tree.basedir.split('/')
4308
parent = '/'.join(location_names[:-1])
4309
child = '/'.join(location_names + ['child'])
4311
parser[parent]['file'] = 'parent'
4313
parser[child]['file'] = 'child'
4314
self.assertSectionNames([self.tree.basedir, parent], loc_config)
4316
def test_branch_data_default_section(self):
4317
self.assertSectionNames([None],
4318
self.branch_config._get_branch_data_config())
4320
def test_branch_default_sections(self):
4321
# No sections are defined in an empty locations file
4322
self.assertSectionNames([None, 'DEFAULT'],
4324
# Unless we define an option
4325
self.branch_config._get_location_config().set_user_option(
4326
'file', 'locations')
4327
self.assertSectionNames([self.tree.basedir, None, 'DEFAULT'],
4330
def test_bazaar_named_section(self):
4331
# We need to cheat as the API doesn't give direct access to sections
4332
# other than DEFAULT.
4333
self.bazaar_config.set_alias('bazaar', 'bzr')
4334
self.assertSectionNames(['ALIASES'], self.bazaar_config, 'ALIASES')
1308
4337
class TestAuthenticationConfigFile(tests.TestCase):
1309
4338
"""Test the authentication.conf file matching"""