1312
1665
self.assertIs(None, bzrdir_config.get_default_stack_on())
1668
class TestOldConfigHooks(tests.TestCaseWithTransport):
1671
super(TestOldConfigHooks, self).setUp()
1672
create_configs_with_file_option(self)
1674
def assertGetHook(self, conf, name, value):
1678
config.OldConfigHooks.install_named_hook('get', hook, None)
1680
config.OldConfigHooks.uninstall_named_hook, 'get', None)
1681
self.assertLength(0, calls)
1682
actual_value = conf.get_user_option(name)
1683
self.assertEqual(value, actual_value)
1684
self.assertLength(1, calls)
1685
self.assertEqual((conf, name, value), calls[0])
1687
def test_get_hook_bazaar(self):
1688
self.assertGetHook(self.bazaar_config, 'file', 'bazaar')
1690
def test_get_hook_locations(self):
1691
self.assertGetHook(self.locations_config, 'file', 'locations')
1693
def test_get_hook_branch(self):
1694
# Since locations masks branch, we define a different option
1695
self.branch_config.set_user_option('file2', 'branch')
1696
self.assertGetHook(self.branch_config, 'file2', 'branch')
1698
def assertSetHook(self, conf, name, value):
1702
config.OldConfigHooks.install_named_hook('set', hook, None)
1704
config.OldConfigHooks.uninstall_named_hook, 'set', None)
1705
self.assertLength(0, calls)
1706
conf.set_user_option(name, value)
1707
self.assertLength(1, calls)
1708
# We can't assert the conf object below as different configs use
1709
# different means to implement set_user_option and we care only about
1711
self.assertEqual((name, value), calls[0][1:])
1713
def test_set_hook_bazaar(self):
1714
self.assertSetHook(self.bazaar_config, 'foo', 'bazaar')
1716
def test_set_hook_locations(self):
1717
self.assertSetHook(self.locations_config, 'foo', 'locations')
1719
def test_set_hook_branch(self):
1720
self.assertSetHook(self.branch_config, 'foo', 'branch')
1722
def assertRemoveHook(self, conf, name, section_name=None):
1726
config.OldConfigHooks.install_named_hook('remove', hook, None)
1728
config.OldConfigHooks.uninstall_named_hook, 'remove', None)
1729
self.assertLength(0, calls)
1730
conf.remove_user_option(name, section_name)
1731
self.assertLength(1, calls)
1732
# We can't assert the conf object below as different configs use
1733
# different means to implement remove_user_option and we care only about
1735
self.assertEqual((name,), calls[0][1:])
1737
def test_remove_hook_bazaar(self):
1738
self.assertRemoveHook(self.bazaar_config, 'file')
1740
def test_remove_hook_locations(self):
1741
self.assertRemoveHook(self.locations_config, 'file',
1742
self.locations_config.location)
1744
def test_remove_hook_branch(self):
1745
self.assertRemoveHook(self.branch_config, 'file')
1747
def assertLoadHook(self, name, conf_class, *conf_args):
1751
config.OldConfigHooks.install_named_hook('load', hook, None)
1753
config.OldConfigHooks.uninstall_named_hook, 'load', None)
1754
self.assertLength(0, calls)
1756
conf = conf_class(*conf_args)
1757
# Access an option to trigger a load
1758
conf.get_user_option(name)
1759
self.assertLength(1, calls)
1760
# Since we can't assert about conf, we just use the number of calls ;-/
1762
def test_load_hook_bazaar(self):
1763
self.assertLoadHook('file', config.GlobalConfig)
1765
def test_load_hook_locations(self):
1766
self.assertLoadHook('file', config.LocationConfig, self.tree.basedir)
1768
def test_load_hook_branch(self):
1769
self.assertLoadHook('file', config.BranchConfig, self.tree.branch)
1771
def assertSaveHook(self, conf):
1775
config.OldConfigHooks.install_named_hook('save', hook, None)
1777
config.OldConfigHooks.uninstall_named_hook, 'save', None)
1778
self.assertLength(0, calls)
1779
# Setting an option triggers a save
1780
conf.set_user_option('foo', 'bar')
1781
self.assertLength(1, calls)
1782
# Since we can't assert about conf, we just use the number of calls ;-/
1784
def test_save_hook_bazaar(self):
1785
self.assertSaveHook(self.bazaar_config)
1787
def test_save_hook_locations(self):
1788
self.assertSaveHook(self.locations_config)
1790
def test_save_hook_branch(self):
1791
self.assertSaveHook(self.branch_config)
1794
class TestOldConfigHooksForRemote(tests.TestCaseWithTransport):
1795
"""Tests config hooks for remote configs.
1797
No tests for the remove hook as this is not implemented there.
1801
super(TestOldConfigHooksForRemote, self).setUp()
1802
self.transport_server = test_server.SmartTCPServer_for_testing
1803
create_configs_with_file_option(self)
1805
def assertGetHook(self, conf, name, value):
1809
config.OldConfigHooks.install_named_hook('get', hook, None)
1811
config.OldConfigHooks.uninstall_named_hook, 'get', None)
1812
self.assertLength(0, calls)
1813
actual_value = conf.get_option(name)
1814
self.assertEqual(value, actual_value)
1815
self.assertLength(1, calls)
1816
self.assertEqual((conf, name, value), calls[0])
1818
def test_get_hook_remote_branch(self):
1819
remote_branch = branch.Branch.open(self.get_url('tree'))
1820
self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
1822
def test_get_hook_remote_bzrdir(self):
1823
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
1824
conf = remote_bzrdir._get_config()
1825
conf.set_option('remotedir', 'file')
1826
self.assertGetHook(conf, 'file', 'remotedir')
1828
def assertSetHook(self, conf, name, value):
1832
config.OldConfigHooks.install_named_hook('set', hook, None)
1834
config.OldConfigHooks.uninstall_named_hook, 'set', None)
1835
self.assertLength(0, calls)
1836
conf.set_option(value, name)
1837
self.assertLength(1, calls)
1838
# We can't assert the conf object below as different configs use
1839
# different means to implement set_user_option and we care only about
1841
self.assertEqual((name, value), calls[0][1:])
1843
def test_set_hook_remote_branch(self):
1844
remote_branch = branch.Branch.open(self.get_url('tree'))
1845
self.addCleanup(remote_branch.lock_write().unlock)
1846
self.assertSetHook(remote_branch._get_config(), 'file', 'remote')
1848
def test_set_hook_remote_bzrdir(self):
1849
remote_branch = branch.Branch.open(self.get_url('tree'))
1850
self.addCleanup(remote_branch.lock_write().unlock)
1851
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
1852
self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
1854
def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
1858
config.OldConfigHooks.install_named_hook('load', hook, None)
1860
config.OldConfigHooks.uninstall_named_hook, 'load', None)
1861
self.assertLength(0, calls)
1863
conf = conf_class(*conf_args)
1864
# Access an option to trigger a load
1865
conf.get_option(name)
1866
self.assertLength(expected_nb_calls, calls)
1867
# Since we can't assert about conf, we just use the number of calls ;-/
1869
def test_load_hook_remote_branch(self):
1870
remote_branch = branch.Branch.open(self.get_url('tree'))
1871
self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
1873
def test_load_hook_remote_bzrdir(self):
1874
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
1875
# The config file doesn't exist, set an option to force its creation
1876
conf = remote_bzrdir._get_config()
1877
conf.set_option('remotedir', 'file')
1878
# We get one call for the server and one call for the client, this is
1879
# caused by the differences in implementations betwen
1880
# SmartServerBzrDirRequestConfigFile (in smart/bzrdir.py) and
1881
# SmartServerBranchGetConfigFile (in smart/branch.py)
1882
self.assertLoadHook(2 ,'file', remote.RemoteBzrDirConfig, remote_bzrdir)
1884
def assertSaveHook(self, conf):
1888
config.OldConfigHooks.install_named_hook('save', hook, None)
1890
config.OldConfigHooks.uninstall_named_hook, 'save', None)
1891
self.assertLength(0, calls)
1892
# Setting an option triggers a save
1893
conf.set_option('foo', 'bar')
1894
self.assertLength(1, calls)
1895
# Since we can't assert about conf, we just use the number of calls ;-/
1897
def test_save_hook_remote_branch(self):
1898
remote_branch = branch.Branch.open(self.get_url('tree'))
1899
self.addCleanup(remote_branch.lock_write().unlock)
1900
self.assertSaveHook(remote_branch._get_config())
1902
def test_save_hook_remote_bzrdir(self):
1903
remote_branch = branch.Branch.open(self.get_url('tree'))
1904
self.addCleanup(remote_branch.lock_write().unlock)
1905
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
1906
self.assertSaveHook(remote_bzrdir._get_config())
1909
class TestOptionNames(tests.TestCase):
1911
def is_valid(self, name):
1912
return config._option_ref_re.match('{%s}' % name) is not None
1914
def test_valid_names(self):
1915
self.assertTrue(self.is_valid('foo'))
1916
self.assertTrue(self.is_valid('foo.bar'))
1917
self.assertTrue(self.is_valid('f1'))
1918
self.assertTrue(self.is_valid('_'))
1919
self.assertTrue(self.is_valid('__bar__'))
1920
self.assertTrue(self.is_valid('a_'))
1921
self.assertTrue(self.is_valid('a1'))
1922
# Don't break bzr-svn for no good reason
1923
self.assertTrue(self.is_valid('guessed-layout'))
1925
def test_invalid_names(self):
1926
self.assertFalse(self.is_valid(' foo'))
1927
self.assertFalse(self.is_valid('foo '))
1928
self.assertFalse(self.is_valid('1'))
1929
self.assertFalse(self.is_valid('1,2'))
1930
self.assertFalse(self.is_valid('foo$'))
1931
self.assertFalse(self.is_valid('!foo'))
1932
self.assertFalse(self.is_valid('foo.'))
1933
self.assertFalse(self.is_valid('foo..bar'))
1934
self.assertFalse(self.is_valid('{}'))
1935
self.assertFalse(self.is_valid('{a}'))
1936
self.assertFalse(self.is_valid('a\n'))
1937
self.assertFalse(self.is_valid('-'))
1938
self.assertFalse(self.is_valid('-a'))
1939
self.assertFalse(self.is_valid('a-'))
1940
self.assertFalse(self.is_valid('a--a'))
1942
def assertSingleGroup(self, reference):
1943
# the regexp is used with split and as such should match the reference
1944
# *only*, if more groups needs to be defined, (?:...) should be used.
1945
m = config._option_ref_re.match('{a}')
1946
self.assertLength(1, m.groups())
1948
def test_valid_references(self):
1949
self.assertSingleGroup('{a}')
1950
self.assertSingleGroup('{{a}}')
1953
class TestOption(tests.TestCase):
1955
def test_default_value(self):
1956
opt = config.Option('foo', default='bar')
1957
self.assertEqual('bar', opt.get_default())
1959
def test_callable_default_value(self):
1960
def bar_as_unicode():
1962
opt = config.Option('foo', default=bar_as_unicode)
1963
self.assertEqual('bar', opt.get_default())
1965
def test_default_value_from_env(self):
1966
opt = config.Option('foo', default='bar', default_from_env=['FOO'])
1967
self.overrideEnv('FOO', 'quux')
1968
# Env variable provides a default taking over the option one
1969
self.assertEqual('quux', opt.get_default())
1971
def test_first_default_value_from_env_wins(self):
1972
opt = config.Option('foo', default='bar',
1973
default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
1974
self.overrideEnv('FOO', 'foo')
1975
self.overrideEnv('BAZ', 'baz')
1976
# The first env var set wins
1977
self.assertEqual('foo', opt.get_default())
1979
def test_not_supported_list_default_value(self):
1980
self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
1982
def test_not_supported_object_default_value(self):
1983
self.assertRaises(AssertionError, config.Option, 'foo',
1986
def test_not_supported_callable_default_value_not_unicode(self):
1987
def bar_not_unicode():
1989
opt = config.Option('foo', default=bar_not_unicode)
1990
self.assertRaises(AssertionError, opt.get_default)
1992
def test_get_help_topic(self):
1993
opt = config.Option('foo')
1994
self.assertEqual('foo', opt.get_help_topic())
1997
class TestOptionConverter(tests.TestCase):
1999
def assertConverted(self, expected, opt, value):
2000
self.assertEqual(expected, opt.convert_from_unicode(None, value))
2002
def assertCallsWarning(self, opt, value):
2006
warnings.append(args[0] % args[1:])
2007
self.overrideAttr(trace, 'warning', warning)
2008
self.assertEqual(None, opt.convert_from_unicode(None, value))
2009
self.assertLength(1, warnings)
2011
'Value "%s" is not valid for "%s"' % (value, opt.name),
2014
def assertCallsError(self, opt, value):
2015
self.assertRaises(errors.ConfigOptionValueError,
2016
opt.convert_from_unicode, None, value)
2018
def assertConvertInvalid(self, opt, invalid_value):
2020
self.assertEqual(None, opt.convert_from_unicode(None, invalid_value))
2021
opt.invalid = 'warning'
2022
self.assertCallsWarning(opt, invalid_value)
2023
opt.invalid = 'error'
2024
self.assertCallsError(opt, invalid_value)
2027
class TestOptionWithBooleanConverter(TestOptionConverter):
2029
def get_option(self):
2030
return config.Option('foo', help='A boolean.',
2031
from_unicode=config.bool_from_store)
2033
def test_convert_invalid(self):
2034
opt = self.get_option()
2035
# A string that is not recognized as a boolean
2036
self.assertConvertInvalid(opt, u'invalid-boolean')
2037
# A list of strings is never recognized as a boolean
2038
self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
2040
def test_convert_valid(self):
2041
opt = self.get_option()
2042
self.assertConverted(True, opt, u'True')
2043
self.assertConverted(True, opt, u'1')
2044
self.assertConverted(False, opt, u'False')
2047
class TestOptionWithIntegerConverter(TestOptionConverter):
2049
def get_option(self):
2050
return config.Option('foo', help='An integer.',
2051
from_unicode=config.int_from_store)
2053
def test_convert_invalid(self):
2054
opt = self.get_option()
2055
# A string that is not recognized as an integer
2056
self.assertConvertInvalid(opt, u'forty-two')
2057
# A list of strings is never recognized as an integer
2058
self.assertConvertInvalid(opt, [u'a', u'list'])
2060
def test_convert_valid(self):
2061
opt = self.get_option()
2062
self.assertConverted(16, opt, u'16')
2065
class TestOptionWithSIUnitConverter(TestOptionConverter):
2067
def get_option(self):
2068
return config.Option('foo', help='An integer in SI units.',
2069
from_unicode=config.int_SI_from_store)
2071
def test_convert_invalid(self):
2072
opt = self.get_option()
2073
self.assertConvertInvalid(opt, u'not-a-unit')
2074
self.assertConvertInvalid(opt, u'Gb') # Forgot the value
2075
self.assertConvertInvalid(opt, u'1b') # Forgot the unit
2076
self.assertConvertInvalid(opt, u'1GG')
2077
self.assertConvertInvalid(opt, u'1Mbb')
2078
self.assertConvertInvalid(opt, u'1MM')
2080
def test_convert_valid(self):
2081
opt = self.get_option()
2082
self.assertConverted(int(5e3), opt, u'5kb')
2083
self.assertConverted(int(5e6), opt, u'5M')
2084
self.assertConverted(int(5e6), opt, u'5MB')
2085
self.assertConverted(int(5e9), opt, u'5g')
2086
self.assertConverted(int(5e9), opt, u'5gB')
2087
self.assertConverted(100, opt, u'100')
2090
class TestListOption(TestOptionConverter):
2092
def get_option(self):
2093
return config.ListOption('foo', help='A list.')
2095
def test_convert_invalid(self):
2096
opt = self.get_option()
2097
# We don't even try to convert a list into a list, we only expect
2099
self.assertConvertInvalid(opt, [1])
2100
# No string is invalid as all forms can be converted to a list
2102
def test_convert_valid(self):
2103
opt = self.get_option()
2104
# An empty string is an empty list
2105
self.assertConverted([], opt, '') # Using a bare str() just in case
2106
self.assertConverted([], opt, u'')
2108
self.assertConverted([u'True'], opt, u'True')
2110
self.assertConverted([u'42'], opt, u'42')
2112
self.assertConverted([u'bar'], opt, u'bar')
2115
class TestRegistryOption(TestOptionConverter):
2117
def get_option(self, registry):
2118
return config.RegistryOption('foo', registry,
2119
help='A registry option.')
2121
def test_convert_invalid(self):
2122
registry = _mod_registry.Registry()
2123
opt = self.get_option(registry)
2124
self.assertConvertInvalid(opt, [1])
2125
self.assertConvertInvalid(opt, u"notregistered")
2127
def test_convert_valid(self):
2128
registry = _mod_registry.Registry()
2129
registry.register("someval", 1234)
2130
opt = self.get_option(registry)
2131
# Using a bare str() just in case
2132
self.assertConverted(1234, opt, "someval")
2133
self.assertConverted(1234, opt, u'someval')
2134
self.assertConverted(None, opt, None)
2136
def test_help(self):
2137
registry = _mod_registry.Registry()
2138
registry.register("someval", 1234, help="some option")
2139
registry.register("dunno", 1234, help="some other option")
2140
opt = self.get_option(registry)
2142
'A registry option.\n'
2144
'The following values are supported:\n'
2145
' dunno - some other option\n'
2146
' someval - some option\n',
2149
def test_get_help_text(self):
2150
registry = _mod_registry.Registry()
2151
registry.register("someval", 1234, help="some option")
2152
registry.register("dunno", 1234, help="some other option")
2153
opt = self.get_option(registry)
2155
'A registry option.\n'
2157
'The following values are supported:\n'
2158
' dunno - some other option\n'
2159
' someval - some option\n',
2160
opt.get_help_text())
2163
class TestOptionRegistry(tests.TestCase):
2166
super(TestOptionRegistry, self).setUp()
2167
# Always start with an empty registry
2168
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2169
self.registry = config.option_registry
2171
def test_register(self):
2172
opt = config.Option('foo')
2173
self.registry.register(opt)
2174
self.assertIs(opt, self.registry.get('foo'))
2176
def test_registered_help(self):
2177
opt = config.Option('foo', help='A simple option')
2178
self.registry.register(opt)
2179
self.assertEqual('A simple option', self.registry.get_help('foo'))
2181
def test_dont_register_illegal_name(self):
2182
self.assertRaises(errors.IllegalOptionName,
2183
self.registry.register, config.Option(' foo'))
2184
self.assertRaises(errors.IllegalOptionName,
2185
self.registry.register, config.Option('bar,'))
2187
lazy_option = config.Option('lazy_foo', help='Lazy help')
2189
def test_register_lazy(self):
2190
self.registry.register_lazy('lazy_foo', self.__module__,
2191
'TestOptionRegistry.lazy_option')
2192
self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
2194
def test_registered_lazy_help(self):
2195
self.registry.register_lazy('lazy_foo', self.__module__,
2196
'TestOptionRegistry.lazy_option')
2197
self.assertEqual('Lazy help', self.registry.get_help('lazy_foo'))
2199
def test_dont_lazy_register_illegal_name(self):
2200
# This is where the root cause of http://pad.lv/1235099 is better
2201
# understood: 'register_lazy' doc string mentions that key should match
2202
# the option name which indirectly requires that the option name is a
2203
# valid python identifier. We violate that rule here (using a key that
2204
# doesn't match the option name) to test the option name checking.
2205
self.assertRaises(errors.IllegalOptionName,
2206
self.registry.register_lazy, ' foo', self.__module__,
2207
'TestOptionRegistry.lazy_option')
2208
self.assertRaises(errors.IllegalOptionName,
2209
self.registry.register_lazy, '1,2', self.__module__,
2210
'TestOptionRegistry.lazy_option')
2213
class TestRegisteredOptions(tests.TestCase):
2214
"""All registered options should verify some constraints."""
2216
scenarios = [(key, {'option_name': key, 'option': option}) for key, option
2217
in config.option_registry.iteritems()]
2220
super(TestRegisteredOptions, self).setUp()
2221
self.registry = config.option_registry
2223
def test_proper_name(self):
2224
# An option should be registered under its own name, this can't be
2225
# checked at registration time for the lazy ones.
2226
self.assertEqual(self.option_name, self.option.name)
2228
def test_help_is_set(self):
2229
option_help = self.registry.get_help(self.option_name)
2230
# Come on, think about the user, he really wants to know what the
2232
self.assertIsNot(None, option_help)
2233
self.assertNotEqual('', option_help)
2236
class TestSection(tests.TestCase):
2238
# FIXME: Parametrize so that all sections produced by Stores run these
2239
# tests -- vila 2011-04-01
2241
def test_get_a_value(self):
2242
a_dict = dict(foo='bar')
2243
section = config.Section('myID', a_dict)
2244
self.assertEqual('bar', section.get('foo'))
2246
def test_get_unknown_option(self):
2248
section = config.Section(None, a_dict)
2249
self.assertEqual('out of thin air',
2250
section.get('foo', 'out of thin air'))
2252
def test_options_is_shared(self):
2254
section = config.Section(None, a_dict)
2255
self.assertIs(a_dict, section.options)
2258
class TestMutableSection(tests.TestCase):
2260
scenarios = [('mutable',
2262
lambda opts: config.MutableSection('myID', opts)},),
2266
a_dict = dict(foo='bar')
2267
section = self.get_section(a_dict)
2268
section.set('foo', 'new_value')
2269
self.assertEqual('new_value', section.get('foo'))
2270
# The change appears in the shared section
2271
self.assertEqual('new_value', a_dict.get('foo'))
2272
# We keep track of the change
2273
self.assertTrue('foo' in section.orig)
2274
self.assertEqual('bar', section.orig.get('foo'))
2276
def test_set_preserve_original_once(self):
2277
a_dict = dict(foo='bar')
2278
section = self.get_section(a_dict)
2279
section.set('foo', 'first_value')
2280
section.set('foo', 'second_value')
2281
# We keep track of the original value
2282
self.assertTrue('foo' in section.orig)
2283
self.assertEqual('bar', section.orig.get('foo'))
2285
def test_remove(self):
2286
a_dict = dict(foo='bar')
2287
section = self.get_section(a_dict)
2288
section.remove('foo')
2289
# We get None for unknown options via the default value
2290
self.assertEqual(None, section.get('foo'))
2291
# Or we just get the default value
2292
self.assertEqual('unknown', section.get('foo', 'unknown'))
2293
self.assertFalse('foo' in section.options)
2294
# We keep track of the deletion
2295
self.assertTrue('foo' in section.orig)
2296
self.assertEqual('bar', section.orig.get('foo'))
2298
def test_remove_new_option(self):
2300
section = self.get_section(a_dict)
2301
section.set('foo', 'bar')
2302
section.remove('foo')
2303
self.assertFalse('foo' in section.options)
2304
# The option didn't exist initially so it we need to keep track of it
2305
# with a special value
2306
self.assertTrue('foo' in section.orig)
2307
self.assertEqual(config._NewlyCreatedOption, section.orig['foo'])
2310
class TestCommandLineStore(tests.TestCase):
2313
super(TestCommandLineStore, self).setUp()
2314
self.store = config.CommandLineStore()
2315
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2317
def get_section(self):
2318
"""Get the unique section for the command line overrides."""
2319
sections = list(self.store.get_sections())
2320
self.assertLength(1, sections)
2321
store, section = sections[0]
2322
self.assertEqual(self.store, store)
2325
def test_no_override(self):
2326
self.store._from_cmdline([])
2327
section = self.get_section()
2328
self.assertLength(0, list(section.iter_option_names()))
2330
def test_simple_override(self):
2331
self.store._from_cmdline(['a=b'])
2332
section = self.get_section()
2333
self.assertEqual('b', section.get('a'))
2335
def test_list_override(self):
2336
opt = config.ListOption('l')
2337
config.option_registry.register(opt)
2338
self.store._from_cmdline(['l=1,2,3'])
2339
val = self.get_section().get('l')
2340
self.assertEqual('1,2,3', val)
2341
# Reminder: lists should be registered as such explicitely, otherwise
2342
# the conversion needs to be done afterwards.
2343
self.assertEqual(['1', '2', '3'],
2344
opt.convert_from_unicode(self.store, val))
2346
def test_multiple_overrides(self):
2347
self.store._from_cmdline(['a=b', 'x=y'])
2348
section = self.get_section()
2349
self.assertEqual('b', section.get('a'))
2350
self.assertEqual('y', section.get('x'))
2352
def test_wrong_syntax(self):
2353
self.assertRaises(errors.BzrCommandError,
2354
self.store._from_cmdline, ['a=b', 'c'])
2356
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
2358
scenarios = [(key, {'get_store': builder}) for key, builder
2359
in config.test_store_builder_registry.iteritems()] + [
2360
('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
2363
store = self.get_store(self)
2364
if isinstance(store, config.TransportIniFileStore):
2365
raise tests.TestNotApplicable(
2366
"%s is not a concrete Store implementation"
2367
" so it doesn't need an id" % (store.__class__.__name__,))
2368
self.assertIsNot(None, store.id)
2371
class TestStore(tests.TestCaseWithTransport):
2373
def assertSectionContent(self, expected, store_and_section):
2374
"""Assert that some options have the proper values in a section."""
2375
_, section = store_and_section
2376
expected_name, expected_options = expected
2377
self.assertEqual(expected_name, section.id)
2380
dict([(k, section.get(k)) for k in expected_options.keys()]))
2383
class TestReadonlyStore(TestStore):
2385
scenarios = [(key, {'get_store': builder}) for key, builder
2386
in config.test_store_builder_registry.iteritems()]
2388
def test_building_delays_load(self):
2389
store = self.get_store(self)
2390
self.assertEqual(False, store.is_loaded())
2391
store._load_from_string('')
2392
self.assertEqual(True, store.is_loaded())
2394
def test_get_no_sections_for_empty(self):
2395
store = self.get_store(self)
2396
store._load_from_string('')
2397
self.assertEqual([], list(store.get_sections()))
2399
def test_get_default_section(self):
2400
store = self.get_store(self)
2401
store._load_from_string('foo=bar')
2402
sections = list(store.get_sections())
2403
self.assertLength(1, sections)
2404
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2406
def test_get_named_section(self):
2407
store = self.get_store(self)
2408
store._load_from_string('[baz]\nfoo=bar')
2409
sections = list(store.get_sections())
2410
self.assertLength(1, sections)
2411
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2413
def test_load_from_string_fails_for_non_empty_store(self):
2414
store = self.get_store(self)
2415
store._load_from_string('foo=bar')
2416
self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
2419
class TestStoreQuoting(TestStore):
2421
scenarios = [(key, {'get_store': builder}) for key, builder
2422
in config.test_store_builder_registry.iteritems()]
2425
super(TestStoreQuoting, self).setUp()
2426
self.store = self.get_store(self)
2427
# We need a loaded store but any content will do
2428
self.store._load_from_string('')
2430
def assertIdempotent(self, s):
2431
"""Assert that quoting an unquoted string is a no-op and vice-versa.
2433
What matters here is that option values, as they appear in a store, can
2434
be safely round-tripped out of the store and back.
2436
:param s: A string, quoted if required.
2438
self.assertEqual(s, self.store.quote(self.store.unquote(s)))
2439
self.assertEqual(s, self.store.unquote(self.store.quote(s)))
2441
def test_empty_string(self):
2442
if isinstance(self.store, config.IniFileStore):
2443
# configobj._quote doesn't handle empty values
2444
self.assertRaises(AssertionError,
2445
self.assertIdempotent, '')
2447
self.assertIdempotent('')
2448
# But quoted empty strings are ok
2449
self.assertIdempotent('""')
2451
def test_embedded_spaces(self):
2452
self.assertIdempotent('" a b c "')
2454
def test_embedded_commas(self):
2455
self.assertIdempotent('" a , b c "')
2457
def test_simple_comma(self):
2458
if isinstance(self.store, config.IniFileStore):
2459
# configobj requires that lists are special-cased
2460
self.assertRaises(AssertionError,
2461
self.assertIdempotent, ',')
2463
self.assertIdempotent(',')
2464
# When a single comma is required, quoting is also required
2465
self.assertIdempotent('","')
2467
def test_list(self):
2468
if isinstance(self.store, config.IniFileStore):
2469
# configobj requires that lists are special-cased
2470
self.assertRaises(AssertionError,
2471
self.assertIdempotent, 'a,b')
2473
self.assertIdempotent('a,b')
2476
class TestDictFromStore(tests.TestCase):
2478
def test_unquote_not_string(self):
2479
conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
2480
value = conf.get('a_section')
2481
# Urgh, despite 'conf' asking for the no-name section, we get the
2482
# content of another section as a dict o_O
2483
self.assertEqual({'a': '1'}, value)
2484
unquoted = conf.store.unquote(value)
2485
# Which cannot be unquoted but shouldn't crash either (the use cases
2486
# are getting the value or displaying it. In the later case, '%s' will
2488
self.assertEqual({'a': '1'}, unquoted)
2489
self.assertEqual("{u'a': u'1'}", '%s' % (unquoted,))
2492
class TestIniFileStoreContent(tests.TestCaseWithTransport):
2493
"""Simulate loading a config store with content of various encodings.
2495
All files produced by bzr are in utf8 content.
2497
Users may modify them manually and end up with a file that can't be
2498
loaded. We need to issue proper error messages in this case.
2501
invalid_utf8_char = '\xff'
2503
def test_load_utf8(self):
2504
"""Ensure we can load an utf8-encoded file."""
2505
t = self.get_transport()
2506
# From http://pad.lv/799212
2507
unicode_user = u'b\N{Euro Sign}ar'
2508
unicode_content = u'user=%s' % (unicode_user,)
2509
utf8_content = unicode_content.encode('utf8')
2510
# Store the raw content in the config file
2511
t.put_bytes('foo.conf', utf8_content)
2512
store = config.TransportIniFileStore(t, 'foo.conf')
2514
stack = config.Stack([store.get_sections], store)
2515
self.assertEqual(unicode_user, stack.get('user'))
2517
def test_load_non_ascii(self):
2518
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2519
t = self.get_transport()
2520
t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
2521
store = config.TransportIniFileStore(t, 'foo.conf')
2522
self.assertRaises(errors.ConfigContentError, store.load)
2524
def test_load_erroneous_content(self):
2525
"""Ensure we display a proper error on content that can't be parsed."""
2526
t = self.get_transport()
2527
t.put_bytes('foo.conf', '[open_section\n')
2528
store = config.TransportIniFileStore(t, 'foo.conf')
2529
self.assertRaises(errors.ParseConfigError, store.load)
2531
def test_load_permission_denied(self):
2532
"""Ensure we get warned when trying to load an inaccessible file."""
2535
warnings.append(args[0] % args[1:])
2536
self.overrideAttr(trace, 'warning', warning)
2538
t = self.get_transport()
2540
def get_bytes(relpath):
2541
raise errors.PermissionDenied(relpath, "")
2542
t.get_bytes = get_bytes
2543
store = config.TransportIniFileStore(t, 'foo.conf')
2544
self.assertRaises(errors.PermissionDenied, store.load)
2547
[u'Permission denied while trying to load configuration store %s.'
2548
% store.external_url()])
2551
class TestIniConfigContent(tests.TestCaseWithTransport):
2552
"""Simulate loading a IniBasedConfig with content of various encodings.
2554
All files produced by bzr are in utf8 content.
2556
Users may modify them manually and end up with a file that can't be
2557
loaded. We need to issue proper error messages in this case.
2560
invalid_utf8_char = '\xff'
2562
def test_load_utf8(self):
2563
"""Ensure we can load an utf8-encoded file."""
2564
# From http://pad.lv/799212
2565
unicode_user = u'b\N{Euro Sign}ar'
2566
unicode_content = u'user=%s' % (unicode_user,)
2567
utf8_content = unicode_content.encode('utf8')
2568
# Store the raw content in the config file
2569
with open('foo.conf', 'wb') as f:
2570
f.write(utf8_content)
2571
conf = config.IniBasedConfig(file_name='foo.conf')
2572
self.assertEqual(unicode_user, conf.get_user_option('user'))
2574
def test_load_badly_encoded_content(self):
2575
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2576
with open('foo.conf', 'wb') as f:
2577
f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
2578
conf = config.IniBasedConfig(file_name='foo.conf')
2579
self.assertRaises(errors.ConfigContentError, conf._get_parser)
2581
def test_load_erroneous_content(self):
2582
"""Ensure we display a proper error on content that can't be parsed."""
2583
with open('foo.conf', 'wb') as f:
2584
f.write('[open_section\n')
2585
conf = config.IniBasedConfig(file_name='foo.conf')
2586
self.assertRaises(errors.ParseConfigError, conf._get_parser)
2589
class TestMutableStore(TestStore):
2591
scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
2592
in config.test_store_builder_registry.iteritems()]
2595
super(TestMutableStore, self).setUp()
2596
self.transport = self.get_transport()
2598
def has_store(self, store):
2599
store_basename = urlutils.relative_url(self.transport.external_url(),
2600
store.external_url())
2601
return self.transport.has(store_basename)
2603
def test_save_empty_creates_no_file(self):
2604
# FIXME: There should be a better way than relying on the test
2605
# parametrization to identify branch.conf -- vila 2011-0526
2606
if self.store_id in ('branch', 'remote_branch'):
2607
raise tests.TestNotApplicable(
2608
'branch.conf is *always* created when a branch is initialized')
2609
store = self.get_store(self)
2611
self.assertEqual(False, self.has_store(store))
2613
def test_mutable_section_shared(self):
2614
store = self.get_store(self)
2615
store._load_from_string('foo=bar\n')
2616
# FIXME: There should be a better way than relying on the test
2617
# parametrization to identify branch.conf -- vila 2011-0526
2618
if self.store_id in ('branch', 'remote_branch'):
2619
# branch stores requires write locked branches
2620
self.addCleanup(store.branch.lock_write().unlock)
2621
section1 = store.get_mutable_section(None)
2622
section2 = store.get_mutable_section(None)
2623
# If we get different sections, different callers won't share the
2625
self.assertIs(section1, section2)
2627
def test_save_emptied_succeeds(self):
2628
store = self.get_store(self)
2629
store._load_from_string('foo=bar\n')
2630
# FIXME: There should be a better way than relying on the test
2631
# parametrization to identify branch.conf -- vila 2011-0526
2632
if self.store_id in ('branch', 'remote_branch'):
2633
# branch stores requires write locked branches
2634
self.addCleanup(store.branch.lock_write().unlock)
2635
section = store.get_mutable_section(None)
2636
section.remove('foo')
2638
self.assertEqual(True, self.has_store(store))
2639
modified_store = self.get_store(self)
2640
sections = list(modified_store.get_sections())
2641
self.assertLength(0, sections)
2643
def test_save_with_content_succeeds(self):
2644
# FIXME: There should be a better way than relying on the test
2645
# parametrization to identify branch.conf -- vila 2011-0526
2646
if self.store_id in ('branch', 'remote_branch'):
2647
raise tests.TestNotApplicable(
2648
'branch.conf is *always* created when a branch is initialized')
2649
store = self.get_store(self)
2650
store._load_from_string('foo=bar\n')
2651
self.assertEqual(False, self.has_store(store))
2653
self.assertEqual(True, self.has_store(store))
2654
modified_store = self.get_store(self)
2655
sections = list(modified_store.get_sections())
2656
self.assertLength(1, sections)
2657
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2659
def test_set_option_in_empty_store(self):
2660
store = self.get_store(self)
2661
# FIXME: There should be a better way than relying on the test
2662
# parametrization to identify branch.conf -- vila 2011-0526
2663
if self.store_id in ('branch', 'remote_branch'):
2664
# branch stores requires write locked branches
2665
self.addCleanup(store.branch.lock_write().unlock)
2666
section = store.get_mutable_section(None)
2667
section.set('foo', 'bar')
2669
modified_store = self.get_store(self)
2670
sections = list(modified_store.get_sections())
2671
self.assertLength(1, sections)
2672
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2674
def test_set_option_in_default_section(self):
2675
store = self.get_store(self)
2676
store._load_from_string('')
2677
# FIXME: There should be a better way than relying on the test
2678
# parametrization to identify branch.conf -- vila 2011-0526
2679
if self.store_id in ('branch', 'remote_branch'):
2680
# branch stores requires write locked branches
2681
self.addCleanup(store.branch.lock_write().unlock)
2682
section = store.get_mutable_section(None)
2683
section.set('foo', 'bar')
2685
modified_store = self.get_store(self)
2686
sections = list(modified_store.get_sections())
2687
self.assertLength(1, sections)
2688
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2690
def test_set_option_in_named_section(self):
2691
store = self.get_store(self)
2692
store._load_from_string('')
2693
# FIXME: There should be a better way than relying on the test
2694
# parametrization to identify branch.conf -- vila 2011-0526
2695
if self.store_id in ('branch', 'remote_branch'):
2696
# branch stores requires write locked branches
2697
self.addCleanup(store.branch.lock_write().unlock)
2698
section = store.get_mutable_section('baz')
2699
section.set('foo', 'bar')
2701
modified_store = self.get_store(self)
2702
sections = list(modified_store.get_sections())
2703
self.assertLength(1, sections)
2704
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2706
def test_load_hook(self):
2707
# First, we need to ensure that the store exists
2708
store = self.get_store(self)
2709
# FIXME: There should be a better way than relying on the test
2710
# parametrization to identify branch.conf -- vila 2011-0526
2711
if self.store_id in ('branch', 'remote_branch'):
2712
# branch stores requires write locked branches
2713
self.addCleanup(store.branch.lock_write().unlock)
2714
section = store.get_mutable_section('baz')
2715
section.set('foo', 'bar')
2717
# Now we can try to load it
2718
store = self.get_store(self)
2722
config.ConfigHooks.install_named_hook('load', hook, None)
2723
self.assertLength(0, calls)
2725
self.assertLength(1, calls)
2726
self.assertEqual((store,), calls[0])
2728
def test_save_hook(self):
2732
config.ConfigHooks.install_named_hook('save', hook, None)
2733
self.assertLength(0, calls)
2734
store = self.get_store(self)
2735
# FIXME: There should be a better way than relying on the test
2736
# parametrization to identify branch.conf -- vila 2011-0526
2737
if self.store_id in ('branch', 'remote_branch'):
2738
# branch stores requires write locked branches
2739
self.addCleanup(store.branch.lock_write().unlock)
2740
section = store.get_mutable_section('baz')
2741
section.set('foo', 'bar')
2743
self.assertLength(1, calls)
2744
self.assertEqual((store,), calls[0])
2746
def test_set_mark_dirty(self):
2747
stack = config.MemoryStack('')
2748
self.assertLength(0, stack.store.dirty_sections)
2749
stack.set('foo', 'baz')
2750
self.assertLength(1, stack.store.dirty_sections)
2751
self.assertTrue(stack.store._need_saving())
2753
def test_remove_mark_dirty(self):
2754
stack = config.MemoryStack('foo=bar')
2755
self.assertLength(0, stack.store.dirty_sections)
2757
self.assertLength(1, stack.store.dirty_sections)
2758
self.assertTrue(stack.store._need_saving())
2761
class TestStoreSaveChanges(tests.TestCaseWithTransport):
2762
"""Tests that config changes are kept in memory and saved on-demand."""
2765
super(TestStoreSaveChanges, self).setUp()
2766
self.transport = self.get_transport()
2767
# Most of the tests involve two stores pointing to the same persistent
2768
# storage to observe the effects of concurrent changes
2769
self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
2770
self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
2773
self.warnings.append(args[0] % args[1:])
2774
self.overrideAttr(trace, 'warning', warning)
2776
def has_store(self, store):
2777
store_basename = urlutils.relative_url(self.transport.external_url(),
2778
store.external_url())
2779
return self.transport.has(store_basename)
2781
def get_stack(self, store):
2782
# Any stack will do as long as it uses the right store, just a single
2783
# no-name section is enough
2784
return config.Stack([store.get_sections], store)
2786
def test_no_changes_no_save(self):
2787
s = self.get_stack(self.st1)
2788
s.store.save_changes()
2789
self.assertEqual(False, self.has_store(self.st1))
2791
def test_unrelated_concurrent_update(self):
2792
s1 = self.get_stack(self.st1)
2793
s2 = self.get_stack(self.st2)
2794
s1.set('foo', 'bar')
2795
s2.set('baz', 'quux')
2797
# Changes don't propagate magically
2798
self.assertEqual(None, s1.get('baz'))
2799
s2.store.save_changes()
2800
self.assertEqual('quux', s2.get('baz'))
2801
# Changes are acquired when saving
2802
self.assertEqual('bar', s2.get('foo'))
2803
# Since there is no overlap, no warnings are emitted
2804
self.assertLength(0, self.warnings)
2806
def test_concurrent_update_modified(self):
2807
s1 = self.get_stack(self.st1)
2808
s2 = self.get_stack(self.st2)
2809
s1.set('foo', 'bar')
2810
s2.set('foo', 'baz')
2813
s2.store.save_changes()
2814
self.assertEqual('baz', s2.get('foo'))
2815
# But the user get a warning
2816
self.assertLength(1, self.warnings)
2817
warning = self.warnings[0]
2818
self.assertStartsWith(warning, 'Option foo in section None')
2819
self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
2820
' The baz value will be saved.')
2822
def test_concurrent_deletion(self):
2823
self.st1._load_from_string('foo=bar')
2825
s1 = self.get_stack(self.st1)
2826
s2 = self.get_stack(self.st2)
2829
s1.store.save_changes()
2831
self.assertLength(0, self.warnings)
2832
s2.store.save_changes()
2834
self.assertLength(1, self.warnings)
2835
warning = self.warnings[0]
2836
self.assertStartsWith(warning, 'Option foo in section None')
2837
self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
2838
' The <DELETED> value will be saved.')
2841
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
2843
def get_store(self):
2844
return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
2846
def test_get_quoted_string(self):
2847
store = self.get_store()
2848
store._load_from_string('foo= " abc "')
2849
stack = config.Stack([store.get_sections])
2850
self.assertEqual(' abc ', stack.get('foo'))
2852
def test_set_quoted_string(self):
2853
store = self.get_store()
2854
stack = config.Stack([store.get_sections], store)
2855
stack.set('foo', ' a b c ')
2857
self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
2860
class TestTransportIniFileStore(TestStore):
2862
def test_loading_unknown_file_fails(self):
2863
store = config.TransportIniFileStore(self.get_transport(),
2865
self.assertRaises(errors.NoSuchFile, store.load)
2867
def test_invalid_content(self):
2868
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
2869
self.assertEqual(False, store.is_loaded())
2870
exc = self.assertRaises(
2871
errors.ParseConfigError, store._load_from_string,
2872
'this is invalid !')
2873
self.assertEndsWith(exc.filename, 'foo.conf')
2874
# And the load failed
2875
self.assertEqual(False, store.is_loaded())
2877
def test_get_embedded_sections(self):
2878
# A more complicated example (which also shows that section names and
2879
# option names share the same name space...)
2880
# FIXME: This should be fixed by forbidding dicts as values ?
2881
# -- vila 2011-04-05
2882
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
2883
store._load_from_string('''
2887
foo_in_DEFAULT=foo_DEFAULT
2895
sections = list(store.get_sections())
2896
self.assertLength(4, sections)
2897
# The default section has no name.
2898
# List values are provided as strings and need to be explicitly
2899
# converted by specifying from_unicode=list_from_store at option
2901
self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
2903
self.assertSectionContent(
2904
('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
2905
self.assertSectionContent(
2906
('bar', {'foo_in_bar': 'barbar'}), sections[2])
2907
# sub sections are provided as embedded dicts.
2908
self.assertSectionContent(
2909
('baz', {'foo_in_baz': 'barbaz', 'qux': {'foo_in_qux': 'quux'}}),
2913
class TestLockableIniFileStore(TestStore):
2915
def test_create_store_in_created_dir(self):
2916
self.assertPathDoesNotExist('dir')
2917
t = self.get_transport('dir/subdir')
2918
store = config.LockableIniFileStore(t, 'foo.conf')
2919
store.get_mutable_section(None).set('foo', 'bar')
2921
self.assertPathExists('dir/subdir')
2924
class TestConcurrentStoreUpdates(TestStore):
2925
"""Test that Stores properly handle conccurent updates.
2927
New Store implementation may fail some of these tests but until such
2928
implementations exist it's hard to properly filter them from the scenarios
2929
applied here. If you encounter such a case, contact the bzr devs.
2932
scenarios = [(key, {'get_stack': builder}) for key, builder
2933
in config.test_stack_builder_registry.iteritems()]
2936
super(TestConcurrentStoreUpdates, self).setUp()
2937
self.stack = self.get_stack(self)
2938
if not isinstance(self.stack, config._CompatibleStack):
2939
raise tests.TestNotApplicable(
2940
'%s is not meant to be compatible with the old config design'
2942
self.stack.set('one', '1')
2943
self.stack.set('two', '2')
2945
self.stack.store.save()
2947
def test_simple_read_access(self):
2948
self.assertEqual('1', self.stack.get('one'))
2950
def test_simple_write_access(self):
2951
self.stack.set('one', 'one')
2952
self.assertEqual('one', self.stack.get('one'))
2954
def test_listen_to_the_last_speaker(self):
2956
c2 = self.get_stack(self)
2957
c1.set('one', 'ONE')
2958
c2.set('two', 'TWO')
2959
self.assertEqual('ONE', c1.get('one'))
2960
self.assertEqual('TWO', c2.get('two'))
2961
# The second update respect the first one
2962
self.assertEqual('ONE', c2.get('one'))
2964
def test_last_speaker_wins(self):
2965
# If the same config is not shared, the same variable modified twice
2966
# can only see a single result.
2968
c2 = self.get_stack(self)
2971
self.assertEqual('c2', c2.get('one'))
2972
# The first modification is still available until another refresh
2974
self.assertEqual('c1', c1.get('one'))
2975
c1.set('two', 'done')
2976
self.assertEqual('c2', c1.get('one'))
2978
def test_writes_are_serialized(self):
2980
c2 = self.get_stack(self)
2982
# We spawn a thread that will pause *during* the config saving.
2983
before_writing = threading.Event()
2984
after_writing = threading.Event()
2985
writing_done = threading.Event()
2986
c1_save_without_locking_orig = c1.store.save_without_locking
2987
def c1_save_without_locking():
2988
before_writing.set()
2989
c1_save_without_locking_orig()
2990
# The lock is held. We wait for the main thread to decide when to
2992
after_writing.wait()
2993
c1.store.save_without_locking = c1_save_without_locking
2997
t1 = threading.Thread(target=c1_set)
2998
# Collect the thread after the test
2999
self.addCleanup(t1.join)
3000
# Be ready to unblock the thread if the test goes wrong
3001
self.addCleanup(after_writing.set)
3003
before_writing.wait()
3004
self.assertRaises(errors.LockContention,
3005
c2.set, 'one', 'c2')
3006
self.assertEqual('c1', c1.get('one'))
3007
# Let the lock be released
3011
self.assertEqual('c2', c2.get('one'))
3013
def test_read_while_writing(self):
3015
# We spawn a thread that will pause *during* the write
3016
ready_to_write = threading.Event()
3017
do_writing = threading.Event()
3018
writing_done = threading.Event()
3019
# We override the _save implementation so we know the store is locked
3020
c1_save_without_locking_orig = c1.store.save_without_locking
3021
def c1_save_without_locking():
3022
ready_to_write.set()
3023
# The lock is held. We wait for the main thread to decide when to
3026
c1_save_without_locking_orig()
3028
c1.store.save_without_locking = c1_save_without_locking
3031
t1 = threading.Thread(target=c1_set)
3032
# Collect the thread after the test
3033
self.addCleanup(t1.join)
3034
# Be ready to unblock the thread if the test goes wrong
3035
self.addCleanup(do_writing.set)
3037
# Ensure the thread is ready to write
3038
ready_to_write.wait()
3039
self.assertEqual('c1', c1.get('one'))
3040
# If we read during the write, we get the old value
3041
c2 = self.get_stack(self)
3042
self.assertEqual('1', c2.get('one'))
3043
# Let the writing occur and ensure it occurred
3046
# Now we get the updated value
3047
c3 = self.get_stack(self)
3048
self.assertEqual('c1', c3.get('one'))
3050
# FIXME: It may be worth looking into removing the lock dir when it's not
3051
# needed anymore and look at possible fallouts for concurrent lockers. This
3052
# will matter if/when we use config files outside of bazaar directories
3053
# (.bazaar or .bzr) -- vila 20110-04-111
3056
class TestSectionMatcher(TestStore):
3058
scenarios = [('location', {'matcher': config.LocationMatcher}),
3059
('id', {'matcher': config.NameMatcher}),]
3062
super(TestSectionMatcher, self).setUp()
3063
# Any simple store is good enough
3064
self.get_store = config.test_store_builder_registry.get('configobj')
3066
def test_no_matches_for_empty_stores(self):
3067
store = self.get_store(self)
3068
store._load_from_string('')
3069
matcher = self.matcher(store, '/bar')
3070
self.assertEqual([], list(matcher.get_sections()))
3072
def test_build_doesnt_load_store(self):
3073
store = self.get_store(self)
3074
self.matcher(store, '/bar')
3075
self.assertFalse(store.is_loaded())
3078
class TestLocationSection(tests.TestCase):
3080
def get_section(self, options, extra_path):
3081
section = config.Section('foo', options)
3082
return config.LocationSection(section, extra_path)
3084
def test_simple_option(self):
3085
section = self.get_section({'foo': 'bar'}, '')
3086
self.assertEqual('bar', section.get('foo'))
3088
def test_option_with_extra_path(self):
3089
section = self.get_section({'foo': 'bar', 'foo:policy': 'appendpath'},
3091
self.assertEqual('bar/baz', section.get('foo'))
3093
def test_invalid_policy(self):
3094
section = self.get_section({'foo': 'bar', 'foo:policy': 'die'},
3096
# invalid policies are ignored
3097
self.assertEqual('bar', section.get('foo'))
3100
class TestLocationMatcher(TestStore):
3103
super(TestLocationMatcher, self).setUp()
3104
# Any simple store is good enough
3105
self.get_store = config.test_store_builder_registry.get('configobj')
3107
def test_unrelated_section_excluded(self):
3108
store = self.get_store(self)
3109
store._load_from_string('''
3117
section=/foo/bar/baz
3121
self.assertEqual(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
3123
[section.id for _, section in store.get_sections()])
3124
matcher = config.LocationMatcher(store, '/foo/bar/quux')
3125
sections = [section for _, section in matcher.get_sections()]
3126
self.assertEqual(['/foo/bar', '/foo'],
3127
[section.id for section in sections])
3128
self.assertEqual(['quux', 'bar/quux'],
3129
[section.extra_path for section in sections])
3131
def test_more_specific_sections_first(self):
3132
store = self.get_store(self)
3133
store._load_from_string('''
3139
self.assertEqual(['/foo', '/foo/bar'],
3140
[section.id for _, section in store.get_sections()])
3141
matcher = config.LocationMatcher(store, '/foo/bar/baz')
3142
sections = [section for _, section in matcher.get_sections()]
3143
self.assertEqual(['/foo/bar', '/foo'],
3144
[section.id for section in sections])
3145
self.assertEqual(['baz', 'bar/baz'],
3146
[section.extra_path for section in sections])
3148
def test_appendpath_in_no_name_section(self):
3149
# It's a bit weird to allow appendpath in a no-name section, but
3150
# someone may found a use for it
3151
store = self.get_store(self)
3152
store._load_from_string('''
3154
foo:policy = appendpath
3156
matcher = config.LocationMatcher(store, 'dir/subdir')
3157
sections = list(matcher.get_sections())
3158
self.assertLength(1, sections)
3159
self.assertEqual('bar/dir/subdir', sections[0][1].get('foo'))
3161
def test_file_urls_are_normalized(self):
3162
store = self.get_store(self)
3163
if sys.platform == 'win32':
3164
expected_url = 'file:///C:/dir/subdir'
3165
expected_location = 'C:/dir/subdir'
3167
expected_url = 'file:///dir/subdir'
3168
expected_location = '/dir/subdir'
3169
matcher = config.LocationMatcher(store, expected_url)
3170
self.assertEqual(expected_location, matcher.location)
3172
def test_branch_name_colo(self):
3173
store = self.get_store(self)
3174
store._load_from_string(dedent("""\
3176
push_location=my{branchname}
3178
matcher = config.LocationMatcher(store, 'file:///,branch=example%3c')
3179
self.assertEqual('example<', matcher.branch_name)
3180
((_, section),) = matcher.get_sections()
3181
self.assertEqual('example<', section.locals['branchname'])
3183
def test_branch_name_basename(self):
3184
store = self.get_store(self)
3185
store._load_from_string(dedent("""\
3187
push_location=my{branchname}
3189
matcher = config.LocationMatcher(store, 'file:///parent/example%3c')
3190
self.assertEqual('example<', matcher.branch_name)
3191
((_, section),) = matcher.get_sections()
3192
self.assertEqual('example<', section.locals['branchname'])
3195
class TestStartingPathMatcher(TestStore):
3198
super(TestStartingPathMatcher, self).setUp()
3199
# Any simple store is good enough
3200
self.store = config.IniFileStore()
3202
def assertSectionIDs(self, expected, location, content):
3203
self.store._load_from_string(content)
3204
matcher = config.StartingPathMatcher(self.store, location)
3205
sections = list(matcher.get_sections())
3206
self.assertLength(len(expected), sections)
3207
self.assertEqual(expected, [section.id for _, section in sections])
3210
def test_empty(self):
3211
self.assertSectionIDs([], self.get_url(), '')
3213
def test_url_vs_local_paths(self):
3214
# The matcher location is an url and the section names are local paths
3215
self.assertSectionIDs(['/foo/bar', '/foo'],
3216
'file:///foo/bar/baz', '''\
3221
def test_local_path_vs_url(self):
3222
# The matcher location is a local path and the section names are urls
3223
self.assertSectionIDs(['file:///foo/bar', 'file:///foo'],
3224
'/foo/bar/baz', '''\
3230
def test_no_name_section_included_when_present(self):
3231
# Note that other tests will cover the case where the no-name section
3232
# is empty and as such, not included.
3233
sections = self.assertSectionIDs(['/foo/bar', '/foo', None],
3234
'/foo/bar/baz', '''\
3235
option = defined so the no-name section exists
3239
self.assertEqual(['baz', 'bar/baz', '/foo/bar/baz'],
3240
[s.locals['relpath'] for _, s in sections])
3242
def test_order_reversed(self):
3243
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3248
def test_unrelated_section_excluded(self):
3249
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3255
def test_glob_included(self):
3256
sections = self.assertSectionIDs(['/foo/*/baz', '/foo/b*', '/foo'],
3257
'/foo/bar/baz', '''\
3263
# Note that 'baz' as a relpath for /foo/b* is not fully correct, but
3264
# nothing really is... as far using {relpath} to append it to something
3265
# else, this seems good enough though.
3266
self.assertEqual(['', 'baz', 'bar/baz'],
3267
[s.locals['relpath'] for _, s in sections])
3269
def test_respect_order(self):
3270
self.assertSectionIDs(['/foo', '/foo/b*', '/foo/*/baz'],
3271
'/foo/bar/baz', '''\
3279
class TestNameMatcher(TestStore):
3282
super(TestNameMatcher, self).setUp()
3283
self.matcher = config.NameMatcher
3284
# Any simple store is good enough
3285
self.get_store = config.test_store_builder_registry.get('configobj')
3287
def get_matching_sections(self, name):
3288
store = self.get_store(self)
3289
store._load_from_string('''
3297
matcher = self.matcher(store, name)
3298
return list(matcher.get_sections())
3300
def test_matching(self):
3301
sections = self.get_matching_sections('foo')
3302
self.assertLength(1, sections)
3303
self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
3305
def test_not_matching(self):
3306
sections = self.get_matching_sections('baz')
3307
self.assertLength(0, sections)
3310
class TestBaseStackGet(tests.TestCase):
3313
super(TestBaseStackGet, self).setUp()
3314
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3316
def test_get_first_definition(self):
3317
store1 = config.IniFileStore()
3318
store1._load_from_string('foo=bar')
3319
store2 = config.IniFileStore()
3320
store2._load_from_string('foo=baz')
3321
conf = config.Stack([store1.get_sections, store2.get_sections])
3322
self.assertEqual('bar', conf.get('foo'))
3324
def test_get_with_registered_default_value(self):
3325
config.option_registry.register(config.Option('foo', default='bar'))
3326
conf_stack = config.Stack([])
3327
self.assertEqual('bar', conf_stack.get('foo'))
3329
def test_get_without_registered_default_value(self):
3330
config.option_registry.register(config.Option('foo'))
3331
conf_stack = config.Stack([])
3332
self.assertEqual(None, conf_stack.get('foo'))
3334
def test_get_without_default_value_for_not_registered(self):
3335
conf_stack = config.Stack([])
3336
self.assertEqual(None, conf_stack.get('foo'))
3338
def test_get_for_empty_section_callable(self):
3339
conf_stack = config.Stack([lambda : []])
3340
self.assertEqual(None, conf_stack.get('foo'))
3342
def test_get_for_broken_callable(self):
3343
# Trying to use and invalid callable raises an exception on first use
3344
conf_stack = config.Stack([object])
3345
self.assertRaises(TypeError, conf_stack.get, 'foo')
3348
class TestStackWithSimpleStore(tests.TestCase):
3351
super(TestStackWithSimpleStore, self).setUp()
3352
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3353
self.registry = config.option_registry
3355
def get_conf(self, content=None):
3356
return config.MemoryStack(content)
3358
def test_override_value_from_env(self):
3359
self.overrideEnv('FOO', None)
3360
self.registry.register(
3361
config.Option('foo', default='bar', override_from_env=['FOO']))
3362
self.overrideEnv('FOO', 'quux')
3363
# Env variable provides a default taking over the option one
3364
conf = self.get_conf('foo=store')
3365
self.assertEqual('quux', conf.get('foo'))
3367
def test_first_override_value_from_env_wins(self):
3368
self.overrideEnv('NO_VALUE', None)
3369
self.overrideEnv('FOO', None)
3370
self.overrideEnv('BAZ', None)
3371
self.registry.register(
3372
config.Option('foo', default='bar',
3373
override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
3374
self.overrideEnv('FOO', 'foo')
3375
self.overrideEnv('BAZ', 'baz')
3376
# The first env var set wins
3377
conf = self.get_conf('foo=store')
3378
self.assertEqual('foo', conf.get('foo'))
3381
class TestMemoryStack(tests.TestCase):
3384
conf = config.MemoryStack('foo=bar')
3385
self.assertEqual('bar', conf.get('foo'))
3388
conf = config.MemoryStack('foo=bar')
3389
conf.set('foo', 'baz')
3390
self.assertEqual('baz', conf.get('foo'))
3392
def test_no_content(self):
3393
conf = config.MemoryStack()
3394
# No content means no loading
3395
self.assertFalse(conf.store.is_loaded())
3396
self.assertRaises(NotImplementedError, conf.get, 'foo')
3397
# But a content can still be provided
3398
conf.store._load_from_string('foo=bar')
3399
self.assertEqual('bar', conf.get('foo'))
3402
class TestStackIterSections(tests.TestCase):
3404
def test_empty_stack(self):
3405
conf = config.Stack([])
3406
sections = list(conf.iter_sections())
3407
self.assertLength(0, sections)
3409
def test_empty_store(self):
3410
store = config.IniFileStore()
3411
store._load_from_string('')
3412
conf = config.Stack([store.get_sections])
3413
sections = list(conf.iter_sections())
3414
self.assertLength(0, sections)
3416
def test_simple_store(self):
3417
store = config.IniFileStore()
3418
store._load_from_string('foo=bar')
3419
conf = config.Stack([store.get_sections])
3420
tuples = list(conf.iter_sections())
3421
self.assertLength(1, tuples)
3422
(found_store, found_section) = tuples[0]
3423
self.assertIs(store, found_store)
3425
def test_two_stores(self):
3426
store1 = config.IniFileStore()
3427
store1._load_from_string('foo=bar')
3428
store2 = config.IniFileStore()
3429
store2._load_from_string('bar=qux')
3430
conf = config.Stack([store1.get_sections, store2.get_sections])
3431
tuples = list(conf.iter_sections())
3432
self.assertLength(2, tuples)
3433
self.assertIs(store1, tuples[0][0])
3434
self.assertIs(store2, tuples[1][0])
3437
class TestStackWithTransport(tests.TestCaseWithTransport):
3439
scenarios = [(key, {'get_stack': builder}) for key, builder
3440
in config.test_stack_builder_registry.iteritems()]
3443
class TestConcreteStacks(TestStackWithTransport):
3445
def test_build_stack(self):
3446
# Just a smoke test to help debug builders
3447
self.get_stack(self)
3450
class TestStackGet(TestStackWithTransport):
3453
super(TestStackGet, self).setUp()
3454
self.conf = self.get_stack(self)
3456
def test_get_for_empty_stack(self):
3457
self.assertEqual(None, self.conf.get('foo'))
3459
def test_get_hook(self):
3460
self.conf.set('foo', 'bar')
3464
config.ConfigHooks.install_named_hook('get', hook, None)
3465
self.assertLength(0, calls)
3466
value = self.conf.get('foo')
3467
self.assertEqual('bar', value)
3468
self.assertLength(1, calls)
3469
self.assertEqual((self.conf, 'foo', 'bar'), calls[0])
3472
class TestStackGetWithConverter(tests.TestCase):
3475
super(TestStackGetWithConverter, self).setUp()
3476
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3477
self.registry = config.option_registry
3479
def get_conf(self, content=None):
3480
return config.MemoryStack(content)
3482
def register_bool_option(self, name, default=None, default_from_env=None):
3483
b = config.Option(name, help='A boolean.',
3484
default=default, default_from_env=default_from_env,
3485
from_unicode=config.bool_from_store)
3486
self.registry.register(b)
3488
def test_get_default_bool_None(self):
3489
self.register_bool_option('foo')
3490
conf = self.get_conf('')
3491
self.assertEqual(None, conf.get('foo'))
3493
def test_get_default_bool_True(self):
3494
self.register_bool_option('foo', u'True')
3495
conf = self.get_conf('')
3496
self.assertEqual(True, conf.get('foo'))
3498
def test_get_default_bool_False(self):
3499
self.register_bool_option('foo', False)
3500
conf = self.get_conf('')
3501
self.assertEqual(False, conf.get('foo'))
3503
def test_get_default_bool_False_as_string(self):
3504
self.register_bool_option('foo', u'False')
3505
conf = self.get_conf('')
3506
self.assertEqual(False, conf.get('foo'))
3508
def test_get_default_bool_from_env_converted(self):
3509
self.register_bool_option('foo', u'True', default_from_env=['FOO'])
3510
self.overrideEnv('FOO', 'False')
3511
conf = self.get_conf('')
3512
self.assertEqual(False, conf.get('foo'))
3514
def test_get_default_bool_when_conversion_fails(self):
3515
self.register_bool_option('foo', default='True')
3516
conf = self.get_conf('foo=invalid boolean')
3517
self.assertEqual(True, conf.get('foo'))
3519
def register_integer_option(self, name,
3520
default=None, default_from_env=None):
3521
i = config.Option(name, help='An integer.',
3522
default=default, default_from_env=default_from_env,
3523
from_unicode=config.int_from_store)
3524
self.registry.register(i)
3526
def test_get_default_integer_None(self):
3527
self.register_integer_option('foo')
3528
conf = self.get_conf('')
3529
self.assertEqual(None, conf.get('foo'))
3531
def test_get_default_integer(self):
3532
self.register_integer_option('foo', 42)
3533
conf = self.get_conf('')
3534
self.assertEqual(42, conf.get('foo'))
3536
def test_get_default_integer_as_string(self):
3537
self.register_integer_option('foo', u'42')
3538
conf = self.get_conf('')
3539
self.assertEqual(42, conf.get('foo'))
3541
def test_get_default_integer_from_env(self):
3542
self.register_integer_option('foo', default_from_env=['FOO'])
3543
self.overrideEnv('FOO', '18')
3544
conf = self.get_conf('')
3545
self.assertEqual(18, conf.get('foo'))
3547
def test_get_default_integer_when_conversion_fails(self):
3548
self.register_integer_option('foo', default='12')
3549
conf = self.get_conf('foo=invalid integer')
3550
self.assertEqual(12, conf.get('foo'))
3552
def register_list_option(self, name, default=None, default_from_env=None):
3553
l = config.ListOption(name, help='A list.', default=default,
3554
default_from_env=default_from_env)
3555
self.registry.register(l)
3557
def test_get_default_list_None(self):
3558
self.register_list_option('foo')
3559
conf = self.get_conf('')
3560
self.assertEqual(None, conf.get('foo'))
3562
def test_get_default_list_empty(self):
3563
self.register_list_option('foo', '')
3564
conf = self.get_conf('')
3565
self.assertEqual([], conf.get('foo'))
3567
def test_get_default_list_from_env(self):
3568
self.register_list_option('foo', default_from_env=['FOO'])
3569
self.overrideEnv('FOO', '')
3570
conf = self.get_conf('')
3571
self.assertEqual([], conf.get('foo'))
3573
def test_get_with_list_converter_no_item(self):
3574
self.register_list_option('foo', None)
3575
conf = self.get_conf('foo=,')
3576
self.assertEqual([], conf.get('foo'))
3578
def test_get_with_list_converter_many_items(self):
3579
self.register_list_option('foo', None)
3580
conf = self.get_conf('foo=m,o,r,e')
3581
self.assertEqual(['m', 'o', 'r', 'e'], conf.get('foo'))
3583
def test_get_with_list_converter_embedded_spaces_many_items(self):
3584
self.register_list_option('foo', None)
3585
conf = self.get_conf('foo=" bar", "baz "')
3586
self.assertEqual([' bar', 'baz '], conf.get('foo'))
3588
def test_get_with_list_converter_stripped_spaces_many_items(self):
3589
self.register_list_option('foo', None)
3590
conf = self.get_conf('foo= bar , baz ')
3591
self.assertEqual(['bar', 'baz'], conf.get('foo'))
3594
class TestIterOptionRefs(tests.TestCase):
3595
"""iter_option_refs is a bit unusual, document some cases."""
3597
def assertRefs(self, expected, string):
3598
self.assertEqual(expected, list(config.iter_option_refs(string)))
3600
def test_empty(self):
3601
self.assertRefs([(False, '')], '')
3603
def test_no_refs(self):
3604
self.assertRefs([(False, 'foo bar')], 'foo bar')
3606
def test_single_ref(self):
3607
self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
3609
def test_broken_ref(self):
3610
self.assertRefs([(False, '{foo')], '{foo')
3612
def test_embedded_ref(self):
3613
self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
3616
def test_two_refs(self):
3617
self.assertRefs([(False, ''), (True, '{foo}'),
3618
(False, ''), (True, '{bar}'),
3622
def test_newline_in_refs_are_not_matched(self):
3623
self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
3626
class TestStackExpandOptions(tests.TestCaseWithTransport):
3629
super(TestStackExpandOptions, self).setUp()
3630
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3631
self.registry = config.option_registry
3632
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3633
self.conf = config.Stack([store.get_sections], store)
3635
def assertExpansion(self, expected, string, env=None):
3636
self.assertEqual(expected, self.conf.expand_options(string, env))
3638
def test_no_expansion(self):
3639
self.assertExpansion('foo', 'foo')
3641
def test_expand_default_value(self):
3642
self.conf.store._load_from_string('bar=baz')
3643
self.registry.register(config.Option('foo', default=u'{bar}'))
3644
self.assertEqual('baz', self.conf.get('foo', expand=True))
3646
def test_expand_default_from_env(self):
3647
self.conf.store._load_from_string('bar=baz')
3648
self.registry.register(config.Option('foo', default_from_env=['FOO']))
3649
self.overrideEnv('FOO', '{bar}')
3650
self.assertEqual('baz', self.conf.get('foo', expand=True))
3652
def test_expand_default_on_failed_conversion(self):
3653
self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
3654
self.registry.register(
3655
config.Option('foo', default=u'{bar}',
3656
from_unicode=config.int_from_store))
3657
self.assertEqual(42, self.conf.get('foo', expand=True))
3659
def test_env_adding_options(self):
3660
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3662
def test_env_overriding_options(self):
3663
self.conf.store._load_from_string('foo=baz')
3664
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3666
def test_simple_ref(self):
3667
self.conf.store._load_from_string('foo=xxx')
3668
self.assertExpansion('xxx', '{foo}')
3670
def test_unknown_ref(self):
3671
self.assertRaises(errors.ExpandingUnknownOption,
3672
self.conf.expand_options, '{foo}')
3674
def test_illegal_def_is_ignored(self):
3675
self.assertExpansion('{1,2}', '{1,2}')
3676
self.assertExpansion('{ }', '{ }')
3677
self.assertExpansion('${Foo,f}', '${Foo,f}')
3679
def test_indirect_ref(self):
3680
self.conf.store._load_from_string('''
3684
self.assertExpansion('xxx', '{bar}')
3686
def test_embedded_ref(self):
3687
self.conf.store._load_from_string('''
3691
self.assertExpansion('xxx', '{{bar}}')
3693
def test_simple_loop(self):
3694
self.conf.store._load_from_string('foo={foo}')
3695
self.assertRaises(errors.OptionExpansionLoop,
3696
self.conf.expand_options, '{foo}')
3698
def test_indirect_loop(self):
3699
self.conf.store._load_from_string('''
3703
e = self.assertRaises(errors.OptionExpansionLoop,
3704
self.conf.expand_options, '{foo}')
3705
self.assertEqual('foo->bar->baz', e.refs)
3706
self.assertEqual('{foo}', e.string)
3708
def test_list(self):
3709
self.conf.store._load_from_string('''
3713
list={foo},{bar},{baz}
3715
self.registry.register(
3716
config.ListOption('list'))
3717
self.assertEqual(['start', 'middle', 'end'],
3718
self.conf.get('list', expand=True))
3720
def test_cascading_list(self):
3721
self.conf.store._load_from_string('''
3727
self.registry.register(config.ListOption('list'))
3728
# Register an intermediate option as a list to ensure no conversion
3729
# happen while expanding. Conversion should only occur for the original
3730
# option ('list' here).
3731
self.registry.register(config.ListOption('baz'))
3732
self.assertEqual(['start', 'middle', 'end'],
3733
self.conf.get('list', expand=True))
3735
def test_pathologically_hidden_list(self):
3736
self.conf.store._load_from_string('''
3742
hidden={start}{middle}{end}
3744
# What matters is what the registration says, the conversion happens
3745
# only after all expansions have been performed
3746
self.registry.register(config.ListOption('hidden'))
3747
self.assertEqual(['bin', 'go'],
3748
self.conf.get('hidden', expand=True))
3751
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
3754
super(TestStackCrossSectionsExpand, self).setUp()
3756
def get_config(self, location, string):
3759
# Since we don't save the config we won't strictly require to inherit
3760
# from TestCaseInTempDir, but an error occurs so quickly...
3761
c = config.LocationStack(location)
3762
c.store._load_from_string(string)
3765
def test_dont_cross_unrelated_section(self):
3766
c = self.get_config('/another/branch/path','''
3771
[/another/branch/path]
3774
self.assertRaises(errors.ExpandingUnknownOption,
3775
c.get, 'bar', expand=True)
3777
def test_cross_related_sections(self):
3778
c = self.get_config('/project/branch/path','''
3782
[/project/branch/path]
3785
self.assertEqual('quux', c.get('bar', expand=True))
3788
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
3790
def test_cross_global_locations(self):
3791
l_store = config.LocationStore()
3792
l_store._load_from_string('''
3798
g_store = config.GlobalStore()
3799
g_store._load_from_string('''
3805
stack = config.LocationStack('/branch')
3806
self.assertEqual('glob-bar', stack.get('lbar', expand=True))
3807
self.assertEqual('loc-foo', stack.get('gfoo', expand=True))
3810
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
3812
def test_expand_locals_empty(self):
3813
l_store = config.LocationStore()
3814
l_store._load_from_string('''
3815
[/home/user/project]
3820
stack = config.LocationStack('/home/user/project/')
3821
self.assertEqual('', stack.get('base', expand=True))
3822
self.assertEqual('', stack.get('rel', expand=True))
3824
def test_expand_basename_locally(self):
3825
l_store = config.LocationStore()
3826
l_store._load_from_string('''
3827
[/home/user/project]
3831
stack = config.LocationStack('/home/user/project/branch')
3832
self.assertEqual('branch', stack.get('bfoo', expand=True))
3834
def test_expand_basename_locally_longer_path(self):
3835
l_store = config.LocationStore()
3836
l_store._load_from_string('''
3841
stack = config.LocationStack('/home/user/project/dir/branch')
3842
self.assertEqual('branch', stack.get('bfoo', expand=True))
3844
def test_expand_relpath_locally(self):
3845
l_store = config.LocationStore()
3846
l_store._load_from_string('''
3847
[/home/user/project]
3848
lfoo = loc-foo/{relpath}
3851
stack = config.LocationStack('/home/user/project/branch')
3852
self.assertEqual('loc-foo/branch', stack.get('lfoo', expand=True))
3854
def test_expand_relpath_unknonw_in_global(self):
3855
g_store = config.GlobalStore()
3856
g_store._load_from_string('''
3861
stack = config.LocationStack('/home/user/project/branch')
3862
self.assertRaises(errors.ExpandingUnknownOption,
3863
stack.get, 'gfoo', expand=True)
3865
def test_expand_local_option_locally(self):
3866
l_store = config.LocationStore()
3867
l_store._load_from_string('''
3868
[/home/user/project]
3869
lfoo = loc-foo/{relpath}
3873
g_store = config.GlobalStore()
3874
g_store._load_from_string('''
3880
stack = config.LocationStack('/home/user/project/branch')
3881
self.assertEqual('glob-bar', stack.get('lbar', expand=True))
3882
self.assertEqual('loc-foo/branch', stack.get('gfoo', expand=True))
3884
def test_locals_dont_leak(self):
3885
"""Make sure we chose the right local in presence of several sections.
3887
l_store = config.LocationStore()
3888
l_store._load_from_string('''
3890
lfoo = loc-foo/{relpath}
3891
[/home/user/project]
3892
lfoo = loc-foo/{relpath}
3895
stack = config.LocationStack('/home/user/project/branch')
3896
self.assertEqual('loc-foo/branch', stack.get('lfoo', expand=True))
3897
stack = config.LocationStack('/home/user/bar/baz')
3898
self.assertEqual('loc-foo/bar/baz', stack.get('lfoo', expand=True))
3902
class TestStackSet(TestStackWithTransport):
3904
def test_simple_set(self):
3905
conf = self.get_stack(self)
3906
self.assertEqual(None, conf.get('foo'))
3907
conf.set('foo', 'baz')
3908
# Did we get it back ?
3909
self.assertEqual('baz', conf.get('foo'))
3911
def test_set_creates_a_new_section(self):
3912
conf = self.get_stack(self)
3913
conf.set('foo', 'baz')
3914
self.assertEqual, 'baz', conf.get('foo')
3916
def test_set_hook(self):
3920
config.ConfigHooks.install_named_hook('set', hook, None)
3921
self.assertLength(0, calls)
3922
conf = self.get_stack(self)
3923
conf.set('foo', 'bar')
3924
self.assertLength(1, calls)
3925
self.assertEqual((conf, 'foo', 'bar'), calls[0])
3928
class TestStackRemove(TestStackWithTransport):
3930
def test_remove_existing(self):
3931
conf = self.get_stack(self)
3932
conf.set('foo', 'bar')
3933
self.assertEqual('bar', conf.get('foo'))
3935
# Did we get it back ?
3936
self.assertEqual(None, conf.get('foo'))
3938
def test_remove_unknown(self):
3939
conf = self.get_stack(self)
3940
self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
3942
def test_remove_hook(self):
3946
config.ConfigHooks.install_named_hook('remove', hook, None)
3947
self.assertLength(0, calls)
3948
conf = self.get_stack(self)
3949
conf.set('foo', 'bar')
3951
self.assertLength(1, calls)
3952
self.assertEqual((conf, 'foo'), calls[0])
3955
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
3958
super(TestConfigGetOptions, self).setUp()
3959
create_configs(self)
3961
def test_no_variable(self):
3962
# Using branch should query branch, locations and bazaar
3963
self.assertOptions([], self.branch_config)
3965
def test_option_in_bazaar(self):
3966
self.bazaar_config.set_user_option('file', 'bazaar')
3967
self.assertOptions([('file', 'bazaar', 'DEFAULT', 'bazaar')],
3970
def test_option_in_locations(self):
3971
self.locations_config.set_user_option('file', 'locations')
3973
[('file', 'locations', self.tree.basedir, 'locations')],
3974
self.locations_config)
3976
def test_option_in_branch(self):
3977
self.branch_config.set_user_option('file', 'branch')
3978
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch')],
3981
def test_option_in_bazaar_and_branch(self):
3982
self.bazaar_config.set_user_option('file', 'bazaar')
3983
self.branch_config.set_user_option('file', 'branch')
3984
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch'),
3985
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
3988
def test_option_in_branch_and_locations(self):
3989
# Hmm, locations override branch :-/
3990
self.locations_config.set_user_option('file', 'locations')
3991
self.branch_config.set_user_option('file', 'branch')
3993
[('file', 'locations', self.tree.basedir, 'locations'),
3994
('file', 'branch', 'DEFAULT', 'branch'),],
3997
def test_option_in_bazaar_locations_and_branch(self):
3998
self.bazaar_config.set_user_option('file', 'bazaar')
3999
self.locations_config.set_user_option('file', 'locations')
4000
self.branch_config.set_user_option('file', 'branch')
4002
[('file', 'locations', self.tree.basedir, 'locations'),
4003
('file', 'branch', 'DEFAULT', 'branch'),
4004
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4008
class TestConfigRemoveOption(tests.TestCaseWithTransport, TestOptionsMixin):
4011
super(TestConfigRemoveOption, self).setUp()
4012
create_configs_with_file_option(self)
4014
def test_remove_in_locations(self):
4015
self.locations_config.remove_user_option('file', self.tree.basedir)
4017
[('file', 'branch', 'DEFAULT', 'branch'),
4018
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4021
def test_remove_in_branch(self):
4022
self.branch_config.remove_user_option('file')
4024
[('file', 'locations', self.tree.basedir, 'locations'),
4025
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4028
def test_remove_in_bazaar(self):
4029
self.bazaar_config.remove_user_option('file')
4031
[('file', 'locations', self.tree.basedir, 'locations'),
4032
('file', 'branch', 'DEFAULT', 'branch'),],
4036
class TestConfigGetSections(tests.TestCaseWithTransport):
4039
super(TestConfigGetSections, self).setUp()
4040
create_configs(self)
4042
def assertSectionNames(self, expected, conf, name=None):
4043
"""Check which sections are returned for a given config.
4045
If fallback configurations exist their sections can be included.
4047
:param expected: A list of section names.
4049
:param conf: The configuration that will be queried.
4051
:param name: An optional section name that will be passed to
4054
sections = list(conf._get_sections(name))
4055
self.assertLength(len(expected), sections)
4056
self.assertEqual(expected, [n for n, _, _ in sections])
4058
def test_bazaar_default_section(self):
4059
self.assertSectionNames(['DEFAULT'], self.bazaar_config)
4061
def test_locations_default_section(self):
4062
# No sections are defined in an empty file
4063
self.assertSectionNames([], self.locations_config)
4065
def test_locations_named_section(self):
4066
self.locations_config.set_user_option('file', 'locations')
4067
self.assertSectionNames([self.tree.basedir], self.locations_config)
4069
def test_locations_matching_sections(self):
4070
loc_config = self.locations_config
4071
loc_config.set_user_option('file', 'locations')
4072
# We need to cheat a bit here to create an option in sections above and
4073
# below the 'location' one.
4074
parser = loc_config._get_parser()
4075
# locations.cong deals with '/' ignoring native os.sep
4076
location_names = self.tree.basedir.split('/')
4077
parent = '/'.join(location_names[:-1])
4078
child = '/'.join(location_names + ['child'])
4080
parser[parent]['file'] = 'parent'
4082
parser[child]['file'] = 'child'
4083
self.assertSectionNames([self.tree.basedir, parent], loc_config)
4085
def test_branch_data_default_section(self):
4086
self.assertSectionNames([None],
4087
self.branch_config._get_branch_data_config())
4089
def test_branch_default_sections(self):
4090
# No sections are defined in an empty locations file
4091
self.assertSectionNames([None, 'DEFAULT'],
4093
# Unless we define an option
4094
self.branch_config._get_location_config().set_user_option(
4095
'file', 'locations')
4096
self.assertSectionNames([self.tree.basedir, None, 'DEFAULT'],
4099
def test_bazaar_named_section(self):
4100
# We need to cheat as the API doesn't give direct access to sections
4101
# other than DEFAULT.
4102
self.bazaar_config.set_alias('bazaar', 'bzr')
4103
self.assertSectionNames(['ALIASES'], self.bazaar_config, 'ALIASES')
4106
class TestSharedStores(tests.TestCaseInTempDir):
4108
def test_bazaar_conf_shared(self):
4109
g1 = config.GlobalStack()
4110
g2 = config.GlobalStack()
4111
# The two stacks share the same store
4112
self.assertIs(g1.store, g2.store)
1315
4115
class TestAuthenticationConfigFile(tests.TestCase):
1316
4116
"""Test the authentication.conf file matching"""