Home | History | Annotate | Download | only in diagnostics
      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 
     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.
     65   WinConsole()
     66       : std_out_(INVALID_HANDLE_VALUE),
     67         std_in_(INVALID_HANDLE_VALUE)  {
     68   }
     69 
     70   virtual ~WinConsole() {
     71     ::FreeConsole();
     72   }
     73 
     74   virtual bool Init() {
     75     ::AllocConsole();
     76     return SetIOHandles();
     77   }
     78 
     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.
     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.
     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:
    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 
    131 SimpleConsole* SimpleConsole::Create() {
    132   return new WinConsole();
    133 }
    134 
    135 #elif defined(OS_POSIX)
    136 
    137 class PosixConsole : public SimpleConsole {
    138  public:
    139   PosixConsole() : use_color_(false) { }
    140 
    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 
    148   virtual bool Write(const std::wstring& text) {
    149     printf("%s", base::SysWideToNativeMB(text).c_str());
    150     return true;
    151   }
    152 
    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 
    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 
    190 SimpleConsole* SimpleConsole::Create() {
    191   return new PosixConsole();
    192 }
    193 
    194 #else  // !defined(OS_WIN) && !defined(OS_POSIX)
    195 
    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.
    208   explicit TestWriter(SimpleConsole* console)
    209       : console_(console),
    210         failures_(0) {
    211   }
    212 
    213   // How many tests reported failure.
    214   int failures() { return failures_; }
    215 
    216   // Write an informational line of text in white over black.
    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|.
    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 
    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:
    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.
    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.
    290   virtual void OnProgress(int id, int percent, DiagnosticsModel* model) {
    291   }
    292 
    293   virtual void OnSkipped(int id, DiagnosticsModel* model) {
    294     // TODO(cpu): display skipped tests.
    295   }
    296 
    297   virtual void OnFinished(int id, DiagnosticsModel* model) {
    298     // As each test completes we output the results.
    299     ShowResult(model->GetTest(id));
    300   }
    301 
    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:
    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 
    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