mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Merge pull request #6958 from youknowone/os-posix
Update os from v3.14.2 and fix nt,posix,_os
This commit is contained in:
15 files changed
+804
-399
No files matched your search
@@ -10,7 +10,7 @@ This exports:
|
||||
- os.extsep is the extension separator (always '.')
|
||||
- os.altsep is the alternate pathname separator (None or '/')
|
||||
- os.pathsep is the component separator used in $PATH etc
|
||||
- os.linesep is the line separator in text files ('\r' or '\n' or '\r\n')
|
||||
- os.linesep is the line separator in text files ('\n' or '\r\n')
|
||||
- os.defpath is the default search path for executables
|
||||
- os.devnull is the file path of the null device ('/dev/null', etc.)
|
||||
|
||||
@@ -64,6 +64,10 @@ if 'posix' in _names:
|
||||
from posix import _have_functions
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
from posix import _create_environ
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import posix
|
||||
__all__.extend(_get_exports_list(posix))
|
||||
@@ -88,6 +92,10 @@ elif 'nt' in _names:
|
||||
from nt import _have_functions
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
from nt import _create_environ
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
else:
|
||||
raise ImportError('no os specific module found')
|
||||
@@ -366,61 +374,45 @@ def walk(top, topdown=True, onerror=None, followlinks=False):
|
||||
# minor reason when (say) a thousand readable directories are still
|
||||
# left to visit.
|
||||
try:
|
||||
scandir_it = scandir(top)
|
||||
with scandir(top) as entries:
|
||||
for entry in entries:
|
||||
try:
|
||||
if followlinks is _walk_symlinks_as_files:
|
||||
is_dir = entry.is_dir(follow_symlinks=False) and not entry.is_junction()
|
||||
else:
|
||||
is_dir = entry.is_dir()
|
||||
except OSError:
|
||||
# If is_dir() raises an OSError, consider the entry not to
|
||||
# be a directory, same behaviour as os.path.isdir().
|
||||
is_dir = False
|
||||
|
||||
if is_dir:
|
||||
dirs.append(entry.name)
|
||||
else:
|
||||
nondirs.append(entry.name)
|
||||
|
||||
if not topdown and is_dir:
|
||||
# Bottom-up: traverse into sub-directory, but exclude
|
||||
# symlinks to directories if followlinks is False
|
||||
if followlinks:
|
||||
walk_into = True
|
||||
else:
|
||||
try:
|
||||
is_symlink = entry.is_symlink()
|
||||
except OSError:
|
||||
# If is_symlink() raises an OSError, consider the
|
||||
# entry not to be a symbolic link, same behaviour
|
||||
# as os.path.islink().
|
||||
is_symlink = False
|
||||
walk_into = not is_symlink
|
||||
|
||||
if walk_into:
|
||||
walk_dirs.append(entry.path)
|
||||
except OSError as error:
|
||||
if onerror is not None:
|
||||
onerror(error)
|
||||
continue
|
||||
|
||||
cont = False
|
||||
with scandir_it:
|
||||
while True:
|
||||
try:
|
||||
try:
|
||||
entry = next(scandir_it)
|
||||
except StopIteration:
|
||||
break
|
||||
except OSError as error:
|
||||
if onerror is not None:
|
||||
onerror(error)
|
||||
cont = True
|
||||
break
|
||||
|
||||
try:
|
||||
if followlinks is _walk_symlinks_as_files:
|
||||
is_dir = entry.is_dir(follow_symlinks=False) and not entry.is_junction()
|
||||
else:
|
||||
is_dir = entry.is_dir()
|
||||
except OSError:
|
||||
# If is_dir() raises an OSError, consider the entry not to
|
||||
# be a directory, same behaviour as os.path.isdir().
|
||||
is_dir = False
|
||||
|
||||
if is_dir:
|
||||
dirs.append(entry.name)
|
||||
else:
|
||||
nondirs.append(entry.name)
|
||||
|
||||
if not topdown and is_dir:
|
||||
# Bottom-up: traverse into sub-directory, but exclude
|
||||
# symlinks to directories if followlinks is False
|
||||
if followlinks:
|
||||
walk_into = True
|
||||
else:
|
||||
try:
|
||||
is_symlink = entry.is_symlink()
|
||||
except OSError:
|
||||
# If is_symlink() raises an OSError, consider the
|
||||
# entry not to be a symbolic link, same behaviour
|
||||
# as os.path.islink().
|
||||
is_symlink = False
|
||||
walk_into = not is_symlink
|
||||
|
||||
if walk_into:
|
||||
walk_dirs.append(entry.path)
|
||||
if cont:
|
||||
continue
|
||||
|
||||
if topdown:
|
||||
# Yield before sub-directory traversal if going top down
|
||||
yield top, dirs, nondirs
|
||||
@@ -774,7 +766,7 @@ class _Environ(MutableMapping):
|
||||
new.update(self)
|
||||
return new
|
||||
|
||||
def _createenviron():
|
||||
def _create_environ_mapping():
|
||||
if name == 'nt':
|
||||
# Where Env Var Names Must Be UPPERCASE
|
||||
def check_str(value):
|
||||
@@ -804,10 +796,25 @@ def _createenviron():
|
||||
encode, decode)
|
||||
|
||||
# unicode environ
|
||||
environ = _createenviron()
|
||||
del _createenviron
|
||||
environ = _create_environ_mapping()
|
||||
del _create_environ_mapping
|
||||
|
||||
|
||||
if _exists("_create_environ"):
|
||||
def reload_environ():
|
||||
data = _create_environ()
|
||||
if name == 'nt':
|
||||
encodekey = environ.encodekey
|
||||
data = {encodekey(key): value
|
||||
for key, value in data.items()}
|
||||
|
||||
# modify in-place to keep os.environb in sync
|
||||
env_data = environ._data
|
||||
env_data.clear()
|
||||
env_data.update(data)
|
||||
|
||||
__all__.append("reload_environ")
|
||||
|
||||
def getenv(key, default=None):
|
||||
"""Get an environment variable, return None if it doesn't exist.
|
||||
The optional second argument can specify an alternate default.
|
||||
|
||||
Vendored
-1
@@ -170,7 +170,6 @@ class GenericTest:
|
||||
os.close(w)
|
||||
self.assertFalse(self.pathmodule.exists(r))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_exists_bool(self):
|
||||
for fd in False, True:
|
||||
with self.assertWarnsRegex(RuntimeWarning,
|
||||
|
||||
Vendored
+223
-80
@@ -104,7 +104,7 @@ requires_splice_pipe = unittest.skipIf(sys.platform.startswith("aix"),
|
||||
|
||||
|
||||
def tearDownModule():
|
||||
asyncio.set_event_loop_policy(None)
|
||||
asyncio.events._set_event_loop_policy(None)
|
||||
|
||||
|
||||
class MiscTests(unittest.TestCase):
|
||||
@@ -187,10 +187,6 @@ class FileTests(unittest.TestCase):
|
||||
os.close(f)
|
||||
self.assertTrue(os.access(os_helper.TESTFN, os.W_OK))
|
||||
|
||||
@unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; BrokenPipeError: (32, 'The process cannot access the file because it is being used by another process. (os error 32)')")
|
||||
@unittest.skipIf(
|
||||
support.is_emscripten, "Test is unstable under Emscripten."
|
||||
)
|
||||
@unittest.skipIf(
|
||||
support.is_wasi, "WASI does not support dup."
|
||||
)
|
||||
@@ -233,6 +229,94 @@ class FileTests(unittest.TestCase):
|
||||
self.assertEqual(type(s), bytes)
|
||||
self.assertEqual(s, b"spam")
|
||||
|
||||
def test_readinto(self):
|
||||
with open(os_helper.TESTFN, "w+b") as fobj:
|
||||
fobj.write(b"spam")
|
||||
fobj.flush()
|
||||
fd = fobj.fileno()
|
||||
os.lseek(fd, 0, 0)
|
||||
# Oversized so readinto without hitting end.
|
||||
buffer = bytearray(7)
|
||||
s = os.readinto(fd, buffer)
|
||||
self.assertEqual(type(s), int)
|
||||
self.assertEqual(s, 4)
|
||||
# Should overwrite the first 4 bytes of the buffer.
|
||||
self.assertEqual(buffer[:4], b"spam")
|
||||
|
||||
# Readinto at EOF should return 0 and not touch buffer.
|
||||
buffer[:] = b"notspam"
|
||||
s = os.readinto(fd, buffer)
|
||||
self.assertEqual(type(s), int)
|
||||
self.assertEqual(s, 0)
|
||||
self.assertEqual(bytes(buffer), b"notspam")
|
||||
s = os.readinto(fd, buffer)
|
||||
self.assertEqual(s, 0)
|
||||
self.assertEqual(bytes(buffer), b"notspam")
|
||||
|
||||
# Readinto a 0 length bytearray when at EOF should return 0
|
||||
self.assertEqual(os.readinto(fd, bytearray()), 0)
|
||||
|
||||
# Readinto a 0 length bytearray with data available should return 0.
|
||||
os.lseek(fd, 0, 0)
|
||||
self.assertEqual(os.readinto(fd, bytearray()), 0)
|
||||
|
||||
@unittest.skipUnless(hasattr(os, 'get_blocking'),
|
||||
'needs os.get_blocking() and os.set_blocking()')
|
||||
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
|
||||
@unittest.skipIf(support.is_emscripten, "set_blocking does not work correctly")
|
||||
def test_readinto_non_blocking(self):
|
||||
# Verify behavior of a readinto which would block on a non-blocking fd.
|
||||
r, w = os.pipe()
|
||||
try:
|
||||
os.set_blocking(r, False)
|
||||
with self.assertRaises(BlockingIOError):
|
||||
os.readinto(r, bytearray(5))
|
||||
|
||||
# Pass some data through
|
||||
os.write(w, b"spam")
|
||||
self.assertEqual(os.readinto(r, bytearray(4)), 4)
|
||||
|
||||
# Still don't block or return 0.
|
||||
with self.assertRaises(BlockingIOError):
|
||||
os.readinto(r, bytearray(5))
|
||||
|
||||
# At EOF should return size 0
|
||||
os.close(w)
|
||||
w = None
|
||||
self.assertEqual(os.readinto(r, bytearray(5)), 0)
|
||||
self.assertEqual(os.readinto(r, bytearray(5)), 0) # Still EOF
|
||||
|
||||
finally:
|
||||
os.close(r)
|
||||
if w is not None:
|
||||
os.close(w)
|
||||
|
||||
def test_readinto_badarg(self):
|
||||
with open(os_helper.TESTFN, "w+b") as fobj:
|
||||
fobj.write(b"spam")
|
||||
fobj.flush()
|
||||
fd = fobj.fileno()
|
||||
os.lseek(fd, 0, 0)
|
||||
|
||||
for bad_arg in ("test", bytes(), 14):
|
||||
with self.subTest(f"bad buffer {type(bad_arg)}"):
|
||||
with self.assertRaises(TypeError):
|
||||
os.readinto(fd, bad_arg)
|
||||
|
||||
with self.subTest("doesn't work on file objects"):
|
||||
with self.assertRaises(TypeError):
|
||||
os.readinto(fobj, bytearray(5))
|
||||
|
||||
# takes two args
|
||||
with self.assertRaises(TypeError):
|
||||
os.readinto(fd)
|
||||
|
||||
# No data should have been read with the bad arguments.
|
||||
buffer = bytearray(4)
|
||||
s = os.readinto(fd, buffer)
|
||||
self.assertEqual(s, 4)
|
||||
self.assertEqual(buffer, b"spam")
|
||||
|
||||
@support.cpython_only
|
||||
# Skip the test on 32-bit platforms: the number of bytes must fit in a
|
||||
# Py_ssize_t type
|
||||
@@ -252,6 +336,29 @@ class FileTests(unittest.TestCase):
|
||||
# operating system is free to return less bytes than requested.
|
||||
self.assertEqual(data, b'test')
|
||||
|
||||
|
||||
@support.cpython_only
|
||||
# Skip the test on 32-bit platforms: the number of bytes must fit in a
|
||||
# Py_ssize_t type
|
||||
@unittest.skipUnless(INT_MAX < PY_SSIZE_T_MAX,
|
||||
"needs INT_MAX < PY_SSIZE_T_MAX")
|
||||
@support.bigmemtest(size=INT_MAX + 10, memuse=1, dry_run=False)
|
||||
def test_large_readinto(self, size):
|
||||
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
|
||||
create_file(os_helper.TESTFN, b'test')
|
||||
|
||||
# Issue #21932: For readinto the buffer contains the length rather than
|
||||
# a length being passed explicitly to read, should still get capped to a
|
||||
# valid size / not raise an OverflowError for sizes larger than INT_MAX.
|
||||
buffer = bytearray(INT_MAX + 10)
|
||||
with open(os_helper.TESTFN, "rb") as fp:
|
||||
length = os.readinto(fp.fileno(), buffer)
|
||||
|
||||
# The test does not try to read more than 2 GiB at once because the
|
||||
# operating system is free to return less bytes than requested.
|
||||
self.assertEqual(length, 4)
|
||||
self.assertEqual(buffer[:4], b'test')
|
||||
|
||||
def test_write(self):
|
||||
# os.write() accepts bytes- and buffer-like objects but not strings
|
||||
fd = os.open(os_helper.TESTFN, os.O_CREAT | os.O_WRONLY)
|
||||
@@ -710,7 +817,7 @@ class StatAttributeTests(unittest.TestCase):
|
||||
self.assertEqual(ctx.exception.errno, errno.EBADF)
|
||||
|
||||
def check_file_attributes(self, result):
|
||||
self.assertTrue(hasattr(result, 'st_file_attributes'))
|
||||
self.assertHasAttr(result, 'st_file_attributes')
|
||||
self.assertTrue(isinstance(result.st_file_attributes, int))
|
||||
self.assertTrue(0 <= result.st_file_attributes <= 0xFFFFFFFF)
|
||||
|
||||
@@ -805,14 +912,28 @@ class UtimeTests(unittest.TestCase):
|
||||
set_time(filename, (atime_ns, mtime_ns))
|
||||
st = os.stat(filename)
|
||||
|
||||
if support_subsecond:
|
||||
self.assertAlmostEqual(st.st_atime, atime_ns * 1e-9, delta=1e-6)
|
||||
self.assertAlmostEqual(st.st_mtime, mtime_ns * 1e-9, delta=1e-6)
|
||||
if support.is_emscripten:
|
||||
# Emscripten timestamps are roundtripped through a 53 bit integer of
|
||||
# nanoseconds. If we want to represent ~50 years which is an 11
|
||||
# digits number of seconds:
|
||||
# 2*log10(60) + log10(24) + log10(365) + log10(60) + log10(50)
|
||||
# is about 11. Because 53 * log10(2) is about 16, we only have 5
|
||||
# digits worth of sub-second precision.
|
||||
# Some day it would be good to fix this upstream.
|
||||
delta=1e-5
|
||||
self.assertAlmostEqual(st.st_atime, atime_ns * 1e-9, delta=1e-5)
|
||||
self.assertAlmostEqual(st.st_mtime, mtime_ns * 1e-9, delta=1e-5)
|
||||
self.assertAlmostEqual(st.st_atime_ns, atime_ns, delta=1e9 * 1e-5)
|
||||
self.assertAlmostEqual(st.st_mtime_ns, mtime_ns, delta=1e9 * 1e-5)
|
||||
else:
|
||||
self.assertEqual(st.st_atime, atime_ns * 1e-9)
|
||||
self.assertEqual(st.st_mtime, mtime_ns * 1e-9)
|
||||
self.assertEqual(st.st_atime_ns, atime_ns)
|
||||
self.assertEqual(st.st_mtime_ns, mtime_ns)
|
||||
if support_subsecond:
|
||||
self.assertAlmostEqual(st.st_atime, atime_ns * 1e-9, delta=1e-6)
|
||||
self.assertAlmostEqual(st.st_mtime, mtime_ns * 1e-9, delta=1e-6)
|
||||
else:
|
||||
self.assertEqual(st.st_atime, atime_ns * 1e-9)
|
||||
self.assertEqual(st.st_mtime, mtime_ns * 1e-9)
|
||||
self.assertEqual(st.st_atime_ns, atime_ns)
|
||||
self.assertEqual(st.st_mtime_ns, mtime_ns)
|
||||
|
||||
def test_utime(self):
|
||||
def set_time(filename, ns):
|
||||
@@ -825,9 +946,7 @@ class UtimeTests(unittest.TestCase):
|
||||
# Convert a number of nanosecond (int) to a number of seconds (float).
|
||||
# Round towards infinity by adding 0.5 nanosecond to avoid rounding
|
||||
# issue, os.utime() rounds towards minus infinity.
|
||||
# XXX: RUSTPYTHON os.utime() use `[Duration::from_secs_f64](https://doc.rust-lang.org/std/time/struct.Duration.html#method.try_from_secs_f64)`
|
||||
# return (ns * 1e-9) + 0.5e-9
|
||||
return (ns * 1e-9)
|
||||
return (ns * 1e-9) + 0.5e-9
|
||||
|
||||
def test_utime_by_indexed(self):
|
||||
# pass times as floating-point seconds as the second indexed parameter
|
||||
@@ -1300,6 +1419,52 @@ class EnvironTests(mapping_tests.BasicTestMappingProtocol):
|
||||
self._test_underlying_process_env('_A_', '')
|
||||
self._test_underlying_process_env(overridden_key, original_value)
|
||||
|
||||
def test_reload_environ(self):
|
||||
# Test os.reload_environ()
|
||||
has_environb = hasattr(os, 'environb')
|
||||
|
||||
# Test with putenv() which doesn't update os.environ
|
||||
os.environ['test_env'] = 'python_value'
|
||||
os.putenv("test_env", "new_value")
|
||||
self.assertEqual(os.environ['test_env'], 'python_value')
|
||||
if has_environb:
|
||||
self.assertEqual(os.environb[b'test_env'], b'python_value')
|
||||
|
||||
os.reload_environ()
|
||||
self.assertEqual(os.environ['test_env'], 'new_value')
|
||||
if has_environb:
|
||||
self.assertEqual(os.environb[b'test_env'], b'new_value')
|
||||
|
||||
# Test with unsetenv() which doesn't update os.environ
|
||||
os.unsetenv('test_env')
|
||||
self.assertEqual(os.environ['test_env'], 'new_value')
|
||||
if has_environb:
|
||||
self.assertEqual(os.environb[b'test_env'], b'new_value')
|
||||
|
||||
os.reload_environ()
|
||||
self.assertNotIn('test_env', os.environ)
|
||||
if has_environb:
|
||||
self.assertNotIn(b'test_env', os.environb)
|
||||
|
||||
if has_environb:
|
||||
# test reload_environ() on os.environb with putenv()
|
||||
os.environb[b'test_env'] = b'python_value2'
|
||||
os.putenv("test_env", "new_value2")
|
||||
self.assertEqual(os.environb[b'test_env'], b'python_value2')
|
||||
self.assertEqual(os.environ['test_env'], 'python_value2')
|
||||
|
||||
os.reload_environ()
|
||||
self.assertEqual(os.environb[b'test_env'], b'new_value2')
|
||||
self.assertEqual(os.environ['test_env'], 'new_value2')
|
||||
|
||||
# test reload_environ() on os.environb with unsetenv()
|
||||
os.unsetenv('test_env')
|
||||
self.assertEqual(os.environb[b'test_env'], b'new_value2')
|
||||
self.assertEqual(os.environ['test_env'], 'new_value2')
|
||||
|
||||
os.reload_environ()
|
||||
self.assertNotIn(b'test_env', os.environb)
|
||||
self.assertNotIn('test_env', os.environ)
|
||||
|
||||
class WalkTests(unittest.TestCase):
|
||||
"""Tests for os.walk()."""
|
||||
@@ -1370,9 +1535,7 @@ class WalkTests(unittest.TestCase):
|
||||
else:
|
||||
self.sub2_tree = (sub2_path, ["SUB21"], ["tmp3"])
|
||||
|
||||
if not support.is_emscripten:
|
||||
# Emscripten fails with inaccessible directory
|
||||
os.chmod(sub21_path, 0)
|
||||
os.chmod(sub21_path, 0)
|
||||
try:
|
||||
os.listdir(sub21_path)
|
||||
except PermissionError:
|
||||
@@ -1668,9 +1831,6 @@ class FwalkTests(WalkTests):
|
||||
# check that listdir() returns consistent information
|
||||
self.assertEqual(set(os.listdir(rootfd)), set(dirs) | set(files))
|
||||
|
||||
@unittest.skipIf(
|
||||
support.is_emscripten, "Cannot dup stdout on Emscripten"
|
||||
)
|
||||
@unittest.skipIf(
|
||||
support.is_android, "dup return value is unpredictable on Android"
|
||||
)
|
||||
@@ -1687,9 +1847,6 @@ class FwalkTests(WalkTests):
|
||||
self.addCleanup(os.close, newfd)
|
||||
self.assertEqual(newfd, minfd)
|
||||
|
||||
@unittest.skipIf(
|
||||
support.is_emscripten, "Cannot dup stdout on Emscripten"
|
||||
)
|
||||
@unittest.skipIf(
|
||||
support.is_android, "dup return value is unpredictable on Android"
|
||||
)
|
||||
@@ -1725,17 +1882,6 @@ class BytesWalkTests(WalkTests):
|
||||
bdirs[:] = list(map(os.fsencode, dirs))
|
||||
bfiles[:] = list(map(os.fsencode, files))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; WalkTests doesn't have these methods
|
||||
def test_compare_to_walk(self):
|
||||
return super().test_compare_to_walk()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; WalkTests doesn't have these methods
|
||||
def test_dir_fd(self):
|
||||
return super().test_dir_fd()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; WalkTests doesn't have these methods
|
||||
def test_yields_correct_dir_fd(self):
|
||||
return super().test_yields_correct_dir_fd()
|
||||
|
||||
@unittest.skipUnless(hasattr(os, 'fwalk'), "Test needs os.fwalk()")
|
||||
class BytesFwalkTests(FwalkTests):
|
||||
@@ -1770,10 +1916,12 @@ class MakedirTests(unittest.TestCase):
|
||||
os.makedirs(path)
|
||||
|
||||
@unittest.skipIf(
|
||||
support.is_emscripten or support.is_wasi,
|
||||
"Emscripten's/WASI's umask is a stub."
|
||||
support.is_wasi,
|
||||
"WASI's umask is a stub."
|
||||
)
|
||||
def test_mode(self):
|
||||
# Note: in some cases, the umask might already be 2 in which case this
|
||||
# will pass even if os.umask is actually broken.
|
||||
with os_helper.temp_umask(0o002):
|
||||
base = os_helper.TESTFN
|
||||
parent = os.path.join(base, 'dir1')
|
||||
@@ -1786,8 +1934,8 @@ class MakedirTests(unittest.TestCase):
|
||||
self.assertEqual(os.stat(parent).st_mode & 0o777, 0o775)
|
||||
|
||||
@unittest.skipIf(
|
||||
support.is_emscripten or support.is_wasi,
|
||||
"Emscripten's/WASI's umask is a stub."
|
||||
support.is_wasi,
|
||||
"WASI's umask is a stub."
|
||||
)
|
||||
def test_exist_ok_existing_directory(self):
|
||||
path = os.path.join(os_helper.TESTFN, 'dir1')
|
||||
@@ -1804,8 +1952,8 @@ class MakedirTests(unittest.TestCase):
|
||||
os.makedirs(os.path.abspath('/'), exist_ok=True)
|
||||
|
||||
@unittest.skipIf(
|
||||
support.is_emscripten or support.is_wasi,
|
||||
"Emscripten's/WASI's umask is a stub."
|
||||
support.is_wasi,
|
||||
"WASI's umask is a stub."
|
||||
)
|
||||
def test_exist_ok_s_isgid_directory(self):
|
||||
path = os.path.join(os_helper.TESTFN, 'dir1')
|
||||
@@ -2035,7 +2183,7 @@ class GetRandomTests(unittest.TestCase):
|
||||
self.assertEqual(empty, b'')
|
||||
|
||||
def test_getrandom_random(self):
|
||||
self.assertTrue(hasattr(os, 'GRND_RANDOM'))
|
||||
self.assertHasAttr(os, 'GRND_RANDOM')
|
||||
|
||||
# Don't test os.getrandom(1, os.GRND_RANDOM) to not consume the rare
|
||||
# resource /dev/random
|
||||
@@ -2319,9 +2467,13 @@ class Win32ErrorTests(unittest.TestCase):
|
||||
|
||||
@unittest.skipIf(support.is_wasi, "Cannot create invalid FD on WASI.")
|
||||
class TestInvalidFD(unittest.TestCase):
|
||||
singles = ["fchdir", "dup", "fdatasync", "fstat",
|
||||
"fstatvfs", "fsync", "tcgetpgrp", "ttyname"]
|
||||
singles_fildes = {"fchdir", "fdatasync", "fsync"}
|
||||
singles = ["fchdir", "dup", "fstat", "fstatvfs", "tcgetpgrp", "ttyname"]
|
||||
singles_fildes = {"fchdir"}
|
||||
# systemd-nspawn --suppress-sync=true does not verify fd passed
|
||||
# fdatasync() and fsync(), and always returns success
|
||||
if not support.in_systemd_nspawn_sync_suppressed():
|
||||
singles += ["fdatasync", "fsync"]
|
||||
singles_fildes |= {"fdatasync", "fsync"}
|
||||
#singles.append("close")
|
||||
#We omit close because it doesn't raise an exception on some platforms
|
||||
def get_single(f):
|
||||
@@ -2379,10 +2531,6 @@ class TestInvalidFD(unittest.TestCase):
|
||||
self.check(os.dup2, 20)
|
||||
|
||||
@unittest.skipUnless(hasattr(os, 'dup2'), 'test needs os.dup2()')
|
||||
@unittest.skipIf(
|
||||
support.is_emscripten,
|
||||
"dup2() with negative fds is broken on Emscripten (see gh-102179)"
|
||||
)
|
||||
def test_dup2_negative_fd(self):
|
||||
valid_fd = os.open(__file__, os.O_RDONLY)
|
||||
self.addCleanup(os.close, valid_fd)
|
||||
@@ -2406,20 +2554,21 @@ class TestInvalidFD(unittest.TestCase):
|
||||
def test_fchown(self):
|
||||
self.check(os.fchown, -1, -1)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
@unittest.skipUnless(hasattr(os, 'fpathconf'), 'test needs os.fpathconf()')
|
||||
@unittest.skipIf(
|
||||
support.is_emscripten or support.is_wasi,
|
||||
"musl libc issue on Emscripten/WASI, bpo-46390"
|
||||
)
|
||||
def test_fpathconf(self):
|
||||
self.assertIn("PC_NAME_MAX", os.pathconf_names)
|
||||
self.check(os.pathconf, "PC_NAME_MAX")
|
||||
self.check(os.fpathconf, "PC_NAME_MAX")
|
||||
self.check_bool(os.pathconf, "PC_NAME_MAX")
|
||||
self.check_bool(os.fpathconf, "PC_NAME_MAX")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
@unittest.skipUnless(hasattr(os, 'fpathconf'), 'test needs os.fpathconf()')
|
||||
@unittest.skipIf(
|
||||
support.linked_to_musl(),
|
||||
'musl pathconf ignores the file descriptor and returns a constant',
|
||||
)
|
||||
def test_fpathconf_bad_fd(self):
|
||||
self.check(os.pathconf, "PC_NAME_MAX")
|
||||
self.check(os.fpathconf, "PC_NAME_MAX")
|
||||
|
||||
@unittest.skipUnless(hasattr(os, 'ftruncate'), 'test needs os.ftruncate()')
|
||||
def test_ftruncate(self):
|
||||
self.check(os.truncate, 0)
|
||||
@@ -2434,6 +2583,10 @@ class TestInvalidFD(unittest.TestCase):
|
||||
def test_read(self):
|
||||
self.check(os.read, 1)
|
||||
|
||||
@unittest.skipUnless(hasattr(os, 'readinto'), 'test needs os.readinto()')
|
||||
def test_readinto(self):
|
||||
self.check(os.readinto, bytearray(5))
|
||||
|
||||
@unittest.skipUnless(hasattr(os, 'readv'), 'test needs os.readv()')
|
||||
def test_readv(self):
|
||||
buf = bytearray(10)
|
||||
@@ -2462,13 +2615,8 @@ class TestInvalidFD(unittest.TestCase):
|
||||
self.check(os.get_blocking)
|
||||
self.check(os.set_blocking, True)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_fchdir(self):
|
||||
return super().test_fchdir()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_fsync(self):
|
||||
return super().test_fsync()
|
||||
|
||||
|
||||
|
||||
@unittest.skipUnless(hasattr(os, 'link'), 'requires os.link')
|
||||
@@ -3355,9 +3503,6 @@ class DeviceEncodingTests(unittest.TestCase):
|
||||
@unittest.skipUnless(os.isatty(0) and not win32_is_iot() and (sys.platform.startswith('win') or
|
||||
(hasattr(locale, 'nl_langinfo') and hasattr(locale, 'CODESET'))),
|
||||
'test requires a tty and either Windows or nl_langinfo(CODESET)')
|
||||
@unittest.skipIf(
|
||||
support.is_emscripten, "Cannot get encoding of stdin on Emscripten"
|
||||
)
|
||||
def test_device_encoding(self):
|
||||
encoding = os.device_encoding(0)
|
||||
self.assertIsNotNone(encoding)
|
||||
@@ -3485,8 +3630,8 @@ class SpawnTests(unittest.TestCase):
|
||||
exitcode = os.spawnl(os.P_WAIT, program, *args)
|
||||
self.assertEqual(exitcode, self.exitcode)
|
||||
|
||||
@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON; fix spawnve on Windows")
|
||||
@requires_os_func('spawnle')
|
||||
@unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; fix spawnve on Windows")
|
||||
def test_spawnle(self):
|
||||
program, args = self.create_args(with_env=True)
|
||||
exitcode = os.spawnle(os.P_WAIT, program, *args, self.env)
|
||||
@@ -3514,8 +3659,8 @@ class SpawnTests(unittest.TestCase):
|
||||
exitcode = os.spawnv(os.P_WAIT, FakePath(program), args)
|
||||
self.assertEqual(exitcode, self.exitcode)
|
||||
|
||||
@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON; fix spawnve on Windows")
|
||||
@requires_os_func('spawnve')
|
||||
@unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; fix spawnve on Windows")
|
||||
def test_spawnve(self):
|
||||
program, args = self.create_args(with_env=True)
|
||||
exitcode = os.spawnve(os.P_WAIT, program, args, self.env)
|
||||
@@ -3539,7 +3684,6 @@ class SpawnTests(unittest.TestCase):
|
||||
pid = os.spawnv(os.P_NOWAIT, program, args)
|
||||
support.wait_process(pid, exitcode=self.exitcode)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; fix spawnv bytes
|
||||
@requires_os_func('spawnve')
|
||||
def test_spawnve_bytes(self):
|
||||
# Test bytes handling in parse_arglist and parse_envlist (#28114)
|
||||
@@ -3623,8 +3767,8 @@ class SpawnTests(unittest.TestCase):
|
||||
exitcode = spawn(os.P_WAIT, program, args, newenv)
|
||||
self.assertEqual(exitcode, 0)
|
||||
|
||||
@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON; fix spawnve on Windows")
|
||||
@requires_os_func('spawnve')
|
||||
@unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; fix spawnve on Windows")
|
||||
def test_spawnve_invalid_env(self):
|
||||
self._test_invalid_env(os.spawnve)
|
||||
|
||||
@@ -4989,7 +5133,7 @@ class TestScandir(unittest.TestCase):
|
||||
entry_lstat,
|
||||
os.name == 'nt')
|
||||
|
||||
@unittest.skipIf(sys.platform == 'linux', 'TODO: RUSTPYTHON; flaky test')
|
||||
@unittest.skipIf(sys.platform == "linux", "TODO: RUSTPYTHON; flaky test")
|
||||
def test_attributes(self):
|
||||
link = os_helper.can_hardlink()
|
||||
symlink = os_helper.can_symlink()
|
||||
@@ -5171,7 +5315,7 @@ class TestScandir(unittest.TestCase):
|
||||
with self.assertRaises(TypeError):
|
||||
os.scandir(path_bytes)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <builtin_function_or_method object at 0xba3106920> not found in {<builtin_function_or_method object at 0xba31078e0>, <builtin_function_or_method object at 0xba31079c0>, <builtin_function_or_method object at 0xba3107b10>, <builtin_function_or_method object at 0xba3159500>, <builtin_function_or_method object at 0xba3159570>, <builtin_function_or_method object at 0xba3107800>, <builtin_function_or_method object at 0xba3106760>, <builtin_function_or_method object at 0xba3106a00>, <builtin_function_or_method object at 0xba3106990>, <builtin_function_or_method object at 0xba3107330>, <builtin_function_or_method object at 0xba31072c0>, <builtin_function_or_method object at 0xba31064c0>}
|
||||
@unittest.skipUnless(os.listdir in os.supports_fd,
|
||||
'fd support for listdir required for this test.')
|
||||
def test_fd(self):
|
||||
@@ -5253,7 +5397,6 @@ class TestScandir(unittest.TestCase):
|
||||
with self.check_no_resource_warning():
|
||||
del iterator
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_resource_warning(self):
|
||||
self.create_file("file.txt")
|
||||
self.create_file("file2.txt")
|
||||
@@ -5293,8 +5436,8 @@ class TestPEP519(unittest.TestCase):
|
||||
|
||||
def test_pathlike(self):
|
||||
self.assertEqual('#feelthegil', self.fspath(FakePath('#feelthegil')))
|
||||
self.assertTrue(issubclass(FakePath, os.PathLike))
|
||||
self.assertTrue(isinstance(FakePath('x'), os.PathLike))
|
||||
self.assertIsSubclass(FakePath, os.PathLike)
|
||||
self.assertIsInstance(FakePath('x'), os.PathLike)
|
||||
|
||||
def test_garbage_in_exception_out(self):
|
||||
vapor = type('blah', (), {})
|
||||
@@ -5320,8 +5463,8 @@ class TestPEP519(unittest.TestCase):
|
||||
# true on abstract implementation.
|
||||
class A(os.PathLike):
|
||||
pass
|
||||
self.assertFalse(issubclass(FakePath, A))
|
||||
self.assertTrue(issubclass(FakePath, os.PathLike))
|
||||
self.assertNotIsSubclass(FakePath, A)
|
||||
self.assertIsSubclass(FakePath, os.PathLike)
|
||||
|
||||
def test_pathlike_class_getitem(self):
|
||||
self.assertIsInstance(os.PathLike[bytes], types.GenericAlias)
|
||||
@@ -5331,7 +5474,7 @@ class TestPEP519(unittest.TestCase):
|
||||
__slots__ = ()
|
||||
def __fspath__(self):
|
||||
return ''
|
||||
self.assertFalse(hasattr(A(), '__dict__'))
|
||||
self.assertNotHasAttr(A(), '__dict__')
|
||||
|
||||
def test_fspath_set_to_None(self):
|
||||
class Foo:
|
||||
@@ -5435,7 +5578,7 @@ class ForkTests(unittest.TestCase):
|
||||
self.assertEqual(err.decode("utf-8"), "")
|
||||
self.assertEqual(out.decode("utf-8"), "")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: b"can't fork at interpreter shutdown" not found in b"Exception ignored in: <function AtFinalization.__del__ at 0xc508b30c0>\nAttributeError: 'NoneType' object has no attribute 'fork'\n"
|
||||
def test_fork_at_finalization(self):
|
||||
code = """if 1:
|
||||
import atexit
|
||||
|
||||
Vendored
+9
-2
@@ -57,14 +57,21 @@ class PopenTest(unittest.TestCase):
|
||||
def test_contextmanager(self):
|
||||
with os.popen("echo hello") as f:
|
||||
self.assertEqual(f.read(), "hello\n")
|
||||
self.assertFalse(f.closed)
|
||||
self.assertTrue(f.closed)
|
||||
|
||||
def test_iterating(self):
|
||||
with os.popen("echo hello") as f:
|
||||
self.assertEqual(list(f), ["hello\n"])
|
||||
self.assertFalse(f.closed)
|
||||
self.assertTrue(f.closed)
|
||||
|
||||
def test_keywords(self):
|
||||
with os.popen(cmd="exit 0", mode="w", buffering=-1):
|
||||
pass
|
||||
with os.popen(cmd="echo hello", mode="r", buffering=-1) as f:
|
||||
self.assertEqual(f.read(), "hello\n")
|
||||
self.assertFalse(f.closed)
|
||||
self.assertTrue(f.closed)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Vendored
+66
-15
@@ -583,7 +583,6 @@ class PosixTester(unittest.TestCase):
|
||||
self.assertGreater(len(path), 0)
|
||||
self.assertEqual(posix.confstr(posix.confstr_names["CS_PATH"]), path)
|
||||
|
||||
@unittest.expectedFailureIf(sys.platform in ('darwin', 'linux'), '''TODO: RUSTPYTHON; AssertionError: "configuration names must be strings or integers" does not match "Expected type 'str' but 'float' found."''')
|
||||
@unittest.skipUnless(hasattr(posix, 'sysconf'),
|
||||
'test needs posix.sysconf()')
|
||||
def test_sysconf(self):
|
||||
@@ -1018,7 +1017,7 @@ class PosixTester(unittest.TestCase):
|
||||
target = self.tempdir()
|
||||
self.check_chmod(posix.chmod, target)
|
||||
|
||||
@unittest.skipIf(sys.platform in ('darwin', 'linux'), 'TODO: RUSTPYTHON; crash')
|
||||
@unittest.skipIf(sys.platform in ("darwin", "linux"), "TODO: RUSTPYTHON; crash")
|
||||
@os_helper.skip_unless_working_chmod
|
||||
def test_fchmod_file(self):
|
||||
with open(os_helper.TESTFN, 'wb+') as f:
|
||||
@@ -1075,7 +1074,7 @@ class PosixTester(unittest.TestCase):
|
||||
self.check_chmod_link(posix.chmod, target, link)
|
||||
self.check_chmod_link(posix.chmod, target, link, follow_symlinks=True)
|
||||
|
||||
@unittest.skipIf(sys.platform == 'win32', 'TODO: RUSTPYTHON; flaky')
|
||||
@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON; flaky")
|
||||
@os_helper.skip_unless_symlink
|
||||
def test_chmod_dir_symlink(self):
|
||||
target = self.tempdir()
|
||||
@@ -1110,7 +1109,7 @@ class PosixTester(unittest.TestCase):
|
||||
|
||||
def _test_chflags_regular_file(self, chflags_func, target_file, **kwargs):
|
||||
st = os.stat(target_file)
|
||||
self.assertTrue(hasattr(st, 'st_flags'))
|
||||
self.assertHasAttr(st, 'st_flags')
|
||||
|
||||
# ZFS returns EOPNOTSUPP when attempting to set flag UF_IMMUTABLE.
|
||||
flags = st.st_flags | stat.UF_IMMUTABLE
|
||||
@@ -1146,7 +1145,7 @@ class PosixTester(unittest.TestCase):
|
||||
def test_lchflags_symlink(self):
|
||||
testfn_st = os.stat(os_helper.TESTFN)
|
||||
|
||||
self.assertTrue(hasattr(testfn_st, 'st_flags'))
|
||||
self.assertHasAttr(testfn_st, 'st_flags')
|
||||
|
||||
self.addCleanup(os_helper.unlink, _DUMMY_SYMLINK)
|
||||
os.symlink(os_helper.TESTFN, _DUMMY_SYMLINK)
|
||||
@@ -1350,7 +1349,6 @@ class PosixTester(unittest.TestCase):
|
||||
param = posix.sched_param(sched_priority=-large)
|
||||
self.assertRaises(OverflowError, posix.sched_setparam, 0, param)
|
||||
|
||||
@unittest.expectedFailureIf(sys.platform == 'linux', "TODO: RUSTPYTHON; TypeError: cannot pickle 'sched_param' object")
|
||||
@requires_sched
|
||||
def test_sched_param(self):
|
||||
param = posix.sched_param(1)
|
||||
@@ -1370,6 +1368,14 @@ class PosixTester(unittest.TestCase):
|
||||
self.assertNotEqual(newparam, param)
|
||||
self.assertEqual(newparam.sched_priority, 0)
|
||||
|
||||
@requires_sched
|
||||
def test_bug_140634(self):
|
||||
sched_priority = float('inf') # any new reference
|
||||
param = posix.sched_param(sched_priority)
|
||||
param.__reduce__()
|
||||
del sched_priority, param # should not crash
|
||||
support.gc_collect() # just to be sure
|
||||
|
||||
@unittest.skipUnless(hasattr(posix, "sched_rr_get_interval"), "no function")
|
||||
def test_sched_rr_get_interval(self):
|
||||
try:
|
||||
@@ -1525,6 +1531,51 @@ class PosixTester(unittest.TestCase):
|
||||
self.assertEqual(cm.exception.errno, errno.EINVAL)
|
||||
os.close(os.pidfd_open(os.getpid(), 0))
|
||||
|
||||
@os_helper.skip_unless_hardlink
|
||||
@os_helper.skip_unless_symlink
|
||||
def test_link_follow_symlinks(self):
|
||||
default_follow = sys.platform.startswith(
|
||||
('darwin', 'freebsd', 'netbsd', 'openbsd', 'dragonfly', 'sunos5'))
|
||||
default_no_follow = sys.platform.startswith(('win32', 'linux'))
|
||||
orig = os_helper.TESTFN
|
||||
symlink = orig + 'symlink'
|
||||
posix.symlink(orig, symlink)
|
||||
self.addCleanup(os_helper.unlink, symlink)
|
||||
|
||||
with self.subTest('no follow_symlinks'):
|
||||
# no follow_symlinks -> platform depending
|
||||
link = orig + 'link'
|
||||
posix.link(symlink, link)
|
||||
self.addCleanup(os_helper.unlink, link)
|
||||
if os.link in os.supports_follow_symlinks or default_follow:
|
||||
self.assertEqual(posix.lstat(link), posix.lstat(orig))
|
||||
elif default_no_follow:
|
||||
self.assertEqual(posix.lstat(link), posix.lstat(symlink))
|
||||
|
||||
with self.subTest('follow_symlinks=False'):
|
||||
# follow_symlinks=False -> duplicate the symlink itself
|
||||
link = orig + 'link_nofollow'
|
||||
try:
|
||||
posix.link(symlink, link, follow_symlinks=False)
|
||||
except NotImplementedError:
|
||||
if os.link in os.supports_follow_symlinks or default_no_follow:
|
||||
raise
|
||||
else:
|
||||
self.addCleanup(os_helper.unlink, link)
|
||||
self.assertEqual(posix.lstat(link), posix.lstat(symlink))
|
||||
|
||||
with self.subTest('follow_symlinks=True'):
|
||||
# follow_symlinks=True -> duplicate the target file
|
||||
link = orig + 'link_following'
|
||||
try:
|
||||
posix.link(symlink, link, follow_symlinks=True)
|
||||
except NotImplementedError:
|
||||
if os.link in os.supports_follow_symlinks or default_follow:
|
||||
raise
|
||||
else:
|
||||
self.addCleanup(os_helper.unlink, link)
|
||||
self.assertEqual(posix.lstat(link), posix.lstat(orig))
|
||||
|
||||
|
||||
# tests for the posix *at functions follow
|
||||
class TestPosixDirFd(unittest.TestCase):
|
||||
@@ -1570,7 +1621,6 @@ class TestPosixDirFd(unittest.TestCase):
|
||||
with self.prepare_file() as (dir_fd, name, fullname):
|
||||
posix.chown(name, os.getuid(), os.getgid(), dir_fd=dir_fd)
|
||||
|
||||
@unittest.expectedFailureIf(sys.platform in ('darwin', 'linux'), 'TODO: RUSTPYTHON; AssertionError: RuntimeWarning not triggered')
|
||||
@unittest.skipUnless(os.stat in os.supports_dir_fd, "test needs dir_fd support in os.stat()")
|
||||
def test_stat_dir_fd(self):
|
||||
with self.prepare() as (dir_fd, name, fullname):
|
||||
@@ -1973,7 +2023,7 @@ class _PosixSpawnMixin:
|
||||
[sys.executable, "-c", "pass"],
|
||||
os.environ, setsigdef=[signal.NSIG, signal.NSIG+1])
|
||||
|
||||
@unittest.expectedFailureIf(sys.platform in ('darwin', 'linux'), 'TODO: RUSTPYTHON; NotImplementedError: scheduler parameter is not yet implemented')
|
||||
@unittest.expectedFailureIf(sys.platform in ("darwin", "linux"), "TODO: RUSTPYTHON; NotImplementedError: scheduler parameter is not yet implemented")
|
||||
@requires_sched
|
||||
@unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')),
|
||||
"bpo-34685: test can fail on BSD")
|
||||
@@ -1994,14 +2044,15 @@ class _PosixSpawnMixin:
|
||||
)
|
||||
support.wait_process(pid, exitcode=0)
|
||||
|
||||
@unittest.expectedFailureIf(sys.platform in ('darwin', 'linux'), 'TODO: RUSTPYTHON; NotImplementedError: scheduler parameter is not yet implemented')
|
||||
@unittest.expectedFailureIf(sys.platform in ("darwin", "linux"), "TODO: RUSTPYTHON; NotImplementedError: scheduler parameter is not yet implemented")
|
||||
@requires_sched
|
||||
@unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')),
|
||||
"bpo-34685: test can fail on BSD")
|
||||
@unittest.skipIf(platform.libc_ver()[0] == 'glibc' and
|
||||
os.sched_getscheduler(0) in [
|
||||
os.SCHED_BATCH,
|
||||
os.SCHED_IDLE],
|
||||
os.SCHED_IDLE,
|
||||
os.SCHED_DEADLINE],
|
||||
"Skip test due to glibc posix_spawn policy")
|
||||
def test_setscheduler_with_policy(self):
|
||||
policy = os.sched_getscheduler(0)
|
||||
@@ -2081,7 +2132,7 @@ class _PosixSpawnMixin:
|
||||
with open(outfile, encoding="utf-8") as f:
|
||||
self.assertEqual(f.read(), 'hello')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; the rust runtime reopens closed stdio fds at startup, so this test fails, even though POSIX_SPAWN_CLOSE does actually have an effect
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; the rust runtime reopens closed stdio fds at startup, so this test fails, even though POSIX_SPAWN_CLOSE does actually have an effect
|
||||
def test_close_file(self):
|
||||
closefile = os_helper.TESTFN
|
||||
self.addCleanup(os_helper.unlink, closefile)
|
||||
@@ -2186,12 +2237,12 @@ class TestPosixWeaklinking(unittest.TestCase):
|
||||
def test_pwritev(self):
|
||||
self._verify_available("HAVE_PWRITEV")
|
||||
if self.mac_ver >= (10, 16):
|
||||
self.assertTrue(hasattr(os, "pwritev"), "os.pwritev is not available")
|
||||
self.assertTrue(hasattr(os, "preadv"), "os.readv is not available")
|
||||
self.assertHasAttr(os, "pwritev")
|
||||
self.assertHasAttr(os, "preadv")
|
||||
|
||||
else:
|
||||
self.assertFalse(hasattr(os, "pwritev"), "os.pwritev is available")
|
||||
self.assertFalse(hasattr(os, "preadv"), "os.readv is available")
|
||||
self.assertNotHasAttr(os, "pwritev")
|
||||
self.assertNotHasAttr(os, "preadv")
|
||||
|
||||
def test_stat(self):
|
||||
self._verify_available("HAVE_FSTATAT")
|
||||
|
||||
Vendored
-2
@@ -192,7 +192,6 @@ class WindowsSignalTests(unittest.TestCase):
|
||||
self.assertNotIn(signal.NSIG, s)
|
||||
self.assertLess(len(s), signal.NSIG)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_issue9324(self):
|
||||
# Updated for issue #10003, adding SIGBREAK
|
||||
handler = lambda x, y: None
|
||||
@@ -1414,7 +1413,6 @@ class RaiseSignalTest(unittest.TestCase):
|
||||
with self.assertRaises(KeyboardInterrupt):
|
||||
signal.raise_signal(signal.SIGINT)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
@unittest.skipIf(sys.platform != "win32", "Windows specific test")
|
||||
def test_invalid_argument(self):
|
||||
try:
|
||||
|
||||
Vendored
-1
@@ -800,7 +800,6 @@ class ProcessTestCase(BaseTestCase):
|
||||
stdout, stderr = p.communicate()
|
||||
self.assertEqual(stdout, b"orange")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
@unittest.skipUnless(sys.platform == "win32", "Windows only issue")
|
||||
def test_win32_duplicate_envs(self):
|
||||
newenv = os.environ.copy()
|
||||
|
||||
@@ -16,8 +16,8 @@ mod _socket {
|
||||
common::os::ErrorExt,
|
||||
convert::{IntoPyException, ToPyObject, TryFromBorrowedObject, TryFromObject},
|
||||
function::{
|
||||
ArgBytesLike, ArgMemoryBuffer, ArgStrOrBytesLike, Either, FsPath, OptionalArg,
|
||||
OptionalOption,
|
||||
ArgBytesLike, ArgIntoFloat, ArgMemoryBuffer, ArgStrOrBytesLike, Either, FsPath,
|
||||
OptionalArg, OptionalOption,
|
||||
},
|
||||
types::{Constructor, DefaultConstructor, Initializer, Representable},
|
||||
utils::ToCString,
|
||||
@@ -2201,12 +2201,29 @@ mod _socket {
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
fn settimeout(&self, timeout: Option<Duration>) -> io::Result<()> {
|
||||
self.timeout
|
||||
.store(timeout.map_or(-1.0, |d| d.as_secs_f64()));
|
||||
fn settimeout(&self, timeout: Option<ArgIntoFloat>, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let timeout = match timeout {
|
||||
Some(t) => {
|
||||
let f = t.into_float();
|
||||
if f.is_nan() {
|
||||
return Err(
|
||||
vm.new_value_error("Invalid value NaN (not a number)".to_owned())
|
||||
);
|
||||
}
|
||||
if f < 0.0 || !f.is_finite() {
|
||||
return Err(vm.new_value_error("Timeout value out of range".to_owned()));
|
||||
}
|
||||
Some(f)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
self.timeout.store(timeout.unwrap_or(-1.0));
|
||||
// even if timeout is > 0 the socket needs to be nonblocking in order for us to select() on
|
||||
// it
|
||||
self.sock()?.set_nonblocking(timeout.is_some())
|
||||
self.sock()
|
||||
.map_err(|e| e.into_pyexception(vm))?
|
||||
.set_nonblocking(timeout.is_some())
|
||||
.map_err(|e| e.into_pyexception(vm))
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
@@ -3366,8 +3383,22 @@ mod _socket {
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn setdefaulttimeout(timeout: Option<Duration>) {
|
||||
DEFAULT_TIMEOUT.store(timeout.map_or(-1.0, |d| d.as_secs_f64()));
|
||||
fn setdefaulttimeout(timeout: Option<ArgIntoFloat>, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let val = match timeout {
|
||||
Some(t) => {
|
||||
let f = t.into_float();
|
||||
if f.is_nan() {
|
||||
return Err(vm.new_value_error("Invalid value NaN (not a number)".to_owned()));
|
||||
}
|
||||
if f < 0.0 || !f.is_finite() {
|
||||
return Err(vm.new_value_error("Timeout value out of range".to_owned()));
|
||||
}
|
||||
f
|
||||
}
|
||||
None => -1.0,
|
||||
};
|
||||
DEFAULT_TIMEOUT.store(val);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
|
||||
@@ -126,10 +126,23 @@ impl TryFromObject for core::time::Duration {
|
||||
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
|
||||
if let Some(float) = obj.downcast_ref::<PyFloat>() {
|
||||
let f = float.to_f64();
|
||||
if f < 0.0 {
|
||||
return Err(vm.new_value_error("negative duration"));
|
||||
if f.is_nan() {
|
||||
return Err(vm.new_value_error("Invalid value NaN (not a number)".to_owned()));
|
||||
}
|
||||
Ok(Self::from_secs_f64(f))
|
||||
if f < 0.0 {
|
||||
return Err(vm.new_value_error("negative duration".to_owned()));
|
||||
}
|
||||
if !f.is_finite() || f > u64::MAX as f64 {
|
||||
return Err(vm.new_overflow_error(
|
||||
"timestamp too large to convert to C PyTime_t".to_owned(),
|
||||
));
|
||||
}
|
||||
// Convert float to Duration using floor rounding (_PyTime_ROUND_FLOOR)
|
||||
let secs = f.trunc() as u64;
|
||||
let frac = f.fract();
|
||||
// Use floor to round down the nanoseconds
|
||||
let nanos = (frac * 1_000_000_000.0).floor() as u32;
|
||||
Ok(Self::new(secs, nanos))
|
||||
} else if let Some(int) = obj.try_index_opt(vm) {
|
||||
let int = int?;
|
||||
let bigint = int.as_bigint();
|
||||
|
||||
+14
-1
@@ -1,8 +1,9 @@
|
||||
use rustpython_common::crt_fd;
|
||||
|
||||
use crate::{
|
||||
PyObjectRef, PyResult, VirtualMachine,
|
||||
AsObject, PyObjectRef, PyResult, VirtualMachine,
|
||||
builtins::{PyBytes, PyStr},
|
||||
class::StaticType,
|
||||
convert::{IntoPyException, ToPyException, ToPyObject, TryFromObject},
|
||||
function::FsPath,
|
||||
};
|
||||
@@ -80,6 +81,18 @@ impl PathConverter {
|
||||
) -> PyResult<OsPathOrFd<'fd>> {
|
||||
// Handle fd (before __fspath__ check, like CPython)
|
||||
if let Some(int) = obj.try_index_opt(vm) {
|
||||
// Warn if bool is used as a file descriptor
|
||||
if obj
|
||||
.class()
|
||||
.is(crate::builtins::bool_::PyBool::static_type())
|
||||
{
|
||||
crate::stdlib::warnings::warn(
|
||||
vm.ctx.exceptions.runtime_warning,
|
||||
"bool is used as a file descriptor".to_owned(),
|
||||
1,
|
||||
vm,
|
||||
)?;
|
||||
}
|
||||
let fd = int?.try_to_primitive(vm)?;
|
||||
return unsafe { crt_fd::Borrowed::try_borrow_raw(fd) }
|
||||
.map(OsPathOrFd::Fd)
|
||||
|
||||
+26
-19
@@ -183,6 +183,18 @@ pub(crate) mod module {
|
||||
environ
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn _create_environ(vm: &VirtualMachine) -> PyDictRef {
|
||||
let environ = vm.ctx.new_dict();
|
||||
for (key, value) in env::vars() {
|
||||
if key.starts_with('=') {
|
||||
continue;
|
||||
}
|
||||
environ.set_item(&key, vm.new_pyobj(value), vm).unwrap();
|
||||
}
|
||||
environ
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct ChmodArgs<'a> {
|
||||
#[pyarg(any)]
|
||||
@@ -903,16 +915,14 @@ pub(crate) mod module {
|
||||
argv: Either<PyListRef, PyTupleRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<intptr_t> {
|
||||
use crate::function::FsPath;
|
||||
use std::iter::once;
|
||||
|
||||
let make_widestring =
|
||||
|s: &str| widestring::WideCString::from_os_str(s).map_err(|err| err.to_pyexception(vm));
|
||||
|
||||
let path = path.to_wide_cstring(vm)?;
|
||||
|
||||
let argv = vm.extract_elements_with(argv.as_ref(), |obj| {
|
||||
let arg = PyStrRef::try_from_object(vm, obj)?;
|
||||
make_widestring(arg.as_str())
|
||||
let fspath = FsPath::try_from_path_like(obj, true, vm)?;
|
||||
fspath.to_wide_cstring(vm)
|
||||
})?;
|
||||
|
||||
let first = argv
|
||||
@@ -946,16 +956,14 @@ pub(crate) mod module {
|
||||
env: PyDictRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<intptr_t> {
|
||||
use crate::function::FsPath;
|
||||
use std::iter::once;
|
||||
|
||||
let make_widestring =
|
||||
|s: &str| widestring::WideCString::from_os_str(s).map_err(|err| err.to_pyexception(vm));
|
||||
|
||||
let path = path.to_wide_cstring(vm)?;
|
||||
|
||||
let argv = vm.extract_elements_with(argv.as_ref(), |obj| {
|
||||
let arg = PyStrRef::try_from_object(vm, obj)?;
|
||||
make_widestring(arg.as_str())
|
||||
let fspath = FsPath::try_from_path_like(obj, true, vm)?;
|
||||
fspath.to_wide_cstring(vm)
|
||||
})?;
|
||||
|
||||
let first = argv
|
||||
@@ -975,15 +983,11 @@ pub(crate) mod module {
|
||||
// Build environment strings as "KEY=VALUE\0" wide strings
|
||||
let mut env_strings: Vec<widestring::WideCString> = Vec::new();
|
||||
for (key, value) in env.into_iter() {
|
||||
let key = PyStrRef::try_from_object(vm, key)?;
|
||||
let value = PyStrRef::try_from_object(vm, value)?;
|
||||
let key_str = key.as_str();
|
||||
let value_str = value.as_str();
|
||||
let key = FsPath::try_from_path_like(key, true, vm)?;
|
||||
let value = FsPath::try_from_path_like(value, true, vm)?;
|
||||
let key_str = key.to_string_lossy();
|
||||
let value_str = value.to_string_lossy();
|
||||
|
||||
// Validate: no null characters in key or value
|
||||
if key_str.contains('\0') || value_str.contains('\0') {
|
||||
return Err(vm.new_value_error("embedded null character"));
|
||||
}
|
||||
// Validate: no '=' in key (search from index 1 because on Windows
|
||||
// starting '=' is allowed for defining hidden environment variables)
|
||||
if key_str.get(1..).is_some_and(|s| s.contains('=')) {
|
||||
@@ -991,7 +995,10 @@ pub(crate) mod module {
|
||||
}
|
||||
|
||||
let env_str = format!("{}={}", key_str, value_str);
|
||||
env_strings.push(make_widestring(&env_str)?);
|
||||
env_strings.push(
|
||||
widestring::WideCString::from_os_str(&*std::ffi::OsString::from(env_str))
|
||||
.map_err(|err| err.to_pyexception(vm))?,
|
||||
);
|
||||
}
|
||||
|
||||
let envp: Vec<*const u16> = env_strings
|
||||
|
||||
+90
-23
@@ -87,6 +87,7 @@ impl<const AVAILABLE: usize> FromArgs for DirFd<'_, AVAILABLE> {
|
||||
Some(o) if vm.is_none(&o) => Ok(DEFAULT_DIR_FD),
|
||||
None => Ok(DEFAULT_DIR_FD),
|
||||
Some(o) => {
|
||||
warn_if_bool_fd(&o, vm).map_err(Into::<ArgumentError>::into)?;
|
||||
let fd = o.try_index_opt(vm).unwrap_or_else(|| {
|
||||
Err(vm.new_type_error(format!(
|
||||
"argument should be integer or None, not {}",
|
||||
@@ -118,8 +119,25 @@ fn bytes_as_os_str<'a>(b: &'a [u8], vm: &VirtualMachine) -> PyResult<&'a std::ff
|
||||
.map_err(|_| vm.new_unicode_decode_error("can't decode path for utf-8"))
|
||||
}
|
||||
|
||||
pub(crate) fn warn_if_bool_fd(obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
use crate::class::StaticType;
|
||||
if obj
|
||||
.class()
|
||||
.is(crate::builtins::bool_::PyBool::static_type())
|
||||
{
|
||||
crate::stdlib::warnings::warn(
|
||||
vm.ctx.exceptions.runtime_warning,
|
||||
"bool is used as a file descriptor".to_owned(),
|
||||
1,
|
||||
vm,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl TryFromObject for crt_fd::Owned {
|
||||
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
|
||||
warn_if_bool_fd(&obj, vm)?;
|
||||
let fd = crt_fd::Raw::try_from_object(vm, obj)?;
|
||||
unsafe { crt_fd::Owned::try_from_raw(fd) }.map_err(|e| e.into_pyexception(vm))
|
||||
}
|
||||
@@ -127,6 +145,7 @@ impl TryFromObject for crt_fd::Owned {
|
||||
|
||||
impl TryFromObject for crt_fd::Borrowed<'_> {
|
||||
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
|
||||
warn_if_bool_fd(&obj, vm)?;
|
||||
let fd = crt_fd::Raw::try_from_object(vm, obj)?;
|
||||
unsafe { crt_fd::Borrowed::try_borrow_raw(fd) }.map_err(|e| e.into_pyexception(vm))
|
||||
}
|
||||
@@ -165,11 +184,11 @@ pub(super) mod _os {
|
||||
},
|
||||
convert::{IntoPyException, ToPyObject},
|
||||
exceptions::OSErrorBuilder,
|
||||
function::{ArgBytesLike, FsPath, FuncArgs, OptionalArg},
|
||||
function::{ArgBytesLike, ArgMemoryBuffer, FsPath, FuncArgs, OptionalArg},
|
||||
ospath::{OsPath, OsPathOrFd, OutputMode, PathConverter},
|
||||
protocol::PyIterReturn,
|
||||
recursion::ReprGuard,
|
||||
types::{IterNext, Iterable, PyStructSequence, Representable, SelfIter},
|
||||
types::{Destructor, IterNext, Iterable, PyStructSequence, Representable, SelfIter},
|
||||
vm::VirtualMachine,
|
||||
};
|
||||
use core::time::Duration;
|
||||
@@ -296,6 +315,26 @@ pub(super) mod _os {
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn readinto(
|
||||
fd: crt_fd::Borrowed<'_>,
|
||||
buffer: ArgMemoryBuffer,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<usize> {
|
||||
buffer.with_ref(|buf| {
|
||||
loop {
|
||||
match crt_fd::read(fd, buf) {
|
||||
Ok(n) => return Ok(n),
|
||||
Err(e) if e.raw_os_error() == Some(libc::EINTR) => {
|
||||
vm.check_signals()?;
|
||||
continue;
|
||||
}
|
||||
Err(e) => return Err(e.into_pyexception(vm)),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn write(fd: crt_fd::Borrowed<'_>, data: ArgBytesLike) -> io::Result<usize> {
|
||||
data.with_ref(|b| crt_fd::write(fd, b))
|
||||
@@ -754,7 +793,7 @@ pub(super) mod _os {
|
||||
mode: OutputMode,
|
||||
}
|
||||
|
||||
#[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))]
|
||||
#[pyclass(flags(DISALLOW_INSTANTIATION), with(Destructor, IterNext, Iterable))]
|
||||
impl ScandirIterator {
|
||||
#[pymethod]
|
||||
fn close(&self) {
|
||||
@@ -777,6 +816,21 @@ pub(super) mod _os {
|
||||
Err(vm.new_type_error("cannot pickle 'ScandirIterator' object".to_owned()))
|
||||
}
|
||||
}
|
||||
impl Destructor for ScandirIterator {
|
||||
fn del(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<()> {
|
||||
// Emit ResourceWarning if the iterator is not yet exhausted/closed
|
||||
if zelf.entries.read().is_some() {
|
||||
let _ = crate::stdlib::warnings::warn(
|
||||
vm.ctx.exceptions.resource_warning,
|
||||
format!("unclosed scandir iterator {:?}", zelf.as_object()),
|
||||
1,
|
||||
vm,
|
||||
);
|
||||
zelf.close();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl SelfIter for ScandirIterator {}
|
||||
impl IterNext for ScandirIterator {
|
||||
fn next(zelf: &crate::Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
|
||||
@@ -1267,13 +1321,24 @@ pub(super) mod _os {
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn link(
|
||||
#[derive(FromArgs)]
|
||||
struct LinkArgs {
|
||||
#[pyarg(any)]
|
||||
src: OsPath,
|
||||
#[pyarg(any)]
|
||||
dst: OsPath,
|
||||
follow_symlinks: FollowSymlinks,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
#[pyarg(named, name = "follow_symlinks", optional)]
|
||||
follow_symlinks: OptionalArg<bool>,
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn link(args: LinkArgs, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let LinkArgs {
|
||||
src,
|
||||
dst,
|
||||
follow_symlinks,
|
||||
} = args;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
@@ -1282,11 +1347,8 @@ pub(super) mod _os {
|
||||
let dst_cstr = std::ffi::CString::new(dst.path.as_os_str().as_bytes())
|
||||
.map_err(|_| vm.new_value_error("embedded null byte"))?;
|
||||
|
||||
let flags = if follow_symlinks.0 {
|
||||
libc::AT_SYMLINK_FOLLOW
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let follow = follow_symlinks.into_option().unwrap_or(true);
|
||||
let flags = if follow { libc::AT_SYMLINK_FOLLOW } else { 0 };
|
||||
|
||||
let ret = unsafe {
|
||||
libc::linkat(
|
||||
@@ -1311,15 +1373,18 @@ pub(super) mod _os {
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
// On non-Unix platforms, ignore follow_symlinks if it's the default value
|
||||
// or raise NotImplementedError if explicitly set to False
|
||||
if !follow_symlinks.0 {
|
||||
return Err(vm.new_not_implemented_error(
|
||||
"link: follow_symlinks unavailable on this platform",
|
||||
));
|
||||
}
|
||||
let src_path = match follow_symlinks.into_option() {
|
||||
Some(true) => {
|
||||
// Explicit follow_symlinks=True: resolve symlinks
|
||||
fs::canonicalize(&src.path).unwrap_or_else(|_| PathBuf::from(src.path.clone()))
|
||||
}
|
||||
Some(false) | None => {
|
||||
// Default or explicit no-follow: native hard_link behavior
|
||||
PathBuf::from(src.path.clone())
|
||||
}
|
||||
};
|
||||
|
||||
fs::hard_link(&src.path, &dst.path).map_err(|err| {
|
||||
fs::hard_link(&src_path, &dst.path).map_err(|err| {
|
||||
let builder = err.to_os_error_builder(vm);
|
||||
let builder = builder.filename(src.filename(vm));
|
||||
let builder = builder.filename2(dst.filename(vm));
|
||||
@@ -1657,8 +1722,10 @@ pub(super) mod _os {
|
||||
|
||||
#[pyfunction]
|
||||
fn truncate(path: PyObjectRef, length: crt_fd::Offset, vm: &VirtualMachine) -> PyResult<()> {
|
||||
if let Ok(fd) = path.clone().try_into_value(vm) {
|
||||
return ftruncate(fd, length).map_err(|e| e.into_pyexception(vm));
|
||||
match path.clone().try_into_value::<crt_fd::Borrowed<'_>>(vm) {
|
||||
Ok(fd) => return ftruncate(fd, length).map_err(|e| e.into_pyexception(vm)),
|
||||
Err(e) if e.fast_isinstance(vm.ctx.exceptions.warning) => return Err(e),
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
|
||||
+215
-182
@@ -15,19 +15,36 @@ pub fn set_inheritable(fd: BorrowedFd<'_>, inheritable: bool) -> nix::Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pymodule(name = "posix", with(super::os::_os))]
|
||||
#[pymodule(name = "posix", with(
|
||||
super::os::_os,
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd",
|
||||
target_os = "android"
|
||||
))]
|
||||
posix_sched
|
||||
))]
|
||||
pub mod module {
|
||||
use crate::{
|
||||
AsObject, Py, PyObjectRef, PyPayload, PyResult, VirtualMachine,
|
||||
builtins::{PyDictRef, PyInt, PyListRef, PyStrRef, PyTupleRef, PyType, PyUtf8StrRef},
|
||||
AsObject, Py, PyObjectRef, PyResult, VirtualMachine,
|
||||
builtins::{PyDictRef, PyInt, PyListRef, PyStr, PyTupleRef},
|
||||
convert::{IntoPyException, ToPyObject, TryFromObject},
|
||||
exceptions::OSErrorBuilder,
|
||||
function::{Either, KwArgs, OptionalArg},
|
||||
ospath::{OsPath, OsPathOrFd},
|
||||
stdlib::os::{_os, DirFd, FollowSymlinks, SupportFunc, TargetIsDirectory, fs_metadata},
|
||||
types::{Constructor, Representable},
|
||||
utils::ToCString,
|
||||
stdlib::os::{
|
||||
_os, DirFd, FollowSymlinks, SupportFunc, TargetIsDirectory, fs_metadata,
|
||||
warn_if_bool_fd,
|
||||
},
|
||||
};
|
||||
#[cfg(any(
|
||||
target_os = "android",
|
||||
target_os = "freebsd",
|
||||
target_os = "linux",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
use crate::{builtins::PyStrRef, utils::ToCString};
|
||||
use alloc::ffi::CString;
|
||||
use bitflags::bitflags;
|
||||
use core::ffi::CStr;
|
||||
@@ -41,7 +58,7 @@ pub mod module {
|
||||
};
|
||||
use strum_macros::{EnumIter, EnumString};
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
#[pyattr]
|
||||
use libc::{SCHED_DEADLINE, SCHED_NORMAL};
|
||||
|
||||
@@ -278,6 +295,7 @@ pub mod module {
|
||||
|
||||
impl TryFromObject for BorrowedFd<'_> {
|
||||
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
|
||||
crate::stdlib::os::warn_if_bool_fd(&obj, vm)?;
|
||||
let fd = i32::try_from_object(vm, obj)?;
|
||||
if fd == -1 {
|
||||
return Err(io::Error::from_raw_os_error(libc::EBADF).into_pyexception(vm));
|
||||
@@ -443,6 +461,19 @@ pub mod module {
|
||||
environ
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn _create_environ(vm: &VirtualMachine) -> PyDictRef {
|
||||
use rustpython_common::os::ffi::OsStringExt;
|
||||
|
||||
let environ = vm.ctx.new_dict();
|
||||
for (key, value) in env::vars_os() {
|
||||
let key: PyObjectRef = vm.ctx.new_bytes(key.into_vec()).into();
|
||||
let value: PyObjectRef = vm.ctx.new_bytes(value.into_vec()).into();
|
||||
environ.set_item(&*key, value, vm).unwrap();
|
||||
}
|
||||
environ
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
pub(super) struct SymlinkArgs<'fd> {
|
||||
src: OsPath,
|
||||
@@ -483,8 +514,15 @@ pub mod module {
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
#[pyfunction]
|
||||
fn fchdir(fd: BorrowedFd<'_>, vm: &VirtualMachine) -> PyResult<()> {
|
||||
nix::unistd::fchdir(fd).map_err(|err| err.into_pyexception(vm))
|
||||
fn fchdir(fd: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
warn_if_bool_fd(&fd, vm)?;
|
||||
let fd = i32::try_from_object(vm, fd)?;
|
||||
let ret = unsafe { libc::fchdir(fd) };
|
||||
if ret == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::last_os_error().into_pyexception(vm))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
@@ -859,175 +897,6 @@ pub mod module {
|
||||
nix::sched::sched_yield().map_err(|e| e.into_pyexception(vm))
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pyclass(name = "sched_param")]
|
||||
#[derive(Debug, PyPayload)]
|
||||
struct SchedParam {
|
||||
sched_priority: PyObjectRef,
|
||||
}
|
||||
|
||||
impl TryFromObject for SchedParam {
|
||||
fn try_from_object(_vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
sched_priority: obj,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(with(Constructor, Representable))]
|
||||
impl SchedParam {
|
||||
#[pygetset]
|
||||
fn sched_priority(&self, vm: &VirtualMachine) -> PyObjectRef {
|
||||
self.sched_priority.clone().to_pyobject(vm)
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd",
|
||||
target_os = "android"
|
||||
))]
|
||||
#[cfg(not(target_env = "musl"))]
|
||||
fn try_to_libc(&self, vm: &VirtualMachine) -> PyResult<libc::sched_param> {
|
||||
use crate::AsObject;
|
||||
let priority_class = self.sched_priority.class();
|
||||
let priority_type = priority_class.name();
|
||||
let priority = self.sched_priority.clone();
|
||||
let value = priority.downcast::<PyInt>().map_err(|_| {
|
||||
vm.new_type_error(format!("an integer is required (got type {priority_type})"))
|
||||
})?;
|
||||
let sched_priority = value.try_to_primitive(vm)?;
|
||||
Ok(libc::sched_param { sched_priority })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
pub struct SchedParamArg {
|
||||
sched_priority: PyObjectRef,
|
||||
}
|
||||
|
||||
impl Constructor for SchedParam {
|
||||
type Args = SchedParamArg;
|
||||
|
||||
fn py_new(_cls: &Py<PyType>, arg: Self::Args, _vm: &VirtualMachine) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
sched_priority: arg.sched_priority,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Representable for SchedParam {
|
||||
#[inline]
|
||||
fn repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
|
||||
let sched_priority_repr = zelf.sched_priority.repr(vm)?;
|
||||
Ok(format!(
|
||||
"posix.sched_param(sched_priority = {})",
|
||||
sched_priority_repr.as_str()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd",
|
||||
target_os = "android"
|
||||
))]
|
||||
#[pyfunction]
|
||||
fn sched_getscheduler(pid: libc::pid_t, vm: &VirtualMachine) -> PyResult<i32> {
|
||||
let policy = unsafe { libc::sched_getscheduler(pid) };
|
||||
if policy == -1 {
|
||||
Err(vm.new_last_errno_error())
|
||||
} else {
|
||||
Ok(policy)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd",
|
||||
target_os = "android"
|
||||
))]
|
||||
#[derive(FromArgs)]
|
||||
struct SchedSetschedulerArgs {
|
||||
#[pyarg(positional)]
|
||||
pid: i32,
|
||||
#[pyarg(positional)]
|
||||
policy: i32,
|
||||
#[pyarg(positional)]
|
||||
sched_param_obj: crate::PyRef<SchedParam>,
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd",
|
||||
target_os = "android"
|
||||
))]
|
||||
#[cfg(not(target_env = "musl"))]
|
||||
#[pyfunction]
|
||||
fn sched_setscheduler(args: SchedSetschedulerArgs, vm: &VirtualMachine) -> PyResult<i32> {
|
||||
let libc_sched_param = args.sched_param_obj.try_to_libc(vm)?;
|
||||
let policy = unsafe { libc::sched_setscheduler(args.pid, args.policy, &libc_sched_param) };
|
||||
if policy == -1 {
|
||||
Err(vm.new_last_errno_error())
|
||||
} else {
|
||||
Ok(policy)
|
||||
}
|
||||
}
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd",
|
||||
target_os = "android"
|
||||
))]
|
||||
#[pyfunction]
|
||||
fn sched_getparam(pid: libc::pid_t, vm: &VirtualMachine) -> PyResult<SchedParam> {
|
||||
let param = unsafe {
|
||||
let mut param = core::mem::MaybeUninit::uninit();
|
||||
if -1 == libc::sched_getparam(pid, param.as_mut_ptr()) {
|
||||
return Err(vm.new_last_errno_error());
|
||||
}
|
||||
param.assume_init()
|
||||
};
|
||||
Ok(SchedParam {
|
||||
sched_priority: param.sched_priority.to_pyobject(vm),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd",
|
||||
target_os = "android"
|
||||
))]
|
||||
#[derive(FromArgs)]
|
||||
struct SchedSetParamArgs {
|
||||
#[pyarg(positional)]
|
||||
pid: i32,
|
||||
#[pyarg(positional)]
|
||||
sched_param_obj: crate::PyRef<SchedParam>,
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd",
|
||||
target_os = "android"
|
||||
))]
|
||||
#[cfg(not(target_env = "musl"))]
|
||||
#[pyfunction]
|
||||
fn sched_setparam(args: SchedSetParamArgs, vm: &VirtualMachine) -> PyResult<i32> {
|
||||
let libc_sched_param = args.sched_param_obj.try_to_libc(vm)?;
|
||||
let ret = unsafe { libc::sched_setparam(args.pid, &libc_sched_param) };
|
||||
if ret == -1 {
|
||||
Err(vm.new_last_errno_error())
|
||||
} else {
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn get_inheritable(fd: BorrowedFd<'_>, vm: &VirtualMachine) -> PyResult<bool> {
|
||||
let flags = fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD);
|
||||
@@ -1183,7 +1052,7 @@ pub mod module {
|
||||
let path = path.into_cstring(vm)?;
|
||||
|
||||
let argv = vm.extract_elements_with(argv.as_ref(), |obj| {
|
||||
PyStrRef::try_from_object(vm, obj)?.to_cstring(vm)
|
||||
OsPath::try_from_object(vm, obj)?.into_cstring(vm)
|
||||
})?;
|
||||
let argv: Vec<&CStr> = argv.iter().map(|entry| entry.as_c_str()).collect();
|
||||
|
||||
@@ -1209,7 +1078,7 @@ pub mod module {
|
||||
let path = path.into_cstring(vm)?;
|
||||
|
||||
let argv = vm.extract_elements_with(argv.as_ref(), |obj| {
|
||||
PyStrRef::try_from_object(vm, obj)?.to_cstring(vm)
|
||||
OsPath::try_from_object(vm, obj)?.into_cstring(vm)
|
||||
})?;
|
||||
let argv: Vec<&CStr> = argv.iter().map(|entry| entry.as_c_str()).collect();
|
||||
|
||||
@@ -2068,7 +1937,11 @@ pub mod module {
|
||||
let i = match obj.downcast::<PyInt>() {
|
||||
Ok(int) => int.try_to_primitive(vm)?,
|
||||
Err(obj) => {
|
||||
let s = PyStrRef::try_from_object(vm, obj)?;
|
||||
let s = obj.downcast::<PyStr>().map_err(|_| {
|
||||
vm.new_type_error(
|
||||
"configuration names must be strings or integers".to_owned(),
|
||||
)
|
||||
})?;
|
||||
s.as_str()
|
||||
.parse::<PathconfVar>()
|
||||
.map_err(|_| vm.new_value_error("unrecognized configuration name"))?
|
||||
@@ -2462,7 +2335,11 @@ pub mod module {
|
||||
let i = match obj.downcast::<PyInt>() {
|
||||
Ok(int) => int.try_to_primitive(vm)?,
|
||||
Err(obj) => {
|
||||
let s = PyUtf8StrRef::try_from_object(vm, obj)?;
|
||||
let s = obj.downcast::<PyStr>().map_err(|_| {
|
||||
vm.new_type_error(
|
||||
"configuration names must be strings or integers".to_owned(),
|
||||
)
|
||||
})?;
|
||||
s.as_str().parse::<SysconfVar>().or_else(|_| {
|
||||
if s.as_str() == "SC_PAGESIZE" {
|
||||
Ok(SysconfVar::SC_PAGESIZE)
|
||||
@@ -2630,3 +2507,159 @@ pub mod module {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "netbsd",
|
||||
target_os = "freebsd",
|
||||
target_os = "android"
|
||||
))]
|
||||
#[pymodule(sub)]
|
||||
mod posix_sched {
|
||||
use crate::{
|
||||
AsObject, Py, PyObjectRef, PyResult, VirtualMachine,
|
||||
builtins::{PyInt, PyTupleRef},
|
||||
convert::ToPyObject,
|
||||
function::FuncArgs,
|
||||
types::PyStructSequence,
|
||||
};
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct SchedParamArgs {
|
||||
#[pyarg(any)]
|
||||
sched_priority: PyObjectRef,
|
||||
}
|
||||
|
||||
#[pystruct_sequence_data]
|
||||
struct SchedParamData {
|
||||
pub sched_priority: PyObjectRef,
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
#[pystruct_sequence(name = "sched_param", module = "posix", data = "SchedParamData")]
|
||||
struct PySchedParam;
|
||||
|
||||
#[pyclass(with(PyStructSequence))]
|
||||
impl PySchedParam {
|
||||
#[pyslot]
|
||||
fn slot_new(
|
||||
cls: crate::builtins::PyTypeRef,
|
||||
args: FuncArgs,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
use crate::PyPayload;
|
||||
let SchedParamArgs { sched_priority } = args.bind(vm)?;
|
||||
let items = vec![sched_priority];
|
||||
crate::builtins::PyTuple::new_unchecked(items.into_boxed_slice())
|
||||
.into_ref_with_type(vm, cls)
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
#[extend_class]
|
||||
fn extend_pyclass(ctx: &crate::vm::Context, class: &'static Py<crate::builtins::PyType>) {
|
||||
// Override __reduce__ to return (type, (sched_priority,))
|
||||
// instead of the generic structseq (type, ((sched_priority,),)).
|
||||
// The trait's extend_class checks contains_key before setting default.
|
||||
const SCHED_PARAM_REDUCE: crate::function::PyMethodDef =
|
||||
crate::function::PyMethodDef::new_const(
|
||||
"__reduce__",
|
||||
|zelf: crate::PyRef<crate::builtins::PyTuple>,
|
||||
vm: &VirtualMachine|
|
||||
-> PyTupleRef {
|
||||
vm.new_tuple((zelf.class().to_owned(), (zelf[0].clone(),)))
|
||||
},
|
||||
crate::function::PyMethodFlags::METHOD,
|
||||
None,
|
||||
);
|
||||
class.set_attr(
|
||||
ctx.intern_str("__reduce__"),
|
||||
SCHED_PARAM_REDUCE.to_proper_method(class, ctx),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_env = "musl"))]
|
||||
fn convert_sched_param(obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult<libc::sched_param> {
|
||||
use crate::{builtins::PyTuple, class::StaticType};
|
||||
if !obj.fast_isinstance(PySchedParam::static_type()) {
|
||||
return Err(vm.new_type_error("must have a sched_param object".to_owned()));
|
||||
}
|
||||
let tuple = obj.downcast_ref::<PyTuple>().unwrap();
|
||||
let priority = tuple[0].clone();
|
||||
let priority_type = priority.class().name().to_string();
|
||||
let value = priority.downcast::<PyInt>().map_err(|_| {
|
||||
vm.new_type_error(format!("an integer is required (got type {priority_type})"))
|
||||
})?;
|
||||
let sched_priority = value.try_to_primitive(vm)?;
|
||||
Ok(libc::sched_param { sched_priority })
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn sched_getscheduler(pid: libc::pid_t, vm: &VirtualMachine) -> PyResult<i32> {
|
||||
let policy = unsafe { libc::sched_getscheduler(pid) };
|
||||
if policy == -1 {
|
||||
Err(vm.new_last_errno_error())
|
||||
} else {
|
||||
Ok(policy)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct SchedSetschedulerArgs {
|
||||
#[pyarg(positional)]
|
||||
pid: i32,
|
||||
#[pyarg(positional)]
|
||||
policy: i32,
|
||||
#[pyarg(positional)]
|
||||
sched_param: PyObjectRef,
|
||||
}
|
||||
|
||||
#[cfg(not(target_env = "musl"))]
|
||||
#[pyfunction]
|
||||
fn sched_setscheduler(args: SchedSetschedulerArgs, vm: &VirtualMachine) -> PyResult<i32> {
|
||||
let libc_sched_param = convert_sched_param(&args.sched_param, vm)?;
|
||||
let policy = unsafe { libc::sched_setscheduler(args.pid, args.policy, &libc_sched_param) };
|
||||
if policy == -1 {
|
||||
Err(vm.new_last_errno_error())
|
||||
} else {
|
||||
Ok(policy)
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn sched_getparam(pid: libc::pid_t, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
|
||||
let param = unsafe {
|
||||
let mut param = core::mem::MaybeUninit::uninit();
|
||||
if -1 == libc::sched_getparam(pid, param.as_mut_ptr()) {
|
||||
return Err(vm.new_last_errno_error());
|
||||
}
|
||||
param.assume_init()
|
||||
};
|
||||
Ok(PySchedParam::from_data(
|
||||
SchedParamData {
|
||||
sched_priority: param.sched_priority.to_pyobject(vm),
|
||||
},
|
||||
vm,
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct SchedSetParamArgs {
|
||||
#[pyarg(positional)]
|
||||
pid: i32,
|
||||
#[pyarg(positional)]
|
||||
sched_param: PyObjectRef,
|
||||
}
|
||||
|
||||
#[cfg(not(target_env = "musl"))]
|
||||
#[pyfunction]
|
||||
fn sched_setparam(args: SchedSetParamArgs, vm: &VirtualMachine) -> PyResult<i32> {
|
||||
let libc_sched_param = convert_sched_param(&args.sched_param, vm)?;
|
||||
let ret = unsafe { libc::sched_setparam(args.pid, &libc_sched_param) };
|
||||
if ret == -1 {
|
||||
Err(vm.new_last_errno_error())
|
||||
} else {
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,6 +101,10 @@ pub(crate) mod _signal {
|
||||
#[pyattr]
|
||||
pub use libc::{SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM};
|
||||
|
||||
#[cfg(windows)]
|
||||
#[pyattr]
|
||||
const SIGBREAK: i32 = 21; // _SIGBREAK
|
||||
|
||||
// Windows-specific control events for GenerateConsoleCtrlEvent
|
||||
#[cfg(windows)]
|
||||
#[pyattr]
|
||||
@@ -201,6 +205,21 @@ pub(crate) mod _signal {
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Option<PyObjectRef>> {
|
||||
signal::assert_in_range(signalnum, vm)?;
|
||||
#[cfg(windows)]
|
||||
{
|
||||
const VALID_SIGNALS: &[i32] = &[
|
||||
libc::SIGINT,
|
||||
libc::SIGILL,
|
||||
libc::SIGFPE,
|
||||
libc::SIGSEGV,
|
||||
libc::SIGTERM,
|
||||
SIGBREAK,
|
||||
libc::SIGABRT,
|
||||
];
|
||||
if !VALID_SIGNALS.contains(&signalnum) {
|
||||
return Err(vm.new_value_error(format!("signal number {} out of range", signalnum)));
|
||||
}
|
||||
}
|
||||
let signal_handlers = vm
|
||||
.signal_handlers
|
||||
.as_deref()
|
||||
@@ -482,18 +501,20 @@ pub(crate) mod _signal {
|
||||
// On Windows, only certain signals are supported
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use crate::convert::IntoPyException;
|
||||
// Windows supports: SIGINT(2), SIGILL(4), SIGFPE(8), SIGSEGV(11), SIGTERM(15), SIGABRT(22)
|
||||
// Windows supports: SIGINT(2), SIGILL(4), SIGFPE(8), SIGSEGV(11), SIGTERM(15), SIGBREAK(21), SIGABRT(22)
|
||||
const VALID_SIGNALS: &[i32] = &[
|
||||
libc::SIGINT,
|
||||
libc::SIGILL,
|
||||
libc::SIGFPE,
|
||||
libc::SIGSEGV,
|
||||
libc::SIGTERM,
|
||||
SIGBREAK,
|
||||
libc::SIGABRT,
|
||||
];
|
||||
if !VALID_SIGNALS.contains(&signalnum) {
|
||||
return Err(std::io::Error::from_raw_os_error(libc::EINVAL).into_pyexception(vm));
|
||||
return Err(vm
|
||||
.new_errno_error(libc::EINVAL, "Invalid argument")
|
||||
.upcast());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,6 +558,7 @@ pub(crate) mod _signal {
|
||||
libc::SIGFPE => "Floating-point exception",
|
||||
libc::SIGSEGV => "Segmentation fault",
|
||||
libc::SIGTERM => "Terminated",
|
||||
SIGBREAK => "Break",
|
||||
libc::SIGABRT => "Aborted",
|
||||
_ => return Ok(None),
|
||||
};
|
||||
@@ -573,6 +595,7 @@ pub(crate) mod _signal {
|
||||
libc::SIGFPE,
|
||||
libc::SIGSEGV,
|
||||
libc::SIGTERM,
|
||||
SIGBREAK,
|
||||
libc::SIGABRT,
|
||||
] {
|
||||
set.add(vm.ctx.new_int(signum).into(), vm)?;
|
||||
|
||||
@@ -437,7 +437,9 @@ mod _winapi {
|
||||
return Err(vm.new_runtime_error("environment changed size during iteration"));
|
||||
}
|
||||
|
||||
let mut out = widestring::WideString::new();
|
||||
// Deduplicate case-insensitive keys, keeping the last value
|
||||
use std::collections::HashMap;
|
||||
let mut last_entry: HashMap<String, widestring::WideString> = HashMap::new();
|
||||
for (k, v) in keys.into_iter().zip(values.into_iter()) {
|
||||
let k = PyStrRef::try_from_object(vm, k)?;
|
||||
let k = k.as_str();
|
||||
@@ -449,10 +451,22 @@ mod _winapi {
|
||||
if k.is_empty() || k[1..].contains('=') {
|
||||
return Err(vm.new_value_error("illegal environment variable name"));
|
||||
}
|
||||
out.push_str(k);
|
||||
out.push_str("=");
|
||||
out.push_str(v);
|
||||
out.push_str("\0");
|
||||
let key_upper = k.to_uppercase();
|
||||
let mut entry = widestring::WideString::new();
|
||||
entry.push_str(k);
|
||||
entry.push_str("=");
|
||||
entry.push_str(v);
|
||||
entry.push_str("\0");
|
||||
last_entry.insert(key_upper, entry);
|
||||
}
|
||||
|
||||
// Sort by uppercase key for case-insensitive ordering
|
||||
let mut entries: Vec<(String, widestring::WideString)> = last_entry.into_iter().collect();
|
||||
entries.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
let mut out = widestring::WideString::new();
|
||||
for (_, entry) in entries {
|
||||
out.push(entry);
|
||||
}
|
||||
out.push_str("\0");
|
||||
Ok(out.into_vec())
|
||||
|
||||
Reference in new issue
Block a user