From 477c9c4119df5fd45c1129651922d238dccad8c9 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Tue, 16 Sep 2014 15:50:04 -0700 Subject: testsuite: Added unit tests for new option parsing --- .../Testsrc/Testlib/TestOptions/TestOptions.py | 469 +++++++++++++++++++++ 1 file changed, 469 insertions(+) create mode 100644 testsuite/Testsrc/Testlib/TestOptions/TestOptions.py (limited to 'testsuite/Testsrc/Testlib/TestOptions/TestOptions.py') diff --git a/testsuite/Testsrc/Testlib/TestOptions/TestOptions.py b/testsuite/Testsrc/Testlib/TestOptions/TestOptions.py new file mode 100644 index 000000000..b76cd6d3a --- /dev/null +++ b/testsuite/Testsrc/Testlib/TestOptions/TestOptions.py @@ -0,0 +1,469 @@ +"""basic option parsing tests.""" + +import argparse +import os +import tempfile + +import mock + +from Bcfg2.Compat import ConfigParser +from Bcfg2.Options import Option, PathOption, BooleanOption, Parser, \ + PositionalArgument, OptionParserException, Common, new_parser, get_parser +from testsuite.Testsrc.Testlib.TestOptions import OptionTestCase, \ + make_config, clean_environment + + +class TestBasicOptions(OptionTestCase): + """test basic option parsing.""" + def setUp(self): + # parsing options can modify the Option objects themselves. + # that's probably bad -- and it's definitely bad if we ever + # want to do real on-the-fly config changes -- but it's easier + # to leave it as is and set the options on each test. + self.options = [ + BooleanOption("--test-true-boolean", env="TEST_TRUE_BOOLEAN", + cf=("test", "true_boolean"), default=True), + BooleanOption("--test-false-boolean", env="TEST_FALSE_BOOLEAN", + cf=("test", "false_boolean"), default=False), + BooleanOption(cf=("test", "true_config_boolean"), + default=True), + BooleanOption(cf=("test", "false_config_boolean"), + default=False), + Option("--test-option", env="TEST_OPTION", cf=("test", "option"), + default="foo"), + PathOption("--test-path-option", env="TEST_PATH_OPTION", + cf=("test", "path"), default="/test")] + + @clean_environment + def _test_options(self, options=None, env=None, config=None): + """helper to test a set of options. + + returns the namespace from parsing the given CLI options with + the given config and environment. + """ + if config is not None: + config = {"test": config} + if options is None: + options = [] + + @make_config(config) + def inner(config_file): + """do the actual tests, since py2.4 lacks context managers.""" + result = argparse.Namespace() + parser = Parser(components=[self], namespace=result) + parser.parse(argv=["-C", config_file] + options) + return result + + if env is not None: + for name, value in env.items(): + os.environ[name] = value + + return inner() + + def test_expand_path(self): + """expand ~ in path option.""" + options = self._test_options(options=["--test-path-option", + "~/test"]) + self.assertEqual(options.test_path_option, + os.path.expanduser("~/test")) + + def test_canonicalize_path(self): + """get absolute path from path option.""" + options = self._test_options(options=["--test-path-option", + "./test"]) + self.assertEqual(options.test_path_option, + os.path.abspath("./test")) + + def test_default_bool(self): + """use the default value of boolean options.""" + options = self._test_options() + self.assertTrue(options.test_true_boolean) + self.assertFalse(options.test_false_boolean) + self.assertTrue(options.true_config_boolean) + self.assertFalse(options.false_config_boolean) + + def test_default(self): + """use the default value of an option.""" + options = self._test_options() + self.assertEqual(options.test_option, "foo") + + def test_default_path(self): + """use the default value of a path option.""" + options = self._test_options() + self.assertEqual(options.test_path_option, "/test") + + def test_invalid_boolean(self): + """set boolean to invalid values.""" + self.assertRaises(ValueError, + self._test_options, + config={"true_boolean": "you betcha"}) + self.assertRaises(ValueError, + self._test_options, + env={"TEST_TRUE_BOOLEAN": "hell no"}) + + def test_set_boolean_in_config(self): + """set boolean options in config files.""" + set_to_defaults = {"true_boolean": "1", + "false_boolean": "0", + "true_config_boolean": "yes", + "false_config_boolean": "no"} + options = self._test_options(config=set_to_defaults) + self.assertTrue(options.test_true_boolean) + self.assertFalse(options.test_false_boolean) + self.assertTrue(options.true_config_boolean) + self.assertFalse(options.false_config_boolean) + + set_to_other = {"true_boolean": "false", + "false_boolean": "true", + "true_config_boolean": "off", + "false_config_boolean": "on"} + options = self._test_options(config=set_to_other) + self.assertFalse(options.test_true_boolean) + self.assertTrue(options.test_false_boolean) + self.assertFalse(options.true_config_boolean) + self.assertTrue(options.false_config_boolean) + + def test_set_in_config(self): + """set options in config files.""" + options = self._test_options(config={"option": "foo"}) + self.assertEqual(options.test_option, "foo") + + options = self._test_options(config={"option": "bar"}) + self.assertEqual(options.test_option, "bar") + + def test_set_path_in_config(self): + """set path options in config files.""" + options = self._test_options(config={"path": "/test"}) + self.assertEqual(options.test_path_option, "/test") + + options = self._test_options(config={"path": "/foo"}) + self.assertEqual(options.test_path_option, "/foo") + + def test_set_boolean_in_env(self): + """set boolean options in environment.""" + set_to_defaults = {"TEST_TRUE_BOOLEAN": "1", + "TEST_FALSE_BOOLEAN": "0"} + options = self._test_options(env=set_to_defaults) + self.assertTrue(options.test_true_boolean) + self.assertFalse(options.test_false_boolean) + + set_to_other = {"TEST_TRUE_BOOLEAN": "false", + "TEST_FALSE_BOOLEAN": "true"} + options = self._test_options(env=set_to_other) + self.assertFalse(options.test_true_boolean) + self.assertTrue(options.test_false_boolean) + + def test_set_in_env(self): + """set options in environment.""" + options = self._test_options(env={"TEST_OPTION": "foo"}) + self.assertEqual(options.test_option, "foo") + + options = self._test_options(env={"TEST_OPTION": "bar"}) + self.assertEqual(options.test_option, "bar") + + def test_set_path_in_env(self): + """set path options in environment.""" + options = self._test_options(env={"TEST_PATH_OPTION": "/test"}) + self.assertEqual(options.test_path_option, "/test") + + options = self._test_options(env={"TEST_PATH_OPTION": "/foo"}) + self.assertEqual(options.test_path_option, "/foo") + + def test_set_boolean_in_cli(self): + """set boolean options in CLI options.""" + # passing the option yields the reverse of the default, no + # matter the default + options = self._test_options(options=["--test-true-boolean", + "--test-false-boolean"]) + self.assertFalse(options.test_true_boolean) + self.assertTrue(options.test_false_boolean) + + def test_set_in_cli(self): + """set options in CLI options.""" + options = self._test_options(options=["--test-option", "foo"]) + self.assertEqual(options.test_option, "foo") + + options = self._test_options(options=["--test-option", "bar"]) + self.assertEqual(options.test_option, "bar") + + def test_set_path_in_cli(self): + """set path options in CLI options.""" + options = self._test_options(options=["--test-path-option", "/test"]) + self.assertEqual(options.test_path_option, "/test") + + options = self._test_options(options=["--test-path-option", "/foo"]) + self.assertEqual(options.test_path_option, "/foo") + + def test_env_overrides_config_bool(self): + """setting boolean option in the environment overrides config file.""" + config = {"true_boolean": "false", + "false_boolean": "true"} + env = {"TEST_TRUE_BOOLEAN": "yes", + "TEST_FALSE_BOOLEAN": "no"} + options = self._test_options(config=config, env=env) + self.assertTrue(options.test_true_boolean) + self.assertFalse(options.test_false_boolean) + + def test_env_overrides_config(self): + """setting option in the environment overrides config file.""" + options = self._test_options(config={"option": "bar"}, + env={"TEST_OPTION": "baz"}) + self.assertEqual(options.test_option, "baz") + + def test_env_overrides_config_path(self): + """setting path option in the environment overrides config file.""" + options = self._test_options(config={"path": "/foo"}, + env={"TEST_PATH_OPTION": "/bar"}) + self.assertEqual(options.test_path_option, "/bar") + + def test_cli_overrides_config_bool(self): + """setting boolean option in the CLI overrides config file.""" + config = {"true_boolean": "on", + "false_boolean": "off"} + options = ["--test-true-boolean", "--test-false-boolean"] + options = self._test_options(options=options, config=config) + self.assertFalse(options.test_true_boolean) + self.assertTrue(options.test_false_boolean) + + def test_cli_overrides_config(self): + """setting option in the CLI overrides config file.""" + options = self._test_options(options=["--test-option", "baz"], + config={"option": "bar"}) + self.assertEqual(options.test_option, "baz") + + def test_cli_overrides_config_path(self): + """setting path option in the CLI overrides config file.""" + options = self._test_options(options=["--test-path-option", "/bar"], + config={"path": "/foo"}) + self.assertEqual(options.test_path_option, "/bar") + + def test_cli_overrides_env_bool(self): + """setting boolean option in the CLI overrides environment.""" + env = {"TEST_TRUE_BOOLEAN": "0", + "TEST_FALSE_BOOLEAN": "1"} + options = ["--test-true-boolean", "--test-false-boolean"] + options = self._test_options(options=options, env=env) + self.assertFalse(options.test_true_boolean) + self.assertTrue(options.test_false_boolean) + + def test_cli_overrides_env(self): + """setting option in the CLI overrides environment.""" + options = self._test_options(options=["--test-option", "baz"], + env={"TEST_OPTION": "bar"}) + self.assertEqual(options.test_option, "baz") + + def test_cli_overrides_env_path(self): + """setting path option in the CLI overrides environment.""" + options = self._test_options(options=["--test-path-option", "/bar"], + env={"TEST_PATH_OPTION": "/foo"}) + self.assertEqual(options.test_path_option, "/bar") + + def test_cli_overrides_all_bool(self): + """setting boolean option in the CLI overrides everything else.""" + config = {"true_boolean": "no", + "false_boolean": "yes"} + env = {"TEST_TRUE_BOOLEAN": "0", + "TEST_FALSE_BOOLEAN": "1"} + options = ["--test-true-boolean", "--test-false-boolean"] + options = self._test_options(options=options, env=env) + self.assertFalse(options.test_true_boolean) + self.assertTrue(options.test_false_boolean) + + def test_cli_overrides_all(self): + """setting option in the CLI overrides everything else.""" + options = self._test_options(options=["--test-option", "baz"], + env={"TEST_OPTION": "bar"}, + config={"test": "quux"}) + self.assertEqual(options.test_option, "baz") + + def test_cli_overrides_all_path(self): + """setting path option in the CLI overrides everything else.""" + options = self._test_options(options=["--test-path-option", "/bar"], + env={"TEST_PATH_OPTION": "/foo"}, + config={"path": "/baz"}) + self.assertEqual(options.test_path_option, "/bar") + + @make_config() + def _test_dest(self, *args, **kwargs): + """helper to test that ``dest`` is set properly.""" + args = list(args) + expected = args.pop(0) + config_file = args.pop() + + sentinel = object() + kwargs["default"] = sentinel + + result = argparse.Namespace() + parser = Parser(namespace=result) + parser.add_options([Option(*args, **kwargs)]) + parser.parse(["-C", config_file]) + + self.assertTrue(hasattr(result, expected)) + self.assertEqual(getattr(result, expected), sentinel) + + def test_explicit_dest(self): + """set the ``dest`` of an option explicitly.""" + self._test_dest("bar", dest="bar") + + def test_dest_from_env_var(self): + """set the ``dest`` of an option from the env var name.""" + self._test_dest("foo", env="FOO") + + def test_dest_from_cf(self): + """set the ``dest`` of an option from the config option.""" + self._test_dest("foo_bar", cf=("test", "foo-bar")) + + def test_dest_from_cli(self): + """set the ``dest`` of an option from the CLI option.""" + self._test_dest("test_foo", "--test-foo") + + def test_dest_from_all(self): + """set the ``dest`` of an option from the best of multiple sources.""" + self._test_dest("foo_baz", cf=("test", "foo-bar"), env="FOO_BAZ") + self._test_dest("xyzzy", + "--xyzzy", cf=("test", "foo-bar"), env="FOO_BAZ") + self._test_dest("quux", + "--xyzzy", cf=("test", "foo-bar"), env="FOO_BAZ", + dest="quux") + + @make_config() + def test_positional_args(self, config_file): + """get values from positional arguments.""" + result = argparse.Namespace() + parser = Parser(namespace=result) + parser.add_options([PositionalArgument("single")]) + parser.parse(["-C", config_file, "single"]) + self.assertEqual(result.single, "single") + + result = argparse.Namespace() + parser = Parser(namespace=result) + parser.add_options([PositionalArgument("one"), + PositionalArgument("two")]) + parser.parse(["-C", config_file, "one", "two"]) + self.assertEqual(result.one, "one") + self.assertEqual(result.two, "two") + + def test_duplicate_cli_option(self): + """add duplicate CLI option.""" + parser = Parser(components=[self]) + self.assertRaises( + argparse.ArgumentError, + parser.add_options, + [Option("--test-option")]) + + def test_duplicate_env_option(self): + """add duplicate environment option.""" + parser = Parser(components=[self]) + self.assertRaises( + OptionParserException, + parser.add_options, + [Option(env="TEST_OPTION")]) + + def test_duplicate_cf_option(self): + """add duplicate config file option.""" + parser = Parser(components=[self]) + self.assertRaises( + OptionParserException, + parser.add_options, + [Option(cf=("test", "option"))]) + + @make_config() + def test_repository_macro(self, config_file): + """fix up macros.""" + result = argparse.Namespace() + parser = Parser(namespace=result) + parser.add_options([PathOption("--test1"), + PathOption("--test2"), + Common.repository]) + parser.parse(["-C", config_file, "-Q", "/foo/bar", + "--test1", "/test1", + "--test2", ""]) + self.assertEqual(result.repository, "/foo/bar") + self.assertEqual(result.test1, "/foo/bar/test1") + self.assertEqual(result.test2, "/foo/bar/foo/bar") + + @make_config() + def test_file_like_path_option(self, config_file): + """get file-like object from PathOption.""" + result = argparse.Namespace() + parser = Parser(namespace=result) + parser.add_options([PathOption("--test", type=argparse.FileType('r'))]) + + fd, name = tempfile.mkstemp() + fh = os.fdopen(fd, "w") + fh.write("test") + fh.close() + + parser.parse(["-C", config_file, "--test", name]) + self.assertEqual(result.test.name, name) + self.assertEqual(result.test.read(), "test") + + @clean_environment + @make_config() + def test_unknown_options(self, config_file): + """error on unknown options.""" + parser = Parser(components=[self]) + self.assertRaises(SystemExit, + parser.parse, + ["-C", config_file, "--not-a-real-option"]) + + @clean_environment + @make_config() + def test_reparse(self, config_file): + """reparse options.""" + result = argparse.Namespace() + parser = Parser(components=[self], namespace=result) + parser.parse(["-C", config_file]) + self.assertFalse(result.test_false_boolean) + + parser.parse(["-C", config_file]) + self.assertFalse(result.test_false_boolean) + + parser.reparse() + self.assertFalse(result.test_false_boolean) + + parser.reparse(["-C", config_file, "--test-false-boolean"]) + self.assertTrue(result.test_false_boolean) + + cfp = ConfigParser.ConfigParser() + cfp.add_section("test") + cfp.set("test", "false_boolean", "on") + parser.parse(["-C", config_file]) + cfp.write(open(config_file, "w")) + self.assertTrue(result.test_false_boolean) + + +class TestParsingHooks(OptionTestCase): + """test option parsing hooks.""" + def setUp(self): + self.options_parsed_hook = mock.Mock() + self.options = [BooleanOption("--test", default=False)] + self.results = argparse.Namespace() + new_parser() + self.parser = get_parser(components=[self], namespace=self.results) + + @make_config() + def test_parsing_hooks(self, config_file): + """option parsing hooks are called.""" + self.parser.parse(["-C", config_file]) + self.options_parsed_hook.assert_called_with() + + +class TestEarlyParsingHooks(OptionTestCase): + """test early option parsing hooks.""" + parse_first = True + + def setUp(self): + self.component_parsed_hook = mock.Mock() + self.options = [BooleanOption("--early-test", default=False)] + self.results = argparse.Namespace() + new_parser() + self.parser = get_parser(components=[self], namespace=self.results) + + @make_config() + def test_parsing_hooks(self, config_file): + """early option parsing hooks are called.""" + self.parser.parse(["-C", config_file, "--early-test"]) + self.assertEqual(self.component_parsed_hook.call_count, 1) + early_opts = self.component_parsed_hook.call_args[0][0] + self.assertTrue(early_opts.early_test) -- cgit v1.2.3-1-g7c22