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