~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_config.py

  • Committer: Vincent Ladeuil
  • Date: 2012-02-14 17:22:37 UTC
  • mfrom: (6466 +trunk)
  • mto: This revision was merged to the branch mainline in revision 6468.
  • Revision ID: v.ladeuil+lp@free.fr-20120214172237-7dv7er3n4uy8d5m4
Merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005-2012 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
33
33
    errors,
34
34
    osutils,
35
35
    mail_client,
36
 
    mergetools,
37
36
    ui,
38
37
    urlutils,
 
38
    registry as _mod_registry,
 
39
    remote,
39
40
    tests,
40
41
    trace,
41
 
    transport,
42
 
    )
 
42
    )
 
43
from bzrlib.symbol_versioning import (
 
44
    deprecated_in,
 
45
    )
 
46
from bzrlib.transport import remote as transport_remote
43
47
from bzrlib.tests import (
44
48
    features,
45
49
    scenarios,
 
50
    test_server,
46
51
    )
47
52
from bzrlib.util.configobj import configobj
48
53
 
61
66
 
62
67
load_tests = scenarios.load_tests_apply_scenarios
63
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
 
64
172
 
65
173
sample_long_alias="log -r-15..-1 --line"
66
174
sample_config_text = u"""
69
177
editor=vim
70
178
change_editor=vimdiff -of @new_path @old_path
71
179
gpg_signing_command=gnome-gpg
 
180
gpg_signing_key=DD4D5088
72
181
log_format=short
 
182
validate_signatures_in_log=true
 
183
acceptable_keys=amy
73
184
user_global_option=something
74
185
bzr.mergetool.sometool=sometool {base} {this} {other} -o {result}
75
186
bzr.mergetool.funkytool=funkytool "arg with spaces" {this_temp}
 
187
bzr.mergetool.newtool='"newtool with spaces" {this_temp}'
76
188
bzr.default_mergetool=sometool
77
189
[ALIASES]
78
190
h=help
121
233
[/a/]
122
234
check_signatures=check-available
123
235
gpg_signing_command=false
 
236
gpg_signing_key=default
124
237
user_local_option=local
125
238
# test trailing / matching
126
239
[/a/*]
216
329
 
217
330
class FakeBranch(object):
218
331
 
219
 
    def __init__(self, base=None, user_id=None):
 
332
    def __init__(self, base=None):
220
333
        if base is None:
221
334
            self.base = "http://example.com/branches/demo"
222
335
        else:
223
336
            self.base = base
224
337
        self._transport = self.control_files = \
225
 
            FakeControlFilesAndTransport(user_id=user_id)
 
338
            FakeControlFilesAndTransport()
226
339
 
227
340
    def _get_config(self):
228
341
        return config.TransportConfig(self._transport, 'branch.conf')
236
349
 
237
350
class FakeControlFilesAndTransport(object):
238
351
 
239
 
    def __init__(self, user_id=None):
 
352
    def __init__(self):
240
353
        self.files = {}
241
 
        if user_id:
242
 
            self.files['email'] = user_id
243
354
        self._transport = self
244
355
 
245
 
    def get_utf8(self, filename):
246
 
        # from LockableFiles
247
 
        raise AssertionError("get_utf8 should no longer be used")
248
 
 
249
356
    def get(self, filename):
250
357
        # from Transport
251
358
        try:
313
420
        """
314
421
        co = config.ConfigObj()
315
422
        co['test'] = 'foo#bar'
316
 
        lines = co.write()
 
423
        outfile = StringIO()
 
424
        co.write(outfile=outfile)
 
425
        lines = outfile.getvalue().splitlines()
317
426
        self.assertEqual(lines, ['test = "foo#bar"'])
318
427
        co2 = config.ConfigObj(lines)
319
428
        self.assertEqual(co2['test'], 'foo#bar')
320
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
 
321
454
 
322
455
erroneous_config = """[section] # line 1
323
456
good=good # line 2
344
477
        config.Config()
345
478
 
346
479
    def test_no_default_editor(self):
347
 
        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)
348
484
 
349
485
    def test_user_email(self):
350
486
        my_config = InstrumentedConfig()
359
495
 
360
496
    def test_signatures_default(self):
361
497
        my_config = config.Config()
362
 
        self.assertFalse(my_config.signature_needed())
 
498
        self.assertFalse(
 
499
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
500
                my_config.signature_needed))
363
501
        self.assertEqual(config.CHECK_IF_POSSIBLE,
364
 
                         my_config.signature_checking())
 
502
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
503
                my_config.signature_checking))
365
504
        self.assertEqual(config.SIGN_WHEN_REQUIRED,
366
 
                         my_config.signing_policy())
 
505
                self.applyDeprecated(deprecated_in((2, 5, 0)),
 
506
                    my_config.signing_policy))
367
507
 
368
508
    def test_signatures_template_method(self):
369
509
        my_config = InstrumentedConfig()
370
 
        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))
371
513
        self.assertEqual(['_get_signature_checking'], my_config._calls)
372
514
 
373
515
    def test_signatures_template_method_none(self):
374
516
        my_config = InstrumentedConfig()
375
517
        my_config._signatures = None
376
518
        self.assertEqual(config.CHECK_IF_POSSIBLE,
377
 
                         my_config.signature_checking())
 
519
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
520
                             my_config.signature_checking))
378
521
        self.assertEqual(['_get_signature_checking'], my_config._calls)
379
522
 
380
523
    def test_gpg_signing_command_default(self):
381
524
        my_config = config.Config()
382
 
        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))
383
528
 
384
529
    def test_get_user_option_default(self):
385
530
        my_config = config.Config()
387
532
 
388
533
    def test_post_commit_default(self):
389
534
        my_config = config.Config()
390
 
        self.assertEqual(None, my_config.post_commit())
 
535
        self.assertEqual(None, self.applyDeprecated(deprecated_in((2, 5, 0)),
 
536
                                                    my_config.post_commit))
 
537
 
391
538
 
392
539
    def test_log_format_default(self):
393
540
        my_config = config.Config()
394
 
        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())
395
553
 
396
554
    def test_get_change_editor(self):
397
555
        my_config = InstrumentedConfig()
419
577
    def test_config_dir(self):
420
578
        self.assertEqual(config.config_dir(), self.bzr_home)
421
579
 
 
580
    def test_config_dir_is_unicode(self):
 
581
        self.assertIsInstance(config.config_dir(), unicode)
 
582
 
422
583
    def test_config_filename(self):
423
584
        self.assertEqual(config.config_filename(),
424
585
                         self.bzr_home + '/bazaar.conf')
483
644
    def test_cached(self):
484
645
        my_config = config.IniBasedConfig.from_string(sample_config_text)
485
646
        parser = my_config._get_parser()
486
 
        self.failUnless(my_config._get_parser() is parser)
 
647
        self.assertTrue(my_config._get_parser() is parser)
487
648
 
488
649
    def _dummy_chown(self, path, uid, gid):
489
650
        self.path, self.uid, self.gid = path, uid, gid
528
689
        self.assertFileEqual(content, 'test.conf')
529
690
 
530
691
 
531
 
class TestIniConfigInterpolation(tests.TestCase):
532
 
    """Test interpolation from the IniConfig level.
 
692
class TestIniConfigOptionExpansionDefaultValue(tests.TestCaseInTempDir):
 
693
    """What is the default value of expand for config options.
 
694
 
 
695
    This is an opt-in beta feature used to evaluate whether or not option
 
696
    references can appear in dangerous place raising exceptions, disapearing
 
697
    (and as such corrupting data) or if it's safe to activate the option by
 
698
    default.
 
699
 
 
700
    Note that these tests relies on config._expand_default_value being already
 
701
    overwritten in the parent class setUp.
 
702
    """
 
703
 
 
704
    def setUp(self):
 
705
        super(TestIniConfigOptionExpansionDefaultValue, self).setUp()
 
706
        self.config = None
 
707
        self.warnings = []
 
708
        def warning(*args):
 
709
            self.warnings.append(args[0] % args[1:])
 
710
        self.overrideAttr(trace, 'warning', warning)
 
711
 
 
712
    def get_config(self, expand):
 
713
        c = config.GlobalConfig.from_string('bzr.config.expand=%s' % (expand,),
 
714
                                            save=True)
 
715
        return c
 
716
 
 
717
    def assertExpandIs(self, expected):
 
718
        actual = config._get_expand_default_value()
 
719
        #self.config.get_user_option_as_bool('bzr.config.expand')
 
720
        self.assertEquals(expected, actual)
 
721
 
 
722
    def test_default_is_None(self):
 
723
        self.assertEquals(None, config._expand_default_value)
 
724
 
 
725
    def test_default_is_False_even_if_None(self):
 
726
        self.config = self.get_config(None)
 
727
        self.assertExpandIs(False)
 
728
 
 
729
    def test_default_is_False_even_if_invalid(self):
 
730
        self.config = self.get_config('<your choice>')
 
731
        self.assertExpandIs(False)
 
732
        # ...
 
733
        # Huh ? My choice is False ? Thanks, always happy to hear that :D
 
734
        # Wait, you've been warned !
 
735
        self.assertLength(1, self.warnings)
 
736
        self.assertEquals(
 
737
            'Value "<your choice>" is not a boolean for "bzr.config.expand"',
 
738
            self.warnings[0])
 
739
 
 
740
    def test_default_is_True(self):
 
741
        self.config = self.get_config(True)
 
742
        self.assertExpandIs(True)
 
743
 
 
744
    def test_default_is_False(self):
 
745
        self.config = self.get_config(False)
 
746
        self.assertExpandIs(False)
 
747
 
 
748
 
 
749
class TestIniConfigOptionExpansion(tests.TestCase):
 
750
    """Test option expansion from the IniConfig level.
533
751
 
534
752
    What we really want here is to test the Config level, but the class being
535
753
    abstract as far as storing values is concerned, this can't be done
536
754
    properly (yet).
537
755
    """
538
756
    # FIXME: This should be rewritten when all configs share a storage
539
 
    # implementation.
 
757
    # implementation -- vila 2011-02-18
540
758
 
541
759
    def get_config(self, string=None):
542
760
        if string is None:
544
762
        c = config.IniBasedConfig.from_string(string)
545
763
        return c
546
764
 
547
 
    def assertInterpolate(self, expected, conf, string, env=None):
548
 
        self.assertEquals(expected, conf.interpolate(string, env))
 
765
    def assertExpansion(self, expected, conf, string, env=None):
 
766
        self.assertEquals(expected, conf.expand_options(string, env))
549
767
 
550
 
    def test_no_interpolation(self):
 
768
    def test_no_expansion(self):
551
769
        c = self.get_config('')
552
 
        self.assertInterpolate('foo', c, 'foo')
 
770
        self.assertExpansion('foo', c, 'foo')
553
771
 
554
772
    def test_env_adding_options(self):
555
773
        c = self.get_config('')
556
 
        self.assertInterpolate('bar', c, '{foo}', {'foo': 'bar'})
 
774
        self.assertExpansion('bar', c, '{foo}', {'foo': 'bar'})
557
775
 
558
776
    def test_env_overriding_options(self):
559
777
        c = self.get_config('foo=baz')
560
 
        self.assertInterpolate('bar', c, '{foo}', {'foo': 'bar'})
 
778
        self.assertExpansion('bar', c, '{foo}', {'foo': 'bar'})
561
779
 
562
780
    def test_simple_ref(self):
563
781
        c = self.get_config('foo=xxx')
564
 
        self.assertInterpolate('xxx', c, '{foo}')
 
782
        self.assertExpansion('xxx', c, '{foo}')
565
783
 
566
784
    def test_unknown_ref(self):
567
785
        c = self.get_config('')
568
 
        self.assertRaises(errors.InterpolationUnknownOption,
569
 
                          c.interpolate, '{foo}')
 
786
        self.assertRaises(errors.ExpandingUnknownOption,
 
787
                          c.expand_options, '{foo}')
570
788
 
571
789
    def test_indirect_ref(self):
572
790
        c = self.get_config('''
573
791
foo=xxx
574
792
bar={foo}
575
793
''')
576
 
        self.assertInterpolate('xxx', c, '{bar}')
 
794
        self.assertExpansion('xxx', c, '{bar}')
577
795
 
578
796
    def test_embedded_ref(self):
579
797
        c = self.get_config('''
580
798
foo=xxx
581
799
bar=foo
582
800
''')
583
 
        self.assertInterpolate('xxx', c, '{{bar}}')
 
801
        self.assertExpansion('xxx', c, '{{bar}}')
584
802
 
585
803
    def test_simple_loop(self):
586
804
        c = self.get_config('foo={foo}')
587
 
        self.assertRaises(errors.InterpolationLoop, c.interpolate, '{foo}')
 
805
        self.assertRaises(errors.OptionExpansionLoop, c.expand_options, '{foo}')
588
806
 
589
807
    def test_indirect_loop(self):
590
808
        c = self.get_config('''
591
809
foo={bar}
592
810
bar={baz}
593
811
baz={foo}''')
594
 
        e = self.assertRaises(errors.InterpolationLoop,
595
 
                              c.interpolate, '{foo}')
 
812
        e = self.assertRaises(errors.OptionExpansionLoop,
 
813
                              c.expand_options, '{foo}')
596
814
        self.assertEquals('foo->bar->baz', e.refs)
597
815
        self.assertEquals('{foo}', e.string)
598
816
 
604
822
list={foo},{bar},{baz}
605
823
''')
606
824
        self.assertEquals(['start', 'middle', 'end'],
607
 
                           conf.get_user_option('list'))
 
825
                           conf.get_user_option('list', expand=True))
608
826
 
609
827
    def test_cascading_list(self):
610
828
        conf = self.get_config('''
614
832
list={foo}
615
833
''')
616
834
        self.assertEquals(['start', 'middle', 'end'],
617
 
                           conf.get_user_option('list'))
 
835
                           conf.get_user_option('list', expand=True))
618
836
 
619
837
    def test_pathological_hidden_list(self):
620
838
        conf = self.get_config('''
628
846
        # Nope, it's either a string or a list, and the list wins as soon as a
629
847
        # ',' appears, so the string concatenation never occur.
630
848
        self.assertEquals(['{foo', '}', '{', 'bar}'],
631
 
                          conf.get_user_option('hidden'))
 
849
                          conf.get_user_option('hidden', expand=True))
 
850
 
 
851
 
 
852
class TestLocationConfigOptionExpansion(tests.TestCaseInTempDir):
 
853
 
 
854
    def get_config(self, location, string=None):
 
855
        if string is None:
 
856
            string = ''
 
857
        # Since we don't save the config we won't strictly require to inherit
 
858
        # from TestCaseInTempDir, but an error occurs so quickly...
 
859
        c = config.LocationConfig.from_string(string, location)
 
860
        return c
 
861
 
 
862
    def test_dont_cross_unrelated_section(self):
 
863
        c = self.get_config('/another/branch/path','''
 
864
[/one/branch/path]
 
865
foo = hello
 
866
bar = {foo}/2
 
867
 
 
868
[/another/branch/path]
 
869
bar = {foo}/2
 
870
''')
 
871
        self.assertRaises(errors.ExpandingUnknownOption,
 
872
                          c.get_user_option, 'bar', expand=True)
 
873
 
 
874
    def test_cross_related_sections(self):
 
875
        c = self.get_config('/project/branch/path','''
 
876
[/project]
 
877
foo = qu
 
878
 
 
879
[/project/branch/path]
 
880
bar = {foo}ux
 
881
''')
 
882
        self.assertEquals('quux', c.get_user_option('bar', expand=True))
632
883
 
633
884
 
634
885
class TestIniBaseConfigOnDisk(tests.TestCaseInTempDir):
716
967
        def c1_write_config_file():
717
968
            before_writing.set()
718
969
            c1_orig()
719
 
            # The lock is held we wait for the main thread to decide when to
 
970
            # The lock is held. We wait for the main thread to decide when to
720
971
            # continue
721
972
            after_writing.wait()
722
973
        c1._write_config_file = c1_write_config_file
749
1000
       c1_orig = c1._write_config_file
750
1001
       def c1_write_config_file():
751
1002
           ready_to_write.set()
752
 
           # The lock is held we wait for the main thread to decide when to
 
1003
           # The lock is held. We wait for the main thread to decide when to
753
1004
           # continue
754
1005
           do_writing.wait()
755
1006
           c1_orig()
814
1065
        # automatically cast to list
815
1066
        self.assertEqual(['x'], get_list('one_item'))
816
1067
 
 
1068
    def test_get_user_option_as_int_from_SI(self):
 
1069
        conf, parser = self.make_config_parser("""
 
1070
plain = 100
 
1071
si_k = 5k,
 
1072
si_kb = 5kb,
 
1073
si_m = 5M,
 
1074
si_mb = 5MB,
 
1075
si_g = 5g,
 
1076
si_gb = 5gB,
 
1077
""")
 
1078
        def get_si(s, default=None):
 
1079
            return self.applyDeprecated(
 
1080
                deprecated_in((2, 5, 0)),
 
1081
                conf.get_user_option_as_int_from_SI, s, default)
 
1082
        self.assertEqual(100, get_si('plain'))
 
1083
        self.assertEqual(5000, get_si('si_k'))
 
1084
        self.assertEqual(5000, get_si('si_kb'))
 
1085
        self.assertEqual(5000000, get_si('si_m'))
 
1086
        self.assertEqual(5000000, get_si('si_mb'))
 
1087
        self.assertEqual(5000000000, get_si('si_g'))
 
1088
        self.assertEqual(5000000000, get_si('si_gb'))
 
1089
        self.assertEqual(None, get_si('non-exist'))
 
1090
        self.assertEqual(42, get_si('non-exist-with-default',  42))
 
1091
 
817
1092
 
818
1093
class TestSupressWarning(TestIniConfig):
819
1094
 
846
1121
            parser = my_config._get_parser()
847
1122
        finally:
848
1123
            config.ConfigObj = oldparserclass
849
 
        self.failUnless(isinstance(parser, InstrumentedConfigObj))
 
1124
        self.assertIsInstance(parser, InstrumentedConfigObj)
850
1125
        self.assertEqual(parser._calls, [('__init__', config.config_filename(),
851
1126
                                          'utf-8')])
852
1127
 
863
1138
        my_config = config.BranchConfig(branch)
864
1139
        location_config = my_config._get_location_config()
865
1140
        self.assertEqual(branch.base, location_config.location)
866
 
        self.failUnless(location_config is my_config._get_location_config())
 
1141
        self.assertIs(location_config, my_config._get_location_config())
867
1142
 
868
1143
    def test_get_config(self):
869
1144
        """The Branch.get_config method works properly"""
969
1244
 
