# 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
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()