Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(51)

Unified Diff: extensions/renderer/api_bindings_system_unittest.cc

Issue 2438623002: [Extensions Bindings] Add APIBindingsSystem (Closed)
Patch Set: optional null type definitions Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « extensions/renderer/api_bindings_system.cc ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: extensions/renderer/api_bindings_system_unittest.cc
diff --git a/extensions/renderer/api_bindings_system_unittest.cc b/extensions/renderer/api_bindings_system_unittest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f16ba1c6c89c0cb1db683d30c76153a581a9c526
--- /dev/null
+++ b/extensions/renderer/api_bindings_system_unittest.cc
@@ -0,0 +1,417 @@
+// Copyright 2016 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 "extensions/renderer/api_bindings_system.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "extensions/common/extension_api.h"
+#include "extensions/renderer/api_binding.h"
+#include "extensions/renderer/api_binding_test_util.h"
+#include "gin/converter.h"
+#include "gin/public/context_holder.h"
+#include "gin/public/isolate_holder.h"
+#include "gin/test/v8_test.h"
+#include "gin/try_catch.h"
+
+namespace extensions {
+
+namespace {
+
+// Fake API for testing.
+const char kAlphaAPIName[] = "alpha";
+const char kAlphaAPISpec[] =
+ "{"
+ " 'types': [{"
+ " 'id': 'objRef',"
+ " 'type': 'object',"
+ " 'properties': {"
+ " 'prop1': {'type': 'string'},"
+ " 'prop2': {'type': 'integer', 'optional': true}"
+ " }"
+ " }],"
+ " 'functions': [{"
+ " 'name': 'functionWithCallback',"
+ " 'parameters': [{"
+ " 'name': 'str',"
+ " 'type': 'string'"
+ " }, {"
+ " 'name': 'callback',"
+ " 'type': 'function'"
+ " }]"
+ " }, {"
+ " 'name': 'functionWithRefAndCallback',"
+ " 'parameters': [{"
+ " 'name': 'ref',"
+ " '$ref': 'objRef'"
+ " }, {"
+ " 'name': 'callback',"
+ " 'type': 'function'"
+ " }]"
+ " }]"
+ "}";
+
+// Another fake API for testing.
+const char kBetaAPIName[] = "beta";
+const char kBetaAPISpec[] =
+ "{"
+ " 'functions': [{"
+ " 'name': 'simpleFunc',"
+ " 'parameters': [{'name': 'int', 'type': 'integer'}]"
+ " }]"
+ "}";
+
+} // namespace
+
+// The base class to test the APIBindingsSystem. This allows subclasses to
+// retrieve API schemas differently.
+class APIBindingsSystemTestBase : public gin::V8Test {
+ public:
+ // Returns the DictionaryValue representing the schema with the given API
+ // name.
+ virtual const base::DictionaryValue& GetAPISchema(
+ const std::string& api_name) = 0;
+
+ // Stores the request in |last_request_|.
+ void OnAPIRequest(std::unique_ptr<APIBindingsSystem::Request> request) {
+ ASSERT_FALSE(last_request_);
+ last_request_ = std::move(request);
+ }
+
+ protected:
+ APIBindingsSystemTestBase() {}
+ void SetUp() override;
+
+ void TearDown() override {
+ bindings_system_.reset();
+ holder_.reset();
+ gin::V8Test::TearDown();
+ }
+
+ // Checks that |last_request_| exists and was provided with the
+ // |expected_name| and |expected_arguments|.
+ void ValidateLastRequest(const std::string& expected_name,
+ const std::string& expected_arguments);
+
+ const APIBindingsSystem::Request* last_request() const {
+ return last_request_.get();
+ }
+ void reset_last_request() { last_request_.reset(); }
+ APIBindingsSystem* bindings_system() { return bindings_system_.get(); }
+
+ private:
+ std::unique_ptr<gin::ContextHolder> holder_;
+
+ // The APIBindingsSystem associated with the test. Safe to use across multiple
+ // contexts.
+ std::unique_ptr<APIBindingsSystem> bindings_system_;
+
+ // The last request to be received from the APIBindingsSystem, or null if
+ // there is none.
+ std::unique_ptr<APIBindingsSystem::Request> last_request_;
+
+ DISALLOW_COPY_AND_ASSIGN(APIBindingsSystemTestBase);
+};
+
+void APIBindingsSystemTestBase::SetUp() {
+ gin::V8Test::SetUp();
+ v8::HandleScope handle_scope(instance_->isolate());
+ holder_ = base::MakeUnique<gin::ContextHolder>(instance_->isolate());
+ holder_->SetContext(
+ v8::Local<v8::Context>::New(instance_->isolate(), context_));
+
+ bindings_system_ = base::MakeUnique<APIBindingsSystem>(
+ base::Bind(&RunFunctionOnGlobalAndIgnoreResult),
+ base::Bind(&APIBindingsSystemTestBase::GetAPISchema,
+ base::Unretained(this)),
+ base::Bind(&APIBindingsSystemTestBase::OnAPIRequest,
+ base::Unretained(this)));
+}
+
+void APIBindingsSystemTestBase::ValidateLastRequest(
+ const std::string& expected_name,
+ const std::string& expected_arguments) {
+ ASSERT_TRUE(last_request());
+ // Note that even if no arguments are provided by the API call, we should have
+ // an empty list.
+ ASSERT_TRUE(last_request()->arguments);
+ EXPECT_EQ(expected_name, last_request()->method_name);
+ EXPECT_EQ(ReplaceSingleQuotes(expected_arguments),
+ ValueToString(*last_request()->arguments));
+}
+
+// An implementation that works with fake/supplied APIs, for easy testability.
+class APIBindingsSystemTest : public APIBindingsSystemTestBase {
+ protected:
+ APIBindingsSystemTest() {}
+
+ // Calls a function constructed from |script_source| on the given |object|.
+ void CallFunctionOnObject(v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ const std::string& script_source);
+
+ private:
+ const base::DictionaryValue& GetAPISchema(
+ const std::string& api_name) override {
+ EXPECT_TRUE(base::ContainsKey(api_schemas_, api_name));
+ return *api_schemas_[api_name];
+ }
+ void SetUp() override;
+
+ // The API schemas for the fake APIs.
+ std::map<std::string, std::unique_ptr<base::DictionaryValue>> api_schemas_;
+
+ DISALLOW_COPY_AND_ASSIGN(APIBindingsSystemTest);
+};
+
+void APIBindingsSystemTest::CallFunctionOnObject(
+ v8::Local<v8::Context> context,
+ v8::Local<v8::Object> object,
+ const std::string& script_source) {
+ std::string wrapped_script_source =
+ base::StringPrintf("(function(obj) { %s })", script_source.c_str());
+
+ v8::Local<v8::Function> func =
+ FunctionFromString(context, wrapped_script_source);
+ ASSERT_FALSE(func.IsEmpty());
+
+ v8::Local<v8::Value> argv[] = {object};
+ RunFunction(func, context, 1, argv);
+}
+
+void APIBindingsSystemTest::SetUp() {
+ APIBindingsSystemTestBase::SetUp();
lazyboy 2016/10/31 20:08:31 Better if this goes at the end of the func, after
Devlin 2016/10/31 22:03:36 Done.
+
+ // Create the fake API schemas.
+ {
+ struct APIData {
+ const char* name;
+ const char* spec;
+ } api_data[] = {
+ {kAlphaAPIName, kAlphaAPISpec}, {kBetaAPIName, kBetaAPISpec},
+ };
+ for (const auto& api : api_data) {
+ std::unique_ptr<base::DictionaryValue> api_schema =
+ DictionaryValueFromString(api.spec);
+ ASSERT_TRUE(api_schema);
+ api_schemas_[api.name] = std::move(api_schema);
+ }
+ }
+}
+
+// Tests API object initialization, calling a method on the supplied APIs, and
+// triggering the callback for the request.
+TEST_F(APIBindingsSystemTest, TestInitializationAndCallbacks) {
+ v8::Isolate* isolate = instance_->isolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Context> context =
+ v8::Local<v8::Context>::New(isolate, context_);
+
+ v8::Local<v8::Object> alpha_api =
+ bindings_system()->CreateAPIInstance(kAlphaAPIName, context, isolate);
+ ASSERT_FALSE(alpha_api.IsEmpty());
+ v8::Local<v8::Object> beta_api =
+ bindings_system()->CreateAPIInstance(kBetaAPIName, context, isolate);
+ ASSERT_FALSE(beta_api.IsEmpty());
+
+ {
+ // Test a simple call -> response.
+ const char kTestCall[] =
+ "obj.functionWithCallback('foo', function() {\n"
+ " this.callbackArguments = Array.from(arguments);\n"
+ "});";
+ CallFunctionOnObject(context, alpha_api, kTestCall);
+
+ ValidateLastRequest("alpha.functionWithCallback", "['foo']");
+
+ const char kResponseArgsJson[] = "['response',1,{'key':42}]";
+ std::unique_ptr<base::ListValue> expected_args =
+ ListValueFromString(kResponseArgsJson);
+ bindings_system()->CompleteRequest(last_request()->request_id,
+ *expected_args);
+
+ std::unique_ptr<base::Value> result = GetBaseValuePropertyFromObject(
+ context->Global(), context, "callbackArguments");
+ ASSERT_TRUE(result);
+ EXPECT_EQ(ReplaceSingleQuotes(kResponseArgsJson), ValueToString(*result));
+ reset_last_request();
+ }
+
+ {
+ // Test a call with references -> response.
+ const char kTestCall[] =
+ "obj.functionWithRefAndCallback({prop1: 'alpha', prop2: 42},\n"
+ " function() {\n"
+ " this.callbackArguments = Array.from(arguments);\n"
+ "});";
+
+ CallFunctionOnObject(context, alpha_api, kTestCall);
+
+ ValidateLastRequest("alpha.functionWithRefAndCallback",
+ "[{'prop1':'alpha','prop2':42}]");
+
+ bindings_system()->CompleteRequest(last_request()->request_id,
+ base::ListValue());
+
+ std::unique_ptr<base::Value> result = GetBaseValuePropertyFromObject(
+ context->Global(), context, "callbackArguments");
+ ASSERT_TRUE(result);
+ EXPECT_EQ("[]", ValueToString(*result));
+ reset_last_request();
+ }
+
+ {
+ // Test a call -> response on the second API.
+ const char kTestCall[] = "obj.simpleFunc(2)";
+ CallFunctionOnObject(context, beta_api, kTestCall);
+ ValidateLastRequest("beta.simpleFunc", "[2]");
+ EXPECT_TRUE(last_request()->request_id.empty());
+ reset_last_request();
+ }
+}
+
+// An implementation using real API schemas.
+class APIBindingsSystemTestWithRealAPI : public APIBindingsSystemTestBase {
+ protected:
+ APIBindingsSystemTestWithRealAPI() {}
+
+ // Executes the given |script_source| in |context|, expecting no exceptions.
+ void ExecuteScript(v8::Local<v8::Context> context,
+ const std::string& script_source);
+
+ // Executes the given |script_source| in |context| and compares a caught
+ // error to |expected_error|.
+ void ExecuteScriptAndExpectError(v8::Local<v8::Context> context,
+ const std::string& script_source,
+ const std::string& expected_error);
+
+ private:
+ const base::DictionaryValue& GetAPISchema(
+ const std::string& api_name) override {
+ const base::DictionaryValue* schema =
+ ExtensionAPI::GetSharedInstance()->GetSchema(api_name);
+ EXPECT_TRUE(schema);
+ return *schema;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(APIBindingsSystemTestWithRealAPI);
+};
+
+void APIBindingsSystemTestWithRealAPI::ExecuteScript(
+ v8::Local<v8::Context> context,
+ const std::string& script_source) {
+ v8::Isolate* isolate = instance_->isolate();
+
+ v8::TryCatch try_catch(isolate);
+ // V8ValueFromScriptSource runs the source and returns the result; here, we
+ // only care about running the source.
+ V8ValueFromScriptSource(context, script_source);
+ EXPECT_FALSE(try_catch.HasCaught())
+ << gin::V8ToString(try_catch.Message()->Get());
+}
+
+void APIBindingsSystemTestWithRealAPI::ExecuteScriptAndExpectError(
+ v8::Local<v8::Context> context,
+ const std::string& script_source,
+ const std::string& expected_error) {
+ v8::Isolate* isolate = instance_->isolate();
+
+ v8::TryCatch try_catch(isolate);
+ V8ValueFromScriptSource(context, script_source);
+ ASSERT_TRUE(try_catch.HasCaught()) << script_source;
+ EXPECT_EQ(expected_error, gin::V8ToString(try_catch.Message()->Get()));
+}
+
+// The following test demonstrates how APIBindingsSystem can be used with "real"
+// Extension APIs; that is, using the raw Extension API schemas, rather than a
+// substituted test schema. This is useful to both show how the system is
+// intended to be used in the future as well as to make sure that it works with
+// actual APIs.
+TEST_F(APIBindingsSystemTestWithRealAPI, RealAPIs) {
+ v8::Isolate* isolate = instance_->isolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Context> context =
+ v8::Local<v8::Context>::New(isolate, context_);
+
+ v8::Local<v8::Object> chrome = v8::Object::New(isolate);
+ {
+ v8::Maybe<bool> res = context->Global()->Set(
+ context, gin::StringToV8(isolate, "chrome"), chrome);
+ ASSERT_TRUE(res.IsJust());
+ ASSERT_TRUE(res.FromJust());
+ }
+
+ auto add_api_to_chrome = [this, &chrome,
+ &context](const std::string& api_name) {
+ v8::Local<v8::Object> api = bindings_system()->CreateAPIInstance(
+ api_name, context, context->GetIsolate());
+ ASSERT_FALSE(api.IsEmpty()) << api_name;
+ v8::Maybe<bool> res = chrome->Set(
+ context, gin::StringToV8(context->GetIsolate(), api_name), api);
+ ASSERT_TRUE(res.IsJust());
+ ASSERT_TRUE(res.FromJust());
+ };
+
+ // Pick two relatively small APIs that don't have any custom bindings (which
+ // aren't supported yet).
+ add_api_to_chrome("idle");
+ add_api_to_chrome("power");
+
+ // Test passing methods.
+ {
+ const char kTestCall[] = "chrome.power.requestKeepAwake('display');";
+ ExecuteScript(context, kTestCall);
+ ValidateLastRequest("power.requestKeepAwake", "['display']");
+ EXPECT_TRUE(last_request()->request_id.empty());
+ reset_last_request();
+ }
+
+ {
+ const char kTestCall[] = "chrome.power.releaseKeepAwake()";
+ ExecuteScript(context, kTestCall);
+ ValidateLastRequest("power.releaseKeepAwake", "[]");
+ EXPECT_TRUE(last_request()->request_id.empty());
+ reset_last_request();
+ }
+
+ {
+ const char kTestCall[] = "chrome.idle.queryState(30, function() {})";
+ ExecuteScript(context, kTestCall);
+ ValidateLastRequest("idle.queryState", "[30]");
+ EXPECT_FALSE(last_request()->request_id.empty());
+ reset_last_request();
+ }
+
+ {
+ const char kTestCall[] = "chrome.idle.setDetectionInterval(30);";
+ ExecuteScript(context, kTestCall);
+ ValidateLastRequest("idle.setDetectionInterval", "[30]");
+ EXPECT_TRUE(last_request()->request_id.empty());
+ reset_last_request();
+ }
+
+ // Check catching errors.
+ const char kError[] = "Uncaught TypeError: Invalid invocation";
+ {
+ // "disp" is not an allowed enum value.
+ const char kTestCall[] = "chrome.power.requestKeepAwake('disp');";
+ ExecuteScriptAndExpectError(context, kTestCall, kError);
+ EXPECT_FALSE(last_request());
+ reset_last_request(); // Just to not pollute future results.
+ }
+
+ {
+ // The queryState() param has a minimum of 15.
+ const char kTestCall[] = "chrome.idle.queryState(10, function() {});";
+ ExecuteScriptAndExpectError(context, kTestCall, kError);
+ EXPECT_FALSE(last_request());
+ reset_last_request(); // Just to not pollute future results.
+ }
+}
+
+} // namespace extensions
« no previous file with comments | « extensions/renderer/api_bindings_system.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698