1
# Copyright (C) 2005-2012 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for finding and reading the bzr config file[s]."""
18
# import system imports here
19
from cStringIO import StringIO
25
from testtools import matchers
27
#import bzrlib specific imports here
38
registry as _mod_registry,
43
from bzrlib.symbol_versioning import (
46
from bzrlib.transport import remote as transport_remote
47
from bzrlib.tests import (
52
from bzrlib.util.configobj import configobj
55
def lockable_config_scenarios():
58
{'config_class': config.GlobalConfig,
60
'config_section': 'DEFAULT'}),
62
{'config_class': config.LocationConfig,
64
'config_section': '.'}),]
67
load_tests = scenarios.load_tests_apply_scenarios
69
# Register helpers to build stores
70
config.test_store_builder_registry.register(
71
'configobj', lambda test: config.TransportIniFileStore(
72
test.get_transport(), 'configobj.conf'))
73
config.test_store_builder_registry.register(
74
'bazaar', lambda test: config.GlobalStore())
75
config.test_store_builder_registry.register(
76
'location', lambda test: config.LocationStore())
79
def build_backing_branch(test, relpath,
80
transport_class=None, server_class=None):
81
"""Test helper to create a backing branch only once.
83
Some tests needs multiple stores/stacks to check concurrent update
84
behaviours. As such, they need to build different branch *objects* even if
85
they share the branch on disk.
87
:param relpath: The relative path to the branch. (Note that the helper
88
should always specify the same relpath).
90
:param transport_class: The Transport class the test needs to use.
92
:param server_class: The server associated with the ``transport_class``
95
Either both or neither of ``transport_class`` and ``server_class`` should
98
if transport_class is not None and server_class is not None:
99
test.transport_class = transport_class
100
test.transport_server = server_class
101
elif not (transport_class is None and server_class is None):
102
raise AssertionError('Specify both ``transport_class`` and '
103
'``server_class`` or neither of them')
104
if getattr(test, 'backing_branch', None) is None:
105
# First call, let's build the branch on disk
106
test.backing_branch = test.make_branch(relpath)
109
def build_branch_store(test):
110
build_backing_branch(test, 'branch')
111
b = branch.Branch.open('branch')
112
return config.BranchStore(b)
113
config.test_store_builder_registry.register('branch', build_branch_store)
116
def build_control_store(test):
117
build_backing_branch(test, 'branch')
118
b = controldir.ControlDir.open('branch')
119
return config.ControlStore(b)
120
config.test_store_builder_registry.register('control', build_control_store)
123
def build_remote_branch_store(test):
124
# There is only one permutation (but we won't be able to handle more with
125
# this design anyway)
127
server_class) = transport_remote.get_test_permutations()[0]
128
build_backing_branch(test, 'branch', transport_class, server_class)
129
b = branch.Branch.open(test.get_url('branch'))
130
return config.BranchStore(b)
131
config.test_store_builder_registry.register('remote_branch',
132
build_remote_branch_store)
135
config.test_stack_builder_registry.register(
136
'bazaar', lambda test: config.GlobalStack())
137
config.test_stack_builder_registry.register(
138
'location', lambda test: config.LocationStack('.'))
141
def build_branch_stack(test):
142
build_backing_branch(test, 'branch')
143
b = branch.Branch.open('branch')
144
return config.BranchStack(b)
145
config.test_stack_builder_registry.register('branch', build_branch_stack)
148
def build_branch_only_stack(test):
149
# There is only one permutation (but we won't be able to handle more with
150
# this design anyway)
152
server_class) = transport_remote.get_test_permutations()[0]
153
build_backing_branch(test, 'branch', transport_class, server_class)
154
b = branch.Branch.open(test.get_url('branch'))
155
return config.BranchOnlyStack(b)
156
config.test_stack_builder_registry.register('branch_only',
157
build_branch_only_stack)
159
def build_remote_control_stack(test):
160
# There is only one permutation (but we won't be able to handle more with
161
# this design anyway)
163
server_class) = transport_remote.get_test_permutations()[0]
164
# We need only a bzrdir for this, not a full branch, but it's not worth
165
# creating a dedicated helper to create only the bzrdir
166
build_backing_branch(test, 'branch', transport_class, server_class)
167
b = branch.Branch.open(test.get_url('branch'))
168
return config.RemoteControlStack(b.bzrdir)
169
config.test_stack_builder_registry.register('remote_control',
170
build_remote_control_stack)
173
sample_long_alias="log -r-15..-1 --line"
174
sample_config_text = u"""
176
email=Erik B\u00e5gfors <erik@bagfors.nu>
178
change_editor=vimdiff -of @new_path @old_path
179
gpg_signing_command=gnome-gpg
180
gpg_signing_key=DD4D5088
182
validate_signatures_in_log=true
184
user_global_option=something
185
bzr.mergetool.sometool=sometool {base} {this} {other} -o {result}
186
bzr.mergetool.funkytool=funkytool "arg with spaces" {this_temp}
187
bzr.mergetool.newtool='"newtool with spaces" {this_temp}'
188
bzr.default_mergetool=sometool
191
ll=""" + sample_long_alias + "\n"
194
sample_always_signatures = """
196
check_signatures=ignore
197
create_signatures=always
200
sample_ignore_signatures = """
202
check_signatures=require
203
create_signatures=never
206
sample_maybe_signatures = """
208
check_signatures=ignore
209
create_signatures=when-required
212
sample_branches_text = """
213
[http://www.example.com]
215
email=Robert Collins <robertc@example.org>
216
normal_option = normal
217
appendpath_option = append
218
appendpath_option:policy = appendpath
219
norecurse_option = norecurse
220
norecurse_option:policy = norecurse
221
[http://www.example.com/ignoreparent]
222
# different project: ignore parent dir config
224
[http://www.example.com/norecurse]
225
# configuration items that only apply to this dir
227
normal_option = norecurse
228
[http://www.example.com/dir]
229
appendpath_option = normal
231
check_signatures=require
232
# test trailing / matching with no children
234
check_signatures=check-available
235
gpg_signing_command=false
236
gpg_signing_key=default
237
user_local_option=local
238
# test trailing / matching
240
#subdirs will match but not the parent
242
check_signatures=ignore
243
post_commit=bzrlib.tests.test_config.post_commit
244
#testing explicit beats globs
248
def create_configs(test):
249
"""Create configuration files for a given test.
251
This requires creating a tree (and populate the ``test.tree`` attribute)
252
and its associated branch and will populate the following attributes:
254
- branch_config: A BranchConfig for the associated branch.
256
- locations_config : A LocationConfig for the associated branch
258
- bazaar_config: A GlobalConfig.
260
The tree and branch are created in a 'tree' subdirectory so the tests can
261
still use the test directory to stay outside of the branch.
263
tree = test.make_branch_and_tree('tree')
265
test.branch_config = config.BranchConfig(tree.branch)
266
test.locations_config = config.LocationConfig(tree.basedir)
267
test.bazaar_config = config.GlobalConfig()
270
def create_configs_with_file_option(test):
271
"""Create configuration files with a ``file`` option set in each.
273
This builds on ``create_configs`` and add one ``file`` option in each
274
configuration with a value which allows identifying the configuration file.
277
test.bazaar_config.set_user_option('file', 'bazaar')
278
test.locations_config.set_user_option('file', 'locations')
279
test.branch_config.set_user_option('file', 'branch')
282
class TestOptionsMixin:
284
def assertOptions(self, expected, conf):
285
# We don't care about the parser (as it will make tests hard to write
286
# and error-prone anyway)
287
self.assertThat([opt[:4] for opt in conf._get_options()],
288
matchers.Equals(expected))
291
class InstrumentedConfigObj(object):
292
"""A config obj look-enough-alike to record calls made to it."""
294
def __contains__(self, thing):
295
self._calls.append(('__contains__', thing))
298
def __getitem__(self, key):
299
self._calls.append(('__getitem__', key))
302
def __init__(self, input, encoding=None):
303
self._calls = [('__init__', input, encoding)]
305
def __setitem__(self, key, value):
306
self._calls.append(('__setitem__', key, value))
308
def __delitem__(self, key):
309
self._calls.append(('__delitem__', key))
312
self._calls.append(('keys',))
316
self._calls.append(('reload',))
318
def write(self, arg):
319
self._calls.append(('write',))
321
def as_bool(self, value):
322
self._calls.append(('as_bool', value))
325
def get_value(self, section, name):
326
self._calls.append(('get_value', section, name))
330
class FakeBranch(object):
332
def __init__(self, base=None):
334
self.base = "http://example.com/branches/demo"
337
self._transport = self.control_files = \
338
FakeControlFilesAndTransport()
340
def _get_config(self):
341
return config.TransportConfig(self._transport, 'branch.conf')
343
def lock_write(self):
350
class FakeControlFilesAndTransport(object):
354
self._transport = self
356
def get(self, filename):
359
return StringIO(self.files[filename])
361
raise errors.NoSuchFile(filename)
363
def get_bytes(self, filename):
366
return self.files[filename]
368
raise errors.NoSuchFile(filename)
370
def put(self, filename, fileobj):
371
self.files[filename] = fileobj.read()
373
def put_file(self, filename, fileobj):
374
return self.put(filename, fileobj)
377
class InstrumentedConfig(config.Config):
378
"""An instrumented config that supplies stubs for template methods."""
381
super(InstrumentedConfig, self).__init__()
383
self._signatures = config.CHECK_NEVER
385
def _get_user_id(self):
386
self._calls.append('_get_user_id')
387
return "Robert Collins <robert.collins@example.org>"
389
def _get_signature_checking(self):
390
self._calls.append('_get_signature_checking')
391
return self._signatures
393
def _get_change_editor(self):
394
self._calls.append('_get_change_editor')
395
return 'vimdiff -fo @new_path @old_path'
398
bool_config = """[DEFAULT]
407
class TestConfigObj(tests.TestCase):
409
def test_get_bool(self):
410
co = config.ConfigObj(StringIO(bool_config))
411
self.assertIs(co.get_bool('DEFAULT', 'active'), True)
412
self.assertIs(co.get_bool('DEFAULT', 'inactive'), False)
413
self.assertIs(co.get_bool('UPPERCASE', 'active'), True)
414
self.assertIs(co.get_bool('UPPERCASE', 'nonactive'), False)
416
def test_hash_sign_in_value(self):
418
Before 4.5.0, ConfigObj did not quote # signs in values, so they'd be
419
treated as comments when read in again. (#86838)
421
co = config.ConfigObj()
422
co['test'] = 'foo#bar'
424
co.write(outfile=outfile)
425
lines = outfile.getvalue().splitlines()
426
self.assertEqual(lines, ['test = "foo#bar"'])
427
co2 = config.ConfigObj(lines)
428
self.assertEqual(co2['test'], 'foo#bar')
430
def test_triple_quotes(self):
431
# Bug #710410: if the value string has triple quotes
432
# then ConfigObj versions up to 4.7.2 will quote them wrong
433
# and won't able to read them back
434
triple_quotes_value = '''spam
435
""" that's my spam """
437
co = config.ConfigObj()
438
co['test'] = triple_quotes_value
439
# While writing this test another bug in ConfigObj has been found:
440
# method co.write() without arguments produces list of lines
441
# one option per line, and multiline values are not split
442
# across multiple lines,
443
# and that breaks the parsing these lines back by ConfigObj.
444
# This issue only affects test, but it's better to avoid
445
# `co.write()` construct at all.
446
# [bialix 20110222] bug report sent to ConfigObj's author
448
co.write(outfile=outfile)
449
output = outfile.getvalue()
450
# now we're trying to read it back
451
co2 = config.ConfigObj(StringIO(output))
452
self.assertEquals(triple_quotes_value, co2['test'])
455
erroneous_config = """[section] # line 1
458
whocares=notme # line 4
462
class TestConfigObjErrors(tests.TestCase):
464
def test_duplicate_section_name_error_line(self):
466
co = configobj.ConfigObj(StringIO(erroneous_config),
468
except config.configobj.DuplicateError, e:
469
self.assertEqual(3, e.line_number)
471
self.fail('Error in config file not detected')
474
class TestConfig(tests.TestCase):
476
def test_constructs(self):
479
def test_no_default_editor(self):
482
self.applyDeprecated, deprecated_in((2, 4, 0)),
483
config.Config().get_editor)
485
def test_user_email(self):
486
my_config = InstrumentedConfig()
487
self.assertEqual('robert.collins@example.org', my_config.user_email())
488
self.assertEqual(['_get_user_id'], my_config._calls)
490
def test_username(self):
491
my_config = InstrumentedConfig()
492
self.assertEqual('Robert Collins <robert.collins@example.org>',
493
my_config.username())
494
self.assertEqual(['_get_user_id'], my_config._calls)
496
def test_signatures_default(self):
497
my_config = config.Config()
499
self.applyDeprecated(deprecated_in((2, 5, 0)),
500
my_config.signature_needed))
501
self.assertEqual(config.CHECK_IF_POSSIBLE,
502
self.applyDeprecated(deprecated_in((2, 5, 0)),
503
my_config.signature_checking))
504
self.assertEqual(config.SIGN_WHEN_REQUIRED,
505
self.applyDeprecated(deprecated_in((2, 5, 0)),
506
my_config.signing_policy))
508
def test_signatures_template_method(self):
509
my_config = InstrumentedConfig()
510
self.assertEqual(config.CHECK_NEVER,
511
self.applyDeprecated(deprecated_in((2, 5, 0)),
512
my_config.signature_checking))
513
self.assertEqual(['_get_signature_checking'], my_config._calls)
515
def test_signatures_template_method_none(self):
516
my_config = InstrumentedConfig()
517
my_config._signatures = None
518
self.assertEqual(config.CHECK_IF_POSSIBLE,
519
self.applyDeprecated(deprecated_in((2, 5, 0)),
520
my_config.signature_checking))
521
self.assertEqual(['_get_signature_checking'], my_config._calls)
523
def test_gpg_signing_command_default(self):
524
my_config = config.Config()
525
self.assertEqual('gpg',
526
self.applyDeprecated(deprecated_in((2, 5, 0)),
527
my_config.gpg_signing_command))
529
def test_get_user_option_default(self):
530
my_config = config.Config()
531
self.assertEqual(None, my_config.get_user_option('no_option'))
533
def test_post_commit_default(self):
534
my_config = config.Config()
535
self.assertEqual(None, self.applyDeprecated(deprecated_in((2, 5, 0)),
536
my_config.post_commit))
539
def test_log_format_default(self):
540
my_config = config.Config()
541
self.assertEqual('long',
542
self.applyDeprecated(deprecated_in((2, 5, 0)),
543
my_config.log_format))
545
def test_acceptable_keys_default(self):
546
my_config = config.Config()
547
self.assertEqual(None, self.applyDeprecated(deprecated_in((2, 5, 0)),
548
my_config.acceptable_keys))
550
def test_validate_signatures_in_log_default(self):
551
my_config = config.Config()
552
self.assertEqual(False, my_config.validate_signatures_in_log())
554
def test_get_change_editor(self):
555
my_config = InstrumentedConfig()
556
change_editor = my_config.get_change_editor('old_tree', 'new_tree')
557
self.assertEqual(['_get_change_editor'], my_config._calls)
558
self.assertIs(diff.DiffFromTool, change_editor.__class__)
559
self.assertEqual(['vimdiff', '-fo', '@new_path', '@old_path'],
560
change_editor.command_template)
563
class TestConfigPath(tests.TestCase):
566
super(TestConfigPath, self).setUp()
567
self.overrideEnv('HOME', '/home/bogus')
568
self.overrideEnv('XDG_CACHE_DIR', '')
569
if sys.platform == 'win32':
571
'BZR_HOME', r'C:\Documents and Settings\bogus\Application Data')
573
'C:/Documents and Settings/bogus/Application Data/bazaar/2.0'
575
self.bzr_home = '/home/bogus/.bazaar'
577
def test_config_dir(self):
578
self.assertEqual(config.config_dir(), self.bzr_home)
580
def test_config_dir_is_unicode(self):
581
self.assertIsInstance(config.config_dir(), unicode)
583
def test_config_filename(self):
584
self.assertEqual(config.config_filename(),
585
self.bzr_home + '/bazaar.conf')
587
def test_locations_config_filename(self):
588
self.assertEqual(config.locations_config_filename(),
589
self.bzr_home + '/locations.conf')
591
def test_authentication_config_filename(self):
592
self.assertEqual(config.authentication_config_filename(),
593
self.bzr_home + '/authentication.conf')
595
def test_xdg_cache_dir(self):
596
self.assertEqual(config.xdg_cache_dir(),
597
'/home/bogus/.cache')
600
class TestXDGConfigDir(tests.TestCaseInTempDir):
601
# must be in temp dir because config tests for the existence of the bazaar
602
# subdirectory of $XDG_CONFIG_HOME
605
if sys.platform in ('darwin', 'win32'):
606
raise tests.TestNotApplicable(
607
'XDG config dir not used on this platform')
608
super(TestXDGConfigDir, self).setUp()
609
self.overrideEnv('HOME', self.test_home_dir)
610
# BZR_HOME overrides everything we want to test so unset it.
611
self.overrideEnv('BZR_HOME', None)
613
def test_xdg_config_dir_exists(self):
614
"""When ~/.config/bazaar exists, use it as the config dir."""
615
newdir = osutils.pathjoin(self.test_home_dir, '.config', 'bazaar')
617
self.assertEqual(config.config_dir(), newdir)
619
def test_xdg_config_home(self):
620
"""When XDG_CONFIG_HOME is set, use it."""
621
xdgconfigdir = osutils.pathjoin(self.test_home_dir, 'xdgconfig')
622
self.overrideEnv('XDG_CONFIG_HOME', xdgconfigdir)
623
newdir = osutils.pathjoin(xdgconfigdir, 'bazaar')
625
self.assertEqual(config.config_dir(), newdir)
628
class TestIniConfig(tests.TestCaseInTempDir):
630
def make_config_parser(self, s):
631
conf = config.IniBasedConfig.from_string(s)
632
return conf, conf._get_parser()
635
class TestIniConfigBuilding(TestIniConfig):
637
def test_contructs(self):
638
my_config = config.IniBasedConfig()
640
def test_from_fp(self):
641
my_config = config.IniBasedConfig.from_string(sample_config_text)
642
self.assertIsInstance(my_config._get_parser(), configobj.ConfigObj)
644
def test_cached(self):
645
my_config = config.IniBasedConfig.from_string(sample_config_text)
646
parser = my_config._get_parser()
647
self.assertTrue(my_config._get_parser() is parser)
649
def _dummy_chown(self, path, uid, gid):
650
self.path, self.uid, self.gid = path, uid, gid
652
def test_ini_config_ownership(self):
653
"""Ensure that chown is happening during _write_config_file"""
654
self.requireFeature(features.chown_feature)
655
self.overrideAttr(os, 'chown', self._dummy_chown)
656
self.path = self.uid = self.gid = None
657
conf = config.IniBasedConfig(file_name='./foo.conf')
658
conf._write_config_file()
659
self.assertEquals(self.path, './foo.conf')
660
self.assertTrue(isinstance(self.uid, int))
661
self.assertTrue(isinstance(self.gid, int))
663
def test_get_filename_parameter_is_deprecated_(self):
664
conf = self.callDeprecated([
665
'IniBasedConfig.__init__(get_filename) was deprecated in 2.3.'
666
' Use file_name instead.'],
667
config.IniBasedConfig, lambda: 'ini.conf')
668
self.assertEqual('ini.conf', conf.file_name)
670
def test_get_parser_file_parameter_is_deprecated_(self):
671
config_file = StringIO(sample_config_text.encode('utf-8'))
672
conf = config.IniBasedConfig.from_string(sample_config_text)
673
conf = self.callDeprecated([
674
'IniBasedConfig._get_parser(file=xxx) was deprecated in 2.3.'
675
' Use IniBasedConfig(_content=xxx) instead.'],
676
conf._get_parser, file=config_file)
679
class TestIniConfigSaving(tests.TestCaseInTempDir):
681
def test_cant_save_without_a_file_name(self):
682
conf = config.IniBasedConfig()
683
self.assertRaises(AssertionError, conf._write_config_file)
685
def test_saved_with_content(self):
686
content = 'foo = bar\n'
687
conf = config.IniBasedConfig.from_string(
688
content, file_name='./test.conf', save=True)
689
self.assertFileEqual(content, 'test.conf')
692
class TestIniConfigOptionExpansion(tests.TestCase):
693
"""Test option expansion from the IniConfig level.
695
What we really want here is to test the Config level, but the class being
696
abstract as far as storing values is concerned, this can't be done
699
# FIXME: This should be rewritten when all configs share a storage
700
# implementation -- vila 2011-02-18
702
def get_config(self, string=None):
705
c = config.IniBasedConfig.from_string(string)
708
def assertExpansion(self, expected, conf, string, env=None):
709
self.assertEquals(expected, conf.expand_options(string, env))
711
def test_no_expansion(self):
712
c = self.get_config('')
713
self.assertExpansion('foo', c, 'foo')
715
def test_env_adding_options(self):
716
c = self.get_config('')
717
self.assertExpansion('bar', c, '{foo}', {'foo': 'bar'})
719
def test_env_overriding_options(self):
720
c = self.get_config('foo=baz')
721
self.assertExpansion('bar', c, '{foo}', {'foo': 'bar'})
723
def test_simple_ref(self):
724
c = self.get_config('foo=xxx')
725
self.assertExpansion('xxx', c, '{foo}')
727
def test_unknown_ref(self):
728
c = self.get_config('')
729
self.assertRaises(errors.ExpandingUnknownOption,
730
c.expand_options, '{foo}')
732
def test_indirect_ref(self):
733
c = self.get_config('''
737
self.assertExpansion('xxx', c, '{bar}')
739
def test_embedded_ref(self):
740
c = self.get_config('''
744
self.assertExpansion('xxx', c, '{{bar}}')
746
def test_simple_loop(self):
747
c = self.get_config('foo={foo}')
748
self.assertRaises(errors.OptionExpansionLoop, c.expand_options, '{foo}')
750
def test_indirect_loop(self):
751
c = self.get_config('''
755
e = self.assertRaises(errors.OptionExpansionLoop,
756
c.expand_options, '{foo}')
757
self.assertEquals('foo->bar->baz', e.refs)
758
self.assertEquals('{foo}', e.string)
761
conf = self.get_config('''
765
list={foo},{bar},{baz}
767
self.assertEquals(['start', 'middle', 'end'],
768
conf.get_user_option('list', expand=True))
770
def test_cascading_list(self):
771
conf = self.get_config('''
777
self.assertEquals(['start', 'middle', 'end'],
778
conf.get_user_option('list', expand=True))
780
def test_pathological_hidden_list(self):
781
conf = self.get_config('''
787
hidden={start}{middle}{end}
789
# Nope, it's either a string or a list, and the list wins as soon as a
790
# ',' appears, so the string concatenation never occur.
791
self.assertEquals(['{foo', '}', '{', 'bar}'],
792
conf.get_user_option('hidden', expand=True))
795
class TestLocationConfigOptionExpansion(tests.TestCaseInTempDir):
797
def get_config(self, location, string=None):
800
# Since we don't save the config we won't strictly require to inherit
801
# from TestCaseInTempDir, but an error occurs so quickly...
802
c = config.LocationConfig.from_string(string, location)
805
def test_dont_cross_unrelated_section(self):
806
c = self.get_config('/another/branch/path','''
811
[/another/branch/path]
814
self.assertRaises(errors.ExpandingUnknownOption,
815
c.get_user_option, 'bar', expand=True)
817
def test_cross_related_sections(self):
818
c = self.get_config('/project/branch/path','''
822
[/project/branch/path]
825
self.assertEquals('quux', c.get_user_option('bar', expand=True))
828
class TestIniBaseConfigOnDisk(tests.TestCaseInTempDir):
830
def test_cannot_reload_without_name(self):
831
conf = config.IniBasedConfig.from_string(sample_config_text)
832
self.assertRaises(AssertionError, conf.reload)
834
def test_reload_see_new_value(self):
835
c1 = config.IniBasedConfig.from_string('editor=vim\n',
836
file_name='./test/conf')
837
c1._write_config_file()
838
c2 = config.IniBasedConfig.from_string('editor=emacs\n',
839
file_name='./test/conf')
840
c2._write_config_file()
841
self.assertEqual('vim', c1.get_user_option('editor'))
842
self.assertEqual('emacs', c2.get_user_option('editor'))
843
# Make sure we get the Right value
845
self.assertEqual('emacs', c1.get_user_option('editor'))
848
class TestLockableConfig(tests.TestCaseInTempDir):
850
scenarios = lockable_config_scenarios()
855
config_section = None
858
super(TestLockableConfig, self).setUp()
859
self._content = '[%s]\none=1\ntwo=2\n' % (self.config_section,)
860
self.config = self.create_config(self._content)
862
def get_existing_config(self):
863
return self.config_class(*self.config_args)
865
def create_config(self, content):
866
kwargs = dict(save=True)
867
c = self.config_class.from_string(content, *self.config_args, **kwargs)
870
def test_simple_read_access(self):
871
self.assertEquals('1', self.config.get_user_option('one'))
873
def test_simple_write_access(self):
874
self.config.set_user_option('one', 'one')
875
self.assertEquals('one', self.config.get_user_option('one'))
877
def test_listen_to_the_last_speaker(self):
879
c2 = self.get_existing_config()
880
c1.set_user_option('one', 'ONE')
881
c2.set_user_option('two', 'TWO')
882
self.assertEquals('ONE', c1.get_user_option('one'))
883
self.assertEquals('TWO', c2.get_user_option('two'))
884
# The second update respect the first one
885
self.assertEquals('ONE', c2.get_user_option('one'))
887
def test_last_speaker_wins(self):
888
# If the same config is not shared, the same variable modified twice
889
# can only see a single result.
891
c2 = self.get_existing_config()
892
c1.set_user_option('one', 'c1')
893
c2.set_user_option('one', 'c2')
894
self.assertEquals('c2', c2._get_user_option('one'))
895
# The first modification is still available until another refresh
897
self.assertEquals('c1', c1._get_user_option('one'))
898
c1.set_user_option('two', 'done')
899
self.assertEquals('c2', c1._get_user_option('one'))
901
def test_writes_are_serialized(self):
903
c2 = self.get_existing_config()
905
# We spawn a thread that will pause *during* the write
906
before_writing = threading.Event()
907
after_writing = threading.Event()
908
writing_done = threading.Event()
909
c1_orig = c1._write_config_file
910
def c1_write_config_file():
913
# The lock is held. We wait for the main thread to decide when to
916
c1._write_config_file = c1_write_config_file
918
c1.set_user_option('one', 'c1')
920
t1 = threading.Thread(target=c1_set_option)
921
# Collect the thread after the test
922
self.addCleanup(t1.join)
923
# Be ready to unblock the thread if the test goes wrong
924
self.addCleanup(after_writing.set)
926
before_writing.wait()
927
self.assertTrue(c1._lock.is_held)
928
self.assertRaises(errors.LockContention,
929
c2.set_user_option, 'one', 'c2')
930
self.assertEquals('c1', c1.get_user_option('one'))
931
# Let the lock be released
934
c2.set_user_option('one', 'c2')
935
self.assertEquals('c2', c2.get_user_option('one'))
937
def test_read_while_writing(self):
939
# We spawn a thread that will pause *during* the write
940
ready_to_write = threading.Event()
941
do_writing = threading.Event()
942
writing_done = threading.Event()
943
c1_orig = c1._write_config_file
944
def c1_write_config_file():
946
# The lock is held. We wait for the main thread to decide when to
951
c1._write_config_file = c1_write_config_file
953
c1.set_user_option('one', 'c1')
954
t1 = threading.Thread(target=c1_set_option)
955
# Collect the thread after the test
956
self.addCleanup(t1.join)
957
# Be ready to unblock the thread if the test goes wrong
958
self.addCleanup(do_writing.set)
960
# Ensure the thread is ready to write
961
ready_to_write.wait()
962
self.assertTrue(c1._lock.is_held)
963
self.assertEquals('c1', c1.get_user_option('one'))
964
# If we read during the write, we get the old value
965
c2 = self.get_existing_config()
966
self.assertEquals('1', c2.get_user_option('one'))
967
# Let the writing occur and ensure it occurred
970
# Now we get the updated value
971
c3 = self.get_existing_config()
972
self.assertEquals('c1', c3.get_user_option('one'))
975
class TestGetUserOptionAs(TestIniConfig):
977
def test_get_user_option_as_bool(self):
978
conf, parser = self.make_config_parser("""
981
an_invalid_bool = maybe
982
a_list = hmm, who knows ? # This is interpreted as a list !
984
get_bool = conf.get_user_option_as_bool
985
self.assertEqual(True, get_bool('a_true_bool'))
986
self.assertEqual(False, get_bool('a_false_bool'))
989
warnings.append(args[0] % args[1:])
990
self.overrideAttr(trace, 'warning', warning)
991
msg = 'Value "%s" is not a boolean for "%s"'
992
self.assertIs(None, get_bool('an_invalid_bool'))
993
self.assertEquals(msg % ('maybe', 'an_invalid_bool'), warnings[0])
995
self.assertIs(None, get_bool('not_defined_in_this_config'))
996
self.assertEquals([], warnings)
998
def test_get_user_option_as_list(self):
999
conf, parser = self.make_config_parser("""
1004
get_list = conf.get_user_option_as_list
1005
self.assertEqual(['a', 'b', 'c'], get_list('a_list'))
1006
self.assertEqual(['1'], get_list('length_1'))
1007
self.assertEqual('x', conf.get_user_option('one_item'))
1008
# automatically cast to list
1009
self.assertEqual(['x'], get_list('one_item'))
1011
def test_get_user_option_as_int_from_SI(self):
1012
conf, parser = self.make_config_parser("""
1021
def get_si(s, default=None):
1022
return self.applyDeprecated(
1023
deprecated_in((2, 5, 0)),
1024
conf.get_user_option_as_int_from_SI, s, default)
1025
self.assertEqual(100, get_si('plain'))
1026
self.assertEqual(5000, get_si('si_k'))
1027
self.assertEqual(5000, get_si('si_kb'))
1028
self.assertEqual(5000000, get_si('si_m'))
1029
self.assertEqual(5000000, get_si('si_mb'))
1030
self.assertEqual(5000000000, get_si('si_g'))
1031
self.assertEqual(5000000000, get_si('si_gb'))
1032
self.assertEqual(None, get_si('non-exist'))
1033
self.assertEqual(42, get_si('non-exist-with-default', 42))
1036
class TestSupressWarning(TestIniConfig):
1038
def make_warnings_config(self, s):
1039
conf, parser = self.make_config_parser(s)
1040
return conf.suppress_warning
1042
def test_suppress_warning_unknown(self):
1043
suppress_warning = self.make_warnings_config('')
1044
self.assertEqual(False, suppress_warning('unknown_warning'))
1046
def test_suppress_warning_known(self):
1047
suppress_warning = self.make_warnings_config('suppress_warnings=a,b')
1048
self.assertEqual(False, suppress_warning('c'))
1049
self.assertEqual(True, suppress_warning('a'))
1050
self.assertEqual(True, suppress_warning('b'))
1053
class TestGetConfig(tests.TestCase):
1055
def test_constructs(self):
1056
my_config = config.GlobalConfig()
1058
def test_calls_read_filenames(self):
1059
# replace the class that is constructed, to check its parameters
1060
oldparserclass = config.ConfigObj
1061
config.ConfigObj = InstrumentedConfigObj
1062
my_config = config.GlobalConfig()
1064
parser = my_config._get_parser()
1066
config.ConfigObj = oldparserclass
1067
self.assertIsInstance(parser, InstrumentedConfigObj)
1068
self.assertEqual(parser._calls, [('__init__', config.config_filename(),
1072
class TestBranchConfig(tests.TestCaseWithTransport):
1074
def test_constructs(self):
1075
branch = FakeBranch()
1076
my_config = config.BranchConfig(branch)
1077
self.assertRaises(TypeError, config.BranchConfig)
1079
def test_get_location_config(self):
1080
branch = FakeBranch()
1081
my_config = config.BranchConfig(branch)
1082
location_config = my_config._get_location_config()
1083
self.assertEqual(branch.base, location_config.location)
1084
self.assertIs(location_config, my_config._get_location_config())
1086
def test_get_config(self):
1087
"""The Branch.get_config method works properly"""
1088
b = controldir.ControlDir.create_standalone_workingtree('.').branch
1089
my_config = b.get_config()
1090
self.assertIs(my_config.get_user_option('wacky'), None)
1091
my_config.set_user_option('wacky', 'unlikely')
1092
self.assertEqual(my_config.get_user_option('wacky'), 'unlikely')
1094
# Ensure we get the same thing if we start again
1095
b2 = branch.Branch.open('.')
1096
my_config2 = b2.get_config()
1097
self.assertEqual(my_config2.get_user_option('wacky'), 'unlikely')
1099
def test_has_explicit_nickname(self):
1100
b = self.make_branch('.')
1101
self.assertFalse(b.get_config().has_explicit_nickname())
1103
self.assertTrue(b.get_config().has_explicit_nickname())
1105
def test_config_url(self):
1106
"""The Branch.get_config will use section that uses a local url"""
1107
branch = self.make_branch('branch')
1108
self.assertEqual('branch', branch.nick)
1110
local_url = urlutils.local_path_to_url('branch')
1111
conf = config.LocationConfig.from_string(
1112
'[%s]\nnickname = foobar' % (local_url,),
1113
local_url, save=True)
1114
self.assertEqual('foobar', branch.nick)
1116
def test_config_local_path(self):
1117
"""The Branch.get_config will use a local system path"""
1118
branch = self.make_branch('branch')
1119
self.assertEqual('branch', branch.nick)
1121
local_path = osutils.getcwd().encode('utf8')
1122
conf = config.LocationConfig.from_string(
1123
'[%s/branch]\nnickname = barry' % (local_path,),
1124
'branch', save=True)
1125
self.assertEqual('barry', branch.nick)
1127
def test_config_creates_local(self):
1128
"""Creating a new entry in config uses a local path."""
1129
branch = self.make_branch('branch', format='knit')
1130
branch.set_push_location('http://foobar')
1131
local_path = osutils.getcwd().encode('utf8')
1132
# Surprisingly ConfigObj doesn't create a trailing newline
1133
self.check_file_contents(config.locations_config_filename(),
1135
'push_location = http://foobar\n'
1136
'push_location:policy = norecurse\n'
1139
def test_autonick_urlencoded(self):
1140
b = self.make_branch('!repo')
1141
self.assertEqual('!repo', b.get_config().get_nickname())
1143
def test_autonick_uses_branch_name(self):
1144
b = self.make_branch('foo', name='bar')
1145
self.assertEqual('bar', b.get_config().get_nickname())
1147
def test_warn_if_masked(self):
1150
warnings.append(args[0] % args[1:])
1151
self.overrideAttr(trace, 'warning', warning)
1153
def set_option(store, warn_masked=True):
1155
conf.set_user_option('example_option', repr(store), store=store,
1156
warn_masked=warn_masked)
1157
def assertWarning(warning):
1159
self.assertEqual(0, len(warnings))
1161
self.assertEqual(1, len(warnings))
1162
self.assertEqual(warning, warnings[0])
1163
branch = self.make_branch('.')
1164
conf = branch.get_config()
1165
set_option(config.STORE_GLOBAL)
1167
set_option(config.STORE_BRANCH)
1169
set_option(config.STORE_GLOBAL)
1170
assertWarning('Value "4" is masked by "3" from branch.conf')
1171
set_option(config.STORE_GLOBAL, warn_masked=False)
1173
set_option(config.STORE_LOCATION)
1175
set_option(config.STORE_BRANCH)
1176
assertWarning('Value "3" is masked by "0" from locations.conf')
1177
set_option(config.STORE_BRANCH, warn_masked=False)
1181
class TestGlobalConfigItems(tests.TestCaseInTempDir):
1183
def test_user_id(self):
1184
my_config = config.GlobalConfig.from_string(sample_config_text)
1185
self.assertEqual(u"Erik B\u00e5gfors <erik@bagfors.nu>",
1186
my_config._get_user_id())
1188
def test_absent_user_id(self):
1189
my_config = config.GlobalConfig()
1190
self.assertEqual(None, my_config._get_user_id())
1192
def test_configured_editor(self):
1193
my_config = config.GlobalConfig.from_string(sample_config_text)
1194
editor = self.applyDeprecated(
1195
deprecated_in((2, 4, 0)), my_config.get_editor)
1196
self.assertEqual('vim', editor)
1198
def test_signatures_always(self):
1199
my_config = config.GlobalConfig.from_string(sample_always_signatures)
1200
self.assertEqual(config.CHECK_NEVER,
1201
self.applyDeprecated(deprecated_in((2, 5, 0)),
1202
my_config.signature_checking))
1203
self.assertEqual(config.SIGN_ALWAYS,
1204
self.applyDeprecated(deprecated_in((2, 5, 0)),
1205
my_config.signing_policy))
1206
self.assertEqual(True,
1207
self.applyDeprecated(deprecated_in((2, 5, 0)),
1208
my_config.signature_needed))
1210
def test_signatures_if_possible(self):
1211
my_config = config.GlobalConfig.from_string(sample_maybe_signatures)
1212
self.assertEqual(config.CHECK_NEVER,
1213
self.applyDeprecated(deprecated_in((2, 5, 0)),
1214
my_config.signature_checking))
1215
self.assertEqual(config.SIGN_WHEN_REQUIRED,
1216
self.applyDeprecated(deprecated_in((2, 5, 0)),
1217
my_config.signing_policy))
1218
self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
1219
my_config.signature_needed))
1221
def test_signatures_ignore(self):
1222
my_config = config.GlobalConfig.from_string(sample_ignore_signatures)
1223
self.assertEqual(config.CHECK_ALWAYS,
1224
self.applyDeprecated(deprecated_in((2, 5, 0)),
1225
my_config.signature_checking))
1226
self.assertEqual(config.SIGN_NEVER,
1227
self.applyDeprecated(deprecated_in((2, 5, 0)),
1228
my_config.signing_policy))
1229
self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
1230
my_config.signature_needed))
1232
def _get_sample_config(self):
1233
my_config = config.GlobalConfig.from_string(sample_config_text)
1236
def test_gpg_signing_command(self):
1237
my_config = self._get_sample_config()
1238
self.assertEqual("gnome-gpg",
1239
self.applyDeprecated(
1240
deprecated_in((2, 5, 0)), my_config.gpg_signing_command))
1241
self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
1242
my_config.signature_needed))
1244
def test_gpg_signing_key(self):
1245
my_config = self._get_sample_config()
1246
self.assertEqual("DD4D5088",
1247
self.applyDeprecated(deprecated_in((2, 5, 0)),
1248
my_config.gpg_signing_key))
1250
def _get_empty_config(self):
1251
my_config = config.GlobalConfig()
1254
def test_gpg_signing_command_unset(self):
1255
my_config = self._get_empty_config()
1256
self.assertEqual("gpg",
1257
self.applyDeprecated(
1258
deprecated_in((2, 5, 0)), my_config.gpg_signing_command))
1260
def test_get_user_option_default(self):
1261
my_config = self._get_empty_config()
1262
self.assertEqual(None, my_config.get_user_option('no_option'))
1264
def test_get_user_option_global(self):
1265
my_config = self._get_sample_config()
1266
self.assertEqual("something",
1267
my_config.get_user_option('user_global_option'))
1269
def test_post_commit_default(self):
1270
my_config = self._get_sample_config()
1271
self.assertEqual(None,
1272
self.applyDeprecated(deprecated_in((2, 5, 0)),
1273
my_config.post_commit))
1275
def test_configured_logformat(self):
1276
my_config = self._get_sample_config()
1277
self.assertEqual("short",
1278
self.applyDeprecated(deprecated_in((2, 5, 0)),
1279
my_config.log_format))
1281
def test_configured_acceptable_keys(self):
1282
my_config = self._get_sample_config()
1283
self.assertEqual("amy",
1284
self.applyDeprecated(deprecated_in((2, 5, 0)),
1285
my_config.acceptable_keys))
1287
def test_configured_validate_signatures_in_log(self):
1288
my_config = self._get_sample_config()
1289
self.assertEqual(True, my_config.validate_signatures_in_log())
1291
def test_get_alias(self):
1292
my_config = self._get_sample_config()
1293
self.assertEqual('help', my_config.get_alias('h'))
1295
def test_get_aliases(self):
1296
my_config = self._get_sample_config()
1297
aliases = my_config.get_aliases()
1298
self.assertEqual(2, len(aliases))
1299
sorted_keys = sorted(aliases)
1300
self.assertEqual('help', aliases[sorted_keys[0]])
1301
self.assertEqual(sample_long_alias, aliases[sorted_keys[1]])
1303
def test_get_no_alias(self):
1304
my_config = self._get_sample_config()
1305
self.assertEqual(None, my_config.get_alias('foo'))
1307
def test_get_long_alias(self):
1308
my_config = self._get_sample_config()
1309
self.assertEqual(sample_long_alias, my_config.get_alias('ll'))
1311
def test_get_change_editor(self):
1312
my_config = self._get_sample_config()
1313
change_editor = my_config.get_change_editor('old', 'new')
1314
self.assertIs(diff.DiffFromTool, change_editor.__class__)
1315
self.assertEqual('vimdiff -of @new_path @old_path',
1316
' '.join(change_editor.command_template))
1318
def test_get_no_change_editor(self):
1319
my_config = self._get_empty_config()
1320
change_editor = my_config.get_change_editor('old', 'new')
1321
self.assertIs(None, change_editor)
1323
def test_get_merge_tools(self):
1324
conf = self._get_sample_config()
1325
tools = conf.get_merge_tools()
1326
self.log(repr(tools))
1328
{u'funkytool' : u'funkytool "arg with spaces" {this_temp}',
1329
u'sometool' : u'sometool {base} {this} {other} -o {result}',
1330
u'newtool' : u'"newtool with spaces" {this_temp}'},
1333
def test_get_merge_tools_empty(self):
1334
conf = self._get_empty_config()
1335
tools = conf.get_merge_tools()
1336
self.assertEqual({}, tools)
1338
def test_find_merge_tool(self):
1339
conf = self._get_sample_config()
1340
cmdline = conf.find_merge_tool('sometool')
1341
self.assertEqual('sometool {base} {this} {other} -o {result}', cmdline)
1343
def test_find_merge_tool_not_found(self):
1344
conf = self._get_sample_config()
1345
cmdline = conf.find_merge_tool('DOES NOT EXIST')
1346
self.assertIs(cmdline, None)
1348
def test_find_merge_tool_known(self):
1349
conf = self._get_empty_config()
1350
cmdline = conf.find_merge_tool('kdiff3')
1351
self.assertEquals('kdiff3 {base} {this} {other} -o {result}', cmdline)
1353
def test_find_merge_tool_override_known(self):
1354
conf = self._get_empty_config()
1355
conf.set_user_option('bzr.mergetool.kdiff3', 'kdiff3 blah')
1356
cmdline = conf.find_merge_tool('kdiff3')
1357
self.assertEqual('kdiff3 blah', cmdline)
1360
class TestGlobalConfigSavingOptions(tests.TestCaseInTempDir):
1362
def test_empty(self):
1363
my_config = config.GlobalConfig()
1364
self.assertEqual(0, len(my_config.get_aliases()))
1366
def test_set_alias(self):
1367
my_config = config.GlobalConfig()
1368
alias_value = 'commit --strict'
1369
my_config.set_alias('commit', alias_value)
1370
new_config = config.GlobalConfig()
1371
self.assertEqual(alias_value, new_config.get_alias('commit'))
1373
def test_remove_alias(self):
1374
my_config = config.GlobalConfig()
1375
my_config.set_alias('commit', 'commit --strict')
1376
# Now remove the alias again.
1377
my_config.unset_alias('commit')
1378
new_config = config.GlobalConfig()
1379
self.assertIs(None, new_config.get_alias('commit'))
1382
class TestLocationConfig(tests.TestCaseInTempDir, TestOptionsMixin):
1384
def test_constructs(self):
1385
my_config = config.LocationConfig('http://example.com')
1386
self.assertRaises(TypeError, config.LocationConfig)
1388
def test_branch_calls_read_filenames(self):
1389
# This is testing the correct file names are provided.
1390
# TODO: consolidate with the test for GlobalConfigs filename checks.
1392
# replace the class that is constructed, to check its parameters
1393
oldparserclass = config.ConfigObj
1394
config.ConfigObj = InstrumentedConfigObj
1396
my_config = config.LocationConfig('http://www.example.com')
1397
parser = my_config._get_parser()
1399
config.ConfigObj = oldparserclass
1400
self.assertIsInstance(parser, InstrumentedConfigObj)
1401
self.assertEqual(parser._calls,
1402
[('__init__', config.locations_config_filename(),
1405
def test_get_global_config(self):
1406
my_config = config.BranchConfig(FakeBranch('http://example.com'))
1407
global_config = my_config._get_global_config()
1408
self.assertIsInstance(global_config, config.GlobalConfig)
1409
self.assertIs(global_config, my_config._get_global_config())
1411
def assertLocationMatching(self, expected):
1412
self.assertEqual(expected,
1413
list(self.my_location_config._get_matching_sections()))
1415
def test__get_matching_sections_no_match(self):
1416
self.get_branch_config('/')
1417
self.assertLocationMatching([])
1419
def test__get_matching_sections_exact(self):
1420
self.get_branch_config('http://www.example.com')
1421
self.assertLocationMatching([('http://www.example.com', '')])
1423
def test__get_matching_sections_suffix_does_not(self):
1424
self.get_branch_config('http://www.example.com-com')
1425
self.assertLocationMatching([])
1427
def test__get_matching_sections_subdir_recursive(self):
1428
self.get_branch_config('http://www.example.com/com')
1429
self.assertLocationMatching([('http://www.example.com', 'com')])
1431
def test__get_matching_sections_ignoreparent(self):
1432
self.get_branch_config('http://www.example.com/ignoreparent')
1433
self.assertLocationMatching([('http://www.example.com/ignoreparent',
1436
def test__get_matching_sections_ignoreparent_subdir(self):
1437
self.get_branch_config(
1438
'http://www.example.com/ignoreparent/childbranch')
1439
self.assertLocationMatching([('http://www.example.com/ignoreparent',
1442
def test__get_matching_sections_subdir_trailing_slash(self):
1443
self.get_branch_config('/b')
1444
self.assertLocationMatching([('/b/', '')])
1446
def test__get_matching_sections_subdir_child(self):
1447
self.get_branch_config('/a/foo')
1448
self.assertLocationMatching([('/a/*', ''), ('/a/', 'foo')])
1450
def test__get_matching_sections_subdir_child_child(self):
1451
self.get_branch_config('/a/foo/bar')
1452
self.assertLocationMatching([('/a/*', 'bar'), ('/a/', 'foo/bar')])
1454
def test__get_matching_sections_trailing_slash_with_children(self):
1455
self.get_branch_config('/a/')
1456
self.assertLocationMatching([('/a/', '')])
1458
def test__get_matching_sections_explicit_over_glob(self):
1459
# XXX: 2006-09-08 jamesh
1460
# This test only passes because ord('c') > ord('*'). If there
1461
# was a config section for '/a/?', it would get precedence
1463
self.get_branch_config('/a/c')
1464
self.assertLocationMatching([('/a/c', ''), ('/a/*', ''), ('/a/', 'c')])
1466
def test__get_option_policy_normal(self):
1467
self.get_branch_config('http://www.example.com')
1469
self.my_location_config._get_config_policy(
1470
'http://www.example.com', 'normal_option'),
1473
def test__get_option_policy_norecurse(self):
1474
self.get_branch_config('http://www.example.com')
1476
self.my_location_config._get_option_policy(
1477
'http://www.example.com', 'norecurse_option'),
1478
config.POLICY_NORECURSE)
1479
# Test old recurse=False setting:
1481
self.my_location_config._get_option_policy(
1482
'http://www.example.com/norecurse', 'normal_option'),
1483
config.POLICY_NORECURSE)
1485
def test__get_option_policy_normal(self):
1486
self.get_branch_config('http://www.example.com')
1488
self.my_location_config._get_option_policy(
1489
'http://www.example.com', 'appendpath_option'),
1490
config.POLICY_APPENDPATH)
1492
def test__get_options_with_policy(self):
1493
self.get_branch_config('/dir/subdir',
1494
location_config="""\
1496
other_url = /other-dir
1497
other_url:policy = appendpath
1499
other_url = /other-subdir
1502
[(u'other_url', u'/other-subdir', u'/dir/subdir', 'locations'),
1503
(u'other_url', u'/other-dir', u'/dir', 'locations'),
1504
(u'other_url:policy', u'appendpath', u'/dir', 'locations')],
1505
self.my_location_config)
1507
def test_location_without_username(self):
1508
self.get_branch_config('http://www.example.com/ignoreparent')
1509
self.assertEqual(u'Erik B\u00e5gfors <erik@bagfors.nu>',
1510
self.my_config.username())
1512
def test_location_not_listed(self):
1513
"""Test that the global username is used when no location matches"""
1514
self.get_branch_config('/home/robertc/sources')
1515
self.assertEqual(u'Erik B\u00e5gfors <erik@bagfors.nu>',
1516
self.my_config.username())
1518
def test_overriding_location(self):
1519
self.get_branch_config('http://www.example.com/foo')
1520
self.assertEqual('Robert Collins <robertc@example.org>',
1521
self.my_config.username())
1523
def test_signatures_not_set(self):
1524
self.get_branch_config('http://www.example.com',
1525
global_config=sample_ignore_signatures)
1526
self.assertEqual(config.CHECK_ALWAYS,
1527
self.applyDeprecated(deprecated_in((2, 5, 0)),
1528
self.my_config.signature_checking))
1529
self.assertEqual(config.SIGN_NEVER,
1530
self.applyDeprecated(deprecated_in((2, 5, 0)),
1531
self.my_config.signing_policy))
1533
def test_signatures_never(self):
1534
self.get_branch_config('/a/c')
1535
self.assertEqual(config.CHECK_NEVER,
1536
self.applyDeprecated(deprecated_in((2, 5, 0)),
1537
self.my_config.signature_checking))
1539
def test_signatures_when_available(self):
1540
self.get_branch_config('/a/', global_config=sample_ignore_signatures)
1541
self.assertEqual(config.CHECK_IF_POSSIBLE,
1542
self.applyDeprecated(deprecated_in((2, 5, 0)),
1543
self.my_config.signature_checking))
1545
def test_signatures_always(self):
1546
self.get_branch_config('/b')
1547
self.assertEqual(config.CHECK_ALWAYS,
1548
self.applyDeprecated(deprecated_in((2, 5, 0)),
1549
self.my_config.signature_checking))
1551
def test_gpg_signing_command(self):
1552
self.get_branch_config('/b')
1553
self.assertEqual("gnome-gpg",
1554
self.applyDeprecated(deprecated_in((2, 5, 0)),
1555
self.my_config.gpg_signing_command))
1557
def test_gpg_signing_command_missing(self):
1558
self.get_branch_config('/a')
1559
self.assertEqual("false",
1560
self.applyDeprecated(deprecated_in((2, 5, 0)),
1561
self.my_config.gpg_signing_command))
1563
def test_gpg_signing_key(self):
1564
self.get_branch_config('/b')
1565
self.assertEqual("DD4D5088", self.applyDeprecated(deprecated_in((2, 5, 0)),
1566
self.my_config.gpg_signing_key))
1568
def test_gpg_signing_key_default(self):
1569
self.get_branch_config('/a')
1570
self.assertEqual("erik@bagfors.nu",
1571
self.applyDeprecated(deprecated_in((2, 5, 0)),
1572
self.my_config.gpg_signing_key))
1574
def test_get_user_option_global(self):
1575
self.get_branch_config('/a')
1576
self.assertEqual('something',
1577
self.my_config.get_user_option('user_global_option'))
1579
def test_get_user_option_local(self):
1580
self.get_branch_config('/a')
1581
self.assertEqual('local',
1582
self.my_config.get_user_option('user_local_option'))
1584
def test_get_user_option_appendpath(self):
1585
# returned as is for the base path:
1586
self.get_branch_config('http://www.example.com')
1587
self.assertEqual('append',
1588
self.my_config.get_user_option('appendpath_option'))
1589
# Extra path components get appended:
1590
self.get_branch_config('http://www.example.com/a/b/c')
1591
self.assertEqual('append/a/b/c',
1592
self.my_config.get_user_option('appendpath_option'))
1593
# Overriden for http://www.example.com/dir, where it is a
1595
self.get_branch_config('http://www.example.com/dir/a/b/c')
1596
self.assertEqual('normal',
1597
self.my_config.get_user_option('appendpath_option'))
1599
def test_get_user_option_norecurse(self):
1600
self.get_branch_config('http://www.example.com')
1601
self.assertEqual('norecurse',
1602
self.my_config.get_user_option('norecurse_option'))
1603
self.get_branch_config('http://www.example.com/dir')
1604
self.assertEqual(None,
1605
self.my_config.get_user_option('norecurse_option'))
1606
# http://www.example.com/norecurse is a recurse=False section
1607
# that redefines normal_option. Subdirectories do not pick up
1608
# this redefinition.
1609
self.get_branch_config('http://www.example.com/norecurse')
1610
self.assertEqual('norecurse',
1611
self.my_config.get_user_option('normal_option'))
1612
self.get_branch_config('http://www.example.com/norecurse/subdir')
1613
self.assertEqual('normal',
1614
self.my_config.get_user_option('normal_option'))
1616
def test_set_user_option_norecurse(self):
1617
self.get_branch_config('http://www.example.com')
1618
self.my_config.set_user_option('foo', 'bar',
1619
store=config.STORE_LOCATION_NORECURSE)
1621
self.my_location_config._get_option_policy(
1622
'http://www.example.com', 'foo'),
1623
config.POLICY_NORECURSE)
1625
def test_set_user_option_appendpath(self):
1626
self.get_branch_config('http://www.example.com')
1627
self.my_config.set_user_option('foo', 'bar',
1628
store=config.STORE_LOCATION_APPENDPATH)
1630
self.my_location_config._get_option_policy(
1631
'http://www.example.com', 'foo'),
1632
config.POLICY_APPENDPATH)
1634
def test_set_user_option_change_policy(self):
1635
self.get_branch_config('http://www.example.com')
1636
self.my_config.set_user_option('norecurse_option', 'normal',
1637
store=config.STORE_LOCATION)
1639
self.my_location_config._get_option_policy(
1640
'http://www.example.com', 'norecurse_option'),
1643
def test_set_user_option_recurse_false_section(self):
1644
# The following section has recurse=False set. The test is to
1645
# make sure that a normal option can be added to the section,
1646
# converting recurse=False to the norecurse policy.
1647
self.get_branch_config('http://www.example.com/norecurse')
1648
self.callDeprecated(['The recurse option is deprecated as of 0.14. '
1649
'The section "http://www.example.com/norecurse" '
1650
'has been converted to use policies.'],
1651
self.my_config.set_user_option,
1652
'foo', 'bar', store=config.STORE_LOCATION)
1654
self.my_location_config._get_option_policy(
1655
'http://www.example.com/norecurse', 'foo'),
1657
# The previously existing option is still norecurse:
1659
self.my_location_config._get_option_policy(
1660
'http://www.example.com/norecurse', 'normal_option'),
1661
config.POLICY_NORECURSE)
1663
def test_post_commit_default(self):
1664
self.get_branch_config('/a/c')
1665
self.assertEqual('bzrlib.tests.test_config.post_commit',
1666
self.applyDeprecated(deprecated_in((2, 5, 0)),
1667
self.my_config.post_commit))
1669
def get_branch_config(self, location, global_config=None,
1670
location_config=None):
1671
my_branch = FakeBranch(location)
1672
if global_config is None:
1673
global_config = sample_config_text
1674
if location_config is None:
1675
location_config = sample_branches_text
1677
my_global_config = config.GlobalConfig.from_string(global_config,
1679
my_location_config = config.LocationConfig.from_string(
1680
location_config, my_branch.base, save=True)
1681
my_config = config.BranchConfig(my_branch)
1682
self.my_config = my_config
1683
self.my_location_config = my_config._get_location_config()
1685
def test_set_user_setting_sets_and_saves(self):
1686
self.get_branch_config('/a/c')
1687
record = InstrumentedConfigObj("foo")
1688
self.my_location_config._parser = record
1690
self.callDeprecated(['The recurse option is deprecated as of '
1691
'0.14. The section "/a/c" has been '
1692
'converted to use policies.'],
1693
self.my_config.set_user_option,
1694
'foo', 'bar', store=config.STORE_LOCATION)
1695
self.assertEqual([('reload',),
1696
('__contains__', '/a/c'),
1697
('__contains__', '/a/c/'),
1698
('__setitem__', '/a/c', {}),
1699
('__getitem__', '/a/c'),
1700
('__setitem__', 'foo', 'bar'),
1701
('__getitem__', '/a/c'),
1702
('as_bool', 'recurse'),
1703
('__getitem__', '/a/c'),
1704
('__delitem__', 'recurse'),
1705
('__getitem__', '/a/c'),
1707
('__getitem__', '/a/c'),
1708
('__contains__', 'foo:policy'),
1712
def test_set_user_setting_sets_and_saves2(self):
1713
self.get_branch_config('/a/c')
1714
self.assertIs(self.my_config.get_user_option('foo'), None)
1715
self.my_config.set_user_option('foo', 'bar')
1717
self.my_config.branch.control_files.files['branch.conf'].strip(),
1719
self.assertEqual(self.my_config.get_user_option('foo'), 'bar')
1720
self.my_config.set_user_option('foo', 'baz',
1721
store=config.STORE_LOCATION)
1722
self.assertEqual(self.my_config.get_user_option('foo'), 'baz')
1723
self.my_config.set_user_option('foo', 'qux')
1724
self.assertEqual(self.my_config.get_user_option('foo'), 'baz')
1726
def test_get_bzr_remote_path(self):
1727
my_config = config.LocationConfig('/a/c')
1728
self.assertEqual('bzr', my_config.get_bzr_remote_path())
1729
my_config.set_user_option('bzr_remote_path', '/path-bzr')
1730
self.assertEqual('/path-bzr', my_config.get_bzr_remote_path())
1731
self.overrideEnv('BZR_REMOTE_PATH', '/environ-bzr')
1732
self.assertEqual('/environ-bzr', my_config.get_bzr_remote_path())
1735
precedence_global = 'option = global'
1736
precedence_branch = 'option = branch'
1737
precedence_location = """
1741
[http://example.com/specific]
1745
class TestBranchConfigItems(tests.TestCaseInTempDir):
1747
def get_branch_config(self, global_config=None, location=None,
1748
location_config=None, branch_data_config=None):
1749
my_branch = FakeBranch(location)
1750
if global_config is not None:
1751
my_global_config = config.GlobalConfig.from_string(global_config,
1753
if location_config is not None:
1754
my_location_config = config.LocationConfig.from_string(
1755
location_config, my_branch.base, save=True)
1756
my_config = config.BranchConfig(my_branch)
1757
if branch_data_config is not None:
1758
my_config.branch.control_files.files['branch.conf'] = \
1762
def test_user_id(self):
1763
branch = FakeBranch()
1764
my_config = config.BranchConfig(branch)
1765
self.assertIsNot(None, my_config.username())
1766
my_config.branch.control_files.files['email'] = "John"
1767
my_config.set_user_option('email',
1768
"Robert Collins <robertc@example.org>")
1769
self.assertEqual("Robert Collins <robertc@example.org>",
1770
my_config.username())
1772
def test_BZR_EMAIL_OVERRIDES(self):
1773
self.overrideEnv('BZR_EMAIL', "Robert Collins <robertc@example.org>")
1774
branch = FakeBranch()
1775
my_config = config.BranchConfig(branch)
1776
self.assertEqual("Robert Collins <robertc@example.org>",
1777
my_config.username())
1779
def test_signatures_forced(self):
1780
my_config = self.get_branch_config(
1781
global_config=sample_always_signatures)
1782
self.assertEqual(config.CHECK_NEVER,
1783
self.applyDeprecated(deprecated_in((2, 5, 0)),
1784
my_config.signature_checking))
1785
self.assertEqual(config.SIGN_ALWAYS,
1786
self.applyDeprecated(deprecated_in((2, 5, 0)),
1787
my_config.signing_policy))
1788
self.assertTrue(self.applyDeprecated(deprecated_in((2, 5, 0)),
1789
my_config.signature_needed))
1791
def test_signatures_forced_branch(self):
1792
my_config = self.get_branch_config(
1793
global_config=sample_ignore_signatures,
1794
branch_data_config=sample_always_signatures)
1795
self.assertEqual(config.CHECK_NEVER,
1796
self.applyDeprecated(deprecated_in((2, 5, 0)),
1797
my_config.signature_checking))
1798
self.assertEqual(config.SIGN_ALWAYS,
1799
self.applyDeprecated(deprecated_in((2, 5, 0)),
1800
my_config.signing_policy))
1801
self.assertTrue(self.applyDeprecated(deprecated_in((2, 5, 0)),
1802
my_config.signature_needed))
1804
def test_gpg_signing_command(self):
1805
my_config = self.get_branch_config(
1806
global_config=sample_config_text,
1807
# branch data cannot set gpg_signing_command
1808
branch_data_config="gpg_signing_command=pgp")
1809
self.assertEqual('gnome-gpg',
1810
self.applyDeprecated(deprecated_in((2, 5, 0)),
1811
my_config.gpg_signing_command))
1813
def test_get_user_option_global(self):
1814
my_config = self.get_branch_config(global_config=sample_config_text)
1815
self.assertEqual('something',
1816
my_config.get_user_option('user_global_option'))
1818
def test_post_commit_default(self):
1819
my_config = self.get_branch_config(global_config=sample_config_text,
1821
location_config=sample_branches_text)
1822
self.assertEqual(my_config.branch.base, '/a/c')
1823
self.assertEqual('bzrlib.tests.test_config.post_commit',
1824
self.applyDeprecated(deprecated_in((2, 5, 0)),
1825
my_config.post_commit))
1826
my_config.set_user_option('post_commit', 'rmtree_root')
1827
# post-commit is ignored when present in branch data
1828
self.assertEqual('bzrlib.tests.test_config.post_commit',
1829
self.applyDeprecated(deprecated_in((2, 5, 0)),
1830
my_config.post_commit))
1831
my_config.set_user_option('post_commit', 'rmtree_root',
1832
store=config.STORE_LOCATION)
1833
self.assertEqual('rmtree_root',
1834
self.applyDeprecated(deprecated_in((2, 5, 0)),
1835
my_config.post_commit))
1837
def test_config_precedence(self):
1838
# FIXME: eager test, luckily no persitent config file makes it fail
1840
my_config = self.get_branch_config(global_config=precedence_global)
1841
self.assertEqual(my_config.get_user_option('option'), 'global')
1842
my_config = self.get_branch_config(global_config=precedence_global,
1843
branch_data_config=precedence_branch)
1844
self.assertEqual(my_config.get_user_option('option'), 'branch')
1845
my_config = self.get_branch_config(
1846
global_config=precedence_global,
1847
branch_data_config=precedence_branch,
1848
location_config=precedence_location)
1849
self.assertEqual(my_config.get_user_option('option'), 'recurse')
1850
my_config = self.get_branch_config(
1851
global_config=precedence_global,
1852
branch_data_config=precedence_branch,
1853
location_config=precedence_location,
1854
location='http://example.com/specific')
1855
self.assertEqual(my_config.get_user_option('option'), 'exact')
1858
class TestMailAddressExtraction(tests.TestCase):
1860
def test_extract_email_address(self):
1861
self.assertEqual('jane@test.com',
1862
config.extract_email_address('Jane <jane@test.com>'))
1863
self.assertRaises(errors.NoEmailInUsername,
1864
config.extract_email_address, 'Jane Tester')
1866
def test_parse_username(self):
1867
self.assertEqual(('', 'jdoe@example.com'),
1868
config.parse_username('jdoe@example.com'))
1869
self.assertEqual(('', 'jdoe@example.com'),
1870
config.parse_username('<jdoe@example.com>'))
1871
self.assertEqual(('John Doe', 'jdoe@example.com'),
1872
config.parse_username('John Doe <jdoe@example.com>'))
1873
self.assertEqual(('John Doe', ''),
1874
config.parse_username('John Doe'))
1875
self.assertEqual(('John Doe', 'jdoe@example.com'),
1876
config.parse_username('John Doe jdoe@example.com'))
1878
class TestTreeConfig(tests.TestCaseWithTransport):
1880
def test_get_value(self):
1881
"""Test that retreiving a value from a section is possible"""
1882
branch = self.make_branch('.')
1883
tree_config = config.TreeConfig(branch)
1884
tree_config.set_option('value', 'key', 'SECTION')
1885
tree_config.set_option('value2', 'key2')
1886
tree_config.set_option('value3-top', 'key3')
1887
tree_config.set_option('value3-section', 'key3', 'SECTION')
1888
value = tree_config.get_option('key', 'SECTION')
1889
self.assertEqual(value, 'value')
1890
value = tree_config.get_option('key2')
1891
self.assertEqual(value, 'value2')
1892
self.assertEqual(tree_config.get_option('non-existant'), None)
1893
value = tree_config.get_option('non-existant', 'SECTION')
1894
self.assertEqual(value, None)
1895
value = tree_config.get_option('non-existant', default='default')
1896
self.assertEqual(value, 'default')
1897
self.assertEqual(tree_config.get_option('key2', 'NOSECTION'), None)
1898
value = tree_config.get_option('key2', 'NOSECTION', default='default')
1899
self.assertEqual(value, 'default')
1900
value = tree_config.get_option('key3')
1901
self.assertEqual(value, 'value3-top')
1902
value = tree_config.get_option('key3', 'SECTION')
1903
self.assertEqual(value, 'value3-section')
1906
class TestTransportConfig(tests.TestCaseWithTransport):
1908
def test_load_utf8(self):
1909
"""Ensure we can load an utf8-encoded file."""
1910
t = self.get_transport()
1911
unicode_user = u'b\N{Euro Sign}ar'
1912
unicode_content = u'user=%s' % (unicode_user,)
1913
utf8_content = unicode_content.encode('utf8')
1914
# Store the raw content in the config file
1915
t.put_bytes('foo.conf', utf8_content)
1916
conf = config.TransportConfig(t, 'foo.conf')
1917
self.assertEquals(unicode_user, conf.get_option('user'))
1919
def test_load_non_ascii(self):
1920
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
1921
t = self.get_transport()
1922
t.put_bytes('foo.conf', 'user=foo\n#\xff\n')
1923
conf = config.TransportConfig(t, 'foo.conf')
1924
self.assertRaises(errors.ConfigContentError, conf._get_configobj)
1926
def test_load_erroneous_content(self):
1927
"""Ensure we display a proper error on content that can't be parsed."""
1928
t = self.get_transport()
1929
t.put_bytes('foo.conf', '[open_section\n')
1930
conf = config.TransportConfig(t, 'foo.conf')
1931
self.assertRaises(errors.ParseConfigError, conf._get_configobj)
1933
def test_load_permission_denied(self):
1934
"""Ensure we get an empty config file if the file is inaccessible."""
1937
warnings.append(args[0] % args[1:])
1938
self.overrideAttr(trace, 'warning', warning)
1940
class DenyingTransport(object):
1942
def __init__(self, base):
1945
def get_bytes(self, relpath):
1946
raise errors.PermissionDenied(relpath, "")
1948
cfg = config.TransportConfig(
1949
DenyingTransport("nonexisting://"), 'control.conf')
1950
self.assertIs(None, cfg.get_option('non-existant', 'SECTION'))
1953
[u'Permission denied while trying to open configuration file '
1954
u'nonexisting:///control.conf.'])
1956
def test_get_value(self):
1957
"""Test that retreiving a value from a section is possible"""
1958
bzrdir_config = config.TransportConfig(self.get_transport('.'),
1960
bzrdir_config.set_option('value', 'key', 'SECTION')
1961
bzrdir_config.set_option('value2', 'key2')
1962
bzrdir_config.set_option('value3-top', 'key3')
1963
bzrdir_config.set_option('value3-section', 'key3', 'SECTION')
1964
value = bzrdir_config.get_option('key', 'SECTION')
1965
self.assertEqual(value, 'value')
1966
value = bzrdir_config.get_option('key2')
1967
self.assertEqual(value, 'value2')
1968
self.assertEqual(bzrdir_config.get_option('non-existant'), None)
1969
value = bzrdir_config.get_option('non-existant', 'SECTION')
1970
self.assertEqual(value, None)
1971
value = bzrdir_config.get_option('non-existant', default='default')
1972
self.assertEqual(value, 'default')
1973
self.assertEqual(bzrdir_config.get_option('key2', 'NOSECTION'), None)
1974
value = bzrdir_config.get_option('key2', 'NOSECTION',
1976
self.assertEqual(value, 'default')
1977
value = bzrdir_config.get_option('key3')
1978
self.assertEqual(value, 'value3-top')
1979
value = bzrdir_config.get_option('key3', 'SECTION')
1980
self.assertEqual(value, 'value3-section')
1982
def test_set_unset_default_stack_on(self):
1983
my_dir = self.make_bzrdir('.')
1984
bzrdir_config = config.BzrDirConfig(my_dir)
1985
self.assertIs(None, bzrdir_config.get_default_stack_on())
1986
bzrdir_config.set_default_stack_on('Foo')
1987
self.assertEqual('Foo', bzrdir_config._config.get_option(
1988
'default_stack_on'))
1989
self.assertEqual('Foo', bzrdir_config.get_default_stack_on())
1990
bzrdir_config.set_default_stack_on(None)
1991
self.assertIs(None, bzrdir_config.get_default_stack_on())
1994
class TestOldConfigHooks(tests.TestCaseWithTransport):
1997
super(TestOldConfigHooks, self).setUp()
1998
create_configs_with_file_option(self)
2000
def assertGetHook(self, conf, name, value):
2004
config.OldConfigHooks.install_named_hook('get', hook, None)
2006
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2007
self.assertLength(0, calls)
2008
actual_value = conf.get_user_option(name)
2009
self.assertEquals(value, actual_value)
2010
self.assertLength(1, calls)
2011
self.assertEquals((conf, name, value), calls[0])
2013
def test_get_hook_bazaar(self):
2014
self.assertGetHook(self.bazaar_config, 'file', 'bazaar')
2016
def test_get_hook_locations(self):
2017
self.assertGetHook(self.locations_config, 'file', 'locations')
2019
def test_get_hook_branch(self):
2020
# Since locations masks branch, we define a different option
2021
self.branch_config.set_user_option('file2', 'branch')
2022
self.assertGetHook(self.branch_config, 'file2', 'branch')
2024
def assertSetHook(self, conf, name, value):
2028
config.OldConfigHooks.install_named_hook('set', hook, None)
2030
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2031
self.assertLength(0, calls)
2032
conf.set_user_option(name, value)
2033
self.assertLength(1, calls)
2034
# We can't assert the conf object below as different configs use
2035
# different means to implement set_user_option and we care only about
2037
self.assertEquals((name, value), calls[0][1:])
2039
def test_set_hook_bazaar(self):
2040
self.assertSetHook(self.bazaar_config, 'foo', 'bazaar')
2042
def test_set_hook_locations(self):
2043
self.assertSetHook(self.locations_config, 'foo', 'locations')
2045
def test_set_hook_branch(self):
2046
self.assertSetHook(self.branch_config, 'foo', 'branch')
2048
def assertRemoveHook(self, conf, name, section_name=None):
2052
config.OldConfigHooks.install_named_hook('remove', hook, None)
2054
config.OldConfigHooks.uninstall_named_hook, 'remove', None)
2055
self.assertLength(0, calls)
2056
conf.remove_user_option(name, section_name)
2057
self.assertLength(1, calls)
2058
# We can't assert the conf object below as different configs use
2059
# different means to implement remove_user_option and we care only about
2061
self.assertEquals((name,), calls[0][1:])
2063
def test_remove_hook_bazaar(self):
2064
self.assertRemoveHook(self.bazaar_config, 'file')
2066
def test_remove_hook_locations(self):
2067
self.assertRemoveHook(self.locations_config, 'file',
2068
self.locations_config.location)
2070
def test_remove_hook_branch(self):
2071
self.assertRemoveHook(self.branch_config, 'file')
2073
def assertLoadHook(self, name, conf_class, *conf_args):
2077
config.OldConfigHooks.install_named_hook('load', hook, None)
2079
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2080
self.assertLength(0, calls)
2082
conf = conf_class(*conf_args)
2083
# Access an option to trigger a load
2084
conf.get_user_option(name)
2085
self.assertLength(1, calls)
2086
# Since we can't assert about conf, we just use the number of calls ;-/
2088
def test_load_hook_bazaar(self):
2089
self.assertLoadHook('file', config.GlobalConfig)
2091
def test_load_hook_locations(self):
2092
self.assertLoadHook('file', config.LocationConfig, self.tree.basedir)
2094
def test_load_hook_branch(self):
2095
self.assertLoadHook('file', config.BranchConfig, self.tree.branch)
2097
def assertSaveHook(self, conf):
2101
config.OldConfigHooks.install_named_hook('save', hook, None)
2103
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2104
self.assertLength(0, calls)
2105
# Setting an option triggers a save
2106
conf.set_user_option('foo', 'bar')
2107
self.assertLength(1, calls)
2108
# Since we can't assert about conf, we just use the number of calls ;-/
2110
def test_save_hook_bazaar(self):
2111
self.assertSaveHook(self.bazaar_config)
2113
def test_save_hook_locations(self):
2114
self.assertSaveHook(self.locations_config)
2116
def test_save_hook_branch(self):
2117
self.assertSaveHook(self.branch_config)
2120
class TestOldConfigHooksForRemote(tests.TestCaseWithTransport):
2121
"""Tests config hooks for remote configs.
2123
No tests for the remove hook as this is not implemented there.
2127
super(TestOldConfigHooksForRemote, self).setUp()
2128
self.transport_server = test_server.SmartTCPServer_for_testing
2129
create_configs_with_file_option(self)
2131
def assertGetHook(self, conf, name, value):
2135
config.OldConfigHooks.install_named_hook('get', hook, None)
2137
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2138
self.assertLength(0, calls)
2139
actual_value = conf.get_option(name)
2140
self.assertEquals(value, actual_value)
2141
self.assertLength(1, calls)
2142
self.assertEquals((conf, name, value), calls[0])
2144
def test_get_hook_remote_branch(self):
2145
remote_branch = branch.Branch.open(self.get_url('tree'))
2146
self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
2148
def test_get_hook_remote_bzrdir(self):
2149
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2150
conf = remote_bzrdir._get_config()
2151
conf.set_option('remotedir', 'file')
2152
self.assertGetHook(conf, 'file', 'remotedir')
2154
def assertSetHook(self, conf, name, value):
2158
config.OldConfigHooks.install_named_hook('set', hook, None)
2160
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2161
self.assertLength(0, calls)
2162
conf.set_option(value, name)
2163
self.assertLength(1, calls)
2164
# We can't assert the conf object below as different configs use
2165
# different means to implement set_user_option and we care only about
2167
self.assertEquals((name, value), calls[0][1:])
2169
def test_set_hook_remote_branch(self):
2170
remote_branch = branch.Branch.open(self.get_url('tree'))
2171
self.addCleanup(remote_branch.lock_write().unlock)
2172
self.assertSetHook(remote_branch._get_config(), 'file', 'remote')
2174
def test_set_hook_remote_bzrdir(self):
2175
remote_branch = branch.Branch.open(self.get_url('tree'))
2176
self.addCleanup(remote_branch.lock_write().unlock)
2177
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2178
self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
2180
def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
2184
config.OldConfigHooks.install_named_hook('load', hook, None)
2186
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2187
self.assertLength(0, calls)
2189
conf = conf_class(*conf_args)
2190
# Access an option to trigger a load
2191
conf.get_option(name)
2192
self.assertLength(expected_nb_calls, calls)
2193
# Since we can't assert about conf, we just use the number of calls ;-/
2195
def test_load_hook_remote_branch(self):
2196
remote_branch = branch.Branch.open(self.get_url('tree'))
2197
self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
2199
def test_load_hook_remote_bzrdir(self):
2200
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2201
# The config file doesn't exist, set an option to force its creation
2202
conf = remote_bzrdir._get_config()
2203
conf.set_option('remotedir', 'file')
2204
# We get one call for the server and one call for the client, this is
2205
# caused by the differences in implementations betwen
2206
# SmartServerBzrDirRequestConfigFile (in smart/bzrdir.py) and
2207
# SmartServerBranchGetConfigFile (in smart/branch.py)
2208
self.assertLoadHook(2 ,'file', remote.RemoteBzrDirConfig, remote_bzrdir)
2210
def assertSaveHook(self, conf):
2214
config.OldConfigHooks.install_named_hook('save', hook, None)
2216
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2217
self.assertLength(0, calls)
2218
# Setting an option triggers a save
2219
conf.set_option('foo', 'bar')
2220
self.assertLength(1, calls)
2221
# Since we can't assert about conf, we just use the number of calls ;-/
2223
def test_save_hook_remote_branch(self):
2224
remote_branch = branch.Branch.open(self.get_url('tree'))
2225
self.addCleanup(remote_branch.lock_write().unlock)
2226
self.assertSaveHook(remote_branch._get_config())
2228
def test_save_hook_remote_bzrdir(self):
2229
remote_branch = branch.Branch.open(self.get_url('tree'))
2230
self.addCleanup(remote_branch.lock_write().unlock)
2231
remote_bzrdir = controldir.ControlDir.open(self.get_url('tree'))
2232
self.assertSaveHook(remote_bzrdir._get_config())
2235
class TestOption(tests.TestCase):
2237
def test_default_value(self):
2238
opt = config.Option('foo', default='bar')
2239
self.assertEquals('bar', opt.get_default())
2241
def test_callable_default_value(self):
2242
def bar_as_unicode():
2244
opt = config.Option('foo', default=bar_as_unicode)
2245
self.assertEquals('bar', opt.get_default())
2247
def test_default_value_from_env(self):
2248
opt = config.Option('foo', default='bar', default_from_env=['FOO'])
2249
self.overrideEnv('FOO', 'quux')
2250
# Env variable provides a default taking over the option one
2251
self.assertEquals('quux', opt.get_default())
2253
def test_first_default_value_from_env_wins(self):
2254
opt = config.Option('foo', default='bar',
2255
default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
2256
self.overrideEnv('FOO', 'foo')
2257
self.overrideEnv('BAZ', 'baz')
2258
# The first env var set wins
2259
self.assertEquals('foo', opt.get_default())
2261
def test_not_supported_list_default_value(self):
2262
self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
2264
def test_not_supported_object_default_value(self):
2265
self.assertRaises(AssertionError, config.Option, 'foo',
2268
def test_not_supported_callable_default_value_not_unicode(self):
2269
def bar_not_unicode():
2271
opt = config.Option('foo', default=bar_not_unicode)
2272
self.assertRaises(AssertionError, opt.get_default)
2274
def test_get_help_topic(self):
2275
opt = config.Option('foo')
2276
self.assertEquals('foo', opt.get_help_topic())
2279
class TestOptionConverterMixin(object):
2281
def assertConverted(self, expected, opt, value):
2282
self.assertEquals(expected, opt.convert_from_unicode(None, value))
2284
def assertWarns(self, opt, value):
2287
warnings.append(args[0] % args[1:])
2288
self.overrideAttr(trace, 'warning', warning)
2289
self.assertEquals(None, opt.convert_from_unicode(None, value))
2290
self.assertLength(1, warnings)
2292
'Value "%s" is not valid for "%s"' % (value, opt.name),
2295
def assertErrors(self, opt, value):
2296
self.assertRaises(errors.ConfigOptionValueError,
2297
opt.convert_from_unicode, None, value)
2299
def assertConvertInvalid(self, opt, invalid_value):
2301
self.assertEquals(None, opt.convert_from_unicode(None, invalid_value))
2302
opt.invalid = 'warning'
2303
self.assertWarns(opt, invalid_value)
2304
opt.invalid = 'error'
2305
self.assertErrors(opt, invalid_value)
2308
class TestOptionWithBooleanConverter(tests.TestCase, TestOptionConverterMixin):
2310
def get_option(self):
2311
return config.Option('foo', help='A boolean.',
2312
from_unicode=config.bool_from_store)
2314
def test_convert_invalid(self):
2315
opt = self.get_option()
2316
# A string that is not recognized as a boolean
2317
self.assertConvertInvalid(opt, u'invalid-boolean')
2318
# A list of strings is never recognized as a boolean
2319
self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
2321
def test_convert_valid(self):
2322
opt = self.get_option()
2323
self.assertConverted(True, opt, u'True')
2324
self.assertConverted(True, opt, u'1')
2325
self.assertConverted(False, opt, u'False')
2328
class TestOptionWithIntegerConverter(tests.TestCase, TestOptionConverterMixin):
2330
def get_option(self):
2331
return config.Option('foo', help='An integer.',
2332
from_unicode=config.int_from_store)
2334
def test_convert_invalid(self):
2335
opt = self.get_option()
2336
# A string that is not recognized as an integer
2337
self.assertConvertInvalid(opt, u'forty-two')
2338
# A list of strings is never recognized as an integer
2339
self.assertConvertInvalid(opt, [u'a', u'list'])
2341
def test_convert_valid(self):
2342
opt = self.get_option()
2343
self.assertConverted(16, opt, u'16')
2346
class TestOptionWithSIUnitConverter(tests.TestCase, TestOptionConverterMixin):
2348
def get_option(self):
2349
return config.Option('foo', help='An integer in SI units.',
2350
from_unicode=config.int_SI_from_store)
2352
def test_convert_invalid(self):
2353
opt = self.get_option()
2354
self.assertConvertInvalid(opt, u'not-a-unit')
2355
self.assertConvertInvalid(opt, u'Gb') # Forgot the int
2356
self.assertConvertInvalid(opt, u'1b') # Forgot the unit
2357
self.assertConvertInvalid(opt, u'1GG')
2358
self.assertConvertInvalid(opt, u'1Mbb')
2359
self.assertConvertInvalid(opt, u'1MM')
2361
def test_convert_valid(self):
2362
opt = self.get_option()
2363
self.assertConverted(int(5e3), opt, u'5kb')
2364
self.assertConverted(int(5e6), opt, u'5M')
2365
self.assertConverted(int(5e6), opt, u'5MB')
2366
self.assertConverted(int(5e9), opt, u'5g')
2367
self.assertConverted(int(5e9), opt, u'5gB')
2368
self.assertConverted(100, opt, u'100')
2371
class TestListOption(tests.TestCase, TestOptionConverterMixin):
2373
def get_option(self):
2374
return config.ListOption('foo', help='A list.')
2376
def test_convert_invalid(self):
2377
opt = self.get_option()
2378
# We don't even try to convert a list into a list, we only expect
2380
self.assertConvertInvalid(opt, [1])
2381
# No string is invalid as all forms can be converted to a list
2383
def test_convert_valid(self):
2384
opt = self.get_option()
2385
# An empty string is an empty list
2386
self.assertConverted([], opt, '') # Using a bare str() just in case
2387
self.assertConverted([], opt, u'')
2389
self.assertConverted([u'True'], opt, u'True')
2391
self.assertConverted([u'42'], opt, u'42')
2393
self.assertConverted([u'bar'], opt, u'bar')
2396
class TestRegistryOption(tests.TestCase, TestOptionConverterMixin):
2398
def get_option(self, registry):
2399
return config.RegistryOption('foo', registry,
2400
help='A registry option.')
2402
def test_convert_invalid(self):
2403
registry = _mod_registry.Registry()
2404
opt = self.get_option(registry)
2405
self.assertConvertInvalid(opt, [1])
2406
self.assertConvertInvalid(opt, u"notregistered")
2408
def test_convert_valid(self):
2409
registry = _mod_registry.Registry()
2410
registry.register("someval", 1234)
2411
opt = self.get_option(registry)
2412
# Using a bare str() just in case
2413
self.assertConverted(1234, opt, "someval")
2414
self.assertConverted(1234, opt, u'someval')
2415
self.assertConverted(None, opt, None)
2417
def test_help(self):
2418
registry = _mod_registry.Registry()
2419
registry.register("someval", 1234, help="some option")
2420
registry.register("dunno", 1234, help="some other option")
2421
opt = self.get_option(registry)
2423
'A registry option.\n'
2425
'The following values are supported:\n'
2426
' dunno - some other option\n'
2427
' someval - some option\n',
2430
def test_get_help_text(self):
2431
registry = _mod_registry.Registry()
2432
registry.register("someval", 1234, help="some option")
2433
registry.register("dunno", 1234, help="some other option")
2434
opt = self.get_option(registry)
2436
'A registry option.\n'
2438
'The following values are supported:\n'
2439
' dunno - some other option\n'
2440
' someval - some option\n',
2441
opt.get_help_text())
2444
class TestOptionRegistry(tests.TestCase):
2447
super(TestOptionRegistry, self).setUp()
2448
# Always start with an empty registry
2449
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2450
self.registry = config.option_registry
2452
def test_register(self):
2453
opt = config.Option('foo')
2454
self.registry.register(opt)
2455
self.assertIs(opt, self.registry.get('foo'))
2457
def test_registered_help(self):
2458
opt = config.Option('foo', help='A simple option')
2459
self.registry.register(opt)
2460
self.assertEquals('A simple option', self.registry.get_help('foo'))
2462
lazy_option = config.Option('lazy_foo', help='Lazy help')
2464
def test_register_lazy(self):
2465
self.registry.register_lazy('lazy_foo', self.__module__,
2466
'TestOptionRegistry.lazy_option')
2467
self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
2469
def test_registered_lazy_help(self):
2470
self.registry.register_lazy('lazy_foo', self.__module__,
2471
'TestOptionRegistry.lazy_option')
2472
self.assertEquals('Lazy help', self.registry.get_help('lazy_foo'))
2475
class TestRegisteredOptions(tests.TestCase):
2476
"""All registered options should verify some constraints."""
2478
scenarios = [(key, {'option_name': key, 'option': option}) for key, option
2479
in config.option_registry.iteritems()]
2482
super(TestRegisteredOptions, self).setUp()
2483
self.registry = config.option_registry
2485
def test_proper_name(self):
2486
# An option should be registered under its own name, this can't be
2487
# checked at registration time for the lazy ones.
2488
self.assertEquals(self.option_name, self.option.name)
2490
def test_help_is_set(self):
2491
option_help = self.registry.get_help(self.option_name)
2492
self.assertNotEquals(None, option_help)
2493
# Come on, think about the user, he really wants to know what the
2495
self.assertIsNot(None, option_help)
2496
self.assertNotEquals('', option_help)
2499
class TestSection(tests.TestCase):
2501
# FIXME: Parametrize so that all sections produced by Stores run these
2502
# tests -- vila 2011-04-01
2504
def test_get_a_value(self):
2505
a_dict = dict(foo='bar')
2506
section = config.Section('myID', a_dict)
2507
self.assertEquals('bar', section.get('foo'))
2509
def test_get_unknown_option(self):
2511
section = config.Section(None, a_dict)
2512
self.assertEquals('out of thin air',
2513
section.get('foo', 'out of thin air'))
2515
def test_options_is_shared(self):
2517
section = config.Section(None, a_dict)
2518
self.assertIs(a_dict, section.options)
2521
class TestMutableSection(tests.TestCase):
2523
scenarios = [('mutable',
2525
lambda opts: config.MutableSection('myID', opts)},),
2529
a_dict = dict(foo='bar')
2530
section = self.get_section(a_dict)
2531
section.set('foo', 'new_value')
2532
self.assertEquals('new_value', section.get('foo'))
2533
# The change appears in the shared section
2534
self.assertEquals('new_value', a_dict.get('foo'))
2535
# We keep track of the change
2536
self.assertTrue('foo' in section.orig)
2537
self.assertEquals('bar', section.orig.get('foo'))
2539
def test_set_preserve_original_once(self):
2540
a_dict = dict(foo='bar')
2541
section = self.get_section(a_dict)
2542
section.set('foo', 'first_value')
2543
section.set('foo', 'second_value')
2544
# We keep track of the original value
2545
self.assertTrue('foo' in section.orig)
2546
self.assertEquals('bar', section.orig.get('foo'))
2548
def test_remove(self):
2549
a_dict = dict(foo='bar')
2550
section = self.get_section(a_dict)
2551
section.remove('foo')
2552
# We get None for unknown options via the default value
2553
self.assertEquals(None, section.get('foo'))
2554
# Or we just get the default value
2555
self.assertEquals('unknown', section.get('foo', 'unknown'))
2556
self.assertFalse('foo' in section.options)
2557
# We keep track of the deletion
2558
self.assertTrue('foo' in section.orig)
2559
self.assertEquals('bar', section.orig.get('foo'))
2561
def test_remove_new_option(self):
2563
section = self.get_section(a_dict)
2564
section.set('foo', 'bar')
2565
section.remove('foo')
2566
self.assertFalse('foo' in section.options)
2567
# The option didn't exist initially so it we need to keep track of it
2568
# with a special value
2569
self.assertTrue('foo' in section.orig)
2570
self.assertEquals(config._NewlyCreatedOption, section.orig['foo'])
2573
class TestCommandLineStore(tests.TestCase):
2576
super(TestCommandLineStore, self).setUp()
2577
self.store = config.CommandLineStore()
2578
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2580
def get_section(self):
2581
"""Get the unique section for the command line overrides."""
2582
sections = list(self.store.get_sections())
2583
self.assertLength(1, sections)
2584
store, section = sections[0]
2585
self.assertEquals(self.store, store)
2588
def test_no_override(self):
2589
self.store._from_cmdline([])
2590
section = self.get_section()
2591
self.assertLength(0, list(section.iter_option_names()))
2593
def test_simple_override(self):
2594
self.store._from_cmdline(['a=b'])
2595
section = self.get_section()
2596
self.assertEqual('b', section.get('a'))
2598
def test_list_override(self):
2599
opt = config.ListOption('l')
2600
config.option_registry.register(opt)
2601
self.store._from_cmdline(['l=1,2,3'])
2602
val = self.get_section().get('l')
2603
self.assertEqual('1,2,3', val)
2604
# Reminder: lists should be registered as such explicitely, otherwise
2605
# the conversion needs to be done afterwards.
2606
self.assertEqual(['1', '2', '3'],
2607
opt.convert_from_unicode(self.store, val))
2609
def test_multiple_overrides(self):
2610
self.store._from_cmdline(['a=b', 'x=y'])
2611
section = self.get_section()
2612
self.assertEquals('b', section.get('a'))
2613
self.assertEquals('y', section.get('x'))
2615
def test_wrong_syntax(self):
2616
self.assertRaises(errors.BzrCommandError,
2617
self.store._from_cmdline, ['a=b', 'c'])
2619
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
2621
scenarios = [(key, {'get_store': builder}) for key, builder
2622
in config.test_store_builder_registry.iteritems()] + [
2623
('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
2626
store = self.get_store(self)
2627
if type(store) == config.TransportIniFileStore:
2628
raise tests.TestNotApplicable(
2629
"%s is not a concrete Store implementation"
2630
" so it doesn't need an id" % (store.__class__.__name__,))
2631
self.assertIsNot(None, store.id)
2634
class TestStore(tests.TestCaseWithTransport):
2636
def assertSectionContent(self, expected, (store, section)):
2637
"""Assert that some options have the proper values in a section."""
2638
expected_name, expected_options = expected
2639
self.assertEquals(expected_name, section.id)
2642
dict([(k, section.get(k)) for k in expected_options.keys()]))
2645
class TestReadonlyStore(TestStore):
2647
scenarios = [(key, {'get_store': builder}) for key, builder
2648
in config.test_store_builder_registry.iteritems()]
2650
def test_building_delays_load(self):
2651
store = self.get_store(self)
2652
self.assertEquals(False, store.is_loaded())
2653
store._load_from_string('')
2654
self.assertEquals(True, store.is_loaded())
2656
def test_get_no_sections_for_empty(self):
2657
store = self.get_store(self)
2658
store._load_from_string('')
2659
self.assertEquals([], list(store.get_sections()))
2661
def test_get_default_section(self):
2662
store = self.get_store(self)
2663
store._load_from_string('foo=bar')
2664
sections = list(store.get_sections())
2665
self.assertLength(1, sections)
2666
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2668
def test_get_named_section(self):
2669
store = self.get_store(self)
2670
store._load_from_string('[baz]\nfoo=bar')
2671
sections = list(store.get_sections())
2672
self.assertLength(1, sections)
2673
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2675
def test_load_from_string_fails_for_non_empty_store(self):
2676
store = self.get_store(self)
2677
store._load_from_string('foo=bar')
2678
self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
2681
class TestStoreQuoting(TestStore):
2683
scenarios = [(key, {'get_store': builder}) for key, builder
2684
in config.test_store_builder_registry.iteritems()]
2687
super(TestStoreQuoting, self).setUp()
2688
self.store = self.get_store(self)
2689
# We need a loaded store but any content will do
2690
self.store._load_from_string('')
2692
def assertIdempotent(self, s):
2693
"""Assert that quoting an unquoted string is a no-op and vice-versa.
2695
What matters here is that option values, as they appear in a store, can
2696
be safely round-tripped out of the store and back.
2698
:param s: A string, quoted if required.
2700
self.assertEquals(s, self.store.quote(self.store.unquote(s)))
2701
self.assertEquals(s, self.store.unquote(self.store.quote(s)))
2703
def test_empty_string(self):
2704
if isinstance(self.store, config.IniFileStore):
2705
# configobj._quote doesn't handle empty values
2706
self.assertRaises(AssertionError,
2707
self.assertIdempotent, '')
2709
self.assertIdempotent('')
2710
# But quoted empty strings are ok
2711
self.assertIdempotent('""')
2713
def test_embedded_spaces(self):
2714
self.assertIdempotent('" a b c "')
2716
def test_embedded_commas(self):
2717
self.assertIdempotent('" a , b c "')
2719
def test_simple_comma(self):
2720
if isinstance(self.store, config.IniFileStore):
2721
# configobj requires that lists are special-cased
2722
self.assertRaises(AssertionError,
2723
self.assertIdempotent, ',')
2725
self.assertIdempotent(',')
2726
# When a single comma is required, quoting is also required
2727
self.assertIdempotent('","')
2729
def test_list(self):
2730
if isinstance(self.store, config.IniFileStore):
2731
# configobj requires that lists are special-cased
2732
self.assertRaises(AssertionError,
2733
self.assertIdempotent, 'a,b')
2735
self.assertIdempotent('a,b')
2738
class TestDictFromStore(tests.TestCase):
2740
def test_unquote_not_string(self):
2741
conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
2742
value = conf.get('a_section')
2743
# Urgh, despite 'conf' asking for the no-name section, we get the
2744
# content of another section as a dict o_O
2745
self.assertEquals({'a': '1'}, value)
2746
unquoted = conf.store.unquote(value)
2747
# Which cannot be unquoted but shouldn't crash either (the use cases
2748
# are getting the value or displaying it. In the later case, '%s' will
2750
self.assertEquals({'a': '1'}, unquoted)
2751
self.assertEquals("{u'a': u'1'}", '%s' % (unquoted,))
2754
class TestIniFileStoreContent(tests.TestCaseWithTransport):
2755
"""Simulate loading a config store with content of various encodings.
2757
All files produced by bzr are in utf8 content.
2759
Users may modify them manually and end up with a file that can't be
2760
loaded. We need to issue proper error messages in this case.
2763
invalid_utf8_char = '\xff'
2765
def test_load_utf8(self):
2766
"""Ensure we can load an utf8-encoded file."""
2767
t = self.get_transport()
2768
# From http://pad.lv/799212
2769
unicode_user = u'b\N{Euro Sign}ar'
2770
unicode_content = u'user=%s' % (unicode_user,)
2771
utf8_content = unicode_content.encode('utf8')
2772
# Store the raw content in the config file
2773
t.put_bytes('foo.conf', utf8_content)
2774
store = config.TransportIniFileStore(t, 'foo.conf')
2776
stack = config.Stack([store.get_sections], store)
2777
self.assertEquals(unicode_user, stack.get('user'))
2779
def test_load_non_ascii(self):
2780
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2781
t = self.get_transport()
2782
t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
2783
store = config.TransportIniFileStore(t, 'foo.conf')
2784
self.assertRaises(errors.ConfigContentError, store.load)
2786
def test_load_erroneous_content(self):
2787
"""Ensure we display a proper error on content that can't be parsed."""
2788
t = self.get_transport()
2789
t.put_bytes('foo.conf', '[open_section\n')
2790
store = config.TransportIniFileStore(t, 'foo.conf')
2791
self.assertRaises(errors.ParseConfigError, store.load)
2793
def test_load_permission_denied(self):
2794
"""Ensure we get warned when trying to load an inaccessible file."""
2797
warnings.append(args[0] % args[1:])
2798
self.overrideAttr(trace, 'warning', warning)
2800
t = self.get_transport()
2802
def get_bytes(relpath):
2803
raise errors.PermissionDenied(relpath, "")
2804
t.get_bytes = get_bytes
2805
store = config.TransportIniFileStore(t, 'foo.conf')
2806
self.assertRaises(errors.PermissionDenied, store.load)
2809
[u'Permission denied while trying to load configuration store %s.'
2810
% store.external_url()])
2813
class TestIniConfigContent(tests.TestCaseWithTransport):
2814
"""Simulate loading a IniBasedConfig with content of various encodings.
2816
All files produced by bzr are in utf8 content.
2818
Users may modify them manually and end up with a file that can't be
2819
loaded. We need to issue proper error messages in this case.
2822
invalid_utf8_char = '\xff'
2824
def test_load_utf8(self):
2825
"""Ensure we can load an utf8-encoded file."""
2826
# From http://pad.lv/799212
2827
unicode_user = u'b\N{Euro Sign}ar'
2828
unicode_content = u'user=%s' % (unicode_user,)
2829
utf8_content = unicode_content.encode('utf8')
2830
# Store the raw content in the config file
2831
with open('foo.conf', 'wb') as f:
2832
f.write(utf8_content)
2833
conf = config.IniBasedConfig(file_name='foo.conf')
2834
self.assertEquals(unicode_user, conf.get_user_option('user'))
2836
def test_load_badly_encoded_content(self):
2837
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2838
with open('foo.conf', 'wb') as f:
2839
f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
2840
conf = config.IniBasedConfig(file_name='foo.conf')
2841
self.assertRaises(errors.ConfigContentError, conf._get_parser)
2843
def test_load_erroneous_content(self):
2844
"""Ensure we display a proper error on content that can't be parsed."""
2845
with open('foo.conf', 'wb') as f:
2846
f.write('[open_section\n')
2847
conf = config.IniBasedConfig(file_name='foo.conf')
2848
self.assertRaises(errors.ParseConfigError, conf._get_parser)
2851
class TestMutableStore(TestStore):
2853
scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
2854
in config.test_store_builder_registry.iteritems()]
2857
super(TestMutableStore, self).setUp()
2858
self.transport = self.get_transport()
2860
def has_store(self, store):
2861
store_basename = urlutils.relative_url(self.transport.external_url(),
2862
store.external_url())
2863
return self.transport.has(store_basename)
2865
def test_save_empty_creates_no_file(self):
2866
# FIXME: There should be a better way than relying on the test
2867
# parametrization to identify branch.conf -- vila 2011-0526
2868
if self.store_id in ('branch', 'remote_branch'):
2869
raise tests.TestNotApplicable(
2870
'branch.conf is *always* created when a branch is initialized')
2871
store = self.get_store(self)
2873
self.assertEquals(False, self.has_store(store))
2875
def test_save_emptied_succeeds(self):
2876
store = self.get_store(self)
2877
store._load_from_string('foo=bar\n')
2878
# FIXME: There should be a better way than relying on the test
2879
# parametrization to identify branch.conf -- vila 2011-0526
2880
if self.store_id in ('branch', 'remote_branch'):
2881
# branch stores requires write locked branches
2882
self.addCleanup(store.branch.lock_write().unlock)
2883
section = store.get_mutable_section(None)
2884
section.remove('foo')
2886
self.assertEquals(True, self.has_store(store))
2887
modified_store = self.get_store(self)
2888
sections = list(modified_store.get_sections())
2889
self.assertLength(0, sections)
2891
def test_save_with_content_succeeds(self):
2892
# FIXME: There should be a better way than relying on the test
2893
# parametrization to identify branch.conf -- vila 2011-0526
2894
if self.store_id in ('branch', 'remote_branch'):
2895
raise tests.TestNotApplicable(
2896
'branch.conf is *always* created when a branch is initialized')
2897
store = self.get_store(self)
2898
store._load_from_string('foo=bar\n')
2899
self.assertEquals(False, self.has_store(store))
2901
self.assertEquals(True, self.has_store(store))
2902
modified_store = self.get_store(self)
2903
sections = list(modified_store.get_sections())
2904
self.assertLength(1, sections)
2905
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2907
def test_set_option_in_empty_store(self):
2908
store = self.get_store(self)
2909
# FIXME: There should be a better way than relying on the test
2910
# parametrization to identify branch.conf -- vila 2011-0526
2911
if self.store_id in ('branch', 'remote_branch'):
2912
# branch stores requires write locked branches
2913
self.addCleanup(store.branch.lock_write().unlock)
2914
section = store.get_mutable_section(None)
2915
section.set('foo', 'bar')
2917
modified_store = self.get_store(self)
2918
sections = list(modified_store.get_sections())
2919
self.assertLength(1, sections)
2920
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2922
def test_set_option_in_default_section(self):
2923
store = self.get_store(self)
2924
store._load_from_string('')
2925
# FIXME: There should be a better way than relying on the test
2926
# parametrization to identify branch.conf -- vila 2011-0526
2927
if self.store_id in ('branch', 'remote_branch'):
2928
# branch stores requires write locked branches
2929
self.addCleanup(store.branch.lock_write().unlock)
2930
section = store.get_mutable_section(None)
2931
section.set('foo', 'bar')
2933
modified_store = self.get_store(self)
2934
sections = list(modified_store.get_sections())
2935
self.assertLength(1, sections)
2936
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2938
def test_set_option_in_named_section(self):
2939
store = self.get_store(self)
2940
store._load_from_string('')
2941
# FIXME: There should be a better way than relying on the test
2942
# parametrization to identify branch.conf -- vila 2011-0526
2943
if self.store_id in ('branch', 'remote_branch'):
2944
# branch stores requires write locked branches
2945
self.addCleanup(store.branch.lock_write().unlock)
2946
section = store.get_mutable_section('baz')
2947
section.set('foo', 'bar')
2949
modified_store = self.get_store(self)
2950
sections = list(modified_store.get_sections())
2951
self.assertLength(1, sections)
2952
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2954
def test_load_hook(self):
2955
# First, we need to ensure that the store exists
2956
store = self.get_store(self)
2957
# FIXME: There should be a better way than relying on the test
2958
# parametrization to identify branch.conf -- vila 2011-0526
2959
if self.store_id in ('branch', 'remote_branch'):
2960
# branch stores requires write locked branches
2961
self.addCleanup(store.branch.lock_write().unlock)
2962
section = store.get_mutable_section('baz')
2963
section.set('foo', 'bar')
2965
# Now we can try to load it
2966
store = self.get_store(self)
2970
config.ConfigHooks.install_named_hook('load', hook, None)
2971
self.assertLength(0, calls)
2973
self.assertLength(1, calls)
2974
self.assertEquals((store,), calls[0])
2976
def test_save_hook(self):
2980
config.ConfigHooks.install_named_hook('save', hook, None)
2981
self.assertLength(0, calls)
2982
store = self.get_store(self)
2983
# FIXME: There should be a better way than relying on the test
2984
# parametrization to identify branch.conf -- vila 2011-0526
2985
if self.store_id in ('branch', 'remote_branch'):
2986
# branch stores requires write locked branches
2987
self.addCleanup(store.branch.lock_write().unlock)
2988
section = store.get_mutable_section('baz')
2989
section.set('foo', 'bar')
2991
self.assertLength(1, calls)
2992
self.assertEquals((store,), calls[0])
2994
def test_set_mark_dirty(self):
2995
stack = config.MemoryStack('')
2996
self.assertLength(0, stack.store.dirty_sections)
2997
stack.set('foo', 'baz')
2998
self.assertLength(1, stack.store.dirty_sections)
2999
self.assertTrue(stack.store._need_saving())
3001
def test_remove_mark_dirty(self):
3002
stack = config.MemoryStack('foo=bar')
3003
self.assertLength(0, stack.store.dirty_sections)
3005
self.assertLength(1, stack.store.dirty_sections)
3006
self.assertTrue(stack.store._need_saving())
3009
class TestStoreSaveChanges(tests.TestCaseWithTransport):
3010
"""Tests that config changes are kept in memory and saved on-demand."""
3013
super(TestStoreSaveChanges, self).setUp()
3014
self.transport = self.get_transport()
3015
# Most of the tests involve two stores pointing to the same persistent
3016
# storage to observe the effects of concurrent changes
3017
self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
3018
self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
3021
self.warnings.append(args[0] % args[1:])
3022
self.overrideAttr(trace, 'warning', warning)
3024
def has_store(self, store):
3025
store_basename = urlutils.relative_url(self.transport.external_url(),
3026
store.external_url())
3027
return self.transport.has(store_basename)
3029
def get_stack(self, store):
3030
# Any stack will do as long as it uses the right store, just a single
3031
# no-name section is enough
3032
return config.Stack([store.get_sections], store)
3034
def test_no_changes_no_save(self):
3035
s = self.get_stack(self.st1)
3036
s.store.save_changes()
3037
self.assertEquals(False, self.has_store(self.st1))
3039
def test_unrelated_concurrent_update(self):
3040
s1 = self.get_stack(self.st1)
3041
s2 = self.get_stack(self.st2)
3042
s1.set('foo', 'bar')
3043
s2.set('baz', 'quux')
3045
# Changes don't propagate magically
3046
self.assertEquals(None, s1.get('baz'))
3047
s2.store.save_changes()
3048
self.assertEquals('quux', s2.get('baz'))
3049
# Changes are acquired when saving
3050
self.assertEquals('bar', s2.get('foo'))
3051
# Since there is no overlap, no warnings are emitted
3052
self.assertLength(0, self.warnings)
3054
def test_concurrent_update_modified(self):
3055
s1 = self.get_stack(self.st1)
3056
s2 = self.get_stack(self.st2)
3057
s1.set('foo', 'bar')
3058
s2.set('foo', 'baz')
3061
s2.store.save_changes()
3062
self.assertEquals('baz', s2.get('foo'))
3063
# But the user get a warning
3064
self.assertLength(1, self.warnings)
3065
warning = self.warnings[0]
3066
self.assertStartsWith(warning, 'Option foo in section None')
3067
self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
3068
' The baz value will be saved.')
3070
def test_concurrent_deletion(self):
3071
self.st1._load_from_string('foo=bar')
3073
s1 = self.get_stack(self.st1)
3074
s2 = self.get_stack(self.st2)
3077
s1.store.save_changes()
3079
self.assertLength(0, self.warnings)
3080
s2.store.save_changes()
3082
self.assertLength(1, self.warnings)
3083
warning = self.warnings[0]
3084
self.assertStartsWith(warning, 'Option foo in section None')
3085
self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
3086
' The <DELETED> value will be saved.')
3089
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
3091
def get_store(self):
3092
return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3094
def test_get_quoted_string(self):
3095
store = self.get_store()
3096
store._load_from_string('foo= " abc "')
3097
stack = config.Stack([store.get_sections])
3098
self.assertEquals(' abc ', stack.get('foo'))
3100
def test_set_quoted_string(self):
3101
store = self.get_store()
3102
stack = config.Stack([store.get_sections], store)
3103
stack.set('foo', ' a b c ')
3105
self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
3108
class TestTransportIniFileStore(TestStore):
3110
def test_loading_unknown_file_fails(self):
3111
store = config.TransportIniFileStore(self.get_transport(),
3113
self.assertRaises(errors.NoSuchFile, store.load)
3115
def test_invalid_content(self):
3116
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3117
self.assertEquals(False, store.is_loaded())
3118
exc = self.assertRaises(
3119
errors.ParseConfigError, store._load_from_string,
3120
'this is invalid !')
3121
self.assertEndsWith(exc.filename, 'foo.conf')
3122
# And the load failed
3123
self.assertEquals(False, store.is_loaded())
3125
def test_get_embedded_sections(self):
3126
# A more complicated example (which also shows that section names and
3127
# option names share the same name space...)
3128
# FIXME: This should be fixed by forbidding dicts as values ?
3129
# -- vila 2011-04-05
3130
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3131
store._load_from_string('''
3135
foo_in_DEFAULT=foo_DEFAULT
3143
sections = list(store.get_sections())
3144
self.assertLength(4, sections)
3145
# The default section has no name.
3146
# List values are provided as strings and need to be explicitly
3147
# converted by specifying from_unicode=list_from_store at option
3149
self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
3151
self.assertSectionContent(
3152
('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
3153
self.assertSectionContent(
3154
('bar', {'foo_in_bar': 'barbar'}), sections[2])
3155
# sub sections are provided as embedded dicts.
3156
self.assertSectionContent(
3157
('baz', {'foo_in_baz': 'barbaz', 'qux': {'foo_in_qux': 'quux'}}),
3161
class TestLockableIniFileStore(TestStore):
3163
def test_create_store_in_created_dir(self):
3164
self.assertPathDoesNotExist('dir')
3165
t = self.get_transport('dir/subdir')
3166
store = config.LockableIniFileStore(t, 'foo.conf')
3167
store.get_mutable_section(None).set('foo', 'bar')
3169
self.assertPathExists('dir/subdir')
3172
class TestConcurrentStoreUpdates(TestStore):
3173
"""Test that Stores properly handle conccurent updates.
3175
New Store implementation may fail some of these tests but until such
3176
implementations exist it's hard to properly filter them from the scenarios
3177
applied here. If you encounter such a case, contact the bzr devs.
3180
scenarios = [(key, {'get_stack': builder}) for key, builder
3181
in config.test_stack_builder_registry.iteritems()]
3184
super(TestConcurrentStoreUpdates, self).setUp()
3185
self.stack = self.get_stack(self)
3186
if not isinstance(self.stack, config._CompatibleStack):
3187
raise tests.TestNotApplicable(
3188
'%s is not meant to be compatible with the old config design'
3190
self.stack.set('one', '1')
3191
self.stack.set('two', '2')
3193
self.stack.store.save()
3195
def test_simple_read_access(self):
3196
self.assertEquals('1', self.stack.get('one'))
3198
def test_simple_write_access(self):
3199
self.stack.set('one', 'one')
3200
self.assertEquals('one', self.stack.get('one'))
3202
def test_listen_to_the_last_speaker(self):
3204
c2 = self.get_stack(self)
3205
c1.set('one', 'ONE')
3206
c2.set('two', 'TWO')
3207
self.assertEquals('ONE', c1.get('one'))
3208
self.assertEquals('TWO', c2.get('two'))
3209
# The second update respect the first one
3210
self.assertEquals('ONE', c2.get('one'))
3212
def test_last_speaker_wins(self):
3213
# If the same config is not shared, the same variable modified twice
3214
# can only see a single result.
3216
c2 = self.get_stack(self)
3219
self.assertEquals('c2', c2.get('one'))
3220
# The first modification is still available until another refresh
3222
self.assertEquals('c1', c1.get('one'))
3223
c1.set('two', 'done')
3224
self.assertEquals('c2', c1.get('one'))
3226
def test_writes_are_serialized(self):
3228
c2 = self.get_stack(self)
3230
# We spawn a thread that will pause *during* the config saving.
3231
before_writing = threading.Event()
3232
after_writing = threading.Event()
3233
writing_done = threading.Event()
3234
c1_save_without_locking_orig = c1.store.save_without_locking
3235
def c1_save_without_locking():
3236
before_writing.set()
3237
c1_save_without_locking_orig()
3238
# The lock is held. We wait for the main thread to decide when to
3240
after_writing.wait()
3241
c1.store.save_without_locking = c1_save_without_locking
3245
t1 = threading.Thread(target=c1_set)
3246
# Collect the thread after the test
3247
self.addCleanup(t1.join)
3248
# Be ready to unblock the thread if the test goes wrong
3249
self.addCleanup(after_writing.set)
3251
before_writing.wait()
3252
self.assertRaises(errors.LockContention,
3253
c2.set, 'one', 'c2')
3254
self.assertEquals('c1', c1.get('one'))
3255
# Let the lock be released
3259
self.assertEquals('c2', c2.get('one'))
3261
def test_read_while_writing(self):
3263
# We spawn a thread that will pause *during* the write
3264
ready_to_write = threading.Event()
3265
do_writing = threading.Event()
3266
writing_done = threading.Event()
3267
# We override the _save implementation so we know the store is locked
3268
c1_save_without_locking_orig = c1.store.save_without_locking
3269
def c1_save_without_locking():
3270
ready_to_write.set()
3271
# The lock is held. We wait for the main thread to decide when to
3274
c1_save_without_locking_orig()
3276
c1.store.save_without_locking = c1_save_without_locking
3279
t1 = threading.Thread(target=c1_set)
3280
# Collect the thread after the test
3281
self.addCleanup(t1.join)
3282
# Be ready to unblock the thread if the test goes wrong
3283
self.addCleanup(do_writing.set)
3285
# Ensure the thread is ready to write
3286
ready_to_write.wait()
3287
self.assertEquals('c1', c1.get('one'))
3288
# If we read during the write, we get the old value
3289
c2 = self.get_stack(self)
3290
self.assertEquals('1', c2.get('one'))
3291
# Let the writing occur and ensure it occurred
3294
# Now we get the updated value
3295
c3 = self.get_stack(self)
3296
self.assertEquals('c1', c3.get('one'))
3298
# FIXME: It may be worth looking into removing the lock dir when it's not
3299
# needed anymore and look at possible fallouts for concurrent lockers. This
3300
# will matter if/when we use config files outside of bazaar directories
3301
# (.bazaar or .bzr) -- vila 20110-04-111
3304
class TestSectionMatcher(TestStore):
3306
scenarios = [('location', {'matcher': config.LocationMatcher}),
3307
('id', {'matcher': config.NameMatcher}),]
3310
super(TestSectionMatcher, self).setUp()
3311
# Any simple store is good enough
3312
self.get_store = config.test_store_builder_registry.get('configobj')
3314
def test_no_matches_for_empty_stores(self):
3315
store = self.get_store(self)
3316
store._load_from_string('')
3317
matcher = self.matcher(store, '/bar')
3318
self.assertEquals([], list(matcher.get_sections()))
3320
def test_build_doesnt_load_store(self):
3321
store = self.get_store(self)
3322
matcher = self.matcher(store, '/bar')
3323
self.assertFalse(store.is_loaded())
3326
class TestLocationSection(tests.TestCase):
3328
def get_section(self, options, extra_path):
3329
section = config.Section('foo', options)
3330
return config.LocationSection(section, extra_path)
3332
def test_simple_option(self):
3333
section = self.get_section({'foo': 'bar'}, '')
3334
self.assertEquals('bar', section.get('foo'))
3336
def test_option_with_extra_path(self):
3337
section = self.get_section({'foo': 'bar', 'foo:policy': 'appendpath'},
3339
self.assertEquals('bar/baz', section.get('foo'))
3341
def test_invalid_policy(self):
3342
section = self.get_section({'foo': 'bar', 'foo:policy': 'die'},
3344
# invalid policies are ignored
3345
self.assertEquals('bar', section.get('foo'))
3348
class TestLocationMatcher(TestStore):
3351
super(TestLocationMatcher, self).setUp()
3352
# Any simple store is good enough
3353
self.get_store = config.test_store_builder_registry.get('configobj')
3355
def test_unrelated_section_excluded(self):
3356
store = self.get_store(self)
3357
store._load_from_string('''
3365
section=/foo/bar/baz
3369
self.assertEquals(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
3371
[section.id for _, section in store.get_sections()])
3372
matcher = config.LocationMatcher(store, '/foo/bar/quux')
3373
sections = [section for _, section in matcher.get_sections()]
3374
self.assertEquals(['/foo/bar', '/foo'],
3375
[section.id for section in sections])
3376
self.assertEquals(['quux', 'bar/quux'],
3377
[section.extra_path for section in sections])
3379
def test_more_specific_sections_first(self):
3380
store = self.get_store(self)
3381
store._load_from_string('''
3387
self.assertEquals(['/foo', '/foo/bar'],
3388
[section.id for _, section in store.get_sections()])
3389
matcher = config.LocationMatcher(store, '/foo/bar/baz')
3390
sections = [section for _, section in matcher.get_sections()]
3391
self.assertEquals(['/foo/bar', '/foo'],
3392
[section.id for section in sections])
3393
self.assertEquals(['baz', 'bar/baz'],
3394
[section.extra_path for section in sections])
3396
def test_appendpath_in_no_name_section(self):
3397
# It's a bit weird to allow appendpath in a no-name section, but
3398
# someone may found a use for it
3399
store = self.get_store(self)
3400
store._load_from_string('''
3402
foo:policy = appendpath
3404
matcher = config.LocationMatcher(store, 'dir/subdir')
3405
sections = list(matcher.get_sections())
3406
self.assertLength(1, sections)
3407
self.assertEquals('bar/dir/subdir', sections[0][1].get('foo'))
3409
def test_file_urls_are_normalized(self):
3410
store = self.get_store(self)
3411
if sys.platform == 'win32':
3412
expected_url = 'file:///C:/dir/subdir'
3413
expected_location = 'C:/dir/subdir'
3415
expected_url = 'file:///dir/subdir'
3416
expected_location = '/dir/subdir'
3417
matcher = config.LocationMatcher(store, expected_url)
3418
self.assertEquals(expected_location, matcher.location)
3421
class TestStartingPathMatcher(TestStore):
3424
super(TestStartingPathMatcher, self).setUp()
3425
# Any simple store is good enough
3426
self.store = config.IniFileStore()
3428
def assertSectionIDs(self, expected, location, content):
3429
self.store._load_from_string(content)
3430
matcher = config.StartingPathMatcher(self.store, location)
3431
sections = list(matcher.get_sections())
3432
self.assertLength(len(expected), sections)
3433
self.assertEqual(expected, [section.id for _, section in sections])
3436
def test_empty(self):
3437
self.assertSectionIDs([], self.get_url(), '')
3439
def test_url_vs_local_paths(self):
3440
# The matcher location is an url and the section names are local paths
3441
sections = self.assertSectionIDs(['/foo/bar', '/foo'],
3442
'file:///foo/bar/baz', '''\
3447
def test_local_path_vs_url(self):
3448
# The matcher location is a local path and the section names are urls
3449
sections = self.assertSectionIDs(['file:///foo/bar', 'file:///foo'],
3450
'/foo/bar/baz', '''\
3456
def test_no_name_section_included_when_present(self):
3457
# Note that other tests will cover the case where the no-name section
3458
# is empty and as such, not included.
3459
sections = self.assertSectionIDs(['/foo/bar', '/foo', None],
3460
'/foo/bar/baz', '''\
3461
option = defined so the no-name section exists
3465
self.assertEquals(['baz', 'bar/baz', '/foo/bar/baz'],
3466
[s.locals['relpath'] for _, s in sections])
3468
def test_order_reversed(self):
3469
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3474
def test_unrelated_section_excluded(self):
3475
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3481
def test_glob_included(self):
3482
sections = self.assertSectionIDs(['/foo/*/baz', '/foo/b*', '/foo'],
3483
'/foo/bar/baz', '''\
3489
# Note that 'baz' as a relpath for /foo/b* is not fully correct, but
3490
# nothing really is... as far using {relpath} to append it to something
3491
# else, this seems good enough though.
3492
self.assertEquals(['', 'baz', 'bar/baz'],
3493
[s.locals['relpath'] for _, s in sections])
3495
def test_respect_order(self):
3496
self.assertSectionIDs(['/foo', '/foo/b*', '/foo/*/baz'],
3497
'/foo/bar/baz', '''\
3505
class TestNameMatcher(TestStore):
3508
super(TestNameMatcher, self).setUp()
3509
self.matcher = config.NameMatcher
3510
# Any simple store is good enough
3511
self.get_store = config.test_store_builder_registry.get('configobj')
3513
def get_matching_sections(self, name):
3514
store = self.get_store(self)
3515
store._load_from_string('''
3523
matcher = self.matcher(store, name)
3524
return list(matcher.get_sections())
3526
def test_matching(self):
3527
sections = self.get_matching_sections('foo')
3528
self.assertLength(1, sections)
3529
self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
3531
def test_not_matching(self):
3532
sections = self.get_matching_sections('baz')
3533
self.assertLength(0, sections)
3536
class TestBaseStackGet(tests.TestCase):
3539
super(TestBaseStackGet, self).setUp()
3540
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3542
def test_get_first_definition(self):
3543
store1 = config.IniFileStore()
3544
store1._load_from_string('foo=bar')
3545
store2 = config.IniFileStore()
3546
store2._load_from_string('foo=baz')
3547
conf = config.Stack([store1.get_sections, store2.get_sections])
3548
self.assertEquals('bar', conf.get('foo'))
3550
def test_get_with_registered_default_value(self):
3551
config.option_registry.register(config.Option('foo', default='bar'))
3552
conf_stack = config.Stack([])
3553
self.assertEquals('bar', conf_stack.get('foo'))
3555
def test_get_without_registered_default_value(self):
3556
config.option_registry.register(config.Option('foo'))
3557
conf_stack = config.Stack([])
3558
self.assertEquals(None, conf_stack.get('foo'))
3560
def test_get_without_default_value_for_not_registered(self):
3561
conf_stack = config.Stack([])
3562
self.assertEquals(None, conf_stack.get('foo'))
3564
def test_get_for_empty_section_callable(self):
3565
conf_stack = config.Stack([lambda : []])
3566
self.assertEquals(None, conf_stack.get('foo'))
3568
def test_get_for_broken_callable(self):
3569
# Trying to use and invalid callable raises an exception on first use
3570
conf_stack = config.Stack([object])
3571
self.assertRaises(TypeError, conf_stack.get, 'foo')
3574
class TestStackWithSimpleStore(tests.TestCase):
3577
super(TestStackWithSimpleStore, self).setUp()
3578
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3579
self.registry = config.option_registry
3581
def get_conf(self, content=None):
3582
return config.MemoryStack(content)
3584
def test_override_value_from_env(self):
3585
self.registry.register(
3586
config.Option('foo', default='bar', override_from_env=['FOO']))
3587
self.overrideEnv('FOO', 'quux')
3588
# Env variable provides a default taking over the option one
3589
conf = self.get_conf('foo=store')
3590
self.assertEquals('quux', conf.get('foo'))
3592
def test_first_override_value_from_env_wins(self):
3593
self.registry.register(
3594
config.Option('foo', default='bar',
3595
override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
3596
self.overrideEnv('FOO', 'foo')
3597
self.overrideEnv('BAZ', 'baz')
3598
# The first env var set wins
3599
conf = self.get_conf('foo=store')
3600
self.assertEquals('foo', conf.get('foo'))
3603
class TestMemoryStack(tests.TestCase):
3606
conf = config.MemoryStack('foo=bar')
3607
self.assertEquals('bar', conf.get('foo'))
3610
conf = config.MemoryStack('foo=bar')
3611
conf.set('foo', 'baz')
3612
self.assertEquals('baz', conf.get('foo'))
3614
def test_no_content(self):
3615
conf = config.MemoryStack()
3616
# No content means no loading
3617
self.assertFalse(conf.store.is_loaded())
3618
self.assertRaises(NotImplementedError, conf.get, 'foo')
3619
# But a content can still be provided
3620
conf.store._load_from_string('foo=bar')
3621
self.assertEquals('bar', conf.get('foo'))
3624
class TestStackIterSections(tests.TestCase):
3626
def test_empty_stack(self):
3627
conf = config.Stack([])
3628
sections = list(conf.iter_sections())
3629
self.assertLength(0, sections)
3631
def test_empty_store(self):
3632
store = config.IniFileStore()
3633
store._load_from_string('')
3634
conf = config.Stack([store.get_sections])
3635
sections = list(conf.iter_sections())
3636
self.assertLength(0, sections)
3638
def test_simple_store(self):
3639
store = config.IniFileStore()
3640
store._load_from_string('foo=bar')
3641
conf = config.Stack([store.get_sections])
3642
tuples = list(conf.iter_sections())
3643
self.assertLength(1, tuples)
3644
(found_store, found_section) = tuples[0]
3645
self.assertIs(store, found_store)
3647
def test_two_stores(self):
3648
store1 = config.IniFileStore()
3649
store1._load_from_string('foo=bar')
3650
store2 = config.IniFileStore()
3651
store2._load_from_string('bar=qux')
3652
conf = config.Stack([store1.get_sections, store2.get_sections])
3653
tuples = list(conf.iter_sections())
3654
self.assertLength(2, tuples)
3655
self.assertIs(store1, tuples[0][0])
3656
self.assertIs(store2, tuples[1][0])
3659
class TestStackWithTransport(tests.TestCaseWithTransport):
3661
scenarios = [(key, {'get_stack': builder}) for key, builder
3662
in config.test_stack_builder_registry.iteritems()]
3665
class TestConcreteStacks(TestStackWithTransport):
3667
def test_build_stack(self):
3668
# Just a smoke test to help debug builders
3669
stack = self.get_stack(self)
3672
class TestStackGet(TestStackWithTransport):
3675
super(TestStackGet, self).setUp()
3676
self.conf = self.get_stack(self)
3678
def test_get_for_empty_stack(self):
3679
self.assertEquals(None, self.conf.get('foo'))
3681
def test_get_hook(self):
3682
self.conf.set('foo', 'bar')
3686
config.ConfigHooks.install_named_hook('get', hook, None)
3687
self.assertLength(0, calls)
3688
value = self.conf.get('foo')
3689
self.assertEquals('bar', value)
3690
self.assertLength(1, calls)
3691
self.assertEquals((self.conf, 'foo', 'bar'), calls[0])
3694
class TestStackGetWithConverter(tests.TestCase):
3697
super(TestStackGetWithConverter, self).setUp()
3698
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3699
self.registry = config.option_registry
3701
def get_conf(self, content=None):
3702
return config.MemoryStack(content)
3704
def register_bool_option(self, name, default=None, default_from_env=None):
3705
b = config.Option(name, help='A boolean.',
3706
default=default, default_from_env=default_from_env,
3707
from_unicode=config.bool_from_store)
3708
self.registry.register(b)
3710
def test_get_default_bool_None(self):
3711
self.register_bool_option('foo')
3712
conf = self.get_conf('')
3713
self.assertEquals(None, conf.get('foo'))
3715
def test_get_default_bool_True(self):
3716
self.register_bool_option('foo', u'True')
3717
conf = self.get_conf('')
3718
self.assertEquals(True, conf.get('foo'))
3720
def test_get_default_bool_False(self):
3721
self.register_bool_option('foo', False)
3722
conf = self.get_conf('')
3723
self.assertEquals(False, conf.get('foo'))
3725
def test_get_default_bool_False_as_string(self):
3726
self.register_bool_option('foo', u'False')
3727
conf = self.get_conf('')
3728
self.assertEquals(False, conf.get('foo'))
3730
def test_get_default_bool_from_env_converted(self):
3731
self.register_bool_option('foo', u'True', default_from_env=['FOO'])
3732
self.overrideEnv('FOO', 'False')
3733
conf = self.get_conf('')
3734
self.assertEquals(False, conf.get('foo'))
3736
def test_get_default_bool_when_conversion_fails(self):
3737
self.register_bool_option('foo', default='True')
3738
conf = self.get_conf('foo=invalid boolean')
3739
self.assertEquals(True, conf.get('foo'))
3741
def register_integer_option(self, name,
3742
default=None, default_from_env=None):
3743
i = config.Option(name, help='An integer.',
3744
default=default, default_from_env=default_from_env,
3745
from_unicode=config.int_from_store)
3746
self.registry.register(i)
3748
def test_get_default_integer_None(self):
3749
self.register_integer_option('foo')
3750
conf = self.get_conf('')
3751
self.assertEquals(None, conf.get('foo'))
3753
def test_get_default_integer(self):
3754
self.register_integer_option('foo', 42)
3755
conf = self.get_conf('')
3756
self.assertEquals(42, conf.get('foo'))
3758
def test_get_default_integer_as_string(self):
3759
self.register_integer_option('foo', u'42')
3760
conf = self.get_conf('')
3761
self.assertEquals(42, conf.get('foo'))
3763
def test_get_default_integer_from_env(self):
3764
self.register_integer_option('foo', default_from_env=['FOO'])
3765
self.overrideEnv('FOO', '18')
3766
conf = self.get_conf('')
3767
self.assertEquals(18, conf.get('foo'))
3769
def test_get_default_integer_when_conversion_fails(self):
3770
self.register_integer_option('foo', default='12')
3771
conf = self.get_conf('foo=invalid integer')
3772
self.assertEquals(12, conf.get('foo'))
3774
def register_list_option(self, name, default=None, default_from_env=None):
3775
l = config.ListOption(name, help='A list.', default=default,
3776
default_from_env=default_from_env)
3777
self.registry.register(l)
3779
def test_get_default_list_None(self):
3780
self.register_list_option('foo')
3781
conf = self.get_conf('')
3782
self.assertEquals(None, conf.get('foo'))
3784
def test_get_default_list_empty(self):
3785
self.register_list_option('foo', '')
3786
conf = self.get_conf('')
3787
self.assertEquals([], conf.get('foo'))
3789
def test_get_default_list_from_env(self):
3790
self.register_list_option('foo', default_from_env=['FOO'])
3791
self.overrideEnv('FOO', '')
3792
conf = self.get_conf('')
3793
self.assertEquals([], conf.get('foo'))
3795
def test_get_with_list_converter_no_item(self):
3796
self.register_list_option('foo', None)
3797
conf = self.get_conf('foo=,')
3798
self.assertEquals([], conf.get('foo'))
3800
def test_get_with_list_converter_many_items(self):
3801
self.register_list_option('foo', None)
3802
conf = self.get_conf('foo=m,o,r,e')
3803
self.assertEquals(['m', 'o', 'r', 'e'], conf.get('foo'))
3805
def test_get_with_list_converter_embedded_spaces_many_items(self):
3806
self.register_list_option('foo', None)
3807
conf = self.get_conf('foo=" bar", "baz "')
3808
self.assertEquals([' bar', 'baz '], conf.get('foo'))
3810
def test_get_with_list_converter_stripped_spaces_many_items(self):
3811
self.register_list_option('foo', None)
3812
conf = self.get_conf('foo= bar , baz ')
3813
self.assertEquals(['bar', 'baz'], conf.get('foo'))
3816
class TestIterOptionRefs(tests.TestCase):
3817
"""iter_option_refs is a bit unusual, document some cases."""
3819
def assertRefs(self, expected, string):
3820
self.assertEquals(expected, list(config.iter_option_refs(string)))
3822
def test_empty(self):
3823
self.assertRefs([(False, '')], '')
3825
def test_no_refs(self):
3826
self.assertRefs([(False, 'foo bar')], 'foo bar')
3828
def test_single_ref(self):
3829
self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
3831
def test_broken_ref(self):
3832
self.assertRefs([(False, '{foo')], '{foo')
3834
def test_embedded_ref(self):
3835
self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
3838
def test_two_refs(self):
3839
self.assertRefs([(False, ''), (True, '{foo}'),
3840
(False, ''), (True, '{bar}'),
3844
def test_newline_in_refs_are_not_matched(self):
3845
self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
3848
class TestStackExpandOptions(tests.TestCaseWithTransport):
3851
super(TestStackExpandOptions, self).setUp()
3852
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3853
self.registry = config.option_registry
3854
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3855
self.conf = config.Stack([store.get_sections], store)
3857
def assertExpansion(self, expected, string, env=None):
3858
self.assertEquals(expected, self.conf.expand_options(string, env))
3860
def test_no_expansion(self):
3861
self.assertExpansion('foo', 'foo')
3863
def test_expand_default_value(self):
3864
self.conf.store._load_from_string('bar=baz')
3865
self.registry.register(config.Option('foo', default=u'{bar}'))
3866
self.assertEquals('baz', self.conf.get('foo', expand=True))
3868
def test_expand_default_from_env(self):
3869
self.conf.store._load_from_string('bar=baz')
3870
self.registry.register(config.Option('foo', default_from_env=['FOO']))
3871
self.overrideEnv('FOO', '{bar}')
3872
self.assertEquals('baz', self.conf.get('foo', expand=True))
3874
def test_expand_default_on_failed_conversion(self):
3875
self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
3876
self.registry.register(
3877
config.Option('foo', default=u'{bar}',
3878
from_unicode=config.int_from_store))
3879
self.assertEquals(42, self.conf.get('foo', expand=True))
3881
def test_env_adding_options(self):
3882
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3884
def test_env_overriding_options(self):
3885
self.conf.store._load_from_string('foo=baz')
3886
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3888
def test_simple_ref(self):
3889
self.conf.store._load_from_string('foo=xxx')
3890
self.assertExpansion('xxx', '{foo}')
3892
def test_unknown_ref(self):
3893
self.assertRaises(errors.ExpandingUnknownOption,
3894
self.conf.expand_options, '{foo}')
3896
def test_indirect_ref(self):
3897
self.conf.store._load_from_string('''
3901
self.assertExpansion('xxx', '{bar}')
3903
def test_embedded_ref(self):
3904
self.conf.store._load_from_string('''
3908
self.assertExpansion('xxx', '{{bar}}')
3910
def test_simple_loop(self):
3911
self.conf.store._load_from_string('foo={foo}')
3912
self.assertRaises(errors.OptionExpansionLoop,
3913
self.conf.expand_options, '{foo}')
3915
def test_indirect_loop(self):
3916
self.conf.store._load_from_string('''
3920
e = self.assertRaises(errors.OptionExpansionLoop,
3921
self.conf.expand_options, '{foo}')
3922
self.assertEquals('foo->bar->baz', e.refs)
3923
self.assertEquals('{foo}', e.string)
3925
def test_list(self):
3926
self.conf.store._load_from_string('''
3930
list={foo},{bar},{baz}
3932
self.registry.register(
3933
config.ListOption('list'))
3934
self.assertEquals(['start', 'middle', 'end'],
3935
self.conf.get('list', expand=True))
3937
def test_cascading_list(self):
3938
self.conf.store._load_from_string('''
3944
self.registry.register(config.ListOption('list'))
3945
# Register an intermediate option as a list to ensure no conversion
3946
# happen while expanding. Conversion should only occur for the original
3947
# option ('list' here).
3948
self.registry.register(config.ListOption('baz'))
3949
self.assertEquals(['start', 'middle', 'end'],
3950
self.conf.get('list', expand=True))
3952
def test_pathologically_hidden_list(self):
3953
self.conf.store._load_from_string('''
3959
hidden={start}{middle}{end}
3961
# What matters is what the registration says, the conversion happens
3962
# only after all expansions have been performed
3963
self.registry.register(config.ListOption('hidden'))
3964
self.assertEquals(['bin', 'go'],
3965
self.conf.get('hidden', expand=True))
3968
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
3971
super(TestStackCrossSectionsExpand, self).setUp()
3973
def get_config(self, location, string):
3976
# Since we don't save the config we won't strictly require to inherit
3977
# from TestCaseInTempDir, but an error occurs so quickly...
3978
c = config.LocationStack(location)
3979
c.store._load_from_string(string)
3982
def test_dont_cross_unrelated_section(self):
3983
c = self.get_config('/another/branch/path','''
3988
[/another/branch/path]
3991
self.assertRaises(errors.ExpandingUnknownOption,
3992
c.get, 'bar', expand=True)
3994
def test_cross_related_sections(self):
3995
c = self.get_config('/project/branch/path','''
3999
[/project/branch/path]
4002
self.assertEquals('quux', c.get('bar', expand=True))
4005
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
4007
def test_cross_global_locations(self):
4008
l_store = config.LocationStore()
4009
l_store._load_from_string('''
4015
g_store = config.GlobalStore()
4016
g_store._load_from_string('''
4022
stack = config.LocationStack('/branch')
4023
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4024
self.assertEquals('loc-foo', stack.get('gfoo', expand=True))
4027
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
4029
def test_expand_locals_empty(self):
4030
l_store = config.LocationStore()
4031
l_store._load_from_string('''
4032
[/home/user/project]
4037
stack = config.LocationStack('/home/user/project/')
4038
self.assertEquals('', stack.get('base', expand=True))
4039
self.assertEquals('', stack.get('rel', expand=True))
4041
def test_expand_basename_locally(self):
4042
l_store = config.LocationStore()
4043
l_store._load_from_string('''
4044
[/home/user/project]
4048
stack = config.LocationStack('/home/user/project/branch')
4049
self.assertEquals('branch', stack.get('bfoo', expand=True))
4051
def test_expand_basename_locally_longer_path(self):
4052
l_store = config.LocationStore()
4053
l_store._load_from_string('''
4058
stack = config.LocationStack('/home/user/project/dir/branch')
4059
self.assertEquals('branch', stack.get('bfoo', expand=True))
4061
def test_expand_relpath_locally(self):
4062
l_store = config.LocationStore()
4063
l_store._load_from_string('''
4064
[/home/user/project]
4065
lfoo = loc-foo/{relpath}
4068
stack = config.LocationStack('/home/user/project/branch')
4069
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4071
def test_expand_relpath_unknonw_in_global(self):
4072
g_store = config.GlobalStore()
4073
g_store._load_from_string('''
4078
stack = config.LocationStack('/home/user/project/branch')
4079
self.assertRaises(errors.ExpandingUnknownOption,
4080
stack.get, 'gfoo', expand=True)
4082
def test_expand_local_option_locally(self):
4083
l_store = config.LocationStore()
4084
l_store._load_from_string('''
4085
[/home/user/project]
4086
lfoo = loc-foo/{relpath}
4090
g_store = config.GlobalStore()
4091
g_store._load_from_string('''
4097
stack = config.LocationStack('/home/user/project/branch')
4098
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4099
self.assertEquals('loc-foo/branch', stack.get('gfoo', expand=True))
4101
def test_locals_dont_leak(self):
4102
"""Make sure we chose the right local in presence of several sections.
4104
l_store = config.LocationStore()
4105
l_store._load_from_string('''
4107
lfoo = loc-foo/{relpath}
4108
[/home/user/project]
4109
lfoo = loc-foo/{relpath}
4112
stack = config.LocationStack('/home/user/project/branch')
4113
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4114
stack = config.LocationStack('/home/user/bar/baz')
4115
self.assertEquals('loc-foo/bar/baz', stack.get('lfoo', expand=True))
4119
class TestStackSet(TestStackWithTransport):
4121
def test_simple_set(self):
4122
conf = self.get_stack(self)
4123
self.assertEquals(None, conf.get('foo'))
4124
conf.set('foo', 'baz')
4125
# Did we get it back ?
4126
self.assertEquals('baz', conf.get('foo'))
4128
def test_set_creates_a_new_section(self):
4129
conf = self.get_stack(self)
4130
conf.set('foo', 'baz')
4131
self.assertEquals, 'baz', conf.get('foo')
4133
def test_set_hook(self):
4137
config.ConfigHooks.install_named_hook('set', hook, None)
4138
self.assertLength(0, calls)
4139
conf = self.get_stack(self)
4140
conf.set('foo', 'bar')
4141
self.assertLength(1, calls)
4142
self.assertEquals((conf, 'foo', 'bar'), calls[0])
4145
class TestStackRemove(TestStackWithTransport):
4147
def test_remove_existing(self):
4148
conf = self.get_stack(self)
4149
conf.set('foo', 'bar')
4150
self.assertEquals('bar', conf.get('foo'))
4152
# Did we get it back ?
4153
self.assertEquals(None, conf.get('foo'))
4155
def test_remove_unknown(self):
4156
conf = self.get_stack(self)
4157
self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
4159
def test_remove_hook(self):
4163
config.ConfigHooks.install_named_hook('remove', hook, None)
4164
self.assertLength(0, calls)
4165
conf = self.get_stack(self)
4166
conf.set('foo', 'bar')
4168
self.assertLength(1, calls)
4169
self.assertEquals((conf, 'foo'), calls[0])
4172
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
4175
super(TestConfigGetOptions, self).setUp()
4176
create_configs(self)
4178
def test_no_variable(self):
4179
# Using branch should query branch, locations and bazaar
4180
self.assertOptions([], self.branch_config)
4182
def test_option_in_bazaar(self):
4183
self.bazaar_config.set_user_option('file', 'bazaar')
4184
self.assertOptions([('file', 'bazaar', 'DEFAULT', 'bazaar')],
4187
def test_option_in_locations(self):
4188
self.locations_config.set_user_option('file', 'locations')
4190
[('file', 'locations', self.tree.basedir, 'locations')],
4191
self.locations_config)
4193
def test_option_in_branch(self):
4194
self.branch_config.set_user_option('file', 'branch')
4195
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch')],
4198
def test_option_in_bazaar_and_branch(self):
4199
self.bazaar_config.set_user_option('file', 'bazaar')
4200
self.branch_config.set_user_option('file', 'branch')
4201
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch'),
4202
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4205
def test_option_in_branch_and_locations(self):
4206
# Hmm, locations override branch :-/
4207
self.locations_config.set_user_option('file', 'locations')
4208
self.branch_config.set_user_option('file', 'branch')
4210
[('file', 'locations', self.tree.basedir, 'locations'),
4211
('file', 'branch', 'DEFAULT', 'branch'),],
4214
def test_option_in_bazaar_locations_and_branch(self):
4215
self.bazaar_config.set_user_option('file', 'bazaar')
4216
self.locations_config.set_user_option('file', 'locations')
4217
self.branch_config.set_user_option('file', 'branch')
4219
[('file', 'locations', self.tree.basedir, 'locations'),
4220
('file', 'branch', 'DEFAULT', 'branch'),
4221
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4225
class TestConfigRemoveOption(tests.TestCaseWithTransport, TestOptionsMixin):
4228
super(TestConfigRemoveOption, self).setUp()
4229
create_configs_with_file_option(self)
4231
def test_remove_in_locations(self):
4232
self.locations_config.remove_user_option('file', self.tree.basedir)
4234
[('file', 'branch', 'DEFAULT', 'branch'),
4235
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4238
def test_remove_in_branch(self):
4239
self.branch_config.remove_user_option('file')
4241
[('file', 'locations', self.tree.basedir, 'locations'),
4242
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4245
def test_remove_in_bazaar(self):
4246
self.bazaar_config.remove_user_option('file')
4248
[('file', 'locations', self.tree.basedir, 'locations'),
4249
('file', 'branch', 'DEFAULT', 'branch'),],
4253
class TestConfigGetSections(tests.TestCaseWithTransport):
4256
super(TestConfigGetSections, self).setUp()
4257
create_configs(self)
4259
def assertSectionNames(self, expected, conf, name=None):
4260
"""Check which sections are returned for a given config.
4262
If fallback configurations exist their sections can be included.
4264
:param expected: A list of section names.
4266
:param conf: The configuration that will be queried.
4268
:param name: An optional section name that will be passed to
4271
sections = list(conf._get_sections(name))
4272
self.assertLength(len(expected), sections)
4273
self.assertEqual(expected, [name for name, _, _ in sections])
4275
def test_bazaar_default_section(self):
4276
self.assertSectionNames(['DEFAULT'], self.bazaar_config)
4278
def test_locations_default_section(self):
4279
# No sections are defined in an empty file
4280
self.assertSectionNames([], self.locations_config)
4282
def test_locations_named_section(self):
4283
self.locations_config.set_user_option('file', 'locations')
4284
self.assertSectionNames([self.tree.basedir], self.locations_config)
4286
def test_locations_matching_sections(self):
4287
loc_config = self.locations_config
4288
loc_config.set_user_option('file', 'locations')
4289
# We need to cheat a bit here to create an option in sections above and
4290
# below the 'location' one.
4291
parser = loc_config._get_parser()
4292
# locations.cong deals with '/' ignoring native os.sep
4293
location_names = self.tree.basedir.split('/')
4294
parent = '/'.join(location_names[:-1])
4295
child = '/'.join(location_names + ['child'])
4297
parser[parent]['file'] = 'parent'
4299
parser[child]['file'] = 'child'
4300
self.assertSectionNames([self.tree.basedir, parent], loc_config)
4302
def test_branch_data_default_section(self):
4303
self.assertSectionNames([None],
4304
self.branch_config._get_branch_data_config())
4306
def test_branch_default_sections(self):
4307
# No sections are defined in an empty locations file
4308
self.assertSectionNames([None, 'DEFAULT'],
4310
# Unless we define an option
4311
self.branch_config._get_location_config().set_user_option(
4312
'file', 'locations')
4313
self.assertSectionNames([self.tree.basedir, None, 'DEFAULT'],
4316
def test_bazaar_named_section(self):
4317
# We need to cheat as the API doesn't give direct access to sections
4318
# other than DEFAULT.
4319
self.bazaar_config.set_alias('bazaar', 'bzr')
4320
self.assertSectionNames(['ALIASES'], self.bazaar_config, 'ALIASES')
4323
class TestAuthenticationConfigFile(tests.TestCase):
4324
"""Test the authentication.conf file matching"""
4326
def _got_user_passwd(self, expected_user, expected_password,
4327
config, *args, **kwargs):
4328
credentials = config.get_credentials(*args, **kwargs)
4329
if credentials is None:
4333
user = credentials['user']
4334
password = credentials['password']
4335
self.assertEquals(expected_user, user)
4336
self.assertEquals(expected_password, password)
4338
def test_empty_config(self):
4339
conf = config.AuthenticationConfig(_file=StringIO())
4340
self.assertEquals({}, conf._get_config())
4341
self._got_user_passwd(None, None, conf, 'http', 'foo.net')
4343
def test_non_utf8_config(self):
4344
conf = config.AuthenticationConfig(_file=StringIO(
4346
self.assertRaises(errors.ConfigContentError, conf._get_config)
4348
def test_missing_auth_section_header(self):
4349
conf = config.AuthenticationConfig(_file=StringIO('foo = bar'))
4350
self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
4352
def test_auth_section_header_not_closed(self):
4353
conf = config.AuthenticationConfig(_file=StringIO('[DEF'))
4354
self.assertRaises(errors.ParseConfigError, conf._get_config)
4356
def test_auth_value_not_boolean(self):
4357
conf = config.AuthenticationConfig(_file=StringIO(
4361
verify_certificates=askme # Error: Not a boolean
4363
self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
4365
def test_auth_value_not_int(self):
4366
conf = config.AuthenticationConfig(_file=StringIO(
4370
port=port # Error: Not an int
4372
self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
4374
def test_unknown_password_encoding(self):
4375
conf = config.AuthenticationConfig(_file=StringIO(
4379
password_encoding=unknown
4381
self.assertRaises(ValueError, conf.get_password,
4382
'ftp', 'foo.net', 'joe')
4384
def test_credentials_for_scheme_host(self):
4385
conf = config.AuthenticationConfig(_file=StringIO(
4386
"""# Identity on foo.net
4391
password=secret-pass
4394
self._got_user_passwd('joe', 'secret-pass', conf, 'ftp', 'foo.net')
4396
self._got_user_passwd(None, None, conf, 'http', 'foo.net')
4398
self._got_user_passwd(None, None, conf, 'ftp', 'bar.net')
4400
def test_credentials_for_host_port(self):
4401
conf = config.AuthenticationConfig(_file=StringIO(
4402
"""# Identity on foo.net
4408
password=secret-pass
4411
self._got_user_passwd('joe', 'secret-pass',
4412
conf, 'ftp', 'foo.net', port=10021)
4414
self._got_user_passwd(None, None, conf, 'ftp', 'foo.net')
4416
def test_for_matching_host(self):
4417
conf = config.AuthenticationConfig(_file=StringIO(
4418
"""# Identity on foo.net
4424
[sourceforge domain]
4431
self._got_user_passwd('georges', 'bendover',
4432
conf, 'bzr', 'foo.bzr.sf.net')
4434
self._got_user_passwd(None, None,
4435
conf, 'bzr', 'bbzr.sf.net')
4437
def test_for_matching_host_None(self):
4438
conf = config.AuthenticationConfig(_file=StringIO(
4439
"""# Identity on foo.net
4449
self._got_user_passwd('joe', 'joepass',
4450
conf, 'bzr', 'quux.net')
4451
# no host but different scheme
4452
self._got_user_passwd('georges', 'bendover',
4453
conf, 'ftp', 'quux.net')
4455
def test_credentials_for_path(self):
4456
conf = config.AuthenticationConfig(_file=StringIO(
4472
self._got_user_passwd(None, None,
4473
conf, 'http', host='bar.org', path='/dir3')
4475
self._got_user_passwd('georges', 'bendover',
4476
conf, 'http', host='bar.org', path='/dir2')
4478
self._got_user_passwd('jim', 'jimpass',
4479
conf, 'http', host='bar.org',path='/dir1/subdir')
4481
def test_credentials_for_user(self):
4482
conf = config.AuthenticationConfig(_file=StringIO(
4491
self._got_user_passwd('jim', 'jimpass',
4492
conf, 'http', 'bar.org')
4494
self._got_user_passwd('jim', 'jimpass',
4495
conf, 'http', 'bar.org', user='jim')
4496
# Don't get a different user if one is specified
4497
self._got_user_passwd(None, None,
4498
conf, 'http', 'bar.org', user='georges')
4500
def test_credentials_for_user_without_password(self):
4501
conf = config.AuthenticationConfig(_file=StringIO(
4508
# Get user but no password
4509
self._got_user_passwd('jim', None,
4510
conf, 'http', 'bar.org')
4512
def test_verify_certificates(self):
4513
conf = config.AuthenticationConfig(_file=StringIO(
4520
verify_certificates=False
4527
credentials = conf.get_credentials('https', 'bar.org')
4528
self.assertEquals(False, credentials.get('verify_certificates'))
4529
credentials = conf.get_credentials('https', 'foo.net')
4530
self.assertEquals(True, credentials.get('verify_certificates'))
4533
class TestAuthenticationStorage(tests.TestCaseInTempDir):
4535
def test_set_credentials(self):
4536
conf = config.AuthenticationConfig()
4537
conf.set_credentials('name', 'host', 'user', 'scheme', 'password',
4538
99, path='/foo', verify_certificates=False, realm='realm')
4539
credentials = conf.get_credentials(host='host', scheme='scheme',
4540
port=99, path='/foo',
4542
CREDENTIALS = {'name': 'name', 'user': 'user', 'password': 'password',
4543
'verify_certificates': False, 'scheme': 'scheme',
4544
'host': 'host', 'port': 99, 'path': '/foo',
4546
self.assertEqual(CREDENTIALS, credentials)
4547
credentials_from_disk = config.AuthenticationConfig().get_credentials(
4548
host='host', scheme='scheme', port=99, path='/foo', realm='realm')
4549
self.assertEqual(CREDENTIALS, credentials_from_disk)
4551
def test_reset_credentials_different_name(self):
4552
conf = config.AuthenticationConfig()
4553
conf.set_credentials('name', 'host', 'user', 'scheme', 'password'),
4554
conf.set_credentials('name2', 'host', 'user2', 'scheme', 'password'),
4555
self.assertIs(None, conf._get_config().get('name'))
4556
credentials = conf.get_credentials(host='host', scheme='scheme')
4557
CREDENTIALS = {'name': 'name2', 'user': 'user2', 'password':
4558
'password', 'verify_certificates': True,
4559
'scheme': 'scheme', 'host': 'host', 'port': None,
4560
'path': None, 'realm': None}
4561
self.assertEqual(CREDENTIALS, credentials)
4564
class TestAuthenticationConfig(tests.TestCase):
4565
"""Test AuthenticationConfig behaviour"""
4567
def _check_default_password_prompt(self, expected_prompt_format, scheme,
4568
host=None, port=None, realm=None,
4572
user, password = 'jim', 'precious'
4573
expected_prompt = expected_prompt_format % {
4574
'scheme': scheme, 'host': host, 'port': port,
4575
'user': user, 'realm': realm}
4577
stdout = tests.StringIOWrapper()
4578
stderr = tests.StringIOWrapper()
4579
ui.ui_factory = tests.TestUIFactory(stdin=password + '\n',
4580
stdout=stdout, stderr=stderr)
4581
# We use an empty conf so that the user is always prompted
4582
conf = config.AuthenticationConfig()
4583
self.assertEquals(password,
4584
conf.get_password(scheme, host, user, port=port,
4585
realm=realm, path=path))
4586
self.assertEquals(expected_prompt, stderr.getvalue())
4587
self.assertEquals('', stdout.getvalue())
4589
def _check_default_username_prompt(self, expected_prompt_format, scheme,
4590
host=None, port=None, realm=None,
4595
expected_prompt = expected_prompt_format % {
4596
'scheme': scheme, 'host': host, 'port': port,
4598
stdout = tests.StringIOWrapper()
4599
stderr = tests.StringIOWrapper()
4600
ui.ui_factory = tests.TestUIFactory(stdin=username+ '\n',
4601
stdout=stdout, stderr=stderr)
4602
# We use an empty conf so that the user is always prompted
4603
conf = config.AuthenticationConfig()
4604
self.assertEquals(username, conf.get_user(scheme, host, port=port,
4605
realm=realm, path=path, ask=True))
4606
self.assertEquals(expected_prompt, stderr.getvalue())
4607
self.assertEquals('', stdout.getvalue())
4609
def test_username_defaults_prompts(self):
4610
# HTTP prompts can't be tested here, see test_http.py
4611
self._check_default_username_prompt(u'FTP %(host)s username: ', 'ftp')
4612
self._check_default_username_prompt(
4613
u'FTP %(host)s:%(port)d username: ', 'ftp', port=10020)
4614
self._check_default_username_prompt(
4615
u'SSH %(host)s:%(port)d username: ', 'ssh', port=12345)
4617
def test_username_default_no_prompt(self):
4618
conf = config.AuthenticationConfig()
4619
self.assertEquals(None,
4620
conf.get_user('ftp', 'example.com'))
4621
self.assertEquals("explicitdefault",
4622
conf.get_user('ftp', 'example.com', default="explicitdefault"))
4624
def test_password_default_prompts(self):
4625
# HTTP prompts can't be tested here, see test_http.py
4626
self._check_default_password_prompt(
4627
u'FTP %(user)s@%(host)s password: ', 'ftp')
4628
self._check_default_password_prompt(
4629
u'FTP %(user)s@%(host)s:%(port)d password: ', 'ftp', port=10020)
4630
self._check_default_password_prompt(
4631
u'SSH %(user)s@%(host)s:%(port)d password: ', 'ssh', port=12345)
4632
# SMTP port handling is a bit special (it's handled if embedded in the
4634
# FIXME: should we: forbid that, extend it to other schemes, leave
4635
# things as they are that's fine thank you ?
4636
self._check_default_password_prompt(
4637
u'SMTP %(user)s@%(host)s password: ', 'smtp')
4638
self._check_default_password_prompt(
4639
u'SMTP %(user)s@%(host)s password: ', 'smtp', host='bar.org:10025')
4640
self._check_default_password_prompt(
4641
u'SMTP %(user)s@%(host)s:%(port)d password: ', 'smtp', port=10025)
4643
def test_ssh_password_emits_warning(self):
4644
conf = config.AuthenticationConfig(_file=StringIO(
4652
entered_password = 'typed-by-hand'
4653
stdout = tests.StringIOWrapper()
4654
stderr = tests.StringIOWrapper()
4655
ui.ui_factory = tests.TestUIFactory(stdin=entered_password + '\n',
4656
stdout=stdout, stderr=stderr)
4658
# Since the password defined in the authentication config is ignored,
4659
# the user is prompted
4660
self.assertEquals(entered_password,
4661
conf.get_password('ssh', 'bar.org', user='jim'))
4662
self.assertContainsRe(
4664
'password ignored in section \[ssh with password\]')
4666
def test_ssh_without_password_doesnt_emit_warning(self):
4667
conf = config.AuthenticationConfig(_file=StringIO(
4674
entered_password = 'typed-by-hand'
4675
stdout = tests.StringIOWrapper()
4676
stderr = tests.StringIOWrapper()
4677
ui.ui_factory = tests.TestUIFactory(stdin=entered_password + '\n',
4681
# Since the password defined in the authentication config is ignored,
4682
# the user is prompted
4683
self.assertEquals(entered_password,
4684
conf.get_password('ssh', 'bar.org', user='jim'))
4685
# No warning shoud be emitted since there is no password. We are only
4687
self.assertNotContainsRe(
4689
'password ignored in section \[ssh with password\]')
4691
def test_uses_fallback_stores(self):
4692
self.overrideAttr(config, 'credential_store_registry',
4693
config.CredentialStoreRegistry())
4694
store = StubCredentialStore()
4695
store.add_credentials("http", "example.com", "joe", "secret")
4696
config.credential_store_registry.register("stub", store, fallback=True)
4697
conf = config.AuthenticationConfig(_file=StringIO())
4698
creds = conf.get_credentials("http", "example.com")
4699
self.assertEquals("joe", creds["user"])
4700
self.assertEquals("secret", creds["password"])
4703
class StubCredentialStore(config.CredentialStore):
4709
def add_credentials(self, scheme, host, user, password=None):
4710
self._username[(scheme, host)] = user
4711
self._password[(scheme, host)] = password
4713
def get_credentials(self, scheme, host, port=None, user=None,
4714
path=None, realm=None):
4715
key = (scheme, host)
4716
if not key in self._username:
4718
return { "scheme": scheme, "host": host, "port": port,
4719
"user": self._username[key], "password": self._password[key]}
4722
class CountingCredentialStore(config.CredentialStore):
4727
def get_credentials(self, scheme, host, port=None, user=None,
4728
path=None, realm=None):
4733
class TestCredentialStoreRegistry(tests.TestCase):
4735
def _get_cs_registry(self):
4736
return config.credential_store_registry
4738
def test_default_credential_store(self):
4739
r = self._get_cs_registry()
4740
default = r.get_credential_store(None)
4741
self.assertIsInstance(default, config.PlainTextCredentialStore)
4743
def test_unknown_credential_store(self):
4744
r = self._get_cs_registry()
4745
# It's hard to imagine someone creating a credential store named
4746
# 'unknown' so we use that as an never registered key.
4747
self.assertRaises(KeyError, r.get_credential_store, 'unknown')
4749
def test_fallback_none_registered(self):
4750
r = config.CredentialStoreRegistry()
4751
self.assertEquals(None,
4752
r.get_fallback_credentials("http", "example.com"))
4754
def test_register(self):
4755
r = config.CredentialStoreRegistry()
4756
r.register("stub", StubCredentialStore(), fallback=False)
4757
r.register("another", StubCredentialStore(), fallback=True)
4758
self.assertEquals(["another", "stub"], r.keys())
4760
def test_register_lazy(self):
4761
r = config.CredentialStoreRegistry()
4762
r.register_lazy("stub", "bzrlib.tests.test_config",
4763
"StubCredentialStore", fallback=False)
4764
self.assertEquals(["stub"], r.keys())
4765
self.assertIsInstance(r.get_credential_store("stub"),
4766
StubCredentialStore)
4768
def test_is_fallback(self):
4769
r = config.CredentialStoreRegistry()
4770
r.register("stub1", None, fallback=False)
4771
r.register("stub2", None, fallback=True)
4772
self.assertEquals(False, r.is_fallback("stub1"))
4773
self.assertEquals(True, r.is_fallback("stub2"))
4775
def test_no_fallback(self):
4776
r = config.CredentialStoreRegistry()
4777
store = CountingCredentialStore()
4778
r.register("count", store, fallback=False)
4779
self.assertEquals(None,
4780
r.get_fallback_credentials("http", "example.com"))
4781
self.assertEquals(0, store._calls)
4783
def test_fallback_credentials(self):
4784
r = config.CredentialStoreRegistry()
4785
store = StubCredentialStore()
4786
store.add_credentials("http", "example.com",
4787
"somebody", "geheim")
4788
r.register("stub", store, fallback=True)
4789
creds = r.get_fallback_credentials("http", "example.com")
4790
self.assertEquals("somebody", creds["user"])
4791
self.assertEquals("geheim", creds["password"])
4793
def test_fallback_first_wins(self):
4794
r = config.CredentialStoreRegistry()
4795
stub1 = StubCredentialStore()
4796
stub1.add_credentials("http", "example.com",
4797
"somebody", "stub1")
4798
r.register("stub1", stub1, fallback=True)
4799
stub2 = StubCredentialStore()
4800
stub2.add_credentials("http", "example.com",
4801
"somebody", "stub2")
4802
r.register("stub2", stub1, fallback=True)
4803
creds = r.get_fallback_credentials("http", "example.com")
4804
self.assertEquals("somebody", creds["user"])
4805
self.assertEquals("stub1", creds["password"])
4808
class TestPlainTextCredentialStore(tests.TestCase):
4810
def test_decode_password(self):
4811
r = config.credential_store_registry
4812
plain_text = r.get_credential_store()
4813
decoded = plain_text.decode_password(dict(password='secret'))
4814
self.assertEquals('secret', decoded)
4817
# FIXME: Once we have a way to declare authentication to all test servers, we
4818
# can implement generic tests.
4819
# test_user_password_in_url
4820
# test_user_in_url_password_from_config
4821
# test_user_in_url_password_prompted
4822
# test_user_in_config
4823
# test_user_getpass.getuser
4824
# test_user_prompted ?
4825
class TestAuthenticationRing(tests.TestCaseWithTransport):
4829
class TestAutoUserId(tests.TestCase):
4830
"""Test inferring an automatic user name."""
4832
def test_auto_user_id(self):
4833
"""Automatic inference of user name.
4835
This is a bit hard to test in an isolated way, because it depends on
4836
system functions that go direct to /etc or perhaps somewhere else.
4837
But it's reasonable to say that on Unix, with an /etc/mailname, we ought
4838
to be able to choose a user name with no configuration.
4840
if sys.platform == 'win32':
4841
raise tests.TestSkipped(
4842
"User name inference not implemented on win32")
4843
realname, address = config._auto_user_id()
4844
if os.path.exists('/etc/mailname'):
4845
self.assertIsNot(None, realname)
4846
self.assertIsNot(None, address)
4848
self.assertEquals((None, None), (realname, address))
4851
class EmailOptionTests(tests.TestCase):
4853
def test_default_email_uses_BZR_EMAIL(self):
4854
conf = config.MemoryStack('email=jelmer@debian.org')
4855
# BZR_EMAIL takes precedence over EMAIL
4856
self.overrideEnv('BZR_EMAIL', 'jelmer@samba.org')
4857
self.overrideEnv('EMAIL', 'jelmer@apache.org')
4858
self.assertEquals('jelmer@samba.org', conf.get('email'))
4860
def test_default_email_uses_EMAIL(self):
4861
conf = config.MemoryStack('')
4862
self.overrideEnv('BZR_EMAIL', None)
4863
self.overrideEnv('EMAIL', 'jelmer@apache.org')
4864
self.assertEquals('jelmer@apache.org', conf.get('email'))
4866
def test_BZR_EMAIL_overrides(self):
4867
conf = config.MemoryStack('email=jelmer@debian.org')
4868
self.overrideEnv('BZR_EMAIL', 'jelmer@apache.org')
4869
self.assertEquals('jelmer@apache.org', conf.get('email'))
4870
self.overrideEnv('BZR_EMAIL', None)
4871
self.overrideEnv('EMAIL', 'jelmer@samba.org')
4872
self.assertEquals('jelmer@debian.org', conf.get('email'))
4875
class MailClientOptionTests(tests.TestCase):
4877
def test_default(self):
4878
conf = config.MemoryStack('')
4879
client = conf.get('mail_client')
4880
self.assertIs(client, mail_client.DefaultMail)
4882
def test_evolution(self):
4883
conf = config.MemoryStack('mail_client=evolution')
4884
client = conf.get('mail_client')
4885
self.assertIs(client, mail_client.Evolution)
4887
def test_kmail(self):
4888
conf = config.MemoryStack('mail_client=kmail')
4889
client = conf.get('mail_client')
4890
self.assertIs(client, mail_client.KMail)
4892
def test_mutt(self):
4893
conf = config.MemoryStack('mail_client=mutt')
4894
client = conf.get('mail_client')
4895
self.assertIs(client, mail_client.Mutt)
4897
def test_thunderbird(self):
4898
conf = config.MemoryStack('mail_client=thunderbird')
4899
client = conf.get('mail_client')
4900
self.assertIs(client, mail_client.Thunderbird)
4902
def test_explicit_default(self):
4903
conf = config.MemoryStack('mail_client=default')
4904
client = conf.get('mail_client')
4905
self.assertIs(client, mail_client.DefaultMail)
4907
def test_editor(self):
4908
conf = config.MemoryStack('mail_client=editor')
4909
client = conf.get('mail_client')
4910
self.assertIs(client, mail_client.Editor)
4912
def test_mapi(self):
4913
conf = config.MemoryStack('mail_client=mapi')
4914
client = conf.get('mail_client')
4915
self.assertIs(client, mail_client.MAPIClient)
4917
def test_xdg_email(self):
4918
conf = config.MemoryStack('mail_client=xdg-email')
4919
client = conf.get('mail_client')
4920
self.assertIs(client, mail_client.XDGEmail)
4922
def test_unknown(self):
4923
conf = config.MemoryStack('mail_client=firebird')
4924
self.assertRaises(errors.ConfigOptionValueError, conf.get,