/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_config.py

  • Committer: Jelmer Vernooij
  • Date: 2012-02-06 11:36:02 UTC
  • mfrom: (6462 +trunk)
  • mto: This revision was merged to the branch mainline in revision 6463.
  • Revision ID: jelmer@samba.org-20120206113602-yu3j0xe7qbk1szw9
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 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
19
19
from cStringIO import StringIO
20
20
import os
21
21
import sys
 
22
import threading
 
23
 
 
24
 
 
25
from testtools import matchers
22
26
 
23
27
#import bzrlib specific imports here
24
28
from bzrlib import (
31
35
    mail_client,
32
36
    ui,
33
37
    urlutils,
 
38
    registry as _mod_registry,
 
39
    remote,
34
40
    tests,
35
41
    trace,
36
 
    transport,
 
42
    )
 
43
from bzrlib.symbol_versioning import (
 
44
    deprecated_in,
 
45
    )
 
46
from bzrlib.transport import remote as transport_remote
 
47
from bzrlib.tests import (
 
48
    features,
 
49
    scenarios,
 
50
    test_server,
37
51
    )
38
52
from bzrlib.util.configobj import configobj
39
53
 
40
54
 
 
55
def lockable_config_scenarios():
 
56
    return [
 
57
        ('global',
 
58
         {'config_class': config.GlobalConfig,
 
59
          'config_args': [],
 
60
          'config_section': 'DEFAULT'}),
 
61
        ('locations',
 
62
         {'config_class': config.LocationConfig,
 
63
          'config_args': ['.'],
 
64
          'config_section': '.'}),]
 
65
 
 
66
 
 
67
load_tests = scenarios.load_tests_apply_scenarios
 
68
 
 
69
# Register helpers to build stores
 
70
config.test_store_builder_registry.register(
 
71
    'configobj', lambda test: config.TransportIniFileStore(
 
72
        test.get_transport(), 'configobj.conf'))
 
73
config.test_store_builder_registry.register(
 
74
    'bazaar', lambda test: config.GlobalStore())
 
75
config.test_store_builder_registry.register(
 
76
    'location', lambda test: config.LocationStore())
 
77
 
 
78
 
 
79
def build_backing_branch(test, relpath,
 
80
                         transport_class=None, server_class=None):
 
81
    """Test helper to create a backing branch only once.
 
82
 
 
83
    Some tests needs multiple stores/stacks to check concurrent update
 
84
    behaviours. As such, they need to build different branch *objects* even if
 
85
    they share the branch on disk.
 
86
 
 
87
    :param relpath: The relative path to the branch. (Note that the helper
 
88
        should always specify the same relpath).
 
89
 
 
90
    :param transport_class: The Transport class the test needs to use.
 
91
 
 
92
    :param server_class: The server associated with the ``transport_class``
 
93
        above.
 
94
 
 
95
    Either both or neither of ``transport_class`` and ``server_class`` should
 
96
    be specified.
 
97
    """
 
98
    if transport_class is not None and server_class is not None:
 
99
        test.transport_class = transport_class
 
100
        test.transport_server = server_class
 
101
    elif not (transport_class is None and server_class is None):
 
102
        raise AssertionError('Specify both ``transport_class`` and '
 
103
                             '``server_class`` or neither of them')
 
104
    if getattr(test, 'backing_branch', None) is None:
 
105
        # First call, let's build the branch on disk
 
106
        test.backing_branch = test.make_branch(relpath)
 
107
 
 
108
 
 
109
def build_branch_store(test):
 
110
    build_backing_branch(test, 'branch')
 
111
    b = branch.Branch.open('branch')
 
112
    return config.BranchStore(b)
 
113
config.test_store_builder_registry.register('branch', build_branch_store)
 
114
 
 
115
 
 
116
def build_control_store(test):
 
117
    build_backing_branch(test, 'branch')
 
118
    b = bzrdir.BzrDir.open('branch')
 
119
    return config.ControlStore(b)
 
120
config.test_store_builder_registry.register('control', build_control_store)
 
121
 
 
122
 
 
123
def build_remote_branch_store(test):
 
124
    # There is only one permutation (but we won't be able to handle more with
 
125
    # this design anyway)
 
126
    (transport_class,
 
127
     server_class) = transport_remote.get_test_permutations()[0]
 
128
    build_backing_branch(test, 'branch', transport_class, server_class)
 
129
    b = branch.Branch.open(test.get_url('branch'))
 
130
    return config.BranchStore(b)
 
131
config.test_store_builder_registry.register('remote_branch',
 
132
                                            build_remote_branch_store)
 
133
 
 
134
 
 
135
config.test_stack_builder_registry.register(
 
136
    'bazaar', lambda test: config.GlobalStack())
 
137
config.test_stack_builder_registry.register(
 
138
    'location', lambda test: config.LocationStack('.'))
 
139
 
 
140
 
 
141
def build_branch_stack(test):
 
142
    build_backing_branch(test, 'branch')
 
143
    b = branch.Branch.open('branch')
 
144
    return config.BranchStack(b)
 
145
config.test_stack_builder_registry.register('branch', build_branch_stack)
 
146
 
 
147
 
 
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)
 
171
 
 
172
 
41
173
sample_long_alias="log -r-15..-1 --line"
42
174
sample_config_text = u"""
43
175
[DEFAULT]
45
177
editor=vim
46
178
change_editor=vimdiff -of @new_path @old_path
47
179
gpg_signing_command=gnome-gpg
 
180
gpg_signing_key=DD4D5088
48
181
log_format=short
 
182
validate_signatures_in_log=true
 
183
acceptable_keys=amy
49
184
user_global_option=something
 
185
bzr.mergetool.sometool=sometool {base} {this} {other} -o {result}
 
186
bzr.mergetool.funkytool=funkytool "arg with spaces" {this_temp}
 
187
bzr.mergetool.newtool='"newtool with spaces" {this_temp}'
 
188
bzr.default_mergetool=sometool
50
189
[ALIASES]
51
190
h=help
52
191
ll=""" + sample_long_alias + "\n"
94
233
[/a/]
95
234
check_signatures=check-available
96
235
gpg_signing_command=false
 
