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
191static void AddPnaclDisabledParm(const base::FilePath::StringType& url,
192                                 base::FilePath::StringType* url_with_parm) {
193  if (url.find(FILE_PATH_LITERAL("?")) == base::FilePath::StringType::npos) {
194    *url_with_parm = url + FILE_PATH_LITERAL("?pnacl_disabled=1");
195  } else {
196    *url_with_parm = url + FILE_PATH_LITERAL("&pnacl_disabled=1");
197  }
198}
199
200NaClBrowserTestBase::NaClBrowserTestBase() {
201}
202
203NaClBrowserTestBase::~NaClBrowserTestBase() {
204}
205
206void NaClBrowserTestBase::SetUpCommandLine(base::CommandLine* command_line) {
207  command_line->AppendSwitch(switches::kEnableNaCl);
208}
209
210void NaClBrowserTestBase::SetUpOnMainThread() {
211  // Sanity check.
212  base::FilePath plugin_lib;
213  ASSERT_TRUE(PathService::Get(chrome::FILE_NACL_PLUGIN, &plugin_lib));
214  ASSERT_TRUE(base::PathExists(plugin_lib)) << plugin_lib.value();
215
216  ASSERT_TRUE(StartTestServer()) << "Cannot start test server.";
217}
218
219bool NaClBrowserTestBase::GetDocumentRoot(base::FilePath* document_root) {
220  return GetNaClVariantRoot(Variant(), document_root);
221}
222
223bool NaClBrowserTestBase::IsAPnaclTest() {
224  return false;
225}
226
227bool NaClBrowserTestBase::IsPnaclDisabled() {
228  return false;
229}
230
231GURL NaClBrowserTestBase::TestURL(
232    const base::FilePath::StringType& url_fragment) {
233  base::FilePath expanded_url = base::FilePath(FILE_PATH_LITERAL("files"));
234  expanded_url = expanded_url.Append(url_fragment);
235  return test_server_->GetURL(expanded_url.MaybeAsASCII());
236}
237
238bool NaClBrowserTestBase::RunJavascriptTest(
239    const GURL& url,
240    content::TestMessageHandler* handler) {
241  content::JavascriptTestObserver observer(
242      browser()->tab_strip_model()->GetActiveWebContents(),
243      handler);
244  ui_test_utils::NavigateToURL(browser(), url);
245  return observer.Run();
246}
247
248void NaClBrowserTestBase::RunLoadTest(
249    const base::FilePath::StringType& test_file) {
250  LoadTestMessageHandler handler;
251  base::FilePath::StringType test_file_with_pnacl = test_file;
252  if (IsAPnaclTest()) {
253    AddPnaclParm(test_file, &test_file_with_pnacl);
254  }
255  base::FilePath::StringType test_file_with_both = test_file_with_pnacl;
256  if (IsPnaclDisabled()) {
257    AddPnaclDisabledParm(test_file_with_pnacl, &test_file_with_both);
258  }
259  bool ok = RunJavascriptTest(TestURL(test_file_with_both), &handler);
260  ASSERT_TRUE(ok) << handler.error_message();
261  ASSERT_TRUE(handler.test_passed()) << "Test failed.";
262}
263
264void NaClBrowserTestBase::RunNaClIntegrationTest(
265    const base::FilePath::StringType& url_fragment, bool full_url) {
266  NaClIntegrationMessageHandler handler;
267  base::FilePath::StringType url_fragment_with_pnacl = url_fragment;
268  if (IsAPnaclTest()) {
269    AddPnaclParm(url_fragment, &url_fragment_with_pnacl);
270  }
271  base::FilePath::StringType url_fragment_with_both = url_fragment_with_pnacl;
272  if (IsPnaclDisabled()) {
273    AddPnaclDisabledParm(url_fragment_with_pnacl, &url_fragment_with_both);
274  }
275  bool ok = RunJavascriptTest(full_url
276                              ? GURL(url_fragment_with_both)
277                              : TestURL(url_fragment_with_both),
278                              &handler);
279  ASSERT_TRUE(ok) << handler.error_message();
280  ASSERT_TRUE(handler.test_passed()) << "Test failed.";
281}
282
283bool NaClBrowserTestBase::StartTestServer() {
284  // Launch the web server.
285  base::FilePath document_root;
286  if (!GetDocumentRoot(&document_root))
287    return false;
288  test_server_.reset(new net::SpawnedTestServer(
289                         net::SpawnedTestServer::TYPE_HTTP,
290                         net::SpawnedTestServer::kLocalhost,
291                         document_root));
292  return test_server_->Start();
293}
294
295base::FilePath::StringType NaClBrowserTestNewlib::Variant() {
296  return FILE_PATH_LITERAL("newlib");
297}
298
299base::FilePath::StringType NaClBrowserTestGLibc::Variant() {
300  return FILE_PATH_LITERAL("glibc");
301}
302
303base::FilePath::StringType NaClBrowserTestPnacl::Variant() {
304  return FILE_PATH_LITERAL("pnacl");
305}
306
307bool NaClBrowserTestPnacl::IsAPnaclTest() {
308  return true;
309}
310
311base::FilePath::StringType NaClBrowserTestPnaclDisabled::Variant() {
312  return FILE_PATH_LITERAL("pnacl");
313}
314
315bool NaClBrowserTestPnaclDisabled::IsAPnaclTest() {
316  return true;
317}
318
319bool NaClBrowserTestPnaclDisabled::IsPnaclDisabled() {
320  return true;
321}
322void NaClBrowserTestPnaclDisabled::SetUpCommandLine(
323    base::CommandLine* command_line) {
324  NaClBrowserTestBase::SetUpCommandLine(command_line);
325  command_line->AppendSwitch(switches::kDisablePnacl);
326}
327
328base::FilePath::StringType NaClBrowserTestNonSfiMode::Variant() {
329  return FILE_PATH_LITERAL("libc-free");
330}
331
332void NaClBrowserTestNonSfiMode::SetUpCommandLine(
333    base::CommandLine* command_line) {
334  NaClBrowserTestBase::SetUpCommandLine(command_line);
335  command_line->AppendSwitch(switches::kEnableNaClNonSfiMode);
336}
337
338base::FilePath::StringType NaClBrowserTestStatic::Variant() {
339  return FILE_PATH_LITERAL("static");
340}
341
342bool NaClBrowserTestStatic::GetDocumentRoot(base::FilePath* document_root) {
343  *document_root = base::FilePath(FILE_PATH_LITERAL("chrome/test/data/nacl"));
344  return true;
345}
346
347base::FilePath::StringType NaClBrowserTestPnaclNonSfi::Variant() {
348  return FILE_PATH_LITERAL("nonsfi");
349}
350
351void NaClBrowserTestPnaclNonSfi::SetUpCommandLine(
352    base::CommandLine* command_line) {
353  NaClBrowserTestBase::SetUpCommandLine(command_line);
354  command_line->AppendSwitch(switches::kEnableNaClNonSfiMode);
355}
356
357void NaClBrowserTestNewlibExtension::SetUpCommandLine(
358    CommandLine* command_line) {
359  NaClBrowserTestBase::SetUpCommandLine(command_line);
360  base::FilePath src_root;
361  ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &src_root));
362
363  // Extension-based tests should specialize the GetDocumentRoot() / Variant()
364  // to point at the isolated the test extension directory.
365  // Otherwise, multiple NaCl extensions tests will end up sharing the
366  // same directory when loading the extension files.
367  base::FilePath document_root;
368  ASSERT_TRUE(GetDocumentRoot(&document_root));
369
370  // Document root is relative to source root, and source root may not be CWD.
371  command_line->AppendSwitchPath(switches::kLoadExtension,
372                                 src_root.Append(document_root));
373}
374