~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_config.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-05-04 12:10:51 UTC
  • mfrom: (5819.1.4 777007-developer-doc)
  • Revision ID: pqm@pqm.ubuntu.com-20110504121051-aovlsmqiivjmc4fc
(jelmer) Small fixes to developer documentation. (Jonathan Riddell)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 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"
39
68
[DEFAULT]
40
69
email=Erik B\u00e5gfors <erik@bagfors.nu>
41
70
editor=vim
 
71
change_editor=vimdiff -of @new_path @old_path
42
72
gpg_signing_command=gnome-gpg
43
73
log_format=short
44
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
45
78
[ALIASES]
46
79
h=help
47
80
ll=""" + sample_long_alias + "\n"
100
133
"""
101
134
 
102
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))
 
177
 
 
178
 
103
179
class InstrumentedConfigObj(object):
104
180
    """A config obj look-enough-alike to record calls made to it."""
105
181
 
124
200
        self._calls.append(('keys',))
125
201
        return []
126
202
 
 
203
    def reload(self):
 
204
        self._calls.append(('reload',))
 
205
 
127
206
    def write(self, arg):
128
207
        self._calls.append(('write',))
129
208
 
143
222
            self.base = "http://example.com/branches/demo"
144
223
        else:
145
224
            self.base = base
146
 
        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')
147
230
 
148
231
    def lock_write(self):
149
232
        pass
152
235
        pass
153
236
 
154
237
 
155
 
class FakeControlFiles(object):
 
238
class FakeControlFilesAndTransport(object):
156
239
 
157
240
    def __init__(self, user_id=None):
158
 
        self.email = user_id
159
241
        self.files = {}
 
242
        if user_id:
 
243
            self.files['email'] = user_id
 
244
        self._transport = self
160
245
 
161
246
    def get_utf8(self, filename):
162
 
        if filename != 'email':
163
 
            raise NotImplementedError
164
 
        if self.email is not None:
165
 
            return StringIO(self.email)
166
 
        raise errors.NoSuchFile(filename)
 
247
        # from LockableFiles
 
248
        raise AssertionError("get_utf8 should no longer be used")
167
249
 
168
250
    def get(self, filename):
 
251
        # from Transport
169
252
        try:
170
253
            return StringIO(self.files[filename])
171
254
        except KeyError:
172
255
            raise errors.NoSuchFile(filename)
173
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
 
174
264
    def put(self, filename, fileobj):
175
265
        self.files[filename] = fileobj.read()
176
266
 
 
267
    def put_file(self, filename, fileobj):
 
268
        return self.put(filename, fileobj)
 
269
 
177
270
 
178
271
class InstrumentedConfig(config.Config):
179
272
    """An instrumented config that supplies stubs for template methods."""
180
 
    
 
273
 
181
274
    def __init__(self):
182
275
        super(InstrumentedConfig, self).__init__()
183
276
        self._calls = []
191
284
        self._calls.append('_get_signature_checking')
192
285
        return self._signatures
193
286
 
 
287
    def _get_change_editor(self):
 
288
        self._calls.append('_get_change_editor')
 
289
        return 'vimdiff -fo @new_path @old_path'
 
290
 
194
291
 
195
292
bool_config = """[DEFAULT]
196
293
active = true
199
296
active = True
200
297
nonactive = False
201
298
"""
202
 
class TestConfigObj(TestCase):
 
299
 
 
300
 
 
301
class TestConfigObj(tests.TestCase):
 
302
 
203
303
    def test_get_bool(self):
204
 
        from bzrlib.config import ConfigObj
205
 
        co = ConfigObj(StringIO(bool_config))
 
304
        co = config.ConfigObj(StringIO(bool_config))
206
305
        self.assertIs(co.get_bool('DEFAULT', 'active'), True)
207
306
        self.assertIs(co.get_bool('DEFAULT', 'inactive'), False)
208
307
        self.assertIs(co.get_bool('UPPERCASE', 'active'), True)
209
308
        self.assertIs(co.get_bool('UPPERCASE', 'nonactive'), False)
210
309
 
211
 
 
212
 
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):
213
369
 
214
370
    def test_constructs(self):
215
371
        config.Config()
216
 
 
 
372
 
217
373
    def test_no_default_editor(self):
218
374
        self.assertRaises(NotImplementedError, config.Config().get_editor)
