1// Copyright (c) 2012 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/nacl/nacl_browsertest_util.h"
6
7#include <stdlib.h>
8#include "base/command_line.h"
9#include "base/json/json_reader.h"
10#include "base/path_service.h"
11#include "base/values.h"
12#include "chrome/browser/ui/browser.h"
13#include "chrome/browser/ui/tabs/tab_strip_model.h"
14#include "chrome/common/chrome_paths.h"
15#include "chrome/common/chrome_switches.h"
16#include "chrome/test/base/ui_test_utils.h"
17#include "components/nacl/common/nacl_switches.h"
18#include "content/public/browser/plugin_service.h"
19#include "content/public/browser/web_contents.h"
20#include "content/public/common/webplugininfo.h"
21#include "net/base/net_util.h"
22
23typedef content::TestMessageHandler::MessageResponse MessageResponse;
24
25MessageResponse StructuredMessageHandler::HandleMessage(
26    const std::string& json) {
27  scoped_ptr<base::Value> value;
28  base::JSONReader reader(base::JSON_ALLOW_TRAILING_COMMAS);
29  // Automation messages are stringified before they are sent because the
30  // automation channel cannot handle arbitrary objects.  This means we
31  // need to decode the json twice to get the original message.
32  value.reset(reader.ReadToValue(json));
33  if (!value.get())
34    return InternalError("Could parse automation JSON: " + json +
35                         " because " + reader.GetErrorMessage());
36
37  std::string temp;
38  if (!value->GetAsString(&temp))
39    return InternalError("Message was not a string: " + json);
40
41  value.reset(reader.ReadToValue(temp));
42  if (!value.get())
43    return InternalError("Could not parse message JSON: " + temp +
44                         " because " + reader.GetErrorMessage());
45
46  base::DictionaryValue* msg;
47  if (!value->GetAsDictionary(&msg))
48    return InternalError("Message was not an object: " + temp);
49
50  std::string type;
51  if (!msg->GetString("type", &type))
52    return MissingField("unknown", "type");
53
54  return HandleStructuredMessage(type, msg);
55}
56
57MessageResponse StructuredMessageHandler::MissingField(
58    const std::string& type,
59    const std::string& field) {
60  return InternalError(type + " message did not have field: " + field);
61}
62
63MessageResponse StructuredMessageHandler::InternalError(
64    const std::string& reason) {
65  SetError(reason);
66  return DONE;
67}
68
69LoadTestMessageHandler::LoadTestMessageHandler()
70    : test_passed_(false) {
71}
72
73void LoadTestMessageHandler::Log(const std::string& type,
74                                 const std::string& message) {
75  // TODO(ncbray) better logging.
76  LOG(INFO) << type << " " << message;
77}
78
79MessageResponse LoadTestMessageHandler::HandleStructuredMessage(
80   const std::string& type,
81   base::DictionaryValue* msg) {
82  if (type == "Log") {
83    std::string message;
84    if (!msg->GetString("message", &message))
85      return MissingField(type, "message");
86    Log("LOG", message);
87    return CONTINUE;
88  } else if (type == "Shutdown") {
89    std::string message;
90    if (!msg->GetString("message", &message))
91      return MissingField(type, "message");
92    if (!msg->GetBoolean("passed", &test_passed_))
93      return MissingField(type, "passed");
94    Log("SHUTDOWN", message);
95    return DONE;
96  } else {
97    return InternalError("Unknown message type: " + type);
98  }
99}
100
101// A message handler for nacl_integration tests ported to be browser_tests.
102// nacl_integration tests report to their test jig using a series of RPC calls
103// that are encoded as URL requests. When these tests run as browser_tests,
104// they make the same RPC requests, but use the automation channel instead of
105// URL requests. This message handler decodes and responds to these requests.
106class NaClIntegrationMessageHandler : public StructuredMessageHandler {
107 public:
108  NaClIntegrationMessageHandler();
109
110  void Log(const std::string& message);
111
112  virtual MessageResponse HandleStructuredMessage(
113      const std::string& type,
114      base::DictionaryValue* msg) OVERRIDE;
115
116  bool test_passed() const {
117    return test_passed_;
118  }
119
120 private:
121  bool test_passed_;
122
123  DISALLOW_COPY_AND_ASSIGN(NaClIntegrationMessageHandler);
124};
125
126NaClIntegrationMessageHandler::NaClIntegrationMessageHandler()
127    : test_passed_(false) {
128}
129
130void NaClIntegrationMessageHandler::Log(const std::string& message) {
131  // TODO(ncbray) better logging.
132  LOG(INFO) << "|||| " << message;
133}
134
135MessageResponse NaClIntegrationMessageHandler::HandleStructuredMessage(
136    const std::string& type,
137    base::DictionaryValue* msg) {
138  if (type == "TestLog") {
139    std::string message;
140    if (!msg->GetString("message", &message))
141      return MissingField(type, "message");
142    Log(message);
143    return CONTINUE;
144  } else if (type == "Shutdown") {
145    std::string message;
146    if (!msg->GetString("message", &message))
147      return MissingField(type, "message");
148    if (!msg->GetBoolean("passed", &test_passed_))
149      return MissingField(type, "passed");
150    Log(message);
151    return DONE;
152  } else if (type == "Ping") {
153    return CONTINUE;
154  } else if (type == "JavaScriptIsAlive") {
155    return CONTINUE;
156  } else {
157    return InternalError("Unknown message type: " + type);
158  }
159}
160
161// NaCl browser tests serve files out of the build directory because nexes and
162// pexes are artifacts of the build.  To keep things tidy, all test data is kept
163// in a subdirectory.  Several variants of a test may be run, for example when
164// linked against newlib and when linked against glibc.  These variants are kept
165// in different subdirectories.  For example, the build directory will look
166// something like this on Linux:
167// out/
168//     Release/
169//             nacl_test_data/
170//                            newlib/
171//                            glibc/
172//                            pnacl/
173static bool GetNaClVariantRoot(const base::FilePath::StringType& variant,
174                               base::FilePath* document_root) {
175  if (!ui_test_utils::GetRelativeBuildDirectory(document_root))
176    return false;
177  *document_root = document_root->Append(FILE_PATH_LITERAL("nacl_test_data"));
178  *document_root = document_root->Append(variant);
179  return true;
180}
181
182static void AddPnaclParm(const base::FilePath::StringType& url,
183                         base::FilePath::StringType* url_with_parm) {
184  if (url.find(FILE_PATH_LITERAL("?")) == base::FilePath::StringType::npos) {
185    *url_with_parm = url + FILE_PATH_LITERAL("?pnacl=1");
186  } else {
187    *url_with_parm = url + FILE_PATH_LITERAL("&pnacl=1");
188  }
189}
190
191NaClBrowserTestBase::NaClBrowserTestBase() {
192}
193
194NaClBrowserTestBase::~NaClBrowserTestBase() {
195}
196
197void NaClBrowserTestBase::SetUpCommandLine(base::CommandLine* command_line) {
198  command_line->AppendSwitch(switches::kEnableNaCl);
199}
200
201void NaClBrowserTestBase::SetUpOnMainThread() {
202  ASSERT_TRUE(StartTestServer()) << "Cannot start test server.";
203}
204
205bool NaClBrowserTestBase::GetDocumentRoot(base::FilePath* document_root) {
206  return GetNaClVariantRoot(Variant(), document_root);
207}
208
209bool NaClBrowserTestBase::IsAPnaclTest() {
210  return false;
211}
212
213GURL NaClBrowserTestBase::TestURL(
214    const base::FilePath::StringType& url_fragment) {
215  base::FilePath expanded_url = base::FilePath(FILE_PATH_LITERAL("files"));
216  expanded_url = expanded_url.Append(url_fragment);
217  return test_server_->GetURL(expanded_url.MaybeAsASCII());
218}
219
220bool NaClBrowserTestBase::RunJavascriptTest(
221    const GURL& url,
222    content::TestMessageHandler* handler) {
223  content::JavascriptTestObserver observer(
224      browser()->tab_strip_model()->GetActiveWebContents(),
225      handler);
226  ui_test_utils::NavigateToURL(browser(), url);
227  return observer.Run();
228}
229
230void NaClBrowserTestBase::RunLoadTest(
231    const base::FilePath::StringType& test_file) {
232  LoadTestMessageHandler handler;
233  base::FilePath::StringType test_file_with_pnacl = test_file;
234  if (IsAPnaclTest()) {
235    AddPnaclParm(test_file, &test_file_with_pnacl);
236  }
237  base::FilePath::StringType test_file_with_both = test_file_with_pnacl;
238  bool ok = RunJavascriptTest(TestURL(test_file_with_both), &handler);
239  ASSERT_TRUE(ok) << handler.error_message();
240  ASSERT_TRUE(handler.test_passed()) << "Test failed.";
241}
242
243void NaClBrowserTestBase::RunNaClIntegrationTest(
244    const base::FilePath::StringType& url_fragment, bool full_url) {
245  NaClIntegrationMessageHandler handler;
246  base::FilePath::StringType url_fragment_with_pnacl = url_fragment;
247  if (IsAPnaclTest()) {
248    AddPnaclParm(url_fragment, &url_fragment_with_pnacl);
249  }
250  base::FilePath::StringType url_fragment_with_both = url_fragment_with_pnacl;
251  bool ok = RunJavascriptTest(full_url
252                              ? GURL(url_fragment_with_both)
253                              : TestURL(url_fragment_with_both),
254                              &handler);
255  ASSERT_TRUE(ok) << handler.error_message();
256  ASSERT_TRUE(handler.test_passed()) << "Test failed.";
257}
258
259bool NaClBrowserTestBase::StartTestServer() {
260  // Launch the web server.
261  base::FilePath document_root;
262  if (!GetDocumentRoot(&document_root))
263    return false;
264  test_server_.reset(new net::SpawnedTestServer(
265                         net::SpawnedTestServer::TYPE_HTTP,
266                         net::SpawnedTestServer::kLocalhost,
267                         document_root));
268  return test_server_->Start();
269}
270
271base::FilePath::StringType NaClBrowserTestNewlib::Variant() {
272  return FILE_PATH_LITERAL("newlib");
273}
274
275base::FilePath::StringType NaClBrowserTestGLibc::Variant() {
276  return FILE_PATH_LITERAL("glibc");
277}
278
279base::FilePath::StringType NaClBrowserTestPnacl::Variant() {
280  return FILE_PATH_LITERAL("pnacl");
281}
282
283bool NaClBrowserTestPnacl::IsAPnaclTest() {
284  return true;
285}
286
287base::FilePath::StringType NaClBrowserTestNonSfiMode::Variant() {
288  return FILE_PATH_LITERAL("libc-free");
289}
290
291void NaClBrowserTestNonSfiMode::SetUpCommandLine(
292    base::CommandLine* command_line) {
293  NaClBrowserTestBase::SetUpCommandLine(command_line);
294  command_line->AppendSwitch(switches::kEnableNaClNonSfiMode);
295}
296
297base::FilePath::StringType NaClBrowserTestStatic::Variant() {
298  return FILE_PATH_LITERAL("static");
299}
300
301bool NaClBrowserTestStatic::GetDocumentRoot(base::FilePath* document_root) {
302  *document_root = base::FilePath(FILE_PATH_LITERAL("chrome/test/data/nacl"));
303  return true;
304}
305
306base::FilePath::StringType NaClBrowserTestPnaclNonSfi::Variant() {
307  return FILE_PATH_LITERAL("nonsfi");
308}
309
310void NaClBrowserTestPnaclNonSfi::SetUpCommandLine(
311    base::CommandLine* command_line) {
312  NaClBrowserTestBase::SetUpCommandLine(command_line);
313  command_line->AppendSwitch(switches::kEnableNaClNonSfiMode);
314}
315
316void NaClBrowserTestNewlibExtension::SetUpCommandLine(
317    CommandLine* command_line) {
318  NaClBrowserTestBase::SetUpCommandLine(command_line);
319  base::FilePath src_root;
320  ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &src_root));
321
322  // Extension-based tests should specialize the GetDocumentRoot() / Variant()
323  // to point at the isolated the test extension directory.
324  // Otherwise, multiple NaCl extensions tests will end up sharing the
325  // same directory when loading the extension files.
326  base::FilePath document_root;
327  ASSERT_TRUE(GetDocumentRoot(&document_root));
328
329  // Document root is relative to source root, and source root may not be CWD.
330  command_line->AppendSwitchPath(switches::kLoadExtension,
331                                 src_root.Append(document_root));
332}
333
334void NaClBrowserTestGLibcExtension::SetUpCommandLine(
335    CommandLine* command_line) {
336  NaClBrowserTestBase::SetUpCommandLine(command_line);
337  base::FilePath src_root;
338  ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &src_root));
339
340  // Extension-based tests should specialize the GetDocumentRoot() / Variant()
341  // to point at the isolated the test extension directory.
342  // Otherwise, multiple NaCl extensions tests will end up sharing the
343  // same directory when loading the extension files.
344  base::FilePath document_root;
345  ASSERT_TRUE(GetDocumentRoot(&document_root));
346
347  // Document root is relative to source root, and source root may not be CWD.
348  command_line->AppendSwitchPath(switches::kLoadExtension,
349                                 src_root.Append(document_root));
350}
351