~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_config.py

  • Committer: John Arbash Meinel
  • Date: 2011-05-19 15:02:58 UTC
  • mto: This revision was merged to the branch mainline in revision 5900.
  • Revision ID: john@arbash-meinel.com-20110519150258-qbvu80sa6qp7ndu1
test cases for stuff like a parent becoming its own parent,
and replacing a directory underneath an otherwise unmoving child.
(broken)

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
        matcher = config.LocationMatcher(store, 'file:///dir/subdir')
 
2204
        self.assertEquals('/dir/subdir', matcher.location)
 
2205
 
 
2206
 
 
2207
class TestStackGet(tests.TestCase):
 
2208
 
 
2209
    # FIXME: This should be parametrized for all known Stack or dedicated
 
2210
    # paramerized tests created to avoid bloating -- vila 2011-03-31
 
2211
 
 
2212
    def test_single_config_get(self):
 
2213
        conf = dict(foo='bar')
 
2214
        conf_stack = config.Stack([conf])
 
2215
        self.assertEquals('bar', conf_stack.get('foo'))
 
2216
 
 
2217
    def test_get_first_definition(self):
 
2218
        conf1 = dict(foo='bar')
 
2219
        conf2 = dict(foo='baz')
 
2220
        conf_stack = config.Stack([conf1, conf2])
 
2221
        self.assertEquals('bar', conf_stack.get('foo'))
 
2222
 
 
2223
    def test_get_embedded_definition(self):
 
2224
        conf1 = dict(yy='12')
 
2225
        conf2 = config.Stack([dict(xx='42'), dict(foo='baz')])
 
2226
        conf_stack = config.Stack([conf1, conf2])
 
2227
        self.assertEquals('baz', conf_stack.get('foo'))
 
2228
 
 
2229
    def test_get_for_empty_stack(self):
 
2230
        conf_stack = config.Stack([])
 
2231
        self.assertEquals(None, conf_stack.get('foo'))
 
2232
 
 
2233
    def test_get_for_empty_section_callable(self):
 
2234
        conf_stack = config.Stack([lambda : []])
 
2235
        self.assertEquals(None, conf_stack.get('foo'))
 
2236
 
 
2237
    def test_get_for_broken_callable(self):
 
2238
        # Trying to use and invalid callable raises an exception on first use
 
2239
        conf_stack = config.Stack([lambda : object()])
 
2240
        self.assertRaises(TypeError, conf_stack.get, 'foo')
 
2241
 
 
2242
 
 
2243
class TestStackWithTransport(tests.TestCaseWithTransport):
 
2244
 
 
2245
    def setUp(self):
 
2246
        super(TestStackWithTransport, self).setUp()
 
2247
        # FIXME: A more elaborate builder for the stack would avoid building a
 
2248
        # branch even for tests that don't need it.
 
2249
        self.branch = self.make_branch('branch')
 
2250
 
 
2251
 
 
2252
class TestStackSet(TestStackWithTransport):
 
2253
 
 
2254
    scenarios = [(key, {'get_stack': builder})
 
2255
                 for key, builder in test_stack_builder_registry.iteritems()]
 
2256
 
 
2257
    def test_simple_set(self):
 
2258
        conf = self.get_stack(self)
 
2259
        conf.store._load_from_string('foo=bar')
 
2260
        self.assertEquals('bar', conf.get('foo'))
 
2261
        conf.set('foo', 'baz')
 
2262
        # Did we get it back ?
 
2263
        self.assertEquals('baz', conf.get('foo'))
 
2264
 
 
2265
    def test_set_creates_a_new_section(self):
 
2266
        conf = self.get_stack(self)
 
2267
        conf.set('foo', 'baz')
 
2268
        self.assertEquals, 'baz', conf.get('foo')
 
2269
 
 
2270
 
 
2271
class TestStackRemove(TestStackWithTransport):
 
2272
 
 
2273
    scenarios = [(key, {'get_stack': builder})
 
2274
                 for key, builder in test_stack_builder_registry.iteritems()]
 
2275
 
 
2276
    def test_remove_existing(self):
 
2277
        conf = self.get_stack(self)
 
2278
        conf.store._load_from_string('foo=bar')
 
2279
        self.assertEquals('bar', conf.get('foo'))
 
2280
        conf.remove('foo')
 
2281
        # Did we get it back ?
 
2282
        self.assertEquals(None, conf.get('foo'))
 
2283
 
 
2284
    def test_remove_unknown(self):
 
2285
        conf = self.get_stack(self)
 
2286
        self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
 
2287
 
 
2288
 
 
2289
class TestConcreteStacks(TestStackWithTransport):
 
2290
 
 
2291
    scenarios = [(key, {'get_stack': builder})
 
2292
                 for key, builder in test_stack_builder_registry.iteritems()]
 
