Commit bb77f7a3 authored by Adam Rice's avatar Adam Rice Committed by Commit Bot

Add ScriptPromiseTester

Simplify writing tests that verify promise behaviour by adding a
ScriptPromiseTester helper class.

Use the new class in transform_stream_test.cc and cache_test.cc to prove
the concept.

Change-Id: I52f525506c34d0f38f9627b964bfbfe823ee9e11
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1789206
Commit-Queue: Hiroki Nakagawa <nhiroki@chromium.org>
Reviewed-by: default avatarHiroki Nakagawa <nhiroki@chromium.org>
Reviewed-by: default avatarYutaka Hirano <yhirano@chromium.org>
Reviewed-by: default avatarYuki Shiino <yukishiino@chromium.org>
Reviewed-by: default avatarKouhei Ueno <kouhei@chromium.org>
Cr-Commit-Position: refs/heads/master@{#699618}
parent ba18dba6
...@@ -217,6 +217,8 @@ bindings_unittest_files = ...@@ -217,6 +217,8 @@ bindings_unittest_files =
"core/v8/serialization/v8_script_value_serializer_test.cc", "core/v8/serialization/v8_script_value_serializer_test.cc",
"core/v8/v8_extras_test_utils.cc", "core/v8/v8_extras_test_utils.cc",
"core/v8/v8_extras_test_utils.h", "core/v8/v8_extras_test_utils.h",
"core/v8/script_promise_tester.cc",
"core/v8/script_promise_tester.h",
], ],
"abspath") "abspath")
bindings_unittest_files += bindings_modules_v8_unittest_files bindings_unittest_files += bindings_modules_v8_unittest_files
// Copyright 2019 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 "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
#include <utility>
#include "third_party/blink/renderer/bindings/core/v8/script_function.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/heap/visitor.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "v8/include/v8.h"
namespace blink {
class ScriptPromiseTester::ThenFunction : public ScriptFunction {
public:
static v8::Local<v8::Function> CreateFunction(
ScriptState* script_state,
base::WeakPtr<ScriptPromiseTester> owner,
ScriptPromiseTester::State target_state) {
ThenFunction* self = MakeGarbageCollected<ThenFunction>(
script_state, std::move(owner), target_state);
return self->BindToV8Function();
}
ThenFunction(ScriptState* script_state,
base::WeakPtr<ScriptPromiseTester> owner,
ScriptPromiseTester::State target_state)
: ScriptFunction(script_state),
owner_(std::move(owner)),
target_state_(target_state) {}
ScriptValue Call(ScriptValue value) override {
if (!owner_)
return value;
DCHECK_EQ(owner_->state_, State::kNotSettled);
owner_->state_ = target_state_;
DCHECK(owner_->value_.IsEmpty());
owner_->value_ = value;
return value;
}
private:
base::WeakPtr<ScriptPromiseTester> owner_;
State target_state_;
};
ScriptPromiseTester::ScriptPromiseTester(ScriptState* script_state,
ScriptPromise script_promise)
: script_state_(script_state) {
DCHECK(script_state);
script_promise.Then(
ThenFunction::CreateFunction(script_state, weak_factory_.GetWeakPtr(),
State::kFulfilled),
ThenFunction::CreateFunction(script_state, weak_factory_.GetWeakPtr(),
State::kRejected));
}
void ScriptPromiseTester::WaitUntilSettled() {
auto* isolate = script_state_->GetIsolate();
while (state_ == State::kNotSettled) {
v8::MicrotasksScope::PerformCheckpoint(isolate);
test::RunPendingTasks();
}
}
ScriptValue ScriptPromiseTester::Value() const {
return value_;
}
void ScriptPromiseTester::Trace(Visitor* visitor) {
visitor->Trace(script_state_);
visitor->Trace(value_);
}
} // namespace blink
// Copyright 2019 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_SCRIPT_PROMISE_TESTER_H_
#define THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_SCRIPT_PROMISE_TESTER_H_
#include "base/memory/weak_ptr.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
namespace blink {
class ScriptState;
class ScriptPromise;
class Visitor;
// Utility for writing unit tests involving promises.
// Typical usage:
// ScriptPromiseTester tester(script_state, script_promise);
// tester.WaitUntilSettled(); // Runs a nested event loop.
// EXPECT_TRUE(tester.IsFulfilled());
// EXPECT_TRUE(tester.Value().IsUndefined());
class ScriptPromiseTester final {
DISALLOW_NEW();
public:
ScriptPromiseTester(ScriptState*, ScriptPromise);
// Run microtasks and tasks until the promise is either fulfilled or rejected.
// If the promise never settles this will busy loop until the test times out.
void WaitUntilSettled();
// Did the promise fulfill?
bool IsFulfilled() const { return state_ == State::kFulfilled; }
// Did the promise reject?
bool IsRejected() const { return state_ == State::kRejected; }
// The value the promise fulfilled or rejected with.
ScriptValue Value() const;
void Trace(Visitor*);
private:
class ThenFunction;
enum class State { kNotSettled, kFulfilled, kRejected };
Member<ScriptState> script_state_;
State state_ = State::kNotSettled;
ScriptValue value_;
base::WeakPtrFactory<ScriptPromiseTester> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(ScriptPromiseTester);
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_SCRIPT_PROMISE_TESTER_H_
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/bindings/core/v8/script_function.h" #include "third_party/blink/renderer/bindings/core/v8/script_function.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h" #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h" #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
...@@ -176,116 +177,45 @@ TEST_P(TransformStreamTest, FlushIsCalled) { ...@@ -176,116 +177,45 @@ TEST_P(TransformStreamTest, FlushIsCalled) {
Mock::AllowLeak(mock); Mock::AllowLeak(mock);
} }
class ExpectNotReached : public ScriptFunction { bool IsIteratorForStringMatching(ScriptState* script_state,
public: ScriptValue value,
static v8::Local<v8::Function> Create(ScriptState* script_state) { const String& expected) {
auto* self = MakeGarbageCollected<ExpectNotReached>(script_state); if (!value.IsObject()) {
return self->BindToV8Function(); return false;
}
explicit ExpectNotReached(ScriptState* script_state)
: ScriptFunction(script_state) {}
private:
ScriptValue Call(ScriptValue) override {
ADD_FAILURE() << "ExpectNotReached was reached";
return ScriptValue();
}
};
// Fails the test if the iterator passed to the function does not have a value
// of exactly |expected|.
class ExpectChunkIsString : public ScriptFunction {
public:
static v8::Local<v8::Function> Create(ScriptState* script_state,
const String& expected,
bool* called) {
auto* self = MakeGarbageCollected<ExpectChunkIsString>(script_state,
expected, called);
return self->BindToV8Function();
}
ExpectChunkIsString(ScriptState* script_state,
const String& expected,
bool* called)
: ScriptFunction(script_state), expected_(expected), called_(called) {}
private:
ScriptValue Call(ScriptValue value) override {
*called_ = true;
if (!value.IsObject()) {
ADD_FAILURE() << "iterator must be an object";
return ScriptValue();
}
bool done = false;
auto* script_state = GetScriptState();
auto chunk = V8UnpackIteratorResult(
script_state,
value.V8Value()->ToObject(script_state->GetContext()).ToLocalChecked(),
&done);
EXPECT_FALSE(done);
EXPECT_FALSE(chunk.IsEmpty());
EXPECT_EQ(ToCoreStringWithUndefinedOrNullCheck(chunk.ToLocalChecked()),
expected_);
return ScriptValue();
}
String expected_;
bool* called_;
};
class ExpectTypeError : public ScriptFunction {
public:
static v8::Local<v8::Function> Create(ScriptState* script_state,
const String& message,
bool* called) {
auto* self =
MakeGarbageCollected<ExpectTypeError>(script_state, message, called);
return self->BindToV8Function();
}
ExpectTypeError(ScriptState* script_state,
const String& message,
bool* called)
: ScriptFunction(script_state), message_(message), called_(called) {}
private:
ScriptValue Call(ScriptValue value) override {
*called_ = true;
EXPECT_TRUE(IsTypeError(GetScriptState(), value, message_));
return ScriptValue();
} }
bool done = false;
auto chunk = V8UnpackIteratorResult(
script_state,
value.V8Value()->ToObject(script_state->GetContext()).ToLocalChecked(),
&done);
if (done || chunk.IsEmpty())
return false;
return ToCoreStringWithUndefinedOrNullCheck(chunk.ToLocalChecked()) ==
expected;
}
static bool IsTypeError(ScriptState* script_state, bool IsTypeError(ScriptState* script_state,
ScriptValue value, ScriptValue value,
const String& message) { const String& message) {
v8::Local<v8::Object> object; v8::Local<v8::Object> object;
if (!value.V8Value() if (!value.V8Value()->ToObject(script_state->GetContext()).ToLocal(&object)) {
->ToObject(script_state->GetContext()) return false;
.ToLocal(&object)) {
return false;
}
if (!object->IsNativeError())
return false;
return Has(script_state, object, "name", "TypeError") &&
Has(script_state, object, "message", message);
} }
if (!object->IsNativeError())
return false;
static bool Has(ScriptState* script_state, const auto& Has = [script_state, object](const String& key,
v8::Local<v8::Object> object, const String& value) -> bool {
const String& key,
const String& value) {
auto context = script_state->GetContext();
auto* isolate = script_state->GetIsolate();
v8::Local<v8::Value> actual; v8::Local<v8::Value> actual;
return object->Get(context, V8AtomicString(isolate, key)) return object
->Get(script_state->GetContext(),
V8AtomicString(script_state->GetIsolate(), key))
.ToLocal(&actual) && .ToLocal(&actual) &&
ToCoreStringWithUndefinedOrNullCheck(actual) == value; ToCoreStringWithUndefinedOrNullCheck(actual) == value;
} };
String message_; return Has("name", "TypeError") && Has("message", message);
bool* called_; }
};
TEST_P(TransformStreamTest, EnqueueFromTransform) { TEST_P(TransformStreamTest, EnqueueFromTransform) {
V8TestingScope scope; V8TestingScope scope;
...@@ -302,12 +232,10 @@ TEST_P(TransformStreamTest, EnqueueFromTransform) { ...@@ -302,12 +232,10 @@ TEST_P(TransformStreamTest, EnqueueFromTransform) {
ReadableStream* readable = Stream()->Readable(); ReadableStream* readable = Stream()->Readable();
auto* read_handle = auto* read_handle =
readable->GetReadHandle(script_state, ASSERT_NO_EXCEPTION); readable->GetReadHandle(script_state, ASSERT_NO_EXCEPTION);
bool chunk_seen = false; ScriptPromiseTester tester(script_state, read_handle->Read(script_state));
read_handle->Read(script_state) tester.WaitUntilSettled();
.Then(ExpectChunkIsString::Create(script_state, "a", &chunk_seen), EXPECT_TRUE(tester.IsFulfilled());
ExpectNotReached::Create(script_state)); EXPECT_TRUE(IsIteratorForStringMatching(script_state, tester.Value(), "a"));
v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
EXPECT_TRUE(chunk_seen);
} }
TEST_P(TransformStreamTest, EnqueueFromFlush) { TEST_P(TransformStreamTest, EnqueueFromFlush) {
...@@ -346,12 +274,10 @@ TEST_P(TransformStreamTest, EnqueueFromFlush) { ...@@ -346,12 +274,10 @@ TEST_P(TransformStreamTest, EnqueueFromFlush) {
ReadableStream* readable = Stream()->Readable(); ReadableStream* readable = Stream()->Readable();
auto* read_handle = auto* read_handle =
readable->GetReadHandle(script_state, ASSERT_NO_EXCEPTION); readable->GetReadHandle(script_state, ASSERT_NO_EXCEPTION);
bool chunkSeen = false; ScriptPromiseTester tester(script_state, read_handle->Read(script_state));
read_handle->Read(script_state) tester.WaitUntilSettled();
.Then(ExpectChunkIsString::Create(script_state, "a", &chunkSeen), EXPECT_TRUE(tester.IsFulfilled());
ExpectNotReached::Create(script_state)); EXPECT_TRUE(IsIteratorForStringMatching(script_state, tester.Value(), "a"));
v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
EXPECT_TRUE(chunkSeen);
} }
TEST_P(TransformStreamTest, ThrowFromTransform) { TEST_P(TransformStreamTest, ThrowFromTransform) {
...@@ -393,19 +319,16 @@ TEST_P(TransformStreamTest, ThrowFromTransform) { ...@@ -393,19 +319,16 @@ TEST_P(TransformStreamTest, ThrowFromTransform) {
ReadableStream* readable = Stream()->Readable(); ReadableStream* readable = Stream()->Readable();
auto* read_handle = auto* read_handle =
readable->GetReadHandle(script_state, ASSERT_NO_EXCEPTION); readable->GetReadHandle(script_state, ASSERT_NO_EXCEPTION);
bool readableTypeErrorThrown = false; ScriptPromiseTester read_tester(script_state,
bool writableTypeErrorThrown = false; read_handle->Read(script_state));
read_handle->Read(script_state) read_tester.WaitUntilSettled();
.Then(ExpectNotReached::Create(script_state), EXPECT_TRUE(read_tester.IsRejected());
ExpectTypeError::Create(script_state, kMessage, EXPECT_TRUE(IsTypeError(script_state, read_tester.Value(), kMessage));
&readableTypeErrorThrown)); ScriptPromiseTester write_tester(script_state,
ScriptPromise::Cast(script_state, promise) ScriptPromise::Cast(script_state, promise));
.Then(ExpectNotReached::Create(script_state), write_tester.WaitUntilSettled();
ExpectTypeError::Create(script_state, kMessage, EXPECT_TRUE(write_tester.IsRejected());
&writableTypeErrorThrown)); EXPECT_TRUE(IsTypeError(script_state, write_tester.Value(), kMessage));
v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
EXPECT_TRUE(readableTypeErrorThrown);
EXPECT_TRUE(writableTypeErrorThrown);
} }
TEST_P(TransformStreamTest, ThrowFromFlush) { TEST_P(TransformStreamTest, ThrowFromFlush) {
...@@ -445,19 +368,16 @@ TEST_P(TransformStreamTest, ThrowFromFlush) { ...@@ -445,19 +368,16 @@ TEST_P(TransformStreamTest, ThrowFromFlush) {
ReadableStream* readable = Stream()->Readable(); ReadableStream* readable = Stream()->Readable();
auto* read_handle = auto* read_handle =
readable->GetReadHandle(script_state, ASSERT_NO_EXCEPTION); readable->GetReadHandle(script_state, ASSERT_NO_EXCEPTION);
bool readableTypeErrorThrown = false; ScriptPromiseTester read_tester(script_state,
bool writableTypeErrorThrown = false; read_handle->Read(script_state));
read_handle->Read(script_state) read_tester.WaitUntilSettled();
.Then(ExpectNotReached::Create(script_state), EXPECT_TRUE(read_tester.IsRejected());
ExpectTypeError::Create(script_state, kMessage, EXPECT_TRUE(IsTypeError(script_state, read_tester.Value(), kMessage));
&readableTypeErrorThrown)); ScriptPromiseTester write_tester(script_state,
ScriptPromise::Cast(script_state, promise) ScriptPromise::Cast(script_state, promise));
.Then(ExpectNotReached::Create(script_state), write_tester.WaitUntilSettled();
ExpectTypeError::Create(script_state, kMessage, EXPECT_TRUE(write_tester.IsRejected());
&writableTypeErrorThrown)); EXPECT_TRUE(IsTypeError(script_state, write_tester.Value(), kMessage));
v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
EXPECT_TRUE(readableTypeErrorThrown);
EXPECT_TRUE(writableTypeErrorThrown);
} }
TEST_P(TransformStreamTest, CreateFromReadableWritablePair) { TEST_P(TransformStreamTest, CreateFromReadableWritablePair) {
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include "third_party/blink/renderer/bindings/core/v8/script_function.h" #include "third_party/blink/renderer/bindings/core/v8/script_function.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h" #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h" #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_request.h" #include "third_party/blink/renderer/bindings/core/v8/v8_request.h"
...@@ -303,12 +304,10 @@ class CacheStorageTest : public PageTestBase { ...@@ -303,12 +304,10 @@ class CacheStorageTest : public PageTestBase {
// Convenience methods for testing the returned promises. // Convenience methods for testing the returned promises.
ScriptValue GetRejectValue(ScriptPromise& promise) { ScriptValue GetRejectValue(ScriptPromise& promise) {
ScriptValue on_reject; ScriptPromiseTester tester(GetScriptState(), promise);
promise.Then(UnreachableFunction::Create(GetScriptState()), tester.WaitUntilSettled();
TestFunction::Create(GetScriptState(), &on_reject)); EXPECT_TRUE(tester.IsRejected());
v8::MicrotasksScope::PerformCheckpoint(GetIsolate()); return tester.Value();
test::RunPendingTasks();
return on_reject;
} }
std::string GetRejectString(ScriptPromise& promise) { std::string GetRejectString(ScriptPromise& promise) {
...@@ -320,12 +319,10 @@ class CacheStorageTest : public PageTestBase { ...@@ -320,12 +319,10 @@ class CacheStorageTest : public PageTestBase {
} }
ScriptValue GetResolveValue(ScriptPromise& promise) { ScriptValue GetResolveValue(ScriptPromise& promise) {
ScriptValue on_resolve; ScriptPromiseTester tester(GetScriptState(), promise);
promise.Then(TestFunction::Create(GetScriptState(), &on_resolve), tester.WaitUntilSettled();
UnreachableFunction::Create(GetScriptState())); EXPECT_TRUE(tester.IsFulfilled());
v8::MicrotasksScope::PerformCheckpoint(GetIsolate()); return tester.Value();
test::RunPendingTasks();
return on_resolve;
} }
std::string GetResolveString(ScriptPromise& promise) { std::string GetResolveString(ScriptPromise& promise) {
...@@ -337,48 +334,6 @@ class CacheStorageTest : public PageTestBase { ...@@ -337,48 +334,6 @@ class CacheStorageTest : public PageTestBase {
} }
private: private:
// A ScriptFunction that creates a test failure if it is ever called.
class UnreachableFunction : public ScriptFunction {
public:
static v8::Local<v8::Function> Create(ScriptState* script_state) {
UnreachableFunction* self =
MakeGarbageCollected<UnreachableFunction>(script_state);
return self->BindToV8Function();
}
UnreachableFunction(ScriptState* script_state)
: ScriptFunction(script_state) {}
ScriptValue Call(ScriptValue value) override {
ADD_FAILURE() << "Unexpected call to a null ScriptFunction.";
return value;
}
};
// A ScriptFunction that saves its parameter; used by tests to assert on
// correct values being passed.
class TestFunction : public ScriptFunction {
public:
static v8::Local<v8::Function> Create(ScriptState* script_state,
ScriptValue* out_value) {
TestFunction* self =
MakeGarbageCollected<TestFunction>(script_state, out_value);
return self->BindToV8Function();
}
TestFunction(ScriptState* script_state, ScriptValue* out_value)
: ScriptFunction(script_state), value_(out_value) {}
ScriptValue Call(ScriptValue value) override {
DCHECK(!value.IsEmpty());
*value_ = value;
return value;
}
private:
ScriptValue* value_;
};
std::unique_ptr<ErrorCacheForTests> cache_; std::unique_ptr<ErrorCacheForTests> cache_;
std::unique_ptr<mojo::AssociatedReceiver<mojom::blink::CacheStorageCache>> std::unique_ptr<mojo::AssociatedReceiver<mojom::blink::CacheStorageCache>>
receiver_; receiver_;
......
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