~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_config.py

Add bzrlib.tests.per_repository_vf.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2011 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Tests for finding and reading the bzr config file[s]."""
 
18
# import system imports here
 
19
from cStringIO import StringIO
 
20
import os
 
21
import sys
 
22
import threading
 
23
 
 
24
 
 
25
from testtools import matchers
 
26
 
 
27
#import bzrlib specific imports here
 
28
from bzrlib import (
 
29
    branch,
 
30
    bzrdir,
 
31
    config,
 
32
    diff,
 
33
    errors,
 
34
    osutils,
 
35
    mail_client,
 
36
    mergetools,
 
37
    ui,
 
38
    urlutils,
 
39
    tests,
 
40
    trace,
 
41
    transport,
 
42
    )
 
43
from bzrlib.tests import (
 
44
    features,
 
45
    scenarios,
 
46
    )
 
47
from bzrlib.util.configobj import configobj
 
48
 
 
49
 
 
50
def lockable_config_scenarios():
 
51
    return [
 
52
        ('global',
 
53
         {'config_class': config.GlobalConfig,
 
54
          'config_args': [],
 
55
          'config_section': 'DEFAULT'}),
 
56
        ('locations',
 
57
         {'config_class': config.LocationConfig,
 
58
          'config_args': ['.'],
 
59
          'config_section': '.'}),]
 
60
 
 
61
 
 
62
load_tests = scenarios.load_tests_apply_scenarios
 
63
 
 
64
 
 
65
sample_long_alias="log -r-15..-1 --line"
 
66
sample_config_text = u"""
 
67
[DEFAULT]
 
68
email=Erik B\u00e5gfors <erik@bagfors.nu>
 
69
editor=vim
 
70
change_editor=vimdiff -of @new_path @old_path
 
71
gpg_signing_command=gnome-gpg
 
72
log_format=short
 
73
user_global_option=something
 
74
bzr.mergetool.sometool=sometool {base} {this} {other} -o {result}
 
75
bzr.mergetool.funkytool=funkytool "arg with spaces" {this_temp}
 
76
bzr.default_mergetool=sometool
 
77
[ALIASES]
 
78
h=help
 
79
ll=""" + sample_long_alias + "\n"
 
80
 
 
81
 
 
82
sample_always_signatures = """
 
83
[DEFAULT]
 
84
check_signatures=ignore
 
85
create_signatures=always
 
86
"""
 
87
 
 
88
sample_ignore_signatures = """
 
89
[DEFAULT]
 
90
check_signatures=require
 
91
create_signatures=never
 
92
"""
 
93
 
 
94
sample_maybe_signatures = """
 
95
[DEFAULT]
 
96
check_signatures=ignore
 
97
create_signatures=when-required
 
98
"""
 
99
 
 
100
sample_branches_text = """
 
