mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 03:04:38 +08:00
* Reuse unchanged string results in affix removal and partitioning Assisted-by: Codex:GPT-6 * Qualify the ASCII macro in the string test Assisted-by: Codex:GPT-6
1296 lines
42 KiB
Python
1296 lines
42 KiB
Python
import sys
|
||
|
||
from testutils import AssertRaises, assert_raises, skip_if_unsupported
|
||
|
||
assert "".__eq__(1) == NotImplemented
|
||
assert "a" == "a"
|
||
assert """a""" == "a"
|
||
assert len(""" " "" " "" """) == 11
|
||
assert '"' == '"'
|
||
assert '"' == """\""""
|
||
|
||
assert (
|
||
"\n"
|
||
== """
|
||
"""
|
||
)
|
||
|
||
assert len(""" " \" """) == 5
|
||
assert len("é") == 1
|
||
assert len("é") == 2
|
||
assert len("あ") == 1
|
||
|
||
assert type("") is str
|
||
assert type(b"") is bytes
|
||
|
||
assert str(1) == "1"
|
||
assert str(2.1) == "2.1"
|
||
assert str() == ""
|
||
assert str("abc") == "abc"
|
||
|
||
assert_raises(TypeError, lambda: str("abc", "utf-8"))
|
||
assert str(b"abc", "utf-8") == "abc"
|
||
assert str(b"abc", encoding="ascii") == "abc"
|
||
|
||
assert repr("a") == "'a'"
|
||
assert repr("can't") == '"can\'t"'
|
||
assert repr('"won\'t"') == "'\"won\\'t\"'"
|
||
assert repr("\n\t") == "'\\n\\t'"
|
||
|
||
assert str(["a", "b", "can't"]) == "['a', 'b', \"can't\"]"
|
||
|
||
assert "xy" * 3 == "xyxyxy"
|
||
assert "x" * 0 == ""
|
||
assert "x" * -1 == ""
|
||
|
||
assert 3 * "xy" == "xyxyxy"
|
||
assert 0 * "x" == ""
|
||
assert -1 * "x" == ""
|
||
|
||
assert_raises(OverflowError, lambda: "xy" * 234234234234234234234234234234)
|
||
|
||
a = "Hallo"
|
||
assert a.lower() == "hallo"
|
||
assert a.upper() == "HALLO"
|
||
assert a.startswith("H")
|
||
assert a.startswith(("H", 1))
|
||
assert a.startswith(("A", "H"))
|
||
assert not a.startswith("f")
|
||
assert not a.startswith(("A", "f"))
|
||
assert a.endswith("llo")
|
||
assert a.endswith(("lo", 1))
|
||
assert a.endswith(("A", "lo"))
|
||
assert not a.endswith("on")
|
||
assert not a.endswith(("A", "ll"))
|
||
assert a.zfill(8) == "000Hallo"
|
||
assert "á".zfill(4) == "000á"
|
||
assert "🙂".zfill(5) == "0000🙂"
|
||
assert "+あ".zfill(5) == "+000あ"
|
||
assert a.isalnum()
|
||
assert not a.isdigit()
|
||
assert not a.isdecimal()
|
||
assert not a.isnumeric()
|
||
assert a.istitle()
|
||
assert "\u1c89".istitle()
|
||
assert "DZ".title() == "Dz"
|
||
assert a.isalpha()
|
||
assert not "\u093f".isalpha()
|
||
|
||
# Combining characters differ slightly between Rust and Python
|
||
assert "\u006e".isalnum()
|
||
assert not "\u0303".isalnum()
|
||
assert not "\u006e\u0303".isalnum()
|
||
assert "\u00f1".isalnum()
|
||
assert not "\u0345".isalnum()
|
||
assert not "\u093f".isalnum()
|
||
for raw in range(0x0363, 0x036F):
|
||
assert not chr(raw).isalnum()
|
||
|
||
# isdigit is true for exponents
|
||
assert "⁰".isdigit()
|
||
assert "⁰".isnumeric()
|
||
assert not "½".isdigit()
|
||
assert "½".isnumeric()
|
||
assert not "Ⅻ".isdigit()
|
||
assert "Ⅻ".isnumeric()
|
||
|
||
# isnumeric is broader than Rust's
|
||
assert "\u3405".isnumeric()
|
||
|
||
s = "1 2 3"
|
||
assert s.split(" ", 1) == ["1", "2 3"]
|
||
assert s.rsplit(" ", 1) == ["1 2", "3"]
|
||
|
||
b = " hallo "
|
||
assert b.strip() == "hallo"
|
||
assert b.lstrip() == "hallo "
|
||
assert b.rstrip() == " hallo"
|
||
|
||
s = "^*RustPython*^"
|
||
assert s.strip("^*") == "RustPython"
|
||
assert s.lstrip("^*") == "RustPython*^"
|
||
assert s.rstrip("^*") == "^*RustPython"
|
||
assert " abc ".strip(" é") == "abc"
|
||
assert "éabcé".strip("é\ud800") == "abc"
|
||
|
||
|
||
def test_strip_unchanged():
|
||
class StrSubclass(str):
|
||
pass
|
||
|
||
for text in ("already clean", "déjà propre", "a\ud800b"):
|
||
for method in (str.strip, str.lstrip, str.rstrip):
|
||
for chars in (None, "#"):
|
||
assert method(text, chars) is text
|
||
result = method(StrSubclass(text), chars)
|
||
assert result == text
|
||
assert type(result) is str
|
||
|
||
|
||
test_strip_unchanged()
|
||
|
||
s = "RustPython"
|
||
assert s.ljust(8) == "RustPython"
|
||
assert s.rjust(8) == "RustPython"
|
||
assert s.ljust(12) == "RustPython "
|
||
assert s.rjust(12) == " RustPython"
|
||
assert s.ljust(12, "_") == "RustPython__"
|
||
assert s.rjust(12, "_") == "__RustPython"
|
||
# The fill character must be exactly one character long
|
||
assert_raises(TypeError, lambda: s.ljust(12, "__"))
|
||
assert_raises(TypeError, lambda: s.rjust(12, "__"))
|
||
|
||
c = "hallo"
|
||
assert c.capitalize() == "Hallo"
|
||
assert "ßello".capitalize() == "Ssello"
|
||
assert "İstanbul".capitalize() == "İstanbul"
|
||
assert "a\u0301bc".capitalize() == "Ábc"
|
||
assert "ΣΙΓΜΑ".capitalize() == "Σιγμα"
|
||
assert "😀hello".capitalize() == "😀hello"
|
||
assert "élan".capitalize() == "Élan"
|
||
assert c.center(11, "-") == "---hallo---"
|
||
assert ["koki".center(i, "|") for i in range(3, 10)] == [
|
||
"koki",
|
||
"koki",
|
||
"|koki",
|
||
"|koki|",
|
||
"||koki|",
|
||
"||koki||",
|
||
"|||koki||",
|
||
]
|
||
|
||
|
||
assert ["kok".center(i, "|") for i in range(2, 10)] == [
|
||
"kok",
|
||
"kok",
|
||
"kok|",
|
||
"|kok|",
|
||
"|kok||",
|
||
"||kok||",
|
||
"||kok|||",
|
||
"|||kok|||",
|
||
]
|
||
|
||
|
||
# requires CPython 3.7, and the CI currently runs with 3.6
|
||
# assert c.isascii()
|
||
assert c.index("a") == 1
|
||
assert c.rindex("l") == 3
|
||
assert c.find("h") == 0
|
||
assert c.rfind("x") == -1
|
||
assert c.islower()
|
||
assert c.title() == "Hallo"
|
||
assert c.count("l") == 2
|
||
|
||
assert "aaa".count("a") == 3
|
||
assert "aaa".count("a", 1) == 2
|
||
assert "aaa".count("a", 1, 2) == 1
|
||
assert "aaa".count("a", 2, 2) == 0
|
||
assert "aaa".count("a", 2, 1) == 0
|
||
|
||
# An empty needle is counted in characters, not in encoded positions.
|
||
assert "".count("") == 1
|
||
assert "abc".count("") == 4
|
||
assert "가나다".count("") == 4
|
||
assert "가나다".count("", 1) == 3
|
||
assert "가나다".count("", 1, 2) == 2
|
||
assert "가나다".count("", 4, 4) == 0
|
||
assert "a\U0001f600b".count("") == 4
|
||
|
||
assert "___a__".find("a") == 3
|
||
assert "___a__".find("a", -10) == 3
|
||
assert "___a__".find("a", -3) == 3
|
||
assert "___a__".find("a", -2) == -1
|
||
assert "___a__".find("a", -1) == -1
|
||
assert "___a__".find("a", 0) == 3
|
||
assert "___a__".find("a", 3) == 3
|
||
assert "___a__".find("a", 4) == -1
|
||
assert "___a__".find("a", 10) == -1
|
||
assert "___a__".rfind("a", 3) == 3
|
||
assert "___a__".index("a", 3) == 3
|
||
|
||
assert "___a__".find("a", 0, -10) == -1
|
||
assert "___a__".find("a", 0, -3) == -1
|
||
assert "___a__".find("a", 0, -2) == 3
|
||
assert "___a__".find("a", 0, -1) == 3
|
||
assert "___a__".find("a", 0, 0) == -1
|
||
assert "___a__".find("a", 0, 3) == -1
|
||
assert "___a__".find("a", 0, 4) == 3
|
||
assert "___a__".find("a", 0, 10) == 3
|
||
|
||
assert "___a__".find("a", 3, 3) == -1
|
||
assert "___a__".find("a", 3, 4) == 3
|
||
assert "___a__".find("a", 4, 3) == -1
|
||
|
||
assert "abcd".startswith("b", 1)
|
||
assert "abcd".startswith(("b", "z"), 1)
|
||
assert not "abcd".startswith("b", -4)
|
||
assert "abcd".startswith("b", -3)
|
||
|
||
assert not "abcd".startswith("b", 3, 3)
|
||
assert "abcd".startswith("", 3, 3)
|
||
assert not "abcd".startswith("", 4, 3)
|
||
|
||
assert " ".isspace()
|
||
assert "hello\nhallo\nHallo".splitlines() == ["hello", "hallo", "Hallo"]
|
||
assert "hello\nhallo\nHallo\n".splitlines() == ["hello", "hallo", "Hallo"]
|
||
assert "hello\nhallo\nHallo".splitlines(keepends=True) == [
|
||
"hello\n",
|
||
"hallo\n",
|
||
"Hallo",
|
||
]
|
||
assert "hello\nhallo\nHallo\n".splitlines(keepends=True) == [
|
||
"hello\n",
|
||
"hallo\n",
|
||
"Hallo\n",
|
||
]
|
||
assert (
|
||
"hello\vhallo\x0cHallo\x1cHELLO\x1dhoho\x1ehaha\x85another\u2028yetanother\u2029last\r\n.".splitlines()
|
||
== [
|
||
"hello",
|
||
"hallo",
|
||
"Hallo",
|
||
"HELLO",
|
||
"hoho",
|
||
"haha",
|
||
"another",
|
||
"yetanother",
|
||
"last",
|
||
".",
|
||
]
|
||
)
|
||
assert (
|
||
"hello\vhallo\x0cHallo\x1cHELLO\x1dhoho\x1ehaha\x85another\u2028yetanother\u2029last\r\n.".splitlines(
|
||
keepends=True
|
||
)
|
||
== [
|
||
"hello\x0b",
|
||
"hallo\x0c",
|
||
"Hallo\x1c",
|
||
"HELLO\x1d",
|
||
"hoho\x1e",
|
||
"haha\x85",
|
||
"another\u2028",
|
||
"yetanother\u2029",
|
||
"last\r\n",
|
||
".",
|
||
]
|
||
)
|
||
assert "abc\t12345\txyz".expandtabs() == "abc 12345 xyz"
|
||
assert "-".join(["1", "2", "3"]) == "1-2-3"
|
||
assert "-".join(("1", "2")) == "1-2"
|
||
assert "-".join([]) == ""
|
||
assert "-".join(x for x in "ab") == "a-b"
|
||
with assert_raises(TypeError) as cm:
|
||
"-".join(["a", 1])
|
||
assert str(cm.exception) == "sequence item 1: expected str instance, int found"
|
||
|
||
|
||
class JoinStr(str):
|
||
pass
|
||
|
||
|
||
assert type("-".join([JoinStr("a")])) is str
|
||
assert type("-".join((JoinStr("a"), "b"))) is str
|
||
single = "single"
|
||
assert "-".join([single]) is single
|
||
|
||
|
||
def join_broken_iterable():
|
||
yield 42
|
||
yield "a"
|
||
raise RuntimeError("producer failed")
|
||
|
||
|
||
with assert_raises(RuntimeError):
|
||
"-".join(join_broken_iterable())
|
||
|
||
assert "HALLO".isupper()
|
||
assert not "123".isupper()
|
||
assert not "123".islower()
|
||
assert not "\U0001f431".isupper()
|
||
assert not "\U0001f431".islower()
|
||
assert "\U0001f431 CAT".isupper()
|
||
assert "\U0001f431 cat".islower()
|
||
if sys.version_info >= (3, 15):
|
||
assert not "\u0295".islower()
|
||
assert not "\u0295".isupper()
|
||
assert not "\u0295".istitle()
|
||
assert "\u1c89".isupper()
|
||
assert "hello, my name is".partition("my ") == ("hello, ", "my ", "name is")
|
||
assert "hello".partition("is") == ("hello", "", "")
|
||
assert "hello, my name is".rpartition("is") == ("hello, my name ", "is", "")
|
||
assert "hello".rpartition("is") == ("", "", "hello")
|
||
assert not "".isdecimal()
|
||
assert "123".isdecimal()
|
||
assert not "\u00b2".isdecimal()
|
||
|
||
assert not "".isidentifier()
|
||
assert "python".isidentifier()
|
||
assert "_".isidentifier()
|
||
assert "유니코드".isidentifier()
|
||
assert not "😂".isidentifier()
|
||
assert not "123".isidentifier()
|
||
|
||
assert "Σίσυφος".swapcase() == "σΊΣΥΦΟΣ"
|
||
assert "\u0295".swapcase() == "\u0295"
|
||
assert "\u1c89".swapcase() == "\u1c8a"
|
||
|
||
# String Formatting
|
||
assert "{} {}".format(1, 2) == "1 2"
|
||
assert "{0} {1}".format(2, 3) == "2 3"
|
||
assert "--{:s>4}--".format(1) == "--sss1--"
|
||
assert "{keyword} {0}".format(1, keyword=2) == "2 1"
|
||
assert (
|
||
"repr() shows quotes: {!r}; str() doesn't: {!s}".format("test1", "test2")
|
||
== "repr() shows quotes: 'test1'; str() doesn't: test2"
|
||
), "Output: {!r}, {!s}".format("test1", "test2")
|
||
|
||
|
||
class Foo:
|
||
def __str__(self):
|
||
return "str(Foo)"
|
||
|
||
def __repr__(self):
|
||
return "repr(Foo)"
|
||
|
||
|
||
f = Foo()
|
||
assert "{} {!s} {!r} {!a}".format(f, f, f, f) == "str(Foo) str(Foo) repr(Foo) repr(Foo)"
|
||
assert (
|
||
"{foo} {foo!s} {foo!r} {foo!a}".format(foo=f)
|
||
== "str(Foo) str(Foo) repr(Foo) repr(Foo)"
|
||
)
|
||
# assert '{} {!r} {:10} {!r:10} {foo!r:10} {foo!r} {foo}'.format('txt1', 'txt2', 'txt3', 'txt4', 'txt5', foo='bar')
|
||
|
||
|
||
# Printf-style String formatting
|
||
assert "%d %d" % (1, 2) == "1 2"
|
||
assert "%*c " % (3, "❤") == " ❤ "
|
||
for precision in (-1, -3, -(2**31)):
|
||
assert "%.*s" % (precision, "🐍hello") == ""
|
||
assert "%*.*d" % (-6, -3, 12) == "12 "
|
||
assert "%.*f" % (-3, 1.25) == "1"
|
||
assert "%.*g" % (-3, 12.5) == "1e+01"
|
||
assert_raises(OverflowError, "%.*s".__mod__, (-(2**31) - 1, "abc"))
|
||
assert_raises(TypeError, "%.*s".__mod__, (1.0, "abc"))
|
||
|
||
|
||
class PercentIndex:
|
||
def __index__(self):
|
||
return 7
|
||
|
||
|
||
class PercentInt(PercentIndex):
|
||
def __init__(self, value):
|
||
self.value = value
|
||
|
||
def __int__(self):
|
||
if isinstance(self.value, Exception):
|
||
raise self.value
|
||
return self.value
|
||
|
||
|
||
class PercentFloat(float):
|
||
__int__ = PercentInt.__int__
|
||
__index__ = PercentIndex.__index__
|
||
|
||
|
||
class PercentFloatIndex(float):
|
||
__index__ = PercentIndex.__index__
|
||
|
||
|
||
class PercentIntSubclass(int):
|
||
def __int__(self):
|
||
raise AssertionError("int subclasses must use their stored value")
|
||
|
||
|
||
for template in ("%d", "%i", "%u", b"%d", b"%i", b"%u"):
|
||
assert template % PercentInt(3) == template % 3
|
||
assert template % PercentIndex() == template % 7
|
||
for value in (None, TypeError("conversion failed")):
|
||
with assert_raises(TypeError) as cm:
|
||
template % PercentInt(value)
|
||
conversion = chr(template[-1]) if isinstance(template, bytes) else template[-1]
|
||
assert str(cm.exception) == (
|
||
f"format argument: %{conversion} requires a real number, not PercentInt"
|
||
)
|
||
assert_raises(
|
||
RuntimeError, template.__mod__, PercentInt(RuntimeError("conversion failed"))
|
||
)
|
||
number = PercentFloat(1.25)
|
||
number.value = 3
|
||
assert template % number == template % 3
|
||
number.value = None
|
||
assert_raises(TypeError, template.__mod__, number)
|
||
number.value = RuntimeError("conversion failed")
|
||
assert_raises(RuntimeError, template.__mod__, number)
|
||
assert template % PercentFloatIndex(1.25) == template % 1
|
||
assert template % PercentIntSubclass(1) == template % 1
|
||
assert "%x" % PercentInt(3) == "7"
|
||
assert b"%o" % PercentInt(3) == b"7"
|
||
|
||
|
||
def test_percent_character_subclasses():
|
||
for base, value, templates in (
|
||
(str, "ab", ("%c",)),
|
||
(bytes, b"ab", (b"%c", bytearray(b"%c"))),
|
||
(bytearray, b"ab", (b"%c", bytearray(b"%c"))),
|
||
):
|
||
|
||
class Indexable(base):
|
||
def __index__(self):
|
||
raise AssertionError("%c must check the character length first")
|
||
|
||
for template in templates:
|
||
assert template % Indexable(value[:1]) == template % value[:1]
|
||
for text in (value[:0], value):
|
||
with assert_raises(TypeError) as expected:
|
||
template % base(text)
|
||
with assert_raises(TypeError) as actual:
|
||
template % Indexable(text)
|
||
assert str(actual.exception) == str(expected.exception)
|
||
assert template % PercentIndex() == template % 7
|
||
|
||
|
||
test_percent_character_subclasses()
|
||
|
||
assert (
|
||
"%(first)s %(second)s" % {"second": "World!", "first": "Hello,"} == "Hello, World!"
|
||
)
|
||
assert "%(key())s" % {"key()": "aaa"}
|
||
assert "%s %a %r" % (f, f, f) == "str(Foo) repr(Foo) repr(Foo)"
|
||
assert (
|
||
"repr() shows quotes: %r; str() doesn't: %s" % ("test1", "test2")
|
||
== "repr() shows quotes: 'test1'; str() doesn't: test2"
|
||
)
|
||
assert "%f" % (1.2345) == "1.234500"
|
||
assert "%+f" % (1.2345) == "+1.234500"
|
||
assert "% f" % (1.2345) == " 1.234500"
|
||
assert "%f" % (-1.2345) == "-1.234500"
|
||
assert "%f" % (1.23456789012) == "1.234568"
|
||
assert "%f" % (123) == "123.000000"
|
||
assert "%f" % (-123) == "-123.000000"
|
||
assert "%e" % 1 == "1.000000e+00"
|
||
assert "%e" % 0 == "0.000000e+00"
|
||
assert "%e" % 0.1 == "1.000000e-01"
|
||
assert "%e" % 10 == "1.000000e+01"
|
||
assert "%.10e" % 1.2345678901234567890 == "1.2345678901e+00"
|
||
assert "%e" % float("nan") == "nan"
|
||
assert "%e" % float("-nan") == "nan"
|
||
assert "%E" % float("nan") == "NAN"
|
||
assert "%e" % float("inf") == "inf"
|
||
assert "%e" % float("-inf") == "-inf"
|
||
assert "%E" % float("inf") == "INF"
|
||
assert "%g" % 123456.78901234567890 == "123457"
|
||
assert "%.0g" % 123456.78901234567890 == "1e+05"
|
||
assert "%.1g" % 123456.78901234567890 == "1e+05"
|
||
assert "%.2g" % 123456.78901234567890 == "1.2e+05"
|
||
assert "%g" % 1234567.8901234567890 == "1.23457e+06"
|
||
assert "%.0g" % 1234567.8901234567890 == "1e+06"
|
||
assert "%.1g" % 1234567.8901234567890 == "1e+06"
|
||
assert "%.2g" % 1234567.8901234567890 == "1.2e+06"
|
||
assert "%.3g" % 1234567.8901234567890 == "1.23e+06"
|
||
assert "%.5g" % 1234567.8901234567890 == "1.2346e+06"
|
||
assert "%.6g" % 1234567.8901234567890 == "1.23457e+06"
|
||
assert "%.7g" % 1234567.8901234567890 == "1234568"
|
||
assert "%.8g" % 1234567.8901234567890 == "1234567.9"
|
||
assert "%G" % 123456.78901234567890 == "123457"
|
||
assert "%.0G" % 123456.78901234567890 == "1E+05"
|
||
assert "%.1G" % 123456.78901234567890 == "1E+05"
|
||
assert "%.2G" % 123456.78901234567890 == "1.2E+05"
|
||
assert "%G" % 1234567.8901234567890 == "1.23457E+06"
|
||
assert "%.0G" % 1234567.8901234567890 == "1E+06"
|
||
assert "%.1G" % 1234567.8901234567890 == "1E+06"
|
||
assert "%.2G" % 1234567.8901234567890 == "1.2E+06"
|
||
assert "%.3G" % 1234567.8901234567890 == "1.23E+06"
|
||
assert "%.5G" % 1234567.8901234567890 == "1.2346E+06"
|
||
assert "%.6G" % 1234567.8901234567890 == "1.23457E+06"
|
||
assert "%.7G" % 1234567.8901234567890 == "1234568"
|
||
assert "%.8G" % 1234567.8901234567890 == "1234567.9"
|
||
assert "%g" % 0.12345678901234567890 == "0.123457"
|
||
assert "%g" % 0.12345678901234567890e-1 == "0.0123457"
|
||
assert "%g" % 0.12345678901234567890e-2 == "0.00123457"
|
||
assert "%g" % 0.12345678901234567890e-3 == "0.000123457"
|
||
assert "%g" % 0.12345678901234567890e-4 == "1.23457e-05"
|
||
assert "%g" % 0.12345678901234567890e-5 == "1.23457e-06"
|
||
assert "%.6g" % 0.12345678901234567890e-5 == "1.23457e-06"
|
||
assert "%.10g" % 0.12345678901234567890e-5 == "1.23456789e-06"
|
||
assert "%.20g" % 0.12345678901234567890e-5 == "1.2345678901234567384e-06"
|
||
assert "%G" % 0.12345678901234567890 == "0.123457"
|
||
assert "%G" % 0.12345678901234567890e-1 == "0.0123457"
|
||
assert "%G" % 0.12345678901234567890e-2 == "0.00123457"
|
||
assert "%G" % 0.12345678901234567890e-3 == "0.000123457"
|
||
assert "%G" % 0.12345678901234567890e-4 == "1.23457E-05"
|
||
assert "%G" % 0.12345678901234567890e-5 == "1.23457E-06"
|
||
assert "%.6G" % 0.12345678901234567890e-5 == "1.23457E-06"
|
||
assert "%.10G" % 0.12345678901234567890e-5 == "1.23456789E-06"
|
||
assert "%.20G" % 0.12345678901234567890e-5 == "1.2345678901234567384E-06"
|
||
assert "%g" % float("nan") == "nan"
|
||
assert "%g" % float("-nan") == "nan"
|
||
assert "%G" % float("nan") == "NAN"
|
||
assert "%g" % float("inf") == "inf"
|
||
assert "%g" % float("-inf") == "-inf"
|
||
assert "%G" % float("inf") == "INF"
|
||
assert "%.0g" % 1.020e-13 == "1e-13"
|
||
assert "%.0g" % 1.020e-13 == "1e-13"
|
||
assert "%.1g" % 1.020e-13 == "1e-13"
|
||
assert "%.2g" % 1.020e-13 == "1e-13"
|
||
assert "%.3g" % 1.020e-13 == "1.02e-13"
|
||
assert "%.4g" % 1.020e-13 == "1.02e-13"
|
||
assert "%.5g" % 1.020e-13 == "1.02e-13"
|
||
assert "%.6g" % 1.020e-13 == "1.02e-13"
|
||
assert "%.7g" % 1.020e-13 == "1.02e-13"
|
||
assert "%g" % 1.020e-13 == "1.02e-13"
|
||
assert "%g" % 1.020e-4 == "0.000102"
|
||
|
||
assert_raises(
|
||
TypeError,
|
||
lambda: "My name is %s and I'm %(age)d years old" % ("Foo", 25),
|
||
_msg="format requires a mapping",
|
||
)
|
||
assert_raises(
|
||
TypeError, lambda: "My name is %(name)s" % "Foo", _msg="format requires a mapping"
|
||
)
|
||
assert_raises(
|
||
ValueError,
|
||
lambda: "This %(food}s is great!" % {"food": "cookie"},
|
||
_msg="incomplete format key",
|
||
)
|
||
assert_raises(ValueError, lambda: "My name is %" % "Foo", _msg="incomplete format")
|
||
|
||
assert "a" < "b"
|
||
assert "a" <= "b"
|
||
assert "a" <= "a"
|
||
assert "z" > "b"
|
||
assert "z" >= "b"
|
||
assert "a" >= "a"
|
||
|
||
# str.translate
|
||
assert "abc".translate({97: "🎅", 98: None, 99: "xd"}) == "🎅xd"
|
||
assert "abc".translate({97: 100}) == "dbc"
|
||
# Any `LookupError` leaves the character unchanged, not only `KeyError`.
|
||
assert "\x00bc".translate(["x"]) == "xbc"
|
||
assert "\x00\x01\x05".translate(("z", None)) == "z\x05"
|
||
|
||
|
||
class TranslateMissing(dict):
|
||
def __missing__(self, key):
|
||
return "M"
|
||
|
||
|
||
assert "abc".translate(TranslateMissing({97: "A"})) == "AMM"
|
||
assert "".translate(5) == ""
|
||
with assert_raises(TypeError):
|
||
"a".translate(5)
|
||
with assert_raises(ValueError):
|
||
"a".translate({97: 0x110000})
|
||
with assert_raises(TypeError):
|
||
"a".translate({97: 1.5})
|
||
|
||
# str.maketrans
|
||
assert str.maketrans({"a": "abc", "b": None, "c": 33}) == {97: "abc", 98: None, 99: 33}
|
||
assert str.maketrans("hello", "world", "rust") == {
|
||
104: 119,
|
||
101: 111,
|
||
108: 108,
|
||
111: 100,
|
||
114: None,
|
||
117: None,
|
||
115: None,
|
||
116: None,
|
||
}
|
||
|
||
|
||
def try_mutate_str():
|
||
word = "word"
|
||
word[0] = "x"
|
||
|
||
|
||
assert_raises(TypeError, try_mutate_str)
|
||
|
||
ss = ["Hello", "안녕", "👋"]
|
||
bs = [b"Hello", b"\xec\x95\x88\xeb\x85\x95", b"\xf0\x9f\x91\x8b"]
|
||
|
||
for s, b in zip(ss, bs):
|
||
assert s.encode() == b
|
||
|
||
for s, b, e in zip(ss, bs, ["u8", "U8", "utf-8", "UTF-8", "utf_8"]):
|
||
assert s.encode(e) == b
|
||
# assert s.encode(encoding=e) == b
|
||
|
||
# str.isisprintable
|
||
assert "".isprintable()
|
||
assert " ".isprintable()
|
||
assert "abcdefg".isprintable()
|
||
assert not "abcdefg\n".isprintable()
|
||
assert "ʹ".isprintable()
|
||
|
||
# test unicode literals
|
||
assert "\xac" == "¬"
|
||
assert "\u0037" == "7"
|
||
assert "\u0040" == "@"
|
||
assert "\u0041" == "A"
|
||
assert "\u00be" == "¾"
|
||
assert "\u9487" == "钇"
|
||
assert "\U0001f609" == "😉"
|
||
|
||
# test str iter
|
||
iterable_str = "12345678😉"
|
||
str_iter = iter(iterable_str)
|
||
|
||
assert next(str_iter) == "1"
|
||
assert next(str_iter) == "2"
|
||
assert next(str_iter) == "3"
|
||
assert next(str_iter) == "4"
|
||
assert next(str_iter) == "5"
|
||
assert next(str_iter) == "6"
|
||
assert next(str_iter) == "7"
|
||
assert next(str_iter) == "8"
|
||
assert next(str_iter) == "😉"
|
||
assert next(str_iter, None) == None
|
||
assert_raises(StopIteration, next, str_iter)
|
||
|
||
str_iter_reversed = reversed(iterable_str)
|
||
|
||
assert next(str_iter_reversed) == "😉"
|
||
assert next(str_iter_reversed) == "8"
|
||
assert next(str_iter_reversed) == "7"
|
||
assert next(str_iter_reversed) == "6"
|
||
assert next(str_iter_reversed) == "5"
|
||
assert next(str_iter_reversed) == "4"
|
||
assert next(str_iter_reversed) == "3"
|
||
assert next(str_iter_reversed) == "2"
|
||
assert next(str_iter_reversed) == "1"
|
||
assert next(str_iter_reversed, None) == None
|
||
assert_raises(StopIteration, next, str_iter_reversed)
|
||
|
||
assert str.__rmod__("%i", 30) == NotImplemented
|
||
assert_raises(TypeError, lambda: str.__rmod__(30, "%i"))
|
||
|
||
# test str index
|
||
index_str = "Rust Python"
|
||
|
||
assert index_str[0] == "R"
|
||
assert index_str[-1] == "n"
|
||
|
||
assert_raises(TypeError, lambda: index_str["a"])
|
||
|
||
assert chr(9).__repr__() == "'\\t'"
|
||
assert chr(99).__repr__() == "'c'"
|
||
assert chr(999).__repr__() == "'ϧ'"
|
||
assert chr(9999).__repr__() == "'✏'"
|
||
assert chr(99999).__repr__() == "'𘚟'"
|
||
assert chr(999999).__repr__() == "'\\U000f423f'"
|
||
|
||
assert "a".__ne__("b")
|
||
assert not "a".__ne__("a")
|
||
assert not "".__ne__("")
|
||
assert "".__ne__(1) == NotImplemented
|
||
|
||
# check non-cased characters
|
||
assert "A_B".isupper()
|
||
assert "a_b".islower()
|
||
assert "A1".isupper()
|
||
assert "1A".isupper()
|
||
assert "a1".islower()
|
||
assert "1a".islower()
|
||
assert "가나다a".islower()
|
||
assert "가나다A".isupper()
|
||
assert not "ジョジョ".isupper()
|
||
assert not "ジョジョ".islower()
|
||
|
||
# test str.format_map()
|
||
#
|
||
# The following tests were performed in Python 3.7.5:
|
||
# Python 3.7.5 (default, Dec 19 2019, 17:11:32)
|
||
# [GCC 5.4.0 20160609] on linux
|
||
|
||
# >>> '{x} {y}'.format_map({'x': 1, 'y': 2})
|
||
# '1 2'
|
||
assert "{x} {y}".format_map({"x": 1, "y": 2}) == "1 2"
|
||
|
||
# >>> '{x:04d}'.format_map({'x': 1})
|
||
# '0001'
|
||
assert "{x:04d}".format_map({"x": 1}) == "0001"
|
||
|
||
# >>> '{x} {y}'.format_map('foo')
|
||
# Traceback (most recent call last):
|
||
# File "<stdin>", line 1, in <module>
|
||
# TypeError: string indices must be integers
|
||
with AssertRaises(TypeError, None):
|
||
"{x} {y}".format_map("foo")
|
||
|
||
# >>> '{x} {y}'.format_map(['foo'])
|
||
# Traceback (most recent call last):
|
||
# File "<stdin>", line 1, in <module>
|
||
# TypeError: list indices must be integers or slices, not str
|
||
with AssertRaises(TypeError, None):
|
||
"{x} {y}".format_map(["foo"])
|
||
|
||
# >>> '{x} {y}'.format_map()
|
||
# Traceback (most recent call last):
|
||
# File "<stdin>", line 1, in <module>
|
||
# TypeError: format_map() takes exactly one argument (0 given)
|
||
with AssertRaises(
|
||
TypeError, msg="TypeError: format_map() takes exactly one argument (0 given)"
|
||
):
|
||
("{x} {y}".format_map(),)
|
||
|
||
# >>> '{x} {y}'.format_map('foo', 'bar')
|
||
# Traceback (most recent call last):
|
||
# File "<stdin>", line 1, in <module>
|
||
# TypeError: format_map() takes exactly one argument (2 given)
|
||
with AssertRaises(
|
||
TypeError, msg="TypeError: format_map() takes exactly one argument (2 given)"
|
||
):
|
||
"{x} {y}".format_map("foo", "bar")
|
||
|
||
# >>> '{x} {y}'.format_map({'x': 1})
|
||
# Traceback (most recent call last):
|
||
# File "<stdin>", line 1, in <module>
|
||
# KeyError: 'y'
|
||
with AssertRaises(KeyError, msg="KeyError: 'y'"):
|
||
"{x} {y}".format_map({"x": 1})
|
||
|
||
# >>> '{x} {y}'.format_map({'x': 1, 'z': 2})
|
||
# Traceback (most recent call last):
|
||
# File "<stdin>", line 1, in <module>
|
||
# KeyError: 'y'
|
||
with AssertRaises(KeyError, msg="KeyError: 'y'"):
|
||
"{x} {y}".format_map({"x": 1, "z": 2})
|
||
|
||
# >>> '{{literal}}'.format_map('foo')
|
||
# '{literal}'
|
||
assert "{{literal}}".format_map("foo") == "{literal}"
|
||
|
||
# test formatting float values
|
||
assert f"{5:f}" == "5.000000"
|
||
assert f"{-5:f}" == "-5.000000"
|
||
assert f"{5.0:f}" == "5.000000"
|
||
assert f"{-5.0:f}" == "-5.000000"
|
||
assert f"{5:.2f}" == "5.00"
|
||
assert f"{5.0:.2f}" == "5.00"
|
||
assert f"{-5:.2f}" == "-5.00"
|
||
assert f"{-5.0:.2f}" == "-5.00"
|
||
assert f"{5.0:04f}" == "5.000000"
|
||
assert f"{5.1234:+f}" == "+5.123400"
|
||
assert f"{5.1234: f}" == " 5.123400"
|
||
assert f"{5.1234:-f}" == "5.123400"
|
||
assert f"{-5.1234:-f}" == "-5.123400"
|
||
assert f"{1.0:+}" == "+1.0"
|
||
assert f"--{1.0:f>4}--" == "--f1.0--"
|
||
assert f"--{1.0:f<4}--" == "--1.0f--"
|
||
assert f"--{1.0:d^4}--" == "--1.0d--"
|
||
assert f"--{1.0:d^5}--" == "--d1.0d--"
|
||
assert f"--{1.1:f>6}--" == "--fff1.1--"
|
||
assert "{}".format(float("nan")) == "nan"
|
||
assert "{:f}".format(float("nan")) == "nan"
|
||
assert "{:f}".format(float("-nan")) == "nan"
|
||
assert "{:F}".format(float("nan")) == "NAN"
|
||
assert "{}".format(float("inf")) == "inf"
|
||
assert "{:f}".format(float("inf")) == "inf"
|
||
assert "{:f}".format(float("-inf")) == "-inf"
|
||
assert "{:F}".format(float("inf")) == "INF"
|
||
assert f"{1234567890.1234:,.2f}" == "1,234,567,890.12"
|
||
assert f"{1234567890.1234:_.2f}" == "1_234_567_890.12"
|
||
with AssertRaises(ValueError, msg="Unknown format code 'd' for object of type 'float'"):
|
||
f"{5.0:04d}"
|
||
|
||
# Test % formatting
|
||
assert f"{10:%}" == "1000.000000%"
|
||
assert f"{10.0:%}" == "1000.000000%"
|
||
assert f"{10.0:.2%}" == "1000.00%"
|
||
assert f"{10.0:.8%}" == "1000.00000000%"
|
||
assert f"{-10:%}" == "-1000.000000%"
|
||
assert f"{-10.0:%}" == "-1000.000000%"
|
||
assert f"{-10.0:.2%}" == "-1000.00%"
|
||
assert f"{-10.0:.8%}" == "-1000.00000000%"
|
||
assert "{:%}".format(float("nan")) == "nan%"
|
||
assert "{:.2%}".format(float("nan")) == "nan%"
|
||
assert "{:%}".format(float("inf")) == "inf%"
|
||
assert "{:.2%}".format(float("inf")) == "inf%"
|
||
with AssertRaises(ValueError, msg="Invalid format specifier"):
|
||
f"{10.0:%3}"
|
||
|
||
# Test e & E formatting
|
||
assert "{:e}".format(10) == "1.000000e+01"
|
||
assert "{:.2e}".format(11) == "1.10e+01"
|
||
assert "{:e}".format(10.0) == "1.000000e+01"
|
||
assert "{:e}".format(-10.0) == "-1.000000e+01"
|
||
assert "{:.2e}".format(10.0) == "1.00e+01"
|
||
assert "{:.2e}".format(-10.0) == "-1.00e+01"
|
||
assert "{:.2e}".format(10.1) == "1.01e+01"
|
||
assert "{:.2e}".format(-10.1) == "-1.01e+01"
|
||
assert "{:.2e}".format(10.001) == "1.00e+01"
|
||
assert "{:.4e}".format(100.234) == "1.0023e+02"
|
||
assert "{:.5e}".format(100.234) == "1.00234e+02"
|
||
assert "{:.2E}".format(10.0) == "1.00E+01"
|
||
assert "{:.2E}".format(-10.0) == "-1.00E+01"
|
||
assert "{:e}".format(float("nan")) == "nan"
|
||
assert "{:e}".format(float("-nan")) == "nan"
|
||
assert "{:E}".format(float("nan")) == "NAN"
|
||
assert "{:e}".format(float("inf")) == "inf"
|
||
assert "{:e}".format(float("-inf")) == "-inf"
|
||
assert "{:E}".format(float("inf")) == "INF"
|
||
|
||
# Test g & G formatting
|
||
assert "{:g}".format(10.0) == "10"
|
||
assert "{:g}".format(100000.0) == "100000"
|
||
assert "{:g}".format(123456.78901234567890) == "123457"
|
||
assert "{:.0g}".format(123456.78901234567890) == "1e+05"
|
||
assert "{:.1g}".format(123456.78901234567890) == "1e+05"
|
||
assert "{:.2g}".format(123456.78901234567890) == "1.2e+05"
|
||
assert "{:g}".format(1234567.8901234567890) == "1.23457e+06"
|
||
assert "{:.0g}".format(1234567.8901234567890) == "1e+06"
|
||
assert "{:.1g}".format(1234567.8901234567890) == "1e+06"
|
||
assert "{:.2g}".format(1234567.8901234567890) == "1.2e+06"
|
||
assert "{:.3g}".format(1234567.8901234567890) == "1.23e+06"
|
||
assert "{:.5g}".format(1234567.8901234567890) == "1.2346e+06"
|
||
assert "{:.6g}".format(1234567.8901234567890) == "1.23457e+06"
|
||
assert "{:.7g}".format(1234567.8901234567890) == "1234568"
|
||
assert "{:.8g}".format(1234567.8901234567890) == "1234567.9"
|
||
assert "{:G}".format(123456.78901234567890) == "123457"
|
||
assert "{:.0G}".format(123456.78901234567890) == "1E+05"
|
||
assert "{:.1G}".format(123456.78901234567890) == "1E+05"
|
||
assert "{:.2G}".format(123456.78901234567890) == "1.2E+05"
|
||
assert "{:G}".format(1234567.8901234567890) == "1.23457E+06"
|
||
assert "{:.0G}".format(1234567.8901234567890) == "1E+06"
|
||
assert "{:.1G}".format(1234567.8901234567890) == "1E+06"
|
||
assert "{:.2G}".format(1234567.8901234567890) == "1.2E+06"
|
||
assert "{:.3G}".format(1234567.8901234567890) == "1.23E+06"
|
||
assert "{:.5G}".format(1234567.8901234567890) == "1.2346E+06"
|
||
assert "{:.6G}".format(1234567.8901234567890) == "1.23457E+06"
|
||
assert "{:.7G}".format(1234567.8901234567890) == "1234568"
|
||
assert "{:.8G}".format(1234567.8901234567890) == "1234567.9"
|
||
assert "{:g}".format(0.12345678901234567890) == "0.123457"
|
||
assert "{:g}".format(0.12345678901234567890e-1) == "0.0123457"
|
||
assert "{:g}".format(0.12345678901234567890e-2) == "0.00123457"
|
||
assert "{:g}".format(0.12345678901234567890e-3) == "0.000123457"
|
||
assert "{:g}".format(0.12345678901234567890e-4) == "1.23457e-05"
|
||
assert "{:g}".format(0.12345678901234567890e-5) == "1.23457e-06"
|
||
assert "{:.6g}".format(0.12345678901234567890e-5) == "1.23457e-06"
|
||
assert "{:.10g}".format(0.12345678901234567890e-5) == "1.23456789e-06"
|
||
assert "{:.20g}".format(0.12345678901234567890e-5) == "1.2345678901234567384e-06"
|
||
assert "{:G}".format(0.12345678901234567890) == "0.123457"
|
||
assert "{:G}".format(0.12345678901234567890e-1) == "0.0123457"
|
||
assert "{:G}".format(0.12345678901234567890e-2) == "0.00123457"
|
||
assert "{:G}".format(0.12345678901234567890e-3) == "0.000123457"
|
||
assert "{:G}".format(0.12345678901234567890e-4) == "1.23457E-05"
|
||
assert "{:G}".format(0.12345678901234567890e-5) == "1.23457E-06"
|
||
assert "{:.6G}".format(0.12345678901234567890e-5) == "1.23457E-06"
|
||
assert "{:.10G}".format(0.12345678901234567890e-5) == "1.23456789E-06"
|
||
assert "{:.20G}".format(0.12345678901234567890e-5) == "1.2345678901234567384E-06"
|
||
assert "{:g}".format(float("nan")) == "nan"
|
||
assert "{:g}".format(float("-nan")) == "nan"
|
||
assert "{:G}".format(float("nan")) == "NAN"
|
||
assert "{:g}".format(float("inf")) == "inf"
|
||
assert "{:g}".format(float("-inf")) == "-inf"
|
||
assert "{:G}".format(float("inf")) == "INF"
|
||
assert "{:.0g}".format(1.020e-13) == "1e-13"
|
||
assert "{:.0g}".format(1.020e-13) == "1e-13"
|
||
assert "{:.1g}".format(1.020e-13) == "1e-13"
|
||
assert "{:.2g}".format(1.020e-13) == "1e-13"
|
||
assert "{:.3g}".format(1.020e-13) == "1.02e-13"
|
||
assert "{:.4g}".format(1.020e-13) == "1.02e-13"
|
||
assert "{:.5g}".format(1.020e-13) == "1.02e-13"
|
||
assert "{:.6g}".format(1.020e-13) == "1.02e-13"
|
||
assert "{:.7g}".format(1.020e-13) == "1.02e-13"
|
||
assert "{:g}".format(1.020e-13) == "1.02e-13"
|
||
assert "{:g}".format(1.020e-4) == "0.000102"
|
||
|
||
# Test n & N formatting
|
||
assert "{:n}".format(999999.1234) == "999999"
|
||
assert "{:n}".format(9999.1234) == "9999.12"
|
||
assert "{:n}".format(-1000000.1234) == "-1e+06"
|
||
assert "{:n}".format(1000000.1234) == "1e+06"
|
||
assert "{:.1n}".format(1000000.1234) == "1e+06"
|
||
assert "{:.2n}".format(1000000.1234) == "1e+06"
|
||
assert "{:.3n}".format(1000000.1234) == "1e+06"
|
||
assert "{:.4n}".format(1000000.1234) == "1e+06"
|
||
assert "{:.5n}".format(1000000.1234) == "1e+06"
|
||
assert "{:.6n}".format(1000000.1234) == "1e+06"
|
||
assert "{:.7n}".format(1000000.1234) == "1000000"
|
||
assert "{:.8n}".format(1000000.1234) == "1000000.1"
|
||
assert "{:.10n}".format(1000000.1234) == "1000000.123"
|
||
assert "{:.11n}".format(1000000.1234) == "1000000.1234"
|
||
assert "{:.11n}".format(-1000000.1234) == "-1000000.1234"
|
||
assert "{:0n}".format(-1000000.1234) == "-1e+06"
|
||
assert "{:n}".format(-1000000.1234) == "-1e+06"
|
||
assert "{:-1n}".format(-1000000.1234) == "-1e+06"
|
||
|
||
with AssertRaises(ValueError, msg="Unknown format code 'N' for object of type 'float'"):
|
||
"{:N}".format(999999.1234)
|
||
with AssertRaises(ValueError, msg="Unknown format code 'N' for object of type 'float'"):
|
||
"{:.1N}".format(1000000.1234)
|
||
with AssertRaises(ValueError, msg="Unknown format code 'N' for object of type 'float'"):
|
||
"{:0N}".format(-1000000.1234)
|
||
with AssertRaises(ValueError, msg="Unknown format code 'N' for object of type 'float'"):
|
||
"{:-1N}".format(-1000000.1234)
|
||
|
||
|
||
# remove*fix test
|
||
def test_removeprefix():
|
||
s = "foobarfoo"
|
||
s_ref = "foobarfoo"
|
||
assert s.removeprefix("f") == s_ref[1:]
|
||
assert s.removeprefix("fo") == s_ref[2:]
|
||
assert s.removeprefix("foo") == s_ref[3:]
|
||
|
||
assert s.removeprefix("") == s_ref
|
||
assert s.removeprefix("bar") == s_ref
|
||
assert s.removeprefix("lol") == s_ref
|
||
assert s.removeprefix("_foo") == s_ref
|
||
assert s.removeprefix("-foo") == s_ref
|
||
assert s.removeprefix("afoo") == s_ref
|
||
assert s.removeprefix("*foo") == s_ref
|
||
|
||
assert s == s_ref, "undefined test fail"
|
||
|
||
s_uc = "😱foobarfoo🖖"
|
||
s_ref_uc = "😱foobarfoo🖖"
|
||
assert s_uc.removeprefix("😱") == s_ref_uc[1:]
|
||
assert s_uc.removeprefix("😱fo") == s_ref_uc[3:]
|
||
assert s_uc.removeprefix("😱foo") == s_ref_uc[4:]
|
||
|
||
assert s_uc.removeprefix("🖖") == s_ref_uc
|
||
assert s_uc.removeprefix("foo") == s_ref_uc
|
||
assert s_uc.removeprefix(" ") == s_ref_uc
|
||
assert s_uc.removeprefix("_😱") == s_ref_uc
|
||
assert s_uc.removeprefix(" 😱") == s_ref_uc
|
||
assert s_uc.removeprefix("-😱") == s_ref_uc
|
||
assert s_uc.removeprefix("#😱") == s_ref_uc
|
||
|
||
|
||
def test_removeprefix_types():
|
||
s = "0123456"
|
||
s_ref = "0123456"
|
||
others = [0, ["012"]]
|
||
found = False
|
||
for o in others:
|
||
try:
|
||
s.removeprefix(o)
|
||
except:
|
||
found = True
|
||
|
||
assert found, f"Removeprefix accepts other type: {type(o)}: {o=}"
|
||
|
||
|
||
def test_removesuffix():
|
||
s = "foobarfoo"
|
||
s_ref = "foobarfoo"
|
||
assert s.removesuffix("o") == s_ref[:-1]
|
||
assert s.removesuffix("oo") == s_ref[:-2]
|
||
assert s.removesuffix("foo") == s_ref[:-3]
|
||
|
||
assert s.removesuffix("") == s_ref
|
||
assert s.removesuffix("bar") == s_ref
|
||
assert s.removesuffix("lol") == s_ref
|
||
assert s.removesuffix("foo_") == s_ref
|
||
assert s.removesuffix("foo-") == s_ref
|
||
assert s.removesuffix("foo*") == s_ref
|
||
assert s.removesuffix("fooa") == s_ref
|
||
|
||
assert s == s_ref, "undefined test fail"
|
||
|
||
s_uc = "😱foobarfoo🖖"
|
||
s_ref_uc = "😱foobarfoo🖖"
|
||
assert s_uc.removesuffix("🖖") == s_ref_uc[:-1]
|
||
assert s_uc.removesuffix("oo🖖") == s_ref_uc[:-3]
|
||
assert s_uc.removesuffix("foo🖖") == s_ref_uc[:-4]
|
||
|
||
assert s_uc.removesuffix("😱") == s_ref_uc
|
||
assert s_uc.removesuffix("foo") == s_ref_uc
|
||
assert s_uc.removesuffix(" ") == s_ref_uc
|
||
assert s_uc.removesuffix("🖖_") == s_ref_uc
|
||
assert s_uc.removesuffix("🖖 ") == s_ref_uc
|
||
assert s_uc.removesuffix("🖖-") == s_ref_uc
|
||
assert s_uc.removesuffix("🖖#") == s_ref_uc
|
||
|
||
|
||
def test_removesuffix_types():
|
||
s = "0123456"
|
||
s_ref = "0123456"
|
||
others = [0, 6, ["6"]]
|
||
found = False
|
||
for o in others:
|
||
try:
|
||
s.removesuffix(o)
|
||
except:
|
||
found = True
|
||
|
||
assert found, f"Removesuffix accepts other type: {type(o)}: {o=}"
|
||
|
||
|
||
skip_if_unsupported(3, 9, test_removeprefix)
|
||
skip_if_unsupported(3, 9, test_removeprefix_types)
|
||
skip_if_unsupported(3, 9, test_removesuffix)
|
||
skip_if_unsupported(3, 9, test_removesuffix_types)
|
||
|
||
|
||
# Regression to
|
||
# https://github.com/RustPython/RustPython/issues/2840
|
||
|
||
a = "abc123()"
|
||
|
||
assert id(a) == id(a)
|
||
assert id(a) != id(a * -1)
|
||
assert id(a) != id(a * 0)
|
||
assert id(a) == id(a * 1) # only cases
|
||
assert id(a) == id(1 * a) # when `id` stays the same
|
||
assert id(a) != id(a * 2)
|
||
|
||
|
||
class MyString(str):
|
||
pass
|
||
|
||
|
||
b = MyString("0123abc*&")
|
||
assert id(b) == id(b)
|
||
assert id(b) != id(b * -1)
|
||
assert id(b) != id(b * 0)
|
||
assert id(b) != id(b * 1)
|
||
assert id(b) != id(1 * b)
|
||
assert id(b) != id(b * 2)
|
||
|
||
|
||
def test_huge_width():
|
||
# A width that cannot be allocated is a MemoryError, not an aborted
|
||
# process, and a tabsize wider than a C int does not fit at all.
|
||
for meth in ("center", "ljust", "rjust", "zfill"):
|
||
assert_raises(MemoryError, lambda meth=meth: getattr("a", meth)(1 << 62))
|
||
assert_raises(OverflowError, lambda: "\ta".expandtabs(1 << 62))
|
||
assert_raises(OverflowError, lambda: "\ta".expandtabs(2**31))
|
||
# The widest tabsize that still fits is accepted. With no tab to expand
|
||
# there is nothing to lay out, so the width is never allocated.
|
||
assert "a".expandtabs(2**31 - 1) == "a"
|
||
|
||
|
||
test_huge_width()
|
||
|
||
|
||
def test_replace_empty_pattern():
|
||
# An empty pattern matches at every code point boundary, and only there.
|
||
# Matching it byte by byte instead put the replacement inside a multi-byte
|
||
# character, so what came back was no longer the text that went in.
|
||
assert "abc".replace("", "-") == "-a-b-c-"
|
||
assert "ábç".replace("", "#") == "#á#b#ç#"
|
||
assert "😀".replace("", "-") == "-😀-"
|
||
assert "".replace("", "-") == "-"
|
||
assert "abc".replace("", "") == "abc"
|
||
|
||
# The count is a number of insertions, and the one after the last
|
||
# character only happens if the count reaches that far.
|
||
assert "abc".replace("", "-", 0) == "abc"
|
||
assert "abc".replace("", "-", 1) == "-abc"
|
||
assert "abc".replace("", "-", 3) == "-a-b-c"
|
||
assert "abc".replace("", "-", 4) == "-a-b-c-"
|
||
assert "abc".replace("", "-", 99) == "-a-b-c-"
|
||
assert "ábç".replace("", "#", 2) == "#á#bç"
|
||
|
||
# The result has to stay readable as text afterwards.
|
||
spread = "á".replace("", "-")
|
||
assert len(spread) == 3
|
||
assert list(spread) == ["-", "á", "-"]
|
||
assert spread[1] == "á"
|
||
assert spread.upper() == "-Á-"
|
||
assert spread.encode("utf-8") == b"-\xc3\xa1-"
|
||
|
||
# A pattern that is not empty was already fine and stays that way.
|
||
assert "ábç".replace("b", "#") == "á#ç"
|
||
assert "ábç".replace("á", "#") == "#bç"
|
||
assert "aaa".replace("a", "b", 2) == "bba"
|
||
|
||
|
||
test_replace_empty_pattern()
|
||
|
||
|
||
def test_replace_unchanged():
|
||
for text in ("already clean", "déjà propre", "a\ud800b"):
|
||
for old, new, count in (
|
||
(text[:1], "#", 0),
|
||
(text[:1], text[:1], -1),
|
||
(text + " more", "replacement", -1),
|
||
):
|
||
assert text.replace(old, new, count) is text
|
||
result = MyString(text).replace(old, new, count)
|
||
assert result == text
|
||
assert type(result) is str
|
||
|
||
# Returning the original string must not bypass argument conversion.
|
||
assert_raises(TypeError, lambda: "abc".replace(1, "x", 0))
|
||
assert_raises(TypeError, lambda: "abc".replace("a", 1, 0))
|
||
assert_raises(TypeError, lambda: "abc".replace("a", "a", None))
|
||
|
||
|
||
test_replace_unchanged()
|
||
|
||
|
||
def test_replace_missing_pattern():
|
||
for text in ("ordinary text", "\u00e1\u03b2\U0001f600", "a\ud800b\udfff"):
|
||
for count in (-1, 1, 10):
|
||
assert text.replace("\r", "replacement", count) is text
|
||
result = MyString(text).replace("\r", "replacement", count)
|
||
assert type(result) is str
|
||
assert result == text
|
||
|
||
# Continue the same search after the first match, including lone surrogates.
|
||
assert "ababa".replace("aba", "x") == "xba"
|
||
assert "\ud800x\ud800".replace("\ud800", "\udfff", 1) == "\udfffx\ud800"
|
||
assert "\ud800x\ud800".replace("\ud800", "\udfff") == "\udfffx\udfff"
|
||
|
||
|
||
test_replace_missing_pattern()
|
||
|
||
|
||
def test_expandtabs_zero_tabsize():
|
||
# With no width to advance to, the tabs come out and nothing else moves.
|
||
# A tab that followed a character used to ask for a run of usize::MAX
|
||
# spaces and take the interpreter down with it.
|
||
for tabsize in (0, -1, -8):
|
||
assert "a\tb".expandtabs(tabsize) == "ab"
|
||
assert "ab\tcd\tef".expandtabs(tabsize) == "abcdef"
|
||
assert "a\nb\tc".expandtabs(tabsize) == "a\nbc"
|
||
assert "a\r\nb\tc".expandtabs(tabsize) == "a\r\nbc"
|
||
assert "á\tb".expandtabs(tabsize) == "áb"
|
||
assert "😀\tb".expandtabs(tabsize) == "😀b"
|
||
assert "\ta".expandtabs(tabsize) == "a"
|
||
assert "\t".expandtabs(tabsize) == ""
|
||
assert "".expandtabs(tabsize) == ""
|
||
assert "no tabs".expandtabs(tabsize) == "no tabs"
|
||
assert b"a\tb".expandtabs(tabsize) == b"ab"
|
||
assert bytearray(b"a\tb").expandtabs(tabsize) == bytearray(b"ab")
|
||
|
||
# A tab size that is actually there keeps working.
|
||
assert "a\tb".expandtabs(8) == "a b"
|
||
assert "a\tb".expandtabs(1) == "a b"
|
||
assert "abcd\te".expandtabs(4) == "abcd e"
|
||
assert "a\nb\tc".expandtabs(4) == "a\nb c"
|
||
|
||
|
||
test_expandtabs_zero_tabsize()
|
||
|
||
|
||
def test_concat_error_message():
|
||
# CPython falls back to str's sequence concatenation once a reflected
|
||
# __radd__ declines, so the message names the concatenation, not the
|
||
# generic binary operation. Types that define __radd__ (every number)
|
||
# used to take the generic path here.
|
||
def message(other):
|
||
try:
|
||
"a" + other
|
||
except TypeError as err:
|
||
return str(err)
|
||
raise AssertionError("TypeError was not raised")
|
||
|
||
assert message(1) == 'can only concatenate str (not "int") to str'
|
||
assert message(1.5) == 'can only concatenate str (not "float") to str'
|
||
assert message(True) == 'can only concatenate str (not "bool") to str'
|
||
assert message([]) == 'can only concatenate str (not "list") to str'
|
||
assert message(None) == 'can only concatenate str (not "NoneType") to str'
|
||
assert message(b"b") == 'can only concatenate str (not "bytes") to str'
|
||
|
||
class Declines:
|
||
def __radd__(self, other):
|
||
return NotImplemented
|
||
|
||
assert message(Declines()) == 'can only concatenate str (not "Declines") to str'
|
||
|
||
# A __radd__ that returns a value still wins.
|
||
class Accepts:
|
||
def __radd__(self, other):
|
||
return "from-radd"
|
||
|
||
assert "a" + Accepts() == "from-radd"
|
||
|
||
|
||
test_concat_error_message()
|
||
|
||
|
||
def test_subscript_typeerror_message():
|
||
def message(index):
|
||
try:
|
||
"abc"[index]
|
||
except TypeError as err:
|
||
return str(err)
|
||
raise AssertionError("TypeError was not raised")
|
||
|
||
assert message("def") == "string indices must be integers, not 'str'"
|
||
assert message(object()) == "string indices must be integers, not 'object'"
|
||
assert message(1.5) == "string indices must be integers, not 'float'"
|
||
assert message(None) == "string indices must be integers, not 'NoneType'"
|
||
|
||
Long = type("A" * 250, (), {})
|
||
assert message(Long()) == "string indices must be integers, not '{}'".format(
|
||
"A" * 200
|
||
)
|
||
|
||
|
||
test_subscript_typeerror_message()
|
||
|
||
|
||
# Decimal conversion errors identify the consumed argument, including star arguments.
|
||
for is_bytes in (False, True):
|
||
for conversion in "diu":
|
||
for template, values, context in (
|
||
("%" + conversion, "bad", ""),
|
||
("%" + conversion, ("bad",), " 1"),
|
||
("%s %" + conversion, ("ok", "bad"), " 2"),
|
||
("%*.*" + conversion, (5, 2, "bad"), " 3"),
|
||
("%(value)" + conversion, {"value": "bad"}, " 'value'"),
|
||
):
|
||
if is_bytes:
|
||
template = template.encode()
|
||
if isinstance(values, dict):
|
||
values = {b"value": "bad"}
|
||
context = " b'value'"
|
||
elif isinstance(values, tuple) and len(values) == 2:
|
||
values = (b"ok", "bad")
|
||
with assert_raises(TypeError) as caught:
|
||
template % values
|
||
assert str(caught.exception) == (
|
||
f"format argument{context}: %{conversion} requires a real number, not str"
|
||
)
|
||
|
||
|
||
def test_percent_callback_order_bounded():
|
||
for template in ("%r %", b"%r %", bytearray(b"%r %")):
|
||
calls = []
|
||
|
||
class Value:
|
||
def __repr__(self):
|
||
calls.append("repr")
|
||
return "value"
|
||
|
||
with assert_raises(ValueError):
|
||
template % (Value(),)
|
||
assert calls == ["repr"]
|
||
|
||
for template, key in (
|
||
("%(x)*r", "x"),
|
||
(b"%(x)*r", b"x"),
|
||
(bytearray(b"%(x)*r"), b"x"),
|
||
):
|
||
calls = []
|
||
|
||
class Mapping:
|
||
def __getitem__(self, name):
|
||
calls.append(name)
|
||
return 5
|
||
|
||
with assert_raises(ValueError):
|
||
template % Mapping()
|
||
assert calls == [key]
|
||
|
||
|
||
test_percent_callback_order_bounded()
|
||
|
||
|
||
class PartitionString(str):
|
||
pass
|
||
|
||
|
||
partition_string = PartitionString("abc")
|
||
assert partition_string.partition(":")[0] is partition_string
|
||
assert partition_string.rpartition(":")[2] is partition_string
|