236
gpg_signing_key=default
97
237
user_local_option=local
98
238
# test trailing / matching
99
239
[/a/*]
105
245
"""
106
246
 
107
247
 
 
248
def create_configs(test):
 
249
    """Create configuration files for a given test.
 
250
 
 
251
    This requires creating a tree (and populate the ``test.tree`` attribute)
 
252
    and its associated branch and will populate the following attributes:
 
253
 
 
254
    - branch_config: A BranchConfig for the associated branch.
 
255
 
 
256
    - locations_config : A LocationConfig for the associated branch
 
257
 
 
258
    - bazaar_config: A GlobalConfig.
 
259
 
 
260
    The tree and branch are created in a 'tree' subdirectory so the tests can
 
261
    still use the test directory to stay outside of the branch.
 
262
    """
 
263
    tree = test.make_branch_and_tree('tree')
 
264
    test.tree = tree
 
265
    test.branch_config = config.BranchConfig(tree.branch)
 
266
    test.locations_config = config.LocationConfig(tree.basedir)
 
267
    test.bazaar_config = config.GlobalConfig()
 
268
 
 
269
 
 
270
def create_configs_with_file_option(test):
 
271
    """Create configuration files with a ``file`` option set in each.
 
272
 
 
273
    This builds on ``create_configs`` and add one ``file`` option in each
 
274
    configuration with a value which allows identifying the configuration file.
 
275
    """
 
276
    create_configs(test)
 
277
    test.bazaar_config.set_user_option('file', 'bazaar')
 
278
    test.locations_config.set_user_option('file', 'locations')
 
279
    test.branch_config.set_user_option('file', 'branch')
 
280
 
 
281
 
 
282
class TestOptionsMixin:
 
283
 
 
284
    def assertOptions(self, expected, conf):
 
285
        # We don't care about the parser (as it will make tests hard to write
 
286
        # and error-prone anyway)
 
287
        self.assertThat([opt[:4] for opt in conf._get_options()],
 
288
                        matchers.Equals(expected))
 
289
 
 
290
 
108
291
class InstrumentedConfigObj(object):
109
292
    """A config obj look-enough-alike to record calls made to it."""
110
293
 
129
312
        self._calls.append(('keys',))
130
313
        return []
131
314
 
 
315
    def reload(self):
 
316
        self._calls.append(('reload',))
 
317
 
132
318
    def write(self, arg):
133
319
        self._calls.append(('write',))
134
320
 
143
329
 
144
330
class FakeBranch(object):
145
331
 
146
 
    def __init__(self, base=None, user_id=None):
 
332
    def __init__(self, base=None):
147
333
        if base is None:
148
334
            self.base = "http://example.com/branches/demo"
149
335
        else:
150
336
            self.base = base
151
337
        self._transport = self.control_files = \
152
 
            FakeControlFilesAndTransport(user_id=user_id)
 
338
            FakeControlFilesAndTransport()
153
339
 
154
340
    def _get_config(self):
155
341
        return config.TransportConfig(self._transport, 'branch.conf')
163
349
 
164
350
class FakeControlFilesAndTransport(object):
165
351
 
166
 
    def __init__(self, user_id=None):
 
352
    def __init__(self):
167
353
        self.files = {}
168
 
        if user_id:
169
 
            self.files['email'] = user_id
170
354
        self._transport = self
171
355
 
172
 
    def get_utf8(self, filename):
173
 
        # from LockableFiles
174
 
        raise AssertionError("get_utf8 should no longer be used")
175
 
 
176
356
    def get(self, filename):
177
357
        # from Transport
178
358
        try:
240
420
        """
241
421
        co = config.ConfigObj()
242
422
        co['test'] = 'foo#bar'
243
 
        lines = co.write()
 
423
        outfile = StringIO()
 
424
        co.write(outfile=outfile)
 
425
        lines = outfile.getvalue().splitlines()
244
426
        self.assertEqual(lines, ['test = "foo#bar"'])
245
427
        co2 = config.ConfigObj(lines)
246
428
        self.assertEqual(co2['test'], 'foo#bar')
247
429
 
 
430
    def test_triple_quotes(self):
 
431
        # Bug #710410: if the value string has triple quotes
 
432
        # then ConfigObj versions up to 4.7.2 will quote them wrong
 
433
        # and won't able to read them back
 
434
        triple_quotes_value = '''spam
 
435
""" that's my spam """
 
436
eggs'''
 
437
        co = config.ConfigObj()
 
438
        co['test'] = triple_quotes_value
 
439
        # While writing this test another bug in ConfigObj has been found:
 
440
        # method co.write() without arguments produces list of lines
 
441
        # one option per line, and multiline values are not split
 
442
        # across multiple lines,
 
443
        # and that breaks the parsing these lines back by ConfigObj.
 
444
        # This issue only affects test, but it's better to avoid
 
445
        # `co.write()` construct at all.
 
446
        # [bialix 20110222] bug report sent to ConfigObj's author
 
447
        outfile = StringIO()
 
448
        co.write(outfile=outfile)
 
449
        output = outfile.getvalue()
 
450
        # now we're trying to read it back
 
451
        co2 = config.ConfigObj(StringIO(output))
 
452
        self.assertEquals(triple_quotes_value, co2['test'])
 
453
 
248
454
 
249
455
erroneous_config = """[section] # line 1
250
456
good=good # line 2
271
477
        config.Config()
272
478
 
273
479
    def test_no_default_editor(self):
274
 
        self.assertRaises(NotImplementedError, config.Config().get_editor)
 
480
        self.assertRaises(
 
481
            NotImplementedError,
 
482
            self.applyDeprecated, deprecated_in((2, 4, 0)),
 
483
            config.Config().get_editor)
275
484
 
276
485
    def test_user_email(self):
277
486
        my_config = InstrumentedConfig()
286
495
 
287
496
    def test_signatures_default(self):
288
497
        my_config = config.Config()
289
 
        self.assertFalse(my_config.signature_needed())
 
498
        self.assertFalse(
 
499
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
500
                my_config.signature_needed))
290
501
        self.assertEqual(config.CHECK_IF_POSSIBLE,
291
 
                         my_config.signature_checking())
 
502
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
503
                my_config.signature_checking))
292
504
        self.assertEqual(config.SIGN_WHEN_REQUIRED,
293
 
                         my_config.signing_policy())
 
505
                self.applyDeprecated(deprecated_in((2, 5, 0)),
 
506
                    my_config.signing_policy))
294
507
 
295
508
    def test_signatures_template_method(self):
296
509
        my_config = InstrumentedConfig()
297
 
        self.assertEqual(config.CHECK_NEVER, my_config.signature_checking())
 
510
        self.assertEqual(config.CHECK_NEVER,
 
511
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
512
                my_config.signature_checking))
298
513
        self.assertEqual(['_get_signature_checking'], my_config._calls)
299
514
 
300
515
    def test_signatures_template_method_none(self):
301
516
        my_config = InstrumentedConfig()
302
517
        my_config._signatures = None
303
518
        self.assertEqual(config.CHECK_IF_POSSIBLE,
304
 
                         my_config.signature_checking())
 
519
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
520
                             my_config.signature_checking))
305
521
        self.assertEqual(['_get_signature_checking'], my_config._calls)
306
522
 
307
523
    def test_gpg_signing_command_default(self):
308
524
        my_config = config.Config()
309
 
        self.assertEqual('gpg', my_config.gpg_signing_command())
 
525
        self.assertEqual('gpg',
 
526
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
527
                my_config.gpg_signing_command))
310
528
 
311
529
    def test_get_user_option_default(self):
312
530
        my_config = config.Config()
314
532
 
315
533
    def test_post_commit_default(self):
316
534
        my_config = config.Config()
317
 
        self.assertEqual(None, my_config.post_commit())
 
535
        self.assertEqual(None, self.applyDeprecated(deprecated_in((2, 5, 0)),
 
536
                                                    my_config.post_commit))
 
537
 
318
538
 
319
539
    def test_log_format_default(self):
320
540
        my_config = config.Config()
321
 
        self.assertEqual('long', my_config.log_format())
 
541
        self.assertEqual('long',
 
542
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
543
                                              my_config.log_format))
 
544
 
 
545
    def test_acceptable_keys_default(self):
 
546
        my_config = config.Config()
 
547
        self.assertEqual(None, self.applyDeprecated(deprecated_in((2, 5, 0)),
 
548
            my_config.acceptable_keys))
 
549
 
 
550
    def test_validate_signatures_in_log_default(self):
 
551
        my_config = config.Config()
 
552
        self.assertEqual(False, my_config.validate_signatures_in_log())
322
553
 
323
554
    def test_get_change_editor(self):
324
555
        my_config = InstrumentedConfig()
333
564
 
334
565
    def setUp(self):
335
566
        super(TestConfigPath, self).setUp()
336
 
        os.environ['HOME'] = '/home/bogus'
337
 
        os.environ['XDG_CACHE_DIR'] = ''
 
567
        self.overrideEnv('HOME', '/home/bogus')
 
568
        self.overrideEnv('XDG_CACHE_DIR', '')
338
569
        if sys.platform == 'win32':
339
 
            os.environ['BZR_HOME'] = \
340
 
                r'C:\Documents and Settings\bogus\Application Data'
 
570
            self.overrideEnv(
 
571
                'BZR_HOME', r'C:\Documents and Settings\bogus\Application Data')
341
572
            self.bzr_home = \
342
573
                'C:/Documents and Settings/bogus/Application Data/bazaar/2.0'
343
574
        else:
346
577
    def test_config_dir(self):
347
578
        self.assertEqual(config.config_dir(), self.bzr_home)
348
579
 
 
580
    def test_config_dir_is_unicode(self):
 
581
        self.assertIsInstance(config.config_dir(), unicode)
 
582
 
349
583
    def test_config_filename(self):
350
584
        self.assertEqual(config.config_filename(),
351
585
                         self.bzr_home + '/bazaar.conf')
352
586
 
353
 
    def test_branches_config_filename(self):
354
 
        self.assertEqual(config.branches_config_filename(),
355
 
                         self.bzr_home + '/branches.conf')
356
 
 
357
587
    def test_locations_config_filename(self):
358
588
        self.assertEqual(config.locations_config_filename(),
359
589
                         self.bzr_home + '/locations.conf')
367
597
            '/home/bogus/.cache')
368
598
 
369
599
 
370
 
class TestIniConfig(tests.TestCase):
 
600
class TestXDGConfigDir(tests.TestCaseInTempDir):
 
601
    # must be in temp dir because config tests for the existence of the bazaar
 
602
    # subdirectory of $XDG_CONFIG_HOME
 
603
 
 
604
    def setUp(self):
 
605
        if sys.platform in ('darwin', 'win32'):
 
606
            raise tests.TestNotApplicable(
 
607
                'XDG config dir not used on this platform')
 
608
        super(TestXDGConfigDir, self).setUp()
 
609
        self.overrideEnv('HOME', self.test_home_dir)
 
610
        # BZR_HOME overrides everything we want to test so unset it.
 
611
        self.overrideEnv('BZR_HOME', None)
 
612
 
 
613
    def test_xdg_config_dir_exists(self):
 
614
        """When ~/.config/bazaar exists, use it as the config dir."""
 
615
        newdir = osutils.pathjoin(self.test_home_dir, '.config', 'bazaar')
 
616
        os.makedirs(newdir)
 
617
        self.assertEqual(config.config_dir(), newdir)
 
618
 
 
619
    def test_xdg_config_home(self):
 
620
        """When XDG_CONFIG_HOME is set, use it."""
 
621
        xdgconfigdir = osutils.pathjoin(self.test_home_dir, 'xdgconfig')
 
622
        self.overrideEnv('XDG_CONFIG_HOME', xdgconfigdir)
 
623
        newdir = osutils.pathjoin(xdgconfigdir, 'bazaar')
 
624
        os.makedirs(newdir)
 
625
        self.assertEqual(config.config_dir(), newdir)
 
626
 
 
627
 
 
628
class TestIniConfig(tests.TestCaseInTempDir):
371
629
 
372
630
    def make_config_parser(self, s):
373
 
        conf = config.IniBasedConfig(None)
374
 
        parser = conf._get_parser(file=StringIO(s.encode('utf-8')))
375
 
        return conf, parser
 
631
        conf = config.IniBasedConfig.from_string(s)
 
632
        return conf, conf._get_parser()
376
633
 
377
634
 
378
635
class TestIniConfigBuilding(TestIniConfig):
379
636
 
380
637
    def test_contructs(self):
381
 
        my_config = config.IniBasedConfig("nothing")
 
638
        my_config = config.IniBasedConfig()
382
639
 
383
640
    def test_from_fp(self):
384
 
        config_file = StringIO(sample_config_text.encode('utf-8'))
385
 
        my_config = config.IniBasedConfig(None)
386
 
        self.failUnless(
387
 
            isinstance(my_config._get_parser(file=config_file),
388
 
                        configobj.ConfigObj))
 
641
        my_config = config.IniBasedConfig.from_string(sample_config_text)
 
642
        self.assertIsInstance(my_config._get_parser(), configobj.ConfigObj)
389
643
 
390
644
    def test_cached(self):
 
645
        my_config = config.IniBasedConfig.from_string(sample_config_text)
 
646
        parser = my_config._get_parser()
 
647
        self.assertTrue(my_config._get_parser() is parser)
 
648
 
 
649
    def _dummy_chown(self, path, uid, gid):
 
650
        self.path, self.uid, self.gid = path, uid, gid
 
651
 
 
652
    def test_ini_config_ownership(self):
 
653
        """Ensure that chown is happening during _write_config_file"""
 
654
        self.requireFeature(features.chown_feature)
 
655
        self.overrideAttr(os, 'chown', self._dummy_chown)
 
656
        self.path = self.uid = self.gid = None
 
657
        conf = config.IniBasedConfig(file_name='./foo.conf')
 
658
        conf._write_config_file()
 
659
        self.assertEquals(self.path, './foo.conf')
 
660
        self.assertTrue(isinstance(self.uid, int))
 
661
        self.assertTrue(isinstance(self.gid, int))
 
662
 
 
663
    def test_get_filename_parameter_is_deprecated_(self):
 
664
        conf = self.callDeprecated([
 
665
            'IniBasedConfig.__init__(get_filename) was deprecated in 2.3.'
 
666
            ' Use file_name instead.'],
 
667
            config.IniBasedConfig, lambda: 'ini.conf')
 
668
        self.assertEqual('ini.conf', conf.file_name)
 
669
 
 
670
    def test_get_parser_file_parameter_is_deprecated_(self):
391
671
        config_file = StringIO(sample_config_text.encode('utf-8'))
392
 
        my_config = config.IniBasedConfig(None)
393
 
        parser = my_config._get_parser(file=config_file)
394
 
        self.failUnless(my_config._get_parser() is parser)
 
672
        conf = config.IniBasedConfig.from_string(sample_config_text)
 
673
        conf = self.callDeprecated([
 
674
            'IniBasedConfig._get_parser(file=xxx) was deprecated in 2.3.'
 
675
            ' Use IniBasedConfig(_content=xxx) instead.'],
 
676
            conf._get_parser, file=config_file)
 
677
 
 
678
 
 
679
class TestIniConfigSaving(tests.TestCaseInTempDir):
 
680
 
 
681
    def test_cant_save_without_a_file_name(self):
 
682
        conf = config.IniBasedConfig()
 
683
        self.assertRaises(AssertionError, conf._write_config_file)
 
684
 
 
685
    def test_saved_with_content(self):
 
686
        content = 'foo = bar\n'
 
687
        conf = config.IniBasedConfig.from_string(
 
688
            content, file_name='./test.conf', save=True)
 
689
        self.assertFileEqual(content, 'test.conf')
 
690
 
 
691
 
 
692
class TestIniConfigOptionExpansionDefaultValue(tests.TestCaseInTempDir):
 
693
    """What is the default value of expand for config options.
 
694
 
 
695
    This is an opt-in beta feature used to evaluate whether or not option
 
696
    references can appear in dangerous place raising exceptions, disapearing
 
697
    (and as such corrupting data) or if it's safe to activate the option by
 
698
    default.
 
699
 
 
700
    Note that these tests relies on config._expand_default_value being already
 
701
    overwritten in the parent class setUp.
 
702
    """
 
703
 
 
704
    def setUp(self):
 
705
        super(TestIniConfigOptionExpansionDefaultValue, self).setUp()
 
706
        self.config = None
 
707
        self.warnings = []
 
708
        def warning(*args):
 
709
            self.warnings.append(args[0] % args[1:])
 
710
        self.overrideAttr(trace, 'warning', warning)
 
711
 
 
712
    def get_config(self, expand):
 
713
        c = config.GlobalConfig.from_string('bzr.config.expand=%s' % (expand,),
 
714
                                            save=True)
 
715
        return c
 
716
 
 
717
    def assertExpandIs(self, expected):
 
718
        actual = config._get_expand_default_value()
 
719
        #self.config.get_user_option_as_bool('bzr.config.expand')
 
720
        self.assertEquals(expected, actual)
 
721
 
 
722
    def test_default_is_None(self):
 
723
        self.assertEquals(None, config._expand_default_value)
 
724
 
 
725
    def test_default_is_False_even_if_None(self):
 
726
        self.config = self.get_config(None)
 
727
        self.assertExpandIs(False)
 
728
 
 
729
    def test_default_is_False_even_if_invalid(self):
 
730
        self.config = self.get_config('<your choice>')
 
731
        self.assertExpandIs(False)
 
732
        # ...
 
733
        # Huh ? My choice is False ? Thanks, always happy to hear that :D
 
734
        # Wait, you've been warned !
 
735
        self.assertLength(1, self.warnings)
 
736
        self.assertEquals(
 
737
            'Value "<your choice>" is not a boolean for "bzr.config.expand"',
 
738
            self.warnings[0])
 
739
 
 
740
    def test_default_is_True(self):
 
741
        self.config = self.get_config(True)
 
742
        self.assertExpandIs(True)
 
743
 
 
744
    def test_default_is_False(self):
 
745
        self.config = self.get_config(False)
 
746
        self.assertExpandIs(False)
 
747
 
 
748
 
 
749
class TestIniConfigOptionExpansion(tests.TestCase):
 
750
    """Test option expansion from the IniConfig level.
 
751
 
 
752
    What we really want here is to test the Config level, but the class being
 
753
    abstract as far as storing values is concerned, this can't be done
 
754
    properly (yet).
 
755
    """
 
756
    # FIXME: This should be rewritten when all configs share a storage
 
757
    # implementation -- vila 2011-02-18
 
758
 
 
759
    def get_config(self, string=None):
 
760
        if string is None:
 
761
            string = ''
 
762
        c = config.IniBasedConfig.from_string(string)
 
763
        return c
 
764
 
 
765
    def assertExpansion(self, expected, conf, string, env=None):
 
766
        self.assertEquals(expected, conf.expand_options(string, env))
 
767
 
 
768
    def test_no_expansion(self):
 
769
        c = self.get_config('')
 
770
        self.assertExpansion('foo', c, 'foo')
 
771
 
 
772
    def test_env_adding_options(self):
 
773
        c = self.get_config('')
 
774
        self.assertExpansion('bar', c, '{foo}', {'foo': 'bar'})
 
775
 
 
776
    def test_env_overriding_options(self):
 
777
        c = self.get_config('foo=baz')
 
778
        self.assertExpansion('bar', c, '{foo}', {'foo': 'bar'})
 
779
 
 
780
    def test_simple_ref(self):
 
781
        c = self.get_config('foo=xxx')
 
782
        self.assertExpansion('xxx', c, '{foo}')
 
783
 
 
784
    def test_unknown_ref(self):
 
785
        c = self.get_config('')
 
786
        self.assertRaises(errors.ExpandingUnknownOption,
 
787
                          c.expand_options, '{foo}')
 
788
 
 
789
    def test_indirect_ref(self):
 
790
        c = self.get_config('''
 
791
foo=xxx
 
792
bar={foo}
 
793
''')
 
794
        self.assertExpansion('xxx', c, '{bar}')
 
795
 
 
796
    def test_embedded_ref(self):
 
797
        c = self.get_config('''
 
798
foo=xxx
 
799
bar=foo
 
800
''')
 
801
        self.assertExpansion('xxx', c, '{{bar}}')
 
802
 
 
803
    def test_simple_loop(self):
 
804
        c = self.get_config('foo={foo}')
 
805
        self.assertRaises(errors.OptionExpansionLoop, c.expand_options, '{foo}')
 
806
 
 
807
    def test_indirect_loop(self):
 
808
        c = self.get_config('''
 
809
foo={bar}
 
810
bar={baz}
 
811
baz={foo}''')
 
812
        e = self.assertRaises(errors.OptionExpansionLoop,
 
813
                              c.expand_options, '{foo}')
 
814
        self.assertEquals('foo->bar->baz', e.refs)
 
815
        self.assertEquals('{foo}', e.string)
 
816
 
 
817
    def test_list(self):
 
818
        conf = self.get_config('''
 
819
foo=start
 
820
bar=middle
 
821
baz=end
 
822
list={foo},{bar},{baz}
 
823
''')
 
824
        self.assertEquals(['start', 'middle', 'end'],
 
825
                           conf.get_user_option('list', expand=True))
 
826
 
 
827
    def test_cascading_list(self):
 
828
        conf = self.get_config('''
 
829
foo=start,{bar}
 
830
bar=middle,{baz}
 
831
baz=end
 
832
list={foo}
 
833
''')
 
834
        self.assertEquals(['start', 'middle', 'end'],
 
835
                           conf.get_user_option('list', expand=True))
 
836
 
 
837
    def test_pathological_hidden_list(self):
 
838
        conf = self.get_config('''
 
839
foo=bin
 
840
bar=go
 
841
start={foo
 
842
middle=},{
 
843
end=bar}
 
844
hidden={start}{middle}{end}
 
845
''')
 
846
        # Nope, it's either a string or a list, and the list wins as soon as a
 
847
        # ',' appears, so the string concatenation never occur.
 
848
        self.assertEquals(['{foo', '}', '{', 'bar}'],
 
849
                          conf.get_user_option('hidden', expand=True))
 
850
 
 
851
 
 
852
class TestLocationConfigOptionExpansion(tests.TestCaseInTempDir):
 
853
 
 
854
    def get_config(self, location, string=None):
 
855
        if string is None:
 
856
            string = ''
 
857
        # Since we don't save the config we won't strictly require to inherit
 
858
        # from TestCaseInTempDir, but an error occurs so quickly...
 
859
        c = config.LocationConfig.from_string(string, location)
 
860
        return c
 
861
 
 
862
    def test_dont_cross_unrelated_section(self):
 
863
        c = self.get_config('/another/branch/path','''
 
864
[/one/branch/path]
 
865
foo = hello
 
866
bar = {foo}/2
 
867
 
 
868
[/another/branch/path]
 
869
bar = {foo}/2
 
870
''')
 
871
        self.assertRaises(errors.ExpandingUnknownOption,
 
872
                          c.get_user_option, 'bar', expand=True)
 
873
 
 
874
    def test_cross_related_sections(self):
 
875
        c = self.get_config('/project/branch/path','''
 
876
[/project]
 
877
foo = qu
 
878
 
 
879
[/project/branch/path]
 
880
bar = {foo}ux
 
881
''')
 
882
        self.assertEquals('quux', c.get_user_option('bar', expand=True))
 
883
 
 
884
 
 
885
class TestIniBaseConfigOnDisk(tests.TestCaseInTempDir):
 
886
 
 
887
    def test_cannot_reload_without_name(self):
 
888
        conf = config.IniBasedConfig.from_string(sample_config_text)
 
889
        self.assertRaises(AssertionError, conf.reload)
 
890
 
 
891
    def test_reload_see_new_value(self):
 
892
        c1 = config.IniBasedConfig.from_string('editor=vim\n',
 
893
                                               file_name='./test/conf')
 
894
        c1._write_config_file()
 
895
        c2 = config.IniBasedConfig.from_string('editor=emacs\n',
 
896
                                               file_name='./test/conf')
 
897
        c2._write_config_file()
 
898
        self.assertEqual('vim', c1.get_user_option('editor'))
 
899
        self.assertEqual('emacs', c2.get_user_option('editor'))
 
900
        # Make sure we get the Right value
 
901
        c1.reload()
 
902
        self.assertEqual('emacs', c1.get_user_option('editor'))
 
903
 
 
904
 
 
905
class TestLockableConfig(tests.TestCaseInTempDir):
 
906
 
 
907
    scenarios = lockable_config_scenarios()
 
908
 
 
909
    # Set by load_tests
 
910
    config_class = None
 
911
    config_args = None
 
912
    config_section = None
 
913
 
 
914
    def setUp(self):
 
915
        super(TestLockableConfig, self).setUp()
 
916
        self._content = '[%s]\none=1\ntwo=2\n' % (self.config_section,)
 
917
        self.config = self.create_config(self._content)
 
918
 
 
919
    def get_existing_config(self):
 
920
        return self.config_class(*self.config_args)
 
921
 
 
922
    def create_config(self, content):
 
923
        kwargs = dict(save=True)
 
924
        c = self.config_class.from_string(content, *self.config_args, **kwargs)
 
925
        return c
 
926
 
 
927
    def test_simple_read_access(self):
 
928
        self.assertEquals('1', self.config.get_user_option('one'))
 
929
 
 
930
    def test_simple_write_access(self):
 
931
        self.config.set_user_option('one', 'one')
 
932
        self.assertEquals('one', self.config.get_user_option('one'))
 
933
 
 
934
    def test_listen_to_the_last_speaker(self):
 
935
        c1 = self.config
 
936
        c2 = self.get_existing_config()
 
937
        c1.set_user_option('one', 'ONE')
 
938
        c2.set_user_option('two', 'TWO')
 
939
        self.assertEquals('ONE', c1.get_user_option('one'))
 
940
        self.assertEquals('TWO', c2.get_user_option('two'))
 
941
        # The second update respect the first one
 
942
        self.assertEquals('ONE', c2.get_user_option('one'))
 
943
 
 
944
    def test_last_speaker_wins(self):
 
945
        # If the same config is not shared, the same variable modified twice
 
946
        # can only see a single result.
 
947
        c1 = self.config
 
948
        c2 = self.get_existing_config()
 
949
        c1.set_user_option('one', 'c1')
 
950
        c2.set_user_option('one', 'c2')
 
951
        self.assertEquals('c2', c2._get_user_option('one'))
 
952
        # The first modification is still available until another refresh
 
953
        # occur
 
954
        self.assertEquals('c1', c1._get_user_option('one'))
 
955
        c1.set_user_option('two', 'done')
 
956
        self.assertEquals('c2', c1._get_user_option('one'))
 
957
 
 
958
    def test_writes_are_serialized(self):
 
959
        c1 = self.config
 
960
        c2 = self.get_existing_config()
 
961
 
 
962
        # We spawn a thread that will pause *during* the write
 
963
        before_writing = threading.Event()
 
964
        after_writing = threading.Event()
 
965
        writing_done = threading.Event()
 
966
        c1_orig = c1._write_config_file
 
967
        def c1_write_config_file():
 
968
            before_writing.set()
 
969
            c1_orig()
 
970
            # The lock is held. We wait for the main thread to decide when to
 
971
            # continue
 
972
            after_writing.wait()
 
973
        c1._write_config_file = c1_write_config_file
 
974
        def c1_set_option():
 
975
            c1.set_user_option('one', 'c1')
 
976
            writing_done.set()
 
977
        t1 = threading.Thread(target=c1_set_option)
 
978
        # Collect the thread after the test
 
979
        self.addCleanup(t1.join)
 
980
        # Be ready to unblock the thread if the test goes wrong
 
981
        self.addCleanup(after_writing.set)
 
982
        t1.start()
 
983
        before_writing.wait()
 
984
        self.assertTrue(c1._lock.is_held)
 
985
        self.assertRaises(errors.LockContention,
 
986
                          c2.set_user_option, 'one', 'c2')
 
987
        self.assertEquals('c1', c1.get_user_option('one'))
 
988
        # Let the lock be released
 
989
        after_writing.set()
 
990
        writing_done.wait()
 
991
        c2.set_user_option('one', 'c2')
 
992
        self.assertEquals('c2', c2.get_user_option('one'))
 
993
 
 
994
    def test_read_while_writing(self):
 
995
       c1 = self.config
 
996
       # We spawn a thread that will pause *during* the write
 
997
       ready_to_write = threading.Event()
 
998
       do_writing = threading.Event()
 
999
       writing_done = threading.Event()
 
1000
       c1_orig = c1._write_config_file
 
1001
       def c1_write_config_file():
 
1002
           ready_to_write.set()
 
1003
           # The lock is held. We wait for the main thread to decide when to
 
1004
           # continue
 
1005
           do_writing.wait()
 
1006
           c1_orig()
 
1007
           writing_done.set()
 
1008
       c1._write_config_file = c1_write_config_file
 
1009
       def c1_set_option():
 
1010
           c1.set_user_option('one', 'c1')
 
1011
       t1 = threading.Thread(target=c1_set_option)
 
1012
       # Collect the thread after the test
 
1013
       self.addCleanup(t1.join)
 
1014
       # Be ready to unblock the thread if the test goes wrong
 
1015
       self.addCleanup(do_writing.set)
 
1016
       t1.start()
 
1017
       # Ensure the thread is ready to write
 
1018
       ready_to_write.wait()
 
1019
       self.assertTrue(c1._lock.is_held)
 
1020
       self.assertEquals('c1', c1.get_user_option('one'))
 
1021
       # If we read during the write, we get the old value
 
1022
       c2 = self.get_existing_config()
 
1023
       self.assertEquals('1', c2.get_user_option('one'))
 
1024
       # Let the writing occur and ensure it occurred
 
1025
       do_writing.set()
 
1026
       writing_done.wait()
 
1027
       # Now we get the updated value
 
1028
       c3 = self.get_existing_config()
 
1029
       self.assertEquals('c1', c3.get_user_option('one'))
395
1030
 
396
1031
 
397
1032
class TestGetUserOptionAs(TestIniConfig):
430
1065
        # automatically cast to list
431
1066
        self.assertEqual(['x'], get_list('one_item'))
432
1067
 
 
1068
    def test_get_user_option_as_int_from_SI(self):
 
1069
        conf, parser = self.make_config_parser("""
 
1070
plain = 100
 
1071
si_k = 5k,
 
1072
si_kb = 5kb,
 
1073
si_m = 5M,
 
1074
si_mb = 5MB,
 
1075
si_g = 5g,
 
1076
si_gb = 5gB,
 
1077
""")
 
1078
        def get_si(s, default=None):
 
1079
            return self.applyDeprecated(
 
1080
                deprecated_in((2, 5, 0)),
 
1081
                conf.get_user_option_as_int_from_SI, s, default)
 
1082
        self.assertEqual(100, get_si('plain'))
 
1083
        self.assertEqual(5000, get_si('si_k'))
 
1084
        self.assertEqual(5000, get_si('si_kb'))
 
1085
        self.assertEqual(5000000, get_si('si_m'))
 
1086
        self.assertEqual(5000000, get_si('si_mb'))
 
1087
        self.assertEqual(5000000000, get_si('si_g'))
 
1088
        self.assertEqual(5000000000, get_si('si_gb'))
 
1089
        self.assertEqual(None, get_si('non-exist'))
 
1090
        self.assertEqual(42, get_si('non-exist-with-default',  42))
 
1091
 
433
1092
 
434
1093
class TestSupressWarning(TestIniConfig):
435
1094
 
462
1121
            parser = my_config._get_parser()
463
1122
        finally:
464
1123
            config.ConfigObj = oldparserclass
465
 
        self.failUnless(isinstance(parser, InstrumentedConfigObj))
 
1124
        self.assertIsInstance(parser, InstrumentedConfigObj)
466
1125
        self.assertEqual(parser._calls, [('__init__', config.config_filename(),
467
1126
                                          'utf-8')])
468
1127
 
479
1138
        my_config = config.BranchConfig(branch)
480
1139
        location_config = my_config._get_location_config()
481
1140
        self.assertEqual(branch.base, location_config.location)
482
 
        self.failUnless(location_config is my_config._get_location_config())
 
1141
        self.assertIs(location_config, my_config._get_location_config())
483
1142
 
484
1143
    def test_get_config(self):
485
1144
        """The Branch.get_config method works properly"""
505
1164
        branch = self.make_branch('branch')
506
1165
        self.assertEqual('branch', branch.nick)
507
1166
 
508
 
        locations = config.locations_config_filename()
509
 
        config.ensure_config_dir_exists()
510
1167
        local_url = urlutils.local_path_to_url('branch')
511
 
        open(locations, 'wb').write('[%s]\nnickname = foobar'
512
 
                                    % (local_url,))
 
1168
        conf = config.LocationConfig.from_string(
 
1169
            '[%s]\nnickname = foobar' % (local_url,),
 
1170
            local_url, save=True)
513
1171
        self.assertEqual('foobar', branch.nick)
514
1172
 
515
1173
    def test_config_local_path(self):
517
1175
        branch = self.make_branch('branch')
518
1176
        self.assertEqual('branch', branch.nick)
519
1177
 
520
 
        locations = config.locations_config_filename()
521
 
        config.ensure_config_dir_exists()
522
 
        open(locations, 'wb').write('[%s/branch]\nnickname = barry'
523
 
                                    % (osutils.getcwd().encode('utf8'),))
 
1178
        local_path = osutils.getcwd().encode('utf8')
 
1179
        conf = config.LocationConfig.from_string(
 
1180
            '[%s/branch]\nnickname = barry' % (local_path,),
 
1181
            'branch',  save=True)
524
1182
        self.assertEqual('barry', branch.nick)
525
1183
 
526
1184
    def test_config_creates_local(self):
527
1185
        """Creating a new entry in config uses a local path."""
528
1186
        branch = self.make_branch('branch', format='knit')
529
1187
        branch.set_push_location('http://foobar')
530
 
        locations = config.locations_config_filename()
531
1188
        local_path = osutils.getcwd().encode('utf8')
532
1189
        # Surprisingly ConfigObj doesn't create a trailing newline
533
 
        self.check_file_contents(locations,
 
1190
        self.check_file_contents(config.locations_config_filename(),
534
1191
                                 '[%s/branch]\n'
535
1192
                                 'push_location = http://foobar\n'
536
1193
                                 'push_location:policy = norecurse\n'
541
1198
        self.assertEqual('!repo', b.get_config().get_nickname())
542
1199
 
543
1200
    def test_warn_if_masked(self):
544
 
        _warning = trace.warning
545
1201
        warnings = []
546
1202
        def warning(*args):
547
1203
            warnings.append(args[0] % args[1:])
 
1204
        self.overrideAttr(trace, 'warning', warning)
548
1205
 
549
1206
        def set_option(store, warn_masked=True):
550
1207
            warnings[:] = []
556
1213
            else:
557
1214
                self.assertEqual(1, len(warnings))
558
1215
                self.assertEqual(warning, warnings[0])
559
 
        trace.warning = warning
560
 
        try:
561
 
            branch = self.make_branch('.')
562
 
            conf = branch.get_config()
563
 
            set_option(config.STORE_GLOBAL)
564
 
            assertWarning(None)
565
 
            set_option(config.STORE_BRANCH)
566
 
            assertWarning(None)
567
 
            set_option(config.STORE_GLOBAL)
568
 
            assertWarning('Value "4" is masked by "3" from branch.conf')
569
 
            set_option(config.STORE_GLOBAL, warn_masked=False)
570
 
            assertWarning(None)
571
 
            set_option(config.STORE_LOCATION)
572
 
            assertWarning(None)
573
 
            set_option(config.STORE_BRANCH)
574
 
            assertWarning('Value "3" is masked by "0" from locations.conf')
575
 
            set_option(config.STORE_BRANCH, warn_masked=False)
576
 
            assertWarning(None)
577
 
        finally:
578
 
            trace.warning = _warning
579
 
 
580
 
 
581
 
class TestGlobalConfigItems(tests.TestCase):
 
1216
        branch = self.make_branch('.')
 
1217
        conf = branch.get_config()
 
1218
        set_option(config.STORE_GLOBAL)
 
1219
        assertWarning(None)
 
1220
        set_option(config.STORE_BRANCH)
 
1221
        assertWarning(None)
 
1222
        set_option(config.STORE_GLOBAL)
 
1223
        assertWarning('Value "4" is masked by "3" from branch.conf')
 
1224
        set_option(config.STORE_GLOBAL, warn_masked=False)
 
1225
        assertWarning(None)
 
1226
        set_option(config.STORE_LOCATION)
 
1227
        assertWarning(None)
 
1228
        set_option(config.STORE_BRANCH)
 
1229
        assertWarning('Value "3" is masked by "0" from locations.conf')
 
1230
        set_option(config.STORE_BRANCH, warn_masked=False)
 
1231
        assertWarning(None)
 
1232
 
 
1233
 
 
1234
class TestGlobalConfigItems(tests.TestCaseInTempDir):
582
1235
 
583
1236
    def test_user_id(self):
584
 
        config_file = StringIO(sample_config_text.encode('utf-8'))
585
 
        my_config = config.GlobalConfig()
586
 
        my_config._parser = my_config._get_parser(file=config_file)
 
1237
        my_config = config.GlobalConfig.from_string(sample_config_text)
587
1238
        self.assertEqual(u"Erik B\u00e5gfors <erik@bagfors.nu>",
588
1239
                         my_config._get_user_id())
589
1240
 
590
1241
    def test_absent_user_id(self):
591
 
        config_file = StringIO("")
592
1242
        my_config = config.GlobalConfig()
593
 
        my_config._parser = my_config._get_parser(file=config_file)
594
1243
        self.assertEqual(None, my_config._get_user_id())
595
1244
 
596
1245
    def test_configured_editor(self):
597
 
        config_file = StringIO(sample_config_text.encode('utf-8'))
598
 
        my_config = config.GlobalConfig()
599
 
        my_config._parser = my_config._get_parser(file=config_file)
600
 
        self.assertEqual("vim", my_config.get_editor())
 
1246
        my_config = config.GlobalConfig.from_string(sample_config_text)
 
1247
        editor = self.applyDeprecated(
 
1248
            deprecated_in((2, 4, 0)), my_config.get_editor)
 
1249
        self.assertEqual('vim', editor)
601
1250
 
602
1251
    def test_signatures_always(self):
603
 
        config_file = StringIO(sample_always_signatures)
604
 
        my_config = config.GlobalConfig()
605
 
        my_config._parser = my_config._get_parser(file=config_file)
 
1252
        my_config = config.GlobalConfig.from_string(sample_always_signatures)
606
1253
        self.assertEqual(config.CHECK_NEVER,
607
 
                         my_config.signature_checking())
 
1254
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1255
                             my_config.signature_checking))
608
1256
        self.assertEqual(config.SIGN_ALWAYS,
609
 
                         my_config.signing_policy())
610
 
        self.assertEqual(True, my_config.signature_needed())
 
1257
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1258
                             my_config.signing_policy))
 
1259
        self.assertEqual(True,
 
1260
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1261
                my_config.signature_needed))
611
1262
 
612
1263
    def test_signatures_if_possible(self):
613
 
        config_file = StringIO(sample_maybe_signatures)
614
 
        my_config = config.GlobalConfig()
615
 
        my_config._parser = my_config._get_parser(file=config_file)
 
1264
        my_config = config.GlobalConfig.from_string(sample_maybe_signatures)
616
1265
        self.assertEqual(config.CHECK_NEVER,
617
 
                         my_config.signature_checking())
 
1266
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1267
                             my_config.signature_checking))
618
1268
        self.assertEqual(config.SIGN_WHEN_REQUIRED,
619
 
                         my_config.signing_policy())
620
 
        self.assertEqual(False, my_config.signature_needed())
 
1269
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1270
                             my_config.signing_policy))
 
1271
        self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1272
            my_config.signature_needed))
621
1273
 
622
1274
    def test_signatures_ignore(self):
623
 
        config_file = StringIO(sample_ignore_signatures)
624
 
        my_config = config.GlobalConfig()
625
 
        my_config._parser = my_config._get_parser(file=config_file)
 
1275
        my_config = config.GlobalConfig.from_string(sample_ignore_signatures)
626
1276
        self.assertEqual(config.CHECK_ALWAYS,
627
 
                         my_config.signature_checking())
 
1277
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1278
                             my_config.signature_checking))
628
1279
        self.assertEqual(config.SIGN_NEVER,
629
 
                         my_config.signing_policy())
630
 
        self.assertEqual(False, my_config.signature_needed())
 
1280
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1281
                             my_config.signing_policy))
 
1282
        self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1283
            my_config.signature_needed))
631
1284
 
632
1285
    def _get_sample_config(self):
633
 
        config_file = StringIO(sample_config_text.encode('utf-8'))
634
 
        my_config = config.GlobalConfig()
635
 
        my_config._parser = my_config._get_parser(file=config_file)
 
1286
        my_config = config.GlobalConfig.from_string(sample_config_text)
636
1287
        return my_config
637
1288
 
638
1289
    def test_gpg_signing_command(self):
639
1290
        my_config = self._get_sample_config()
640
 
        self.assertEqual("gnome-gpg", my_config.gpg_signing_command())
641
 
        self.assertEqual(False, my_config.signature_needed())
 
1291
        self.assertEqual("gnome-gpg",
 
1292
            self.applyDeprecated(
 
1293
                deprecated_in((2, 5, 0)), my_config.gpg_signing_command))
 
1294
        self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1295
            my_config.signature_needed))
 
1296
 
 
1297
    def test_gpg_signing_key(self):
 
1298
        my_config = self._get_sample_config()
 
1299
        self.assertEqual("DD4D5088",
 
1300
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1301
                my_config.gpg_signing_key))
642
1302
 
643
1303
    def _get_empty_config(self):
644
 
        config_file = StringIO("")
645
1304
        my_config = config.GlobalConfig()
646
 
        my_config._parser = my_config._get_parser(file=config_file)
647
1305
        return my_config
648
1306
 
649
1307
    def test_gpg_signing_command_unset(self):
650
1308
        my_config = self._get_empty_config()
651
 
        self.assertEqual("gpg", my_config.gpg_signing_command())
 
1309
        self.assertEqual("gpg",
 
1310
            self.applyDeprecated(
 
1311
                deprecated_in((2, 5, 0)), my_config.gpg_signing_command))
652
1312
 
653
1313
    def test_get_user_option_default(self):
654
1314
        my_config = self._get_empty_config()
661
1321
 
662
1322
    def test_post_commit_default(self):
663
1323
        my_config = self._get_sample_config()
664
 
        self.assertEqual(None, my_config.post_commit())
 
1324
        self.assertEqual(None,
 
1325
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1326
                                              my_config.post_commit))
665
1327
 
666
1328
    def test_configured_logformat(self):
667
1329
        my_config = self._get_sample_config()
668
 
        self.assertEqual("short", my_config.log_format())
 
1330
        self.assertEqual("short",
 
1331
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1332
                                              my_config.log_format))
 
1333
 
 
1334
    def test_configured_acceptable_keys(self):
 
1335
        my_config = self._get_sample_config()
 
1336
        self.assertEqual("amy",
 
1337
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1338
                my_config.acceptable_keys))
 
1339
 
 
1340
    def test_configured_validate_signatures_in_log(self):
 
1341
        my_config = self._get_sample_config()
 
1342
        self.assertEqual(True, my_config.validate_signatures_in_log())
669
1343
 
670
1344
    def test_get_alias(self):
671
1345
        my_config = self._get_sample_config()
699
1373
        change_editor = my_config.get_change_editor('old', 'new')
700
1374
        self.assertIs(None, change_editor)
701
1375
 
 
1376
    def test_get_merge_tools(self):
 
1377
        conf = self._get_sample_config()
 
1378
        tools = conf.get_merge_tools()
 
1379
        self.log(repr(tools))
 
1380
        self.assertEqual(
 
1381
            {u'funkytool' : u'funkytool "arg with spaces" {this_temp}',
 
1382
            u'sometool' : u'sometool {base} {this} {other} -o {result}',
 
1383
            u'newtool' : u'"newtool with spaces" {this_temp}'},
 
1384
            tools)
 
1385
 
 
1386
    def test_get_merge_tools_empty(self):
 
1387
        conf = self._get_empty_config()
 
1388
        tools = conf.get_merge_tools()
 
1389
        self.assertEqual({}, tools)
 
1390
 
 
1391
    def test_find_merge_tool(self):
 
1392
        conf = self._get_sample_config()
 
1393
        cmdline = conf.find_merge_tool('sometool')
 
1394
        self.assertEqual('sometool {base} {this} {other} -o {result}', cmdline)
 
1395
 
 
1396
    def test_find_merge_tool_not_found(self):
 
1397
        conf = self._get_sample_config()
 
1398
        cmdline = conf.find_merge_tool('DOES NOT EXIST')
 
1399
        self.assertIs(cmdline, None)
 
1400
 
 
1401
    def test_find_merge_tool_known(self):
 
1402
        conf = self._get_empty_config()
 
1403
        cmdline = conf.find_merge_tool('kdiff3')
 
1404
        self.assertEquals('kdiff3 {base} {this} {other} -o {result}', cmdline)
 
1405
 
 
1406
    def test_find_merge_tool_override_known(self):
 
1407
        conf = self._get_empty_config()
 
1408
        conf.set_user_option('bzr.mergetool.kdiff3', 'kdiff3 blah')
 
1409
        cmdline = conf.find_merge_tool('kdiff3')
 
1410
        self.assertEqual('kdiff3 blah', cmdline)
 
1411
 
702
1412
 
703
1413
class TestGlobalConfigSavingOptions(tests.TestCaseInTempDir):
704
1414
 
722
1432
        self.assertIs(None, new_config.get_alias('commit'))
723
1433
 
724
1434
 
725
 
class TestLocationConfig(tests.TestCaseInTempDir):
 
1435
class TestLocationConfig(tests.TestCaseInTempDir, TestOptionsMixin):
726
1436
 
727
1437
    def test_constructs(self):
728
1438
        my_config = config.LocationConfig('http://example.com')
740
1450
            parser = my_config._get_parser()
741
1451
        finally:
742
1452
            config.ConfigObj = oldparserclass
743
 
        self.failUnless(isinstance(parser, InstrumentedConfigObj))
 
1453
        self.assertIsInstance(parser, InstrumentedConfigObj)
744
1454
        self.assertEqual(parser._calls,
745
1455
                         [('__init__', config.locations_config_filename(),
746
1456
                           'utf-8')])
747
 
        config.ensure_config_dir_exists()
748
 
        #os.mkdir(config.config_dir())
749
 
        f = file(config.branches_config_filename(), 'wb')
750
 
        f.write('')
751
 
        f.close()
752
 
        oldparserclass = config.ConfigObj
753
 
        config.ConfigObj = InstrumentedConfigObj
754
 
        try:
755
 
            my_config = config.LocationConfig('http://www.example.com')
756
 
            parser = my_config._get_parser()
757
 
        finally:
758
 
            config.ConfigObj = oldparserclass
759
1457
 
760
1458
    def test_get_global_config(self):
761
1459
        my_config = config.BranchConfig(FakeBranch('http://example.com'))
762
1460
        global_config = my_config._get_global_config()
763
 
        self.failUnless(isinstance(global_config, config.GlobalConfig))
764
 
        self.failUnless(global_config is my_config._get_global_config())
 
1461
        self.assertIsInstance(global_config, config.GlobalConfig)
 
1462
        self.assertIs(global_config, my_config._get_global_config())
 
1463
 
 
1464
    def assertLocationMatching(self, expected):
 
1465
        self.assertEqual(expected,
 
1466
                         list(self.my_location_config._get_matching_sections()))
765
1467
 
766
1468
    def test__get_matching_sections_no_match(self):
767
1469
        self.get_branch_config('/')
768
 
        self.assertEqual([], self.my_location_config._get_matching_sections())
 
1470
        self.assertLocationMatching([])
769
1471
 
770
1472
    def test__get_matching_sections_exact(self):
771
1473
        self.get_branch_config('http://www.example.com')
772
 
        self.assertEqual([('http://www.example.com', '')],
773
 
                         self.my_location_config._get_matching_sections())
 
1474
        self.assertLocationMatching([('http://www.example.com', '')])
774
1475
 
775
1476
    def test__get_matching_sections_suffix_does_not(self):
776
1477
        self.get_branch_config('http://www.example.com-com')
777
 
        self.assertEqual([], self.my_location_config._get_matching_sections())
 
1478
        self.assertLocationMatching([])
778
1479
 
779
1480
    def test__get_matching_sections_subdir_recursive(self):
780
1481
        self.get_branch_config('http://www.example.com/com')
781
 
        self.assertEqual([('http://www.example.com', 'com')],
782
 
                         self.my_location_config._get_matching_sections())
 
1482
        self.assertLocationMatching([('http://www.example.com', 'com')])
783
1483
 
784
1484
    def test__get_matching_sections_ignoreparent(self):
785
1485
        self.get_branch_config('http://www.example.com/ignoreparent')
786
 
        self.assertEqual([('http://www.example.com/ignoreparent', '')],
787
 
                         self.my_location_config._get_matching_sections())
 
1486
        self.assertLocationMatching([('http://www.example.com/ignoreparent',
 
1487
                                      '')])
788
1488
 
789
1489
    def test__get_matching_sections_ignoreparent_subdir(self):
790
1490
        self.get_branch_config(
791
1491
            'http://www.example.com/ignoreparent/childbranch')
792
 
        self.assertEqual([('http://www.example.com/ignoreparent',
793
 
                           'childbranch')],
794
 
                         self.my_location_config._get_matching_sections())
 
1492
        self.assertLocationMatching([('http://www.example.com/ignoreparent',
 
1493
                                      'childbranch')])
795
1494
 
796
1495
    def test__get_matching_sections_subdir_trailing_slash(self):
797
1496
        self.get_branch_config('/b')
798
 
        self.assertEqual([('/b/', '')],
799
 
                         self.my_location_config._get_matching_sections())
 
1497
        self.assertLocationMatching([('/b/', '')])
800
1498
 
801
1499
    def test__get_matching_sections_subdir_child(self):
802
1500
        self.get_branch_config('/a/foo')
803
 
        self.assertEqual([('/a/*', ''), ('/a/', 'foo')],
804
 
                         self.my_location_config._get_matching_sections())
 
1501
        self.assertLocationMatching([('/a/*', ''), ('/a/', 'foo')])
805
1502
 
806
1503
    def test__get_matching_sections_subdir_child_child(self):
807
1504
        self.get_branch_config('/a/foo/bar')
808
 
        self.assertEqual([('/a/*', 'bar'), ('/a/', 'foo/bar')],
809
 
                         self.my_location_config._get_matching_sections())
 
1505
        self.assertLocationMatching([('/a/*', 'bar'), ('/a/', 'foo/bar')])
810
1506
 
811
1507
    def test__get_matching_sections_trailing_slash_with_children(self):
812
1508
        self.get_branch_config('/a/')
813
 
        self.assertEqual([('/a/', '')],
814
 
                         self.my_location_config._get_matching_sections())
 
1509
        self.assertLocationMatching([('/a/', '')])
815
1510
 
816
1511
    def test__get_matching_sections_explicit_over_glob(self):
817
1512
        # XXX: 2006-09-08 jamesh
819
1514
        # was a config section for '/a/?', it would get precedence
820
1515
        # over '/a/c'.
821
1516
        self.get_branch_config('/a/c')
822
 
        self.assertEqual([('/a/c', ''), ('/a/*', ''), ('/a/', 'c')],
823
 
                         self.my_location_config._get_matching_sections())
 
1517
        self.assertLocationMatching([('/a/c', ''), ('/a/*', ''), ('/a/', 'c')])
824
1518
 
825
1519
    def test__get_option_policy_normal(self):
826
1520
        self.get_branch_config('http://www.example.com')
848
1542
            'http://www.example.com', 'appendpath_option'),
849
1543
            config.POLICY_APPENDPATH)
850
1544
 
 
1545
    def test__get_options_with_policy(self):
 
1546
        self.get_branch_config('/dir/subdir',
 
1547
                               location_config="""\
 
1548
[/dir]
 
1549
other_url = /other-dir
 
1550
other_url:policy = appendpath
 
1551
[/dir/subdir]
 
1552
other_url = /other-subdir
 
1553
""")
 
1554
        self.assertOptions(
 
1555
            [(u'other_url', u'/other-subdir', u'/dir/subdir', 'locations'),
 
1556
             (u'other_url', u'/other-dir', u'/dir', 'locations'),
 
1557
             (u'other_url:policy', u'appendpath', u'/dir', 'locations')],
 
1558
            self.my_location_config)
 
1559
 
851
1560
    def test_location_without_username(self):
852
1561
        self.get_branch_config('http://www.example.com/ignoreparent')
853
1562
        self.assertEqual(u'Erik B\u00e5gfors <erik@bagfors.nu>',
868
1577
        self.get_branch_config('http://www.example.com',
869
1578
                                 global_config=sample_ignore_signatures)
870
1579
        self.assertEqual(config.CHECK_ALWAYS,
871
 
                         self.my_config.signature_checking())
 
1580
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1581
                             self.my_config.signature_checking))
872
1582
        self.assertEqual(config.SIGN_NEVER,
873
 
                         self.my_config.signing_policy())
 
1583
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1584
                             self.my_config.signing_policy))
874
1585
 
875
1586
    def test_signatures_never(self):
876
1587
        self.get_branch_config('/a/c')
877
1588
        self.assertEqual(config.CHECK_NEVER,
878
 
                         self.my_config.signature_checking())
 
1589
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1590
                             self.my_config.signature_checking))
879
1591
 
880
1592
    def test_signatures_when_available(self):
881
1593
        self.get_branch_config('/a/', global_config=sample_ignore_signatures)
882
1594
        self.assertEqual(config.CHECK_IF_POSSIBLE,
883
 
                         self.my_config.signature_checking())
 
1595
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1596
                             self.my_config.signature_checking))
884
1597
 
885
1598
    def test_signatures_always(self):
886
1599
        self.get_branch_config('/b')
887
1600
        self.assertEqual(config.CHECK_ALWAYS,
888
 
                         self.my_config.signature_checking())
 
1601
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1602
                         self.my_config.signature_checking))
889
1603
 
890
1604
    def test_gpg_signing_command(self):
891
1605
        self.get_branch_config('/b')
892
 
        self.assertEqual("gnome-gpg", self.my_config.gpg_signing_command())
 
1606
        self.assertEqual("gnome-gpg",
 
1607
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1608
                self.my_config.gpg_signing_command))
893
1609
 
894
1610
    def test_gpg_signing_command_missing(self):
895
1611
        self.get_branch_config('/a')
896
 
        self.assertEqual("false", self.my_config.gpg_signing_command())
 
1612
        self.assertEqual("false",
 
1613
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1614
                self.my_config.gpg_signing_command))
 
1615
 
 
1616
    def test_gpg_signing_key(self):
 
1617
        self.get_branch_config('/b')
 
1618
        self.assertEqual("DD4D5088", self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1619
            self.my_config.gpg_signing_key))
 
1620
 
 
1621
    def test_gpg_signing_key_default(self):
 
1622
        self.get_branch_config('/a')
 
1623
        self.assertEqual("erik@bagfors.nu",
 
1624
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1625
                self.my_config.gpg_signing_key))
897
1626
 
898
1627
    def test_get_user_option_global(self):
899
1628
        self.get_branch_config('/a')
987
1716
    def test_post_commit_default(self):
988
1717
        self.get_branch_config('/a/c')
989
1718
        self.assertEqual('bzrlib.tests.test_config.post_commit',
990
 
                         self.my_config.post_commit())
 
1719
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1720
                                              self.my_config.post_commit))
991
1721
 
992
 
    def get_branch_config(self, location, global_config=None):
 
1722
    def get_branch_config(self, location, global_config=None,
 
1723
                          location_config=None):
 
1724
        my_branch = FakeBranch(location)
993
1725
        if global_config is None:
994
 
            global_file = StringIO(sample_config_text.encode('utf-8'))
995
 
        else:
996
 
            global_file = StringIO(global_config.encode('utf-8'))
997
 
        branches_file = StringIO(sample_branches_text.encode('utf-8'))
998
 
        self.my_config = config.BranchConfig(FakeBranch(location))
999
 
        # Force location config to use specified file
1000
 
        self.my_location_config = self.my_config._get_location_config()
1001
 
        self.my_location_config._get_parser(branches_file)
1002
 
        # Force global config to use specified file
1003
 
        self.my_config._get_global_config()._get_parser(global_file)
 
1726
            global_config = sample_config_text
 
1727
        if location_config is None:
 
1728
            location_config = sample_branches_text
 
1729
 
 
1730
        my_global_config = config.GlobalConfig.from_string(global_config,
 
1731
                                                           save=True)
 
1732
        my_location_config = config.LocationConfig.from_string(
 
1733
            location_config, my_branch.base, save=True)
 
1734
        my_config = config.BranchConfig(my_branch)
 
1735
        self.my_config = my_config
 
1736
        self.my_location_config = my_config._get_location_config()
1004
1737
 
1005
1738
    def test_set_user_setting_sets_and_saves(self):
1006
1739
        self.get_branch_config('/a/c')
1007
1740
        record = InstrumentedConfigObj("foo")
1008
1741
        self.my_location_config._parser = record
1009
1742
 
1010
 
        real_mkdir = os.mkdir
1011
 
        self.created = False
1012
 
        def checked_mkdir(path, mode=0777):
1013
 
            self.log('making directory: %s', path)
1014
 
            real_mkdir(path, mode)
1015
 
            self.created = True
1016
 
 
1017
 
        os.mkdir = checked_mkdir
1018
 
        try:
1019
 
            self.callDeprecated(['The recurse option is deprecated as of '
1020
 
                                 '0.14.  The section "/a/c" has been '
1021
 
                                 'converted to use policies.'],
1022
 
                                self.my_config.set_user_option,
1023
 
                                'foo', 'bar', store=config.STORE_LOCATION)
1024
 
        finally:
1025
 
            os.mkdir = real_mkdir
1026
 
 
1027
 
        self.failUnless(self.created, 'Failed to create ~/.bazaar')
1028
 
        self.assertEqual([('__contains__', '/a/c'),
 
1743
        self.callDeprecated(['The recurse option is deprecated as of '
 
1744
                             '0.14.  The section "/a/c" has been '
 
1745
                             'converted to use policies.'],
 
1746
                            self.my_config.set_user_option,
 
1747
                            'foo', 'bar', store=config.STORE_LOCATION)
 
1748
        self.assertEqual([('reload',),
 
1749
                          ('__contains__', '/a/c'),
1029
1750
                          ('__contains__', '/a/c/'),
1030
1751
                          ('__setitem__', '/a/c', {}),
1031
1752
                          ('__getitem__', '/a/c'),
1060
1781
        self.assertEqual('bzr', my_config.get_bzr_remote_path())
1061
1782
        my_config.set_user_option('bzr_remote_path', '/path-bzr')
1062
1783
        self.assertEqual('/path-bzr', my_config.get_bzr_remote_path())
1063
 
        os.environ['BZR_REMOTE_PATH'] = '/environ-bzr'
 
1784
        self.overrideEnv('BZR_REMOTE_PATH', '/environ-bzr')
1064
1785
        self.assertEqual('/environ-bzr', my_config.get_bzr_remote_path())
1065
1786
 
1066
1787
 
1074
1795
option = exact
1075
1796
"""
1076
1797
 
1077
 
 
1078
1798
class TestBranchConfigItems(tests.TestCaseInTempDir):
1079
1799
 
1080
1800
    def get_branch_config(self, global_config=None, location=None,
1081
1801
                          location_config=None, branch_data_config=None):
1082
 
        my_config = config.BranchConfig(FakeBranch(location))
 
1802
        my_branch = FakeBranch(location)
1083
1803
        if global_config is not None:
1084
 
            global_file = StringIO(global_config.encode('utf-8'))
1085
 
            my_config._get_global_config()._get_parser(global_file)
1086
 
        self.my_location_config = my_config._get_location_config()
 
1804
            my_global_config = config.GlobalConfig.from_string(global_config,
 
1805
                                                               save=True)
1087
1806
        if location_config is not None:
1088
 
            location_file = StringIO(location_config.encode('utf-8'))
1089
 
            self.my_location_config._get_parser(location_file)
 
1807
            my_location_config = config.LocationConfig.from_string(
 
1808
                location_config, my_branch.base, save=True)
 
1809
        my_config = config.BranchConfig(my_branch)
1090
1810
        if branch_data_config is not None:
1091
1811
            my_config.branch.control_files.files['branch.conf'] = \
1092
1812
                branch_data_config
1093
1813
        return my_config
1094
1814
 
1095
1815
    def test_user_id(self):
1096
 
        branch = FakeBranch(user_id='Robert Collins <robertc@example.net>')
 
1816
        branch = FakeBranch()
1097
1817
        my_config = config.BranchConfig(branch)
1098
 
        self.assertEqual("Robert Collins <robertc@example.net>",
1099
 
                         my_config.username())
 
1818
        self.assertIsNot(None, my_config.username())
1100
1819
        my_config.branch.control_files.files['email'] = "John"
1101
1820
        my_config.set_user_option('email',
1102
1821
                                  "Robert Collins <robertc@example.org>")
1103
 
        self.assertEqual("John", my_config.username())
1104
 
        del my_config.branch.control_files.files['email']
1105
1822
        self.assertEqual("Robert Collins <robertc@example.org>",
1106
 
                         my_config.username())
1107
 
 
1108
 
    def test_not_set_in_branch(self):
1109
 
        my_config = self.get_branch_config(sample_config_text)
1110
 
        self.assertEqual(u"Erik B\u00e5gfors <erik@bagfors.nu>",
1111
 
                         my_config._get_user_id())
1112
 
        my_config.branch.control_files.files['email'] = "John"
1113
 
        self.assertEqual("John", my_config._get_user_id())
 
1823
                        my_config.username())
1114
1824
 
1115
1825
    def test_BZR_EMAIL_OVERRIDES(self):
1116
 
        os.environ['BZR_EMAIL'] = "Robert Collins <robertc@example.org>"
 
1826
        self.overrideEnv('BZR_EMAIL', "Robert Collins <robertc@example.org>")
1117
1827
        branch = FakeBranch()
1118
1828
        my_config = config.BranchConfig(branch)
1119
1829
        self.assertEqual("Robert Collins <robertc@example.org>",
1122
1832
    def test_signatures_forced(self):
1123
1833
        my_config = self.get_branch_config(
1124
1834
            global_config=sample_always_signatures)
1125
 
        self.assertEqual(config.CHECK_NEVER, my_config.signature_checking())
1126
 
        self.assertEqual(config.SIGN_ALWAYS, my_config.signing_policy())
1127
 
        self.assertTrue(my_config.signature_needed())
 
1835
        self.assertEqual(config.CHECK_NEVER,
 
1836
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1837
                my_config.signature_checking))
 
1838
        self.assertEqual(config.SIGN_ALWAYS,
 
1839
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1840
                my_config.signing_policy))
 
1841
        self.assertTrue(self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1842
            my_config.signature_needed))
1128
1843
 
1129
1844
    def test_signatures_forced_branch(self):
1130
1845
        my_config = self.get_branch_config(
1131
1846
            global_config=sample_ignore_signatures,
1132
1847
            branch_data_config=sample_always_signatures)
1133
 
        self.assertEqual(config.CHECK_NEVER, my_config.signature_checking())
1134
 
        self.assertEqual(config.SIGN_ALWAYS, my_config.signing_policy())
1135
 
        self.assertTrue(my_config.signature_needed())
 
1848
        self.assertEqual(config.CHECK_NEVER,
 
1849
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1850
                my_config.signature_checking))
 
1851
        self.assertEqual(config.SIGN_ALWAYS,
 
1852
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1853
                my_config.signing_policy))
 
1854
        self.assertTrue(self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1855
            my_config.signature_needed))
1136
1856
 
1137
1857
    def test_gpg_signing_command(self):
1138
1858
        my_config = self.get_branch_config(
 
1859
            global_config=sample_config_text,
1139
1860
            # branch data cannot set gpg_signing_command
1140
1861
            branch_data_config="gpg_signing_command=pgp")
1141
 
        config_file = StringIO(sample_config_text.encode('utf-8'))
1142
 
        my_config._get_global_config()._get_parser(config_file)
1143
 
        self.assertEqual('gnome-gpg', my_config.gpg_signing_command())
 
1862
        self.assertEqual('gnome-gpg',
 
1863
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1864
                my_config.gpg_signing_command))
1144
1865
 
1145
1866
    def test_get_user_option_global(self):
1146
 
        branch = FakeBranch()
1147
 
        my_config = config.BranchConfig(branch)
1148
 
        config_file = StringIO(sample_config_text.encode('utf-8'))
1149
 
        (my_config._get_global_config()._get_parser(config_file))
 
1867
        my_config = self.get_branch_config(global_config=sample_config_text)
1150
1868
        self.assertEqual('something',
1151
1869
                         my_config.get_user_option('user_global_option'))
1152
1870
 
1153
1871
    def test_post_commit_default(self):
1154
 
        branch = FakeBranch()
1155
 
        my_config = self.get_branch_config(sample_config_text, '/a/c',
1156
 
                                           sample_branches_text)
 
1872
        my_config = self.get_branch_config(global_config=sample_config_text,
 
1873
                                      location='/a/c',
 
1874
                                      location_config=sample_branches_text)
1157
1875
        self.assertEqual(my_config.branch.base, '/a/c')
1158
1876
        self.assertEqual('bzrlib.tests.test_config.post_commit',
1159
 
                         my_config.post_commit())
 
1877
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1878
                                              my_config.post_commit))
1160
1879
        my_config.set_user_option('post_commit', 'rmtree_root')
1161
 
        # post-commit is ignored when bresent in branch data
 
1880
        # post-commit is ignored when present in branch data
1162
1881
        self.assertEqual('bzrlib.tests.test_config.post_commit',
1163
 
                         my_config.post_commit())
 
1882
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1883
                                              my_config.post_commit))
1164
1884
        my_config.set_user_option('post_commit', 'rmtree_root',
1165
1885
                                  store=config.STORE_LOCATION)
1166
 
        self.assertEqual('rmtree_root', my_config.post_commit())
 
1886
        self.assertEqual('rmtree_root',
 
1887
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1888
                                              my_config.post_commit))
1167
1889
 
1168
1890
    def test_config_precedence(self):
 
1891
        # FIXME: eager test, luckily no persitent config file makes it fail
 
1892
        # -- vila 20100716
1169
1893
        my_config = self.get_branch_config(global_config=precedence_global)
1170
1894
        self.assertEqual(my_config.get_user_option('option'), 'global')
1171
1895
        my_config = self.get_branch_config(global_config=precedence_global,
1172
 
                                      branch_data_config=precedence_branch)
 
1896
                                           branch_data_config=precedence_branch)
1173
1897
        self.assertEqual(my_config.get_user_option('option'), 'branch')
1174
 
        my_config = self.get_branch_config(global_config=precedence_global,
1175
 
                                      branch_data_config=precedence_branch,
1176
 
                                      location_config=precedence_location)
 
1898
        my_config = self.get_branch_config(
 
1899
            global_config=precedence_global,
 
1900
            branch_data_config=precedence_branch,
 
1901
            location_config=precedence_location)
1177
1902
        self.assertEqual(my_config.get_user_option('option'), 'recurse')
1178
 
        my_config = self.get_branch_config(global_config=precedence_global,
1179
 
                                      branch_data_config=precedence_branch,
1180
 
                                      location_config=precedence_location,
1181
 
                                      location='http://example.com/specific')
 
1903
        my_config = self.get_branch_config(
 
1904
            global_config=precedence_global,
 
1905
            branch_data_config=precedence_branch,
 
1906
            location_config=precedence_location,
 
1907
            location='http://example.com/specific')
1182
1908
        self.assertEqual(my_config.get_user_option('option'), 'exact')
1183
1909
 
1184
 
    def test_get_mail_client(self):
1185
 
        config = self.get_branch_config()
1186
 
        client = config.get_mail_client()
1187
 
        self.assertIsInstance(client, mail_client.DefaultMail)
1188
 
 
1189
 
        # Specific clients
1190
 
        config.set_user_option('mail_client', 'evolution')
1191
 
        client = config.get_mail_client()
1192
 
        self.assertIsInstance(client, mail_client.Evolution)
1193
 
 
1194
 
        config.set_user_option('mail_client', 'kmail')
1195
 
        client = config.get_mail_client()
1196
 
        self.assertIsInstance(client, mail_client.KMail)
1197
 
 
1198
 
        config.set_user_option('mail_client', 'mutt')
1199
 
        client = config.get_mail_client()
1200
 
        self.assertIsInstance(client, mail_client.Mutt)
1201
 
 
1202
 
        config.set_user_option('mail_client', 'thunderbird')
1203
 
        client = config.get_mail_client()
1204
 
        self.assertIsInstance(client, mail_client.Thunderbird)
1205
 
 
1206
 
        # Generic options
1207
 
        config.set_user_option('mail_client', 'default')
1208
 
        client = config.get_mail_client()
1209
 
        self.assertIsInstance(client, mail_client.DefaultMail)
1210
 
 
1211
 
        config.set_user_option('mail_client', 'editor')
1212
 
        client = config.get_mail_client()
1213
 
        self.assertIsInstance(client, mail_client.Editor)
1214
 
 
1215
 
        config.set_user_option('mail_client', 'mapi')
1216
 
        client = config.get_mail_client()
1217
 
        self.assertIsInstance(client, mail_client.MAPIClient)
1218
 
 
1219
 
        config.set_user_option('mail_client', 'xdg-email')
1220
 
        client = config.get_mail_client()
1221
 
        self.assertIsInstance(client, mail_client.XDGEmail)
1222
 
 
1223
 
        config.set_user_option('mail_client', 'firebird')
1224
 
        self.assertRaises(errors.UnknownMailClient, config.get_mail_client)
1225
 
 
1226
1910
 
1227
1911
class TestMailAddressExtraction(tests.TestCase):
1228
1912
 
1274
1958
 
1275
1959
class TestTransportConfig(tests.TestCaseWithTransport):
1276
1960
 
 
1961
    def test_load_utf8(self):
 
1962
        """Ensure we can load an utf8-encoded file."""
 
1963
        t = self.get_transport()
 
1964
        unicode_user = u'b\N{Euro Sign}ar'
 
1965
        unicode_content = u'user=%s' % (unicode_user,)
 
1966
        utf8_content = unicode_content.encode('utf8')
 
1967
        # Store the raw content in the config file
 
1968
        t.put_bytes('foo.conf', utf8_content)
 
1969
        conf = config.TransportConfig(t, 'foo.conf')
 
1970
        self.assertEquals(unicode_user, conf.get_option('user'))
 
1971
 
 
1972
    def test_load_non_ascii(self):
 
1973
        """Ensure we display a proper error on non-ascii, non utf-8 content."""
 
1974
        t = self.get_transport()
 
1975
        t.put_bytes('foo.conf', 'user=foo\n#\xff\n')
 
1976
        conf = config.TransportConfig(t, 'foo.conf')
 
1977
        self.assertRaises(errors.ConfigContentError, conf._get_configobj)
 
1978
 
 
1979
    def test_load_erroneous_content(self):
 
1980
        """Ensure we display a proper error on content that can't be parsed."""
 
1981
        t = self.get_transport()
 
1982
        t.put_bytes('foo.conf', '[open_section\n')
 
1983
        conf = config.TransportConfig(t, 'foo.conf')
 
1984
        self.assertRaises(errors.ParseConfigError, conf._get_configobj)
 
1985
 
 
1986
    def test_load_permission_denied(self):
 
1987
        """Ensure we get an empty config file if the file is inaccessible."""
 
1988
        warnings = []
 
1989
        def warning(*args):
 
1990
            warnings.append(args[0] % args[1:])
 
1991
        self.overrideAttr(trace, 'warning', warning)
 
1992
 
 
1993
        class DenyingTransport(object):
 
1994
 
 
1995
            def __init__(self, base):
 
1996
                self.base = base
 
1997
 
 
1998
            def get_bytes(self, relpath):
 
1999
                raise errors.PermissionDenied(relpath, "")
 
2000
 
 
2001
        cfg = config.TransportConfig(
 
2002
            DenyingTransport("nonexisting://"), 'control.conf')
 
2003
        self.assertIs(None, cfg.get_option('non-existant', 'SECTION'))
 
2004
        self.assertEquals(
 
2005
            warnings,
 
2006
            [u'Permission denied while trying to open configuration file '
 
2007
             u'nonexisting:///control.conf.'])
 
2008
 
1277
2009
    def test_get_value(self):
1278
2010
        """Test that retreiving a value from a section is possible"""
1279
 
        bzrdir_config = config.TransportConfig(transport.get_transport('.'),
 
2011
        bzrdir_config = config.TransportConfig(self.get_transport('.'),
1280
2012
                                               'control.conf')
1281
2013
        bzrdir_config.set_option('value', 'key', 'SECTION')
1282
2014
        bzrdir_config.set_option('value2', 'key2')
1312
2044
        self.assertIs(None, bzrdir_config.get_default_stack_on())
1313
2045
 
1314
2046
 
 
2047
class TestOldConfigHooks(tests.TestCaseWithTransport):
 
2048
 
 
2049
    def setUp(self):
 
2050
        super(TestOldConfigHooks, self).setUp()
 
2051
        create_configs_with_file_option(self)
 
2052
 
 
2053
    def assertGetHook(self, conf, name, value):
 
2054
        calls = []
 
2055
        def hook(*args):
 
2056
            calls.append(args)
 
2057
        config.OldConfigHooks.install_named_hook('get', hook, None)
 
2058
        self.addCleanup(
 
2059
            config.OldConfigHooks.uninstall_named_hook, 'get', None)
 
2060
        self.assertLength(0, calls)
 
2061
        actual_value = conf.get_user_option(name)
 
2062
        self.assertEquals(value, actual_value)
 
2063
        self.assertLength(1, calls)
 
2064
        self.assertEquals((conf, name, value), calls[0])
 
2065
 
 
2066
    def test_get_hook_bazaar(self):
 
2067
        self.assertGetHook(self.bazaar_config, 'file', 'bazaar')
 
2068
 
 
2069
    def test_get_hook_locations(self):
 
2070
        self.assertGetHook(self.locations_config, 'file', 'locations')
 
2071
 
 
2072
    def test_get_hook_branch(self):
 
2073
        # Since locations masks branch, we define a different option
 
2074
        self.branch_config.set_user_option('file2', 'branch')
 
2075
        self.assertGetHook(self.branch_config, 'file2', 'branch')
 
2076
 
 
2077
    def assertSetHook(self, conf, name, value):
 
2078
        calls = []
 
2079
        def hook(*args):
 
2080
            calls.append(args)
 
2081
        config.OldConfigHooks.install_named_hook('set', hook, None)
 
2082
        self.addCleanup(
 
2083
            config.OldConfigHooks.uninstall_named_hook, 'set', None)
 
2084
        self.assertLength(0, calls)
 
2085
        conf.set_user_option(name, value)
 
2086
        self.assertLength(1, calls)
 
2087
        # We can't assert the conf object below as different configs use
 
2088
        # different means to implement set_user_option and we care only about
 
2089
        # coverage here.
 
2090
        self.assertEquals((name, value), calls[0][1:])
 
2091
 
 
2092
    def test_set_hook_bazaar(self):
 
2093
        self.assertSetHook(self.bazaar_config, 'foo', 'bazaar')
 
2094
 
 
2095
    def test_set_hook_locations(self):
 
2096
        self.assertSetHook(self.locations_config, 'foo', 'locations')
 
2097
 
 
2098
    def test_set_hook_branch(self):
 
2099
        self.assertSetHook(self.branch_config, 'foo', 'branch')
 
2100
 
 
2101
    def assertRemoveHook(self, conf, name, section_name=None):
 
2102
        calls = []
 
2103
        def hook(*args):
 
2104
            calls.append(args)
 
2105
        config.OldConfigHooks.install_named_hook('remove', hook, None)
 
2106
        self.addCleanup(
 
2107
            config.OldConfigHooks.uninstall_named_hook, 'remove', None)
 
2108
        self.assertLength(0, calls)
 
2109
        conf.remove_user_option(name, section_name)
 
2110
        self.assertLength(1, calls)
 
2111
        # We can't assert the conf object below as different configs use
 
2112
        # different means to implement remove_user_option and we care only about
 
2113
        # coverage here.
 
2114
        self.assertEquals((name,), calls[0][1:])
 
2115
 
 
2116
    def test_remove_hook_bazaar(self):
 
2117
        self.assertRemoveHook(self.bazaar_config, 'file')
 
2118
 
 
2119
    def test_remove_hook_locations(self):
 
2120
        self.assertRemoveHook(self.locations_config, 'file',
 
2121
                              self.locations_config.location)
 
2122
 
 
2123
    def test_remove_hook_branch(self):
 
2124
        self.assertRemoveHook(self.branch_config, 'file')
 
2125
 
 
2126
    def assertLoadHook(self, name, conf_class, *conf_args):
 
2127
        calls = []
 
2128
        def hook(*args):
 
2129
            calls.append(args)
 
2130
        config.OldConfigHooks.install_named_hook('load', hook, None)
 
2131
        self.addCleanup(
 
2132
            config.OldConfigHooks.uninstall_named_hook, 'load', None)
 
2133
        self.assertLength(0, calls)
 
2134
        # Build a config
 
2135
        conf = conf_class(*conf_args)
 
2136
        # Access an option to trigger a load
 
2137
        conf.get_user_option(name)
 
2138
        self.assertLength(1, calls)
 
2139
        # Since we can't assert about conf, we just use the number of calls ;-/
 
2140
 
 
2141
    def test_load_hook_bazaar(self):
 
2142
        self.assertLoadHook('file', config.GlobalConfig)
 
2143
 
 
2144
    def test_load_hook_locations(self):
 
2145
        self.assertLoadHook('file', config.LocationConfig, self.tree.basedir)
 
2146
 
 
2147
    def test_load_hook_branch(self):
 
2148
        self.assertLoadHook('file', config.BranchConfig, self.tree.branch)
 
2149
 
 
2150
    def assertSaveHook(self, conf):
 
2151
        calls = []
 
2152
        def hook(*args):
 
2153
            calls.append(args)
 
2154
        config.OldConfigHooks.install_named_hook('save', hook, None)
 
2155
        self.addCleanup(
 
2156
            config.OldConfigHooks.uninstall_named_hook, 'save', None)
 
2157
        self.assertLength(0, calls)
 
2158
        # Setting an option triggers a save
 
2159
        conf.set_user_option('foo', 'bar')
 
2160
        self.assertLength(1, calls)
 
2161
        # Since we can't assert about conf, we just use the number of calls ;-/
 
2162
 
 
2163
    def test_save_hook_bazaar(self):
 
2164
        self.assertSaveHook(self.bazaar_config)
 
2165
 
 
2166
    def test_save_hook_locations(self):
 
2167
        self.assertSaveHook(self.locations_config)
 
2168
 
 
2169
    def test_save_hook_branch(self):
 
2170
        self.assertSaveHook(self.branch_config)
 
2171
 
 
2172
 
 
2173
class TestOldConfigHooksForRemote(tests.TestCaseWithTransport):
 
2174
    """Tests config hooks for remote configs.
 
2175
 
 
2176
    No tests for the remove hook as this is not implemented there.
 
2177
    """
 
2178
 
 
2179
    def setUp(self):
 
2180
        super(TestOldConfigHooksForRemote, self).setUp()
 
2181
        self.transport_server = test_server.SmartTCPServer_for_testing
 
2182
        create_configs_with_file_option(self)
 
2183
 
 
2184
    def assertGetHook(self, conf, name, value):
 
2185
        calls = []
 
2186
        def hook(*args):
 
2187
            calls.append(args)
 
2188
        config.OldConfigHooks.install_named_hook('get', hook, None)
 
2189
        self.addCleanup(
 
2190
            config.OldConfigHooks.uninstall_named_hook, 'get', None)
 
2191
        self.assertLength(0, calls)
 
2192
        actual_value = conf.get_option(name)
 
2193
        self.assertEquals(value, actual_value)
 
2194
        self.assertLength(1, calls)
 
2195
        self.assertEquals((conf, name, value), calls[0])
 
2196
 
 
2197
    def test_get_hook_remote_branch(self):
 
2198
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2199
        self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
 
2200
 
 
2201
    def test_get_hook_remote_bzrdir(self):
 
2202
        remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
 
2203
        conf = remote_bzrdir._get_config()
 
2204
        conf.set_option('remotedir', 'file')
 
2205
        self.assertGetHook(conf, 'file', 'remotedir')
 
2206
 
 
2207
    def assertSetHook(self, conf, name, value):
 
2208
        calls = []
 
2209
        def hook(*args):
 
2210
            calls.append(args)
 
2211
        config.OldConfigHooks.install_named_hook('set', hook, None)
 
2212
        self.addCleanup(
 
2213
            config.OldConfigHooks.uninstall_named_hook, 'set', None)
 
2214
        self.assertLength(0, calls)
 
2215
        conf.set_option(value, name)
 
2216
        self.assertLength(1, calls)
 
2217
        # We can't assert the conf object below as different configs use
 
2218
        # different means to implement set_user_option and we care only about
 
2219
        # coverage here.
 
2220
        self.assertEquals((name, value), calls[0][1:])
 
2221
 
 
2222
    def test_set_hook_remote_branch(self):
 
2223
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2224
        self.addCleanup(remote_branch.lock_write().unlock)
 
2225
        self.assertSetHook(remote_branch._get_config(), 'file', 'remote')
 
2226
 
 
2227
    def test_set_hook_remote_bzrdir(self):
 
2228
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2229
        self.addCleanup(remote_branch.lock_write().unlock)
 
2230
        remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
 
2231
        self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
 
2232
 
 
2233
    def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
 
2234
        calls = []
 
2235
        def hook(*args):
 
2236
            calls.append(args)
 
2237
        config.OldConfigHooks.install_named_hook('load', hook, None)
 
2238
        self.addCleanup(
 
2239
            config.OldConfigHooks.uninstall_named_hook, 'load', None)
 
2240
        self.assertLength(0, calls)
 
2241
        # Build a config
 
2242
        conf = conf_class(*conf_args)
 
2243
        # Access an option to trigger a load
 
2244
        conf.get_option(name)
 
2245
        self.assertLength(expected_nb_calls, calls)
 
2246
        # Since we can't assert about conf, we just use the number of calls ;-/
 
2247
 
 
2248
    def test_load_hook_remote_branch(self):
 
2249
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2250
        self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
 
2251
 
 
2252
    def test_load_hook_remote_bzrdir(self):
 
2253
        remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
 
2254
        # The config file doesn't exist, set an option to force its creation
 
2255
        conf = remote_bzrdir._get_config()
 
2256
        conf.set_option('remotedir', 'file')
 
2257
        # We get one call for the server and one call for the client, this is
 
2258
        # caused by the differences in implementations betwen
 
2259
        # SmartServerBzrDirRequestConfigFile (in smart/bzrdir.py) and
 
2260
        # SmartServerBranchGetConfigFile (in smart/branch.py)
 
2261
        self.assertLoadHook(2 ,'file', remote.RemoteBzrDirConfig, remote_bzrdir)
 
2262
 
 
2263
    def assertSaveHook(self, conf):
 
2264
        calls = []
 
2265
        def hook(*args):
 
2266
            calls.append(args)
 
2267
        config.OldConfigHooks.install_named_hook('save', hook, None)
 
2268
        self.addCleanup(
 
2269
            config.OldConfigHooks.uninstall_named_hook, 'save', None)
 
2270
        self.assertLength(0, calls)
 
2271
        # Setting an option triggers a save
 
2272
        conf.set_option('foo', 'bar')
 
2273
        self.assertLength(1, calls)
 
2274
        # Since we can't assert about conf, we just use the number of calls ;-/
 
2275
 
 
2276
    def test_save_hook_remote_branch(self):
 
2277
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2278
        self.addCleanup(remote_branch.lock_write().unlock)
 
2279
        self.assertSaveHook(remote_branch._get_config())
 
2280
 
 
2281
    def test_save_hook_remote_bzrdir(self):
 
2282
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2283
        self.addCleanup(remote_branch.lock_write().unlock)
 
2284
        remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
 
2285
        self.assertSaveHook(remote_bzrdir._get_config())
 
2286
 
 
2287
 
 
2288
class TestOption(tests.TestCase):
 
2289
 
 
2290
    def test_default_value(self):
 
2291
        opt = config.Option('foo', default='bar')
 
2292
        self.assertEquals('bar', opt.get_default())
 
2293
 
 
2294
    def test_callable_default_value(self):
 
2295
        def bar_as_unicode():
 
2296
            return u'bar'
 
2297
        opt = config.Option('foo', default=bar_as_unicode)
 
2298
        self.assertEquals('bar', opt.get_default())
 
2299
 
 
2300
    def test_default_value_from_env(self):
 
2301
        opt = config.Option('foo', default='bar', default_from_env=['FOO'])
 
2302
        self.overrideEnv('FOO', 'quux')
 
2303
        # Env variable provides a default taking over the option one
 
2304
        self.assertEquals('quux', opt.get_default())
 
2305
 
 
2306
    def test_first_default_value_from_env_wins(self):
 
2307
        opt = config.Option('foo', default='bar',
 
2308
                            default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
 
2309
        self.overrideEnv('FOO', 'foo')
 
2310
        self.overrideEnv('BAZ', 'baz')
 
2311
        # The first env var set wins
 
2312
        self.assertEquals('foo', opt.get_default())
 
2313
 
 
2314
    def test_not_supported_list_default_value(self):
 
2315
        self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
 
2316
 
 
2317
    def test_not_supported_object_default_value(self):
 
2318
        self.assertRaises(AssertionError, config.Option, 'foo',
 
2319
                          default=object())
 
2320
 
 
2321
    def test_not_supported_callable_default_value_not_unicode(self):
 
2322
        def bar_not_unicode():
 
2323
            return 'bar'
 
2324
        opt = config.Option('foo', default=bar_not_unicode)
 
2325
        self.assertRaises(AssertionError, opt.get_default)
 
2326
 
 
2327
 
 
2328
class TestOptionConverterMixin(object):
 
2329
 
 
2330
    def assertConverted(self, expected, opt, value):
 
2331
        self.assertEquals(expected, opt.convert_from_unicode(None, value))
 
2332
 
 
2333
    def assertWarns(self, opt, value):
 
2334
        warnings = []
 
2335
        def warning(*args):
 
2336
            warnings.append(args[0] % args[1:])
 
2337
        self.overrideAttr(trace, 'warning', warning)
 
2338
        self.assertEquals(None, opt.convert_from_unicode(None, value))
 
2339
        self.assertLength(1, warnings)
 
2340
        self.assertEquals(
 
2341
            'Value "%s" is not valid for "%s"' % (value, opt.name),
 
2342
            warnings[0])
 
2343
 
 
2344
    def assertErrors(self, opt, value):
 
2345
        self.assertRaises(errors.ConfigOptionValueError,
 
2346
                          opt.convert_from_unicode, None, value)
 
2347
 
 
2348
    def assertConvertInvalid(self, opt, invalid_value):
 
2349
        opt.invalid = None
 
2350
        self.assertEquals(None, opt.convert_from_unicode(None, invalid_value))
 
2351
        opt.invalid = 'warning'
 
2352
        self.assertWarns(opt, invalid_value)
 
2353
        opt.invalid = 'error'
 
2354
        self.assertErrors(opt, invalid_value)
 
2355
 
 
2356
 
 
2357
class TestOptionWithBooleanConverter(tests.TestCase, TestOptionConverterMixin):
 
2358
 
 
2359
    def get_option(self):
 
2360
        return config.Option('foo', help='A boolean.',
 
2361
                             from_unicode=config.bool_from_store)
 
2362
 
 
2363
    def test_convert_invalid(self):
 
2364
        opt = self.get_option()
 
2365
        # A string that is not recognized as a boolean
 
2366
        self.assertConvertInvalid(opt, u'invalid-boolean')
 
2367
        # A list of strings is never recognized as a boolean
 
2368
        self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
 
2369
 
 
2370
    def test_convert_valid(self):
 
2371
        opt = self.get_option()
 
2372
        self.assertConverted(True, opt, u'True')
 
2373
        self.assertConverted(True, opt, u'1')
 
2374
        self.assertConverted(False, opt, u'False')
 
2375
 
 
2376
 
 
2377
class TestOptionWithIntegerConverter(tests.TestCase, TestOptionConverterMixin):
 
2378
 
 
2379
    def get_option(self):
 
2380
        return config.Option('foo', help='An integer.',
 
2381
                             from_unicode=config.int_from_store)
 
2382
 
 
2383
    def test_convert_invalid(self):
 
2384
        opt = self.get_option()
 
2385
        # A string that is not recognized as an integer
 
2386
        self.assertConvertInvalid(opt, u'forty-two')
 
2387
        # A list of strings is never recognized as an integer
 
2388
        self.assertConvertInvalid(opt, [u'a', u'list'])
 
2389
 
 
2390
    def test_convert_valid(self):
 
2391
        opt = self.get_option()
 
2392
        self.assertConverted(16, opt, u'16')
 
2393
 
 
2394
 
 
2395
class TestOptionWithSIUnitConverter(tests.TestCase, TestOptionConverterMixin):
 
2396
 
 
2397
    def get_option(self):
 
2398
        return config.Option('foo', help='An integer in SI units.',
 
2399
                             from_unicode=config.int_SI_from_store)
 
2400
 
 
2401
    def test_convert_invalid(self):
 
2402
        opt = self.get_option()
 
2403
        self.assertConvertInvalid(opt, u'not-a-unit')
 
2404
        self.assertConvertInvalid(opt, u'Gb') # Forgot the int
 
2405
        self.assertConvertInvalid(opt, u'1b') # Forgot the unit
 
2406
        self.assertConvertInvalid(opt, u'1GG')
 
2407
        self.assertConvertInvalid(opt, u'1Mbb')
 
2408
        self.assertConvertInvalid(opt, u'1MM')
 
2409
 
 
2410
    def test_convert_valid(self):
 
2411
        opt = self.get_option()
 
2412
        self.assertConverted(int(5e3), opt, u'5kb')
 
2413
        self.assertConverted(int(5e6), opt, u'5M')
 
2414
        self.assertConverted(int(5e6), opt, u'5MB')
 
2415
        self.assertConverted(int(5e9), opt, u'5g')
 
2416
        self.assertConverted(int(5e9), opt, u'5gB')
 
2417
        self.assertConverted(100, opt, u'100')
 
2418
 
 
2419
 
 
2420
class TestListOption(tests.TestCase, TestOptionConverterMixin):
 
2421
 
 
2422
    def get_option(self):
 
2423
        return config.ListOption('foo', help='A list.')
 
2424
 
 
2425
    def test_convert_invalid(self):
 
2426
        opt = self.get_option()
 
2427
        # We don't even try to convert a list into a list, we only expect
 
2428
        # strings
 
2429
        self.assertConvertInvalid(opt, [1])
 
2430
        # No string is invalid as all forms can be converted to a list
 
2431
 
 
2432
    def test_convert_valid(self):
 
2433
        opt = self.get_option()
 
2434
        # An empty string is an empty list
 
2435
        self.assertConverted([], opt, '') # Using a bare str() just in case
 
2436
        self.assertConverted([], opt, u'')
 
2437
        # A boolean
 
2438
        self.assertConverted([u'True'], opt, u'True')
 
2439
        # An integer
 
2440
        self.assertConverted([u'42'], opt, u'42')
 
2441
        # A single string
 
2442
        self.assertConverted([u'bar'], opt, u'bar')
 
2443
 
 
2444
 
 
2445
class TestRegistryOption(tests.TestCase, TestOptionConverterMixin):
 
2446
 
 
2447
    def get_option(self, registry):
 
2448
        return config.RegistryOption('foo', registry,
 
2449
                help='A registry option.')
 
2450
 
 
2451
    def test_convert_invalid(self):
 
2452
        registry = _mod_registry.Registry()
 
2453
        opt = self.get_option(registry)
 
2454
        self.assertConvertInvalid(opt, [1])
 
2455
        self.assertConvertInvalid(opt, u"notregistered")
 
2456
 
 
2457
    def test_convert_valid(self):
 
2458
        registry = _mod_registry.Registry()
 
2459
        registry.register("someval", 1234)
 
2460
        opt = self.get_option(registry)
 
2461
        # Using a bare str() just in case
 
2462
        self.assertConverted(1234, opt, "someval")
 
2463
        self.assertConverted(1234, opt, u'someval')
 
2464
        self.assertConverted(None, opt, None)
 
2465
 
 
2466
    def test_help(self):
 
2467
        registry = _mod_registry.Registry()
 
2468
        registry.register("someval", 1234, help="some option")
 
2469
        registry.register("dunno", 1234, help="some other option")
 
2470
        opt = self.get_option(registry)
 
2471
        self.assertEquals(
 
2472
            'A registry option.\n'
 
2473
            '\n'
 
2474
            'The following values are supported:\n'
 
2475
            ' dunno - some other option\n'
 
2476
            ' someval - some option\n',
 
2477
            opt.help)
 
2478
 
 
2479
    def test_get_help_text(self):
 
2480
        registry = _mod_registry.Registry()
 
2481
        registry.register("someval", 1234, help="some option")
 
2482
        registry.register("dunno", 1234, help="some other option")
 
2483
        opt = self.get_option(registry)
 
2484
        self.assertEquals(
 
2485
            'A registry option.\n'
 
2486
            '\n'
 
2487
            'The following values are supported:\n'
 
2488
            ' dunno - some other option\n'
 
2489
            ' someval - some option\n',
 
2490
            opt.get_help_text())
 
2491
 
 
2492
 
 
2493
class TestOptionRegistry(tests.TestCase):
 
2494
 
 
2495
    def setUp(self):
 
2496
        super(TestOptionRegistry, self).setUp()
 
2497
        # Always start with an empty registry
 
2498
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
2499
        self.registry = config.option_registry
 
2500
 
 
2501
    def test_register(self):
 
2502
        opt = config.Option('foo')
 
2503
        self.registry.register(opt)
 
2504
        self.assertIs(opt, self.registry.get('foo'))
 
2505
 
 
2506
    def test_registered_help(self):
 
2507
        opt = config.Option('foo', help='A simple option')
 
2508
        self.registry.register(opt)
 
2509
        self.assertEquals('A simple option', self.registry.get_help('foo'))
 
2510
 
 
2511
    lazy_option = config.Option('lazy_foo', help='Lazy help')
 
2512
 
 
2513
    def test_register_lazy(self):
 
2514
        self.registry.register_lazy('lazy_foo', self.__module__,
 
2515
                                    'TestOptionRegistry.lazy_option')
 
2516
        self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
 
2517
 
 
2518
    def test_registered_lazy_help(self):
 
2519
        self.registry.register_lazy('lazy_foo', self.__module__,
 
2520
                                    'TestOptionRegistry.lazy_option')
 
2521
        self.assertEquals('Lazy help', self.registry.get_help('lazy_foo'))
 
2522
 
 
2523
 
 
2524
class TestRegisteredOptions(tests.TestCase):
 
2525
    """All registered options should verify some constraints."""
 
2526
 
 
2527
    scenarios = [(key, {'option_name': key, 'option': option}) for key, option
 
2528
                 in config.option_registry.iteritems()]
 
2529
 
 
2530
    def setUp(self):
 
2531
        super(TestRegisteredOptions, self).setUp()
 
2532
        self.registry = config.option_registry
 
2533
 
 
2534
    def test_proper_name(self):
 
2535
        # An option should be registered under its own name, this can't be
 
2536
        # checked at registration time for the lazy ones.
 
2537
        self.assertEquals(self.option_name, self.option.name)
 
2538
 
 
2539
    def test_help_is_set(self):
 
2540
        option_help = self.registry.get_help(self.option_name)
 
2541
        self.assertNotEquals(None, option_help)
 
2542
        # Come on, think about the user, he really wants to know what the
 
2543
        # option is about
 
2544
        self.assertIsNot(None, option_help)
 
2545
        self.assertNotEquals('', option_help)
 
2546
 
 
2547
 
 
2548
class TestSection(tests.TestCase):
 
2549
 
 
2550
    # FIXME: Parametrize so that all sections produced by Stores run these
 
2551
    # tests -- vila 2011-04-01
 
2552
 
 
2553
    def test_get_a_value(self):
 
2554
        a_dict = dict(foo='bar')
 
2555
        section = config.Section('myID', a_dict)
 
2556
        self.assertEquals('bar', section.get('foo'))
 
2557
 
 
2558
    def test_get_unknown_option(self):
 
2559
        a_dict = dict()
 
2560
        section = config.Section(None, a_dict)
 
2561
        self.assertEquals('out of thin air',
 
2562
                          section.get('foo', 'out of thin air'))
 
2563
 
 
2564
    def test_options_is_shared(self):
 
2565
        a_dict = dict()
 
2566
        section = config.Section(None, a_dict)
 
2567
        self.assertIs(a_dict, section.options)
 
2568
 
 
2569
 
 
2570
class TestMutableSection(tests.TestCase):
 
2571
 
 
2572
    scenarios = [('mutable',
 
2573
                  {'get_section':
 
2574
                       lambda opts: config.MutableSection('myID', opts)},),
 
2575
        ]
 
2576
 
 
2577
    def test_set(self):
 
2578
        a_dict = dict(foo='bar')
 
2579
        section = self.get_section(a_dict)
 
2580
        section.set('foo', 'new_value')
 
2581
        self.assertEquals('new_value', section.get('foo'))
 
2582
        # The change appears in the shared section
 
2583
        self.assertEquals('new_value', a_dict.get('foo'))
 
2584
        # We keep track of the change
 
2585
        self.assertTrue('foo' in section.orig)
 
2586
        self.assertEquals('bar', section.orig.get('foo'))
 
2587
 
 
2588
    def test_set_preserve_original_once(self):
 
2589
        a_dict = dict(foo='bar')
 
2590
        section = self.get_section(a_dict)
 
2591
        section.set('foo', 'first_value')
 
2592
        section.set('foo', 'second_value')
 
2593
        # We keep track of the original value
 
2594
        self.assertTrue('foo' in section.orig)
 
2595
        self.assertEquals('bar', section.orig.get('foo'))
 
2596
 
 
2597
    def test_remove(self):
 
2598
        a_dict = dict(foo='bar')
 
2599
        section = self.get_section(a_dict)
 
2600
        section.remove('foo')
 
2601
        # We get None for unknown options via the default value
 
2602
        self.assertEquals(None, section.get('foo'))
 
2603
        # Or we just get the default value
 
2604
        self.assertEquals('unknown', section.get('foo', 'unknown'))
 
2605
        self.assertFalse('foo' in section.options)
 
2606
        # We keep track of the deletion
 
2607
        self.assertTrue('foo' in section.orig)
 
2608
        self.assertEquals('bar', section.orig.get('foo'))
 
2609
 
 
2610
    def test_remove_new_option(self):
 
2611
        a_dict = dict()
 
2612
        section = self.get_section(a_dict)
 
2613
        section.set('foo', 'bar')
 
2614
        section.remove('foo')
 
2615
        self.assertFalse('foo' in section.options)
 
2616
        # The option didn't exist initially so it we need to keep track of it
 
2617
        # with a special value
 
2618
        self.assertTrue('foo' in section.orig)
 
2619
        self.assertEquals(config._NewlyCreatedOption, section.orig['foo'])
 
2620
 
 
2621
 
 
2622
class TestCommandLineStore(tests.TestCase):
 
2623
 
 
2624
    def setUp(self):
 
2625
        super(TestCommandLineStore, self).setUp()
 
2626
        self.store = config.CommandLineStore()
 
2627
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
2628
 
 
2629
    def get_section(self):
 
2630
        """Get the unique section for the command line overrides."""
 
2631
        sections = list(self.store.get_sections())
 
2632
        self.assertLength(1, sections)
 
2633
        store, section = sections[0]
 
2634
        self.assertEquals(self.store, store)
 
2635
        return section
 
2636
 
 
2637
    def test_no_override(self):
 
2638
        self.store._from_cmdline([])
 
2639
        section = self.get_section()
 
2640
        self.assertLength(0, list(section.iter_option_names()))
 
2641
 
 
2642
    def test_simple_override(self):
 
2643
        self.store._from_cmdline(['a=b'])
 
2644
        section = self.get_section()
 
2645
        self.assertEqual('b', section.get('a'))
 
2646
 
 
2647
    def test_list_override(self):
 
2648
        opt = config.ListOption('l')
 
2649
        config.option_registry.register(opt)
 
2650
        self.store._from_cmdline(['l=1,2,3'])
 
2651
        val = self.get_section().get('l')
 
2652
        self.assertEqual('1,2,3', val)
 
2653
        # Reminder: lists should be registered as such explicitely, otherwise
 
2654
        # the conversion needs to be done afterwards.
 
2655
        self.assertEqual(['1', '2', '3'],
 
2656
                         opt.convert_from_unicode(self.store, val))
 
2657
 
 
2658
    def test_multiple_overrides(self):
 
2659
        self.store._from_cmdline(['a=b', 'x=y'])
 
2660
        section = self.get_section()
 
2661
        self.assertEquals('b', section.get('a'))
 
2662
        self.assertEquals('y', section.get('x'))
 
2663
 
 
2664
    def test_wrong_syntax(self):
 
2665
        self.assertRaises(errors.BzrCommandError,
 
2666
                          self.store._from_cmdline, ['a=b', 'c'])
 
2667
 
 
2668
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
 
2669
 
 
2670
    scenarios = [(key, {'get_store': builder}) for key, builder
 
2671
                 in config.test_store_builder_registry.iteritems()] + [
 
2672
        ('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
 
2673
 
 
2674
    def test_id(self):
 
2675
        store = self.get_store(self)
 
2676
        if type(store) == config.TransportIniFileStore:
 
2677
            raise tests.TestNotApplicable(
 
2678
                "%s is not a concrete Store implementation"
 
2679
                " so it doesn't need an id" % (store.__class__.__name__,))
 
2680
        self.assertIsNot(None, store.id)
 
2681
 
 
2682
 
 
2683
class TestStore(tests.TestCaseWithTransport):
 
2684
 
 
2685
    def assertSectionContent(self, expected, (store, section)):
 
2686
        """Assert that some options have the proper values in a section."""
 
2687
        expected_name, expected_options = expected
 
2688
        self.assertEquals(expected_name, section.id)
 
2689
        self.assertEquals(
 
2690
            expected_options,
 
2691
            dict([(k, section.get(k)) for k in expected_options.keys()]))
 
2692
 
 
2693
 
 
2694
class TestReadonlyStore(TestStore):
 
2695
 
 
2696
    scenarios = [(key, {'get_store': builder}) for key, builder
 
2697
                 in config.test_store_builder_registry.iteritems()]
 
2698
 
 
2699
    def test_building_delays_load(self):
 
2700
        store = self.get_store(self)
 
2701
        self.assertEquals(False, store.is_loaded())
 
2702
        store._load_from_string('')
 
2703
        self.assertEquals(True, store.is_loaded())
 
2704
 
 
2705
    def test_get_no_sections_for_empty(self):
 
2706
        store = self.get_store(self)
 
2707
        store._load_from_string('')
 
2708
        self.assertEquals([], list(store.get_sections()))
 
2709
 
 
2710
    def test_get_default_section(self):
 
2711
        store = self.get_store(self)
 
2712
        store._load_from_string('foo=bar')
 
2713
        sections = list(store.get_sections())
 
2714
        self.assertLength(1, sections)
 
2715
        self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
 
2716
 
 
2717
    def test_get_named_section(self):
 
2718
        store = self.get_store(self)
 
2719
        store._load_from_string('[baz]\nfoo=bar')
 
2720
        sections = list(store.get_sections())
 
2721
        self.assertLength(1, sections)
 
2722
        self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
 
2723
 
 
2724
    def test_load_from_string_fails_for_non_empty_store(self):
 
2725
        store = self.get_store(self)
 
2726
        store._load_from_string('foo=bar')
 
2727
        self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
 
2728
 
 
2729
 
 
2730
class TestStoreQuoting(TestStore):
 
2731
 
 
2732
    scenarios = [(key, {'get_store': builder}) for key, builder
 
2733
                 in config.test_store_builder_registry.iteritems()]
 
2734
 
 
2735
    def setUp(self):
 
2736
        super(TestStoreQuoting, self).setUp()
 
2737
        self.store = self.get_store(self)
 
2738
        # We need a loaded store but any content will do
 
2739
        self.store._load_from_string('')
 
2740
 
 
2741
    def assertIdempotent(self, s):
 
2742
        """Assert that quoting an unquoted string is a no-op and vice-versa.
 
2743
 
 
2744
        What matters here is that option values, as they appear in a store, can
 
2745
        be safely round-tripped out of the store and back.
 
2746
 
 
2747
        :param s: A string, quoted if required.
 
2748
        """
 
2749
        self.assertEquals(s, self.store.quote(self.store.unquote(s)))
 
2750
        self.assertEquals(s, self.store.unquote(self.store.quote(s)))
 
2751
 
 
2752
    def test_empty_string(self):
 
2753
        if isinstance(self.store, config.IniFileStore):
 
2754
            # configobj._quote doesn't handle empty values
 
2755
            self.assertRaises(AssertionError,
 
2756
                              self.assertIdempotent, '')
 
2757
        else:
 
2758
            self.assertIdempotent('')
 
2759
        # But quoted empty strings are ok
 
2760
        self.assertIdempotent('""')
 
2761
 
 
2762
    def test_embedded_spaces(self):
 
2763
        self.assertIdempotent('" a b c "')
 
2764
 
 
2765
    def test_embedded_commas(self):
 
2766
        self.assertIdempotent('" a , b c "')
 
2767
 
 
2768
    def test_simple_comma(self):
 
2769
        if isinstance(self.store, config.IniFileStore):
 
2770
            # configobj requires that lists are special-cased
 
2771
           self.assertRaises(AssertionError,
 
2772
                             self.assertIdempotent, ',')
 
2773
        else:
 
2774
            self.assertIdempotent(',')
 
2775
        # When a single comma is required, quoting is also required
 
2776
        self.assertIdempotent('","')
 
2777
 
 
2778
    def test_list(self):
 
2779
        if isinstance(self.store, config.IniFileStore):
 
2780
            # configobj requires that lists are special-cased
 
2781
            self.assertRaises(AssertionError,
 
2782
                              self.assertIdempotent, 'a,b')
 
2783
        else:
 
2784
            self.assertIdempotent('a,b')
 
2785
 
 
2786
 
 
2787
class TestDictFromStore(tests.TestCase):
 
2788
 
 
2789
    def test_unquote_not_string(self):
 
2790
        conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
 
2791
        value = conf.get('a_section')
 
2792
        # Urgh, despite 'conf' asking for the no-name section, we get the
 
2793
        # content of another section as a dict o_O
 
2794
        self.assertEquals({'a': '1'}, value)
 
2795
        unquoted = conf.store.unquote(value)
 
2796
        # Which cannot be unquoted but shouldn't crash either (the use cases
 
2797
        # are getting the value or displaying it. In the later case, '%s' will
 
2798
        # do).
 
2799
        self.assertEquals({'a': '1'}, unquoted)
 
2800
        self.assertEquals("{u'a': u'1'}", '%s' % (unquoted,))
 
2801
 
 
2802
 
 
2803
class TestIniFileStoreContent(tests.TestCaseWithTransport):
 
2804
    """Simulate loading a config store with content of various encodings.
 
2805
 
 
2806
    All files produced by bzr are in utf8 content.
 
2807
 
 
2808
    Users may modify them manually and end up with a file that can't be
 
2809
    loaded. We need to issue proper error messages in this case.
 
2810
    """
 
2811
 
 
2812
    invalid_utf8_char = '\xff'
 
2813
 
 
2814
    def test_load_utf8(self):
 
2815
        """Ensure we can load an utf8-encoded file."""
 
2816
        t = self.get_transport()
 
2817
        # From http://pad.lv/799212
 
2818
        unicode_user = u'b\N{Euro Sign}ar'
 
2819
        unicode_content = u'user=%s' % (unicode_user,)
 
2820
        utf8_content = unicode_content.encode('utf8')
 
2821
        # Store the raw content in the config file
 
2822
        t.put_bytes('foo.conf', utf8_content)
 
2823
        store = config.TransportIniFileStore(t, 'foo.conf')
 
2824
        store.load()
 
2825
        stack = config.Stack([store.get_sections], store)
 
2826
        self.assertEquals(unicode_user, stack.get('user'))
 
2827
 
 
2828
    def test_load_non_ascii(self):
 
2829
        """Ensure we display a proper error on non-ascii, non utf-8 content."""
 
2830
        t = self.get_transport()
 
2831
        t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
 
2832
        store = config.TransportIniFileStore(t, 'foo.conf')
 
2833
        self.assertRaises(errors.ConfigContentError, store.load)
 
2834
 
 
2835
    def test_load_erroneous_content(self):
 
2836
        """Ensure we display a proper error on content that can't be parsed."""
 
2837
        t = self.get_transport()
 
2838
        t.put_bytes('foo.conf', '[open_section\n')
 
2839
        store = config.TransportIniFileStore(t, 'foo.conf')
 
2840
        self.assertRaises(errors.ParseConfigError, store.load)
 
2841
 
 
2842
    def test_load_permission_denied(self):
 
2843
        """Ensure we get warned when trying to load an inaccessible file."""
 
2844
        warnings = []
 
2845
        def warning(*args):
 
2846
            warnings.append(args[0] % args[1:])
 
2847
        self.overrideAttr(trace, 'warning', warning)
 
2848
 
 
2849
        t = self.get_transport()
 
2850
 
 
2851
        def get_bytes(relpath):
 
2852
            raise errors.PermissionDenied(relpath, "")
 
2853
        t.get_bytes = get_bytes
 
2854
        store = config.TransportIniFileStore(t, 'foo.conf')
 
2855
        self.assertRaises(errors.PermissionDenied, store.load)
 
2856
        self.assertEquals(
 
2857
            warnings,
 
2858
            [u'Permission denied while trying to load configuration store %s.'
 
2859
             % store.external_url()])
 
2860
 
 
2861
 
 
2862
class TestIniConfigContent(tests.TestCaseWithTransport):
 
2863
    """Simulate loading a IniBasedConfig with content of various encodings.
 
2864
 
 
2865
    All files produced by bzr are in utf8 content.
 
2866
 
 
2867
    Users may modify them manually and end up with a file that can't be
 
2868
    loaded. We need to issue proper error messages in this case.
 
2869
    """
 
2870
 
 
2871
    invalid_utf8_char = '\xff'
 
2872
 
 
2873
    def test_load_utf8(self):
 
2874
        """Ensure we can load an utf8-encoded file."""
 
2875
        # From http://pad.lv/799212
 
2876
        unicode_user = u'b\N{Euro Sign}ar'
 
2877
        unicode_content = u'user=%s' % (unicode_user,)
 
2878
        utf8_content = unicode_content.encode('utf8')
 
2879
        # Store the raw content in the config file
 
2880
        with open('foo.conf', 'wb') as f:
 
2881
            f.write(utf8_content)
 
2882
        conf = config.IniBasedConfig(file_name='foo.conf')
 
2883
        self.assertEquals(unicode_user, conf.get_user_option('user'))
 
2884
 
 
2885
    def test_load_badly_encoded_content(self):
 
2886
        """Ensure we display a proper error on non-ascii, non utf-8 content."""
 
2887
        with open('foo.conf', 'wb') as f:
 
2888
            f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
 
2889
        conf = config.IniBasedConfig(file_name='foo.conf')
 
2890
        self.assertRaises(errors.ConfigContentError, conf._get_parser)
 
2891
 
 
2892
    def test_load_erroneous_content(self):
 
2893
        """Ensure we display a proper error on content that can't be parsed."""
 
2894
        with open('foo.conf', 'wb') as f:
 
2895
            f.write('[open_section\n')
 
2896
        conf = config.IniBasedConfig(file_name='foo.conf')
 
2897
        self.assertRaises(errors.ParseConfigError, conf._get_parser)
 
2898
 
 
2899
 
 
2900
class TestMutableStore(TestStore):
 
2901
 
 
2902
    scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
 
2903
                 in config.test_store_builder_registry.iteritems()]
 
2904
 
 
2905
    def setUp(self):
 
2906
        super(TestMutableStore, self).setUp()
 
2907
        self.transport = self.get_transport()
 
2908
 
 
2909
    def has_store(self, store):
 
2910
        store_basename = urlutils.relative_url(self.transport.external_url(),
 
2911
                                               store.external_url())
 
2912
        return self.transport.has(store_basename)
 
2913
 
 
2914
    def test_save_empty_creates_no_file(self):
 
2915
        # FIXME: There should be a better way than relying on the test
 
2916
        # parametrization to identify branch.conf -- vila 2011-0526
 
2917
        if self.store_id in ('branch', 'remote_branch'):
 
2918
            raise tests.TestNotApplicable(
 
2919
                'branch.conf is *always* created when a branch is initialized')
 
2920
        store = self.get_store(self)
 
2921
        store.save()
 
2922
        self.assertEquals(False, self.has_store(store))
 
2923
 
 
2924
    def test_save_emptied_succeeds(self):
 
2925
        store = self.get_store(self)
 
2926
        store._load_from_string('foo=bar\n')
 
2927
        section = store.get_mutable_section(None)
 
2928
        section.remove('foo')
 
2929
        store.save()
 
2930
        self.assertEquals(True, self.has_store(store))
 
2931
        modified_store = self.get_store(self)
 
2932
        sections = list(modified_store.get_sections())
 
2933
        self.assertLength(0, sections)
 
2934
 
 
2935
    def test_save_with_content_succeeds(self):
 
2936
        # FIXME: There should be a better way than relying on the test
 
2937
        # parametrization to identify branch.conf -- vila 2011-0526
 
2938
        if self.store_id in ('branch', 'remote_branch'):
 
2939
            raise tests.TestNotApplicable(
 
2940
                'branch.conf is *always* created when a branch is initialized')
 
2941
        store = self.get_store(self)
 
2942
        store._load_from_string('foo=bar\n')
 
2943
        self.assertEquals(False, self.has_store(store))
 
2944
        store.save()
 
2945
        self.assertEquals(True, self.has_store(store))
 
2946
        modified_store = self.get_store(self)
 
2947
        sections = list(modified_store.get_sections())
 
2948
        self.assertLength(1, sections)
 
2949
        self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
 
2950
 
 
2951
    def test_set_option_in_empty_store(self):
 
2952
        store = self.get_store(self)
 
2953
        section = store.get_mutable_section(None)
 
2954
        section.set('foo', 'bar')
 
2955
        store.save()
 
2956
        modified_store = self.get_store(self)
 
2957
        sections = list(modified_store.get_sections())
 
2958
        self.assertLength(1, sections)
 
2959
        self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
 
2960
 
 
2961
    def test_set_option_in_default_section(self):
 
2962
        store = self.get_store(self)
 
2963
        store._load_from_string('')
 
2964
        section = store.get_mutable_section(None)
 
2965
        section.set('foo', 'bar')
 
2966
        store.save()
 
2967
        modified_store = self.get_store(self)
 
2968
        sections = list(modified_store.get_sections())
 
2969
        self.assertLength(1, sections)
 
2970
        self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
 
2971
 
 
2972
    def test_set_option_in_named_section(self):
 
2973
        store = self.get_store(self)
 
2974
        store._load_from_string('')
 
2975
        section = store.get_mutable_section('baz')
 
2976
        section.set('foo', 'bar')
 
2977
        store.save()
 
2978
        modified_store = self.get_store(self)
 
2979
        sections = list(modified_store.get_sections())
 
2980
        self.assertLength(1, sections)
 
2981
        self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
 
2982
 
 
2983
    def test_load_hook(self):
 
2984
        # We first needs to ensure that the store exists
 
2985
        store = self.get_store(self)
 
2986
        section = store.get_mutable_section('baz')
 
2987
        section.set('foo', 'bar')
 
2988
        store.save()
 
2989
        # Now we can try to load it
 
2990
        store = self.get_store(self)
 
2991
        calls = []
 
2992
        def hook(*args):
 
2993
            calls.append(args)
 
2994
        config.ConfigHooks.install_named_hook('load', hook, None)
 
2995
        self.assertLength(0, calls)
 
2996
        store.load()
 
2997
        self.assertLength(1, calls)
 
2998
        self.assertEquals((store,), calls[0])
 
2999
 
 
3000
    def test_save_hook(self):
 
3001
        calls = []
 
3002
        def hook(*args):
 
3003
            calls.append(args)
 
3004
        config.ConfigHooks.install_named_hook('save', hook, None)
 
3005
        self.assertLength(0, calls)
 
3006
        store = self.get_store(self)
 
3007
        section = store.get_mutable_section('baz')
 
3008
        section.set('foo', 'bar')
 
3009
        store.save()
 
3010
        self.assertLength(1, calls)
 
3011
        self.assertEquals((store,), calls[0])
 
3012
 
 
3013
    def test_set_mark_dirty(self):
 
3014
        stack = config.MemoryStack('')
 
3015
        self.assertLength(0, stack.store.dirty_sections)
 
3016
        stack.set('foo', 'baz')
 
3017
        self.assertLength(1, stack.store.dirty_sections)
 
3018
        self.assertTrue(stack.store._need_saving())
 
3019
 
 
3020
    def test_remove_mark_dirty(self):
 
3021
        stack = config.MemoryStack('foo=bar')
 
3022
        self.assertLength(0, stack.store.dirty_sections)
 
3023
        stack.remove('foo')
 
3024
        self.assertLength(1, stack.store.dirty_sections)
 
3025
        self.assertTrue(stack.store._need_saving())
 
3026
 
 
3027
 
 
3028
class TestStoreSaveChanges(tests.TestCaseWithTransport):
 
3029
    """Tests that config changes are kept in memory and saved on-demand."""
 
3030
 
 
3031
    def setUp(self):
 
3032
        super(TestStoreSaveChanges, self).setUp()
 
3033
        self.transport = self.get_transport()
 
3034
        # Most of the tests involve two stores pointing to the same persistent
 
3035
        # storage to observe the effects of concurrent changes
 
3036
        self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
 
3037
        self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
 
3038
        self.warnings = []
 
3039
        def warning(*args):
 
3040
            self.warnings.append(args[0] % args[1:])
 
3041
        self.overrideAttr(trace, 'warning', warning)
 
3042
 
 
3043
    def has_store(self, store):
 
3044
        store_basename = urlutils.relative_url(self.transport.external_url(),
 
3045
                                               store.external_url())
 
3046
        return self.transport.has(store_basename)
 
3047
 
 
3048
    def get_stack(self, store):
 
3049
        # Any stack will do as long as it uses the right store, just a single
 
3050
        # no-name section is enough
 
3051
        return config.Stack([store.get_sections], store)
 
3052
 
 
3053
    def test_no_changes_no_save(self):
 
3054
        s = self.get_stack(self.st1)
 
3055
        s.store.save_changes()
 
3056
        self.assertEquals(False, self.has_store(self.st1))
 
3057
 
 
3058
    def test_unrelated_concurrent_update(self):
 
3059
        s1 = self.get_stack(self.st1)
 
3060
        s2 = self.get_stack(self.st2)
 
3061
        s1.set('foo', 'bar')
 
3062
        s2.set('baz', 'quux')
 
3063
        s1.store.save()
 
3064
        # Changes don't propagate magically
 
3065
        self.assertEquals(None, s1.get('baz'))
 
3066
        s2.store.save_changes()
 
3067
        self.assertEquals('quux', s2.get('baz'))
 
3068
        # Changes are acquired when saving
 
3069
        self.assertEquals('bar', s2.get('foo'))
 
3070
        # Since there is no overlap, no warnings are emitted
 
3071
        self.assertLength(0, self.warnings)
 
3072
 
 
3073
    def test_concurrent_update_modified(self):
 
3074
        s1 = self.get_stack(self.st1)
 
3075
        s2 = self.get_stack(self.st2)
 
3076
        s1.set('foo', 'bar')
 
3077
        s2.set('foo', 'baz')
 
3078
        s1.store.save()
 
3079
        # Last speaker wins
 
3080
        s2.store.save_changes()
 
3081
        self.assertEquals('baz', s2.get('foo'))
 
3082
        # But the user get a warning
 
3083
        self.assertLength(1, self.warnings)
 
3084
        warning = self.warnings[0]
 
3085
        self.assertStartsWith(warning, 'Option foo in section None')
 
3086
        self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
 
3087
                            ' The baz value will be saved.')
 
3088
 
 
3089
    def test_concurrent_deletion(self):
 
3090
        self.st1._load_from_string('foo=bar')
 
3091
        self.st1.save()
 
3092
        s1 = self.get_stack(self.st1)
 
3093
        s2 = self.get_stack(self.st2)
 
3094
        s1.remove('foo')
 
3095
        s2.remove('foo')
 
3096
        s1.store.save_changes()
 
3097
        # No warning yet
 
3098
        self.assertLength(0, self.warnings)
 
3099
        s2.store.save_changes()
 
3100
        # Now we get one
 
3101
        self.assertLength(1, self.warnings)
 
3102
        warning = self.warnings[0]
 
3103
        self.assertStartsWith(warning, 'Option foo in section None')
 
3104
        self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
 
3105
                            ' The <DELETED> value will be saved.')
 
3106
 
 
3107
 
 
3108
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
 
3109
 
 
3110
    def get_store(self):
 
3111
        return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
 
3112
 
 
3113
    def test_get_quoted_string(self):
 
3114
        store = self.get_store()
 
3115
        store._load_from_string('foo= " abc "')
 
3116
        stack = config.Stack([store.get_sections])
 
3117
        self.assertEquals(' abc ', stack.get('foo'))
 
3118
 
 
3119
    def test_set_quoted_string(self):
 
3120
        store = self.get_store()
 
3121
        stack = config.Stack([store.get_sections], store)
 
3122
        stack.set('foo', ' a b c ')
 
3123
        store.save()
 
3124
        self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
 
3125
 
 
3126
 
 
3127
class TestTransportIniFileStore(TestStore):
 
3128
 
 
3129
    def test_loading_unknown_file_fails(self):
 
3130
        store = config.TransportIniFileStore(self.get_transport(),
 
3131
            'I-do-not-exist')
 
3132
        self.assertRaises(errors.NoSuchFile, store.load)
 
3133
 
 
3134
    def test_invalid_content(self):
 
3135
        store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
 
3136
        self.assertEquals(False, store.is_loaded())
 
3137
        exc = self.assertRaises(
 
3138
            errors.ParseConfigError, store._load_from_string,
 
3139
            'this is invalid !')
 
3140
        self.assertEndsWith(exc.filename, 'foo.conf')
 
3141
        # And the load failed
 
3142
        self.assertEquals(False, store.is_loaded())
 
3143
 
 
3144
    def test_get_embedded_sections(self):
 
3145
        # A more complicated example (which also shows that section names and
 
3146
        # option names share the same name space...)
 
3147
        # FIXME: This should be fixed by forbidding dicts as values ?
 
3148
        # -- vila 2011-04-05
 
3149
        store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
 
3150
        store._load_from_string('''
 
3151
foo=bar
 
3152
l=1,2
 
3153
[DEFAULT]
 
3154
foo_in_DEFAULT=foo_DEFAULT
 
3155
[bar]
 
3156
foo_in_bar=barbar
 
3157
[baz]
 
3158
foo_in_baz=barbaz
 
3159
[[qux]]
 
3160
foo_in_qux=quux
 
3161
''')
 
3162
        sections = list(store.get_sections())
 
3163
        self.assertLength(4, sections)
 
3164
        # The default section has no name.
 
3165
        # List values are provided as strings and need to be explicitly
 
3166
        # converted by specifying from_unicode=list_from_store at option
 
3167
        # registration
 
3168
        self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
 
3169
                                  sections[0])
 
3170
        self.assertSectionContent(
 
3171
            ('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
 
3172
        self.assertSectionContent(
 
3173
            ('bar', {'foo_in_bar': 'barbar'}), sections[2])
 
3174
        # sub sections are provided as embedded dicts.
 
3175
        self.assertSectionContent(
 
3176
            ('baz', {'foo_in_baz': 'barbaz', 'qux': {'foo_in_qux': 'quux'}}),
 
3177
            sections[3])
 
3178
 
 
3179
 
 
3180
class TestLockableIniFileStore(TestStore):
 
3181
 
 
3182
    def test_create_store_in_created_dir(self):
 
3183
        self.assertPathDoesNotExist('dir')
 
3184
        t = self.get_transport('dir/subdir')
 
3185
        store = config.LockableIniFileStore(t, 'foo.conf')
 
3186
        store.get_mutable_section(None).set('foo', 'bar')
 
3187
        store.save()
 
3188
        self.assertPathExists('dir/subdir')
 
3189
 
 
3190
 
 
3191
class TestConcurrentStoreUpdates(TestStore):
 
3192
    """Test that Stores properly handle conccurent updates.
 
3193
 
 
3194
    New Store implementation may fail some of these tests but until such
 
3195
    implementations exist it's hard to properly filter them from the scenarios
 
3196
    applied here. If you encounter such a case, contact the bzr devs.
 
3197
    """
 
3198
 
 
3199
    scenarios = [(key, {'get_stack': builder}) for key, builder
 
3200
                 in config.test_stack_builder_registry.iteritems()]
 
3201
 
 
3202
    def setUp(self):
 
3203
        super(TestConcurrentStoreUpdates, self).setUp()
 
3204
        self.stack = self.get_stack(self)
 
3205
        if not isinstance(self.stack, config._CompatibleStack):
 
3206
            raise tests.TestNotApplicable(
 
3207
                '%s is not meant to be compatible with the old config design'
 
3208
                % (self.stack,))
 
3209
        self.stack.set('one', '1')
 
3210
        self.stack.set('two', '2')
 
3211
        # Flush the store
 
3212
        self.stack.store.save()
 
3213
 
 
3214
    def test_simple_read_access(self):
 
3215
        self.assertEquals('1', self.stack.get('one'))
 
3216
 
 
3217
    def test_simple_write_access(self):
 
3218
        self.stack.set('one', 'one')
 
3219
        self.assertEquals('one', self.stack.get('one'))
 
3220
 
 
3221
    def test_listen_to_the_last_speaker(self):
 
3222
        c1 = self.stack
 
3223
        c2 = self.get_stack(self)
 
3224
        c1.set('one', 'ONE')
 
3225
        c2.set('two', 'TWO')
 
3226
        self.assertEquals('ONE', c1.get('one'))
 
3227
        self.assertEquals('TWO', c2.get('two'))
 
3228
        # The second update respect the first one
 
3229
        self.assertEquals('ONE', c2.get('one'))
 
3230
 
 
3231
    def test_last_speaker_wins(self):
 
3232
        # If the same config is not shared, the same variable modified twice
 
3233
        # can only see a single result.
 
3234
        c1 = self.stack
 
3235
        c2 = self.get_stack(self)
 
3236
        c1.set('one', 'c1')
 
3237
        c2.set('one', 'c2')
 
3238
        self.assertEquals('c2', c2.get('one'))
 
3239
        # The first modification is still available until another refresh
 
3240
        # occur
 
3241
        self.assertEquals('c1', c1.get('one'))
 
3242
        c1.set('two', 'done')
 
3243
        self.assertEquals('c2', c1.get('one'))
 
3244
 
 
3245
    def test_writes_are_serialized(self):
 
3246
        c1 = self.stack
 
3247
        c2 = self.get_stack(self)
 
3248
 
 
3249
        # We spawn a thread that will pause *during* the config saving.
 
3250
        before_writing = threading.Event()
 
3251
        after_writing = threading.Event()
 
3252
        writing_done = threading.Event()
 
3253
        c1_save_without_locking_orig = c1.store.save_without_locking
 
3254
        def c1_save_without_locking():
 
3255
            before_writing.set()
 
3256
            c1_save_without_locking_orig()
 
3257
            # The lock is held. We wait for the main thread to decide when to
 
3258
            # continue
 
3259
            after_writing.wait()
 
3260
        c1.store.save_without_locking = c1_save_without_locking
 
3261
        def c1_set():
 
3262
            c1.set('one', 'c1')
 
3263
            writing_done.set()
 
3264
        t1 = threading.Thread(target=c1_set)
 
3265
        # Collect the thread after the test
 
3266
        self.addCleanup(t1.join)
 
3267
        # Be ready to unblock the thread if the test goes wrong
 
3268
        self.addCleanup(after_writing.set)
 
3269
        t1.start()
 
3270
        before_writing.wait()
 
3271
        self.assertRaises(errors.LockContention,
 
3272
                          c2.set, 'one', 'c2')
 
3273
        self.assertEquals('c1', c1.get('one'))
 
3274
        # Let the lock be released
 
3275
        after_writing.set()
 
3276
        writing_done.wait()
 
3277
        c2.set('one', 'c2')
 
3278
        self.assertEquals('c2', c2.get('one'))
 
3279
 
 
3280
    def test_read_while_writing(self):
 
3281
       c1 = self.stack
 
3282
       # We spawn a thread that will pause *during* the write
 
3283
       ready_to_write = threading.Event()
 
3284
       do_writing = threading.Event()
 
3285
       writing_done = threading.Event()
 
3286
       # We override the _save implementation so we know the store is locked
 
3287
       c1_save_without_locking_orig = c1.store.save_without_locking
 
3288
       def c1_save_without_locking():
 
3289
           ready_to_write.set()
 
3290
           # The lock is held. We wait for the main thread to decide when to
 
3291
           # continue
 
3292
           do_writing.wait()
 
3293
           c1_save_without_locking_orig()
 
3294
           writing_done.set()
 
3295
       c1.store.save_without_locking = c1_save_without_locking
 
3296
       def c1_set():
 
3297
           c1.set('one', 'c1')
 
3298
       t1 = threading.Thread(target=c1_set)
 
3299
       # Collect the thread after the test
 
3300
       self.addCleanup(t1.join)
 
3301
       # Be ready to unblock the thread if the test goes wrong
 
3302
       self.addCleanup(do_writing.set)
 
3303
       t1.start()
 
3304
       # Ensure the thread is ready to write
 
3305
       ready_to_write.wait()
 
3306
       self.assertEquals('c1', c1.get('one'))
 
3307
       # If we read during the write, we get the old value
 
3308
       c2 = self.get_stack(self)
 
3309
       self.assertEquals('1', c2.get('one'))
 
3310
       # Let the writing occur and ensure it occurred
 
3311
       do_writing.set()
 
3312
       writing_done.wait()
 
3313
       # Now we get the updated value
 
3314
       c3 = self.get_stack(self)
 
3315
       self.assertEquals('c1', c3.get('one'))
 
3316
 
 
3317
    # FIXME: It may be worth looking into removing the lock dir when it's not
 
3318
    # needed anymore and look at possible fallouts for concurrent lockers. This
 
3319
    # will matter if/when we use config files outside of bazaar directories
 
3320
    # (.bazaar or .bzr) -- vila 20110-04-111
 
3321
 
 
3322
 
 
3323
class TestSectionMatcher(TestStore):
 
3324
 
 
3325
    scenarios = [('location', {'matcher': config.LocationMatcher}),
 
3326
                 ('id', {'matcher': config.NameMatcher}),]
 
3327
 
 
3328
    def setUp(self):
 
3329
        super(TestSectionMatcher, self).setUp()
 
3330
        # Any simple store is good enough
 
3331
        self.get_store = config.test_store_builder_registry.get('configobj')
 
3332
 
 
3333
    def test_no_matches_for_empty_stores(self):
 
3334
        store = self.get_store(self)
 
3335
        store._load_from_string('')
 
3336
        matcher = self.matcher(store, '/bar')
 
3337
        self.assertEquals([], list(matcher.get_sections()))
 
3338
 
 
3339
    def test_build_doesnt_load_store(self):
 
3340
        store = self.get_store(self)
 
3341
        matcher = self.matcher(store, '/bar')
 
3342
        self.assertFalse(store.is_loaded())
 
3343
 
 
3344
 
 
3345
class TestLocationSection(tests.TestCase):
 
3346
 
 
3347
    def get_section(self, options, extra_path):
 
3348
        section = config.Section('foo', options)
 
3349
        return config.LocationSection(section, extra_path)
 
3350
 
 
3351
    def test_simple_option(self):
 
3352
        section = self.get_section({'foo': 'bar'}, '')
 
3353
        self.assertEquals('bar', section.get('foo'))
 
3354
 
 
3355
    def test_option_with_extra_path(self):
 
3356
        section = self.get_section({'foo': 'bar', 'foo:policy': 'appendpath'},
 
3357
                                   'baz')
 
3358
        self.assertEquals('bar/baz', section.get('foo'))
 
3359
 
 
3360
    def test_invalid_policy(self):
 
3361
        section = self.get_section({'foo': 'bar', 'foo:policy': 'die'},
 
3362
                                   'baz')
 
3363
        # invalid policies are ignored
 
3364
        self.assertEquals('bar', section.get('foo'))
 
3365
 
 
3366
 
 
3367
class TestLocationMatcher(TestStore):
 
3368
 
 
3369
    def setUp(self):
 
3370
        super(TestLocationMatcher, self).setUp()
 
3371
        # Any simple store is good enough
 
3372
        self.get_store = config.test_store_builder_registry.get('configobj')
 
3373
 
 
3374
    def test_unrelated_section_excluded(self):
 
3375
        store = self.get_store(self)
 
3376
        store._load_from_string('''
 
3377
[/foo]
 
3378
section=/foo
 
3379
[/foo/baz]
 
3380
section=/foo/baz
 
3381
[/foo/bar]
 
3382
section=/foo/bar
 
3383
[/foo/bar/baz]
 
3384
section=/foo/bar/baz
 
3385
[/quux/quux]
 
3386
section=/quux/quux
 
3387
''')
 
3388
        self.assertEquals(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
 
3389
                           '/quux/quux'],
 
3390
                          [section.id for _, section in store.get_sections()])
 
3391
        matcher = config.LocationMatcher(store, '/foo/bar/quux')
 
3392
        sections = [section for _, section in matcher.get_sections()]
 
3393
        self.assertEquals(['/foo/bar', '/foo'],
 
3394
                          [section.id for section in sections])
 
3395
        self.assertEquals(['quux', 'bar/quux'],
 
3396
                          [section.extra_path for section in sections])
 
3397
 
 
3398
    def test_more_specific_sections_first(self):
 
3399
        store = self.get_store(self)
 
3400
        store._load_from_string('''
 
3401
[/foo]
 
3402
section=/foo
 
3403
[/foo/bar]
 
3404
section=/foo/bar
 
3405
''')
 
3406
        self.assertEquals(['/foo', '/foo/bar'],
 
3407
                          [section.id for _, section in store.get_sections()])
 
3408
        matcher = config.LocationMatcher(store, '/foo/bar/baz')
 
3409
        sections = [section for _, section in matcher.get_sections()]
 
3410
        self.assertEquals(['/foo/bar', '/foo'],
 
3411
                          [section.id for section in sections])
 
3412
        self.assertEquals(['baz', 'bar/baz'],
 
3413
                          [section.extra_path for section in sections])
 
3414
 
 
3415
    def test_appendpath_in_no_name_section(self):
 
3416
        # It's a bit weird to allow appendpath in a no-name section, but
 
3417
        # someone may found a use for it
 
3418
        store = self.get_store(self)
 
3419
        store._load_from_string('''
 
3420
foo=bar
 
3421
foo:policy = appendpath
 
3422
''')
 
3423
        matcher = config.LocationMatcher(store, 'dir/subdir')
 
3424
        sections = list(matcher.get_sections())
 
3425
        self.assertLength(1, sections)
 
3426
        self.assertEquals('bar/dir/subdir', sections[0][1].get('foo'))
 
3427
 
 
3428
    def test_file_urls_are_normalized(self):
 
3429
        store = self.get_store(self)
 
3430
        if sys.platform == 'win32':
 
3431
            expected_url = 'file:///C:/dir/subdir'
 
3432
            expected_location = 'C:/dir/subdir'
 
3433
        else:
 
3434
            expected_url = 'file:///dir/subdir'
 
3435
            expected_location = '/dir/subdir'
 
3436
        matcher = config.LocationMatcher(store, expected_url)
 
3437
        self.assertEquals(expected_location, matcher.location)
 
3438
 
 
3439
 
 
3440
class TestStartingPathMatcher(TestStore):
 
3441
 
 
3442
    def setUp(self):
 
3443
        super(TestStartingPathMatcher, self).setUp()
 
3444
        # Any simple store is good enough
 
3445
        self.store = config.IniFileStore()
 
3446
 
 
3447
    def assertSectionIDs(self, expected, location, content):
 
3448
        self.store._load_from_string(content)
 
3449
        matcher = config.StartingPathMatcher(self.store, location)
 
3450
        sections = list(matcher.get_sections())
 
3451
        self.assertLength(len(expected), sections)
 
3452
        self.assertEqual(expected, [section.id for _, section in sections])
 
3453
        return sections
 
3454
 
 
3455
    def test_empty(self):
 
3456
        self.assertSectionIDs([], self.get_url(), '')
 
3457
 
 
3458
    def test_url_vs_local_paths(self):
 
3459
        # The matcher location is an url and the section names are local paths
 
3460
        sections = self.assertSectionIDs(['/foo/bar', '/foo'],
 
3461
                                         'file:///foo/bar/baz', '''\
 
3462
[/foo]
 
3463
[/foo/bar]
 
3464
''')
 
3465
 
 
3466
    def test_local_path_vs_url(self):
 
3467
        # The matcher location is a local path and the section names are urls
 
3468
        sections = self.assertSectionIDs(['file:///foo/bar', 'file:///foo'],
 
3469
                                         '/foo/bar/baz', '''\
 
3470
[file:///foo]
 
3471
[file:///foo/bar]
 
3472
''')
 
3473
 
 
3474
 
 
3475
    def test_no_name_section_included_when_present(self):
 
3476
        # Note that other tests will cover the case where the no-name section
 
3477
        # is empty and as such, not included.
 
3478
        sections = self.assertSectionIDs(['/foo/bar', '/foo', None],
 
3479
                                         '/foo/bar/baz', '''\
 
3480
option = defined so the no-name section exists
 
3481
[/foo]
 
3482
[/foo/bar]
 
3483
''')
 
3484
        self.assertEquals(['baz', 'bar/baz', '/foo/bar/baz'],
 
3485
                          [s.locals['relpath'] for _, s in sections])
 
3486
 
 
3487
    def test_order_reversed(self):
 
3488
        self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
 
3489
[/foo]
 
3490
[/foo/bar]
 
3491
''')
 
3492
 
 
3493
    def test_unrelated_section_excluded(self):
 
3494
        self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
 
3495
[/foo]
 
3496
[/foo/qux]
 
3497
[/foo/bar]
 
3498
''')
 
3499
 
 
3500
    def test_glob_included(self):
 
3501
        sections = self.assertSectionIDs(['/foo/*/baz', '/foo/b*', '/foo'],
 
3502
                                         '/foo/bar/baz', '''\
 
3503
[/foo]
 
3504
[/foo/qux]
 
3505
[/foo/b*]
 
3506
[/foo/*/baz]
 
3507
''')
 
