363 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			363 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# -*- coding: utf-8; -*-
 | 
						|
 | 
						|
import sys
 | 
						|
from unittest import TestCase
 | 
						|
from unittest.mock import patch, MagicMock
 | 
						|
 | 
						|
import pytest
 | 
						|
 | 
						|
from wuttjamaican import util as mod
 | 
						|
from wuttjamaican.progress import ProgressBase
 | 
						|
 | 
						|
 | 
						|
class A:
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class B(A):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class C(B):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class TestGetClassHierarchy(TestCase):
 | 
						|
 | 
						|
    def test_basic(self):
 | 
						|
 | 
						|
        classes = mod.get_class_hierarchy(A)
 | 
						|
        self.assertEqual(classes, [A])
 | 
						|
 | 
						|
        classes = mod.get_class_hierarchy(B)
 | 
						|
        self.assertEqual(classes, [A, B])
 | 
						|
 | 
						|
        classes = mod.get_class_hierarchy(C)
 | 
						|
        self.assertEqual(classes, [A, B, C])
 | 
						|
 | 
						|
        classes = mod.get_class_hierarchy(C, topfirst=False)
 | 
						|
        self.assertEqual(classes, [C, B, A])
 | 
						|
 | 
						|
 | 
						|
class TestLoadEntryPoints(TestCase):
 | 
						|
 | 
						|
    def test_empty(self):
 | 
						|
        # empty set returned for unknown group
 | 
						|
        result = mod.load_entry_points("this_should_never_exist!!!!!!")
 | 
						|
        self.assertEqual(result, {})
 | 
						|
 | 
						|
    def test_basic(self):
 | 
						|
        # load some entry points which should "always" be present,
 | 
						|
        # even in a testing environment.  basic sanity check
 | 
						|
        result = mod.load_entry_points("console_scripts", ignore_errors=True)
 | 
						|
        self.assertTrue(len(result) >= 1)
 | 
						|
        self.assertIn("pip", result)
 | 
						|
 | 
						|
    def test_basic_pre_python_3_10(self):
 | 
						|
 | 
						|
        # the goal here is to get coverage for code which would only
 | 
						|
        # run on python 3,9 and older, but we only need that coverage
 | 
						|
        # if we are currently testing python 3.10+
 | 
						|
        if sys.version_info.major == 3 and sys.version_info.minor < 10:
 | 
						|
            pytest.skip("this test is not relevant before python 3.10")
 | 
						|
 | 
						|
        import importlib.metadata
 | 
						|
 | 
						|
        real_entry_points = importlib.metadata.entry_points()
 | 
						|
 | 
						|
        class FakeEntryPoints(dict):
 | 
						|
            def get(self, group, default):
 | 
						|
                return real_entry_points.select(group=group)
 | 
						|
 | 
						|
        importlib = MagicMock()
 | 
						|
        importlib.metadata.entry_points.return_value = FakeEntryPoints()
 | 
						|
 | 
						|
        with patch.dict("sys.modules", **{"importlib": importlib}):
 | 
						|
 | 
						|
            # load some entry points which should "always" be present,
 | 
						|
            # even in a testing environment.  basic sanity check
 | 
						|
            result = mod.load_entry_points("console_scripts", ignore_errors=True)
 | 
						|
            self.assertTrue(len(result) >= 1)
 | 
						|
            self.assertIn("pytest", result)
 | 
						|
 | 
						|
    def test_basic_pre_python_3_8(self):
 | 
						|
 | 
						|
        # the goal here is to get coverage for code which would only
 | 
						|
        # run on python 3.7 and older, but we only need that coverage
 | 
						|
        # if we are currently testing python 3.8+
 | 
						|
        if sys.version_info.major == 3 and sys.version_info.minor < 8:
 | 
						|
            pytest.skip("this test is not relevant before python 3.8")
 | 
						|
 | 
						|
        from importlib.metadata import entry_points
 | 
						|
 | 
						|
        real_entry_points = entry_points()
 | 
						|
 | 
						|
        class FakeEntryPoints(dict):
 | 
						|
            def get(self, group, default):
 | 
						|
                if hasattr(real_entry_points, "select"):
 | 
						|
                    return real_entry_points.select(group=group)
 | 
						|
                return real_entry_points.get(group, [])
 | 
						|
 | 
						|
        importlib_metadata = MagicMock()
 | 
						|
        importlib_metadata.entry_points.return_value = FakeEntryPoints()
 | 
						|
 | 
						|
        orig_import = __import__
 | 
						|
 | 
						|
        def mock_import(name, *args, **kwargs):
 | 
						|
            if name == "importlib.metadata":
 | 
						|
                raise ImportError
 | 
						|
            if name == "importlib_metadata":
 | 
						|
                return importlib_metadata
 | 
						|
            return orig_import(name, *args, **kwargs)
 | 
						|
 | 
						|
        with patch("builtins.__import__", side_effect=mock_import):
 | 
						|
 | 
						|
            # load some entry points which should "always" be present,
 | 
						|
            # even in a testing environment.  basic sanity check
 | 
						|
            result = mod.load_entry_points("console_scripts", ignore_errors=True)
 | 
						|
            self.assertTrue(len(result) >= 1)
 | 
						|
            self.assertIn("pytest", result)
 | 
						|
 | 
						|
    def test_error(self):
 | 
						|
 | 
						|
        # skip if < 3.8
 | 
						|
        if sys.version_info.major == 3 and sys.version_info.minor < 8:
 | 
						|
            pytest.skip("this requires python 3.8 for entry points via importlib")
 | 
						|
 | 
						|
        entry_point = MagicMock()
 | 
						|
        entry_point.load.side_effect = NotImplementedError
 | 
						|
 | 
						|
        entry_points = MagicMock()
 | 
						|
        entry_points.select.return_value = [entry_point]
 | 
						|
 | 
						|
        importlib = MagicMock()
 | 
						|
        importlib.metadata.entry_points.return_value = entry_points
 | 
						|
 | 
						|
        with patch.dict("sys.modules", **{"importlib": importlib}):
 | 
						|
 | 
						|
            # empty set returned if errors suppressed
 | 
						|
            result = mod.load_entry_points("wuttatest.thingers", ignore_errors=True)
 | 
						|
            self.assertEqual(result, {})
 | 
						|
            importlib.metadata.entry_points.assert_called_once_with()
 | 
						|
            entry_points.select.assert_called_once_with(group="wuttatest.thingers")
 | 
						|
            entry_point.load.assert_called_once_with()
 | 
						|
 | 
						|
            # error is raised, if not suppressed
 | 
						|
            importlib.metadata.entry_points.reset_mock()
 | 
						|
            entry_points.select.reset_mock()
 | 
						|
            entry_point.load.reset_mock()
 | 
						|
            self.assertRaises(
 | 
						|
                NotImplementedError, mod.load_entry_points, "wuttatest.thingers"
 | 
						|
            )
 | 
						|
            importlib.metadata.entry_points.assert_called_once_with()
 | 
						|
            entry_points.select.assert_called_once_with(group="wuttatest.thingers")
 | 
						|
            entry_point.load.assert_called_once_with()
 | 
						|
 | 
						|
 | 
						|
