1
# Copyright (C) 2005-2012 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for finding and reading the bzr config file[s]."""
20
from cStringIO import StringIO
21
from textwrap import dedent
26
from testtools import matchers
38
registry as _mod_registry,
43
from bzrlib.symbol_versioning import (
46
from bzrlib.transport import remote as transport_remote
47
from bzrlib.tests import (
52
from bzrlib.util.configobj import configobj
55
def lockable_config_scenarios():
58
{'config_class': config.GlobalConfig,
60
'config_section': 'DEFAULT'}),
62
{'config_class': config.LocationConfig,
64
'config_section': '.'}),]
67
load_tests = scenarios.load_tests_apply_scenarios
69
# Register helpers to build stores
70
config.test_store_builder_registry.register(
71
'configobj', lambda test: config.TransportIniFileStore(
72
test.get_transport(), 'configobj.conf'))
73
config.test_store_builder_registry.register(
74
'bazaar', lambda test: config.GlobalStore())
75
config.test_store_builder_registry.register(
76
'location', lambda test: config.LocationStore())
79
def build_backing_branch(test, relpath,
80
transport_class=None, server_class=None):
81
"""Test helper to create a backing branch only once.
83
Some tests needs multiple stores/stacks to check concurrent update
84
behaviours. As such, they need to build different branch *objects* even if
85
they share the branch on disk.
87
:param relpath: The relative path to the branch. (Note that the helper
88
should always specify the same relpath).
90
:param transport_class: The Transport class the test needs to use.
92
:param server_class: The server associated with the ``transport_class``
95
Either both or neither of ``transport_class`` and ``server_class`` should
98
if transport_class is not None and server_class is not None:
99
test.transport_class = transport_class
100
test.transport_server = server_class
101
elif not (transport_class is None and server_class is None):
102
raise AssertionError('Specify both ``transport_class`` and '
103
'``server_class`` or neither of them')
104
if getattr(test, 'backing_branch', None) is None:
105
# First call, let's build the branch on disk
106
test.backing_branch = test.make_branch(relpath)
109
def build_branch_store(test):
110
build_backing_branch(test, 'branch')
111
b = branch.Branch.open('branch')
112
return config.BranchStore(b)
113
config.test_store_builder_registry.register('branch', build_branch_store)
116
def build_control_store(test):
117
build_backing_branch(test, 'branch')
118
b = controldir.ControlDir.open('branch')
119
return config.ControlStore(b)
120
config.test_store_builder_registry.register('control', build_control_store)
123
def build_remote_branch_store(test):
124
# There is only one permutation (but we won't be able to handle more with
125
# this design anyway)
127
server_class) = transport_remote.get_test_permutations()[0]
128
build_backing_branch(test, 'branch', transport_class, server_class)
129
b = branch.Branch.open(test.get_url('branch'))
130
return config.BranchStore(b)
131
config.test_store_builder_registry.register('remote_branch',
132
build_remote_branch_store)
135
config.test_stack_builder_registry.register(
136
'bazaar', lambda test: config.GlobalStack())
137
config.test_stack_builder_registry.register(
138
'location', lambda test: config.LocationStack('.'))
141
def build_branch_stack(test):
142
build_backing_branch(test, 'branch')
143
b = branch.Branch.open('branch')
144
return config.BranchStack(b)
145
config.test_stack_builder_registry.register('branch', build_branch_stack)
148
def build_branch_only_stack(test):
149
# There is only one permutation (but we won't be able to handle more with
150
# this design anyway)
152
server_class) = transport_remote.get_test_permutations()[0]
153
build_backing_branch(test, 'branch', transport_class, server_class)
154
b = branch.Branch.open(test.get_url('branch'))
155
return config.BranchOnlyStack(b)
156
config.test_stack_builder_registry.register('branch_only',
157
build_branch_only_stack)
159
def build_remote_control_stack(test):
160
# There is only one permutation (but we won't be able to handle more with
161
# this design anyway)
163
server_class) = transport_remote.get_test_permutations()[0]
164
# We need only a bzrdir for this, not a full branch, but it's not worth
165
# creating a dedicated helper to create only the bzrdir
166
build_backing_branch(test, 'branch', transport_class, server_class)
167
b = branch.Branch.open(test.get_url('branch'))
168
return config.RemoteControlStack(b.bzrdir)
169
config.test_stack_builder_registry.register('remote_control',
170
build_remote_control_stack)
173
sample_long_alias="log -r-15..-1 --line"
174
sample_config_text = u"""
176
email=Erik B\u00e5gfors <erik@bagfors.nu>
178
change_editor=vimdiff -of @new_path @old_path
179
gpg_signing_command=gnome-gpg
180
gpg_signing_key=DD4D5088
182
validate_signatures_in_log=true
184
user_global_option=something
185
bzr.mergetool.sometool=sometool {base} {this} {other} -o {result}
186
bzr.mergetool.funkytool=funkytool "arg with spaces" {this_temp}
187
bzr.mergetool.newtool='"newtool with spaces" {this_temp}'
188
bzr.default_mergetool=sometool
191
ll=""" + sample_long_alias + "\n"
194
sample_always_signatures = """
196
check_signatures=ignore
197
create_signatures=always
200
sample_ignore_signatures = """
202
check_signatures=require
203
create_signatures=never
206
sample_maybe_signatures = """
208
check_signatures=ignore
209
create_signatures=when-required
212
sample_branches_text = """
213
[http://www.example.com]
215
email=Robert Collins <robertc@example.org>
216
normal_option = normal
217
appendpath_option = append
218
appendpath_option:policy = appendpath
219
norecurse_option = norecurse
220
norecurse_option:policy = norecurse
221
[http://www.example.com/ignoreparent]
222
# different project: ignore parent dir config
224
[http://www.example.com/norecurse]
225
# configuration items that only apply to this dir
227
normal_option = norecurse
228
[http://www.example.com/dir]
229
appendpath_option = normal
231
check_signatures=require
232
# test trailing / matching with no children
234
check_signatures=check-available
235
gpg_signing_command=false
236
gpg_signing_key=default
237
user_local_option=local
238
# test trailing / matching
240
#subdirs will match but not the parent
242
check_signatures=ignore
243
post_commit=bzrlib.tests.test_config.post_commit
244
#testing explicit beats globs
248
def create_configs(test):
249
"""Create configuration files for a given test.
251
This requires creating a tree (and populate the ``test.tree`` attribute)
252
and its associated branch and will populate the following attributes:
254
- branch_config: A BranchConfig for the associated branch.
256
- locations_config : A LocationConfig for the associated branch
258
- bazaar_config: A GlobalConfig.
260
The tree and branch are created in a 'tree' subdirectory so the tests can
261
still use the test directory to stay outside of the branch.
263
tree = test.make_branch_and_tree('tree')
265
test.branch_config = config.BranchConfig(tree.branch)
266
test.locations_config = config.LocationConfig(tree.basedir)
267
test.bazaar_config = config.GlobalConfig()
270
def create_configs_with_file_option(test):
271
"""Create configuration files with a ``file`` option set in each.
273
This builds on ``create_configs`` and add one ``file`` option in each
274
configuration with a value which allows identifying the configuration file.
277
test.bazaar_config.set_user_option('file', 'bazaar')
278
test.locations_config.set_user_option('file', 'locations')
279
test.branch_config.set_user_option('file', 'branch')
282
class TestOptionsMixin:
284
def assertOptions(self, expected, conf):
285
# We don't care about the parser (as it will make tests hard to write
286
# and error-prone anyway)
287
self.assertThat([opt[:4] for opt in conf._get_options()],
288
matchers.Equals(expected))
291
class InstrumentedConfigObj(object):
292
"""A config obj look-enough-alike to record calls made to it."""
294
def __contains__(self, thing):
295
self._calls.append(('__contains__', thing))
298
def __getitem__(self, key):
299
self._calls.append(('__getitem__', key))
302
def __init__(self, input, encoding=None):
303
self._calls = [('__init__', input, encoding)]
305
def __setitem__(self, key, value):
306
self._calls.append(('__setitem__', key, value))
308
def __delitem__(self, key):
309
self._calls.append(('__delitem__', key))
312
self._calls.append(('keys',))
316
self._calls.append(('reload',))
318
def write(self, arg):
319
self._calls.append(('write',))
321
def as_bool(self, value):
322
self._calls.append(('as_bool', value))
325
def get_value(self, section, name):
326
self._calls.append(('get_value', section, name))
330
class FakeBranch(object):
332
def __init__(self, base=None):
334
self.base = "http://example.com/branches/demo"
337
self._transport = self.control_files = \
338
FakeControlFilesAndTransport()
340
def _get_config(self):
341
return config.TransportConfig(self._transport, 'branch.conf')
343
def lock_write(self):
350
class FakeControlFilesAndTransport(object):
354
self._transport = self
356
def get(self, filename):
359
return StringIO(self.files[filename])
361
raise errors.NoSuchFile(filename)
363
def get_bytes(self, filename):
366
return self.files[filename]
368
raise errors.NoSuchFile(filename)
370
def put(self, filename, fileobj):
371
self.files[filename] = fileobj.read()
373
def put_file(self, filename, fileobj):
374
return self.put(filename, fileobj)
377
class InstrumentedConfig(config.Config):
378
"""An instrumented config that supplies stubs for template methods."""
381
super(InstrumentedConfig, self).__init__()
383
self._signatures = config.CHECK_NEVER
385
def _get_user_id(self):
386
self._calls.append('_get_user_id')
387
return "Robert Collins <robert.collins@example.org>"
389
def _get_signature_checking(self):
390
self._calls.append('_get_signature_checking')
391
return self._signatures
393
def _get_change_editor(self):
394
self._calls.append('_get_change_editor')
395
return 'vimdiff -fo @new_path @old_path'
398
bool_config = """[DEFAULT]
407
class TestConfigObj(tests.TestCase):
409
def test_get_bool(self):
410
co = config.ConfigObj(StringIO(bool_config))
411
self.assertIs(co.get_bool('DEFAULT', 'active'), True)
412
self.assertIs(co.get_bool('DEFAULT', 'inactive'), False)
413
self.assertIs(co.get_bool('UPPERCASE', 'active'), True)
414
self.assertIs(co.get_bool('UPPERCASE', 'nonactive'), False)
416
def test_hash_sign_in_value(self):
418
Before 4.5.0, ConfigObj did not quote # signs in values, so they'd be
419
treated as comments when read in again. (#86838)
421
co = config.ConfigObj()
422
co['test'] = 'foo#bar'
424
co.write(outfile=outfile)
425
lines = outfile.getvalue().splitlines()
426
self.assertEqual(lines, ['test = "foo#bar"'])
427
co2 = config.ConfigObj(lines)
428
self.assertEqual(co2['test'], 'foo#bar')
430
def test_triple_quotes(self):
431
# Bug #710410: if the value string has triple quotes
432
# then ConfigObj versions up to 4.7.2 will quote them wrong
433
# and won't able to read them back
434
triple_quotes_value = '''spam
435
""" that's my spam """
437
co = config.ConfigObj()
438
co['test'] = triple_quotes_value
439
# While writing this test another bug in ConfigObj has been found:
440
# method co.write() without arguments produces list of lines
441
# one option per line, and multiline values are not split
442
# across multiple lines,
443
# and that breaks the parsing these lines back by ConfigObj.
444
# This issue only affects test, but it's better to avoid
445
# `co.write()` construct at all.
446
# [bialix 20110222] bug report sent to ConfigObj's author
448
co.write(outfile=outfile)
449
output = outfile.getvalue()
450
# now we're trying to read it back
451
co2 = config.ConfigObj(StringIO(output))
452
self.assertEquals(triple_quotes_value, co2['test'])
455
erroneous_config = """[section] # line 1
458
whocares=notme # line 4
462
class TestConfigObjErrors(tests.TestCase):
464
def test_duplicate_section_name_error_line(self):
466
co = configobj.ConfigObj(StringIO(erroneous_config),
468
except config.configobj.DuplicateError, e:
469
self.assertEqual(3, e.line_number)
471
self.fail('Error in config file not detected')
474
class TestConfig(tests.TestCase):
476
def test_constructs(self):
479
def test_user_email(self):
480
my_config = InstrumentedConfig()
481
self.assertEqual('robert.collins@example.org', my_config.user_email())
482
self.assertEqual(['_get_user_id'], my_config._calls)
484
def test_username(self):
485
my_config = InstrumentedConfig()
486
self.assertEqual('Robert Collins <robert.collins@example.org>',
487
my_config.username())
488
self.assertEqual(['_get_user_id'], my_config._calls)
490
def test_signatures_default(self):
491
my_config = config.Config()
493
self.applyDeprecated(deprecated_in((2, 5, 0)),
494
my_config.signature_needed))
495
self.assertEqual(config.CHECK_IF_POSSIBLE,
496
self.applyDeprecated(deprecated_in((2, 5, 0)),
497
my_config.signature_checking))
498
self.assertEqual(config.SIGN_WHEN_REQUIRED,
499
self.applyDeprecated(deprecated_in((2, 5, 0)),
500
my_config.signing_policy))
502
def test_signatures_template_method(self):
503
my_config = InstrumentedConfig()
504
self.assertEqual(config.CHECK_NEVER,
505
self.applyDeprecated(deprecated_in((2, 5, 0)),
506
my_config.signature_checking))
507
self.assertEqual(['_get_signature_checking'], my_config._calls)
509
def test_signatures_template_method_none(self):
510
my_config = InstrumentedConfig()
511
my_config._signatures = None
512
self.assertEqual(config.CHECK_IF_POSSIBLE,
513
self.applyDeprecated(deprecated_in((2, 5, 0)),
514
my_config.signature_checking))
515
self.assertEqual(['_get_signature_checking'], my_config._calls)
517
def test_gpg_signing_command_default(self):
518
my_config = config.Config()
519
self.assertEqual('gpg',
520
self.applyDeprecated(deprecated_in((2, 5, 0)),
521
my_config.gpg_signing_command))
523
def test_get_user_option_default(self):
524
my_config = config.Config()
525
self.assertEqual(None, my_config.get_user_option('no_option'))
527
def test_post_commit_default(self):
528
my_config = config.Config()
529
self.assertEqual(None, self.applyDeprecated(deprecated_in((2, 5, 0)),
530
my_config.post_commit))
533
def test_log_format_default(self):
534
my_config = config.Config()
535
self.assertEqual('long',
536
self.applyDeprecated(deprecated_in((2, 5, 0)),
537
my_config.log_format))
539
def test_acceptable_keys_default(self):
540
my_config = config.Config()
541
self.assertEqual(None, self.applyDeprecated(deprecated_in((2, 5, 0)),
542
my_config.acceptable_keys))
544
def test_validate_signatures_in_log_default(self):
545
my_config = config.Config()
546
self.assertEqual(False, my_config.validate_signatures_in_log())
548
def test_get_change_editor(self):
549
my_config = InstrumentedConfig()
550
change_editor = my_config.get_change_editor('old_tree', 'new_tree')
551
self.assertEqual(['_get_change_editor'], my_config._calls)
552
self.assertIs(diff.DiffFromTool, change_editor.__class__)
553
self.assertEqual(['vimdiff', '-fo', '@new_path', '@old_path'],
554
change_editor.command_template)
557
class TestConfigPath(tests.TestCase):
560
super(TestConfigPath, self).setUp()
561
self.overrideEnv('HOME', '/home/bogus')
562
self.overrideEnv('XDG_CACHE_DIR', '')
563
if sys.platform == 'win32':
565
'BZR_HOME', r'C:\Documents and Settings\bogus\Application Data')
567
'C:/Documents and Settings/bogus/Application Data/bazaar/2.0'
569
self.bzr_home = '/home/bogus/.bazaar'
571
def test_config_dir(self):
572
self.assertEqual(config.config_dir(), self.bzr_home)
574
def test_config_dir_is_unicode(self):
575
self.assertIsInstance(config.config_dir(), unicode)
577
def test_config_filename(self):
578
self.assertEqual(config.config_filename(),
579
self.bzr_home + '/bazaar.conf')
581
def test_locations_config_filename(self):
582
self.assertEqual(config.locations_config_filename(),
583
self.bzr_home + '/locations.conf')
585
def test_authentication_config_filename(self):
586
self.assertEqual(config.authentication_config_filename(),
587
self.bzr_home + '/authentication.conf')
589
def test_xdg_cache_dir(self):
590
self.assertEqual(config.xdg_cache_dir(),
591
'/home/bogus/.cache')
594
class TestXDGConfigDir(tests.TestCaseInTempDir):
595
# must be in temp dir because config tests for the existence of the bazaar
596
# subdirectory of $XDG_CONFIG_HOME
599
if sys.platform in ('darwin', 'win32'):
600
raise tests.TestNotApplicable(
601
'XDG config dir not used on this platform')
602
super(TestXDGConfigDir, self).setUp()
603
self.overrideEnv('HOME', self.test_home_dir)
604
# BZR_HOME overrides everything we want to test so unset it.
605
self.overrideEnv('BZR_HOME', None)
607
def test_xdg_config_dir_exists(self):
608
"""When ~/.config/bazaar exists, use it as the config dir."""
609
newdir = osutils.pathjoin(self.test_home_dir, '.config', 'bazaar')
611
self.assertEqual(config.config_dir(), newdir)
613
def test_xdg_config_home(self):
614
"""When XDG_CONFIG_HOME is set, use it."""
615
xdgconfigdir = osutils.pathjoin(self.test_home_dir, 'xdgconfig')
616
self.overrideEnv('XDG_CONFIG_HOME', xdgconfigdir)
617
newdir = osutils.pathjoin(xdgconfigdir, 'bazaar')
619
self.assertEqual(config.config_dir(), newdir)
622
class TestIniConfig(tests.TestCaseInTempDir):
624
def make_config_parser(self, s):
625
conf = config.IniBasedConfig.from_string(s)
626
return conf, conf._get_parser()
629
class TestIniConfigBuilding(TestIniConfig):
631
def test_contructs(self):
632
my_config = config.IniBasedConfig()
634
def test_from_fp(self):
635
my_config = config.IniBasedConfig.from_string(sample_config_text)
636
self.assertIsInstance(my_config._get_parser(), configobj.ConfigObj)
638
def test_cached(self):
639
my_config = config.IniBasedConfig.from_string(sample_config_text)
640
parser = my_config._get_parser()
641
self.assertTrue(my_config._get_parser() is parser)
643
def _dummy_chown(self, path, uid, gid):
644
self.path, self.uid, self.gid = path, uid, gid
646
def test_ini_config_ownership(self):
647
"""Ensure that chown is happening during _write_config_file"""
648
self.requireFeature(features.chown_feature)
649
self.overrideAttr(os, 'chown', self._dummy_chown)
650
self.path = self.uid = self.gid = None
651
conf = config.IniBasedConfig(file_name='./foo.conf')
652
conf._write_config_file()
653
self.assertEquals(self.path, './foo.conf')
654
self.assertTrue(isinstance(self.uid, int))
655
self.assertTrue(isinstance(self.gid, int))
657
def test_get_filename_parameter_is_deprecated_(self):
658
conf = self.callDeprecated([
659
'IniBasedConfig.__init__(get_filename) was deprecated in 2.3.'
660
' Use file_name instead.'],
661
config.IniBasedConfig, lambda: 'ini.conf')
662
self.assertEqual('ini.conf', conf.file_name)
664
def test_get_parser_file_parameter_is_deprecated_(self):
665
config_file = StringIO(sample_config_text.encode('utf-8'))
666
conf = config.IniBasedConfig.from_string(sample_config_text)
667
conf = self.callDeprecated([
668
'IniBasedConfig._get_parser(file=xxx) was deprecated in 2.3.'
669
' Use IniBasedConfig(_content=xxx) instead.'],
670
conf._get_parser, file=config_file)
673
class TestIniConfigSaving(tests.TestCaseInTempDir):
675
def test_cant_save_without_a_file_name(self):
676
conf = config.IniBasedConfig()
677
self.assertRaises(AssertionError, conf._write_config_file)
679
def test_saved_with_content(self):
680
content = 'foo = bar\n'
681
conf = config.IniBasedConfig.from_string(
682
content, file_name='./test.conf', save=True)
683
self.assertFileEqual(content, 'test.conf')
686
class TestIniConfigOptionExpansion(tests.TestCase):
687
"""Test option expansion from the IniConfig level.
689
What we really want here is to test the Config level, but the class being
690
abstract as far as storing values is concerned, this can't be done
693
# FIXME: This should be rewritten when all configs share a storage
694
# implementation -- vila 2011-02-18
696
def get_config(self, string=None):
699
c = config.IniBasedConfig.from_string(string)
702
def assertExpansion(self, expected, conf, string, env=None):
703
self.assertEquals(expected, conf.expand_options(string, env))
705
def test_no_expansion(self):
706
c = self.get_config('')
707
self.assertExpansion('foo', c, 'foo')
709
def test_env_adding_options(self):
710
c = self.get_config('')
711
self.assertExpansion('bar', c, '{foo}', {'foo': 'bar'})
713
def test_env_overriding_options(self):
714
c = self.get_config('foo=baz')
715
self.assertExpansion('bar', c, '{foo}', {'foo': 'bar'})
717
def test_simple_ref(self):
718
c = self.get_config('foo=xxx')
719
self.assertExpansion('xxx', c, '{foo}')
721
def test_unknown_ref(self):
722
c = self.get_config('')
723
self.assertRaises(errors.ExpandingUnknownOption,
724
c.expand_options, '{foo}')
726
def test_indirect_ref(self):
727
c = self.get_config('''
731
self.assertExpansion('xxx', c, '{bar}')
733
def test_embedded_ref(self):
734
c = self.get_config('''
738
self.assertExpansion('xxx', c, '{{bar}}')
740
def test_simple_loop(self):
741
c = self.get_config('foo={foo}')
742
self.assertRaises(errors.OptionExpansionLoop, c.expand_options, '{foo}')
744
def test_indirect_loop(self):
745
c = self.get_config('''
749
e = self.assertRaises(errors.OptionExpansionLoop,
750
c.expand_options, '{foo}')
751
self.assertEquals('foo->bar->baz', e.refs)
752
self.assertEquals('{foo}', e.string)
755
conf = self.get_config('''
759
list={foo},{bar},{baz}
761
self.assertEquals(['start', 'middle', 'end'],
762
conf.get_user_option('list', expand=True))
764
def test_cascading_list(self):
765
conf = self.get_config('''
771
self.assertEquals(['start', 'middle', 'end'],
772
conf.get_user_option('list', expand=True))
774
def test_pathological_hidden_list(self):
775
conf = self.get_config('''
781
hidden={start}{middle}{end}
783
# Nope, it's either a string or a list, and the list wins as soon as a
784
# ',' appears, so the string concatenation never occur.
785
self.assertEquals(['{foo', '}', '{', 'bar}'],
786
conf.get_user_option('hidden', expand=True))
789
class TestLocationConfigOptionExpansion(tests.TestCaseInTempDir):
791
def get_config(self, location, string=None):
794
# Since we don't save the config we won't strictly require to inherit
795
# from TestCaseInTempDir, but an error occurs so quickly...
796
c = config.LocationConfig.from_string(string, location)
799
def test_dont_cross_unrelated_section(self):
800
c = self.get_config('/another/branch/path','''
805
[/another/branch/path]
808
self.assertRaises(errors.ExpandingUnknownOption,
809
c.get_user_option, 'bar', expand=True)
811
def test_cross_related_sections(self):
812
c = self.get_config('/project/branch/path','''
816
[/project/branch/path]
819
self.assertEquals('quux', c.get_user_option('bar', expand=True))
822
class TestIniBaseConfigOnDisk(tests.TestCaseInTempDir):
824
def test_cannot_reload_without_name(self):
825
conf = config.IniBasedConfig.from_string(sample_config_text)
826
self.assertRaises(AssertionError, conf.reload)
828
def test_reload_see_new_value(self):
829
c1 = config.IniBasedConfig.from_string('editor=vim\n',
830
file_name='./test/conf')
831
c1._write_config_file()
832
c2 = config.IniBasedConfig.from_string('editor=emacs\n',
833
file_name='./test/conf')
834
c2._write_config_file()
835
self.assertEqual('vim', c1.get_user_option('editor'))
836
self.assertEqual('emacs', c2.get_user_option('editor'))
837
# Make sure we get the Right value
839
self.assertEqual('emacs', c1.get_user_option('editor'))
842
class TestLockableConfig(tests.TestCaseInTempDir):
844
scenarios = lockable_config_scenarios()
849
config_section = None
852
super(TestLockableConfig, self).setUp()
853
self._content = '[%s]\none=1\ntwo=2\n' % (self.config_section,)
854
self.config = self.create_config(self._content)
856
def get_existing_config(self):
857
return self.config_class(*self.config_args)
859
def create_config(self, content):
860
kwargs = dict(save=True)
861
c = self.config_class.from_string(content, *self.config_args, **kwargs)
864
def test_simple_read_access(self):
865
self.assertEquals('1', self.config.get_user_option('one'))
867
def test_simple_write_access(self):
868
self.config.set_user_option('one', 'one')
869
self.assertEquals('one', self.config.get_user_option('one'))
871
def test_listen_to_the_last_speaker(self):
873
c2 = self.get_existing_config()
874
c1.set_user_option('one', 'ONE')
875
c2.set_user_option('two', 'TWO')
876
self.assertEquals('ONE', c1.get_user_option('one'))
877
self.assertEquals('TWO', c2.get_user_option('two'))
878
# The second update respect the first one
879
self.assertEquals('ONE', c2.get_user_option('one'))
881
def test_last_speaker_wins(self):
882
# If the same config is not shared, the same variable modified twice
883
# can only see a single result.
885
c2 = self.get_existing_config()
886
c1.set_user_option('one', 'c1')
887
c2.set_user_option('one', 'c2')
888
self.assertEquals('c2', c2._get_user_option('one'))
889
# The first modification is still available until another refresh
891
self.assertEquals('c1', c1._get_user_option('one'))
892
c1.set_user_option('two', 'done')
893
self.assertEquals('c2', c1._get_user_option('one'))
895
def test_writes_are_serialized(self):
897
c2 = self.get_existing_config()
899
# We spawn a thread that will pause *during* the write
900
before_writing = threading.Event()
901
after_writing = threading.Event()
902
writing_done = threading.Event()
903
c1_orig = c1._write_config_file
904
def c1_write_config_file():
907
# The lock is held. We wait for the main thread to decide when to
910
c1._write_config_file = c1_write_config_file
912
c1.set_user_option('one', 'c1')
914
t1 = threading.Thread(target=c1_set_option)
915
# Collect the thread after the test
916
self.addCleanup(t1.join)
917
# Be ready to unblock the thread if the test goes wrong
918
self.addCleanup(after_writing.set)
920
before_writing.wait()
921
self.assertTrue(c1._lock.is_held)
922
self.assertRaises(errors.LockContention,
923
c2.set_user_option, 'one', 'c2')
924
self.assertEquals('c1', c1.get_user_option('one'))
925
# Let the lock be released
928
c2.set_user_option('one', 'c2')
929
self.assertEquals('c2', c2.get_user_option('one'))
931
def test_read_while_writing(self):
933
# We spawn a thread that will pause *during* the write
934
ready_to_write = threading.Event()
935
do_writing = threading.Event()
936
writing_done = threading.Event()
937
c1_orig = c1._write_config_file
938
def c1_write_config_file():
940
# The lock is held. We wait for the main thread to decide when to
945
c1._write_config_file = c1_write_config_file
947
c1.set_user_option('one', 'c1')
948
t1 = threading.Thread(target=c1_set_option)
949
# Collect the thread after the test
950
self.addCleanup(t1.join)
951
# Be ready to unblock the thread if the test goes wrong
952
self.addCleanup(do_writing.set)
954
# Ensure the thread is ready to write
955
ready_to_write.wait()
956
self.assertTrue(c1._lock.is_held)
957
self.assertEquals('c1', c1.get_user_option('one'))
958
# If we read during the write, we get the old value
959
c2 = self.get_existing_config()
960
self.assertEquals('1', c2.get_user_option('one'))
961
# Let the writing occur and ensure it occurred
964
# Now we get the updated value
965
c3 = self.get_existing_config()
966
self.assertEquals('c1', c3.get_user_option('one'))
969
class TestGetUserOptionAs(TestIniConfig):
971
def test_get_user_option_as_bool(self):
972
conf, parser = self.make_config_parser("""
975
an_invalid_bool = maybe
976
a_list = hmm, who knows ? # This is interpreted as a list !
978
get_bool = conf.get_user_option_as_bool
979
self.assertEqual(True, get_bool('a_true_bool'))
980
self.assertEqual(False, get_bool('a_false_bool'))
983
warnings.append(args[0] % args[1:])
984
self.overrideAttr(trace, 'warning', warning)
985
msg = 'Value "%s" is not a boolean for "%s"'
986
self.assertIs(None, get_bool('an_invalid_bool'))
987
self.assertEquals(msg % ('maybe', 'an_invalid_bool'), warnings[0])
989
self.assertIs(None, get_bool('not_defined_in_this_config'))
990
self.assertEquals([], warnings)
992
def test_get_user_option_as_list(self):
993
conf, parser = self.make_config_parser("""
998
get_list = conf.get_user_option_as_list
999
self.assertEqual(['a', 'b', 'c'], get_list('a_list'))
1000
self.assertEqual(['1'], get_list('length_1'))
1001
self.assertEqual('x', conf.get_user_option('one_item'))
1002
# automatically cast to list
1003
self.assertEqual(['x'], get_list('one_item'))
1005
def test_get_user_option_as_int_from_SI(self):
1006
conf, parser = self.make_config_parser("""
1015
def get_si(s, default=None):
1016
return self.applyDeprecated(
1017
deprecated_in((2, 5, 0)),
1018
conf.get_user_option_as_int_from_SI, s, default)
1019
self.assertEqual(100, get_si('plain'))
1020
self.assertEqual(5000, get_si('si_k'))
1021
self.assertEqual(5000, get_si('si_kb'))
1022
self.assertEqual(5000000, get_si('si_m'))
1023
self.assertEqual(5000000, get_si('si_mb'))
1024
self.assertEqual(5000000000, get_si('si_g'))
1025
self.assertEqual(5000000000, get_si('si_gb'))
1026
self.assertEqual(None, get_si('non-exist'))
1027
self.assertEqual(42, get_si('non-exist-with-default', 42))
1030
class TestSupressWarning(TestIniConfig):
1032
def make_warnings_config(self, s):
1033
conf, parser = self.make_config_parser(s)
1034
return conf.suppress_warning
1036
def test_suppress_warning_unknown(self):
1037
suppress_warning = self.make_warnings_config('')
1038
self.assertEqual(False, suppress_warning('unknown_warning'))
1040
def test_suppress_warning_known(self):
1041
suppress_warning = self.make_warnings_config('suppress_warnings=a,b')
1042
self.assertEqual(False, suppress_warning('c'))
1043
self.assertEqual(True, suppress_warning('a'))
1044
self.assertEqual(True, suppress_warning('b'))
1047
class TestGetConfig(tests.TestCase):
1049
def test_constructs(self):
1050
my_config = config.GlobalConfig()
1052
def test_calls_read_filenames(self):
1053
# replace the class that is constructed, to check its parameters
1054
oldparserclass = config.ConfigObj
1055
config.ConfigObj = InstrumentedConfigObj
1056
my_config = config.GlobalConfig()
1058
parser = my_config._get_parser()
1060
config.ConfigObj = oldparserclass
1061
self.assertIsInstance(parser, InstrumentedConfigObj)
1062
self.assertEqual(parser._calls, [('__init__', config.config_filename(),
1066
class TestBranchConfig(tests.TestCaseWithTransport):
1068
def test_constructs(self):
1069
branch = FakeBranch()
1070
my_config = config.BranchConfig(branch)
1071
self.assertRaises(TypeError, config.BranchConfig)
1073
def test_get_location_config(self):
1074
branch = FakeBranch()
1075
my_config = config.BranchConfig(branch)
1076
location_config = my_config._get_location_config()
1077
self.assertEqual(branch.base, location_config.location)
1078
self.assertIs(location_config, my_config._get_location_config())
1080
def test_get_config(self):
1081
"""The Branch.get_config method works properly"""
1082
b = controldir.ControlDir.create_standalone_workingtree('.').branch
1083
my_config = b.get_config()
1084
self.assertIs(my_config.get_user_option('wacky'), None)
1085
my_config.set_user_option('wacky', 'unlikely')
1086
self.assertEqual(my_config.get_user_option('wacky'), 'unlikely')
1088
# Ensure we get the same thing if we start again
1089
b2 = branch.Branch.open('.')
1090
my_config2 = b2.get_config()
1091
self.assertEqual(my_config2.get_user_option('wacky'), 'unlikely')
1093
def test_has_explicit_nickname(self):
1094
b = self.make_branch('.')
1095
self.assertFalse(b.get_config().has_explicit_nickname())
1097
self.assertTrue(b.get_config().has_explicit_nickname())
1099
def test_config_url(self):
1100
"""The Branch.get_config will use section that uses a local url"""
1101
branch = self.make_branch('branch')
1102
self.assertEqual('branch', branch.nick)
1104
local_url = urlutils.local_path_to_url('branch')
1105
conf = config.LocationConfig.from_string(
1106
'[%s]\nnickname = foobar' % (local_url,),
1107
local_url, save=True)
1108
self.assertEqual('foobar', branch.nick)
1110
def test_config_local_path(self):
1111
"""The Branch.get_config will use a local system path"""
1112
branch = self.make_branch('branch')
1113
self.assertEqual('branch', branch.nick)
1115
local_path = osutils.getcwd().encode('utf8')
1116
conf = config.LocationConfig.from_string(
1117
'[%s/branch]\nnickname = barry' % (local_path,),
1118
'branch', save=True)
1119
self.assertEqual('barry', branch.nick)
1121
def test_config_creates_local(self):
1122
"""Creating a new entry in config uses a local path."""
1123
branch = self.make_branch('branch', format='knit')
1124
branch.set_push_location('http://foobar')
1125
local_path = osutils.getcwd().encode('utf8')
1126
# Surprisingly ConfigObj doesn't create a trailing newline
1127
self.check_file_contents(config.locations_config_filename(),
1129
'push_location = http://foobar\n'
1130
'push_location:policy = norecurse\n'
1133
def test_autonick_urlencoded(self):
1134
b = self.make_branch('!repo')
1135
self.assertEqual('!repo', b.get_config().get_nickname())
1137
def test_autonick_uses_branch_name(self):
1138
b = self.make_branch('foo', name='bar')
1139
self.assertEqual('bar', b.get_config().get_nickname())
1141
def test_warn_if_masked(self):
1144
warnings.append(args[0] % args[1:])
1145
self.overrideAttr(trace, 'warning', warning)
1147
def set_option(store, warn_masked=True):
1149
conf.set_user_option('example_option', repr(store), store=store,
1150
warn_masked=warn_masked)
1151
def assertWarning(warning):
1153
self.assertEqual(0, len(warnings))
1155
self.assertEqual(1, len(warnings))
1156
self.assertEqual(warning, warnings[0])
1157
branch = self.make_branch('.')
1158
conf = branch.get_config()
1159
set_option(config.STORE_GLOBAL)
1161
set_option(config.STORE_BRANCH)
1163
set_option(config.STORE_GLOBAL)
1164
assertWarning('Value "4" is masked by "3" from branch.conf')
1165
set_option(config.STORE_GLOBAL, warn_masked=False)
1167
set_option(config.STORE_LOCATION)
1169
set_option(config.STORE_BRANCH)
1170
assertWarning('Value "3" is masked by "0" from locations.conf')
1171
set_option(config.STORE_BRANCH, warn_masked=False)
1175
class TestGlobalConfigItems(tests.TestCaseInTempDir):
1177
def test_user_id(self):
1178
my_config = config.GlobalConfig.from_string(sample_config_text)
1179
self.assertEqual(u"Erik B\u00e5gfors <erik@bagfors.nu>",
1180
my_config._get_user_id())
1182
def test_absent_user_id(self):
1183
my_config = config.GlobalConfig()
1184
self.assertEqual(None, my_config._get_user_id())
1186
def test_signatures_always(self):
1187
my_config = config.GlobalConfig.from_string(sample_always_signatures)
1188
self.assertEqual(config.CHECK_NEVER,
1189
self.applyDeprecated(deprecated_in((2, 5, 0)),
1190
my_config.signature_checking))
1191
self.assertEqual(config.SIGN_ALWAYS,
1192
self.applyDeprecated(deprecated_in((2, 5, 0)),
1193
my_config.signing_policy))
1194
self.assertEqual(True,
1195
self.applyDeprecated(deprecated_in((2, 5, 0)),
1196
my_config.signature_needed))
1198
def test_signatures_if_possible(self):
1199
my_config = config.GlobalConfig.from_string(sample_maybe_signatures)
1200
self.assertEqual(config.CHECK_NEVER,
1201
self.applyDeprecated(deprecated_in((2, 5, 0)),
1202
my_config.signature_checking))
1203
self.assertEqual(config.SIGN_WHEN_REQUIRED,
1204
self.applyDeprecated(deprecated_in((2, 5, 0)),
1205
my_config.signing_policy))
1206
self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
1207
my_config.signature_needed))
1209
def test_signatures_ignore(self):
1210
my_config = config.GlobalConfig.from_string(sample_ignore_signatures)
1211
self.assertEqual(config.CHECK_ALWAYS,
1212
self.applyDeprecated(deprecated_in((2, 5, 0)),
1213
my_config.signature_checking))
1214
self.assertEqual(config.SIGN_NEVER,
1215
self.applyDeprecated(deprecated_in((2, 5, 0)),
1216
my_config.signing_policy))
1217
self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
1218
my_config.signature_needed))
1220
def _get_sample_config(self):
1221
my_config = config.GlobalConfig.from_string(sample_config_text)
1224
def test_gpg_signing_command(self):
1225
my_config = self._get_sample_config()
1226
self.assertEqual("gnome-gpg",
1227
self.applyDeprecated(
1228
deprecated_in((2, 5, 0)), my_config.gpg_signing_command))
1229
self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
1230
my_config.signature_needed))
1232
def test_gpg_signing_key(self):
1233
my_config = self._get_sample_config()
1234
self.assertEqual("DD4D5088",
1235
self.applyDeprecated(deprecated_in((2, 5, 0)),
1236
my_config.gpg_signing_key))
1238
def _get_empty_config(self):
1239
my_config = config.GlobalConfig()
1242
def test_gpg_signing_command_unset(self):
1243
my_config = self._get_empty_config()
1244
self.assertEqual("gpg",
1245
self.applyDeprecated(
1246
deprecated_in((2, 5, 0)), my_config.gpg_signing_command))
1248
def test_get_user_option_default(self):
1249
my_config = self._get_empty_config()
1250
self.assertEqual(None, my_config.get_user_option('no_option'))
1252
def test_get_user_option_global(self):
1253
my_config = self._get_sample_config()
1254
self.assertEqual("something",
1255
my_config.get_user_option('user_global_option'))
1257
def test_post_commit_default(self):
1258
my_config = self._get_sample_config()
1259
self.assertEqual(None,
1260
self.applyDeprecated(deprecated_in((2, 5, 0)),
1261
my_config.post_commit))
1263
def test_configured_logformat(self):
1264
my_config = self._get_sample_config()
1265
self.assertEqual("short",
1266
self.applyDeprecated(deprecated_in((2, 5, 0)),
1267
my_config.log_format))
1269
def test_configured_acceptable_keys(self):
1270
my_config = self._get_sample_config()
1271
self.assertEqual("amy",
1272
self.applyDeprecated(deprecated_in((2, 5, 0)),
1273
my_config.acceptable_keys))
1275
def test_configured_validate_signatures_in_log(self):
1276
my_config = self._get_sample_config()
1277
self.assertEqual(True, my_config.validate_signatures_in_log())
1279
def test_get_alias(self):
1280
my_config = self._get_sample_config()
1281
self.assertEqual('help', my_config.get_alias('h'))
1283
def test_get_aliases(self):
1284
my_config = self._get_sample_config()
1285
aliases = my_config.get_aliases()
1286
self.assertEqual(2, len(aliases))
1287
sorted_keys = sorted(aliases)
1288
self.assertEqual('help', aliases[sorted_keys[0]])
1289
self.assertEqual(sample_long_alias, aliases[sorted_keys[1]])
1291
def test_get_no_alias(self):
1292
my_config = self._get_sample_config()
1293
self.assertEqual(None, my_config.get_alias('foo'))
1295
def test_get_long_alias(self):
1296
my_config = self._get_sample_config()
1297
self.assertEqual(sample_long_alias, my_config.get_alias('ll'))
1299
def test_get_change_editor(self):
1300
my_config = self._get_sample_config()
1301
change_editor = my_config.get_change_editor('old', 'new')
1302
self.assertIs(diff.DiffFromTool, change_editor.__class__)
1303
self.assertEqual('vimdiff -of @new_path @old_path',
1304
' '.join(change_editor.command_template))
1306
def test_get_no_change_editor(self):
1307
my_config = self._get_empty_config()
1308
change_editor = my_config.get_change_editor('old', 'new')
1309
self.assertIs(None, change_editor)
1311
def test_get_merge_tools(self):
1312
conf = self._get_sample_config()
1313
tools = conf.get_merge_tools()
1314
self.log(repr(tools))
1316
{u'funkytool' : u'funkytool "arg with spaces" {this_temp}',
1317
u'sometool' : u'sometool {base} {this} {other} -o {result}',
1318
u'newtool' : u'"newtool with spaces" {this_temp}'},
1321
def test_get_merge_tools_empty(self):
1322
conf = self._get_empty_config()
1323
tools = conf.get_merge_tools()
1324
self.assertEqual({}, tools)
1326
def test_find_merge_tool(self):
1327
conf = self._get_sample_config()
1328
cmdline = conf.find_merge_tool('sometool')
1329
self.assertEqual('sometool {base} {this} {other} -o {result}', cmdline)
1331
def test_find_merge_tool_not_found(self):
1332
conf = self._get_sample_config()
1333
cmdline = conf.find_merge_tool('DOES NOT EXIST')
1334
self.assertIs(cmdline, None)
1336
def test_find_merge_tool_known(self):
1337
conf = self._get_empty_config()
1338
cmdline = conf.find_merge_tool('kdiff3')
1339
self.assertEquals('kdiff3 {base} {this} {other} -o {result}', cmdline)
1341
def test_find_merge_tool_override_known(self):
1342
conf = self._get_empty_config()
1343
conf.set_user_option('bzr.mergetool.kdiff3', 'kdiff3 blah')
1344
cmdline = conf.find_merge_tool('kdiff3')
1345
self.assertEqual('kdiff3 blah', cmdline)
1348
class TestGlobalConfigSavingOptions(tests.TestCaseInTempDir):
1350
def test_empty(self):
1351
my_config = config.GlobalConfig()
1352
self.assertEqual(0, len(my_config.get_aliases()))
1354
def test_set_alias(self):
1355
my_config = config.GlobalConfig()
1356
alias_value = 'commit --strict'
1357
my_config.set_alias('commit', alias_value)
1358
new_config = config.GlobalConfig()
1359
self.assertEqual(alias_value, new_config.get_alias('commit'))
1361
def test_remove_alias(self):
1362
my_config = config.GlobalConfig()
1363
my_config.set_alias('commit', 'commit --strict')
1364
# Now remove the alias again.
1365
my_config.unset_alias('commit')
1366
new_config = config.GlobalConfig()
1367
self.assertIs(None, new_config.get_alias('commit'))
1370
class TestLocationConfig(tests.TestCaseInTempDir, TestOptionsMixin):
1372
def test_constructs(self):
1373
my_config = config.LocationConfig('http://example.com')
1374
self.assertRaises(TypeError, config.LocationConfig)
1376
def test_branch_calls_read_filenames(self):
1377
# This is testing the correct file names are provided.
1378
# TODO: consolidate with the test for GlobalConfigs filename checks.
1380
# replace the class that is constructed, to check its parameters
1381
oldparserclass = config.ConfigObj
1382
config.ConfigObj = InstrumentedConfigObj
1384
my_config = config.LocationConfig('http://www.example.com')
1385
parser = my_config._get_parser()
1387
config.ConfigObj = oldparserclass
1388
self.assertIsInstance(parser, InstrumentedConfigObj)
1389
self.assertEqual(parser._calls,
1390
[('__init__', config.locations_config_filename(),
1393
def test_get_global_config(self):
1394
my_config = config.BranchConfig(FakeBranch('http://example.com'))
1395
global_config = my_config._get_global_config()
1396
self.assertIsInstance(global_config, config.GlobalConfig)
1397
self.assertIs(global_config, my_config._get_global_config())
1399
def assertLocationMatching(self, expected):
1400
self.assertEqual(expected,
1401
list(self.my_location_config._get_matching_sections()))
1403
def test__get_matching_sections_no_match(self):
1404
self.get_branch_config('/')
1405
self.assertLocationMatching([])
1407
def test__get_matching_sections_exact(self):
1408
self.get_branch_config('http://www.example.com')
1409
self.assertLocationMatching([('http://www.example.com', '')])
1411
def test__get_matching_sections_suffix_does_not(self):
1412
self.get_branch_config('http://www.example.com-com')
1413
self.assertLocationMatching([])
1415
def test__get_matching_sections_subdir_recursive(self):
1416
self.get_branch_config('http://www.example.com/com')
1417
self.assertLocationMatching([('http://www.example.com', 'com')])
1419
def test__get_matching_sections_ignoreparent(self):
1420
self.get_branch_config('http://www.example.com/ignoreparent')
1421
self.assertLocationMatching([('http://www.example.com/ignoreparent',
1424
def test__get_matching_sections_ignoreparent_subdir(self):
1425
self.get_branch_config(
1426
'http://www.example.com/ignoreparent/childbranch')
1427
self.assertLocationMatching([('http://www.example.com/ignoreparent',
1430
def test__get_matching_sections_subdir_trailing_slash(self):
1431
self.get_branch_config('/b')
1432
self.assertLocationMatching([('/b/', '')])
1434
def test__get_matching_sections_subdir_child(self):
1435
self.get_branch_config('/a/foo')
1436
self.assertLocationMatching([('/a/*', ''), ('/a/', 'foo')])
1438
def test__get_matching_sections_subdir_child_child(self):
1439
self.get_branch_config('/a/foo/bar')
1440
self.assertLocationMatching([('/a/*', 'bar'), ('/a/', 'foo/bar')])
1442
def test__get_matching_sections_trailing_slash_with_children(self):
1443
self.get_branch_config('/a/')
1444
self.assertLocationMatching([('/a/', '')])
1446
def test__get_matching_sections_explicit_over_glob(self):
1447
# XXX: 2006-09-08 jamesh
1448
# This test only passes because ord('c') > ord('*'). If there
1449
# was a config section for '/a/?', it would get precedence
1451
self.get_branch_config('/a/c')
1452
self.assertLocationMatching([('/a/c', ''), ('/a/*', ''), ('/a/', 'c')])
1454
def test__get_option_policy_normal(self):
1455
self.get_branch_config('http://www.example.com')
1457
self.my_location_config._get_config_policy(
1458
'http://www.example.com', 'normal_option'),
1461
def test__get_option_policy_norecurse(self):
1462
self.get_branch_config('http://www.example.com')
1464
self.my_location_config._get_option_policy(
1465
'http://www.example.com', 'norecurse_option'),
1466
config.POLICY_NORECURSE)
1467
# Test old recurse=False setting:
1469
self.my_location_config._get_option_policy(
1470
'http://www.example.com/norecurse', 'normal_option'),
1471
config.POLICY_NORECURSE)
1473
def test__get_option_policy_normal(self):
1474
self.get_branch_config('http://www.example.com')
1476
self.my_location_config._get_option_policy(
1477
'http://www.example.com', 'appendpath_option'),
1478
config.POLICY_APPENDPATH)
1480
def test__get_options_with_policy(self):
1481
self.get_branch_config('/dir/subdir',
1482
location_config="""\
1484
other_url = /other-dir
1485
other_url:policy = appendpath
1487
other_url = /other-subdir
1490
[(u'other_url', u'/other-subdir', u'/dir/subdir', 'locations'),
1491
(u'other_url', u'/other-dir', u'/dir', 'locations'),
1492
(u'other_url:policy', u'appendpath', u'/dir', 'locations')],
1493
self.my_location_config)
1495
def test_location_without_username(self):
1496
self.get_branch_config('http://www.example.com/ignoreparent')
1497
self.assertEqual(u'Erik B\u00e5gfors <erik@bagfors.nu>',
1498
self.my_config.username())
1500
def test_location_not_listed(self):
1501
"""Test that the global username is used when no location matches"""
1502
self.get_branch_config('/home/robertc/sources')
1503
self.assertEqual(u'Erik B\u00e5gfors <erik@bagfors.nu>',
1504
self.my_config.username())
1506
def test_overriding_location(self):
1507
self.get_branch_config('http://www.example.com/foo')
1508
self.assertEqual('Robert Collins <robertc@example.org>',
1509
self.my_config.username())
1511
def test_signatures_not_set(self):
1512
self.get_branch_config('http://www.example.com',
1513
global_config=sample_ignore_signatures)
1514
self.assertEqual(config.CHECK_ALWAYS,
1515
self.applyDeprecated(deprecated_in((2, 5, 0)),
1516
self.my_config.signature_checking))
1517
self.assertEqual(config.SIGN_NEVER,
1518
self.applyDeprecated(deprecated_in((2, 5, 0)),
1519
self.my_config.signing_policy))
1521
def test_signatures_never(self):
1522
self.get_branch_config('/a/c')
1523
self.assertEqual(config.CHECK_NEVER,
1524
self.applyDeprecated(deprecated_in((2, 5, 0)),
1525
self.my_config.signature_checking))
1527
def test_signatures_when_available(self):
1528
self.get_branch_config('/a/', global_config=sample_ignore_signatures)
1529
self.assertEqual(config.CHECK_IF_POSSIBLE,
1530
self.applyDeprecated(deprecated_in((2, 5, 0)),
1531
self.my_config.signature_checking))
1533
def test_signatures_always(self):
1534
self.get_branch_config('/b')
1535
self.assertEqual(config.CHECK_ALWAYS,
1536
self.applyDeprecated(deprecated_in((2, 5, 0)),
1537
self.my_config.signature_checking))
1539
def test_gpg_signing_command(self):
1540
self.get_branch_config('/b')
1541
self.assertEqual("gnome-gpg",
1542
self.applyDeprecated(deprecated_in((2, 5, 0)),
1543
self.my_config.gpg_signing_command))
1545
def test_gpg_signing_command_missing(self):
1546
self.get_branch_config('/a')
1547
self.assertEqual("false",
1548
self.applyDeprecated(deprecated_in((2, 5, 0)),
1549
self.my_config.gpg_signing_command))
1551
def test_gpg_signing_key(self):
1552
self.get_branch_config('/b')
1553
self.assertEqual("DD4D5088", self.applyDeprecated(deprecated_in((2, 5, 0)),
1554
self.my_config.gpg_signing_key))
1556
def test_gpg_signing_key_default(self):
1557
self.get_branch_config('/a')
1558
self.assertEqual("erik@bagfors.nu",
1559
self.applyDeprecated(deprecated_in((2, 5, 0)),
1560
self.my_config.gpg_signing_key))
1562
def test_get_user_option_global(self):
1563
self.get_branch_config('/a')
1564
self.assertEqual('something',
1565
self.my_config.get_user_option('user_global_option'))
1567
def test_get_user_option_local(self):
1568
self.get_branch_config('/a')
1569
self.assertEqual('local',
1570
self.my_config.get_user_option('user_local_option'))
1572
def test_get_user_option_appendpath(self):
1573
# returned as is for the base path:
1574
self.get_branch_config('http://www.example.com')
1575
self.assertEqual('append',
1576
self.my_config.get_user_option('appendpath_option'))
1577
# Extra path components get appended:
1578
self.get_branch_config('http://www.example.com/a/b/c')
1579
self.assertEqual('append/a/b/c',
1580
self.my_config.get_user_option('appendpath_option'))
1581
# Overriden for http://www.example.com/dir, where it is a
1583
self.get_branch_config('http://www.example.com/dir/a/b/c')
1584
self.assertEqual('normal',
1585
self.my_config.get_user_option('appendpath_option'))
1587
def test_get_user_option_norecurse(self):
1588
self.get_branch_config('http://www.example.com')
1589
self.assertEqual('norecurse',
1590
self.my_config.get_user_option('norecurse_option'))
1591
self.get_branch_config('http://www.example.com/dir')
1592
self.assertEqual(None,
1593
self.my_config.get_user_option('norecurse_option'))
1594
# http://www.example.com/norecurse is a recurse=False section
1595
# that redefines normal_option. Subdirectories do not pick up
1596
# this redefinition.
1597
self.get_branch_config('http://www.example.com/norecurse')
1598
self.assertEqual('norecurse',
1599
self.my_config.get_user_option('normal_option'))
1600
self.get_branch_config('http://www.example.com/norecurse/subdir')
1601
self.assertEqual('normal',
1602
self.my_config.get_user_option('normal_option'))
1604
def test_set_user_option_norecurse(self):
1605
self.get_branch_config('http://www.example.com')
1606
self.my_config.set_user_option('foo', 'bar',
1607
store=config.STORE_LOCATION_NORECURSE)
1609
self.my_location_config._get_option_policy(
1610
'http://www.example.com', 'foo'),
1611
config.POLICY_NORECURSE)
1613
def test_set_user_option_appendpath(self):
1614
self.get_branch_config('http://www.example.com')
1615
self.my_config.set_user_option('foo', 'bar',
1616
store=config.STORE_LOCATION_APPENDPATH)
1618
self.my_location_config._get_option_policy(
1619
'http://www.example.com', 'foo'),
1620
config.POLICY_APPENDPATH)
1622
def test_set_user_option_change_policy(self):
1623
self.get_branch_config('http://www.example.com')
1624
self.my_config.set_user_option('norecurse_option', 'normal',
1625
store=config.STORE_LOCATION)
1627
self.my_location_config._get_option_policy(
1628
'http://www.example.com', 'norecurse_option'),
1631
def test_set_user_option_recurse_false_section(self):
1632
# The following section has recurse=False set. The test is to
1633
# make sure that a normal option can be added to the section,
1634
# converting recurse=False to the norecurse policy.
1635
self.get_branch_config('http://www.example.com/norecurse')
1636
self.callDeprecated(['The recurse option is deprecated as of 0.14. '
1637
'The section "http://www.example.com/norecurse" '
1638
'has been converted to use policies.'],
1639
self.my_config.set_user_option,
1640
'foo', 'bar', store=config.STORE_LOCATION)
1642
self.my_location_config._get_option_policy(
1643
'http://www.example.com/norecurse', 'foo'),
1645
# The previously existing option is still norecurse:
1647
self.my_location_config._get_option_policy(
1648
'http://www.example.com/norecurse', 'normal_option'),
1649
config.POLICY_NORECURSE)
1651
def test_post_commit_default(self):
1652
self.get_branch_config('/a/c')
1653
self.assertEqual('bzrlib.tests.test_config.post_commit',
1654
self.applyDeprecated(deprecated_in((2, 5, 0)),
1655
self.my_config.post_commit))
1657
def get_branch_config(self, location, global_config=None,
1658
location_config=None):
1659
my_branch = FakeBranch(location)
1660
if global_config is None:
1661
global_config = sample_config_text
1662
if location_config is None:
1663
location_config = sample_branches_text
1665
my_global_config = config.GlobalConfig.from_string(global_config,
1667
my_location_config = config.LocationConfig.from_string(
1668
location_config, my_branch.base, save=True)
1669
my_config = config.BranchConfig(my_branch)
1670
self.my_config = my_config
1671
self.my_location_config = my_config._get_location_config()
1673
def test_set_user_setting_sets_and_saves(self):
1674
self.get_branch_config('/a/c')
1675
record = InstrumentedConfigObj("foo")
1676
self.my_location_config._parser = record
1678
self.callDeprecated(['The recurse option is deprecated as of '
1679
'0.14. The section "/a/c" has been '
1680
'converted to use policies.'],
1681
self.my_config.set_user_option,
1682
'foo', 'bar', store=config.STORE_LOCATION)
1683
self.assertEqual([('reload',),
1684
('__contains__', '/a/c'),
1685
('__contains__', '/a/c/'),
1686
('__setitem__', '/a/c', {}),
1687
('__getitem__', '/a/c'),
1688
('__setitem__', 'foo', 'bar'),
1689
('__getitem__', '/a/c'),
1690
('as_bool', 'recurse'),
1691
('__getitem__', '/a/c'),
1692
('__delitem__', 'recurse'),
1693
('__getitem__', '/a/c'),
1695
('__getitem__', '/a/c'),
1696
('__contains__', 'foo:policy'),
1700
def test_set_user_setting_sets_and_saves2(self):
1701
self.get_branch_config('/a/c')
1702
self.assertIs(self.my_config.get_user_option('foo'), None)
1703
self.my_config.set_user_option('foo', 'bar')
1705
self.my_config.branch.control_files.files['branch.conf'].strip(),
1707
self.assertEqual(self.my_config.get_user_option('foo'), 'bar')
1708
self.my_config.set_user_option('foo', 'baz',
1709
store=config.STORE_LOCATION)
1710
self.assertEqual(self.my_config.get_user_option('foo'), 'baz')
1711
self.my_config.set_user_option('foo', 'qux')
1712
self.assertEqual(self.my_config.get_user_option('foo'), 'baz')
1714
def test_get_bzr_remote_path(self):
1715
my_config = config.LocationConfig('/a/c')
1716
self.assertEqual('bzr', my_config.get_bzr_remote_path())
1717
my_config.set_user_option('bzr_remote_path', '/path-bzr')
1718
self.assertEqual('/path-bzr', my_config.get_bzr_remote_path())
1719
self.overrideEnv('BZR_REMOTE_PATH', '/environ-bzr')
1720
self.assertEqual('/environ-bzr', my_config.get_bzr_remote_path())
1723
precedence_global = 'option = global'
1724
precedence_branch = 'option = branch'
1725
precedence_location = """
1729
[http://example.com/specific]
1733
class TestBranchConfigItems(tests.TestCaseInTempDir):
1735
def get_branch_config(self, global_config=None, location=None,
1736
location_config=None, branch_data_config=None):
1737
my_branch = FakeBranch(location)
1738
if global_config is not None:
1739
my_global_config = config.GlobalConfig.from_string(global_config,
1741
if location_config is not None:
1742
my_location_config = config.LocationConfig.from_string(
1743
location_config, my_branch.base, save=True)
1744
my_config = config.BranchConfig(my_branch)
1745
if branch_data_config is not None:
1746
my_config.branch.control_files.files['branch.conf'] = \
1750
def test_user_id(self):
1751
branch = FakeBranch()
1752
my_config = config.BranchConfig(branch)
1753
self.assertIsNot(None, my_config.username())
1754
my_config.branch.control_files.files['email'] = "John"
1755
my_config.set_user_option('email',
1756
"Robert Collins <robertc@example.org>")
1757
self.assertEqual("Robert Collins <robertc@example.org>",
1758
my_config.username())
1760
def test_BZR_EMAIL_OVERRIDES(self):
1761
self.overrideEnv('BZR_EMAIL', "Robert Collins <robertc@example.org>")
1762
branch = FakeBranch()
1763
my_config = config.BranchConfig(branch)
1764
self.assertEqual("Robert Collins <robertc@example.org>",
1765
my_config.username())
1767
def test_signatures_forced(self):
1768
my_config = self.get_branch_config(
1769
global_config=sample_always_signatures)
1770
self.assertEqual(config.CHECK_NEVER,
1771
self.applyDeprecated(deprecated_in((2, 5, 0)),
1772
my_config.signature_checking))
1773
self.assertEqual(config.SIGN_ALWAYS,
1774
self.applyDeprecated(deprecated_in((2, 5, 0)),
1775
my_config.signing_policy))
1776
self.assertTrue(self.applyDeprecated(deprecated_in((2, 5, 0)),
1777
my_config.signature_needed))
1779
def test_signatures_forced_branch(self):
1780
my_config = self.get_branch_config(
1781
global_config=sample_ignore_signatures,
1782
branch_data_config=sample_always_signatures)
1783
self.assertEqual(config.CHECK_NEVER,
1784
self.applyDeprecated(deprecated_in((2, 5, 0)),
1785
my_config.signature_checking))
1786
self.assertEqual(config.SIGN_ALWAYS,
1787
self.applyDeprecated(deprecated_in((2, 5, 0)),
1788
my_config.signing_policy))
1789
self.assertTrue(self.applyDeprecated(deprecated_in((2, 5, 0)),
1790
my_config.signature_needed))
1792
def test_gpg_signing_command(self):
1793
my_config = self.get_branch_config(
1794
global_config=sample_config_text,
1795
# branch data cannot set gpg_signing_command
1796
branch_data_config="gpg_signing_command=pgp")
1797
self.assertEqual('gnome-gpg',
1798
self.applyDeprecated(deprecated_in((2, 5, 0)),
1799
my_config.gpg_signing_command))
1801
def test_get_user_option_global(self):
1802
my_config = self.get_branch_config(global_config=sample_config_text)
1803
self.assertEqual('something',
1804
my_config.get_user_option('user_global_option'))
1806
def test_post_commit_default(self):
1807
my_config = self.get_branch_config(global_config=sample_config_text,
1809
location_config=sample_branches_text)
1810
self.assertEqual(my_config.branch.base, '/a/c')
1811
self.assertEqual('bzrlib.tests.test_config.post_commit',
1812
self.applyDeprecated(deprecated_in((2, 5, 0)),
1813
my_config.post_commit))
1814
my_config.set_user_option('post_commit', 'rmtree_root')
1815
# post-commit is ignored when present in branch data
1816
self.assertEqual('bzrlib.tests.test_config.post_commit',
1817
self.applyDeprecated(deprecated_in((2, 5, 0)),
1818
my_config.post_commit))
1819
my_config.set_user_option('post_commit', 'rmtree_root',
1820
store=config.STORE_LOCATION)
1821
self.assertEqual('rmtree_root',
1822
self.applyDeprecated(deprecated_in((2, 5, 0)),
1823
my_config.post_commit))
1825
def test_config_precedence(self):
1826
# FIXME: eager test, luckily no persitent config file makes it fail
1828
my_config = self.get_branch_config(global_config=precedence_global)
1829
self.assertEqual(my_config.get_user_option('option'), 'global')
1830
my_config = self.get_branch_config(global_config=precedence_global,
1831
branch_data_config=precedence_branch)
1832
self.assertEqual(my_config.get_user_option('option'), 'branch')
1833
my_config = self.get_branch_config(
1834
global_config=precedence_global,
1835
branch_data_config=precedence_branch,
1836
location_config=precedence_location)
1837
self.assertEqual(my_config.get_user_option('option'), 'recurse')
1838
my_config = self.get_branch_config(
1839
global_config=precedence_global,
1840
branch_data_config=precedence_branch,
1841
location_config=precedence_location,
1842
location='http://example.com/specific')
1843
self.assertEqual(my_config.get_user_option('option'), 'exact')
1846
class TestMailAddressExtraction(tests.TestCase):
1848
def test_extract_email_address(self):
1849
self.assertEqual('jane@test.com',
1850
config.extract_email_address('Jane <jane@test.com>'))
1851
self.assertRaises(errors.NoEmailInUsername,
1852
config.extract_email_address, 'Jane Tester')
1854
def test_parse_username(self):
1855
self.assertEqual(('', 'jdoe@example.com'),
1856
config.parse_username('jdoe@example.com'))
1857
self.assertEqual(('', 'jdoe@example.com'),
1858
config.parse_username('<jdoe@example.com>'))
1859
self.assertEqual(('John Doe', 'jdoe@example.com'),
1860
config.parse_username('John Doe <jdoe@example.com>'))
1861
self.assertEqual(('John Doe', ''),
1862
config.parse_username('John Doe'))
1863
self.assertEqual(('John Doe', 'jdoe@example.com'),
1864
config.parse_username('John Doe jdoe@example.com'))
1866
class TestTreeConfig(tests.TestCaseWithTransport):
1868
def test_get_value(self):
1869
"""Test that retreiving a value from a section is possible"""
1870
branch = self.make_branch('.')
1871
tree_config = config.TreeConfig(branch)
1872
tree_config.set_option('value', 'key', 'SECTION')
1873
tree_config.set_option('value2', 'key2')
1874
tree_config.set_option('value3-top', 'key3')
1875
tree_config.set_option('value3-section', 'key3', 'SECTION')
1876
value = tree_config.get_option('key', 'SECTION')
1877
self.assertEqual(value, 'value')
1878
value = tree_config.get_option('key2')
1879
self.assertEqual(value, 'value2')
1880
self.assertEqual(tree_config.get_option('non-existant'), None)
1881
value = tree_config.get_option('non-existant', 'SECTION')
1882
self.assertEqual(value, None)
1883
value = tree_config.get_option('non-existant', default='default')
1884
self.assertEqual(value, 'default')
1885
self.assertEqual(tree_config.get_option('key2', 'NOSECTION'), None)
1886
value = tree_config.get_option('key2', 'NOSECTION', default='default')
1887
self.assertEqual(value, 'default')
1888
value = tree_config.get_option('key3')
1889
self.assertEqual(value, 'value3-top')
1890
value = tree_config.get_option('key3', 'SECTION')
1891
self.assertEqual(value, 'value3-section')
1894
class TestTransportConfig(tests.TestCaseWithTransport):
1896
def test_load_utf8(self):
1897
"""Ensure we can load an utf8-encoded file."""
1898
t = self.get_transport()
1899
unicode_user = u'b\N{Euro Sign}ar'
1900
unicode_content = u'user=%s' % (unicode_user,)
1901
utf8_content = unicode_content.encode('utf8')
1902
# Store the raw content in the config file
1903
t.put_bytes('foo.conf', utf8_content)
1904
conf = config.TransportConfig(t, 'foo.conf')
1905
self.assertEquals(unicode_user, conf.get_option('user'))
1907
def test_load_non_ascii(self):
1908
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
1909
t = self.get_transport()
1910
t.put_bytes('foo.conf', 'user=foo\n#\xff\n')
1911
conf = config.TransportConfig(t, 'foo.conf')
1912
self.assertRaises(errors.ConfigContentError, conf._get_configobj)
1914
def test_load_erroneous_content(self):
1915
"""Ensure we display a proper error on content that can't be parsed."""
1916
t = self.get_transport()
1917
t.put_bytes('foo.conf', '[open_section\n')
1918
conf = config.TransportConfig(t, 'foo.conf')
1919
self.assertRaises(errors.ParseConfigError, conf._get_configobj)
1921
def test_load_permission_denied(self):
1922
"""Ensure we get an empty config file if the file is inaccessible."""
1925
warnings.append(args[0] % args[1:])
1926
self.overrideAttr(trace, 'warning', warning)
1928
class DenyingTransport(object):
1930
def __init__(self, base):
1933
def get_bytes(self, relpath):
1934
raise errors.PermissionDenied(relpath, "")
1936
cfg = config.TransportConfig(
1937
DenyingTransport("nonexisting://"), 'control.conf')
1938
self.assertIs(None, cfg.get_option('non-existant', 'SECTION'))
1941
[u'Permission denied while trying to open configuration file '
1942
u'nonexisting:///control.conf.'])
1944
def test_get_value(self):
1945
"""Test that retreiving a value from a section is possible"""
1946
bzrdir_config = config.TransportConfig(self.get_transport('.'),
1948
bzrdir_config.set_option('value', 'key', 'SECTION')
1949
bzrdir_config.set_option('value2', 'key2')
1950
bzrdir_config.set_option('value3-top', 'key3')
1951
bzrdir_config.set_option('value3-section', 'key3', 'SECTION')
1952
value = bzrdir_config.get_option('key', 'SECTION')
1953
self.assertEqual(value, 'value')
1954
value = bzrdir_config.get_option('key2')
1955
self.assertEqual(value, 'value2')
1956
self.assertEqual(bzrdir_config.get_option('non-existant'), None)
1957
value = bzrdir_config.get_option('non-existant', 'SECTION')
1958
self.assertEqual(value, None)
1959
value = bzrdir_config.get_option('non-existant', default='default')
1960
self.assertEqual(value, 'default')
1961
self.assertEqual(bzrdir_config.get_option('key2', 'NOSECTION'), None)
1962
value = bzrdir_config.get_option('key2', 'NOSECTION',
1964
self.assertEqual(value, 'default')
1965
value = bzrdir_config.get_option('key3')
1966
self.assertEqual(value, 'value3-top')
1967
value = bzrdir_config.get_option('key3', 'SECTION')
1968
self.assertEqual(value, 'value3-section')
1970
def test_set_unset_default_stack_on(self):
1971
my_dir = self.make_bzrdir('.')
1972
bzrdir_config = config.BzrDirConfig(my_dir)
1973
self.assertIs(None, bzrdir_config.get_default_stack_on())
1974
bzrdir_config.set_default_stack_on('Foo')
1975
self.assertEqual('Foo', bzrdir_config._config.get_option(
1976
'default_stack_on'))
1977
self.assertEqual('Foo', bzrdir_config.get_default_stack_on())
1978
bzrdir_config.set_default_stack_on(None)
1979
self.assertIs(None, bzrdir_config.get_default_stack_on())
1982
class TestOldConfigHooks(tests.TestCaseWithTransport):
1985
super(TestOldConfigHooks, self).setUp()
1986
create_configs_with_file_option(self)
1988
def assertGetHook(self, conf, name, value):
1992
config.OldConfigHooks.install_named_hook('get', hook, None)
1994
config.OldConfigHooks.uninstall_named_hook, 'get', None)
1995
self.assertLength(0, calls)
1996
actual_value = conf.get_user_option(name)
1997
self.assertEquals(value, actual_value)
1998
self.assertLength(1, calls)
1999
self.assertEquals((conf, name, value), calls[0])
2001
def test_get_hook_bazaar(self):
2002
self.assertGetHook(self.bazaar_config, 'file', 'bazaar')
2004
def test_get_hook_locations(self):
2005
self.assertGetHook(self.locations_config, 'file', 'locations')
2007
def test_get_hook_branch(self):
2008
# Since locations masks branch, we define a different option
2009
self.branch_config.set_user_option('file2', 'branch')
2010
self.assertGetHook(self.branch_config, 'file2', 'branch')
2012
def assertSetHook(self, conf, name, value):
2016
config.OldConfigHooks.install_named_hook('set', hook, None)
2018
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2019
self.assertLength(0, calls)
2020
conf.set_user_option(name, value)
2021
self.assertLength(1, calls)
2022
# We can't assert the conf object below as different configs use
2023
# different means to implement set_user_option and we care only about
2025
self.assertEquals((name, value), calls[0][1:])
2027
def test_set_hook_bazaar(self):
2028
self.assertSetHook(self.bazaar_config, 'foo', 'bazaar')
2030
def test_set_hook_locations(self):
2031
self.assertSetHook(self.locations_config, 'foo', 'locations')
2033
def test_set_hook_branch(self):
2034
self.assertSetHook(self.branch_config, 'foo', 'branch')
2036
def assertRemoveHook(self, conf, name, section_name=None):
2040
config.OldConfigHooks.install_named_hook('remove', hook, None)
2042
config.OldConfigHooks.uninstall_named_hook, 'remove', None)
2043
self.assertLength(0, calls)
2044
conf.remove_user_option(name, section_name)
2045
self.assertLength(1, calls)
2046
# We can't assert the conf object below as different configs use
2047
# different means to implement remove_user_option and we care only about
2049
self.assertEquals((name,), calls[0][1:])
2051
def test_remove_hook_bazaar(self):
2052
self.assertRemoveHook(self.bazaar_config, 'file')
2054
def test_remove_hook_locations(self):
2055
self.assertRemoveHook(self.locations_config, 'file',
2056
self.locations_config.location)
2058
def test_remove_hook_branch(self):
2059
self.assertRemoveHook(self.branch_config, 'file')
2061
def assertLoadHook(self, name, conf_class, *conf_args):
2065
config.OldConfigHooks.install_named_hook('load', hook, None)
2067
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2068
self.assertLength(0, calls)
2070
conf = conf_class(*conf_args)
2071
# Access an option to trigger a load
2072
conf.get_user_option(name)
2073
self.assertLength(1, calls)
2074
# Since we can't assert about conf, we just use the number of calls ;-/
2076
def test_load_hook_bazaar(self):
2077
self.assertLoadHook('file', config.GlobalConfig)
2079
def test_load_hook_locations(self):
2080
self.assertLoadHook('file', config.LocationConfig, self.tree.basedir)
2082
def test_load_hook_branch(self):
2083
self.assertLoadHook('file', config.BranchConfig, self.tree.branch)
2085
def assertSaveHook(self, conf):
2089
config.OldConfigHooks.install_named_hook('save', hook, None)
2091
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2092
self.assertLength(0, calls)
2093
# Setting an option triggers a save
2094
conf.set_user_option('foo', 'bar')
2095
self.assertLength(1, calls)
2096
# Since we can't assert about conf, we just use the number of calls ;-/
2098
def test_save_hook_bazaar(self):
2099
self.assertSaveHook(self.bazaar_config)
2101
def test_save_hook_locations(self):
2102
self.assertSaveHook(self.locations_config)
2104
def test_save_hook_branch(self):
2105
self.assertSaveHook(self.branch_config)
2108
class TestOldConfigHooksForRemote(tests.TestCaseWithTransport):
2109
"""Tests config hooks for remote configs.
2111
No tests for the remove hook as this is not implemented there.
2115
super(TestOldConfigHooksForRemote, self).setUp()
2116
self.transport_server = test_server.SmartTCPServer_for_testing
2117
create_configs_with_file_option(self)
2119
def assertGetHook(self, conf, name, value):
2123
config.OldConfigHooks.install_named_hook('get', hook, None)
2125
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2126
self.assertLength(0, calls)
2127
actual_value = conf.get_option(name)
2128
self.assertEquals(value, actual_value)
2129
self.assertLength(1, calls)
2130
self.assertEquals((conf, name, value), calls[0])
2132
def test_get_hook_remote_branch(self):
2133
remote_branch = branch.Branch.open(self.get_url('tree'))
2134
self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
2136
def test_get_hook_remote_bzrdir(self):
2137
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2138
conf = remote_bzrdir._get_config()
2139
conf.set_option('remotedir', 'file')
2140
self.assertGetHook(conf, 'file', 'remotedir')
2142
def assertSetHook(self, conf, name, value):
2146
config.OldConfigHooks.install_named_hook('set', hook, None)
2148
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2149
self.assertLength(0, calls)
2150
conf.set_option(value, name)
2151
self.assertLength(1, calls)
2152
# We can't assert the conf object below as different configs use
2153
# different means to implement set_user_option and we care only about
2155
self.assertEquals((name, value), calls[0][1:])
2157
def test_set_hook_remote_branch(self):
2158
remote_branch = branch.Branch.open(self.get_url('tree'))
2159
self.addCleanup(remote_branch.lock_write().unlock)
2160
self.assertSetHook(remote_branch._get_config(), 'file', 'remote')
2162
def test_set_hook_remote_bzrdir(self):
2163
remote_branch = branch.Branch.open(self.get_url('tree'))
2164
self.addCleanup(remote_branch.lock_write().unlock)
2165
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2166
self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
2168
def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
2172
config.OldConfigHooks.install_named_hook('load', hook, None)
2174
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2175
self.assertLength(0, calls)
2177
conf = conf_class(*conf_args)
2178
# Access an option to trigger a load
2179
conf.get_option(name)
2180
self.assertLength(expected_nb_calls, calls)
2181
# Since we can't assert about conf, we just use the number of calls ;-/
2183
def test_load_hook_remote_branch(self):
2184
remote_branch = branch.Branch.open(self.get_url('tree'))
2185
self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
2187
def test_load_hook_remote_bzrdir(self):
2188
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2189
# The config file doesn't exist, set an option to force its creation
2190
conf = remote_bzrdir._get_config()
2191
conf.set_option('remotedir', 'file')
2192
# We get one call for the server and one call for the client, this is
2193
# caused by the differences in implementations betwen
2194
# SmartServerBzrDirRequestConfigFile (in smart/bzrdir.py) and
2195
# SmartServerBranchGetConfigFile (in smart/branch.py)
2196
self.assertLoadHook(2 ,'file', remote.RemoteBzrDirConfig, remote_bzrdir)
2198
def assertSaveHook(self, conf):
2202
config.OldConfigHooks.install_named_hook('save', hook, None)
2204
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2205
self.assertLength(0, calls)
2206
# Setting an option triggers a save
2207
conf.set_option('foo', 'bar')
2208
self.assertLength(1, calls)
2209
# Since we can't assert about conf, we just use the number of calls ;-/
2211
def test_save_hook_remote_branch(self):
2212
remote_branch = branch.Branch.open(self.get_url('tree'))
2213
self.addCleanup(remote_branch.lock_write().unlock)
2214
self.assertSaveHook(remote_branch._get_config())
2216
def test_save_hook_remote_bzrdir(self):
2217
remote_branch = branch.Branch.open(self.get_url('tree'))
2218
self.addCleanup(remote_branch.lock_write().unlock)
2219
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2220
self.assertSaveHook(remote_bzrdir._get_config())
2223
class TestOption(tests.TestCase):
2225
def test_default_value(self):
2226
opt = config.Option('foo', default='bar')
2227
self.assertEquals('bar', opt.get_default())
2229
def test_callable_default_value(self):
2230
def bar_as_unicode():
2232
opt = config.Option('foo', default=bar_as_unicode)
2233
self.assertEquals('bar', opt.get_default())
2235
def test_default_value_from_env(self):
2236
opt = config.Option('foo', default='bar', default_from_env=['FOO'])
2237
self.overrideEnv('FOO', 'quux')
2238
# Env variable provides a default taking over the option one
2239
self.assertEquals('quux', opt.get_default())
2241
def test_first_default_value_from_env_wins(self):
2242
opt = config.Option('foo', default='bar',
2243
default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
2244
self.overrideEnv('FOO', 'foo')
2245
self.overrideEnv('BAZ', 'baz')
2246
# The first env var set wins
2247
self.assertEquals('foo', opt.get_default())
2249
def test_not_supported_list_default_value(self):
2250
self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
2252
def test_not_supported_object_default_value(self):
2253
self.assertRaises(AssertionError, config.Option, 'foo',
2256
def test_not_supported_callable_default_value_not_unicode(self):
2257
def bar_not_unicode():
2259
opt = config.Option('foo', default=bar_not_unicode)
2260
self.assertRaises(AssertionError, opt.get_default)
2262
def test_get_help_topic(self):
2263
opt = config.Option('foo')
2264
self.assertEquals('foo', opt.get_help_topic())
2267
class TestOptionConverterMixin(object):
2269
def assertConverted(self, expected, opt, value):
2270
self.assertEquals(expected, opt.convert_from_unicode(None, value))
2272
def assertWarns(self, opt, value):
2275
warnings.append(args[0] % args[1:])
2276
self.overrideAttr(trace, 'warning', warning)
2277
self.assertEquals(None, opt.convert_from_unicode(None, value))
2278
self.assertLength(1, warnings)
2280
'Value "%s" is not valid for "%s"' % (value, opt.name),
2283
def assertErrors(self, opt, value):
2284
self.assertRaises(errors.ConfigOptionValueError,
2285
opt.convert_from_unicode, None, value)
2287
def assertConvertInvalid(self, opt, invalid_value):
2289
self.assertEquals(None, opt.convert_from_unicode(None, invalid_value))
2290
opt.invalid = 'warning'
2291
self.assertWarns(opt, invalid_value)
2292
opt.invalid = 'error'
2293
self.assertErrors(opt, invalid_value)
2296
class TestOptionWithBooleanConverter(tests.TestCase, TestOptionConverterMixin):
2298
def get_option(self):
2299
return config.Option('foo', help='A boolean.',
2300
from_unicode=config.bool_from_store)
2302
def test_convert_invalid(self):
2303
opt = self.get_option()
2304
# A string that is not recognized as a boolean
2305
self.assertConvertInvalid(opt, u'invalid-boolean')
2306
# A list of strings is never recognized as a boolean
2307
self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
2309
def test_convert_valid(self):
2310
opt = self.get_option()
2311
self.assertConverted(True, opt, u'True')
2312
self.assertConverted(True, opt, u'1')
2313
self.assertConverted(False, opt, u'False')
2316
class TestOptionWithIntegerConverter(tests.TestCase, TestOptionConverterMixin):
2318
def get_option(self):
2319
return config.Option('foo', help='An integer.',
2320
from_unicode=config.int_from_store)
2322
def test_convert_invalid(self):
2323
opt = self.get_option()
2324
# A string that is not recognized as an integer
2325
self.assertConvertInvalid(opt, u'forty-two')
2326
# A list of strings is never recognized as an integer
2327
self.assertConvertInvalid(opt, [u'a', u'list'])
2329
def test_convert_valid(self):
2330
opt = self.get_option()
2331
self.assertConverted(16, opt, u'16')
2334
class TestOptionWithSIUnitConverter(tests.TestCase, TestOptionConverterMixin):
2336
def get_option(self):
2337
return config.Option('foo', help='An integer in SI units.',
2338
from_unicode=config.int_SI_from_store)
2340
def test_convert_invalid(self):
2341
opt = self.get_option()
2342
self.assertConvertInvalid(opt, u'not-a-unit')
2343
self.assertConvertInvalid(opt, u'Gb') # Forgot the int
2344
self.assertConvertInvalid(opt, u'1b') # Forgot the unit
2345
self.assertConvertInvalid(opt, u'1GG')
2346
self.assertConvertInvalid(opt, u'1Mbb')
2347
self.assertConvertInvalid(opt, u'1MM')
2349
def test_convert_valid(self):
2350
opt = self.get_option()
2351
self.assertConverted(int(5e3), opt, u'5kb')
2352
self.assertConverted(int(5e6), opt, u'5M')
2353
self.assertConverted(int(5e6), opt, u'5MB')
2354
self.assertConverted(int(5e9), opt, u'5g')
2355
self.assertConverted(int(5e9), opt, u'5gB')
2356
self.assertConverted(100, opt, u'100')
2359
class TestListOption(tests.TestCase, TestOptionConverterMixin):
2361
def get_option(self):
2362
return config.ListOption('foo', help='A list.')
2364
def test_convert_invalid(self):
2365
opt = self.get_option()
2366
# We don't even try to convert a list into a list, we only expect
2368
self.assertConvertInvalid(opt, [1])
2369
# No string is invalid as all forms can be converted to a list
2371
def test_convert_valid(self):
2372
opt = self.get_option()
2373
# An empty string is an empty list
2374
self.assertConverted([], opt, '') # Using a bare str() just in case
2375
self.assertConverted([], opt, u'')
2377
self.assertConverted([u'True'], opt, u'True')
2379
self.assertConverted([u'42'], opt, u'42')
2381
self.assertConverted([u'bar'], opt, u'bar')
2384
class TestRegistryOption(tests.TestCase, TestOptionConverterMixin):
2386
def get_option(self, registry):
2387
return config.RegistryOption('foo', registry,
2388
help='A registry option.')
2390
def test_convert_invalid(self):
2391
registry = _mod_registry.Registry()
2392
opt = self.get_option(registry)
2393
self.assertConvertInvalid(opt, [1])
2394
self.assertConvertInvalid(opt, u"notregistered")
2396
def test_convert_valid(self):
2397
registry = _mod_registry.Registry()
2398
registry.register("someval", 1234)
2399
opt = self.get_option(registry)
2400
# Using a bare str() just in case
2401
self.assertConverted(1234, opt, "someval")
2402
self.assertConverted(1234, opt, u'someval')
2403
self.assertConverted(None, opt, None)
2405
def test_help(self):
2406
registry = _mod_registry.Registry()
2407
registry.register("someval", 1234, help="some option")
2408
registry.register("dunno", 1234, help="some other option")
2409
opt = self.get_option(registry)
2411
'A registry option.\n'
2413
'The following values are supported:\n'
2414
' dunno - some other option\n'
2415
' someval - some option\n',
2418
def test_get_help_text(self):
2419
registry = _mod_registry.Registry()
2420
registry.register("someval", 1234, help="some option")
2421
registry.register("dunno", 1234, help="some other option")
2422
opt = self.get_option(registry)
2424
'A registry option.\n'
2426
'The following values are supported:\n'
2427
' dunno - some other option\n'
2428
' someval - some option\n',
2429
opt.get_help_text())
2432
class TestOptionRegistry(tests.TestCase):
2435
super(TestOptionRegistry, self).setUp()
2436
# Always start with an empty registry
2437
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2438
self.registry = config.option_registry
2440
def test_register(self):
2441
opt = config.Option('foo')
2442
self.registry.register(opt)
2443
self.assertIs(opt, self.registry.get('foo'))
2445
def test_registered_help(self):
2446
opt = config.Option('foo', help='A simple option')
2447
self.registry.register(opt)
2448
self.assertEquals('A simple option', self.registry.get_help('foo'))
2450
lazy_option = config.Option('lazy_foo', help='Lazy help')
2452
def test_register_lazy(self):
2453
self.registry.register_lazy('lazy_foo', self.__module__,
2454
'TestOptionRegistry.lazy_option')
2455
self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
2457
def test_registered_lazy_help(self):
2458
self.registry.register_lazy('lazy_foo', self.__module__,
2459
'TestOptionRegistry.lazy_option')
2460
self.assertEquals('Lazy help', self.registry.get_help('lazy_foo'))
2463
class TestRegisteredOptions(tests.TestCase):
2464
"""All registered options should verify some constraints."""
2466
scenarios = [(key, {'option_name': key, 'option': option}) for key, option
2467
in config.option_registry.iteritems()]
2470
super(TestRegisteredOptions, self).setUp()
2471
self.registry = config.option_registry
2473
def test_proper_name(self):
2474
# An option should be registered under its own name, this can't be
2475
# checked at registration time for the lazy ones.
2476
self.assertEquals(self.option_name, self.option.name)
2478
def test_help_is_set(self):
2479
option_help = self.registry.get_help(self.option_name)
2480
# Come on, think about the user, he really wants to know what the
2482
self.assertIsNot(None, option_help)
2483
self.assertNotEquals('', option_help)
2486
class TestSection(tests.TestCase):
2488
# FIXME: Parametrize so that all sections produced by Stores run these
2489
# tests -- vila 2011-04-01
2491
def test_get_a_value(self):
2492
a_dict = dict(foo='bar')
2493
section = config.Section('myID', a_dict)
2494
self.assertEquals('bar', section.get('foo'))
2496
def test_get_unknown_option(self):
2498
section = config.Section(None, a_dict)
2499
self.assertEquals('out of thin air',
2500
section.get('foo', 'out of thin air'))
2502
def test_options_is_shared(self):
2504
section = config.Section(None, a_dict)
2505
self.assertIs(a_dict, section.options)
2508
class TestMutableSection(tests.TestCase):
2510
scenarios = [('mutable',
2512
lambda opts: config.MutableSection('myID', opts)},),
2516
a_dict = dict(foo='bar')
2517
section = self.get_section(a_dict)
2518
section.set('foo', 'new_value')
2519
self.assertEquals('new_value', section.get('foo'))
2520
# The change appears in the shared section
2521
self.assertEquals('new_value', a_dict.get('foo'))
2522
# We keep track of the change
2523
self.assertTrue('foo' in section.orig)
2524
self.assertEquals('bar', section.orig.get('foo'))
2526
def test_set_preserve_original_once(self):
2527
a_dict = dict(foo='bar')
2528
section = self.get_section(a_dict)
2529
section.set('foo', 'first_value')
2530
section.set('foo', 'second_value')
2531
# We keep track of the original value
2532
self.assertTrue('foo' in section.orig)
2533
self.assertEquals('bar', section.orig.get('foo'))
2535
def test_remove(self):
2536
a_dict = dict(foo='bar')
2537
section = self.get_section(a_dict)
2538
section.remove('foo')
2539
# We get None for unknown options via the default value
2540
self.assertEquals(None, section.get('foo'))
2541
# Or we just get the default value
2542
self.assertEquals('unknown', section.get('foo', 'unknown'))
2543
self.assertFalse('foo' in section.options)
2544
# We keep track of the deletion
2545
self.assertTrue('foo' in section.orig)
2546
self.assertEquals('bar', section.orig.get('foo'))
2548
def test_remove_new_option(self):
2550
section = self.get_section(a_dict)
2551
section.set('foo', 'bar')
2552
section.remove('foo')
2553
self.assertFalse('foo' in section.options)
2554
# The option didn't exist initially so it we need to keep track of it
2555
# with a special value
2556
self.assertTrue('foo' in section.orig)
2557
self.assertEquals(config._NewlyCreatedOption, section.orig['foo'])
2560
class TestCommandLineStore(tests.TestCase):
2563
super(TestCommandLineStore, self).setUp()
2564
self.store = config.CommandLineStore()
2565
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2567
def get_section(self):
2568
"""Get the unique section for the command line overrides."""
2569
sections = list(self.store.get_sections())
2570
self.assertLength(1, sections)
2571
store, section = sections[0]
2572
self.assertEquals(self.store, store)
2575
def test_no_override(self):
2576
self.store._from_cmdline([])
2577
section = self.get_section()
2578
self.assertLength(0, list(section.iter_option_names()))
2580
def test_simple_override(self):
2581
self.store._from_cmdline(['a=b'])
2582
section = self.get_section()
2583
self.assertEqual('b', section.get('a'))
2585
def test_list_override(self):
2586
opt = config.ListOption('l')
2587
config.option_registry.register(opt)
2588
self.store._from_cmdline(['l=1,2,3'])
2589
val = self.get_section().get('l')
2590
self.assertEqual('1,2,3', val)
2591
# Reminder: lists should be registered as such explicitely, otherwise
2592
# the conversion needs to be done afterwards.
2593
self.assertEqual(['1', '2', '3'],
2594
opt.convert_from_unicode(self.store, val))
2596
def test_multiple_overrides(self):
2597
self.store._from_cmdline(['a=b', 'x=y'])
2598
section = self.get_section()
2599
self.assertEquals('b', section.get('a'))
2600
self.assertEquals('y', section.get('x'))
2602
def test_wrong_syntax(self):
2603
self.assertRaises(errors.BzrCommandError,
2604
self.store._from_cmdline, ['a=b', 'c'])
2606
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
2608
scenarios = [(key, {'get_store': builder}) for key, builder
2609
in config.test_store_builder_registry.iteritems()] + [
2610
('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
2613
store = self.get_store(self)
2614
if type(store) == config.TransportIniFileStore:
2615
raise tests.TestNotApplicable(
2616
"%s is not a concrete Store implementation"
2617
" so it doesn't need an id" % (store.__class__.__name__,))
2618
self.assertIsNot(None, store.id)
2621
class TestStore(tests.TestCaseWithTransport):
2623
def assertSectionContent(self, expected, (store, section)):
2624
"""Assert that some options have the proper values in a section."""
2625
expected_name, expected_options = expected
2626
self.assertEquals(expected_name, section.id)
2629
dict([(k, section.get(k)) for k in expected_options.keys()]))
2632
class TestReadonlyStore(TestStore):
2634
scenarios = [(key, {'get_store': builder}) for key, builder
2635
in config.test_store_builder_registry.iteritems()]
2637
def test_building_delays_load(self):
2638
store = self.get_store(self)
2639
self.assertEquals(False, store.is_loaded())
2640
store._load_from_string('')
2641
self.assertEquals(True, store.is_loaded())
2643
def test_get_no_sections_for_empty(self):
2644
store = self.get_store(self)
2645
store._load_from_string('')
2646
self.assertEquals([], list(store.get_sections()))
2648
def test_get_default_section(self):
2649
store = self.get_store(self)
2650
store._load_from_string('foo=bar')
2651
sections = list(store.get_sections())
2652
self.assertLength(1, sections)
2653
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2655
def test_get_named_section(self):
2656
store = self.get_store(self)
2657
store._load_from_string('[baz]\nfoo=bar')
2658
sections = list(store.get_sections())
2659
self.assertLength(1, sections)
2660
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2662
def test_load_from_string_fails_for_non_empty_store(self):
2663
store = self.get_store(self)
2664
store._load_from_string('foo=bar')
2665
self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
2668
class TestStoreQuoting(TestStore):
2670
scenarios = [(key, {'get_store': builder}) for key, builder
2671
in config.test_store_builder_registry.iteritems()]
2674
super(TestStoreQuoting, self).setUp()
2675
self.store = self.get_store(self)
2676
# We need a loaded store but any content will do
2677
self.store._load_from_string('')
2679
def assertIdempotent(self, s):
2680
"""Assert that quoting an unquoted string is a no-op and vice-versa.
2682
What matters here is that option values, as they appear in a store, can
2683
be safely round-tripped out of the store and back.
2685
:param s: A string, quoted if required.
2687
self.assertEquals(s, self.store.quote(self.store.unquote(s)))
2688
self.assertEquals(s, self.store.unquote(self.store.quote(s)))
2690
def test_empty_string(self):
2691
if isinstance(self.store, config.IniFileStore):
2692
# configobj._quote doesn't handle empty values
2693
self.assertRaises(AssertionError,
2694
self.assertIdempotent, '')
2696
self.assertIdempotent('')
2697
# But quoted empty strings are ok
2698
self.assertIdempotent('""')
2700
def test_embedded_spaces(self):
2701
self.assertIdempotent('" a b c "')
2703
def test_embedded_commas(self):
2704
self.assertIdempotent('" a , b c "')
2706
def test_simple_comma(self):
2707
if isinstance(self.store, config.IniFileStore):
2708
# configobj requires that lists are special-cased
2709
self.assertRaises(AssertionError,
2710
self.assertIdempotent, ',')
2712
self.assertIdempotent(',')
2713
# When a single comma is required, quoting is also required
2714
self.assertIdempotent('","')
2716
def test_list(self):
2717
if isinstance(self.store, config.IniFileStore):
2718
# configobj requires that lists are special-cased
2719
self.assertRaises(AssertionError,
2720
self.assertIdempotent, 'a,b')
2722
self.assertIdempotent('a,b')
2725
class TestDictFromStore(tests.TestCase):
2727
def test_unquote_not_string(self):
2728
conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
2729
value = conf.get('a_section')
2730
# Urgh, despite 'conf' asking for the no-name section, we get the
2731
# content of another section as a dict o_O
2732
self.assertEquals({'a': '1'}, value)
2733
unquoted = conf.store.unquote(value)
2734
# Which cannot be unquoted but shouldn't crash either (the use cases
2735
# are getting the value or displaying it. In the later case, '%s' will
2737
self.assertEquals({'a': '1'}, unquoted)
2738
self.assertEquals("{u'a': u'1'}", '%s' % (unquoted,))
2741
class TestIniFileStoreContent(tests.TestCaseWithTransport):
2742
"""Simulate loading a config store with content of various encodings.
2744
All files produced by bzr are in utf8 content.
2746
Users may modify them manually and end up with a file that can't be
2747
loaded. We need to issue proper error messages in this case.
2750
invalid_utf8_char = '\xff'
2752
def test_load_utf8(self):
2753
"""Ensure we can load an utf8-encoded file."""
2754
t = self.get_transport()
2755
# From http://pad.lv/799212
2756
unicode_user = u'b\N{Euro Sign}ar'
2757
unicode_content = u'user=%s' % (unicode_user,)
2758
utf8_content = unicode_content.encode('utf8')
2759
# Store the raw content in the config file
2760
t.put_bytes('foo.conf', utf8_content)
2761
store = config.TransportIniFileStore(t, 'foo.conf')
2763
stack = config.Stack([store.get_sections], store)
2764
self.assertEquals(unicode_user, stack.get('user'))
2766
def test_load_non_ascii(self):
2767
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2768
t = self.get_transport()
2769
t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
2770
store = config.TransportIniFileStore(t, 'foo.conf')
2771
self.assertRaises(errors.ConfigContentError, store.load)
2773
def test_load_erroneous_content(self):
2774
"""Ensure we display a proper error on content that can't be parsed."""
2775
t = self.get_transport()
2776
t.put_bytes('foo.conf', '[open_section\n')
2777
store = config.TransportIniFileStore(t, 'foo.conf')
2778
self.assertRaises(errors.ParseConfigError, store.load)
2780
def test_load_permission_denied(self):
2781
"""Ensure we get warned when trying to load an inaccessible file."""
2784
warnings.append(args[0] % args[1:])
2785
self.overrideAttr(trace, 'warning', warning)
2787
t = self.get_transport()
2789
def get_bytes(relpath):
2790
raise errors.PermissionDenied(relpath, "")
2791
t.get_bytes = get_bytes
2792
store = config.TransportIniFileStore(t, 'foo.conf')
2793
self.assertRaises(errors.PermissionDenied, store.load)
2796
[u'Permission denied while trying to load configuration store %s.'
2797
% store.external_url()])
2800
class TestIniConfigContent(tests.TestCaseWithTransport):
2801
"""Simulate loading a IniBasedConfig with content of various encodings.
2803
All files produced by bzr are in utf8 content.
2805
Users may modify them manually and end up with a file that can't be
2806
loaded. We need to issue proper error messages in this case.
2809
invalid_utf8_char = '\xff'
2811
def test_load_utf8(self):
2812
"""Ensure we can load an utf8-encoded file."""
2813
# From http://pad.lv/799212
2814
unicode_user = u'b\N{Euro Sign}ar'
2815
unicode_content = u'user=%s' % (unicode_user,)
2816
utf8_content = unicode_content.encode('utf8')
2817
# Store the raw content in the config file
2818
with open('foo.conf', 'wb') as f:
2819
f.write(utf8_content)
2820
conf = config.IniBasedConfig(file_name='foo.conf')
2821
self.assertEquals(unicode_user, conf.get_user_option('user'))
2823
def test_load_badly_encoded_content(self):
2824
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2825
with open('foo.conf', 'wb') as f:
2826
f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
2827
conf = config.IniBasedConfig(file_name='foo.conf')
2828
self.assertRaises(errors.ConfigContentError, conf._get_parser)
2830
def test_load_erroneous_content(self):
2831
"""Ensure we display a proper error on content that can't be parsed."""
2832
with open('foo.conf', 'wb') as f:
2833
f.write('[open_section\n')
2834
conf = config.IniBasedConfig(file_name='foo.conf')
2835
self.assertRaises(errors.ParseConfigError, conf._get_parser)
2838
class TestMutableStore(TestStore):
2840
scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
2841
in config.test_store_builder_registry.iteritems()]
2844
super(TestMutableStore, self).setUp()
2845
self.transport = self.get_transport()
2847
def has_store(self, store):
2848
store_basename = urlutils.relative_url(self.transport.external_url(),
2849
store.external_url())
2850
return self.transport.has(store_basename)
2852
def test_save_empty_creates_no_file(self):
2853
# FIXME: There should be a better way than relying on the test
2854
# parametrization to identify branch.conf -- vila 2011-0526
2855
if self.store_id in ('branch', 'remote_branch'):
2856
raise tests.TestNotApplicable(
2857
'branch.conf is *always* created when a branch is initialized')
2858
store = self.get_store(self)
2860
self.assertEquals(False, self.has_store(store))
2862
def test_mutable_section_shared(self):
2863
store = self.get_store(self)
2864
store._load_from_string('foo=bar\n')
2865
# FIXME: There should be a better way than relying on the test
2866
# parametrization to identify branch.conf -- vila 2011-0526
2867
if self.store_id in ('branch', 'remote_branch'):
2868
# branch stores requires write locked branches
2869
self.addCleanup(store.branch.lock_write().unlock)
2870
section1 = store.get_mutable_section(None)
2871
section2 = store.get_mutable_section(None)
2872
# If we get different sections, different callers won't share the
2874
self.assertIs(section1, section2)
2876
def test_save_emptied_succeeds(self):
2877
store = self.get_store(self)
2878
store._load_from_string('foo=bar\n')
2879
# FIXME: There should be a better way than relying on the test
2880
# parametrization to identify branch.conf -- vila 2011-0526
2881
if self.store_id in ('branch', 'remote_branch'):
2882
# branch stores requires write locked branches
2883
self.addCleanup(store.branch.lock_write().unlock)
2884
section = store.get_mutable_section(None)
2885
section.remove('foo')
2887
self.assertEquals(True, self.has_store(store))
2888
modified_store = self.get_store(self)
2889
sections = list(modified_store.get_sections())
2890
self.assertLength(0, sections)
2892
def test_save_with_content_succeeds(self):
2893
# FIXME: There should be a better way than relying on the test
2894
# parametrization to identify branch.conf -- vila 2011-0526
2895
if self.store_id in ('branch', 'remote_branch'):
2896
raise tests.TestNotApplicable(
2897
'branch.conf is *always* created when a branch is initialized')
2898
store = self.get_store(self)
2899
store._load_from_string('foo=bar\n')
2900
self.assertEquals(False, self.has_store(store))
2902
self.assertEquals(True, self.has_store(store))
2903
modified_store = self.get_store(self)
2904
sections = list(modified_store.get_sections())
2905
self.assertLength(1, sections)
2906
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2908
def test_set_option_in_empty_store(self):
2909
store = self.get_store(self)
2910
# FIXME: There should be a better way than relying on the test
2911
# parametrization to identify branch.conf -- vila 2011-0526
2912
if self.store_id in ('branch', 'remote_branch'):
2913
# branch stores requires write locked branches
2914
self.addCleanup(store.branch.lock_write().unlock)
2915
section = store.get_mutable_section(None)
2916
section.set('foo', 'bar')
2918
modified_store = self.get_store(self)
2919
sections = list(modified_store.get_sections())
2920
self.assertLength(1, sections)
2921
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2923
def test_set_option_in_default_section(self):
2924
store = self.get_store(self)
2925
store._load_from_string('')
2926
# FIXME: There should be a better way than relying on the test
2927
# parametrization to identify branch.conf -- vila 2011-0526
2928
if self.store_id in ('branch', 'remote_branch'):
2929
# branch stores requires write locked branches
2930
self.addCleanup(store.branch.lock_write().unlock)
2931
section = store.get_mutable_section(None)
2932
section.set('foo', 'bar')
2934
modified_store = self.get_store(self)
2935
sections = list(modified_store.get_sections())
2936
self.assertLength(1, sections)
2937
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2939
def test_set_option_in_named_section(self):
2940
store = self.get_store(self)
2941
store._load_from_string('')
2942
# FIXME: There should be a better way than relying on the test
2943
# parametrization to identify branch.conf -- vila 2011-0526
2944
if self.store_id in ('branch', 'remote_branch'):
2945
# branch stores requires write locked branches
2946
self.addCleanup(store.branch.lock_write().unlock)
2947
section = store.get_mutable_section('baz')
2948
section.set('foo', 'bar')
2950
modified_store = self.get_store(self)
2951
sections = list(modified_store.get_sections())
2952
self.assertLength(1, sections)
2953
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2955
def test_load_hook(self):
2956
# First, we need to ensure that the store exists
2957
store = self.get_store(self)
2958
# FIXME: There should be a better way than relying on the test
2959
# parametrization to identify branch.conf -- vila 2011-0526
2960
if self.store_id in ('branch', 'remote_branch'):
2961
# branch stores requires write locked branches
2962
self.addCleanup(store.branch.lock_write().unlock)
2963
section = store.get_mutable_section('baz')
2964
section.set('foo', 'bar')
2966
# Now we can try to load it
2967
store = self.get_store(self)
2971
config.ConfigHooks.install_named_hook('load', hook, None)
2972
self.assertLength(0, calls)
2974
self.assertLength(1, calls)
2975
self.assertEquals((store,), calls[0])
2977
def test_save_hook(self):
2981
config.ConfigHooks.install_named_hook('save', hook, None)
2982
self.assertLength(0, calls)
2983
store = self.get_store(self)
2984
# FIXME: There should be a better way than relying on the test
2985
# parametrization to identify branch.conf -- vila 2011-0526
2986
if self.store_id in ('branch', 'remote_branch'):
2987
# branch stores requires write locked branches
2988
self.addCleanup(store.branch.lock_write().unlock)
2989
section = store.get_mutable_section('baz')
2990
section.set('foo', 'bar')
2992
self.assertLength(1, calls)
2993
self.assertEquals((store,), calls[0])
2995
def test_set_mark_dirty(self):
2996
stack = config.MemoryStack('')
2997
self.assertLength(0, stack.store.dirty_sections)
2998
stack.set('foo', 'baz')
2999
self.assertLength(1, stack.store.dirty_sections)
3000
self.assertTrue(stack.store._need_saving())
3002
def test_remove_mark_dirty(self):
3003
stack = config.MemoryStack('foo=bar')
3004
self.assertLength(0, stack.store.dirty_sections)
3006
self.assertLength(1, stack.store.dirty_sections)
3007
self.assertTrue(stack.store._need_saving())
3010
class TestStoreSaveChanges(tests.TestCaseWithTransport):
3011
"""Tests that config changes are kept in memory and saved on-demand."""
3014
super(TestStoreSaveChanges, self).setUp()
3015
self.transport = self.get_transport()
3016
# Most of the tests involve two stores pointing to the same persistent
3017
# storage to observe the effects of concurrent changes
3018
self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
3019
self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
3022
self.warnings.append(args[0] % args[1:])
3023
self.overrideAttr(trace, 'warning', warning)
3025
def has_store(self, store):
3026
store_basename = urlutils.relative_url(self.transport.external_url(),
3027
store.external_url())
3028
return self.transport.has(store_basename)
3030
def get_stack(self, store):
3031
# Any stack will do as long as it uses the right store, just a single
3032
# no-name section is enough
3033
return config.Stack([store.get_sections], store)
3035
def test_no_changes_no_save(self):
3036
s = self.get_stack(self.st1)
3037
s.store.save_changes()
3038
self.assertEquals(False, self.has_store(self.st1))
3040
def test_unrelated_concurrent_update(self):
3041
s1 = self.get_stack(self.st1)
3042
s2 = self.get_stack(self.st2)
3043
s1.set('foo', 'bar')
3044
s2.set('baz', 'quux')
3046
# Changes don't propagate magically
3047
self.assertEquals(None, s1.get('baz'))
3048
s2.store.save_changes()
3049
self.assertEquals('quux', s2.get('baz'))
3050
# Changes are acquired when saving
3051
self.assertEquals('bar', s2.get('foo'))
3052
# Since there is no overlap, no warnings are emitted
3053
self.assertLength(0, self.warnings)
3055
def test_concurrent_update_modified(self):
3056
s1 = self.get_stack(self.st1)
3057
s2 = self.get_stack(self.st2)
3058
s1.set('foo', 'bar')
3059
s2.set('foo', 'baz')
3062
s2.store.save_changes()
3063
self.assertEquals('baz', s2.get('foo'))
3064
# But the user get a warning
3065
self.assertLength(1, self.warnings)
3066
warning = self.warnings[0]
3067
self.assertStartsWith(warning, 'Option foo in section None')
3068
self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
3069
' The baz value will be saved.')
3071
def test_concurrent_deletion(self):
3072
self.st1._load_from_string('foo=bar')
3074
s1 = self.get_stack(self.st1)
3075
s2 = self.get_stack(self.st2)
3078
s1.store.save_changes()
3080
self.assertLength(0, self.warnings)
3081
s2.store.save_changes()
3083
self.assertLength(1, self.warnings)
3084
warning = self.warnings[0]
3085
self.assertStartsWith(warning, 'Option foo in section None')
3086
self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
3087
' The <DELETED> value will be saved.')
3090
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
3092
def get_store(self):
3093
return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3095
def test_get_quoted_string(self):
3096
store = self.get_store()
3097
store._load_from_string('foo= " abc "')
3098
stack = config.Stack([store.get_sections])
3099
self.assertEquals(' abc ', stack.get('foo'))
3101
def test_set_quoted_string(self):
3102
store = self.get_store()
3103
stack = config.Stack([store.get_sections], store)
3104
stack.set('foo', ' a b c ')
3106
self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
3109
class TestTransportIniFileStore(TestStore):
3111
def test_loading_unknown_file_fails(self):
3112
store = config.TransportIniFileStore(self.get_transport(),
3114
self.assertRaises(errors.NoSuchFile, store.load)
3116
def test_invalid_content(self):
3117
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3118
self.assertEquals(False, store.is_loaded())
3119
exc = self.assertRaises(
3120
errors.ParseConfigError, store._load_from_string,
3121
'this is invalid !')
3122
self.assertEndsWith(exc.filename, 'foo.conf')
3123
# And the load failed
3124
self.assertEquals(False, store.is_loaded())
3126
def test_get_embedded_sections(self):
3127
# A more complicated example (which also shows that section names and
3128
# option names share the same name space...)
3129
# FIXME: This should be fixed by forbidding dicts as values ?
3130
# -- vila 2011-04-05
3131
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3132
store._load_from_string('''
3136
foo_in_DEFAULT=foo_DEFAULT
3144
sections = list(store.get_sections())
3145
self.assertLength(4, sections)
3146
# The default section has no name.
3147
# List values are provided as strings and need to be explicitly
3148
# converted by specifying from_unicode=list_from_store at option
3150
self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
3152
self.assertSectionContent(
3153
('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
3154
self.assertSectionContent(
3155
('bar', {'foo_in_bar': 'barbar'}), sections[2])
3156
# sub sections are provided as embedded dicts.
3157
self.assertSectionContent(
3158
('baz', {'foo_in_baz': 'barbaz', 'qux': {'foo_in_qux': 'quux'}}),
3162
class TestLockableIniFileStore(TestStore):
3164
def test_create_store_in_created_dir(self):
3165
self.assertPathDoesNotExist('dir')
3166
t = self.get_transport('dir/subdir')
3167
store = config.LockableIniFileStore(t, 'foo.conf')
3168
store.get_mutable_section(None).set('foo', 'bar')
3170
self.assertPathExists('dir/subdir')
3173
class TestConcurrentStoreUpdates(TestStore):
3174
"""Test that Stores properly handle conccurent updates.
3176
New Store implementation may fail some of these tests but until such
3177
implementations exist it's hard to properly filter them from the scenarios
3178
applied here. If you encounter such a case, contact the bzr devs.
3181
scenarios = [(key, {'get_stack': builder}) for key, builder
3182
in config.test_stack_builder_registry.iteritems()]
3185
super(TestConcurrentStoreUpdates, self).setUp()
3186
self.stack = self.get_stack(self)
3187
if not isinstance(self.stack, config._CompatibleStack):
3188
raise tests.TestNotApplicable(
3189
'%s is not meant to be compatible with the old config design'
3191
self.stack.set('one', '1')
3192
self.stack.set('two', '2')
3194
self.stack.store.save()
3196
def test_simple_read_access(self):
3197
self.assertEquals('1', self.stack.get('one'))
3199
def test_simple_write_access(self):
3200
self.stack.set('one', 'one')
3201
self.assertEquals('one', self.stack.get('one'))
3203
def test_listen_to_the_last_speaker(self):
3205
c2 = self.get_stack(self)
3206
c1.set('one', 'ONE')
3207
c2.set('two', 'TWO')
3208
self.assertEquals('ONE', c1.get('one'))
3209
self.assertEquals('TWO', c2.get('two'))
3210
# The second update respect the first one
3211
self.assertEquals('ONE', c2.get('one'))
3213
def test_last_speaker_wins(self):
3214
# If the same config is not shared, the same variable modified twice
3215
# can only see a single result.
3217
c2 = self.get_stack(self)
3220
self.assertEquals('c2', c2.get('one'))
3221
# The first modification is still available until another refresh
3223
self.assertEquals('c1', c1.get('one'))
3224
c1.set('two', 'done')
3225
self.assertEquals('c2', c1.get('one'))
3227
def test_writes_are_serialized(self):
3229
c2 = self.get_stack(self)
3231
# We spawn a thread that will pause *during* the config saving.
3232
before_writing = threading.Event()
3233
after_writing = threading.Event()
3234
writing_done = threading.Event()
3235
c1_save_without_locking_orig = c1.store.save_without_locking
3236
def c1_save_without_locking():
3237
before_writing.set()
3238
c1_save_without_locking_orig()
3239
# The lock is held. We wait for the main thread to decide when to
3241
after_writing.wait()
3242
c1.store.save_without_locking = c1_save_without_locking
3246
t1 = threading.Thread(target=c1_set)
3247
# Collect the thread after the test
3248
self.addCleanup(t1.join)
3249
# Be ready to unblock the thread if the test goes wrong
3250
self.addCleanup(after_writing.set)
3252
before_writing.wait()
3253
self.assertRaises(errors.LockContention,
3254
c2.set, 'one', 'c2')
3255
self.assertEquals('c1', c1.get('one'))
3256
# Let the lock be released
3260
self.assertEquals('c2', c2.get('one'))
3262
def test_read_while_writing(self):
3264
# We spawn a thread that will pause *during* the write
3265
ready_to_write = threading.Event()
3266
do_writing = threading.Event()
3267
writing_done = threading.Event()
3268
# We override the _save implementation so we know the store is locked
3269
c1_save_without_locking_orig = c1.store.save_without_locking
3270
def c1_save_without_locking():
3271
ready_to_write.set()
3272
# The lock is held. We wait for the main thread to decide when to
3275
c1_save_without_locking_orig()
3277
c1.store.save_without_locking = c1_save_without_locking
3280
t1 = threading.Thread(target=c1_set)
3281
# Collect the thread after the test
3282
self.addCleanup(t1.join)
3283
# Be ready to unblock the thread if the test goes wrong
3284
self.addCleanup(do_writing.set)
3286
# Ensure the thread is ready to write
3287
ready_to_write.wait()
3288
self.assertEquals('c1', c1.get('one'))
3289
# If we read during the write, we get the old value
3290
c2 = self.get_stack(self)
3291
self.assertEquals('1', c2.get('one'))
3292
# Let the writing occur and ensure it occurred
3295
# Now we get the updated value
3296
c3 = self.get_stack(self)
3297
self.assertEquals('c1', c3.get('one'))
3299
# FIXME: It may be worth looking into removing the lock dir when it's not
3300
# needed anymore and look at possible fallouts for concurrent lockers. This
3301
# will matter if/when we use config files outside of bazaar directories
3302
# (.bazaar or .bzr) -- vila 20110-04-111
3305
class TestSectionMatcher(TestStore):
3307
scenarios = [('location', {'matcher': config.LocationMatcher}),
3308
('id', {'matcher': config.NameMatcher}),]
3311
super(TestSectionMatcher, self).setUp()
3312
# Any simple store is good enough
3313
self.get_store = config.test_store_builder_registry.get('configobj')
3315
def test_no_matches_for_empty_stores(self):
3316
store = self.get_store(self)
3317
store._load_from_string('')
3318
matcher = self.matcher(store, '/bar')
3319
self.assertEquals([], list(matcher.get_sections()))
3321
def test_build_doesnt_load_store(self):
3322
store = self.get_store(self)
3323
matcher = self.matcher(store, '/bar')
3324
self.assertFalse(store.is_loaded())
3327
class TestLocationSection(tests.TestCase):
3329
def get_section(self, options, extra_path):
3330
section = config.Section('foo', options)
3331
return config.LocationSection(section, extra_path)
3333
def test_simple_option(self):
3334
section = self.get_section({'foo': 'bar'}, '')
3335
self.assertEquals('bar', section.get('foo'))
3337
def test_option_with_extra_path(self):
3338
section = self.get_section({'foo': 'bar', 'foo:policy': 'appendpath'},
3340
self.assertEquals('bar/baz', section.get('foo'))
3342
def test_invalid_policy(self):
3343
section = self.get_section({'foo': 'bar', 'foo:policy': 'die'},
3345
# invalid policies are ignored
3346
self.assertEquals('bar', section.get('foo'))
3349
class TestLocationMatcher(TestStore):
3352
super(TestLocationMatcher, self).setUp()
3353
# Any simple store is good enough
3354
self.get_store = config.test_store_builder_registry.get('configobj')
3356
def test_unrelated_section_excluded(self):
3357
store = self.get_store(self)
3358
store._load_from_string('''
3366
section=/foo/bar/baz
3370
self.assertEquals(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
3372
[section.id for _, section in store.get_sections()])
3373
matcher = config.LocationMatcher(store, '/foo/bar/quux')
3374
sections = [section for _, section in matcher.get_sections()]
3375
self.assertEquals(['/foo/bar', '/foo'],
3376
[section.id for section in sections])
3377
self.assertEquals(['quux', 'bar/quux'],
3378
[section.extra_path for section in sections])
3380
def test_more_specific_sections_first(self):
3381
store = self.get_store(self)
3382
store._load_from_string('''
3388
self.assertEquals(['/foo', '/foo/bar'],
3389
[section.id for _, section in store.get_sections()])
3390
matcher = config.LocationMatcher(store, '/foo/bar/baz')
3391
sections = [section for _, section in matcher.get_sections()]
3392
self.assertEquals(['/foo/bar', '/foo'],
3393
[section.id for section in sections])
3394
self.assertEquals(['baz', 'bar/baz'],
3395
[section.extra_path for section in sections])
3397
def test_appendpath_in_no_name_section(self):
3398
# It's a bit weird to allow appendpath in a no-name section, but
3399
# someone may found a use for it
3400
store = self.get_store(self)
3401
store._load_from_string('''
3403
foo:policy = appendpath
3405
matcher = config.LocationMatcher(store, 'dir/subdir')
3406
sections = list(matcher.get_sections())
3407
self.assertLength(1, sections)
3408
self.assertEquals('bar/dir/subdir', sections[0][1].get('foo'))
3410
def test_file_urls_are_normalized(self):
3411
store = self.get_store(self)
3412
if sys.platform == 'win32':
3413
expected_url = 'file:///C:/dir/subdir'
3414
expected_location = 'C:/dir/subdir'
3416
expected_url = 'file:///dir/subdir'
3417
expected_location = '/dir/subdir'
3418
matcher = config.LocationMatcher(store, expected_url)
3419
self.assertEquals(expected_location, matcher.location)
3421
def test_branch_name_colo(self):
3422
store = self.get_store(self)
3423
store._load_from_string(dedent("""\
3425
push_location=my{branchname}
3427
matcher = config.LocationMatcher(store, 'file:///,branch=example%3c')
3428
self.assertEqual('example<', matcher.branch_name)
3429
((_, section),) = matcher.get_sections()
3430
self.assertEqual('example<', section.locals['branchname'])
3432
def test_branch_name_basename(self):
3433
store = self.get_store(self)
3434
store._load_from_string(dedent("""\
3436
push_location=my{branchname}
3438
matcher = config.LocationMatcher(store, 'file:///parent/example%3c')
3439
self.assertEqual('example<', matcher.branch_name)
3440
((_, section),) = matcher.get_sections()
3441
self.assertEqual('example<', section.locals['branchname'])
3444
class TestStartingPathMatcher(TestStore):
3447
super(TestStartingPathMatcher, self).setUp()
3448
# Any simple store is good enough
3449
self.store = config.IniFileStore()
3451
def assertSectionIDs(self, expected, location, content):
3452
self.store._load_from_string(content)
3453
matcher = config.StartingPathMatcher(self.store, location)
3454
sections = list(matcher.get_sections())
3455
self.assertLength(len(expected), sections)
3456
self.assertEqual(expected, [section.id for _, section in sections])
3459
def test_empty(self):
3460
self.assertSectionIDs([], self.get_url(), '')
3462
def test_url_vs_local_paths(self):
3463
# The matcher location is an url and the section names are local paths
3464
sections = self.assertSectionIDs(['/foo/bar', '/foo'],
3465
'file:///foo/bar/baz', '''\
3470
def test_local_path_vs_url(self):
3471
# The matcher location is a local path and the section names are urls
3472
sections = self.assertSectionIDs(['file:///foo/bar', 'file:///foo'],
3473
'/foo/bar/baz', '''\
3479
def test_no_name_section_included_when_present(self):
3480
# Note that other tests will cover the case where the no-name section
3481
# is empty and as such, not included.
3482
sections = self.assertSectionIDs(['/foo/bar', '/foo', None],
3483
'/foo/bar/baz', '''\
3484
option = defined so the no-name section exists
3488
self.assertEquals(['baz', 'bar/baz', '/foo/bar/baz'],
3489
[s.locals['relpath'] for _, s in sections])
3491
def test_order_reversed(self):
3492
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3497
def test_unrelated_section_excluded(self):
3498
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3504
def test_glob_included(self):
3505
sections = self.assertSectionIDs(['/foo/*/baz', '/foo/b*', '/foo'],
3506
'/foo/bar/baz', '''\
3512
# Note that 'baz' as a relpath for /foo/b* is not fully correct, but
3513
# nothing really is... as far using {relpath} to append it to something
3514
# else, this seems good enough though.
3515
self.assertEquals(['', 'baz', 'bar/baz'],
3516
[s.locals['relpath'] for _, s in sections])
3518
def test_respect_order(self):
3519
self.assertSectionIDs(['/foo', '/foo/b*', '/foo/*/baz'],
3520
'/foo/bar/baz', '''\
3528
class TestNameMatcher(TestStore):
3531
super(TestNameMatcher, self).setUp()
3532
self.matcher = config.NameMatcher
3533
# Any simple store is good enough
3534
self.get_store = config.test_store_builder_registry.get('configobj')
3536
def get_matching_sections(self, name):
3537
store = self.get_store(self)
3538
store._load_from_string('''
3546
matcher = self.matcher(store, name)
3547
return list(matcher.get_sections())
3549
def test_matching(self):
3550
sections = self.get_matching_sections('foo')
3551
self.assertLength(1, sections)
3552
self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
3554
def test_not_matching(self):
3555
sections = self.get_matching_sections('baz')
3556
self.assertLength(0, sections)
3559
class TestBaseStackGet(tests.TestCase):
3562
super(TestBaseStackGet, self).setUp()
3563
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3565
def test_get_first_definition(self):
3566
store1 = config.IniFileStore()
3567
store1._load_from_string('foo=bar')
3568
store2 = config.IniFileStore()
3569
store2._load_from_string('foo=baz')
3570
conf = config.Stack([store1.get_sections, store2.get_sections])
3571
self.assertEquals('bar', conf.get('foo'))
3573
def test_get_with_registered_default_value(self):
3574
config.option_registry.register(config.Option('foo', default='bar'))
3575
conf_stack = config.Stack([])
3576
self.assertEquals('bar', conf_stack.get('foo'))
3578
def test_get_without_registered_default_value(self):
3579
config.option_registry.register(config.Option('foo'))
3580
conf_stack = config.Stack([])
3581
self.assertEquals(None, conf_stack.get('foo'))
3583
def test_get_without_default_value_for_not_registered(self):
3584
conf_stack = config.Stack([])
3585
self.assertEquals(None, conf_stack.get('foo'))
3587
def test_get_for_empty_section_callable(self):
3588
conf_stack = config.Stack([lambda : []])
3589
self.assertEquals(None, conf_stack.get('foo'))
3591
def test_get_for_broken_callable(self):
3592
# Trying to use and invalid callable raises an exception on first use
3593
conf_stack = config.Stack([object])
3594
self.assertRaises(TypeError, conf_stack.get, 'foo')
3597
class TestStackWithSimpleStore(tests.TestCase):
3600
super(TestStackWithSimpleStore, self).setUp()
3601
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3602
self.registry = config.option_registry
3604
def get_conf(self, content=None):
3605
return config.MemoryStack(content)
3607
def test_override_value_from_env(self):
3608
self.overrideEnv('FOO', None)
3609
self.registry.register(
3610
config.Option('foo', default='bar', override_from_env=['FOO']))
3611
self.overrideEnv('FOO', 'quux')
3612
# Env variable provides a default taking over the option one
3613
conf = self.get_conf('foo=store')
3614
self.assertEquals('quux', conf.get('foo'))
3616
def test_first_override_value_from_env_wins(self):
3617
self.overrideEnv('NO_VALUE', None)
3618
self.overrideEnv('FOO', None)
3619
self.overrideEnv('BAZ', None)
3620
self.registry.register(
3621
config.Option('foo', default='bar',
3622
override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
3623
self.overrideEnv('FOO', 'foo')
3624
self.overrideEnv('BAZ', 'baz')
3625
# The first env var set wins
3626
conf = self.get_conf('foo=store')
3627
self.assertEquals('foo', conf.get('foo'))
3630
class TestMemoryStack(tests.TestCase):
3633
conf = config.MemoryStack('foo=bar')
3634
self.assertEquals('bar', conf.get('foo'))
3637
conf = config.MemoryStack('foo=bar')
3638
conf.set('foo', 'baz')
3639
self.assertEquals('baz', conf.get('foo'))
3641
def test_no_content(self):
3642
conf = config.MemoryStack()
3643
# No content means no loading
3644
self.assertFalse(conf.store.is_loaded())
3645
self.assertRaises(NotImplementedError, conf.get, 'foo')
3646
# But a content can still be provided
3647
conf.store._load_from_string('foo=bar')
3648
self.assertEquals('bar', conf.get('foo'))
3651
class TestStackIterSections(tests.TestCase):
3653
def test_empty_stack(self):
3654
conf = config.Stack([])
3655
sections = list(conf.iter_sections())
3656
self.assertLength(0, sections)
3658
def test_empty_store(self):
3659
store = config.IniFileStore()
3660
store._load_from_string('')
3661
conf = config.Stack([store.get_sections])
3662
sections = list(conf.iter_sections())
3663
self.assertLength(0, sections)
3665
def test_simple_store(self):
3666
store = config.IniFileStore()
3667
store._load_from_string('foo=bar')
3668
conf = config.Stack([store.get_sections])
3669
tuples = list(conf.iter_sections())
3670
self.assertLength(1, tuples)
3671
(found_store, found_section) = tuples[0]
3672
self.assertIs(store, found_store)
3674
def test_two_stores(self):
3675
store1 = config.IniFileStore()
3676
store1._load_from_string('foo=bar')
3677
store2 = config.IniFileStore()
3678
store2._load_from_string('bar=qux')
3679
conf = config.Stack([store1.get_sections, store2.get_sections])
3680
tuples = list(conf.iter_sections())
3681
self.assertLength(2, tuples)
3682
self.assertIs(store1, tuples[0][0])
3683
self.assertIs(store2, tuples[1][0])
3686
class TestStackWithTransport(tests.TestCaseWithTransport):
3688
scenarios = [(key, {'get_stack': builder}) for key, builder
3689
in config.test_stack_builder_registry.iteritems()]
3692
class TestConcreteStacks(TestStackWithTransport):
3694
def test_build_stack(self):
3695
# Just a smoke test to help debug builders
3696
stack = self.get_stack(self)
3699
class TestStackGet(TestStackWithTransport):
3702
super(TestStackGet, self).setUp()
3703
self.conf = self.get_stack(self)
3705
def test_get_for_empty_stack(self):
3706
self.assertEquals(None, self.conf.get('foo'))
3708
def test_get_hook(self):
3709
self.conf.set('foo', 'bar')
3713
config.ConfigHooks.install_named_hook('get', hook, None)
3714
self.assertLength(0, calls)
3715
value = self.conf.get('foo')
3716
self.assertEquals('bar', value)
3717
self.assertLength(1, calls)
3718
self.assertEquals((self.conf, 'foo', 'bar'), calls[0])
3721
class TestStackGetWithConverter(tests.TestCase):
3724
super(TestStackGetWithConverter, self).setUp()
3725
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3726
self.registry = config.option_registry
3728
def get_conf(self, content=None):
3729
return config.MemoryStack(content)
3731
def register_bool_option(self, name, default=None, default_from_env=None):
3732
b = config.Option(name, help='A boolean.',
3733
default=default, default_from_env=default_from_env,
3734
from_unicode=config.bool_from_store)
3735
self.registry.register(b)
3737
def test_get_default_bool_None(self):
3738
self.register_bool_option('foo')
3739
conf = self.get_conf('')
3740
self.assertEquals(None, conf.get('foo'))
3742
def test_get_default_bool_True(self):
3743
self.register_bool_option('foo', u'True')
3744
conf = self.get_conf('')
3745
self.assertEquals(True, conf.get('foo'))
3747
def test_get_default_bool_False(self):
3748
self.register_bool_option('foo', False)
3749
conf = self.get_conf('')
3750
self.assertEquals(False, conf.get('foo'))
3752
def test_get_default_bool_False_as_string(self):
3753
self.register_bool_option('foo', u'False')
3754
conf = self.get_conf('')
3755
self.assertEquals(False, conf.get('foo'))
3757
def test_get_default_bool_from_env_converted(self):
3758
self.register_bool_option('foo', u'True', default_from_env=['FOO'])
3759
self.overrideEnv('FOO', 'False')
3760
conf = self.get_conf('')
3761
self.assertEquals(False, conf.get('foo'))
3763
def test_get_default_bool_when_conversion_fails(self):
3764
self.register_bool_option('foo', default='True')
3765
conf = self.get_conf('foo=invalid boolean')
3766
self.assertEquals(True, conf.get('foo'))
3768
def register_integer_option(self, name,
3769
default=None, default_from_env=None):
3770
i = config.Option(name, help='An integer.',
3771
default=default, default_from_env=default_from_env,
3772
from_unicode=config.int_from_store)
3773
self.registry.register(i)
3775
def test_get_default_integer_None(self):
3776
self.register_integer_option('foo')
3777
conf = self.get_conf('')
3778
self.assertEquals(None, conf.get('foo'))
3780
def test_get_default_integer(self):
3781
self.register_integer_option('foo', 42)
3782
conf = self.get_conf('')
3783
self.assertEquals(42, conf.get('foo'))
3785
def test_get_default_integer_as_string(self):
3786
self.register_integer_option('foo', u'42')
3787
conf = self.get_conf('')
3788
self.assertEquals(42, conf.get('foo'))
3790
def test_get_default_integer_from_env(self):
3791
self.register_integer_option('foo', default_from_env=['FOO'])
3792
self.overrideEnv('FOO', '18')
3793
conf = self.get_conf('')
3794
self.assertEquals(18, conf.get('foo'))
3796
def test_get_default_integer_when_conversion_fails(self):
3797
self.register_integer_option('foo', default='12')
3798
conf = self.get_conf('foo=invalid integer')
3799
self.assertEquals(12, conf.get('foo'))
3801
def register_list_option(self, name, default=None, default_from_env=None):
3802
l = config.ListOption(name, help='A list.', default=default,
3803
default_from_env=default_from_env)
3804
self.registry.register(l)
3806
def test_get_default_list_None(self):
3807
self.register_list_option('foo')
3808
conf = self.get_conf('')
3809
self.assertEquals(None, conf.get('foo'))
3811
def test_get_default_list_empty(self):
3812
self.register_list_option('foo', '')
3813
conf = self.get_conf('')
3814
self.assertEquals([], conf.get('foo'))
3816
def test_get_default_list_from_env(self):
3817
self.register_list_option('foo', default_from_env=['FOO'])
3818
self.overrideEnv('FOO', '')
3819
conf = self.get_conf('')
3820
self.assertEquals([], conf.get('foo'))
3822
def test_get_with_list_converter_no_item(self):
3823
self.register_list_option('foo', None)
3824
conf = self.get_conf('foo=,')
3825
self.assertEquals([], conf.get('foo'))
3827
def test_get_with_list_converter_many_items(self):
3828
self.register_list_option('foo', None)
3829
conf = self.get_conf('foo=m,o,r,e')
3830
self.assertEquals(['m', 'o', 'r', 'e'], conf.get('foo'))
3832
def test_get_with_list_converter_embedded_spaces_many_items(self):
3833
self.register_list_option('foo', None)
3834
conf = self.get_conf('foo=" bar", "baz "')
3835
self.assertEquals([' bar', 'baz '], conf.get('foo'))
3837
def test_get_with_list_converter_stripped_spaces_many_items(self):
3838
self.register_list_option('foo', None)
3839
conf = self.get_conf('foo= bar , baz ')
3840
self.assertEquals(['bar', 'baz'], conf.get('foo'))
3843
class TestIterOptionRefs(tests.TestCase):
3844
"""iter_option_refs is a bit unusual, document some cases."""
3846
def assertRefs(self, expected, string):
3847
self.assertEquals(expected, list(config.iter_option_refs(string)))
3849
def test_empty(self):
3850
self.assertRefs([(False, '')], '')
3852
def test_no_refs(self):
3853
self.assertRefs([(False, 'foo bar')], 'foo bar')
3855
def test_single_ref(self):
3856
self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
3858
def test_broken_ref(self):
3859
self.assertRefs([(False, '{foo')], '{foo')
3861
def test_embedded_ref(self):
3862
self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
3865
def test_two_refs(self):
3866
self.assertRefs([(False, ''), (True, '{foo}'),
3867
(False, ''), (True, '{bar}'),
3871
def test_newline_in_refs_are_not_matched(self):
3872
self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
3875
class TestStackExpandOptions(tests.TestCaseWithTransport):
3878
super(TestStackExpandOptions, self).setUp()
3879
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3880
self.registry = config.option_registry
3881
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3882
self.conf = config.Stack([store.get_sections], store)
3884
def assertExpansion(self, expected, string, env=None):
3885
self.assertEquals(expected, self.conf.expand_options(string, env))
3887
def test_no_expansion(self):
3888
self.assertExpansion('foo', 'foo')
3890
def test_expand_default_value(self):
3891
self.conf.store._load_from_string('bar=baz')
3892
self.registry.register(config.Option('foo', default=u'{bar}'))
3893
self.assertEquals('baz', self.conf.get('foo', expand=True))
3895
def test_expand_default_from_env(self):
3896
self.conf.store._load_from_string('bar=baz')
3897
self.registry.register(config.Option('foo', default_from_env=['FOO']))
3898
self.overrideEnv('FOO', '{bar}')
3899
self.assertEquals('baz', self.conf.get('foo', expand=True))
3901
def test_expand_default_on_failed_conversion(self):
3902
self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
3903
self.registry.register(
3904
config.Option('foo', default=u'{bar}',
3905
from_unicode=config.int_from_store))
3906
self.assertEquals(42, self.conf.get('foo', expand=True))
3908
def test_env_adding_options(self):
3909
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3911
def test_env_overriding_options(self):
3912
self.conf.store._load_from_string('foo=baz')
3913
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3915
def test_simple_ref(self):
3916
self.conf.store._load_from_string('foo=xxx')
3917
self.assertExpansion('xxx', '{foo}')
3919
def test_unknown_ref(self):
3920
self.assertRaises(errors.ExpandingUnknownOption,
3921
self.conf.expand_options, '{foo}')
3923
def test_indirect_ref(self):
3924
self.conf.store._load_from_string('''
3928
self.assertExpansion('xxx', '{bar}')
3930
def test_embedded_ref(self):
3931
self.conf.store._load_from_string('''
3935
self.assertExpansion('xxx', '{{bar}}')
3937
def test_simple_loop(self):
3938
self.conf.store._load_from_string('foo={foo}')
3939
self.assertRaises(errors.OptionExpansionLoop,
3940
self.conf.expand_options, '{foo}')
3942
def test_indirect_loop(self):
3943
self.conf.store._load_from_string('''
3947
e = self.assertRaises(errors.OptionExpansionLoop,
3948
self.conf.expand_options, '{foo}')
3949
self.assertEquals('foo->bar->baz', e.refs)
3950
self.assertEquals('{foo}', e.string)
3952
def test_list(self):
3953
self.conf.store._load_from_string('''
3957
list={foo},{bar},{baz}
3959
self.registry.register(
3960
config.ListOption('list'))
3961
self.assertEquals(['start', 'middle', 'end'],
3962
self.conf.get('list', expand=True))
3964
def test_cascading_list(self):
3965
self.conf.store._load_from_string('''
3971
self.registry.register(config.ListOption('list'))
3972
# Register an intermediate option as a list to ensure no conversion
3973
# happen while expanding. Conversion should only occur for the original
3974
# option ('list' here).
3975
self.registry.register(config.ListOption('baz'))
3976
self.assertEquals(['start', 'middle', 'end'],
3977
self.conf.get('list', expand=True))
3979
def test_pathologically_hidden_list(self):
3980
self.conf.store._load_from_string('''
3986
hidden={start}{middle}{end}
3988
# What matters is what the registration says, the conversion happens
3989
# only after all expansions have been performed
3990
self.registry.register(config.ListOption('hidden'))
3991
self.assertEquals(['bin', 'go'],
3992
self.conf.get('hidden', expand=True))
3995
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
3998
super(TestStackCrossSectionsExpand, self).setUp()
4000
def get_config(self, location, string):
4003
# Since we don't save the config we won't strictly require to inherit
4004
# from TestCaseInTempDir, but an error occurs so quickly...
4005
c = config.LocationStack(location)
4006
c.store._load_from_string(string)
4009
def test_dont_cross_unrelated_section(self):
4010
c = self.get_config('/another/branch/path','''
4015
[/another/branch/path]
4018
self.assertRaises(errors.ExpandingUnknownOption,
4019
c.get, 'bar', expand=True)
4021
def test_cross_related_sections(self):
4022
c = self.get_config('/project/branch/path','''
4026
[/project/branch/path]
4029
self.assertEquals('quux', c.get('bar', expand=True))
4032
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
4034
def test_cross_global_locations(self):
4035
l_store = config.LocationStore()
4036
l_store._load_from_string('''
4042
g_store = config.GlobalStore()
4043
g_store._load_from_string('''
4049
stack = config.LocationStack('/branch')
4050
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4051
self.assertEquals('loc-foo', stack.get('gfoo', expand=True))
4054
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
4056
def test_expand_locals_empty(self):
4057
l_store = config.LocationStore()
4058
l_store._load_from_string('''
4059
[/home/user/project]
4064
stack = config.LocationStack('/home/user/project/')
4065
self.assertEquals('', stack.get('base', expand=True))
4066
self.assertEquals('', stack.get('rel', expand=True))
4068
def test_expand_basename_locally(self):
4069
l_store = config.LocationStore()
4070
l_store._load_from_string('''
4071
[/home/user/project]
4075
stack = config.LocationStack('/home/user/project/branch')
4076
self.assertEquals('branch', stack.get('bfoo', expand=True))
4078
def test_expand_basename_locally_longer_path(self):
4079
l_store = config.LocationStore()
4080
l_store._load_from_string('''
4085
stack = config.LocationStack('/home/user/project/dir/branch')
4086
self.assertEquals('branch', stack.get('bfoo', expand=True))
4088
def test_expand_relpath_locally(self):
4089
l_store = config.LocationStore()
4090
l_store._load_from_string('''
4091
[/home/user/project]
4092
lfoo = loc-foo/{relpath}
4095
stack = config.LocationStack('/home/user/project/branch')
4096
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4098
def test_expand_relpath_unknonw_in_global(self):
4099
g_store = config.GlobalStore()
4100
g_store._load_from_string('''
4105
stack = config.LocationStack('/home/user/project/branch')
4106
self.assertRaises(errors.ExpandingUnknownOption,
4107
stack.get, 'gfoo', expand=True)
4109
def test_expand_local_option_locally(self):
4110
l_store = config.LocationStore()
4111
l_store._load_from_string('''
4112
[/home/user/project]
4113
lfoo = loc-foo/{relpath}
4117
g_store = config.GlobalStore()
4118
g_store._load_from_string('''
4124
stack = config.LocationStack('/home/user/project/branch')
4125
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4126
self.assertEquals('loc-foo/branch', stack.get('gfoo', expand=True))
4128
def test_locals_dont_leak(self):
4129
"""Make sure we chose the right local in presence of several sections.
4131
l_store = config.LocationStore()
4132
l_store._load_from_string('''
4134
lfoo = loc-foo/{relpath}
4135
[/home/user/project]
4136
lfoo = loc-foo/{relpath}
4139
stack = config.LocationStack('/home/user/project/branch')
4140
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4141
stack = config.LocationStack('/home/user/bar/baz')
4142
self.assertEquals('loc-foo/bar/baz', stack.get('lfoo', expand=True))
4146
class TestStackSet(TestStackWithTransport):
4148
def test_simple_set(self):
4149
conf = self.get_stack(self)
4150
self.assertEquals(None, conf.get('foo'))
4151
conf.set('foo', 'baz')
4152
# Did we get it back ?
4153
self.assertEquals('baz', conf.get('foo'))
4155
def test_set_creates_a_new_section(self):
4156
conf = self.get_stack(self)
4157
conf.set('foo', 'baz')
4158
self.assertEquals, 'baz', conf.get('foo')
4160
def test_set_hook(self):
4164
config.ConfigHooks.install_named_hook('set', hook, None)
4165
self.assertLength(0, calls)
4166
conf = self.get_stack(self)
4167
conf.set('foo', 'bar')
4168
self.assertLength(1, calls)
4169
self.assertEquals((conf, 'foo', 'bar'), calls[0])
4172
class TestStackRemove(TestStackWithTransport):
4174
def test_remove_existing(self):
4175
conf = self.get_stack(self)
4176
conf.set('foo', 'bar')
4177
self.assertEquals('bar', conf.get('foo'))
4179
# Did we get it back ?
4180
self.assertEquals(None, conf.get('foo'))
4182
def test_remove_unknown(self):
4183
conf = self.get_stack(self)
4184
self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
4186
def test_remove_hook(self):
4190
config.ConfigHooks.install_named_hook('remove', hook, None)
4191
self.assertLength(0, calls)
4192
conf = self.get_stack(self)
4193
conf.set('foo', 'bar')
4195
self.assertLength(1, calls)
4196
self.assertEquals((conf, 'foo'), calls[0])
4199
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
4202
super(TestConfigGetOptions, self).setUp()
4203
create_configs(self)
4205
def test_no_variable(self):
4206
# Using branch should query branch, locations and bazaar
4207
self.assertOptions([], self.branch_config)
4209
def test_option_in_bazaar(self):
4210
self.bazaar_config.set_user_option('file', 'bazaar')
4211
self.assertOptions([('file', 'bazaar', 'DEFAULT', 'bazaar')],
4214
def test_option_in_locations(self):
4215
self.locations_config.set_user_option('file', 'locations')
4217
[('file', 'locations', self.tree.basedir, 'locations')],
4218
self.locations_config)
4220
def test_option_in_branch(self):
4221
self.branch_config.set_user_option('file', 'branch')
4222
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch')],
4225
def test_option_in_bazaar_and_branch(self):
4226
self.bazaar_config.set_user_option('file', 'bazaar')
4227
self.branch_config.set_user_option('file', 'branch')
4228
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch'),
4229
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4232
def test_option_in_branch_and_locations(self):
4233
# Hmm, locations override branch :-/
4234
self.locations_config.set_user_option('file', 'locations')
4235
self.branch_config.set_user_option('file', 'branch')
4237
[('file', 'locations', self.tree.basedir, 'locations'),
4238
('file', 'branch', 'DEFAULT', 'branch'),],
4241
def test_option_in_bazaar_locations_and_branch(self):
4242
self.bazaar_config.set_user_option('file', 'bazaar')
4243
self.locations_config.set_user_option('file', 'locations')
4244
self.branch_config.set_user_option('file', 'branch')
4246
[('file', 'locations', self.tree.basedir, 'locations'),
4247
('file', 'branch', 'DEFAULT', 'branch'),
4248
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4252
class TestConfigRemoveOption(tests.TestCaseWithTransport, TestOptionsMixin):
4255
super(TestConfigRemoveOption, self).setUp()
4256
create_configs_with_file_option(self)
4258
def test_remove_in_locations(self):
4259
self.locations_config.remove_user_option('file', self.tree.basedir)
4261
[('file', 'branch', 'DEFAULT', 'branch'),
4262
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4265
def test_remove_in_branch(self):
4266
self.branch_config.remove_user_option('file')
4268
[('file', 'locations', self.tree.basedir, 'locations'),
4269
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4272
def test_remove_in_bazaar(self):
4273
self.bazaar_config.remove_user_option('file')
4275
[('file', 'locations', self.tree.basedir, 'locations'),
4276
('file', 'branch', 'DEFAULT', 'branch'),],
4280
class TestConfigGetSections(tests.TestCaseWithTransport):
4283
super(TestConfigGetSections, self).setUp()
4284
create_configs(self)
4286
def assertSectionNames(self, expected, conf, name=None):
4287
"""Check which sections are returned for a given config.
4289
If fallback configurations exist their sections can be included.
4291
:param expected: A list of section names.
4293
:param conf: The configuration that will be queried.
4295
:param name: An optional section name that will be passed to
4298
sections = list(conf._get_sections(name))
4299
self.assertLength(len(expected), sections)
4300
self.assertEqual(expected, [name for name, _, _ in sections])
4302
def test_bazaar_default_section(self):
4303
self.assertSectionNames(['DEFAULT'], self.bazaar_config)
4305
def test_locations_default_section(self):
4306
# No sections are defined in an empty file
4307
self.assertSectionNames([], self.locations_config)
4309
def test_locations_named_section(self):
4310
self.locations_config.set_user_option('file', 'locations')
4311
self.assertSectionNames([self.tree.basedir], self.locations_config)
4313
def test_locations_matching_sections(self):
4314
loc_config = self.locations_config
4315
loc_config.set_user_option('file', 'locations')
4316
# We need to cheat a bit here to create an option in sections above and
4317
# below the 'location' one.
4318
parser = loc_config._get_parser()
4319
# locations.cong deals with '/' ignoring native os.sep
4320
location_names = self.tree.basedir.split('/')
4321
parent = '/'.join(location_names[:-1])
4322
child = '/'.join(location_names + ['child'])
4324
parser[parent]['file'] = 'parent'
4326
parser[child]['file'] = 'child'
4327
self.assertSectionNames([self.tree.basedir, parent], loc_config)
4329
def test_branch_data_default_section(self):
4330
self.assertSectionNames([None],
4331
self.branch_config._get_branch_data_config())
4333
def test_branch_default_sections(self):
4334
# No sections are defined in an empty locations file
4335
self.assertSectionNames([None, 'DEFAULT'],
4337
# Unless we define an option
4338
self.branch_config._get_location_config().set_user_option(
4339
'file', 'locations')
4340
self.assertSectionNames([self.tree.basedir, None, 'DEFAULT'],
4343
def test_bazaar_named_section(self):
4344
# We need to cheat as the API doesn't give direct access to sections
4345
# other than DEFAULT.
4346
self.bazaar_config.set_alias('bazaar', 'bzr')
4347
self.assertSectionNames(['ALIASES'], self.bazaar_config, 'ALIASES')
4350
class TestSharedStores(tests.TestCaseInTempDir):
4352
def test_bazaar_conf_shared(self):
4353
g1 = config.GlobalStack()
4354
g2 = config.GlobalStack()
4355
# The two stacks share the same store
4356
self.assertIs(g1.store, g2.store)
4359
class TestAuthenticationConfigFile(tests.TestCase):
4360
"""Test the authentication.conf file matching"""
4362
def _got_user_passwd(self, expected_user, expected_password,
4363
config, *args, **kwargs):
4364
credentials = config.get_credentials(*args, **kwargs)
4365
if credentials is None:
4369
user = credentials['user']
4370
password = credentials['password']
4371
self.assertEquals(expected_user, user)
4372
self.assertEquals(expected_password, password)
4374
def test_empty_config(self):
4375
conf = config.AuthenticationConfig(_file=StringIO())
4376
self.assertEquals({}, conf._get_config())
4377
self._got_user_passwd(None, None, conf, 'http', 'foo.net')
4379
def test_non_utf8_config(self):
4380
conf = config.AuthenticationConfig(_file=StringIO(
4382
self.assertRaises(errors.ConfigContentError, conf._get_config)
4384
def test_missing_auth_section_header(self):
4385
conf = config.AuthenticationConfig(_file=StringIO('foo = bar'))
4386
self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
4388
def test_auth_section_header_not_closed(self):
4389
conf = config.AuthenticationConfig(_file=StringIO('[DEF'))
4390
self.assertRaises(errors.ParseConfigError, conf._get_config)
4392
def test_auth_value_not_boolean(self):
4393
conf = config.AuthenticationConfig(_file=StringIO(
4397
verify_certificates=askme # Error: Not a boolean
4399
self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
4401
def test_auth_value_not_int(self):
4402
conf = config.AuthenticationConfig(_file=StringIO(
4406
port=port # Error: Not an int
4408
self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
4410
def test_unknown_password_encoding(self):
4411
conf = config.AuthenticationConfig(_file=StringIO(
4415
password_encoding=unknown
4417
self.assertRaises(ValueError, conf.get_password,
4418
'ftp', 'foo.net', 'joe')
4420
def test_credentials_for_scheme_host(self):
4421
conf = config.AuthenticationConfig(_file=StringIO(
4422
"""# Identity on foo.net
4427
password=secret-pass
4430
self._got_user_passwd('joe', 'secret-pass', conf, 'ftp', 'foo.net')
4432
self._got_user_passwd(None, None, conf, 'http', 'foo.net')
4434
self._got_user_passwd(None, None, conf, 'ftp', 'bar.net')
4436
def test_credentials_for_host_port(self):
4437
conf = config.AuthenticationConfig(_file=StringIO(
4438
"""# Identity on foo.net
4444
password=secret-pass
4447
self._got_user_passwd('joe', 'secret-pass',
4448
conf, 'ftp', 'foo.net', port=10021)
4450
self._got_user_passwd(None, None, conf, 'ftp', 'foo.net')
4452
def test_for_matching_host(self):
4453
conf = config.AuthenticationConfig(_file=StringIO(
4454
"""# Identity on foo.net
4460
[sourceforge domain]
4467
self._got_user_passwd('georges', 'bendover',
4468
conf, 'bzr', 'foo.bzr.sf.net')
4470
self._got_user_passwd(None, None,
4471
conf, 'bzr', 'bbzr.sf.net')
4473
def test_for_matching_host_None(self):
4474
conf = config.AuthenticationConfig(_file=StringIO(
4475
"""# Identity on foo.net
4485
self._got_user_passwd('joe', 'joepass',
4486
conf, 'bzr', 'quux.net')
4487
# no host but different scheme
4488
self._got_user_passwd('georges', 'bendover',
4489
conf, 'ftp', 'quux.net')
4491
def test_credentials_for_path(self):
4492
conf = config.AuthenticationConfig(_file=StringIO(
4508
self._got_user_passwd(None, None,
4509
conf, 'http', host='bar.org', path='/dir3')
4511
self._got_user_passwd('georges', 'bendover',
4512
conf, 'http', host='bar.org', path='/dir2')
4514
self._got_user_passwd('jim', 'jimpass',
4515
conf, 'http', host='bar.org',path='/dir1/subdir')
4517
def test_credentials_for_user(self):
4518
conf = config.AuthenticationConfig(_file=StringIO(
4527
self._got_user_passwd('jim', 'jimpass',
4528
conf, 'http', 'bar.org')
4530
self._got_user_passwd('jim', 'jimpass',
4531
conf, 'http', 'bar.org', user='jim')
4532
# Don't get a different user if one is specified
4533
self._got_user_passwd(None, None,
4534
conf, 'http', 'bar.org', user='georges')
4536
def test_credentials_for_user_without_password(self):
4537
conf = config.AuthenticationConfig(_file=StringIO(
4544
# Get user but no password
4545
self._got_user_passwd('jim', None,
4546
conf, 'http', 'bar.org')
4548
def test_verify_certificates(self):
4549
conf = config.AuthenticationConfig(_file=StringIO(
4556
verify_certificates=False
4563
credentials = conf.get_credentials('https', 'bar.org')
4564
self.assertEquals(False, credentials.get('verify_certificates'))
4565
credentials = conf.get_credentials('https', 'foo.net')
4566
self.assertEquals(True, credentials.get('verify_certificates'))
4569
class TestAuthenticationStorage(tests.TestCaseInTempDir):
4571
def test_set_credentials(self):
4572
conf = config.AuthenticationConfig()
4573
conf.set_credentials('name', 'host', 'user', 'scheme', 'password',
4574
99, path='/foo', verify_certificates=False, realm='realm')
4575
credentials = conf.get_credentials(host='host', scheme='scheme',
4576
port=99, path='/foo',
4578
CREDENTIALS = {'name': 'name', 'user': 'user', 'password': 'password',
4579
'verify_certificates': False, 'scheme': 'scheme',
4580
'host': 'host', 'port': 99, 'path': '/foo',
4582
self.assertEqual(CREDENTIALS, credentials)
4583
credentials_from_disk = config.AuthenticationConfig().get_credentials(
4584
host='host', scheme='scheme', port=99, path='/foo', realm='realm')
4585
self.assertEqual(CREDENTIALS, credentials_from_disk)
4587
def test_reset_credentials_different_name(self):
4588
conf = config.AuthenticationConfig()
4589
conf.set_credentials('name', 'host', 'user', 'scheme', 'password'),
4590
conf.set_credentials('name2', 'host', 'user2', 'scheme', 'password'),
4591
self.assertIs(None, conf._get_config().get('name'))
4592
credentials = conf.get_credentials(host='host', scheme='scheme')
4593
CREDENTIALS = {'name': 'name2', 'user': 'user2', 'password':
4594
'password', 'verify_certificates': True,
4595
'scheme': 'scheme', 'host': 'host', 'port': None,
4596
'path': None, 'realm': None}
4597
self.assertEqual(CREDENTIALS, credentials)
4600
class TestAuthenticationConfig(tests.TestCase):
4601
"""Test AuthenticationConfig behaviour"""
4603
def _check_default_password_prompt(self, expected_prompt_format, scheme,
4604
host=None, port=None, realm=None,
4608
user, password = 'jim', 'precious'
4609
expected_prompt = expected_prompt_format % {
4610
'scheme': scheme, 'host': host, 'port': port,
4611
'user': user, 'realm': realm}
4613
stdout = tests.StringIOWrapper()
4614
stderr = tests.StringIOWrapper()
4615
ui.ui_factory = tests.TestUIFactory(stdin=password + '\n',
4616
stdout=stdout, stderr=stderr)
4617
# We use an empty conf so that the user is always prompted
4618
conf = config.AuthenticationConfig()
4619
self.assertEquals(password,
4620
conf.get_password(scheme, host, user, port=port,
4621
realm=realm, path=path))
4622
self.assertEquals(expected_prompt, stderr.getvalue())
4623
self.assertEquals('', stdout.getvalue())
4625
def _check_default_username_prompt(self, expected_prompt_format, scheme,
4626
host=None, port=None, realm=None,
4631
expected_prompt = expected_prompt_format % {
4632
'scheme': scheme, 'host': host, 'port': port,
4634
stdout = tests.StringIOWrapper()
4635
stderr = tests.StringIOWrapper()
4636
ui.ui_factory = tests.TestUIFactory(stdin=username+ '\n',
4637
stdout=stdout, stderr=stderr)
4638
# We use an empty conf so that the user is always prompted
4639
conf = config.AuthenticationConfig()
4640
self.assertEquals(username, conf.get_user(scheme, host, port=port,
4641
realm=realm, path=path, ask=True))
4642
self.assertEquals(expected_prompt, stderr.getvalue())
4643
self.assertEquals('', stdout.getvalue())
4645
def test_username_defaults_prompts(self):
4646
# HTTP prompts can't be tested here, see test_http.py
4647
self._check_default_username_prompt(u'FTP %(host)s username: ', 'ftp')
4648
self._check_default_username_prompt(
4649
u'FTP %(host)s:%(port)d username: ', 'ftp', port=10020)
4650
self._check_default_username_prompt(
4651
u'SSH %(host)s:%(port)d username: ', 'ssh', port=12345)
4653
def test_username_default_no_prompt(self):
4654
conf = config.AuthenticationConfig()
4655
self.assertEquals(None,
4656
conf.get_user('ftp', 'example.com'))
4657
self.assertEquals("explicitdefault",
4658
conf.get_user('ftp', 'example.com', default="explicitdefault"))
4660
def test_password_default_prompts(self):
4661
# HTTP prompts can't be tested here, see test_http.py
4662
self._check_default_password_prompt(
4663
u'FTP %(user)s@%(host)s password: ', 'ftp')
4664
self._check_default_password_prompt(
4665
u'FTP %(user)s@%(host)s:%(port)d password: ', 'ftp', port=10020)
4666
self._check_default_password_prompt(
4667
u'SSH %(user)s@%(host)s:%(port)d password: ', 'ssh', port=12345)
4668
# SMTP port handling is a bit special (it's handled if embedded in the
4670
# FIXME: should we: forbid that, extend it to other schemes, leave
4671
# things as they are that's fine thank you ?
4672
self._check_default_password_prompt(
4673
u'SMTP %(user)s@%(host)s password: ', 'smtp')
4674
self._check_default_password_prompt(
4675
u'SMTP %(user)s@%(host)s password: ', 'smtp', host='bar.org:10025')
4676
self._check_default_password_prompt(
4677
u'SMTP %(user)s@%(host)s:%(port)d password: ', 'smtp', port=10025)
4679
def test_ssh_password_emits_warning(self):
4680
conf = config.AuthenticationConfig(_file=StringIO(
4688
entered_password = 'typed-by-hand'
4689
stdout = tests.StringIOWrapper()
4690
stderr = tests.StringIOWrapper()
4691
ui.ui_factory = tests.TestUIFactory(stdin=entered_password + '\n',
4692
stdout=stdout, stderr=stderr)
4694
# Since the password defined in the authentication config is ignored,
4695
# the user is prompted
4696
self.assertEquals(entered_password,
4697
conf.get_password('ssh', 'bar.org', user='jim'))
4698
self.assertContainsRe(
4700
'password ignored in section \[ssh with password\]')
4702
def test_ssh_without_password_doesnt_emit_warning(self):
4703
conf = config.AuthenticationConfig(_file=StringIO(
4710
entered_password = 'typed-by-hand'
4711
stdout = tests.StringIOWrapper()
4712
stderr = tests.StringIOWrapper()
4713
ui.ui_factory = tests.TestUIFactory(stdin=entered_password + '\n',
4717
# Since the password defined in the authentication config is ignored,
4718
# the user is prompted
4719
self.assertEquals(entered_password,
4720
conf.get_password('ssh', 'bar.org', user='jim'))
4721
# No warning shoud be emitted since there is no password. We are only
4723
self.assertNotContainsRe(
4725
'password ignored in section \[ssh with password\]')
4727
def test_uses_fallback_stores(self):
4728
self.overrideAttr(config, 'credential_store_registry',
4729
config.CredentialStoreRegistry())
4730
store = StubCredentialStore()
4731
store.add_credentials("http", "example.com", "joe", "secret")
4732
config.credential_store_registry.register("stub", store, fallback=True)
4733
conf = config.AuthenticationConfig(_file=StringIO())
4734
creds = conf.get_credentials("http", "example.com")
4735
self.assertEquals("joe", creds["user"])
4736
self.assertEquals("secret", creds["password"])
4739
class StubCredentialStore(config.CredentialStore):
4745
def add_credentials(self, scheme, host, user, password=None):
4746
self._username[(scheme, host)] = user
4747
self._password[(scheme, host)] = password
4749
def get_credentials(self, scheme, host, port=None, user=None,
4750
path=None, realm=None):
4751
key = (scheme, host)
4752
if not key in self._username:
4754
return { "scheme": scheme, "host": host, "port": port,
4755
"user": self._username[key], "password": self._password[key]}
4758
class CountingCredentialStore(config.CredentialStore):
4763
def get_credentials(self, scheme, host, port=None, user=None,
4764
path=None, realm=None):
4769
class TestCredentialStoreRegistry(tests.TestCase):
4771
def _get_cs_registry(self):
4772
return config.credential_store_registry
4774
def test_default_credential_store(self):
4775
r = self._get_cs_registry()
4776
default = r.get_credential_store(None)
4777
self.assertIsInstance(default, config.PlainTextCredentialStore)
4779
def test_unknown_credential_store(self):
4780
r = self._get_cs_registry()
4781
# It's hard to imagine someone creating a credential store named
4782
# 'unknown' so we use that as an never registered key.
4783
self.assertRaises(KeyError, r.get_credential_store, 'unknown')
4785
def test_fallback_none_registered(self):
4786
r = config.CredentialStoreRegistry()
4787
self.assertEquals(None,
4788
r.get_fallback_credentials("http", "example.com"))
4790
def test_register(self):
4791
r = config.CredentialStoreRegistry()
4792
r.register("stub", StubCredentialStore(), fallback=False)
4793
r.register("another", StubCredentialStore(), fallback=True)
4794
self.assertEquals(["another", "stub"], r.keys())
4796
def test_register_lazy(self):
4797
r = config.CredentialStoreRegistry()
4798
r.register_lazy("stub", "bzrlib.tests.test_config",
4799
"StubCredentialStore", fallback=False)
4800
self.assertEquals(["stub"], r.keys())
4801
self.assertIsInstance(r.get_credential_store("stub"),
4802
StubCredentialStore)
4804
def test_is_fallback(self):
4805
r = config.CredentialStoreRegistry()
4806
r.register("stub1", None, fallback=False)
4807
r.register("stub2", None, fallback=True)
4808
self.assertEquals(False, r.is_fallback("stub1"))
4809
self.assertEquals(True, r.is_fallback("stub2"))
4811
def test_no_fallback(self):
4812
r = config.CredentialStoreRegistry()
4813
store = CountingCredentialStore()
4814
r.register("count", store, fallback=False)
4815
self.assertEquals(None,
4816
r.get_fallback_credentials("http", "example.com"))
4817
self.assertEquals(0, store._calls)
4819
def test_fallback_credentials(self):
4820
r = config.CredentialStoreRegistry()
4821
store = StubCredentialStore()
4822
store.add_credentials("http", "example.com",
4823
"somebody", "geheim")
4824
r.register("stub", store, fallback=True)
4825
creds = r.get_fallback_credentials("http", "example.com")
4826
self.assertEquals("somebody", creds["user"])
4827
self.assertEquals("geheim", creds["password"])
4829
def test_fallback_first_wins(self):
4830
r = config.CredentialStoreRegistry()
4831
stub1 = StubCredentialStore()
4832
stub1.add_credentials("http", "example.com",
4833
"somebody", "stub1")
4834
r.register("stub1", stub1, fallback=True)
4835
stub2 = StubCredentialStore()
4836
stub2.add_credentials("http", "example.com",
4837
"somebody", "stub2")
4838
r.register("stub2", stub1, fallback=True)
4839
creds = r.get_fallback_credentials("http", "example.com")
4840
self.assertEquals("somebody", creds["user"])
4841
self.assertEquals("stub1", creds["password"])
4844
class TestPlainTextCredentialStore(tests.TestCase):
4846
def test_decode_password(self):
4847
r = config.credential_store_registry
4848
plain_text = r.get_credential_store()
4849
decoded = plain_text.decode_password(dict(password='secret'))
4850
self.assertEquals('secret', decoded)
4853
class TestBase64CredentialStore(tests.TestCase):
4855
def test_decode_password(self):
4856
r = config.credential_store_registry
4857
plain_text = r.get_credential_store('base64')
4858
decoded = plain_text.decode_password(dict(password='c2VjcmV0'))
4859
self.assertEquals('secret', decoded)
4862
# FIXME: Once we have a way to declare authentication to all test servers, we
4863
# can implement generic tests.
4864
# test_user_password_in_url
4865
# test_user_in_url_password_from_config
4866
# test_user_in_url_password_prompted
4867
# test_user_in_config
4868
# test_user_getpass.getuser
4869
# test_user_prompted ?
4870
class TestAuthenticationRing(tests.TestCaseWithTransport):
4874
class TestAutoUserId(tests.TestCase):
4875
"""Test inferring an automatic user name."""
4877
def test_auto_user_id(self):
4878
"""Automatic inference of user name.
4880
This is a bit hard to test in an isolated way, because it depends on
4881
system functions that go direct to /etc or perhaps somewhere else.
4882
But it's reasonable to say that on Unix, with an /etc/mailname, we ought
4883
to be able to choose a user name with no configuration.
4885
if sys.platform == 'win32':
4886
raise tests.TestSkipped(
4887
"User name inference not implemented on win32")
4888
realname, address = config._auto_user_id()
4889
if os.path.exists('/etc/mailname'):
4890
self.assertIsNot(None, realname)
4891
self.assertIsNot(None, address)
4893
self.assertEquals((None, None), (realname, address))
4896
class TestDefaultMailDomain(tests.TestCaseInTempDir):
4897
"""Test retrieving default domain from mailname file"""
4899
def test_default_mail_domain_simple(self):
4900
f = file('simple', 'w')
4902
f.write("domainname.com\n")
4905
r = config._get_default_mail_domain('simple')
4906
self.assertEquals('domainname.com', r)
4908
def test_default_mail_domain_no_eol(self):
4909
f = file('no_eol', 'w')
4911
f.write("domainname.com")
4914
r = config._get_default_mail_domain('no_eol')
4915
self.assertEquals('domainname.com', r)
4917
def test_default_mail_domain_multiple_lines(self):
4918
f = file('multiple_lines', 'w')
4920
f.write("domainname.com\nsome other text\n")
4923
r = config._get_default_mail_domain('multiple_lines')
4924
self.assertEquals('domainname.com', r)
4927
class EmailOptionTests(tests.TestCase):
4929
def test_default_email_uses_BZR_EMAIL(self):
4930
conf = config.MemoryStack('email=jelmer@debian.org')
4931
# BZR_EMAIL takes precedence over EMAIL
4932
self.overrideEnv('BZR_EMAIL', 'jelmer@samba.org')
4933
self.overrideEnv('EMAIL', 'jelmer@apache.org')
4934
self.assertEquals('jelmer@samba.org', conf.get('email'))
4936
def test_default_email_uses_EMAIL(self):
4937
conf = config.MemoryStack('')
4938
self.overrideEnv('BZR_EMAIL', None)
4939
self.overrideEnv('EMAIL', 'jelmer@apache.org')
4940
self.assertEquals('jelmer@apache.org', conf.get('email'))
4942
def test_BZR_EMAIL_overrides(self):
4943
conf = config.MemoryStack('email=jelmer@debian.org')
4944
self.overrideEnv('BZR_EMAIL', 'jelmer@apache.org')
4945
self.assertEquals('jelmer@apache.org', conf.get('email'))
4946
self.overrideEnv('BZR_EMAIL', None)
4947
self.overrideEnv('EMAIL', 'jelmer@samba.org')
4948
self.assertEquals('jelmer@debian.org', conf.get('email'))
4951
class MailClientOptionTests(tests.TestCase):
4953
def test_default(self):
4954
conf = config.MemoryStack('')
4955
client = conf.get('mail_client')
4956
self.assertIs(client, mail_client.DefaultMail)
4958
def test_evolution(self):
4959
conf = config.MemoryStack('mail_client=evolution')
4960
client = conf.get('mail_client')
4961
self.assertIs(client, mail_client.Evolution)
4963
def test_kmail(self):
4964
conf = config.MemoryStack('mail_client=kmail')
4965
client = conf.get('mail_client')
4966
self.assertIs(client, mail_client.KMail)
4968
def test_mutt(self):
4969
conf = config.MemoryStack('mail_client=mutt')
4970
client = conf.get('mail_client')
4971
self.assertIs(client, mail_client.Mutt)
4973
def test_thunderbird(self):
4974
conf = config.MemoryStack('mail_client=thunderbird')
4975
client = conf.get('mail_client')
4976
self.assertIs(client, mail_client.Thunderbird)
4978
def test_explicit_default(self):
4979
conf = config.MemoryStack('mail_client=default')
4980
client = conf.get('mail_client')
4981
self.assertIs(client, mail_client.DefaultMail)
4983
def test_editor(self):
4984
conf = config.MemoryStack('mail_client=editor')
4985
client = conf.get('mail_client')
4986
self.assertIs(client, mail_client.Editor)
4988
def test_mapi(self):
4989
conf = config.MemoryStack('mail_client=mapi')
4990
client = conf.get('mail_client')
4991
self.assertIs(client, mail_client.MAPIClient)
4993
def test_xdg_email(self):
4994
conf = config.MemoryStack('mail_client=xdg-email')
4995
client = conf.get('mail_client')
4996
self.assertIs(client, mail_client.XDGEmail)
4998
def test_unknown(self):
4999
conf = config.MemoryStack('mail_client=firebird')
5000
self.assertRaises(errors.ConfigOptionValueError, conf.get,