3508
        # Note that 'baz' as a relpath for /foo/b* is not fully correct, but
 
3509
        # nothing really is... as far using {relpath} to append it to something
 
3510
        # else, this seems good enough though.
 
3511
        self.assertEquals(['', 'baz', 'bar/baz'],
 
3512
                          [s.locals['relpath'] for _, s in sections])
 
3513
 
 
3514
    def test_respect_order(self):
 
3515
        self.assertSectionIDs(['/foo', '/foo/b*', '/foo/*/baz'],
 
3516
                              '/foo/bar/baz', '''\
 
3517
[/foo/*/baz]
 
3518
[/foo/qux]
 
3519
[/foo/b*]
 
3520
[/foo]
 
3521
''')
 
3522
 
 
3523
 
 
3524
class TestNameMatcher(TestStore):
 
3525
 
 
3526
    def setUp(self):
 
3527
        super(TestNameMatcher, self).setUp()
 
3528
        self.matcher = config.NameMatcher
 
3529
        # Any simple store is good enough
 
3530
        self.get_store = config.test_store_builder_registry.get('configobj')
 
3531
 
 
3532
    def get_matching_sections(self, name):
 
3533
        store = self.get_store(self)
 
3534
        store._load_from_string('''
 
3535
[foo]
 
3536
option=foo
 
3537
[foo/baz]
 
3538
option=foo/baz
 
3539
[bar]
 
3540
option=bar
 
3541
''')
 
