Skip to content

Commit 9ac362f

Browse files
committed
Enable test_os
1 parent 3ef134b commit 9ac362f

3 files changed

Lines changed: 202 additions & 80 deletions

File tree

src/core/IronPython.Modules/nt.cs

Lines changed: 92 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,8 @@ public static bool access(CodeContext context, object? path, int mode, [ParamDic
285285
#if FEATURE_FILESYSTEM
286286

287287
public static void chdir([NotNone] string path) {
288-
if (String.IsNullOrEmpty(path)) {
289-
throw PythonOps.OSError(PythonExceptions._OSError.ERROR_INVALID_NAME, "Path cannot be an empty string", path, PythonExceptions._OSError.ERROR_INVALID_NAME);
288+
if (string.IsNullOrEmpty(path)) {
289+
throw GetOsOrWinError(PythonErrno.ENOENT, PythonExceptions._OSError.ERROR_INVALID_NAME, path);
290290
}
291291

292292
try {
@@ -553,7 +553,7 @@ public static PythonList listdir(CodeContext/*!*/ context, string? path = null)
553553
}
554554

555555
if (path == string.Empty) {
556-
throw PythonOps.OSError(PythonExceptions._OSError.ERROR_PATH_NOT_FOUND, "The system cannot find the path specified", path, PythonExceptions._OSError.ERROR_PATH_NOT_FOUND);
556+
throw GetOsOrWinError(PythonErrno.ENOENT, PythonExceptions._OSError.ERROR_PATH_NOT_FOUND, path);
557557
}
558558

559559
#if !NETFRAMEWORK
@@ -628,7 +628,7 @@ internal DirEntry(CodeContext context, FileSystemInfo info, bool asBytes) {
628628

629629
[LightThrowing]
630630
public object? inode() {
631-
var obj = stat(follow_symlinks: false);
631+
var obj = PythonNT.stat(info.FullName, new Dictionary<string, object>());
632632
if (obj is stat_result res) return res.st_ino;
633633
return obj;
634634
}
@@ -640,9 +640,15 @@ internal DirEntry(CodeContext context, FileSystemInfo info, bool asBytes) {
640640
public bool is_symlink() => info.Attributes.HasFlag(FileAttributes.ReparsePoint) ? throw new NotImplementedException() : false;
641641

642642
[LightThrowing]
643-
public object? stat(bool follow_symlinks = true) => PythonNT.stat(info.FullName, new Dictionary<string, object>());
643+
public object? stat(bool follow_symlinks = true) {
644+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
645+
return statWindowsImpl(info);
646+
return PythonNT.stat(info.FullName, new Dictionary<string, object>());
647+
}
644648

645649
public string __repr__(CodeContext context) => $"<DirEntry {PythonOps.Repr(context, name)}>";
650+
651+
public object __fspath__(CodeContext context) => path;
646652
}
647653

648654
[PythonType, PythonHidden]
@@ -675,6 +681,8 @@ internal ScandirIterator(CodeContext context, IEnumerable<FileSystemInfo> list,
675681

676682
[PythonHidden]
677683
public void Reset() => enumerator.Reset();
684+
685+
public void close() => Dispose();
678686
}
679687

680688
public static ScandirIterator scandir(CodeContext context, string? path = null)
@@ -689,7 +697,7 @@ private static IEnumerable<FileSystemInfo> ScandirHelper(CodeContext context, st
689697
}
690698

691699
if (path == string.Empty) {
692-
throw PythonOps.OSError(PythonExceptions._OSError.ERROR_PATH_NOT_FOUND, "The system cannot find the path specified", path, PythonExceptions._OSError.ERROR_PATH_NOT_FOUND);
700+
throw GetOsOrWinError(PythonErrno.ENOENT, PythonExceptions._OSError.ERROR_PATH_NOT_FOUND, path);
693701
}
694702

695703
#if !NETFRAMEWORK
@@ -1396,8 +1404,8 @@ public PythonTuple __reduce__() {
13961404
}
13971405
}
13981406

1399-
private static bool HasExecutableExtension(string path) {
1400-
string extension = Path.GetExtension(path).ToLower(CultureInfo.InvariantCulture);
1407+
private static bool HasExecutableExtension(string extension) {
1408+
extension = extension.ToLower(CultureInfo.InvariantCulture);
14011409
return (extension == ".exe" || extension == ".dll" || extension == ".com" || extension == ".bat");
14021410
}
14031411

@@ -1456,50 +1464,22 @@ public static object stat([NotNone] string path, [ParamDictionary] IDictionary<s
14561464
VerifyPath(path, functionName: nameof(stat), argName: nameof(path));
14571465

14581466
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
1467+
if (IsNulFile(path)) {
1468+
return new stat_result(0x2000);
1469+
}
1470+
14591471
try {
14601472
FileInfo fi = new FileInfo(path);
1461-
int mode = 0;
1462-
long size;
1463-
1464-
if (IsNulFile(path)) {
1465-
return new stat_result(0x2000);
1466-
} else if (Directory.Exists(path)) {
1467-
size = 0;
1468-
mode = 0x4000 | S_IEXEC;
1469-
} else if (File.Exists(path)) {
1470-
size = fi.Length;
1471-
mode = 0x8000;
1472-
if (HasExecutableExtension(path)) {
1473-
mode |= S_IEXEC;
1474-
}
1475-
} else {
1476-
return LightExceptions.Throw(PythonOps.OSError(0, "file does not exist", path, PythonExceptions._OSError.ERROR_PATH_NOT_FOUND));
1477-
}
1478-
1479-
mode |= S_IREAD;
1480-
if ((fi.Attributes & FileAttributes.ReadOnly) == 0) {
1481-
mode |= S_IWRITE;
1473+
if (fi.Exists) {
1474+
return statWindowsImpl(fi);
14821475
}
1483-
1484-
const long epochDifferenceLong = 62135596800 * TimeSpan.TicksPerSecond;
1485-
1486-
// 1 tick = 100 nanoseconds
1487-
long st_atime_ns = (fi.LastAccessTime.ToUniversalTime().Ticks - epochDifferenceLong) * 100;
1488-
long st_mtime_ns = (fi.LastWriteTime.ToUniversalTime().Ticks - epochDifferenceLong) * 100;
1489-
long st_ctime_ns = (fi.CreationTime.ToUniversalTime().Ticks - epochDifferenceLong) * 100;
1490-
1491-
ulong fileIdx = 0;
1492-
var handle = CreateFile(path, FILE_READ_ATTRIBUTES, 0, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, IntPtr.Zero);
1493-
if (!handle.IsInvalid) {
1494-
if (GetFileInformationByHandle(handle, out BY_HANDLE_FILE_INFORMATION fileInfo)) {
1495-
fileIdx = (((ulong)fileInfo.FileIndexHigh) << 32) + fileInfo.FileIndexLow;
1496-
}
1497-
handle.Close();
1476+
DirectoryInfo di = new DirectoryInfo(path);
1477+
if (di.Exists) {
1478+
return statWindowsImpl(di);
14981479
}
1499-
1500-
return new stat_result(mode, fileIdx, size, st_atime_ns, st_mtime_ns, st_ctime_ns);
1480+
return LightExceptions.Throw(GetOsOrWinError(PythonErrno.ENOENT, PythonExceptions._OSError.ERROR_FILE_NOT_FOUND, path));
15011481
} catch (ArgumentException) {
1502-
return LightExceptions.Throw(PythonOps.OSError(0, "The path is invalid", path, PythonExceptions._OSError.ERROR_INVALID_NAME));
1482+
return LightExceptions.Throw(GetOsOrWinError(PythonErrno.ENOENT, PythonExceptions._OSError.ERROR_INVALID_NAME, path));
15031483
} catch (Exception e) {
15041484
return LightExceptions.Throw(ToPythonException(e, path));
15051485
}
@@ -1510,6 +1490,46 @@ public static object stat([NotNone] string path, [ParamDictionary] IDictionary<s
15101490
}
15111491
}
15121492

1493+
[SupportedOSPlatform("windows")]
1494+
private static stat_result statWindowsImpl(FileSystemInfo fsi) {
1495+
int mode = 0;
1496+
long size;
1497+
if (fsi is FileInfo fi) {
1498+
size = fi.Length;
1499+
mode = 0x8000;
1500+
if (HasExecutableExtension(fi.Extension)) {
1501+
mode |= S_IEXEC;
1502+
}
1503+
} else {
1504+
Debug.Assert(fsi is DirectoryInfo);
1505+
size = 0;
1506+
mode = 0x4000 | S_IEXEC;
1507+
}
1508+
1509+
mode |= S_IREAD;
1510+
if ((fsi.Attributes & FileAttributes.ReadOnly) == 0) {
1511+
mode |= S_IWRITE;
1512+
}
1513+
1514+
const long epochDifferenceLong = 62135596800 * TimeSpan.TicksPerSecond;
1515+
1516+
// 1 tick = 100 nanoseconds
1517+
long st_atime_ns = (fsi.LastAccessTime.ToUniversalTime().Ticks - epochDifferenceLong) * 100;
1518+
long st_mtime_ns = (fsi.LastWriteTime.ToUniversalTime().Ticks - epochDifferenceLong) * 100;
1519+
long st_ctime_ns = (fsi.CreationTime.ToUniversalTime().Ticks - epochDifferenceLong) * 100;
1520+
1521+
ulong fileIdx = 0;
1522+
var handle = CreateFile(fsi.FullName, FILE_READ_ATTRIBUTES, 0, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, IntPtr.Zero);
1523+
if (!handle.IsInvalid) {
1524+
if (GetFileInformationByHandle(handle, out BY_HANDLE_FILE_INFORMATION fileInfo)) {
1525+
fileIdx = (((ulong)fileInfo.FileIndexHigh) << 32) + fileInfo.FileIndexLow;
1526+
}
1527+
handle.Close();
1528+
}
1529+
1530+
return new stat_result(mode, fileIdx, size, st_atime_ns, st_mtime_ns, st_ctime_ns);
1531+
}
1532+
15131533
[LightThrowing, Documentation("")]
15141534
public static object stat(CodeContext context, [NotNone] Bytes path, [ParamDictionary] IDictionary<string, object> dict)
15151535
=> stat(path.ToFsString(context), dict);
@@ -1663,7 +1683,20 @@ public static void remove([NotNone] string path, [ParamDictionary] IDictionary<s
16631683
throw PythonOps.TypeError("'{0}' is an invalid keyword argument for this function", key);
16641684
}
16651685
}
1666-
UnlinkWorker(path);
1686+
1687+
if (path.IndexOfAny(Path.GetInvalidPathChars()) != -1 || Path.GetFileName(path).IndexOfAny(Path.GetInvalidFileNameChars()) != -1) {
1688+
throw GetOsOrWinError(PythonErrno.ENOENT, PythonExceptions._OSError.ERROR_INVALID_NAME, path);
1689+
}
1690+
1691+
bool existing = File.Exists(path); // will return false also on access denied
1692+
try {
1693+
File.Delete(path); // will throw an exception on access denied, no exception on file not existing
1694+
} catch (Exception e) {
1695+
throw ToPythonException(e, path);
1696+
}
1697+
if (!existing) { // file was not existing in the first place
1698+
throw GetOsOrWinError(PythonErrno.ENOENT, PythonExceptions._OSError.ERROR_FILE_NOT_FOUND, path);
1699+
}
16671700
}
16681701

16691702
[Documentation("")]
@@ -1685,24 +1718,6 @@ public static void unlink(CodeContext context, [NotNone] Bytes path, [ParamDicti
16851718
[Documentation("")]
16861719
public static void unlink(CodeContext context, object? path, [ParamDictionary] IDictionary<string, object> kwargs)
16871720
=> unlink(ConvertToFsString(context, path, nameof(path)), kwargs);
1688-
1689-
private static void UnlinkWorker(string path) {
1690-
if (path == null) {
1691-
throw new ArgumentNullException(nameof(path));
1692-
} else if (path.IndexOfAny(Path.GetInvalidPathChars()) != -1 || Path.GetFileName(path).IndexOfAny(Path.GetInvalidFileNameChars()) != -1) {
1693-
throw PythonOps.OSError(PythonExceptions._OSError.ERROR_INVALID_NAME, "The filename, directory name, or volume label syntax is incorrect", path, PythonExceptions._OSError.ERROR_INVALID_NAME);
1694-
}
1695-
1696-
bool existing = File.Exists(path); // will return false also on access denied
1697-
try {
1698-
File.Delete(path); // will throw an exception on access denied, no exception on file not existing
1699-
} catch (Exception e) {
1700-
throw ToPythonException(e, path);
1701-
}
1702-
if (!existing) { // file was not existing in the first place
1703-
throw PythonOps.OSError(PythonExceptions._OSError.ERROR_FILE_NOT_FOUND, "The system cannot find the file specified", path, PythonExceptions._OSError.ERROR_FILE_NOT_FOUND);
1704-
}
1705-
}
17061721
#endif
17071722

17081723
#if FEATURE_PROCESS
@@ -2127,15 +2142,13 @@ private static Exception ToPythonException(Exception e, string? filename = null)
21272142
message = e.Message;
21282143
isWindowsError = true;
21292144
} else if (e is UnauthorizedAccessException unauth) {
2130-
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
2131-
GetWin32Error(PythonExceptions._OSError.ERROR_ACCESS_DENIED, filename) :
2132-
GetOsError(PythonErrno.EACCES, filename);
2145+
return GetOsOrWinError(PythonErrno.EACCES, PythonExceptions._OSError.ERROR_ACCESS_DENIED, filename);
21332146
} else {
21342147
var ioe = e as IOException;
21352148
Exception? pe = IOExceptionToPythonException(ioe, error, filename);
21362149
if (pe != null) return pe;
21372150

2138-
errorCode = System.Runtime.InteropServices.Marshal.GetHRForException(e);
2151+
errorCode = Marshal.GetHRForException(e);
21392152

21402153
if ((errorCode & ~0xfff) == (unchecked((int)0x80070000))) {
21412154
// Win32 HR, translate HR to Python error code if possible, otherwise
@@ -2310,19 +2323,21 @@ private static bool TryGetShellCommand(string command, [NotNullWhen(true)] out s
23102323

23112324
#endif
23122325

2313-
private static Exception DirectoryExistsError(string? filename) {
2326+
private static Exception DirectoryExistsError(string? filename)
2327+
=> GetOsOrWinError(PythonErrno.EEXIST, PythonExceptions._OSError.ERROR_ALREADY_EXISTS, filename);
2328+
2329+
internal static Exception GetOsError(int errno, string? filename = null, string? filename2 = null)
2330+
=> PythonOps.OSError(errno, strerror(errno), filename, null, filename2);
2331+
2332+
internal static Exception GetOsOrWinError(int errno, int winerror, string? filename = null, string? filename2 = null) {
23142333
#if FEATURE_NATIVE
23152334
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
2316-
return GetWin32Error(PythonExceptions._OSError.ERROR_ALREADY_EXISTS, filename);
2335+
return GetWin32Error(winerror, filename, filename2);
23172336
}
23182337
#endif
2319-
return GetOsError(PythonErrno.EEXIST, filename);
2338+
return PythonOps.OSError(errno, strerror(errno), filename, null, filename2);
23202339
}
23212340

2322-
2323-
internal static Exception GetOsError(int errno, string? filename = null, string? filename2 = null)
2324-
=> PythonOps.OSError(errno, strerror(errno), filename, null, filename2);
2325-
23262341
#if FEATURE_NATIVE || FEATURE_CTYPES
23272342

23282343
[SupportedOSPlatform("windows")]

tests/IronPython.Tests/Cases/CPythonCasesManifest.ini

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -640,9 +640,6 @@ RunCondition=$(IS_POSIX)
640640
Ignore=true
641641
Reason=unittest.case.SkipTest: os.openpty() not available.
642642

643-
[CPython.test_os]
644-
Ignore=true
645-
646643
[CPython.test_ossaudiodev] # Module has been removed in 3.13 - https://github.com/IronLanguages/ironpython3/issues/1352
647644
Ignore=true
648645
Reason=unittest.case.SkipTest: No module named 'ossaudiodev'

0 commit comments

Comments
 (0)