// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/diagnostics/diagnostics_main.h" #if defined(OS_POSIX) #include <stdio.h> #include <unistd.h> #endif #include <iostream> #include "app/app_paths.h" #include "base/basictypes.h" #include "base/command_line.h" #include "base/i18n/icu_util.h" #include "base/string_util.h" #include "base/sys_string_conversions.h" #include "base/time.h" #include "base/utf_string_conversions.h" #include "chrome/browser/diagnostics/diagnostics_model.h" #include "chrome/common/chrome_paths.h" #include "ui/base/ui_base_paths.h" namespace { // This is a minimalistic interface to wrap the platform console. This will be // eventually replaced by a view that can be subclassed for each platform and // that the approved look and feel. class SimpleConsole { public: enum Color { DEFAULT, RED, GREEN, }; virtual ~SimpleConsole() { } // Init must be called before using any other method. If it returns // false there would be no console output. virtual bool Init() = 0; // Writes a string to the console with the current color. virtual bool Write(const std::wstring& text) = 0; // Reads a string from the console. Internally it may be limited to 256 // characters. virtual bool Read(std::wstring* txt) = 0; // Sets the foreground and background color. virtual bool SetColor(Color color) = 0; // Create an appropriate SimpleConsole instance. May return NULL if there is // no implementation for the current platform. static SimpleConsole* Create(); }; #if defined(OS_WIN) // Wrapper for the windows console operating in high-level IO mode. class WinConsole : public SimpleConsole { public: // The ctor allocates a console always. This avoids having to ask // the user to start chrome from a command prompt. WinConsole() : std_out_(INVALID_HANDLE_VALUE), std_in_(INVALID_HANDLE_VALUE) { } virtual ~WinConsole() { ::FreeConsole(); } virtual bool Init() { ::AllocConsole(); return SetIOHandles(); } virtual bool Write(const std::wstring& txt) { DWORD sz = txt.size(); return (TRUE == ::WriteConsoleW(std_out_, txt.c_str(), sz, &sz, NULL)); } // Reads a string from the console. Internally it is limited to 256 // characters. virtual bool Read(std::wstring* txt) { wchar_t buf[256]; DWORD read = sizeof(buf) - sizeof(buf[0]); if (!::ReadConsoleW(std_in_, buf, read, &read, NULL)) return false; // Note that |read| is in bytes. txt->assign(buf, read/2); return true; } // Sets the foreground and background color. virtual bool SetColor(Color color) { uint16 color_combo = FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY; switch (color) { case RED: color_combo = FOREGROUND_RED|FOREGROUND_INTENSITY; break; case GREEN: color_combo = FOREGROUND_GREEN|FOREGROUND_INTENSITY; break; case DEFAULT: break; default: NOTREACHED(); } return (TRUE == ::SetConsoleTextAttribute(std_out_, color_combo)); } private: bool SetIOHandles() { std_out_ = ::GetStdHandle(STD_OUTPUT_HANDLE); std_in_ = ::GetStdHandle(STD_INPUT_HANDLE); return ((std_out_ != INVALID_HANDLE_VALUE) && (std_in_ != INVALID_HANDLE_VALUE)); } // The input and output handles to the screen. They seem to be // implemented as pipes but they have non-documented protocol. HANDLE std_out_; HANDLE std_in_; DISALLOW_COPY_AND_ASSIGN(WinConsole); }; SimpleConsole* SimpleConsole::Create() { return new WinConsole(); } #elif defined(OS_POSIX) class PosixConsole : public SimpleConsole { public: PosixConsole() : use_color_(false) { } virtual bool Init() { // Technically, we should also check the terminal capabilities before using // color, but in practice this is unlikely to be an issue. use_color_ = isatty(STDOUT_FILENO); return true; } virtual bool Write(const std::wstring& text) { printf("%s", base::SysWideToNativeMB(text).c_str()); return true; } virtual bool Read(std::wstring* txt) { std::string input; if (!std::getline(std::cin, input)) { std::cin.clear(); return false; } *txt = UTF8ToWide(input); return true; } virtual bool SetColor(Color color) { if (!use_color_) return false; const char* code = "\033[m"; switch (color) { case RED: code = "\033[1;31m"; break; case GREEN: code = "\033[1;32m"; break; case DEFAULT: break; default: NOTREACHED(); } printf("%s", code); return true; } private: bool use_color_; DISALLOW_COPY_AND_ASSIGN(PosixConsole); }; SimpleConsole* SimpleConsole::Create() { return new PosixConsole(); } #else // !defined(OS_WIN) && !defined(OS_POSIX) SimpleConsole* SimpleConsole::Create() { return NULL; } #endif // This class wraps a SimpleConsole for the specific use case of // writing the results of the diagnostic tests. // TODO(cpu) figure out the localization strategy. class TestWriter { public: // The |console| must be valid and properly initialized. This // class does not own it. explicit TestWriter(SimpleConsole* console) : console_(console), failures_(0) { } // How many tests reported failure. int failures() { return failures_; } // Write an informational line of text in white over black. bool WriteInfoText(const std::wstring& txt) { console_->SetColor(SimpleConsole::DEFAULT); return console_->Write(txt); } // Write a result block. It consist of two lines. The first line // has [PASS] or [FAIL] with |name| and the second line has // the text in |extra|. bool WriteResult(bool success, const std::wstring& name, const std::wstring& extra) { if (success) { console_->SetColor(SimpleConsole::GREEN); console_->Write(L"[PASS] "); } else { console_->SetColor(SimpleConsole::RED); console_->Write(L"[FAIL] "); failures_++; } WriteInfoText(name + L"\n"); std::wstring second_line(L" "); second_line.append(extra); return WriteInfoText(second_line + L"\n\n"); } private: SimpleConsole* console_; // Keeps track of how many tests reported failure. int failures_; DISALLOW_COPY_AND_ASSIGN(TestWriter); }; std::wstring PrintableUSCurrentTime() { base::Time::Exploded exploded = {0}; base::Time::Now().UTCExplode(&exploded); return StringPrintf(L"%d:%d:%d.%d:%d:%d", exploded.year, exploded.month, exploded.day_of_month, exploded.hour, exploded.minute, exploded.second); } // This class is a basic test controller. In this design the view (TestWriter) // and the model (DiagnosticsModel) do not talk to each other directly but they // are mediated by the controller. This has a name: 'passive view'. // More info at http://martinfowler.com/eaaDev/PassiveScreen.html class TestController : public DiagnosticsModel::Observer { public: explicit TestController(TestWriter* writer) : model_(NULL), writer_(writer) { } // Run all the diagnostics of |model| and invoke the view as the model // callbacks arrive. void Run(DiagnosticsModel* model) { std::wstring title(L"Chrome Diagnostics Mode ("); writer_->WriteInfoText(title.append(PrintableUSCurrentTime()) + L")\n"); if (!model) { writer_->WriteResult(false, L"Diagnostics start", L"model is null"); return; } bool icu_result = icu_util::Initialize(); if (!icu_result) { writer_->WriteResult(false, L"Diagnostics start", L"ICU failure"); return; } int count = model->GetTestAvailableCount(); writer_->WriteInfoText(StringPrintf(L"%d available test(s)\n\n", count)); model->RunAll(this); } // Next four are overridden from DiagnosticsModel::Observer. virtual void OnProgress(int id, int percent, DiagnosticsModel* model) { } virtual void OnSkipped(int id, DiagnosticsModel* model) { // TODO(cpu): display skipped tests. } virtual void OnFinished(int id, DiagnosticsModel* model) { // As each test completes we output the results. ShowResult(model->GetTest(id)); } virtual void OnDoneAll(DiagnosticsModel* model) { if (writer_->failures() > 0) { writer_->WriteInfoText(StringPrintf(L"DONE. %d failure(s)\n\n", writer_->failures())); } else { writer_->WriteInfoText(L"DONE\n\n"); } } private: void ShowResult(DiagnosticsModel::TestInfo& test_info) { bool success = (DiagnosticsModel::TEST_OK == test_info.GetResult()); writer_->WriteResult(success, UTF16ToWide(test_info.GetTitle()), UTF16ToWide(test_info.GetAdditionalInfo())); } DiagnosticsModel* model_; TestWriter* writer_; DISALLOW_COPY_AND_ASSIGN(TestController); }; } // namespace // This entry point is called from ChromeMain() when very few things // have been initialized. To wit: // -(win) Breakpad // -(macOS) base::EnableTerminationOnHeapCorruption() // -(macOS) base::EnableTerminationOnOutOfMemory() // -(all) RegisterInvalidParamHandler() // -(all) base::AtExitManager::AtExitManager() // -(macOS) base::ScopedNSAutoreleasePool // -(posix) base::GlobalDescriptors::GetInstance()->Set(kPrimaryIPCChannel) // -(linux) base::GlobalDescriptors::GetInstance()->Set(kCrashDumpSignal) // -(posix) setlocale(LC_ALL,..) // -(all) CommandLine::Init(); int DiagnosticsMain(const CommandLine& command_line) { // If we can't initialize the console exit right away. SimpleConsole* console = SimpleConsole::Create(); if (!console || !console->Init()) return 1; // We need to have the path providers registered. They both // return void so there is no early error signal that we can use. app::RegisterPathProvider(); ui::RegisterPathProvider(); chrome::RegisterPathProvider(); TestWriter writer(console); DiagnosticsModel* model = MakeDiagnosticsModel(command_line); TestController controller(&writer); // Run all the diagnostic tests. controller.Run(model); delete model; // The "press enter to continue" prompt isn't very unixy, so only do that on // Windows. #if defined(OS_WIN) // Block here so the user can see the results. writer.WriteInfoText(L"Press [enter] to continue\n"); std::wstring txt; console->Read(&txt); #endif delete console; return 0; }