2293
 
 
2294
    def test_build_stack(self):
 
2295
        stack = self.get_stack(self)
 
2296
 
 
2297
 
 
2298
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
 
2299
 
 
2300
    def setUp(self):
 
2301
        super(TestConfigGetOptions, self).setUp()
 
2302
        create_configs(self)
 
2303
 
 
2304
    def test_no_variable(self):
 
2305
        # Using branch should query branch, locations and bazaar
 
2306
        self.assertOptions([], self.branch_config)
 
2307
 
 
2308
    def test_option_in_bazaar(self):
 
2309
        self.bazaar_config.set_user_option('file', 'bazaar')
 
2310
        self.assertOptions([('file', 'bazaar', 'DEFAULT', 'bazaar')],
 
2311
                           self.bazaar_config)
 
2312
 
 
2313
    def test_option_in_locations(self):
 
2314
        self.locations_config.set_user_option('file', 'locations')
 
2315
        self.assertOptions(
 
2316
            [('file', 'locations', self.tree.basedir, 'locations')],
 
2317
            self.locations_config)
 
2318
 
 
2319
    def test_option_in_branch(self):
 
2320
        self.branch_config.set_user_option('file', 'branch')
 
2321
        self.assertOptions([('file', 'branch', 'DEFAULT', 'branch')],
 
2322
                           self.branch_config)
 
2323
 
 
2324
    def test_option_in_bazaar_and_branch(self):
 
2325
        self.bazaar_config.set_user_option('file', 'bazaar')
 
2326
        self.branch_config.set_user_option('file', 'branch')
 
2327
        self.assertOptions([('file', 'branch', 'DEFAULT', 'branch'),
 
2328
                            ('file', 'bazaar', 'DEFAULT', 'bazaar'),],
 
2329
                           self.branch_config)
 
2330
 
 
2331
    def test_option_in_branch_and_locations(self):
 
2332
        # Hmm, locations override branch :-/
 
2333
        self.locations_config.set_user_option('file', 'locations')
 
2334
        self.branch_config.set_user_option('file', 'branch')
 
2335
        self.assertOptions(
 
2336
            [('file', 'locations', self.tree.basedir, 'locations'),
 
2337
             ('file', 'branch', 'DEFAULT', 'branch'),],
 
2338
            self.branch_config)
 
2339
 
 
2340
    def test_option_in_bazaar_locations_and_branch(self):
 
2341
        self.bazaar_config.set_user_option('file', 'bazaar')
 
2342
        self.locations_config.set_user_option('file', 'locations')
 
2343
        self.branch_config.set_user_option('file', 'branch')
 
2344
        self.assertOptions(
 
2345
            [('file', 'locations', self.tree.basedir, 'locations'),
 
2346
             ('file', 'branch', 'DEFAULT', 'branch'),
 
2347
             ('file', 'bazaar', 'DEFAULT', 'bazaar'),],
 
2348
            self.branch_config)
 
2349
 
 
2350
 
 
2351
class TestConfigRemoveOption(tests.TestCaseWithTransport, TestOptionsMixin):
 
2352
 
 
2353
    def setUp(self):
 
2354
        super(TestConfigRemoveOption, self).setUp()
 
2355
        create_configs_with_file_option(self)
 
2356
 
 
2357
    def test_remove_in_locations(self):
 
2358
        self.locations_config.remove_user_option('file', self.tree.basedir)
 
2359
        self.assertOptions(
 
2360
            [('file', 'branch', 'DEFAULT', 'branch'),
 
2361
             ('file', 'bazaar', 'DEFAULT', 'bazaar'),],
 
2362
            self.branch_config)
 
2363
 
 
2364
    def test_remove_in_branch(self):
 
2365
        self.branch_config.remove_user_option('file')
 
2366
        self.assertOptions(
 
2367
            [('file', 'locations', self.tree.basedir, 'locations'),
 
2368
             ('file', 'bazaar', 'DEFAULT', 'bazaar'),],
 
2369
            self.branch_config)
 
2370
 
 
2371
    def test_remove_in_bazaar(self):
 
2372
        self.bazaar_config.remove_user_option('file')
 
2373
        self.assertOptions(
 
2374
            [('file', 'locations', self.tree.basedir, 'locations'),
 
2375
             ('file', 'branch', 'DEFAULT', 'branch'),],
 
2376
            self.branch_config)
 
2377
 
 
2378
 
 
2379
class TestConfigGetSections(tests.TestCaseWithTransport):
 
2380
 
 
2381
    def setUp(self):
 
2382
        super(TestConfigGetSections, self).setUp()
 
2383
        create_configs(self)
 
