From dc9ac625149c7ec68f3ac2468ea6dc60b023bb88 Mon Sep 17 00:00:00 2001 From: jenisys Date: Wed, 17 Jan 2018 22:45:45 +0100 Subject: [PATCH] Add UserDataNamespace and use it in JUnitReporter. --- behave/contrib/formatter_missing_steps.py | 80 +++++ behave/reporter/junit.py | 21 +- behave/runner_util.py | 39 ++- behave/userdata.py | 90 ++++++ docs/formatters.rst | 14 +- test/test_userdata.py | 184 ----------- tests/unit/test_userdata.py | 362 ++++++++++++++++++++++ 7 files changed, 581 insertions(+), 209 deletions(-) create mode 100644 behave/contrib/formatter_missing_steps.py delete mode 100644 test/test_userdata.py create mode 100644 tests/unit/test_userdata.py diff --git a/behave/contrib/formatter_missing_steps.py b/behave/contrib/formatter_missing_steps.py new file mode 100644 index 000000000..dd42c453d --- /dev/null +++ b/behave/contrib/formatter_missing_steps.py @@ -0,0 +1,80 @@ +# -*- coding: UTF-8 -*- +""" +Provides a formatter that writes prototypes for missing step functions +into a step module file by using step snippets. + +NOTE: This is only simplistic, proof-of-concept code. +""" + +from __future__ import absolute_import, print_function +from behave.runner_util import make_undefined_step_snippets +from .steps import StepsUsageFormatter + + +STEP_MODULE_TEMPLATE = '''\ +# -*- coding: {encoding} -*- +""" +Missing step implementations (proof-of-concept). +""" + +from behave import given, when, then, step + +{step_snippets} +''' + + +class MissingStepsFormatter(StepsUsageFormatter): + """Formatter that writes missing steps snippets into a step module file. + + Reuses StepsUsageFormatter class because it already contains the logic + for discovering missing/undefined steps. + + .. code-block:: ini + + # -- FILE: behave.ini + # NOTE: Long text value needs indentation on following lines. + [behave.userdata] + behave.formatter.missing_steps.template = # -*- coding: {encoding} -*- + # Missing step implementations. + from behave import given, when, then, step + + {step_snippets} + """ + name = "missing-steps" + description = "Writes implementation for missing step definitions." + template = STEP_MODULE_TEMPLATE + scope = "behave.formatter.missing_steps" + + def __init__(self, stream_opener, config): + super(MissingStepsFormatter, self).__init__(stream_opener, config) + self.template = self.__class__.template + self.init_from_userdata(config.userdata) + + def init_from_userdata(self, userdata): + scoped_name = "%s.%s" %(self.scope, "template") + template_text = userdata.get(scoped_name, self.template) + self.template = template_text + + def close(self): + """Called at end of test run. + NOTE: Overwritten to avoid to truncate/overwrite output-file. + """ + if self.step_registry and self.undefined_steps: + # -- ENSURE: Output stream is open. + self.stream = self.open() + self.report() + + # -- FINALLY: + self.close_stream() + + # -- REPORT SPECIFIC-API: + def report(self): + """Writes missing step implementations by using step snippets.""" + step_snippets = make_undefined_step_snippets(undefined_steps) + encoding = self.stream.encoding or "UTF-8" + function_separator = u"\n\n\n" + step_snippets_text = function_separator.join(step_snippets) + module_text = self.template.format(encoding=encoding, + step_snippets=step_snippets_text) + self.stream.write(module_text) + self.stream.write("\n") diff --git a/behave/reporter/junit.py b/behave/reporter/junit.py index 9e7c20353..48e1411d4 100644 --- a/behave/reporter/junit.py +++ b/behave/reporter/junit.py @@ -80,6 +80,7 @@ from behave.formatter import ansi_escapes from behave.model_describe import ModelDescriptor from behave.textutil import indent, make_indentation, text as _text +from behave.userdata import UserDataNamespace import six if six.PY2: # -- USE: Python3 backport for better unicode compatibility. @@ -193,17 +194,15 @@ def setup_with_userdata(self, userdata): behave.reporter.junit.show_hostname = false """ # -- EXPERIMENTAL: - option_names = [ - "show_timings", "show_skipped_always", - "show_timestamp", "show_hostname", - "show_scenarios", "show_tags", "show_multiline", - ] - for option_name in option_names: - name = "%s.%s" % (self.userdata_scope, option_name) - default_value = getattr(self, option_name) - value = userdata.getbool(name, default_value) - if value != default_value: - setattr(self, option_name, value) + config = UserDataNamespace(self.userdata_scope, userdata) + self.show_hostname = config.getbool("show_hostname", self.show_hostname) + self.show_multiline = config.getbool("show_multiline", self.show_multiline) + self.show_scenarios = config.getbool("show_scenarios", self.show_scenarios) + self.show_tags = config.getbool("show_tags", self.show_tags) + self.show_timings = config.getbool("show_timings", self.show_timings) + self.show_timestamp = config.getbool("show_timestamp", self.show_timestamp) + self.show_skipped_always = config.getbool("show_skipped_always", + self.show_skipped_always) def make_feature_filename(self, feature): filename = None diff --git a/behave/runner_util.py b/behave/runner_util.py index 3b76fe28c..59376dcb6 100644 --- a/behave/runner_util.py +++ b/behave/runner_util.py @@ -414,10 +414,9 @@ def load_step_modules(step_paths): def make_undefined_step_snippet(step, language=None): - """ - Helper function to create an undefined-step snippet for a step. + """Helper function to create an undefined-step snippet for a step. - :param step: Step to use (as Step object or step text). + :param step: Step to use (as Step object or string). :param language: i18n language, optionally needed for step text parsing. :return: Undefined-step snippet (as string). """ @@ -426,9 +425,7 @@ def make_undefined_step_snippet(step, language=None): steps = parser.parse_steps(step_text, language=language) step = steps[0] assert step, "ParseError: %s" % step_text - # prefix = u"" - # if sys.version_info[0] == 2: - # prefix = u"u" + prefix = u"u" single_quote = "'" if single_quote in step.name: @@ -441,6 +438,29 @@ def make_undefined_step_snippet(step, language=None): return snippet +def make_undefined_step_snippets(undefined_steps, make_snippet=None): + """Creates a list of undefined step snippets. + Note that duplicated steps are removed internally. + + :param undefined_steps: List of undefined steps (as Step object or string). + :param make_snippet: Function that generates snippet (optional) + :return: List of undefined step snippets (as list of strings) + """ + if make_snippet is None: + make_snippet = make_undefined_step_snippet + + # -- NOTE: Remove any duplicated undefined steps. + step_snippets = [] + collected_steps = set() + for undefined_step in undefined_steps: + if undefined_step in collected_steps: + continue + collected_steps.add(undefined_step) + step_snippet = make_snippet(undefined_step) + step_snippets.append(step_snippet) + return step_snippets + + def print_undefined_step_snippets(undefined_steps, stream=None, colored=True): """ Print snippets for the undefined steps that were discovered. @@ -456,12 +476,7 @@ def print_undefined_step_snippets(undefined_steps, stream=None, colored=True): msg = u"\nYou can implement step definitions for undefined steps with " msg += u"these snippets:\n\n" - printed = set() - for step in undefined_steps: - if step in printed: - continue - printed.add(step) - msg += make_undefined_step_snippet(step) + msg += u"\n".join(make_undefined_step_snippets(undefined_steps)) if colored: # -- OOPS: Unclear if stream supports ANSI coloring. diff --git a/behave/userdata.py b/behave/userdata.py index 7d3c65936..84a79eb12 100644 --- a/behave/userdata.py +++ b/behave/userdata.py @@ -128,3 +128,93 @@ def getbool(self, name, default=False): :raises: ValueError, if type conversion fails. """ return self.getas(parse_bool, name, default, valuetype=bool) + + @classmethod + def make(cls, data): + if data is None: + data = cls() + elif not isinstance(data, cls): + data = cls(data) + return data + + +class UserDataNamespace(object): + """Provides a light-weight dictview to the user data that allows you + to access all params in a namespace, that use "{namespace}.*" names. + + .. code-block:: python + + my_config = UserDataNamespace("my.config", userdata) + value1 = my_config.getint("value1") # USE: my.config.value1 + value2 = my_config.get("value2") # USE: my.config.value2 + """ + + def __init__(self, namespace, data=None): + self.namespace = namespace or "" + self.data = UserData.make(data) + + @staticmethod + def make_scoped(namespace, name): + """Creates a scoped-name from its parts.""" + if not namespace: # noqa + return name + return "%s.%s" % (namespace, name) + + # -- DICT-LIKE: + def get(self, name, default=None): + scoped_name = self.make_scoped(self.namespace, name) + return self.data.get(scoped_name, default) + + def getas(self, convert, name, default=None, valuetype=None): + scoped_name = self.make_scoped(self.namespace, name) + return self.data.getas(convert, scoped_name, default=default, + valuetype=valuetype) + + def getint(self, name, default=0): + scoped_name = self.make_scoped(self.namespace, name) + return self.data.getint(scoped_name, default=default) + + def getfloat(self, name, default=0.0): + scoped_name = self.make_scoped(self.namespace, name) + return self.data.getfloat(scoped_name, default=default) + + def getbool(self, name, default=False): + scoped_name = self.make_scoped(self.namespace, name) + return self.data.getbool(scoped_name, default=default) + + def __contains__(self, name): + scoped_name = self.make_scoped(self.namespace, name) + return scoped_name in self.data + + def __getitem__(self, name): + scoped_name = self.make_scoped(self.namespace, name) + return self.data[scoped_name] + + def __setitem__(self, name, value): + scoped_name = self.make_scoped(self.namespace, name) + self.data[scoped_name] = value + + def __len__(self): + return len(self.scoped_keys()) + + def scoped_keys(self): + if not self.namespace: # noqa + return self.data.keys() + prefix = "%s." % self.namespace + return [key for key in self.data.keys() if key.startswith(prefix)] + + def keys(self): + prefix = "%s." % self.namespace + for scoped_name in self.scoped_keys(): + name = scoped_name.replace(prefix, "", 1) + yield name + + def values(self): + for scoped_name in self.scoped_keys(): + yield self.data[scoped_name] + + def items(self): + for name in self.keys(): + scoped_name = self.make_scoped(self.namespace, name) + value = self.data[scoped_name] + yield (name, value) diff --git a/docs/formatters.rst b/docs/formatters.rst index 59ea525c5..a40fd8d6b 100644 --- a/docs/formatters.rst +++ b/docs/formatters.rst @@ -71,7 +71,9 @@ Behave allows you to provide your own formatter (class):: behave -f foo.bar:Json2Formatter ... The usage of a user-defined formatter can be simplified by providing an -alias name for it in the configuration file:: +alias name for it in the configuration file: + +.. code-block:: ini # -- FILE: behave.ini # ALIAS SUPPORTS: behave -f json2 ... @@ -80,7 +82,9 @@ alias name for it in the configuration file:: json2 = foo.bar:Json2Formatter If your formatter can be configured, you should use the userdata concept -to provide them. The formatter should use the attribute schema:: +to provide them. The formatter should use the attribute schema: + +.. code-block:: ini # -- FILE: behave.ini # SCHEMA: behave.formatter.. @@ -105,4 +109,10 @@ teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI te with behave. ============== ========================================================================= +.. code-block:: ini + # -- FILE: behave.ini + # FORMATTER ALIASES: behave -f allure ... + [behave.formatters] + allure = allure_behave.formatter:AllureFormatter + teamcity = behave_teamcity:TeamcityFormatter diff --git a/test/test_userdata.py b/test/test_userdata.py deleted file mode 100644 index 6c5e4bd6f..000000000 --- a/test/test_userdata.py +++ /dev/null @@ -1,184 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import -from behave.configuration import Configuration -from behave.userdata import UserData, parse_user_define -from behave._types import Unknown -from unittest import TestCase -from nose.tools import eq_ - - -class TestParseUserDefine(TestCase): - """Test parse_user_define() function.""" - - def test_parse__name_value(self): - parts = parse_user_define("person=Alice") - self.assertEqual(parts, ("person", "Alice")) - - def test_parse__name_only_for_boolean_flag(self): - parts = parse_user_define("boolean_flag") - self.assertEqual(parts, ("boolean_flag", "true")) - - def test_parse__name_value_with_padded_whitespace(self): - texts = [ - " person=Alice", - "person=Alice ", - "person = Alice", - ] - for text in texts: - parts = parse_user_define(text) - self.assertEqual(parts, ("person", "Alice")) - - def test_parse__name_value_with_quoted_name_value_pair(self): - texts = [ - '"person=Alice and Bob"', - "'person=Alice and Bob'", - ] - for text in texts: - parts = parse_user_define(text) - self.assertEqual(parts, ("person", "Alice and Bob")) - - def test_parse__name_value_with_quoted_value(self): - texts = [ - 'person="Alice and Bob"', - "person='Alice and Bob'", - ] - for text in texts: - parts = parse_user_define(text) - self.assertEqual(parts, ("person", "Alice and Bob")) - - - - -class TestUserData(TestCase): - """Test UserData class.""" - - def test_userdata_is_dictlike(self): - userdata = UserData(name="Foo", number=42) - value1 = userdata["name"] - value2 = userdata.get("number") - value3 = userdata.get("unknown", Unknown) - assert isinstance(userdata, dict) - self.assertEqual(value1, "Foo") - self.assertEqual(value2, 42) - assert value3 is Unknown - - def test_getas__with_known_param_and_valid_text(self): - userdata = UserData(param="42") - assert "param" in userdata, "ENSURE: known param" - - value = userdata.getas(int, "param") - assert isinstance(value, int) - self.assertEqual(value, 42) - - def test_getas__with_known_param_and_invalid_text_raises_ValueError(self): - userdata = UserData(param="__BAD_NUMBER__") - assert "param" in userdata, "ENSURE: known param" - self.assertRaises(ValueError, userdata.getas, int, "param") - - def test_getas__with_known_param_and_preconverted_value(self): - userdata = UserData(param=42) - assert "param" in userdata, "ENSURE: known param" - - value = userdata.getas(int, "param") - assert isinstance(value, int) - self.assertEqual(value, 42) - - def test_getas__with_known_param_and_preconverted_value_and_valuetype(self): - userdata = UserData(param=42) - assert "param" in userdata, "ENSURE: known param" - - def parse_int(text): - return int(text) - - value = userdata.getas(parse_int, "param", valuetype=int) - assert isinstance(value, int) - self.assertEqual(value, 42) - - def test_getas__with_unknown_param_without_default_returns_none(self): - userdata = UserData() - assert "param" not in userdata, "ENSURE: unknown param" - - value = userdata.getas(int, "param") - assert value is None - - def test_getas__with_unknown_param_returns_default_value(self): - userdata = UserData() - assert "param" not in userdata, "ENSURE: unknown param" - - value = userdata.getas(int, "param", 123) - assert isinstance(value, int) - self.assertEqual(value, 123) - - - def test_getint__with_known_param_and_valid_text(self): - userdata = UserData(param="42") - value = userdata.getint("param") - assert isinstance(value, int) - self.assertEqual(value, 42) - - def test_getint__with_known_param_and_invalid_text_raises_ValueError(self): - userdata = UserData(param="__BAD_NUMBER__") - self.assertRaises(ValueError, userdata.getint, "param") - - def test_getint__with_unknown_param_without_default_returns_zero(self): - userdata = UserData() - value = userdata.getint("param") - self.assertEqual(value, 0) - - def test_getint__with_unknown_param_returns_default_value(self): - userdata = UserData() - value = userdata.getint("param", 123) - assert isinstance(value, int) - self.assertEqual(value, 123) - - def test_getfloat__with_known_param_and_valid_text(self): - for valid_text in ["1.2", "2", "-1E+3", "+2.34E-5"]: - userdata = UserData(param=valid_text) - value = userdata.getfloat("param") - assert isinstance(value, float) - self.assertEqual(value, float(valid_text)) - - def test_getfloat__with_known_param_and_invalid_text_raises_ValueError(self): - userdata = UserData(param="__BAD_NUMBER__") - self.assertRaises(ValueError, userdata.getfloat, "param") - - def test_getfloat__with_unknown_param_without_default_returns_zero(self): - userdata = UserData() - value = userdata.getfloat("param") - self.assertEqual(value, 0.0) - - def test_getfloat__with_unknown_param_returns_default_value(self): - userdata = UserData() - value = userdata.getint("param", 1.2) - assert isinstance(value, float) - self.assertEqual(value, 1.2) - - - def test_getbool__with_known_param_and_valid_text(self): - for true_text in ["true", "TRUE", "True", "yes", "on", "1"]: - userdata = UserData(param=true_text) - value = userdata.getbool("param") - assert isinstance(value, bool), "text=%s" % true_text - self.assertEqual(value, True) - - for false_text in ["false", "FALSE", "False", "no", "off", "0"]: - userdata = UserData(param=false_text) - value = userdata.getbool("param") - assert isinstance(value, bool), "text=%s" % false_text - self.assertEqual(value, False) - - def test_getbool__with_known_param_and_invalid_text_raises_ValueError(self): - userdata = UserData(param="__BAD_VALUE__") - self.assertRaises(ValueError, userdata.getbool, "param") - - def test_getbool__with_unknown_param_without_default_returns_false(self): - userdata = UserData() - value = userdata.getfloat("param") - self.assertEqual(value, False) - - def test_getbool__with_unknown_param_returns_default_value(self): - userdata = UserData() - value = userdata.getint("param", 1.2) - assert isinstance(value, float) - self.assertEqual(value, 1.2) diff --git a/tests/unit/test_userdata.py b/tests/unit/test_userdata.py new file mode 100644 index 000000000..d8716beb7 --- /dev/null +++ b/tests/unit/test_userdata.py @@ -0,0 +1,362 @@ +# -*- coding: UTF-8 -*- + +from __future__ import absolute_import +from behave.userdata import parse_user_define, UserData, UserDataNamespace +from behave._types import Unknown +import pytest + + +class TestParseUserDefine(object): + """Test parse_user_define() function.""" + + def test_parse__name_value(self): + parts = parse_user_define("person=Alice") + assert parts == ("person", "Alice") + + def test_parse__name_only_for_boolean_flag(self): + parts = parse_user_define("boolean_flag") + assert parts == ("boolean_flag", "true") + + + @pytest.mark.parametrize("text", [ + " person=Alice", + "person=Alice ", + "person = Alice", + ]) + def test_parse__name_value_with_padded_whitespace(self, text): + parts = parse_user_define(text) + assert parts == ("person", "Alice") + + + @pytest.mark.parametrize("text", [ + '"person=Alice and Bob"', + "'person=Alice and Bob'", + ]) + def test_parse__name_value_with_quoted_name_value_pair(self, text): + parts = parse_user_define(text) + assert parts == ("person", "Alice and Bob") + + @pytest.mark.parametrize("text", [ + 'person="Alice and Bob"', + "person='Alice and Bob'", + ]) + def test_parse__name_value_with_quoted_value(self, text): + parts = parse_user_define(text) + assert parts == ("person", "Alice and Bob") + + + + +class TestUserData(object): + """Test UserData class.""" + + def test_userdata_is_dictlike(self): + userdata = UserData(name="Foo", number=42) + value1 = userdata["name"] + value2 = userdata.get("number") + value3 = userdata.get("unknown", Unknown) + assert isinstance(userdata, dict) + assert value1 == "Foo" + assert value2 == 42 + assert value3 is Unknown + + def test_getas__with_known_param_and_valid_text(self): + userdata = UserData(param="42") + assert "param" in userdata, "ENSURE: known param" + + value = userdata.getas(int, "param") + assert isinstance(value, int) + assert value == 42 + + def test_getas__with_known_param_and_invalid_text_raises_ValueError(self): + userdata = UserData(param="__BAD_NUMBER__") + assert "param" in userdata, "ENSURE: known param" + with pytest.raises(ValueError): + userdata.getas(int, "param") + + def test_getas__with_known_param_and_preconverted_value(self): + userdata = UserData(param=42) + assert "param" in userdata, "ENSURE: known param" + + value = userdata.getas(int, "param") + assert isinstance(value, int) + assert value == 42 + + def test_getas__with_known_param_and_preconverted_value_and_valuetype(self): + userdata = UserData(param=42) + assert "param" in userdata, "ENSURE: known param" + + def parse_int(text): + return int(text) + + value = userdata.getas(parse_int, "param", valuetype=int) + assert isinstance(value, int) + assert value == 42 + + def test_getas__with_unknown_param_without_default_returns_none(self): + userdata = UserData() + assert "param" not in userdata, "ENSURE: unknown param" + + value = userdata.getas(int, "param") + assert value is None + + def test_getas__with_unknown_param_returns_default_value(self): + userdata = UserData() + assert "param" not in userdata, "ENSURE: unknown param" + + value = userdata.getas(int, "param", 123) + assert isinstance(value, int) + assert value == 123 + + + def test_getint__with_known_param_and_valid_text(self): + userdata = UserData(param="42") + value = userdata.getint("param") + assert isinstance(value, int) + assert value == 42 + + def test_getint__with_known_param_and_invalid_text_raises_ValueError(self): + userdata = UserData(param="__BAD_NUMBER__") + with pytest.raises(ValueError): + userdata.getint("param") + + def test_getint__with_unknown_param_without_default_returns_zero(self): + userdata = UserData() + value = userdata.getint("param") + assert value == 0 + + def test_getint__with_unknown_param_returns_default_value(self): + userdata = UserData() + value = userdata.getint("param", 123) + assert isinstance(value, int) + assert value == 123 + + def test_getfloat__with_known_param_and_valid_text(self): + for valid_text in ["1.2", "2", "-1E+3", "+2.34E-5"]: + userdata = UserData(param=valid_text) + value = userdata.getfloat("param") + assert isinstance(value, float) + assert value == float(valid_text) + + def test_getfloat__with_known_param_and_invalid_text_raises_ValueError(self): + userdata = UserData(param="__BAD_NUMBER__") + with pytest.raises(ValueError): + userdata.getfloat("param") + + def test_getfloat__with_unknown_param_without_default_returns_zero(self): + userdata = UserData() + value = userdata.getfloat("param") + assert value == 0.0 + + def test_getfloat__with_unknown_param_returns_default_value(self): + userdata = UserData() + value = userdata.getint("param", 1.2) + assert isinstance(value, float) + assert value == 1.2 + + + @pytest.mark.parametrize("text", [ + "true", "TRUE", "True", "yes", "on", "1" + ]) + def test_getbool__with_known_param_and_valid_true_text(self, text): + true_text = text + userdata = UserData(param=true_text) + value = userdata.getbool("param") + assert isinstance(value, bool), "text=%s" % true_text + assert value is True + + @pytest.mark.parametrize("text", [ + "false", "FALSE", "False", "no", "off", "0" + ]) + def test_getbool__with_known_param_and_valid_false_text(self, text): + false_text = text + userdata = UserData(param=false_text) + value = userdata.getbool("param") + assert isinstance(value, bool), "text=%s" % false_text + assert value is False + + def test_getbool__with_known_param_and_invalid_text_raises_ValueError(self): + userdata = UserData(param="__BAD_VALUE__") + with pytest.raises(ValueError): + userdata.getbool("param") + + def test_getbool__with_unknown_param_without_default_returns_false(self): + userdata = UserData() + value = userdata.getfloat("param") + assert value == False + + def test_getbool__with_unknown_param_returns_default_value(self): + userdata = UserData() + value = userdata.getint("param", 1.2) + assert isinstance(value, float) + assert value == 1.2 + + +class TestUserDataNamespace(object): + + def test_make_scoped(self): + scoped_name = UserDataNamespace.make_scoped("my.scope", "param") + assert scoped_name == "my.scope.param" + + def test_make_scoped__with_empty_scope(self): + scoped_name = UserDataNamespace.make_scoped("", "param") + assert scoped_name == "param" + + def test_ctor__converts_dict_into_userdata(self): + userdata = {"my.scope.param1": 12} + config = UserDataNamespace("my.scope", userdata) + assert isinstance(config.data, UserData) + assert config.data is not userdata + assert config.data == userdata + + def test_ctor__converts_items_into_userdata(self): + userdata = {"my.scope.param1": 12} + config = UserDataNamespace("my.scope", userdata.items()) + assert isinstance(config.data, UserData) + assert config.data is not userdata + assert config.data == userdata + + def test_ctor__can_assign_userdata_afterwards(self): + userdata = UserData({"my.scope.param1": 12}) + config = UserDataNamespace("my.scope") + config.data = userdata + assert isinstance(config.data, UserData) + assert config.data is userdata + + def test_get__retrieves_value_when_scoped_param_exists(self): + userdata = UserData({"my.scope.param1": 12}) + config = UserDataNamespace("my.scope", userdata) + assert config.get("param1") == 12 + assert config["param1"] == 12 + + def test_get__returns_default_when_scoped_param_not_exists(self): + config = UserDataNamespace("my.scope", UserData({})) + assert config.get("UNKNOWN_PARAM", "DEFAULT1") == "DEFAULT1" + assert config.get("UNKNOWN_PARAM", "DEFAULT2") == "DEFAULT2" + + def test_getint__retrieves_value_when_scoped_param_exists(self): + userdata = UserData({"my.scope.param1": "12"}) + config = UserDataNamespace("my.scope", userdata) + assert config.getint("param1") == 12 + + def test_getint__retrieves_value_when_scoped_param_exists(self): + userdata = UserData({"my.scope.param2": "12"}) + config = UserDataNamespace("my.scope", userdata) + assert config.getint("param2") == 12 + + def test_getint__returns_default_when_scoped_param_not_exists(self): + userdata = UserData({}) + config = UserDataNamespace("my.scope", userdata) + assert config.getint("UNKNOWN_PARAM", 123) == 123 + assert config.getint("UNKNOWN_PARAM", 321) == 321 + + def test_getbool__retrieves_value_when_scoped_param_exists(self): + userdata = UserData({"my.scope.param3": "yes"}) + config = UserDataNamespace("my.scope", userdata) + assert config.getbool("param3") == True + + def test_getbool__returns_default_when_scoped_param_not_exists(self): + userdata = UserData({}) + config = UserDataNamespace("my.scope", userdata) + assert config.getint("UNKNOWN_PARAM", True) == True + assert config.getint("UNKNOWN_PARAM", False) == False + + def test_contains__when_scoped_param_exists(self): + userdata = UserData({"my.scope.param": 12}) + config = UserDataNamespace("my.scope", userdata) + assert "param" in config + assert not("param" not in config) + + def test_contains__when_scoped_param_not_exists(self): + userdata = UserData({"my.scope.param": 12}) + config = UserDataNamespace("my.scope", userdata) + assert "UNKNOWN_PARAM" not in config + assert not ("UNKNOWN_PARAM" in config) + + def test_getitem__returns_value_when_param_exists(self): + userdata = UserData({"my.scope.param": "123"}) + config = UserDataNamespace("my.scope", userdata) + assert config["param"] == "123" + + def test_getitem__raises_error_when_param_not_exists(self): + userdata = UserData({"my.scope.param": "123"}) + config = UserDataNamespace("my.scope", userdata) + with pytest.raises(KeyError): + _ = config["UNKNOWN_PARAM"] + + def test_setitem__stores_value(self): + userdata = UserData({"my.scope.param1": "123"}) + config = UserDataNamespace("my.scope", userdata) + scoped_name = "my.scope.new_param" + config["new_param"] = 1234 + assert "new_param" in config + assert config["new_param"] == 1234 + assert scoped_name in config.data + assert config.data[scoped_name] == 1234 + + def test_length__returns_zero_without_params(self): + userdata = UserData({"my.other_scope.param1": "123"}) + config = UserDataNamespace("my.scope", userdata) + assert len(config) == 0 + + def test_length__with_scoped_params(self): + userdata1 = UserData({"my.scope.param1": "123"}) + userdata2 = UserData({ + "my.other_scope.param1": "123", + "my.scope.param1": "123", + "my.scope.param2": "456", + }) + userdata3 = UserData({ + "my.other_scope.param1": "123", + "my.scope.param1": "123", + "my.scope.param2": "456", + "my.scope.param3": "789", + "my.other_scope.param2": "123", + }) + config = UserDataNamespace("my.scope") + config.data = userdata1 + assert len(config) == 1 + config.data = userdata2 + assert len(config) == 2 + config.data = userdata3 + assert len(config) == 3 + + def test_scoped_keys__with_scoped_params(self): + userdata = UserData({ + "my.other_scope.param1": "123", + "my.scope.param1": "123", + "my.scope.param2": "456", + "my.other_scope.param2": "123", + }) + config = UserDataNamespace("my.scope", userdata) + assert sorted(config.scoped_keys()) == ["my.scope.param1", "my.scope.param2"] + + def test_keys__with_scoped_params(self): + userdata = UserData({ + "my.other_scope.param1": "__OTHER1__", + "my.scope.param1": "123", + "my.scope.param2": "456", + "my.other_scope.param2": "__OTHER2__", + }) + config = UserDataNamespace("my.scope", userdata) + assert sorted(config.keys()) == ["param1", "param2"] + + def test_values__with_scoped_params(self): + userdata = UserData({ + "my.other_scope.param1": "__OTHER1__", + "my.scope.param1": "123", + "my.scope.param2": "456", + "my.other_scope.param2": "__OTHER2__", + }) + config = UserDataNamespace("my.scope", userdata) + assert sorted(config.values()) == ["123", "456"] + + def test_items__with_scoped_params(self): + userdata = UserData({ + "my.other_scope.param1": "__OTHER1__", + "my.scope.param1": "123", + "my.scope.param2": "456", + "my.other_scope.param2": "__OTHER2__", + }) + config = UserDataNamespace("my.scope", userdata) + assert sorted(config.items()) == [("param1", "123"), ("param2", "456")]