970
1245
    def test_configured_editor(self):
971
1246
        my_config = config.GlobalConfig.from_string(sample_config_text)
972
 
        self.assertEqual("vim", my_config.get_editor())
 
1247
        editor = self.applyDeprecated(
 
1248
            deprecated_in((2, 4, 0)), my_config.get_editor)
 
1249
        self.assertEqual('vim', editor)
973
1250
 
974
1251
    def test_signatures_always(self):
975
1252
        my_config = config.GlobalConfig.from_string(sample_always_signatures)
976
1253
        self.assertEqual(config.CHECK_NEVER,
977
 
                         my_config.signature_checking())
 
1254
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1255
                             my_config.signature_checking))
978
1256
        self.assertEqual(config.SIGN_ALWAYS,
979
 
                         my_config.signing_policy())
980
 
        self.assertEqual(True, my_config.signature_needed())
 
1257
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1258
                             my_config.signing_policy))
 
1259
        self.assertEqual(True,
 
1260
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1261
                my_config.signature_needed))
981
1262
 
982
1263
    def test_signatures_if_possible(self):
983
1264
        my_config = config.GlobalConfig.from_string(sample_maybe_signatures)
984
1265
        self.assertEqual(config.CHECK_NEVER,
985
 
                         my_config.signature_checking())
 
1266
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1267
                             my_config.signature_checking))
986
1268
        self.assertEqual(config.SIGN_WHEN_REQUIRED,
987
 
                         my_config.signing_policy())
988
 
        self.assertEqual(False, my_config.signature_needed())
 
1269
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1270
                             my_config.signing_policy))
 
1271
        self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1272
            my_config.signature_needed))
989
1273
 
990
1274
    def test_signatures_ignore(self):
991
1275
        my_config = config.GlobalConfig.from_string(sample_ignore_signatures)
992
1276
        self.assertEqual(config.CHECK_ALWAYS,
993
 
                         my_config.signature_checking())
 
1277
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1278
                             my_config.signature_checking))
994
1279
        self.assertEqual(config.SIGN_NEVER,
995
 
                         my_config.signing_policy())
996
 
        self.assertEqual(False, my_config.signature_needed())
 
1280
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1281
                             my_config.signing_policy))
 
1282
        self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1283
            my_config.signature_needed))
997
1284
 
998
1285
    def _get_sample_config(self):
999
1286
        my_config = config.GlobalConfig.from_string(sample_config_text)
1001
1288
 
1002
1289
    def test_gpg_signing_command(self):
1003
1290
        my_config = self._get_sample_config()
1004
 
        self.assertEqual("gnome-gpg", my_config.gpg_signing_command())
1005
 
        self.assertEqual(False, my_config.signature_needed())
 
1291
        self.assertEqual("gnome-gpg",
 
1292
            self.applyDeprecated(
 
1293
                deprecated_in((2, 5, 0)), my_config.gpg_signing_command))
 
1294
        self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1295
            my_config.signature_needed))
 
1296
 
 
1297
    def test_gpg_signing_key(self):
 
1298
        my_config = self._get_sample_config()
 
1299
        self.assertEqual("DD4D5088",
 
1300
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1301
                my_config.gpg_signing_key))
1006
1302
 
1007
1303
    def _get_empty_config(self):
1008
1304
        my_config = config.GlobalConfig()
1010
1306
 
1011
1307
    def test_gpg_signing_command_unset(self):
1012
1308
        my_config = self._get_empty_config()
1013
 
        self.assertEqual("gpg", my_config.gpg_signing_command())
 
1309
        self.assertEqual("gpg",
 
1310
            self.applyDeprecated(
 
1311
                deprecated_in((2, 5, 0)), my_config.gpg_signing_command))
1014
1312
 
1015
1313
    def test_get_user_option_default(self):
1016
1314
        my_config = self._get_empty_config()
1023
1321
 
1024
1322
    def test_post_commit_default(self):
1025
1323
        my_config = self._get_sample_config()
1026
 
        self.assertEqual(None, my_config.post_commit())
 
1324
        self.assertEqual(None,
 
1325
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1326
                                              my_config.post_commit))
1027
1327
 
1028
1328
    def test_configured_logformat(self):
1029
1329
        my_config = self._get_sample_config()
1030
 
        self.assertEqual("short", my_config.log_format())
 
1330
        self.assertEqual("short",
 
1331
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1332
                                              my_config.log_format))
 
1333
 
 
1334
    def test_configured_acceptable_keys(self):
 
1335
        my_config = self._get_sample_config()
 
1336
        self.assertEqual("amy",
 
1337
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1338
                my_config.acceptable_keys))
 
1339
 
 
1340
    def test_configured_validate_signatures_in_log(self):
 
1341
        my_config = self._get_sample_config()
 
1342
        self.assertEqual(True, my_config.validate_signatures_in_log())
1031
1343
 
1032
1344
    def test_get_alias(self):
1033
1345
        my_config = self._get_sample_config()
1067
1379
        self.log(repr(tools))
1068
1380
        self.assertEqual(
1069
1381
            {u'funkytool' : u'funkytool "arg with spaces" {this_temp}',
1070
 
            u'sometool' : u'sometool {base} {this} {other} -o {result}'},
 
1382
            u'sometool' : u'sometool {base} {this} {other} -o {result}',
 
1383
            u'newtool' : u'"newtool with spaces" {this_temp}'},
1071
1384
            tools)
1072
1385
 
1073
1386
    def test_get_merge_tools_empty(self):
1137
1450
            parser = my_config._get_parser()
1138
1451
        finally:
1139
1452
            config.ConfigObj = oldparserclass
1140
 
        self.failUnless(isinstance(parser, InstrumentedConfigObj))
 
1453
        self.assertIsInstance(parser, InstrumentedConfigObj)
1141
1454
        self.assertEqual(parser._calls,
1142
1455
                         [('__init__', config.locations_config_filename(),
1143
1456
                           'utf-8')])
1145
1458
    def test_get_global_config(self):
1146
1459
        my_config = config.BranchConfig(FakeBranch('http://example.com'))
1147
1460
        global_config = my_config._get_global_config()
1148
 
        self.failUnless(isinstance(global_config, config.GlobalConfig))
1149
 
        self.failUnless(global_config is my_config._get_global_config())
 
1461
        self.assertIsInstance(global_config, config.GlobalConfig)
 
1462
        self.assertIs(global_config, my_config._get_global_config())
 
1463
 
 
1464
    def assertLocationMatching(self, expected):
 
1465
        self.assertEqual(expected,
 
1466
                         list(self.my_location_config._get_matching_sections()))
1150
1467
 
1151
1468
    def test__get_matching_sections_no_match(self):
1152
1469
        self.get_branch_config('/')
1153
 
        self.assertEqual([], self.my_location_config._get_matching_sections())
 
1470
        self.assertLocationMatching([])
1154
1471
 
1155
1472
    def test__get_matching_sections_exact(self):
1156
1473
        self.get_branch_config('http://www.example.com')
1157
 
        self.assertEqual([('http://www.example.com', '')],
1158
 
                         self.my_location_config._get_matching_sections())
 
1474
        self.assertLocationMatching([('http://www.example.com', '')])
1159
1475
 
1160
1476
    def test__get_matching_sections_suffix_does_not(self):
1161
1477
        self.get_branch_config('http://www.example.com-com')
1162
 
        self.assertEqual([], self.my_location_config._get_matching_sections())
 
1478
        self.assertLocationMatching([])
1163
1479
 
1164
1480
    def test__get_matching_sections_subdir_recursive(self):
1165
1481
        self.get_branch_config('http://www.example.com/com')
1166
 
        self.assertEqual([('http://www.example.com', 'com')],
1167
 
                         self.my_location_config._get_matching_sections())
 
1482
        self.assertLocationMatching([('http://www.example.com', 'com')])
1168
1483
 
1169
1484
    def test__get_matching_sections_ignoreparent(self):
1170
1485
        self.get_branch_config('http://www.example.com/ignoreparent')
1171
 
        self.assertEqual([('http://www.example.com/ignoreparent', '')],
1172
 
                         self.my_location_config._get_matching_sections())
 
1486
        self.assertLocationMatching([('http://www.example.com/ignoreparent',
 
1487
                                      '')])
1173
1488
 
1174
1489
    def test__get_matching_sections_ignoreparent_subdir(self):
1175
1490
        self.get_branch_config(
1176
1491
            'http://www.example.com/ignoreparent/childbranch')
1177
 
        self.assertEqual([('http://www.example.com/ignoreparent',
1178
 
                           'childbranch')],
1179
 
                         self.my_location_config._get_matching_sections())
 
1492
        self.assertLocationMatching([('http://www.example.com/ignoreparent',
 
1493
                                      'childbranch')])
1180
1494
 
1181
1495
    def test__get_matching_sections_subdir_trailing_slash(self):
1182
1496
        self.get_branch_config('/b')
1183
 
        self.assertEqual([('/b/', '')],
1184
 
                         self.my_location_config._get_matching_sections())
 
1497
        self.assertLocationMatching([('/b/', '')])
1185
1498
 
1186
1499
    def test__get_matching_sections_subdir_child(self):
1187
1500
        self.get_branch_config('/a/foo')
1188
 
        self.assertEqual([('/a/*', ''), ('/a/', 'foo')],
1189
 
                         self.my_location_config._get_matching_sections())
 
1501
        self.assertLocationMatching([('/a/*', ''), ('/a/', 'foo')])
1190
1502
 
1191
1503
    def test__get_matching_sections_subdir_child_child(self):
1192
1504
        self.get_branch_config('/a/foo/bar')
1193
 
        self.assertEqual([('/a/*', 'bar'), ('/a/', 'foo/bar')],
1194
 
                         self.my_location_config._get_matching_sections())
 
1505
        self.assertLocationMatching([('/a/*', 'bar'), ('/a/', 'foo/bar')])
1195
1506
 
1196
1507
    def test__get_matching_sections_trailing_slash_with_children(self):
1197
1508
        self.get_branch_config('/a/')
1198
 
        self.assertEqual([('/a/', '')],
1199
 
                         self.my_location_config._get_matching_sections())
 
1509
        self.assertLocationMatching([('/a/', '')])
1200
1510
 
1201
1511
    def test__get_matching_sections_explicit_over_glob(self):
1202
1512
        # XXX: 2006-09-08 jamesh
1204
1514
        # was a config section for '/a/?', it would get precedence
1205
1515
        # over '/a/c'.
1206
1516
        self.get_branch_config('/a/c')
1207
 
        self.assertEqual([('/a/c', ''), ('/a/*', ''), ('/a/', 'c')],
1208
 
                         self.my_location_config._get_matching_sections())
 
1517
        self.assertLocationMatching([('/a/c', ''), ('/a/*', ''), ('/a/', 'c')])
1209
1518
 
1210
1519
    def test__get_option_policy_normal(self):
1211
1520
        self.get_branch_config('http://www.example.com')
1268
1577
        self.get_branch_config('http://www.example.com',
1269
1578
                                 global_config=sample_ignore_signatures)
1270
1579
        self.assertEqual(config.CHECK_ALWAYS,
1271
 
                         self.my_config.signature_checking())
 
1580
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1581
                             self.my_config.signature_checking))
1272
1582
        self.assertEqual(config.SIGN_NEVER,
1273
 
                         self.my_config.signing_policy())
 
1583
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1584
                             self.my_config.signing_policy))
1274
1585
 
1275
1586
    def test_signatures_never(self):
1276
1587
        self.get_branch_config('/a/c')
1277
1588
        self.assertEqual(config.CHECK_NEVER,
1278
 
                         self.my_config.signature_checking())
 
1589
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1590
                             self.my_config.signature_checking))
1279
1591
 
1280
1592
    def test_signatures_when_available(self):
1281
1593
        self.get_branch_config('/a/', global_config=sample_ignore_signatures)
1282
1594
        self.assertEqual(config.CHECK_IF_POSSIBLE,
1283
 
                         self.my_config.signature_checking())
 
1595
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1596
                             self.my_config.signature_checking))
1284
1597
 
1285
1598
    def test_signatures_always(self):
1286
1599
        self.get_branch_config('/b')
1287
1600
        self.assertEqual(config.CHECK_ALWAYS,
1288
 
                         self.my_config.signature_checking())
 
1601
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1602
                         self.my_config.signature_checking))
1289
1603
 
1290
1604
    def test_gpg_signing_command(self):
1291
1605
        self.get_branch_config('/b')
1292
 
        self.assertEqual("gnome-gpg", self.my_config.gpg_signing_command())
 
1606
        self.assertEqual("gnome-gpg",
 
1607
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1608
                self.my_config.gpg_signing_command))
1293
1609
 
1294
1610
    def test_gpg_signing_command_missing(self):
1295
1611
        self.get_branch_config('/a')
1296
 
        self.assertEqual("false", self.my_config.gpg_signing_command())
 
1612
        self.assertEqual("false",
 
1613
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1614
                self.my_config.gpg_signing_command))
 
1615
 
 
1616
    def test_gpg_signing_key(self):
 
1617
        self.get_branch_config('/b')
 
1618
        self.assertEqual("DD4D5088", self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1619
            self.my_config.gpg_signing_key))
 
1620
 
 
1621
    def test_gpg_signing_key_default(self):
 
1622
        self.get_branch_config('/a')
 
1623
        self.assertEqual("erik@bagfors.nu",
 
1624
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1625
                self.my_config.gpg_signing_key))
1297
1626
 
1298
1627
    def test_get_user_option_global(self):
1299
1628
        self.get_branch_config('/a')
1387
1716
    def test_post_commit_default(self):
1388
1717
        self.get_branch_config('/a/c')
1389
1718
        self.assertEqual('bzrlib.tests.test_config.post_commit',
1390
 
                         self.my_config.post_commit())
 
1719
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1720
                                              self.my_config.post_commit))
1391
1721
 
1392
1722
    def get_branch_config(self, location, global_config=None,
1393
1723
                          location_config=None):
1483
1813
        return my_config
1484
1814
 
1485
1815
    def test_user_id(self):
1486
 
        branch = FakeBranch(user_id='Robert Collins <robertc@example.net>')
 
1816
        branch = FakeBranch()
1487
1817
        my_config = config.BranchConfig(branch)
1488
 
        self.assertEqual("Robert Collins <robertc@example.net>",
1489
 
                         my_config.username())
 
1818
        self.assertIsNot(None, my_config.username())
1490
1819
        my_config.branch.control_files.files['email'] = "John"
1491
1820
        my_config.set_user_option('email',
1492
1821
                                  "Robert Collins <robertc@example.org>")
1493
 
        self.assertEqual("John", my_config.username())
1494
 
        del my_config.branch.control_files.files['email']
1495
1822
        self.assertEqual("Robert Collins <robertc@example.org>",
1496
 
                         my_config.username())
1497
 
 
1498
 
    def test_not_set_in_branch(self):
1499
 
        my_config = self.get_branch_config(global_config=sample_config_text)
1500
 
        self.assertEqual(u"Erik B\u00e5gfors <erik@bagfors.nu>",
1501
 
                         my_config._get_user_id())
1502
 
        my_config.branch.control_files.files['email'] = "John"
1503
 
        self.assertEqual("John", my_config._get_user_id())
 
1823
                        my_config.username())
1504
1824
 
1505
1825
    def test_BZR_EMAIL_OVERRIDES(self):
1506
1826
        self.overrideEnv('BZR_EMAIL', "Robert Collins <robertc@example.org>")
1512
1832
    def test_signatures_forced(self):
1513
1833
        my_config = self.get_branch_config(
1514
1834
            global_config=sample_always_signatures)
1515
 
        self.assertEqual(config.CHECK_NEVER, my_config.signature_checking())
1516
 
        self.assertEqual(config.SIGN_ALWAYS, my_config.signing_policy())
1517
 
        self.assertTrue(my_config.signature_needed())
 
1835
        self.assertEqual(config.CHECK_NEVER,
 
1836
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1837
                my_config.signature_checking))
 
1838
        self.assertEqual(config.SIGN_ALWAYS,
 
1839
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1840
                my_config.signing_policy))
 
1841
        self.assertTrue(self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1842
            my_config.signature_needed))
1518
1843
 
1519
1844
    def test_signatures_forced_branch(self):
1520
1845
        my_config = self.get_branch_config(
1521
1846
            global_config=sample_ignore_signatures,
1522
1847
            branch_data_config=sample_always_signatures)
1523
 
        self.assertEqual(config.CHECK_NEVER, my_config.signature_checking())
1524
 
        self.assertEqual(config.SIGN_ALWAYS, my_config.signing_policy())
1525
 
        self.assertTrue(my_config.signature_needed())
 
1848
        self.assertEqual(config.CHECK_NEVER,
 
1849
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1850
                my_config.signature_checking))
 
1851
        self.assertEqual(config.SIGN_ALWAYS,
 
1852
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1853
                my_config.signing_policy))
 
1854
        self.assertTrue(self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1855
            my_config.signature_needed))
1526
1856
 
1527
1857
    def test_gpg_signing_command(self):
1528
1858
        my_config = self.get_branch_config(
1529
1859
            global_config=sample_config_text,
1530
1860
            # branch data cannot set gpg_signing_command
1531
1861
            branch_data_config="gpg_signing_command=pgp")
1532
 
        self.assertEqual('gnome-gpg', my_config.gpg_signing_command())
 
1862
        self.assertEqual('gnome-gpg',
 
1863
            self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1864
                my_config.gpg_signing_command))
1533
1865
 
1534
1866
    def test_get_user_option_global(self):
1535
1867
        my_config = self.get_branch_config(global_config=sample_config_text)
1542
1874
                                      location_config=sample_branches_text)
1543
1875
        self.assertEqual(my_config.branch.base, '/a/c')
1544
1876
        self.assertEqual('bzrlib.tests.test_config.post_commit',
1545
 
                         my_config.post_commit())
 
1877
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1878
                                              my_config.post_commit))
1546
1879
        my_config.set_user_option('post_commit', 'rmtree_root')
1547
1880
        # post-commit is ignored when present in branch data
1548
1881
        self.assertEqual('bzrlib.tests.test_config.post_commit',
1549
 
                         my_config.post_commit())
 
1882
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1883
                                              my_config.post_commit))
1550
1884
        my_config.set_user_option('post_commit', 'rmtree_root',
1551
1885
                                  store=config.STORE_LOCATION)
1552
 
        self.assertEqual('rmtree_root', my_config.post_commit())
 
1886
        self.assertEqual('rmtree_root',
 
1887
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1888
                                              my_config.post_commit))
1553
1889
 
1554
1890
    def test_config_precedence(self):
1555
1891
        # FIXME: eager test, luckily no persitent config file makes it fail
