From 1bb276314a7432ccbdc59f150287ed9338194da4 Mon Sep 17 00:00:00 2001 From: Michelle Ark Date: Mon, 9 Jan 2023 15:22:06 -0500 Subject: [PATCH] unit tests for --warn-error-options --- core/dbt/events/functions.py | 10 +---- core/dbt/flags.py | 1 + core/dbt/helper_types.py | 11 +++-- test/unit/test_flags.py | 75 ++++++++++++++++++++++++++++++++- tests/unit/test_functions.py | 49 +++++++++++++++++++++ tests/unit/test_helper_types.py | 46 ++++++++++++++++++++ 6 files changed, 177 insertions(+), 15 deletions(-) create mode 100644 tests/unit/test_functions.py create mode 100644 tests/unit/test_helper_types.py diff --git a/core/dbt/events/functions.py b/core/dbt/events/functions.py index 066e72d6ab0..ae73b8a2a74 100644 --- a/core/dbt/events/functions.py +++ b/core/dbt/events/functions.py @@ -165,15 +165,7 @@ def warn_or_error(event, node=None): from dbt.helper_types import WarnErrorOptions warn_error_options = WarnErrorOptions.from_yaml_string(str(flags.WARN_ERROR_OPTIONS)) - - if ( - flags.WARN_ERROR - or ( - warn_error_options.include in warn_error_options.INCLUDE_ALL - and event.info.name not in warn_error_options.exclude - ) - or (event.info.name in warn_error_options.include) - ): + if flags.WARN_ERROR or warn_error_options.includes(event.info.name): # TODO: resolve this circular import when at top from dbt.exceptions import EventCompilationException diff --git a/core/dbt/flags.py b/core/dbt/flags.py index 63981c60647..1e3ee1e2220 100644 --- a/core/dbt/flags.py +++ b/core/dbt/flags.py @@ -55,6 +55,7 @@ "INDIRECT_SELECTION", "TARGET_PATH", "LOG_PATH", + "WARN_ERROR_OPTIONS", ] _NON_DBT_ENV_FLAGS = ["DO_NOT_TRACK"] diff --git a/core/dbt/helper_types.py b/core/dbt/helper_types.py index d5ace498064..a337971ef59 100644 --- a/core/dbt/helper_types.py +++ b/core/dbt/helper_types.py @@ -5,7 +5,6 @@ from dataclasses import dataclass, field from datetime import timedelta -from importlib import import_module from pathlib import Path from typing import Tuple, AbstractSet, Union from hologram import FieldEncoder, JsonDict @@ -18,6 +17,7 @@ StrEnum, ) from dbt.exceptions import ValidationException +import dbt.events.types as dbt_event_types class Port(int, SerializableType): @@ -114,11 +114,15 @@ def __post_init__(self): if isinstance(self.exclude, list): self._validate_items(self.exclude) + def includes(self, item_name: str): + return ( + item_name in self.include or self.include in self.INCLUDE_ALL + ) and item_name not in self.exclude + def _validate_items(self, items: List[str]): pass -# TODO: find a better spot for this class WarnErrorOptions(IncludeExclude): # TODO: this method can be removed once the click CLI is in use @classmethod @@ -134,9 +138,8 @@ def from_yaml_string(cls, warn_error_options_str: str): ) def _validate_items(self, items: List[str]): - mod = import_module("dbt.events.types") valid_exception_names = set( - [name for name, cls in mod.__dict__.items() if isinstance(cls, type)] + [name for name, cls in dbt_event_types.__dict__.items() if isinstance(cls, type)] ) for item in items: if item not in valid_exception_names: diff --git a/test/unit/test_flags.py b/test/unit/test_flags.py index 4be866338a2..a0d5499a183 100644 --- a/test/unit/test_flags.py +++ b/test/unit/test_flags.py @@ -1,8 +1,8 @@ import os -from unittest import mock, TestCase +from unittest import TestCase from argparse import Namespace +import pytest -from .utils import normalize from dbt import flags from dbt.contracts.project import UserConfig from dbt.graph.selector_spec import IndirectSelection @@ -63,6 +63,21 @@ def test__flags(self): flags.WARN_ERROR = False self.user_config.warn_error = None + # warn_error_options + self.user_config.warn_error_options = '{"include": "all"}' + flags.set_from_args(self.args, self.user_config) + self.assertEqual(flags.WARN_ERROR_OPTIONS, '{"include": "all"}') + os.environ['DBT_WARN_ERROR_OPTIONS'] = '{"include": []}' + flags.set_from_args(self.args, self.user_config) + self.assertEqual(flags.WARN_ERROR_OPTIONS, '{"include": []}') + setattr(self.args, 'warn_error_options', '{"include": "all"}') + flags.set_from_args(self.args, self.user_config) + self.assertEqual(flags.WARN_ERROR_OPTIONS, '{"include": "all"}') + # cleanup + os.environ.pop('DBT_WARN_ERROR_OPTIONS') + delattr(self.args, 'warn_error_options') + self.user_config.warn_error_options = None + # write_json self.user_config.write_json = True flags.set_from_args(self.args, self.user_config) @@ -261,3 +276,59 @@ def test__flags(self): # cleanup os.environ.pop('DBT_LOG_PATH') delattr(self.args, 'log_path') + + def test__flags_are_mutually_exclusive(self): + # options from user config + self.user_config.warn_error = False + self.user_config.warn_error_options = '{"include":"all}' + with pytest.raises(ValueError): + flags.set_from_args(self.args, self.user_config) + #cleanup + self.user_config.warn_error = None + self.user_config.warn_error_options = None + + # options from args + setattr(self.args, 'warn_error', False) + setattr(self.args, 'warn_error_options', '{"include":"all}') + with pytest.raises(ValueError): + flags.set_from_args(self.args, self.user_config) + # cleanup + delattr(self.args, 'warn_error') + delattr(self.args, 'warn_error_options') + + # options from environment + os.environ['DBT_WARN_ERROR'] = 'false' + os.environ['DBT_WARN_ERROR_OPTIONS'] = '{"include": []}' + with pytest.raises(ValueError): + flags.set_from_args(self.args, self.user_config) + #cleanup + os.environ.pop('DBT_WARN_ERROR') + os.environ.pop('DBT_WARN_ERROR_OPTIONS') + + # options from user config + args + self.user_config.warn_error = False + setattr(self.args, 'warn_error_options', '{"include":"all}') + with pytest.raises(ValueError): + flags.set_from_args(self.args, self.user_config) + # cleanup + self.user_config.warn_error = None + delattr(self.args, 'warn_error_options') + + # options from user config + environ + self.user_config.warn_error = False + os.environ['DBT_WARN_ERROR_OPTIONS'] = '{"include": []}' + with pytest.raises(ValueError): + flags.set_from_args(self.args, self.user_config) + # cleanup + self.user_config.warn_error = None + os.environ.pop('DBT_WARN_ERROR_OPTIONS') + + # options from args + environ + setattr(self.args, 'warn_error', False) + os.environ['DBT_WARN_ERROR_OPTIONS'] = '{"include": []}' + with pytest.raises(ValueError): + flags.set_from_args(self.args, self.user_config) + # cleanup + delattr(self.args, 'warn_error') + os.environ.pop('DBT_WARN_ERROR_OPTIONS') + diff --git a/tests/unit/test_functions.py b/tests/unit/test_functions.py new file mode 100644 index 00000000000..ac193f167b5 --- /dev/null +++ b/tests/unit/test_functions.py @@ -0,0 +1,49 @@ +from argparse import Namespace +import pytest + +import dbt.flags as flags +from dbt.events.functions import warn_or_error +from dbt.events.types import NoNodesForSelectionCriteria +from dbt.exceptions import EventCompilationException + + +@pytest.mark.parametrize( + "warn_error_options,expect_compilation_exception", + [ + ('{"include": "all"}', True), + ('{"include": [NoNodesForSelectionCriteria]}', True), + ('{"include": []}', False), + ('{}', False), + ('{"include": [MainTrackingUserState]}', False), + ('{"include": "all", "exclude": [NoNodesForSelectionCriteria]}', False), + ], +) +def test_warn_or_error_warn_error_options(warn_error_options, expect_compilation_exception): + args = Namespace( + warn_error_options=warn_error_options + ) + flags.set_from_args(args, {}) + if expect_compilation_exception: + with pytest.raises(EventCompilationException): + warn_or_error(NoNodesForSelectionCriteria()) + else: + warn_or_error(NoNodesForSelectionCriteria()) + + +@pytest.mark.parametrize( + "warn_error,expect_compilation_exception", + [ + (True, True), + (False, False), + ], +) +def test_warn_or_error_warn_error(warn_error, expect_compilation_exception): + args = Namespace( + warn_error=warn_error + ) + flags.set_from_args(args, {}) + if expect_compilation_exception: + with pytest.raises(EventCompilationException): + warn_or_error(NoNodesForSelectionCriteria()) + else: + warn_or_error(NoNodesForSelectionCriteria()) diff --git a/tests/unit/test_helper_types.py b/tests/unit/test_helper_types.py new file mode 100644 index 00000000000..3a6069b3686 --- /dev/null +++ b/tests/unit/test_helper_types.py @@ -0,0 +1,46 @@ + +import pytest + +from dbt.helper_types import IncludeExclude, WarnErrorOptions +from dbt.exceptions import ValidationException + + +class TestIncludeExclude: + def test_init_invalid(self): + with pytest.raises(ValidationException): + IncludeExclude(include="invalid") + + with pytest.raises(ValidationException): + IncludeExclude(include=["ItemA"], exclude=["ItemB"]) + + @pytest.mark.parametrize( + "include,exclude,expected_includes", + [ + ("all", [], True), + ("*", [], True), + ("*", ["ItemA"], False), + (["ItemA"], [], True), + (["ItemA", "ItemB"], [], True), + ] + ) + def test_includes(self, include, exclude, expected_includes): + include_exclude = IncludeExclude(include=include, exclude=exclude) + + assert include_exclude.includes("ItemA") == expected_includes + + +class TestWarnErrorOptions: + def test_init(self): + with pytest.raises(ValidationException): + WarnErrorOptions(include=["InvalidError"]) + + with pytest.raises(ValidationException): + WarnErrorOptions(include="*", exclude=["InvalidError"]) + + warn_error_options = WarnErrorOptions(include=["NoNodesForSelectionCriteria"]) + assert warn_error_options.include == ["NoNodesForSelectionCriteria"] + assert warn_error_options.exclude == [] + + warn_error_options = WarnErrorOptions(include="*", exclude=["NoNodesForSelectionCriteria"]) + assert warn_error_options.include == "*" + assert warn_error_options.exclude == ["NoNodesForSelectionCriteria"]