1
# Copyright (C) 2005-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for finding and reading the bzr config file[s]."""
18
# import system imports here
19
from cStringIO import StringIO
25
from testtools import matchers
27
#import bzrlib specific imports here
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 = bzrdir.BzrDir.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 TestIniConfigOptionExpansionDefaultValue(tests.TestCaseInTempDir):
693
"""What is the default value of expand for config options.
695
This is an opt-in beta feature used to evaluate whether or not option
696
references can appear in dangerous place raising exceptions, disapearing
697
(and as such corrupting data) or if it's safe to activate the option by
700
Note that these tests relies on config._expand_default_value being already
701
overwritten in the parent class setUp.
705
super(TestIniConfigOptionExpansionDefaultValue, self).setUp()
709
self.warnings.append(args[0] % args[1:])
710
self.overrideAttr(trace, 'warning', warning)
712
def get_config(self, expand):
713
c = config.GlobalConfig.from_string('bzr.config.expand=%s' % (expand,),
717
def assertExpandIs(self, expected):
718
actual = config._get_expand_default_value()
719
#self.config.get_user_option_as_bool('bzr.config.expand')
720
self.assertEquals(expected, actual)
722
def test_default_is_None(self):
723
self.assertEquals(None, config._expand_default_value)
725
def test_default_is_False_even_if_None(self):
726
self.config = self.get_config(None)
727
self.assertExpandIs(False)
729
def test_default_is_False_even_if_invalid(self):
730
self.config = self.get_config('<your choice>')
731
self.assertExpandIs(False)
733
# Huh ? My choice is False ? Thanks, always happy to hear that :D
734
# Wait, you've been warned !
735
self.assertLength(1, self.warnings)
737
'Value "<your choice>" is not a boolean for "bzr.config.expand"',
740
def test_default_is_True(self):
741
self.config = self.get_config(True)
742
self.assertExpandIs(True)
744
def test_default_is_False(self):
745
self.config = self.get_config(False)
746
self.assertExpandIs(False)
749
class TestIniConfigOptionExpansion(tests.TestCase):
750
"""Test option expansion from the IniConfig level.
752
What we really want here is to test the Config level, but the class being
753
abstract as far as storing values is concerned, this can't be done
756
# FIXME: This should be rewritten when all configs share a storage
757
# implementation -- vila 2011-02-18
759
def get_config(self, string=None):
762
c = config.IniBasedConfig.from_string(string)
765
def assertExpansion(self, expected, conf, string, env=None):
766
self.assertEquals(expected, conf.expand_options(string, env))
768
def test_no_expansion(self):
769
c = self.get_config('')
770
self.assertExpansion('foo', c, 'foo')
772
def test_env_adding_options(self):
773
c = self.get_config('')
774
self.assertExpansion('bar', c, '{foo}', {'foo': 'bar'})
776
def test_env_overriding_options(self):
777
c = self.get_config('foo=baz')
778
self.assertExpansion('bar', c, '{foo}', {'foo': 'bar'})
780
def test_simple_ref(self):
781
c = self.get_config('foo=xxx')
782
self.assertExpansion('xxx', c, '{foo}')
784
def test_unknown_ref(self):
785
c = self.get_config('')
786
self.assertRaises(errors.ExpandingUnknownOption,
787
c.expand_options, '{foo}')
789
def test_indirect_ref(self):
790
c = self.get_config('''
794
self.assertExpansion('xxx', c, '{bar}')
796
def test_embedded_ref(self):
797
c = self.get_config('''
801
self.assertExpansion('xxx', c, '{{bar}}')
803
def test_simple_loop(self):
804
c = self.get_config('foo={foo}')
805
self.assertRaises(errors.OptionExpansionLoop, c.expand_options, '{foo}')
807
def test_indirect_loop(self):
808
c = self.get_config('''
812
e = self.assertRaises(errors.OptionExpansionLoop,
813
c.expand_options, '{foo}')
814
self.assertEquals('foo->bar->baz', e.refs)
815
self.assertEquals('{foo}', e.string)
818
conf = self.get_config('''
822
list={foo},{bar},{baz}
824
self.assertEquals(['start', 'middle', 'end'],
825
conf.get_user_option('list', expand=True))
827
def test_cascading_list(self):
828
conf = self.get_config('''
834
self.assertEquals(['start', 'middle', 'end'],
835
conf.get_user_option('list', expand=True))
837
def test_pathological_hidden_list(self):
838
conf = self.get_config('''
844
hidden={start}{middle}{end}
846
# Nope, it's either a string or a list, and the list wins as soon as a
847
# ',' appears, so the string concatenation never occur.
848
self.assertEquals(['{foo', '}', '{', 'bar}'],
849
conf.get_user_option('hidden', expand=True))
852
class TestLocationConfigOptionExpansion(tests.TestCaseInTempDir):
854
def get_config(self, location, string=None):
857
# Since we don't save the config we won't strictly require to inherit
858
# from TestCaseInTempDir, but an error occurs so quickly...
859
c = config.LocationConfig.from_string(string, location)
862
def test_dont_cross_unrelated_section(self):
863
c = self.get_config('/another/branch/path','''
868
[/another/branch/path]
871
self.assertRaises(errors.ExpandingUnknownOption,
872
c.get_user_option, 'bar', expand=True)
874
def test_cross_related_sections(self):
875
c = self.get_config('/project/branch/path','''
879
[/project/branch/path]
882
self.assertEquals('quux', c.get_user_option('bar', expand=True))
885
class TestIniBaseConfigOnDisk(tests.TestCaseInTempDir):
887
def test_cannot_reload_without_name(self):
888
conf = config.IniBasedConfig.from_string(sample_config_text)
889
self.assertRaises(AssertionError, conf.reload)
891
def test_reload_see_new_value(self):
892
c1 = config.IniBasedConfig.from_string('editor=vim\n',
893
file_name='./test/conf')
894
c1._write_config_file()
895
c2 = config.IniBasedConfig.from_string('editor=emacs\n',
896
file_name='./test/conf')
897
c2._write_config_file()
898
self.assertEqual('vim', c1.get_user_option('editor'))
899
self.assertEqual('emacs', c2.get_user_option('editor'))
900
# Make sure we get the Right value
902
self.assertEqual('emacs', c1.get_user_option('editor'))
905
class TestLockableConfig(tests.TestCaseInTempDir):
907
scenarios = lockable_config_scenarios()
912
config_section = None
915
super(TestLockableConfig, self).setUp()
916
self._content = '[%s]\none=1\ntwo=2\n' % (self.config_section,)
917
self.config = self.create_config(self._content)
919
def get_existing_config(self):
920
return self.config_class(*self.config_args)
922
def create_config(self, content):
923
kwargs = dict(save=True)
924
c = self.config_class.from_string(content, *self.config_args, **kwargs)
927
def test_simple_read_access(self):
928
self.assertEquals('1', self.config.get_user_option('one'))
930
def test_simple_write_access(self):
931
self.config.set_user_option('one', 'one')
932
self.assertEquals('one', self.config.get_user_option('one'))
934
def test_listen_to_the_last_speaker(self):
936
c2 = self.get_existing_config()
937
c1.set_user_option('one', 'ONE')
938
c2.set_user_option('two', 'TWO')
939
self.assertEquals('ONE', c1.get_user_option('one'))
940
self.assertEquals('TWO', c2.get_user_option('two'))
941
# The second update respect the first one
942
self.assertEquals('ONE', c2.get_user_option('one'))
944
def test_last_speaker_wins(self):
945
# If the same config is not shared, the same variable modified twice
946
# can only see a single result.
948
c2 = self.get_existing_config()
949
c1.set_user_option('one', 'c1')
950
c2.set_user_option('one', 'c2')
951
self.assertEquals('c2', c2._get_user_option('one'))
952
# The first modification is still available until another refresh
954
self.assertEquals('c1', c1._get_user_option('one'))
955
c1.set_user_option('two', 'done')
956
self.assertEquals('c2', c1._get_user_option('one'))
958
def test_writes_are_serialized(self):
960
c2 = self.get_existing_config()
962
# We spawn a thread that will pause *during* the write
963
before_writing = threading.Event()
964
after_writing = threading.Event()
965
writing_done = threading.Event()
966
c1_orig = c1._write_config_file
967
def c1_write_config_file():
970
# The lock is held. We wait for the main thread to decide when to
973
c1._write_config_file = c1_write_config_file
975
c1.set_user_option('one', 'c1')
977
t1 = threading.Thread(target=c1_set_option)
978
# Collect the thread after the test
979
self.addCleanup(t1.join)
980
# Be ready to unblock the thread if the test goes wrong
981
self.addCleanup(after_writing.set)
983
before_writing.wait()
984
self.assertTrue(c1._lock.is_held)
985
self.assertRaises(errors.LockContention,
986
c2.set_user_option, 'one', 'c2')
987
self.assertEquals('c1', c1.get_user_option('one'))
988
# Let the lock be released
991
c2.set_user_option('one', 'c2')
992
self.assertEquals('c2', c2.get_user_option('one'))
994
def test_read_while_writing(self):
996
# We spawn a thread that will pause *during* the write
997
ready_to_write = threading.Event()
998
do_writing = threading.Event()
999
writing_done = threading.Event()
1000
c1_orig = c1._write_config_file
1001
def c1_write_config_file():
1002
ready_to_write.set()
1003
# The lock is held. We wait for the main thread to decide when to
1008
c1._write_config_file = c1_write_config_file
1009
def c1_set_option():
1010
c1.set_user_option('one', 'c1')
1011
t1 = threading.Thread(target=c1_set_option)
1012
# Collect the thread after the test
1013
self.addCleanup(t1.join)
1014
# Be ready to unblock the thread if the test goes wrong
1015
self.addCleanup(do_writing.set)
1017
# Ensure the thread is ready to write
1018
ready_to_write.wait()
1019
self.assertTrue(c1._lock.is_held)
1020
self.assertEquals('c1', c1.get_user_option('one'))
1021
# If we read during the write, we get the old value
1022
c2 = self.get_existing_config()
1023
self.assertEquals('1', c2.get_user_option('one'))
1024
# Let the writing occur and ensure it occurred
1027
# Now we get the updated value
1028
c3 = self.get_existing_config()
1029
self.assertEquals('c1', c3.get_user_option('one'))
1032
class TestGetUserOptionAs(TestIniConfig):
1034
def test_get_user_option_as_bool(self):
1035
conf, parser = self.make_config_parser("""
1038
an_invalid_bool = maybe
1039
a_list = hmm, who knows ? # This is interpreted as a list !
1041
get_bool = conf.get_user_option_as_bool
1042
self.assertEqual(True, get_bool('a_true_bool'))
1043
self.assertEqual(False, get_bool('a_false_bool'))
1046
warnings.append(args[0] % args[1:])
1047
self.overrideAttr(trace, 'warning', warning)
1048
msg = 'Value "%s" is not a boolean for "%s"'
1049
self.assertIs(None, get_bool('an_invalid_bool'))
1050
self.assertEquals(msg % ('maybe', 'an_invalid_bool'), warnings[0])
1052
self.assertIs(None, get_bool('not_defined_in_this_config'))
1053
self.assertEquals([], warnings)
1055
def test_get_user_option_as_list(self):
1056
conf, parser = self.make_config_parser("""
1061
get_list = conf.get_user_option_as_list
1062
self.assertEqual(['a', 'b', 'c'], get_list('a_list'))
1063
self.assertEqual(['1'], get_list('length_1'))
1064
self.assertEqual('x', conf.get_user_option('one_item'))
1065
# automatically cast to list
1066
self.assertEqual(['x'], get_list('one_item'))
1068
def test_get_user_option_as_int_from_SI(self):
1069
conf, parser = self.make_config_parser("""
1078
def get_si(s, default=None):
1079
return self.applyDeprecated(
1080
deprecated_in((2, 5, 0)),
1081
conf.get_user_option_as_int_from_SI, s, default)
1082
self.assertEqual(100, get_si('plain'))
1083
self.assertEqual(5000, get_si('si_k'))
1084
self.assertEqual(5000, get_si('si_kb'))
1085
self.assertEqual(5000000, get_si('si_m'))
1086
self.assertEqual(5000000, get_si('si_mb'))
1087
self.assertEqual(5000000000, get_si('si_g'))
1088
self.assertEqual(5000000000, get_si('si_gb'))
1089
self.assertEqual(None, get_si('non-exist'))
1090
self.assertEqual(42, get_si('non-exist-with-default', 42))
1093
class TestSupressWarning(TestIniConfig):
1095
def make_warnings_config(self, s):
1096
conf, parser = self.make_config_parser(s)
1097
return conf.suppress_warning
1099
def test_suppress_warning_unknown(self):
1100
suppress_warning = self.make_warnings_config('')
1101
self.assertEqual(False, suppress_warning('unknown_warning'))
1103
def test_suppress_warning_known(self):
1104
suppress_warning = self.make_warnings_config('suppress_warnings=a,b')
1105
self.assertEqual(False, suppress_warning('c'))
1106
self.assertEqual(True, suppress_warning('a'))
1107
self.assertEqual(True, suppress_warning('b'))
1110
class TestGetConfig(tests.TestCase):
1112
def test_constructs(self):
1113
my_config = config.GlobalConfig()
1115
def test_calls_read_filenames(self):
1116
# replace the class that is constructed, to check its parameters
1117
oldparserclass = config.ConfigObj
1118
config.ConfigObj = InstrumentedConfigObj
1119
my_config = config.GlobalConfig()
1121
parser = my_config._get_parser()
1123
config.ConfigObj = oldparserclass
1124
self.assertIsInstance(parser, InstrumentedConfigObj)
1125
self.assertEqual(parser._calls, [('__init__', config.config_filename(),
1129
class TestBranchConfig(tests.TestCaseWithTransport):
1131
def test_constructs(self):
1132
branch = FakeBranch()
1133
my_config = config.BranchConfig(branch)
1134
self.assertRaises(TypeError, config.BranchConfig)
1136
def test_get_location_config(self):
1137
branch = FakeBranch()
1138
my_config = config.BranchConfig(branch)
1139
location_config = my_config._get_location_config()
1140
self.assertEqual(branch.base, location_config.location)
1141
self.assertIs(location_config, my_config._get_location_config())
1143
def test_get_config(self):
1144
"""The Branch.get_config method works properly"""
1145
b = bzrdir.BzrDir.create_standalone_workingtree('.').branch
1146
my_config = b.get_config()
1147
self.assertIs(my_config.get_user_option('wacky'), None)
1148
my_config.set_user_option('wacky', 'unlikely')
1149
self.assertEqual(my_config.get_user_option('wacky'), 'unlikely')
1151
# Ensure we get the same thing if we start again
1152
b2 = branch.Branch.open('.')
1153
my_config2 = b2.get_config()
1154
self.assertEqual(my_config2.get_user_option('wacky'), 'unlikely')
1156
def test_has_explicit_nickname(self):
1157
b = self.make_branch('.')
1158
self.assertFalse(b.get_config().has_explicit_nickname())
1160
self.assertTrue(b.get_config().has_explicit_nickname())
1162
def test_config_url(self):
1163
"""The Branch.get_config will use section that uses a local url"""
1164
branch = self.make_branch('branch')
1165
self.assertEqual('branch', branch.nick)
1167
local_url = urlutils.local_path_to_url('branch')
1168
conf = config.LocationConfig.from_string(
1169
'[%s]\nnickname = foobar' % (local_url,),
1170
local_url, save=True)
1171
self.assertEqual('foobar', branch.nick)
1173
def test_config_local_path(self):
1174
"""The Branch.get_config will use a local system path"""
1175
branch = self.make_branch('branch')
1176
self.assertEqual('branch', branch.nick)
1178
local_path = osutils.getcwd().encode('utf8')
1179
conf = config.LocationConfig.from_string(
1180
'[%s/branch]\nnickname = barry' % (local_path,),
1181
'branch', save=True)
1182
self.assertEqual('barry', branch.nick)
1184
def test_config_creates_local(self):
1185
"""Creating a new entry in config uses a local path."""
1186
branch = self.make_branch('branch', format='knit')
1187
branch.set_push_location('http://foobar')
1188
local_path = osutils.getcwd().encode('utf8')
1189
# Surprisingly ConfigObj doesn't create a trailing newline
1190
self.check_file_contents(config.locations_config_filename(),
1192
'push_location = http://foobar\n'
1193
'push_location:policy = norecurse\n'
1196
def test_autonick_urlencoded(self):
1197
b = self.make_branch('!repo')
1198
self.assertEqual('!repo', b.get_config().get_nickname())
1200
def test_warn_if_masked(self):
1203
warnings.append(args[0] % args[1:])
1204
self.overrideAttr(trace, 'warning', warning)
1206
def set_option(store, warn_masked=True):
1208
conf.set_user_option('example_option', repr(store), store=store,
1209
warn_masked=warn_masked)
1210
def assertWarning(warning):
1212
self.assertEqual(0, len(warnings))
1214
self.assertEqual(1, len(warnings))
1215
self.assertEqual(warning, warnings[0])
1216
branch = self.make_branch('.')
1217
conf = branch.get_config()
1218
set_option(config.STORE_GLOBAL)
1220
set_option(config.STORE_BRANCH)
1222
set_option(config.STORE_GLOBAL)
1223
assertWarning('Value "4" is masked by "3" from branch.conf')
1224
set_option(config.STORE_GLOBAL, warn_masked=False)
1226
set_option(config.STORE_LOCATION)
1228
set_option(config.STORE_BRANCH)
1229
assertWarning('Value "3" is masked by "0" from locations.conf')
1230
set_option(config.STORE_BRANCH, warn_masked=False)
1234
class TestGlobalConfigItems(tests.TestCaseInTempDir):
1236
def test_user_id(self):
1237
my_config = config.GlobalConfig.from_string(sample_config_text)
1238
self.assertEqual(u"Erik B\u00e5gfors <erik@bagfors.nu>",
1239
my_config._get_user_id())
1241
def test_absent_user_id(self):
1242
my_config = config.GlobalConfig()
1243
self.assertEqual(None, my_config._get_user_id())
1245
def test_configured_editor(self):
1246
my_config = config.GlobalConfig.from_string(sample_config_text)
1247
editor = self.applyDeprecated(
1248
deprecated_in((2, 4, 0)), my_config.get_editor)
1249
self.assertEqual('vim', editor)
1251
def test_signatures_always(self):
1252
my_config = config.GlobalConfig.from_string(sample_always_signatures)
1253
self.assertEqual(config.CHECK_NEVER,
1254
self.applyDeprecated(deprecated_in((2, 5, 0)),
1255
my_config.signature_checking))
1256
self.assertEqual(config.SIGN_ALWAYS,
1257
self.applyDeprecated(deprecated_in((2, 5, 0)),
1258
my_config.signing_policy))
1259
self.assertEqual(True,
1260
self.applyDeprecated(deprecated_in((2, 5, 0)),
1261
my_config.signature_needed))
1263
def test_signatures_if_possible(self):
1264
my_config = config.GlobalConfig.from_string(sample_maybe_signatures)
1265
self.assertEqual(config.CHECK_NEVER,
1266
self.applyDeprecated(deprecated_in((2, 5, 0)),
1267
my_config.signature_checking))
1268
self.assertEqual(config.SIGN_WHEN_REQUIRED,
1269
self.applyDeprecated(deprecated_in((2, 5, 0)),
1270
my_config.signing_policy))
1271
self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
1272
my_config.signature_needed))
1274
def test_signatures_ignore(self):
1275
my_config = config.GlobalConfig.from_string(sample_ignore_signatures)
1276
self.assertEqual(config.CHECK_ALWAYS,
1277
self.applyDeprecated(deprecated_in((2, 5, 0)),
1278
my_config.signature_checking))
1279
self.assertEqual(config.SIGN_NEVER,
1280
self.applyDeprecated(deprecated_in((2, 5, 0)),
1281
my_config.signing_policy))
1282
self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
1283
my_config.signature_needed))
1285
def _get_sample_config(self):
1286
my_config = config.GlobalConfig.from_string(sample_config_text)
1289
def test_gpg_signing_command(self):
1290
my_config = self._get_sample_config()
1291
self.assertEqual("gnome-gpg",
1292
self.applyDeprecated(
1293
deprecated_in((2, 5, 0)), my_config.gpg_signing_command))
1294
self.assertEqual(False, self.applyDeprecated(deprecated_in((2, 5, 0)),
1295
my_config.signature_needed))
1297
def test_gpg_signing_key(self):
1298
my_config = self._get_sample_config()
1299
self.assertEqual("DD4D5088",
1300
self.applyDeprecated(deprecated_in((2, 5, 0)),
1301
my_config.gpg_signing_key))
1303
def _get_empty_config(self):
1304
my_config = config.GlobalConfig()
1307
def test_gpg_signing_command_unset(self):
1308
my_config = self._get_empty_config()
1309
self.assertEqual("gpg",
1310
self.applyDeprecated(
1311
deprecated_in((2, 5, 0)), my_config.gpg_signing_command))
1313
def test_get_user_option_default(self):
1314
my_config = self._get_empty_config()
1315
self.assertEqual(None, my_config.get_user_option('no_option'))
1317
def test_get_user_option_global(self):
1318
my_config = self._get_sample_config()
1319
self.assertEqual("something",
1320
my_config.get_user_option('user_global_option'))
1322
def test_post_commit_default(self):
1323
my_config = self._get_sample_config()
1324
self.assertEqual(None,
1325
self.applyDeprecated(deprecated_in((2, 5, 0)),
1326
my_config.post_commit))
1328
def test_configured_logformat(self):
1329
my_config = self._get_sample_config()
1330
self.assertEqual("short",
1331
self.applyDeprecated(deprecated_in((2, 5, 0)),
1332
my_config.log_format))
1334
def test_configured_acceptable_keys(self):
1335
my_config = self._get_sample_config()
1336
self.assertEqual("amy",
1337
self.applyDeprecated(deprecated_in((2, 5, 0)),
1338
my_config.acceptable_keys))
1340
def test_configured_validate_signatures_in_log(self):
1341
my_config = self._get_sample_config()
1342
self.assertEqual(True, my_config.validate_signatures_in_log())
1344
def test_get_alias(self):
1345
my_config = self._get_sample_config()
1346
self.assertEqual('help', my_config.get_alias('h'))
1348
def test_get_aliases(self):
1349
my_config = self._get_sample_config()
1350
aliases = my_config.get_aliases()
1351
self.assertEqual(2, len(aliases))
1352
sorted_keys = sorted(aliases)
1353
self.assertEqual('help', aliases[sorted_keys[0]])
1354
self.assertEqual(sample_long_alias, aliases[sorted_keys[1]])
1356
def test_get_no_alias(self):
1357
my_config = self._get_sample_config()
1358
self.assertEqual(None, my_config.get_alias('foo'))
1360
def test_get_long_alias(self):
1361
my_config = self._get_sample_config()
1362
self.assertEqual(sample_long_alias, my_config.get_alias('ll'))
1364
def test_get_change_editor(self):
1365
my_config = self._get_sample_config()
1366
change_editor = my_config.get_change_editor('old', 'new')
1367
self.assertIs(diff.DiffFromTool, change_editor.__class__)
1368
self.assertEqual('vimdiff -of @new_path @old_path',
1369
' '.join(change_editor.command_template))
1371
def test_get_no_change_editor(self):
1372
my_config = self._get_empty_config()
1373
change_editor = my_config.get_change_editor('old', 'new')
1374
self.assertIs(None, change_editor)
1376
def test_get_merge_tools(self):
1377
conf = self._get_sample_config()
1378
tools = conf.get_merge_tools()
1379
self.log(repr(tools))
1381
{u'funkytool' : u'funkytool "arg with spaces" {this_temp}',
1382
u'sometool' : u'sometool {base} {this} {other} -o {result}',
1383
u'newtool' : u'"newtool with spaces" {this_temp}'},
1386
def test_get_merge_tools_empty(self):
1387
conf = self._get_empty_config()
1388
tools = conf.get_merge_tools()
1389
self.assertEqual({}, tools)
1391
def test_find_merge_tool(self):
1392
conf = self._get_sample_config()
1393
cmdline = conf.find_merge_tool('sometool')
1394
self.assertEqual('sometool {base} {this} {other} -o {result}', cmdline)
1396
def test_find_merge_tool_not_found(self):
1397
conf = self._get_sample_config()
1398
cmdline = conf.find_merge_tool('DOES NOT EXIST')
1399
self.assertIs(cmdline, None)
1401
def test_find_merge_tool_known(self):
1402
conf = self._get_empty_config()
1403
cmdline = conf.find_merge_tool('kdiff3')
1404
self.assertEquals('kdiff3 {base} {this} {other} -o {result}', cmdline)
1406
def test_find_merge_tool_override_known(self):
1407
conf = self._get_empty_config()
1408
conf.set_user_option('bzr.mergetool.kdiff3', 'kdiff3 blah')
1409
cmdline = conf.find_merge_tool('kdiff3')
1410
self.assertEqual('kdiff3 blah', cmdline)
1413
class TestGlobalConfigSavingOptions(tests.TestCaseInTempDir):
1415
def test_empty(self):
1416
my_config = config.GlobalConfig()
1417
self.assertEqual(0, len(my_config.get_aliases()))
1419
def test_set_alias(self):
1420
my_config = config.GlobalConfig()
1421
alias_value = 'commit --strict'
1422
my_config.set_alias('commit', alias_value)
1423
new_config = config.GlobalConfig()
1424
self.assertEqual(alias_value, new_config.get_alias('commit'))
1426
def test_remove_alias(self):
1427
my_config = config.GlobalConfig()
1428
my_config.set_alias('commit', 'commit --strict')
1429
# Now remove the alias again.
1430
my_config.unset_alias('commit')
1431
new_config = config.GlobalConfig()
1432
self.assertIs(None, new_config.get_alias('commit'))
1435
class TestLocationConfig(tests.TestCaseInTempDir, TestOptionsMixin):
1437
def test_constructs(self):
1438
my_config = config.LocationConfig('http://example.com')
1439
self.assertRaises(TypeError, config.LocationConfig)
1441
def test_branch_calls_read_filenames(self):
1442
# This is testing the correct file names are provided.
1443
# TODO: consolidate with the test for GlobalConfigs filename checks.
1445
# replace the class that is constructed, to check its parameters
1446
oldparserclass = config.ConfigObj
1447
config.ConfigObj = InstrumentedConfigObj
1449
my_config = config.LocationConfig('http://www.example.com')
1450
parser = my_config._get_parser()
1452
config.ConfigObj = oldparserclass
1453
self.assertIsInstance(parser, InstrumentedConfigObj)
1454
self.assertEqual(parser._calls,
1455
[('__init__', config.locations_config_filename(),
1458
def test_get_global_config(self):
1459
my_config = config.BranchConfig(FakeBranch('http://example.com'))
1460
global_config = my_config._get_global_config()
1461
self.assertIsInstance(global_config, config.GlobalConfig)
1462
self.assertIs(global_config, my_config._get_global_config())
1464
def assertLocationMatching(self, expected):
1465
self.assertEqual(expected,
1466
list(self.my_location_config._get_matching_sections()))
1468
def test__get_matching_sections_no_match(self):
1469
self.get_branch_config('/')
1470
self.assertLocationMatching([])
1472
def test__get_matching_sections_exact(self):
1473
self.get_branch_config('http://www.example.com')
1474
self.assertLocationMatching([('http://www.example.com', '')])
1476
def test__get_matching_sections_suffix_does_not(self):
1477
self.get_branch_config('http://www.example.com-com')
1478
self.assertLocationMatching([])
1480
def test__get_matching_sections_subdir_recursive(self):
1481
self.get_branch_config('http://www.example.com/com')
1482
self.assertLocationMatching([('http://www.example.com', 'com')])
1484
def test__get_matching_sections_ignoreparent(self):
1485
self.get_branch_config('http://www.example.com/ignoreparent')
1486
self.assertLocationMatching([('http://www.example.com/ignoreparent',
1489
def test__get_matching_sections_ignoreparent_subdir(self):
1490
self.get_branch_config(
1491
'http://www.example.com/ignoreparent/childbranch')
1492
self.assertLocationMatching([('http://www.example.com/ignoreparent',
1495
def test__get_matching_sections_subdir_trailing_slash(self):
1496
self.get_branch_config('/b')
1497
self.assertLocationMatching([('/b/', '')])
1499
def test__get_matching_sections_subdir_child(self):
1500
self.get_branch_config('/a/foo')
1501
self.assertLocationMatching([('/a/*', ''), ('/a/', 'foo')])
1503
def test__get_matching_sections_subdir_child_child(self):
1504
self.get_branch_config('/a/foo/bar')
1505
self.assertLocationMatching([('/a/*', 'bar'), ('/a/', 'foo/bar')])
1507
def test__get_matching_sections_trailing_slash_with_children(self):
1508
self.get_branch_config('/a/')
1509
self.assertLocationMatching([('/a/', '')])
1511
def test__get_matching_sections_explicit_over_glob(self):
1512
# XXX: 2006-09-08 jamesh
1513
# This test only passes because ord('c') > ord('*'). If there
1514
# was a config section for '/a/?', it would get precedence
1516
self.get_branch_config('/a/c')
1517
self.assertLocationMatching([('/a/c', ''), ('/a/*', ''), ('/a/', 'c')])
1519
def test__get_option_policy_normal(self):
1520
self.get_branch_config('http://www.example.com')
1522
self.my_location_config._get_config_policy(
1523
'http://www.example.com', 'normal_option'),
1526
def test__get_option_policy_norecurse(self):
1527
self.get_branch_config('http://www.example.com')
1529
self.my_location_config._get_option_policy(
1530
'http://www.example.com', 'norecurse_option'),
1531
config.POLICY_NORECURSE)
1532
# Test old recurse=False setting:
1534
self.my_location_config._get_option_policy(
1535
'http://www.example.com/norecurse', 'normal_option'),
1536
config.POLICY_NORECURSE)
1538
def test__get_option_policy_normal(self):
1539
self.get_branch_config('http://www.example.com')
1541
self.my_location_config._get_option_policy(
1542
'http://www.example.com', 'appendpath_option'),
1543
config.POLICY_APPENDPATH)
1545
def test__get_options_with_policy(self):
1546
self.get_branch_config('/dir/subdir',
1547
location_config="""\
1549
other_url = /other-dir
1550
other_url:policy = appendpath
1552
other_url = /other-subdir
1555
[(u'other_url', u'/other-subdir', u'/dir/subdir', 'locations'),
1556
(u'other_url', u'/other-dir', u'/dir', 'locations'),
1557
(u'other_url:policy', u'appendpath', u'/dir', 'locations')],
1558
self.my_location_config)
1560
def test_location_without_username(self):
1561
self.get_branch_config('http://www.example.com/ignoreparent')
1562
self.assertEqual(u'Erik B\u00e5gfors <erik@bagfors.nu>',
1563
self.my_config.username())
1565
def test_location_not_listed(self):
1566
"""Test that the global username is used when no location matches"""
1567
self.get_branch_config('/home/robertc/sources')
1568
self.assertEqual(u'Erik B\u00e5gfors <erik@bagfors.nu>',
1569
self.my_config.username())
1571
def test_overriding_location(self):
1572
self.get_branch_config('http://www.example.com/foo')
1573
self.assertEqual('Robert Collins <robertc@example.org>',
1574
self.my_config.username())
1576
def test_signatures_not_set(self):
1577
self.get_branch_config('http://www.example.com',
1578
global_config=sample_ignore_signatures)
1579
self.assertEqual(config.CHECK_ALWAYS,
1580
self.applyDeprecated(deprecated_in((2, 5, 0)),
1581
self.my_config.signature_checking))
1582
self.assertEqual(config.SIGN_NEVER,
1583
self.applyDeprecated(deprecated_in((2, 5, 0)),
1584
self.my_config.signing_policy))
1586
def test_signatures_never(self):
1587
self.get_branch_config('/a/c')
1588
self.assertEqual(config.CHECK_NEVER,
1589
self.applyDeprecated(deprecated_in((2, 5, 0)),
1590
self.my_config.signature_checking))
1592
def test_signatures_when_available(self):
1593
self.get_branch_config('/a/', global_config=sample_ignore_signatures)
1594
self.assertEqual(config.CHECK_IF_POSSIBLE,
1595
self.applyDeprecated(deprecated_in((2, 5, 0)),
1596
self.my_config.signature_checking))
1598
def test_signatures_always(self):
1599
self.get_branch_config('/b')
1600
self.assertEqual(config.CHECK_ALWAYS,
1601
self.applyDeprecated(deprecated_in((2, 5, 0)),
1602
self.my_config.signature_checking))
1604
def test_gpg_signing_command(self):
1605
self.get_branch_config('/b')
1606
self.assertEqual("gnome-gpg",
1607
self.applyDeprecated(deprecated_in((2, 5, 0)),
1608
self.my_config.gpg_signing_command))
1610
def test_gpg_signing_command_missing(self):
1611
self.get_branch_config('/a')
1612
self.assertEqual("false",
1613
self.applyDeprecated(deprecated_in((2, 5, 0)),
1614
self.my_config.gpg_signing_command))
1616
def test_gpg_signing_key(self):
1617
self.get_branch_config('/b')
1618
self.assertEqual("DD4D5088", self.applyDeprecated(deprecated_in((2, 5, 0)),
1619
self.my_config.gpg_signing_key))
1621
def test_gpg_signing_key_default(self):
1622
self.get_branch_config('/a')
1623
self.assertEqual("erik@bagfors.nu",
1624
self.applyDeprecated(deprecated_in((2, 5, 0)),
1625
self.my_config.gpg_signing_key))
1627
def test_get_user_option_global(self):
1628
self.get_branch_config('/a')
1629
self.assertEqual('something',
1630
self.my_config.get_user_option('user_global_option'))
1632
def test_get_user_option_local(self):
1633
self.get_branch_config('/a')
1634
self.assertEqual('local',
1635
self.my_config.get_user_option('user_local_option'))
1637
def test_get_user_option_appendpath(self):
1638
# returned as is for the base path:
1639
self.get_branch_config('http://www.example.com')
1640
self.assertEqual('append',
1641
self.my_config.get_user_option('appendpath_option'))
1642
# Extra path components get appended:
1643
self.get_branch_config('http://www.example.com/a/b/c')
1644
self.assertEqual('append/a/b/c',
1645
self.my_config.get_user_option('appendpath_option'))
1646
# Overriden for http://www.example.com/dir, where it is a
1648
self.get_branch_config('http://www.example.com/dir/a/b/c')
1649
self.assertEqual('normal',
1650
self.my_config.get_user_option('appendpath_option'))
1652
def test_get_user_option_norecurse(self):
1653
self.get_branch_config('http://www.example.com')
1654
self.assertEqual('norecurse',
1655
self.my_config.get_user_option('norecurse_option'))
1656
self.get_branch_config('http://www.example.com/dir')
1657
self.assertEqual(None,
1658
self.my_config.get_user_option('norecurse_option'))
1659
# http://www.example.com/norecurse is a recurse=False section
1660
# that redefines normal_option. Subdirectories do not pick up
1661
# this redefinition.
1662
self.get_branch_config('http://www.example.com/norecurse')
1663
self.assertEqual('norecurse',
1664
self.my_config.get_user_option('normal_option'))
1665
self.get_branch_config('http://www.example.com/norecurse/subdir')
1666
self.assertEqual('normal',
1667
self.my_config.get_user_option('normal_option'))
1669
def test_set_user_option_norecurse(self):
1670
self.get_branch_config('http://www.example.com')
1671
self.my_config.set_user_option('foo', 'bar',
1672
store=config.STORE_LOCATION_NORECURSE)
1674
self.my_location_config._get_option_policy(
1675
'http://www.example.com', 'foo'),
1676
config.POLICY_NORECURSE)
1678
def test_set_user_option_appendpath(self):
1679
self.get_branch_config('http://www.example.com')
1680
self.my_config.set_user_option('foo', 'bar',
1681
store=config.STORE_LOCATION_APPENDPATH)
1683
self.my_location_config._get_option_policy(
1684
'http://www.example.com', 'foo'),
1685
config.POLICY_APPENDPATH)
1687
def test_set_user_option_change_policy(self):
1688
self.get_branch_config('http://www.example.com')
1689
self.my_config.set_user_option('norecurse_option', 'normal',
1690
store=config.STORE_LOCATION)
1692
self.my_location_config._get_option_policy(
1693
'http://www.example.com', 'norecurse_option'),
1696
def test_set_user_option_recurse_false_section(self):
1697
# The following section has recurse=False set. The test is to
1698
# make sure that a normal option can be added to the section,
1699
# converting recurse=False to the norecurse policy.
1700
self.get_branch_config('http://www.example.com/norecurse')
1701
self.callDeprecated(['The recurse option is deprecated as of 0.14. '
1702
'The section "http://www.example.com/norecurse" '
1703
'has been converted to use policies.'],
1704
self.my_config.set_user_option,
1705
'foo', 'bar', store=config.STORE_LOCATION)
1707
self.my_location_config._get_option_policy(
1708
'http://www.example.com/norecurse', 'foo'),
1710
# The previously existing option is still norecurse:
1712
self.my_location_config._get_option_policy(
1713
'http://www.example.com/norecurse', 'normal_option'),
1714
config.POLICY_NORECURSE)
1716
def test_post_commit_default(self):
1717
self.get_branch_config('/a/c')
1718
self.assertEqual('bzrlib.tests.test_config.post_commit',
1719
self.applyDeprecated(deprecated_in((2, 5, 0)),
1720
self.my_config.post_commit))
1722
def get_branch_config(self, location, global_config=None,
1723
location_config=None):
1724
my_branch = FakeBranch(location)
1725
if global_config is None:
1726
global_config = sample_config_text
1727
if location_config is None:
1728
location_config = sample_branches_text
1730
my_global_config = config.GlobalConfig.from_string(global_config,
1732
my_location_config = config.LocationConfig.from_string(
1733
location_config, my_branch.base, save=True)
1734
my_config = config.BranchConfig(my_branch)
1735
self.my_config = my_config
1736
self.my_location_config = my_config._get_location_config()
1738
def test_set_user_setting_sets_and_saves(self):
1739
self.get_branch_config('/a/c')
1740
record = InstrumentedConfigObj("foo")
1741
self.my_location_config._parser = record
1743
self.callDeprecated(['The recurse option is deprecated as of '
1744
'0.14. The section "/a/c" has been '
1745
'converted to use policies.'],
1746
self.my_config.set_user_option,
1747
'foo', 'bar', store=config.STORE_LOCATION)
1748
self.assertEqual([('reload',),
1749
('__contains__', '/a/c'),
1750
('__contains__', '/a/c/'),
1751
('__setitem__', '/a/c', {}),
1752
('__getitem__', '/a/c'),
1753
('__setitem__', 'foo', 'bar'),
1754
('__getitem__', '/a/c'),
1755
('as_bool', 'recurse'),
1756
('__getitem__', '/a/c'),
1757
('__delitem__', 'recurse'),
1758
('__getitem__', '/a/c'),
1760
('__getitem__', '/a/c'),
1761
('__contains__', 'foo:policy'),
1765
def test_set_user_setting_sets_and_saves2(self):
1766
self.get_branch_config('/a/c')
1767
self.assertIs(self.my_config.get_user_option('foo'), None)
1768
self.my_config.set_user_option('foo', 'bar')
1770
self.my_config.branch.control_files.files['branch.conf'].strip(),
1772
self.assertEqual(self.my_config.get_user_option('foo'), 'bar')
1773
self.my_config.set_user_option('foo', 'baz',
1774
store=config.STORE_LOCATION)
1775
self.assertEqual(self.my_config.get_user_option('foo'), 'baz')
1776
self.my_config.set_user_option('foo', 'qux')
1777
self.assertEqual(self.my_config.get_user_option('foo'), 'baz')
1779
def test_get_bzr_remote_path(self):
1780
my_config = config.LocationConfig('/a/c')
1781
self.assertEqual('bzr', my_config.get_bzr_remote_path())
1782
my_config.set_user_option('bzr_remote_path', '/path-bzr')
1783
self.assertEqual('/path-bzr', my_config.get_bzr_remote_path())
1784
self.overrideEnv('BZR_REMOTE_PATH', '/environ-bzr')
1785
self.assertEqual('/environ-bzr', my_config.get_bzr_remote_path())
1788
precedence_global = 'option = global'
1789
precedence_branch = 'option = branch'
1790
precedence_location = """
1794
[http://example.com/specific]
1798
class TestBranchConfigItems(tests.TestCaseInTempDir):
1800
def get_branch_config(self, global_config=None, location=None,
1801
location_config=None, branch_data_config=None):
1802
my_branch = FakeBranch(location)
1803
if global_config is not None:
1804
my_global_config = config.GlobalConfig.from_string(global_config,
1806
if location_config is not None:
1807
my_location_config = config.LocationConfig.from_string(
1808
location_config, my_branch.base, save=True)
1809
my_config = config.BranchConfig(my_branch)
1810
if branch_data_config is not None:
1811
my_config.branch.control_files.files['branch.conf'] = \
1815
def test_user_id(self):
1816
branch = FakeBranch()
1817
my_config = config.BranchConfig(branch)
1818
self.assertIsNot(None, my_config.username())
1819
my_config.branch.control_files.files['email'] = "John"
1820
my_config.set_user_option('email',
1821
"Robert Collins <robertc@example.org>")
1822
self.assertEqual("Robert Collins <robertc@example.org>",
1823
my_config.username())
1825
def test_BZR_EMAIL_OVERRIDES(self):
1826
self.overrideEnv('BZR_EMAIL', "Robert Collins <robertc@example.org>")
1827
branch = FakeBranch()
1828
my_config = config.BranchConfig(branch)
1829
self.assertEqual("Robert Collins <robertc@example.org>",
1830
my_config.username())
1832
def test_signatures_forced(self):
1833
my_config = self.get_branch_config(
1834
global_config=sample_always_signatures)
1835
self.assertEqual(config.CHECK_NEVER,
1836
self.applyDeprecated(deprecated_in((2, 5, 0)),
1837
my_config.signature_checking))
1838
self.assertEqual(config.SIGN_ALWAYS,
1839
self.applyDeprecated(deprecated_in((2, 5, 0)),
1840
my_config.signing_policy))
1841
self.assertTrue(self.applyDeprecated(deprecated_in((2, 5, 0)),
1842
my_config.signature_needed))
1844
def test_signatures_forced_branch(self):
1845
my_config = self.get_branch_config(
1846
global_config=sample_ignore_signatures,
1847
branch_data_config=sample_always_signatures)
1848
self.assertEqual(config.CHECK_NEVER,
1849
self.applyDeprecated(deprecated_in((2, 5, 0)),
1850
my_config.signature_checking))
1851
self.assertEqual(config.SIGN_ALWAYS,
1852
self.applyDeprecated(deprecated_in((2, 5, 0)),
1853
my_config.signing_policy))
1854
self.assertTrue(self.applyDeprecated(deprecated_in((2, 5, 0)),
1855
my_config.signature_needed))
1857
def test_gpg_signing_command(self):
1858
my_config = self.get_branch_config(
1859
global_config=sample_config_text,
1860
# branch data cannot set gpg_signing_command
1861
branch_data_config="gpg_signing_command=pgp")
1862
self.assertEqual('gnome-gpg',
1863
self.applyDeprecated(deprecated_in((2, 5, 0)),
1864
my_config.gpg_signing_command))
1866
def test_get_user_option_global(self):
1867
my_config = self.get_branch_config(global_config=sample_config_text)
1868
self.assertEqual('something',
1869
my_config.get_user_option('user_global_option'))
1871
def test_post_commit_default(self):
1872
my_config = self.get_branch_config(global_config=sample_config_text,
1874
location_config=sample_branches_text)
1875
self.assertEqual(my_config.branch.base, '/a/c')
1876
self.assertEqual('bzrlib.tests.test_config.post_commit',
1877
self.applyDeprecated(deprecated_in((2, 5, 0)),
1878
my_config.post_commit))
1879
my_config.set_user_option('post_commit', 'rmtree_root')
1880
# post-commit is ignored when present in branch data
1881
self.assertEqual('bzrlib.tests.test_config.post_commit',
1882
self.applyDeprecated(deprecated_in((2, 5, 0)),
1883
my_config.post_commit))
1884
my_config.set_user_option('post_commit', 'rmtree_root',
1885
store=config.STORE_LOCATION)
1886
self.assertEqual('rmtree_root',
1887
self.applyDeprecated(deprecated_in((2, 5, 0)),
1888
my_config.post_commit))
1890
def test_config_precedence(self):
1891
# FIXME: eager test, luckily no persitent config file makes it fail
1893
my_config = self.get_branch_config(global_config=precedence_global)
1894
self.assertEqual(my_config.get_user_option('option'), 'global')
1895
my_config = self.get_branch_config(global_config=precedence_global,
1896
branch_data_config=precedence_branch)
1897
self.assertEqual(my_config.get_user_option('option'), 'branch')
1898
my_config = self.get_branch_config(
1899
global_config=precedence_global,
1900
branch_data_config=precedence_branch,
1901
location_config=precedence_location)
1902
self.assertEqual(my_config.get_user_option('option'), 'recurse')
1903
my_config = self.get_branch_config(
1904
global_config=precedence_global,
1905
branch_data_config=precedence_branch,
1906
location_config=precedence_location,
1907
location='http://example.com/specific')
1908
self.assertEqual(my_config.get_user_option('option'), 'exact')
1911
class TestMailAddressExtraction(tests.TestCase):
1913
def test_extract_email_address(self):
1914
self.assertEqual('jane@test.com',
1915
config.extract_email_address('Jane <jane@test.com>'))
1916
self.assertRaises(errors.NoEmailInUsername,
1917
config.extract_email_address, 'Jane Tester')
1919
def test_parse_username(self):
1920
self.assertEqual(('', 'jdoe@example.com'),
1921
config.parse_username('jdoe@example.com'))
1922
self.assertEqual(('', 'jdoe@example.com'),
1923
config.parse_username('<jdoe@example.com>'))
1924
self.assertEqual(('John Doe', 'jdoe@example.com'),
1925
config.parse_username('John Doe <jdoe@example.com>'))
1926
self.assertEqual(('John Doe', ''),
1927
config.parse_username('John Doe'))
1928
self.assertEqual(('John Doe', 'jdoe@example.com'),
1929
config.parse_username('John Doe jdoe@example.com'))
1931
class TestTreeConfig(tests.TestCaseWithTransport):
1933
def test_get_value(self):
1934
"""Test that retreiving a value from a section is possible"""
1935
branch = self.make_branch('.')
1936
tree_config = config.TreeConfig(branch)
1937
tree_config.set_option('value', 'key', 'SECTION')
1938
tree_config.set_option('value2', 'key2')
1939
tree_config.set_option('value3-top', 'key3')
1940
tree_config.set_option('value3-section', 'key3', 'SECTION')
1941
value = tree_config.get_option('key', 'SECTION')
1942
self.assertEqual(value, 'value')
1943
value = tree_config.get_option('key2')
1944
self.assertEqual(value, 'value2')
1945
self.assertEqual(tree_config.get_option('non-existant'), None)
1946
value = tree_config.get_option('non-existant', 'SECTION')
1947
self.assertEqual(value, None)
1948
value = tree_config.get_option('non-existant', default='default')
1949
self.assertEqual(value, 'default')
1950
self.assertEqual(tree_config.get_option('key2', 'NOSECTION'), None)
1951
value = tree_config.get_option('key2', 'NOSECTION', default='default')
1952
self.assertEqual(value, 'default')
1953
value = tree_config.get_option('key3')
1954
self.assertEqual(value, 'value3-top')
1955
value = tree_config.get_option('key3', 'SECTION')
1956
self.assertEqual(value, 'value3-section')
1959
class TestTransportConfig(tests.TestCaseWithTransport):
1961
def test_load_utf8(self):
1962
"""Ensure we can load an utf8-encoded file."""
1963
t = self.get_transport()
1964
unicode_user = u'b\N{Euro Sign}ar'
1965
unicode_content = u'user=%s' % (unicode_user,)
1966
utf8_content = unicode_content.encode('utf8')
1967
# Store the raw content in the config file
1968
t.put_bytes('foo.conf', utf8_content)
1969
conf = config.TransportConfig(t, 'foo.conf')
1970
self.assertEquals(unicode_user, conf.get_option('user'))
1972
def test_load_non_ascii(self):
1973
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
1974
t = self.get_transport()
1975
t.put_bytes('foo.conf', 'user=foo\n#\xff\n')
1976
conf = config.TransportConfig(t, 'foo.conf')
1977
self.assertRaises(errors.ConfigContentError, conf._get_configobj)
1979
def test_load_erroneous_content(self):
1980
"""Ensure we display a proper error on content that can't be parsed."""
1981
t = self.get_transport()
1982
t.put_bytes('foo.conf', '[open_section\n')
1983
conf = config.TransportConfig(t, 'foo.conf')
1984
self.assertRaises(errors.ParseConfigError, conf._get_configobj)
1986
def test_load_permission_denied(self):
1987
"""Ensure we get an empty config file if the file is inaccessible."""
1990
warnings.append(args[0] % args[1:])
1991
self.overrideAttr(trace, 'warning', warning)
1993
class DenyingTransport(object):
1995
def __init__(self, base):
1998
def get_bytes(self, relpath):
1999
raise errors.PermissionDenied(relpath, "")
2001
cfg = config.TransportConfig(
2002
DenyingTransport("nonexisting://"), 'control.conf')
2003
self.assertIs(None, cfg.get_option('non-existant', 'SECTION'))
2006
[u'Permission denied while trying to open configuration file '
2007
u'nonexisting:///control.conf.'])
2009
def test_get_value(self):
2010
"""Test that retreiving a value from a section is possible"""
2011
bzrdir_config = config.TransportConfig(self.get_transport('.'),
2013
bzrdir_config.set_option('value', 'key', 'SECTION')
2014
bzrdir_config.set_option('value2', 'key2')
2015
bzrdir_config.set_option('value3-top', 'key3')
2016
bzrdir_config.set_option('value3-section', 'key3', 'SECTION')
2017
value = bzrdir_config.get_option('key', 'SECTION')
2018
self.assertEqual(value, 'value')
2019
value = bzrdir_config.get_option('key2')
2020
self.assertEqual(value, 'value2')
2021
self.assertEqual(bzrdir_config.get_option('non-existant'), None)
2022
value = bzrdir_config.get_option('non-existant', 'SECTION')
2023
self.assertEqual(value, None)
2024
value = bzrdir_config.get_option('non-existant', default='default')
2025
self.assertEqual(value, 'default')
2026
self.assertEqual(bzrdir_config.get_option('key2', 'NOSECTION'), None)
2027
value = bzrdir_config.get_option('key2', 'NOSECTION',
2029
self.assertEqual(value, 'default')
2030
value = bzrdir_config.get_option('key3')
2031
self.assertEqual(value, 'value3-top')
2032
value = bzrdir_config.get_option('key3', 'SECTION')
2033
self.assertEqual(value, 'value3-section')
2035
def test_set_unset_default_stack_on(self):
2036
my_dir = self.make_bzrdir('.')
2037
bzrdir_config = config.BzrDirConfig(my_dir)
2038
self.assertIs(None, bzrdir_config.get_default_stack_on())
2039
bzrdir_config.set_default_stack_on('Foo')
2040
self.assertEqual('Foo', bzrdir_config._config.get_option(
2041
'default_stack_on'))
2042
self.assertEqual('Foo', bzrdir_config.get_default_stack_on())
2043
bzrdir_config.set_default_stack_on(None)
2044
self.assertIs(None, bzrdir_config.get_default_stack_on())
2047
class TestOldConfigHooks(tests.TestCaseWithTransport):
2050
super(TestOldConfigHooks, self).setUp()
2051
create_configs_with_file_option(self)
2053
def assertGetHook(self, conf, name, value):
2057
config.OldConfigHooks.install_named_hook('get', hook, None)
2059
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2060
self.assertLength(0, calls)
2061
actual_value = conf.get_user_option(name)
2062
self.assertEquals(value, actual_value)
2063
self.assertLength(1, calls)
2064
self.assertEquals((conf, name, value), calls[0])
2066
def test_get_hook_bazaar(self):
2067
self.assertGetHook(self.bazaar_config, 'file', 'bazaar')
2069
def test_get_hook_locations(self):
2070
self.assertGetHook(self.locations_config, 'file', 'locations')
2072
def test_get_hook_branch(self):
2073
# Since locations masks branch, we define a different option
2074
self.branch_config.set_user_option('file2', 'branch')
2075
self.assertGetHook(self.branch_config, 'file2', 'branch')
2077
def assertSetHook(self, conf, name, value):
2081
config.OldConfigHooks.install_named_hook('set', hook, None)
2083
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2084
self.assertLength(0, calls)
2085
conf.set_user_option(name, value)
2086
self.assertLength(1, calls)
2087
# We can't assert the conf object below as different configs use
2088
# different means to implement set_user_option and we care only about
2090
self.assertEquals((name, value), calls[0][1:])
2092
def test_set_hook_bazaar(self):
2093
self.assertSetHook(self.bazaar_config, 'foo', 'bazaar')
2095
def test_set_hook_locations(self):
2096
self.assertSetHook(self.locations_config, 'foo', 'locations')
2098
def test_set_hook_branch(self):
2099
self.assertSetHook(self.branch_config, 'foo', 'branch')
2101
def assertRemoveHook(self, conf, name, section_name=None):
2105
config.OldConfigHooks.install_named_hook('remove', hook, None)
2107
config.OldConfigHooks.uninstall_named_hook, 'remove', None)
2108
self.assertLength(0, calls)
2109
conf.remove_user_option(name, section_name)
2110
self.assertLength(1, calls)
2111
# We can't assert the conf object below as different configs use
2112
# different means to implement remove_user_option and we care only about
2114
self.assertEquals((name,), calls[0][1:])
2116
def test_remove_hook_bazaar(self):
2117
self.assertRemoveHook(self.bazaar_config, 'file')
2119
def test_remove_hook_locations(self):
2120
self.assertRemoveHook(self.locations_config, 'file',
2121
self.locations_config.location)
2123
def test_remove_hook_branch(self):
2124
self.assertRemoveHook(self.branch_config, 'file')
2126
def assertLoadHook(self, name, conf_class, *conf_args):
2130
config.OldConfigHooks.install_named_hook('load', hook, None)
2132
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2133
self.assertLength(0, calls)
2135
conf = conf_class(*conf_args)
2136
# Access an option to trigger a load
2137
conf.get_user_option(name)
2138
self.assertLength(1, calls)
2139
# Since we can't assert about conf, we just use the number of calls ;-/
2141
def test_load_hook_bazaar(self):
2142
self.assertLoadHook('file', config.GlobalConfig)
2144
def test_load_hook_locations(self):
2145
self.assertLoadHook('file', config.LocationConfig, self.tree.basedir)
2147
def test_load_hook_branch(self):
2148
self.assertLoadHook('file', config.BranchConfig, self.tree.branch)
2150
def assertSaveHook(self, conf):
2154
config.OldConfigHooks.install_named_hook('save', hook, None)
2156
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2157
self.assertLength(0, calls)
2158
# Setting an option triggers a save
2159
conf.set_user_option('foo', 'bar')
2160
self.assertLength(1, calls)
2161
# Since we can't assert about conf, we just use the number of calls ;-/
2163
def test_save_hook_bazaar(self):
2164
self.assertSaveHook(self.bazaar_config)
2166
def test_save_hook_locations(self):
2167
self.assertSaveHook(self.locations_config)
2169
def test_save_hook_branch(self):
2170
self.assertSaveHook(self.branch_config)
2173
class TestOldConfigHooksForRemote(tests.TestCaseWithTransport):
2174
"""Tests config hooks for remote configs.
2176
No tests for the remove hook as this is not implemented there.
2180
super(TestOldConfigHooksForRemote, self).setUp()
2181
self.transport_server = test_server.SmartTCPServer_for_testing
2182
create_configs_with_file_option(self)
2184
def assertGetHook(self, conf, name, value):
2188
config.OldConfigHooks.install_named_hook('get', hook, None)
2190
config.OldConfigHooks.uninstall_named_hook, 'get', None)
2191
self.assertLength(0, calls)
2192
actual_value = conf.get_option(name)
2193
self.assertEquals(value, actual_value)
2194
self.assertLength(1, calls)
2195
self.assertEquals((conf, name, value), calls[0])
2197
def test_get_hook_remote_branch(self):
2198
remote_branch = branch.Branch.open(self.get_url('tree'))
2199
self.assertGetHook(remote_branch._get_config(), 'file', 'branch')
2201
def test_get_hook_remote_bzrdir(self):
2202
remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
2203
conf = remote_bzrdir._get_config()
2204
conf.set_option('remotedir', 'file')
2205
self.assertGetHook(conf, 'file', 'remotedir')
2207
def assertSetHook(self, conf, name, value):
2211
config.OldConfigHooks.install_named_hook('set', hook, None)
2213
config.OldConfigHooks.uninstall_named_hook, 'set', None)
2214
self.assertLength(0, calls)
2215
conf.set_option(value, name)
2216
self.assertLength(1, calls)
2217
# We can't assert the conf object below as different configs use
2218
# different means to implement set_user_option and we care only about
2220
self.assertEquals((name, value), calls[0][1:])
2222
def test_set_hook_remote_branch(self):
2223
remote_branch = branch.Branch.open(self.get_url('tree'))
2224
self.addCleanup(remote_branch.lock_write().unlock)
2225
self.assertSetHook(remote_branch._get_config(), 'file', 'remote')
2227
def test_set_hook_remote_bzrdir(self):
2228
remote_branch = branch.Branch.open(self.get_url('tree'))
2229
self.addCleanup(remote_branch.lock_write().unlock)
2230
remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
2231
self.assertSetHook(remote_bzrdir._get_config(), 'file', 'remotedir')
2233
def assertLoadHook(self, expected_nb_calls, name, conf_class, *conf_args):
2237
config.OldConfigHooks.install_named_hook('load', hook, None)
2239
config.OldConfigHooks.uninstall_named_hook, 'load', None)
2240
self.assertLength(0, calls)
2242
conf = conf_class(*conf_args)
2243
# Access an option to trigger a load
2244
conf.get_option(name)
2245
self.assertLength(expected_nb_calls, calls)
2246
# Since we can't assert about conf, we just use the number of calls ;-/
2248
def test_load_hook_remote_branch(self):
2249
remote_branch = branch.Branch.open(self.get_url('tree'))
2250
self.assertLoadHook(1, 'file', remote.RemoteBranchConfig, remote_branch)
2252
def test_load_hook_remote_bzrdir(self):
2253
remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
2254
# The config file doesn't exist, set an option to force its creation
2255
conf = remote_bzrdir._get_config()
2256
conf.set_option('remotedir', 'file')
2257
# We get one call for the server and one call for the client, this is
2258
# caused by the differences in implementations betwen
2259
# SmartServerBzrDirRequestConfigFile (in smart/bzrdir.py) and
2260
# SmartServerBranchGetConfigFile (in smart/branch.py)
2261
self.assertLoadHook(2 ,'file', remote.RemoteBzrDirConfig, remote_bzrdir)
2263
def assertSaveHook(self, conf):
2267
config.OldConfigHooks.install_named_hook('save', hook, None)
2269
config.OldConfigHooks.uninstall_named_hook, 'save', None)
2270
self.assertLength(0, calls)
2271
# Setting an option triggers a save
2272
conf.set_option('foo', 'bar')
2273
self.assertLength(1, calls)
2274
# Since we can't assert about conf, we just use the number of calls ;-/
2276
def test_save_hook_remote_branch(self):
2277
remote_branch = branch.Branch.open(self.get_url('tree'))
2278
self.addCleanup(remote_branch.lock_write().unlock)
2279
self.assertSaveHook(remote_branch._get_config())
2281
def test_save_hook_remote_bzrdir(self):
2282
remote_branch = branch.Branch.open(self.get_url('tree'))
2283
self.addCleanup(remote_branch.lock_write().unlock)
2284
remote_bzrdir = bzrdir.BzrDir.open(self.get_url('tree'))
2285
self.assertSaveHook(remote_bzrdir._get_config())
2288
class TestOption(tests.TestCase):
2290
def test_default_value(self):
2291
opt = config.Option('foo', default='bar')
2292
self.assertEquals('bar', opt.get_default())
2294
def test_callable_default_value(self):
2295
def bar_as_unicode():
2297
opt = config.Option('foo', default=bar_as_unicode)
2298
self.assertEquals('bar', opt.get_default())
2300
def test_default_value_from_env(self):
2301
opt = config.Option('foo', default='bar', default_from_env=['FOO'])
2302
self.overrideEnv('FOO', 'quux')
2303
# Env variable provides a default taking over the option one
2304
self.assertEquals('quux', opt.get_default())
2306
def test_first_default_value_from_env_wins(self):
2307
opt = config.Option('foo', default='bar',
2308
default_from_env=['NO_VALUE', 'FOO', 'BAZ'])
2309
self.overrideEnv('FOO', 'foo')
2310
self.overrideEnv('BAZ', 'baz')
2311
# The first env var set wins
2312
self.assertEquals('foo', opt.get_default())
2314
def test_not_supported_list_default_value(self):
2315
self.assertRaises(AssertionError, config.Option, 'foo', default=[1])
2317
def test_not_supported_object_default_value(self):
2318
self.assertRaises(AssertionError, config.Option, 'foo',
2321
def test_not_supported_callable_default_value_not_unicode(self):
2322
def bar_not_unicode():
2324
opt = config.Option('foo', default=bar_not_unicode)
2325
self.assertRaises(AssertionError, opt.get_default)
2328
class TestOptionConverterMixin(object):
2330
def assertConverted(self, expected, opt, value):
2331
self.assertEquals(expected, opt.convert_from_unicode(None, value))
2333
def assertWarns(self, opt, value):
2336
warnings.append(args[0] % args[1:])
2337
self.overrideAttr(trace, 'warning', warning)
2338
self.assertEquals(None, opt.convert_from_unicode(None, value))
2339
self.assertLength(1, warnings)
2341
'Value "%s" is not valid for "%s"' % (value, opt.name),
2344
def assertErrors(self, opt, value):
2345
self.assertRaises(errors.ConfigOptionValueError,
2346
opt.convert_from_unicode, None, value)
2348
def assertConvertInvalid(self, opt, invalid_value):
2350
self.assertEquals(None, opt.convert_from_unicode(None, invalid_value))
2351
opt.invalid = 'warning'
2352
self.assertWarns(opt, invalid_value)
2353
opt.invalid = 'error'
2354
self.assertErrors(opt, invalid_value)
2357
class TestOptionWithBooleanConverter(tests.TestCase, TestOptionConverterMixin):
2359
def get_option(self):
2360
return config.Option('foo', help='A boolean.',
2361
from_unicode=config.bool_from_store)
2363
def test_convert_invalid(self):
2364
opt = self.get_option()
2365
# A string that is not recognized as a boolean
2366
self.assertConvertInvalid(opt, u'invalid-boolean')
2367
# A list of strings is never recognized as a boolean
2368
self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
2370
def test_convert_valid(self):
2371
opt = self.get_option()
2372
self.assertConverted(True, opt, u'True')
2373
self.assertConverted(True, opt, u'1')
2374
self.assertConverted(False, opt, u'False')
2377
class TestOptionWithIntegerConverter(tests.TestCase, TestOptionConverterMixin):
2379
def get_option(self):
2380
return config.Option('foo', help='An integer.',
2381
from_unicode=config.int_from_store)
2383
def test_convert_invalid(self):
2384
opt = self.get_option()
2385
# A string that is not recognized as an integer
2386
self.assertConvertInvalid(opt, u'forty-two')
2387
# A list of strings is never recognized as an integer
2388
self.assertConvertInvalid(opt, [u'a', u'list'])
2390
def test_convert_valid(self):
2391
opt = self.get_option()
2392
self.assertConverted(16, opt, u'16')
2395
class TestOptionWithSIUnitConverter(tests.TestCase, TestOptionConverterMixin):
2397
def get_option(self):
2398
return config.Option('foo', help='An integer in SI units.',
2399
from_unicode=config.int_SI_from_store)
2401
def test_convert_invalid(self):
2402
opt = self.get_option()
2403
self.assertConvertInvalid(opt, u'not-a-unit')
2404
self.assertConvertInvalid(opt, u'Gb') # Forgot the int
2405
self.assertConvertInvalid(opt, u'1b') # Forgot the unit
2406
self.assertConvertInvalid(opt, u'1GG')
2407
self.assertConvertInvalid(opt, u'1Mbb')
2408
self.assertConvertInvalid(opt, u'1MM')
2410
def test_convert_valid(self):
2411
opt = self.get_option()
2412
self.assertConverted(int(5e3), opt, u'5kb')
2413
self.assertConverted(int(5e6), opt, u'5M')
2414
self.assertConverted(int(5e6), opt, u'5MB')
2415
self.assertConverted(int(5e9), opt, u'5g')
2416
self.assertConverted(int(5e9), opt, u'5gB')
2417
self.assertConverted(100, opt, u'100')
2420
class TestListOption(tests.TestCase, TestOptionConverterMixin):
2422
def get_option(self):
2423
return config.ListOption('foo', help='A list.')
2425
def test_convert_invalid(self):
2426
opt = self.get_option()
2427
# We don't even try to convert a list into a list, we only expect
2429
self.assertConvertInvalid(opt, [1])
2430
# No string is invalid as all forms can be converted to a list
2432
def test_convert_valid(self):
2433
opt = self.get_option()
2434
# An empty string is an empty list
2435
self.assertConverted([], opt, '') # Using a bare str() just in case
2436
self.assertConverted([], opt, u'')
2438
self.assertConverted([u'True'], opt, u'True')
2440
self.assertConverted([u'42'], opt, u'42')
2442
self.assertConverted([u'bar'], opt, u'bar')
2445
class TestRegistryOption(tests.TestCase, TestOptionConverterMixin):
2447
def get_option(self, registry):
2448
return config.RegistryOption('foo', registry,
2449
help='A registry option.')
2451
def test_convert_invalid(self):
2452
registry = _mod_registry.Registry()
2453
opt = self.get_option(registry)
2454
self.assertConvertInvalid(opt, [1])
2455
self.assertConvertInvalid(opt, u"notregistered")
2457
def test_convert_valid(self):
2458
registry = _mod_registry.Registry()
2459
registry.register("someval", 1234)
2460
opt = self.get_option(registry)
2461
# Using a bare str() just in case
2462
self.assertConverted(1234, opt, "someval")
2463
self.assertConverted(1234, opt, u'someval')
2464
self.assertConverted(None, opt, None)
2466
def test_help(self):
2467
registry = _mod_registry.Registry()
2468
registry.register("someval", 1234, help="some option")
2469
registry.register("dunno", 1234, help="some other option")
2470
opt = self.get_option(registry)
2472
'A registry option.\n'
2474
'The following values are supported:\n'
2475
' dunno - some other option\n'
2476
' someval - some option\n',
2479
def test_get_help_text(self):
2480
registry = _mod_registry.Registry()
2481
registry.register("someval", 1234, help="some option")
2482
registry.register("dunno", 1234, help="some other option")
2483
opt = self.get_option(registry)
2485
'A registry option.\n'
2487
'The following values are supported:\n'
2488
' dunno - some other option\n'
2489
' someval - some option\n',
2490
opt.get_help_text())
2493
class TestOptionRegistry(tests.TestCase):
2496
super(TestOptionRegistry, self).setUp()
2497
# Always start with an empty registry
2498
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2499
self.registry = config.option_registry
2501
def test_register(self):
2502
opt = config.Option('foo')
2503
self.registry.register(opt)
2504
self.assertIs(opt, self.registry.get('foo'))
2506
def test_registered_help(self):
2507
opt = config.Option('foo', help='A simple option')
2508
self.registry.register(opt)
2509
self.assertEquals('A simple option', self.registry.get_help('foo'))
2511
lazy_option = config.Option('lazy_foo', help='Lazy help')
2513
def test_register_lazy(self):
2514
self.registry.register_lazy('lazy_foo', self.__module__,
2515
'TestOptionRegistry.lazy_option')
2516
self.assertIs(self.lazy_option, self.registry.get('lazy_foo'))
2518
def test_registered_lazy_help(self):
2519
self.registry.register_lazy('lazy_foo', self.__module__,
2520
'TestOptionRegistry.lazy_option')
2521
self.assertEquals('Lazy help', self.registry.get_help('lazy_foo'))
2524
class TestRegisteredOptions(tests.TestCase):
2525
"""All registered options should verify some constraints."""
2527
scenarios = [(key, {'option_name': key, 'option': option}) for key, option
2528
in config.option_registry.iteritems()]
2531
super(TestRegisteredOptions, self).setUp()
2532
self.registry = config.option_registry
2534
def test_proper_name(self):
2535
# An option should be registered under its own name, this can't be
2536
# checked at registration time for the lazy ones.
2537
self.assertEquals(self.option_name, self.option.name)
2539
def test_help_is_set(self):
2540
option_help = self.registry.get_help(self.option_name)
2541
self.assertNotEquals(None, option_help)
2542
# Come on, think about the user, he really wants to know what the
2544
self.assertIsNot(None, option_help)
2545
self.assertNotEquals('', option_help)
2548
class TestSection(tests.TestCase):
2550
# FIXME: Parametrize so that all sections produced by Stores run these
2551
# tests -- vila 2011-04-01
2553
def test_get_a_value(self):
2554
a_dict = dict(foo='bar')
2555
section = config.Section('myID', a_dict)
2556
self.assertEquals('bar', section.get('foo'))
2558
def test_get_unknown_option(self):
2560
section = config.Section(None, a_dict)
2561
self.assertEquals('out of thin air',
2562
section.get('foo', 'out of thin air'))
2564
def test_options_is_shared(self):
2566
section = config.Section(None, a_dict)
2567
self.assertIs(a_dict, section.options)
2570
class TestMutableSection(tests.TestCase):
2572
scenarios = [('mutable',
2574
lambda opts: config.MutableSection('myID', opts)},),
2578
a_dict = dict(foo='bar')
2579
section = self.get_section(a_dict)
2580
section.set('foo', 'new_value')
2581
self.assertEquals('new_value', section.get('foo'))
2582
# The change appears in the shared section
2583
self.assertEquals('new_value', a_dict.get('foo'))
2584
# We keep track of the change
2585
self.assertTrue('foo' in section.orig)
2586
self.assertEquals('bar', section.orig.get('foo'))
2588
def test_set_preserve_original_once(self):
2589
a_dict = dict(foo='bar')
2590
section = self.get_section(a_dict)
2591
section.set('foo', 'first_value')
2592
section.set('foo', 'second_value')
2593
# We keep track of the original value
2594
self.assertTrue('foo' in section.orig)
2595
self.assertEquals('bar', section.orig.get('foo'))
2597
def test_remove(self):
2598
a_dict = dict(foo='bar')
2599
section = self.get_section(a_dict)
2600
section.remove('foo')
2601
# We get None for unknown options via the default value
2602
self.assertEquals(None, section.get('foo'))
2603
# Or we just get the default value
2604
self.assertEquals('unknown', section.get('foo', 'unknown'))
2605
self.assertFalse('foo' in section.options)
2606
# We keep track of the deletion
2607
self.assertTrue('foo' in section.orig)
2608
self.assertEquals('bar', section.orig.get('foo'))
2610
def test_remove_new_option(self):
2612
section = self.get_section(a_dict)
2613
section.set('foo', 'bar')
2614
section.remove('foo')
2615
self.assertFalse('foo' in section.options)
2616
# The option didn't exist initially so it we need to keep track of it
2617
# with a special value
2618
self.assertTrue('foo' in section.orig)
2619
self.assertEquals(config._NewlyCreatedOption, section.orig['foo'])
2622
class TestCommandLineStore(tests.TestCase):
2625
super(TestCommandLineStore, self).setUp()
2626
self.store = config.CommandLineStore()
2627
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2629
def get_section(self):
2630
"""Get the unique section for the command line overrides."""
2631
sections = list(self.store.get_sections())
2632
self.assertLength(1, sections)
2633
store, section = sections[0]
2634
self.assertEquals(self.store, store)
2637
def test_no_override(self):
2638
self.store._from_cmdline([])
2639
section = self.get_section()
2640
self.assertLength(0, list(section.iter_option_names()))
2642
def test_simple_override(self):
2643
self.store._from_cmdline(['a=b'])
2644
section = self.get_section()
2645
self.assertEqual('b', section.get('a'))
2647
def test_list_override(self):
2648
opt = config.ListOption('l')
2649
config.option_registry.register(opt)
2650
self.store._from_cmdline(['l=1,2,3'])
2651
val = self.get_section().get('l')
2652
self.assertEqual('1,2,3', val)
2653
# Reminder: lists should be registered as such explicitely, otherwise
2654
# the conversion needs to be done afterwards.
2655
self.assertEqual(['1', '2', '3'],
2656
opt.convert_from_unicode(self.store, val))
2658
def test_multiple_overrides(self):
2659
self.store._from_cmdline(['a=b', 'x=y'])
2660
section = self.get_section()
2661
self.assertEquals('b', section.get('a'))
2662
self.assertEquals('y', section.get('x'))
2664
def test_wrong_syntax(self):
2665
self.assertRaises(errors.BzrCommandError,
2666
self.store._from_cmdline, ['a=b', 'c'])
2668
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
2670
scenarios = [(key, {'get_store': builder}) for key, builder
2671
in config.test_store_builder_registry.iteritems()] + [
2672
('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
2675
store = self.get_store(self)
2676
if type(store) == config.TransportIniFileStore:
2677
raise tests.TestNotApplicable(
2678
"%s is not a concrete Store implementation"
2679
" so it doesn't need an id" % (store.__class__.__name__,))
2680
self.assertIsNot(None, store.id)
2683
class TestStore(tests.TestCaseWithTransport):
2685
def assertSectionContent(self, expected, (store, section)):
2686
"""Assert that some options have the proper values in a section."""
2687
expected_name, expected_options = expected
2688
self.assertEquals(expected_name, section.id)
2691
dict([(k, section.get(k)) for k in expected_options.keys()]))
2694
class TestReadonlyStore(TestStore):
2696
scenarios = [(key, {'get_store': builder}) for key, builder
2697
in config.test_store_builder_registry.iteritems()]
2699
def test_building_delays_load(self):
2700
store = self.get_store(self)
2701
self.assertEquals(False, store.is_loaded())
2702
store._load_from_string('')
2703
self.assertEquals(True, store.is_loaded())
2705
def test_get_no_sections_for_empty(self):
2706
store = self.get_store(self)
2707
store._load_from_string('')
2708
self.assertEquals([], list(store.get_sections()))
2710
def test_get_default_section(self):
2711
store = self.get_store(self)
2712
store._load_from_string('foo=bar')
2713
sections = list(store.get_sections())
2714
self.assertLength(1, sections)
2715
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2717
def test_get_named_section(self):
2718
store = self.get_store(self)
2719
store._load_from_string('[baz]\nfoo=bar')
2720
sections = list(store.get_sections())
2721
self.assertLength(1, sections)
2722
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2724
def test_load_from_string_fails_for_non_empty_store(self):
2725
store = self.get_store(self)
2726
store._load_from_string('foo=bar')
2727
self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
2730
class TestStoreQuoting(TestStore):
2732
scenarios = [(key, {'get_store': builder}) for key, builder
2733
in config.test_store_builder_registry.iteritems()]
2736
super(TestStoreQuoting, self).setUp()
2737
self.store = self.get_store(self)
2738
# We need a loaded store but any content will do
2739
self.store._load_from_string('')
2741
def assertIdempotent(self, s):
2742
"""Assert that quoting an unquoted string is a no-op and vice-versa.
2744
What matters here is that option values, as they appear in a store, can
2745
be safely round-tripped out of the store and back.
2747
:param s: A string, quoted if required.
2749
self.assertEquals(s, self.store.quote(self.store.unquote(s)))
2750
self.assertEquals(s, self.store.unquote(self.store.quote(s)))
2752
def test_empty_string(self):
2753
if isinstance(self.store, config.IniFileStore):
2754
# configobj._quote doesn't handle empty values
2755
self.assertRaises(AssertionError,
2756
self.assertIdempotent, '')
2758
self.assertIdempotent('')
2759
# But quoted empty strings are ok
2760
self.assertIdempotent('""')
2762
def test_embedded_spaces(self):
2763
self.assertIdempotent('" a b c "')
2765
def test_embedded_commas(self):
2766
self.assertIdempotent('" a , b c "')
2768
def test_simple_comma(self):
2769
if isinstance(self.store, config.IniFileStore):
2770
# configobj requires that lists are special-cased
2771
self.assertRaises(AssertionError,
2772
self.assertIdempotent, ',')
2774
self.assertIdempotent(',')
2775
# When a single comma is required, quoting is also required
2776
self.assertIdempotent('","')
2778
def test_list(self):
2779
if isinstance(self.store, config.IniFileStore):
2780
# configobj requires that lists are special-cased
2781
self.assertRaises(AssertionError,
2782
self.assertIdempotent, 'a,b')
2784
self.assertIdempotent('a,b')
2787
class TestDictFromStore(tests.TestCase):
2789
def test_unquote_not_string(self):
2790
conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
2791
value = conf.get('a_section')
2792
# Urgh, despite 'conf' asking for the no-name section, we get the
2793
# content of another section as a dict o_O
2794
self.assertEquals({'a': '1'}, value)
2795
unquoted = conf.store.unquote(value)
2796
# Which cannot be unquoted but shouldn't crash either (the use cases
2797
# are getting the value or displaying it. In the later case, '%s' will
2799
self.assertEquals({'a': '1'}, unquoted)
2800
self.assertEquals("{u'a': u'1'}", '%s' % (unquoted,))
2803
class TestIniFileStoreContent(tests.TestCaseWithTransport):
2804
"""Simulate loading a config store with content of various encodings.
2806
All files produced by bzr are in utf8 content.
2808
Users may modify them manually and end up with a file that can't be
2809
loaded. We need to issue proper error messages in this case.
2812
invalid_utf8_char = '\xff'
2814
def test_load_utf8(self):
2815
"""Ensure we can load an utf8-encoded file."""
2816
t = self.get_transport()
2817
# From http://pad.lv/799212
2818
unicode_user = u'b\N{Euro Sign}ar'
2819
unicode_content = u'user=%s' % (unicode_user,)
2820
utf8_content = unicode_content.encode('utf8')
2821
# Store the raw content in the config file
2822
t.put_bytes('foo.conf', utf8_content)
2823
store = config.TransportIniFileStore(t, 'foo.conf')
2825
stack = config.Stack([store.get_sections], store)
2826
self.assertEquals(unicode_user, stack.get('user'))
2828
def test_load_non_ascii(self):
2829
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2830
t = self.get_transport()
2831
t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
2832
store = config.TransportIniFileStore(t, 'foo.conf')
2833
self.assertRaises(errors.ConfigContentError, store.load)
2835
def test_load_erroneous_content(self):
2836
"""Ensure we display a proper error on content that can't be parsed."""
2837
t = self.get_transport()
2838
t.put_bytes('foo.conf', '[open_section\n')
2839
store = config.TransportIniFileStore(t, 'foo.conf')
2840
self.assertRaises(errors.ParseConfigError, store.load)
2842
def test_load_permission_denied(self):
2843
"""Ensure we get warned when trying to load an inaccessible file."""
2846
warnings.append(args[0] % args[1:])
2847
self.overrideAttr(trace, 'warning', warning)
2849
t = self.get_transport()
2851
def get_bytes(relpath):
2852
raise errors.PermissionDenied(relpath, "")
2853
t.get_bytes = get_bytes
2854
store = config.TransportIniFileStore(t, 'foo.conf')
2855
self.assertRaises(errors.PermissionDenied, store.load)
2858
[u'Permission denied while trying to load configuration store %s.'
2859
% store.external_url()])
2862
class TestIniConfigContent(tests.TestCaseWithTransport):
2863
"""Simulate loading a IniBasedConfig with content of various encodings.
2865
All files produced by bzr are in utf8 content.
2867
Users may modify them manually and end up with a file that can't be
2868
loaded. We need to issue proper error messages in this case.
2871
invalid_utf8_char = '\xff'
2873
def test_load_utf8(self):
2874
"""Ensure we can load an utf8-encoded file."""
2875
# From http://pad.lv/799212
2876
unicode_user = u'b\N{Euro Sign}ar'
2877
unicode_content = u'user=%s' % (unicode_user,)
2878
utf8_content = unicode_content.encode('utf8')
2879
# Store the raw content in the config file
2880
with open('foo.conf', 'wb') as f:
2881
f.write(utf8_content)
2882
conf = config.IniBasedConfig(file_name='foo.conf')
2883
self.assertEquals(unicode_user, conf.get_user_option('user'))
2885
def test_load_badly_encoded_content(self):
2886
"""Ensure we display a proper error on non-ascii, non utf-8 content."""
2887
with open('foo.conf', 'wb') as f:
2888
f.write('user=foo\n#%s\n' % (self.invalid_utf8_char,))
2889
conf = config.IniBasedConfig(file_name='foo.conf')
2890
self.assertRaises(errors.ConfigContentError, conf._get_parser)
2892
def test_load_erroneous_content(self):
2893
"""Ensure we display a proper error on content that can't be parsed."""
2894
with open('foo.conf', 'wb') as f:
2895
f.write('[open_section\n')
2896
conf = config.IniBasedConfig(file_name='foo.conf')
2897
self.assertRaises(errors.ParseConfigError, conf._get_parser)
2900
class TestMutableStore(TestStore):
2902
scenarios = [(key, {'store_id': key, 'get_store': builder}) for key, builder
2903
in config.test_store_builder_registry.iteritems()]
2906
super(TestMutableStore, self).setUp()
2907
self.transport = self.get_transport()
2909
def has_store(self, store):
2910
store_basename = urlutils.relative_url(self.transport.external_url(),
2911
store.external_url())
2912
return self.transport.has(store_basename)
2914
def test_save_empty_creates_no_file(self):
2915
# FIXME: There should be a better way than relying on the test
2916
# parametrization to identify branch.conf -- vila 2011-0526
2917
if self.store_id in ('branch', 'remote_branch'):
2918
raise tests.TestNotApplicable(
2919
'branch.conf is *always* created when a branch is initialized')
2920
store = self.get_store(self)
2922
self.assertEquals(False, self.has_store(store))
2924
def test_save_emptied_succeeds(self):
2925
store = self.get_store(self)
2926
store._load_from_string('foo=bar\n')
2927
section = store.get_mutable_section(None)
2928
section.remove('foo')
2930
self.assertEquals(True, self.has_store(store))
2931
modified_store = self.get_store(self)
2932
sections = list(modified_store.get_sections())
2933
self.assertLength(0, sections)
2935
def test_save_with_content_succeeds(self):
2936
# FIXME: There should be a better way than relying on the test
2937
# parametrization to identify branch.conf -- vila 2011-0526
2938
if self.store_id in ('branch', 'remote_branch'):
2939
raise tests.TestNotApplicable(
2940
'branch.conf is *always* created when a branch is initialized')
2941
store = self.get_store(self)
2942
store._load_from_string('foo=bar\n')
2943
self.assertEquals(False, self.has_store(store))
2945
self.assertEquals(True, self.has_store(store))
2946
modified_store = self.get_store(self)
2947
sections = list(modified_store.get_sections())
2948
self.assertLength(1, sections)
2949
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2951
def test_set_option_in_empty_store(self):
2952
store = self.get_store(self)
2953
section = store.get_mutable_section(None)
2954
section.set('foo', 'bar')
2956
modified_store = self.get_store(self)
2957
sections = list(modified_store.get_sections())
2958
self.assertLength(1, sections)
2959
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2961
def test_set_option_in_default_section(self):
2962
store = self.get_store(self)
2963
store._load_from_string('')
2964
section = store.get_mutable_section(None)
2965
section.set('foo', 'bar')
2967
modified_store = self.get_store(self)
2968
sections = list(modified_store.get_sections())
2969
self.assertLength(1, sections)
2970
self.assertSectionContent((None, {'foo': 'bar'}), sections[0])
2972
def test_set_option_in_named_section(self):
2973
store = self.get_store(self)
2974
store._load_from_string('')
2975
section = store.get_mutable_section('baz')
2976
section.set('foo', 'bar')
2978
modified_store = self.get_store(self)
2979
sections = list(modified_store.get_sections())
2980
self.assertLength(1, sections)
2981
self.assertSectionContent(('baz', {'foo': 'bar'}), sections[0])
2983
def test_load_hook(self):
2984
# We first needs to ensure that the store exists
2985
store = self.get_store(self)
2986
section = store.get_mutable_section('baz')
2987
section.set('foo', 'bar')
2989
# Now we can try to load it
2990
store = self.get_store(self)
2994
config.ConfigHooks.install_named_hook('load', hook, None)
2995
self.assertLength(0, calls)
2997
self.assertLength(1, calls)
2998
self.assertEquals((store,), calls[0])
3000
def test_save_hook(self):
3004
config.ConfigHooks.install_named_hook('save', hook, None)
3005
self.assertLength(0, calls)
3006
store = self.get_store(self)
3007
section = store.get_mutable_section('baz')
3008
section.set('foo', 'bar')
3010
self.assertLength(1, calls)
3011
self.assertEquals((store,), calls[0])
3013
def test_set_mark_dirty(self):
3014
stack = config.MemoryStack('')
3015
self.assertLength(0, stack.store.dirty_sections)
3016
stack.set('foo', 'baz')
3017
self.assertLength(1, stack.store.dirty_sections)
3018
self.assertTrue(stack.store._need_saving())
3020
def test_remove_mark_dirty(self):
3021
stack = config.MemoryStack('foo=bar')
3022
self.assertLength(0, stack.store.dirty_sections)
3024
self.assertLength(1, stack.store.dirty_sections)
3025
self.assertTrue(stack.store._need_saving())
3028
class TestStoreSaveChanges(tests.TestCaseWithTransport):
3029
"""Tests that config changes are kept in memory and saved on-demand."""
3032
super(TestStoreSaveChanges, self).setUp()
3033
self.transport = self.get_transport()
3034
# Most of the tests involve two stores pointing to the same persistent
3035
# storage to observe the effects of concurrent changes
3036
self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
3037
self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
3040
self.warnings.append(args[0] % args[1:])
3041
self.overrideAttr(trace, 'warning', warning)
3043
def has_store(self, store):
3044
store_basename = urlutils.relative_url(self.transport.external_url(),
3045
store.external_url())
3046
return self.transport.has(store_basename)
3048
def get_stack(self, store):
3049
# Any stack will do as long as it uses the right store, just a single
3050
# no-name section is enough
3051
return config.Stack([store.get_sections], store)
3053
def test_no_changes_no_save(self):
3054
s = self.get_stack(self.st1)
3055
s.store.save_changes()
3056
self.assertEquals(False, self.has_store(self.st1))
3058
def test_unrelated_concurrent_update(self):
3059
s1 = self.get_stack(self.st1)
3060
s2 = self.get_stack(self.st2)
3061
s1.set('foo', 'bar')
3062
s2.set('baz', 'quux')
3064
# Changes don't propagate magically
3065
self.assertEquals(None, s1.get('baz'))
3066
s2.store.save_changes()
3067
self.assertEquals('quux', s2.get('baz'))
3068
# Changes are acquired when saving
3069
self.assertEquals('bar', s2.get('foo'))
3070
# Since there is no overlap, no warnings are emitted
3071
self.assertLength(0, self.warnings)
3073
def test_concurrent_update_modified(self):
3074
s1 = self.get_stack(self.st1)
3075
s2 = self.get_stack(self.st2)
3076
s1.set('foo', 'bar')
3077
s2.set('foo', 'baz')
3080
s2.store.save_changes()
3081
self.assertEquals('baz', s2.get('foo'))
3082
# But the user get a warning
3083
self.assertLength(1, self.warnings)
3084
warning = self.warnings[0]
3085
self.assertStartsWith(warning, 'Option foo in section None')
3086
self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
3087
' The baz value will be saved.')
3089
def test_concurrent_deletion(self):
3090
self.st1._load_from_string('foo=bar')
3092
s1 = self.get_stack(self.st1)
3093
s2 = self.get_stack(self.st2)
3096
s1.store.save_changes()
3098
self.assertLength(0, self.warnings)
3099
s2.store.save_changes()
3101
self.assertLength(1, self.warnings)
3102
warning = self.warnings[0]
3103
self.assertStartsWith(warning, 'Option foo in section None')
3104
self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
3105
' The <DELETED> value will be saved.')
3108
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
3110
def get_store(self):
3111
return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3113
def test_get_quoted_string(self):
3114
store = self.get_store()
3115
store._load_from_string('foo= " abc "')
3116
stack = config.Stack([store.get_sections])
3117
self.assertEquals(' abc ', stack.get('foo'))
3119
def test_set_quoted_string(self):
3120
store = self.get_store()
3121
stack = config.Stack([store.get_sections], store)
3122
stack.set('foo', ' a b c ')
3124
self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
3127
class TestTransportIniFileStore(TestStore):
3129
def test_loading_unknown_file_fails(self):
3130
store = config.TransportIniFileStore(self.get_transport(),
3132
self.assertRaises(errors.NoSuchFile, store.load)
3134
def test_invalid_content(self):
3135
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3136
self.assertEquals(False, store.is_loaded())
3137
exc = self.assertRaises(
3138
errors.ParseConfigError, store._load_from_string,
3139
'this is invalid !')
3140
self.assertEndsWith(exc.filename, 'foo.conf')
3141
# And the load failed
3142
self.assertEquals(False, store.is_loaded())
3144
def test_get_embedded_sections(self):
3145
# A more complicated example (which also shows that section names and
3146
# option names share the same name space...)
3147
# FIXME: This should be fixed by forbidding dicts as values ?
3148
# -- vila 2011-04-05
3149
store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3150
store._load_from_string('''
3154
foo_in_DEFAULT=foo_DEFAULT
3162
sections = list(store.get_sections())
3163
self.assertLength(4, sections)
3164
# The default section has no name.
3165
# List values are provided as strings and need to be explicitly
3166
# converted by specifying from_unicode=list_from_store at option
3168
self.assertSectionContent((None, {'foo': 'bar', 'l': u'1,2'}),
3170
self.assertSectionContent(
3171
('DEFAULT', {'foo_in_DEFAULT': 'foo_DEFAULT'}), sections[1])
3172
self.assertSectionContent(
3173
('bar', {'foo_in_bar': 'barbar'}), sections[2])
3174
# sub sections are provided as embedded dicts.
3175
self.assertSectionContent(
3176
('baz', {'foo_in_baz': 'barbaz', 'qux': {'foo_in_qux': 'quux'}}),
3180
class TestLockableIniFileStore(TestStore):
3182
def test_create_store_in_created_dir(self):
3183
self.assertPathDoesNotExist('dir')
3184
t = self.get_transport('dir/subdir')
3185
store = config.LockableIniFileStore(t, 'foo.conf')
3186
store.get_mutable_section(None).set('foo', 'bar')
3188
self.assertPathExists('dir/subdir')
3191
class TestConcurrentStoreUpdates(TestStore):
3192
"""Test that Stores properly handle conccurent updates.
3194
New Store implementation may fail some of these tests but until such
3195
implementations exist it's hard to properly filter them from the scenarios
3196
applied here. If you encounter such a case, contact the bzr devs.
3199
scenarios = [(key, {'get_stack': builder}) for key, builder
3200
in config.test_stack_builder_registry.iteritems()]
3203
super(TestConcurrentStoreUpdates, self).setUp()
3204
self.stack = self.get_stack(self)
3205
if not isinstance(self.stack, config._CompatibleStack):
3206
raise tests.TestNotApplicable(
3207
'%s is not meant to be compatible with the old config design'
3209
self.stack.set('one', '1')
3210
self.stack.set('two', '2')
3212
self.stack.store.save()
3214
def test_simple_read_access(self):
3215
self.assertEquals('1', self.stack.get('one'))
3217
def test_simple_write_access(self):
3218
self.stack.set('one', 'one')
3219
self.assertEquals('one', self.stack.get('one'))
3221
def test_listen_to_the_last_speaker(self):
3223
c2 = self.get_stack(self)
3224
c1.set('one', 'ONE')
3225
c2.set('two', 'TWO')
3226
self.assertEquals('ONE', c1.get('one'))
3227
self.assertEquals('TWO', c2.get('two'))
3228
# The second update respect the first one
3229
self.assertEquals('ONE', c2.get('one'))
3231
def test_last_speaker_wins(self):
3232
# If the same config is not shared, the same variable modified twice
3233
# can only see a single result.
3235
c2 = self.get_stack(self)
3238
self.assertEquals('c2', c2.get('one'))
3239
# The first modification is still available until another refresh
3241
self.assertEquals('c1', c1.get('one'))
3242
c1.set('two', 'done')
3243
self.assertEquals('c2', c1.get('one'))
3245
def test_writes_are_serialized(self):
3247
c2 = self.get_stack(self)
3249
# We spawn a thread that will pause *during* the config saving.
3250
before_writing = threading.Event()
3251
after_writing = threading.Event()
3252
writing_done = threading.Event()
3253
c1_save_without_locking_orig = c1.store.save_without_locking
3254
def c1_save_without_locking():
3255
before_writing.set()
3256
c1_save_without_locking_orig()
3257
# The lock is held. We wait for the main thread to decide when to
3259
after_writing.wait()
3260
c1.store.save_without_locking = c1_save_without_locking
3264
t1 = threading.Thread(target=c1_set)
3265
# Collect the thread after the test
3266
self.addCleanup(t1.join)
3267
# Be ready to unblock the thread if the test goes wrong
3268
self.addCleanup(after_writing.set)
3270
before_writing.wait()
3271
self.assertRaises(errors.LockContention,
3272
c2.set, 'one', 'c2')
3273
self.assertEquals('c1', c1.get('one'))
3274
# Let the lock be released
3278
self.assertEquals('c2', c2.get('one'))
3280
def test_read_while_writing(self):
3282
# We spawn a thread that will pause *during* the write
3283
ready_to_write = threading.Event()
3284
do_writing = threading.Event()
3285
writing_done = threading.Event()
3286
# We override the _save implementation so we know the store is locked
3287
c1_save_without_locking_orig = c1.store.save_without_locking
3288
def c1_save_without_locking():
3289
ready_to_write.set()
3290
# The lock is held. We wait for the main thread to decide when to
3293
c1_save_without_locking_orig()
3295
c1.store.save_without_locking = c1_save_without_locking
3298
t1 = threading.Thread(target=c1_set)
3299
# Collect the thread after the test
3300
self.addCleanup(t1.join)
3301
# Be ready to unblock the thread if the test goes wrong
3302
self.addCleanup(do_writing.set)
3304
# Ensure the thread is ready to write
3305
ready_to_write.wait()
3306
self.assertEquals('c1', c1.get('one'))
3307
# If we read during the write, we get the old value
3308
c2 = self.get_stack(self)
3309
self.assertEquals('1', c2.get('one'))
3310
# Let the writing occur and ensure it occurred
3313
# Now we get the updated value
3314
c3 = self.get_stack(self)
3315
self.assertEquals('c1', c3.get('one'))
3317
# FIXME: It may be worth looking into removing the lock dir when it's not
3318
# needed anymore and look at possible fallouts for concurrent lockers. This
3319
# will matter if/when we use config files outside of bazaar directories
3320
# (.bazaar or .bzr) -- vila 20110-04-111
3323
class TestSectionMatcher(TestStore):
3325
scenarios = [('location', {'matcher': config.LocationMatcher}),
3326
('id', {'matcher': config.NameMatcher}),]
3329
super(TestSectionMatcher, self).setUp()
3330
# Any simple store is good enough
3331
self.get_store = config.test_store_builder_registry.get('configobj')
3333
def test_no_matches_for_empty_stores(self):
3334
store = self.get_store(self)
3335
store._load_from_string('')
3336
matcher = self.matcher(store, '/bar')
3337
self.assertEquals([], list(matcher.get_sections()))
3339
def test_build_doesnt_load_store(self):
3340
store = self.get_store(self)
3341
matcher = self.matcher(store, '/bar')
3342
self.assertFalse(store.is_loaded())
3345
class TestLocationSection(tests.TestCase):
3347
def get_section(self, options, extra_path):
3348
section = config.Section('foo', options)
3349
return config.LocationSection(section, extra_path)
3351
def test_simple_option(self):
3352
section = self.get_section({'foo': 'bar'}, '')
3353
self.assertEquals('bar', section.get('foo'))
3355
def test_option_with_extra_path(self):
3356
section = self.get_section({'foo': 'bar', 'foo:policy': 'appendpath'},
3358
self.assertEquals('bar/baz', section.get('foo'))
3360
def test_invalid_policy(self):
3361
section = self.get_section({'foo': 'bar', 'foo:policy': 'die'},
3363
# invalid policies are ignored
3364
self.assertEquals('bar', section.get('foo'))
3367
class TestLocationMatcher(TestStore):
3370
super(TestLocationMatcher, self).setUp()
3371
# Any simple store is good enough
3372
self.get_store = config.test_store_builder_registry.get('configobj')
3374
def test_unrelated_section_excluded(self):
3375
store = self.get_store(self)
3376
store._load_from_string('''
3384
section=/foo/bar/baz
3388
self.assertEquals(['/foo', '/foo/baz', '/foo/bar', '/foo/bar/baz',
3390
[section.id for _, section in store.get_sections()])
3391
matcher = config.LocationMatcher(store, '/foo/bar/quux')
3392
sections = [section for _, section in matcher.get_sections()]
3393
self.assertEquals(['/foo/bar', '/foo'],
3394
[section.id for section in sections])
3395
self.assertEquals(['quux', 'bar/quux'],
3396
[section.extra_path for section in sections])
3398
def test_more_specific_sections_first(self):
3399
store = self.get_store(self)
3400
store._load_from_string('''
3406
self.assertEquals(['/foo', '/foo/bar'],
3407
[section.id for _, section in store.get_sections()])
3408
matcher = config.LocationMatcher(store, '/foo/bar/baz')
3409
sections = [section for _, section in matcher.get_sections()]
3410
self.assertEquals(['/foo/bar', '/foo'],
3411
[section.id for section in sections])
3412
self.assertEquals(['baz', 'bar/baz'],
3413
[section.extra_path for section in sections])
3415
def test_appendpath_in_no_name_section(self):
3416
# It's a bit weird to allow appendpath in a no-name section, but
3417
# someone may found a use for it
3418
store = self.get_store(self)
3419
store._load_from_string('''
3421
foo:policy = appendpath
3423
matcher = config.LocationMatcher(store, 'dir/subdir')
3424
sections = list(matcher.get_sections())
3425
self.assertLength(1, sections)
3426
self.assertEquals('bar/dir/subdir', sections[0][1].get('foo'))
3428
def test_file_urls_are_normalized(self):
3429
store = self.get_store(self)
3430
if sys.platform == 'win32':
3431
expected_url = 'file:///C:/dir/subdir'
3432
expected_location = 'C:/dir/subdir'
3434
expected_url = 'file:///dir/subdir'
3435
expected_location = '/dir/subdir'
3436
matcher = config.LocationMatcher(store, expected_url)
3437
self.assertEquals(expected_location, matcher.location)
3440
class TestStartingPathMatcher(TestStore):
3443
super(TestStartingPathMatcher, self).setUp()
3444
# Any simple store is good enough
3445
self.store = config.IniFileStore()
3447
def assertSectionIDs(self, expected, location, content):
3448
self.store._load_from_string(content)
3449
matcher = config.StartingPathMatcher(self.store, location)
3450
sections = list(matcher.get_sections())
3451
self.assertLength(len(expected), sections)
3452
self.assertEqual(expected, [section.id for _, section in sections])
3455
def test_empty(self):
3456
self.assertSectionIDs([], self.get_url(), '')
3458
def test_url_vs_local_paths(self):
3459
# The matcher location is an url and the section names are local paths
3460
sections = self.assertSectionIDs(['/foo/bar', '/foo'],
3461
'file:///foo/bar/baz', '''\
3466
def test_local_path_vs_url(self):
3467
# The matcher location is a local path and the section names are urls
3468
sections = self.assertSectionIDs(['file:///foo/bar', 'file:///foo'],
3469
'/foo/bar/baz', '''\
3475
def test_no_name_section_included_when_present(self):
3476
# Note that other tests will cover the case where the no-name section
3477
# is empty and as such, not included.
3478
sections = self.assertSectionIDs(['/foo/bar', '/foo', None],
3479
'/foo/bar/baz', '''\
3480
option = defined so the no-name section exists
3484
self.assertEquals(['baz', 'bar/baz', '/foo/bar/baz'],
3485
[s.locals['relpath'] for _, s in sections])
3487
def test_order_reversed(self):
3488
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3493
def test_unrelated_section_excluded(self):
3494
self.assertSectionIDs(['/foo/bar', '/foo'], '/foo/bar/baz', '''\
3500
def test_glob_included(self):
3501
sections = self.assertSectionIDs(['/foo/*/baz', '/foo/b*', '/foo'],
3502
'/foo/bar/baz', '''\
3508
# Note that 'baz' as a relpath for /foo/b* is not fully correct, but
3509
# nothing really is... as far using {relpath} to append it to something
3510
# else, this seems good enough though.
3511
self.assertEquals(['', 'baz', 'bar/baz'],
3512
[s.locals['relpath'] for _, s in sections])
3514
def test_respect_order(self):
3515
self.assertSectionIDs(['/foo', '/foo/b*', '/foo/*/baz'],
3516
'/foo/bar/baz', '''\
3524
class TestNameMatcher(TestStore):
3527
super(TestNameMatcher, self).setUp()
3528
self.matcher = config.NameMatcher
3529
# Any simple store is good enough
3530
self.get_store = config.test_store_builder_registry.get('configobj')
3532
def get_matching_sections(self, name):
3533
store = self.get_store(self)
3534
store._load_from_string('''
3542
matcher = self.matcher(store, name)
3543
return list(matcher.get_sections())
3545
def test_matching(self):
3546
sections = self.get_matching_sections('foo')
3547
self.assertLength(1, sections)
3548
self.assertSectionContent(('foo', {'option': 'foo'}), sections[0])
3550
def test_not_matching(self):
3551
sections = self.get_matching_sections('baz')
3552
self.assertLength(0, sections)
3555
class TestBaseStackGet(tests.TestCase):
3558
super(TestBaseStackGet, self).setUp()
3559
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3561
def test_get_first_definition(self):
3562
store1 = config.IniFileStore()
3563
store1._load_from_string('foo=bar')
3564
store2 = config.IniFileStore()
3565
store2._load_from_string('foo=baz')
3566
conf = config.Stack([store1.get_sections, store2.get_sections])
3567
self.assertEquals('bar', conf.get('foo'))
3569
def test_get_with_registered_default_value(self):
3570
config.option_registry.register(config.Option('foo', default='bar'))
3571
conf_stack = config.Stack([])
3572
self.assertEquals('bar', conf_stack.get('foo'))
3574
def test_get_without_registered_default_value(self):
3575
config.option_registry.register(config.Option('foo'))
3576
conf_stack = config.Stack([])
3577
self.assertEquals(None, conf_stack.get('foo'))
3579
def test_get_without_default_value_for_not_registered(self):
3580
conf_stack = config.Stack([])
3581
self.assertEquals(None, conf_stack.get('foo'))
3583
def test_get_for_empty_section_callable(self):
3584
conf_stack = config.Stack([lambda : []])
3585
self.assertEquals(None, conf_stack.get('foo'))
3587
def test_get_for_broken_callable(self):
3588
# Trying to use and invalid callable raises an exception on first use
3589
conf_stack = config.Stack([object])
3590
self.assertRaises(TypeError, conf_stack.get, 'foo')
3593
class TestStackWithSimpleStore(tests.TestCase):
3596
super(TestStackWithSimpleStore, self).setUp()
3597
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3598
self.registry = config.option_registry
3600
def get_conf(self, content=None):
3601
return config.MemoryStack(content)
3603
def test_override_value_from_env(self):
3604
self.registry.register(
3605
config.Option('foo', default='bar', override_from_env=['FOO']))
3606
self.overrideEnv('FOO', 'quux')
3607
# Env variable provides a default taking over the option one
3608
conf = self.get_conf('foo=store')
3609
self.assertEquals('quux', conf.get('foo'))
3611
def test_first_override_value_from_env_wins(self):
3612
self.registry.register(
3613
config.Option('foo', default='bar',
3614
override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
3615
self.overrideEnv('FOO', 'foo')
3616
self.overrideEnv('BAZ', 'baz')
3617
# The first env var set wins
3618
conf = self.get_conf('foo=store')
3619
self.assertEquals('foo', conf.get('foo'))
3622
class TestMemoryStack(tests.TestCase):
3625
conf = config.MemoryStack('foo=bar')
3626
self.assertEquals('bar', conf.get('foo'))
3629
conf = config.MemoryStack('foo=bar')
3630
conf.set('foo', 'baz')
3631
self.assertEquals('baz', conf.get('foo'))
3633
def test_no_content(self):
3634
conf = config.MemoryStack()
3635
# No content means no loading
3636
self.assertFalse(conf.store.is_loaded())
3637
self.assertRaises(NotImplementedError, conf.get, 'foo')
3638
# But a content can still be provided
3639
conf.store._load_from_string('foo=bar')
3640
self.assertEquals('bar', conf.get('foo'))
3643
class TestStackWithTransport(tests.TestCaseWithTransport):
3645
scenarios = [(key, {'get_stack': builder}) for key, builder
3646
in config.test_stack_builder_registry.iteritems()]
3649
class TestConcreteStacks(TestStackWithTransport):
3651
def test_build_stack(self):
3652
# Just a smoke test to help debug builders
3653
stack = self.get_stack(self)
3656
class TestStackGet(TestStackWithTransport):
3659
super(TestStackGet, self).setUp()
3660
self.conf = self.get_stack(self)
3662
def test_get_for_empty_stack(self):
3663
self.assertEquals(None, self.conf.get('foo'))
3665
def test_get_hook(self):
3666
self.conf.set('foo', 'bar')
3670
config.ConfigHooks.install_named_hook('get', hook, None)
3671
self.assertLength(0, calls)
3672
value = self.conf.get('foo')
3673
self.assertEquals('bar', value)
3674
self.assertLength(1, calls)
3675
self.assertEquals((self.conf, 'foo', 'bar'), calls[0])
3678
class TestStackGetWithConverter(tests.TestCase):
3681
super(TestStackGetWithConverter, self).setUp()
3682
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3683
self.registry = config.option_registry
3685
def get_conf(self, content=None):
3686
return config.MemoryStack(content)
3688
def register_bool_option(self, name, default=None, default_from_env=None):
3689
b = config.Option(name, help='A boolean.',
3690
default=default, default_from_env=default_from_env,
3691
from_unicode=config.bool_from_store)
3692
self.registry.register(b)
3694
def test_get_default_bool_None(self):
3695
self.register_bool_option('foo')
3696
conf = self.get_conf('')
3697
self.assertEquals(None, conf.get('foo'))
3699
def test_get_default_bool_True(self):
3700
self.register_bool_option('foo', u'True')
3701
conf = self.get_conf('')
3702
self.assertEquals(True, conf.get('foo'))
3704
def test_get_default_bool_False(self):
3705
self.register_bool_option('foo', False)
3706
conf = self.get_conf('')
3707
self.assertEquals(False, conf.get('foo'))
3709
def test_get_default_bool_False_as_string(self):
3710
self.register_bool_option('foo', u'False')
3711
conf = self.get_conf('')
3712
self.assertEquals(False, conf.get('foo'))
3714
def test_get_default_bool_from_env_converted(self):
3715
self.register_bool_option('foo', u'True', default_from_env=['FOO'])
3716
self.overrideEnv('FOO', 'False')
3717
conf = self.get_conf('')
3718
self.assertEquals(False, conf.get('foo'))
3720
def test_get_default_bool_when_conversion_fails(self):
3721
self.register_bool_option('foo', default='True')
3722
conf = self.get_conf('foo=invalid boolean')
3723
self.assertEquals(True, conf.get('foo'))
3725
def register_integer_option(self, name,
3726
default=None, default_from_env=None):
3727
i = config.Option(name, help='An integer.',
3728
default=default, default_from_env=default_from_env,
3729
from_unicode=config.int_from_store)
3730
self.registry.register(i)
3732
def test_get_default_integer_None(self):
3733
self.register_integer_option('foo')
3734
conf = self.get_conf('')
3735
self.assertEquals(None, conf.get('foo'))
3737
def test_get_default_integer(self):
3738
self.register_integer_option('foo', 42)
3739
conf = self.get_conf('')
3740
self.assertEquals(42, conf.get('foo'))
3742
def test_get_default_integer_as_string(self):
3743
self.register_integer_option('foo', u'42')
3744
conf = self.get_conf('')
3745
self.assertEquals(42, conf.get('foo'))
3747
def test_get_default_integer_from_env(self):
3748
self.register_integer_option('foo', default_from_env=['FOO'])
3749
self.overrideEnv('FOO', '18')
3750
conf = self.get_conf('')
3751
self.assertEquals(18, conf.get('foo'))
3753
def test_get_default_integer_when_conversion_fails(self):
3754
self.register_integer_option('foo', default='12')
3755
conf = self.get_conf('foo=invalid integer')
3756
self.assertEquals(12, conf.get('foo'))
3758
def register_list_option(self, name, default=None, default_from_env=None):
3759
l = config.ListOption(name, help='A list.', default=default,
3760
default_from_env=default_from_env)
3761
self.registry.register(l)
3763
def test_get_default_list_None(self):
3764
self.register_list_option('foo')
3765
conf = self.get_conf('')
3766
self.assertEquals(None, conf.get('foo'))
3768
def test_get_default_list_empty(self):
3769
self.register_list_option('foo', '')
3770
conf = self.get_conf('')
3771
self.assertEquals([], conf.get('foo'))
3773
def test_get_default_list_from_env(self):
3774
self.register_list_option('foo', default_from_env=['FOO'])
3775
self.overrideEnv('FOO', '')
3776
conf = self.get_conf('')
3777
self.assertEquals([], conf.get('foo'))
3779
def test_get_with_list_converter_no_item(self):
3780
self.register_list_option('foo', None)
3781
conf = self.get_conf('foo=,')
3782
self.assertEquals([], conf.get('foo'))
3784
def test_get_with_list_converter_many_items(self):
3785
self.register_list_option('foo', None)
3786
conf = self.get_conf('foo=m,o,r,e')
3787
self.assertEquals(['m', 'o', 'r', 'e'], conf.get('foo'))
3789
def test_get_with_list_converter_embedded_spaces_many_items(self):
3790
self.register_list_option('foo', None)
3791
conf = self.get_conf('foo=" bar", "baz "')
3792
self.assertEquals([' bar', 'baz '], conf.get('foo'))
3794
def test_get_with_list_converter_stripped_spaces_many_items(self):
3795
self.register_list_option('foo', None)
3796
conf = self.get_conf('foo= bar , baz ')
3797
self.assertEquals(['bar', 'baz'], conf.get('foo'))
3800
class TestIterOptionRefs(tests.TestCase):
3801
"""iter_option_refs is a bit unusual, document some cases."""
3803
def assertRefs(self, expected, string):
3804
self.assertEquals(expected, list(config.iter_option_refs(string)))
3806
def test_empty(self):
3807
self.assertRefs([(False, '')], '')
3809
def test_no_refs(self):
3810
self.assertRefs([(False, 'foo bar')], 'foo bar')
3812
def test_single_ref(self):
3813
self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
3815
def test_broken_ref(self):
3816
self.assertRefs([(False, '{foo')], '{foo')
3818
def test_embedded_ref(self):
3819
self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
3822
def test_two_refs(self):
3823
self.assertRefs([(False, ''), (True, '{foo}'),
3824
(False, ''), (True, '{bar}'),
3828
def test_newline_in_refs_are_not_matched(self):
3829
self.assertRefs([(False, '{\nxx}{xx\n}{{\n}}')], '{\nxx}{xx\n}{{\n}}')
3832
class TestStackExpandOptions(tests.TestCaseWithTransport):
3835
super(TestStackExpandOptions, self).setUp()
3836
self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3837
self.registry = config.option_registry
3838
self.conf = build_branch_stack(self)
3840
def assertExpansion(self, expected, string, env=None):
3841
self.assertEquals(expected, self.conf.expand_options(string, env))
3843
def test_no_expansion(self):
3844
self.assertExpansion('foo', 'foo')
3846
def test_expand_default_value(self):
3847
self.conf.store._load_from_string('bar=baz')
3848
self.registry.register(config.Option('foo', default=u'{bar}'))
3849
self.assertEquals('baz', self.conf.get('foo', expand=True))
3851
def test_expand_default_from_env(self):
3852
self.conf.store._load_from_string('bar=baz')
3853
self.registry.register(config.Option('foo', default_from_env=['FOO']))
3854
self.overrideEnv('FOO', '{bar}')
3855
self.assertEquals('baz', self.conf.get('foo', expand=True))
3857
def test_expand_default_on_failed_conversion(self):
3858
self.conf.store._load_from_string('baz=bogus\nbar=42\nfoo={baz}')
3859
self.registry.register(
3860
config.Option('foo', default=u'{bar}',
3861
from_unicode=config.int_from_store))
3862
self.assertEquals(42, self.conf.get('foo', expand=True))
3864
def test_env_adding_options(self):
3865
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3867
def test_env_overriding_options(self):
3868
self.conf.store._load_from_string('foo=baz')
3869
self.assertExpansion('bar', '{foo}', {'foo': 'bar'})
3871
def test_simple_ref(self):
3872
self.conf.store._load_from_string('foo=xxx')
3873
self.assertExpansion('xxx', '{foo}')
3875
def test_unknown_ref(self):
3876
self.assertRaises(errors.ExpandingUnknownOption,
3877
self.conf.expand_options, '{foo}')
3879
def test_indirect_ref(self):
3880
self.conf.store._load_from_string('''
3884
self.assertExpansion('xxx', '{bar}')
3886
def test_embedded_ref(self):
3887
self.conf.store._load_from_string('''
3891
self.assertExpansion('xxx', '{{bar}}')
3893
def test_simple_loop(self):
3894
self.conf.store._load_from_string('foo={foo}')
3895
self.assertRaises(errors.OptionExpansionLoop,
3896
self.conf.expand_options, '{foo}')
3898
def test_indirect_loop(self):
3899
self.conf.store._load_from_string('''
3903
e = self.assertRaises(errors.OptionExpansionLoop,
3904
self.conf.expand_options, '{foo}')
3905
self.assertEquals('foo->bar->baz', e.refs)
3906
self.assertEquals('{foo}', e.string)
3908
def test_list(self):
3909
self.conf.store._load_from_string('''
3913
list={foo},{bar},{baz}
3915
self.registry.register(
3916
config.ListOption('list'))
3917
self.assertEquals(['start', 'middle', 'end'],
3918
self.conf.get('list', expand=True))
3920
def test_cascading_list(self):
3921
self.conf.store._load_from_string('''
3927
self.registry.register(
3928
config.ListOption('list'))
3929
self.assertEquals(['start', 'middle', 'end'],
3930
self.conf.get('list', expand=True))
3932
def test_pathologically_hidden_list(self):
3933
self.conf.store._load_from_string('''
3939
hidden={start}{middle}{end}
3941
# What matters is what the registration says, the conversion happens
3942
# only after all expansions have been performed
3943
self.registry.register(config.ListOption('hidden'))
3944
self.assertEquals(['bin', 'go'],
3945
self.conf.get('hidden', expand=True))
3948
class TestStackCrossSectionsExpand(tests.TestCaseWithTransport):
3951
super(TestStackCrossSectionsExpand, self).setUp()
3953
def get_config(self, location, string):
3956
# Since we don't save the config we won't strictly require to inherit
3957
# from TestCaseInTempDir, but an error occurs so quickly...
3958
c = config.LocationStack(location)
3959
c.store._load_from_string(string)
3962
def test_dont_cross_unrelated_section(self):
3963
c = self.get_config('/another/branch/path','''
3968
[/another/branch/path]
3971
self.assertRaises(errors.ExpandingUnknownOption,
3972
c.get, 'bar', expand=True)
3974
def test_cross_related_sections(self):
3975
c = self.get_config('/project/branch/path','''
3979
[/project/branch/path]
3982
self.assertEquals('quux', c.get('bar', expand=True))
3985
class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
3987
def test_cross_global_locations(self):
3988
l_store = config.LocationStore()
3989
l_store._load_from_string('''
3995
g_store = config.GlobalStore()
3996
g_store._load_from_string('''
4002
stack = config.LocationStack('/branch')
4003
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4004
self.assertEquals('loc-foo', stack.get('gfoo', expand=True))
4007
class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
4009
def test_expand_locals_empty(self):
4010
l_store = config.LocationStore()
4011
l_store._load_from_string('''
4012
[/home/user/project]
4017
stack = config.LocationStack('/home/user/project/')
4018
self.assertEquals('', stack.get('base', expand=True))
4019
self.assertEquals('', stack.get('rel', expand=True))
4021
def test_expand_basename_locally(self):
4022
l_store = config.LocationStore()
4023
l_store._load_from_string('''
4024
[/home/user/project]
4028
stack = config.LocationStack('/home/user/project/branch')
4029
self.assertEquals('branch', stack.get('bfoo', expand=True))
4031
def test_expand_basename_locally_longer_path(self):
4032
l_store = config.LocationStore()
4033
l_store._load_from_string('''
4038
stack = config.LocationStack('/home/user/project/dir/branch')
4039
self.assertEquals('branch', stack.get('bfoo', expand=True))
4041
def test_expand_relpath_locally(self):
4042
l_store = config.LocationStore()
4043
l_store._load_from_string('''
4044
[/home/user/project]
4045
lfoo = loc-foo/{relpath}
4048
stack = config.LocationStack('/home/user/project/branch')
4049
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4051
def test_expand_relpath_unknonw_in_global(self):
4052
g_store = config.GlobalStore()
4053
g_store._load_from_string('''
4058
stack = config.LocationStack('/home/user/project/branch')
4059
self.assertRaises(errors.ExpandingUnknownOption,
4060
stack.get, 'gfoo', expand=True)
4062
def test_expand_local_option_locally(self):
4063
l_store = config.LocationStore()
4064
l_store._load_from_string('''
4065
[/home/user/project]
4066
lfoo = loc-foo/{relpath}
4070
g_store = config.GlobalStore()
4071
g_store._load_from_string('''
4077
stack = config.LocationStack('/home/user/project/branch')
4078
self.assertEquals('glob-bar', stack.get('lbar', expand=True))
4079
self.assertEquals('loc-foo/branch', stack.get('gfoo', expand=True))
4081
def test_locals_dont_leak(self):
4082
"""Make sure we chose the right local in presence of several sections.
4084
l_store = config.LocationStore()
4085
l_store._load_from_string('''
4087
lfoo = loc-foo/{relpath}
4088
[/home/user/project]
4089
lfoo = loc-foo/{relpath}
4092
stack = config.LocationStack('/home/user/project/branch')
4093
self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
4094
stack = config.LocationStack('/home/user/bar/baz')
4095
self.assertEquals('loc-foo/bar/baz', stack.get('lfoo', expand=True))
4099
class TestStackSet(TestStackWithTransport):
4101
def test_simple_set(self):
4102
conf = self.get_stack(self)
4103
self.assertEquals(None, conf.get('foo'))
4104
conf.set('foo', 'baz')
4105
# Did we get it back ?
4106
self.assertEquals('baz', conf.get('foo'))
4108
def test_set_creates_a_new_section(self):
4109
conf = self.get_stack(self)
4110
conf.set('foo', 'baz')
4111
self.assertEquals, 'baz', conf.get('foo')
4113
def test_set_hook(self):
4117
config.ConfigHooks.install_named_hook('set', hook, None)
4118
self.assertLength(0, calls)
4119
conf = self.get_stack(self)
4120
conf.set('foo', 'bar')
4121
self.assertLength(1, calls)
4122
self.assertEquals((conf, 'foo', 'bar'), calls[0])
4125
class TestStackRemove(TestStackWithTransport):
4127
def test_remove_existing(self):
4128
conf = self.get_stack(self)
4129
conf.set('foo', 'bar')
4130
self.assertEquals('bar', conf.get('foo'))
4132
# Did we get it back ?
4133
self.assertEquals(None, conf.get('foo'))
4135
def test_remove_unknown(self):
4136
conf = self.get_stack(self)
4137
self.assertRaises(KeyError, conf.remove, 'I_do_not_exist')
4139
def test_remove_hook(self):
4143
config.ConfigHooks.install_named_hook('remove', hook, None)
4144
self.assertLength(0, calls)
4145
conf = self.get_stack(self)
4146
conf.set('foo', 'bar')
4148
self.assertLength(1, calls)
4149
self.assertEquals((conf, 'foo'), calls[0])
4152
class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
4155
super(TestConfigGetOptions, self).setUp()
4156
create_configs(self)
4158
def test_no_variable(self):
4159
# Using branch should query branch, locations and bazaar
4160
self.assertOptions([], self.branch_config)
4162
def test_option_in_bazaar(self):
4163
self.bazaar_config.set_user_option('file', 'bazaar')
4164
self.assertOptions([('file', 'bazaar', 'DEFAULT', 'bazaar')],
4167
def test_option_in_locations(self):
4168
self.locations_config.set_user_option('file', 'locations')
4170
[('file', 'locations', self.tree.basedir, 'locations')],
4171
self.locations_config)
4173
def test_option_in_branch(self):
4174
self.branch_config.set_user_option('file', 'branch')
4175
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch')],
4178
def test_option_in_bazaar_and_branch(self):
4179
self.bazaar_config.set_user_option('file', 'bazaar')
4180
self.branch_config.set_user_option('file', 'branch')
4181
self.assertOptions([('file', 'branch', 'DEFAULT', 'branch'),
4182
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4185
def test_option_in_branch_and_locations(self):
4186
# Hmm, locations override branch :-/
4187
self.locations_config.set_user_option('file', 'locations')
4188
self.branch_config.set_user_option('file', 'branch')
4190
[('file', 'locations', self.tree.basedir, 'locations'),
4191
('file', 'branch', 'DEFAULT', 'branch'),],
4194
def test_option_in_bazaar_locations_and_branch(self):
4195
self.bazaar_config.set_user_option('file', 'bazaar')
4196
self.locations_config.set_user_option('file', 'locations')
4197
self.branch_config.set_user_option('file', 'branch')
4199
[('file', 'locations', self.tree.basedir, 'locations'),
4200
('file', 'branch', 'DEFAULT', 'branch'),
4201
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4205
class TestConfigRemoveOption(tests.TestCaseWithTransport, TestOptionsMixin):
4208
super(TestConfigRemoveOption, self).setUp()
4209
create_configs_with_file_option(self)
4211
def test_remove_in_locations(self):
4212
self.locations_config.remove_user_option('file', self.tree.basedir)
4214
[('file', 'branch', 'DEFAULT', 'branch'),
4215
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4218
def test_remove_in_branch(self):
4219
self.branch_config.remove_user_option('file')
4221
[('file', 'locations', self.tree.basedir, 'locations'),
4222
('file', 'bazaar', 'DEFAULT', 'bazaar'),],
4225
def test_remove_in_bazaar(self):
4226
self.bazaar_config.remove_user_option('file')
4228
[('file', 'locations', self.tree.basedir, 'locations'),
4229
('file', 'branch', 'DEFAULT', 'branch'),],
4233
class TestConfigGetSections(tests.TestCaseWithTransport):
4236
super(TestConfigGetSections, self).setUp()
4237
create_configs(self)
4239
def assertSectionNames(self, expected, conf, name=None):
4240
"""Check which sections are returned for a given config.
4242
If fallback configurations exist their sections can be included.
4244
:param expected: A list of section names.
4246
:param conf: The configuration that will be queried.
4248
:param name: An optional section name that will be passed to
4251
sections = list(conf._get_sections(name))
4252
self.assertLength(len(expected), sections)
4253
self.assertEqual(expected, [name for name, _, _ in sections])
4255
def test_bazaar_default_section(self):
4256
self.assertSectionNames(['DEFAULT'], self.bazaar_config)
4258
def test_locations_default_section(self):
4259
# No sections are defined in an empty file
4260
self.assertSectionNames([], self.locations_config)
4262
def test_locations_named_section(self):
4263
self.locations_config.set_user_option('file', 'locations')
4264
self.assertSectionNames([self.tree.basedir], self.locations_config)
4266
def test_locations_matching_sections(self):
4267
loc_config = self.locations_config
4268
loc_config.set_user_option('file', 'locations')
4269
# We need to cheat a bit here to create an option in sections above and
4270
# below the 'location' one.
4271
parser = loc_config._get_parser()
4272
# locations.cong deals with '/' ignoring native os.sep
4273
location_names = self.tree.basedir.split('/')
4274
parent = '/'.join(location_names[:-1])
4275
child = '/'.join(location_names + ['child'])
4277
parser[parent]['file'] = 'parent'
4279
parser[child]['file'] = 'child'
4280
self.assertSectionNames([self.tree.basedir, parent], loc_config)
4282
def test_branch_data_default_section(self):
4283
self.assertSectionNames([None],
4284
self.branch_config._get_branch_data_config())
4286
def test_branch_default_sections(self):
4287
# No sections are defined in an empty locations file
4288
self.assertSectionNames([None, 'DEFAULT'],
4290
# Unless we define an option
4291
self.branch_config._get_location_config().set_user_option(
4292
'file', 'locations')
4293
self.assertSectionNames([self.tree.basedir, None, 'DEFAULT'],
4296
def test_bazaar_named_section(self):
4297
# We need to cheat as the API doesn't give direct access to sections
4298
# other than DEFAULT.
4299
self.bazaar_config.set_alias('bazaar', 'bzr')
4300
self.assertSectionNames(['ALIASES'], self.bazaar_config, 'ALIASES')
4303
class TestAuthenticationConfigFile(tests.TestCase):
4304
"""Test the authentication.conf file matching"""
4306
def _got_user_passwd(self, expected_user, expected_password,
4307
config, *args, **kwargs):
4308
credentials = config.get_credentials(*args, **kwargs)
4309
if credentials is None:
4313
user = credentials['user']
4314
password = credentials['password']
4315
self.assertEquals(expected_user, user)
4316
self.assertEquals(expected_password, password)
4318
def test_empty_config(self):
4319
conf = config.AuthenticationConfig(_file=StringIO())
4320
self.assertEquals({}, conf._get_config())
4321
self._got_user_passwd(None, None, conf, 'http', 'foo.net')
4323
def test_non_utf8_config(self):
4324
conf = config.AuthenticationConfig(_file=StringIO(
4326
self.assertRaises(errors.ConfigContentError, conf._get_config)
4328
def test_missing_auth_section_header(self):
4329
conf = config.AuthenticationConfig(_file=StringIO('foo = bar'))
4330
self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
4332
def test_auth_section_header_not_closed(self):
4333
conf = config.AuthenticationConfig(_file=StringIO('[DEF'))
4334
self.assertRaises(errors.ParseConfigError, conf._get_config)
4336
def test_auth_value_not_boolean(self):
4337
conf = config.AuthenticationConfig(_file=StringIO(
4341
verify_certificates=askme # Error: Not a boolean
4343
self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
4345
def test_auth_value_not_int(self):
4346
conf = config.AuthenticationConfig(_file=StringIO(
4350
port=port # Error: Not an int
4352
self.assertRaises(ValueError, conf.get_credentials, 'ftp', 'foo.net')
4354
def test_unknown_password_encoding(self):
4355
conf = config.AuthenticationConfig(_file=StringIO(
4359
password_encoding=unknown
4361
self.assertRaises(ValueError, conf.get_password,
4362
'ftp', 'foo.net', 'joe')
4364
def test_credentials_for_scheme_host(self):
4365
conf = config.AuthenticationConfig(_file=StringIO(
4366
"""# Identity on foo.net
4371
password=secret-pass
4374
self._got_user_passwd('joe', 'secret-pass', conf, 'ftp', 'foo.net')
4376
self._got_user_passwd(None, None, conf, 'http', 'foo.net')
4378
self._got_user_passwd(None, None, conf, 'ftp', 'bar.net')
4380
def test_credentials_for_host_port(self):
4381
conf = config.AuthenticationConfig(_file=StringIO(
4382
"""# Identity on foo.net
4388
password=secret-pass
4391
self._got_user_passwd('joe', 'secret-pass',
4392
conf, 'ftp', 'foo.net', port=10021)
4394
self._got_user_passwd(None, None, conf, 'ftp', 'foo.net')
4396
def test_for_matching_host(self):
4397
conf = config.AuthenticationConfig(_file=StringIO(
4398
"""# Identity on foo.net
4404
[sourceforge domain]
4411
self._got_user_passwd('georges', 'bendover',
4412
conf, 'bzr', 'foo.bzr.sf.net')
4414
self._got_user_passwd(None, None,
4415
conf, 'bzr', 'bbzr.sf.net')
4417
def test_for_matching_host_None(self):
4418
conf = config.AuthenticationConfig(_file=StringIO(
4419
"""# Identity on foo.net
4429
self._got_user_passwd('joe', 'joepass',
4430
conf, 'bzr', 'quux.net')
4431
# no host but different scheme
4432
self._got_user_passwd('georges', 'bendover',
4433
conf, 'ftp', 'quux.net')
4435
def test_credentials_for_path(self):
4436
conf = config.AuthenticationConfig(_file=StringIO(
4452
self._got_user_passwd(None, None,
4453
conf, 'http', host='bar.org', path='/dir3')
4455
self._got_user_passwd('georges', 'bendover',
4456
conf, 'http', host='bar.org', path='/dir2')
4458
self._got_user_passwd('jim', 'jimpass',
4459
conf, 'http', host='bar.org',path='/dir1/subdir')
4461
def test_credentials_for_user(self):
4462
conf = config.AuthenticationConfig(_file=StringIO(
4471
self._got_user_passwd('jim', 'jimpass',
4472
conf, 'http', 'bar.org')
4474
self._got_user_passwd('jim', 'jimpass',
4475
conf, 'http', 'bar.org', user='jim')
4476
# Don't get a different user if one is specified
4477
self._got_user_passwd(None, None,
4478
conf, 'http', 'bar.org', user='georges')
4480
def test_credentials_for_user_without_password(self):
4481
conf = config.AuthenticationConfig(_file=StringIO(
4488
# Get user but no password
4489
self._got_user_passwd('jim', None,
4490
conf, 'http', 'bar.org')
4492
def test_verify_certificates(self):
4493
conf = config.AuthenticationConfig(_file=StringIO(
4500
verify_certificates=False
4507
credentials = conf.get_credentials('https', 'bar.org')
4508
self.assertEquals(False, credentials.get('verify_certificates'))
4509
credentials = conf.get_credentials('https', 'foo.net')
4510
self.assertEquals(True, credentials.get('verify_certificates'))
4513
class TestAuthenticationStorage(tests.TestCaseInTempDir):
4515
def test_set_credentials(self):
4516
conf = config.AuthenticationConfig()
4517
conf.set_credentials('name', 'host', 'user', 'scheme', 'password',
4518
99, path='/foo', verify_certificates=False, realm='realm')
4519
credentials = conf.get_credentials(host='host', scheme='scheme',
4520
port=99, path='/foo',
4522
CREDENTIALS = {'name': 'name', 'user': 'user', 'password': 'password',
4523
'verify_certificates': False, 'scheme': 'scheme',
4524
'host': 'host', 'port': 99, 'path': '/foo',
4526
self.assertEqual(CREDENTIALS, credentials)
4527
credentials_from_disk = config.AuthenticationConfig().get_credentials(
4528
host='host', scheme='scheme', port=99, path='/foo', realm='realm')
4529
self.assertEqual(CREDENTIALS, credentials_from_disk)
4531
def test_reset_credentials_different_name(self):
4532
conf = config.AuthenticationConfig()
4533
conf.set_credentials('name', 'host', 'user', 'scheme', 'password'),
4534
conf.set_credentials('name2', 'host', 'user2', 'scheme', 'password'),
4535
self.assertIs(None, conf._get_config().get('name'))
4536
credentials = conf.get_credentials(host='host', scheme='scheme')
4537
CREDENTIALS = {'name': 'name2', 'user': 'user2', 'password':
4538
'password', 'verify_certificates': True,
4539
'scheme': 'scheme', 'host': 'host', 'port': None,
4540
'path': None, 'realm': None}
4541
self.assertEqual(CREDENTIALS, credentials)
4544
class TestAuthenticationConfig(tests.TestCase):
4545
"""Test AuthenticationConfig behaviour"""
4547
def _check_default_password_prompt(self, expected_prompt_format, scheme,
4548
host=None, port=None, realm=None,
4552
user, password = 'jim', 'precious'
4553
expected_prompt = expected_prompt_format % {
4554
'scheme': scheme, 'host': host, 'port': port,
4555
'user': user, 'realm': realm}
4557
stdout = tests.StringIOWrapper()
4558
stderr = tests.StringIOWrapper()
4559
ui.ui_factory = tests.TestUIFactory(stdin=password + '\n',
4560
stdout=stdout, stderr=stderr)
4561
# We use an empty conf so that the user is always prompted
4562
conf = config.AuthenticationConfig()
4563
self.assertEquals(password,
4564
conf.get_password(scheme, host, user, port=port,
4565
realm=realm, path=path))
4566
self.assertEquals(expected_prompt, stderr.getvalue())
4567
self.assertEquals('', stdout.getvalue())
4569
def _check_default_username_prompt(self, expected_prompt_format, scheme,
4570
host=None, port=None, realm=None,
4575
expected_prompt = expected_prompt_format % {
4576
'scheme': scheme, 'host': host, 'port': port,
4578
stdout = tests.StringIOWrapper()
4579
stderr = tests.StringIOWrapper()
4580
ui.ui_factory = tests.TestUIFactory(stdin=username+ '\n',
4581
stdout=stdout, stderr=stderr)
4582
# We use an empty conf so that the user is always prompted
4583
conf = config.AuthenticationConfig()
4584
self.assertEquals(username, conf.get_user(scheme, host, port=port,
4585
realm=realm, path=path, ask=True))
4586
self.assertEquals(expected_prompt, stderr.getvalue())
4587
self.assertEquals('', stdout.getvalue())
4589
def test_username_defaults_prompts(self):
4590
# HTTP prompts can't be tested here, see test_http.py
4591
self._check_default_username_prompt(u'FTP %(host)s username: ', 'ftp')
4592
self._check_default_username_prompt(
4593
u'FTP %(host)s:%(port)d username: ', 'ftp', port=10020)
4594
self._check_default_username_prompt(
4595
u'SSH %(host)s:%(port)d username: ', 'ssh', port=12345)
4597
def test_username_default_no_prompt(self):
4598
conf = config.AuthenticationConfig()
4599
self.assertEquals(None,
4600
conf.get_user('ftp', 'example.com'))
4601
self.assertEquals("explicitdefault",
4602
conf.get_user('ftp', 'example.com', default="explicitdefault"))
4604
def test_password_default_prompts(self):
4605
# HTTP prompts can't be tested here, see test_http.py
4606
self._check_default_password_prompt(
4607
u'FTP %(user)s@%(host)s password: ', 'ftp')
4608
self._check_default_password_prompt(
4609
u'FTP %(user)s@%(host)s:%(port)d password: ', 'ftp', port=10020)
4610
self._check_default_password_prompt(
4611
u'SSH %(user)s@%(host)s:%(port)d password: ', 'ssh', port=12345)
4612
# SMTP port handling is a bit special (it's handled if embedded in the
4614
# FIXME: should we: forbid that, extend it to other schemes, leave
4615
# things as they are that's fine thank you ?
4616
self._check_default_password_prompt(
4617
u'SMTP %(user)s@%(host)s password: ', 'smtp')
4618
self._check_default_password_prompt(
4619
u'SMTP %(user)s@%(host)s password: ', 'smtp', host='bar.org:10025')
4620
self._check_default_password_prompt(
4621
u'SMTP %(user)s@%(host)s:%(port)d password: ', 'smtp', port=10025)
4623
def test_ssh_password_emits_warning(self):
4624
conf = config.AuthenticationConfig(_file=StringIO(
4632
entered_password = 'typed-by-hand'
4633
stdout = tests.StringIOWrapper()
4634
stderr = tests.StringIOWrapper()
4635
ui.ui_factory = tests.TestUIFactory(stdin=entered_password + '\n',
4636
stdout=stdout, stderr=stderr)
4638
# Since the password defined in the authentication config is ignored,
4639
# the user is prompted
4640
self.assertEquals(entered_password,
4641
conf.get_password('ssh', 'bar.org', user='jim'))
4642
self.assertContainsRe(
4644
'password ignored in section \[ssh with password\]')
4646
def test_ssh_without_password_doesnt_emit_warning(self):
4647
conf = config.AuthenticationConfig(_file=StringIO(
4654
entered_password = 'typed-by-hand'
4655
stdout = tests.StringIOWrapper()
4656
stderr = tests.StringIOWrapper()
4657
ui.ui_factory = tests.TestUIFactory(stdin=entered_password + '\n',
4661
# Since the password defined in the authentication config is ignored,
4662
# the user is prompted
4663
self.assertEquals(entered_password,
4664
conf.get_password('ssh', 'bar.org', user='jim'))
4665
# No warning shoud be emitted since there is no password. We are only
4667
self.assertNotContainsRe(
4669
'password ignored in section \[ssh with password\]')
4671
def test_uses_fallback_stores(self):
4672
self.overrideAttr(config, 'credential_store_registry',
4673
config.CredentialStoreRegistry())
4674
store = StubCredentialStore()
4675
store.add_credentials("http", "example.com", "joe", "secret")
4676
config.credential_store_registry.register("stub", store, fallback=True)
4677
conf = config.AuthenticationConfig(_file=StringIO())
4678
creds = conf.get_credentials("http", "example.com")
4679
self.assertEquals("joe", creds["user"])
4680
self.assertEquals("secret", creds["password"])
4683
class StubCredentialStore(config.CredentialStore):
4689
def add_credentials(self, scheme, host, user, password=None):
4690
self._username[(scheme, host)] = user
4691
self._password[(scheme, host)] = password
4693
def get_credentials(self, scheme, host, port=None, user=None,
4694
path=None, realm=None):
4695
key = (scheme, host)
4696
if not key in self._username:
4698
return { "scheme": scheme, "host": host, "port": port,
4699
"user": self._username[key], "password": self._password[key]}
4702
class CountingCredentialStore(config.CredentialStore):
4707
def get_credentials(self, scheme, host, port=None, user=None,
4708
path=None, realm=None):
4713
class TestCredentialStoreRegistry(tests.TestCase):
4715
def _get_cs_registry(self):
4716
return config.credential_store_registry
4718
def test_default_credential_store(self):
4719
r = self._get_cs_registry()
4720
default = r.get_credential_store(None)
4721
self.assertIsInstance(default, config.PlainTextCredentialStore)
4723
def test_unknown_credential_store(self):
4724
r = self._get_cs_registry()
4725
# It's hard to imagine someone creating a credential store named
4726
# 'unknown' so we use that as an never registered key.
4727
self.assertRaises(KeyError, r.get_credential_store, 'unknown')
4729
def test_fallback_none_registered(self):
4730
r = config.CredentialStoreRegistry()
4731
self.assertEquals(None,
4732
r.get_fallback_credentials("http", "example.com"))
4734
def test_register(self):
4735
r = config.CredentialStoreRegistry()
4736
r.register("stub", StubCredentialStore(), fallback=False)
4737
r.register("another", StubCredentialStore(), fallback=True)
4738
self.assertEquals(["another", "stub"], r.keys())
4740
def test_register_lazy(self):
4741
r = config.CredentialStoreRegistry()
4742
r.register_lazy("stub", "bzrlib.tests.test_config",
4743
"StubCredentialStore", fallback=False)
4744
self.assertEquals(["stub"], r.keys())
4745
self.assertIsInstance(r.get_credential_store("stub"),
4746
StubCredentialStore)
4748
def test_is_fallback(self):
4749
r = config.CredentialStoreRegistry()
4750
r.register("stub1", None, fallback=False)
4751
r.register("stub2", None, fallback=True)
4752
self.assertEquals(False, r.is_fallback("stub1"))
4753
self.assertEquals(True, r.is_fallback("stub2"))
4755
def test_no_fallback(self):
4756
r = config.CredentialStoreRegistry()
4757
store = CountingCredentialStore()
4758
r.register("count", store, fallback=False)
4759
self.assertEquals(None,
4760
r.get_fallback_credentials("http", "example.com"))
4761
self.assertEquals(0, store._calls)
4763
def test_fallback_credentials(self):
4764
r = config.CredentialStoreRegistry()
4765
store = StubCredentialStore()
4766
store.add_credentials("http", "example.com",
4767
"somebody", "geheim")
4768
r.register("stub", store, fallback=True)
4769
creds = r.get_fallback_credentials("http", "example.com")
4770
self.assertEquals("somebody", creds["user"])
4771
self.assertEquals("geheim", creds["password"])
4773
def test_fallback_first_wins(self):
4774
r = config.CredentialStoreRegistry()
4775
stub1 = StubCredentialStore()
4776
stub1.add_credentials("http", "example.com",
4777
"somebody", "stub1")
4778
r.register("stub1", stub1, fallback=True)
4779
stub2 = StubCredentialStore()
4780
stub2.add_credentials("http", "example.com",
4781
"somebody", "stub2")
4782
r.register("stub2", stub1, fallback=True)
4783
creds = r.get_fallback_credentials("http", "example.com")
4784
self.assertEquals("somebody", creds["user"])
4785
self.assertEquals("stub1", creds["password"])
4788
class TestPlainTextCredentialStore(tests.TestCase):
4790
def test_decode_password(self):
4791
r = config.credential_store_registry
4792
plain_text = r.get_credential_store()
4793
decoded = plain_text.decode_password(dict(password='secret'))
4794
self.assertEquals('secret', decoded)
4797
# FIXME: Once we have a way to declare authentication to all test servers, we
4798
# can implement generic tests.
4799
# test_user_password_in_url
4800
# test_user_in_url_password_from_config
4801
# test_user_in_url_password_prompted
4802
# test_user_in_config
4803
# test_user_getpass.getuser
4804
# test_user_prompted ?
4805
class TestAuthenticationRing(tests.TestCaseWithTransport):
4809
class TestAutoUserId(tests.TestCase):
4810
"""Test inferring an automatic user name."""
4812
def test_auto_user_id(self):
4813
"""Automatic inference of user name.
4815
This is a bit hard to test in an isolated way, because it depends on
4816
system functions that go direct to /etc or perhaps somewhere else.
4817
But it's reasonable to say that on Unix, with an /etc/mailname, we ought
4818
to be able to choose a user name with no configuration.
4820
if sys.platform == 'win32':
4821
raise tests.TestSkipped(
4822
"User name inference not implemented on win32")
4823
realname, address = config._auto_user_id()
4824
if os.path.exists('/etc/mailname'):
4825
self.assertIsNot(None, realname)
4826
self.assertIsNot(None, address)
4828
self.assertEquals((None, None), (realname, address))
4831
class EmailOptionTests(tests.TestCase):
4833
def test_default_email_uses_BZR_EMAIL(self):
4834
conf = config.MemoryStack('email=jelmer@debian.org')
4835
# BZR_EMAIL takes precedence over EMAIL
4836
self.overrideEnv('BZR_EMAIL', 'jelmer@samba.org')
4837
self.overrideEnv('EMAIL', 'jelmer@apache.org')
4838
self.assertEquals('jelmer@samba.org', conf.get('email'))
4840
def test_default_email_uses_EMAIL(self):
4841
conf = config.MemoryStack('')
4842
self.overrideEnv('BZR_EMAIL', None)
4843
self.overrideEnv('EMAIL', 'jelmer@apache.org')
4844
self.assertEquals('jelmer@apache.org', conf.get('email'))
4846
def test_BZR_EMAIL_overrides(self):
4847
conf = config.MemoryStack('email=jelmer@debian.org')
4848
self.overrideEnv('BZR_EMAIL', 'jelmer@apache.org')
4849
self.assertEquals('jelmer@apache.org', conf.get('email'))
4850
self.overrideEnv('BZR_EMAIL', None)
4851
self.overrideEnv('EMAIL', 'jelmer@samba.org')
4852
self.assertEquals('jelmer@debian.org', conf.get('email'))
4855
class MailClientOptionTests(tests.TestCase):
4857
def test_default(self):
4858
conf = config.MemoryStack('')
4859
client = conf.get('mail_client')
4860
self.assertIs(client, mail_client.DefaultMail)
4862
def test_evolution(self):
4863
conf = config.MemoryStack('mail_client=evolution')
4864
client = conf.get('mail_client')
4865
self.assertIs(client, mail_client.Evolution)
4867
def test_kmail(self):
4868
conf = config.MemoryStack('mail_client=kmail')
4869
client = conf.get('mail_client')
4870
self.assertIs(client, mail_client.KMail)
4872
def test_mutt(self):
4873
conf = config.MemoryStack('mail_client=mutt')
4874
client = conf.get('mail_client')
4875
self.assertIs(client, mail_client.Mutt)
4877
def test_thunderbird(self):
4878
conf = config.MemoryStack('mail_client=thunderbird')
4879
client = conf.get('mail_client')
4880
self.assertIs(client, mail_client.Thunderbird)
4882
def test_explicit_default(self):
4883
conf = config.MemoryStack('mail_client=default')
4884
client = conf.get('mail_client')
4885
self.assertIs(client, mail_client.DefaultMail)
4887
def test_editor(self):
4888
conf = config.MemoryStack('mail_client=editor')
4889
client = conf.get('mail_client')
4890
self.assertIs(client, mail_client.Editor)
4892
def test_mapi(self):
4893
conf = config.MemoryStack('mail_client=mapi')
4894
client = conf.get('mail_client')
4895
self.assertIs(client, mail_client.MAPIClient)
4897
def test_xdg_email(self):
4898
conf = config.MemoryStack('mail_client=xdg-email')
4899
client = conf.get('mail_client')
4900
self.assertIs(client, mail_client.XDGEmail)
4902
def test_unknown(self):
4903
conf = config.MemoryStack('mail_client=firebird')
4904
self.assertRaises(errors.ConfigOptionValueError, conf.get,