/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_config.py

  • Committer: Jelmer Vernooij
  • Date: 2012-06-29 08:19:21 UTC
  • mfrom: (0.40.147 trunk)
  • mto: This revision was merged to the branch mainline in revision 6555.
  • Revision ID: jelmer@samba.org-20120629081921-m3asg03n6z9fzcxn
Ship the grep plugin.

Show diffs side-by-side

added added

removed removed

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