-
Notifications
You must be signed in to change notification settings - Fork 178
Differences to Standard Filesystem Implementations
This page tries to collect test cases from the unit tests that currently fail when run against an existing C++17 std::filesystem implementation of more current enough compilers.
[Update: I wrote a more detailed blog post about the differences between various std::filesystem
implementations (and as that differences to ghc::filesystem too).]
Node: The test code is based on Catch2 macros.
All tests pass on ghc::filesystem
because they are from its test suite. I have by no means the authority nor knowledge
of all mails, forums, chats and other channels outside of the standard that have lead the developers to their implementation, and I might have missed an LWG that is relevant here, so I'm not saying mine is the way to do things, and I already changed some behaviour to follow other
implementations, as I'm interested in a common sense of what should happen. But some results are still irritating for me, so I keep my implementation the way it is, until I have better arguments otherwise.
I would like to thank Jonathan Wakely at GCCs Bugzilla, as he pointed me into the right directions a few times, was helpful and quick in fixing actual issues I found and reported and Nico Josuttis for his WG21 P1164R0 to clear up the create_directory
/create_directories
issue!
CHECK( fs::u8path("\xc3\xa4/\xe2\x82\xac\xf0\x9d\x84\x9e").u16string() == std::u16string(u"\u00E4/\u20AC\U0001D11E") )
due to unexpected exception with messages:
filesystem error: Cannot convert character sequence: Illegal byte sequence
GCC 8.1, 8.2, 8.3: I had observed this with GCC 8.1/8.2 on Ubuntu 18.04 and macOS, and GCC 8.3.0 on Wandbox. The compiler doesn't recognize the valid Unicode codepoint U+1D11E. (see GCC issue 90281)
For an std::ostringstream
filled by:
std::ostringstream os;
os << fs::path("/root/foo bar");
and this failure:
CHECK( os.str() == "\"\\\\root\\\\foo bar\"" )
with expansion:
""/root/foo bar"" == ""\\root\\foo bar""
CHECK( os.str() == "\"\\\\root\\\\foo\\\"bar\"" )
with expansion:
""/root/foo\"bar"" == ""\\root\\foo\"bar""
MSVC++ 14.16: The standard describes the effect of the path inserter as: "Equivalent to: os << quoted(p.string<charT, traits>());" and fs::path::string
is one of the native format observers that MSVC returns a backslashed path for. Still the stream inserter outputs the generic format (that Windows API will accept) and this seems inconsistent.
std::error_code ec;
generateFile("foo");
fs::directory_entry none;
CHECK_THROWS_AS(none.refresh(), fs::filesystem_error);
ec.clear();
CHECK_NOTHROW(none.refresh(ec));
CHECK(ec);
CHECK_THROWS_AS(de.assign(""), fs::filesystem_error);
ec.clear();
CHECK_NOTHROW(de.assign("", ec));
CHECK(ec);
CHECK_THROWS_AS(de.replace_filename("bar"), fs::filesystem_error);
CHECK_NOTHROW(de.replace_filename("foo"));
ec.clear();
CHECK_NOTHROW(de.replace_filename("bar", ec));
CHECK(ec);
GCC 9.2.0: Using refresh
or assign
on empty fs::path
instances leads to no exception but ec
set, when using the error_code
variants. Also GCC doesn't throw an exception on replace_filename
with a non-existent name, while reporting an error in the error_code
variant. That seems inconsistent and ghc::filesystem
sees this as an error in all variants.
std::error_code ec;
CHECK(fs::absolute("") == fs::current_path() / "");
CHECK(fs::absolute("", ec) == fs::current_path() / "");
CHECK(!ec);
GCC 9.2.0: On Linux/macOS GCC throws an exception with filesystem error: cannot make absolute path: Invalid argument []
or sets the error_code
in this case, besides the standard note: "Implementations are strongly encouraged to not query secondary storage, and not consider !exists(p) an error." and the example implementation from the standard: "For POSIX-based operating systems, absolute(p) is simply current_path()/p.".
CHECK_THROWS_AS( fs::canonical(""), fs::filesystem_error )
because no exception was thrown where one was expected:
Clang 7, GCC <8.3, MSVC++ 14.16: This seems to be a violation of the standard, as 30.10.15.2 (4) specifies
"!exists(p)
is an error." and the compiler reports false
for exists("")
.
My guess is, they first call absolute(p)
and after that, p
is not empty and exists(absolute(""))
is normally true
as it is the current directory path.
With GCC 8.3.0, the GCC implementation also treats this as an error.
REQUIRE_NOTHROW( fs::copy("dir1", "dir3", fs::copy_options::create_symlinks | fs::copy_options::recursive) )
due to unexpected exception with message:
filesystem error: in copy: Is a directory [dir1] [dir3]
Clang 7, GCC 8, MSVC++ 14.16: The compilers complain about a copy call with fs::copy_options::recursive
combined
with fs::copy_options::create_symlinks
or fs::copy_options::create_hard_links
if the
source is a directory. There is nothing in the standard that forbids this combination
and it is the only way to deep-copy a tree while only create links for the files.
There is LWG #2682 that supports this
interpretation, but the issue ignores the usefulness of the combination with recursive
and part of the justification for the proposed solution is "we did it so for almost two years".
But this makes fs::copy
with fs::copy_options::create_symlinks
or fs::copy_options::create_hard_links
just a more complicated syntax for the fs::create_symlink
or fs::create_hardlink
operation to create
a single link. I'm not sure, if that was the intention of the original writing.
As there is another issue related to copy, with a different take on the description,
I keep my version the way I read the description, as it is not contradicting the standard and useful. I might need
to change that, if the final solution the LWG comes up with will lead to a definitive wording in the standard.
Note: As the current draft leading to C++20 explicitly defines this as an error, I guess, I will adapt to this behaviour, even if I think it is a strange reduced usability of the create_symlink
option.
CHECK_THROWS_AS( fs::equivalent("foo", "foo3"), fs::filesystem_error )
because no exception was thrown where one was expected:
This test expects LWG #2937 result conformance.
CHECK( ec )
with expansion:
system:0
with message:
This test expects LWG #2937 result conformance.
CHECK_THROWS_AS( fs::equivalent("foo3", "foo"), fs::filesystem_error )
because no exception was thrown where one was expected:
This test expects LWG #2937 result conformance.
CHECK( ec )
with expansion:
system:0
with message:
This test expects LWG #2937 result conformance.
GCC 8.x, GCC 9.2: With LWG #2937 was specified,
that it is an error if either of the given path objects doesn't resolve to an existing file and ghc::filesystem
follows this rule (but there is a define at the top, where this can be disabled). Clang 7 follows 2937