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]."""
18
# import system imports here
19
from cStringIO import StringIO
20
from textwrap import dedent
26
from testtools import matchers
28
#import bzrlib specific imports here
39
registry as _mod_registry,
44
from bzrlib.symbol_versioning import (
47
from bzrlib.transport import remote as transport_remote
48
from bzrlib.tests import (
53
from bzrlib.util.configobj import configobj
56
def lockable_config_scenarios():
59
{'config_class': config.GlobalConfig,
61
'config_section': 'DEFAULT'}),
63
{'config_class': config.LocationConfig,
65
'config_section': '.'}),]
68
load_tests = scenarios.load_tests_apply_scenarios
70
# Register helpers to build stores
71
config.test_store_builder_registry.register(
72
'configobj', lambda test: config.TransportIniFileStore(
73
test.get_transport(), 'configobj.conf'))
74
config.test_store_builder_registry.register(
75
'bazaar', lambda test: config.GlobalStore())
76
config.test_store_builder_registry.register(
77
'location', lambda test: config.LocationStore())
80
def build_backing_branch(test, relpath,
81
transport_class=None, server_class=None):
82
"""Test helper to create a backing branch only once.
84
Some tests needs multiple stores/stacks to check concurrent update
85
behaviours. As such, they need to build different branch *objects* even if
86
they share the branch on disk.
88
:param relpath: The relative path to the branch. (Note that the helper
89
should always specify the same relpath).
91
:param transport_class: The Transport class the test needs to use.
93
:param server_class: The server associated with the ``transport_class``
96
Either both or neither of ``transport_class`` and ``server_class`` should
99
if transport_class is not None and server_class is not None:
100
test.transport_class = transport_class
101
test.transport_server = server_class
102
elif not (transport_class is None and server_class is None):
103
raise AssertionError('Specify both ``transport_class`` and '
104
'``server_class`` or neither of them')
105
if getattr(test, 'backing_branch', None) is None:
106
# First call, let's build the branch on disk
107
test.backing_branch = test.make_branch(relpath)
110
def build_branch_store(test):
111
build_backing_branch(test, 'branch')
112
b = branch.Branch.open('branch')
113
return config.BranchStore(b)
114
config.test_store_builder_registry.register('branch', build_branch_store)
117
def build_control_store(test):
118
build_backing_branch(test, 'branch')
119
b = controldir.ControlDir.open('branch')
120
return config.ControlStore(b)
121
config.test_store_builder_registry.register('control', build_control_store)
124
def build_remote_branch_store(test):
125
# There is only one permutation (but we won't be able to handle more with
126
# this design anyway)
128
server_class) = transport_remote.get_test_permutations()[0]
129
build_backing_branch(test, 'branch', transport_class, server_class)
130
b = branch.Branch.open(test.get_url('branch'))
131
return config.BranchStore(b)
132
config.test_store_builder_registry.register('remote_branch',
133
build_remote_branch_store)
136
config.test_stack_builder_registry.register(
137
'bazaar', lambda test: config.GlobalStack())
138
config.test_stack_builder_registry.register(
139
'location', lambda test: config.LocationStack('.'))
142
def build_branch_stack(test):
143
build_backing_branch(test, 'branch')
144
b = branch.Branch.open('branch')
145
return config.BranchStack(b)
146
config.test_stack_builder_registry.register('branch', build_branch_stack)
149
def build_branch_only_stack(test):
150
# There is only one permutation (but we won't be able to handle more with
151
# this design anyway)
153
server_class) = transport_remote.get_test_permutations()[0]
154
build_backing_branch(test, 'branch', transport_class, server_class)
155
b = branch.Branch.open(test.get_url('branch'))
156
return config.BranchOnlyStack(b)
157
config.test_stack_builder_registry.register('branch_only',
158
build_branch_only_stack)
160
def build_remote_control_stack(test):
161
# There is only one permutation (but we won't be able to handle more with
162
# this design anyway)
164
server_class) = transport_remote.get_test_permutations()[0]
165
# We need only a bzrdir for this, not a full branch, but it's not worth
166
# creating a dedicated helper to create only the bzrdir
167
build_backing_branch(test, 'branch', transport_class, server_class)
168
b = branch.Branch.open(test.get_url('branch'))
169
return config.RemoteControlStack(b.bzrdir)
170
config.test_stack_builder_registry.register('remote_control',
171
build_remote_control_stack)
174
sample_long_alias="log -r-15..-1 --line"
175
sample_config_text = u"""
177
email=Erik B\u00e5gfors <erik@bagfors.nu>
179
change_editor=vimdiff -of @new_path @old_path
180
gpg_signing_command=gnome-gpg
181
gpg_signing_key=DD4D5088
183
validate_signatures_in_log=true
185
user_global_option=something
186
bzr.mergetool.sometool=sometool {base} {this} {other} -o {result}
187
bzr.mergetool.funkytool=funkytool "arg with spaces" {this_temp}
188
bzr.mergetool.newtool='"newtool with spaces" {this_temp}'
189
bzr.default_mergetool=sometool
192
ll=""" + sample_long_alias + "\n"
195
sample_always_signatures = """
197
check_signatures=ignore
198
create_signatures=always
201
sample_ignore_signatures = """
203
check_signatures=require
204
create_signatures=never
207
sample_maybe_signatures = """
209
check_signatures=ignore
210
create_signatures=when-required
213
sample_branches_text = """
214
[http://www.example.com]
216
email=Robert Collins <robertc@example.org>
217
normal_option = normal
218
appendpath_option = append
219
appendpath_option:policy = appendpath
220
norecurse_option = norecurse
221
norecurse_option:policy = norecurse
222
[http://www.example.com/ignoreparent]
223
# different project: ignore parent dir config
225
[http://www.example.com/norecurse]
226
# configuration items that only apply to this dir
228
normal_option = norecurse
229
[http://www.example.com/dir]
230
appendpath_option = normal
232
check_signatures=require
233
# test trailing / matching with no children
235
check_signatures=check-available
236
gpg_signing_command=false
237
gpg_signing_key=default
238
user_local_option=local
239
# test trailing / matching
241
#subdirs will match but not the parent
243
check_signatures=ignore
244
post_commit=bzrlib.tests.test_config.post_commit
245
#testing explicit beats globs
249
def create_configs(test):
250
"""Create configuration files for a given test.
252
This requires creating a tree (and populate the ``test.tree`` attribute)
253
and its associated branch and will populate the following attributes:
255
- branch_config: A BranchConfig for the associated branch.
257
- locations_config : A LocationConfig for the associated branch
259
- bazaar_config: A GlobalConfig.
261
The tree and branch are created in a 'tree' subdirectory so the tests can
262
still use the test directory to stay outside of the branch.
264
tree = test.make_branch_and_tree('tree')
266
test.branch_config = config.BranchConfig(tree.branch)
267
test.locations_config = config.LocationConfig(tree.basedir)
268
test.bazaar_config = config.GlobalConfig()
271
def create_configs_with_file_option(test):
272
"""Create configuration files with a ``file`` option set in each.
274
This builds on ``create_configs`` and add one ``file`` option in each
275
configuration with a value which allows identifying the configuration file.
278
test.bazaar_config.set_user_option('file', 'bazaar')
279
test.locations_config.set_user_option('file', 'locations')
280
test.branch_config.set_user_option('file', 'branch')
283
class TestOptionsMixin:
285
def assertOptions(self, expected, conf):
286
# We don't care about the parser (as it will make tests hard to write
287
# and error-prone anyway)
288
self.assertThat([opt[:4] for opt in conf._get_options()],
289
matchers.Equals(expected))
292
class InstrumentedConfigObj(object):
293
"""A config obj look-enough-alike to record calls made to it."""
295
def __contains__(self, thing):
296
self._calls.append(('__contains__', thing))
299
def __getitem__(self, key):
300
self._calls.append(('__getitem__', key))
303
def __init__(self, input, encoding=None):
304
self._calls = [('__init__', input, encoding)]
306
def __setitem__(self, key, value):
307
self._calls.append(('__setitem__', key, value))
309
def __delitem__(self, key):
310
self._calls.append(('__delitem__', key))
313
self._calls.append(('keys',))
317
self._calls.append(('reload',))
319
def write(self, arg):
320
self._calls.append(('write',))
322
def as_bool(self, value):
323
self._calls.append(('as_bool', value))
326
def get_value(self, section, name):
327
self._calls.append(('get_value', section, name))
331
class FakeBranch(object):
333
def __init__(self, base=None):
335
self.base = "http://example.com/branches/demo"
338
self._transport = self.control_files = \
339
FakeControlFilesAndTransport()
341
def _get_config(self):
342
return config.TransportConfig(self._transport, 'branch.conf')
344
def lock_write(self):
351
class FakeControlFilesAndTransport(object):
355
self._transport = self
357
def get(self, filename):
360
return StringIO(self.files[filename])
362
raise errors.NoSuchFile(filename)
364
def get_bytes(self, filename):
367
return self.files[filename]
369
raise errors.NoSuchFile(filename)
371
def put(self, filename, fileobj):
372
self.files[filename] = fileobj.read()
374
def put_file(self, filename, fileobj):
375
return self.put(filename, fileobj)
378
class InstrumentedConfig(config.Config):
379
"""An instrumented config that supplies stubs for template methods."""
382
super(InstrumentedConfig, self).__init__()
384
self._signatures = config.CHECK_NEVER
386
def _get_user_id(self):
387
self._calls.append('_get_user_id')
388
return "Robert Collins <robert.collins@example.org>"
390
def _get_signature_checking(self):
391
self._calls.append('_get_signature_checking')
392
return self._signatures
394
def _get_change_editor(self):
395
self._calls.append('_get_change_editor')
396
return 'vimdiff -fo @new_path @old_path'
399
bool_config = """[DEFAULT]
408
class TestConfigObj(tests.TestCase):
410
def test_get_bool(self):
411
co = config.ConfigObj(StringIO(bool_config))
412
self.assertIs(co.get_bool('DEFAULT', 'active'), True)
413
self.assertIs(co.get_bool('DEFAULT', 'inactive'), False)
414
self.assertIs(co.get_bool('UPPERCASE', 'active'), True)
415
self.assertIs(co.get_bool('UPPERCASE', 'nonactive'), False)
417
def test_hash_sign_in_value(self):
419
Before 4.5.0, ConfigObj did not quote # signs in values, so they'd be
420
treated as comments when read in again. (#86838)
422
co = config.ConfigObj()
423
co['test'] = 'foo#bar'
425
co.write(outfile=outfile)
426
lines = outfile.getvalue().splitlines()
427
self.assertEqual(lines, ['test = "foo#bar"'])
428
co2 = config.ConfigObj(lines)
429
self.assertEqual(co2['test'], 'foo#bar')
431
def test_triple_quotes(self):
432
# Bug #710410: if the value string has triple quotes
433
# then ConfigObj versions up to 4.7.2 will quote them wrong
434
# and won't able to read them back
435
triple_quotes_value = '''spam
436
""" that's my spam """
438
co = config.ConfigObj()
439
co['test'] = triple_quotes_value
440
# While writing this test another bug in ConfigObj has been found:
441
# method co.write() without arguments produces list of lines
442
# one option per line, and multiline values are not split
443
# across multiple lines,
444
# and that breaks the parsing these lines back by ConfigObj.
445
# This issue only affects test, but it's better to avoid
446
# `co.write()` construct at all.
447
# [bialix 20110222] bug report sent to ConfigObj's author
449
co.write(outfile=outfile)
450
output = outfile.getvalue()
451
# now we're trying to read it back
452
co2 = config.ConfigObj(StringIO(output))
453
self.assertEquals(triple_quotes_value, co2['test'])
456
erroneous_config = """[section] # line 1
459
whocares=notme # line 4
463
class TestConfigObjErrors(tests.TestCase):
465
def test_duplicate_section_name_error_line(self):
467
co = configobj.ConfigObj(StringIO(erroneous_config),
469
except config.configobj.DuplicateError, e:
470
self.assertEqual(3, e.line_number)
472
self.fail('Error in config file not detected')
475
class TestConfig(tests.TestCase):
477
def test_constructs(self):
480
def test_user_email(self):
481
my_config = InstrumentedConfig()
482
self.assertEqual('robert.collins@example.org', my_config.user_email())
483
self.assertEqual(['_get_user_id'], my_config._calls)
485
def test_username(self):
486
my_config = InstrumentedConfig()
487
self.assertEqual('Robert Collins <robert.collins@example.org>',
488
my_config.username())
489
self.assertEqual(['_get_user_id'], my_config._calls)
491
def test_signatures_default(self):
492
my_config = config.Config()
494
self.applyDeprecated(deprecated_in((2, 5, 0)),
495
my_config.signature_needed))
496
self.assertEqual(config.CHECK_IF_POSSIBLE,
497
self.applyDeprecated(deprecated_in((2, 5, 0)),
498
my_config.signature_checking))
499
self.assertEqual(config.SIGN_WHEN_REQUIRED,
500
self.applyDeprecated(deprecated_in((2, 5, 0)),
501
my_config.signing_policy))
503
def test_signatures_template_method(self):
504
my_config = InstrumentedConfig()
505
self.assertEqual(config.CHECK_NEVER,
506
self.applyDeprecated(deprecated_in((2, 5, 0)),
507
my_config.signature_checking))
508
self.assertEqual(['_get_signature_checking'], my_config._calls)
510
def test_signatures_template_method_none(self):
511
my_config = InstrumentedConfig()
512
my_config._signatures = None
513
self.assertEqual(config.CHECK_IF_POSSIBLE,
514
self.applyDeprecated(deprecated_in((2, 5, 0)),
515
my_config.signature_checking))
516
self.assertEqual(['_get_signature_checking'], my_config._calls)
518
def test_gpg_signing_command_default(self):
519
my_config = config.Config()
520
self.assertEqual('gpg',
521
self.applyDeprecated(deprecated_in((2, 5, 0)),
522
my_config.gpg_signing_command))
524
def test_get_user_option_default(self):
525
my_config = config.Config()
526
self.assertEqual(None, my_config.get_user_option('no_option'))
528
def test_post_commit_default(self):
529
my_config = config.Config()
530
self.assertEqual(None, self.applyDeprecated(deprecated_in((2, 5, 0)),
531
my_config.post_commit))
534
def test_log_format_default(self):
535
my_config = config.Config()
536
self.assertEqual('long',
537
self.applyDeprecated(deprecated_in((2, 5, 0)),
538
my_config.log_format))
540
def test_acceptable_keys_default(self):
541
my_config = config.Config()
542
self.assertEqual(None, self.applyDeprecated(deprecated_in((2, 5, 0)),
543
my_config.acceptable_keys))
545
def test_validate_signatures_in_log_default(self):
546
my_config = config.Config()
547
self.assertEqual(False, my_config.validate_signatures_in_log())
549
def test_get_change_editor(self):
550
my_config = InstrumentedConfig()
551
change_editor = my_config.get_change_editor('old_tree', 'new_tree')
552
self.assertEqual(['_get_change_editor'], my_config._calls)
553
self.assertIs(diff.DiffFromTool, change_editor.__class__)
554
self.assertEqual(['vimdiff', '-fo', '@new_path', '@old_path'],
555
change_editor.command_template)
558
class TestConfigPath(tests.TestCase):
561
super(TestConfigPath, self).setUp()
562
self.overrideEnv('HOME', '/home/bogus')
563
self.overrideEnv('XDG_CACHE_DIR', '')
564
if sys.platform == 'win32':
566
'BZR_HOME', r'C:\Documents and Settings\bogus\Application Data')
568
'C:/Documents and Settings/bogus/Application Data/bazaar/2.0'
570
self.bzr_home = '/home/bogus/.bazaar'
572
def test_config_dir(self):
573
self.assertEqual(config.config_dir(), self.bzr_home)
575
def test_config_dir_is_unicode(self):
576
self.assertIsInstance(config.config_dir(), unicode)
578
def test_config_filename(self):
579
self.assertEqual(config.config_filename(),
580
self.bzr_home + '/bazaar.conf')
582
def test_locations_config_filename(self):
583
self.assertEqual(config.locations_config_filename(),
584
self.bzr_home + '/locations.conf')
586
def test_authentication_config_filename(self):
587
self.assertEqual(config.authentication_config_filename(),
588
self.bzr_home + '/authentication.conf')
590
def test_xdg_cache_dir(self):
591
self.assertEqual(config.xdg_cache_dir(),
592
'/home/bogus/.cache')
595
class TestXDGConfigDir(tests.TestCaseInTempDir):
596
# must be in temp dir because config tests for the existence of the bazaar
597
# subdirectory of $XDG_CONFIG_HOME
600
if sys.platform in ('darwin', 'win32'):
601
raise tests.TestNotApplicable(
602
'XDG config dir not used on this platform')
603
super(TestXDGConfigDir, self).setUp()
604
self.overrideEnv('HOME', self.test_home_dir)
605
# BZR_HOME overrides everything we want to test so unset it.
606
self.overrideEnv('BZR_HOME', None)
608
def test_xdg_config_dir_exists(self):
609
"""When ~/.config/bazaar exists, use it as the config dir."""
610
newdir = osutils.pathjoin(self.test_home_dir, '.config', 'bazaar')
612
self.assertEqual(config.config_dir(), newdir)
614
def test_xdg_config_home(self):
615
"""When XDG_CONFIG_HOME is set, use it."""
616
xdgconfigdir = osutils.pathjoin(self.test_home_dir, 'xdgconfig')
617
self.overrideEnv('XDG_CONFIG_HOME', xdgconfigdir)
618
newdir = osutils.pathjoin(xdgconfigdir, 'bazaar')
620
self.assertEqual(config.config_dir(), newdir)
623
class TestIniConfig(tests.TestCaseInTempDir):
625
def make_config_parser(self, s):
626
conf = config.IniBasedConfig.from_string(s)
627
return conf, conf._get_parser()
630
class TestIniConfigBuilding(TestIniConfig):
632
def test_contructs(self):
633
my_config = config.IniBasedConfig()
635
def test_from_fp(self):
636
my_config = config.IniBasedConfig.from_string(sample_config_text)
637
self.assertIsInstance(my_config._get_parser(), configobj.ConfigObj)
639
def test_cached(self):
640
my_config = config.IniBasedConfig.from_string(sample_config_text)
641
parser = my_config._get_parser()
642
self.assertTrue(my_config._get_parser() is parser)
644
def _dummy_chown(self, path, uid, gid):
645
self.path, self.uid, self.gid = path, uid, gid
647
def test_ini_config_ownership(self):
648
"""Ensure that chown is happening during _write_config_file"""
649
self.requireFeature(features.chown_feature)
650
self.overrideAttr(os, 'chown', self._dummy_chown)
651
self.path = self.uid = self.gid = None
652
conf = config.IniBasedConfig(file_name='./foo.conf')
653
conf._write_config_file()
654
self.assertEquals(self.path, './foo.conf')
655
self.assertTrue(isinstance(self.uid, int))
656
self.assertTrue(isinstance(self.gid, int))
658
def test_get_filename_parameter_is_deprecated_(self):
659
conf = self.callDeprecated([
660
'IniBasedConfig.__init__(get_filename) was deprecated in 2.3.'
661
' Use file_name instead.'],
662
config.IniBasedConfig, lambda: 'ini.conf')
663
self.assertEqual('ini.conf', conf.file_name)
665
def test_get_parser_file_parameter_is_deprecated_(self):
666
config_file = StringIO(sample_config_text.encode('utf-8'))
667
conf = config.IniBasedConfig.from_string(sample_config_text)
668
conf = self.callDeprecated([
669
'IniBasedConfig._get_parser(file=xxx) was deprecated in 2.3.'
670
' Use IniBasedConfig(_content=xxx) instead.'],
671
conf._get_parser, file=config_file)
674
class TestIniConfigSaving(tests.TestCaseInTempDir):
676
def test_cant_save_without_a_file_name(self):
677
conf = config.IniBasedConfig()
678
self.assertRaises(AssertionError, conf._write_config_file)
680
def test_saved_with_content(self):
681
content = 'foo = bar\n'
682
conf = config.IniBasedConfig.from_string(
683
content, file_name='./test.conf', save=True)
684
self.assertFileEqual(content, 'test.conf')
687
class TestIniConfigOptionExpansion(tests.TestCase):
688
"""Test option expansion from the IniConfig level.
690
What we really want here is to test the Config level, but the class being
691
abstract as far as storing values is concerned, this can't be done
694
# FIXME: This should be rewritten when all configs share a storage
695
# implementation -- vila 2011-02-18
697
def get_config(self, string=None):
700
c = config.IniBasedConfig.from_string(string)
703
def assertExpansion(self, expected, conf, string, env=None):
704
self.assertEquals(expected, conf.expand_options(string, env))
706
def test_no_expansion(self):
707
c = self.get_config('')
708
self.assertExpansion('foo', c, 'foo')
710
def test_env_adding_options(self):
711
c = self.get_config('')
712
self.assertExpansion('bar', c, '{foo}', {'foo': 'bar'})
714
def test_env_overriding_options(self):
715
c = self.get_config('foo=baz')
716
self.assertExpansion('bar', c, '{foo}', {'foo': 'bar'})
718
def test_simple_ref(self):
719
c = self.get_config('foo=xxx')
720
self.assertExpansion('xxx', c, '{foo}')
722
def test_unknown_ref(self):
723
c = self.get_config('')
724
self.assertRaises(errors.ExpandingUnknownOption,
725
c.expand_options, '{foo}')
727
def test_indirect_ref(self):
728
c = self.get_config('''
732
self.assertExpansion('xxx', c, '{bar}')
734
def test_embedded_ref(self):
735
c = self.get_config('''
739
self.assertExpansion('xxx', c, '{{bar}}')
741
def test_simple_loop(self):
742
c = self.get_config('foo={foo}')
743
self.assertRaises(errors.OptionExpansionLoop, c.expand_options, '{foo}')
745
def test_indirect_loop(self):
746
c = self.get_config('''
750
e = self.assertRaises(errors.OptionExpansionLoop,
751
c.expand_options, '{foo}')
752
self.assertEquals('foo->bar->baz', e.refs)
753
self.assertEquals('{foo}', e.string)
756
conf = self.get_config('''
760
list={foo},{bar},{baz}
762
self.assertEquals(['start', 'middle', 'end'],
763
conf.get_user_option('list', expand=True))
765
def test_cascading_list(self):
766
conf = self.get_config('''
772
self.assertEquals(['start', 'middle', 'end'],
773
conf.get_user_option('list', expand=True))
775
def test_pathological_hidden_list(self):
776
conf = self.get_config('''
782
hidden={start}{middle}{end}
784
# Nope, it's either a string or a list, and the list wins as soon as a
785
# ',' appears, so the string concatenation never occur.
786
self.assertEquals(['{foo', '}', '{', 'bar}'],
787
conf.get_user_option('hidden', expand=True))
790
class TestLocationConfigOptionExpansion(tests.TestCaseInTempDir):
792
def get_config(self, location, string=None):
795
# Since we don't save the config we won't strictly require to inherit
796
# from TestCaseInTempDir, but an error occurs so quickly...
797
c = config.LocationConfig.from_string(string, location)
800
def test_dont_cross_unrelated_section(self):
801
c = self.get_config('/another/branch/path','''
806
[/another/branch/path]
809
self.assertRaises(errors.ExpandingUnknownOption,
810
c.get_user_option, 'bar', expand=True)
812
def test_cross_related_sections(self):
813
c = self.get_config('/project/branch/path','''
817
[/project/branch/path]
820
self.assertEquals('quux', c.get_user_option('bar', expand=True))
823
class TestIniBaseConfigOnDisk(tests.TestCaseInTempDir):
825
def test_cannot_reload_without_name(self):
826
conf = config.IniBasedConfig.from_string(sample_config_text)
827
self.assertRaises(AssertionError, conf.reload)
829
def test_reload_see_new_value(self):
830
c1 = config.IniBasedConfig.from_string('editor=vim\n',
831
file_name='./test/conf')
832
c1._write_config_file()
833
c2 = config.IniBasedConfig.from_string('editor=emacs\n',
834
file_name='./test/conf')
835
c2._write_config_file()
836
self.assertEqual('vim', c1.get_user_option('editor'))
837
self.assertEqual('emacs', c2.get_user_option('editor'))
838
# Make sure we get the Right value
840
self.assertEqual('emacs', c1.get_user_option('editor'))
843
class TestLockableConfig(tests.TestCaseInTempDir):
845
scenarios = lockable_config_scenarios()
850
config_section = None
853
super(TestLockableConfig, self).setUp()
854
self._content = '[%s]\none=1\ntwo=2\n' % (self.config_section,)
855
self.config = self.create_config(self._content)
857
def get_existing_config(self):
858
return self.config_class(*self.config_args)
860
def create_config(self, content):
861
kwargs = dict(save=True)
862
c = self.config_class.from_string(content, *self.config_args, **kwargs)
865
def test_simple_read_access(self):
866
self.assertEquals('1', self.config.get_user_option('one'))
868
def test_simple_write_access(self):
869
self.config.set_user_option('one', 'one')
870
self.assertEquals('one', self.config.get_user_option('one'))
872
def test_listen_to_the_last_speaker(self):
874
c2 = self.get_existing_config()
875
c1.set_user_option('one', 'ONE')
876
c2.set_user_option('two', 'TWO')
877
self.assertEquals('ONE', c1.get_user_option('one'))
878
self.assertEquals('TWO', c2.get_user_option('two'))
879
# The second update respect the first one
880
self.assertEquals('ONE', c2.get_user_option('one'))
882
def test_last_speaker_wins(self):
883
# If the same config is not shared, the same variable modified twice
884
# can only see a single result.
886
c2 = self.get_existing_config()
887
c1.set_user_option('one', 'c1')
888
c2.set_user_option('one', 'c2')
889
self.assertEquals('c2', c2._get_user_option('one'))
890
# The first modification is still available until another refresh
892
self.assertEquals('c1', c1._get_user_option('one'))
893
c1.set_user_option('two', 'done')
894
self.assertEquals('c2', c1._get_user_option('one'))
896
def test_writes_are_serialized(self):
898
c2 = self.get_existing_config()
900
# We spawn a thread that will pause *during* the write
901
before_writing = threading.Event()
902
after_writing = threading.Event()
903
writing_done = threading.Event()
904
c1_orig = c1._write_config_file
905
def c1_write_config_file():
908
# The lock is held. We wait for the main thread to decide when to
911
c1._write_config_file = c1_write_config_file
913
c1.set_user_option('one', 'c1')
915
t1 = threading.Thread(target=c1_set_option)
916
# Collect the thread after the test
917
self.addCleanup(t1.join)
918
# Be ready to unblock the thread if the test goes wrong
919
self.addCleanup(after_writing.set)
921
before_writing.wait()
922
self.assertTrue(c1._lock.is_held)
923
self.assertRaises(errors.LockContention,
924
c2.set_user_option, 'one', 'c2')
925
self.assertEquals('c1', c1.get_user_option('one'))
926
# Let the lock be released
929
c2.set_user_option('one', 'c2')
930
self.assertEquals('c2', c2.get_user_option('one'))
932
def test_read_while_writing(self):
934
# We spawn a thread that will pause *during* the write
935
ready_to_write = threading.Event()
936
do_writing = threading.Event()
937
writing_done = threading.Event()
938
c1_orig = c1._write_config_file
939
def c1_write_config_file():
941
# The lock is held. We wait for the main thread to decide when to
946
c1._write_config_file = c1_write_config_file
948
c1.set_user_option('one', 'c1')
949
t1 = threading.Thread(target=c1_set_option)
950
# Collect the thread after the test
951
self.addCleanup(t1.join)
952
# Be ready to unblock the thread if the test goes wrong
953
self.addCleanup(do_writing.set)
955
# Ensure the thread is ready to write
956
ready_to_write.wait()
957
self.assertTrue(c1._lock.is_held)
958
self.assertEquals('c1', c1.get_user_option('one'))
959
# If we read during the write, we get the old value
960
c2 = self.get_existing_config()
961
self.assertEquals('1', c2.get_user_option('one'))
962
# Let the writing occur and ensure it occurred
965
# Now we get the updated value
966
c3 = self.get_existing_config()
967
self.assertEquals('c1', c3.get_user_option('one'))
970
class TestGetUserOptionAs(TestIniConfig):
972
def test_get_user_option_as_bool(self):
973
conf, parser = self.make_config_parser("""
976
an_invalid_bool = maybe
977
a_list = hmm, who knows ? # This is interpreted as a list !
979
get_bool = conf.get_user_option_as_bool
980
self.assertEqual(True, get_bool('a_true_bool'))
981
self.assertEqual(False, get_bool('a_false_bool'))
984
warnings.append(args[0] % args[1:])
985
self.overrideAttr(trace, 'warning', warning)
986
msg = 'Value "%s" is not a boolean for "%s"'
987
self.assertIs(None, get_bool('an_invalid_bool'))
988
self.assertEquals(msg % ('maybe', 'an_invalid_bool'), warnings[0])
990
self.assertIs(None, get_bool('not_defined_in_this_config'))
991
self.assertEquals([], warnings)
993
def test_get_user_option_as_list(self):
994
conf, parser = self.make_config_parser("""
999
get_list = conf.get_user_option_as_list
1000
self.assertEqual(['a', 'b', 'c'], get_list('a_list'))
1001
self.assertEqual(['1'], get_list('length_1'))
1002
self.assertEqual('x', conf.get_user_option('one_item'))
1003
# automatically cast to list
1004
self.assertEqual(['x'], get_list('one_item'))
1006
def test_get_user_option_as_int_from_SI(self):
1007
conf, parser = self.make_config_parser("""
1016
def get_si(s, default=None):
1017
return self.applyDeprecated(
1018
deprecated_in((2, 5, 0)),
1019
conf.get_user_option_as_int_from_SI, s, default)
1020
self.assertEqual(100, get_si('plain'))
1021
self.assertEqual(5000, get_si('si_k'))
1022
self.assertEqual(5000, get_si('si_kb'))
1023
self.assertEqual(5000000, get_si('si_m'))
1024
self.assertEqual(5000000, get_si('si_mb'))
1025
self.assertEqual(5000000000, get_si('si_g'))
1026
self.assertEqual(5000000000, get_si('si_gb'))
1027
self.assertEqual(None, get_si('non-exist'))
1028
self.assertEqual(42, get_si('non-exist-with-default', 42))
1031
class TestSupressWarning(TestIniConfig):
1033
def make_warnings_config(self, s):
1034
conf, parser = self.make_config_parser(s)
1035
return conf.suppress_warning
1037
def test_suppress_warning_unknown(self):
1038
suppress_warning = self.make_warnings_config('')
1039
self.assertEqual(False, suppress_warning('unknown_warning'))
1041
def test_suppress_warning_known(self):
1042
suppress_warning = self.make_warnings_config('suppress_warnings=a,b')
1043
self.assertEqual(False, suppress_warning('c'))
1044
self.assertEqual(True, suppress_warning('a'))
1045
self.assertEqual(True, suppress_warning('b'))
1048
class TestGetConfig(tests.TestCase):
1050
def test_constructs(self):
1051
my_config = config.GlobalConfig()
1053
def test_calls_read_filenames(self):
1054
# replace the class that is constructed, to check its parameters
1055
oldparserclass = config.ConfigObj
1056
config.ConfigObj = InstrumentedConfigObj
1057
my_config = config.GlobalConfig()
1059
parser = my_config._get_parser()
1061
config.ConfigObj = oldparserclass
1062
self.assertIsInstance(parser, InstrumentedConfigObj)
1063
self.assertEqual(parser._calls, [('__init__', config.config_filename(),
1067
class TestBranchConfig(tests.TestCaseWithTransport):
1069
def test_constructs(self):
1070
branch = FakeBranch()
1071
my_config = config.BranchConfig(branch)
1072
self.assertRaises(TypeError, config.BranchConfig)
1074
def test_get_location_config(self):
1075
branch = FakeBranch()
1076
my_config = config.BranchConfig(branch)
1077
location_config = my_config._get_location_config()
1078
self.assertEqual(branch.base, location_config.location)
1079
self.assertIs(location_config, my_config._get_location_config())
1081
def test_get_config(self):
1082
"""The Branch.get_config method works properly"""
1083
b = controldir.ControlDir.create_standalone_workingtree('.').branch
1084
my_config = b.get_config()
1085
self.assertIs(my_config.get_user_option('wacky'), None)
1086
my_config.set_user_option('wacky', 'unlikely')
1087
self.assertEqual(my_config.get_user_option('wacky'), 'unlikely')
1089
# Ensure we get the same thing if we start again
1090
b2 = branch.Branch.open('.')
1091
my_config2 = b2.get_config()
1092
self.assertEqual(my_config2.get_user_option('wacky'), 'unlikely')
1094
def test_has_explicit_nickname(self):
1095
b = self.make_branch('.')
1096
self.assertFalse(b.get_config().has_explicit_nickname())
1098
self.assertTrue(b.get_config().has_explicit_nickname())
1100
def test_config_url(self):
1101
"""The Branch.get_config will use section that uses a local url"""
1102
branch = self.make_branch('branch')
1103
self.assertEqual('branch', branch.nick)
1105
local_url = urlutils.local_path_to_url('branch')
1106
conf = config.LocationConfig.from_string(
1107
'[%s]\nnickname = foobar' % (local_url,),
1108
local_url, save=True)
1109
self.assertEqual('foobar', branch.nick)
1111
def test_config_local_path(self):
1112
"""The Branch.get_config will use a local system path"""
1113
branch = self.make_branch('branch')
1114
self.assertEqual('branch', branch.nick)
1116
local_path = osutils.getcwd().encode('utf8')
1117
conf = config.LocationConfig.from_string(
1118
'[%s/branch]\nnickname = barry' % (local_path,),
1119
'branch', save=True)
1120
self.assertEqual('barry', branch.nick)
1122
def test_config_creates_local(self):
1123
"""Creating a new entry in config uses a local path."""
1124
branch = self.make_branch('branch', format='knit')
1125
branch.set_push_location('http://foobar')
1126
local_path = osutils.getcwd().encode('utf8')
1127
# Surprisingly ConfigObj doesn't create a trailing newline
1128
self.check_file_contents(config.locations_config_filename(),
1130
'push_location = http://foobar\n'
1131
'push_location:policy = norecurse\n'
1134
def test_autonick_urlencoded(self):
1135
b = self.make_branch('!repo')
1136
self.assertEqual('!repo', b.get_config().get_nickname())
1138
def test_autonick_uses_branch_name(self):
1139
b = self.make_branch('foo', name='bar')
1140
self.assertEqual('bar', b.get_config().get_nickname())
1142
def test_warn_if_masked(self):
1145
warnings.append(args[0] % args[1:])
1146
self.overrideAttr(trace, 'warning', warning)
1148
def set_option(store, warn_masked=True):
1150
conf.set_user_option('example_option', repr(store), store=store,
1151
warn_masked=warn_masked)
1152
def assertWarning(warning):
1154
self.assertEqual(0, len(warnings))
1156
self.assertEqual(1, len(warnings))
1157
self.assertEqual(warning, warnings[0])
1158
branch = self.make_branch('.')
1159
conf = branch.get_config()
1160
set_option(config.STORE_GLOBAL)
1162
set_option(config.STORE_BRANCH)
1164
set_option(config.STORE_GLOBAL)
1165
assertWarning('Value "4" is masked by "3" from branch.conf')
1166
set_option(config.STORE_GLOBAL, warn_masked=False)
1168
set_option(config.STORE_LOCATION)
1170
set_option(config.STORE_BRANCH)
1171
assertWarning('Value "3" is masked by "0" from locations.conf')
1172
set_option(config.STORE_BRANCH, warn_masked=False)
1176
class TestGlobalConfigItems(tests.TestCaseInTempDir):
1178
def test_user_id(self):
1179
my_config = config.GlobalConfig.from_string(sample_config_text)
1180
self.assertEqual(u"Erik B\u00e5gfors <erik@bagfors.nu>",
1181
my_config._get_user_id())
1183
def test_absent_user_id(self):
1184
my_config = config.GlobalConfig()
1185
self.assertEqual(None, my_config._get_user_id())
1187
def test_signatures_always(self):
1188
my_config = config.GlobalConfig.from_string(sample_always_signatures)
1189
self.assertEqual(config.CHECK_NEVER,
1190
self.applyDeprecated(deprecated_in((2, 5, 0)),
1191
my_config.signature_checking))
1192
self.assertEqual(config.SIGN_ALWAYS,
1193
self.applyDeprecated(deprecated_in((2, 5, 0)),
1194
my_config.signing_policy))
1195
self.assertEqual(True,
1196
self.applyDeprecated(deprecated_in((2, 5, 0)),
1197
my_config.signature_needed))
1199
def test_signatures_if_possible(self):
1200
my_config = config.GlobalConfig.from_string(sample_maybe_signatures)
1201
self.assertEqual(config.CHECK_NEVER,
1202
self.applyDeprecated(deprecated_in((2, 5, 0)),
1203
my_config.signature_checking))
1204
self.assertEqual(config.SIGN_WHEN_REQUIRED,
1205
self.applyDeprecated(deprecated_in((2, 5, 0)),
1206
my_config.signing_policy))
1207
self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
1208
my_config.signature_needed))
1210
def test_signatures_ignore(self):
1211
my_config = config.GlobalConfig.from_string(sample_ignore_signatures)
1212
self.assertEqual(config.CHECK_ALWAYS,
1213
self.applyDeprecated(deprecated_in((2, 5, 0)),
1214
my_config.signature_checking))
1215
self.assertEqual(config.SIGN_NEVER,
1216
self.applyDeprecated(deprecated_in((2, 5, 0)),
1217
my_config.signing_policy))
1218
self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
1219
my_config.signature_needed))
1221
def _get_sample_config(self):
1222
my_config = config.GlobalConfig.from_string(sample_config_text)
1225
def test_gpg_signing_command(self):
1226
my_config = self._get_sample_config()
1227
self.assertEqual("gnome-gpg",
1228
self.applyDeprecated(
1229
deprecated_in((2, 5, 0)), my_config.gpg_signing_command))
1230
self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
1231
my_config.signature_needed))
1233
def test_gpg_signing_key(self):
1234
my_config = self._get_sample_config()
1235
self.assertEqual("DD4D5088",
1236
self.applyDeprecated(deprecated_in((2, 5, 0)),
1237
my_config.gpg_signing_key))
1239
def _get_empty_config(self):
1240
my_config = config.GlobalConfig()
1243
def test_gpg_signing_command_unset(self):
1244
my_config = self._get_empty_config()
1245
self.assertEqual("gpg",
1246
self.applyDeprecated(
1247
deprecated_in((2, 5, 0)), my_config.gpg_signing_command))
1249
def test_get_user_option_default(self):
1250
my_config = self._get_empty_config()
1251
self.assertEqual(None, my_config.get_user_option('no_option'))
1253
def test_get_user_option_global(self):
1254
my_config = self._get_sample_config()
1255
self.assertEqual("something",
1256
my_config.get_user_option('user_global_option'))
1258
def test_post_commit_default(self):
1259
my_config = self._get_sample_config()
1260
self.assertEqual(None,
1261
self.applyDeprecated(deprecated_in((2, 5, 0)),
1262
my_config.post_commit))
1264
def test_configured_logformat(self):
1265
my_config = self._get_sample_config()
1266
self.assertEqual("short",
1267
self.applyDeprecated(deprecated_in((2, 5, 0)),
1268
my_config.log_format))
1270
def test_configured_acceptable_keys(self):
1271
my_config = self._get_sample_config()
1272
self.assertEqual("amy",
1273
self.applyDeprecated(deprecated_in((2, 5, 0)),
1274
my_config.acceptable_keys))
1276
def test_configured_validate_signatures_in_log(self):
1277
my_config = self._get_sample_config()
1278
self.assertEqual(True, my_config.validate_signatures_in_log())
1280
def test_get_alias(self):
1281
my_config = self._get_sample_config()
1282
self.assertEqual('help', my_config.get_alias('h'))
1284
def test_get_aliases(self):
1285
my_config = self._get_sample_config()
1286
aliases = my_config.get_aliases()
1287
self.assertEqual(2, len(aliases))
1288
sorted_keys = sorted(aliases)
1289
self.assertEqual('help', aliases[sorted_keys[0]])
1290
self.assertEqual(sample_long_alias, aliases[sorted_keys[1]])
1292
def test_get_no_alias(self):
1293
my_config = self._get_sample_config()
1294
self.assertEqual(None, my_config.get_alias('foo'))
1296
def test_get_long_alias(self):
1297
my_config = self._get_sample_config()
1298
self.assertEqual(sample_long_alias, my_config.get_alias('ll'))
1300
def test_get_change_editor(self):
1301
my_config = self._get_sample_config()
1302
change_editor = my_config.get_change_editor('old', 'new')
1303
self.assertIs(diff.DiffFromTool, change_editor.__class__)
1304
self.assertEqual('vimdiff -of @new_path @old_path',
1305
' '.join(change_editor.command_template))
1307
def test_get_no_change_editor(self):
1308
my_config = self._get_empty_config()
1309
change_editor = my_config.get_change_editor('old', 'new')
1310
self.assertIs(None, change_editor)
1312
def test_get_merge_tools(self):
1313
conf = self._get_sample_config()
1314
tools = conf.get_merge_tools()
1315
self.log(repr(tools))
1317
{u'funkytool' : u'funkytool "arg with spaces" {this_temp}',
1318
u'sometool' : u'sometool {base} {this} {other} -o {result}',
1319
u'newtool' : u'"newtool with spaces" {this_temp}'},
1322
def test_get_merge_tools_empty(self):
1323
conf = self._get_empty_config()
1324
tools = conf.get_merge_tools()
1325
self.assertEqual({}, tools)
1327
def test_find_merge_tool(self):
1328
conf = self._get_sample_config()
1329
cmdline = conf.find_merge_tool('sometool')
1330
self.assertEqual('sometool {base} {this} {other} -o {result}', cmdline)
1332
def test_find_merge_tool_not_found(self):
1333
conf = self._get_sample_config()
1334
cmdline = conf.find_merge_tool('DOES NOT EXIST')
1335
self.assertIs(cmdline, None)
1337
def test_find_merge_tool_known(self):
1338
conf = self._get_empty_config()
1339
cmdline = conf.find_merge_tool('kdiff3')
1340
self.assertEquals('kdiff3 {base} {this} {other} -o {result}', cmdline)
1342
def test_find_merge_tool_override_known(self):
1343
conf = self._get_empty_config()
1344
conf.set_user_option('bzr.mergetool.kdiff3', 'kdiff3 blah')
1345
cmdline = conf.find_merge_tool('kdiff3')
1346
self.assertEqual('kdiff3 blah', cmdline)
1349
class TestGlobalConfigSavingOptions(tests.TestCaseInTempDir):
1351
def test_empty(self):
1352
my_config = config.GlobalConfig()
1353
self.assertEqual(0, len(my_config.get_aliases()))
1355
def test_set_alias(self):
1356
my_config = config.GlobalConfig()
1357
alias_value = 'commit --strict'
1358
my_config.set_alias('commit', alias_value)
1359
new_config = config.GlobalConfig()
1360
self.assertEqual(alias_value, new_config.get_alias('commit'))
1362
def test_remove_alias(self):
1363
my_config = config.GlobalConfig()
1364
my_config.set_alias('commit', 'commit --strict')
1365
# Now remove the alias again.
1366
my_config.unset_alias('commit')
1367
new_config = config.GlobalConfig()
1368
self.assertIs(None, new_config.get_alias('commit'))
1371
class TestLocationConfig(tests.TestCaseInTempDir, TestOptionsMixin):
1373
def test_constructs(self):
1374
my_config = config.LocationConfig('http://example.com')
1375
self.assertRaises(TypeError, config.LocationConfig)
1377
def test_branch_calls_read_filenames(self):
1378
# This is testing the correct file names are provided.
1379
# TODO: consolidate with the test for GlobalConfigs filename checks.
1381
# replace the class that is constructed, to check its parameters
1382
oldparserclass = config.ConfigObj
1383
config.ConfigObj = InstrumentedConfigObj
1385
my_config = config.LocationConfig('http://www.example.com')
1386
parser = my_config._get_parser()
1388
config.ConfigObj = oldparserclass
1389
self.assertIsInstance(parser, InstrumentedConfigObj)
1390
self.assertEqual(parser._calls,
1391
[('__init__', config.locations_config_filename(),
1394
def test_get_global_config(self):
1395
my_config = config.BranchConfig(FakeBranch('http://example.com'))
1396
global_config = my_config._get_global_config()
1397
self.assertIsInstance(global_config, config.GlobalConfig)
1398
self.assertIs(global_config, my_config._get_global_config())
1400
def assertLocationMatching(self, expected):
1401
self.assertEqual(expected,
1402
list(self.my_location_config._get_matching_sections()))
1404
def test__get_matching_sections_no_match(self):
1405
self.get_branch_config('/')
1406
self.assertLocationMatching([])
1408
def test__get_matching_sections_exact(self):
1409
self.get_branch_config('http://www.example.com')
1410
self.assertLocationMatching([('http://www.example.com', '')])
1412
def test__get_matching_sections_suffix_does_not(self):
1413
self.get_branch_config('http://www.example.com-com')
1414
self.assertLocationMatching([])
1416
def test__get_matching_sections_subdir_recursive(self):
1417
self.get_branch_config('http://www.example.com/com')
1418
self.assertLocationMatching([('http://www.example.com', 'com')])
1420
def test__get_matching_sections_ignoreparent(self):
1421
self.get_branch_config('http://www.example.com/ignoreparent')
1422
self.assertLocationMatching([('http://www.example.com/ignoreparent',
1425
def test__get_matching_sections_ignoreparent_subdir(self):
1426
self.get_branch_config(
1427
'http://www.example.com/ignoreparent/childbranch')
1428
self.assertLocationMatching([('http://www.example.com/ignoreparent',
1431
def test__get_matching_sections_subdir_trailing_slash(self):
1432
self.get_branch_config('/b')
1433
self.assertLocationMatching([('/b/', '')])
1435
def test__get_matching_sections_subdir_child(self):
1436
self.get_branch_config('/a/foo')
1437
self.assertLocationMatching([('/a/*', ''), ('/a/', 'foo')])
1439
def test__get_matching_sections_subdir_child_child(self):
1440
self.get_branch_config('/a/foo/bar')
1441
self.assertLocationMatching([('/a/*', 'bar'), ('/a/', 'foo/bar')])
1443
def test__get_matching_sections_trailing_slash_with_children(self):
1444
self.get_branch_config('/a/')
1445
self.assertLocationMatching([('/a/', '')])
1447
def test__get_matching_sections_explicit_over_glob(self):
1448
# XXX: 2006-09-08 jamesh
1449
# This test only passes because ord('c') > ord('*'). If there
1450
# was a config section for '/a/?', it would get precedence
1452
self.get_branch_config('/a/c')
1453
self.assertLocationMatching([('/a/c', ''), ('/a/*', ''), ('/a/', 'c')])
1455
def test__get_option_policy_normal(self):
1456
self.get_branch_config('http://www.example.com')
1458
self.my_location_config._get_config_policy(
1459
'http://www.example.com', 'normal_option'),
1462
def test__get_option_policy_norecurse(self):
1463
self.get_branch_config('http://www.example.com')
1465
self.my_location_config._get_option_policy(
1466
'http://www.example.com', 'norecurse_option'),
1467
config.POLICY_NORECURSE)
1468
# Test old recurse=False setting:
1470
self.my_location_config._get_option_policy(
1471
'http://www.example.com/norecurse', 'normal_option'),
1472
config.POLICY_NORECURSE)
1474
def test__get_option_policy_normal(self):
1475
self.get_branch_config('http://www.example.com')
1477
self.my_location_config._get_option_policy(
1478
'http://www.example.com', 'appendpath_option'),
1479
config.POLICY_APPENDPATH)
1481
def test__get_options_with_policy(self):
1482
self.get_branch_config('/dir/subdir',
1483
location_config="""\
1485
other_url = /other-dir
1486
other_url:policy = appendpath
1488
other_url = /other-subdir
1491
[(u'other_url', u'/other-subdir', u'/dir/subdir', 'locations'),
1492
(u'other_url', u'/other-dir', u'/dir', 'locations'),
1493
(u'other_url:policy', u'appendpath', u'/dir', 'locations')],
1494
self.my_location_config)
1496
def test_location_without_username(self):
1497
self.get_branch_config('http://www.example.com/ignoreparent')
1498
self.assertEqual(u'Erik B\u00e5gfors <erik@bagfors.nu>',
1499
self.my_config.username())
1501
def test_location_not_listed(self):
1502
"""Test that the global username is used when no location matches"""
1503
self.get_branch_config('/home/robertc/sources')
1504
self.assertEqual(u'Erik B\u00e5gfors <erik@bagfors.nu>',
1505
self.my_config.username())
1507
def test_overriding_location(self):
1508
self.get_branch_config('http://www.example.com/foo')
1509
self.assertEqual('Robert Collins <robertc@example.org>',
1510
self.my_config.username())
1512
def test_signatures_not_set(self):
1513
self.get_branch_config('http://www.example.com',
1514
global_config=sample_ignore_signatures)
1515
self.assertEqual(config.CHECK_ALWAYS,
1516
self.applyDeprecated(deprecated_in((2, 5, 0)),
1517
self.my_config.signature_checking))
1518
self.assertEqual(config.SIGN_NEVER,
1519
self.applyDeprecated(deprecated_in((2, 5, 0)),
1520
self.my_config.signing_policy))
1522
def test_signatures_never(self):
1523
self.get_branch_config('/a/c')
1524
self.assertEqual(config.CHECK_NEVER,
1525
self.applyDeprecated(deprecated_in((2, 5, 0)),
1526
self.my_config.signature_checking))
1528
def test_signatures_when_available(self):
1529
self.get_branch_config('/a/', global_config=sample_ignore_signatures)
1530
self.assertEqual(config.CHECK_IF_POSSIBLE,
1531
self.applyDeprecated(deprecated_in((2, 5, 0)),
1532
self.my_config.signature_checking))
1534
def test_signatures_always(self):
1535
self.get_branch_config('/b')
1536
self.assertEqual(config.CHECK_ALWAYS,
1537
self.applyDeprecated(deprecated_in((2, 5, 0)),
1538
self.my_config.signature_checking))
1540
def test_gpg_signing_command(self):
1541
self.get_branch_config('/b')
1542
self.assertEqual("gnome-gpg",
1543
self.applyDeprecated(deprecated_in((2, 5, 0)),
1544
self.my_config.gpg_signing_command))
1546
def test_gpg_signing_command_missing(self):
1547
self.get_branch_config('/a')
1548
self.assertEqual("false",
1549
self.applyDeprecated(deprecated_in((2, 5, 0)),
1550
self.my_config.gpg_signing_command))
1552
def test_gpg_signing_key(self):
1553
self.get_branch_config('/b')
1554
self.assertEqual("DD4D5088", self.applyDeprecated(deprecated_in((2, 5, 0)),
1555
self.my_config.gpg_signing_key))
1557
def test_gpg_signing_key_default(self):
1558
self.get_branch_config('/a')
1559
self.assertEqual("erik@bagfors.nu",
1560
self.applyDeprecated(deprecated_in((2, 5, 0)),
1561
self.my_config.gpg_signing_key))
1563
def test_get_user_option_global(self):
1564
self.get_branch_config('/a')
1565
self.assertEqual('something',
1566
self.my_config.get_user_option('user_global_option'))
1568
def test_get_user_option_local(self):
1569
self.get_branch_config('/a')
1570
self.assertEqual('local',
1571
self.my_config.get_user_option('user_local_option'))
1573
def test_get_user_option_appendpath(self):
1574
# returned as is for the base path:
1575
self.get_branch_config('http://www.example.com')
1576
self.assertEqual('append',
1577
self.my_config.get_user_option('appendpath_option'))
1578
# Extra path components get appended:
1579
self.get_branch_config('http://www.example.com/a/b/c')
1580
self.assertEqual('append/a/b/c',
1581
self.my_config.get_user_option('appendpath_option'))
1582
# Overriden for http://www.example.com/dir, where it is a
1584
self.get_branch_config('http://www.example.com/dir/a/b/c')
1585
self.assertEqual('normal',
1586
self.my_config.get_user_option('appendpath_option'))
1588
def test_get_user_option_norecurse(self):
1589
self.get_branch_config('http://www.example.com')
1590
self.assertEqual('norecurse',
1591
self.my_config.get_user_option('norecurse_option'))
1592
self.get_branch_config('http://www.example.com/dir')
1593
self.assertEqual(None,
1594
self.my_config.get_user_option('norecurse_option'))
1595
# http://www.example.com/norecurse is a recurse=False section
1596
# that redefines normal_option. Subdirectories do not pick up
1597
# this redefinition.
1598
self.get_branch_config('http://www.example.com/norecurse')
1599
self.assertEqual('norecurse',
1600
self.my_config.get_user_option('normal_option'))
1601
self.get_branch_config('http://www.example.com/norecurse/subdir')
1602
self.assertEqual('normal',
1603
self.my_config.get_user_option('normal_option'))
1605
def test_set_user_option_norecurse(self):
1606
self.get_branch_config('http://www.example.com')
1607
self.my_config.set_user_option('foo', 'bar',
1608
store=config.STORE_LOCATION_NORECURSE)
1610
self.my_location_config._get_option_policy(
1611
'http://www.example.com', 'foo'),
1612
config.POLICY_NORECURSE)
1614
def test_set_user_option_appendpath(self):
1615
self.get_branch_config('http://www.example.com')
1616
self.my_config.set_user_option('foo', 'bar',
1617
store=config.STORE_LOCATION_APPENDPATH)
1619
self.my_location_config._get_option_policy(
1620
'http://www.example.com', 'foo'),
1621
config.POLICY_APPENDPATH)
1623
def test_set_user_option_change_policy(self):
1624
self.get_branch_config('http://www.example.com')
1625
self.my_config.set_user_option('norecurse_option', 'normal',
1626
store=config.STORE_LOCATION)
1628
self.my_location_config._get_option_policy(
1629
'http://www.example.com', 'norecurse_option'),
1632
def test_set_user_option_recurse_false_section(self):
1633
# The following section has recurse=False set. The test is to
1634
# make sure that a normal option can be added to the section,
1635
# converting recurse=False to the norecurse policy.
1636
self.get_branch_config('http://www.example.com/norecurse')
1637
self.callDeprecated(['The recurse option is deprecated as of 0.14. '
1638
'The section "http://www.example.com/norecurse" '
1639
'has been converted to use policies.'],
1640
self.my_config.set_user_option,
1641
'foo', 'bar', store=config.STORE_LOCATION)
1643
self.my_location_config._get_option_policy(
1644
'http://www.example.com/norecurse', 'foo'),
1646
# The previously existing option is still norecurse:
1648
self.my_location_config._get_option_policy(
1649
'http://www.example.com/norecurse', 'normal_option'),
1650
config.POLICY_NORECURSE)
1652
def test_post_commit_default(self):
1653
self.get_branch_config('/a/c')
1654
self.assertEqual('bzrlib.tests.test_config.post_commit',
1655
self.applyDeprecated(deprecated_in((2, 5, 0)),
1656
self.my_config.post_commit))
1658
def get_branch_config(self, location, global_config=None,
1659
location_config=None):
1660
my_branch = FakeBranch(location)
1661
if global_config is None:
1662
global_config = sample_config_text
1663
if location_config is None:
1664
location_config = sample_branches_text
1666
my_global_config = config.GlobalConfig.from_string(global_config,
1668
my_location_config = config.LocationConfig.from_string(
1669
location_config, my_branch.base, save=True)
1670
my_config = config.BranchConfig(my_branch)
1671
self.my_config = my_config
1672
self.my_location_config = my_config._get_location_config()
1674
def test_set_user_setting_sets_and_saves(self):
1675
self.get_branch_config('/a/c')
1676
record = InstrumentedConfigObj("foo")
1677
self.my_location_config._parser = record
1679
self.callDeprecated(['The recurse option is deprecated as of '
1680
'0.14. The section "/a/c" has been '
1681
'converted to use policies.'],
1682
self.my_config.set_user_option,
1683
'foo', 'bar', store=config.STORE_LOCATION)
1684
self.assertEqual([('reload',),
1685
('__contains__', '/a/c'),
1686
('__contains__', '/a/c/'),
1687
('__setitem__', '/a/c', {}),
1688
('__getitem__', '/a/c'),
1689
('__setitem__', 'foo', 'bar'),
1690
('__getitem__', '/a/c'),
1691
('as_bool', 'recurse'),
1692
('__getitem__', '/a/c'),
1693
('__delitem__', 'recurse'),
1694
('__getitem__', '/a/c'),
1696
('__getitem__', '/a/c'),
1697
('__contains__', 'foo:policy'),
1701
def test_set_user_setting_sets_and_saves2(self):
1702
self.get_branch_config('/a/c')
1703
self.assertIs(self.my_config.get_user_option('foo'), None)
1704
self.my_config.set_user_option('foo', 'bar')
1706
self.my_config.branch.control_files.files['branch.conf'].strip(),
1708
self.assertEqual(self.my_config.get_user_option('foo'), 'bar')
1709
self.my_config.set_user_option('foo', 'baz',
1710
store=config.STORE_LOCATION)
1711
self.assertEqual(self.my_config.get_user_option('foo'), 'baz')
1712
self.my_config.set_user_option('foo', 'qux')
1713
self.assertEqual(self.my_config.get_user_option('foo'), 'baz')
1715
def test_get_bzr_remote_path(self):
1716
my_config = config.LocationConfig('/a/c')
1717
self.assertEqual('bzr', my_config.get_bzr_remote_path())
1718
my_config.set_user_option('bzr_remote_path', '/path-bzr')
1719
self.assertEqual('/path-bzr', my_config.get_bzr_remote_path())
1720
self.overrideEnv('BZR_REMOTE_PATH', '/environ-bzr')
1721
self.assertEqual('/environ-bzr', my_config.get_bzr_remote_path())
1724
precedence_global = 'option = global'
1725
precedence_branch = 'option = branch'
1726
precedence_location = """
1730
[http://example.com/specific]
1734
class TestBranchConfigItems(tests.TestCaseInTempDir):
1736
def get_branch_config(self, global_config=None, location=None,
1737
location_config=None, branch_data_config=None):
1738
my_branch = FakeBranch(location)
1739
if global_config is not None:
1740
my_global_config = config.GlobalConfig.from_string(global_config,
1742
if location_config is not None:
1743
my_location_config = config.LocationConfig.from_string(
1744
location_config, my_branch.base, save=True)
1745
my_config = config.BranchConfig(my_branch)
1746
if branch_data_config is not None:
1747
my_config.branch.control_files.files['branch.conf'] = \
1751
def test_user_id(self):
1752
branch = FakeBranch()
1753
my_config = config.BranchConfig(branch)
1754
self.assertIsNot(None, my_config.username())
1755
my_config.branch.control_files.files['email'] = "John"
1756
my_config.set_user_option('email',
1757
"Robert Collins <robertc@example.org>")
1758
self.assertEqual("Robert Collins <robertc@example.org>",
1759
my_config.username())
1761
def test_BZR_EMAIL_OVERRIDES(self):
1762
self.overrideEnv('BZR_EMAIL', "Robert Collins <robertc@example.org>")
1763
branch = FakeBranch()
1764
my_config = config.BranchConfig(branch)
1765
self.assertEqual("Robert Collins <robertc@example.org>",
1766
my_config.username())
1768
def test_signatures_forced(self):
1769
my_config = self.get_branch_config(
1770
global_config=sample_always_signatures)
1771
self.assertEqual(config.CHECK_NEVER,
1772
self.applyDeprecated(deprecated_in((2, 5, 0)),
1773
my_config.signature_checking))
1774
self.assertEqual(config.SIGN_ALWAYS,
1775
self.applyDeprecated(deprecated_in((2, 5, 0)),
1776
my_config.signing_policy))
1777
self.assertTrue(self.applyDeprecated(deprecated_in((2, 5, 0)),
1778
my_config.signature_needed))
1780
def test_signatures_forced_branch(self):
1781
my_config = self.get_branch_config(
1782
global_config=sample_ignore_signatures,
1783
branch_data_config=sample_always_signatures)
1784
self.assertEqual(config.CHECK_NEVER,
1785
self.applyDeprecated(deprecated_in((2, 5, 0)),
1786
my_config.signature_checking))
1787
self.assertEqual(config.SIGN_ALWAYS,
1788
self.applyDeprecated(deprecated_in((2, 5, 0)),
1789
my_config.signing_policy))
1790
self.assertTrue(self.applyDeprecated(deprecated_in((2, 5, 0)),
1791
my_config.signature_needed))
1793
def test_gpg_signing_command(self):
1794
my_config = self.get_branch_config(
1795
global_config=sample_config_text,
1796
# branch data cannot set gpg_signing_command
1797
branch_data_config="gpg_signing_command=pgp")
1798
self.assertEqual('gnome-gpg',
1799
self.applyDeprecated(deprecated_in((2, 5, 0)),
1800
my_config.gpg_signing_command))
1802
def test_get_user_option_global(self):
1803
my_config = self.get_branch_config(global_config=sample_config_text)
1804
self.assertEqual('something',
1805
my_config.get_user_option('user_global_option'))
1807
def test_post_commit_default(self):
1808
my_config = self.get_branch_config(global_config=sample_config_text,
1810
location_config=sample_branches_text)
1811
self.assertEqual(my_config.branch.base, '/a/c')
1812
self.assertEqual('bzrlib.tests.test_config.post_commit',
1813
self.applyDeprecated(deprecated_in((2, 5, 0)),
1814
my_config.post_commit))
1815
my_config.set_user_option('post_commit', 'rmtree_root')
1816
# post-commit is ignored when present in branch data
1817
self.assertEqual('bzrlib.tests.test_config.post_commit',
1818
self.applyDeprecated(deprecated_in((2, 5, 0)),
1819
my_config.post_commit))
1820
my_config.set_user_option('post_commit', 'rmtree_root',
1821
store=config.STORE_LOCATION)
1822
self.assertEqual('rmtree_root',
1823
self.applyDeprecated(deprecated_in((2, 5, 0)),
1824
my_config.post_commit))
1826
def test_config_precedence(self):
1827
# FIXME: eager test, luckily no persitent config file makes it fail
1829
my_config = self.get_branch_config(global_config=precedence_global)
1830
self.assertEqual(my_config.get_user_option('option'), 'global')
1831
my_config = self.get_branch_config(global_config=precedence_global,
1832
branch_data_config=precedence_branch)
1833
self.assertEqual(my_config.get_user_option('option'), 'branch')
1834
my_config = self.get_branch_config(
1835
global_config=precedence_global,
1836
branch_data_config=precedence_branch,
1837
location_config=precedence_location)
1838
self.assertEqual(my_config.get_user_option('option'), 'recurse')
1839
my_config = self.get_branch_config(
1840
global_config=precedence_global,
1841
branch_data_config=precedence_branch,
1842
location_config=precedence_location,
1843
location='http://example.com/specific')
1844
self.assertEqual(my_config.get_user_option('option'), 'exact')
1847
class TestMailAddressExtraction(tests.TestCase):
1849
def test_extract_email_address(self):
1850
self.assertEqual('jane@test.com',
1851
config.extract_email_address('Jane <jane@test.com>'))
1852
self.assertRaises(errors.NoEmailInUsername,
1853
config.extract_email_address, 'Jane Tester')
1855
def test_parse_username(self):
1856
self.assertEqual(('', 'jdoe@example.com'),
1857
config.parse_username('jdoe@example.com'))
1858
self.assertEqual(('', 'jdoe@example.com'),
1859
config.parse_username('<jdoe@example.com>'))
1860
self.assertEqual(('John Doe', 'jdoe@example.com'),
1861
config.parse_username('John Doe <jdoe@example.com>'))
1862
self.assertEqual(('John Doe', ''),
1863
config.parse_username('John Doe'))
1864
self.assertEqual(('John Doe', 'jdoe@example.com'),
1865
config.parse_username('John Doe jdoe@example.com'))
1867
class TestTreeConfig(tests.TestCaseWithTransport):
1869
def test_get_value(self):
1870
"""Test that retreiving a value from a section is possible"""
1871
branch = self.make_branch('.')
1872
tree_config = config.TreeConfig(branch)
1873
tree_config.set_option('value', 'key', 'SECTION')
1874
tree_config.set_option('value2', 'key2')
1875
tree_config.set_option('value3-top', 'key3')
1876
tree_config.set_option('value3-section', 'key3', 'SECTION')
1877
value = tree_config.get_option('key', 'SECTION')
1878
self.assertEqual(value, 'value')
1879
value = tree_config.get_option('key2')
1880
self.assertEqual(value, 'value2')
1881
self.assertEqual(tree_config.get_option('non-existant'), None)
1882
value = tree_config.get_option('non-existant', 'SECTION')
1883
self.assertEqual(value, None)
1884
value = tree_config.get_option('non-existant', default='default')
1885
self.assertEqual(value, 'default')
1886
self.assertEqual(tree_config.get_option('key2', 'NOSECTION'), None)
1887
value = tree_config.get_option('key2', 'NOSECTION', default='default')
1888
self.assertEqual(value, 'default')
1889
value = tree_config.get_option('key3')
1890
self.assertEqual(value, 'value3-top')
1891
value = tree_config.get_option('key3', 'SECTION')
1892
self.assertEqual(value, 'value3-section')
1895
class TestTransportConfig(tests.TestCaseWithTransport):
1897
def test_load_utf8(self):
1898
"""Ensure we can load an utf8-encoded file."""
1899
t = self.get_transport()
1900
unicode_user = u'b\N{Euro Sign}ar'
1901
unicode_content = u'user=%s' % (unicode_user,)
1902
utf8_content = unicode_content.encode('utf8')
1903
# Store the raw content in the config file
1904
t.put_bytes('foo.conf', utf8_content)
1905
conf = config.TransportConfig(t, 'foo.conf')
1906
self.assertEquals(unicode_user, conf.get_option('user'))
1908
def test_load_non_ascii(self):
1909
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
1910
t = self.get_transport()
1911
t.put_bytes('foo.conf', 'user=foo\n#\xff\n')
1912
conf = config.TransportConfig(t, 'foo.conf')
1913
self.assertRaises(errors.ConfigContentError, conf._get_configobj)
1915
def test_load_erroneous_content(self):
1916
"""Ensure we display a proper error on content that can't be parsed."""
1917
t = self.get_transport()
1918
t.put_bytes('foo.conf', '[open_section\n')
1919
conf = config.TransportConfig(t, 'foo.conf')
1920
self.assertRaises(errors.ParseConfigError, conf._get_configobj)
1922
def test_load_permission_denied(self):
1923
"""Ensure we get an empty config file if the file is inaccessible."""
1926
warnings.append(args[0] % args[1:])
1927
self.overrideAttr(trace, 'warning', warning)
1929
class DenyingTransport(object):
1931
def __init__(self, base):
1934
def get_bytes(self, relpath):
1935
raise errors.PermissionDenied(relpath, "")
1937
cfg = config.TransportConfig(
1938
DenyingTransport("nonexisting://"), 'control.conf')
1939
self.assertIs(None, cfg.get_option('non-existant', 'SECTION'))
1942
[u'Permission denied while trying to open configuration file '
1943
u'nonexisting:///control.conf.'])
1945
def test_get_value(self):
1946
"""Test that retreiving a value from a section is possible"""
1947
bzrdir_config = config.TransportConfig(self.get_transport('.'),
1949
bzrdir_config.set_option('value', 'key', 'SECTION')
1950
bzrdir_config.set_option('value2', 'key2')
1951
bzrdir_config.set_option('value3-top', 'key3')
1952
bzrdir_config.set_option('value3-section', 'key3', 'SECTION')
1953
value = bzrdir_config.get_option('key', 'SECTION')
1954
self.assertEqual(value, 'value')
1955
value = bzrdir_config.get_option('key2')
1956
self.assertEqual(value, 'value2')
1957
self.assertEqual(bzrdir_config.get_option('non-existant'), None)
1958
value = bzrdir_config.get_option('non-existant', 'SECTION')
1959
self.assertEqual(value, None)
1960
value = bzrdir_config.get_option('non-existant', default='default')
1961
self.assertEqual(value, 'default')
1962
self.assertEqual(bzrdir_config.get_option('key2', 'NOSECTION'), None)
1963
value = bzrdir_config.get_option('key2', 'NOSECTION',
1965
self.assertEqual(value, 'default')
1966
value = bzrdir_config.get_option('key3')
1967
self.assertEqual(value, 'value3-top')
1968
value = bzrdir_config.get_option('key3', 'SECTION')
1969
self.assertEqual(value, 'value3-section')
1971
def test_set_unset_default_stack_on(self):
1972
my_dir = self.make_bzrdir('.')
1973
bzrdir_config = config.BzrDirConfig(my_dir)
1974
self.assertIs(None, bzrdir_config.get_default_stack_on())
1975
bzrdir_config.set_default_stack_on('Foo')
1976
self.assertEqual('Foo', bzrdir_config._config.get_option(
1977
'default_stack_on'))
1978
self.assertEqual('Foo', bzrdir_config.get_default_stack_on())
1979
bzrdir_config.set_default_stack_on(None)
1980
self.assertIs(None, bzrdir_config.get_default_stack_on())
1983
class TestOldConfigHooks(tests.TestCaseWithTransport):
1986
super(TestOldConfigHooks, self).setUp()
1987
create_configs_with_file_option(self)
1989
def assertGetHook(self, conf, name, value):
1993
config.OldConfigHooks.install_named_hook('get', hook, None)
1995
config.OldConfigHooks.uninstall_named_hook, 'get', None)
1996
self.assertLength(0, calls)
1997
actual_value = conf.get_user_option(name)
1998
self.assertEquals(value, actual_value)
1999
self.assertLength(1, calls)
2000
self.assertEquals((conf, name, value), calls[0])
2002
def test_get_hook_bazaar(self):
2003
self.assertGetHook(self.bazaar_config, 'file', 'bazaar')
2005
def test_get_hook_locations(self):
2006
self.assertGetHook(self.locations_config, 'file', 'locations')
2008
def test_get_hook_branch(self):
2009
# Since locations masks branch, we define a different option
2010
self.branch_config.set_user_option('file2', 'branch')
2011
self.assertGetHook(self.branch_config, 'file2', 'branch')
2013
def assertSetHook(self, conf, name, value):
2017
config.OldConfigHooks.install_named_hook('set', hook, None)
2019
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2020
self.assertLength(0, calls)
2021
conf.set_user_option(name, value)
2022
self.assertLength(1, calls)
2023
# We can't assert the conf object below as different configs use
2024
# different means to implement set_user_option and we care only about
2026
self.assertEquals((name, value), calls[0][1:])
2028
def test_set_hook_bazaar(self):
2029
self.assertSetHook(self.bazaar_config, 'foo', 'bazaar')
2031
def test_set_hook_locations(self):
2032
self.assertSetHook(self.locations_config, 'foo', 'locations')
2034
def test_set_hook_branch(self):
2035
self.assertSetHook(self.branch_config, 'foo', 'branch')
2037
def assertRemoveHook(self, conf, name, section_name=None):
2041
config.OldConfigHooks.install_named_hook('remove', hook, None)
2043
config.OldConfigHooks.uninstall_named_hook, 'remove', None)
2044
self.assertLength(0, calls)
2045
conf.remove_user_option(name, section_name)
2046
self.assertLength(1, calls)
2047
# We can't assert the conf object below as different configs use
2048
# different means to implement remove_user_option and we care only about
2050
self.assertEquals((name,), calls[0][1:])
2052
def test_remove_hook_bazaar(self):
2053
self.assertRemoveHook(self.bazaar_config, 'file')
2055
def test_remove_hook_locations(self):
2056
self.assertRemoveHook(self.locations_config, 'file',
2057
self.locations_config.location)
2059
def test_remove_hook_branch(self):
2060
self.assertRemoveHook(self.branch_config, 'file')
2062
def assertLoadHook(self, name, conf_class, *conf_args):
2066
config.OldConfigHooks.install_named_hook('load', hook, None)
2068
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2069
self.assertLength(0, calls)
2071
conf = conf_class(*conf_args)
2072
# Access an option to trigger a load
2073
conf.get_user_option(name)
2074
self.assertLength(1, calls)
2075
# Since we can't assert about conf, we just use the number of calls ;-/
2077
def test_load_hook_bazaar(self):
2078
self.assertLoadHook('file', config.GlobalConfig)
2080
def test_load_hook_locations(self):
2081
self.assertLoadHook('file', config.LocationConfig, self.tree.basedir)
2083
def test_load_hook_branch(self):
2084
self.assertLoadHook('file', config.BranchConfig, self.tree.branch)
2086
def assertSaveHook(self, conf):
2090
config.OldConfigHooks.install_named_hook('save', hook, None)
2092
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2093
self.assertLength(0, calls)
2094
# Setting an option triggers a save
2095
conf.set_user_option('foo', 'bar')
2096
self.assertLength(1, calls)
2097
# Since we can't assert about conf, we just use the number of calls ;-/
2099
def test_save_hook_bazaar(self):
2100
self.assertSaveHook(self.bazaar_config)
2102
def test_save_hook_locations(self):
2103
self.assertSaveHook(self.locations_config)
2105
def test_save_hook_branch(self):
2106
self.assertSaveHook(self.branch_config)
2109
class TestOldConfigHooksForRemote(tests.TestCaseWithTransport):
2110
"""Tests config hooks for remote configs.
2112
No tests for the remove hook as this is not implemented there.
2116
super(TestOldConfigHooksForRemote, self).setUp()
2117
self.transport_server = test_server.SmartTCPServer_for_testing
2118
create_configs_with_file_option(self)
2120
def assertGetHook(self, conf, name, value):
2124
config.OldConfigHooks.install_named_hook('get', hook, None)
2126
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2127
self.assertLength(0, calls)
2128
actual_value = conf.get_option(name)
2129
self.assertEquals(value, actual_value)
2130
self.assertLength(1, calls)
2131
self.assertEquals((conf, name, value), calls[0])
2133
def test_get_hook_remote_branch(self):
2134
remote_branch = branch.Branch.open(self.get_url('tree'))
2135
self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
2137
def test_get_hook_remote_bzrdir(self):
2138
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2139
conf = remote_bzrdir._get_config()
2140
conf.set_option('remotedir', 'file')
2141
self.assertGetHook(conf, 'file', 'remotedir')
2143
def assertSetHook(self, conf, name, value):
2147
config.OldConfigHooks.install_named_hook('set', hook, None)
2149
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2150
self.assertLength(0, calls)
2151
conf.set_option(value, name)
2152
self.assertLength(1, calls)
2153
# We can't assert the conf object below as different configs use
2154
# different means to implement set_user_option and we care only about
2156
self.assertEquals((name, value), calls[0][1:])
2158
def test_set_hook_remote_branch(self):
2159
remote_branch = branch.Branch.open(self.get_url('tree'))
2160
self.addCleanup(remote_branch.lock_write().unlock)
2161
self.assertSetHook(remote_branch._get_config(), 'file', 'remote')
2163
def test_set_hook_remote_bzrdir(self):
2164
remote_branch = branch.Branch.open(self.get_url('tree'))
2165
self.addCleanup(remote_branch.lock_write().unlock)
2166
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2167
self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
2169
def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
2173
config.OldConfigHooks.install_named_hook('load', hook, None)
2175
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2176
self.assertLength(0, calls)
2178
conf = conf_class(*conf_args)
2179
# Access an option to trigger a load
2180
conf.get_option(name)
2181
self.assertLength(expected_nb_calls, calls)
2182
# Since we can't assert about conf, we just use the number of calls ;-/
2184
def test_load_hook_remote_branch(self):
2185
remote_branch = branch.Branch.open(self.get_url('tree'))
2186
self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
2188
def test_load_hook_remote_bzrdir(self):
2189
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2190
# The config file doesn't exist, set an option to force its creation
2191
conf = remote_bzrdir._get_config()
2192
conf.set_option('remotedir', 'file')
2193
# We get one call for the server and one call for the client, this is
2194
# caused by the differences in implementations betwen
2195
# SmartServerBzrDirRequestConfigFile (in smart/bzrdir.py) and
2196
# SmartServerBranchGetConfigFile (in smart/branch.py)
2197
self.assertLoadHook(2 ,'file', remote.RemoteBzrDirConfig, remote_bzrdir)
2199
def assertSaveHook(self, conf):
2203
config.OldConfigHooks.install_named_hook('save', hook, None)
2205
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2206
self.assertLength(0, calls)
2207
# Setting an option triggers a save
2208
conf.set_option('foo', 'bar')
2209
self.assertLength(1, calls)
2210
# Since we can't assert about conf, we just use the number of calls ;-/
2212
def test_save_hook_remote_branch(self):
2213
remote_branch = branch.Branch.open(self.get_url('tree'))
2214
self.addCleanup(remote_branch.lock_write().unlock)
2215
self.assertSaveHook(remote_branch._get_config())
2217
def test_save_hook_remote_bzrdir(self):
2218
remote_branch = branch.Branch.open(self.get_url('tree'))
2219
self.addCleanup(remote_branch.lock_write().unlock)
2220
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2221
self.assertSaveHook(remote_bzrdir._get_config())
2224
class TestOption(tests.TestCase):
2226
def test_default_value(self):
2227
opt = config.Option('foo', default='bar')
2228
self.assertEquals('bar', opt.get_default())
2230
def test_callable_default_value(self):
2231
def bar_as_unicode():
2233
opt = config.Option('foo', default=bar_as_unicode)
2234
self.assertEquals('bar', opt.get_default())
2236
def test_default_value_from_env(self):
2237
opt = config.Option('foo', default='bar', default_from_env=['FOO'])
2238
self.overrideEnv('FOO', 'quux')
2239
# Env variable provides a default taking over the option one
2240
self.assertEquals('quux', opt.get_default())
2242
def test_first_default_value_from_env_wins(self):
2243
opt = config.Option('foo', default='bar',
2244
default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
2245
self.overrideEnv('FOO', 'foo')
2246
self.overrideEnv('BAZ', 'baz')
2247
# The first env var set wins
2248
self.assertEquals('foo', opt.get_default())
2250
def test_not_supported_list_default_value(self):
2251
self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
2253
def test_not_supported_object_default_value(self):
2254
self.assertRaises(AssertionError, config.Option, 'foo',
2257
def test_not_supported_callable_default_value_not_unicode(self):
2258
def bar_not_unicode():
2260
opt = config.Option('foo', default=bar_not_unicode)
2261
self.assertRaises(AssertionError, opt.get_default)
2263
def test_get_help_topic(self):
2264
opt = config.Option('foo')
2265
self.assertEquals('foo', opt.get_help_topic())
2268
class TestOptionConverterMixin(object):
2270
def assertConverted(self, expected, opt, value):
2271
self.assertEquals(expected, opt.convert_from_unicode(None, value))
2273
def assertWarns(self, opt, value):
2276
warnings.append(args[0] % args[1:])
2277
self.overrideAttr(trace, 'warning', warning)
2278
self.assertEquals(None, opt.convert_from_unicode(None, value))
2279
self.assertLength(1, warnings)
2281
'Value "%s" is not valid for "%s"' % (value, opt.name),
2284
def assertErrors(self, opt, value):
2285
self.assertRaises(errors.ConfigOptionValueError,
2286
opt.convert_from_unicode, None, value)
2288
def assertConvertInvalid(self, opt, invalid_value):
2290
self.assertEquals(None, opt.convert_from_unicode(None, invalid_value))
2291
opt.invalid = 'warning'
2292
self.assertWarns(opt, invalid_value)
2293
opt.invalid = 'error'
2294
self.assertErrors(opt, invalid_value)
2297
class TestOptionWithBooleanConverter(tests.TestCase, TestOptionConverterMixin):
2299
def get_option(self):
2300
return config.Option('foo', help='A boolean.',
2301
from_unicode=config.bool_from_store)
2303
def test_convert_invalid(self):
2304
opt = self.get_option()
2305
# A string that is not recognized as a boolean
2306
self.assertConvertInvalid(opt, u'invalid-boolean')
2307
# A list of strings is never recognized as a boolean
2308
self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
2310
def test_convert_valid(self):
2311
opt = self.get_option()
2312
self.assertConverted(True, opt, u'True')
2313
self.assertConverted(True, opt, u'1')
2314
self.assertConverted(False, opt, u'False')
2317
class TestOptionWithIntegerConverter(tests.TestCase, TestOptionConverterMixin):
2319
def get_option(self):
2320
return config.Option('foo', help='An integer.',
2321
from_unicode=config.int_from_store)
2323
def test_convert_invalid(self):
2324
opt = self.get_option()
2325
# A string that is not recognized as an integer
2326
self.assertConvertInvalid(opt, u'forty-two')
2327
# A list of strings is never recognized as an integer
2328
self.assertConvertInvalid(opt, [u'a', u'list'])
2330
def test_convert_valid(self):
2331
opt = self.get_option()
2332
self.assertConverted(16, opt, u'16')
2335
class TestOptionWithSIUnitConverter(tests.TestCase, TestOptionConverterMixin):
2337
def get_option(self):
2338
return config.Option('foo', help='An integer in SI units.',
2339
from_unicode=config.int_SI_from_store)
2341
def test_convert_invalid(self):
2342
opt = self.get_option()
2343
self.assertConvertInvalid(opt, u'not-a-unit')
2344
self.assertConvertInvalid(opt, u'Gb') # Forgot the int
2345
self.assertConvertInvalid(opt, u'1b') # Forgot the unit
2346
self.assertConvertInvalid(opt, u'1GG')
2347
self.assertConvertInvalid(opt, u'1Mbb')
2348
self.assertConvertInvalid(opt, u'1MM')
2350
def test_convert_valid(self):
2351
opt = self.get_option()
2352
self.assertConverted(int(5e3), opt, u'5kb')
2353
self.assertConverted(int(5e6), opt, u'5M')
2354
self.assertConverted(int(5e6), opt, u'5MB')
2355
self.assertConverted(int(5e9), opt, u'5g')
2356
self.assertConverted(int(5e9), opt, u'5gB')
2357
self.assertConverted(100, opt, u'100')
2360
class TestListOption(tests.TestCase, TestOptionConverterMixin):
2362
def get_option(self):
2363
return config.ListOption('foo', help='A list.')
2365
def test_convert_invalid(self):
2366
opt = self.get_option()
2367
# We don't even try to convert a list into a list, we only expect
2369
self.assertConvertInvalid(opt, [1])
2370
# No string is invalid as all forms can be converted to a list
2372
def test_convert_valid(self):
2373
opt = self.get_option()
2374
# An empty string is an empty list
2375
self.assertConverted([], opt, '') # Using a bare str() just in case
2376
self.assertConverted([], opt, u'')
2378
self.assertConverted([u'True'], opt, u'True')
2380
self.assertConverted([u'42'], opt, u'42')
2382
self.assertConverted([u'bar'], opt, u'bar')
2385
class TestRegistryOption(tests.TestCase, TestOptionConverterMixin):
2387
def get_option(self, registry):
2388
return config.RegistryOption('foo', registry,
2389
help='A registry option.')
2391
def test_convert_invalid(self):
2392
registry = _mod_registry.Registry()
2393
opt = self.get_option(registry)
2394
self.assertConvertInvalid(opt, [1])
2395
self.assertConvertInvalid(opt, u"notregistered")
2397
def test_convert_valid(self):
2398
registry = _mod_registry.Registry()
2399
registry.register("someval", 1234)
2400
opt = self.get_option(registry)
2401
# Using a bare str() just in case
2402
self.assertConverted(1234, opt, "someval")
2403
self.assertConverted(1234, opt, u'someval')
2404
self.assertConverted(None, opt, None)
2406
def test_help(self):
2407
registry = _mod_registry.Registry()
2408
registry.register("someval", 1234, help="some option")
2409
registry.register("dunno", 1234, help="some other option")
2410
opt = self.get_option(registry)
2412
'A registry option.\n'
2414
'The following values are supported:\n'
2415
' dunno - some other option\n'
2416
' someval - some option\n',
2419
def test_get_help_text(self):
2420
registry = _mod_registry.Registry()
2421
registry.register("someval", 1234, help="some option")
2422
registry.register("dunno", 1234, help="some other option")
2423
opt = self.get_option(registry)
2425
'A registry option.\n'
2427
'The following values are supported:\n'
2428
' dunno - some other option\n'
2429
' someval - some option\n',
2430
opt.get_help_text())
2433
class TestOptionRegistry(tests.TestCase):
2436
super(TestOptionRegistry, self).setUp()
2437
# Always start with an empty registry
2438
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2439
self.registry = config.option_registry
2441
def test_register(self):
2442
opt = config.Option('foo')
2443
self.registry.register(opt)
2444
self.assertIs(opt, self.registry.get('foo'))
2446
def test_registered_help(self):
2447
opt = config.Option('foo', help='A simple option')
2448
self.registry.register(opt)
2449
self.assertEquals('A simple option', self.registry.get_help('foo'))
2451
lazy_option = config.Option('lazy_foo', help='Lazy help')
2453
def test_register_lazy(self):
2454
self.registry.register_lazy('lazy_foo', self.__module__,
2455
'TestOptionRegistry.lazy_option')
2456
self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
2458
def test_registered_lazy_help(self):
2459
self.registry.register_lazy('lazy_foo', self.__module__,
2460
'TestOptionRegistry.lazy_option')
2461
self.assertEquals('Lazy help', self.registry.get_help('lazy_foo'))
2464
class TestRegisteredOptions(tests.TestCase):
2465
"""All registered options should verify some constraints."""
2467
scenarios = [(key, {'option_name': key, 'option': option}) for key, option
2468
in config.option_registry.iteritems()]
2471
super(TestRegisteredOptions, self).setUp()
2472
self.registry = config.option_registry
2474
def test_proper_name(self):
2475
# An option should be registered under its own name, this can't be
2476
# checked at registration time for the lazy ones.
2477
self.assertEquals(self.option_name, self.option.name)
2479
def test_help_is_set(self):
2480
option_help = self.registry.get_help(self.option_name)
2481
# Come on, think about the user, he really wants to know what the
2483
self.assertIsNot(None, option_help)
2484
self.assertNotEquals('', option_help)
2487
class TestSection(tests.TestCase):
2489
# FIXME: Parametrize so that all sections produced by Stores run these
2490
# tests -- vila 2011-04-01
2492
def test_get_a_value(self):
2493
a_dict = dict(foo='bar')
2494
section = config.Section('myID', a_dict)
2495
self.assertEquals('bar', section.get('foo'))
2497
def test_get_unknown_option(self):
2499
section = config.Section(None, a_dict)
2500
self.assertEquals('out of thin air',
2501
section.get('foo', 'out of thin air'))
2503
def test_options_is_shared(self):
2505
section = config.Section(None, a_dict)
2506
self.assertIs(a_dict, section.options)
2509
class TestMutableSection(tests.TestCase):
2511
scenarios = [('mutable',
2513
lambda opts: config.MutableSection('myID', opts)},),
2517
a_dict = dict(foo='bar')
2518
section = self.get_section(a_dict)
2519
section.set('foo', 'new_value')
2520
self.assertEquals('new_value', section.get('foo'))
2521
# The change appears in the shared section
2522
self.assertEquals('new_value', a_dict.get('foo'))
2523
# We keep track of the change
2524
self.assertTrue('foo' in section.orig)
2525
self.assertEquals('bar', section.orig.get('foo'))
2527
def test_set_preserve_original_once(self):
2528
a_dict = dict(foo='bar')
2529
section = self.get_section(a_dict)
2530
section.set('foo', 'first_value')
2531
section.set('foo', 'second_value')
2532
# We keep track of the original value
2533
self.assertTrue('foo' in section.orig)
2534
self.assertEquals('bar', section.orig.get('foo'))
2536
def test_remove(self):
2537
a_dict = dict(foo='bar')
2538
section = self.get_section(a_dict)
2539
section.remove('foo')
2540
# We get None for unknown options via the default value
2541
self.assertEquals(None, section.get('foo'))
2542
# Or we just get the default value
2543
self.assertEquals('unknown', section.get('foo', 'unknown'))
2544
self.assertFalse('foo' in section.options)
2545
# We keep track of the deletion
2546
self.assertTrue('foo' in section.orig)
2547
self.assertEquals('bar', section.orig.get('foo'))
2549
def test_remove_new_option(self):
2551
section = self.get_section(a_dict)
2552
section.set('foo', 'bar')
2553
section.remove('foo')
2554
self.assertFalse('foo' in section.options)
2555
# The option didn't exist initially so it we need to keep track of it
2556
# with a special value
2557
self.assertTrue('foo' in section.orig)
2558
self.assertEquals(config._NewlyCreatedOption, section.orig['foo'])
2561
class TestCommandLineStore(tests.TestCase):
2564
super(TestCommandLineStore, self).setUp()
2565
self.store = config.CommandLineStore()
2566
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2568
def get_section(self):
2569
"""Get the unique section for the command line overrides."""
2570
sections = list(self.store.get_sections())
2571
self.assertLength(1, sections)
2572
store, section = sections[0]
2573
self.assertEquals(self.store, store)
2576
def test_no_override(self):
2577
self.store._from_cmdline([])
2578
section = self.get_section()
2579
self.assertLength(0, list(section.iter_option_names()))
2581
def test_simple_override(self):
2582
self.store._from_cmdline(['a=b'])
2583
section = self.get_section()
2584
self.assertEqual('b', section.get('a'))
2586
def test_list_override(self):
2587
opt = config.ListOption('l')
2588
config.option_registry.register(opt)
2589
self.store._from_cmdline(['l=1,2,3'])
2590
val = self.get_section().get('l')
2591
self.assertEqual('1,2,3', val)
2592
# Reminder: lists should be registered as such explicitely, otherwise
2593
# the conversion needs to be done afterwards.
2594
self.assertEqual(['1', '2', '3'],
2595
opt.convert_from_unicode(self.store, val))
2597
def test_multiple_overrides(self):
2598
self.store._from_cmdline(['a=b', 'x=y'])
2599
section = self.get_section()
2600
self.assertEquals('b', section.get('a'))
2601
self.assertEquals('y', section.get('x'))
2603
def test_wrong_syntax(self):
2604
self.assertRaises(errors.BzrCommandError,
2605
self.store._from_cmdline, ['a=b', 'c'])
2607
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
2609
scenarios = [(key, {'get_store': builder}) for key, builder
2610
in config.test_store_builder_registry.iteritems()] + [
2611
('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
2614
store = self.get_store(self)
2615
if type(store) == config.TransportIniFileStore:
2616
raise tests.TestNotApplicable(
2617
"%s is not a concrete Store implementation"
2618
" so it doesn't need an id" % (store.__class__.__name__,))
2619
self.assertIsNot(None, store.id)
2622
class TestStore(tests.TestCaseWithTransport):
2624
def assertSectionContent(self, expected, (store, section)):
2625
"""Assert that some options have the proper values in a section."""
2626
expected_name, expected_options = expected
2627
self.assertEquals(expected_name, section.id)
2630
dict([(k, section.get(k)) for k in expected_options.keys()]))
2633
class TestReadonlyStore(TestStore):
2635
scenarios = [(key, {'get_store': builder}) for key, builder
2636
in config.test_store_builder_registry.iteritems()]
2638
def test_building_delays_load(self):
2639
store = self.get_store(self)
2640
self.assertEquals(False, store.is_loaded())
2641
store._load_from_string('')
2642
self.assertEquals(True, store.is_loaded())
2644
def test_get_no_sections_for_empty(self):
2645
store = self.get_store(self)
2646
store._load_from_string('')
2647
self.assertEquals([], list(store.get_sections()))
2649
def test_get_default_section(self):
2650
store = self.get_store(self)
2651
store._load_from_string('foo=bar')
2652
sections = list(store.get_sections())
2653
self.assertLength(1, sections)
2654
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2656
def test_get_named_section(self):
2657
store = self.get_store(self)
2658
store._load_from_string('[baz]\nfoo=bar')
2659
sections = list(store.get_sections())
2660
self.assertLength(1, sections)
2661
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2663
def test_load_from_string_fails_for_non_empty_store(self):
2664
store = self.get_store(self)
2665
store._load_from_string('foo=bar')
2666
self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
2669
class TestStoreQuoting(TestStore):
2671
scenarios = [(key, {'get_store': builder}) for key, builder
2672
in config.test_store_builder_registry.iteritems()]
2675
super(TestStoreQuoting, self).setUp()
2676
self.store = self.get_store(self)
2677
# We need a loaded store but any content will do
2678
self.store._load_from_string('')
2680
def assertIdempotent(self, s):
2681
"""Assert that quoting an unquoted string is a no-op and vice-versa.
2683
What matters here is that option values, as they appear in a store, can
2684
be safely round-tripped out of the store and back.
2686
:param s: A string, quoted if required.
2688
self.assertEquals(s, self.store.quote(self.store.unquote(s)))
2689
self.assertEquals(s, self.store.unquote(self.store.quote(s)))
2691
def test_empty_string(self):
2692
if isinstance(self.store, config.IniFileStore):
2693
# configobj._quote doesn't handle empty values
2694
self.assertRaises(AssertionError,
2695
self.assertIdempotent, '')
2697
self.assertIdempotent('')
2698
# But quoted empty strings are ok
2699
self.assertIdempotent('""')
2701
def test_embedded_spaces(self):
2702
self.assertIdempotent('" a b c "')
2704
def test_embedded_commas(self):
2705
self.assertIdempotent('" a , b c "')
2707
def test_simple_comma(self):
2708
if isinstance(self.store, config.IniFileStore):
2709
# configobj requires that lists are special-cased
2710
self.assertRaises(AssertionError,
2711
self.assertIdempotent, ',')
2713
self.assertIdempotent(',')
2714
# When a single comma is required, quoting is also required
2715
self.assertIdempotent('","')
2717
def test_list(self):
2718
if isinstance(self.store, config.IniFileStore):
2719
# configobj requires that lists are special-cased
2720
self.assertRaises(AssertionError,
2721
self.assertIdempotent, 'a,b')
2723
self.assertIdempotent('a,b')
2726
class TestDictFromStore(tests.TestCase):
2728
def test_unquote_not_string(self):
2729
conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
2730
value = conf.get('a_section')
2731
# Urgh, despite 'conf' asking for the no-name section, we get the
2732
# content of another section as a dict o_O
2733
self.assertEquals({'a': '1'}, value)
2734
unquoted = conf.store.unquote(value)
2735
# Which cannot be unquoted but shouldn't crash either (the use cases
2736
# are getting the value or displaying it. In the later case, '%s' will
2738
self.assertEquals({'a': '1'}, unquoted)
2739
self.assertEquals("{u'a': u'1'}", '%s' % (unquoted,))
2742
class TestIniFileStoreContent(tests.TestCaseWithTransport):
2743
"""Simulate loading a config store with content of various encodings.
2745
All files produced by bzr are in utf8 content.
2747
Users may modify them manually and end up with a file that can't be
2748
loaded. We need to issue proper error messages in this case.
2751
invalid_utf8_char = '\xff'
2753
def test_load_utf8(self):
2754
"""Ensure we can load an utf8-encoded file."""
2755
t = self.get_transport()
2756
# From http://pad.lv/799212
2757
unicode_user = u'b\N{Euro Sign}ar'
2758
unicode_content = u'user=%s' % (unicode_user,)
2759
utf8_content = unicode_content.encode('utf8')
2760
# Store the raw content in the config file
2761
t.put_bytes('foo.conf', utf8_content)
2762
store = config.TransportIniFileStore(t, 'foo.conf')
2764
stack = config.Stack([store.get_sections], store)
2765
self.assertEquals(unicode_user, stack.get('user'))
2767
def test_load_non_ascii(self):
2768
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2769
t = self.get_transport()
2770
t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
2771
store = config.TransportIniFileStore(t, 'foo.conf')
2772
self.assertRaises(errors.ConfigContentError, store.load)
2774
def test_load_erroneous_content(self):
2775
"""Ensure we display a proper error on content that can't be parsed."""
2776
t = self.get_transport()
2777
t.put_bytes('foo.conf', '[open_section\n')
2778
store = config.TransportIniFileStore(t, 'foo.conf')
2779
self.assertRaises(errors.ParseConfigError, store.load)
2781
def test_load_permission_denied(self):
2782
"""Ensure we get warned when trying to load an inaccessible file."""
2785
warnings.append(args[0] % args[1:])
2786
self.overrideAttr(trace, 'warning', warning)
2788
t = self.get_transport()
2790
def get_bytes(relpath):
2791
raise errors.PermissionDenied(relpath, "")
2792
t.get_bytes = get_bytes
2793
store = config.TransportIniFileStore(t, 'foo.conf')
2794
self.assertRaises(errors.PermissionDenied, store.load)
2797
[u'Permission denied while trying to load configuration store %s.'
2798
% store.external_url()])
2801
class TestIniConfigContent(tests.TestCaseWithTransport):
2802
"""Simulate loading a IniBasedConfig with content of various encodings.
2804
All files produced by bzr are in utf8 content.
2806
Users may modify them manually and end up with a file that can't be
2807
loaded. We need to issue proper error messages in this case.
2810
invalid_utf8_char = '\xff'
2812
def test_load_utf8(self):
2813
"""Ensure we can load an utf8-encoded file."""
2814
# From http://pad.lv/799212
2815
unicode_user = u'b\N{Euro Sign}ar'
2816
unicode_content = u'user=%s' % (unicode_user,)
2817
utf8_content = unicode_content.encode('utf8')
2818
# Store the raw content in the config file
2819
with open('foo.conf', 'wb') as f:
2820
f.write(utf8_content)
2821
conf = config.IniBasedConfig(file_name='foo.conf')
2822
self.assertEquals(unicode_user, conf.get_user_option('user'))
2824
def test_load_badly_encoded_content(self):
2825
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2826
with open('foo.conf', 'wb') as f:
2827
f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
2828
conf = config.IniBasedConfig(file_name='foo.conf')
2829
self.assertRaises(errors.ConfigContentError, conf._get_parser)
2831
def test_load_erroneous_content(self):
2832
"""Ensure we display a proper error on content that can't be parsed."""
2833
with open('foo.conf', 'wb') as f:
2834
f.write('[open_section\n')
2835
conf = config.IniBasedConfig(file_name='foo.conf')
2836
self.assertRaises(errors.ParseConfigError, conf._get_parser)
2839
class TestMutableStore(TestStore):
2841
scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
2842
in config.test_store_builder_registry.iteritems()]
2845
super(TestMutableStore, self).setUp()
2846
self.transport = self.get_transport()
2848
def has_store(self, store):
2849
store_basename = urlutils.relative_url(self.transport.external_url(),
2850
store.external_url())
2851
return self.transport.has(store_basename)
2853
def test_save_empty_creates_no_file(self):
2854
# FIXME: There should be a better way than relying on the test
2855
# parametrization to identify branch.conf -- vila 2011-0526
2856
if self.store_id in ('branch', 'remote_branch'):
2857
raise tests.TestNotApplicable(
2858
'branch.conf is *always* created when a branch is initialized')
2859
store = self.get_store(self)
2861
self.assertEquals(False, self.has_store(store))
2863
def test_mutable_section_shared(self):
2864
store = self.get_store(self)
2865
store._load_from_string('foo=bar\n')
2866
# FIXME: There should be a better way than relying on the test
2867
# parametrization to identify branch.conf -- vila 2011-0526
2868
if self.store_id in ('branch', 'remote_branch'):
2869
# branch stores requires write locked branches
2870
self.addCleanup(store.branch.lock_write().unlock)
2871
section1 = store.get_mutable_section(None)
2872
section2 = store.get_mutable_section(None)
2873
# If we get different sections, different callers won't share the
2875
self.assertIs(section1, section2)
2877
def test_save_emptied_succeeds(self):
2878
store = self.get_store(self)
2879
store._load_from_string('foo=bar\n')
2880
# FIXME: There should be a better way than relying on the test
2881
# parametrization to identify branch.conf -- vila 2011-0526
2882
if self.store_id in ('branch', 'remote_branch'):
2883
# branch stores requires write locked branches
2884
self.addCleanup(store.branch.lock_write().unlock)
2885
section = store.get_mutable_section(None)
2886
section.remove('foo')
2888
self.assertEquals(True, self.has_store(store))
2889
modified_store = self.get_store(self)
2890
sections = list(modified_store.get_sections())
2891
self.assertLength(0, sections)
2893
def test_save_with_content_succeeds(self):
2894
# FIXME: There should be a better way than relying on the test
2895
# parametrization to identify branch.conf -- vila 2011-0526
2896
if self.store_id in ('branch', 'remote_branch'):
2897
raise tests.TestNotApplicable(
2898
'branch.conf is *always* created when a branch is initialized')
2899
store = self.get_store(self)
2900
store._load_from_string('foo=bar\n')
2901
self.assertEquals(False, self.has_store(store))
2903
self.assertEquals(True, self.has_store(store))
2904
modified_store = self.get_store(self)
2905
sections = list(modified_store.get_sections())
2906
self.assertLength(1, sections)
2907
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2909
def test_set_option_in_empty_store(self):
2910
store = self.get_store(self)
2911
# FIXME: There should be a better way than relying on the test
2912
# parametrization to identify branch.conf -- vila 2011-0526
2913
if self.store_id in ('branch', 'remote_branch'):
2914
# branch stores requires write locked branches
2915
self.addCleanup(store.branch.lock_write().unlock)
2916
section = store.get_mutable_section(None)
2917
section.set('foo', 'bar')
2919
modified_store = self.get_store(self)
2920
sections = list(modified_store.get_sections())
2921
self.assertLength(1, sections)
2922
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2924
def test_set_option_in_default_section(self):
2925
store = self.get_store(self)
2926
store._load_from_string('')
2927
# FIXME: There should be a better way than relying on the test
2928
# parametrization to identify branch.conf -- vila 2011-0526
2929
if self.store_id in ('branch', 'remote_branch'):
2930
# branch stores requires write locked branches
2931
self.addCleanup(store.branch.lock_write().unlock)
2932
section = store.get_mutable_section(None)
2933
section.set('foo', 'bar')
2935
modified_store = self.get_store(self)
2936
sections = list(modified_store.get_sections())
2937
self.assertLength(1, sections)
2938
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2940
def test_set_option_in_named_section(self):
2941
store = self.get_store(self)
2942
store._load_from_string('')
2943
# FIXME: There should be a better way than relying on the test
2944
# parametrization to identify branch.conf -- vila 2011-0526
2945
if self.store_id in ('branch', 'remote_branch'):
2946
# branch stores requires write locked branches
2947
self.addCleanup(store.branch.lock_write().unlock)
2948
section = store.get_mutable_section('baz')
2949
section.set('foo', 'bar')
2951
modified_store = self.get_store(self)
2952
sections = list(modified_store.get_sections())
2953
self.assertLength(1, sections)
2954
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2956
def test_load_hook(self):
2957
# First, we need to ensure that the store exists
2958
store = self.get_store(self)
2959
# FIXME: There should be a better way than relying on the test
2960
# parametrization to identify branch.conf -- vila 2011-0526
2961
if self.store_id in ('branch', 'remote_branch'):
2962
# branch stores requires write locked branches
2963
self.addCleanup(store.branch.lock_write().unlock)
2964
section = store.get_mutable_section('baz')
2965
section.set('foo', 'bar')
2967
# Now we can try to load it
2968
store = self.get_store(self)
2972
config.ConfigHooks.install_named_hook('load', hook, None)
2973
self.assertLength(0, calls)
2975
self.assertLength(1, calls)
2976
self.assertEquals((store,), calls[0])
2978
def test_save_hook(self):
2982
config.ConfigHooks.install_named_hook('save', hook, None)
2983
self.assertLength(0, calls)
2984
store = self.get_store(self)
2985
# FIXME: There should be a better way than relying on the test
2986
# parametrization to identify branch.conf -- vila 2011-0526
2987
if self.store_id in ('branch', 'remote_branch'):
2988
# branch stores requires write locked branches
2989
self.addCleanup(store.branch.lock_write().unlock)
2990
section = store.get_mutable_section('baz')
2991
section.set('foo', 'bar')
2993
self.assertLength(1, calls)
2994
self.assertEquals((store,), calls[0])
2996
def test_set_mark_dirty(self):
2997
stack = config.MemoryStack('')
2998
self.assertLength(0, stack.store.dirty_sections)
2999
stack.set('foo', 'baz')
3000
self.assertLength(1, stack.store.dirty_sections)
3001
self.assertTrue(stack.store._need_saving())
3003
def test_remove_mark_dirty(self):
3004
stack = config.MemoryStack('foo=bar')
3005
self.assertLength(0, stack.store.dirty_sections)
3007
self.assertLength(1, stack.store.dirty_sections)
3008
self.assertTrue(stack.store._need_saving())
3011
class TestStoreSaveChanges(tests.TestCaseWithTransport):
3012
"""Tests that config changes are kept in memory and saved on-demand."""
3015
super(TestStoreSaveChanges, self).setUp()
3016
self.transport = self.get_transport()
3017
# Most of the tests involve two stores pointing to the same persistent
3018
# storage to observe the effects of concurrent changes
3019
self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
3020
self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
3023
self.warnings.append(args[0] % args[1:])
3024
self.overrideAttr(trace, 'warning', warning)
3026
def has_store(self, store):
3027
store_basename = urlutils.relative_url(self.transport.external_url(),
3028
store.external_url())
3029
return self.transport.has(store_basename)
3031
def get_stack(self, store):
3032
# Any stack will do as long as it uses the right store, just a single
3033
# no-name section is enough
3034
return config.Stack([store.get_sections], store)
3036
def test_no_changes_no_save(self):
3037
s = self.get_stack(self.st1)
3038
s.store.save_changes()
3039
self.assertEquals(False, self.has_store(self.st1))
3041
def test_unrelated_concurrent_update(self):
3042
s1 = self.get_stack(self.st1)
3043
s2 = self.get_stack(self.st2)
3044
s1.set('foo', 'bar')
3045
s2.set('baz', 'quux')
3047
# Changes don't propagate magically
3048
self.assertEquals(None, s1.get('baz'))
3049
s2.store.save_changes()
3050
self.assertEquals('quux', s2.get('baz'))
3051
# Changes are acquired when saving
3052
self.assertEquals('bar', s2.get('foo'))
3053
# Since there is no overlap, no warnings are emitted
3054
self.assertLength(0, self.warnings)
3056
def test_concurrent_update_modified(self):
3057
s1 = self.get_stack(self.st1)
3058
s2 = self.get_stack(self.st2)
3059
s1.set('foo', 'bar')
3060
s2.set('foo', 'baz')
3063
s2.store.save_changes()
3064
self.assertEquals('baz', s2.get('foo'))
3065
# But the user get a warning
3066
self.assertLength(1, self.warnings)
3067
warning = self.warnings[0]
3068
self.assertStartsWith(warning, 'Option foo in section None')
3069
self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
3070
' The baz value will be saved.')
3072
def test_concurrent_deletion(self):
3073
self.st1._load_from_string('foo=bar')
3075
s1 = self.get_stack(self.st1)
3076
s2 = self.get_stack(self.st2)
3079
s1.store.save_changes()
3081
self.assertLength(0, self.warnings)
3082
s2.store.save_changes()
3084
self.assertLength(1, self.warnings)
3085
warning = self.warnings[0]
3086
self.assertStartsWith(warning, 'Option foo in section None')
3087
self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
3088
' The <DELETED> value will be saved.')
3091
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
3093
def get_store(self):
3094
return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3096
def test_get_quoted_string(self):
3097
store = self.get_store()
3098
store._load_from_string('foo= " abc "')
3099
stack = config.Stack([store.get_sections])
3100
self.assertEquals(' abc ', stack.get('foo'))
3102
def test_set_quoted_string(self):
3103
store = self.get_store()
3104
stack = config.Stack([store.get_sections], store)
3105
stack.set('foo', ' a b c ')
3107
self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
3110
class TestTransportIniFileStore(TestStore):
3112
def test_loading_unknown_file_fails(self):
3113
store = config.TransportIniFileStore(self.get_transport(),
3115
self.assertRaises(errors.NoSuchFile, store.load)
3117
def test_invalid_content(self):
3118
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3119
self.assertEquals(False, store.is_loaded())
3120
exc = self.assertRaises(
3121
errors.ParseConfigError, store._load_from_string,
3122
'this is invalid !')
3123
self.assertEndsWith(exc.filename, 'foo.conf')
3124
# And the load failed
3125
self.assertEquals(False, store.is_loaded())
3127
def test_get_embedded_sections(self):
3128
# A more complicated example (which also shows that section names and
3129
# option names share the same name space...)
3130
# FIXME: This should be fixed by forbidding dicts as values ?
3131
# -- vila 2011-04-05
3132
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3133
store._load_from_string('''
3137
foo_in_DEFAULT=foo_DEFAULT
3145
sections = list(store.get_sections())
3146
self.assertLength(4, sections)
3147
# The default section has no name.
3148
# List values are provided as strings and need to be explicitly
3149
# converted by specifying from_unicode=list_from_store at option
3151
self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
3153
self.assertSectionContent(
3154
('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
3155
self.assertSectionContent(
3156
('bar', {'foo_in_bar': 'barbar'}), sections[2])
3157
# sub sections are provided as embedded dicts.
3158
self.assertSectionContent(
3159
('baz', {'foo_in_baz': 'barbaz', 'qux': {'foo_in_qux': 'quux'}}),
3163
class TestLockableIniFileStore(TestStore):
3165
def test_create_store_in_created_dir(self):
3166
self.assertPathDoesNotExist('dir')
3167
t = self.get_transport('dir/subdir')
3168
store = config.LockableIniFileStore(t, 'foo.conf')
3169
store.get_mutable_section(None).set('foo', 'bar')
3171
self.assertPathExists('dir/subdir')
3174
class TestConcurrentStoreUpdates(TestStore):
3175
"""Test that Stores properly handle conccurent updates.
3177
New Store implementation may fail some of these tests but until such
3178
implementations exist it's hard to properly filter them from the scenarios
3179
applied here. If you encounter such a case, contact the bzr devs.
3182
scenarios = [(key, {'get_stack': builder}) for key, builder
3183
in config.test_stack_builder_registry.iteritems()]
3186
super(TestConcurrentStoreUpdates, self).setUp()
3187
self.stack = self.get_stack(self)
3188
if not isinstance(self.stack, config._CompatibleStack):
3189
raise tests.TestNotApplicable(
3190
'%s is not meant to be compatible with the old config design'
3192
self.stack.set('one', '1')
3193
self.stack.set('two', '2')
3195
self.stack.store.save()
3197
def test_simple_read_access(self):
3198
self.assertEquals('1', self.stack.get('one'))
3200
def test_simple_write_access(self):
3201
self.stack.set('one', 'one')
3202
self.assertEquals('one', self.stack.get('one'))
3204
def test_listen_to_the_last_speaker(self):
3206
c2 = self.get_stack(self)
3207
c1.set('one', 'ONE')
3208
c2.set('two', 'TWO')
3209
self.assertEquals('ONE', c1.get('one'))
3210
self.assertEquals('TWO', c2.get('two'))
3211
# The second update respect the first one
3212
self.assertEquals('ONE', c2.get('one'))
3214
def test_last_speaker_wins(self):
3215
# If the same config is not shared, the same variable modified twice
3216
# can only see a single result.
3218
c2 = self.get_stack(self)
3221
self.assertEquals('c2', c2.get('one'))
3222
# The first modification is still available until another refresh
3224
self.assertEquals('c1', c1.get('one'))
3225
c1.set('two', 'done')
3226
self.assertEquals('c2', c1.get('one'))
3228
def test_writes_are_serialized(self):
3230
c2 = self.get_stack(self)
3232
# We spawn a thread that will pause *during* the config saving.
3233
before_writing = threading.Event()
3234
after_writing = threading.Event()
3235
writing_done = threading.Event()
3236
c1_save_without_locking_orig = c1.store.save_without_locking
3237
def c1_save_without_locking():
3238
before_writing.set()
3239
c1_save_without_locking_orig()
3240
# The lock is held. We wait for the main thread to decide when to
3242
after_writing.wait()
3243
c1.store.save_without_locking = c1_save_without_locking
3247
t1 = threading.Thread(target=c1_set)
3248
# Collect the thread after the test
3249
self.addCleanup(t1.join)
3250
# Be ready to unblock the thread if the test goes wrong
3251
self.addCleanup(after_writing.set)
3253
before_writing.wait()
3254
self.assertRaises(errors.LockContention,
3255
c2.set, 'one', 'c2')
3256
self.assertEquals('c1', c1.get('one'))
3257
# Let the lock be released
3261
self.assertEquals('c2', c2.get('one'))
3263
def test_read_while_writing(self):
3265
# We spawn a thread that will pause *during* the write
3266
ready_to_write = threading.Event()
3267
do_writing = threading.Event()
3268
writing_done = threading.Event()
3269
# We override the _save implementation so we know the store is locked
3270
c1_save_without_locking_orig = c1.store.save_without_locking
3271
def c1_save_without_locking():
3272
ready_to_write.set()
3273
# The lock is held. We wait for the main thread to decide when to
3276
c1_save_without_locking_orig()
3278
c1.store.save_without_locking = c1_save_without_locking
3281
t1 = threading.Thread(target=c1_set)
3282
# Collect the thread after the test
3283
self.addCleanup(t1.join)
3284
# Be ready to unblock the thread if the test goes wrong
3285
self.addCleanup(do_writing.set)
3287
# Ensure the thread is ready to write
3288
ready_to_write.wait()
3289
self.assertEquals('c1', c1.get('one'))
3290
# If we read during the write, we get the old value
3291
c2 = self.get_stack(self)
3292
self.assertEquals('1', c2.get('one'))
3293
# Let the writing occur and ensure it occurred
3296
# Now we get the updated value
3297
c3 = self.get_stack(self)
3298
self.assertEquals('c1', c3.get('one'))
3300
# FIXME: It may be worth looking into removing the lock dir when it's not
3301
# needed anymore and look at possible fallouts for concurrent lockers. This
3302
# will matter if/when we use config files outside of bazaar directories
3303
# (.bazaar or .bzr) -- vila 20110-04-111
3306
class TestSectionMatcher(TestStore):
3308
scenarios = [('location', {'matcher': config.LocationMatcher}),
3309
('id', {'matcher': config.NameMatcher}),]
3312
super(TestSectionMatcher, self).setUp()
3313
# Any simple store is good enough
3314
self.get_store = config.test_store_builder_registry.get('configobj')
3316
def test_no_matches_for_empty_stores(self):
3317
store = self.get_store(self)
3318
store._load_from_string('')
3319
matcher = self.matcher(store, '/bar')
3320
self.assertEquals([], list(matcher.get_sections()))
3322
def test_build_doesnt_load_store(self):
3323
store = self.get_store(self)
3324
matcher = self.matcher(store, '/bar')
3325
self.assertFalse(store.is_loaded())
3328
class TestLocationSection(tests.TestCase):
3330
def get_section(self, options, extra_path):
3331
section = config.Section('foo', options)
3332
return config.LocationSection(section, extra_path)
3334
def test_simple_option(self):
3335
section = self.get_section({'foo': 'bar'}, '')
3336
self.assertEquals('bar', section.get('foo'))
3338
def test_option_with_extra_path(self):
3339
section = self.get_section({'foo': 'bar', 'foo:policy': 'appendpath'},
3341
self.assertEquals('bar/baz', section.get('foo'))
3343
def test_invalid_policy(self):
3344
section = self.get_section({'foo': 'bar', 'foo:policy': 'die'},
3346
# invalid policies are ignored
3347
self.assertEquals('bar', section.get('foo'))
3350
class TestLocationMatcher(TestStore):
3353
super(TestLocationMatcher, self).setUp()
3354
# Any simple store is good enough
3355
self.get_store = config.test_store_builder_registry.get('configobj')
3357
def test_unrelated_section_excluded(self):
3358
store = self.get_store(self)
3359
store._load_from_string('''
3367
section=/foo/bar/baz
3371
self.assertEquals(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
3373
[section.id for _, section in store.get_sections()])
3374
matcher = config.LocationMatcher(store, '/foo/bar/quux')
3375
sections = [section for _, section in matcher.get_sections()]
3376
self.assertEquals(['/foo/bar', '/foo'],
3377
[section.id for section in sections])
3378
self.assertEquals(['quux', 'bar/quux'],
3379
[section.extra_path for section in sections])
3381
def test_more_specific_sections_first(self):
3382
store = self.get_store(self)
3383
store._load_from_string('''
3389
self.assertEquals(['/foo', '/foo/bar'],
3390
[section.id for _, section in store.get_sections()])
3391
matcher = config.LocationMatcher(store, '/foo/bar/baz')
3392
sections = [section for _, section in matcher.get_sections()]
3393
self.assertEquals(['/foo/bar', '/foo'],
3394
[section.id for section in sections])
3395
self.assertEquals(['baz', 'bar/baz'],
3396
[section.extra_path for section in sections])
3398
def test_appendpath_in_no_name_section(self):
3399
# It's a bit weird to allow appendpath in a no-name section, but
3400
# someone may found a use for it
3401
store = self.get_store(self)
3402
store._load_from_string('''
3404
foo:policy = appendpath
3406
matcher = config.LocationMatcher(store, 'dir/subdir')
3407
sections = list(matcher.get_sections())
3408
self.assertLength(1, sections)
3409
self.assertEquals('bar/dir/subdir', sections[0][1].get('foo'))
3411
def test_file_urls_are_normalized(self):
3412
store = self.get_store(self)
3413
if sys.platform == 'win32':
3414
expected_url = 'file:///C:/dir/subdir'
3415
expected_location = 'C:/dir/subdir'
3417
expected_url = 'file:///dir/subdir'
3418
expected_location = '/dir/subdir'
3419
matcher = config.LocationMatcher(store, expected_url)
3420
self.assertEquals(expected_location, matcher.location)
3422
def test_branch_name_colo(self):
3423
store = self.get_store(self)
3424
store._load_from_string(dedent("""\
3426
push_location=my{branchname}
3428
matcher = config.LocationMatcher(store, 'file:///,branch=example%3c')
3429
self.assertEqual('example<', matcher.branch_name)
3430
((_, section),) = matcher.get_sections()
3431
self.assertEqual('example<', section.locals['branchname'])
3433
def test_branch_name_basename(self):
3434
store = self.get_store(self)
3435
store._load_from_string(dedent("""\
3437
push_location=my{branchname}
3439
matcher = config.LocationMatcher(store, 'file:///parent/example%3c')
3440
self.assertEqual('example<', matcher.branch_name)
3441
((_, section),) = matcher.get_sections()
3442
self.assertEqual('example<', section.locals['branchname'])
3445
class TestStartingPathMatcher(TestStore):
3448
super(TestStartingPathMatcher, self).setUp()
3449
# Any simple store is good enough
3450
self.store = config.IniFileStore()
3452
def assertSectionIDs(self, expected, location, content):
3453
self.store._load_from_string(content)
3454
matcher = config.StartingPathMatcher(self.store, location)
3455
sections = list(matcher.get_sections())
3456
self.assertLength(len(expected), sections)
3457
self.assertEqual(expected, [section.id for _, section in sections])
3460
def test_empty(self):
3461
self.assertSectionIDs([], self.get_url(), '')
3463
def test_url_vs_local_paths(self):
3464
# The matcher location is an url and the section names are local paths
3465
sections = self.assertSectionIDs(['/foo/bar', '/foo'],
3466
'file:///foo/bar/baz', '''\
3471
def test_local_path_vs_url(self):
3472
# The matcher location is a local path and the section names are urls
3473
sections = self.assertSectionIDs(['file:///foo/bar', 'file:///foo'],
3474
'/foo/bar/baz', '''\
3480
def test_no_name_section_included_when_present(self):
3481
# Note that other tests will cover the case where the no-name section
3482
# is empty and as such, not included.
3483
sections = self.assertSectionIDs(['/foo/bar', '/foo', None],
3484
'/foo/bar/baz', '''\
3485
option = defined so the no-name section exists
3489
self.assertEquals(['baz', 'bar/baz', '/foo/bar/baz'],
3490
[s.locals['relpath'] for _, s in sections])
3492
def test_order_reversed(self):
3493
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3498
def test_unrelated_section_excluded(self):
3499
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3505
def test_glob_included(self):
3506
sections = self.assertSectionIDs(['/foo/*/baz', '/foo/b*', '/foo'],
3507
'/foo/bar/baz', '''\
3513
# Note that 'baz' as a relpath for /foo/b* is not fully correct, but
3514
# nothing really is... as far using {relpath} to append it to something
3515
# else, this seems good enough though.
3516
self.assertEquals(['', 'baz', 'bar/baz'],
3517
[s.locals['relpath'] for _, s in sections])
3519
def test_respect_order(self):
3520
self.assertSectionIDs(['/foo', '/foo/b*', '/foo/*/baz'],
3521
'/foo/bar/baz', '''\
3529
class TestNameMatcher(TestStore):
3532
super(TestNameMatcher, self).setUp()
3533
self.matcher = config.NameMatcher
3534
# Any simple store is good enough
3535
self.get_store = config.test_store_builder_registry.get('configobj')
3537
def get_matching_sections(self, name):
3538
store = self.get_store(self)
3539
store._load_from_string('''
3547
matcher = self.matcher(store, name)
3548
return list(matcher.get_sections())
3550
def test_matching(self):
3551
sections = self.get_matching_sections('foo')
3552
self.assertLength(1, sections)
3553
self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
3555
def test_not_matching(self):
3556
sections = self.get_matching_sections('baz')
3557
self.assertLength(0, sections)
3560
class TestBaseStackGet(tests.TestCase):
3563
super(TestBaseStackGet, self).setUp()
3564
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3566
def test_get_first_definition(self):
3567
store1 = config.IniFileStore()
3568
store1._load_from_string('foo=bar')
3569
store2 = config.IniFileStore()
3570
store2._load_from_string('foo=baz')
3571
conf = config.Stack([store1.get_sections, store2.get_sections])
3572
self.assertEquals('bar', conf.get('foo'))
3574
def test_get_with_registered_default_value(self):
3575
config.option_registry.register(config.Option('foo', default='bar'))
3576
conf_stack = config.Stack([])
3577
self.assertEquals('bar', conf_stack.get('foo'))
3579
def test_get_without_registered_default_value(self):
3580
config.option_registry.register(config.Option('foo'))
3581
conf_stack = config.Stack([])
3582
self.assertEquals(None, conf_stack.get('foo'))
3584
def test_get_without_default_value_for_not_registered(self):
3585
conf_stack = config.Stack([])
3586
self.assertEquals(None, conf_stack.get('foo'))
3588
def test_get_for_empty_section_callable(self):
3589
conf_stack = config.Stack([lambda : []])
3590
self.assertEquals(None, conf_stack.get('foo'))
3592
def test_get_for_broken_callable(self):
3593
# Trying to use and invalid callable raises an exception on first use
3594
conf_stack = config.Stack([object])
3595
self.assertRaises(TypeError, conf_stack.get, 'foo')
3598
class TestStackWithSimpleStore(tests.TestCase):
3601
super(TestStackWithSimpleStore, self).setUp()
3602
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3603
self.registry = config.option_registry
3605
def get_conf(self, content=None):
3606
return config.MemoryStack(content)
3608
def test_override_value_from_env(self):
3609
self.overrideEnv('FOO', None)
3610
self.registry.register(
3611
config.Option('foo', default='bar', override_from_env=['FOO']))
3612
self.overrideEnv('FOO', 'quux')
3613
# Env variable provides a default taking over the option one
3614
conf = self.get_conf('foo=store')
3615
self.assertEquals('quux', conf.get('foo'))
3617
def test_first_override_value_from_env_wins(self):
3618
self.overrideEnv('NO_VALUE', None)
3619
self.overrideEnv('FOO', None)
3620
self.overrideEnv('BAZ', None)
3621
self.registry.register(
3622
config.Option('foo', default='bar',
3623
override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
3624
self.overrideEnv('FOO', 'foo')
3625
self.overrideEnv('BAZ', 'baz')
3626
# The first env var set wins
3627
conf = self.get_conf('foo=store')
3628
self.assertEquals('foo', conf.get('foo'))
3631
class TestMemoryStack(tests.TestCase):
3634
conf = config.MemoryStack('foo=bar')
3635
self.assertEquals('bar', conf.get('foo'))
3638
conf = config.MemoryStack('foo=bar')
3639
conf.set('foo', 'baz')
3640
self.assertEquals('baz', conf.get('foo'))
3642
def test_no_content(self):
3643
conf = config.MemoryStack()
3644
# No content means no loading
3645
self.assertFalse(conf.store.is_loaded())
3646
self.assertRaises(NotImplementedError, conf.get, 'foo')
3647
# But a content can still be provided
3648
conf.store._load_from_string('foo=bar')
3649
self.assertEquals('bar', conf.get('foo'))
3652
class TestStackIterSections(tests.TestCase):
3654
def test_empty_stack(self):
3655
conf = config.Stack([])
3656
sections = list(conf.iter_sections())
3657
self.assertLength(0, sections)
3659
def test_empty_store(self):
3660
store = config.IniFileStore()
3661
store._load_from_string('')
3662
conf = config.Stack([store.get_sections])
3663
sections = list(conf.iter_sections())
3664
self.assertLength(0, sections)
3666
def test_simple_store(self):
3667
store = config.IniFileStore()
3668
store._load_from_string('foo=bar')
3669
conf = config.Stack([store.get_sections])
3670
tuples = list(conf.iter_sections())
3671
self.assertLength(1, tuples)
3672
(found_store, found_section) = tuples[0]
3673
self.assertIs(store, found_store)
3675
def test_two_stores(self):
3676
store1 = config.IniFileStore()
3677
store1._load_from_string('foo=bar')
3678
store2 = config.IniFileStore()
3679
store2._load_from_string('bar=qux')
3680
conf = config.Stack([store1.get_sections, store2.get_sections])
3681
tuples = list(conf.iter_sections())
3682
self.assertLength(2, tuples)
3683
self.assertIs(store1, tuples[0][0])
3684
self.assertIs(store2, tuples[1][0])
3687
class TestStackWithTransport(tests.TestCaseWithTransport):
3689
scenarios = [(key, {'get_stack': builder}) for key, builder
3690
in config.test_stack_builder_registry.iteritems()]
3693
class TestConcreteStacks(TestStackWithTransport):
3695
def test_build_stack(self):
3696
# Just a smoke test to help debug builders
3697
stack = self.get_stack(self)
3700
class TestStackGet(TestStackWithTransport):
3703
super(TestStackGet, self).setUp()
3704
self.conf = self.get_stack(self)
3706
def test_get_for_empty_stack(self):
3707
self.assertEquals(None, self.conf.get('foo'))
3709
def test_get_hook(self):
3710
self.conf.set('foo', 'bar')
3714
config.ConfigHooks.install_named_hook('get', hook, None)
3715
self.assertLength(0, calls)
3716
value = self.conf.get('foo')
3717
self.assertEquals('bar', value)
3718
self.assertLength(1, calls)
3719
self.assertEquals((self.conf, 'foo', 'bar'), calls[0])
3722
class TestStackGetWithConverter(tests.TestCase):
3725
super(TestStackGetWithConverter, self).setUp()
3726
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3727
self.registry = config.option_registry
3729
def get_conf(self, content=None):
3730
return config.MemoryStack(content)
3732
def register_bool_option(self, name, default=None, default_from_env=None):
3733
b = config.Option(name, help='A boolean.',
3734
default=default, default_from_env=default_from_env,
3735
from_unicode=config.bool_from_store)
3736
self.registry.register(b)
3738
def test_get_default_bool_None(self):
3739
self.register_bool_option('foo')
3740
conf = self.get_conf('')
3741
self.assertEquals(None, conf.get('foo'))
3743
def test_get_default_bool_True(self):
3744
self.register_bool_option('foo', u'True')
3745
conf = self.get_conf('')
3746
self.assertEquals(True, conf.get('foo'))
3748
def test_get_default_bool_False(self):
3749
self.register_bool_option('foo', False)
3750
conf = self.get_conf('')
3751
self.assertEquals(False, conf.get('foo'))
3753
def test_get_default_bool_False_as_string(self):
3754
self.register_bool_option('foo', u'False')
3755
conf = self.get_conf('')
3756
self.assertEquals(False, conf.get('foo'))
3758
def test_get_default_bool_from_env_converted(self):
3759
self.register_bool_option('foo', u'True', default_from_env=['FOO'])
3760
self.overrideEnv('FOO', 'False')
3761
conf = self.get_conf('')
3762
self.assertEquals(False, conf.get('foo'))
3764
def test_get_default_bool_when_conversion_fails(self):
3765
self.register_bool_option('foo', default='True')
3766
conf = self.get_conf('foo=invalid boolean')
3767
self.assertEquals(True, conf.get('foo'))
3769
def register_integer_option(self, name,
3770
default=None, default_from_env=None):
3771
i = config.Option(name, help='An integer.',
3772
default=default, default_from_env=default_from_env,
3773
from_unicode=config.int_from_store)
3774
self.registry.register(i)
3776
def test_get_default_integer_None(self):
3777
self.register_integer_option('foo')
3778
conf = self.get_conf('')
3779
self.assertEquals(None, conf.get('foo'))
3781
def test_get_default_integer(self):
3782
self.register_integer_option('foo', 42)
3783
conf = self.get_conf('')
3784
self.assertEquals(42, conf.get('foo'))
3786
def test_get_default_integer_as_string(self):
3787
self.register_integer_option('foo', u'42')
3788
conf = self.get_conf('')
3789
self.assertEquals(42, conf.get('foo'))
3791
def test_get_default_integer_from_env(self):
3792
self.register_integer_option('foo', default_from_env=['FOO'])
3793
self.overrideEnv('FOO', '18')
3794
conf = self.get_conf('')
3795
self.assertEquals(18, conf.get('foo'))
3797
def test_get_default_integer_when_conversion_fails(self):
3798
self.register_integer_option('foo', default='12')
3799
conf = self.get_conf('foo=invalid integer')
3800
self.assertEquals(12, conf.get('foo'))
3802
def register_list_option(self, name, default=None, default_from_env=None):
3803
l = config.ListOption(name, help='A list.', default=default,
3804
default_from_env=default_from_env)
3805
self.registry.register(l)
3807
def test_get_default_list_None(self):
3808
self.register_list_option('foo')
3809
conf = self.get_conf('')
3810
self.assertEquals(None, conf.get('foo'))
3812
def test_get_default_list_empty(self):
3813
self.register_list_option('foo', '')
3814
conf = self.get_conf('')
3815
self.assertEquals([], conf.get('foo'))
3817
def test_get_default_list_from_env(self):
3818
self.register_list_option('foo', default_from_env=['FOO'])
3819
self.overrideEnv('FOO', '')
3820
conf = self.get_conf('')
3821
self.assertEquals([], conf.get('foo'))
3823
def test_get_with_list_converter_no_item(self):
3824
self.register_list_option('foo', None)
3825
conf = self.get_conf('foo=,')
3826
self.assertEquals([], conf.get('foo'))
3828
def test_get_with_list_converter_many_items(self):
3829
self.register_list_option('foo', None)
3830
conf = self.get_conf('foo=m,o,r,e')
3831
self.assertEquals(['m', 'o', 'r', 'e'], conf.get('foo'))
3833
def test_get_with_list_converter_embedded_spaces_many_items(self):
3834
self.register_list_option('foo', None)
3835
conf = self.get_conf('foo=" bar", "baz "')
3836
self.assertEquals([' bar', 'baz '], conf.get('foo'))
3838
def test_get_with_list_converter_stripped_spaces_many_items(self):
3839
self.register_list_option('foo', None)
3840
conf = self.get_conf('foo= bar , baz ')
3841
self.assertEquals(['bar', 'baz'], conf.get('foo'))
3844
class TestIterOptionRefs(tests.TestCase):
3845
"""iter_option_refs is a bit unusual, document some cases."""
3847
def assertRefs(self, expected, string):
3848
self.assertEquals(expected, list(config.iter_option_refs(string)))
3850
def test_empty(self):
3851
self.assertRefs([(False, '')], '')
3853
def test_no_refs(self):
3854
self.assertRefs([(False, 'foo bar')], 'foo bar')
3856
def test_single_ref(self):
3857
self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
3859
def test_broken_ref(self):
3860
self.assertRefs([(False, '{foo')], '{foo')
3862
def test_embedded_ref(self):
3863
self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
3866
def test_two_refs(self):
3867
self.assertRefs([(False, ''), (True, '{foo}'),
3868
(False, ''), (True, '{bar}'),
3872
def test_newline_in_refs_are_not_matched(self):
3873
self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
3876
class TestStackExpandOptions(tests.TestCaseWithTransport):
3879
super(TestStackExpandOptions, self).setUp()
3880
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3881
self.registry = config.option_registry
3882
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3883
self.conf = config.Stack([store.get_sections], store)
3885
def assertExpansion(self, expected, string, env=None):
3886
self.assertEquals(expected, self.conf.expand_options(string, env))
3888
def test_no_expansion(self):
3889
self.assertExpansion('foo', 'foo')
3891
def test_expand_default_value(self):
3892
self.conf.store._load_from_string('bar=baz')
3893
self.registry.register(config.Option('foo', default=u'{bar}'))
3894
self.assertEquals('baz', self.conf.get('foo', expand=True))
3896
def test_expand_default_from_env(self):
3897
self.conf.store._load_from_string('bar=baz')
3898
self.registry.register(config.Option('foo', default_from_env=['FOO']))
3899
self.overrideEnv('FOO', '{bar}')
3900
self.assertEquals('baz', self.conf.get('foo', expand=True))
3902
def test_expand_default_on_failed_conversion(self):
3903
self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
3904
self.registry.register(
3905
config.Option('foo', default=u'{bar}',
3906
from_unicode=config.int_from_store))
3907
self.assertEquals(42, self.conf.get('foo', expand=True))
3909
def test_env_adding_options(self):
3910
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3912
def test_env_overriding_options(self):
3913
self.conf.store._load_from_string('foo=baz')
3914
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3916
def test_simple_ref(self):
3917
self.conf.store._load_from_string('foo=xxx')
3918
self.assertExpansion('xxx', '{foo}')
3920
def test_unknown_ref(self):
3921
self.assertRaises(errors.ExpandingUnknownOption,
3922
self.conf.expand_options, '{foo}')
3924
def test_indirect_ref(self):
3925
self.conf.store._load_from_string('''
3929
self.assertExpansion('xxx', '{bar}')
3931
def test_embedded_ref(self):
3932
self.conf.store._load_from_string('''
3936
self.assertExpansion('xxx', '{{bar}}')
3938
def test_simple_loop(self):
3939
self.conf.store._load_from_string('foo={foo}')
3940
self.assertRaises(errors.OptionExpansionLoop,
3941
self.conf.expand_options, '{foo}')
3943
def test_indirect_loop(self):
3944
self.conf.store._load_from_string('''
3948
e = self.assertRaises(errors.OptionExpansionLoop,
3949
self.conf.expand_options, '{foo}')
3950
self.assertEquals('foo->bar->baz', e.refs)
3951
self.assertEquals('{foo}', e.string)
3953
def test_list(self):
3954
self.conf.store._load_from_string('''
3958
list={foo},{bar},{baz}
3960
self.registry.register(
3961
config.ListOption('list'))
3962
self.assertEquals(['start', 'middle', 'end'],
3963
self.conf.get('list', expand=True))
3965
def test_cascading_list(self):
3966
self.conf.store._load_from_string('''
3972
self.registry.register(config.ListOption('list'))
3973
# Register an intermediate option as a list to ensure no conversion
3974
# happen while expanding. Conversion should only occur for the original
3975
# option ('list' here).
3976
self.registry.register(config.ListOption('baz'))
3977
self.assertEquals(['start', 'middle', 'end'],
3978
self.conf.get('list', expand=True))
3980
def test_pathologically_hidden_list(self):
3981
self.conf.store._load_from_string('''
3987
hidden={start}{middle}{end}
3989
# What matters is what the registration says, the conversion happens
3990
# only after all expansions have been performed
3991
self.registry.register(config.ListOption('hidden'))
3992
self.assertEquals(['bin', 'go'],
3993
self.conf.get('hidden', expand=True))
3996
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
3999
super(TestStackCrossSectionsExpand, self).setUp()
4001
def get_config(self, location, string):
4004
# Since we don't save the config we won't strictly require to inherit
4005
# from TestCaseInTempDir, but an error occurs so quickly...
4006
c = config.LocationStack(location)
4007
c.store._load_from_string(string)
4010
def test_dont_cross_unrelated_section(self):
4011
c = self.get_config('/another/branch/path','''
4016
[/another/branch/path]
4019
self.assertRaises(errors.ExpandingUnknownOption,
4020
c.get, 'bar', expand=True)
4022
def test_cross_related_sections(self):
4023
c = self.get_config('/project/branch/path','''
4027
[/project/branch/path]
4030
self.assertEquals('quux', c.get('bar', expand=True))
4033
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
4035
def test_cross_global_locations(self):
4036
l_store = config.LocationStore()
4037
l_store._load_from_string('''
4043
g_store = config.GlobalStore()
4044
g_store._load_from_string('''
4050
stack = config.LocationStack('/branch')
4051
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4052
self.assertEquals('loc-foo', stack.get('gfoo', expand=True))
4055
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
4057
def test_expand_locals_empty(self):
4058
l_store = config.LocationStore()
4059
l_store._load_from_string('''
4060
[/home/user/project]
4065
stack = config.LocationStack('/home/user/project/')
4066
self.assertEquals('', stack.get('base', expand=True))
4067
self.assertEquals('', stack.get('rel', expand=True))
4069
def test_expand_basename_locally(self):
4070
l_store = config.LocationStore()
4071
l_store._load_from_string('''
4072
[/home/user/project]
4076
stack = config.LocationStack('/home/user/project/branch')
4077
self.assertEquals('branch', stack.get('bfoo', expand=True))
4079
def test_expand_basename_locally_longer_path(self):
4080
l_store = config.LocationStore()
4081
l_store._load_from_string('''
4086
stack = config.LocationStack('/home/user/project/dir/branch')
4087
self.assertEquals('branch', stack.get('bfoo', expand=True))
4089
def test_expand_relpath_locally(self):
4090
l_store = config.LocationStore()
4091
l_store._load_from_string('''
4092
[/home/user/project]
4093
lfoo = loc-foo/{relpath}
4096
stack = config.LocationStack('/home/user/project/branch')
4097
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4099
def test_expand_relpath_unknonw_in_global(self):
4100
g_store = config.GlobalStore()
4101
g_store._load_from_string('''
4106
stack = config.LocationStack('/home/user/project/branch')
4107
self.assertRaises(errors.ExpandingUnknownOption,
4108
stack.get, 'gfoo', expand=True)
4110
def test_expand_local_option_locally(self):
4111
l_store = config.LocationStore()
4112
l_store._load_from_string('''
4113
[/home/user/project]
4114
lfoo = loc-foo/{relpath}
4118
g_store = config.GlobalStore()
4119
g_store._load_from_string('''
4125
stack = config.LocationStack('/home/user/project/branch')
4126
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4127
self.assertEquals('loc-foo/branch', stack.get('gfoo', expand=True))
4129
def test_locals_dont_leak(self):
4130
"""Make sure we chose the right local in presence of several sections.
4132
l_store = config.LocationStore()
4133
l_store._load_from_string('''
4135
lfoo = loc-foo/{relpath}
4136
[/home/user/project]
4137
lfoo = loc-foo/{relpath}
4140
stack = config.LocationStack('/home/user/project/branch')
4141
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4142
stack = config.LocationStack('/home/user/bar/baz')
4143
self.assertEquals('loc-foo/bar/baz', stack.get('lfoo', expand=True))
4147
class TestStackSet(TestStackWithTransport):
4149
def test_simple_set(self):
4150
conf = self.get_stack(self)
4151
self.assertEquals(None, conf.get('foo'))
4152
conf.set('foo', 'baz')
4153
# Did we get it back ?
4154
self.assertEquals('baz', conf.get('foo'))
4156
def test_set_creates_a_new_section(self):
4157
conf = self.get_stack(self)
4158
conf.set('foo', 'baz')
4159
self.assertEquals, 'baz', conf.get('foo')
4161
def test_set_hook(self):
4165
config.ConfigHooks.install_named_hook('set', hook, None)
4166
self.assertLength(0, calls)
4167
conf = self.get_stack(self)
4168
conf.set('foo', 'bar')
4169
self.assertLength(1, calls)
4170
self.assertEquals((conf, 'foo', 'bar'), calls[0])
4173
class TestStackRemove(TestStackWithTransport):
4175
def test_remove_existing(self):
4176
conf = self.get_stack(self)
4177
conf.set('foo', 'bar')
4178
self.assertEquals('bar', conf.get('foo'))
4180
# Did we get it back ?
4181
self.assertEquals(None, conf.get('foo'))
4183
def test_remove_unknown(self):
4184
conf = self.get_stack(self)
4185
self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
4187
def test_remove_hook(self):
4191
config.ConfigHooks.install_named_hook('remove', hook, None)
4192
self.assertLength(0, calls)
4193
conf = self.get_stack(self)
4194
conf.set('foo', 'bar')
4196
self.assertLength(1, calls)
4197
self.assertEquals((conf, 'foo'), calls[0])
4200
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
4203
super(TestConfigGetOptions, self).setUp()
4204
create_configs(self)
4206
def test_no_variable(self):
4207
# Using branch should query branch, locations and bazaar
4208
self.assertOptions([], self.branch_config)
4210
def test_option_in_bazaar(self):
4211
self.bazaar_config.set_user_option('file', 'bazaar')
4212
self.assertOptions([('file', 'bazaar', 'DEFAULT', 'bazaar')],
4215
def test_option_in_locations(self):
4216
self.locations_config.set_user_option('file', 'locations')
4218
[('file', 'locations', self.tree.basedir, 'locations')],
4219
self.locations_config)
4221
def test_option_in_branch(self):
4222
self.branch_config.set_user_option('file', 'branch')
4223
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch')],
4226
def test_option_in_bazaar_and_branch(self):
4227
self.bazaar_config.set_user_option('file', 'bazaar')
4228
self.branch_config.set_user_option('file', 'branch')
4229
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch'),
4230
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4233
def test_option_in_branch_and_locations(self):
4234
# Hmm, locations override branch :-/
4235
self.locations_config.set_user_option('file', 'locations')
4236
self.branch_config.set_user_option('file', 'branch')
4238
[('file', 'locations', self.tree.basedir, 'locations'),
4239
('file', 'branch', 'DEFAULT', 'branch'),],
4242
def test_option_in_bazaar_locations_and_branch(self):
4243
self.bazaar_config.set_user_option('file', 'bazaar')
4244
self.locations_config.set_user_option('file', 'locations')
4245
self.branch_config.set_user_option('file', 'branch')
4247
[('file', 'locations', self.tree.basedir, 'locations'),
4248
('file', 'branch', 'DEFAULT', 'branch'),
4249
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4253
class TestConfigRemoveOption(tests.TestCaseWithTransport, TestOptionsMixin):
4256
super(TestConfigRemoveOption, self).setUp()
4257
create_configs_with_file_option(self)
4259
def test_remove_in_locations(self):
4260
self.locations_config.remove_user_option('file', self.tree.basedir)
4262
[('file', 'branch', 'DEFAULT', 'branch'),
4263
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4266
def test_remove_in_branch(self):
4267
self.branch_config.remove_user_option('file')
4269
[('file', 'locations', self.tree.basedir, 'locations'),
4270
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4273
def test_remove_in_bazaar(self):
4274
self.bazaar_config.remove_user_option('file')
4276
[('file', 'locations', self.tree.basedir, 'locations'),
4277
('file', 'branch', 'DEFAULT', 'branch'),],
4281
class TestConfigGetSections(tests.TestCaseWithTransport):
4284
super(TestConfigGetSections, self).setUp()
4285
create_configs(self)
4287
def assertSectionNames(self, expected, conf, name=None):
4288
"""Check which sections are returned for a given config.
4290
If fallback configurations exist their sections can be included.
4292
:param expected: A list of section names.
4294
:param conf: The configuration that will be queried.
4296
:param name: An optional section name that will be passed to
4299
sections = list(conf._get_sections(name))
4300
self.assertLength(len(expected), sections)
4301
self.assertEqual(expected, [name for name, _, _ in sections])
4303
def test_bazaar_default_section(self):
4304
self.assertSectionNames(['DEFAULT'], self.bazaar_config)
4306
def test_locations_default_section(self):
4307
# No sections are defined in an empty file
4308
self.assertSectionNames([], self.locations_config)
4310
def test_locations_named_section(self):
4311
self.locations_config.set_user_option('file', 'locations')
4312
self.assertSectionNames([self.tree.basedir], self.locations_config)
4314
def test_locations_matching_sections(self):
4315
loc_config = self.locations_config
4316
loc_config.set_user_option('file', 'locations')
4317
# We need to cheat a bit here to create an option in sections above and
4318
# below the 'location' one.
4319
parser = loc_config._get_parser()
4320
# locations.cong deals with '/' ignoring native os.sep
4321
location_names = self.tree.basedir.split('/')
4322
parent = '/'.join(location_names[:-1])
4323
child = '/'.join(location_names + ['child'])
4325
parser[parent]['file'] = 'parent'
4327
parser[child]['file'] = 'child'
4328
self.assertSectionNames([self.tree.basedir, parent], loc_config)
4330
def test_branch_data_default_section(self):
4331
self.assertSectionNames([None],
4332
self.branch_config._get_branch_data_config())
4334
def test_branch_default_sections(self):
4335
# No sections are defined in an empty locations file
4336
self.assertSectionNames([None, 'DEFAULT'],
4338
# Unless we define an option
4339
self.branch_config._get_location_config().set_user_option(
4340
'file', 'locations')
4341
self.assertSectionNames([self.tree.basedir, None, 'DEFAULT'],
4344
def test_bazaar_named_section(self):
4345
# We need to cheat as the API doesn't give direct access to sections
4346
# other than DEFAULT.
4347
self.bazaar_config.set_alias('bazaar', 'bzr')
4348
self.assertSectionNames(['ALIASES'], self.bazaar_config, 'ALIASES')
4351
class TestAuthenticationConfigFile(tests.TestCase):
4352
"""Test the authentication.conf file matching"""
4354
def _got_user_passwd(self, expected_user, expected_password,
4355
config, *args, **kwargs):
4356
credentials = config.get_credentials(*args, **kwargs)
4357
if credentials is None:
4361
user = credentials['user']
4362
password = credentials['password']
4363
self.assertEquals(expected_user, user)
4364
self.assertEquals(expected_password, password)
4366
def test_empty_config(self):
4367
conf = config.AuthenticationConfig(_file=StringIO())
4368
self.assertEquals({}, conf._get_config())
4369
self._got_user_passwd(None, None, conf, 'http', 'foo.net')
4371
def test_non_utf8_config(self):
4372
conf = config.AuthenticationConfig(_file=StringIO(
4374
self.assertRaises(errors.ConfigContentError, conf._get_config)
4376
def test_missing_auth_section_header(self):
4377
conf = config.AuthenticationConfig(_file=StringIO('foo = bar'))
4378
self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
4380
def test_auth_section_header_not_closed(self):
4381
conf = config.AuthenticationConfig(_file=StringIO('[DEF'))
4382
self.assertRaises(errors.ParseConfigError, conf._get_config)
4384
def test_auth_value_not_boolean(self):
4385
conf = config.AuthenticationConfig(_file=StringIO(
4389
verify_certificates=askme # Error: Not a boolean
4391
self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
4393
def test_auth_value_not_int(self):
4394
conf = config.AuthenticationConfig(_file=StringIO(
4398
port=port # Error: Not an int
4400
self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
4402
def test_unknown_password_encoding(self):
4403
conf = config.AuthenticationConfig(_file=StringIO(
4407
password_encoding=unknown
4409
self.assertRaises(ValueError, conf.get_password,
4410
'ftp', 'foo.net', 'joe')
4412
def test_credentials_for_scheme_host(self):
4413
conf = config.AuthenticationConfig(_file=StringIO(
4414
"""# Identity on foo.net
4419
password=secret-pass
4422
self._got_user_passwd('joe', 'secret-pass', conf, 'ftp', 'foo.net')
4424
self._got_user_passwd(None, None, conf, 'http', 'foo.net')
4426
self._got_user_passwd(None, None, conf, 'ftp', 'bar.net')
4428
def test_credentials_for_host_port(self):
4429
conf = config.AuthenticationConfig(_file=StringIO(
4430
"""# Identity on foo.net
4436
password=secret-pass
4439
self._got_user_passwd('joe', 'secret-pass',
4440
conf, 'ftp', 'foo.net', port=10021)
4442
self._got_user_passwd(None, None, conf, 'ftp', 'foo.net')
4444
def test_for_matching_host(self):
4445
conf = config.AuthenticationConfig(_file=StringIO(
4446
"""# Identity on foo.net
4452
[sourceforge domain]
4459
self._got_user_passwd('georges', 'bendover',
4460
conf, 'bzr', 'foo.bzr.sf.net')
4462
self._got_user_passwd(None, None,
4463
conf, 'bzr', 'bbzr.sf.net')
4465
def test_for_matching_host_None(self):
4466
conf = config.AuthenticationConfig(_file=StringIO(
4467
"""# Identity on foo.net
4477
self._got_user_passwd('joe', 'joepass',
4478
conf, 'bzr', 'quux.net')
4479
# no host but different scheme
4480
self._got_user_passwd('georges', 'bendover',
4481
conf, 'ftp', 'quux.net')
4483
def test_credentials_for_path(self):
4484
conf = config.AuthenticationConfig(_file=StringIO(
4500
self._got_user_passwd(None, None,
4501
conf, 'http', host='bar.org', path='/dir3')
4503
self._got_user_passwd('georges', 'bendover',
4504
conf, 'http', host='bar.org', path='/dir2')
4506
self._got_user_passwd('jim', 'jimpass',
4507
conf, 'http', host='bar.org',path='/dir1/subdir')
4509
def test_credentials_for_user(self):
4510
conf = config.AuthenticationConfig(_file=StringIO(
4519
self._got_user_passwd('jim', 'jimpass',
4520
conf, 'http', 'bar.org')
4522
self._got_user_passwd('jim', 'jimpass',
4523
conf, 'http', 'bar.org', user='jim')
4524
# Don't get a different user if one is specified
4525
self._got_user_passwd(None, None,
4526
conf, 'http', 'bar.org', user='georges')
4528
def test_credentials_for_user_without_password(self):
4529
conf = config.AuthenticationConfig(_file=StringIO(
4536
# Get user but no password
4537
self._got_user_passwd('jim', None,
4538
conf, 'http', 'bar.org')
4540
def test_verify_certificates(self):
4541
conf = config.AuthenticationConfig(_file=StringIO(
4548
verify_certificates=False
4555
credentials = conf.get_credentials('https', 'bar.org')
4556
self.assertEquals(False, credentials.get('verify_certificates'))
4557
credentials = conf.get_credentials('https', 'foo.net')
4558
self.assertEquals(True, credentials.get('verify_certificates'))
4561
class TestAuthenticationStorage(tests.TestCaseInTempDir):
4563
def test_set_credentials(self):
4564
conf = config.AuthenticationConfig()
4565
conf.set_credentials('name', 'host', 'user', 'scheme', 'password',
4566
99, path='/foo', verify_certificates=False, realm='realm')
4567
credentials = conf.get_credentials(host='host', scheme='scheme',
4568
port=99, path='/foo',
4570
CREDENTIALS = {'name': 'name', 'user': 'user', 'password': 'password',
4571
'verify_certificates': False, 'scheme': 'scheme',
4572
'host': 'host', 'port': 99, 'path': '/foo',
4574
self.assertEqual(CREDENTIALS, credentials)
4575
credentials_from_disk = config.AuthenticationConfig().get_credentials(
4576
host='host', scheme='scheme', port=99, path='/foo', realm='realm')
4577
self.assertEqual(CREDENTIALS, credentials_from_disk)
4579
def test_reset_credentials_different_name(self):
4580
conf = config.AuthenticationConfig()
4581
conf.set_credentials('name', 'host', 'user', 'scheme', 'password'),
4582
conf.set_credentials('name2', 'host', 'user2', 'scheme', 'password'),
4583
self.assertIs(None, conf._get_config().get('name'))
4584
credentials = conf.get_credentials(host='host', scheme='scheme')
4585
CREDENTIALS = {'name': 'name2', 'user': 'user2', 'password':
4586
'password', 'verify_certificates': True,
4587
'scheme': 'scheme', 'host': 'host', 'port': None,
4588
'path': None, 'realm': None}
4589
self.assertEqual(CREDENTIALS, credentials)
4592
class TestAuthenticationConfig(tests.TestCase):
4593
"""Test AuthenticationConfig behaviour"""
4595
def _check_default_password_prompt(self, expected_prompt_format, scheme,
4596
host=None, port=None, realm=None,
4600
user, password = 'jim', 'precious'
4601
expected_prompt = expected_prompt_format % {
4602
'scheme': scheme, 'host': host, 'port': port,
4603
'user': user, 'realm': realm}
4605
stdout = tests.StringIOWrapper()
4606
stderr = tests.StringIOWrapper()
4607
ui.ui_factory = tests.TestUIFactory(stdin=password + '\n',
4608
stdout=stdout, stderr=stderr)
4609
# We use an empty conf so that the user is always prompted
4610
conf = config.AuthenticationConfig()
4611
self.assertEquals(password,
4612
conf.get_password(scheme, host, user, port=port,
4613
realm=realm, path=path))
4614
self.assertEquals(expected_prompt, stderr.getvalue())
4615
self.assertEquals('', stdout.getvalue())
4617
def _check_default_username_prompt(self, expected_prompt_format, scheme,
4618
host=None, port=None, realm=None,
4623
expected_prompt = expected_prompt_format % {
4624
'scheme': scheme, 'host': host, 'port': port,
4626
stdout = tests.StringIOWrapper()
4627
stderr = tests.StringIOWrapper()
4628
ui.ui_factory = tests.TestUIFactory(stdin=username+ '\n',
4629
stdout=stdout, stderr=stderr)
4630
# We use an empty conf so that the user is always prompted
4631
conf = config.AuthenticationConfig()
4632
self.assertEquals(username, conf.get_user(scheme, host, port=port,
4633
realm=realm, path=path, ask=True))
4634
self.assertEquals(expected_prompt, stderr.getvalue())
4635
self.assertEquals('', stdout.getvalue())
4637
def test_username_defaults_prompts(self):
4638
# HTTP prompts can't be tested here, see test_http.py
4639
self._check_default_username_prompt(u'FTP %(host)s username: ', 'ftp')
4640
self._check_default_username_prompt(
4641
u'FTP %(host)s:%(port)d username: ', 'ftp', port=10020)
4642
self._check_default_username_prompt(
4643
u'SSH %(host)s:%(port)d username: ', 'ssh', port=12345)
4645
def test_username_default_no_prompt(self):
4646
conf = config.AuthenticationConfig()
4647
self.assertEquals(None,
4648
conf.get_user('ftp', 'example.com'))
4649
self.assertEquals("explicitdefault",
4650
conf.get_user('ftp', 'example.com', default="explicitdefault"))
4652
def test_password_default_prompts(self):
4653
# HTTP prompts can't be tested here, see test_http.py
4654
self._check_default_password_prompt(
4655
u'FTP %(user)s@%(host)s password: ', 'ftp')
4656
self._check_default_password_prompt(
4657
u'FTP %(user)s@%(host)s:%(port)d password: ', 'ftp', port=10020)
4658
self._check_default_password_prompt(
4659
u'SSH %(user)s@%(host)s:%(port)d password: ', 'ssh', port=12345)
4660
# SMTP port handling is a bit special (it's handled if embedded in the
4662
# FIXME: should we: forbid that, extend it to other schemes, leave
4663
# things as they are that's fine thank you ?
4664
self._check_default_password_prompt(
4665
u'SMTP %(user)s@%(host)s password: ', 'smtp')
4666
self._check_default_password_prompt(
4667
u'SMTP %(user)s@%(host)s password: ', 'smtp', host='bar.org:10025')
4668
self._check_default_password_prompt(
4669
u'SMTP %(user)s@%(host)s:%(port)d password: ', 'smtp', port=10025)
4671
def test_ssh_password_emits_warning(self):
4672
conf = config.AuthenticationConfig(_file=StringIO(
4680
entered_password = 'typed-by-hand'
4681
stdout = tests.StringIOWrapper()
4682
stderr = tests.StringIOWrapper()
4683
ui.ui_factory = tests.TestUIFactory(stdin=entered_password + '\n',
4684
stdout=stdout, stderr=stderr)
4686
# Since the password defined in the authentication config is ignored,
4687
# the user is prompted
4688
self.assertEquals(entered_password,
4689
conf.get_password('ssh', 'bar.org', user='jim'))
4690
self.assertContainsRe(
4692
'password ignored in section \[ssh with password\]')
4694
def test_ssh_without_password_doesnt_emit_warning(self):
4695
conf = config.AuthenticationConfig(_file=StringIO(
4702
entered_password = 'typed-by-hand'
4703
stdout = tests.StringIOWrapper()
4704
stderr = tests.StringIOWrapper()
4705
ui.ui_factory = tests.TestUIFactory(stdin=entered_password + '\n',
4709
# Since the password defined in the authentication config is ignored,
4710
# the user is prompted
4711
self.assertEquals(entered_password,
4712
conf.get_password('ssh', 'bar.org', user='jim'))
4713
# No warning shoud be emitted since there is no password. We are only
4715
self.assertNotContainsRe(
4717
'password ignored in section \[ssh with password\]')
4719
def test_uses_fallback_stores(self):
4720
self.overrideAttr(config, 'credential_store_registry',
4721
config.CredentialStoreRegistry())
4722
store = StubCredentialStore()
4723
store.add_credentials("http", "example.com", "joe", "secret")
4724
config.credential_store_registry.register("stub", store, fallback=True)
4725
conf = config.AuthenticationConfig(_file=StringIO())
4726
creds = conf.get_credentials("http", "example.com")
4727
self.assertEquals("joe", creds["user"])
4728
self.assertEquals("secret", creds["password"])
4731
class StubCredentialStore(config.CredentialStore):
4737
def add_credentials(self, scheme, host, user, password=None):
4738
self._username[(scheme, host)] = user
4739
self._password[(scheme, host)] = password
4741
def get_credentials(self, scheme, host, port=None, user=None,
4742
path=None, realm=None):
4743
key = (scheme, host)
4744
if not key in self._username:
4746
return { "scheme": scheme, "host": host, "port": port,
4747
"user": self._username[key], "password": self._password[key]}
4750
class CountingCredentialStore(config.CredentialStore):
4755
def get_credentials(self, scheme, host, port=None, user=None,
4756
path=None, realm=None):
4761
class TestCredentialStoreRegistry(tests.TestCase):
4763
def _get_cs_registry(self):
4764
return config.credential_store_registry
4766
def test_default_credential_store(self):
4767
r = self._get_cs_registry()
4768
default = r.get_credential_store(None)
4769
self.assertIsInstance(default, config.PlainTextCredentialStore)
4771
def test_unknown_credential_store(self):
4772
r = self._get_cs_registry()
4773
# It's hard to imagine someone creating a credential store named
4774
# 'unknown' so we use that as an never registered key.
4775
self.assertRaises(KeyError, r.get_credential_store, 'unknown')
4777
def test_fallback_none_registered(self):
4778
r = config.CredentialStoreRegistry()
4779
self.assertEquals(None,
4780
r.get_fallback_credentials("http", "example.com"))
4782
def test_register(self):
4783
r = config.CredentialStoreRegistry()
4784
r.register("stub", StubCredentialStore(), fallback=False)
4785
r.register("another", StubCredentialStore(), fallback=True)
4786
self.assertEquals(["another", "stub"], r.keys())
4788
def test_register_lazy(self):
4789
r = config.CredentialStoreRegistry()
4790
r.register_lazy("stub", "bzrlib.tests.test_config",
4791
"StubCredentialStore", fallback=False)
4792
self.assertEquals(["stub"], r.keys())
4793
self.assertIsInstance(r.get_credential_store("stub"),
4794
StubCredentialStore)
4796
def test_is_fallback(self):
4797
r = config.CredentialStoreRegistry()
4798
r.register("stub1", None, fallback=False)
4799
r.register("stub2", None, fallback=True)
4800
self.assertEquals(False, r.is_fallback("stub1"))
4801
self.assertEquals(True, r.is_fallback("stub2"))
4803
def test_no_fallback(self):
4804
r = config.CredentialStoreRegistry()
4805
store = CountingCredentialStore()
4806
r.register("count", store, fallback=False)
4807
self.assertEquals(None,
4808
r.get_fallback_credentials("http", "example.com"))
4809
self.assertEquals(0, store._calls)
4811
def test_fallback_credentials(self):
4812
r = config.CredentialStoreRegistry()
4813
store = StubCredentialStore()
4814
store.add_credentials("http", "example.com",
4815
"somebody", "geheim")
4816
r.register("stub", store, fallback=True)
4817
creds = r.get_fallback_credentials("http", "example.com")
4818
self.assertEquals("somebody", creds["user"])
4819
self.assertEquals("geheim", creds["password"])
4821
def test_fallback_first_wins(self):
4822
r = config.CredentialStoreRegistry()
4823
stub1 = StubCredentialStore()
4824
stub1.add_credentials("http", "example.com",
4825
"somebody", "stub1")
4826
r.register("stub1", stub1, fallback=True)
4827
stub2 = StubCredentialStore()
4828
stub2.add_credentials("http", "example.com",
4829
"somebody", "stub2")
4830
r.register("stub2", stub1, fallback=True)
4831
creds = r.get_fallback_credentials("http", "example.com")
4832
self.assertEquals("somebody", creds["user"])
4833
self.assertEquals("stub1", creds["password"])
4836
class TestPlainTextCredentialStore(tests.TestCase):
4838
def test_decode_password(self):
4839
r = config.credential_store_registry
4840
plain_text = r.get_credential_store()
4841
decoded = plain_text.decode_password(dict(password='secret'))
4842
self.assertEquals('secret', decoded)
4845
# FIXME: Once we have a way to declare authentication to all test servers, we
4846
# can implement generic tests.
4847
# test_user_password_in_url
4848
# test_user_in_url_password_from_config
4849
# test_user_in_url_password_prompted
4850
# test_user_in_config
4851
# test_user_getpass.getuser
4852
# test_user_prompted ?
4853
class TestAuthenticationRing(tests.TestCaseWithTransport):
4857
class TestAutoUserId(tests.TestCase):
4858
"""Test inferring an automatic user name."""
4860
def test_auto_user_id(self):
4861
"""Automatic inference of user name.
4863
This is a bit hard to test in an isolated way, because it depends on
4864
system functions that go direct to /etc or perhaps somewhere else.
4865
But it's reasonable to say that on Unix, with an /etc/mailname, we ought
4866
to be able to choose a user name with no configuration.
4868
if sys.platform == 'win32':
4869
raise tests.TestSkipped(
4870
"User name inference not implemented on win32")
4871
realname, address = config._auto_user_id()
4872
if os.path.exists('/etc/mailname'):
4873
self.assertIsNot(None, realname)
4874
self.assertIsNot(None, address)
4876
self.assertEquals((None, None), (realname, address))
4879
class EmailOptionTests(tests.TestCase):
4881
def test_default_email_uses_BZR_EMAIL(self):
4882
conf = config.MemoryStack('email=jelmer@debian.org')
4883
# BZR_EMAIL takes precedence over EMAIL
4884
self.overrideEnv('BZR_EMAIL', 'jelmer@samba.org')
4885
self.overrideEnv('EMAIL', 'jelmer@apache.org')
4886
self.assertEquals('jelmer@samba.org', conf.get('email'))
4888
def test_default_email_uses_EMAIL(self):
4889
conf = config.MemoryStack('')
4890
self.overrideEnv('BZR_EMAIL', None)
4891
self.overrideEnv('EMAIL', 'jelmer@apache.org')
4892
self.assertEquals('jelmer@apache.org', conf.get('email'))
4894
def test_BZR_EMAIL_overrides(self):
4895
conf = config.MemoryStack('email=jelmer@debian.org')
4896
self.overrideEnv('BZR_EMAIL', 'jelmer@apache.org')
4897
self.assertEquals('jelmer@apache.org', conf.get('email'))
4898
self.overrideEnv('BZR_EMAIL', None)
4899
self.overrideEnv('EMAIL', 'jelmer@samba.org')
4900
self.assertEquals('jelmer@debian.org', conf.get('email'))
4903
class MailClientOptionTests(tests.TestCase):
4905
def test_default(self):
4906
conf = config.MemoryStack('')
4907
client = conf.get('mail_client')
4908
self.assertIs(client, mail_client.DefaultMail)
4910
def test_evolution(self):
4911
conf = config.MemoryStack('mail_client=evolution')
4912
client = conf.get('mail_client')
4913
self.assertIs(client, mail_client.Evolution)
4915
def test_kmail(self):
4916
conf = config.MemoryStack('mail_client=kmail')
4917
client = conf.get('mail_client')
4918
self.assertIs(client, mail_client.KMail)
4920
def test_mutt(self):
4921
conf = config.MemoryStack('mail_client=mutt')
4922
client = conf.get('mail_client')
4923
self.assertIs(client, mail_client.Mutt)
4925
def test_thunderbird(self):
4926
conf = config.MemoryStack('mail_client=thunderbird')
4927
client = conf.get('mail_client')
4928
self.assertIs(client, mail_client.Thunderbird)
4930
def test_explicit_default(self):
4931
conf = config.MemoryStack('mail_client=default')
4932
client = conf.get('mail_client')
4933
self.assertIs(client, mail_client.DefaultMail)
4935
def test_editor(self):
4936
conf = config.MemoryStack('mail_client=editor')
4937
client = conf.get('mail_client')
4938
self.assertIs(client, mail_client.Editor)
4940
def test_mapi(self):
4941
conf = config.MemoryStack('mail_client=mapi')
4942
client = conf.get('mail_client')
4943
self.assertIs(client, mail_client.MAPIClient)
4945
def test_xdg_email(self):
4946
conf = config.MemoryStack('mail_client=xdg-email')
4947
client = conf.get('mail_client')
4948
self.assertIs(client, mail_client.XDGEmail)
4950
def test_unknown(self):
4951
conf = config.MemoryStack('mail_client=firebird')
4952
self.assertRaises(errors.ConfigOptionValueError, conf.get,