~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_config.py

Abbreviate pack_stat struct format to '>6L'

Show diffs side-by-side

added added

removed removed

Lines of Context:
33
33
    errors,
34
34
    osutils,
35
35
    mail_client,
36
 
    mergetools,
37
36
    ui,
38
37
    urlutils,
39
 
    registry,
 
38
    remote,
40
39
    tests,
41
40
    trace,
42
 
    transport,
43
 
    )
 
41
    )
 
42
from bzrlib.symbol_versioning import (
 
43
    deprecated_in,
 
44
    )
 
45
from bzrlib.transport import remote as transport_remote
44
46
from bzrlib.tests import (
45
47
    features,
46
 
    TestSkipped,
47
48
    scenarios,
 
49
    test_server,
48
50
    )
49
51
from bzrlib.util.configobj import configobj
50
52
 
63
65
 
64
66
load_tests = scenarios.load_tests_apply_scenarios
65
67
 
66
 
# We need adapters that can build a config store in a test context. Test
67
 
# classes, based on TestCaseWithTransport, can use the registry to parametrize
68
 
# themselves. The builder will receive a test instance and should return a
69
 
# ready-to-use store.  Plugins that defines new stores can also register
70
 
# themselves here to be tested against the tests defined below.
71
 
 
72
 
# FIXME: plugins should *not* need to import test_config to register their
73
 
# helpers (or selftest -s xxx will be broken), the following registry should be
74
 
# moved to bzrlib.config instead so that selftest -s bt.test_config also runs
75
 
# the plugin specific tests (selftest -s bp.xxx won't, that would be against
76
 
# the spirit of '-s') -- vila 20110503
77
 
test_store_builder_registry = registry.Registry()
78
 
