From 58f10c864fd301fbe509c232488cab7b5c99a6b8 Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Mon, 7 Sep 2015 15:11:19 +0200 Subject: test version for gmenuharness --- src/gmenuharness/MenuItemMatcher.cpp | 902 +++++++++++++++++++++++++++++++++++ 1 file changed, 902 insertions(+) create mode 100644 src/gmenuharness/MenuItemMatcher.cpp (limited to 'src/gmenuharness/MenuItemMatcher.cpp') diff --git a/src/gmenuharness/MenuItemMatcher.cpp b/src/gmenuharness/MenuItemMatcher.cpp new file mode 100644 index 0000000..2280ef5 --- /dev/null +++ b/src/gmenuharness/MenuItemMatcher.cpp @@ -0,0 +1,902 @@ +/* + * 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 . + * + * Authored by: Pete Woods + */ + +#include +#include +#include + +#include +#include + +using namespace std; + +namespace unity +{ + +namespace gmenuharness +{ + +namespace +{ + +enum class LinkType +{ + any, + section, + submenu +}; + +static string bool_to_string(bool value) +{ + return value? "true" : "false"; +} + +static shared_ptr get_action_group_attribute(const shared_ptr& actionGroup, const gchar* attribute) +{ + shared_ptr value( + g_action_group_get_action_state(actionGroup.get(), attribute), + &gvariant_deleter); + return value; +} + +static shared_ptr get_attribute(const shared_ptr menuItem, const gchar* attribute) +{ + shared_ptr value( + g_menu_item_get_attribute_value(menuItem.get(), attribute, nullptr), + &gvariant_deleter); + return value; +} + +static string get_string_attribute(const shared_ptr menuItem, const gchar* attribute) +{ + string result; + char* temp = nullptr; + if (g_menu_item_get_attribute(menuItem.get(), attribute, "s", &temp)) + { + result = temp; + g_free(temp); + } + return result; +} + +static pair split_action(const string& action) +{ + auto index = action.find('.'); + + if (index == string::npos) + { + return make_pair(string(), action); + } + + return make_pair(action.substr(0, index), action.substr(index + 1, action.size())); +} + +static string type_to_string(MenuItemMatcher::Type type) +{ + switch(type) + { + case MenuItemMatcher::Type::plain: + return "plain"; + case MenuItemMatcher::Type::checkbox: + return "checkbox"; + case MenuItemMatcher::Type::radio: + return "radio"; + } + + return string(); +} +} + +struct MenuItemMatcher::Priv +{ + void all(MatchResult& matchResult, const vector& location, + const shared_ptr& menu, + map>& actions) + { + int count = g_menu_model_get_n_items(menu.get()); + + if (m_items.size() != (unsigned int) count) + { + matchResult.failure( + location, + "Expected " + to_string(m_items.size()) + + " children, but found " + to_string(count)); + return; + } + + for (size_t i = 0; i < m_items.size(); ++i) + { + const auto& matcher = m_items.at(i); + matcher.match(matchResult, location, menu, actions, i); + } + } + + void startsWith(MatchResult& matchResult, const vector& location, + const shared_ptr& menu, + map>& actions) + { + int count = g_menu_model_get_n_items(menu.get()); + if (m_items.size() > (unsigned int) count) + { + matchResult.failure( + location, + "Expected at least " + to_string(m_items.size()) + + " children, but found " + to_string(count)); + return; + } + + for (size_t i = 0; i < m_items.size(); ++i) + { + const auto& matcher = m_items.at(i); + matcher.match(matchResult, location, menu, actions, i); + } + } + + void endsWith(MatchResult& matchResult, const vector& location, + const shared_ptr& menu, + map>& actions) + { + int count = g_menu_model_get_n_items(menu.get()); + if (m_items.size() > (unsigned int) count) + { + matchResult.failure( + location, + "Expected at least " + to_string(m_items.size()) + + " children, but found " + to_string(count)); + return; + } + + // match the last N items + size_t j; + for (size_t i = count - m_items.size(), j = 0; i < count && j < m_items.size(); ++i, ++j) + { + const auto& matcher = m_items.at(j); + matcher.match(matchResult, location, menu, actions, i); + } + } + + Type m_type = Type::plain; + + Mode m_mode = Mode::all; + + LinkType m_linkType = LinkType::any; + + shared_ptr m_expectedSize; + + shared_ptr m_label; + + shared_ptr m_icon; + + shared_ptr m_action; + + vector m_state_icons; + + vector>> m_attributes; + + vector>> m_pass_through_attributes; + + shared_ptr m_isToggled; + + vector m_items; + + vector>> m_activations; + + vector>> m_setActionStates; + + double m_maxDifference = 0.0; +}; + +MenuItemMatcher MenuItemMatcher::checkbox() +{ + MenuItemMatcher matcher; + matcher.type(Type::checkbox); + return matcher; +} + +MenuItemMatcher MenuItemMatcher::radio() +{ + MenuItemMatcher matcher; + matcher.type(Type::radio); + return matcher; +} + +MenuItemMatcher::MenuItemMatcher() : + p(new Priv) +{ +} + +MenuItemMatcher::~MenuItemMatcher() +{ +} + +MenuItemMatcher::MenuItemMatcher(const MenuItemMatcher& other) : + p(new Priv) +{ + *this = other; +} + +MenuItemMatcher::MenuItemMatcher(MenuItemMatcher&& other) +{ + *this = move(other); +} + +MenuItemMatcher& MenuItemMatcher::operator=(const MenuItemMatcher& other) +{ + p->m_type = other.p->m_type; + p->m_mode = other.p->m_mode; + p->m_expectedSize = other.p->m_expectedSize; + p->m_label = other.p->m_label; + p->m_icon = other.p->m_icon; + p->m_action = other.p->m_action; + p->m_state_icons = other.p->m_state_icons; + p->m_attributes = other.p->m_attributes; + p->m_pass_through_attributes = other.p->m_pass_through_attributes; + p->m_isToggled = other.p->m_isToggled; + p->m_linkType = other.p->m_linkType; + p->m_items = other.p->m_items; + p->m_activations = other.p->m_activations; + p->m_setActionStates = other.p->m_setActionStates; + p->m_maxDifference = other.p->m_maxDifference; + return *this; +} + +MenuItemMatcher& MenuItemMatcher::operator=(MenuItemMatcher&& other) +{ + p = move(other.p); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::type(Type type) +{ + p->m_type = type; + return *this; +} + +MenuItemMatcher& MenuItemMatcher::label(const string& label) +{ + p->m_label = make_shared(label); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::action(const string& action) +{ + p->m_action = make_shared(action); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::state_icons(const std::vector& state_icons) +{ + p->m_state_icons = state_icons; + return *this; +} + +MenuItemMatcher& MenuItemMatcher::icon(const string& icon) +{ + p->m_icon = make_shared(icon); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::widget(const string& widget) +{ + return string_attribute("x-canonical-type", widget); +} + +MenuItemMatcher& MenuItemMatcher::pass_through_attribute(const string& actionName, const shared_ptr& value) +{ + p->m_pass_through_attributes.emplace_back(actionName, value); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::pass_through_boolean_attribute(const string& actionName, bool value) +{ + return pass_through_attribute( + actionName, + shared_ptr(g_variant_new_boolean(value), + &gvariant_deleter)); +} + +MenuItemMatcher& MenuItemMatcher::pass_through_string_attribute(const string& actionName, const string& value) +{ + return pass_through_attribute( + actionName, + shared_ptr(g_variant_new_string(value.c_str()), + &gvariant_deleter)); +} + +MenuItemMatcher& MenuItemMatcher::pass_through_double_attribute(const std::string& actionName, double value) +{ + return pass_through_attribute( + actionName, + shared_ptr(g_variant_new_double(value), + &gvariant_deleter)); +} + +MenuItemMatcher& MenuItemMatcher::round_doubles(double maxDifference) +{ + p->m_maxDifference = maxDifference; + return *this; +} + +MenuItemMatcher& MenuItemMatcher::attribute(const string& name, const shared_ptr& value) +{ + p->m_attributes.emplace_back(name, value); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::boolean_attribute(const string& name, bool value) +{ + return attribute( + name, + shared_ptr(g_variant_new_boolean(value), + &gvariant_deleter)); +} + +MenuItemMatcher& MenuItemMatcher::string_attribute(const string& name, const string& value) +{ + return attribute( + name, + shared_ptr(g_variant_new_string(value.c_str()), + &gvariant_deleter)); +} + +MenuItemMatcher& MenuItemMatcher::toggled(bool isToggled) +{ + p->m_isToggled = make_shared(isToggled); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::mode(Mode mode) +{ + p->m_mode = mode; + return *this; +} + +MenuItemMatcher& MenuItemMatcher::submenu() +{ + p->m_linkType = LinkType::submenu; + return *this; +} + +MenuItemMatcher& MenuItemMatcher::section() +{ + p->m_linkType = LinkType::section; + return *this; +} + +MenuItemMatcher& MenuItemMatcher::is_empty() +{ + return has_exactly(0); +} + +MenuItemMatcher& MenuItemMatcher::has_exactly(size_t children) +{ + p->m_expectedSize = make_shared(children); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::item(const MenuItemMatcher& item) +{ + p->m_items.emplace_back(item); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::item(MenuItemMatcher&& item) +{ + p->m_items.emplace_back(item); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::pass_through_activate(std::string const& action, const shared_ptr& parameter) +{ + p->m_activations.emplace_back(action, parameter); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::activate(const shared_ptr& parameter) +{ + p->m_activations.emplace_back(string(), parameter); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::set_pass_through_action_state(const std::string& action, const std::shared_ptr& state) +{ + p->m_setActionStates.emplace_back(action, state); + return *this; +} + +MenuItemMatcher& MenuItemMatcher::set_action_state(const std::shared_ptr& state) +{ + p->m_setActionStates.emplace_back("", state); + return *this; +} + +void MenuItemMatcher::match( + MatchResult& matchResult, + const vector& parentLocation, + const shared_ptr& menu, + map>& actions, + int index) const +{ + shared_ptr menuItem(g_menu_item_new_from_model(menu.get(), index), &g_object_deleter); + + vector location(parentLocation); + location.emplace_back(index); + + string action = get_string_attribute(menuItem, G_MENU_ATTRIBUTE_ACTION); + + bool isCheckbox = false; + bool isRadio = false; + bool isToggled = false; + + pair idPair; + shared_ptr actionGroup; + shared_ptr state; + + if (!action.empty()) + { + idPair = split_action(action); + actionGroup = actions[idPair.first]; + state = shared_ptr(g_action_group_get_action_state(actionGroup.get(), + idPair.second.c_str()), + &gvariant_deleter); + auto attributeTarget = get_attribute(menuItem, G_MENU_ATTRIBUTE_TARGET); + + if (attributeTarget && state) + { + isToggled = g_variant_equal(state.get(), attributeTarget.get()); + isRadio = true; + } + else if (state + && g_variant_is_of_type(state.get(), G_VARIANT_TYPE_BOOLEAN)) + { + isToggled = g_variant_get_boolean(state.get()); + isCheckbox = true; + } + } + + Type actualType = Type::plain; + if (isCheckbox) + { + actualType = Type::checkbox; + } + else if (isRadio) + { + actualType = Type::radio; + } + + if (actualType != p->m_type) + { + matchResult.failure( + location, + "Expected " + type_to_string(p->m_type) + ", found " + + type_to_string(actualType)); + } + + string label = get_string_attribute(menuItem, G_MENU_ATTRIBUTE_LABEL); + if (p->m_label && (*p->m_label) != label) + { + matchResult.failure( + location, + "Expected label '" + *p->m_label + "', but found '" + label + + "'"); + } + + string icon = get_string_attribute(menuItem, G_MENU_ATTRIBUTE_ICON); + if (p->m_icon && (*p->m_icon) != icon) + { + matchResult.failure( + location, + "Expected icon '" + *p->m_icon + "', but found '" + icon + "'"); + } + + if (p->m_action && (*p->m_action) != action) + { + matchResult.failure( + location, + "Expected action '" + *p->m_action + "', but found '" + action + + "'"); + } + + if (!p->m_state_icons.empty() && !state) + { + matchResult.failure( + location, + "Expected state icons but no state was found"); + } + else if (!p->m_state_icons.empty() && state && + !g_variant_is_of_type(state.get(), G_VARIANT_TYPE_VARDICT)) + { + matchResult.failure( + location, + "Expected state icons vardict, found " + + type_to_string(actualType)); + } + else if (!p->m_state_icons.empty() && state && + g_variant_is_of_type(state.get(), G_VARIANT_TYPE_VARDICT)) + { + std::vector actual_state_icons; + GVariantIter it; + gchar* key; + GVariant* value; + + g_variant_iter_init(&it, state.get()); + while (g_variant_iter_loop(&it, "{sv}", &key, &value)) + { + if (std::string(key) == "icon") { + auto gicon = g_icon_deserialize(value); + if (gicon && G_IS_THEMED_ICON(gicon)) + { + auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(gicon)); + // Just take the first icon in the list (there is only ever one) + actual_state_icons.push_back(iconNames[0]); + g_object_unref(gicon); + } + } + else if (std::string(key) == "icons" && g_variant_is_of_type(value, G_VARIANT_TYPE("av"))) + { + // If we find "icons" in the map, clear any icons we may have found in "icon", + // then break from the loop as we have found all icons now. + actual_state_icons.clear(); + GVariantIter icon_it; + GVariant* icon_value; + + g_variant_iter_init(&icon_it, value); + while (g_variant_iter_loop(&icon_it, "v", &icon_value)) + { + auto gicon = g_icon_deserialize(icon_value); + if (gicon && G_IS_THEMED_ICON(gicon)) + { + auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(gicon)); + // Just take the first icon in the list (there is only ever one) + actual_state_icons.push_back(iconNames[0]); + g_object_unref(gicon); + } + } + // We're breaking out of g_variant_iter_loop here so clean up + g_variant_unref(value); + g_free(key); + break; + } + } + + if (p->m_state_icons != actual_state_icons) + { + std::string expected_icons; + for (unsigned int i = 0; i < p->m_state_icons.size(); ++i) + { + expected_icons += i == 0 ? p->m_state_icons[i] : ", " + p->m_state_icons[i]; + } + std::string actual_icons; + for (unsigned int i = 0; i < actual_state_icons.size(); ++i) + { + actual_icons += i == 0 ? actual_state_icons[i] : ", " + actual_state_icons[i]; + } + matchResult.failure( + location, + "Expected state_icons == {" + expected_icons + + "} but found {" + actual_icons + "}"); + } + } + + for (const auto& e: p->m_pass_through_attributes) + { + string actionName = get_string_attribute(menuItem, e.first.c_str()); + if (actionName.empty()) + { + matchResult.failure( + location, + "Could not find action name '" + e.first + "'"); + } + else + { + auto passThroughIdPair = split_action(actionName); + auto actionGroup = actions[passThroughIdPair.first]; + if (actionGroup) + { + auto value = get_action_group_attribute( + actionGroup, passThroughIdPair.second.c_str()); + if (!value) + { + matchResult.failure( + location, + "Expected pass-through attribute '" + e.first + + "' was not present"); + } + else if (!g_variant_is_of_type(e.second.get(), g_variant_get_type(value.get()))) + { + std::string expectedType = g_variant_get_type_string(e.second.get()); + std::string actualType = g_variant_get_type_string(value.get()); + matchResult.failure( + location, + "Expected pass-through attribute type '" + expectedType + + "' but found '" + actualType + "'"); + } + else if (g_variant_compare(e.second.get(), value.get())) + { + bool reportMismatch = true; + if (g_strcmp0(g_variant_get_type_string(value.get()),"d") == 0 && p->m_maxDifference) + { + auto actualDouble = g_variant_get_double(value.get()); + auto expectedDouble = g_variant_get_double(e.second.get()); + auto difference = actualDouble-expectedDouble; + if (difference < 0) difference = difference * -1.0; + if (difference <= p->m_maxDifference) + { + reportMismatch = false; + } + } + if (reportMismatch) + { + gchar* expectedString = g_variant_print(e.second.get(), true); + gchar* actualString = g_variant_print(value.get(), true); + matchResult.failure( + location, + "Expected pass-through attribute '" + e.first + + "' == " + expectedString + " but found " + + actualString); + + g_free(expectedString); + g_free(actualString); + } + } + } + else + { + matchResult.failure(location, "Could not find action group for ID '" + passThroughIdPair.first + "'"); + } + } + } + + for (const auto& e: p->m_attributes) + { + auto value = get_attribute(menuItem, e.first.c_str()); + if (!value) + { + matchResult.failure(location, + "Expected attribute '" + e.first + + "' could not be found"); + } + else if (!g_variant_is_of_type(e.second.get(), g_variant_get_type(value.get()))) + { + std::string expectedType = g_variant_get_type_string(e.second.get()); + std::string actualType = g_variant_get_type_string(value.get()); + matchResult.failure( + location, + "Expected attribute type '" + expectedType + + "' but found '" + actualType + "'"); + } + else if (g_variant_compare(e.second.get(), value.get())) + { + gchar* expectedString = g_variant_print(e.second.get(), true); + gchar* actualString = g_variant_print(value.get(), true); + matchResult.failure( + location, + "Expected attribute '" + e.first + "' == " + expectedString + + ", but found " + actualString); + g_free(expectedString); + g_free(actualString); + } + } + + if (p->m_isToggled && (*p->m_isToggled) != isToggled) + { + matchResult.failure( + location, + "Expected toggled = " + bool_to_string(*p->m_isToggled) + + ", but found " + bool_to_string(isToggled)); + } + + if (!matchResult.success()) + { + return; + } + + if (!p->m_items.empty() || p->m_expectedSize) + { + shared_ptr link; + + switch (p->m_linkType) + { + case LinkType::any: + { + link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SUBMENU), &g_object_deleter); + if (!link) + { + link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SECTION), &g_object_deleter); + } + break; + } + case LinkType::submenu: + { + link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SUBMENU), &g_object_deleter); + break; + } + case LinkType::section: + { + link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SECTION), &g_object_deleter); + break; + } + } + + + if (!link) + { + if (p->m_expectedSize) + { + matchResult.failure( + location, + "Expected " + to_string(*p->m_expectedSize) + + " children, but found none"); + } + else + { + matchResult.failure( + location, + "Expected " + to_string(p->m_items.size()) + + " children, but found none"); + } + return; + } + else + { + while (true) + { + MatchResult childMatchResult(matchResult.createChild()); + + if (p->m_expectedSize + && *p->m_expectedSize + != (unsigned int) g_menu_model_get_n_items( + link.get())) + { + childMatchResult.failure( + location, + "Expected " + to_string(*p->m_expectedSize) + + " child items, but found " + + to_string( + g_menu_model_get_n_items( + link.get()))); + } + else if (!p->m_items.empty()) + { + switch (p->m_mode) + { + case Mode::all: + p->all(childMatchResult, location, link, actions); + break; + case Mode::starts_with: + p->startsWith(childMatchResult, location, link, actions); + break; + case Mode::ends_with: + p->endsWith(childMatchResult, location, link, actions); + break; + } + } + + if (childMatchResult.success()) + { + matchResult.merge(childMatchResult); + break; + } + else + { + if (matchResult.hasTimedOut()) + { + matchResult.merge(childMatchResult); + break; + } + menuWaitForItems(link); + } + } + } + } + + + for (const auto& a: p->m_setActionStates) + { + auto stateAction = action; + auto stateIdPair = idPair; + auto stateActionGroup = actionGroup; + if (!a.first.empty()) + { + stateAction = get_string_attribute(menuItem, a.first.c_str());; + stateIdPair = split_action(stateAction); + stateActionGroup = actions[stateIdPair.first]; + } + + if (stateAction.empty()) + { + matchResult.failure( + location, + "Tried to set action state, but no action was found"); + } + else if(!stateActionGroup) + { + matchResult.failure( + location, + "Tried to set action state for action group '" + stateIdPair.first + + "', but action group wasn't found"); + } + else if (!g_action_group_has_action(stateActionGroup.get(), stateIdPair.second.c_str())) + { + matchResult.failure( + location, + "Tried to set action state for action '" + stateAction + + "', but action was not found"); + } + else + { + g_action_group_change_action_state(stateActionGroup.get(), stateIdPair.second.c_str(), + g_variant_ref(a.second.get())); + } + + // FIXME this is a dodgy way to ensure the action state change gets dispatched + menuWaitForItems(menu, 100); + } + + for (const auto& a: p->m_activations) + { + string tmpAction = action; + auto tmpIdPair = idPair; + auto tmpActionGroup = actionGroup; + if (!a.first.empty()) + { + tmpAction = get_string_attribute(menuItem, a.first.c_str()); + tmpIdPair = split_action(tmpAction); + tmpActionGroup = actions[tmpIdPair.first]; + } + + if (tmpAction.empty()) + { + matchResult.failure( + location, + "Tried to activate action, but no action was found"); + } + else if(!tmpActionGroup) + { + matchResult.failure( + location, + "Tried to activate action group '" + tmpIdPair.first + + "', but action group wasn't found"); + } + else if (!g_action_group_has_action(tmpActionGroup.get(), tmpIdPair.second.c_str())) + { + matchResult.failure( + location, + "Tried to activate action '" + tmpAction + "', but action was not found"); + } + else + { + if (a.second) + { + g_action_group_activate_action(tmpActionGroup.get(), tmpIdPair.second.c_str(), + g_variant_ref(a.second.get())); + } + else + { + g_action_group_activate_action(tmpActionGroup.get(), tmpIdPair.second.c_str(), nullptr); + } + + // FIXME this is a dodgy way to ensure the activation gets dispatched + menuWaitForItems(menu, 100); + } + } +} + +} // namepsace gmenuharness + +} // namespace unity -- cgit v1.2.3 From 22de41f3cc382adbf06be8642af5cfa7ec664c8e Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Mon, 21 Sep 2015 16:01:33 +0200 Subject: Added separated integration tests for desktop and phone, with different instances of pulseaudio --- include/unity/gmenuharness/MenuItemMatcher.h | 4 + src/gmenuharness/MenuItemMatcher.cpp | 16 ++ tests/integration/indicator-sound-test-base.cpp | 76 +++++++++- tests/integration/indicator-sound-test-base.h | 11 +- tests/integration/test-indicator.cpp | 186 +++++++++++++++++++++--- tests/integration/utils/dbus-pulse-volume.cpp | 5 + 6 files changed, 275 insertions(+), 23 deletions(-) (limited to 'src/gmenuharness/MenuItemMatcher.cpp') diff --git a/include/unity/gmenuharness/MenuItemMatcher.h b/include/unity/gmenuharness/MenuItemMatcher.h index dc1d6f3..6f7f94c 100644 --- a/include/unity/gmenuharness/MenuItemMatcher.h +++ b/include/unity/gmenuharness/MenuItemMatcher.h @@ -93,6 +93,10 @@ public: MenuItemMatcher& string_attribute(const std::string& name, const std::string& value); + MenuItemMatcher& int32_attribute(const std::string& name, int value); + + MenuItemMatcher& double_attribute(const std::string& name, double value); + MenuItemMatcher& toggled(bool toggled); MenuItemMatcher& mode(Mode mode); diff --git a/src/gmenuharness/MenuItemMatcher.cpp b/src/gmenuharness/MenuItemMatcher.cpp index 2280ef5..ab22364 100644 --- a/src/gmenuharness/MenuItemMatcher.cpp +++ b/src/gmenuharness/MenuItemMatcher.cpp @@ -354,6 +354,22 @@ MenuItemMatcher& MenuItemMatcher::string_attribute(const string& name, const str &gvariant_deleter)); } +MenuItemMatcher& MenuItemMatcher::int32_attribute(const std::string& name, int value) +{ + return attribute( + name, + shared_ptr(g_variant_new_int32 (value), + &gvariant_deleter)); +} + +MenuItemMatcher& MenuItemMatcher::double_attribute(const std::string& name, double value) +{ + return attribute( + name, + shared_ptr(g_variant_new_double (value), + &gvariant_deleter)); +} + MenuItemMatcher& MenuItemMatcher::toggled(bool isToggled) { p->m_isToggled = make_shared(isToggled); diff --git a/tests/integration/indicator-sound-test-base.cpp b/tests/integration/indicator-sound-test-base.cpp index 49e2ec5..a839fbc 100644 --- a/tests/integration/indicator-sound-test-base.cpp +++ b/tests/integration/indicator-sound-test-base.cpp @@ -45,7 +45,7 @@ IndicatorSoundTestBase::~IndicatorSoundTestBase() } -bool IndicatorSoundTestBase::setVolume(QString const &role, double volume) +bool IndicatorSoundTestBase::setStreamRestoreVolume(QString const &role, double volume) { QProcess setVolume; setVolume.start(VOLUME_SET_BIN, QStringList() @@ -60,6 +60,25 @@ bool IndicatorSoundTestBase::setVolume(QString const &role, double volume) return setVolume.exitCode() == 0; } +bool IndicatorSoundTestBase::setSinkVolume(double volume) +{ + QString volume_percentage = QString("%1\%").arg(volume*100); + QProcess setVolume; + setVolume.start("pactl", QStringList() + << "-s" + << "127.0.0.1" + << "set-sink-volume" + << "0" + << volume_percentage); + if (!setVolume.waitForStarted()) + return false; + + if (!setVolume.waitForFinished()) + return false; + + return setVolume.exitCode() == 0; +} + bool IndicatorSoundTestBase::startTestSound(QString const &role) { testSoundProcess.terminate(); @@ -81,7 +100,37 @@ void IndicatorSoundTestBase::stopTestSound() testSoundProcess.terminate(); } -void IndicatorSoundTestBase::startPulse() +void IndicatorSoundTestBase::startPulseDesktop() +{ + try + { + pulseaudio.reset( + new QProcessDBusService(DBusTypes::DBUS_PULSE, + QDBusConnection::SessionBus, + "pulseaudio", + QStringList() << "--start" + << "-vvvv" + << "--disable-shm=true" + << "--daemonize=false" + << "--use-pid-file=false" + << "--system=false" + << "--exit-idle-time=-1" + << "-n" + << "--load=module-null-sink" + << "--log-target=file:/tmp/pulse-daemon.log" + << "--load=module-dbus-protocol" + << "--load=module-native-protocol-tcp auth-ip-acl=127.0.0.1" + )); + pulseaudio->start(dbusTestRunner.sessionConnection()); + } + catch (exception const& e) + { + cout << "pulseaudio(): " << e.what() << endl; + throw; + } +} + +void IndicatorSoundTestBase::startPulsePhone() { try { @@ -151,8 +200,6 @@ void IndicatorSoundTestBase::startIndicator() } } -// /usr/bin/pulseaudio --start -vvvv --disable-shm=true --daemonize=false --use-pid-file=false --system=false --exit-idle-time=-1 -n "--load=module-null-sink sink_name=multimedia" --load=module-stream-restore - mh::MenuMatcher::Parameters IndicatorSoundTestBase::desktopParameters() { return mh::MenuMatcher::Parameters( @@ -161,6 +208,14 @@ mh::MenuMatcher::Parameters IndicatorSoundTestBase::desktopParameters() "/com/canonical/indicator/sound/desktop"); } +mh::MenuMatcher::Parameters IndicatorSoundTestBase::phoneParameters() +{ + return mh::MenuMatcher::Parameters( + "com.canonical.indicator.sound", + { { "indicator", "/com/canonical/indicator/sound" } }, + "/com/canonical/indicator/sound/phone"); +} + void IndicatorSoundTestBase::SetUp() { setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, true); @@ -216,9 +271,22 @@ unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::volumeSlider(double return mh::MenuItemMatcher().radio() .label("Volume") .round_doubles(0.1) + .int32_attribute("target", 0) + .double_attribute("min-value", 0.0) + .double_attribute("max-value", 1.0) + .double_attribute("step", 0.01) + .string_attribute("x-canonical-type", "com.canonical.unity.slider") .pass_through_double_attribute("action", volume); } +unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::silentModeSwitch(bool toggled) +{ + return mh::MenuItemMatcher::checkbox() + .label("Silent Mode") + .action("indicator.silent-mode") + .toggled(toggled); +} + bool IndicatorSoundTestBase::waitMenuChange() { if (!menu_interface_) diff --git a/tests/integration/indicator-sound-test-base.h b/tests/integration/indicator-sound-test-base.h index 04a579f..019efc9 100644 --- a/tests/integration/indicator-sound-test-base.h +++ b/tests/integration/indicator-sound-test-base.h @@ -45,10 +45,13 @@ protected: void TearDown() override; void startIndicator(); - void startPulse(); + void startPulseDesktop(); + void startPulsePhone(); void startAccountsService(); - bool setVolume(QString const &role, double volume); + bool setStreamRestoreVolume(QString const &role, double volume); + + bool setSinkVolume(double volume); bool startTestSound(QString const &role); @@ -58,8 +61,12 @@ protected: static unity::gmenuharness::MenuMatcher::Parameters desktopParameters(); + static unity::gmenuharness::MenuMatcher::Parameters phoneParameters(); + static unity::gmenuharness::MenuItemMatcher volumeSlider(double volume); + static unity::gmenuharness::MenuItemMatcher silentModeSwitch(bool toggled); + bool waitMenuChange(); bool waitVolumeChangedInIndicator(); diff --git a/tests/integration/test-indicator.cpp b/tests/integration/test-indicator.cpp index bb5adec..a248658 100644 --- a/tests/integration/test-indicator.cpp +++ b/tests/integration/test-indicator.cpp @@ -32,16 +32,15 @@ class TestIndicator: public IndicatorSoundTestBase { }; -TEST_F(TestIndicator, ChangeRoleVolume) +TEST_F(TestIndicator, PhoneChangeRoleVolume) { double INITIAL_VOLUME = 0.0; ASSERT_NO_THROW(startAccountsService()); - ASSERT_NO_THROW(startPulse()); + ASSERT_NO_THROW(startPulsePhone()); // initialize volumes in pulseaudio - EXPECT_TRUE(setVolume("mutimedia", INITIAL_VOLUME)); - EXPECT_TRUE(setVolume("alert", INITIAL_VOLUME)); + EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME)); // start now the indicator, so it picks the new volumes ASSERT_NO_THROW(startIndicator()); @@ -53,7 +52,7 @@ TEST_F(TestIndicator, ChangeRoleVolume) double randomVolume = randInt / 100.0; // set an initial volume to the alert role - setVolume("alert", 1.0); + setStreamRestoreVolume("alert", 1.0); EXPECT_TRUE(waitVolumeChangedInIndicator()); // play a test sound, it should change the role in the indicator @@ -61,31 +60,31 @@ TEST_F(TestIndicator, ChangeRoleVolume) EXPECT_TRUE(waitVolumeChangedInIndicator()); // set the random volume to the multimedia role - EXPECT_TRUE(setVolume("multimedia", randomVolume)); + EXPECT_TRUE(setStreamRestoreVolume("multimedia", randomVolume)); if (randomVolume != INITIAL_VOLUME) { EXPECT_TRUE(waitVolumeChangedInIndicator()); } // check the indicator - EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters()) + EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters()) .item(mh::MenuItemMatcher() .action("indicator.root") .string_attribute("x-canonical-type", "com.canonical.indicator.root") + .string_attribute("x-canonical-scroll-action", "indicator.scroll") .string_attribute("x-canonical-secondary-action", "indicator.mute") + .string_attribute("submenu-action", "indicator.indicator-shown") .mode(mh::MenuItemMatcher::Mode::starts_with) .submenu() .item(mh::MenuItemMatcher() .section() - .item(mh::MenuItemMatcher().checkbox() - .label("Mute") - ) + .item(silentModeSwitch(false)) .item(volumeSlider(randomVolume)) ) ).match()); // check that the last item is Sound Settings - EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters()) + EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters()) .item(mh::MenuItemMatcher() .action("indicator.root") .string_attribute("x-canonical-type", "com.canonical.indicator.root") @@ -94,6 +93,7 @@ TEST_F(TestIndicator, ChangeRoleVolume) .submenu() .item(mh::MenuItemMatcher() .label("Sound Settings…") + .action("indicator.phone-settings") ) ).match()); @@ -106,6 +106,97 @@ TEST_F(TestIndicator, ChangeRoleVolume) EXPECT_TRUE(waitVolumeChangedInIndicator()); } + // check the initial volume for the alert role + EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters()) + .item(mh::MenuItemMatcher() + .action("indicator.root") + .string_attribute("x-canonical-type", "com.canonical.indicator.root") + .string_attribute("x-canonical-scroll-action", "indicator.scroll") + .string_attribute("x-canonical-secondary-action", "indicator.mute") + .string_attribute("submenu-action", "indicator.indicator-shown") + .mode(mh::MenuItemMatcher::Mode::starts_with) + .submenu() + .item(mh::MenuItemMatcher() + .section() + .item(silentModeSwitch(false)) + .item(volumeSlider(1.0)) + ) + ).match()); + + // check that the last item is Sound Settings + EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters()) + .item(mh::MenuItemMatcher() + .action("indicator.root") + .string_attribute("x-canonical-type", "com.canonical.indicator.root") + .string_attribute("x-canonical-secondary-action", "indicator.mute") + .mode(mh::MenuItemMatcher::Mode::ends_with) + .submenu() + .item(mh::MenuItemMatcher() + .label("Sound Settings…") + .action("indicator.phone-settings") + ) + ).match()); +} + +TEST_F(TestIndicator, PhoneBasicInitialVolume) +{ + double INITIAL_VOLUME = 0.0; + + ASSERT_NO_THROW(startAccountsService()); + ASSERT_NO_THROW(startPulsePhone()); + + // initialize volumes in pulseaudio + EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME)); + + // start now the indicator, so it picks the new volumes + ASSERT_NO_THROW(startIndicator()); + + // check the initial volume for the alert role + EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters()) + .item(mh::MenuItemMatcher() + .action("indicator.root") + .string_attribute("x-canonical-type", "com.canonical.indicator.root") + .string_attribute("x-canonical-scroll-action", "indicator.scroll") + .string_attribute("x-canonical-secondary-action", "indicator.mute") + .string_attribute("submenu-action", "indicator.indicator-shown") + .mode(mh::MenuItemMatcher::Mode::starts_with) + .submenu() + .item(mh::MenuItemMatcher() + .section() + .item(silentModeSwitch(false)) + .item(volumeSlider(INITIAL_VOLUME)) + ) + ).match()); + + // check that the last item is Sound Settings + EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters()) + .item(mh::MenuItemMatcher() + .action("indicator.root") + .string_attribute("x-canonical-type", "com.canonical.indicator.root") + .string_attribute("x-canonical-secondary-action", "indicator.mute") + .mode(mh::MenuItemMatcher::Mode::ends_with) + .submenu() + .item(mh::MenuItemMatcher() + .label("Sound Settings…") + .action("indicator.phone-settings") + ) + ).match()); +} + +TEST_F(TestIndicator, DesktopBasicInitialVolume) +{ + double INITIAL_VOLUME = 0.0; + + ASSERT_NO_THROW(startAccountsService()); + ASSERT_NO_THROW(startPulseDesktop()); + + // initialize volumes in pulseaudio + EXPECT_FALSE(setStreamRestoreVolume("alert", INITIAL_VOLUME)); + EXPECT_TRUE(setSinkVolume(INITIAL_VOLUME)); + + // start now the indicator, so it picks the new volumes + ASSERT_NO_THROW(startIndicator()); + // check the initial volume for the alert role EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters()) .item(mh::MenuItemMatcher() @@ -119,7 +210,7 @@ TEST_F(TestIndicator, ChangeRoleVolume) .item(mh::MenuItemMatcher().checkbox() .label("Mute") ) - .item(volumeSlider(1.0)) + .item(volumeSlider(INITIAL_VOLUME)) ) ).match()); @@ -137,20 +228,42 @@ TEST_F(TestIndicator, ChangeRoleVolume) ).match()); } -TEST_F(TestIndicator, BasicInitialVolume) +TEST_F(TestIndicator, DesktopChangeRoleVolume) { double INITIAL_VOLUME = 0.0; ASSERT_NO_THROW(startAccountsService()); - ASSERT_NO_THROW(startPulse()); + ASSERT_NO_THROW(startPulseDesktop()); // initialize volumes in pulseaudio - EXPECT_TRUE(setVolume("alert", INITIAL_VOLUME)); + // expect false for stream restore, because the module + // is not loaded in the desktop pulseaudio instance + EXPECT_FALSE(setStreamRestoreVolume("mutimedia", INITIAL_VOLUME)); + EXPECT_FALSE(setStreamRestoreVolume("alert", INITIAL_VOLUME)); + + EXPECT_TRUE(setSinkVolume(INITIAL_VOLUME)); // start now the indicator, so it picks the new volumes ASSERT_NO_THROW(startIndicator()); - // check the initial volume for the alert role + // Generate a random volume + QTime now = QTime::currentTime(); + qsrand(now.msec()); + int randInt = qrand() % 100; + double randomVolume = randInt / 100.0; + +// // play a test sound, it should NOT change the role in the indicator + EXPECT_TRUE(startTestSound("multimedia")); + EXPECT_FALSE(waitVolumeChangedInIndicator()); + + // set the random volume + EXPECT_TRUE(setSinkVolume(randomVolume)); + if (randomVolume != INITIAL_VOLUME) + { + EXPECT_FALSE(waitVolumeChangedInIndicator()); + } + + // check the indicator EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters()) .item(mh::MenuItemMatcher() .action("indicator.root") @@ -163,7 +276,7 @@ TEST_F(TestIndicator, BasicInitialVolume) .item(mh::MenuItemMatcher().checkbox() .label("Mute") ) - .item(volumeSlider(INITIAL_VOLUME)) + .item(volumeSlider(randomVolume)) ) ).match()); @@ -179,6 +292,45 @@ TEST_F(TestIndicator, BasicInitialVolume) .label("Sound Settings…") ) ).match()); + + // stop the test sound, the role should change again to alert + stopTestSound(); + + // although we were playing something in the multimedia role + // the server does not have the streamrestore module, so + // the volume does not change (the role does not change) + EXPECT_FALSE(waitVolumeChangedInIndicator()); + + // check the initial volume for the alert role + EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters()) + .item(mh::MenuItemMatcher() + .action("indicator.root") + .string_attribute("x-canonical-type", "com.canonical.indicator.root") + .string_attribute("x-canonical-scroll-action", "indicator.scroll") + .string_attribute("x-canonical-secondary-action", "indicator.mute") + .string_attribute("submenu-action", "indicator.indicator-shown") + .mode(mh::MenuItemMatcher::Mode::starts_with) + .submenu() + .item(mh::MenuItemMatcher() + .section() + .item(silentModeSwitch(false)) + .item(volumeSlider(randomVolume)) + ) + ).match()); + + // check that the last item is Sound Settings + EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters()) + .item(mh::MenuItemMatcher() + .action("indicator.root") + .string_attribute("x-canonical-type", "com.canonical.indicator.root") + .string_attribute("x-canonical-secondary-action", "indicator.mute") + .mode(mh::MenuItemMatcher::Mode::ends_with) + .submenu() + .item(mh::MenuItemMatcher() + .label("Sound Settings…") + .action("indicator.phone-settings") + ) + ).match()); } } // namespace diff --git a/tests/integration/utils/dbus-pulse-volume.cpp b/tests/integration/utils/dbus-pulse-volume.cpp index a282f77..c8b6ae6 100644 --- a/tests/integration/utils/dbus-pulse-volume.cpp +++ b/tests/integration/utils/dbus-pulse-volume.cpp @@ -150,6 +150,11 @@ bool DBusPulseVolume::setVolume(QString const & role, double volume) } } } + else + { + qWarning() << "SetVolume::setVolume(): role " << role << " was not found."; + return false; + } return true; } -- cgit v1.2.3 From b9ef4f6abdefb7b1d31d34f35b742016f1b5b87f Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Tue, 22 Sep 2015 11:52:43 +0200 Subject: Added themed_icon to integration tests --- include/unity/gmenuharness/MenuItemMatcher.h | 4 ++ src/gmenuharness/MenuItemMatcher.cpp | 70 +++++++++++++++++++++++++ tests/integration/indicator-sound-test-base.cpp | 2 + 3 files changed, 76 insertions(+) (limited to 'src/gmenuharness/MenuItemMatcher.cpp') diff --git a/include/unity/gmenuharness/MenuItemMatcher.h b/include/unity/gmenuharness/MenuItemMatcher.h index 6f7f94c..8f6deaf 100644 --- a/include/unity/gmenuharness/MenuItemMatcher.h +++ b/include/unity/gmenuharness/MenuItemMatcher.h @@ -75,6 +75,8 @@ public: MenuItemMatcher& icon(const std::string& icon); + MenuItemMatcher& themed_icon(const std::string& iconName, const std::vector& icons); + MenuItemMatcher& widget(const std::string& widget); MenuItemMatcher& pass_through_attribute(const std::string& actionName, const std::shared_ptr& value); @@ -95,6 +97,8 @@ public: MenuItemMatcher& int32_attribute(const std::string& name, int value); + MenuItemMatcher& int64_attribute(const std::string& name, int value); + MenuItemMatcher& double_attribute(const std::string& name, double value); MenuItemMatcher& toggled(bool toggled); diff --git a/src/gmenuharness/MenuItemMatcher.cpp b/src/gmenuharness/MenuItemMatcher.cpp index ab22364..dae9e7d 100644 --- a/src/gmenuharness/MenuItemMatcher.cpp +++ b/src/gmenuharness/MenuItemMatcher.cpp @@ -22,6 +22,7 @@ #include #include +#include using namespace std; @@ -182,6 +183,8 @@ struct MenuItemMatcher::Priv shared_ptr m_icon; + map, vector> m_themed_icons; + shared_ptr m_action; vector m_state_icons; @@ -242,6 +245,7 @@ MenuItemMatcher& MenuItemMatcher::operator=(const MenuItemMatcher& other) p->m_expectedSize = other.p->m_expectedSize; p->m_label = other.p->m_label; p->m_icon = other.p->m_icon; + p->m_themed_icons = other.p->m_themed_icons; p->m_action = other.p->m_action; p->m_state_icons = other.p->m_state_icons; p->m_attributes = other.p->m_attributes; @@ -291,6 +295,12 @@ MenuItemMatcher& MenuItemMatcher::icon(const string& icon) return *this; } +MenuItemMatcher& MenuItemMatcher::themed_icon(const std::string& iconName, const std::vector& icons) +{ + p->m_themed_icons[make_shared(iconName)] = icons; + return *this; +} + MenuItemMatcher& MenuItemMatcher::widget(const string& widget) { return string_attribute("x-canonical-type", widget); @@ -362,6 +372,14 @@ MenuItemMatcher& MenuItemMatcher::int32_attribute(const std::string& name, int v &gvariant_deleter)); } +MenuItemMatcher& MenuItemMatcher::int64_attribute(const std::string& name, int value) +{ + return attribute( + name, + shared_ptr(g_variant_new_int64 (value), + &gvariant_deleter)); +} + MenuItemMatcher& MenuItemMatcher::double_attribute(const std::string& name, double value) { return attribute( @@ -503,6 +521,58 @@ void MenuItemMatcher::match( + type_to_string(actualType)); } + // check themed icons + map, vector>::iterator iter; + for (iter = p->m_themed_icons.begin(); iter != p->m_themed_icons.end(); ++iter) + { + auto icon_val = g_menu_item_get_attribute_value(menuItem.get(), (*iter).first->c_str(), nullptr); + if (!icon_val) + { + matchResult.failure( + location, + "Expected themed icon " + (*(*iter).first) + " was not found"); + } + + auto gicon = g_icon_deserialize(icon_val); + if (!gicon || !G_IS_THEMED_ICON(gicon)) + { + matchResult.failure( + location, + "Expected attribute " + (*(*iter).first) + " is not a themed icon"); + } + auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(gicon)); + int nb_icons = 0; + while(iconNames[nb_icons]) + { + ++nb_icons; + } + + if (nb_icons != (*iter).second.size()) + { + matchResult.failure( + location, + "Expected " + to_string((*iter).second.size()) + + " icons for themed icon [" + (*(*iter).first) + + "], but " + to_string(nb_icons) + " were found."); + } + else + { + // now compare all the icons + for (int i = 0; i < nb_icons; ++i) + { + if (string(iconNames[i]) != (*iter).second[i]) + { + matchResult.failure( + location, + "Icon at position " + to_string(i) + + " for themed icon [" + (*(*iter).first) + + "], mismatchs. Expected: " + iconNames[i] + " but found " + (*iter).second[i]); + } + } + } + g_object_unref(gicon); + } + string label = get_string_attribute(menuItem, G_MENU_ATTRIBUTE_LABEL); if (p->m_label && (*p->m_label) != label) { diff --git a/tests/integration/indicator-sound-test-base.cpp b/tests/integration/indicator-sound-test-base.cpp index a839fbc..bbede82 100644 --- a/tests/integration/indicator-sound-test-base.cpp +++ b/tests/integration/indicator-sound-test-base.cpp @@ -276,6 +276,8 @@ unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::volumeSlider(double .double_attribute("max-value", 1.0) .double_attribute("step", 0.01) .string_attribute("x-canonical-type", "com.canonical.unity.slider") + .themed_icon("max-icon", {"audio-volume-high-panel", "audio-volume-high", "audio-volume", "audio"}) + .themed_icon("min-icon", {"audio-volume-low-zero-panel", "audio-volume-low-zero", "audio-volume-low", "audio-volume", "audio"}) .pass_through_double_attribute("action", volume); } -- cgit v1.2.3 From 5d88ce9836c1d32abfd369e650354ed855ec4654 Mon Sep 17 00:00:00 2001 From: Xavi Garcia Mena Date: Fri, 25 Sep 2015 10:31:24 +0200 Subject: Added attribute_not_set to gmenuharness, added tests for player control buttons --- include/unity/gmenuharness/MenuItemMatcher.h | 2 + src/gmenuharness/MenuItemMatcher.cpp | 20 +++ src/media-player-mpris.vala | 2 +- tests/integration/CMakeLists.txt | 1 + tests/integration/indicator-sound-test-base.cpp | 19 ++ tests/integration/indicator-sound-test-base.h | 2 + tests/integration/test-indicator.cpp | 228 +++++++++++++++++++++++- 7 files changed, 268 insertions(+), 6 deletions(-) (limited to 'src/gmenuharness/MenuItemMatcher.cpp') diff --git a/include/unity/gmenuharness/MenuItemMatcher.h b/include/unity/gmenuharness/MenuItemMatcher.h index 8f6deaf..38a5187 100644 --- a/include/unity/gmenuharness/MenuItemMatcher.h +++ b/include/unity/gmenuharness/MenuItemMatcher.h @@ -101,6 +101,8 @@ public: MenuItemMatcher& double_attribute(const std::string& name, double value); + MenuItemMatcher& attribute_not_set(const std::string& name); + MenuItemMatcher& toggled(bool toggled); MenuItemMatcher& mode(Mode mode); diff --git a/src/gmenuharness/MenuItemMatcher.cpp b/src/gmenuharness/MenuItemMatcher.cpp index dae9e7d..f39acef 100644 --- a/src/gmenuharness/MenuItemMatcher.cpp +++ b/src/gmenuharness/MenuItemMatcher.cpp @@ -191,6 +191,8 @@ struct MenuItemMatcher::Priv vector>> m_attributes; + vector m_not_exist_attributes; + vector>> m_pass_through_attributes; shared_ptr m_isToggled; @@ -249,6 +251,7 @@ MenuItemMatcher& MenuItemMatcher::operator=(const MenuItemMatcher& other) p->m_action = other.p->m_action; p->m_state_icons = other.p->m_state_icons; p->m_attributes = other.p->m_attributes; + p->m_not_exist_attributes = other.p->m_not_exist_attributes; p->m_pass_through_attributes = other.p->m_pass_through_attributes; p->m_isToggled = other.p->m_isToggled; p->m_linkType = other.p->m_linkType; @@ -388,6 +391,12 @@ MenuItemMatcher& MenuItemMatcher::double_attribute(const std::string& name, doub &gvariant_deleter)); } +MenuItemMatcher& MenuItemMatcher::attribute_not_set(const std::string& name) +{ + p->m_not_exist_attributes.emplace_back (name); + return *this; +} + MenuItemMatcher& MenuItemMatcher::toggled(bool isToggled) { p->m_isToggled = make_shared(isToggled); @@ -779,6 +788,17 @@ void MenuItemMatcher::match( } } + for (const auto& e: p->m_not_exist_attributes) + { + auto value = get_attribute(menuItem, e.c_str()); + if (value) + { + matchResult.failure(location, + "Not expected attribute '" + e + + "' was found"); + } + } + if (p->m_isToggled && (*p->m_isToggled) != isToggled) { matchResult.failure( diff --git a/src/media-player-mpris.vala b/src/media-player-mpris.vala index 8bc2884..b9961f5 100644 --- a/src/media-player-mpris.vala +++ b/src/media-player-mpris.vala @@ -290,7 +290,7 @@ public class MediaPlayerMpris: MediaPlayer { if (changed_properties.lookup ("PlaybackStatus", "s", null)) { this.state = this.proxy.PlaybackStatus != null ? this.proxy.PlaybackStatus : "Unknown"; } - if (changed_properties.lookup ("CanGoNext", "b", null) || changed_properties.lookup ("CanGoPrev", "b", null)) { + if (changed_properties.lookup ("CanGoNext", "b", null) || changed_properties.lookup ("CanGoPrevious", "b", null)) { this.playbackstatus_changed (); } diff --git a/tests/integration/CMakeLists.txt b/tests/integration/CMakeLists.txt index 5798f14..939e329 100644 --- a/tests/integration/CMakeLists.txt +++ b/tests/integration/CMakeLists.txt @@ -31,6 +31,7 @@ add_definitions(-DSOUND_SERVICE_BIN="${CMAKE_BINARY_DIR}/src/indicator-sound-ser -DVOLUME_SET_BIN="${CMAKE_BINARY_DIR}/tests/integration/set-volume" -DACCOUNTS_SERVICE_BIN="${CMAKE_BINARY_DIR}/tests/service-mocks/accounts-mock/accounts-service-sound" -DMEDIA_PLAYER_MPRIS_BIN="${CMAKE_BINARY_DIR}/tests/service-mocks/media-player-mpris-mock/media-player-mpris-mock" + -DMEDIA_PLAYER_MPRIS_UPDATE_BIN="${CMAKE_BINARY_DIR}/tests/service-mocks/media-player-mpris-mock/media-player-mpris-mock-update" -DTEST_SOUND="${CMAKE_SOURCE_DIR}/tests/integration/test-sound.wav" -DQT_NO_KEYWORDS=1 -DXDG_DATA_DIRS="${XDG_DATA_DIRS}" diff --git a/tests/integration/indicator-sound-test-base.cpp b/tests/integration/indicator-sound-test-base.cpp index 2b6b3d7..16f0bd6 100644 --- a/tests/integration/indicator-sound-test-base.cpp +++ b/tests/integration/indicator-sound-test-base.cpp @@ -109,6 +109,25 @@ bool IndicatorSoundTestBase::startTestMprisPlayer(QString const& playerName) return true; } +bool IndicatorSoundTestBase::setTestMprisPlayerProperty(QString const &testPlayer, QString const &property, bool value) +{ + QProcess setProperty; + QString strValue; + strValue = value ? "true" : "false"; + + setProperty.start(MEDIA_PLAYER_MPRIS_UPDATE_BIN, QStringList() + << testPlayer + << property + << strValue); + if (!setProperty.waitForStarted()) + return false; + + if (!setProperty.waitForFinished()) + return false; + + return setProperty.exitCode() == 0; +} + bool IndicatorSoundTestBase::startTestSound(QString const &role) { testSoundProcess.terminate(); diff --git a/tests/integration/indicator-sound-test-base.h b/tests/integration/indicator-sound-test-base.h index 741580b..00ccf1a 100644 --- a/tests/integration/indicator-sound-test-base.h +++ b/tests/integration/indicator-sound-test-base.h @@ -53,6 +53,8 @@ protected: bool startTestMprisPlayer(QString const& playerName); + bool setTestMprisPlayerProperty(QString const &testPlayer, QString const &property, bool value); + bool setStreamRestoreVolume(QString const &role, double volume); bool setSinkVolume(double volume); diff --git a/tests/integration/test-indicator.cpp b/tests/integration/test-indicator.cpp index ca3d298..82f06a4 100644 --- a/tests/integration/test-indicator.cpp +++ b/tests/integration/test-indicator.cpp @@ -166,10 +166,41 @@ TEST_F(TestIndicator, PhoneBasicInitialVolume) .item(silentModeSwitch(false)) .item(volumeSlider(INITIAL_VOLUME)) ) -// .item(mh::MenuItemMatcher() -// .section() -// .action("indicator.testplayer1.desktop") -// ) + .item(mh::MenuItemMatcher() + .label("Sound Settings…") + .action("indicator.phone-settings") + ) + ).match()); +} + +TEST_F(TestIndicator, PhoneAddMprisPlayer) +{ + double INITIAL_VOLUME = 0.0; + + ASSERT_NO_THROW(startAccountsService()); + EXPECT_TRUE(clearGSettingsPlayers()); + ASSERT_NO_THROW(startPulsePhone()); + + // initialize volumes in pulseaudio + EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME)); + + // start now the indicator, so it picks the new volumes + ASSERT_NO_THROW(startIndicator()); + + EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters()) + .item(mh::MenuItemMatcher() + .action("indicator.root") + .string_attribute("x-canonical-type", "com.canonical.indicator.root") + .string_attribute("x-canonical-scroll-action", "indicator.scroll") + .string_attribute("x-canonical-secondary-action", "indicator.mute") + .string_attribute("submenu-action", "indicator.indicator-shown") + .mode(mh::MenuItemMatcher::Mode::all) + .submenu() + .item(mh::MenuItemMatcher() + .section() + .item(silentModeSwitch(false)) + .item(volumeSlider(INITIAL_VOLUME)) + ) .item(mh::MenuItemMatcher() .label("Sound Settings…") .action("indicator.phone-settings") @@ -189,9 +220,66 @@ TEST_F(TestIndicator, DesktopBasicInitialVolume) EXPECT_FALSE(setStreamRestoreVolume("alert", INITIAL_VOLUME)); EXPECT_TRUE(setSinkVolume(INITIAL_VOLUME)); + // start the test player + EXPECT_TRUE(startTestMprisPlayer("testplayer1")); + + // start now the indicator, so it picks the new volumes + ASSERT_NO_THROW(startIndicator()); + + EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters()) + .item(mh::MenuItemMatcher() + .action("indicator.root") + .string_attribute("x-canonical-type", "com.canonical.indicator.root") + .string_attribute("x-canonical-secondary-action", "indicator.mute") + .mode(mh::MenuItemMatcher::Mode::all) + .submenu() + .item(mh::MenuItemMatcher() + .section() + .item(mh::MenuItemMatcher().checkbox() + .label("Mute") + ) + .item(volumeSlider(INITIAL_VOLUME)) + ) + .item(mh::MenuItemMatcher() + .section() + .item(mh::MenuItemMatcher() + .action("indicator.testplayer1.desktop") + .label("TestPlayer1") + .themed_icon("icon", {"testplayer"}) + .string_attribute("x-canonical-type", "com.canonical.unity.media-player") + ) + .item(mh::MenuItemMatcher() + .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop") + .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop") + .string_attribute("x-canonical-next-action","indicator.next.testplayer1.desktop") + .string_attribute("x-canonical-type","com.canonical.unity.playback-item") + ) + ) + .item(mh::MenuItemMatcher() + .label("Sound Settings…") + ) + ).match()); +} + +TEST_F(TestIndicator, DesktopAddMprisPlayer) +{ + double INITIAL_VOLUME = 0.0; + + ASSERT_NO_THROW(startAccountsService()); + EXPECT_TRUE(clearGSettingsPlayers()); + ASSERT_NO_THROW(startPulseDesktop()); + + // initialize volumes in pulseaudio + EXPECT_FALSE(setStreamRestoreVolume("alert", INITIAL_VOLUME)); + EXPECT_TRUE(setSinkVolume(INITIAL_VOLUME)); + + // start the test player + EXPECT_TRUE(startTestMprisPlayer("testplayer1")); + // start now the indicator, so it picks the new volumes ASSERT_NO_THROW(startIndicator()); + // check that the player is added EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters()) .item(mh::MenuItemMatcher() .action("indicator.root") @@ -206,13 +294,28 @@ TEST_F(TestIndicator, DesktopBasicInitialVolume) ) .item(volumeSlider(INITIAL_VOLUME)) ) + .item(mh::MenuItemMatcher() + .section() + .item(mh::MenuItemMatcher() + .action("indicator.testplayer1.desktop") + .label("TestPlayer1") + .themed_icon("icon", {"testplayer"}) + .string_attribute("x-canonical-type", "com.canonical.unity.media-player") + ) + .item(mh::MenuItemMatcher() + .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop") + .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop") + .string_attribute("x-canonical-next-action","indicator.next.testplayer1.desktop") + .string_attribute("x-canonical-type","com.canonical.unity.playback-item") + ) + ) .item(mh::MenuItemMatcher() .label("Sound Settings…") ) ).match()); } -TEST_F(TestIndicator, DesktopAddPlayer) +TEST_F(TestIndicator, DesktopMprisPlayerButtonsState) { double INITIAL_VOLUME = 0.0; @@ -264,6 +367,121 @@ TEST_F(TestIndicator, DesktopAddPlayer) .label("Sound Settings…") ) ).match()); + + // change the state of CanGoNext + EXPECT_TRUE(setTestMprisPlayerProperty("testplayer1", "CanGoNext", false)); + + // verify that the action changes + EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters()) + .item(mh::MenuItemMatcher() + .action("indicator.root") + .string_attribute("x-canonical-type", "com.canonical.indicator.root") + .string_attribute("x-canonical-secondary-action", "indicator.mute") + .mode(mh::MenuItemMatcher::Mode::all) + .submenu() + .item(mh::MenuItemMatcher() + .section() + .item(mh::MenuItemMatcher().checkbox() + .label("Mute") + ) + .item(volumeSlider(INITIAL_VOLUME)) + ) + .item(mh::MenuItemMatcher() + .section() + .item(mh::MenuItemMatcher() + .action("indicator.testplayer1.desktop") + .label("TestPlayer1") + .themed_icon("icon", {"testplayer"}) + .string_attribute("x-canonical-type", "com.canonical.unity.media-player") + ) + .item(mh::MenuItemMatcher() + .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop") + .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop") + .attribute_not_set("x-canonical-next-action") + .string_attribute("x-canonical-type","com.canonical.unity.playback-item") + ) + ) + .item(mh::MenuItemMatcher() + .label("Sound Settings…") + ) + ).match()); + + + // change the state of CanGoPrevious + EXPECT_TRUE(setTestMprisPlayerProperty("testplayer1", "CanGoPrevious", false)); + + // verify that the action changes + EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters()) + .item(mh::MenuItemMatcher() + .action("indicator.root") + .string_attribute("x-canonical-type", "com.canonical.indicator.root") + .string_attribute("x-canonical-secondary-action", "indicator.mute") + .mode(mh::MenuItemMatcher::Mode::all) + .submenu() + .item(mh::MenuItemMatcher() + .section() + .item(mh::MenuItemMatcher().checkbox() + .label("Mute") + ) + .item(volumeSlider(INITIAL_VOLUME)) + ) + .item(mh::MenuItemMatcher() + .section() + .item(mh::MenuItemMatcher() + .action("indicator.testplayer1.desktop") + .label("TestPlayer1") + .themed_icon("icon", {"testplayer"}) + .string_attribute("x-canonical-type", "com.canonical.unity.media-player") + ) + .item(mh::MenuItemMatcher() + .attribute_not_set("x-canonical-previous-action") + .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop") + .attribute_not_set("x-canonical-next-action") + .string_attribute("x-canonical-type","com.canonical.unity.playback-item") + ) + ) + .item(mh::MenuItemMatcher() + .label("Sound Settings…") + ) + ).match()); + + // set back both to true + EXPECT_TRUE(setTestMprisPlayerProperty("testplayer1", "CanGoNext", true)); + EXPECT_TRUE(setTestMprisPlayerProperty("testplayer1", "CanGoPrevious", true)); + + EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters()) + .item(mh::MenuItemMatcher() + .action("indicator.root") + .string_attribute("x-canonical-type", "com.canonical.indicator.root") + .string_attribute("x-canonical-secondary-action", "indicator.mute") + .mode(mh::MenuItemMatcher::Mode::all) + .submenu() + .item(mh::MenuItemMatcher() + .section() + .item(mh::MenuItemMatcher().checkbox() + .label("Mute") + ) + .item(volumeSlider(INITIAL_VOLUME)) + ) + .item(mh::MenuItemMatcher() + .section() + .item(mh::MenuItemMatcher() + .action("indicator.testplayer1.desktop") + .label("TestPlayer1") + .themed_icon("icon", {"testplayer"}) + .string_attribute("x-canonical-type", "com.canonical.unity.media-player") + ) + .item(mh::MenuItemMatcher() + .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop") + .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop") + .string_attribute("x-canonical-next-action","indicator.next.testplayer1.desktop") + .string_attribute("x-canonical-type","com.canonical.unity.playback-item") + ) + ) + .item(mh::MenuItemMatcher() + .label("Sound Settings…") + ) + ).match()); } TEST_F(TestIndicator, DesktopChangeRoleVolume) -- cgit v1.2.3