/* * 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 as published by * the Free Software Foundation; version 3. * * 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 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: * Ted Gould */ #include #include #include #include #include #include #include #include class IndicatorFixture : public ::testing::Test { private: std::string _indicatorPath; std::string _indicatorAddress; std::vector> _mocks; protected: std::chrono::milliseconds _eventuallyTime; private: class PerRunData { public: /* We're private in the fixture but other than that we don't care, we don't leak out. This object's purpose isn't to hide data it is to make the lifecycle of the items more clear. */ std::shared_ptr _menu; std::shared_ptr _actions; DbusTestService * _session_service; DbusTestService * _system_service; DbusTestTask * _test_indicator; DbusTestTask * _test_dummy; GDBusConnection * _session; GDBusConnection * _system; PerRunData (const std::string& indicatorPath, const std::string& indicatorAddress, std::vector>& mocks) : _menu(nullptr) , _session(nullptr) { _session_service = dbus_test_service_new(nullptr); dbus_test_service_set_bus(_session_service, DBUS_TEST_SERVICE_BUS_SESSION); _system_service = dbus_test_service_new(nullptr); dbus_test_service_set_bus(_system_service, DBUS_TEST_SERVICE_BUS_SYSTEM); _test_indicator = DBUS_TEST_TASK(dbus_test_process_new(indicatorPath.c_str())); dbus_test_task_set_name(_test_indicator, "Indicator"); dbus_test_service_add_task(_session_service, _test_indicator); _test_dummy = dbus_test_task_new(); dbus_test_task_set_wait_for(_test_dummy, indicatorAddress.c_str()); dbus_test_task_set_name(_test_dummy, "Dummy"); dbus_test_service_add_task(_session_service, _test_dummy); for(auto task : mocks) { if (dbus_test_task_get_bus(task.get()) == DBUS_TEST_SERVICE_BUS_SYSTEM) { dbus_test_service_add_task(_system_service, task.get()); } else { dbus_test_service_add_task(_session_service, task.get()); } } g_debug("Starting System Bus"); dbus_test_service_start_tasks(_system_service); _system = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr); g_dbus_connection_set_exit_on_close(_system, FALSE); g_debug("Starting Session Bus"); dbus_test_service_start_tasks(_session_service); _session = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr); g_dbus_connection_set_exit_on_close(_session, FALSE); } virtual ~PerRunData (void) { _menu.reset(); _actions.reset(); /* D-Bus Test Stuff */ g_clear_object(&_test_dummy); g_clear_object(&_test_indicator); g_clear_object(&_session_service); g_clear_object(&_system_service); /* Wait for D-Bus session bus to go */ if (!g_dbus_connection_is_closed(_session)) { g_dbus_connection_close_sync(_session, nullptr, nullptr); } g_clear_object(&_session); if (!g_dbus_connection_is_closed(_system)) { g_dbus_connection_close_sync(_system, nullptr, nullptr); } g_clear_object(&_system); } }; std::shared_ptr run; public: virtual ~IndicatorFixture() = default; IndicatorFixture (const std::string& path, const std::string& addr) : _indicatorPath(path) , _indicatorAddress(addr) , _eventuallyTime(std::chrono::seconds(5)) { }; protected: virtual void SetUp() override { run = std::make_shared(_indicatorPath, _indicatorAddress, _mocks); _mocks.clear(); } virtual void TearDown() override { run.reset(); } void addMock (std::shared_ptr mock) { _mocks.push_back(mock); } std::shared_ptr buildBustleMock (const std::string& filename, DbusTestServiceBus bus = DBUS_TEST_SERVICE_BUS_BOTH) { return std::shared_ptr([filename, bus]() { DbusTestTask * bustle = DBUS_TEST_TASK(dbus_test_bustle_new(filename.c_str())); dbus_test_task_set_name(bustle, "Bustle"); dbus_test_task_set_bus(bustle, bus); return bustle; }(), [](DbusTestTask * bustle) { g_clear_object(&bustle); }); } private: void waitForCore (GObject * obj, const gchar * signalname) { auto loop = g_main_loop_new(nullptr, FALSE); /* Our two exit criteria */ gulong signal = g_signal_connect_swapped(obj, signalname, G_CALLBACK(g_main_loop_quit), loop); guint timer = g_timeout_add_seconds(5, [](gpointer user_data) -> gboolean { g_warning("Menu Timeout"); g_main_loop_quit((GMainLoop *)user_data); return G_SOURCE_CONTINUE; }, loop); /* Wait for sync */ g_main_loop_run(loop); /* Clean up */ g_source_remove(timer); g_signal_handler_disconnect(obj, signal); g_main_loop_unref(loop); } void menuWaitForItems (const std::shared_ptr& menu) { auto count = g_menu_model_get_n_items(menu.get()); if (count != 0) return; waitForCore(G_OBJECT(menu.get()), "items-changed"); } void agWaitForActions (const std::shared_ptr& group) { auto list = std::shared_ptr( g_action_group_list_actions(group.get()), [](gchar ** list) { g_strfreev(list); }); if (g_strv_length(list.get()) != 0) { return; } waitForCore(G_OBJECT(group.get()), "action-added"); } testing::AssertionResult expectEventually (std::function &testfunc) { auto loop = std::shared_ptr(g_main_loop_new(nullptr, FALSE), [](GMainLoop * loop) { if (loop != nullptr) g_main_loop_unref(loop); }); std::promise retpromise; auto retfuture = retpromise.get_future(); auto start = std::chrono::steady_clock::now(); /* The core of the idle function as an object so we can use the C++-isms of attaching the variables and make this code reasonably readable */ std::function idlefunc = [&loop, &retpromise, &testfunc, &start, this]() -> void { auto result = testfunc(); if (result == false && _eventuallyTime > (std::chrono::steady_clock::now() - start)) { return; } retpromise.set_value(result); g_main_loop_quit(loop.get()); }; auto idlesrc = g_idle_add([](gpointer data) -> gboolean { auto func = reinterpret_cast *>(data); (*func)(); return G_SOURCE_CONTINUE; }, &idlefunc); g_main_loop_run(loop.get()); g_source_remove(idlesrc); return retfuture.get(); } protected: void setMenu (const std::string& path) { run->_menu.reset(); g_debug("Getting Menu: %s:%s", _indicatorAddress.c_str(), path.c_str()); run->_menu = std::shared_ptr(G_MENU_MODEL(g_dbus_menu_model_get(run->_session, _indicatorAddress.c_str(), path.c_str())), [](GMenuModel * modelptr) { g_clear_object(&modelptr); }); menuWaitForItems(run->_menu); } void setActions (const std::string& path) { run->_actions.reset(); run->_actions = std::shared_ptr(G_ACTION_GROUP(g_dbus_action_group_get(run->_session, _indicatorAddress.c_str(), path.c_str())), [](GActionGroup * groupptr) { g_clear_object(&groupptr); }); agWaitForActions(run->_actions); } void activateAction (const std::string &name, std::shared_ptr ¶meter) { g_action_group_activate_action(run->_actions.get(), name.c_str(), parameter.get()); } void activateAction (const std::string &name, GVariant * parameter = nullptr) { std::shared_ptr param; if (parameter != nullptr) param = std::shared_ptr(g_variant_ref_sink(parameter), [](GVariant * var) { g_variant_unref(var); }); return activateAction(name, param); } testing::AssertionResult expectActionExists (const gchar * nameStr, const std::string& name) { bool hasit = g_action_group_has_action(run->_actions.get(), name.c_str()); if (!hasit) { auto result = testing::AssertionFailure(); result << " Action: " << nameStr << std::endl << " Expected: " << "Exists" << std::endl << " Actual: " << "No action found" << std::endl; return result; } auto result = testing::AssertionSuccess(); return result; } template testing::AssertionResult expectEventuallyActionExists (Args&& ... args) { std::function func = [&]() { return expectActionExists(std::forward(args)...); }; return expectEventually(func); } testing::AssertionResult expectActionDoesNotExist (const gchar * nameStr, const std::string& name) { bool hasit = g_action_group_has_action(run->_actions.get(), name.c_str()); if (hasit) { auto result = testing::AssertionFailure(); result << " Action: " << nameStr << std::endl << " Expected: " << "No action found" << std::endl << " Actual: " << "Exists" << std::endl; return result; } auto result = testing::AssertionSuccess(); return result; } template testing::AssertionResult expectEventuallyActionDoesNotExist (Args&& ... args) { std::function func = [&]() { return expectActionDoesNotExist(std::forward(args)...); }; return expectEventually(func); } testing::AssertionResult expectActionEnabled (const char * nameStr, const char * typeStr, const std::string& name, bool enabled) { auto aenabled = g_action_group_get_action_enabled(run->_actions.get(), name.c_str()); if (enabled != aenabled) { auto result = testing::AssertionFailure(); result << " Action: " << nameStr << std::endl << " Expected: " << enabled << std::endl << " Actual: " << aenabled << std::endl; return result; } auto result = testing::AssertionSuccess(); return result; } template testing::AssertionResult expectEventuallyActionEnabled (Args&& ... args) { std::function func = [&]() { return expectActionEnabled(std::forward(args)...); }; return expectEventually(func); } testing::AssertionResult expectActionStateType (const char * nameStr, const char * typeStr, const std::string& name, const GVariantType * type) { auto atype = g_action_group_get_action_state_type(run->_actions.get(), name.c_str()); bool same = false; if (atype != nullptr) { same = g_variant_type_equal(atype, type); } if (!same) { auto result = testing::AssertionFailure(); result << " Action: " << nameStr << std::endl << " Expected: " << typeStr << std::endl << " Actual: " << (atype == nullptr ? "(null)" : g_variant_type_peek_string(atype)) << std::endl; return result; } auto result = testing::AssertionSuccess(); return result; } template testing::AssertionResult expectEventuallyActionStateType (Args&& ... args) { std::function func = [&]() { return expectActionStateType(std::forward(args)...); }; return expectEventually(func); } testing::AssertionResult expectActionActivationType (const char * nameStr, const char * typeStr, const std::string& name, const GVariantType * type) { auto atype = g_action_group_get_action_parameter_type(run->_actions.get(), name.c_str()); bool same = false; if (atype != nullptr) { same = g_variant_type_equal(atype, type); } if (!same) { auto result = testing::AssertionFailure(); result << " Action: " << nameStr << std::endl << " Expected: " << typeStr << std::endl << " Actual: " << (atype == nullptr ? "(null)" : g_variant_type_peek_string(atype)) << std::endl; return result; } auto result = testing::AssertionSuccess(); return result; } template testing::AssertionResult expectEventuallyActionActivationType (Args&& ... args) { std::function func = [&]() { return expectActionActivationType(std::forward(args)...); }; return expectEventually(func); } testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, std::shared_ptr varref) { auto aval = std::shared_ptr(g_action_group_get_action_state(run->_actions.get(), name.c_str()), [] (GVariant * varptr) { if (varptr != nullptr) g_variant_unref(varptr); }); bool match = false; if (aval != nullptr) { match = g_variant_equal(aval.get(), varref.get()); } if (!match) { gchar * attstr = nullptr; if (aval != nullptr) { attstr = g_variant_print(aval.get(), TRUE); } else { attstr = g_strdup("nullptr"); } auto result = testing::AssertionFailure(); result << " Action: " << nameStr << std::endl << " Expected: " << valueStr << std::endl << " Actual: " << attstr << std::endl; g_free(attstr); return result; } else { auto result = testing::AssertionSuccess(); return result; } } testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, GVariant * value) { auto varref = std::shared_ptr(g_variant_ref_sink(value), [](GVariant * varptr) { if (varptr != nullptr) g_variant_unref(varptr); }); return expectActionStateIs(nameStr, valueStr, name, varref); } testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, bool value) { GVariant * var = g_variant_new_boolean(value); return expectActionStateIs(nameStr, valueStr, name, var); } testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, std::string value) { GVariant * var = g_variant_new_string(value.c_str()); return expectActionStateIs(nameStr, valueStr, name, var); } testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, const char * value) { GVariant * var = g_variant_new_string(value); return expectActionStateIs(nameStr, valueStr, name, var); } testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, double value) { GVariant * var = g_variant_new_double(value); return expectActionStateIs(nameStr, valueStr, name, var); } testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, float value) { GVariant * var = g_variant_new_double(value); return expectActionStateIs(nameStr, valueStr, name, var); } template testing::AssertionResult expectEventuallyActionStateIs (Args&& ... args) { std::function func = [&]() { return expectActionStateIs(std::forward(args)...); }; return expectEventually(func); } private: std::shared_ptr getMenuAttributeVal (int location, std::shared_ptr& menu, const std::string& attribute, std::shared_ptr& value) { if (!(location < g_menu_model_get_n_items(menu.get()))) { return nullptr; } if (location >= g_menu_model_get_n_items(menu.get())) return nullptr; auto menuval = std::shared_ptr(g_menu_model_get_item_attribute_value(menu.get(), location, attribute.c_str(), g_variant_get_type(value.get())), [](GVariant * varptr) { if (varptr != nullptr) g_variant_unref(varptr); }); return menuval; } std::shared_ptr getMenuAttributeRecurse (std::vector::const_iterator menuLocation, std::vector::const_iterator menuEnd, const std::string& attribute, std::shared_ptr& value, std::shared_ptr& menu) { if (menuLocation == menuEnd) return nullptr; if (menuLocation + 1 == menuEnd) return getMenuAttributeVal(*menuLocation, menu, attribute, value); auto clearfunc = [](GMenuModel * modelptr) { g_clear_object(&modelptr); }; auto submenu = std::shared_ptr(g_menu_model_get_item_link(menu.get(), *menuLocation, G_MENU_LINK_SUBMENU), clearfunc); if (submenu == nullptr) submenu = std::shared_ptr(g_menu_model_get_item_link(menu.get(), *menuLocation, G_MENU_LINK_SECTION), clearfunc); if (submenu == nullptr) return nullptr; menuWaitForItems(submenu); return getMenuAttributeRecurse(menuLocation + 1, menuEnd, attribute, value, submenu); } protected: testing::AssertionResult expectMenuAttribute (const char * menuLocationStr, const char * attributeStr, const char * valueStr, const std::vector menuLocation, const std::string& attribute, GVariant * value) { auto varref = std::shared_ptr(g_variant_ref_sink(value), [](GVariant * varptr) { if (varptr != nullptr) g_variant_unref(varptr); }); auto attrib = getMenuAttributeRecurse(menuLocation.cbegin(), menuLocation.cend(), attribute, varref, run->_menu); bool same = false; if (attrib != nullptr && varref != nullptr) { same = g_variant_equal(attrib.get(), varref.get()); } if (!same) { gchar * attstr = nullptr; if (attrib != nullptr) { attstr = g_variant_print(attrib.get(), TRUE); } else { attstr = g_strdup("nullptr"); } auto result = testing::AssertionFailure(); result << " Menu: " << menuLocationStr << std::endl << " Attribute: " << attributeStr << std::endl << " Expected: " << valueStr << std::endl << " Actual: " << attstr << std::endl; g_free(attstr); return result; } else { auto result = testing::AssertionSuccess(); return result; } } testing::AssertionResult expectMenuAttribute (const char * menuLocationStr, const char * attributeStr, const char * valueStr, const std::vector menuLocation, const std::string& attribute, bool value) { GVariant * var = g_variant_new_boolean(value); return expectMenuAttribute(menuLocationStr, attributeStr, valueStr, menuLocation, attribute, var); } testing::AssertionResult expectMenuAttribute (const char * menuLocationStr, const char * attributeStr, const char * valueStr, const std::vector menuLocation, const std::string& attribute, std::string value) { GVariant * var = g_variant_new_string(value.c_str()); return expectMenuAttribute(menuLocationStr, attributeStr, valueStr, menuLocation, attribute, var); } testing::AssertionResult expectMenuAttribute (const char * menuLocationStr, const char * attributeStr, const char * valueStr, const std::vector menuLocation, const std::string& attribute, const char * value) { GVariant * var = g_variant_new_string(value); return expectMenuAttribute(menuLocationStr, attributeStr, valueStr, menuLocation, attribute, var); } template testing::AssertionResult expectEventuallyMenuAttribute (Args&& ... args) { std::function func = [&]() { return expectMenuAttribute(std::forward(args)...); }; return expectEventually(func); } /* Eventually Helpers */ #define _EVENTUALLY_HELPER(oper) \ template testing::AssertionResult expectEventually##oper (Args&& ... args) { \ std::function func = [&]() { \ return testing::internal::CmpHelper##oper(std::forward(args)...); \ }; \ return expectEventually(func); \ } _EVENTUALLY_HELPER(EQ); _EVENTUALLY_HELPER(NE); _EVENTUALLY_HELPER(LT); _EVENTUALLY_HELPER(GT); _EVENTUALLY_HELPER(STREQ); _EVENTUALLY_HELPER(STRNE); #undef _EVENTUALLY_HELPER }; /* Menu Attrib */ #define ASSERT_MENU_ATTRIB(menu, attrib, value) \ ASSERT_PRED_FORMAT3(IndicatorFixture::expectMenuAttribute, menu, attrib, value) #define EXPECT_MENU_ATTRIB(menu, attrib, value) \ EXPECT_PRED_FORMAT3(IndicatorFixture::expectMenuAttribute, menu, attrib, value) #define EXPECT_EVENTUALLY_MENU_ATTRIB(menu, attrib, value) \ EXPECT_PRED_FORMAT3(IndicatorFixture::expectEventuallyMenuAttribute, menu, attrib, value) /* Action Exists */ #define ASSERT_ACTION_EXISTS(action) \ ASSERT_PRED_FORMAT1(IndicatorFixture::expectActionExists, action) #define EXPECT_ACTION_EXISTS(action) \ EXPECT_PRED_FORMAT1(IndicatorFixture::expectActionExists, action) #define EXPECT_EVENTUALLY_ACTION_EXISTS(action) \ EXPECT_PRED_FORMAT1(IndicatorFixture::expectEventuallyActionExists, action) /* Action Does Not Exist */ #define ASSERT_ACTION_DOES_NOT_EXIST(action) \ ASSERT_PRED_FORMAT1(IndicatorFixture::expectActionDoesNotExist, action) #define EXPECT_ACTION_DOES_NOT_EXIST(action) \ EXPECT_PRED_FORMAT1(IndicatorFixture::expectActionDoesNotExist, action) #define EXPECT_EVENTUALLY_ACTION_DOES_NOT_EXIST(action) \ EXPECT_PRED_FORMAT1(IndicatorFixture::expectEventuallyActionDoesNotExist, action) /* Action Enabled */ #define ASSERT_ACTION_ENABLED(action, state) \ ASSERT_PRED_FORMAT2(IndicatorFixture::expectActionEnabled, action, state) #define EXPECT_ACTION_ENABLED(action, state) \ EXPECT_PRED_FORMAT2(IndicatorFixture::expectActionEnabled, action, state) #define EXPECT_EVENTUALLY_ACTION_ENABLED(action, state) \ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyActionEnabled, action, state) /* Action State */ #define ASSERT_ACTION_STATE(action, value) \ ASSERT_PRED_FORMAT2(IndicatorFixture::expectActionStateIs, action, value) #define EXPECT_ACTION_STATE(action, value) \ EXPECT_PRED_FORMAT2(IndicatorFixture::expectActionStateIs, action, value) #define EXPECT_EVENTUALLY_ACTION_STATE(action, value) \ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyActionStateIs, action, value) /* Action State Type */ #define ASSERT_ACTION_STATE_TYPE(action, type) \ ASSERT_PRED_FORMAT2(IndicatorFixture::expectActionStateType, action, type) #define EXPECT_ACTION_STATE_TYPE(action, type) \ EXPECT_PRED_FORMAT2(IndicatorFixture::expectActionStateType, action, type) #define EXPECT_EVENTUALLY_ACTION_STATE_TYPE(action, type) \ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyActionStateType, action, type) /* Action Activation Type */ #define ASSERT_ACTION_ACTIVATION_TYPE(action, type) \ ASSERT_PRED_FORMAT2(IndicatorFixture::expectActionActivationType, action, type) #define EXPECT_ACTION_ACTIVATION_TYPE(action, type) \ EXPECT_PRED_FORMAT2(IndicatorFixture::expectActionActivationType, action, type) #define EXPECT_EVENTUALLY_ACTION_ACTIVATION_TYPE(action, type) \ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyActionActivationType, action, type) /* Helpers */ #define EXPECT_EVENTUALLY_EQ(expected, actual) \ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyEQ, expected, actual) #define EXPECT_EVENTUALLY_NE(expected, actual) \ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyNE, expected, actual) #define EXPECT_EVENTUALLY_LT(expected, actual) \ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyLT, expected, actual) #define EXPECT_EVENTUALLY_GT(expected, actual) \ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyGT, expected, actual) #define EXPECT_EVENTUALLY_STREQ(expected, actual) \ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallySTREQ, expected, actual) #define EXPECT_EVENTUALLY_STRNE(expected, actual) \ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallySTRNE, expected, actual)