1571
1907
            location='http://example.com/specific')
1572
1908
        self.assertEqual(my_config.get_user_option('option'), 'exact')
1573
1909
 
1574
 
    def test_get_mail_client(self):
1575
 
        config = self.get_branch_config()
1576
 
        client = config.get_mail_client()
1577
 
        self.assertIsInstance(client, mail_client.DefaultMail)
1578
 
 
1579
 
        # Specific clients
1580
 
        config.set_user_option('mail_client', 'evolution')
1581
 
        client = config.get_mail_client()
1582
 
        self.assertIsInstance(client, mail_client.Evolution)
1583
 
 
1584
 
        config.set_user_option('mail_client', 'kmail')
1585
 
        client = config.get_mail_client()
1586
 
        self.assertIsInstance(client, mail_client.KMail)
1587
 
 
1588
 
        config.set_user_option('mail_client', 'mutt')
1589
 
        client = config.get_mail_client()
1590
 
        self.assertIsInstance(client, mail_client.Mutt)
1591
 
 
1592
 
        config.set_user_option('mail_client', 'thunderbird')
1593
 
        client = config.get_mail_client()
1594
 
        self.assertIsInstance(client, mail_client.Thunderbird)
1595
 
 
1596
 
        # Generic options
1597
 
        config.set_user_option('mail_client', 'default')
1598
 
        client = config.get_mail_client()
1599
 
        self.assertIsInstance(client, mail_client.DefaultMail)
1600
 
 
1601
 
        config.set_user_option('mail_client', 'editor')
1602
 
        client = config.get_mail_client()
1603
 
        self.assertIsInstance(client, mail_client.Editor)
1604
 
 
1605
 
        config.set_user_option('mail_client', 'mapi')
1606
 
        client = config.get_mail_client()
1607
 
        self.assertIsInstance(client, mail_client.MAPIClient)
1608
 
 
1609
 
        config.set_user_option('mail_client', 'xdg-email')
1610
 
        client = config.get_mail_client()
1611
 
        self.assertIsInstance(client, mail_client.XDGEmail)
1612
 
 
1613
 
        config.set_user_option('mail_client', 'firebird')
1614
 
        self.assertRaises(errors.UnknownMailClient, config.get_mail_client)
1615
 
 
1616
1910
 
1617
1911
class TestMailAddressExtraction(tests.TestCase):
1618
1912
 
1664
1958
 
1665
1959
class TestTransportConfig(tests.TestCaseWithTransport):
1666
1960
 
 
1961
    def test_load_utf8(self):
 
1962
        """Ensure we can load an utf8-encoded file."""
 
1963
        t = self.get_transport()
 
1964
        unicode_user = u'b\N{Euro Sign}ar'
 
1965
        unicode_content = u'user=%s' % (unicode_user,)
 
1966
        utf8_content = unicode_content.encode('utf8')
 
1967
        # Store the raw content in the config file
 
1968
        t.put_bytes('foo.conf', utf8_content)
 
1969
        conf = config.TransportConfig(t, 'foo.conf')
 
1970
        self.assertEquals(unicode_user, conf.get_option('user'))
 
1971
 
 
1972
    def test_load_non_ascii(self):
 
1973
        """Ensure we display a proper error on non-ascii, non utf-8 content."""
 
1974
        t = self.get_transport()
 
1975
        t.put_bytes('foo.conf', 'user=foo\n#\xff\n')
 
1976
        conf = config.TransportConfig(t, 'foo.conf')
 
1977
        self.assertRaises(errors.ConfigContentError, conf._get_configobj)
 
1978
 
 
1979
    def test_load_erroneous_content(self):
 
1980
        """Ensure we display a proper error on content that can't be parsed."""
 
1981
        t = self.get_transport()
 
1982
        t.put_bytes('foo.conf', '[open_section\n')
 
1983
        conf = config.TransportConfig(t, 'foo.conf')
 
1984
        self.assertRaises(errors.ParseConfigError, conf._get_configobj)
 
1985
 
 
1986
    def test_load_permission_denied(self):
 
1987
        """Ensure we get an empty config file if the file is inaccessible."""
 
1988
        warnings = []
 
1989
        def warning(*args):
 
1990
            warnings.append(args[0] % args[1:])
 
1991
        self.overrideAttr(trace, 'warning', warning)
 
1992
 
 
1993
        class DenyingTransport(object):
 
1994
 
 
1995
            def __init__(self, base):
 
1996
                self.base = base
 
1997
 
 
1998
            def get_bytes(self, relpath):
 
1999
                raise errors.PermissionDenied(relpath, "")
 
2000
 
 
2001
        cfg = config.TransportConfig(
 
2002
            DenyingTransport("nonexisting://"), 'control.conf')
 
2003
        self.assertIs(None, cfg.get_option('non-existant', 'SECTION'))
 
2004
        self.assertEquals(
 
2005
            warnings,
 
2006
            [u'Permission denied while trying to open configuration file '
 
2007
             u'nonexisting:///control.conf.'])
 
2008
 
1667
2009
    def test_get_value(self):
1668
2010
        """Test that retreiving a value from a section is possible"""
