blob: d9568fca5f2e784ee6a590b13ad6459f06524bcb [file] [log] [blame]
// Copyright 2020 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/modules/direct_sockets/tcp_readable_stream_wrapper.h"
#include "base/test/mock_callback.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_iterator_result_value.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_uint8_array.h"
#include "third_party/blink/renderer/core/streams/readable_stream.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
namespace blink {
namespace {
using ::testing::ElementsAre;
using ::testing::StrictMock;
// The purpose of this class is to ensure that the data pipe is reset before the
// V8TestingScope is destroyed, so that the TCPReadableStreamWrapper object
// doesn't try to create a DOMException after the ScriptState has gone away.
class StreamCreator {
STACK_ALLOCATED();
public:
StreamCreator() = default;
~StreamCreator() {
ClosePipe();
// Let the TCPReadableStreamWrapper object respond to the closure if it
// needs to.
test::RunPendingTasks();
}
// The default value of |capacity| means some sensible value selected by mojo.
TCPReadableStreamWrapper* Create(const V8TestingScope& scope,
uint32_t capacity = 0) {
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(MojoCreateDataPipeOptions);
options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
options.element_num_bytes = 1;
options.capacity_num_bytes = capacity;
mojo::ScopedDataPipeConsumerHandle data_pipe_consumer;
MojoResult result =
mojo::CreateDataPipe(&options, data_pipe_producer_, data_pipe_consumer);
if (result != MOJO_RESULT_OK) {
ADD_FAILURE() << "CreateDataPipe() returned " << result;
}
auto* script_state = scope.GetScriptState();
auto* tcp_readable_stream_wrapper =
MakeGarbageCollected<TCPReadableStreamWrapper>(
script_state,
base::BindOnce(&StreamCreator::OnAbort, base::Unretained(this)),
std::move(data_pipe_consumer));
return tcp_readable_stream_wrapper;
}
void WriteToPipe(Vector<uint8_t> data) {
uint32_t num_bytes = data.size();
EXPECT_EQ(data_pipe_producer_->WriteData(data.data(), &num_bytes,
MOJO_WRITE_DATA_FLAG_ALL_OR_NONE),
MOJO_RESULT_OK);
EXPECT_EQ(num_bytes, data.size());
}
void ClosePipe() { data_pipe_producer_.reset(); }
// Copies the contents of a v8::Value containing a Uint8Array to a Vector.
static Vector<uint8_t> ToVector(const V8TestingScope& scope,
v8::Local<v8::Value> v8value) {
Vector<uint8_t> ret;
DOMUint8Array* value =
V8Uint8Array::ToImplWithTypeCheck(scope.GetIsolate(), v8value);
if (!value) {
ADD_FAILURE() << "chunk is not an Uint8Array";
return ret;
}
ret.Append(static_cast<uint8_t*>(value->Data()),
static_cast<wtf_size_t>(value->byteLength()));
return ret;
}
struct Iterator {
bool done = false;
Vector<uint8_t> value;
};
// Performs a single read from |reader|, converting the output to the
// Iterator type. Assumes that the readable stream is not errored.
// static Iterator Read(const V8TestingScope& scope,
Iterator Read(const V8TestingScope& scope,
ReadableStreamDefaultReader* reader) {
auto* script_state = scope.GetScriptState();
ScriptPromise read_promise =
reader->read(script_state, ASSERT_NO_EXCEPTION);
ScriptPromiseTester tester(script_state, read_promise);
tester.WaitUntilSettled();
EXPECT_TRUE(tester.IsFulfilled());
return IteratorFromReadResult(scope, tester.Value().V8Value());
}
static Iterator IteratorFromReadResult(const V8TestingScope& scope,
v8::Local<v8::Value> result) {
CHECK(result->IsObject());
Iterator ret;
v8::Local<v8::Value> v8value;
if (!V8UnpackIteratorResult(scope.GetScriptState(), result.As<v8::Object>(),
&ret.done)
.ToLocal(&v8value)) {
ADD_FAILURE() << "Couldn't unpack iterator";
return ret;
}
if (ret.done) {
EXPECT_TRUE(v8value->IsUndefined());
return ret;
}
ret.value = ToVector(scope, v8value);
return ret;
}
void OnAbort() { on_abort_called_ = true; }
bool HasAborted() const { return on_abort_called_; }
private:
bool on_abort_called_ = false;
mojo::ScopedDataPipeProducerHandle data_pipe_producer_;
};
TEST(TCPReadableStreamWrapperTest, Create) {
V8TestingScope scope;
StreamCreator stream_creator;
auto* tcp_readable_stream_wrapper = stream_creator.Create(scope);
EXPECT_TRUE(tcp_readable_stream_wrapper->Readable());
}
TEST(TCPReadableStreamWrapperTest, ReadArrayBuffer) {
V8TestingScope scope;
StreamCreator stream_creator;
auto* tcp_readable_stream_wrapper = stream_creator.Create(scope);
auto* script_state = scope.GetScriptState();
auto* reader =
tcp_readable_stream_wrapper->Readable()->GetDefaultReaderForTesting(
script_state, ASSERT_NO_EXCEPTION);
stream_creator.WriteToPipe({'A'});
StreamCreator::Iterator result = stream_creator.Read(scope, reader);
EXPECT_FALSE(result.done);
EXPECT_THAT(result.value, ElementsAre('A'));
}
TEST(TCPReadableStreamWrapperTest, WriteToPipeWithPendingRead) {
V8TestingScope scope;
StreamCreator stream_creator;
auto* tcp_readable_stream_wrapper = stream_creator.Create(scope);
auto* script_state = scope.GetScriptState();
auto* reader =
tcp_readable_stream_wrapper->Readable()->GetDefaultReaderForTesting(
script_state, ASSERT_NO_EXCEPTION);
ScriptPromise read_promise = reader->read(script_state, ASSERT_NO_EXCEPTION);
ScriptPromiseTester tester(script_state, read_promise);
test::RunPendingTasks();
stream_creator.WriteToPipe({'A'});
tester.WaitUntilSettled();
ASSERT_TRUE(tester.IsFulfilled());
StreamCreator::Iterator result =
stream_creator.IteratorFromReadResult(scope, tester.Value().V8Value());
EXPECT_FALSE(result.done);
EXPECT_THAT(result.value, ElementsAre('A'));
}
TEST(TCPReadableStreamWrapperTest, TriggerOnAborted) {
V8TestingScope scope;
StreamCreator stream_creator;
EXPECT_FALSE(stream_creator.HasAborted());
auto* tcp_readable_stream_wrapper = stream_creator.Create(scope);
auto* script_state = scope.GetScriptState();
auto* reader =
tcp_readable_stream_wrapper->Readable()->GetDefaultReaderForTesting(
script_state, ASSERT_NO_EXCEPTION);
ScriptPromise read_promise = reader->read(script_state, ASSERT_NO_EXCEPTION);
ScriptPromiseTester tester(script_state, read_promise);
test::RunPendingTasks();
stream_creator.WriteToPipe({'A'});
// Trigger OnAborted() on purpose.
stream_creator.ClosePipe();
tester.WaitUntilSettled();
ASSERT_TRUE(tester.IsFulfilled());
EXPECT_TRUE(stream_creator.HasAborted());
}
} // namespace
} // namespace blink