class TestLoadObject(TestCase):
 | 
						|
 | 
						|
    def test_missing_spec(self):
 | 
						|
        self.assertRaises(ValueError, mod.load_object, None)
 | 
						|
 | 
						|
    def test_basic(self):
 | 
						|
        result = mod.load_object("unittest:TestCase")
 | 
						|
        self.assertIs(result, TestCase)
 | 
						|
 | 
						|
 | 
						|
class TestMakeUUID(TestCase):
 | 
						|
 | 
						|
    def test_basic(self):
 | 
						|
        uuid = mod.make_uuid()
 | 
						|
        self.assertEqual(len(uuid), 32)
 | 
						|
 | 
						|
 | 
						|
class TestParseBool(TestCase):
 | 
						|
 | 
						|
    def test_null(self):
 | 
						|
        self.assertIsNone(mod.parse_bool(None))
 | 
						|
 | 
						|
    def test_bool(self):
 | 
						|
        self.assertTrue(mod.parse_bool(True))
 | 
						|
        self.assertFalse(mod.parse_bool(False))
 | 
						|
 | 
						|
    def test_string_true(self):
 | 
						|
        self.assertTrue(mod.parse_bool("true"))
 | 
						|
        self.assertTrue(mod.parse_bool("yes"))
 | 
						|
        self.assertTrue(mod.parse_bool("y"))
 | 
						|
        self.assertTrue(mod.parse_bool("on"))
 | 
						|
        self.assertTrue(mod.parse_bool("1"))
 | 
						|
 | 
						|
    def test_string_false(self):
 | 
						|
        self.assertFalse(mod.parse_bool("false"))
 | 
						|
        self.assertFalse(mod.parse_bool("no"))
 | 
						|
        self.assertFalse(mod.parse_bool("n"))
 | 
						|
        self.assertFalse(mod.parse_bool("off"))
 | 
						|
        self.assertFalse(mod.parse_bool("0"))
 | 
						|
        # nb. assume false for unrecognized input
 | 
						|
        self.assertFalse(mod.parse_bool("whatever-else"))
 | 
						|
 | 
						|
 | 
						|
