From c615d83c03e73c2eb695e5fa7b1d5b427407b48c Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Fri, 17 Jul 2026 10:56:29 +0200 Subject: [PATCH 1/7] [GR-77512] Fix bytes.strip matching the wrong whitespace category --- .../src/tests/test_bytes.py | 17 +++++++++++++++++ .../objects/bytes/BytesCommonBuiltins.java | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_bytes.py b/graalpython/com.oracle.graal.python.test/src/tests/test_bytes.py index a379b1c93e..309ba2a0a8 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_bytes.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_bytes.py @@ -37,6 +37,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import base64 import sys import unittest @@ -629,6 +630,22 @@ def test_strip_bytes(): assert b'abc'.lstrip(b'ac') == b'bc' assert b'abc'.rstrip(b'ac') == b'ab' + +def test_strip_default_whitespace(): + whitespace = b'\x09\x0a\x0b\x0c\x0d ' + non_whitespace = b'\x1c\x1d\x1e\x1f' + for type2test in (bytes, bytearray): + value = type2test(whitespace + non_whitespace + whitespace) + assert value.strip() == non_whitespace + assert value.lstrip() == non_whitespace + whitespace + assert value.rstrip() == whitespace + non_whitespace + + +def test_strip_base32_result_with_control_bytes(): + value = base64.b32decode(b'INFAO2ZW7APB2===') + assert value == b'CJ\x07k6\xf8\x1e\x1d' + assert value.strip() == value + class BaseTestSplit: def test_string_error(self): diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/bytes/BytesCommonBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/bytes/BytesCommonBuiltins.java index f401e4aacb..16bd5e6bd4 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/bytes/BytesCommonBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/bytes/BytesCommonBuiltins.java @@ -1922,7 +1922,7 @@ protected int findIndex(byte[] bs) { @TruffleBoundary private static boolean isWhitespace(byte b) { - return Character.isWhitespace(b); + return BytesUtils.isSpace(b); } protected int findIndex(byte[] bs, byte[] stripBs, int stripBsLen) { From d84fcbbc17effe2cc660260ab1ea6603e2733d19 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Fri, 17 Jul 2026 11:00:25 +0200 Subject: [PATCH 2/7] [GR-76975] Fix re.sub missing terminal empty-match replacement after a consuming match --- .../com.oracle.graal.python.test/src/tests/test_re.py | 9 +++++++++ .../python/builtins/modules/re/PatternBuiltins.java | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_re.py b/graalpython/com.oracle.graal.python.test/src/tests/test_re.py index 29d2a98033..807e3a493f 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_re.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_re.py @@ -317,6 +317,15 @@ def test_re_subn(self): self.assertEqual(re.subn("b*", "x", "xyz", 2), ('xxxyz', 2)) self.assertEqual(re.subn("b*", "x", "xyz", count=2), ('xxxyz', 2)) + def test_terminal_empty_match_after_consuming_match(self): + cases = [ + (r"(?:(?P\*)){0,2}", "*"), + (r"(?:(?:\s){0,2}){0,2}", " "), + ] + for pattern, string in cases: + self.assertEqual(re.sub(pattern, "X", string), "XX") + self.assertEqual(re.subn(pattern, "X", string), ("XX", 2)) + def test_re_split(self): for string in ":a:b::c", S(":a:b::c"): self.assertTypedEqual(re.split(":", string), diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/re/PatternBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/re/PatternBuiltins.java index 2c11b8288f..0c0836a211 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/re/PatternBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/re/PatternBuiltins.java @@ -1191,7 +1191,7 @@ private static Object doReplace(Object compiledRegex, Object compiledRegexMustAd int n = 0; int pos = 0; boolean mustAdvance = false; - while ((count == 0 || n < count) && pos < stringLength) { + while ((count == 0 || n < count) && pos <= stringLength) { final Object searchResult; if (mustAdvance) { searchResult = invokeExecMethodNodeMustAdvance.execute(inliningTarget, compiledRegexMustAdvance, input, pos); From f3ce498a0987e34c81351c76707a39fcc260a2a7 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Fri, 17 Jul 2026 11:10:37 +0200 Subject: [PATCH 3/7] [GR-77539] Remove incorrect tp_str overrides in float, int, and bool --- .../src/tests/test_repr.py | 28 +++++++++++++++++++ .../builtins/objects/bool/BoolBuiltins.java | 9 ++---- .../objects/floats/FloatBuiltins.java | 7 +---- .../builtins/objects/ints/IntBuiltins.java | 7 +---- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_repr.py b/graalpython/com.oracle.graal.python.test/src/tests/test_repr.py index a99aa531a3..2cbc6587e1 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_repr.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_repr.py @@ -86,6 +86,34 @@ def __repr__(self): assert str(e) == "__repr__ returned non-string (type int)" +def test_numeric_str_uses_overridden_repr(): + class IntSubclass(int): + def __repr__(self): + return "IntSubclass" + + class FloatSubclass(float): + def __repr__(self): + return "FloatSubclass" + + class ComplexSubclass(complex): + def __repr__(self): + return "ComplexSubclass" + + for numeric_type in (int, float, bool, complex): + assert numeric_type.__str__ is object.__str__ + + for value, expected in ( + (IntSubclass(), "IntSubclass"), + (FloatSubclass(), "FloatSubclass"), + (ComplexSubclass(), "ComplexSubclass"), + ): + assert repr(value) == expected + assert str(value) == expected + + assert str(True) == "True" + assert repr(True) == "True" + + def test_repr_deep_userlist_raises_recursion_error(): a = UserList([]) for _ in range(REPR_RECURSION_LIMIT + 10): diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/bool/BoolBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/bool/BoolBuiltins.java index f0f7a60502..2b180c11c8 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/bool/BoolBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/bool/BoolBuiltins.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. * Copyright (c) 2014, Regents of the University of California * * All rights reserved. @@ -76,7 +76,7 @@ public static boolean bool(VirtualFrame frame, @SuppressWarnings("unused") Objec } } - @Slot(value = SlotKind.tp_str, isComplex = true) + @Slot(value = SlotKind.tp_repr, isComplex = true) @TypeSystemReference(PythonIntegerTypes.class) @GenerateNodeFactory abstract static class StrNode extends PythonUnaryBuiltinNode { @@ -96,11 +96,6 @@ public static TruffleString doPInt(PInt self) { } } - @Slot(value = SlotKind.tp_repr, isComplex = true) - @GenerateNodeFactory - abstract static class RepNode extends StrNode { - } - @Slot(value = SlotKind.nb_and, isComplex = true) @GenerateNodeFactory abstract static class AndNode extends BinaryOpBuiltinNode { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/floats/FloatBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/floats/FloatBuiltins.java index 48e340f7ad..f0fc8e72c2 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/floats/FloatBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/floats/FloatBuiltins.java @@ -325,7 +325,7 @@ protected static boolean isPrimitiveFloat(Node inliningTarget, Object cls, Built } } - @Slot(value = SlotKind.tp_str, isComplex = true) + @Slot(value = SlotKind.tp_repr, isComplex = true) @GenerateNodeFactory public abstract static class StrNode extends AbstractNumericUnaryBuiltin { public static final Spec spec = new Spec(' ', '>', Spec.NONE, false, Spec.UNSPECIFIED, Spec.NONE, 0, 'r'); @@ -343,11 +343,6 @@ public static TruffleString doFormat(double d, FloatFormatter f) { } } - @Slot(value = SlotKind.tp_repr, isComplex = true) - @GenerateNodeFactory - abstract static class ReprNode extends StrNode { - } - @Builtin(name = J___FORMAT__, minNumOfPositionalArgs = 2, parameterNames = {"$self", "format_spec"}) @ArgumentClinic(name = "format_spec", conversion = ClinicConversion.TString) @GenerateNodeFactory diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/ints/IntBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/ints/IntBuiltins.java index 477dfe3e70..a1c1e600da 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/ints/IntBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/ints/IntBuiltins.java @@ -2534,7 +2534,7 @@ static boolean toBoolean(PInt self) { } } - @Slot(value = SlotKind.tp_str, isComplex = true) + @Slot(value = SlotKind.tp_repr, isComplex = true) @GenerateNodeFactory @TypeSystemReference(PythonIntegerTypes.class) abstract static class StrNode extends PythonUnaryBuiltinNode { @@ -2592,11 +2592,6 @@ private static int positiveBitLength(PInt self) { } } - @Slot(value = SlotKind.tp_repr, isComplex = true) - @GenerateNodeFactory - abstract static class ReprNode extends StrNode { - } - @Builtin(name = J___FORMAT__, minNumOfPositionalArgs = 2, parameterNames = {"$self", "format_spec"}) @ArgumentClinic(name = "format_spec", conversion = ClinicConversion.TString) @GenerateNodeFactory From aebd96a32352936013e472063a1d52c4c6021480 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Fri, 17 Jul 2026 11:08:00 +0200 Subject: [PATCH 4/7] [GR-76941] Support installed CPython layout in benchmark builds --- .../mx_graalpython_python_benchmarks.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/mx.graalpython/mx_graalpython_python_benchmarks.py b/mx.graalpython/mx_graalpython_python_benchmarks.py index 330e5b4725..f8369e981d 100644 --- a/mx.graalpython/mx_graalpython_python_benchmarks.py +++ b/mx.graalpython/mx_graalpython_python_benchmarks.py @@ -134,15 +134,26 @@ def add_cpython_build_env(env=None): if python3_home := os.environ.get("PYTHON3_HOME"): - include_dir = join(python3_home, "Include") - if os.path.exists(join(include_dir, "Python.h")): + checkout_include = join(python3_home, "Include") + installed_includes = sorted(glob.glob(join(python3_home, os.pardir, "include", "python*"))) + include_dir = next( + (path for path in [checkout_include, *installed_includes] if os.path.exists(join(path, "Python.h"))), + None, + ) + if include_dir: env = env.copy() if env is not None else os.environ.copy() - python_includes = os.pathsep.join([include_dir, python3_home]) + include_dirs = [include_dir] + if include_dir == checkout_include: + include_dirs.append(python3_home) + python_includes = os.pathsep.join(include_dirs) include_flags = " ".join(f"-I{path}" for path in python_includes.split(os.pathsep)) env["CPATH"] = python_includes + (os.pathsep + env["CPATH"] if env.get("CPATH") else "") for key in ["CFLAGS", "CPPFLAGS", "CXXFLAGS"]: env[key] = include_flags + (" " + env[key] if env.get(key) else "") - env["LIBRARY_PATH"] = python3_home + (os.pathsep + env["LIBRARY_PATH"] if env.get("LIBRARY_PATH") else "") + lib_dir = join(python3_home, os.pardir, "lib") + if not os.path.isdir(lib_dir): + lib_dir = python3_home + env["LIBRARY_PATH"] = lib_dir + (os.pathsep + env["LIBRARY_PATH"] if env.get("LIBRARY_PATH") else "") return env From 911fceeb25ae3cfeafed0c091ef40748a64e014e Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Fri, 17 Jul 2026 11:27:05 +0200 Subject: [PATCH 5/7] [GR-76933] Ignore pending polyglot threads in test same as in launcher. --- .../graal/python/test/integration/PythonTests.java | 12 ++++++++---- .../test/integration/module/ThreadPoolTests.java | 5 +++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test.integration/src/com/oracle/graal/python/test/integration/PythonTests.java b/graalpython/com.oracle.graal.python.test.integration/src/com/oracle/graal/python/test/integration/PythonTests.java index e5874b01fd..d1fd62e8dd 100644 --- a/graalpython/com.oracle.graal.python.test.integration/src/com/oracle/graal/python/test/integration/PythonTests.java +++ b/graalpython/com.oracle.graal.python.test.integration/src/com/oracle/graal/python/test/integration/PythonTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. * Copyright (c) 2013, Regents of the University of California * * All rights reserved. @@ -76,17 +76,21 @@ public static Context enterContext(Map options, String[] args) { return context; } - private static void closeContext(Context ctxt) { + private static void closeContext(Context ctxt, boolean cancelIfExecuting) { try { ctxt.leave(); } catch (RuntimeException e) { } - ctxt.close(); + ctxt.close(cancelIfExecuting); } public static void closeContext() { + closeContext(false); + } + + public static void closeContext(boolean cancelIfExecuting) { if (context != null) { - closeContext(context); + closeContext(context, cancelIfExecuting); context = null; } } diff --git a/graalpython/com.oracle.graal.python.test.integration/src/com/oracle/graal/python/test/integration/module/ThreadPoolTests.java b/graalpython/com.oracle.graal.python.test.integration/src/com/oracle/graal/python/test/integration/module/ThreadPoolTests.java index b98ff80a05..319990c99e 100644 --- a/graalpython/com.oracle.graal.python.test.integration/src/com/oracle/graal/python/test/integration/module/ThreadPoolTests.java +++ b/graalpython/com.oracle.graal.python.test.integration/src/com/oracle/graal/python/test/integration/module/ThreadPoolTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -68,11 +68,12 @@ public void threadPool() { "pool = ThreadPool(2)\n" + "res = list(pool.imap(fun, items))\n" + "pool.close()\n" + + "pool.join()\n" + "\n" + "print(res)\n"; final ByteArrayOutputStream byteArray = new ByteArrayOutputStream(); final PrintStream printStream = new PrintStream(byteArray); - PythonTests.runScript(new HashMap<>(), new String[0], source, printStream, System.err, () -> PythonTests.closeContext()); + PythonTests.runScript(new HashMap<>(), new String[0], source, printStream, System.err, () -> PythonTests.closeContext(true)); String result = byteArray.toString().replaceAll("\r\n", "\n"); assertEquals("[True, True, True, True, True, True, True, True, True, True]\n", result); } From 62978553ad5a634efc219fe2e042c0b82925a328 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Fri, 17 Jul 2026 11:41:59 +0200 Subject: [PATCH 6/7] [GR-77469] Modify racy pipe test (jvm threads may reuse same fds) --- .../src/tests/test_posix.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_posix.py b/graalpython/com.oracle.graal.python.test/src/tests/test_posix.py index 0bd4a412a8..4b3070171d 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_posix.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_posix.py @@ -272,16 +272,17 @@ def test_open_bytes_path(self): def test_failed_read_write_errno(self): read_fd, write_fd = os.pipe() - os.close(read_fd) - os.close(write_fd) - - with self.assertRaises(OSError) as read_error: - os.read(read_fd, 1) - self.assertEqual(errno.EBADF, read_error.exception.errno) + try: + with self.assertRaises(OSError) as read_error: + os.read(write_fd, 1) + self.assertEqual(errno.EBADF, read_error.exception.errno) - with self.assertRaises(OSError) as write_error: - os.write(write_fd, b'x') - self.assertEqual(errno.EBADF, write_error.exception.errno) + with self.assertRaises(OSError) as write_error: + os.write(read_fd, b'x') + self.assertEqual(errno.EBADF, write_error.exception.errno) + finally: + os.close(read_fd) + os.close(write_fd) def test_fd_converter(self): class MyInt(int): From 2cf489c1e8320eb9028fa7e44d1e18ac1c6426bd Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Fri, 17 Jul 2026 13:59:04 +0200 Subject: [PATCH 7/7] Fix style --- graalpython/com.oracle.graal.python.test.integration/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python.test.integration/pom.xml b/graalpython/com.oracle.graal.python.test.integration/pom.xml index 14dd9c7f2c..20803f3050 100644 --- a/graalpython/com.oracle.graal.python.test.integration/pom.xml +++ b/graalpython/com.oracle.graal.python.test.integration/pom.xml @@ -64,7 +64,7 @@ Additionally, one can change the polyglot artifacts version with 17 17 UTF-8 - 25.1.3 + 25.2.3