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
23 typedef content::TestMessageHandler::MessageResponse MessageResponse;
24
HandleMessage(const std::string & json)25 MessageResponse 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
MissingField(const std::string & type,const std::string & field)57 MessageResponse StructuredMessageHandler::MissingField(
58 const std::string& type,
59 const std::string& field) {
60 return InternalError(type + " message did not have field: " + field);
61 }
62
InternalError(const std::string & reason)63 MessageResponse StructuredMessageHandler::InternalError(
64 const std::string& reason) {
65 SetError(reason);
66 return DONE;
67 }
68
LoadTestMessageHandler()69 LoadTestMessageHandler::LoadTestMessageHandler()
70 : test_passed_(false) {
71 }
72
Log(const std::string & type,const std::string & message)73 void LoadTestMessageHandler::Log(const std::string& type,
74 const std::string& message) {
75 // TODO(ncbray) better logging.
76 LOG(INFO) << type << " " << message;
77 }
78
HandleStructuredMessage(const std::string & type,base::DictionaryValue * msg)79 MessageResponse 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.
106 class 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
test_passed() const116 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
NaClIntegrationMessageHandler()126 NaClIntegrationMessageHandler::NaClIntegrationMessageHandler()
127 : test_passed_(false) {
128 }
129
Log(const std::string & message)130 void NaClIntegrationMessageHandler::Log(const std::string& message) {
131 // TODO(ncbray) better logging.
132 LOG(INFO) << "|||| " << message;
133 }
134
HandleStructuredMessage(const std::string & type,base::DictionaryValue * msg)135 MessageResponse 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/
GetNaClVariantRoot(const base::FilePath::StringType & variant,base::FilePath * document_root)173 static 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
AddPnaclParm(const base::FilePath::StringType & url,base::FilePath::StringType * url_with_parm)182 static 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
AddPnaclDisabledParm(const base::FilePath::StringType & url,base::FilePath::StringType * url_with_parm)191 static 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
NaClBrowserTestBase()200 NaClBrowserTestBase::NaClBrowserTestBase() {
201 }
202
~NaClBrowserTestBase()203 NaClBrowserTestBase::~NaClBrowserTestBase() {
204 }
205
SetUpCommandLine(base::CommandLine * command_line)206 void NaClBrowserTestBase::SetUpCommandLine(base::CommandLine* command_line) {
207 command_line->AppendSwitch(switches::kEnableNaCl);
208 }
209
SetUpOnMainThread()210 void 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
GetDocumentRoot(base::FilePath * document_root)219 bool NaClBrowserTestBase::GetDocumentRoot(base::FilePath* document_root) {
220 return GetNaClVariantRoot(Variant(), document_root);
221 }
222
IsAPnaclTest()223 bool NaClBrowserTestBase::IsAPnaclTest() {
224 return false;
225 }
226
IsPnaclDisabled()227 bool NaClBrowserTestBase::IsPnaclDisabled() {
228 return false;
229 }
230
TestURL(const base::FilePath::StringType & url_fragment)231 GURL 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
RunJavascriptTest(const GURL & url,content::TestMessageHandler * handler)238 bool 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
RunLoadTest(const base::FilePath::StringType & test_file)248 void 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
RunNaClIntegrationTest(const base::FilePath::StringType & url_fragment,bool full_url)264 void 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
StartTestServer()283 bool 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
Variant()295 base::FilePath::StringType NaClBrowserTestNewlib::Variant() {
296 return FILE_PATH_LITERAL("newlib");
297 }
298
Variant()299 base::FilePath::StringType NaClBrowserTestGLibc::Variant() {
300 return FILE_PATH_LITERAL("glibc");
301 }
302
Variant()303 base::FilePath::StringType NaClBrowserTestPnacl::Variant() {
304 return FILE_PATH_LITERAL("pnacl");
305 }
306
IsAPnaclTest()307 bool NaClBrowserTestPnacl::IsAPnaclTest() {
308 return true;
309 }
310
Variant()311 base::FilePath::StringType NaClBrowserTestPnaclDisabled::Variant() {
312 return FILE_PATH_LITERAL("pnacl");
313 }
314
IsAPnaclTest()315 bool NaClBrowserTestPnaclDisabled::IsAPnaclTest() {
316 return true;
317 }
318
IsPnaclDisabled()319 bool NaClBrowserTestPnaclDisabled::IsPnaclDisabled() {
320 return true;
321 }
SetUpCommandLine(base::CommandLine * command_line)322 void NaClBrowserTestPnaclDisabled::SetUpCommandLine(
323 base::CommandLine* command_line) {
324 NaClBrowserTestBase::SetUpCommandLine(command_line);
325 command_line->AppendSwitch(switches::kDisablePnacl);
326 }
327
Variant()328 base::FilePath::StringType NaClBrowserTestNonSfiMode::Variant() {
329 return FILE_PATH_LITERAL("libc-free");
330 }
331
SetUpCommandLine(base::CommandLine * command_line)332 void NaClBrowserTestNonSfiMode::SetUpCommandLine(
333 base::CommandLine* command_line) {
334 NaClBrowserTestBase::SetUpCommandLine(command_line);
335 command_line->AppendSwitch(switches::kEnableNaClNonSfiMode);
336 }
337
Variant()338 base::FilePath::StringType NaClBrowserTestStatic::Variant() {
339 return FILE_PATH_LITERAL("static");
340 }
341
GetDocumentRoot(base::FilePath * document_root)342 bool NaClBrowserTestStatic::GetDocumentRoot(base::FilePath* document_root) {
343 *document_root = base::FilePath(FILE_PATH_LITERAL("chrome/test/data/nacl"));
344 return true;
345 }
346
Variant()347 base::FilePath::StringType NaClBrowserTestPnaclNonSfi::Variant() {
348 return FILE_PATH_LITERAL("nonsfi");
349 }
350
SetUpCommandLine(base::CommandLine * command_line)351 void NaClBrowserTestPnaclNonSfi::SetUpCommandLine(
352 base::CommandLine* command_line) {
353 NaClBrowserTestBase::SetUpCommandLine(command_line);
354 command_line->AppendSwitch(switches::kEnableNaClNonSfiMode);
355 }
356
SetUpCommandLine(CommandLine * command_line)357 void 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