3542
        matcher = self.matcher(store, name)
 
3543
        return list(matcher.get_sections())
 
3544
 
 
3545
    def test_matching(self):
 
3546
        sections = self.get_matching_sections('foo')
 
3547
        self.assertLength(1, sections)
 
3548
        self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
 
3549
 
 
3550
    def test_not_matching(self):
 
3551
        sections = self.get_matching_sections('baz')
 
3552
        self.assertLength(0, sections)
 
3553
 
 
3554
 
 
3555
class TestBaseStackGet(tests.TestCase):
 
3556
 
 
3557
    def setUp(self):
 
3558
        super(TestBaseStackGet, self).setUp()
 
3559
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
3560
 
 
3561
    def test_get_first_definition(self):
 
3562
        store1 = config.IniFileStore()
 
3563
        store1._load_from_string('foo=bar')
 
3564
        store2 = config.IniFileStore()
 
3565
        store2._load_from_string('foo=baz')
 
3566
        conf = config.Stack([store1.get_sections, store2.get_sections])
 
3567
        self.assertEquals('bar', conf.get('foo'))
 
3568
 
 
3569
    def test_get_with_registered_default_value(self):
 
3570
        config.option_registry.register(config.Option('foo', default='bar'))
 
3571
        conf_stack = config.Stack([])
 