2384
 
 
2385
    def assertSectionNames(self, expected, conf, name=None):
 
2386
        """Check which sections are returned for a given config.
 
2387
 
 
2388
        If fallback configurations exist their sections can be included.
 
2389
 
 
2390
        :param expected: A list of section names.
 
2391
 
 
2392
        :param conf: The configuration that will be queried.
 
2393
 
 
2394
        :param name: An optional section name that will be passed to
 
2395
            get_sections().
 
2396
        """
 
2397
        sections = list(conf._get_sections(name))
 
2398
        self.assertLength(len(expected), sections)
 
2399
        self.assertEqual(expected, [name for name, _, _ in sections])
 
2400
 
 
2401
    def test_bazaar_default_section(self):
 
2402
        self.assertSectionNames(['DEFAULT'], self.bazaar_config)
 
2403
 
 
2404
    def test_locations_default_section(self):
 
2405
        # No sections are defined in an empty file
 
2406
        self.assertSectionNames([], self.locations_config)
 
2407
 
 
2408
    def test_locations_named_section(self):
 
2409
        self.locations_config.set_user_option('file', 'locations')
 
2410
        self.assertSectionNames([self.tree.basedir], self.locations_config)
 
2411
 
 
2412
    def test_locations_matching_sections(self):
 
2413
        loc_config = self.locations_config
 
2414
        loc_config.set_user_option('file', 'locations')
 
2415
        # We need to cheat a bit here to create an option in sections above and
 
2416
        # below the 'location' one.
 
2417
        parser = loc_config._get_parser()
 
2418
        # locations.cong deals with '/' ignoring native os.sep
 
2419
        location_names = self.tree.basedir.split('/')
 
2420
        parent = '/'.join(location_names[:-1])
 
2421
        child = '/'.join(location_names + ['child'])
 
2422
        parser[parent] = {}
 
2423
        parser[parent]['file'] = 'parent'
 
2424
        parser[child] = {}
 
2425
        parser[child]['file'] = 'child'
 
2426
        self.assertSectionNames([self.tree.basedir, parent], loc_config)
 
2427
 
 
2428
    def test_branch_data_default_section(self):
 
2429
        self.assertSectionNames([None],
 
2430
                                self.branch_config._get_branch_data_config())
 
2431
 
 
2432
    def test_branch_default_sections(self):
 
2433
        # No sections are defined in an empty locations file
 
2434
        self.assertSectionNames([None, 'DEFAULT'],
 
2435
                                self.branch_config)
 
2436
        # Unless we define an option
 
2437
        self.branch_config._get_location_config().set_user_option(
 
2438
            'file', 'locations')
 
2439
        self.assertSectionNames([self.tree.basedir, None, 'DEFAULT'],
 
2440
                                self.branch_config)
 
2441
 
 
2442
    def test_bazaar_named_section(self):
 
2443
        # We need to cheat as the API doesn't give direct access to sections
 
2444
        # other than DEFAULT.
 
2445
        self.bazaar_config.set_alias('bazaar', 'bzr')
 
2446
        self.assertSectionNames(['ALIASES'], self.bazaar_config, 'ALIASES')
 
2447
 
 
2448
 
 
2449
class TestAuthenticationConfigFile(tests.TestCase):
 
2450
    """Test the authentication.conf file matching"""
 
2451
 
 
2452
    def _got_user_passwd(self, expected_user, expected_password,
 
2453
                         config, *args, **kwargs):
 
2454
        credentials = config.get_credentials(*args, **kwargs)
 
2455
        if credentials is None:
 
2456
            user = None
 
2457
            password = None
 
2458
        else:
 
2459
            user = credentials['user']
 
2460
            password = credentials['password']
 
2461
        self.assertEquals(expected_user, user)
 
2462
        self.assertEquals(expected_password, password)
 
2463
 
 
2464
    def test_empty_config(self):
 
2465
        conf = config.AuthenticationConfig(_file=StringIO())
 
2466
        self.assertEquals({}, conf._get_config())
 
2467
        self._got_user_passwd(None, None, conf, 'http', 'foo.net')
 
2468
 
 
2469
    def test_missing_auth_section_header(self):
 
2470
        conf = config.AuthenticationConfig(_file=StringIO('foo = bar'))
 
2471
        self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
 
2472
 
 
2473
    def test_auth_section_header_not_closed(self):
 
2474
        conf = config.AuthenticationConfig(_file=StringIO('[DEF'))
 
2475
        self.assertRaises(errors.ParseConfigError, conf._get_config)
 
2476
 
 
2477
    def test_auth_value_not_boolean(self):
 
2478
        conf = config.AuthenticationConfig(_file=StringIO(
 
2479
                """[broken]
 
2480
scheme=ftp
 
2481
user=joe
 
2482
verify_certificates=askme # Error: Not a boolean
 
2483
"""))
 
