summaryrefslogtreecommitdiff
path: root/chromium/third_party/googletest/custom/gtest/internal/custom/gtest_port_wrapper.cc
blob: 950f7d1d8b5dbb393053547d1cf24775887a12ca (plain)
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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
148
149
150
151
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// TODO(crbug.com/1009553): Remove this wrapper after finding a way to plumb a
// workable temporary path into googletest on Android.

// This wrapper lets us compile gtest-port.cc without its stream redirection
// code. We replace this code with a variant that works on all Chrome platforms.
// This is a temporary workaround until we get good code upstream.
//
// Stream redirection requires the ability to create files in a temporary
// directory. Traditionally, this directory has been /sdcard on Android.
// Commit bf0fe874a27bd6c9a4a35b98e662d2d02f8879a2 changed the Android
// directory to /data/local/tmp, which is not writable in Chrome's testing
// setup. We work around this problem by using the old code for now.
//
// It is tempting to consider disabling the stream redirection code altogether,
// by setting GTEST_HAS_STREAM_REDIRECTION to 0 in googletest's BUILD.gn.
// This breaks gtest-death-test.cc, which assumes the existence of
// testing::internal::{GetCapturedStderr,CaptureStderr} without any macro
// checking.

#define GTEST_HAS_STREAM_REDIRECTION 0
#include "third_party/googletest/src/googletest/src/gtest-port.cc"

namespace testing {
namespace internal {

// Object that captures an output stream (stdout/stderr).
class CapturedStream {
 public:
  // The ctor redirects the stream to a temporary file.
  explicit CapturedStream(int fd) : fd_(fd), uncaptured_fd_(dup(fd)) {
    std::string temp_dir = ::testing::TempDir();

    // testing::TempDir() should return a directory with a path separator.
    // However, this rule was documented fairly recently, so we normalize across
    // implementations with and without a trailing path separator.
    if (temp_dir.back() != GTEST_PATH_SEP_[0])
      temp_dir.push_back(GTEST_PATH_SEP_[0]);

#if GTEST_OS_WINDOWS
    char temp_file_path[MAX_PATH + 1] = {'\0'};  // NOLINT
    const UINT success = ::GetTempFileNameA(temp_dir.c_str(), "gtest_redir",
                                            0,  // Generate unique file name.
                                            temp_file_path);
    GTEST_CHECK_(success != 0)
        << "Unable to create a temporary file in " << temp_dir;
    const int captured_fd = creat(temp_file_path, _S_IREAD | _S_IWRITE);
    GTEST_CHECK_(captured_fd != -1)
        << "Unable to open temporary file " << temp_file_path;
    filename_ = temp_file_path;
#else
    std::string name_template = temp_dir + "gtest_captured_stream.XXXXXX";

    // mkstemp() modifies the string bytes in place, and does not go beyond the
    // string's length. This results in well-defined behavior in C++17.
    //
    // The const_cast is needed below C++17. The constraints on std::string
    // implementations in C++11 and above make assumption behind the const_cast
    // fairly safe.
    const int captured_fd = ::mkstemp(const_cast<char*>(name_template.data()));
    GTEST_CHECK_(captured_fd != -1)
        << "Failed to create tmp file " << name_template
        << " for test; does the test have write access to the directory?";
    filename_ = std::move(name_template);
#endif  // GTEST_OS_WINDOWS
    fflush(nullptr);
    dup2(captured_fd, fd_);
    close(captured_fd);
  }

  ~CapturedStream() { remove(filename_.c_str()); }

  std::string GetCapturedString() {
    if (uncaptured_fd_ != -1) {
      // Restores the original stream.
      fflush(nullptr);
      dup2(uncaptured_fd_, fd_);
      close(uncaptured_fd_);
      uncaptured_fd_ = -1;
    }

    FILE* const file = posix::FOpen(filename_.c_str(), "r");
    if (file == nullptr) {
      GTEST_LOG_(FATAL) << "Failed to open tmp file " << filename_
                        << " for capturing stream.";
    }
    const std::string content = ReadEntireFile(file);
    posix::FClose(file);
    return content;
  }

 private:
  const int fd_;  // A stream to capture.
  int uncaptured_fd_;
  // Name of the temporary file holding the stderr output.
  ::std::string filename_;

  GTEST_DISALLOW_COPY_AND_ASSIGN_(CapturedStream);
};

GTEST_DISABLE_MSC_DEPRECATED_POP_()

static CapturedStream* g_captured_stderr = nullptr;
static CapturedStream* g_captured_stdout = nullptr;

// Starts capturing an output stream (stdout/stderr).
static void CaptureStream(int fd,
                          const char* stream_name,
                          CapturedStream** stream) {
  if (*stream != nullptr) {
    GTEST_LOG_(FATAL) << "Only one " << stream_name
                      << " capturer can exist at a time.";
  }
  *stream = new CapturedStream(fd);
}

// Stops capturing the output stream and returns the captured string.
static std::string GetCapturedStream(CapturedStream** captured_stream) {
  const std::string content = (*captured_stream)->GetCapturedString();

  delete *captured_stream;
  *captured_stream = nullptr;

  return content;
}

// Starts capturing stdout.
void CaptureStdout() {
  CaptureStream(kStdOutFileno, "stdout", &g_captured_stdout);
}

// Starts capturing stderr.
void CaptureStderr() {
  CaptureStream(kStdErrFileno, "stderr", &g_captured_stderr);
}

// Stops capturing stdout and returns the captured string.
std::string GetCapturedStdout() {
  return GetCapturedStream(&g_captured_stdout);
}

// Stops capturing stderr and returns the captured string.
std::string GetCapturedStderr() {
  return GetCapturedStream(&g_captured_stderr);
}

}  // namespace internal
}  // namespace testing