[gnome-calculator] Add initial installed tests



commit 3591d986db708aa023b3ca3fb223a8f21d9d29e5
Author: Vadim Rutkovsky <vrutkovs redhat com>
Date:   Sat Mar 15 00:43:18 2014 +0100

    Add initial installed tests

 tests/advanced_mode.feature    |   21 +++++
 tests/basic_mode.feature       |   84 ++++++++++++++++++++
 tests/common_steps.py          |  168 ++++++++++++++++++++++++++++++++++++++++
 tests/environment.py           |   40 ++++++++++
 tests/financial_mode.feature   |   21 +++++
 tests/programming_mode.feature |   21 +++++
 tests/steps/steps.py           |   51 ++++++++++++
 7 files changed, 406 insertions(+), 0 deletions(-)
---
diff --git a/tests/advanced_mode.feature b/tests/advanced_mode.feature
new file mode 100644
index 0000000..e60ba5b
--- /dev/null
+++ b/tests/advanced_mode.feature
@@ -0,0 +1,21 @@
+ advanced_mode
+Feature: Advanced mode
+
+  Background:
+    * Make sure that Calculator is running
+    * Switch to Advanced mode
+
+  Scenario Outline: Simple calculations in advanced mode
+    * Calculate "<expression>"
+    Then result is "<result>"
+
+  Examples:
+    | expression            | result     |
+    | 123456789 + 987654321 | 1111111110 |
+    | 987654321 + 0         | 987654321  |
+    | 987654321 - 987654322 | -1         |
+    | -98765-0              | -98765     |
+    | 3/6                   | 0.5        |
+    | -8/2                  | -4         |
+    | 10 / 3 * 3            | 10         |
+    | 6 / (3 * 2)           | 1          |
diff --git a/tests/basic_mode.feature b/tests/basic_mode.feature
new file mode 100644
index 0000000..3f8095d
--- /dev/null
+++ b/tests/basic_mode.feature
@@ -0,0 +1,84 @@
+ basic_mode
+Feature: Basic mode
+
+  Background:
+    * Make sure that Calculator is running
+    * Switch to Basic mode
+
+  @basic_add
+  Scenario Outline: Add
+    * Calculate "<expression>"
+    Then result is "<result>"
+
+    Examples:
+      | expression            | result     |
+      | 123456789 + 987654321 | 1111111110 |
+      | 987654321 + 0         | 987654321  |
+      | -987 + (-14)          | -1001      |
+      | -0 + 0                | 0          |
+
+  @basic_add @basic_add_overflow
+  Scenario: Add - overflow
+    * Calculate "987654321^789456 +123456789"
+    Then "Overflow: the result couldn't be calculated" error is displayed
+
+  @basic_subtract
+  Scenario Outline: Subtract
+    * Calculate "<expression>"
+    Then result is "<result>"
+
+    Examples:
+      | expression            | result |
+      | 987654321 - 987654322 | -1     |
+      | -98765-0              | -98765 |
+      | 0-0                   | 0      |
+      | -6-(-5)               | -1     |
+
+  @basic_subtract @basic_subtract_overflow
+  Scenario: Subtract overflow
+    * Calculate "-987654321^98-987654321"
+    Then "Overflow: the result couldn't be calculated" error is displayed
+
+  @basic_multiply
+  Scenario: Multiply
+    * Calculate "10 / 3 * 3"
+    Then result is "10"
+
+  @basic_divide
+  Scenario Outline: Divide
+    * Calculate "<expression>"
+    Then result is "<result>"
+
+    Examples:
+      | expression     | result      |
+      | 3/6            | 0.5         |
+      | 20/8           | 2.5         |
+      | 1000000/100000 | 10          |
+      | -8/2           | -4          |
+      | -9/-7          | 1.285714286 |
+      | 3.256/0.36     | 9.044444444 |
+      | -1/1           | -1          |
+      | 0/987          | 0           |
+      | 10/3*3         | 10          |
+      | 15/99          | 0.151515152 |
+
+  @basic_divide @basic_divide_by_zero
+  Scenario: Division by zero
+    * Calculate "5/0"
+    Then "Division by zero is undefined" error is displayed
+
+  @basic_divide @basic_divide_by_zero_with_decimal_point
+  Scenario: Division by zero with decimal point
+    * Calculate "0.0/0.0"
+    Then "Division by zero is undefined" error is displayed
+
+  @basic_brackets
+  Scenario Outline: Brackets
+    * Calculate "<expression>"
+    Then result is "<result>"
+
+    Examples:
+      | expression                | result |
+      | 6 / (3 * 2)               | 1      |
+      | 0 * ( 1 + 2 + 3 + 4)      | 0      |
+      | (((2 + 8) * (5 / 2)) - 3) | 22     |
diff --git a/tests/common_steps.py b/tests/common_steps.py
new file mode 100644
index 0000000..2b84504
--- /dev/null
+++ b/tests/common_steps.py
@@ -0,0 +1,168 @@
+# -*- 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
+from signal import signal, alarm, SIGALRM, SIGKILL
+from subprocess import Popen
+from behave import step
+from gi.repository import GLib
+
+from dogtail.rawinput import keyCombo, absoluteMotion, pressKey
+from dogtail.tree import root
+from dogtail.utils import run
+
+
+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.instance)
+       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):
+            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('<Control><Alt><Shift>R')
+
+        try:
+            kill(self.pid, SIGKILL)
+        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=1)
+
+        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'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..f48d38b
--- /dev/null
+++ b/tests/environment.py
@@ -0,0 +1,40 @@
+# -*- coding: UTF-8 -*-
+
+from time import sleep
+from dogtail.utils import isA11yEnabled, enableA11y
+if not isA11yEnabled():
+    enableA11y(True)
+
+from common_steps import App
+from dogtail.config import config
+
+
+def before_all(context):
+    """Setup gnome-calculator stuff
+    Being executed before all features
+    """
+
+    try:
+        # Skip dogtail actions to print to stdout
+        config.logDebugToStdOut = False
+        config.typingDelay = 0.2
+
+        context.app_class = App('gnome-calculator')
+
+    except Exception as e:
+        print("Error in before_all: %s" % e.message)
+
+
+def after_scenario(context, scenario):
+    """Teardown for each scenario
+    Kill gnome-calculator (in order to make this reliable we send sigkill)
+    """
+    try:
+        # Stop gnome-calculator
+        context.app_class.kill()
+
+        # 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 --git a/tests/financial_mode.feature b/tests/financial_mode.feature
new file mode 100644
index 0000000..4c0ae82
--- /dev/null
+++ b/tests/financial_mode.feature
@@ -0,0 +1,21 @@
+ financial_mode
+Feature: Financial mode
+
+  Background:
+    * Make sure that Calculator is running
+    * Switch to Financial mode
+
+  Scenario Outline: Simple calculations in financial mode
+    * Calculate "<expression>"
+    Then result is "<result>"
+
+  Examples:
+    | expression            | result     |
+    | 123456789 + 987654321 | 1111111110 |
+    | 987654321 + 0         | 987654321  |
+    | 987654321 - 987654322 | -1         |
+    | -98765-0              | -98765     |
+    | 3/6                   | 0.5        |
+    | -8/2                  | -4         |
+    | 10 / 3 * 3            | 10         |
+    | 6 / (3 * 2)           | 1          |
diff --git a/tests/programming_mode.feature b/tests/programming_mode.feature
new file mode 100644
index 0000000..5a6ccf7
--- /dev/null
+++ b/tests/programming_mode.feature
@@ -0,0 +1,21 @@
+ programming_mode
+Feature: Programming mode
+
+  Background:
+    * Make sure that Calculator is running
+    * Switch to Programming mode
+
+  Scenario Outline: Simple calculations in programming mode
+    * Calculate "<expression>"
+    Then result is "<result>"
+
+  Examples:
+    | expression            | result     |
+    | 123456789 + 987654321 | 1111111110 |
+    | 987654321 + 0         | 987654321  |
+    | 987654321 - 987654322 | -1         |
+    | -98765-0              | -98765     |
+    | 3/6                   | 0.5        |
+    | -8/2                  | -4         |
+    | 10 / 3 * 3            | 10         |
+    | 6 / (3 * 2)           | 1          |
diff --git a/tests/steps/steps.py b/tests/steps/steps.py
new file mode 100644
index 0000000..065205b
--- /dev/null
+++ b/tests/steps/steps.py
@@ -0,0 +1,51 @@
+# -*- coding: UTF-8 -*-
+from behave import step, then
+
+from dogtail.tree import root
+from dogtail.rawinput import typeText
+from common_steps import limit_execution_time_to, wait_until
+from time import sleep
+
+
+ step(u'Help section "{name}" is displayed')
+def help_is_displayed(context, name):
+    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)
+
+
+ step(u'Switch to {mode:w} mode')
+def switch_to_basic_mode(context, mode):
+    context.app.child(roleName='toggle button', name='Menu').click()
+    context.app.child(roleName='radio menu item', name='%s Mode' % mode).click()
+
+
+ limit_execution_time_to(seconds=30)
+def wait_until_spinner_showing(context):
+    # Wait until spinner disappears
+    sleep(0.1)
+    spinner = context.app.child('Spinner')
+    while spinner.showing:
+        sleep(0.1)
+
+
+ step(u'Calculate "{expression}"')
+def calculate(context, expression):
+    typeText(expression)
+    context.app.child('result').click()
+    wait_until_spinner_showing(context)
+
+
+ step(u'result is "{result}"')
+def verify_result(context, result):
+    # Replace unicode negative sign to simple '-'
+    actual = context.app.child(roleName='editbar').text.replace('\xe2\x88\x92', '-')
+    assert result == actual, "Incorrect result, expected '%s' but was '%s'" % (result, actual)
+
+
+ then(u'"{message}" error is displayed')
+def error_message_displayed(context, message):
+    actual = context.app.child(roleName='editbar').parent.textentry('').text
+    assert message == actual, "Incorrect error, expected '%s' but was '%s'" % (message, actual)


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