Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit 742d51b
Author: Steffen Schuemann <[email protected]>
Date:   Sat Apr 27 10:17:15 2019 +0200

    Fixed ctest call.

commit f96b655
Author: Steffen Schümann <[email protected]>
Date:   Sat Apr 27 09:42:20 2019 +0200

    Fix in canonical root path handling.

commit 1cd346d
Author: gulrak <[email protected]>
Date:   Sat Apr 27 06:22:13 2019 +0200

    Work on MingW issues and additional tests.
  • Loading branch information
gulrak committed Apr 27, 2019
1 parent 55cf530 commit c75a663
Show file tree
Hide file tree
Showing 5 changed files with 342 additions and 35 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,5 @@ before_script:
script:
- export VERBOSE=1
- cmake --build . --config Release
- ctest
- ctest -C Release -E Windows

41 changes: 30 additions & 11 deletions include/ghc/filesystem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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] = '\\';
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand Down
6 changes: 5 additions & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
Expand Down
225 changes: 225 additions & 0 deletions test/cmake/ParseAndAddCatchTests.cmake
Original file line number Diff line number Diff line change
@@ -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()
Loading

0 comments on commit c75a663

Please sign in to comment.