blob: 6989d76ea5de65eb91bb9e68e0db881d6489036e [file] [log] [blame] [edit]
/*
* Copyright (C) 2022 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "ArgumentCoders.h"
#include "Connection.h"
#include "Utilities.h"
#include <optional>
#include <wtf/Forward.h>
namespace TestWebKitAPI {
template <typename T>
std::optional<T> copyViaEncoder(const T& o)
{
IPC::Encoder encoder(static_cast<IPC::MessageName>(78), 0);
encoder << o;
auto decoder = IPC::Decoder::create(encoder.span(), encoder.releaseAttachments());
return decoder->decode<T>();
}
struct MessageInfo {
IPC::MessageName messageName;
uint64_t destinationID;
};
struct MockTestMessage1 {
static constexpr bool isSync = false;
static constexpr bool canDispatchOutOfOrder = true;
static constexpr bool replyCanDispatchOutOfOrder = false;
static constexpr IPC::MessageName name() { return static_cast<IPC::MessageName>(123); }
std::tuple<> arguments() { return { }; }
};
struct MockTestMessageWithAsyncReply1 {
static constexpr bool isSync = false;
static constexpr bool canDispatchOutOfOrder = false;
static constexpr bool replyCanDispatchOutOfOrder = false;
static constexpr IPC::MessageName name() { return static_cast<IPC::MessageName>(124); }
// Just using WebPage_GetBytecodeProfileReply as something that is async message name.
// If WebPage_GetBytecodeProfileReply is removed, just use another one.
static constexpr IPC::MessageName asyncMessageReplyName() { return IPC::MessageName::WebPage_GetBytecodeProfileReply; }
std::tuple<> arguments() { return { }; }
using ReplyArguments = std::tuple<uint64_t>;
using Promise = WTF::NativePromise<uint64_t, IPC::Error>;
};
class MockConnectionClient final : public IPC::Connection::Client {
public:
~MockConnectionClient()
{
}
Vector<MessageInfo> takeMessages()
{
Vector<MessageInfo> result;
result.appendRange(m_messages.begin(), m_messages.end());
m_messages.clear();
return result;
}
MessageInfo waitForMessage(Seconds timeout)
{
if (m_messages.isEmpty()) {
m_continueWaitForMessage = false;
Util::runFor(&m_continueWaitForMessage, timeout);
}
ASSERT(m_messages.size() >= 1);
return m_messages.takeFirst();
}
bool gotDidClose() const
{
return m_didClose;
}
bool waitForDidClose(Seconds timeout)
{
ASSERT(!m_didClose); // Caller checks this.
Util::runFor(&m_didClose, timeout);
return m_didClose;
}
// Handler returns false if the message should be just recorded.
void setAsyncMessageHandler(Function<bool(IPC::Decoder&)>&& handler)
{
m_asyncMessageHandler = WTFMove(handler);
}
// IPC::Connection::Client overrides.
void didReceiveMessage(IPC::Connection&, IPC::Decoder& decoder) override
{
if (m_asyncMessageHandler && m_asyncMessageHandler(decoder))
return;
m_messages.append({ decoder.messageName(), decoder.destinationID() });
m_continueWaitForMessage = true;
}
// Handler contract as IPC::MessageReceiver::didReceiveSyncMessage: false on invalid message, may adopt encoder,
// decoder used only during the call, if encoder not adopted it will be submitted.
void setSyncMessageHandler(Function<bool(IPC::Decoder&, UniqueRef<IPC::Encoder>&)>&& handler)
{
m_syncMessageHandler = WTFMove(handler);
}
bool didReceiveSyncMessage(IPC::Connection&, IPC::Decoder& decoder, UniqueRef<IPC::Encoder>& encoder) override
{
if (m_syncMessageHandler)
return m_syncMessageHandler(decoder, encoder);
return false;
}
void didClose(IPC::Connection&) override
{
m_didClose = true;
}
void didReceiveInvalidMessage(IPC::Connection&, IPC::MessageName message, int32_t) override
{
m_didReceiveInvalidMessage = message;
}
private:
bool m_didClose { false };
std::optional<IPC::MessageName> m_didReceiveInvalidMessage;
Deque<MessageInfo> m_messages;
bool m_continueWaitForMessage { false };
Function<bool(IPC::Decoder&)> m_asyncMessageHandler;
Function<bool(IPC::Decoder&, UniqueRef<IPC::Encoder>&)> m_syncMessageHandler;
};
enum class ConnectionTestDirection {
ServerIsA,
ClientIsA
};
void PrintTo(ConnectionTestDirection, ::std::ostream*);
class ConnectionTestBase {
public:
void setupBase();
void teardownBase();
::testing::AssertionResult openA()
{
if (!a())
return ::testing::AssertionFailure() << "No A.";
if (!a()->open(aClient()))
return ::testing::AssertionFailure() << "Failed to open A";
return ::testing::AssertionSuccess();
}
::testing::AssertionResult openB()
{
if (!b())
return ::testing::AssertionFailure() << "No b.";
if (!b()->open(bClient()))
return ::testing::AssertionFailure() << "Failed to open B";
return ::testing::AssertionSuccess();
}
::testing::AssertionResult openBoth()
{
auto result = openA();
if (result)
result = openB();
return result;
}
IPC::Connection* a()
{
return m_connections[0].connection.get();
}
MockConnectionClient& aClient()
{
return m_connections[0].client;
}
IPC::Connection* b()
{
return m_connections[1].connection.get();
}
MockConnectionClient& bClient()
{
return m_connections[1].client;
}
void deleteA()
{
m_connections[0].connection = nullptr;
}
void deleteB()
{
m_connections[1].connection = nullptr;
}
protected:
static void ensureConnectionWorkQueueEmpty(IPC::Connection&);
struct {
RefPtr<IPC::Connection> connection;
MockConnectionClient client;
} m_connections[2];
};
// Test fixture for tests that are run two times:
// - Server as a(), and client as b()
// - Server as b() and client as a()
// The setup and teardown of the Connection is not symmetric, so this fixture is useful to test various scenarios
// around these.
class ConnectionTestABBA : public testing::TestWithParam<std::tuple<ConnectionTestDirection>>, protected ConnectionTestBase {
public:
bool serverIsA() const { return std::get<0>(GetParam()) == ConnectionTestDirection::ServerIsA; }
void SetUp() override
{
setupBase();
if (!serverIsA())
std::swap(m_connections[0].connection, m_connections[1].connection);
}
void TearDown() override
{
teardownBase();
}
};
}