~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_config.py

  • Committer: Patch Queue Manager
  • Date: 2012-03-28 16:13:49 UTC
  • mfrom: (6499.2.3 948339-config-caching)
  • Revision ID: pqm@pqm.ubuntu.com-20120328161349-2gsc0g11fcu43hlc
(vila) Properly share mutable config sections and save the branch config
 only during the final unlock (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005-2012 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
27
27
#import bzrlib specific imports here
28
28
from bzrlib import (
29
29
    branch,
30
 
    bzrdir,
31
30
    config,
 
31
    controldir,
32
32
    diff,
33
33
    errors,
34
34
    osutils,
35
35
    mail_client,
36
 
    mergetools,
37
36
    ui,
38
37
    urlutils,
39
 
    registry,
 
38
    registry as _mod_registry,
40
39
    remote,
41
40
    tests,
42
41
    trace,
43
 
    transport,
44
42
    )
45
43
from bzrlib.symbol_versioning import (
46
44
    deprecated_in,
47
 
    deprecated_method,
48
45
    )
49
46
from bzrlib.transport import remote as transport_remote
50
47
from bzrlib.tests import (
71
68
 
72
69
# Register helpers to build stores
73
70
config.test_store_builder_registry.register(
74
 
    'configobj', lambda test: config.IniFileStore(test.get_transport(),
75
 
                                                  'configobj.conf'))
 
71
    'configobj', lambda test: config.TransportIniFileStore(
 
72
        test.get_transport(), 'configobj.conf'))
76
73
config.test_store_builder_registry.register(
77
74
    'bazaar', lambda test: config.GlobalStore())
78
75
config.test_store_builder_registry.register(
116
113
config.test_store_builder_registry.register('branch', build_branch_store)
117
114
 
118
115
 
 
116
def build_control_store(test):
 
117
    build_backing_branch(test, 'branch')
 
118
    b = controldir.ControlDir.open('branch')
 
119
    return config.ControlStore(b)
 
120
config.test_store_builder_registry.register('control', build_control_store)
 
121
 
 
122
 
119
123
def build_remote_branch_store(test):
120
124
    # There is only one permutation (but we won't be able to handle more with
121
125
    # this design anyway)
141
145
config.test_stack_builder_registry.register('branch', build_branch_stack)
142
146
 
143
147
 
144
 
def build_remote_branch_stack(test):
145
 
    # There is only one permutation (but we won't be able to handle more with
146
 
    # this design anyway)
147
 
    (transport_class,
148
 
     server_class) = transport_remote.get_test_permutations()[0]
149
 
    build_backing_branch(test, 'branch', transport_class, server_class)
150
 
    b = branch.Branch.open(test.get_url('branch'))
151
 
    return config.BranchStack(b)
152
 
config.test_stack_builder_registry.register('remote_branch',
153
 
                                            build_remote_branch_stack)
 
148
def build_branch_only_stack(test):
 
149
    # There is only one permutation (but we won't be able to handle more with
 
150
    # this design anyway)
 
151
    (transport_class,
 
152
     server_class) = transport_remote.get_test_permutations()[0]
 
153
    build_backing_branch(test, 'branch', transport_class, server_class)
 
154
    b = branch.Branch.open(test.get_url('branch'))
 
155
    return config.BranchOnlyStack(b)
 
156
config.test_stack_builder_registry.register('branch_only',
 
157
                                            build_branch_only_stack)
 
158
 
 
159
def build_remote_control_stack(test):
 
160
    # There is only one permutation (but we won't be able to handle more with
 
161
    # this design anyway)
 
162
    (transport_class,
 
163
     server_class) = transport_remote.get_test_permutations()[0]
 
164
    # We need only a bzrdir for this, not a full branch, but it's not worth
 
165
    # creating a dedicated helper to create only the bzrdir
 
166
    build_backing_branch(test, 'branch', transport_class, server_class)
 
167
    b = branch.Branch.open(test.get_url('branch'))
 
168
    return config.RemoteControlStack(b.bzrdir)
 
169
config.test_stack_builder_registry.register('remote_control',
 
170
                                            build_remote_control_stack)
154
171
 
155
172
 
156
173
sample_long_alias="log -r-15..-1 --line"
160
177
editor=vim
161
178
change_editor=vimdiff -of @new_path @old_path
162
179
gpg_signing_command=gnome-gpg
 
180
gpg_signing_key=DD4D5088
163
181
log_format=short
164
182
validate_signatures_in_log=true
165
183
acceptable_keys=amy
166
184
user_global_option=something
167
185
bzr.mergetool.sometool=sometool {base} {this} {other} -o {result}
168
186
bzr.mergetool.funkytool=funkytool "arg with spaces" {this_temp}
 
187
bzr.mergetool.newtool='"newtool with spaces" {this_temp}'
169
188
bzr.default_mergetool=sometool
170
189
[ALIASES]
171
190
h=help
214
233
[/a/]
215
234
check_signatures=check-available
216
235
gpg_signing_command=false
 
236
gpg_signing_key=default
217
237
user_local_option=local
218
238
# test trailing / matching
219
239
[/a/*]
309
329
 
310
330
class FakeBranch(object):
311
331
 
312
 
    def __init__(self, base=None, user_id=None):
 
332
    def __init__(self, base=None):
313
333
        if base is None:
314
334
            self.base = "http://example.com/branches/demo"
315
335
        else:
316
336
            self.base = base
317
337
        self._transport = self.control_files = \
318
 
            FakeControlFilesAndTransport(user_id=user_id)
 
338
            FakeControlFilesAndTransport()
319
339
 
320
340
    def _get_config(self):
321
341
        return config.TransportConfig(self._transport, 'branch.conf')
329
349
 
330
350
class FakeControlFilesAndTransport(object):
331
351
 
332
 
    def __init__(self, user_id=None):
 
352
    def __init__(self):
333
353
        self.files = {}
334
 
        if user_id:
335
 
            self.files['email'] = user_id
336
354
        self._transport = self
337
355
 
338
 
    def get_utf8(self, filename):
339
 
        # from LockableFiles
340
 
        raise AssertionError("get_utf8 should no longer be used")
341
 
 
342
356
    def get(self, filename):
343
357
        # from Transport
344
358
        try:
462
476
    def test_constructs(self):
463
477
        config.Config()
464
478
 
465
 
    def test_no_default_editor(self):
466
 
        self.assertRaises(
467
 
            NotImplementedError,
468
 
            self.applyDeprecated, deprecated_in((2, 4, 0)),
469
 
            config.Config().get_editor)
470
 
 
471
479
    def test_user_email(self):
472
480
        my_config = InstrumentedConfig()
473
481
        self.assertEqual('robert.collins@example.org', my_config.user_email())
481
489
 
482
490
    def test_signatures_default(self):
483
491
        my_config = config.Config()
484
 
        self.assertFalse(my_config.signature_needed())
 
492
        self.assertFalse(
 
493
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
494
                my_config.signature_needed))
485
495
        self.assertEqual(config.CHECK_IF_POSSIBLE,
486
 
                         my_config.signature_checking())
 
496
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
497
                my_config.signature_checking))
487
498
        self.assertEqual(config.SIGN_WHEN_REQUIRED,
488
 
                         my_config.signing_policy())
 
499
                self.applyDeprecated(deprecated_in((2, 5, 0)),
 
500
                    my_config.signing_policy))
489
501
 
490
502
    def test_signatures_template_method(self):
491
503
        my_config = InstrumentedConfig()
492
 
        self.assertEqual(config.CHECK_NEVER, my_config.signature_checking())
 
504
        self.assertEqual(config.CHECK_NEVER,
 
505
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
506
                my_config.signature_checking))
493
507
        self.assertEqual(['_get_signature_checking'], my_config._calls)
494
508
 
495
509
    def test_signatures_template_method_none(self):
496
510
        my_config = InstrumentedConfig()
497
511
        my_config._signatures = None
498
512
        self.assertEqual(config.CHECK_IF_POSSIBLE,
499
 
                         my_config.signature_checking())
 
513
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
514
                             my_config.signature_checking))
500
515
        self.assertEqual(['_get_signature_checking'], my_config._calls)
501
516
 
502
517
    def test_gpg_signing_command_default(self):
503
518
        my_config = config.Config()
504
 
        self.assertEqual('gpg', my_config.gpg_signing_command())
 
519
        self.assertEqual('gpg',
 
520
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
521
                my_config.gpg_signing_command))
505
522
 
506
523
    def test_get_user_option_default(self):
507
524
        my_config = config.Config()
509
526
 
510
527
    def test_post_commit_default(self):
511
528
        my_config = config.Config()
512
 
        self.assertEqual(None, my_config.post_commit())
 
529
        self.assertEqual(None, self.applyDeprecated(deprecated_in((2, 5, 0)),
 
530
                                                    my_config.post_commit))
 
531
 
513
532
 
514
533
    def test_log_format_default(self):
515
534
        my_config = config.Config()
516
 
        self.assertEqual('long', my_config.log_format())
 
535
        self.assertEqual('long',
 
536
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
537
                                              my_config.log_format))
517
538
 
518
539
    def test_acceptable_keys_default(self):
519
540
        my_config = config.Config()
520
 
        self.assertEqual(None, my_config.acceptable_keys())
 
541
        self.assertEqual(None, self.applyDeprecated(deprecated_in((2, 5, 0)),
 
542
            my_config.acceptable_keys))
521
543
 
522
544
    def test_validate_signatures_in_log_default(self):
523
545
        my_config = config.Config()
549
571
    def test_config_dir(self):
550
572
        self.assertEqual(config.config_dir(), self.bzr_home)
551
573
 
 
574
    def test_config_dir_is_unicode(self):
 
575
        self.assertIsInstance(config.config_dir(), unicode)
 
576
 
552
577
    def test_config_filename(self):
553
578
        self.assertEqual(config.config_filename(),
554
579
                         self.bzr_home + '/bazaar.conf')
658
683
        self.assertFileEqual(content, 'test.conf')
659
684
 
660
685
 
661
 
class TestIniConfigOptionExpansionDefaultValue(tests.TestCaseInTempDir):
662
 
    """What is the default value of expand for config options.
663
 
 
664
 
    This is an opt-in beta feature used to evaluate whether or not option
665
 
    references can appear in dangerous place raising exceptions, disapearing
666
 
    (and as such corrupting data) or if it's safe to activate the option by
667
 
    default.
668
 
 
669
 
    Note that these tests relies on config._expand_default_value being already
670
 
    overwritten in the parent class setUp.
671
 
    """
672
 
 
673
 
    def setUp(self):
674
 
        super(TestIniConfigOptionExpansionDefaultValue, self).setUp()
675
 
        self.config = None
676
 
        self.warnings = []
677
 
        def warning(*args):
678
 
            self.warnings.append(args[0] % args[1:])
679
 
        self.overrideAttr(trace, 'warning', warning)
680
 
 
681
 
    def get_config(self, expand):
682
 
        c = config.GlobalConfig.from_string('bzr.config.expand=%s' % (expand,),
683
 
                                            save=True)
684
 
        return c
685
 
 
686
 
    def assertExpandIs(self, expected):
687
 
        actual = config._get_expand_default_value()
688
 
        #self.config.get_user_option_as_bool('bzr.config.expand')
689
 
        self.assertEquals(expected, actual)
690
 
 
691
 
    def test_default_is_None(self):
692
 
        self.assertEquals(None, config._expand_default_value)
693
 
 
694
 
    def test_default_is_False_even_if_None(self):
695
 
        self.config = self.get_config(None)
696
 
        self.assertExpandIs(False)
697
 
 
698
 
    def test_default_is_False_even_if_invalid(self):
699
 
        self.config = self.get_config('<your choice>')
700
 
        self.assertExpandIs(False)
701
 
        # ...
702
 
        # Huh ? My choice is False ? Thanks, always happy to hear that :D
703
 
        # Wait, you've been warned !
704
 
        self.assertLength(1, self.warnings)
705
 
        self.assertEquals(
706
 
            'Value "<your choice>" is not a boolean for "bzr.config.expand"',
707
 
            self.warnings[0])
708
 
 
709
 
    def test_default_is_True(self):
710
 
        self.config = self.get_config(True)
711
 
        self.assertExpandIs(True)
712
 
 
713
 
    def test_default_is_False(self):
714
 
        self.config = self.get_config(False)
715
 
        self.assertExpandIs(False)
716
 
 
717
 
 
718
686
class TestIniConfigOptionExpansion(tests.TestCase):
719
687
    """Test option expansion from the IniConfig level.
720
688
 
817
785
        self.assertEquals(['{foo', '}', '{', 'bar}'],
818
786
                          conf.get_user_option('hidden', expand=True))
819
787
 
 
788
 
820
789
class TestLocationConfigOptionExpansion(tests.TestCaseInTempDir):
821
790
 
822
791
    def get_config(self, location, string=None):
1033
1002
        # automatically cast to list
1034
1003
        self.assertEqual(['x'], get_list('one_item'))
1035
1004
 
 
1005
    def test_get_user_option_as_int_from_SI(self):
 
1006
        conf, parser = self.make_config_parser("""
 
1007
plain = 100
 
1008
si_k = 5k,
 
1009
si_kb = 5kb,
 
1010
si_m = 5M,
 
1011
si_mb = 5MB,
 
1012
si_g = 5g,
 
1013
si_gb = 5gB,
 
1014
""")
 
1015
        def get_si(s, default=None):
 
1016
            return self.applyDeprecated(
 
1017
                deprecated_in((2, 5, 0)),
 
1018
                conf.get_user_option_as_int_from_SI, s, default)
 
1019
        self.assertEqual(100, get_si('plain'))
 
1020
        self.assertEqual(5000, get_si('si_k'))
 
1021
        self.assertEqual(5000, get_si('si_kb'))
 
1022
        self.assertEqual(5000000, get_si('si_m'))
 
1023
        self.assertEqual(5000000, get_si('si_mb'))
 
1024
        self.assertEqual(5000000000, get_si('si_g'))
 
1025
        self.assertEqual(5000000000, get_si('si_gb'))
 
1026
        self.assertEqual(None, get_si('non-exist'))
 
1027
        self.assertEqual(42, get_si('non-exist-with-default',  42))
 
1028
 
1036
1029
 
1037
1030
class TestSupressWarning(TestIniConfig):
1038
1031
 
1086
1079
 
1087
1080
    def test_get_config(self):
1088
1081
        """The Branch.get_config method works properly"""
1089
 
        b = bzrdir.BzrDir.create_standalone_workingtree('.').branch
 
1082
        b = controldir.ControlDir.create_standalone_workingtree('.').branch
1090
1083
        my_config = b.get_config()
1091
1084
        self.assertIs(my_config.get_user_option('wacky'), None)
1092
1085
        my_config.set_user_option('wacky', 'unlikely')
1141
1134
        b = self.make_branch('!repo')
1142
1135
        self.assertEqual('!repo', b.get_config().get_nickname())
1143
1136
 
 
1137
    def test_autonick_uses_branch_name(self):
 
1138
        b = self.make_branch('foo', name='bar')
 
1139
        self.assertEqual('bar', b.get_config().get_nickname())
 
1140
 
1144
1141
    def test_warn_if_masked(self):
1145
1142
        warnings = []
1146
1143
        def warning(*args):
1186
1183
        my_config = config.GlobalConfig()
1187
1184
        self.assertEqual(None, my_config._get_user_id())
1188
1185
 
1189
 
    def test_configured_editor(self):
1190
 
        my_config = config.GlobalConfig.from_string(sample_config_text)
1191
 
        editor = self.applyDeprecated(
1192
 
            deprecated_in((2, 4, 0)), my_config.get_editor)
1193
 
        self.assertEqual('vim', editor)
1194
 
 
1195
1186
    def test_signatures_always(self):
1196
1187
        my_config = config.GlobalConfig.from_string(sample_always_signatures)
1197
1188
        self.assertEqual(config.CHECK_NEVER,
1198
 
                         my_config.signature_checking())
 
1189
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1190
                             my_config.signature_checking))
1199
1191
        self.assertEqual(config.SIGN_ALWAYS,
1200
 
                         my_config.signing_policy())
1201
 
        self.assertEqual(True, my_config.signature_needed())
 
1192
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1193
                             my_config.signing_policy))
 
1194
        self.assertEqual(True,
 
1195
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1196
                my_config.signature_needed))
1202
1197
 
1203
1198
    def test_signatures_if_possible(self):
1204
1199
        my_config = config.GlobalConfig.from_string(sample_maybe_signatures)
1205
1200
        self.assertEqual(config.CHECK_NEVER,
1206
 
                         my_config.signature_checking())
 
1201
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1202
                             my_config.signature_checking))
1207
1203
        self.assertEqual(config.SIGN_WHEN_REQUIRED,
1208
 
                         my_config.signing_policy())
1209
 
        self.assertEqual(False, my_config.signature_needed())
 
1204
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1205
                             my_config.signing_policy))
 
1206
        self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1207
            my_config.signature_needed))
1210
1208
 
1211
1209
    def test_signatures_ignore(self):
1212
1210
        my_config = config.GlobalConfig.from_string(sample_ignore_signatures)
1213
1211
        self.assertEqual(config.CHECK_ALWAYS,
1214
 
                         my_config.signature_checking())
 
1212
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1213
                             my_config.signature_checking))
1215
1214
        self.assertEqual(config.SIGN_NEVER,
1216
 
                         my_config.signing_policy())
1217
 
        self.assertEqual(False, my_config.signature_needed())
 
1215
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1216
                             my_config.signing_policy))
 
1217
        self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1218
            my_config.signature_needed))
1218
1219
 
1219
1220
    def _get_sample_config(self):
1220
1221
        my_config = config.GlobalConfig.from_string(sample_config_text)
1222
1223
 
1223
1224
    def test_gpg_signing_command(self):
1224
1225
        my_config = self._get_sample_config()
1225
 
        self.assertEqual("gnome-gpg", my_config.gpg_signing_command())
1226
 
        self.assertEqual(False, my_config.signature_needed())
 
1226
        self.assertEqual("gnome-gpg",
 
1227
            self.applyDeprecated(
 
1228
                deprecated_in((2, 5, 0)), my_config.gpg_signing_command))
 
1229
        self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1230
            my_config.signature_needed))
 
1231
 
 
1232
    def test_gpg_signing_key(self):
 
1233
        my_config = self._get_sample_config()
 
1234
        self.assertEqual("DD4D5088",
 
1235
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1236
                my_config.gpg_signing_key))
1227
1237
 
1228
1238
    def _get_empty_config(self):
1229
1239
        my_config = config.GlobalConfig()
1231
1241
 
1232
1242
    def test_gpg_signing_command_unset(self):
1233
1243
        my_config = self._get_empty_config()
1234
 
        self.assertEqual("gpg", my_config.gpg_signing_command())
 
1244
        self.assertEqual("gpg",
 
1245
            self.applyDeprecated(
 
1246
                deprecated_in((2, 5, 0)), my_config.gpg_signing_command))
1235
1247
 
1236
1248
    def test_get_user_option_default(self):
1237
1249
        my_config = self._get_empty_config()
1244
1256
 
1245
1257
    def test_post_commit_default(self):
1246
1258
        my_config = self._get_sample_config()
1247
 
        self.assertEqual(None, my_config.post_commit())
 
1259
        self.assertEqual(None,
 
1260
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1261
                                              my_config.post_commit))
1248
1262
 
1249
1263
    def test_configured_logformat(self):
1250
1264
        my_config = self._get_sample_config()
1251
 
        self.assertEqual("short", my_config.log_format())
 
1265
        self.assertEqual("short",
 
1266
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1267
                                              my_config.log_format))
1252
1268
 
1253
1269
    def test_configured_acceptable_keys(self):
1254
1270
        my_config = self._get_sample_config()
1255
 
        self.assertEqual("amy", my_config.acceptable_keys())
 
1271
        self.assertEqual("amy",
 
1272
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1273
                my_config.acceptable_keys))
1256
1274
 
1257
1275
    def test_configured_validate_signatures_in_log(self):
1258
1276
        my_config = self._get_sample_config()
1296
1314
        self.log(repr(tools))
1297
1315
        self.assertEqual(
1298
1316
            {u'funkytool' : u'funkytool "arg with spaces" {this_temp}',
1299
 
            u'sometool' : u'sometool {base} {this} {other} -o {result}'},
 
1317
            u'sometool' : u'sometool {base} {this} {other} -o {result}',
 
1318
            u'newtool' : u'"newtool with spaces" {this_temp}'},
1300
1319
            tools)
1301
1320
 
1302
1321
    def test_get_merge_tools_empty(self):
1493
1512
        self.get_branch_config('http://www.example.com',
1494
1513
                                 global_config=sample_ignore_signatures)
1495
1514
        self.assertEqual(config.CHECK_ALWAYS,
1496
 
                         self.my_config.signature_checking())
 
1515
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1516
                             self.my_config.signature_checking))
1497
1517
        self.assertEqual(config.SIGN_NEVER,
1498
 
                         self.my_config.signing_policy())
 
1518
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1519
                             self.my_config.signing_policy))
1499
1520
 
1500
1521
    def test_signatures_never(self):
1501
1522
        self.get_branch_config('/a/c')
1502
1523
        self.assertEqual(config.CHECK_NEVER,
1503
 
                         self.my_config.signature_checking())
 
1524
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1525
                             self.my_config.signature_checking))
1504
1526
 
1505
1527
    def test_signatures_when_available(self):
1506
1528
        self.get_branch_config('/a/', global_config=sample_ignore_signatures)
1507
1529
        self.assertEqual(config.CHECK_IF_POSSIBLE,
1508
 
                         self.my_config.signature_checking())
 
1530
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1531
                             self.my_config.signature_checking))
1509
1532
 
1510
1533
    def test_signatures_always(self):
1511
1534
        self.get_branch_config('/b')
1512
1535
        self.assertEqual(config.CHECK_ALWAYS,
1513
 
                         self.my_config.signature_checking())
 
1536
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1537
                         self.my_config.signature_checking))
1514
1538
 
1515
1539
    def test_gpg_signing_command(self):
1516
1540
        self.get_branch_config('/b')
1517
 
        self.assertEqual("gnome-gpg", self.my_config.gpg_signing_command())
 
1541
        self.assertEqual("gnome-gpg",
 
1542
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1543
                self.my_config.gpg_signing_command))
1518
1544
 
1519
1545
    def test_gpg_signing_command_missing(self):
1520
1546
        self.get_branch_config('/a')
1521
 
        self.assertEqual("false", self.my_config.gpg_signing_command())
 
1547
        self.assertEqual("false",
 
1548
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1549
                self.my_config.gpg_signing_command))
 
1550
 
 
1551
    def test_gpg_signing_key(self):
 
1552
        self.get_branch_config('/b')
 
1553
        self.assertEqual("DD4D5088", self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1554
            self.my_config.gpg_signing_key))
 
1555
 
 
1556
    def test_gpg_signing_key_default(self):
 
1557
        self.get_branch_config('/a')
 
1558
        self.assertEqual("erik@bagfors.nu",
 
1559
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1560
                self.my_config.gpg_signing_key))
1522
1561
 
1523
1562
    def test_get_user_option_global(self):
1524
1563
        self.get_branch_config('/a')
1612
1651
    def test_post_commit_default(self):
1613
1652
        self.get_branch_config('/a/c')
1614
1653
        self.assertEqual('bzrlib.tests.test_config.post_commit',
1615
 
                         self.my_config.post_commit())
 
1654
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1655
                                              self.my_config.post_commit))
1616
1656
 
1617
1657
    def get_branch_config(self, location, global_config=None,
1618
1658
                          location_config=None):
1708
1748
        return my_config
1709
1749
 
1710
1750
    def test_user_id(self):
1711
 
        branch = FakeBranch(user_id='Robert Collins <robertc@example.net>')
 
1751
        branch = FakeBranch()
1712
1752
        my_config = config.BranchConfig(branch)
1713
 
        self.assertEqual("Robert Collins <robertc@example.net>",
1714
 
                         my_config.username())
 
1753
        self.assertIsNot(None, my_config.username())
1715
1754
        my_config.branch.control_files.files['email'] = "John"
1716
1755
        my_config.set_user_option('email',
1717
1756
                                  "Robert Collins <robertc@example.org>")
1718
 
        self.assertEqual("John", my_config.username())
1719
 
        del my_config.branch.control_files.files['email']
1720
1757
        self.assertEqual("Robert Collins <robertc@example.org>",
1721
 
                         my_config.username())
1722
 
 
1723
 
    def test_not_set_in_branch(self):
1724
 
        my_config = self.get_branch_config(global_config=sample_config_text)
1725
 
        self.assertEqual(u"Erik B\u00e5gfors <erik@bagfors.nu>",
1726
 
                         my_config._get_user_id())
1727
 
        my_config.branch.control_files.files['email'] = "John"
1728
 
        self.assertEqual("John", my_config._get_user_id())
 
1758
                        my_config.username())
1729
1759
 
1730
1760
    def test_BZR_EMAIL_OVERRIDES(self):
1731
1761
        self.overrideEnv('BZR_EMAIL', "Robert Collins <robertc@example.org>")
1737
1767
    def test_signatures_forced(self):
1738
1768
        my_config = self.get_branch_config(
1739
1769
            global_config=sample_always_signatures)
1740
 
        self.assertEqual(config.CHECK_NEVER, my_config.signature_checking())
1741
 
        self.assertEqual(config.SIGN_ALWAYS, my_config.signing_policy())
1742
 
        self.assertTrue(my_config.signature_needed())
 
1770
        self.assertEqual(config.CHECK_NEVER,
 
1771
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1772
                my_config.signature_checking))
 
1773
        self.assertEqual(config.SIGN_ALWAYS,
 
1774
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1775
                my_config.signing_policy))
 
1776
        self.assertTrue(self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1777
            my_config.signature_needed))
1743
1778
 
1744
1779
    def test_signatures_forced_branch(self):
1745
1780
        my_config = self.get_branch_config(
1746
1781
            global_config=sample_ignore_signatures,
1747
1782
            branch_data_config=sample_always_signatures)
1748
 
        self.assertEqual(config.CHECK_NEVER, my_config.signature_checking())
1749
 
        self.assertEqual(config.SIGN_ALWAYS, my_config.signing_policy())
1750
 
        self.assertTrue(my_config.signature_needed())
 
1783
        self.assertEqual(config.CHECK_NEVER,
 
1784
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1785
                my_config.signature_checking))
 
1786
        self.assertEqual(config.SIGN_ALWAYS,
 
1787
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1788
                my_config.signing_policy))
 
1789
        self.assertTrue(self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1790
            my_config.signature_needed))
1751
1791
 
1752
1792
    def test_gpg_signing_command(self):
1753
1793
        my_config = self.get_branch_config(
1754
1794
            global_config=sample_config_text,
1755
1795
            # branch data cannot set gpg_signing_command
1756
1796
            branch_data_config="gpg_signing_command=pgp")
1757
 
        self.assertEqual('gnome-gpg', my_config.gpg_signing_command())
 
1797
        self.assertEqual('gnome-gpg',
 
1798
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1799
                my_config.gpg_signing_command))
1758
1800
 
1759
1801
    def test_get_user_option_global(self):
1760
1802
        my_config = self.get_branch_config(global_config=sample_config_text)
1767
1809
                                      location_config=sample_branches_text)
1768
1810
        self.assertEqual(my_config.branch.base, '/a/c')
1769
1811
        self.assertEqual('bzrlib.tests.test_config.post_commit',
1770
 
                         my_config.post_commit())
 
1812
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1813
                                              my_config.post_commit))
1771
1814
        my_config.set_user_option('post_commit', 'rmtree_root')
1772
1815
        # post-commit is ignored when present in branch data
1773
1816
        self.assertEqual('bzrlib.tests.test_config.post_commit',
1774
 
                         my_config.post_commit())
 
1817
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1818
                                              my_config.post_commit))
1775
1819
        my_config.set_user_option('post_commit', 'rmtree_root',
1776
1820
                                  store=config.STORE_LOCATION)
1777
 
        self.assertEqual('rmtree_root', my_config.post_commit())
 
1821
        self.assertEqual('rmtree_root',
 
1822
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1823
                                              my_config.post_commit))
1778
1824
 
1779
1825
    def test_config_precedence(self):
1780
1826
        # FIXME: eager test, luckily no persitent config file makes it fail
1796
1842
            location='http://example.com/specific')
1797
1843
        self.assertEqual(my_config.get_user_option('option'), 'exact')
1798
1844
 
1799
 
    def test_get_mail_client(self):
1800
 
        config = self.get_branch_config()
1801
 
        client = config.get_mail_client()
1802
 
        self.assertIsInstance(client, mail_client.DefaultMail)
1803
 
 
1804
 
        # Specific clients
1805
 
        config.set_user_option('mail_client', 'evolution')
1806
 
        client = config.get_mail_client()
1807
 
        self.assertIsInstance(client, mail_client.Evolution)
1808
 
 
1809
 
        config.set_user_option('mail_client', 'kmail')
1810
 
        client = config.get_mail_client()
1811
 
        self.assertIsInstance(client, mail_client.KMail)
1812
 
 
1813
 
        config.set_user_option('mail_client', 'mutt')
1814
 
        client = config.get_mail_client()
1815
 
        self.assertIsInstance(client, mail_client.Mutt)
1816
 
 
1817
 
        config.set_user_option('mail_client', 'thunderbird')
1818
 
        client = config.get_mail_client()
1819
 
        self.assertIsInstance(client, mail_client.Thunderbird)
1820
 
 
1821
 
        # Generic options
1822
 
        config.set_user_option('mail_client', 'default')
1823
 
        client = config.get_mail_client()
1824
 
        self.assertIsInstance(client, mail_client.DefaultMail)
1825
 
 
1826
 
        config.set_user_option('mail_client', 'editor')
1827
 
        client = config.get_mail_client()
1828
 
        self.assertIsInstance(client, mail_client.Editor)
1829
 
 
1830
 
        config.set_user_option('mail_client', 'mapi')
1831
 
        client = config.get_mail_client()
1832
 
        self.assertIsInstance(client, mail_client.MAPIClient)
1833
 
 
1834
 
        config.set_user_option('mail_client', 'xdg-email')
1835
 
        client = config.get_mail_client()
1836
 
        self.assertIsInstance(client, mail_client.XDGEmail)
1837
 
 
1838
 
        config.set_user_option('mail_client', 'firebird')
1839
 
        self.assertRaises(errors.UnknownMailClient, config.get_mail_client)
1840
 
 
1841
1845
 
1842
1846
class TestMailAddressExtraction(tests.TestCase):
1843
1847
 
1914
1918
        conf = config.TransportConfig(t, 'foo.conf')
1915
1919
        self.assertRaises(errors.ParseConfigError, conf._get_configobj)
1916
1920
 
 
1921
    def test_load_permission_denied(self):
 
1922
        """Ensure we get an empty config file if the file is inaccessible."""
 
1923
        warnings = []
 
1924
        def warning(*args):
 
1925
            warnings.append(args[0] % args[1:])
 
1926
        self.overrideAttr(trace, 'warning', warning)
 
1927
 
 
1928
        class DenyingTransport(object):
 
1929
 
 
1930
            def __init__(self, base):
 
1931
                self.base = base
 
1932
 
 
1933
            def get_bytes(self, relpath):
 
1934
                raise errors.PermissionDenied(relpath, "")
 
1935
 
 
1936
        cfg = config.TransportConfig(
 
1937
            DenyingTransport("nonexisting://"), 'control.conf')
 
1938
        self.assertIs(None, cfg.get_option('non-existant', 'SECTION'))
 
1939
        self.assertEquals(
 
1940
            warnings,
 
1941
            [u'Permission denied while trying to open configuration file '
 
1942
             u'nonexisting:///control.conf.'])
 
1943
 
1917
1944
    def test_get_value(self):
1918
1945
        """Test that retreiving a value from a section is possible"""
1919
1946
        bzrdir_config = config.TransportConfig(self.get_transport('.'),
2107
2134
        self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
2108
2135
 
2109
2136
    def test_get_hook_remote_bzrdir(self):
2110
 
        remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
 
2137
        remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2111
2138
        conf = remote_bzrdir._get_config()
2112
2139
        conf.set_option('remotedir', 'file')
2113
2140
        self.assertGetHook(conf, 'file', 'remotedir')
2135
2162
    def test_set_hook_remote_bzrdir(self):
2136
2163
        remote_branch = branch.Branch.open(self.get_url('tree'))
2137
2164
        self.addCleanup(remote_branch.lock_write().unlock)
2138
 
        remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
 
2165
        remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2139
2166
        self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
2140
2167
 
2141
2168
    def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
2158
2185
        self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
2159
2186
 
2160
2187
    def test_load_hook_remote_bzrdir(self):
2161
 
        remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
 
2188
        remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2162
2189
        # The config file doesn't exist, set an option to force its creation
2163
2190
        conf = remote_bzrdir._get_config()
2164
2191
        conf.set_option('remotedir', 'file')
2189
2216
    def test_save_hook_remote_bzrdir(self):
2190
2217
        remote_branch = branch.Branch.open(self.get_url('tree'))
2191
2218
        self.addCleanup(remote_branch.lock_write().unlock)
2192
 
        remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
 
2219
        remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2193
2220
        self.assertSaveHook(remote_bzrdir._get_config())
2194
2221
 
2195
2222
 
2199
2226
        opt = config.Option('foo', default='bar')
2200
2227
        self.assertEquals('bar', opt.get_default())
2201
2228
 
 
2229
    def test_callable_default_value(self):
 
2230
        def bar_as_unicode():
 
2231
            return u'bar'
 
2232
        opt = config.Option('foo', default=bar_as_unicode)
 
2233
        self.assertEquals('bar', opt.get_default())
 
2234
 
 
2235
    def test_default_value_from_env(self):
 
2236
        opt = config.Option('foo', default='bar', default_from_env=['FOO'])
 
2237
        self.overrideEnv('FOO', 'quux')
 
2238
        # Env variable provides a default taking over the option one
 
2239
        self.assertEquals('quux', opt.get_default())
 
2240
 
 
2241
    def test_first_default_value_from_env_wins(self):
 
2242
        opt = config.Option('foo', default='bar',
 
2243
                            default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
 
2244
        self.overrideEnv('FOO', 'foo')
 
2245
        self.overrideEnv('BAZ', 'baz')
 
2246
        # The first env var set wins
 
2247
        self.assertEquals('foo', opt.get_default())
 
2248
 
 
2249
    def test_not_supported_list_default_value(self):
 
2250
        self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
 
2251
 
 
2252
    def test_not_supported_object_default_value(self):
 
2253
        self.assertRaises(AssertionError, config.Option, 'foo',
 
2254
                          default=object())
 
2255
 
 
2256
    def test_not_supported_callable_default_value_not_unicode(self):
 
2257
        def bar_not_unicode():
 
2258
            return 'bar'
 
2259
        opt = config.Option('foo', default=bar_not_unicode)
 
2260
        self.assertRaises(AssertionError, opt.get_default)
 
2261
 
 
2262
    def test_get_help_topic(self):
 
2263
        opt = config.Option('foo')
 
2264
        self.assertEquals('foo', opt.get_help_topic())
 
2265
 
 
2266
 
 
2267
class TestOptionConverterMixin(object):
 
2268
 
 
2269
    def assertConverted(self, expected, opt, value):
 
2270
        self.assertEquals(expected, opt.convert_from_unicode(None, value))
 
2271
 
 
2272
    def assertWarns(self, opt, value):
 
2273
        warnings = []
 
2274
        def warning(*args):
 
2275
            warnings.append(args[0] % args[1:])
 
2276
        self.overrideAttr(trace, 'warning', warning)
 
2277
        self.assertEquals(None, opt.convert_from_unicode(None, value))
 
2278
        self.assertLength(1, warnings)
 
2279
        self.assertEquals(
 
2280
            'Value "%s" is not valid for "%s"' % (value, opt.name),
 
2281
            warnings[0])
 
2282
 
 
2283
    def assertErrors(self, opt, value):
 
2284
        self.assertRaises(errors.ConfigOptionValueError,
 
2285
                          opt.convert_from_unicode, None, value)
 
2286
 
 
2287
    def assertConvertInvalid(self, opt, invalid_value):
 
2288
        opt.invalid = None
 
2289
        self.assertEquals(None, opt.convert_from_unicode(None, invalid_value))
 
2290
        opt.invalid = 'warning'
 
2291
        self.assertWarns(opt, invalid_value)
 
2292
        opt.invalid = 'error'
 
2293
        self.assertErrors(opt, invalid_value)
 
2294
 
 
2295
 
 
2296
class TestOptionWithBooleanConverter(tests.TestCase, TestOptionConverterMixin):
 
2297
 
 
2298
    def get_option(self):
 
2299
        return config.Option('foo', help='A boolean.',
 
2300
                             from_unicode=config.bool_from_store)
 
2301
 
 
2302
    def test_convert_invalid(self):
 
2303
        opt = self.get_option()
 
2304
        # A string that is not recognized as a boolean
 
2305
        self.assertConvertInvalid(opt, u'invalid-boolean')
 
2306
        # A list of strings is never recognized as a boolean
 
2307
        self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
 
2308
 
 
2309
    def test_convert_valid(self):
 
2310
        opt = self.get_option()
 
2311
        self.assertConverted(True, opt, u'True')
 
2312
        self.assertConverted(True, opt, u'1')
 
2313
        self.assertConverted(False, opt, u'False')
 
2314
 
 
2315
 
 
2316
class TestOptionWithIntegerConverter(tests.TestCase, TestOptionConverterMixin):
 
2317
 
 
2318
    def get_option(self):
 
2319
        return config.Option('foo', help='An integer.',
 
2320
                             from_unicode=config.int_from_store)
 
2321
 
 
2322
    def test_convert_invalid(self):
 
2323
        opt = self.get_option()
 
2324
        # A string that is not recognized as an integer
 
2325
        self.assertConvertInvalid(opt, u'forty-two')
 
2326
        # A list of strings is never recognized as an integer
 
2327
        self.assertConvertInvalid(opt, [u'a', u'list'])
 
2328
 
 
2329
    def test_convert_valid(self):
 
2330
        opt = self.get_option()
 
2331
        self.assertConverted(16, opt, u'16')
 
2332
 
 
2333
 
 
2334
class TestOptionWithSIUnitConverter(tests.TestCase, TestOptionConverterMixin):
 
2335
 
 
2336
    def get_option(self):
 
2337
        return config.Option('foo', help='An integer in SI units.',
 
2338
                             from_unicode=config.int_SI_from_store)
 
2339
 
 
2340
    def test_convert_invalid(self):
 
2341
        opt = self.get_option()
 
2342
        self.assertConvertInvalid(opt, u'not-a-unit')
 
2343
        self.assertConvertInvalid(opt, u'Gb') # Forgot the int
 
2344
        self.assertConvertInvalid(opt, u'1b') # Forgot the unit
 
2345
        self.assertConvertInvalid(opt, u'1GG')
 
2346
        self.assertConvertInvalid(opt, u'1Mbb')
 
2347
        self.assertConvertInvalid(opt, u'1MM')
 
2348
 
 
2349
    def test_convert_valid(self):
 
2350
        opt = self.get_option()
 
2351
        self.assertConverted(int(5e3), opt, u'5kb')
 
2352
        self.assertConverted(int(5e6), opt, u'5M')
 
2353
        self.assertConverted(int(5e6), opt, u'5MB')
 
2354
        self.assertConverted(int(5e9), opt, u'5g')
 
2355
        self.assertConverted(int(5e9), opt, u'5gB')
 
2356
        self.assertConverted(100, opt, u'100')
 
2357
 
 
2358
 
 
2359
class TestListOption(tests.TestCase, TestOptionConverterMixin):
 
2360
 
 
2361
    def get_option(self):
 
2362
        return config.ListOption('foo', help='A list.')
 
2363
 
 
2364
    def test_convert_invalid(self):
 
2365
        opt = self.get_option()
 
2366
        # We don't even try to convert a list into a list, we only expect
 
2367
        # strings
 
2368
        self.assertConvertInvalid(opt, [1])
 
2369
        # No string is invalid as all forms can be converted to a list
 
2370
 
 
2371
    def test_convert_valid(self):
 
2372
        opt = self.get_option()
 
2373
        # An empty string is an empty list
 
2374
        self.assertConverted([], opt, '') # Using a bare str() just in case
 
2375
        self.assertConverted([], opt, u'')
 
2376
        # A boolean
 
2377
        self.assertConverted([u'True'], opt, u'True')
 
2378
        # An integer
 
2379
        self.assertConverted([u'42'], opt, u'42')
 
2380
        # A single string
 
2381
        self.assertConverted([u'bar'], opt, u'bar')
 
2382
 
 
2383
 
 
2384
class TestRegistryOption(tests.TestCase, TestOptionConverterMixin):
 
2385
 
 
2386
    def get_option(self, registry):
 
2387
        return config.RegistryOption('foo', registry,
 
2388
                help='A registry option.')
 
2389
 
 
2390
    def test_convert_invalid(self):
 
2391
        registry = _mod_registry.Registry()
 
2392
        opt = self.get_option(registry)
 
2393
        self.assertConvertInvalid(opt, [1])
 
2394
        self.assertConvertInvalid(opt, u"notregistered")
 
2395
 
 
2396
    def test_convert_valid(self):
 
2397
        registry = _mod_registry.Registry()
 
2398
        registry.register("someval", 1234)
 
2399
        opt = self.get_option(registry)
 
2400
        # Using a bare str() just in case
 
2401
        self.assertConverted(1234, opt, "someval")
 
2402
        self.assertConverted(1234, opt, u'someval')
 
2403
        self.assertConverted(None, opt, None)
 
2404
 
 
2405
    def test_help(self):
 
2406
        registry = _mod_registry.Registry()
 
2407
        registry.register("someval", 1234, help="some option")
 
2408
        registry.register("dunno", 1234, help="some other option")
 
2409
        opt = self.get_option(registry)
 
2410
        self.assertEquals(
 
2411
            'A registry option.\n'
 
2412
            '\n'
 
2413
            'The following values are supported:\n'
 
2414
            ' dunno - some other option\n'
 
2415
            ' someval - some option\n',
 
2416
            opt.help)
 
2417
 
 
2418
    def test_get_help_text(self):
 
2419
        registry = _mod_registry.Registry()
 
2420
        registry.register("someval", 1234, help="some option")
 
2421
        registry.register("dunno", 1234, help="some other option")
 
2422
        opt = self.get_option(registry)
 
2423
        self.assertEquals(
 
2424
            'A registry option.\n'
 
2425
            '\n'
 
2426
            'The following values are supported:\n'
 
2427
            ' dunno - some other option\n'
 
2428
            ' someval - some option\n',
 
2429
            opt.get_help_text())
 
2430
 
2202
2431
 
2203
2432
class TestOptionRegistry(tests.TestCase):
2204
2433
 
2205
2434
    def setUp(self):
2206
2435
        super(TestOptionRegistry, self).setUp()
2207
2436
        # Always start with an empty registry
2208
 
        self.overrideAttr(config, 'option_registry', registry.Registry())
 
2437
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2209
2438
        self.registry = config.option_registry
2210
2439
 
2211
2440
    def test_register(self):
2212
2441
        opt = config.Option('foo')
2213
 
        self.registry.register('foo', opt)
 
2442
        self.registry.register(opt)
2214
2443
        self.assertIs(opt, self.registry.get('foo'))
2215
2444
 
2216
 
    lazy_option = config.Option('lazy_foo')
2217
 
 
2218
 
    def test_register_lazy(self):
2219
 
        self.registry.register_lazy('foo', self.__module__,
2220
 
                                    'TestOptionRegistry.lazy_option')
2221
 
        self.assertIs(self.lazy_option, self.registry.get('foo'))
2222
 
 
2223
2445
    def test_registered_help(self):
2224
 
        opt = config.Option('foo')
2225
 
        self.registry.register('foo', opt, help='A simple option')
 
2446
        opt = config.Option('foo', help='A simple option')
 
2447
        self.registry.register(opt)
2226
2448
        self.assertEquals('A simple option', self.registry.get_help('foo'))
2227
2449
 
 
2450
    lazy_option = config.Option('lazy_foo', help='Lazy help')
 
2451
 
 
2452
    def test_register_lazy(self):
 
2453
        self.registry.register_lazy('lazy_foo', self.__module__,
 
2454
                                    'TestOptionRegistry.lazy_option')
 
2455
        self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
 
2456
 
 
2457
    def test_registered_lazy_help(self):
 
2458
        self.registry.register_lazy('lazy_foo', self.__module__,
 
2459
                                    'TestOptionRegistry.lazy_option')
 
2460
        self.assertEquals('Lazy help', self.registry.get_help('lazy_foo'))
 
2461
 
2228
2462
 
2229
2463
class TestRegisteredOptions(tests.TestCase):
2230
2464
    """All registered options should verify some constraints."""
2244
2478
    def test_help_is_set(self):
2245
2479
        option_help = self.registry.get_help(self.option_name)
2246
2480
        self.assertNotEquals(None, option_help)
2247
 
        # Come on, think about the user, he really wants to know whst the
 
2481
        # Come on, think about the user, he really wants to know what the
2248
2482
        # option is about
 
2483
        self.assertIsNot(None, option_help)
2249
2484
        self.assertNotEquals('', option_help)
2250
2485
 
2251
2486
 
2273
2508
 
2274
2509
class TestMutableSection(tests.TestCase):
2275
2510
 
2276
 
    # FIXME: Parametrize so that all sections (including os.environ and the
2277
 
    # ones produced by Stores) run these tests -- vila 2011-04-01
 
2511
    scenarios = [('mutable',
 
2512
                  {'get_section':
 
2513
                       lambda opts: config.MutableSection('myID', opts)},),
 
2514
        ]
2278
2515
 
2279
2516
    def test_set(self):
2280
2517
        a_dict = dict(foo='bar')
2281
 
        section = config.MutableSection('myID', a_dict)
 
2518
        section = self.get_section(a_dict)
2282
2519
        section.set('foo', 'new_value')
2283
2520
        self.assertEquals('new_value', section.get('foo'))
2284
2521
        # The change appears in the shared section
2289
2526
 
2290
2527
    def test_set_preserve_original_once(self):
2291
2528
        a_dict = dict(foo='bar')
2292
 
        section = config.MutableSection('myID', a_dict)
 
2529
        section = self.get_section(a_dict)
2293
2530
        section.set('foo', 'first_value')
2294
2531
        section.set('foo', 'second_value')
2295
2532
        # We keep track of the original value
2298
2535
 
2299
2536
    def test_remove(self):
2300
2537
        a_dict = dict(foo='bar')
2301
 
        section = config.MutableSection('myID', a_dict)
 
2538
        section = self.get_section(a_dict)
2302
2539
        section.remove('foo')
2303
2540
        # We get None for unknown options via the default value
2304
2541
        self.assertEquals(None, section.get('foo'))
2311
2548
 
2312
2549
    def test_remove_new_option(self):
2313
2550
        a_dict = dict()
2314
 
        section = config.MutableSection('myID', a_dict)
 
2551
        section = self.get_section(a_dict)
2315
2552
        section.set('foo', 'bar')
2316
2553
        section.remove('foo')
2317
2554
        self.assertFalse('foo' in section.options)
2321
2558
        self.assertEquals(config._NewlyCreatedOption, section.orig['foo'])
2322
2559
 
2323
2560
 
 
2561
class TestCommandLineStore(tests.TestCase):
 
2562
 
 
2563
    def setUp(self):
 
2564
        super(TestCommandLineStore, self).setUp()
 
2565
        self.store = config.CommandLineStore()
 
2566
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
2567
 
 
2568
    def get_section(self):
 
2569
        """Get the unique section for the command line overrides."""
 
2570
        sections = list(self.store.get_sections())
 
2571
        self.assertLength(1, sections)
 
2572
        store, section = sections[0]
 
2573
        self.assertEquals(self.store, store)
 
2574
        return section
 
2575
 
 
2576
    def test_no_override(self):
 
2577
        self.store._from_cmdline([])
 
2578
        section = self.get_section()
 
2579
        self.assertLength(0, list(section.iter_option_names()))
 
2580
 
 
2581
    def test_simple_override(self):
 
2582
        self.store._from_cmdline(['a=b'])
 
2583
        section = self.get_section()
 
2584
        self.assertEqual('b', section.get('a'))
 
2585
 
 
2586
    def test_list_override(self):
 
2587
        opt = config.ListOption('l')
 
2588
        config.option_registry.register(opt)
 
2589
        self.store._from_cmdline(['l=1,2,3'])
 
2590
        val = self.get_section().get('l')
 
2591
        self.assertEqual('1,2,3', val)
 
2592
        # Reminder: lists should be registered as such explicitely, otherwise
 
2593
        # the conversion needs to be done afterwards.
 
2594
        self.assertEqual(['1', '2', '3'],
 
2595
                         opt.convert_from_unicode(self.store, val))
 
2596
 
 
2597
    def test_multiple_overrides(self):
 
2598
        self.store._from_cmdline(['a=b', 'x=y'])
 
2599
        section = self.get_section()
 
2600
        self.assertEquals('b', section.get('a'))
 
2601
        self.assertEquals('y', section.get('x'))
 
2602
 
 
2603
    def test_wrong_syntax(self):
 
2604
        self.assertRaises(errors.BzrCommandError,
 
2605
                          self.store._from_cmdline, ['a=b', 'c'])
 
2606
 
 
2607
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
 
2608
 
 
2609
    scenarios = [(key, {'get_store': builder}) for key, builder
 
2610
                 in config.test_store_builder_registry.iteritems()] + [
 
2611
        ('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
 
2612
 
 
2613
    def test_id(self):
 
2614
        store = self.get_store(self)
 
2615
        if type(store) == config.TransportIniFileStore:
 
2616
            raise tests.TestNotApplicable(
 
2617
                "%s is not a concrete Store implementation"
 
2618
                " so it doesn't need an id" % (store.__class__.__name__,))
 
2619
        self.assertIsNot(None, store.id)
 
2620
 
 
2621
 
2324
2622
class TestStore(tests.TestCaseWithTransport):
2325
2623
 
2326
 
    def assertSectionContent(self, expected, section):
 
2624
    def assertSectionContent(self, expected, (store, section)):
2327
2625
        """Assert that some options have the proper values in a section."""
2328
2626
        expected_name, expected_options = expected
2329
2627
        self.assertEquals(expected_name, section.id)
2337
2635
    scenarios = [(key, {'get_store': builder}) for key, builder
2338
2636
                 in config.test_store_builder_registry.iteritems()]
2339
2637
 
2340
 
    def setUp(self):
2341
 
        super(TestReadonlyStore, self).setUp()
2342
 
 
2343
2638
    def test_building_delays_load(self):
2344
2639
        store = self.get_store(self)
2345
2640
        self.assertEquals(False, store.is_loaded())
2371
2666
        self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
2372
2667
 
2373
2668
 
 
2669
class TestStoreQuoting(TestStore):
 
2670
 
 
2671
    scenarios = [(key, {'get_store': builder}) for key, builder
 
2672
                 in config.test_store_builder_registry.iteritems()]
 
2673
 
 
2674
    def setUp(self):
 
2675
        super(TestStoreQuoting, self).setUp()
 
2676
        self.store = self.get_store(self)
 
2677
        # We need a loaded store but any content will do
 
2678
        self.store._load_from_string('')
 
2679
 
 
2680
    def assertIdempotent(self, s):
 
2681
        """Assert that quoting an unquoted string is a no-op and vice-versa.
 
2682
 
 
2683
        What matters here is that option values, as they appear in a store, can
 
2684
        be safely round-tripped out of the store and back.
 
2685
 
 
2686
        :param s: A string, quoted if required.
 
2687
        """
 
2688
        self.assertEquals(s, self.store.quote(self.store.unquote(s)))
 
2689
        self.assertEquals(s, self.store.unquote(self.store.quote(s)))
 
2690
 
 
2691
    def test_empty_string(self):
 
2692
        if isinstance(self.store, config.IniFileStore):
 
2693
            # configobj._quote doesn't handle empty values
 
2694
            self.assertRaises(AssertionError,
 
2695
                              self.assertIdempotent, '')
 
2696
        else:
 
2697
            self.assertIdempotent('')
 
2698
        # But quoted empty strings are ok
 
2699
        self.assertIdempotent('""')
 
2700
 
 
2701
    def test_embedded_spaces(self):
 
2702
        self.assertIdempotent('" a b c "')
 
2703
 
 
2704
    def test_embedded_commas(self):
 
2705
        self.assertIdempotent('" a , b c "')
 
2706
 
 
2707
    def test_simple_comma(self):
 
2708
        if isinstance(self.store, config.IniFileStore):
 
2709
            # configobj requires that lists are special-cased
 
2710
           self.assertRaises(AssertionError,
 
2711
                             self.assertIdempotent, ',')
 
2712
        else:
 
2713
            self.assertIdempotent(',')
 
2714
        # When a single comma is required, quoting is also required
 
2715
        self.assertIdempotent('","')
 
2716
 
 
2717
    def test_list(self):
 
2718
        if isinstance(self.store, config.IniFileStore):
 
2719
            # configobj requires that lists are special-cased
 
2720
            self.assertRaises(AssertionError,
 
2721
                              self.assertIdempotent, 'a,b')
 
2722
        else:
 
2723
            self.assertIdempotent('a,b')
 
2724
 
 
2725
 
 
2726
class TestDictFromStore(tests.TestCase):
 
2727
 
 
2728
    def test_unquote_not_string(self):
 
2729
        conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
 
2730
        value = conf.get('a_section')
 
2731
        # Urgh, despite 'conf' asking for the no-name section, we get the
 
2732
        # content of another section as a dict o_O
 
2733
        self.assertEquals({'a': '1'}, value)
 
2734
        unquoted = conf.store.unquote(value)
 
2735
        # Which cannot be unquoted but shouldn't crash either (the use cases
 
2736
        # are getting the value or displaying it. In the later case, '%s' will
 
2737
        # do).
 
2738
        self.assertEquals({'a': '1'}, unquoted)
 
2739
        self.assertEquals("{u'a': u'1'}", '%s' % (unquoted,))
 
2740
 
 
2741
 
2374
2742
class TestIniFileStoreContent(tests.TestCaseWithTransport):
2375
 
    """Simulate loading a config store without content of various encodings.
 
2743
    """Simulate loading a config store with content of various encodings.
2376
2744
 
2377
2745
    All files produced by bzr are in utf8 content.
2378
2746
 
2391
2759
        utf8_content = unicode_content.encode('utf8')
2392
2760
        # Store the raw content in the config file
2393
2761
        t.put_bytes('foo.conf', utf8_content)
2394
 
        store = config.IniFileStore(t, 'foo.conf')
 
2762
        store = config.TransportIniFileStore(t, 'foo.conf')
2395
2763
        store.load()
2396
2764
        stack = config.Stack([store.get_sections], store)
2397
2765
        self.assertEquals(unicode_user, stack.get('user'))
2400
2768
        """Ensure we display a proper error on non-ascii, non utf-8 content."""
2401
2769
        t = self.get_transport()
2402
2770
        t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
2403
 
        store = config.IniFileStore(t, 'foo.conf')
 
2771
        store = config.TransportIniFileStore(t, 'foo.conf')
2404
2772
        self.assertRaises(errors.ConfigContentError, store.load)
2405
2773
 
2406
2774
    def test_load_erroneous_content(self):
2407
2775
        """Ensure we display a proper error on content that can't be parsed."""
2408
2776
        t = self.get_transport()
2409
2777
        t.put_bytes('foo.conf', '[open_section\n')
2410
 
        store = config.IniFileStore(t, 'foo.conf')
 
2778
        store = config.TransportIniFileStore(t, 'foo.conf')
2411
2779
        self.assertRaises(errors.ParseConfigError, store.load)
2412
2780
 
 
2781
    def test_load_permission_denied(self):
 
2782
        """Ensure we get warned when trying to load an inaccessible file."""
 
2783
        warnings = []
 
2784
        def warning(*args):
 
2785
            warnings.append(args[0] % args[1:])
 
2786
        self.overrideAttr(trace, 'warning', warning)
 
2787
 
 
2788
        t = self.get_transport()
 
2789
 
 
2790
        def get_bytes(relpath):
 
2791
            raise errors.PermissionDenied(relpath, "")
 
2792
        t.get_bytes = get_bytes
 
2793
        store = config.TransportIniFileStore(t, 'foo.conf')
 
2794
        self.assertRaises(errors.PermissionDenied, store.load)
 
2795
        self.assertEquals(
 
2796
            warnings,
 
2797
            [u'Permission denied while trying to load configuration store %s.'
 
2798
             % store.external_url()])
 
2799
 
2413
2800
 
2414
2801
class TestIniConfigContent(tests.TestCaseWithTransport):
2415
 
    """Simulate loading a IniBasedConfig without content of various encodings.
 
2802
    """Simulate loading a IniBasedConfig with content of various encodings.
2416
2803
 
2417
2804
    All files produced by bzr are in utf8 content.
2418
2805
 
2473
2860
        store.save()
2474
2861
        self.assertEquals(False, self.has_store(store))
2475
2862
 
 
2863
    def test_mutable_section_shared(self):
 
2864
        store = self.get_store(self)
 
2865
        store._load_from_string('foo=bar\n')
 
2866
        # FIXME: There should be a better way than relying on the test
 
2867
        # parametrization to identify branch.conf -- vila 2011-0526
 
2868
        if self.store_id in ('branch', 'remote_branch'):
 
2869
            # branch stores requires write locked branches
 
2870
            self.addCleanup(store.branch.lock_write().unlock)
 
2871
        section1 = store.get_mutable_section(None)
 
2872
        section2 = store.get_mutable_section(None)
 
2873
        # If we get different sections, different callers won't share the
 
2874
        # modification
 
2875
        self.assertIs(section1, section2)
 
2876
 
2476
2877
    def test_save_emptied_succeeds(self):
2477
2878
        store = self.get_store(self)
2478
2879
        store._load_from_string('foo=bar\n')
 
2880
        # FIXME: There should be a better way than relying on the test
 
2881
        # parametrization to identify branch.conf -- vila 2011-0526
 
2882
        if self.store_id in ('branch', 'remote_branch'):
 
2883
            # branch stores requires write locked branches
 
2884
            self.addCleanup(store.branch.lock_write().unlock)
2479
2885
        section = store.get_mutable_section(None)
2480
2886
        section.remove('foo')
2481
2887
        store.save()
2502
2908
 
2503
2909
    def test_set_option_in_empty_store(self):
2504
2910
        store = self.get_store(self)
 
2911
        # FIXME: There should be a better way than relying on the test
 
2912
        # parametrization to identify branch.conf -- vila 2011-0526
 
2913
        if self.store_id in ('branch', 'remote_branch'):
 
2914
            # branch stores requires write locked branches
 
2915
            self.addCleanup(store.branch.lock_write().unlock)
2505
2916
        section = store.get_mutable_section(None)
2506
2917
        section.set('foo', 'bar')
2507
2918
        store.save()
2513
2924
    def test_set_option_in_default_section(self):
2514
2925
        store = self.get_store(self)
2515
2926
        store._load_from_string('')
 
2927
        # FIXME: There should be a better way than relying on the test
 
2928
        # parametrization to identify branch.conf -- vila 2011-0526
 
2929
        if self.store_id in ('branch', 'remote_branch'):
 
2930
            # branch stores requires write locked branches
 
2931
            self.addCleanup(store.branch.lock_write().unlock)
2516
2932
        section = store.get_mutable_section(None)
2517
2933
        section.set('foo', 'bar')
2518
2934
        store.save()
2524
2940
    def test_set_option_in_named_section(self):
2525
2941
        store = self.get_store(self)
2526
2942
        store._load_from_string('')
 
2943
        # FIXME: There should be a better way than relying on the test
 
2944
        # parametrization to identify branch.conf -- vila 2011-0526
 
2945
        if self.store_id in ('branch', 'remote_branch'):
 
2946
            # branch stores requires write locked branches
 
2947
            self.addCleanup(store.branch.lock_write().unlock)
2527
2948
        section = store.get_mutable_section('baz')
2528
2949
        section.set('foo', 'bar')
2529
2950
        store.save()
2533
2954
        self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2534
2955
 
2535
2956
    def test_load_hook(self):
2536
 
        # We first needs to ensure that the store exists
 
2957
        # First, we need to ensure that the store exists
2537
2958
        store = self.get_store(self)
 
2959
        # FIXME: There should be a better way than relying on the test
 
2960
        # parametrization to identify branch.conf -- vila 2011-0526
 
2961
        if self.store_id in ('branch', 'remote_branch'):
 
2962
            # branch stores requires write locked branches
 
2963
            self.addCleanup(store.branch.lock_write().unlock)
2538
2964
        section = store.get_mutable_section('baz')
2539
2965
        section.set('foo', 'bar')
2540
2966
        store.save()
2556
2982
        config.ConfigHooks.install_named_hook('save', hook, None)
2557
2983
        self.assertLength(0, calls)
2558
2984
        store = self.get_store(self)
 
2985
        # FIXME: There should be a better way than relying on the test
 
2986
        # parametrization to identify branch.conf -- vila 2011-0526
 
2987
        if self.store_id in ('branch', 'remote_branch'):
 
2988
            # branch stores requires write locked branches
 
2989
            self.addCleanup(store.branch.lock_write().unlock)
2559
2990
        section = store.get_mutable_section('baz')
2560
2991
        section.set('foo', 'bar')
2561
2992
        store.save()
2562
2993
        self.assertLength(1, calls)
2563
2994
        self.assertEquals((store,), calls[0])
2564
2995
 
2565
 
 
2566
 
class TestIniFileStore(TestStore):
 
2996
    def test_set_mark_dirty(self):
 
2997
        stack = config.MemoryStack('')
 
2998
        self.assertLength(0, stack.store.dirty_sections)
 
2999
        stack.set('foo', 'baz')
 
3000
        self.assertLength(1, stack.store.dirty_sections)
 
3001
        self.assertTrue(stack.store._need_saving())
 
3002
 
 
3003
    def test_remove_mark_dirty(self):
 
3004
        stack = config.MemoryStack('foo=bar')
 
3005
        self.assertLength(0, stack.store.dirty_sections)
 
3006
        stack.remove('foo')
 
3007
        self.assertLength(1, stack.store.dirty_sections)
 
3008
        self.assertTrue(stack.store._need_saving())
 
3009
 
 
3010
 
 
3011
class TestStoreSaveChanges(tests.TestCaseWithTransport):
 
3012
    """Tests that config changes are kept in memory and saved on-demand."""
 
3013
 
 
3014
    def setUp(self):
 
3015
        super(TestStoreSaveChanges, self).setUp()
 
3016
        self.transport = self.get_transport()
 
3017
        # Most of the tests involve two stores pointing to the same persistent
 
3018
        # storage to observe the effects of concurrent changes
 
3019
        self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
 
3020
        self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
 
3021
        self.warnings = []
 
3022
        def warning(*args):
 
3023
            self.warnings.append(args[0] % args[1:])
 
3024
        self.overrideAttr(trace, 'warning', warning)
 
3025
 
 
3026
    def has_store(self, store):
 
3027
        store_basename = urlutils.relative_url(self.transport.external_url(),
 
3028
                                               store.external_url())
 
3029
        return self.transport.has(store_basename)
 
3030
 
 
3031
    def get_stack(self, store):
 
3032
        # Any stack will do as long as it uses the right store, just a single
 
3033
        # no-name section is enough
 
3034
        return config.Stack([store.get_sections], store)
 
3035
 
 
3036
    def test_no_changes_no_save(self):
 
3037
        s = self.get_stack(self.st1)
 
3038
        s.store.save_changes()
 
3039
        self.assertEquals(False, self.has_store(self.st1))
 
3040
 
 
3041
    def test_unrelated_concurrent_update(self):
 
3042
        s1 = self.get_stack(self.st1)
 
3043
        s2 = self.get_stack(self.st2)
 
3044
        s1.set('foo', 'bar')
 
3045
        s2.set('baz', 'quux')
 
3046
        s1.store.save()
 
3047
        # Changes don't propagate magically
 
3048
        self.assertEquals(None, s1.get('baz'))
 
3049
        s2.store.save_changes()
 
3050
        self.assertEquals('quux', s2.get('baz'))
 
3051
        # Changes are acquired when saving
 
3052
        self.assertEquals('bar', s2.get('foo'))
 
3053
        # Since there is no overlap, no warnings are emitted
 
3054
        self.assertLength(0, self.warnings)
 
3055
 
 
3056
    def test_concurrent_update_modified(self):
 
3057
        s1 = self.get_stack(self.st1)
 
3058
        s2 = self.get_stack(self.st2)
 
3059
        s1.set('foo', 'bar')
 
3060
        s2.set('foo', 'baz')
 
3061
        s1.store.save()
 
3062
        # Last speaker wins
 
3063
        s2.store.save_changes()
 
3064
        self.assertEquals('baz', s2.get('foo'))
 
3065
        # But the user get a warning
 
3066
        self.assertLength(1, self.warnings)
 
3067
        warning = self.warnings[0]
 
3068
        self.assertStartsWith(warning, 'Option foo in section None')
 
3069
        self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
 
3070
                            ' The baz value will be saved.')
 
3071
 
 
3072
    def test_concurrent_deletion(self):
 
3073
        self.st1._load_from_string('foo=bar')
 
3074
        self.st1.save()
 
3075
        s1 = self.get_stack(self.st1)
 
3076
        s2 = self.get_stack(self.st2)
 
3077
        s1.remove('foo')
 
3078
        s2.remove('foo')
 
3079
        s1.store.save_changes()
 
3080
        # No warning yet
 
3081
        self.assertLength(0, self.warnings)
 
3082
        s2.store.save_changes()
 
3083
        # Now we get one
 
3084
        self.assertLength(1, self.warnings)
 
3085
        warning = self.warnings[0]
 
3086
        self.assertStartsWith(warning, 'Option foo in section None')
 
3087
        self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
 
3088
                            ' The <DELETED> value will be saved.')
 
3089
 
 
3090
 
 
3091
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
 
3092
 
 
3093
    def get_store(self):
 
3094
        return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
 
3095
 
 
3096
    def test_get_quoted_string(self):
 
3097
        store = self.get_store()
 
3098
        store._load_from_string('foo= " abc "')
 
3099
        stack = config.Stack([store.get_sections])
 
3100
        self.assertEquals(' abc ', stack.get('foo'))
 
3101
 
 
3102
    def test_set_quoted_string(self):
 
3103
        store = self.get_store()
 
3104
        stack = config.Stack([store.get_sections], store)
 
3105
        stack.set('foo', ' a b c ')
 
3106
        store.save()
 
3107
        self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
 
3108
 
 
3109
 
 
3110
class TestTransportIniFileStore(TestStore):
2567
3111
 
2568
3112
    def test_loading_unknown_file_fails(self):
2569
 
        store = config.IniFileStore(self.get_transport(), 'I-do-not-exist')
 
3113
        store = config.TransportIniFileStore(self.get_transport(),
 
3114
            'I-do-not-exist')
2570
3115
        self.assertRaises(errors.NoSuchFile, store.load)
2571
3116
 
2572
3117
    def test_invalid_content(self):
2573
 
        store = config.IniFileStore(self.get_transport(), 'foo.conf', )
 
3118
        store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
2574
3119
        self.assertEquals(False, store.is_loaded())
2575
3120
        exc = self.assertRaises(
2576
3121
            errors.ParseConfigError, store._load_from_string,
2584
3129
        # option names share the same name space...)
2585
3130
        # FIXME: This should be fixed by forbidding dicts as values ?
2586
3131
        # -- vila 2011-04-05
2587
 
        store = config.IniFileStore(self.get_transport(), 'foo.conf', )
 
3132
        store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
2588
3133
        store._load_from_string('''
2589
3134
foo=bar
2590
3135
l=1,2
2600
3145
        sections = list(store.get_sections())
2601
3146
        self.assertLength(4, sections)
2602
3147
        # The default section has no name.
2603
 
        # List values are provided as lists
2604
 
        self.assertSectionContent((None, {'foo': 'bar', 'l': ['1', '2']}),
 
3148
        # List values are provided as strings and need to be explicitly
 
3149
        # converted by specifying from_unicode=list_from_store at option
 
3150
        # registration
 
3151
        self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
2605
3152
                                  sections[0])
2606
3153
        self.assertSectionContent(
2607
3154
            ('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
2637
3184
 
2638
3185
    def setUp(self):
2639
3186
        super(TestConcurrentStoreUpdates, self).setUp()
2640
 
        self._content = 'one=1\ntwo=2\n'
2641
3187
        self.stack = self.get_stack(self)
2642
3188
        if not isinstance(self.stack, config._CompatibleStack):
2643
3189
            raise tests.TestNotApplicable(
2644
3190
                '%s is not meant to be compatible with the old config design'
2645
3191
                % (self.stack,))
2646
 
        self.stack.store._load_from_string(self._content)
 
3192
        self.stack.set('one', '1')
 
3193
        self.stack.set('two', '2')
2647
3194
        # Flush the store
2648
3195
        self.stack.store.save()
2649
3196
 
2753
3300
    # FIXME: It may be worth looking into removing the lock dir when it's not
2754
3301
    # needed anymore and look at possible fallouts for concurrent lockers. This
2755
3302
    # will matter if/when we use config files outside of bazaar directories
2756
 
    # (.bazaar or .bzr) -- vila 20110-04-11
 
3303
    # (.bazaar or .bzr) -- vila 20110-04-111
2757
3304
 
2758
3305
 
2759
3306
class TestSectionMatcher(TestStore):
2760
3307
 
2761
 
    scenarios = [('location', {'matcher': config.LocationMatcher})]
 
3308
    scenarios = [('location', {'matcher': config.LocationMatcher}),
 
3309
                 ('id', {'matcher': config.NameMatcher}),]
2762
3310
 
2763
 
    def get_store(self, file_name):
2764
 
        return config.IniFileStore(self.get_readonly_transport(), file_name)
 
3311
    def setUp(self):
 
3312
        super(TestSectionMatcher, self).setUp()
 
3313
        # Any simple store is good enough
 
3314
        self.get_store = config.test_store_builder_registry.get('configobj')
2765
3315
 
2766
3316
    def test_no_matches_for_empty_stores(self):
2767
 
        store = self.get_store('foo.conf')
 
3317
        store = self.get_store(self)
2768
3318
        store._load_from_string('')
2769
3319
        matcher = self.matcher(store, '/bar')
2770
3320
        self.assertEquals([], list(matcher.get_sections()))
2771
3321
 
2772
3322
    def test_build_doesnt_load_store(self):
2773
 
        store = self.get_store('foo.conf')
 
3323
        store = self.get_store(self)
2774
3324
        matcher = self.matcher(store, '/bar')
2775
3325
        self.assertFalse(store.is_loaded())
2776
3326
 
2779
3329
 
2780
3330
    def get_section(self, options, extra_path):
2781
3331
        section = config.Section('foo', options)
2782
 
        # We don't care about the length so we use '0'
2783
 
        return config.LocationSection(section, 0, extra_path)
 
3332
        return config.LocationSection(section, extra_path)
2784
3333
 
2785
3334
    def test_simple_option(self):
2786
3335
        section = self.get_section({'foo': 'bar'}, '')
2800
3349
 
2801
3350
class TestLocationMatcher(TestStore):
2802
3351
 
2803
 
    def get_store(self, file_name):
2804
 
        return config.IniFileStore(self.get_readonly_transport(), file_name)
 
3352
    def setUp(self):
 
3353
        super(TestLocationMatcher, self).setUp()
 
3354
        # Any simple store is good enough
 
3355
        self.get_store = config.test_store_builder_registry.get('configobj')
 
3356
 
 
3357
    def test_unrelated_section_excluded(self):
 
3358
        store = self.get_store(self)
 
3359
        store._load_from_string('''
 
3360
[/foo]
 
3361
section=/foo
 
3362
[/foo/baz]
 
3363
section=/foo/baz
 
3364
[/foo/bar]
 
3365
section=/foo/bar
 
3366
[/foo/bar/baz]
 
3367
section=/foo/bar/baz
 
3368
[/quux/quux]
 
3369
section=/quux/quux
 
3370
''')
 
3371
        self.assertEquals(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
 
3372
                           '/quux/quux'],
 
3373
                          [section.id for _, section in store.get_sections()])
 
3374
        matcher = config.LocationMatcher(store, '/foo/bar/quux')
 
3375
        sections = [section for _, section in matcher.get_sections()]
 
3376
        self.assertEquals(['/foo/bar', '/foo'],
 
3377
                          [section.id for section in sections])
 
3378
        self.assertEquals(['quux', 'bar/quux'],
 
3379
                          [section.extra_path for section in sections])
2805
3380
 
2806
3381
    def test_more_specific_sections_first(self):
2807
 
        store = self.get_store('foo.conf')
 
3382
        store = self.get_store(self)
2808
3383
        store._load_from_string('''
2809
3384
[/foo]
2810
3385
section=/foo
2812
3387
section=/foo/bar
2813
3388
''')
2814
3389
        self.assertEquals(['/foo', '/foo/bar'],
2815
 
                          [section.id for section in store.get_sections()])
 
3390
                          [section.id for _, section in store.get_sections()])
2816
3391
        matcher = config.LocationMatcher(store, '/foo/bar/baz')
2817
 
        sections = list(matcher.get_sections())
2818
 
        self.assertEquals([3, 2],
2819
 
                          [section.length for section in sections])
 
3392
        sections = [section for _, section in matcher.get_sections()]
2820
3393
        self.assertEquals(['/foo/bar', '/foo'],
2821
3394
                          [section.id for section in sections])
2822
3395
        self.assertEquals(['baz', 'bar/baz'],
2825
3398
    def test_appendpath_in_no_name_section(self):
2826
3399
        # It's a bit weird to allow appendpath in a no-name section, but
2827
3400
        # someone may found a use for it
2828
 
        store = self.get_store('foo.conf')
 
3401
        store = self.get_store(self)
2829
3402
        store._load_from_string('''
2830
3403
foo=bar
2831
3404
foo:policy = appendpath
2833
3406
        matcher = config.LocationMatcher(store, 'dir/subdir')
2834
3407
        sections = list(matcher.get_sections())
2835
3408
        self.assertLength(1, sections)
2836
 
        self.assertEquals('bar/dir/subdir', sections[0].get('foo'))
 
3409
        self.assertEquals('bar/dir/subdir', sections[0][1].get('foo'))
2837
3410
 
2838
3411
    def test_file_urls_are_normalized(self):
2839
 
        store = self.get_store('foo.conf')
 
3412
        store = self.get_store(self)
2840
3413
        if sys.platform == 'win32':
2841
3414
            expected_url = 'file:///C:/dir/subdir'
2842
3415
            expected_location = 'C:/dir/subdir'
2847
3420
        self.assertEquals(expected_location, matcher.location)
2848
3421
 
2849
3422
 
2850
 
class TestStackGet(tests.TestCase):
2851
 
 
2852
 
    # FIXME: This should be parametrized for all known Stack or dedicated
2853
 
    # paramerized tests created to avoid bloating -- vila 2011-03-31
2854
 
 
2855
 
    def test_single_config_get(self):
2856
 
        conf = dict(foo='bar')
2857
 
        conf_stack = config.Stack([conf])
2858
 
        self.assertEquals('bar', conf_stack.get('foo'))
 
3423
class TestStartingPathMatcher(TestStore):
 
3424
 
 
3425
    def setUp(self):
 
3426
        super(TestStartingPathMatcher, self).setUp()
 
3427
        # Any simple store is good enough
 
3428
        self.store = config.IniFileStore()
 
3429
 
 
3430
    def assertSectionIDs(self, expected, location, content):
 
3431
        self.store._load_from_string(content)
 
3432
        matcher = config.StartingPathMatcher(self.store, location)
 
3433
        sections = list(matcher.get_sections())
 
3434
        self.assertLength(len(expected), sections)
 
3435
        self.assertEqual(expected, [section.id for _, section in sections])
 
3436
        return sections
 
3437
 
 
3438
    def test_empty(self):
 
3439
        self.assertSectionIDs([], self.get_url(), '')
 
3440
 
 
3441
    def test_url_vs_local_paths(self):
 
3442
        # The matcher location is an url and the section names are local paths
 
3443
        sections = self.assertSectionIDs(['/foo/bar', '/foo'],
 
3444
                                         'file:///foo/bar/baz', '''\
 
3445
[/foo]
 
3446
[/foo/bar]
 
3447
''')
 
3448
 
 
3449
    def test_local_path_vs_url(self):
 
3450
        # The matcher location is a local path and the section names are urls
 
3451
        sections = self.assertSectionIDs(['file:///foo/bar', 'file:///foo'],
 
3452
                                         '/foo/bar/baz', '''\
 
3453
[file:///foo]
 
3454
[file:///foo/bar]
 
3455
''')
 
3456
 
 
3457
 
 
3458
    def test_no_name_section_included_when_present(self):
 
3459
        # Note that other tests will cover the case where the no-name section
 
3460
        # is empty and as such, not included.
 
3461
        sections = self.assertSectionIDs(['/foo/bar', '/foo', None],
 
3462
                                         '/foo/bar/baz', '''\
 
3463
option = defined so the no-name section exists
 
3464
[/foo]
 
3465
[/foo/bar]
 
3466
''')
 
3467
        self.assertEquals(['baz', 'bar/baz', '/foo/bar/baz'],
 
3468
                          [s.locals['relpath'] for _, s in sections])
 
3469
 
 
3470
    def test_order_reversed(self):
 
3471
        self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
 
3472
[/foo]
 
3473
[/foo/bar]
 
3474
''')
 
3475
 
 
3476
    def test_unrelated_section_excluded(self):
 
3477
        self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
 
3478
[/foo]
 
3479
[/foo/qux]
 
3480
[/foo/bar]
 
3481
''')
 
3482
 
 
3483
    def test_glob_included(self):
 
3484
        sections = self.assertSectionIDs(['/foo/*/baz', '/foo/b*', '/foo'],
 
3485
                                         '/foo/bar/baz', '''\
 
3486
[/foo]
 
3487
[/foo/qux]
 
3488
[/foo/b*]
 
3489
[/foo/*/baz]
 
3490
''')
 
3491
        # Note that 'baz' as a relpath for /foo/b* is not fully correct, but
 
3492
        # nothing really is... as far using {relpath} to append it to something
 
3493
        # else, this seems good enough though.
 
3494
        self.assertEquals(['', 'baz', 'bar/baz'],
 
3495
                          [s.locals['relpath'] for _, s in sections])
 
3496
 
 
3497
    def test_respect_order(self):
 
3498
        self.assertSectionIDs(['/foo', '/foo/b*', '/foo/*/baz'],
 
3499
                              '/foo/bar/baz', '''\
 
3500
[/foo/*/baz]
 
3501
[/foo/qux]
 
3502
[/foo/b*]
 
3503
[/foo]
 
3504
''')
 
3505
 
 
3506
 
 
3507
class TestNameMatcher(TestStore):
 
3508
 
 
3509
    def setUp(self):
 
3510
        super(TestNameMatcher, self).setUp()
 
3511
        self.matcher = config.NameMatcher
 
3512
        # Any simple store is good enough
 
3513
        self.get_store = config.test_store_builder_registry.get('configobj')
 
3514
 
 
3515
    def get_matching_sections(self, name):
 
3516
        store = self.get_store(self)
 
3517
        store._load_from_string('''
 
3518
[foo]
 
3519
option=foo
 
3520
[foo/baz]
 
3521
option=foo/baz
 
3522
[bar]
 
3523
option=bar
 
3524
''')
 
3525
        matcher = self.matcher(store, name)
 
3526
        return list(matcher.get_sections())
 
3527
 
 
3528
    def test_matching(self):
 
3529
        sections = self.get_matching_sections('foo')
 
3530
        self.assertLength(1, sections)
 
3531
        self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
 
3532
 
 
3533
    def test_not_matching(self):
 
3534
        sections = self.get_matching_sections('baz')
 
3535
        self.assertLength(0, sections)
 
3536
 
 
3537
 
 
3538
class TestBaseStackGet(tests.TestCase):
 
3539
 
 
3540
    def setUp(self):
 
3541
        super(TestBaseStackGet, self).setUp()
 
3542
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
3543
 
 
3544
    def test_get_first_definition(self):
 
3545
        store1 = config.IniFileStore()
 
3546
        store1._load_from_string('foo=bar')
 
3547
        store2 = config.IniFileStore()
 
3548
        store2._load_from_string('foo=baz')
 
3549
        conf = config.Stack([store1.get_sections, store2.get_sections])
 
3550
        self.assertEquals('bar', conf.get('foo'))
2859
3551
 
2860
3552
    def test_get_with_registered_default_value(self):
2861
 
        conf_stack = config.Stack([dict()])
2862
 
        opt = config.Option('foo', default='bar')
2863
 
        self.overrideAttr(config, 'option_registry', registry.Registry())
2864
 
        config.option_registry.register('foo', opt)
 
3553
        config.option_registry.register(config.Option('foo', default='bar'))
 
3554
        conf_stack = config.Stack([])
2865
3555
        self.assertEquals('bar', conf_stack.get('foo'))
2866
3556
 
2867
3557
    def test_get_without_registered_default_value(self):
2868
 
        conf_stack = config.Stack([dict()])
2869
 
        opt = config.Option('foo')
2870
 
        self.overrideAttr(config, 'option_registry', registry.Registry())
2871
 
        config.option_registry.register('foo', opt)
 
3558
        config.option_registry.register(config.Option('foo'))
 
3559
        conf_stack = config.Stack([])
2872
3560
        self.assertEquals(None, conf_stack.get('foo'))
2873
3561
 
2874
3562
    def test_get_without_default_value_for_not_registered(self):
2875
 
        conf_stack = config.Stack([dict()])
2876
 
        opt = config.Option('foo')
2877
 
        self.overrideAttr(config, 'option_registry', registry.Registry())
 
3563
        conf_stack = config.Stack([])
2878
3564
        self.assertEquals(None, conf_stack.get('foo'))
2879
3565
 
2880
 
    def test_get_first_definition(self):
2881
 
        conf1 = dict(foo='bar')
2882
 
        conf2 = dict(foo='baz')
2883
 
        conf_stack = config.Stack([conf1, conf2])
2884
 
        self.assertEquals('bar', conf_stack.get('foo'))
2885
 
 
2886
 
    def test_get_embedded_definition(self):
2887
 
        conf1 = dict(yy='12')
2888
 
        conf2 = config.Stack([dict(xx='42'), dict(foo='baz')])
2889
 
        conf_stack = config.Stack([conf1, conf2])
2890
 
        self.assertEquals('baz', conf_stack.get('foo'))
2891
 
 
2892
3566
    def test_get_for_empty_section_callable(self):
2893
3567
        conf_stack = config.Stack([lambda : []])
2894
3568
        self.assertEquals(None, conf_stack.get('foo'))
2895
3569
 
2896
3570
    def test_get_for_broken_callable(self):
2897
3571
        # Trying to use and invalid callable raises an exception on first use
2898
 
        conf_stack = config.Stack([lambda : object()])
 
3572
        conf_stack = config.Stack([object])
2899
3573
        self.assertRaises(TypeError, conf_stack.get, 'foo')
2900
3574
 
2901
3575
 
 
3576
class TestStackWithSimpleStore(tests.TestCase):
 
3577
 
 
3578
    def setUp(self):
 
3579
        super(TestStackWithSimpleStore, self).setUp()
 
3580
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
3581
        self.registry = config.option_registry
 
3582
 
 
3583
    def get_conf(self, content=None):
 
3584
        return config.MemoryStack(content)
 
3585
 
 
3586
    def test_override_value_from_env(self):
 
3587
        self.registry.register(
 
3588
            config.Option('foo', default='bar', override_from_env=['FOO']))
 
3589
        self.overrideEnv('FOO', 'quux')
 
3590
        # Env variable provides a default taking over the option one
 
3591
        conf = self.get_conf('foo=store')
 
3592
        self.assertEquals('quux', conf.get('foo'))
 
3593
 
 
3594
    def test_first_override_value_from_env_wins(self):
 
3595
        self.registry.register(
 
3596
            config.Option('foo', default='bar',
 
3597
                          override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
 
3598
        self.overrideEnv('FOO', 'foo')
 
3599
        self.overrideEnv('BAZ', 'baz')
 
3600
        # The first env var set wins
 
3601
        conf = self.get_conf('foo=store')
 
3602
        self.assertEquals('foo', conf.get('foo'))
 
3603
 
 
3604
 
 
3605
class TestMemoryStack(tests.TestCase):
 
3606
 
 
3607
    def test_get(self):
 
3608
        conf = config.MemoryStack('foo=bar')
 
3609
        self.assertEquals('bar', conf.get('foo'))
 
3610
 
 
3611
    def test_set(self):
 
3612
        conf = config.MemoryStack('foo=bar')
 
3613
        conf.set('foo', 'baz')
 
3614
        self.assertEquals('baz', conf.get('foo'))
 
3615
 
 
3616
    def test_no_content(self):
 
3617
        conf = config.MemoryStack()
 
3618
        # No content means no loading
 
3619
        self.assertFalse(conf.store.is_loaded())
 
3620
        self.assertRaises(NotImplementedError, conf.get, 'foo')
 
3621
        # But a content can still be provided
 
3622
        conf.store._load_from_string('foo=bar')
 
3623
        self.assertEquals('bar', conf.get('foo'))
 
3624
 
 
3625
 
 
3626
class TestStackIterSections(tests.TestCase):
 
3627
 
 
3628
    def test_empty_stack(self):
 
3629
        conf = config.Stack([])
 
3630
        sections = list(conf.iter_sections())
 
3631
        self.assertLength(0, sections)
 
3632
 
 
3633
    def test_empty_store(self):
 
3634
        store = config.IniFileStore()
 
3635
        store._load_from_string('')
 
3636
        conf = config.Stack([store.get_sections])
 
3637
        sections = list(conf.iter_sections())
 
3638
        self.assertLength(0, sections)
 
3639
 
 
3640
    def test_simple_store(self):
 
3641
        store = config.IniFileStore()
 
3642
        store._load_from_string('foo=bar')
 
3643
        conf = config.Stack([store.get_sections])
 
3644
        tuples = list(conf.iter_sections())
 
3645
        self.assertLength(1, tuples)
 
3646
        (found_store, found_section) = tuples[0]
 
3647
        self.assertIs(store, found_store)
 
3648
 
 
3649
    def test_two_stores(self):
 
3650
        store1 = config.IniFileStore()
 
3651
        store1._load_from_string('foo=bar')
 
3652
        store2 = config.IniFileStore()
 
3653
        store2._load_from_string('bar=qux')
 
3654
        conf = config.Stack([store1.get_sections, store2.get_sections])
 
3655
        tuples = list(conf.iter_sections())
 
3656
        self.assertLength(2, tuples)
 
3657
        self.assertIs(store1, tuples[0][0])
 
3658
        self.assertIs(store2, tuples[1][0])
 
3659
 
 
3660
 
2902
3661
class TestStackWithTransport(tests.TestCaseWithTransport):
2903
3662
 
2904
3663
    scenarios = [(key, {'get_stack': builder}) for key, builder
2914
3673
 
2915
3674
class TestStackGet(TestStackWithTransport):
2916
3675
 
 
3676
    def setUp(self):
 
3677
        super(TestStackGet, self).setUp()
 
3678
        self.conf = self.get_stack(self)
 
3679
 
2917
3680
    def test_get_for_empty_stack(self):
2918
 
        conf = self.get_stack(self)
2919
 
        self.assertEquals(None, conf.get('foo'))
 
3681
        self.assertEquals(None, self.conf.get('foo'))
2920
3682
 
2921
3683
    def test_get_hook(self):
2922
 
        conf = self.get_stack(self)
2923
 
        conf.store._load_from_string('foo=bar')
 
3684
        self.conf.set('foo', 'bar')
2924
3685
        calls = []
2925
3686
        def hook(*args):
2926
3687
            calls.append(args)
2927
3688
        config.ConfigHooks.install_named_hook('get', hook, None)
2928
3689
        self.assertLength(0, calls)
2929
 
        value = conf.get('foo')
 
3690
        value = self.conf.get('foo')
2930
3691
        self.assertEquals('bar', value)
2931
3692
        self.assertLength(1, calls)
2932
 
        self.assertEquals((conf, 'foo', 'bar'), calls[0])
 
3693
        self.assertEquals((self.conf, 'foo', 'bar'), calls[0])
 
3694
 
 
3695
 
 
3696
class TestStackGetWithConverter(tests.TestCase):
 
3697
 
 
3698
    def setUp(self):
 
3699
        super(TestStackGetWithConverter, self).setUp()
 
3700
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
3701
        self.registry = config.option_registry
 
3702
 
 
3703
    def get_conf(self, content=None):
 
3704
        return config.MemoryStack(content)
 
3705
 
 
3706
    def register_bool_option(self, name, default=None, default_from_env=None):
 
3707
        b = config.Option(name, help='A boolean.',
 
3708
                          default=default, default_from_env=default_from_env,
 
3709
                          from_unicode=config.bool_from_store)
 
3710
        self.registry.register(b)
 
3711
 
 
3712
    def test_get_default_bool_None(self):
 
3713
        self.register_bool_option('foo')
 
3714
        conf = self.get_conf('')
 
3715
        self.assertEquals(None, conf.get('foo'))
 
3716
 
 
3717
    def test_get_default_bool_True(self):
 
3718
        self.register_bool_option('foo', u'True')
 
3719
        conf = self.get_conf('')
 
3720
        self.assertEquals(True, conf.get('foo'))
 
3721
 
 
3722
    def test_get_default_bool_False(self):
 
3723
        self.register_bool_option('foo', False)
 
3724
        conf = self.get_conf('')
 
3725
        self.assertEquals(False, conf.get('foo'))
 
3726
 
 
3727
    def test_get_default_bool_False_as_string(self):
 
3728
        self.register_bool_option('foo', u'False')
 
3729
        conf = self.get_conf('')
 
3730
        self.assertEquals(False, conf.get('foo'))
 
3731
 
 
3732
    def test_get_default_bool_from_env_converted(self):
 
3733
        self.register_bool_option('foo', u'True', default_from_env=['FOO'])
 
3734
        self.overrideEnv('FOO', 'False')
 
3735
        conf = self.get_conf('')
 
3736
        self.assertEquals(False, conf.get('foo'))
 
3737
 
 
3738
    def test_get_default_bool_when_conversion_fails(self):
 
3739
        self.register_bool_option('foo', default='True')
 
3740
        conf = self.get_conf('foo=invalid boolean')
 
3741
        self.assertEquals(True, conf.get('foo'))
 
3742
 
 
3743
    def register_integer_option(self, name,
 
3744
                                default=None, default_from_env=None):
 
3745
        i = config.Option(name, help='An integer.',
 
3746
                          default=default, default_from_env=default_from_env,
 
3747
                          from_unicode=config.int_from_store)
 
3748
        self.registry.register(i)
 
3749
 
 
3750
    def test_get_default_integer_None(self):
 
3751
        self.register_integer_option('foo')
 
3752
        conf = self.get_conf('')
 
3753
        self.assertEquals(None, conf.get('foo'))
 
3754
 
 
3755
    def test_get_default_integer(self):
 
3756
        self.register_integer_option('foo', 42)
 
3757
        conf = self.get_conf('')
 
3758
        self.assertEquals(42, conf.get('foo'))
 
3759
 
 
3760
    def test_get_default_integer_as_string(self):
 
3761
        self.register_integer_option('foo', u'42')
 
3762
        conf = self.get_conf('')
 
3763
        self.assertEquals(42, conf.get('foo'))
 
3764
 
 
3765
    def test_get_default_integer_from_env(self):
 
3766
        self.register_integer_option('foo', default_from_env=['FOO'])
 
3767
        self.overrideEnv('FOO', '18')
 
3768
        conf = self.get_conf('')
 
3769
        self.assertEquals(18, conf.get('foo'))
 
3770
 
 
3771
    def test_get_default_integer_when_conversion_fails(self):
 
3772
        self.register_integer_option('foo', default='12')
 
3773
        conf = self.get_conf('foo=invalid integer')
 
3774
        self.assertEquals(12, conf.get('foo'))
 
3775
 
 
3776
    def register_list_option(self, name, default=None, default_from_env=None):
 
3777
        l = config.ListOption(name, help='A list.', default=default,
 
3778
                              default_from_env=default_from_env)
 
3779
        self.registry.register(l)
 
3780
 
 
3781
    def test_get_default_list_None(self):
 
3782
        self.register_list_option('foo')
 
3783
        conf = self.get_conf('')
 
3784
        self.assertEquals(None, conf.get('foo'))
 
3785
 
 
3786
    def test_get_default_list_empty(self):
 
3787
        self.register_list_option('foo', '')
 
3788
        conf = self.get_conf('')
 
3789
        self.assertEquals([], conf.get('foo'))
 
3790
 
 
3791
    def test_get_default_list_from_env(self):
 
3792
        self.register_list_option('foo', default_from_env=['FOO'])
 
3793
        self.overrideEnv('FOO', '')
 
3794
        conf = self.get_conf('')
 
3795
        self.assertEquals([], conf.get('foo'))
 
3796
 
 
3797
    def test_get_with_list_converter_no_item(self):
 
3798
        self.register_list_option('foo', None)
 
3799
        conf = self.get_conf('foo=,')
 
3800
        self.assertEquals([], conf.get('foo'))
 
3801
 
 
3802
    def test_get_with_list_converter_many_items(self):
 
3803
        self.register_list_option('foo', None)
 
3804
        conf = self.get_conf('foo=m,o,r,e')
 
3805
        self.assertEquals(['m', 'o', 'r', 'e'], conf.get('foo'))
 
3806
 
 
3807
    def test_get_with_list_converter_embedded_spaces_many_items(self):
 
3808
        self.register_list_option('foo', None)
 
3809
        conf = self.get_conf('foo=" bar", "baz "')
 
3810
        self.assertEquals([' bar', 'baz '], conf.get('foo'))
 
3811
 
 
3812
    def test_get_with_list_converter_stripped_spaces_many_items(self):
 
3813
        self.register_list_option('foo', None)
 
3814
        conf = self.get_conf('foo= bar ,  baz ')
 
3815
        self.assertEquals(['bar', 'baz'], conf.get('foo'))
 
3816
 
 
3817
 
 
3818
class TestIterOptionRefs(tests.TestCase):
 
3819
    """iter_option_refs is a bit unusual, document some cases."""
 
3820
 
 
3821
    def assertRefs(self, expected, string):
 
3822
        self.assertEquals(expected, list(config.iter_option_refs(string)))
 
3823
 
 
3824
    def test_empty(self):
 
3825
        self.assertRefs([(False, '')], '')
 
3826
 
 
3827
    def test_no_refs(self):
 
3828
        self.assertRefs([(False, 'foo bar')], 'foo bar')
 
3829
 
 
3830
    def test_single_ref(self):
 
3831
        self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
 
3832
 
 
3833
    def test_broken_ref(self):
 
3834
        self.assertRefs([(False, '{foo')], '{foo')
 
3835
 
 
3836
    def test_embedded_ref(self):
 
3837
        self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
 
3838
                        '{{foo}}')
 
3839
 
 
3840
    def test_two_refs(self):
 
3841
        self.assertRefs([(False, ''), (True, '{foo}'),
 
3842
                         (False, ''), (True, '{bar}'),
 
3843
                         (False, ''),],
 
3844
                        '{foo}{bar}')
 
3845
 
 
3846
    def test_newline_in_refs_are_not_matched(self):
 
3847
        self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
 
3848
 
 
3849
 
 
3850
class TestStackExpandOptions(tests.TestCaseWithTransport):
 
3851
 
 
3852
    def setUp(self):
 
3853
        super(TestStackExpandOptions, self).setUp()
 
3854
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
3855
        self.registry = config.option_registry
 
3856
        store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
 
3857
        self.conf = config.Stack([store.get_sections], store)
 
3858
 
 
3859
    def assertExpansion(self, expected, string, env=None):
 
3860
        self.assertEquals(expected, self.conf.expand_options(string, env))
 
3861
 
 
3862
    def test_no_expansion(self):
 
3863
        self.assertExpansion('foo', 'foo')
 
3864
 
 
3865
    def test_expand_default_value(self):
 
3866
        self.conf.store._load_from_string('bar=baz')
 
3867
        self.registry.register(config.Option('foo', default=u'{bar}'))
 
3868
        self.assertEquals('baz', self.conf.get('foo', expand=True))
 
3869
 
 
3870
    def test_expand_default_from_env(self):
 
3871
        self.conf.store._load_from_string('bar=baz')
 
3872
        self.registry.register(config.Option('foo', default_from_env=['FOO']))
 
3873
        self.overrideEnv('FOO', '{bar}')
 
3874
        self.assertEquals('baz', self.conf.get('foo', expand=True))
 
3875
 
 
3876
    def test_expand_default_on_failed_conversion(self):
 
3877
        self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
 
3878
        self.registry.register(
 
3879
            config.Option('foo', default=u'{bar}',
 
3880
                          from_unicode=config.int_from_store))
 
3881
        self.assertEquals(42, self.conf.get('foo', expand=True))
 
3882
 
 
3883
    def test_env_adding_options(self):
 
3884
        self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
 
3885
 
 
3886
    def test_env_overriding_options(self):
 
3887
        self.conf.store._load_from_string('foo=baz')
 
3888
        self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
 
3889
 
 
3890
    def test_simple_ref(self):
 
3891
        self.conf.store._load_from_string('foo=xxx')
 
3892
        self.assertExpansion('xxx', '{foo}')
 
3893
 
 
3894
    def test_unknown_ref(self):
 
3895
        self.assertRaises(errors.ExpandingUnknownOption,
 
3896
                          self.conf.expand_options, '{foo}')
 
3897
 
 
3898
    def test_indirect_ref(self):
 
3899
        self.conf.store._load_from_string('''
 
3900
foo=xxx
 
3901
bar={foo}
 
3902
''')
 
3903
        self.assertExpansion('xxx', '{bar}')
 
3904
 
 
3905
    def test_embedded_ref(self):
 
3906
        self.conf.store._load_from_string('''
 
3907
foo=xxx
 
3908
bar=foo
 
3909
''')
 
3910
        self.assertExpansion('xxx', '{{bar}}')
 
3911
 
 
3912
    def test_simple_loop(self):
 
3913
        self.conf.store._load_from_string('foo={foo}')
 
3914
        self.assertRaises(errors.OptionExpansionLoop,
 
3915
                          self.conf.expand_options, '{foo}')
 
3916
 
 
3917
    def test_indirect_loop(self):
 
3918
        self.conf.store._load_from_string('''
 
3919
foo={bar}
 
3920
bar={baz}
 
3921
baz={foo}''')
 
3922
        e = self.assertRaises(errors.OptionExpansionLoop,
 
3923
                              self.conf.expand_options, '{foo}')
 
3924
        self.assertEquals('foo->bar->baz', e.refs)
 
3925
        self.assertEquals('{foo}', e.string)
 
3926
 
 
3927
    def test_list(self):
 
3928
        self.conf.store._load_from_string('''
 
3929
foo=start
 
3930
bar=middle
 
3931
baz=end
 
3932
list={foo},{bar},{baz}
 
3933
''')
 
3934
        self.registry.register(
 
3935
            config.ListOption('list'))
 
3936
        self.assertEquals(['start', 'middle', 'end'],
 
3937
                           self.conf.get('list', expand=True))
 
3938
 
 
3939
    def test_cascading_list(self):
 
3940
        self.conf.store._load_from_string('''
 
3941
foo=start,{bar}
 
3942
bar=middle,{baz}
 
3943
baz=end
 
3944
list={foo}
 
3945
''')
 
3946
        self.registry.register(config.ListOption('list'))
 
3947
        # Register an intermediate option as a list to ensure no conversion
 
3948
        # happen while expanding. Conversion should only occur for the original
 
3949
        # option ('list' here).
 
3950
        self.registry.register(config.ListOption('baz'))
 
3951
        self.assertEquals(['start', 'middle', 'end'],
 
3952
                           self.conf.get('list', expand=True))
 
3953
 
 
3954
    def test_pathologically_hidden_list(self):
 
3955
        self.conf.store._load_from_string('''
 
3956
foo=bin
 
3957
bar=go
 
3958
start={foo
 
3959
middle=},{
 
3960
end=bar}
 
3961
hidden={start}{middle}{end}
 
3962
''')
 
3963
        # What matters is what the registration says, the conversion happens
 
3964
        # only after all expansions have been performed
 
3965
        self.registry.register(config.ListOption('hidden'))
 
3966
        self.assertEquals(['bin', 'go'],
 
3967
                          self.conf.get('hidden', expand=True))
 
3968
 
 
3969
 
 
3970
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
 
3971
 
 
3972
    def setUp(self):
 
3973
        super(TestStackCrossSectionsExpand, self).setUp()
 
3974
 
 
3975
    def get_config(self, location, string):
 
3976
        if string is None:
 
3977
            string = ''
 
3978
        # Since we don't save the config we won't strictly require to inherit
 
3979
        # from TestCaseInTempDir, but an error occurs so quickly...
 
3980
        c = config.LocationStack(location)
 
3981
        c.store._load_from_string(string)
 
3982
        return c
 
3983
 
 
3984
    def test_dont_cross_unrelated_section(self):
 
3985
        c = self.get_config('/another/branch/path','''
 
3986
[/one/branch/path]
 
3987
foo = hello
 
3988
bar = {foo}/2
 
3989
 
 
3990
[/another/branch/path]
 
3991
bar = {foo}/2
 
3992
''')
 
3993
        self.assertRaises(errors.ExpandingUnknownOption,
 
3994
                          c.get, 'bar', expand=True)
 
3995
 
 
3996
    def test_cross_related_sections(self):
 
3997
        c = self.get_config('/project/branch/path','''
 
3998
[/project]
 
3999
foo = qu
 
4000
 
 
4001
[/project/branch/path]
 
4002
bar = {foo}ux
 
4003
''')
 
4004
        self.assertEquals('quux', c.get('bar', expand=True))
 
4005
 
 
4006
 
 
4007
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
 
4008
 
 
4009
    def test_cross_global_locations(self):
 
4010
        l_store = config.LocationStore()
 
4011
        l_store._load_from_string('''
 
4012
[/branch]
 
4013
lfoo = loc-foo
 
4014
lbar = {gbar}
 
4015
''')
 
4016
        l_store.save()
 
4017
        g_store = config.GlobalStore()
 
4018
        g_store._load_from_string('''
 
4019
[DEFAULT]
 
4020
gfoo = {lfoo}
 
4021
gbar = glob-bar
 
4022
''')
 
4023
        g_store.save()
 
4024
        stack = config.LocationStack('/branch')
 
4025
        self.assertEquals('glob-bar', stack.get('lbar', expand=True))
 
4026
        self.assertEquals('loc-foo', stack.get('gfoo', expand=True))
 
4027
 
 
4028
 
 
4029
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
 
4030
 
 
4031
    def test_expand_locals_empty(self):
 
4032
        l_store = config.LocationStore()
 
4033
        l_store._load_from_string('''
 
4034
[/home/user/project]
 
4035
base = {basename}
 
4036
rel = {relpath}
 
4037
''')
 
4038
        l_store.save()
 
4039
        stack = config.LocationStack('/home/user/project/')
 
4040
        self.assertEquals('', stack.get('base', expand=True))
 
4041
        self.assertEquals('', stack.get('rel', expand=True))
 
4042
 
 
4043
    def test_expand_basename_locally(self):
 
4044
        l_store = config.LocationStore()
 
4045
        l_store._load_from_string('''
 
4046
[/home/user/project]
 
4047
bfoo = {basename}
 
4048
''')
 
4049
        l_store.save()
 
4050
        stack = config.LocationStack('/home/user/project/branch')
 
4051
        self.assertEquals('branch', stack.get('bfoo', expand=True))
 
4052
 
 
4053
    def test_expand_basename_locally_longer_path(self):
 
4054
        l_store = config.LocationStore()
 
4055
        l_store._load_from_string('''
 
4056
[/home/user]
 
4057
bfoo = {basename}
 
4058
''')
 
4059
        l_store.save()
 
4060
        stack = config.LocationStack('/home/user/project/dir/branch')
 
4061
        self.assertEquals('branch', stack.get('bfoo', expand=True))
 
4062
 
 
4063
    def test_expand_relpath_locally(self):
 
4064
        l_store = config.LocationStore()
 
4065
        l_store._load_from_string('''
 
4066
[/home/user/project]
 
4067
lfoo = loc-foo/{relpath}
 
4068
''')
 
4069
        l_store.save()
 
4070
        stack = config.LocationStack('/home/user/project/branch')
 
4071
        self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
 
4072
 
 
4073
    def test_expand_relpath_unknonw_in_global(self):
 
4074
        g_store = config.GlobalStore()
 
4075
        g_store._load_from_string('''
 
4076
[DEFAULT]
 
4077
gfoo = {relpath}
 
4078
''')
 
4079
        g_store.save()
 
4080
        stack = config.LocationStack('/home/user/project/branch')
 
4081
        self.assertRaises(errors.ExpandingUnknownOption,
 
4082
                          stack.get, 'gfoo', expand=True)
 
4083
 
 
4084
    def test_expand_local_option_locally(self):
 
4085
        l_store = config.LocationStore()
 
4086
        l_store._load_from_string('''
 
4087
[/home/user/project]
 
4088
lfoo = loc-foo/{relpath}
 
4089
lbar = {gbar}
 
4090
''')
 
4091
        l_store.save()
 
4092
        g_store = config.GlobalStore()
 
4093
        g_store._load_from_string('''
 
4094
[DEFAULT]
 
4095
gfoo = {lfoo}
 
4096
gbar = glob-bar
 
4097
''')
 
4098
        g_store.save()
 
4099
        stack = config.LocationStack('/home/user/project/branch')
 
4100
        self.assertEquals('glob-bar', stack.get('lbar', expand=True))
 
4101
        self.assertEquals('loc-foo/branch', stack.get('gfoo', expand=True))
 
4102
 
 
4103
    def test_locals_dont_leak(self):
 
4104
        """Make sure we chose the right local in presence of several sections.
 
4105
        """
 
4106
        l_store = config.LocationStore()
 
4107
        l_store._load_from_string('''
 
4108
[/home/user]
 
4109
lfoo = loc-foo/{relpath}
 
4110
[/home/user/project]
 
4111
lfoo = loc-foo/{relpath}
 
4112
''')
 
4113
        l_store.save()
 
4114
        stack = config.LocationStack('/home/user/project/branch')
 
4115
        self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
 
4116
        stack = config.LocationStack('/home/user/bar/baz')
 
4117
        self.assertEquals('loc-foo/bar/baz', stack.get('lfoo', expand=True))
 
4118
 
2933
4119
 
2934
4120
 
2935
4121
class TestStackSet(TestStackWithTransport):
2936
4122
 
2937
4123
    def test_simple_set(self):
2938
4124
        conf = self.get_stack(self)
2939
 
        conf.store._load_from_string('foo=bar')
2940
 
        self.assertEquals('bar', conf.get('foo'))
 
4125
        self.assertEquals(None, conf.get('foo'))
2941
4126
        conf.set('foo', 'baz')
2942
4127
        # Did we get it back ?
2943
4128
        self.assertEquals('baz', conf.get('foo'))
2963
4148
 
2964
4149
    def test_remove_existing(self):
2965
4150
        conf = self.get_stack(self)
2966
 
        conf.store._load_from_string('foo=bar')
 
4151
        conf.set('foo', 'bar')
2967
4152
        self.assertEquals('bar', conf.get('foo'))
2968
4153
        conf.remove('foo')
2969
4154
        # Did we get it back ?
2980
4165
        config.ConfigHooks.install_named_hook('remove', hook, None)
2981
4166
        self.assertLength(0, calls)
2982
4167
        conf = self.get_stack(self)
2983
 
        conf.store._load_from_string('foo=bar')
 
4168
        conf.set('foo', 'bar')
2984
4169
        conf.remove('foo')
2985
4170
        self.assertLength(1, calls)
2986
4171
        self.assertEquals((conf, 'foo'), calls[0])
3161
4346
        conf = config.AuthenticationConfig(_file=StringIO(
3162
4347
                'foo = bar\xff'))
3163
4348
        self.assertRaises(errors.ConfigContentError, conf._get_config)
3164
 
        
 
4349
 
3165
4350
    def test_missing_auth_section_header(self):
3166
4351
        conf = config.AuthenticationConfig(_file=StringIO('foo = bar'))
3167
4352
        self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
3648
4833
 
3649
4834
    def test_auto_user_id(self):
3650
4835
        """Automatic inference of user name.
3651
 
        
 
4836
 
3652
4837
        This is a bit hard to test in an isolated way, because it depends on
3653
4838
        system functions that go direct to /etc or perhaps somewhere else.
3654
4839
        But it's reasonable to say that on Unix, with an /etc/mailname, we ought
3664
4849
        else:
3665
4850
            self.assertEquals((None, None), (realname, address))
3666
4851
 
 
4852
 
 
4853
class EmailOptionTests(tests.TestCase):
 
4854
 
 
4855
    def test_default_email_uses_BZR_EMAIL(self):
 
4856
        conf = config.MemoryStack('email=jelmer@debian.org')
 
4857
        # BZR_EMAIL takes precedence over EMAIL
 
4858
        self.overrideEnv('BZR_EMAIL', 'jelmer@samba.org')
 
4859
        self.overrideEnv('EMAIL', 'jelmer@apache.org')
 
4860
        self.assertEquals('jelmer@samba.org', conf.get('email'))
 
4861
 
 
4862
    def test_default_email_uses_EMAIL(self):
 
4863
        conf = config.MemoryStack('')
 
4864
        self.overrideEnv('BZR_EMAIL', None)
 
4865
        self.overrideEnv('EMAIL', 'jelmer@apache.org')
 
4866
        self.assertEquals('jelmer@apache.org', conf.get('email'))
 
4867
 
 
4868
    def test_BZR_EMAIL_overrides(self):
 
4869
        conf = config.MemoryStack('email=jelmer@debian.org')
 
4870
        self.overrideEnv('BZR_EMAIL', 'jelmer@apache.org')
 
4871
        self.assertEquals('jelmer@apache.org', conf.get('email'))
 
4872
        self.overrideEnv('BZR_EMAIL', None)
 
4873
        self.overrideEnv('EMAIL', 'jelmer@samba.org')
 
4874
        self.assertEquals('jelmer@debian.org', conf.get('email'))
 
4875
 
 
4876
 
 
4877
class MailClientOptionTests(tests.TestCase):
 
4878
 
 
4879
    def test_default(self):
 
4880
        conf = config.MemoryStack('')
 
4881
        client = conf.get('mail_client')
 
4882
        self.assertIs(client, mail_client.DefaultMail)
 
4883
 
 
4884
    def test_evolution(self):
 
4885
        conf = config.MemoryStack('mail_client=evolution')
 
4886
        client = conf.get('mail_client')
 
4887
        self.assertIs(client, mail_client.Evolution)
 
4888
 
 
4889
    def test_kmail(self):
 
4890
        conf = config.MemoryStack('mail_client=kmail')
 
4891
        client = conf.get('mail_client')
 
4892
        self.assertIs(client, mail_client.KMail)
 
4893
 
 
4894
    def test_mutt(self):
 
4895
        conf = config.MemoryStack('mail_client=mutt')
 
4896
        client = conf.get('mail_client')
 
4897
        self.assertIs(client, mail_client.Mutt)
 
4898
 
 
4899
    def test_thunderbird(self):
 
4900
        conf = config.MemoryStack('mail_client=thunderbird')
 
4901
        client = conf.get('mail_client')
 
4902
        self.assertIs(client, mail_client.Thunderbird)
 
4903
 
 
4904
    def test_explicit_default(self):
 
4905
        conf = config.MemoryStack('mail_client=default')
 
4906
        client = conf.get('mail_client')
 
4907
        self.assertIs(client, mail_client.DefaultMail)
 
4908
 
 
4909
    def test_editor(self):
 
4910
        conf = config.MemoryStack('mail_client=editor')
 
4911
        client = conf.get('mail_client')
 
4912
        self.assertIs(client, mail_client.Editor)
 
4913
 
 
4914
    def test_mapi(self):
 
4915
        conf = config.MemoryStack('mail_client=mapi')
 
4916
        client = conf.get('mail_client')
 
4917
        self.assertIs(client, mail_client.MAPIClient)
 
4918
 
 
4919
    def test_xdg_email(self):
 
4920
        conf = config.MemoryStack('mail_client=xdg-email')
 
4921
        client = conf.get('mail_client')
 
4922
        self.assertIs(client, mail_client.XDGEmail)
 
4923
 
 
4924
    def test_unknown(self):
 
4925
        conf = config.MemoryStack('mail_client=firebird')
 
4926
        self.assertRaises(errors.ConfigOptionValueError, conf.get,
 
4927
                'mail_client')