~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_config.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-10-13 06:08:53 UTC
  • mfrom: (4737.1.1 merge-2.0-into-devel)
  • Revision ID: pqm@pqm.ubuntu.com-20091013060853-erk2aaj80fnkrv25
(andrew) Merge lp:bzr/2.0 into lp:bzr, including fixes for #322807,
        #389413, #402623 and documentation improvements.

Show diffs side-by-side

added added

removed removed

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