Skip to content

Commit 3fb8ba9

Browse files
committed
[GR-76039] Fix Windows native long buffer formats
PullRequest: graalpython/4578
2 parents 55d6f65 + 444e28c commit 3fb8ba9

5 files changed

Lines changed: 66 additions & 27 deletions

File tree

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_shutdown.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import sys
4848
from unittest import skipIf
4949
from tests import compile_module_from_string
50+
from tests.util import run_subprocess_with_graalpy_startup_retry
5051

5152
GRAALPY = sys.implementation.name == 'graalpy'
5253

@@ -65,7 +66,7 @@
6566

6667
# Test that running Py_DECREF in native global destructor doesn't crash
6768
def test_normal_exit():
68-
subprocess.run(COMMAND, check=True, env=ENV)
69+
run_subprocess_with_graalpy_startup_retry(COMMAND, check=True, env=ENV)
6970

7071

7172
def test_sigterm():

graalpython/com.oracle.graal.python.test/src/tests/test_memoryview.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
# Copyright (c) 2018, 2024, Oracle and/or its affiliates.
1+
# Copyright (c) 2018, 2026, Oracle and/or its affiliates.
22
# Copyright (C) 1996-2017 Python Software Foundation
33
#
44
# Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
55

66
import sys
7+
import struct
78
import unittest
89

910
from tests.util import assert_raises
@@ -93,15 +94,16 @@ def test_slice():
9394
assert e1 == e2
9495

