blob: 53c6ee1ab0dfe046657a820fadf855c05f2f147a [file] [log] [blame]
// Copyright 2021 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 CONTENT_SERVICES_AUCTION_WORKLET_WORKLET_V8_DEBUG_TEST_UTIL_H_
#define CONTENT_SERVICES_AUCTION_WORKLET_WORKLET_V8_DEBUG_TEST_UTIL_H_
#include <list>
#include <memory>
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/task/sequenced_task_runner.h"
#include "base/thread_annotations.h"
#include "base/values.h"
#include "v8/include/v8-inspector.h"
namespace auction_worklet {
// This is a collection of helpers for testing debugging by directly setting a
// V8Inspector (with minimalist test versions of various dependencies) on an
// AuctionV8Helper.
class AuctionV8Helper;
// Absolutely minimal implelementation of V8InspectorClient, which isn't
// good enough for anything like breakpoints.
class TestInspectorClient : public v8_inspector::V8InspectorClient {
public:
explicit TestInspectorClient(AuctionV8Helper* helper);
TestInspectorClient(const TestInspectorClient&) = delete;
TestInspectorClient& operator=(const TestInspectorClient&) = delete;
void runIfWaitingForDebugger(int context_group_id) override;
private:
raw_ptr<AuctionV8Helper> v8_helper_;
};
// A thread-safe output channel that records everything that V8 tells it,
// and lets one wait for particular events. It also keeps track of a
// v8_inspector::V8InspectorSession* pointer to send commands the other way.
class TestChannel : public v8_inspector::V8Inspector::Channel {
public:
// Representation of a message sent back by V8's debugger code.
struct Event {
enum class Type { Response, Notification };
Type type;
int call_id = -1; // used for responses only.
// Payload of the message.
base::Value value;
};
using EventPredicate = base::RepeatingCallback<bool(const Event&)>;
// TestChannel instances are created via
// ScopedInspectorSupport::ConnectDebuggerSession, and are owned by
// ScopedInspectorSupport.
TestChannel(const TestChannel&) = delete;
TestChannel& operator=(const TestChannel&) = delete;
~TestChannel() override;
void SetInspectorSession(
v8_inspector::V8InspectorSession* v8_inspector_session) {
v8_inspector_session_ = v8_inspector_session;
}
// Executes a debugger command with sequence number `call_id`, invoking method
// `method`, and message body `payload` (which should have the same id and
// method included, and must be in JSON), and waits for a corresponding
// Response back from the debugger.
//
// Can be called on any thread.
Event RunCommandAndWaitForResult(int call_id,
std::string method,
std::string payload);
// Can be called on any thread, but the world state needs to be stable for it
// to be possible to interpret the return value in a non-flaky way.
std::list<Event> TakeAllEvents();
// Waits until an event matching `predicate` has been output by v8, and
// returns it, removing it from event list. All events previously will also be
// considered as no longer eligible for WaitForEvent() (but will show up in
// TakeAllEvents). Can be called on any thread.
Event WaitForEvent(EventPredicate predicate);
// Waits for a notification with method field matching `method`.
Event WaitForMethodNotification(const std::string& method);
// v8_inspector::V8Inspector::Channel implementation.
void sendResponse(
int call_id,
std::unique_ptr<v8_inspector::StringBuffer> message) override;
void sendNotification(
std::unique_ptr<v8_inspector::StringBuffer> message) override;
void flushProtocolNotifications() override;
private:
friend class ScopedInspectorSupport;
explicit TestChannel(AuctionV8Helper* v8_helper);
void LogEvent(Event::Type type,
int call_id,
std::unique_ptr<v8_inspector::StringBuffer> message);
// Can be called on any thread.
void RunCommand(int call_id, std::string method, std::string payload);
void RunCommandOnV8Thread(int call_id,
std::string method,
std::string payload);
scoped_refptr<AuctionV8Helper> v8_helper_;
raw_ptr<v8_inspector::V8InspectorSession> v8_inspector_session_;
base::Lock lock_;
base::ConditionVariable wake_up_ GUARDED_BY(lock_);
std::list<Event> seen_events_ GUARDED_BY(lock_);
std::list<Event> events_ GUARDED_BY(lock_);
};
// Class that helps set a V8Inspector w/a TestInspectorClient on an
// AuctionV8Helper, and clean it up properly. Assumes v8 thread is separate from
// main thread it runs on. Also helps with connecting debugger sessions.
class ScopedInspectorSupport {
public:
explicit ScopedInspectorSupport(AuctionV8Helper* v8_helper);
ScopedInspectorSupport(const ScopedInspectorSupport&) = delete;
~ScopedInspectorSupport();
ScopedInspectorSupport& operator=(const ScopedInspectorSupport&) = delete;
// Connects a debugger session for given `context_group_id`. Will spin an
// event loop. Returned object is owned by this.
TestChannel* ConnectDebuggerSession(int context_group_id);
private:
struct V8State {
V8State();
~V8State();
scoped_refptr<AuctionV8Helper> v8_helper_;
std::unique_ptr<TestInspectorClient> inspector_client_;
std::vector<std::unique_ptr<TestChannel>> output_channels_;
std::vector<std::unique_ptr<v8_inspector::V8InspectorSession>>
inspector_sessions_;
};
void ConnectDebuggerSessionOnV8Thread(int context_group_id,
TestChannel** result,
base::OnceClosure done);
// `v8_state_` is created on main thread, and used and destroyed on V8 thread,
// except it's safe to access `v8_state_->v8_helper_->v8_runner()` from main
// thread as well.
std::unique_ptr<V8State, base::OnTaskRunnerDeleter> v8_state_;
};
} // namespace auction_worklet
#endif // CONTENT_SERVICES_AUCTION_WORKLET_WORKLET_V8_DEBUG_TEST_UTIL_H_