gtest-filepath.cc 12.6 KB
Newer Older
shiqian's avatar
shiqian committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Copyright 2008, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Authors: keith.ray@gmail.com (Keith Ray)

#include <gtest/internal/gtest-filepath.h>
#include <gtest/internal/gtest-port.h>

35
36
#include <stdlib.h>

37
38
#ifdef _WIN32_WCE
#include <windows.h>
zhanyong.wan's avatar
zhanyong.wan committed
39
#elif GTEST_OS_WINDOWS
shiqian's avatar
shiqian committed
40
41
42
#include <direct.h>
#include <io.h>
#include <sys/stat.h>
zhanyong.wan's avatar
zhanyong.wan committed
43
#elif GTEST_OS_SYMBIAN
shiqian's avatar
shiqian committed
44
45
46
// Symbian OpenC has PATH_MAX in sys/syslimits.h
#include <sys/syslimits.h>
#include <unistd.h>
47
#else
48
#include <limits.h>
49
50
#include <sys/stat.h>  // NOLINT
#include <unistd.h>  // NOLINT
51
#include <climits>  // Some Linux distributions define PATH_MAX here.
52
53
#endif  // _WIN32_WCE or _WIN32

zhanyong.wan's avatar
zhanyong.wan committed
54
#if GTEST_OS_WINDOWS
55
56
57
58
59
60
61
62
#define GTEST_PATH_MAX_ _MAX_PATH
#elif defined(PATH_MAX)
#define GTEST_PATH_MAX_ PATH_MAX
#elif defined(_XOPEN_PATH_MAX)
#define GTEST_PATH_MAX_ _XOPEN_PATH_MAX
#else
#define GTEST_PATH_MAX_ _POSIX_PATH_MAX
#endif  // GTEST_OS_WINDOWS
shiqian's avatar
shiqian committed
63
64
65
66
67
68

#include <gtest/internal/gtest-string.h>