219
375
 
264
420
        my_config = config.Config()
265
421
        self.assertEqual('long', my_config.log_format())
266
422
 
267
 
 
268
 
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):
269
433
 
270
434
    def setUp(self):
271
435
        super(TestConfigPath, self).setUp()
272
 
        self.old_home = os.environ.get('HOME', None)
273
 
        self.old_appdata = os.environ.get('APPDATA', None)
274
 
        os.environ['HOME'] = '/home/bogus'
275
 
        os.environ['APPDATA'] = \
276
 
            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'
277
445
 
278
 
    def tearDown(self):
279
 
        if self.old_home is None:
280
 
            del os.environ['HOME']
281
 
        else:
282
 
            os.environ['HOME'] = self.old_home
283
 
        if self.old_appdata is None:
284
 
            del os.environ['APPDATA']
285
 
        else:
286
 
            os.environ['APPDATA'] = self.old_appdata
287
 
        super(TestConfigPath, self).tearDown()
288
 
    
289
446
    def test_config_dir(self):
290
 
        if sys.platform == 'win32':
291
 
            self.assertEqual(config.config_dir(), 
292
 
                'C:/Documents and Settings/bogus/Application Data/bazaar/2.0')
293
 
        else:
294
 
            self.assertEqual(config.config_dir(), '/home/bogus/.bazaar')
 
447
        self.assertEqual(config.config_dir(), self.bzr_home)
295
448
 
296
449
    def test_config_filename(self):
297
 
        if sys.platform == 'win32':
298
 
            self.assertEqual(config.config_filename(), 
299
 
                'C:/Documents and Settings/bogus/Application Data/bazaar/2.0/bazaar.conf')
300
 
        else:
301
 
            self.assertEqual(config.config_filename(),
302
 
                             '/home/bogus/.bazaar/bazaar.conf')
303
 
 
304
 
    def test_branches_config_filename(self):
305
 
        if sys.platform == 'win32':
306
 
            self.assertEqual(config.branches_config_filename(), 
307
 
                'C:/Documents and Settings/bogus/Application Data/bazaar/2.0/branches.conf')
308
 
        else:
309
 
            self.assertEqual(config.branches_config_filename(),
310
 
                             '/home/bogus/.bazaar/branches.conf')
 
450
        self.assertEqual(config.config_filename(),
 
451
                         self.bzr_home + '/bazaar.conf')
311
452
 
312
453
    def test_locations_config_filename(self):
313
 
        if sys.platform == 'win32':
314
 
            self.assertEqual(config.locations_config_filename(), 
315
 
                'C:/Documents and Settings/bogus/Application Data/bazaar/2.0/locations.conf')
316
 
        else:
317
 
            self.assertEqual(config.locations_config_filename(),
318
 
                             '/home/bogus/.bazaar/locations.conf')
319
 
 
320
 
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):
321
502
 
322
503
    def test_contructs(self):
323
 
        my_config = config.IniBasedConfig("nothing")
 
504
        my_config = config.IniBasedConfig()
324
505
 
325
506
    def test_from_fp(self):
326
 
        config_file = StringIO(sample_config_text.encode('utf-8'))
327
 
        my_config = config.IniBasedConfig(None)
328
 
        self.failUnless(
329
 
            isinstance(my_config._get_parser(file=config_file),
330
 
                        ConfigObj))
 
507
        my_config = config.IniBasedConfig.from_string(sample_config_text)
 
508
        self.assertIsInstance(my_config._get_parser(), configobj.ConfigObj)
331
509
 
332
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):
333
537
        config_file = StringIO(sample_config_text.encode('utf-8'))
334
 
        my_config = config.IniBasedConfig(None)
335
 
        parser = my_config._get_parser(file=config_file)
336
 
        self.failUnless(my_config._get_parser() is parser)
337
 
 
338
 
 
339
 
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):
340
952
 
341
953
    def test_constructs(self):
342
954
        my_config = config.GlobalConfig()
343
955
 
344
956
    def test_calls_read_filenames(self):
345
 
        # replace the class that is constructured, to check its parameters
 
957
        # replace the class that is constructed, to check its parameters
346
958
        oldparserclass = config.ConfigObj
347
959
        config.ConfigObj = InstrumentedConfigObj
348
960
        my_config = config.GlobalConfig()
