1305
1983
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.assertEquals(value, actual_value)
2002
self.assertLength(1, calls)
2003
self.assertEquals((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.assertEquals((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.assertEquals((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.assertEquals(value, actual_value)
2133
self.assertLength(1, calls)
2134
self.assertEquals((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.assertEquals((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'))
2241
def test_invalid_names(self):
2242
self.assertFalse(self.is_valid(' foo'))
2243
self.assertFalse(self.is_valid('foo '))
2244
self.assertFalse(self.is_valid('1'))
2245
self.assertFalse(self.is_valid('1,2'))
2246
self.assertFalse(self.is_valid('foo$'))
2247
self.assertFalse(self.is_valid('!foo'))
2248
self.assertFalse(self.is_valid('foo.'))
2249
self.assertFalse(self.is_valid('foo..bar'))
2250
self.assertFalse(self.is_valid('{}'))
2251
self.assertFalse(self.is_valid('{a}'))
2252
self.assertFalse(self.is_valid('a\n'))
2254
def assertSingleGroup(self, reference):
2255
# the regexp is used with split and as such should match the reference
2256
# *only*, if more groups needs to be defined, (?:...) should be used.
2257
m = config._option_ref_re.match('{a}')
2258
self.assertLength(1, m.groups())
2260
def test_valid_references(self):
2261
self.assertSingleGroup('{a}')
2262
self.assertSingleGroup('{{a}}')
2265
class TestOption(tests.TestCase):
2267
def test_default_value(self):
2268
opt = config.Option('foo', default='bar')
2269
self.assertEquals('bar', opt.get_default())
2271
def test_callable_default_value(self):
2272
def bar_as_unicode():
2274
opt = config.Option('foo', default=bar_as_unicode)
2275
self.assertEquals('bar', opt.get_default())
2277
def test_default_value_from_env(self):
2278
opt = config.Option('foo', default='bar', default_from_env=['FOO'])
2279
self.overrideEnv('FOO', 'quux')
2280
# Env variable provides a default taking over the option one
2281
self.assertEquals('quux', opt.get_default())
2283
def test_first_default_value_from_env_wins(self):
2284
opt = config.Option('foo', default='bar',
2285
default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
2286
self.overrideEnv('FOO', 'foo')
2287
self.overrideEnv('BAZ', 'baz')
2288
# The first env var set wins
2289
self.assertEquals('foo', opt.get_default())
2291
def test_not_supported_list_default_value(self):
2292
self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
2294
def test_not_supported_object_default_value(self):
2295
self.assertRaises(AssertionError, config.Option, 'foo',
2298
def test_not_supported_callable_default_value_not_unicode(self):
2299
def bar_not_unicode():
2301
opt = config.Option('foo', default=bar_not_unicode)
2302
self.assertRaises(AssertionError, opt.get_default)
2304
def test_get_help_topic(self):
2305
opt = config.Option('foo')
2306
self.assertEquals('foo', opt.get_help_topic())
2309
class TestOptionConverter(tests.TestCase):
2311
def assertConverted(self, expected, opt, value):
2312
self.assertEquals(expected, opt.convert_from_unicode(None, value))
2314
def assertCallsWarning(self, opt, value):
2318
warnings.append(args[0] % args[1:])
2319
self.overrideAttr(trace, 'warning', warning)
2320
self.assertEquals(None, opt.convert_from_unicode(None, value))
2321
self.assertLength(1, warnings)
2323
'Value "%s" is not valid for "%s"' % (value, opt.name),
2326
def assertCallsError(self, opt, value):
2327
self.assertRaises(errors.ConfigOptionValueError,
2328
opt.convert_from_unicode, None, value)
2330
def assertConvertInvalid(self, opt, invalid_value):
2332
self.assertEquals(None, opt.convert_from_unicode(None, invalid_value))
2333
opt.invalid = 'warning'
2334
self.assertCallsWarning(opt, invalid_value)
2335
opt.invalid = 'error'
2336
self.assertCallsError(opt, invalid_value)
2339
class TestOptionWithBooleanConverter(TestOptionConverter):
2341
def get_option(self):
2342
return config.Option('foo', help='A boolean.',
2343
from_unicode=config.bool_from_store)
2345
def test_convert_invalid(self):
2346
opt = self.get_option()
2347
# A string that is not recognized as a boolean
2348
self.assertConvertInvalid(opt, u'invalid-boolean')
2349
# A list of strings is never recognized as a boolean
2350
self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
2352
def test_convert_valid(self):
2353
opt = self.get_option()
2354
self.assertConverted(True, opt, u'True')
2355
self.assertConverted(True, opt, u'1')
2356
self.assertConverted(False, opt, u'False')
2359
class TestOptionWithIntegerConverter(TestOptionConverter):
2361
def get_option(self):
2362
return config.Option('foo', help='An integer.',
2363
from_unicode=config.int_from_store)
2365
def test_convert_invalid(self):
2366
opt = self.get_option()
2367
# A string that is not recognized as an integer
2368
self.assertConvertInvalid(opt, u'forty-two')
2369
# A list of strings is never recognized as an integer
2370
self.assertConvertInvalid(opt, [u'a', u'list'])
2372
def test_convert_valid(self):
2373
opt = self.get_option()
2374
self.assertConverted(16, opt, u'16')
2377
class TestOptionWithSIUnitConverter(TestOptionConverter):
2379
def get_option(self):
2380
return config.Option('foo', help='An integer in SI units.',
2381
from_unicode=config.int_SI_from_store)
2383
def test_convert_invalid(self):
2384
opt = self.get_option()
2385
self.assertConvertInvalid(opt, u'not-a-unit')
2386
self.assertConvertInvalid(opt, u'Gb') # Forgot the value
2387
self.assertConvertInvalid(opt, u'1b') # Forgot the unit
2388
self.assertConvertInvalid(opt, u'1GG')
2389
self.assertConvertInvalid(opt, u'1Mbb')
2390
self.assertConvertInvalid(opt, u'1MM')
2392
def test_convert_valid(self):
2393
opt = self.get_option()
2394
self.assertConverted(int(5e3), opt, u'5kb')
2395
self.assertConverted(int(5e6), opt, u'5M')
2396
self.assertConverted(int(5e6), opt, u'5MB')
2397
self.assertConverted(int(5e9), opt, u'5g')
2398
self.assertConverted(int(5e9), opt, u'5gB')
2399
self.assertConverted(100, opt, u'100')
2402
class TestListOption(TestOptionConverter):
2404
def get_option(self):
2405
return config.ListOption('foo', help='A list.')
2407
def test_convert_invalid(self):
2408
opt = self.get_option()
2409
# We don't even try to convert a list into a list, we only expect
2411
self.assertConvertInvalid(opt, [1])
2412
# No string is invalid as all forms can be converted to a list
2414
def test_convert_valid(self):
2415
opt = self.get_option()
2416
# An empty string is an empty list
2417
self.assertConverted([], opt, '') # Using a bare str() just in case
2418
self.assertConverted([], opt, u'')
2420
self.assertConverted([u'True'], opt, u'True')
2422
self.assertConverted([u'42'], opt, u'42')
2424
self.assertConverted([u'bar'], opt, u'bar')
2427
class TestRegistryOption(TestOptionConverter):
2429
def get_option(self, registry):
2430
return config.RegistryOption('foo', registry,
2431
help='A registry option.')
2433
def test_convert_invalid(self):
2434
registry = _mod_registry.Registry()
2435
opt = self.get_option(registry)
2436
self.assertConvertInvalid(opt, [1])
2437
self.assertConvertInvalid(opt, u"notregistered")
2439
def test_convert_valid(self):
2440
registry = _mod_registry.Registry()
2441
registry.register("someval", 1234)
2442
opt = self.get_option(registry)
2443
# Using a bare str() just in case
2444
self.assertConverted(1234, opt, "someval")
2445
self.assertConverted(1234, opt, u'someval')
2446
self.assertConverted(None, opt, None)
2448
def test_help(self):
2449
registry = _mod_registry.Registry()
2450
registry.register("someval", 1234, help="some option")
2451
registry.register("dunno", 1234, help="some other option")
2452
opt = self.get_option(registry)
2454
'A registry option.\n'
2456
'The following values are supported:\n'
2457
' dunno - some other option\n'
2458
' someval - some option\n',
2461
def test_get_help_text(self):
2462
registry = _mod_registry.Registry()
2463
registry.register("someval", 1234, help="some option")
2464
registry.register("dunno", 1234, help="some other option")
2465
opt = self.get_option(registry)
2467
'A registry option.\n'
2469
'The following values are supported:\n'
2470
' dunno - some other option\n'
2471
' someval - some option\n',
2472
opt.get_help_text())
2475
class TestOptionRegistry(tests.TestCase):
2478
super(TestOptionRegistry, self).setUp()
2479
# Always start with an empty registry
2480
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2481
self.registry = config.option_registry
2483
def test_register(self):
2484
opt = config.Option('foo')
2485
self.registry.register(opt)
2486
self.assertIs(opt, self.registry.get('foo'))
2488
def test_registered_help(self):
2489
opt = config.Option('foo', help='A simple option')
2490
self.registry.register(opt)
2491
self.assertEquals('A simple option', self.registry.get_help('foo'))
2493
def test_dont_register_illegal_name(self):
2494
self.assertRaises(errors.IllegalOptionName,
2495
self.registry.register, config.Option(' foo'))
2496
self.assertRaises(errors.IllegalOptionName,
2497
self.registry.register, config.Option('bar,'))
2499
lazy_option = config.Option('lazy_foo', help='Lazy help')
2501
def test_register_lazy(self):
2502
self.registry.register_lazy('lazy_foo', self.__module__,
2503
'TestOptionRegistry.lazy_option')
2504
self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
2506
def test_registered_lazy_help(self):
2507
self.registry.register_lazy('lazy_foo', self.__module__,
2508
'TestOptionRegistry.lazy_option')
2509
self.assertEquals('Lazy help', self.registry.get_help('lazy_foo'))
2511
def test_dont_lazy_register_illegal_name(self):
2512
# This is where the root cause of http://pad.lv/1235099 is better
2513
# understood: 'register_lazy' doc string mentions that key should match
2514
# the option name which indirectly requires that the option name is a
2515
# valid python identifier. We violate that rule here (using a key that
2516
# doesn't match the option name) to test the option name checking.
2517
self.assertRaises(errors.IllegalOptionName,
2518
self.registry.register_lazy, ' foo', self.__module__,
2519
'TestOptionRegistry.lazy_option')
2520
self.assertRaises(errors.IllegalOptionName,
2521
self.registry.register_lazy, '1,2', self.__module__,
2522
'TestOptionRegistry.lazy_option')
2525
class TestRegisteredOptions(tests.TestCase):
2526
"""All registered options should verify some constraints."""
2528
scenarios = [(key, {'option_name': key, 'option': option}) for key, option
2529
in config.option_registry.iteritems()]
2532
super(TestRegisteredOptions, self).setUp()
2533
self.registry = config.option_registry
2535
def test_proper_name(self):
2536
# An option should be registered under its own name, this can't be
2537
# checked at registration time for the lazy ones.
2538
self.assertEquals(self.option_name, self.option.name)
2540
def test_help_is_set(self):
2541
option_help = self.registry.get_help(self.option_name)
2542
# Come on, think about the user, he really wants to know what the
2544
self.assertIsNot(None, option_help)
2545
self.assertNotEquals('', option_help)
2548
class TestSection(tests.TestCase):
2550
# FIXME: Parametrize so that all sections produced by Stores run these
2551
# tests -- vila 2011-04-01
2553
def test_get_a_value(self):
2554
a_dict = dict(foo='bar')
2555
section = config.Section('myID', a_dict)
2556
self.assertEquals('bar', section.get('foo'))
2558
def test_get_unknown_option(self):
2560
section = config.Section(None, a_dict)
2561
self.assertEquals('out of thin air',
2562
section.get('foo', 'out of thin air'))
2564
def test_options_is_shared(self):
2566
section = config.Section(None, a_dict)
2567
self.assertIs(a_dict, section.options)
2570
class TestMutableSection(tests.TestCase):
2572
scenarios = [('mutable',
2574
lambda opts: config.MutableSection('myID', opts)},),
2578
a_dict = dict(foo='bar')
2579
section = self.get_section(a_dict)
2580
section.set('foo', 'new_value')
2581
self.assertEquals('new_value', section.get('foo'))
2582
# The change appears in the shared section
2583
self.assertEquals('new_value', a_dict.get('foo'))
2584
# We keep track of the change
2585
self.assertTrue('foo' in section.orig)
2586
self.assertEquals('bar', section.orig.get('foo'))
2588
def test_set_preserve_original_once(self):
2589
a_dict = dict(foo='bar')
2590
section = self.get_section(a_dict)
2591
section.set('foo', 'first_value')
2592
section.set('foo', 'second_value')
2593
# We keep track of the original value
2594
self.assertTrue('foo' in section.orig)
2595
self.assertEquals('bar', section.orig.get('foo'))
2597
def test_remove(self):
2598
a_dict = dict(foo='bar')
2599
section = self.get_section(a_dict)
2600
section.remove('foo')
2601
# We get None for unknown options via the default value
2602
self.assertEquals(None, section.get('foo'))
2603
# Or we just get the default value
2604
self.assertEquals('unknown', section.get('foo', 'unknown'))
2605
self.assertFalse('foo' in section.options)
2606
# We keep track of the deletion
2607
self.assertTrue('foo' in section.orig)
2608
self.assertEquals('bar', section.orig.get('foo'))
2610
def test_remove_new_option(self):
2612
section = self.get_section(a_dict)
2613
section.set('foo', 'bar')
2614
section.remove('foo')
2615
self.assertFalse('foo' in section.options)
2616
# The option didn't exist initially so it we need to keep track of it
2617
# with a special value
2618
self.assertTrue('foo' in section.orig)
2619
self.assertEquals(config._NewlyCreatedOption, section.orig['foo'])
2622
class TestCommandLineStore(tests.TestCase):
2625
super(TestCommandLineStore, self).setUp()
2626
self.store = config.CommandLineStore()
2627
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2629
def get_section(self):
2630
"""Get the unique section for the command line overrides."""
2631
sections = list(self.store.get_sections())
2632
self.assertLength(1, sections)
2633
store, section = sections[0]
2634
self.assertEquals(self.store, store)
2637
def test_no_override(self):
2638
self.store._from_cmdline([])
2639
section = self.get_section()
2640
self.assertLength(0, list(section.iter_option_names()))
2642
def test_simple_override(self):
2643
self.store._from_cmdline(['a=b'])
2644
section = self.get_section()
2645
self.assertEqual('b', section.get('a'))
2647
def test_list_override(self):
2648
opt = config.ListOption('l')
2649
config.option_registry.register(opt)
2650
self.store._from_cmdline(['l=1,2,3'])
2651
val = self.get_section().get('l')
2652
self.assertEqual('1,2,3', val)
2653
# Reminder: lists should be registered as such explicitely, otherwise
2654
# the conversion needs to be done afterwards.
2655
self.assertEqual(['1', '2', '3'],
2656
opt.convert_from_unicode(self.store, val))
2658
def test_multiple_overrides(self):
2659
self.store._from_cmdline(['a=b', 'x=y'])
2660
section = self.get_section()
2661
self.assertEquals('b', section.get('a'))
2662
self.assertEquals('y', section.get('x'))
2664
def test_wrong_syntax(self):
2665
self.assertRaises(errors.BzrCommandError,
2666
self.store._from_cmdline, ['a=b', 'c'])
2668
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
2670
scenarios = [(key, {'get_store': builder}) for key, builder
2671
in config.test_store_builder_registry.iteritems()] + [
2672
('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
2675
store = self.get_store(self)
2676
if type(store) == config.TransportIniFileStore:
2677
raise tests.TestNotApplicable(
2678
"%s is not a concrete Store implementation"
2679
" so it doesn't need an id" % (store.__class__.__name__,))
2680
self.assertIsNot(None, store.id)
2683
class TestStore(tests.TestCaseWithTransport):
2685
def assertSectionContent(self, expected, (store, section)):
2686
"""Assert that some options have the proper values in a section."""
2687
expected_name, expected_options = expected
2688
self.assertEquals(expected_name, section.id)
2691
dict([(k, section.get(k)) for k in expected_options.keys()]))
2694
class TestReadonlyStore(TestStore):
2696
scenarios = [(key, {'get_store': builder}) for key, builder
2697
in config.test_store_builder_registry.iteritems()]
2699
def test_building_delays_load(self):
2700
store = self.get_store(self)
2701
self.assertEquals(False, store.is_loaded())
2702
store._load_from_string('')
2703
self.assertEquals(True, store.is_loaded())
2705
def test_get_no_sections_for_empty(self):
2706
store = self.get_store(self)
2707
store._load_from_string('')
2708
self.assertEquals([], list(store.get_sections()))
2710
def test_get_default_section(self):
2711
store = self.get_store(self)
2712
store._load_from_string('foo=bar')
2713
sections = list(store.get_sections())
2714
self.assertLength(1, sections)
2715
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2717
def test_get_named_section(self):
2718
store = self.get_store(self)
2719
store._load_from_string('[baz]\nfoo=bar')
2720
sections = list(store.get_sections())
2721
self.assertLength(1, sections)
2722
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2724
def test_load_from_string_fails_for_non_empty_store(self):
2725
store = self.get_store(self)
2726
store._load_from_string('foo=bar')
2727
self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
2730
class TestStoreQuoting(TestStore):
2732
scenarios = [(key, {'get_store': builder}) for key, builder
2733
in config.test_store_builder_registry.iteritems()]
2736
super(TestStoreQuoting, self).setUp()
2737
self.store = self.get_store(self)
2738
# We need a loaded store but any content will do
2739
self.store._load_from_string('')
2741
def assertIdempotent(self, s):
2742
"""Assert that quoting an unquoted string is a no-op and vice-versa.
2744
What matters here is that option values, as they appear in a store, can
2745
be safely round-tripped out of the store and back.
2747
:param s: A string, quoted if required.
2749
self.assertEquals(s, self.store.quote(self.store.unquote(s)))
2750
self.assertEquals(s, self.store.unquote(self.store.quote(s)))
2752
def test_empty_string(self):
2753
if isinstance(self.store, config.IniFileStore):
2754
# configobj._quote doesn't handle empty values
2755
self.assertRaises(AssertionError,
2756
self.assertIdempotent, '')
2758
self.assertIdempotent('')
2759
# But quoted empty strings are ok
2760
self.assertIdempotent('""')
2762
def test_embedded_spaces(self):
2763
self.assertIdempotent('" a b c "')
2765
def test_embedded_commas(self):
2766
self.assertIdempotent('" a , b c "')
2768
def test_simple_comma(self):
2769
if isinstance(self.store, config.IniFileStore):
2770
# configobj requires that lists are special-cased
2771
self.assertRaises(AssertionError,
2772
self.assertIdempotent, ',')
2774
self.assertIdempotent(',')
2775
# When a single comma is required, quoting is also required
2776
self.assertIdempotent('","')
2778
def test_list(self):
2779
if isinstance(self.store, config.IniFileStore):
2780
# configobj requires that lists are special-cased
2781
self.assertRaises(AssertionError,
2782
self.assertIdempotent, 'a,b')
2784
self.assertIdempotent('a,b')
2787
class TestDictFromStore(tests.TestCase):
2789
def test_unquote_not_string(self):
2790
conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
2791
value = conf.get('a_section')
2792
# Urgh, despite 'conf' asking for the no-name section, we get the
2793
# content of another section as a dict o_O
2794
self.assertEquals({'a': '1'}, value)
2795
unquoted = conf.store.unquote(value)
2796
# Which cannot be unquoted but shouldn't crash either (the use cases
2797
# are getting the value or displaying it. In the later case, '%s' will
2799
self.assertEquals({'a': '1'}, unquoted)
2800
self.assertEquals("{u'a': u'1'}", '%s' % (unquoted,))
2803
class TestIniFileStoreContent(tests.TestCaseWithTransport):
2804
"""Simulate loading a config store with content of various encodings.
2806
All files produced by bzr are in utf8 content.
2808
Users may modify them manually and end up with a file that can't be
2809
loaded. We need to issue proper error messages in this case.
2812
invalid_utf8_char = '\xff'
2814
def test_load_utf8(self):
2815
"""Ensure we can load an utf8-encoded file."""
2816
t = self.get_transport()
2817
# From http://pad.lv/799212
2818
unicode_user = u'b\N{Euro Sign}ar'
2819
unicode_content = u'user=%s' % (unicode_user,)
2820
utf8_content = unicode_content.encode('utf8')
2821
# Store the raw content in the config file
2822
t.put_bytes('foo.conf', utf8_content)
2823
store = config.TransportIniFileStore(t, 'foo.conf')
2825
stack = config.Stack([store.get_sections], store)
2826
self.assertEquals(unicode_user, stack.get('user'))
2828
def test_load_non_ascii(self):
2829
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2830
t = self.get_transport()
2831
t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
2832
store = config.TransportIniFileStore(t, 'foo.conf')
2833
self.assertRaises(errors.ConfigContentError, store.load)
2835
def test_load_erroneous_content(self):
2836
"""Ensure we display a proper error on content that can't be parsed."""
2837
t = self.get_transport()
2838
t.put_bytes('foo.conf', '[open_section\n')
2839
store = config.TransportIniFileStore(t, 'foo.conf')
2840
self.assertRaises(errors.ParseConfigError, store.load)
2842
def test_load_permission_denied(self):
2843
"""Ensure we get warned when trying to load an inaccessible file."""
2846
warnings.append(args[0] % args[1:])
2847
self.overrideAttr(trace, 'warning', warning)
2849
t = self.get_transport()
2851
def get_bytes(relpath):
2852
raise errors.PermissionDenied(relpath, "")
2853
t.get_bytes = get_bytes
2854
store = config.TransportIniFileStore(t, 'foo.conf')
2855
self.assertRaises(errors.PermissionDenied, store.load)
2858
[u'Permission denied while trying to load configuration store %s.'
2859
% store.external_url()])
2862
class TestIniConfigContent(tests.TestCaseWithTransport):
2863
"""Simulate loading a IniBasedConfig with content of various encodings.
2865
All files produced by bzr are in utf8 content.
2867
Users may modify them manually and end up with a file that can't be
2868
loaded. We need to issue proper error messages in this case.
2871
invalid_utf8_char = '\xff'
2873
def test_load_utf8(self):
2874
"""Ensure we can load an utf8-encoded file."""
2875
# From http://pad.lv/799212
2876
unicode_user = u'b\N{Euro Sign}ar'
2877
unicode_content = u'user=%s' % (unicode_user,)
2878
utf8_content = unicode_content.encode('utf8')
2879
# Store the raw content in the config file
2880
with open('foo.conf', 'wb') as f:
2881
f.write(utf8_content)
2882
conf = config.IniBasedConfig(file_name='foo.conf')
2883
self.assertEquals(unicode_user, conf.get_user_option('user'))
2885
def test_load_badly_encoded_content(self):
2886
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2887
with open('foo.conf', 'wb') as f:
2888
f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
2889
conf = config.IniBasedConfig(file_name='foo.conf')
2890
self.assertRaises(errors.ConfigContentError, conf._get_parser)
2892
def test_load_erroneous_content(self):
2893
"""Ensure we display a proper error on content that can't be parsed."""
2894
with open('foo.conf', 'wb') as f:
2895
f.write('[open_section\n')
2896
conf = config.IniBasedConfig(file_name='foo.conf')
2897
self.assertRaises(errors.ParseConfigError, conf._get_parser)
2900
class TestMutableStore(TestStore):
2902
scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
2903
in config.test_store_builder_registry.iteritems()]
2906
super(TestMutableStore, self).setUp()
2907
self.transport = self.get_transport()
2909
def has_store(self, store):
2910
store_basename = urlutils.relative_url(self.transport.external_url(),
2911
store.external_url())
2912
return self.transport.has(store_basename)
2914
def test_save_empty_creates_no_file(self):
2915
# FIXME: There should be a better way than relying on the test
2916
# parametrization to identify branch.conf -- vila 2011-0526
2917
if self.store_id in ('branch', 'remote_branch'):
2918
raise tests.TestNotApplicable(
2919
'branch.conf is *always* created when a branch is initialized')
2920
store = self.get_store(self)
2922
self.assertEquals(False, self.has_store(store))
2924
def test_mutable_section_shared(self):
2925
store = self.get_store(self)
2926
store._load_from_string('foo=bar\n')
2927
# FIXME: There should be a better way than relying on the test
2928
# parametrization to identify branch.conf -- vila 2011-0526
2929
if self.store_id in ('branch', 'remote_branch'):
2930
# branch stores requires write locked branches
2931
self.addCleanup(store.branch.lock_write().unlock)
2932
section1 = store.get_mutable_section(None)
2933
section2 = store.get_mutable_section(None)
2934
# If we get different sections, different callers won't share the
2936
self.assertIs(section1, section2)
2938
def test_save_emptied_succeeds(self):
2939
store = self.get_store(self)
2940
store._load_from_string('foo=bar\n')
2941
# FIXME: There should be a better way than relying on the test
2942
# parametrization to identify branch.conf -- vila 2011-0526
2943
if self.store_id in ('branch', 'remote_branch'):
2944
# branch stores requires write locked branches
2945
self.addCleanup(store.branch.lock_write().unlock)
2946
section = store.get_mutable_section(None)
2947
section.remove('foo')
2949
self.assertEquals(True, self.has_store(store))
2950
modified_store = self.get_store(self)
2951
sections = list(modified_store.get_sections())
2952
self.assertLength(0, sections)
2954
def test_save_with_content_succeeds(self):
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
raise tests.TestNotApplicable(
2959
'branch.conf is *always* created when a branch is initialized')
2960
store = self.get_store(self)
2961
store._load_from_string('foo=bar\n')
2962
self.assertEquals(False, self.has_store(store))
2964
self.assertEquals(True, self.has_store(store))
2965
modified_store = self.get_store(self)
2966
sections = list(modified_store.get_sections())
2967
self.assertLength(1, sections)
2968
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2970
def test_set_option_in_empty_store(self):
2971
store = self.get_store(self)
2972
# FIXME: There should be a better way than relying on the test
2973
# parametrization to identify branch.conf -- vila 2011-0526
2974
if self.store_id in ('branch', 'remote_branch'):
2975
# branch stores requires write locked branches
2976
self.addCleanup(store.branch.lock_write().unlock)
2977
section = store.get_mutable_section(None)
2978
section.set('foo', 'bar')
2980
modified_store = self.get_store(self)
2981
sections = list(modified_store.get_sections())
2982
self.assertLength(1, sections)
2983
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2985
def test_set_option_in_default_section(self):
2986
store = self.get_store(self)
2987
store._load_from_string('')
2988
# FIXME: There should be a better way than relying on the test
2989
# parametrization to identify branch.conf -- vila 2011-0526
2990
if self.store_id in ('branch', 'remote_branch'):
2991
# branch stores requires write locked branches
2992
self.addCleanup(store.branch.lock_write().unlock)
2993
section = store.get_mutable_section(None)
2994
section.set('foo', 'bar')
2996
modified_store = self.get_store(self)
2997
sections = list(modified_store.get_sections())
2998
self.assertLength(1, sections)
2999
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
3001
def test_set_option_in_named_section(self):
3002
store = self.get_store(self)
3003
store._load_from_string('')
3004
# FIXME: There should be a better way than relying on the test
3005
# parametrization to identify branch.conf -- vila 2011-0526
3006
if self.store_id in ('branch', 'remote_branch'):
3007
# branch stores requires write locked branches
3008
self.addCleanup(store.branch.lock_write().unlock)
3009
section = store.get_mutable_section('baz')
3010
section.set('foo', 'bar')
3012
modified_store = self.get_store(self)
3013
sections = list(modified_store.get_sections())
3014
self.assertLength(1, sections)
3015
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
3017
def test_load_hook(self):
3018
# First, we need to ensure that the store exists
3019
store = self.get_store(self)
3020
# FIXME: There should be a better way than relying on the test
3021
# parametrization to identify branch.conf -- vila 2011-0526
3022
if self.store_id in ('branch', 'remote_branch'):
3023
# branch stores requires write locked branches
3024
self.addCleanup(store.branch.lock_write().unlock)
3025
section = store.get_mutable_section('baz')
3026
section.set('foo', 'bar')
3028
# Now we can try to load it
3029
store = self.get_store(self)
3033
config.ConfigHooks.install_named_hook('load', hook, None)
3034
self.assertLength(0, calls)
3036
self.assertLength(1, calls)
3037
self.assertEquals((store,), calls[0])
3039
def test_save_hook(self):
3043
config.ConfigHooks.install_named_hook('save', hook, None)
3044
self.assertLength(0, calls)
3045
store = self.get_store(self)
3046
# FIXME: There should be a better way than relying on the test
3047
# parametrization to identify branch.conf -- vila 2011-0526
3048
if self.store_id in ('branch', 'remote_branch'):
3049
# branch stores requires write locked branches
3050
self.addCleanup(store.branch.lock_write().unlock)
3051
section = store.get_mutable_section('baz')
3052
section.set('foo', 'bar')
3054
self.assertLength(1, calls)
3055
self.assertEquals((store,), calls[0])
3057
def test_set_mark_dirty(self):
3058
stack = config.MemoryStack('')
3059
self.assertLength(0, stack.store.dirty_sections)
3060
stack.set('foo', 'baz')
3061
self.assertLength(1, stack.store.dirty_sections)
3062
self.assertTrue(stack.store._need_saving())
3064
def test_remove_mark_dirty(self):
3065
stack = config.MemoryStack('foo=bar')
3066
self.assertLength(0, stack.store.dirty_sections)
3068
self.assertLength(1, stack.store.dirty_sections)
3069
self.assertTrue(stack.store._need_saving())
3072
class TestStoreSaveChanges(tests.TestCaseWithTransport):
3073
"""Tests that config changes are kept in memory and saved on-demand."""
3076
super(TestStoreSaveChanges, self).setUp()
3077
self.transport = self.get_transport()
3078
# Most of the tests involve two stores pointing to the same persistent
3079
# storage to observe the effects of concurrent changes
3080
self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
3081
self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
3084
self.warnings.append(args[0] % args[1:])
3085
self.overrideAttr(trace, 'warning', warning)
3087
def has_store(self, store):
3088
store_basename = urlutils.relative_url(self.transport.external_url(),
3089
store.external_url())
3090
return self.transport.has(store_basename)
3092
def get_stack(self, store):
3093
# Any stack will do as long as it uses the right store, just a single
3094
# no-name section is enough
3095
return config.Stack([store.get_sections], store)
3097
def test_no_changes_no_save(self):
3098
s = self.get_stack(self.st1)
3099
s.store.save_changes()
3100
self.assertEquals(False, self.has_store(self.st1))
3102
def test_unrelated_concurrent_update(self):
3103
s1 = self.get_stack(self.st1)
3104
s2 = self.get_stack(self.st2)
3105
s1.set('foo', 'bar')
3106
s2.set('baz', 'quux')
3108
# Changes don't propagate magically
3109
self.assertEquals(None, s1.get('baz'))
3110
s2.store.save_changes()
3111
self.assertEquals('quux', s2.get('baz'))
3112
# Changes are acquired when saving
3113
self.assertEquals('bar', s2.get('foo'))
3114
# Since there is no overlap, no warnings are emitted
3115
self.assertLength(0, self.warnings)
3117
def test_concurrent_update_modified(self):
3118
s1 = self.get_stack(self.st1)
3119
s2 = self.get_stack(self.st2)
3120
s1.set('foo', 'bar')
3121
s2.set('foo', 'baz')
3124
s2.store.save_changes()
3125
self.assertEquals('baz', s2.get('foo'))
3126
# But the user get a warning
3127
self.assertLength(1, self.warnings)
3128
warning = self.warnings[0]
3129
self.assertStartsWith(warning, 'Option foo in section None')
3130
self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
3131
' The baz value will be saved.')
3133
def test_concurrent_deletion(self):
3134
self.st1._load_from_string('foo=bar')
3136
s1 = self.get_stack(self.st1)
3137
s2 = self.get_stack(self.st2)
3140
s1.store.save_changes()
3142
self.assertLength(0, self.warnings)
3143
s2.store.save_changes()
3145
self.assertLength(1, self.warnings)
3146
warning = self.warnings[0]
3147
self.assertStartsWith(warning, 'Option foo in section None')
3148
self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
3149
' The <DELETED> value will be saved.')
3152
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
3154
def get_store(self):
3155
return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3157
def test_get_quoted_string(self):
3158
store = self.get_store()
3159
store._load_from_string('foo= " abc "')
3160
stack = config.Stack([store.get_sections])
3161
self.assertEquals(' abc ', stack.get('foo'))
3163
def test_set_quoted_string(self):
3164
store = self.get_store()
3165
stack = config.Stack([store.get_sections], store)
3166
stack.set('foo', ' a b c ')
3168
self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
3171
class TestTransportIniFileStore(TestStore):
3173
def test_loading_unknown_file_fails(self):
3174
store = config.TransportIniFileStore(self.get_transport(),
3176
self.assertRaises(errors.NoSuchFile, store.load)
3178
def test_invalid_content(self):
3179
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3180
self.assertEquals(False, store.is_loaded())
3181
exc = self.assertRaises(
3182
errors.ParseConfigError, store._load_from_string,
3183
'this is invalid !')
3184
self.assertEndsWith(exc.filename, 'foo.conf')
3185
# And the load failed
3186
self.assertEquals(False, store.is_loaded())
3188
def test_get_embedded_sections(self):
3189
# A more complicated example (which also shows that section names and
3190
# option names share the same name space...)
3191
# FIXME: This should be fixed by forbidding dicts as values ?
3192
# -- vila 2011-04-05
3193
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3194
store._load_from_string('''
3198
foo_in_DEFAULT=foo_DEFAULT
3206
sections = list(store.get_sections())
3207
self.assertLength(4, sections)
3208
# The default section has no name.
3209
# List values are provided as strings and need to be explicitly
3210
# converted by specifying from_unicode=list_from_store at option
3212
self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
3214
self.assertSectionContent(
3215
('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
3216
self.assertSectionContent(
3217
('bar', {'foo_in_bar': 'barbar'}), sections[2])
3218
# sub sections are provided as embedded dicts.
3219
self.assertSectionContent(
3220
('baz', {'foo_in_baz': 'barbaz', 'qux': {'foo_in_qux': 'quux'}}),
3224
class TestLockableIniFileStore(TestStore):
3226
def test_create_store_in_created_dir(self):
3227
self.assertPathDoesNotExist('dir')
3228
t = self.get_transport('dir/subdir')
3229
store = config.LockableIniFileStore(t, 'foo.conf')
3230
store.get_mutable_section(None).set('foo', 'bar')
3232
self.assertPathExists('dir/subdir')
3235
class TestConcurrentStoreUpdates(TestStore):
3236
"""Test that Stores properly handle conccurent updates.
3238
New Store implementation may fail some of these tests but until such
3239
implementations exist it's hard to properly filter them from the scenarios
3240
applied here. If you encounter such a case, contact the bzr devs.
3243
scenarios = [(key, {'get_stack': builder}) for key, builder
3244
in config.test_stack_builder_registry.iteritems()]
3247
super(TestConcurrentStoreUpdates, self).setUp()
3248
self.stack = self.get_stack(self)
3249
if not isinstance(self.stack, config._CompatibleStack):
3250
raise tests.TestNotApplicable(
3251
'%s is not meant to be compatible with the old config design'
3253
self.stack.set('one', '1')
3254
self.stack.set('two', '2')
3256
self.stack.store.save()
3258
def test_simple_read_access(self):
3259
self.assertEquals('1', self.stack.get('one'))
3261
def test_simple_write_access(self):
3262
self.stack.set('one', 'one')
3263
self.assertEquals('one', self.stack.get('one'))
3265
def test_listen_to_the_last_speaker(self):
3267
c2 = self.get_stack(self)
3268
c1.set('one', 'ONE')
3269
c2.set('two', 'TWO')
3270
self.assertEquals('ONE', c1.get('one'))
3271
self.assertEquals('TWO', c2.get('two'))
3272
# The second update respect the first one
3273
self.assertEquals('ONE', c2.get('one'))
3275
def test_last_speaker_wins(self):
3276
# If the same config is not shared, the same variable modified twice
3277
# can only see a single result.
3279
c2 = self.get_stack(self)
3282
self.assertEquals('c2', c2.get('one'))
3283
# The first modification is still available until another refresh
3285
self.assertEquals('c1', c1.get('one'))
3286
c1.set('two', 'done')
3287
self.assertEquals('c2', c1.get('one'))
3289
def test_writes_are_serialized(self):
3291
c2 = self.get_stack(self)
3293
# We spawn a thread that will pause *during* the config saving.
3294
before_writing = threading.Event()
3295
after_writing = threading.Event()
3296
writing_done = threading.Event()
3297
c1_save_without_locking_orig = c1.store.save_without_locking
3298
def c1_save_without_locking():
3299
before_writing.set()
3300
c1_save_without_locking_orig()
3301
# The lock is held. We wait for the main thread to decide when to
3303
after_writing.wait()
3304
c1.store.save_without_locking = c1_save_without_locking
3308
t1 = threading.Thread(target=c1_set)
3309
# Collect the thread after the test
3310
self.addCleanup(t1.join)
3311
# Be ready to unblock the thread if the test goes wrong
3312
self.addCleanup(after_writing.set)
3314
before_writing.wait()
3315
self.assertRaises(errors.LockContention,
3316
c2.set, 'one', 'c2')
3317
self.assertEquals('c1', c1.get('one'))
3318
# Let the lock be released
3322
self.assertEquals('c2', c2.get('one'))
3324
def test_read_while_writing(self):
3326
# We spawn a thread that will pause *during* the write
3327
ready_to_write = threading.Event()
3328
do_writing = threading.Event()
3329
writing_done = threading.Event()
3330
# We override the _save implementation so we know the store is locked
3331
c1_save_without_locking_orig = c1.store.save_without_locking
3332
def c1_save_without_locking():
3333
ready_to_write.set()
3334
# The lock is held. We wait for the main thread to decide when to
3337
c1_save_without_locking_orig()
3339
c1.store.save_without_locking = c1_save_without_locking
3342
t1 = threading.Thread(target=c1_set)
3343
# Collect the thread after the test
3344
self.addCleanup(t1.join)
3345
# Be ready to unblock the thread if the test goes wrong
3346
self.addCleanup(do_writing.set)
3348
# Ensure the thread is ready to write
3349
ready_to_write.wait()
3350
self.assertEquals('c1', c1.get('one'))
3351
# If we read during the write, we get the old value
3352
c2 = self.get_stack(self)
3353
self.assertEquals('1', c2.get('one'))
3354
# Let the writing occur and ensure it occurred
3357
# Now we get the updated value
3358
c3 = self.get_stack(self)
3359
self.assertEquals('c1', c3.get('one'))
3361
# FIXME: It may be worth looking into removing the lock dir when it's not
3362
# needed anymore and look at possible fallouts for concurrent lockers. This
3363
# will matter if/when we use config files outside of bazaar directories
3364
# (.bazaar or .bzr) -- vila 20110-04-111
3367
class TestSectionMatcher(TestStore):
3369
scenarios = [('location', {'matcher': config.LocationMatcher}),
3370
('id', {'matcher': config.NameMatcher}),]
3373
super(TestSectionMatcher, self).setUp()
3374
# Any simple store is good enough
3375
self.get_store = config.test_store_builder_registry.get('configobj')
3377
def test_no_matches_for_empty_stores(self):
3378
store = self.get_store(self)
3379
store._load_from_string('')
3380
matcher = self.matcher(store, '/bar')
3381
self.assertEquals([], list(matcher.get_sections()))
3383
def test_build_doesnt_load_store(self):
3384
store = self.get_store(self)
3385
self.matcher(store, '/bar')
3386
self.assertFalse(store.is_loaded())
3389
class TestLocationSection(tests.TestCase):
3391
def get_section(self, options, extra_path):
3392
section = config.Section('foo', options)
3393
return config.LocationSection(section, extra_path)
3395
def test_simple_option(self):
3396
section = self.get_section({'foo': 'bar'}, '')
3397
self.assertEquals('bar', section.get('foo'))
3399
def test_option_with_extra_path(self):
3400
section = self.get_section({'foo': 'bar', 'foo:policy': 'appendpath'},
3402
self.assertEquals('bar/baz', section.get('foo'))
3404
def test_invalid_policy(self):
3405
section = self.get_section({'foo': 'bar', 'foo:policy': 'die'},
3407
# invalid policies are ignored
3408
self.assertEquals('bar', section.get('foo'))
3411
class TestLocationMatcher(TestStore):
3414
super(TestLocationMatcher, self).setUp()
3415
# Any simple store is good enough
3416
self.get_store = config.test_store_builder_registry.get('configobj')
3418
def test_unrelated_section_excluded(self):
3419
store = self.get_store(self)
3420
store._load_from_string('''
3428
section=/foo/bar/baz
3432
self.assertEquals(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
3434
[section.id for _, section in store.get_sections()])
3435
matcher = config.LocationMatcher(store, '/foo/bar/quux')
3436
sections = [section for _, section in matcher.get_sections()]
3437
self.assertEquals(['/foo/bar', '/foo'],
3438
[section.id for section in sections])
3439
self.assertEquals(['quux', 'bar/quux'],
3440
[section.extra_path for section in sections])
3442
def test_more_specific_sections_first(self):
3443
store = self.get_store(self)
3444
store._load_from_string('''
3450
self.assertEquals(['/foo', '/foo/bar'],
3451
[section.id for _, section in store.get_sections()])
3452
matcher = config.LocationMatcher(store, '/foo/bar/baz')
3453
sections = [section for _, section in matcher.get_sections()]
3454
self.assertEquals(['/foo/bar', '/foo'],
3455
[section.id for section in sections])
3456
self.assertEquals(['baz', 'bar/baz'],
3457
[section.extra_path for section in sections])
3459
def test_appendpath_in_no_name_section(self):
3460
# It's a bit weird to allow appendpath in a no-name section, but
3461
# someone may found a use for it
3462
store = self.get_store(self)
3463
store._load_from_string('''
3465
foo:policy = appendpath
3467
matcher = config.LocationMatcher(store, 'dir/subdir')
3468
sections = list(matcher.get_sections())
3469
self.assertLength(1, sections)
3470
self.assertEquals('bar/dir/subdir', sections[0][1].get('foo'))
3472
def test_file_urls_are_normalized(self):
3473
store = self.get_store(self)
3474
if sys.platform == 'win32':
3475
expected_url = 'file:///C:/dir/subdir'
3476
expected_location = 'C:/dir/subdir'
3478
expected_url = 'file:///dir/subdir'
3479
expected_location = '/dir/subdir'
3480
matcher = config.LocationMatcher(store, expected_url)
3481
self.assertEquals(expected_location, matcher.location)
3483
def test_branch_name_colo(self):
3484
store = self.get_store(self)
3485
store._load_from_string(dedent("""\
3487
push_location=my{branchname}
3489
matcher = config.LocationMatcher(store, 'file:///,branch=example%3c')
3490
self.assertEqual('example<', matcher.branch_name)
3491
((_, section),) = matcher.get_sections()
3492
self.assertEqual('example<', section.locals['branchname'])
3494
def test_branch_name_basename(self):
3495
store = self.get_store(self)
3496
store._load_from_string(dedent("""\
3498
push_location=my{branchname}
3500
matcher = config.LocationMatcher(store, 'file:///parent/example%3c')
3501
self.assertEqual('example<', matcher.branch_name)
3502
((_, section),) = matcher.get_sections()
3503
self.assertEqual('example<', section.locals['branchname'])
3506
class TestStartingPathMatcher(TestStore):
3509
super(TestStartingPathMatcher, self).setUp()
3510
# Any simple store is good enough
3511
self.store = config.IniFileStore()
3513
def assertSectionIDs(self, expected, location, content):
3514
self.store._load_from_string(content)
3515
matcher = config.StartingPathMatcher(self.store, location)
3516
sections = list(matcher.get_sections())
3517
self.assertLength(len(expected), sections)
3518
self.assertEqual(expected, [section.id for _, section in sections])
3521
def test_empty(self):
3522
self.assertSectionIDs([], self.get_url(), '')
3524
def test_url_vs_local_paths(self):
3525
# The matcher location is an url and the section names are local paths
3526
self.assertSectionIDs(['/foo/bar', '/foo'],
3527
'file:///foo/bar/baz', '''\
3532
def test_local_path_vs_url(self):
3533
# The matcher location is a local path and the section names are urls
3534
self.assertSectionIDs(['file:///foo/bar', 'file:///foo'],
3535
'/foo/bar/baz', '''\
3541
def test_no_name_section_included_when_present(self):
3542
# Note that other tests will cover the case where the no-name section
3543
# is empty and as such, not included.
3544
sections = self.assertSectionIDs(['/foo/bar', '/foo', None],
3545
'/foo/bar/baz', '''\
3546
option = defined so the no-name section exists
3550
self.assertEquals(['baz', 'bar/baz', '/foo/bar/baz'],
3551
[s.locals['relpath'] for _, s in sections])
3553
def test_order_reversed(self):
3554
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3559
def test_unrelated_section_excluded(self):
3560
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3566
def test_glob_included(self):
3567
sections = self.assertSectionIDs(['/foo/*/baz', '/foo/b*', '/foo'],
3568
'/foo/bar/baz', '''\
3574
# Note that 'baz' as a relpath for /foo/b* is not fully correct, but
3575
# nothing really is... as far using {relpath} to append it to something
3576
# else, this seems good enough though.
3577
self.assertEquals(['', 'baz', 'bar/baz'],
3578
[s.locals['relpath'] for _, s in sections])
3580
def test_respect_order(self):
3581
self.assertSectionIDs(['/foo', '/foo/b*', '/foo/*/baz'],
3582
'/foo/bar/baz', '''\
3590
class TestNameMatcher(TestStore):
3593
super(TestNameMatcher, self).setUp()
3594
self.matcher = config.NameMatcher
3595
# Any simple store is good enough
3596
self.get_store = config.test_store_builder_registry.get('configobj')
3598
def get_matching_sections(self, name):
3599
store = self.get_store(self)
3600
store._load_from_string('''
3608
matcher = self.matcher(store, name)
3609
return list(matcher.get_sections())
3611
def test_matching(self):
3612
sections = self.get_matching_sections('foo')
3613
self.assertLength(1, sections)
3614
self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
3616
def test_not_matching(self):
3617
sections = self.get_matching_sections('baz')
3618
self.assertLength(0, sections)
3621
class TestBaseStackGet(tests.TestCase):
3624
super(TestBaseStackGet, self).setUp()
3625
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3627
def test_get_first_definition(self):
3628
store1 = config.IniFileStore()
3629
store1._load_from_string('foo=bar')
3630
store2 = config.IniFileStore()
3631
store2._load_from_string('foo=baz')
3632
conf = config.Stack([store1.get_sections, store2.get_sections])
3633
self.assertEquals('bar', conf.get('foo'))
3635
def test_get_with_registered_default_value(self):
3636
config.option_registry.register(config.Option('foo', default='bar'))
3637
conf_stack = config.Stack([])
3638
self.assertEquals('bar', conf_stack.get('foo'))
3640
def test_get_without_registered_default_value(self):
3641
config.option_registry.register(config.Option('foo'))
3642
conf_stack = config.Stack([])
3643
self.assertEquals(None, conf_stack.get('foo'))
3645
def test_get_without_default_value_for_not_registered(self):
3646
conf_stack = config.Stack([])
3647
self.assertEquals(None, conf_stack.get('foo'))
3649
def test_get_for_empty_section_callable(self):
3650
conf_stack = config.Stack([lambda : []])
3651
self.assertEquals(None, conf_stack.get('foo'))
3653
def test_get_for_broken_callable(self):
3654
# Trying to use and invalid callable raises an exception on first use
3655
conf_stack = config.Stack([object])
3656
self.assertRaises(TypeError, conf_stack.get, 'foo')
3659
class TestStackWithSimpleStore(tests.TestCase):
3662
super(TestStackWithSimpleStore, self).setUp()
3663
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3664
self.registry = config.option_registry
3666
def get_conf(self, content=None):
3667
return config.MemoryStack(content)
3669
def test_override_value_from_env(self):
3670
self.overrideEnv('FOO', None)
3671
self.registry.register(
3672
config.Option('foo', default='bar', override_from_env=['FOO']))
3673
self.overrideEnv('FOO', 'quux')
3674
# Env variable provides a default taking over the option one
3675
conf = self.get_conf('foo=store')
3676
self.assertEquals('quux', conf.get('foo'))
3678
def test_first_override_value_from_env_wins(self):
3679
self.overrideEnv('NO_VALUE', None)
3680
self.overrideEnv('FOO', None)
3681
self.overrideEnv('BAZ', None)
3682
self.registry.register(
3683
config.Option('foo', default='bar',
3684
override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
3685
self.overrideEnv('FOO', 'foo')
3686
self.overrideEnv('BAZ', 'baz')
3687
# The first env var set wins
3688
conf = self.get_conf('foo=store')
3689
self.assertEquals('foo', conf.get('foo'))
3692
class TestMemoryStack(tests.TestCase):
3695
conf = config.MemoryStack('foo=bar')
3696
self.assertEquals('bar', conf.get('foo'))
3699
conf = config.MemoryStack('foo=bar')
3700
conf.set('foo', 'baz')
3701
self.assertEquals('baz', conf.get('foo'))
3703
def test_no_content(self):
3704
conf = config.MemoryStack()
3705
# No content means no loading
3706
self.assertFalse(conf.store.is_loaded())
3707
self.assertRaises(NotImplementedError, conf.get, 'foo')
3708
# But a content can still be provided
3709
conf.store._load_from_string('foo=bar')
3710
self.assertEquals('bar', conf.get('foo'))
3713
class TestStackIterSections(tests.TestCase):
3715
def test_empty_stack(self):
3716
conf = config.Stack([])
3717
sections = list(conf.iter_sections())
3718
self.assertLength(0, sections)
3720
def test_empty_store(self):
3721
store = config.IniFileStore()
3722
store._load_from_string('')
3723
conf = config.Stack([store.get_sections])
3724
sections = list(conf.iter_sections())
3725
self.assertLength(0, sections)
3727
def test_simple_store(self):
3728
store = config.IniFileStore()
3729
store._load_from_string('foo=bar')
3730
conf = config.Stack([store.get_sections])
3731
tuples = list(conf.iter_sections())
3732
self.assertLength(1, tuples)
3733
(found_store, found_section) = tuples[0]
3734
self.assertIs(store, found_store)
3736
def test_two_stores(self):
3737
store1 = config.IniFileStore()
3738
store1._load_from_string('foo=bar')
3739
store2 = config.IniFileStore()
3740
store2._load_from_string('bar=qux')
3741
conf = config.Stack([store1.get_sections, store2.get_sections])
3742
tuples = list(conf.iter_sections())
3743
self.assertLength(2, tuples)
3744
self.assertIs(store1, tuples[0][0])
3745
self.assertIs(store2, tuples[1][0])
3748
class TestStackWithTransport(tests.TestCaseWithTransport):
3750
scenarios = [(key, {'get_stack': builder}) for key, builder
3751
in config.test_stack_builder_registry.iteritems()]
3754
class TestConcreteStacks(TestStackWithTransport):
3756
def test_build_stack(self):
3757
# Just a smoke test to help debug builders
3758
self.get_stack(self)
3761
class TestStackGet(TestStackWithTransport):
3764
super(TestStackGet, self).setUp()
3765
self.conf = self.get_stack(self)
3767
def test_get_for_empty_stack(self):
3768
self.assertEquals(None, self.conf.get('foo'))
3770
def test_get_hook(self):
3771
self.conf.set('foo', 'bar')
3775
config.ConfigHooks.install_named_hook('get', hook, None)
3776
self.assertLength(0, calls)
3777
value = self.conf.get('foo')
3778
self.assertEquals('bar', value)
3779
self.assertLength(1, calls)
3780
self.assertEquals((self.conf, 'foo', 'bar'), calls[0])
3783
class TestStackGetWithConverter(tests.TestCase):
3786
super(TestStackGetWithConverter, self).setUp()
3787
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3788
self.registry = config.option_registry
3790
def get_conf(self, content=None):
3791
return config.MemoryStack(content)
3793
def register_bool_option(self, name, default=None, default_from_env=None):
3794
b = config.Option(name, help='A boolean.',
3795
default=default, default_from_env=default_from_env,
3796
from_unicode=config.bool_from_store)
3797
self.registry.register(b)
3799
def test_get_default_bool_None(self):
3800
self.register_bool_option('foo')
3801
conf = self.get_conf('')
3802
self.assertEquals(None, conf.get('foo'))
3804
def test_get_default_bool_True(self):
3805
self.register_bool_option('foo', u'True')
3806
conf = self.get_conf('')
3807
self.assertEquals(True, conf.get('foo'))
3809
def test_get_default_bool_False(self):
3810
self.register_bool_option('foo', False)
3811
conf = self.get_conf('')
3812
self.assertEquals(False, conf.get('foo'))
3814
def test_get_default_bool_False_as_string(self):
3815
self.register_bool_option('foo', u'False')
3816
conf = self.get_conf('')
3817
self.assertEquals(False, conf.get('foo'))
3819
def test_get_default_bool_from_env_converted(self):
3820
self.register_bool_option('foo', u'True', default_from_env=['FOO'])
3821
self.overrideEnv('FOO', 'False')
3822
conf = self.get_conf('')
3823
self.assertEquals(False, conf.get('foo'))
3825
def test_get_default_bool_when_conversion_fails(self):
3826
self.register_bool_option('foo', default='True')
3827
conf = self.get_conf('foo=invalid boolean')
3828
self.assertEquals(True, conf.get('foo'))
3830
def register_integer_option(self, name,
3831
default=None, default_from_env=None):
3832
i = config.Option(name, help='An integer.',
3833
default=default, default_from_env=default_from_env,
3834
from_unicode=config.int_from_store)
3835
self.registry.register(i)
3837
def test_get_default_integer_None(self):
3838
self.register_integer_option('foo')
3839
conf = self.get_conf('')
3840
self.assertEquals(None, conf.get('foo'))
3842
def test_get_default_integer(self):
3843
self.register_integer_option('foo', 42)
3844
conf = self.get_conf('')
3845
self.assertEquals(42, conf.get('foo'))
3847
def test_get_default_integer_as_string(self):
3848
self.register_integer_option('foo', u'42')
3849
conf = self.get_conf('')
3850
self.assertEquals(42, conf.get('foo'))
3852
def test_get_default_integer_from_env(self):
3853
self.register_integer_option('foo', default_from_env=['FOO'])
3854
self.overrideEnv('FOO', '18')
3855
conf = self.get_conf('')
3856
self.assertEquals(18, conf.get('foo'))
3858
def test_get_default_integer_when_conversion_fails(self):
3859
self.register_integer_option('foo', default='12')
3860
conf = self.get_conf('foo=invalid integer')
3861
self.assertEquals(12, conf.get('foo'))
3863
def register_list_option(self, name, default=None, default_from_env=None):
3864
l = config.ListOption(name, help='A list.', default=default,
3865
default_from_env=default_from_env)
3866
self.registry.register(l)
3868
def test_get_default_list_None(self):
3869
self.register_list_option('foo')
3870
conf = self.get_conf('')
3871
self.assertEquals(None, conf.get('foo'))
3873
def test_get_default_list_empty(self):
3874
self.register_list_option('foo', '')
3875
conf = self.get_conf('')
3876
self.assertEquals([], conf.get('foo'))
3878
def test_get_default_list_from_env(self):
3879
self.register_list_option('foo', default_from_env=['FOO'])
3880
self.overrideEnv('FOO', '')
3881
conf = self.get_conf('')
3882
self.assertEquals([], conf.get('foo'))
3884
def test_get_with_list_converter_no_item(self):
3885
self.register_list_option('foo', None)
3886
conf = self.get_conf('foo=,')
3887
self.assertEquals([], conf.get('foo'))
3889
def test_get_with_list_converter_many_items(self):
3890
self.register_list_option('foo', None)
3891
conf = self.get_conf('foo=m,o,r,e')
3892
self.assertEquals(['m', 'o', 'r', 'e'], conf.get('foo'))
3894
def test_get_with_list_converter_embedded_spaces_many_items(self):
3895
self.register_list_option('foo', None)
3896
conf = self.get_conf('foo=" bar", "baz "')
3897
self.assertEquals([' bar', 'baz '], conf.get('foo'))
3899
def test_get_with_list_converter_stripped_spaces_many_items(self):
3900
self.register_list_option('foo', None)
3901
conf = self.get_conf('foo= bar , baz ')
3902
self.assertEquals(['bar', 'baz'], conf.get('foo'))
3905
class TestIterOptionRefs(tests.TestCase):
3906
"""iter_option_refs is a bit unusual, document some cases."""
3908
def assertRefs(self, expected, string):
3909
self.assertEquals(expected, list(config.iter_option_refs(string)))
3911
def test_empty(self):
3912
self.assertRefs([(False, '')], '')
3914
def test_no_refs(self):
3915
self.assertRefs([(False, 'foo bar')], 'foo bar')
3917
def test_single_ref(self):
3918
self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
3920
def test_broken_ref(self):
3921
self.assertRefs([(False, '{foo')], '{foo')
3923
def test_embedded_ref(self):
3924
self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
3927
def test_two_refs(self):
3928
self.assertRefs([(False, ''), (True, '{foo}'),
3929
(False, ''), (True, '{bar}'),
3933
def test_newline_in_refs_are_not_matched(self):
3934
self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
3937
class TestStackExpandOptions(tests.TestCaseWithTransport):
3940
super(TestStackExpandOptions, self).setUp()
3941
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3942
self.registry = config.option_registry
3943
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3944
self.conf = config.Stack([store.get_sections], store)
3946
def assertExpansion(self, expected, string, env=None):
3947
self.assertEquals(expected, self.conf.expand_options(string, env))
3949
def test_no_expansion(self):
3950
self.assertExpansion('foo', 'foo')
3952
def test_expand_default_value(self):
3953
self.conf.store._load_from_string('bar=baz')
3954
self.registry.register(config.Option('foo', default=u'{bar}'))
3955
self.assertEquals('baz', self.conf.get('foo', expand=True))
3957
def test_expand_default_from_env(self):
3958
self.conf.store._load_from_string('bar=baz')
3959
self.registry.register(config.Option('foo', default_from_env=['FOO']))
3960
self.overrideEnv('FOO', '{bar}')
3961
self.assertEquals('baz', self.conf.get('foo', expand=True))
3963
def test_expand_default_on_failed_conversion(self):
3964
self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
3965
self.registry.register(
3966
config.Option('foo', default=u'{bar}',
3967
from_unicode=config.int_from_store))
3968
self.assertEquals(42, self.conf.get('foo', expand=True))
3970
def test_env_adding_options(self):
3971
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3973
def test_env_overriding_options(self):
3974
self.conf.store._load_from_string('foo=baz')
3975
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3977
def test_simple_ref(self):
3978
self.conf.store._load_from_string('foo=xxx')
3979
self.assertExpansion('xxx', '{foo}')
3981
def test_unknown_ref(self):
3982
self.assertRaises(errors.ExpandingUnknownOption,
3983
self.conf.expand_options, '{foo}')
3985
def test_illegal_def_is_ignored(self):
3986
self.assertExpansion('{1,2}', '{1,2}')
3987
self.assertExpansion('{ }', '{ }')
3988
self.assertExpansion('${Foo,f}', '${Foo,f}')
3990
def test_indirect_ref(self):
3991
self.conf.store._load_from_string('''
3995
self.assertExpansion('xxx', '{bar}')
3997
def test_embedded_ref(self):
3998
self.conf.store._load_from_string('''
4002
self.assertExpansion('xxx', '{{bar}}')
4004
def test_simple_loop(self):
4005
self.conf.store._load_from_string('foo={foo}')
4006
self.assertRaises(errors.OptionExpansionLoop,
4007
self.conf.expand_options, '{foo}')
4009
def test_indirect_loop(self):
4010
self.conf.store._load_from_string('''
4014
e = self.assertRaises(errors.OptionExpansionLoop,
4015
self.conf.expand_options, '{foo}')
4016
self.assertEquals('foo->bar->baz', e.refs)
4017
self.assertEquals('{foo}', e.string)
4019
def test_list(self):
4020
self.conf.store._load_from_string('''
4024
list={foo},{bar},{baz}
4026
self.registry.register(
4027
config.ListOption('list'))
4028
self.assertEquals(['start', 'middle', 'end'],
4029
self.conf.get('list', expand=True))
4031
def test_cascading_list(self):
4032
self.conf.store._load_from_string('''
4038
self.registry.register(config.ListOption('list'))
4039
# Register an intermediate option as a list to ensure no conversion
4040
# happen while expanding. Conversion should only occur for the original
4041
# option ('list' here).
4042
self.registry.register(config.ListOption('baz'))
4043
self.assertEquals(['start', 'middle', 'end'],
4044
self.conf.get('list', expand=True))
4046
def test_pathologically_hidden_list(self):
4047
self.conf.store._load_from_string('''
4053
hidden={start}{middle}{end}
4055
# What matters is what the registration says, the conversion happens
4056
# only after all expansions have been performed
4057
self.registry.register(config.ListOption('hidden'))
4058
self.assertEquals(['bin', 'go'],
4059
self.conf.get('hidden', expand=True))
4062
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
4065
super(TestStackCrossSectionsExpand, self).setUp()
4067
def get_config(self, location, string):
4070
# Since we don't save the config we won't strictly require to inherit
4071
# from TestCaseInTempDir, but an error occurs so quickly...
4072
c = config.LocationStack(location)
4073
c.store._load_from_string(string)
4076
def test_dont_cross_unrelated_section(self):
4077
c = self.get_config('/another/branch/path','''
4082
[/another/branch/path]
4085
self.assertRaises(errors.ExpandingUnknownOption,
4086
c.get, 'bar', expand=True)
4088
def test_cross_related_sections(self):
4089
c = self.get_config('/project/branch/path','''
4093
[/project/branch/path]
4096
self.assertEquals('quux', c.get('bar', expand=True))
4099
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
4101
def test_cross_global_locations(self):
4102
l_store = config.LocationStore()
4103
l_store._load_from_string('''
4109
g_store = config.GlobalStore()
4110
g_store._load_from_string('''
4116
stack = config.LocationStack('/branch')
4117
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4118
self.assertEquals('loc-foo', stack.get('gfoo', expand=True))
4121
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
4123
def test_expand_locals_empty(self):
4124
l_store = config.LocationStore()
4125
l_store._load_from_string('''
4126
[/home/user/project]
4131
stack = config.LocationStack('/home/user/project/')
4132
self.assertEquals('', stack.get('base', expand=True))
4133
self.assertEquals('', stack.get('rel', expand=True))
4135
def test_expand_basename_locally(self):
4136
l_store = config.LocationStore()
4137
l_store._load_from_string('''
4138
[/home/user/project]
4142
stack = config.LocationStack('/home/user/project/branch')
4143
self.assertEquals('branch', stack.get('bfoo', expand=True))
4145
def test_expand_basename_locally_longer_path(self):
4146
l_store = config.LocationStore()
4147
l_store._load_from_string('''
4152
stack = config.LocationStack('/home/user/project/dir/branch')
4153
self.assertEquals('branch', stack.get('bfoo', expand=True))
4155
def test_expand_relpath_locally(self):
4156
l_store = config.LocationStore()
4157
l_store._load_from_string('''
4158
[/home/user/project]
4159
lfoo = loc-foo/{relpath}
4162
stack = config.LocationStack('/home/user/project/branch')
4163
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4165
def test_expand_relpath_unknonw_in_global(self):
4166
g_store = config.GlobalStore()
4167
g_store._load_from_string('''
4172
stack = config.LocationStack('/home/user/project/branch')
4173
self.assertRaises(errors.ExpandingUnknownOption,
4174
stack.get, 'gfoo', expand=True)
4176
def test_expand_local_option_locally(self):
4177
l_store = config.LocationStore()
4178
l_store._load_from_string('''
4179
[/home/user/project]
4180
lfoo = loc-foo/{relpath}
4184
g_store = config.GlobalStore()
4185
g_store._load_from_string('''
4191
stack = config.LocationStack('/home/user/project/branch')
4192
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4193
self.assertEquals('loc-foo/branch', stack.get('gfoo', expand=True))
4195
def test_locals_dont_leak(self):
4196
"""Make sure we chose the right local in presence of several sections.
4198
l_store = config.LocationStore()
4199
l_store._load_from_string('''
4201
lfoo = loc-foo/{relpath}
4202
[/home/user/project]
4203
lfoo = loc-foo/{relpath}
4206
stack = config.LocationStack('/home/user/project/branch')
4207
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4208
stack = config.LocationStack('/home/user/bar/baz')
4209
self.assertEquals('loc-foo/bar/baz', stack.get('lfoo', expand=True))
4213
class TestStackSet(TestStackWithTransport):
4215
def test_simple_set(self):
4216
conf = self.get_stack(self)
4217
self.assertEquals(None, conf.get('foo'))
4218
conf.set('foo', 'baz')
4219
# Did we get it back ?
4220
self.assertEquals('baz', conf.get('foo'))
4222
def test_set_creates_a_new_section(self):
4223
conf = self.get_stack(self)
4224
conf.set('foo', 'baz')
4225
self.assertEquals, 'baz', conf.get('foo')
4227
def test_set_hook(self):
4231
config.ConfigHooks.install_named_hook('set', hook, None)
4232
self.assertLength(0, calls)
4233
conf = self.get_stack(self)
4234
conf.set('foo', 'bar')
4235
self.assertLength(1, calls)
4236
self.assertEquals((conf, 'foo', 'bar'), calls[0])
4239
class TestStackRemove(TestStackWithTransport):
4241
def test_remove_existing(self):
4242
conf = self.get_stack(self)
4243
conf.set('foo', 'bar')
4244
self.assertEquals('bar', conf.get('foo'))
4246
# Did we get it back ?
4247
self.assertEquals(None, conf.get('foo'))
4249
def test_remove_unknown(self):
4250
conf = self.get_stack(self)
4251
self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
4253
def test_remove_hook(self):
4257
config.ConfigHooks.install_named_hook('remove', hook, None)
4258
self.assertLength(0, calls)
4259
conf = self.get_stack(self)
4260
conf.set('foo', 'bar')
4262
self.assertLength(1, calls)
4263
self.assertEquals((conf, 'foo'), calls[0])
4266
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
4269
super(TestConfigGetOptions, self).setUp()
4270
create_configs(self)
4272
def test_no_variable(self):
4273
# Using branch should query branch, locations and bazaar
4274
self.assertOptions([], self.branch_config)
4276
def test_option_in_bazaar(self):
4277
self.bazaar_config.set_user_option('file', 'bazaar')
4278
self.assertOptions([('file', 'bazaar', 'DEFAULT', 'bazaar')],
4281
def test_option_in_locations(self):
4282
self.locations_config.set_user_option('file', 'locations')
4284
[('file', 'locations', self.tree.basedir, 'locations')],
4285
self.locations_config)
4287
def test_option_in_branch(self):
4288
self.branch_config.set_user_option('file', 'branch')
4289
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch')],
4292
def test_option_in_bazaar_and_branch(self):
4293
self.bazaar_config.set_user_option('file', 'bazaar')
4294
self.branch_config.set_user_option('file', 'branch')
4295
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch'),
4296
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4299
def test_option_in_branch_and_locations(self):
4300
# Hmm, locations override branch :-/
4301
self.locations_config.set_user_option('file', 'locations')
4302
self.branch_config.set_user_option('file', 'branch')
4304
[('file', 'locations', self.tree.basedir, 'locations'),
4305
('file', 'branch', 'DEFAULT', 'branch'),],
4308
def test_option_in_bazaar_locations_and_branch(self):
4309
self.bazaar_config.set_user_option('file', 'bazaar')
4310
self.locations_config.set_user_option('file', 'locations')
4311
self.branch_config.set_user_option('file', 'branch')
4313
[('file', 'locations', self.tree.basedir, 'locations'),
4314
('file', 'branch', 'DEFAULT', 'branch'),
4315
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4319
class TestConfigRemoveOption(tests.TestCaseWithTransport, TestOptionsMixin):
4322
super(TestConfigRemoveOption, self).setUp()
4323
create_configs_with_file_option(self)
4325
def test_remove_in_locations(self):
4326
self.locations_config.remove_user_option('file', self.tree.basedir)
4328
[('file', 'branch', 'DEFAULT', 'branch'),
4329
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4332
def test_remove_in_branch(self):
4333
self.branch_config.remove_user_option('file')
4335
[('file', 'locations', self.tree.basedir, 'locations'),
4336
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4339
def test_remove_in_bazaar(self):
4340
self.bazaar_config.remove_user_option('file')
4342
[('file', 'locations', self.tree.basedir, 'locations'),
4343
('file', 'branch', 'DEFAULT', 'branch'),],
4347
class TestConfigGetSections(tests.TestCaseWithTransport):
4350
super(TestConfigGetSections, self).setUp()
4351
create_configs(self)
4353
def assertSectionNames(self, expected, conf, name=None):
4354
"""Check which sections are returned for a given config.
4356
If fallback configurations exist their sections can be included.
4358
:param expected: A list of section names.
4360
:param conf: The configuration that will be queried.
4362
:param name: An optional section name that will be passed to
4365
sections = list(conf._get_sections(name))
4366
self.assertLength(len(expected), sections)
4367
self.assertEqual(expected, [n for n, _, _ in sections])
4369
def test_bazaar_default_section(self):
4370
self.assertSectionNames(['DEFAULT'], self.bazaar_config)
4372
def test_locations_default_section(self):
4373
# No sections are defined in an empty file
4374
self.assertSectionNames([], self.locations_config)
4376
def test_locations_named_section(self):
4377
self.locations_config.set_user_option('file', 'locations')
4378
self.assertSectionNames([self.tree.basedir], self.locations_config)
4380
def test_locations_matching_sections(self):
4381
loc_config = self.locations_config
4382
loc_config.set_user_option('file', 'locations')
4383
# We need to cheat a bit here to create an option in sections above and
4384
# below the 'location' one.
4385
parser = loc_config._get_parser()
4386
# locations.cong deals with '/' ignoring native os.sep
4387
location_names = self.tree.basedir.split('/')
4388
parent = '/'.join(location_names[:-1])
4389
child = '/'.join(location_names + ['child'])
4391
parser[parent]['file'] = 'parent'
4393
parser[child]['file'] = 'child'
4394
self.assertSectionNames([self.tree.basedir, parent], loc_config)
4396
def test_branch_data_default_section(self):
4397
self.assertSectionNames([None],
4398
self.branch_config._get_branch_data_config())
4400
def test_branch_default_sections(self):
4401
# No sections are defined in an empty locations file
4402
self.assertSectionNames([None, 'DEFAULT'],
4404
# Unless we define an option
4405
self.branch_config._get_location_config().set_user_option(
4406
'file', 'locations')
4407
self.assertSectionNames([self.tree.basedir, None, 'DEFAULT'],
4410
def test_bazaar_named_section(self):
4411
# We need to cheat as the API doesn't give direct access to sections
4412
# other than DEFAULT.
4413
self.bazaar_config.set_alias('bazaar', 'bzr')
4414
self.assertSectionNames(['ALIASES'], self.bazaar_config, 'ALIASES')
4417
class TestSharedStores(tests.TestCaseInTempDir):
4419
def test_bazaar_conf_shared(self):
4420
g1 = config.GlobalStack()
4421
g2 = config.GlobalStack()
4422
# The two stacks share the same store
4423
self.assertIs(g1.store, g2.store)
1308
4426
class TestAuthenticationConfigFile(tests.TestCase):
1309
4427
"""Test the authentication.conf file matching"""