/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: Martin
  • Date: 2017-06-27 00:10:52 UTC
  • mto: This revision was merged to the branch mainline in revision 6721.
  • Revision ID: gzlist@googlemail.com-20170627001052-o70zln144nmwhamo
Switch c_api helpers for _static_tuple_c to capsules

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