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