Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/dep/googletest/src/gtest-filepath.cc
4804 views
1
// Copyright 2008, Google Inc.
2
// All rights reserved.
3
//
4
// Redistribution and use in source and binary forms, with or without
5
// modification, are permitted provided that the following conditions are
6
// met:
7
//
8
// * Redistributions of source code must retain the above copyright
9
// notice, this list of conditions and the following disclaimer.
10
// * Redistributions in binary form must reproduce the above
11
// copyright notice, this list of conditions and the following disclaimer
12
// in the documentation and/or other materials provided with the
13
// distribution.
14
// * Neither the name of Google Inc. nor the names of its
15
// contributors may be used to endorse or promote products derived from
16
// this software without specific prior written permission.
17
//
18
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30
#include "gtest/internal/gtest-filepath.h"
31
32
#include <stdlib.h>
33
34
#include <iterator>
35
#include <string>
36
37
#include "gtest/gtest-message.h"
38
#include "gtest/internal/gtest-port.h"
39
40
#ifdef GTEST_OS_WINDOWS_MOBILE
41
#include <windows.h>
42
#elif defined(GTEST_OS_WINDOWS)
43
#include <direct.h>
44
#include <io.h>
45
#else
46
#include <limits.h>
47
48
#include <climits> // Some Linux distributions define PATH_MAX here.
49
#endif // GTEST_OS_WINDOWS_MOBILE
50
51
#include "gtest/internal/gtest-string.h"
52
53
#ifdef GTEST_OS_WINDOWS
54
#define GTEST_PATH_MAX_ _MAX_PATH
55
#elif defined(PATH_MAX)
56
#define GTEST_PATH_MAX_ PATH_MAX
57
#elif defined(_XOPEN_PATH_MAX)
58
#define GTEST_PATH_MAX_ _XOPEN_PATH_MAX
59
#else
60
#define GTEST_PATH_MAX_ _POSIX_PATH_MAX
61
#endif // GTEST_OS_WINDOWS
62
63
#if GTEST_HAS_FILE_SYSTEM
64
65
namespace testing {
66
namespace internal {
67
68
#ifdef GTEST_OS_WINDOWS
69
// On Windows, '\\' is the standard path separator, but many tools and the
70
// Windows API also accept '/' as an alternate path separator. Unless otherwise
71
// noted, a file path can contain either kind of path separators, or a mixture
72
// of them.
73
const char kPathSeparator = '\\';
74
const char kAlternatePathSeparator = '/';
75
const char kAlternatePathSeparatorString[] = "/";
76
#ifdef GTEST_OS_WINDOWS_MOBILE
77
// Windows CE doesn't have a current directory. You should not use
78
// the current directory in tests on Windows CE, but this at least
79
// provides a reasonable fallback.
80
const char kCurrentDirectoryString[] = "\\";
81
// Windows CE doesn't define INVALID_FILE_ATTRIBUTES
82
const DWORD kInvalidFileAttributes = 0xffffffff;
83
#else
84
const char kCurrentDirectoryString[] = ".\\";
85
#endif // GTEST_OS_WINDOWS_MOBILE
86
#else
87
const char kPathSeparator = '/';
88
const char kCurrentDirectoryString[] = "./";
89
#endif // GTEST_OS_WINDOWS
90
91
// Returns whether the given character is a valid path separator.
92
static bool IsPathSeparator(char c) {
93
#if GTEST_HAS_ALT_PATH_SEP_
94
return (c == kPathSeparator) || (c == kAlternatePathSeparator);
95
#else
96
return c == kPathSeparator;
97
#endif
98
}
99
100
// Returns the current working directory, or "" if unsuccessful.
101
FilePath FilePath::GetCurrentDir() {
102
#if defined(GTEST_OS_WINDOWS_MOBILE) || defined(GTEST_OS_WINDOWS_PHONE) || \
103
defined(GTEST_OS_WINDOWS_RT) || defined(GTEST_OS_ESP8266) || \
104
defined(GTEST_OS_ESP32) || defined(GTEST_OS_XTENSA) || \
105
defined(GTEST_OS_QURT) || defined(GTEST_OS_NXP_QN9090) || \
106
defined(GTEST_OS_NRF52)
107
// These platforms do not have a current directory, so we just return
108
// something reasonable.
109
return FilePath(kCurrentDirectoryString);
110
#elif defined(GTEST_OS_WINDOWS)
111
char cwd[GTEST_PATH_MAX_ + 1] = {'\0'};
112
return FilePath(_getcwd(cwd, sizeof(cwd)) == nullptr ? "" : cwd);
113
#else
114
char cwd[GTEST_PATH_MAX_ + 1] = {'\0'};
115
char* result = getcwd(cwd, sizeof(cwd));
116
#ifdef GTEST_OS_NACL
117
// getcwd will likely fail in NaCl due to the sandbox, so return something
118
// reasonable. The user may have provided a shim implementation for getcwd,
119
// however, so fallback only when failure is detected.
120
return FilePath(result == nullptr ? kCurrentDirectoryString : cwd);
121
#endif // GTEST_OS_NACL
122
return FilePath(result == nullptr ? "" : cwd);
123
#endif // GTEST_OS_WINDOWS_MOBILE
124
}
125
126
// Returns a copy of the FilePath with the case-insensitive extension removed.
127
// Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns
128
// FilePath("dir/file"). If a case-insensitive extension is not
129
// found, returns a copy of the original FilePath.
130
FilePath FilePath::RemoveExtension(const char* extension) const {
131
const std::string dot_extension = std::string(".") + extension;
132
if (String::EndsWithCaseInsensitive(pathname_, dot_extension)) {
133
return FilePath(
134
pathname_.substr(0, pathname_.length() - dot_extension.length()));
135
}
136
return *this;
137
}
138
139
// Returns a pointer to the last occurrence of a valid path separator in
140
// the FilePath. On Windows, for example, both '/' and '\' are valid path
141
// separators. Returns NULL if no path separator was found.
142
const char* FilePath::FindLastPathSeparator() const {
143
const char* const last_sep = strrchr(c_str(), kPathSeparator);
144
#if GTEST_HAS_ALT_PATH_SEP_
145
const char* const last_alt_sep = strrchr(c_str(), kAlternatePathSeparator);
146
// Comparing two pointers of which only one is NULL is undefined.
147
if (last_alt_sep != nullptr &&
148
(last_sep == nullptr || last_alt_sep > last_sep)) {
149
return last_alt_sep;
150
}
151
#endif
152
return last_sep;
153
}
154
155
size_t FilePath::CalculateRootLength() const {
156
const auto& path = pathname_;
157
auto s = path.begin();
158
auto end = path.end();
159
#ifdef GTEST_OS_WINDOWS
160
if (end - s >= 2 && s[1] == ':' && (end - s == 2 || IsPathSeparator(s[2])) &&
161
(('A' <= s[0] && s[0] <= 'Z') || ('a' <= s[0] && s[0] <= 'z'))) {
162
// A typical absolute path like "C:\Windows" or "D:"
163
s += 2;
164
if (s != end) {
165
++s;
166
}
167
} else if (end - s >= 3 && IsPathSeparator(*s) && IsPathSeparator(*(s + 1)) &&
168
!IsPathSeparator(*(s + 2))) {
169
// Move past the "\\" prefix in a UNC path like "\\Server\Share\Folder"
170
s += 2;
171
// Skip 2 components and their following separators ("Server\" and "Share\")
172
for (int i = 0; i < 2; ++i) {
173
while (s != end) {
174
bool stop = IsPathSeparator(*s);
175
++s;
176
if (stop) {
177
break;
178
}
179
}
180
}
181
} else if (s != end && IsPathSeparator(*s)) {
182
// A drive-rooted path like "\Windows"
183
++s;
184
}
185
#else
186
if (s != end && IsPathSeparator(*s)) {
187
++s;
188
}
189
#endif
190
return static_cast<size_t>(s - path.begin());
191
}
192
193
// Returns a copy of the FilePath with the directory part removed.
194
// Example: FilePath("path/to/file").RemoveDirectoryName() returns
195
// FilePath("file"). If there is no directory part ("just_a_file"), it returns
196
// the FilePath unmodified. If there is no file part ("just_a_dir/") it
197
// returns an empty FilePath ("").
198
// On Windows platform, '\' is the path separator, otherwise it is '/'.
199
FilePath FilePath::RemoveDirectoryName() const {
200
const char* const last_sep = FindLastPathSeparator();
201
return last_sep ? FilePath(last_sep + 1) : *this;
202
}
203
204
// RemoveFileName returns the directory path with the filename removed.
205
// Example: FilePath("path/to/file").RemoveFileName() returns "path/to/".
206
// If the FilePath is "a_file" or "/a_file", RemoveFileName returns
207
// FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does
208
// not have a file, like "just/a/dir/", it returns the FilePath unmodified.
209
// On Windows platform, '\' is the path separator, otherwise it is '/'.
210
FilePath FilePath::RemoveFileName() const {
211
const char* const last_sep = FindLastPathSeparator();
212
std::string dir;
213
if (last_sep) {
214
dir = std::string(c_str(), static_cast<size_t>(last_sep + 1 - c_str()));
215
} else {
216
dir = kCurrentDirectoryString;
217
}
218
return FilePath(dir);
219
}
220
221
// Helper functions for naming files in a directory for xml output.
222
223
// Given directory = "dir", base_name = "test", number = 0,
224
// extension = "xml", returns "dir/test.xml". If number is greater
225
// than zero (e.g., 12), returns "dir/test_12.xml".
226
// On Windows platform, uses \ as the separator rather than /.
227
FilePath FilePath::MakeFileName(const FilePath& directory,
228
const FilePath& base_name, int number,
229
const char* extension) {
230
std::string file;
231
if (number == 0) {
232
file = base_name.string() + "." + extension;
233
} else {
234
file =
235
base_name.string() + "_" + StreamableToString(number) + "." + extension;
236
}
237
return ConcatPaths(directory, FilePath(file));
238
}
239
240
// Given directory = "dir", relative_path = "test.xml", returns "dir/test.xml".
241
// On Windows, uses \ as the separator rather than /.
242
FilePath FilePath::ConcatPaths(const FilePath& directory,
243
const FilePath& relative_path) {
244
if (directory.IsEmpty()) return relative_path;
245
const FilePath dir(directory.RemoveTrailingPathSeparator());
246
return FilePath(dir.string() + kPathSeparator + relative_path.string());
247
}
248
249
// Returns true if pathname describes something findable in the file-system,
250
// either a file, directory, or whatever.
251
bool FilePath::FileOrDirectoryExists() const {
252
#ifdef GTEST_OS_WINDOWS_MOBILE
253
LPCWSTR unicode = String::AnsiToUtf16(pathname_.c_str());
254
const DWORD attributes = GetFileAttributes(unicode);
255
delete[] unicode;
256
return attributes != kInvalidFileAttributes;
257
#else
258
posix::StatStruct file_stat{};
259
return posix::Stat(pathname_.c_str(), &file_stat) == 0;
260
#endif // GTEST_OS_WINDOWS_MOBILE
261
}
262
263
// Returns true if pathname describes a directory in the file-system
264
// that exists.
265
bool FilePath::DirectoryExists() const {
266
bool result = false;
267
#ifdef GTEST_OS_WINDOWS
268
// Don't strip off trailing separator if path is a root directory on
269
// Windows (like "C:\\").
270
const FilePath& path(IsRootDirectory() ? *this
271
: RemoveTrailingPathSeparator());
272
#else
273
const FilePath& path(*this);
274
#endif
275
276
#ifdef GTEST_OS_WINDOWS_MOBILE
277
LPCWSTR unicode = String::AnsiToUtf16(path.c_str());
278
const DWORD attributes = GetFileAttributes(unicode);
279
delete[] unicode;
280
if ((attributes != kInvalidFileAttributes) &&
281
(attributes & FILE_ATTRIBUTE_DIRECTORY)) {
282
result = true;
283
}
284
#else
285
posix::StatStruct file_stat{};
286
result =
287
posix::Stat(path.c_str(), &file_stat) == 0 && posix::IsDir(file_stat);
288
#endif // GTEST_OS_WINDOWS_MOBILE
289
290
return result;
291
}
292
293
// Returns true if pathname describes a root directory. (Windows has one
294
// root directory per disk drive. UNC share roots are also included.)
295
bool FilePath::IsRootDirectory() const {
296
size_t root_length = CalculateRootLength();
297
return root_length > 0 && root_length == pathname_.size() &&
298
IsPathSeparator(pathname_[root_length - 1]);
299
}
300
301
// Returns true if pathname describes an absolute path.
302
bool FilePath::IsAbsolutePath() const { return CalculateRootLength() > 0; }
303
304
// Returns a pathname for a file that does not currently exist. The pathname
305
// will be directory/base_name.extension or
306
// directory/base_name_<number>.extension if directory/base_name.extension
307
// already exists. The number will be incremented until a pathname is found
308
// that does not already exist.
309
// Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'.
310
// There could be a race condition if two or more processes are calling this
311
// function at the same time -- they could both pick the same filename.
312
FilePath FilePath::GenerateUniqueFileName(const FilePath& directory,
313
const FilePath& base_name,
314
const char* extension) {
315
FilePath full_pathname;
316
int number = 0;
317
do {
318
full_pathname.Set(MakeFileName(directory, base_name, number++, extension));
319
} while (full_pathname.FileOrDirectoryExists());
320
return full_pathname;
321
}
322
323
// Returns true if FilePath ends with a path separator, which indicates that
324
// it is intended to represent a directory. Returns false otherwise.
325
// This does NOT check that a directory (or file) actually exists.
326
bool FilePath::IsDirectory() const {
327
return !pathname_.empty() &&
328
IsPathSeparator(pathname_.c_str()[pathname_.length() - 1]);
329
}
330
331
// Create directories so that path exists. Returns true if successful or if
332
// the directories already exist; returns false if unable to create directories
333
// for any reason.
334
bool FilePath::CreateDirectoriesRecursively() const {
335
if (!this->IsDirectory()) {
336
return false;
337
}
338
339
if (pathname_.empty() || this->DirectoryExists()) {
340
return true;
341
}
342
343
const FilePath parent(this->RemoveTrailingPathSeparator().RemoveFileName());
344
return parent.CreateDirectoriesRecursively() && this->CreateFolder();
345
}
346
347
// Create the directory so that path exists. Returns true if successful or
348
// if the directory already exists; returns false if unable to create the
349
// directory for any reason, including if the parent directory does not
350
// exist. Not named "CreateDirectory" because that's a macro on Windows.
351
bool FilePath::CreateFolder() const {
352
#ifdef GTEST_OS_WINDOWS_MOBILE
353
FilePath removed_sep(this->RemoveTrailingPathSeparator());
354
LPCWSTR unicode = String::AnsiToUtf16(removed_sep.c_str());
355
int result = CreateDirectory(unicode, nullptr) ? 0 : -1;
356
delete[] unicode;
357
#elif defined(GTEST_OS_WINDOWS)
358
int result = _mkdir(pathname_.c_str());
359
#elif defined(GTEST_OS_ESP8266) || defined(GTEST_OS_XTENSA) || \
360
defined(GTEST_OS_QURT) || defined(GTEST_OS_NXP_QN9090) || \
361
defined(GTEST_OS_NRF52)
362
// do nothing
363
int result = 0;
364
#else
365
int result = mkdir(pathname_.c_str(), 0777);
366
#endif // GTEST_OS_WINDOWS_MOBILE
367
368
if (result == -1) {
369
return this->DirectoryExists(); // An error is OK if the directory exists.
370
}
371
return true; // No error.
372
}
373
374
// If input name has a trailing separator character, remove it and return the
375
// name, otherwise return the name string unmodified.
376
// On Windows platform, uses \ as the separator, other platforms use /.
377
FilePath FilePath::RemoveTrailingPathSeparator() const {
378
return IsDirectory() ? FilePath(pathname_.substr(0, pathname_.length() - 1))
379
: *this;
380
}
381
382
// Removes any redundant separators that might be in the pathname.
383
// For example, "bar///foo" becomes "bar/foo". Does not eliminate other
384
// redundancies that might be in a pathname involving "." or "..".
385
// Note that "\\Host\Share" does not contain a redundancy on Windows!
386
void FilePath::Normalize() {
387
auto out = pathname_.begin();
388
389
auto i = pathname_.cbegin();
390
#ifdef GTEST_OS_WINDOWS
391
// UNC paths are treated specially
392
if (pathname_.end() - i >= 3 && IsPathSeparator(*i) &&
393
IsPathSeparator(*(i + 1)) && !IsPathSeparator(*(i + 2))) {
394
*(out++) = kPathSeparator;
395
*(out++) = kPathSeparator;
396
}
397
#endif
398
while (i != pathname_.end()) {
399
const char character = *i;
400
if (!IsPathSeparator(character)) {
401
*(out++) = character;
402
} else if (out == pathname_.begin() || *std::prev(out) != kPathSeparator) {
403
*(out++) = kPathSeparator;
404
}
405
++i;
406
}
407
408
pathname_.erase(out, pathname_.end());
409
}
410
411
} // namespace internal
412
} // namespace testing
413
414
#endif // GTEST_HAS_FILE_SYSTEM
415
416