1312
1986
self.assertIs(None, bzrdir_config.get_default_stack_on())
1989
class TestOldConfigHooks(tests.TestCaseWithTransport):
1992
super(TestOldConfigHooks, self).setUp()
1993
create_configs_with_file_option(self)
1995
def assertGetHook(self, conf, name, value):
1999
config.OldConfigHooks.install_named_hook('get', hook, None)
2001
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2002
self.assertLength(0, calls)
2003
actual_value = conf.get_user_option(name)
2004
self.assertEqual(value, actual_value)
2005
self.assertLength(1, calls)
2006
self.assertEqual((conf, name, value), calls[0])
2008
def test_get_hook_bazaar(self):
2009
self.assertGetHook(self.bazaar_config, 'file', 'bazaar')
2011
def test_get_hook_locations(self):
2012
self.assertGetHook(self.locations_config, 'file', 'locations')
2014
def test_get_hook_branch(self):
2015
# Since locations masks branch, we define a different option
2016
self.branch_config.set_user_option('file2', 'branch')
2017
self.assertGetHook(self.branch_config, 'file2', 'branch')
2019
def assertSetHook(self, conf, name, value):
2023
config.OldConfigHooks.install_named_hook('set', hook, None)
2025
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2026
self.assertLength(0, calls)
2027
conf.set_user_option(name, value)
2028
self.assertLength(1, calls)
2029
# We can't assert the conf object below as different configs use
2030
# different means to implement set_user_option and we care only about
2032
self.assertEqual((name, value), calls[0][1:])
2034
def test_set_hook_bazaar(self):
2035
self.assertSetHook(self.bazaar_config, 'foo', 'bazaar')
2037
def test_set_hook_locations(self):
2038
self.assertSetHook(self.locations_config, 'foo', 'locations')
2040
def test_set_hook_branch(self):
2041
self.assertSetHook(self.branch_config, 'foo', 'branch')
2043
def assertRemoveHook(self, conf, name, section_name=None):
2047
config.OldConfigHooks.install_named_hook('remove', hook, None)
2049
config.OldConfigHooks.uninstall_named_hook, 'remove', None)
2050
self.assertLength(0, calls)
2051
conf.remove_user_option(name, section_name)
2052
self.assertLength(1, calls)
2053
# We can't assert the conf object below as different configs use
2054
# different means to implement remove_user_option and we care only about
2056
self.assertEqual((name,), calls[0][1:])
2058
def test_remove_hook_bazaar(self):
2059
self.assertRemoveHook(self.bazaar_config, 'file')
2061
def test_remove_hook_locations(self):
2062
self.assertRemoveHook(self.locations_config, 'file',
2063
self.locations_config.location)
2065
def test_remove_hook_branch(self):
2066
self.assertRemoveHook(self.branch_config, 'file')
2068
def assertLoadHook(self, name, conf_class, *conf_args):
2072
config.OldConfigHooks.install_named_hook('load', hook, None)
2074
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2075
self.assertLength(0, calls)
2077
conf = conf_class(*conf_args)
2078
# Access an option to trigger a load
2079
conf.get_user_option(name)
2080
self.assertLength(1, calls)
2081
# Since we can't assert about conf, we just use the number of calls ;-/
2083
def test_load_hook_bazaar(self):
2084
self.assertLoadHook('file', config.GlobalConfig)
2086
def test_load_hook_locations(self):
2087
self.assertLoadHook('file', config.LocationConfig, self.tree.basedir)
2089
def test_load_hook_branch(self):
2090
self.assertLoadHook('file', config.BranchConfig, self.tree.branch)
2092
def assertSaveHook(self, conf):
2096
config.OldConfigHooks.install_named_hook('save', hook, None)
2098
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2099
self.assertLength(0, calls)
2100
# Setting an option triggers a save
2101
conf.set_user_option('foo', 'bar')
2102
self.assertLength(1, calls)
2103
# Since we can't assert about conf, we just use the number of calls ;-/
2105
def test_save_hook_bazaar(self):
2106
self.assertSaveHook(self.bazaar_config)
2108
def test_save_hook_locations(self):
2109
self.assertSaveHook(self.locations_config)
2111
def test_save_hook_branch(self):
2112
self.assertSaveHook(self.branch_config)
2115
class TestOldConfigHooksForRemote(tests.TestCaseWithTransport):
2116
"""Tests config hooks for remote configs.
2118
No tests for the remove hook as this is not implemented there.
2122
super(TestOldConfigHooksForRemote, self).setUp()
2123
self.transport_server = test_server.SmartTCPServer_for_testing
2124
create_configs_with_file_option(self)
2126
def assertGetHook(self, conf, name, value):
2130
config.OldConfigHooks.install_named_hook('get', hook, None)
2132
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2133
self.assertLength(0, calls)
2134
actual_value = conf.get_option(name)
2135
self.assertEqual(value, actual_value)
2136
self.assertLength(1, calls)
2137
self.assertEqual((conf, name, value), calls[0])
2139
def test_get_hook_remote_branch(self):
2140
remote_branch = branch.Branch.open(self.get_url('tree'))
2141
self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
2143
def test_get_hook_remote_bzrdir(self):
2144
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2145
conf = remote_bzrdir._get_config()
2146
conf.set_option('remotedir', 'file')
2147
self.assertGetHook(conf, 'file', 'remotedir')
2149
def assertSetHook(self, conf, name, value):
2153
config.OldConfigHooks.install_named_hook('set', hook, None)
2155
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2156
self.assertLength(0, calls)
2157
conf.set_option(value, name)
2158
self.assertLength(1, calls)
2159
# We can't assert the conf object below as different configs use
2160
# different means to implement set_user_option and we care only about
2162
self.assertEqual((name, value), calls[0][1:])
2164
def test_set_hook_remote_branch(self):
2165
remote_branch = branch.Branch.open(self.get_url('tree'))
2166
self.addCleanup(remote_branch.lock_write().unlock)
2167
self.assertSetHook(remote_branch._get_config(), 'file', 'remote')
2169
def test_set_hook_remote_bzrdir(self):
2170
remote_branch = branch.Branch.open(self.get_url('tree'))
2171
self.addCleanup(remote_branch.lock_write().unlock)
2172
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2173
self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
2175
def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
2179
config.OldConfigHooks.install_named_hook('load', hook, None)
2181
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2182
self.assertLength(0, calls)
2184
conf = conf_class(*conf_args)
2185
# Access an option to trigger a load
2186
conf.get_option(name)
2187
self.assertLength(expected_nb_calls, calls)
2188
# Since we can't assert about conf, we just use the number of calls ;-/
2190
def test_load_hook_remote_branch(self):
2191
remote_branch = branch.Branch.open(self.get_url('tree'))
2192
self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
2194
def test_load_hook_remote_bzrdir(self):
2195
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2196
# The config file doesn't exist, set an option to force its creation
2197
conf = remote_bzrdir._get_config()
2198
conf.set_option('remotedir', 'file')
2199
# We get one call for the server and one call for the client, this is
2200
# caused by the differences in implementations betwen
2201
# SmartServerBzrDirRequestConfigFile (in smart/bzrdir.py) and
2202
# SmartServerBranchGetConfigFile (in smart/branch.py)
2203
self.assertLoadHook(2 ,'file', remote.RemoteBzrDirConfig, remote_bzrdir)
2205
def assertSaveHook(self, conf):
2209
config.OldConfigHooks.install_named_hook('save', hook, None)
2211
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2212
self.assertLength(0, calls)
2213
# Setting an option triggers a save
2214
conf.set_option('foo', 'bar')
2215
self.assertLength(1, calls)
2216
# Since we can't assert about conf, we just use the number of calls ;-/
2218
def test_save_hook_remote_branch(self):
2219
remote_branch = branch.Branch.open(self.get_url('tree'))
2220
self.addCleanup(remote_branch.lock_write().unlock)
2221
self.assertSaveHook(remote_branch._get_config())
2223
def test_save_hook_remote_bzrdir(self):
2224
remote_branch = branch.Branch.open(self.get_url('tree'))
2225
self.addCleanup(remote_branch.lock_write().unlock)
2226
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2227
self.assertSaveHook(remote_bzrdir._get_config())
2230
class TestOptionNames(tests.TestCase):
2232
def is_valid(self, name):
2233
return config._option_ref_re.match('{%s}' % name) is not None
2235
def test_valid_names(self):
2236
self.assertTrue(self.is_valid('foo'))
2237
self.assertTrue(self.is_valid('foo.bar'))
2238
self.assertTrue(self.is_valid('f1'))
2239
self.assertTrue(self.is_valid('_'))
2240
self.assertTrue(self.is_valid('__bar__'))
2241
self.assertTrue(self.is_valid('a_'))
2242
self.assertTrue(self.is_valid('a1'))
2243
# Don't break bzr-svn for no good reason
2244
self.assertTrue(self.is_valid('guessed-layout'))
2246
def test_invalid_names(self):
2247
self.assertFalse(self.is_valid(' foo'))
2248
self.assertFalse(self.is_valid('foo '))
2249
self.assertFalse(self.is_valid('1'))
2250
self.assertFalse(self.is_valid('1,2'))
2251
self.assertFalse(self.is_valid('foo$'))
2252
self.assertFalse(self.is_valid('!foo'))
2253
self.assertFalse(self.is_valid('foo.'))
2254
self.assertFalse(self.is_valid('foo..bar'))
2255
self.assertFalse(self.is_valid('{}'))
2256
self.assertFalse(self.is_valid('{a}'))
2257
self.assertFalse(self.is_valid('a\n'))
2258
self.assertFalse(self.is_valid('-'))
2259
self.assertFalse(self.is_valid('-a'))
2260
self.assertFalse(self.is_valid('a-'))
2261
self.assertFalse(self.is_valid('a--a'))
2263
def assertSingleGroup(self, reference):
2264
# the regexp is used with split and as such should match the reference
2265
# *only*, if more groups needs to be defined, (?:...) should be used.
2266
m = config._option_ref_re.match('{a}')
2267
self.assertLength(1, m.groups())
2269
def test_valid_references(self):
2270
self.assertSingleGroup('{a}')
2271
self.assertSingleGroup('{{a}}')
2274
class TestOption(tests.TestCase):
2276
def test_default_value(self):
2277
opt = config.Option('foo', default='bar')
2278
self.assertEqual('bar', opt.get_default())
2280
def test_callable_default_value(self):
2281
def bar_as_unicode():
2283
opt = config.Option('foo', default=bar_as_unicode)
2284
self.assertEqual('bar', opt.get_default())
2286
def test_default_value_from_env(self):
2287
opt = config.Option('foo', default='bar', default_from_env=['FOO'])
2288
self.overrideEnv('FOO', 'quux')
2289
# Env variable provides a default taking over the option one
2290
self.assertEqual('quux', opt.get_default())
2292
def test_first_default_value_from_env_wins(self):
2293
opt = config.Option('foo', default='bar',
2294
default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
2295
self.overrideEnv('FOO', 'foo')
2296
self.overrideEnv('BAZ', 'baz')
2297
# The first env var set wins
2298
self.assertEqual('foo', opt.get_default())
2300
def test_not_supported_list_default_value(self):
2301
self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
2303
def test_not_supported_object_default_value(self):
2304
self.assertRaises(AssertionError, config.Option, 'foo',
2307
def test_not_supported_callable_default_value_not_unicode(self):
2308
def bar_not_unicode():
2310
opt = config.Option('foo', default=bar_not_unicode)
2311
self.assertRaises(AssertionError, opt.get_default)
2313
def test_get_help_topic(self):
2314
opt = config.Option('foo')
2315
self.assertEqual('foo', opt.get_help_topic())
2318
class TestOptionConverter(tests.TestCase):
2320
def assertConverted(self, expected, opt, value):
2321
self.assertEqual(expected, opt.convert_from_unicode(None, value))
2323
def assertCallsWarning(self, opt, value):
2327
warnings.append(args[0] % args[1:])
2328
self.overrideAttr(trace, 'warning', warning)
2329
self.assertEqual(None, opt.convert_from_unicode(None, value))
2330
self.assertLength(1, warnings)
2332
'Value "%s" is not valid for "%s"' % (value, opt.name),
2335
def assertCallsError(self, opt, value):
2336
self.assertRaises(errors.ConfigOptionValueError,
2337
opt.convert_from_unicode, None, value)
2339
def assertConvertInvalid(self, opt, invalid_value):
2341
self.assertEqual(None, opt.convert_from_unicode(None, invalid_value))
2342
opt.invalid = 'warning'
2343
self.assertCallsWarning(opt, invalid_value)
2344
opt.invalid = 'error'
2345
self.assertCallsError(opt, invalid_value)
2348
class TestOptionWithBooleanConverter(TestOptionConverter):
2350
def get_option(self):
2351
return config.Option('foo', help='A boolean.',
2352
from_unicode=config.bool_from_store)
2354
def test_convert_invalid(self):
2355
opt = self.get_option()
2356
# A string that is not recognized as a boolean
2357
self.assertConvertInvalid(opt, u'invalid-boolean')
2358
# A list of strings is never recognized as a boolean
2359
self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
2361
def test_convert_valid(self):
2362
opt = self.get_option()
2363
self.assertConverted(True, opt, u'True')
2364
self.assertConverted(True, opt, u'1')
2365
self.assertConverted(False, opt, u'False')
2368
class TestOptionWithIntegerConverter(TestOptionConverter):
2370
def get_option(self):
2371
return config.Option('foo', help='An integer.',
2372
from_unicode=config.int_from_store)
2374
def test_convert_invalid(self):
2375
opt = self.get_option()
2376
# A string that is not recognized as an integer
2377
self.assertConvertInvalid(opt, u'forty-two')
2378
# A list of strings is never recognized as an integer
2379
self.assertConvertInvalid(opt, [u'a', u'list'])
2381
def test_convert_valid(self):
2382
opt = self.get_option()
2383
self.assertConverted(16, opt, u'16')
2386
class TestOptionWithSIUnitConverter(TestOptionConverter):
2388
def get_option(self):
2389
return config.Option('foo', help='An integer in SI units.',
2390
from_unicode=config.int_SI_from_store)
2392
def test_convert_invalid(self):
2393
opt = self.get_option()
2394
self.assertConvertInvalid(opt, u'not-a-unit')
2395
self.assertConvertInvalid(opt, u'Gb') # Forgot the value
2396
self.assertConvertInvalid(opt, u'1b') # Forgot the unit
2397
self.assertConvertInvalid(opt, u'1GG')
2398
self.assertConvertInvalid(opt, u'1Mbb')
2399
self.assertConvertInvalid(opt, u'1MM')
2401
def test_convert_valid(self):
2402
opt = self.get_option()
2403
self.assertConverted(int(5e3), opt, u'5kb')
2404
self.assertConverted(int(5e6), opt, u'5M')
2405
self.assertConverted(int(5e6), opt, u'5MB')
2406
self.assertConverted(int(5e9), opt, u'5g')
2407
self.assertConverted(int(5e9), opt, u'5gB')
2408
self.assertConverted(100, opt, u'100')
2411
class TestListOption(TestOptionConverter):
2413
def get_option(self):
2414
return config.ListOption('foo', help='A list.')
2416
def test_convert_invalid(self):
2417
opt = self.get_option()
2418
# We don't even try to convert a list into a list, we only expect
2420
self.assertConvertInvalid(opt, [1])
2421
# No string is invalid as all forms can be converted to a list
2423
def test_convert_valid(self):
2424
opt = self.get_option()
2425
# An empty string is an empty list
2426
self.assertConverted([], opt, '') # Using a bare str() just in case
2427
self.assertConverted([], opt, u'')
2429
self.assertConverted([u'True'], opt, u'True')
2431
self.assertConverted([u'42'], opt, u'42')
2433
self.assertConverted([u'bar'], opt, u'bar')
2436
class TestRegistryOption(TestOptionConverter):
2438
def get_option(self, registry):
2439
return config.RegistryOption('foo', registry,
2440
help='A registry option.')
2442
def test_convert_invalid(self):
2443
registry = _mod_registry.Registry()
2444
opt = self.get_option(registry)
2445
self.assertConvertInvalid(opt, [1])
2446
self.assertConvertInvalid(opt, u"notregistered")
2448
def test_convert_valid(self):
2449
registry = _mod_registry.Registry()
2450
registry.register("someval", 1234)
2451
opt = self.get_option(registry)
2452
# Using a bare str() just in case
2453
self.assertConverted(1234, opt, "someval")
2454
self.assertConverted(1234, opt, u'someval')
2455
self.assertConverted(None, opt, None)
2457
def test_help(self):
2458
registry = _mod_registry.Registry()
2459
registry.register("someval", 1234, help="some option")
2460
registry.register("dunno", 1234, help="some other option")
2461
opt = self.get_option(registry)
2463
'A registry option.\n'
2465
'The following values are supported:\n'
2466
' dunno - some other option\n'
2467
' someval - some option\n',
2470
def test_get_help_text(self):
2471
registry = _mod_registry.Registry()
2472
registry.register("someval", 1234, help="some option")
2473
registry.register("dunno", 1234, help="some other option")
2474
opt = self.get_option(registry)
2476
'A registry option.\n'
2478
'The following values are supported:\n'
2479
' dunno - some other option\n'
2480
' someval - some option\n',
2481
opt.get_help_text())
2484
class TestOptionRegistry(tests.TestCase):
2487
super(TestOptionRegistry, self).setUp()
2488
# Always start with an empty registry
2489
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2490
self.registry = config.option_registry
2492
def test_register(self):
2493
opt = config.Option('foo')
2494
self.registry.register(opt)
2495
self.assertIs(opt, self.registry.get('foo'))
2497
def test_registered_help(self):
2498
opt = config.Option('foo', help='A simple option')
2499
self.registry.register(opt)
2500
self.assertEqual('A simple option', self.registry.get_help('foo'))
2502
def test_dont_register_illegal_name(self):
2503
self.assertRaises(errors.IllegalOptionName,
2504
self.registry.register, config.Option(' foo'))
2505
self.assertRaises(errors.IllegalOptionName,
2506
self.registry.register, config.Option('bar,'))
2508
lazy_option = config.Option('lazy_foo', help='Lazy help')
2510
def test_register_lazy(self):
2511
self.registry.register_lazy('lazy_foo', self.__module__,
2512
'TestOptionRegistry.lazy_option')
2513
self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
2515
def test_registered_lazy_help(self):
2516
self.registry.register_lazy('lazy_foo', self.__module__,
2517
'TestOptionRegistry.lazy_option')
2518
self.assertEqual('Lazy help', self.registry.get_help('lazy_foo'))
2520
def test_dont_lazy_register_illegal_name(self):
2521
# This is where the root cause of http://pad.lv/1235099 is better
2522
# understood: 'register_lazy' doc string mentions that key should match
2523
# the option name which indirectly requires that the option name is a
2524
# valid python identifier. We violate that rule here (using a key that
2525
# doesn't match the option name) to test the option name checking.
2526
self.assertRaises(errors.IllegalOptionName,
2527
self.registry.register_lazy, ' foo', self.__module__,
2528
'TestOptionRegistry.lazy_option')
2529
self.assertRaises(errors.IllegalOptionName,
2530
self.registry.register_lazy, '1,2', self.__module__,
2531
'TestOptionRegistry.lazy_option')
2534
class TestRegisteredOptions(tests.TestCase):
2535
"""All registered options should verify some constraints."""
2537
scenarios = [(key, {'option_name': key, 'option': option}) for key, option
2538
in config.option_registry.iteritems()]
2541
super(TestRegisteredOptions, self).setUp()
2542
self.registry = config.option_registry
2544
def test_proper_name(self):
2545
# An option should be registered under its own name, this can't be
2546
# checked at registration time for the lazy ones.
2547
self.assertEqual(self.option_name, self.option.name)
2549
def test_help_is_set(self):
2550
option_help = self.registry.get_help(self.option_name)
2551
# Come on, think about the user, he really wants to know what the
2553
self.assertIsNot(None, option_help)
2554
self.assertNotEqual('', option_help)
2557
class TestSection(tests.TestCase):
2559
# FIXME: Parametrize so that all sections produced by Stores run these
2560
# tests -- vila 2011-04-01
2562
def test_get_a_value(self):
2563
a_dict = dict(foo='bar')
2564
section = config.Section('myID', a_dict)
2565
self.assertEqual('bar', section.get('foo'))
2567
def test_get_unknown_option(self):
2569
section = config.Section(None, a_dict)
2570
self.assertEqual('out of thin air',
2571
section.get('foo', 'out of thin air'))
2573
def test_options_is_shared(self):
2575
section = config.Section(None, a_dict)
2576
self.assertIs(a_dict, section.options)
2579
class TestMutableSection(tests.TestCase):
2581
scenarios = [('mutable',
2583
lambda opts: config.MutableSection('myID', opts)},),
2587
a_dict = dict(foo='bar')
2588
section = self.get_section(a_dict)
2589
section.set('foo', 'new_value')
2590
self.assertEqual('new_value', section.get('foo'))
2591
# The change appears in the shared section
2592
self.assertEqual('new_value', a_dict.get('foo'))
2593
# We keep track of the change
2594
self.assertTrue('foo' in section.orig)
2595
self.assertEqual('bar', section.orig.get('foo'))
2597
def test_set_preserve_original_once(self):
2598
a_dict = dict(foo='bar')
2599
section = self.get_section(a_dict)
2600
section.set('foo', 'first_value')
2601
section.set('foo', 'second_value')
2602
# We keep track of the original value
2603
self.assertTrue('foo' in section.orig)
2604
self.assertEqual('bar', section.orig.get('foo'))
2606
def test_remove(self):
2607
a_dict = dict(foo='bar')
2608
section = self.get_section(a_dict)
2609
section.remove('foo')
2610
# We get None for unknown options via the default value
2611
self.assertEqual(None, section.get('foo'))
2612
# Or we just get the default value
2613
self.assertEqual('unknown', section.get('foo', 'unknown'))
2614
self.assertFalse('foo' in section.options)
2615
# We keep track of the deletion
2616
self.assertTrue('foo' in section.orig)
2617
self.assertEqual('bar', section.orig.get('foo'))
2619
def test_remove_new_option(self):
2621
section = self.get_section(a_dict)
2622
section.set('foo', 'bar')
2623
section.remove('foo')
2624
self.assertFalse('foo' in section.options)
2625
# The option didn't exist initially so it we need to keep track of it
2626
# with a special value
2627
self.assertTrue('foo' in section.orig)
2628
self.assertEqual(config._NewlyCreatedOption, section.orig['foo'])
2631
class TestCommandLineStore(tests.TestCase):
2634
super(TestCommandLineStore, self).setUp()
2635
self.store = config.CommandLineStore()
2636
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2638
def get_section(self):
2639
"""Get the unique section for the command line overrides."""
2640
sections = list(self.store.get_sections())
2641
self.assertLength(1, sections)
2642
store, section = sections[0]
2643
self.assertEqual(self.store, store)
2646
def test_no_override(self):
2647
self.store._from_cmdline([])
2648
section = self.get_section()
2649
self.assertLength(0, list(section.iter_option_names()))
2651
def test_simple_override(self):
2652
self.store._from_cmdline(['a=b'])
2653
section = self.get_section()
2654
self.assertEqual('b', section.get('a'))
2656
def test_list_override(self):
2657
opt = config.ListOption('l')
2658
config.option_registry.register(opt)
2659
self.store._from_cmdline(['l=1,2,3'])
2660
val = self.get_section().get('l')
2661
self.assertEqual('1,2,3', val)
2662
# Reminder: lists should be registered as such explicitely, otherwise
2663
# the conversion needs to be done afterwards.
2664
self.assertEqual(['1', '2', '3'],
2665
opt.convert_from_unicode(self.store, val))
2667
def test_multiple_overrides(self):
2668
self.store._from_cmdline(['a=b', 'x=y'])
2669
section = self.get_section()
2670
self.assertEqual('b', section.get('a'))
2671
self.assertEqual('y', section.get('x'))
2673
def test_wrong_syntax(self):
2674
self.assertRaises(errors.BzrCommandError,
2675
self.store._from_cmdline, ['a=b', 'c'])
2677
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
2679
scenarios = [(key, {'get_store': builder}) for key, builder
2680
in config.test_store_builder_registry.iteritems()] + [
2681
('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
2684
store = self.get_store(self)
2685
if isinstance(store, config.TransportIniFileStore):
2686
raise tests.TestNotApplicable(
2687
"%s is not a concrete Store implementation"
2688
" so it doesn't need an id" % (store.__class__.__name__,))
2689
self.assertIsNot(None, store.id)
2692
class TestStore(tests.TestCaseWithTransport):
2694
def assertSectionContent(self, expected, store_and_section):
2695
"""Assert that some options have the proper values in a section."""
2696
_, section = store_and_section
2697
expected_name, expected_options = expected
2698
self.assertEqual(expected_name, section.id)
2701
dict([(k, section.get(k)) for k in expected_options.keys()]))
2704
class TestReadonlyStore(TestStore):
2706
scenarios = [(key, {'get_store': builder}) for key, builder
2707
in config.test_store_builder_registry.iteritems()]
2709
def test_building_delays_load(self):
2710
store = self.get_store(self)
2711
self.assertEqual(False, store.is_loaded())
2712
store._load_from_string('')
2713
self.assertEqual(True, store.is_loaded())
2715
def test_get_no_sections_for_empty(self):
2716
store = self.get_store(self)
2717
store._load_from_string('')
2718
self.assertEqual([], list(store.get_sections()))
2720
def test_get_default_section(self):
2721
store = self.get_store(self)
2722
store._load_from_string('foo=bar')
2723
sections = list(store.get_sections())
2724
self.assertLength(1, sections)
2725
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2727
def test_get_named_section(self):
2728
store = self.get_store(self)
2729
store._load_from_string('[baz]\nfoo=bar')
2730
sections = list(store.get_sections())
2731
self.assertLength(1, sections)
2732
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2734
def test_load_from_string_fails_for_non_empty_store(self):
2735
store = self.get_store(self)
2736
store._load_from_string('foo=bar')
2737
self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
2740
class TestStoreQuoting(TestStore):
2742
scenarios = [(key, {'get_store': builder}) for key, builder
2743
in config.test_store_builder_registry.iteritems()]
2746
super(TestStoreQuoting, self).setUp()
2747
self.store = self.get_store(self)
2748
# We need a loaded store but any content will do
2749
self.store._load_from_string('')
2751
def assertIdempotent(self, s):
2752
"""Assert that quoting an unquoted string is a no-op and vice-versa.
2754
What matters here is that option values, as they appear in a store, can
2755
be safely round-tripped out of the store and back.
2757
:param s: A string, quoted if required.
2759
self.assertEqual(s, self.store.quote(self.store.unquote(s)))
2760
self.assertEqual(s, self.store.unquote(self.store.quote(s)))
2762
def test_empty_string(self):
2763
if isinstance(self.store, config.IniFileStore):
2764
# configobj._quote doesn't handle empty values
2765
self.assertRaises(AssertionError,
2766
self.assertIdempotent, '')
2768
self.assertIdempotent('')
2769
# But quoted empty strings are ok
2770
self.assertIdempotent('""')
2772
def test_embedded_spaces(self):
2773
self.assertIdempotent('" a b c "')
2775
def test_embedded_commas(self):
2776
self.assertIdempotent('" a , b c "')
2778
def test_simple_comma(self):
2779
if isinstance(self.store, config.IniFileStore):
2780
# configobj requires that lists are special-cased
2781
self.assertRaises(AssertionError,
2782
self.assertIdempotent, ',')
2784
self.assertIdempotent(',')
2785
# When a single comma is required, quoting is also required
2786
self.assertIdempotent('","')
2788
def test_list(self):
2789
if isinstance(self.store, config.IniFileStore):
2790
# configobj requires that lists are special-cased
2791
self.assertRaises(AssertionError,
2792
self.assertIdempotent, 'a,b')
2794
self.assertIdempotent('a,b')
2797
class TestDictFromStore(tests.TestCase):
2799
def test_unquote_not_string(self):
2800
conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
2801
value = conf.get('a_section')
2802
# Urgh, despite 'conf' asking for the no-name section, we get the
2803
# content of another section as a dict o_O
2804
self.assertEqual({'a': '1'}, value)
2805
unquoted = conf.store.unquote(value)
2806
# Which cannot be unquoted but shouldn't crash either (the use cases
2807
# are getting the value or displaying it. In the later case, '%s' will
2809
self.assertEqual({'a': '1'}, unquoted)
2810
self.assertEqual("{u'a': u'1'}", '%s' % (unquoted,))
2813
class TestIniFileStoreContent(tests.TestCaseWithTransport):
2814
"""Simulate loading a config store with content of various encodings.
2816
All files produced by bzr are in utf8 content.
2818
Users may modify them manually and end up with a file that can't be
2819
loaded. We need to issue proper error messages in this case.
2822
invalid_utf8_char = '\xff'
2824
def test_load_utf8(self):
2825
"""Ensure we can load an utf8-encoded file."""
2826
t = self.get_transport()
2827
# From http://pad.lv/799212
2828
unicode_user = u'b\N{Euro Sign}ar'
2829
unicode_content = u'user=%s' % (unicode_user,)
2830
utf8_content = unicode_content.encode('utf8')
2831
# Store the raw content in the config file
2832
t.put_bytes('foo.conf', utf8_content)
2833
store = config.TransportIniFileStore(t, 'foo.conf')
2835
stack = config.Stack([store.get_sections], store)
2836
self.assertEqual(unicode_user, stack.get('user'))
2838
def test_load_non_ascii(self):
2839
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2840
t = self.get_transport()
2841
t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
2842
store = config.TransportIniFileStore(t, 'foo.conf')
2843
self.assertRaises(errors.ConfigContentError, store.load)
2845
def test_load_erroneous_content(self):
2846
"""Ensure we display a proper error on content that can't be parsed."""
2847
t = self.get_transport()
2848
t.put_bytes('foo.conf', '[open_section\n')
2849
store = config.TransportIniFileStore(t, 'foo.conf')
2850
self.assertRaises(errors.ParseConfigError, store.load)
2852
def test_load_permission_denied(self):
2853
"""Ensure we get warned when trying to load an inaccessible file."""
2856
warnings.append(args[0] % args[1:])
2857
self.overrideAttr(trace, 'warning', warning)
2859
t = self.get_transport()
2861
def get_bytes(relpath):
2862
raise errors.PermissionDenied(relpath, "")
2863
t.get_bytes = get_bytes
2864
store = config.TransportIniFileStore(t, 'foo.conf')
2865
self.assertRaises(errors.PermissionDenied, store.load)
2868
[u'Permission denied while trying to load configuration store %s.'
2869
% store.external_url()])
2872
class TestIniConfigContent(tests.TestCaseWithTransport):
2873
"""Simulate loading a IniBasedConfig with content of various encodings.
2875
All files produced by bzr are in utf8 content.
2877
Users may modify them manually and end up with a file that can't be
2878
loaded. We need to issue proper error messages in this case.
2881
invalid_utf8_char = '\xff'
2883
def test_load_utf8(self):
2884
"""Ensure we can load an utf8-encoded file."""
2885
# From http://pad.lv/799212
2886
unicode_user = u'b\N{Euro Sign}ar'
2887
unicode_content = u'user=%s' % (unicode_user,)
2888
utf8_content = unicode_content.encode('utf8')
2889
# Store the raw content in the config file
2890
with open('foo.conf', 'wb') as f:
2891
f.write(utf8_content)
2892
conf = config.IniBasedConfig(file_name='foo.conf')
2893
self.assertEqual(unicode_user, conf.get_user_option('user'))
2895
def test_load_badly_encoded_content(self):
2896
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2897
with open('foo.conf', 'wb') as f:
2898
f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
2899
conf = config.IniBasedConfig(file_name='foo.conf')
2900
self.assertRaises(errors.ConfigContentError, conf._get_parser)
2902
def test_load_erroneous_content(self):
2903
"""Ensure we display a proper error on content that can't be parsed."""
2904
with open('foo.conf', 'wb') as f:
2905
f.write('[open_section\n')
2906
conf = config.IniBasedConfig(file_name='foo.conf')
2907
self.assertRaises(errors.ParseConfigError, conf._get_parser)
2910
class TestMutableStore(TestStore):
2912
scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
2913
in config.test_store_builder_registry.iteritems()]
2916
super(TestMutableStore, self).setUp()
2917
self.transport = self.get_transport()
2919
def has_store(self, store):
2920
store_basename = urlutils.relative_url(self.transport.external_url(),
2921
store.external_url())
2922
return self.transport.has(store_basename)
2924
def test_save_empty_creates_no_file(self):
2925
# FIXME: There should be a better way than relying on the test
2926
# parametrization to identify branch.conf -- vila 2011-0526
2927
if self.store_id in ('branch', 'remote_branch'):
2928
raise tests.TestNotApplicable(
2929
'branch.conf is *always* created when a branch is initialized')
2930
store = self.get_store(self)
2932
self.assertEqual(False, self.has_store(store))
2934
def test_mutable_section_shared(self):
2935
store = self.get_store(self)
2936
store._load_from_string('foo=bar\n')
2937
# FIXME: There should be a better way than relying on the test
2938
# parametrization to identify branch.conf -- vila 2011-0526
2939
if self.store_id in ('branch', 'remote_branch'):
2940
# branch stores requires write locked branches
2941
self.addCleanup(store.branch.lock_write().unlock)
2942
section1 = store.get_mutable_section(None)
2943
section2 = store.get_mutable_section(None)
2944
# If we get different sections, different callers won't share the
2946
self.assertIs(section1, section2)
2948
def test_save_emptied_succeeds(self):
2949
store = self.get_store(self)
2950
store._load_from_string('foo=bar\n')
2951
# FIXME: There should be a better way than relying on the test
2952
# parametrization to identify branch.conf -- vila 2011-0526
2953
if self.store_id in ('branch', 'remote_branch'):
2954
# branch stores requires write locked branches
2955
self.addCleanup(store.branch.lock_write().unlock)
2956
section = store.get_mutable_section(None)
2957
section.remove('foo')
2959
self.assertEqual(True, self.has_store(store))
2960
modified_store = self.get_store(self)
2961
sections = list(modified_store.get_sections())
2962
self.assertLength(0, sections)
2964
def test_save_with_content_succeeds(self):
2965
# FIXME: There should be a better way than relying on the test
2966
# parametrization to identify branch.conf -- vila 2011-0526
2967
if self.store_id in ('branch', 'remote_branch'):
2968
raise tests.TestNotApplicable(
2969
'branch.conf is *always* created when a branch is initialized')
2970
store = self.get_store(self)
2971
store._load_from_string('foo=bar\n')
2972
self.assertEqual(False, self.has_store(store))
2974
self.assertEqual(True, self.has_store(store))
2975
modified_store = self.get_store(self)
2976
sections = list(modified_store.get_sections())
2977
self.assertLength(1, sections)
2978
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2980
def test_set_option_in_empty_store(self):
2981
store = self.get_store(self)
2982
# FIXME: There should be a better way than relying on the test
2983
# parametrization to identify branch.conf -- vila 2011-0526
2984
if self.store_id in ('branch', 'remote_branch'):
2985
# branch stores requires write locked branches
2986
self.addCleanup(store.branch.lock_write().unlock)
2987
section = store.get_mutable_section(None)
2988
section.set('foo', 'bar')
2990
modified_store = self.get_store(self)
2991
sections = list(modified_store.get_sections())
2992
self.assertLength(1, sections)
2993
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2995
def test_set_option_in_default_section(self):
2996
store = self.get_store(self)
2997
store._load_from_string('')
2998
# FIXME: There should be a better way than relying on the test
2999
# parametrization to identify branch.conf -- vila 2011-0526
3000
if self.store_id in ('branch', 'remote_branch'):
3001
# branch stores requires write locked branches
3002
self.addCleanup(store.branch.lock_write().unlock)
3003
section = store.get_mutable_section(None)
3004
section.set('foo', 'bar')
3006
modified_store = self.get_store(self)
3007
sections = list(modified_store.get_sections())
3008
self.assertLength(1, sections)
3009
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
3011
def test_set_option_in_named_section(self):
3012
store = self.get_store(self)
3013
store._load_from_string('')
3014
# FIXME: There should be a better way than relying on the test
3015
# parametrization to identify branch.conf -- vila 2011-0526
3016
if self.store_id in ('branch', 'remote_branch'):
3017
# branch stores requires write locked branches
3018
self.addCleanup(store.branch.lock_write().unlock)
3019
section = store.get_mutable_section('baz')
3020
section.set('foo', 'bar')
3022
modified_store = self.get_store(self)
3023
sections = list(modified_store.get_sections())
3024
self.assertLength(1, sections)
3025
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
3027
def test_load_hook(self):
3028
# First, we need to ensure that the store exists
3029
store = self.get_store(self)
3030
# FIXME: There should be a better way than relying on the test
3031
# parametrization to identify branch.conf -- vila 2011-0526
3032
if self.store_id in ('branch', 'remote_branch'):
3033
# branch stores requires write locked branches
3034
self.addCleanup(store.branch.lock_write().unlock)
3035
section = store.get_mutable_section('baz')
3036
section.set('foo', 'bar')
3038
# Now we can try to load it
3039
store = self.get_store(self)
3043
config.ConfigHooks.install_named_hook('load', hook, None)
3044
self.assertLength(0, calls)
3046
self.assertLength(1, calls)
3047
self.assertEqual((store,), calls[0])
3049
def test_save_hook(self):
3053
config.ConfigHooks.install_named_hook('save', hook, None)
3054
self.assertLength(0, calls)
3055
store = self.get_store(self)
3056
# FIXME: There should be a better way than relying on the test
3057
# parametrization to identify branch.conf -- vila 2011-0526
3058
if self.store_id in ('branch', 'remote_branch'):
3059
# branch stores requires write locked branches
3060
self.addCleanup(store.branch.lock_write().unlock)
3061
section = store.get_mutable_section('baz')
3062
section.set('foo', 'bar')
3064
self.assertLength(1, calls)
3065
self.assertEqual((store,), calls[0])
3067
def test_set_mark_dirty(self):
3068
stack = config.MemoryStack('')
3069
self.assertLength(0, stack.store.dirty_sections)
3070
stack.set('foo', 'baz')
3071
self.assertLength(1, stack.store.dirty_sections)
3072
self.assertTrue(stack.store._need_saving())
3074
def test_remove_mark_dirty(self):
3075
stack = config.MemoryStack('foo=bar')
3076
self.assertLength(0, stack.store.dirty_sections)
3078
self.assertLength(1, stack.store.dirty_sections)
3079
self.assertTrue(stack.store._need_saving())
3082
class TestStoreSaveChanges(tests.TestCaseWithTransport):
3083
"""Tests that config changes are kept in memory and saved on-demand."""
3086
super(TestStoreSaveChanges, self).setUp()
3087
self.transport = self.get_transport()
3088
# Most of the tests involve two stores pointing to the same persistent
3089
# storage to observe the effects of concurrent changes
3090
self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
3091
self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
3094
self.warnings.append(args[0] % args[1:])
3095
self.overrideAttr(trace, 'warning', warning)
3097
def has_store(self, store):
3098
store_basename = urlutils.relative_url(self.transport.external_url(),
3099
store.external_url())
3100
return self.transport.has(store_basename)
3102
def get_stack(self, store):
3103
# Any stack will do as long as it uses the right store, just a single
3104
# no-name section is enough
3105
return config.Stack([store.get_sections], store)
3107
def test_no_changes_no_save(self):
3108
s = self.get_stack(self.st1)
3109
s.store.save_changes()
3110
self.assertEqual(False, self.has_store(self.st1))
3112
def test_unrelated_concurrent_update(self):
3113
s1 = self.get_stack(self.st1)
3114
s2 = self.get_stack(self.st2)
3115
s1.set('foo', 'bar')
3116
s2.set('baz', 'quux')
3118
# Changes don't propagate magically
3119
self.assertEqual(None, s1.get('baz'))
3120
s2.store.save_changes()
3121
self.assertEqual('quux', s2.get('baz'))
3122
# Changes are acquired when saving
3123
self.assertEqual('bar', s2.get('foo'))
3124
# Since there is no overlap, no warnings are emitted
3125
self.assertLength(0, self.warnings)
3127
def test_concurrent_update_modified(self):
3128
s1 = self.get_stack(self.st1)
3129
s2 = self.get_stack(self.st2)
3130
s1.set('foo', 'bar')
3131
s2.set('foo', 'baz')
3134
s2.store.save_changes()
3135
self.assertEqual('baz', s2.get('foo'))
3136
# But the user get a warning
3137
self.assertLength(1, self.warnings)
3138
warning = self.warnings[0]
3139
self.assertStartsWith(warning, 'Option foo in section None')
3140
self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
3141
' The baz value will be saved.')
3143
def test_concurrent_deletion(self):
3144
self.st1._load_from_string('foo=bar')
3146
s1 = self.get_stack(self.st1)
3147
s2 = self.get_stack(self.st2)
3150
s1.store.save_changes()
3152
self.assertLength(0, self.warnings)
3153
s2.store.save_changes()
3155
self.assertLength(1, self.warnings)
3156
warning = self.warnings[0]
3157
self.assertStartsWith(warning, 'Option foo in section None')
3158
self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
3159
' The <DELETED> value will be saved.')
3162
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
3164
def get_store(self):
3165
return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3167
def test_get_quoted_string(self):
3168
store = self.get_store()
3169
store._load_from_string('foo= " abc "')
3170
stack = config.Stack([store.get_sections])
3171
self.assertEqual(' abc ', stack.get('foo'))
3173
def test_set_quoted_string(self):
3174
store = self.get_store()
3175
stack = config.Stack([store.get_sections], store)
3176
stack.set('foo', ' a b c ')
3178
self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
3181
class TestTransportIniFileStore(TestStore):
3183
def test_loading_unknown_file_fails(self):
3184
store = config.TransportIniFileStore(self.get_transport(),
3186
self.assertRaises(errors.NoSuchFile, store.load)
3188
def test_invalid_content(self):
3189
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3190
self.assertEqual(False, store.is_loaded())
3191
exc = self.assertRaises(
3192
errors.ParseConfigError, store._load_from_string,
3193
'this is invalid !')
3194
self.assertEndsWith(exc.filename, 'foo.conf')
3195
# And the load failed
3196
self.assertEqual(False, store.is_loaded())
3198
def test_get_embedded_sections(self):
3199
# A more complicated example (which also shows that section names and
3200
# option names share the same name space...)
3201
# FIXME: This should be fixed by forbidding dicts as values ?
3202
# -- vila 2011-04-05
3203
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3204
store._load_from_string('''
3208
foo_in_DEFAULT=foo_DEFAULT
3216
sections = list(store.get_sections())
3217
self.assertLength(4, sections)
3218
# The default section has no name.
3219
# List values are provided as strings and need to be explicitly
3220
# converted by specifying from_unicode=list_from_store at option
3222
self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
3224
self.assertSectionContent(
3225
('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
3226
self.assertSectionContent(
3227
('bar', {'foo_in_bar': 'barbar'}), sections[2])
3228
# sub sections are provided as embedded dicts.
3229
self.assertSectionContent(
3230
('baz', {'foo_in_baz': 'barbaz', 'qux': {'foo_in_qux': 'quux'}}),
3234
class TestLockableIniFileStore(TestStore):
3236
def test_create_store_in_created_dir(self):
3237
self.assertPathDoesNotExist('dir')
3238
t = self.get_transport('dir/subdir')
3239
store = config.LockableIniFileStore(t, 'foo.conf')
3240
store.get_mutable_section(None).set('foo', 'bar')
3242
self.assertPathExists('dir/subdir')
3245
class TestConcurrentStoreUpdates(TestStore):
3246
"""Test that Stores properly handle conccurent updates.
3248
New Store implementation may fail some of these tests but until such
3249
implementations exist it's hard to properly filter them from the scenarios
3250
applied here. If you encounter such a case, contact the bzr devs.
3253
scenarios = [(key, {'get_stack': builder}) for key, builder
3254
in config.test_stack_builder_registry.iteritems()]
3257
super(TestConcurrentStoreUpdates, self).setUp()
3258
self.stack = self.get_stack(self)
3259
if not isinstance(self.stack, config._CompatibleStack):
3260
raise tests.TestNotApplicable(
3261
'%s is not meant to be compatible with the old config design'
3263
self.stack.set('one', '1')
3264
self.stack.set('two', '2')
3266
self.stack.store.save()
3268
def test_simple_read_access(self):
3269
self.assertEqual('1', self.stack.get('one'))
3271
def test_simple_write_access(self):
3272
self.stack.set('one', 'one')
3273
self.assertEqual('one', self.stack.get('one'))
3275
def test_listen_to_the_last_speaker(self):
3277
c2 = self.get_stack(self)
3278
c1.set('one', 'ONE')
3279
c2.set('two', 'TWO')
3280
self.assertEqual('ONE', c1.get('one'))
3281
self.assertEqual('TWO', c2.get('two'))
3282
# The second update respect the first one
3283
self.assertEqual('ONE', c2.get('one'))
3285
def test_last_speaker_wins(self):
3286
# If the same config is not shared, the same variable modified twice
3287
# can only see a single result.
3289
c2 = self.get_stack(self)
3292
self.assertEqual('c2', c2.get('one'))
3293
# The first modification is still available until another refresh
3295
self.assertEqual('c1', c1.get('one'))
3296
c1.set('two', 'done')
3297
self.assertEqual('c2', c1.get('one'))
3299
def test_writes_are_serialized(self):
3301
c2 = self.get_stack(self)
3303
# We spawn a thread that will pause *during* the config saving.
3304
before_writing = threading.Event()
3305
after_writing = threading.Event()
3306
writing_done = threading.Event()
3307
c1_save_without_locking_orig = c1.store.save_without_locking
3308
def c1_save_without_locking():
3309
before_writing.set()
3310
c1_save_without_locking_orig()
3311
# The lock is held. We wait for the main thread to decide when to
3313
after_writing.wait()
3314
c1.store.save_without_locking = c1_save_without_locking
3318
t1 = threading.Thread(target=c1_set)
3319
# Collect the thread after the test
3320
self.addCleanup(t1.join)
3321
# Be ready to unblock the thread if the test goes wrong
3322
self.addCleanup(after_writing.set)
3324
before_writing.wait()
3325
self.assertRaises(errors.LockContention,
3326
c2.set, 'one', 'c2')
3327
self.assertEqual('c1', c1.get('one'))
3328
# Let the lock be released
3332
self.assertEqual('c2', c2.get('one'))
3334
def test_read_while_writing(self):
3336
# We spawn a thread that will pause *during* the write
3337
ready_to_write = threading.Event()
3338
do_writing = threading.Event()
3339
writing_done = threading.Event()
3340
# We override the _save implementation so we know the store is locked
3341
c1_save_without_locking_orig = c1.store.save_without_locking
3342
def c1_save_without_locking():
3343
ready_to_write.set()
3344
# The lock is held. We wait for the main thread to decide when to
3347
c1_save_without_locking_orig()
3349
c1.store.save_without_locking = c1_save_without_locking
3352
t1 = threading.Thread(target=c1_set)
3353
# Collect the thread after the test
3354
self.addCleanup(t1.join)
3355
# Be ready to unblock the thread if the test goes wrong
3356
self.addCleanup(do_writing.set)
3358
# Ensure the thread is ready to write
3359
ready_to_write.wait()
3360
self.assertEqual('c1', c1.get('one'))
3361
# If we read during the write, we get the old value
3362
c2 = self.get_stack(self)
3363
self.assertEqual('1', c2.get('one'))
3364
# Let the writing occur and ensure it occurred
3367
# Now we get the updated value
3368
c3 = self.get_stack(self)
3369
self.assertEqual('c1', c3.get('one'))
3371
# FIXME: It may be worth looking into removing the lock dir when it's not
3372
# needed anymore and look at possible fallouts for concurrent lockers. This
3373
# will matter if/when we use config files outside of bazaar directories
3374
# (.bazaar or .bzr) -- vila 20110-04-111
3377
class TestSectionMatcher(TestStore):
3379
scenarios = [('location', {'matcher': config.LocationMatcher}),
3380
('id', {'matcher': config.NameMatcher}),]
3383
super(TestSectionMatcher, self).setUp()
3384
# Any simple store is good enough
3385
self.get_store = config.test_store_builder_registry.get('configobj')
3387
def test_no_matches_for_empty_stores(self):
3388
store = self.get_store(self)
3389
store._load_from_string('')
3390
matcher = self.matcher(store, '/bar')
3391
self.assertEqual([], list(matcher.get_sections()))
3393
def test_build_doesnt_load_store(self):
3394
store = self.get_store(self)
3395
self.matcher(store, '/bar')
3396
self.assertFalse(store.is_loaded())
3399
class TestLocationSection(tests.TestCase):
3401
def get_section(self, options, extra_path):
3402
section = config.Section('foo', options)
3403
return config.LocationSection(section, extra_path)
3405
def test_simple_option(self):
3406
section = self.get_section({'foo': 'bar'}, '')
3407
self.assertEqual('bar', section.get('foo'))
3409
def test_option_with_extra_path(self):
3410
section = self.get_section({'foo': 'bar', 'foo:policy': 'appendpath'},
3412
self.assertEqual('bar/baz', section.get('foo'))
3414
def test_invalid_policy(self):
3415
section = self.get_section({'foo': 'bar', 'foo:policy': 'die'},
3417
# invalid policies are ignored
3418
self.assertEqual('bar', section.get('foo'))
3421
class TestLocationMatcher(TestStore):
3424
super(TestLocationMatcher, self).setUp()
3425
# Any simple store is good enough
3426
self.get_store = config.test_store_builder_registry.get('configobj')
3428
def test_unrelated_section_excluded(self):
3429
store = self.get_store(self)
3430
store._load_from_string('''
3438
section=/foo/bar/baz
3442
self.assertEqual(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
3444
[section.id for _, section in store.get_sections()])
3445
matcher = config.LocationMatcher(store, '/foo/bar/quux')
3446
sections = [section for _, section in matcher.get_sections()]
3447
self.assertEqual(['/foo/bar', '/foo'],
3448
[section.id for section in sections])
3449
self.assertEqual(['quux', 'bar/quux'],
3450
[section.extra_path for section in sections])
3452
def test_more_specific_sections_first(self):
3453
store = self.get_store(self)
3454
store._load_from_string('''
3460
self.assertEqual(['/foo', '/foo/bar'],
3461
[section.id for _, section in store.get_sections()])
3462
matcher = config.LocationMatcher(store, '/foo/bar/baz')
3463
sections = [section for _, section in matcher.get_sections()]
3464
self.assertEqual(['/foo/bar', '/foo'],
3465
[section.id for section in sections])
3466
self.assertEqual(['baz', 'bar/baz'],
3467
[section.extra_path for section in sections])
3469
def test_appendpath_in_no_name_section(self):
3470
# It's a bit weird to allow appendpath in a no-name section, but
3471
# someone may found a use for it
3472
store = self.get_store(self)
3473
store._load_from_string('''
3475
foo:policy = appendpath
3477
matcher = config.LocationMatcher(store, 'dir/subdir')
3478
sections = list(matcher.get_sections())
3479
self.assertLength(1, sections)
3480
self.assertEqual('bar/dir/subdir', sections[0][1].get('foo'))
3482
def test_file_urls_are_normalized(self):
3483
store = self.get_store(self)
3484
if sys.platform == 'win32':
3485
expected_url = 'file:///C:/dir/subdir'
3486
expected_location = 'C:/dir/subdir'
3488
expected_url = 'file:///dir/subdir'
3489
expected_location = '/dir/subdir'
3490
matcher = config.LocationMatcher(store, expected_url)
3491
self.assertEqual(expected_location, matcher.location)
3493
def test_branch_name_colo(self):
3494
store = self.get_store(self)
3495
store._load_from_string(dedent("""\
3497
push_location=my{branchname}
3499
matcher = config.LocationMatcher(store, 'file:///,branch=example%3c')
3500
self.assertEqual('example<', matcher.branch_name)
3501
((_, section),) = matcher.get_sections()
3502
self.assertEqual('example<', section.locals['branchname'])
3504
def test_branch_name_basename(self):
3505
store = self.get_store(self)
3506
store._load_from_string(dedent("""\
3508
push_location=my{branchname}
3510
matcher = config.LocationMatcher(store, 'file:///parent/example%3c')
3511
self.assertEqual('example<', matcher.branch_name)
3512
((_, section),) = matcher.get_sections()
3513
self.assertEqual('example<', section.locals['branchname'])
3516
class TestStartingPathMatcher(TestStore):
3519
super(TestStartingPathMatcher, self).setUp()
3520
# Any simple store is good enough
3521
self.store = config.IniFileStore()
3523
def assertSectionIDs(self, expected, location, content):
3524
self.store._load_from_string(content)
3525
matcher = config.StartingPathMatcher(self.store, location)
3526
sections = list(matcher.get_sections())
3527
self.assertLength(len(expected), sections)
3528
self.assertEqual(expected, [section.id for _, section in sections])
3531
def test_empty(self):
3532
self.assertSectionIDs([], self.get_url(), '')
3534
def test_url_vs_local_paths(self):
3535
# The matcher location is an url and the section names are local paths
3536
self.assertSectionIDs(['/foo/bar', '/foo'],
3537
'file:///foo/bar/baz', '''\
3542
def test_local_path_vs_url(self):
3543
# The matcher location is a local path and the section names are urls
3544
self.assertSectionIDs(['file:///foo/bar', 'file:///foo'],
3545
'/foo/bar/baz', '''\
3551
def test_no_name_section_included_when_present(self):
3552
# Note that other tests will cover the case where the no-name section
3553
# is empty and as such, not included.
3554
sections = self.assertSectionIDs(['/foo/bar', '/foo', None],
3555
'/foo/bar/baz', '''\
3556
option = defined so the no-name section exists
3560
self.assertEqual(['baz', 'bar/baz', '/foo/bar/baz'],
3561
[s.locals['relpath'] for _, s in sections])
3563
def test_order_reversed(self):
3564
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3569
def test_unrelated_section_excluded(self):
3570
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3576
def test_glob_included(self):
3577
sections = self.assertSectionIDs(['/foo/*/baz', '/foo/b*', '/foo'],
3578
'/foo/bar/baz', '''\
3584
# Note that 'baz' as a relpath for /foo/b* is not fully correct, but
3585
# nothing really is... as far using {relpath} to append it to something
3586
# else, this seems good enough though.
3587
self.assertEqual(['', 'baz', 'bar/baz'],
3588
[s.locals['relpath'] for _, s in sections])
3590
def test_respect_order(self):
3591
self.assertSectionIDs(['/foo', '/foo/b*', '/foo/*/baz'],
3592
'/foo/bar/baz', '''\
3600
class TestNameMatcher(TestStore):
3603
super(TestNameMatcher, self).setUp()
3604
self.matcher = config.NameMatcher
3605
# Any simple store is good enough
3606
self.get_store = config.test_store_builder_registry.get('configobj')
3608
def get_matching_sections(self, name):
3609
store = self.get_store(self)
3610
store._load_from_string('''
3618
matcher = self.matcher(store, name)
3619
return list(matcher.get_sections())
3621
def test_matching(self):
3622
sections = self.get_matching_sections('foo')
3623
self.assertLength(1, sections)
3624
self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
3626
def test_not_matching(self):
3627
sections = self.get_matching_sections('baz')
3628
self.assertLength(0, sections)
3631
class TestBaseStackGet(tests.TestCase):
3634
super(TestBaseStackGet, self).setUp()
3635
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3637
def test_get_first_definition(self):
3638
store1 = config.IniFileStore()
3639
store1._load_from_string('foo=bar')
3640
store2 = config.IniFileStore()
3641
store2._load_from_string('foo=baz')
3642
conf = config.Stack([store1.get_sections, store2.get_sections])
3643
self.assertEqual('bar', conf.get('foo'))
3645
def test_get_with_registered_default_value(self):
3646
config.option_registry.register(config.Option('foo', default='bar'))
3647
conf_stack = config.Stack([])
3648
self.assertEqual('bar', conf_stack.get('foo'))
3650
def test_get_without_registered_default_value(self):
3651
config.option_registry.register(config.Option('foo'))
3652
conf_stack = config.Stack([])
3653
self.assertEqual(None, conf_stack.get('foo'))
3655
def test_get_without_default_value_for_not_registered(self):
3656
conf_stack = config.Stack([])
3657
self.assertEqual(None, conf_stack.get('foo'))
3659
def test_get_for_empty_section_callable(self):
3660
conf_stack = config.Stack([lambda : []])
3661
self.assertEqual(None, conf_stack.get('foo'))
3663
def test_get_for_broken_callable(self):
3664
# Trying to use and invalid callable raises an exception on first use
3665
conf_stack = config.Stack([object])
3666
self.assertRaises(TypeError, conf_stack.get, 'foo')
3669
class TestStackWithSimpleStore(tests.TestCase):
3672
super(TestStackWithSimpleStore, self).setUp()
3673
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3674
self.registry = config.option_registry
3676
def get_conf(self, content=None):
3677
return config.MemoryStack(content)
3679
def test_override_value_from_env(self):
3680
self.overrideEnv('FOO', None)
3681
self.registry.register(
3682
config.Option('foo', default='bar', override_from_env=['FOO']))
3683
self.overrideEnv('FOO', 'quux')
3684
# Env variable provides a default taking over the option one
3685
conf = self.get_conf('foo=store')
3686
self.assertEqual('quux', conf.get('foo'))
3688
def test_first_override_value_from_env_wins(self):
3689
self.overrideEnv('NO_VALUE', None)
3690
self.overrideEnv('FOO', None)
3691
self.overrideEnv('BAZ', None)
3692
self.registry.register(
3693
config.Option('foo', default='bar',
3694
override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
3695
self.overrideEnv('FOO', 'foo')
3696
self.overrideEnv('BAZ', 'baz')
3697
# The first env var set wins
3698
conf = self.get_conf('foo=store')
3699
self.assertEqual('foo', conf.get('foo'))
3702
class TestMemoryStack(tests.TestCase):
3705
conf = config.MemoryStack('foo=bar')
3706
self.assertEqual('bar', conf.get('foo'))
3709
conf = config.MemoryStack('foo=bar')
3710
conf.set('foo', 'baz')
3711
self.assertEqual('baz', conf.get('foo'))
3713
def test_no_content(self):
3714
conf = config.MemoryStack()
3715
# No content means no loading
3716
self.assertFalse(conf.store.is_loaded())
3717
self.assertRaises(NotImplementedError, conf.get, 'foo')
3718
# But a content can still be provided
3719
conf.store._load_from_string('foo=bar')
3720
self.assertEqual('bar', conf.get('foo'))
3723
class TestStackIterSections(tests.TestCase):
3725
def test_empty_stack(self):
3726
conf = config.Stack([])
3727
sections = list(conf.iter_sections())
3728
self.assertLength(0, sections)
3730
def test_empty_store(self):
3731
store = config.IniFileStore()
3732
store._load_from_string('')
3733
conf = config.Stack([store.get_sections])
3734
sections = list(conf.iter_sections())
3735
self.assertLength(0, sections)
3737
def test_simple_store(self):
3738
store = config.IniFileStore()
3739
store._load_from_string('foo=bar')
3740
conf = config.Stack([store.get_sections])
3741
tuples = list(conf.iter_sections())
3742
self.assertLength(1, tuples)
3743
(found_store, found_section) = tuples[0]
3744
self.assertIs(store, found_store)
3746
def test_two_stores(self):
3747
store1 = config.IniFileStore()
3748
store1._load_from_string('foo=bar')
3749
store2 = config.IniFileStore()
3750
store2._load_from_string('bar=qux')
3751
conf = config.Stack([store1.get_sections, store2.get_sections])
3752
tuples = list(conf.iter_sections())
3753
self.assertLength(2, tuples)
3754
self.assertIs(store1, tuples[0][0])
3755
self.assertIs(store2, tuples[1][0])
3758
class TestStackWithTransport(tests.TestCaseWithTransport):
3760
scenarios = [(key, {'get_stack': builder}) for key, builder
3761
in config.test_stack_builder_registry.iteritems()]
3764
class TestConcreteStacks(TestStackWithTransport):
3766
def test_build_stack(self):
3767
# Just a smoke test to help debug builders
3768
self.get_stack(self)
3771
class TestStackGet(TestStackWithTransport):
3774
super(TestStackGet, self).setUp()
3775
self.conf = self.get_stack(self)
3777
def test_get_for_empty_stack(self):
3778
self.assertEqual(None, self.conf.get('foo'))
3780
def test_get_hook(self):
3781
self.conf.set('foo', 'bar')
3785
config.ConfigHooks.install_named_hook('get', hook, None)
3786
self.assertLength(0, calls)
3787
value = self.conf.get('foo')
3788
self.assertEqual('bar', value)
3789
self.assertLength(1, calls)
3790
self.assertEqual((self.conf, 'foo', 'bar'), calls[0])
3793
class TestStackGetWithConverter(tests.TestCase):
3796
super(TestStackGetWithConverter, self).setUp()
3797
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3798
self.registry = config.option_registry
3800
def get_conf(self, content=None):
3801
return config.MemoryStack(content)
3803
def register_bool_option(self, name, default=None, default_from_env=None):
3804
b = config.Option(name, help='A boolean.',
3805
default=default, default_from_env=default_from_env,
3806
from_unicode=config.bool_from_store)
3807
self.registry.register(b)
3809
def test_get_default_bool_None(self):
3810
self.register_bool_option('foo')
3811
conf = self.get_conf('')
3812
self.assertEqual(None, conf.get('foo'))
3814
def test_get_default_bool_True(self):
3815
self.register_bool_option('foo', u'True')
3816
conf = self.get_conf('')
3817
self.assertEqual(True, conf.get('foo'))
3819
def test_get_default_bool_False(self):
3820
self.register_bool_option('foo', False)
3821
conf = self.get_conf('')
3822
self.assertEqual(False, conf.get('foo'))
3824
def test_get_default_bool_False_as_string(self):
3825
self.register_bool_option('foo', u'False')
3826
conf = self.get_conf('')
3827
self.assertEqual(False, conf.get('foo'))
3829
def test_get_default_bool_from_env_converted(self):
3830
self.register_bool_option('foo', u'True', default_from_env=['FOO'])
3831
self.overrideEnv('FOO', 'False')
3832
conf = self.get_conf('')
3833
self.assertEqual(False, conf.get('foo'))
3835
def test_get_default_bool_when_conversion_fails(self):
3836
self.register_bool_option('foo', default='True')
3837
conf = self.get_conf('foo=invalid boolean')
3838
self.assertEqual(True, conf.get('foo'))
3840
def register_integer_option(self, name,
3841
default=None, default_from_env=None):
3842
i = config.Option(name, help='An integer.',
3843
default=default, default_from_env=default_from_env,
3844
from_unicode=config.int_from_store)
3845
self.registry.register(i)
3847
def test_get_default_integer_None(self):
3848
self.register_integer_option('foo')
3849
conf = self.get_conf('')
3850
self.assertEqual(None, conf.get('foo'))
3852
def test_get_default_integer(self):
3853
self.register_integer_option('foo', 42)
3854
conf = self.get_conf('')
3855
self.assertEqual(42, conf.get('foo'))
3857
def test_get_default_integer_as_string(self):
3858
self.register_integer_option('foo', u'42')
3859
conf = self.get_conf('')
3860
self.assertEqual(42, conf.get('foo'))
3862
def test_get_default_integer_from_env(self):
3863
self.register_integer_option('foo', default_from_env=['FOO'])
3864
self.overrideEnv('FOO', '18')
3865
conf = self.get_conf('')
3866
self.assertEqual(18, conf.get('foo'))
3868
def test_get_default_integer_when_conversion_fails(self):
3869
self.register_integer_option('foo', default='12')
3870
conf = self.get_conf('foo=invalid integer')
3871
self.assertEqual(12, conf.get('foo'))
3873
def register_list_option(self, name, default=None, default_from_env=None):
3874
l = config.ListOption(name, help='A list.', default=default,
3875
default_from_env=default_from_env)
3876
self.registry.register(l)
3878
def test_get_default_list_None(self):
3879
self.register_list_option('foo')
3880
conf = self.get_conf('')
3881
self.assertEqual(None, conf.get('foo'))
3883
def test_get_default_list_empty(self):
3884
self.register_list_option('foo', '')
3885
conf = self.get_conf('')
3886
self.assertEqual([], conf.get('foo'))
3888
def test_get_default_list_from_env(self):
3889
self.register_list_option('foo', default_from_env=['FOO'])
3890
self.overrideEnv('FOO', '')
3891
conf = self.get_conf('')
3892
self.assertEqual([], conf.get('foo'))
3894
def test_get_with_list_converter_no_item(self):
3895
self.register_list_option('foo', None)
3896
conf = self.get_conf('foo=,')
3897
self.assertEqual([], conf.get('foo'))
3899
def test_get_with_list_converter_many_items(self):
3900
self.register_list_option('foo', None)
3901
conf = self.get_conf('foo=m,o,r,e')
3902
self.assertEqual(['m', 'o', 'r', 'e'], conf.get('foo'))
3904
def test_get_with_list_converter_embedded_spaces_many_items(self):
3905
self.register_list_option('foo', None)
3906
conf = self.get_conf('foo=" bar", "baz "')
3907
self.assertEqual([' bar', 'baz '], conf.get('foo'))
3909
def test_get_with_list_converter_stripped_spaces_many_items(self):
3910
self.register_list_option('foo', None)
3911
conf = self.get_conf('foo= bar , baz ')
3912
self.assertEqual(['bar', 'baz'], conf.get('foo'))
3915
class TestIterOptionRefs(tests.TestCase):
3916
"""iter_option_refs is a bit unusual, document some cases."""
3918
def assertRefs(self, expected, string):
3919
self.assertEqual(expected, list(config.iter_option_refs(string)))
3921
def test_empty(self):
3922
self.assertRefs([(False, '')], '')
3924
def test_no_refs(self):
3925
self.assertRefs([(False, 'foo bar')], 'foo bar')
3927
def test_single_ref(self):
3928
self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
3930
def test_broken_ref(self):
3931
self.assertRefs([(False, '{foo')], '{foo')
3933
def test_embedded_ref(self):
3934
self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
3937
def test_two_refs(self):
3938
self.assertRefs([(False, ''), (True, '{foo}'),
3939
(False, ''), (True, '{bar}'),
3943
def test_newline_in_refs_are_not_matched(self):
3944
self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
3947
class TestStackExpandOptions(tests.TestCaseWithTransport):
3950
super(TestStackExpandOptions, self).setUp()
3951
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3952
self.registry = config.option_registry
3953
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3954
self.conf = config.Stack([store.get_sections], store)
3956
def assertExpansion(self, expected, string, env=None):
3957
self.assertEqual(expected, self.conf.expand_options(string, env))
3959
def test_no_expansion(self):
3960
self.assertExpansion('foo', 'foo')
3962
def test_expand_default_value(self):
3963
self.conf.store._load_from_string('bar=baz')
3964
self.registry.register(config.Option('foo', default=u'{bar}'))
3965
self.assertEqual('baz', self.conf.get('foo', expand=True))
3967
def test_expand_default_from_env(self):
3968
self.conf.store._load_from_string('bar=baz')
3969
self.registry.register(config.Option('foo', default_from_env=['FOO']))
3970
self.overrideEnv('FOO', '{bar}')
3971
self.assertEqual('baz', self.conf.get('foo', expand=True))
3973
def test_expand_default_on_failed_conversion(self):
3974
self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
3975
self.registry.register(
3976
config.Option('foo', default=u'{bar}',
3977
from_unicode=config.int_from_store))
3978
self.assertEqual(42, self.conf.get('foo', expand=True))
3980
def test_env_adding_options(self):
3981
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3983
def test_env_overriding_options(self):
3984
self.conf.store._load_from_string('foo=baz')
3985
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3987
def test_simple_ref(self):
3988
self.conf.store._load_from_string('foo=xxx')
3989
self.assertExpansion('xxx', '{foo}')
3991
def test_unknown_ref(self):
3992
self.assertRaises(errors.ExpandingUnknownOption,
3993
self.conf.expand_options, '{foo}')
3995
def test_illegal_def_is_ignored(self):
3996
self.assertExpansion('{1,2}', '{1,2}')
3997
self.assertExpansion('{ }', '{ }')
3998
self.assertExpansion('${Foo,f}', '${Foo,f}')
4000
def test_indirect_ref(self):
4001
self.conf.store._load_from_string('''
4005
self.assertExpansion('xxx', '{bar}')
4007
def test_embedded_ref(self):
4008
self.conf.store._load_from_string('''
4012
self.assertExpansion('xxx', '{{bar}}')
4014
def test_simple_loop(self):
4015
self.conf.store._load_from_string('foo={foo}')
4016
self.assertRaises(errors.OptionExpansionLoop,
4017
self.conf.expand_options, '{foo}')
4019
def test_indirect_loop(self):
4020
self.conf.store._load_from_string('''
4024
e = self.assertRaises(errors.OptionExpansionLoop,
4025
self.conf.expand_options, '{foo}')
4026
self.assertEqual('foo->bar->baz', e.refs)
4027
self.assertEqual('{foo}', e.string)
4029
def test_list(self):
4030
self.conf.store._load_from_string('''
4034
list={foo},{bar},{baz}
4036
self.registry.register(
4037
config.ListOption('list'))
4038
self.assertEqual(['start', 'middle', 'end'],
4039
self.conf.get('list', expand=True))
4041
def test_cascading_list(self):
4042
self.conf.store._load_from_string('''
4048
self.registry.register(config.ListOption('list'))
4049
# Register an intermediate option as a list to ensure no conversion
4050
# happen while expanding. Conversion should only occur for the original
4051
# option ('list' here).
4052
self.registry.register(config.ListOption('baz'))
4053
self.assertEqual(['start', 'middle', 'end'],
4054
self.conf.get('list', expand=True))
4056
def test_pathologically_hidden_list(self):
4057
self.conf.store._load_from_string('''
4063
hidden={start}{middle}{end}
4065
# What matters is what the registration says, the conversion happens
4066
# only after all expansions have been performed
4067
self.registry.register(config.ListOption('hidden'))
4068
self.assertEqual(['bin', 'go'],
4069
self.conf.get('hidden', expand=True))
4072
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
4075
super(TestStackCrossSectionsExpand, self).setUp()
4077
def get_config(self, location, string):
4080
# Since we don't save the config we won't strictly require to inherit
4081
# from TestCaseInTempDir, but an error occurs so quickly...
4082
c = config.LocationStack(location)
4083
c.store._load_from_string(string)
4086
def test_dont_cross_unrelated_section(self):
4087
c = self.get_config('/another/branch/path','''
4092
[/another/branch/path]
4095
self.assertRaises(errors.ExpandingUnknownOption,
4096
c.get, 'bar', expand=True)
4098
def test_cross_related_sections(self):
4099
c = self.get_config('/project/branch/path','''
4103
[/project/branch/path]
4106
self.assertEqual('quux', c.get('bar', expand=True))
4109
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
4111
def test_cross_global_locations(self):
4112
l_store = config.LocationStore()
4113
l_store._load_from_string('''
4119
g_store = config.GlobalStore()
4120
g_store._load_from_string('''
4126
stack = config.LocationStack('/branch')
4127
self.assertEqual('glob-bar', stack.get('lbar', expand=True))
4128
self.assertEqual('loc-foo', stack.get('gfoo', expand=True))
4131
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
4133
def test_expand_locals_empty(self):
4134
l_store = config.LocationStore()
4135
l_store._load_from_string('''
4136
[/home/user/project]
4141
stack = config.LocationStack('/home/user/project/')
4142
self.assertEqual('', stack.get('base', expand=True))
4143
self.assertEqual('', stack.get('rel', expand=True))
4145
def test_expand_basename_locally(self):
4146
l_store = config.LocationStore()
4147
l_store._load_from_string('''
4148
[/home/user/project]
4152
stack = config.LocationStack('/home/user/project/branch')
4153
self.assertEqual('branch', stack.get('bfoo', expand=True))
4155
def test_expand_basename_locally_longer_path(self):
4156
l_store = config.LocationStore()
4157
l_store._load_from_string('''
4162
stack = config.LocationStack('/home/user/project/dir/branch')
4163
self.assertEqual('branch', stack.get('bfoo', expand=True))
4165
def test_expand_relpath_locally(self):
4166
l_store = config.LocationStore()
4167
l_store._load_from_string('''
4168
[/home/user/project]
4169
lfoo = loc-foo/{relpath}
4172
stack = config.LocationStack('/home/user/project/branch')
4173
self.assertEqual('loc-foo/branch', stack.get('lfoo', expand=True))
4175
def test_expand_relpath_unknonw_in_global(self):
4176
g_store = config.GlobalStore()
4177
g_store._load_from_string('''
4182
stack = config.LocationStack('/home/user/project/branch')
4183
self.assertRaises(errors.ExpandingUnknownOption,
4184
stack.get, 'gfoo', expand=True)
4186
def test_expand_local_option_locally(self):
4187
l_store = config.LocationStore()
4188
l_store._load_from_string('''
4189
[/home/user/project]
4190
lfoo = loc-foo/{relpath}
4194
g_store = config.GlobalStore()
4195
g_store._load_from_string('''
4201
stack = config.LocationStack('/home/user/project/branch')
4202
self.assertEqual('glob-bar', stack.get('lbar', expand=True))
4203
self.assertEqual('loc-foo/branch', stack.get('gfoo', expand=True))
4205
def test_locals_dont_leak(self):
4206
"""Make sure we chose the right local in presence of several sections.
4208
l_store = config.LocationStore()
4209
l_store._load_from_string('''
4211
lfoo = loc-foo/{relpath}
4212
[/home/user/project]
4213
lfoo = loc-foo/{relpath}
4216
stack = config.LocationStack('/home/user/project/branch')
4217
self.assertEqual('loc-foo/branch', stack.get('lfoo', expand=True))
4218
stack = config.LocationStack('/home/user/bar/baz')
4219
self.assertEqual('loc-foo/bar/baz', stack.get('lfoo', expand=True))
4223
class TestStackSet(TestStackWithTransport):
4225
def test_simple_set(self):
4226
conf = self.get_stack(self)
4227
self.assertEqual(None, conf.get('foo'))
4228
conf.set('foo', 'baz')
4229
# Did we get it back ?
4230
self.assertEqual('baz', conf.get('foo'))
4232
def test_set_creates_a_new_section(self):
4233
conf = self.get_stack(self)
4234
conf.set('foo', 'baz')
4235
self.assertEqual, 'baz', conf.get('foo')
4237
def test_set_hook(self):
4241
config.ConfigHooks.install_named_hook('set', hook, None)
4242
self.assertLength(0, calls)
4243
conf = self.get_stack(self)
4244
conf.set('foo', 'bar')
4245
self.assertLength(1, calls)
4246
self.assertEqual((conf, 'foo', 'bar'), calls[0])
4249
class TestStackRemove(TestStackWithTransport):
4251
def test_remove_existing(self):
4252
conf = self.get_stack(self)
4253
conf.set('foo', 'bar')
4254
self.assertEqual('bar', conf.get('foo'))
4256
# Did we get it back ?
4257
self.assertEqual(None, conf.get('foo'))
4259
def test_remove_unknown(self):
4260
conf = self.get_stack(self)
4261
self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
4263
def test_remove_hook(self):
4267
config.ConfigHooks.install_named_hook('remove', hook, None)
4268
self.assertLength(0, calls)
4269
conf = self.get_stack(self)
4270
conf.set('foo', 'bar')
4272
self.assertLength(1, calls)
4273
self.assertEqual((conf, 'foo'), calls[0])
4276
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
4279
super(TestConfigGetOptions, self).setUp()
4280
create_configs(self)
4282
def test_no_variable(self):
4283
# Using branch should query branch, locations and bazaar
4284
self.assertOptions([], self.branch_config)
4286
def test_option_in_bazaar(self):
4287
self.bazaar_config.set_user_option('file', 'bazaar')
4288
self.assertOptions([('file', 'bazaar', 'DEFAULT', 'bazaar')],
4291
def test_option_in_locations(self):
4292
self.locations_config.set_user_option('file', 'locations')
4294
[('file', 'locations', self.tree.basedir, 'locations')],
4295
self.locations_config)
4297
def test_option_in_branch(self):
4298
self.branch_config.set_user_option('file', 'branch')
4299
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch')],
4302
def test_option_in_bazaar_and_branch(self):
4303
self.bazaar_config.set_user_option('file', 'bazaar')
4304
self.branch_config.set_user_option('file', 'branch')
4305
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch'),
4306
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4309
def test_option_in_branch_and_locations(self):
4310
# Hmm, locations override branch :-/
4311
self.locations_config.set_user_option('file', 'locations')
4312
self.branch_config.set_user_option('file', 'branch')
4314
[('file', 'locations', self.tree.basedir, 'locations'),
4315
('file', 'branch', 'DEFAULT', 'branch'),],
4318
def test_option_in_bazaar_locations_and_branch(self):
4319
self.bazaar_config.set_user_option('file', 'bazaar')
4320
self.locations_config.set_user_option('file', 'locations')
4321
self.branch_config.set_user_option('file', 'branch')
4323
[('file', 'locations', self.tree.basedir, 'locations'),
4324
('file', 'branch', 'DEFAULT', 'branch'),
4325
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4329
class TestConfigRemoveOption(tests.TestCaseWithTransport, TestOptionsMixin):
4332
super(TestConfigRemoveOption, self).setUp()
4333
create_configs_with_file_option(self)
4335
def test_remove_in_locations(self):
4336
self.locations_config.remove_user_option('file', self.tree.basedir)
4338
[('file', 'branch', 'DEFAULT', 'branch'),
4339
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4342
def test_remove_in_branch(self):
4343
self.branch_config.remove_user_option('file')
4345
[('file', 'locations', self.tree.basedir, 'locations'),
4346
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4349
def test_remove_in_bazaar(self):
4350
self.bazaar_config.remove_user_option('file')
4352
[('file', 'locations', self.tree.basedir, 'locations'),
4353
('file', 'branch', 'DEFAULT', 'branch'),],
4357
class TestConfigGetSections(tests.TestCaseWithTransport):
4360
super(TestConfigGetSections, self).setUp()
4361
create_configs(self)
4363
def assertSectionNames(self, expected, conf, name=None):
4364
"""Check which sections are returned for a given config.
4366
If fallback configurations exist their sections can be included.
4368
:param expected: A list of section names.
4370
:param conf: The configuration that will be queried.
4372
:param name: An optional section name that will be passed to
4375
sections = list(conf._get_sections(name))
4376
self.assertLength(len(expected), sections)
4377
self.assertEqual(expected, [n for n, _, _ in sections])
4379
def test_bazaar_default_section(self):
4380
self.assertSectionNames(['DEFAULT'], self.bazaar_config)
4382
def test_locations_default_section(self):
4383
# No sections are defined in an empty file
4384
self.assertSectionNames([], self.locations_config)
4386
def test_locations_named_section(self):
4387
self.locations_config.set_user_option('file', 'locations')
4388
self.assertSectionNames([self.tree.basedir], self.locations_config)
4390
def test_locations_matching_sections(self):
4391
loc_config = self.locations_config
4392
loc_config.set_user_option('file', 'locations')
4393
# We need to cheat a bit here to create an option in sections above and
4394
# below the 'location' one.
4395
parser = loc_config._get_parser()
4396
# locations.cong deals with '/' ignoring native os.sep
4397
location_names = self.tree.basedir.split('/')
4398
parent = '/'.join(location_names[:-1])
4399
child = '/'.join(location_names + ['child'])
4401
parser[parent]['file'] = 'parent'
4403
parser[child]['file'] = 'child'
4404
self.assertSectionNames([self.tree.basedir, parent], loc_config)
4406
def test_branch_data_default_section(self):
4407
self.assertSectionNames([None],
4408
self.branch_config._get_branch_data_config())
4410
def test_branch_default_sections(self):
4411
# No sections are defined in an empty locations file
4412
self.assertSectionNames([None, 'DEFAULT'],
4414
# Unless we define an option
4415
self.branch_config._get_location_config().set_user_option(
4416
'file', 'locations')
4417
self.assertSectionNames([self.tree.basedir, None, 'DEFAULT'],
4420
def test_bazaar_named_section(self):
4421
# We need to cheat as the API doesn't give direct access to sections
4422
# other than DEFAULT.
4423
self.bazaar_config.set_alias('bazaar', 'bzr')
4424
self.assertSectionNames(['ALIASES'], self.bazaar_config, 'ALIASES')
4427
class TestSharedStores(tests.TestCaseInTempDir):
4429
def test_bazaar_conf_shared(self):
4430
g1 = config.GlobalStack()
4431
g2 = config.GlobalStack()
4432
# The two stacks share the same store
4433
self.assertIs(g1.store, g2.store)
1315
4436
class TestAuthenticationConfigFile(tests.TestCase):
1316
4437
"""Test the authentication.conf file matching"""