1312
1699
self.assertIs(None, bzrdir_config.get_default_stack_on())
1702
class TestOldConfigHooks(tests.TestCaseWithTransport):
1705
super(TestOldConfigHooks, self).setUp()
1706
create_configs_with_file_option(self)
1708
def assertGetHook(self, conf, name, value):
1712
config.OldConfigHooks.install_named_hook('get', hook, None)
1714
config.OldConfigHooks.uninstall_named_hook, 'get', None)
1715
self.assertLength(0, calls)
1716
actual_value = conf.get_user_option(name)
1717
self.assertEqual(value, actual_value)
1718
self.assertLength(1, calls)
1719
self.assertEqual((conf, name, value), calls[0])
1721
def test_get_hook_breezy(self):
1722
self.assertGetHook(self.breezy_config, 'file', 'breezy')
1724
def test_get_hook_locations(self):
1725
self.assertGetHook(self.locations_config, 'file', 'locations')
1727
def test_get_hook_branch(self):
1728
# Since locations masks branch, we define a different option
1729
self.branch_config.set_user_option('file2', 'branch')
1730
self.assertGetHook(self.branch_config, 'file2', 'branch')
1732
def assertSetHook(self, conf, name, value):
1736
config.OldConfigHooks.install_named_hook('set', hook, None)
1738
config.OldConfigHooks.uninstall_named_hook, 'set', None)
1739
self.assertLength(0, calls)
1740
conf.set_user_option(name, value)
1741
self.assertLength(1, calls)
1742
# We can't assert the conf object below as different configs use
1743
# different means to implement set_user_option and we care only about
1745
self.assertEqual((name, value), calls[0][1:])
1747
def test_set_hook_breezy(self):
1748
self.assertSetHook(self.breezy_config, 'foo', 'breezy')
1750
def test_set_hook_locations(self):
1751
self.assertSetHook(self.locations_config, 'foo', 'locations')
1753
def test_set_hook_branch(self):
1754
self.assertSetHook(self.branch_config, 'foo', 'branch')
1756
def assertRemoveHook(self, conf, name, section_name=None):
1760
config.OldConfigHooks.install_named_hook('remove', hook, None)
1762
config.OldConfigHooks.uninstall_named_hook, 'remove', None)
1763
self.assertLength(0, calls)
1764
conf.remove_user_option(name, section_name)
1765
self.assertLength(1, calls)
1766
# We can't assert the conf object below as different configs use
1767
# different means to implement remove_user_option and we care only about
1769
self.assertEqual((name,), calls[0][1:])
1771
def test_remove_hook_breezy(self):
1772
self.assertRemoveHook(self.breezy_config, 'file')
1774
def test_remove_hook_locations(self):
1775
self.assertRemoveHook(self.locations_config, 'file',
1776
self.locations_config.location)
1778
def test_remove_hook_branch(self):
1779
self.assertRemoveHook(self.branch_config, 'file')
1781
def assertLoadHook(self, name, conf_class, *conf_args):
1785
config.OldConfigHooks.install_named_hook('load', hook, None)
1787
config.OldConfigHooks.uninstall_named_hook, 'load', None)
1788
self.assertLength(0, calls)
1790
conf = conf_class(*conf_args)
1791
# Access an option to trigger a load
1792
conf.get_user_option(name)
1793
self.assertLength(1, calls)
1794
# Since we can't assert about conf, we just use the number of calls ;-/
1796
def test_load_hook_breezy(self):
1797
self.assertLoadHook('file', config.GlobalConfig)
1799
def test_load_hook_locations(self):
1800
self.assertLoadHook('file', config.LocationConfig, self.tree.basedir)
1802
def test_load_hook_branch(self):
1803
self.assertLoadHook('file', config.BranchConfig, self.tree.branch)
1805
def assertSaveHook(self, conf):
1809
config.OldConfigHooks.install_named_hook('save', hook, None)
1811
config.OldConfigHooks.uninstall_named_hook, 'save', None)
1812
self.assertLength(0, calls)
1813
# Setting an option triggers a save
1814
conf.set_user_option('foo', 'bar')
1815
self.assertLength(1, calls)
1816
# Since we can't assert about conf, we just use the number of calls ;-/
1818
def test_save_hook_breezy(self):
1819
self.assertSaveHook(self.breezy_config)
1821
def test_save_hook_locations(self):
1822
self.assertSaveHook(self.locations_config)
1824
def test_save_hook_branch(self):
1825
self.assertSaveHook(self.branch_config)
1828
class TestOldConfigHooksForRemote(tests.TestCaseWithTransport):
1829
"""Tests config hooks for remote configs.
1831
No tests for the remove hook as this is not implemented there.
1835
super(TestOldConfigHooksForRemote, self).setUp()
1836
self.transport_server = test_server.SmartTCPServer_for_testing
1837
create_configs_with_file_option(self)
1839
def assertGetHook(self, conf, name, value):
1843
config.OldConfigHooks.install_named_hook('get', hook, None)
1845
config.OldConfigHooks.uninstall_named_hook, 'get', None)
1846
self.assertLength(0, calls)
1847
actual_value = conf.get_option(name)
1848
self.assertEqual(value, actual_value)
1849
self.assertLength(1, calls)
1850
self.assertEqual((conf, name, value), calls[0])
1852
def test_get_hook_remote_branch(self):
1853
remote_branch = branch.Branch.open(self.get_url('tree'))
1854
self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
1856
def test_get_hook_remote_bzrdir(self):
1857
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
1858
conf = remote_bzrdir._get_config()
1859
conf.set_option('remotedir', 'file')
1860
self.assertGetHook(conf, 'file', 'remotedir')
1862
def assertSetHook(self, conf, name, value):
1866
config.OldConfigHooks.install_named_hook('set', hook, None)
1868
config.OldConfigHooks.uninstall_named_hook, 'set', None)
1869
self.assertLength(0, calls)
1870
conf.set_option(value, name)
1871
self.assertLength(1, calls)
1872
# We can't assert the conf object below as different configs use
1873
# different means to implement set_user_option and we care only about
1875
self.assertEqual((name, value), calls[0][1:])
1877
def test_set_hook_remote_branch(self):
1878
remote_branch = branch.Branch.open(self.get_url('tree'))
1879
self.addCleanup(remote_branch.lock_write().unlock)
1880
self.assertSetHook(remote_branch._get_config(), 'file', 'remote')
1882
def test_set_hook_remote_bzrdir(self):
1883
remote_branch = branch.Branch.open(self.get_url('tree'))
1884
self.addCleanup(remote_branch.lock_write().unlock)
1885
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
1886
self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
1888
def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
1892
config.OldConfigHooks.install_named_hook('load', hook, None)
1894
config.OldConfigHooks.uninstall_named_hook, 'load', None)
1895
self.assertLength(0, calls)
1897
conf = conf_class(*conf_args)
1898
# Access an option to trigger a load
1899
conf.get_option(name)
1900
self.assertLength(expected_nb_calls, calls)
1901
# Since we can't assert about conf, we just use the number of calls ;-/
1903
def test_load_hook_remote_branch(self):
1904
remote_branch = branch.Branch.open(self.get_url('tree'))
1905
self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
1907
def test_load_hook_remote_bzrdir(self):
1908
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
1909
# The config file doesn't exist, set an option to force its creation
1910
conf = remote_bzrdir._get_config()
1911
conf.set_option('remotedir', 'file')
1912
# We get one call for the server and one call for the client, this is
1913
# caused by the differences in implementations betwen
1914
# SmartServerBzrDirRequestConfigFile (in smart/bzrdir.py) and
1915
# SmartServerBranchGetConfigFile (in smart/branch.py)
1916
self.assertLoadHook(2, 'file', remote.RemoteBzrDirConfig, remote_bzrdir)
1918
def assertSaveHook(self, conf):
1922
config.OldConfigHooks.install_named_hook('save', hook, None)
1924
config.OldConfigHooks.uninstall_named_hook, 'save', None)
1925
self.assertLength(0, calls)
1926
# Setting an option triggers a save
1927
conf.set_option('foo', 'bar')
1928
self.assertLength(1, calls)
1929
# Since we can't assert about conf, we just use the number of calls ;-/
1931
def test_save_hook_remote_branch(self):
1932
remote_branch = branch.Branch.open(self.get_url('tree'))
1933
self.addCleanup(remote_branch.lock_write().unlock)
1934
self.assertSaveHook(remote_branch._get_config())
1936
def test_save_hook_remote_bzrdir(self):
1937
remote_branch = branch.Branch.open(self.get_url('tree'))
1938
self.addCleanup(remote_branch.lock_write().unlock)
1939
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
1940
self.assertSaveHook(remote_bzrdir._get_config())
1943
class TestOptionNames(tests.TestCase):
1945
def is_valid(self, name):
1946
return config._option_ref_re.match('{%s}' % name) is not None
1948
def test_valid_names(self):
1949
self.assertTrue(self.is_valid('foo'))
1950
self.assertTrue(self.is_valid('foo.bar'))
1951
self.assertTrue(self.is_valid('f1'))
1952
self.assertTrue(self.is_valid('_'))
1953
self.assertTrue(self.is_valid('__bar__'))
1954
self.assertTrue(self.is_valid('a_'))
1955
self.assertTrue(self.is_valid('a1'))
1956
# Don't break bzr-svn for no good reason
1957
self.assertTrue(self.is_valid('guessed-layout'))
1959
def test_invalid_names(self):
1960
self.assertFalse(self.is_valid(' foo'))
1961
self.assertFalse(self.is_valid('foo '))
1962
self.assertFalse(self.is_valid('1'))
1963
self.assertFalse(self.is_valid('1,2'))
1964
self.assertFalse(self.is_valid('foo$'))
1965
self.assertFalse(self.is_valid('!foo'))
1966
self.assertFalse(self.is_valid('foo.'))
1967
self.assertFalse(self.is_valid('foo..bar'))
1968
self.assertFalse(self.is_valid('{}'))
1969
self.assertFalse(self.is_valid('{a}'))
1970
self.assertFalse(self.is_valid('a\n'))
1971
self.assertFalse(self.is_valid('-'))
1972
self.assertFalse(self.is_valid('-a'))
1973
self.assertFalse(self.is_valid('a-'))
1974
self.assertFalse(self.is_valid('a--a'))
1976
def assertSingleGroup(self, reference):
1977
# the regexp is used with split and as such should match the reference
1978
# *only*, if more groups needs to be defined, (?:...) should be used.
1979
m = config._option_ref_re.match('{a}')
1980
self.assertLength(1, m.groups())
1982
def test_valid_references(self):
1983
self.assertSingleGroup('{a}')
1984
self.assertSingleGroup('{{a}}')
1987
class TestOption(tests.TestCase):
1989
def test_default_value(self):
1990
opt = config.Option('foo', default='bar')
1991
self.assertEqual('bar', opt.get_default())
1993
def test_callable_default_value(self):
1994
def bar_as_unicode():
1996
opt = config.Option('foo', default=bar_as_unicode)
1997
self.assertEqual('bar', opt.get_default())
1999
def test_default_value_from_env(self):
2000
opt = config.Option('foo', default='bar', default_from_env=['FOO'])
2001
self.overrideEnv('FOO', 'quux')
2002
# Env variable provides a default taking over the option one
2003
self.assertEqual('quux', opt.get_default())
2005
def test_first_default_value_from_env_wins(self):
2006
opt = config.Option('foo', default='bar',
2007
default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
2008
self.overrideEnv('FOO', 'foo')
2009
self.overrideEnv('BAZ', 'baz')
2010
# The first env var set wins
2011
self.assertEqual('foo', opt.get_default())
2013
def test_not_supported_list_default_value(self):
2014
self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
2016
def test_not_supported_object_default_value(self):
2017
self.assertRaises(AssertionError, config.Option, 'foo',
2020
def test_not_supported_callable_default_value_not_unicode(self):
2021
def bar_not_unicode():
2023
opt = config.Option('foo', default=bar_not_unicode)
2024
self.assertRaises(AssertionError, opt.get_default)
2026
def test_get_help_topic(self):
2027
opt = config.Option('foo')
2028
self.assertEqual('foo', opt.get_help_topic())
2031
class TestOptionConverter(tests.TestCase):
2033
def assertConverted(self, expected, opt, value):
2034
self.assertEqual(expected, opt.convert_from_unicode(None, value))
2036
def assertCallsWarning(self, opt, value):
2040
warnings.append(args[0] % args[1:])
2041
self.overrideAttr(trace, 'warning', warning)
2042
self.assertEqual(None, opt.convert_from_unicode(None, value))
2043
self.assertLength(1, warnings)
2045
'Value "%s" is not valid for "%s"' % (value, opt.name),
2048
def assertCallsError(self, opt, value):
2049
self.assertRaises(config.ConfigOptionValueError,
2050
opt.convert_from_unicode, None, value)
2052
def assertConvertInvalid(self, opt, invalid_value):
2054
self.assertEqual(None, opt.convert_from_unicode(None, invalid_value))
2055
opt.invalid = 'warning'
2056
self.assertCallsWarning(opt, invalid_value)
2057
opt.invalid = 'error'
2058
self.assertCallsError(opt, invalid_value)
2061
class TestOptionWithBooleanConverter(TestOptionConverter):
2063
def get_option(self):
2064
return config.Option('foo', help='A boolean.',
2065
from_unicode=config.bool_from_store)
2067
def test_convert_invalid(self):
2068
opt = self.get_option()
2069
# A string that is not recognized as a boolean
2070
self.assertConvertInvalid(opt, u'invalid-boolean')
2071
# A list of strings is never recognized as a boolean
2072
self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
2074
def test_convert_valid(self):
2075
opt = self.get_option()
2076
self.assertConverted(True, opt, u'True')
2077
self.assertConverted(True, opt, u'1')
2078
self.assertConverted(False, opt, u'False')
2081
class TestOptionWithIntegerConverter(TestOptionConverter):
2083
def get_option(self):
2084
return config.Option('foo', help='An integer.',
2085
from_unicode=config.int_from_store)
2087
def test_convert_invalid(self):
2088
opt = self.get_option()
2089
# A string that is not recognized as an integer
2090
self.assertConvertInvalid(opt, u'forty-two')
2091
# A list of strings is never recognized as an integer
2092
self.assertConvertInvalid(opt, [u'a', u'list'])
2094
def test_convert_valid(self):
2095
opt = self.get_option()
2096
self.assertConverted(16, opt, u'16')
2099
class TestOptionWithSIUnitConverter(TestOptionConverter):
2101
def get_option(self):
2102
return config.Option('foo', help='An integer in SI units.',
2103
from_unicode=config.int_SI_from_store)
2105
def test_convert_invalid(self):
2106
opt = self.get_option()
2107
self.assertConvertInvalid(opt, u'not-a-unit')
2108
self.assertConvertInvalid(opt, u'Gb') # Forgot the value
2109
self.assertConvertInvalid(opt, u'1b') # Forgot the unit
2110
self.assertConvertInvalid(opt, u'1GG')
2111
self.assertConvertInvalid(opt, u'1Mbb')
2112
self.assertConvertInvalid(opt, u'1MM')
2114
def test_convert_valid(self):
2115
opt = self.get_option()
2116
self.assertConverted(int(5e3), opt, u'5kb')
2117
self.assertConverted(int(5e6), opt, u'5M')
2118
self.assertConverted(int(5e6), opt, u'5MB')
2119
self.assertConverted(int(5e9), opt, u'5g')
2120
self.assertConverted(int(5e9), opt, u'5gB')
2121
self.assertConverted(100, opt, u'100')
2124
class TestListOption(TestOptionConverter):
2126
def get_option(self):
2127
return config.ListOption('foo', help='A list.')
2129
def test_convert_invalid(self):
2130
opt = self.get_option()
2131
# We don't even try to convert a list into a list, we only expect
2133
self.assertConvertInvalid(opt, [1])
2134
# No string is invalid as all forms can be converted to a list
2136
def test_convert_valid(self):
2137
opt = self.get_option()
2138
# An empty string is an empty list
2139
self.assertConverted([], opt, '') # Using a bare str() just in case
2140
self.assertConverted([], opt, u'')
2142
self.assertConverted([u'True'], opt, u'True')
2144
self.assertConverted([u'42'], opt, u'42')
2146
self.assertConverted([u'bar'], opt, u'bar')
2149
class TestRegistryOption(TestOptionConverter):
2151
def get_option(self, registry):
2152
return config.RegistryOption('foo', registry,
2153
help='A registry option.')
2155
def test_convert_invalid(self):
2156
registry = _mod_registry.Registry()
2157
opt = self.get_option(registry)
2158
self.assertConvertInvalid(opt, [1])
2159
self.assertConvertInvalid(opt, u"notregistered")
2161
def test_convert_valid(self):
2162
registry = _mod_registry.Registry()
2163
registry.register("someval", 1234)
2164
opt = self.get_option(registry)
2165
# Using a bare str() just in case
2166
self.assertConverted(1234, opt, "someval")
2167
self.assertConverted(1234, opt, u'someval')
2168
self.assertConverted(None, opt, None)
2170
def test_help(self):
2171
registry = _mod_registry.Registry()
2172
registry.register("someval", 1234, help="some option")
2173
registry.register("dunno", 1234, help="some other option")
2174
opt = self.get_option(registry)
2176
'A registry option.\n'
2178
'The following values are supported:\n'
2179
' dunno - some other option\n'
2180
' someval - some option\n',
2183
def test_get_help_text(self):
2184
registry = _mod_registry.Registry()
2185
registry.register("someval", 1234, help="some option")
2186
registry.register("dunno", 1234, help="some other option")
2187
opt = self.get_option(registry)
2189
'A registry option.\n'
2191
'The following values are supported:\n'
2192
' dunno - some other option\n'
2193
' someval - some option\n',
2194
opt.get_help_text())
2197
class TestOptionRegistry(tests.TestCase):
2200
super(TestOptionRegistry, self).setUp()
2201
# Always start with an empty registry
2202
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2203
self.registry = config.option_registry
2205
def test_register(self):
2206
opt = config.Option('foo')
2207
self.registry.register(opt)
2208
self.assertIs(opt, self.registry.get('foo'))
2210
def test_registered_help(self):
2211
opt = config.Option('foo', help='A simple option')
2212
self.registry.register(opt)
2213
self.assertEqual('A simple option', self.registry.get_help('foo'))
2215
def test_dont_register_illegal_name(self):
2216
self.assertRaises(config.IllegalOptionName,
2217
self.registry.register, config.Option(' foo'))
2218
self.assertRaises(config.IllegalOptionName,
2219
self.registry.register, config.Option('bar,'))
2221
lazy_option = config.Option('lazy_foo', help='Lazy help')
2223
def test_register_lazy(self):
2224
self.registry.register_lazy('lazy_foo', self.__module__,
2225
'TestOptionRegistry.lazy_option')
2226
self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
2228
def test_registered_lazy_help(self):
2229
self.registry.register_lazy('lazy_foo', self.__module__,
2230
'TestOptionRegistry.lazy_option')
2231
self.assertEqual('Lazy help', self.registry.get_help('lazy_foo'))
2233
def test_dont_lazy_register_illegal_name(self):
2234
# This is where the root cause of http://pad.lv/1235099 is better
2235
# understood: 'register_lazy' doc string mentions that key should match
2236
# the option name which indirectly requires that the option name is a
2237
# valid python identifier. We violate that rule here (using a key that
2238
# doesn't match the option name) to test the option name checking.
2239
self.assertRaises(config.IllegalOptionName,
2240
self.registry.register_lazy, ' foo', self.__module__,
2241
'TestOptionRegistry.lazy_option')
2242
self.assertRaises(config.IllegalOptionName,
2243
self.registry.register_lazy, '1,2', self.__module__,
2244
'TestOptionRegistry.lazy_option')
2247
class TestRegisteredOptions(tests.TestCase):
2248
"""All registered options should verify some constraints."""
2250
scenarios = [(key, {'option_name': key, 'option': option}) for key, option
2251
in config.option_registry.iteritems()]
2254
super(TestRegisteredOptions, self).setUp()
2255
self.registry = config.option_registry
2257
def test_proper_name(self):
2258
# An option should be registered under its own name, this can't be
2259
# checked at registration time for the lazy ones.
2260
self.assertEqual(self.option_name, self.option.name)
2262
def test_help_is_set(self):
2263
option_help = self.registry.get_help(self.option_name)
2264
# Come on, think about the user, he really wants to know what the
2266
self.assertIsNot(None, option_help)
2267
self.assertNotEqual('', option_help)
2270
class TestSection(tests.TestCase):
2272
# FIXME: Parametrize so that all sections produced by Stores run these
2273
# tests -- vila 2011-04-01
2275
def test_get_a_value(self):
2276
a_dict = dict(foo='bar')
2277
section = config.Section('myID', a_dict)
2278
self.assertEqual('bar', section.get('foo'))
2280
def test_get_unknown_option(self):
2282
section = config.Section(None, a_dict)
2283
self.assertEqual('out of thin air',
2284
section.get('foo', 'out of thin air'))
2286
def test_options_is_shared(self):
2288
section = config.Section(None, a_dict)
2289
self.assertIs(a_dict, section.options)
2292
class TestMutableSection(tests.TestCase):
2294
scenarios = [('mutable',
2296
lambda opts: config.MutableSection('myID', opts)},),
2300
a_dict = dict(foo='bar')
2301
section = self.get_section(a_dict)
2302
section.set('foo', 'new_value')
2303
self.assertEqual('new_value', section.get('foo'))
2304
# The change appears in the shared section
2305
self.assertEqual('new_value', a_dict.get('foo'))
2306
# We keep track of the change
2307
self.assertTrue('foo' in section.orig)
2308
self.assertEqual('bar', section.orig.get('foo'))
2310
def test_set_preserve_original_once(self):
2311
a_dict = dict(foo='bar')
2312
section = self.get_section(a_dict)
2313
section.set('foo', 'first_value')
2314
section.set('foo', 'second_value')
2315
# We keep track of the original value
2316
self.assertTrue('foo' in section.orig)
2317
self.assertEqual('bar', section.orig.get('foo'))
2319
def test_remove(self):
2320
a_dict = dict(foo='bar')
2321
section = self.get_section(a_dict)
2322
section.remove('foo')
2323
# We get None for unknown options via the default value
2324
self.assertEqual(None, section.get('foo'))
2325
# Or we just get the default value
2326
self.assertEqual('unknown', section.get('foo', 'unknown'))
2327
self.assertFalse('foo' in section.options)
2328
# We keep track of the deletion
2329
self.assertTrue('foo' in section.orig)
2330
self.assertEqual('bar', section.orig.get('foo'))
2332
def test_remove_new_option(self):
2334
section = self.get_section(a_dict)
2335
section.set('foo', 'bar')
2336
section.remove('foo')
2337
self.assertFalse('foo' in section.options)
2338
# The option didn't exist initially so it we need to keep track of it
2339
# with a special value
2340
self.assertTrue('foo' in section.orig)
2341
self.assertEqual(config._NewlyCreatedOption, section.orig['foo'])
2344
class TestCommandLineStore(tests.TestCase):
2347
super(TestCommandLineStore, self).setUp()
2348
self.store = config.CommandLineStore()
2349
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2351
def get_section(self):
2352
"""Get the unique section for the command line overrides."""
2353
sections = list(self.store.get_sections())
2354
self.assertLength(1, sections)
2355
store, section = sections[0]
2356
self.assertEqual(self.store, store)
2359
def test_no_override(self):
2360
self.store._from_cmdline([])
2361
section = self.get_section()
2362
self.assertLength(0, list(section.iter_option_names()))
2364
def test_simple_override(self):
2365
self.store._from_cmdline(['a=b'])
2366
section = self.get_section()
2367
self.assertEqual('b', section.get('a'))
2369
def test_list_override(self):
2370
opt = config.ListOption('l')
2371
config.option_registry.register(opt)
2372
self.store._from_cmdline(['l=1,2,3'])
2373
val = self.get_section().get('l')
2374
self.assertEqual('1,2,3', val)
2375
# Reminder: lists should be registered as such explicitely, otherwise
2376
# the conversion needs to be done afterwards.
2377
self.assertEqual(['1', '2', '3'],
2378
opt.convert_from_unicode(self.store, val))
2380
def test_multiple_overrides(self):
2381
self.store._from_cmdline(['a=b', 'x=y'])
2382
section = self.get_section()
2383
self.assertEqual('b', section.get('a'))
2384
self.assertEqual('y', section.get('x'))
2386
def test_wrong_syntax(self):
2387
self.assertRaises(errors.BzrCommandError,
2388
self.store._from_cmdline, ['a=b', 'c'])
2390
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
2392
scenarios = [(key, {'get_store': builder}) for key, builder
2393
in config.test_store_builder_registry.iteritems()] + [
2394
('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
2397
store = self.get_store(self)
2398
if isinstance(store, config.TransportIniFileStore):
2399
raise tests.TestNotApplicable(
2400
"%s is not a concrete Store implementation"
2401
" so it doesn't need an id" % (store.__class__.__name__,))
2402
self.assertIsNot(None, store.id)
2405
class TestStore(tests.TestCaseWithTransport):
2407
def assertSectionContent(self, expected, store_and_section):
2408
"""Assert that some options have the proper values in a section."""
2409
_, section = store_and_section
2410
expected_name, expected_options = expected
2411
self.assertEqual(expected_name, section.id)
2414
dict([(k, section.get(k)) for k in expected_options.keys()]))
2417
class TestReadonlyStore(TestStore):
2419
scenarios = [(key, {'get_store': builder}) for key, builder
2420
in config.test_store_builder_registry.iteritems()]
2422
def test_building_delays_load(self):
2423
store = self.get_store(self)
2424
self.assertEqual(False, store.is_loaded())
2425
store._load_from_string('')
2426
self.assertEqual(True, store.is_loaded())
2428
def test_get_no_sections_for_empty(self):
2429
store = self.get_store(self)
2430
store._load_from_string('')
2431
self.assertEqual([], list(store.get_sections()))
2433
def test_get_default_section(self):
2434
store = self.get_store(self)
2435
store._load_from_string('foo=bar')
2436
sections = list(store.get_sections())
2437
self.assertLength(1, sections)
2438
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2440
def test_get_named_section(self):
2441
store = self.get_store(self)
2442
store._load_from_string('[baz]\nfoo=bar')
2443
sections = list(store.get_sections())
2444
self.assertLength(1, sections)
2445
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2447
def test_load_from_string_fails_for_non_empty_store(self):
2448
store = self.get_store(self)
2449
store._load_from_string('foo=bar')
2450
self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
2453
class TestStoreQuoting(TestStore):
2455
scenarios = [(key, {'get_store': builder}) for key, builder
2456
in config.test_store_builder_registry.iteritems()]
2459
super(TestStoreQuoting, self).setUp()
2460
self.store = self.get_store(self)
2461
# We need a loaded store but any content will do
2462
self.store._load_from_string('')
2464
def assertIdempotent(self, s):
2465
"""Assert that quoting an unquoted string is a no-op and vice-versa.
2467
What matters here is that option values, as they appear in a store, can
2468
be safely round-tripped out of the store and back.
2470
:param s: A string, quoted if required.
2472
self.assertEqual(s, self.store.quote(self.store.unquote(s)))
2473
self.assertEqual(s, self.store.unquote(self.store.quote(s)))
2475
def test_empty_string(self):
2476
if isinstance(self.store, config.IniFileStore):
2477
# configobj._quote doesn't handle empty values
2478
self.assertRaises(AssertionError,
2479
self.assertIdempotent, '')
2481
self.assertIdempotent('')
2482
# But quoted empty strings are ok
2483
self.assertIdempotent('""')
2485
def test_embedded_spaces(self):
2486
self.assertIdempotent('" a b c "')
2488
def test_embedded_commas(self):
2489
self.assertIdempotent('" a , b c "')
2491
def test_simple_comma(self):
2492
if isinstance(self.store, config.IniFileStore):
2493
# configobj requires that lists are special-cased
2494
self.assertRaises(AssertionError,
2495
self.assertIdempotent, ',')
2497
self.assertIdempotent(',')
2498
# When a single comma is required, quoting is also required
2499
self.assertIdempotent('","')
2501
def test_list(self):
2502
if isinstance(self.store, config.IniFileStore):
2503
# configobj requires that lists are special-cased
2504
self.assertRaises(AssertionError,
2505
self.assertIdempotent, 'a,b')
2507
self.assertIdempotent('a,b')
2510
class TestDictFromStore(tests.TestCase):
2512
def test_unquote_not_string(self):
2513
conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
2514
value = conf.get('a_section')
2515
# Urgh, despite 'conf' asking for the no-name section, we get the
2516
# content of another section as a dict o_O
2517
self.assertEqual({'a': '1'}, value)
2518
unquoted = conf.store.unquote(value)
2519
# Which cannot be unquoted but shouldn't crash either (the use cases
2520
# are getting the value or displaying it. In the later case, '%s' will
2522
self.assertEqual({'a': '1'}, unquoted)
2523
self.assertEqual("{u'a': u'1'}", '%s' % (unquoted,))
2526
class TestIniFileStoreContent(tests.TestCaseWithTransport):
2527
"""Simulate loading a config store with content of various encodings.
2529
All files produced by bzr are in utf8 content.
2531
Users may modify them manually and end up with a file that can't be
2532
loaded. We need to issue proper error messages in this case.
2535
invalid_utf8_char = '\xff'
2537
def test_load_utf8(self):
2538
"""Ensure we can load an utf8-encoded file."""
2539
t = self.get_transport()
2540
# From http://pad.lv/799212
2541
unicode_user = u'b\N{Euro Sign}ar'
2542
unicode_content = u'user=%s' % (unicode_user,)
2543
utf8_content = unicode_content.encode('utf8')
2544
# Store the raw content in the config file
2545
t.put_bytes('foo.conf', utf8_content)
2546
store = config.TransportIniFileStore(t, 'foo.conf')
2548
stack = config.Stack([store.get_sections], store)
2549
self.assertEqual(unicode_user, stack.get('user'))
2551
def test_load_non_ascii(self):
2552
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2553
t = self.get_transport()
2554
t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
2555
store = config.TransportIniFileStore(t, 'foo.conf')
2556
self.assertRaises(config.ConfigContentError, store.load)
2558
def test_load_erroneous_content(self):
2559
"""Ensure we display a proper error on content that can't be parsed."""
2560
t = self.get_transport()
2561
t.put_bytes('foo.conf', '[open_section\n')
2562
store = config.TransportIniFileStore(t, 'foo.conf')
2563
self.assertRaises(config.ParseConfigError, store.load)
2565
def test_load_permission_denied(self):
2566
"""Ensure we get warned when trying to load an inaccessible file."""
2569
warnings.append(args[0] % args[1:])
2570
self.overrideAttr(trace, 'warning', warning)
2572
t = self.get_transport()
2574
def get_bytes(relpath):
2575
raise errors.PermissionDenied(relpath, "")
2576
t.get_bytes = get_bytes
2577
store = config.TransportIniFileStore(t, 'foo.conf')
2578
self.assertRaises(errors.PermissionDenied, store.load)
2581
[u'Permission denied while trying to load configuration store %s.'
2582
% store.external_url()])
2585
class TestIniConfigContent(tests.TestCaseWithTransport):
2586
"""Simulate loading a IniBasedConfig with content of various encodings.
2588
All files produced by bzr are in utf8 content.
2590
Users may modify them manually and end up with a file that can't be
2591
loaded. We need to issue proper error messages in this case.
2594
invalid_utf8_char = '\xff'
2596
def test_load_utf8(self):
2597
"""Ensure we can load an utf8-encoded file."""
2598
# From http://pad.lv/799212
2599
unicode_user = u'b\N{Euro Sign}ar'
2600
unicode_content = u'user=%s' % (unicode_user,)
2601
utf8_content = unicode_content.encode('utf8')
2602
# Store the raw content in the config file
2603
with open('foo.conf', 'wb') as f:
2604
f.write(utf8_content)
2605
conf = config.IniBasedConfig(file_name='foo.conf')
2606
self.assertEqual(unicode_user, conf.get_user_option('user'))
2608
def test_load_badly_encoded_content(self):
2609
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2610
with open('foo.conf', 'wb') as f:
2611
f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
2612
conf = config.IniBasedConfig(file_name='foo.conf')
2613
self.assertRaises(config.ConfigContentError, conf._get_parser)
2615
def test_load_erroneous_content(self):
2616
"""Ensure we display a proper error on content that can't be parsed."""
2617
with open('foo.conf', 'wb') as f:
2618
f.write('[open_section\n')
2619
conf = config.IniBasedConfig(file_name='foo.conf')
2620
self.assertRaises(config.ParseConfigError, conf._get_parser)
2623
class TestMutableStore(TestStore):
2625
scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
2626
in config.test_store_builder_registry.iteritems()]
2629
super(TestMutableStore, self).setUp()
2630
self.transport = self.get_transport()
2632
def has_store(self, store):
2633
store_basename = urlutils.relative_url(self.transport.external_url(),
2634
store.external_url())
2635
return self.transport.has(store_basename)
2637
def test_save_empty_creates_no_file(self):
2638
# FIXME: There should be a better way than relying on the test
2639
# parametrization to identify branch.conf -- vila 2011-0526
2640
if self.store_id in ('branch', 'remote_branch'):
2641
raise tests.TestNotApplicable(
2642
'branch.conf is *always* created when a branch is initialized')
2643
store = self.get_store(self)
2645
self.assertEqual(False, self.has_store(store))
2647
def test_mutable_section_shared(self):
2648
store = self.get_store(self)
2649
store._load_from_string('foo=bar\n')
2650
# FIXME: There should be a better way than relying on the test
2651
# parametrization to identify branch.conf -- vila 2011-0526
2652
if self.store_id in ('branch', 'remote_branch'):
2653
# branch stores requires write locked branches
2654
self.addCleanup(store.branch.lock_write().unlock)
2655
section1 = store.get_mutable_section(None)
2656
section2 = store.get_mutable_section(None)
2657
# If we get different sections, different callers won't share the
2659
self.assertIs(section1, section2)
2661
def test_save_emptied_succeeds(self):
2662
store = self.get_store(self)
2663
store._load_from_string('foo=bar\n')
2664
# FIXME: There should be a better way than relying on the test
2665
# parametrization to identify branch.conf -- vila 2011-0526
2666
if self.store_id in ('branch', 'remote_branch'):
2667
# branch stores requires write locked branches
2668
self.addCleanup(store.branch.lock_write().unlock)
2669
section = store.get_mutable_section(None)
2670
section.remove('foo')
2672
self.assertEqual(True, self.has_store(store))
2673
modified_store = self.get_store(self)
2674
sections = list(modified_store.get_sections())
2675
self.assertLength(0, sections)
2677
def test_save_with_content_succeeds(self):
2678
# FIXME: There should be a better way than relying on the test
2679
# parametrization to identify branch.conf -- vila 2011-0526
2680
if self.store_id in ('branch', 'remote_branch'):
2681
raise tests.TestNotApplicable(
2682
'branch.conf is *always* created when a branch is initialized')
2683
store = self.get_store(self)
2684
store._load_from_string('foo=bar\n')
2685
self.assertEqual(False, self.has_store(store))
2687
self.assertEqual(True, self.has_store(store))
2688
modified_store = self.get_store(self)
2689
sections = list(modified_store.get_sections())
2690
self.assertLength(1, sections)
2691
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2693
def test_set_option_in_empty_store(self):
2694
store = self.get_store(self)
2695
# FIXME: There should be a better way than relying on the test
2696
# parametrization to identify branch.conf -- vila 2011-0526
2697
if self.store_id in ('branch', 'remote_branch'):
2698
# branch stores requires write locked branches
2699
self.addCleanup(store.branch.lock_write().unlock)
2700
section = store.get_mutable_section(None)
2701
section.set('foo', 'bar')
2703
modified_store = self.get_store(self)
2704
sections = list(modified_store.get_sections())
2705
self.assertLength(1, sections)
2706
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2708
def test_set_option_in_default_section(self):
2709
store = self.get_store(self)
2710
store._load_from_string('')
2711
# FIXME: There should be a better way than relying on the test
2712
# parametrization to identify branch.conf -- vila 2011-0526
2713
if self.store_id in ('branch', 'remote_branch'):
2714
# branch stores requires write locked branches
2715
self.addCleanup(store.branch.lock_write().unlock)
2716
section = store.get_mutable_section(None)
2717
section.set('foo', 'bar')
2719
modified_store = self.get_store(self)
2720
sections = list(modified_store.get_sections())
2721
self.assertLength(1, sections)
2722
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2724
def test_set_option_in_named_section(self):
2725
store = self.get_store(self)
2726
store._load_from_string('')
2727
# FIXME: There should be a better way than relying on the test
2728
# parametrization to identify branch.conf -- vila 2011-0526
2729
if self.store_id in ('branch', 'remote_branch'):
2730
# branch stores requires write locked branches
2731
self.addCleanup(store.branch.lock_write().unlock)
2732
section = store.get_mutable_section('baz')
2733
section.set('foo', 'bar')
2735
modified_store = self.get_store(self)
2736
sections = list(modified_store.get_sections())
2737
self.assertLength(1, sections)
2738
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2740
def test_load_hook(self):
2741
# First, we need to ensure that the store exists
2742
store = self.get_store(self)
2743
# FIXME: There should be a better way than relying on the test
2744
# parametrization to identify branch.conf -- vila 2011-0526
2745
if self.store_id in ('branch', 'remote_branch'):
2746
# branch stores requires write locked branches
2747
self.addCleanup(store.branch.lock_write().unlock)
2748
section = store.get_mutable_section('baz')
2749
section.set('foo', 'bar')
2751
# Now we can try to load it
2752
store = self.get_store(self)
2756
config.ConfigHooks.install_named_hook('load', hook, None)
2757
self.assertLength(0, calls)
2759
self.assertLength(1, calls)
2760
self.assertEqual((store,), calls[0])
2762
def test_save_hook(self):
2766
config.ConfigHooks.install_named_hook('save', hook, None)
2767
self.assertLength(0, calls)
2768
store = self.get_store(self)
2769
# FIXME: There should be a better way than relying on the test
2770
# parametrization to identify branch.conf -- vila 2011-0526
2771
if self.store_id in ('branch', 'remote_branch'):
2772
# branch stores requires write locked branches
2773
self.addCleanup(store.branch.lock_write().unlock)
2774
section = store.get_mutable_section('baz')
2775
section.set('foo', 'bar')
2777
self.assertLength(1, calls)
2778
self.assertEqual((store,), calls[0])
2780
def test_set_mark_dirty(self):
2781
stack = config.MemoryStack('')
2782
self.assertLength(0, stack.store.dirty_sections)
2783
stack.set('foo', 'baz')
2784
self.assertLength(1, stack.store.dirty_sections)
2785
self.assertTrue(stack.store._need_saving())
2787
def test_remove_mark_dirty(self):
2788
stack = config.MemoryStack('foo=bar')
2789
self.assertLength(0, stack.store.dirty_sections)
2791
self.assertLength(1, stack.store.dirty_sections)
2792
self.assertTrue(stack.store._need_saving())
2795
class TestStoreSaveChanges(tests.TestCaseWithTransport):
2796
"""Tests that config changes are kept in memory and saved on-demand."""
2799
super(TestStoreSaveChanges, self).setUp()
2800
self.transport = self.get_transport()
2801
# Most of the tests involve two stores pointing to the same persistent
2802
# storage to observe the effects of concurrent changes
2803
self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
2804
self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
2807
self.warnings.append(args[0] % args[1:])
2808
self.overrideAttr(trace, 'warning', warning)
2810
def has_store(self, store):
2811
store_basename = urlutils.relative_url(self.transport.external_url(),
2812
store.external_url())
2813
return self.transport.has(store_basename)
2815
def get_stack(self, store):
2816
# Any stack will do as long as it uses the right store, just a single
2817
# no-name section is enough
2818
return config.Stack([store.get_sections], store)
2820
def test_no_changes_no_save(self):
2821
s = self.get_stack(self.st1)
2822
s.store.save_changes()
2823
self.assertEqual(False, self.has_store(self.st1))
2825
def test_unrelated_concurrent_update(self):
2826
s1 = self.get_stack(self.st1)
2827
s2 = self.get_stack(self.st2)
2828
s1.set('foo', 'bar')
2829
s2.set('baz', 'quux')
2831
# Changes don't propagate magically
2832
self.assertEqual(None, s1.get('baz'))
2833
s2.store.save_changes()
2834
self.assertEqual('quux', s2.get('baz'))
2835
# Changes are acquired when saving
2836
self.assertEqual('bar', s2.get('foo'))
2837
# Since there is no overlap, no warnings are emitted
2838
self.assertLength(0, self.warnings)
2840
def test_concurrent_update_modified(self):
2841
s1 = self.get_stack(self.st1)
2842
s2 = self.get_stack(self.st2)
2843
s1.set('foo', 'bar')
2844
s2.set('foo', 'baz')
2847
s2.store.save_changes()
2848
self.assertEqual('baz', s2.get('foo'))
2849
# But the user get a warning
2850
self.assertLength(1, self.warnings)
2851
warning = self.warnings[0]
2852
self.assertStartsWith(warning, 'Option foo in section None')
2853
self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
2854
' The baz value will be saved.')
2856
def test_concurrent_deletion(self):
2857
self.st1._load_from_string('foo=bar')
2859
s1 = self.get_stack(self.st1)
2860
s2 = self.get_stack(self.st2)
2863
s1.store.save_changes()
2865
self.assertLength(0, self.warnings)
2866
s2.store.save_changes()
2868
self.assertLength(1, self.warnings)
2869
warning = self.warnings[0]
2870
self.assertStartsWith(warning, 'Option foo in section None')
2871
self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
2872
' The <DELETED> value will be saved.')
2875
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
2877
def get_store(self):
2878
return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
2880
def test_get_quoted_string(self):
2881
store = self.get_store()
2882
store._load_from_string('foo= " abc "')
2883
stack = config.Stack([store.get_sections])
2884
self.assertEqual(' abc ', stack.get('foo'))
2886
def test_set_quoted_string(self):
2887
store = self.get_store()
2888
stack = config.Stack([store.get_sections], store)
2889
stack.set('foo', ' a b c ')
2891
self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
2894
class TestTransportIniFileStore(TestStore):
2896
def test_loading_unknown_file_fails(self):
2897
store = config.TransportIniFileStore(self.get_transport(),
2899
self.assertRaises(errors.NoSuchFile, store.load)
2901
def test_invalid_content(self):
2902
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
2903
self.assertEqual(False, store.is_loaded())
2904
exc = self.assertRaises(
2905
config.ParseConfigError, store._load_from_string,
2906
'this is invalid !')
2907
self.assertEndsWith(exc.filename, 'foo.conf')
2908
# And the load failed
2909
self.assertEqual(False, store.is_loaded())
2911
def test_get_embedded_sections(self):
2912
# A more complicated example (which also shows that section names and
2913
# option names share the same name space...)
2914
# FIXME: This should be fixed by forbidding dicts as values ?
2915
# -- vila 2011-04-05
2916
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
2917
store._load_from_string('''
2921
foo_in_DEFAULT=foo_DEFAULT
2929
sections = list(store.get_sections())
2930
self.assertLength(4, sections)
2931
# The default section has no name.
2932
# List values are provided as strings and need to be explicitly
2933
# converted by specifying from_unicode=list_from_store at option
2935
self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
2937
self.assertSectionContent(
2938
('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
2939
self.assertSectionContent(
2940
('bar', {'foo_in_bar': 'barbar'}), sections[2])
2941
# sub sections are provided as embedded dicts.
2942
self.assertSectionContent(
2943
('baz', {'foo_in_baz': 'barbaz', 'qux': {'foo_in_qux': 'quux'}}),
2947
class TestLockableIniFileStore(TestStore):
2949
def test_create_store_in_created_dir(self):
2950
self.assertPathDoesNotExist('dir')
2951
t = self.get_transport('dir/subdir')
2952
store = config.LockableIniFileStore(t, 'foo.conf')
2953
store.get_mutable_section(None).set('foo', 'bar')
2955
self.assertPathExists('dir/subdir')
2958
class TestConcurrentStoreUpdates(TestStore):
2959
"""Test that Stores properly handle conccurent updates.
2961
New Store implementation may fail some of these tests but until such
2962
implementations exist it's hard to properly filter them from the scenarios
2963
applied here. If you encounter such a case, contact the bzr devs.
2966
scenarios = [(key, {'get_stack': builder}) for key, builder
2967
in config.test_stack_builder_registry.iteritems()]
2970
super(TestConcurrentStoreUpdates, self).setUp()
2971
self.stack = self.get_stack(self)
2972
if not isinstance(self.stack, config._CompatibleStack):
2973
raise tests.TestNotApplicable(
2974
'%s is not meant to be compatible with the old config design'
2976
self.stack.set('one', '1')
2977
self.stack.set('two', '2')
2979
self.stack.store.save()
2981
def test_simple_read_access(self):
2982
self.assertEqual('1', self.stack.get('one'))
2984
def test_simple_write_access(self):
2985
self.stack.set('one', 'one')
2986
self.assertEqual('one', self.stack.get('one'))
2988
def test_listen_to_the_last_speaker(self):
2990
c2 = self.get_stack(self)
2991
c1.set('one', 'ONE')
2992
c2.set('two', 'TWO')
2993
self.assertEqual('ONE', c1.get('one'))
2994
self.assertEqual('TWO', c2.get('two'))
2995
# The second update respect the first one
2996
self.assertEqual('ONE', c2.get('one'))
2998
def test_last_speaker_wins(self):
2999
# If the same config is not shared, the same variable modified twice
3000
# can only see a single result.
3002
c2 = self.get_stack(self)
3005
self.assertEqual('c2', c2.get('one'))
3006
# The first modification is still available until another refresh
3008
self.assertEqual('c1', c1.get('one'))
3009
c1.set('two', 'done')
3010
self.assertEqual('c2', c1.get('one'))
3012
def test_writes_are_serialized(self):
3014
c2 = self.get_stack(self)
3016
# We spawn a thread that will pause *during* the config saving.
3017
before_writing = threading.Event()
3018
after_writing = threading.Event()
3019
writing_done = threading.Event()
3020
c1_save_without_locking_orig = c1.store.save_without_locking
3021
def c1_save_without_locking():
3022
before_writing.set()
3023
c1_save_without_locking_orig()
3024
# The lock is held. We wait for the main thread to decide when to
3026
after_writing.wait()
3027
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(after_writing.set)
3037
before_writing.wait()
3038
self.assertRaises(errors.LockContention,
3039
c2.set, 'one', 'c2')
3040
self.assertEqual('c1', c1.get('one'))
3041
# Let the lock be released
3045
self.assertEqual('c2', c2.get('one'))
3047
def test_read_while_writing(self):
3049
# We spawn a thread that will pause *during* the write
3050
ready_to_write = threading.Event()
3051
do_writing = threading.Event()
3052
writing_done = threading.Event()
3053
# We override the _save implementation so we know the store is locked
3054
c1_save_without_locking_orig = c1.store.save_without_locking
3055
def c1_save_without_locking():
3056
ready_to_write.set()
3057
# The lock is held. We wait for the main thread to decide when to
3060
c1_save_without_locking_orig()
3062
c1.store.save_without_locking = c1_save_without_locking
3065
t1 = threading.Thread(target=c1_set)
3066
# Collect the thread after the test
3067
self.addCleanup(t1.join)
3068
# Be ready to unblock the thread if the test goes wrong
3069
self.addCleanup(do_writing.set)
3071
# Ensure the thread is ready to write
3072
ready_to_write.wait()
3073
self.assertEqual('c1', c1.get('one'))
3074
# If we read during the write, we get the old value
3075
c2 = self.get_stack(self)
3076
self.assertEqual('1', c2.get('one'))
3077
# Let the writing occur and ensure it occurred
3080
# Now we get the updated value
3081
c3 = self.get_stack(self)
3082
self.assertEqual('c1', c3.get('one'))
3084
# FIXME: It may be worth looking into removing the lock dir when it's not
3085
# needed anymore and look at possible fallouts for concurrent lockers. This
3086
# will matter if/when we use config files outside of breezy directories
3087
# (.config/breezy or .bzr) -- vila 20110-04-111
3090
class TestSectionMatcher(TestStore):
3092
scenarios = [('location', {'matcher': config.LocationMatcher}),
3093
('id', {'matcher': config.NameMatcher}),]
3096
super(TestSectionMatcher, self).setUp()
3097
# Any simple store is good enough
3098
self.get_store = config.test_store_builder_registry.get('configobj')
3100
def test_no_matches_for_empty_stores(self):
3101
store = self.get_store(self)
3102
store._load_from_string('')
3103
matcher = self.matcher(store, '/bar')
3104
self.assertEqual([], list(matcher.get_sections()))
3106
def test_build_doesnt_load_store(self):
3107
store = self.get_store(self)
3108
self.matcher(store, '/bar')
3109
self.assertFalse(store.is_loaded())
3112
class TestLocationSection(tests.TestCase):
3114
def get_section(self, options, extra_path):
3115
section = config.Section('foo', options)
3116
return config.LocationSection(section, extra_path)
3118
def test_simple_option(self):
3119
section = self.get_section({'foo': 'bar'}, '')
3120
self.assertEqual('bar', section.get('foo'))
3122
def test_option_with_extra_path(self):
3123
section = self.get_section({'foo': 'bar', 'foo:policy': 'appendpath'},
3125
self.assertEqual('bar/baz', section.get('foo'))
3127
def test_invalid_policy(self):
3128
section = self.get_section({'foo': 'bar', 'foo:policy': 'die'},
3130
# invalid policies are ignored
3131
self.assertEqual('bar', section.get('foo'))
3134
class TestLocationMatcher(TestStore):
3137
super(TestLocationMatcher, self).setUp()
3138
# Any simple store is good enough
3139
self.get_store = config.test_store_builder_registry.get('configobj')
3141
def test_unrelated_section_excluded(self):
3142
store = self.get_store(self)
3143
store._load_from_string('''
3151
section=/foo/bar/baz
3155
self.assertEqual(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
3157
[section.id for _, section in store.get_sections()])
3158
matcher = config.LocationMatcher(store, '/foo/bar/quux')
3159
sections = [section for _, section in matcher.get_sections()]
3160
self.assertEqual(['/foo/bar', '/foo'],
3161
[section.id for section in sections])
3162
self.assertEqual(['quux', 'bar/quux'],
3163
[section.extra_path for section in sections])
3165
def test_more_specific_sections_first(self):
3166
store = self.get_store(self)
3167
store._load_from_string('''
3173
self.assertEqual(['/foo', '/foo/bar'],
3174
[section.id for _, section in store.get_sections()])
3175
matcher = config.LocationMatcher(store, '/foo/bar/baz')
3176
sections = [section for _, section in matcher.get_sections()]
3177
self.assertEqual(['/foo/bar', '/foo'],
3178
[section.id for section in sections])
3179
self.assertEqual(['baz', 'bar/baz'],
3180
[section.extra_path for section in sections])
3182
def test_appendpath_in_no_name_section(self):
3183
# It's a bit weird to allow appendpath in a no-name section, but
3184
# someone may found a use for it
3185
store = self.get_store(self)
3186
store._load_from_string('''
3188
foo:policy = appendpath
3190
matcher = config.LocationMatcher(store, 'dir/subdir')
3191
sections = list(matcher.get_sections())
3192
self.assertLength(1, sections)
3193
self.assertEqual('bar/dir/subdir', sections[0][1].get('foo'))
3195
def test_file_urls_are_normalized(self):
3196
store = self.get_store(self)
3197
if sys.platform == 'win32':
3198
expected_url = 'file:///C:/dir/subdir'
3199
expected_location = 'C:/dir/subdir'
3201
expected_url = 'file:///dir/subdir'
3202
expected_location = '/dir/subdir'
3203
matcher = config.LocationMatcher(store, expected_url)
3204
self.assertEqual(expected_location, matcher.location)
3206
def test_branch_name_colo(self):
3207
store = self.get_store(self)
3208
store._load_from_string(dedent("""\
3210
push_location=my{branchname}
3212
matcher = config.LocationMatcher(store, 'file:///,branch=example%3c')
3213
self.assertEqual('example<', matcher.branch_name)
3214
((_, section),) = matcher.get_sections()
3215
self.assertEqual('example<', section.locals['branchname'])
3217
def test_branch_name_basename(self):
3218
store = self.get_store(self)
3219
store._load_from_string(dedent("""\
3221
push_location=my{branchname}
3223
matcher = config.LocationMatcher(store, 'file:///parent/example%3c')
3224
self.assertEqual('example<', matcher.branch_name)
3225
((_, section),) = matcher.get_sections()
3226
self.assertEqual('example<', section.locals['branchname'])
3229
class TestStartingPathMatcher(TestStore):
3232
super(TestStartingPathMatcher, self).setUp()
3233
# Any simple store is good enough
3234
self.store = config.IniFileStore()
3236
def assertSectionIDs(self, expected, location, content):
3237
self.store._load_from_string(content)
3238
matcher = config.StartingPathMatcher(self.store, location)
3239
sections = list(matcher.get_sections())
3240
self.assertLength(len(expected), sections)
3241
self.assertEqual(expected, [section.id for _, section in sections])
3244
def test_empty(self):
3245
self.assertSectionIDs([], self.get_url(), '')
3247
def test_url_vs_local_paths(self):
3248
# The matcher location is an url and the section names are local paths
3249
self.assertSectionIDs(['/foo/bar', '/foo'],
3250
'file:///foo/bar/baz', '''\
3255
def test_local_path_vs_url(self):
3256
# The matcher location is a local path and the section names are urls
3257
self.assertSectionIDs(['file:///foo/bar', 'file:///foo'],
3258
'/foo/bar/baz', '''\
3264
def test_no_name_section_included_when_present(self):
3265
# Note that other tests will cover the case where the no-name section
3266
# is empty and as such, not included.
3267
sections = self.assertSectionIDs(['/foo/bar', '/foo', None],
3268
'/foo/bar/baz', '''\
3269
option = defined so the no-name section exists
3273
self.assertEqual(['baz', 'bar/baz', '/foo/bar/baz'],
3274
[s.locals['relpath'] for _, s in sections])
3276
def test_order_reversed(self):
3277
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3282
def test_unrelated_section_excluded(self):
3283
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3289
def test_glob_included(self):
3290
sections = self.assertSectionIDs(['/foo/*/baz', '/foo/b*', '/foo'],
3291
'/foo/bar/baz', '''\
3297
# Note that 'baz' as a relpath for /foo/b* is not fully correct, but
3298
# nothing really is... as far using {relpath} to append it to something
3299
# else, this seems good enough though.
3300
self.assertEqual(['', 'baz', 'bar/baz'],
3301
[s.locals['relpath'] for _, s in sections])
3303
def test_respect_order(self):
3304
self.assertSectionIDs(['/foo', '/foo/b*', '/foo/*/baz'],
3305
'/foo/bar/baz', '''\
3313
class TestNameMatcher(TestStore):
3316
super(TestNameMatcher, self).setUp()
3317
self.matcher = config.NameMatcher
3318
# Any simple store is good enough
3319
self.get_store = config.test_store_builder_registry.get('configobj')
3321
def get_matching_sections(self, name):
3322
store = self.get_store(self)
3323
store._load_from_string('''
3331
matcher = self.matcher(store, name)
3332
return list(matcher.get_sections())
3334
def test_matching(self):
3335
sections = self.get_matching_sections('foo')
3336
self.assertLength(1, sections)
3337
self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
3339
def test_not_matching(self):
3340
sections = self.get_matching_sections('baz')
3341
self.assertLength(0, sections)
3344
class TestBaseStackGet(tests.TestCase):
3347
super(TestBaseStackGet, self).setUp()
3348
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3350
def test_get_first_definition(self):
3351
store1 = config.IniFileStore()
3352
store1._load_from_string('foo=bar')
3353
store2 = config.IniFileStore()
3354
store2._load_from_string('foo=baz')
3355
conf = config.Stack([store1.get_sections, store2.get_sections])
3356
self.assertEqual('bar', conf.get('foo'))
3358
def test_get_with_registered_default_value(self):
3359
config.option_registry.register(config.Option('foo', default='bar'))
3360
conf_stack = config.Stack([])
3361
self.assertEqual('bar', conf_stack.get('foo'))
3363
def test_get_without_registered_default_value(self):
3364
config.option_registry.register(config.Option('foo'))
3365
conf_stack = config.Stack([])
3366
self.assertEqual(None, conf_stack.get('foo'))
3368
def test_get_without_default_value_for_not_registered(self):
3369
conf_stack = config.Stack([])
3370
self.assertEqual(None, conf_stack.get('foo'))
3372
def test_get_for_empty_section_callable(self):
3373
conf_stack = config.Stack([lambda : []])
3374
self.assertEqual(None, conf_stack.get('foo'))
3376
def test_get_for_broken_callable(self):
3377
# Trying to use and invalid callable raises an exception on first use
3378
conf_stack = config.Stack([object])
3379
self.assertRaises(TypeError, conf_stack.get, 'foo')
3382
class TestStackWithSimpleStore(tests.TestCase):
3385
super(TestStackWithSimpleStore, self).setUp()
3386
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3387
self.registry = config.option_registry
3389
def get_conf(self, content=None):
3390
return config.MemoryStack(content)
3392
def test_override_value_from_env(self):
3393
self.overrideEnv('FOO', None)
3394
self.registry.register(
3395
config.Option('foo', default='bar', override_from_env=['FOO']))
3396
self.overrideEnv('FOO', 'quux')
3397
# Env variable provides a default taking over the option one
3398
conf = self.get_conf('foo=store')
3399
self.assertEqual('quux', conf.get('foo'))
3401
def test_first_override_value_from_env_wins(self):
3402
self.overrideEnv('NO_VALUE', None)
3403
self.overrideEnv('FOO', None)
3404
self.overrideEnv('BAZ', None)
3405
self.registry.register(
3406
config.Option('foo', default='bar',
3407
override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
3408
self.overrideEnv('FOO', 'foo')
3409
self.overrideEnv('BAZ', 'baz')
3410
# The first env var set wins
3411
conf = self.get_conf('foo=store')
3412
self.assertEqual('foo', conf.get('foo'))
3415
class TestMemoryStack(tests.TestCase):
3418
conf = config.MemoryStack('foo=bar')
3419
self.assertEqual('bar', conf.get('foo'))
3422
conf = config.MemoryStack('foo=bar')
3423
conf.set('foo', 'baz')
3424
self.assertEqual('baz', conf.get('foo'))
3426
def test_no_content(self):
3427
conf = config.MemoryStack()
3428
# No content means no loading
3429
self.assertFalse(conf.store.is_loaded())
3430
self.assertRaises(NotImplementedError, conf.get, 'foo')
3431
# But a content can still be provided
3432
conf.store._load_from_string('foo=bar')
3433
self.assertEqual('bar', conf.get('foo'))
3436
class TestStackIterSections(tests.TestCase):
3438
def test_empty_stack(self):
3439
conf = config.Stack([])
3440
sections = list(conf.iter_sections())
3441
self.assertLength(0, sections)
3443
def test_empty_store(self):
3444
store = config.IniFileStore()
3445
store._load_from_string('')
3446
conf = config.Stack([store.get_sections])
3447
sections = list(conf.iter_sections())
3448
self.assertLength(0, sections)
3450
def test_simple_store(self):
3451
store = config.IniFileStore()
3452
store._load_from_string('foo=bar')
3453
conf = config.Stack([store.get_sections])
3454
tuples = list(conf.iter_sections())
3455
self.assertLength(1, tuples)
3456
(found_store, found_section) = tuples[0]
3457
self.assertIs(store, found_store)
3459
def test_two_stores(self):
3460
store1 = config.IniFileStore()
3461
store1._load_from_string('foo=bar')
3462
store2 = config.IniFileStore()
3463
store2._load_from_string('bar=qux')
3464
conf = config.Stack([store1.get_sections, store2.get_sections])
3465
tuples = list(conf.iter_sections())
3466
self.assertLength(2, tuples)
3467
self.assertIs(store1, tuples[0][0])
3468
self.assertIs(store2, tuples[1][0])
3471
class TestStackWithTransport(tests.TestCaseWithTransport):
3473
scenarios = [(key, {'get_stack': builder}) for key, builder
3474
in config.test_stack_builder_registry.iteritems()]
3477
class TestConcreteStacks(TestStackWithTransport):
3479
def test_build_stack(self):
3480
# Just a smoke test to help debug builders
3481
self.get_stack(self)
3484
class TestStackGet(TestStackWithTransport):
3487
super(TestStackGet, self).setUp()
3488
self.conf = self.get_stack(self)
3490
def test_get_for_empty_stack(self):
3491
self.assertEqual(None, self.conf.get('foo'))
3493
def test_get_hook(self):
3494
self.conf.set('foo', 'bar')
3498
config.ConfigHooks.install_named_hook('get', hook, None)
3499
self.assertLength(0, calls)
3500
value = self.conf.get('foo')
3501
self.assertEqual('bar', value)
3502
self.assertLength(1, calls)
3503
self.assertEqual((self.conf, 'foo', 'bar'), calls[0])
3506
class TestStackGetWithConverter(tests.TestCase):
3509
super(TestStackGetWithConverter, self).setUp()
3510
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3511
self.registry = config.option_registry
3513
def get_conf(self, content=None):
3514
return config.MemoryStack(content)
3516
def register_bool_option(self, name, default=None, default_from_env=None):
3517
b = config.Option(name, help='A boolean.',
3518
default=default, default_from_env=default_from_env,
3519
from_unicode=config.bool_from_store)
3520
self.registry.register(b)
3522
def test_get_default_bool_None(self):
3523
self.register_bool_option('foo')
3524
conf = self.get_conf('')
3525
self.assertEqual(None, conf.get('foo'))
3527
def test_get_default_bool_True(self):
3528
self.register_bool_option('foo', u'True')
3529
conf = self.get_conf('')
3530
self.assertEqual(True, conf.get('foo'))
3532
def test_get_default_bool_False(self):
3533
self.register_bool_option('foo', False)
3534
conf = self.get_conf('')
3535
self.assertEqual(False, conf.get('foo'))
3537
def test_get_default_bool_False_as_string(self):
3538
self.register_bool_option('foo', u'False')
3539
conf = self.get_conf('')
3540
self.assertEqual(False, conf.get('foo'))
3542
def test_get_default_bool_from_env_converted(self):
3543
self.register_bool_option('foo', u'True', default_from_env=['FOO'])
3544
self.overrideEnv('FOO', 'False')
3545
conf = self.get_conf('')
3546
self.assertEqual(False, conf.get('foo'))
3548
def test_get_default_bool_when_conversion_fails(self):
3549
self.register_bool_option('foo', default='True')
3550
conf = self.get_conf('foo=invalid boolean')
3551
self.assertEqual(True, conf.get('foo'))
3553
def register_integer_option(self, name,
3554
default=None, default_from_env=None):
3555
i = config.Option(name, help='An integer.',
3556
default=default, default_from_env=default_from_env,
3557
from_unicode=config.int_from_store)
3558
self.registry.register(i)
3560
def test_get_default_integer_None(self):
3561
self.register_integer_option('foo')
3562
conf = self.get_conf('')
3563
self.assertEqual(None, conf.get('foo'))
3565
def test_get_default_integer(self):
3566
self.register_integer_option('foo', 42)
3567
conf = self.get_conf('')
3568
self.assertEqual(42, conf.get('foo'))
3570
def test_get_default_integer_as_string(self):
3571
self.register_integer_option('foo', u'42')
3572
conf = self.get_conf('')
3573
self.assertEqual(42, conf.get('foo'))
3575
def test_get_default_integer_from_env(self):
3576
self.register_integer_option('foo', default_from_env=['FOO'])
3577
self.overrideEnv('FOO', '18')
3578
conf = self.get_conf('')
3579
self.assertEqual(18, conf.get('foo'))
3581
def test_get_default_integer_when_conversion_fails(self):
3582
self.register_integer_option('foo', default='12')
3583
conf = self.get_conf('foo=invalid integer')
3584
self.assertEqual(12, conf.get('foo'))
3586
def register_list_option(self, name, default=None, default_from_env=None):
3587
l = config.ListOption(name, help='A list.', default=default,
3588
default_from_env=default_from_env)
3589
self.registry.register(l)
3591
def test_get_default_list_None(self):
3592
self.register_list_option('foo')
3593
conf = self.get_conf('')
3594
self.assertEqual(None, conf.get('foo'))
3596
def test_get_default_list_empty(self):
3597
self.register_list_option('foo', '')
3598
conf = self.get_conf('')
3599
self.assertEqual([], conf.get('foo'))
3601
def test_get_default_list_from_env(self):
3602
self.register_list_option('foo', default_from_env=['FOO'])
3603
self.overrideEnv('FOO', '')
3604
conf = self.get_conf('')
3605
self.assertEqual([], conf.get('foo'))
3607
def test_get_with_list_converter_no_item(self):
3608
self.register_list_option('foo', None)
3609
conf = self.get_conf('foo=,')
3610
self.assertEqual([], conf.get('foo'))
3612
def test_get_with_list_converter_many_items(self):
3613
self.register_list_option('foo', None)
3614
conf = self.get_conf('foo=m,o,r,e')
3615
self.assertEqual(['m', 'o', 'r', 'e'], conf.get('foo'))
3617
def test_get_with_list_converter_embedded_spaces_many_items(self):
3618
self.register_list_option('foo', None)
3619
conf = self.get_conf('foo=" bar", "baz "')
3620
self.assertEqual([' bar', 'baz '], conf.get('foo'))
3622
def test_get_with_list_converter_stripped_spaces_many_items(self):
3623
self.register_list_option('foo', None)
3624
conf = self.get_conf('foo= bar , baz ')
3625
self.assertEqual(['bar', 'baz'], conf.get('foo'))
3628
class TestIterOptionRefs(tests.TestCase):
3629
"""iter_option_refs is a bit unusual, document some cases."""
3631
def assertRefs(self, expected, string):
3632
self.assertEqual(expected, list(config.iter_option_refs(string)))
3634
def test_empty(self):
3635
self.assertRefs([(False, '')], '')
3637
def test_no_refs(self):
3638
self.assertRefs([(False, 'foo bar')], 'foo bar')
3640
def test_single_ref(self):
3641
self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
3643
def test_broken_ref(self):
3644
self.assertRefs([(False, '{foo')], '{foo')
3646
def test_embedded_ref(self):
3647
self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
3650
def test_two_refs(self):
3651
self.assertRefs([(False, ''), (True, '{foo}'),
3652
(False, ''), (True, '{bar}'),
3656
def test_newline_in_refs_are_not_matched(self):
3657
self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
3660
class TestStackExpandOptions(tests.TestCaseWithTransport):
3663
super(TestStackExpandOptions, self).setUp()
3664
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3665
self.registry = config.option_registry
3666
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3667
self.conf = config.Stack([store.get_sections], store)
3669
def assertExpansion(self, expected, string, env=None):
3670
self.assertEqual(expected, self.conf.expand_options(string, env))
3672
def test_no_expansion(self):
3673
self.assertExpansion('foo', 'foo')
3675
def test_expand_default_value(self):
3676
self.conf.store._load_from_string('bar=baz')
3677
self.registry.register(config.Option('foo', default=u'{bar}'))
3678
self.assertEqual('baz', self.conf.get('foo', expand=True))
3680
def test_expand_default_from_env(self):
3681
self.conf.store._load_from_string('bar=baz')
3682
self.registry.register(config.Option('foo', default_from_env=['FOO']))
3683
self.overrideEnv('FOO', '{bar}')
3684
self.assertEqual('baz', self.conf.get('foo', expand=True))
3686
def test_expand_default_on_failed_conversion(self):
3687
self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
3688
self.registry.register(
3689
config.Option('foo', default=u'{bar}',
3690
from_unicode=config.int_from_store))
3691
self.assertEqual(42, self.conf.get('foo', expand=True))
3693
def test_env_adding_options(self):
3694
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3696
def test_env_overriding_options(self):
3697
self.conf.store._load_from_string('foo=baz')
3698
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3700
def test_simple_ref(self):
3701
self.conf.store._load_from_string('foo=xxx')
3702
self.assertExpansion('xxx', '{foo}')
3704
def test_unknown_ref(self):
3705
self.assertRaises(config.ExpandingUnknownOption,
3706
self.conf.expand_options, '{foo}')
3708
def test_illegal_def_is_ignored(self):
3709
self.assertExpansion('{1,2}', '{1,2}')
3710
self.assertExpansion('{ }', '{ }')
3711
self.assertExpansion('${Foo,f}', '${Foo,f}')
3713
def test_indirect_ref(self):
3714
self.conf.store._load_from_string('''
3718
self.assertExpansion('xxx', '{bar}')
3720
def test_embedded_ref(self):
3721
self.conf.store._load_from_string('''
3725
self.assertExpansion('xxx', '{{bar}}')
3727
def test_simple_loop(self):
3728
self.conf.store._load_from_string('foo={foo}')
3729
self.assertRaises(config.OptionExpansionLoop,
3730
self.conf.expand_options, '{foo}')
3732
def test_indirect_loop(self):
3733
self.conf.store._load_from_string('''
3737
e = self.assertRaises(config.OptionExpansionLoop,
3738
self.conf.expand_options, '{foo}')
3739
self.assertEqual('foo->bar->baz', e.refs)
3740
self.assertEqual('{foo}', e.string)
3742
def test_list(self):
3743
self.conf.store._load_from_string('''
3747
list={foo},{bar},{baz}
3749
self.registry.register(
3750
config.ListOption('list'))
3751
self.assertEqual(['start', 'middle', 'end'],
3752
self.conf.get('list', expand=True))
3754
def test_cascading_list(self):
3755
self.conf.store._load_from_string('''
3761
self.registry.register(config.ListOption('list'))
3762
# Register an intermediate option as a list to ensure no conversion
3763
# happen while expanding. Conversion should only occur for the original
3764
# option ('list' here).
3765
self.registry.register(config.ListOption('baz'))
3766
self.assertEqual(['start', 'middle', 'end'],
3767
self.conf.get('list', expand=True))
3769
def test_pathologically_hidden_list(self):
3770
self.conf.store._load_from_string('''
3776
hidden={start}{middle}{end}
3778
# What matters is what the registration says, the conversion happens
3779
# only after all expansions have been performed
3780
self.registry.register(config.ListOption('hidden'))
3781
self.assertEqual(['bin', 'go'],
3782
self.conf.get('hidden', expand=True))
3785
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
3788
super(TestStackCrossSectionsExpand, self).setUp()
3790
def get_config(self, location, string):
3793
# Since we don't save the config we won't strictly require to inherit
3794
# from TestCaseInTempDir, but an error occurs so quickly...
3795
c = config.LocationStack(location)
3796
c.store._load_from_string(string)
3799
def test_dont_cross_unrelated_section(self):
3800
c = self.get_config('/another/branch/path', '''
3805
[/another/branch/path]
3808
self.assertRaises(config.ExpandingUnknownOption,
3809
c.get, 'bar', expand=True)
3811
def test_cross_related_sections(self):
3812
c = self.get_config('/project/branch/path', '''
3816
[/project/branch/path]
3819
self.assertEqual('quux', c.get('bar', expand=True))
3822
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
3824
def test_cross_global_locations(self):
3825
l_store = config.LocationStore()
3826
l_store._load_from_string('''
3832
g_store = config.GlobalStore()
3833
g_store._load_from_string('''
3839
stack = config.LocationStack('/branch')
3840
self.assertEqual('glob-bar', stack.get('lbar', expand=True))
3841
self.assertEqual('loc-foo', stack.get('gfoo', expand=True))
3844
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
3846
def test_expand_locals_empty(self):
3847
l_store = config.LocationStore()
3848
l_store._load_from_string('''
3849
[/home/user/project]
3854
stack = config.LocationStack('/home/user/project/')
3855
self.assertEqual('', stack.get('base', expand=True))
3856
self.assertEqual('', stack.get('rel', expand=True))
3858
def test_expand_basename_locally(self):
3859
l_store = config.LocationStore()
3860
l_store._load_from_string('''
3861
[/home/user/project]
3865
stack = config.LocationStack('/home/user/project/branch')
3866
self.assertEqual('branch', stack.get('bfoo', expand=True))
3868
def test_expand_basename_locally_longer_path(self):
3869
l_store = config.LocationStore()
3870
l_store._load_from_string('''
3875
stack = config.LocationStack('/home/user/project/dir/branch')
3876
self.assertEqual('branch', stack.get('bfoo', expand=True))
3878
def test_expand_relpath_locally(self):
3879
l_store = config.LocationStore()
3880
l_store._load_from_string('''
3881
[/home/user/project]
3882
lfoo = loc-foo/{relpath}
3885
stack = config.LocationStack('/home/user/project/branch')
3886
self.assertEqual('loc-foo/branch', stack.get('lfoo', expand=True))
3888
def test_expand_relpath_unknonw_in_global(self):
3889
g_store = config.GlobalStore()
3890
g_store._load_from_string('''
3895
stack = config.LocationStack('/home/user/project/branch')
3896
self.assertRaises(config.ExpandingUnknownOption,
3897
stack.get, 'gfoo', expand=True)
3899
def test_expand_local_option_locally(self):
3900
l_store = config.LocationStore()
3901
l_store._load_from_string('''
3902
[/home/user/project]
3903
lfoo = loc-foo/{relpath}
3907
g_store = config.GlobalStore()
3908
g_store._load_from_string('''
3914
stack = config.LocationStack('/home/user/project/branch')
3915
self.assertEqual('glob-bar', stack.get('lbar', expand=True))
3916
self.assertEqual('loc-foo/branch', stack.get('gfoo', expand=True))
3918
def test_locals_dont_leak(self):
3919
"""Make sure we chose the right local in presence of several sections.
3921
l_store = config.LocationStore()
3922
l_store._load_from_string('''
3924
lfoo = loc-foo/{relpath}
3925
[/home/user/project]
3926
lfoo = loc-foo/{relpath}
3929
stack = config.LocationStack('/home/user/project/branch')
3930
self.assertEqual('loc-foo/branch', stack.get('lfoo', expand=True))
3931
stack = config.LocationStack('/home/user/bar/baz')
3932
self.assertEqual('loc-foo/bar/baz', stack.get('lfoo', expand=True))
3936
class TestStackSet(TestStackWithTransport):
3938
def test_simple_set(self):
3939
conf = self.get_stack(self)
3940
self.assertEqual(None, conf.get('foo'))
3941
conf.set('foo', 'baz')
3942
# Did we get it back ?
3943
self.assertEqual('baz', conf.get('foo'))
3945
def test_set_creates_a_new_section(self):
3946
conf = self.get_stack(self)
3947
conf.set('foo', 'baz')
3948
self.assertEqual, 'baz', conf.get('foo')
3950
def test_set_hook(self):
3954
config.ConfigHooks.install_named_hook('set', hook, None)
3955
self.assertLength(0, calls)
3956
conf = self.get_stack(self)
3957
conf.set('foo', 'bar')
3958
self.assertLength(1, calls)
3959
self.assertEqual((conf, 'foo', 'bar'), calls[0])
3962
class TestStackRemove(TestStackWithTransport):
3964
def test_remove_existing(self):
3965
conf = self.get_stack(self)
3966
conf.set('foo', 'bar')
3967
self.assertEqual('bar', conf.get('foo'))
3969
# Did we get it back ?
3970
self.assertEqual(None, conf.get('foo'))
3972
def test_remove_unknown(self):
3973
conf = self.get_stack(self)
3974
self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
3976
def test_remove_hook(self):
3980
config.ConfigHooks.install_named_hook('remove', hook, None)
3981
self.assertLength(0, calls)
3982
conf = self.get_stack(self)
3983
conf.set('foo', 'bar')
3985
self.assertLength(1, calls)
3986
self.assertEqual((conf, 'foo'), calls[0])
3989
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
3992
super(TestConfigGetOptions, self).setUp()
3993
create_configs(self)
3995
def test_no_variable(self):
3996
# Using branch should query branch, locations and breezy
3997
self.assertOptions([], self.branch_config)
3999
def test_option_in_breezy(self):
4000
self.breezy_config.set_user_option('file', 'breezy')
4001
self.assertOptions([('file', 'breezy', 'DEFAULT', 'breezy')],
4004
def test_option_in_locations(self):
4005
self.locations_config.set_user_option('file', 'locations')
4007
[('file', 'locations', self.tree.basedir, 'locations')],
4008
self.locations_config)
4010
def test_option_in_branch(self):
4011
self.branch_config.set_user_option('file', 'branch')
4012
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch')],
4015
def test_option_in_breezy_and_branch(self):
4016
self.breezy_config.set_user_option('file', 'breezy')
4017
self.branch_config.set_user_option('file', 'branch')
4018
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch'),
4019
('file', 'breezy', 'DEFAULT', 'breezy'),],
4022
def test_option_in_branch_and_locations(self):
4023
# Hmm, locations override branch :-/
4024
self.locations_config.set_user_option('file', 'locations')
4025
self.branch_config.set_user_option('file', 'branch')
4027
[('file', 'locations', self.tree.basedir, 'locations'),
4028
('file', 'branch', 'DEFAULT', 'branch'),],
4031
def test_option_in_breezy_locations_and_branch(self):
4032
self.breezy_config.set_user_option('file', 'breezy')
4033
self.locations_config.set_user_option('file', 'locations')
4034
self.branch_config.set_user_option('file', 'branch')
4036
[('file', 'locations', self.tree.basedir, 'locations'),
4037
('file', 'branch', 'DEFAULT', 'branch'),
4038
('file', 'breezy', 'DEFAULT', 'breezy'),],
4042
class TestConfigRemoveOption(tests.TestCaseWithTransport, TestOptionsMixin):
4045
super(TestConfigRemoveOption, self).setUp()
4046
create_configs_with_file_option(self)
4048
def test_remove_in_locations(self):
4049
self.locations_config.remove_user_option('file', self.tree.basedir)
4051
[('file', 'branch', 'DEFAULT', 'branch'),
4052
('file', 'breezy', 'DEFAULT', 'breezy'),],
4055
def test_remove_in_branch(self):
4056
self.branch_config.remove_user_option('file')
4058
[('file', 'locations', self.tree.basedir, 'locations'),
4059
('file', 'breezy', 'DEFAULT', 'breezy'),],
4062
def test_remove_in_breezy(self):
4063
self.breezy_config.remove_user_option('file')
4065
[('file', 'locations', self.tree.basedir, 'locations'),
4066
('file', 'branch', 'DEFAULT', 'branch'),],
4070
class TestConfigGetSections(tests.TestCaseWithTransport):
4073
super(TestConfigGetSections, self).setUp()
4074
create_configs(self)
4076
def assertSectionNames(self, expected, conf, name=None):
4077
"""Check which sections are returned for a given config.
4079
If fallback configurations exist their sections can be included.
4081
:param expected: A list of section names.
4083
:param conf: The configuration that will be queried.
4085
:param name: An optional section name that will be passed to
4088
sections = list(conf._get_sections(name))
4089
self.assertLength(len(expected), sections)
4090
self.assertEqual(expected, [n for n, _, _ in sections])
4092
def test_breezy_default_section(self):
4093
self.assertSectionNames(['DEFAULT'], self.breezy_config)
4095
def test_locations_default_section(self):
4096
# No sections are defined in an empty file
4097
self.assertSectionNames([], self.locations_config)
4099
def test_locations_named_section(self):
4100
self.locations_config.set_user_option('file', 'locations')
4101
self.assertSectionNames([self.tree.basedir], self.locations_config)
4103
def test_locations_matching_sections(self):
4104
loc_config = self.locations_config
4105
loc_config.set_user_option('file', 'locations')
4106
# We need to cheat a bit here to create an option in sections above and
4107
# below the 'location' one.
4108
parser = loc_config._get_parser()
4109
# locations.cong deals with '/' ignoring native os.sep
4110
location_names = self.tree.basedir.split('/')
4111
parent = '/'.join(location_names[:-1])
4112
child = '/'.join(location_names + ['child'])
4114
parser[parent]['file'] = 'parent'
4116
parser[child]['file'] = 'child'
4117
self.assertSectionNames([self.tree.basedir, parent], loc_config)
4119
def test_branch_data_default_section(self):
4120
self.assertSectionNames([None],
4121
self.branch_config._get_branch_data_config())
4123
def test_branch_default_sections(self):
4124
# No sections are defined in an empty locations file
4125
self.assertSectionNames([None, 'DEFAULT'],
4127
# Unless we define an option
4128
self.branch_config._get_location_config().set_user_option(
4129
'file', 'locations')
4130
self.assertSectionNames([self.tree.basedir, None, 'DEFAULT'],
4133
def test_breezy_named_section(self):
4134
# We need to cheat as the API doesn't give direct access to sections
4135
# other than DEFAULT.
4136
self.breezy_config.set_alias('breezy', 'bzr')
4137
self.assertSectionNames(['ALIASES'], self.breezy_config, 'ALIASES')
4140
class TestSharedStores(tests.TestCaseInTempDir):
4142
def test_breezy_conf_shared(self):
4143
g1 = config.GlobalStack()
4144
g2 = config.GlobalStack()
4145
# The two stacks share the same store
4146
self.assertIs(g1.store, g2.store)
4149
class TestAuthenticationConfigFilePermissions(tests.TestCaseInTempDir):
4150
"""Test warning for permissions of authentication.conf."""
4153
super(TestAuthenticationConfigFilePermissions, self).setUp()
4154
self.path = osutils.pathjoin(self.test_dir, 'authentication.conf')
4155
with open(self.path, 'w') as f:
4156
f.write(b"""[broken]
4159
port=port # Error: Not an int
4161
self.overrideAttr(config, 'authentication_config_filename',
4163
osutils.chmod_if_possible(self.path, 0o755)
4165
def test_check_warning(self):
4166
conf = config.AuthenticationConfig()
4167
self.assertEqual(conf._filename, self.path)
4168
self.assertContainsRe(self.get_log(),
4169
'Saved passwords may be accessible by other users.')
4171
def test_check_suppressed_warning(self):
4172
global_config = config.GlobalConfig()
4173
global_config.set_user_option('suppress_warnings',
4174
'insecure_permissions')
4175
conf = config.AuthenticationConfig()
4176
self.assertEqual(conf._filename, self.path)
4177
self.assertNotContainsRe(self.get_log(),
4178
'Saved passwords may be accessible by other users.')
1315
4181
class TestAuthenticationConfigFile(tests.TestCase):
1316
4182
"""Test the authentication.conf file matching"""