~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_config.py

  • Committer: Jelmer Vernooij
  • Date: 2012-02-01 19:55:27 UTC
  • mto: This revision was merged to the branch mainline in revision 6460.
  • Revision ID: jelmer@samba.org-20120201195527-p11zolw13w81035y
Consider invalid mail clients an error rather than just a warning.

Show diffs side-by-side

added added

removed removed

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