2484
        self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
 
2485
 
 
2486
    def test_auth_value_not_int(self):
 
2487
        conf = config.AuthenticationConfig(_file=StringIO(
 
2488
                """[broken]
 
2489
scheme=ftp
 
2490
user=joe
 
2491
port=port # Error: Not an int
 
2492
"""))
 
2493
        self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
 
2494
 
 
2495
    def test_unknown_password_encoding(self):
 
2496
        conf = config.AuthenticationConfig(_file=StringIO(
 
2497
                """[broken]
 
2498
scheme=ftp
 
2499
user=joe
 
2500
password_encoding=unknown
 
2501
"""))
 
2502
        self.assertRaises(ValueError, conf.get_password,
 
2503
                          'ftp', 'foo.net', 'joe')
 
2504
 
 
2505
    def test_credentials_for_scheme_host(self):
 
2506
        conf = config.AuthenticationConfig(_file=StringIO(
 
2507
                """# Identity on foo.net
 
2508
[ftp definition]
 
2509
scheme=ftp
 
2510
host=foo.net
 
2511
user=joe
 
2512
password=secret-pass
 
2513
"""))
 
2514
        # Basic matching
 
2515
        self._got_user_passwd('joe', 'secret-pass', conf, 'ftp', 'foo.net')
 
2516
        # different scheme
 
2517
        self._got_user_passwd(None, None, conf, 'http', 'foo.net')
 
2518
        # different host
 
2519
        self._got_user_passwd(None, None, conf, 'ftp', 'bar.net')
 
2520
 
 
2521
    def test_credentials_for_host_port(self):
 
2522
        conf = config.AuthenticationConfig(_file=StringIO(
 
2523
                """# Identity on foo.net
 
2524
[ftp definition]
 
2525
scheme=ftp
 
2526
port=10021
 
2527
host=foo.net
 
2528
user=joe
 
2529
password=secret-pass
 
2530
"""))
 
2531
        # No port
 
2532
        self._got_user_passwd('joe', 'secret-pass',
 
2533
                              conf, 'ftp', 'foo.net', port=10021)
 
2534
        # different port
 
2535
        self._got_user_passwd(None, None, conf, 'ftp', 'foo.net')
 
2536
 
 
2537
    def test_for_matching_host(self):
 
2538
        conf = config.AuthenticationConfig(_file=StringIO(
 
2539
                """# Identity on foo.net
 
2540
[sourceforge]
 
2541
scheme=bzr
 
2542
host=bzr.sf.net
 
2543
user=joe
 
2544
password=joepass
 
2545
[sourceforge domain]
 
2546
scheme=bzr
 
2547
host=.bzr.sf.net
 
2548
user=georges
 
2549
password=bendover
 
2550
"""))
 
2551
        # matching domain
 
2552
        self._got_user_passwd('georges', 'bendover',
 
2553
                              conf, 'bzr', 'foo.bzr.sf.net')
 
2554
        # phishing attempt
 
2555
        self._got_user_passwd(None, None,
 
2556
                              conf, 'bzr', 'bbzr.sf.net')
 
2557
 
 
2558
    def test_for_matching_host_None(self):
 
2559
        conf = config.AuthenticationConfig(_file=StringIO(
 
2560
                """# Identity on foo.net
 
2561
[catchup bzr]
 
2562
scheme=bzr
 
2563
user=joe
 
2564
password=joepass
 
2565
[DEFAULT]
 
2566
user=georges
 
2567
password=bendover
 
2568
"""))
 
2569
        # match no host
 
2570
        self._got_user_passwd('joe', 'joepass',
 
2571
                              conf, 'bzr', 'quux.net')
 
2572
        # no host but different scheme
 
2573
        self._got_user_passwd('georges', 'bendover',
 
2574
                              conf, 'ftp', 'quux.net')
 
2575
 
 
2576
    def test_credentials_for_path(self):
 
2577
        conf = config.AuthenticationConfig(_file=StringIO(
 
2578
                """
 
2579
[http dir1]
 
2580
scheme=http
 
2581
host=bar.org
 
2582
path=/dir1
 
2583
user=jim
 
2584
password=jimpass
 
2585
[http dir2]
 
2586
scheme=http
 
2587
host=bar.org
 
2588
path=/dir2
 
2589
user=georges
 
2590
password=bendover
 
2591
"""))
 
2592
        # no path no dice
 
2593
        self._got_user_passwd(None, None,
 
2594
                              conf, 'http', host='bar.org', path='/dir3')
 
2595
        # matching path
 
2596
        self._got_user_passwd('georges', 'bendover',
 
2597
                              conf, 'http', host='bar.org', path='/dir2')
 
