/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/tests/test_config.py

  • Committer: Jelmer Vernooij
  • Date: 2017-05-22 00:56:52 UTC
  • mfrom: (6621.2.26 py3_pokes)
  • Revision ID: jelmer@jelmer.uk-20170522005652-yjahcr9hwmjkno7n
Merge Python3 porting work ('py3 pokes')

Show diffs side-by-side

added added

removed removed

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