350
962
            parser = my_config._get_parser()
351
963
        finally:
352
964
            config.ConfigObj = oldparserclass
353
 
        self.failUnless(isinstance(parser, InstrumentedConfigObj))
 
965
        self.assertIsInstance(parser, InstrumentedConfigObj)
354
966
        self.assertEqual(parser._calls, [('__init__', config.config_filename(),
355
967
                                          'utf-8')])
356
968
 
357
969
 
358
 
class TestBranchConfig(TestCaseWithTransport):
 
970
class TestBranchConfig(tests.TestCaseWithTransport):
359
971
 
360
972
    def test_constructs(self):
361
973
        branch = FakeBranch()
367
979
        my_config = config.BranchConfig(branch)
368
980
        location_config = my_config._get_location_config()
369
981
        self.assertEqual(branch.base, location_config.location)
370
 
        self.failUnless(location_config is my_config._get_location_config())
 
982
        self.assertIs(location_config, my_config._get_location_config())
371
983
 
372
984
    def test_get_config(self):
373
985
        """The Branch.get_config method works properly"""
374
 
        b = BzrDir.create_standalone_workingtree('.').branch
 
986
        b = bzrdir.BzrDir.create_standalone_workingtree('.').branch
375
987
        my_config = b.get_config()
376
988
        self.assertIs(my_config.get_user_option('wacky'), None)
377
989
        my_config.set_user_option('wacky', 'unlikely')
378
990
        self.assertEqual(my_config.get_user_option('wacky'), 'unlikely')
379
991
 
380
992
        # Ensure we get the same thing if we start again
381
 
        b2 = Branch.open('.')
 
993
        b2 = branch.Branch.open('.')
382
994
        my_config2 = b2.get_config()
383
995
        self.assertEqual(my_config2.get_user_option('wacky'), 'unlikely')
384
996
 
393
1005
        branch = self.make_branch('branch')
394
1006
        self.assertEqual('branch', branch.nick)
395
1007
 
396
 
        locations = config.locations_config_filename()
397
 
        config.ensure_config_dir_exists()
398
1008
        local_url = urlutils.local_path_to_url('branch')
399
 
        open(locations, 'wb').write('[%s]\nnickname = foobar' 
400
 
                                    % (local_url,))
 
1009
        conf = config.LocationConfig.from_string(
 
1010
            '[%s]\nnickname = foobar' % (local_url,),
 
1011
            local_url, save=True)
401
1012
        self.assertEqual('foobar', branch.nick)
402
1013
 
403
1014
    def test_config_local_path(self):
405
1016
        branch = self.make_branch('branch')
406
1017
        self.assertEqual('branch', branch.nick)
407
1018
 
408
 
        locations = config.locations_config_filename()
409
 
        config.ensure_config_dir_exists()
410
 
        open(locations, 'wb').write('[%s/branch]\nnickname = barry' 
411
 
                                    % (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)
412
1023
        self.assertEqual('barry', branch.nick)
413
1024
 
414
1025
    def test_config_creates_local(self):
415
1026
        """Creating a new entry in config uses a local path."""
416
 
        branch = self.make_branch('branch')
 
1027
        branch = self.make_branch('branch', format='knit')
417
1028
        branch.set_push_location('http://foobar')
418
 
        locations = config.locations_config_filename()
419
1029
        local_path = osutils.getcwd().encode('utf8')
420
1030
        # Surprisingly ConfigObj doesn't create a trailing newline
421
 
        self.check_file_contents(locations,
422
 
            '[%s/branch]\npush_location = http://foobar\npush_location:policy = norecurse' % (local_path,))
 
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,))
423
1036
 
424
1037
    def test_autonick_urlencoded(self):
425
1038
        b = self.make_branch('!repo')
426
1039
        self.assertEqual('!repo', b.get_config().get_nickname())
427
1040
 
428
 
 
429
 
class TestGlobalConfigItems(TestCase):
 
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):
430
1076
 
431
1077
    def test_user_id(self):
432
 
        config_file = StringIO(sample_config_text.encode('utf-8'))
433
 
        my_config = config.GlobalConfig()
434
 
        my_config._parser = my_config._get_parser(file=config_file)
 
1078
        my_config = config.GlobalConfig.from_string(sample_config_text)
435
1079
        self.assertEqual(u"Erik B\u00e5gfors <erik@bagfors.nu>",
436
1080
                         my_config._get_user_id())