2598
        # matching subdir
 
2599
        self._got_user_passwd('jim', 'jimpass',
 
2600
                              conf, 'http', host='bar.org',path='/dir1/subdir')
 
2601
 
 
2602
    def test_credentials_for_user(self):
 
2603
        conf = config.AuthenticationConfig(_file=StringIO(
 
2604
                """
 
2605
[with user]
 
2606
scheme=http
 
2607
host=bar.org
 
2608
user=jim
 
2609
password=jimpass
 
2610
"""))
 
2611
        # Get user
 
2612
        self._got_user_passwd('jim', 'jimpass',
 
2613
                              conf, 'http', 'bar.org')
 
2614
        # Get same user
 
2615
        self._got_user_passwd('jim', 'jimpass',
 
2616
                              conf, 'http', 'bar.org', user='jim')
 
2617
        # Don't get a different user if one is specified
 
2618
        self._got_user_passwd(None, None,
 
2619
                              conf, 'http', 'bar.org', user='georges')
 
2620
 
 
2621
    def test_credentials_for_user_without_password(self):
 
2622
        conf = config.AuthenticationConfig(_file=StringIO(
 
2623
                """
 
2624
[without password]
 
2625
scheme=http
 
2626
host=bar.org
 
2627
user=jim
 
2628
"""))
 
2629
        # Get user but no password
 
2630
        self._got_user_passwd('jim', None,
 
2631
                              conf, 'http', 'bar.org')
 
2632
 
 
2633
    def test_verify_certificates(self):
 
2634
        conf = config.AuthenticationConfig(_file=StringIO(
 
2635
                """
 
2636
[self-signed]
 
2637
scheme=https
 
2638
host=bar.org
 
2639
user=jim
 
2640
password=jimpass
 
2641
verify_certificates=False
 
2642
[normal]
 
2643
scheme=https
 
2644
host=foo.net
 
2645
user=georges
 
2646
password=bendover
 
2647
"""))
 
2648
        credentials = conf.get_credentials('https', 'bar.org')
 
2649
        self.assertEquals(False, credentials.get('verify_certificates'))
 
2650
        credentials = conf.get_credentials('https', 'foo.net')
 
2651
        self.assertEquals(True, credentials.get('verify_certificates'))
 
2652
 
 
2653
 
 
2654
class TestAuthenticationStorage(tests.TestCaseInTempDir):
 
2655
 
 
2656
    def test_set_credentials(self):
 
2657
        conf = config.AuthenticationConfig()
 
2658
        conf.set_credentials('name', 'host', 'user', 'scheme', 'password',
 
2659
        99, path='/foo', verify_certificates=False, realm='realm')
 
2660
        credentials = conf.get_credentials(host='host', scheme='scheme',
 
2661
                                           port=99, path='/foo',
 
2662
                                           realm='realm')
 
2663
        CREDENTIALS = {'name': 'name', 'user': 'user', 'password': 'password',
 
2664
                       'verify_certificates': False, 'scheme': 'scheme', 
 
2665
                       'host': 'host', 'port': 99, 'path': '/foo', 
 
2666
                       'realm': 'realm'}
 
2667
        self.assertEqual(CREDENTIALS, credentials)
 
2668
        credentials_from_disk = config.AuthenticationConfig().get_credentials(
 
2669
            host='host', scheme='scheme', port=99, path='/foo', realm='realm')
 
2670
        self.assertEqual(CREDENTIALS, credentials_from_disk)
 
2671
 
 
2672
    def test_reset_credentials_different_name(self):
 
2673
        conf = config.AuthenticationConfig()
 
2674
        conf.set_credentials('name', 'host', 'user', 'scheme', 'password'),
 
2675
        conf.set_credentials('name2', 'host', 'user2', 'scheme', 'password'),
 
2676
        self.assertIs(None, conf._get_config().get('name'))
 
2677
        credentials = conf.get_credentials(host='host', scheme='scheme')
 
2678
        CREDENTIALS = {'name': 'name2', 'user': 'user2', 'password':
 
2679
                       'password', 'verify_certificates': True, 
 
2680
                       'scheme': 'scheme', 'host': 'host', 'port': None, 
 
2681
                       'path': None, 'realm': None}
 
2682
        self.assertEqual(CREDENTIALS, credentials)
 
2683
 
 
2684
 
 
2685
class TestAuthenticationConfig(tests.TestCase):
 
2686
    """Test AuthenticationConfig behaviour"""
 
2687
 
 
2688
    def _check_default_password_prompt(self, expected_prompt_format, scheme,
 
2689
                                       host=None, port=None, realm=None,
 
2690
                                       path=None):
 
2691
        if host is None:
 
