blob: c228fba7ef61dd035826498ec2c3c2552f3f7685 [file] [log] [blame]
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import importlib
import sys
from inspect import isabstract
from typing import Any
from unittest import TestCase, mock
from parameterized import parameterized
from airflow.models.baseoperator import BaseOperator
from tests.deprecated_classes import ALL, RENAMED_ALL
class TestMovingCoreToContrib(TestCase):
@staticmethod
def assert_warning(msg: str, warning: Any):
error = "Text '{}' not in warnings".format(msg)
assert any(msg in str(w) for w in warning.warnings), error
def assert_is_subclass(self, clazz, other):
self.assertTrue(
issubclass(clazz, other), "{} is not subclass of {}".format(clazz, other)
)
def assert_proper_import(self, old_resource, new_resource):
new_path, _, _ = new_resource.rpartition(".")
old_path, _, _ = old_resource.rpartition(".")
with self.assertWarns(DeprecationWarning) as warning_msg:
# Reload to see deprecation warning each time
importlib.reload(importlib.import_module(old_path))
self.assert_warning(new_path, warning_msg)
def skip_test_with_mssql_in_py38(self, path_a="", path_b=""):
py_38 = sys.version_info >= (3, 8)
if py_38:
if "mssql" in path_a or "mssql" in path_b:
raise self.skipTest("Mssql package not available when Python >= 3.8.")
@staticmethod
def get_class_from_path(path_to_class, parent=False):
"""
:param parent indicates if "path_to_class" arg is super class
"""
path, _, class_name = path_to_class.rpartition(".")
module = importlib.import_module(path)
class_ = getattr(module, class_name)
if isabstract(class_) and not parent:
class_name = f"Mock({class_.__name__})"
attributes = {
a: mock.MagicMock() for a in class_.__abstractmethods__
}
new_class = type(class_name, (class_,), attributes)
return new_class
return class_
@parameterized.expand(RENAMED_ALL)
def test_is_class_deprecated(self, new_module, old_module):
self.skip_test_with_mssql_in_py38(new_module, old_module)
deprecation_warning_msg = "This class is deprecated."
old_module_class = self.get_class_from_path(old_module)
with self.assertWarnsRegex(DeprecationWarning, deprecation_warning_msg) as wrn:
with mock.patch("{}.__init__".format(new_module)) as init_mock:
init_mock.return_value = None
klass = old_module_class()
if isinstance(klass, BaseOperator):
# In case of operators we are validating that proper stacklevel
# is used (=3 or =4 if @apply_defaults)
assert len(wrn.warnings) == 1
assert wrn.warnings[0].filename == __file__
init_mock.assert_called_once_with()
@parameterized.expand(ALL)
def test_is_subclass(self, parent_class_path, sub_class_path):
self.skip_test_with_mssql_in_py38(parent_class_path, sub_class_path)
with mock.patch("{}.__init__".format(parent_class_path)):
parent_class_path = self.get_class_from_path(parent_class_path, parent=True)
sub_class_path = self.get_class_from_path(sub_class_path)
self.assert_is_subclass(sub_class_path, parent_class_path)
@parameterized.expand(ALL)
def test_warning_on_import(self, new_path, old_path):
self.skip_test_with_mssql_in_py38(new_path, old_path)
self.assert_proper_import(old_path, new_path)
def test_no_redirect_to_deprecated_classes(self):
"""
When we have the following items:
new_A, old_B
old_B, old_C
This will tell us to use new_A instead of old_B.
"""
all_classes_by_old = {
old: new for new, old in ALL
}
for new, old in ALL:
# Using if statement allows us to create a developer-friendly message only when we need it.
# Otherwise, it wouldn't always be possible - KeyError
if new in all_classes_by_old:
raise AssertionError(
f'Deprecation "{old}" to "{new}" is incorrect. '
f'Please use \"{all_classes_by_old[new]}\" instead of "{old}".'
)