1
# Copyright (C) 2005-2011 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
25
from testtools import matchers
27
#import bzrlib specific imports here
42
from bzrlib.symbol_versioning import (
45
from bzrlib.transport import remote as transport_remote
46
from bzrlib.tests import (
51
from bzrlib.util.configobj import configobj
54
def lockable_config_scenarios():
57
{'config_class': config.GlobalConfig,
59
'config_section': 'DEFAULT'}),
61
{'config_class': config.LocationConfig,
63
'config_section': '.'}),]
66
load_tests = scenarios.load_tests_apply_scenarios
68
# Register helpers to build stores
69
config.test_store_builder_registry.register(
70
'configobj', lambda test: config.TransportIniFileStore(
71
test.get_transport(), 'configobj.conf'))
72
config.test_store_builder_registry.register(
73
'bazaar', lambda test: config.GlobalStore())
74
config.test_store_builder_registry.register(
75
'location', lambda test: config.LocationStore())
78
def build_backing_branch(test, relpath,
79
transport_class=None, server_class=None):
80
"""Test helper to create a backing branch only once.
82
Some tests needs multiple stores/stacks to check concurrent update
83
behaviours. As such, they need to build different branch *objects* even if
84
they share the branch on disk.
86
:param relpath: The relative path to the branch. (Note that the helper
87
should always specify the same relpath).
89
:param transport_class: The Transport class the test needs to use.
91
:param server_class: The server associated with the ``transport_class``
94
Either both or neither of ``transport_class`` and ``server_class`` should
97
if transport_class is not None and server_class is not None:
98
test.transport_class = transport_class
99
test.transport_server = server_class
100
elif not (transport_class is None and server_class is None):
101
raise AssertionError('Specify both ``transport_class`` and '
102
'``server_class`` or neither of them')
103
if getattr(test, 'backing_branch', None) is None:
104
# First call, let's build the branch on disk
105
test.backing_branch = test.make_branch(relpath)
108
def build_branch_store(test):
109
build_backing_branch(test, 'branch')
110
b = branch.Branch.open('branch')
111
return config.BranchStore(b)
112
config.test_store_builder_registry.register('branch', build_branch_store)
115
def build_control_store(test):
116
build_backing_branch(test, 'branch')
117
b = bzrdir.BzrDir.open('branch')
118
return config.ControlStore(b)
119
config.test_store_builder_registry.register('control', build_control_store)
122
def build_remote_branch_store(test):
123
# There is only one permutation (but we won't be able to handle more with
124
# this design anyway)
126
server_class) = transport_remote.get_test_permutations()[0]
127
build_backing_branch(test, 'branch', transport_class, server_class)
128
b = branch.Branch.open(test.get_url('branch'))
129
return config.BranchStore(b)
130
config.test_store_builder_registry.register('remote_branch',
131
build_remote_branch_store)
134
config.test_stack_builder_registry.register(
135
'bazaar', lambda test: config.GlobalStack())
136
config.test_stack_builder_registry.register(
137
'location', lambda test: config.LocationStack('.'))
140
def build_branch_stack(test):
141
build_backing_branch(test, 'branch')
142
b = branch.Branch.open('branch')
143
return config.BranchStack(b)
144
config.test_stack_builder_registry.register('branch', build_branch_stack)
147
def build_branch_only_stack(test):
148
# There is only one permutation (but we won't be able to handle more with
149
# this design anyway)
151
server_class) = transport_remote.get_test_permutations()[0]
152
build_backing_branch(test, 'branch', transport_class, server_class)
153
b = branch.Branch.open(test.get_url('branch'))
154
return config.BranchOnlyStack(b)
155
config.test_stack_builder_registry.register('branch_only',
156
build_branch_only_stack)
158
def build_remote_control_stack(test):
159
# There is only one permutation (but we won't be able to handle more with
160
# this design anyway)
162
server_class) = transport_remote.get_test_permutations()[0]
163
# We need only a bzrdir for this, not a full branch, but it's not worth
164
# creating a dedicated helper to create only the bzrdir
165
build_backing_branch(test, 'branch', transport_class, server_class)
166
b = branch.Branch.open(test.get_url('branch'))
167
return config.RemoteControlStack(b.bzrdir)
168
config.test_stack_builder_registry.register('remote_control',
169
build_remote_control_stack)
172
sample_long_alias="log -r-15..-1 --line"
173
sample_config_text = u"""
175
email=Erik B\u00e5gfors <erik@bagfors.nu>
177
change_editor=vimdiff -of @new_path @old_path
178
gpg_signing_command=gnome-gpg
179
gpg_signing_key=DD4D5088
181
validate_signatures_in_log=true
183
user_global_option=something
184
bzr.mergetool.sometool=sometool {base} {this} {other} -o {result}
185
bzr.mergetool.funkytool=funkytool "arg with spaces" {this_temp}
186
bzr.mergetool.newtool='"newtool with spaces" {this_temp}'
187
bzr.default_mergetool=sometool
190
ll=""" + sample_long_alias + "\n"
193
sample_always_signatures = """
195
check_signatures=ignore
196
create_signatures=always
199
sample_ignore_signatures = """
201
check_signatures=require
202
create_signatures=never
205
sample_maybe_signatures = """
207
check_signatures=ignore
208
create_signatures=when-required
211
sample_branches_text = """
212
[http://www.example.com]
214
email=Robert Collins <robertc@example.org>
215
normal_option = normal
216
appendpath_option = append
217
appendpath_option:policy = appendpath
218
norecurse_option = norecurse
219
norecurse_option:policy = norecurse
220
[http://www.example.com/ignoreparent]
221
# different project: ignore parent dir config
223
[http://www.example.com/norecurse]
224
# configuration items that only apply to this dir
226
normal_option = norecurse
227
[http://www.example.com/dir]
228
appendpath_option = normal
230
check_signatures=require
231
# test trailing / matching with no children
233
check_signatures=check-available
234
gpg_signing_command=false
235
gpg_signing_key=default
236
user_local_option=local
237
# test trailing / matching
239
#subdirs will match but not the parent
241
check_signatures=ignore
242
post_commit=bzrlib.tests.test_config.post_commit
243
#testing explicit beats globs
247
def create_configs(test):
248
"""Create configuration files for a given test.
250
This requires creating a tree (and populate the ``test.tree`` attribute)
251
and its associated branch and will populate the following attributes:
253
- branch_config: A BranchConfig for the associated branch.
255
- locations_config : A LocationConfig for the associated branch
257
- bazaar_config: A GlobalConfig.
259
The tree and branch are created in a 'tree' subdirectory so the tests can
260
still use the test directory to stay outside of the branch.
262
tree = test.make_branch_and_tree('tree')
264
test.branch_config = config.BranchConfig(tree.branch)
265
test.locations_config = config.LocationConfig(tree.basedir)
266
test.bazaar_config = config.GlobalConfig()
269
def create_configs_with_file_option(test):
270
"""Create configuration files with a ``file`` option set in each.
272
This builds on ``create_configs`` and add one ``file`` option in each
273
configuration with a value which allows identifying the configuration file.
276
test.bazaar_config.set_user_option('file', 'bazaar')
277
test.locations_config.set_user_option('file', 'locations')
278
test.branch_config.set_user_option('file', 'branch')
281
class TestOptionsMixin:
283
def assertOptions(self, expected, conf):
284
# We don't care about the parser (as it will make tests hard to write
285
# and error-prone anyway)
286
self.assertThat([opt[:4] for opt in conf._get_options()],
287
matchers.Equals(expected))
290
class InstrumentedConfigObj(object):
291
"""A config obj look-enough-alike to record calls made to it."""
293
def __contains__(self, thing):
294
self._calls.append(('__contains__', thing))
297
def __getitem__(self, key):
298
self._calls.append(('__getitem__', key))
301
def __init__(self, input, encoding=None):
302
self._calls = [('__init__', input, encoding)]
304
def __setitem__(self, key, value):
305
self._calls.append(('__setitem__', key, value))
307
def __delitem__(self, key):
308
self._calls.append(('__delitem__', key))
311
self._calls.append(('keys',))
315
self._calls.append(('reload',))
317
def write(self, arg):
318
self._calls.append(('write',))
320
def as_bool(self, value):
321
self._calls.append(('as_bool', value))
324
def get_value(self, section, name):
325
self._calls.append(('get_value', section, name))
329
class FakeBranch(object):
331
def __init__(self, base=None):
333
self.base = "http://example.com/branches/demo"
336
self._transport = self.control_files = \
337
FakeControlFilesAndTransport()
339
def _get_config(self):
340
return config.TransportConfig(self._transport, 'branch.conf')
342
def lock_write(self):
349
class FakeControlFilesAndTransport(object):
353
self._transport = self
355
def get(self, filename):
358
return StringIO(self.files[filename])
360
raise errors.NoSuchFile(filename)
362
def get_bytes(self, filename):
365
return self.files[filename]
367
raise errors.NoSuchFile(filename)
369
def put(self, filename, fileobj):
370
self.files[filename] = fileobj.read()
372
def put_file(self, filename, fileobj):
373
return self.put(filename, fileobj)
376
class InstrumentedConfig(config.Config):
377
"""An instrumented config that supplies stubs for template methods."""
380
super(InstrumentedConfig, self).__init__()
382
self._signatures = config.CHECK_NEVER
384
def _get_user_id(self):
385
self._calls.append('_get_user_id')
386
return "Robert Collins <robert.collins@example.org>"
388
def _get_signature_checking(self):
389
self._calls.append('_get_signature_checking')
390
return self._signatures
392
def _get_change_editor(self):
393
self._calls.append('_get_change_editor')
394
return 'vimdiff -fo @new_path @old_path'
397
bool_config = """[DEFAULT]
406
class TestConfigObj(tests.TestCase):
408
def test_get_bool(self):
409
co = config.ConfigObj(StringIO(bool_config))
410
self.assertIs(co.get_bool('DEFAULT', 'active'), True)
411
self.assertIs(co.get_bool('DEFAULT', 'inactive'), False)
412
self.assertIs(co.get_bool('UPPERCASE', 'active'), True)
413
self.assertIs(co.get_bool('UPPERCASE', 'nonactive'), False)
415
def test_hash_sign_in_value(self):
417
Before 4.5.0, ConfigObj did not quote # signs in values, so they'd be
418
treated as comments when read in again. (#86838)
420
co = config.ConfigObj()
421
co['test'] = 'foo#bar'
423
co.write(outfile=outfile)
424
lines = outfile.getvalue().splitlines()
425
self.assertEqual(lines, ['test = "foo#bar"'])
426
co2 = config.ConfigObj(lines)
427
self.assertEqual(co2['test'], 'foo#bar')
429
def test_triple_quotes(self):
430
# Bug #710410: if the value string has triple quotes
431
# then ConfigObj versions up to 4.7.2 will quote them wrong
432
# and won't able to read them back
433
triple_quotes_value = '''spam
434
""" that's my spam """
436
co = config.ConfigObj()
437
co['test'] = triple_quotes_value
438
# While writing this test another bug in ConfigObj has been found:
439
# method co.write() without arguments produces list of lines
440
# one option per line, and multiline values are not split
441
# across multiple lines,
442
# and that breaks the parsing these lines back by ConfigObj.
443
# This issue only affects test, but it's better to avoid
444
# `co.write()` construct at all.
445
# [bialix 20110222] bug report sent to ConfigObj's author
447
co.write(outfile=outfile)
448
output = outfile.getvalue()
449
# now we're trying to read it back
450
co2 = config.ConfigObj(StringIO(output))
451
self.assertEquals(triple_quotes_value, co2['test'])
454
erroneous_config = """[section] # line 1
457
whocares=notme # line 4
461
class TestConfigObjErrors(tests.TestCase):
463
def test_duplicate_section_name_error_line(self):
465
co = configobj.ConfigObj(StringIO(erroneous_config),
467
except config.configobj.DuplicateError, e:
468
self.assertEqual(3, e.line_number)
470
self.fail('Error in config file not detected')
473
class TestConfig(tests.TestCase):
475
def test_constructs(self):
478
def test_no_default_editor(self):
481
self.applyDeprecated, deprecated_in((2, 4, 0)),
482
config.Config().get_editor)
484
def test_user_email(self):
485
my_config = InstrumentedConfig()
486
self.assertEqual('robert.collins@example.org', my_config.user_email())
487
self.assertEqual(['_get_user_id'], my_config._calls)
489
def test_username(self):
490
my_config = InstrumentedConfig()
491
self.assertEqual('Robert Collins <robert.collins@example.org>',
492
my_config.username())
493
self.assertEqual(['_get_user_id'], my_config._calls)
495
def test_signatures_default(self):
496
my_config = config.Config()
498
self.applyDeprecated(deprecated_in((2, 5, 0)),
499
my_config.signature_needed))
500
self.assertEqual(config.CHECK_IF_POSSIBLE,
501
self.applyDeprecated(deprecated_in((2, 5, 0)),
502
my_config.signature_checking))
503
self.assertEqual(config.SIGN_WHEN_REQUIRED,
504
self.applyDeprecated(deprecated_in((2, 5, 0)),
505
my_config.signing_policy))
507
def test_signatures_template_method(self):
508
my_config = InstrumentedConfig()
509
self.assertEqual(config.CHECK_NEVER,
510
self.applyDeprecated(deprecated_in((2, 5, 0)),
511
my_config.signature_checking))
512
self.assertEqual(['_get_signature_checking'], my_config._calls)
514
def test_signatures_template_method_none(self):
515
my_config = InstrumentedConfig()
516
my_config._signatures = None
517
self.assertEqual(config.CHECK_IF_POSSIBLE,
518
self.applyDeprecated(deprecated_in((2, 5, 0)),
519
my_config.signature_checking))
520
self.assertEqual(['_get_signature_checking'], my_config._calls)
522
def test_gpg_signing_command_default(self):
523
my_config = config.Config()
524
self.assertEqual('gpg',
525
self.applyDeprecated(deprecated_in((2, 5, 0)),
526
my_config.gpg_signing_command))
528
def test_get_user_option_default(self):
529
my_config = config.Config()
530
self.assertEqual(None, my_config.get_user_option('no_option'))
532
def test_post_commit_default(self):
533
my_config = config.Config()
534
self.assertEqual(None, self.applyDeprecated(deprecated_in((2, 5, 0)),
535
my_config.post_commit))
538
def test_log_format_default(self):
539
my_config = config.Config()
540
self.assertEqual('long',
541
self.applyDeprecated(deprecated_in((2, 5, 0)),
542
my_config.log_format))
544
def test_acceptable_keys_default(self):
545
my_config = config.Config()
546
self.assertEqual(None, self.applyDeprecated(deprecated_in((2, 5, 0)),
547
my_config.acceptable_keys))
549
def test_validate_signatures_in_log_default(self):
550
my_config = config.Config()
551
self.assertEqual(False, my_config.validate_signatures_in_log())
553
def test_get_change_editor(self):
554
my_config = InstrumentedConfig()
555
change_editor = my_config.get_change_editor('old_tree', 'new_tree')
556
self.assertEqual(['_get_change_editor'], my_config._calls)
557
self.assertIs(diff.DiffFromTool, change_editor.__class__)
558
self.assertEqual(['vimdiff', '-fo', '@new_path', '@old_path'],
559
change_editor.command_template)
562
class TestConfigPath(tests.TestCase):
565
super(TestConfigPath, self).setUp()
566
self.overrideEnv('HOME', '/home/bogus')
567
self.overrideEnv('XDG_CACHE_DIR', '')
568
if sys.platform == 'win32':
570
'BZR_HOME', r'C:\Documents and Settings\bogus\Application Data')
572
'C:/Documents and Settings/bogus/Application Data/bazaar/2.0'
574
self.bzr_home = '/home/bogus/.bazaar'
576
def test_config_dir(self):
577
self.assertEqual(config.config_dir(), self.bzr_home)
579
def test_config_filename(self):
580
self.assertEqual(config.config_filename(),
581
self.bzr_home + '/bazaar.conf')
583
def test_locations_config_filename(self):
584
self.assertEqual(config.locations_config_filename(),
585
self.bzr_home + '/locations.conf')
587
def test_authentication_config_filename(self):
588
self.assertEqual(config.authentication_config_filename(),
589
self.bzr_home + '/authentication.conf')
591
def test_xdg_cache_dir(self):
592
self.assertEqual(config.xdg_cache_dir(),
593
'/home/bogus/.cache')
596
class TestXDGConfigDir(tests.TestCaseInTempDir):
597
# must be in temp dir because config tests for the existence of the bazaar
598
# subdirectory of $XDG_CONFIG_HOME
601
if sys.platform in ('darwin', 'win32'):
602
raise tests.TestNotApplicable(
603
'XDG config dir not used on this platform')
604
super(TestXDGConfigDir, self).setUp()
605
self.overrideEnv('HOME', self.test_home_dir)
606
# BZR_HOME overrides everything we want to test so unset it.
607
self.overrideEnv('BZR_HOME', None)
609
def test_xdg_config_dir_exists(self):
610
"""When ~/.config/bazaar exists, use it as the config dir."""
611
newdir = osutils.pathjoin(self.test_home_dir, '.config', 'bazaar')
613
self.assertEqual(config.config_dir(), newdir)
615
def test_xdg_config_home(self):
616
"""When XDG_CONFIG_HOME is set, use it."""
617
xdgconfigdir = osutils.pathjoin(self.test_home_dir, 'xdgconfig')
618
self.overrideEnv('XDG_CONFIG_HOME', xdgconfigdir)
619
newdir = osutils.pathjoin(xdgconfigdir, 'bazaar')
621
self.assertEqual(config.config_dir(), newdir)
624
class TestIniConfig(tests.TestCaseInTempDir):
626
def make_config_parser(self, s):
627
conf = config.IniBasedConfig.from_string(s)
628
return conf, conf._get_parser()
631
class TestIniConfigBuilding(TestIniConfig):
633
def test_contructs(self):
634
my_config = config.IniBasedConfig()
636
def test_from_fp(self):
637
my_config = config.IniBasedConfig.from_string(sample_config_text)
638
self.assertIsInstance(my_config._get_parser(), configobj.ConfigObj)
640
def test_cached(self):
641
my_config = config.IniBasedConfig.from_string(sample_config_text)
642
parser = my_config._get_parser()
643
self.assertTrue(my_config._get_parser() is parser)
645
def _dummy_chown(self, path, uid, gid):
646
self.path, self.uid, self.gid = path, uid, gid
648
def test_ini_config_ownership(self):
649
"""Ensure that chown is happening during _write_config_file"""
650
self.requireFeature(features.chown_feature)
651
self.overrideAttr(os, 'chown', self._dummy_chown)
652
self.path = self.uid = self.gid = None
653
conf = config.IniBasedConfig(file_name='./foo.conf')
654
conf._write_config_file()
655
self.assertEquals(self.path, './foo.conf')
656
self.assertTrue(isinstance(self.uid, int))
657
self.assertTrue(isinstance(self.gid, int))
659
def test_get_filename_parameter_is_deprecated_(self):
660
conf = self.callDeprecated([
661
'IniBasedConfig.__init__(get_filename) was deprecated in 2.3.'
662
' Use file_name instead.'],
663
config.IniBasedConfig, lambda: 'ini.conf')
664
self.assertEqual('ini.conf', conf.file_name)
666
def test_get_parser_file_parameter_is_deprecated_(self):
667
config_file = StringIO(sample_config_text.encode('utf-8'))
668
conf = config.IniBasedConfig.from_string(sample_config_text)
669
conf = self.callDeprecated([
670
'IniBasedConfig._get_parser(file=xxx) was deprecated in 2.3.'
671
' Use IniBasedConfig(_content=xxx) instead.'],
672
conf._get_parser, file=config_file)
675
class TestIniConfigSaving(tests.TestCaseInTempDir):
677
def test_cant_save_without_a_file_name(self):
678
conf = config.IniBasedConfig()
679
self.assertRaises(AssertionError, conf._write_config_file)
681
def test_saved_with_content(self):
682
content = 'foo = bar\n'
683
conf = config.IniBasedConfig.from_string(
684
content, file_name='./test.conf', save=True)
685
self.assertFileEqual(content, 'test.conf')
688
class TestIniConfigOptionExpansionDefaultValue(tests.TestCaseInTempDir):
689
"""What is the default value of expand for config options.
691
This is an opt-in beta feature used to evaluate whether or not option
692
references can appear in dangerous place raising exceptions, disapearing
693
(and as such corrupting data) or if it's safe to activate the option by
696
Note that these tests relies on config._expand_default_value being already
697
overwritten in the parent class setUp.
701
super(TestIniConfigOptionExpansionDefaultValue, self).setUp()
705
self.warnings.append(args[0] % args[1:])
706
self.overrideAttr(trace, 'warning', warning)
708
def get_config(self, expand):
709
c = config.GlobalConfig.from_string('bzr.config.expand=%s' % (expand,),
713
def assertExpandIs(self, expected):
714
actual = config._get_expand_default_value()
715
#self.config.get_user_option_as_bool('bzr.config.expand')
716
self.assertEquals(expected, actual)
718
def test_default_is_None(self):
719
self.assertEquals(None, config._expand_default_value)
721
def test_default_is_False_even_if_None(self):
722
self.config = self.get_config(None)
723
self.assertExpandIs(False)
725
def test_default_is_False_even_if_invalid(self):
726
self.config = self.get_config('<your choice>')
727
self.assertExpandIs(False)
729
# Huh ? My choice is False ? Thanks, always happy to hear that :D
730
# Wait, you've been warned !
731
self.assertLength(1, self.warnings)
733
'Value "<your choice>" is not a boolean for "bzr.config.expand"',
736
def test_default_is_True(self):
737
self.config = self.get_config(True)
738
self.assertExpandIs(True)
740
def test_default_is_False(self):
741
self.config = self.get_config(False)
742
self.assertExpandIs(False)
745
class TestIniConfigOptionExpansion(tests.TestCase):
746
"""Test option expansion from the IniConfig level.
748
What we really want here is to test the Config level, but the class being
749
abstract as far as storing values is concerned, this can't be done
752
# FIXME: This should be rewritten when all configs share a storage
753
# implementation -- vila 2011-02-18
755
def get_config(self, string=None):
758
c = config.IniBasedConfig.from_string(string)
761
def assertExpansion(self, expected, conf, string, env=None):
762
self.assertEquals(expected, conf.expand_options(string, env))
764
def test_no_expansion(self):
765
c = self.get_config('')
766
self.assertExpansion('foo', c, 'foo')
768
def test_env_adding_options(self):
769
c = self.get_config('')
770
self.assertExpansion('bar', c, '{foo}', {'foo': 'bar'})
772
def test_env_overriding_options(self):
773
c = self.get_config('foo=baz')
774
self.assertExpansion('bar', c, '{foo}', {'foo': 'bar'})
776
def test_simple_ref(self):
777
c = self.get_config('foo=xxx')
778
self.assertExpansion('xxx', c, '{foo}')
780
def test_unknown_ref(self):
781
c = self.get_config('')
782
self.assertRaises(errors.ExpandingUnknownOption,
783
c.expand_options, '{foo}')
785
def test_indirect_ref(self):
786
c = self.get_config('''
790
self.assertExpansion('xxx', c, '{bar}')
792
def test_embedded_ref(self):
793
c = self.get_config('''
797
self.assertExpansion('xxx', c, '{{bar}}')
799
def test_simple_loop(self):
800
c = self.get_config('foo={foo}')
801
self.assertRaises(errors.OptionExpansionLoop, c.expand_options, '{foo}')
803
def test_indirect_loop(self):
804
c = self.get_config('''
808
e = self.assertRaises(errors.OptionExpansionLoop,
809
c.expand_options, '{foo}')
810
self.assertEquals('foo->bar->baz', e.refs)
811
self.assertEquals('{foo}', e.string)
814
conf = self.get_config('''
818
list={foo},{bar},{baz}
820
self.assertEquals(['start', 'middle', 'end'],
821
conf.get_user_option('list', expand=True))
823
def test_cascading_list(self):
824
conf = self.get_config('''
830
self.assertEquals(['start', 'middle', 'end'],
831
conf.get_user_option('list', expand=True))
833
def test_pathological_hidden_list(self):
834
conf = self.get_config('''
840
hidden={start}{middle}{end}
842
# Nope, it's either a string or a list, and the list wins as soon as a
843
# ',' appears, so the string concatenation never occur.
844
self.assertEquals(['{foo', '}', '{', 'bar}'],
845
conf.get_user_option('hidden', expand=True))
848
class TestLocationConfigOptionExpansion(tests.TestCaseInTempDir):
850
def get_config(self, location, string=None):
853
# Since we don't save the config we won't strictly require to inherit
854
# from TestCaseInTempDir, but an error occurs so quickly...
855
c = config.LocationConfig.from_string(string, location)
858
def test_dont_cross_unrelated_section(self):
859
c = self.get_config('/another/branch/path','''
864
[/another/branch/path]
867
self.assertRaises(errors.ExpandingUnknownOption,
868
c.get_user_option, 'bar', expand=True)
870
def test_cross_related_sections(self):
871
c = self.get_config('/project/branch/path','''
875
[/project/branch/path]
878
self.assertEquals('quux', c.get_user_option('bar', expand=True))
881
class TestIniBaseConfigOnDisk(tests.TestCaseInTempDir):
883
def test_cannot_reload_without_name(self):
884
conf = config.IniBasedConfig.from_string(sample_config_text)
885
self.assertRaises(AssertionError, conf.reload)
887
def test_reload_see_new_value(self):
888
c1 = config.IniBasedConfig.from_string('editor=vim\n',
889
file_name='./test/conf')
890
c1._write_config_file()
891
c2 = config.IniBasedConfig.from_string('editor=emacs\n',
892
file_name='./test/conf')
893
c2._write_config_file()
894
self.assertEqual('vim', c1.get_user_option('editor'))
895
self.assertEqual('emacs', c2.get_user_option('editor'))
896
# Make sure we get the Right value
898
self.assertEqual('emacs', c1.get_user_option('editor'))
901
class TestLockableConfig(tests.TestCaseInTempDir):
903
scenarios = lockable_config_scenarios()
908
config_section = None
911
super(TestLockableConfig, self).setUp()
912
self._content = '[%s]\none=1\ntwo=2\n' % (self.config_section,)
913
self.config = self.create_config(self._content)
915
def get_existing_config(self):
916
return self.config_class(*self.config_args)
918
def create_config(self, content):
919
kwargs = dict(save=True)
920
c = self.config_class.from_string(content, *self.config_args, **kwargs)
923
def test_simple_read_access(self):
924
self.assertEquals('1', self.config.get_user_option('one'))
926
def test_simple_write_access(self):
927
self.config.set_user_option('one', 'one')
928
self.assertEquals('one', self.config.get_user_option('one'))
930
def test_listen_to_the_last_speaker(self):
932
c2 = self.get_existing_config()
933
c1.set_user_option('one', 'ONE')
934
c2.set_user_option('two', 'TWO')
935
self.assertEquals('ONE', c1.get_user_option('one'))
936
self.assertEquals('TWO', c2.get_user_option('two'))
937
# The second update respect the first one
938
self.assertEquals('ONE', c2.get_user_option('one'))
940
def test_last_speaker_wins(self):
941
# If the same config is not shared, the same variable modified twice
942
# can only see a single result.
944
c2 = self.get_existing_config()
945
c1.set_user_option('one', 'c1')
946
c2.set_user_option('one', 'c2')
947
self.assertEquals('c2', c2._get_user_option('one'))
948
# The first modification is still available until another refresh
950
self.assertEquals('c1', c1._get_user_option('one'))
951
c1.set_user_option('two', 'done')
952
self.assertEquals('c2', c1._get_user_option('one'))
954
def test_writes_are_serialized(self):
956
c2 = self.get_existing_config()
958
# We spawn a thread that will pause *during* the write
959
before_writing = threading.Event()
960
after_writing = threading.Event()
961
writing_done = threading.Event()
962
c1_orig = c1._write_config_file
963
def c1_write_config_file():
966
# The lock is held. We wait for the main thread to decide when to
969
c1._write_config_file = c1_write_config_file
971
c1.set_user_option('one', 'c1')
973
t1 = threading.Thread(target=c1_set_option)
974
# Collect the thread after the test
975
self.addCleanup(t1.join)
976
# Be ready to unblock the thread if the test goes wrong
977
self.addCleanup(after_writing.set)
979
before_writing.wait()
980
self.assertTrue(c1._lock.is_held)
981
self.assertRaises(errors.LockContention,
982
c2.set_user_option, 'one', 'c2')
983
self.assertEquals('c1', c1.get_user_option('one'))
984
# Let the lock be released
987
c2.set_user_option('one', 'c2')
988
self.assertEquals('c2', c2.get_user_option('one'))
990
def test_read_while_writing(self):
992
# We spawn a thread that will pause *during* the write
993
ready_to_write = threading.Event()
994
do_writing = threading.Event()
995
writing_done = threading.Event()
996
c1_orig = c1._write_config_file
997
def c1_write_config_file():
999
# The lock is held. We wait for the main thread to decide when to
1004
c1._write_config_file = c1_write_config_file
1005
def c1_set_option():
1006
c1.set_user_option('one', 'c1')
1007
t1 = threading.Thread(target=c1_set_option)
1008
# Collect the thread after the test
1009
self.addCleanup(t1.join)
1010
# Be ready to unblock the thread if the test goes wrong
1011
self.addCleanup(do_writing.set)
1013
# Ensure the thread is ready to write
1014
ready_to_write.wait()
1015
self.assertTrue(c1._lock.is_held)
1016
self.assertEquals('c1', c1.get_user_option('one'))
1017
# If we read during the write, we get the old value
1018
c2 = self.get_existing_config()
1019
self.assertEquals('1', c2.get_user_option('one'))
1020
# Let the writing occur and ensure it occurred
1023
# Now we get the updated value
1024
c3 = self.get_existing_config()
1025
self.assertEquals('c1', c3.get_user_option('one'))
1028
class TestGetUserOptionAs(TestIniConfig):
1030
def test_get_user_option_as_bool(self):
1031
conf, parser = self.make_config_parser("""
1034
an_invalid_bool = maybe
1035
a_list = hmm, who knows ? # This is interpreted as a list !
1037
get_bool = conf.get_user_option_as_bool
1038
self.assertEqual(True, get_bool('a_true_bool'))
1039
self.assertEqual(False, get_bool('a_false_bool'))
1042
warnings.append(args[0] % args[1:])
1043
self.overrideAttr(trace, 'warning', warning)
1044
msg = 'Value "%s" is not a boolean for "%s"'
1045
self.assertIs(None, get_bool('an_invalid_bool'))
1046
self.assertEquals(msg % ('maybe', 'an_invalid_bool'), warnings[0])
1048
self.assertIs(None, get_bool('not_defined_in_this_config'))
1049
self.assertEquals([], warnings)
1051
def test_get_user_option_as_list(self):
1052
conf, parser = self.make_config_parser("""
1057
get_list = conf.get_user_option_as_list
1058
self.assertEqual(['a', 'b', 'c'], get_list('a_list'))
1059
self.assertEqual(['1'], get_list('length_1'))
1060
self.assertEqual('x', conf.get_user_option('one_item'))
1061
# automatically cast to list
1062
self.assertEqual(['x'], get_list('one_item'))
1064
def test_get_user_option_as_int_from_SI(self):
1065
conf, parser = self.make_config_parser("""
1074
def get_si(s, default=None):
1075
return self.applyDeprecated(
1076
deprecated_in((2, 5, 0)),
1077
conf.get_user_option_as_int_from_SI, s, default)
1078
self.assertEqual(100, get_si('plain'))
1079
self.assertEqual(5000, get_si('si_k'))
1080
self.assertEqual(5000, get_si('si_kb'))
1081
self.assertEqual(5000000, get_si('si_m'))
1082
self.assertEqual(5000000, get_si('si_mb'))
1083
self.assertEqual(5000000000, get_si('si_g'))
1084
self.assertEqual(5000000000, get_si('si_gb'))
1085
self.assertEqual(None, get_si('non-exist'))
1086
self.assertEqual(42, get_si('non-exist-with-default', 42))
1089
class TestSupressWarning(TestIniConfig):
1091
def make_warnings_config(self, s):
1092
conf, parser = self.make_config_parser(s)
1093
return conf.suppress_warning
1095
def test_suppress_warning_unknown(self):
1096
suppress_warning = self.make_warnings_config('')
1097
self.assertEqual(False, suppress_warning('unknown_warning'))
1099
def test_suppress_warning_known(self):
1100
suppress_warning = self.make_warnings_config('suppress_warnings=a,b')
1101
self.assertEqual(False, suppress_warning('c'))
1102
self.assertEqual(True, suppress_warning('a'))
1103
self.assertEqual(True, suppress_warning('b'))
1106
class TestGetConfig(tests.TestCase):
1108
def test_constructs(self):
1109
my_config = config.GlobalConfig()
1111
def test_calls_read_filenames(self):
1112
# replace the class that is constructed, to check its parameters
1113
oldparserclass = config.ConfigObj
1114
config.ConfigObj = InstrumentedConfigObj
1115
my_config = config.GlobalConfig()
1117
parser = my_config._get_parser()
1119
config.ConfigObj = oldparserclass
1120
self.assertIsInstance(parser, InstrumentedConfigObj)
1121
self.assertEqual(parser._calls, [('__init__', config.config_filename(),
1125
class TestBranchConfig(tests.TestCaseWithTransport):
1127
def test_constructs(self):
1128
branch = FakeBranch()
1129
my_config = config.BranchConfig(branch)
1130
self.assertRaises(TypeError, config.BranchConfig)
1132
def test_get_location_config(self):
1133
branch = FakeBranch()
1134
my_config = config.BranchConfig(branch)
1135
location_config = my_config._get_location_config()
1136
self.assertEqual(branch.base, location_config.location)
1137
self.assertIs(location_config, my_config._get_location_config())
1139
def test_get_config(self):
1140
"""The Branch.get_config method works properly"""
1141
b = bzrdir.BzrDir.create_standalone_workingtree('.').branch
1142
my_config = b.get_config()
1143
self.assertIs(my_config.get_user_option('wacky'), None)
1144
my_config.set_user_option('wacky', 'unlikely')
1145
self.assertEqual(my_config.get_user_option('wacky'), 'unlikely')
1147
# Ensure we get the same thing if we start again
1148
b2 = branch.Branch.open('.')
1149
my_config2 = b2.get_config()
1150
self.assertEqual(my_config2.get_user_option('wacky'), 'unlikely')
1152
def test_has_explicit_nickname(self):
1153
b = self.make_branch('.')
1154
self.assertFalse(b.get_config().has_explicit_nickname())
1156
self.assertTrue(b.get_config().has_explicit_nickname())
1158
def test_config_url(self):
1159
"""The Branch.get_config will use section that uses a local url"""
1160
branch = self.make_branch('branch')
1161
self.assertEqual('branch', branch.nick)
1163
local_url = urlutils.local_path_to_url('branch')
1164
conf = config.LocationConfig.from_string(
1165
'[%s]\nnickname = foobar' % (local_url,),
1166
local_url, save=True)
1167
self.assertEqual('foobar', branch.nick)
1169
def test_config_local_path(self):
1170
"""The Branch.get_config will use a local system path"""
1171
branch = self.make_branch('branch')
1172
self.assertEqual('branch', branch.nick)
1174
local_path = osutils.getcwd().encode('utf8')
1175
conf = config.LocationConfig.from_string(
1176
'[%s/branch]\nnickname = barry' % (local_path,),
1177
'branch', save=True)
1178
self.assertEqual('barry', branch.nick)
1180
def test_config_creates_local(self):
1181
"""Creating a new entry in config uses a local path."""
1182
branch = self.make_branch('branch', format='knit')
1183
branch.set_push_location('http://foobar')
1184
local_path = osutils.getcwd().encode('utf8')
1185
# Surprisingly ConfigObj doesn't create a trailing newline
1186
self.check_file_contents(config.locations_config_filename(),
1188
'push_location = http://foobar\n'
1189
'push_location:policy = norecurse\n'
1192
def test_autonick_urlencoded(self):
1193
b = self.make_branch('!repo')
1194
self.assertEqual('!repo', b.get_config().get_nickname())
1196
def test_warn_if_masked(self):
1199
warnings.append(args[0] % args[1:])
1200
self.overrideAttr(trace, 'warning', warning)
1202
def set_option(store, warn_masked=True):
1204
conf.set_user_option('example_option', repr(store), store=store,
1205
warn_masked=warn_masked)
1206
def assertWarning(warning):
1208
self.assertEqual(0, len(warnings))
1210
self.assertEqual(1, len(warnings))
1211
self.assertEqual(warning, warnings[0])
1212
branch = self.make_branch('.')
1213
conf = branch.get_config()
1214
set_option(config.STORE_GLOBAL)
1216
set_option(config.STORE_BRANCH)
1218
set_option(config.STORE_GLOBAL)
1219
assertWarning('Value "4" is masked by "3" from branch.conf')
1220
set_option(config.STORE_GLOBAL, warn_masked=False)
1222
set_option(config.STORE_LOCATION)
1224
set_option(config.STORE_BRANCH)
1225
assertWarning('Value "3" is masked by "0" from locations.conf')
1226
set_option(config.STORE_BRANCH, warn_masked=False)
1230
class TestGlobalConfigItems(tests.TestCaseInTempDir):
1232
def test_user_id(self):
1233
my_config = config.GlobalConfig.from_string(sample_config_text)
1234
self.assertEqual(u"Erik B\u00e5gfors <erik@bagfors.nu>",
1235
my_config._get_user_id())
1237
def test_absent_user_id(self):
1238
my_config = config.GlobalConfig()
1239
self.assertEqual(None, my_config._get_user_id())
1241
def test_configured_editor(self):
1242
my_config = config.GlobalConfig.from_string(sample_config_text)
1243
editor = self.applyDeprecated(
1244
deprecated_in((2, 4, 0)), my_config.get_editor)
1245
self.assertEqual('vim', editor)
1247
def test_signatures_always(self):
1248
my_config = config.GlobalConfig.from_string(sample_always_signatures)
1249
self.assertEqual(config.CHECK_NEVER,
1250
self.applyDeprecated(deprecated_in((2, 5, 0)),
1251
my_config.signature_checking))
1252
self.assertEqual(config.SIGN_ALWAYS,
1253
self.applyDeprecated(deprecated_in((2, 5, 0)),
1254
my_config.signing_policy))
1255
self.assertEqual(True,
1256
self.applyDeprecated(deprecated_in((2, 5, 0)),
1257
my_config.signature_needed))
1259
def test_signatures_if_possible(self):
1260
my_config = config.GlobalConfig.from_string(sample_maybe_signatures)
1261
self.assertEqual(config.CHECK_NEVER,
1262
self.applyDeprecated(deprecated_in((2, 5, 0)),
1263
my_config.signature_checking))
1264
self.assertEqual(config.SIGN_WHEN_REQUIRED,
1265
self.applyDeprecated(deprecated_in((2, 5, 0)),
1266
my_config.signing_policy))
1267
self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
1268
my_config.signature_needed))
1270
def test_signatures_ignore(self):
1271
my_config = config.GlobalConfig.from_string(sample_ignore_signatures)
1272
self.assertEqual(config.CHECK_ALWAYS,
1273
self.applyDeprecated(deprecated_in((2, 5, 0)),
1274
my_config.signature_checking))
1275
self.assertEqual(config.SIGN_NEVER,
1276
self.applyDeprecated(deprecated_in((2, 5, 0)),
1277
my_config.signing_policy))
1278
self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
1279
my_config.signature_needed))
1281
def _get_sample_config(self):
1282
my_config = config.GlobalConfig.from_string(sample_config_text)
1285
def test_gpg_signing_command(self):
1286
my_config = self._get_sample_config()
1287
self.assertEqual("gnome-gpg",
1288
self.applyDeprecated(
1289
deprecated_in((2, 5, 0)), my_config.gpg_signing_command))
1290
self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
1291
my_config.signature_needed))
1293
def test_gpg_signing_key(self):
1294
my_config = self._get_sample_config()
1295
self.assertEqual("DD4D5088",
1296
self.applyDeprecated(deprecated_in((2, 5, 0)),
1297
my_config.gpg_signing_key))
1299
def _get_empty_config(self):
1300
my_config = config.GlobalConfig()
1303
def test_gpg_signing_command_unset(self):
1304
my_config = self._get_empty_config()
1305
self.assertEqual("gpg",
1306
self.applyDeprecated(
1307
deprecated_in((2, 5, 0)), my_config.gpg_signing_command))
1309
def test_get_user_option_default(self):
1310
my_config = self._get_empty_config()
1311
self.assertEqual(None, my_config.get_user_option('no_option'))
1313
def test_get_user_option_global(self):
1314
my_config = self._get_sample_config()
1315
self.assertEqual("something",
1316
my_config.get_user_option('user_global_option'))
1318
def test_post_commit_default(self):
1319
my_config = self._get_sample_config()
1320
self.assertEqual(None,
1321
self.applyDeprecated(deprecated_in((2, 5, 0)),
1322
my_config.post_commit))
1324
def test_configured_logformat(self):
1325
my_config = self._get_sample_config()
1326
self.assertEqual("short",
1327
self.applyDeprecated(deprecated_in((2, 5, 0)),
1328
my_config.log_format))
1330
def test_configured_acceptable_keys(self):
1331
my_config = self._get_sample_config()
1332
self.assertEqual("amy",
1333
self.applyDeprecated(deprecated_in((2, 5, 0)),
1334
my_config.acceptable_keys))
1336
def test_configured_validate_signatures_in_log(self):
1337
my_config = self._get_sample_config()
1338
self.assertEqual(True, my_config.validate_signatures_in_log())
1340
def test_get_alias(self):
1341
my_config = self._get_sample_config()
1342
self.assertEqual('help', my_config.get_alias('h'))
1344
def test_get_aliases(self):
1345
my_config = self._get_sample_config()
1346
aliases = my_config.get_aliases()
1347
self.assertEqual(2, len(aliases))
1348
sorted_keys = sorted(aliases)
1349
self.assertEqual('help', aliases[sorted_keys[0]])
1350
self.assertEqual(sample_long_alias, aliases[sorted_keys[1]])
1352
def test_get_no_alias(self):
1353
my_config = self._get_sample_config()
1354
self.assertEqual(None, my_config.get_alias('foo'))
1356
def test_get_long_alias(self):
1357
my_config = self._get_sample_config()
1358
self.assertEqual(sample_long_alias, my_config.get_alias('ll'))
1360
def test_get_change_editor(self):
1361
my_config = self._get_sample_config()
1362
change_editor = my_config.get_change_editor('old', 'new')
1363
self.assertIs(diff.DiffFromTool, change_editor.__class__)
1364
self.assertEqual('vimdiff -of @new_path @old_path',
1365
' '.join(change_editor.command_template))
1367
def test_get_no_change_editor(self):
1368
my_config = self._get_empty_config()
1369
change_editor = my_config.get_change_editor('old', 'new')
1370
self.assertIs(None, change_editor)
1372
def test_get_merge_tools(self):
1373
conf = self._get_sample_config()
1374
tools = conf.get_merge_tools()
1375
self.log(repr(tools))
1377
{u'funkytool' : u'funkytool "arg with spaces" {this_temp}',
1378
u'sometool' : u'sometool {base} {this} {other} -o {result}',
1379
u'newtool' : u'"newtool with spaces" {this_temp}'},
1382
def test_get_merge_tools_empty(self):
1383
conf = self._get_empty_config()
1384
tools = conf.get_merge_tools()
1385
self.assertEqual({}, tools)
1387
def test_find_merge_tool(self):
1388
conf = self._get_sample_config()
1389
cmdline = conf.find_merge_tool('sometool')
1390
self.assertEqual('sometool {base} {this} {other} -o {result}', cmdline)
1392
def test_find_merge_tool_not_found(self):
1393
conf = self._get_sample_config()
1394
cmdline = conf.find_merge_tool('DOES NOT EXIST')
1395
self.assertIs(cmdline, None)
1397
def test_find_merge_tool_known(self):
1398
conf = self._get_empty_config()
1399
cmdline = conf.find_merge_tool('kdiff3')
1400
self.assertEquals('kdiff3 {base} {this} {other} -o {result}', cmdline)
1402
def test_find_merge_tool_override_known(self):
1403
conf = self._get_empty_config()
1404
conf.set_user_option('bzr.mergetool.kdiff3', 'kdiff3 blah')
1405
cmdline = conf.find_merge_tool('kdiff3')
1406
self.assertEqual('kdiff3 blah', cmdline)
1409
class TestGlobalConfigSavingOptions(tests.TestCaseInTempDir):
1411
def test_empty(self):
1412
my_config = config.GlobalConfig()
1413
self.assertEqual(0, len(my_config.get_aliases()))
1415
def test_set_alias(self):
1416
my_config = config.GlobalConfig()
1417
alias_value = 'commit --strict'
1418
my_config.set_alias('commit', alias_value)
1419
new_config = config.GlobalConfig()
1420
self.assertEqual(alias_value, new_config.get_alias('commit'))
1422
def test_remove_alias(self):
1423
my_config = config.GlobalConfig()
1424
my_config.set_alias('commit', 'commit --strict')
1425
# Now remove the alias again.
1426
my_config.unset_alias('commit')
1427
new_config = config.GlobalConfig()
1428
self.assertIs(None, new_config.get_alias('commit'))
1431
class TestLocationConfig(tests.TestCaseInTempDir, TestOptionsMixin):
1433
def test_constructs(self):
1434
my_config = config.LocationConfig('http://example.com')
1435
self.assertRaises(TypeError, config.LocationConfig)
1437
def test_branch_calls_read_filenames(self):
1438
# This is testing the correct file names are provided.
1439
# TODO: consolidate with the test for GlobalConfigs filename checks.
1441
# replace the class that is constructed, to check its parameters
1442
oldparserclass = config.ConfigObj
1443
config.ConfigObj = InstrumentedConfigObj
1445
my_config = config.LocationConfig('http://www.example.com')
1446
parser = my_config._get_parser()
1448
config.ConfigObj = oldparserclass
1449
self.assertIsInstance(parser, InstrumentedConfigObj)
1450
self.assertEqual(parser._calls,
1451
[('__init__', config.locations_config_filename(),
1454
def test_get_global_config(self):
1455
my_config = config.BranchConfig(FakeBranch('http://example.com'))
1456
global_config = my_config._get_global_config()
1457
self.assertIsInstance(global_config, config.GlobalConfig)
1458
self.assertIs(global_config, my_config._get_global_config())
1460
def assertLocationMatching(self, expected):
1461
self.assertEqual(expected,
1462
list(self.my_location_config._get_matching_sections()))
1464
def test__get_matching_sections_no_match(self):
1465
self.get_branch_config('/')
1466
self.assertLocationMatching([])
1468
def test__get_matching_sections_exact(self):
1469
self.get_branch_config('http://www.example.com')
1470
self.assertLocationMatching([('http://www.example.com', '')])
1472
def test__get_matching_sections_suffix_does_not(self):
1473
self.get_branch_config('http://www.example.com-com')
1474
self.assertLocationMatching([])
1476
def test__get_matching_sections_subdir_recursive(self):
1477
self.get_branch_config('http://www.example.com/com')
1478
self.assertLocationMatching([('http://www.example.com', 'com')])
1480
def test__get_matching_sections_ignoreparent(self):
1481
self.get_branch_config('http://www.example.com/ignoreparent')
1482
self.assertLocationMatching([('http://www.example.com/ignoreparent',
1485
def test__get_matching_sections_ignoreparent_subdir(self):
1486
self.get_branch_config(
1487
'http://www.example.com/ignoreparent/childbranch')
1488
self.assertLocationMatching([('http://www.example.com/ignoreparent',
1491
def test__get_matching_sections_subdir_trailing_slash(self):
1492
self.get_branch_config('/b')
1493
self.assertLocationMatching([('/b/', '')])
1495
def test__get_matching_sections_subdir_child(self):
1496
self.get_branch_config('/a/foo')
1497
self.assertLocationMatching([('/a/*', ''), ('/a/', 'foo')])
1499
def test__get_matching_sections_subdir_child_child(self):
1500
self.get_branch_config('/a/foo/bar')
1501
self.assertLocationMatching([('/a/*', 'bar'), ('/a/', 'foo/bar')])
1503
def test__get_matching_sections_trailing_slash_with_children(self):
1504
self.get_branch_config('/a/')
1505
self.assertLocationMatching([('/a/', '')])
1507
def test__get_matching_sections_explicit_over_glob(self):
1508
# XXX: 2006-09-08 jamesh
1509
# This test only passes because ord('c') > ord('*'). If there
1510
# was a config section for '/a/?', it would get precedence
1512
self.get_branch_config('/a/c')
1513
self.assertLocationMatching([('/a/c', ''), ('/a/*', ''), ('/a/', 'c')])
1515
def test__get_option_policy_normal(self):
1516
self.get_branch_config('http://www.example.com')
1518
self.my_location_config._get_config_policy(
1519
'http://www.example.com', 'normal_option'),
1522
def test__get_option_policy_norecurse(self):
1523
self.get_branch_config('http://www.example.com')
1525
self.my_location_config._get_option_policy(
1526
'http://www.example.com', 'norecurse_option'),
1527
config.POLICY_NORECURSE)
1528
# Test old recurse=False setting:
1530
self.my_location_config._get_option_policy(
1531
'http://www.example.com/norecurse', 'normal_option'),
1532
config.POLICY_NORECURSE)
1534
def test__get_option_policy_normal(self):
1535
self.get_branch_config('http://www.example.com')
1537
self.my_location_config._get_option_policy(
1538
'http://www.example.com', 'appendpath_option'),
1539
config.POLICY_APPENDPATH)
1541
def test__get_options_with_policy(self):
1542
self.get_branch_config('/dir/subdir',
1543
location_config="""\
1545
other_url = /other-dir
1546
other_url:policy = appendpath
1548
other_url = /other-subdir
1551
[(u'other_url', u'/other-subdir', u'/dir/subdir', 'locations'),
1552
(u'other_url', u'/other-dir', u'/dir', 'locations'),
1553
(u'other_url:policy', u'appendpath', u'/dir', 'locations')],
1554
self.my_location_config)
1556
def test_location_without_username(self):
1557
self.get_branch_config('http://www.example.com/ignoreparent')
1558
self.assertEqual(u'Erik B\u00e5gfors <erik@bagfors.nu>',
1559
self.my_config.username())
1561
def test_location_not_listed(self):
1562
"""Test that the global username is used when no location matches"""
1563
self.get_branch_config('/home/robertc/sources')
1564
self.assertEqual(u'Erik B\u00e5gfors <erik@bagfors.nu>',
1565
self.my_config.username())
1567
def test_overriding_location(self):
1568
self.get_branch_config('http://www.example.com/foo')
1569
self.assertEqual('Robert Collins <robertc@example.org>',
1570
self.my_config.username())
1572
def test_signatures_not_set(self):
1573
self.get_branch_config('http://www.example.com',
1574
global_config=sample_ignore_signatures)
1575
self.assertEqual(config.CHECK_ALWAYS,
1576
self.applyDeprecated(deprecated_in((2, 5, 0)),
1577
self.my_config.signature_checking))
1578
self.assertEqual(config.SIGN_NEVER,
1579
self.applyDeprecated(deprecated_in((2, 5, 0)),
1580
self.my_config.signing_policy))
1582
def test_signatures_never(self):
1583
self.get_branch_config('/a/c')
1584
self.assertEqual(config.CHECK_NEVER,
1585
self.applyDeprecated(deprecated_in((2, 5, 0)),
1586
self.my_config.signature_checking))
1588
def test_signatures_when_available(self):
1589
self.get_branch_config('/a/', global_config=sample_ignore_signatures)
1590
self.assertEqual(config.CHECK_IF_POSSIBLE,
1591
self.applyDeprecated(deprecated_in((2, 5, 0)),
1592
self.my_config.signature_checking))
1594
def test_signatures_always(self):
1595
self.get_branch_config('/b')
1596
self.assertEqual(config.CHECK_ALWAYS,
1597
self.applyDeprecated(deprecated_in((2, 5, 0)),
1598
self.my_config.signature_checking))
1600
def test_gpg_signing_command(self):
1601
self.get_branch_config('/b')
1602
self.assertEqual("gnome-gpg",
1603
self.applyDeprecated(deprecated_in((2, 5, 0)),
1604
self.my_config.gpg_signing_command))
1606
def test_gpg_signing_command_missing(self):
1607
self.get_branch_config('/a')
1608
self.assertEqual("false",
1609
self.applyDeprecated(deprecated_in((2, 5, 0)),
1610
self.my_config.gpg_signing_command))
1612
def test_gpg_signing_key(self):
1613
self.get_branch_config('/b')
1614
self.assertEqual("DD4D5088", self.applyDeprecated(deprecated_in((2, 5, 0)),
1615
self.my_config.gpg_signing_key))
1617
def test_gpg_signing_key_default(self):
1618
self.get_branch_config('/a')
1619
self.assertEqual("erik@bagfors.nu",
1620
self.applyDeprecated(deprecated_in((2, 5, 0)),
1621
self.my_config.gpg_signing_key))
1623
def test_get_user_option_global(self):
1624
self.get_branch_config('/a')
1625
self.assertEqual('something',
1626
self.my_config.get_user_option('user_global_option'))
1628
def test_get_user_option_local(self):
1629
self.get_branch_config('/a')
1630
self.assertEqual('local',
1631
self.my_config.get_user_option('user_local_option'))
1633
def test_get_user_option_appendpath(self):
1634
# returned as is for the base path:
1635
self.get_branch_config('http://www.example.com')
1636
self.assertEqual('append',
1637
self.my_config.get_user_option('appendpath_option'))
1638
# Extra path components get appended:
1639
self.get_branch_config('http://www.example.com/a/b/c')
1640
self.assertEqual('append/a/b/c',
1641
self.my_config.get_user_option('appendpath_option'))
1642
# Overriden for http://www.example.com/dir, where it is a
1644
self.get_branch_config('http://www.example.com/dir/a/b/c')
1645
self.assertEqual('normal',
1646
self.my_config.get_user_option('appendpath_option'))
1648
def test_get_user_option_norecurse(self):
1649
self.get_branch_config('http://www.example.com')
1650
self.assertEqual('norecurse',
1651
self.my_config.get_user_option('norecurse_option'))
1652
self.get_branch_config('http://www.example.com/dir')
1653
self.assertEqual(None,
1654
self.my_config.get_user_option('norecurse_option'))
1655
# http://www.example.com/norecurse is a recurse=False section
1656
# that redefines normal_option. Subdirectories do not pick up
1657
# this redefinition.
1658
self.get_branch_config('http://www.example.com/norecurse')
1659
self.assertEqual('norecurse',
1660
self.my_config.get_user_option('normal_option'))
1661
self.get_branch_config('http://www.example.com/norecurse/subdir')
1662
self.assertEqual('normal',
1663
self.my_config.get_user_option('normal_option'))
1665
def test_set_user_option_norecurse(self):
1666
self.get_branch_config('http://www.example.com')
1667
self.my_config.set_user_option('foo', 'bar',
1668
store=config.STORE_LOCATION_NORECURSE)
1670
self.my_location_config._get_option_policy(
1671
'http://www.example.com', 'foo'),
1672
config.POLICY_NORECURSE)
1674
def test_set_user_option_appendpath(self):
1675
self.get_branch_config('http://www.example.com')
1676
self.my_config.set_user_option('foo', 'bar',
1677
store=config.STORE_LOCATION_APPENDPATH)
1679
self.my_location_config._get_option_policy(
1680
'http://www.example.com', 'foo'),
1681
config.POLICY_APPENDPATH)
1683
def test_set_user_option_change_policy(self):
1684
self.get_branch_config('http://www.example.com')
1685
self.my_config.set_user_option('norecurse_option', 'normal',
1686
store=config.STORE_LOCATION)
1688
self.my_location_config._get_option_policy(
1689
'http://www.example.com', 'norecurse_option'),
1692
def test_set_user_option_recurse_false_section(self):
1693
# The following section has recurse=False set. The test is to
1694
# make sure that a normal option can be added to the section,
1695
# converting recurse=False to the norecurse policy.
1696
self.get_branch_config('http://www.example.com/norecurse')
1697
self.callDeprecated(['The recurse option is deprecated as of 0.14. '
1698
'The section "http://www.example.com/norecurse" '
1699
'has been converted to use policies.'],
1700
self.my_config.set_user_option,
1701
'foo', 'bar', store=config.STORE_LOCATION)
1703
self.my_location_config._get_option_policy(
1704
'http://www.example.com/norecurse', 'foo'),
1706
# The previously existing option is still norecurse:
1708
self.my_location_config._get_option_policy(
1709
'http://www.example.com/norecurse', 'normal_option'),
1710
config.POLICY_NORECURSE)
1712
def test_post_commit_default(self):
1713
self.get_branch_config('/a/c')
1714
self.assertEqual('bzrlib.tests.test_config.post_commit',
1715
self.applyDeprecated(deprecated_in((2, 5, 0)),
1716
self.my_config.post_commit))
1718
def get_branch_config(self, location, global_config=None,
1719
location_config=None):
1720
my_branch = FakeBranch(location)
1721
if global_config is None:
1722
global_config = sample_config_text
1723
if location_config is None:
1724
location_config = sample_branches_text
1726
my_global_config = config.GlobalConfig.from_string(global_config,
1728
my_location_config = config.LocationConfig.from_string(
1729
location_config, my_branch.base, save=True)
1730
my_config = config.BranchConfig(my_branch)
1731
self.my_config = my_config
1732
self.my_location_config = my_config._get_location_config()
1734
def test_set_user_setting_sets_and_saves(self):
1735
self.get_branch_config('/a/c')
1736
record = InstrumentedConfigObj("foo")
1737
self.my_location_config._parser = record
1739
self.callDeprecated(['The recurse option is deprecated as of '
1740
'0.14. The section "/a/c" has been '
1741
'converted to use policies.'],
1742
self.my_config.set_user_option,
1743
'foo', 'bar', store=config.STORE_LOCATION)
1744
self.assertEqual([('reload',),
1745
('__contains__', '/a/c'),
1746
('__contains__', '/a/c/'),
1747
('__setitem__', '/a/c', {}),
1748
('__getitem__', '/a/c'),
1749
('__setitem__', 'foo', 'bar'),
1750
('__getitem__', '/a/c'),
1751
('as_bool', 'recurse'),
1752
('__getitem__', '/a/c'),
1753
('__delitem__', 'recurse'),
1754
('__getitem__', '/a/c'),
1756
('__getitem__', '/a/c'),
1757
('__contains__', 'foo:policy'),
1761
def test_set_user_setting_sets_and_saves2(self):
1762
self.get_branch_config('/a/c')
1763
self.assertIs(self.my_config.get_user_option('foo'), None)
1764
self.my_config.set_user_option('foo', 'bar')
1766
self.my_config.branch.control_files.files['branch.conf'].strip(),
1768
self.assertEqual(self.my_config.get_user_option('foo'), 'bar')
1769
self.my_config.set_user_option('foo', 'baz',
1770
store=config.STORE_LOCATION)
1771
self.assertEqual(self.my_config.get_user_option('foo'), 'baz')
1772
self.my_config.set_user_option('foo', 'qux')
1773
self.assertEqual(self.my_config.get_user_option('foo'), 'baz')
1775
def test_get_bzr_remote_path(self):
1776
my_config = config.LocationConfig('/a/c')
1777
self.assertEqual('bzr', my_config.get_bzr_remote_path())
1778
my_config.set_user_option('bzr_remote_path', '/path-bzr')
1779
self.assertEqual('/path-bzr', my_config.get_bzr_remote_path())
1780
self.overrideEnv('BZR_REMOTE_PATH', '/environ-bzr')
1781
self.assertEqual('/environ-bzr', my_config.get_bzr_remote_path())
1784
precedence_global = 'option = global'
1785
precedence_branch = 'option = branch'
1786
precedence_location = """
1790
[http://example.com/specific]
1794
class TestBranchConfigItems(tests.TestCaseInTempDir):
1796
def get_branch_config(self, global_config=None, location=None,
1797
location_config=None, branch_data_config=None):
1798
my_branch = FakeBranch(location)
1799
if global_config is not None:
1800
my_global_config = config.GlobalConfig.from_string(global_config,
1802
if location_config is not None:
1803
my_location_config = config.LocationConfig.from_string(
1804
location_config, my_branch.base, save=True)
1805
my_config = config.BranchConfig(my_branch)
1806
if branch_data_config is not None:
1807
my_config.branch.control_files.files['branch.conf'] = \
1811
def test_user_id(self):
1812
branch = FakeBranch()
1813
my_config = config.BranchConfig(branch)
1814
self.assertIsNot(None, my_config.username())
1815
my_config.branch.control_files.files['email'] = "John"
1816
my_config.set_user_option('email',
1817
"Robert Collins <robertc@example.org>")
1818
self.assertEqual("Robert Collins <robertc@example.org>",
1819
my_config.username())
1821
def test_BZR_EMAIL_OVERRIDES(self):
1822
self.overrideEnv('BZR_EMAIL', "Robert Collins <robertc@example.org>")
1823
branch = FakeBranch()
1824
my_config = config.BranchConfig(branch)
1825
self.assertEqual("Robert Collins <robertc@example.org>",
1826
my_config.username())
1828
def test_signatures_forced(self):
1829
my_config = self.get_branch_config(
1830
global_config=sample_always_signatures)
1831
self.assertEqual(config.CHECK_NEVER,
1832
self.applyDeprecated(deprecated_in((2, 5, 0)),
1833
my_config.signature_checking))
1834
self.assertEqual(config.SIGN_ALWAYS,
1835
self.applyDeprecated(deprecated_in((2, 5, 0)),
1836
my_config.signing_policy))
1837
self.assertTrue(self.applyDeprecated(deprecated_in((2, 5, 0)),
1838
my_config.signature_needed))
1840
def test_signatures_forced_branch(self):
1841
my_config = self.get_branch_config(
1842
global_config=sample_ignore_signatures,
1843
branch_data_config=sample_always_signatures)
1844
self.assertEqual(config.CHECK_NEVER,
1845
self.applyDeprecated(deprecated_in((2, 5, 0)),
1846
my_config.signature_checking))
1847
self.assertEqual(config.SIGN_ALWAYS,
1848
self.applyDeprecated(deprecated_in((2, 5, 0)),
1849
my_config.signing_policy))
1850
self.assertTrue(self.applyDeprecated(deprecated_in((2, 5, 0)),
1851
my_config.signature_needed))
1853
def test_gpg_signing_command(self):
1854
my_config = self.get_branch_config(
1855
global_config=sample_config_text,
1856
# branch data cannot set gpg_signing_command
1857
branch_data_config="gpg_signing_command=pgp")
1858
self.assertEqual('gnome-gpg',
1859
self.applyDeprecated(deprecated_in((2, 5, 0)),
1860
my_config.gpg_signing_command))
1862
def test_get_user_option_global(self):
1863
my_config = self.get_branch_config(global_config=sample_config_text)
1864
self.assertEqual('something',
1865
my_config.get_user_option('user_global_option'))
1867
def test_post_commit_default(self):
1868
my_config = self.get_branch_config(global_config=sample_config_text,
1870
location_config=sample_branches_text)
1871
self.assertEqual(my_config.branch.base, '/a/c')
1872
self.assertEqual('bzrlib.tests.test_config.post_commit',
1873
self.applyDeprecated(deprecated_in((2, 5, 0)),
1874
my_config.post_commit))
1875
my_config.set_user_option('post_commit', 'rmtree_root')
1876
# post-commit is ignored when present in branch data
1877
self.assertEqual('bzrlib.tests.test_config.post_commit',
1878
self.applyDeprecated(deprecated_in((2, 5, 0)),
1879
my_config.post_commit))
1880
my_config.set_user_option('post_commit', 'rmtree_root',
1881
store=config.STORE_LOCATION)
1882
self.assertEqual('rmtree_root',
1883
self.applyDeprecated(deprecated_in((2, 5, 0)),
1884
my_config.post_commit))
1886
def test_config_precedence(self):
1887
# FIXME: eager test, luckily no persitent config file makes it fail
1889
my_config = self.get_branch_config(global_config=precedence_global)
1890
self.assertEqual(my_config.get_user_option('option'), 'global')
1891
my_config = self.get_branch_config(global_config=precedence_global,
1892
branch_data_config=precedence_branch)
1893
self.assertEqual(my_config.get_user_option('option'), 'branch')
1894
my_config = self.get_branch_config(
1895
global_config=precedence_global,
1896
branch_data_config=precedence_branch,
1897
location_config=precedence_location)
1898
self.assertEqual(my_config.get_user_option('option'), 'recurse')
1899
my_config = self.get_branch_config(
1900
global_config=precedence_global,
1901
branch_data_config=precedence_branch,
1902
location_config=precedence_location,
1903
location='http://example.com/specific')
1904
self.assertEqual(my_config.get_user_option('option'), 'exact')
1906
def test_get_mail_client(self):
1907
config = self.get_branch_config()
1908
client = config.get_mail_client()
1909
self.assertIsInstance(client, mail_client.DefaultMail)
1912
config.set_user_option('mail_client', 'evolution')
1913
client = config.get_mail_client()
1914
self.assertIsInstance(client, mail_client.Evolution)
1916
config.set_user_option('mail_client', 'kmail')
1917
client = config.get_mail_client()
1918
self.assertIsInstance(client, mail_client.KMail)
1920
config.set_user_option('mail_client', 'mutt')
1921
client = config.get_mail_client()
1922
self.assertIsInstance(client, mail_client.Mutt)
1924
config.set_user_option('mail_client', 'thunderbird')
1925
client = config.get_mail_client()
1926
self.assertIsInstance(client, mail_client.Thunderbird)
1929
config.set_user_option('mail_client', 'default')
1930
client = config.get_mail_client()
1931
self.assertIsInstance(client, mail_client.DefaultMail)
1933
config.set_user_option('mail_client', 'editor')
1934
client = config.get_mail_client()
1935
self.assertIsInstance(client, mail_client.Editor)
1937
config.set_user_option('mail_client', 'mapi')
1938
client = config.get_mail_client()
1939
self.assertIsInstance(client, mail_client.MAPIClient)
1941
config.set_user_option('mail_client', 'xdg-email')
1942
client = config.get_mail_client()
1943
self.assertIsInstance(client, mail_client.XDGEmail)
1945
config.set_user_option('mail_client', 'firebird')
1946
self.assertRaises(errors.UnknownMailClient, config.get_mail_client)
1949
class TestMailAddressExtraction(tests.TestCase):
1951
def test_extract_email_address(self):
1952
self.assertEqual('jane@test.com',
1953
config.extract_email_address('Jane <jane@test.com>'))
1954
self.assertRaises(errors.NoEmailInUsername,
1955
config.extract_email_address, 'Jane Tester')
1957
def test_parse_username(self):
1958
self.assertEqual(('', 'jdoe@example.com'),
1959
config.parse_username('jdoe@example.com'))
1960
self.assertEqual(('', 'jdoe@example.com'),
1961
config.parse_username('<jdoe@example.com>'))
1962
self.assertEqual(('John Doe', 'jdoe@example.com'),
1963
config.parse_username('John Doe <jdoe@example.com>'))
1964
self.assertEqual(('John Doe', ''),
1965
config.parse_username('John Doe'))
1966
self.assertEqual(('John Doe', 'jdoe@example.com'),
1967
config.parse_username('John Doe jdoe@example.com'))
1969
class TestTreeConfig(tests.TestCaseWithTransport):
1971
def test_get_value(self):
1972
"""Test that retreiving a value from a section is possible"""
1973
branch = self.make_branch('.')
1974
tree_config = config.TreeConfig(branch)
1975
tree_config.set_option('value', 'key', 'SECTION')
1976
tree_config.set_option('value2', 'key2')
1977
tree_config.set_option('value3-top', 'key3')
1978
tree_config.set_option('value3-section', 'key3', 'SECTION')
1979
value = tree_config.get_option('key', 'SECTION')
1980
self.assertEqual(value, 'value')
1981
value = tree_config.get_option('key2')
1982
self.assertEqual(value, 'value2')
1983
self.assertEqual(tree_config.get_option('non-existant'), None)
1984
value = tree_config.get_option('non-existant', 'SECTION')
1985
self.assertEqual(value, None)
1986
value = tree_config.get_option('non-existant', default='default')
1987
self.assertEqual(value, 'default')
1988
self.assertEqual(tree_config.get_option('key2', 'NOSECTION'), None)
1989
value = tree_config.get_option('key2', 'NOSECTION', default='default')
1990
self.assertEqual(value, 'default')
1991
value = tree_config.get_option('key3')
1992
self.assertEqual(value, 'value3-top')
1993
value = tree_config.get_option('key3', 'SECTION')
1994
self.assertEqual(value, 'value3-section')
1997
class TestTransportConfig(tests.TestCaseWithTransport):
1999
def test_load_utf8(self):
2000
"""Ensure we can load an utf8-encoded file."""
2001
t = self.get_transport()
2002
unicode_user = u'b\N{Euro Sign}ar'
2003
unicode_content = u'user=%s' % (unicode_user,)
2004
utf8_content = unicode_content.encode('utf8')
2005
# Store the raw content in the config file
2006
t.put_bytes('foo.conf', utf8_content)
2007
conf = config.TransportConfig(t, 'foo.conf')
2008
self.assertEquals(unicode_user, conf.get_option('user'))
2010
def test_load_non_ascii(self):
2011
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2012
t = self.get_transport()
2013
t.put_bytes('foo.conf', 'user=foo\n#\xff\n')
2014
conf = config.TransportConfig(t, 'foo.conf')
2015
self.assertRaises(errors.ConfigContentError, conf._get_configobj)
2017
def test_load_erroneous_content(self):
2018
"""Ensure we display a proper error on content that can't be parsed."""
2019
t = self.get_transport()
2020
t.put_bytes('foo.conf', '[open_section\n')
2021
conf = config.TransportConfig(t, 'foo.conf')
2022
self.assertRaises(errors.ParseConfigError, conf._get_configobj)
2024
def test_load_permission_denied(self):
2025
"""Ensure we get an empty config file if the file is inaccessible."""
2028
warnings.append(args[0] % args[1:])
2029
self.overrideAttr(trace, 'warning', warning)
2031
class DenyingTransport(object):
2033
def __init__(self, base):
2036
def get_bytes(self, relpath):
2037
raise errors.PermissionDenied(relpath, "")
2039
cfg = config.TransportConfig(
2040
DenyingTransport("nonexisting://"), 'control.conf')
2041
self.assertIs(None, cfg.get_option('non-existant', 'SECTION'))
2044
[u'Permission denied while trying to open configuration file '
2045
u'nonexisting:///control.conf.'])
2047
def test_get_value(self):
2048
"""Test that retreiving a value from a section is possible"""
2049
bzrdir_config = config.TransportConfig(self.get_transport('.'),
2051
bzrdir_config.set_option('value', 'key', 'SECTION')
2052
bzrdir_config.set_option('value2', 'key2')
2053
bzrdir_config.set_option('value3-top', 'key3')
2054
bzrdir_config.set_option('value3-section', 'key3', 'SECTION')
2055
value = bzrdir_config.get_option('key', 'SECTION')
2056
self.assertEqual(value, 'value')
2057
value = bzrdir_config.get_option('key2')
2058
self.assertEqual(value, 'value2')
2059
self.assertEqual(bzrdir_config.get_option('non-existant'), None)
2060
value = bzrdir_config.get_option('non-existant', 'SECTION')
2061
self.assertEqual(value, None)
2062
value = bzrdir_config.get_option('non-existant', default='default')
2063
self.assertEqual(value, 'default')
2064
self.assertEqual(bzrdir_config.get_option('key2', 'NOSECTION'), None)
2065
value = bzrdir_config.get_option('key2', 'NOSECTION',
2067
self.assertEqual(value, 'default')
2068
value = bzrdir_config.get_option('key3')
2069
self.assertEqual(value, 'value3-top')
2070
value = bzrdir_config.get_option('key3', 'SECTION')
2071
self.assertEqual(value, 'value3-section')
2073
def test_set_unset_default_stack_on(self):
2074
my_dir = self.make_bzrdir('.')
2075
bzrdir_config = config.BzrDirConfig(my_dir)
2076
self.assertIs(None, bzrdir_config.get_default_stack_on())
2077
bzrdir_config.set_default_stack_on('Foo')
2078
self.assertEqual('Foo', bzrdir_config._config.get_option(
2079
'default_stack_on'))
2080
self.assertEqual('Foo', bzrdir_config.get_default_stack_on())
2081
bzrdir_config.set_default_stack_on(None)
2082
self.assertIs(None, bzrdir_config.get_default_stack_on())
2085
class TestOldConfigHooks(tests.TestCaseWithTransport):
2088
super(TestOldConfigHooks, self).setUp()
2089
create_configs_with_file_option(self)
2091
def assertGetHook(self, conf, name, value):
2095
config.OldConfigHooks.install_named_hook('get', hook, None)
2097
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2098
self.assertLength(0, calls)
2099
actual_value = conf.get_user_option(name)
2100
self.assertEquals(value, actual_value)
2101
self.assertLength(1, calls)
2102
self.assertEquals((conf, name, value), calls[0])
2104
def test_get_hook_bazaar(self):
2105
self.assertGetHook(self.bazaar_config, 'file', 'bazaar')
2107
def test_get_hook_locations(self):
2108
self.assertGetHook(self.locations_config, 'file', 'locations')
2110
def test_get_hook_branch(self):
2111
# Since locations masks branch, we define a different option
2112
self.branch_config.set_user_option('file2', 'branch')
2113
self.assertGetHook(self.branch_config, 'file2', 'branch')
2115
def assertSetHook(self, conf, name, value):
2119
config.OldConfigHooks.install_named_hook('set', hook, None)
2121
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2122
self.assertLength(0, calls)
2123
conf.set_user_option(name, value)
2124
self.assertLength(1, calls)
2125
# We can't assert the conf object below as different configs use
2126
# different means to implement set_user_option and we care only about
2128
self.assertEquals((name, value), calls[0][1:])
2130
def test_set_hook_bazaar(self):
2131
self.assertSetHook(self.bazaar_config, 'foo', 'bazaar')
2133
def test_set_hook_locations(self):
2134
self.assertSetHook(self.locations_config, 'foo', 'locations')
2136
def test_set_hook_branch(self):
2137
self.assertSetHook(self.branch_config, 'foo', 'branch')
2139
def assertRemoveHook(self, conf, name, section_name=None):
2143
config.OldConfigHooks.install_named_hook('remove', hook, None)
2145
config.OldConfigHooks.uninstall_named_hook, 'remove', None)
2146
self.assertLength(0, calls)
2147
conf.remove_user_option(name, section_name)
2148
self.assertLength(1, calls)
2149
# We can't assert the conf object below as different configs use
2150
# different means to implement remove_user_option and we care only about
2152
self.assertEquals((name,), calls[0][1:])
2154
def test_remove_hook_bazaar(self):
2155
self.assertRemoveHook(self.bazaar_config, 'file')
2157
def test_remove_hook_locations(self):
2158
self.assertRemoveHook(self.locations_config, 'file',
2159
self.locations_config.location)
2161
def test_remove_hook_branch(self):
2162
self.assertRemoveHook(self.branch_config, 'file')
2164
def assertLoadHook(self, name, conf_class, *conf_args):
2168
config.OldConfigHooks.install_named_hook('load', hook, None)
2170
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2171
self.assertLength(0, calls)
2173
conf = conf_class(*conf_args)
2174
# Access an option to trigger a load
2175
conf.get_user_option(name)
2176
self.assertLength(1, calls)
2177
# Since we can't assert about conf, we just use the number of calls ;-/
2179
def test_load_hook_bazaar(self):
2180
self.assertLoadHook('file', config.GlobalConfig)
2182
def test_load_hook_locations(self):
2183
self.assertLoadHook('file', config.LocationConfig, self.tree.basedir)
2185
def test_load_hook_branch(self):
2186
self.assertLoadHook('file', config.BranchConfig, self.tree.branch)
2188
def assertSaveHook(self, conf):
2192
config.OldConfigHooks.install_named_hook('save', hook, None)
2194
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2195
self.assertLength(0, calls)
2196
# Setting an option triggers a save
2197
conf.set_user_option('foo', 'bar')
2198
self.assertLength(1, calls)
2199
# Since we can't assert about conf, we just use the number of calls ;-/
2201
def test_save_hook_bazaar(self):
2202
self.assertSaveHook(self.bazaar_config)
2204
def test_save_hook_locations(self):
2205
self.assertSaveHook(self.locations_config)
2207
def test_save_hook_branch(self):
2208
self.assertSaveHook(self.branch_config)
2211
class TestOldConfigHooksForRemote(tests.TestCaseWithTransport):
2212
"""Tests config hooks for remote configs.
2214
No tests for the remove hook as this is not implemented there.
2218
super(TestOldConfigHooksForRemote, self).setUp()
2219
self.transport_server = test_server.SmartTCPServer_for_testing
2220
create_configs_with_file_option(self)
2222
def assertGetHook(self, conf, name, value):
2226
config.OldConfigHooks.install_named_hook('get', hook, None)
2228
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2229
self.assertLength(0, calls)
2230
actual_value = conf.get_option(name)
2231
self.assertEquals(value, actual_value)
2232
self.assertLength(1, calls)
2233
self.assertEquals((conf, name, value), calls[0])
2235
def test_get_hook_remote_branch(self):
2236
remote_branch = branch.Branch.open(self.get_url('tree'))
2237
self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
2239
def test_get_hook_remote_bzrdir(self):
2240
remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
2241
conf = remote_bzrdir._get_config()
2242
conf.set_option('remotedir', 'file')
2243
self.assertGetHook(conf, 'file', 'remotedir')
2245
def assertSetHook(self, conf, name, value):
2249
config.OldConfigHooks.install_named_hook('set', hook, None)
2251
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2252
self.assertLength(0, calls)
2253
conf.set_option(value, name)
2254
self.assertLength(1, calls)
2255
# We can't assert the conf object below as different configs use
2256
# different means to implement set_user_option and we care only about
2258
self.assertEquals((name, value), calls[0][1:])
2260
def test_set_hook_remote_branch(self):
2261
remote_branch = branch.Branch.open(self.get_url('tree'))
2262
self.addCleanup(remote_branch.lock_write().unlock)
2263
self.assertSetHook(remote_branch._get_config(), 'file', 'remote')
2265
def test_set_hook_remote_bzrdir(self):
2266
remote_branch = branch.Branch.open(self.get_url('tree'))
2267
self.addCleanup(remote_branch.lock_write().unlock)
2268
remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
2269
self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
2271
def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
2275
config.OldConfigHooks.install_named_hook('load', hook, None)
2277
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2278
self.assertLength(0, calls)
2280
conf = conf_class(*conf_args)
2281
# Access an option to trigger a load
2282
conf.get_option(name)
2283
self.assertLength(expected_nb_calls, calls)
2284
# Since we can't assert about conf, we just use the number of calls ;-/
2286
def test_load_hook_remote_branch(self):
2287
remote_branch = branch.Branch.open(self.get_url('tree'))
2288
self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
2290
def test_load_hook_remote_bzrdir(self):
2291
remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
2292
# The config file doesn't exist, set an option to force its creation
2293
conf = remote_bzrdir._get_config()
2294
conf.set_option('remotedir', 'file')
2295
# We get one call for the server and one call for the client, this is
2296
# caused by the differences in implementations betwen
2297
# SmartServerBzrDirRequestConfigFile (in smart/bzrdir.py) and
2298
# SmartServerBranchGetConfigFile (in smart/branch.py)
2299
self.assertLoadHook(2 ,'file', remote.RemoteBzrDirConfig, remote_bzrdir)
2301
def assertSaveHook(self, conf):
2305
config.OldConfigHooks.install_named_hook('save', hook, None)
2307
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2308
self.assertLength(0, calls)
2309
# Setting an option triggers a save
2310
conf.set_option('foo', 'bar')
2311
self.assertLength(1, calls)
2312
# Since we can't assert about conf, we just use the number of calls ;-/
2314
def test_save_hook_remote_branch(self):
2315
remote_branch = branch.Branch.open(self.get_url('tree'))
2316
self.addCleanup(remote_branch.lock_write().unlock)
2317
self.assertSaveHook(remote_branch._get_config())
2319
def test_save_hook_remote_bzrdir(self):
2320
remote_branch = branch.Branch.open(self.get_url('tree'))
2321
self.addCleanup(remote_branch.lock_write().unlock)
2322
remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
2323
self.assertSaveHook(remote_bzrdir._get_config())
2326
class TestOption(tests.TestCase):
2328
def test_default_value(self):
2329
opt = config.Option('foo', default='bar')
2330
self.assertEquals('bar', opt.get_default())
2332
def test_callable_default_value(self):
2333
def bar_as_unicode():
2335
opt = config.Option('foo', default=bar_as_unicode)
2336
self.assertEquals('bar', opt.get_default())
2338
def test_default_value_from_env(self):
2339
opt = config.Option('foo', default='bar', default_from_env=['FOO'])
2340
self.overrideEnv('FOO', 'quux')
2341
# Env variable provides a default taking over the option one
2342
self.assertEquals('quux', opt.get_default())
2344
def test_first_default_value_from_env_wins(self):
2345
opt = config.Option('foo', default='bar',
2346
default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
2347
self.overrideEnv('FOO', 'foo')
2348
self.overrideEnv('BAZ', 'baz')
2349
# The first env var set wins
2350
self.assertEquals('foo', opt.get_default())
2352
def test_not_supported_list_default_value(self):
2353
self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
2355
def test_not_supported_object_default_value(self):
2356
self.assertRaises(AssertionError, config.Option, 'foo',
2359
def test_not_supported_callable_default_value_not_unicode(self):
2360
def bar_not_unicode():
2362
opt = config.Option('foo', default=bar_not_unicode)
2363
self.assertRaises(AssertionError, opt.get_default)
2366
class TestOptionConverterMixin(object):
2368
def assertConverted(self, expected, opt, value):
2369
self.assertEquals(expected, opt.convert_from_unicode(None, value))
2371
def assertWarns(self, opt, value):
2374
warnings.append(args[0] % args[1:])
2375
self.overrideAttr(trace, 'warning', warning)
2376
self.assertEquals(None, opt.convert_from_unicode(None, value))
2377
self.assertLength(1, warnings)
2379
'Value "%s" is not valid for "%s"' % (value, opt.name),
2382
def assertErrors(self, opt, value):
2383
self.assertRaises(errors.ConfigOptionValueError,
2384
opt.convert_from_unicode, None, value)
2386
def assertConvertInvalid(self, opt, invalid_value):
2388
self.assertEquals(None, opt.convert_from_unicode(None, invalid_value))
2389
opt.invalid = 'warning'
2390
self.assertWarns(opt, invalid_value)
2391
opt.invalid = 'error'
2392
self.assertErrors(opt, invalid_value)
2395
class TestOptionWithBooleanConverter(tests.TestCase, TestOptionConverterMixin):
2397
def get_option(self):
2398
return config.Option('foo', help='A boolean.',
2399
from_unicode=config.bool_from_store)
2401
def test_convert_invalid(self):
2402
opt = self.get_option()
2403
# A string that is not recognized as a boolean
2404
self.assertConvertInvalid(opt, u'invalid-boolean')
2405
# A list of strings is never recognized as a boolean
2406
self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
2408
def test_convert_valid(self):
2409
opt = self.get_option()
2410
self.assertConverted(True, opt, u'True')
2411
self.assertConverted(True, opt, u'1')
2412
self.assertConverted(False, opt, u'False')
2415
class TestOptionWithIntegerConverter(tests.TestCase, TestOptionConverterMixin):
2417
def get_option(self):
2418
return config.Option('foo', help='An integer.',
2419
from_unicode=config.int_from_store)
2421
def test_convert_invalid(self):
2422
opt = self.get_option()
2423
# A string that is not recognized as an integer
2424
self.assertConvertInvalid(opt, u'forty-two')
2425
# A list of strings is never recognized as an integer
2426
self.assertConvertInvalid(opt, [u'a', u'list'])
2428
def test_convert_valid(self):
2429
opt = self.get_option()
2430
self.assertConverted(16, opt, u'16')
2433
class TestOptionWithSIUnitConverter(tests.TestCase, TestOptionConverterMixin):
2435
def get_option(self):
2436
return config.Option('foo', help='An integer in SI units.',
2437
from_unicode=config.int_SI_from_store)
2439
def test_convert_invalid(self):
2440
opt = self.get_option()
2441
self.assertConvertInvalid(opt, u'not-a-unit')
2442
self.assertConvertInvalid(opt, u'Gb') # Forgot the int
2443
self.assertConvertInvalid(opt, u'1b') # Forgot the unit
2444
self.assertConvertInvalid(opt, u'1GG')
2445
self.assertConvertInvalid(opt, u'1Mbb')
2446
self.assertConvertInvalid(opt, u'1MM')
2448
def test_convert_valid(self):
2449
opt = self.get_option()
2450
self.assertConverted(int(5e3), opt, u'5kb')
2451
self.assertConverted(int(5e6), opt, u'5M')
2452
self.assertConverted(int(5e6), opt, u'5MB')
2453
self.assertConverted(int(5e9), opt, u'5g')
2454
self.assertConverted(int(5e9), opt, u'5gB')
2455
self.assertConverted(100, opt, u'100')
2458
class TestListOption(tests.TestCase, TestOptionConverterMixin):
2460
def get_option(self):
2461
return config.ListOption('foo', help='A list.')
2463
def test_convert_invalid(self):
2464
opt = self.get_option()
2465
# We don't even try to convert a list into a list, we only expect
2467
self.assertConvertInvalid(opt, [1])
2468
# No string is invalid as all forms can be converted to a list
2470
def test_convert_valid(self):
2471
opt = self.get_option()
2472
# An empty string is an empty list
2473
self.assertConverted([], opt, '') # Using a bare str() just in case
2474
self.assertConverted([], opt, u'')
2476
self.assertConverted([u'True'], opt, u'True')
2478
self.assertConverted([u'42'], opt, u'42')
2480
self.assertConverted([u'bar'], opt, u'bar')
2483
class TestOptionRegistry(tests.TestCase):
2486
super(TestOptionRegistry, self).setUp()
2487
# Always start with an empty registry
2488
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2489
self.registry = config.option_registry
2491
def test_register(self):
2492
opt = config.Option('foo')
2493
self.registry.register(opt)
2494
self.assertIs(opt, self.registry.get('foo'))
2496
def test_registered_help(self):
2497
opt = config.Option('foo', help='A simple option')
2498
self.registry.register(opt)
2499
self.assertEquals('A simple option', self.registry.get_help('foo'))
2501
lazy_option = config.Option('lazy_foo', help='Lazy help')
2503
def test_register_lazy(self):
2504
self.registry.register_lazy('lazy_foo', self.__module__,
2505
'TestOptionRegistry.lazy_option')
2506
self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
2508
def test_registered_lazy_help(self):
2509
self.registry.register_lazy('lazy_foo', self.__module__,
2510
'TestOptionRegistry.lazy_option')
2511
self.assertEquals('Lazy help', self.registry.get_help('lazy_foo'))
2514
class TestRegisteredOptions(tests.TestCase):
2515
"""All registered options should verify some constraints."""
2517
scenarios = [(key, {'option_name': key, 'option': option}) for key, option
2518
in config.option_registry.iteritems()]
2521
super(TestRegisteredOptions, self).setUp()
2522
self.registry = config.option_registry
2524
def test_proper_name(self):
2525
# An option should be registered under its own name, this can't be
2526
# checked at registration time for the lazy ones.
2527
self.assertEquals(self.option_name, self.option.name)
2529
def test_help_is_set(self):
2530
option_help = self.registry.get_help(self.option_name)
2531
self.assertNotEquals(None, option_help)
2532
# Come on, think about the user, he really wants to know what the
2534
self.assertIsNot(None, option_help)
2535
self.assertNotEquals('', option_help)
2538
class TestSection(tests.TestCase):
2540
# FIXME: Parametrize so that all sections produced by Stores run these
2541
# tests -- vila 2011-04-01
2543
def test_get_a_value(self):
2544
a_dict = dict(foo='bar')
2545
section = config.Section('myID', a_dict)
2546
self.assertEquals('bar', section.get('foo'))
2548
def test_get_unknown_option(self):
2550
section = config.Section(None, a_dict)
2551
self.assertEquals('out of thin air',
2552
section.get('foo', 'out of thin air'))
2554
def test_options_is_shared(self):
2556
section = config.Section(None, a_dict)
2557
self.assertIs(a_dict, section.options)
2560
class TestMutableSection(tests.TestCase):
2562
scenarios = [('mutable',
2564
lambda opts: config.MutableSection('myID', opts)},),
2568
a_dict = dict(foo='bar')
2569
section = self.get_section(a_dict)
2570
section.set('foo', 'new_value')
2571
self.assertEquals('new_value', section.get('foo'))
2572
# The change appears in the shared section
2573
self.assertEquals('new_value', a_dict.get('foo'))
2574
# We keep track of the change
2575
self.assertTrue('foo' in section.orig)
2576
self.assertEquals('bar', section.orig.get('foo'))
2578
def test_set_preserve_original_once(self):
2579
a_dict = dict(foo='bar')
2580
section = self.get_section(a_dict)
2581
section.set('foo', 'first_value')
2582
section.set('foo', 'second_value')
2583
# We keep track of the original value
2584
self.assertTrue('foo' in section.orig)
2585
self.assertEquals('bar', section.orig.get('foo'))
2587
def test_remove(self):
2588
a_dict = dict(foo='bar')
2589
section = self.get_section(a_dict)
2590
section.remove('foo')
2591
# We get None for unknown options via the default value
2592
self.assertEquals(None, section.get('foo'))
2593
# Or we just get the default value
2594
self.assertEquals('unknown', section.get('foo', 'unknown'))
2595
self.assertFalse('foo' in section.options)
2596
# We keep track of the deletion
2597
self.assertTrue('foo' in section.orig)
2598
self.assertEquals('bar', section.orig.get('foo'))
2600
def test_remove_new_option(self):
2602
section = self.get_section(a_dict)
2603
section.set('foo', 'bar')
2604
section.remove('foo')
2605
self.assertFalse('foo' in section.options)
2606
# The option didn't exist initially so it we need to keep track of it
2607
# with a special value
2608
self.assertTrue('foo' in section.orig)
2609
self.assertEquals(config._NewlyCreatedOption, section.orig['foo'])
2612
class TestCommandLineStore(tests.TestCase):
2615
super(TestCommandLineStore, self).setUp()
2616
self.store = config.CommandLineStore()
2617
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2619
def get_section(self):
2620
"""Get the unique section for the command line overrides."""
2621
sections = list(self.store.get_sections())
2622
self.assertLength(1, sections)
2623
store, section = sections[0]
2624
self.assertEquals(self.store, store)
2627
def test_no_override(self):
2628
self.store._from_cmdline([])
2629
section = self.get_section()
2630
self.assertLength(0, list(section.iter_option_names()))
2632
def test_simple_override(self):
2633
self.store._from_cmdline(['a=b'])
2634
section = self.get_section()
2635
self.assertEqual('b', section.get('a'))
2637
def test_list_override(self):
2638
opt = config.ListOption('l')
2639
config.option_registry.register(opt)
2640
self.store._from_cmdline(['l=1,2,3'])
2641
val = self.get_section().get('l')
2642
self.assertEqual('1,2,3', val)
2643
# Reminder: lists should be registered as such explicitely, otherwise
2644
# the conversion needs to be done afterwards.
2645
self.assertEqual(['1', '2', '3'],
2646
opt.convert_from_unicode(self.store, val))
2648
def test_multiple_overrides(self):
2649
self.store._from_cmdline(['a=b', 'x=y'])
2650
section = self.get_section()
2651
self.assertEquals('b', section.get('a'))
2652
self.assertEquals('y', section.get('x'))
2654
def test_wrong_syntax(self):
2655
self.assertRaises(errors.BzrCommandError,
2656
self.store._from_cmdline, ['a=b', 'c'])
2658
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
2660
scenarios = [(key, {'get_store': builder}) for key, builder
2661
in config.test_store_builder_registry.iteritems()] + [
2662
('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
2665
store = self.get_store(self)
2666
if type(store) == config.TransportIniFileStore:
2667
raise tests.TestNotApplicable(
2668
"%s is not a concrete Store implementation"
2669
" so it doesn't need an id" % (store.__class__.__name__,))
2670
self.assertIsNot(None, store.id)
2673
class TestStore(tests.TestCaseWithTransport):
2675
def assertSectionContent(self, expected, (store, section)):
2676
"""Assert that some options have the proper values in a section."""
2677
expected_name, expected_options = expected
2678
self.assertEquals(expected_name, section.id)
2681
dict([(k, section.get(k)) for k in expected_options.keys()]))
2684
class TestReadonlyStore(TestStore):
2686
scenarios = [(key, {'get_store': builder}) for key, builder
2687
in config.test_store_builder_registry.iteritems()]
2689
def test_building_delays_load(self):
2690
store = self.get_store(self)
2691
self.assertEquals(False, store.is_loaded())
2692
store._load_from_string('')
2693
self.assertEquals(True, store.is_loaded())
2695
def test_get_no_sections_for_empty(self):
2696
store = self.get_store(self)
2697
store._load_from_string('')
2698
self.assertEquals([], list(store.get_sections()))
2700
def test_get_default_section(self):
2701
store = self.get_store(self)
2702
store._load_from_string('foo=bar')
2703
sections = list(store.get_sections())
2704
self.assertLength(1, sections)
2705
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2707
def test_get_named_section(self):
2708
store = self.get_store(self)
2709
store._load_from_string('[baz]\nfoo=bar')
2710
sections = list(store.get_sections())
2711
self.assertLength(1, sections)
2712
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2714
def test_load_from_string_fails_for_non_empty_store(self):
2715
store = self.get_store(self)
2716
store._load_from_string('foo=bar')
2717
self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
2720
class TestStoreQuoting(TestStore):
2722
scenarios = [(key, {'get_store': builder}) for key, builder
2723
in config.test_store_builder_registry.iteritems()]
2726
super(TestStoreQuoting, self).setUp()
2727
self.store = self.get_store(self)
2728
# We need a loaded store but any content will do
2729
self.store._load_from_string('')
2731
def assertIdempotent(self, s):
2732
"""Assert that quoting an unquoted string is a no-op and vice-versa.
2734
What matters here is that option values, as they appear in a store, can
2735
be safely round-tripped out of the store and back.
2737
:param s: A string, quoted if required.
2739
self.assertEquals(s, self.store.quote(self.store.unquote(s)))
2740
self.assertEquals(s, self.store.unquote(self.store.quote(s)))
2742
def test_empty_string(self):
2743
if isinstance(self.store, config.IniFileStore):
2744
# configobj._quote doesn't handle empty values
2745
self.assertRaises(AssertionError,
2746
self.assertIdempotent, '')
2748
self.assertIdempotent('')
2749
# But quoted empty strings are ok
2750
self.assertIdempotent('""')
2752
def test_embedded_spaces(self):
2753
self.assertIdempotent('" a b c "')
2755
def test_embedded_commas(self):
2756
self.assertIdempotent('" a , b c "')
2758
def test_simple_comma(self):
2759
if isinstance(self.store, config.IniFileStore):
2760
# configobj requires that lists are special-cased
2761
self.assertRaises(AssertionError,
2762
self.assertIdempotent, ',')
2764
self.assertIdempotent(',')
2765
# When a single comma is required, quoting is also required
2766
self.assertIdempotent('","')
2768
def test_list(self):
2769
if isinstance(self.store, config.IniFileStore):
2770
# configobj requires that lists are special-cased
2771
self.assertRaises(AssertionError,
2772
self.assertIdempotent, 'a,b')
2774
self.assertIdempotent('a,b')
2777
class TestDictFromStore(tests.TestCase):
2779
def test_unquote_not_string(self):
2780
conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
2781
value = conf.get('a_section')
2782
# Urgh, despite 'conf' asking for the no-name section, we get the
2783
# content of another section as a dict o_O
2784
self.assertEquals({'a': '1'}, value)
2785
unquoted = conf.store.unquote(value)
2786
# Which cannot be unquoted but shouldn't crash either (the use cases
2787
# are getting the value or displaying it. In the later case, '%s' will
2789
self.assertEquals({'a': '1'}, unquoted)
2790
self.assertEquals("{u'a': u'1'}", '%s' % (unquoted,))
2793
class TestIniFileStoreContent(tests.TestCaseWithTransport):
2794
"""Simulate loading a config store with content of various encodings.
2796
All files produced by bzr are in utf8 content.
2798
Users may modify them manually and end up with a file that can't be
2799
loaded. We need to issue proper error messages in this case.
2802
invalid_utf8_char = '\xff'
2804
def test_load_utf8(self):
2805
"""Ensure we can load an utf8-encoded file."""
2806
t = self.get_transport()
2807
# From http://pad.lv/799212
2808
unicode_user = u'b\N{Euro Sign}ar'
2809
unicode_content = u'user=%s' % (unicode_user,)
2810
utf8_content = unicode_content.encode('utf8')
2811
# Store the raw content in the config file
2812
t.put_bytes('foo.conf', utf8_content)
2813
store = config.TransportIniFileStore(t, 'foo.conf')
2815
stack = config.Stack([store.get_sections], store)
2816
self.assertEquals(unicode_user, stack.get('user'))
2818
def test_load_non_ascii(self):
2819
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2820
t = self.get_transport()
2821
t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
2822
store = config.TransportIniFileStore(t, 'foo.conf')
2823
self.assertRaises(errors.ConfigContentError, store.load)
2825
def test_load_erroneous_content(self):
2826
"""Ensure we display a proper error on content that can't be parsed."""
2827
t = self.get_transport()
2828
t.put_bytes('foo.conf', '[open_section\n')
2829
store = config.TransportIniFileStore(t, 'foo.conf')
2830
self.assertRaises(errors.ParseConfigError, store.load)
2832
def test_load_permission_denied(self):
2833
"""Ensure we get warned when trying to load an inaccessible file."""
2836
warnings.append(args[0] % args[1:])
2837
self.overrideAttr(trace, 'warning', warning)
2839
t = self.get_transport()
2841
def get_bytes(relpath):
2842
raise errors.PermissionDenied(relpath, "")
2843
t.get_bytes = get_bytes
2844
store = config.TransportIniFileStore(t, 'foo.conf')
2845
self.assertRaises(errors.PermissionDenied, store.load)
2848
[u'Permission denied while trying to load configuration store %s.'
2849
% store.external_url()])
2852
class TestIniConfigContent(tests.TestCaseWithTransport):
2853
"""Simulate loading a IniBasedConfig with content of various encodings.
2855
All files produced by bzr are in utf8 content.
2857
Users may modify them manually and end up with a file that can't be
2858
loaded. We need to issue proper error messages in this case.
2861
invalid_utf8_char = '\xff'
2863
def test_load_utf8(self):
2864
"""Ensure we can load an utf8-encoded file."""
2865
# From http://pad.lv/799212
2866
unicode_user = u'b\N{Euro Sign}ar'
2867
unicode_content = u'user=%s' % (unicode_user,)
2868
utf8_content = unicode_content.encode('utf8')
2869
# Store the raw content in the config file
2870
with open('foo.conf', 'wb') as f:
2871
f.write(utf8_content)
2872
conf = config.IniBasedConfig(file_name='foo.conf')
2873
self.assertEquals(unicode_user, conf.get_user_option('user'))
2875
def test_load_badly_encoded_content(self):
2876
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2877
with open('foo.conf', 'wb') as f:
2878
f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
2879
conf = config.IniBasedConfig(file_name='foo.conf')
2880
self.assertRaises(errors.ConfigContentError, conf._get_parser)
2882
def test_load_erroneous_content(self):
2883
"""Ensure we display a proper error on content that can't be parsed."""
2884
with open('foo.conf', 'wb') as f:
2885
f.write('[open_section\n')
2886
conf = config.IniBasedConfig(file_name='foo.conf')
2887
self.assertRaises(errors.ParseConfigError, conf._get_parser)
2890
class TestMutableStore(TestStore):
2892
scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
2893
in config.test_store_builder_registry.iteritems()]
2896
super(TestMutableStore, self).setUp()
2897
self.transport = self.get_transport()
2899
def has_store(self, store):
2900
store_basename = urlutils.relative_url(self.transport.external_url(),
2901
store.external_url())
2902
return self.transport.has(store_basename)
2904
def test_save_empty_creates_no_file(self):
2905
# FIXME: There should be a better way than relying on the test
2906
# parametrization to identify branch.conf -- vila 2011-0526
2907
if self.store_id in ('branch', 'remote_branch'):
2908
raise tests.TestNotApplicable(
2909
'branch.conf is *always* created when a branch is initialized')
2910
store = self.get_store(self)
2912
self.assertEquals(False, self.has_store(store))
2914
def test_save_emptied_succeeds(self):
2915
store = self.get_store(self)
2916
store._load_from_string('foo=bar\n')
2917
section = store.get_mutable_section(None)
2918
section.remove('foo')
2920
self.assertEquals(True, self.has_store(store))
2921
modified_store = self.get_store(self)
2922
sections = list(modified_store.get_sections())
2923
self.assertLength(0, sections)
2925
def test_save_with_content_succeeds(self):
2926
# FIXME: There should be a better way than relying on the test
2927
# parametrization to identify branch.conf -- vila 2011-0526
2928
if self.store_id in ('branch', 'remote_branch'):
2929
raise tests.TestNotApplicable(
2930
'branch.conf is *always* created when a branch is initialized')
2931
store = self.get_store(self)
2932
store._load_from_string('foo=bar\n')
2933
self.assertEquals(False, self.has_store(store))
2935
self.assertEquals(True, self.has_store(store))
2936
modified_store = self.get_store(self)
2937
sections = list(modified_store.get_sections())
2938
self.assertLength(1, sections)
2939
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2941
def test_set_option_in_empty_store(self):
2942
store = self.get_store(self)
2943
section = store.get_mutable_section(None)
2944
section.set('foo', 'bar')
2946
modified_store = self.get_store(self)
2947
sections = list(modified_store.get_sections())
2948
self.assertLength(1, sections)
2949
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2951
def test_set_option_in_default_section(self):
2952
store = self.get_store(self)
2953
store._load_from_string('')
2954
section = store.get_mutable_section(None)
2955
section.set('foo', 'bar')
2957
modified_store = self.get_store(self)
2958
sections = list(modified_store.get_sections())
2959
self.assertLength(1, sections)
2960
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2962
def test_set_option_in_named_section(self):
2963
store = self.get_store(self)
2964
store._load_from_string('')
2965
section = store.get_mutable_section('baz')
2966
section.set('foo', 'bar')
2968
modified_store = self.get_store(self)
2969
sections = list(modified_store.get_sections())
2970
self.assertLength(1, sections)
2971
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2973
def test_load_hook(self):
2974
# We first needs to ensure that the store exists
2975
store = self.get_store(self)
2976
section = store.get_mutable_section('baz')
2977
section.set('foo', 'bar')
2979
# Now we can try to load it
2980
store = self.get_store(self)
2984
config.ConfigHooks.install_named_hook('load', hook, None)
2985
self.assertLength(0, calls)
2987
self.assertLength(1, calls)
2988
self.assertEquals((store,), calls[0])
2990
def test_save_hook(self):
2994
config.ConfigHooks.install_named_hook('save', hook, None)
2995
self.assertLength(0, calls)
2996
store = self.get_store(self)
2997
section = store.get_mutable_section('baz')
2998
section.set('foo', 'bar')
3000
self.assertLength(1, calls)
3001
self.assertEquals((store,), calls[0])
3003
def test_set_mark_dirty(self):
3004
stack = config.MemoryStack('')
3005
self.assertLength(0, stack.store.dirty_sections)
3006
stack.set('foo', 'baz')
3007
self.assertLength(1, stack.store.dirty_sections)
3008
self.assertTrue(stack.store._need_saving())
3010
def test_remove_mark_dirty(self):
3011
stack = config.MemoryStack('foo=bar')
3012
self.assertLength(0, stack.store.dirty_sections)
3014
self.assertLength(1, stack.store.dirty_sections)
3015
self.assertTrue(stack.store._need_saving())
3018
class TestStoreSaveChanges(tests.TestCaseWithTransport):
3019
"""Tests that config changes are kept in memory and saved on-demand."""
3022
super(TestStoreSaveChanges, self).setUp()
3023
self.transport = self.get_transport()
3024
# Most of the tests involve two stores pointing to the same persistent
3025
# storage to observe the effects of concurrent changes
3026
self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
3027
self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
3030
self.warnings.append(args[0] % args[1:])
3031
self.overrideAttr(trace, 'warning', warning)
3033
def has_store(self, store):
3034
store_basename = urlutils.relative_url(self.transport.external_url(),
3035
store.external_url())
3036
return self.transport.has(store_basename)
3038
def get_stack(self, store):
3039
# Any stack will do as long as it uses the right store, just a single
3040
# no-name section is enough
3041
return config.Stack([store.get_sections], store)
3043
def test_no_changes_no_save(self):
3044
s = self.get_stack(self.st1)
3045
s.store.save_changes()
3046
self.assertEquals(False, self.has_store(self.st1))
3048
def test_unrelated_concurrent_update(self):
3049
s1 = self.get_stack(self.st1)
3050
s2 = self.get_stack(self.st2)
3051
s1.set('foo', 'bar')
3052
s2.set('baz', 'quux')
3054
# Changes don't propagate magically
3055
self.assertEquals(None, s1.get('baz'))
3056
s2.store.save_changes()
3057
self.assertEquals('quux', s2.get('baz'))
3058
# Changes are acquired when saving
3059
self.assertEquals('bar', s2.get('foo'))
3060
# Since there is no overlap, no warnings are emitted
3061
self.assertLength(0, self.warnings)
3063
def test_concurrent_update_modified(self):
3064
s1 = self.get_stack(self.st1)
3065
s2 = self.get_stack(self.st2)
3066
s1.set('foo', 'bar')
3067
s2.set('foo', 'baz')
3070
s2.store.save_changes()
3071
self.assertEquals('baz', s2.get('foo'))
3072
# But the user get a warning
3073
self.assertLength(1, self.warnings)
3074
warning = self.warnings[0]
3075
self.assertStartsWith(warning, 'Option foo in section None')
3076
self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
3077
' The baz value will be saved.')
3079
def test_concurrent_deletion(self):
3080
self.st1._load_from_string('foo=bar')
3082
s1 = self.get_stack(self.st1)
3083
s2 = self.get_stack(self.st2)
3086
s1.store.save_changes()
3088
self.assertLength(0, self.warnings)
3089
s2.store.save_changes()
3091
self.assertLength(1, self.warnings)
3092
warning = self.warnings[0]
3093
self.assertStartsWith(warning, 'Option foo in section None')
3094
self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
3095
' The <DELETED> value will be saved.')
3098
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
3100
def get_store(self):
3101
return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3103
def test_get_quoted_string(self):
3104
store = self.get_store()
3105
store._load_from_string('foo= " abc "')
3106
stack = config.Stack([store.get_sections])
3107
self.assertEquals(' abc ', stack.get('foo'))
3109
def test_set_quoted_string(self):
3110
store = self.get_store()
3111
stack = config.Stack([store.get_sections], store)
3112
stack.set('foo', ' a b c ')
3114
self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
3117
class TestTransportIniFileStore(TestStore):
3119
def test_loading_unknown_file_fails(self):
3120
store = config.TransportIniFileStore(self.get_transport(),
3122
self.assertRaises(errors.NoSuchFile, store.load)
3124
def test_invalid_content(self):
3125
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3126
self.assertEquals(False, store.is_loaded())
3127
exc = self.assertRaises(
3128
errors.ParseConfigError, store._load_from_string,
3129
'this is invalid !')
3130
self.assertEndsWith(exc.filename, 'foo.conf')
3131
# And the load failed
3132
self.assertEquals(False, store.is_loaded())
3134
def test_get_embedded_sections(self):
3135
# A more complicated example (which also shows that section names and
3136
# option names share the same name space...)
3137
# FIXME: This should be fixed by forbidding dicts as values ?
3138
# -- vila 2011-04-05
3139
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3140
store._load_from_string('''
3144
foo_in_DEFAULT=foo_DEFAULT
3152
sections = list(store.get_sections())
3153
self.assertLength(4, sections)
3154
# The default section has no name.
3155
# List values are provided as strings and need to be explicitly
3156
# converted by specifying from_unicode=list_from_store at option
3158
self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
3160
self.assertSectionContent(
3161
('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
3162
self.assertSectionContent(
3163
('bar', {'foo_in_bar': 'barbar'}), sections[2])
3164
# sub sections are provided as embedded dicts.
3165
self.assertSectionContent(
3166
('baz', {'foo_in_baz': 'barbaz', 'qux': {'foo_in_qux': 'quux'}}),
3170
class TestLockableIniFileStore(TestStore):
3172
def test_create_store_in_created_dir(self):
3173
self.assertPathDoesNotExist('dir')
3174
t = self.get_transport('dir/subdir')
3175
store = config.LockableIniFileStore(t, 'foo.conf')
3176
store.get_mutable_section(None).set('foo', 'bar')
3178
self.assertPathExists('dir/subdir')
3181
class TestConcurrentStoreUpdates(TestStore):
3182
"""Test that Stores properly handle conccurent updates.
3184
New Store implementation may fail some of these tests but until such
3185
implementations exist it's hard to properly filter them from the scenarios
3186
applied here. If you encounter such a case, contact the bzr devs.
3189
scenarios = [(key, {'get_stack': builder}) for key, builder
3190
in config.test_stack_builder_registry.iteritems()]
3193
super(TestConcurrentStoreUpdates, self).setUp()
3194
self.stack = self.get_stack(self)
3195
if not isinstance(self.stack, config._CompatibleStack):
3196
raise tests.TestNotApplicable(
3197
'%s is not meant to be compatible with the old config design'
3199
self.stack.set('one', '1')
3200
self.stack.set('two', '2')
3202
self.stack.store.save()
3204
def test_simple_read_access(self):
3205
self.assertEquals('1', self.stack.get('one'))
3207
def test_simple_write_access(self):
3208
self.stack.set('one', 'one')
3209
self.assertEquals('one', self.stack.get('one'))
3211
def test_listen_to_the_last_speaker(self):
3213
c2 = self.get_stack(self)
3214
c1.set('one', 'ONE')
3215
c2.set('two', 'TWO')
3216
self.assertEquals('ONE', c1.get('one'))
3217
self.assertEquals('TWO', c2.get('two'))
3218
# The second update respect the first one
3219
self.assertEquals('ONE', c2.get('one'))
3221
def test_last_speaker_wins(self):
3222
# If the same config is not shared, the same variable modified twice
3223
# can only see a single result.
3225
c2 = self.get_stack(self)
3228
self.assertEquals('c2', c2.get('one'))
3229
# The first modification is still available until another refresh
3231
self.assertEquals('c1', c1.get('one'))
3232
c1.set('two', 'done')
3233
self.assertEquals('c2', c1.get('one'))
3235
def test_writes_are_serialized(self):
3237
c2 = self.get_stack(self)
3239
# We spawn a thread that will pause *during* the config saving.
3240
before_writing = threading.Event()
3241
after_writing = threading.Event()
3242
writing_done = threading.Event()
3243
c1_save_without_locking_orig = c1.store.save_without_locking
3244
def c1_save_without_locking():
3245
before_writing.set()
3246
c1_save_without_locking_orig()
3247
# The lock is held. We wait for the main thread to decide when to
3249
after_writing.wait()
3250
c1.store.save_without_locking = c1_save_without_locking
3254
t1 = threading.Thread(target=c1_set)
3255
# Collect the thread after the test
3256
self.addCleanup(t1.join)
3257
# Be ready to unblock the thread if the test goes wrong
3258
self.addCleanup(after_writing.set)
3260
before_writing.wait()
3261
self.assertRaises(errors.LockContention,
3262
c2.set, 'one', 'c2')
3263
self.assertEquals('c1', c1.get('one'))
3264
# Let the lock be released
3268
self.assertEquals('c2', c2.get('one'))
3270
def test_read_while_writing(self):
3272
# We spawn a thread that will pause *during* the write
3273
ready_to_write = threading.Event()
3274
do_writing = threading.Event()
3275
writing_done = threading.Event()
3276
# We override the _save implementation so we know the store is locked
3277
c1_save_without_locking_orig = c1.store.save_without_locking
3278
def c1_save_without_locking():
3279
ready_to_write.set()
3280
# The lock is held. We wait for the main thread to decide when to
3283
c1_save_without_locking_orig()
3285
c1.store.save_without_locking = c1_save_without_locking
3288
t1 = threading.Thread(target=c1_set)
3289
# Collect the thread after the test
3290
self.addCleanup(t1.join)
3291
# Be ready to unblock the thread if the test goes wrong
3292
self.addCleanup(do_writing.set)
3294
# Ensure the thread is ready to write
3295
ready_to_write.wait()
3296
self.assertEquals('c1', c1.get('one'))
3297
# If we read during the write, we get the old value
3298
c2 = self.get_stack(self)
3299
self.assertEquals('1', c2.get('one'))
3300
# Let the writing occur and ensure it occurred
3303
# Now we get the updated value
3304
c3 = self.get_stack(self)
3305
self.assertEquals('c1', c3.get('one'))
3307
# FIXME: It may be worth looking into removing the lock dir when it's not
3308
# needed anymore and look at possible fallouts for concurrent lockers. This
3309
# will matter if/when we use config files outside of bazaar directories
3310
# (.bazaar or .bzr) -- vila 20110-04-111
3313
class TestSectionMatcher(TestStore):
3315
scenarios = [('location', {'matcher': config.LocationMatcher}),
3316
('id', {'matcher': config.NameMatcher}),]
3319
super(TestSectionMatcher, self).setUp()
3320
# Any simple store is good enough
3321
self.get_store = config.test_store_builder_registry.get('configobj')
3323
def test_no_matches_for_empty_stores(self):
3324
store = self.get_store(self)
3325
store._load_from_string('')
3326
matcher = self.matcher(store, '/bar')
3327
self.assertEquals([], list(matcher.get_sections()))
3329
def test_build_doesnt_load_store(self):
3330
store = self.get_store(self)
3331
matcher = self.matcher(store, '/bar')
3332
self.assertFalse(store.is_loaded())
3335
class TestLocationSection(tests.TestCase):
3337
def get_section(self, options, extra_path):
3338
section = config.Section('foo', options)
3339
# We don't care about the length so we use '0'
3340
return config.LocationSection(section, 0, extra_path)
3342
def test_simple_option(self):
3343
section = self.get_section({'foo': 'bar'}, '')
3344
self.assertEquals('bar', section.get('foo'))
3346
def test_option_with_extra_path(self):
3347
section = self.get_section({'foo': 'bar', 'foo:policy': 'appendpath'},
3349
self.assertEquals('bar/baz', section.get('foo'))
3351
def test_invalid_policy(self):
3352
section = self.get_section({'foo': 'bar', 'foo:policy': 'die'},
3354
# invalid policies are ignored
3355
self.assertEquals('bar', section.get('foo'))
3358
class TestLocationMatcher(TestStore):
3361
super(TestLocationMatcher, self).setUp()
3362
# Any simple store is good enough
3363
self.get_store = config.test_store_builder_registry.get('configobj')
3365
def test_unrelated_section_excluded(self):
3366
store = self.get_store(self)
3367
store._load_from_string('''
3375
section=/foo/bar/baz
3379
self.assertEquals(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
3381
[section.id for _, section in store.get_sections()])
3382
matcher = config.LocationMatcher(store, '/foo/bar/quux')
3383
sections = [section for s, section in matcher.get_sections()]
3384
self.assertEquals([3, 2],
3385
[section.length for section in sections])
3386
self.assertEquals(['/foo/bar', '/foo'],
3387
[section.id for section in sections])
3388
self.assertEquals(['quux', 'bar/quux'],
3389
[section.extra_path for section in sections])
3391
def test_more_specific_sections_first(self):
3392
store = self.get_store(self)
3393
store._load_from_string('''
3399
self.assertEquals(['/foo', '/foo/bar'],
3400
[section.id for _, section in store.get_sections()])
3401
matcher = config.LocationMatcher(store, '/foo/bar/baz')
3402
sections = [section for s, section in matcher.get_sections()]
3403
self.assertEquals([3, 2],
3404
[section.length for section in sections])
3405
self.assertEquals(['/foo/bar', '/foo'],
3406
[section.id for section in sections])
3407
self.assertEquals(['baz', 'bar/baz'],
3408
[section.extra_path for section in sections])
3410
def test_appendpath_in_no_name_section(self):
3411
# It's a bit weird to allow appendpath in a no-name section, but
3412
# someone may found a use for it
3413
store = self.get_store(self)
3414
store._load_from_string('''
3416
foo:policy = appendpath
3418
matcher = config.LocationMatcher(store, 'dir/subdir')
3419
sections = list(matcher.get_sections())
3420
self.assertLength(1, sections)
3421
self.assertEquals('bar/dir/subdir', sections[0][1].get('foo'))
3423
def test_file_urls_are_normalized(self):
3424
store = self.get_store(self)
3425
if sys.platform == 'win32':
3426
expected_url = 'file:///C:/dir/subdir'
3427
expected_location = 'C:/dir/subdir'
3429
expected_url = 'file:///dir/subdir'
3430
expected_location = '/dir/subdir'
3431
matcher = config.LocationMatcher(store, expected_url)
3432
self.assertEquals(expected_location, matcher.location)
3435
class TestNameMatcher(TestStore):
3438
super(TestNameMatcher, self).setUp()
3439
self.matcher = config.NameMatcher
3440
# Any simple store is good enough
3441
self.get_store = config.test_store_builder_registry.get('configobj')
3443
def get_matching_sections(self, name):
3444
store = self.get_store(self)
3445
store._load_from_string('''
3453
matcher = self.matcher(store, name)
3454
return list(matcher.get_sections())
3456
def test_matching(self):
3457
sections = self.get_matching_sections('foo')
3458
self.assertLength(1, sections)
3459
self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
3461
def test_not_matching(self):
3462
sections = self.get_matching_sections('baz')
3463
self.assertLength(0, sections)
3466
class TestBaseStackGet(tests.TestCase):
3469
super(TestBaseStackGet, self).setUp()
3470
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3472
def test_get_first_definition(self):
3473
store1 = config.IniFileStore()
3474
store1._load_from_string('foo=bar')
3475
store2 = config.IniFileStore()
3476
store2._load_from_string('foo=baz')
3477
conf = config.Stack([store1.get_sections, store2.get_sections])
3478
self.assertEquals('bar', conf.get('foo'))
3480
def test_get_with_registered_default_value(self):
3481
config.option_registry.register(config.Option('foo', default='bar'))
3482
conf_stack = config.Stack([])
3483
self.assertEquals('bar', conf_stack.get('foo'))
3485
def test_get_without_registered_default_value(self):
3486
config.option_registry.register(config.Option('foo'))
3487
conf_stack = config.Stack([])
3488
self.assertEquals(None, conf_stack.get('foo'))
3490
def test_get_without_default_value_for_not_registered(self):
3491
conf_stack = config.Stack([])
3492
self.assertEquals(None, conf_stack.get('foo'))
3494
def test_get_for_empty_section_callable(self):
3495
conf_stack = config.Stack([lambda : []])
3496
self.assertEquals(None, conf_stack.get('foo'))
3498
def test_get_for_broken_callable(self):
3499
# Trying to use and invalid callable raises an exception on first use
3500
conf_stack = config.Stack([object])
3501
self.assertRaises(TypeError, conf_stack.get, 'foo')
3504
class TestStackWithSimpleStore(tests.TestCase):
3507
super(TestStackWithSimpleStore, self).setUp()
3508
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3509
self.registry = config.option_registry
3511
def get_conf(self, content=None):
3512
return config.MemoryStack(content)
3514
def test_override_value_from_env(self):
3515
self.registry.register(
3516
config.Option('foo', default='bar', override_from_env=['FOO']))
3517
self.overrideEnv('FOO', 'quux')
3518
# Env variable provides a default taking over the option one
3519
conf = self.get_conf('foo=store')
3520
self.assertEquals('quux', conf.get('foo'))
3522
def test_first_override_value_from_env_wins(self):
3523
self.registry.register(
3524
config.Option('foo', default='bar',
3525
override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
3526
self.overrideEnv('FOO', 'foo')
3527
self.overrideEnv('BAZ', 'baz')
3528
# The first env var set wins
3529
conf = self.get_conf('foo=store')
3530
self.assertEquals('foo', conf.get('foo'))
3533
class TestMemoryStack(tests.TestCase):
3536
conf = config.MemoryStack('foo=bar')
3537
self.assertEquals('bar', conf.get('foo'))
3540
conf = config.MemoryStack('foo=bar')
3541
conf.set('foo', 'baz')
3542
self.assertEquals('baz', conf.get('foo'))
3544
def test_no_content(self):
3545
conf = config.MemoryStack()
3546
# No content means no loading
3547
self.assertFalse(conf.store.is_loaded())
3548
self.assertRaises(NotImplementedError, conf.get, 'foo')
3549
# But a content can still be provided
3550
conf.store._load_from_string('foo=bar')
3551
self.assertEquals('bar', conf.get('foo'))
3554
class TestStackWithTransport(tests.TestCaseWithTransport):
3556
scenarios = [(key, {'get_stack': builder}) for key, builder
3557
in config.test_stack_builder_registry.iteritems()]
3560
class TestConcreteStacks(TestStackWithTransport):
3562
def test_build_stack(self):
3563
# Just a smoke test to help debug builders
3564
stack = self.get_stack(self)
3567
class TestStackGet(TestStackWithTransport):
3570
super(TestStackGet, self).setUp()
3571
self.conf = self.get_stack(self)
3573
def test_get_for_empty_stack(self):
3574
self.assertEquals(None, self.conf.get('foo'))
3576
def test_get_hook(self):
3577
self.conf.set('foo', 'bar')
3581
config.ConfigHooks.install_named_hook('get', hook, None)
3582
self.assertLength(0, calls)
3583
value = self.conf.get('foo')
3584
self.assertEquals('bar', value)
3585
self.assertLength(1, calls)
3586
self.assertEquals((self.conf, 'foo', 'bar'), calls[0])
3589
class TestStackGetWithConverter(tests.TestCase):
3592
super(TestStackGetWithConverter, self).setUp()
3593
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3594
self.registry = config.option_registry
3596
def get_conf(self, content=None):
3597
return config.MemoryStack(content)
3599
def register_bool_option(self, name, default=None, default_from_env=None):
3600
b = config.Option(name, help='A boolean.',
3601
default=default, default_from_env=default_from_env,
3602
from_unicode=config.bool_from_store)
3603
self.registry.register(b)
3605
def test_get_default_bool_None(self):
3606
self.register_bool_option('foo')
3607
conf = self.get_conf('')
3608
self.assertEquals(None, conf.get('foo'))
3610
def test_get_default_bool_True(self):
3611
self.register_bool_option('foo', u'True')
3612
conf = self.get_conf('')
3613
self.assertEquals(True, conf.get('foo'))
3615
def test_get_default_bool_False(self):
3616
self.register_bool_option('foo', False)
3617
conf = self.get_conf('')
3618
self.assertEquals(False, conf.get('foo'))
3620
def test_get_default_bool_False_as_string(self):
3621
self.register_bool_option('foo', u'False')
3622
conf = self.get_conf('')
3623
self.assertEquals(False, conf.get('foo'))
3625
def test_get_default_bool_from_env_converted(self):
3626
self.register_bool_option('foo', u'True', default_from_env=['FOO'])
3627
self.overrideEnv('FOO', 'False')
3628
conf = self.get_conf('')
3629
self.assertEquals(False, conf.get('foo'))
3631
def test_get_default_bool_when_conversion_fails(self):
3632
self.register_bool_option('foo', default='True')
3633
conf = self.get_conf('foo=invalid boolean')
3634
self.assertEquals(True, conf.get('foo'))
3636
def register_integer_option(self, name,
3637
default=None, default_from_env=None):
3638
i = config.Option(name, help='An integer.',
3639
default=default, default_from_env=default_from_env,
3640
from_unicode=config.int_from_store)
3641
self.registry.register(i)
3643
def test_get_default_integer_None(self):
3644
self.register_integer_option('foo')
3645
conf = self.get_conf('')
3646
self.assertEquals(None, conf.get('foo'))
3648
def test_get_default_integer(self):
3649
self.register_integer_option('foo', 42)
3650
conf = self.get_conf('')
3651
self.assertEquals(42, conf.get('foo'))
3653
def test_get_default_integer_as_string(self):
3654
self.register_integer_option('foo', u'42')
3655
conf = self.get_conf('')
3656
self.assertEquals(42, conf.get('foo'))
3658
def test_get_default_integer_from_env(self):
3659
self.register_integer_option('foo', default_from_env=['FOO'])
3660
self.overrideEnv('FOO', '18')
3661
conf = self.get_conf('')
3662
self.assertEquals(18, conf.get('foo'))
3664
def test_get_default_integer_when_conversion_fails(self):
3665
self.register_integer_option('foo', default='12')
3666
conf = self.get_conf('foo=invalid integer')
3667
self.assertEquals(12, conf.get('foo'))
3669
def register_list_option(self, name, default=None, default_from_env=None):
3670
l = config.ListOption(name, help='A list.', default=default,
3671
default_from_env=default_from_env)
3672
self.registry.register(l)
3674
def test_get_default_list_None(self):
3675
self.register_list_option('foo')
3676
conf = self.get_conf('')
3677
self.assertEquals(None, conf.get('foo'))
3679
def test_get_default_list_empty(self):
3680
self.register_list_option('foo', '')
3681
conf = self.get_conf('')
3682
self.assertEquals([], conf.get('foo'))
3684
def test_get_default_list_from_env(self):
3685
self.register_list_option('foo', default_from_env=['FOO'])
3686
self.overrideEnv('FOO', '')
3687
conf = self.get_conf('')
3688
self.assertEquals([], conf.get('foo'))
3690
def test_get_with_list_converter_no_item(self):
3691
self.register_list_option('foo', None)
3692
conf = self.get_conf('foo=,')
3693
self.assertEquals([], conf.get('foo'))
3695
def test_get_with_list_converter_many_items(self):
3696
self.register_list_option('foo', None)
3697
conf = self.get_conf('foo=m,o,r,e')
3698
self.assertEquals(['m', 'o', 'r', 'e'], conf.get('foo'))
3700
def test_get_with_list_converter_embedded_spaces_many_items(self):
3701
self.register_list_option('foo', None)
3702
conf = self.get_conf('foo=" bar", "baz "')
3703
self.assertEquals([' bar', 'baz '], conf.get('foo'))
3705
def test_get_with_list_converter_stripped_spaces_many_items(self):
3706
self.register_list_option('foo', None)
3707
conf = self.get_conf('foo= bar , baz ')
3708
self.assertEquals(['bar', 'baz'], conf.get('foo'))
3711
class TestIterOptionRefs(tests.TestCase):
3712
"""iter_option_refs is a bit unusual, document some cases."""
3714
def assertRefs(self, expected, string):
3715
self.assertEquals(expected, list(config.iter_option_refs(string)))
3717
def test_empty(self):
3718
self.assertRefs([(False, '')], '')
3720
def test_no_refs(self):
3721
self.assertRefs([(False, 'foo bar')], 'foo bar')
3723
def test_single_ref(self):
3724
self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
3726
def test_broken_ref(self):
3727
self.assertRefs([(False, '{foo')], '{foo')
3729
def test_embedded_ref(self):
3730
self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
3733
def test_two_refs(self):
3734
self.assertRefs([(False, ''), (True, '{foo}'),
3735
(False, ''), (True, '{bar}'),
3739
def test_newline_in_refs_are_not_matched(self):
3740
self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
3743
class TestStackExpandOptions(tests.TestCaseWithTransport):
3746
super(TestStackExpandOptions, self).setUp()
3747
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3748
self.registry = config.option_registry
3749
self.conf = build_branch_stack(self)
3751
def assertExpansion(self, expected, string, env=None):
3752
self.assertEquals(expected, self.conf.expand_options(string, env))
3754
def test_no_expansion(self):
3755
self.assertExpansion('foo', 'foo')
3757
def test_expand_default_value(self):
3758
self.conf.store._load_from_string('bar=baz')
3759
self.registry.register(config.Option('foo', default=u'{bar}'))
3760
self.assertEquals('baz', self.conf.get('foo', expand=True))
3762
def test_expand_default_from_env(self):
3763
self.conf.store._load_from_string('bar=baz')
3764
self.registry.register(config.Option('foo', default_from_env=['FOO']))
3765
self.overrideEnv('FOO', '{bar}')
3766
self.assertEquals('baz', self.conf.get('foo', expand=True))
3768
def test_expand_default_on_failed_conversion(self):
3769
self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
3770
self.registry.register(
3771
config.Option('foo', default=u'{bar}',
3772
from_unicode=config.int_from_store))
3773
self.assertEquals(42, self.conf.get('foo', expand=True))
3775
def test_env_adding_options(self):
3776
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3778
def test_env_overriding_options(self):
3779
self.conf.store._load_from_string('foo=baz')
3780
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3782
def test_simple_ref(self):
3783
self.conf.store._load_from_string('foo=xxx')
3784
self.assertExpansion('xxx', '{foo}')
3786
def test_unknown_ref(self):
3787
self.assertRaises(errors.ExpandingUnknownOption,
3788
self.conf.expand_options, '{foo}')
3790
def test_indirect_ref(self):
3791
self.conf.store._load_from_string('''
3795
self.assertExpansion('xxx', '{bar}')
3797
def test_embedded_ref(self):
3798
self.conf.store._load_from_string('''
3802
self.assertExpansion('xxx', '{{bar}}')
3804
def test_simple_loop(self):
3805
self.conf.store._load_from_string('foo={foo}')
3806
self.assertRaises(errors.OptionExpansionLoop,
3807
self.conf.expand_options, '{foo}')
3809
def test_indirect_loop(self):
3810
self.conf.store._load_from_string('''
3814
e = self.assertRaises(errors.OptionExpansionLoop,
3815
self.conf.expand_options, '{foo}')
3816
self.assertEquals('foo->bar->baz', e.refs)
3817
self.assertEquals('{foo}', e.string)
3819
def test_list(self):
3820
self.conf.store._load_from_string('''
3824
list={foo},{bar},{baz}
3826
self.registry.register(
3827
config.ListOption('list'))
3828
self.assertEquals(['start', 'middle', 'end'],
3829
self.conf.get('list', expand=True))
3831
def test_cascading_list(self):
3832
self.conf.store._load_from_string('''
3838
self.registry.register(
3839
config.ListOption('list'))
3840
self.assertEquals(['start', 'middle', 'end'],
3841
self.conf.get('list', expand=True))
3843
def test_pathologically_hidden_list(self):
3844
self.conf.store._load_from_string('''
3850
hidden={start}{middle}{end}
3852
# What matters is what the registration says, the conversion happens
3853
# only after all expansions have been performed
3854
self.registry.register(config.ListOption('hidden'))
3855
self.assertEquals(['bin', 'go'],
3856
self.conf.get('hidden', expand=True))
3859
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
3862
super(TestStackCrossSectionsExpand, self).setUp()
3864
def get_config(self, location, string):
3867
# Since we don't save the config we won't strictly require to inherit
3868
# from TestCaseInTempDir, but an error occurs so quickly...
3869
c = config.LocationStack(location)
3870
c.store._load_from_string(string)
3873
def test_dont_cross_unrelated_section(self):
3874
c = self.get_config('/another/branch/path','''
3879
[/another/branch/path]
3882
self.assertRaises(errors.ExpandingUnknownOption,
3883
c.get, 'bar', expand=True)
3885
def test_cross_related_sections(self):
3886
c = self.get_config('/project/branch/path','''
3890
[/project/branch/path]
3893
self.assertEquals('quux', c.get('bar', expand=True))
3896
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
3898
def test_cross_global_locations(self):
3899
l_store = config.LocationStore()
3900
l_store._load_from_string('''
3906
g_store = config.GlobalStore()
3907
g_store._load_from_string('''
3913
stack = config.LocationStack('/branch')
3914
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
3915
self.assertEquals('loc-foo', stack.get('gfoo', expand=True))
3918
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
3920
def test_expand_locals_empty(self):
3921
l_store = config.LocationStore()
3922
l_store._load_from_string('''
3923
[/home/user/project]
3928
stack = config.LocationStack('/home/user/project/')
3929
self.assertEquals('', stack.get('base', expand=True))
3930
self.assertEquals('', stack.get('rel', expand=True))
3932
def test_expand_basename_locally(self):
3933
l_store = config.LocationStore()
3934
l_store._load_from_string('''
3935
[/home/user/project]
3939
stack = config.LocationStack('/home/user/project/branch')
3940
self.assertEquals('branch', stack.get('bfoo', expand=True))
3942
def test_expand_basename_locally_longer_path(self):
3943
l_store = config.LocationStore()
3944
l_store._load_from_string('''
3949
stack = config.LocationStack('/home/user/project/dir/branch')
3950
self.assertEquals('branch', stack.get('bfoo', expand=True))
3952
def test_expand_relpath_locally(self):
3953
l_store = config.LocationStore()
3954
l_store._load_from_string('''
3955
[/home/user/project]
3956
lfoo = loc-foo/{relpath}
3959
stack = config.LocationStack('/home/user/project/branch')
3960
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
3962
def test_expand_relpath_unknonw_in_global(self):
3963
g_store = config.GlobalStore()
3964
g_store._load_from_string('''
3969
stack = config.LocationStack('/home/user/project/branch')
3970
self.assertRaises(errors.ExpandingUnknownOption,
3971
stack.get, 'gfoo', expand=True)
3973
def test_expand_local_option_locally(self):
3974
l_store = config.LocationStore()
3975
l_store._load_from_string('''
3976
[/home/user/project]
3977
lfoo = loc-foo/{relpath}
3981
g_store = config.GlobalStore()
3982
g_store._load_from_string('''
3988
stack = config.LocationStack('/home/user/project/branch')
3989
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
3990
self.assertEquals('loc-foo/branch', stack.get('gfoo', expand=True))
3992
def test_locals_dont_leak(self):
3993
"""Make sure we chose the right local in presence of several sections.
3995
l_store = config.LocationStore()
3996
l_store._load_from_string('''
3998
lfoo = loc-foo/{relpath}
3999
[/home/user/project]
4000
lfoo = loc-foo/{relpath}
4003
stack = config.LocationStack('/home/user/project/branch')
4004
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4005
stack = config.LocationStack('/home/user/bar/baz')
4006
self.assertEquals('loc-foo/bar/baz', stack.get('lfoo', expand=True))
4010
class TestStackSet(TestStackWithTransport):
4012
def test_simple_set(self):
4013
conf = self.get_stack(self)
4014
self.assertEquals(None, conf.get('foo'))
4015
conf.set('foo', 'baz')
4016
# Did we get it back ?
4017
self.assertEquals('baz', conf.get('foo'))
4019
def test_set_creates_a_new_section(self):
4020
conf = self.get_stack(self)
4021
conf.set('foo', 'baz')
4022
self.assertEquals, 'baz', conf.get('foo')
4024
def test_set_hook(self):
4028
config.ConfigHooks.install_named_hook('set', hook, None)
4029
self.assertLength(0, calls)
4030
conf = self.get_stack(self)
4031
conf.set('foo', 'bar')
4032
self.assertLength(1, calls)
4033
self.assertEquals((conf, 'foo', 'bar'), calls[0])
4036
class TestStackRemove(TestStackWithTransport):
4038
def test_remove_existing(self):
4039
conf = self.get_stack(self)
4040
conf.set('foo', 'bar')
4041
self.assertEquals('bar', conf.get('foo'))
4043
# Did we get it back ?
4044
self.assertEquals(None, conf.get('foo'))
4046
def test_remove_unknown(self):
4047
conf = self.get_stack(self)
4048
self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
4050
def test_remove_hook(self):
4054
config.ConfigHooks.install_named_hook('remove', hook, None)
4055
self.assertLength(0, calls)
4056
conf = self.get_stack(self)
4057
conf.set('foo', 'bar')
4059
self.assertLength(1, calls)
4060
self.assertEquals((conf, 'foo'), calls[0])
4063
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
4066
super(TestConfigGetOptions, self).setUp()
4067
create_configs(self)
4069
def test_no_variable(self):
4070
# Using branch should query branch, locations and bazaar
4071
self.assertOptions([], self.branch_config)
4073
def test_option_in_bazaar(self):
4074
self.bazaar_config.set_user_option('file', 'bazaar')
4075
self.assertOptions([('file', 'bazaar', 'DEFAULT', 'bazaar')],
4078
def test_option_in_locations(self):
4079
self.locations_config.set_user_option('file', 'locations')
4081
[('file', 'locations', self.tree.basedir, 'locations')],
4082
self.locations_config)
4084
def test_option_in_branch(self):
4085
self.branch_config.set_user_option('file', 'branch')
4086
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch')],
4089
def test_option_in_bazaar_and_branch(self):
4090
self.bazaar_config.set_user_option('file', 'bazaar')
4091
self.branch_config.set_user_option('file', 'branch')
4092
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch'),
4093
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4096
def test_option_in_branch_and_locations(self):
4097
# Hmm, locations override branch :-/
4098
self.locations_config.set_user_option('file', 'locations')
4099
self.branch_config.set_user_option('file', 'branch')
4101
[('file', 'locations', self.tree.basedir, 'locations'),
4102
('file', 'branch', 'DEFAULT', 'branch'),],
4105
def test_option_in_bazaar_locations_and_branch(self):
4106
self.bazaar_config.set_user_option('file', 'bazaar')
4107
self.locations_config.set_user_option('file', 'locations')
4108
self.branch_config.set_user_option('file', 'branch')
4110
[('file', 'locations', self.tree.basedir, 'locations'),
4111
('file', 'branch', 'DEFAULT', 'branch'),
4112
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4116
class TestConfigRemoveOption(tests.TestCaseWithTransport, TestOptionsMixin):
4119
super(TestConfigRemoveOption, self).setUp()
4120
create_configs_with_file_option(self)
4122
def test_remove_in_locations(self):
4123
self.locations_config.remove_user_option('file', self.tree.basedir)
4125
[('file', 'branch', 'DEFAULT', 'branch'),
4126
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4129
def test_remove_in_branch(self):
4130
self.branch_config.remove_user_option('file')
4132
[('file', 'locations', self.tree.basedir, 'locations'),
4133
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4136
def test_remove_in_bazaar(self):
4137
self.bazaar_config.remove_user_option('file')
4139
[('file', 'locations', self.tree.basedir, 'locations'),
4140
('file', 'branch', 'DEFAULT', 'branch'),],
4144
class TestConfigGetSections(tests.TestCaseWithTransport):
4147
super(TestConfigGetSections, self).setUp()
4148
create_configs(self)
4150
def assertSectionNames(self, expected, conf, name=None):
4151
"""Check which sections are returned for a given config.
4153
If fallback configurations exist their sections can be included.
4155
:param expected: A list of section names.
4157
:param conf: The configuration that will be queried.
4159
:param name: An optional section name that will be passed to
4162
sections = list(conf._get_sections(name))
4163
self.assertLength(len(expected), sections)
4164
self.assertEqual(expected, [name for name, _, _ in sections])
4166
def test_bazaar_default_section(self):
4167
self.assertSectionNames(['DEFAULT'], self.bazaar_config)
4169
def test_locations_default_section(self):
4170
# No sections are defined in an empty file
4171
self.assertSectionNames([], self.locations_config)
4173
def test_locations_named_section(self):
4174
self.locations_config.set_user_option('file', 'locations')
4175
self.assertSectionNames([self.tree.basedir], self.locations_config)
4177
def test_locations_matching_sections(self):
4178
loc_config = self.locations_config
4179
loc_config.set_user_option('file', 'locations')
4180
# We need to cheat a bit here to create an option in sections above and
4181
# below the 'location' one.
4182
parser = loc_config._get_parser()
4183
# locations.cong deals with '/' ignoring native os.sep
4184
location_names = self.tree.basedir.split('/')
4185
parent = '/'.join(location_names[:-1])
4186
child = '/'.join(location_names + ['child'])
4188
parser[parent]['file'] = 'parent'
4190
parser[child]['file'] = 'child'
4191
self.assertSectionNames([self.tree.basedir, parent], loc_config)
4193
def test_branch_data_default_section(self):
4194
self.assertSectionNames([None],
4195
self.branch_config._get_branch_data_config())
4197
def test_branch_default_sections(self):
4198
# No sections are defined in an empty locations file
4199
self.assertSectionNames([None, 'DEFAULT'],
4201
# Unless we define an option
4202
self.branch_config._get_location_config().set_user_option(
4203
'file', 'locations')
4204
self.assertSectionNames([self.tree.basedir, None, 'DEFAULT'],
4207
def test_bazaar_named_section(self):
4208
# We need to cheat as the API doesn't give direct access to sections
4209
# other than DEFAULT.
4210
self.bazaar_config.set_alias('bazaar', 'bzr')
4211
self.assertSectionNames(['ALIASES'], self.bazaar_config, 'ALIASES')
4214
class TestAuthenticationConfigFile(tests.TestCase):
4215
"""Test the authentication.conf file matching"""
4217
def _got_user_passwd(self, expected_user, expected_password,
4218
config, *args, **kwargs):
4219
credentials = config.get_credentials(*args, **kwargs)
4220
if credentials is None:
4224
user = credentials['user']
4225
password = credentials['password']
4226
self.assertEquals(expected_user, user)
4227
self.assertEquals(expected_password, password)
4229
def test_empty_config(self):
4230
conf = config.AuthenticationConfig(_file=StringIO())
4231
self.assertEquals({}, conf._get_config())
4232
self._got_user_passwd(None, None, conf, 'http', 'foo.net')
4234
def test_non_utf8_config(self):
4235
conf = config.AuthenticationConfig(_file=StringIO(
4237
self.assertRaises(errors.ConfigContentError, conf._get_config)
4239
def test_missing_auth_section_header(self):
4240
conf = config.AuthenticationConfig(_file=StringIO('foo = bar'))
4241
self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
4243
def test_auth_section_header_not_closed(self):
4244
conf = config.AuthenticationConfig(_file=StringIO('[DEF'))
4245
self.assertRaises(errors.ParseConfigError, conf._get_config)
4247
def test_auth_value_not_boolean(self):
4248
conf = config.AuthenticationConfig(_file=StringIO(
4252
verify_certificates=askme # Error: Not a boolean
4254
self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
4256
def test_auth_value_not_int(self):
4257
conf = config.AuthenticationConfig(_file=StringIO(
4261
port=port # Error: Not an int
4263
self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
4265
def test_unknown_password_encoding(self):
4266
conf = config.AuthenticationConfig(_file=StringIO(
4270
password_encoding=unknown
4272
self.assertRaises(ValueError, conf.get_password,
4273
'ftp', 'foo.net', 'joe')
4275
def test_credentials_for_scheme_host(self):
4276
conf = config.AuthenticationConfig(_file=StringIO(
4277
"""# Identity on foo.net
4282
password=secret-pass
4285
self._got_user_passwd('joe', 'secret-pass', conf, 'ftp', 'foo.net')
4287
self._got_user_passwd(None, None, conf, 'http', 'foo.net')
4289
self._got_user_passwd(None, None, conf, 'ftp', 'bar.net')
4291
def test_credentials_for_host_port(self):
4292
conf = config.AuthenticationConfig(_file=StringIO(
4293
"""# Identity on foo.net
4299
password=secret-pass
4302
self._got_user_passwd('joe', 'secret-pass',
4303
conf, 'ftp', 'foo.net', port=10021)
4305
self._got_user_passwd(None, None, conf, 'ftp', 'foo.net')
4307
def test_for_matching_host(self):
4308
conf = config.AuthenticationConfig(_file=StringIO(
4309
"""# Identity on foo.net
4315
[sourceforge domain]
4322
self._got_user_passwd('georges', 'bendover',
4323
conf, 'bzr', 'foo.bzr.sf.net')
4325
self._got_user_passwd(None, None,
4326
conf, 'bzr', 'bbzr.sf.net')
4328
def test_for_matching_host_None(self):
4329
conf = config.AuthenticationConfig(_file=StringIO(
4330
"""# Identity on foo.net
4340
self._got_user_passwd('joe', 'joepass',
4341
conf, 'bzr', 'quux.net')
4342
# no host but different scheme
4343
self._got_user_passwd('georges', 'bendover',
4344
conf, 'ftp', 'quux.net')
4346
def test_credentials_for_path(self):
4347
conf = config.AuthenticationConfig(_file=StringIO(
4363
self._got_user_passwd(None, None,
4364
conf, 'http', host='bar.org', path='/dir3')
4366
self._got_user_passwd('georges', 'bendover',
4367
conf, 'http', host='bar.org', path='/dir2')
4369
self._got_user_passwd('jim', 'jimpass',
4370
conf, 'http', host='bar.org',path='/dir1/subdir')
4372
def test_credentials_for_user(self):
4373
conf = config.AuthenticationConfig(_file=StringIO(
4382
self._got_user_passwd('jim', 'jimpass',
4383
conf, 'http', 'bar.org')
4385
self._got_user_passwd('jim', 'jimpass',
4386
conf, 'http', 'bar.org', user='jim')
4387
# Don't get a different user if one is specified
4388
self._got_user_passwd(None, None,
4389
conf, 'http', 'bar.org', user='georges')
4391
def test_credentials_for_user_without_password(self):
4392
conf = config.AuthenticationConfig(_file=StringIO(
4399
# Get user but no password
4400
self._got_user_passwd('jim', None,
4401
conf, 'http', 'bar.org')
4403
def test_verify_certificates(self):
4404
conf = config.AuthenticationConfig(_file=StringIO(
4411
verify_certificates=False
4418
credentials = conf.get_credentials('https', 'bar.org')
4419
self.assertEquals(False, credentials.get('verify_certificates'))
4420
credentials = conf.get_credentials('https', 'foo.net')
4421
self.assertEquals(True, credentials.get('verify_certificates'))
4424
class TestAuthenticationStorage(tests.TestCaseInTempDir):
4426
def test_set_credentials(self):
4427
conf = config.AuthenticationConfig()
4428
conf.set_credentials('name', 'host', 'user', 'scheme', 'password',
4429
99, path='/foo', verify_certificates=False, realm='realm')
4430
credentials = conf.get_credentials(host='host', scheme='scheme',
4431
port=99, path='/foo',
4433
CREDENTIALS = {'name': 'name', 'user': 'user', 'password': 'password',
4434
'verify_certificates': False, 'scheme': 'scheme',
4435
'host': 'host', 'port': 99, 'path': '/foo',
4437
self.assertEqual(CREDENTIALS, credentials)
4438
credentials_from_disk = config.AuthenticationConfig().get_credentials(
4439
host='host', scheme='scheme', port=99, path='/foo', realm='realm')
4440
self.assertEqual(CREDENTIALS, credentials_from_disk)
4442
def test_reset_credentials_different_name(self):
4443
conf = config.AuthenticationConfig()
4444
conf.set_credentials('name', 'host', 'user', 'scheme', 'password'),
4445
conf.set_credentials('name2', 'host', 'user2', 'scheme', 'password'),
4446
self.assertIs(None, conf._get_config().get('name'))
4447
credentials = conf.get_credentials(host='host', scheme='scheme')
4448
CREDENTIALS = {'name': 'name2', 'user': 'user2', 'password':
4449
'password', 'verify_certificates': True,
4450
'scheme': 'scheme', 'host': 'host', 'port': None,
4451
'path': None, 'realm': None}
4452
self.assertEqual(CREDENTIALS, credentials)
4455
class TestAuthenticationConfig(tests.TestCase):
4456
"""Test AuthenticationConfig behaviour"""
4458
def _check_default_password_prompt(self, expected_prompt_format, scheme,
4459
host=None, port=None, realm=None,
4463
user, password = 'jim', 'precious'
4464
expected_prompt = expected_prompt_format % {
4465
'scheme': scheme, 'host': host, 'port': port,
4466
'user': user, 'realm': realm}
4468
stdout = tests.StringIOWrapper()
4469
stderr = tests.StringIOWrapper()
4470
ui.ui_factory = tests.TestUIFactory(stdin=password + '\n',
4471
stdout=stdout, stderr=stderr)
4472
# We use an empty conf so that the user is always prompted
4473
conf = config.AuthenticationConfig()
4474
self.assertEquals(password,
4475
conf.get_password(scheme, host, user, port=port,
4476
realm=realm, path=path))
4477
self.assertEquals(expected_prompt, stderr.getvalue())
4478
self.assertEquals('', stdout.getvalue())
4480
def _check_default_username_prompt(self, expected_prompt_format, scheme,
4481
host=None, port=None, realm=None,
4486
expected_prompt = expected_prompt_format % {
4487
'scheme': scheme, 'host': host, 'port': port,
4489
stdout = tests.StringIOWrapper()
4490
stderr = tests.StringIOWrapper()
4491
ui.ui_factory = tests.TestUIFactory(stdin=username+ '\n',
4492
stdout=stdout, stderr=stderr)
4493
# We use an empty conf so that the user is always prompted
4494
conf = config.AuthenticationConfig()
4495
self.assertEquals(username, conf.get_user(scheme, host, port=port,
4496
realm=realm, path=path, ask=True))
4497
self.assertEquals(expected_prompt, stderr.getvalue())
4498
self.assertEquals('', stdout.getvalue())
4500
def test_username_defaults_prompts(self):
4501
# HTTP prompts can't be tested here, see test_http.py
4502
self._check_default_username_prompt(u'FTP %(host)s username: ', 'ftp')
4503
self._check_default_username_prompt(
4504
u'FTP %(host)s:%(port)d username: ', 'ftp', port=10020)
4505
self._check_default_username_prompt(
4506
u'SSH %(host)s:%(port)d username: ', 'ssh', port=12345)
4508
def test_username_default_no_prompt(self):
4509
conf = config.AuthenticationConfig()
4510
self.assertEquals(None,
4511
conf.get_user('ftp', 'example.com'))
4512
self.assertEquals("explicitdefault",
4513
conf.get_user('ftp', 'example.com', default="explicitdefault"))
4515
def test_password_default_prompts(self):
4516
# HTTP prompts can't be tested here, see test_http.py
4517
self._check_default_password_prompt(
4518
u'FTP %(user)s@%(host)s password: ', 'ftp')
4519
self._check_default_password_prompt(
4520
u'FTP %(user)s@%(host)s:%(port)d password: ', 'ftp', port=10020)
4521
self._check_default_password_prompt(
4522
u'SSH %(user)s@%(host)s:%(port)d password: ', 'ssh', port=12345)
4523
# SMTP port handling is a bit special (it's handled if embedded in the
4525
# FIXME: should we: forbid that, extend it to other schemes, leave
4526
# things as they are that's fine thank you ?
4527
self._check_default_password_prompt(
4528
u'SMTP %(user)s@%(host)s password: ', 'smtp')
4529
self._check_default_password_prompt(
4530
u'SMTP %(user)s@%(host)s password: ', 'smtp', host='bar.org:10025')
4531
self._check_default_password_prompt(
4532
u'SMTP %(user)s@%(host)s:%(port)d password: ', 'smtp', port=10025)
4534
def test_ssh_password_emits_warning(self):
4535
conf = config.AuthenticationConfig(_file=StringIO(
4543
entered_password = 'typed-by-hand'
4544
stdout = tests.StringIOWrapper()
4545
stderr = tests.StringIOWrapper()
4546
ui.ui_factory = tests.TestUIFactory(stdin=entered_password + '\n',
4547
stdout=stdout, stderr=stderr)
4549
# Since the password defined in the authentication config is ignored,
4550
# the user is prompted
4551
self.assertEquals(entered_password,
4552
conf.get_password('ssh', 'bar.org', user='jim'))
4553
self.assertContainsRe(
4555
'password ignored in section \[ssh with password\]')
4557
def test_ssh_without_password_doesnt_emit_warning(self):
4558
conf = config.AuthenticationConfig(_file=StringIO(
4565
entered_password = 'typed-by-hand'
4566
stdout = tests.StringIOWrapper()
4567
stderr = tests.StringIOWrapper()
4568
ui.ui_factory = tests.TestUIFactory(stdin=entered_password + '\n',
4572
# Since the password defined in the authentication config is ignored,
4573
# the user is prompted
4574
self.assertEquals(entered_password,
4575
conf.get_password('ssh', 'bar.org', user='jim'))
4576
# No warning shoud be emitted since there is no password. We are only
4578
self.assertNotContainsRe(
4580
'password ignored in section \[ssh with password\]')
4582
def test_uses_fallback_stores(self):
4583
self.overrideAttr(config, 'credential_store_registry',
4584
config.CredentialStoreRegistry())
4585
store = StubCredentialStore()
4586
store.add_credentials("http", "example.com", "joe", "secret")
4587
config.credential_store_registry.register("stub", store, fallback=True)
4588
conf = config.AuthenticationConfig(_file=StringIO())
4589
creds = conf.get_credentials("http", "example.com")
4590
self.assertEquals("joe", creds["user"])
4591
self.assertEquals("secret", creds["password"])
4594
class StubCredentialStore(config.CredentialStore):
4600
def add_credentials(self, scheme, host, user, password=None):
4601
self._username[(scheme, host)] = user
4602
self._password[(scheme, host)] = password
4604
def get_credentials(self, scheme, host, port=None, user=None,
4605
path=None, realm=None):
4606
key = (scheme, host)
4607
if not key in self._username:
4609
return { "scheme": scheme, "host": host, "port": port,
4610
"user": self._username[key], "password": self._password[key]}
4613
class CountingCredentialStore(config.CredentialStore):
4618
def get_credentials(self, scheme, host, port=None, user=None,
4619
path=None, realm=None):
4624
class TestCredentialStoreRegistry(tests.TestCase):
4626
def _get_cs_registry(self):
4627
return config.credential_store_registry
4629
def test_default_credential_store(self):
4630
r = self._get_cs_registry()
4631
default = r.get_credential_store(None)
4632
self.assertIsInstance(default, config.PlainTextCredentialStore)
4634
def test_unknown_credential_store(self):
4635
r = self._get_cs_registry()
4636
# It's hard to imagine someone creating a credential store named
4637
# 'unknown' so we use that as an never registered key.
4638
self.assertRaises(KeyError, r.get_credential_store, 'unknown')
4640
def test_fallback_none_registered(self):
4641
r = config.CredentialStoreRegistry()
4642
self.assertEquals(None,
4643
r.get_fallback_credentials("http", "example.com"))
4645
def test_register(self):
4646
r = config.CredentialStoreRegistry()
4647
r.register("stub", StubCredentialStore(), fallback=False)
4648
r.register("another", StubCredentialStore(), fallback=True)
4649
self.assertEquals(["another", "stub"], r.keys())
4651
def test_register_lazy(self):
4652
r = config.CredentialStoreRegistry()
4653
r.register_lazy("stub", "bzrlib.tests.test_config",
4654
"StubCredentialStore", fallback=False)
4655
self.assertEquals(["stub"], r.keys())
4656
self.assertIsInstance(r.get_credential_store("stub"),
4657
StubCredentialStore)
4659
def test_is_fallback(self):
4660
r = config.CredentialStoreRegistry()
4661
r.register("stub1", None, fallback=False)
4662
r.register("stub2", None, fallback=True)
4663
self.assertEquals(False, r.is_fallback("stub1"))
4664
self.assertEquals(True, r.is_fallback("stub2"))
4666
def test_no_fallback(self):
4667
r = config.CredentialStoreRegistry()
4668
store = CountingCredentialStore()
4669
r.register("count", store, fallback=False)
4670
self.assertEquals(None,
4671
r.get_fallback_credentials("http", "example.com"))
4672
self.assertEquals(0, store._calls)
4674
def test_fallback_credentials(self):
4675
r = config.CredentialStoreRegistry()
4676
store = StubCredentialStore()
4677
store.add_credentials("http", "example.com",
4678
"somebody", "geheim")
4679
r.register("stub", store, fallback=True)
4680
creds = r.get_fallback_credentials("http", "example.com")
4681
self.assertEquals("somebody", creds["user"])
4682
self.assertEquals("geheim", creds["password"])
4684
def test_fallback_first_wins(self):
4685
r = config.CredentialStoreRegistry()
4686
stub1 = StubCredentialStore()
4687
stub1.add_credentials("http", "example.com",
4688
"somebody", "stub1")
4689
r.register("stub1", stub1, fallback=True)
4690
stub2 = StubCredentialStore()
4691
stub2.add_credentials("http", "example.com",
4692
"somebody", "stub2")
4693
r.register("stub2", stub1, fallback=True)
4694
creds = r.get_fallback_credentials("http", "example.com")
4695
self.assertEquals("somebody", creds["user"])
4696
self.assertEquals("stub1", creds["password"])
4699
class TestPlainTextCredentialStore(tests.TestCase):
4701
def test_decode_password(self):
4702
r = config.credential_store_registry
4703
plain_text = r.get_credential_store()
4704
decoded = plain_text.decode_password(dict(password='secret'))
4705
self.assertEquals('secret', decoded)
4708
# FIXME: Once we have a way to declare authentication to all test servers, we
4709
# can implement generic tests.
4710
# test_user_password_in_url
4711
# test_user_in_url_password_from_config
4712
# test_user_in_url_password_prompted
4713
# test_user_in_config
4714
# test_user_getpass.getuser
4715
# test_user_prompted ?
4716
class TestAuthenticationRing(tests.TestCaseWithTransport):
4720
class TestAutoUserId(tests.TestCase):
4721
"""Test inferring an automatic user name."""
4723
def test_auto_user_id(self):
4724
"""Automatic inference of user name.
4726
This is a bit hard to test in an isolated way, because it depends on
4727
system functions that go direct to /etc or perhaps somewhere else.
4728
But it's reasonable to say that on Unix, with an /etc/mailname, we ought
4729
to be able to choose a user name with no configuration.
4731
if sys.platform == 'win32':
4732
raise tests.TestSkipped(
4733
"User name inference not implemented on win32")
4734
realname, address = config._auto_user_id()
4735
if os.path.exists('/etc/mailname'):
4736
self.assertIsNot(None, realname)
4737
self.assertIsNot(None, address)
4739
self.assertEquals((None, None), (realname, address))
4742
class EmailOptionTests(tests.TestCase):
4744
def test_default_email_uses_BZR_EMAIL(self):
4745
conf = config.MemoryStack('email=jelmer@debian.org')
4746
# BZR_EMAIL takes precedence over EMAIL
4747
self.overrideEnv('BZR_EMAIL', 'jelmer@samba.org')
4748
self.overrideEnv('EMAIL', 'jelmer@apache.org')
4749
self.assertEquals('jelmer@samba.org', conf.get('email'))
4751
def test_default_email_uses_EMAIL(self):
4752
conf = config.MemoryStack('')
4753
self.overrideEnv('BZR_EMAIL', None)
4754
self.overrideEnv('EMAIL', 'jelmer@apache.org')
4755
self.assertEquals('jelmer@apache.org', conf.get('email'))
4757
def test_BZR_EMAIL_overrides(self):
4758
conf = config.MemoryStack('email=jelmer@debian.org')
4759
self.overrideEnv('BZR_EMAIL', 'jelmer@apache.org')
4760
self.assertEquals('jelmer@apache.org', conf.get('email'))
4761
self.overrideEnv('BZR_EMAIL', None)
4762
self.overrideEnv('EMAIL', 'jelmer@samba.org')
4763
self.assertEquals('jelmer@debian.org', conf.get('email'))