From 81a54a27585d3fa7687e6586929dcf114d199706 Mon Sep 17 00:00:00 2001 From: Son Seong Jun <166786347+SonAIengine@users.noreply.github.com> Date: Sun, 25 Oct 2026 22:35:13 +0900 Subject: [PATCH] =?UTF-8?q?Windows=EC=97=90=EC=84=9C=20=EB=94=94=EB=A0=89?= =?UTF-8?q?=ED=84=B0=EB=A6=AC=EB=A5=BC=20=ED=8C=8C=EC=9D=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=97=B4=20=EB=95=8C=EB=8F=84=20not=5Fa=5Ffile=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A5=98=ED=96=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #101 이후 main CI의 x86_64-pc-windows-msvc job에서 failures_name_missing_paths_and_wrong_kinds_without_values가 실패했다 (expected NotAFile, got Unavailable). Unix는 디렉터리를 읽기용으로 열면 성공한 뒤 metadata 검사에서 NotAFile이 되지만, Windows는 open 자체가 access denied로 실패해서 "거부"로 분류됐다. - 열기 오류 분류에 Directory를 추가했다. NotFound가 아닌 오류일 때 symlink_metadata가 디렉터리(symlink 아님)를 가리키면 Directory로 본다. symlink는 계속 거부다. - 파일 열기(read-text, read-text-lines, stat 대상 파일)는 Directory를 not_a_file로, 디렉터리 열기 경로(list-directory, 쓰기 부모)는 기존처럼 거부로 다룬다. - 같은 job의 나머지 테스트는 --no-fail-fast로 모두 통과했다. 실패는 이 1건뿐이었다. Co-Authored-By: Claude Opus 5.5 --- .../src/atomic_commit.rs | 4 +++- crates/xgen-adapter-filesystem/src/path.rs | 22 +++++++++++-------- crates/xgen-adapter-filesystem/src/query.rs | 3 ++- .../xgen-adapter-filesystem/src/read_text.rs | 4 +++- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/crates/xgen-adapter-filesystem/src/atomic_commit.rs b/crates/xgen-adapter-filesystem/src/atomic_commit.rs index 2aad9b0..53c1f1d 100644 --- a/crates/xgen-adapter-filesystem/src/atomic_commit.rs +++ b/crates/xgen-adapter-filesystem/src/atomic_commit.rs @@ -146,7 +146,9 @@ fn open_parent(root: &Dir, path: &RelativePath) -> Result<(Dir, String), AtomicC OpenErrorClass::Missing | OpenErrorClass::NotADirectory => { AtomicCommitFailure::ParentNotFound } - OpenErrorClass::Refused => AtomicCommitFailure::OpenParent, + OpenErrorClass::Directory | OpenErrorClass::Refused => { + AtomicCommitFailure::OpenParent + } } })?; if is_windows_reparse_point( diff --git a/crates/xgen-adapter-filesystem/src/path.rs b/crates/xgen-adapter-filesystem/src/path.rs index 918bba8..51c7db4 100644 --- a/crates/xgen-adapter-filesystem/src/path.rs +++ b/crates/xgen-adapter-filesystem/src/path.rs @@ -73,6 +73,9 @@ pub(crate) enum OpenErrorClass { Missing, /// Something other than a directory exists at the name. NotADirectory, + /// A directory exists at the name. Some platforms refuse to open a directory as a file, so + /// this is how a file open learns that the name has the wrong kind. + Directory, /// The name exists but is refused, such as a symbolic link, or the error is not classified. Refused, } @@ -85,15 +88,16 @@ pub(crate) fn classify_open_error( name: &str, error: &std::io::Error, ) -> OpenErrorClass { - match error.kind() { - std::io::ErrorKind::NotFound => OpenErrorClass::Missing, - std::io::ErrorKind::NotADirectory - if !parent - .symlink_metadata(name) - .is_ok_and(|metadata| metadata.file_type().is_symlink()) => - { - OpenErrorClass::NotADirectory - } + if error.kind() == std::io::ErrorKind::NotFound { + return OpenErrorClass::Missing; + } + let file_type = parent + .symlink_metadata(name) + .map(|metadata| metadata.file_type()); + match file_type { + Ok(file_type) if file_type.is_symlink() => OpenErrorClass::Refused, + _ if error.kind() == std::io::ErrorKind::NotADirectory => OpenErrorClass::NotADirectory, + Ok(file_type) if file_type.is_dir() => OpenErrorClass::Directory, _ => OpenErrorClass::Refused, } } diff --git a/crates/xgen-adapter-filesystem/src/query.rs b/crates/xgen-adapter-filesystem/src/query.rs index 72b9a52..65d1056 100644 --- a/crates/xgen-adapter-filesystem/src/query.rs +++ b/crates/xgen-adapter-filesystem/src/query.rs @@ -1009,7 +1009,7 @@ fn open_directory(root: &Dir, path: &RelativePath) -> Result // means the requested path does not exist. OpenErrorClass::NotADirectory if index == last => QueryFailure::NotADirectory, OpenErrorClass::Missing | OpenErrorClass::NotADirectory => QueryFailure::NotFound, - OpenErrorClass::Refused => QueryFailure::Open, + OpenErrorClass::Directory | OpenErrorClass::Refused => QueryFailure::Open, } })?; if is_windows_reparse_point(&directory.dir_metadata().map_err(|_| QueryFailure::Open)?) { @@ -1035,6 +1035,7 @@ fn open_regular_file(root: &Dir, path: &RelativePath) -> Result QueryFailure::NotFound, + OpenErrorClass::Directory => QueryFailure::NotAFile, OpenErrorClass::Refused => QueryFailure::Open, } })?; diff --git a/crates/xgen-adapter-filesystem/src/read_text.rs b/crates/xgen-adapter-filesystem/src/read_text.rs index c065f6f..6d9321b 100644 --- a/crates/xgen-adapter-filesystem/src/read_text.rs +++ b/crates/xgen-adapter-filesystem/src/read_text.rs @@ -270,10 +270,12 @@ impl ReadFailure { } /// A missing component, or a parent component that is not a directory, means the path does not -/// exist. Every other open error stays unclassified beyond "unavailable". +/// exist, and a directory is not a file. Every other open error stays unclassified beyond +/// "unavailable". fn open_failure(parent: &Dir, name: &str, error: &std::io::Error) -> ReadFailure { match classify_open_error(parent, name, error) { OpenErrorClass::Missing | OpenErrorClass::NotADirectory => ReadFailure::NotFound, + OpenErrorClass::Directory => ReadFailure::NonRegular, OpenErrorClass::Refused => ReadFailure::Open, } }