Squashed commit of the following:

commit 742d51b24cce19915e10f0d1f97cf49ff408b66d
Author: Steffen Schuemann <s.schuemann@pobox.com>
Date:   Sat Apr 27 10:17:15 2019 +0200

    Fixed ctest call.

commit f96b65562d42cc6801ba3855dc603117e4ce864a
Author: Steffen Schümann <s.schuemann@pobox.com>
Date:   Sat Apr 27 09:42:20 2019 +0200

    Fix in canonical root path handling.

commit 1cd346d23e87b274d32b9d56a8b6327e36be4147
Author: gulrak <s.schuemann@pobox.com>
Date:   Sat Apr 27 06:22:13 2019 +0200

    Work on MingW issues and additional tests.
This commit is contained in:
Steffen Schuemann 2019-04-27 10:36:00 +02:00
parent 55cf5307c6
commit c75a663ec9
5 changed files with 342 additions and 35 deletions

View File

@ -56,5 +56,5 @@ before_script:
script:
- export VERBOSE=1
- cmake --build . --config Release
- ctest
- ctest -C Release -E Windows

View File

@ -75,7 +75,7 @@
#elif defined(GHC_FILESYSTEM_FWD)
#define GHC_INLINE
#ifdef GHC_OS_WINDOWS
#define GHC_FS_API extern
#define GHC_FS_API extern
#define GHC_FS_API_CLASS
#else
#define GHC_FS_API extern
@ -1259,8 +1259,7 @@ namespace detail {
GHC_INLINE bool startsWith(const std::string& what, const std::string& with)
{
return with.length() <= what.length()
&& equal(with.begin(), with.end(), what.begin());
return with.length() <= what.length() && equal(with.begin(), with.end(), what.begin());
}
GHC_INLINE void postprocess_path_with_format(path::string_type& p, path::format fmt)
@ -1277,11 +1276,11 @@ GHC_INLINE void postprocess_path_with_format(path::string_type& p, path::format
case path::auto_format:
case path::native_format:
#endif
if(startsWith(p, std::string("\\\\?\\"))) {
if (startsWith(p, std::string("\\\\?\\"))) {
// remove Windows long filename marker
p.erase(0, 4);
if(startsWith(p, std::string("UNC\\"))) {
p.erase(0,2);
if (startsWith(p, std::string("UNC\\"))) {
p.erase(0, 2);
p[0] = '\\';
}
}
@ -1356,7 +1355,15 @@ namespace detail {
GHC_INLINE bool compare_no_case(const char* str1, const char* str2)
{
#ifdef GHC_OS_WINDOWS
# ifdef __GNUC__
while (::tolower((unsigned char)*str1) == ::tolower((unsigned char)*str2++)) {
if (*str1++ == 0)
return false;
}
return true;
# else
return ::_stricmp(str1, str2);
# endif
#else
return ::strcasecmp(str1, str2);
#endif
@ -1530,10 +1537,8 @@ GHC_INLINE path resolveSymlink(const path& p, std::error_code& ec)
char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE] = {0};
REPARSE_DATA_BUFFER& reparseData = *(REPARSE_DATA_BUFFER*)buffer;
ULONG bufferUsed;
ULONG dwError;
path result;
if (DeviceIoControl(file.get(), FSCTL_GET_REPARSE_POINT, 0, 0, &reparseData, sizeof(buffer), &bufferUsed, 0)) {
dwError = NOERROR;
if (IsReparseTagMicrosoft(reparseData.ReparseTag)) {
switch (reparseData.ReparseTag) {
case IO_REPARSE_TAG_SYMLINK:
@ -2080,9 +2085,9 @@ GHC_INLINE void path::swap(path& rhs) noexcept
GHC_INLINE const path::string_type& path::native() const
{
#ifdef GHC_OS_WINDOWS
if(is_absolute() && _path.length() >= MAX_PATH) {
if (is_absolute() && _path.length() > MAX_PATH - 10) {
// expand long Windows filenames with marker
if(has_root_name() && _path[0] == '/') {
if (has_root_name() && _path[0] == '/') {
_native_cache = "\\\\?\\UNC" + _path.substr(1);
}
else {
@ -2850,7 +2855,10 @@ GHC_INLINE path canonical(const path& p, std::error_code& ec)
result = result.parent_path();
continue;
}
else if ((result/pe).string().length() <= root.string().length()) {
result /= pe;
continue;
}
auto sls = symlink_status(result / pe, ec);
if (ec) {
return path();
@ -3700,6 +3708,16 @@ GHC_INLINE void permissions(const path& p, perms prms, perm_options opts, std::e
}
}
#ifdef GHC_OS_WINDOWS
# ifdef __GNUC__
auto oldAttr = GetFileAttributesW(p.wstring().c_str());
if (oldAttr != INVALID_FILE_ATTRIBUTES) {
DWORD newAttr = ((prms & perms::owner_write) == perms::owner_write) ? oldAttr & ~FILE_ATTRIBUTE_READONLY : oldAttr | FILE_ATTRIBUTE_READONLY;
if (oldAttr == newAttr || SetFileAttributesW(p.wstring().c_str(), newAttr)) {
return;
}
}
ec = std::error_code(::GetLastError(), std::system_category());
# else
int mode = 0;
if ((prms & perms::owner_read) == perms::owner_read) {
mode |= _S_IREAD;
@ -3710,6 +3728,7 @@ GHC_INLINE void permissions(const path& p, perms prms, perm_options opts, std::e
if (::_wchmod(p.wstring().c_str(), mode) != 0) {
ec = std::error_code(::GetLastError(), std::system_category());
}
# endif
#else
if ((opts & perm_options::nofollow) != perm_options::nofollow) {
if (::chmod(p.c_str(), static_cast<mode_t>(prms)) != 0) {

View File

@ -1,4 +1,8 @@
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/")
set(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS ON)
include(ParseAndAddCatchTests)
add_executable(filesystem_test filesystem_test.cpp catch.hpp)
target_link_libraries(filesystem_test ghc_filesystem)
target_compile_options(filesystem_test PRIVATE
@ -13,7 +17,7 @@ if(CMAKE_GENERATOR STREQUAL Xcode)
target_compile_options(filesystem_test_cov PRIVATE "$<$<CONFIG:DEBUG>:--coverage>")
target_link_libraries(filesystem_test_cov PUBLIC ghc_filesystem PRIVATE --coverage)
endif()
add_test(filesystem_test filesystem_test)
ParseAndAddCatchTests(filesystem_test filesystem_test)
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" AND (CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 7.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0))
if(APPLE)

View File

@ -0,0 +1,225 @@
#==================================================================================================#
# supported macros #
# - TEST_CASE, #
# - SCENARIO, #
# - TEST_CASE_METHOD, #
# - CATCH_TEST_CASE, #
# - CATCH_SCENARIO, #
# - CATCH_TEST_CASE_METHOD. #
# #
# Usage #
# 1. make sure this module is in the path or add this otherwise: #
# set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") #
# 2. make sure that you've enabled testing option for the project by the call: #
# enable_testing() #
# 3. add the lines to the script for testing target (sample CMakeLists.txt): #
# project(testing_target) #
# set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") #
# enable_testing() #
# #
# find_path(CATCH_INCLUDE_DIR "catch.hpp") #
# include_directories(${INCLUDE_DIRECTORIES} ${CATCH_INCLUDE_DIR}) #
# #
# file(GLOB SOURCE_FILES "*.cpp") #
# add_executable(${PROJECT_NAME} ${SOURCE_FILES}) #
# #
# include(ParseAndAddCatchTests) #
# ParseAndAddCatchTests(${PROJECT_NAME}) #
# #
# The following variables affect the behavior of the script: #
# #
# PARSE_CATCH_TESTS_VERBOSE (Default OFF) #
# -- enables debug messages #
# PARSE_CATCH_TESTS_NO_HIDDEN_TESTS (Default OFF) #
# -- excludes tests marked with [!hide], [.] or [.foo] tags #
# PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME (Default ON) #
# -- adds fixture class name to the test name #
# PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME (Default ON) #
# -- adds cmake target name to the test name #
# PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS (Default OFF) #
# -- causes CMake to rerun when file with tests changes so that new tests will be discovered #
# #
# One can also set (locally) the optional variable OptionalCatchTestLauncher to precise the way #
# a test should be run. For instance to use test MPI, one can write #
# set(OptionalCatchTestLauncher ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${NUMPROC}) #
# just before calling this ParseAndAddCatchTests function #
# #
# The AdditionalCatchParameters optional variable can be used to pass extra argument to the test #
# command. For example, to include successful tests in the output, one can write #
# set(AdditionalCatchParameters --success) #
# #
# After the script, the ParseAndAddCatchTests_TESTS property for the target, and for each source #
# file in the target is set, and contains the list of the tests extracted from that target, or #
# from that file. This is useful, for example to add further labels or properties to the tests. #
# #
#==================================================================================================#
if (CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.8)
message(FATAL_ERROR "ParseAndAddCatchTests requires CMake 2.8.8 or newer")
endif()
option(PARSE_CATCH_TESTS_VERBOSE "Print Catch to CTest parser debug messages" OFF)
option(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS "Exclude tests with [!hide], [.] or [.foo] tags" OFF)
option(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME "Add fixture class name to the test name" ON)
option(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME "Add target name to the test name" ON)
option(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS "Add test file to CMAKE_CONFIGURE_DEPENDS property" OFF)
function(ParseAndAddCatchTests_PrintDebugMessage)
if(PARSE_CATCH_TESTS_VERBOSE)
message(STATUS "ParseAndAddCatchTests: ${ARGV}")
endif()
endfunction()
# This removes the contents between
# - block comments (i.e. /* ... */)
# - full line comments (i.e. // ... )
# contents have been read into '${CppCode}'.
# !keep partial line comments
function(ParseAndAddCatchTests_RemoveComments CppCode)
string(ASCII 2 CMakeBeginBlockComment)
string(ASCII 3 CMakeEndBlockComment)
string(REGEX REPLACE "/\\*" "${CMakeBeginBlockComment}" ${CppCode} "${${CppCode}}")
string(REGEX REPLACE "\\*/" "${CMakeEndBlockComment}" ${CppCode} "${${CppCode}}")
string(REGEX REPLACE "${CMakeBeginBlockComment}[^${CMakeEndBlockComment}]*${CMakeEndBlockComment}" "" ${CppCode} "${${CppCode}}")
string(REGEX REPLACE "\n[ \t]*//+[^\n]+" "\n" ${CppCode} "${${CppCode}}")
set(${CppCode} "${${CppCode}}" PARENT_SCOPE)
endfunction()
# Worker function
function(ParseAndAddCatchTests_ParseFile SourceFile TestTarget)
# If SourceFile is an object library, do not scan it (as it is not a file). Exit without giving a warning about a missing file.
if(SourceFile MATCHES "\\\$<TARGET_OBJECTS:.+>")
ParseAndAddCatchTests_PrintDebugMessage("Detected OBJECT library: ${SourceFile} this will not be scanned for tests.")
return()
endif()
# According to CMake docs EXISTS behavior is well-defined only for full paths.
get_filename_component(SourceFile ${SourceFile} ABSOLUTE)
if(NOT EXISTS ${SourceFile})
message(WARNING "Cannot find source file: ${SourceFile}")
return()
endif()
ParseAndAddCatchTests_PrintDebugMessage("parsing ${SourceFile}")
file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME)
# Remove block and fullline comments
ParseAndAddCatchTests_RemoveComments(Contents)
# Find definition of test names
string(REGEX MATCHALL "[ \t]*(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^\)]+\\)+[ \t\n]*{+[ \t]*(//[^\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \t]*[0-9]+)*" Tests "${Contents}")
if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests)
ParseAndAddCatchTests_PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property")
set_property(
DIRECTORY
APPEND
PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile}
)
endif()
foreach(TestName ${Tests})
# Strip newlines
string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}")
# Get test type and fixture if applicable
string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}")
string(REGEX MATCH "(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}")
string(REGEX REPLACE "${TestType}\\([ \t]*" "" TestFixture "${TestTypeAndFixture}")
# Get string parts of test definition
string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}")
# Strip wrapping quotation marks
string(REGEX REPLACE "^\"(.*)\"$" "\\1" TestStrings "${TestStrings}")
string(REPLACE "\";\"" ";" TestStrings "${TestStrings}")
# Validate that a test name and tags have been provided
list(LENGTH TestStrings TestStringsLength)
if(TestStringsLength GREATER 2 OR TestStringsLength LESS 1)
message(FATAL_ERROR "You must provide a valid test name and tags for all tests in ${SourceFile}")
endif()
# Assign name and tags
list(GET TestStrings 0 Name)
if("${TestType}" STREQUAL "SCENARIO")
set(Name "Scenario: ${Name}")
endif()
if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND TestFixture)
set(CTestName "${TestFixture}:${Name}")
else()
set(CTestName "${Name}")
endif()
if(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME)
set(CTestName "${TestTarget}:${CTestName}")
endif()
# add target to labels to enable running all tests added from this target
set(Labels ${TestTarget})
if(TestStringsLength EQUAL 2)
list(GET TestStrings 1 Tags)
string(TOLOWER "${Tags}" Tags)
# remove target from labels if the test is hidden
if("${Tags}" MATCHES ".*\\[!?(hide|\\.)\\].*")
list(REMOVE_ITEM Labels ${TestTarget})
endif()
string(REPLACE "]" ";" Tags "${Tags}")
string(REPLACE "[" "" Tags "${Tags}")
else()
# unset tags variable from previous loop
unset(Tags)
endif()
list(APPEND Labels ${Tags})
set(HiddenTagFound OFF)
foreach(label ${Labels})
string(REGEX MATCH "^!hide|^\\." result ${label})
if(result)
set(HiddenTagFound ON)
break()
endif(result)
endforeach(label)
if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_LESS "3.9")
ParseAndAddCatchTests_PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label")
else()
ParseAndAddCatchTests_PrintDebugMessage("Adding test \"${CTestName}\"")
if(Labels)
ParseAndAddCatchTests_PrintDebugMessage("Setting labels to ${Labels}")
endif()
# Escape commas in the test spec
string(REPLACE "," "\\," Name ${Name})
# Add the test and set its properties
add_test(NAME "\"${CTestName}\"" COMMAND ${OptionalCatchTestLauncher} $<TARGET_FILE:${TestTarget}> ${Name} ${AdditionalCatchParameters})
# Old CMake versions do not document VERSION_GREATER_EQUAL, so we use VERSION_GREATER with 3.8 instead
if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_GREATER "3.8")
ParseAndAddCatchTests_PrintDebugMessage("Setting DISABLED test property")
set_tests_properties("\"${CTestName}\"" PROPERTIES DISABLED ON)
else()
set_tests_properties("\"${CTestName}\"" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran"
LABELS "${Labels}")
endif()
set_property(
TARGET ${TestTarget}
APPEND
PROPERTY ParseAndAddCatchTests_TESTS "\"${CTestName}\"")
set_property(
SOURCE ${SourceFile}
APPEND
PROPERTY ParseAndAddCatchTests_TESTS "\"${CTestName}\"")
endif()
endforeach()
endfunction()
# entry point
function(ParseAndAddCatchTests TestTarget)
ParseAndAddCatchTests_PrintDebugMessage("Started parsing ${TestTarget}")
get_target_property(SourceFiles ${TestTarget} SOURCES)
ParseAndAddCatchTests_PrintDebugMessage("Found the following sources: ${SourceFiles}")
foreach(SourceFile ${SourceFiles})
ParseAndAddCatchTests_ParseFile(${SourceFile} ${TestTarget})
endforeach()
ParseAndAddCatchTests_PrintDebugMessage("Finished parsing ${TestTarget}")
endfunction()

View File

@ -39,8 +39,10 @@
#include <random>
#include <sstream>
#include <thread>
#ifdef WIN32
#define NOMINMAX
#if defined(WIN32) || defined(_WIN32)
#ifndef __GNUC__
#define NOMINMAX 1
#endif
#include <windows.h>
#else
#include <sys/socket.h>
@ -70,7 +72,6 @@ using fstream = std::fstream;
#ifdef GHC_FILESYSTEM_FWD_TEST
#include <ghc/fs_fwd.hpp>
#else
#define NOMINMAX
#include <ghc/filesystem.hpp>
#endif
namespace fs {
@ -174,32 +175,57 @@ static void generateFile(const fs::path& pathname, int withSize = -1)
}
#ifdef GHC_OS_WINDOWS
inline bool isWow64Proc()
{
typedef BOOL(WINAPI * IsWow64Process_t)(HANDLE, PBOOL);
BOOL bIsWow64 = FALSE;
auto fnIsWow64Process = (IsWow64Process_t)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
if (NULL != fnIsWow64Process) {
if (!fnIsWow64Process(GetCurrentProcess(), &bIsWow64)) {
bIsWow64 = FALSE;
}
}
return bIsWow64;
}
static bool is_symlink_creation_supported()
{
bool result = true;
HKEY key;
REGSAM flags = KEY_READ;
#ifdef _WIN64
flags |= KEY_WOW64_64KEY;
#else
BOOL f64 = FALSE;
if (IsWow64Process(GetCurrentProcess(), &f64) && f64) {
#elif defined(KEY_WOW64_64KEY)
if (isWow64Proc()) {
flags |= KEY_WOW64_64KEY;
}
else {
flags |= KEY_WOW64_32KEY;
}
#else
result = false;
#endif
auto err = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock", 0, flags, &key);
if (err != ERROR_SUCCESS) {
return false;
if (result) {
auto err = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock", 0, flags, &key);
if (err == ERROR_SUCCESS) {
DWORD val = 0, size = sizeof(DWORD);
err = RegQueryValueExW(key, L"AllowDevelopmentWithoutDevLicense", 0, NULL, reinterpret_cast<LPBYTE>(&val), &size);
RegCloseKey(key);
if (err != ERROR_SUCCESS) {
result = false;
}
else {
result = (val != 0);
}
}
else {
result = false;
}
}
DWORD val = 0, size = sizeof(DWORD);
err = RegQueryValueExW(key, L"AllowDevelopmentWithoutDevLicense", 0, NULL, reinterpret_cast<LPBYTE>(&val), &size);
RegCloseKey(key);
if (err != ERROR_SUCCESS) {
return false;
if (!result) {
std::clog << "Warning: Symlink creation not supported." << std::endl;
}
return val != 0;
return result;
}
#else
static bool is_symlink_creation_supported()
@ -1058,7 +1084,7 @@ TEST_CASE("30.10.12 class directory_entry", "[filesystem][directory_entry][fs.di
CHECK(de.hard_link_count() == 1);
}
TEST_CASE("30.10.13 class directory_iterator", "[filesystem][directory_iterator]")
TEST_CASE("30.10.13 class directory_iterator", "[filesystem][directory_iterator][fs.class.directory_iterator]")
{
{
TemporaryDirectory t;
@ -1107,7 +1133,7 @@ TEST_CASE("30.10.13 class directory_iterator", "[filesystem][directory_iterator]
}
}
TEST_CASE("30.10.14 class recursive_directory_iterator", "[filesystem][recursive_directory_iterator]")
TEST_CASE("30.10.14 class recursive_directory_iterator", "[filesystem][recursive_directory_iterator][fs.class.rec.dir.itr]")
{
{
TemporaryDirectory t;
@ -1246,7 +1272,7 @@ TEST_CASE("30.10.15.3 copy", "[filesystem][operations][fs.op.copy]")
CHECK(fs::exists("dir4/file2"));
CHECK(fs::exists("dir4/dir2/file3"));
}
if(is_symlink_creation_supported()) {
if (is_symlink_creation_supported()) {
TemporaryDirectory t(TempOpt::change_path);
std::error_code ec;
fs::create_directory("dir1");
@ -1427,7 +1453,7 @@ TEST_CASE("30.10.15.7 create_directory", "[filesystem][operations][fs.op.create_
TEST_CASE("30.10.15.8 create_directory_symlink", "[filesystem][operations][fs.op.create_directory_symlink]")
{
if(is_symlink_creation_supported()) {
if (is_symlink_creation_supported()) {
TemporaryDirectory t;
fs::create_directory(t.path() / "dir1");
generateFile(t.path() / "dir1/test1");
@ -1463,7 +1489,7 @@ TEST_CASE("30.10.15.9 create_hard_link", "[filesystem][operations][fs.op.create_
TEST_CASE("30.10.15.10 create_symlink", "[filesystem][operations][fs.op.create_symlink]")
{
if(is_symlink_creation_supported()) {
if (is_symlink_creation_supported()) {
TemporaryDirectory t;
fs::create_directory(t.path() / "dir1");
generateFile(t.path() / "dir1/test1");
@ -1770,7 +1796,7 @@ TEST_CASE_METHOD(FileTypeMixFixture, "30.10.15.18 is_directory", "[filesystem][o
CHECK(!fs::is_directory(fs::file_status(fs::file_type::unknown)));
}
TEST_CASE("30.10.15.19 is_empty", "[filesystem][options][fs.op.is_empty]")
TEST_CASE("30.10.15.19 is_empty", "[filesystem][operations][fs.op.is_empty]")
{
TemporaryDirectory t(TempOpt::change_path);
std::error_code ec;
@ -2038,7 +2064,7 @@ TEST_CASE("30.10.15.27 proximate", "[filesystem][operations][fs.op.proximate]")
TEST_CASE("30.10.15.28 read_symlink", "[filesystem][operations][fs.op.read_symlink]")
{
if(is_symlink_creation_supported()) {
if (is_symlink_creation_supported()) {
TemporaryDirectory t(TempOpt::change_path);
std::error_code ec;
generateFile("foo");
@ -2272,3 +2298,36 @@ TEST_CASE("30.10.15.39 weakly_canonical", "[filesystem][operations][fs.op.weakly
CHECK(fs::weakly_canonical(rel / "d1/../f1/../f2") == dir / "f2");
}
}
#ifdef GHC_OS_WINDOWS
TEST_CASE("Windows: Long filename support", "[filesystem][path][fs.path.win.long]")
{
TemporaryDirectory t(TempOpt::change_path);
char c = 'A';
fs::path dir = fs::current_path();
for (; c <= 'Z'; ++c) {
std::string part = std::string(16, c);
dir /= part;
CHECK_NOTHROW(fs::create_directory(dir));
CHECK(fs::exists(dir));
generateFile(dir / "f0");
CHECK(fs::exists(dir / "f0"));
std::string native = dir.u8string();
if (native.substr(0, 4) == "\\\\?\\") {
break;
}
}
CHECK(c <= 'Z');
}
TEST_CASE("Windows: UNC path support", "[filesystem][path][fs.path.win.unc]")
{
std::error_code ec;
fs::path p(R"(\\localhost\c$\Windows)");
auto symstat = fs::symlink_status(p, ec);
CHECK(!ec);
auto p2 = fs::canonical(p, ec);
CHECK(!ec);
CHECK(p2 == p);
}
#endif