Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions graalpython/com.oracle.graal.python.test/src/tests/test_msvcrt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Copyright (c) 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
#
# Subject to the condition set forth below, permission is hereby granted to any
# person obtaining a copy of this software, associated documentation and/or
# data (collectively the "Software"), free of charge and under any and all
# copyright rights in the Software, and any and all patent rights owned or
# freely licensable by each licensor hereunder covering either (i) the
# unmodified Software as contributed to or provided by such licensor, or (ii)
# the Larger Works (as defined below), to deal in both
#
# (a) the Software, and
#
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
# one is included with the Software each a "Larger Work" to which the Software
# is contributed by such licensors),
#
# without restriction, including without limitation the rights to copy, create
# derivative works of, display, perform, and distribute the Software and make,
# use, sell, offer for sale, import, export, have made, and have sold the
# Software and the Larger Work(s), and to sublicense the foregoing rights on
# either these or other terms.
#
# This license is subject to the following condition:
#
# The above copyright notice and either this complete permission notice or at a
# minimum a reference to the UPL must be included in all copies or substantial
# portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import os
import sys
import tempfile
import unittest


@unittest.skipUnless(sys.platform == "win32", "Windows only")
class MsvcrtTests(unittest.TestCase):
def test_setmode(self):
import msvcrt

with tempfile.TemporaryFile() as file:
fd = file.fileno()
self.assertEqual(msvcrt.setmode(fd, os.O_BINARY), os.O_BINARY)
self.assertEqual(msvcrt.setmode(fd, os.O_TEXT), os.O_BINARY)
self.assertEqual(msvcrt.setmode(fd, os.O_BINARY), os.O_TEXT)

def test_setmode_invalid_fd(self):
import msvcrt

with self.assertRaises(OSError):
msvcrt.setmode(-1, os.O_BINARY)
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ test.test_dbm.TestCase_dumb.test_keys @ darwin-arm64,linux-aarch64,linux-aarch64
test.test_dbm.TestCase_dumb.test_open_with_bytes @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github
test.test_dbm.TestCase_dumb.test_open_with_pathlib_path @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github
test.test_dbm.TestCase_dumb.test_open_with_pathlib_path_bytes @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github
test.test_dbm.WhichDBTestCase.test_whichdb @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github
test.test_dbm.WhichDBTestCase.test_whichdb @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github
# GR-78201
!test.test_dbm.WhichDBTestCase.test_whichdb @ win32-AMD64,win32-AMD64-github

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,29 @@ protected ArgumentClinicProvider getArgumentClinic() {
}
}

@Builtin(name = "setmode", minNumOfPositionalArgs = 2, parameterNames = {"fd", "flags"})
@ArgumentClinic(name = "fd", conversion = ArgumentClinic.ClinicConversion.Int)
@ArgumentClinic(name = "flags", conversion = ArgumentClinic.ClinicConversion.Int)
@GenerateNodeFactory
public abstract static class SetModeNode extends PythonBinaryClinicBuiltinNode {
@Specialization
int setMode(VirtualFrame frame, int fd, int flags,
@Bind Node inliningTarget,
@CachedLibrary("getPosixSupport()") PosixSupportLibrary posixLib,
@Cached PConstructAndRaiseNode.Lazy constructAndRaiseNode) {
try {
return posixLib.setMode(getPosixSupport(), fd, flags);
} catch (PosixSupportLibrary.PosixException e) {
throw constructAndRaiseNode.get(inliningTarget).raiseOSErrorFromPosixException(frame, e);
}
}

@Override
protected ArgumentClinicProvider getArgumentClinic() {
return MsvcrtModuleBuiltinsClinicProviders.SetModeNodeClinicProviderGen.INSTANCE;
}
}

@Builtin(name = "get_osfhandle", minNumOfPositionalArgs = 1, parameterNames = {"fd"})
@ArgumentClinic(name = "fd", conversion = ArgumentClinic.ClinicConversion.Int)
@GenerateNodeFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,20 @@ public int openOsfHandle(long handle, int flags) throws UnsupportedPosixFeatureE
throw createUnsupportedFeature("open_osfhandle");
}

@ExportMessage
public int setMode(int fd, int mode) throws PosixException {
int binary = PosixConstants.O_BINARY.getValueIfDefined();
int text = PosixConstants.O_TEXT.getValueIfDefined();
if (mode != binary && mode != text) {
throw posixException(OSErrorEnum.EINVAL);
}
int previousMode = setModeTracked(fd, mode);
if (previousMode < 0) {
throw posixException(OSErrorEnum.EBADF);
}
return previousMode;
}

