blob: bca611d25c0696653db7d5f8fcde6f62b00e0402 [file] [log] [blame]
// Copyright 2017 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/service_worker/thread_safe_script_container.h"
#include <memory>
#include "base/synchronization/waitable_event.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/platform/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink {
using ScriptStatus = ThreadSafeScriptContainer::ScriptStatus;
const char kKeyUrl[] = "https://example.com/key";
class ThreadSafeScriptContainerTest : public ::testing::Test {
public:
ThreadSafeScriptContainerTest()
: writer_thread_(Platform::Current()->CreateThread(
ThreadCreationParams(WebThreadType::kTestThread)
.SetThreadNameForTest("writer_thread"))),
reader_thread_(Platform::Current()->CreateThread(
ThreadCreationParams(WebThreadType::kTestThread)
.SetThreadNameForTest("reader_thread"))),
writer_waiter_(std::make_unique<base::WaitableEvent>(
base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED)),
reader_waiter_(std::make_unique<base::WaitableEvent>(
base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED)),
container_(base::MakeRefCounted<ThreadSafeScriptContainer>()) {}
protected:
base::WaitableEvent* AddOnWriterThread(
ThreadSafeScriptContainer::RawScriptData** out_data) {
PostCrossThreadTask(
*writer_thread_->GetTaskRunner(), FROM_HERE,
CrossThreadBindOnce(
[](scoped_refptr<ThreadSafeScriptContainer> container,
ThreadSafeScriptContainer::RawScriptData** out_data,
base::WaitableEvent* waiter) {
auto data =
std::make_unique<ThreadSafeScriptContainer::RawScriptData>(
String::FromUTF8("utf-8") /* encoding */,
Vector<uint8_t>() /* script_text */,
Vector<uint8_t>() /* meta_data */);
*out_data = data.get();
container->AddOnIOThread(KURL(kKeyUrl), std::move(data));
waiter->Signal();
},
container_, CrossThreadUnretained(out_data),
CrossThreadUnretained(writer_waiter_.get())));
return writer_waiter_.get();
}
base::WaitableEvent* OnAllDataAddedOnWriterThread() {
PostCrossThreadTask(
*writer_thread_->GetTaskRunner(), FROM_HERE,
CrossThreadBindOnce(
[](scoped_refptr<ThreadSafeScriptContainer> container,
base::WaitableEvent* waiter) {
container->OnAllDataAddedOnIOThread();
waiter->Signal();
},
container_, CrossThreadUnretained(writer_waiter_.get())));
return writer_waiter_.get();
}
base::WaitableEvent* GetStatusOnReaderThread(ScriptStatus* out_status) {
PostCrossThreadTask(
*reader_thread_->GetTaskRunner(), FROM_HERE,
CrossThreadBindOnce(
[](scoped_refptr<ThreadSafeScriptContainer> container,
ScriptStatus* out_status, base::WaitableEvent* waiter) {
*out_status = container->GetStatusOnWorkerThread(KURL(kKeyUrl));
waiter->Signal();
},
container_, CrossThreadUnretained(out_status),
CrossThreadUnretained(reader_waiter_.get())));
return reader_waiter_.get();
}
base::WaitableEvent* WaitOnReaderThread(bool* out_exists) {
PostCrossThreadTask(
*reader_thread_->GetTaskRunner(), FROM_HERE,
CrossThreadBindOnce(
[](scoped_refptr<ThreadSafeScriptContainer> container,
bool* out_exists, base::WaitableEvent* waiter) {
*out_exists = container->WaitOnWorkerThread(KURL(kKeyUrl));
waiter->Signal();
},
container_, CrossThreadUnretained(out_exists),
CrossThreadUnretained(reader_waiter_.get())));
return reader_waiter_.get();
}
base::WaitableEvent* TakeOnReaderThread(
ThreadSafeScriptContainer::RawScriptData** out_data) {
PostCrossThreadTask(
*reader_thread_->GetTaskRunner(), FROM_HERE,
CrossThreadBindOnce(
[](scoped_refptr<ThreadSafeScriptContainer> container,
ThreadSafeScriptContainer::RawScriptData** out_data,
base::WaitableEvent* waiter) {
auto data = container->TakeOnWorkerThread(KURL(kKeyUrl));
*out_data = data.get();
waiter->Signal();
},
container_, CrossThreadUnretained(out_data),
CrossThreadUnretained(reader_waiter_.get())));
return reader_waiter_.get();
}
private:
std::unique_ptr<Thread> writer_thread_;
std::unique_ptr<Thread> reader_thread_;
std::unique_ptr<base::WaitableEvent> writer_waiter_;
std::unique_ptr<base::WaitableEvent> reader_waiter_;
scoped_refptr<ThreadSafeScriptContainer> container_;
};
TEST_F(ThreadSafeScriptContainerTest, WaitExistingKey) {
{
ScriptStatus result = ScriptStatus::kReceived;
GetStatusOnReaderThread(&result)->Wait();
EXPECT_EQ(ScriptStatus::kPending, result);
}
ThreadSafeScriptContainer::RawScriptData* added_data;
{
bool result = false;
base::WaitableEvent* pending_wait = WaitOnReaderThread(&result);
// This should not be signaled until data is added.
EXPECT_FALSE(pending_wait->IsSignaled());
base::WaitableEvent* pending_write = AddOnWriterThread(&added_data);
pending_wait->Wait();
pending_write->Wait();
EXPECT_TRUE(result);
}
{
ScriptStatus result = ScriptStatus::kFailed;
GetStatusOnReaderThread(&result)->Wait();
EXPECT_EQ(ScriptStatus::kReceived, result);
}
{
ThreadSafeScriptContainer::RawScriptData* taken_data;
TakeOnReaderThread(&taken_data)->Wait();
EXPECT_EQ(added_data, taken_data);
}
{
ScriptStatus result = ScriptStatus::kFailed;
GetStatusOnReaderThread(&result)->Wait();
// The record should exist though it's already taken.
EXPECT_EQ(ScriptStatus::kTaken, result);
}
{
bool result = false;
WaitOnReaderThread(&result)->Wait();
// Waiting for the record being already taken should succeed.
EXPECT_TRUE(result);
// The record status should still be |kTaken|.
ScriptStatus status = ScriptStatus::kFailed;
GetStatusOnReaderThread(&status)->Wait();
EXPECT_EQ(ScriptStatus::kTaken, status);
}
// Finish adding data.
OnAllDataAddedOnWriterThread()->Wait();
{
bool result = false;
WaitOnReaderThread(&result)->Wait();
// The record is in |kTaken| status, so Wait shouldn't fail.
EXPECT_TRUE(result);
// The status of record should still be |kTaken|.
ScriptStatus status = ScriptStatus::kFailed;
GetStatusOnReaderThread(&status)->Wait();
EXPECT_EQ(ScriptStatus::kTaken, status);
}
}
TEST_F(ThreadSafeScriptContainerTest, WaitNonExistingKey) {
{
ScriptStatus result = ScriptStatus::kReceived;
GetStatusOnReaderThread(&result)->Wait();
EXPECT_EQ(ScriptStatus::kPending, result);
}
{
bool result = true;
base::WaitableEvent* pending_wait = WaitOnReaderThread(&result);
// This should not be signaled until OnAllDataAdded is called.
EXPECT_FALSE(pending_wait->IsSignaled());
base::WaitableEvent* pending_on_all_data_added =
OnAllDataAddedOnWriterThread();
pending_wait->Wait();
pending_on_all_data_added->Wait();
// Aborted wait should return false.
EXPECT_FALSE(result);
}
{
bool result = true;
WaitOnReaderThread(&result)->Wait();
// Wait fails immediately because OnAllDataAdded is called.
EXPECT_FALSE(result);
}
}
} // namespace blink