From 3b2c8da6b3117ca186e27a7f94fa44b17f6a82ed Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 18:10:54 +0200 Subject: Adding upstream version 2.1.2. Signed-off-by: Daniel Baumann --- tests/conftest.py | 37 +++++++ tests/test_escape.py | 29 ++++++ tests/test_exception_custom_html.py | 18 ++++ tests/test_leak.py | 28 ++++++ tests/test_markupsafe.py | 196 ++++++++++++++++++++++++++++++++++++ 5 files changed, 308 insertions(+) create mode 100644 tests/conftest.py create mode 100644 tests/test_escape.py create mode 100644 tests/test_exception_custom_html.py create mode 100644 tests/test_leak.py create mode 100644 tests/test_markupsafe.py (limited to 'tests') diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..d040ea8 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,37 @@ +import pytest + +from markupsafe import _native + +try: + from markupsafe import _speedups +except ImportError: + _speedups = None # type: ignore + + +@pytest.fixture( + scope="session", + params=( + _native, + pytest.param( + _speedups, + marks=pytest.mark.skipif(_speedups is None, reason="speedups unavailable"), + ), + ), +) +def _mod(request): + return request.param + + +@pytest.fixture(scope="session") +def escape(_mod): + return _mod.escape + + +@pytest.fixture(scope="session") +def escape_silent(_mod): + return _mod.escape_silent + + +@pytest.fixture(scope="session") +def soft_str(_mod): + return _mod.soft_str diff --git a/tests/test_escape.py b/tests/test_escape.py new file mode 100644 index 0000000..bf53fac --- /dev/null +++ b/tests/test_escape.py @@ -0,0 +1,29 @@ +import pytest + +from markupsafe import Markup + + +@pytest.mark.parametrize( + ("value", "expect"), + ( + # empty + ("", ""), + # ascii + ("abcd&><'\"efgh", "abcd&><'"efgh"), + ("&><'\"efgh", "&><'"efgh"), + ("abcd&><'\"", "abcd&><'""), + # 2 byte + ("こんにちは&><'\"こんばんは", "こんにちは&><'"こんばんは"), + ("&><'\"こんばんは", "&><'"こんばんは"), + ("こんにちは&><'\"", "こんにちは&><'""), + # 4 byte + ( + "\U0001F363\U0001F362&><'\"\U0001F37A xyz", + "\U0001F363\U0001F362&><'"\U0001F37A xyz", + ), + ("&><'\"\U0001F37A xyz", "&><'"\U0001F37A xyz"), + ("\U0001F363\U0001F362&><'\"", "\U0001F363\U0001F362&><'""), + ), +) +def test_escape(escape, value, expect): + assert escape(value) == Markup(expect) diff --git a/tests/test_exception_custom_html.py b/tests/test_exception_custom_html.py new file mode 100644 index 0000000..ec2f10b --- /dev/null +++ b/tests/test_exception_custom_html.py @@ -0,0 +1,18 @@ +import pytest + + +class CustomHtmlThatRaises: + def __html__(self): + raise ValueError(123) + + +def test_exception_custom_html(escape): + """Checks whether exceptions in custom __html__ implementations are + propagated correctly. + + There was a bug in the native implementation at some point: + https://github.com/pallets/markupsafe/issues/108 + """ + obj = CustomHtmlThatRaises() + with pytest.raises(ValueError): + escape(obj) diff --git a/tests/test_leak.py b/tests/test_leak.py new file mode 100644 index 0000000..55b10b9 --- /dev/null +++ b/tests/test_leak.py @@ -0,0 +1,28 @@ +import gc +import platform + +import pytest + +from markupsafe import escape + + +@pytest.mark.skipif( + escape.__module__ == "markupsafe._native", + reason="only test memory leak with speedups", +) +def test_markup_leaks(): + counts = set() + + for _i in range(20): + for _j in range(1000): + escape("foo") + escape("") + escape("foo") + escape("") + + if platform.python_implementation() == "PyPy": + gc.collect() + + counts.add(len(gc.get_objects())) + + assert len(counts) == 1 diff --git a/tests/test_markupsafe.py b/tests/test_markupsafe.py new file mode 100644 index 0000000..a62ebf9 --- /dev/null +++ b/tests/test_markupsafe.py @@ -0,0 +1,196 @@ +import pytest + +from markupsafe import Markup + + +def test_adding(escape): + unsafe = '' + safe = Markup("username") + assert unsafe + safe == str(escape(unsafe)) + str(safe) + + +@pytest.mark.parametrize( + ("template", "data", "expect"), + ( + ("%s", "", "<bad user>"), + ( + "%(username)s", + {"username": ""}, + "<bad user>", + ), + ("%i", 3.14, "3"), + ("%.2f", 3.14, "3.14"), + ), +) +def test_string_interpolation(template, data, expect): + assert Markup(template) % data == expect + + +def test_type_behavior(): + assert type(Markup("foo") + "bar") is Markup + x = Markup("foo") + assert x.__html__() is x + + +def test_html_interop(): + class Foo: + def __html__(self): + return "awesome" + + def __str__(self): + return "awesome" + + assert Markup(Foo()) == "awesome" + result = Markup("%s") % Foo() + assert result == "awesome" + + +@pytest.mark.parametrize("args", ["foo", 42, ("foo", 42)]) +def test_missing_interpol(args): + with pytest.raises(TypeError): + Markup("") % args + + +def test_tuple_interpol(): + result = Markup("%s:%s") % ("", "") + expect = Markup("<foo>:<bar>") + assert result == expect + + +def test_dict_interpol(): + result = Markup("%(foo)s") % {"foo": ""} + expect = Markup("<foo>") + assert result == expect + + result = Markup("%(foo)s:%(bar)s") % {"foo": "", "bar": ""} + expect = Markup("<foo>:<bar>") + assert result == expect + + +def test_escaping(escape): + assert escape("\"<>&'") == ""<>&'" + assert ( + Markup( + "" + "Foo & Bar" + "" + "" + "" + "" + ).striptags() + == "Foo & Bar" + ) + + +def test_unescape(): + assert Markup("<test>").unescape() == "" + + result = Markup("jack & tavi are cooler than mike & russ").unescape() + expect = "jack & tavi are cooler than mike & russ" + assert result == expect + + original = "&foo;" + once = Markup(original).unescape() + twice = Markup(once).unescape() + expect = "&foo;" + assert once == expect + assert twice == expect + + +def test_format(): + result = Markup("{awesome}").format(awesome="") + assert result == "<awesome>" + + result = Markup("{0[1][bar]}").format([0, {"bar": ""}]) + assert result == "<bar/>" + + result = Markup("{0[1][bar]}").format([0, {"bar": Markup("")}]) + assert result == "" + + +def test_formatting_empty(): + formatted = Markup("{}").format(0) + assert formatted == Markup("0") + + +def test_custom_formatting(): + class HasHTMLOnly: + def __html__(self): + return Markup("") + + class HasHTMLAndFormat: + def __html__(self): + return Markup("") + + def __html_format__(self, spec): + return Markup("") + + assert Markup("{0}").format(HasHTMLOnly()) == Markup("") + assert Markup("{0}").format(HasHTMLAndFormat()) == Markup("") + + +def test_complex_custom_formatting(): + class User: + def __init__(self, id, username): + self.id = id + self.username = username + + def __html_format__(self, format_spec): + if format_spec == "link": + return Markup('{1}').format( + self.id, self.__html__() + ) + elif format_spec: + raise ValueError("Invalid format spec") + + return self.__html__() + + def __html__(self): + return Markup("{0}").format(self.username) + + user = User(1, "foo") + result = Markup("

User: {0:link}").format(user) + expect = Markup('

User: foo') + assert result == expect + + +def test_formatting_with_objects(): + class Stringable: + def __str__(self): + return "строка" + + assert Markup("{s}").format(s=Stringable()) == Markup("строка") + + +def test_escape_silent(escape, escape_silent): + assert escape_silent(None) == Markup() + assert escape(None) == Markup(None) + assert escape_silent("") == Markup("<foo>") + + +def test_splitting(): + expect = [Markup("a"), Markup("b")] + assert Markup("a b").split() == expect + assert Markup("a b").rsplit() == expect + assert Markup("a\nb").splitlines() == expect + + +def test_mul(): + assert Markup("a") * 3 == Markup("aaa") + + +def test_escape_return_type(escape): + assert isinstance(escape("a"), Markup) + assert isinstance(escape(Markup("a")), Markup) + + class Foo: + def __html__(self): + return "Foo" + + assert isinstance(escape(Foo()), Markup) + + +def test_soft_str(soft_str): + assert type(soft_str("")) is str + assert type(soft_str(Markup())) is Markup + assert type(soft_str(15)) is str -- cgit v1.2.3