[gnome-boxes] Add general tests



commit c8ddf2192f0d9948736b2d02de2dd90f5be64dd6
Author: Vladimir Benes <vbenes redhat com>
Date:   Tue Jan 6 07:58:42 2015 -0500

    Add general tests
    
    Behave/Dogtail set of automated tests. Tests use 3 10 MB iso files that
    are downloaded at the beginning. Installed-tests infrastructure bits are
    also included so tests can be executed via gnome-desktop-testing-runner
    (after running ./autogen.sh --enable-installed-tests). Test can be
    executed one by one or in a batch.
    
    More details to be found in README file.
    
    Coverage in general section:
     * start/stop
     * help/credits
     * download an iso
     * customize memory
     * start box
     * search boxes
    
    WARNING: These tests are designed to run in a prestine test environment and to
             that end, remove all your VMs.
    
    https://bugzilla.gnome.org/review?bug=736288

 Makefile.am                  |    5 +
 configure.ac                 |    4 +
 m4/behave-installed-tests.m4 |  137 +++++++++++++++++++++++++++++
 tests/README                 |   57 ++++++++++++
 tests/common_steps.py        |  194 ++++++++++++++++++++++++++++++++++++++++++
 tests/environment.py         |  157 ++++++++++++++++++++++++++++++++++
 tests/general.feature        |  130 ++++++++++++++++++++++++++++
 tests/steps/creation.py      |   59 +++++++++++++
 tests/steps/general.py       |  184 +++++++++++++++++++++++++++++++++++++++
 tests/steps/utils.py         |   75 ++++++++++++++++
 10 files changed, 1002 insertions(+), 0 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 5607fab..1dc3ca6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -73,4 +73,9 @@ vala-clean:
 win-tools.iso:
        @wget -O $@ 
http://people.gnome.org/~teuf/gnome-boxes/boxes-unattended-win-drivers/boxes-unattended-win-drivers-LATEST.iso
 
+ BEHAVE_INSTALLED_TESTS_RULE@
+INSTALLED_TESTS=                               \
+       general.feature                         \
+       $(NULL)
+INSTALLED_TESTS_TYPE=session-exclusive
 -include $(top_srcdir)/git.mk
diff --git a/configure.ac b/configure.ac
index cd0c487..749dd21 100644
--- a/configure.ac
+++ b/configure.ac
@@ -194,6 +194,9 @@ fi
 
 AC_SUBST(VALA_DEBUG_FLAGS)
 
