1312
1667
self.assertIs(None, bzrdir_config.get_default_stack_on())
1670
class TestOldConfigHooks(tests.TestCaseWithTransport):
1673
super(TestOldConfigHooks, self).setUp()
1674
create_configs_with_file_option(self)
1676
def assertGetHook(self, conf, name, value):
1680
config.OldConfigHooks.install_named_hook('get', hook, None)
1682
config.OldConfigHooks.uninstall_named_hook, 'get', None)
1683
self.assertLength(0, calls)
1684
actual_value = conf.get_user_option(name)
1685
self.assertEqual(value, actual_value)
1686
self.assertLength(1, calls)
1687
self.assertEqual((conf, name, value), calls[0])
1689
def test_get_hook_bazaar(self):
1690
self.assertGetHook(self.bazaar_config, 'file', 'bazaar')
1692
def test_get_hook_locations(self):
1693
self.assertGetHook(self.locations_config, 'file', 'locations')
1695
def test_get_hook_branch(self):
1696
# Since locations masks branch, we define a different option
1697
self.branch_config.set_user_option('file2', 'branch')
1698
self.assertGetHook(self.branch_config, 'file2', 'branch')
1700
def assertSetHook(self, conf, name, value):
1704
config.OldConfigHooks.install_named_hook('set', hook, None)
1706
config.OldConfigHooks.uninstall_named_hook, 'set', None)
1707
self.assertLength(0, calls)
1708
conf.set_user_option(name, value)
1709
self.assertLength(1, calls)
1710
# We can't assert the conf object below as different configs use
1711
# different means to implement set_user_option and we care only about
1713
self.assertEqual((name, value), calls[0][1:])
1715
def test_set_hook_bazaar(self):
1716
self.assertSetHook(self.bazaar_config, 'foo', 'bazaar')
1718
def test_set_hook_locations(self):
1719
self.assertSetHook(self.locations_config, 'foo', 'locations')
1721
def test_set_hook_branch(self):
1722
self.assertSetHook(self.branch_config, 'foo', 'branch')
1724
def assertRemoveHook(self, conf, name, section_name=None):
1728
config.OldConfigHooks.install_named_hook('remove', hook, None)
1730
config.OldConfigHooks.uninstall_named_hook, 'remove', None)
1731
self.assertLength(0, calls)
1732
conf.remove_user_option(name, section_name)
1733
self.assertLength(1, calls)
1734
# We can't assert the conf object below as different configs use
1735
# different means to implement remove_user_option and we care only about
1737
self.assertEqual((name,), calls[0][1:])
1739
def test_remove_hook_bazaar(self):
1740
self.assertRemoveHook(self.bazaar_config, 'file')
1742
def test_remove_hook_locations(self):
1743
self.assertRemoveHook(self.locations_config, 'file',
1744
self.locations_config.location)
1746
def test_remove_hook_branch(self):
1747
self.assertRemoveHook(self.branch_config, 'file')
1749
def assertLoadHook(self, name, conf_class, *conf_args):
1753
config.OldConfigHooks.install_named_hook('load', hook, None)
1755
config.OldConfigHooks.uninstall_named_hook, 'load', None)
1756
self.assertLength(0, calls)
1758
conf = conf_class(*conf_args)
1759
# Access an option to trigger a load
1760
conf.get_user_option(name)
1761
self.assertLength(1, calls)
1762
# Since we can't assert about conf, we just use the number of calls ;-/
1764
def test_load_hook_bazaar(self):
1765
self.assertLoadHook('file', config.GlobalConfig)
1767
def test_load_hook_locations(self):
1768
self.assertLoadHook('file', config.LocationConfig, self.tree.basedir)
1770
def test_load_hook_branch(self):
1771
self.assertLoadHook('file', config.BranchConfig, self.tree.branch)
1773
def assertSaveHook(self, conf):
1777
config.OldConfigHooks.install_named_hook('save', hook, None)
1779
config.OldConfigHooks.uninstall_named_hook, 'save', None)
1780
self.assertLength(0, calls)
1781
# Setting an option triggers a save
1782
conf.set_user_option('foo', 'bar')
1783
self.assertLength(1, calls)
1784
# Since we can't assert about conf, we just use the number of calls ;-/
1786
def test_save_hook_bazaar(self):
1787
self.assertSaveHook(self.bazaar_config)
1789
def test_save_hook_locations(self):
1790
self.assertSaveHook(self.locations_config)
1792
def test_save_hook_branch(self):
1793
self.assertSaveHook(self.branch_config)
1796
class TestOldConfigHooksForRemote(tests.TestCaseWithTransport):
1797
"""Tests config hooks for remote configs.
1799
No tests for the remove hook as this is not implemented there.
1803
super(TestOldConfigHooksForRemote, self).setUp()
1804
self.transport_server = test_server.SmartTCPServer_for_testing
1805
create_configs_with_file_option(self)
1807
def assertGetHook(self, conf, name, value):
1811
config.OldConfigHooks.install_named_hook('get', hook, None)
1813
config.OldConfigHooks.uninstall_named_hook, 'get', None)
1814
self.assertLength(0, calls)
1815
actual_value = conf.get_option(name)
1816
self.assertEqual(value, actual_value)
1817
self.assertLength(1, calls)
1818
self.assertEqual((conf, name, value), calls[0])
1820
def test_get_hook_remote_branch(self):
1821
remote_branch = branch.Branch.open(self.get_url('tree'))
1822
self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
1824
def test_get_hook_remote_bzrdir(self):
1825
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
1826
conf = remote_bzrdir._get_config()
1827
conf.set_option('remotedir', 'file')
1828
self.assertGetHook(conf, 'file', 'remotedir')
1830
def assertSetHook(self, conf, name, value):
1834
config.OldConfigHooks.install_named_hook('set', hook, None)
1836
config.OldConfigHooks.uninstall_named_hook, 'set', None)
1837
self.assertLength(0, calls)
1838
conf.set_option(value, name)
1839
self.assertLength(1, calls)
1840
# We can't assert the conf object below as different configs use
1841
# different means to implement set_user_option and we care only about
1843
self.assertEqual((name, value), calls[0][1:])
1845
def test_set_hook_remote_branch(self):
1846
remote_branch = branch.Branch.open(self.get_url('tree'))
1847
self.addCleanup(remote_branch.lock_write().unlock)
1848
self.assertSetHook(remote_branch._get_config(), 'file', 'remote')
1850
def test_set_hook_remote_bzrdir(self):
1851
remote_branch = branch.Branch.open(self.get_url('tree'))
1852
self.addCleanup(remote_branch.lock_write().unlock)
1853
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
1854
self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
1856
def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
1860
config.OldConfigHooks.install_named_hook('load', hook, None)
1862
config.OldConfigHooks.uninstall_named_hook, 'load', None)
1863
self.assertLength(0, calls)
1865
conf = conf_class(*conf_args)
1866
# Access an option to trigger a load
1867
conf.get_option(name)
1868
self.assertLength(expected_nb_calls, calls)
1869
# Since we can't assert about conf, we just use the number of calls ;-/
1871
def test_load_hook_remote_branch(self):
1872
remote_branch = branch.Branch.open(self.get_url('tree'))
1873
self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
1875
def test_load_hook_remote_bzrdir(self):
1876
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
1877
# The config file doesn't exist, set an option to force its creation
1878
conf = remote_bzrdir._get_config()
1879
conf.set_option('remotedir', 'file')
1880
# We get one call for the server and one call for the client, this is
1881
# caused by the differences in implementations betwen
1882
# SmartServerBzrDirRequestConfigFile (in smart/bzrdir.py) and
1883
# SmartServerBranchGetConfigFile (in smart/branch.py)
1884
self.assertLoadHook(2 ,'file', remote.RemoteBzrDirConfig, remote_bzrdir)
1886
def assertSaveHook(self, conf):
1890
config.OldConfigHooks.install_named_hook('save', hook, None)
1892
config.OldConfigHooks.uninstall_named_hook, 'save', None)
1893
self.assertLength(0, calls)
1894
# Setting an option triggers a save
1895
conf.set_option('foo', 'bar')
1896
self.assertLength(1, calls)
1897
# Since we can't assert about conf, we just use the number of calls ;-/
1899
def test_save_hook_remote_branch(self):
1900
remote_branch = branch.Branch.open(self.get_url('tree'))
1901
self.addCleanup(remote_branch.lock_write().unlock)
1902
self.assertSaveHook(remote_branch._get_config())
1904
def test_save_hook_remote_bzrdir(self):
1905
remote_branch = branch.Branch.open(self.get_url('tree'))
1906
self.addCleanup(remote_branch.lock_write().unlock)
1907
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
1908
self.assertSaveHook(remote_bzrdir._get_config())
1911
class TestOptionNames(tests.TestCase):
1913
def is_valid(self, name):
1914
return config._option_ref_re.match('{%s}' % name) is not None
1916
def test_valid_names(self):
1917
self.assertTrue(self.is_valid('foo'))
1918
self.assertTrue(self.is_valid('foo.bar'))
1919
self.assertTrue(self.is_valid('f1'))
1920
self.assertTrue(self.is_valid('_'))
1921
self.assertTrue(self.is_valid('__bar__'))
1922
self.assertTrue(self.is_valid('a_'))
1923
self.assertTrue(self.is_valid('a1'))
1924
# Don't break bzr-svn for no good reason
1925
self.assertTrue(self.is_valid('guessed-layout'))
1927
def test_invalid_names(self):
1928
self.assertFalse(self.is_valid(' foo'))
1929
self.assertFalse(self.is_valid('foo '))
1930
self.assertFalse(self.is_valid('1'))
1931
self.assertFalse(self.is_valid('1,2'))
1932
self.assertFalse(self.is_valid('foo$'))
1933
self.assertFalse(self.is_valid('!foo'))
1934
self.assertFalse(self.is_valid('foo.'))
1935
self.assertFalse(self.is_valid('foo..bar'))
1936
self.assertFalse(self.is_valid('{}'))
1937
self.assertFalse(self.is_valid('{a}'))
1938
self.assertFalse(self.is_valid('a\n'))
1939
self.assertFalse(self.is_valid('-'))
1940
self.assertFalse(self.is_valid('-a'))
1941
self.assertFalse(self.is_valid('a-'))
1942
self.assertFalse(self.is_valid('a--a'))
1944
def assertSingleGroup(self, reference):
1945
# the regexp is used with split and as such should match the reference
1946
# *only*, if more groups needs to be defined, (?:...) should be used.
1947
m = config._option_ref_re.match('{a}')
1948
self.assertLength(1, m.groups())
1950
def test_valid_references(self):
1951
self.assertSingleGroup('{a}')
1952
self.assertSingleGroup('{{a}}')
1955
class TestOption(tests.TestCase):
1957
def test_default_value(self):
1958
opt = config.Option('foo', default='bar')
1959
self.assertEqual('bar', opt.get_default())
1961
def test_callable_default_value(self):
1962
def bar_as_unicode():
1964
opt = config.Option('foo', default=bar_as_unicode)
1965
self.assertEqual('bar', opt.get_default())
1967
def test_default_value_from_env(self):
1968
opt = config.Option('foo', default='bar', default_from_env=['FOO'])
1969
self.overrideEnv('FOO', 'quux')
1970
# Env variable provides a default taking over the option one
1971
self.assertEqual('quux', opt.get_default())
1973
def test_first_default_value_from_env_wins(self):
1974
opt = config.Option('foo', default='bar',
1975
default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
1976
self.overrideEnv('FOO', 'foo')
1977
self.overrideEnv('BAZ', 'baz')
1978
# The first env var set wins
1979
self.assertEqual('foo', opt.get_default())
1981
def test_not_supported_list_default_value(self):
1982
self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
1984
def test_not_supported_object_default_value(self):
1985
self.assertRaises(AssertionError, config.Option, 'foo',
1988
def test_not_supported_callable_default_value_not_unicode(self):
1989
def bar_not_unicode():
1991
opt = config.Option('foo', default=bar_not_unicode)
1992
self.assertRaises(AssertionError, opt.get_default)
1994
def test_get_help_topic(self):
1995
opt = config.Option('foo')
1996
self.assertEqual('foo', opt.get_help_topic())
1999
class TestOptionConverter(tests.TestCase):
2001
def assertConverted(self, expected, opt, value):
2002
self.assertEqual(expected, opt.convert_from_unicode(None, value))
2004
def assertCallsWarning(self, opt, value):
2008
warnings.append(args[0] % args[1:])
2009
self.overrideAttr(trace, 'warning', warning)
2010
self.assertEqual(None, opt.convert_from_unicode(None, value))
2011
self.assertLength(1, warnings)
2013
'Value "%s" is not valid for "%s"' % (value, opt.name),
2016
def assertCallsError(self, opt, value):
2017
self.assertRaises(errors.ConfigOptionValueError,
2018
opt.convert_from_unicode, None, value)
2020
def assertConvertInvalid(self, opt, invalid_value):
2022
self.assertEqual(None, opt.convert_from_unicode(None, invalid_value))
2023
opt.invalid = 'warning'
2024
self.assertCallsWarning(opt, invalid_value)
2025
opt.invalid = 'error'
2026
self.assertCallsError(opt, invalid_value)
2029
class TestOptionWithBooleanConverter(TestOptionConverter):
2031
def get_option(self):
2032
return config.Option('foo', help='A boolean.',
2033
from_unicode=config.bool_from_store)
2035
def test_convert_invalid(self):
2036
opt = self.get_option()
2037
# A string that is not recognized as a boolean
2038
self.assertConvertInvalid(opt, u'invalid-boolean')
2039
# A list of strings is never recognized as a boolean
2040
self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
2042
def test_convert_valid(self):
2043
opt = self.get_option()
2044
self.assertConverted(True, opt, u'True')
2045
self.assertConverted(True, opt, u'1')
2046
self.assertConverted(False, opt, u'False')
2049
class TestOptionWithIntegerConverter(TestOptionConverter):
2051
def get_option(self):
2052
return config.Option('foo', help='An integer.',
2053
from_unicode=config.int_from_store)
2055
def test_convert_invalid(self):
2056
opt = self.get_option()
2057
# A string that is not recognized as an integer
2058
self.assertConvertInvalid(opt, u'forty-two')
2059
# A list of strings is never recognized as an integer
2060
self.assertConvertInvalid(opt, [u'a', u'list'])
2062
def test_convert_valid(self):
2063
opt = self.get_option()
2064
self.assertConverted(16, opt, u'16')
2067
class TestOptionWithSIUnitConverter(TestOptionConverter):
2069
def get_option(self):
2070
return config.Option('foo', help='An integer in SI units.',
2071
from_unicode=config.int_SI_from_store)
2073
def test_convert_invalid(self):
2074
opt = self.get_option()
2075
self.assertConvertInvalid(opt, u'not-a-unit')
2076
self.assertConvertInvalid(opt, u'Gb') # Forgot the value
2077
self.assertConvertInvalid(opt, u'1b') # Forgot the unit
2078
self.assertConvertInvalid(opt, u'1GG')
2079
self.assertConvertInvalid(opt, u'1Mbb')
2080
self.assertConvertInvalid(opt, u'1MM')
2082
def test_convert_valid(self):
2083
opt = self.get_option()
2084
self.assertConverted(int(5e3), opt, u'5kb')
2085
self.assertConverted(int(5e6), opt, u'5M')
2086
self.assertConverted(int(5e6), opt, u'5MB')
2087
self.assertConverted(int(5e9), opt, u'5g')
2088
self.assertConverted(int(5e9), opt, u'5gB')
2089
self.assertConverted(100, opt, u'100')
2092
class TestListOption(TestOptionConverter):
2094
def get_option(self):
2095
return config.ListOption('foo', help='A list.')
2097
def test_convert_invalid(self):
2098
opt = self.get_option()
2099
# We don't even try to convert a list into a list, we only expect
2101
self.assertConvertInvalid(opt, [1])
2102
# No string is invalid as all forms can be converted to a list
2104
def test_convert_valid(self):
2105
opt = self.get_option()
2106
# An empty string is an empty list
2107
self.assertConverted([], opt, '') # Using a bare str() just in case
2108
self.assertConverted([], opt, u'')
2110
self.assertConverted([u'True'], opt, u'True')
2112
self.assertConverted([u'42'], opt, u'42')
2114
self.assertConverted([u'bar'], opt, u'bar')
2117
class TestRegistryOption(TestOptionConverter):
2119
def get_option(self, registry):
2120
return config.RegistryOption('foo', registry,
2121
help='A registry option.')
2123
def test_convert_invalid(self):
2124
registry = _mod_registry.Registry()
2125
opt = self.get_option(registry)
2126
self.assertConvertInvalid(opt, [1])
2127
self.assertConvertInvalid(opt, u"notregistered")
2129
def test_convert_valid(self):
2130
registry = _mod_registry.Registry()
2131
registry.register("someval", 1234)
2132
opt = self.get_option(registry)
2133
# Using a bare str() just in case
2134
self.assertConverted(1234, opt, "someval")
2135
self.assertConverted(1234, opt, u'someval')
2136
self.assertConverted(None, opt, None)
2138
def test_help(self):
2139
registry = _mod_registry.Registry()
2140
registry.register("someval", 1234, help="some option")
2141
registry.register("dunno", 1234, help="some other option")
2142
opt = self.get_option(registry)
2144
'A registry option.\n'
2146
'The following values are supported:\n'
2147
' dunno - some other option\n'
2148
' someval - some option\n',
2151
def test_get_help_text(self):
2152
registry = _mod_registry.Registry()
2153
registry.register("someval", 1234, help="some option")
2154
registry.register("dunno", 1234, help="some other option")
2155
opt = self.get_option(registry)
2157
'A registry option.\n'
2159
'The following values are supported:\n'
2160
' dunno - some other option\n'
2161
' someval - some option\n',
2162
opt.get_help_text())
2165
class TestOptionRegistry(tests.TestCase):
2168
super(TestOptionRegistry, self).setUp()
2169
# Always start with an empty registry
2170
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2171
self.registry = config.option_registry
2173
def test_register(self):
2174
opt = config.Option('foo')
2175
self.registry.register(opt)
2176
self.assertIs(opt, self.registry.get('foo'))
2178
def test_registered_help(self):
2179
opt = config.Option('foo', help='A simple option')
2180
self.registry.register(opt)
2181
self.assertEqual('A simple option', self.registry.get_help('foo'))
2183
def test_dont_register_illegal_name(self):
2184
self.assertRaises(errors.IllegalOptionName,
2185
self.registry.register, config.Option(' foo'))
2186
self.assertRaises(errors.IllegalOptionName,
2187
self.registry.register, config.Option('bar,'))
2189
lazy_option = config.Option('lazy_foo', help='Lazy help')
2191
def test_register_lazy(self):
2192
self.registry.register_lazy('lazy_foo', self.__module__,
2193
'TestOptionRegistry.lazy_option')
2194
self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
2196
def test_registered_lazy_help(self):
2197
self.registry.register_lazy('lazy_foo', self.__module__,
2198
'TestOptionRegistry.lazy_option')
2199
self.assertEqual('Lazy help', self.registry.get_help('lazy_foo'))
2201
def test_dont_lazy_register_illegal_name(self):
2202
# This is where the root cause of http://pad.lv/1235099 is better
2203
# understood: 'register_lazy' doc string mentions that key should match
2204
# the option name which indirectly requires that the option name is a
2205
# valid python identifier. We violate that rule here (using a key that
2206
# doesn't match the option name) to test the option name checking.
2207
self.assertRaises(errors.IllegalOptionName,
2208
self.registry.register_lazy, ' foo', self.__module__,
2209
'TestOptionRegistry.lazy_option')
2210
self.assertRaises(errors.IllegalOptionName,
2211
self.registry.register_lazy, '1,2', self.__module__,
2212
'TestOptionRegistry.lazy_option')
2215
class TestRegisteredOptions(tests.TestCase):
2216
"""All registered options should verify some constraints."""
2218
scenarios = [(key, {'option_name': key, 'option': option}) for key, option
2219
in config.option_registry.iteritems()]
2222
super(TestRegisteredOptions, self).setUp()
2223
self.registry = config.option_registry
2225
def test_proper_name(self):
2226
# An option should be registered under its own name, this can't be
2227
# checked at registration time for the lazy ones.
2228
self.assertEqual(self.option_name, self.option.name)
2230
def test_help_is_set(self):
2231
option_help = self.registry.get_help(self.option_name)
2232
# Come on, think about the user, he really wants to know what the
2234
self.assertIsNot(None, option_help)
2235
self.assertNotEqual('', option_help)
2238
class TestSection(tests.TestCase):
2240
# FIXME: Parametrize so that all sections produced by Stores run these
2241
# tests -- vila 2011-04-01
2243
def test_get_a_value(self):
2244
a_dict = dict(foo='bar')
2245
section = config.Section('myID', a_dict)
2246
self.assertEqual('bar', section.get('foo'))
2248
def test_get_unknown_option(self):
2250
section = config.Section(None, a_dict)
2251
self.assertEqual('out of thin air',
2252
section.get('foo', 'out of thin air'))
2254
def test_options_is_shared(self):
2256
section = config.Section(None, a_dict)
2257
self.assertIs(a_dict, section.options)
2260
class TestMutableSection(tests.TestCase):
2262
scenarios = [('mutable',
2264
lambda opts: config.MutableSection('myID', opts)},),
2268
a_dict = dict(foo='bar')
2269
section = self.get_section(a_dict)
2270
section.set('foo', 'new_value')
2271
self.assertEqual('new_value', section.get('foo'))
2272
# The change appears in the shared section
2273
self.assertEqual('new_value', a_dict.get('foo'))
2274
# We keep track of the change
2275
self.assertTrue('foo' in section.orig)
2276
self.assertEqual('bar', section.orig.get('foo'))
2278
def test_set_preserve_original_once(self):
2279
a_dict = dict(foo='bar')
2280
section = self.get_section(a_dict)
2281
section.set('foo', 'first_value')
2282
section.set('foo', 'second_value')
2283
# We keep track of the original value
2284
self.assertTrue('foo' in section.orig)
2285
self.assertEqual('bar', section.orig.get('foo'))
2287
def test_remove(self):
2288
a_dict = dict(foo='bar')
2289
section = self.get_section(a_dict)
2290
section.remove('foo')
2291
# We get None for unknown options via the default value
2292
self.assertEqual(None, section.get('foo'))
2293
# Or we just get the default value
2294
self.assertEqual('unknown', section.get('foo', 'unknown'))
2295
self.assertFalse('foo' in section.options)
2296
# We keep track of the deletion
2297
self.assertTrue('foo' in section.orig)
2298
self.assertEqual('bar', section.orig.get('foo'))
2300
def test_remove_new_option(self):
2302
section = self.get_section(a_dict)
2303
section.set('foo', 'bar')
2304
section.remove('foo')
2305
self.assertFalse('foo' in section.options)
2306
# The option didn't exist initially so it we need to keep track of it
2307
# with a special value
2308
self.assertTrue('foo' in section.orig)
2309
self.assertEqual(config._NewlyCreatedOption, section.orig['foo'])
2312
class TestCommandLineStore(tests.TestCase):
2315
super(TestCommandLineStore, self).setUp()
2316
self.store = config.CommandLineStore()
2317
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2319
def get_section(self):
2320
"""Get the unique section for the command line overrides."""
2321
sections = list(self.store.get_sections())
2322
self.assertLength(1, sections)
2323
store, section = sections[0]
2324
self.assertEqual(self.store, store)
2327
def test_no_override(self):
2328
self.store._from_cmdline([])
2329
section = self.get_section()
2330
self.assertLength(0, list(section.iter_option_names()))
2332
def test_simple_override(self):
2333
self.store._from_cmdline(['a=b'])
2334
section = self.get_section()
2335
self.assertEqual('b', section.get('a'))
2337
def test_list_override(self):
2338
opt = config.ListOption('l')
2339
config.option_registry.register(opt)
2340
self.store._from_cmdline(['l=1,2,3'])
2341
val = self.get_section().get('l')
2342
self.assertEqual('1,2,3', val)
2343
# Reminder: lists should be registered as such explicitely, otherwise
2344
# the conversion needs to be done afterwards.
2345
self.assertEqual(['1', '2', '3'],
2346
opt.convert_from_unicode(self.store, val))
2348
def test_multiple_overrides(self):
2349
self.store._from_cmdline(['a=b', 'x=y'])
2350
section = self.get_section()
2351
self.assertEqual('b', section.get('a'))
2352
self.assertEqual('y', section.get('x'))
2354
def test_wrong_syntax(self):
2355
self.assertRaises(errors.BzrCommandError,
2356
self.store._from_cmdline, ['a=b', 'c'])
2358
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
2360
scenarios = [(key, {'get_store': builder}) for key, builder
2361
in config.test_store_builder_registry.iteritems()] + [
2362
('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
2365
store = self.get_store(self)
2366
if isinstance(store, config.TransportIniFileStore):
2367
raise tests.TestNotApplicable(
2368
"%s is not a concrete Store implementation"
2369
" so it doesn't need an id" % (store.__class__.__name__,))
2370
self.assertIsNot(None, store.id)
2373
class TestStore(tests.TestCaseWithTransport):
2375
def assertSectionContent(self, expected, store_and_section):
2376
"""Assert that some options have the proper values in a section."""
2377
_, section = store_and_section
2378
expected_name, expected_options = expected
2379
self.assertEqual(expected_name, section.id)
2382
dict([(k, section.get(k)) for k in expected_options.keys()]))
2385
class TestReadonlyStore(TestStore):
2387
scenarios = [(key, {'get_store': builder}) for key, builder
2388
in config.test_store_builder_registry.iteritems()]
2390
def test_building_delays_load(self):
2391
store = self.get_store(self)
2392
self.assertEqual(False, store.is_loaded())
2393
store._load_from_string('')
2394
self.assertEqual(True, store.is_loaded())
2396
def test_get_no_sections_for_empty(self):
2397
store = self.get_store(self)
2398
store._load_from_string('')
2399
self.assertEqual([], list(store.get_sections()))
2401
def test_get_default_section(self):
2402
store = self.get_store(self)
2403
store._load_from_string('foo=bar')
2404
sections = list(store.get_sections())
2405
self.assertLength(1, sections)
2406
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2408
def test_get_named_section(self):
2409
store = self.get_store(self)
2410
store._load_from_string('[baz]\nfoo=bar')
2411
sections = list(store.get_sections())
2412
self.assertLength(1, sections)
2413
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2415
def test_load_from_string_fails_for_non_empty_store(self):
2416
store = self.get_store(self)
2417
store._load_from_string('foo=bar')
2418
self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
2421
class TestStoreQuoting(TestStore):
2423
scenarios = [(key, {'get_store': builder}) for key, builder
2424
in config.test_store_builder_registry.iteritems()]
2427
super(TestStoreQuoting, self).setUp()
2428
self.store = self.get_store(self)
2429
# We need a loaded store but any content will do
2430
self.store._load_from_string('')
2432
def assertIdempotent(self, s):
2433
"""Assert that quoting an unquoted string is a no-op and vice-versa.
2435
What matters here is that option values, as they appear in a store, can
2436
be safely round-tripped out of the store and back.
2438
:param s: A string, quoted if required.
2440
self.assertEqual(s, self.store.quote(self.store.unquote(s)))
2441
self.assertEqual(s, self.store.unquote(self.store.quote(s)))
2443
def test_empty_string(self):
2444
if isinstance(self.store, config.IniFileStore):
2445
# configobj._quote doesn't handle empty values
2446
self.assertRaises(AssertionError,
2447
self.assertIdempotent, '')
2449
self.assertIdempotent('')
2450
# But quoted empty strings are ok
2451
self.assertIdempotent('""')
2453
def test_embedded_spaces(self):
2454
self.assertIdempotent('" a b c "')
2456
def test_embedded_commas(self):
2457
self.assertIdempotent('" a , b c "')
2459
def test_simple_comma(self):
2460
if isinstance(self.store, config.IniFileStore):
2461
# configobj requires that lists are special-cased
2462
self.assertRaises(AssertionError,
2463
self.assertIdempotent, ',')
2465
self.assertIdempotent(',')
2466
# When a single comma is required, quoting is also required
2467
self.assertIdempotent('","')
2469
def test_list(self):
2470
if isinstance(self.store, config.IniFileStore):
2471
# configobj requires that lists are special-cased
2472
self.assertRaises(AssertionError,
2473
self.assertIdempotent, 'a,b')
2475
self.assertIdempotent('a,b')
2478
class TestDictFromStore(tests.TestCase):
2480
def test_unquote_not_string(self):
2481
conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
2482
value = conf.get('a_section')
2483
# Urgh, despite 'conf' asking for the no-name section, we get the
2484
# content of another section as a dict o_O
2485
self.assertEqual({'a': '1'}, value)
2486
unquoted = conf.store.unquote(value)
2487
# Which cannot be unquoted but shouldn't crash either (the use cases
2488
# are getting the value or displaying it. In the later case, '%s' will
2490
self.assertEqual({'a': '1'}, unquoted)
2491
self.assertEqual("{u'a': u'1'}", '%s' % (unquoted,))
2494
class TestIniFileStoreContent(tests.TestCaseWithTransport):
2495
"""Simulate loading a config store with content of various encodings.
2497
All files produced by bzr are in utf8 content.
2499
Users may modify them manually and end up with a file that can't be
2500
loaded. We need to issue proper error messages in this case.
2503
invalid_utf8_char = '\xff'
2505
def test_load_utf8(self):
2506
"""Ensure we can load an utf8-encoded file."""
2507
t = self.get_transport()
2508
# From http://pad.lv/799212
2509
unicode_user = u'b\N{Euro Sign}ar'
2510
unicode_content = u'user=%s' % (unicode_user,)
2511
utf8_content = unicode_content.encode('utf8')
2512
# Store the raw content in the config file
2513
t.put_bytes('foo.conf', utf8_content)
2514
store = config.TransportIniFileStore(t, 'foo.conf')
2516
stack = config.Stack([store.get_sections], store)
2517
self.assertEqual(unicode_user, stack.get('user'))
2519
def test_load_non_ascii(self):
2520
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2521
t = self.get_transport()
2522
t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
2523
store = config.TransportIniFileStore(t, 'foo.conf')
2524
self.assertRaises(errors.ConfigContentError, store.load)
2526
def test_load_erroneous_content(self):
2527
"""Ensure we display a proper error on content that can't be parsed."""
2528
t = self.get_transport()
2529
t.put_bytes('foo.conf', '[open_section\n')
2530
store = config.TransportIniFileStore(t, 'foo.conf')
2531
self.assertRaises(errors.ParseConfigError, store.load)
2533
def test_load_permission_denied(self):
2534
"""Ensure we get warned when trying to load an inaccessible file."""
2537
warnings.append(args[0] % args[1:])
2538
self.overrideAttr(trace, 'warning', warning)
2540
t = self.get_transport()
2542
def get_bytes(relpath):
2543
raise errors.PermissionDenied(relpath, "")
2544
t.get_bytes = get_bytes
2545
store = config.TransportIniFileStore(t, 'foo.conf')
2546
self.assertRaises(errors.PermissionDenied, store.load)
2549
[u'Permission denied while trying to load configuration store %s.'
2550
% store.external_url()])
2553
class TestIniConfigContent(tests.TestCaseWithTransport):
2554
"""Simulate loading a IniBasedConfig with content of various encodings.
2556
All files produced by bzr are in utf8 content.
2558
Users may modify them manually and end up with a file that can't be
2559
loaded. We need to issue proper error messages in this case.
2562
invalid_utf8_char = '\xff'
2564
def test_load_utf8(self):
2565
"""Ensure we can load an utf8-encoded file."""
2566
# From http://pad.lv/799212
2567
unicode_user = u'b\N{Euro Sign}ar'
2568
unicode_content = u'user=%s' % (unicode_user,)
2569
utf8_content = unicode_content.encode('utf8')
2570
# Store the raw content in the config file
2571
with open('foo.conf', 'wb') as f:
2572
f.write(utf8_content)
2573
conf = config.IniBasedConfig(file_name='foo.conf')
2574
self.assertEqual(unicode_user, conf.get_user_option('user'))
2576
def test_load_badly_encoded_content(self):
2577
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2578
with open('foo.conf', 'wb') as f:
2579
f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
2580
conf = config.IniBasedConfig(file_name='foo.conf')
2581
self.assertRaises(errors.ConfigContentError, conf._get_parser)
2583
def test_load_erroneous_content(self):
2584
"""Ensure we display a proper error on content that can't be parsed."""
2585
with open('foo.conf', 'wb') as f:
2586
f.write('[open_section\n')
2587
conf = config.IniBasedConfig(file_name='foo.conf')
2588
self.assertRaises(errors.ParseConfigError, conf._get_parser)
2591
class TestMutableStore(TestStore):
2593
scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
2594
in config.test_store_builder_registry.iteritems()]
2597
super(TestMutableStore, self).setUp()
2598
self.transport = self.get_transport()
2600
def has_store(self, store):
2601
store_basename = urlutils.relative_url(self.transport.external_url(),
2602
store.external_url())
2603
return self.transport.has(store_basename)
2605
def test_save_empty_creates_no_file(self):
2606
# FIXME: There should be a better way than relying on the test
2607
# parametrization to identify branch.conf -- vila 2011-0526
2608
if self.store_id in ('branch', 'remote_branch'):
2609
raise tests.TestNotApplicable(
2610
'branch.conf is *always* created when a branch is initialized')
2611
store = self.get_store(self)
2613
self.assertEqual(False, self.has_store(store))
2615
def test_mutable_section_shared(self):
2616
store = self.get_store(self)
2617
store._load_from_string('foo=bar\n')
2618
# FIXME: There should be a better way than relying on the test
2619
# parametrization to identify branch.conf -- vila 2011-0526
2620
if self.store_id in ('branch', 'remote_branch'):
2621
# branch stores requires write locked branches
2622
self.addCleanup(store.branch.lock_write().unlock)
2623
section1 = store.get_mutable_section(None)
2624
section2 = store.get_mutable_section(None)
2625
# If we get different sections, different callers won't share the
2627
self.assertIs(section1, section2)
2629
def test_save_emptied_succeeds(self):
2630
store = self.get_store(self)
2631
store._load_from_string('foo=bar\n')
2632
# FIXME: There should be a better way than relying on the test
2633
# parametrization to identify branch.conf -- vila 2011-0526
2634
if self.store_id in ('branch', 'remote_branch'):
2635
# branch stores requires write locked branches
2636
self.addCleanup(store.branch.lock_write().unlock)
2637
section = store.get_mutable_section(None)
2638
section.remove('foo')
2640
self.assertEqual(True, self.has_store(store))
2641
modified_store = self.get_store(self)
2642
sections = list(modified_store.get_sections())
2643
self.assertLength(0, sections)
2645
def test_save_with_content_succeeds(self):
2646
# FIXME: There should be a better way than relying on the test
2647
# parametrization to identify branch.conf -- vila 2011-0526
2648
if self.store_id in ('branch', 'remote_branch'):
2649
raise tests.TestNotApplicable(
2650
'branch.conf is *always* created when a branch is initialized')
2651
store = self.get_store(self)
2652
store._load_from_string('foo=bar\n')
2653
self.assertEqual(False, self.has_store(store))
2655
self.assertEqual(True, self.has_store(store))
2656
modified_store = self.get_store(self)
2657
sections = list(modified_store.get_sections())
2658
self.assertLength(1, sections)
2659
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2661
def test_set_option_in_empty_store(self):
2662
store = self.get_store(self)
2663
# FIXME: There should be a better way than relying on the test
2664
# parametrization to identify branch.conf -- vila 2011-0526
2665
if self.store_id in ('branch', 'remote_branch'):
2666
# branch stores requires write locked branches
2667
self.addCleanup(store.branch.lock_write().unlock)
2668
section = store.get_mutable_section(None)
2669
section.set('foo', 'bar')
2671
modified_store = self.get_store(self)
2672
sections = list(modified_store.get_sections())
2673
self.assertLength(1, sections)
2674
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2676
def test_set_option_in_default_section(self):
2677
store = self.get_store(self)
2678
store._load_from_string('')
2679
# FIXME: There should be a better way than relying on the test
2680
# parametrization to identify branch.conf -- vila 2011-0526
2681
if self.store_id in ('branch', 'remote_branch'):
2682
# branch stores requires write locked branches
2683
self.addCleanup(store.branch.lock_write().unlock)
2684
section = store.get_mutable_section(None)
2685
section.set('foo', 'bar')
2687
modified_store = self.get_store(self)
2688
sections = list(modified_store.get_sections())
2689
self.assertLength(1, sections)
2690
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2692
def test_set_option_in_named_section(self):
2693
store = self.get_store(self)
2694
store._load_from_string('')
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('baz')
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(('baz', {'foo': 'bar'}), sections[0])
2708
def test_load_hook(self):
2709
# First, we need to ensure that the store exists
2710
store = self.get_store(self)
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('baz')
2717
section.set('foo', 'bar')
2719
# Now we can try to load it
2720
store = self.get_store(self)
2724
config.ConfigHooks.install_named_hook('load', hook, None)
2725
self.assertLength(0, calls)
2727
self.assertLength(1, calls)
2728
self.assertEqual((store,), calls[0])
2730
def test_save_hook(self):
2734
config.ConfigHooks.install_named_hook('save', hook, None)
2735
self.assertLength(0, calls)
2736
store = self.get_store(self)
2737
# FIXME: There should be a better way than relying on the test
2738
# parametrization to identify branch.conf -- vila 2011-0526
2739
if self.store_id in ('branch', 'remote_branch'):
2740
# branch stores requires write locked branches
2741
self.addCleanup(store.branch.lock_write().unlock)
2742
section = store.get_mutable_section('baz')
2743
section.set('foo', 'bar')
2745
self.assertLength(1, calls)
2746
self.assertEqual((store,), calls[0])
2748
def test_set_mark_dirty(self):
2749
stack = config.MemoryStack('')
2750
self.assertLength(0, stack.store.dirty_sections)
2751
stack.set('foo', 'baz')
2752
self.assertLength(1, stack.store.dirty_sections)
2753
self.assertTrue(stack.store._need_saving())
2755
def test_remove_mark_dirty(self):
2756
stack = config.MemoryStack('foo=bar')
2757
self.assertLength(0, stack.store.dirty_sections)
2759
self.assertLength(1, stack.store.dirty_sections)
2760
self.assertTrue(stack.store._need_saving())
2763
class TestStoreSaveChanges(tests.TestCaseWithTransport):
2764
"""Tests that config changes are kept in memory and saved on-demand."""
2767
super(TestStoreSaveChanges, self).setUp()
2768
self.transport = self.get_transport()
2769
# Most of the tests involve two stores pointing to the same persistent
2770
# storage to observe the effects of concurrent changes
2771
self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
2772
self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
2775
self.warnings.append(args[0] % args[1:])
2776
self.overrideAttr(trace, 'warning', warning)
2778
def has_store(self, store):
2779
store_basename = urlutils.relative_url(self.transport.external_url(),
2780
store.external_url())
2781
return self.transport.has(store_basename)
2783
def get_stack(self, store):
2784
# Any stack will do as long as it uses the right store, just a single
2785
# no-name section is enough
2786
return config.Stack([store.get_sections], store)
2788
def test_no_changes_no_save(self):
2789
s = self.get_stack(self.st1)
2790
s.store.save_changes()
2791
self.assertEqual(False, self.has_store(self.st1))
2793
def test_unrelated_concurrent_update(self):
2794
s1 = self.get_stack(self.st1)
2795
s2 = self.get_stack(self.st2)
2796
s1.set('foo', 'bar')
2797
s2.set('baz', 'quux')
2799
# Changes don't propagate magically
2800
self.assertEqual(None, s1.get('baz'))
2801
s2.store.save_changes()
2802
self.assertEqual('quux', s2.get('baz'))
2803
# Changes are acquired when saving
2804
self.assertEqual('bar', s2.get('foo'))
2805
# Since there is no overlap, no warnings are emitted
2806
self.assertLength(0, self.warnings)
2808
def test_concurrent_update_modified(self):
2809
s1 = self.get_stack(self.st1)
2810
s2 = self.get_stack(self.st2)
2811
s1.set('foo', 'bar')
2812
s2.set('foo', 'baz')
2815
s2.store.save_changes()
2816
self.assertEqual('baz', s2.get('foo'))
2817
# But the user get a warning
2818
self.assertLength(1, self.warnings)
2819
warning = self.warnings[0]
2820
self.assertStartsWith(warning, 'Option foo in section None')
2821
self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
2822
' The baz value will be saved.')
2824
def test_concurrent_deletion(self):
2825
self.st1._load_from_string('foo=bar')
2827
s1 = self.get_stack(self.st1)
2828
s2 = self.get_stack(self.st2)
2831
s1.store.save_changes()
2833
self.assertLength(0, self.warnings)
2834
s2.store.save_changes()
2836
self.assertLength(1, self.warnings)
2837
warning = self.warnings[0]
2838
self.assertStartsWith(warning, 'Option foo in section None')
2839
self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
2840
' The <DELETED> value will be saved.')
2843
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
2845
def get_store(self):
2846
return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
2848
def test_get_quoted_string(self):
2849
store = self.get_store()
2850
store._load_from_string('foo= " abc "')
2851
stack = config.Stack([store.get_sections])
2852
self.assertEqual(' abc ', stack.get('foo'))
2854
def test_set_quoted_string(self):
2855
store = self.get_store()
2856
stack = config.Stack([store.get_sections], store)
2857
stack.set('foo', ' a b c ')
2859
self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
2862
class TestTransportIniFileStore(TestStore):
2864
def test_loading_unknown_file_fails(self):
2865
store = config.TransportIniFileStore(self.get_transport(),
2867
self.assertRaises(errors.NoSuchFile, store.load)
2869
def test_invalid_content(self):
2870
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
2871
self.assertEqual(False, store.is_loaded())
2872
exc = self.assertRaises(
2873
errors.ParseConfigError, store._load_from_string,
2874
'this is invalid !')
2875
self.assertEndsWith(exc.filename, 'foo.conf')
2876
# And the load failed
2877
self.assertEqual(False, store.is_loaded())
2879
def test_get_embedded_sections(self):
2880
# A more complicated example (which also shows that section names and
2881
# option names share the same name space...)
2882
# FIXME: This should be fixed by forbidding dicts as values ?
2883
# -- vila 2011-04-05
2884
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
2885
store._load_from_string('''
2889
foo_in_DEFAULT=foo_DEFAULT
2897
sections = list(store.get_sections())
2898
self.assertLength(4, sections)
2899
# The default section has no name.
2900
# List values are provided as strings and need to be explicitly
2901
# converted by specifying from_unicode=list_from_store at option
2903
self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
2905
self.assertSectionContent(
2906
('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
2907
self.assertSectionContent(
2908
('bar', {'foo_in_bar': 'barbar'}), sections[2])
2909
# sub sections are provided as embedded dicts.
2910
self.assertSectionContent(
2911
('baz', {'foo_in_baz': 'barbaz', 'qux': {'foo_in_qux': 'quux'}}),
2915
class TestLockableIniFileStore(TestStore):
2917
def test_create_store_in_created_dir(self):
2918
self.assertPathDoesNotExist('dir')
2919
t = self.get_transport('dir/subdir')
2920
store = config.LockableIniFileStore(t, 'foo.conf')
2921
store.get_mutable_section(None).set('foo', 'bar')
2923
self.assertPathExists('dir/subdir')
2926
class TestConcurrentStoreUpdates(TestStore):
2927
"""Test that Stores properly handle conccurent updates.
2929
New Store implementation may fail some of these tests but until such
2930
implementations exist it's hard to properly filter them from the scenarios
2931
applied here. If you encounter such a case, contact the bzr devs.
2934
scenarios = [(key, {'get_stack': builder}) for key, builder
2935
in config.test_stack_builder_registry.iteritems()]
2938
super(TestConcurrentStoreUpdates, self).setUp()
2939
self.stack = self.get_stack(self)
2940
if not isinstance(self.stack, config._CompatibleStack):
2941
raise tests.TestNotApplicable(
2942
'%s is not meant to be compatible with the old config design'
2944
self.stack.set('one', '1')
2945
self.stack.set('two', '2')
2947
self.stack.store.save()
2949
def test_simple_read_access(self):
2950
self.assertEqual('1', self.stack.get('one'))
2952
def test_simple_write_access(self):
2953
self.stack.set('one', 'one')
2954
self.assertEqual('one', self.stack.get('one'))
2956
def test_listen_to_the_last_speaker(self):
2958
c2 = self.get_stack(self)
2959
c1.set('one', 'ONE')
2960
c2.set('two', 'TWO')
2961
self.assertEqual('ONE', c1.get('one'))
2962
self.assertEqual('TWO', c2.get('two'))
2963
# The second update respect the first one
2964
self.assertEqual('ONE', c2.get('one'))
2966
def test_last_speaker_wins(self):
2967
# If the same config is not shared, the same variable modified twice
2968
# can only see a single result.
2970
c2 = self.get_stack(self)
2973
self.assertEqual('c2', c2.get('one'))
2974
# The first modification is still available until another refresh
2976
self.assertEqual('c1', c1.get('one'))
2977
c1.set('two', 'done')
2978
self.assertEqual('c2', c1.get('one'))
2980
def test_writes_are_serialized(self):
2982
c2 = self.get_stack(self)
2984
# We spawn a thread that will pause *during* the config saving.
2985
before_writing = threading.Event()
2986
after_writing = threading.Event()
2987
writing_done = threading.Event()
2988
c1_save_without_locking_orig = c1.store.save_without_locking
2989
def c1_save_without_locking():
2990
before_writing.set()
2991
c1_save_without_locking_orig()
2992
# The lock is held. We wait for the main thread to decide when to
2994
after_writing.wait()
2995
c1.store.save_without_locking = c1_save_without_locking
2999
t1 = threading.Thread(target=c1_set)
3000
# Collect the thread after the test
3001
self.addCleanup(t1.join)
3002
# Be ready to unblock the thread if the test goes wrong
3003
self.addCleanup(after_writing.set)
3005
before_writing.wait()
3006
self.assertRaises(errors.LockContention,
3007
c2.set, 'one', 'c2')
3008
self.assertEqual('c1', c1.get('one'))
3009
# Let the lock be released
3013
self.assertEqual('c2', c2.get('one'))
3015
def test_read_while_writing(self):
3017
# We spawn a thread that will pause *during* the write
3018
ready_to_write = threading.Event()
3019
do_writing = threading.Event()
3020
writing_done = threading.Event()
3021
# We override the _save implementation so we know the store is locked
3022
c1_save_without_locking_orig = c1.store.save_without_locking
3023
def c1_save_without_locking():
3024
ready_to_write.set()
3025
# The lock is held. We wait for the main thread to decide when to
3028
c1_save_without_locking_orig()
3030
c1.store.save_without_locking = c1_save_without_locking
3033
t1 = threading.Thread(target=c1_set)
3034
# Collect the thread after the test
3035
self.addCleanup(t1.join)
3036
# Be ready to unblock the thread if the test goes wrong
3037
self.addCleanup(do_writing.set)
3039
# Ensure the thread is ready to write
3040
ready_to_write.wait()
3041
self.assertEqual('c1', c1.get('one'))
3042
# If we read during the write, we get the old value
3043
c2 = self.get_stack(self)
3044
self.assertEqual('1', c2.get('one'))
3045
# Let the writing occur and ensure it occurred
3048
# Now we get the updated value
3049
c3 = self.get_stack(self)
3050
self.assertEqual('c1', c3.get('one'))
3052
# FIXME: It may be worth looking into removing the lock dir when it's not
3053
# needed anymore and look at possible fallouts for concurrent lockers. This
3054
# will matter if/when we use config files outside of bazaar directories
3055
# (.bazaar or .bzr) -- vila 20110-04-111
3058
class TestSectionMatcher(TestStore):
3060
scenarios = [('location', {'matcher': config.LocationMatcher}),
3061
('id', {'matcher': config.NameMatcher}),]
3064
super(TestSectionMatcher, self).setUp()
3065
# Any simple store is good enough
3066
self.get_store = config.test_store_builder_registry.get('configobj')
3068
def test_no_matches_for_empty_stores(self):
3069
store = self.get_store(self)
3070
store._load_from_string('')
3071
matcher = self.matcher(store, '/bar')
3072
self.assertEqual([], list(matcher.get_sections()))
3074
def test_build_doesnt_load_store(self):
3075
store = self.get_store(self)
3076
self.matcher(store, '/bar')
3077
self.assertFalse(store.is_loaded())
3080
class TestLocationSection(tests.TestCase):
3082
def get_section(self, options, extra_path):
3083
section = config.Section('foo', options)
3084
return config.LocationSection(section, extra_path)
3086
def test_simple_option(self):
3087
section = self.get_section({'foo': 'bar'}, '')
3088
self.assertEqual('bar', section.get('foo'))
3090
def test_option_with_extra_path(self):
3091
section = self.get_section({'foo': 'bar', 'foo:policy': 'appendpath'},
3093
self.assertEqual('bar/baz', section.get('foo'))
3095
def test_invalid_policy(self):
3096
section = self.get_section({'foo': 'bar', 'foo:policy': 'die'},
3098
# invalid policies are ignored
3099
self.assertEqual('bar', section.get('foo'))
3102
class TestLocationMatcher(TestStore):
3105
super(TestLocationMatcher, self).setUp()
3106
# Any simple store is good enough
3107
self.get_store = config.test_store_builder_registry.get('configobj')
3109
def test_unrelated_section_excluded(self):
3110
store = self.get_store(self)
3111
store._load_from_string('''
3119
section=/foo/bar/baz
3123
self.assertEqual(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
3125
[section.id for _, section in store.get_sections()])
3126
matcher = config.LocationMatcher(store, '/foo/bar/quux')
3127
sections = [section for _, section in matcher.get_sections()]
3128
self.assertEqual(['/foo/bar', '/foo'],
3129
[section.id for section in sections])
3130
self.assertEqual(['quux', 'bar/quux'],
3131
[section.extra_path for section in sections])
3133
def test_more_specific_sections_first(self):
3134
store = self.get_store(self)
3135
store._load_from_string('''
3141
self.assertEqual(['/foo', '/foo/bar'],
3142
[section.id for _, section in store.get_sections()])
3143
matcher = config.LocationMatcher(store, '/foo/bar/baz')
3144
sections = [section for _, section in matcher.get_sections()]
3145
self.assertEqual(['/foo/bar', '/foo'],
3146
[section.id for section in sections])
3147
self.assertEqual(['baz', 'bar/baz'],
3148
[section.extra_path for section in sections])
3150
def test_appendpath_in_no_name_section(self):
3151
# It's a bit weird to allow appendpath in a no-name section, but
3152
# someone may found a use for it
3153
store = self.get_store(self)
3154
store._load_from_string('''
3156
foo:policy = appendpath
3158
matcher = config.LocationMatcher(store, 'dir/subdir')
3159
sections = list(matcher.get_sections())
3160
self.assertLength(1, sections)
3161
self.assertEqual('bar/dir/subdir', sections[0][1].get('foo'))
3163
def test_file_urls_are_normalized(self):
3164
store = self.get_store(self)
3165
if sys.platform == 'win32':
3166
expected_url = 'file:///C:/dir/subdir'
3167
expected_location = 'C:/dir/subdir'
3169
expected_url = 'file:///dir/subdir'
3170
expected_location = '/dir/subdir'
3171
matcher = config.LocationMatcher(store, expected_url)
3172
self.assertEqual(expected_location, matcher.location)
3174
def test_branch_name_colo(self):
3175
store = self.get_store(self)
3176
store._load_from_string(dedent("""\
3178
push_location=my{branchname}
3180
matcher = config.LocationMatcher(store, 'file:///,branch=example%3c')
3181
self.assertEqual('example<', matcher.branch_name)
3182
((_, section),) = matcher.get_sections()
3183
self.assertEqual('example<', section.locals['branchname'])
3185
def test_branch_name_basename(self):
3186
store = self.get_store(self)
3187
store._load_from_string(dedent("""\
3189
push_location=my{branchname}
3191
matcher = config.LocationMatcher(store, 'file:///parent/example%3c')
3192
self.assertEqual('example<', matcher.branch_name)
3193
((_, section),) = matcher.get_sections()
3194
self.assertEqual('example<', section.locals['branchname'])
3197
class TestStartingPathMatcher(TestStore):
3200
super(TestStartingPathMatcher, self).setUp()
3201
# Any simple store is good enough
3202
self.store = config.IniFileStore()
3204
def assertSectionIDs(self, expected, location, content):
3205
self.store._load_from_string(content)
3206
matcher = config.StartingPathMatcher(self.store, location)
3207
sections = list(matcher.get_sections())
3208
self.assertLength(len(expected), sections)
3209
self.assertEqual(expected, [section.id for _, section in sections])
3212
def test_empty(self):
3213
self.assertSectionIDs([], self.get_url(), '')
3215
def test_url_vs_local_paths(self):
3216
# The matcher location is an url and the section names are local paths
3217
self.assertSectionIDs(['/foo/bar', '/foo'],
3218
'file:///foo/bar/baz', '''\
3223
def test_local_path_vs_url(self):
3224
# The matcher location is a local path and the section names are urls
3225
self.assertSectionIDs(['file:///foo/bar', 'file:///foo'],
3226
'/foo/bar/baz', '''\
3232
def test_no_name_section_included_when_present(self):
3233
# Note that other tests will cover the case where the no-name section
3234
# is empty and as such, not included.
3235
sections = self.assertSectionIDs(['/foo/bar', '/foo', None],
3236
'/foo/bar/baz', '''\
3237
option = defined so the no-name section exists
3241
self.assertEqual(['baz', 'bar/baz', '/foo/bar/baz'],
3242
[s.locals['relpath'] for _, s in sections])
3244
def test_order_reversed(self):
3245
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3250
def test_unrelated_section_excluded(self):
3251
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3257
def test_glob_included(self):
3258
sections = self.assertSectionIDs(['/foo/*/baz', '/foo/b*', '/foo'],
3259
'/foo/bar/baz', '''\
3265
# Note that 'baz' as a relpath for /foo/b* is not fully correct, but
3266
# nothing really is... as far using {relpath} to append it to something
3267
# else, this seems good enough though.
3268
self.assertEqual(['', 'baz', 'bar/baz'],
3269
[s.locals['relpath'] for _, s in sections])
3271
def test_respect_order(self):
3272
self.assertSectionIDs(['/foo', '/foo/b*', '/foo/*/baz'],
3273
'/foo/bar/baz', '''\
3281
class TestNameMatcher(TestStore):
3284
super(TestNameMatcher, self).setUp()
3285
self.matcher = config.NameMatcher
3286
# Any simple store is good enough
3287
self.get_store = config.test_store_builder_registry.get('configobj')
3289
def get_matching_sections(self, name):
3290
store = self.get_store(self)
3291
store._load_from_string('''
3299
matcher = self.matcher(store, name)
3300
return list(matcher.get_sections())
3302
def test_matching(self):
3303
sections = self.get_matching_sections('foo')
3304
self.assertLength(1, sections)
3305
self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
3307
def test_not_matching(self):
3308
sections = self.get_matching_sections('baz')
3309
self.assertLength(0, sections)
3312
class TestBaseStackGet(tests.TestCase):
3315
super(TestBaseStackGet, self).setUp()
3316
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3318
def test_get_first_definition(self):
3319
store1 = config.IniFileStore()
3320
store1._load_from_string('foo=bar')
3321
store2 = config.IniFileStore()
3322
store2._load_from_string('foo=baz')
3323
conf = config.Stack([store1.get_sections, store2.get_sections])
3324
self.assertEqual('bar', conf.get('foo'))
3326
def test_get_with_registered_default_value(self):
3327
config.option_registry.register(config.Option('foo', default='bar'))
3328
conf_stack = config.Stack([])
3329
self.assertEqual('bar', conf_stack.get('foo'))
3331
def test_get_without_registered_default_value(self):
3332
config.option_registry.register(config.Option('foo'))
3333
conf_stack = config.Stack([])
3334
self.assertEqual(None, conf_stack.get('foo'))
3336
def test_get_without_default_value_for_not_registered(self):
3337
conf_stack = config.Stack([])
3338
self.assertEqual(None, conf_stack.get('foo'))
3340
def test_get_for_empty_section_callable(self):
3341
conf_stack = config.Stack([lambda : []])
3342
self.assertEqual(None, conf_stack.get('foo'))
3344
def test_get_for_broken_callable(self):
3345
# Trying to use and invalid callable raises an exception on first use
3346
conf_stack = config.Stack([object])
3347
self.assertRaises(TypeError, conf_stack.get, 'foo')
3350
class TestStackWithSimpleStore(tests.TestCase):
3353
super(TestStackWithSimpleStore, self).setUp()
3354
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3355
self.registry = config.option_registry
3357
def get_conf(self, content=None):
3358
return config.MemoryStack(content)
3360
def test_override_value_from_env(self):
3361
self.overrideEnv('FOO', None)
3362
self.registry.register(
3363
config.Option('foo', default='bar', override_from_env=['FOO']))
3364
self.overrideEnv('FOO', 'quux')
3365
# Env variable provides a default taking over the option one
3366
conf = self.get_conf('foo=store')
3367
self.assertEqual('quux', conf.get('foo'))
3369
def test_first_override_value_from_env_wins(self):
3370
self.overrideEnv('NO_VALUE', None)
3371
self.overrideEnv('FOO', None)
3372
self.overrideEnv('BAZ', None)
3373
self.registry.register(
3374
config.Option('foo', default='bar',
3375
override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
3376
self.overrideEnv('FOO', 'foo')
3377
self.overrideEnv('BAZ', 'baz')
3378
# The first env var set wins
3379
conf = self.get_conf('foo=store')
3380
self.assertEqual('foo', conf.get('foo'))
3383
class TestMemoryStack(tests.TestCase):
3386
conf = config.MemoryStack('foo=bar')
3387
self.assertEqual('bar', conf.get('foo'))
3390
conf = config.MemoryStack('foo=bar')
3391
conf.set('foo', 'baz')
3392
self.assertEqual('baz', conf.get('foo'))
3394
def test_no_content(self):
3395
conf = config.MemoryStack()
3396
# No content means no loading
3397
self.assertFalse(conf.store.is_loaded())
3398
self.assertRaises(NotImplementedError, conf.get, 'foo')
3399
# But a content can still be provided
3400
conf.store._load_from_string('foo=bar')
3401
self.assertEqual('bar', conf.get('foo'))
3404
class TestStackIterSections(tests.TestCase):
3406
def test_empty_stack(self):
3407
conf = config.Stack([])
3408
sections = list(conf.iter_sections())
3409
self.assertLength(0, sections)
3411
def test_empty_store(self):
3412
store = config.IniFileStore()
3413
store._load_from_string('')
3414
conf = config.Stack([store.get_sections])
3415
sections = list(conf.iter_sections())
3416
self.assertLength(0, sections)
3418
def test_simple_store(self):
3419
store = config.IniFileStore()
3420
store._load_from_string('foo=bar')
3421
conf = config.Stack([store.get_sections])
3422
tuples = list(conf.iter_sections())
3423
self.assertLength(1, tuples)
3424
(found_store, found_section) = tuples[0]
3425
self.assertIs(store, found_store)
3427
def test_two_stores(self):
3428
store1 = config.IniFileStore()
3429
store1._load_from_string('foo=bar')
3430
store2 = config.IniFileStore()
3431
store2._load_from_string('bar=qux')
3432
conf = config.Stack([store1.get_sections, store2.get_sections])
3433
tuples = list(conf.iter_sections())
3434
self.assertLength(2, tuples)
3435
self.assertIs(store1, tuples[0][0])
3436
self.assertIs(store2, tuples[1][0])
3439
class TestStackWithTransport(tests.TestCaseWithTransport):
3441
scenarios = [(key, {'get_stack': builder}) for key, builder
3442
in config.test_stack_builder_registry.iteritems()]
3445
class TestConcreteStacks(TestStackWithTransport):
3447
def test_build_stack(self):
3448
# Just a smoke test to help debug builders
3449
self.get_stack(self)
3452
class TestStackGet(TestStackWithTransport):
3455
super(TestStackGet, self).setUp()
3456
self.conf = self.get_stack(self)
3458
def test_get_for_empty_stack(self):
3459
self.assertEqual(None, self.conf.get('foo'))
3461
def test_get_hook(self):
3462
self.conf.set('foo', 'bar')
3466
config.ConfigHooks.install_named_hook('get', hook, None)
3467
self.assertLength(0, calls)
3468
value = self.conf.get('foo')
3469
self.assertEqual('bar', value)
3470
self.assertLength(1, calls)
3471
self.assertEqual((self.conf, 'foo', 'bar'), calls[0])
3474
class TestStackGetWithConverter(tests.TestCase):
3477
super(TestStackGetWithConverter, self).setUp()
3478
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3479
self.registry = config.option_registry
3481
def get_conf(self, content=None):
3482
return config.MemoryStack(content)
3484
def register_bool_option(self, name, default=None, default_from_env=None):
3485
b = config.Option(name, help='A boolean.',
3486
default=default, default_from_env=default_from_env,
3487
from_unicode=config.bool_from_store)
3488
self.registry.register(b)
3490
def test_get_default_bool_None(self):
3491
self.register_bool_option('foo')
3492
conf = self.get_conf('')
3493
self.assertEqual(None, conf.get('foo'))
3495
def test_get_default_bool_True(self):
3496
self.register_bool_option('foo', u'True')
3497
conf = self.get_conf('')
3498
self.assertEqual(True, conf.get('foo'))
3500
def test_get_default_bool_False(self):
3501
self.register_bool_option('foo', False)
3502
conf = self.get_conf('')
3503
self.assertEqual(False, conf.get('foo'))
3505
def test_get_default_bool_False_as_string(self):
3506
self.register_bool_option('foo', u'False')
3507
conf = self.get_conf('')
3508
self.assertEqual(False, conf.get('foo'))
3510
def test_get_default_bool_from_env_converted(self):
3511
self.register_bool_option('foo', u'True', default_from_env=['FOO'])
3512
self.overrideEnv('FOO', 'False')
3513
conf = self.get_conf('')
3514
self.assertEqual(False, conf.get('foo'))
3516
def test_get_default_bool_when_conversion_fails(self):
3517
self.register_bool_option('foo', default='True')
3518
conf = self.get_conf('foo=invalid boolean')
3519
self.assertEqual(True, conf.get('foo'))
3521
def register_integer_option(self, name,
3522
default=None, default_from_env=None):
3523
i = config.Option(name, help='An integer.',
3524
default=default, default_from_env=default_from_env,
3525
from_unicode=config.int_from_store)
3526
self.registry.register(i)
3528
def test_get_default_integer_None(self):
3529
self.register_integer_option('foo')
3530
conf = self.get_conf('')
3531
self.assertEqual(None, conf.get('foo'))
3533
def test_get_default_integer(self):
3534
self.register_integer_option('foo', 42)
3535
conf = self.get_conf('')
3536
self.assertEqual(42, conf.get('foo'))
3538
def test_get_default_integer_as_string(self):
3539
self.register_integer_option('foo', u'42')
3540
conf = self.get_conf('')
3541
self.assertEqual(42, conf.get('foo'))
3543
def test_get_default_integer_from_env(self):
3544
self.register_integer_option('foo', default_from_env=['FOO'])
3545
self.overrideEnv('FOO', '18')
3546
conf = self.get_conf('')
3547
self.assertEqual(18, conf.get('foo'))
3549
def test_get_default_integer_when_conversion_fails(self):
3550
self.register_integer_option('foo', default='12')
3551
conf = self.get_conf('foo=invalid integer')
3552
self.assertEqual(12, conf.get('foo'))
3554
def register_list_option(self, name, default=None, default_from_env=None):
3555
l = config.ListOption(name, help='A list.', default=default,
3556
default_from_env=default_from_env)
3557
self.registry.register(l)
3559
def test_get_default_list_None(self):
3560
self.register_list_option('foo')
3561
conf = self.get_conf('')
3562
self.assertEqual(None, conf.get('foo'))
3564
def test_get_default_list_empty(self):
3565
self.register_list_option('foo', '')
3566
conf = self.get_conf('')
3567
self.assertEqual([], conf.get('foo'))
3569
def test_get_default_list_from_env(self):
3570
self.register_list_option('foo', default_from_env=['FOO'])
3571
self.overrideEnv('FOO', '')
3572
conf = self.get_conf('')
3573
self.assertEqual([], conf.get('foo'))
3575
def test_get_with_list_converter_no_item(self):
3576
self.register_list_option('foo', None)
3577
conf = self.get_conf('foo=,')
3578
self.assertEqual([], conf.get('foo'))
3580
def test_get_with_list_converter_many_items(self):
3581
self.register_list_option('foo', None)
3582
conf = self.get_conf('foo=m,o,r,e')
3583
self.assertEqual(['m', 'o', 'r', 'e'], conf.get('foo'))
3585
def test_get_with_list_converter_embedded_spaces_many_items(self):
3586
self.register_list_option('foo', None)
3587
conf = self.get_conf('foo=" bar", "baz "')
3588
self.assertEqual([' bar', 'baz '], conf.get('foo'))
3590
def test_get_with_list_converter_stripped_spaces_many_items(self):
3591
self.register_list_option('foo', None)
3592
conf = self.get_conf('foo= bar , baz ')
3593
self.assertEqual(['bar', 'baz'], conf.get('foo'))
3596
class TestIterOptionRefs(tests.TestCase):
3597
"""iter_option_refs is a bit unusual, document some cases."""
3599
def assertRefs(self, expected, string):
3600
self.assertEqual(expected, list(config.iter_option_refs(string)))
3602
def test_empty(self):
3603
self.assertRefs([(False, '')], '')
3605
def test_no_refs(self):
3606
self.assertRefs([(False, 'foo bar')], 'foo bar')
3608
def test_single_ref(self):
3609
self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
3611
def test_broken_ref(self):
3612
self.assertRefs([(False, '{foo')], '{foo')
3614
def test_embedded_ref(self):
3615
self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
3618
def test_two_refs(self):
3619
self.assertRefs([(False, ''), (True, '{foo}'),
3620
(False, ''), (True, '{bar}'),
3624
def test_newline_in_refs_are_not_matched(self):
3625
self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
3628
class TestStackExpandOptions(tests.TestCaseWithTransport):
3631
super(TestStackExpandOptions, self).setUp()
3632
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3633
self.registry = config.option_registry
3634
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3635
self.conf = config.Stack([store.get_sections], store)
3637
def assertExpansion(self, expected, string, env=None):
3638
self.assertEqual(expected, self.conf.expand_options(string, env))
3640
def test_no_expansion(self):
3641
self.assertExpansion('foo', 'foo')
3643
def test_expand_default_value(self):
3644
self.conf.store._load_from_string('bar=baz')
3645
self.registry.register(config.Option('foo', default=u'{bar}'))
3646
self.assertEqual('baz', self.conf.get('foo', expand=True))
3648
def test_expand_default_from_env(self):
3649
self.conf.store._load_from_string('bar=baz')
3650
self.registry.register(config.Option('foo', default_from_env=['FOO']))
3651
self.overrideEnv('FOO', '{bar}')
3652
self.assertEqual('baz', self.conf.get('foo', expand=True))
3654
def test_expand_default_on_failed_conversion(self):
3655
self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
3656
self.registry.register(
3657
config.Option('foo', default=u'{bar}',
3658
from_unicode=config.int_from_store))
3659
self.assertEqual(42, self.conf.get('foo', expand=True))
3661
def test_env_adding_options(self):
3662
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3664
def test_env_overriding_options(self):
3665
self.conf.store._load_from_string('foo=baz')
3666
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3668
def test_simple_ref(self):
3669
self.conf.store._load_from_string('foo=xxx')
3670
self.assertExpansion('xxx', '{foo}')
3672
def test_unknown_ref(self):
3673
self.assertRaises(errors.ExpandingUnknownOption,
3674
self.conf.expand_options, '{foo}')
3676
def test_illegal_def_is_ignored(self):
3677
self.assertExpansion('{1,2}', '{1,2}')
3678
self.assertExpansion('{ }', '{ }')
3679
self.assertExpansion('${Foo,f}', '${Foo,f}')
3681
def test_indirect_ref(self):
3682
self.conf.store._load_from_string('''
3686
self.assertExpansion('xxx', '{bar}')
3688
def test_embedded_ref(self):
3689
self.conf.store._load_from_string('''
3693
self.assertExpansion('xxx', '{{bar}}')
3695
def test_simple_loop(self):
3696
self.conf.store._load_from_string('foo={foo}')
3697
self.assertRaises(errors.OptionExpansionLoop,
3698
self.conf.expand_options, '{foo}')
3700
def test_indirect_loop(self):
3701
self.conf.store._load_from_string('''
3705
e = self.assertRaises(errors.OptionExpansionLoop,
3706
self.conf.expand_options, '{foo}')
3707
self.assertEqual('foo->bar->baz', e.refs)
3708
self.assertEqual('{foo}', e.string)
3710
def test_list(self):
3711
self.conf.store._load_from_string('''
3715
list={foo},{bar},{baz}
3717
self.registry.register(
3718
config.ListOption('list'))
3719
self.assertEqual(['start', 'middle', 'end'],
3720
self.conf.get('list', expand=True))
3722
def test_cascading_list(self):
3723
self.conf.store._load_from_string('''
3729
self.registry.register(config.ListOption('list'))
3730
# Register an intermediate option as a list to ensure no conversion
3731
# happen while expanding. Conversion should only occur for the original
3732
# option ('list' here).
3733
self.registry.register(config.ListOption('baz'))
3734
self.assertEqual(['start', 'middle', 'end'],
3735
self.conf.get('list', expand=True))
3737
def test_pathologically_hidden_list(self):
3738
self.conf.store._load_from_string('''
3744
hidden={start}{middle}{end}
3746
# What matters is what the registration says, the conversion happens
3747
# only after all expansions have been performed
3748
self.registry.register(config.ListOption('hidden'))
3749
self.assertEqual(['bin', 'go'],
3750
self.conf.get('hidden', expand=True))
3753
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
3756
super(TestStackCrossSectionsExpand, self).setUp()
3758
def get_config(self, location, string):
3761
# Since we don't save the config we won't strictly require to inherit
3762
# from TestCaseInTempDir, but an error occurs so quickly...
3763
c = config.LocationStack(location)
3764
c.store._load_from_string(string)
3767
def test_dont_cross_unrelated_section(self):
3768
c = self.get_config('/another/branch/path','''
3773
[/another/branch/path]
3776
self.assertRaises(errors.ExpandingUnknownOption,
3777
c.get, 'bar', expand=True)
3779
def test_cross_related_sections(self):
3780
c = self.get_config('/project/branch/path','''
3784
[/project/branch/path]
3787
self.assertEqual('quux', c.get('bar', expand=True))
3790
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
3792
def test_cross_global_locations(self):
3793
l_store = config.LocationStore()
3794
l_store._load_from_string('''
3800
g_store = config.GlobalStore()
3801
g_store._load_from_string('''
3807
stack = config.LocationStack('/branch')
3808
self.assertEqual('glob-bar', stack.get('lbar', expand=True))
3809
self.assertEqual('loc-foo', stack.get('gfoo', expand=True))
3812
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
3814
def test_expand_locals_empty(self):
3815
l_store = config.LocationStore()
3816
l_store._load_from_string('''
3817
[/home/user/project]
3822
stack = config.LocationStack('/home/user/project/')
3823
self.assertEqual('', stack.get('base', expand=True))
3824
self.assertEqual('', stack.get('rel', expand=True))
3826
def test_expand_basename_locally(self):
3827
l_store = config.LocationStore()
3828
l_store._load_from_string('''
3829
[/home/user/project]
3833
stack = config.LocationStack('/home/user/project/branch')
3834
self.assertEqual('branch', stack.get('bfoo', expand=True))
3836
def test_expand_basename_locally_longer_path(self):
3837
l_store = config.LocationStore()
3838
l_store._load_from_string('''
3843
stack = config.LocationStack('/home/user/project/dir/branch')
3844
self.assertEqual('branch', stack.get('bfoo', expand=True))
3846
def test_expand_relpath_locally(self):
3847
l_store = config.LocationStore()
3848
l_store._load_from_string('''
3849
[/home/user/project]
3850
lfoo = loc-foo/{relpath}
3853
stack = config.LocationStack('/home/user/project/branch')
3854
self.assertEqual('loc-foo/branch', stack.get('lfoo', expand=True))
3856
def test_expand_relpath_unknonw_in_global(self):
3857
g_store = config.GlobalStore()
3858
g_store._load_from_string('''
3863
stack = config.LocationStack('/home/user/project/branch')
3864
self.assertRaises(errors.ExpandingUnknownOption,
3865
stack.get, 'gfoo', expand=True)
3867
def test_expand_local_option_locally(self):
3868
l_store = config.LocationStore()
3869
l_store._load_from_string('''
3870
[/home/user/project]
3871
lfoo = loc-foo/{relpath}
3875
g_store = config.GlobalStore()
3876
g_store._load_from_string('''
3882
stack = config.LocationStack('/home/user/project/branch')
3883
self.assertEqual('glob-bar', stack.get('lbar', expand=True))
3884
self.assertEqual('loc-foo/branch', stack.get('gfoo', expand=True))
3886
def test_locals_dont_leak(self):
3887
"""Make sure we chose the right local in presence of several sections.
3889
l_store = config.LocationStore()
3890
l_store._load_from_string('''
3892
lfoo = loc-foo/{relpath}
3893
[/home/user/project]
3894
lfoo = loc-foo/{relpath}
3897
stack = config.LocationStack('/home/user/project/branch')
3898
self.assertEqual('loc-foo/branch', stack.get('lfoo', expand=True))
3899
stack = config.LocationStack('/home/user/bar/baz')
3900
self.assertEqual('loc-foo/bar/baz', stack.get('lfoo', expand=True))
3904
class TestStackSet(TestStackWithTransport):
3906
def test_simple_set(self):
3907
conf = self.get_stack(self)
3908
self.assertEqual(None, conf.get('foo'))
3909
conf.set('foo', 'baz')
3910
# Did we get it back ?
3911
self.assertEqual('baz', conf.get('foo'))
3913
def test_set_creates_a_new_section(self):
3914
conf = self.get_stack(self)
3915
conf.set('foo', 'baz')
3916
self.assertEqual, 'baz', conf.get('foo')
3918
def test_set_hook(self):
3922
config.ConfigHooks.install_named_hook('set', hook, None)
3923
self.assertLength(0, calls)
3924
conf = self.get_stack(self)
3925
conf.set('foo', 'bar')
3926
self.assertLength(1, calls)
3927
self.assertEqual((conf, 'foo', 'bar'), calls[0])
3930
class TestStackRemove(TestStackWithTransport):
3932
def test_remove_existing(self):
3933
conf = self.get_stack(self)
3934
conf.set('foo', 'bar')
3935
self.assertEqual('bar', conf.get('foo'))
3937
# Did we get it back ?
3938
self.assertEqual(None, conf.get('foo'))
3940
def test_remove_unknown(self):
3941
conf = self.get_stack(self)
3942
self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
3944
def test_remove_hook(self):
3948
config.ConfigHooks.install_named_hook('remove', hook, None)
3949
self.assertLength(0, calls)
3950
conf = self.get_stack(self)
3951
conf.set('foo', 'bar')
3953
self.assertLength(1, calls)
3954
self.assertEqual((conf, 'foo'), calls[0])
3957
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
3960
super(TestConfigGetOptions, self).setUp()
3961
create_configs(self)
3963
def test_no_variable(self):
3964
# Using branch should query branch, locations and bazaar
3965
self.assertOptions([], self.branch_config)
3967
def test_option_in_bazaar(self):
3968
self.bazaar_config.set_user_option('file', 'bazaar')
3969
self.assertOptions([('file', 'bazaar', 'DEFAULT', 'bazaar')],
3972
def test_option_in_locations(self):
3973
self.locations_config.set_user_option('file', 'locations')
3975
[('file', 'locations', self.tree.basedir, 'locations')],
3976
self.locations_config)
3978
def test_option_in_branch(self):
3979
self.branch_config.set_user_option('file', 'branch')
3980
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch')],
3983
def test_option_in_bazaar_and_branch(self):
3984
self.bazaar_config.set_user_option('file', 'bazaar')
3985
self.branch_config.set_user_option('file', 'branch')
3986
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch'),
3987
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
3990
def test_option_in_branch_and_locations(self):
3991
# Hmm, locations override branch :-/
3992
self.locations_config.set_user_option('file', 'locations')
3993
self.branch_config.set_user_option('file', 'branch')
3995
[('file', 'locations', self.tree.basedir, 'locations'),
3996
('file', 'branch', 'DEFAULT', 'branch'),],
3999
def test_option_in_bazaar_locations_and_branch(self):
4000
self.bazaar_config.set_user_option('file', 'bazaar')
4001
self.locations_config.set_user_option('file', 'locations')
4002
self.branch_config.set_user_option('file', 'branch')
4004
[('file', 'locations', self.tree.basedir, 'locations'),
4005
('file', 'branch', 'DEFAULT', 'branch'),
4006
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4010
class TestConfigRemoveOption(tests.TestCaseWithTransport, TestOptionsMixin):
4013
super(TestConfigRemoveOption, self).setUp()
4014
create_configs_with_file_option(self)
4016
def test_remove_in_locations(self):
4017
self.locations_config.remove_user_option('file', self.tree.basedir)
4019
[('file', 'branch', 'DEFAULT', 'branch'),
4020
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4023
def test_remove_in_branch(self):
4024
self.branch_config.remove_user_option('file')
4026
[('file', 'locations', self.tree.basedir, 'locations'),
4027
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4030
def test_remove_in_bazaar(self):
4031
self.bazaar_config.remove_user_option('file')
4033
[('file', 'locations', self.tree.basedir, 'locations'),
4034
('file', 'branch', 'DEFAULT', 'branch'),],
4038
class TestConfigGetSections(tests.TestCaseWithTransport):
4041
super(TestConfigGetSections, self).setUp()
4042
create_configs(self)
4044
def assertSectionNames(self, expected, conf, name=None):
4045
"""Check which sections are returned for a given config.
4047
If fallback configurations exist their sections can be included.
4049
:param expected: A list of section names.
4051
:param conf: The configuration that will be queried.
4053
:param name: An optional section name that will be passed to
4056
sections = list(conf._get_sections(name))
4057
self.assertLength(len(expected), sections)
4058
self.assertEqual(expected, [n for n, _, _ in sections])
4060
def test_bazaar_default_section(self):
4061
self.assertSectionNames(['DEFAULT'], self.bazaar_config)
4063
def test_locations_default_section(self):
4064
# No sections are defined in an empty file
4065
self.assertSectionNames([], self.locations_config)
4067
def test_locations_named_section(self):
4068
self.locations_config.set_user_option('file', 'locations')
4069
self.assertSectionNames([self.tree.basedir], self.locations_config)
4071
def test_locations_matching_sections(self):
4072
loc_config = self.locations_config
4073
loc_config.set_user_option('file', 'locations')
4074
# We need to cheat a bit here to create an option in sections above and
4075
# below the 'location' one.
4076
parser = loc_config._get_parser()
4077
# locations.cong deals with '/' ignoring native os.sep
4078
location_names = self.tree.basedir.split('/')
4079
parent = '/'.join(location_names[:-1])
4080
child = '/'.join(location_names + ['child'])
4082
parser[parent]['file'] = 'parent'
4084
parser[child]['file'] = 'child'
4085
self.assertSectionNames([self.tree.basedir, parent], loc_config)
4087
def test_branch_data_default_section(self):
4088
self.assertSectionNames([None],
4089
self.branch_config._get_branch_data_config())
4091
def test_branch_default_sections(self):
4092
# No sections are defined in an empty locations file
4093
self.assertSectionNames([None, 'DEFAULT'],
4095
# Unless we define an option
4096
self.branch_config._get_location_config().set_user_option(
4097
'file', 'locations')
4098
self.assertSectionNames([self.tree.basedir, None, 'DEFAULT'],
4101
def test_bazaar_named_section(self):
4102
# We need to cheat as the API doesn't give direct access to sections
4103
# other than DEFAULT.
4104
self.bazaar_config.set_alias('bazaar', 'bzr')
4105
self.assertSectionNames(['ALIASES'], self.bazaar_config, 'ALIASES')
4108
class TestSharedStores(tests.TestCaseInTempDir):
4110
def test_bazaar_conf_shared(self):
4111
g1 = config.GlobalStack()
4112
g2 = config.GlobalStack()
4113
# The two stacks share the same store
4114
self.assertIs(g1.store, g2.store)
4117
class TestAuthenticationConfigFilePermissions(tests.TestCaseInTempDir):
4118
"""Test warning for permissions of authentication.conf."""
4121
super(TestAuthenticationConfigFilePermissions, self).setUp()
4122
self.path = osutils.pathjoin(self.test_dir, 'authentication.conf')
4123
with open(self.path, 'w') as f:
4124
f.write(b"""[broken]
4127
port=port # Error: Not an int
4129
self.overrideAttr(config, 'authentication_config_filename',
4131
osutils.chmod_if_possible(self.path, 0o755)
4133
def test_check_warning(self):
4134
conf = config.AuthenticationConfig()
4135
self.assertEqual(conf._filename, self.path)
4136
self.assertContainsRe(self.get_log(),
4137
'Saved passwords may be accessible by other users.')
4139
def test_check_suppressed_warning(self):
4140
global_config = config.GlobalConfig()
4141
global_config.set_user_option('suppress_warnings',
4142
'insecure_permissions')
4143
conf = config.AuthenticationConfig()
4144
self.assertEqual(conf._filename, self.path)
4145
self.assertNotContainsRe(self.get_log(),
4146
'Saved passwords may be accessible by other users.')
1315
4149
class TestAuthenticationConfigFile(tests.TestCase):
1316
4150
"""Test the authentication.conf file matching"""