diagnostics_main.cc revision 3345a6884c488ff3a535c2c9acdd33d74b37e311
1// Copyright (c) 2010 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_main.h"
6
7#if defined(OS_POSIX)
8#include <stdio.h>
9#include <unistd.h>
10#endif
11
12#include <iostream>
13
14#include "app/app_paths.h"
15#include "base/basictypes.h"
16#include "base/command_line.h"
17#include "base/i18n/icu_util.h"
18#include "base/string_util.h"
19#include "base/sys_string_conversions.h"
20#include "base/time.h"
21#include "base/utf_string_conversions.h"
22#include "chrome/browser/diagnostics/diagnostics_model.h"
23#include "chrome/common/chrome_paths.h"
24
25namespace {
26// This is a minimalistic interface to wrap the platform console.  This will be
27// eventually replaced by a view that can be subclassed for each platform and
28// that the approved look and feel.
29class SimpleConsole {
30 public:
31  enum Color {
32    DEFAULT,
33    RED,
34    GREEN,
35  };
36
37  virtual ~SimpleConsole() { }
38
39  // Init must be called before using any other method. If it returns
40  // false there would be no console output.
41  virtual bool Init() = 0;
42
43  // Writes a string to the console with the current color.
44  virtual bool Write(const std::wstring& text) = 0;
45
46  // Reads a string from the console. Internally it may be limited to 256
47  // characters.
48  virtual bool Read(std::wstring* txt) = 0;
49
50  // Sets the foreground and background color.
51  virtual bool SetColor(Color color) = 0;
52
53  // Create an appropriate SimpleConsole instance.  May return NULL if there is
54  // no implementation for the current platform.
55  static SimpleConsole* Create();
56};
57
58#if defined(OS_WIN)
59// Wrapper for the windows console operating in high-level IO mode.
60class WinConsole : public SimpleConsole {
61 public:
62  // The ctor allocates a console always. This avoids having to ask
63  // the user to start chrome from a command prompt.
64  WinConsole()
65      : std_out_(INVALID_HANDLE_VALUE),
66        std_in_(INVALID_HANDLE_VALUE)  {
67  }
68
69  virtual ~WinConsole() {
70    ::FreeConsole();
71  }
72
73  virtual bool Init() {
74    ::AllocConsole();
75    return SetIOHandles();
76  }
77
78  virtual bool Write(const std::wstring& 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 bool Read(std::wstring* txt) {
86    wchar_t buf[256];
87    DWORD read = sizeof(buf) - sizeof(buf[0]);
88    if (!::ReadConsoleW(std_in_, buf, read, &read, NULL))
89      return false;
90    // Note that |read| is in bytes.
91    txt->assign(buf, read/2);
92    return true;
93  }
94
95  // Sets the foreground and background color.
96  virtual bool SetColor(Color color) {
97    uint16 color_combo =
98        FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY;
99    switch (color) {
100      case RED:
101        color_combo = FOREGROUND_RED|FOREGROUND_INTENSITY;
102        break;
103      case GREEN:
104        color_combo = FOREGROUND_GREEN|FOREGROUND_INTENSITY;
105        break;
106      case DEFAULT:
107        break;
108      default:
109        NOTREACHED();
110    }
111    return (TRUE == ::SetConsoleTextAttribute(std_out_, color_combo));
112  }
113
114 private:
115  bool SetIOHandles() {
116    std_out_ = ::GetStdHandle(STD_OUTPUT_HANDLE);
117    std_in_ = ::GetStdHandle(STD_INPUT_HANDLE);
118    return ((std_out_ != INVALID_HANDLE_VALUE) &&
119            (std_in_ != INVALID_HANDLE_VALUE));
120  }
121
122  // The input and output handles to the screen. They seem to be
123  // implemented as pipes but they have non-documented protocol.
124  HANDLE std_out_;
125  HANDLE std_in_;
126
127  DISALLOW_COPY_AND_ASSIGN(WinConsole);
128};
129
130SimpleConsole* SimpleConsole::Create() {
131  return new WinConsole();
132}
133
134#elif defined(OS_POSIX)
135
136class PosixConsole : public SimpleConsole {
137 public:
138  PosixConsole() : use_color_(false) { }
139
140  virtual bool Init() {
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 std::wstring& text) {
148    printf("%s", base::SysWideToNativeMB(text).c_str());
149    return true;
150  }
151
152  virtual bool Read(std::wstring* txt) {
153    std::string input;
154    if (!std::getline(std::cin, input)) {
155      std::cin.clear();
156      return false;
157    }
158    *txt = UTF8ToWide(input);
159    return true;
160  }
161
162  virtual bool SetColor(Color color) {
163    if (!use_color_)
164      return false;
165
166    const char* code = "\033[m";
167    switch (color) {
168      case RED:
169        code = "\033[1;31m";
170        break;
171      case GREEN:
172        code = "\033[1;32m";
173        break;
174      case DEFAULT:
175        break;
176      default:
177        NOTREACHED();
178    }
179    printf("%s", code);
180    return true;
181  }
182
183 private:
184  bool use_color_;
185
186  DISALLOW_COPY_AND_ASSIGN(PosixConsole);
187};
188
189SimpleConsole* SimpleConsole::Create() {
190  return new PosixConsole();
191}
192
193#else  // !defined(OS_WIN) && !defined(OS_POSIX)
194
195SimpleConsole* SimpleConsole::Create() {
196  return NULL;
197}
198#endif
199
200// This class wraps a SimpleConsole for the specific use case of
201// writing the results of the diagnostic tests.
202// TODO(cpu) figure out the localization strategy.
203class TestWriter {
204 public:
205  // The |console| must be valid and properly initialized. This
206  // class does not own it.
207  explicit TestWriter(SimpleConsole* console) : console_(console) {
208  }
209
210  // Write an informational line of text in white over black.
211  bool WriteInfoText(const std::wstring& txt) {
212    console_->SetColor(SimpleConsole::DEFAULT);
213    return console_->Write(txt);
214  }
215
216  // Write a result block. It consist of two lines. The first line
217  // has [PASS] or [FAIL] with |name| and the second line has
218  // the text in |extra|.
219  bool WriteResult(bool success, const std::wstring& name,
220                   const std::wstring& extra) {
221    if (success) {
222      console_->SetColor(SimpleConsole::GREEN);
223      console_->Write(L"[PASS] ");
224    } else {
225      console_->SetColor(SimpleConsole::RED);
226      console_->Write(L"[FAIL] ");
227    }
228    WriteInfoText(name + L"\n");
229    std::wstring second_line(L"   ");
230    second_line.append(extra);
231    return WriteInfoText(second_line + L"\n\n");
232  }
233
234 private:
235
236  SimpleConsole* console_;
237
238  DISALLOW_COPY_AND_ASSIGN(TestWriter);
239};
240
241std::wstring PrintableUSCurrentTime() {
242  base::Time::Exploded exploded = {0};
243  base::Time::Now().UTCExplode(&exploded);
244  return StringPrintf(L"%d:%d:%d.%d:%d:%d",
245      exploded.year, exploded.month, exploded.day_of_month,
246      exploded.hour, exploded.minute, exploded.second);
247}
248
249// This class is a basic test controller. In this design the view (TestWriter)
250// and the model (DiagnosticsModel) do not talk to each other directly but they
251// are mediated by the controller. This has a name: 'passive view'.
252// More info at http://martinfowler.com/eaaDev/PassiveScreen.html
253class TestController : public DiagnosticsModel::Observer {
254 public:
255  explicit TestController(TestWriter* writer)
256      : model_(NULL),
257        writer_(writer) {
258  }
259
260  // Run all the diagnostics of |model| and invoke the view as the model
261  // callbacks arrive.
262  void Run(DiagnosticsModel* model) {
263    std::wstring title(L"Chrome Diagnostics Mode (");
264    writer_->WriteInfoText(title.append(PrintableUSCurrentTime()) + L")\n");
265    if (!model) {
266      writer_->WriteResult(false, L"Diagnostics start", L"model is null");
267      return;
268    }
269    bool icu_result = icu_util::Initialize();
270    if (!icu_result) {
271      writer_->WriteResult(false, L"Diagnostics start", L"ICU failure");
272      return;
273    }
274    int count = model->GetTestAvailableCount();
275    writer_->WriteInfoText(StringPrintf(L"%d available test(s)\n\n", count));
276    model->RunAll(this);
277  }
278
279  // Next four are overridden from DiagnosticsModel::Observer.
280  virtual void OnProgress(int id, int percent, DiagnosticsModel* model) {
281  }
282
283  virtual void OnSkipped(int id, DiagnosticsModel* model) {
284    // TODO(cpu): display skipped tests.
285  }
286
287  virtual void OnFinished(int id, DiagnosticsModel* model) {
288    // As each test completes we output the results.
289    ShowResult(model->GetTest(id));
290  }
291
292  virtual void OnDoneAll(DiagnosticsModel* model) {
293    writer_->WriteInfoText(L"DONE\n\n");
294  }
295
296 private:
297  void ShowResult(DiagnosticsModel::TestInfo& test_info) {
298    bool success = (DiagnosticsModel::TEST_OK == test_info.GetResult());
299    writer_->WriteResult(success, UTF16ToWide(test_info.GetTitle()),
300                         UTF16ToWide(test_info.GetAdditionalInfo()));
301  }
302
303  DiagnosticsModel* model_;
304  TestWriter* writer_;
305
306  DISALLOW_COPY_AND_ASSIGN(TestController);
307};
308}  // namespace
309
310// This entry point is called from ChromeMain() when very few things
311// have been initialized. To wit:
312// -(win)   Breakpad
313// -(macOS) base::EnableTerminationOnHeapCorruption()
314// -(macOS) base::EnableTerminationOnOutOfMemory()
315// -(all)   RegisterInvalidParamHandler()
316// -(all)   base::AtExitManager::AtExitManager()
317// -(macOS) base::ScopedNSAutoreleasePool
318// -(posix) Singleton<base::GlobalDescriptors>::Set(kPrimaryIPCChannel)
319// -(linux) Singleton<base::GlobalDescriptors>::Set(kCrashDumpSignal)
320// -(posix) setlocale(LC_ALL,..)
321// -(all)   CommandLine::Init();
322
323int DiagnosticsMain(const CommandLine& command_line) {
324  // If we can't initialize the console exit right away.
325  SimpleConsole* console = SimpleConsole::Create();
326  if (!console || !console->Init())
327    return 1;
328
329  // We need to have the path providers registered. They both
330  // return void so there is no early error signal that we can use.
331  app::RegisterPathProvider();
332  chrome::RegisterPathProvider();
333
334  TestWriter writer(console);
335  DiagnosticsModel* model = MakeDiagnosticsModel(command_line);
336  TestController controller(&writer);
337
338  // Run all the diagnostic tests.
339  controller.Run(model);
340  delete model;
341
342  // The "press enter to continue" prompt isn't very unixy, so only do that on
343  // Windows.
344#if defined(OS_WIN)
345  // Block here so the user can see the results.
346  writer.WriteInfoText(L"Press [enter] to continue\n");
347  std::wstring txt;
348  console->Read(&txt);
349#endif
350  delete console;
351  return 0;
352}
353