class TestParseList(TestCase):
 | 
						|
 | 
						|
    def test_null(self):
 | 
						|
        value = mod.parse_list(None)
 | 
						|
        self.assertIsInstance(value, list)
 | 
						|
        self.assertEqual(len(value), 0)
 | 
						|
 | 
						|
    def test_list_instance(self):
 | 
						|
        mylist = []
 | 
						|
        value = mod.parse_list(mylist)
 | 
						|
        self.assertIs(value, mylist)
 | 
						|
 | 
						|
    def test_single_value(self):
 | 
						|
        value = mod.parse_list("foo")
 | 
						|
        self.assertEqual(len(value), 1)
 | 
						|
        self.assertEqual(value[0], "foo")
 | 
						|
 | 
						|
    def test_single_value_padded_by_spaces(self):
 | 
						|
        value = mod.parse_list("   foo   ")
 | 
						|
        self.assertEqual(len(value), 1)
 | 
						|
        self.assertEqual(value[0], "foo")
 | 
						|
 | 
						|
    def test_slash_is_not_a_separator(self):
 | 
						|
        value = mod.parse_list("/dev/null")
 | 
						|
        self.assertEqual(len(value), 1)
 | 
						|
        self.assertEqual(value[0], "/dev/null")
 | 
						|
 | 
						|
    def test_multiple_values_separated_by_whitespace(self):
 | 
						|
        value = mod.parse_list("foo bar baz")
 | 
						|
        self.assertEqual(len(value), 3)
 | 
						|
        self.assertEqual(value[0], "foo")
 | 
						|
        self.assertEqual(value[1], "bar")
 | 
						|
        self.assertEqual(value[2], "baz")
 | 
						|
 | 
						|
    def test_multiple_values_separated_by_commas(self):
 | 
						|
        value = mod.parse_list("foo,bar,baz")
 | 
						|
        self.assertEqual(len(value), 3)
 | 
						|
        self.assertEqual(value[0], "foo")
 | 
						|
        self.assertEqual(value[1], "bar")
 | 
						|
        self.assertEqual(value[2], "baz")
 | 
						|
 | 
						|
    def test_multiple_values_separated_by_whitespace_and_commas(self):
 | 
						|
        value = mod.parse_list("  foo,   bar   baz")
 | 
						|
        self.assertEqual(len(value), 3)
 | 
						|
        self.assertEqual(value[0], "foo")
 | 
						|
        self.assertEqual(value[1], "bar")
 | 
						|
        self.assertEqual(value[2], "baz")
 | 
						|
 | 
						|
    def test_multiple_values_separated_by_whitespace_and_commas_with_some_quoting(self):
 | 
						|
        value = mod.parse_list(
 | 
						|
            """
 | 
						|
        foo
 | 
						|
        "C:\\some path\\with spaces\\and, a comma",
 | 
						|
        baz
 | 
						|
        """
 | 
						|
        )
 | 
						|
        self.assertEqual(len(value), 3)
 | 
						|
        self.assertEqual(value[0], "foo")
 | 
						|
        self.assertEqual(value[1], "C:\\some path\\with spaces\\and, a comma")
 | 
						|
        self.assertEqual(value[2], "baz")
 | 
						|
 | 
						|
    def test_multiple_values_separated_by_whitespace_and_commas_with_single_quotes(
 | 
						|
        self,
 | 
						|
    ):
 | 
						|
        value = mod.parse_list(
 | 
						|
            """
 | 
						|
        foo
 | 
						|
        'C:\\some path\\with spaces\\and, a comma',
 | 
						|
        baz
 | 
						|
        """
 | 
						|
        )
 | 
						|
        self.assertEqual(len(value), 3)
 | 
						|
        self.assertEqual(value[0], "foo")
 | 
						|
        self.assertEqual(value[1], "C:\\some path\\with spaces\\and, a comma")
 | 
						|
        self.assertEqual(value[2], "baz")
 | 
						|
 | 
						|
 | 
						|