1669
 
        bzrdir_config = config.TransportConfig(transport.get_transport('.'),
 
2011
        bzrdir_config = config.TransportConfig(self.get_transport('.'),
1670
2012
                                               'control.conf')
1671
2013
        bzrdir_config.set_option('value', 'key', 'SECTION')
1672
2014
        bzrdir_config.set_option('value2', 'key2')
1702
2044
        self.assertIs(None, bzrdir_config.get_default_stack_on())
1703
2045
 
1704
2046
 
 
2047
class TestOldConfigHooks(tests.TestCaseWithTransport):
 
2048
 
 
2049
    def setUp(self):
 
2050
        super(TestOldConfigHooks, self).setUp()
 
2051
        create_configs_with_file_option(self)
 
2052
 
 
2053
    def assertGetHook(self, conf, name, value):
 
2054
        calls = []
 
2055
        def hook(*args):
 
2056
            calls.append(args)
 
2057
        config.OldConfigHooks.install_named_hook('get', hook, None)
 
2058
        self.addCleanup(
 
2059
            config.OldConfigHooks.uninstall_named_hook, 'get', None)
 
2060
        self.assertLength(0, calls)
 
2061
        actual_value = conf.get_user_option(name)
 
2062
        self.assertEquals(value, actual_value)
 
2063
        self.assertLength(1, calls)
 
2064
        self.assertEquals((conf, name, value), calls[0])
 
2065
 
 
2066
    def test_get_hook_bazaar(self):
 
2067
        self.assertGetHook(self.bazaar_config, 'file', 'bazaar')
 
2068
 
 
2069
    def test_get_hook_locations(self):
 
2070
        self.assertGetHook(self.locations_config, 'file', 'locations')
 
2071
 
 
2072
    def test_get_hook_branch(self):
 
2073
        # Since locations masks branch, we define a different option
 
2074
        self.branch_config.set_user_option('file2', 'branch')
 
2075
        self.assertGetHook(self.branch_config, 'file2', 'branch')
 
2076
 
 
2077
    def assertSetHook(self, conf, name, value):
 
2078
        calls = []
 
2079
        def hook(*args):
 
2080
            calls.append(args)
 
2081
        config.OldConfigHooks.install_named_hook('set', hook, None)
 
2082
        self.addCleanup(
 
2083
            config.OldConfigHooks.uninstall_named_hook, 'set', None)
 
2084
        self.assertLength(0, calls)
 
2085
        conf.set_user_option(name, value)
 
2086
        self.assertLength(1, calls)
 
2087
        # We can't assert the conf object below as different configs use
 
2088
        # different means to implement set_user_option and we care only about
 
2089
        # coverage here.
 
2090
        self.assertEquals((name, value), calls[0][1:])
 
2091
 
 
2092
    def test_set_hook_bazaar(self):
 
2093
        self.assertSetHook(self.bazaar_config, 'foo', 'bazaar')
 
2094
 
 
2095
    def test_set_hook_locations(self):
 
2096
        self.assertSetHook(self.locations_config, 'foo', 'locations')
 
2097
 
 
2098
    def test_set_hook_branch(self):
 
2099
        self.assertSetHook(self.branch_config, 'foo', 'branch')
 
2100
 
 
2101
    def assertRemoveHook(self, conf, name, section_name=None):
 
2102
        calls = []
 
2103
        def hook(*args):
 
2104
            calls.append(args)
 
2105
        config.OldConfigHooks.install_named_hook('remove', hook, None)
 
2106
        self.addCleanup(
 
2107
            config.OldConfigHooks.uninstall_named_hook, 'remove', None)
 
2108
        self.assertLength(0, calls)
 
2109
        conf.remove_user_option(name, section_name)
 
2110
        self.assertLength(1, calls)
 
2111
        # We can't assert the conf object below as different configs use
 
2112
        # different means to implement remove_user_option and we care only about
 
2113
        # coverage here.
 
2114
        self.assertEquals((name,), calls[0][1:])
 
2115
 
 
2116
    def test_remove_hook_bazaar(self):
 
2117
        self.assertRemoveHook(self.bazaar_config, 'file')
 
2118
 
 
2119
    def test_remove_hook_locations(self):
 
2120
        self.assertRemoveHook(self.locations_config, 'file',
 
2121
                              self.locations_config.location)
 
2122
 
 
2123
    def test_remove_hook_branch(self):
 
2124
        self.assertRemoveHook(self.branch_config, 'file')
 
2125
 
 
2126
    def assertLoadHook(self, name, conf_class, *conf_args):
 
2127
        calls = []
 
2128
        def hook(*args):
 
2129
            calls.append(args)
 
2130
        config.OldConfigHooks.install_named_hook('load', hook, None)
 
2131
        self.addCleanup(
 
2132
            config.OldConfigHooks.uninstall_named_hook, 'load', None)
 
2133
        self.assertLength(0, calls)
 
2134
        # Build a config
 
2135
        conf = conf_class(*conf_args)
 
2136
        # Access an option to trigger a load
 
2137
        conf.get_user_option(name)
 
2138
        self.assertLength(1, calls)
 
2139
        # Since we can't assert about conf, we just use the number of calls ;-/
 
2140
 
 
2141
    def test_load_hook_bazaar(self):
 
2142
        self.assertLoadHook('file', config.GlobalConfig)
 
2143
 
 
2144
    def test_load_hook_locations(self):
 
2145
        self.assertLoadHook('file', config.LocationConfig, self.tree.basedir)
 
2146
 
 
2147
    def test_load_hook_branch(self):
 
2148
        self.assertLoadHook('file', config.BranchConfig, self.tree.branch)
 
2149
 
 
2150
    def assertSaveHook(self, conf):
 
2151
        calls = []
 
2152
        def hook(*args):
 
2153
            calls.append(args)
 
2154
        config.OldConfigHooks.install_named_hook('save', hook, None)
 
2155
        self.addCleanup(
 
2156
            config.OldConfigHooks.uninstall_named_hook, 'save', None)
 
2157
        self.assertLength(0, calls)
 
2158
        # Setting an option triggers a save
 
2159
        conf.set_user_option('foo', 'bar')
 
2160
        self.assertLength(1, calls)
 
2161
        # Since we can't assert about conf, we just use the number of calls ;-/
 
2162
 
 
2163
    def test_save_hook_bazaar(self):
 
2164
        self.assertSaveHook(self.bazaar_config)
 
2165
 
 
2166
    def test_save_hook_locations(self):
 
2167
        self.assertSaveHook(self.locations_config)
 
2168
 
 
2169
    def test_save_hook_branch(self):
 
2170
        self.assertSaveHook(self.branch_config)
 
2171
 
 
2172
 
 
2173
class TestOldConfigHooksForRemote(tests.TestCaseWithTransport):
 
2174
    """Tests config hooks for remote configs.
 
2175
 
 
2176
    No tests for the remove hook as this is not implemented there.
 
2177
    """
 
2178
 
 
2179
    def setUp(self):
 
2180
        super(TestOldConfigHooksForRemote, self).setUp()
 
2181
        self.transport_server = test_server.SmartTCPServer_for_testing
 
2182
        create_configs_with_file_option(self)
 
2183
 
 
2184
    def assertGetHook(self, conf, name, value):
 
2185
        calls = []
 
2186
        def hook(*args):
 
2187
            calls.append(args)
 
2188
        config.OldConfigHooks.install_named_hook('get', hook, None)
 
2189
        self.addCleanup(
 
2190
            config.OldConfigHooks.uninstall_named_hook, 'get', None)
 
2191
        self.assertLength(0, calls)
 
2192
        actual_value = conf.get_option(name)
 
2193
        self.assertEquals(value, actual_value)
 
2194
        self.assertLength(1, calls)
 
2195
        self.assertEquals((conf, name, value), calls[0])
 
2196
 
 
2197
    def test_get_hook_remote_branch(self):
 
2198
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2199
        self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
 
2200
 
 
2201
    def test_get_hook_remote_bzrdir(self):
 
2202
        remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
 
2203
        conf = remote_bzrdir._get_config()
 
2204
        conf.set_option('remotedir', 'file')
 
2205
        self.assertGetHook(conf, 'file', 'remotedir')
 
2206
 
 
2207
    def assertSetHook(self, conf, name, value):
 
2208
        calls = []
 
2209
        def hook(*args):
 
2210
            calls.append(args)
 
2211
        config.OldConfigHooks.install_named_hook('set', hook, None)
 
2212
        self.addCleanup(
 
2213
            config.OldConfigHooks.uninstall_named_hook, 'set', None)
 
2214
        self.assertLength(0, calls)
 
2215
        conf.set_option(value, name)
 
2216
        self.assertLength(1, calls)
 
2217
        # We can't assert the conf object below as different configs use
 
2218
        # different means to implement set_user_option and we care only about
 
2219
        # coverage here.
 
2220
        self.assertEquals((name, value), calls[0][1:])
 
2221
 
 
2222
    def test_set_hook_remote_branch(self):
 
2223
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2224
        self.addCleanup(remote_branch.lock_write().unlock)
 
2225
        self.assertSetHook(remote_branch._get_config(), 'file', 'remote')
 
2226
 
 
2227
    def test_set_hook_remote_bzrdir(self):
 
2228
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2229
        self.addCleanup(remote_branch.lock_write().unlock)
 
2230
        remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
 
2231
        self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
 
2232
 
 
2233
    def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
 
2234
        calls = []
 
2235
        def hook(*args):
 
2236
            calls.append(args)
 
2237
        config.OldConfigHooks.install_named_hook('load', hook, None)
 
2238
        self.addCleanup(
 
2239
            config.OldConfigHooks.uninstall_named_hook, 'load', None)
 
2240
        self.assertLength(0, calls)
 
2241
        # Build a config
 
2242
        conf = conf_class(*conf_args)
 
2243
        # Access an option to trigger a load
 
2244
        conf.get_option(name)
 
2245
        self.assertLength(expected_nb_calls, calls)
 
2246
        # Since we can't assert about conf, we just use the number of calls ;-/
 
2247
 
 
2248
    def test_load_hook_remote_branch(self):
 
2249
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2250
        self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
 
2251
 
 
2252
    def test_load_hook_remote_bzrdir(self):
 
2253
        remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
 
2254
        # The config file doesn't exist, set an option to force its creation
 
2255
        conf = remote_bzrdir._get_config()
 
2256
        conf.set_option('remotedir', 'file')
 
2257
        # We get one call for the server and one call for the client, this is
 
2258
        # caused by the differences in implementations betwen
 
2259
        # SmartServerBzrDirRequestConfigFile (in smart/bzrdir.py) and
 
2260
        # SmartServerBranchGetConfigFile (in smart/branch.py)
 
2261
        self.assertLoadHook(2 ,'file', remote.RemoteBzrDirConfig, remote_bzrdir)
 
2262
 
 
2263
    def assertSaveHook(self, conf):
 
2264
        calls = []
 
2265
        def hook(*args):
 
2266
            calls.append(args)
 
2267
        config.OldConfigHooks.install_named_hook('save', hook, None)
 
2268
        self.addCleanup(
 
2269
            config.OldConfigHooks.uninstall_named_hook, 'save', None)
 
2270
        self.assertLength(0, calls)
 
2271
        # Setting an option triggers a save
 
2272
        conf.set_option('foo', 'bar')
 
2273
        self.assertLength(1, calls)
 
2274
        # Since we can't assert about conf, we just use the number of calls ;-/
 
2275
 
 
2276
    def test_save_hook_remote_branch(self):
 
2277
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2278
        self.addCleanup(remote_branch.lock_write().unlock)
 
2279
        self.assertSaveHook(remote_branch._get_config())
 
2280
 
 
2281
    def test_save_hook_remote_bzrdir(self):
 
2282
        remote_branch = branch.Branch.open(self.get_url('tree'))
 
2283
        self.addCleanup(remote_branch.lock_write().unlock)
 
2284
        remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
 
2285
        self.assertSaveHook(remote_bzrdir._get_config())
 
2286
 
 
2287
 
 
2288
class TestOption(tests.TestCase):
 
2289
 
 
2290
    def test_default_value(self):
 
2291
        opt = config.Option('foo', default='bar')
 
2292
        self.assertEquals('bar', opt.get_default())
 
2293
 
 
2294
    def test_callable_default_value(self):
 
2295
        def bar_as_unicode():
 
2296
            return u'bar'
 
2297
        opt = config.Option('foo', default=bar_as_unicode)
 
2298
        self.assertEquals('bar', opt.get_default())
 
2299
 
 
2300
    def test_default_value_from_env(self):
 
2301
        opt = config.Option('foo', default='bar', default_from_env=['FOO'])
 
2302
        self.overrideEnv('FOO', 'quux')
 
2303
        # Env variable provides a default taking over the option one
 
2304
        self.assertEquals('quux', opt.get_default())
 
2305
 
 
2306
    def test_first_default_value_from_env_wins(self):
 
2307
        opt = config.Option('foo', default='bar',
 
2308
                            default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
 
2309
        self.overrideEnv('FOO', 'foo')
 
2310
        self.overrideEnv('BAZ', 'baz')
 
2311
        # The first env var set wins
 
2312
        self.assertEquals('foo', opt.get_default())
 
2313
 
 
2314
    def test_not_supported_list_default_value(self):
 
2315
        self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
 
2316
 
 
2317
    def test_not_supported_object_default_value(self):
 
2318
        self.assertRaises(AssertionError, config.Option, 'foo',
 
2319
                          default=object())
 
2320
 
 
2321
    def test_not_supported_callable_default_value_not_unicode(self):
 
2322
        def bar_not_unicode():
 
2323
            return 'bar'
 
2324
        opt = config.Option('foo', default=bar_not_unicode)
 
2325
        self.assertRaises(AssertionError, opt.get_default)
 
2326
 
 
2327
 
 
2328
class TestOptionConverterMixin(object):
 
2329
 
 
2330
    def assertConverted(self, expected, opt, value):
 
2331
        self.assertEquals(expected, opt.convert_from_unicode(None, value))
 
2332
 
 
2333
    def assertWarns(self, opt, value):
 
2334
        warnings = []
 
2335
        def warning(*args):
 
2336
            warnings.append(args[0] % args[1:])
 
2337
        self.overrideAttr(trace, 'warning', warning)
 
2338
        self.assertEquals(None, opt.convert_from_unicode(None, value))
 
2339
        self.assertLength(1, warnings)
 
2340
        self.assertEquals(
 
2341
            'Value "%s" is not valid for "%s"' % (value, opt.name),
 
2342
            warnings[0])
 
2343
 
 
2344
    def assertErrors(self, opt, value):
 
2345
        self.assertRaises(errors.ConfigOptionValueError,
 
2346
                          opt.convert_from_unicode, None, value)
 
2347
 
 
2348
    def assertConvertInvalid(self, opt, invalid_value):
 
2349
        opt.invalid = None
 
2350
        self.assertEquals(None, opt.convert_from_unicode(None, invalid_value))
 
2351
        opt.invalid = 'warning'
 
2352
        self.assertWarns(opt, invalid_value)
 
2353
        opt.invalid = 'error'
 
2354
        self.assertErrors(opt, invalid_value)
 
2355
 
 
2356
 
 
2357
class TestOptionWithBooleanConverter(tests.TestCase, TestOptionConverterMixin):
 
2358
 
 
2359
    def get_option(self):
 
2360
        return config.Option('foo', help='A boolean.',
 
2361
                             from_unicode=config.bool_from_store)
 
2362
 
 
2363
    def test_convert_invalid(self):
 
2364
        opt = self.get_option()
 
2365
        # A string that is not recognized as a boolean
 
2366
        self.assertConvertInvalid(opt, u'invalid-boolean')
 
2367
        # A list of strings is never recognized as a boolean
 
2368
        self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
 
2369
 
 
2370
    def test_convert_valid(self):
 
2371
        opt = self.get_option()
 
2372
        self.assertConverted(True, opt, u'True')
 
2373
        self.assertConverted(True, opt, u'1')
 
2374
        self.assertConverted(False, opt, u'False')
 
2375
 
 
2376
 
 
2377
class TestOptionWithIntegerConverter(tests.TestCase, TestOptionConverterMixin):
 
2378
 
 
2379
    def get_option(self):
 
2380
        return config.Option('foo', help='An integer.',
 
2381
                             from_unicode=config.int_from_store)
 
2382
 
 
2383
    def test_convert_invalid(self):
 
2384
        opt = self.get_option()
 
2385
        # A string that is not recognized as an integer
 
2386
        self.assertConvertInvalid(opt, u'forty-two')
 
2387
        # A list of strings is never recognized as an integer
 
2388
        self.assertConvertInvalid(opt, [u'a', u'list'])
 
2389
 
 
2390
    def test_convert_valid(self):
 
2391
        opt = self.get_option()
 
2392
        self.assertConverted(16, opt, u'16')
 
2393
 
 
2394
 
 
2395
class TestOptionWithSIUnitConverter(tests.TestCase, TestOptionConverterMixin):
 
2396
 
 
2397
    def get_option(self):
 
2398
        return config.Option('foo', help='An integer in SI units.',
 
2399
                             from_unicode=config.int_SI_from_store)
 
2400
 
 
2401
    def test_convert_invalid(self):
 
2402
        opt = self.get_option()
 
2403
        self.assertConvertInvalid(opt, u'not-a-unit')
 
2404
        self.assertConvertInvalid(opt, u'Gb') # Forgot the int
 
2405
        self.assertConvertInvalid(opt, u'1b') # Forgot the unit
 
2406
        self.assertConvertInvalid(opt, u'1GG')
 
2407
        self.assertConvertInvalid(opt, u'1Mbb')
 
2408
        self.assertConvertInvalid(opt, u'1MM')
 
2409
 
 
2410
    def test_convert_valid(self):
 
2411
        opt = self.get_option()
 
2412
        self.assertConverted(int(5e3), opt, u'5kb')
 
2413
        self.assertConverted(int(5e6), opt, u'5M')
 
2414
        self.assertConverted(int(5e6), opt, u'5MB')
 
2415
        self.assertConverted(int(5e9), opt, u'5g')
 
2416
        self.assertConverted(int(5e9), opt, u'5gB')
 
2417
        self.assertConverted(100, opt, u'100')
 
2418
 
 
2419
 
 
2420
class TestListOption(tests.TestCase, TestOptionConverterMixin):
 
2421
 
 
2422
    def get_option(self):
 
2423
        return config.ListOption('foo', help='A list.')
 
2424
 
 
2425
    def test_convert_invalid(self):
 
2426
        opt = self.get_option()
 
2427
        # We don't even try to convert a list into a list, we only expect
 
2428
        # strings
 
2429
        self.assertConvertInvalid(opt, [1])
 
2430
        # No string is invalid as all forms can be converted to a list
 
2431
 
 
2432
    def test_convert_valid(self):
 
2433
        opt = self.get_option()
 
2434
        # An empty string is an empty list
 
2435
        self.assertConverted([], opt, '') # Using a bare str() just in case
 
2436
        self.assertConverted([], opt, u'')
 
2437
        # A boolean
 
2438
        self.assertConverted([u'True'], opt, u'True')
 
2439
        # An integer
 
2440
        self.assertConverted([u'42'], opt, u'42')
 
2441
        # A single string
 
2442
        self.assertConverted([u'bar'], opt, u'bar')
 
2443
 
 
2444
 
 
2445
class TestRegistryOption(tests.TestCase, TestOptionConverterMixin):
 
2446
 
 
2447
    def get_option(self, registry):
 
2448
        return config.RegistryOption('foo', registry,
 
2449
                help='A registry option.')
 
2450
 
 
2451
    def test_convert_invalid(self):
 
2452
        registry = _mod_registry.Registry()
 
2453
        opt = self.get_option(registry)
 
2454
        self.assertConvertInvalid(opt, [1])
 
2455
        self.assertConvertInvalid(opt, u"notregistered")
 
2456
 
 
2457
    def test_convert_valid(self):
 
2458
        registry = _mod_registry.Registry()
 
2459
        registry.register("someval", 1234)
 
2460
        opt = self.get_option(registry)
 
2461
        # Using a bare str() just in case
 
2462
        self.assertConverted(1234, opt, "someval")
 
2463
        self.assertConverted(1234, opt, u'someval')
 
2464
        self.assertConverted(None, opt, None)
 
2465
 
 
2466
    def test_help(self):
 
2467
        registry = _mod_registry.Registry()
 
2468
        registry.register("someval", 1234, help="some option")
 
2469
        registry.register("dunno", 1234, help="some other option")
 
2470
        opt = self.get_option(registry)
 
2471
        self.assertEquals(
 
2472
            'A registry option.\n'
 
2473
            '\n'
 
2474
            'The following values are supported:\n'
 
2475
            ' dunno - some other option\n'
 
2476
            ' someval - some option\n',
 
2477
            opt.help)
 
2478
 
 
2479
    def test_get_help_text(self):
 
2480
        registry = _mod_registry.Registry()
 
2481
        registry.register("someval", 1234, help="some option")
 
2482
        registry.register("dunno", 1234, help="some other option")
 
2483
        opt = self.get_option(registry)
 
2484
        self.assertEquals(
 
2485
            'A registry option.\n'
 
2486
            '\n'
 
2487
            'The following values are supported:\n'
 
2488
            ' dunno - some other option\n'
 
2489
            ' someval - some option\n',
 
2490
            opt.get_help_text())
 
2491
 
 
2492
 
 
2493
class TestOptionRegistry(tests.TestCase):
 
2494
 
 
2495
    def setUp(self):
 
2496
        super(TestOptionRegistry, self).setUp()
 
2497
        # Always start with an empty registry
 
2498
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
2499
        self.registry = config.option_registry
 
2500
 
 
2501
    def test_register(self):
 
2502
        opt = config.Option('foo')
 
2503
        self.registry.register(opt)
 
2504
        self.assertIs(opt, self.registry.get('foo'))
 
2505
 
 
2506
    def test_registered_help(self):
 
2507
        opt = config.Option('foo', help='A simple option')
 
2508
        self.registry.register(opt)
 
2509
        self.assertEquals('A simple option', self.registry.get_help('foo'))
 
2510
 
 
2511
    lazy_option = config.Option('lazy_foo', help='Lazy help')
 
2512
 
 
2513
    def test_register_lazy(self):
 
2514
        self.registry.register_lazy('lazy_foo', self.__module__,
 
2515
                                    'TestOptionRegistry.lazy_option')
 
2516
        self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
 
2517
 
 
2518
    def test_registered_lazy_help(self):
 
2519
        self.registry.register_lazy('lazy_foo', self.__module__,
 
2520
                                    'TestOptionRegistry.lazy_option')
 
2521
        self.assertEquals('Lazy help', self.registry.get_help('lazy_foo'))
 
2522
 
 
2523
 
 
2524
class TestRegisteredOptions(tests.TestCase):
 
2525
    """All registered options should verify some constraints."""
 
2526
 
 
2527
    scenarios = [(key, {'option_name': key, 'option': option}) for key, option
 
2528
                 in config.option_registry.iteritems()]
 
2529
 
 
2530
    def setUp(self):
 
2531
        super(TestRegisteredOptions, self).setUp()
 
2532
        self.registry = config.option_registry
 
2533
 
 
2534
    def test_proper_name(self):
 
2535
        # An option should be registered under its own name, this can't be
 
2536
        # checked at registration time for the lazy ones.
 
2537
        self.assertEquals(self.option_name, self.option.name)
 
2538
 
 
2539
    def test_help_is_set(self):
 
2540
        option_help = self.registry.get_help(self.option_name)
 
2541
        self.assertNotEquals(None, option_help)
 
2542
        # Come on, think about the user, he really wants to know what the
 
2543
        # option is about
 
2544
        self.assertIsNot(None, option_help)
 
2545
        self.assertNotEquals('', option_help)
 
2546
 
 
2547
 
 
2548
class TestSection(tests.TestCase):
 
2549
 
 
2550
    # FIXME: Parametrize so that all sections produced by Stores run these
 
2551
    # tests -- vila 2011-04-01
 
2552
 
 
2553
    def test_get_a_value(self):
 
2554
        a_dict = dict(foo='bar')
 
2555
        section = config.Section('myID', a_dict)
 
2556
        self.assertEquals('bar', section.get('foo'))
 
2557
 
 
2558
    def test_get_unknown_option(self):
 
2559
        a_dict = dict()
 
2560
        section = config.Section(None, a_dict)
 
2561
        self.assertEquals('out of thin air',
 
2562
                          section.get('foo', 'out of thin air'))
 
2563
 
 
2564
    def test_options_is_shared(self):
 
2565
        a_dict = dict()
 
2566
        section = config.Section(None, a_dict)
 
2567
        self.assertIs(a_dict, section.options)
 
2568
 
 
2569
 
 
2570
class TestMutableSection(tests.TestCase):
 
2571
 
 
2572
    scenarios = [('mutable',
 
2573
                  {'get_section':
 
2574
                       lambda opts: config.MutableSection('myID', opts)},),
 
2575
        ]
 
2576
 
 
2577
    def test_set(self):
 
2578
        a_dict = dict(foo='bar')
 
2579
        section = self.get_section(a_dict)
 
2580
        section.set('foo', 'new_value')
 
2581
        self.assertEquals('new_value', section.get('foo'))
 
2582
        # The change appears in the shared section
 
2583
        self.assertEquals('new_value', a_dict.get('foo'))
 
2584
        # We keep track of the change
 
2585
        self.assertTrue('foo' in section.orig)
 
2586
        self.assertEquals('bar', section.orig.get('foo'))
 
2587
 
 
2588
    def test_set_preserve_original_once(self):
 
2589
        a_dict = dict(foo='bar')
 
2590
        section = self.get_section(a_dict)
 
2591
        section.set('foo', 'first_value')
 
2592
        section.set('foo', 'second_value')
 
2593
        # We keep track of the original value
 
2594
        self.assertTrue('foo' in section.orig)
 
2595
        self.assertEquals('bar', section.orig.get('foo'))
 
2596
 
 
2597
    def test_remove(self):
 
2598
        a_dict = dict(foo='bar')
 
2599
        section = self.get_section(a_dict)
 
2600
        section.remove('foo')
 
2601
        # We get None for unknown options via the default value
 
2602
        self.assertEquals(None, section.get('foo'))
 
2603
        # Or we just get the default value
 
2604
        self.assertEquals('unknown', section.get('foo', 'unknown'))
 
2605
        self.assertFalse('foo' in section.options)
 
2606
        # We keep track of the deletion
 
2607
        self.assertTrue('foo' in section.orig)
 
2608
        self.assertEquals('bar', section.orig.get('foo'))
 
2609
 
 
2610
    def test_remove_new_option(self):
 
2611
        a_dict = dict()
 
2612
        section = self.get_section(a_dict)
 
2613
        section.set('foo', 'bar')
 
2614
        section.remove('foo')
 
2615
        self.assertFalse('foo' in section.options)
 
2616
        # The option didn't exist initially so it we need to keep track of it
 
2617
        # with a special value
 
2618
        self.assertTrue('foo' in section.orig)
 
2619
        self.assertEquals(config._NewlyCreatedOption, section.orig['foo'])
 
2620
 
 
2621
 
 
2622
class TestCommandLineStore(tests.TestCase):
 
2623
 
 
2624
    def setUp(self):
 
2625
        super(TestCommandLineStore, self).setUp()
 
2626
        self.store = config.CommandLineStore()
 
2627
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
2628
 
 
2629
    def get_section(self):
 
2630
        """Get the unique section for the command line overrides."""
 
2631
        sections = list(self.store.get_sections())
 
2632
        self.assertLength(1, sections)
 
2633
        store, section = sections[0]
 
2634
        self.assertEquals(self.store, store)
 
2635
        return section
 
2636
 
 
2637
    def test_no_override(self):
 
2638
        self.store._from_cmdline([])
 
2639
        section = self.get_section()
 
2640
        self.assertLength(0, list(section.iter_option_names()))
 
2641
 
 
2642
    def test_simple_override(self):
 
2643
        self.store._from_cmdline(['a=b'])
 
2644
        section = self.get_section()
 
2645
        self.assertEqual('b', section.get('a'))
 
2646
 
 
2647
    def test_list_override(self):
 
2648
        opt = config.ListOption('l')
 
2649
        config.option_registry.register(opt)
 
2650
        self.store._from_cmdline(['l=1,2,3'])
 
2651
        val = self.get_section().get('l')
 
2652
        self.assertEqual('1,2,3', val)
 
2653
        # Reminder: lists should be registered as such explicitely, otherwise
 
2654
        # the conversion needs to be done afterwards.
 
2655
        self.assertEqual(['1', '2', '3'],
 
2656
                         opt.convert_from_unicode(self.store, val))
 
2657
 
 
2658
    def test_multiple_overrides(self):
 
2659
        self.store._from_cmdline(['a=b', 'x=y'])
 
2660
        section = self.get_section()
 
2661
        self.assertEquals('b', section.get('a'))
 
2662
        self.assertEquals('y', section.get('x'))
 
2663
 
 
2664
    def test_wrong_syntax(self):
 
2665
        self.assertRaises(errors.BzrCommandError,
 
2666
                          self.store._from_cmdline, ['a=b', 'c'])
 
2667
 
 
2668
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
 
2669
 
 
2670
    scenarios = [(key, {'get_store': builder}) for key, builder
 
2671
                 in config.test_store_builder_registry.iteritems()] + [
 
2672
        ('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
 
2673
 
 
2674
    def test_id(self):
 
2675
        store = self.get_store(self)
 
2676
        if type(store) == config.TransportIniFileStore:
 
2677
            raise tests.TestNotApplicable(
 
2678
                "%s is not a concrete Store implementation"
 
2679
                " so it doesn't need an id" % (store.__class__.__name__,))
 
2680
        self.assertIsNot(None, store.id)
 
2681
 
 
2682
 
 
2683
class TestStore(tests.TestCaseWithTransport):
 
2684
 
 
2685
    def assertSectionContent(self, expected, (store, section)):
 
2686
        """Assert that some options have the proper values in a section."""
 
2687
        expected_name, expected_options = expected
 
2688
        self.assertEquals(expected_name, section.id)
 
2689
        self.assertEquals(
 
2690
            expected_options,
 
2691
            dict([(k, section.get(k)) for k in expected_options.keys()]))
 
2692
 
 
2693
 
 
2694
class TestReadonlyStore(TestStore):
 
2695
 
 
2696
    scenarios = [(key, {'get_store': builder}) for key, builder
 
2697
                 in config.test_store_builder_registry.iteritems()]
 
2698
 
 
2699
    def test_building_delays_load(self):
 
2700
        store = self.get_store(self)
 
2701
        self.assertEquals(False, store.is_loaded())
 
2702
        store._load_from_string('')
 
2703
        self.assertEquals(True, store.is_loaded())
 
2704
 
 
2705
    def test_get_no_sections_for_empty(self):
 
2706
        store = self.get_store(self)
 
2707
        store._load_from_string('')
 
2708
        self.assertEquals([], list(store.get_sections()))
 
2709
 
 
2710
    def test_get_default_section(self):
 
2711
        store = self.get_store(self)
 
2712
        store._load_from_string('foo=bar')
 
2713
        sections = list(store.get_sections())
 
2714
        self.assertLength(1, sections)
 
2715
        self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
 
2716
 
 
2717
    def test_get_named_section(self):
 
2718
        store = self.get_store(self)
 
2719
        store._load_from_string('[baz]\nfoo=bar')
 
2720
        sections = list(store.get_sections())
 
2721
        self.assertLength(1, sections)
 
2722
        self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
 
2723
 
 
2724
    def test_load_from_string_fails_for_non_empty_store(self):
 
2725
        store = self.get_store(self)
 
2726
        store._load_from_string('foo=bar')
 
2727
        self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
 
2728
 
 
2729
 
 
2730
class TestStoreQuoting(TestStore):
 
2731
 
 
2732
    scenarios = [(key, {'get_store': builder}) for key, builder
 
2733
                 in config.test_store_builder_registry.iteritems()]
 
2734
 
 
2735
    def setUp(self):
 
2736
        super(TestStoreQuoting, self).setUp()
 
2737
        self.store = self.get_store(self)
 
2738
        # We need a loaded store but any content will do
 
2739
        self.store._load_from_string('')
 
2740
 
 
2741
    def assertIdempotent(self, s):
 
2742
        """Assert that quoting an unquoted string is a no-op and vice-versa.
 
2743
 
 
2744
        What matters here is that option values, as they appear in a store, can
 
2745
        be safely round-tripped out of the store and back.
 
2746
 
 
2747
        :param s: A string, quoted if required.
 
2748
        """
 
2749
        self.assertEquals(s, self.store.quote(self.store.unquote(s)))
 
2750
        self.assertEquals(s, self.store.unquote(self.store.quote(s)))
 
2751
 
 
2752
    def test_empty_string(self):
 
2753
        if isinstance(self.store, config.IniFileStore):
 
2754
            # configobj._quote doesn't handle empty values
 
2755
            self.assertRaises(AssertionError,
 
2756
                              self.assertIdempotent, '')
 
2757
        else:
 
2758
            self.assertIdempotent('')
 
2759
        # But quoted empty strings are ok
 
2760
        self.assertIdempotent('""')
 
2761
 
 
2762
    def test_embedded_spaces(self):
 
2763
        self.assertIdempotent('" a b c "')
 
2764
 
 
2765
    def test_embedded_commas(self):
 
2766
        self.assertIdempotent('" a , b c "')
 
2767
 
 
2768
    def test_simple_comma(self):
 
2769
        if isinstance(self.store, config.IniFileStore):
 
2770
            # configobj requires that lists are special-cased
 
2771
           self.assertRaises(AssertionError,
 
2772
                             self.assertIdempotent, ',')
 
2773
        else:
 
2774
            self.assertIdempotent(',')
 
2775
        # When a single comma is required, quoting is also required
 
2776
        self.assertIdempotent('","')
 
2777
 
 
2778
    def test_list(self):
 
2779
        if isinstance(self.store, config.IniFileStore):
 
2780
            # configobj requires that lists are special-cased
 
2781
            self.assertRaises(AssertionError,
 
2782
                              self.assertIdempotent, 'a,b')
 
2783
        else:
 
2784
            self.assertIdempotent('a,b')
 
2785
 
 
2786
 
 
2787
class TestDictFromStore(tests.TestCase):
 
2788
 
 
2789
    def test_unquote_not_string(self):
 
2790
        conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
 
2791
        value = conf.get('a_section')
 
2792
        # Urgh, despite 'conf' asking for the no-name section, we get the
 
2793
        # content of another section as a dict o_O
 
2794
        self.assertEquals({'a': '1'}, value)
 
2795
        unquoted = conf.store.unquote(value)
 
2796
        # Which cannot be unquoted but shouldn't crash either (the use cases
 
2797
        # are getting the value or displaying it. In the later case, '%s' will
 
2798
        # do).
 
2799
        self.assertEquals({'a': '1'}, unquoted)
 
2800
        self.assertEquals("{u'a': u'1'}", '%s' % (unquoted,))
 
2801
 
 
2802
 
 
2803
class TestIniFileStoreContent(tests.TestCaseWithTransport):
 
2804
    """Simulate loading a config store with content of various encodings.
 
2805
 
 
2806
    All files produced by bzr are in utf8 content.
 
2807
 
 
2808
    Users may modify them manually and end up with a file that can't be
 
2809
    loaded. We need to issue proper error messages in this case.
 
2810
    """
 
2811
 
 
2812
    invalid_utf8_char = '\xff'
 
2813
 
 
2814
    def test_load_utf8(self):
 
2815
        """Ensure we can load an utf8-encoded file."""
 
2816
        t = self.get_transport()
 
2817
        # From http://pad.lv/799212
 
2818
        unicode_user = u'b\N{Euro Sign}ar'
 
2819
        unicode_content = u'user=%s' % (unicode_user,)
 
2820
        utf8_content = unicode_content.encode('utf8')
 
2821
        # Store the raw content in the config file
 
2822
        t.put_bytes('foo.conf', utf8_content)
 
2823
        store = config.TransportIniFileStore(t, 'foo.conf')
 
2824
        store.load()
 
2825
        stack = config.Stack([store.get_sections], store)
 
2826
        self.assertEquals(unicode_user, stack.get('user'))
 
2827
 
 
2828
    def test_load_non_ascii(self):
 
2829
        """Ensure we display a proper error on non-ascii, non utf-8 content."""
 
2830
        t = self.get_transport()
 
2831
        t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
 
2832
        store = config.TransportIniFileStore(t, 'foo.conf')
 
2833
        self.assertRaises(errors.ConfigContentError, store.load)
 
2834
 
 
2835
    def test_load_erroneous_content(self):
 
2836
        """Ensure we display a proper error on content that can't be parsed."""
 
2837
        t = self.get_transport()
 
2838
        t.put_bytes('foo.conf', '[open_section\n')
 
2839
        store = config.TransportIniFileStore(t, 'foo.conf')
 
2840
        self.assertRaises(errors.ParseConfigError, store.load)
 
2841
 
 
2842
    def test_load_permission_denied(self):
 
2843
        """Ensure we get warned when trying to load an inaccessible file."""
 
2844
        warnings = []
 
2845
        def warning(*args):
 
2846
            warnings.append(args[0] % args[1:])
 
2847
        self.overrideAttr(trace, 'warning', warning)
 
2848
 
 
2849
        t = self.get_transport()
 
2850
 
 
2851
        def get_bytes(relpath):
 
2852
            raise errors.PermissionDenied(relpath, "")
 
2853
        t.get_bytes = get_bytes
 
2854
        store = config.TransportIniFileStore(t, 'foo.conf')
 
2855
        self.assertRaises(errors.PermissionDenied, store.load)
 
2856
        self.assertEquals(
 
2857
            warnings,
 
2858
            [u'Permission denied while trying to load configuration store %s.'
 
2859
             % store.external_url()])
 
2860
 
 
2861
 
 
2862
class TestIniConfigContent(tests.TestCaseWithTransport):
 
2863
    """Simulate loading a IniBasedConfig with content of various encodings.
 
2864
 
 
2865
    All files produced by bzr are in utf8 content.
 
2866
 
 
2867
    Users may modify them manually and end up with a file that can't be
 
2868
    loaded. We need to issue proper error messages in this case.
 
2869
    """
 
2870
 
 
2871
    invalid_utf8_char = '\xff'
 
2872
 
 
2873
    def test_load_utf8(self):
 
2874
        """Ensure we can load an utf8-encoded file."""
 
2875
        # From http://pad.lv/799212
 
2876
        unicode_user = u'b\N{Euro Sign}ar'
 
2877
        unicode_content = u'user=%s' % (unicode_user,)
 
2878
        utf8_content = unicode_content.encode('utf8')
 
2879
        # Store the raw content in the config file
 
2880
        with open('foo.conf', 'wb') as f:
 
2881
            f.write(utf8_content)
 
2882
        conf = config.IniBasedConfig(file_name='foo.conf')
 
2883
        self.assertEquals(unicode_user, conf.get_user_option('user'))
 
2884
 
 
2885
    def test_load_badly_encoded_content(self):
 
2886
        """Ensure we display a proper error on non-ascii, non utf-8 content."""
 
2887
        with open('foo.conf', 'wb') as f:
 
2888
            f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
 
2889
        conf = config.IniBasedConfig(file_name='foo.conf')
 
2890
        self.assertRaises(errors.ConfigContentError, conf._get_parser)
 
2891
 
 
2892
    def test_load_erroneous_content(self):
 
2893
        """Ensure we display a proper error on content that can't be parsed."""
 
2894
        with open('foo.conf', 'wb') as f:
 
2895
            f.write('[open_section\n')
 
2896
        conf = config.IniBasedConfig(file_name='foo.conf')
 
2897
        self.assertRaises(errors.ParseConfigError, conf._get_parser)
 
2898
 
 
2899
 
 
2900
class TestMutableStore(TestStore):
 
2901
 
 
2902
    scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
 
2903
                 in config.test_store_builder_registry.iteritems()]
 
2904
 
 
2905
    def setUp(self):
 
2906
        super(TestMutableStore, self).setUp()
 
2907
        self.transport = self.get_transport()
 
2908
 
 
2909
    def has_store(self, store):
 
2910
        store_basename = urlutils.relative_url(self.transport.external_url(),
 
2911
                                               store.external_url())
 
2912
        return self.transport.has(store_basename)
 
2913
 
 
2914
    def test_save_empty_creates_no_file(self):
 
2915
        # FIXME: There should be a better way than relying on the test
 
2916
        # parametrization to identify branch.conf -- vila 2011-0526
 
2917
        if self.store_id in ('branch', 'remote_branch'):
 
2918
            raise tests.TestNotApplicable(
 
2919
                'branch.conf is *always* created when a branch is initialized')
 
2920
        store = self.get_store(self)
 
2921
        store.save()
 
2922
        self.assertEquals(False, self.has_store(store))
 
2923
 
 
2924
    def test_save_emptied_succeeds(self):
 
2925
        store = self.get_store(self)
 
2926
        store._load_from_string('foo=bar\n')
 
2927
        # FIXME: There should be a better way than relying on the test
 
2928
        # parametrization to identify branch.conf -- vila 2011-0526
 
2929
        if self.store_id in ('branch', 'remote_branch'):
 
2930
            # branch stores requires write locked branches
 
2931
            self.addCleanup(store.branch.lock_write().unlock)
 
2932
        section = store.get_mutable_section(None)
 
2933
        section.remove('foo')
 
2934
        store.save()
 
2935
        self.assertEquals(True, self.has_store(store))
 
2936
        modified_store = self.get_store(self)
 
2937
        sections = list(modified_store.get_sections())
 
2938
        self.assertLength(0, sections)
 
2939
 
 
2940
    def test_save_with_content_succeeds(self):
 
2941
        # FIXME: There should be a better way than relying on the test
 
2942
        # parametrization to identify branch.conf -- vila 2011-0526
 
2943
        if self.store_id in ('branch', 'remote_branch'):
 
2944
            raise tests.TestNotApplicable(
 
2945
                'branch.conf is *always* created when a branch is initialized')
 
2946
        store = self.get_store(self)
 
2947
        store._load_from_string('foo=bar\n')
 
2948
        self.assertEquals(False, self.has_store(store))
 
2949
        store.save()
 
2950
        self.assertEquals(True, self.has_store(store))
 
2951
        modified_store = self.get_store(self)
 
2952
        sections = list(modified_store.get_sections())
 
2953
        self.assertLength(1, sections)
 
2954
        self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
 
2955
 
 
2956
    def test_set_option_in_empty_store(self):
 
2957
        store = self.get_store(self)
 
2958
        # FIXME: There should be a better way than relying on the test
 
2959
        # parametrization to identify branch.conf -- vila 2011-0526
 
2960
        if self.store_id in ('branch', 'remote_branch'):
 
2961
            # branch stores requires write locked branches
 
2962
            self.addCleanup(store.branch.lock_write().unlock)
 
2963
        section = store.get_mutable_section(None)
 
2964
        section.set('foo', 'bar')
 
2965
        store.save()
 
2966
        modified_store = self.get_store(self)
 
2967
        sections = list(modified_store.get_sections())
 
2968
        self.assertLength(1, sections)
 
2969
        self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
 
2970
 
 
2971
    def test_set_option_in_default_section(self):
 
2972
        store = self.get_store(self)
 
2973
        store._load_from_string('')
 
2974
        # FIXME: There should be a better way than relying on the test
 
2975
        # parametrization to identify branch.conf -- vila 2011-0526
 
2976
        if self.store_id in ('branch', 'remote_branch'):
 
2977
            # branch stores requires write locked branches
 
2978
            self.addCleanup(store.branch.lock_write().unlock)
 
2979
        section = store.get_mutable_section(None)
 
2980
        section.set('foo', 'bar')
 
2981
        store.save()
 
2982
        modified_store = self.get_store(self)
 
2983
        sections = list(modified_store.get_sections())
 
2984
        self.assertLength(1, sections)
 
2985
        self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
 
2986
 
 
2987
    def test_set_option_in_named_section(self):
 
2988
        store = self.get_store(self)
 
2989
        store._load_from_string('')
 
2990
        # FIXME: There should be a better way than relying on the test
 
2991
        # parametrization to identify branch.conf -- vila 2011-0526
 
2992
        if self.store_id in ('branch', 'remote_branch'):
 
2993
            # branch stores requires write locked branches
 
2994
            self.addCleanup(store.branch.lock_write().unlock)
 
2995
        section = store.get_mutable_section('baz')
 
2996
        section.set('foo', 'bar')
 
2997
        store.save()
 
2998
        modified_store = self.get_store(self)
 
2999
        sections = list(modified_store.get_sections())
 
3000
        self.assertLength(1, sections)
 
3001
        self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
 
3002
 
 
3003
    def test_load_hook(self):
 
3004
        # First, we need to ensure that the store exists
 
3005
        store = self.get_store(self)
 
3006
        # FIXME: There should be a better way than relying on the test
 
3007
        # parametrization to identify branch.conf -- vila 2011-0526
 
3008
        if self.store_id in ('branch', 'remote_branch'):
 
3009
            # branch stores requires write locked branches
 
3010
            self.addCleanup(store.branch.lock_write().unlock)
 
3011
        section = store.get_mutable_section('baz')
 
3012
        section.set('foo', 'bar')
 
3013
        store.save()
 
3014
        # Now we can try to load it
 
3015
        store = self.get_store(self)
 
3016
        calls = []
 
3017
        def hook(*args):
 
3018
            calls.append(args)
 
3019
        config.ConfigHooks.install_named_hook('load', hook, None)
 
3020
        self.assertLength(0, calls)
 
3021
        store.load()
 
3022
        self.assertLength(1, calls)
 
3023
        self.assertEquals((store,), calls[0])
 
3024
 
 
3025
    def test_save_hook(self):
 
3026
        calls = []
 
3027
        def hook(*args):
 
3028
            calls.append(args)
 
3029
        config.ConfigHooks.install_named_hook('save', hook, None)
 
3030
        self.assertLength(0, calls)
 
3031
        store = self.get_store(self)
 
3032
        # FIXME: There should be a better way than relying on the test
 
3033
        # parametrization to identify branch.conf -- vila 2011-0526
 
3034
        if self.store_id in ('branch', 'remote_branch'):
 
3035
            # branch stores requires write locked branches
 
3036
            self.addCleanup(store.branch.lock_write().unlock)
 
3037
        section = store.get_mutable_section('baz')
 
3038
        section.set('foo', 'bar')
 
3039
        store.save()
 
3040
        self.assertLength(1, calls)
 
3041
        self.assertEquals((store,), calls[0])
 
3042
 
 
3043
    def test_set_mark_dirty(self):
 
3044
        stack = config.MemoryStack('')
 
3045
        self.assertLength(0, stack.store.dirty_sections)
 
3046
        stack.set('foo', 'baz')
 
3047
        self.assertLength(1, stack.store.dirty_sections)
 
3048
        self.assertTrue(stack.store._need_saving())
 
3049
 
 
3050
    def test_remove_mark_dirty(self):
 
3051
        stack = config.MemoryStack('foo=bar')
 
3052
        self.assertLength(0, stack.store.dirty_sections)
 
3053
        stack.remove('foo')
 
3054
        self.assertLength(1, stack.store.dirty_sections)
 
3055
        self.assertTrue(stack.store._need_saving())
 
3056
 
 
3057
 
 
3058
class TestStoreSaveChanges(tests.TestCaseWithTransport):
 
3059
    """Tests that config changes are kept in memory and saved on-demand."""
 
3060
 
 
3061
    def setUp(self):
 
3062
        super(TestStoreSaveChanges, self).setUp()
 
3063
        self.transport = self.get_transport()
 
3064
        # Most of the tests involve two stores pointing to the same persistent
 
3065
        # storage to observe the effects of concurrent changes
 
3066
        self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
 
3067
        self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
 
3068
        self.warnings = []
 
3069
        def warning(*args):
 
3070
            self.warnings.append(args[0] % args[1:])
 
3071
        self.overrideAttr(trace, 'warning', warning)
 
3072
 
 
3073
    def has_store(self, store):
 
3074
        store_basename = urlutils.relative_url(self.transport.external_url(),
 
3075
                                               store.external_url())
 
3076
        return self.transport.has(store_basename)
 
3077
 
 
3078
    def get_stack(self, store):
 
3079
        # Any stack will do as long as it uses the right store, just a single
 
3080
        # no-name section is enough
 
3081
        return config.Stack([store.get_sections], store)
 
3082
 
 
3083
    def test_no_changes_no_save(self):
 
3084
        s = self.get_stack(self.st1)
 
3085
        s.store.save_changes()
 
3086
        self.assertEquals(False, self.has_store(self.st1))
 
3087
 
 
3088
    def test_unrelated_concurrent_update(self):
 
3089
        s1 = self.get_stack(self.st1)
 
3090
        s2 = self.get_stack(self.st2)
 
3091
        s1.set('foo', 'bar')
 
3092
        s2.set('baz', 'quux')
 
3093
        s1.store.save()
 
3094
        # Changes don't propagate magically
 
3095
        self.assertEquals(None, s1.get('baz'))
 
3096
        s2.store.save_changes()
 
3097
        self.assertEquals('quux', s2.get('baz'))
 
3098
        # Changes are acquired when saving
 
3099
        self.assertEquals('bar', s2.get('foo'))
 
3100
        # Since there is no overlap, no warnings are emitted
 
3101
        self.assertLength(0, self.warnings)
 
3102
 
 
3103
    def test_concurrent_update_modified(self):
 
3104
        s1 = self.get_stack(self.st1)
 
3105
        s2 = self.get_stack(self.st2)
 
3106
        s1.set('foo', 'bar')
 
3107
        s2.set('foo', 'baz')
 
3108
        s1.store.save()
 
3109
        # Last speaker wins
 
3110
        s2.store.save_changes()
 
3111
        self.assertEquals('baz', s2.get('foo'))
 
3112
        # But the user get a warning
 
3113
        self.assertLength(1, self.warnings)
 
3114
        warning = self.warnings[0]
 
3115
        self.assertStartsWith(warning, 'Option foo in section None')
 
3116
        self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
 
3117
                            ' The baz value will be saved.')
 
3118
 
 
3119
    def test_concurrent_deletion(self):
 
3120
        self.st1._load_from_string('foo=bar')
 
3121
        self.st1.save()
 
3122
        s1 = self.get_stack(self.st1)
 
3123
        s2 = self.get_stack(self.st2)
 
3124
        s1.remove('foo')
 
3125
        s2.remove('foo')
 
3126
        s1.store.save_changes()
 
3127
        # No warning yet
 
3128
        self.assertLength(0, self.warnings)
 
3129
        s2.store.save_changes()
 
3130
        # Now we get one
 
3131
        self.assertLength(1, self.warnings)
 
3132
        warning = self.warnings[0]
 
3133
        self.assertStartsWith(warning, 'Option foo in section None')
 
3134
        self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
 
3135
                            ' The <DELETED> value will be saved.')
 
3136
 
 
3137
 
 
3138
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
 
3139
 
 
3140
    def get_store(self):
 
3141
        return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
 
3142
 
 
3143
    def test_get_quoted_string(self):
 
3144
        store = self.get_store()
 
3145
        store._load_from_string('foo= " abc "')
 
3146
        stack = config.Stack([store.get_sections])
 
3147
        self.assertEquals(' abc ', stack.get('foo'))
 
3148
 
 
3149
    def test_set_quoted_string(self):
 
3150
        store = self.get_store()
 
3151
        stack = config.Stack([store.get_sections], store)
 
3152
        stack.set('foo', ' a b c ')
 
3153
        store.save()
 
3154
        self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
 
3155
 
 
3156
 
 
3157
class TestTransportIniFileStore(TestStore):
 
3158
 
 
3159
    def test_loading_unknown_file_fails(self):
 
3160
        store = config.TransportIniFileStore(self.get_transport(),
 
3161
            'I-do-not-exist')
 
3162
        self.assertRaises(errors.NoSuchFile, store.load)
 
3163
 
 
3164
    def test_invalid_content(self):
 
3165
        store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
 
3166
        self.assertEquals(False, store.is_loaded())
 
3167
        exc = self.assertRaises(
 
3168
            errors.ParseConfigError, store._load_from_string,
 
3169
            'this is invalid !')
 
3170
        self.assertEndsWith(exc.filename, 'foo.conf')
 
3171
        # And the load failed
 
3172
        self.assertEquals(False, store.is_loaded())
 
3173
 
 
3174
    def test_get_embedded_sections(self):
 
3175
        # A more complicated example (which also shows that section names and
 
3176
        # option names share the same name space...)
 
3177
        # FIXME: This should be fixed by forbidding dicts as values ?
 
3178
        # -- vila 2011-04-05
 
3179
        store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
 
3180
        store._load_from_string('''
 
3181
foo=bar
 
3182
l=1,2
 
3183
[DEFAULT]
 
3184
foo_in_DEFAULT=foo_DEFAULT
 
3185
[bar]
 
3186
foo_in_bar=barbar
 
3187
[baz]
 
3188
foo_in_baz=barbaz
 
3189
[[qux]]
 
3190
foo_in_qux=quux
 
3191
''')
 
3192
        sections = list(store.get_sections())
 
3193
        self.assertLength(4, sections)
 
3194
        # The default section has no name.
 
3195
        # List values are provided as strings and need to be explicitly
 
3196
        # converted by specifying from_unicode=list_from_store at option
 
3197
        # registration
 
3198
        self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
 
3199
                                  sections[0])
 