437
1081
 
438
1082
    def test_absent_user_id(self):
439
 
        config_file = StringIO("")
440
1083
        my_config = config.GlobalConfig()
441
 
        my_config._parser = my_config._get_parser(file=config_file)
442
1084
        self.assertEqual(None, my_config._get_user_id())
443
1085
 
444
1086
    def test_configured_editor(self):
445
 
        config_file = StringIO(sample_config_text.encode('utf-8'))
446
 
        my_config = config.GlobalConfig()
447
 
        my_config._parser = my_config._get_parser(file=config_file)
 
1087
        my_config = config.GlobalConfig.from_string(sample_config_text)
448
1088
        self.assertEqual("vim", my_config.get_editor())
449
1089
 
450
1090
    def test_signatures_always(self):
451
 
        config_file = StringIO(sample_always_signatures)
452
 
        my_config = config.GlobalConfig()
453
 
        my_config._parser = my_config._get_parser(file=config_file)
 
1091
        my_config = config.GlobalConfig.from_string(sample_always_signatures)
454
1092
        self.assertEqual(config.CHECK_NEVER,
455
1093
                         my_config.signature_checking())
456
1094
        self.assertEqual(config.SIGN_ALWAYS,
458
1096
        self.assertEqual(True, my_config.signature_needed())
459
1097
 
460
1098
    def test_signatures_if_possible(self):
461
 
        config_file = StringIO(sample_maybe_signatures)
462
 
        my_config = config.GlobalConfig()
463
 
        my_config._parser = my_config._get_parser(file=config_file)
 
1099
        my_config = config.GlobalConfig.from_string(sample_maybe_signatures)
464
1100
        self.assertEqual(config.CHECK_NEVER,
465
1101
                         my_config.signature_checking())
466
1102
        self.assertEqual(config.SIGN_WHEN_REQUIRED,
468
1104
        self.assertEqual(False, my_config.signature_needed())
469
1105
 
470
1106
    def test_signatures_ignore(self):
471
 
        config_file = StringIO(sample_ignore_signatures)
472
 
        my_config = config.GlobalConfig()
473
 
        my_config._parser = my_config._get_parser(file=config_file)
 
1107
        my_config = config.GlobalConfig.from_string(sample_ignore_signatures)
474
1108
        self.assertEqual(config.CHECK_ALWAYS,
475
1109
                         my_config.signature_checking())
476
1110
        self.assertEqual(config.SIGN_NEVER,
478
1112
        self.assertEqual(False, my_config.signature_needed())
479
1113
 
480
1114
    def _get_sample_config(self):
481
 
        config_file = StringIO(sample_config_text.encode('utf-8'))
482
 
        my_config = config.GlobalConfig()
483
 
        my_config._parser = my_config._get_parser(file=config_file)
 
1115
        my_config = config.GlobalConfig.from_string(sample_config_text)
484
1116
        return my_config
485
1117
 
486
1118
    def test_gpg_signing_command(self):
489
1121
        self.assertEqual(False, my_config.signature_needed())
490
1122
 
491
1123
    def _get_empty_config(self):
492
 
        config_file = StringIO("")
493
1124
        my_config = config.GlobalConfig()
494
 
        my_config._parser = my_config._get_parser(file=config_file)
495
1125
        return my_config
496
1126
 
497
1127
    def test_gpg_signing_command_unset(self):
506
1136
        my_config = self._get_sample_config()
507
1137
        self.assertEqual("something",
508
1138
                         my_config.get_user_option('user_global_option'))
509
 
        
 
1139
 
510
1140
    def test_post_commit_default(self):
511
1141
        my_config = self._get_sample_config()
512
1142
        self.assertEqual(None, my_config.post_commit())
519
1149
        my_config = self._get_sample_config()
520
1150
        self.assertEqual('help', my_config.get_alias('h'))
521
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
 
522
1160
    def test_get_no_alias(self):
523
1161
        my_config = self._get_sample_config()
524
1162
        self.assertEqual(None, my_config.get_alias('foo'))
527
1165
        my_config = self._get_sample_config()
528
1166
        self.assertEqual(sample_long_alias, my_config.get_alias('ll'))
529
1167
 
530
 
 
531
 
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):
532
1239
 
533
1240
    def test_constructs(self):
534
1241
        my_config = config.LocationConfig('http://example.com')
538
1245
        # This is testing the correct file names are provided.
539
1246
        # TODO: consolidate with the test for GlobalConfigs filename checks.
540
1247
        #
541
 
        # replace the class that is constructured, to check its parameters
 
1248
        # replace the class that is constructed, to check its parameters
542
1249
        oldparserclass = config.ConfigObj
543
1250
        config.ConfigObj = InstrumentedConfigObj
544
1251
        try:
546
1253
            parser = my_config._get_parser()
547
1254
        finally:
548
1255
            config.ConfigObj = oldparserclass
549
 
        self.failUnless(isinstance(parser, InstrumentedConfigObj))
 
1256
        self.assertIsInstance(parser, InstrumentedConfigObj)
550
1257
        self.assertEqual(parser._calls,
551
1258
                         [('__init__', config.locations_config_filename(),
552
1259
                           'utf-8')])
553
 
        config.ensure_config_dir_exists()
554
 
        #os.mkdir(config.config_dir())
555
 
        f = file(config.branches_config_filename(), 'wb')
556
 
        f.write('')
557
 
        f.close()
558
 
        oldparserclass = config.ConfigObj
559
 
        config.ConfigObj = InstrumentedConfigObj
560
 
        try:
561
 
            my_config = config.LocationConfig('http://www.example.com')
562
 
            parser = my_config._get_parser()
563
 
        finally:
564
 
            config.ConfigObj = oldparserclass
565
1260
 
566
1261
    def test_get_global_config(self):
567
1262
        my_config = config.BranchConfig(FakeBranch('http://example.com'))
568
1263
        global_config = my_config._get_global_config()
569
 
        self.failUnless(isinstance(global_config, config.GlobalConfig))
570
 
        self.failUnless(global_config is my_config._get_global_config())
 
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()))
571
1270
 
