1// Copyright (c) 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/test/chromedriver/chrome/console_logger.h"
6
7#include "base/compiler_specific.h"
8#include "base/format_macros.h"
9#include "base/memory/scoped_vector.h"
10#include "base/time/time.h"
11#include "base/values.h"
12#include "chrome/test/chromedriver/chrome/log.h"
13#include "chrome/test/chromedriver/chrome/status.h"
14#include "chrome/test/chromedriver/chrome/stub_devtools_client.h"
15#include "testing/gtest/include/gtest/gtest.h"
16
17namespace {
18
19class FakeDevToolsClient : public StubDevToolsClient {
20 public:
21  explicit FakeDevToolsClient(const std::string& id)
22      : id_(id), listener_(NULL) {}
23  virtual ~FakeDevToolsClient() {}
24
25  std::string PopSentCommand() {
26    std::string command;
27    if (!sent_command_queue_.empty()) {
28      command = sent_command_queue_.front();
29      sent_command_queue_.pop_front();
30    }
31    return command;
32  }
33
34  Status TriggerEvent(const std::string& method,
35                    const base::DictionaryValue& params) {
36    return listener_->OnEvent(this, method, params);
37  }
38
39  // Overridden from DevToolsClient:
40  virtual Status ConnectIfNecessary() OVERRIDE {
41    return listener_->OnConnected(this);
42  }
43
44  virtual Status SendCommandAndGetResult(
45      const std::string& method,
46      const base::DictionaryValue& params,
47      scoped_ptr<base::DictionaryValue>* result) OVERRIDE {
48    sent_command_queue_.push_back(method);
49    return Status(kOk);
50  }
51
52  virtual void AddListener(DevToolsEventListener* listener) OVERRIDE {
53    CHECK(!listener_);
54    listener_ = listener;
55  }
56
57  virtual const std::string& GetId() OVERRIDE {
58    return id_;
59  }
60
61 private:
62  const std::string id_;  // WebView id.
63  std::list<std::string> sent_command_queue_;  // Commands that were sent.
64  DevToolsEventListener* listener_;  // The fake allows only one event listener.
65};
66
67struct LogEntry {
68  const base::Time timestamp;
69  const Log::Level level;
70  const std::string source;
71  const std::string message;
72
73  LogEntry(const base::Time& timestamp,
74           Log::Level level,
75           const std::string& source,
76           const std::string& message)
77      : timestamp(timestamp), level(level), source(source), message(message) {}
78};
79
80class FakeLog : public Log {
81 public:
82  virtual void AddEntryTimestamped(const base::Time& timestamp,
83                                   Level level,
84                                   const std::string& source,
85                                   const std::string& message) OVERRIDE;
86
87  const ScopedVector<LogEntry>& GetEntries() {
88    return entries_;
89  }
90
91private:
92  ScopedVector<LogEntry> entries_;
93};
94
95void FakeLog::AddEntryTimestamped(const base::Time& timestamp,
96                                  Level level,
97                                  const std::string& source,
98                                  const std::string& message) {
99  entries_.push_back(new LogEntry(timestamp, level, source, message));
100}
101
102void ValidateLogEntry(const LogEntry *entry,
103                      Log::Level expected_level,
104                      const std::string& expected_source,
105                      const std::string& expected_message) {
106  EXPECT_EQ(expected_level, entry->level);
107  EXPECT_EQ(expected_source, entry->source);
108  EXPECT_LT(0, entry->timestamp.ToTimeT());
109  EXPECT_EQ(expected_message, entry->message);
110}
111
112void ConsoleLogParams(base::DictionaryValue* out_params,
113                      const char* source,
114                      const char* url,
115                      const char* level,
116                      int line,
117                      int column,
118                      const char* text) {
119  if (source != NULL)
120    out_params->SetString("message.source", source);
121  if (url != NULL)
122    out_params->SetString("message.url", url);
123  if (level != NULL)
124    out_params->SetString("message.level", level);
125  if (line != -1)
126    out_params->SetInteger("message.line", line);
127  if (column != -1)
128    out_params->SetInteger("message.column", column);
129  if (text != NULL)
130    out_params->SetString("message.text", text);
131}
132
133}  // namespace
134
135TEST(ConsoleLogger, ConsoleMessages) {
136  FakeDevToolsClient client("webview");
137  FakeLog log;
138  ConsoleLogger logger(&log);
139
140  client.AddListener(&logger);
141  logger.OnConnected(&client);
142  EXPECT_EQ("Console.enable", client.PopSentCommand());
143  EXPECT_TRUE(client.PopSentCommand().empty());
144
145  base::DictionaryValue params1;  // All fields are set.
146  ConsoleLogParams(&params1, "source1", "url1", "debug", 10, 1, "text1");
147  ASSERT_EQ(kOk, client.TriggerEvent("Console.messageAdded", params1).code());
148  // Ignored -- wrong method.
149  ASSERT_EQ(kOk, client.TriggerEvent("Console.gaga", params1).code());
150
151  base::DictionaryValue params2;  // All optionals are not set.
152  ConsoleLogParams(&params2, "source2", NULL, "log", -1, -1, "text2");
153  ASSERT_EQ(kOk, client.TriggerEvent("Console.messageAdded", params2).code());
154
155  base::DictionaryValue params3;  // Line without column, no source.
156  ConsoleLogParams(&params3, NULL, "url3", "warning", 30, -1, "text3");
157  ASSERT_EQ(kOk, client.TriggerEvent("Console.messageAdded", params3).code());
158
159  base::DictionaryValue params4;  // Column without line.
160  ConsoleLogParams(&params4, "source4", "url4", "error", -1, 4, "text4");
161  ASSERT_EQ(kOk, client.TriggerEvent("Console.messageAdded", params4).code());
162
163  base::DictionaryValue params5;  // Bad level name.
164  ConsoleLogParams(&params5, "source5", "url5", "gaga", 50, 5, "ulala");
165  ASSERT_EQ(kOk, client.TriggerEvent("Console.messageAdded", params5).code());
166
167  base::DictionaryValue params6;  // Unset level.
168  ConsoleLogParams(&params6, "source6", "url6", NULL, 60, 6, NULL);
169  ASSERT_EQ(kOk, client.TriggerEvent("Console.messageAdded", params6).code());
170
171  base::DictionaryValue params7;  // No text.
172  ConsoleLogParams(&params7, "source7", "url7", "log", -1, -1, NULL);
173  ASSERT_EQ(kOk, client.TriggerEvent("Console.messageAdded", params7).code());
174
175  base::DictionaryValue params8;  // No message object.
176  params8.SetInteger("gaga", 8);
177  ASSERT_EQ(kOk, client.TriggerEvent("Console.messageAdded", params8).code());
178
179  EXPECT_TRUE(client.PopSentCommand().empty());  // No other commands sent.
180
181  ASSERT_EQ(8u, log.GetEntries().size());
182  ValidateLogEntry(log.GetEntries()[0], Log::kDebug, "source1",
183                   "url1 10:1 text1");
184  ValidateLogEntry(log.GetEntries()[1], Log::kInfo, "source2",
185                   "source2 - text2");
186  ValidateLogEntry(log.GetEntries()[2], Log::kWarning, "", "url3 30 text3");
187  ValidateLogEntry(log.GetEntries()[3], Log::kError, "source4",
188                   "url4 - text4");
189  ValidateLogEntry(
190      log.GetEntries()[4], Log::kWarning, "",
191      "{\"message\":{\"column\":5,\"level\":\"gaga\",\"line\":50,"
192      "\"source\":\"source5\",\"text\":\"ulala\",\"url\":\"url5\"}}");
193  ValidateLogEntry(
194      log.GetEntries()[5], Log::kWarning, "",
195      "{\"message\":{\"column\":6,\"line\":60,"
196      "\"source\":\"source6\",\"url\":\"url6\"}}");
197  ValidateLogEntry(
198      log.GetEntries()[6], Log::kWarning, "",
199      "{\"message\":{\"level\":\"log\","
200      "\"source\":\"source7\",\"url\":\"url7\"}}");
201  ValidateLogEntry(log.GetEntries()[7], Log::kWarning, "", "{\"gaga\":8}");
202}
203