class TestMakeTitle(TestCase):
 | 
						|
 | 
						|
    def test_basic(self):
 | 
						|
        text = mod.make_title("foo_bar")
 | 
						|
        self.assertEqual(text, "Foo Bar")
 | 
						|
 | 
						|
 | 
						|
class TestMakeFullName(TestCase):
 | 
						|
 | 
						|
    def test_basic(self):
 | 
						|
        name = mod.make_full_name("Fred", "", "Flintstone", "")
 | 
						|
        self.assertEqual(name, "Fred Flintstone")
 | 
						|
 | 
						|
 | 
						|
class TestProgressLoop(TestCase):
 | 
						|
 | 
						|
    def test_basic(self):
 | 
						|
 | 
						|
        def act(obj, i):
 | 
						|
            pass
 | 
						|
 | 
						|
        # with progress
 | 
						|
        mod.progress_loop(act, [1, 2, 3], ProgressBase, message="whatever")
 | 
						|
 | 
						|
        # without progress
 | 
						|
        mod.progress_loop(act, [1, 2, 3], None, message="whatever")
 | 
						|
 | 
						|
 | 
						|
class TestResourcePath(TestCase):
 | 
						|
 | 
						|
    def test_basic(self):
 | 
						|
 | 
						|
        # package spec is resolved to path
 | 
						|
        path = mod.resource_path("wuttjamaican:util.py")
 | 
						|
        self.assertTrue(path.endswith("wuttjamaican/util.py"))
 | 
						|
 | 
						|
        # absolute path returned as-is
 | 
						|
        self.assertEqual(
 | 
						|
            mod.resource_path("/tmp/doesnotexist.txt"), "/tmp/doesnotexist.txt"
 | 
						|
        )
 | 
						|
 | 
						|
    def test_basic_pre_python_3_9(self):
 | 
						|
 | 
						|
        # the goal here is to get coverage for code which would only
 | 
						|
        # run on python 3.8 and older, but we only need that coverage
 | 
						|
        # if we are currently testing python 3.9+
 | 
						|
        if sys.version_info.major == 3 and sys.version_info.minor < 9:
 | 
						|
            pytest.skip("this test is not relevant before python 3.9")
 | 
						|
 | 
						|
        from importlib.resources import files, as_file
 | 
						|
 | 
						|
        orig_import = __import__
 | 
						|
 | 
						|
        def mock_import(name, globals=None, locals=None, fromlist=(), level=0):
 | 
						|
            if name == "importlib.resources":
 | 
						|
                raise ImportError
 | 
						|
            if name == "importlib_resources":
 | 
						|
                return MagicMock(files=files, as_file=as_file)
 | 
						|
            return orig_import(name, globals, locals, fromlist, level)
 | 
						|
 | 
						|
        with patch("builtins.__import__", side_effect=mock_import):
 | 
						|
 | 
						|
            # package spec is resolved to path
 | 
						|
            path = mod.resource_path("wuttjamaican:util.py")
 | 
						|
            self.assertTrue(path.endswith("wuttjamaican/util.py"))
 | 
						|
 | 
						|
            # absolute path returned as-is
 | 
						|
            self.assertEqual(
 | 
						|
                mod.resource_path("/tmp/doesnotexist.txt"), "/tmp/doesnotexist.txt"
 | 
						|
            )
 | 
						|
 | 
						|
 | 
						|
class TestSimpleError(TestCase):
 | 
						|
 | 
						|
    def test_with_description(self):
 | 
						|
        try:
 | 
						|
            raise RuntimeError("just testin")
 | 
						|
        except Exception as error:
 | 
						|
            result = mod.simple_error(error)
 | 
						|
        self.assertEqual(result, "RuntimeError: just testin")
 | 
						|
 | 
						|
    def test_without_description(self):
 | 
						|
        try:
 | 
						|
            raise RuntimeError
 | 
						|
        except Exception as error:
 | 
						|
            result = mod.simple_error(error)
 | 
						|
        self.assertEqual(result, "RuntimeError")
 |