3200
        self.assertSectionContent(
 
3201
            ('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
 
3202
        self.assertSectionContent(
 
3203
            ('bar', {'foo_in_bar': 'barbar'}), sections[2])
 
3204
        # sub sections are provided as embedded dicts.
 
3205
        self.assertSectionContent(
 
3206
            ('baz', {'foo_in_baz': 'barbaz', 'qux': {'foo_in_qux': 'quux'}}),
 
3207
            sections[3])
 
3208
 
 
3209
 
 
3210
class TestLockableIniFileStore(TestStore):
 
3211
 
 
3212
    def test_create_store_in_created_dir(self):
 
3213
        self.assertPathDoesNotExist('dir')
 
3214
        t = self.get_transport('dir/subdir')
 
3215
        store = config.LockableIniFileStore(t, 'foo.conf')
 
3216
        store.get_mutable_section(None).set('foo', 'bar')
 
3217
        store.save()
 
3218
        self.assertPathExists('dir/subdir')
 
3219
 
 
3220
 
 
3221
class TestConcurrentStoreUpdates(TestStore):
 
3222
    """Test that Stores properly handle conccurent updates.
 
3223
 
 
3224
    New Store implementation may fail some of these tests but until such
 
3225
    implementations exist it's hard to properly filter them from the scenarios
 
3226
    applied here. If you encounter such a case, contact the bzr devs.
 
3227
    """
 
3228
 
 
3229
    scenarios = [(key, {'get_stack': builder}) for key, builder
 
3230
                 in config.test_stack_builder_registry.iteritems()]
 
3231
 
 
3232
    def setUp(self):
 
3233
        super(TestConcurrentStoreUpdates, self).setUp()
 
3234
        self.stack = self.get_stack(self)
 
3235
        if not isinstance(self.stack, config._CompatibleStack):
 
3236
            raise tests.TestNotApplicable(
 
3237
                '%s is not meant to be compatible with the old config design'
 
3238
                % (self.stack,))
 
3239
        self.stack.set('one', '1')
 
3240
        self.stack.set('two', '2')
 
3241
        # Flush the store
 
3242
        self.stack.store.save()
 
3243
 
 
3244
    def test_simple_read_access(self):
 
3245
        self.assertEquals('1', self.stack.get('one'))
 
3246
 
 
3247
    def test_simple_write_access(self):
 
3248
        self.stack.set('one', 'one')
 
3249
        self.assertEquals('one', self.stack.get('one'))
 
3250
 
 
3251
    def test_listen_to_the_last_speaker(self):
 
3252
        c1 = self.stack
 
3253
        c2 = self.get_stack(self)
 
3254
        c1.set('one', 'ONE')
 
3255
        c2.set('two', 'TWO')
 
3256
        self.assertEquals('ONE', c1.get('one'))
 
3257
        self.assertEquals('TWO', c2.get('two'))
 
3258
        # The second update respect the first one
 
3259
        self.assertEquals('ONE', c2.get('one'))
 
3260
 
 
3261
    def test_last_speaker_wins(self):
 
3262
        # If the same config is not shared, the same variable modified twice
 
3263
        # can only see a single result.
 
3264
        c1 = self.stack
 
3265
        c2 = self.get_stack(self)
 
3266
        c1.set('one', 'c1')
 
3267
        c2.set('one', 'c2')
 
3268
        self.assertEquals('c2', c2.get('one'))
 
3269
        # The first modification is still available until another refresh
 
3270
        # occur
 
3271
        self.assertEquals('c1', c1.get('one'))
 
3272
        c1.set('two', 'done')
 
3273
        self.assertEquals('c2', c1.get('one'))
 
3274
 
 
3275
    def test_writes_are_serialized(self):
 
3276
        c1 = self.stack
 
3277
        c2 = self.get_stack(self)
 
3278
 
 
3279
        # We spawn a thread that will pause *during* the config saving.
 
3280
        before_writing = threading.Event()
 
3281
        after_writing = threading.Event()
 
3282
        writing_done = threading.Event()
 
3283
        c1_save_without_locking_orig = c1.store.save_without_locking
 
3284
        def c1_save_without_locking():
 
3285
            before_writing.set()
 
3286
            c1_save_without_locking_orig()
 
3287
            # The lock is held. We wait for the main thread to decide when to
 
3288
            # continue
 
3289
            after_writing.wait()
 
3290
        c1.store.save_without_locking = c1_save_without_locking
 
3291
        def c1_set():
 
3292
            c1.set('one', 'c1')
 
3293
            writing_done.set()
 
3294
        t1 = threading.Thread(target=c1_set)
 
3295
        # Collect the thread after the test
 
3296
        self.addCleanup(t1.join)
 
3297
        # Be ready to unblock the thread if the test goes wrong
 
3298
        self.addCleanup(after_writing.set)
 
3299
        t1.start()
 
3300
        before_writing.wait()
 
3301
        self.assertRaises(errors.LockContention,
 
3302
                          c2.set, 'one', 'c2')
 
3303
        self.assertEquals('c1', c1.get('one'))
 
3304
        # Let the lock be released
 
3305
        after_writing.set()
 
3306
        writing_done.wait()
 
3307
        c2.set('one', 'c2')
 
3308
        self.assertEquals('c2', c2.get('one'))
 
3309
 
 
3310
    def test_read_while_writing(self):
 
3311
       c1 = self.stack
 
3312
       # We spawn a thread that will pause *during* the write
 
3313
       ready_to_write = threading.Event()
 
3314
       do_writing = threading.Event()
 
3315
       writing_done = threading.Event()
 
3316
       # We override the _save implementation so we know the store is locked
 
3317
       c1_save_without_locking_orig = c1.store.save_without_locking
 
3318
       def c1_save_without_locking():
 
3319
           ready_to_write.set()
 
3320
           # The lock is held. We wait for the main thread to decide when to
 
3321
           # continue
 
3322
           do_writing.wait()
 
3323
           c1_save_without_locking_orig()
 
3324
           writing_done.set()
 
3325
       c1.store.save_without_locking = c1_save_without_locking
 
3326
       def c1_set():
 
3327
           c1.set('one', 'c1')
 
3328
       t1 = threading.Thread(target=c1_set)
 
3329
       # Collect the thread after the test
 
3330
       self.addCleanup(t1.join)
 
3331
       # Be ready to unblock the thread if the test goes wrong
 
3332
       self.addCleanup(do_writing.set)
 
3333
       t1.start()
 
3334
       # Ensure the thread is ready to write
 
3335
       ready_to_write.wait()
 
3336
       self.assertEquals('c1', c1.get('one'))
 
3337
       # If we read during the write, we get the old value
 
3338
       c2 = self.get_stack(self)
 
3339
       self.assertEquals('1', c2.get('one'))
 
3340
       # Let the writing occur and ensure it occurred
 
3341
       do_writing.set()
 
3342
       writing_done.wait()
 
3343
       # Now we get the updated value
 
3344
       c3 = self.get_stack(self)
 
3345
       self.assertEquals('c1', c3.get('one'))
 
3346
 
 
3347
    # FIXME: It may be worth looking into removing the lock dir when it's not
 
3348
    # needed anymore and look at possible fallouts for concurrent lockers. This
 
3349
    # will matter if/when we use config files outside of bazaar directories
 
3350
    # (.bazaar or .bzr) -- vila 20110-04-111
 
3351
 
 
3352
 
 
3353
class TestSectionMatcher(TestStore):
 
3354
 
 
3355
    scenarios = [('location', {'matcher': config.LocationMatcher}),
 
3356
                 ('id', {'matcher': config.NameMatcher}),]
 
3357
 
 
3358
    def setUp(self):
 
3359
        super(TestSectionMatcher, self).setUp()
 
3360
        # Any simple store is good enough
 
3361
        self.get_store = config.test_store_builder_registry.get('configobj')
 
3362
 
 
3363
    def test_no_matches_for_empty_stores(self):
 
3364
        store = self.get_store(self)
 
3365
        store._load_from_string('')
 
3366
        matcher = self.matcher(store, '/bar')
 
3367
        self.assertEquals([], list(matcher.get_sections()))
 
3368
 
 
3369
    def test_build_doesnt_load_store(self):
 
3370
        store = self.get_store(self)
 
3371
        matcher = self.matcher(store, '/bar')
 
3372
        self.assertFalse(store.is_loaded())
 
3373
 
 
3374
 
 
3375
class TestLocationSection(tests.TestCase):
 
3376
 
 
3377
    def get_section(self, options, extra_path):
 
3378
        section = config.Section('foo', options)
 
3379
        return config.LocationSection(section, extra_path)
 
3380
 
 
3381
    def test_simple_option(self):
 
3382
        section = self.get_section({'foo': 'bar'}, '')
 
3383
        self.assertEquals('bar', section.get('foo'))
 
3384
 
 
3385
    def test_option_with_extra_path(self):
 
3386
        section = self.get_section({'foo': 'bar', 'foo:policy': 'appendpath'},
 
3387
                                   'baz')
 
3388
        self.assertEquals('bar/baz', section.get('foo'))
 
3389
 
 
3390
    def test_invalid_policy(self):
 
3391
        section = self.get_section({'foo': 'bar', 'foo:policy': 'die'},
 
3392
                                   'baz')
 
3393
        # invalid policies are ignored
 
3394
        self.assertEquals('bar', section.get('foo'))
 
3395
 
 
3396
 
 
3397
class TestLocationMatcher(TestStore):
 
3398
 
 
3399
    def setUp(self):
 
3400
        super(TestLocationMatcher, self).setUp()
 
3401
        # Any simple store is good enough
 
3402
        self.get_store = config.test_store_builder_registry.get('configobj')
 
3403
 
 
3404
    def test_unrelated_section_excluded(self):
 
3405
        store = self.get_store(self)
 
3406
        store._load_from_string('''
 
3407
[/foo]
 
3408
section=/foo
 
3409
[/foo/baz]
 
3410
section=/foo/baz
 
3411
[/foo/bar]
 
3412
section=/foo/bar
 
3413
[/foo/bar/baz]
 
3414
section=/foo/bar/baz
 
3415
[/quux/quux]
 
3416
section=/quux/quux
 
3417
''')
 
3418
        self.assertEquals(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
 
3419
                           '/quux/quux'],
 
3420
                          [section.id for _, section in store.get_sections()])
 
3421
        matcher = config.LocationMatcher(store, '/foo/bar/quux')
 
3422
        sections = [section for _, section in matcher.get_sections()]
 
3423
        self.assertEquals(['/foo/bar', '/foo'],
 
3424
                          [section.id for section in sections])
 
3425
        self.assertEquals(['quux', 'bar/quux'],
 
3426
                          [section.extra_path for section in sections])
 
3427
 
 
3428
    def test_more_specific_sections_first(self):
 
3429
        store = self.get_store(self)
 
3430
        store._load_from_string('''
 
3431
[/foo]
 
3432
section=/foo
 
3433
[/foo/bar]
 
3434
section=/foo/bar
 
3435
''')
 
3436
        self.assertEquals(['/foo', '/foo/bar'],
 
3437
                          [section.id for _, section in store.get_sections()])
 
3438
        matcher = config.LocationMatcher(store, '/foo/bar/baz')
 
3439
        sections = [section for _, section in matcher.get_sections()]
 
3440
        self.assertEquals(['/foo/bar', '/foo'],
 
3441
                          [section.id for section in sections])
 
3442
        self.assertEquals(['baz', 'bar/baz'],
 
3443
                          [section.extra_path for section in sections])
 
3444
 
 
3445
    def test_appendpath_in_no_name_section(self):
 
3446
        # It's a bit weird to allow appendpath in a no-name section, but
 
3447
        # someone may found a use for it
 
3448
        store = self.get_store(self)
 
3449
        store._load_from_string('''
 
3450
foo=bar
 
3451
foo:policy = appendpath
 
3452
''')
 
3453
        matcher = config.LocationMatcher(store, 'dir/subdir')
 
3454
        sections = list(matcher.get_sections())
 
3455
        self.assertLength(1, sections)
 
3456
        self.assertEquals('bar/dir/subdir', sections[0][1].get('foo'))
 
3457
 
 
3458
    def test_file_urls_are_normalized(self):
 
3459
        store = self.get_store(self)
 
3460
        if sys.platform == 'win32':
 
3461
            expected_url = 'file:///C:/dir/subdir'
 
3462
            expected_location = 'C:/dir/subdir'
 
3463
        else:
 
3464
            expected_url = 'file:///dir/subdir'
 
3465
            expected_location = '/dir/subdir'
 
3466
        matcher = config.LocationMatcher(store, expected_url)
 
3467
        self.assertEquals(expected_location, matcher.location)
 
3468
 
 
3469
 
 
3470
class TestStartingPathMatcher(TestStore):
 
3471
 
 
3472
    def setUp(self):
 
3473
        super(TestStartingPathMatcher, self).setUp()
 
3474
        # Any simple store is good enough
 
3475
        self.store = config.IniFileStore()
 
3476
 
 
3477
    def assertSectionIDs(self, expected, location, content):
 
3478
        self.store._load_from_string(content)
 
3479
        matcher = config.StartingPathMatcher(self.store, location)
 
3480
        sections = list(matcher.get_sections())
 
3481
        self.assertLength(len(expected), sections)
 
3482
        self.assertEqual(expected, [section.id for _, section in sections])
 
3483
        return sections
 
3484
 
 
3485
    def test_empty(self):
 
3486
        self.assertSectionIDs([], self.get_url(), '')
 
3487
 
 
3488
    def test_url_vs_local_paths(self):
 
3489
        # The matcher location is an url and the section names are local paths
 
3490
        sections = self.assertSectionIDs(['/foo/bar', '/foo'],
 
3491
                                         'file:///foo/bar/baz', '''\
 
3492
[/foo]
 
3493
[/foo/bar]
 
3494
''')
 
3495
 
 
3496
    def test_local_path_vs_url(self):
 
3497
        # The matcher location is a local path and the section names are urls
 
3498
        sections = self.assertSectionIDs(['file:///foo/bar', 'file:///foo'],
 
3499
                                         '/foo/bar/baz', '''\
 
3500
[file:///foo]
 
3501
[file:///foo/bar]
 
3502
''')
 
3503
 
 
3504
 
 
3505
    def test_no_name_section_included_when_present(self):
 
3506
        # Note that other tests will cover the case where the no-name section
 
3507
        # is empty and as such, not included.
 
3508
        sections = self.assertSectionIDs(['/foo/bar', '/foo', None],
 
3509
                                         '/foo/bar/baz', '''\
 
3510
option = defined so the no-name section exists
 
3511
[/foo]
 
3512
[/foo/bar]
 
3513
''')
 
3514
        self.assertEquals(['baz', 'bar/baz', '/foo/bar/baz'],
 
3515
                          [s.locals['relpath'] for _, s in sections])
 
3516
 
 
3517
    def test_order_reversed(self):
 
3518
        self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
 
3519
[/foo]
 
3520
[/foo/bar]
 
3521
''')
 
3522
 
 
3523
    def test_unrelated_section_excluded(self):
 
3524
        self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
 
3525
[/foo]
 
3526
[/foo/qux]
 
3527
[/foo/bar]
 
3528
''')
 
