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:
David Pursell 2016-01-29 08:10:50 -08:00
parent d95ecfc432
commit c3a466960f
6 changed files with 350 additions and 15 deletions

View file

@ -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

View file

@ -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);
}

View file

@ -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
View 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
View 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_

View file

@ -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");
}