3572
        self.assertEquals('bar', conf_stack.get('foo'))
 
3573
 
 
3574
    def test_get_without_registered_default_value(self):
 
3575
        config.option_registry.register(config.Option('foo'))
 
3576
        conf_stack = config.Stack([])
 
3577
        self.assertEquals(None, conf_stack.get('foo'))
 
3578
 
 
3579
    def test_get_without_default_value_for_not_registered(self):
 
3580
        conf_stack = config.Stack([])
 
3581
        self.assertEquals(None, conf_stack.get('foo'))
 
3582
 
 
3583
    def test_get_for_empty_section_callable(self):
 
3584
        conf_stack = config.Stack([lambda : []])
 
3585
        self.assertEquals(None, conf_stack.get('foo'))
 
3586
 
 
3587
    def test_get_for_broken_callable(self):
 
3588
        # Trying to use and invalid callable raises an exception on first use
 
3589
        conf_stack = config.Stack([object])
 
3590
        self.assertRaises(TypeError, conf_stack.get, 'foo')
 
3591
 
 
3592
 
 
3593
class TestStackWithSimpleStore(tests.TestCase):
 
3594
 
 
3595
    def setUp(self):
 
3596
        super(TestStackWithSimpleStore, self).setUp()
 
3597
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
3598
        self.registry = config.option_registry
 