2692
            host = 'bar.org'
 
2693
        user, password = 'jim', 'precious'
 
2694
        expected_prompt = expected_prompt_format % {
 
2695
            'scheme': scheme, 'host': host, 'port': port,
 
2696
            'user': user, 'realm': realm}
 
2697
 
 
2698
        stdout = tests.StringIOWrapper()
 
2699
        stderr = tests.StringIOWrapper()
 
2700
        ui.ui_factory = tests.TestUIFactory(stdin=password + '\n',
 
2701
                                            stdout=stdout, stderr=stderr)
 
2702
        # We use an empty conf so that the user is always prompted
 
2703
        conf = config.AuthenticationConfig()
 
2704
        self.assertEquals(password,
 
2705
                          conf.get_password(scheme, host, user, port=port,
 
2706
                                            realm=realm, path=path))
 
2707
        self.assertEquals(expected_prompt, stderr.getvalue())
 
2708
        self.assertEquals('', stdout.getvalue())
 
2709
 
 
2710
    def _check_default_username_prompt(self, expected_prompt_format, scheme,
 
2711
                                       host=None, port=None, realm=None,
 
2712
                                       path=None):
 
2713
        if host is None:
 
2714
            host = 'bar.org'
 
2715
        username = 'jim'
 
2716
        expected_prompt = expected_prompt_format % {
 
2717
            'scheme': scheme, 'host': host, 'port': port,
 
2718
            'realm': realm}
 
2719
        stdout = tests.StringIOWrapper()
 
2720
        stderr = tests.StringIOWrapper()
 
2721
        ui.ui_factory = tests.TestUIFactory(stdin=username+ '\n',
 
2722
                                            stdout=stdout, stderr=stderr)
 
2723
        # We use an empty conf so that the user is always prompted
 
2724
        conf = config.AuthenticationConfig()
 
2725
        self.assertEquals(username, conf.get_user(scheme, host, port=port,
 
2726
                          realm=realm, path=path, ask=True))
 
2727
        self.assertEquals(expected_prompt, stderr.getvalue())
 
2728
        self.assertEquals('', stdout.getvalue())
 
2729
 
 
2730
    def test_username_defaults_prompts(self):
 
2731
        # HTTP prompts can't be tested here, see test_http.py
 
2732
        self._check_default_username_prompt('FTP %(host)s username: ', 'ftp')
 
2733
        self._check_default_username_prompt(
 
2734
            'FTP %(host)s:%(port)d username: ', 'ftp', port=10020)
 
2735
        self._check_default_username_prompt(
 
2736
            'SSH %(host)s:%(port)d username: ', 'ssh', port=12345)
 
2737
 
 
2738
    def test_username_default_no_prompt(self):
 
2739
        conf = config.AuthenticationConfig()
 
2740
        self.assertEquals(None,
 
2741
            conf.get_user('ftp', 'example.com'))
 
2742
        self.assertEquals("explicitdefault",
 
2743
            conf.get_user('ftp', 'example.com', default="explicitdefault"))
 
2744
 
 
2745
    def test_password_default_prompts(self):
 
2746
        # HTTP prompts can't be tested here, see test_http.py
 
2747
        self._check_default_password_prompt(
 
2748
            'FTP %(user)s@%(host)s password: ', 'ftp')
 
2749
        self._check_default_password_prompt(
 
2750
            'FTP %(user)s@%(host)s:%(port)d password: ', 'ftp', port=10020)
 
2751
        self._check_default_password_prompt(
 
2752
            'SSH %(user)s@%(host)s:%(port)d password: ', 'ssh', port=12345)
 
2753
        # SMTP port handling is a bit special (it's handled if embedded in the
 
2754
        # host too)
 
2755
        # FIXME: should we: forbid that, extend it to other schemes, leave
 
2756
        # things as they are that's fine thank you ?
 
2757
        self._check_default_password_prompt('SMTP %(user)s@%(host)s password: ',
 
2758
                                            'smtp')
 
2759
        self._check_default_password_prompt('SMTP %(user)s@%(host)s password: ',
 
2760
                                            'smtp', host='bar.org:10025')
 
2761
        self._check_default_password_prompt(
 
2762
            'SMTP %(user)s@%(host)s:%(port)d password: ',
 
2763
            'smtp', port=10025)
 
2764
 
 
2765
    def test_ssh_password_emits_warning(self):
 
2766
        conf = config.AuthenticationConfig(_file=StringIO(
 
2767
                """
 
2768
[ssh with password]
 
2769
scheme=ssh
 
2770
host=bar.org
 
2771
user=jim
 
2772
password=jimpass
 
2773
"""))
 
2774
        entered_password = 'typed-by-hand'
 