3529
 
 
3530
    def test_glob_included(self):
 
3531
        sections = self.assertSectionIDs(['/foo/*/baz', '/foo/b*', '/foo'],
 
3532
                                         '/foo/bar/baz', '''\
 
3533
[/foo]
 
3534
[/foo/qux]
 
3535
[/foo/b*]
 
3536
[/foo/*/baz]
 
3537
''')
 
3538
        # Note that 'baz' as a relpath for /foo/b* is not fully correct, but
 
3539
        # nothing really is... as far using {relpath} to append it to something
 
3540
        # else, this seems good enough though.
 
3541
        self.assertEquals(['', 'baz', 'bar/baz'],
 
3542
                          [s.locals['relpath'] for _, s in sections])
 
3543
 
 
3544
    def test_respect_order(self):
 
3545
        self.assertSectionIDs(['/foo', '/foo/b*', '/foo/*/baz'],
 
3546
                              '/foo/bar/baz', '''\
 
3547
[/foo/*/baz]
 
3548
[/foo/qux]
 
3549
[/foo/b*]
 
3550
[/foo]
 
3551
''')
 
3552
 
 
3553
 
 
3554
class TestNameMatcher(TestStore):
 
3555
 
 
3556
    def setUp(self):
 
3557
        super(TestNameMatcher, self).setUp()
 
