From d911528cfb367fac34a5764ad6bce339a12f56d0 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 6 Mar 2016 23:00:42 -0600 Subject: add ADB server/client + tests --- tests/CMakeLists.txt | 12 ++-- tests/adbd-client-test.cpp | 94 ++++++++++++++++++++++++++++ tests/adbd-server.h | 148 +++++++++++++++++++++++++++++++++++++++++++++ tests/glib-fixture.h | 94 +++++++++++++++++++++++++++- 4 files changed, 338 insertions(+), 10 deletions(-) create mode 100644 tests/adbd-client-test.cpp create mode 100644 tests/adbd-server.h (limited to 'tests') diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 59e50bc..706e35b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,19 +3,14 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) include_directories(${GMOCK_INCLUDE_DIRS}) include_directories(${GTEST_INCLUDE_DIRS}) -# build libgtest -#add_library (gtest STATIC -# ${GTEST_SOURCE_DIR}/gtest-all.cc -# ${GTEST_SOURCE_DIR}/gtest_main.cc) -#set_target_properties (gtest PROPERTIES INCLUDE_DIRECTORIES ${INCLUDE_DIRECTORIES} ${GTEST_INCLUDE_DIR}) -#set_target_properties (gtest PROPERTIES COMPILE_FLAGS ${COMPILE_FLAGS} -w) +set(CTEST_ENVIRONMENT "${CTEST_ENVIRONMENT};G_MESSAGES_DEBUG=all") if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") # turn off the warnings that break Google Test set (CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Wno-global-constructors -Wno-weak-vtables -Wno-undef -Wno-c++98-compat-pedantic -Wno-missing-noreturn -Wno-used-but-marked-unused -Wno-padded -Wno-deprecated -Wno-sign-compare -Wno-shift-sign-overflow") endif() -SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g ${CXX_WARNING_ARGS}") +SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g -pthread ${CXX_WARNING_ARGS}") # look for headers in our src dir, and also in the directories where we autogenerate files... include_directories (${CMAKE_SOURCE_DIR}/src) @@ -26,10 +21,13 @@ function(add_test_by_name name) set (TEST_NAME ${name}) add_executable (${TEST_NAME} ${TEST_NAME}.cpp) add_test (${TEST_NAME} ${TEST_NAME}) + set_property(TEST ${TEST_NAME} PROPERTY ENVIRONMENT "${CTEST_ENVIRONMENT}") + add_dependencies (${TEST_NAME} libindicatordisplayservice) target_link_libraries (${TEST_NAME} indicatordisplayservice ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) endfunction() add_test_by_name(rotation-lock-test) +add_test_by_name(adbd-client-test) add_test (cppcheck cppcheck --enable=all -USCHEMA_DIR --error-exitcode=2 --inline-suppr -I${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/tests) diff --git a/tests/adbd-client-test.cpp b/tests/adbd-client-test.cpp new file mode 100644 index 0000000..4fa16a8 --- /dev/null +++ b/tests/adbd-client-test.cpp @@ -0,0 +1,94 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include +#include + +#include + +#include // mkdtemp + +class AdbdClientFixture: public TestDBusFixture +{ +private: + typedef TestDBusFixture super; + +protected: + + std::string m_tmpdir; + + void SetUp() + { + super::SetUp(); + + char tmpl[] {"adb-client-test-XXXXXX"}; + m_tmpdir = mkdtemp(tmpl); + g_message("using tmpdir '%s'", m_tmpdir.c_str()); + } + + void TearDown() + { + g_rmdir(m_tmpdir.c_str()); + + super::TearDown(); + } +}; + + +TEST_F(AdbdClientFixture, SocketPlumbing) +{ + const auto socket_path = m_tmpdir + "/test-socket-plumbing"; + g_message("socket_path is %s", socket_path.c_str()); + + struct { + const std::string request; + const std::string expected_pk; + AdbdClient::PKResponse response; + const std::string expected_response; + } tests[] = { + { "PKHelloWorld", "HelloWorld", AdbdClient::PKResponse::ALLOW, "OK" }, + { "PKHelloWorld", "HelloWorld", AdbdClient::PKResponse::DENY, "NO" }, + { "PKFooBar", "FooBar", AdbdClient::PKResponse::ALLOW, "OK" }, + { "PK", "", AdbdClient::PKResponse::DENY, "NO" } + }; + + for (const auto& test : tests) + { + // make the AdbdClient and start listening for Requests + std::string pk; + auto adbd_client = std::make_shared(socket_path); + adbd_client->on_pk_request().connect([&pk, test](const AdbdClient::PKRequest& req){ + g_message("in on_pk_request with %s", req.public_key.c_str()); + pk = req.public_key; + req.respond(test.response); + }); + + // fire up a mock ADB server with a preloaded request, and wait for a response + auto adbd_server = std::make_shared(socket_path, std::vector{test.request}); + wait_for([adbd_server](){return !adbd_server->m_responses.empty();}, 2000); + EXPECT_EQ(test.expected_pk, pk); + ASSERT_EQ(1, adbd_server->m_responses.size()); + EXPECT_EQ(test.expected_response, adbd_server->m_responses.front()); + + // cleanup + adbd_client.reset(); + adbd_server.reset(); + g_unlink(socket_path.c_str()); + } +} diff --git a/tests/adbd-server.h b/tests/adbd-server.h new file mode 100644 index 0000000..740faaa --- /dev/null +++ b/tests/adbd-server.h @@ -0,0 +1,148 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include +#include + +#include +#include +#include + + +/** + * A Mock ADBD server. + * + * Binds to a local domain socket, sends public key requests across it, + * and reads back the client's responses. + */ +class GAdbdServer +{ +public: + + GAdbdServer(const std::string& socket_path, + const std::vector& requests): + m_requests{requests}, + m_socket_path{socket_path}, + m_cancellable{g_cancellable_new()}, + m_worker_thread{&GAdbdServer::worker_func, this} + { + } + + ~GAdbdServer() + { + // tell the worker thread to stop whatever it's doing and exit. + g_cancellable_cancel(m_cancellable); + m_worker_thread.join(); + g_clear_object(&m_cancellable); + } + + const std::vector m_requests; + std::vector m_responses; + +private: + + void worker_func() // runs in worker thread + { + auto server_socket = create_server_socket(m_socket_path); + auto requests = m_requests; + + GError* error {}; + g_socket_listen (server_socket, &error); + g_assert_no_error (error); + + while (!g_cancellable_is_cancelled(m_cancellable) && !requests.empty()) + { + // wait for a client connection + g_message("GAdbdServer::Impl::worker_func() calling g_socket_accept()"); + auto client_socket = g_socket_accept(server_socket, m_cancellable, &error); + if (error != nullptr) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("GAdbdServer: Error accepting socket connection: %s", error->message); + g_clear_error(&error); + break; + } + + // pop the next request off the stack + auto request = requests.front(); + requests.erase(requests.begin()); + + // send the request + g_message("GAdbdServer::Impl::worker_func() sending req [%s]", request.c_str()); + g_socket_send(client_socket, + request.c_str(), + request.size(), + m_cancellable, + &error); + if (error != nullptr) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("GAdbdServer: Error sending request: %s", error->message); + g_clear_error(&error); + g_clear_object(&client_socket); + break; + } + + // read the response + g_message("GAdbdServer::Impl::worker_func() reading response"); + char buf[4096]; + const auto n_bytes = g_socket_receive(client_socket, + buf, + sizeof(buf), + m_cancellable, + &error); + if (error != nullptr) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("GAdbdServer: Error reading response: %s", error->message); + g_clear_error(&error); + g_clear_object(&client_socket); + break; + } + const std::string response(buf, std::string::size_type(n_bytes)); + g_message("server got response: %s", response.c_str()); + m_responses.push_back(response); + + // cleanup + g_clear_object(&client_socket); + } + + g_clear_object(&server_socket); + } + + // bind to a local domain socket + static GSocket* create_server_socket(const std::string& socket_path) + { + GError* error {}; + auto socket = g_socket_new(G_SOCKET_FAMILY_UNIX, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + g_assert_no_error(error); + auto address = g_unix_socket_address_new (socket_path.c_str()); + g_socket_bind (socket, address, false, &error); + g_assert_no_error (error); + g_clear_object (&address); + + return socket; + } + + const std::string m_socket_path; + GCancellable* m_cancellable = nullptr; + std::thread m_worker_thread; +}; + + diff --git a/tests/glib-fixture.h b/tests/glib-fixture.h index 41ac6e8..ccdeccd 100644 --- a/tests/glib-fixture.h +++ b/tests/glib-fixture.h @@ -19,7 +19,9 @@ #pragma once +#include // std::function #include +#include // std::shared_ptr #include #include @@ -74,7 +76,7 @@ class GlibFixture : public ::testing::Test static gboolean wait_for_signal__timeout(gpointer name) { - g_error("%s: timed out waiting for signal '%s'", G_STRLOC, (char*)name); + g_error("%s: timed out waiting for signal '%s'", G_STRLOC, static_cast(name)); return G_SOURCE_REMOVE; } @@ -88,7 +90,7 @@ class GlibFixture : public ::testing::Test protected: /* convenience func to loop while waiting for a GObject's signal */ - void wait_for_signal(gpointer o, const gchar * signal, const int timeout_seconds=5) + void wait_for_signal(gpointer o, const gchar * signal, guint timeout_seconds=5) { // wait for the signal or for timeout, whichever comes first const auto handler_id = g_signal_connect_swapped(o, signal, @@ -103,13 +105,99 @@ class GlibFixture : public ::testing::Test } /* convenience func to loop for N msec */ - void wait_msec(int msec=50) + void wait_msec(guint msec=50) { const auto id = g_timeout_add(msec, wait_msec__timeout, loop); g_main_loop_run(loop); g_source_remove(id); } + bool wait_for(std::function test_function, guint timeout_msec=1000) + { + auto timer = std::shared_ptr(g_timer_new(), [](GTimer* t){g_timer_destroy(t);}); + const auto timeout_sec = timeout_msec / 1000.0; + for (;;) { + if (test_function()) + return true; + //g_message("%f ... %f", g_timer_elapsed(timer.get(), nullptr), timeout_sec); + if (g_timer_elapsed(timer.get(), nullptr) >= timeout_sec) + return false; + wait_msec(); + } + } + + bool wait_for_name_owned(GDBusConnection* connection, + const gchar* name, + guint timeout_msec=1000, + GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) + { + struct Data { + GMainLoop* loop = nullptr; + bool owned = false; + }; + Data data; + + auto on_name_appeared = [](GDBusConnection* /*connection*/, + const gchar* /*name_*/, + const gchar* name_owner, + gpointer gdata) + { + if (name_owner == nullptr) + return; + auto tmp = static_cast(gdata); + tmp->owned = true; + g_main_loop_quit(tmp->loop); + }; + + const auto timeout_id = g_timeout_add(timeout_msec, wait_msec__timeout, loop); + data.loop = loop; + const auto watch_id = g_bus_watch_name_on_connection(connection, + name, + flags, + on_name_appeared, + nullptr, /* name_vanished */ + &data, + nullptr); /* user_data_free_func */ + g_main_loop_run(loop); + + g_bus_unwatch_name(watch_id); + g_source_remove(timeout_id); + + return data.owned; + } + + void EXPECT_NAME_OWNED_EVENTUALLY(GDBusConnection* connection, + const gchar* name, + guint timeout_msec=1000, + GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) + { + EXPECT_TRUE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name; + } + + void EXPECT_NAME_NOT_OWNED_EVENTUALLY(GDBusConnection* connection, + const gchar* name, + guint timeout_msec=1000, + GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) + { + EXPECT_FALSE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name; + } + + void ASSERT_NAME_OWNED_EVENTUALLY(GDBusConnection* connection, + const gchar* name, + guint timeout_msec=1000, + GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) + { + ASSERT_TRUE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name; + } + + void ASSERT_NAME_NOT_OWNED_EVENTUALLY(GDBusConnection* connection, + const gchar* name, + guint timeout_msec=1000, + GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) + { + ASSERT_FALSE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name; + } + GMainLoop * loop; }; -- cgit v1.2.3