~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_config.py

  • Committer: Jelmer Vernooij
  • Date: 2012-02-07 00:49:58 UTC
  • mto: This revision was merged to the branch mainline in revision 6465.
  • Revision ID: jelmer@samba.org-20120207004958-rdtzmipi10p1oq97
Migrate 'bugtracker' setting to config stacks.

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