1983
1559
self.assertIs(None, bzrdir_config.get_default_stack_on())
1986
class TestOldConfigHooks(tests.TestCaseWithTransport):
1989
super(TestOldConfigHooks, self).setUp()
1990
create_configs_with_file_option(self)
1992
def assertGetHook(self, conf, name, value):
1996
config.OldConfigHooks.install_named_hook('get', hook, None)
1998
config.OldConfigHooks.uninstall_named_hook, 'get', None)
1999
self.assertLength(0, calls)
2000
actual_value = conf.get_user_option(name)
2001
self.assertEqual(value, actual_value)
2002
self.assertLength(1, calls)
2003
self.assertEqual((conf, name, value), calls[0])
2005
def test_get_hook_bazaar(self):
2006
self.assertGetHook(self.bazaar_config, 'file', 'bazaar')
2008
def test_get_hook_locations(self):
2009
self.assertGetHook(self.locations_config, 'file', 'locations')
2011
def test_get_hook_branch(self):
2012
# Since locations masks branch, we define a different option
2013
self.branch_config.set_user_option('file2', 'branch')
2014
self.assertGetHook(self.branch_config, 'file2', 'branch')
2016
def assertSetHook(self, conf, name, value):
2020
config.OldConfigHooks.install_named_hook('set', hook, None)
2022
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2023
self.assertLength(0, calls)
2024
conf.set_user_option(name, value)
2025
self.assertLength(1, calls)
2026
# We can't assert the conf object below as different configs use
2027
# different means to implement set_user_option and we care only about
2029
self.assertEqual((name, value), calls[0][1:])
2031
def test_set_hook_bazaar(self):
2032
self.assertSetHook(self.bazaar_config, 'foo', 'bazaar')
2034
def test_set_hook_locations(self):
2035
self.assertSetHook(self.locations_config, 'foo', 'locations')
2037
def test_set_hook_branch(self):
2038
self.assertSetHook(self.branch_config, 'foo', 'branch')
2040
def assertRemoveHook(self, conf, name, section_name=None):
2044
config.OldConfigHooks.install_named_hook('remove', hook, None)
2046
config.OldConfigHooks.uninstall_named_hook, 'remove', None)
2047
self.assertLength(0, calls)
2048
conf.remove_user_option(name, section_name)
2049
self.assertLength(1, calls)
2050
# We can't assert the conf object below as different configs use
2051
# different means to implement remove_user_option and we care only about
2053
self.assertEqual((name,), calls[0][1:])
2055
def test_remove_hook_bazaar(self):
2056
self.assertRemoveHook(self.bazaar_config, 'file')
2058
def test_remove_hook_locations(self):
2059
self.assertRemoveHook(self.locations_config, 'file',
2060
self.locations_config.location)
2062
def test_remove_hook_branch(self):
2063
self.assertRemoveHook(self.branch_config, 'file')
2065
def assertLoadHook(self, name, conf_class, *conf_args):
2069
config.OldConfigHooks.install_named_hook('load', hook, None)
2071
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2072
self.assertLength(0, calls)
2074
conf = conf_class(*conf_args)
2075
# Access an option to trigger a load
2076
conf.get_user_option(name)
2077
self.assertLength(1, calls)
2078
# Since we can't assert about conf, we just use the number of calls ;-/
2080
def test_load_hook_bazaar(self):
2081
self.assertLoadHook('file', config.GlobalConfig)
2083
def test_load_hook_locations(self):
2084
self.assertLoadHook('file', config.LocationConfig, self.tree.basedir)
2086
def test_load_hook_branch(self):
2087
self.assertLoadHook('file', config.BranchConfig, self.tree.branch)
2089
def assertSaveHook(self, conf):
2093
config.OldConfigHooks.install_named_hook('save', hook, None)
2095
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2096
self.assertLength(0, calls)
2097
# Setting an option triggers a save
2098
conf.set_user_option('foo', 'bar')
2099
self.assertLength(1, calls)
2100
# Since we can't assert about conf, we just use the number of calls ;-/
2102
def test_save_hook_bazaar(self):
2103
self.assertSaveHook(self.bazaar_config)
2105
def test_save_hook_locations(self):
2106
self.assertSaveHook(self.locations_config)
2108
def test_save_hook_branch(self):
2109
self.assertSaveHook(self.branch_config)
2112
class TestOldConfigHooksForRemote(tests.TestCaseWithTransport):
2113
"""Tests config hooks for remote configs.
2115
No tests for the remove hook as this is not implemented there.
2119
super(TestOldConfigHooksForRemote, self).setUp()
2120
self.transport_server = test_server.SmartTCPServer_for_testing
2121
create_configs_with_file_option(self)
2123
def assertGetHook(self, conf, name, value):
2127
config.OldConfigHooks.install_named_hook('get', hook, None)
2129
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2130
self.assertLength(0, calls)
2131
actual_value = conf.get_option(name)
2132
self.assertEqual(value, actual_value)
2133
self.assertLength(1, calls)
2134
self.assertEqual((conf, name, value), calls[0])
2136
def test_get_hook_remote_branch(self):
2137
remote_branch = branch.Branch.open(self.get_url('tree'))
2138
self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
2140
def test_get_hook_remote_bzrdir(self):
2141
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2142
conf = remote_bzrdir._get_config()
2143
conf.set_option('remotedir', 'file')
2144
self.assertGetHook(conf, 'file', 'remotedir')
2146
def assertSetHook(self, conf, name, value):
2150
config.OldConfigHooks.install_named_hook('set', hook, None)
2152
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2153
self.assertLength(0, calls)
2154
conf.set_option(value, name)
2155
self.assertLength(1, calls)
2156
# We can't assert the conf object below as different configs use
2157
# different means to implement set_user_option and we care only about
2159
self.assertEqual((name, value), calls[0][1:])
2161
def test_set_hook_remote_branch(self):
2162
remote_branch = branch.Branch.open(self.get_url('tree'))
2163
self.addCleanup(remote_branch.lock_write().unlock)
2164
self.assertSetHook(remote_branch._get_config(), 'file', 'remote')
2166
def test_set_hook_remote_bzrdir(self):
2167
remote_branch = branch.Branch.open(self.get_url('tree'))
2168
self.addCleanup(remote_branch.lock_write().unlock)
2169
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2170
self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
2172
def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
2176
config.OldConfigHooks.install_named_hook('load', hook, None)
2178
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2179
self.assertLength(0, calls)
2181
conf = conf_class(*conf_args)
2182
# Access an option to trigger a load
2183
conf.get_option(name)
2184
self.assertLength(expected_nb_calls, calls)
2185
# Since we can't assert about conf, we just use the number of calls ;-/
2187
def test_load_hook_remote_branch(self):
2188
remote_branch = branch.Branch.open(self.get_url('tree'))
2189
self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
2191
def test_load_hook_remote_bzrdir(self):
2192
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2193
# The config file doesn't exist, set an option to force its creation
2194
conf = remote_bzrdir._get_config()
2195
conf.set_option('remotedir', 'file')
2196
# We get one call for the server and one call for the client, this is
2197
# caused by the differences in implementations betwen
2198
# SmartServerBzrDirRequestConfigFile (in smart/bzrdir.py) and
2199
# SmartServerBranchGetConfigFile (in smart/branch.py)
2200
self.assertLoadHook(2 ,'file', remote.RemoteBzrDirConfig, remote_bzrdir)
2202
def assertSaveHook(self, conf):
2206
config.OldConfigHooks.install_named_hook('save', hook, None)
2208
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2209
self.assertLength(0, calls)
2210
# Setting an option triggers a save
2211
conf.set_option('foo', 'bar')
2212
self.assertLength(1, calls)
2213
# Since we can't assert about conf, we just use the number of calls ;-/
2215
def test_save_hook_remote_branch(self):
2216
remote_branch = branch.Branch.open(self.get_url('tree'))
2217
self.addCleanup(remote_branch.lock_write().unlock)
2218
self.assertSaveHook(remote_branch._get_config())
2220
def test_save_hook_remote_bzrdir(self):
2221
remote_branch = branch.Branch.open(self.get_url('tree'))
2222
self.addCleanup(remote_branch.lock_write().unlock)
2223
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2224
self.assertSaveHook(remote_bzrdir._get_config())
2227
class TestOptionNames(tests.TestCase):
2229
def is_valid(self, name):
2230
return config._option_ref_re.match('{%s}' % name) is not None
2232
def test_valid_names(self):
2233
self.assertTrue(self.is_valid('foo'))
2234
self.assertTrue(self.is_valid('foo.bar'))
2235
self.assertTrue(self.is_valid('f1'))
2236
self.assertTrue(self.is_valid('_'))
2237
self.assertTrue(self.is_valid('__bar__'))
2238
self.assertTrue(self.is_valid('a_'))
2239
self.assertTrue(self.is_valid('a1'))
2240
# Don't break bzr-svn for no good reason
2241
self.assertTrue(self.is_valid('guessed-layout'))
2243
def test_invalid_names(self):
2244
self.assertFalse(self.is_valid(' foo'))
2245
self.assertFalse(self.is_valid('foo '))
2246
self.assertFalse(self.is_valid('1'))
2247
self.assertFalse(self.is_valid('1,2'))
2248
self.assertFalse(self.is_valid('foo$'))
2249
self.assertFalse(self.is_valid('!foo'))
2250
self.assertFalse(self.is_valid('foo.'))
2251
self.assertFalse(self.is_valid('foo..bar'))
2252
self.assertFalse(self.is_valid('{}'))
2253
self.assertFalse(self.is_valid('{a}'))
2254
self.assertFalse(self.is_valid('a\n'))
2255
self.assertFalse(self.is_valid('-'))
2256
self.assertFalse(self.is_valid('-a'))
2257
self.assertFalse(self.is_valid('a-'))
2258
self.assertFalse(self.is_valid('a--a'))
2260
def assertSingleGroup(self, reference):
2261
# the regexp is used with split and as such should match the reference
2262
# *only*, if more groups needs to be defined, (?:...) should be used.
2263
m = config._option_ref_re.match('{a}')
2264
self.assertLength(1, m.groups())
2266
def test_valid_references(self):
2267
self.assertSingleGroup('{a}')
2268
self.assertSingleGroup('{{a}}')
2271
class TestOption(tests.TestCase):
2273
def test_default_value(self):
2274
opt = config.Option('foo', default='bar')
2275
self.assertEqual('bar', opt.get_default())
2277
def test_callable_default_value(self):
2278
def bar_as_unicode():
2280
opt = config.Option('foo', default=bar_as_unicode)
2281
self.assertEqual('bar', opt.get_default())
2283
def test_default_value_from_env(self):
2284
opt = config.Option('foo', default='bar', default_from_env=['FOO'])
2285
self.overrideEnv('FOO', 'quux')
2286
# Env variable provides a default taking over the option one
2287
self.assertEqual('quux', opt.get_default())
2289
def test_first_default_value_from_env_wins(self):
2290
opt = config.Option('foo', default='bar',
2291
default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
2292
self.overrideEnv('FOO', 'foo')
2293
self.overrideEnv('BAZ', 'baz')
2294
# The first env var set wins
2295
self.assertEqual('foo', opt.get_default())
2297
def test_not_supported_list_default_value(self):
2298
self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
2300
def test_not_supported_object_default_value(self):
2301
self.assertRaises(AssertionError, config.Option, 'foo',
2304
def test_not_supported_callable_default_value_not_unicode(self):
2305
def bar_not_unicode():
2307
opt = config.Option('foo', default=bar_not_unicode)
2308
self.assertRaises(AssertionError, opt.get_default)
2310
def test_get_help_topic(self):
2311
opt = config.Option('foo')
2312
self.assertEqual('foo', opt.get_help_topic())
2315
class TestOptionConverter(tests.TestCase):
2317
def assertConverted(self, expected, opt, value):
2318
self.assertEqual(expected, opt.convert_from_unicode(None, value))
2320
def assertCallsWarning(self, opt, value):
2324
warnings.append(args[0] % args[1:])
2325
self.overrideAttr(trace, 'warning', warning)
2326
self.assertEqual(None, opt.convert_from_unicode(None, value))
2327
self.assertLength(1, warnings)
2329
'Value "%s" is not valid for "%s"' % (value, opt.name),
2332
def assertCallsError(self, opt, value):
2333
self.assertRaises(errors.ConfigOptionValueError,
2334
opt.convert_from_unicode, None, value)
2336
def assertConvertInvalid(self, opt, invalid_value):
2338
self.assertEqual(None, opt.convert_from_unicode(None, invalid_value))
2339
opt.invalid = 'warning'
2340
self.assertCallsWarning(opt, invalid_value)
2341
opt.invalid = 'error'
2342
self.assertCallsError(opt, invalid_value)
2345
class TestOptionWithBooleanConverter(TestOptionConverter):
2347
def get_option(self):
2348
return config.Option('foo', help='A boolean.',
2349
from_unicode=config.bool_from_store)
2351
def test_convert_invalid(self):
2352
opt = self.get_option()
2353
# A string that is not recognized as a boolean
2354
self.assertConvertInvalid(opt, u'invalid-boolean')
2355
# A list of strings is never recognized as a boolean
2356
self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
2358
def test_convert_valid(self):
2359
opt = self.get_option()
2360
self.assertConverted(True, opt, u'True')
2361
self.assertConverted(True, opt, u'1')
2362
self.assertConverted(False, opt, u'False')
2365
class TestOptionWithIntegerConverter(TestOptionConverter):
2367
def get_option(self):
2368
return config.Option('foo', help='An integer.',
2369
from_unicode=config.int_from_store)
2371
def test_convert_invalid(self):
2372
opt = self.get_option()
2373
# A string that is not recognized as an integer
2374
self.assertConvertInvalid(opt, u'forty-two')
2375
# A list of strings is never recognized as an integer
2376
self.assertConvertInvalid(opt, [u'a', u'list'])
2378
def test_convert_valid(self):
2379
opt = self.get_option()
2380
self.assertConverted(16, opt, u'16')
2383
class TestOptionWithSIUnitConverter(TestOptionConverter):
2385
def get_option(self):
2386
return config.Option('foo', help='An integer in SI units.',
2387
from_unicode=config.int_SI_from_store)
2389
def test_convert_invalid(self):
2390
opt = self.get_option()
2391
self.assertConvertInvalid(opt, u'not-a-unit')
2392
self.assertConvertInvalid(opt, u'Gb') # Forgot the value
2393
self.assertConvertInvalid(opt, u'1b') # Forgot the unit
2394
self.assertConvertInvalid(opt, u'1GG')
2395
self.assertConvertInvalid(opt, u'1Mbb')
2396
self.assertConvertInvalid(opt, u'1MM')
2398
def test_convert_valid(self):
2399
opt = self.get_option()
2400
self.assertConverted(int(5e3), opt, u'5kb')
2401
self.assertConverted(int(5e6), opt, u'5M')
2402
self.assertConverted(int(5e6), opt, u'5MB')
2403
self.assertConverted(int(5e9), opt, u'5g')
2404
self.assertConverted(int(5e9), opt, u'5gB')
2405
self.assertConverted(100, opt, u'100')
2408
class TestListOption(TestOptionConverter):
2410
def get_option(self):
2411
return config.ListOption('foo', help='A list.')
2413
def test_convert_invalid(self):
2414
opt = self.get_option()
2415
# We don't even try to convert a list into a list, we only expect
2417
self.assertConvertInvalid(opt, [1])
2418
# No string is invalid as all forms can be converted to a list
2420
def test_convert_valid(self):
2421
opt = self.get_option()
2422
# An empty string is an empty list
2423
self.assertConverted([], opt, '') # Using a bare str() just in case
2424
self.assertConverted([], opt, u'')
2426
self.assertConverted([u'True'], opt, u'True')
2428
self.assertConverted([u'42'], opt, u'42')
2430
self.assertConverted([u'bar'], opt, u'bar')
2433
class TestRegistryOption(TestOptionConverter):
2435
def get_option(self, registry):
2436
return config.RegistryOption('foo', registry,
2437
help='A registry option.')
2439
def test_convert_invalid(self):
2440
registry = _mod_registry.Registry()
2441
opt = self.get_option(registry)
2442
self.assertConvertInvalid(opt, [1])
2443
self.assertConvertInvalid(opt, u"notregistered")
2445
def test_convert_valid(self):
2446
registry = _mod_registry.Registry()
2447
registry.register("someval", 1234)
2448
opt = self.get_option(registry)
2449
# Using a bare str() just in case
2450
self.assertConverted(1234, opt, "someval")
2451
self.assertConverted(1234, opt, u'someval')
2452
self.assertConverted(None, opt, None)
2454
def test_help(self):
2455
registry = _mod_registry.Registry()
2456
registry.register("someval", 1234, help="some option")
2457
registry.register("dunno", 1234, help="some other option")
2458
opt = self.get_option(registry)
2460
'A registry option.\n'
2462
'The following values are supported:\n'
2463
' dunno - some other option\n'
2464
' someval - some option\n',
2467
def test_get_help_text(self):
2468
registry = _mod_registry.Registry()
2469
registry.register("someval", 1234, help="some option")
2470
registry.register("dunno", 1234, help="some other option")
2471
opt = self.get_option(registry)
2473
'A registry option.\n'
2475
'The following values are supported:\n'
2476
' dunno - some other option\n'
2477
' someval - some option\n',
2478
opt.get_help_text())
2481
class TestOptionRegistry(tests.TestCase):
2484
super(TestOptionRegistry, self).setUp()
2485
# Always start with an empty registry
2486
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2487
self.registry = config.option_registry
2489
def test_register(self):
2490
opt = config.Option('foo')
2491
self.registry.register(opt)
2492
self.assertIs(opt, self.registry.get('foo'))
2494
def test_registered_help(self):
2495
opt = config.Option('foo', help='A simple option')
2496
self.registry.register(opt)
2497
self.assertEqual('A simple option', self.registry.get_help('foo'))
2499
def test_dont_register_illegal_name(self):
2500
self.assertRaises(errors.IllegalOptionName,
2501
self.registry.register, config.Option(' foo'))
2502
self.assertRaises(errors.IllegalOptionName,
2503
self.registry.register, config.Option('bar,'))
2505
lazy_option = config.Option('lazy_foo', help='Lazy help')
2507
def test_register_lazy(self):
2508
self.registry.register_lazy('lazy_foo', self.__module__,
2509
'TestOptionRegistry.lazy_option')
2510
self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
2512
def test_registered_lazy_help(self):
2513
self.registry.register_lazy('lazy_foo', self.__module__,
2514
'TestOptionRegistry.lazy_option')
2515
self.assertEqual('Lazy help', self.registry.get_help('lazy_foo'))
2517
def test_dont_lazy_register_illegal_name(self):
2518
# This is where the root cause of http://pad.lv/1235099 is better
2519
# understood: 'register_lazy' doc string mentions that key should match
2520
# the option name which indirectly requires that the option name is a
2521
# valid python identifier. We violate that rule here (using a key that
2522
# doesn't match the option name) to test the option name checking.
2523
self.assertRaises(errors.IllegalOptionName,
2524
self.registry.register_lazy, ' foo', self.__module__,
2525
'TestOptionRegistry.lazy_option')
2526
self.assertRaises(errors.IllegalOptionName,
2527
self.registry.register_lazy, '1,2', self.__module__,
2528
'TestOptionRegistry.lazy_option')
2531
class TestRegisteredOptions(tests.TestCase):
2532
"""All registered options should verify some constraints."""
2534
scenarios = [(key, {'option_name': key, 'option': option}) for key, option
2535
in config.option_registry.iteritems()]
2538
super(TestRegisteredOptions, self).setUp()
2539
self.registry = config.option_registry
2541
def test_proper_name(self):
2542
# An option should be registered under its own name, this can't be
2543
# checked at registration time for the lazy ones.
2544
self.assertEqual(self.option_name, self.option.name)
2546
def test_help_is_set(self):
2547
option_help = self.registry.get_help(self.option_name)
2548
# Come on, think about the user, he really wants to know what the
2550
self.assertIsNot(None, option_help)
2551
self.assertNotEqual('', option_help)
2554
class TestSection(tests.TestCase):
2556
# FIXME: Parametrize so that all sections produced by Stores run these
2557
# tests -- vila 2011-04-01
2559
def test_get_a_value(self):
2560
a_dict = dict(foo='bar')
2561
section = config.Section('myID', a_dict)
2562
self.assertEqual('bar', section.get('foo'))
2564
def test_get_unknown_option(self):
2566
section = config.Section(None, a_dict)
2567
self.assertEqual('out of thin air',
2568
section.get('foo', 'out of thin air'))
2570
def test_options_is_shared(self):
2572
section = config.Section(None, a_dict)
2573
self.assertIs(a_dict, section.options)
2576
class TestMutableSection(tests.TestCase):
2578
scenarios = [('mutable',
2580
lambda opts: config.MutableSection('myID', opts)},),
2584
a_dict = dict(foo='bar')
2585
section = self.get_section(a_dict)
2586
section.set('foo', 'new_value')
2587
self.assertEqual('new_value', section.get('foo'))
2588
# The change appears in the shared section
2589
self.assertEqual('new_value', a_dict.get('foo'))
2590
# We keep track of the change
2591
self.assertTrue('foo' in section.orig)
2592
self.assertEqual('bar', section.orig.get('foo'))
2594
def test_set_preserve_original_once(self):
2595
a_dict = dict(foo='bar')
2596
section = self.get_section(a_dict)
2597
section.set('foo', 'first_value')
2598
section.set('foo', 'second_value')
2599
# We keep track of the original value
2600
self.assertTrue('foo' in section.orig)
2601
self.assertEqual('bar', section.orig.get('foo'))
2603
def test_remove(self):
2604
a_dict = dict(foo='bar')
2605
section = self.get_section(a_dict)
2606
section.remove('foo')
2607
# We get None for unknown options via the default value
2608
self.assertEqual(None, section.get('foo'))
2609
# Or we just get the default value
2610
self.assertEqual('unknown', section.get('foo', 'unknown'))
2611
self.assertFalse('foo' in section.options)
2612
# We keep track of the deletion
2613
self.assertTrue('foo' in section.orig)
2614
self.assertEqual('bar', section.orig.get('foo'))
2616
def test_remove_new_option(self):
2618
section = self.get_section(a_dict)
2619
section.set('foo', 'bar')
2620
section.remove('foo')
2621
self.assertFalse('foo' in section.options)
2622
# The option didn't exist initially so it we need to keep track of it
2623
# with a special value
2624
self.assertTrue('foo' in section.orig)
2625
self.assertEqual(config._NewlyCreatedOption, section.orig['foo'])
2628
class TestCommandLineStore(tests.TestCase):
2631
super(TestCommandLineStore, self).setUp()
2632
self.store = config.CommandLineStore()
2633
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2635
def get_section(self):
2636
"""Get the unique section for the command line overrides."""
2637
sections = list(self.store.get_sections())
2638
self.assertLength(1, sections)
2639
store, section = sections[0]
2640
self.assertEqual(self.store, store)
2643
def test_no_override(self):
2644
self.store._from_cmdline([])
2645
section = self.get_section()
2646
self.assertLength(0, list(section.iter_option_names()))
2648
def test_simple_override(self):
2649
self.store._from_cmdline(['a=b'])
2650
section = self.get_section()
2651
self.assertEqual('b', section.get('a'))
2653
def test_list_override(self):
2654
opt = config.ListOption('l')
2655
config.option_registry.register(opt)
2656
self.store._from_cmdline(['l=1,2,3'])
2657
val = self.get_section().get('l')
2658
self.assertEqual('1,2,3', val)
2659
# Reminder: lists should be registered as such explicitely, otherwise
2660
# the conversion needs to be done afterwards.
2661
self.assertEqual(['1', '2', '3'],
2662
opt.convert_from_unicode(self.store, val))
2664
def test_multiple_overrides(self):
2665
self.store._from_cmdline(['a=b', 'x=y'])
2666
section = self.get_section()
2667
self.assertEqual('b', section.get('a'))
2668
self.assertEqual('y', section.get('x'))
2670
def test_wrong_syntax(self):
2671
self.assertRaises(errors.BzrCommandError,
2672
self.store._from_cmdline, ['a=b', 'c'])
2674
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
2676
scenarios = [(key, {'get_store': builder}) for key, builder
2677
in config.test_store_builder_registry.iteritems()] + [
2678
('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
2681
store = self.get_store(self)
2682
if type(store) == config.TransportIniFileStore:
2683
raise tests.TestNotApplicable(
2684
"%s is not a concrete Store implementation"
2685
" so it doesn't need an id" % (store.__class__.__name__,))
2686
self.assertIsNot(None, store.id)
2689
class TestStore(tests.TestCaseWithTransport):
2691
def assertSectionContent(self, expected, (store, section)):
2692
"""Assert that some options have the proper values in a section."""
2693
expected_name, expected_options = expected
2694
self.assertEqual(expected_name, section.id)
2697
dict([(k, section.get(k)) for k in expected_options.keys()]))
2700
class TestReadonlyStore(TestStore):
2702
scenarios = [(key, {'get_store': builder}) for key, builder
2703
in config.test_store_builder_registry.iteritems()]
2705
def test_building_delays_load(self):
2706
store = self.get_store(self)
2707
self.assertEqual(False, store.is_loaded())
2708
store._load_from_string('')
2709
self.assertEqual(True, store.is_loaded())
2711
def test_get_no_sections_for_empty(self):
2712
store = self.get_store(self)
2713
store._load_from_string('')
2714
self.assertEqual([], list(store.get_sections()))
2716
def test_get_default_section(self):
2717
store = self.get_store(self)
2718
store._load_from_string('foo=bar')
2719
sections = list(store.get_sections())
2720
self.assertLength(1, sections)
2721
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2723
def test_get_named_section(self):
2724
store = self.get_store(self)
2725
store._load_from_string('[baz]\nfoo=bar')
2726
sections = list(store.get_sections())
2727
self.assertLength(1, sections)
2728
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2730
def test_load_from_string_fails_for_non_empty_store(self):
2731
store = self.get_store(self)
2732
store._load_from_string('foo=bar')
2733
self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
2736
class TestStoreQuoting(TestStore):
2738
scenarios = [(key, {'get_store': builder}) for key, builder
2739
in config.test_store_builder_registry.iteritems()]
2742
super(TestStoreQuoting, self).setUp()
2743
self.store = self.get_store(self)
2744
# We need a loaded store but any content will do
2745
self.store._load_from_string('')
2747
def assertIdempotent(self, s):
2748
"""Assert that quoting an unquoted string is a no-op and vice-versa.
2750
What matters here is that option values, as they appear in a store, can
2751
be safely round-tripped out of the store and back.
2753
:param s: A string, quoted if required.
2755
self.assertEqual(s, self.store.quote(self.store.unquote(s)))
2756
self.assertEqual(s, self.store.unquote(self.store.quote(s)))
2758
def test_empty_string(self):
2759
if isinstance(self.store, config.IniFileStore):
2760
# configobj._quote doesn't handle empty values
2761
self.assertRaises(AssertionError,
2762
self.assertIdempotent, '')
2764
self.assertIdempotent('')
2765
# But quoted empty strings are ok
2766
self.assertIdempotent('""')
2768
def test_embedded_spaces(self):
2769
self.assertIdempotent('" a b c "')
2771
def test_embedded_commas(self):
2772
self.assertIdempotent('" a , b c "')
2774
def test_simple_comma(self):
2775
if isinstance(self.store, config.IniFileStore):
2776
# configobj requires that lists are special-cased
2777
self.assertRaises(AssertionError,
2778
self.assertIdempotent, ',')
2780
self.assertIdempotent(',')
2781
# When a single comma is required, quoting is also required
2782
self.assertIdempotent('","')
2784
def test_list(self):
2785
if isinstance(self.store, config.IniFileStore):
2786
# configobj requires that lists are special-cased
2787
self.assertRaises(AssertionError,
2788
self.assertIdempotent, 'a,b')
2790
self.assertIdempotent('a,b')
2793
class TestDictFromStore(tests.TestCase):
2795
def test_unquote_not_string(self):
2796
conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
2797
value = conf.get('a_section')
2798
# Urgh, despite 'conf' asking for the no-name section, we get the
2799
# content of another section as a dict o_O
2800
self.assertEqual({'a': '1'}, value)
2801
unquoted = conf.store.unquote(value)
2802
# Which cannot be unquoted but shouldn't crash either (the use cases
2803
# are getting the value or displaying it. In the later case, '%s' will
2805
self.assertEqual({'a': '1'}, unquoted)
2806
self.assertEqual("{u'a': u'1'}", '%s' % (unquoted,))
2809
class TestIniFileStoreContent(tests.TestCaseWithTransport):
2810
"""Simulate loading a config store with content of various encodings.
2812
All files produced by bzr are in utf8 content.
2814
Users may modify them manually and end up with a file that can't be
2815
loaded. We need to issue proper error messages in this case.
2818
invalid_utf8_char = '\xff'
2820
def test_load_utf8(self):
2821
"""Ensure we can load an utf8-encoded file."""
2822
t = self.get_transport()
2823
# From http://pad.lv/799212
2824
unicode_user = u'b\N{Euro Sign}ar'
2825
unicode_content = u'user=%s' % (unicode_user,)
2826
utf8_content = unicode_content.encode('utf8')
2827
# Store the raw content in the config file
2828
t.put_bytes('foo.conf', utf8_content)
2829
store = config.TransportIniFileStore(t, 'foo.conf')
2831
stack = config.Stack([store.get_sections], store)
2832
self.assertEqual(unicode_user, stack.get('user'))
2834
def test_load_non_ascii(self):
2835
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2836
t = self.get_transport()
2837
t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
2838
store = config.TransportIniFileStore(t, 'foo.conf')
2839
self.assertRaises(errors.ConfigContentError, store.load)
2841
def test_load_erroneous_content(self):
2842
"""Ensure we display a proper error on content that can't be parsed."""
2843
t = self.get_transport()
2844
t.put_bytes('foo.conf', '[open_section\n')
2845
store = config.TransportIniFileStore(t, 'foo.conf')
2846
self.assertRaises(errors.ParseConfigError, store.load)
2848
def test_load_permission_denied(self):
2849
"""Ensure we get warned when trying to load an inaccessible file."""
2852
warnings.append(args[0] % args[1:])
2853
self.overrideAttr(trace, 'warning', warning)
2855
t = self.get_transport()
2857
def get_bytes(relpath):
2858
raise errors.PermissionDenied(relpath, "")
2859
t.get_bytes = get_bytes
2860
store = config.TransportIniFileStore(t, 'foo.conf')
2861
self.assertRaises(errors.PermissionDenied, store.load)
2864
[u'Permission denied while trying to load configuration store %s.'
2865
% store.external_url()])
2868
class TestIniConfigContent(tests.TestCaseWithTransport):
2869
"""Simulate loading a IniBasedConfig with content of various encodings.
2871
All files produced by bzr are in utf8 content.
2873
Users may modify them manually and end up with a file that can't be
2874
loaded. We need to issue proper error messages in this case.
2877
invalid_utf8_char = '\xff'
2879
def test_load_utf8(self):
2880
"""Ensure we can load an utf8-encoded file."""
2881
# From http://pad.lv/799212
2882
unicode_user = u'b\N{Euro Sign}ar'
2883
unicode_content = u'user=%s' % (unicode_user,)
2884
utf8_content = unicode_content.encode('utf8')
2885
# Store the raw content in the config file
2886
with open('foo.conf', 'wb') as f:
2887
f.write(utf8_content)
2888
conf = config.IniBasedConfig(file_name='foo.conf')
2889
self.assertEqual(unicode_user, conf.get_user_option('user'))
2891
def test_load_badly_encoded_content(self):
2892
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2893
with open('foo.conf', 'wb') as f:
2894
f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
2895
conf = config.IniBasedConfig(file_name='foo.conf')
2896
self.assertRaises(errors.ConfigContentError, conf._get_parser)
2898
def test_load_erroneous_content(self):
2899
"""Ensure we display a proper error on content that can't be parsed."""
2900
with open('foo.conf', 'wb') as f:
2901
f.write('[open_section\n')
2902
conf = config.IniBasedConfig(file_name='foo.conf')
2903
self.assertRaises(errors.ParseConfigError, conf._get_parser)
2906
class TestMutableStore(TestStore):
2908
scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
2909
in config.test_store_builder_registry.iteritems()]
2912
super(TestMutableStore, self).setUp()
2913
self.transport = self.get_transport()
2915
def has_store(self, store):
2916
store_basename = urlutils.relative_url(self.transport.external_url(),
2917
store.external_url())
2918
return self.transport.has(store_basename)
2920
def test_save_empty_creates_no_file(self):
2921
# FIXME: There should be a better way than relying on the test
2922
# parametrization to identify branch.conf -- vila 2011-0526
2923
if self.store_id in ('branch', 'remote_branch'):
2924
raise tests.TestNotApplicable(
2925
'branch.conf is *always* created when a branch is initialized')
2926
store = self.get_store(self)
2928
self.assertEqual(False, self.has_store(store))
2930
def test_mutable_section_shared(self):
2931
store = self.get_store(self)
2932
store._load_from_string('foo=bar\n')
2933
# FIXME: There should be a better way than relying on the test
2934
# parametrization to identify branch.conf -- vila 2011-0526
2935
if self.store_id in ('branch', 'remote_branch'):
2936
# branch stores requires write locked branches
2937
self.addCleanup(store.branch.lock_write().unlock)
2938
section1 = store.get_mutable_section(None)
2939
section2 = store.get_mutable_section(None)
2940
# If we get different sections, different callers won't share the
2942
self.assertIs(section1, section2)
2944
def test_save_emptied_succeeds(self):
2945
store = self.get_store(self)
2946
store._load_from_string('foo=bar\n')
2947
# FIXME: There should be a better way than relying on the test
2948
# parametrization to identify branch.conf -- vila 2011-0526
2949
if self.store_id in ('branch', 'remote_branch'):
2950
# branch stores requires write locked branches
2951
self.addCleanup(store.branch.lock_write().unlock)
2952
section = store.get_mutable_section(None)
2953
section.remove('foo')
2955
self.assertEqual(True, self.has_store(store))
2956
modified_store = self.get_store(self)
2957
sections = list(modified_store.get_sections())
2958
self.assertLength(0, sections)
2960
def test_save_with_content_succeeds(self):
2961
# FIXME: There should be a better way than relying on the test
2962
# parametrization to identify branch.conf -- vila 2011-0526
2963
if self.store_id in ('branch', 'remote_branch'):
2964
raise tests.TestNotApplicable(
2965
'branch.conf is *always* created when a branch is initialized')
2966
store = self.get_store(self)
2967
store._load_from_string('foo=bar\n')
2968
self.assertEqual(False, self.has_store(store))
2970
self.assertEqual(True, self.has_store(store))
2971
modified_store = self.get_store(self)
2972
sections = list(modified_store.get_sections())
2973
self.assertLength(1, sections)
2974
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2976
def test_set_option_in_empty_store(self):
2977
store = self.get_store(self)
2978
# FIXME: There should be a better way than relying on the test
2979
# parametrization to identify branch.conf -- vila 2011-0526
2980
if self.store_id in ('branch', 'remote_branch'):
2981
# branch stores requires write locked branches
2982
self.addCleanup(store.branch.lock_write().unlock)
2983
section = store.get_mutable_section(None)
2984
section.set('foo', 'bar')
2986
modified_store = self.get_store(self)
2987
sections = list(modified_store.get_sections())
2988
self.assertLength(1, sections)
2989
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2991
def test_set_option_in_default_section(self):
2992
store = self.get_store(self)
2993
store._load_from_string('')
2994
# FIXME: There should be a better way than relying on the test
2995
# parametrization to identify branch.conf -- vila 2011-0526
2996
if self.store_id in ('branch', 'remote_branch'):
2997
# branch stores requires write locked branches
2998
self.addCleanup(store.branch.lock_write().unlock)
2999
section = store.get_mutable_section(None)
3000
section.set('foo', 'bar')
3002
modified_store = self.get_store(self)
3003
sections = list(modified_store.get_sections())
3004
self.assertLength(1, sections)
3005
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
3007
def test_set_option_in_named_section(self):
3008
store = self.get_store(self)
3009
store._load_from_string('')
3010
# FIXME: There should be a better way than relying on the test
3011
# parametrization to identify branch.conf -- vila 2011-0526
3012
if self.store_id in ('branch', 'remote_branch'):
3013
# branch stores requires write locked branches
3014
self.addCleanup(store.branch.lock_write().unlock)
3015
section = store.get_mutable_section('baz')
3016
section.set('foo', 'bar')
3018
modified_store = self.get_store(self)
3019
sections = list(modified_store.get_sections())
3020
self.assertLength(1, sections)
3021
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
3023
def test_load_hook(self):
3024
# First, we need to ensure that the store exists
3025
store = self.get_store(self)
3026
# FIXME: There should be a better way than relying on the test
3027
# parametrization to identify branch.conf -- vila 2011-0526
3028
if self.store_id in ('branch', 'remote_branch'):
3029
# branch stores requires write locked branches
3030
self.addCleanup(store.branch.lock_write().unlock)
3031
section = store.get_mutable_section('baz')
3032
section.set('foo', 'bar')
3034
# Now we can try to load it
3035
store = self.get_store(self)
3039
config.ConfigHooks.install_named_hook('load', hook, None)
3040
self.assertLength(0, calls)
3042
self.assertLength(1, calls)
3043
self.assertEqual((store,), calls[0])
3045
def test_save_hook(self):
3049
config.ConfigHooks.install_named_hook('save', hook, None)
3050
self.assertLength(0, calls)
3051
store = self.get_store(self)
3052
# FIXME: There should be a better way than relying on the test
3053
# parametrization to identify branch.conf -- vila 2011-0526
3054
if self.store_id in ('branch', 'remote_branch'):
3055
# branch stores requires write locked branches
3056
self.addCleanup(store.branch.lock_write().unlock)
3057
section = store.get_mutable_section('baz')
3058
section.set('foo', 'bar')
3060
self.assertLength(1, calls)
3061
self.assertEqual((store,), calls[0])
3063
def test_set_mark_dirty(self):
3064
stack = config.MemoryStack('')
3065
self.assertLength(0, stack.store.dirty_sections)
3066
stack.set('foo', 'baz')
3067
self.assertLength(1, stack.store.dirty_sections)
3068
self.assertTrue(stack.store._need_saving())
3070
def test_remove_mark_dirty(self):
3071
stack = config.MemoryStack('foo=bar')
3072
self.assertLength(0, stack.store.dirty_sections)
3074
self.assertLength(1, stack.store.dirty_sections)
3075
self.assertTrue(stack.store._need_saving())
3078
class TestStoreSaveChanges(tests.TestCaseWithTransport):
3079
"""Tests that config changes are kept in memory and saved on-demand."""
3082
super(TestStoreSaveChanges, self).setUp()
3083
self.transport = self.get_transport()
3084
# Most of the tests involve two stores pointing to the same persistent
3085
# storage to observe the effects of concurrent changes
3086
self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
3087
self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
3090
self.warnings.append(args[0] % args[1:])
3091
self.overrideAttr(trace, 'warning', warning)
3093
def has_store(self, store):
3094
store_basename = urlutils.relative_url(self.transport.external_url(),
3095
store.external_url())
3096
return self.transport.has(store_basename)
3098
def get_stack(self, store):
3099
# Any stack will do as long as it uses the right store, just a single
3100
# no-name section is enough
3101
return config.Stack([store.get_sections], store)
3103
def test_no_changes_no_save(self):
3104
s = self.get_stack(self.st1)
3105
s.store.save_changes()
3106
self.assertEqual(False, self.has_store(self.st1))
3108
def test_unrelated_concurrent_update(self):
3109
s1 = self.get_stack(self.st1)
3110
s2 = self.get_stack(self.st2)
3111
s1.set('foo', 'bar')
3112
s2.set('baz', 'quux')
3114
# Changes don't propagate magically
3115
self.assertEqual(None, s1.get('baz'))
3116
s2.store.save_changes()
3117
self.assertEqual('quux', s2.get('baz'))
3118
# Changes are acquired when saving
3119
self.assertEqual('bar', s2.get('foo'))
3120
# Since there is no overlap, no warnings are emitted
3121
self.assertLength(0, self.warnings)
3123
def test_concurrent_update_modified(self):
3124
s1 = self.get_stack(self.st1)
3125
s2 = self.get_stack(self.st2)
3126
s1.set('foo', 'bar')
3127
s2.set('foo', 'baz')
3130
s2.store.save_changes()
3131
self.assertEqual('baz', s2.get('foo'))
3132
# But the user get a warning
3133
self.assertLength(1, self.warnings)
3134
warning = self.warnings[0]
3135
self.assertStartsWith(warning, 'Option foo in section None')
3136
self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
3137
' The baz value will be saved.')
3139
def test_concurrent_deletion(self):
3140
self.st1._load_from_string('foo=bar')
3142
s1 = self.get_stack(self.st1)
3143
s2 = self.get_stack(self.st2)
3146
s1.store.save_changes()
3148
self.assertLength(0, self.warnings)
3149
s2.store.save_changes()
3151
self.assertLength(1, self.warnings)
3152
warning = self.warnings[0]
3153
self.assertStartsWith(warning, 'Option foo in section None')
3154
self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
3155
' The <DELETED> value will be saved.')
3158
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
3160
def get_store(self):
3161
return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3163
def test_get_quoted_string(self):
3164
store = self.get_store()
3165
store._load_from_string('foo= " abc "')
3166
stack = config.Stack([store.get_sections])
3167
self.assertEqual(' abc ', stack.get('foo'))
3169
def test_set_quoted_string(self):
3170
store = self.get_store()
3171
stack = config.Stack([store.get_sections], store)
3172
stack.set('foo', ' a b c ')
3174
self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
3177
class TestTransportIniFileStore(TestStore):
3179
def test_loading_unknown_file_fails(self):
3180
store = config.TransportIniFileStore(self.get_transport(),
3182
self.assertRaises(errors.NoSuchFile, store.load)
3184
def test_invalid_content(self):
3185
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3186
self.assertEqual(False, store.is_loaded())
3187
exc = self.assertRaises(
3188
errors.ParseConfigError, store._load_from_string,
3189
'this is invalid !')
3190
self.assertEndsWith(exc.filename, 'foo.conf')
3191
# And the load failed
3192
self.assertEqual(False, store.is_loaded())
3194
def test_get_embedded_sections(self):
3195
# A more complicated example (which also shows that section names and
3196
# option names share the same name space...)
3197
# FIXME: This should be fixed by forbidding dicts as values ?
3198
# -- vila 2011-04-05
3199
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3200
store._load_from_string('''
3204
foo_in_DEFAULT=foo_DEFAULT
3212
sections = list(store.get_sections())
3213
self.assertLength(4, sections)
3214
# The default section has no name.
3215
# List values are provided as strings and need to be explicitly
3216
# converted by specifying from_unicode=list_from_store at option
3218
self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
3220
self.assertSectionContent(
3221
('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
3222
self.assertSectionContent(
3223
('bar', {'foo_in_bar': 'barbar'}), sections[2])
3224
# sub sections are provided as embedded dicts.
3225
self.assertSectionContent(
3226
('baz', {'foo_in_baz': 'barbaz', 'qux': {'foo_in_qux': 'quux'}}),
3230
class TestLockableIniFileStore(TestStore):
3232
def test_create_store_in_created_dir(self):
3233
self.assertPathDoesNotExist('dir')
3234
t = self.get_transport('dir/subdir')
3235
store = config.LockableIniFileStore(t, 'foo.conf')
3236
store.get_mutable_section(None).set('foo', 'bar')
3238
self.assertPathExists('dir/subdir')
3241
class TestConcurrentStoreUpdates(TestStore):
3242
"""Test that Stores properly handle conccurent updates.
3244
New Store implementation may fail some of these tests but until such
3245
implementations exist it's hard to properly filter them from the scenarios
3246
applied here. If you encounter such a case, contact the bzr devs.
3249
scenarios = [(key, {'get_stack': builder}) for key, builder
3250
in config.test_stack_builder_registry.iteritems()]
3253
super(TestConcurrentStoreUpdates, self).setUp()
3254
self.stack = self.get_stack(self)
3255
if not isinstance(self.stack, config._CompatibleStack):
3256
raise tests.TestNotApplicable(
3257
'%s is not meant to be compatible with the old config design'
3259
self.stack.set('one', '1')
3260
self.stack.set('two', '2')
3262
self.stack.store.save()
3264
def test_simple_read_access(self):
3265
self.assertEqual('1', self.stack.get('one'))
3267
def test_simple_write_access(self):
3268
self.stack.set('one', 'one')
3269
self.assertEqual('one', self.stack.get('one'))
3271
def test_listen_to_the_last_speaker(self):
3273
c2 = self.get_stack(self)
3274
c1.set('one', 'ONE')
3275
c2.set('two', 'TWO')
3276
self.assertEqual('ONE', c1.get('one'))
3277
self.assertEqual('TWO', c2.get('two'))
3278
# The second update respect the first one
3279
self.assertEqual('ONE', c2.get('one'))
3281
def test_last_speaker_wins(self):
3282
# If the same config is not shared, the same variable modified twice
3283
# can only see a single result.
3285
c2 = self.get_stack(self)
3288
self.assertEqual('c2', c2.get('one'))
3289
# The first modification is still available until another refresh
3291
self.assertEqual('c1', c1.get('one'))
3292
c1.set('two', 'done')
3293
self.assertEqual('c2', c1.get('one'))
3295
def test_writes_are_serialized(self):
3297
c2 = self.get_stack(self)
3299
# We spawn a thread that will pause *during* the config saving.
3300
before_writing = threading.Event()
3301
after_writing = threading.Event()
3302
writing_done = threading.Event()
3303
c1_save_without_locking_orig = c1.store.save_without_locking
3304
def c1_save_without_locking():
3305
before_writing.set()
3306
c1_save_without_locking_orig()
3307
# The lock is held. We wait for the main thread to decide when to
3309
after_writing.wait()
3310
c1.store.save_without_locking = c1_save_without_locking
3314
t1 = threading.Thread(target=c1_set)
3315
# Collect the thread after the test
3316
self.addCleanup(t1.join)
3317
# Be ready to unblock the thread if the test goes wrong
3318
self.addCleanup(after_writing.set)
3320
before_writing.wait()
3321
self.assertRaises(errors.LockContention,
3322
c2.set, 'one', 'c2')
3323
self.assertEqual('c1', c1.get('one'))
3324
# Let the lock be released
3328
self.assertEqual('c2', c2.get('one'))
3330
def test_read_while_writing(self):
3332
# We spawn a thread that will pause *during* the write
3333
ready_to_write = threading.Event()
3334
do_writing = threading.Event()
3335
writing_done = threading.Event()
3336
# We override the _save implementation so we know the store is locked
3337
c1_save_without_locking_orig = c1.store.save_without_locking
3338
def c1_save_without_locking():
3339
ready_to_write.set()
3340
# The lock is held. We wait for the main thread to decide when to
3343
c1_save_without_locking_orig()
3345
c1.store.save_without_locking = c1_save_without_locking
3348
t1 = threading.Thread(target=c1_set)
3349
# Collect the thread after the test
3350
self.addCleanup(t1.join)
3351
# Be ready to unblock the thread if the test goes wrong
3352
self.addCleanup(do_writing.set)
3354
# Ensure the thread is ready to write
3355
ready_to_write.wait()
3356
self.assertEqual('c1', c1.get('one'))
3357
# If we read during the write, we get the old value
3358
c2 = self.get_stack(self)
3359
self.assertEqual('1', c2.get('one'))
3360
# Let the writing occur and ensure it occurred
3363
# Now we get the updated value
3364
c3 = self.get_stack(self)
3365
self.assertEqual('c1', c3.get('one'))
3367
# FIXME: It may be worth looking into removing the lock dir when it's not
3368
# needed anymore and look at possible fallouts for concurrent lockers. This
3369
# will matter if/when we use config files outside of bazaar directories
3370
# (.bazaar or .bzr) -- vila 20110-04-111
3373
class TestSectionMatcher(TestStore):
3375
scenarios = [('location', {'matcher': config.LocationMatcher}),
3376
('id', {'matcher': config.NameMatcher}),]
3379
super(TestSectionMatcher, self).setUp()
3380
# Any simple store is good enough
3381
self.get_store = config.test_store_builder_registry.get('configobj')
3383
def test_no_matches_for_empty_stores(self):
3384
store = self.get_store(self)
3385
store._load_from_string('')
3386
matcher = self.matcher(store, '/bar')
3387
self.assertEqual([], list(matcher.get_sections()))
3389
def test_build_doesnt_load_store(self):
3390
store = self.get_store(self)
3391
self.matcher(store, '/bar')
3392
self.assertFalse(store.is_loaded())
3395
class TestLocationSection(tests.TestCase):
3397
def get_section(self, options, extra_path):
3398
section = config.Section('foo', options)
3399
return config.LocationSection(section, extra_path)
3401
def test_simple_option(self):
3402
section = self.get_section({'foo': 'bar'}, '')
3403
self.assertEqual('bar', section.get('foo'))
3405
def test_option_with_extra_path(self):
3406
section = self.get_section({'foo': 'bar', 'foo:policy': 'appendpath'},
3408
self.assertEqual('bar/baz', section.get('foo'))
3410
def test_invalid_policy(self):
3411
section = self.get_section({'foo': 'bar', 'foo:policy': 'die'},
3413
# invalid policies are ignored
3414
self.assertEqual('bar', section.get('foo'))
3417
class TestLocationMatcher(TestStore):
3420
super(TestLocationMatcher, self).setUp()
3421
# Any simple store is good enough
3422
self.get_store = config.test_store_builder_registry.get('configobj')
3424
def test_unrelated_section_excluded(self):
3425
store = self.get_store(self)
3426
store._load_from_string('''
3434
section=/foo/bar/baz
3438
self.assertEqual(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
3440
[section.id for _, section in store.get_sections()])
3441
matcher = config.LocationMatcher(store, '/foo/bar/quux')
3442
sections = [section for _, section in matcher.get_sections()]
3443
self.assertEqual(['/foo/bar', '/foo'],
3444
[section.id for section in sections])
3445
self.assertEqual(['quux', 'bar/quux'],
3446
[section.extra_path for section in sections])
3448
def test_more_specific_sections_first(self):
3449
store = self.get_store(self)
3450
store._load_from_string('''
3456
self.assertEqual(['/foo', '/foo/bar'],
3457
[section.id for _, section in store.get_sections()])
3458
matcher = config.LocationMatcher(store, '/foo/bar/baz')
3459
sections = [section for _, section in matcher.get_sections()]
3460
self.assertEqual(['/foo/bar', '/foo'],
3461
[section.id for section in sections])
3462
self.assertEqual(['baz', 'bar/baz'],
3463
[section.extra_path for section in sections])
3465
def test_appendpath_in_no_name_section(self):
3466
# It's a bit weird to allow appendpath in a no-name section, but
3467
# someone may found a use for it
3468
store = self.get_store(self)
3469
store._load_from_string('''
3471
foo:policy = appendpath
3473
matcher = config.LocationMatcher(store, 'dir/subdir')
3474
sections = list(matcher.get_sections())
3475
self.assertLength(1, sections)
3476
self.assertEqual('bar/dir/subdir', sections[0][1].get('foo'))
3478
def test_file_urls_are_normalized(self):
3479
store = self.get_store(self)
3480
if sys.platform == 'win32':
3481
expected_url = 'file:///C:/dir/subdir'
3482
expected_location = 'C:/dir/subdir'
3484
expected_url = 'file:///dir/subdir'
3485
expected_location = '/dir/subdir'
3486
matcher = config.LocationMatcher(store, expected_url)
3487
self.assertEqual(expected_location, matcher.location)
3489
def test_branch_name_colo(self):
3490
store = self.get_store(self)
3491
store._load_from_string(dedent("""\
3493
push_location=my{branchname}
3495
matcher = config.LocationMatcher(store, 'file:///,branch=example%3c')
3496
self.assertEqual('example<', matcher.branch_name)
3497
((_, section),) = matcher.get_sections()
3498
self.assertEqual('example<', section.locals['branchname'])
3500
def test_branch_name_basename(self):
3501
store = self.get_store(self)
3502
store._load_from_string(dedent("""\
3504
push_location=my{branchname}
3506
matcher = config.LocationMatcher(store, 'file:///parent/example%3c')
3507
self.assertEqual('example<', matcher.branch_name)
3508
((_, section),) = matcher.get_sections()
3509
self.assertEqual('example<', section.locals['branchname'])
3512
class TestStartingPathMatcher(TestStore):
3515
super(TestStartingPathMatcher, self).setUp()
3516
# Any simple store is good enough
3517
self.store = config.IniFileStore()
3519
def assertSectionIDs(self, expected, location, content):
3520
self.store._load_from_string(content)
3521
matcher = config.StartingPathMatcher(self.store, location)
3522
sections = list(matcher.get_sections())
3523
self.assertLength(len(expected), sections)
3524
self.assertEqual(expected, [section.id for _, section in sections])
3527
def test_empty(self):
3528
self.assertSectionIDs([], self.get_url(), '')
3530
def test_url_vs_local_paths(self):
3531
# The matcher location is an url and the section names are local paths
3532
self.assertSectionIDs(['/foo/bar', '/foo'],
3533
'file:///foo/bar/baz', '''\
3538
def test_local_path_vs_url(self):
3539
# The matcher location is a local path and the section names are urls
3540
self.assertSectionIDs(['file:///foo/bar', 'file:///foo'],
3541
'/foo/bar/baz', '''\
3547
def test_no_name_section_included_when_present(self):
3548
# Note that other tests will cover the case where the no-name section
3549
# is empty and as such, not included.
3550
sections = self.assertSectionIDs(['/foo/bar', '/foo', None],
3551
'/foo/bar/baz', '''\
3552
option = defined so the no-name section exists
3556
self.assertEqual(['baz', 'bar/baz', '/foo/bar/baz'],
3557
[s.locals['relpath'] for _, s in sections])
3559
def test_order_reversed(self):
3560
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3565
def test_unrelated_section_excluded(self):
3566
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3572
def test_glob_included(self):
3573
sections = self.assertSectionIDs(['/foo/*/baz', '/foo/b*', '/foo'],
3574
'/foo/bar/baz', '''\
3580
# Note that 'baz' as a relpath for /foo/b* is not fully correct, but
3581
# nothing really is... as far using {relpath} to append it to something
3582
# else, this seems good enough though.
3583
self.assertEqual(['', 'baz', 'bar/baz'],
3584
[s.locals['relpath'] for _, s in sections])
3586
def test_respect_order(self):
3587
self.assertSectionIDs(['/foo', '/foo/b*', '/foo/*/baz'],
3588
'/foo/bar/baz', '''\
3596
class TestNameMatcher(TestStore):
3599
super(TestNameMatcher, self).setUp()
3600
self.matcher = config.NameMatcher
3601
# Any simple store is good enough
3602
self.get_store = config.test_store_builder_registry.get('configobj')
3604
def get_matching_sections(self, name):
3605
store = self.get_store(self)
3606
store._load_from_string('''
3614
matcher = self.matcher(store, name)
3615
return list(matcher.get_sections())
3617
def test_matching(self):
3618
sections = self.get_matching_sections('foo')
3619
self.assertLength(1, sections)
3620
self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
3622
def test_not_matching(self):
3623
sections = self.get_matching_sections('baz')
3624
self.assertLength(0, sections)
3627
class TestBaseStackGet(tests.TestCase):
3630
super(TestBaseStackGet, self).setUp()
3631
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3633
def test_get_first_definition(self):
3634
store1 = config.IniFileStore()
3635
store1._load_from_string('foo=bar')
3636
store2 = config.IniFileStore()
3637
store2._load_from_string('foo=baz')
3638
conf = config.Stack([store1.get_sections, store2.get_sections])
3639
self.assertEqual('bar', conf.get('foo'))
3641
def test_get_with_registered_default_value(self):
3642
config.option_registry.register(config.Option('foo', default='bar'))
3643
conf_stack = config.Stack([])
3644
self.assertEqual('bar', conf_stack.get('foo'))
3646
def test_get_without_registered_default_value(self):
3647
config.option_registry.register(config.Option('foo'))
3648
conf_stack = config.Stack([])
3649
self.assertEqual(None, conf_stack.get('foo'))
3651
def test_get_without_default_value_for_not_registered(self):
3652
conf_stack = config.Stack([])
3653
self.assertEqual(None, conf_stack.get('foo'))
3655
def test_get_for_empty_section_callable(self):
3656
conf_stack = config.Stack([lambda : []])
3657
self.assertEqual(None, conf_stack.get('foo'))
3659
def test_get_for_broken_callable(self):
3660
# Trying to use and invalid callable raises an exception on first use
3661
conf_stack = config.Stack([object])
3662
self.assertRaises(TypeError, conf_stack.get, 'foo')
3665
class TestStackWithSimpleStore(tests.TestCase):
3668
super(TestStackWithSimpleStore, self).setUp()
3669
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3670
self.registry = config.option_registry
3672
def get_conf(self, content=None):
3673
return config.MemoryStack(content)
3675
def test_override_value_from_env(self):
3676
self.overrideEnv('FOO', None)
3677
self.registry.register(
3678
config.Option('foo', default='bar', override_from_env=['FOO']))
3679
self.overrideEnv('FOO', 'quux')
3680
# Env variable provides a default taking over the option one
3681
conf = self.get_conf('foo=store')
3682
self.assertEqual('quux', conf.get('foo'))
3684
def test_first_override_value_from_env_wins(self):
3685
self.overrideEnv('NO_VALUE', None)
3686
self.overrideEnv('FOO', None)
3687
self.overrideEnv('BAZ', None)
3688
self.registry.register(
3689
config.Option('foo', default='bar',
3690
override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
3691
self.overrideEnv('FOO', 'foo')
3692
self.overrideEnv('BAZ', 'baz')
3693
# The first env var set wins
3694
conf = self.get_conf('foo=store')
3695
self.assertEqual('foo', conf.get('foo'))
3698
class TestMemoryStack(tests.TestCase):
3701
conf = config.MemoryStack('foo=bar')
3702
self.assertEqual('bar', conf.get('foo'))
3705
conf = config.MemoryStack('foo=bar')
3706
conf.set('foo', 'baz')
3707
self.assertEqual('baz', conf.get('foo'))
3709
def test_no_content(self):
3710
conf = config.MemoryStack()
3711
# No content means no loading
3712
self.assertFalse(conf.store.is_loaded())
3713
self.assertRaises(NotImplementedError, conf.get, 'foo')
3714
# But a content can still be provided
3715
conf.store._load_from_string('foo=bar')
3716
self.assertEqual('bar', conf.get('foo'))
3719
class TestStackIterSections(tests.TestCase):
3721
def test_empty_stack(self):
3722
conf = config.Stack([])
3723
sections = list(conf.iter_sections())
3724
self.assertLength(0, sections)
3726
def test_empty_store(self):
3727
store = config.IniFileStore()
3728
store._load_from_string('')
3729
conf = config.Stack([store.get_sections])
3730
sections = list(conf.iter_sections())
3731
self.assertLength(0, sections)
3733
def test_simple_store(self):
3734
store = config.IniFileStore()
3735
store._load_from_string('foo=bar')
3736
conf = config.Stack([store.get_sections])
3737
tuples = list(conf.iter_sections())
3738
self.assertLength(1, tuples)
3739
(found_store, found_section) = tuples[0]
3740
self.assertIs(store, found_store)
3742
def test_two_stores(self):
3743
store1 = config.IniFileStore()
3744
store1._load_from_string('foo=bar')
3745
store2 = config.IniFileStore()
3746
store2._load_from_string('bar=qux')
3747
conf = config.Stack([store1.get_sections, store2.get_sections])
3748
tuples = list(conf.iter_sections())
3749
self.assertLength(2, tuples)
3750
self.assertIs(store1, tuples[0][0])
3751
self.assertIs(store2, tuples[1][0])
3754
class TestStackWithTransport(tests.TestCaseWithTransport):
3756
scenarios = [(key, {'get_stack': builder}) for key, builder
3757
in config.test_stack_builder_registry.iteritems()]
3760
class TestConcreteStacks(TestStackWithTransport):
3762
def test_build_stack(self):
3763
# Just a smoke test to help debug builders
3764
self.get_stack(self)
3767
class TestStackGet(TestStackWithTransport):
3770
super(TestStackGet, self).setUp()
3771
self.conf = self.get_stack(self)
3773
def test_get_for_empty_stack(self):
3774
self.assertEqual(None, self.conf.get('foo'))
3776
def test_get_hook(self):
3777
self.conf.set('foo', 'bar')
3781
config.ConfigHooks.install_named_hook('get', hook, None)
3782
self.assertLength(0, calls)
3783
value = self.conf.get('foo')
3784
self.assertEqual('bar', value)
3785
self.assertLength(1, calls)
3786
self.assertEqual((self.conf, 'foo', 'bar'), calls[0])
3789
class TestStackGetWithConverter(tests.TestCase):
3792
super(TestStackGetWithConverter, self).setUp()
3793
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3794
self.registry = config.option_registry
3796
def get_conf(self, content=None):
3797
return config.MemoryStack(content)
3799
def register_bool_option(self, name, default=None, default_from_env=None):
3800
b = config.Option(name, help='A boolean.',
3801
default=default, default_from_env=default_from_env,
3802
from_unicode=config.bool_from_store)
3803
self.registry.register(b)
3805
def test_get_default_bool_None(self):
3806
self.register_bool_option('foo')
3807
conf = self.get_conf('')
3808
self.assertEqual(None, conf.get('foo'))
3810
def test_get_default_bool_True(self):
3811
self.register_bool_option('foo', u'True')
3812
conf = self.get_conf('')
3813
self.assertEqual(True, conf.get('foo'))
3815
def test_get_default_bool_False(self):
3816
self.register_bool_option('foo', False)
3817
conf = self.get_conf('')
3818
self.assertEqual(False, conf.get('foo'))
3820
def test_get_default_bool_False_as_string(self):
3821
self.register_bool_option('foo', u'False')
3822
conf = self.get_conf('')
3823
self.assertEqual(False, conf.get('foo'))
3825
def test_get_default_bool_from_env_converted(self):
3826
self.register_bool_option('foo', u'True', default_from_env=['FOO'])
3827
self.overrideEnv('FOO', 'False')
3828
conf = self.get_conf('')
3829
self.assertEqual(False, conf.get('foo'))
3831
def test_get_default_bool_when_conversion_fails(self):
3832
self.register_bool_option('foo', default='True')
3833
conf = self.get_conf('foo=invalid boolean')
3834
self.assertEqual(True, conf.get('foo'))
3836
def register_integer_option(self, name,
3837
default=None, default_from_env=None):
3838
i = config.Option(name, help='An integer.',
3839
default=default, default_from_env=default_from_env,
3840
from_unicode=config.int_from_store)
3841
self.registry.register(i)
3843
def test_get_default_integer_None(self):
3844
self.register_integer_option('foo')
3845
conf = self.get_conf('')
3846
self.assertEqual(None, conf.get('foo'))
3848
def test_get_default_integer(self):
3849
self.register_integer_option('foo', 42)
3850
conf = self.get_conf('')
3851
self.assertEqual(42, conf.get('foo'))
3853
def test_get_default_integer_as_string(self):
3854
self.register_integer_option('foo', u'42')
3855
conf = self.get_conf('')
3856
self.assertEqual(42, conf.get('foo'))
3858
def test_get_default_integer_from_env(self):
3859
self.register_integer_option('foo', default_from_env=['FOO'])
3860
self.overrideEnv('FOO', '18')
3861
conf = self.get_conf('')
3862
self.assertEqual(18, conf.get('foo'))
3864
def test_get_default_integer_when_conversion_fails(self):
3865
self.register_integer_option('foo', default='12')
3866
conf = self.get_conf('foo=invalid integer')
3867
self.assertEqual(12, conf.get('foo'))
3869
def register_list_option(self, name, default=None, default_from_env=None):
3870
l = config.ListOption(name, help='A list.', default=default,
3871
default_from_env=default_from_env)
3872
self.registry.register(l)
3874
def test_get_default_list_None(self):
3875
self.register_list_option('foo')
3876
conf = self.get_conf('')
3877
self.assertEqual(None, conf.get('foo'))
3879
def test_get_default_list_empty(self):
3880
self.register_list_option('foo', '')
3881
conf = self.get_conf('')
3882
self.assertEqual([], conf.get('foo'))
3884
def test_get_default_list_from_env(self):
3885
self.register_list_option('foo', default_from_env=['FOO'])
3886
self.overrideEnv('FOO', '')
3887
conf = self.get_conf('')
3888
self.assertEqual([], conf.get('foo'))
3890
def test_get_with_list_converter_no_item(self):
3891
self.register_list_option('foo', None)
3892
conf = self.get_conf('foo=,')
3893
self.assertEqual([], conf.get('foo'))
3895
def test_get_with_list_converter_many_items(self):
3896
self.register_list_option('foo', None)
3897
conf = self.get_conf('foo=m,o,r,e')
3898
self.assertEqual(['m', 'o', 'r', 'e'], conf.get('foo'))
3900
def test_get_with_list_converter_embedded_spaces_many_items(self):
3901
self.register_list_option('foo', None)
3902
conf = self.get_conf('foo=" bar", "baz "')
3903
self.assertEqual([' bar', 'baz '], conf.get('foo'))
3905
def test_get_with_list_converter_stripped_spaces_many_items(self):
3906
self.register_list_option('foo', None)
3907
conf = self.get_conf('foo= bar , baz ')
3908
self.assertEqual(['bar', 'baz'], conf.get('foo'))
3911
class TestIterOptionRefs(tests.TestCase):
3912
"""iter_option_refs is a bit unusual, document some cases."""
3914
def assertRefs(self, expected, string):
3915
self.assertEqual(expected, list(config.iter_option_refs(string)))
3917
def test_empty(self):
3918
self.assertRefs([(False, '')], '')
3920
def test_no_refs(self):
3921
self.assertRefs([(False, 'foo bar')], 'foo bar')
3923
def test_single_ref(self):
3924
self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
3926
def test_broken_ref(self):
3927
self.assertRefs([(False, '{foo')], '{foo')
3929
def test_embedded_ref(self):
3930
self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
3933
def test_two_refs(self):
3934
self.assertRefs([(False, ''), (True, '{foo}'),
3935
(False, ''), (True, '{bar}'),
3939
def test_newline_in_refs_are_not_matched(self):
3940
self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
3943
class TestStackExpandOptions(tests.TestCaseWithTransport):
3946
super(TestStackExpandOptions, self).setUp()
3947
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3948
self.registry = config.option_registry
3949
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3950
self.conf = config.Stack([store.get_sections], store)
3952
def assertExpansion(self, expected, string, env=None):
3953
self.assertEqual(expected, self.conf.expand_options(string, env))
3955
def test_no_expansion(self):
3956
self.assertExpansion('foo', 'foo')
3958
def test_expand_default_value(self):
3959
self.conf.store._load_from_string('bar=baz')
3960
self.registry.register(config.Option('foo', default=u'{bar}'))
3961
self.assertEqual('baz', self.conf.get('foo', expand=True))
3963
def test_expand_default_from_env(self):
3964
self.conf.store._load_from_string('bar=baz')
3965
self.registry.register(config.Option('foo', default_from_env=['FOO']))
3966
self.overrideEnv('FOO', '{bar}')
3967
self.assertEqual('baz', self.conf.get('foo', expand=True))
3969
def test_expand_default_on_failed_conversion(self):
3970
self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
3971
self.registry.register(
3972
config.Option('foo', default=u'{bar}',
3973
from_unicode=config.int_from_store))
3974
self.assertEqual(42, self.conf.get('foo', expand=True))
3976
def test_env_adding_options(self):
3977
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3979
def test_env_overriding_options(self):
3980
self.conf.store._load_from_string('foo=baz')
3981
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3983
def test_simple_ref(self):
3984
self.conf.store._load_from_string('foo=xxx')
3985
self.assertExpansion('xxx', '{foo}')
3987
def test_unknown_ref(self):
3988
self.assertRaises(errors.ExpandingUnknownOption,
3989
self.conf.expand_options, '{foo}')
3991
def test_illegal_def_is_ignored(self):
3992
self.assertExpansion('{1,2}', '{1,2}')
3993
self.assertExpansion('{ }', '{ }')
3994
self.assertExpansion('${Foo,f}', '${Foo,f}')
3996
def test_indirect_ref(self):
3997
self.conf.store._load_from_string('''
4001
self.assertExpansion('xxx', '{bar}')
4003
def test_embedded_ref(self):
4004
self.conf.store._load_from_string('''
4008
self.assertExpansion('xxx', '{{bar}}')
4010
def test_simple_loop(self):
4011
self.conf.store._load_from_string('foo={foo}')
4012
self.assertRaises(errors.OptionExpansionLoop,
4013
self.conf.expand_options, '{foo}')
4015
def test_indirect_loop(self):
4016
self.conf.store._load_from_string('''
4020
e = self.assertRaises(errors.OptionExpansionLoop,
4021
self.conf.expand_options, '{foo}')
4022
self.assertEqual('foo->bar->baz', e.refs)
4023
self.assertEqual('{foo}', e.string)
4025
def test_list(self):
4026
self.conf.store._load_from_string('''
4030
list={foo},{bar},{baz}
4032
self.registry.register(
4033
config.ListOption('list'))
4034
self.assertEqual(['start', 'middle', 'end'],
4035
self.conf.get('list', expand=True))
4037
def test_cascading_list(self):
4038
self.conf.store._load_from_string('''
4044
self.registry.register(config.ListOption('list'))
4045
# Register an intermediate option as a list to ensure no conversion
4046
# happen while expanding. Conversion should only occur for the original
4047
# option ('list' here).
4048
self.registry.register(config.ListOption('baz'))
4049
self.assertEqual(['start', 'middle', 'end'],
4050
self.conf.get('list', expand=True))
4052
def test_pathologically_hidden_list(self):
4053
self.conf.store._load_from_string('''
4059
hidden={start}{middle}{end}
4061
# What matters is what the registration says, the conversion happens
4062
# only after all expansions have been performed
4063
self.registry.register(config.ListOption('hidden'))
4064
self.assertEqual(['bin', 'go'],
4065
self.conf.get('hidden', expand=True))
4068
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
4071
super(TestStackCrossSectionsExpand, self).setUp()
4073
def get_config(self, location, string):
4076
# Since we don't save the config we won't strictly require to inherit
4077
# from TestCaseInTempDir, but an error occurs so quickly...
4078
c = config.LocationStack(location)
4079
c.store._load_from_string(string)
4082
def test_dont_cross_unrelated_section(self):
4083
c = self.get_config('/another/branch/path','''
4088
[/another/branch/path]
4091
self.assertRaises(errors.ExpandingUnknownOption,
4092
c.get, 'bar', expand=True)
4094
def test_cross_related_sections(self):
4095
c = self.get_config('/project/branch/path','''
4099
[/project/branch/path]
4102
self.assertEqual('quux', c.get('bar', expand=True))
4105
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
4107
def test_cross_global_locations(self):
4108
l_store = config.LocationStore()
4109
l_store._load_from_string('''
4115
g_store = config.GlobalStore()
4116
g_store._load_from_string('''
4122
stack = config.LocationStack('/branch')
4123
self.assertEqual('glob-bar', stack.get('lbar', expand=True))
4124
self.assertEqual('loc-foo', stack.get('gfoo', expand=True))
4127
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
4129
def test_expand_locals_empty(self):
4130
l_store = config.LocationStore()
4131
l_store._load_from_string('''
4132
[/home/user/project]
4137
stack = config.LocationStack('/home/user/project/')
4138
self.assertEqual('', stack.get('base', expand=True))
4139
self.assertEqual('', stack.get('rel', expand=True))
4141
def test_expand_basename_locally(self):
4142
l_store = config.LocationStore()
4143
l_store._load_from_string('''
4144
[/home/user/project]
4148
stack = config.LocationStack('/home/user/project/branch')
4149
self.assertEqual('branch', stack.get('bfoo', expand=True))
4151
def test_expand_basename_locally_longer_path(self):
4152
l_store = config.LocationStore()
4153
l_store._load_from_string('''
4158
stack = config.LocationStack('/home/user/project/dir/branch')
4159
self.assertEqual('branch', stack.get('bfoo', expand=True))
4161
def test_expand_relpath_locally(self):
4162
l_store = config.LocationStore()
4163
l_store._load_from_string('''
4164
[/home/user/project]
4165
lfoo = loc-foo/{relpath}
4168
stack = config.LocationStack('/home/user/project/branch')
4169
self.assertEqual('loc-foo/branch', stack.get('lfoo', expand=True))
4171
def test_expand_relpath_unknonw_in_global(self):
4172
g_store = config.GlobalStore()
4173
g_store._load_from_string('''
4178
stack = config.LocationStack('/home/user/project/branch')
4179
self.assertRaises(errors.ExpandingUnknownOption,
4180
stack.get, 'gfoo', expand=True)
4182
def test_expand_local_option_locally(self):
4183
l_store = config.LocationStore()
4184
l_store._load_from_string('''
4185
[/home/user/project]
4186
lfoo = loc-foo/{relpath}
4190
g_store = config.GlobalStore()
4191
g_store._load_from_string('''
4197
stack = config.LocationStack('/home/user/project/branch')
4198
self.assertEqual('glob-bar', stack.get('lbar', expand=True))
4199
self.assertEqual('loc-foo/branch', stack.get('gfoo', expand=True))
4201
def test_locals_dont_leak(self):
4202
"""Make sure we chose the right local in presence of several sections.
4204
l_store = config.LocationStore()
4205
l_store._load_from_string('''
4207
lfoo = loc-foo/{relpath}
4208
[/home/user/project]
4209
lfoo = loc-foo/{relpath}
4212
stack = config.LocationStack('/home/user/project/branch')
4213
self.assertEqual('loc-foo/branch', stack.get('lfoo', expand=True))
4214
stack = config.LocationStack('/home/user/bar/baz')
4215
self.assertEqual('loc-foo/bar/baz', stack.get('lfoo', expand=True))
4219
class TestStackSet(TestStackWithTransport):
4221
def test_simple_set(self):
4222
conf = self.get_stack(self)
4223
self.assertEqual(None, conf.get('foo'))
4224
conf.set('foo', 'baz')
4225
# Did we get it back ?
4226
self.assertEqual('baz', conf.get('foo'))
4228
def test_set_creates_a_new_section(self):
4229
conf = self.get_stack(self)
4230
conf.set('foo', 'baz')
4231
self.assertEqual, 'baz', conf.get('foo')
4233
def test_set_hook(self):
4237
config.ConfigHooks.install_named_hook('set', hook, None)
4238
self.assertLength(0, calls)
4239
conf = self.get_stack(self)
4240
conf.set('foo', 'bar')
4241
self.assertLength(1, calls)
4242
self.assertEqual((conf, 'foo', 'bar'), calls[0])
4245
class TestStackRemove(TestStackWithTransport):
4247
def test_remove_existing(self):
4248
conf = self.get_stack(self)
4249
conf.set('foo', 'bar')
4250
self.assertEqual('bar', conf.get('foo'))
4252
# Did we get it back ?
4253
self.assertEqual(None, conf.get('foo'))
4255
def test_remove_unknown(self):
4256
conf = self.get_stack(self)
4257
self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
4259
def test_remove_hook(self):
4263
config.ConfigHooks.install_named_hook('remove', hook, None)
4264
self.assertLength(0, calls)
4265
conf = self.get_stack(self)
4266
conf.set('foo', 'bar')
4268
self.assertLength(1, calls)
4269
self.assertEqual((conf, 'foo'), calls[0])
4272
1562
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
4274
1564
def setUp(self):
4275
1565
super(TestConfigGetOptions, self).setUp()
4276
1566
create_configs(self)
1568
# One variable in none of the above
4278
1569
def test_no_variable(self):
4279
1570
# Using branch should query branch, locations and bazaar
4280
1571
self.assertOptions([], self.branch_config)