2775
        stdout = tests.StringIOWrapper()
 
2776
        stderr = tests.StringIOWrapper()
 
2777
        ui.ui_factory = tests.TestUIFactory(stdin=entered_password + '\n',
 
2778
                                            stdout=stdout, stderr=stderr)
 
2779
 
 
2780
        # Since the password defined in the authentication config is ignored,
 
2781
        # the user is prompted
 
2782
        self.assertEquals(entered_password,
 
2783
                          conf.get_password('ssh', 'bar.org', user='jim'))
 
2784
        self.assertContainsRe(
 
2785
            self.get_log(),
 
2786
            'password ignored in section \[ssh with password\]')
 
2787
 
 
2788
    def test_ssh_without_password_doesnt_emit_warning(self):
 
2789
        conf = config.AuthenticationConfig(_file=StringIO(
 
2790
                """
 
2791
[ssh with password]
 
2792
scheme=ssh
 
2793
host=bar.org
 
2794
user=jim
 
2795
"""))
 
2796
        entered_password = 'typed-by-hand'
 
2797
        stdout = tests.StringIOWrapper()
 
2798
        stderr = tests.StringIOWrapper()
 
2799
        ui.ui_factory = tests.TestUIFactory(stdin=entered_password + '\n',
 
2800
                                            stdout=stdout,
 
2801
                                            stderr=stderr)
 
2802
 
 
2803
        # Since the password defined in the authentication config is ignored,
 
2804
        # the user is prompted
 
2805
        self.assertEquals(entered_password,
 
2806
                          conf.get_password('ssh', 'bar.org', user='jim'))
 
2807
        # No warning shoud be emitted since there is no password. We are only
 
2808
        # providing "user".
 
2809
        self.assertNotContainsRe(
 
2810
            self.get_log(),
 
2811
            'password ignored in section \[ssh with password\]')
 
2812
 
 
2813
    def test_uses_fallback_stores(self):
 
2814
        self.overrideAttr(config, 'credential_store_registry',
 
2815
                          config.CredentialStoreRegistry())
 
2816
        store = StubCredentialStore()
 
2817
        store.add_credentials("http", "example.com", "joe", "secret")
 
2818
        config.credential_store_registry.register("stub", store, fallback=True)
 
2819
        conf = config.AuthenticationConfig(_file=StringIO())
 
2820
        creds = conf.get_credentials("http", "example.com")
 
2821
        self.assertEquals("joe", creds["user"])
 
2822
        self.assertEquals("secret", creds["password"])
 
2823
 
 
2824
 
 
2825
class StubCredentialStore(config.CredentialStore):
 
2826
 
 
2827
    def __init__(self):
 
2828
        self._username = {}
 
2829
        self._password = {}
 
2830
 
 
2831
    def add_credentials(self, scheme, host, user, password=None):
 
2832
        self._username[(scheme, host)] = user
 
2833
        self._password[(scheme, host)] = password
 
2834
 
 
2835
    def get_credentials(self, scheme, host, port=None, user=None,
 
2836
        path=None, realm=None):
 
2837
        key = (scheme, host)
 
2838
        if not key in self._username:
 
2839
            return None
 
2840
        return { "scheme": scheme, "host": host, "port": port,
 
2841
                "user": self._username[key], "password": self._password[key]}
 
2842
 
 
2843
 
 
2844
class CountingCredentialStore(config.CredentialStore):
 
2845
 
 
2846
    def __init__(self):
 
2847
        self._calls = 0
 
2848
 
 
2849
    def get_credentials(self, scheme, host, port=None, user=None,
 
2850
        path=None, realm=None):
 
2851
        self._calls += 1
 
2852
        return None
 
2853
 
 
2854
 
 
2855
class TestCredentialStoreRegistry(tests.TestCase):
 
2856
 
 
2857
    def _get_cs_registry(self):
 
2858
        return config.credential_store_registry
 
2859
 
 
2860
    def test_default_credential_store(self):
 
2861
        r = self._get_cs_registry()
 
2862
        default = r.get_credential_store(None)
 
2863
        self.assertIsInstance(default, config.PlainTextCredentialStore)
 
2864
 
 
2865
    def test_unknown_credential_store(self):
 
2866
        r = self._get_cs_registry()
 
2867
        # It's hard to imagine someone creating a credential store named
 
2868
        # 'unknown' so we use that as an never registered key.
 
2869
        self.assertRaises(KeyError, r.get_credential_store, 'unknown')
 
2870
 
 
2871
    def test_fallback_none_registered(self):
 
2872
        r = config.CredentialStoreRegistry()
 
2873
        self.assertEquals(None,
 
2874
                          r.get_fallback_credentials("http", "example.com"))
 
2875
 
 
2876
    def test_register(self):
 