572
1271
    def test__get_matching_sections_no_match(self):
573
1272
        self.get_branch_config('/')
574
 
        self.assertEqual([], self.my_location_config._get_matching_sections())
575
 
        
 
1273
        self.assertLocationMatching([])
 
1274
 
576
1275
    def test__get_matching_sections_exact(self):
577
1276
        self.get_branch_config('http://www.example.com')
578
 
        self.assertEqual([('http://www.example.com', '')],
579
 
                         self.my_location_config._get_matching_sections())
580
 
   
 
1277
        self.assertLocationMatching([('http://www.example.com', '')])
 
1278
 
581
1279
    def test__get_matching_sections_suffix_does_not(self):
582
1280
        self.get_branch_config('http://www.example.com-com')
583
 
        self.assertEqual([], self.my_location_config._get_matching_sections())
 
1281
        self.assertLocationMatching([])
584
1282
 
585
1283
    def test__get_matching_sections_subdir_recursive(self):
586
1284
        self.get_branch_config('http://www.example.com/com')
587
 
        self.assertEqual([('http://www.example.com', 'com')],
588
 
                         self.my_location_config._get_matching_sections())
 
1285
        self.assertLocationMatching([('http://www.example.com', 'com')])
589
1286
 
590
1287
    def test__get_matching_sections_ignoreparent(self):
591
1288
        self.get_branch_config('http://www.example.com/ignoreparent')
592
 
        self.assertEqual([('http://www.example.com/ignoreparent', '')],
593
 
                         self.my_location_config._get_matching_sections())
 
1289
        self.assertLocationMatching([('http://www.example.com/ignoreparent',
 
1290
                                      '')])
594
1291
 
595
1292
    def test__get_matching_sections_ignoreparent_subdir(self):
596
1293
        self.get_branch_config(
597
1294
            'http://www.example.com/ignoreparent/childbranch')
598
 
        self.assertEqual([('http://www.example.com/ignoreparent', 'childbranch')],
599
 
                         self.my_location_config._get_matching_sections())
 
1295
        self.assertLocationMatching([('http://www.example.com/ignoreparent',
 
1296
                                      'childbranch')])
600
1297
 
601
1298
    def test__get_matching_sections_subdir_trailing_slash(self):
602
1299
        self.get_branch_config('/b')
603
 
        self.assertEqual([('/b/', '')],
604
 
                         self.my_location_config._get_matching_sections())
 
1300
        self.assertLocationMatching([('/b/', '')])
