~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_config.py

  • Committer: INADA Naoki
  • Date: 2011-05-05 09:15:34 UTC
  • mto: (5830.3.3 i18n-msgfmt)
  • mto: This revision was merged to the branch mainline in revision 5873.
  • Revision ID: songofacandy@gmail.com-20110505091534-7sv835xpofwrmpt4
Add update-pot command to Makefile and tools/bzrgettext script that
extracts help text from bzr commands.

Show diffs side-by-side

added added

removed removed

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