From 91b1e2ee92dc5820405527005e49c2e05095427b Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Wed, 30 Jul 2014 11:24:03 +0200 Subject: [PATCH] Add patch to add missing tests files in the distribution tarball --- evolution-3.13.4-missing-tests.patch | 1282 ++++++++++++++++++++++++++ evolution.spec | 4 + 2 files changed, 1286 insertions(+) create mode 100644 evolution-3.13.4-missing-tests.patch diff --git a/evolution-3.13.4-missing-tests.patch b/evolution-3.13.4-missing-tests.patch new file mode 100644 index 0000000..cab4f03 --- /dev/null +++ b/evolution-3.13.4-missing-tests.patch @@ -0,0 +1,1282 @@ +diff -up evolution-3.13.4/tests/addressbook.feature.missing-tests evolution-3.13.4/tests/addressbook.feature +--- evolution-3.13.4/tests/addressbook.feature.missing-tests 2014-07-30 11:21:47.031172903 +0200 ++++ evolution-3.13.4/tests/addressbook.feature 2014-07-30 11:21:47.031172903 +0200 +@@ -0,0 +1,101 @@ ++Feature: Addressbook: File: Create contacts ++ ++ Background: ++ * Open Evolution and setup fake account ++ * Open "Contacts" section ++ * Select "Personal" addressbook ++ * Change categories view to "Any Category" ++ * Delete all contacts containing "Doe" ++ ++ @addressbook_contacts ++ Scenario: Create a simple contact ++ * Create a new contact ++ * Set "Full Name..." in contact editor to "John Doe" ++ * Save the contact ++ * Refresh addressbook ++ * Select "Doe, John" contact ++ * Open contact editor for selected contact ++ Then "Full Name..." property is set to "John Doe" ++ ++ @addressbook_contacts ++ Scenario: Create a new contact with data ++ * Create a new contact ++ * Set "Full Name..." in contact editor to "Jimmy Doe" ++ * Set "Nickname:" in contact editor to "Unknown" ++ * Set emails in contact editor to ++ | Field | Value | ++ | Work Email | jimmy.doe@company.com | ++ | Home Email | jimmy_doe_72@gmail.com | ++ | Other Email | jimmydoe72@yahoo.com | ++ | Other Email | xxjimmyxx@free_email.com | ++ * Tick "Wants to receive HTML mail" checkbox ++ * Set phones in contact editor to ++ | Field | Value | ++ | Assistant Phone | 123 | ++ | Business Phone | 234 | ++ | Business Fax | 345 | ++ | Callback Phone | 456 | ++ | Car Phone | 567 | ++ | Company Phone | 678 | ++ | Home Phone | 789 | ++ | Home Fax | 890 | ++ | ISDN | 123 | ++ | Mobile Phone | 234 | ++ | Other Phone | 345 | ++ | Other Fax | 456 | ++ | Pager | 567 | ++ | Primary Phone | 678 | ++ | Radio | 789 | ++ | Telex | 890 | ++ * Set IMs in contact editor to ++ | Field | Value | ++ | AIM | 123 | ++ | Jabber | 234 | ++ | Yahoo | 345 | ++ | Gadu-Gadu | 456 | ++ | MSN | 123 | ++ | ICQ | 234 | ++ | GroupWise | 345 | ++ | Skype | jimmy.doe | ++ | Twitter | @jimmydoe | ++ * Save the contact ++ * Refresh addressbook ++ * Select "Doe, Jimmy" contact ++ * Open contact editor for selected contact ++ Then "Nickname:" property is set to "Unknown" ++ And Emails are set to ++ | Field | Value | ++ | Work Email | jimmy.doe@company.com | ++ | Home Email | jimmy_doe_72@gmail.com | ++ | Other Email | jimmydoe72@yahoo.com | ++ | Other Email | xxjimmyxx@free_email.com | ++ And "Wants to receive HTML mail" checkbox is ticked ++ And Phones are set to ++ | Field | Value | ++ | Assistant Phone | 123 | ++ | Business Phone | 234 | ++ | Business Fax | 345 | ++ | Callback Phone | 456 | ++ | Car Phone | 567 | ++ | Company Phone | 678 | ++ | Home Phone | 789 | ++ | Home Fax | 890 | ++ | ISDN | 123 | ++ | Mobile Phone | 234 | ++ | Other Phone | 345 | ++ | Other Fax | 456 | ++ | Pager | 567 | ++ | Primary Phone | 678 | ++ | Radio | 789 | ++ | Telex | 890 | ++ And IMs are set to ++ | Field | Value | ++ | AIM | 123 | ++ | Jabber | 234 | ++ | Yahoo | 345 | ++ | Gadu-Gadu | 456 | ++ | MSN | 123 | ++ | ICQ | 234 | ++ | GroupWise | 345 | ++ | Skype | jimmy.doe | ++ | Twitter | @jimmydoe | +diff -up evolution-3.13.4/tests/common_steps.py.missing-tests evolution-3.13.4/tests/common_steps.py +--- evolution-3.13.4/tests/common_steps.py.missing-tests 2014-07-30 11:21:47.031172903 +0200 ++++ evolution-3.13.4/tests/common_steps.py 2014-07-30 11:21:47.031172903 +0200 +@@ -0,0 +1,238 @@ ++# -*- coding: UTF-8 -*- ++from dogtail.utils import isA11yEnabled, enableA11y ++if isA11yEnabled() is False: ++ enableA11y(True) ++ ++from time import time, sleep ++from functools import wraps ++from os import strerror, errno, system ++from signal import signal, alarm, SIGALRM ++from subprocess import Popen, PIPE ++from behave import step ++from gi.repository import GLib, Gio ++import fcntl, os ++ ++from dogtail.rawinput import keyCombo, absoluteMotion, pressKey ++from dogtail.tree import root ++from unittest import TestCase ++ ++ ++# Create a dummy unittest class to have nice assertions ++class dummy(TestCase): ++ def runTest(self): # pylint: disable=R0201 ++ assert True ++ ++ ++def wait_until(my_lambda, element, timeout=30, period=0.25): ++ """ ++ This function keeps running lambda with specified params until the result is True ++ or timeout is reached ++ Sample usages: ++ * wait_until(lambda x: x.name != 'Loading...', context.app) ++ Pause until window title is not 'Loading...'. ++ Return False if window title is still 'Loading...' ++ Throw an exception if window doesn't exist after default timeout ++ ++ * wait_until(lambda element, expected: x.text == expected, element, ('Expected text')) ++ Wait until element text becomes the expected (passed to the lambda) ++ ++ """ ++ exception_thrown = None ++ mustend = int(time()) + timeout ++ while int(time()) < mustend: ++ try: ++ if my_lambda(element): ++ return True ++ except Exception as e: ++ # If lambda has thrown the exception we'll re-raise it later ++ # and forget about if lambda passes ++ exception_thrown = e ++ sleep(period) ++ if exception_thrown: ++ raise exception_thrown ++ else: ++ return False ++ ++ ++class TimeoutError(Exception): ++ """ ++ Timeout exception class for limit_execution_time_to function ++ """ ++ pass ++ ++ ++def limit_execution_time_to( ++ seconds=10, error_message=strerror(errno.ETIME)): ++ """ ++ Decorator to limit function execution to specified limit ++ """ ++ def decorator(func): ++ def _handle_timeout(signum, frame): ++ raise TimeoutError(error_message) ++ ++ def wrapper(*args, **kwargs): ++ signal(SIGALRM, _handle_timeout) ++ alarm(seconds) ++ try: ++ result = func(*args, **kwargs) ++ finally: ++ alarm(0) ++ return result ++ ++ return wraps(func)(wrapper) ++ ++ return decorator ++ ++ ++class App(object): ++ """ ++ This class does all basic events with the app ++ """ ++ def __init__( ++ self, appName, shortcut='', a11yAppName=None, ++ forceKill=True, parameters='', recordVideo=False): ++ """ ++ Initialize object App ++ appName command to run the app ++ shortcut default quit shortcut ++ a11yAppName app's a11y name is different than binary ++ forceKill is the app supposed to be kill before/after test? ++ parameters has the app any params needed to start? (only for startViaCommand) ++ recordVideo start gnome-shell recording while running the app ++ """ ++ self.appCommand = appName ++ self.shortcut = shortcut ++ self.forceKill = forceKill ++ self.parameters = parameters ++ self.internCommand = self.appCommand.lower() ++ self.a11yAppName = a11yAppName ++ self.recordVideo = recordVideo ++ self.pid = None ++ ++ # a way of overcoming overview autospawn when mouse in 1,1 from start ++ pressKey('Esc') ++ absoluteMotion(100, 100, 2) ++ ++ # attempt to make a recording of the test ++ if self.recordVideo: ++ keyCombo('R') ++ ++ def isRunning(self): ++ """ ++ Is the app running? ++ """ ++ if self.a11yAppName is None: ++ self.a11yAppName = self.internCommand ++ ++ # Trap weird bus errors ++ for attempt in xrange(0, 30): ++ sleep(1) ++ try: ++ return self.a11yAppName in [x.name for x in root.applications()] ++ except GLib.GError: ++ continue ++ raise Exception("10 at-spi errors, seems that bus is blocked") ++ ++ def kill(self): ++ """ ++ Kill the app via 'killall' ++ """ ++ if self.recordVideo: ++ keyCombo('R') ++ ++ try: ++ self.process.kill() ++ except: ++ # Fall back to killall ++ Popen("killall " + self.appCommand, shell=True).wait() ++ ++ def startViaCommand(self): ++ """ ++ Start the app via command ++ """ ++ if self.forceKill and self.isRunning(): ++ self.kill() ++ assert not self.isRunning(), "Application cannot be stopped" ++ ++ #command = "%s %s" % (self.appCommand, self.parameters) ++ #self.pid = run(command, timeout=5) ++ self.process = Popen(self.appCommand.split() + self.parameters.split(), ++ stdout=PIPE, stderr=PIPE, bufsize=0) ++ self.pid = self.process.pid ++ ++ assert self.isRunning(), "Application failed to start" ++ return root.application(self.a11yAppName) ++ ++ def closeViaShortcut(self): ++ """ ++ Close the app via shortcut ++ """ ++ if not self.isRunning(): ++ raise Exception("App is not running") ++ ++ keyCombo(self.shortcut) ++ assert not self.isRunning(), "Application cannot be stopped" ++ ++ ++@step(u'Start a new Evolution instance') ++def start_new_evolution_instance(context): ++ context.app = context.app_class.startViaCommand() ++ ++ ++def cleanup(): ++ # Remove cached data and settings ++ folders = ['~/.local/share/evolution', '~/.cache/evolution', '~/.config/evolution'] ++ for folder in folders: ++ system("rm -rf %s > /dev/null" % folder) ++ ++ # Clean up goa data ++ system("rm -rf ~/.config/goa-1.0/accounts.conf") ++ system("killall goa-daemon 2&> /dev/null") ++ ++ # Reset GSettings ++ schemas = [x for x in Gio.Settings.list_schemas() if 'evolution' in x.lower()] ++ for schema in schemas: ++ system("gsettings reset-recursively %s" % schema) ++ ++ # Skip warning dialog ++ system("gsettings set org.gnome.evolution.shell skip-warning-dialog true") ++ # Show switcher buttons as icons (to minimize tree scrolling) ++ system("gsettings set org.gnome.evolution.shell buttons-style icons") ++ # Skip default mailer handler dialog ++ system("gsettings set org.gnome.evolution.mail prompt-check-if-default-mailer false") ++ ++ ++def check_for_errors(context): ++ """Check that no error is displayed on Evolution UI""" ++ # Don't try to check for errors on dead app ++ if not context.app or context.app.dead: ++ return ++ alerts = context.app.findChildren(lambda x: x.roleName == 'alert') ++ if not alerts: ++ # alerts can also return None ++ return ++ alerts = filter(lambda x: x.showing, alerts) ++ if len(alerts) > 0: ++ labels = alerts[0].findChildren(lambda x: x.roleName == 'label') ++ messages = [x.name for x in labels] ++ ++ if alerts[0].name != 'Error' and alerts[0].showing: ++ # Erase the configuration and start all over again ++ system("evolution --force-shutdown &> /dev/null") ++ ++ # Remove previous data ++ folders = ['~/.local/share/evolution', '~/.cache/evolution', '~/.config/evolution'] ++ for folder in folders: ++ system("rm -rf %s > /dev/null" % folder) ++ ++ raise RuntimeError("Error occurred: %s" % messages) ++ ++ ++def non_block_read(output): ++ fd = output.fileno() ++ fl = fcntl.fcntl(fd, fcntl.F_GETFL) ++ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) ++ try: ++ return output.read() ++ except: ++ return "" +diff -up evolution-3.13.4/tests/environment.py.missing-tests evolution-3.13.4/tests/environment.py +--- evolution-3.13.4/tests/environment.py.missing-tests 2014-07-30 11:21:47.031172903 +0200 ++++ evolution-3.13.4/tests/environment.py 2014-07-30 11:21:47.031172903 +0200 +@@ -0,0 +1,82 @@ ++# -*- coding: UTF-8 -*- ++ ++from time import sleep, localtime, strftime ++from dogtail.utils import isA11yEnabled, enableA11y ++if not isA11yEnabled(): ++ enableA11y(True) ++ ++from common_steps import App, dummy, cleanup, non_block_read ++from dogtail.config import config ++import os ++ ++ ++def before_all(context): ++ """Setup evolution stuff ++ Being executed once before any test ++ """ ++ ++ try: ++ # Close running evo instances ++ os.system("evolution --force-shutdown > /dev/null") ++ ++ # Skip dogtail actions to print to stdout ++ config.logDebugToStdOut = False ++ config.typingDelay = 0.2 ++ ++ # Include assertion object ++ context.assertion = dummy() ++ ++ # Cleanup existing data before any test ++ cleanup() ++ ++ # Store scenario start time for session logs ++ context.log_start_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) ++ ++ context.app_class = App('evolution') ++ ++ except Exception as e: ++ print("Error in before_all: %s" % e.message) ++ ++ ++def after_step(context, step): ++ try: ++ if step.status == 'failed' and hasattr(context, "embed"): ++ # Embed screenshot if HTML report is used ++ os.system("dbus-send --print-reply --session --type=method_call " + ++ "--dest='org.gnome.Shell.Screenshot' " + ++ "'/org/gnome/Shell/Screenshot' " + ++ "org.gnome.Shell.Screenshot.Screenshot " + ++ "boolean:true boolean:false string:/tmp/screenshot.png") ++ context.embed('image/png', open("/tmp/screenshot.png", 'r').read()) ++ except Exception as e: ++ print("Error in after_step: %s" % str(e)) ++ ++ ++def after_scenario(context, scenario): ++ """Teardown for each scenario ++ Kill evolution (in order to make this reliable we send sigkill) ++ """ ++ try: ++ # Attach journalctl logs ++ if hasattr(context, "embed"): ++ os.system("journalctl /usr/bin/gnome-session --no-pager -o cat --since='%s'> /tmp/journal-session.log" % context.log_start_time) ++ data = open("/tmp/journal-session.log", 'r').read() ++ if data: ++ context.embed('text/plain', data) ++ ++ context.app_class.kill() ++ ++ stdout = non_block_read(context.app_class.process.stdout) ++ stderr = non_block_read(context.app_class.process.stderr) ++ ++ if stdout: ++ context.embed('text/plain', stdout) ++ ++ if stderr: ++ context.embed('text/plain', stderr) ++ ++ # Make some pause after scenario ++ sleep(1) ++ except Exception as e: ++ # Stupid behave simply crashes in case exception has occurred ++ print("Error in after_scenario: %s" % e.message) +diff -up evolution-3.13.4/tests/shortcuts.feature.missing-tests evolution-3.13.4/tests/shortcuts.feature +--- evolution-3.13.4/tests/shortcuts.feature.missing-tests 2014-07-30 11:21:47.032172902 +0200 ++++ evolution-3.13.4/tests/shortcuts.feature 2014-07-30 11:21:47.031172903 +0200 +@@ -0,0 +1,135 @@ ++Feature: Shortcuts ++ ++ Background: ++ * Open Evolution and setup fake account ++ ++ @general_shortcuts ++ Scenario: Ctrl-Q to quit application - two instances ++ * Start a new Evolution instance ++ * Press "Q" ++ Then Evolution is closed ++ ++ @general_shortcuts ++ Scenario: F1 to launch help ++ * Press "" ++ Then Help section "Evolution Mail and Calendar" is displayed ++ ++ @general_shortcuts ++ Scenario: Shift-Ctrl-W to open a new window ++ * Press "W" ++ Then Evolution has 2 windows opened ++ ++ @general_shortcuts ++ Scenario: Ctrl-W to close a window ++ * Press "W" ++ * Press "W" ++ Then Evolution has 1 window opened ++ ++ @general_shortcuts ++ Scenario: Ctrl-Shift-S to open Preferences ++ * Press "S" ++ Then Preferences dialog is opened ++ ++ @mail_shortcuts ++ Scenario: Mail: Ctrl-Shift-M to compose new message ++ * Open "Mail" section ++ * Press "M" ++ Then Message composer with title "Compose Message" is opened ++ ++ @contacts_shortcuts ++ Scenario: Contacts: Ctrl-Shift-C to create new contact ++ * Open "Contacts" section ++ * Press "C" ++ Then Contact editor window is opened ++ ++ @contacts_shortcuts ++ Scenario: Contacts: Ctrl-Shift-L to create new contact list ++ * Open "Contacts" section ++ * Press "L" ++ Then Contact List editor window is opened ++ ++ @calendar_shortcuts ++ Scenario: Calendar: Ctrl-Shift-A to create new appointment ++ * Open "Calendar" section ++ * Press "A" ++ Then Event editor with title "Appointment - No Summary" is displayed ++ ++ @calendar_shortcuts ++ Scenario: Calendar: Ctrl-Shift-E to create new meeting ++ * Open "Calendar" section ++ * Press "E" ++ Then Event editor with title "Meeting - No Summary" is displayed ++ ++ @calendar_shortcuts ++ Scenario: Tasks: Ctrl-Shift-T to create new task ++ * Open "Tasks" section ++ * Press "T" ++ Then Task editor with title "Task - No Summary" is opened ++ ++ @memos_shortcuts ++ Scenario: Memos: Ctrl-Shift-O to create new memo ++ * Open "Memos" section ++ * Press "O" ++ Then Memo editor with title "Memo - No Summary" is opened ++ ++ @memos_shortcuts ++ Scenario: Memos: Ctrl-Shift-O to create new task ++ * Open "Memos" section ++ * Press "O" ++ Then Shared memo editor with title "Memo - No Summary" is opened ++ ++ @view_shortcuts ++ Scenario Outline: Ctrl+<1-5> to switch views ++ * Press "" ++ Then "
" view is opened ++ ++ Examples: ++ | shortcut | section | ++ | 1 | Mail | ++ | 2 | Contacts | ++ | 3 | Calendar | ++ | 4 | Tasks | ++ | 5 | Memos | ++ ++ @menu_shortcuts ++ Scenario Outline: Menu shortcuts on all views ++ * Open "
" section ++ * Press "" ++ Then "" menu is opened ++ ++ Examples: ++ | section | shortcut | menu | ++ | Mail | F | File | ++ | Mail | E | Edit | ++ | Mail | V | View | ++ | Mail | O | Folder | ++ | Mail | M | Message | ++ | Mail | S | Search | ++ | Mail | H | Help | ++ ++ | Contacts | F | File | ++ | Contacts | E | Edit | ++ | Contacts | V | View | ++ | Contacts | A | Actions | ++ | Contacts | S | Search | ++ | Contacts | H | Help | ++ ++ | Calendar | F | File | ++ | Calendar | E | Edit | ++ | Calendar | V | View | ++ | Calendar | A | Actions | ++ | Calendar | S | Search | ++ | Calendar | H | Help | ++ ++ | Tasks | F | File | ++ | Tasks | E | Edit | ++ | Tasks | V | View | ++ | Tasks | A | Actions | ++ | Tasks | S | Search | ++ | Tasks | H | Help | ++ ++ | Memos | F | File | ++ | Memos | E | Edit | ++ | Memos | V | View | ++ | Memos | S | Search | ++ | Memos | H | Help | +diff -up evolution-3.13.4/tests/steps/addressbook_steps.py.missing-tests evolution-3.13.4/tests/steps/addressbook_steps.py +--- evolution-3.13.4/tests/steps/addressbook_steps.py.missing-tests 2014-07-30 11:21:47.032172902 +0200 ++++ evolution-3.13.4/tests/steps/addressbook_steps.py 2014-07-30 11:21:47.032172902 +0200 +@@ -0,0 +1,391 @@ ++# -*- coding: UTF-8 -*- ++from behave import step, then ++from common_steps import wait_until ++from dogtail.predicate import GenericPredicate ++from dogtail.rawinput import keyCombo ++from time import time, sleep ++from gi.repository import GLib ++import pyatspi ++ ++ ++@step(u'Select "{name}" addressbook') ++def select_addressbook(context, name, password=None): ++ cells = context.app.findChildren( ++ GenericPredicate(name=name, roleName='table cell')) ++ visible_cells = filter(lambda x: x.showing, cells) ++ if visible_cells == []: ++ raise RuntimeError("Cannot find addressbook '%s'" % name) ++ visible_cells[0].click() ++ # Wait for addressbook to load ++ try: ++ spinner = context.app.findChild( ++ GenericPredicate(name='Spinner'), retry=False, requireResult=False) ++ if spinner: ++ start_time = time() ++ while spinner.showing: ++ sleep(1) ++ if (time() - start_time) > 180: ++ raise RuntimeError("Contacts take too long to synchronize") ++ except (GLib.GError, TypeError): ++ pass ++ ++ ++@step(u'Change categories view to "{category}"') ++def change_categories_view(context, category): ++ labels = context.app.findChildren( ++ lambda x: x.labeller.name == 'Show:' and x.showing) ++ if labels == []: ++ raise RuntimeError("Cannot find category switcher") ++ labels[0].combovalue = category ++ ++ ++@step(u'Delete selected contact') ++def delete_selected_contact(context): ++ context.app.menu('Edit').click() ++ mnu = context.app.menu('Edit').menuItem("Delete Contact") ++ if pyatspi.STATE_ENABLED in mnu.getState().getStates(): ++ context.app.menu('Edit').menuItem("Delete Contact").click() ++ ++ alert = context.app.child(roleName='alert', name='Question') ++ alert.button('Delete').click() ++ context.execute_steps(u"* Wait for email to synchronize") ++ ++ ++@step(u'Delete all contacts containing "{part}"') ++def delete_all_contacts_containing(context, part): ++ context.app.search_bar.grab_focus() ++ for attempts in range(0, 10): ++ try: ++ context.app.search_bar.text = part ++ break ++ except (GLib.GError, AttributeError): ++ sleep(0.1) ++ continue ++ keyCombo("") ++ context.execute_steps(u"* Wait for email to synchronize") ++ context.app.search_bar.grab_focus() ++ keyCombo("") ++ sleep(3) ++ heading = context.app.findChild( ++ GenericPredicate(roleName='heading'), ++ retry=False, requireResult=False) ++ if heading: ++ keyCombo("a") ++ context.execute_steps(u"* Delete selected contact") ++ sleep(3) ++ ++ ++@step(u'Create a new contact') ++def create_a_new_contact(context): ++ context.app.menu('File').click() ++ context.app.menu('File').menu('New').point() ++ context.app.menu('File').menu('New').menuItem("Contact").click() ++ context.execute_steps(u"Then Contact editor window is opened") ++ ++ ++def get_element_by_name(contact_editor, name, section=None): ++ """Get a field object by name in section (if specified)""" ++ element = None ++ if section: ++ panel = contact_editor.findChild( ++ GenericPredicate(roleName='panel', name=section), retry=False, requireResult=False) ++ if not panel: ++ # Other section is not a panel, but a toggle button ++ panel = contact_editor.child(roleName='toggle button', name=section) ++ element = panel.childLabelled(name) ++ else: ++ label = contact_editor.findChild( ++ GenericPredicate(label=name), retry=False, requireResult=False) ++ if not label: ++ # In case childLabelled is missing ++ # Find a filler with this name and get its text child ++ element = contact_editor.child( ++ roleName='filler', name=name).child(roleName='text') ++ else: ++ element = contact_editor.childLabelled(name) ++ if element: ++ return element ++ else: ++ raise RuntimeError("Cannot find element named '%s' in section '%s'" % ( ++ name, section)) ++ ++ ++@step(u'Set "{field_name}" in contact editor to "{field_value}"') ++def set_field_to_value(context, field_name, field_value): ++ element = get_element_by_name(context.app.contact_editor, field_name) ++ if element.roleName == "text": ++ element.text = field_value ++ elif element.roleName == "combo box": ++ if element.combovalue != field_value: ++ element.combovalue = field_value ++ ++ ++@step(u'Save the contact') ++def save_contact(context): ++ context.app.contact_editor.button('Save').click() ++ assert wait_until(lambda x: not x.showing, context.app.contact_editor),\ ++ "Contact Editor was not hidden" ++ assert wait_until(lambda x: x.dead, context.app.contact_editor),\ ++ "Contact Editor was not closed" ++ context.app.contact_editor = None ++ ++ ++@step(u'Refresh addressbook') ++def refresh_addressbook(context): ++ #Clear the search ++ icons = context.app.search_bar.findChildren(lambda x: x.roleName == 'icon') ++ if icons != []: ++ icons[-1].click() ++ else: ++ for attempts in range(0, 10): ++ try: ++ context.app.search_bar.text = '' ++ break ++ except (GLib.GError, AttributeError): ++ sleep(0.1) ++ continue ++ context.app.search_bar.grab_focus() ++ keyCombo('') ++ context.execute_steps(u"* Wait for email to synchronize") ++ ++ ++@step(u'Select "{contact_name}" contact list') ++@step(u'Select "{contact_name}" contact') ++def select_contact_with_name(context, contact_name): ++ # heading shows the name of currently selected contact ++ # We have to keep on pressing Tab to select the next contact ++ # Until we meet the first contact ++ # WARNING - what if we will have two identical contacts? ++ fail = False ++ selected_contact = None ++ ++ # HACK ++ # To make the contact table appear ++ # we need to focus on search window ++ # and send Tabs to have the first contact focused ++ context.app.search_bar.grab_focus() ++ sleep(0.1) ++ # Switch to 'Any field contains' (not reachable in 3.6) ++ icons = context.app.search_bar.findChildren(GenericPredicate(roleName='icon')) ++ ++ if icons != []: ++ icons[0].click() ++ wait_until(lambda x: x.findChildren( ++ GenericPredicate(roleName='check menu item', name='Any field contains')) != [], ++ context.app) ++ context.app.menuItem('Any field contains').click() ++ for attempts in range(0, 10): ++ try: ++ context.app.search_bar.text = contact_name ++ break ++ except (GLib.GError, AttributeError): ++ sleep(0.1) ++ continue ++ keyCombo("") ++ context.app.search_bar.grab_focus() ++ ++ keyCombo("") ++ first_contact_name = context.app.child(roleName='heading').text ++ ++ while True: ++ selected_contact = context.app.child(roleName='heading') ++ if selected_contact.text == contact_name: ++ fail = False ++ break ++ keyCombo("") ++ # Wait until contact data is being rendered ++ sleep(1) ++ if first_contact_name == selected_contact.text: ++ fail = True ++ break ++ ++ context.assertion.assertFalse( ++ fail, "Can't find contact named '%s'" % contact_name) ++ context.selected_contact_text = selected_contact.text ++ ++ ++@step(u'Open contact editor for selected contact') ++def open_contact_editor_for_selected_contact(context): ++ context.app.menu('File').click() ++ context.app.menu('File').menuItem('Open Contact').click() ++ context.execute_steps(u""" ++ Then Contact editor window with title "Contact Editor - %s" is opened ++ """ % context.selected_contact_text) ++ ++ ++@then(u'"{field}" property is set to "{expected}"') ++def property_in_contact_window_is_set_to(context, field, expected): ++ element = get_element_by_name(context.app.contact_editor, field) ++ actual = None ++ if element.roleName == "text": ++ actual = element.text ++ elif element.roleName == "combo box": ++ actual = element.combovalue ++ if actual == '': ++ actual = element.textentry('').text ++ assert unicode(actual) == expected, "Incorrect value" ++ ++ ++def get_combobox_textbox_object(contact_editor, section, scroll_to_bottom=True): ++ """Get a list of paired 'combobox-textbox' objects in contact editor""" ++ section_names = { ++ 'Ims': 'Instant Messaging', ++ 'Phones': 'Telephone', ++ 'Emails': 'Email'} ++ section = section_names[section.capitalize()] ++ lbl = contact_editor.child(roleName='label', name=section) ++ panel = lbl.findAncestor(GenericPredicate(roleName='panel')) ++ textboxes = panel.findChildren(GenericPredicate(roleName='text')) ++ ++ # Scroll to the bottom of the page if needed ++ pagetab = panel.findAncestor(GenericPredicate(roleName='page tab')) ++ for scroll in pagetab.findChildren(lambda x: x.roleName == 'scroll bar'): ++ if scroll_to_bottom: ++ scroll.value = scroll.maxValue ++ else: ++ scroll.value = 0 ++ ++ # Expand section if button exists ++ button = panel.findChild( ++ GenericPredicate(roleName='push button', name=section), ++ retry=False, requireResult=False) ++ # Expand button if any of textboxes is not visible ++ if button and (False in [x.showing for x in textboxes]): ++ button.click() ++ ++ comboboxes = panel.findChildren(GenericPredicate(roleName='combo box')) ++ ++ # Rearrange comboboxes and textboxes according to their position ++ result = [] ++ for combo in comboboxes: ++ combo_row = combo.position[1] ++ matching_textboxes = [ ++ x for x in textboxes ++ if ((x.position[1] - combo_row) == 0) and (x.position[0] > combo.position[0])] ++ if (matching_textboxes != []): ++ correct_textbox = min(matching_textboxes, key=lambda x: x.position[0]) ++ result.append((combo, correct_textbox)) ++ ++ comboboxes = [x[0] for x in result][::-1] ++ textboxes = [x[1] for x in result][::-1] ++ ++ return (textboxes, comboboxes, button) ++ ++ ++@step(u'Set {section} in contact editor to') ++def set_contact_emails_to_value(context, section): ++ (textboxes, comboboxes, collapse_button) = get_combobox_textbox_object( ++ context.app.contact_editor, section) ++ ++ # clear existing data ++ for textbox in textboxes: ++ textbox.text = "" ++ ++ for index, row in enumerate(context.table.rows): ++ # Check that we have sufficient amount of textboxes ++ # If not - click plus buttons until we have enough ++ if index == len(textboxes): ++ textboxes[0].parent.child(roleName="push button").click() ++ (textboxes, comboboxes, collapse_button) = get_combobox_textbox_object( ++ context.app.contact_editor, section) ++ textboxes[index].text = row['Value'] ++ if comboboxes[index].combovalue != row['Field']: ++ comboboxes[index].combovalue = row['Field'] ++ ++ ++@then(u'{section} are set to') ++def emails_are_set_to(context, section): ++ (textboxes, comboboxes, collapse_button) = get_combobox_textbox_object( ++ context.app.contact_editor, section, section == 'IMs') ++ ++ actual = [] ++ for index, textbox in enumerate(textboxes): ++ combo_value = textbox.text ++ if combo_value.strip() != '': ++ type_value = comboboxes[index].combovalue ++ actual.append({'Field': unicode(type_value), 'Value': unicode(combo_value)}) ++ actual = sorted(actual) ++ ++ expected = [] ++ for row in context.table: ++ expected.append({'Field': row['Field'], 'Value': row['Value']}) ++ expected = sorted(expected) ++ ++ assert actual == expected, "Incorrect %s value:\nexpected:%s\n but was:%s" % ( ++ row['Field'], expected, actual) ++ ++ # Collapse the section after check ++ collapse_button.click() ++ ++ ++@step(u'Tick "Wants to receive HTML mail" checkbox') ++def tick_checkbox(context): ++ context.app.contact_editor.childNamed("Wants to receive HTML mail").click() ++ ++ ++@step(u'"Wants to receive HTML mail" checkbox is ticked') ++def checkbox_is_ticked(context): ++ check_state = context.app.childNamed("Wants to receive HTML mail").checked ++ assert check_state, "Incorrect checkbox state" ++ ++ ++@step(u'Switch to "{name}" tab in contact editor') ++def switch_to_tab(context, name): ++ context.app.contact_editor.tab(name).click() ++ ++ ++@step(u'Set the following properties in contact editor') ++def set_properties(context): ++ for row in context.table.rows: ++ context.execute_steps(u""" ++ * Set "%s" in contact editor to "%s" ++ """ % (row['Field'], row['Value'])) ++ ++ ++@step(u'The following properties in contact editor are set') ++def verify_properties(context): ++ for row in context.table.rows: ++ context.execute_steps(u""" ++ Then "%s" property is set to "%s" ++ """ % (row['Field'], row['Value'])) ++ ++ ++@step(u'Set the following properties in "{section}" section of contact editor') ++def set_properties_in_section(context, section): ++ for row in context.table.rows: ++ context.execute_steps(u""" ++ * Set "%s" in "%s" section of contact editor to "%s" ++ """ % (row['Field'], section, row['Value'])) ++ ++ ++@step(u'The following properties in "{section}" section of contact editor are set') ++def verify_properties_in_section(context, section): ++ for row in context.table.rows: ++ context.execute_steps(u""" ++ Then "%s" property in "%s" section is set to "%s" ++ """ % (row['Field'], section, row['Value'])) ++ ++ ++@step(u'Set the following note for the contact') ++def set_note_for_contact(context): ++ context.app.contact_editor.child( ++ roleName='page tab', name='Notes').textentry('').text = context.text ++ ++ ++@then(u'The following note is set for the contact') ++def verify_note_set_for_contact(context): ++ actual = context.app.contact_editor.child( ++ roleName='page tab', name='Notes').textentry('').text ++ expected = context.text ++ assert actual == expected,\ ++ "Incorrect note value:\nexpected:%s\n but was:%s" % (expected, actual) ++ ++ ++@step(u'Set "{field_name}" in "{section}" section of contact editor to "{field_value}"') ++def set_field_in_section_to_value(context, field_name, section, field_value): ++ element = get_element_by_name( ++ context.app.contact_editor, field_name, section=section) ++ if element.roleName == "text": ++ element.text = field_value ++ elif element.roleName == "combo box": ++ element.combovalue = field_value +diff -up evolution-3.13.4/tests/steps/initial_setup_steps.py.missing-tests evolution-3.13.4/tests/steps/initial_setup_steps.py +--- evolution-3.13.4/tests/steps/initial_setup_steps.py.missing-tests 2014-07-30 11:21:47.032172902 +0200 ++++ evolution-3.13.4/tests/steps/initial_setup_steps.py 2014-07-30 11:21:47.032172902 +0200 +@@ -0,0 +1,130 @@ ++# -*- coding: UTF-8 -*- ++from behave import step ++ ++from common_steps import check_for_errors ++from dogtail.tree import root ++from os import system ++from pyatspi import STATE_SENSITIVE ++from time import sleep ++ ++ ++@step(u'Open Evolution and setup fake account') ++def open_evolution_and_setup_fake_account(context): ++ system("evolution --force-shutdown 2&> /dev/null") ++ context.execute_steps(u'* Start a new Evolution instance') ++ window = context.app.child(roleName='frame') ++ if window.name == 'Welcome': ++ context.execute_steps(u""" ++ * Complete Welcome dialog in Evolution Account Assistant ++ * Complete Restore from Backup dialog in Evolution Account Assistant ++ * Complete Identity dialog setting name to "GNOME QE User" and email address to "test@test" ++ * Wait for account is being looked up dialog in Evolution Account Assistant ++ * Complete Receiving Email dialog of Evolution Account Assistant setting ++ | Field | Value | ++ | Server Type: | None | ++ * Complete Sending Email dialog of Evolution Account Assistant setting ++ | Field | Value | ++ | Server Type: | Sendmail | ++ * Complete Account Summary in Evolution Account Assistant ++ * Complete Done dialog in Evolution Account Assistant ++ """) ++ ++ ++@step(u'Complete Receiving Options in Evolution Account Assistant') ++@step(u'Complete Account Summary in Evolution Account Assistant') ++@step(u'Complete Restore from Backup dialog in Evolution Account Assistant') ++@step(u'Complete Welcome dialog in Evolution Account Assistant') ++def evo_account_assistant_dummy_dialogs(context): ++ # nothing to do here, skip it ++ window = context.app.child(roleName='frame') ++ click_next(window) ++ ++ ++@step(u'Complete Identity dialog setting name to "{name}" and email address to "{email}"') ++def evo_account_assistant_identity_dialog(context, name, email): ++ # nothing to do here, skip it ++ window = context.app.child(roleName='frame') ++ window.childLabelled("Full Name:").text = name ++ window.childLabelled("Email Address:").text = email ++ click_next(window) ++ ++ ++@step(u"Wait for account is being looked up dialog in Evolution Account Assistant") ++def wait_for_account_to_be_looked_up(context): ++ window = context.app.child(roleName='frame') ++ skip_lookup = window.findChildren(lambda x: x.name == 'Skip Lookup') ++ visible_skip_lookup = [x for x in skip_lookup if x.showing] ++ if len(visible_skip_lookup) > 0: ++ visible_skip_lookup = visible_skip_lookup[0] ++ # bug https://bugzilla.gnome.org/show_bug.cgi?id=726539: Skip Lookup is not being removed ++ #assert wait_until(lambda x: not x.showing, visible_skip_lookup),\ ++ # "Skip Lookup button didn't dissappear" ++ ++ ++def click_next(window): ++ # As initial wizard dialog creates a bunch of 'Next' buttons ++ # We have to click to the visible and enabled one ++ buttons = window.findChildren(lambda x: x.name == 'Next' and x.showing and ++ STATE_SENSITIVE in x.getState().getStates()) ++ if buttons == []: ++ raise Exception("Enabled Next button was not found") ++ else: ++ buttons[0].click() ++ ++ ++@step(u'Complete {sending_or_receiving} Email dialog of Evolution Account Assistant setting') ++def evo_account_assistant_receiving_email_dialog_from_table(context, sending_or_receiving): ++ window = context.app.child(roleName='frame') ++ for row in context.table: ++ label = str(row['Field']) ++ value = str(row['Value']) ++ filler = window.child(roleName='filler', name='%s Email' % sending_or_receiving) ++ widgets = filler.findChildren(lambda x: x.showing) ++ visible_widgets = [x for x in widgets if x.labeller and x.labeller.name == label] ++ if len(visible_widgets) == 0: ++ raise RuntimeError("Cannot find visible widget labelled '%s'" % label) ++ widget = visible_widgets[0] ++ if widget.roleName == 'combo box': ++ if label != 'Port:': ++ widget.click() ++ widget.menuItem(value).click() ++ else: ++ # Port is a combobox, but you can type your port there ++ widget.textentry('').text = value ++ widget.textentry('').grab_focus() ++ widget.textentry('').keyCombo("") ++ if widget.roleName == 'text': ++ widget.text = value ++ ++ # Check for password here and accept self-generated certificate (if appears) ++ btns = window.findChildren(lambda x: x.name == 'Check for Supported Types') ++ visible_btns = [w for w in btns if w.showing] ++ if visible_btns == []: ++ click_next(window) ++ return ++ visible_btns[0].click() ++ ++ # Confirm all certificates by clicking 'Accept Permanently' until dialog is visible ++ apps = [x.name for x in root.applications()] ++ if 'evolution-user-prompter' in apps: ++ prompter = root.application('evolution-user-prompter') ++ dialog = prompter.child(roleName='dialog') ++ while dialog.showing: ++ if prompter.findChild(lambda x: x.name == 'Accept Permanently', retry=False, requireResult=False): ++ prompter.button('Accept Permanently').click() ++ else: ++ sleep(0.1) ++ ++ # Wait until Cancel button disappears ++ cancel = filler.findChildren(lambda x: x.name == 'Cancel')[0] ++ while cancel.showing: ++ sleep(0.1) ++ check_for_errors(context) ++ click_next(window) ++ ++ ++@step(u'Complete Done dialog in Evolution Account Assistant') ++def evo_account_assistant_done_dialog(context): ++ # nothing to do here, skip it ++ window = context.app.child(roleName='frame') ++ window.button('Apply').click() +diff -up evolution-3.13.4/tests/steps/steps.py.missing-tests evolution-3.13.4/tests/steps/steps.py +--- evolution-3.13.4/tests/steps/steps.py.missing-tests 2014-07-30 11:21:47.032172902 +0200 ++++ evolution-3.13.4/tests/steps/steps.py 2014-07-30 11:21:47.032172902 +0200 +@@ -0,0 +1,177 @@ ++# -*- coding: UTF-8 -*- ++from behave import step, then ++from common_steps import wait_until ++from dogtail.tree import root ++from dogtail.rawinput import keyCombo ++from time import sleep, time ++from os import system ++from gi.repository import Gio, GLib ++ ++ ++@step(u'Help section "{name}" is displayed') ++def help_is_displayed(context, name): ++ try: ++ context.yelp = root.application('yelp') ++ frame = context.yelp.child(roleName='frame') ++ wait_until(lambda x: x.showing, frame) ++ sleep(1) ++ context.assertion.assertEquals(name, frame.name) ++ finally: ++ system("killall yelp") ++ ++ ++@step(u'Evolution has {num:d} window opened') ++@step(u'Evolution has {num:d} windows opened') ++def evolution_has_num_windows_opened(context, num): ++ windows = context.app.findChildren(lambda x: x.roleName == 'frame') ++ context.assertion.assertEqual(len(windows), num) ++ ++ ++@step(u'Preferences dialog is opened') ++def preferences_dialog_opened(context): ++ context.app.window('Evolution Preferences') ++ ++ ++@step(u'"{name}" view is opened') ++def view_is_opened(context, name): ++ if name != 'Mail': ++ window_name = context.app.children[0].name ++ context.assertion.assertEquals(window_name, "%s - Evolution" % name) ++ else: ++ # A special case for Mail ++ context.assertion.assertTrue(context.app.menu('Message').showing) ++ ++ ++def get_visible_searchbar(context): ++ """Wait for searchbar to become visible""" ++ def get_searchbars(): ++ return context.app.findChildren(lambda x: x.labeller.name == 'Search:' and x.showing) ++ assert wait_until(lambda x: len(x()) > 0, get_searchbars), "No visible searchbars found" ++ return get_searchbars()[0] ++ ++ ++@step(u'Open "{section_name}" section') ++def open_section_by_name(context, section_name): ++ wait_until(lambda x: x.showing, context.app.menu('View')) ++ sleep(0.2) ++ context.app.menu('View').click() ++ context.app.menu('View').menu('Window').point() ++ context.app.menu('View').menu('Window').menuItem(section_name).click() ++ ++ # Find a search bar ++ context.app.search_bar = get_visible_searchbar(context) ++ ++ # Check that service required for this sections is running ++ required_services = { ++ 'Mail': 'org.gnome.evolution.dataserver.Sources', ++ 'Calendar': 'org.gnome.evolution.dataserver.Calendar', ++ 'Tasks': 'org.gnome.evolution.dataserver.Calendar', ++ 'Memos': 'org.gnome.evolution.dataserver.Calendar', ++ 'Contacts': 'org.gnome.evolution.dataserver.AddressBook', ++ } ++ required_service = required_services[section_name] ++ bus = Gio.bus_get_sync(Gio.BusType.SESSION, None) ++ dbus_proxy = Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.NONE, None, ++ 'org.freedesktop.DBus', ++ '/org/freedesktop/DBus', ++ 'org.freedesktop.DBus', None) ++ for attempt in xrange(0, 10): ++ result = dbus_proxy.call_sync( ++ 'ListNames', None, Gio.DBusCallFlags.NO_AUTO_START, 500, None) ++ sleep(1) ++ if True in [required_service in x for x in result[0]]: ++ return ++ raise RuntimeError("%s service was not found" % required_service) ++ ++ ++@step(u'"{name}" menu is opened') ++def menu_is_opened(context, name): ++ sleep(0.5) ++ menu = context.app.menu(name) ++ children_displayed = [x.showing for x in menu.children] ++ context.assertion.assertTrue(True in children_displayed, "Menu '%s' is not opened" % name) ++ ++ ++@step(u'Press "{sequence}"') ++def press_button_sequence(context, sequence): ++ keyCombo(sequence) ++ sleep(0.5) ++ ++ ++@then(u'Evolution is closed') ++def evolution_is_closed(context): ++ assert wait_until(lambda x: x.dead, context.app),\ ++ "Evolution window is opened" ++ context.assertion.assertFalse(context.app_class.isRunning(), "Evolution is in the process list") ++ ++ ++@step(u'Message composer with title "{name}" is opened') ++def message_composer_is_opened(context, name): ++ context.app.composer = context.app.window(name) ++ ++ ++@then(u'Contact editor window with title "{title}" is opened') ++def contact_editor_with_label_is_opened(context, title): ++ context.app.contact_editor = context.app.dialog(title) ++ context.assertion.assertIsNotNone( ++ context.app.contact_editor, "Contact Editor was not found") ++ context.assertion.assertTrue( ++ context.app.contact_editor.showing, "Contact Editor didn't appear") ++ ++ ++@then(u'Contact editor window is opened') ++def contact_editor_is_opened(context): ++ context.execute_steps(u'Then Contact editor window with title "Contact Editor" is opened') ++ ++ ++@then(u'Contact List editor window is opened') ++def contact_list_editor_is_opened(context): ++ context.execute_steps( ++ u'Then Contact List editor window with title "Contact List Editor" is opened') ++ ++ ++@then(u'Contact List editor window with title "{name}" is opened') ++def contact_list_editor__with_name_is_opened(context, name): ++ context.app.contact_list_editor = context.app.dialog(name) ++ ++ ++@step(u'Memo editor with title "{name}" is opened') ++def memo_editor_is_opened(context, name): ++ context.execute_steps(u'* Task editor with title "%s" is opened' % name) ++ ++ ++@step(u'Shared Memo editor with title "{name}" is opened') ++def shared_memo_editor_is_opened(context, name): ++ context.execute_steps(u'* Task editor with title "%s" is opened' % name) ++ ++ ++@step(u'Task editor with title "{title}" is opened') ++def task_editor_with_title_is_opened(context, title): ++ context.app.task_editor = context.app.window(title) ++ # Spoof event_editor for assigned tasks ++ if 'Assigned' in title: ++ context.app.event_editor = context.app.task_editor ++ ++ ++@step(u'Event editor with title "{name}" is displayed') ++def event_editor_with_name_displayed(context, name): ++ context.app.event_editor = context.app.window(name) ++ ++ ++@step(u'Wait for email to synchronize') ++def wait_for_mail_folder_to_synchronize(context): ++ # Wait until Google calendar is loaded ++ for attempt in range(0, 10): ++ start_time = time() ++ try: ++ spinners = context.app.findChildren(lambda x: x.name == 'Spinner') ++ for spinner in spinners: ++ try: ++ while spinner.showing: ++ sleep(0.1) ++ if (time() - start_time) > 180: ++ raise RuntimeError("Mail takes too long to synchronize") ++ except GLib.GError: ++ continue ++ except (GLib.GError, TypeError): ++ continue diff --git a/evolution.spec b/evolution.spec index 865bee1..45faed1 100644 --- a/evolution.spec +++ b/evolution.spec @@ -50,6 +50,8 @@ Patch01: evolution-1.4.4-ldap-x86_64-hack.patch # RH bug #589555 Patch02: evolution-2.30.1-help-contents.patch +Patch03: evolution-3.13.4-missing-tests.patch + ## Dependencies ### Requires: gvfs @@ -216,6 +218,7 @@ the functionality of the installed %{name} package. %setup -q -n evolution-%{version} %patch01 -p1 -b .ldaphack %patch02 -p1 -b .help-contents +%patch03 -p1 -b .missing-tests # Remove the welcome email from Novell for inbox in mail/default/*/Inbox; do @@ -545,6 +548,7 @@ rm -rf $RPM_BUILD_ROOT * Wed Jul 30 2014 Milan Crha - 3.13.4-1 - Update to 3.13.4 - Introduce tests subpackage with installed tests +- Add patch to add missing tests files in the distribution tarball - Remove patch to drop gnome-icon-theme dependency (fixed upstream) * Mon Jul 14 2014 Milan Crha - 3.12.4-1