605
1301
 
606
1302
    def test__get_matching_sections_subdir_child(self):
607
1303
        self.get_branch_config('/a/foo')
608
 
        self.assertEqual([('/a/*', ''), ('/a/', 'foo')],
609
 
                         self.my_location_config._get_matching_sections())
 
1304
        self.assertLocationMatching([('/a/*', ''), ('/a/', 'foo')])
610
1305
 
611
1306
    def test__get_matching_sections_subdir_child_child(self):
612
1307
        self.get_branch_config('/a/foo/bar')
613
 
        self.assertEqual([('/a/*', 'bar'), ('/a/', 'foo/bar')],
614
 
                         self.my_location_config._get_matching_sections())
 
1308
        self.assertLocationMatching([('/a/*', 'bar'), ('/a/', 'foo/bar')])
615
1309
 
616
1310
    def test__get_matching_sections_trailing_slash_with_children(self):
617
1311
        self.get_branch_config('/a/')
618
 
        self.assertEqual([('/a/', '')],
619
 
                         self.my_location_config._get_matching_sections())
 
1312
        self.assertLocationMatching([('/a/', '')])
620
1313
 
621
1314
    def test__get_matching_sections_explicit_over_glob(self):
622
1315
        # XXX: 2006-09-08 jamesh
624
1317
        # was a config section for '/a/?', it would get precedence
625
1318
        # over '/a/c'.
626
1319
        self.get_branch_config('/a/c')
627
 
        self.assertEqual([('/a/c', ''), ('/a/*', ''), ('/a/', 'c')],
628
 
                         self.my_location_config._get_matching_sections())
 
1320
        self.assertLocationMatching([('/a/c', ''), ('/a/*', ''), ('/a/', 'c')])
629
1321
 
630
1322
    def test__get_option_policy_normal(self):
631
1323
        self.get_branch_config('http://www.example.com')
653
1345
            'http://www.example.com', 'appendpath_option'),
654
1346
            config.POLICY_APPENDPATH)
655
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)
 
1362
 
656
1363
    def test_location_without_username(self):
657
1364
        self.get_branch_config('http://www.example.com/ignoreparent')
658
1365
        self.assertEqual(u'Erik B\u00e5gfors <erik@bagfors.nu>',
681
1388
        self.get_branch_config('/a/c')
682
1389
        self.assertEqual(config.CHECK_NEVER,
683
1390
                         self.my_config.signature_checking())
684
 
        
 
1391
 
685
1392
    def test_signatures_when_available(self):
686
1393
        self.get_branch_config('/a/', global_config=sample_ignore_signatures)
687
1394
        self.assertEqual(config.CHECK_IF_POSSIBLE,
688
1395
                         self.my_config.signature_checking())
689
 
        
 
1396
 
690
1397
    def test_signatures_always(self):
691
1398
        self.get_branch_config('/b')
692
1399
        self.assertEqual(config.CHECK_ALWAYS,
693
1400
                         self.my_config.signature_checking())
694
 
        
 
1401
 
695
1402
    def test_gpg_signing_command(self):
696
1403
        self.get_branch_config('/b')
697
1404
        self.assertEqual("gnome-gpg", self.my_config.gpg_signing_command())
788
1495
            self.my_location_config._get_option_policy(
789
1496
            'http://www.example.com/norecurse', 'normal_option'),
790
1497
            config.POLICY_NORECURSE)
791
 
        
792
1498
 
793
1499
    def test_post_commit_default(self):
794
1500
        self.get_branch_config('/a/c')
795
1501
        self.assertEqual('bzrlib.tests.test_config.post_commit',
796
1502
                         self.my_config.post_commit())
797
1503
 
798
 
    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)
799
1507
        if global_config is None:
800
 
            global_file = StringIO(sample_config_text.encode('utf-8'))
801
 
        else:
802
 
            global_file = StringIO(global_config.encode('utf-8'))
803
 
        branches_file = StringIO(sample_branches_text.encode('utf-8'))
804
 
        self.my_config = config.BranchConfig(FakeBranch(location))
805
 
        # Force location config to use specified file
806
 
        self.my_location_config = self.my_config._get_location_config()
807
 
        self.my_location_config._get_parser(branches_file)
808
 
        # Force global config to use specified file
809
 
        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()
810
1519
 