+dnl Installed tests
+BEHAVE_INSTALLED_TESTS
+
 AC_CONFIG_FILES([
   Makefile
   data/Makefile
@@ -221,4 +224,5 @@ AC_MSG_NOTICE([
         USB redirection support:  $enable_usbredir
         Smartcard support:        $enable_smartcard
         oVirt support:            $have_govirt
+        Installed tests:          $enable_installed_tests
 ])
diff --git a/m4/behave-installed-tests.m4 b/m4/behave-installed-tests.m4
new file mode 100644
index 0000000..3dc3fff
--- /dev/null
+++ b/m4/behave-installed-tests.m4
@@ -0,0 +1,137 @@
+# 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=$(wildcard $(srcdir)/tests/*.feature)
+BEHAVE_STEP_DEFINITION=$(wildcard $(srcdir)/tests/steps/*.py)
+BEHAVE_COMMON_FILES=$(srcdir)/tests/environment.py $(srcdir)/tests/common_steps.py
+
+FINAL_TEST_ENVIRONMENT=
+ifneq ($(INSTALLED_TESTS_ENVIRONMENT),)
+      FINAL_TEST_ENVIRONMENT="env $(INSTALLED_TESTS_ENVIRONMENT)"
+endif
+
+installed-tests-exec-hook:
+       @$(MKDIR_P) $(EXEC_DIRECTORY);
+       @for feature in $(BEHAVE_FEATURES); do                                                                
                  \
+           $(LIBTOOL) --mode=install $(INSTALL) --mode=777 $$feature $(EXEC_DIRECTORY);\
+       done
+       @for common_file in $(BEHAVE_COMMON_FILES); do                                                        
                  \
+           $(LIBTOOL) --mode=install $(INSTALL) --mode=777 $$common_file $(EXEC_DIRECTORY);\
+       done
+       @$(MKDIR_P) $(EXEC_DIRECTORY)/steps;
+       @for step_definition in $(BEHAVE_STEP_DEFINITION); do                                                 
                  \
+           $(LIBTOOL) --mode=install $(INSTALL) --mode=777 $$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 -i $$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/tests/README b/tests/README
new file mode 100644
index 0000000..a47ca42
--- /dev/null
+++ b/tests/README
@@ -0,0 +1,57 @@
+Introduction
+============
+
+Tests for GNOME 3 Boxes application. Tests are written in python using Behave [1]
+and Dogtail [2] frameworks. Tests are divided into several feature files and can
+be executed accordingly.
+
+Structure
+=========
+
+* Feature Files
+  * general.feature
+    * start/stop
+    * help/credits
+    * download iso
+    * customize machine memory
+    * start box
+    * search boxes
+* Environment file
+  * everything for setting/cleaning up the environment
+* Steps
+  * Dogtail implementation of Behave steps
+* README
+
+Requirements
+============
+
+* for downstream package maintainers
+  * behave (python-behave in Fedora)
+  * dogtail
+
+Execution
+=========
+
+WARNING: These tests are designed to run in a prestine test environment and to
+         that end, remove all your VMs.
+
+* in tests directory run:
+  * behave
+  to run full test set
+
+  * behave -t $test -k
+  to run single test
+
+  * behave -i $category.feature
+  to run whole feature file
+
+Authors
+======
+
+* Vladimir Benes <vbenes redhat com>
+
+References
+==========
+
+* [1] http://pythonhosted.org/behave/
+* [2] https://fedorahosted.org/dogtail/
diff --git a/tests/common_steps.py b/tests/common_steps.py
new file mode 100644
index 0000000..156c9f9
--- /dev/null
+++ b/tests/common_steps.py
@@ -0,0 +1,194 @@
+# -*- 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, kill, system
+from signal import signal, alarm, SIGALRM, SIGKILL
+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 dogtail.utils import run
+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='<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
+
+        # 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, 10):
+            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 quit(self):
+        """
+        Quit the app via 'Ctrl+Q'
+        """
+        if self.recordVideo:
+            keyCombo('<Control><Alt><Shift>R')
+
+        try:
+            #os.system("pkill -9 gnome-boxes")
+            keyCombo('<Ctrl><Q>')
+            if self.isRunning():
+                self.kill()
+        except:
+            pass
+
+    def kill(self):
+        """
+        Kill the app via 'killall'
+        """
+        try:
+            os.system("pkill -9 gnome-boxes")
+            #keyCombo('<Alt><F4>')
+        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"
+
+        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"
+
+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 ""
+
+ step(u'Make sure that {app} is running')
+def ensure_app_running(context, app):
+    context.app = context.app_class.startViaCommand()
diff --git a/tests/environment.py b/tests/environment.py
new file mode 100644
index 0000000..e61bab5
--- /dev/null
+++ b/tests/environment.py
@@ -0,0 +1,157 @@
+# -*- coding: UTF-8 -*-
+
+from time import sleep, localtime, strftime
+from dogtail.utils import isA11yEnabled, enableA11y
+if not isA11yEnabled():
+    enableA11y(True)
+from dogtail.rawinput import pressKey
+from subprocess import call
+from common_steps import App, dummy, non_block_read, ensure_app_running
+from dogtail.config import config
+import os
+from urllib2 import urlopen, URLError, HTTPError
+import sys
+
+def downloadfile(url):
+    # Open the url
+    try:
+        f = urlopen(url)
+        print "** Downloading: " + url
+
+        # Open our local file for writing
+        if not os.path.isfile("%s/Downloads/%s" % (os.path.expanduser("~"), os.path.basename(url))):
+            with open("%s/Downloads/%s" % (os.path.expanduser("~"), os.path.basename(url)), "wb") as 
local_file:
+                local_file.write(f.read())
+
+    except HTTPError, e:
+        print "HTTP Error:", e.code, url
+    except URLError, e:
+        print "URL Error:", e.reason, url
+
+def before_all(context):
+    """Setup stuff
+    Being executed once before any test
+    """
+
+    try:
+        if not os.path.isfile('/tmp/boxes_configured'):
+            print "** Turning off gnome idle"
+            if call("gsettings set org.gnome.desktop.session idle-delay 0", shell=True) == 0:
+                print "PASS\n"
+            else:
+                print "FAIL: unable to turn off screensaver. This can cause failures"
+
+            # Download Core-5.3.iso and images for import if not there
+            downloadfile('http://distro.ibiblio.org/tinycorelinux/5.x/x86/archive/5.3/Core-5.3.iso')
+            downloadfile('https://dl.dropboxusercontent.com/u/93657599/vbenes/Core-5.3.vmdk')
+            downloadfile('https://dl.dropboxusercontent.com/u/93657599/vbenes/Core-5.3.qcow2')
+            call('cp ~/Downloads/Core-5.3.iso /tmp', shell=True)
+            call('touch /tmp/boxes_configured', shell=True)
+
+        # Skip dogtail actions to print to stdout
+        config.logDebugToStdOut = False
+        config.typingDelay = 0.1
+        config.childrenLimit = 500
+
+        # Include assertion object
+        context.assertion = dummy()
+
+        # Store scenario start time for session logs
+        context.log_start_time = strftime("%Y-%m-%d %H:%M:%S", localtime())
+
+        context.app_class = App('gnome-boxes')
+
+    except Exception as e:
+        print "Error in before_all: %s" % e.message
+
+def before_scenario(context, scenario):
+    pass
+
+def before_tag(context, tag):
+    if 'help' in tag:
+        os.system('pkill -9 yelp')
+
+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_tag(context, tag):
+    if 'help' in tag:
+        os.system('pkill -9 yelp')
+
+def after_scenario(context, scenario):
+    """Teardown for each scenario
+    Kill gnome-boxes (in order to make this reliable we send sigkill)
+    """
+
+    try:
+
+    # Delete all boxes from GUI
+        if context.app_class.isRunning():
+            new = context.app.findChildren(lambda x: x.name == 'New')[0]
+
+            # Is new visible?
+            if not new.showing:
+            # ave to press back button if visible
+                backs = context.app.findChildren(lambda x: x.name == 'Back' and x.showing)
+                if backs:
+                    backs[0].click()
+
+            # Is new finally visible?
+            new = context.app.findChildren(lambda x: x.name == 'New')[0]
+            if not new.showing:
+            # Have to press vm unnamed back button
+                panel = context.app.child('Boxes').children[0].findChildren(lambda x: x.roleName == 'panel' \
+                                                                                                     and 
x.showing)[0]
+                buttons = panel.findChildren(lambda x: x.roleName == 'push button' and x.showing)
+                if buttons:
+                    buttons[0].click()
+
+            new.grabFocus()
+            pane = context.app.child(roleName='layered pane')
+            if len(pane.children) != 0:
+                for child in pane.children:
+                    child.click(button=3)
+                context.app.findChildren(lambda x: x.name == 'Delete' and x.showing)[0].click()
+                context.app.findChildren(lambda x: x.name == 'Undo' and x.showing)[0].grabFocus()
+                pressKey('Tab')
+                pressKey('Enter')
+                sleep(2)
+
+        # 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.quit()
+
+            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)
+
+    except Exception as e:
+        # Stupid behave simply crashes in case exception has occurred
+        print "Error in after_scenario: %s" % e.message
+
+    # clean all boxes
+    os.system("rm -rf ~/.config/libvirt/storage/*")
+    os.system("rm -rf ~/.cache/gnome-boxes/sources/qemu*")
+    os.system("rm -rf ~/.local/share/gnome-boxes/images/*")
diff --git a/tests/general.feature b/tests/general.feature
new file mode 100644
index 0000000..b56c60b
--- /dev/null
+++ b/tests/general.feature
@@ -0,0 +1,130 @@
+Feature: General
+
+  Background:
+    * Make sure that gnome-boxes is running
+    * Wait until overview is loaded
+
+  @open_help_via_shortcut
+  Scenario: Open help via shortcut
+    * Hit "<F1>"
+    Then Help is shown
+
+  @open_help_via_menu
+  Scenario: Open help from menu
+    * Select "Help" from supermenu
+    Then Help is shown
+
+  @open_about_via_menu
+  Scenario: Open about from menu
+    * Select "About" from supermenu
+    * Press "Credits"
+    * Press "About"
+    Then About is shown
+
+  @quit_via_panel
+  Scenario: Quit Boxes via super menu
+    * Select "Quit" from supermenu
+    Then Boxes are not running
+
+  @quit_via_shortcut
+  Scenario: Quit Boxes via shortcut
+    * Select "Quit" from supermenu
+    Then Boxes are not running
+
+  @no_boxes
+  Scenario: No boxes installed
+    Then No box is visible
+
+  @download_iso_http
+  Scenario: Download iso http
+    * Create new box from url 
"http://ftp.vim.org/os/Linux/distr/tinycorelinux/5.x/x86/archive/5.2/Core-5.2.iso";
+    * Wait for "sleep 10" end
+    * Hit "Enter"
+    * Save IP for machine "Core-5"
+    * Press "back" in vm
+    Then Box "Core-5" "does" exist
+    Then Ping "Core-5"
+
+  @customize_machine_before_installation
+  Scenario: Customize machine before installation
+    * Create new box from menu "Core-5"
+    * Customize mem to 64 MB
+    * Press "Create"
+    * Wait for "sleep 10" end
+    Then "65536 KiB" is visible with command "DOM=$(virsh list |grep boxes |awk {'print $1'}); virsh dominfo 
$DOM"
+
+  @rename_via_button
+  Scenario: Rename via button
+    * Initiate new box "Core-5" installation
+    * Select "Core-5" box
+    * Press "Properties"
+    * Rename "Core-5" to "Kernel-6" via "button"
+    * Press "Back"
+    * Quit Boxes
+    * Start Boxes
+    Then Box "Kernel-6" "does" exist
+
+  @rename_via_label
+  Scenario: Rename via label
+    * Initiate new box "Core-5" installation
+    * Select "Core-5" box
+    * Press "Properties"
+    * Rename "Core-5" to "Kernel-6" via "label"
+    * Press "Back"
+    * Quit Boxes
+    * Start Boxes
+    Then Box "Kernel-6" "does" exist
+
+  @start_box_from_console
+  Scenario: Start box directly from console
+    * Create new box "Core-5"
+    Then Ping "Core-5"
+    * Quit Boxes
+    * Start box name "Core-5"
+    * Type "sudo ifconfig eth0 down"
+    * Wait for "sleep 4" end
+    Then Cannot ping "Core-5"
+
+  @search_via_shortcut
+  Scenario: Search via shotcut
+    * Initiate new box "Core-5" installation
+    * Initiate new box "Core-5" installation
+    * Hit "<Ctrl><f>"
+    * Type "Core-5 2"
+    Then Box "Core-5 2" "does" exist
+    Then Box "Core-5" "does not" exist
+    * Hit "<Ctrl><a>"
+    * Type "Core"
+    Then Box "Core-5 2" "does" exist
+    Then Box "Core-5" "does" exist
+
+  @search_via_button
+  Scenario: Search via button
+    * Initiate new box "Core-5" installation
+    * Initiate new box "Core-5" installation
+    * Press "Search"
+    * Type "Core-5 2"
+    Then Box "Core-5 2" "does" exist
+    Then Box "Core-5" "does not" exist
+    * Hit "<Ctrl><a>"
+    * Type "Core"
+    Then Box "Core-5 2" "does" exist
+    Then Box "Core-5" "does" exist
+
+  @search_escape
+  Scenario: Return from search via Esc
+    * Initiate new box "Core-5" installation
+    * Initiate new box "Core-5" installation
+    * Hit "<Ctrl><f>"
+    * Type "Core-5 2"
+    Then Box "Core-5 2" "does" exist
+    Then Box "Core-5" "does not" exist
+    * Hit "Esc"
+    Then Box "Core-5 2" "does" exist
+    Then Box "Core-5" "does" exist
+
+### TBD ###
+  # local_machine_paused_after_quit
+  # import_from_system_broker
+  # detach_from_system_broker
+  # add_machine_from_system_broker_via_url
diff --git a/tests/steps/creation.py b/tests/steps/creation.py
new file mode 100644
index 0000000..c4db556
--- /dev/null
+++ b/tests/steps/creation.py
@@ -0,0 +1,59 @@
+# -*- coding: UTF-8 -*-
+
+from behave import step
+from dogtail.rawinput import typeText
+from time import sleep
+from utils import get_showing_node_name
+
+ step(u'Create new box "{name}"')
+def create_machine(context, name):
+    """
+    Create new box, wait till it finish and save IP
+    """
+    context.execute_steps(u"""
+        * Create new box from menu "%s"
+        * Press "Create"
+        * Wait for "sleep 3" end
+        * Hit "Enter"
+        * Save IP for machine "%s"
+        * Press "back" in vm
+        """ %(name, name))
+
+ step(u'Create new box from url "{url}"')
+def create_new_vm_via_url(context, url):
+    context.app.child('New').click()
+    context.app.child('Continue').click()
+    context.app.child('Enter URL').click()
+
+    typeText(url)
+    context.app.child('Continue').click()
+
+    if url.find('http') != -1:
+        half_minutes = 0
+        while half_minutes < 40:
+            half_minutes += 1
+            create = context.app.child('Create')
+            if create.sensitive and create.showing:
+                create.click()
+                break
+            else:
+                sleep(30)
+
+ step(u'Create new box from menu "{sys_name}"')
+def create_new_vm_from_menu(context, sys_name):
+    context.app.child('New').click()
+    context.app.child('Continue').click()
+    get_showing_node_name(sys_name, context.app).click()
+
+ step(u'Initiate new box "{name}" installation')
+def create_machine_no_wait(context, name):
+    """
+    Initiate new box installation, no IP saved, no wait for box readines
+    """
+    context.execute_steps(u"""
+        * Create new box from menu "%s"
+        * Press "Create"
+        * Wait for "sleep 3" end
+        * Hit "Enter"
+        * Press "back" in vm
+        """ %(name))
diff --git a/tests/steps/general.py b/tests/steps/general.py
new file mode 100644
index 0000000..8d20ceb
--- /dev/null
+++ b/tests/steps/general.py
@@ -0,0 +1,184 @@
+# -*- coding: UTF-8 -*-
+
+from behave import step
+from dogtail.tree import root
+from dogtail.rawinput import typeText, pressKey, keyCombo
+from time import sleep
+from common_steps import wait_until
+from subprocess import call, check_output, Popen
+
+ step(u'About is shown')
+def about_shown(context):
+    assert context.app.child('About Boxes') != None, "About window cannot be focused"
+
+ step(u'Box "{name}" "{state}" exist')
+def does_box_exists(context, name, state):
+    found = False
+    pane = context.app.child(roleName='layered pane')
+    for child in pane.children:
+        if child.text == name:
+            found = True
+            break
+
+    if state == 'does':
+        assert found == True, "Machine %s was not found in overview" % name
+    if state == 'does not':
+        assert found == False, "Machine %s was found in overview" % name
+
+ step(u'Boxes are not running')
+def boxes_not_running(context):
+    assert context.app_class.isRunning() != True, "Boxes window still visible"
+
+ step(u'Boxes app has "{num}" windows')
+def number_of_windows(context, num):
+    assert len(context.app.children) == int(num), "App has just %s windows not %s" 
%(len(context.app.children), num)
+
+ step(u'Customize mem to 64 MB')
+def customize_vm(context):
+    context.app.child(u'Customize…').click()
+    sleep(0.5)
+    pressKey('Tab')
+    pressKey('Tab')
+    pressKey('Page_Up')
+    pressKey('Page_Up')
+
+    context.app.children[0].children[0].children[3].child('Back').click()
+    sleep(0.5)
+
+ step(u'Delete all boxes')
+def delete_all(context):
+    context.app.findChildren(lambda x: x.name == 'New')[0].grabFocus()
+    pane = context.app.child(roleName='layered pane')
+    if len(pane.children) != 0:
+        for child in pane.children:
+            child.click(button=3)
+        context.app.child('Delete').click()
+        context.app.findChildren(lambda x: x.name == 'Undo' and x.showing)[0].grabFocus()
+        pressKey('Tab')
+        pressKey('Enter')
+        sleep(4)
+
+ step(u'Go into "{vm}" box')
+def go_into_vm(context, vm):
+    pane = context.app.child(roleName='layered pane')
+    for child in pane.children:
+        if child.text == vm:
+            child.click()
+            sleep(0.5)
+            break
+
+ step(u'Help is shown')
+def help_shown(context):
+    sleep(1)
+    yelp = root.application('yelp')
+    assert yelp.child('Boxes') != None, "Yelp wasn't opened"
+
+ step(u'No box is visible')
+def no_box_sign(context):
+    assert context.app.child('No boxes found') != None
+
+ step(u'Press "{action}" in vm')
+def press_back_in_vm(context, action):
+    panel = context.app.child('Boxes').children[0].findChildren(lambda x: x.roleName == 'panel' and 
x.showing)[0]
+    buttons = panel.findChildren(lambda x: x.roleName == 'push button' and x.showing)
+    if action == 'back':
+        buttons[0].click()
+    if action == 'prefs':
+        buttons[1].click()
+    sleep(0.5)
+
+ step(u'Press "{action}" in alert')
+def press_back_in_prefs(context, action):
+    button = context.app.child(roleName='alert').child(action)
+    button.click()
+    sleep(0.5)
+
+ step(u'Quit Boxes')
+def quit_boxes(context):
+    keyCombo('<Ctrl><Q>')
+    sleep(5)
+
+ step(u'Rename "{machine}" to "{name}" via "{way}"')
+def rename_vm(context, machine, name, way):
+    if way == 'button':
+        context.app.child(machine, roleName='push button').click()
+        sleep(0.5)
+    if way == 'label':
+        context.app.child('Name').parent.children[-2].child(roleName='push button').click()
+    typeText(name)
+    pressKey('Enter')
+    sleep(0.5)
+
+ step(u'Save IP for machine "{vm}"')
+def save_ip_for_vm(context, vm):
+    if not hasattr(context, 'ips'):
+        context.ips = {}
+
+    ip_cmd = "head -n 1 /var/lib/libvirt/dnsmasq/default.leases | awk {'print $3'}"
+
+    wait = 0
+    while True:
+        ip = check_output(ip_cmd, shell=True).strip()
+        cmd = "ping -q -c 1 %s > /dev/null 2>&1" % ip
+        ret = call(cmd, shell=True)
+
+        if ip in context.ips.values() or ret != 0:
+            wait += 1
+            sleep(1)
+            if wait == 80:
+                print check_output('cat /var/lib/libvirt/dnsmasq/default.leases', shell=True)
+                print context.ips.values()
+                print check_output('date', shell=True)
+                print check_output('ip a s', shell=True)
+                raise Exception("no new address cannot be found for machine %s" %vm)
+        else:
+            break
+
+    count = 1
+    for key in context.ips.keys():
+        if key.find(vm) != -1:
+            count += 1
+    if count != 1:
+        vm = vm + " %s" %count
+
+    context.ips[vm] = ip
+
+ step(u'Select "{vm}" box')
+def select_vm(context, vm):
+    pane = context.app.child(roleName='layered pane')
+    for child in pane.children:
+        if child.text == vm:
+            child.click(button='3')
+            sleep(0.2)
+            break
+
+ step(u'Select "{action}" from supermenu')
+def select_menu_action(context, action):
+    keyCombo("<Super_L><F10>")
+    if action == 'About':
+        pressKey('Down')
+    if action == 'Quit':
+        pressKey('Down')
+        pressKey('Down')
+    pressKey('Enter')
+
+ step(u'Start Boxes')
+def start_boxes(context):
+    cmd = 'gnome-boxes'
+    Popen(cmd, shell=True)
+    sleep(1)
+    context.app = root.application('gnome-boxes')
+
+ step(u'Start box name "{box}"')
+def start_boxes_via_vm(context, box):
+    cmd = 'gnome-boxes %s' %box
+    Popen(cmd, shell=True)
+    sleep(5)
+    context.app = root.application('gnome-boxes')
+
+ step(u'Wait until overview is loaded')
+def initial_page_loaded(context):
+    wait_until(lambda x: x.name != 'New', context.app)
+    context.execute_steps(u"""
+        * Delete all boxes
+        """)
diff --git a/tests/steps/utils.py b/tests/steps/utils.py
new file mode 100644
index 0000000..4c19942
--- /dev/null
+++ b/tests/steps/utils.py
@@ -0,0 +1,75 @@
+# -*- coding: UTF-8 -*-
+
+from behave import step
+from dogtail.rawinput import typeText, pressKey, keyCombo
+from time import sleep
+from subprocess import call, check_output, CalledProcessError, STDOUT
+
+def get_showing_node_name(name, parent, timeout=30, step=0.25):
+    sleep = 0
+    while len(parent.findChildren(lambda x: x.name == name and x.showing and x.sensitive)) == 0:
+        sleep(step)
+        sleep += step
+        if sleep == timeout:
+            raise Exception("Timeout: Node %s wasn't found showing" %name)
+
+    return parent.findChildren(lambda x: x.name == name and x.showing and x.sensitive)[0]
+
+def get_showing_node_rolename(rolename, parent, timeout=30, step=0.25):
+    sleep = 0
+    while len(parent.findChildren(lambda x: x.roleName == rolename and x.showing and x.sensitive)) == 0:
+        sleep(step)
+        sleep += 1
+        if sleep == timeout:
+            raise Exception("Timeout: Node %s wasn't found showing" %rolename)
+
+    return parent.findChildren(lambda x: x.roleName == rolename and x.showing and x.sensitive)[0]
+
+ step(u'Cannot ping "{vm}"')
+def cannot_ping_vm(context, vm):
+    cmd = "ping -qn -c 1 %s" %context.ips[vm]
+    assert call(cmd, shell=True) != 0, "Machine %s is pingable!" %vm
+
+ step(u'Hit "{keycombo}"')
+def hit_keycombo(context, keycombo):
+    sleep(0.2)
+    if keycombo == "Enter":
+        pressKey("Enter")
+    else:
+        keyCombo('%s'%keycombo)
+
+    sleep(0.2)
+
+ step(u'"{pattern}" is visible with command "{command}"')
+def check_pattern_visible(context, pattern, command):
+    sleep(0.2) # time for all to get set
+    try:
+        out = check_output(command, stderr=STDOUT, shell=True)
+    except CalledProcessError as e:
+        out = e.output
+    assert out.find(pattern) != -1, 'pattern %s is not visible with %s' % (pattern, command)
+
+ step(u'"{pattern}" is not visible with command "{command}"')
+def check_pattern_not_visible(context, pattern, command):
+    sleep(0.2) # time for all to get set
+    out = check_output(command, shell=True)
+    assert out.find(pattern) == -1, 'pattern %s is visible with %s' % (pattern, command)
+
+ step(u'Ping "{vm}"')
+def ping_vm(context, vm):
+    cmd = "ping -qn -c 2 %s > /dev/null 2>&1" %context.ips[vm]
+    assert call(cmd, shell=True) == 0, "Machine %s is not pingable" %vm
+
+ step(u'Press "{button}"')
+def press_button(context, button):
+    get_showing_node_name(button, context.app).click()
+    sleep(0.5)
+
+ step(u'Type "{text}"')
+def type_text(context, text):
+    typeText(text)
+    pressKey('Enter')
+
+ step(u'Wait for "{cmd}" end')
+def wait_for_cmd(context, cmd):
+    call(cmd, shell=True)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]