From c3a466960ff19bbf9b28b4d069dd0b872d4165d8 Mon Sep 17 00:00:00 2001 From: David Pursell Date: Fri, 29 Jan 2016 08:10:50 -0800 Subject: [PATCH] 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 --- fastboot/Android.mk | 6 +- fastboot/socket.cpp | 14 +++++ fastboot/socket.h | 7 +++ fastboot/socket_mock.cpp | 132 +++++++++++++++++++++++++++++++++++++++ fastboot/socket_mock.h | 97 ++++++++++++++++++++++++++++ fastboot/socket_test.cpp | 109 +++++++++++++++++++++++++++----- 6 files changed, 350 insertions(+), 15 deletions(-) create mode 100644 fastboot/socket_mock.cpp create mode 100644 fastboot/socket_mock.h diff --git a/fastboot/Android.mk b/fastboot/Android.mk index fcec5b104..65f4e01fa 100644 --- a/fastboot/Android.mk +++ b/fastboot/Android.mk @@ -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 diff --git a/fastboot/socket.cpp b/fastboot/socket.cpp index d41f1fe6f..0a3ddfa2f 100644 --- a/fastboot/socket.cpp +++ b/fastboot/socket.cpp @@ -28,6 +28,7 @@ #include "socket.h" +#include #include 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::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); +} diff --git a/fastboot/socket.h b/fastboot/socket.h index 3e66c274f..a7481dba4 100644 --- a/fastboot/socket.h +++ b/fastboot/socket.h @@ -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 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); diff --git a/fastboot/socket_mock.cpp b/fastboot/socket_mock.cpp new file mode 100644 index 000000000..8fea55466 --- /dev/null +++ b/fastboot/socket_mock.cpp @@ -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 + +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(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(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 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 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 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 _sock) + : type(_type), message(_message), return_value(_return_value), sock(std::move(_sock)) {} diff --git a/fastboot/socket_mock.h b/fastboot/socket_mock.h new file mode 100644 index 000000000..3e62b330e --- /dev/null +++ b/fastboot/socket_mock.h @@ -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 +#include +#include + +#include + +#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 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 sock); + + private: + enum class EventType { kSend, kReceive, kAccept }; + + struct Event { + Event(EventType _type, std::string _message, ssize_t _return_value, + std::unique_ptr _sock); + + EventType type; + std::string message; + ssize_t return_value; + std::unique_ptr sock; + }; + + std::queue events_; + + DISALLOW_COPY_AND_ASSIGN(SocketMock); +}; + +#endif // SOCKET_MOCK_H_ diff --git a/fastboot/socket_test.cpp b/fastboot/socket_test.cpp index 1fd9d7c22..7bfe96714 100644 --- a/fastboot/socket_test.cpp +++ b/fastboot/socket_test.cpp @@ -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 +#include -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* server, - std::unique_ptr* client, const std::string hostname = "localhost", - int port = kTestPort) { - *server = Socket::NewServer(protocol, port); + std::unique_ptr* 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(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"); +}