1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/diagnostics/diagnostics_writer.h"
6
7#include "build/build_config.h"
8
9#if defined(OS_POSIX)
10#include <stdio.h>
11#include <unistd.h>
12#endif
13
14#include <string>
15
16#include "base/basictypes.h"
17#include "base/command_line.h"
18#include "base/logging.h"
19#include "base/strings/string16.h"
20#include "base/strings/stringprintf.h"
21#include "base/strings/utf_string_conversions.h"
22#include "chrome/common/chrome_switches.h"
23#include "ui/base/ui_base_paths.h"
24
25namespace diagnostics {
26
27// This is a minimalistic interface to wrap the platform console.
28class SimpleConsole {
29 public:
30  enum Color {
31    DEFAULT,
32    RED,
33    GREEN,
34  };
35
36  virtual ~SimpleConsole() {}
37
38  // Init must be called before using any other method. If it returns
39  // false there will be no console output.
40  virtual bool Init() = 0;
41
42  // Writes a string to the console with the current color.
43  virtual bool Write(const base::string16& text) = 0;
44
45  // Called when the program is about to exit.
46  virtual void OnQuit() = 0;
47
48  // Sets the foreground text color.
49  virtual bool SetColor(Color color) = 0;
50
51  // Create an appropriate SimpleConsole instance.  May return NULL if there is
52  // no implementation for the current platform.
53  static SimpleConsole* Create();
54};
55
56#if defined(OS_WIN)
57namespace {
58
59// Wrapper for the windows console operating in high-level IO mode.
60class WinConsole : public SimpleConsole {
61 public:
62  // The ctor allocates a console. This avoids having to ask the user to start
63  // chrome from a command prompt.
64  WinConsole()
65      : std_out_(INVALID_HANDLE_VALUE),
66        std_in_(INVALID_HANDLE_VALUE) {
67    ::AllocConsole();
68  }
69
70  virtual ~WinConsole() {
71    ::FreeConsole();
72  }
73
74  virtual bool Init() {
75    return SetIOHandles();
76  }
77
78  virtual bool Write(const base::string16& txt) {
79    DWORD sz = txt.size();
80    return (TRUE == ::WriteConsoleW(std_out_, txt.c_str(), sz, &sz, NULL));
81  }
82
83  // Reads a string from the console. Internally it is limited to 256
84  // characters.
85  virtual void OnQuit() {
86    // Block here so the user can see the results.
87    SetColor(SimpleConsole::DEFAULT);
88    Write(L"Press [enter] to continue\n");
89    wchar_t buf[256];
90    DWORD read = arraysize(buf);
91    ::ReadConsoleW(std_in_, buf, read, &read, NULL);
92  }
93
94  // Sets the foreground and background color.
95  virtual bool SetColor(Color color) {
96    uint16 color_combo = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE |
97                         FOREGROUND_INTENSITY;
98    switch (color) {
99      case RED:
100        color_combo = FOREGROUND_RED | FOREGROUND_INTENSITY;
101        break;
102      case GREEN:
103        color_combo = FOREGROUND_GREEN | FOREGROUND_INTENSITY;
104        break;
105      case DEFAULT:
106        break;
107      default:
108        NOTREACHED();
109    }
110    return (TRUE == ::SetConsoleTextAttribute(std_out_, color_combo));
111  }
112
113 private:
114  bool SetIOHandles() {
115    std_out_ = ::GetStdHandle(STD_OUTPUT_HANDLE);
116    std_in_ = ::GetStdHandle(STD_INPUT_HANDLE);
117    return ((std_out_ != INVALID_HANDLE_VALUE) &&
118            (std_in_ != INVALID_HANDLE_VALUE));
119  }
120
121  // The input and output handles to the screen. They seem to be
122  // implemented as pipes but they have non-documented protocol.
123  HANDLE std_out_;
124  HANDLE std_in_;
125
126  DISALLOW_COPY_AND_ASSIGN(WinConsole);
127};
128
129}  // namespace
130
131SimpleConsole* SimpleConsole::Create() { return new WinConsole(); }
132
133#elif defined(OS_POSIX)
134namespace {
135
136class PosixConsole : public SimpleConsole {
137 public:
138  PosixConsole() : use_color_(false) {}
139
140  virtual bool Init() OVERRIDE {
141    // Technically, we should also check the terminal capabilities before using
142    // color, but in practice this is unlikely to be an issue.
143    use_color_ = isatty(STDOUT_FILENO);
144    return true;
145  }
146
147  virtual bool Write(const base::string16& text) OVERRIDE {
148    // We're assuming that the terminal is using UTF-8 encoding.
149    printf("%s", base::UTF16ToUTF8(text).c_str());
150    return true;
151  }
152
153  virtual void OnQuit() OVERRIDE {
154    // The "press enter to continue" prompt isn't very unixy, so only do that on
155    // Windows.
156  }
157
158  virtual bool SetColor(Color color) OVERRIDE {
159    if (!use_color_)
160      return false;
161
162    const char* code = "\033[m";
163    switch (color) {
164      case RED:
165        code = "\033[1;31m";
166        break;
167      case GREEN:
168        code = "\033[1;32m";
169        break;
170      case DEFAULT:
171        break;
172      default:
173        NOTREACHED();
174    }
175    printf("%s", code);
176    return true;
177  }
178
179 private:
180  bool use_color_;
181
182  DISALLOW_COPY_AND_ASSIGN(PosixConsole);
183};
184
185}  // namespace
186
187SimpleConsole* SimpleConsole::Create() { return new PosixConsole(); }
188
189#else  // !defined(OS_WIN) && !defined(OS_POSIX)
190SimpleConsole* SimpleConsole::Create() { return NULL; }
191#endif
192
193///////////////////////////////////////////////////////////
194//  DiagnosticsWriter
195
196DiagnosticsWriter::DiagnosticsWriter(FormatType format)
197    : failures_(0), format_(format) {
198  // Only create consoles for non-log output.
199  if (format_ != LOG) {
200    console_.reset(SimpleConsole::Create());
201    console_->Init();
202  }
203}
204
205DiagnosticsWriter::~DiagnosticsWriter() {
206  if (console_.get())
207    console_->OnQuit();
208}
209
210bool DiagnosticsWriter::WriteInfoLine(const std::string& info_text) {
211  if (format_ == LOG) {
212    LOG(WARNING) << info_text;
213    return true;
214  } else {
215    if (console_.get()) {
216      console_->SetColor(SimpleConsole::DEFAULT);
217      console_->Write(base::UTF8ToUTF16(info_text + "\n"));
218    }
219  }
220  return true;
221}
222
223void DiagnosticsWriter::OnTestFinished(int index, DiagnosticsModel* model) {
224  const DiagnosticsModel::TestInfo& test_info = model->GetTest(index);
225  bool success = (DiagnosticsModel::TEST_OK == test_info.GetResult());
226  WriteResult(success,
227              test_info.GetName(),
228              test_info.GetTitle(),
229              test_info.GetOutcomeCode(),
230              test_info.GetAdditionalInfo());
231}
232
233void DiagnosticsWriter::OnAllTestsDone(DiagnosticsModel* model) {
234  WriteInfoLine(
235      base::StringPrintf("Finished %d tests.", model->GetTestRunCount()));
236}
237
238void DiagnosticsWriter::OnRecoveryFinished(int index, DiagnosticsModel* model) {
239  const DiagnosticsModel::TestInfo& test_info = model->GetTest(index);
240  WriteInfoLine("Finished Recovery for: " + test_info.GetTitle());
241}
242
243void DiagnosticsWriter::OnAllRecoveryDone(DiagnosticsModel* model) {
244  WriteInfoLine("Finished All Recovery operations.");
245}
246
247bool DiagnosticsWriter::WriteResult(bool success,
248                                    const std::string& id,
249                                    const std::string& name,
250                                    int outcome_code,
251                                    const std::string& extra) {
252  std::string result;
253  SimpleConsole::Color color;
254
255  if (success) {
256    result = "[PASS] ";
257    color = SimpleConsole::GREEN;
258  } else {
259    color = SimpleConsole::RED;
260    result = "[FAIL] ";
261    failures_++;
262  }
263
264  if (format_ != LOG) {
265    if (console_.get()) {
266      console_->SetColor(color);
267      console_->Write(base::ASCIIToUTF16(result));
268    }
269    if (format_ == MACHINE) {
270      return WriteInfoLine(base::StringPrintf(
271          "%03d %s (%s)", outcome_code, id.c_str(), extra.c_str()));
272    } else {
273      return WriteInfoLine(name + "\n       " + extra + "\n");
274    }
275  } else {
276    if (!success) {
277      // For log output, we only care about the tests that failed.
278      return WriteInfoLine(base::StringPrintf("%s%03d %s (%s)",
279                                              result.c_str(),
280                                              outcome_code,
281                                              id.c_str(),
282                                              extra.c_str()));
283    }
284  }
285  return true;
286}
287
288}  // namespace diagnostics
289