diff options
author | Charles Kerr <charles.kerr@canonical.com> | 2016-03-24 16:16:53 +0000 |
---|---|---|
committer | CI Train Bot <ci-train-bot@canonical.com> | 2016-03-24 16:16:53 +0000 |
commit | e3fa9582a01f14e6d80e00d375361a76df716895 (patch) | |
tree | 7b9521ad7cfa7a2e765be265d5ecd98cb9bcad23 /tests | |
parent | d2f14e07000e9a5ee418bcbcd34c5ae57bd45590 (diff) | |
parent | 194d7e85a52cbc0060a2d85b71b9ddd8b606aee4 (diff) | |
download | ayatana-indicator-display-e3fa9582a01f14e6d80e00d375361a76df716895.tar.gz ayatana-indicator-display-e3fa9582a01f14e6d80e00d375361a76df716895.tar.bz2 ayatana-indicator-display-e3fa9582a01f14e6d80e00d375361a76df716895.zip |
When a new device appears to ADB, prompt the user whether or not to allow the connection.
Approved by: PS Jenkins bot, Charles Kerr, Xavi Garcia
Diffstat (limited to 'tests')
-rw-r--r-- | tests/CMakeLists.txt | 56 | ||||
-rw-r--r-- | tests/glib-fixture.h | 152 | ||||
-rw-r--r-- | tests/integration/CMakeLists.txt | 24 | ||||
-rw-r--r-- | tests/integration/usb-manager-test.cpp | 226 | ||||
-rw-r--r-- | tests/unit/CMakeLists.txt | 34 | ||||
-rw-r--r-- | tests/unit/adbd-client-test.cpp | 95 | ||||
-rw-r--r-- | tests/unit/rotation-lock-test.cpp (renamed from tests/test-rotation-lock.cpp) | 6 | ||||
-rw-r--r-- | tests/unit/usb-snap-test.cpp | 143 | ||||
-rw-r--r-- | tests/utils/CMakeLists.txt | 17 | ||||
-rw-r--r-- | tests/utils/adbd-server.h | 150 | ||||
-rw-r--r-- | tests/utils/dbus-types.h | 42 | ||||
-rw-r--r-- | tests/utils/glib-fixture.h | 203 | ||||
-rw-r--r-- | tests/utils/gtest-qt-print-helpers.h | 45 | ||||
-rw-r--r-- | tests/utils/mock-greeter.h | 32 | ||||
-rw-r--r-- | tests/utils/mock-usb-monitor.h | 32 | ||||
-rw-r--r-- | tests/utils/qmain.cpp | 60 | ||||
-rw-r--r-- | tests/utils/qt-fixture.h | 74 | ||||
-rw-r--r-- | tests/utils/test-dbus-fixture.h (renamed from tests/gtestdbus-fixture.h) | 30 |
18 files changed, 1222 insertions, 199 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 054a676..7be2acd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,35 +1,35 @@ -include(FindGMock) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${GMOCK_INCLUDE_DIRS}) -include_directories(${GTEST_INCLUDE_DIRS}) +set(CMAKE_AUTOMOC ON) +find_package(GMock REQUIRED) +find_package(Qt5Core REQUIRED) +find_package(Qt5Test REQUIRED) +find_package(Qt5DBus COMPONENTS Qt5DBusMacros REQUIRED) -# 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) +pkg_check_modules(TEST_DEPS + libqtdbustest-1 REQUIRED + libqtdbusmock-1 REQUIRED +) -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() +include_directories(SYSTEM + ${DBUSTEST_INCLUDE_DIRS} + ${TEST_DEPS_INCLUDE_DIRS} + ${GTEST_INCLUDE_DIRS} + ${GMOCK_INCLUDE_DIRS} +) -SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g ${CXX_WARNING_ARGS}") +list(APPEND CTEST_ENVIRONMENT + G_MESSAGES_DEBUG=all + G_DBUS_DEBUG=call,signal,return,message +) -# look for headers in our src dir, and also in the directories where we autogenerate files... -include_directories (${CMAKE_SOURCE_DIR}/src) -include_directories (${CMAKE_CURRENT_BINARY_DIR}) -include_directories (${DBUSTEST_INCLUDE_DIRS}) +# turn off the warnings that break Google Test +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + list(APPEND CXX_WARNING_ARGS -Wno-global-constructors -Wno-weak-vtables) +endif() -function(add_test_by_name name) - set (TEST_NAME ${name}) - add_executable (${TEST_NAME} ${TEST_NAME}.cpp) - add_test (${TEST_NAME} ${TEST_NAME}) - add_dependencies (${TEST_NAME} libindicatordisplayservice) - target_link_libraries (${TEST_NAME} indicatordisplayservice ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) -endfunction() -add_test_by_name(test-rotation-lock) +add_compile_options(${CXX_WARNING_ARGS}) -add_test (cppcheck cppcheck --enable=all -q --error-exitcode=2 --inline-suppr -I${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/tests) +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(integration) +add_subdirectory(unit) +add_subdirectory(utils) diff --git a/tests/glib-fixture.h b/tests/glib-fixture.h deleted file mode 100644 index 65d2921..0000000 --- a/tests/glib-fixture.h +++ /dev/null @@ -1,152 +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 <http://www.gnu.org/licenses/>. - * - * Authors: - * Charles Kerr <charles.kerr@canonical.com> - */ - -#ifndef INDICATOR_TESTS_GLIB_FIXTURE_H -#define INDICATOR_TESTS_GLIB_FIXTURE_H - -#include <map> - -#include <glib.h> -#include <glib/gstdio.h> -#include <gio/gio.h> - -#include <gtest/gtest.h> - -#include <locale.h> // setlocale() - -class GlibFixture : public ::testing::Test -{ - private: - - GLogFunc realLogHandler; - - std::map<GLogLevelFlags,size_t> expected_log; - std::map<GLogLevelFlags,std::vector<std::string>> log; - - void test_log_counts() - { - const GLogLevelFlags levels_to_test[] = { G_LOG_LEVEL_ERROR, - G_LOG_LEVEL_CRITICAL, - G_LOG_LEVEL_MESSAGE, - G_LOG_LEVEL_WARNING }; - - for(const auto& level : levels_to_test) - { - const auto& v = log[level]; - const auto n = v.size(); - - EXPECT_EQ(expected_log[level], n); - - if (expected_log[level] != n) - for (size_t i=0; i<n; ++i) - g_print("%d %s\n", (n+1), v[i].c_str()); - } - - expected_log.clear(); - log.clear(); - } - - static void default_log_handler(const gchar * log_domain, - GLogLevelFlags log_level, - const gchar * message, - gpointer self) - { - char* tmp = g_strdup_printf ("%s:%d \"%s\"", log_domain, (int)log_level, message); - static_cast<GlibFixture*>(self)->log[log_level].push_back(tmp); - g_free(tmp); - } - - protected: - - void increment_expected_errors(GLogLevelFlags level, size_t n=1) - { - expected_log[level] += n; - } - - virtual void SetUp() - { - setlocale(LC_ALL, "C.UTF-8"); - - loop = g_main_loop_new(nullptr, false); - - g_log_set_default_handler(default_log_handler, this); - - g_assert(g_setenv("GSETTINGS_BACKEND", "memory", true)); - - g_unsetenv("DISPLAY"); - } - - virtual void TearDown() - { - test_log_counts(); - - g_log_set_default_handler(realLogHandler, this); - - g_clear_pointer(&loop, g_main_loop_unref); - } - - private: - - static gboolean - wait_for_signal__timeout(gpointer name) - { - g_error("%s: timed out waiting for signal '%s'", G_STRLOC, (char*)name); - return G_SOURCE_REMOVE; - } - - static gboolean - wait_msec__timeout(gpointer loop) - { - g_main_loop_quit(static_cast<GMainLoop*>(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, const 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); - } - - GMainLoop * loop; - - public: - - virtual ~GlibFixture() =default; -}; - -#endif /* INDICATOR_TESTS_GLIB_FIXTURE_H */ diff --git a/tests/integration/CMakeLists.txt b/tests/integration/CMakeLists.txt new file mode 100644 index 0000000..9ec6688 --- /dev/null +++ b/tests/integration/CMakeLists.txt @@ -0,0 +1,24 @@ +set(SERVICE_LINK_LIBRARIES + ${SERVICE_LIB} + ${SERVICE_DEPS_LIBRARIES} +) +set(QT_LINK_LIBRARIES + test-utils + Qt5::Core + Qt5::Test + Qt5::DBus +) +set(TEST_LINK_LIBRARIES + ${TEST_DEPS_LIBRARIES} + ${GTEST_LIBRARIES} + ${GMOCK_LIBRARIES} +) + +function(add_qt_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} APPEND PROPERTY ENVIRONMENT ${CTEST_ENVIRONMENT}) + target_link_libraries(${TEST_NAME} ${SERVICE_LINK_LIBRARIES} ${QT_LINK_LIBRARIES} ${TEST_LINK_LIBRARIES} ${THREAD_LINK_LIBRARIES}) +endfunction() +add_qt_test_by_name(usb-manager-test) diff --git a/tests/integration/usb-manager-test.cpp b/tests/integration/usb-manager-test.cpp new file mode 100644 index 0000000..d62756f --- /dev/null +++ b/tests/integration/usb-manager-test.cpp @@ -0,0 +1,226 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <tests/utils/adbd-server.h> +#include <tests/utils/qt-fixture.h> +#include <tests/utils/mock-greeter.h> +#include <tests/utils/mock-usb-monitor.h> + +#include <src/dbus-names.h> +#include <src/usb-manager.h> + +#include <libqtdbustest/DBusTestRunner.h> +#include <libqtdbustest/QProcessDBusService.h> +#include <libqtdbusmock/DBusMock.h> + +#include <fstream> +#include <sstream> +#include <vector> + +/*** +**** +***/ + +class UsbManagerFixture: public QtFixture +{ + using super = QtFixture; + +public: + + UsbManagerFixture(): + dbusMock{dbusTestRunner} + { + dbusTestRunner.startServices(); + } + + ~UsbManagerFixture() =default; + +protected: + + static void file_deleter (std::string* s) + { + fprintf(stderr, "remove \"%s\"\n", s->c_str()); + g_remove(s->c_str()); + delete s; + } + + void SetUp() override + { + super::SetUp(); + + m_usb_monitor.reset(new MockUsbMonitor{}); + m_greeter.reset(new MockGreeter{}); + + char tmpl[] = {"usb-manager-test-XXXXXX"}; + m_tmpdir.reset(new std::string{g_mkdtemp(tmpl)}, file_deleter); + g_message("using tmpdir '%s'", m_tmpdir->c_str()); + + dbusMock.registerNotificationDaemon(); + dbusTestRunner.startServices(); + } + + OrgFreedesktopDBusMockInterface& notificationsMockInterface() + { + return dbusMock.mockInterface(DBusNames::Notify::NAME, + DBusNames::Notify::PATH, + DBusNames::Notify::INTERFACE, + QDBusConnection::SessionBus); + } + + QtDBusTest::DBusTestRunner dbusTestRunner; + QtDBusMock::DBusMock dbusMock; + std::shared_ptr<std::string> m_tmpdir; + std::shared_ptr<MockUsbMonitor> m_usb_monitor; + std::shared_ptr<MockGreeter> m_greeter; +}; + +TEST_F(UsbManagerFixture, Allow) +{ + const std::shared_ptr<std::string> socket_path {new std::string{*m_tmpdir+"/socket"}, file_deleter}; + const std::shared_ptr<std::string> public_keys_path {new std::string{*m_tmpdir+"/adb_keys"}, file_deleter}; + + // add a signal spy to listen to the notification daemon + QSignalSpy notificationsSpy( + ¬ificationsMockInterface(), + SIGNAL(MethodCalled(const QString &, const QVariantList &)) + ); + + // start a mock AdbdServer ready to submit a request + const std::string public_key {"qAAAALUHllFjEZjl5jbS9ivjpQpaTNpibl28Re71D/S8sV3usNJTkbpvZYoVPfxtmHSNdCgLkWN6qcDZsHZqE/4myzmx/8Y/RqBy1oirudugi3YUUcJh7aWkY8lKQe9shCLTcrT7cFLZIJIidTvfmWTm0UcU+xmdPALze11I3lGo1Ty5KpCe9oP+qYM8suHbxhm78LKLlo0QJ2QqM8T5isr1pvoPHDgRb+mSESElG+xDIfPWA2BTu77/xk4EnXmOYfcuCr5akF3N4fRo/ACnYgXWDZFX2XdklBXyDj78lVlinF37xdMk7BMQh166X7UNkpH1uG2y5F6lUzyLg8SsFtRnJkw7eVe/gnJj3feQaFQbF5oVDhWhLMtWLtejhX6umvroVBVA4rynG4xEgs00K4u4ly8DUIIJYDO22Ml4myFR5CUm3lOlyitNdzYGh0utLXPq9oc8EbMVxM3i+O7PRxQw5Ul04X6K8GLiGUDV98DB+xYUqfEveq1BRnXi/ZrdPDhQ8Lfkg5xnLccPTFamAqutPtZXV6s7dXJInBTZf0NtBaWL0RdR2cOJBrpeBYkrc9yIyeqFLFdxr66rjaehjaa4pS4S+CD6PkGiIpPWSQtwNC4RlT10qTQ0/K9lRux2p0D8Z8ubUTFuh4kBScGUkN1OV3Z+7d7B+ghmBtZrrgleXsbehjRuKgEAAQA= foo@bar"}; + const std::string fingerprint {"12:23:5f:2d:8c:40:ae:1d:05:7b:ae:bd:88:8a:f0:80"}; + auto adbd_server = std::make_shared<GAdbdServer>(*socket_path, std::vector<std::string>{"PK"+public_key}); + + // set up a UsbManager to process the request + auto usb_manager = std::make_shared<UsbManager>(*socket_path, *public_keys_path, m_usb_monitor, m_greeter); + + // wait for the notification to show up, confirm it looks right + 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: ") + QString::fromUtf8(fingerprint.c_str()), args.at(4)); // body + EXPECT_EQ(QStringList({"allow", "Allow", "deny", "Don't Allow"}), 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(); + + // click on allow in the notification + notificationsMockInterface().EmitSignal( + DBusNames::Notify::INTERFACE, + DBusNames::Notify::ActionInvoked::NAME, + "us", + QVariantList() << uint32_t(1) << "allow" + ); + + // confirm that the AdbdServer got the right response + wait_for([adbd_server](){return !adbd_server->m_responses.empty();}, 2000); + ASSERT_EQ(1, adbd_server->m_responses.size()); + EXPECT_EQ("OK", adbd_server->m_responses.front()); + + // confirm that the public_keys file got the public key appended to it + std::ifstream ifkeys {*public_keys_path}; + std::vector<std::string> lines; + std::string line; + while(getline(ifkeys, line)) + lines.emplace_back(std::move(line)); + ASSERT_EQ(1, lines.size()); + EXPECT_EQ(public_key, lines[0]); +} + +TEST_F(UsbManagerFixture, USBDisconnectedDuringPrompt) +{ + const std::shared_ptr<std::string> socket_path {new std::string{*m_tmpdir+"/socket"}, file_deleter}; + const std::shared_ptr<std::string> public_keys_path {new std::string{*m_tmpdir+"/adb_keys"}, file_deleter}; + + // start a mock AdbdServer ready to submit a request + const std::string public_key {"public_key"}; + auto adbd_server = std::make_shared<GAdbdServer>(*socket_path, std::vector<std::string>{"PK"+public_key}); + + // set up a UsbManager to process the request + auto usb_manager = std::make_shared<UsbManager>(*socket_path, *public_keys_path, m_usb_monitor, m_greeter); + + for (int i=0; i<3; i++) + { + // add a signal spy to listen to the notification daemon + QSignalSpy notificationsSpy( + ¬ificationsMockInterface(), + SIGNAL(MethodCalled(const QString &, const QVariantList &)) + ); + + // wait for a notification to show up + wait_for_signals(notificationsSpy, 1); + EXPECT_EQ("Notify", notificationsSpy.at(0).at(0)); + notificationsSpy.clear(); + + // wait for UsbSnap to receive dbusmock's response to the Notify request. + // there's no event to key off of for this, so just wait for a moment + wait_msec(); + + // disconnect the USB before the user has a chance to allow/deny + m_usb_monitor->m_on_usb_disconnected("android0"); + + // confirm that we requested the notification to be pulled down + wait_for_signals(notificationsSpy, 1); + EXPECT_EQ("CloseNotification", notificationsSpy.at(0).at(0)); + notificationsSpy.clear(); + } +} + +TEST_F(UsbManagerFixture, Greeter) +{ + const std::shared_ptr<std::string> socket_path {new std::string{*m_tmpdir+"/socket"}, file_deleter}; + const std::shared_ptr<std::string> public_keys_path {new std::string{*m_tmpdir+"/adb_keys"}, file_deleter}; + + // start a mock AdbdServer ready to submit a request + const std::string public_key {"public_key"}; + auto adbd_server = std::make_shared<GAdbdServer>(*socket_path, std::vector<std::string>{"PK"+public_key}); + + // set up a UsbManager to process the request + m_greeter->m_is_active.set(true); + auto usb_manager = std::make_shared<UsbManager>(*socket_path, *public_keys_path, m_usb_monitor, m_greeter); + + // add a signal spy to listen to the notification daemon + QSignalSpy notificationsSpy( + ¬ificationsMockInterface(), + SIGNAL(MethodCalled(const QString &, const QVariantList &)) + ); + + // the greeter is active, so the notification should not appear + EXPECT_FALSE(notificationsSpy.wait(2000)); + + // disable the greeter, the notification should appear + m_greeter->m_is_active.set(false); + wait_for_signals(notificationsSpy, 1); + EXPECT_EQ("Notify", notificationsSpy.at(0).at(0)); + notificationsSpy.clear(); +} diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt new file mode 100644 index 0000000..fe70461 --- /dev/null +++ b/tests/unit/CMakeLists.txt @@ -0,0 +1,34 @@ +set(SERVICE_LINK_LIBRARIES + ${SERVICE_LIB} + ${SERVICE_DEPS_LIBRARIES} +) +set(QT_LINK_LIBRARIES + test-utils + Qt5::Core + Qt5::Test + Qt5::DBus +) +set(TEST_LINK_LIBRARIES + ${TEST_DEPS_LIBRARIES} + ${GTEST_LIBRARIES} + ${GMOCK_LIBRARIES} +) + +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} APPEND PROPERTY ENVIRONMENT ${CTEST_ENVIRONMENT}) + target_link_libraries(${TEST_NAME} ${SERVICE_LINK_LIBRARIES} ${TEST_LINK_LIBRARIES} ${THREAD_LINK_LIBRARIES}) +endfunction() +add_test_by_name(adbd-client-test) +add_test_by_name(rotation-lock-test) + +function(add_qt_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} APPEND PROPERTY ENVIRONMENT ${CTEST_ENVIRONMENT}) + target_link_libraries(${TEST_NAME} ${SERVICE_LINK_LIBRARIES} ${QT_LINK_LIBRARIES} ${TEST_LINK_LIBRARIES} ${THREAD_LINK_LIBRARIES}) +endfunction() +add_qt_test_by_name(usb-snap-test) diff --git a/tests/unit/adbd-client-test.cpp b/tests/unit/adbd-client-test.cpp new file mode 100644 index 0000000..754f76c --- /dev/null +++ b/tests/unit/adbd-client-test.cpp @@ -0,0 +1,95 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <tests/utils/test-dbus-fixture.h> +#include <tests/utils/adbd-server.h> + +#include <src/adbd-client.h> + +class AdbdClientFixture: public TestDBusFixture +{ +private: + typedef TestDBusFixture super; + +protected: + + static void file_deleter (std::string* s) + { + fprintf(stderr, "remove \"%s\"\n", s->c_str()); + g_remove(s->c_str()); + delete s; + } + + std::shared_ptr<std::string> m_tmpdir; + + void SetUp() + { + super::SetUp(); + + char tmpl[] = {"adb-client-test-XXXXXX"}; + m_tmpdir.reset(new std::string{g_mkdtemp(tmpl)}, file_deleter); + g_message("using tmpdir '%s'", m_tmpdir->c_str()); + } +}; + + +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<GAdbdClient>(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<GAdbdServer>(socket_path, std::vector<std::string>{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/test-rotation-lock.cpp b/tests/unit/rotation-lock-test.cpp index 946b1dd..b9630b5 100644 --- a/tests/test-rotation-lock.cpp +++ b/tests/unit/rotation-lock-test.cpp @@ -17,14 +17,14 @@ * Charles Kerr <charles.kerr@canonical.com> */ -#include "gtestdbus-fixture.h" +#include <tests/utils/test-dbus-fixture.h> #include <src/rotation-lock.h> -class RotationLockFixture: public GTestDBusFixture +class RotationLockFixture: public TestDBusFixture { private: - typedef GTestDBusFixture super; + typedef TestDBusFixture super; protected: diff --git a/tests/unit/usb-snap-test.cpp b/tests/unit/usb-snap-test.cpp new file mode 100644 index 0000000..3b778dd --- /dev/null +++ b/tests/unit/usb-snap-test.cpp @@ -0,0 +1,143 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <tests/utils/qt-fixture.h> + +#include <src/dbus-names.h> +#include <src/usb-snap.h> + +#include <libqtdbustest/DBusTestRunner.h> +#include <libqtdbustest/QProcessDBusService.h> +#include <libqtdbusmock/DBusMock.h> + +class UsbSnapFixture: public QtFixture +{ + using super = QtFixture; + +public: + + UsbSnapFixture(): + dbusMock{dbusTestRunner} + { + dbusTestRunner.startServices(); + } + + ~UsbSnapFixture() =default; + +protected: + + void SetUp() override + { + super::SetUp(); + + dbusMock.registerNotificationDaemon(); + dbusTestRunner.startServices(); + } + + OrgFreedesktopDBusMockInterface& notificationsMockInterface() + { + return dbusMock.mockInterface(DBusNames::Notify::NAME, + DBusNames::Notify::PATH, + DBusNames::Notify::INTERFACE, + QDBusConnection::SessionBus); + } + + QtDBusTest::DBusTestRunner dbusTestRunner; + QtDBusMock::DBusMock dbusMock; +}; + +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<UsbSnap>(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", "Don't Allow"}), 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( + DBusNames::Notify::INTERFACE, + DBusNames::Notify::ActionInvoked::NAME, + "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/CMakeLists.txt b/tests/utils/CMakeLists.txt new file mode 100644 index 0000000..e458c82 --- /dev/null +++ b/tests/utils/CMakeLists.txt @@ -0,0 +1,17 @@ +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} +) + +add_library( + test-utils + STATIC + qmain.cpp +) + +qt5_use_modules( + test-utils + Core + DBus +) + diff --git a/tests/utils/adbd-server.h b/tests/utils/adbd-server.h new file mode 100644 index 0000000..b574622 --- /dev/null +++ b/tests/utils/adbd-server.h @@ -0,0 +1,150 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <gio/gio.h> +#include <gio/gunixsocketaddress.h> + +#include <string> +#include <thread> +#include <vector> + + +/** + * 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<std::string>& 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<std::string> m_requests; + std::vector<std::string> 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_message("GAdbdServer: Error accepting socket connection: %s", error->message); + g_clear_error(&error); + break; + } + + // pop the next request off the stack + auto request = requests.front(); + + // 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_message("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_message("GAdbdServer: Error reading response: %s", error->message); + g_clear_error(&error); + g_clear_object(&client_socket); + continue; + } + const std::string response(buf, std::string::size_type(n_bytes)); + g_message("server read %d bytes, got response: '%s'", int(n_bytes), response.c_str()); + if (!response.empty()) { + m_responses.push_back(response); + requests.erase(requests.begin()); + } + + // 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..3b3a02d --- /dev/null +++ b/tests/utils/dbus-types.h @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + * Author: Pete Woods <pete.woods@canonical.com> + */ + +#pragma once + +#include <QDBusMetaType> +#include <QtCore> +#include <QString> +#include <QVariantMap> + +typedef QMap<QString, QVariantMap> QVariantDictMap; +Q_DECLARE_METATYPE(QVariantDictMap) + +typedef QMap<QString, QString> QStringMap; +Q_DECLARE_METATYPE(QStringMap) + +namespace DBusTypes +{ + inline void registerMetaTypes() + { + qRegisterMetaType<QVariantDictMap>("QVariantDictMap"); + qRegisterMetaType<QStringMap>("QStringMap"); + + qDBusRegisterMetaType<QVariantDictMap>(); + qDBusRegisterMetaType<QStringMap>(); + } +} 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 <charles.kerr@canonical.com> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <functional> // std::function +#include <map> +#include <memory> // std::shared_ptr + +#include <glib.h> +#include <glib/gstdio.h> +#include <gio/gio.h> + +#include <gtest/gtest.h> + +#include <locale.h> // 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<const char*>(name)); + return G_SOURCE_REMOVE; + } + + static gboolean + wait_msec__timeout(gpointer loop) + { + g_main_loop_quit(static_cast<GMainLoop*>(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<bool()> test_function, guint timeout_msec=1000) + { + auto timer = std::shared_ptr<GTimer>(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<Data*>(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/gtest-qt-print-helpers.h b/tests/utils/gtest-qt-print-helpers.h new file mode 100644 index 0000000..7a0897e --- /dev/null +++ b/tests/utils/gtest-qt-print-helpers.h @@ -0,0 +1,45 @@ + +#pragma once + +#include <QDBusObjectPath> +#include <QString> +#include <QStringList> +#include <QVariant> + +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<QDBusObjectPath>& list, std::ostream* os) { + QString output; + for (const auto& path: list) + { + output.append("\"" + path.path() + "\","); + } + + *os << "QList<QDBusObjectPath>(" << output.toStdString() << ")"; +} + diff --git a/tests/utils/mock-greeter.h b/tests/utils/mock-greeter.h new file mode 100644 index 0000000..5ac85a0 --- /dev/null +++ b/tests/utils/mock-greeter.h @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#pragma once + +#include <src/greeter.h> + +class MockGreeter: public Greeter +{ +public: + MockGreeter() =default; + virtual ~MockGreeter() =default; + core::Property<bool>& is_active() override {return m_is_active;} + core::Property<bool> m_is_active {false}; +}; + diff --git a/tests/utils/mock-usb-monitor.h b/tests/utils/mock-usb-monitor.h new file mode 100644 index 0000000..92b89db --- /dev/null +++ b/tests/utils/mock-usb-monitor.h @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#pragma once + +#include <src/usb-monitor.h> + +class MockUsbMonitor: public UsbMonitor +{ +public: + MockUsbMonitor() =default; + virtual ~MockUsbMonitor() =default; + core::Signal<const std::string&>& on_usb_disconnected() override {return m_on_usb_disconnected;} + core::Signal<const std::string&> m_on_usb_disconnected; +}; + 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Pete Woods <pete.woods@canonical.com> + */ + +//#include <config.h> + +#include <QCoreApplication> +#include <QTimer> +#include <gtest/gtest.h> + +#include <libqtdbusmock/DBusMock.h> + +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/qt-fixture.h b/tests/utils/qt-fixture.h new file mode 100644 index 0000000..95b9b14 --- /dev/null +++ b/tests/utils/qt-fixture.h @@ -0,0 +1,74 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#pragma once + +#define QT_NO_KEYWORDS + +#include <tests/utils/dbus-types.h> +#include <tests/utils/glib-fixture.h> +#include <tests/utils/gtest-qt-print-helpers.h> + +#include <gtest/gtest.h> + +#include <QDBusArgument> +#include <QVariant> +#include <QSignalSpy> + +#define wait_for_signals(signalSpy,signalsExpected) \ +{ \ + while (signalSpy.size() < signalsExpected) \ + { \ + ASSERT_TRUE(signalSpy.wait()); \ + } \ + \ + ASSERT_EQ(signalsExpected, signalSpy.size()); \ +} + +class QtFixture: public GlibFixture +{ + using super = GlibFixture; + +public: + + QtFixture() + { + DBusTypes::registerMetaTypes(); + } + + ~QtFixture() =default; + +protected: + + bool qDBusArgumentToMap(QVariant const& variant, QVariantMap& map) + { + if (variant.canConvert<QDBusArgument>()) + { + QDBusArgument value(variant.value<QDBusArgument>()); + if (value.currentType() == QDBusArgument::MapType) + { + value >> map; + return true; + } + } + + return false; + } +}; + diff --git a/tests/gtestdbus-fixture.h b/tests/utils/test-dbus-fixture.h index c592033..3947e58 100644 --- a/tests/gtestdbus-fixture.h +++ b/tests/utils/test-dbus-fixture.h @@ -17,8 +17,7 @@ * Charles Kerr <charles.kerr@canonical.com> */ -#ifndef INDICATOR_TESTS_GTESTDBUS_FIXTURE_H -#define INDICATOR_TESTS_GTESTDBUS_FIXTURE_H +#pragma once #include "glib-fixture.h" @@ -26,14 +25,14 @@ **** ***/ -class GTestDBusFixture: public GlibFixture +class TestDBusFixture: public GlibFixture { public: - GTestDBusFixture() =default; - virtual ~GTestDBusFixture() =default; + TestDBusFixture() =default; + virtual ~TestDBusFixture() =default; - explicit GTestDBusFixture(const std::vector<std::string>& service_dirs_in): service_dirs(service_dirs_in) {} + explicit TestDBusFixture(const std::vector<std::string>& service_dirs_in): service_dirs(service_dirs_in) {} private: @@ -42,10 +41,10 @@ class GTestDBusFixture: public GlibFixture static void on_bus_opened (GObject* /*object*/, GAsyncResult * res, gpointer gself) { - auto self = static_cast<GTestDBusFixture*>(gself); + auto self = static_cast<TestDBusFixture*>(gself); GError * err = 0; - self->bus = g_bus_get_finish (res, &err); + self->system_bus = g_bus_get_finish (res, &err); g_assert_no_error (err); g_main_loop_quit (self->loop); @@ -54,10 +53,10 @@ class GTestDBusFixture: public GlibFixture static void on_bus_closed (GObject* /*object*/, GAsyncResult * res, gpointer gself) { - auto self = static_cast<GTestDBusFixture*>(gself); + auto self = static_cast<TestDBusFixture*>(gself); GError * err = 0; - g_dbus_connection_close_finish (self->bus, res, &err); + g_dbus_connection_close_finish (self->system_bus, res, &err); g_assert_no_error (err); g_main_loop_quit (self->loop); @@ -66,10 +65,10 @@ class GTestDBusFixture: public GlibFixture protected: GTestDBus * test_dbus = nullptr; - GDBusConnection * bus = nullptr; + GDBusConnection * system_bus = nullptr; const std::vector<std::string> service_dirs; - virtual void SetUp () + virtual void SetUp() override { super::SetUp (); @@ -88,14 +87,14 @@ class GTestDBusFixture: public GlibFixture g_main_loop_run (loop); } - virtual void TearDown () + virtual void TearDown() override { wait_msec(); // close the system bus - g_dbus_connection_close(bus, nullptr, on_bus_closed, this); + g_dbus_connection_close(system_bus, nullptr, on_bus_closed, this); g_main_loop_run(loop); - g_clear_object(&bus); + g_clear_object(&system_bus); // tear down the test dbus g_test_dbus_down(test_dbus); @@ -105,4 +104,3 @@ class GTestDBusFixture: public GlibFixture } }; -#endif /* INDICATOR_TESTS_GTESTDBUS_FIXTURE_H */ |