test_store_builder_registry.register(
 
68
# Register helpers to build stores
 
69
config.test_store_builder_registry.register(
79
70
    'configobj', lambda test: config.IniFileStore(test.get_transport(),
80
71
                                                  'configobj.conf'))
81
 
test_store_builder_registry.register(
 
72
config.test_store_builder_registry.register(
82
73
    'bazaar', lambda test: config.GlobalStore())
83
 
test_store_builder_registry.register(
 
74
config.test_store_builder_registry.register(
84
75
    'location', lambda test: config.LocationStore())
85
 
test_store_builder_registry.register(
86
 
    'branch', lambda test: config.BranchStore(test.branch))
87
 
 
 
76
 
 
77
 
 
78
def build_backing_branch(test, relpath,
 
79
                         transport_class=None, server_class=None):
 
80
    """Test helper to create a backing branch only once.
 
81
 
 
82
    Some tests needs multiple stores/stacks to check concurrent update
 
83
    behaviours. As such, they need to build different branch *objects* even if
 
84
    they share the branch on disk.
 
85
 
 
86
    :param relpath: The relative path to the branch. (Note that the helper
 
87
        should always specify the same relpath).
 
88
 
 
89
    :param transport_class: The Transport class the test needs to use.
 
90
 
 
91
    :param server_class: The server associated with the ``transport_class``
 
92
        above.
 
93
 
 
94
    Either both or neither of ``transport_class`` and ``server_class`` should
 
95
    be specified.
 
96
    """
 
97
    if transport_class is not None and server_class is not None:
 
98
        test.transport_class = transport_class
 
99
        test.transport_server = server_class
 
100
    elif not (transport_class is None and server_class is None):
 
101
        raise AssertionError('Specify both ``transport_class`` and '
 
102
                             '``server_class`` or neither of them')
 
103
    if getattr(test, 'backing_branch', None) is None:
 
104
        # First call, let's build the branch on disk
 
105
        test.backing_branch = test.make_branch(relpath)
 
106
 
 
107
 
 
108
def build_branch_store(test):
 
109
    build_backing_branch(test, 'branch')
 
110
    b = branch.Branch.open('branch')
 
111
    return config.BranchStore(b)
 
112
config.test_store_builder_registry.register('branch', build_branch_store)
 
113
 
 
114
 
 
115
def build_control_store(test):
 
116
    build_backing_branch(test, 'branch')
 
117
    b = bzrdir.BzrDir.open('branch')
 
118
    return config.ControlStore(b)
 
119
config.test_store_builder_registry.register('control', build_control_store)
 
120
 
 
121
 
 
122
def build_remote_branch_store(test):
 
123
    # There is only one permutation (but we won't be able to handle more with
 
124
    # this design anyway)
 
125
    (transport_class,
 
126
     server_class) = transport_remote.get_test_permutations()[0]
 
127
    build_backing_branch(test, 'branch', transport_class, server_class)
 
128
    b = branch.Branch.open(test.get_url('branch'))
 
129
    return config.BranchStore(b)
 
130
config.test_store_builder_registry.register('remote_branch',
 
131
                                            build_remote_branch_store)
 
132
 
 
133
 
 
134
config.test_stack_builder_registry.register(
 
135
    'bazaar', lambda test: config.GlobalStack())
 
136
config.test_stack_builder_registry.register(
 
137
    'location', lambda test: config.LocationStack('.'))
 
138
 
 
139
 
 
140
def build_branch_stack(test):
 
141
    build_backing_branch(test, 'branch')
 
142
    b = branch.Branch.open('branch')
 
143
    return config.BranchStack(b)
 
144
config.test_stack_builder_registry.register('branch', build_branch_stack)
 
145
 
 
146
 
 
147
def build_remote_branch_stack(test):
 
148
    # There is only one permutation (but we won't be able to handle more with
 
149
    # this design anyway)
 
150
    (transport_class,
 
151
     server_class) = transport_remote.get_test_permutations()[0]
 
152
    build_backing_branch(test, 'branch', transport_class, server_class)
 
153
    b = branch.Branch.open(test.get_url('branch'))
 
154
    return config.RemoteBranchStack(b)
 
155
config.test_stack_builder_registry.register('remote_branch',
 
156
                                            build_remote_branch_stack)
 
157
 
 
158
def build_remote_control_stack(test):
 
159
    # There is only one permutation (but we won't be able to handle more with
 
160
    # this design anyway)
 
161
    (transport_class,
 
162
     server_class) = transport_remote.get_test_permutations()[0]
 
163
    # We need only a bzrdir for this, not a full branch, but it's not worth
 
164
    # creating a dedicated helper to create only the bzrdir
 
165
    build_backing_branch(test, 'branch', transport_class, server_class)
 
166
    b = branch.Branch.open(test.get_url('branch'))
 
167
    return config.RemoteControlStack(b.bzrdir)
 
168
config.test_stack_builder_registry.register('remote_control',
 
169
                                            build_remote_control_stack)
88
170
 
89
171
 
90
172
sample_long_alias="log -r-15..-1 --line"
94
176
editor=vim
95
177
change_editor=vimdiff -of @new_path @old_path
96
178
gpg_signing_command=gnome-gpg
 
179
gpg_signing_key=DD4D5088
97
180
log_format=short
 
181
validate_signatures_in_log=true
 
182
acceptable_keys=amy
98
183
user_global_option=something
99
184
bzr.mergetool.sometool=sometool {base} {this} {other} -o {result}
100
185
bzr.mergetool.funkytool=funkytool "arg with spaces" {this_temp}
 
186
bzr.mergetool.newtool='"newtool with spaces" {this_temp}'
101
187
bzr.default_mergetool=sometool
102
188
[ALIASES]
103
189
h=help
146
232
[/a/]
147
233
check_signatures=check-available
148
234
gpg_signing_command=false
 
235
gpg_signing_key=default
149
236
user_local_option=local
150
237
# test trailing / matching
151
238
[/a/*]
395
482
        config.Config()
396
483
 
397
484
    def test_no_default_editor(self):
398
 
        self.assertRaises(NotImplementedError, config.Config().get_editor)
 
485
        self.assertRaises(
 
486
            NotImplementedError,
 
487
            self.applyDeprecated, deprecated_in((2, 4, 0)),
 
488
            config.Config().get_editor)
399
489
 
400
490
    def test_user_email(self):
401
491
        my_config = InstrumentedConfig()
444
534
        my_config = config.Config()
445
535
        self.assertEqual('long', my_config.log_format())
446
536
 
 
537
    def test_acceptable_keys_default(self):
 
538
        my_config = config.Config()
 
539
        self.assertEqual(None, my_config.acceptable_keys())
 
540
 
 
541
    def test_validate_signatures_in_log_default(self):
 
542
        my_config = config.Config()
 
543
        self.assertEqual(False, my_config.validate_signatures_in_log())
 
544
 
447
545
    def test_get_change_editor(self):
448
546
        my_config = InstrumentedConfig()
449
547
        change_editor = my_config.get_change_editor('old_tree', 'new_tree')
630
728
    def test_default_is_True(self):
631
729
        self.config = self.get_config(True)
632
730
        self.assertExpandIs(True)
633
 
        
 
731
 
634
732
    def test_default_is_False(self):
635
733
        self.config = self.get_config(False)
636
734
        self.assertExpandIs(False)
637
 
        
 
735
 
638
736
 
639
737
class TestIniConfigOptionExpansion(tests.TestCase):
640
738
    """Test option expansion from the IniConfig level.
738
836
        self.assertEquals(['{foo', '}', '{', 'bar}'],
739
837
                          conf.get_user_option('hidden', expand=True))
740
838
 
 
839
 
741
840
class TestLocationConfigOptionExpansion(tests.TestCaseInTempDir):
742
841
 
743
842
    def get_config(self, location, string=None):
954
1053
        # automatically cast to list
955
1054
        self.assertEqual(['x'], get_list('one_item'))
956
1055
 
 
1056
    def test_get_user_option_as_int_from_SI(self):
 
1057
        conf, parser = self.make_config_parser("""
 
1058
plain = 100
 
1059
si_k = 5k,
 
1060
si_kb = 5kb,
 
1061
si_m = 5M,
 
1062
si_mb = 5MB,
 
1063
si_g = 5g,
 
1064
si_gb = 5gB,
 
1065
""")
 
1066
        get_si = conf.get_user_option_as_int_from_SI
 
1067
        self.assertEqual(100, get_si('plain'))
 
1068
        self.assertEqual(5000, get_si('si_k'))
 
1069
        self.assertEqual(5000, get_si('si_kb'))
 
1070
        self.assertEqual(5000000, get_si('si_m'))
 
1071
        self.assertEqual(5000000, get_si('si_mb'))
 
1072
        self.assertEqual(5000000000, get_si('si_g'))
 
1073
        self.assertEqual(5000000000, get_si('si_gb'))
 
1074
        self.assertEqual(None, get_si('non-exist'))
 
1075
        self.assertEqual(42, get_si('non-exist-with-default',  42))
957
1076
 
958
1077
class TestSupressWarning(TestIniConfig):
959
1078
 
1109
1228
 
1110
1229
    def test_configured_editor(self):
1111
1230
        my_config = config.GlobalConfig.from_string(sample_config_text)
1112
 
        self.assertEqual("vim", my_config.get_editor())
 
1231
        editor = self.applyDeprecated(
 
1232
            deprecated_in((2, 4, 0)), my_config.get_editor)
 
1233
        self.assertEqual('vim', editor)
1113
1234
 
1114
1235
    def test_signatures_always(self):
1115
1236
        my_config = config.GlobalConfig.from_string(sample_always_signatures)
1144
1265
        self.assertEqual("gnome-gpg", my_config.gpg_signing_command())
1145
1266
        self.assertEqual(False, my_config.signature_needed())
1146
1267
 
 
1268
    def test_gpg_signing_key(self):
 
1269
        my_config = self._get_sample_config()
 
1270
        self.assertEqual("DD4D5088", my_config.gpg_signing_key())
 
1271
 
1147
1272
    def _get_empty_config(self):
1148
1273
        my_config = config.GlobalConfig()
1149
1274
        return my_config
1169
1294
        my_config = self._get_sample_config()
1170
1295
        self.assertEqual("short", my_config.log_format())
1171
1296
 
 
1297
    def test_configured_acceptable_keys(self):
 
1298
        my_config = self._get_sample_config()
 
1299
        self.assertEqual("amy", my_config.acceptable_keys())
 
1300
 
 
1301
    def test_configured_validate_signatures_in_log(self):
 
1302
        my_config = self._get_sample_config()
 
1303
        self.assertEqual(True, my_config.validate_signatures_in_log())
 
1304
 
1172
1305
    def test_get_alias(self):
1173
1306
        my_config = self._get_sample_config()
1174
1307
        self.assertEqual('help', my_config.get_alias('h'))
1207
1340
        self.log(repr(tools))
1208
1341
        self.assertEqual(
1209
1342
            {u'funkytool' : u'funkytool "arg with spaces" {this_temp}',
1210
 
            u'sometool' : u'sometool {base} {this} {other} -o {result}'},
 
1343
            u'sometool' : u'sometool {base} {this} {other} -o {result}',
 
1344
            u'newtool' : u'"newtool with spaces" {this_temp}'},
1211
1345
            tools)
1212
1346
 
1213
1347
    def test_get_merge_tools_empty(self):
1431
1565
        self.get_branch_config('/a')
1432
1566
        self.assertEqual("false", self.my_config.gpg_signing_command())
1433
1567
 
 
1568
    def test_gpg_signing_key(self):
 
1569
        self.get_branch_config('/b')
 
1570
        self.assertEqual("DD4D5088", self.my_config.gpg_signing_key())
 
1571
 
 
1572
    def test_gpg_signing_key_default(self):
 
1573
        self.get_branch_config('/a')
 
1574
        self.assertEqual("erik@bagfors.nu", self.my_config.gpg_signing_key())
 
1575
 
1434
1576
    def test_get_user_option_global(self):
1435
1577
        self.get_branch_config('/a')
1436
1578
        self.assertEqual('something',
1800
1942
 
1801
1943
class TestTransportConfig(tests.TestCaseWithTransport):
1802
1944
 
 
1945
    def test_load_utf8(self):
 
1946
        """Ensure we can load an utf8-encoded file."""
 
1947
        t = self.get_transport()
 
1948
        unicode_user = u'b\N{Euro Sign}ar'
 
1949
        unicode_content = u'user=%s' % (unicode_user,)
 
1950
        utf8_content = unicode_content.encode('utf8')
 
1951
        # Store the raw content in the config file
 
1952
        t.put_bytes('foo.conf', utf8_content)
 
1953
        conf = config.TransportConfig(t, 'foo.conf')
 
1954
        self.assertEquals(unicode_user, conf.get_option('user'))
 
1955
 
 
1956
    def test_load_non_ascii(self):
 
1957
        """Ensure we display a proper error on non-ascii, non utf-8 content."""
 
1958
        t = self.get_transport()
 
1959
        t.put_bytes('foo.conf', 'user=foo\n#\xff\n')
 
1960
        conf = config.TransportConfig(t, 'foo.conf')
 
1961
        self.assertRaises(errors.ConfigContentError, conf._get_configobj)
 
1962
 
 
1963
    def test_load_erroneous_content(self):
 
1964
        """Ensure we display a proper error on content that can't be parsed."""
 
1965
        t = self.get_transport()
 
1966
        t.put_bytes('foo.conf', '[open_section\n')
 
1967
        conf = config.TransportConfig(t, 'foo.conf')
 
1968
        self.assertRaises(errors.ParseConfigError, conf._get_configobj)
 
1969
 
 
1970
    def test_load_permission_denied(self):
 
1971
        """Ensure we get an empty config file if the file is inaccessible."""
 
1972
        warnings = []
 
1973
        def warning(*args):
 
1974
            warnings.append(args[0] % args[1:])
 
1975
        self.overrideAttr(trace, 'warning', warning)
 
1976
 
 
1977
        class DenyingTransport(object):
 
1978
 
 
1979
            def __init__(self, base):
 
1980
                self.base = base
 
1981
 
 
1982
            def get_bytes(self, relpath):
 
1983
                raise errors.PermissionDenied(relpath, "")
 
1984
 
 
1985
        cfg = config.TransportConfig(
 
1986
            DenyingTransport("nonexisting://"), 'control.conf')
 
1987
        self.assertIs(None, cfg.get_option('non-existant', 'SECTION'))
 
1988
        self.assertEquals(
 
1989
            warnings,
 
1990
            [u'Permission denied while trying to open configuration file '
 
1991
             u'nonexisting:///control.conf.'])
 
1992
 
1803
1993
    def test_get_value(self):
1804
1994
        """Test that retreiving a value from a section is possible"""
1805
 
        bzrdir_config = config.TransportConfig(transport.get_transport('.'),
 
1995
        bzrdir_config = config.TransportConfig(self.get_transport('.'),
1806
1996
                                               'control.conf')
1807
1997
        bzrdir_config.set_option('value', 'key', 'SECTION')
1808
1998
        bzrdir_config.set_option('value2', 'key2')
1838
2028
        self.assertIs(None, bzrdir_config.get_default_stack_on())
1839
2029
 
1840
2030
 
 
2031
class TestOldConfigHooks(tests.TestCaseWithTransport):
 
2032
 
 
2033
    def setUp(self):
 
2034
        super(TestOldConfigHooks, self).setUp()
 
2035
        create_configs_with_file_option(self)
 
2036
 
 
2037
    def assertGetHook(self, conf, name, value):
 
2038
        calls = []
 
2039
        def hook(*args):
 
2040
            calls.append(args)
 
2041
        config.OldConfigHooks.install_named_hook('get', hook, None)
 
2042
        self.addCleanup(
 
2043
            config.OldConfigHooks.uninstall_named_hook, 'get', None)
 
2044
        self.assertLength(0, calls)
 
2045
        actual_value = conf.get_user_option(name)
 
2046
        self.assertEquals(value, actual_value)
 
2047
        self.assertLength(1, calls)
 
2048
        self.assertEquals((conf, name, value), calls[0])
 
2049
 
 
2050
    def test_get_hook_bazaar(self):
 
2051
        self.assertGetHook(self.bazaar_config, 'file', 'bazaar')
 
2052
 
 
2053
    def test_get_hook_locations(self):
 
2054
        self.assertGetHook(self.locations_config, 'file', 'locations')
 
2055
 
 
2056
    def test_get_hook_branch(self):
 
2057
        # Since locations masks branch, we define a different option
 
2058
        self.branch_config.set_user_option('file2', 'branch')
 
2059
        self.assertGetHook(self.branch_config, 'file2', 'branch')
 
2060
 
 
2061
    def assertSetHook(self, conf, name, value):
 
2062
        calls = []
 
2063
        def hook(*args):
 
2064
            calls.append(args)
 
2065
        config.OldConfigHooks.install_named_hook('set', hook, None)
 
2066
        self.addCleanup(
 
2067
            config.OldConfigHooks.uninstall_named_hook, 'set', None)
 
2068
        self.assertLength(0, calls)
 
2069
        conf.set_user_option(name, value)
 
2070
        self.assertLength(1, calls)
 
2071
        # We can't assert the conf object below as different configs use
 
2072
        # different means to implement set_user_option and we care only about
 
2073
        # coverage here.
 
2074
        self.assertEquals((name, value), calls[0][1:])
 
2075
 
 
2076
    def test_set_hook_bazaar(self):
 
2077
        self.assertSetHook(self.bazaar_config, 'foo', 'bazaar')
 
2078
 
 
2079
    def test_set_hook_locations(self):
 
2080
        self.assertSetHook(self.locations_config, 'foo', 'locations')
 
2081
 
 
2082
    def test_set_hook_branch(self):
 
2083
        self.assertSetHook(self.branch_config, 'foo', 'branch')
 
2084
 
 
2085
    def assertRemoveHook(self, conf, name, section_name=None):
 
2086
        calls = []
 
2087
        def hook(*args):
 
2088
            calls.append(args)
 
2089
        config.OldConfigHooks.install_named_hook('remove', hook, None)
 
2090
        self.addCleanup(
 
2091
            config.OldConfigHooks.uninstall_named_hook, 'remove', None)
 
2092
        self.assertLength(0, calls)
 
2093
        conf.remove_user_option(name, section_name)
 
2094
        self.assertLength(1, calls)
 
2095
        # We can't assert the conf object below as different configs use
 
2096
        # different means to implement remove_user_option and we care only about
 
2097
        # coverage here.
 
2098
        self.assertEquals((name,), calls[0][1:])
 
2099
 
 
2100
    def test_remove_hook_bazaar(self):
 
2101
        self.assertRemoveHook(self.bazaar_config, 'file')
 
2102
 
 
2103
    def test_remove_hook_locations(self):
 
2104
        self.assertRemoveHook(self.locations_config, 'file',
 
2105
                              self.locations_config.location)
 
2106
 
 
2107
    def test_remove_hook_branch(self):
 
2108
        self.assertRemoveHook(self.branch_config, 'file')
 
2109
 
 
2110
    def assertLoadHook(self, name, conf_class, *conf_args):
 
2111
        calls = []
 
2112
        def hook(*args):
 
2113
            calls.append(args)
 
2114
        config.OldConfigHooks.install_named_hook('load', hook, None)
 
2115
        self.addCleanup(
 
2116
            config.OldConfigHooks.uninstall_named_hook, 'load', None)
 
2117
        self.assertLength(0, calls)
 
2118
        # Build a config
 
2119
        conf = conf_class(*conf_args)
 
2120
        # Access an option to trigger a load
 
2121
        conf.get_user_option(name)
 
2122
        self.assertLength(1, calls)
 
2123
        # Since we can't assert about conf, we just use the number of calls ;-/
 
2124
 
 
2125
    def test_load_hook_bazaar(self):
 
2126
        self.assertLoadHook('file', config.GlobalConfig)
 
2127
 
 
2128
    def test_load_hook_locations(self):
 
2129
        self.assertLoadHook('file', config.LocationConfig, self.tree.basedir)
 
2130
 
 
2131
    def test_load_hook_branch(self):
 
2132
        self.assertLoadHook('file', config.BranchConfig, self.tree.branch)
 
2133
 
 
2134
    def assertSaveHook(self, conf):
 
2135
        calls = []
 
2136
        def hook(*args):
 
2137
            calls.append(args)
 
2138
        config.OldConfigHooks.install_named_hook('save', hook, None)
 
2139
        self.addCleanup(
 
2140
            config.OldConfigHooks.uninstall_named_hook, 'save', None)
 
2141
        self.assertLength(0, calls)
 
2142
        # Setting an option triggers a save
 
2143
        conf.set_user_option('foo', 'bar')
 
2144
        self.assertLength(1, calls)
 
2145
        # Since we can't assert about conf, we just use the number of calls ;-/
 
2146
 
 
2147
    def test_save_hook_bazaar(self):
 
2148
        self.assertSaveHook(self.bazaar_config)
 
2149
 
 
2150
    def test_save_hook_locations(self):
 
2151
        self.assertSaveHook(self.locations_config)
 
2152
 
 
2153
    def test_save_hook_branch(self):
 
2154
        self.assertSaveHook(self.branch_config)
 
2155
 
 
2156
 
 
2157
class TestOldConfigHooksForRemote(tests.TestCaseWithTransport):
 
2158
    """Tests config hooks for remote configs.
 
2159
 
 
2160
    No tests for the remove hook as this is not implemented there.
 
2161
    """
 
2162
 
 
2163
    def setUp(self):
 
2164
        super(TestOldConfigHooksForRemote, self).setUp()
 
2165
        self.transport_server = test_server.SmartTCPServer_for_testing
 
2166
        create_configs_with_file_option(self)
 
2167
 
 
2168
    def assertGetHook(self, conf, name, value):
 
2169
        calls = []
 
2170
        def hook(*args):
 
2171
            calls.append(args)
 
2172
        config.OldConfigHooks.install_named_hook('get', hook, None)
 
2173
        self.addCleanup(
 
2174
            config.OldConfigHooks.uninstall_named_hook, 'get', None)
 
2175
        self.assertLength(0, calls)
 
2176
        actual_value = conf.get_option(name)
 
2177
        self.assertEquals(value, actual_value)
 
2178
        self.assertLength(1, calls)
 
2179
        self.assertEquals((conf, name, value), calls[0])
 
2180
 
 
2181
    def test_get_hook_remote_branch(self):
 
2182
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2183
        self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
 
2184
 
 
2185
    def test_get_hook_remote_bzrdir(self):
 
2186
        remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
 
2187
        conf = remote_bzrdir._get_config()
 
2188
        conf.set_option('remotedir', 'file')
 
2189
        self.assertGetHook(conf, 'file', 'remotedir')
 
2190
 
 
2191
    def assertSetHook(self, conf, name, value):
 
2192
        calls = []
 
2193
        def hook(*args):
 
2194
            calls.append(args)
 
2195
        config.OldConfigHooks.install_named_hook('set', hook, None)
 
2196
        self.addCleanup(
 
2197
            config.OldConfigHooks.uninstall_named_hook, 'set', None)
 
2198
        self.assertLength(0, calls)
 
2199
        conf.set_option(value, name)
 
2200
        self.assertLength(1, calls)
 
2201
        # We can't assert the conf object below as different configs use
 
2202
        # different means to implement set_user_option and we care only about
 
2203
        # coverage here.
 
2204
        self.assertEquals((name, value), calls[0][1:])
 
2205
 
 
2206
    def test_set_hook_remote_branch(self):
 
2207
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2208
        self.addCleanup(remote_branch.lock_write().unlock)
 
2209
        self.assertSetHook(remote_branch._get_config(), 'file', 'remote')
 
2210
 
 
2211
    def test_set_hook_remote_bzrdir(self):
 
2212
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2213
        self.addCleanup(remote_branch.lock_write().unlock)
 
2214
        remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
 
2215
        self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
 
2216
 
 
2217
    def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
 
2218
        calls = []
 
2219
        def hook(*args):
 
2220
            calls.append(args)
 
2221
        config.OldConfigHooks.install_named_hook('load', hook, None)
 
2222
        self.addCleanup(
 
2223
            config.OldConfigHooks.uninstall_named_hook, 'load', None)
 
2224
        self.assertLength(0, calls)
 
2225
        # Build a config
 
2226
        conf = conf_class(*conf_args)
 
2227
        # Access an option to trigger a load
 
2228
        conf.get_option(name)
 
2229
        self.assertLength(expected_nb_calls, calls)
 
2230
        # Since we can't assert about conf, we just use the number of calls ;-/
 
2231
 
 
2232
    def test_load_hook_remote_branch(self):
 
2233
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2234
        self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
 
2235
 
 
2236
    def test_load_hook_remote_bzrdir(self):
 
2237
        remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
 
2238
        # The config file doesn't exist, set an option to force its creation
 
2239
        conf = remote_bzrdir._get_config()
 
2240
        conf.set_option('remotedir', 'file')
 
2241
        # We get one call for the server and one call for the client, this is
 
2242
        # caused by the differences in implementations betwen
 
2243
        # SmartServerBzrDirRequestConfigFile (in smart/bzrdir.py) and
 
2244
        # SmartServerBranchGetConfigFile (in smart/branch.py)
 
2245
        self.assertLoadHook(2 ,'file', remote.RemoteBzrDirConfig, remote_bzrdir)
 
2246
 
 
2247
    def assertSaveHook(self, conf):
 
2248
        calls = []
 
2249
        def hook(*args):
 
2250
            calls.append(args)
 
2251
        config.OldConfigHooks.install_named_hook('save', hook, None)
 
2252
        self.addCleanup(
 
2253
            config.OldConfigHooks.uninstall_named_hook, 'save', None)
 
2254
        self.assertLength(0, calls)
 
2255
        # Setting an option triggers a save
 
2256
        conf.set_option('foo', 'bar')
 
2257
        self.assertLength(1, calls)
 
2258
        # Since we can't assert about conf, we just use the number of calls ;-/
 
2259
 
 
2260
    def test_save_hook_remote_branch(self):
 
2261
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2262
        self.addCleanup(remote_branch.lock_write().unlock)
 
2263
        self.assertSaveHook(remote_branch._get_config())
 
2264
 
 
2265
    def test_save_hook_remote_bzrdir(self):
 
2266
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2267
        self.addCleanup(remote_branch.lock_write().unlock)
 
2268
        remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
 
2269
        self.assertSaveHook(remote_bzrdir._get_config())
 
2270
 
 
2271
 
 
2272
class TestOption(tests.TestCase):
 
2273
 
 
2274
    def test_default_value(self):
 
2275
        opt = config.Option('foo', default='bar')
 
2276
        self.assertEquals('bar', opt.get_default())
 
2277
 
 
2278
    def test_default_value_from_env(self):
 
2279
        opt = config.Option('foo', default='bar', default_from_env=['FOO'])
 
2280
        self.overrideEnv('FOO', 'quux')
 
2281
        # Env variable provides a default taking over the option one
 
2282
        self.assertEquals('quux', opt.get_default())
 
2283
 
 
2284
    def test_first_default_value_from_env_wins(self):
 
2285
        opt = config.Option('foo', default='bar',
 
2286
                            default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
 
2287
        self.overrideEnv('FOO', 'foo')
 
2288
        self.overrideEnv('BAZ', 'baz')
 
2289
        # The first env var set wins
 
2290
        self.assertEquals('foo', opt.get_default())
 
2291
 
 
2292
    def test_not_supported_list_default_value(self):
 
2293
        self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
 
2294
 
 
2295
    def test_not_supported_object_default_value(self):
 
2296
        self.assertRaises(AssertionError, config.Option, 'foo',
 
2297
                          default=object())
 
2298
 
 
2299
 
 
2300
class TestOptionConverterMixin(object):
 
2301
 
 
2302
    def assertConverted(self, expected, opt, value):
 
2303
        self.assertEquals(expected, opt.convert_from_unicode(value))
 
2304
 
 
2305
    def assertWarns(self, opt, value):
 
2306
        warnings = []
 
2307
        def warning(*args):
 
2308
            warnings.append(args[0] % args[1:])
 
2309
        self.overrideAttr(trace, 'warning', warning)
 
2310
        self.assertEquals(None, opt.convert_from_unicode(value))
 
2311
        self.assertLength(1, warnings)
 
2312
        self.assertEquals(
 
2313
            'Value "%s" is not valid for "%s"' % (value, opt.name),
 
2314
            warnings[0])
 
2315
 
 
2316
    def assertErrors(self, opt, value):
 
2317
        self.assertRaises(errors.ConfigOptionValueError,
 
2318
                          opt.convert_from_unicode, value)
 
2319
 
 
2320
    def assertConvertInvalid(self, opt, invalid_value):
 
2321
        opt.invalid = None
 
2322
        self.assertEquals(None, opt.convert_from_unicode(invalid_value))
 
2323
        opt.invalid = 'warning'
 
2324
        self.assertWarns(opt, invalid_value)
 
2325
        opt.invalid = 'error'
 
2326
        self.assertErrors(opt, invalid_value)
 
2327
 
 
2328
 
 
2329
class TestOptionWithBooleanConverter(tests.TestCase, TestOptionConverterMixin):
 
2330
 
 
2331
    def get_option(self):
 
2332
        return config.Option('foo', help='A boolean.',
 
2333
                             from_unicode=config.bool_from_store)
 
2334
 
 
2335
    def test_convert_invalid(self):
 
2336
        opt = self.get_option()
 
2337
        # A string that is not recognized as a boolean
 
2338
        self.assertConvertInvalid(opt, u'invalid-boolean')
 
2339
        # A list of strings is never recognized as a boolean
 
2340
        self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
 
2341
 
 
2342
    def test_convert_valid(self):
 
2343
        opt = self.get_option()
 
2344
        self.assertConverted(True, opt, u'True')
 
2345
        self.assertConverted(True, opt, u'1')
 
2346
        self.assertConverted(False, opt, u'False')
 
2347
 
 
2348
 
 
2349
class TestOptionWithIntegerConverter(tests.TestCase, TestOptionConverterMixin):
 
2350
 
 
2351
    def get_option(self):
 
2352
        return config.Option('foo', help='An integer.',
 
2353
                             from_unicode=config.int_from_store)
 
2354
 
 
2355
    def test_convert_invalid(self):
 
2356
        opt = self.get_option()
 
2357
        # A string that is not recognized as an integer
 
2358
        self.assertConvertInvalid(opt, u'forty-two')
 
2359
        # A list of strings is never recognized as an integer
 
2360
        self.assertConvertInvalid(opt, [u'a', u'list'])
 
2361
 
 
2362
    def test_convert_valid(self):
 
2363
        opt = self.get_option()
 
2364
        self.assertConverted(16, opt, u'16')
 
2365
 
 
2366
class TestOptionWithListConverter(tests.TestCase, TestOptionConverterMixin):
 
2367
 
 
2368
    def get_option(self):
 
2369
        return config.Option('foo', help='A list.',
 
2370
                             from_unicode=config.list_from_store)
 
2371
 
 
2372
    def test_convert_invalid(self):
 
2373
        # No string is invalid as all forms can be converted to a list
 
2374
        pass
 
2375
 
 
2376
    def test_convert_valid(self):
 
2377
        opt = self.get_option()
 
2378
        # An empty string is an empty list
 
2379
        self.assertConverted([], opt, '') # Using a bare str() just in case
 
2380
        self.assertConverted([], opt, u'')
 
2381
        # A boolean
 
2382
        self.assertConverted([u'True'], opt, u'True')
 
2383
        # An integer
 
2384
        self.assertConverted([u'42'], opt, u'42')
 
2385
        # A single string
 
2386
        self.assertConverted([u'bar'], opt, u'bar')
 
2387
        # A list remains a list (configObj will turn a string containing commas
 
2388
        # into a list, but that's not what we're testing here)
 
2389
        self.assertConverted([u'foo', u'1', u'True'],
 
2390
                             opt, [u'foo', u'1', u'True'])
 
2391
 
 
2392
 
 
2393
class TestOptionConverterMixin(object):
 
2394
 
 
2395
    def assertConverted(self, expected, opt, value):
 
2396
        self.assertEquals(expected, opt.convert_from_unicode(value))
 
2397
 
 
2398
    def assertWarns(self, opt, value):
 
2399
        warnings = []
 
2400
        def warning(*args):
 
2401
            warnings.append(args[0] % args[1:])
 
2402
        self.overrideAttr(trace, 'warning', warning)
 
2403
        self.assertEquals(None, opt.convert_from_unicode(value))
 
2404
        self.assertLength(1, warnings)
 
2405
        self.assertEquals(
 
2406
            'Value "%s" is not valid for "%s"' % (value, opt.name),
 
2407
            warnings[0])
 
2408
 
 
2409
    def assertErrors(self, opt, value):
 
2410
        self.assertRaises(errors.ConfigOptionValueError,
 
2411
                          opt.convert_from_unicode, value)
 
2412
 
 
2413
    def assertConvertInvalid(self, opt, invalid_value):
 
2414
        opt.invalid = None
 
2415
        self.assertEquals(None, opt.convert_from_unicode(invalid_value))
 
2416
        opt.invalid = 'warning'
 
2417
        self.assertWarns(opt, invalid_value)
 
2418
        opt.invalid = 'error'
 
2419
        self.assertErrors(opt, invalid_value)
 
2420
 
 
2421
 
 
2422
class TestOptionWithBooleanConverter(tests.TestCase, TestOptionConverterMixin):
 
2423
 
 
2424
    def get_option(self):
 
2425
        return config.Option('foo', help='A boolean.',
 
2426
                             from_unicode=config.bool_from_store)
 
2427
 
 
2428
    def test_convert_invalid(self):
 
2429
        opt = self.get_option()
 
2430
        # A string that is not recognized as a boolean
 
2431
        self.assertConvertInvalid(opt, u'invalid-boolean')
 
2432
        # A list of strings is never recognized as a boolean
 
2433
        self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
 
2434
 
 
2435
    def test_convert_valid(self):
 
2436
        opt = self.get_option()
 
2437
        self.assertConverted(True, opt, u'True')
 
2438
        self.assertConverted(True, opt, u'1')
 
2439
        self.assertConverted(False, opt, u'False')
 
2440
 
 
2441
 
 
2442
class TestOptionWithIntegerConverter(tests.TestCase, TestOptionConverterMixin):
 
2443
 
 
2444
    def get_option(self):
 
2445
        return config.Option('foo', help='An integer.',
 
2446
                             from_unicode=config.int_from_store)
 
2447
 
 
2448
    def test_convert_invalid(self):
 
2449
        opt = self.get_option()
 
2450
        # A string that is not recognized as an integer
 
2451
        self.assertConvertInvalid(opt, u'forty-two')
 
2452
        # A list of strings is never recognized as an integer
 
2453
        self.assertConvertInvalid(opt, [u'a', u'list'])
 
2454
 
 
2455
    def test_convert_valid(self):
 
2456
        opt = self.get_option()
 
2457
        self.assertConverted(16, opt, u'16')
 
2458
 
 
2459
 
 
2460
class TestOptionWithListConverter(tests.TestCase, TestOptionConverterMixin):
 
2461
 
 
2462
    def get_option(self):
 
2463
        return config.Option('foo', help='A list.',
 
2464
                             from_unicode=config.list_from_store)
 
2465
 
 
2466
    def test_convert_invalid(self):
 
2467
        opt = self.get_option()
 
2468
        # We don't even try to convert a list into a list, we only expect
 
2469
        # strings
 
2470
        self.assertConvertInvalid(opt, [1])
 
2471
        # No string is invalid as all forms can be converted to a list
 
2472
 
 
2473
    def test_convert_valid(self):
 
2474
        opt = self.get_option()
 
2475
        # An empty string is an empty list
 
2476
        self.assertConverted([], opt, '') # Using a bare str() just in case
 
2477
        self.assertConverted([], opt, u'')
 
2478
        # A boolean
 
2479
        self.assertConverted([u'True'], opt, u'True')
 
2480
        # An integer
 
2481
        self.assertConverted([u'42'], opt, u'42')
 
2482
        # A single string
 
2483
        self.assertConverted([u'bar'], opt, u'bar')
 
2484
 
 
2485
 
 
2486
class TestOptionRegistry(tests.TestCase):
 
2487
 
 
2488
    def setUp(self):
 
2489
        super(TestOptionRegistry, self).setUp()
 
2490
        # Always start with an empty registry
 
2491
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
2492
        self.registry = config.option_registry
 
2493
 
 
2494
    def test_register(self):
 
2495
        opt = config.Option('foo')
 
2496
        self.registry.register(opt)
 
2497
        self.assertIs(opt, self.registry.get('foo'))
 
2498
 
 
2499
    def test_registered_help(self):
 
2500
        opt = config.Option('foo', help='A simple option')
 
2501
        self.registry.register(opt)
 
2502
        self.assertEquals('A simple option', self.registry.get_help('foo'))
 
2503
 
 
2504
    lazy_option = config.Option('lazy_foo', help='Lazy help')
 
2505
 
 
2506
    def test_register_lazy(self):
 
2507
        self.registry.register_lazy('lazy_foo', self.__module__,
 
2508
                                    'TestOptionRegistry.lazy_option')
 
2509
        self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
 
2510
 
 
2511
    def test_registered_lazy_help(self):
 
2512
        self.registry.register_lazy('lazy_foo', self.__module__,
 
2513
                                    'TestOptionRegistry.lazy_option')
 
2514
        self.assertEquals('Lazy help', self.registry.get_help('lazy_foo'))
 
2515
 
 
2516
 
 
2517
class TestRegisteredOptions(tests.TestCase):
 
2518
    """All registered options should verify some constraints."""
 
2519
 
 
2520
    scenarios = [(key, {'option_name': key, 'option': option}) for key, option
 
2521
                 in config.option_registry.iteritems()]
 
2522
 
 
2523
    def setUp(self):
 
2524
        super(TestRegisteredOptions, self).setUp()
 
2525
        self.registry = config.option_registry
 
2526
 
 
2527
    def test_proper_name(self):
 
2528
        # An option should be registered under its own name, this can't be
 
2529
        # checked at registration time for the lazy ones.
 
2530
        self.assertEquals(self.option_name, self.option.name)
 
2531
 
 
2532
    def test_help_is_set(self):
 
2533
        option_help = self.registry.get_help(self.option_name)
 
2534
        self.assertNotEquals(None, option_help)
 
2535
        # Come on, think about the user, he really wants to know what the
 
2536
        # option is about
 
2537
        self.assertIsNot(None, option_help)
 
2538
        self.assertNotEquals('', option_help)
 
2539
 
 
2540
 
1841
2541
class TestSection(tests.TestCase):
1842
2542
 
1843
2543
    # FIXME: Parametrize so that all sections produced by Stores run these
1923
2623
 
1924
2624
class TestReadonlyStore(TestStore):
1925
2625
 
1926
 
    scenarios = [(key, {'get_store': builder})
1927
 
                 for key, builder in test_store_builder_registry.iteritems()]
1928
 
 
1929
 
    def setUp(self):
1930
 
        super(TestReadonlyStore, self).setUp()
1931
 
        self.branch = self.make_branch('branch')
 
2626
    scenarios = [(key, {'get_store': builder}) for key, builder
 
2627
                 in config.test_store_builder_registry.iteritems()]
1932
2628
 
1933
2629
    def test_building_delays_load(self):
1934
2630
        store = self.get_store(self)
1961
2657
        self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
1962
2658
 
1963
2659
 
 
2660
class TestIniFileStoreContent(tests.TestCaseWithTransport):
 
2661
    """Simulate loading a config store with content of various encodings.
 
2662
 
 
2663
    All files produced by bzr are in utf8 content.
 
2664
 
 
2665
    Users may modify them manually and end up with a file that can't be
 
2666
    loaded. We need to issue proper error messages in this case.
 
2667
    """
 
2668
 
 
2669
    invalid_utf8_char = '\xff'
 
2670
 
 
2671
    def test_load_utf8(self):
 
2672
        """Ensure we can load an utf8-encoded file."""
 
2673
        t = self.get_transport()
 
2674
        # From http://pad.lv/799212
 
2675
        unicode_user = u'b\N{Euro Sign}ar'
 
2676
        unicode_content = u'user=%s' % (unicode_user,)
 
2677
        utf8_content = unicode_content.encode('utf8')
 
2678
        # Store the raw content in the config file
 
2679
        t.put_bytes('foo.conf', utf8_content)
 
2680
        store = config.IniFileStore(t, 'foo.conf')
 
2681
        store.load()
 
2682
        stack = config.Stack([store.get_sections], store)
 
2683
        self.assertEquals(unicode_user, stack.get('user'))
 
2684
 
 
2685
    def test_load_non_ascii(self):
 
2686
        """Ensure we display a proper error on non-ascii, non utf-8 content."""
 
2687
        t = self.get_transport()
 
2688
        t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
 
2689
        store = config.IniFileStore(t, 'foo.conf')
 
2690
        self.assertRaises(errors.ConfigContentError, store.load)
 
2691
 
 
2692
    def test_load_erroneous_content(self):
 
2693
        """Ensure we display a proper error on content that can't be parsed."""
 
2694
        t = self.get_transport()
 
2695
        t.put_bytes('foo.conf', '[open_section\n')
 
2696
        store = config.IniFileStore(t, 'foo.conf')
 
2697
        self.assertRaises(errors.ParseConfigError, store.load)
 
2698
 
 
2699
    def test_load_permission_denied(self):
 
2700
        """Ensure we get warned when trying to load an inaccessible file."""
 
2701
        warnings = []
 
2702
        def warning(*args):
 
2703
            warnings.append(args[0] % args[1:])
 
2704
        self.overrideAttr(trace, 'warning', warning)
 
2705
 
 
2706
        t = self.get_transport()
 
2707
 
 
2708
        def get_bytes(relpath):
 
2709
            raise errors.PermissionDenied(relpath, "")
 
2710
        t.get_bytes = get_bytes
 
2711
        store = config.IniFileStore(t, 'foo.conf')
 
2712
        self.assertRaises(errors.PermissionDenied, store.load)
 
2713
        self.assertEquals(
 
2714
            warnings,
 
2715
            [u'Permission denied while trying to load configuration store %s.'
 
2716
             % store.external_url()])
 
2717
 
 
2718
 
 
2719
class TestIniConfigContent(tests.TestCaseWithTransport):
 
2720
    """Simulate loading a IniBasedConfig with content of various encodings.
 
2721
 
 
2722
    All files produced by bzr are in utf8 content.
 
2723
 
 
2724
    Users may modify them manually and end up with a file that can't be
 
2725
    loaded. We need to issue proper error messages in this case.
 
2726
    """
 
2727
 
 
2728
    invalid_utf8_char = '\xff'
 
2729
 
 
2730
    def test_load_utf8(self):
 
2731
        """Ensure we can load an utf8-encoded file."""
 
2732
        # From http://pad.lv/799212
 
2733
        unicode_user = u'b\N{Euro Sign}ar'
 
2734
        unicode_content = u'user=%s' % (unicode_user,)
 
2735
        utf8_content = unicode_content.encode('utf8')
 
2736
        # Store the raw content in the config file
 
2737
        with open('foo.conf', 'wb') as f:
 
2738
            f.write(utf8_content)
 
2739
        conf = config.IniBasedConfig(file_name='foo.conf')
 
2740
        self.assertEquals(unicode_user, conf.get_user_option('user'))
 
2741
 
 
2742
    def test_load_badly_encoded_content(self):
 
2743
        """Ensure we display a proper error on non-ascii, non utf-8 content."""
 
2744
        with open('foo.conf', 'wb') as f:
 
2745
            f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
 
2746
        conf = config.IniBasedConfig(file_name='foo.conf')
 
2747
        self.assertRaises(errors.ConfigContentError, conf._get_parser)
 
2748
 
 
2749
    def test_load_erroneous_content(self):
 
2750
        """Ensure we display a proper error on content that can't be parsed."""
 
2751
        with open('foo.conf', 'wb') as f:
 
2752
            f.write('[open_section\n')
 
2753
        conf = config.IniBasedConfig(file_name='foo.conf')
 
2754
        self.assertRaises(errors.ParseConfigError, conf._get_parser)
 
2755
 
 
2756
 
1964
2757
class TestMutableStore(TestStore):
1965
2758
 
1966
 
    scenarios = [(key, {'store_id': key, 'get_store': builder})
1967
 
                 for key, builder in test_store_builder_registry.iteritems()]
 
2759
    scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
 
2760
                 in config.test_store_builder_registry.iteritems()]
1968
2761
 
1969
2762
    def setUp(self):
1970
2763
        super(TestMutableStore, self).setUp()
1971
2764
        self.transport = self.get_transport()
1972
 
        self.branch = self.make_branch('branch')
1973
2765
 
1974
2766
    def has_store(self, store):
1975
2767
        store_basename = urlutils.relative_url(self.transport.external_url(),
1977
2769
        return self.transport.has(store_basename)
1978
2770
 
1979
2771
    def test_save_empty_creates_no_file(self):
1980
 
        if self.store_id == 'branch':
 
2772
        # FIXME: There should be a better way than relying on the test
 
2773
        # parametrization to identify branch.conf -- vila 2011-0526
 
2774
        if self.store_id in ('branch', 'remote_branch'):
1981
2775
            raise tests.TestNotApplicable(
1982
2776
                'branch.conf is *always* created when a branch is initialized')
1983
2777
        store = self.get_store(self)
1996
2790
        self.assertLength(0, sections)
1997
2791
 
1998
2792
    def test_save_with_content_succeeds(self):
1999
 
        if self.store_id == 'branch':
 
2793
        # FIXME: There should be a better way than relying on the test
 
2794
        # parametrization to identify branch.conf -- vila 2011-0526
 
2795
        if self.store_id in ('branch', 'remote_branch'):
2000
2796
            raise tests.TestNotApplicable(
2001
2797
                'branch.conf is *always* created when a branch is initialized')
2002
2798
        store = self.get_store(self)
2041
2837
        self.assertLength(1, sections)
2042
2838
        self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2043
2839
 
 
2840
    def test_load_hook(self):
 
2841
        # We first needs to ensure that the store exists
 
2842
        store = self.get_store(self)
 
2843
        section = store.get_mutable_section('baz')
 
2844
        section.set('foo', 'bar')
 
2845
        store.save()
 
2846
        # Now we can try to load it
 
2847
        store = self.get_store(self)
 
2848
        calls = []
 
2849
        def hook(*args):
 
2850
            calls.append(args)
 
2851
        config.ConfigHooks.install_named_hook('load', hook, None)
 
2852
        self.assertLength(0, calls)
 
2853
        store.load()
 
2854
        self.assertLength(1, calls)
 
2855
        self.assertEquals((store,), calls[0])
 
2856
 
 
2857
    def test_save_hook(self):
 
2858
        calls = []
 
2859
        def hook(*args):
 
2860
            calls.append(args)
 
2861
        config.ConfigHooks.install_named_hook('save', hook, None)
 
2862
        self.assertLength(0, calls)
 
2863
        store = self.get_store(self)
 
2864
        section = store.get_mutable_section('baz')
 
2865
        section.set('foo', 'bar')
 
2866
        store.save()
 
2867
        self.assertLength(1, calls)
 
2868
        self.assertEquals((store,), calls[0])
 
2869
 
2044
2870
 
2045
2871
class TestIniFileStore(TestStore):
2046
2872
 
2079
2905
        sections = list(store.get_sections())
2080
2906
        self.assertLength(4, sections)
2081
2907
        # The default section has no name.
2082
 
        # List values are provided as lists
2083
 
        self.assertSectionContent((None, {'foo': 'bar', 'l': ['1', '2']}),
 
2908
        # List values are provided as strings and need to be explicitly
 
2909
        # converted by specifying from_unicode=list_from_store at option
 
2910
        # registration
 
2911
        self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
2084
2912
                                  sections[0])
2085
2913
        self.assertSectionContent(
2086
2914
            ('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
2095
2923
class TestLockableIniFileStore(TestStore):
2096
2924
 
2097
2925
    def test_create_store_in_created_dir(self):
 
2926
        self.assertPathDoesNotExist('dir')
2098
2927
        t = self.get_transport('dir/subdir')
2099
2928
        store = config.LockableIniFileStore(t, 'foo.conf')
2100
2929
        store.get_mutable_section(None).set('foo', 'bar')
2101
2930
        store.save()
2102
 
 
2103
 
    # FIXME: We should adapt the tests in TestLockableConfig about concurrent
2104
 
    # writes. Since this requires a clearer rewrite, I'll just rely on using
2105
 
    # the same code in LockableIniFileStore (copied from LockableConfig, but
2106
 
    # trivial enough, the main difference is that we add @needs_write_lock on
2107
 
    # save() instead of set_user_option() and remove_user_option()). The intent
2108
 
    # is to ensure that we always get a valid content for the store even when
2109
 
    # concurrent accesses occur, read/write, write/write. It may be worth
2110
 
    # looking into removing the lock dir when it;s not needed anymore and look
2111
 
    # at possible fallouts for concurrent lockers -- vila 20110-04-06
 
2931
        self.assertPathExists('dir/subdir')
 
2932
 
 
2933
 
 
2934
class TestConcurrentStoreUpdates(TestStore):
 
2935
    """Test that Stores properly handle conccurent updates.
 
2936
 
 
2937
    New Store implementation may fail some of these tests but until such
 
2938
    implementations exist it's hard to properly filter them from the scenarios
 
2939
    applied here. If you encounter such a case, contact the bzr devs.
 
2940
    """
 
2941
 
 
2942
    scenarios = [(key, {'get_stack': builder}) for key, builder
 
2943
                 in config.test_stack_builder_registry.iteritems()]
 
2944
 
 
2945
    def setUp(self):
 
2946
        super(TestConcurrentStoreUpdates, self).setUp()
 
2947
        self._content = 'one=1\ntwo=2\n'
 
2948
        self.stack = self.get_stack(self)
 
2949
        if not isinstance(self.stack, config._CompatibleStack):
 
2950
            raise tests.TestNotApplicable(
 
2951
                '%s is not meant to be compatible with the old config design'
 
2952
                % (self.stack,))
 
2953
        self.stack.store._load_from_string(self._content)
 
2954
        # Flush the store
 
2955
        self.stack.store.save()
 
2956
 
 
2957
    def test_simple_read_access(self):
 
2958
        self.assertEquals('1', self.stack.get('one'))
 
2959
 
 
2960
    def test_simple_write_access(self):
 
2961
        self.stack.set('one', 'one')
 
2962
        self.assertEquals('one', self.stack.get('one'))
 
2963
 
 
2964
    def test_listen_to_the_last_speaker(self):
 
2965
        c1 = self.stack
 
2966
        c2 = self.get_stack(self)
 
2967
        c1.set('one', 'ONE')
 
2968
        c2.set('two', 'TWO')
 
2969
        self.assertEquals('ONE', c1.get('one'))
 
2970
        self.assertEquals('TWO', c2.get('two'))
 
2971
        # The second update respect the first one
 
2972
        self.assertEquals('ONE', c2.get('one'))
 
2973
 
 
2974
    def test_last_speaker_wins(self):
 
2975
        # If the same config is not shared, the same variable modified twice
 
2976
        # can only see a single result.
 
2977
        c1 = self.stack
 
2978
        c2 = self.get_stack(self)
 
2979
        c1.set('one', 'c1')
 
2980
        c2.set('one', 'c2')
 
2981
        self.assertEquals('c2', c2.get('one'))
 
2982
        # The first modification is still available until another refresh
 
2983
        # occur
 
2984
        self.assertEquals('c1', c1.get('one'))
 
2985
        c1.set('two', 'done')
 
2986
        self.assertEquals('c2', c1.get('one'))
 
2987
 
 
2988
    def test_writes_are_serialized(self):
 
2989
        c1 = self.stack
 
2990
        c2 = self.get_stack(self)
 
2991
 
 
2992
        # We spawn a thread that will pause *during* the config saving.
 
2993
        before_writing = threading.Event()
 
2994
        after_writing = threading.Event()
 
2995
        writing_done = threading.Event()
 
2996
        c1_save_without_locking_orig = c1.store.save_without_locking
 
2997
        def c1_save_without_locking():
 
2998
            before_writing.set()
 
2999
            c1_save_without_locking_orig()
 
3000
            # The lock is held. We wait for the main thread to decide when to
 
3001
            # continue
 
3002
            after_writing.wait()
 
3003
        c1.store.save_without_locking = c1_save_without_locking
 
3004
        def c1_set():
 
3005
            c1.set('one', 'c1')
 
3006
            writing_done.set()
 
3007
        t1 = threading.Thread(target=c1_set)
 
3008
        # Collect the thread after the test
 
3009
        self.addCleanup(t1.join)
 
3010
        # Be ready to unblock the thread if the test goes wrong
 
3011
        self.addCleanup(after_writing.set)
 
3012
        t1.start()
 
3013
        before_writing.wait()
 
3014
        self.assertRaises(errors.LockContention,
 
3015
                          c2.set, 'one', 'c2')
 
3016
        self.assertEquals('c1', c1.get('one'))
 
3017
        # Let the lock be released
 
3018
        after_writing.set()
 
3019
        writing_done.wait()
 
3020
        c2.set('one', 'c2')
 
3021
        self.assertEquals('c2', c2.get('one'))
 
3022
 
 
3023
    def test_read_while_writing(self):
 
3024
       c1 = self.stack
 
3025
       # We spawn a thread that will pause *during* the write
 
3026
       ready_to_write = threading.Event()
 
3027
       do_writing = threading.Event()
 
3028
       writing_done = threading.Event()
 
3029
       # We override the _save implementation so we know the store is locked
 
3030
       c1_save_without_locking_orig = c1.store.save_without_locking
 
3031
       def c1_save_without_locking():
 
3032
           ready_to_write.set()
 
3033
           # The lock is held. We wait for the main thread to decide when to
 
3034
           # continue
 
3035
           do_writing.wait()
 
3036
           c1_save_without_locking_orig()
 
3037
           writing_done.set()
 
3038
       c1.store.save_without_locking = c1_save_without_locking
 
3039
       def c1_set():
 
3040
           c1.set('one', 'c1')
 
3041
       t1 = threading.Thread(target=c1_set)
 
3042
       # Collect the thread after the test
 
3043
       self.addCleanup(t1.join)
 
3044
       # Be ready to unblock the thread if the test goes wrong
 
3045
       self.addCleanup(do_writing.set)
 
3046
       t1.start()
 
3047
       # Ensure the thread is ready to write
 
3048
       ready_to_write.wait()
 
3049
       self.assertEquals('c1', c1.get('one'))
 
3050
       # If we read during the write, we get the old value
 
3051
       c2 = self.get_stack(self)
 
3052
       self.assertEquals('1', c2.get('one'))
 
3053
       # Let the writing occur and ensure it occurred
 
3054
       do_writing.set()
 
3055
       writing_done.wait()
 
3056
       # Now we get the updated value
 
3057
       c3 = self.get_stack(self)
 
3058
       self.assertEquals('c1', c3.get('one'))
 
3059
 
 
3060
    # FIXME: It may be worth looking into removing the lock dir when it's not
 
3061
    # needed anymore and look at possible fallouts for concurrent lockers. This
 
3062
    # will matter if/when we use config files outside of bazaar directories
 
3063
    # (.bazaar or .bzr) -- vila 20110-04-111
2112
3064
 
2113
3065
 
2114
3066
class TestSectionMatcher(TestStore):
2115
3067
 
2116
 
    scenarios = [('location', {'matcher': config.LocationMatcher})]
 
3068
    scenarios = [('location', {'matcher': config.LocationMatcher}),
 
3069
                 ('id', {'matcher': config.NameMatcher}),]
2117
3070
 
2118
3071
    def get_store(self, file_name):
2119
3072
        return config.IniFileStore(self.get_readonly_transport(), file_name)
2158
3111
    def get_store(self, file_name):
2159
3112
        return config.IniFileStore(self.get_readonly_transport(), file_name)
2160
3113
 
 
3114
    def test_unrelated_section_excluded(self):
 
3115
        store = self.get_store('foo.conf')
 
3116
        store._load_from_string('''
 
3117
[/foo]
 
3118
section=/foo
 
3119
[/foo/baz]
 
3120
section=/foo/baz
 
3121
[/foo/bar]
 
3122
section=/foo/bar
 
3123
[/foo/bar/baz]
 
3124
section=/foo/bar/baz
 
3125
[/quux/quux]
 
3126
section=/quux/quux
 
3127
''')
 
3128
        self.assertEquals(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
 
3129
                           '/quux/quux'],
 
3130
                          [section.id for section in store.get_sections()])
 
3131
        matcher = config.LocationMatcher(store, '/foo/bar/quux')
 
3132
        sections = list(matcher.get_sections())
 
3133
        self.assertEquals([3, 2],
 
3134
                          [section.length for section in sections])
 
3135
        self.assertEquals(['/foo/bar', '/foo'],
 
3136
                          [section.id for section in sections])
 
3137
        self.assertEquals(['quux', 'bar/quux'],
 
3138
                          [section.extra_path for section in sections])
 
3139
 
2161
3140
    def test_more_specific_sections_first(self):
2162
3141
        store = self.get_store('foo.conf')
2163
3142
        store._load_from_string('''
2177
3156
        self.assertEquals(['baz', 'bar/baz'],
2178
3157
                          [section.extra_path for section in sections])
2179
3158
 
 
3159
    def test_appendpath_in_no_name_section(self):
 
3160
        # It's a bit weird to allow appendpath in a no-name section, but
 
3161
        # someone may found a use for it
 
3162
        store = self.get_store('foo.conf')
 
3163
        store._load_from_string('''
 
3164
foo=bar
 
3165
foo:policy = appendpath
 
3166
''')
 
3167
        matcher = config.LocationMatcher(store, 'dir/subdir')
 
3168
        sections = list(matcher.get_sections())
 
3169
        self.assertLength(1, sections)
 
3170
        self.assertEquals('bar/dir/subdir', sections[0].get('foo'))
 
3171
 
 
3172
    def test_file_urls_are_normalized(self):
 
3173
        store = self.get_store('foo.conf')
 
3174
        if sys.platform == 'win32':
 
3175
            expected_url = 'file:///C:/dir/subdir'
 
3176
            expected_location = 'C:/dir/subdir'
 
3177
        else:
 
3178
            expected_url = 'file:///dir/subdir'
 
3179
            expected_location = '/dir/subdir'
 
3180
        matcher = config.LocationMatcher(store, expected_url)
 
3181
        self.assertEquals(expected_location, matcher.location)
 
3182
 
 
3183
 
 
3184
class TestNameMatcher(TestStore):
 
3185
 
 
3186
    def setUp(self):
 
3187
        super(TestNameMatcher, self).setUp()
 
3188
        self.store = config.IniFileStore(self.get_readonly_transport(),
 
3189
                                         'foo.conf')
 
3190
        self.store._load_from_string('''
 
3191
[foo]
 
3192
option=foo
 
3193
[foo/baz]
 
3194
option=foo/baz
 
3195
[bar]
 
3196
option=bar
 
3197
''')
 
3198
 
 
3199
    def get_matching_sections(self, name):
 
3200
        matcher = config.NameMatcher(self.store, name)
 
3201
        return list(matcher.get_sections())
 
3202
 
 
3203
    def test_matching(self):
 
3204
        sections = self.get_matching_sections('foo')
 
3205
        self.assertLength(1, sections)
 
3206
        self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
 
3207
 
 
3208
    def test_not_matching(self):
 
3209
        sections = self.get_matching_sections('baz')
 
3210
        self.assertLength(0, sections)
2180
3211
 
2181
3212
 
2182
3213
class TestStackGet(tests.TestCase):
2184
3215
    # FIXME: This should be parametrized for all known Stack or dedicated
2185
3216
    # paramerized tests created to avoid bloating -- vila 2011-03-31
2186
3217
 
 
3218
    def overrideOptionRegistry(self):
 
3219
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
3220
 
2187
3221
    def test_single_config_get(self):
2188
3222
        conf = dict(foo='bar')
2189
3223
        conf_stack = config.Stack([conf])
2190
3224
        self.assertEquals('bar', conf_stack.get('foo'))
2191
3225
 
 
3226
    def test_get_with_registered_default_value(self):
 
3227
        conf_stack = config.Stack([dict()])
 
3228
        opt = config.Option('foo', default='bar')
 
3229
        self.overrideOptionRegistry()
 
3230
        config.option_registry.register('foo', opt)
 
3231
        self.assertEquals('bar', conf_stack.get('foo'))
 
3232
 
 
3233
    def test_get_without_registered_default_value(self):
 
3234
        conf_stack = config.Stack([dict()])
 
3235
        opt = config.Option('foo')
 
3236
        self.overrideOptionRegistry()
 
3237
        config.option_registry.register('foo', opt)
 
3238
        self.assertEquals(None, conf_stack.get('foo'))
 
3239
 
 
3240
    def test_get_without_default_value_for_not_registered(self):
 
3241
        conf_stack = config.Stack([dict()])
 
3242
        opt = config.Option('foo')
 
3243
        self.overrideOptionRegistry()
 
3244
        self.assertEquals(None, conf_stack.get('foo'))
 
3245
 
2192
3246
    def test_get_first_definition(self):
2193
3247
        conf1 = dict(foo='bar')
2194
3248
        conf2 = dict(foo='baz')
2201
3255
        conf_stack = config.Stack([conf1, conf2])
2202
3256
        self.assertEquals('baz', conf_stack.get('foo'))
2203
3257
 
2204
 
    def test_get_for_empty_stack(self):
2205
 
        conf_stack = config.Stack([])
2206
 
        self.assertEquals(None, conf_stack.get('foo'))
2207
 
 
2208
3258
    def test_get_for_empty_section_callable(self):
2209
3259
        conf_stack = config.Stack([lambda : []])
2210
3260
        self.assertEquals(None, conf_stack.get('foo'))
2215
3265
        self.assertRaises(TypeError, conf_stack.get, 'foo')
2216
3266
 
2217
3267
 
2218
 
class TestStackSet(tests.TestCaseWithTransport):
2219
 
 
2220
 
    # FIXME: This should be parametrized for all known Stack or dedicated
2221
 
    # paramerized tests created to avoid bloating -- vila 2011-04-05
 
3268
class TestStackWithTransport(tests.TestCaseWithTransport):
 
3269
 
 
3270
    scenarios = [(key, {'get_stack': builder}) for key, builder
 
3271
                 in config.test_stack_builder_registry.iteritems()]
 
3272
 
 
3273
 
 
3274
class TestConcreteStacks(TestStackWithTransport):
 
3275
 
 
3276
    def test_build_stack(self):
 
3277
        # Just a smoke test to help debug builders
 
3278
        stack = self.get_stack(self)
 
3279
 
 
3280
 
 
3281
class TestStackGet(TestStackWithTransport):
 
3282
 
 
3283
    def setUp(self):
 
3284
        super(TestStackGet, self).setUp()
 
3285
        self.conf = self.get_stack(self)
 
3286
 
 
3287
    def test_get_for_empty_stack(self):
 
3288
        self.assertEquals(None, self.conf.get('foo'))
 
3289
 
 
3290
    def test_get_hook(self):
 
3291
        self.conf.store._load_from_string('foo=bar')
 
3292
        calls = []
 
3293
        def hook(*args):
 
3294
            calls.append(args)
 
3295
        config.ConfigHooks.install_named_hook('get', hook, None)
 
3296
        self.assertLength(0, calls)
 
3297
        value = self.conf.get('foo')
 
3298
        self.assertEquals('bar', value)
 
3299
        self.assertLength(1, calls)
 
3300
        self.assertEquals((self.conf, 'foo', 'bar'), calls[0])
 
3301
 
 
3302
 
 
3303
class TestStackGetWithConverter(TestStackGet):
 
3304
 
 
3305
    def setUp(self):
 
3306
        super(TestStackGetWithConverter, self).setUp()
 
3307
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
3308
        self.registry = config.option_registry
 
3309
 
 
3310
    def register_bool_option(self, name, default=None, default_from_env=None):
 
3311
        b = config.Option(name, help='A boolean.',
 
3312
                          default=default, default_from_env=default_from_env,
 
3313
                          from_unicode=config.bool_from_store)
 
3314
        self.registry.register(b)
 
3315
 
 
3316
    def test_get_default_bool_None(self):
 
3317
        self.register_bool_option('foo')
 
3318
        self.assertEquals(None, self.conf.get('foo'))
 
3319
 
 
3320
    def test_get_default_bool_True(self):
 
3321
        self.register_bool_option('foo', u'True')
 
3322
        self.assertEquals(True, self.conf.get('foo'))
 
3323
 
 
3324
    def test_get_default_bool_False(self):
 
3325
        self.register_bool_option('foo', False)
 
3326
        self.assertEquals(False, self.conf.get('foo'))
 
3327
 
 
3328
    def test_get_default_bool_False_as_string(self):
 
3329
        self.register_bool_option('foo', u'False')
 
3330
        self.assertEquals(False, self.conf.get('foo'))
 
3331
 
 
3332
    def test_get_default_bool_from_env_converted(self):
 
3333
        self.register_bool_option('foo', u'True', default_from_env=['FOO'])
 
3334
        self.overrideEnv('FOO', 'False')
 
3335
        self.assertEquals(False, self.conf.get('foo'))
 
3336
 
 
3337
    def test_get_default_bool_when_conversion_fails(self):
 
3338
        self.register_bool_option('foo', default='True')
 
3339
        self.conf.store._load_from_string('foo=invalid boolean')
 
3340
        self.assertEquals(True, self.conf.get('foo'))
 
3341
 
 
3342
    def register_integer_option(self, name,
 
3343
                                default=None, default_from_env=None):
 
3344
        i = config.Option(name, help='An integer.',
 
3345
                          default=default, default_from_env=default_from_env,
 
3346
                          from_unicode=config.int_from_store)
 
3347
        self.registry.register(i)
 
3348
 
 
3349
    def test_get_default_integer_None(self):
 
3350
        self.register_integer_option('foo')
 
3351
        self.assertEquals(None, self.conf.get('foo'))
 
3352
 
 
3353
    def test_get_default_integer(self):
 
3354
        self.register_integer_option('foo', 42)
 
3355
        self.assertEquals(42, self.conf.get('foo'))
 
3356
 
 
3357
    def test_get_default_integer_as_string(self):
 
3358
        self.register_integer_option('foo', u'42')
 
3359
        self.assertEquals(42, self.conf.get('foo'))
 
3360
 
 
3361
    def test_get_default_integer_from_env(self):
 
3362
        self.register_integer_option('foo', default_from_env=['FOO'])
 
3363
        self.overrideEnv('FOO', '18')
 
3364
        self.assertEquals(18, self.conf.get('foo'))
 
3365
 
 
3366
    def test_get_default_integer_when_conversion_fails(self):
 
3367
        self.register_integer_option('foo', default='12')
 
3368
        self.conf.store._load_from_string('foo=invalid integer')
 
3369
        self.assertEquals(12, self.conf.get('foo'))
 
3370
 
 
3371
    def register_list_option(self, name, default=None, default_from_env=None):
 
3372
        l = config.Option(name, help='A list.',
 
3373
                          default=default, default_from_env=default_from_env,
 
3374
                          from_unicode=config.list_from_store)
 
3375
        self.registry.register(l)
 
3376
 
 
3377
    def test_get_default_list_None(self):
 
3378
        self.register_list_option('foo')
 
3379
        self.assertEquals(None, self.conf.get('foo'))
 
3380
 
 
3381
    def test_get_default_list_empty(self):
 
3382
        self.register_list_option('foo', '')
 
3383
        self.assertEquals([], self.conf.get('foo'))
 
3384
 
 
3385
    def test_get_default_list_from_env(self):
 
3386
        self.register_list_option('foo', default_from_env=['FOO'])
 
3387
        self.overrideEnv('FOO', '')
 
3388
        self.assertEquals([], self.conf.get('foo'))
 
3389
 
 
3390
    def test_get_with_list_converter_no_item(self):
 
3391
        self.register_list_option('foo', None)
 
3392
        self.conf.store._load_from_string('foo=,')
 
3393
        self.assertEquals([], self.conf.get('foo'))
 
3394
 
 
3395
    def test_get_with_list_converter_many_items(self):
 
3396
        self.register_list_option('foo', None)
 
3397
        self.conf.store._load_from_string('foo=m,o,r,e')
 
3398
        self.assertEquals(['m', 'o', 'r', 'e'], self.conf.get('foo'))
 
3399
 
 
3400
    def test_get_with_list_converter_embedded_spaces_many_items(self):
 
3401
        self.register_list_option('foo', None)
 
3402
        self.conf.store._load_from_string('foo=" bar", "baz "')
 
3403
        self.assertEquals([' bar', 'baz '], self.conf.get('foo'))
 
3404
 
 
3405
    def test_get_with_list_converter_stripped_spaces_many_items(self):
 
3406
        self.register_list_option('foo', None)
 
3407
        self.conf.store._load_from_string('foo= bar ,  baz ')
 
3408
        self.assertEquals(['bar', 'baz'], self.conf.get('foo'))
 
3409
 
 
3410
 
 
3411
class TestStackExpandOptions(tests.TestCaseWithTransport):
 
3412
 
 
3413
    def setUp(self):
 
3414
        super(TestStackExpandOptions, self).setUp()
 
3415
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
3416
        self.registry = config.option_registry
 
3417
        self.conf = build_branch_stack(self)
 
3418
 
 
3419
    def assertExpansion(self, expected, string, env=None):
 
3420
        self.assertEquals(expected, self.conf.expand_options(string, env))
 
3421
 
 
3422
    def test_no_expansion(self):
 
3423
        self.assertExpansion('foo', 'foo')
 
3424
 
 
3425
    def test_expand_default_value(self):
 
3426
        self.conf.store._load_from_string('bar=baz')
 
3427
        self.registry.register(config.Option('foo', default=u'{bar}'))
 
3428
        self.assertEquals('baz', self.conf.get('foo', expand=True))
 
3429
 
 
3430
    def test_expand_default_from_env(self):
 
3431
        self.conf.store._load_from_string('bar=baz')
 
3432
        self.registry.register(config.Option('foo', default_from_env=['FOO']))
 
3433
        self.overrideEnv('FOO', '{bar}')
 
3434
        self.assertEquals('baz', self.conf.get('foo', expand=True))
 
3435
 
 
3436
    def test_expand_default_on_failed_conversion(self):
 
3437
        self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
 
3438
        self.registry.register(
 
3439
            config.Option('foo', default=u'{bar}',
 
3440
                          from_unicode=config.int_from_store))
 
3441
        self.assertEquals(42, self.conf.get('foo', expand=True))
 
3442
 
 
3443
    def test_env_adding_options(self):
 
3444
        self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
 
3445
 
 
3446
    def test_env_overriding_options(self):
 
3447
        self.conf.store._load_from_string('foo=baz')
 
3448
        self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
 
3449
 
 
3450
    def test_simple_ref(self):
 
3451
        self.conf.store._load_from_string('foo=xxx')
 
3452
        self.assertExpansion('xxx', '{foo}')
 
3453
 
 
3454
    def test_unknown_ref(self):
 
3455
        self.assertRaises(errors.ExpandingUnknownOption,
 
3456
                          self.conf.expand_options, '{foo}')
 
3457
 
 
3458
    def test_indirect_ref(self):
 
3459
        self.conf.store._load_from_string('''
 
3460
foo=xxx
 
3461
bar={foo}
 
3462
''')
 
3463
        self.assertExpansion('xxx', '{bar}')
 
3464
 
 
3465
    def test_embedded_ref(self):
 
3466
        self.conf.store._load_from_string('''
 
3467
foo=xxx
 
3468
bar=foo
 
3469
''')
 
3470
        self.assertExpansion('xxx', '{{bar}}')
 
3471
 
 
3472
    def test_simple_loop(self):
 
3473
        self.conf.store._load_from_string('foo={foo}')
 
3474
        self.assertRaises(errors.OptionExpansionLoop,
 
3475
                          self.conf.expand_options, '{foo}')
 
3476
 
 
3477
    def test_indirect_loop(self):
 
3478
        self.conf.store._load_from_string('''
 
3479
foo={bar}
 
3480
bar={baz}
 
3481
baz={foo}''')
 
3482
        e = self.assertRaises(errors.OptionExpansionLoop,
 
3483
                              self.conf.expand_options, '{foo}')
 
3484
        self.assertEquals('foo->bar->baz', e.refs)
 
3485
        self.assertEquals('{foo}', e.string)
 
3486
 
 
3487
    def test_list(self):
 
3488
        self.conf.store._load_from_string('''
 
3489
foo=start
 
3490
bar=middle
 
3491
baz=end
 
3492
list={foo},{bar},{baz}
 
3493
''')
 
3494
        self.registry.register(
 
3495
            config.Option('list', from_unicode=config.list_from_store))
 
3496
        self.assertEquals(['start', 'middle', 'end'],
 
3497
                           self.conf.get('list', expand=True))
 
3498
 
 
3499
    def test_cascading_list(self):
 
3500
        self.conf.store._load_from_string('''
 
3501
foo=start,{bar}
 
3502
bar=middle,{baz}
 
3503
baz=end
 
3504
list={foo}
 
3505
''')
 
3506
        self.registry.register(
 
3507
            config.Option('list', from_unicode=config.list_from_store))
 
3508
        self.assertEquals(['start', 'middle', 'end'],
 
3509
                           self.conf.get('list', expand=True))
 
3510
 
 
3511
    def test_pathologically_hidden_list(self):
 
3512
        self.conf.store._load_from_string('''
 
3513
foo=bin
 
3514
bar=go
 
3515
start={foo
 
3516
middle=},{
 
3517
end=bar}
 
3518
hidden={start}{middle}{end}
 
3519
''')
 
3520
        # What matters is what the registration says, the conversion happens
 
3521
        # only after all expansions have been performed
 
3522
        self.registry.register(
 
3523
            config.Option('hidden', from_unicode=config.list_from_store))
 
3524
        self.assertEquals(['bin', 'go'],
 
3525
                          self.conf.get('hidden', expand=True))
 
3526
 
 
3527
 
 
3528
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
 
3529
 
 
3530
    def setUp(self):
 
3531
        super(TestStackCrossSectionsExpand, self).setUp()
 
3532
 
 
3533
    def get_config(self, location, string):
 
3534
        if string is None:
 
3535
            string = ''
 
3536
        # Since we don't save the config we won't strictly require to inherit
 
3537
        # from TestCaseInTempDir, but an error occurs so quickly...
 
3538
        c = config.LocationStack(location)
 
3539
        c.store._load_from_string(string)
 
3540
        return c
 
3541
 
 
3542
    def test_dont_cross_unrelated_section(self):
 
3543
        c = self.get_config('/another/branch/path','''
 
3544
[/one/branch/path]
 
3545
foo = hello
 
3546
bar = {foo}/2
 
3547
 
 
3548
[/another/branch/path]
 
3549
bar = {foo}/2
 
3550
''')
 
3551
        self.assertRaises(errors.ExpandingUnknownOption,
 
3552
                          c.get, 'bar', expand=True)
 
3553
 
 
3554
    def test_cross_related_sections(self):
 
3555
        c = self.get_config('/project/branch/path','''
 
3556
[/project]
 
3557
foo = qu
 
3558
 
 
3559
[/project/branch/path]
 
3560
bar = {foo}ux
 
3561
''')
 
3562
        self.assertEquals('quux', c.get('bar', expand=True))
 
3563
 
 
3564
 
 
3565
class TestStackSet(TestStackWithTransport):
2222
3566
 
2223
3567
    def test_simple_set(self):
2224
 
        store = config.IniFileStore(self.get_transport(), 'test.conf')
2225
 
        store._load_from_string('foo=bar')
2226
 
        conf = config.Stack([store.get_sections], store)
 
3568
        conf = self.get_stack(self)
 
3569
        conf.store._load_from_string('foo=bar')
2227
3570
        self.assertEquals('bar', conf.get('foo'))
2228
3571
        conf.set('foo', 'baz')
2229
3572
        # Did we get it back ?
2230
3573
        self.assertEquals('baz', conf.get('foo'))
2231
3574
 
2232
3575
    def test_set_creates_a_new_section(self):
2233
 
        store = config.IniFileStore(self.get_transport(), 'test.conf')
2234
 
        conf = config.Stack([store.get_sections], store)
 
3576
        conf = self.get_stack(self)
2235
3577
        conf.set('foo', 'baz')
2236
3578
        self.assertEquals, 'baz', conf.get('foo')
2237
3579
 
2238
 
 
2239
 
class TestStackRemove(tests.TestCaseWithTransport):
2240
 
 
2241
 
    # FIXME: This should be parametrized for all known Stack or dedicated
2242
 
    # paramerized tests created to avoid bloating -- vila 2011-04-06
 
3580
    def test_set_hook(self):
 
3581
        calls = []
 
3582
        def hook(*args):
 
3583
            calls.append(args)
 
3584
        config.ConfigHooks.install_named_hook('set', hook, None)
 
3585
        self.assertLength(0, calls)
 
3586
        conf = self.get_stack(self)
 
3587
        conf.set('foo', 'bar')
 
3588
        self.assertLength(1, calls)
 
3589
        self.assertEquals((conf, 'foo', 'bar'), calls[0])
 
3590
 
 
3591
 
 
3592
class TestStackRemove(TestStackWithTransport):
2243
3593
 
2244
3594
    def test_remove_existing(self):
2245
 
        store = config.IniFileStore(self.get_transport(), 'test.conf')
2246
 
        store._load_from_string('foo=bar')
2247
 
        conf = config.Stack([store.get_sections], store)
 
3595
        conf = self.get_stack(self)
 
3596
        conf.store._load_from_string('foo=bar')
2248
3597
        self.assertEquals('bar', conf.get('foo'))
2249
3598
        conf.remove('foo')
2250
3599
        # Did we get it back ?
2251
3600
        self.assertEquals(None, conf.get('foo'))
2252
3601
 
2253
3602
    def test_remove_unknown(self):
2254
 
        store = config.IniFileStore(self.get_transport(), 'test.conf')
2255
 
        conf = config.Stack([store.get_sections], store)
 
3603
        conf = self.get_stack(self)
2256
3604
        self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
2257
3605
 
 
3606
    def test_remove_hook(self):
 
3607
        calls = []
 
3608
        def hook(*args):
 
3609
            calls.append(args)
 
3610
        config.ConfigHooks.install_named_hook('remove', hook, None)
 
3611
        self.assertLength(0, calls)
 
3612
        conf = self.get_stack(self)
 
3613
        conf.store._load_from_string('foo=bar')
 
3614
        conf.remove('foo')
 
3615
        self.assertLength(1, calls)
 
3616
        self.assertEquals((conf, 'foo'), calls[0])
 
3617
 
2258
3618
 
2259
3619
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
2260
3620
 
2427
3787
        self.assertEquals({}, conf._get_config())
2428
3788
        self._got_user_passwd(None, None, conf, 'http', 'foo.net')
2429
3789
 
 
3790
    def test_non_utf8_config(self):
 
3791
        conf = config.AuthenticationConfig(_file=StringIO(
 
3792
                'foo = bar\xff'))
 
3793
        self.assertRaises(errors.ConfigContentError, conf._get_config)
 
3794
 
2430
3795
    def test_missing_auth_section_header(self):
2431
3796
        conf = config.AuthenticationConfig(_file=StringIO('foo = bar'))
2432
3797
        self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
2690
4055
 
2691
4056
    def test_username_defaults_prompts(self):
2692
4057
        # HTTP prompts can't be tested here, see test_http.py
2693
 
        self._check_default_username_prompt('FTP %(host)s username: ', 'ftp')
2694
 
        self._check_default_username_prompt(
2695
 
            'FTP %(host)s:%(port)d username: ', 'ftp', port=10020)
2696
 
        self._check_default_username_prompt(
2697
 
            'SSH %(host)s:%(port)d username: ', 'ssh', port=12345)
 
4058
        self._check_default_username_prompt(u'FTP %(host)s username: ', 'ftp')
 
4059
        self._check_default_username_prompt(
 
4060
            u'FTP %(host)s:%(port)d username: ', 'ftp', port=10020)
 
4061
        self._check_default_username_prompt(
 
4062
            u'SSH %(host)s:%(port)d username: ', 'ssh', port=12345)
2698
4063
 
2699
4064
    def test_username_default_no_prompt(self):
2700
4065
        conf = config.AuthenticationConfig()
2706
4071
    def test_password_default_prompts(self):
2707
4072
        # HTTP prompts can't be tested here, see test_http.py
2708
4073
        self._check_default_password_prompt(
2709
 
            'FTP %(user)s@%(host)s password: ', 'ftp')
2710
 
        self._check_default_password_prompt(
2711
 
            'FTP %(user)s@%(host)s:%(port)d password: ', 'ftp', port=10020)
2712
 
        self._check_default_password_prompt(
2713
 
            'SSH %(user)s@%(host)s:%(port)d password: ', 'ssh', port=12345)
 
4074
            u'FTP %(user)s@%(host)s password: ', 'ftp')
 
4075
        self._check_default_password_prompt(
 
4076
            u'FTP %(user)s@%(host)s:%(port)d password: ', 'ftp', port=10020)
 
4077
        self._check_default_password_prompt(
 
4078
            u'SSH %(user)s@%(host)s:%(port)d password: ', 'ssh', port=12345)
2714
4079
        # SMTP port handling is a bit special (it's handled if embedded in the
2715
4080
        # host too)
2716
4081
        # FIXME: should we: forbid that, extend it to other schemes, leave
2717
4082
        # things as they are that's fine thank you ?
2718
 
        self._check_default_password_prompt('SMTP %(user)s@%(host)s password: ',
2719
 
                                            'smtp')
2720
 
        self._check_default_password_prompt('SMTP %(user)s@%(host)s password: ',
2721
 
                                            'smtp', host='bar.org:10025')
2722
 
        self._check_default_password_prompt(
2723
 
            'SMTP %(user)s@%(host)s:%(port)d password: ',
2724
 
            'smtp', port=10025)
 
4083
        self._check_default_password_prompt(
 
4084
            u'SMTP %(user)s@%(host)s password: ', 'smtp')
 
4085
        self._check_default_password_prompt(
 
4086
            u'SMTP %(user)s@%(host)s password: ', 'smtp', host='bar.org:10025')
 
4087
        self._check_default_password_prompt(
 
4088
            u'SMTP %(user)s@%(host)s:%(port)d password: ', 'smtp', port=10025)
2725
4089
 
2726
4090
    def test_ssh_password_emits_warning(self):
2727
4091
        conf = config.AuthenticationConfig(_file=StringIO(
2921
4285
        to be able to choose a user name with no configuration.
2922
4286
        """
2923
4287
        if sys.platform == 'win32':
2924
 
            raise TestSkipped("User name inference not implemented on win32")
 
4288
            raise tests.TestSkipped(
 
4289
                "User name inference not implemented on win32")
2925
4290
        realname, address = config._auto_user_id()
2926
4291
        if os.path.exists('/etc/mailname'):
2927
4292
            self.assertIsNot(None, realname)