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