blob: 85c688e389bbc032b28c8f64c34591bd935e629f [file] [log] [blame]
// 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/core/streams/miscellaneous_operations.h"
#include <math.h>
#include <limits>
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_extras_test_utils.h"
#include "third_party/blink/renderer/core/streams/stream_algorithms.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/v8_binding.h"
#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace blink {
namespace {
// Tests in this file are named MiscellaneousOperations* so that it is easy to
// select them with gtest_filter.
v8::MaybeLocal<v8::Value> EmptyExtraArg() {
return v8::MaybeLocal<v8::Value>();
}
TEST(MiscellaneousOperationsTest, CreateAlgorithmNoMethod) {
V8TestingScope scope;
auto underlying_object = v8::Object::New(scope.GetIsolate());
auto* algo = CreateAlgorithmFromUnderlyingMethod(
scope.GetScriptState(), underlying_object, "pull",
"underlyingSource.pull", EmptyExtraArg(), ASSERT_NO_EXCEPTION);
ASSERT_TRUE(algo);
auto promise = algo->Run(scope.GetScriptState(), 0, nullptr);
ASSERT_FALSE(promise.IsEmpty());
ASSERT_EQ(promise->State(), v8::Promise::kFulfilled);
EXPECT_TRUE(promise->Result()->IsUndefined());
}
TEST(MiscellaneousOperationsTest, CreateAlgorithmUndefinedMethod) {
V8TestingScope scope;
auto underlying_object = v8::Object::New(scope.GetIsolate());
underlying_object
->Set(scope.GetContext(), V8String(scope.GetIsolate(), "pull"),
v8::Undefined(scope.GetIsolate()))
.Check();
auto* algo = CreateAlgorithmFromUnderlyingMethod(
scope.GetScriptState(), underlying_object, "pull",
"underlyingSource.pull", EmptyExtraArg(), ASSERT_NO_EXCEPTION);
ASSERT_TRUE(algo);
auto promise = algo->Run(scope.GetScriptState(), 0, nullptr);
ASSERT_FALSE(promise.IsEmpty());
ASSERT_EQ(promise->State(), v8::Promise::kFulfilled);
EXPECT_TRUE(promise->Result()->IsUndefined());
}
TEST(MiscellaneousOperationsTest, CreateAlgorithmNullMethod) {
V8TestingScope scope;
auto underlying_object = v8::Object::New(scope.GetIsolate());
underlying_object
->Set(scope.GetContext(), V8String(scope.GetIsolate(), "pull"),
v8::Null(scope.GetIsolate()))
.Check();
ExceptionState exception_state(scope.GetIsolate(),
ExceptionState::kExecutionContext, "", "");
auto* algo = CreateAlgorithmFromUnderlyingMethod(
scope.GetScriptState(), underlying_object, "pull",
"underlyingSource.pull", EmptyExtraArg(), exception_state);
EXPECT_FALSE(algo);
EXPECT_TRUE(exception_state.HadException());
}
TEST(MiscellaneousOperationsTest, CreateAlgorithmThrowingGetter) {
V8TestingScope scope;
ScriptValue underlying_value = EvalWithPrintingError(
&scope, "({ get pull() { throw new TypeError(); } })");
ASSERT_TRUE(underlying_value.IsObject());
auto underlying_object = underlying_value.V8Value().As<v8::Object>();
ExceptionState exception_state(scope.GetIsolate(),
ExceptionState::kExecutionContext, "", "");
auto* algo = CreateAlgorithmFromUnderlyingMethod(
scope.GetScriptState(), underlying_object, "pull",
"underlyingSource.pull", EmptyExtraArg(), exception_state);
EXPECT_FALSE(algo);
EXPECT_TRUE(exception_state.HadException());
}
v8::Local<v8::Value> CreateFromFunctionAndGetResult(
V8TestingScope* scope,
const char* function_definition,
v8::MaybeLocal<v8::Value> extra_arg = v8::MaybeLocal<v8::Value>(),
int argc = 0,
v8::Local<v8::Value> argv[] = nullptr) {
String js = String("({start: ") + function_definition + "})" + '\0';
ScriptValue underlying_value =
EvalWithPrintingError(scope, WTF::StringUTF8Adaptor(js).Data());
auto underlying_object = underlying_value.V8Value().As<v8::Object>();
auto* algo = CreateAlgorithmFromUnderlyingMethod(
scope->GetScriptState(), underlying_object, "start",
"underlyingSource.start", extra_arg, ASSERT_NO_EXCEPTION);
auto promise = algo->Run(scope->GetScriptState(), argc, argv);
EXPECT_EQ(promise->State(), v8::Promise::kFulfilled);
return promise->Result();
}
TEST(MiscellaneousOperationsTest, CreateAlgorithmReturnsInteger) {
V8TestingScope scope;
auto result = CreateFromFunctionAndGetResult(&scope, "() => 5");
ASSERT_TRUE(result->IsNumber());
EXPECT_EQ(result.As<v8::Number>()->Value(), 5);
}
TEST(MiscellaneousOperationsTest, CreateAlgorithmReturnsPromise) {
V8TestingScope scope;
auto result =
CreateFromFunctionAndGetResult(&scope, "() => Promise.resolve(2)");
ASSERT_TRUE(result->IsNumber());
EXPECT_EQ(result.As<v8::Number>()->Value(), 2);
}
bool CreateFromFunctionAndGetSuccess(
V8TestingScope* scope,
const char* function_definition,
v8::MaybeLocal<v8::Value> extra_arg = v8::MaybeLocal<v8::Value>(),
int argc = 0,
v8::Local<v8::Value> argv[] = nullptr) {
auto result = CreateFromFunctionAndGetResult(scope, function_definition,
extra_arg, argc, argv);
if (!result->IsBoolean()) {
return false;
}
return result.As<v8::Boolean>()->Value();
}
TEST(MiscellaneousOperationsTest, CreateAlgorithmNoArgs) {
V8TestingScope scope;
EXPECT_TRUE(CreateFromFunctionAndGetSuccess(
&scope, "(...args) => args.length === 0"));
}
TEST(MiscellaneousOperationsTest, CreateAlgorithmExtraArg) {
V8TestingScope scope;
v8::Local<v8::Number> extra_arg = v8::Number::New(scope.GetIsolate(), 7);
EXPECT_TRUE(CreateFromFunctionAndGetSuccess(
&scope, "(...args) => args.length === 1 && args[0] === 7", extra_arg));
}
TEST(MiscellaneousOperationsTest, CreateAlgorithmPassOneArg) {
V8TestingScope scope;
v8::MaybeLocal<v8::Value> extra_arg;
v8::Local<v8::Value> argv[] = {v8::Number::New(scope.GetIsolate(), 10)};
EXPECT_TRUE(CreateFromFunctionAndGetSuccess(
&scope, "(...args) => args.length === 1 && args[0] === 10",
EmptyExtraArg(), 1, argv));
}
TEST(MiscellaneousOperationsTest, CreateAlgorithmPassBoth) {
V8TestingScope scope;
v8::MaybeLocal<v8::Value> extra_arg = v8::Number::New(scope.GetIsolate(), 5);
v8::Local<v8::Value> argv[] = {v8::Number::New(scope.GetIsolate(), 10)};
EXPECT_TRUE(CreateFromFunctionAndGetSuccess(
&scope,
"(...args) => args.length === 2 && args[0] === 10 && args[1] === 5",
extra_arg, 1, argv));
}
TEST(MiscellaneousOperationsTest, CallOrNoop1NoMethod) {
V8TestingScope scope;
auto underlying_object = v8::Object::New(scope.GetIsolate());
v8::Local<v8::Value> arg0 = v8::Number::New(scope.GetIsolate(), 0);
auto maybe_result =
CallOrNoop1(scope.GetScriptState(), underlying_object, "transform",
"transformer.transform", arg0, ASSERT_NO_EXCEPTION);
ASSERT_FALSE(maybe_result.IsEmpty());
EXPECT_TRUE(maybe_result.ToLocalChecked()->IsUndefined());
}
TEST(MiscellaneousOperationsTest, CallOrNoop1NullMethod) {
V8TestingScope scope;
auto underlying_object = v8::Object::New(scope.GetIsolate());
v8::Local<v8::Value> arg0 = v8::Number::New(scope.GetIsolate(), 0);
underlying_object
->Set(scope.GetContext(), V8String(scope.GetIsolate(), "transform"),
v8::Null(scope.GetIsolate()))
.Check();
ExceptionState exception_state(scope.GetIsolate(),
ExceptionState::kExecutionContext, "", "");
auto maybe_result =
CallOrNoop1(scope.GetScriptState(), underlying_object, "transform",
"transformer.transform", arg0, exception_state);
ASSERT_TRUE(maybe_result.IsEmpty());
ASSERT_TRUE(exception_state.HadException());
}
TEST(MiscellaneousOperationsTest, CallOrNoop1CheckCalled) {
V8TestingScope scope;
ScriptValue underlying_value = EvalWithPrintingError(&scope,
R"(({
transform(...args) {
return args.length === 1 && args[0] === 17;
}
}))");
ASSERT_TRUE(underlying_value.IsObject());
auto underlying_object = underlying_value.V8Value().As<v8::Object>();
v8::Local<v8::Value> arg0 = v8::Number::New(scope.GetIsolate(), 17);
auto maybe_result =
CallOrNoop1(scope.GetScriptState(), underlying_object, "transform",
"transformer.transform", arg0, ASSERT_NO_EXCEPTION);
ASSERT_FALSE(maybe_result.IsEmpty());
auto result = maybe_result.ToLocalChecked();
ASSERT_TRUE(result->IsBoolean());
EXPECT_TRUE(result.As<v8::Boolean>()->Value());
}
v8::Local<v8::Promise> PromiseCallFromText(V8TestingScope* scope,
const char* function_definition,
const char* object_definition,
int argc,
v8::Local<v8::Value> argv[]) {
ScriptValue function_value =
EvalWithPrintingError(scope, function_definition);
EXPECT_TRUE(function_value.IsFunction());
ScriptValue object_value = EvalWithPrintingError(scope, object_definition);
EXPECT_TRUE(object_value.IsObject());
return PromiseCall(scope->GetScriptState(),
function_value.V8Value().As<v8::Function>(),
object_value.V8Value().As<v8::Object>(), argc, argv);
}
TEST(MiscellaneousOperationsTest, PromiseCalledWithObject) {
V8TestingScope scope;
v8::Local<v8::Promise> promise =
PromiseCallFromText(&scope, "(function() { return this.value === 15; })",
"({ value: 15 })", 0, nullptr);
ASSERT_EQ(promise->State(), v8::Promise::kFulfilled);
ASSERT_TRUE(promise->Result()->IsBoolean());
EXPECT_TRUE(promise->Result().As<v8::Boolean>()->Value());
}
TEST(MiscellaneousOperationsTest, PromiseCallThrowing) {
V8TestingScope scope;
v8::Local<v8::Promise> promise = PromiseCallFromText(
&scope, "(function() { throw new TypeError(); })", "({})", 0, nullptr);
ASSERT_EQ(promise->State(), v8::Promise::kRejected);
EXPECT_TRUE(promise->Result()->IsNativeError());
}
TEST(MiscellaneousOperationsTest, PromiseCallRejecting) {
V8TestingScope scope;
v8::Local<v8::Promise> promise = PromiseCallFromText(
&scope, "(function() { return Promise.reject(16) })", "({})", 0, nullptr);
ASSERT_EQ(promise->State(), v8::Promise::kRejected);
ASSERT_TRUE(promise->Result()->IsNumber());
EXPECT_EQ(promise->Result().As<v8::Number>()->Value(), 16);
}
TEST(MiscellaneousOperationsTest, ValidatePositiveHighWaterMark) {
V8TestingScope scope;
EXPECT_EQ(ValidateAndNormalizeHighWaterMark(23, ASSERT_NO_EXCEPTION), 23.0);
}
TEST(MiscellaneousOperationsTest, ValidateInfiniteHighWaterMark) {
V8TestingScope scope;
EXPECT_FALSE(isfinite(ValidateAndNormalizeHighWaterMark(
std::numeric_limits<double>::infinity(), ASSERT_NO_EXCEPTION)));
}
TEST(MiscellaneousOperationsTest, NegativeHighWaterMarkInvalid) {
V8TestingScope scope;
ExceptionState exception_state(scope.GetIsolate(),
ExceptionState::kExecutionContext, "", "");
ValidateAndNormalizeHighWaterMark(-1, exception_state);
EXPECT_TRUE(exception_state.HadException());
}
TEST(MiscellaneousOperationsTest, NaNHighWaterMarkInvalid) {
V8TestingScope scope;
ExceptionState exception_state(scope.GetIsolate(),
ExceptionState::kExecutionContext, "", "");
ValidateAndNormalizeHighWaterMark(std::numeric_limits<double>::quiet_NaN(),
exception_state);
EXPECT_TRUE(exception_state.HadException());
}
TEST(MiscellaneousOperationsTest, UndefinedSizeFunction) {
V8TestingScope scope;
auto* algo = MakeSizeAlgorithmFromSizeFunction(
scope.GetScriptState(), v8::Undefined(scope.GetIsolate()),
ASSERT_NO_EXCEPTION);
ASSERT_TRUE(algo);
auto optional =
algo->Run(scope.GetScriptState(), v8::Number::New(scope.GetIsolate(), 97),
ASSERT_NO_EXCEPTION);
ASSERT_TRUE(optional.has_value());
EXPECT_EQ(optional.value(), 1.0);
}
TEST(MiscellaneousOperationsTest, NullSizeFunction) {
V8TestingScope scope;
ExceptionState exception_state(scope.GetIsolate(),
ExceptionState::kExecutionContext, "", "");
EXPECT_EQ(MakeSizeAlgorithmFromSizeFunction(scope.GetScriptState(),
v8::Null(scope.GetIsolate()),
exception_state),
nullptr);
EXPECT_TRUE(exception_state.HadException());
}
StrategySizeAlgorithm* IdentitySizeAlgorithm(V8TestingScope* scope) {
ScriptValue function_value = EvalWithPrintingError(scope, "i => i");
EXPECT_TRUE(function_value.IsFunction());
return MakeSizeAlgorithmFromSizeFunction(
scope->GetScriptState(), function_value.V8Value(), ASSERT_NO_EXCEPTION);
}
TEST(MiscellaneousOperationsTest, SizeAlgorithmWorks) {
V8TestingScope scope;
auto* algo = IdentitySizeAlgorithm(&scope);
ASSERT_TRUE(algo);
auto optional =
algo->Run(scope.GetScriptState(), v8::Number::New(scope.GetIsolate(), 41),
ASSERT_NO_EXCEPTION);
ASSERT_TRUE(optional.has_value());
EXPECT_EQ(optional.value(), 41.0);
}
TEST(MiscellaneousOperationsTest, SizeAlgorithmConvertsToNumber) {
V8TestingScope scope;
auto* algo = IdentitySizeAlgorithm(&scope);
ASSERT_TRUE(algo);
auto optional =
algo->Run(scope.GetScriptState(), V8String(scope.GetIsolate(), "79"),
ASSERT_NO_EXCEPTION);
ASSERT_TRUE(optional.has_value());
EXPECT_EQ(optional.value(), 79.0);
}
TEST(MiscellaneousOperationsTest, ThrowingSizeAlgorithm) {
V8TestingScope scope;
ScriptValue function_value =
EvalWithPrintingError(&scope, "() => { throw new TypeError(); }");
EXPECT_TRUE(function_value.IsFunction());
auto* algo = MakeSizeAlgorithmFromSizeFunction(
scope.GetScriptState(), function_value.V8Value(), ASSERT_NO_EXCEPTION);
ASSERT_TRUE(algo);
ExceptionState exception_state(scope.GetIsolate(),
ExceptionState::kExecutionContext, "", "");
auto optional =
algo->Run(scope.GetScriptState(), V8String(scope.GetIsolate(), "79"),
exception_state);
ASSERT_FALSE(optional.has_value());
EXPECT_TRUE(exception_state.HadException());
}
TEST(MiscellaneousOperationsTest, UnconvertibleSize) {
V8TestingScope scope;
auto* algo = IdentitySizeAlgorithm(&scope);
ASSERT_TRUE(algo);
ScriptValue unconvertible_value =
EvalWithPrintingError(&scope, "({ toString() { throw new Error(); }})");
EXPECT_TRUE(unconvertible_value.IsObject());
ExceptionState exception_state(scope.GetIsolate(),
ExceptionState::kExecutionContext, "", "");
auto optional = algo->Run(scope.GetScriptState(),
unconvertible_value.V8Value(), exception_state);
ASSERT_FALSE(optional.has_value());
EXPECT_TRUE(exception_state.HadException());
}
TEST(MiscellaneousOperationsTest, PromiseResolve) {
V8TestingScope scope;
auto promise = PromiseResolve(scope.GetScriptState(),
v8::Number::New(scope.GetIsolate(), 19));
ASSERT_EQ(promise->State(), v8::Promise::kFulfilled);
ASSERT_TRUE(promise->Result()->IsNumber());
EXPECT_EQ(promise->Result().As<v8::Number>()->Value(), 19);
}
TEST(MiscellaneousOperationsTest, PromiseResolveWithPromise) {
V8TestingScope scope;
auto original_promise = v8::Promise::Resolver::New(scope.GetContext())
.ToLocalChecked()
->GetPromise();
auto resolved_promise =
PromiseResolve(scope.GetScriptState(), original_promise);
EXPECT_EQ(original_promise, resolved_promise);
}
TEST(MiscellaneousOperationsTest, PromiseResolveWithUndefined) {
V8TestingScope scope;
auto promise = PromiseResolveWithUndefined(scope.GetScriptState());
ASSERT_EQ(promise->State(), v8::Promise::kFulfilled);
EXPECT_TRUE(promise->Result()->IsUndefined());
}
TEST(MiscellaneousOperationsTest, PromiseReject) {
V8TestingScope scope;
auto promise = PromiseReject(scope.GetScriptState(),
v8::Number::New(scope.GetIsolate(), 43));
ASSERT_EQ(promise->State(), v8::Promise::kRejected);
ASSERT_TRUE(promise->Result()->IsNumber());
EXPECT_EQ(promise->Result().As<v8::Number>()->Value(), 43);
}
} // namespace
} // namespace blink