3599
 
 
3600
    def get_conf(self, content=None):
 
3601
        return config.MemoryStack(content)
 
3602
 
 
3603
    def test_override_value_from_env(self):
 
3604
        self.registry.register(
 
3605
            config.Option('foo', default='bar', override_from_env=['FOO']))
 
3606
        self.overrideEnv('FOO', 'quux')
 
3607
        # Env variable provides a default taking over the option one
 
3608
        conf = self.get_conf('foo=store')
 
3609
        self.assertEquals('quux', conf.get('foo'))
 
3610
 
 
3611
    def test_first_override_value_from_env_wins(self):
 
3612
        self.registry.register(
 
3613
            config.Option('foo', default='bar',
 
3614
                          override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
 
3615
        self.overrideEnv('FOO', 'foo')
 
3616
        self.overrideEnv('BAZ', 'baz')
 
3617
        # The first env var set wins
 
3618
        conf = self.get_conf('foo=store')
 
3619
        self.assertEquals('foo', conf.get('foo'))
 
3620
 
 
3621
 
 
3622
class TestMemoryStack(tests.TestCase):
 
3623
 
 
3624
    def test_get(self):
 
3625
        conf = config.MemoryStack('foo=bar')
 
3626
        self.assertEquals('bar', conf.get('foo'))
 
3627
 
 
3628
    def test_set(self):
 
3629
        conf = config.MemoryStack('foo=bar')
 
3630
        conf.set('foo', 'baz')
 
3631
        self.assertEquals('baz', conf.get('foo'))
 
3632
 
 
3633
    def test_no_content(self):
 
3634
        conf = config.MemoryStack()
 
3635
        # No content means no loading
 
3636
        self.assertFalse(conf.store.is_loaded())
 
3637
        self.assertRaises(NotImplementedError, conf.get, 'foo')
 
3638
        # But a content can still be provided
 
3639
        conf.store._load_from_string('foo=bar')
 
3640
        self.assertEquals('bar', conf.get('foo'))
 
3641
 
 
3642
 
 
3643
class TestStackWithTransport(tests.TestCaseWithTransport):
 
3644
 
 
3645
    scenarios = [(key, {'get_stack': builder}) for key, builder
 
3646
                 in config.test_stack_builder_registry.iteritems()]
 
3647
 
 
3648
 
 
3649
class TestConcreteStacks(TestStackWithTransport):
 
3650
 
 
3651
    def test_build_stack(self):
 
3652
        # Just a smoke test to help debug builders
 
3653
        stack = self.get_stack(self)
 
3654
 
 
3655
 
 
3656
class TestStackGet(TestStackWithTransport):
 
3657
 
 
3658
    def setUp(self):
 
3659
        super(TestStackGet, self).setUp()
 
3660
        self.conf = self.get_stack(self)
 
3661
 
 
3662
    def test_get_for_empty_stack(self):
 
3663
        self.assertEquals(None, self.conf.get('foo'))
 
3664
 
 
3665
    def test_get_hook(self):
 
3666
        self.conf.set('foo', 'bar')
 
3667
        calls = []
 
3668
        def hook(*args):
 
3669
            calls.append(args)
 
3670
        config.ConfigHooks.install_named_hook('get', hook, None)
 
3671
        self.assertLength(0, calls)
 
3672
        value = self.conf.get('foo')
 
3673
        self.assertEquals('bar', value)
 
3674
        self.assertLength(1, calls)
 
3675
        self.assertEquals((self.conf, 'foo', 'bar'), calls[0])
 
3676
 
 
3677
 
 
3678
class TestStackGetWithConverter(tests.TestCase):
 
3679
 
 
3680
    def setUp(self):
 
3681
        super(TestStackGetWithConverter, self).setUp()
 
3682
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
3683
        self.registry = config.option_registry
 
3684
 
 
3685
    def get_conf(self, content=None):
 
3686
        return config.MemoryStack(content)
 
3687
 
 
3688
    def register_bool_option(self, name, default=None, default_from_env=None):
 
3689
        b = config.Option(name, help='A boolean.',
 
3690
                          default=default, default_from_env=default_from_env,
 
3691
                          from_unicode=config.bool_from_store)
 
3692
        self.registry.register(b)
 
3693
 
 
3694
    def test_get_default_bool_None(self):
 
3695
        self.register_bool_option('foo')
 
3696
        conf = self.get_conf('')
 
3697
        self.assertEquals(None, conf.get('foo'))
 
3698
 
 
3699
    def test_get_default_bool_True(self):
 
3700
        self.register_bool_option('foo', u'True')
 
3701
        conf = self.get_conf('')
 
3702
        self.assertEquals(True, conf.get('foo'))
 
3703
 
 
3704
    def test_get_default_bool_False(self):
 
3705
        self.register_bool_option('foo', False)
 
3706
        conf = self.get_conf('')
 
3707
        self.assertEquals(False, conf.get('foo'))
 
3708
 
 
3709
    def test_get_default_bool_False_as_string(self):
 
3710
        self.register_bool_option('foo', u'False')
 
3711
        conf = self.get_conf('')
 
3712
        self.assertEquals(False, conf.get('foo'))
 
3713
 
 
3714
    def test_get_default_bool_from_env_converted(self):
 
3715
        self.register_bool_option('foo', u'True', default_from_env=['FOO'])
 
3716
        self.overrideEnv('FOO', 'False')
 
3717
        conf = self.get_conf('')
 
3718
        self.assertEquals(False, conf.get('foo'))
 
3719
 
 
3720
    def test_get_default_bool_when_conversion_fails(self):
 
3721
        self.register_bool_option('foo', default='True')
 
3722
        conf = self.get_conf('foo=invalid boolean')
 
3723
        self.assertEquals(True, conf.get('foo'))
 
3724
 
 
3725
    def register_integer_option(self, name,
 
3726
                                default=None, default_from_env=None):
 
3727
        i = config.Option(name, help='An integer.',
 
3728
                          default=default, default_from_env=default_from_env,
 
3729
                          from_unicode=config.int_from_store)
 
3730
        self.registry.register(i)
 
3731
 
 
3732
    def test_get_default_integer_None(self):
 
3733
        self.register_integer_option('foo')
 
3734
        conf = self.get_conf('')
 
3735
        self.assertEquals(None, conf.get('foo'))
 
3736
 
 
3737
    def test_get_default_integer(self):
 
3738
        self.register_integer_option('foo', 42)
 
3739
        conf = self.get_conf('')
 
3740
        self.assertEquals(42, conf.get('foo'))
 
3741
 
 
3742
    def test_get_default_integer_as_string(self):
 
3743
        self.register_integer_option('foo', u'42')
 
3744
        conf = self.get_conf('')
 
3745
        self.assertEquals(42, conf.get('foo'))
 
3746
 
 
3747
    def test_get_default_integer_from_env(self):
 
3748
        self.register_integer_option('foo', default_from_env=['FOO'])
 
3749
        self.overrideEnv('FOO', '18')
 
3750
        conf = self.get_conf('')
 
3751
        self.assertEquals(18, conf.get('foo'))
 
3752
 
 
3753
    def test_get_default_integer_when_conversion_fails(self):
 
3754
        self.register_integer_option('foo', default='12')
 
3755
        conf = self.get_conf('foo=invalid integer')
 
3756
        self.assertEquals(12, conf.get('foo'))
 
3757
 
 
3758
    def register_list_option(self, name, default=None, default_from_env=None):
 
3759
        l = config.ListOption(name, help='A list.', default=default,
 
3760
                              default_from_env=default_from_env)
 
3761
        self.registry.register(l)
 
3762
 
 
3763
    def test_get_default_list_None(self):
 
3764
        self.register_list_option('foo')
 
3765
        conf = self.get_conf('')
 
3766
        self.assertEquals(None, conf.get('foo'))
 
3767
 
 
3768
    def test_get_default_list_empty(self):
 
3769
        self.register_list_option('foo', '')
 
3770
        conf = self.get_conf('')
 
3771
        self.assertEquals([], conf.get('foo'))
 
3772
 
 
3773
    def test_get_default_list_from_env(self):
 
3774
        self.register_list_option('foo', default_from_env=['FOO'])
 
3775
        self.overrideEnv('FOO', '')
 
3776
        conf = self.get_conf('')
 
3777
        self.assertEquals([], conf.get('foo'))
 
3778
 
 
3779
    def test_get_with_list_converter_no_item(self):
 
3780
        self.register_list_option('foo', None)
 
3781
        conf = self.get_conf('foo=,')
 
3782
        self.assertEquals([], conf.get('foo'))
 
3783
 
 
3784
    def test_get_with_list_converter_many_items(self):
 
3785
        self.register_list_option('foo', None)
 
3786
        conf = self.get_conf('foo=m,o,r,e')
 
3787
        self.assertEquals(['m', 'o', 'r', 'e'], conf.get('foo'))
 
3788
 
 
3789
    def test_get_with_list_converter_embedded_spaces_many_items(self):
 
3790
        self.register_list_option('foo', None)
 
3791
        conf = self.get_conf('foo=" bar", "baz "')
 
3792
        self.assertEquals([' bar', 'baz '], conf.get('foo'))
 
3793
 
 
3794
    def test_get_with_list_converter_stripped_spaces_many_items(self):
 
3795
        self.register_list_option('foo', None)
 
3796
        conf = self.get_conf('foo= bar ,  baz ')
 
3797
        self.assertEquals(['bar', 'baz'], conf.get('foo'))
 
3798
 
 
3799
 
 
3800
class TestIterOptionRefs(tests.TestCase):
 
3801
    """iter_option_refs is a bit unusual, document some cases."""
 
3802
 
 
3803
    def assertRefs(self, expected, string):
 
3804
        self.assertEquals(expected, list(config.iter_option_refs(string)))
 
3805
 
 
3806
    def test_empty(self):
 
3807
        self.assertRefs([(False, '')], '')
 
3808
 
 
3809
    def test_no_refs(self):
 
3810
        self.assertRefs([(False, 'foo bar')], 'foo bar')
 
3811
 
 
3812
    def test_single_ref(self):
 
3813
        self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
 
3814
 
 
3815
    def test_broken_ref(self):
 
3816
        self.assertRefs([(False, '{foo')], '{foo')
 
3817
 
 
3818
    def test_embedded_ref(self):
 
3819
        self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
 
3820
                        '{{foo}}')
 
3821
 
 
3822
    def test_two_refs(self):
 
3823
        self.assertRefs([(False, ''), (True, '{foo}'),
 
3824
                         (False, ''), (True, '{bar}'),
 
3825
                         (False, ''),],
 
3826
                        '{foo}{bar}')
 
3827
 
 
3828
    def test_newline_in_refs_are_not_matched(self):
 
3829
        self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
 
3830
 
 
3831
 
 
3832
class TestStackExpandOptions(tests.TestCaseWithTransport):
 
3833
 
 
3834
    def setUp(self):
 
3835
        super(TestStackExpandOptions, self).setUp()
 
3836
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
3837
        self.registry = config.option_registry
 
3838
        self.conf = build_branch_stack(self)
 
3839
 
 
3840
    def assertExpansion(self, expected, string, env=None):
 
3841
        self.assertEquals(expected, self.conf.expand_options(string, env))
 
3842
 
 
3843
    def test_no_expansion(self):
 
3844
        self.assertExpansion('foo', 'foo')
 
3845
 
 
3846
    def test_expand_default_value(self):
 
3847
        self.conf.store._load_from_string('bar=baz')
 
3848
        self.registry.register(config.Option('foo', default=u'{bar}'))
 
3849
        self.assertEquals('baz', self.conf.get('foo', expand=True))
 
3850
 
 
3851
    def test_expand_default_from_env(self):
 
3852
        self.conf.store._load_from_string('bar=baz')
 
3853
        self.registry.register(config.Option('foo', default_from_env=['FOO']))
 
3854
        self.overrideEnv('FOO', '{bar}')
 
3855
        self.assertEquals('baz', self.conf.get('foo', expand=True))
 
3856
 
 
3857
    def test_expand_default_on_failed_conversion(self):
 
3858
        self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
 
3859
        self.registry.register(
 
3860
            config.Option('foo', default=u'{bar}',
 
3861
                          from_unicode=config.int_from_store))
 
3862
        self.assertEquals(42, self.conf.get('foo', expand=True))
 
3863
 
 
3864
    def test_env_adding_options(self):
 
3865
        self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
 
3866
 
 
3867
    def test_env_overriding_options(self):
 
3868
        self.conf.store._load_from_string('foo=baz')
 
3869
        self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
 
3870
 
 
3871
    def test_simple_ref(self):
 
3872
        self.conf.store._load_from_string('foo=xxx')
 
3873
        self.assertExpansion('xxx', '{foo}')
 
3874
 
 
3875
    def test_unknown_ref(self):
 
3876
        self.assertRaises(errors.ExpandingUnknownOption,
 
3877
                          self.conf.expand_options, '{foo}')
 
3878
 
 
3879
    def test_indirect_ref(self):
 
3880
        self.conf.store._load_from_string('''
 
3881
foo=xxx
 
3882
bar={foo}
 
3883
''')
 
3884
        self.assertExpansion('xxx', '{bar}')
 
3885
 
 
3886
    def test_embedded_ref(self):
 
3887
        self.conf.store._load_from_string('''
 
3888
foo=xxx
 
3889
bar=foo
 
3890
''')
 
3891
        self.assertExpansion('xxx', '{{bar}}')
 
3892
 
 
3893
    def test_simple_loop(self):
 
3894
        self.conf.store._load_from_string('foo={foo}')
 
3895
        self.assertRaises(errors.OptionExpansionLoop,
 
3896
                          self.conf.expand_options, '{foo}')
 
3897
 
 
3898
    def test_indirect_loop(self):
 
3899
        self.conf.store._load_from_string('''
 
3900
foo={bar}
 
3901
bar={baz}
 
3902
baz={foo}''')
 
3903
        e = self.assertRaises(errors.OptionExpansionLoop,
 
3904
                              self.conf.expand_options, '{foo}')
 
3905
        self.assertEquals('foo->bar->baz', e.refs)
 
3906
        self.assertEquals('{foo}', e.string)
 
3907
 
 
3908
    def test_list(self):
 
3909
        self.conf.store._load_from_string('''
 
3910
foo=start
 
3911
bar=middle
 
3912
baz=end
 
3913
list={foo},{bar},{baz}
 
3914
''')
 
3915
        self.registry.register(
 
3916
            config.ListOption('list'))
 
3917
        self.assertEquals(['start', 'middle', 'end'],
 
3918
                           self.conf.get('list', expand=True))
 
3919
 
 
3920
    def test_cascading_list(self):
 
3921
        self.conf.store._load_from_string('''
 
3922
foo=start,{bar}
 
3923
bar=middle,{baz}
 
3924
baz=end
 
3925
list={foo}
 
3926
''')
 
3927
        self.registry.register(
 
3928
            config.ListOption('list'))
 
3929
        self.assertEquals(['start', 'middle', 'end'],
 
3930
                           self.conf.get('list', expand=True))
 
3931
 
 
3932
    def test_pathologically_hidden_list(self):
 
3933
        self.conf.store._load_from_string('''
 
3934
foo=bin
 
3935
bar=go
 
3936
start={foo
 
3937
middle=},{
 
3938
end=bar}
 
3939
hidden={start}{middle}{end}
 
3940
''')
 
3941
        # What matters is what the registration says, the conversion happens
 
3942
        # only after all expansions have been performed
 
3943
        self.registry.register(config.ListOption('hidden'))
 
3944
        self.assertEquals(['bin', 'go'],
 
3945
                          self.conf.get('hidden', expand=True))
 
3946
 
 
3947
 
 
3948
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
 
3949
 
 
3950
    def setUp(self):
 
3951
        super(TestStackCrossSectionsExpand, self).setUp()
 
3952
 
 
3953
    def get_config(self, location, string):
 
3954
        if string is None:
 
3955
            string = ''
 
3956
        # Since we don't save the config we won't strictly require to inherit
 
3957
        # from TestCaseInTempDir, but an error occurs so quickly...
 
3958
        c = config.LocationStack(location)
 
3959
        c.store._load_from_string(string)
 
3960
        return c
 
3961
 
 
3962
    def test_dont_cross_unrelated_section(self):
 
3963
        c = self.get_config('/another/branch/path','''
 
3964
[/one/branch/path]
 
3965
foo = hello
 
3966
bar = {foo}/2
 
3967
 
 
3968
[/another/branch/path]
 
3969
bar = {foo}/2
 
3970
''')
 
3971
        self.assertRaises(errors.ExpandingUnknownOption,
 
3972
                          c.get, 'bar', expand=True)
 
3973
 
 
3974
    def test_cross_related_sections(self):
 
3975
        c = self.get_config('/project/branch/path','''
 
3976
[/project]
 
3977
foo = qu
 
3978
 
 
3979
[/project/branch/path]
 
3980
bar = {foo}ux
 
3981
''')
 
3982
        self.assertEquals('quux', c.get('bar', expand=True))
 
3983
 
 
3984
 
 
3985
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
 
3986
 
 
3987
    def test_cross_global_locations(self):
 
3988
        l_store = config.LocationStore()
 
3989
        l_store._load_from_string('''
 
3990
[/branch]
 
3991
lfoo = loc-foo
 
3992
lbar = {gbar}
 
3993
''')
 
3994
        l_store.save()
 
3995
        g_store = config.GlobalStore()
 
3996
        g_store._load_from_string('''
 
3997
[DEFAULT]
 
3998
gfoo = {lfoo}
 
3999
gbar = glob-bar
 
4000
''')
 
4001
        g_store.save()
 
4002
        stack = config.LocationStack('/branch')
 
4003
        self.assertEquals('glob-bar', stack.get('lbar', expand=True))
 
4004
        self.assertEquals('loc-foo', stack.get('gfoo', expand=True))
 
4005
 
 
4006
 
 
4007
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
 
4008
 
 
4009
    def test_expand_locals_empty(self):
 
4010
        l_store = config.LocationStore()
 
4011
        l_store._load_from_string('''
 
4012
[/home/user/project]
 
4013
base = {basename}
 
4014
rel = {relpath}
 
4015
''')
 
4016
        l_store.save()
 
4017
        stack = config.LocationStack('/home/user/project/')
 
4018
        self.assertEquals('', stack.get('base', expand=True))
 
4019
        self.assertEquals('', stack.get('rel', expand=True))
 
4020
 
 
4021
    def test_expand_basename_locally(self):
 
4022
        l_store = config.LocationStore()
 
4023
        l_store._load_from_string('''
 
4024
[/home/user/project]
 
4025
bfoo = {basename}
 
4026
''')
 
4027
        l_store.save()
 
4028
        stack = config.LocationStack('/home/user/project/branch')
 
4029
        self.assertEquals('branch', stack.get('bfoo', expand=True))
 
4030
 
 
4031
    def test_expand_basename_locally_longer_path(self):
 
4032
        l_store = config.LocationStore()
 
4033
        l_store._load_from_string('''
 
4034
[/home/user]
 
4035
bfoo = {basename}
 
4036
''')
 
4037
        l_store.save()
 
4038
        stack = config.LocationStack('/home/user/project/dir/branch')
 
4039
        self.assertEquals('branch', stack.get('bfoo', expand=True))
 
4040
 
 
4041
    def test_expand_relpath_locally(self):
 
4042
        l_store = config.LocationStore()
 
4043
        l_store._load_from_string('''
 
4044
[/home/user/project]
 
4045
lfoo = loc-foo/{relpath}
 
4046
''')
 
4047
        l_store.save()
 
4048
        stack = config.LocationStack('/home/user/project/branch')
 
4049
        self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
 
4050
 
 
4051
    def test_expand_relpath_unknonw_in_global(self):
 
4052
        g_store = config.GlobalStore()
 
4053
        g_store._load_from_string('''
 
4054
[DEFAULT]
 
4055
gfoo = {relpath}
 
4056
''')
 
4057
        g_store.save()
 
4058
        stack = config.LocationStack('/home/user/project/branch')
 
4059
        self.assertRaises(errors.ExpandingUnknownOption,
 
4060
                          stack.get, 'gfoo', expand=True)
 
4061
 
 
4062
    def test_expand_local_option_locally(self):
 
4063
        l_store = config.LocationStore()
 
4064
        l_store._load_from_string('''
 
4065
[/home/user/project]
 
4066
lfoo = loc-foo/{relpath}
 
4067
lbar = {gbar}
 
4068
''')
 
4069
        l_store.save()
 
4070
        g_store = config.GlobalStore()
 
4071
        g_store._load_from_string('''
 
4072
[DEFAULT]
 
4073
gfoo = {lfoo}
 
4074
gbar = glob-bar
 
4075
''')
 
4076
        g_store.save()
 
4077
        stack = config.LocationStack('/home/user/project/branch')
 
4078
        self.assertEquals('glob-bar', stack.get('lbar', expand=True))
 
4079
        self.assertEquals('loc-foo/branch', stack.get('gfoo', expand=True))
 
4080
 
 
4081
    def test_locals_dont_leak(self):
 
4082
        """Make sure we chose the right local in presence of several sections.
 
4083
        """
 
4084
        l_store = config.LocationStore()
 
4085
        l_store._load_from_string('''
 
4086
[/home/user]
 
4087
lfoo = loc-foo/{relpath}
 
4088
[/home/user/project]
 
4089
lfoo = loc-foo/{relpath}
 
4090
''')
 
4091
        l_store.save()
 
4092
        stack = config.LocationStack('/home/user/project/branch')
 
4093
        self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
 
4094
        stack = config.LocationStack('/home/user/bar/baz')
 
4095
        self.assertEquals('loc-foo/bar/baz', stack.get('lfoo', expand=True))
 
4096
 
 
4097
 
 
4098
 
 
4099
class TestStackSet(TestStackWithTransport):
 
4100
 
 
4101
    def test_simple_set(self):
 
4102
        conf = self.get_stack(self)
 
4103
        self.assertEquals(None, conf.get('foo'))
 
4104
        conf.set('foo', 'baz')
 
4105
        # Did we get it back ?
 
4106
        self.assertEquals('baz', conf.get('foo'))
 
4107
 
 
4108
    def test_set_creates_a_new_section(self):
 
4109
        conf = self.get_stack(self)
 
4110
        conf.set('foo', 'baz')
 
4111
        self.assertEquals, 'baz', conf.get('foo')
 
4112
 
 
4113
    def test_set_hook(self):
 
4114
        calls = []
 
4115
        def hook(*args):
 
4116
            calls.append(args)
 
4117
        config.ConfigHooks.install_named_hook('set', hook, None)
 
4118
        self.assertLength(0, calls)
 
4119
        conf = self.get_stack(self)
 
4120
        conf.set('foo', 'bar')
 
4121
        self.assertLength(1, calls)
 
4122
        self.assertEquals((conf, 'foo', 'bar'), calls[0])
 
4123
 
 
4124
 
 
4125
class TestStackRemove(TestStackWithTransport):
 
4126
 
 
4127
    def test_remove_existing(self):
 
4128
        conf = self.get_stack(self)
 
4129
        conf.set('foo', 'bar')
 
4130
        self.assertEquals('bar', conf.get('foo'))
 
4131
        conf.remove('foo')
 
4132
        # Did we get it back ?
 
4133
        self.assertEquals(None, conf.get('foo'))
 
4134
 
 
4135
    def test_remove_unknown(self):
 
4136
        conf = self.get_stack(self)
 
4137
        self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
 
4138
 
 
4139
    def test_remove_hook(self):
 
4140
        calls = []
 
4141
        def hook(*args):
 
4142
            calls.append(args)
 
4143
        config.ConfigHooks.install_named_hook('remove', hook, None)
 
4144
        self.assertLength(0, calls)
 
4145
        conf = self.get_stack(self)
 
4146
        conf.set('foo', 'bar')
 
4147
        conf.remove('foo')
 
4148
        self.assertLength(1, calls)
 
4149
        self.assertEquals((conf, 'foo'), calls[0])
 
4150
 
 
4151
 
 
4152
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
 
4153
 
 
4154
    def setUp(self):
 
4155
        super(TestConfigGetOptions, self).setUp()
 
4156
        create_configs(self)
 
4157
 
 
4158
    def test_no_variable(self):
 
4159
        # Using branch should query branch, locations and bazaar
 
4160
        self.assertOptions([], self.branch_config)
 
4161
 
 
4162
    def test_option_in_bazaar(self):
 
4163
        self.bazaar_config.set_user_option('file', 'bazaar')
 
4164
        self.assertOptions([('file', 'bazaar', 'DEFAULT', 'bazaar')],
 
4165
                           self.bazaar_config)
 
4166
 
 
4167
    def test_option_in_locations(self):
 
4168
        self.locations_config.set_user_option('file', 'locations')
 
4169
        self.assertOptions(
 
4170
            [('file', 'locations', self.tree.basedir, 'locations')],
 
4171
            self.locations_config)
 
4172
 
 
4173
    def test_option_in_branch(self):
 
4174
        self.branch_config.set_user_option('file', 'branch')
 
4175
        self.assertOptions([('file', 'branch', 'DEFAULT', 'branch')],
 
4176
                           self.branch_config)
 
4177
 
 
4178
    def test_option_in_bazaar_and_branch(self):
 
4179
        self.bazaar_config.set_user_option('file', 'bazaar')
 
4180
        self.branch_config.set_user_option('file', 'branch')
 
4181
        self.assertOptions([('file', 'branch', 'DEFAULT', 'branch'),
 
4182
                            ('file', 'bazaar', 'DEFAULT', 'bazaar'),],
 
4183
                           self.branch_config)
 
4184
 
 
4185
    def test_option_in_branch_and_locations(self):
 
4186
        # Hmm, locations override branch :-/
 
4187
        self.locations_config.set_user_option('file', 'locations')
 
4188
        self.branch_config.set_user_option('file', 'branch')
 
4189
        self.assertOptions(
 
4190
            [('file', 'locations', self.tree.basedir, 'locations'),
 
4191
             ('file', 'branch', 'DEFAULT', 'branch'),],
 
4192
            self.branch_config)
 
4193
 
 
4194
    def test_option_in_bazaar_locations_and_branch(self):
 
4195
        self.bazaar_config.set_user_option('file', 'bazaar')
 
4196
        self.locations_config.set_user_option('file', 'locations')
 
4197
        self.branch_config.set_user_option('file', 'branch')
 
4198
        self.assertOptions(
 
4199
            [('file', 'locations', self.tree.basedir, 'locations'),
 
4200
             ('file', 'branch', 'DEFAULT', 'branch'),
 
4201
             ('file', 'bazaar', 'DEFAULT', 'bazaar'),],
 
4202
            self.branch_config)
 
4203
 
 
4204
 
 
4205
class TestConfigRemoveOption(tests.TestCaseWithTransport, TestOptionsMixin):
 
4206
 
 
4207
    def setUp(self):
 
4208
        super(TestConfigRemoveOption, self).setUp()
 
4209
        create_configs_with_file_option(self)
 
4210
 
 
4211
    def test_remove_in_locations(self):
 
4212
        self.locations_config.remove_user_option('file', self.tree.basedir)
 
4213
        self.assertOptions(
 
4214
            [('file', 'branch', 'DEFAULT', 'branch'),
 
4215
             ('file', 'bazaar', 'DEFAULT', 'bazaar'),],
 
4216
            self.branch_config)
 
4217
 
 
4218
    def test_remove_in_branch(self):
 
4219
        self.branch_config.remove_user_option('file')
 
4220
        self.assertOptions(
 
4221
            [('file', 'locations', self.tree.basedir, 'locations'),
 
4222
             ('file', 'bazaar', 'DEFAULT', 'bazaar'),],
 
4223
            self.branch_config)
 
4224
 
 
4225
    def test_remove_in_bazaar(self):
 
4226
        self.bazaar_config.remove_user_option('file')
 
4227
        self.assertOptions(
 
4228
            [('file', 'locations', self.tree.basedir, 'locations'),
 
4229
             ('file', 'branch', 'DEFAULT', 'branch'),],
 
4230
            self.branch_config)
 
4231
 
 
4232
 
 
4233
class TestConfigGetSections(tests.TestCaseWithTransport):
 
4234
 
 
4235
    def setUp(self):
 
4236
        super(TestConfigGetSections, self).setUp()
 
4237
        create_configs(self)
 
4238
 
 
4239
    def assertSectionNames(self, expected, conf, name=None):
 
4240
        """Check which sections are returned for a given config.
 
4241
 
 
4242
        If fallback configurations exist their sections can be included.
 
4243
 
 
4244
        :param expected: A list of section names.
 
4245
 
 
4246
        :param conf: The configuration that will be queried.
 
4247
 
 
4248
        :param name: An optional section name that will be passed to
 
4249
            get_sections().
 
4250
        """
 
4251
        sections = list(conf._get_sections(name))
 
4252
        self.assertLength(len(expected), sections)
 
4253
        self.assertEqual(expected, [name for name, _, _ in sections])
 
4254
 
 
4255
    def test_bazaar_default_section(self):
 
4256
        self.assertSectionNames(['DEFAULT'], self.bazaar_config)
 
4257
 
 
4258
    def test_locations_default_section(self):
 
4259
        # No sections are defined in an empty file
 
4260
        self.assertSectionNames([], self.locations_config)
 
4261
 
 
4262
    def test_locations_named_section(self):
 
4263
        self.locations_config.set_user_option('file', 'locations')
 
4264
        self.assertSectionNames([self.tree.basedir], self.locations_config)
 
4265
 
 
4266
    def test_locations_matching_sections(self):
 
4267
        loc_config = self.locations_config
 
4268
        loc_config.set_user_option('file', 'locations')
 
4269
        # We need to cheat a bit here to create an option in sections above and
 
4270
        # below the 'location' one.
 
4271
        parser = loc_config._get_parser()
 
4272
        # locations.cong deals with '/' ignoring native os.sep
 
4273
        location_names = self.tree.basedir.split('/')
 
4274
        parent = '/'.join(location_names[:-1])
 
4275
        child = '/'.join(location_names + ['child'])
 
4276
        parser[parent] = {}
 
4277
        parser[parent]['file'] = 'parent'
 
4278
        parser[child] = {}
 
4279
        parser[child]['file'] = 'child'
 
4280
        self.assertSectionNames([self.tree.basedir, parent], loc_config)
 
4281
 
 
4282
    def test_branch_data_default_section(self):
 
4283
        self.assertSectionNames([None],
 
4284
                                self.branch_config._get_branch_data_config())
 
4285
 
 
4286
    def test_branch_default_sections(self):
 
4287
        # No sections are defined in an empty locations file
 
4288
        self.assertSectionNames([None, 'DEFAULT'],
 
4289
                                self.branch_config)
 
4290
        # Unless we define an option
 
4291
        self.branch_config._get_location_config().set_user_option(
 
4292
            'file', 'locations')
 
4293
        self.assertSectionNames([self.tree.basedir, None, 'DEFAULT'],
 
4294
                                self.branch_config)
 
4295
 
 
4296
    def test_bazaar_named_section(self):
 
4297
        # We need to cheat as the API doesn't give direct access to sections
 
4298
        # other than DEFAULT.
 
4299
        self.bazaar_config.set_alias('bazaar', 'bzr')
 
4300
        self.assertSectionNames(['ALIASES'], self.bazaar_config, 'ALIASES')
 
4301
 
 
4302
 
1315
4303
class TestAuthenticationConfigFile(tests.TestCase):
1316
4304
    """Test the authentication.conf file matching"""
1317
4305
 
1332
4320
        self.assertEquals({}, conf._get_config())
1333
4321
        self._got_user_passwd(None, None, conf, 'http', 'foo.net')
1334
4322
 
 
4323
    def test_non_utf8_config(self):
 
4324
        conf = config.AuthenticationConfig(_file=StringIO(
 
4325
                'foo = bar\xff'))
 
4326
        self.assertRaises(errors.ConfigContentError, conf._get_config)
 
4327
 
1335
4328
    def test_missing_auth_section_header(self):
1336
4329
        conf = config.AuthenticationConfig(_file=StringIO('foo = bar'))
1337
4330
        self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
1595
4588
 
1596
4589
    def test_username_defaults_prompts(self):
1597
4590
        # HTTP prompts can't be tested here, see test_http.py
1598
 
        self._check_default_username_prompt('FTP %(host)s username: ', 'ftp')
1599
 
        self._check_default_username_prompt(
1600
 
            'FTP %(host)s:%(port)d username: ', 'ftp', port=10020)
1601
 
        self._check_default_username_prompt(
1602
 
            'SSH %(host)s:%(port)d username: ', 'ssh', port=12345)
 
4591
        self._check_default_username_prompt(u'FTP %(host)s username: ', 'ftp')
 
4592
        self._check_default_username_prompt(
 
4593
            u'FTP %(host)s:%(port)d username: ', 'ftp', port=10020)
 
4594
        self._check_default_username_prompt(
 
4595
            u'SSH %(host)s:%(port)d username: ', 'ssh', port=12345)
1603
4596
 
1604
4597
    def test_username_default_no_prompt(self):
1605
4598
        conf = config.AuthenticationConfig()
1611
4604
    def test_password_default_prompts(self):
1612
4605
        # HTTP prompts can't be tested here, see test_http.py
1613
4606
        self._check_default_password_prompt(
1614
 
            'FTP %(user)s@%(host)s password: ', 'ftp')
1615
 
        self._check_default_password_prompt(
1616
 
            'FTP %(user)s@%(host)s:%(port)d password: ', 'ftp', port=10020)
1617
 
        self._check_default_password_prompt(
1618
 
            'SSH %(user)s@%(host)s:%(port)d password: ', 'ssh', port=12345)
 
4607
            u'FTP %(user)s@%(host)s password: ', 'ftp')
 