3558
        self.matcher = config.NameMatcher
 
3559
        # Any simple store is good enough
 
3560
        self.get_store = config.test_store_builder_registry.get('configobj')
 
3561
 
 
3562
    def get_matching_sections(self, name):
 
3563
        store = self.get_store(self)
 
3564
        store._load_from_string('''
 
3565
[foo]
 
3566
option=foo
 
3567
[foo/baz]
 
3568
option=foo/baz
 
3569
[bar]
 
3570
option=bar
 
3571
''')
 
3572
        matcher = self.matcher(store, name)
 
3573
        return list(matcher.get_sections())
 
3574
 
 
3575
    def test_matching(self):
 
3576
        sections = self.get_matching_sections('foo')
 
3577
        self.assertLength(1, sections)
 
3578
        self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
 
3579
 
 
3580
    def test_not_matching(self):
 
3581
        sections = self.get_matching_sections('baz')
 
3582
        self.assertLength(0, sections)
 
3583
 
 
3584
 
 
3585
class TestBaseStackGet(tests.TestCase):
 
3586
 
 
3587
    def setUp(self):
 
3588
        super(TestBaseStackGet, self).setUp()
 
3589
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
3590
 
 
3591
    def test_get_first_definition(self):
 
3592
        store1 = config.IniFileStore()
 
3593
        store1._load_from_string('foo=bar')
 
3594
        store2 = config.IniFileStore()
 
3595
        store2._load_from_string('foo=baz')
 
3596
        conf = config.Stack([store1.get_sections, store2.get_sections])
 
3597
        self.assertEquals('bar', conf.get('foo'))
 
3598
 
 
3599
    def test_get_with_registered_default_value(self):
 
3600
        config.option_registry.register(config.Option('foo', default='bar'))
 
3601
        conf_stack = config.Stack([])
 
3602
        self.assertEquals('bar', conf_stack.get('foo'))
 
3603
 
 
3604
    def test_get_without_registered_default_value(self):
 
3605
        config.option_registry.register(config.Option('foo'))
 
3606
        conf_stack = config.Stack([])
 
3607
        self.assertEquals(None, conf_stack.get('foo'))
 
3608
 
 
3609
    def test_get_without_default_value_for_not_registered(self):
 
3610
        conf_stack = config.Stack([])
 
3611
        self.assertEquals(None, conf_stack.get('foo'))
 
3612
 
 
3613
    def test_get_for_empty_section_callable(self):
 
3614
        conf_stack = config.Stack([lambda : []])
 
3615
        self.assertEquals(None, conf_stack.get('foo'))
 
3616
 
 
3617
    def test_get_for_broken_callable(self):
 
3618
        # Trying to use and invalid callable raises an exception on first use
 
3619
        conf_stack = config.Stack([object])
 
3620
        self.assertRaises(TypeError, conf_stack.get, 'foo')
 
3621
 
 
3622
 
 
3623
class TestStackWithSimpleStore(tests.TestCase):
 
3624
 
 
3625
    def setUp(self):
 
3626
        super(TestStackWithSimpleStore, self).setUp()
 
3627
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
3628
        self.registry = config.option_registry
 
3629
 
 
3630
    def get_conf(self, content=None):
 
3631
        return config.MemoryStack(content)
 
3632
 
 
3633
    def test_override_value_from_env(self):
 
3634
        self.registry.register(
 
3635
            config.Option('foo', default='bar', override_from_env=['FOO']))
 
3636
        self.overrideEnv('FOO', 'quux')
 
3637
        # Env variable provides a default taking over the option one
 
3638
        conf = self.get_conf('foo=store')
 
3639
        self.assertEquals('quux', conf.get('foo'))
 
3640
 
 
3641
    def test_first_override_value_from_env_wins(self):
 
3642
        self.registry.register(
 
3643
            config.Option('foo', default='bar',
 
3644
                          override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
 
3645
        self.overrideEnv('FOO', 'foo')
 
3646
        self.overrideEnv('BAZ', 'baz')
 
3647
        # The first env var set wins
 
3648
        conf = self.get_conf('foo=store')
 
3649
        self.assertEquals('foo', conf.get('foo'))
 
3650
 
 
3651
 
 
3652
class TestMemoryStack(tests.TestCase):
 
3653
 
 
3654
    def test_get(self):
 
3655
        conf = config.MemoryStack('foo=bar')
 
3656
        self.assertEquals('bar', conf.get('foo'))
 
3657
 
 
3658
    def test_set(self):
 
3659
        conf = config.MemoryStack('foo=bar')
 
3660
        conf.set('foo', 'baz')
 
3661
        self.assertEquals('baz', conf.get('foo'))
 
3662
 
 
3663
    def test_no_content(self):
 
3664
        conf = config.MemoryStack()
 
3665
        # No content means no loading
 
3666
        self.assertFalse(conf.store.is_loaded())
 
3667
        self.assertRaises(NotImplementedError, conf.get, 'foo')
 
3668
        # But a content can still be provided
 
3669
        conf.store._load_from_string('foo=bar')
 
3670
        self.assertEquals('bar', conf.get('foo'))
 
3671
 
 
3672
 
 
3673
class TestStackWithTransport(tests.TestCaseWithTransport):
 
3674
 
 
3675
    scenarios = [(key, {'get_stack': builder}) for key, builder
 
3676
                 in config.test_stack_builder_registry.iteritems()]
 
3677
 
 
3678
 
 
3679
class TestConcreteStacks(TestStackWithTransport):
 
3680
 
 
3681
    def test_build_stack(self):
 
3682
        # Just a smoke test to help debug builders
 
3683
        stack = self.get_stack(self)
 
3684
 
 
3685
 
 
3686
class TestStackGet(TestStackWithTransport):
 
3687
 
 
3688
    def setUp(self):
 
3689
        super(TestStackGet, self).setUp()
 
3690
        self.conf = self.get_stack(self)
 
3691
 
 
3692
    def test_get_for_empty_stack(self):
 
3693
        self.assertEquals(None, self.conf.get('foo'))
 
3694
 
 
3695
    def test_get_hook(self):
 
3696
        self.conf.set('foo', 'bar')
 
3697
        calls = []
 
3698
        def hook(*args):
 
3699
            calls.append(args)
 
3700
        config.ConfigHooks.install_named_hook('get', hook, None)
 
3701
        self.assertLength(0, calls)
 
3702
        value = self.conf.get('foo')
 
3703
        self.assertEquals('bar', value)
 
3704
        self.assertLength(1, calls)
 
3705
        self.assertEquals((self.conf, 'foo', 'bar'), calls[0])
 
3706
 
 
3707
 
 
3708
class TestStackGetWithConverter(tests.TestCase):
 
3709
 
 
3710
    def setUp(self):
 
3711
        super(TestStackGetWithConverter, self).setUp()
 
3712
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
3713
        self.registry = config.option_registry
 
3714
 
 
3715
    def get_conf(self, content=None):
 
3716
        return config.MemoryStack(content)
 
3717
 
 
3718
    def register_bool_option(self, name, default=None, default_from_env=None):
 
3719
        b = config.Option(name, help='A boolean.',
 
3720
                          default=default, default_from_env=default_from_env,
 
3721
                          from_unicode=config.bool_from_store)
 
3722
        self.registry.register(b)
 
3723
 
 
3724
    def test_get_default_bool_None(self):
 
3725
        self.register_bool_option('foo')
 
3726
        conf = self.get_conf('')
 
3727
        self.assertEquals(None, conf.get('foo'))
 
3728
 
 
3729
    def test_get_default_bool_True(self):
 
3730
        self.register_bool_option('foo', u'True')
 
3731
        conf = self.get_conf('')
 
3732
        self.assertEquals(True, conf.get('foo'))
 
3733
 
 
3734
    def test_get_default_bool_False(self):
 
3735
        self.register_bool_option('foo', False)
 
3736
        conf = self.get_conf('')
 
3737
        self.assertEquals(False, conf.get('foo'))
 
3738
 
 
3739
    def test_get_default_bool_False_as_string(self):
 
3740
        self.register_bool_option('foo', u'False')
 
3741
        conf = self.get_conf('')
 
3742
        self.assertEquals(False, conf.get('foo'))
 
3743
 
 
3744
    def test_get_default_bool_from_env_converted(self):
 
3745
        self.register_bool_option('foo', u'True', default_from_env=['FOO'])
 
3746
        self.overrideEnv('FOO', 'False')
 
3747
        conf = self.get_conf('')
 
3748
        self.assertEquals(False, conf.get('foo'))
 
3749
 
 
3750
    def test_get_default_bool_when_conversion_fails(self):
 
3751
        self.register_bool_option('foo', default='True')
 
3752
        conf = self.get_conf('foo=invalid boolean')
 
3753
        self.assertEquals(True, conf.get('foo'))
 
3754
 
 
3755
    def register_integer_option(self, name,
 
3756
                                default=None, default_from_env=None):
 
3757
        i = config.Option(name, help='An integer.',
 
3758
                          default=default, default_from_env=default_from_env,
 
3759
                          from_unicode=config.int_from_store)
 
3760
        self.registry.register(i)
 
3761
 
 
3762
    def test_get_default_integer_None(self):
 
3763
        self.register_integer_option('foo')
 
3764
        conf = self.get_conf('')
 
3765
        self.assertEquals(None, conf.get('foo'))
 
3766
 
 
3767
    def test_get_default_integer(self):
 
3768
        self.register_integer_option('foo', 42)
 
3769
        conf = self.get_conf('')
 
3770
        self.assertEquals(42, conf.get('foo'))
 
3771
 
 
3772
    def test_get_default_integer_as_string(self):
 
3773
        self.register_integer_option('foo', u'42')
 
3774
        conf = self.get_conf('')
 
3775
        self.assertEquals(42, conf.get('foo'))
 
3776
 
 
3777
    def test_get_default_integer_from_env(self):
 
3778
        self.register_integer_option('foo', default_from_env=['FOO'])
 
3779
        self.overrideEnv('FOO', '18')
 
3780
        conf = self.get_conf('')
 
3781
        self.assertEquals(18, conf.get('foo'))
 
3782
 
 
3783
    def test_get_default_integer_when_conversion_fails(self):
 
3784
        self.register_integer_option('foo', default='12')
 
3785
        conf = self.get_conf('foo=invalid integer')
 
3786
        self.assertEquals(12, conf.get('foo'))
 
3787
 
 
3788
    def register_list_option(self, name, default=None, default_from_env=None):
 
3789
        l = config.ListOption(name, help='A list.', default=default,
 
3790
                              default_from_env=default_from_env)
 
3791
        self.registry.register(l)
 
3792
 
 
3793
    def test_get_default_list_None(self):
 
3794
        self.register_list_option('foo')
 
3795
        conf = self.get_conf('')
 
3796
        self.assertEquals(None, conf.get('foo'))
 
3797
 
 
3798
    def test_get_default_list_empty(self):
 
3799
        self.register_list_option('foo', '')
 
3800
        conf = self.get_conf('')
 
3801
        self.assertEquals([], conf.get('foo'))
 
3802
 
 
3803
    def test_get_default_list_from_env(self):
 
3804
        self.register_list_option('foo', default_from_env=['FOO'])
 
3805
        self.overrideEnv('FOO', '')
 
3806
        conf = self.get_conf('')
 
3807
        self.assertEquals([], conf.get('foo'))
 
3808
 
 
3809
    def test_get_with_list_converter_no_item(self):
 
3810
        self.register_list_option('foo', None)
 
3811
        conf = self.get_conf('foo=,')
 
3812
        self.assertEquals([], conf.get('foo'))
 
3813
 
 
3814
    def test_get_with_list_converter_many_items(self):
 
3815
        self.register_list_option('foo', None)
 
3816
        conf = self.get_conf('foo=m,o,r,e')
 
3817
        self.assertEquals(['m', 'o', 'r', 'e'], conf.get('foo'))
 
3818
 
 
3819
    def test_get_with_list_converter_embedded_spaces_many_items(self):
 
3820
        self.register_list_option('foo', None)
 
3821
        conf = self.get_conf('foo=" bar", "baz "')
 
3822
        self.assertEquals([' bar', 'baz '], conf.get('foo'))
 
3823
 
 
3824
    def test_get_with_list_converter_stripped_spaces_many_items(self):
 
3825
        self.register_list_option('foo', None)
 
3826
        conf = self.get_conf('foo= bar ,  baz ')
 
3827
        self.assertEquals(['bar', 'baz'], conf.get('foo'))
 
3828
 
 
3829
 
 
3830
class TestIterOptionRefs(tests.TestCase):
 
3831
    """iter_option_refs is a bit unusual, document some cases."""
 
3832
 
 
3833
    def assertRefs(self, expected, string):
 
3834
        self.assertEquals(expected, list(config.iter_option_refs(string)))
 
3835
 
 
3836
    def test_empty(self):
 
3837
        self.assertRefs([(False, '')], '')
 
3838
 
 
3839
    def test_no_refs(self):
 
3840
        self.assertRefs([(False, 'foo bar')], 'foo bar')
 
3841
 
 
3842
    def test_single_ref(self):
 
3843
        self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
 
3844
 
 
3845
    def test_broken_ref(self):
 
3846
        self.assertRefs([(False, '{foo')], '{foo')
 
3847
 
 
3848
    def test_embedded_ref(self):
 
3849
        self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
 
3850
                        '{{foo}}')
 
3851
 
 
3852
    def test_two_refs(self):
 
3853
        self.assertRefs([(False, ''), (True, '{foo}'),
 
3854
                         (False, ''), (True, '{bar}'),
 
3855
                         (False, ''),],
 
3856
                        '{foo}{bar}')
 
3857
 
 
3858
    def test_newline_in_refs_are_not_matched(self):
 
3859
        self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
 
3860
 
 
3861
 
 
3862
class TestStackExpandOptions(tests.TestCaseWithTransport):
 
3863
 
 
3864
    def setUp(self):
 
3865
        super(TestStackExpandOptions, self).setUp()
 
3866
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
3867
        self.registry = config.option_registry
 
3868
        store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
 
3869
        self.conf = config.Stack([store.get_sections], store)
 
3870
 
 
3871
    def assertExpansion(self, expected, string, env=None):
 
3872
        self.assertEquals(expected, self.conf.expand_options(string, env))
 
3873
 
 
3874
    def test_no_expansion(self):
 
3875
        self.assertExpansion('foo', 'foo')
 
3876
 
 
3877
    def test_expand_default_value(self):
 
3878
        self.conf.store._load_from_string('bar=baz')
 
3879
        self.registry.register(config.Option('foo', default=u'{bar}'))
 
3880
        self.assertEquals('baz', self.conf.get('foo', expand=True))
 
3881
 
 
3882
    def test_expand_default_from_env(self):
 
3883
        self.conf.store._load_from_string('bar=baz')
 
3884
        self.registry.register(config.Option('foo', default_from_env=['FOO']))
 
3885
        self.overrideEnv('FOO', '{bar}')
 
3886
        self.assertEquals('baz', self.conf.get('foo', expand=True))
 
3887
 
 
3888
    def test_expand_default_on_failed_conversion(self):
 
3889
        self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
 
3890
        self.registry.register(
 
3891
            config.Option('foo', default=u'{bar}',
 
3892
                          from_unicode=config.int_from_store))
 
3893
        self.assertEquals(42, self.conf.get('foo', expand=True))
 
3894
 
 
3895
    def test_env_adding_options(self):
 
3896
        self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
 
3897
 
 
3898
    def test_env_overriding_options(self):
 
3899
        self.conf.store._load_from_string('foo=baz')
 
3900
        self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
 
3901
 
 
3902
    def test_simple_ref(self):
 
3903
        self.conf.store._load_from_string('foo=xxx')
 
3904
        self.assertExpansion('xxx', '{foo}')
 
3905
 
 
3906
    def test_unknown_ref(self):
 
3907
        self.assertRaises(errors.ExpandingUnknownOption,
 
3908
                          self.conf.expand_options, '{foo}')
 
3909
 
 
3910
    def test_indirect_ref(self):
 
3911
        self.conf.store._load_from_string('''
 
3912
foo=xxx
 
3913
bar={foo}
 
3914
''')
 
3915
        self.assertExpansion('xxx', '{bar}')
 
3916
 
 
3917
    def test_embedded_ref(self):
 
3918
        self.conf.store._load_from_string('''
 
3919
foo=xxx
 
3920
bar=foo
 
3921
''')
 
3922
        self.assertExpansion('xxx', '{{bar}}')
 
3923
 
 
3924
    def test_simple_loop(self):
 
3925
        self.conf.store._load_from_string('foo={foo}')
 
3926
        self.assertRaises(errors.OptionExpansionLoop,
 
3927
                          self.conf.expand_options, '{foo}')
 
3928
 
 
3929
    def test_indirect_loop(self):
 
3930
        self.conf.store._load_from_string('''
 
3931
foo={bar}
 
3932
bar={baz}
 
3933
baz={foo}''')
 
3934
        e = self.assertRaises(errors.OptionExpansionLoop,
 
3935
                              self.conf.expand_options, '{foo}')
 
3936
        self.assertEquals('foo->bar->baz', e.refs)
 
3937
        self.assertEquals('{foo}', e.string)
 
3938
 
 
3939
    def test_list(self):
 
3940
        self.conf.store._load_from_string('''
 
3941
foo=start
 
3942
bar=middle
 
3943
baz=end
 
3944
list={foo},{bar},{baz}
 
3945
''')
 
3946
        self.registry.register(
 
3947
            config.ListOption('list'))
 
3948
        self.assertEquals(['start', 'middle', 'end'],
 
3949
                           self.conf.get('list', expand=True))
 
3950
 
 
3951
    def test_cascading_list(self):
 
3952
        self.conf.store._load_from_string('''
 
3953
foo=start,{bar}
 
3954
bar=middle,{baz}
 
3955
baz=end
 
3956
list={foo}
 
3957
''')
 
3958
        self.registry.register(
 
3959
            config.ListOption('list'))
 
3960
        self.assertEquals(['start', 'middle', 'end'],
 
3961
                           self.conf.get('list', expand=True))
 
3962
 
 
3963
    def test_pathologically_hidden_list(self):
 
3964
        self.conf.store._load_from_string('''
 
3965
foo=bin
 
3966
bar=go
 
3967
start={foo
 
3968
middle=},{
 
3969
end=bar}
 
3970
hidden={start}{middle}{end}
 
3971
''')
 
3972
        # What matters is what the registration says, the conversion happens
 
3973
        # only after all expansions have been performed
 
3974
        self.registry.register(config.ListOption('hidden'))
 
3975
        self.assertEquals(['bin', 'go'],
 
3976
                          self.conf.get('hidden', expand=True))
 
3977
 
 
3978
 
 
3979
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
 
3980
 
 
3981
    def setUp(self):
 
3982
        super(TestStackCrossSectionsExpand, self).setUp()
 
3983
 
 
3984
    def get_config(self, location, string):
 
3985
        if string is None:
 
3986
            string = ''
 
3987
        # Since we don't save the config we won't strictly require to inherit
 
3988
        # from TestCaseInTempDir, but an error occurs so quickly...
 
3989
        c = config.LocationStack(location)
 
3990
        c.store._load_from_string(string)
 
3991
        return c
 
3992
 
 
3993
    def test_dont_cross_unrelated_section(self):
 
3994
        c = self.get_config('/another/branch/path','''
 
3995
[/one/branch/path]
 
3996
foo = hello
 
3997
bar = {foo}/2
 
3998
 
 
3999
[/another/branch/path]
 
4000
bar = {foo}/2
 
4001
''')
 
4002
        self.assertRaises(errors.ExpandingUnknownOption,
 
4003
                          c.get, 'bar', expand=True)
 
4004
 
 
4005
    def test_cross_related_sections(self):
 
4006
        c = self.get_config('/project/branch/path','''
 
4007
[/project]
 
4008
foo = qu
 
4009
 
 
4010
[/project/branch/path]
 
4011
bar = {foo}ux
 
4012
''')
 
4013
        self.assertEquals('quux', c.get('bar', expand=True))
 
4014
 
 
4015
 
 
4016
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
 
4017
 
 
4018
    def test_cross_global_locations(self):
 
4019
        l_store = config.LocationStore()
 
4020
        l_store._load_from_string('''
 
4021
[/branch]
 
4022
lfoo = loc-foo
 
4023
lbar = {gbar}
 
4024
''')
 
4025
        l_store.save()
 
4026
        g_store = config.GlobalStore()
 
4027
        g_store._load_from_string('''
 
4028
[DEFAULT]
 
4029
gfoo = {lfoo}
 
4030
gbar = glob-bar
 
4031
''')
 
4032
        g_store.save()
 
4033
        stack = config.LocationStack('/branch')
 
4034
        self.assertEquals('glob-bar', stack.get('lbar', expand=True))
 
4035
        self.assertEquals('loc-foo', stack.get('gfoo', expand=True))
 
4036
 
 
4037
 
 
4038
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
 
4039
 
 
4040
    def test_expand_locals_empty(self):
 
4041
        l_store = config.LocationStore()
 
4042
        l_store._load_from_string('''
 
4043
[/home/user/project]
 
4044
base = {basename}
 
4045
rel = {relpath}
 
4046
''')
 
4047
        l_store.save()
 
4048
        stack = config.LocationStack('/home/user/project/')
 
4049
        self.assertEquals('', stack.get('base', expand=True))
 
4050
        self.assertEquals('', stack.get('rel', expand=True))
 
4051
 
 
4052
    def test_expand_basename_locally(self):
 
4053
        l_store = config.LocationStore()
 
4054
        l_store._load_from_string('''
 
4055
[/home/user/project]
 
4056
bfoo = {basename}
 
4057
''')
 
4058
        l_store.save()
 
4059
        stack = config.LocationStack('/home/user/project/branch')
 
4060
        self.assertEquals('branch', stack.get('bfoo', expand=True))
 
4061
 
 
4062
    def test_expand_basename_locally_longer_path(self):
 
4063
        l_store = config.LocationStore()
 
4064
        l_store._load_from_string('''
 
4065
[/home/user]
 
4066
bfoo = {basename}
 
4067
''')
 
4068
        l_store.save()
 
4069
        stack = config.LocationStack('/home/user/project/dir/branch')
 
4070
        self.assertEquals('branch', stack.get('bfoo', expand=True))
 
4071
 
 
4072
    def test_expand_relpath_locally(self):
 
4073
        l_store = config.LocationStore()
 
4074
        l_store._load_from_string('''
 
4075
[/home/user/project]
 
4076
lfoo = loc-foo/{relpath}
 
4077
''')
 
4078
        l_store.save()
 
4079
        stack = config.LocationStack('/home/user/project/branch')
 
4080
        self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
 
4081
 
 
4082
    def test_expand_relpath_unknonw_in_global(self):
 
4083
        g_store = config.GlobalStore()
 
4084
        g_store._load_from_string('''
 
4085
[DEFAULT]
 
4086
gfoo = {relpath}
 
4087
''')
 
4088
        g_store.save()
 
4089
        stack = config.LocationStack('/home/user/project/branch')
 
4090
        self.assertRaises(errors.ExpandingUnknownOption,
 
4091
                          stack.get, 'gfoo', expand=True)
 
4092
 
 
4093
    def test_expand_local_option_locally(self):
 
4094
        l_store = config.LocationStore()
 
4095
        l_store._load_from_string('''
 
4096
[/home/user/project]
 
4097
lfoo = loc-foo/{relpath}
 
4098
lbar = {gbar}
 
4099
''')
 
4100
        l_store.save()
 
4101
        g_store = config.GlobalStore()
 
4102
        g_store._load_from_string('''
 
4103
[DEFAULT]
 
4104
gfoo = {lfoo}
 
4105
gbar = glob-bar
 
4106
''')
 
4107
        g_store.save()
 
4108
        stack = config.LocationStack('/home/user/project/branch')
 
4109
        self.assertEquals('glob-bar', stack.get('lbar', expand=True))
 
4110
        self.assertEquals('loc-foo/branch', stack.get('gfoo', expand=True))
 
4111
 
 
4112
    def test_locals_dont_leak(self):
 
4113
        """Make sure we chose the right local in presence of several sections.
 
4114
        """
 
4115
        l_store = config.LocationStore()
 
4116
        l_store._load_from_string('''
 
4117
[/home/user]
 
4118
lfoo = loc-foo/{relpath}
 
4119
[/home/user/project]
 
4120
lfoo = loc-foo/{relpath}
 
4121
''')
 
4122
        l_store.save()
 
4123
        stack = config.LocationStack('/home/user/project/branch')
 
4124
        self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
 
4125
        stack = config.LocationStack('/home/user/bar/baz')
 
4126
        self.assertEquals('loc-foo/bar/baz', stack.get('lfoo', expand=True))
 
4127
 
 
4128
 
 
4129
 
 
4130
class TestStackSet(TestStackWithTransport):
 
4131
 
 
4132
    def test_simple_set(self):
 
4133
        conf = self.get_stack(self)
 
4134
        self.assertEquals(None, conf.get('foo'))
 
4135
        conf.set('foo', 'baz')
 
4136
        # Did we get it back ?
 
4137
        self.assertEquals('baz', conf.get('foo'))
 
4138
 
 
4139
    def test_set_creates_a_new_section(self):
 
4140
        conf = self.get_stack(self)
 
4141
        conf.set('foo', 'baz')
 
4142
        self.assertEquals, 'baz', conf.get('foo')
 
4143
 
 
4144
    def test_set_hook(self):
 
4145
        calls = []
 
4146
        def hook(*args):
 
4147
            calls.append(args)
 
4148
        config.ConfigHooks.install_named_hook('set', hook, None)
 
4149
        self.assertLength(0, calls)
 
4150
        conf = self.get_stack(self)
 
4151
        conf.set('foo', 'bar')
 
4152
        self.assertLength(1, calls)
 
4153
        self.assertEquals((conf, 'foo', 'bar'), calls[0])
 
4154
 
 
4155
 
 
4156
class TestStackRemove(TestStackWithTransport):
 
4157
 
 
4158
    def test_remove_existing(self):
 
4159
        conf = self.get_stack(self)
 
4160
        conf.set('foo', 'bar')
 
4161
        self.assertEquals('bar', conf.get('foo'))
 
4162
        conf.remove('foo')
 
4163
        # Did we get it back ?
 
4164
        self.assertEquals(None, conf.get('foo'))
 
4165
 
 
4166
    def test_remove_unknown(self):
 
4167
        conf = self.get_stack(self)
 
4168
        self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
 
4169
 
 
4170
    def test_remove_hook(self):
 
4171
        calls = []
 
4172
        def hook(*args):
 
4173
            calls.append(args)
 
4174
        config.ConfigHooks.install_named_hook('remove', hook, None)
 
4175
        self.assertLength(0, calls)
 
4176
        conf = self.get_stack(self)
 
4177
        conf.set('foo', 'bar')
 
4178
        conf.remove('foo')
 
4179
        self.assertLength(1, calls)
 
4180
        self.assertEquals((conf, 'foo'), calls[0])
 
4181
 
 
4182
 
1705
4183
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
1706
4184
 
1707
4185
    def setUp(self):
1708
4186
        super(TestConfigGetOptions, self).setUp()
1709
4187
        create_configs(self)
1710
4188
 
1711
 
    # One variable in none of the above
1712
4189
    def test_no_variable(self):
1713
4190
        # Using branch should query branch, locations and bazaar
1714
4191
        self.assertOptions([], self.branch_config)
1874
4351
        self.assertEquals({}, conf._get_config())
1875
4352
        self._got_user_passwd(None, None, conf, 'http', 'foo.net')
1876
4353
 
 
4354
    def test_non_utf8_config(self):
 
4355
        conf = config.AuthenticationConfig(_file=StringIO(
 
4356
                'foo = bar\xff'))
 
4357
        self.assertRaises(errors.ConfigContentError, conf._get_config)
 
4358
 
1877
4359
    def test_missing_auth_section_header(self):
1878
4360
        conf = config.AuthenticationConfig(_file=StringIO('foo = bar'))
1879
4361
        self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
2137
4619
 
2138
4620
    def test_username_defaults_prompts(self):
2139
4621
        # HTTP prompts can't be tested here, see test_http.py
2140
 
        self._check_default_username_prompt('FTP %(host)s username: ', 'ftp')
2141
 
        self._check_default_username_prompt(
2142
 
            'FTP %(host)s:%(port)d username: ', 'ftp', port=10020)
2143
 
        self._check_default_username_prompt(
2144
 
            'SSH %(host)s:%(port)d username: ', 'ssh', port=12345)
 
4622
        self._check_default_username_prompt(u'FTP %(host)s username: ', 'ftp')
 
4623
        self._check_default_username_prompt(
 
4624
            u'FTP %(host)s:%(port)d username: ', 'ftp', port=10020)
 
4625
        self._check_default_username_prompt(
 
4626
            u'SSH %(host)s:%(port)d username: ', 'ssh', port=12345)
2145
4627
 
2146
4628
    def test_username_default_no_prompt(self):
2147
4629
        conf = config.AuthenticationConfig()
2153
4635
    def test_password_default_prompts(self):
2154
4636
        # HTTP prompts can't be tested here, see test_http.py
2155
4637
        self._check_default_password_prompt(
2156
 
            'FTP %(user)s@%(host)s password: ', 'ftp')
2157
 
        self._check_default_password_prompt(
2158
 
            'FTP %(user)s@%(host)s:%(port)d password: ', 'ftp', port=10020)
2159
 
        self._check_default_password_prompt(
2160
 
            'SSH %(user)s@%(host)s:%(port)d password: ', 'ssh', port=12345)
 
4638
            u'FTP %(user)s@%(host)s password: ', 'ftp')
 
