1 // -*- C++ -*- 2 // Filesystem utils for the C++ library testsuite. 3 // 4 // Copyright (C) 2014-2021 Free Software Foundation, Inc. 5 // 6 // This file is part of the GNU ISO C++ Library. This library is free 7 // software; you can redistribute it and/or modify it under the 8 // terms of the GNU General Public License as published by the 9 // Free Software Foundation; either version 3, or (at your option) 10 // any later version. 11 // 12 // This library is distributed in the hope that it will be useful, 13 // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 // GNU General Public License for more details. 16 // 17 // You should have received a copy of the GNU General Public License along 18 // with this library; see the file COPYING3. If not see 19 // <http://www.gnu.org/licenses/>. 20 // 21 22 #ifndef _TESTSUITE_FS_H 23 #define _TESTSUITE_FS_H 1 24 25 // Assume we want std::filesystem in C++17, unless USE_FILESYSTEM_TS defined: 26 #if __cplusplus >= 201703L && ! defined USE_FILESYSTEM_TS 27 #include <filesystem> 28 namespace test_fs = std::filesystem; 29 #else 30 #include <experimental/filesystem> 31 namespace test_fs = std::experimental::filesystem; 32 #endif 33 #include <algorithm> 34 #include <fstream> 35 #include <string> 36 #include <cstdio> 37 #include <unistd.h> // unlink, close, getpid 38 39 #if defined(_GNU_SOURCE) || _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200112L 40 #include <stdlib.h> // mkstemp 41 #else 42 #include <random> // std::random_device 43 #endif 44 45 namespace __gnu_test 46 { 47 #define PATH_CHK(p1, p2, fn) \ 48 if ( p1.fn() != p2.fn() ) \ 49 throw test_fs::filesystem_error("comparing '" #fn "' failed", p1, p2, \ 50 std::make_error_code(std::errc::invalid_argument) ) 51 52 void compare_paths(const test_fs::path & p1,const test_fs::path & p2)53 compare_paths(const test_fs::path& p1, 54 const test_fs::path& p2) 55 { 56 PATH_CHK( p1, p2, native ); 57 PATH_CHK( p1, p2, string ); 58 PATH_CHK( p1, p2, empty ); 59 PATH_CHK( p1, p2, has_root_path ); 60 PATH_CHK( p1, p2, has_root_name ); 61 PATH_CHK( p1, p2, has_root_directory ); 62 PATH_CHK( p1, p2, has_relative_path ); 63 PATH_CHK( p1, p2, has_parent_path ); 64 PATH_CHK( p1, p2, has_filename ); 65 PATH_CHK( p1, p2, has_stem ); 66 PATH_CHK( p1, p2, has_extension ); 67 PATH_CHK( p1, p2, is_absolute ); 68 PATH_CHK( p1, p2, is_relative ); 69 auto d1 = std::distance(p1.begin(), p1.end()); 70 auto d2 = std::distance(p2.begin(), p2.end()); 71 if (d1 != d2) 72 throw test_fs::filesystem_error( 73 "distance(begin1, end1) != distance(begin2, end2)", p1, p2, 74 std::make_error_code(std::errc::invalid_argument) ); 75 if (!std::equal(p1.begin(), p1.end(), p2.begin())) 76 throw test_fs::filesystem_error( 77 "!equal(begin1, end1, begin2)", p1, p2, 78 std::make_error_code(std::errc::invalid_argument) ); 79 80 } 81 82 const std::string test_paths[] = { 83 "", "/", "//", "/.", "/./", "/a", "/a/", "/a//", "/a/b/c/d", "/a//b", 84 "a", "a/b", "a/b/", "a/b/c", "a/b/c.d", "a/b/..", "a/b/c.", "a/b/.c" 85 }; 86 87 test_fs::path root_path()88 root_path() 89 { 90 #if defined(__MINGW32__) || defined(__MINGW64__) 91 return L"c:/"; 92 #else 93 return "/"; 94 #endif 95 } 96 97 // This is NOT supposed to be a secure way to get a unique name! 98 // We just need a path that doesn't exist for testing purposes. 99 test_fs::path 100 nonexistent_path(std::string file = __builtin_FILE()) 101 { 102 // Include the caller's filename to help identify tests that fail to 103 // clean up the files they create. 104 // Remove .cc extension: 105 if (file.length() > 3 && file.compare(file.length() - 3, 3, ".cc") == 0) 106 file.resize(file.length() - 3); 107 // And directory: 108 auto pos = file.find_last_of("/\\"); 109 if (pos != file.npos) 110 file.erase(0, pos+1); 111 112 test_fs::path p; 113 #if defined(_GNU_SOURCE) || _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200112L 114 char tmp[] = "filesystem-test.XXXXXX"; 115 int fd = ::mkstemp(tmp); 116 if (fd == -1) 117 throw test_fs::filesystem_error("mkstemp failed", 118 std::error_code(errno, std::generic_category())); 119 ::unlink(tmp); 120 ::close(fd); 121 if (!file.empty()) 122 file.insert(0, 1, '-'); 123 file.insert(0, tmp); 124 p = file; 125 #else 126 if (file.length() > 64) 127 file.resize(64); 128 char buf[128]; 129 static unsigned counter = std::random_device{}(); 130 #if _GLIBCXX_USE_C99_STDIO 131 std::snprintf(buf, 128, 132 #else 133 std::sprintf(buf, 134 #endif 135 "filesystem-test.%u.%lu-%s", counter++, (unsigned long) ::getpid(), 136 file.c_str()); 137 p = buf; 138 #endif 139 return p; 140 } 141 142 // RAII helper to remove a file on scope exit. 143 struct scoped_file 144 { 145 using path_type = test_fs::path; 146 147 enum adopt_file_t { adopt_file }; 148 149 explicit pathscoped_file150 scoped_file(const path_type& p = nonexistent_path()) : path(p) 151 { std::ofstream{p.c_str()}; } 152 scoped_filescoped_file153 scoped_file(path_type p, adopt_file_t) : path(p) { } 154 ~scoped_filescoped_file155 ~scoped_file() { if (!path.empty()) remove(path); } 156 157 scoped_file(scoped_file&&) = default; 158 scoped_file& operator=(scoped_file&&) = default; 159 160 path_type path; 161 }; 162 163 } // namespace __gnu_test 164 #endif 165