4608
        self._check_default_password_prompt(
 
4609
            u'FTP %(user)s@%(host)s:%(port)d password: ', 'ftp', port=10020)
 
4610
        self._check_default_password_prompt(
 
4611
            u'SSH %(user)s@%(host)s:%(port)d password: ', 'ssh', port=12345)
1619
4612
        # SMTP port handling is a bit special (it's handled if embedded in the
1620
4613
        # host too)
1621
4614
        # FIXME: should we: forbid that, extend it to other schemes, leave
1622
4615
        # things as they are that's fine thank you ?
1623
 
        self._check_default_password_prompt('SMTP %(user)s@%(host)s password: ',
1624
 
                                            'smtp')
1625
 
        self._check_default_password_prompt('SMTP %(user)s@%(host)s password: ',
1626
 
                                            'smtp', host='bar.org:10025')
1627
 
        self._check_default_password_prompt(
1628
 
            'SMTP %(user)s@%(host)s:%(port)d password: ',
1629
 
            'smtp', port=10025)
 
4616
        self._check_default_password_prompt(
 
4617
            u'SMTP %(user)s@%(host)s password: ', 'smtp')
 
4618
        self._check_default_password_prompt(
 
4619
            u'SMTP %(user)s@%(host)s password: ', 'smtp', host='bar.org:10025')
 