811
1520
    def test_set_user_setting_sets_and_saves(self):
812
1521
        self.get_branch_config('/a/c')
813
1522
        record = InstrumentedConfigObj("foo")
814
1523
        self.my_location_config._parser = record
815
1524
 
816
 
        real_mkdir = os.mkdir
817
 
        self.created = False
818
 
        def checked_mkdir(path, mode=0777):
819
 
            self.log('making directory: %s', path)
820
 
            real_mkdir(path, mode)
821
 
            self.created = True
822
 
 
823
 
        os.mkdir = checked_mkdir
824
 
        try:
825
 
            self.callDeprecated(['The recurse option is deprecated as of '
826
 
                                 '0.14.  The section "/a/c" has been '
827
 
                                 'converted to use policies.'],
828
 
                                self.my_config.set_user_option,
829
 
                                'foo', 'bar', store=config.STORE_LOCATION)
830
 
        finally:
831
 
            os.mkdir = real_mkdir
832
 
 
833
 
        self.failUnless(self.created, 'Failed to create ~/.bazaar')
834
 
        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'),
835
1532
                          ('__contains__', '/a/c/'),
836
1533
                          ('__setitem__', '/a/c', {}),
837
1534
                          ('__getitem__', '/a/c'),
852
1549
        self.assertIs(self.my_config.get_user_option('foo'), None)
853
1550
        self.my_config.set_user_option('foo', 'bar')
854
1551
        self.assertEqual(
855
 
            self.my_config.branch.control_files.files['branch.conf'], 
 
1552
            self.my_config.branch.control_files.files['branch.conf'].strip(),
856
1553
            'foo = bar')
857
1554
        self.assertEqual(self.my_config.get_user_option('foo'), 'bar')