4639
        self._check_default_password_prompt(
 
4640
            u'FTP %(user)s@%(host)s:%(port)d password: ', 'ftp', port=10020)
 
4641
        self._check_default_password_prompt(
 
4642
            u'SSH %(user)s@%(host)s:%(port)d password: ', 'ssh', port=12345)
2161
4643
        # SMTP port handling is a bit special (it's handled if embedded in the
2162
4644
        # host too)
2163
4645
        # FIXME: should we: forbid that, extend it to other schemes, leave
2164
4646
        # things as they are that's fine thank you ?
2165
 
        self._check_default_password_prompt('SMTP %(user)s@%(host)s password: ',
2166
 
                                            'smtp')
2167
 
        self._check_default_password_prompt('SMTP %(user)s@%(host)s password: ',
2168
 
                                            'smtp', host='bar.org:10025')
2169
 
        self._check_default_password_prompt(
2170
 
            'SMTP %(user)s@%(host)s:%(port)d password: ',
2171
 
            'smtp', port=10025)
 
4647
        self._check_default_password_prompt(
 
4648
            u'SMTP %(user)s@%(host)s password: ', 'smtp')
 
4649
        self._check_default_password_prompt(
 
4650
            u'SMTP %(user)s@%(host)s password: ', 'smtp', host='bar.org:10025')
 
4651
        self._check_default_password_prompt(
 
4652
            u'SMTP %(user)s@%(host)s:%(port)d password: ', 'smtp', port=10025)
2172
4653
 
2173
4654
    def test_ssh_password_emits_warning(self):
2174
4655
        conf = config.AuthenticationConfig(_file=StringIO(
2354
4835
# test_user_prompted ?
2355
4836
class TestAuthenticationRing(tests.TestCaseWithTransport):
2356
4837
    pass
 
4838
 
 
4839
 
 
4840
class TestAutoUserId(tests.TestCase):
 
4841
    """Test inferring an automatic user name."""
 
4842
 
 
4843
    def test_auto_user_id(self):
 
4844
        """Automatic inference of user name.
 
4845
 
 
4846
        This is a bit hard to test in an isolated way, because it depends on
 
4847
        system functions that go direct to /etc or perhaps somewhere else.
 
4848
        But it's reasonable to say that on Unix, with an /etc/mailname, we ought
 
4849
        to be able to choose a user name with no configuration.
 
4850
        """
 
4851
        if sys.platform == 'win32':
 
4852
            raise tests.TestSkipped(
 
4853
                "User name inference not implemented on win32")
 
4854
        realname, address = config._auto_user_id()
 
4855
        if os.path.exists('/etc/mailname'):
 
4856
            self.assertIsNot(None, realname)
 
4857
            self.assertIsNot(None, address)
 
4858
        else:
 
4859
            self.assertEquals((None, None), (realname, address))
 
4860
 
 
4861
 
 
4862
class EmailOptionTests(tests.TestCase):
 
4863
 
 
4864
    def test_default_email_uses_BZR_EMAIL(self):
 
4865
        conf = config.MemoryStack('email=jelmer@debian.org')
 
4866
        # BZR_EMAIL takes precedence over EMAIL
 
4867
        self.overrideEnv('BZR_EMAIL', 'jelmer@samba.org')
 
4868
        self.overrideEnv('EMAIL', 'jelmer@apache.org')
 
4869
        self.assertEquals('jelmer@samba.org', conf.get('email'))
 
4870
 
 
4871
    def test_default_email_uses_EMAIL(self):
 
4872
        conf = config.MemoryStack('')
 
4873
        self.overrideEnv('BZR_EMAIL', None)
 
4874
        self.overrideEnv('EMAIL', 'jelmer@apache.org')
 
4875
        self.assertEquals('jelmer@apache.org', conf.get('email'))
 
4876
 
 
4877
    def test_BZR_EMAIL_overrides(self):
 
4878
        conf = config.MemoryStack('email=jelmer@debian.org')
 
4879
        self.overrideEnv('BZR_EMAIL', 'jelmer@apache.org')
 
4880
        self.assertEquals('jelmer@apache.org', conf.get('email'))
 
4881
        self.overrideEnv('BZR_EMAIL', None)
 
4882
        self.overrideEnv('EMAIL', 'jelmer@samba.org')
 
4883
        self.assertEquals('jelmer@debian.org', conf.get('email'))
 
4884
 
 
4885
 
 
4886
class MailClientOptionTests(tests.TestCase):
 
4887
 
 
4888
    def test_default(self):
 
4889
        conf = config.MemoryStack('')
 
4890
        client = conf.get('mail_client')
 
4891
        self.assertIs(client, mail_client.DefaultMail)
 
4892
 
 
4893
    def test_evolution(self):
 
4894
        conf = config.MemoryStack('mail_client=evolution')
 
4895
        client = conf.get('mail_client')
 
4896
        self.assertIs(client, mail_client.Evolution)
 
4897
 
 
4898
    def test_kmail(self):
 
4899
        conf = config.MemoryStack('mail_client=kmail')
 
4900
        client = conf.get('mail_client')
 
4901
        self.assertIs(client, mail_client.KMail)
 
4902
 
 
4903
    def test_mutt(self):
 
4904
        conf = config.MemoryStack('mail_client=mutt')
 
4905
        client = conf.get('mail_client')
 
4906
        self.assertIs(client, mail_client.Mutt)
 
4907
 
 
4908
    def test_thunderbird(self):
 
4909
        conf = config.MemoryStack('mail_client=thunderbird')
 
4910
        client = conf.get('mail_client')
 
4911
        self.assertIs(client, mail_client.Thunderbird)
 
4912
 
 
4913
    def test_explicit_default(self):
 
4914
        conf = config.MemoryStack('mail_client=default')
 
4915
        client = conf.get('mail_client')
 
4916
        self.assertIs(client, mail_client.DefaultMail)
 
4917
 
 
4918
    def test_editor(self):
 
4919
        conf = config.MemoryStack('mail_client=editor')
 
4920
        client = conf.get('mail_client')
 
4921
        self.assertIs(client, mail_client.Editor)
 
4922
 
 
4923
    def test_mapi(self):
 
4924
        conf = config.MemoryStack('mail_client=mapi')
 
4925
        client = conf.get('mail_client')
 
4926
        self.assertIs(client, mail_client.MAPIClient)
 
4927
 
 
4928
    def test_xdg_email(self):
 
4929
        conf = config.MemoryStack('mail_client=xdg-email')
 
4930
        client = conf.get('mail_client')
 
4931
        self.assertIs(client, mail_client.XDGEmail)
 
4932
 
 
4933
    def test_unknown(self):
 
4934
        conf = config.MemoryStack('mail_client=firebird')
 
4935
        self.assertRaises(errors.ConfigOptionValueError, conf.get,
 
4936
                'mail_client')