4620
        self._check_default_password_prompt(
 
4621
            u'SMTP %(user)s@%(host)s:%(port)d password: ', 'smtp', port=10025)
1630
4622
 
1631
4623
    def test_ssh_password_emits_warning(self):
1632
4624
        conf = config.AuthenticationConfig(_file=StringIO(
1812
4804
# test_user_prompted ?
1813
4805
class TestAuthenticationRing(tests.TestCaseWithTransport):
1814
4806
    pass
 
4807
 
 
4808
 
 
4809
class TestAutoUserId(tests.TestCase):
 
4810
    """Test inferring an automatic user name."""
 
4811
 
 
4812
    def test_auto_user_id(self):
 
4813
        """Automatic inference of user name.
 
4814
 
 
4815
        This is a bit hard to test in an isolated way, because it depends on
 
4816
        system functions that go direct to /etc or perhaps somewhere else.
 
4817
        But it's reasonable to say that on Unix, with an /etc/mailname, we ought
 
4818
        to be able to choose a user name with no configuration.
 
4819
        """
 
4820
        if sys.platform == 'win32':
 
4821
            raise tests.TestSkipped(
 
4822
                "User name inference not implemented on win32")
 
4823
        realname, address = config._auto_user_id()
 
4824
        if os.path.exists('/etc/mailname'):
 
4825
            self.assertIsNot(None, realname)
 
4826
            self.assertIsNot(None, address)
 
4827
        else:
 
4828
            self.assertEquals((None, None), (realname, address))
 
4829
 
 
4830
 
 
4831
class EmailOptionTests(tests.TestCase):
 
4832
 
 
4833
    def test_default_email_uses_BZR_EMAIL(self):
 
4834
        conf = config.MemoryStack('email=jelmer@debian.org')
 
4835
        # BZR_EMAIL takes precedence over EMAIL
 
4836
        self.overrideEnv('BZR_EMAIL', 'jelmer@samba.org')
 
4837
        self.overrideEnv('EMAIL', 'jelmer@apache.org')
 
4838
        self.assertEquals('jelmer@samba.org', conf.get('email'))
 
4839
 
 
4840
    def test_default_email_uses_EMAIL(self):
 
4841
        conf = config.MemoryStack('')
 
4842
        self.overrideEnv('BZR_EMAIL', None)
 
4843
        self.overrideEnv('EMAIL', 'jelmer@apache.org')
 
4844
        self.assertEquals('jelmer@apache.org', conf.get('email'))
 
4845
 
 
4846
    def test_BZR_EMAIL_overrides(self):
 
4847
        conf = config.MemoryStack('email=jelmer@debian.org')
 
4848
        self.overrideEnv('BZR_EMAIL', 'jelmer@apache.org')
 
4849
        self.assertEquals('jelmer@apache.org', conf.get('email'))
 
4850
        self.overrideEnv('BZR_EMAIL', None)
 
4851
        self.overrideEnv('EMAIL', 'jelmer@samba.org')
 
4852
        self.assertEquals('jelmer@debian.org', conf.get('email'))
 
4853
 
 
4854
 
 
4855
class MailClientOptionTests(tests.TestCase):
 
4856
 
 
4857
    def test_default(self):
 
4858
        conf = config.MemoryStack('')
 
4859
        client = conf.get('mail_client')
 
4860
        self.assertIs(client, mail_client.DefaultMail)
 
4861
 
 
4862
    def test_evolution(self):
 
4863
        conf = config.MemoryStack('mail_client=evolution')
 
4864
        client = conf.get('mail_client')
 
4865
        self.assertIs(client, mail_client.Evolution)
 
4866
 
 
4867
    def test_kmail(self):
 
4868
        conf = config.MemoryStack('mail_client=kmail')
 
4869
        client = conf.get('mail_client')
 
4870
        self.assertIs(client, mail_client.KMail)
 
4871
 
 
4872
    def test_mutt(self):
 
4873
        conf = config.MemoryStack('mail_client=mutt')
 
4874
        client = conf.get('mail_client')
 
4875
        self.assertIs(client, mail_client.Mutt)
 
4876
 
 
4877
    def test_thunderbird(self):
 
4878
        conf = config.MemoryStack('mail_client=thunderbird')
 
4879
        client = conf.get('mail_client')
 
4880
        self.assertIs(client, mail_client.Thunderbird)
 
4881
 
 
4882
    def test_explicit_default(self):
 
4883
        conf = config.MemoryStack('mail_client=default')
 
4884
        client = conf.get('mail_client')
 
4885
        self.assertIs(client, mail_client.DefaultMail)
 
4886
 
 
4887
    def test_editor(self):
 
4888
        conf = config.MemoryStack('mail_client=editor')
 
4889
        client = conf.get('mail_client')
 
4890
        self.assertIs(client, mail_client.Editor)
 
4891
 
 
4892
    def test_mapi(self):
 
4893
        conf = config.MemoryStack('mail_client=mapi')
 
4894
        client = conf.get('mail_client')
 
4895
        self.assertIs(client, mail_client.MAPIClient)
 
4896
 
 
4897
    def test_xdg_email(self):
 
4898
        conf = config.MemoryStack('mail_client=xdg-email')
 
4899
        client = conf.get('mail_client')
 
4900
        self.assertIs(client, mail_client.XDGEmail)
 
4901
 
 
4902
    def test_unknown(self):
 
4903
        conf = config.MemoryStack('mail_client=firebird')
 
4904
        self.assertRaises(errors.ConfigOptionValueError, conf.get,
 
4905
                'mail_client')