From 3c2690b7e7b85e475b0e84348c087689e8881bc5 Mon Sep 17 00:00:00 2001
From: Shivani Poddar <shivani.poddar92@gmail.com>
Date: Tue, 19 Aug 2014 01:18:40 +0530
Subject: [PATCH] Adding tests for Evince

---
 .gitignore                    |   3 +-
 Makefile.am                   |   4 +
 configure.ac                  | 186 +-----------------------
 m4/behave-installed-tests.m4  | 133 +++++++++++++++++
 test/basic-ui-testing.feature |  50 +++++++
 test/behave_common_steps.py   | 321 ++++++++++++++++++++++++++++++++++++++++++
 test/cleanup.py               |   8 ++
 test/environment.py           |  90 ++++++++++++
 test/general.feature          |  23 +++
 test/steps/__init__.py        |   0
 test/steps/steps.py           |  54 +++++++
 test/test-page-labels.pdf     | Bin 0 -> 10150 bytes
 12 files changed, 688 insertions(+), 184 deletions(-)
 create mode 100644 m4/behave-installed-tests.m4
 create mode 100644 test/basic-ui-testing.feature
 create mode 100644 test/behave_common_steps.py
 create mode 100644 test/cleanup.py
 create mode 100644 test/environment.py
 create mode 100644 test/general.feature
 create mode 100644 test/steps/__init__.py
 create mode 100644 test/steps/steps.py
 create mode 100644 test/test-page-labels.pdf

diff --git a/.gitignore b/.gitignore
index ade7d61..c9b568f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
-*.pyc, *.swp
+*.pyc
+*.swp
diff --git a/Makefile.am b/Makefile.am
index ba9a523..e14424b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -101,6 +101,10 @@ DISTCHECK_CONFIGURE_FLAGS = \
 	--disable-silent-rules \
 	--enable-introspection
 
+@BEHAVE_INSTALLED_TESTS_RULE@
+INSTALLED_TESTS=test_file_open_button test_document_viewer_settings test_reload_doc test_print_file
+INSTALLED_TESTS_TYPE=session-exclusive
+
 # Ignore scrollkeeper issues for now.  @#*$& scrollkeeper
 distuninstallcheck_listfiles = find . -type f -print | grep -v scrollkeeper | grep -v /share/gnome/help/ | grep -v \.omf
 
diff --git a/configure.ac b/configure.ac
index d02a684..fe2577f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -51,190 +51,10 @@ if test -z "$enable_maintainer_mode"; then
 fi
 AM_MAINTAINER_MODE([enable])
 