858
1555
        self.my_config.set_user_option('foo', 'baz',
860
1557
        self.assertEqual(self.my_config.get_user_option('foo'), 'baz')
861
1558
        self.my_config.set_user_option('foo', 'qux')
862
1559
        self.assertEqual(self.my_config.get_user_option('foo'), 'baz')
863
 
        
 
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
 
864
1569
 
865
1570
precedence_global = 'option = global'
866
1571
precedence_branch = 'option = branch'
872
1577
option = exact
873
1578
"""
874
1579
 
875
 
 
876
 
class TestBranchConfigItems(TestCaseInTempDir):
877
 
 
878
 
    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,
879
1583
                          location_config=None, branch_data_config=None):
880
 
        my_config = config.BranchConfig(FakeBranch(location))
 
1584
        my_branch = FakeBranch(location)
881
1585
        if global_config is not None:
882
 
            global_file = StringIO(global_config.encode('utf-8'))
883
 
            my_config._get_global_config()._get_parser(global_file)
884
 
        self.my_location_config = my_config._get_location_config()
 
1586
            my_global_config = config.GlobalConfig.from_string(global_config,
 
1587
                                                               save=True)
885
1588
        if location_config is not None:
886
 
            location_file = StringIO(location_config.encode('utf-8'))
887
 
            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)
888
1592
        if branch_data_config is not None:
889
1593
            my_config.branch.control_files.files['branch.conf'] = \
890
1594
                branch_data_config
895
1599
        my_config = config.BranchConfig(branch)
896
1600
        self.assertEqual("Robert Collins <robertc@example.net>",
897
1601
                         my_config.username())
898
 
        branch.control_files.email = "John"
899
 
        my_config.set_user_option('email', 
 
1602
        my_config.branch.control_files.files['email'] = "John"
 
1603
        my_config.set_user_option('email',
900
1604
                                  "Robert Collins <robertc@example.org>")
901
1605
        self.assertEqual("John", my_config.username())
902
 
        branch.control_files.email = None
 
1606
        del my_config.branch.control_files.files['email']
903
1607
        self.assertEqual("Robert Collins <robertc@example.org>",
904
1608
                         my_config.username())
905
1609
 
906
1610
    def test_not_set_in_branch(self):
907
 
        my_config = self.get_branch_config(sample_config_text)
908
 
        my_config.branch.control_files.email = None
 
1611
        my_config = self.get_branch_config(global_config=sample_config_text)
909
1612
        self.assertEqual(u"Erik B\u00e5gfors <erik@bagfors.nu>",
910
1613
                         my_config._get_user_id())
911
 
        my_config.branch.control_files.email = "John"
 
1614
        my_config.branch.control_files.files['email'] = "John"
912
1615
        self.assertEqual("John", my_config._get_user_id())
913
1616
 
914
1617
    def test_BZR_EMAIL_OVERRIDES(self):
915
 
        os.environ['BZR_EMAIL'] = "Robert Collins <robertc@example.org>"
 
1618
        self.overrideEnv('BZR_EMAIL', "Robert Collins <robertc@example.org>")
916
1619
        branch = FakeBranch()
917
1620
        my_config = config.BranchConfig(branch)
918
1621
        self.assertEqual("Robert Collins <robertc@example.org>",
919
1622
                         my_config.username())
920
 
    
 
1623
 
921
1624
    def test_signatures_forced(self):
922
1625
        my_config = self.get_branch_config(
923
1626
            global_config=sample_always_signatures)
935
1638
 
936
1639
    def test_gpg_signing_command(self):
937
1640
        my_config = self.get_branch_config(
 
1641
            global_config=sample_config_text,
938
1642
            # branch data cannot set gpg_signing_command
939
1643
            branch_data_config="gpg_signing_command=pgp")
940
 
        config_file = StringIO(sample_config_text.encode('utf-8'))
941
 
        my_config._get_global_config()._get_parser(config_file)
942
1644
        self.assertEqual('gnome-gpg', my_config.gpg_signing_command())
943
1645
 
944
1646
    def test_get_user_option_global(self):
945
 
        branch = FakeBranch()
946
 
        my_config = config.BranchConfig(branch)
947
 
        config_file = StringIO(sample_config_text.encode('utf-8'))
948
 
        (my_config._get_global_config()._get_parser(config_file))
 
1647
        my_config = self.get_branch_config(global_config=sample_config_text)
949
1648
        self.assertEqual('something',
950
1649
                         my_config.get_user_option('user_global_option'))
951
1650
 
952
1651
    def test_post_commit_default(self):
953
 
        branch = FakeBranch()
954
 
        my_config = self.get_branch_config(sample_config_text, '/a/c',
955
 
                                           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)
956
1655
        self.assertEqual(my_config.branch.base, '/a/c')
957
1656
        self.assertEqual('bzrlib.tests.test_config.post_commit',
958
1657
                         my_config.post_commit())
959
1658
        my_config.set_user_option('post_commit', 'rmtree_root')
960
 
        # post-commit is ignored when bresent in branch data
 
1659
        # post-commit is ignored when present in branch data
961
1660
        self.assertEqual('bzrlib.tests.test_config.post_commit',
962
1661
                         my_config.post_commit())
963
1662
        my_config.set_user_option('post_commit', 'rmtree_root',
965
1664
        self.assertEqual('rmtree_root', my_config.post_commit())
966
1665
 
967
1666
    def test_config_precedence(self):
 
1667
        # FIXME: eager test, luckily no persitent config file makes it fail
 
1668
        # -- vila 20100716
968
1669
        my_config = self.get_branch_config(global_config=precedence_global)
969
1670
        self.assertEqual(my_config.get_user_option('option'), 'global')
970
 
        my_config = self.get_branch_config(global_config=precedence_global, 
971
 
                                      branch_data_config=precedence_branch)
 
1671
        my_config = self.get_branch_config(global_config=precedence_global,
 
1672
                                           branch_data_config=precedence_branch)
972
1673
        self.assertEqual(my_config.get_user_option('option'), 'branch')
973
 
        my_config = self.get_branch_config(global_config=precedence_global, 
974
 
                                      branch_data_config=precedence_branch,
975
 
                                      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)
976
1678
        self.assertEqual(my_config.get_user_option('option'), 'recurse')
977
 
        my_config = self.get_branch_config(global_config=precedence_global, 
978
 
                                      branch_data_config=precedence_branch,
979
 
                                      location_config=precedence_location,
980
 
                                      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')
981
1684
        self.assertEqual(my_config.get_user_option('option'), 'exact')
982
1685
 
983
 
 
984
 
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):
985
1730
 
986
1731
    def test_extract_email_address(self):
987
1732
        self.assertEqual('jane@test.com',
988
1733
                         config.extract_email_address('Jane <jane@test.com>'))
989
1734
        self.assertRaises(errors.NoEmailInUsername,
990
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