# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of logilab-common.
#
# logilab-common is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option) any
# later version.
#
# logilab-common is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with logilab-common. If not, see .
import tempfile
import os
from os.path import join, dirname, abspath
import re
from sys import version_info
from logilab.common import attrdict
from logilab.common.compat import StringIO
from logilab.common.testlib import TestCase, unittest_main
from logilab.common.optik_ext import OptionValueError
from logilab.common.configuration import Configuration, OptionError, \
OptionsManagerMixIn, OptionsProviderMixIn, Method, read_old_config, \
merge_options
DATA = join(dirname(abspath(__file__)), 'data')
OPTIONS = [('dothis', {'type':'yn', 'action': 'store', 'default': True, 'metavar': ''}),
('value', {'type': 'string', 'metavar': '', 'short': 'v'}),
('multiple', {'type': 'csv', 'default': ['yop', 'yep'],
'metavar': '',
'help': 'you can also document the option'}),
('number', {'type': 'int', 'default':2, 'metavar':'', 'help': 'boom'}),
('bytes', {'type': 'bytes', 'default':'1KB', 'metavar':''}),
('choice', {'type': 'choice', 'default':'yo', 'choices': ('yo', 'ye'),
'metavar':''}),
('multiple-choice', {'type': 'multiple_choice', 'default':['yo', 'ye'],
'choices': ('yo', 'ye', 'yu', 'yi', 'ya'),
'metavar':''}),
('named', {'type':'named', 'default':Method('get_named'),
'metavar': ''}),
('diffgroup', {'type':'string', 'default':'pouet', 'metavar': '',
'group': 'agroup'}),
('reset-value', {'type': 'string', 'metavar': '', 'short': 'r',
'dest':'value'}),
('opt-b-1', {'type': 'string', 'metavar': '', 'group': 'bgroup'}),
('opt-b-2', {'type': 'string', 'metavar': '', 'group': 'bgroup'}),
]
class MyConfiguration(Configuration):
"""test configuration"""
def get_named(self):
return {'key': 'val'}
class ConfigurationTC(TestCase):
def setUp(self):
self.cfg = MyConfiguration(name='test', options=OPTIONS, usage='Just do it ! (tm)')
def test_default(self):
cfg = self.cfg
self.assertEqual(cfg['dothis'], True)
self.assertEqual(cfg['value'], None)
self.assertEqual(cfg['multiple'], ['yop', 'yep'])
self.assertEqual(cfg['number'], 2)
self.assertEqual(cfg['bytes'], 1024)
self.assertIsInstance(cfg['bytes'], int)
self.assertEqual(cfg['choice'], 'yo')
self.assertEqual(cfg['multiple-choice'], ['yo', 'ye'])
self.assertEqual(cfg['named'], {'key': 'val'})
def test_base(self):
cfg = self.cfg
cfg.set_option('number', '0')
self.assertEqual(cfg['number'], 0)
self.assertRaises(OptionValueError, cfg.set_option, 'number', 'youpi')
self.assertRaises(OptionValueError, cfg.set_option, 'choice', 'youpi')
self.assertRaises(OptionValueError, cfg.set_option, 'multiple-choice', ('yo', 'y', 'ya'))
cfg.set_option('multiple-choice', 'yo, ya')
self.assertEqual(cfg['multiple-choice'], ['yo', 'ya'])
self.assertEqual(cfg.get('multiple-choice'), ['yo', 'ya'])
self.assertEqual(cfg.get('whatever'), None)
def test_load_command_line_configuration(self):
cfg = self.cfg
args = cfg.load_command_line_configuration(['--choice', 'ye', '--number', '4',
'--multiple=1,2,3', '--dothis=n',
'--bytes=10KB',
'other', 'arguments'])
self.assertEqual(args, ['other', 'arguments'])
self.assertEqual(cfg['dothis'], False)
self.assertEqual(cfg['multiple'], ['1', '2', '3'])
self.assertEqual(cfg['number'], 4)
self.assertEqual(cfg['bytes'], 10240)
self.assertEqual(cfg['choice'], 'ye')
self.assertEqual(cfg['value'], None)
args = cfg.load_command_line_configuration(['-v', 'duh'])
self.assertEqual(args, [])
self.assertEqual(cfg['value'], 'duh')
self.assertEqual(cfg['dothis'], False)
self.assertEqual(cfg['multiple'], ['1', '2', '3'])
self.assertEqual(cfg['number'], 4)
self.assertEqual(cfg['bytes'], 10240)
self.assertEqual(cfg['choice'], 'ye')
def test_load_configuration(self):
cfg = self.cfg
args = cfg.load_configuration(choice='ye', number='4',
multiple='1,2,3', dothis='n',
multiple_choice=('yo', 'ya'))
self.assertEqual(cfg['dothis'], False)
self.assertEqual(cfg['multiple'], ['1', '2', '3'])
self.assertEqual(cfg['number'], 4)
self.assertEqual(cfg['choice'], 'ye')
self.assertEqual(cfg['value'], None)
self.assertEqual(cfg['multiple-choice'], ('yo', 'ya'))
def test_load_configuration_file_case_insensitive(self):
file = tempfile.mktemp()
stream = open(file, 'w')
try:
stream.write("""[Test]
dothis=no
#value=
# you can also document the option
multiple=yop,yepii
# boom
number=3
bytes=1KB
choice=yo
multiple-choice=yo,ye
named=key:val
[agroup]
diffgroup=zou
""")
stream.close()
self.cfg.load_file_configuration(file)
self.assertEqual(self.cfg['dothis'], False)
self.assertEqual(self.cfg['value'], None)
self.assertEqual(self.cfg['multiple'], ['yop', 'yepii'])
self.assertEqual(self.cfg['diffgroup'], 'zou')
finally:
os.remove(file)
def test_option_order(self):
""" Check that options are taken into account in the command line order
and not in the order they are defined in the Configuration object.
"""
file = tempfile.mktemp()
stream = open(file, 'w')
try:
stream.write("""[Test]
reset-value=toto
value=tata
""")
stream.close()
self.cfg.load_file_configuration(file)
finally:
os.remove(file)
self.assertEqual(self.cfg['value'], 'tata')
def test_unsupported_options(self):
file = tempfile.mktemp()
stream = open(file, 'w')
try:
stream.write("""[Test]
whatever=toto
value=tata
""")
stream.close()
self.cfg.load_file_configuration(file)
finally:
os.remove(file)
self.assertEqual(self.cfg['value'], 'tata')
self.assertRaises(OptionError, self.cfg.__getitem__, 'whatever')
def test_generate_config(self):
stream = StringIO()
self.cfg.generate_config(stream)
self.assertMultiLineEqual(stream.getvalue().strip(), """[TEST]
dothis=yes
#value=
# you can also document the option
multiple=yop,yep
# boom
number=2
bytes=1KB
choice=yo
multiple-choice=yo,ye
named=key:val
#reset-value=
[AGROUP]
diffgroup=pouet
[BGROUP]
#opt-b-1=
#opt-b-2=""")
def test_generate_config_with_space_string(self):
self.cfg['value'] = ' '
stream = StringIO()
self.cfg.generate_config(stream)
self.assertMultiLineEqual(stream.getvalue().strip(), """[TEST]
dothis=yes
value=' '
# you can also document the option
multiple=yop,yep
# boom
number=2
bytes=1KB
choice=yo
multiple-choice=yo,ye
named=key:val
reset-value=' '
[AGROUP]
diffgroup=pouet
[BGROUP]
#opt-b-1=
#opt-b-2=""")
def test_generate_config_with_multiline_string(self):
self.cfg['value'] = 'line1\nline2\nline3'
stream = StringIO()
self.cfg.generate_config(stream)
self.assertMultiLineEqual(stream.getvalue().strip(), """[TEST]
dothis=yes
value=
line1
line2
line3
# you can also document the option
multiple=yop,yep
# boom
number=2
bytes=1KB
choice=yo
multiple-choice=yo,ye
named=key:val
reset-value=
line1
line2
line3
[AGROUP]
diffgroup=pouet
[BGROUP]
#opt-b-1=
#opt-b-2=""")
def test_roundtrip(self):
cfg = self.cfg
f = tempfile.mktemp()
stream = open(f, 'w')
try:
self.cfg['dothis'] = False
self.cfg['multiple'] = ["toto", "tata"]
self.cfg['number'] = 3
self.cfg['bytes'] = 2048
cfg.generate_config(stream)
stream.close()
new_cfg = MyConfiguration(name='test', options=OPTIONS)
new_cfg.load_file_configuration(f)
self.assertEqual(cfg['dothis'], new_cfg['dothis'])
self.assertEqual(cfg['multiple'], new_cfg['multiple'])
self.assertEqual(cfg['number'], new_cfg['number'])
self.assertEqual(cfg['bytes'], new_cfg['bytes'])
self.assertEqual(cfg['choice'], new_cfg['choice'])
self.assertEqual(cfg['value'], new_cfg['value'])
self.assertEqual(cfg['multiple-choice'], new_cfg['multiple-choice'])
finally:
os.remove(f)
def test_setitem(self):
self.assertRaises(OptionValueError,
self.cfg.__setitem__, 'multiple-choice', ('a', 'b'))
self.cfg['multiple-choice'] = ('yi', 'ya')
self.assertEqual(self.cfg['multiple-choice'], ('yi', 'ya'))
def test_help(self):
self.cfg.add_help_section('bonus', 'a nice additional help')
help = self.cfg.help().strip()
# at least in python 2.4.2 the output is:
# ' -v , --value='
# it is not unlikely some optik/optparse versions do print -v
# so accept both
help = help.replace(' -v , ', ' -v, ')
help = re.sub('[ ]*(\r?\n)', '\\1', help)
USAGE = """Usage: Just do it ! (tm)
Options:
-h, --help show this help message and exit
--dothis=
-v, --value=
--multiple=
you can also document the option [current: yop,yep]
--number= boom [current: 2]
--bytes=
--choice=
--multiple-choice=
--named=
-r , --reset-value=
Agroup:
--diffgroup=
Bgroup:
--opt-b-1=
--opt-b-2=
Bonus:
a nice additional help"""
if version_info < (2, 5):
# 'usage' header is not capitalized in this version
USAGE = USAGE.replace('Usage: ', 'usage: ')
elif version_info < (2, 4):
USAGE = """usage: Just do it ! (tm)
options:
-h, --help show this help message and exit
--dothis=
-v, --value=
--multiple=
you can also document the option
--number=
--choice=
--multiple-choice=
--named=
Bonus:
a nice additional help
"""
self.assertMultiLineEqual(help, USAGE)
def test_manpage(self):
pkginfo = {}
with open(join(DATA, '__pkginfo__.py')) as fobj:
exec(fobj.read(), pkginfo)
self.cfg.generate_manpage(attrdict(pkginfo), stream=StringIO())
def test_rewrite_config(self):
changes = [('renamed', 'renamed', 'choice'),
('moved', 'named', 'old', 'test'),
]
read_old_config(self.cfg, changes, join(DATA, 'test.ini'))
stream = StringIO()
self.cfg.generate_config(stream)
self.assertMultiLineEqual(stream.getvalue().strip(), """[TEST]
dothis=yes
value=' '
# you can also document the option
multiple=yop
# boom
number=2
bytes=1KB
choice=yo
multiple-choice=yo,ye
named=key:val
reset-value=' '
[AGROUP]
diffgroup=pouet
[BGROUP]
#opt-b-1=
#opt-b-2=""")
class Linter(OptionsManagerMixIn, OptionsProviderMixIn):
options = (
('profile', {'type' : 'yn', 'metavar' : '',
'default': False,
'help' : 'Profiled execution.'}),
)
def __init__(self):
OptionsManagerMixIn.__init__(self, usage="")
OptionsProviderMixIn.__init__(self)
self.register_options_provider(self)
self.load_provider_defaults()
class RegrTC(TestCase):
def setUp(self):
self.linter = Linter()
def test_load_defaults(self):
self.linter.load_command_line_configuration([])
self.assertEqual(self.linter.config.profile, False)
def test_register_options_multiple_groups(self):
"""ensure multiple option groups can be registered at once"""
config = Configuration()
self.assertEqual(config.options, ())
new_options = (
('option1', {'type': 'string', 'help': '',
'group': 'g1', 'level': 2}),
('option2', {'type': 'string', 'help': '',
'group': 'g1', 'level': 2}),
('option3', {'type': 'string', 'help': '',
'group': 'g2', 'level': 2}),
)
config.register_options(new_options)
self.assertEqual(config.options, new_options)
class MergeTC(TestCase):
def test_merge1(self):
merged = merge_options([('dothis', {'type':'yn', 'action': 'store', 'default': True, 'metavar': ''}),
('dothis', {'type':'yn', 'action': 'store', 'default': False, 'metavar': ''}),
])
self.assertEqual(len(merged), 1)
self.assertEqual(merged[0][0], 'dothis')
self.assertEqual(merged[0][1]['default'], True)
def test_merge2(self):
merged = merge_options([('dothis', {'type':'yn', 'action': 'store', 'default': True, 'metavar': ''}),
('value', {'type': 'string', 'metavar': '', 'short': 'v'}),
('dothis', {'type':'yn', 'action': 'store', 'default': False, 'metavar': ''}),
])
self.assertEqual(len(merged), 2)
self.assertEqual(merged[0][0], 'value')
self.assertEqual(merged[1][0], 'dothis')
self.assertEqual(merged[1][1]['default'], True)
if __name__ == '__main__':
unittest_main()