gtest-filepath.cc 12.4 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
#if GTEST_OS_WINDOWS_MOBILE
38
#include <windows.h>
zhanyong.wan's avatar
zhanyong.wan committed
39
#elif GTEST_OS_WINDOWS
shiqian's avatar
shiqian committed
40
41
#include <direct.h>
#include <io.h>
zhanyong.wan's avatar
zhanyong.wan committed
42
#elif GTEST_OS_SYMBIAN
shiqian's avatar
shiqian committed
43
44
// Symbian OpenC has PATH_MAX in sys/syslimits.h
#include <sys/syslimits.h>
45
#else
46
#include <limits.h>
47
#include <climits>  // Some Linux distributions define PATH_MAX here.
48
#endif  // GTEST_OS_WINDOWS_MOBILE
49

zhanyong.wan's avatar
zhanyong.wan committed
50
#if GTEST_OS_WINDOWS
51
52
53
54
55
56
57
58
#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
59
60
61
62
63
64

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

namespace testing {
namespace internal {

zhanyong.wan's avatar
zhanyong.wan committed
65
#if GTEST_OS_WINDOWS
shiqian's avatar
shiqian committed
66
67
const char kPathSeparator = '\\';
const char kPathSeparatorString[] = "\\";
68
#if GTEST_OS_WINDOWS_MOBILE
69
70
71
72
73
74
75
// 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
76
const char kCurrentDirectoryString[] = ".\\";
77
#endif  // GTEST_OS_WINDOWS_MOBILE
shiqian's avatar
shiqian committed
78
79
80
81
82
83
#else
const char kPathSeparator = '/';
const char kPathSeparatorString[] = "/";
const char kCurrentDirectoryString[] = "./";
#endif  // GTEST_OS_WINDOWS

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

shiqian's avatar
shiqian committed
99
100
101
102
103
104
105
// 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())) {
106
    return FilePath(String(pathname_.c_str(), pathname_.length() - 4));
shiqian's avatar
shiqian committed
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
  }
  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);
130
131
132
133
134
135
136
  String dir;
  if (last_sep) {
    dir = String(c_str(), last_sep + 1 - c_str());
  } else {
    dir = kCurrentDirectoryString;
  }
  return FilePath(dir);
shiqian's avatar
shiqian committed
137
138
139
140
141
142
143
144
145
146
147
148
}

// 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) {
149
150
151
152
153
154
155
  String file;
  if (number == 0) {
    file = String::Format("%s.%s", base_name.c_str(), extension);
  } else {
    file = String::Format("%s_%d.%s", base_name.c_str(), number, extension);
  }
  return ConcatPaths(directory, FilePath(file));
156
157
158
159
160
161
162
163
164
165
166
}

// 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
167
168
169
170
171
}

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

// 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
187
#if GTEST_OS_WINDOWS
shiqian's avatar
shiqian committed
188
189
190
191
  // Don't strip off trailing separator if path is a root directory on
  // Windows (like "C:\\").
  const FilePath& path(IsRootDirectory() ? *this :
                                           RemoveTrailingPathSeparator());
192
193
194
195
#else
  const FilePath& path(*this);
#endif

196
#if GTEST_OS_WINDOWS_MOBILE
shiqian's avatar
shiqian committed
197
  LPCWSTR unicode = String::AnsiToUtf16(path.c_str());
198
199
200
201
202
203
204
  const DWORD attributes = GetFileAttributes(unicode);
  delete [] unicode;
  if ((attributes != kInvalidFileAttributes) &&
      (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
    result = true;
  }
#else
205
206
  posix::StatStruct file_stat;
  result = posix::Stat(path.c_str(), &file_stat) == 0 &&
207
      posix::IsDir(file_stat);
208
#endif  // GTEST_OS_WINDOWS_MOBILE
209

shiqian's avatar
shiqian committed
210
211
212
  return result;
}

shiqian's avatar
shiqian committed
213
214
215
// 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
216
#if GTEST_OS_WINDOWS
217
218
219
  // 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.
220
  return pathname_.length() == 3 && IsAbsolutePath();
221
222
223
224
225
226
227
228
#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
229
#if GTEST_OS_WINDOWS
230
  return pathname_.length() >= 3 &&
shiqian's avatar
shiqian committed
231
232
233
234
235
     ((name[0] >= 'a' && name[0] <= 'z') ||
      (name[0] >= 'A' && name[0] <= 'Z')) &&
     name[1] == ':' &&
     name[2] == kPathSeparator;
#else
236
  return name[0] == kPathSeparator;
shiqian's avatar
shiqian committed
237
238
239
#endif
}

shiqian's avatar
shiqian committed
240
241
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
// 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;
  }

274
  if (pathname_.length() == 0 || this->DirectoryExists()) {
shiqian's avatar
shiqian committed
275
276
277
278
279
280
281
282
283
284
285
286
    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 {
287
#if GTEST_OS_WINDOWS_MOBILE
288
289
290
291
  FilePath removed_sep(this->RemoveTrailingPathSeparator());
  LPCWSTR unicode = String::AnsiToUtf16(removed_sep.c_str());
  int result = CreateDirectory(unicode, NULL) ? 0 : -1;
  delete [] unicode;
292
#elif GTEST_OS_WINDOWS
shiqian's avatar
shiqian committed
293
294
295
  int result = _mkdir(pathname_.c_str());
#else
  int result = mkdir(pathname_.c_str(), 0777);
296
297
#endif  // GTEST_OS_WINDOWS_MOBILE

shiqian's avatar
shiqian committed
298
299
300
301
302
303
304
305
306
307
308
  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)
309
      ? FilePath(String(pathname_.c_str(), pathname_.length() - 1))
shiqian's avatar
shiqian committed
310
311
312
      : *this;
}

shiqian's avatar
shiqian committed
313
314
315
316
317
318
319
320
321
// 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();
322
  char* const dest = new char[pathname_.length() + 1];
shiqian's avatar
shiqian committed
323
  char* dest_ptr = dest;
324
  memset(dest_ptr, 0, pathname_.length() + 1);
shiqian's avatar
shiqian committed
325
326
327
328
329
330
331
332
333
334
335
336
337
338

  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
339
340
}  // namespace internal
}  // namespace testing