2877
        r = config.CredentialStoreRegistry()
 
2878
        r.register("stub", StubCredentialStore(), fallback=False)
 
2879
        r.register("another", StubCredentialStore(), fallback=True)
 
2880
        self.assertEquals(["another", "stub"], r.keys())
 
2881
 
 
2882
    def test_register_lazy(self):
 
2883
        r = config.CredentialStoreRegistry()
 
2884
        r.register_lazy("stub", "bzrlib.tests.test_config",
 
2885
                        "StubCredentialStore", fallback=False)
 
2886
        self.assertEquals(["stub"], r.keys())
 
2887
        self.assertIsInstance(r.get_credential_store("stub"),
 
2888
                              StubCredentialStore)
 
2889
 
 
2890
    def test_is_fallback(self):
 
2891
        r = config.CredentialStoreRegistry()
 
2892
        r.register("stub1", None, fallback=False)
 
2893
        r.register("stub2", None, fallback=True)
 
2894
        self.assertEquals(False, r.is_fallback("stub1"))
 
2895
        self.assertEquals(True, r.is_fallback("stub2"))
 
2896
 
 
2897
    def test_no_fallback(self):
 
2898
        r = config.CredentialStoreRegistry()
 
2899
        store = CountingCredentialStore()
 
2900
        r.register("count", store, fallback=False)
 
2901
        self.assertEquals(None,
 
2902
                          r.get_fallback_credentials("http", "example.com"))
 
2903
        self.assertEquals(0, store._calls)
 
2904
 
 
2905
    def test_fallback_credentials(self):
 
2906
        r = config.CredentialStoreRegistry()
 
2907
        store = StubCredentialStore()
 
2908
        store.add_credentials("http", "example.com",
 
2909
                              "somebody", "geheim")
 
2910
        r.register("stub", store, fallback=True)
 
2911
        creds = r.get_fallback_credentials("http", "example.com")
 
2912
        self.assertEquals("somebody", creds["user"])
 
2913
        self.assertEquals("geheim", creds["password"])
 
2914
 
 
2915
    def test_fallback_first_wins(self):
 
2916
        r = config.CredentialStoreRegistry()
 
2917
        stub1 = StubCredentialStore()
 
2918
        stub1.add_credentials("http", "example.com",
 
2919
                              "somebody", "stub1")
 
2920
        r.register("stub1", stub1, fallback=True)
 
2921
        stub2 = StubCredentialStore()
 
2922
        stub2.add_credentials("http", "example.com",
 
2923
                              "somebody", "stub2")
 
2924
        r.register("stub2", stub1, fallback=True)
 
2925
        creds = r.get_fallback_credentials("http", "example.com")
 
2926
        self.assertEquals("somebody", creds["user"])
 
2927
        self.assertEquals("stub1", creds["password"])
 
2928
 
 
2929
 
 
2930
class TestPlainTextCredentialStore(tests.TestCase):
 
2931
 
 
2932
    def test_decode_password(self):
 
2933
        r = config.credential_store_registry
 
2934
        plain_text = r.get_credential_store()
 
2935
        decoded = plain_text.decode_password(dict(password='secret'))
 
2936
        self.assertEquals('secret', decoded)
 
2937
 
 
2938
 
 
2939
# FIXME: Once we have a way to declare authentication to all test servers, we
 
2940
# can implement generic tests.
 
2941
# test_user_password_in_url
 
2942
# test_user_in_url_password_from_config
 
2943
# test_user_in_url_password_prompted
 
2944
# test_user_in_config
 
2945
# test_user_getpass.getuser
 
2946
# test_user_prompted ?
 
2947
class TestAuthenticationRing(tests.TestCaseWithTransport):
 
2948
    pass
 
2949
 
 
2950
 
 
2951
class TestAutoUserId(tests.TestCase):
 
2952
    """Test inferring an automatic user name."""
 
2953
 
 
2954
    def test_auto_user_id(self):
 
2955
        """Automatic inference of user name.
 
2956
        
 
2957
        This is a bit hard to test in an isolated way, because it depends on
 
2958
        system functions that go direct to /etc or perhaps somewhere else.
 
2959
        But it's reasonable to say that on Unix, with an /etc/mailname, we ought
 
2960
        to be able to choose a user name with no configuration.
 
2961
        """
 
2962
        if sys.platform == 'win32':
 
2963
            raise TestSkipped("User name inference not implemented on win32")
 
2964
        realname, address = config._auto_user_id()
 
2965
        if os.path.exists('/etc/mailname'):
 
2966
            self.assertIsNot(None, realname)
 
2967
            self.assertIsNot(None, address)
 
2968
        else:
 
2969
            self.assertEquals((None, None), (realname, address))
 
2970