101
[http://www.example.com]
 
102
# Top level policy
 
103
email=Robert Collins <robertc@example.org>
 
104
normal_option = normal
 
105
appendpath_option = append
 
106
appendpath_option:policy = appendpath
 
107
norecurse_option = norecurse
 
108
norecurse_option:policy = norecurse
 
109
[http://www.example.com/ignoreparent]
 
110
# different project: ignore parent dir config
 
111
ignore_parents=true
 
112
[http://www.example.com/norecurse]
 
113
# configuration items that only apply to this dir
 
114
recurse=false
 
115
normal_option = norecurse
 
116
[http://www.example.com/dir]
 
117
appendpath_option = normal
 
118
[/b/]
 
119
check_signatures=require
 
120
# test trailing / matching with no children
 
121
[/a/]
 
122
check_signatures=check-available
 
123
gpg_signing_command=false
 
124
user_local_option=local
 
125
# test trailing / matching
 
126
[/a/*]
 
127
#subdirs will match but not the parent
 
128
[/a/c]
 
129
check_signatures=ignore
 
130
post_commit=bzrlib.tests.test_config.post_commit
 
131
#testing explicit beats globs
 
132
"""
 
133
 
 
134
 
 
135
def create_configs(test):
 
136
    """Create configuration files for a given test.
 
137
 
 
138
    This requires creating a tree (and populate the ``test.tree`` attribute)
 
139
    and its associated branch and will populate the following attributes:
 
140
 
 
141
    - branch_config: A BranchConfig for the associated branch.
 
142
 
 
143
    - locations_config : A LocationConfig for the associated branch
 
144
 
 
145
    - bazaar_config: A GlobalConfig.
 
146
 
 
147
    The tree and branch are created in a 'tree' subdirectory so the tests can
 
148
    still use the test directory to stay outside of the branch.
 
149
    """
 
150
    tree = test.make_branch_and_tree('tree')
 
151
    test.tree = tree
 
152
    test.branch_config = config.BranchConfig(tree.branch)
 
153
    test.locations_config = config.LocationConfig(tree.basedir)
 
154
    test.bazaar_config = config.GlobalConfig()
 
155
 
 
156
 
 
157
def create_configs_with_file_option(test):
 
158
    """Create configuration files with a ``file`` option set in each.
 
159
 
 
160
    This builds on ``create_configs`` and add one ``file`` option in each
 
161
    configuration with a value which allows identifying the configuration file.
 
162
    """
 
163
    create_configs(test)
 
164
    test.bazaar_config.set_user_option('file', 'bazaar')
 
165
    test.locations_config.set_user_option('file', 'locations')
 
166
    test.branch_config.set_user_option('file', 'branch')
 
167
 
 
168
 
 
169
class TestOptionsMixin:
 
170
 
 
171
    def assertOptions(self, expected, conf):
 
172
        # We don't care about the parser (as it will make tests hard to write
 
173
        # and error-prone anyway)
 
174
        self.assertThat([opt[:4] for opt in conf._get_options()],
 
175
                        matchers.Equals(expected))
 
176
 
 
177
 
 
178
class InstrumentedConfigObj(object):
 
179
    """A config obj look-enough-alike to record calls made to it."""
 
180
 
 
181
    def __contains__(self, thing):
 
182
        self._calls.append(('__contains__', thing))
 
183
        return False
 
184
 
 
185
    def __getitem__(self, key):
 
186
        self._calls.append(('__getitem__', key))
 
187
        return self
 
188
 
 
189
    def __init__(self, input, encoding=None):
 
190
        self._calls = [('__init__', input, encoding)]
 
191
 
 
192
    def __setitem__(self, key, value):
 
193
        self._calls.append(('__setitem__', key, value))
 
194
 
 
195
    def __delitem__(self, key):
 
196
        self._calls.append(('__delitem__', key))
 
197
 
 
198
    def keys(self):
 
199
        self._calls.append(('keys',))
 
200
        return []
 
201
 
 
202
    def reload(self):
 
203
        self._calls.append(('reload',))
 
204
 
 
205
    def write(self, arg):
 
206
        self._calls.append(('write',))
 
207
 
 
208
    def as_bool(self, value):
 
209
        self._calls.append(('as_bool', value))
 
210
        return False
 
211
 
 
212
    def get_value(self, section, name):
 
213
        self._calls.append(('get_value', section, name))
 
214
        return None
 
215
 
 
216
 
 
217
class FakeBranch(object):
 
218
 
 
219
    def __init__(self, base=None, user_id=None):
 
220
        if base is None:
 
221
            self.base = "http://example.com/branches/demo"
 
222
        else:
 
223
            self.base = base
 
224
        self._transport = self.control_files = \
 
225
            FakeControlFilesAndTransport(user_id=user_id)
 
226
 
 
227
    def _get_config(self):
 
228
        return config.TransportConfig(self._transport, 'branch.conf')
 
229
 
 
230
    def lock_write(self):
 
231
        pass
 
232
 
 
233
    def unlock(self):
 
234
        pass
 
235
 
 
236
 
 
237
class FakeControlFilesAndTransport(object):
 
238
 
 
239
    def __init__(self, user_id=None):
 
240
        self.files = {}
 
241
        if user_id:
 
242
            self.files['email'] = user_id
 
243
        self._transport = self
 
244
 
 
245
    def get_utf8(self, filename):
 
246
        # from LockableFiles
 
247
        raise AssertionError("get_utf8 should no longer be used")
 
248
 
 
249
    def get(self, filename):
 
250
        # from Transport
 
251
        try:
 
252
            return StringIO(self.files[filename])
 
253
        except KeyError:
 
254
            raise errors.NoSuchFile(filename)
 
255
 
 
256
    def get_bytes(self, filename):
 
257
        # from Transport
 
258
        try:
 
259
            return self.files[filename]
 
260
        except KeyError:
 
261
            raise errors.NoSuchFile(filename)
 
262
 
 
263
    def put(self, filename, fileobj):
 
264
        self.files[filename] = fileobj.read()
 
265
 
 
266
    def put_file(self, filename, fileobj):
 
267
        return self.put(filename, fileobj)
 
268
 
 
269
 
 
270
class InstrumentedConfig(config.Config):
 
271
    """An instrumented config that supplies stubs for template methods."""
 
272
 
 
273
    def __init__(self):
 
274
        super(InstrumentedConfig, self).__init__()
 
275
        self._calls = []
 
276
        self._signatures = config.CHECK_NEVER
 
277
 
 
278
    def _get_user_id(self):
 
279
        self._calls.append('_get_user_id')
 
280
        return "Robert Collins <robert.collins@example.org>"
 
281
 
 
282
    def _get_signature_checking(self):
 
283
        self._calls.append('_get_signature_checking')
 
284
        return self._signatures
 
285
 
 
286
    def _get_change_editor(self):
 
287
        self._calls.append('_get_change_editor')
 
288
        return 'vimdiff -fo @new_path @old_path'
 
289
 
 
290
 
 
291
bool_config = """[DEFAULT]
 
292
active = true
 
293
inactive = false
 
294
[UPPERCASE]
 
295
active = True
 
296
nonactive = False
 
297
"""
 
298
 
 
299
 
 
300
class TestConfigObj(tests.TestCase):
 
301
 
 
302
    def test_get_bool(self):
 
303
        co = config.ConfigObj(StringIO(bool_config))
 
304
        self.assertIs(co.get_bool('DEFAULT', 'active'), True)
 
305
        self.assertIs(co.get_bool('DEFAULT', 'inactive'), False)
 
306
        self.assertIs(co.get_bool('UPPERCASE', 'active'), True)
 
307
        self.assertIs(co.get_bool('UPPERCASE', 'nonactive'), False)
 
308
 
 
309
    def test_hash_sign_in_value(self):
 
310
        """
 
311
        Before 4.5.0, ConfigObj did not quote # signs in values, so they'd be
 
312
        treated as comments when read in again. (#86838)
 
313
        """
 
314
        co = config.ConfigObj()
 
315
        co['test'] = 'foo#bar'
 
316
        outfile = StringIO()
 
317
        co.write(outfile=outfile)
 
318
        lines = outfile.getvalue().splitlines()
 
319
        self.assertEqual(lines, ['test = "foo#bar"'])
 
320
        co2 = config.ConfigObj(lines)
 
321
        self.assertEqual(co2['test'], 'foo#bar')
 
322
 
 
323
    def test_triple_quotes(self):
 
324
        # Bug #710410: if the value string has triple quotes
 
325
        # then ConfigObj versions up to 4.7.2 will quote them wrong
 
326
        # and won't able to read them back
 
327
        triple_quotes_value = '''spam
 
328
""" that's my spam """
 
329
eggs'''
 
330
        co = config.ConfigObj()
 
331
        co['test'] = triple_quotes_value
 
332
        # While writing this test another bug in ConfigObj has been found:
 
333
        # method co.write() without arguments produces list of lines
 
334
        # one option per line, and multiline values are not split
 
335
        # across multiple lines,
 
336
        # and that breaks the parsing these lines back by ConfigObj.
 
337
        # This issue only affects test, but it's better to avoid
 
338
        # `co.write()` construct at all.
 
339
        # [bialix 20110222] bug report sent to ConfigObj's author
 
340
        outfile = StringIO()
 
341
        co.write(outfile=outfile)
 
342
        output = outfile.getvalue()
 
343
        # now we're trying to read it back
 
344
        co2 = config.ConfigObj(StringIO(output))
 
345
        self.assertEquals(triple_quotes_value, co2['test'])
 
346
 
 
347
 
 
348
erroneous_config = """[section] # line 1
 
349
good=good # line 2
 
350
[section] # line 3
 
351
whocares=notme # line 4
 
352
"""
 
353
 
 
354
 
 
355
class TestConfigObjErrors(tests.TestCase):
 
356
 
 
357
    def test_duplicate_section_name_error_line(self):
 
358
        try:
 
359
            co = configobj.ConfigObj(StringIO(erroneous_config),
 
360
                                     raise_errors=True)
 
361
        except config.configobj.DuplicateError, e:
 
362
            self.assertEqual(3, e.line_number)
 
363
        else:
 
364
            self.fail('Error in config file not detected')
 
365
 
 
366
 
 
367
class TestConfig(tests.TestCase):
 
368
 
 
369
    def test_constructs(self):
 
370
        config.Config()
 
371
 
 
372
    def test_no_default_editor(self):
 
373
        self.assertRaises(NotImplementedError, config.Config().get_editor)
 
374
 
 
375
    def test_user_email(self):
 
376
        my_config = InstrumentedConfig()
 
377
        self.assertEqual('robert.collins@example.org', my_config.user_email())
 
378
        self.assertEqual(['_get_user_id'], my_config._calls)
 
379
 
 
380
    def test_username(self):
 
381
        my_config = InstrumentedConfig()
 
382
        self.assertEqual('Robert Collins <robert.collins@example.org>',
 
383
                         my_config.username())
 
384
        self.assertEqual(['_get_user_id'], my_config._calls)
 
385
 
 
386
    def test_signatures_default(self):
 
387
        my_config = config.Config()
 
388
        self.assertFalse(my_config.signature_needed())
 
389
        self.assertEqual(config.CHECK_IF_POSSIBLE,
 
390
                         my_config.signature_checking())
 
391
        self.assertEqual(config.SIGN_WHEN_REQUIRED,
 
392
                         my_config.signing_policy())
 
393
 
 
394
    def test_signatures_template_method(self):
 
395
        my_config = InstrumentedConfig()
 
396
        self.assertEqual(config.CHECK_NEVER, my_config.signature_checking())
 
397
        self.assertEqual(['_get_signature_checking'], my_config._calls)
 
398
 
 
399
    def test_signatures_template_method_none(self):
 
400
        my_config = InstrumentedConfig()
 
401
        my_config._signatures = None
 
402
        self.assertEqual(config.CHECK_IF_POSSIBLE,
 
403
                         my_config.signature_checking())
 
404
        self.assertEqual(['_get_signature_checking'], my_config._calls)
 
405
 
 
406
    def test_gpg_signing_command_default(self):
 
407
        my_config = config.Config()
 
408
        self.assertEqual('gpg', my_config.gpg_signing_command())
 
409
 
 
410
    def test_get_user_option_default(self):
 
411
        my_config = config.Config()
 
412
        self.assertEqual(None, my_config.get_user_option('no_option'))
 
413
 
 
414
    def test_post_commit_default(self):
 
415
        my_config = config.Config()
 
416
        self.assertEqual(None, my_config.post_commit())
 
417
 
 
418
    def test_log_format_default(self):
 
419
        my_config = config.Config()
 
420
        self.assertEqual('long', my_config.log_format())
 
421
 
 
422
    def test_get_change_editor(self):
 
423
        my_config = InstrumentedConfig()
 
424
        change_editor = my_config.get_change_editor('old_tree', 'new_tree')
 
425
        self.assertEqual(['_get_change_editor'], my_config._calls)
 
426
        self.assertIs(diff.DiffFromTool, change_editor.__class__)
 
427
        self.assertEqual(['vimdiff', '-fo', '@new_path', '@old_path'],
 
428
                         change_editor.command_template)
 
429
 
 
430
 
 
431
class TestConfigPath(tests.TestCase):
 
432
 
 
433
    def setUp(self):
 
434
        super(TestConfigPath, self).setUp()
 
435
        self.overrideEnv('HOME', '/home/bogus')
 
436
        self.overrideEnv('XDG_CACHE_DIR', '')
 
437
        if sys.platform == 'win32':
 
438
            self.overrideEnv(
 
439
                'BZR_HOME', r'C:\Documents and Settings\bogus\Application Data')
 
440
            self.bzr_home = \
 
441
                'C:/Documents and Settings/bogus/Application Data/bazaar/2.0'
 
442
        else:
 
443
            self.bzr_home = '/home/bogus/.bazaar'
 
444
 
 
445
    def test_config_dir(self):
 
446
        self.assertEqual(config.config_dir(), self.bzr_home)
 
447
 
 
448
    def test_config_filename(self):
 
449
        self.assertEqual(config.config_filename(),
 
450
                         self.bzr_home + '/bazaar.conf')
 
451
 
 
452
    def test_locations_config_filename(self):
 
453
        self.assertEqual(config.locations_config_filename(),
 
454
                         self.bzr_home + '/locations.conf')
 
455
 
 
456
    def test_authentication_config_filename(self):
 
457
        self.assertEqual(config.authentication_config_filename(),
 
458
                         self.bzr_home + '/authentication.conf')
 
459
 
 
460
    def test_xdg_cache_dir(self):
 
461
        self.assertEqual(config.xdg_cache_dir(),
 
462
            '/home/bogus/.cache')
 
463
 
 
464
 
 
465
class TestXDGConfigDir(tests.TestCaseInTempDir):
 
466
    # must be in temp dir because config tests for the existence of the bazaar
 
467
    # subdirectory of $XDG_CONFIG_HOME
 
468
 
 
469
    def setUp(self):
 
470
        if sys.platform in ('darwin', 'win32'):
 
471
            raise tests.TestNotApplicable(
 
472
                'XDG config dir not used on this platform')
 
473
        super(TestXDGConfigDir, self).setUp()
 
474
        self.overrideEnv('HOME', self.test_home_dir)
 
475
        # BZR_HOME overrides everything we want to test so unset it.
 
476
        self.overrideEnv('BZR_HOME', None)
 
477
 
 
478
    def test_xdg_config_dir_exists(self):
 
479
        """When ~/.config/bazaar exists, use it as the config dir."""
 
480
        newdir = osutils.pathjoin(self.test_home_dir, '.config', 'bazaar')
 
481
        os.makedirs(newdir)
 
482
        self.assertEqual(config.config_dir(), newdir)
 
483
 
 
484
    def test_xdg_config_home(self):
 
485
        """When XDG_CONFIG_HOME is set, use it."""
 
486
        xdgconfigdir = osutils.pathjoin(self.test_home_dir, 'xdgconfig')
 
487
        self.overrideEnv('XDG_CONFIG_HOME', xdgconfigdir)
 
488
        newdir = osutils.pathjoin(xdgconfigdir, 'bazaar')
 
489
        os.makedirs(newdir)
 
490
        self.assertEqual(config.config_dir(), newdir)
 
491
 
 
492
 
 
493
class TestIniConfig(tests.TestCaseInTempDir):
 
494
 
 
495
    def make_config_parser(self, s):
 
496
        conf = config.IniBasedConfig.from_string(s)
 
497
        return conf, conf._get_parser()
 
498
 
 
499
 
 
500
class TestIniConfigBuilding(TestIniConfig):
 
501
 
 
502
    def test_contructs(self):
 
503
        my_config = config.IniBasedConfig()
 
504
 
 
505
    def test_from_fp(self):
 
506
        my_config = config.IniBasedConfig.from_string(sample_config_text)
 
507
        self.assertIsInstance(my_config._get_parser(), configobj.ConfigObj)
 
508
 
 
509
    def test_cached(self):
 
510
        my_config = config.IniBasedConfig.from_string(sample_config_text)
 
511
        parser = my_config._get_parser()
 
512
        self.failUnless(my_config._get_parser() is parser)
 
513
 
 
514
    def _dummy_chown(self, path, uid, gid):
 
515
        self.path, self.uid, self.gid = path, uid, gid
 
516
 
 
517
    def test_ini_config_ownership(self):
 
518
        """Ensure that chown is happening during _write_config_file"""
 
519
        self.requireFeature(features.chown_feature)
 
520
        self.overrideAttr(os, 'chown', self._dummy_chown)
 
521
        self.path = self.uid = self.gid = None
 
522
        conf = config.IniBasedConfig(file_name='./foo.conf')
 
523
        conf._write_config_file()
 
524
        self.assertEquals(self.path, './foo.conf')
 
525
        self.assertTrue(isinstance(self.uid, int))
 
526
        self.assertTrue(isinstance(self.gid, int))
 
527
 
 
528
    def test_get_filename_parameter_is_deprecated_(self):
 
529
        conf = self.callDeprecated([
 
530
            'IniBasedConfig.__init__(get_filename) was deprecated in 2.3.'
 
531
            ' Use file_name instead.'],
 
532
            config.IniBasedConfig, lambda: 'ini.conf')
 
533
        self.assertEqual('ini.conf', conf.file_name)
 
534
 
 
535
    def test_get_parser_file_parameter_is_deprecated_(self):
 
536
        config_file = StringIO(sample_config_text.encode('utf-8'))
 
537
        conf = config.IniBasedConfig.from_string(sample_config_text)
 
538
        conf = self.callDeprecated([
 
539
            'IniBasedConfig._get_parser(file=xxx) was deprecated in 2.3.'
 
540
            ' Use IniBasedConfig(_content=xxx) instead.'],
 
541
            conf._get_parser, file=config_file)
 
542
 
 
543
 
 
544
class TestIniConfigSaving(tests.TestCaseInTempDir):
 
545
 
 
546
    def test_cant_save_without_a_file_name(self):
 
547
        conf = config.IniBasedConfig()
 
548
        self.assertRaises(AssertionError, conf._write_config_file)
 
549
 
 
550
    def test_saved_with_content(self):
 
551
        content = 'foo = bar\n'
 
552
        conf = config.IniBasedConfig.from_string(
 
553
            content, file_name='./test.conf', save=True)
 
554
        self.assertFileEqual(content, 'test.conf')
 
555
 
 
556
 
 
557
class TestIniConfigOptionExpansionDefaultValue(tests.TestCaseInTempDir):
 
558
    """What is the default value of expand for config options.
 
559
 
 
560
    This is an opt-in beta feature used to evaluate whether or not option
 
561
    references can appear in dangerous place raising exceptions, disapearing
 
562
    (and as such corrupting data) or if it's safe to activate the option by
 
563
    default.
 
564
 
 
565
    Note that these tests relies on config._expand_default_value being already
 
566
    overwritten in the parent class setUp.
 
567
    """
 
568
 
 
569
    def setUp(self):
 
570
        super(TestIniConfigOptionExpansionDefaultValue, self).setUp()
 
571
        self.config = None
 
572
        self.warnings = []
 
573
        def warning(*args):
 
574
            self.warnings.append(args[0] % args[1:])
 
575
        self.overrideAttr(trace, 'warning', warning)
 
576
 
 
577
    def get_config(self, expand):
 
578
        c = config.GlobalConfig.from_string('bzr.config.expand=%s' % (expand,),
 
579
                                            save=True)
 
580
        return c
 
581
 
 
582
    def assertExpandIs(self, expected):
 
583
        actual = config._get_expand_default_value()
 
584
        #self.config.get_user_option_as_bool('bzr.config.expand')
 
585
        self.assertEquals(expected, actual)
 
586
 
 
587
    def test_default_is_None(self):
 
588
        self.assertEquals(None, config._expand_default_value)
 
589
 
 
590
    def test_default_is_False_even_if_None(self):
 
591
        self.config = self.get_config(None)
 
592
        self.assertExpandIs(False)
 
593
 
 
594
    def test_default_is_False_even_if_invalid(self):
 
595
        self.config = self.get_config('<your choice>')
 
596
        self.assertExpandIs(False)
 
597
        # ...
 
598
        # Huh ? My choice is False ? Thanks, always happy to hear that :D
 
599
        # Wait, you've been warned !
 
600
        self.assertLength(1, self.warnings)
 
601
        self.assertEquals(
 
602
            'Value "<your choice>" is not a boolean for "bzr.config.expand"',
 
603
            self.warnings[0])
 
604
 
 
605
    def test_default_is_True(self):
 
606
        self.config = self.get_config(True)
 
607
        self.assertExpandIs(True)
 
608
        
 
609
    def test_default_is_False(self):
 
610
        self.config = self.get_config(False)
 
611
        self.assertExpandIs(False)
 
612
        
 
613
 
 
614
class TestIniConfigOptionExpansion(tests.TestCase):
 
615
    """Test option expansion from the IniConfig level.
 
616
 
 
617
    What we really want here is to test the Config level, but the class being
 
618
    abstract as far as storing values is concerned, this can't be done
 
619
    properly (yet).
 
620
    """
 
621
    # FIXME: This should be rewritten when all configs share a storage
 
622
    # implementation -- vila 2011-02-18
 
623
 
 
624
    def get_config(self, string=None):
 
625
        if string is None:
 
626
            string = ''
 
627
        c = config.IniBasedConfig.from_string(string)
 
628
        return c
 
629
 
 
630
    def assertExpansion(self, expected, conf, string, env=None):
 
631
        self.assertEquals(expected, conf.expand_options(string, env))
 
632
 
 
633
    def test_no_expansion(self):
 
634
        c = self.get_config('')
 
635
        self.assertExpansion('foo', c, 'foo')
 
636
 
 
637
    def test_env_adding_options(self):
 
638
        c = self.get_config('')
 
639
        self.assertExpansion('bar', c, '{foo}', {'foo': 'bar'})
 
640
 
 
641
    def test_env_overriding_options(self):
 
642
        c = self.get_config('foo=baz')
 
643
        self.assertExpansion('bar', c, '{foo}', {'foo': 'bar'})
 
644
 
 
645
    def test_simple_ref(self):
 
646
        c = self.get_config('foo=xxx')
 
647
        self.assertExpansion('xxx', c, '{foo}')
 
648
 
 
649
    def test_unknown_ref(self):
 
650
        c = self.get_config('')
 
651
        self.assertRaises(errors.ExpandingUnknownOption,
 
652
                          c.expand_options, '{foo}')
 
653
 
 
654
    def test_indirect_ref(self):
 
655
        c = self.get_config('''
 
656
foo=xxx
 
657
bar={foo}
 
658
''')
 
659
        self.assertExpansion('xxx', c, '{bar}')
 
660
 
 
661
    def test_embedded_ref(self):
 
662
        c = self.get_config('''
 
663
foo=xxx
 
664
bar=foo
 
665
''')
 
666
        self.assertExpansion('xxx', c, '{{bar}}')
 
667
 
 
668
    def test_simple_loop(self):
 
669
        c = self.get_config('foo={foo}')
 
670
        self.assertRaises(errors.OptionExpansionLoop, c.expand_options, '{foo}')
 
671
 
 
672
    def test_indirect_loop(self):
 
673
        c = self.get_config('''
 
674
foo={bar}
 
675
bar={baz}
 
676
baz={foo}''')
 
677
        e = self.assertRaises(errors.OptionExpansionLoop,
 
678
                              c.expand_options, '{foo}')
 
679
        self.assertEquals('foo->bar->baz', e.refs)
 
680
        self.assertEquals('{foo}', e.string)
 
681
 
 
682
    def test_list(self):
 
683
        conf = self.get_config('''
 
684
foo=start
 
685
bar=middle
 
686
baz=end
 
687
list={foo},{bar},{baz}
 
688
''')
 
689
        self.assertEquals(['start', 'middle', 'end'],
 
690
                           conf.get_user_option('list', expand=True))
 
691
 
 
692
    def test_cascading_list(self):
 
693
        conf = self.get_config('''
 
694
foo=start,{bar}
 
695
bar=middle,{baz}
 
696
baz=end
 
697
list={foo}
 
698
''')
 
699
        self.assertEquals(['start', 'middle', 'end'],
 
700
                           conf.get_user_option('list', expand=True))
 
701
 
 
702
    def test_pathological_hidden_list(self):
 
703
        conf = self.get_config('''
 
704
foo=bin
 
705
bar=go
 
706
start={foo
 
707
middle=},{
 
708
end=bar}
 
709
hidden={start}{middle}{end}
 
710
''')
 
711
        # Nope, it's either a string or a list, and the list wins as soon as a
 
712
        # ',' appears, so the string concatenation never occur.
 
713
        self.assertEquals(['{foo', '}', '{', 'bar}'],
 
714
                          conf.get_user_option('hidden', expand=True))
 
715
 
 
716
class TestLocationConfigOptionExpansion(tests.TestCaseInTempDir):
 
717
 
 
718
    def get_config(self, location, string=None):
 
719
        if string is None:
 
720
            string = ''
 
721
        # Since we don't save the config we won't strictly require to inherit
 
722
        # from TestCaseInTempDir, but an error occurs so quickly...
 
723
        c = config.LocationConfig.from_string(string, location)
 
724
        return c
 
725
 
 
726
    def test_dont_cross_unrelated_section(self):
 
727
        c = self.get_config('/another/branch/path','''
 
728
[/one/branch/path]
 
729
foo = hello
 
730
bar = {foo}/2
 
731
 
 
732
[/another/branch/path]
 
733
bar = {foo}/2
 
734
''')
 
735
        self.assertRaises(errors.ExpandingUnknownOption,
 
736
                          c.get_user_option, 'bar', expand=True)
 
737
 
 
738
    def test_cross_related_sections(self):
 
739
        c = self.get_config('/project/branch/path','''
 
740
[/project]
 
741
foo = qu
 
742
 
 
743
[/project/branch/path]
 
744
bar = {foo}ux
 
745
''')
 
746
        self.assertEquals('quux', c.get_user_option('bar', expand=True))
 
747
 
 
748
 
 
749
class TestIniBaseConfigOnDisk(tests.TestCaseInTempDir):
 
750
 
 
751
    def test_cannot_reload_without_name(self):
 
752
        conf = config.IniBasedConfig.from_string(sample_config_text)
 
753
        self.assertRaises(AssertionError, conf.reload)
 
754
 
 
755
    def test_reload_see_new_value(self):
 
756
        c1 = config.IniBasedConfig.from_string('editor=vim\n',
 
757
                                               file_name='./test/conf')
 
758
        c1._write_config_file()
 
759
        c2 = config.IniBasedConfig.from_string('editor=emacs\n',
 
760
                                               file_name='./test/conf')
 
761
        c2._write_config_file()
 
762
        self.assertEqual('vim', c1.get_user_option('editor'))
 
763
        self.assertEqual('emacs', c2.get_user_option('editor'))
 
764
        # Make sure we get the Right value
 
765
        c1.reload()
 
766
        self.assertEqual('emacs', c1.get_user_option('editor'))
 
767
 
 
768
 
 
769
class TestLockableConfig(tests.TestCaseInTempDir):
 
770
 
 
771
    scenarios = lockable_config_scenarios()
 
772
 
 
773
    # Set by load_tests
 
774
    config_class = None
 
775
    config_args = None
 
776
    config_section = None
 
777
 
 
778
    def setUp(self):
 
779
        super(TestLockableConfig, self).setUp()
 
780
        self._content = '[%s]\none=1\ntwo=2\n' % (self.config_section,)
 
781
        self.config = self.create_config(self._content)
 
782
 
 
783
    def get_existing_config(self):
 
784
        return self.config_class(*self.config_args)
 
785
 
 
786
    def create_config(self, content):
 
787
        kwargs = dict(save=True)
 
788
        c = self.config_class.from_string(content, *self.config_args, **kwargs)
 
789
        return c
 
790
 
 
791
    def test_simple_read_access(self):
 
792
        self.assertEquals('1', self.config.get_user_option('one'))
 
793
 
 
794
    def test_simple_write_access(self):
 
795
        self.config.set_user_option('one', 'one')
 
796
        self.assertEquals('one', self.config.get_user_option('one'))
 
797
 
 
798
    def test_listen_to_the_last_speaker(self):
 
799
        c1 = self.config
 
800
        c2 = self.get_existing_config()
 
801
        c1.set_user_option('one', 'ONE')
 
802
        c2.set_user_option('two', 'TWO')
 
803
        self.assertEquals('ONE', c1.get_user_option('one'))
 
804
        self.assertEquals('TWO', c2.get_user_option('two'))
 
805
        # The second update respect the first one
 
806
        self.assertEquals('ONE', c2.get_user_option('one'))
 
807
 
 
808
    def test_last_speaker_wins(self):
 
809
        # If the same config is not shared, the same variable modified twice
 
810
        # can only see a single result.
 
811
        c1 = self.config
 
812
        c2 = self.get_existing_config()
 
813
        c1.set_user_option('one', 'c1')
 
814
        c2.set_user_option('one', 'c2')
 
815
        self.assertEquals('c2', c2._get_user_option('one'))
 
816
        # The first modification is still available until another refresh
 
817
        # occur
 
818
        self.assertEquals('c1', c1._get_user_option('one'))
 
819
        c1.set_user_option('two', 'done')
 
820
        self.assertEquals('c2', c1._get_user_option('one'))
 
821
 
 
822
    def test_writes_are_serialized(self):
 
823
        c1 = self.config
 
824
        c2 = self.get_existing_config()
 
825
 
 
826
        # We spawn a thread that will pause *during* the write
 
827
        before_writing = threading.Event()
 
828
        after_writing = threading.Event()
 
829
        writing_done = threading.Event()
 
830
        c1_orig = c1._write_config_file
 
831
        def c1_write_config_file():
 
832
            before_writing.set()
 
833
            c1_orig()
 
834
            # The lock is held we wait for the main thread to decide when to
 
835
            # continue
 
836
            after_writing.wait()
 
837
        c1._write_config_file = c1_write_config_file
 
838
        def c1_set_option():
 
839
            c1.set_user_option('one', 'c1')
 
840
            writing_done.set()
 
841
        t1 = threading.Thread(target=c1_set_option)
 
842
        # Collect the thread after the test
 
843
        self.addCleanup(t1.join)
 
844
        # Be ready to unblock the thread if the test goes wrong
 
845
        self.addCleanup(after_writing.set)
 
846
        t1.start()
 
847
        before_writing.wait()
 
848
        self.assertTrue(c1._lock.is_held)
 
849
        self.assertRaises(errors.LockContention,
 
850
                          c2.set_user_option, 'one', 'c2')
 
851
        self.assertEquals('c1', c1.get_user_option('one'))
 
852
        # Let the lock be released
 
853
        after_writing.set()
 
854
        writing_done.wait()
 
855
        c2.set_user_option('one', 'c2')
 
856
        self.assertEquals('c2', c2.get_user_option('one'))
 
857
 
 
858
    def test_read_while_writing(self):
 
859
       c1 = self.config
 
860
       # We spawn a thread that will pause *during* the write
 
861
       ready_to_write = threading.Event()
 
862
       do_writing = threading.Event()
 
863
       writing_done = threading.Event()
 
864
       c1_orig = c1._write_config_file
 
865
       def c1_write_config_file():
 
866
           ready_to_write.set()
 
867
           # The lock is held we wait for the main thread to decide when to
 
868
           # continue
 
869
           do_writing.wait()
 
870
           c1_orig()
 
871
           writing_done.set()
 
872
       c1._write_config_file = c1_write_config_file
 
873
       def c1_set_option():
 
874
           c1.set_user_option('one', 'c1')
 
875
       t1 = threading.Thread(target=c1_set_option)
 
876
       # Collect the thread after the test
 
877
       self.addCleanup(t1.join)
 
878
       # Be ready to unblock the thread if the test goes wrong
 
879
       self.addCleanup(do_writing.set)
 
880
       t1.start()
 
881
       # Ensure the thread is ready to write
 
882
       ready_to_write.wait()
 
883
       self.assertTrue(c1._lock.is_held)
 
884
       self.assertEquals('c1', c1.get_user_option('one'))
 
885
       # If we read during the write, we get the old value
 
886
       c2 = self.get_existing_config()
 
887
       self.assertEquals('1', c2.get_user_option('one'))
 
888
       # Let the writing occur and ensure it occurred
 
889
       do_writing.set()
 
890
       writing_done.wait()
 
891
       # Now we get the updated value
 
892
       c3 = self.get_existing_config()
 
893
       self.assertEquals('c1', c3.get_user_option('one'))
 
894
 
 
895
 
 
896
class TestGetUserOptionAs(TestIniConfig):
 
897
 
 
898
    def test_get_user_option_as_bool(self):
 
899
        conf, parser = self.make_config_parser("""
 
900
a_true_bool = true
 
901
a_false_bool = 0
 
902
an_invalid_bool = maybe
 
903
a_list = hmm, who knows ? # This is interpreted as a list !
 
904
""")
 
905
        get_bool = conf.get_user_option_as_bool
 
906
        self.assertEqual(True, get_bool('a_true_bool'))
 
907
        self.assertEqual(False, get_bool('a_false_bool'))
 
908
        warnings = []
 
909
        def warning(*args):
 
910
            warnings.append(args[0] % args[1:])
 
911
        self.overrideAttr(trace, 'warning', warning)
 
912
        msg = 'Value "%s" is not a boolean for "%s"'
 
913
        self.assertIs(None, get_bool('an_invalid_bool'))
 
914
        self.assertEquals(msg % ('maybe', 'an_invalid_bool'), warnings[0])
 
915
        warnings = []
 
916
        self.assertIs(None, get_bool('not_defined_in_this_config'))
 
917
        self.assertEquals([], warnings)
 
918
 
 
919
    def test_get_user_option_as_list(self):
 
920
        conf, parser = self.make_config_parser("""
 
921
a_list = a,b,c
 
922
length_1 = 1,
 
923
one_item = x
 
924
""")
 
925
        get_list = conf.get_user_option_as_list
 
926
        self.assertEqual(['a', 'b', 'c'], get_list('a_list'))
 
927
        self.assertEqual(['1'], get_list('length_1'))
 
928
        self.assertEqual('x', conf.get_user_option('one_item'))
 
929
        # automatically cast to list
 
930
        self.assertEqual(['x'], get_list('one_item'))
 
931
 
 
932
 
 
933
class TestSupressWarning(TestIniConfig):
 
934
 
 
935
    def make_warnings_config(self, s):
 
936
        conf, parser = self.make_config_parser(s)
 
937
        return conf.suppress_warning
 
938
 
 
939
    def test_suppress_warning_unknown(self):
 
940
        suppress_warning = self.make_warnings_config('')
 
941
        self.assertEqual(False, suppress_warning('unknown_warning'))
 
942
 
 
943
    def test_suppress_warning_known(self):
 
944
        suppress_warning = self.make_warnings_config('suppress_warnings=a,b')
 
945
        self.assertEqual(False, suppress_warning('c'))
 
946
        self.assertEqual(True, suppress_warning('a'))
 
947
        self.assertEqual(True, suppress_warning('b'))
 
948
 
 
949
 
 
950
class TestGetConfig(tests.TestCase):
 
951
 
 
952
    def test_constructs(self):
 
953
        my_config = config.GlobalConfig()
 
954
 
 
955
    def test_calls_read_filenames(self):
 
956
        # replace the class that is constructed, to check its parameters
 
957
        oldparserclass = config.ConfigObj
 
958
        config.ConfigObj = InstrumentedConfigObj
 
959
        my_config = config.GlobalConfig()
 
960
        try:
 
961
            parser = my_config._get_parser()
 
962
        finally:
 
963
            config.ConfigObj = oldparserclass
 
964
        self.failUnless(isinstance(parser, InstrumentedConfigObj))
 
965
        self.assertEqual(parser._calls, [('__init__', config.config_filename(),
 
966
                                          'utf-8')])
 
967
 
 
968
 
 
969
class TestBranchConfig(tests.TestCaseWithTransport):
 
970
 
 
971
    def test_constructs(self):
 
972
        branch = FakeBranch()
 
973
        my_config = config.BranchConfig(branch)
 
974
        self.assertRaises(TypeError, config.BranchConfig)
 
975
 
 
976
    def test_get_location_config(self):
 
977
        branch = FakeBranch()
 
978
        my_config = config.BranchConfig(branch)
 
979
        location_config = my_config._get_location_config()
 
980
        self.assertEqual(branch.base, location_config.location)
 
981
        self.failUnless(location_config is my_config._get_location_config())
 
982
 
 
983
    def test_get_config(self):
 
984
        """The Branch.get_config method works properly"""
 
985
        b = bzrdir.BzrDir.create_standalone_workingtree('.').branch
 
986
        my_config = b.get_config()
 
987
        self.assertIs(my_config.get_user_option('wacky'), None)
 
988
        my_config.set_user_option('wacky', 'unlikely')
 
989
        self.assertEqual(my_config.get_user_option('wacky'), 'unlikely')
 
990
 
 
991
        # Ensure we get the same thing if we start again
 
992
        b2 = branch.Branch.open('.')
 
993
        my_config2 = b2.get_config()
 
994
        self.assertEqual(my_config2.get_user_option('wacky'), 'unlikely')
 
995
 
 
996
    def test_has_explicit_nickname(self):
 
997
        b = self.make_branch('.')
 
998
        self.assertFalse(b.get_config().has_explicit_nickname())
 
999
        b.nick = 'foo'
 
1000
        self.assertTrue(b.get_config().has_explicit_nickname())
 
1001
 
 
1002
    def test_config_url(self):
 
1003
        """The Branch.get_config will use section that uses a local url"""
 
1004
        branch = self.make_branch('branch')
 
1005
        self.assertEqual('branch', branch.nick)
 
1006
 
 
1007
        local_url = urlutils.local_path_to_url('branch')
 
1008
        conf = config.LocationConfig.from_string(
 
1009
            '[%s]\nnickname = foobar' % (local_url,),
 
1010
            local_url, save=True)
 
1011
        self.assertEqual('foobar', branch.nick)
 
1012
 
 
1013
    def test_config_local_path(self):
 
1014
        """The Branch.get_config will use a local system path"""
 
1015
        branch = self.make_branch('branch')
 
1016
        self.assertEqual('branch', branch.nick)
 
1017
 
 
1018
        local_path = osutils.getcwd().encode('utf8')
 
1019
        conf = config.LocationConfig.from_string(
 
1020
            '[%s/branch]\nnickname = barry' % (local_path,),
 
1021
            'branch',  save=True)
 
1022
        self.assertEqual('barry', branch.nick)
 
1023
 
 
1024
    def test_config_creates_local(self):
 
1025
        """Creating a new entry in config uses a local path."""
 
1026
        branch = self.make_branch('branch', format='knit')
 
1027
        branch.set_push_location('http://foobar')
 
1028
        local_path = osutils.getcwd().encode('utf8')
 
1029
        # Surprisingly ConfigObj doesn't create a trailing newline
 
1030
        self.check_file_contents(config.locations_config_filename(),
 
1031
                                 '[%s/branch]\n'
 
1032
                                 'push_location = http://foobar\n'
 
1033
                                 'push_location:policy = norecurse\n'
 
1034
                                 % (local_path,))
 
1035
 
 
1036
    def test_autonick_urlencoded(self):
 
1037
        b = self.make_branch('!repo')
 
1038
        self.assertEqual('!repo', b.get_config().get_nickname())
 
1039
 
 
1040
    def test_warn_if_masked(self):
 
1041
        warnings = []
 
1042
        def warning(*args):
 
1043
            warnings.append(args[0] % args[1:])
 
1044
        self.overrideAttr(trace, 'warning', warning)
 
1045
 
 
1046
        def set_option(store, warn_masked=True):
 
1047
            warnings[:] = []
 
1048
            conf.set_user_option('example_option', repr(store), store=store,
 
1049
                                 warn_masked=warn_masked)
 
1050
        def assertWarning(warning):
 
1051
            if warning is None:
 
1052
                self.assertEqual(0, len(warnings))
 
1053
            else:
 
1054
                self.assertEqual(1, len(warnings))
 
1055
                self.assertEqual(warning, warnings[0])
 
1056
        branch = self.make_branch('.')
 
1057
        conf = branch.get_config()
 
1058
        set_option(config.STORE_GLOBAL)
 
1059
        assertWarning(None)
 
1060
        set_option(config.STORE_BRANCH)
 
1061
        assertWarning(None)
 
1062
        set_option(config.STORE_GLOBAL)
 
1063
        assertWarning('Value "4" is masked by "3" from branch.conf')
 
1064
        set_option(config.STORE_GLOBAL, warn_masked=False)
 
1065
        assertWarning(None)
 
1066
        set_option(config.STORE_LOCATION)
 
1067
        assertWarning(None)
 
1068
        set_option(config.STORE_BRANCH)
 
1069
        assertWarning('Value "3" is masked by "0" from locations.conf')
 
1070
        set_option(config.STORE_BRANCH, warn_masked=False)
 
1071
        assertWarning(None)
 
1072
 
 
1073
 
 
1074
class TestGlobalConfigItems(tests.TestCaseInTempDir):
 
1075
 
 
1076
    def test_user_id(self):
 
1077
        my_config = config.GlobalConfig.from_string(sample_config_text)
 
1078
        self.assertEqual(u"Erik B\u00e5gfors <erik@bagfors.nu>",
 
1079
                         my_config._get_user_id())
 
1080
 
 
1081
    def test_absent_user_id(self):
 
1082
        my_config = config.GlobalConfig()
 
1083
        self.assertEqual(None, my_config._get_user_id())
 
1084
 
 
1085
    def test_configured_editor(self):
 
1086
        my_config = config.GlobalConfig.from_string(sample_config_text)
 
1087
        self.assertEqual("vim", my_config.get_editor())
 
1088
 
 
1089
    def test_signatures_always(self):
 
1090
        my_config = config.GlobalConfig.from_string(sample_always_signatures)
 
1091
        self.assertEqual(config.CHECK_NEVER,
 
1092
                         my_config.signature_checking())
 
1093
        self.assertEqual(config.SIGN_ALWAYS,
 
1094
                         my_config.signing_policy())
 
1095
        self.assertEqual(True, my_config.signature_needed())
 
1096
 
 
1097
    def test_signatures_if_possible(self):
 
1098
        my_config = config.GlobalConfig.from_string(sample_maybe_signatures)
 
1099
        self.assertEqual(config.CHECK_NEVER,
 
1100
                         my_config.signature_checking())
 
1101
        self.assertEqual(config.SIGN_WHEN_REQUIRED,
 
1102
                         my_config.signing_policy())
 
1103
        self.assertEqual(False, my_config.signature_needed())
 
1104
 
 
1105
    def test_signatures_ignore(self):
 
1106
        my_config = config.GlobalConfig.from_string(sample_ignore_signatures)
 
1107
        self.assertEqual(config.CHECK_ALWAYS,
 
1108
                         my_config.signature_checking())
 
1109
        self.assertEqual(config.SIGN_NEVER,
 
1110
                         my_config.signing_policy())
 
1111
        self.assertEqual(False, my_config.signature_needed())
 
1112
 
 
1113
    def _get_sample_config(self):
 
1114
        my_config = config.GlobalConfig.from_string(sample_config_text)
 
1115
        return my_config
 
1116
 
 
1117
    def test_gpg_signing_command(self):
 
1118
        my_config = self._get_sample_config()
 
1119
        self.assertEqual("gnome-gpg", my_config.gpg_signing_command())
 
1120
        self.assertEqual(False, my_config.signature_needed())
 
1121
 
 
1122
    def _get_empty_config(self):
 
1123
        my_config = config.GlobalConfig()
 
1124
        return my_config
 
1125
 
 
1126
    def test_gpg_signing_command_unset(self):
 
1127
        my_config = self._get_empty_config()
 
1128
        self.assertEqual("gpg", my_config.gpg_signing_command())
 
1129
 
 
1130
    def test_get_user_option_default(self):
 
1131
        my_config = self._get_empty_config()
 
1132
        self.assertEqual(None, my_config.get_user_option('no_option'))
 
1133
 
 
1134
    def test_get_user_option_global(self):
 
1135
        my_config = self._get_sample_config()
 
1136
        self.assertEqual("something",
 
1137
                         my_config.get_user_option('user_global_option'))
 
1138
 
 
1139
    def test_post_commit_default(self):
 
1140
        my_config = self._get_sample_config()
 
1141
        self.assertEqual(None, my_config.post_commit())
 
1142
 
 
1143
    def test_configured_logformat(self):
 
1144
        my_config = self._get_sample_config()
 
1145
        self.assertEqual("short", my_config.log_format())
 
1146
 
 
1147
    def test_get_alias(self):
 
1148
        my_config = self._get_sample_config()
 
1149
        self.assertEqual('help', my_config.get_alias('h'))
 
1150
 
 
1151
    def test_get_aliases(self):
 
1152
        my_config = self._get_sample_config()
 
1153
        aliases = my_config.get_aliases()
 
1154
        self.assertEqual(2, len(aliases))
 
1155
        sorted_keys = sorted(aliases)
 
1156
        self.assertEqual('help', aliases[sorted_keys[0]])
 
1157
        self.assertEqual(sample_long_alias, aliases[sorted_keys[1]])
 
1158
 
 
1159
    def test_get_no_alias(self):
 
1160
        my_config = self._get_sample_config()
 
1161
        self.assertEqual(None, my_config.get_alias('foo'))
 
1162
 
 
1163
    def test_get_long_alias(self):
 
1164
        my_config = self._get_sample_config()
 
1165
        self.assertEqual(sample_long_alias, my_config.get_alias('ll'))
 
1166
 
 
1167
    def test_get_change_editor(self):
 
1168
        my_config = self._get_sample_config()
 
1169
        change_editor = my_config.get_change_editor('old', 'new')
 
1170
        self.assertIs(diff.DiffFromTool, change_editor.__class__)
 
1171
        self.assertEqual('vimdiff -of @new_path @old_path',
 
1172
                         ' '.join(change_editor.command_template))
 
1173
 
 
1174
    def test_get_no_change_editor(self):
 
1175
        my_config = self._get_empty_config()
 
1176
        change_editor = my_config.get_change_editor('old', 'new')
 
1177
        self.assertIs(None, change_editor)
 
1178
 
 
1179
    def test_get_merge_tools(self):
 
1180
        conf = self._get_sample_config()
 
1181
        tools = conf.get_merge_tools()
 
1182
        self.log(repr(tools))
 
1183
        self.assertEqual(
 
1184
            {u'funkytool' : u'funkytool "arg with spaces" {this_temp}',
 
1185
            u'sometool' : u'sometool {base} {this} {other} -o {result}'},
 
1186
            tools)
 
1187
 
 
1188
    def test_get_merge_tools_empty(self):
 
1189
        conf = self._get_empty_config()
 
1190
        tools = conf.get_merge_tools()
 
1191
        self.assertEqual({}, tools)
 
1192
 
 
1193
    def test_find_merge_tool(self):
 
1194
        conf = self._get_sample_config()
 
1195
        cmdline = conf.find_merge_tool('sometool')
 
1196
        self.assertEqual('sometool {base} {this} {other} -o {result}', cmdline)
 
1197
 
 
1198
    def test_find_merge_tool_not_found(self):
 
1199
        conf = self._get_sample_config()
 
1200
        cmdline = conf.find_merge_tool('DOES NOT EXIST')
 
1201
        self.assertIs(cmdline, None)
 
1202
 
 
1203
    def test_find_merge_tool_known(self):
 
1204
        conf = self._get_empty_config()
 
1205
        cmdline = conf.find_merge_tool('kdiff3')
 
1206
        self.assertEquals('kdiff3 {base} {this} {other} -o {result}', cmdline)
 
1207
 
 
1208
    def test_find_merge_tool_override_known(self):
 
1209
        conf = self._get_empty_config()
 
1210
        conf.set_user_option('bzr.mergetool.kdiff3', 'kdiff3 blah')
 
1211
        cmdline = conf.find_merge_tool('kdiff3')
 
1212
        self.assertEqual('kdiff3 blah', cmdline)
 
1213
 
 
1214
 
 
1215
class TestGlobalConfigSavingOptions(tests.TestCaseInTempDir):
 
1216
 
 
1217
    def test_empty(self):
 
1218
        my_config = config.GlobalConfig()
 
1219
        self.assertEqual(0, len(my_config.get_aliases()))
 
1220
 
 
1221
    def test_set_alias(self):
 
1222
        my_config = config.GlobalConfig()
 
1223
        alias_value = 'commit --strict'
 
1224
        my_config.set_alias('commit', alias_value)
 
1225
        new_config = config.GlobalConfig()
 
1226
        self.assertEqual(alias_value, new_config.get_alias('commit'))
 
1227
 
 
1228
    def test_remove_alias(self):
 
1229
        my_config = config.GlobalConfig()
 
1230
        my_config.set_alias('commit', 'commit --strict')
 
1231
        # Now remove the alias again.
 
1232
        my_config.unset_alias('commit')
 
1233
        new_config = config.GlobalConfig()
 
1234
        self.assertIs(None, new_config.get_alias('commit'))
 
1235
 
 
1236
 
 
1237
class TestLocationConfig(tests.TestCaseInTempDir, TestOptionsMixin):
 
1238
 
 
1239
    def test_constructs(self):
 
1240
        my_config = config.LocationConfig('http://example.com')
 
1241
        self.assertRaises(TypeError, config.LocationConfig)
 
1242
 
 
1243
    def test_branch_calls_read_filenames(self):
 
1244
        # This is testing the correct file names are provided.
 
1245
        # TODO: consolidate with the test for GlobalConfigs filename checks.
 
1246
        #
 
1247
        # replace the class that is constructed, to check its parameters
 
1248
        oldparserclass = config.ConfigObj
 
1249
        config.ConfigObj = InstrumentedConfigObj
 
1250
        try:
 
1251
            my_config = config.LocationConfig('http://www.example.com')
 
1252
            parser = my_config._get_parser()
 
1253
        finally:
 
1254
            config.ConfigObj = oldparserclass
 
1255
        self.failUnless(isinstance(parser, InstrumentedConfigObj))
 
1256
        self.assertEqual(parser._calls,
 
1257
                         [('__init__', config.locations_config_filename(),
 
1258
                           'utf-8')])
 
1259
 
 
1260
    def test_get_global_config(self):
 
1261
        my_config = config.BranchConfig(FakeBranch('http://example.com'))
 
1262
        global_config = my_config._get_global_config()
 
1263
        self.failUnless(isinstance(global_config, config.GlobalConfig))
 
1264
        self.failUnless(global_config is my_config._get_global_config())
 
1265
 
 
1266
    def test__get_matching_sections_no_match(self):
 
1267
        self.get_branch_config('/')
 
1268
        self.assertEqual([], self.my_location_config._get_matching_sections())
 
1269
 
 
1270
    def test__get_matching_sections_exact(self):
 
1271
        self.get_branch_config('http://www.example.com')
 
1272
        self.assertEqual([('http://www.example.com', '')],
 
1273
                         self.my_location_config._get_matching_sections())
 
1274
 
 
1275
    def test__get_matching_sections_suffix_does_not(self):
 
1276
        self.get_branch_config('http://www.example.com-com')
 
1277
        self.assertEqual([], self.my_location_config._get_matching_sections())
 
1278
 
 
1279
    def test__get_matching_sections_subdir_recursive(self):
 
1280
        self.get_branch_config('http://www.example.com/com')
 
1281
        self.assertEqual([('http://www.example.com', 'com')],
 
1282
                         self.my_location_config._get_matching_sections())
 
1283
 
 
1284
    def test__get_matching_sections_ignoreparent(self):
 
1285
        self.get_branch_config('http://www.example.com/ignoreparent')
 
1286
        self.assertEqual([('http://www.example.com/ignoreparent', '')],
 
1287
                         self.my_location_config._get_matching_sections())
 
1288
 
 
1289
    def test__get_matching_sections_ignoreparent_subdir(self):
 
1290
        self.get_branch_config(
 
1291
            'http://www.example.com/ignoreparent/childbranch')
 
1292
        self.assertEqual([('http://www.example.com/ignoreparent',
 
1293
                           'childbranch')],
 
1294
                         self.my_location_config._get_matching_sections())
 
1295
 
 
1296
    def test__get_matching_sections_subdir_trailing_slash(self):
 
1297
        self.get_branch_config('/b')
 
1298
        self.assertEqual([('/b/', '')],
 
1299
                         self.my_location_config._get_matching_sections())
 
1300
 
 
1301
    def test__get_matching_sections_subdir_child(self):
 
1302
        self.get_branch_config('/a/foo')
 
1303
        self.assertEqual([('/a/*', ''), ('/a/', 'foo')],
 
1304
                         self.my_location_config._get_matching_sections())
 
1305
 
 
1306
    def test__get_matching_sections_subdir_child_child(self):
 
1307
        self.get_branch_config('/a/foo/bar')
 
1308
        self.assertEqual([('/a/*', 'bar'), ('/a/', 'foo/bar')],
 
1309
                         self.my_location_config._get_matching_sections())
 
1310
 
 
1311
    def test__get_matching_sections_trailing_slash_with_children(self):
 
1312
        self.get_branch_config('/a/')
 
1313
        self.assertEqual([('/a/', '')],
 
1314
                         self.my_location_config._get_matching_sections())
 
1315
 
 
1316
    def test__get_matching_sections_explicit_over_glob(self):
 
1317
        # XXX: 2006-09-08 jamesh
 
1318
        # This test only passes because ord('c') > ord('*').  If there
 
1319
        # was a config section for '/a/?', it would get precedence
 
1320
        # over '/a/c'.
 
1321
        self.get_branch_config('/a/c')
 
1322
        self.assertEqual([('/a/c', ''), ('/a/*', ''), ('/a/', 'c')],
 
1323
                         self.my_location_config._get_matching_sections())
 
1324
 
 
1325
    def test__get_option_policy_normal(self):
 
1326
        self.get_branch_config('http://www.example.com')
 
1327
        self.assertEqual(
 
1328
            self.my_location_config._get_config_policy(
 
1329
            'http://www.example.com', 'normal_option'),
 
1330
            config.POLICY_NONE)
 
1331
 
 
1332
    def test__get_option_policy_norecurse(self):
 
1333
        self.get_branch_config('http://www.example.com')
 
1334
        self.assertEqual(
 
1335
            self.my_location_config._get_option_policy(
 
1336
            'http://www.example.com', 'norecurse_option'),
 
1337
            config.POLICY_NORECURSE)
 
1338
        # Test old recurse=False setting:
 
1339
        self.assertEqual(
 
1340
            self.my_location_config._get_option_policy(
 
1341
            'http://www.example.com/norecurse', 'normal_option'),
 
1342
            config.POLICY_NORECURSE)
 
1343
 
 
1344
    def test__get_option_policy_normal(self):
 
1345
        self.get_branch_config('http://www.example.com')
 
1346
        self.assertEqual(
 
1347
            self.my_location_config._get_option_policy(
 
1348
            'http://www.example.com', 'appendpath_option'),
 
1349
            config.POLICY_APPENDPATH)
 
1350
 
 
1351
    def test__get_options_with_policy(self):
 
1352
        self.get_branch_config('/dir/subdir',
 
1353
                               location_config="""\
 
1354
[/dir]
 
1355
other_url = /other-dir
 
1356
other_url:policy = appendpath
 
1357
[/dir/subdir]
 
1358
other_url = /other-subdir
 
1359
""")
 
1360
        self.assertOptions(
 
1361
            [(u'other_url', u'/other-subdir', u'/dir/subdir', 'locations'),
 
1362
             (u'other_url', u'/other-dir', u'/dir', 'locations'),
 
1363
             (u'other_url:policy', u'appendpath', u'/dir', 'locations')],
 
1364
            self.my_location_config)
 
1365
 
 
1366
    def test_location_without_username(self):
 
1367
        self.get_branch_config('http://www.example.com/ignoreparent')
 
1368
        self.assertEqual(u'Erik B\u00e5gfors <erik@bagfors.nu>',
 
1369
                         self.my_config.username())
 
1370
 
 
1371
    def test_location_not_listed(self):
 
1372
        """Test that the global username is used when no location matches"""
 
1373
        self.get_branch_config('/home/robertc/sources')
 
1374
        self.assertEqual(u'Erik B\u00e5gfors <erik@bagfors.nu>',
 
1375
                         self.my_config.username())
 
1376
 
 
1377
    def test_overriding_location(self):
 
1378
        self.get_branch_config('http://www.example.com/foo')
 
1379
        self.assertEqual('Robert Collins <robertc@example.org>',
 
1380
                         self.my_config.username())
 
1381
 
 
1382
    def test_signatures_not_set(self):
 
1383
        self.get_branch_config('http://www.example.com',
 
1384
                                 global_config=sample_ignore_signatures)
 
1385
        self.assertEqual(config.CHECK_ALWAYS,
 
1386
                         self.my_config.signature_checking())
 
1387
        self.assertEqual(config.SIGN_NEVER,
 
1388
                         self.my_config.signing_policy())
 
1389
 
 
1390
    def test_signatures_never(self):
 
1391
        self.get_branch_config('/a/c')
 
1392
        self.assertEqual(config.CHECK_NEVER,
 
1393
                         self.my_config.signature_checking())
 
1394
 
 
1395
    def test_signatures_when_available(self):
 
1396
        self.get_branch_config('/a/', global_config=sample_ignore_signatures)
 
1397
        self.assertEqual(config.CHECK_IF_POSSIBLE,
 
1398
                         self.my_config.signature_checking())
 
1399
 
 
1400
    def test_signatures_always(self):
 
1401
        self.get_branch_config('/b')
 
1402
        self.assertEqual(config.CHECK_ALWAYS,
 
1403
                         self.my_config.signature_checking())
 
1404
 
 
1405
    def test_gpg_signing_command(self):
 
1406
        self.get_branch_config('/b')
 
1407
        self.assertEqual("gnome-gpg", self.my_config.gpg_signing_command())
 
1408
 
 
1409
    def test_gpg_signing_command_missing(self):
 
1410
        self.get_branch_config('/a')
 
1411
        self.assertEqual("false", self.my_config.gpg_signing_command())
 
1412
 
 
1413
    def test_get_user_option_global(self):
 
1414
        self.get_branch_config('/a')
 
1415
        self.assertEqual('something',
 
1416
                         self.my_config.get_user_option('user_global_option'))
 
1417
 
 
1418
    def test_get_user_option_local(self):
 
1419
        self.get_branch_config('/a')
 
1420
        self.assertEqual('local',
 
1421
                         self.my_config.get_user_option('user_local_option'))
 
1422
 
 
1423
    def test_get_user_option_appendpath(self):
 
1424
        # returned as is for the base path:
 
1425
        self.get_branch_config('http://www.example.com')
 
1426
        self.assertEqual('append',
 
1427
                         self.my_config.get_user_option('appendpath_option'))
 
1428
        # Extra path components get appended:
 
1429
        self.get_branch_config('http://www.example.com/a/b/c')
 
1430
        self.assertEqual('append/a/b/c',
 
1431
                         self.my_config.get_user_option('appendpath_option'))
 
1432
        # Overriden for http://www.example.com/dir, where it is a
 
1433
        # normal option:
 
1434
        self.get_branch_config('http://www.example.com/dir/a/b/c')
 
1435
        self.assertEqual('normal',
 
1436
                         self.my_config.get_user_option('appendpath_option'))
 
1437
 
 
1438
    def test_get_user_option_norecurse(self):
 
1439
        self.get_branch_config('http://www.example.com')
 
1440
        self.assertEqual('norecurse',
 
1441
                         self.my_config.get_user_option('norecurse_option'))
 
1442
        self.get_branch_config('http://www.example.com/dir')
 
1443
        self.assertEqual(None,
 
1444
                         self.my_config.get_user_option('norecurse_option'))
 
1445
        # http://www.example.com/norecurse is a recurse=False section
 
1446
        # that redefines normal_option.  Subdirectories do not pick up
 
1447
        # this redefinition.
 
1448
        self.get_branch_config('http://www.example.com/norecurse')
 
1449
        self.assertEqual('norecurse',
 
1450
                         self.my_config.get_user_option('normal_option'))
 
1451
        self.get_branch_config('http://www.example.com/norecurse/subdir')
 
1452
        self.assertEqual('normal',
 
1453
                         self.my_config.get_user_option('normal_option'))
 
1454
 
 
1455
    def test_set_user_option_norecurse(self):
 
1456
        self.get_branch_config('http://www.example.com')
 
1457
        self.my_config.set_user_option('foo', 'bar',
 
1458
                                       store=config.STORE_LOCATION_NORECURSE)
 
1459
        self.assertEqual(
 
1460
            self.my_location_config._get_option_policy(
 
1461
            'http://www.example.com', 'foo'),
 
1462
            config.POLICY_NORECURSE)
 
1463
 
 
1464
    def test_set_user_option_appendpath(self):
 
1465
        self.get_branch_config('http://www.example.com')
 
1466
        self.my_config.set_user_option('foo', 'bar',
 
1467
                                       store=config.STORE_LOCATION_APPENDPATH)
 
1468
        self.assertEqual(
 
1469
            self.my_location_config._get_option_policy(
 
1470
            'http://www.example.com', 'foo'),
 
1471
            config.POLICY_APPENDPATH)
 
1472
 
 
1473
    def test_set_user_option_change_policy(self):
 
1474
        self.get_branch_config('http://www.example.com')
 
1475
        self.my_config.set_user_option('norecurse_option', 'normal',
 
1476
                                       store=config.STORE_LOCATION)
 
1477
        self.assertEqual(
 
1478
            self.my_location_config._get_option_policy(
 
1479
            'http://www.example.com', 'norecurse_option'),
 
1480
            config.POLICY_NONE)
 
1481
 
 
1482
    def test_set_user_option_recurse_false_section(self):
 
1483
        # The following section has recurse=False set.  The test is to
 
1484
        # make sure that a normal option can be added to the section,
 
1485
        # converting recurse=False to the norecurse policy.
 
1486
        self.get_branch_config('http://www.example.com/norecurse')
 
1487
        self.callDeprecated(['The recurse option is deprecated as of 0.14.  '
 
1488
                             'The section "http://www.example.com/norecurse" '
 
1489
                             'has been converted to use policies.'],
 
1490
                            self.my_config.set_user_option,
 
1491
                            'foo', 'bar', store=config.STORE_LOCATION)
 
1492
        self.assertEqual(
 
1493
            self.my_location_config._get_option_policy(
 
1494
            'http://www.example.com/norecurse', 'foo'),
 
1495
            config.POLICY_NONE)
 
1496
        # The previously existing option is still norecurse:
 
1497
        self.assertEqual(
 
1498
            self.my_location_config._get_option_policy(
 
1499
            'http://www.example.com/norecurse', 'normal_option'),
 
1500
            config.POLICY_NORECURSE)
 
1501
 
 
1502
    def test_post_commit_default(self):
 
1503
        self.get_branch_config('/a/c')
 
1504
        self.assertEqual('bzrlib.tests.test_config.post_commit',
 
1505
                         self.my_config.post_commit())
 
1506
 
 
1507
    def get_branch_config(self, location, global_config=None,
 
1508
                          location_config=None):
 
1509
        my_branch = FakeBranch(location)
 
1510
        if global_config is None:
 
1511
            global_config = sample_config_text
 
1512
        if location_config is None:
 
1513
            location_config = sample_branches_text
 
1514
 
 
1515
        my_global_config = config.GlobalConfig.from_string(global_config,
 
1516
                                                           save=True)
 
1517
        my_location_config = config.LocationConfig.from_string(
 
1518
            location_config, my_branch.base, save=True)
 
1519
        my_config = config.BranchConfig(my_branch)
 
1520
        self.my_config = my_config
 
1521
        self.my_location_config = my_config._get_location_config()
 
1522
 
 
1523
    def test_set_user_setting_sets_and_saves(self):
 
1524
        self.get_branch_config('/a/c')
 
1525
        record = InstrumentedConfigObj("foo")
 
1526
        self.my_location_config._parser = record
 
1527
 
 
1528
        self.callDeprecated(['The recurse option is deprecated as of '
 
1529
                             '0.14.  The section "/a/c" has been '
 
1530
                             'converted to use policies.'],
 
1531
                            self.my_config.set_user_option,
 
1532
                            'foo', 'bar', store=config.STORE_LOCATION)
 
1533
        self.assertEqual([('reload',),
 
1534
                          ('__contains__', '/a/c'),
 
1535
                          ('__contains__', '/a/c/'),
 
1536
                          ('__setitem__', '/a/c', {}),
 
1537
                          ('__getitem__', '/a/c'),
 
1538
                          ('__setitem__', 'foo', 'bar'),
 
1539
                          ('__getitem__', '/a/c'),
 
1540
                          ('as_bool', 'recurse'),
 
1541
                          ('__getitem__', '/a/c'),
 
1542
                          ('__delitem__', 'recurse'),
 
1543
                          ('__getitem__', '/a/c'),
 
1544
                          ('keys',),
 
1545
                          ('__getitem__', '/a/c'),
 
1546
                          ('__contains__', 'foo:policy'),
 
1547
                          ('write',)],
 
1548
                         record._calls[1:])
 
1549
 
 
1550
    def test_set_user_setting_sets_and_saves2(self):
 
1551
        self.get_branch_config('/a/c')
 
1552
        self.assertIs(self.my_config.get_user_option('foo'), None)
 
1553
        self.my_config.set_user_option('foo', 'bar')
 
1554
        self.assertEqual(
 
1555
            self.my_config.branch.control_files.files['branch.conf'].strip(),
 
1556
            'foo = bar')
 
1557
        self.assertEqual(self.my_config.get_user_option('foo'), 'bar')
 
1558
        self.my_config.set_user_option('foo', 'baz',
 
1559
                                       store=config.STORE_LOCATION)
 
1560
        self.assertEqual(self.my_config.get_user_option('foo'), 'baz')
 
1561
        self.my_config.set_user_option('foo', 'qux')
 
1562
        self.assertEqual(self.my_config.get_user_option('foo'), 'baz')
 
1563
 
 
1564
    def test_get_bzr_remote_path(self):
 
1565
        my_config = config.LocationConfig('/a/c')
 
1566
        self.assertEqual('bzr', my_config.get_bzr_remote_path())
 
1567
        my_config.set_user_option('bzr_remote_path', '/path-bzr')
 
1568
        self.assertEqual('/path-bzr', my_config.get_bzr_remote_path())
 
1569
        self.overrideEnv('BZR_REMOTE_PATH', '/environ-bzr')
 
1570
        self.assertEqual('/environ-bzr', my_config.get_bzr_remote_path())
 
1571
 
 
1572
 
 
1573
precedence_global = 'option = global'
 
1574
precedence_branch = 'option = branch'
 
1575
precedence_location = """
 
1576
[http://]
 
1577
recurse = true
 
1578
option = recurse
 
1579
[http://example.com/specific]
 
1580
option = exact
 
1581
"""
 
1582
 
 
1583
class TestBranchConfigItems(tests.TestCaseInTempDir):
 
1584
 
 
1585
    def get_branch_config(self, global_config=None, location=None,
 
1586
                          location_config=None, branch_data_config=None):
 
1587
        my_branch = FakeBranch(location)
 
1588
        if global_config is not None:
 
1589
            my_global_config = config.GlobalConfig.from_string(global_config,
 
1590
                                                               save=True)
 
1591
        if location_config is not None:
 
1592
            my_location_config = config.LocationConfig.from_string(
 
1593
                location_config, my_branch.base, save=True)
 
1594
        my_config = config.BranchConfig(my_branch)
 
1595
        if branch_data_config is not None:
 
1596
            my_config.branch.control_files.files['branch.conf'] = \
 
1597
                branch_data_config
 
1598
        return my_config
 
1599
 
 
1600
    def test_user_id(self):
 
1601
        branch = FakeBranch(user_id='Robert Collins <robertc@example.net>')
 
1602
        my_config = config.BranchConfig(branch)
 
1603
        self.assertEqual("Robert Collins <robertc@example.net>",
 
1604
                         my_config.username())
 
1605
        my_config.branch.control_files.files['email'] = "John"
 
1606
        my_config.set_user_option('email',
 
1607
                                  "Robert Collins <robertc@example.org>")
 
1608
        self.assertEqual("John", my_config.username())
 
1609
        del my_config.branch.control_files.files['email']
 
1610
        self.assertEqual("Robert Collins <robertc@example.org>",
 
1611
                         my_config.username())
 
1612
 
 
1613
    def test_not_set_in_branch(self):
 
1614
        my_config = self.get_branch_config(global_config=sample_config_text)
 
1615
        self.assertEqual(u"Erik B\u00e5gfors <erik@bagfors.nu>",
 
1616
                         my_config._get_user_id())
 
1617
        my_config.branch.control_files.files['email'] = "John"
 
1618
        self.assertEqual("John", my_config._get_user_id())
 
1619
 
 
1620
    def test_BZR_EMAIL_OVERRIDES(self):
 
1621
        self.overrideEnv('BZR_EMAIL', "Robert Collins <robertc@example.org>")
 
1622
        branch = FakeBranch()
 
1623
        my_config = config.BranchConfig(branch)
 
1624
        self.assertEqual("Robert Collins <robertc@example.org>",
 
1625
                         my_config.username())
 
1626
 
 
1627
    def test_signatures_forced(self):
 
1628
        my_config = self.get_branch_config(
 
1629
            global_config=sample_always_signatures)
 
1630
        self.assertEqual(config.CHECK_NEVER, my_config.signature_checking())
 
1631
        self.assertEqual(config.SIGN_ALWAYS, my_config.signing_policy())
 
1632
        self.assertTrue(my_config.signature_needed())
 
1633
 
 
1634
    def test_signatures_forced_branch(self):
 
1635
        my_config = self.get_branch_config(
 
1636
            global_config=sample_ignore_signatures,
 
1637
            branch_data_config=sample_always_signatures)
 
1638
        self.assertEqual(config.CHECK_NEVER, my_config.signature_checking())
 
1639
        self.assertEqual(config.SIGN_ALWAYS, my_config.signing_policy())
 
1640
        self.assertTrue(my_config.signature_needed())
 
1641
 
 
1642
    def test_gpg_signing_command(self):
 
1643
        my_config = self.get_branch_config(
 
1644
            global_config=sample_config_text,
 
1645
            # branch data cannot set gpg_signing_command
 
1646
            branch_data_config="gpg_signing_command=pgp")
 
1647
        self.assertEqual('gnome-gpg', my_config.gpg_signing_command())
 
1648
 
 
1649
    def test_get_user_option_global(self):
 
1650
        my_config = self.get_branch_config(global_config=sample_config_text)
 
1651
        self.assertEqual('something',
 
1652
                         my_config.get_user_option('user_global_option'))
 
1653
 
 
1654
    def test_post_commit_default(self):
 
1655
        my_config = self.get_branch_config(global_config=sample_config_text,
 
1656
                                      location='/a/c',
 
1657
                                      location_config=sample_branches_text)
 
1658
        self.assertEqual(my_config.branch.base, '/a/c')
 
1659
        self.assertEqual('bzrlib.tests.test_config.post_commit',
 
1660
                         my_config.post_commit())
 
1661
        my_config.set_user_option('post_commit', 'rmtree_root')
 
1662
        # post-commit is ignored when present in branch data
 
1663
        self.assertEqual('bzrlib.tests.test_config.post_commit',
 
1664
                         my_config.post_commit())
 
1665
        my_config.set_user_option('post_commit', 'rmtree_root',
 
1666
                                  store=config.STORE_LOCATION)
 
1667
        self.assertEqual('rmtree_root', my_config.post_commit())
 
1668
 
 
1669
    def test_config_precedence(self):
 
1670
        # FIXME: eager test, luckily no persitent config file makes it fail
 
1671
        # -- vila 20100716
 
1672
        my_config = self.get_branch_config(global_config=precedence_global)
 
1673
        self.assertEqual(my_config.get_user_option('option'), 'global')
 
1674
        my_config = self.get_branch_config(global_config=precedence_global,
 
1675
                                           branch_data_config=precedence_branch)
 
1676
        self.assertEqual(my_config.get_user_option('option'), 'branch')
 
1677
        my_config = self.get_branch_config(
 
1678
            global_config=precedence_global,
 
1679
            branch_data_config=precedence_branch,
 
1680
            location_config=precedence_location)
 
1681
        self.assertEqual(my_config.get_user_option('option'), 'recurse')
 
1682
        my_config = self.get_branch_config(
 
1683
            global_config=precedence_global,
 
1684
            branch_data_config=precedence_branch,
 
1685
            location_config=precedence_location,
 
1686
            location='http://example.com/specific')
 
1687
        self.assertEqual(my_config.get_user_option('option'), 'exact')
 
1688
 
 
1689
    def test_get_mail_client(self):
 
1690
        config = self.get_branch_config()
 
1691
        client = config.get_mail_client()
 
1692
        self.assertIsInstance(client, mail_client.DefaultMail)
 
1693
 
 
1694
        # Specific clients
 
1695
        config.set_user_option('mail_client', 'evolution')
 
1696
        client = config.get_mail_client()
 
1697
        self.assertIsInstance(client, mail_client.Evolution)
 
1698
 
 
1699
        config.set_user_option('mail_client', 'kmail')
 
1700
        client = config.get_mail_client()
 
1701
        self.assertIsInstance(client, mail_client.KMail)
 
1702
 
 
1703
        config.set_user_option('mail_client', 'mutt')
 
1704
        client = config.get_mail_client()
 
1705
        self.assertIsInstance(client, mail_client.Mutt)
 
1706
 
 
1707
        config.set_user_option('mail_client', 'thunderbird')
 
1708
        client = config.get_mail_client()
 
1709
        self.assertIsInstance(client, mail_client.Thunderbird)
 
1710
 
 
1711
        # Generic options
 
1712
        config.set_user_option('mail_client', 'default')
 
1713
        client = config.get_mail_client()
 
1714
        self.assertIsInstance(client, mail_client.DefaultMail)
 
1715
 
 
1716
        config.set_user_option('mail_client', 'editor')
 
1717
        client = config.get_mail_client()
 
1718
        self.assertIsInstance(client, mail_client.Editor)
 
1719
 
 
1720
        config.set_user_option('mail_client', 'mapi')
 
1721
        client = config.get_mail_client()
 
1722
        self.assertIsInstance(client, mail_client.MAPIClient)
 
1723
 
 
1724
        config.set_user_option('mail_client', 'xdg-email')
 
1725
        client = config.get_mail_client()
 
1726
        self.assertIsInstance(client, mail_client.XDGEmail)
 
1727
 
 
1728
        config.set_user_option('mail_client', 'firebird')
 
1729
        self.assertRaises(errors.UnknownMailClient, config.get_mail_client)
 
1730
 
 
1731
 
 
1732
class TestMailAddressExtraction(tests.TestCase):
 
1733
 
 
1734
    def test_extract_email_address(self):
 
1735
        self.assertEqual('jane@test.com',
 
1736
                         config.extract_email_address('Jane <jane@test.com>'))
 
1737
        self.assertRaises(errors.NoEmailInUsername,
 
1738
                          config.extract_email_address, 'Jane Tester')
 
1739
 
 
1740
    def test_parse_username(self):
 
1741
        self.assertEqual(('', 'jdoe@example.com'),
 
1742
                         config.parse_username('jdoe@example.com'))
 
1743
        self.assertEqual(('', 'jdoe@example.com'),
 
1744
                         config.parse_username('<jdoe@example.com>'))
 
1745
        self.assertEqual(('John Doe', 'jdoe@example.com'),
 
1746
                         config.parse_username('John Doe <jdoe@example.com>'))
 
1747
        self.assertEqual(('John Doe', ''),
 
1748
                         config.parse_username('John Doe'))
 
1749
        self.assertEqual(('John Doe', 'jdoe@example.com'),
 
1750
                         config.parse_username('John Doe jdoe@example.com'))
 
1751
 
 
1752
class TestTreeConfig(tests.TestCaseWithTransport):
 
1753
 
 
1754
    def test_get_value(self):
 
1755
        """Test that retreiving a value from a section is possible"""
 
1756
        branch = self.make_branch('.')
 
1757
        tree_config = config.TreeConfig(branch)
 
1758
        tree_config.set_option('value', 'key', 'SECTION')
 
1759
        tree_config.set_option('value2', 'key2')
 
1760
        tree_config.set_option('value3-top', 'key3')
 
1761
        tree_config.set_option('value3-section', 'key3', 'SECTION')
 
1762
        value = tree_config.get_option('key', 'SECTION')
 
1763
        self.assertEqual(value, 'value')
 
1764
        value = tree_config.get_option('key2')
 
1765
        self.assertEqual(value, 'value2')
 
1766
        self.assertEqual(tree_config.get_option('non-existant'), None)
 
1767
        value = tree_config.get_option('non-existant', 'SECTION')
 
1768
        self.assertEqual(value, None)
 
1769
        value = tree_config.get_option('non-existant', default='default')
 
1770
        self.assertEqual(value, 'default')
 
1771
        self.assertEqual(tree_config.get_option('key2', 'NOSECTION'), None)
 
1772
        value = tree_config.get_option('key2', 'NOSECTION', default='default')
 
1773
        self.assertEqual(value, 'default')
 
1774
        value = tree_config.get_option('key3')
 
1775
        self.assertEqual(value, 'value3-top')
 
1776
        value = tree_config.get_option('key3', 'SECTION')
 
1777
        self.assertEqual(value, 'value3-section')
 
1778
 
 
1779
 
 
1780
class TestTransportConfig(tests.TestCaseWithTransport):
 
1781
 
 
1782
    def test_get_value(self):
 
1783
        """Test that retreiving a value from a section is possible"""
 
1784
        bzrdir_config = config.TransportConfig(transport.get_transport('.'),
 
1785
                                               'control.conf')
 
1786
        bzrdir_config.set_option('value', 'key', 'SECTION')
 
1787
        bzrdir_config.set_option('value2', 'key2')
 
1788
        bzrdir_config.set_option('value3-top', 'key3')
 
1789
        bzrdir_config.set_option('value3-section', 'key3', 'SECTION')
 
1790
        value = bzrdir_config.get_option('key', 'SECTION')
 
1791
        self.assertEqual(value, 'value')
 
1792
        value = bzrdir_config.get_option('key2')
 
1793
        self.assertEqual(value, 'value2')
 
1794
        self.assertEqual(bzrdir_config.get_option('non-existant'), None)
 
1795
        value = bzrdir_config.get_option('non-existant', 'SECTION')
 
1796
        self.assertEqual(value, None)
 
1797
        value = bzrdir_config.get_option('non-existant', default='default')
 
1798
        self.assertEqual(value, 'default')
 
1799
        self.assertEqual(bzrdir_config.get_option('key2', 'NOSECTION'), None)
 
1800
        value = bzrdir_config.get_option('key2', 'NOSECTION',
 
1801
                                         default='default')
 
1802
        self.assertEqual(value, 'default')
 
1803
        value = bzrdir_config.get_option('key3')
 
1804
        self.assertEqual(value, 'value3-top')
 
1805
        value = bzrdir_config.get_option('key3', 'SECTION')
 
1806
        self.assertEqual(value, 'value3-section')
 
1807
 
 
1808
    def test_set_unset_default_stack_on(self):
 
1809
        my_dir = self.make_bzrdir('.')
 
1810
        bzrdir_config = config.BzrDirConfig(my_dir)
 
1811
        self.assertIs(None, bzrdir_config.get_default_stack_on())
 
1812
        bzrdir_config.set_default_stack_on('Foo')
 
1813
        self.assertEqual('Foo', bzrdir_config._config.get_option(
 
1814
                         'default_stack_on'))
 
1815
        self.assertEqual('Foo', bzrdir_config.get_default_stack_on())
 
1816
        bzrdir_config.set_default_stack_on(None)
 
1817
        self.assertIs(None, bzrdir_config.get_default_stack_on())
 
1818
 
 
1819
 
 
1820
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
 
1821
 
 
1822
    def setUp(self):
 
1823
        super(TestConfigGetOptions, self).setUp()
 
1824
        create_configs(self)
 
1825
 
 
1826
    # One variable in none of the above
 
1827
    def test_no_variable(self):
 
1828
        # Using branch should query branch, locations and bazaar
 
1829
        self.assertOptions([], self.branch_config)
 
1830
 
 
1831
    def test_option_in_bazaar(self):
 
1832
        self.bazaar_config.set_user_option('file', 'bazaar')
 
1833
        self.assertOptions([('file', 'bazaar', 'DEFAULT', 'bazaar')],
 
1834
                           self.bazaar_config)
 
1835
 
 
1836
    def test_option_in_locations(self):
 
1837
        self.locations_config.set_user_option('file', 'locations')
 
1838
        self.assertOptions(
 
1839
            [('file', 'locations', self.tree.basedir, 'locations')],
 
1840
            self.locations_config)
 
1841
 
 
1842
    def test_option_in_branch(self):
 
1843
        self.branch_config.set_user_option('file', 'branch')
 
1844
        self.assertOptions([('file', 'branch', 'DEFAULT', 'branch')],
 
1845
                           self.branch_config)
 
1846
 
 
1847
    def test_option_in_bazaar_and_branch(self):
 
1848
        self.bazaar_config.set_user_option('file', 'bazaar')
 
1849
        self.branch_config.set_user_option('file', 'branch')
 
1850
        self.assertOptions([('file', 'branch', 'DEFAULT', 'branch'),
 
1851
                            ('file', 'bazaar', 'DEFAULT', 'bazaar'),],
 
1852
                           self.branch_config)
 
1853
 
 
1854
    def test_option_in_branch_and_locations(self):
 
1855
        # Hmm, locations override branch :-/
 
1856
        self.locations_config.set_user_option('file', 'locations')
 
1857
        self.branch_config.set_user_option('file', 'branch')
 
1858
        self.assertOptions(
 
1859
            [('file', 'locations', self.tree.basedir, 'locations'),
 
1860
             ('file', 'branch', 'DEFAULT', 'branch'),],
 
1861
            self.branch_config)
 
1862
 
 
1863
    def test_option_in_bazaar_locations_and_branch(self):
 
1864
        self.bazaar_config.set_user_option('file', 'bazaar')
 
1865
        self.locations_config.set_user_option('file', 'locations')
 
1866
        self.branch_config.set_user_option('file', 'branch')
 
1867
        self.assertOptions(
 
1868
            [('file', 'locations', self.tree.basedir, 'locations'),
 
1869
             ('file', 'branch', 'DEFAULT', 'branch'),
 
1870
             ('file', 'bazaar', 'DEFAULT', 'bazaar'),],
 
1871
            self.branch_config)
 
1872
 
 
1873
 
 
1874
class TestConfigRemoveOption(tests.TestCaseWithTransport, TestOptionsMixin):
 
1875
 
 
1876
    def setUp(self):
 
1877
        super(TestConfigRemoveOption, self).setUp()
 
1878
        create_configs_with_file_option(self)
 
1879
 
 
1880
    def test_remove_in_locations(self):
 
1881
        self.locations_config.remove_user_option('file', self.tree.basedir)
 
1882
        self.assertOptions(
 
1883
            [('file', 'branch', 'DEFAULT', 'branch'),
 
1884
             ('file', 'bazaar', 'DEFAULT', 'bazaar'),],
 
1885
            self.branch_config)
 
1886
 
 
1887
    def test_remove_in_branch(self):
 
1888
        self.branch_config.remove_user_option('file')
 
1889
        self.assertOptions(
 
1890
            [('file', 'locations', self.tree.basedir, 'locations'),
 
1891
             ('file', 'bazaar', 'DEFAULT', 'bazaar'),],
 
1892
            self.branch_config)
 
1893
 
 
1894
    def test_remove_in_bazaar(self):
 
1895
        self.bazaar_config.remove_user_option('file')
 
1896
        self.assertOptions(
 
1897
            [('file', 'locations', self.tree.basedir, 'locations'),
 
1898
             ('file', 'branch', 'DEFAULT', 'branch'),],
 
1899
            self.branch_config)
 
1900
 
 
1901
 
 
1902
class TestConfigGetSections(tests.TestCaseWithTransport):
 
1903
 
 
1904
    def setUp(self):
 
1905
        super(TestConfigGetSections, self).setUp()
 
1906
        create_configs(self)
 
1907
 
 
1908
    def assertSectionNames(self, expected, conf, name=None):
 
1909
        """Check which sections are returned for a given config.
 
1910
 
 
1911
        If fallback configurations exist their sections can be included.
 
1912
 
 
1913
        :param expected: A list of section names.
 
1914
 
 
1915
        :param conf: The configuration that will be queried.
 
1916
 
 
1917
        :param name: An optional section name that will be passed to
 
1918
            get_sections().
 
1919
        """
 
1920
        sections = list(conf._get_sections(name))
 
1921
        self.assertLength(len(expected), sections)
 
1922
        self.assertEqual(expected, [name for name, _, _ in sections])
 
1923
 
 
1924
    def test_bazaar_default_section(self):
 
1925
        self.assertSectionNames(['DEFAULT'], self.bazaar_config)
 
1926
 
 
1927
    def test_locations_default_section(self):
 
1928
        # No sections are defined in an empty file
 
1929
        self.assertSectionNames([], self.locations_config)
 
1930
 
 
1931
    def test_locations_named_section(self):
 
1932
        self.locations_config.set_user_option('file', 'locations')
 
1933
        self.assertSectionNames([self.tree.basedir], self.locations_config)
 
1934
 
 
1935
    def test_locations_matching_sections(self):
 
1936
        loc_config = self.locations_config
 
1937
        loc_config.set_user_option('file', 'locations')
 
1938
        # We need to cheat a bit here to create an option in sections above and
 
1939
        # below the 'location' one.
 
1940
        parser = loc_config._get_parser()
 
1941
        # locations.cong deals with '/' ignoring native os.sep
 
1942
        location_names = self.tree.basedir.split('/')
 
1943
        parent = '/'.join(location_names[:-1])
 
1944
        child = '/'.join(location_names + ['child'])
 
1945
        parser[parent] = {}
 
1946
        parser[parent]['file'] = 'parent'
 
1947
        parser[child] = {}
 
1948
        parser[child]['file'] = 'child'
 
1949
        self.assertSectionNames([self.tree.basedir, parent], loc_config)
 
1950
 
 
1951
    def test_branch_data_default_section(self):
 
1952
        self.assertSectionNames([None],
 
1953
                                self.branch_config._get_branch_data_config())
 
1954
 
 
1955
    def test_branch_default_sections(self):
 
1956
        # No sections are defined in an empty locations file
 
1957
        self.assertSectionNames([None, 'DEFAULT'],
 
1958
                                self.branch_config)
 
1959
        # Unless we define an option
 
1960
        self.branch_config._get_location_config().set_user_option(
 
1961
            'file', 'locations')
 
1962
        self.assertSectionNames([self.tree.basedir, None, 'DEFAULT'],
 
1963
                                self.branch_config)
 
1964
 
 
1965
    def test_bazaar_named_section(self):
 
1966
        # We need to cheat as the API doesn't give direct access to sections
 
1967
        # other than DEFAULT.
 
1968
        self.bazaar_config.set_alias('bazaar', 'bzr')
 
1969
        self.assertSectionNames(['ALIASES'], self.bazaar_config, 'ALIASES')
 
1970
 
 
1971
 
 
1972
class TestAuthenticationConfigFile(tests.TestCase):
 
1973
    """Test the authentication.conf file matching"""
 
1974
 
 
1975
    def _got_user_passwd(self, expected_user, expected_password,
 
1976
                         config, *args, **kwargs):
 
1977
        credentials = config.get_credentials(*args, **kwargs)
 
1978
        if credentials is None:
 
1979
            user = None
 
1980
            password = None
 
1981
        else:
 
1982
            user = credentials['user']
 
1983
            password = credentials['password']
 
1984
        self.assertEquals(expected_user, user)
 
1985
        self.assertEquals(expected_password, password)
 
1986
 
 
1987
    def test_empty_config(self):
 
1988
        conf = config.AuthenticationConfig(_file=StringIO())
 
1989
        self.assertEquals({}, conf._get_config())
 
1990
        self._got_user_passwd(None, None, conf, 'http', 'foo.net')
 
1991
 
 
1992
    def test_missing_auth_section_header(self):
 
1993
        conf = config.AuthenticationConfig(_file=StringIO('foo = bar'))
 
1994
        self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
 
1995
 
 
1996
    def test_auth_section_header_not_closed(self):
 
1997
        conf = config.AuthenticationConfig(_file=StringIO('[DEF'))
 
1998
        self.assertRaises(errors.ParseConfigError, conf._get_config)
 
1999
 
 
2000
    def test_auth_value_not_boolean(self):
 
2001
        conf = config.AuthenticationConfig(_file=StringIO(
 
2002
                """[broken]
 
2003
scheme=ftp
 
2004
user=joe
 
2005
verify_certificates=askme # Error: Not a boolean
 
2006
"""))
 
2007
        self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
 
2008
 
 
2009
    def test_auth_value_not_int(self):
 
2010
        conf = config.AuthenticationConfig(_file=StringIO(
 
2011
                """[broken]
 
2012
scheme=ftp
 
2013
user=joe
 
2014
port=port # Error: Not an int
 
2015
"""))
 
2016
        self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
 
2017
 
 
2018
    def test_unknown_password_encoding(self):
 
2019
        conf = config.AuthenticationConfig(_file=StringIO(
 
2020
                """[broken]
 
2021
scheme=ftp
 
2022
user=joe
 
2023
password_encoding=unknown
 
2024
"""))
 
2025
        self.assertRaises(ValueError, conf.get_password,
 
2026
                          'ftp', 'foo.net', 'joe')
 
2027
 
 
2028
    def test_credentials_for_scheme_host(self):
 
2029
        conf = config.AuthenticationConfig(_file=StringIO(
 
2030
                """# Identity on foo.net
 
2031
[ftp definition]
 
2032
scheme=ftp
 
2033
host=foo.net
 
2034
user=joe
 
2035
password=secret-pass
 
2036
"""))
 
2037
        # Basic matching
 
2038
        self._got_user_passwd('joe', 'secret-pass', conf, 'ftp', 'foo.net')
 
2039
        # different scheme
 
2040
        self._got_user_passwd(None, None, conf, 'http', 'foo.net')
 
2041
        # different host
 
2042
        self._got_user_passwd(None, None, conf, 'ftp', 'bar.net')
 
2043
 
 
2044
    def test_credentials_for_host_port(self):
 
2045
        conf = config.AuthenticationConfig(_file=StringIO(
 
2046
                """# Identity on foo.net
 
2047
[ftp definition]
 
2048
scheme=ftp
 
2049
port=10021
 
2050
host=foo.net
 
2051
user=joe
 
2052
password=secret-pass
 
2053
"""))
 
2054
        # No port
 
2055
        self._got_user_passwd('joe', 'secret-pass',
 
2056
                              conf, 'ftp', 'foo.net', port=10021)
 
2057
        # different port
 
2058
        self._got_user_passwd(None, None, conf, 'ftp', 'foo.net')
 
2059
 
 
2060
    def test_for_matching_host(self):
 
2061
        conf = config.AuthenticationConfig(_file=StringIO(
 
2062
                """# Identity on foo.net
 
2063
[sourceforge]
 
2064
scheme=bzr
 
2065
host=bzr.sf.net
 
2066
user=joe
 
2067
password=joepass
 
2068
[sourceforge domain]
 
2069
scheme=bzr
 
2070
host=.bzr.sf.net
 
2071
user=georges
 
2072
password=bendover
 
2073
"""))
 
2074
        # matching domain
 
2075
        self._got_user_passwd('georges', 'bendover',
 
2076
                              conf, 'bzr', 'foo.bzr.sf.net')
 
2077
        # phishing attempt
 
2078
        self._got_user_passwd(None, None,
 
2079
                              conf, 'bzr', 'bbzr.sf.net')
 
2080
 
 
2081
    def test_for_matching_host_None(self):
 
2082
        conf = config.AuthenticationConfig(_file=StringIO(
 
2083
                """# Identity on foo.net
 
2084
[catchup bzr]
 
2085
scheme=bzr
 
2086
user=joe
 
2087
password=joepass
 
2088
[DEFAULT]
 
2089
user=georges
 
2090
password=bendover
 
2091
"""))
 
2092
        # match no host
 
2093
        self._got_user_passwd('joe', 'joepass',
 
2094
                              conf, 'bzr', 'quux.net')
 
2095
        # no host but different scheme
 
2096
        self._got_user_passwd('georges', 'bendover',
 
2097
                              conf, 'ftp', 'quux.net')
 
2098
 
 
2099
    def test_credentials_for_path(self):
 
2100
        conf = config.AuthenticationConfig(_file=StringIO(
 
2101
                """
 
2102
[http dir1]
 
2103
scheme=http
 
2104
host=bar.org
 
2105
path=/dir1
 
2106
user=jim
 
2107
password=jimpass
 
2108
[http dir2]
 
2109
scheme=http
 
2110
host=bar.org
 
2111
path=/dir2
 
2112
user=georges
 
2113
password=bendover
 
2114
"""))
 
2115
        # no path no dice
 
2116
        self._got_user_passwd(None, None,
 
2117
                              conf, 'http', host='bar.org', path='/dir3')
 
2118
        # matching path
 
2119
        self._got_user_passwd('georges', 'bendover',
 
2120
                              conf, 'http', host='bar.org', path='/dir2')
 
2121
        # matching subdir
 
2122
        self._got_user_passwd('jim', 'jimpass',
 
2123
                              conf, 'http', host='bar.org',path='/dir1/subdir')
 
2124
 
 
2125
    def test_credentials_for_user(self):
 
2126
        conf = config.AuthenticationConfig(_file=StringIO(
 
2127
                """
 
2128
[with user]
 
2129
scheme=http
 
2130
host=bar.org
 
2131
user=jim
 
2132
password=jimpass
 
2133
"""))
 
2134
        # Get user
 
2135
        self._got_user_passwd('jim', 'jimpass',
 
2136
                              conf, 'http', 'bar.org')
 
2137
        # Get same user
 
2138
        self._got_user_passwd('jim', 'jimpass',
 
2139
                              conf, 'http', 'bar.org', user='jim')
 
2140
        # Don't get a different user if one is specified
 
2141
        self._got_user_passwd(None, None,
 
2142
                              conf, 'http', 'bar.org', user='georges')
 
2143
 
 
2144
    def test_credentials_for_user_without_password(self):
 
2145
        conf = config.AuthenticationConfig(_file=StringIO(
 
2146
                """
 
2147
[without password]
 
2148
scheme=http
 
2149
host=bar.org
 
2150
user=jim
 
2151
"""))
 
2152
        # Get user but no password
 
2153
        self._got_user_passwd('jim', None,
 
2154
                              conf, 'http', 'bar.org')
 
2155
 
 
2156
    def test_verify_certificates(self):
 
2157
        conf = config.AuthenticationConfig(_file=StringIO(
 
2158
                """
 
2159
[self-signed]
 
2160
scheme=https
 
2161
host=bar.org
 
2162
user=jim
 
2163
password=jimpass
 
2164
verify_certificates=False
 
2165
[normal]
 
2166
scheme=https
 
2167
host=foo.net
 
2168
user=georges
 
2169
password=bendover
 
2170
"""))
 
2171
        credentials = conf.get_credentials('https', 'bar.org')
 
2172
        self.assertEquals(False, credentials.get('verify_certificates'))
 
2173
        credentials = conf.get_credentials('https', 'foo.net')
 
2174
        self.assertEquals(True, credentials.get('verify_certificates'))
 
2175
 
 
2176
 
 
2177
class TestAuthenticationStorage(tests.TestCaseInTempDir):
 
2178
 
 
2179
    def test_set_credentials(self):
 
2180
        conf = config.AuthenticationConfig()
 
2181
        conf.set_credentials('name', 'host', 'user', 'scheme', 'password',
 
2182
        99, path='/foo', verify_certificates=False, realm='realm')
 
2183
        credentials = conf.get_credentials(host='host', scheme='scheme',
 
2184
                                           port=99, path='/foo',
 
2185
                                           realm='realm')
 
2186
        CREDENTIALS = {'name': 'name', 'user': 'user', 'password': 'password',
 
2187
                       'verify_certificates': False, 'scheme': 'scheme', 
 
2188
                       'host': 'host', 'port': 99, 'path': '/foo', 
 
2189
                       'realm': 'realm'}
 
2190
        self.assertEqual(CREDENTIALS, credentials)
 
2191
        credentials_from_disk = config.AuthenticationConfig().get_credentials(
 
2192
            host='host', scheme='scheme', port=99, path='/foo', realm='realm')
 
2193
        self.assertEqual(CREDENTIALS, credentials_from_disk)
 
2194
 
 
2195
    def test_reset_credentials_different_name(self):
 
2196
        conf = config.AuthenticationConfig()
 
2197
        conf.set_credentials('name', 'host', 'user', 'scheme', 'password'),
 
2198
        conf.set_credentials('name2', 'host', 'user2', 'scheme', 'password'),
 
2199
        self.assertIs(None, conf._get_config().get('name'))
 
2200
        credentials = conf.get_credentials(host='host', scheme='scheme')
 
2201
        CREDENTIALS = {'name': 'name2', 'user': 'user2', 'password':
 
2202
                       'password', 'verify_certificates': True, 
 
2203
                       'scheme': 'scheme', 'host': 'host', 'port': None, 
 
2204
                       'path': None, 'realm': None}
 
2205
        self.assertEqual(CREDENTIALS, credentials)
 
2206
 
 
2207
 
 
2208
class TestAuthenticationConfig(tests.TestCase):
 
2209
    """Test AuthenticationConfig behaviour"""
 
2210
 
 
2211
    def _check_default_password_prompt(self, expected_prompt_format, scheme,
 
2212
                                       host=None, port=None, realm=None,
 
2213
                                       path=None):
 
2214
        if host is None:
 
2215
            host = 'bar.org'
 
2216
        user, password = 'jim', 'precious'
 
2217
        expected_prompt = expected_prompt_format % {
 
2218
            'scheme': scheme, 'host': host, 'port': port,
 
2219
            'user': user, 'realm': realm}
 
2220
 
 
2221
        stdout = tests.StringIOWrapper()
 
2222
        stderr = tests.StringIOWrapper()
 
2223
        ui.ui_factory = tests.TestUIFactory(stdin=password + '\n',
 
2224
                                            stdout=stdout, stderr=stderr)
 
2225
        # We use an empty conf so that the user is always prompted
 
2226
        conf = config.AuthenticationConfig()
 
2227
        self.assertEquals(password,
 
2228
                          conf.get_password(scheme, host, user, port=port,
 
2229
                                            realm=realm, path=path))
 
2230
        self.assertEquals(expected_prompt, stderr.getvalue())
 
2231
        self.assertEquals('', stdout.getvalue())
 
2232
 
 
2233
    def _check_default_username_prompt(self, expected_prompt_format, scheme,
 
2234
                                       host=None, port=None, realm=None,
 
2235
                                       path=None):
 
2236
        if host is None:
 
2237
            host = 'bar.org'
 
2238
        username = 'jim'
 
2239
        expected_prompt = expected_prompt_format % {
 
2240
            'scheme': scheme, 'host': host, 'port': port,
 
2241
            'realm': realm}
 
2242
        stdout = tests.StringIOWrapper()
 
2243
        stderr = tests.StringIOWrapper()
 
2244
        ui.ui_factory = tests.TestUIFactory(stdin=username+ '\n',
 
2245
                                            stdout=stdout, stderr=stderr)
 
2246
        # We use an empty conf so that the user is always prompted
 
2247
        conf = config.AuthenticationConfig()
 
2248
        self.assertEquals(username, conf.get_user(scheme, host, port=port,
 
2249
                          realm=realm, path=path, ask=True))
 
2250
        self.assertEquals(expected_prompt, stderr.getvalue())
 
2251
        self.assertEquals('', stdout.getvalue())
 
2252
 
 
2253
    def test_username_defaults_prompts(self):
 
2254
        # HTTP prompts can't be tested here, see test_http.py
 
2255
        self._check_default_username_prompt('FTP %(host)s username: ', 'ftp')
 
2256
        self._check_default_username_prompt(
 
2257
            'FTP %(host)s:%(port)d username: ', 'ftp', port=10020)
 
2258
        self._check_default_username_prompt(
 
2259
            'SSH %(host)s:%(port)d username: ', 'ssh', port=12345)
 
2260
 
 
2261
    def test_username_default_no_prompt(self):
 
2262
        conf = config.AuthenticationConfig()
 
2263
        self.assertEquals(None,
 
2264
            conf.get_user('ftp', 'example.com'))
 
2265
        self.assertEquals("explicitdefault",
 
2266
            conf.get_user('ftp', 'example.com', default="explicitdefault"))
 
2267
 
 
2268
    def test_password_default_prompts(self):
 
2269
        # HTTP prompts can't be tested here, see test_http.py
 
2270
        self._check_default_password_prompt(
 
2271
            'FTP %(user)s@%(host)s password: ', 'ftp')
 
2272
        self._check_default_password_prompt(
 
2273
            'FTP %(user)s@%(host)s:%(port)d password: ', 'ftp', port=10020)
 
2274
        self._check_default_password_prompt(
 
2275
            'SSH %(user)s@%(host)s:%(port)d password: ', 'ssh', port=12345)
 
2276
        # SMTP port handling is a bit special (it's handled if embedded in the
 
2277
        # host too)
 
2278
        # FIXME: should we: forbid that, extend it to other schemes, leave
 
2279
        # things as they are that's fine thank you ?
 
2280
        self._check_default_password_prompt('SMTP %(user)s@%(host)s password: ',
 
2281
                                            'smtp')
 
2282
        self._check_default_password_prompt('SMTP %(user)s@%(host)s password: ',
 
2283
                                            'smtp', host='bar.org:10025')
 
2284
        self._check_default_password_prompt(
 
2285
            'SMTP %(user)s@%(host)s:%(port)d password: ',
 
2286
            'smtp', port=10025)
 
2287
 
 
2288
    def test_ssh_password_emits_warning(self):
 
2289
        conf = config.AuthenticationConfig(_file=StringIO(
 
2290
                """
 
2291
[ssh with password]
 
2292
scheme=ssh
 
2293
host=bar.org
 
2294
user=jim
 
2295
password=jimpass
 
2296
"""))
 
2297
        entered_password = 'typed-by-hand'
 
2298
        stdout = tests.StringIOWrapper()
 
2299
        stderr = tests.StringIOWrapper()
 
2300
        ui.ui_factory = tests.TestUIFactory(stdin=entered_password + '\n',
 
2301
                                            stdout=stdout, stderr=stderr)
 
2302
 
 
2303
        # Since the password defined in the authentication config is ignored,
 
2304
        # the user is prompted
 
2305
        self.assertEquals(entered_password,
 
2306
                          conf.get_password('ssh', 'bar.org', user='jim'))
 
2307
        self.assertContainsRe(
 
2308
            self.get_log(),
 
2309
            'password ignored in section \[ssh with password\]')
 
2310
 
 
2311
    def test_ssh_without_password_doesnt_emit_warning(self):
 
2312
        conf = config.AuthenticationConfig(_file=StringIO(
 
2313
                """
 
2314
[ssh with password]
 
2315
scheme=ssh
 
2316
host=bar.org
 
2317
user=jim
 
2318
"""))
 
2319
        entered_password = 'typed-by-hand'
 
2320
        stdout = tests.StringIOWrapper()
 
2321
        stderr = tests.StringIOWrapper()
 
2322
        ui.ui_factory = tests.TestUIFactory(stdin=entered_password + '\n',
 
2323
                                            stdout=stdout,
 
2324
                                            stderr=stderr)
 
2325
 
 
2326
        # Since the password defined in the authentication config is ignored,
 
2327
        # the user is prompted
 
2328
        self.assertEquals(entered_password,
 
2329
                          conf.get_password('ssh', 'bar.org', user='jim'))
 
2330
        # No warning shoud be emitted since there is no password. We are only
 
2331
        # providing "user".
 
2332
        self.assertNotContainsRe(
 
2333
            self.get_log(),
 
2334
            'password ignored in section \[ssh with password\]')
 
2335
 
 
2336
    def test_uses_fallback_stores(self):
 
2337
        self.overrideAttr(config, 'credential_store_registry',
 
2338
                          config.CredentialStoreRegistry())
 
2339
        store = StubCredentialStore()
 
2340
        store.add_credentials("http", "example.com", "joe", "secret")
 
2341
        config.credential_store_registry.register("stub", store, fallback=True)
 
2342
        conf = config.AuthenticationConfig(_file=StringIO())
 
2343
        creds = conf.get_credentials("http", "example.com")
 
2344
        self.assertEquals("joe", creds["user"])
 
2345
        self.assertEquals("secret", creds["password"])
 
2346
 
 
2347
 
 
2348
class StubCredentialStore(config.CredentialStore):
 
2349
 
 
2350
    def __init__(self):
 
2351
        self._username = {}
 
2352
        self._password = {}
 
2353
 
 
2354
    def add_credentials(self, scheme, host, user, password=None):
 
2355
        self._username[(scheme, host)] = user
 
2356
        self._password[(scheme, host)] = password
 
2357
 
 
2358
    def get_credentials(self, scheme, host, port=None, user=None,
 
2359
        path=None, realm=None):
 
2360
        key = (scheme, host)
 
2361
        if not key in self._username:
 
2362
            return None
 
2363
        return { "scheme": scheme, "host": host, "port": port,
 
2364
                "user": self._username[key], "password": self._password[key]}
 
2365
 
 
2366
 
 
2367
class CountingCredentialStore(config.CredentialStore):
 
2368
 
 
2369
    def __init__(self):
 
2370
        self._calls = 0
 
2371
 
 
2372
    def get_credentials(self, scheme, host, port=None, user=None,
 
2373
        path=None, realm=None):
 
2374
        self._calls += 1
 
2375
        return None
 
2376
 
 
2377
 
 
2378
class TestCredentialStoreRegistry(tests.TestCase):
 
2379
 
 
2380
    def _get_cs_registry(self):
 
2381
        return config.credential_store_registry
 
2382
 
 
2383
    def test_default_credential_store(self):
 
2384
        r = self._get_cs_registry()
 
2385
        default = r.get_credential_store(None)
 
2386
        self.assertIsInstance(default, config.PlainTextCredentialStore)
 
2387
 
 
2388
    def test_unknown_credential_store(self):
 
2389
        r = self._get_cs_registry()
 
2390
        # It's hard to imagine someone creating a credential store named
 
2391
        # 'unknown' so we use that as an never registered key.
 
2392
        self.assertRaises(KeyError, r.get_credential_store, 'unknown')
 
2393
 
 
2394
    def test_fallback_none_registered(self):
 
2395
        r = config.CredentialStoreRegistry()
 
2396
        self.assertEquals(None,
 
2397
                          r.get_fallback_credentials("http", "example.com"))
 
2398
 
 
2399
    def test_register(self):
 
2400
        r = config.CredentialStoreRegistry()
 
2401
        r.register("stub", StubCredentialStore(), fallback=False)
 
2402
        r.register("another", StubCredentialStore(), fallback=True)
 
2403
        self.assertEquals(["another", "stub"], r.keys())
 
2404
 
 
2405
    def test_register_lazy(self):
 
2406
        r = config.CredentialStoreRegistry()
 
2407
        r.register_lazy("stub", "bzrlib.tests.test_config",
 
2408
                        "StubCredentialStore", fallback=False)
 
2409
        self.assertEquals(["stub"], r.keys())
 
2410
        self.assertIsInstance(r.get_credential_store("stub"),
 
2411
                              StubCredentialStore)
 
2412
 
 
2413
    def test_is_fallback(self):
 
2414
        r = config.CredentialStoreRegistry()
 
2415
        r.register("stub1", None, fallback=False)
 
2416
        r.register("stub2", None, fallback=True)
 
2417
        self.assertEquals(False, r.is_fallback("stub1"))
 
2418
        self.assertEquals(True, r.is_fallback("stub2"))
 
2419
 
 
2420
    def test_no_fallback(self):
 
2421
        r = config.CredentialStoreRegistry()
 
2422
        store = CountingCredentialStore()
 
2423
        r.register("count", store, fallback=False)
 
2424
        self.assertEquals(None,
 
2425
                          r.get_fallback_credentials("http", "example.com"))
 
2426
        self.assertEquals(0, store._calls)
 
2427
 
 
2428
    def test_fallback_credentials(self):
 
2429
        r = config.CredentialStoreRegistry()
 
2430
        store = StubCredentialStore()
 
2431
        store.add_credentials("http", "example.com",
 
2432
                              "somebody", "geheim")
 
2433
        r.register("stub", store, fallback=True)
 
2434
        creds = r.get_fallback_credentials("http", "example.com")
 
2435
        self.assertEquals("somebody", creds["user"])
 
2436
        self.assertEquals("geheim", creds["password"])
 
2437
 
 
2438
    def test_fallback_first_wins(self):
 
2439
        r = config.CredentialStoreRegistry()
 
2440
        stub1 = StubCredentialStore()
 
2441
        stub1.add_credentials("http", "example.com",
 
2442
                              "somebody", "stub1")
 
2443
        r.register("stub1", stub1, fallback=True)
 
2444
        stub2 = StubCredentialStore()
 
2445
        stub2.add_credentials("http", "example.com",
 
2446
                              "somebody", "stub2")
 
2447
        r.register("stub2", stub1, fallback=True)
 
2448
        creds = r.get_fallback_credentials("http", "example.com")
 
2449
        self.assertEquals("somebody", creds["user"])
 
2450
        self.assertEquals("stub1", creds["password"])
 
2451
 
 
2452
 
 
2453
class TestPlainTextCredentialStore(tests.TestCase):
 
2454
 
 
2455
    def test_decode_password(self):
 
2456
        r = config.credential_store_registry
 
2457
        plain_text = r.get_credential_store()
 
2458
        decoded = plain_text.decode_password(dict(password='secret'))
 
2459
        self.assertEquals('secret', decoded)
 
2460
 
 
2461
 
 
2462
# FIXME: Once we have a way to declare authentication to all test servers, we
 
2463
# can implement generic tests.
 
2464
# test_user_password_in_url
 
2465
# test_user_in_url_password_from_config
 
2466
# test_user_in_url_password_prompted
 
2467
# test_user_in_config
 
2468
# test_user_getpass.getuser
 
2469
# test_user_prompted ?
 
2470
class TestAuthenticationRing(tests.TestCaseWithTransport):
 
2471
    pass