namespace testing {
namespace internal {

zhanyong.wan's avatar
zhanyong.wan committed
69
#if GTEST_OS_WINDOWS
shiqian's avatar
shiqian committed
70
71
const char kPathSeparator = '\\';
const char kPathSeparatorString[] = "\\";
72
73
74
75
76
77
78
79
#ifdef _WIN32_WCE
// Windows CE doesn't have a current directory. You should not use
// the current directory in tests on Windows CE, but this at least
// provides a reasonable fallback.
const char kCurrentDirectoryString[] = "\\";
// Windows CE doesn't define INVALID_FILE_ATTRIBUTES
const DWORD kInvalidFileAttributes = 0xffffffff;
#else
shiqian's avatar
shiqian committed
80
const char kCurrentDirectoryString[] = ".\\";
81
#endif  // _WIN32_WCE
shiqian's avatar
shiqian committed
82
83
84
85
86
87
#else
const char kPathSeparator = '/';
const char kPathSeparatorString[] = "/";
const char kCurrentDirectoryString[] = "./";
#endif  // GTEST_OS_WINDOWS

88
89
90
91
92
93
// Returns the current working directory, or "" if unsuccessful.
FilePath FilePath::GetCurrentDir() {
#ifdef _WIN32_WCE
// Windows CE doesn't have a current directory, so we just return
// something reasonable.
  return FilePath(kCurrentDirectoryString);
zhanyong.wan's avatar
zhanyong.wan committed
94
#elif GTEST_OS_WINDOWS
95
  char cwd[GTEST_PATH_MAX_ + 1] = {};
96
97
  return FilePath(_getcwd(cwd, sizeof(cwd)) == NULL ? "" : cwd);
#else
98
  char cwd[GTEST_PATH_MAX_ + 1] = {};
99
100
101
102
  return FilePath(getcwd(cwd, sizeof(cwd)) == NULL ? "" : cwd);
#endif
}

shiqian's avatar
shiqian committed
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Returns a copy of the FilePath with the case-insensitive extension removed.
// Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns
// FilePath("dir/file"). If a case-insensitive extension is not
// found, returns a copy of the original FilePath.
FilePath FilePath::RemoveExtension(const char* extension) const {
  String dot_extension(String::Format(".%s", extension));
  if (pathname_.EndsWithCaseInsensitive(dot_extension.c_str())) {
    return FilePath(String(pathname_.c_str(), pathname_.GetLength() - 4));
  }
  return *this;
}

// Returns a copy of the FilePath with the directory part removed.
// Example: FilePath("path/to/file").RemoveDirectoryName() returns
// FilePath("file"). If there is no directory part ("just_a_file"), it returns
// the FilePath unmodified. If there is no file part ("just_a_dir/") it
// returns an empty FilePath ("").
// On Windows platform, '\' is the path separator, otherwise it is '/'.
FilePath FilePath::RemoveDirectoryName() const {
  const char* const last_sep = strrchr(c_str(), kPathSeparator);
  return last_sep ? FilePath(String(last_sep + 1)) : *this;
}

// RemoveFileName returns the directory path with the filename removed.
// Example: FilePath("path/to/file").RemoveFileName() returns "path/to/".
// If the FilePath is "a_file" or "/a_file", RemoveFileName returns
// FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does
// not have a file, like "just/a/dir/", it returns the FilePath unmodified.
// On Windows platform, '\' is the path separator, otherwise it is '/'.
FilePath FilePath::RemoveFileName() const {
  const char* const last_sep = strrchr(c_str(), kPathSeparator);
  return FilePath(last_sep ? String(c_str(), last_sep + 1 - c_str())
                           : String(kCurrentDirectoryString));
}

// Helper functions for naming files in a directory for xml output.

// Given directory = "dir", base_name = "test", number = 0,
// extension = "xml", returns "dir/test.xml". If number is greater
// than zero (e.g., 12), returns "dir/test_12.xml".
// On Windows platform, uses \ as the separator rather than /.
FilePath FilePath::MakeFileName(const FilePath& directory,
                                const FilePath& base_name,
                                int number,
                                const char* extension) {
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
  const FilePath file_name(
      (number == 0) ?
      String::Format("%s.%s", base_name.c_str(), extension) :
      String::Format("%s_%d.%s", base_name.c_str(), number, extension));
  return ConcatPaths(directory, file_name);
}

// Given directory = "dir", relative_path = "test.xml", returns "dir/test.xml".
// On Windows, uses \ as the separator rather than /.
FilePath FilePath::ConcatPaths(const FilePath& directory,
                               const FilePath& relative_path) {
  if (directory.IsEmpty())
    return relative_path;
  const FilePath dir(directory.RemoveTrailingPathSeparator());
  return FilePath(String::Format("%s%c%s", dir.c_str(), kPathSeparator,
                                 relative_path.c_str()));
shiqian's avatar
shiqian committed
164
165
166
167
168
}

// Returns true if pathname describes something findable in the file-system,
// either a file, directory, or whatever.
bool FilePath::FileOrDirectoryExists() const {
zhanyong.wan's avatar
zhanyong.wan committed
169
#if GTEST_OS_WINDOWS
170
171
172
173
174
175
#ifdef _WIN32_WCE
  LPCWSTR unicode = String::AnsiToUtf16(pathname_.c_str());
  const DWORD attributes = GetFileAttributes(unicode);
  delete [] unicode;
  return attributes != kInvalidFileAttributes;
#else
shiqian's avatar
shiqian committed
176
177
  struct _stat file_stat = {};
  return _stat(pathname_.c_str(), &file_stat) == 0;
178
#endif  // _WIN32_WCE
shiqian's avatar
shiqian committed
179
180
181
182
183
184
185
186
187
188
#else
  struct stat file_stat = {};
  return stat(pathname_.c_str(), &file_stat) == 0;
#endif  // GTEST_OS_WINDOWS
}

// Returns true if pathname describes a directory in the file-system
// that exists.
bool FilePath::DirectoryExists() const {
  bool result = false;
zhanyong.wan's avatar
zhanyong.wan committed
189
#if GTEST_OS_WINDOWS
shiqian's avatar
shiqian committed
190
191
192
193
  // Don't strip off trailing separator if path is a root directory on
  // Windows (like "C:\\").
  const FilePath& path(IsRootDirectory() ? *this :
                                           RemoveTrailingPathSeparator());
194
#ifdef _WIN32_WCE
shiqian's avatar
shiqian committed
195
  LPCWSTR unicode = String::AnsiToUtf16(path.c_str());
196
197
198
199
200
201
202
  const DWORD attributes = GetFileAttributes(unicode);
  delete [] unicode;
  if ((attributes != kInvalidFileAttributes) &&
      (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
    result = true;
  }
#else
shiqian's avatar
shiqian committed
203
  struct _stat file_stat = {};
shiqian's avatar
shiqian committed
204
  result = _stat(path.c_str(), &file_stat) == 0 &&
shiqian's avatar
shiqian committed
205
      (_S_IFDIR & file_stat.st_mode) != 0;
206
#endif  // _WIN32_WCE
shiqian's avatar
shiqian committed
207
208
209
210
#else
  struct stat file_stat = {};
  result = stat(pathname_.c_str(), &file_stat) == 0 &&
      S_ISDIR(file_stat.st_mode);
shiqian's avatar
shiqian committed
211
#endif  // GTEST_OS_WINDOWS
shiqian's avatar
shiqian committed
212
213
214
  return result;
}

shiqian's avatar
shiqian committed
215
216
217
// Returns true if pathname describes a root directory. (Windows has one
// root directory per disk drive.)
bool FilePath::IsRootDirectory() const {
zhanyong.wan's avatar
zhanyong.wan committed
218
#if GTEST_OS_WINDOWS
219
220
221
222
223
224
225
226
227
228
229
230
  // TODO(wan@google.com): on Windows a network share like
  // \\server\share can be a root directory, although it cannot be the
  // current directory.  Handle this properly.
  return pathname_.GetLength() == 3 && IsAbsolutePath();
#else
  return pathname_ == kPathSeparatorString;
#endif
}

// Returns true if pathname describes an absolute path.
bool FilePath::IsAbsolutePath() const {
  const char* const name = pathname_.c_str();
zhanyong.wan's avatar
zhanyong.wan committed
231
#if GTEST_OS_WINDOWS
232
  return pathname_.GetLength() >= 3 &&
shiqian's avatar
shiqian committed
233
234
235
236
237
     ((name[0] >= 'a' && name[0] <= 'z') ||
      (name[0] >= 'A' && name[0] <= 'Z')) &&
     name[1] == ':' &&
     name[2] == kPathSeparator;
#else
238
  return name[0] == kPathSeparator;
shiqian's avatar
shiqian committed
239
240
241
#endif
}

shiqian's avatar
shiqian committed
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
// Returns a pathname for a file that does not currently exist. The pathname
// will be directory/base_name.extension or
// directory/base_name_<number>.extension if directory/base_name.extension
// already exists. The number will be incremented until a pathname is found
// that does not already exist.
// Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'.
// There could be a race condition if two or more processes are calling this
// function at the same time -- they could both pick the same filename.
FilePath FilePath::GenerateUniqueFileName(const FilePath& directory,
                                          const FilePath& base_name,
                                          const char* extension) {
  FilePath full_pathname;
  int number = 0;
  do {
    full_pathname.Set(MakeFileName(directory, base_name, number++, extension));
  } while (full_pathname.FileOrDirectoryExists());
  return full_pathname;
}

// Returns true if FilePath ends with a path separator, which indicates that
// it is intended to represent a directory. Returns false otherwise.
// This does NOT check that a directory (or file) actually exists.
bool FilePath::IsDirectory() const {
  return pathname_.EndsWith(kPathSeparatorString);
}

// Create directories so that path exists. Returns true if successful or if
// the directories already exist; returns false if unable to create directories
// for any reason.
bool FilePath::CreateDirectoriesRecursively() const {
  if (!this->IsDirectory()) {
    return false;
  }

  if (pathname_.GetLength() == 0 || this->DirectoryExists()) {
    return true;
  }

  const FilePath parent(this->RemoveTrailingPathSeparator().RemoveFileName());
  return parent.CreateDirectoriesRecursively() && this->CreateFolder();
}

// Create the directory so that path exists. Returns true if successful or
// if the directory already exists; returns false if unable to create the
// directory for any reason, including if the parent directory does not
// exist. Not named "CreateDirectory" because that's a macro on Windows.
bool FilePath::CreateFolder() const {
zhanyong.wan's avatar
zhanyong.wan committed
289
#if GTEST_OS_WINDOWS
290
291
292
293
294
295
#ifdef _WIN32_WCE
  FilePath removed_sep(this->RemoveTrailingPathSeparator());
  LPCWSTR unicode = String::AnsiToUtf16(removed_sep.c_str());
  int result = CreateDirectory(unicode, NULL) ? 0 : -1;
  delete [] unicode;
#else
shiqian's avatar
shiqian committed
296
  int result = _mkdir(pathname_.c_str());
297
#endif  // !WIN32_WCE
shiqian's avatar
shiqian committed
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
#else
  int result = mkdir(pathname_.c_str(), 0777);
#endif  // _WIN32
  if (result == -1) {
    return this->DirectoryExists();  // An error is OK if the directory exists.
  }
  return true;  // No error.
}

// If input name has a trailing separator character, remove it and return the
// name, otherwise return the name string unmodified.
// On Windows platform, uses \ as the separator, other platforms use /.
FilePath FilePath::RemoveTrailingPathSeparator() const {
  return pathname_.EndsWith(kPathSeparatorString)
      ? FilePath(String(pathname_.c_str(), pathname_.GetLength() - 1))
      : *this;
}

shiqian's avatar
shiqian committed
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
// Normalize removes any redundant separators that might be in the pathname.
// For example, "bar///foo" becomes "bar/foo". Does not eliminate other
// redundancies that might be in a pathname involving "." or "..".
void FilePath::Normalize() {
  if (pathname_.c_str() == NULL) {
    pathname_ = "";
    return;
  }
  const char* src = pathname_.c_str();
  char* const dest = new char[pathname_.GetLength() + 1];
  char* dest_ptr = dest;
  memset(dest_ptr, 0, pathname_.GetLength() + 1);

  while (*src != '\0') {
    *dest_ptr++ = *src;
    if (*src != kPathSeparator)
      src++;
    else
      while (*src == kPathSeparator)
        src++;
  }
  *dest_ptr = '\0';
  pathname_ = dest;
  delete[] dest;
}

shiqian's avatar
shiqian committed
342
343
}  // namespace internal
}  // namespace testing