-m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
-
-AC_PROG_CC
-AM_PROG_CC_C_O
-AC_PROG_CXX
-
-AC_PROG_SED
-
-# Initialize libtool
-LT_PREREQ([2.2])
-LT_INIT
-LT_LIB_M
-
-GNOME_CXX_WARNINGS
-
-IT_PROG_INTLTOOL([0.35.0])
-
-GETTEXT_PACKAGE=evince
-AC_SUBST(GETTEXT_PACKAGE)
-AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE],["$GETTEXT_PACKAGE"],[Gettext package])
-AM_GLIB_GNU_GETTEXT
-
-m4_pattern_allow([AM_V_GEN])dnl Make autoconf not complain about the rule below
-EV_INTLTOOL_EVINCE_BACKEND_RULE='%.evince-backend:   %.evince-backend.in   $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(AM_V_GEN) LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@'
-AC_SUBST([EV_INTLTOOL_EVINCE_BACKEND_RULE])
-
-GLIB_GSETTINGS
-
-# Check which platform to use
-
-AC_MSG_CHECKING([for which platform to build])
-AC_ARG_WITH([platform],
-  [AS_HELP_STRING([--with-platform=gnome|win32],
-                  [Setting platform (default: gnome)])],
-  [case "$withval" in
-    gnome|win32) ;;
-    *) AC_MSG_ERROR([invalid argument "$withval" for --with-platform]) ;;
-   esac],
-  [case "$host" in
-     *-*-mingw*|*-*-cygwin*) with_platform="win32" ;;
-     *) with_platform=gnome ;;
-   esac])
-
-AC_MSG_RESULT([$with_platform])
-
-if test "$with_platform" = "win32"; then
-  AC_CHECK_TOOL([WINDRES],[windres])
-  AC_MSG_CHECKING([for native Win32])
-  case "$host" in
-    *-*-mingw*)
-      os_win32=yes
-      ;;
-    *)
-      os_win32=no
-      ;;
-  esac
-  AC_MSG_RESULT([$os_win32])
-
-  AM_CFLAGS="$AM_CFLAGS -D_WIN32_WINNT=0x0500"
-fi
-
-AM_CONDITIONAL([PLATFORM_WIN32],[test "$with_platform" = "win32"])
-
-dnl Specify required versions of dependencies
-CAIRO_REQUIRED=1.10.0
-GLIB_REQUIRED=2.36.0
-LIBSECRET_REQUIRED=0.5
-GTK_REQUIRED=3.12.0
-NAUTILUS_REQUIRED=2.91.4
-
-AC_SUBST([GLIB_REQUIRED])
-AC_SUBST([GTK_REQUIRED])
-
-AC_DEFINE([GDK_VERSION_MIN_REQUIRED], [GDK_VERSION_3_8], [Minimum GTK/GDK version required])
-
-ADWAITA_ICON_THEME_REQUIRED=2.17.1
-LIBXML_REQUIRED=2.5.0
-
-dnl Check dependencies
-
-# LIB_CFLAGS       for helpers and generic widgets. (libdocument, cut-and-paste)
-# BACKEND_CFLAGS   for backend implementations.
-# FRONTEND_CFLAGS  for frontend implementations. (properties, thumbnailer)
-# FRONTEND_LIBS
-# SHELL_CFLAGS     for shell implementation.
-# SHELL_LIBS
-
-have_zlib=yes
-AC_CHECK_HEADERS([zlib.h],
-	[AC_CHECK_LIB([z], [inflate],
-		[AC_CHECK_LIB([z], [crc32], [have_zlib=yes], [have_zlib=no])],
-		[have_zlib=no])],
-	[have_zlib=no])
-
-if test x$have_zlib = xno; then
-	AC_MSG_ERROR([No sufficient zlib library found on your system.])
-fi
-
-ZLIB_LIBS=-lz
-AC_SUBST(ZLIB_LIBS)
-
-PKG_CHECK_MODULES(LIBDOCUMENT, gtk+-3.0 >= $GTK_REQUIRED gio-2.0 >= $GLIB_REQUIRED gmodule-no-export-2.0 >= $GLIB_REQUIRED gmodule-2.0)
-PKG_CHECK_MODULES(LIBVIEW, gtk+-3.0 >= $GTK_REQUIRED gthread-2.0 gio-2.0 >= $GLIB_REQUIRED)
-PKG_CHECK_MODULES(BACKEND, cairo >= $CAIRO_REQUIRED gtk+-3.0 >= $GTK_REQUIRED)
-PKG_CHECK_MODULES(FRONTEND_CORE, gtk+-3.0 >= $GTK_REQUIRED gthread-2.0 gio-2.0 >= $GLIB_REQUIRED gmodule-no-export-2.0 >= $GLIB_REQUIRED)
-
-# Although GTK+ 3.10 includes hi-dpi functionality, it does not require a cairo with
-# cairo_surface_set_device_scale(), which we also need if we're to support hi-dpi,
-# so we need check for that explicity.
-
-evince_save_LIBS=$LIBS
-LIBS="$LIBS $LIBVIEW_LIBS"
-AC_CHECK_FUNCS(cairo_surface_set_device_scale)
-LIBS=$evince_save_LIBS
-
-AC_MSG_CHECKING([for hi-dpi support])
-if test "$ac_cv_func_cairo_surface_set_device_scale" = yes ; then
-   AC_DEFINE([HAVE_HIDPI_SUPPORT], [1], [Define if cairo and GTK+ have necessary functions for hi-dpi])
-   AC_MSG_RESULT([yes])
-else
-   AC_MSG_RESULT([no])
-fi
-
-SHELL_PLATFORM_PKGS=
-case "$with_platform" in
-  gnome)
-        # Evince has a rather soft run-time dependency on hicolor-icon-theme.
-        # If the hicolor theme is not available, Evince fails to display some
-        # icons. Because we cannot check for it at run-time, we instead
-        # would like to require the icon theme at compile-time. But, because
-        # the hicolor-icon-theme does not have a pkgconfig file, on gnome we
-        # require the gnome icon theme instead.
-        SHELL_PLATFORM_PKGS="adwaita-icon-theme >= $ADWAITA_ICON_THEME_REQUIRED"
-        ;;
-  *)
-        # On all other platforms we issue a warning about the runtime
-        # dependency.
-        AC_MSG_WARN([Evince has a soft run-time dependency on hicolor-icon-theme. You are advised to have this theme installed when running Evince.]);
-        SHELL_PLATFORM_PKGS=""
-        ;;
-esac
-
-PKG_CHECK_MODULES([SHELL_CORE],[libxml-2.0 >= $LIBXML_REQUIRED gtk+-3.0 >= $GTK_REQUIRED gio-2.0 >= $GLIB_REQUIRED gmodule-no-export-2.0 >= $GLIB_REQUIRED gthread-2.0 $SHELL_PLATFORM_PKGS])
-
-# ***************
-# Build utilities
-# ***************
-
-AC_ARG_VAR([GLIB_GENMARSHAL],[the glib-genmarschal programme])
-AC_PATH_PROG([GLIB_GENMARSHAL],[glib-genmarshal],[])
-if test -z "$GLIB_GENMARSHAL"; then
-  AC_MSG_ERROR([glib-genmarshal not found])
-fi
-
-AC_ARG_VAR([GLIB_MKENUMS],[the glib-mkenums programme])
-AC_PATH_PROG([GLIB_MKENUMS],[glib-mkenums],[])
-if test -z "$GLIB_MKENUMS"; then
-  AC_MSG_ERROR([glib-mkenums not found])
-fi
-
-AC_ARG_VAR([GLIB_COMPILE_RESOURCES],[the glib-compile-resources programme])
-AC_PATH_PROG([GLIB_COMPILE_RESOURCES],[glib-compile-resources],[])
-if test -z "$GLIB_COMPILE_RESOURCES"; then
-  AC_MSG_ERROR([glib-compile-resources not found])
-fi
-
-AC_ARG_VAR([XMLLINT],[the xmllint programme])
-AC_PATH_PROG([XMLLINT],[xmllint],[])
-if test -z "$XMLLINT"; then
-  AC_MSG_ERROR([xmllint not found])
-fi
-
-AC_ARG_VAR([GDBUS_CODEGEN],[the gdbus-codegen programme])
-AC_PATH_PROG([GDBUS_CODEGEN],[gdbus-codegen],[])
-if test -z "$GDBUS_CODEGEN"; then
-  AC_MSG_ERROR([gdbus-codegen not found])
-fi
-
-# ***
-
-BACKEND_LIBTOOL_FLAGS="-module -avoid-version -no-undefined -export-symbols \$(top_srcdir)/backend/backend.symbols"
-AC_SUBST(BACKEND_LIBTOOL_FLAGS)
+dnl Installed tests
+BEHAVE_INSTALLED_TESTS
 
