Commit 08e64697 authored by ncbray@google.com's avatar ncbray@google.com

Add NaCl smoke test to browser_tests.

The smoke test is simple - it checks that Chrome can load nexes. The more
interesting part of this CL is that it lays the groundwork for adding more NaCl
tests to browser_tests. This will allow many nacl_integration tests to be
rewritten as browser_tests. This CL adds an extensible jig for decoding
messages from Javascript, which makes it easier to add test logic in the web
page. This CL also establishes locations in the build directory for nexes and
other test data.

There are two followup CLs planned: 1) simplify how nexes are built in GYP
and 2) share the code for running tests in Javascript with the PPAPI test
suite.

This is a recommit of r151716 with Linux ASAN tests disabled and GYP tweaks for
VS2010.

BUG= http://code.google.com/p/nativeclient/issues/detail?id=2959
TEST= browser_tests --gtest_filter=NaClBrowserTest*

Review URL: https://chromiumcodereview.appspot.com/10827357

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@151963 0039d316-1c4b-4281-b951-d872f2087c98
parent d3128595
......@@ -3100,8 +3100,12 @@
],
'conditions': [
['disable_nacl_untrusted==0', {
'sources': [
'test/nacl/nacl_browsertest.cc',
],
'dependencies': [
'../ppapi/ppapi_untrusted.gyp:ppapi_nacl_tests',
'test/data/nacl/nacl_test_data.gyp:nacl_tests',
'../ppapi/ppapi_untrusted.gyp:ppapi_nacl_tests',
],
}],
['OS=="win" or OS=="linux"', {
......
......@@ -15,6 +15,7 @@
#include "base/callback.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/json/json_reader.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
......@@ -315,6 +316,48 @@ GURL GetTestUrl(const FilePath& dir, const FilePath& file) {
return net::FilePathToFileURL(GetTestFilePath(dir, file));
}
bool GetRelativeBuildDirectory(FilePath* build_dir) {
// This function is used to find the build directory so TestServer can serve
// built files (nexes, etc). TestServer expects a path relative to the source
// root.
FilePath exe_dir = CommandLine::ForCurrentProcess()->GetProgram().DirName();
FilePath src_dir;
if (!PathService::Get(base::DIR_SOURCE_ROOT, &src_dir))
return false;
// We must first generate absolute paths to SRC and EXE and from there
// generate a relative path.
if (!exe_dir.IsAbsolute())
file_util::AbsolutePath(&exe_dir);
if (!src_dir.IsAbsolute())
file_util::AbsolutePath(&src_dir);
if (!exe_dir.IsAbsolute())
return false;
if (!src_dir.IsAbsolute())
return false;
size_t match, exe_size, src_size;
std::vector<FilePath::StringType> src_parts, exe_parts;
// Determine point at which src and exe diverge.
exe_dir.GetComponents(&exe_parts);
src_dir.GetComponents(&src_parts);
exe_size = exe_parts.size();
src_size = src_parts.size();
for (match = 0; match < exe_size && match < src_size; ++match) {
if (exe_parts[match] != src_parts[match])
break;
}
// Create a relative path.
*build_dir = FilePath();
for (size_t tmp_itr = match; tmp_itr < src_size; ++tmp_itr)
*build_dir = build_dir->Append(FILE_PATH_LITERAL(".."));
for (; match < exe_size; ++match)
*build_dir = build_dir->Append(exe_parts[match]);
return true;
}
AppModalDialog* WaitForAppModalDialog() {
content::WindowedNotificationObserver observer(
chrome::NOTIFICATION_APP_MODAL_DIALOG_SHOWN,
......
......@@ -130,6 +130,9 @@ FilePath GetTestFilePath(const FilePath& dir, const FilePath& file);
// The returned path is GURL format.
GURL GetTestUrl(const FilePath& dir, const FilePath& file);
// Generate the path of the build directory, relative to the source root.
bool GetRelativeBuildDirectory(FilePath *build_dir);
// Blocks until an application modal dialog is showns and returns it.
AppModalDialog* WaitForAppModalDialog();
......
include_rules = [
# NaCl modules include PPAPI headers.
"+ppapi",
]
set noparent
bradnelson@chromium.org
ncbray@chromium.org
mseaborn@chromium.org
<html>
<!--
Copyright (c) 2012 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<head>
<title>NaCl Load Test</title>
</head>
<body>
<h2>NaCl Load Test</h2>
</body>
<script>
function report(msg) {
domAutomationController.setAutomationId(0);
// The automation controller seems to choke on Objects, so turn them into
// strings.
domAutomationController.send(JSON.stringify(msg));
}
function create(manifest_url) {
var embed = document.createElement("embed");
embed.src = manifest_url;
embed.type = "application/x-nacl";
embed.addEventListener("load", function(evt) {
report({type: "Shutdown", message: "1 test passed.", passed: true});
}, true);
embed.addEventListener("error", function(evt) {
report({type: "Log", message: "Load error: " + embed.lastError});
report({type: "Shutdown", message: "1 test failed.", passed: false});
}, true);
document.body.appendChild(embed);
}
create("simple.nmf");
</script>
</html>
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
{
'includes': [
'../../../../native_client/build/untrusted.gypi',
],
# TODO(ncbray) factor nexe building code into a common file.
'targets': [
{
'target_name': 'nacl_tests',
'type': 'none',
'dependencies': [
'../../../../ppapi/ppapi_untrusted.gyp:ppapi_cpp_lib',
'../../../../ppapi/native_client/native_client.gyp:ppapi_lib',
'../../../../ppapi/native_client/native_client.gyp:nacl_irt',
],
'copies': [
{
'destination': '<(PRODUCT_DIR)/nacl_test_data/newlib',
'files': [
'nacl_load_test.html',
],
},
{
'destination': '<(PRODUCT_DIR)/nacl_test_data/glibc',
'files': [
'nacl_load_test.html',
],
},
],
'variables': {
'nexe_target': 'simple',
'build_newlib': 1,
'out_newlib32': '<(PRODUCT_DIR)/nacl_test_data/newlib/>(nexe_target)_newlib_x86_32.nexe',
'out_newlib64': '<(PRODUCT_DIR)/nacl_test_data/newlib/>(nexe_target)_newlib_x86_64.nexe',
'out_newlib_arm': '<(PRODUCT_DIR)/nacl_test_data/newlib/>(nexe_target)_newlib_arm.nexe',
'out_glibc32': '<(PRODUCT_DIR)/nacl_test_data/glibc/>(nexe_target)_glibc_x86_32.nexe',
'out_glibc64': '<(PRODUCT_DIR)/nacl_test_data/glibc/>(nexe_target)_glibc_x86_64.nexe',
'out_glibc_arm': '<(PRODUCT_DIR)/nacl_test_data/glibc/>(nexe_target)_glibc_arm.nexe',
'nmf_newlib%': '<(PRODUCT_DIR)/nacl_test_data/newlib/>(nexe_target).nmf',
'include_dirs': [
'../../../..',
],
'link_flags': [
'-lppapi_cpp',
'-lppapi',
'-lpthread',
],
'sources': [
'simple.cc',
],
},
'actions': [
{
'action_name': 'Generate NEWLIB NMF',
# Unlike glibc, nexes are not actually inputs - only the names matter.
# We don't have the nexes as inputs because the ARM nexe may not
# exist. However, VS 2010 seems to blackhole this entire target if
# there are no inputs to this action. To work around this we add a
# bogus input.
'inputs': ['nacl_test_data.gyp'],
'outputs': ['>(nmf_newlib)'],
'action': [
'python',
'<(DEPTH)/native_client_sdk/src/tools/create_nmf.py',
'>(out_newlib64)', '>(out_newlib32)', '>(out_newlib_arm)',
'--output=>(nmf_newlib)',
'--toolchain=newlib',
],
},
],
'conditions': [
['target_arch!="arm" and disable_glibc==0', {
'variables': {
'build_glibc': 1,
# NOTE: Use /lib, not /lib64 here; it is a symbolic link which
# doesn't work on Windows.
'libdir_glibc64': '>(nacl_glibc_tc_root)/x86_64-nacl/lib',
'libdir_glibc32': '>(nacl_glibc_tc_root)/x86_64-nacl/lib32',
'nacl_objdump': '>(nacl_glibc_tc_root)/bin/x86_64-nacl-objdump',
'nmf_glibc%': '<(PRODUCT_DIR)/nacl_test_data/glibc/>(nexe_target).nmf',
},
'actions': [
{
'action_name': 'Generate GLIBC NMF and copy libs',
'inputs': ['>(out_glibc64)', '>(out_glibc32)'],
# NOTE: There is no explicit dependency for the lib32
# and lib64 directories created in the PRODUCT_DIR.
# They are created as a side-effect of NMF creation.
'outputs': ['>(nmf_glibc)'],
'action': [
'python',
'<(DEPTH)/native_client_sdk/src/tools/create_nmf.py',
'>@(_inputs)',
'--objdump=>(nacl_objdump)',
'--library-path=>(libdir_glibc64)',
'--library-path=>(libdir_glibc32)',
'--output=>(nmf_glibc)',
'--stage-dependencies=<(PRODUCT_DIR)/nacl_test_data/glibc',
'--toolchain=glibc',
],
},
],
}],
],
},
],
}
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/module.h"
class SimpleInstance : public pp::Instance {
public:
explicit SimpleInstance(PP_Instance instance) : pp::Instance(instance) {
}
};
class SimpleModule : public pp::Module {
public:
virtual pp::Instance* CreateInstance(PP_Instance instance) {
return new SimpleInstance(instance);
}
};
namespace pp {
Module* CreateModule() {
return new SimpleModule();
}
} // namespace pp
bradnelson@chromium.org
ncbray@chromium.org
mseaborn@chromium.org
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/path_service.h"
#include "base/timer.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/dom_operation_notification_details.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/plugin_service.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "net/base/net_util.h"
#include "webkit/plugins/webplugininfo.h"
namespace {
// Base class for handling a stream of automation messages produced by a
// JavascriptTestObserver.
class TestMessageHandler {
public:
enum MessageResponse {
// Reset the timeout and keep running.
CONTINUE,
// Stop runnning.
DONE
};
TestMessageHandler() : ok_(true) {}
virtual ~TestMessageHandler() {};
virtual MessageResponse HandleMessage(const std::string& json) = 0;
void SetError(const std::string& message) {
ok_ = false;
error_message_ = message;
}
bool ok() const {
return ok_;
}
const std::string& error_message() const {
return error_message_;
}
private:
bool ok_;
std::string error_message_;
};
// A helper base class that decodes structured automation messages of the form:
// {"type": type_name, ...}
class StructuredMessageHandler : public TestMessageHandler {
public:
virtual MessageResponse HandleMessage(const std::string& json) OVERRIDE {
scoped_ptr<Value> value;
base::JSONReader reader(base::JSON_ALLOW_TRAILING_COMMAS);
// Automation messages are stringified before they are sent because the
// automation channel cannot handle arbitrary objects. This means we
// need to decode the json twice to get the original message.
value.reset(reader.ReadToValue(json));
if (!value.get())
return InternalError("Could parse automation JSON: " + json +
" because " + reader.GetErrorMessage());
std::string temp;
if (!value->GetAsString(&temp))
return InternalError("Message was not a string: " + json);
value.reset(reader.ReadToValue(temp));
if (!value.get())
return InternalError("Could not parse message JSON: " + temp +
" because " + reader.GetErrorMessage());
DictionaryValue* msg;
if (!value->GetAsDictionary(&msg))
return InternalError("Message was not an object: " + temp);
std::string type;
if (!msg->GetString("type", &type))
return MissingField("unknown", "type");
return HandleStructuredMessage(type, msg);
}
virtual MessageResponse HandleStructuredMessage(const std::string& type,
DictionaryValue* msg) = 0;
MessageResponse MissingField(const std::string& type,
const std::string& field) WARN_UNUSED_RESULT {
return InternalError(type + " message did not have field: " + field);
}
MessageResponse InternalError(const std::string& reason) WARN_UNUSED_RESULT {
SetError(reason);
return DONE;
}
};
// A simple structured message handler for tests that load nexes.
class LoadTestMessageHandler : public StructuredMessageHandler {
public:
LoadTestMessageHandler() : test_passed_(false) {}
void Log(const std::string& type, const std::string& message) {
// TODO(ncbray) better logging.
LOG(INFO) << type << " " << message;
}
virtual MessageResponse HandleStructuredMessage(
const std::string& type,
DictionaryValue* msg) OVERRIDE {
if (type == "Log") {
std::string message;
if (!msg->GetString("message", &message))
return MissingField(type, "message");
Log("LOG", message);
return CONTINUE;
} else if (type == "Shutdown") {
std::string message;
if (!msg->GetString("message", &message))
return MissingField(type, "message");
if (!msg->GetBoolean("passed", &test_passed_))
return MissingField(type, "passed");
Log("SHUTDOWN", message);
return DONE;
} else {
return InternalError("Unknown message type: " + type);
}
}
bool test_passed() const {
return test_passed_;
}
private:
bool test_passed_;
DISALLOW_COPY_AND_ASSIGN(LoadTestMessageHandler);
};
// This class captures a stream of automation messages coming from a Javascript
// test and dispatches them to a message handler.
// TODO(ncbray) factor out and share with PPAPI tests.
class JavascriptTestObserver : public content::NotificationObserver {
public:
JavascriptTestObserver(
content::RenderViewHost* render_view_host,
TestMessageHandler* handler)
: handler_(handler),
running_(false) {
registrar_.Add(this, content::NOTIFICATION_DOM_OPERATION_RESPONSE,
content::Source<content::RenderViewHost>(render_view_host));
}
// Pump the message loop until the message handler indicates the Javascript
// test is done running. Return true if the test jig functioned correctly and
// nothing timed out.
bool Run() {
running_ = true;
content::RunMessageLoop();
running_ = false;
return handler_->ok();
}
virtual void Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) OVERRIDE {
DCHECK(type == content::NOTIFICATION_DOM_OPERATION_RESPONSE);
content::Details<content::DomOperationNotificationDetails> dom_op_details(
details);
// We might receive responses for other script execution, but we only
// care about the test finished message.
TestMessageHandler::MessageResponse response =
handler_->HandleMessage(dom_op_details->json);
if (response == TestMessageHandler::DONE) {
EndTest();
} else {
Continue();
}
}
private:
void Continue() {
}
void EndTest() {
if (running_) {
MessageLoopForUI::current()->Quit();
}
}
TestMessageHandler* handler_;
bool running_;
content::NotificationRegistrar registrar_;
DISALLOW_COPY_AND_ASSIGN(JavascriptTestObserver);
};
// NaCl browser tests serve files out of the build directory because nexes and
// pexes are artifacts of the build. To keep things tidy, all test data is kept
// in a subdirectory. Several variants of a test may be run, for example when
// linked against newlib and when linked against glibc. These variants are kept
// in different subdirectories. For example, the build directory will look
// something like this on Linux:
// out/
// Release/
// nacl_test_data/
// newlib/
// glibc/
bool GetNaClVariantRoot(const FilePath::StringType& variant,
FilePath* document_root) {
if (!ui_test_utils::GetRelativeBuildDirectory(document_root))
return false;
*document_root = document_root->Append(FILE_PATH_LITERAL("nacl_test_data"));
*document_root = document_root->Append(variant);
return true;
}
class NaClBrowserTestBase : public InProcessBrowserTest {
public:
NaClBrowserTestBase() {}
virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
command_line->AppendSwitch(switches::kNoFirstRun);
command_line->AppendSwitch(switches::kEnableNaCl);
}
virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
// Sanity check.
FilePath plugin_lib;
ASSERT_TRUE(PathService::Get(chrome::FILE_NACL_PLUGIN, &plugin_lib));
ASSERT_TRUE(file_util::PathExists(plugin_lib)) << plugin_lib.value();
ASSERT_TRUE(StartTestServer()) << "Cannot start test server.";
}
virtual FilePath::StringType Variant() = 0;
GURL TestURL(const FilePath::StringType& test_file) {
FilePath real_path = test_server_->document_root().Append(test_file);
EXPECT_TRUE(file_util::PathExists(real_path)) << real_path.value();
FilePath url_path = FilePath(FILE_PATH_LITERAL("files"));
url_path = url_path.Append(test_file);
return test_server_->GetURL(url_path.MaybeAsASCII());
}
bool RunJavascriptTest(const GURL& url, TestMessageHandler* handler) {
JavascriptTestObserver observer(
chrome::GetActiveWebContents(browser())->GetRenderViewHost(),
handler);
ui_test_utils::NavigateToURL(browser(), url);
return observer.Run();
}
void RunLoadTest(const FilePath::StringType& test_file) {
LoadTestMessageHandler handler;
bool ok = RunJavascriptTest(TestURL(test_file), &handler);
ASSERT_TRUE(ok) << handler.error_message();
ASSERT_TRUE(handler.test_passed()) << "Test failed.";
}
private:
bool StartTestServer() {
// Launch the web server.
FilePath document_root;
if (!GetNaClVariantRoot(Variant(), &document_root))
return false;
test_server_.reset(new net::TestServer(net::TestServer::TYPE_HTTP,
net::TestServer::kLocalhost,
document_root));
return test_server_->Start();
}
scoped_ptr<net::TestServer> test_server_;
};
class NaClBrowserTestNewlib : public NaClBrowserTestBase {
virtual FilePath::StringType Variant() {
return FILE_PATH_LITERAL("newlib");
}
};
class NaClBrowserTestGLibc : public NaClBrowserTestBase {
virtual FilePath::StringType Variant() {
return FILE_PATH_LITERAL("glibc");
}
};
// Disable tests under Linux ASAN.
// Linux ASAN doesn't work with NaCl. See: http://crbug.com/104832.
// TODO(ncbray) enable after http://codereview.chromium.org/10830009/ lands.
#if !(defined(ADDRESS_SANITIZER) && defined(OS_LINUX))
IN_PROC_BROWSER_TEST_F(NaClBrowserTestNewlib, SimpleLoadTest) {
RunLoadTest(FILE_PATH_LITERAL("nacl_load_test.html"));
}
IN_PROC_BROWSER_TEST_F(NaClBrowserTestGLibc, SimpleLoadTest) {
RunLoadTest(FILE_PATH_LITERAL("nacl_load_test.html"));
}
#endif // !(defined(ADDRESS_SANITIZER) && defined(OS_LINUX))
} // namespace anonymous
......@@ -166,13 +166,13 @@ void PPAPITestBase::RunTestAndReload(const std::string& test_case) {
void PPAPITestBase::RunTestViaHTTP(const std::string& test_case) {
FilePath document_root;
ASSERT_TRUE(GetHTTPDocumentRoot(&document_root));
ASSERT_TRUE(ui_test_utils::GetRelativeBuildDirectory(&document_root));
RunHTTPTestServer(document_root, test_case, "");
}
void PPAPITestBase::RunTestWithSSLServer(const std::string& test_case) {
FilePath document_root;
ASSERT_TRUE(GetHTTPDocumentRoot(&document_root));
ASSERT_TRUE(ui_test_utils::GetRelativeBuildDirectory(&document_root));
net::TestServer test_server(net::BaseTestServer::HTTPSOptions(),
document_root);
ASSERT_TRUE(test_server.Start());
......@@ -189,7 +189,7 @@ void PPAPITestBase::RunTestWithWebSocketServer(const std::string& test_case) {
int port = server.UseRandomPort();
ASSERT_TRUE(server.Start(websocket_root_dir));
FilePath http_document_root;
ASSERT_TRUE(GetHTTPDocumentRoot(&http_document_root));
ASSERT_TRUE(ui_test_utils::GetRelativeBuildDirectory(&http_document_root));
RunHTTPTestServer(http_document_root, test_case,
StringPrintf("websocket_port=%d", port));
}
......@@ -256,45 +256,6 @@ void PPAPITestBase::RunHTTPTestServer(
RunTestURL(url);
}
bool PPAPITestBase::GetHTTPDocumentRoot(FilePath* document_root) {
// For HTTP tests, we use the output DIR to grab the generated files such
// as the NEXEs.
FilePath exe_dir = CommandLine::ForCurrentProcess()->GetProgram().DirName();
FilePath src_dir;
if (!PathService::Get(base::DIR_SOURCE_ROOT, &src_dir))
return false;
// TestServer expects a path relative to source. So we must first
// generate absolute paths to SRC and EXE and from there generate
// a relative path.
if (!exe_dir.IsAbsolute()) file_util::AbsolutePath(&exe_dir);
if (!src_dir.IsAbsolute()) file_util::AbsolutePath(&src_dir);
if (!exe_dir.IsAbsolute())
return false;
if (!src_dir.IsAbsolute())
return false;
size_t match, exe_size, src_size;
std::vector<FilePath::StringType> src_parts, exe_parts;
// Determine point at which src and exe diverge, and create a relative path.
exe_dir.GetComponents(&exe_parts);
src_dir.GetComponents(&src_parts);
exe_size = exe_parts.size();
src_size = src_parts.size();
for (match = 0; match < exe_size && match < src_size; ++match) {
if (exe_parts[match] != src_parts[match])
break;
}
for (size_t tmp_itr = match; tmp_itr < src_size; ++tmp_itr) {
*document_root = document_root->Append(FILE_PATH_LITERAL(".."));
}
for (; match < exe_size; ++match) {
*document_root = document_root->Append(exe_parts[match]);
}
return true;
}
PPAPITest::PPAPITest() {
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment