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