9596
def test_unpack():
97+
long_bytes = b'\xaa' * struct.calcsize('l')
9698
assert memoryview(b'\xaa')[0] == 170
9799
assert memoryview(b'\xaa').cast('B')[0] == 170
98100
assert memoryview(b'\xaa').cast('b')[0] == -86
99101
assert memoryview(b'\xaa\xaa').cast('H')[0] == 43690
100102
assert memoryview(b'\xaa\xaa').cast('h')[0] == -21846
101103
assert memoryview(b'\xaa\xaa\xaa\xaa').cast('I')[0] == 2863311530
102104
assert memoryview(b'\xaa\xaa\xaa\xaa').cast('i')[0] == -1431655766
103-
assert memoryview(b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa').cast('L')[0] == 12297829382473034410
104-
assert memoryview(b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa').cast('l')[0] == -6148914691236517206
105+
assert memoryview(long_bytes).cast('L')[0] == struct.unpack('L', long_bytes)[0]
106+
assert memoryview(long_bytes).cast('l')[0] == struct.unpack('l', long_bytes)[0]
105107
assert memoryview(b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa').cast('Q')[0] == 12297829382473034410
106108
assert memoryview(b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa').cast('q')[0] == -6148914691236517206
107109
assert memoryview(b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa').cast('N')[0] == 12297829382473034410
@@ -114,6 +116,7 @@ def test_unpack():
114116
assert memoryview(b'\xaa').cast('c')[0] == b'\xaa'
115117

116118
def test_pack():
119+
long_bytes = b'\xaa' * struct.calcsize('l')
117120
b = bytearray(1)
118121
memoryview(b).cast('B')[0] = 170
119122
assert b == b'\xaa'
@@ -132,12 +135,12 @@ def test_pack():
132135
b = bytearray(4)
133136
memoryview(b).cast('i')[0] = -1431655766
134137
assert b == b'\xaa\xaa\xaa\xaa'
135-
b = bytearray(8)
136-
memoryview(b).cast('L')[0] = 12297829382473034410
137-
assert b == b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'
138-
b = bytearray(8)
139-
memoryview(b).cast('l')[0] = -6148914691236517206
140-
assert b == b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'
138+
b = bytearray(struct.calcsize('L'))
139+
memoryview(b).cast('L')[0] = struct.unpack('L', long_bytes)[0]
140+
assert b == long_bytes
141+
b = bytearray(struct.calcsize('l'))
142+
memoryview(b).cast('l')[0] = struct.unpack('l', long_bytes)[0]
143+
assert b == long_bytes
141144
b = bytearray(8)
142145
memoryview(b).cast('Q')[0] = 12297829382473034410
143146
assert b == b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'

graalpython/com.oracle.graal.python.test/src/tests/test_subprocess.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from subprocess import CalledProcessError
1212
from tempfile import mkdtemp
1313

14+
from tests.util import run_subprocess_with_graalpy_startup_retry
15+
1416
POSIX_BACKEND_IS_JAVA = sys.implementation.name == "graalpy" and __graalpython__.posix_module_backend() == 'java'
1517

1618
def test_os_pipe():
@@ -228,54 +230,52 @@ def test_subprocess_inherits_environ(self):
228230
@unittest.skipIf(sys.platform == 'win32', "TODO the cmd replacement breaks the test")
229231
def test_graal_python_args(self):
230232
if sys.implementation.name == "graalpy":
231-
import subprocess
232-
233233
def env_with_graal_python_args(args):
234234
env = os.environ.copy()
235235
env["GRAAL_PYTHON_ARGS"] = args
236236
return env
237237

238238
env = env_with_graal_python_args("-c 12")
239-
result = subprocess.run([sys.executable], env=env)
240-
self.assertEqual(0, result.returncode)
239+
result = run_subprocess_with_graalpy_startup_retry([sys.executable], env=env, text=True)
240+
self.assertEqual(0, result.returncode, result.stderr)
241241

242242
env = env_with_graal_python_args("-c 'print(12)'")
243-
result = subprocess.check_output([sys.executable], env=env, text=True)
243+
result = run_subprocess_with_graalpy_startup_retry([sys.executable], env=env, text=True, check=True).stdout
244244
self.assertEqual('12\n', result)
245245

246246
env = env_with_graal_python_args("""-c 'print("Hello world")'""")
247-
result = subprocess.check_output([sys.executable], env=env, text=True)
247+
result = run_subprocess_with_graalpy_startup_retry([sys.executable], env=env, text=True, check=True).stdout
248248
self.assertEqual('Hello world\n', result)
249249

250250
env = env_with_graal_python_args("""-c ""'print("Hello world")'""""")
251-
result = subprocess.check_output([sys.executable], env=env, text=True)
251+
result = run_subprocess_with_graalpy_startup_retry([sys.executable], env=env, text=True, check=True).stdout
252252
self.assertEqual('Hello world\n', result)
253253

254254
env = env_with_graal_python_args(r"""-c 'print(\'"Hello world"\')'""")
255-
result = subprocess.check_output([sys.executable], env=env, text=True)
255+
result = run_subprocess_with_graalpy_startup_retry([sys.executable], env=env, text=True, check=True).stdout
256256
self.assertEqual('"Hello world"\n', result)
257257

258258
env = env_with_graal_python_args("""\v-c\vprint('"Hello world"')""")
259-
result = subprocess.check_output([sys.executable], env=env, text=True)
259+
result = run_subprocess_with_graalpy_startup_retry([sys.executable], env=env, text=True, check=True).stdout
260260
self.assertEqual('"Hello world"\n', result)
261261

262262
env = env_with_graal_python_args("""\v-c\vprint('Hello', "world")""")
263-
result = subprocess.check_output([sys.executable], env=env, text=True)
263+
result = run_subprocess_with_graalpy_startup_retry([sys.executable], env=env, text=True, check=True).stdout
264264
self.assertEqual('Hello world\n', result)
265265

266266
# check that the subprocess receives the args and thus it should fail because it recurses
267267
args = """\v-c\vimport os\nprint(os.environ.get("GRAAL_PYTHON_ARGS"))"""
268268
env = env_with_graal_python_args(args)
269-
result = subprocess.check_output([sys.executable], env=env, text=True)
269+
result = run_subprocess_with_graalpy_startup_retry([sys.executable], env=env, text=True, check=True).stdout
270270
self.assertEqual(f"{args}\n", result)
271271

272272
# check that the subprocess does not receive the args when we end with \v
273273
env = env_with_graal_python_args("""\v-c\vimport os\nprint(os.environ.get("GRAAL_PYTHON_ARGS"))\v""")
274-
result = subprocess.check_output([sys.executable], env=env, text=True)
274+
result = run_subprocess_with_graalpy_startup_retry([sys.executable], env=env, text=True, check=True).stdout
275275
self.assertEqual('None\n', result)
276276

277277
# check that the subprocess receives an empty arg
278278
args = """\v-c\vimport sys\nprint(repr(sys.argv))\va1\v\va3"""
279279
env = env_with_graal_python_args(args)
280-
result = subprocess.check_output([sys.executable], env=env, text=True)
280+
result = run_subprocess_with_graalpy_startup_retry([sys.executable], env=env, text=True, check=True).stdout
281281
self.assertEqual("['-c', 'a1', '', 'a3']\n", result)

graalpython/com.oracle.graal.python.test/src/tests/util.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,13 @@
3636
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3737
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
# SOFTWARE.
39+
import subprocess
3940
import sys
41+
import time
4042
import unittest
4143

4244
IS_BYTECODE_DSL = sys.implementation.name == 'graalpy' and __graalpython__.is_bytecode_dsl_interpreter
45+
TRANSIENT_GRAALPY_STARTUP_BLOCKING_IO = "ERROR: BlockingIOError: [Errno 11] Resource temporarily unavailable"
4346

4447

4548
def _is_sandboxed():
@@ -108,3 +111,31 @@ def assert_raises(err, fn, *args, err_check=None, **kwargs):
108111
else:
109112
assert err_check(e)
110113
assert raised
114+
115+
116+
def _contains_transient_graalpy_startup_blocking_io(output):
117+
if output is None:
118+
return False
119+
if isinstance(output, bytes):
120+
return TRANSIENT_GRAALPY_STARTUP_BLOCKING_IO.encode() in output
121+
return TRANSIENT_GRAALPY_STARTUP_BLOCKING_IO in output
122+
123+
124+
def run_subprocess_with_graalpy_startup_retry(args, *, attempts=5, retry_delay=0.2, **kwargs):
125+
unsupported_kwargs = {"stdout", "stderr", "capture_output"} & kwargs.keys()
126+
if unsupported_kwargs:
127+
raise TypeError(f"unsupported keyword arguments: {', '.join(sorted(unsupported_kwargs))}")
128+
check = kwargs.pop("check", False)
129+
for attempt in range(attempts):
130+
result = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
131+
if result.returncode == 0 or not (
132+
_contains_transient_graalpy_startup_blocking_io(result.stdout) or
133+
_contains_transient_graalpy_startup_blocking_io(result.stderr)
134+
):
135+
break
136+
if attempt + 1 < attempts:
137+
time.sleep(retry_delay)
138+
retry_delay *= 2
139+
if check and result.returncode != 0:
140+
raise subprocess.CalledProcessError(result.returncode, result.args, output=result.stdout, stderr=result.stderr)
141+
return result

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/BufferFormat.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -44,14 +44,16 @@
4444
import static com.oracle.graal.python.util.PythonUtils.toTruffleStringUncached;
4545
import static com.oracle.graal.python.util.PythonUtils.tsLiteral;
4646

47+
import com.oracle.graal.python.PythonLanguage;
48+
import com.oracle.graal.python.annotations.PythonOS;
4749
import com.oracle.truffle.api.strings.TruffleString;
4850

4951
/**
5052
* This enum represents formats used by {@code array} and {@code memoryview}. The correspondence
5153
* between the type specifier string and {@link BufferFormat} is not 1 to 1. Multiple specifiers may
52-
* represent the same format (e.g. both {@code L} and {@code Q} on 64 bit platforms both map to
53-
* {@link BufferFormat#UINT_64}) format. Therefore it is necessary to keep the original specifier
54-
* string around for error messages.
54+
* represent the same format (e.g. both {@code L} and {@code Q} on 64 bit non-Windows platforms both
55+
* map to {@link BufferFormat#UINT_64}) format. Therefore it is necessary to keep the original
56+
* specifier string around for error messages.
5557
*/
5658
public enum BufferFormat {
5759
UINT_8(1, 0, "B"),
@@ -140,9 +142,11 @@ private static BufferFormat fromCharCommon(char fmtchar) {
140142
case 'i':
141143
return INT_32;
142144
case 'L':
145+
return PythonLanguage.getPythonOS() == PythonOS.PLATFORM_WIN32 ? UINT_32 : UINT_64;
143146
case 'Q':
144147
return UINT_64;
145148
case 'l':
149+
return PythonLanguage.getPythonOS() == PythonOS.PLATFORM_WIN32 ? INT_32 : INT_64;
146150
case 'q':
147151
return INT_64;
148152
case 'e':

0 commit comments

Comments
 (0)