-dnl ===== Check special functions
+special functions
 evince_save_LIBS=$LIBS
 LIBS="$LIBS $BACKEND_LIBS"
 AC_CHECK_FUNCS(cairo_format_stride_for_width)
diff --git a/m4/behave-installed-tests.m4 b/m4/behave-installed-tests.m4
new file mode 100644
index 0000000..f110c02
--- /dev/null
+++ b/m4/behave-installed-tests.m4
@@ -0,0 +1,133 @@
+# How to use the installed tests m4
+#
+#   Place BEHAVE_INSTALLED_TESTS somewhere in configure.ac
+#
+#   Writing your Makefile.am
+#   ~~~~~~~~~~~~~~~~~~~~~~~~
+#
+#   Somewhere in your Makefile.am in this test directory, you need to declare
+#   the following variables:
+#
+#       INSTALLED_TESTS=list of tags for tests to install
+#       INSTALLED_TESTS_TYPE=session-exclusive
+#
+#   First the list of tests which should be installed, followed by
+#   the type of test they should be configured as. The type can
+#   be 'session' or 'session-exclusive'
+#
+#   More information about valid types can be found here:
+#      https://wiki.gnome.org/GnomeGoals/InstalledTests
+#
+#   The last variable is optional, but can be useful to configure
+#   your test program to run in the installed environment as opposed
+#   to the normal `make check' run.
+#
+#   Then place this somewhere in your Makefile.am
+#
+#       @BEHAVE_INSTALLED_TESTS_RULE@
+#
+#   And the following in configure.ac
+#
+#       BEHAVE_INSTALLED_TESTS
+#
+#   And that's it, now your unit tests will be installed along with
+#   a .test metadata file into $(pkglibexecdir) if --enable-installed-tests
+#   is passed to your configure script, and will be run automatically
+#   by the continuous integration servers.
+#
+#   FIXME: Change the above link to point to real documentation, not
+#   a gnome goal page which might disappear at some point.
+#
+# BUGS: This macro hooks into install-exec-am and install-data-am
+# which are internals of Automake. This is because Automake doesnt
+# consider the regular install-exec-local / install-exec-hook or
+# data install components unless variables have been setup for them
+# in advance.
+#
+# This doesnt seem to present a problem, but it is depending on
+# internals of Automake instead of clear documented API.
+
+# Place this in configure.ac to enable
+# the installed tests option.
+
+AC_DEFUN([BEHAVE_INSTALLED_TESTS], [
+AC_PREREQ([2.50])dnl
+AC_REQUIRE([AM_NLS])dnl
+
+  AC_PROG_INSTALL
+  AC_PROG_MKDIR_P
+  AC_PROG_LIBTOOL
+
+  AC_ARG_ENABLE(installed-tests,
+		[AC_HELP_STRING([--enable-installed-tests],
+				[enable installed unit tests [default=no]])],,
+		[enable_installed_tests="no"])
+
+  AM_CONDITIONAL([BEHAVE_INSTALLED_TESTS_ENABLED],[test "x$enable_installed_tests" = "xyes"])
+  AC_SUBST([BEHAVE_INSTALLED_TESTS_ENABLED], [$enable_installed_tests])
+
+  # Define the rule for makefiles
+  BEHAVE_INSTALLED_TESTS_RULE='
+
+ifeq ($(BEHAVE_INSTALLED_TESTS_ENABLED),yes)
+
+install-exec-am: installed-tests-exec-hook
+install-data-am: installed-tests-data-hook
+uninstall-am: uninstall-tests-hook
+
+META_DIRECTORY=${DESTDIR}${datadir}/installed-tests/${PACKAGE}
+EXEC_DIRECTORY=${DESTDIR}${pkglibexecdir}/installed-tests
+
+BEHAVE_FEATURES=$(notdir $(wildcard $(srcdir)/tests/*.feature))
+BEHAVE_STEP_DEFINITION=$(notdir $(wildcard $(srcdir)/tests/steps/*.py))
+BEHAVE_COMMON_FILES=environment.py common_steps.py
+
+installed-tests-exec-hook:
+	@$(MKDIR_P) $(EXEC_DIRECTORY);
+	@for feature in $(BEHAVE_FEATURES); do											\
+	    $(LIBTOOL) --mode=install $(INSTALL) --mode=777 $(srcdir)/tests/$$feature $(EXEC_DIRECTORY);\
+	done
+	@for common_file in $(BEHAVE_COMMON_FILES); do										\
+	    $(LIBTOOL) --mode=install $(INSTALL) --mode=777 $(srcdir)/tests/$$common_file $(EXEC_DIRECTORY);\
+	done
+	@$(MKDIR_P) $(EXEC_DIRECTORY)/steps;
+	@for step_definition in $(BEHAVE_STEP_DEFINITION); do									\
+	    $(LIBTOOL) --mode=install $(INSTALL) --mode=777 $(srcdir)/tests/steps/$$step_definition $(EXEC_DIRECTORY)/steps;\
+	done
+
+
+installed-tests-data-hook:
+	@$(MKDIR_P) $(META_DIRECTORY);
+	@for test in $(INSTALLED_TESTS); do							\
+	    echo "Installing $$test.test to $(META_DIRECTORY)";					\
+	    echo m4_escape([[Test]]) > $(META_DIRECTORY)/$$test.test;				\
+	    echo "Exec=behave $(pkglibexecdir)/installed-tests -t $$test -k -f html -o $$test.html -f plain"	\
+	                                           >> $(META_DIRECTORY)/$$test.test;		\
+	    echo "Type=$(INSTALLED_TESTS_TYPE)" >> $(META_DIRECTORY)/$$test.test;		\
+	done
+
+uninstall-tests-hook:
+	@for feature in $(BEHAVE_FEATURES); do\
+	    echo "Removing feature $(EXEC_DIRECTORY) $$feature";\
+	    $(LIBTOOL) --mode=uninstall $(RM) $(EXEC_DIRECTORY)/$$feature;\
+	done
+	@for common_file in $(BEHAVE_COMMON_FILES); do\
+	    echo "Removing feature $(EXEC_DIRECTORY) $$common_file";\
+	    $(LIBTOOL) --mode=uninstall $(RM) $(EXEC_DIRECTORY)/$$common_file;\
+	done
+	@for step_definition in $(BEHAVE_STEP_DEFINITION); do\
+	    echo "Removing feature $(EXEC_DIRECTORY)/steps $$step_definition";\
+	    $(LIBTOOL) --mode=uninstall $(RM) $(EXEC_DIRECTORY)/steps/$$step_definition;\
+	done
+	@for test in $(INSTALLED_TESTS); do\
+	    $(LIBTOOL) --mode=uninstall $(RM) $(META_DIRECTORY)/$$test.test;\
+	done
+
+endif
+'
+
+  # substitute @BEHAVE_INSTALLED_TESTS_RULE@ in Makefiles
+  AC_SUBST([BEHAVE_INSTALLED_TESTS_RULE])
+  m4_ifdef([_AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE([BEHAVE_INSTALLED_TESTS_RULE])])
+])
+
diff --git a/test/basic-ui-testing.feature b/test/basic-ui-testing.feature
new file mode 100644
index 0000000..2120edf
--- /dev/null
+++ b/test/basic-ui-testing.feature
@@ -0,0 +1,50 @@
+Feature: Basic UI Actions
+
+  @test_file_open_button
+  Scenario: Open new file
+    * Make sure that Evince is running
+    * Press "<Ctrl>o"
+    Then Open document window should be open
+
+  @test_document_viewer_settings
+  Scenario: Edit viewer settings
+    * Make sure that Evince is running
+    * Open Document Viewer Settings
+
+  @test_reload_doc
+  Scenario: Check reloading
+    * Make sure that Evince is running
+    * Copy test document to ~/Documents
+    * Press "<Ctrl>o"
+    * Press "<enter>"
+    * Press "<Ctrl>r"
+    #Add the remove part in cleanup eventually
+    * Remove test document from ~/Documents 
+
+
+  @test_print_file 
+  Scenario: Check Printing of Opened File
+    * Make sure that Evince is running
+    * Copy test document to ~/Documents
+    * Press "<Ctrl>o"
+    * Press "<enter>"
+    * Press "<Ctrl>p"
+    Then Print dialogue should appear
+    * Remove test document from ~/Documents
+
+
+
+#  @test_about_dialog_open
+#  Scenario:Open About dialog 
+#    * Start Evince via command
+#    * Make sure that Evince is running 
+#    * Open About Document Viewer
+#    * Open Credits
+#    Then Credits should show
+
+#  @test_about_dialog_close
+#  Scenario: Open About Document Viewer
+#    * Open Credits
+#    * Close credits
+#    * Close About document viewer
+#    Then About should not show
diff --git a/test/behave_common_steps.py b/test/behave_common_steps.py
new file mode 100644
index 0000000..2086910
--- /dev/null
+++ b/test/behave_common_steps.py
@@ -0,0 +1,321 @@
+# -*- 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,then
+from gi.repository import GLib, Gio
+import fcntl, os
+from dogtail.rawinput import keyCombo, click, typeText, absoluteMotion, pressKey
+from dogtail.tree import root, SearchError
+from iniparse import ConfigParser
+import traceback
+from unittest import TestCase
+import logging
+
+
+# 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, desktopFileName = None, shortcut='<Control><Q>', 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
+        if desktopFileName is None:
+            desktopFileName = self.appCommand
+        self.desktopFileName = desktopFileName
+        # 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('<Control><Alt><Shift>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 getDashIconPosition(name):
+        """Get a position of miniature on Overview"""
+        over = root.application('gnome-shell').child(name='Overview')
+        button = over[2].child(name=name)
+        (x, y) = button.position
+        (a, b) = button.size
+        return (x + a / 2, y + b / 2)
+    
+    def parseDesktopFile(self):
+        """
+        Getting all necessary data from *.dektop file of the app
+        """
+        cmd = "rpm -qlf $(which %s)" % self.appCommand
+        cmd += '| grep "^/usr/share/applications/.*%s.desktop$"' % self.desktopFileName
+        proc = Popen(cmd, shell=True, stdout=PIPE)
+        # !HAVE TO check if the command and its desktop file exist
+        if proc.wait() != 0:
+            raise Exception("*.desktop file of the app not found")
+        output = proc.communicate()[0].rstrip()
+        self.desktopConfig = ConfigParser()
+        self.desktopConfig.read(output)
+    
+    def kill(self):
+        """
+        Kill the app via 'killall'
+        """
+        if self.recordVideo:
+            keyCombo('<Control><Alt><Shift>R')
+
+        try:
+            self.process.kill()
+        except:
+            # Fall back to killall
+            Popen("killall " + self.appCommand, shell=True).wait()
+
+    def getName(self):
+        return self.desktopConfig.get('Desktop Entry', 'name')
+
+    def startViaCommand(self):
+        """
+        Start the app via command
+        """
+        if self.forceKill and self.isRunning():
+            self.kill()
+            sleep(2)
+            assert not self.isRunning(), "Application cannot be stopped"
+
+        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 startViaMenu(self, throughCategories = False):
+        self.parseDesktopFile()
+        if self.forceKill and self.isRunning():
+            self.kill()
+            sleep(2)
+            assert not self.isRunning(), "Application cannot be stopped"
+        try:
+            gnomeShell = root.application('gnome-shell')
+            pressKey('Super_L')
+            sleep(6)
+            if throughCategories:
+                # menu Applications
+                x, y = getDashIconPosition('Show Applications')
+                absoluteMotion(x, y)
+                time.sleep(1)
+                click(x, y)
+                time.sleep(4) # time for all the oversized app icons to appear
+
+                # submenu that contains the app
+                submenu = gnomeShell.child(
+                    name=self.getMenuGroups(), roleName='list item')
+                submenu.click()
+                time.sleep(4)
+
+                # the app itself
+                app = gnomeShell.child(
+                    name=self.getName(), roleName='label')
+                app.click()
+            else:
+                typeText(self.getName())
+                sleep(2)
+                pressKey('Enter')
+
+            assert self.isRunning(), "Application failed to start"
+        except SearchError:
+            print("!!! Lookup error while passing the path")
+        
+        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'Press "{sequence}"')
+def press_button_sequence(context, sequence):
+    keyCombo(sequence)
+    sleep(0.5)
+
+
+def wait_for_app_to_appear(context, app):
+    # Waiting for a window to appear
+    for attempt in xrange(0, 10):
+        try:
+            context.app.instance = root.application(app.lower())
+            context.app.instance.child(roleName='frame')
+            break
+        except (GLib.GError, SearchError):
+            sleep(1)
+            continue
+    context.execute_steps("Then %s should start" % app)
+
+
+@step(u'Start {app} via {type:w}')
+def start_app_via_command(context, app, type):
+    for attempt in xrange(0, 10):
+        try:
+            if type == 'command':
+                context.app.startViaCommand()
+            if type == 'menu':
+                context.app.startViaMenu()
+            break
+        except GLib.GError:
+            sleep(1)
+            if attempt == 6:
+                # Killall the app processes if app didn't show up after 5 seconds
+                os.system("pkill -f %s 2&> /dev/null" % app.lower())
+                os.system("python cleanup.py")
+                context.execute_steps("* Start %s via command" % app)
+            continue
+
+@step(u'Close app via gnome panel')
+def close_app_via_gnome_panel(context):
+    context.app.closeViaGnomePanel()
+
+@step(u'Click "Quit" in GApplication menu')
+def close_app_via_shortcut(context) :
+    context.app.closeViaShortcut
+
+@step(u'Make sure that {app} is running')
+def ensure_app_running(context, app):
+    start_app_via_command(context, app, 'command')
+    wait_for_app_to_appear(context, app)
+    logging.debug("app = %s", root.application(app.lower()))
+
+@then(u'{app} should start')
+def test_app_started(context, app):
+    # Dogtail seems to cache applications list
+    # So we should wait for exception here
+    try:
+        root.application(app.lower()).child(roleName='frame')
+    except SearchError:
+        raise RuntimeError("App '%s' is not running" % app)
+
+@then(u"{app} shouldn't be running anymore")
+def then_app_is_dead(context, app):
+    try:
+        root.application(app.lower()).child(roleName='frame')
+        raise RuntimeError("App '%s' is running" % app)
+    except SearchError:
+        pass
+
+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 --git a/test/cleanup.py b/test/cleanup.py
new file mode 100644
index 0000000..deb5e08
--- /dev/null
+++ b/test/cleanup.py
@@ -0,0 +1,8 @@
+#!/bin/env python
+
+import os
+from gi.repository import Gio
+
+# Reset GSettings
+for schema in ['org.gnome.Evince']:
+    os.system("gsettings reset-recursively %s" % schema)
diff --git a/test/environment.py b/test/environment.py
new file mode 100644
index 0000000..9f3faac
--- /dev/null
+++ b/test/environment.py
@@ -0,0 +1,90 @@
+# -*- coding: UTF-8 -*-
+
+import os
+from behave_common_steps import *
+from dogtail.config import config
+from time import sleep, localtime, strftime
+import problem
+
+
+def before_all(context):
+    """Setup evince stuff
+    Being executed before all features
+    """
+
+    try:
+        # Cleanup abrt crashes
+        [x.delete() for x in problem.list()]
+
+        # Do the cleanup
+        os.system("python cleanup.py > /dev/null")
+
+        # Skip dogtail actions to print to stdout
+        config.logDebugToStdOut = False
+        config.typingDelay = 0.2
+
+        # Kill initial setup
+        os.system("killall /usr/libexec/gnome-initial-setup")
+
+        # Store scenario start time for session logs
+        context.log_start_time = strftime("%Y-%m-%d %H:%M:%S", localtime())
+
+        context.app = App('evince')
+
+    except Exception as e:
+        print("Error in before_all: %s" % e.message)
+
+
+def after_step(context, step):
+    """Teardown after each step.
+    Here we make screenshot and embed it (if one of formatters supports it)
+    """
+    try:
+        if problem.list():
+            problems = problem.list()
+            for crash in problems:
+                if hasattr(context, "embed"):
+                    context.embed('text/plain', "abrt has detected a crash: %s" % crash.reason)
+                else:
+                    print("abrt has detected a crash: %s" % crash.reason)
+
+            # Crash was stored, so it is safe to remove it now
+            [x.delete() for x in problems]
+
+        if step.status == 'failed':
+            # Make screnshot if step has failed
+            if hasattr(context, "embed"):
+                os.system("gnome-screenshot -f /tmp/screenshot.jpg")
+                context.embed('image/jpg', open("/tmp/screenshot.jpg", 'r').read())
+
+            # Test debugging - set DEBUG_ON_FAILURE to drop to ipdb on step failure
+            if os.environ.get('DEBUG_ON_FAILURE'):
+                import ipdb; ipdb.set_trace()  # flake8: noqa
+
+    except Exception as e:
+        print("Error in after_step: %s" % e.message)
+
+
+def after_scenario(context, scenario):
+    """Teardown for each scenario
+    Kill evince (in order to make this reliable we send sigkill)
+    """
+    try:
+        # Stop evince
+        os.system("killall evince &> /dev/null")
+
+        # Attach journalctl logs
+        if hasattr(context, "embed"):
+            os.system("sudo 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)
+
+        # Make some pause after scenario
+        sleep(1)
+
+        # Do the cleanup
+        os.system("python cleanup.py")
+    except Exception as e:
+        # Stupid behave simply crashes in case exception has occurred
+        print("Error in after_scenario: %s" % e.message)
diff --git a/test/general.feature b/test/general.feature
new file mode 100644
index 0000000..00bc54c
--- /dev/null
+++ b/test/general.feature
@@ -0,0 +1,23 @@
+Feature: General actions
+
+  @start_via_command
+  Scenario: Start via command
+    * Start Evince via command
+    Then evince should start
+
+  @start_via_menu
+  Scenario: Start via menu
+    * Start Evince via menu
+    Then evince should start
+
+  @quit_via_shortcut
+  Scenario: Ctrl-Q to quit application
+    * Start Evince via command
+    * Press "<Ctrl>q"
+    Then evince shouldn't be running anymore
+
+  @close_via_gnome_panel
+  Scenario: Close via menu
+    * Start Evince via menu
+    * Click "Quit" in GApplication menu
+    Then Evince shouldn't be running anymore
diff --git a/test/steps/__init__.py b/test/steps/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/test/steps/steps.py b/test/steps/steps.py
new file mode 100644
index 0000000..85a497a
--- /dev/null
+++ b/test/steps/steps.py
@@ -0,0 +1,54 @@
+from behave import step
+from dogtail.tree import *
+from dogtail.rawinput import *
+from dogtail.utils import *
+from behave_common_steps import *
+
+import os
+os.environ['LANG']='C'
+srcdir = os.getcwd()
+
+@step(u'Open the New file Dialog')
+def open_new_file_dia(context):
+    frame = context.app.instance.child(roleName='frame')
+    filler = frame.child(roleName='filler')
+    toolbar = filler.child(roleName='tool bar')
+    open_button = toolbar[7][0]
+    assert open_button.click(),"Open Dialogue is not clickable"
+        
+@then(u'Open document window should be open')
+def check_file_chooser_open(context):    
+    file_chooser = context.app.instance.child(roleName='file chooser')
+    assert file_chooser
+
+@step(u'Open Document Viewer Settings')
+def open_settings(context):
+    frame = context.app.instance.child(roleName='frame')
+    filler = frame.child(roleName='filler')
+    toolbar = filler.child(roleName='tool bar')
+    setting_button = toolbar[6][0]
+    assert setting_button.click(), "Settings button is not clickable"
+
+@step(u'Copy test document to ~/Documents')
+def open_new_file(context):
+    global srcdir
+    print srcdir+'/test-page-labels.pdf'
+    os.rename(srcdir+'/test-page-labels.pdf',os.environ["HOME"]+'/Documents/test-page-labels.pdf')
+    
+@step(u'Remove test document from ~/Documents')
+def remove_test_doc(context):
+    global srcdir
+    os.rename(os.environ["HOME"]+'/Documents/test-page-labels.pdf', srcdir+'/test-page-labels.pdf')
+
+@then(u'Print dialogue should appear')
+def check_print_dialog(context):
+    print_dialog = context.app.instance.child(roleName='dialog')
+    assert print_dialog    
+@step(u'Open About Document Viewer')
+def open_doc_view(context):
+    frame = context.app.instance.child(roleName='frame')
+    filler = frame.child(roleName='filler')
+    toolbar = filler.child(roleName='tool bar')
+    open_button = toolbar[7][0]   
+    open_button.click()
+    open_button.pressKey('enter')
diff --git a/test/test-page-labels.pdf b/test/test-page-labels.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..c7d12671e0ad2b6335d1c88071f3a897f9c353da
GIT binary patch
literal 10150
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

literal 0
HcmV?d00001

-- 
1.8.3.1

