blob: 27a80ad91cc18ed0e987070faf7e373bb1bd08d6 [file] [log] [blame]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/socket/websocket_stream_socket.h"
#include <memory>
#include <vector>
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/socket/socket_test_util.h"
#include "net/socket/websocket_endpoint_lock_manager.h"
#include "net/test/gtest_util.h"
#include "net/test/test_with_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace net {
namespace {
struct ConnectTimingAndResult {
IoMode io_mode;
Error connect_result;
};
constexpr ConnectTimingAndResult kConnectTimingAndResults[] = {
{/*io_mode=*/SYNCHRONOUS, /*connect_result=*/OK},
{/*io_mode=*/SYNCHRONOUS, /*connect_result=*/ERR_FAILED},
{/*io_mode=*/ASYNC, /*connect_result=*/OK},
{/*io_mode=*/ASYNC, /*connect_result=*/ERR_FAILED},
};
class WebSocketStreamSocketTest
: public testing::TestWithParam<ConnectTimingAndResult>,
public WithTaskEnvironment {
public:
WebSocketStreamSocketTest()
: WithTaskEnvironment(
base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
~WebSocketStreamSocketTest() override = default;
std::unique_ptr<WebSocketStreamSocket> CreateSocket(
MockConnect mock_connect) {
auto data_provider = std::make_unique<SequencedSocketData>();
data_provider->set_connect_data(mock_connect);
// Create socket. Only the DataProvider matters.
auto tcp_socket = std::make_unique<MockTCPClientSocket>(
AddressList(kLocalhost), /*net_log=*/nullptr, data_provider.get());
data_providers_.emplace_back(std::move(data_provider));
return std::make_unique<WebSocketStreamSocket>(lock_manager_, kLocalhost,
std::move(tcp_socket));
}
IoMode io_mode() const { return GetParam().io_mode; }
Error connect_result() const { return GetParam().connect_result; }
protected:
const IPEndPoint kLocalhost{IPAddress::IPv4Localhost(), 80};
WebSocketEndpointLockManager lock_manager_;
// Data providers for mock sockets. Mock sockets hold onto raw pointers to
// these, so they have to be kept around and not moved.
std::vector<std::unique_ptr<SequencedSocketData>> data_providers_;
};
INSTANTIATE_TEST_SUITE_P(,
WebSocketStreamSocketTest,
testing::ValuesIn(kConnectTimingAndResults));
TEST_P(WebSocketStreamSocketTest, OneSocket) {
EXPECT_TRUE(lock_manager_.IsEmpty());
base::test::TestFuture<int> callback;
auto socket = CreateSocket(MockConnect(io_mode(), connect_result()));
if (io_mode() == SYNCHRONOUS) {
EXPECT_THAT(socket->Connect(callback.GetCallback()),
test::IsError(connect_result()));
EXPECT_FALSE(lock_manager_.IsEmpty());
EXPECT_FALSE(callback.IsReady());
} else {
EXPECT_THAT(socket->Connect(callback.GetCallback()),
test::IsError(ERR_IO_PENDING));
// With one socket, even in the async case, the lock should be obtained
// synchronously.
EXPECT_FALSE(lock_manager_.IsEmpty());
EXPECT_THAT(callback.Get(), test::IsError(connect_result()));
}
}
TEST_P(WebSocketStreamSocketTest, TwoSockets) {
EXPECT_TRUE(lock_manager_.IsEmpty());
base::test::TestFuture<int> callback1;
auto socket1 = CreateSocket(MockConnect(io_mode(), connect_result()));
if (io_mode() == SYNCHRONOUS) {
EXPECT_THAT(socket1->Connect(callback1.GetCallback()),
test::IsError(connect_result()));
} else {
EXPECT_THAT(socket1->Connect(callback1.GetCallback()),
test::IsError(ERR_IO_PENDING));
EXPECT_THAT(callback1.Get(), test::IsError(connect_result()));
}
EXPECT_FALSE(lock_manager_.IsEmpty());
base::test::TestFuture<int> callback2;
auto socket2 = CreateSocket(MockConnect(io_mode(), connect_result()));
EXPECT_THAT(socket2->Connect(callback2.GetCallback()),
test::IsError(ERR_IO_PENDING));
// `socket1` should be preventing `socket2` from getting the lock. Advance
// time to make there there's no unlock task queued that might free up the
// lock. This value must be much longer than the unlock delay.
FastForwardBy(base::Minutes(1));
EXPECT_FALSE(callback2.IsReady());
// Lock should still be held.
EXPECT_FALSE(lock_manager_.IsEmpty());
// Destroying the second socket should release the lock, albeit after some
// delay.
socket1.reset();
EXPECT_THAT(callback2.Get(), test::IsError(connect_result()));
// The lock should still be held.
EXPECT_FALSE(lock_manager_.IsEmpty());
}
} // namespace
} // namespace net