From f8a5d99b5ac03b5b759f67b33ed2c989fc0d0ceb Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Thu, 10 Mar 2016 12:13:20 -0600 Subject: cmake and test directory cleanup --- CMakeLists.txt | 76 ++++++++------ src/CMakeLists.txt | 61 +++++------ tests/CMakeLists.txt | 50 +++------ tests/adbd-client-test.cpp | 97 ----------------- tests/adbd-server.h | 148 -------------------------- tests/dbus-types.h | 47 --------- tests/glib-fixture.h | 203 ----------------------------------- tests/rotation-lock-test.cpp | 61 ----------- tests/test-dbus-fixture.h | 106 ------------------- tests/unit/adbd-client-test.cpp | 97 +++++++++++++++++ tests/unit/rotation-lock-test.cpp | 61 +++++++++++ tests/unit/usb-snap-test.cpp | 216 ++++++++++++++++++++++++++++++++++++++ tests/usb-snap-test.cpp | 216 -------------------------------------- tests/utils/adbd-server.h | 148 ++++++++++++++++++++++++++ tests/utils/dbus-types.h | 47 +++++++++ tests/utils/glib-fixture.h | 203 +++++++++++++++++++++++++++++++++++ tests/utils/qmain.cpp | 60 +++++++++++ tests/utils/test-dbus-fixture.h | 106 +++++++++++++++++++ 18 files changed, 1022 insertions(+), 981 deletions(-) delete mode 100644 tests/adbd-client-test.cpp delete mode 100644 tests/adbd-server.h delete mode 100644 tests/dbus-types.h delete mode 100644 tests/glib-fixture.h delete mode 100644 tests/rotation-lock-test.cpp delete mode 100644 tests/test-dbus-fixture.h create mode 100644 tests/unit/adbd-client-test.cpp create mode 100644 tests/unit/rotation-lock-test.cpp create mode 100644 tests/unit/usb-snap-test.cpp delete mode 100644 tests/usb-snap-test.cpp create mode 100644 tests/utils/adbd-server.h create mode 100644 tests/utils/dbus-types.h create mode 100644 tests/utils/glib-fixture.h create mode 100644 tests/utils/qmain.cpp create mode 100644 tests/utils/test-dbus-fixture.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b8ef65..5110c9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,30 +1,31 @@ project(indicator-display LANGUAGES C CXX) cmake_minimum_required(VERSION 2.8.9) -list (APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) +list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) -set (PROJECT_VERSION "14.10.0") -set (PACKAGE ${CMAKE_PROJECT_NAME}) -set (GETTEXT_PACKAGE indicator-display) -add_definitions (-DGETTEXT_PACKAGE="${GETTEXT_PACKAGE}" - -DGNOMELOCALEDIR="${CMAKE_INSTALL_FULL_LOCALEDIR}") +set(PACKAGE ${CMAKE_PROJECT_NAME}) +set(GETTEXT_PACKAGE indicator-display) +add_definitions( + -DGETTEXT_PACKAGE="${GETTEXT_PACKAGE}" + -DGNOMELOCALEDIR="${CMAKE_INSTALL_FULL_LOCALEDIR}" +) -set (SERVICE_LIB ${PACKAGE}) -set (SERVICE_EXEC "${PACKAGE}-service") +set(SERVICE_LIB ${PACKAGE}) +set(SERVICE_EXEC "${PACKAGE}-service") -option (enable_tests "Build the package's automatic tests." ON) -option (enable_lcov "Generate lcov code coverage reports." ON) +option(enable_tests "Build the package's automatic tests." ON) +option(enable_lcov "Generate lcov code coverage reports." ON) ## ## GNU standard paths ## -include (GNUInstallDirs) -if (EXISTS "/etc/debian_version") # Workaround for libexecdir on debian - set (CMAKE_INSTALL_LIBEXECDIR "${CMAKE_INSTALL_LIBDIR}") - set (CMAKE_INSTALL_FULL_LIBEXECDIR "${CMAKE_INSTALL_FULL_LIBDIR}") -endif () -set (CMAKE_INSTALL_PKGLIBEXECDIR "${CMAKE_INSTALL_LIBEXECDIR}/${CMAKE_PROJECT_NAME}") -set (CMAKE_INSTALL_FULL_PKGLIBEXECDIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/${CMAKE_PROJECT_NAME}") +include(GNUInstallDirs) +if(EXISTS "/etc/debian_version") # Workaround for libexecdir on debian + set(CMAKE_INSTALL_LIBEXECDIR "${CMAKE_INSTALL_LIBDIR}") + set(CMAKE_INSTALL_FULL_LIBEXECDIR "${CMAKE_INSTALL_FULL_LIBDIR}") +endif() +set(CMAKE_INSTALL_PKGLIBEXECDIR "${CMAKE_INSTALL_LIBEXECDIR}/${CMAKE_PROJECT_NAME}") +set(CMAKE_INSTALL_FULL_PKGLIBEXECDIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/${CMAKE_PROJECT_NAME}") ## ## Check for prerequisites @@ -32,12 +33,15 @@ set (CMAKE_INSTALL_FULL_PKGLIBEXECDIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/${CMAKE_ set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) -find_package (PkgConfig REQUIRED) +find_package(PkgConfig REQUIRED) -pkg_check_modules (SERVICE_DEPS REQUIRED - gio-unix-2.0>=2.36 - glib-2.0>=2.36) -include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS}) +pkg_check_modules(SERVICE_DEPS REQUIRED + gio-unix-2.0>=2.36 + glib-2.0>=2.36 +) +include_directories (SYSTEM + ${SERVICE_DEPS_INCLUDE_DIRS} +) ## ## @@ -47,24 +51,26 @@ set (CMAKE_INCLUDE_CURRENT_DIR OFF) include_directories (${CMAKE_CURRENT_SOURCE_DIR}) # set the compiler warnings -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set (CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Weverything -Wno-c++98-compat -Wno-padded") +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Weverything -Wno-c++98-compat -Wno-padded") else() - set (CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Wall -Wextra -Wpedantic") + set(CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Wall -Wextra -Wpedantic") endif() -set (CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Wno-missing-field-initializers") # GActionEntry +set(CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Wno-missing-field-initializers") # GActionEntry + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -fPIC -g") # testing & coverage -if (${enable_tests}) - enable_testing () - if (${enable_lcov}) +if(${enable_tests}) + enable_testing() + if(${enable_lcov}) include(GCov) - endif () -endif () + endif() +endif() -add_subdirectory (src) -add_subdirectory (data) -add_subdirectory (po) +add_subdirectory(src) +add_subdirectory(data) +add_subdirectory(po) if (${enable_tests}) - add_subdirectory (tests) + add_subdirectory(tests) endif () diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ff385d9..6cfda91 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,32 +1,33 @@ -set (SERVICE_LIB "indicatordisplayservice") -set (SERVICE_EXEC "indicator-display-service") -add_definitions (-DG_LOG_DOMAIN="${CMAKE_PROJECT_NAME}") - -# handwritten source code... -set (SERVICE_LIB_HANDWRITTEN_SOURCES - adbd-client.cpp - exporter.cpp - indicator.cpp - rotation-lock.cpp - usb-snap.cpp) - -add_library (${SERVICE_LIB} STATIC - ${SERVICE_LIB_HANDWRITTEN_SOURCES}) - -# add the bin dir to the include path so that -# the compiler can find the generated header files -include_directories (${CMAKE_CURRENT_BINARY_DIR}) - -link_directories (${SERVICE_DEPS_LIBRARY_DIRS}) - -set (SERVICE_EXEC_HANDWRITTEN_SOURCES main.cpp) -add_executable (${SERVICE_EXEC} ${SERVICE_EXEC_HANDWRITTEN_SOURCES}) -target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} Threads::Threads ${GCOV_LIBS}) -install (TARGETS ${SERVICE_EXEC} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}) - -# add warnings/coverage info on handwritten files -# but not the generated ones... -set_property (SOURCE ${SERVICE_LIB_HANDWRITTEN_SOURCES} ${SERVICE_EXEC_HANDWRITTEN_SOURCES} - APPEND_STRING PROPERTY COMPILE_FLAGS " -std=c++11 -g ${CXX_WARNING_ARGS} ${GCOV_FLAGS}") +add_definitions(-DG_LOG_DOMAIN="${CMAKE_PROJECT_NAME}") + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_WARNING_ARGS} ${GCOV_FLAGS}") + +add_library( + ${SERVICE_LIB} + STATIC + adbd-client.cpp + exporter.cpp + indicator.cpp + rotation-lock.cpp + usb-snap.cpp +) + +add_executable( + ${SERVICE_EXEC} + main.cpp +) + +target_link_libraries(${SERVICE_EXEC} + ${SERVICE_LIB} + ${SERVICE_DEPS_LIBRARIES} + Threads::Threads + ${GCOV_LIBS} +) + +install( + TARGETS + ${SERVICE_EXEC} + RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_PKGLIBEXECDIR} +) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1fbe407..205792e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,57 +1,31 @@ - set(CMAKE_AUTOMOC ON) - find_package(GMock REQUIRED) find_package(Qt5Core REQUIRED) find_package(Qt5Test REQUIRED) find_package(Qt5DBus COMPONENTS Qt5DBusMacros REQUIRED) pkg_check_modules(TEST_DEPS - libqtdbustest-1 REQUIRED - libqtdbusmock-1 REQUIRED + libqtdbustest-1 REQUIRED + libqtdbusmock-1 REQUIRED ) -include_directories( - ${CMAKE_SOURCE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_CURRENT_SOURCE_DIR} -) include_directories(SYSTEM - ${DBUSTEST_INCLUDE_DIRS} - ${TEST_DEPS_INCLUDE_DIRS} - ${GTEST_INCLUDE_DIRS} - ${GMOCK_INCLUDE_DIRS} + ${DBUSTEST_INCLUDE_DIRS} + ${TEST_DEPS_INCLUDE_DIRS} + ${GTEST_INCLUDE_DIRS} + ${GMOCK_INCLUDE_DIRS} ) 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") +# turn off the warnings that break Google Test +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Wno-global-constructors -Wno-weak-vtables") endif() -SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -std=c++11 -g ${CXX_WARNING_ARGS}") - -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} indicatordisplayservice) - target_link_libraries (${TEST_NAME} indicatordisplayservice ${SERVICE_DEPS_LIBRARIES} ${TEST_DEPS_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) -endfunction() -add_test_by_name(rotation-lock-test) -add_test_by_name(adbd-client-test) - -function(add_qt_test_by_name name) - set (TEST_NAME ${name}) - add_executable (${TEST_NAME} ${TEST_NAME}.cpp qmain.cpp) - add_test (${TEST_NAME} ${TEST_NAME}) - set_property(TEST ${TEST_NAME} PROPERTY ENVIRONMENT "${CTEST_ENVIRONMENT}") - add_dependencies (${TEST_NAME} indicatordisplayservice) - target_link_libraries (${TEST_NAME} indicatordisplayservice ${SERVICE_DEPS_LIBRARIES} Qt5::Core Qt5::Test Qt5::DBus ${TEST_DEPS_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) -endfunction() -add_qt_test_by_name(usb-snap-test) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_WARNING_ARGS}") 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) +add_subdirectory(unit) +add_subdirectory(utils) diff --git a/tests/adbd-client-test.cpp b/tests/adbd-client-test.cpp deleted file mode 100644 index ae0378b..0000000 --- a/tests/adbd-client-test.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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) -{ - 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" } - }; - - const auto main_thread = g_thread_self(); - - const auto socket_path = m_tmpdir + "/test-socket-plumbing"; - g_message("socket_path is %s", socket_path.c_str()); - - for (const auto& test : tests) - { - // start an AdbdClient that listens for PKRequests - std::string pk; - auto adbd_client = std::make_shared(socket_path); - adbd_client->on_pk_request().connect([&pk, main_thread, test](const AdbdClient::PKRequest& req){ - EXPECT_EQ(main_thread, g_thread_self()); - g_message("in on_pk_request with %s", req.public_key.c_str()); - pk = req.public_key; - req.respond(test.response); - }); - - // start a mock AdbdServer with to fire test key requests 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 deleted file mode 100644 index 740faaa..0000000 --- a/tests/adbd-server.h +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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/dbus-types.h b/tests/dbus-types.h deleted file mode 100644 index c2dfb81..0000000 --- a/tests/dbus-types.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2013-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 . - * - * Author: Pete Woods - */ - -#pragma once - -#include -#include -#include -#include - -typedef QMap QVariantDictMap; -Q_DECLARE_METATYPE(QVariantDictMap) - -typedef QMap QStringMap; -Q_DECLARE_METATYPE(QStringMap) - -namespace DBusTypes -{ - inline void registerMetaTypes() - { - qRegisterMetaType("QVariantDictMap"); - qRegisterMetaType("QStringMap"); - - qDBusRegisterMetaType(); - qDBusRegisterMetaType(); - } - static constexpr char const* NOTIFY_DBUS_NAME = "org.freedesktop.Notifications"; - - static constexpr char const* NOTIFY_DBUS_INTERFACE = "org.freedesktop.Notifications"; - - static constexpr char const* NOTIFY_DBUS_PATH = "/org/freedesktop/Notifications"; -} diff --git a/tests/glib-fixture.h b/tests/glib-fixture.h deleted file mode 100644 index ccdeccd..0000000 --- a/tests/glib-fixture.h +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr - * - * 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 . - */ - -#pragma once - -#include // std::function -#include -#include // std::shared_ptr - -#include -#include -#include - -#include - -#include // setlocale() - -class GlibFixture : public ::testing::Test -{ - public: - - virtual ~GlibFixture() =default; - - protected: - - virtual void SetUp() override - { - setlocale(LC_ALL, "C.UTF-8"); - - loop = g_main_loop_new(nullptr, false); - -#ifdef SCHEMA_DIR - // only use local, temporary settings - g_assert(g_setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, true)); - g_assert(g_setenv("GSETTINGS_BACKEND", "memory", true)); - g_debug("SCHEMA_DIR is %s", SCHEMA_DIR); -#endif - - // fail on unexpected messages from this domain - g_log_set_fatal_mask(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING); - - g_unsetenv("DISPLAY"); - - } - - virtual void TearDown() override - { - g_test_assert_expected_messages (); - - g_clear_pointer(&loop, g_main_loop_unref); - } - - void expectLogMessage (const gchar *domain, GLogLevelFlags level, const gchar *pattern) - { - g_test_expect_message (domain, level, pattern); - } - - private: - - static gboolean - wait_for_signal__timeout(gpointer name) - { - g_error("%s: timed out waiting for signal '%s'", G_STRLOC, static_cast(name)); - return G_SOURCE_REMOVE; - } - - static gboolean - wait_msec__timeout(gpointer loop) - { - g_main_loop_quit(static_cast(loop)); - return G_SOURCE_CONTINUE; - } - - protected: - - /* convenience func to loop while waiting for a GObject's signal */ - 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, - G_CALLBACK(g_main_loop_quit), - loop); - const auto timeout_id = g_timeout_add_seconds(timeout_seconds, - wait_for_signal__timeout, - loop); - g_main_loop_run(loop); - g_source_remove(timeout_id); - g_signal_handler_disconnect(o, handler_id); - } - - /* convenience func to loop for N msec */ - 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; -}; - diff --git a/tests/rotation-lock-test.cpp b/tests/rotation-lock-test.cpp deleted file mode 100644 index b8661cc..0000000 --- a/tests/rotation-lock-test.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2014 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 "test-dbus-fixture.h" - -#include - -class RotationLockFixture: public TestDBusFixture -{ -private: - typedef TestDBusFixture super; - -protected: - - void SetUp() - { - super::SetUp(); - } - - void TearDown() - { - super::TearDown(); - } -}; - -/*** -**** -***/ - -TEST_F(RotationLockFixture, CheckIndicator) -{ - RotationLockIndicator indicator; - - ASSERT_STREQ("rotation_lock", indicator.name()); - auto actions = indicator.action_group(); - ASSERT_TRUE(actions != nullptr); - ASSERT_TRUE(g_action_group_has_action(G_ACTION_GROUP(actions), "rotation-lock")); - - std::vector> profiles = indicator.profiles(); - ASSERT_EQ(1, profiles.size()); - std::shared_ptr phone = profiles[0]; - ASSERT_EQ(std::string("phone"), phone->name()); - ASSERT_FALSE(phone->header()->is_visible); -} - diff --git a/tests/test-dbus-fixture.h b/tests/test-dbus-fixture.h deleted file mode 100644 index 3947e58..0000000 --- a/tests/test-dbus-fixture.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2013 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 - */ - -#pragma once - -#include "glib-fixture.h" - -/*** -**** -***/ - -class TestDBusFixture: public GlibFixture -{ - public: - - TestDBusFixture() =default; - virtual ~TestDBusFixture() =default; - - explicit TestDBusFixture(const std::vector& service_dirs_in): service_dirs(service_dirs_in) {} - - private: - - typedef GlibFixture super; - - static void - on_bus_opened (GObject* /*object*/, GAsyncResult * res, gpointer gself) - { - auto self = static_cast(gself); - - GError * err = 0; - self->system_bus = g_bus_get_finish (res, &err); - g_assert_no_error (err); - - g_main_loop_quit (self->loop); - } - - static void - on_bus_closed (GObject* /*object*/, GAsyncResult * res, gpointer gself) - { - auto self = static_cast(gself); - - GError * err = 0; - g_dbus_connection_close_finish (self->system_bus, res, &err); - g_assert_no_error (err); - - g_main_loop_quit (self->loop); - } - - protected: - - GTestDBus * test_dbus = nullptr; - GDBusConnection * system_bus = nullptr; - const std::vector service_dirs; - - virtual void SetUp() override - { - super::SetUp (); - - // pull up a test dbus - test_dbus = g_test_dbus_new (G_TEST_DBUS_NONE); - for (const auto& dir : service_dirs) - g_test_dbus_add_service_dir (test_dbus, dir.c_str()); - g_test_dbus_up (test_dbus); - const char * address = g_test_dbus_get_bus_address (test_dbus); - g_setenv ("DBUS_SYSTEM_BUS_ADDRESS", address, true); - g_setenv ("DBUS_SESSION_BUS_ADDRESS", address, true); - g_debug ("test_dbus's address is %s", address); - - // wait for the GDBusConnection before returning - g_bus_get (G_BUS_TYPE_SYSTEM, nullptr, on_bus_opened, this); - g_main_loop_run (loop); - } - - virtual void TearDown() override - { - wait_msec(); - - // close the system bus - g_dbus_connection_close(system_bus, nullptr, on_bus_closed, this); - g_main_loop_run(loop); - g_clear_object(&system_bus); - - // tear down the test dbus - g_test_dbus_down(test_dbus); - g_clear_object(&test_dbus); - - super::TearDown(); - } -}; - diff --git a/tests/unit/adbd-client-test.cpp b/tests/unit/adbd-client-test.cpp new file mode 100644 index 0000000..1e28cc9 --- /dev/null +++ b/tests/unit/adbd-client-test.cpp @@ -0,0 +1,97 @@ +/* + * 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) +{ + 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" } + }; + + const auto main_thread = g_thread_self(); + + const auto socket_path = m_tmpdir + "/test-socket-plumbing"; + g_message("socket_path is %s", socket_path.c_str()); + + for (const auto& test : tests) + { + // start an AdbdClient that listens for PKRequests + std::string pk; + auto adbd_client = std::make_shared(socket_path); + adbd_client->on_pk_request().connect([&pk, main_thread, test](const AdbdClient::PKRequest& req){ + EXPECT_EQ(main_thread, g_thread_self()); + g_message("in on_pk_request with %s", req.public_key.c_str()); + pk = req.public_key; + req.respond(test.response); + }); + + // start a mock AdbdServer with to fire test key requests 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/unit/rotation-lock-test.cpp b/tests/unit/rotation-lock-test.cpp new file mode 100644 index 0000000..b9630b5 --- /dev/null +++ b/tests/unit/rotation-lock-test.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2014 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 + +class RotationLockFixture: public TestDBusFixture +{ +private: + typedef TestDBusFixture super; + +protected: + + void SetUp() + { + super::SetUp(); + } + + void TearDown() + { + super::TearDown(); + } +}; + +/*** +**** +***/ + +TEST_F(RotationLockFixture, CheckIndicator) +{ + RotationLockIndicator indicator; + + ASSERT_STREQ("rotation_lock", indicator.name()); + auto actions = indicator.action_group(); + ASSERT_TRUE(actions != nullptr); + ASSERT_TRUE(g_action_group_has_action(G_ACTION_GROUP(actions), "rotation-lock")); + + std::vector> profiles = indicator.profiles(); + ASSERT_EQ(1, profiles.size()); + std::shared_ptr phone = profiles[0]; + ASSERT_EQ(std::string("phone"), phone->name()); + ASSERT_FALSE(phone->header()->is_visible); +} + diff --git a/tests/unit/usb-snap-test.cpp b/tests/unit/usb-snap-test.cpp new file mode 100644 index 0000000..70c9d97 --- /dev/null +++ b/tests/unit/usb-snap-test.cpp @@ -0,0 +1,216 @@ +/* + * 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 + */ + +#define QT_NO_KEYWORDS +#include +#include + +#include + +#include + +#include +#include +#include + +#include + +#include + +using namespace QtDBusTest; +using namespace QtDBusMock; + +inline QString qVariantToString(const QVariant& variant) { + QString output; + QDebug dbg(&output); + dbg << variant; + return output; +} + +inline void PrintTo(const QVariant& variant, std::ostream* os) { + QString output; + QDebug dbg(&output); + dbg << variant; + + *os << "QVariant(" << output.toStdString() << ")"; +} + +inline void PrintTo(const QString& s, std::ostream* os) { + *os << "\"" << s.toStdString() << "\""; +} + +inline void PrintTo(const QStringList& list, std::ostream* os) { + QString output; + QDebug dbg(&output); + dbg << list; + + *os << "QStringList(" << output.toStdString() << ")"; +} + +inline void PrintTo(const QList& list, std::ostream* os) { + QString output; + for (const auto& path: list) + { + output.append("\"" + path.path() + "\","); + } + + *os << "QList(" << output.toStdString() << ")"; +} + +#define WAIT_FOR_SIGNALS(signalSpy, signalsExpected)\ +{\ + while (signalSpy.size() < signalsExpected)\ + {\ + ASSERT_TRUE(signalSpy.wait());\ + }\ + ASSERT_EQ(signalsExpected, signalSpy.size());\ +} + +class UsbSnapFixture: public GlibFixture +{ + using super = GlibFixture; + +public: + + UsbSnapFixture(): + dbusMock{dbusTestRunner} + { + DBusTypes::registerMetaTypes(); + + dbusTestRunner.startServices(); + } + + ~UsbSnapFixture() =default; + +protected: + + bool qDBusArgumentToMap(QVariant const& variant, QVariantMap& map) + { + if (variant.canConvert()) + { + QDBusArgument value(variant.value()); + if (value.currentType() == QDBusArgument::MapType) + { + value >> map; + return true; + } + } + return false; + } + + void SetUp() override + { + super::SetUp(); + + dbusMock.registerNotificationDaemon(); + dbusTestRunner.startServices(); + } + + OrgFreedesktopDBusMockInterface& notificationsMockInterface() + { + return dbusMock.mockInterface("org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + QDBusConnection::SessionBus); + } + + QtDBusTest::DBusTestRunner dbusTestRunner; + QtDBusMock::DBusMock dbusMock; + QtDBusTest::DBusServicePtr indicator; +}; + +TEST_F(UsbSnapFixture, TestRoundTrip) +{ + struct { + const char* fingerprint; + const char* action_to_invoke; + const AdbdClient::PKResponse expected_response; + } tests[] = { + { "Fingerprint", "allow", AdbdClient::PKResponse::ALLOW }, + { "Fingerprint", "deny", AdbdClient::PKResponse::DENY } + }; + + uint32_t next_id = 1; + for(const auto& test : tests) + { + // Minor wart: we don't have a way of getting the fdo notification id + // from dbusmock so instead we copy its (simple) id generation here + const auto id = next_id++; + + QSignalSpy notificationsSpy( + ¬ificationsMockInterface(), + SIGNAL(MethodCalled(const QString &, const QVariantList &))); + + // start up a UsbSnap to ask about a fingerprint + auto snap = std::make_shared(test.fingerprint); + AdbdClient::PKResponse user_response {}; + bool user_response_set = false; + snap->on_user_response().connect([&user_response,&user_response_set](AdbdClient::PKResponse response, bool /*remember*/){ + user_response = response; + user_response_set = true; + }); + + // test that UsbSnap creates a fdo notification + WAIT_FOR_SIGNALS(notificationsSpy, 1); + { + QVariantList const& call(notificationsSpy.at(0)); + EXPECT_EQ("Notify", call.at(0)); + + QVariantList const& args(call.at(1).toList()); + ASSERT_EQ(8, args.size()); + EXPECT_EQ("", args.at(0)); // app name + EXPECT_EQ(0, args.at(1)); // replaces-id + EXPECT_EQ("computer-symbolic", args.at(2)); // icon name + EXPECT_EQ("Allow USB Debugging?", args.at(3)); // summary + EXPECT_EQ(QString::fromUtf8("The computer's RSA key fingerprint is: ") + test.fingerprint, args.at(4)); // body + EXPECT_EQ(QStringList({"allow", "Allow", "deny", "Deny"}), args.at(5)); // actions + EXPECT_EQ(-1, args.at(7)); + + QVariantMap hints; + ASSERT_TRUE(qDBusArgumentToMap(args.at(6), hints)); + ASSERT_EQ(3, hints.size()); + ASSERT_TRUE(hints.contains("x-canonical-private-affirmative-tint")); + ASSERT_TRUE(hints.contains("x-canonical-non-shaped-icon")); + ASSERT_TRUE(hints.contains("x-canonical-snap-decisions")); + } + notificationsSpy.clear(); + + // fake a user interaction with the fdo notification + notificationsMockInterface().EmitSignal( + DBusTypes::NOTIFY_DBUS_INTERFACE, + "ActionInvoked", + "us", + QVariantList() << id << test.action_to_invoke); + + // test that UsbSnap emits on_user_response() as a result + wait_for([&user_response_set](){return user_response_set;}); + EXPECT_TRUE(user_response_set); + ASSERT_EQ(test.expected_response, user_response); + + // confirm that the snap dtor cleans up the notification + snap.reset(); + WAIT_FOR_SIGNALS(notificationsSpy, 1); + { + QVariantList const& call(notificationsSpy.at(0)); + EXPECT_EQ("CloseNotification", call.at(0)); + QVariantList const& args(call.at(1).toList()); + EXPECT_EQ(id, args.at(0)); + } + } +} diff --git a/tests/usb-snap-test.cpp b/tests/usb-snap-test.cpp deleted file mode 100644 index 309980a..0000000 --- a/tests/usb-snap-test.cpp +++ /dev/null @@ -1,216 +0,0 @@ -/* - * 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 - */ - -#define QT_NO_KEYWORDS -#include -#include - -#include - -#include - -#include -#include -#include - -#include - -#include - -using namespace QtDBusTest; -using namespace QtDBusMock; - -inline QString qVariantToString(const QVariant& variant) { - QString output; - QDebug dbg(&output); - dbg << variant; - return output; -} - -inline void PrintTo(const QVariant& variant, std::ostream* os) { - QString output; - QDebug dbg(&output); - dbg << variant; - - *os << "QVariant(" << output.toStdString() << ")"; -} - -inline void PrintTo(const QString& s, std::ostream* os) { - *os << "\"" << s.toStdString() << "\""; -} - -inline void PrintTo(const QStringList& list, std::ostream* os) { - QString output; - QDebug dbg(&output); - dbg << list; - - *os << "QStringList(" << output.toStdString() << ")"; -} - -inline void PrintTo(const QList& list, std::ostream* os) { - QString output; - for (const auto& path: list) - { - output.append("\"" + path.path() + "\","); - } - - *os << "QList(" << output.toStdString() << ")"; -} - -#define WAIT_FOR_SIGNALS(signalSpy, signalsExpected)\ -{\ - while (signalSpy.size() < signalsExpected)\ - {\ - ASSERT_TRUE(signalSpy.wait());\ - }\ - ASSERT_EQ(signalsExpected, signalSpy.size());\ -} - -class UsbSnapFixture: public GlibFixture -{ - using super = GlibFixture; - -public: - - UsbSnapFixture(): - dbusMock{dbusTestRunner} - { - DBusTypes::registerMetaTypes(); - - dbusTestRunner.startServices(); - } - - ~UsbSnapFixture() =default; - -protected: - - bool qDBusArgumentToMap(QVariant const& variant, QVariantMap& map) - { - if (variant.canConvert()) - { - QDBusArgument value(variant.value()); - if (value.currentType() == QDBusArgument::MapType) - { - value >> map; - return true; - } - } - return false; - } - - void SetUp() override - { - super::SetUp(); - - dbusMock.registerNotificationDaemon(); - dbusTestRunner.startServices(); - } - - OrgFreedesktopDBusMockInterface& notificationsMockInterface() - { - return dbusMock.mockInterface("org.freedesktop.Notifications", - "/org/freedesktop/Notifications", - "org.freedesktop.Notifications", - QDBusConnection::SessionBus); - } - - QtDBusTest::DBusTestRunner dbusTestRunner; - QtDBusMock::DBusMock dbusMock; - QtDBusTest::DBusServicePtr indicator; -}; - -TEST_F(UsbSnapFixture, TestRoundTrip) -{ - struct { - const char* fingerprint; - const char* action_to_invoke; - const AdbdClient::PKResponse expected_response; - } tests[] = { - { "Fingerprint", "allow", AdbdClient::PKResponse::ALLOW }, - { "Fingerprint", "deny", AdbdClient::PKResponse::DENY } - }; - - uint32_t next_id = 1; - for(const auto& test : tests) - { - // Minor wart: we don't have a way of getting the fdo notification id - // from dbusmock so instead we copy its (simple) id generation here - const auto id = next_id++; - - QSignalSpy notificationsSpy( - ¬ificationsMockInterface(), - SIGNAL(MethodCalled(const QString &, const QVariantList &))); - - // start up a UsbSnap to ask about a fingerprint - auto snap = std::make_shared(test.fingerprint); - AdbdClient::PKResponse user_response {}; - bool user_response_set = false; - snap->on_user_response().connect([&user_response,&user_response_set](AdbdClient::PKResponse response, bool /*remember*/){ - user_response = response; - user_response_set = true; - }); - - // test that UsbSnap creates a fdo notification - WAIT_FOR_SIGNALS(notificationsSpy, 1); - { - QVariantList const& call(notificationsSpy.at(0)); - EXPECT_EQ("Notify", call.at(0)); - - QVariantList const& args(call.at(1).toList()); - ASSERT_EQ(8, args.size()); - EXPECT_EQ("", args.at(0)); // app name - EXPECT_EQ(0, args.at(1)); // replaces-id - EXPECT_EQ("computer-symbolic", args.at(2)); // icon name - EXPECT_EQ("Allow USB Debugging?", args.at(3)); // summary - EXPECT_EQ(QString::fromUtf8("The computer's RSA key fingerprint is: ") + test.fingerprint, args.at(4)); // body - EXPECT_EQ(QStringList({"allow", "Allow", "deny", "Deny"}), args.at(5)); // actions - EXPECT_EQ(-1, args.at(7)); - - QVariantMap hints; - ASSERT_TRUE(qDBusArgumentToMap(args.at(6), hints)); - ASSERT_EQ(3, hints.size()); - ASSERT_TRUE(hints.contains("x-canonical-private-affirmative-tint")); - ASSERT_TRUE(hints.contains("x-canonical-non-shaped-icon")); - ASSERT_TRUE(hints.contains("x-canonical-snap-decisions")); - } - notificationsSpy.clear(); - - // fake a user interaction with the fdo notification - notificationsMockInterface().EmitSignal( - DBusTypes::NOTIFY_DBUS_INTERFACE, - "ActionInvoked", - "us", - QVariantList() << id << test.action_to_invoke); - - // test that UsbSnap emits on_user_response() as a result - wait_for([&user_response_set](){return user_response_set;}); - EXPECT_TRUE(user_response_set); - ASSERT_EQ(test.expected_response, user_response); - - // confirm that the snap dtor cleans up the notification - snap.reset(); - WAIT_FOR_SIGNALS(notificationsSpy, 1); - { - QVariantList const& call(notificationsSpy.at(0)); - EXPECT_EQ("CloseNotification", call.at(0)); - QVariantList const& args(call.at(1).toList()); - EXPECT_EQ(id, args.at(0)); - } - } -} diff --git a/tests/utils/adbd-server.h b/tests/utils/adbd-server.h new file mode 100644 index 0000000..740faaa --- /dev/null +++ b/tests/utils/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/utils/dbus-types.h b/tests/utils/dbus-types.h new file mode 100644 index 0000000..c2dfb81 --- /dev/null +++ b/tests/utils/dbus-types.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013-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 . + * + * Author: Pete Woods + */ + +#pragma once + +#include +#include +#include +#include + +typedef QMap QVariantDictMap; +Q_DECLARE_METATYPE(QVariantDictMap) + +typedef QMap QStringMap; +Q_DECLARE_METATYPE(QStringMap) + +namespace DBusTypes +{ + inline void registerMetaTypes() + { + qRegisterMetaType("QVariantDictMap"); + qRegisterMetaType("QStringMap"); + + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + } + static constexpr char const* NOTIFY_DBUS_NAME = "org.freedesktop.Notifications"; + + static constexpr char const* NOTIFY_DBUS_INTERFACE = "org.freedesktop.Notifications"; + + static constexpr char const* NOTIFY_DBUS_PATH = "/org/freedesktop/Notifications"; +} diff --git a/tests/utils/glib-fixture.h b/tests/utils/glib-fixture.h new file mode 100644 index 0000000..ccdeccd --- /dev/null +++ b/tests/utils/glib-fixture.h @@ -0,0 +1,203 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr + * + * 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 . + */ + +#pragma once + +#include // std::function +#include +#include // std::shared_ptr + +#include +#include +#include + +#include + +#include // setlocale() + +class GlibFixture : public ::testing::Test +{ + public: + + virtual ~GlibFixture() =default; + + protected: + + virtual void SetUp() override + { + setlocale(LC_ALL, "C.UTF-8"); + + loop = g_main_loop_new(nullptr, false); + +#ifdef SCHEMA_DIR + // only use local, temporary settings + g_assert(g_setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, true)); + g_assert(g_setenv("GSETTINGS_BACKEND", "memory", true)); + g_debug("SCHEMA_DIR is %s", SCHEMA_DIR); +#endif + + // fail on unexpected messages from this domain + g_log_set_fatal_mask(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING); + + g_unsetenv("DISPLAY"); + + } + + virtual void TearDown() override + { + g_test_assert_expected_messages (); + + g_clear_pointer(&loop, g_main_loop_unref); + } + + void expectLogMessage (const gchar *domain, GLogLevelFlags level, const gchar *pattern) + { + g_test_expect_message (domain, level, pattern); + } + + private: + + static gboolean + wait_for_signal__timeout(gpointer name) + { + g_error("%s: timed out waiting for signal '%s'", G_STRLOC, static_cast(name)); + return G_SOURCE_REMOVE; + } + + static gboolean + wait_msec__timeout(gpointer loop) + { + g_main_loop_quit(static_cast(loop)); + return G_SOURCE_CONTINUE; + } + + protected: + + /* convenience func to loop while waiting for a GObject's signal */ + 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, + G_CALLBACK(g_main_loop_quit), + loop); + const auto timeout_id = g_timeout_add_seconds(timeout_seconds, + wait_for_signal__timeout, + loop); + g_main_loop_run(loop); + g_source_remove(timeout_id); + g_signal_handler_disconnect(o, handler_id); + } + + /* convenience func to loop for N msec */ + 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; +}; + diff --git a/tests/utils/qmain.cpp b/tests/utils/qmain.cpp new file mode 100644 index 0000000..72a49b1 --- /dev/null +++ b/tests/utils/qmain.cpp @@ -0,0 +1,60 @@ +/* + * Copyright © 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Authors: + * Pete Woods + */ + +//#include + +#include +#include +#include + +#include + +using namespace QtDBusMock; + +class Runner: public QObject +{ + Q_OBJECT +public Q_SLOTS: + void run() + { + QCoreApplication::exit(RUN_ALL_TESTS()); + } +}; + +int main(int argc, char **argv) +{ + qputenv("LANG", "C.UTF-8"); + unsetenv("LC_ALL"); + + // boilerplate i18n + setlocale(LC_ALL, ""); + bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR); + textdomain(GETTEXT_PACKAGE); + + QCoreApplication application(argc, argv); + DBusMock::registerMetaTypes(); + ::testing::InitGoogleTest(&argc, argv); + + Runner runner; + QTimer::singleShot(0, &runner, SLOT(run())); + + return application.exec(); +} + +#include "qmain.moc" diff --git a/tests/utils/test-dbus-fixture.h b/tests/utils/test-dbus-fixture.h new file mode 100644 index 0000000..3947e58 --- /dev/null +++ b/tests/utils/test-dbus-fixture.h @@ -0,0 +1,106 @@ +/* + * Copyright 2013 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 + */ + +#pragma once + +#include "glib-fixture.h" + +/*** +**** +***/ + +class TestDBusFixture: public GlibFixture +{ + public: + + TestDBusFixture() =default; + virtual ~TestDBusFixture() =default; + + explicit TestDBusFixture(const std::vector& service_dirs_in): service_dirs(service_dirs_in) {} + + private: + + typedef GlibFixture super; + + static void + on_bus_opened (GObject* /*object*/, GAsyncResult * res, gpointer gself) + { + auto self = static_cast(gself); + + GError * err = 0; + self->system_bus = g_bus_get_finish (res, &err); + g_assert_no_error (err); + + g_main_loop_quit (self->loop); + } + + static void + on_bus_closed (GObject* /*object*/, GAsyncResult * res, gpointer gself) + { + auto self = static_cast(gself); + + GError * err = 0; + g_dbus_connection_close_finish (self->system_bus, res, &err); + g_assert_no_error (err); + + g_main_loop_quit (self->loop); + } + + protected: + + GTestDBus * test_dbus = nullptr; + GDBusConnection * system_bus = nullptr; + const std::vector service_dirs; + + virtual void SetUp() override + { + super::SetUp (); + + // pull up a test dbus + test_dbus = g_test_dbus_new (G_TEST_DBUS_NONE); + for (const auto& dir : service_dirs) + g_test_dbus_add_service_dir (test_dbus, dir.c_str()); + g_test_dbus_up (test_dbus); + const char * address = g_test_dbus_get_bus_address (test_dbus); + g_setenv ("DBUS_SYSTEM_BUS_ADDRESS", address, true); + g_setenv ("DBUS_SESSION_BUS_ADDRESS", address, true); + g_debug ("test_dbus's address is %s", address); + + // wait for the GDBusConnection before returning + g_bus_get (G_BUS_TYPE_SYSTEM, nullptr, on_bus_opened, this); + g_main_loop_run (loop); + } + + virtual void TearDown() override + { + wait_msec(); + + // close the system bus + g_dbus_connection_close(system_bus, nullptr, on_bus_closed, this); + g_main_loop_run(loop); + g_clear_object(&system_bus); + + // tear down the test dbus + g_test_dbus_down(test_dbus); + g_clear_object(&test_dbus); + + super::TearDown(); + } +}; + -- cgit v1.2.3