@ExportMessage(name = "pipe")
public int[] pipeMessage(@Shared("eq") @Cached TruffleString.EqualNode eqNode) throws PosixException {
// TODO: will merge with super.pipe once the super class is merged with this class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,17 @@ final int openOsfHandle(long handle, int flags,
}
}

@ExportMessage
final int setMode(int fd, int mode,
@CachedLibrary("this.delegate") PosixSupportLibrary lib) throws PosixException {
logEnter("setMode", "%d, %d", fd, mode);
try {
return logExit("setMode", "%d", lib.setMode(delegate, fd, mode));
} catch (PosixException e) {
throw logException("setMode", e);
}
}

@ExportMessage
final int[] pipe(
@CachedLibrary("this.delegate") PosixSupportLibrary lib) throws PosixException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ abstract static class PosixNativeFunctionInvoker {
@DowncallSignature(returnType = SINT32, argumentTypes = {SINT64, SINT32})
abstract int call_open_osfhandle(long handle, int flags);

@DowncallSignature(returnType = SINT32, argumentTypes = {SINT32, SINT32})
abstract int call_setmode(int fd, int mode);

@DowncallSignature(returnType = SINT32, argumentTypes = {POINTER})
abstract int call_pipe2(long pipefd);

Expand Down Expand Up @@ -864,6 +867,15 @@ public int openOsfHandle(long handle, int flags) throws PosixException {
return fd;
}

@ExportMessage
public int setMode(int fd, int mode) throws PosixException {
int previousMode = posixNativeFunctionInvoker.call_setmode(fd, mode);
if (previousMode < 0) {
throw getErrnoAndThrowPosixException();
}
return previousMode;
}

@ExportMessage
public int[] pipe() throws PosixException {
int[] fds = new int[2];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 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
Expand Down Expand Up @@ -82,6 +82,7 @@ abstract class PosixResources extends PosixSupport {
/** Context-local file-descriptor mappings and PID mappings */
protected final PythonContext context;
private final SortedMap<Integer, ChannelWrapper> files;
private final Map<Integer, Integer> fileModes;
protected final Map<Integer, String> filePaths;
private final List<Process> children;
private final Map<String, Integer> inodes;
Expand Down Expand Up @@ -222,12 +223,16 @@ void setNewChannel(OutputStream outputStream) {
protected PosixResources(PythonContext context) {
this.context = context;
files = Collections.synchronizedSortedMap(new TreeMap<>());
fileModes = Collections.synchronizedMap(new HashMap<>());
filePaths = Collections.synchronizedMap(new HashMap<>());
children = Collections.synchronizedList(new ArrayList<>());

files.put(FD_STDIN, ChannelWrapper.createForStandardStream());
files.put(FD_STDOUT, ChannelWrapper.createForStandardStream());
files.put(FD_STDERR, ChannelWrapper.createForStandardStream());
fileModes.put(FD_STDIN, defaultFileMode());
fileModes.put(FD_STDOUT, defaultFileMode());
fileModes.put(FD_STDERR, defaultFileMode());
if (PythonLanguage.getPythonOS() == PythonOS.PLATFORM_WIN32) {
filePaths.put(FD_STDIN, "STDIN");
filePaths.put(FD_STDOUT, "STDOUT");
Expand Down Expand Up @@ -260,6 +265,7 @@ private void addFD(int fd, Channel channel) {
@TruffleBoundary
private void addFD(int fd, Channel channel, String path) {
files.put(fd, new ChannelWrapper(channel));
fileModes.put(fd, defaultFileMode());
if (path != null) {
filePaths.put(fd, path);
}
Expand All @@ -278,6 +284,7 @@ protected boolean removeFD(int fd) throws IOException {
}

files.remove(fd);
fileModes.remove(fd);
filePaths.remove(fd);
}
return true;
Expand All @@ -295,12 +302,23 @@ private void dupFD(int fd1, int fd2) {
if (channelWrapper != null) {
channelWrapper.cnt += 1;
files.put(fd2, channelWrapper);
fileModes.put(fd2, fileModes.get(fd1));
if (path != null) {
filePaths.put(fd2, path);
}
}
}

private static int defaultFileMode() {
return PythonLanguage.getPythonOS() == PythonOS.PLATFORM_WIN32 ? PosixConstants.O_BINARY.getValueIfDefined() : 0;
}

@TruffleBoundary
protected int setModeTracked(int fd, int mode) {
Integer previousMode = fileModes.replace(fd, mode);
return previousMode == null ? -1 : previousMode;
}

protected boolean isStandardStream(int fd) {
ChannelWrapper channelWrapper = files.get(fd);
return channelWrapper != null && channelWrapper.isStandardStream;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ public abstract class PosixSupportLibrary extends Library {

public abstract int openOsfHandle(Object receiver, long handle, int flags) throws PosixException;

public abstract int setMode(Object receiver, int fd, int mode) throws PosixException;

public abstract int[] pipe(Object receiver) throws PosixException;

public abstract SelectResult select(Object receiver, int[] readfds, int[] writefds, int[] errorfds, Timeval timeout) throws PosixException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,13 @@ final int openOsfHandle(long handle, int flags,
return nativeLib.openOsfHandle(nativePosixSupport, handle, flags);
}

@ExportMessage
final int setMode(int fd, int mode,
@CachedLibrary("this.nativePosixSupport") PosixSupportLibrary nativeLib) throws PosixException {
checkNotInPreInitialization();
return nativeLib.setMode(nativePosixSupport, fd, mode);
}

@ExportMessage
final int[] pipe(@CachedLibrary("this.nativePosixSupport") PosixSupportLibrary nativeLib) throws PosixException {
checkNotInPreInitialization();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ def _crash(delay=None):
faulthandler._sigsegv()


# Self-signals are only supported in native standalones (GR-74243); skip these
# tests on JVM coverage runs rather than re-tagging them (GR-77822).
def _is_native():
try:
return __graalpython__.is_native
except NameError:
return True # CPython


def _crash_with_data(data):
"""Induces a segfault with dummy data in input."""
_crash()
Expand Down Expand Up @@ -155,6 +164,7 @@ def test_crash_at_task_unpickle(self):
# Check problem occurring while unpickling a task on workers
self._check_crash(BrokenProcessPool, id, CrashAtUnpickle())

@unittest.skipUnless(_is_native(), "self-signals are only supported in native standalone")
def test_crash_during_func_exec_on_worker(self):
# Check problem occurring during func execution on workers
self._check_crash(BrokenProcessPool, _crash)
Expand All @@ -167,6 +177,7 @@ def test_error_during_func_exec_on_worker(self):
# Check problem occurring during func execution on workers
self._check_crash(RuntimeError, _raise_error, RuntimeError)

@unittest.skipUnless(_is_native(), "self-signals are only supported in native standalone")
def test_crash_during_result_pickle_on_worker(self):
# Check problem occurring while pickling a task result
# on workers
Expand Down Expand Up @@ -197,6 +208,7 @@ def test_exit_during_result_unpickle_in_result_handler(self):
# the result_handler thread
self._check_crash(BrokenProcessPool, _return_instance, ExitAtUnpickle)

@unittest.skipUnless(_is_native(), "self-signals are only supported in native standalone")
def test_shutdown_deadlock(self):
# Test that the pool calling shutdown do not cause deadlock
# if a worker fails after the shutdown call.
Expand Down Expand Up @@ -235,6 +247,7 @@ def test_shutdown_deadlock_pickle(self):
# dangling threads
executor_manager.join()

@unittest.skipUnless(_is_native(), "self-signals are only supported in native standalone")
def test_crash_big_data(self):
# Test that there is a clean exception instad of a deadlock when a
# child process crashes while some data is being written into the
Expand Down
19 changes: 19 additions & 0 deletions graalpython/python-libposix/src/posix.c
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,17 @@ GP_EXPORT int32_t call_open_osfhandle(int64_t handle, int32_t flags) {
return open_osfhandle_noraise(handle, flags);
}

GP_EXPORT int32_t call_setmode(int32_t fd, int32_t mode) {
int previous_mode;
BEGIN_SUPPRESS_IPH
previous_mode = _setmode(fd, mode);
END_SUPPRESS_IPH
if (previous_mode < 0) {
capture_errno();
}
return previous_mode;
}

GP_EXPORT int32_t call_pipe2(int32_t *pipefd) {
int result = _pipe(pipefd, 8192, _O_BINARY | _O_NOINHERIT);
if (result < 0) {
Expand Down Expand Up @@ -2170,6 +2181,14 @@ int32_t call_open_osfhandle(int64_t handle, int32_t flags) {
return -1;
}

int32_t call_setmode(int32_t fd, int32_t mode) {
(void) fd;
(void) mode;
errno = ENOSYS;
capture_errno();
return -1;
}

int32_t call_pipe2(int32_t *pipefd) {
#ifdef __gnu_linux__
CAPTURE_ERRNO_AND_RETURN(-1, pipe2(pipefd, O_CLOEXEC));
Expand Down
5 changes: 5 additions & 0 deletions mx.graalpython/mx_graalpython.py
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,11 @@ def run_python_unittests(python_binary, args=None, paths=None, exclude=None, env
if GITHUB_CI:
parallel = 0

if mx_gate.get_jacoco_agent_args():
# JaCoCo execution data is appended to a shared file and cannot be
# safely written by multiple instrumented JVM test workers at once.
parallel = 1

parallelism = str(min(os.cpu_count() or 1, parallel))

args = args or []
Expand Down
Loading