fastboot: socket testing improvements.
(This code was originally part of a huge fastboot CL but has been split out to try to make the CLs a little more manageable). More prep for fastboot TCP and UDP implementations. This CL adds a SocketMock class that makes it easy to mock out network behavior so we can unit test the TCP and UDP protocols. Also uses the new libcutils socket_get_local_port() to avoid hardcoding a server port in unit tests. Bug: http://b/26157893. Change-Id: I1ba10f31e98d7349313fc15f240383d63378a8db
This commit is contained in:
parent
d95ecfc432
commit
c3a466960f
6 changed files with 350 additions and 15 deletions
|
@ -106,7 +106,11 @@ include $(CLEAR_VARS)
|
|||
LOCAL_MODULE := fastboot_test
|
||||
LOCAL_MODULE_HOST_OS := darwin linux windows
|
||||
|
||||
LOCAL_SRC_FILES := socket.cpp socket_test.cpp
|
||||
LOCAL_SRC_FILES := \
|
||||
socket.cpp \
|
||||
socket_mock.cpp \
|
||||
socket_test.cpp \
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := libbase libcutils
|
||||
|
||||
LOCAL_CFLAGS += -Wall -Wextra -Werror -Wunreachable-code
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
#include "socket.h"
|
||||
|
||||
#include <android-base/errors.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
|
||||
Socket::Socket(cutils_socket_t sock) : sock_(sock) {}
|
||||
|
@ -77,6 +78,10 @@ ssize_t Socket::ReceiveAll(void* data, size_t length, int timeout_ms) {
|
|||
return total;
|
||||
}
|
||||
|
||||
int Socket::GetLocalPort() {
|
||||
return socket_get_local_port(sock_);
|
||||
}
|
||||
|
||||
// Implements the Socket interface for UDP.
|
||||
class UdpSocket : public Socket {
|
||||
public:
|
||||
|
@ -210,3 +215,12 @@ std::unique_ptr<Socket> Socket::NewServer(Protocol protocol, int port) {
|
|||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string Socket::GetErrorMessage() {
|
||||
#if defined(_WIN32)
|
||||
DWORD error_code = WSAGetLastError();
|
||||
#else
|
||||
int error_code = errno;
|
||||
#endif
|
||||
return android::base::SystemErrorCodeToString(error_code);
|
||||
}
|
||||
|
|
|
@ -44,6 +44,10 @@ class Socket {
|
|||
public:
|
||||
enum class Protocol { kTcp, kUdp };
|
||||
|
||||
// Returns the socket error message. This must be called immediately after a socket failure
|
||||
// before any other system calls are made.
|
||||
static std::string GetErrorMessage();
|
||||
|
||||
// Creates a new client connection. Clients are connected to a specific hostname/port and can
|
||||
// only send to that destination.
|
||||
// On failure, |error| is filled (if non-null) and nullptr is returned.
|
||||
|
@ -78,6 +82,9 @@ class Socket {
|
|||
// connected to the client on success, nullptr on failure.
|
||||
virtual std::unique_ptr<Socket> Accept() { return nullptr; }
|
||||
|
||||
// Returns the local port the Socket is bound to or -1 on error.
|
||||
int GetLocalPort();
|
||||
|
||||
protected:
|
||||
// Protected constructor to force factory function use.
|
||||
Socket(cutils_socket_t sock);
|
||||
|
|
132
fastboot/socket_mock.cpp
Normal file
132
fastboot/socket_mock.cpp
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * 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 THE COPYRIGHT HOLDERS AND 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 THE
|
||||
* COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
#include "socket_mock.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
SocketMock::SocketMock() : Socket(INVALID_SOCKET) {}
|
||||
|
||||
SocketMock::~SocketMock() {
|
||||
if (!events_.empty()) {
|
||||
ADD_FAILURE() << events_.size() << " event(s) were not handled";
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t SocketMock::Send(const void* data, size_t length) {
|
||||
if (events_.empty()) {
|
||||
ADD_FAILURE() << "Send() was called when no message was expected";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (events_.front().type != EventType::kSend) {
|
||||
ADD_FAILURE() << "Send() was called out-of-order";
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string message(reinterpret_cast<const char*>(data), length);
|
||||
if (events_.front().message != message) {
|
||||
ADD_FAILURE() << "Send() expected " << events_.front().message << ", but got " << message;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t return_value = events_.front().return_value;
|
||||
events_.pop();
|
||||
return return_value;
|
||||
}
|
||||
|
||||
ssize_t SocketMock::Receive(void* data, size_t length, int /*timeout_ms*/) {
|
||||
if (events_.empty()) {
|
||||
ADD_FAILURE() << "Receive() was called when no message was ready";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (events_.front().type != EventType::kReceive) {
|
||||
ADD_FAILURE() << "Receive() was called out-of-order";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (events_.front().return_value > static_cast<ssize_t>(length)) {
|
||||
ADD_FAILURE() << "Receive(): not enough bytes (" << length << ") for "
|
||||
<< events_.front().message;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t return_value = events_.front().return_value;
|
||||
if (return_value > 0) {
|
||||
memcpy(data, events_.front().message.data(), return_value);
|
||||
}
|
||||
events_.pop();
|
||||
return return_value;
|
||||
}
|
||||
|
||||
int SocketMock::Close() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<Socket> SocketMock::Accept() {
|
||||
if (events_.empty()) {
|
||||
ADD_FAILURE() << "Accept() was called when no socket was ready";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (events_.front().type != EventType::kAccept) {
|
||||
ADD_FAILURE() << "Accept() was called out-of-order";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Socket> sock = std::move(events_.front().sock);
|
||||
events_.pop();
|
||||
return sock;
|
||||
}
|
||||
|
||||
void SocketMock::ExpectSend(std::string message) {
|
||||
ssize_t return_value = message.length();
|
||||
events_.push(Event(EventType::kSend, std::move(message), return_value, nullptr));
|
||||
}
|
||||
|
||||
void SocketMock::ExpectSendFailure(std::string message) {
|
||||
events_.push(Event(EventType::kSend, std::move(message), -1, nullptr));
|
||||
}
|
||||
|
||||
void SocketMock::AddReceive(std::string message) {
|
||||
ssize_t return_value = message.length();
|
||||
events_.push(Event(EventType::kReceive, std::move(message), return_value, nullptr));
|
||||
}
|
||||
|
||||
void SocketMock::AddReceiveFailure() {
|
||||
events_.push(Event(EventType::kReceive, "", -1, nullptr));
|
||||
}
|
||||
|
||||
void SocketMock::AddAccept(std::unique_ptr<Socket> sock) {
|
||||
events_.push(Event(EventType::kAccept, "", 0, std::move(sock)));
|
||||
}
|
||||
|
||||
SocketMock::Event::Event(EventType _type, std::string _message, ssize_t _return_value,
|
||||
std::unique_ptr<Socket> _sock)
|
||||
: type(_type), message(_message), return_value(_return_value), sock(std::move(_sock)) {}
|
97
fastboot/socket_mock.h
Normal file
97
fastboot/socket_mock.h
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * 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 THE COPYRIGHT HOLDERS AND 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 THE
|
||||
* COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
#ifndef SOCKET_MOCK_H_
|
||||
#define SOCKET_MOCK_H_
|
||||
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
|
||||
#include <android-base/macros.h>
|
||||
|
||||
#include "socket.h"
|
||||
|
||||
// A mock Socket implementation to be used for testing. Tests can set expectations for messages
|
||||
// to be sent and provide messages to be received in order to verify protocol behavior.
|
||||
//
|
||||
// Example: testing sending "foo" and receiving "bar".
|
||||
// SocketMock mock;
|
||||
// mock.ExpectSend("foo");
|
||||
// mock.AddReceive("bar");
|
||||
// EXPECT_TRUE(DoFooBar(&mock));
|
||||
//
|
||||
// Example: testing sending "foo" and expecting "bar", but receiving "baz" instead.
|
||||
// SocketMock mock;
|
||||
// mock.ExpectSend("foo");
|
||||
// mock.AddReceive("baz");
|
||||
// EXPECT_FALSE(DoFooBar(&mock));
|
||||
class SocketMock : public Socket {
|
||||
public:
|
||||
SocketMock();
|
||||
~SocketMock() override;
|
||||
|
||||
ssize_t Send(const void* data, size_t length) override;
|
||||
ssize_t Receive(void* data, size_t length, int timeout_ms) override;
|
||||
int Close() override;
|
||||
virtual std::unique_ptr<Socket> Accept();
|
||||
|
||||
// Adds an expectation for Send().
|
||||
void ExpectSend(std::string message);
|
||||
|
||||
// Adds an expectation for Send() that returns -1.
|
||||
void ExpectSendFailure(std::string message);
|
||||
|
||||
// Adds data to provide for Receive().
|
||||
void AddReceive(std::string message);
|
||||
|
||||
// Adds a Receive() failure.
|
||||
void AddReceiveFailure();
|
||||
|
||||
// Adds a Socket to return from Accept().
|
||||
void AddAccept(std::unique_ptr<Socket> sock);
|
||||
|
||||
private:
|
||||
enum class EventType { kSend, kReceive, kAccept };
|
||||
|
||||
struct Event {
|
||||
Event(EventType _type, std::string _message, ssize_t _return_value,
|
||||
std::unique_ptr<Socket> _sock);
|
||||
|
||||
EventType type;
|
||||
std::string message;
|
||||
ssize_t return_value;
|
||||
std::unique_ptr<Socket> sock;
|
||||
};
|
||||
|
||||
std::queue<Event> events_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SocketMock);
|
||||
};
|
||||
|
||||
#endif // SOCKET_MOCK_H_
|
|
@ -14,33 +14,31 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Tests UDP functionality using loopback connections. Requires that kTestPort is available
|
||||
// for loopback communication on the host. These tests also assume that no UDP packets are lost,
|
||||
// which should be the case for loopback communication, but is not guaranteed.
|
||||
// Tests socket functionality using loopback connections. The UDP tests assume that no packets are
|
||||
// lost, which should be the case for loopback communication, but is not guaranteed.
|
||||
//
|
||||
// Also tests our SocketMock class to make sure it works as expected and reports errors properly
|
||||
// if the mock expectations aren't met during a test.
|
||||
|
||||
#include "socket.h"
|
||||
#include "socket_mock.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gtest/gtest-spi.h>
|
||||
|
||||
enum {
|
||||
// This port must be available for loopback communication.
|
||||
kTestPort = 54321,
|
||||
|
||||
// Don't wait forever in a unit test.
|
||||
kTestTimeoutMs = 3000,
|
||||
};
|
||||
enum { kTestTimeoutMs = 3000 };
|
||||
|
||||
// Creates connected sockets |server| and |client|. Returns true on success.
|
||||
bool MakeConnectedSockets(Socket::Protocol protocol, std::unique_ptr<Socket>* server,
|
||||
std::unique_ptr<Socket>* client, const std::string hostname = "localhost",
|
||||
int port = kTestPort) {
|
||||
*server = Socket::NewServer(protocol, port);
|
||||
std::unique_ptr<Socket>* client,
|
||||
const std::string hostname = "localhost") {
|
||||
*server = Socket::NewServer(protocol, 0);
|
||||
if (*server == nullptr) {
|
||||
ADD_FAILURE() << "Failed to create server.";
|
||||
return false;
|
||||
}
|
||||
|
||||
*client = Socket::NewClient(protocol, hostname, port, nullptr);
|
||||
*client = Socket::NewClient(protocol, hostname, (*server)->GetLocalPort(), nullptr);
|
||||
if (*client == nullptr) {
|
||||
ADD_FAILURE() << "Failed to create client.";
|
||||
return false;
|
||||
|
@ -124,3 +122,86 @@ TEST(SocketTest, TestUdpReceiveOverflow) {
|
|||
EXPECT_EQ(-1, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SocketMockTest, TestSendSuccess) {
|
||||
SocketMock mock;
|
||||
|
||||
mock.ExpectSend("foo");
|
||||
EXPECT_TRUE(SendString(&mock, "foo"));
|
||||
|
||||
mock.ExpectSend("abc");
|
||||
mock.ExpectSend("123");
|
||||
EXPECT_TRUE(SendString(&mock, "abc"));
|
||||
EXPECT_TRUE(SendString(&mock, "123"));
|
||||
}
|
||||
|
||||
TEST(SocketMockTest, TestSendFailure) {
|
||||
SocketMock* mock = new SocketMock;
|
||||
|
||||
EXPECT_NONFATAL_FAILURE(SendString(mock, "foo"), "no message was expected");
|
||||
|
||||
mock->ExpectSend("foo");
|
||||
EXPECT_NONFATAL_FAILURE(SendString(mock, "bar"), "expected foo, but got bar");
|
||||
EXPECT_TRUE(SendString(mock, "foo"));
|
||||
|
||||
mock->AddReceive("foo");
|
||||
EXPECT_NONFATAL_FAILURE(SendString(mock, "foo"), "called out-of-order");
|
||||
EXPECT_TRUE(ReceiveString(mock, "foo"));
|
||||
|
||||
mock->ExpectSend("foo");
|
||||
EXPECT_NONFATAL_FAILURE(delete mock, "1 event(s) were not handled");
|
||||
}
|
||||
|
||||
TEST(SocketMockTest, TestReceiveSuccess) {
|
||||
SocketMock mock;
|
||||
|
||||
mock.AddReceive("foo");
|
||||
EXPECT_TRUE(ReceiveString(&mock, "foo"));
|
||||
|
||||
mock.AddReceive("abc");
|
||||
mock.AddReceive("123");
|
||||
EXPECT_TRUE(ReceiveString(&mock, "abc"));
|
||||
EXPECT_TRUE(ReceiveString(&mock, "123"));
|
||||
}
|
||||
|
||||
TEST(SocketMockTest, TestReceiveFailure) {
|
||||
SocketMock* mock = new SocketMock;
|
||||
|
||||
EXPECT_NONFATAL_FAILURE(ReceiveString(mock, "foo"), "no message was ready");
|
||||
|
||||
mock->ExpectSend("foo");
|
||||
EXPECT_NONFATAL_FAILURE(ReceiveString(mock, "foo"), "called out-of-order");
|
||||
EXPECT_TRUE(SendString(mock, "foo"));
|
||||
|
||||
char c;
|
||||
mock->AddReceive("foo");
|
||||
EXPECT_NONFATAL_FAILURE(mock->Receive(&c, 1, 0), "not enough bytes (1) for foo");
|
||||
EXPECT_TRUE(ReceiveString(mock, "foo"));
|
||||
|
||||
mock->AddReceive("foo");
|
||||
EXPECT_NONFATAL_FAILURE(delete mock, "1 event(s) were not handled");
|
||||
}
|
||||
|
||||
TEST(SocketMockTest, TestAcceptSuccess) {
|
||||
SocketMock mock;
|
||||
|
||||
SocketMock* mock_handler = new SocketMock;
|
||||
mock.AddAccept(std::unique_ptr<SocketMock>(mock_handler));
|
||||
EXPECT_EQ(mock_handler, mock.Accept().get());
|
||||
|
||||
mock.AddAccept(nullptr);
|
||||
EXPECT_EQ(nullptr, mock.Accept().get());
|
||||
}
|
||||
|
||||
TEST(SocketMockTest, TestAcceptFailure) {
|
||||
SocketMock* mock = new SocketMock;
|
||||
|
||||
EXPECT_NONFATAL_FAILURE(mock->Accept(), "no socket was ready");
|
||||
|
||||
mock->ExpectSend("foo");
|
||||
EXPECT_NONFATAL_FAILURE(mock->Accept(), "called out-of-order");
|
||||
EXPECT_TRUE(SendString(mock, "foo"));
|
||||
|
||||
mock->AddAccept(nullptr);
|
||||
EXPECT_NONFATAL_FAILURE(delete mock, "1 event(s) were not handled");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue