~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_config.py

Merge bzr.dev to resolve conflicts

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