← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~mzibricky/openlp/openlyrics_tests into lp:openlp

 

matysek has proposed merging lp:~mzibricky/openlp/openlyrics_tests into lp:openlp.

Requested reviews:
  Andreas Preikschat (googol)
  Tim Bentley (trb143)

For more details, see:
https://code.launchpad.net/~mzibricky/openlp/openlyrics_tests/+merge/78464

These are tests for openlyrics stuff.

Added docstrings and reformated some comments.

added more comments and fixed some code formatting issues as requested

resolved conflicts and eliminated one letter variable
-- 
https://code.launchpad.net/~mzibricky/openlp/openlyrics_tests/+merge/78464
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/core/__init__.py'
--- openlp/core/__init__.py	2011-09-18 22:45:19 +0000
+++ openlp/core/__init__.py	2011-10-06 17:09:52 +0000
@@ -228,25 +228,29 @@
         help='Set the Qt4 style (passed directly to Qt4).')
     parser.add_option('--testing', dest='testing',
         action='store_true', help='Run by testing framework')
-    # Set up logging
-    log_path = AppLocation.get_directory(AppLocation.CacheDir)
-    check_directory_exists(log_path)
-    filename = os.path.join(log_path, u'openlp.log')
-    logfile = logging.FileHandler(filename, u'w')
-    logfile.setFormatter(logging.Formatter(
-        u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
-    log.addHandler(logfile)
     # Parse command line options and deal with them.
     # Use args supplied programatically if possible.
     (options, args) = parser.parse_args(args) if args else parser.parse_args()
+    # Set up logging. In test mode it is skipped and logging messages
+    # Are sent only to console.
+    if not options.testing:
+        log_path = AppLocation.get_directory(AppLocation.CacheDir)
+        check_directory_exists(log_path)
+        filename = os.path.join(log_path, u'openlp.log')
+        logfile = logging.FileHandler(filename, u'w')
+        logfile.setFormatter(logging.Formatter(
+            u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
+        log.addHandler(logfile)
+        logging.addLevelName(15, u'Timer')
+        if options.loglevel.lower() in ['d', 'debug']:
+            log.setLevel(logging.DEBUG)
+            print 'Logging to:', filename
+        elif options.loglevel.lower() in ['w', 'warning']:
+            log.setLevel(logging.WARNING)
+        else:
+            log.setLevel(logging.INFO)
+    # Deal with other command line options.
     qt_args = []
-    if options.loglevel.lower() in ['d', 'debug']:
-        log.setLevel(logging.DEBUG)
-        print 'Logging to:', filename
-    elif options.loglevel.lower() in ['w', 'warning']:
-        log.setLevel(logging.WARNING)
-    else:
-        log.setLevel(logging.INFO)
     if options.style:
         qt_args.extend(['-style', options.style])
     # Throw the rest of the arguments at Qt, just in case.

=== removed directory 'testing'
=== removed file 'testing/conftest.py'
--- testing/conftest.py	2011-10-04 16:02:45 +0000
+++ testing/conftest.py	1970-01-01 00:00:00 +0000
@@ -1,45 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2011 Raoul Snyman                                        #
-# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan      #
-# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan,      #
-# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias     #
-# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,    #
-# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund             #
-# --------------------------------------------------------------------------- #
-# This program is free software; you can redistribute it and/or modify it     #
-# under the terms of the GNU General Public License as published by the Free  #
-# Software Foundation; version 2 of the License.                              #
-#                                                                             #
-# This program is distributed in the hope that it will be useful, but WITHOUT #
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
-# more details.                                                               #
-#                                                                             #
-# You should have received a copy of the GNU General Public License along     #
-# with this program; if not, write to the Free Software Foundation, Inc., 59  #
-# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
-###############################################################################
-
-"""
-Configuration file for pytest framework.
-"""
-
-from openlp.core import main as openlp_main
-
-
-# Test function argument to make openlp gui instance persistent for all tests.
-# All test cases have to access the same instance. To allow create multiple
-# instances it would be necessary use diffrent configuraion and data files.
-# Created instance will use your OpenLP settings.
-def pytest_funcarg__openlpapp(request):
-    def setup():
-        return openlp_main(['--testing'])
-    def teardown(app):
-        pass
-    return request.cached_setup(setup=setup, teardown=teardown, scope='session')

=== removed file 'testing/run.py'
--- testing/run.py	2011-09-02 11:15:41 +0000
+++ testing/run.py	1970-01-01 00:00:00 +0000
@@ -1,59 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2011 Raoul Snyman                                        #
-# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan      #
-# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan,      #
-# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias     #
-# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,    #
-# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund             #
-# --------------------------------------------------------------------------- #
-# This program is free software; you can redistribute it and/or modify it     #
-# under the terms of the GNU General Public License as published by the Free  #
-# Software Foundation; version 2 of the License.                              #
-#                                                                             #
-# This program is distributed in the hope that it will be useful, but WITHOUT #
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
-# more details.                                                               #
-#                                                                             #
-# You should have received a copy of the GNU General Public License along     #
-# with this program; if not, write to the Free Software Foundation, Inc., 59  #
-# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
-###############################################################################
-
-"""
-This script is used to run set of automated tests of OpenLP. To start tests,
-simply run this script::
-
-    @:~$ ./run.py
-
-"""
-
-import os.path
-import sys
-
-TESTS_PATH = os.path.dirname(os.path.abspath(__file__))
-SRC_PATH = os.path.join(TESTS_PATH, '..')
-
-PYTEST_OPTIONS = [TESTS_PATH]
-
-# Extend python PATH with openlp source
-sys.path.insert(0, SRC_PATH)
-
-# Python testing framework
-# http://pytest.org
-import pytest
-
-
-def main():
-    print 'pytest options:', PYTEST_OPTIONS
-    pytest.main(PYTEST_OPTIONS)
-
-
-if __name__ == u'__main__':
-    main()

=== removed file 'testing/test_app.py'
--- testing/test_app.py	2011-10-04 16:02:45 +0000
+++ testing/test_app.py	1970-01-01 00:00:00 +0000
@@ -1,36 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2011 Raoul Snyman                                        #
-# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan      #
-# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan,      #
-# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias     #
-# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,    #
-# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund             #
-# --------------------------------------------------------------------------- #
-# This program is free software; you can redistribute it and/or modify it     #
-# under the terms of the GNU General Public License as published by the Free  #
-# Software Foundation; version 2 of the License.                              #
-#                                                                             #
-# This program is distributed in the hope that it will be useful, but WITHOUT #
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
-# more details.                                                               #
-#                                                                             #
-# You should have received a copy of the GNU General Public License along     #
-# with this program; if not, write to the Free Software Foundation, Inc., 59  #
-# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
-###############################################################################
-
-from openlp.core import OpenLP
-from openlp.core.ui.mainwindow import MainWindow
-
-
-def test_start_app(openlpapp):
-    assert type(openlpapp) == OpenLP
-    assert type(openlpapp.mainWindow) == MainWindow
-    assert unicode(openlpapp.mainWindow.windowTitle()) == u'OpenLP 2.0'

=== added directory 'tests'
=== added file 'tests/conftest.py'
--- tests/conftest.py	1970-01-01 00:00:00 +0000
+++ tests/conftest.py	2011-10-06 17:09:52 +0000
@@ -0,0 +1,185 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2011 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan      #
+# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan,      #
+# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias     #
+# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,    #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund             #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+
+"""
+Configuration file for pytest framework.
+"""
+
+import os
+import sys
+import subprocess
+import logging
+
+import py.path
+from PyQt4 import QtCore
+from sqlalchemy.orm import clear_mappers
+
+from openlp.core import main as openlp_main
+from openlp.core.utils import AppLocation
+from openlp.core.lib.db import Manager
+from openlp.plugins.songs.lib.db import init_schema
+
+TESTS_PATH = os.path.dirname(os.path.abspath(__file__))
+
+RESOURCES_PATH = os.path.join(TESTS_PATH, 'resources')
+SONGS_PATH = os.path.join(RESOURCES_PATH, 'songs')
+
+# Use development version string.
+sys.argv.append(u'--dev-version')
+
+
+class OpenLPRunner(object):
+    """
+    Class to setup and teardown settings for running openlp tests.
+    """
+    def __init__(self, tmpdir):
+        self.tmpdir = tmpdir
+        self._setup_qapp()
+        self._setup_logging()
+        self._cleanup_qsettings()
+        # Override data dir for OpenLP - it points to tmpdir of a test case.
+        AppLocation.BaseDir = tmpdir.strpath
+
+    def _setup_qapp(self):
+        """
+        Set up basic properties for Qt framework.
+        """
+        QtCore.QCoreApplication.setOrganizationName(u'OpenLP')
+        QtCore.QCoreApplication.setOrganizationDomain(u'openlp.org')
+        QtCore.QCoreApplication.setApplicationName(u'TestOpenLP')
+
+    def _setup_logging(self):
+        """
+        Set up logging for tests to stderr/stdout (console).
+        """
+        _handler = logging.StreamHandler(stream=None)
+        _handler.setFormatter(logging.Formatter(
+            u'%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
+        logging.addLevelName(15, u'Timer')
+        log = logging.getLogger()
+        log.addHandler(_handler)
+        log.setLevel(logging.DEBUG)
+
+    def _cleanup_qsettings(self):
+        """
+        Clean up QSettings for all plugins.
+
+        The issue with QSettings is that it is global for a running process
+        and thus it is necessary to clean it before another test case.
+        If it would not be cleaned up it might be saved in the system.
+        """
+        settings = QtCore.QSettings()
+        keys = settings.allKeys()
+        for key in keys:
+            settings.setValue(key, None)
+
+    def get_songs_db(self, empty=False):
+        """
+        Return initialized database Manager with empty song database or
+        containing some example songs.
+        """
+        if not empty:
+            # copy test data to tmpdir
+            datadir = self.tmpdir.mkdir(u'data').mkdir(u'songs')
+            orig_db = py.path.local(SONGS_PATH).join('songs.sqlite')
+            orig_db.copy(datadir)
+        manager = Manager('songs', init_schema)
+        return manager
+
+    def get_app(self):
+        """
+        Return QGui.QApplication of OpenLP. This allows running different
+        gui tests and allows access to gui objects (e.g MainWindow etc.).
+
+        To allow creating multiple instances of OpenLP in one process
+        it would be necessary use diffrent configuration and data files.
+        Created instance will use your OpenLP settings.
+        """
+        return openlp_main(['--testing'])
+
+    def teardown(self):
+        """
+        Clean up after running a test case.
+        """
+        self._cleanup_qsettings()
+        # Sqlalchemy allows to map classess to only one database at a time.
+        clear_mappers()
+        # Set data dir to original value.
+        AppLocation.BaseDir = None
+
+
+def pytest_funcarg__pth(request):
+    """
+    Test function argument with paths to resources for tests.
+    """
+    def setup():
+        class Pth(object):
+            def __init__(self):
+                self.tests = py.path.local(TESTS_PATH)
+                self.resources = py.path.local(RESOURCES_PATH)
+                self.songs = py.path.local(SONGS_PATH)
+        return Pth()
+    return request.cached_setup(setup=setup, scope='session')
+
+
+def pytest_funcarg__openlp_runner(request):
+    """
+    Test function argument giving access to OpenLP runner.
+    """
+    def setup():
+        return OpenLPRunner(request.getfuncargvalue('tmpdir'))
+
+    def teardown(openlp_runner):
+        openlp_runner.teardown()
+    return request.cached_setup(setup=setup, teardown=teardown, scope='function')
+
+
+class OpenLyricsValidator(object):
+    """
+    Validate xml if it conformns to OpenLyrics xml schema.
+    """
+    def __init__(self, script, schema):
+            self.cmd = [sys.executable, script, schema]
+
+    def validate(self, file_path):
+        self.cmd.append(file_path)
+        print self.cmd
+        retcode = subprocess.call(self.cmd)
+        return retcode == 0
+
+
+def pytest_funcarg__openlyrics_validator(request):
+    """
+    Test function argument giving access to song database.
+    """
+    def setup():
+        script = os.path.join(RESOURCES_PATH, 'openlyrics', 'validate.py')
+        schema = os.path.join(RESOURCES_PATH, 'openlyrics',
+            'openlyrics_schema.rng')
+        return OpenLyricsValidator(script, schema)
+    return request.cached_setup(setup=setup, scope='session')

=== added directory 'tests/resources'
=== added directory 'tests/resources/openlyrics'
=== added file 'tests/resources/openlyrics/openlyrics_schema.rng'
--- tests/resources/openlyrics/openlyrics_schema.rng	1970-01-01 00:00:00 +0000
+++ tests/resources/openlyrics/openlyrics_schema.rng	2011-10-06 17:09:52 +0000
@@ -0,0 +1,487 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grammar xmlns="http://relaxng.org/ns/structure/1.0";
+         datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes";
+         ns="http://openlyrics.info/namespace/2009/song";>
+
+  <!-- TOP LEVEL -->
+
+  <start>
+    <element name="song">
+      <ref name="songAttributes"/>
+      <ref name="properties"/>
+      <optional>
+        <ref name="format"/>
+      </optional>
+      <ref name="lyrics"/>
+    </element>
+  </start>
+
+  <define name="properties">
+    <element name="properties">
+      <interleave> <!-- allow occur in any order -->
+        <!-- at least one title is always required -->
+        <ref name="titles"/>
+        <!-- other properties items are optional -->
+        <optional>
+          <ref name="authors"/>
+        </optional>
+        <optional>
+          <ref name="copyright"/>
+        </optional>
+        <optional>
+          <ref name="ccliNo"/>
+        </optional>
+        <optional>
+          <ref name="releaseDate"/>
+        </optional>
+        <!-- Music Info -->
+        <optional>
+          <ref name="transposition"/>
+        </optional>
+        <optional>
+          <ref name="tempo"/>
+        </optional>
+        <optional>
+          <ref name="key"/>
+        </optional>
+        <!-- Other Info -->
+        <optional>
+          <ref name="variant"/>
+        </optional>
+        <optional>
+          <ref name="publisher"/>
+        </optional>
+        <optional>
+          <ref name="customVersion"/>
+        </optional>
+        <optional>
+          <ref name="keywords"/>
+        </optional>
+        <optional>
+          <ref name="verseOrder"/>
+        </optional>
+        <optional>
+          <ref name="songbooks"/>
+        </optional>
+        <optional>
+          <ref name="themes"/>
+        </optional>
+        <optional>
+          <ref name="comments"/>
+        </optional>
+      </interleave>
+    </element>
+  </define>
+
+  <define name="format">
+    <element name="format">
+        <ref name="formatTags"/>
+    </element>
+  </define>
+
+
+  <define name="lyrics">
+    <element name="lyrics">
+      <!-- at least one verse is required -->
+      <oneOrMore>
+        <ref name="verse"/>
+      </oneOrMore>
+    </element>
+  </define>
+
+  <!-- PROPERTIES -->
+
+  <define name="titles">
+    <element name="titles">
+      <oneOrMore>
+        <element name="title">
+          <ref name="nonEmptyContent"/>
+          <optional>
+            <ref name="langAttribute"/>
+            <optional>
+              <ref name="translitAttribute"/>
+            </optional>
+          </optional>
+          <optional>
+            <attribute name="original">
+              <data type="boolean"/>
+            </attribute>
+          </optional>
+        </element>
+      </oneOrMore>
+    </element>
+  </define>
+
+  <!-- AUTHOR info -->
+
+  <define name="authors">
+    <element name="authors">
+      <oneOrMore>
+        <element name="author">
+          <ref name="nonEmptyContent"/>
+          <optional>
+            <choice>
+              <attribute name="type">
+                <choice>
+                  <value>words</value>
+                  <value>music</value>
+                </choice>
+              </attribute>
+              <!-- when attrib 'type' value is 'translation' require attribute 'lang'.
+                   'xml:lang' can't be used. xml:lang means in what language is the
+                   content of an element and this is not the case. -->
+              <group>
+                <attribute name="type">
+                  <value>translation</value>
+                </attribute>
+                <ref name="langAttribute"/>
+              </group>
+            </choice>
+          </optional>
+        </element>
+      </oneOrMore>
+    </element>
+  </define>
+
+  <define name="copyright">
+    <element name="copyright">
+      <ref name="nonEmptyContent"/>
+    </element>
+  </define>
+
+  <define name="ccliNo">
+    <element name="ccliNo">
+      <data type="positiveInteger"/>
+    </element>
+  </define>
+
+  <define name="releaseDate">
+    <element name="releaseDate">
+      <!-- allowed values
+           1779
+           1779-12
+           1779-12-31
+           1779-12-31T13:15:30+01:00 -->
+      <choice>
+        <data type="gYear"/>
+        <data type="gYearMonth"/>
+        <data type="date"/>
+        <data type="dateTime"/>
+      </choice>
+    </element>
+  </define>
+
+  <!-- MUSIC INFO -->
+
+  <define name="transposition">
+    <element name="transposition">
+      <data type="integer">
+        <param name="minInclusive">-99</param>
+        <param name="maxInclusive">99</param>
+      </data>
+    </element>
+  </define>
+
+  <define name="tempo">
+    <element name="tempo">
+      <choice>
+        <!-- attrib 'type' value 'bpm' - beatss per minute required -->
+        <group>
+          <data type="positiveInteger">
+            <param name="minInclusive">30</param>
+            <param name="maxInclusive">250</param>
+          </data>
+          <attribute name="type">
+            <value>bpm</value>
+          </attribute>
+        </group>
+        <!-- attrib 'type' value 'text' - any text -->
+        <group>
+          <ref name="nonEmptyContent"/>
+          <attribute name="type">
+            <value>text</value>
+          </attribute>
+        </group>
+      </choice>
+    </element>
+  </define>
+ 
+
+  <define name="key">
+    <element name="key">
+      <ref name="nonEmptyContent"/>
+    </element>
+  </define>
+ 
+  <!-- OTHER INFO --> 
+
+  <define name="variant">
+    <element name="variant">
+      <ref name="nonEmptyContent"/>
+    </element>
+  </define>
+
+  <define name="publisher">
+    <element name="publisher">
+      <ref name="nonEmptyContent"/>
+    </element>
+  </define>
+
+  <define name="customVersion">
+    <element name="customVersion">
+      <ref name="nonEmptyContent"/>
+    </element>
+  </define>
+
+  <define name="keywords">
+    <element name="keywords">
+      <ref name="nonEmptyContent"/>
+    </element>
+  </define>
+
+  <define name="verseOrder">
+    <element name="verseOrder">
+      <list>
+        <oneOrMore>
+          <ref name="verseNameType"/>
+        </oneOrMore>
+      </list>
+    </element>
+  </define>
+
+  <define name="songbooks">
+    <element name="songbooks">
+      <oneOrMore>
+        <element name="songbook">
+          <attribute name="name">
+            <ref name="nonEmptyContent"/>
+          </attribute>
+          <optional>
+            <!-- 'entry' is like song number but song number must not
+                 always be integer and it can contain letters.
+                 examples: '153c' or '023', etc. -->
+            <attribute name="entry">
+              <ref name="nonEmptyContent"/>
+            </attribute>
+          </optional>
+        </element>
+      </oneOrMore>
+    </element>
+  </define>
+
+  <define name="themes">
+    <element name="themes">
+      <oneOrMore>
+        <element name="theme">
+          <ref name="nonEmptyContent"/>
+          <optional>
+            <!-- id: line in a ccli theme list from
+                 http://www.ccli.com.au/owners/themes.cfm -->
+            <attribute name="id">
+              <data type="positiveInteger">
+                <param name="minInclusive">1</param>
+                <param name="maxInclusive">999</param>
+              </data>
+            </attribute>
+          </optional>
+          <optional>
+            <ref name="langAttribute"/>
+            <optional>
+              <ref name="translitAttribute"/>
+            </optional>
+          </optional>
+        </element>
+      </oneOrMore>
+    </element>
+  </define>
+
+  <define name="comments">
+    <element name="comments">
+      <oneOrMore>
+        <element name="comment">
+          <ref name="nonEmptyContent"/>
+        </element>
+      </oneOrMore>
+    </element>
+  </define>
+
+  <!-- FORMAT -->
+
+  <define name="formatTags">
+    <!-- Allow only one set of formatting tags for lyrics -->
+    <element name="tags">
+      <attribute name="application">
+        <ref name="nonEmptyContent"/>
+      </attribute>
+      <oneOrMore>
+        <ref name="formatTagsTag"/>
+      </oneOrMore>
+    </element>
+  </define>
+
+  <define name="formatTagsTag">
+    <element name="tag">
+      <attribute name="name">
+        <ref name="nonEmptyContent"/>
+      </attribute>
+      <element name="open">
+        <ref name="nonEmptyContent"/>
+      </element>
+      <!-- Close element is optional. Formatting without text may be present.
+           e.g. <br/> -->
+      <optional>
+        <element name="close">
+          <ref name="nonEmptyContent"/>
+        </element>
+      </optional>
+    </element>
+  </define>
+
+ <!-- LYRICS -->
+
+  <define name="verse">
+    <element name="verse">
+      <ref name="verseAttributes"/>
+      <optional>
+        <ref name="langAttribute"/>
+        <optional>
+          <ref name="translitAttribute"/>
+        </optional>
+      </optional>
+      <oneOrMore>
+        <ref name="lines"/>
+      </oneOrMore>
+    </element>
+  </define>
+ 
+  <define name="lines">
+    <element name="lines">
+      <optional>
+        <attribute name="part">
+          <ref name="nonEmptyContent"/>
+        </attribute>
+      </optional>
+      <optional>
+        <attribute name="break">
+          <value>optional</value>
+        </attribute>
+      </optional>
+      <zeroOrMore>
+        <ref name="linesContent"/>
+      </zeroOrMore>
+      <ref name="linesContent"/>
+    </element>
+  </define>
+ 
+  <define name="chord">
+    <element name="chord">
+      <attribute name="name">
+        <ref name="nonEmptyContent"/>
+      </attribute>
+      <empty/>
+    </element>
+  </define>
+ 
+  <define name="tag">
+    <element name="tag">
+      <attribute name="name">
+        <ref name="nonEmptyContent"/>
+      </attribute>
+      <!-- allow using more formatting tags for text -->
+      <!-- e.g. <tag name="bold"><tag name="red">my text</tag></tag> -->
+      <choice>
+        <oneOrMore>
+          <ref name="linesContent"/>
+        </oneOrMore>
+        <!-- Allow empty tag. Formatting without text may be present.
+             e.g. <tag name="br"/> -->
+        <empty/>
+      </choice>
+    </element>
+  </define>
+ 
+  <define name="verseAttributes">
+    <attribute name="name">
+      <ref name="verseNameType"/>
+    </attribute>
+  </define>
+ 
+  <define name="songAttributes">
+    <!-- by default: value of type string is required in attr -->     
+    <attribute name="version">
+      <data type="NMTOKEN"> <!-- one word value -->
+        <!-- allow only values like: '0.1' '11.2' '13.14.15' -->
+        <param name="pattern">[0-9]+\.[0-9]+(\.[0-9]+)?</param>
+      </data>
+    </attribute>
+    <attribute name="createdIn">
+      <ref name="nonEmptyContent"/>
+    </attribute>
+    <attribute name="modifiedIn">
+      <ref name="nonEmptyContent"/>
+    </attribute>
+    <attribute name="modifiedDate">
+      <!-- date format: ISO 8601 -->
+      <data type="dateTime"/>
+    </attribute>
+  </define>
+
+  <define name="verseNameType">
+    <choice>
+      <data type="NMTOKEN">
+        <param name="minLength">1</param>
+        <!-- verse - v1, v2, v1a, ...  3 letters: [verse][verse_number][verse_part]
+             chorus c, c1, c2, c1a, ca, ...
+             pre-chorus - p, p1, p2, p1a, pa, ...
+             bridge - b, b1, b2, b1a, ba, ...
+             ending - e, e1, e2, e1a, ea, ... -->
+        <param name="pattern">(v[1-9]\d?[a-z]?)|([cpb][a-z]?)|([cpbe][1-9]\d?[a-z]?)</param>
+      </data>
+      <!-- custom values of verse name - one word name -->
+      <data type="NMTOKEN"/>
+    </choice>
+  </define>
+
+  <define name="langAttribute">
+    <attribute name="lang">
+      <data type="language"/>
+    </attribute>
+  </define>
+
+  <!-- transliteration -->
+  <define name="translitAttribute">
+    <attribute name="translit">
+      <data type="language"/>
+    </attribute>
+  </define>
+
+  <define name="nonEmptyContent">
+    <data type="string">
+      <param name="minLength">1</param>
+    </data>
+  </define>
+
+  <define name="linesContent">
+    <!-- allow tag 'tag' inside regular text - mixed content -->
+    <optional>
+      <ref name="tag"/>
+    </optional>
+    <!-- allow tag 'comment' inside regular text - mixed content -->
+    <optional>
+      <element name="comment">
+        <ref name="nonEmptyContent"/>
+      </element>
+    </optional>
+    <!-- allow tag 'chord' inside regular text - mixed content -->
+    <optional>
+      <ref name="chord"/>
+    </optional>
+    <text/>
+    <optional>
+      <element name="br">
+        <empty/>
+      </element>
+    </optional>
+  </define>
+
+</grammar>

=== added file 'tests/resources/openlyrics/validate.py'
--- tests/resources/openlyrics/validate.py	1970-01-01 00:00:00 +0000
+++ tests/resources/openlyrics/validate.py	2011-10-06 17:09:52 +0000
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+
+import sys
+
+try:
+    from lxml import etree
+except ImportError:
+    print('Python module "lxml" is required')
+    exit(1)
+
+
+if len(sys.argv) != 3:
+    print('Usage: python   %s   openlyrics_schema.rng  xmlfile.xml' % __file__)
+    exit(1)
+
+
+relaxng_file = sys.argv[1]
+xml_file = sys.argv[2]
+
+relaxng_doc = etree.parse(relaxng_file)
+xml_doc = etree.parse(xml_file)
+
+relaxng = etree.RelaxNG(relaxng_doc)
+
+relaxng.assertValid(xml_doc)
+

=== added directory 'tests/resources/songs'
=== added file 'tests/resources/songs/openlyrics_0.7_chords_comments.xml'
--- tests/resources/songs/openlyrics_0.7_chords_comments.xml	1970-01-01 00:00:00 +0000
+++ tests/resources/songs/openlyrics_0.7_chords_comments.xml	2011-10-06 17:09:52 +0000
@@ -0,0 +1,36 @@
+<?xml version='1.0' encoding='utf-8'?>
+<song xmlns="http://openlyrics.info/namespace/2009/song"; version="0.7" createdIn="OpenLP 1.9.5" modifiedIn="OpenLP 1.9.5" modifiedDate="2011-09-06T20:49:59">
+  <properties>
+    <titles>
+      <title>Jezu Kriste, štědrý kněže</title>
+    </titles>
+    <authors>
+      <author>M. Jan Hus</author>
+    </authors>
+    <songbooks>
+      <songbook name="Jistebnický kancionál"/>
+    </songbooks>
+  </properties>
+  <lyrics>
+    <verse name="v1">
+      <lines>
+        <comment>Comment1 should be ignored.</comment>
+        <line><chord name="A"/>Jezu <chord name="B"/>Kriste, štěd<chord name="C"/>rý kněže,</line>
+        <comment>Comment2 should be also ignored.</comment>
+        <line>s Otcem, Duchem jeden Bože,</line>
+      </lines>
+      <lines>
+        <line>štědrost <chord name="D"/>Tvá je naše zboží,<chord name="E"/></line>
+        <line>z Tvé milosti.</line>
+      </lines>
+    </verse>
+    <verse name="v2">
+      <lines>
+        <line>Ty jsi v světě, bydlil s námi,</line>
+        <line>Tvé tělo trpělo rány</line>
+        <line>za nás za hříšné křesťany,</line>
+        <line>z Tvé milosti.</line>
+      </lines>
+    </verse>
+  </lyrics>
+</song>

=== added file 'tests/resources/songs/openlyrics_0.7_import.xml'
--- tests/resources/songs/openlyrics_0.7_import.xml	1970-01-01 00:00:00 +0000
+++ tests/resources/songs/openlyrics_0.7_import.xml	2011-10-06 17:09:52 +0000
@@ -0,0 +1,34 @@
+<?xml version='1.0' encoding='utf-8'?>
+<song xmlns="http://openlyrics.info/namespace/2009/song"; version="0.7" createdIn="OpenLP 1.9.5" modifiedIn="OpenLP 1.9.5" modifiedDate="2011-09-06T20:49:59">
+  <properties>
+    <titles>
+      <title>Jezu Kriste, štědrý kněže</title>
+    </titles>
+    <authors>
+      <author>M. Jan Hus</author>
+    </authors>
+    <songbooks>
+      <songbook name="Jistebnický kancionál"/>
+    </songbooks>
+  </properties>
+  <lyrics>
+    <verse name="v1">
+      <lines>
+        <line>Jezu Kriste, štědrý kněže,</line>
+        <line>s Otcem, Duchem jeden Bože,</line>
+      </lines>
+      <lines>
+        <line>štědrost Tvá je naše zboží,</line>
+        <line>z Tvé milosti.</line>
+      </lines>
+    </verse>
+    <verse name="v2">
+      <lines>
+        <line>Ty jsi v světě, bydlil s námi,</line>
+        <line>Tvé tělo trpělo rány</line>
+        <line>za nás za hříšné křesťany,</line>
+        <line>z Tvé milosti.</line>
+      </lines>
+    </verse>
+  </lyrics>
+</song>

=== added file 'tests/resources/songs/openlyrics_chords_comments.xml'
--- tests/resources/songs/openlyrics_chords_comments.xml	1970-01-01 00:00:00 +0000
+++ tests/resources/songs/openlyrics_chords_comments.xml	2011-10-06 17:09:52 +0000
@@ -0,0 +1,22 @@
+<?xml version='1.0' encoding='utf-8'?>
+<song xmlns="http://openlyrics.info/namespace/2009/song"; version="0.8" createdIn="OpenLP 1.9.5" modifiedIn="OpenLP 1.9.5" modifiedDate="2011-09-06T20:49:59">
+  <properties>
+    <titles>
+      <title>Jezu Kriste, štědrý kněže</title>
+    </titles>
+    <authors>
+      <author>M. Jan Hus</author>
+    </authors>
+    <songbooks>
+      <songbook name="Jistebnický kancionál"/>
+    </songbooks>
+  </properties>
+  <lyrics>
+    <verse name="v1">
+      <lines><comment>Comment1 should be ignored.</comment>Jezu Kriste, <chord name="A"/>štědrý kněže,<br/>s Otcem, <chord name="B"/>Duchem jed<chord name="C"/>en Bože,<br/>štědrost <comment>Comment2 should <chord name="D"/>be also ignored.</comment>Tvá je naše zboží,<br/>z Tvé milosti.</lines>
+    </verse>
+    <verse name="v2">
+      <lines>Ty jsi v světě, bydlil s námi,<br/>Tvé tělo trpělo rány<br/>za nás za hříšné křesťany,<br/>z Tvé milosti.</lines>
+    </verse>
+  </lyrics>
+</song>

=== added file 'tests/resources/songs/openlyrics_formatting_tags.xml'
--- tests/resources/songs/openlyrics_formatting_tags.xml	1970-01-01 00:00:00 +0000
+++ tests/resources/songs/openlyrics_formatting_tags.xml	2011-10-06 17:09:52 +0000
@@ -0,0 +1,73 @@
+<?xml version='1.0' encoding='utf-8'?>
+<song xmlns="http://openlyrics.info/namespace/2009/song"; version="0.8" createdIn="OpenLP 1.9.5" modifiedIn="OpenLP 1.9.5" modifiedDate="2011-09-06T20:49:59">
+  <properties>
+    <titles>
+      <title>Jezu Kriste, štědrý kněže</title>
+    </titles>
+    <authors>
+      <author>M. Jan Hus</author>
+    </authors>
+    <songbooks>
+      <songbook name="Jistebnický kancionál"/>
+    </songbooks>
+  </properties>
+  <format>
+    <tags application="OpenLP">
+      <tag name="r">
+        <open>&lt;span style="-webkit-text-fill-color:red"&gt;</open>
+        <close>&lt;/span&gt;</close>
+      </tag>
+      <tag name="bl">
+        <open>&lt;span style="-webkit-text-fill-color:blue"&gt;</open>
+        <close>&lt;/span&gt;</close>
+      </tag>
+      <tag name="y">
+        <open>&lt;span style="-webkit-text-fill-color:yellow"&gt;</open>
+        <close>&lt;/span&gt;</close>
+      </tag>
+      <tag name="o">
+        <open>&lt;span style="-webkit-text-fill-color:#FFA500"&gt;</open>
+        <close>&lt;/span&gt;</close>
+      </tag>
+      <tag name="st">
+        <open>&lt;strong&gt;</open>
+        <close>&lt;/strong&gt;</close>
+      </tag>
+      <tag name="it">
+        <open>&lt;em&gt;</open>
+        <close>&lt;/em&gt;</close>
+      </tag>
+      <tag name="g">
+        <open>&lt;span style="-webkit-text-fill-color:green"&gt;</open>
+        <close>&lt;/span&gt;</close>
+      </tag>
+      <tag name="aq">
+        <open>&lt;span style="-webkit-text-fill-color:#10F7E2"&gt;</open>
+        <close>&lt;/span&gt;</close>
+      </tag>
+      <tag name="br">
+        <open>&lt;br&gt;</open>
+      </tag>
+    </tags>
+  </format>
+  <lyrics>
+    <verse name="v1">
+      <lines><tag name="r">Jezu Kriste</tag>, štědrý kněže,<br/>s <tag name="bl">Otcem, Duchem</tag> jeden <tag name="y">Bože</tag>,<br/>štědrost Tvá je naše zboží,<br/>z <tag name="o"><tag name="st">Tvé</tag></tag> <tag name="it">milosti</tag>.</lines>
+    </verse>
+    <verse name="v2">
+      <lines><tag name="bl">Ty</tag> jsi v světě, bydlil s námi,<br/>Tvé tělo trpělo rány<br/>za nás za hříšné křesťany,<br/>z <tag name="bl">Tvé</tag> milosti.</lines>
+    </verse>
+    <verse name="v3">
+      <lines>Ó, <tag name="g">Tvá dobroto</tag> důstojná<br/>a k nám milosti přehojná!<br/>Dáváš nám bohatství mnohá<br/><tag name="st"><tag name="y">z Tvé milosti.</tag></tag></lines>
+    </verse>
+    <verse name="v4">
+      <lines>Ráčils nás sám zastoupiti,<br/><tag name="it">život za nás položiti,</tag><br/>tak smrt věčnou zahladiti,<br/>z Tvé milosti.</lines>
+    </verse>
+    <verse name="v5">
+      <lines><tag name="st">Ó, <tag name="r">křesťané</tag>, z bludů</tag> vstaňme,<br/>dané dobro nám poznejme,<br/>k <tag name="aq">Synu Božímu</tag> chvátejme,<br/>k té milosti!</lines>
+    </verse>
+    <verse name="v6">
+      <lines>Chvála<tag name="br"/>budiž<tag name="br"/>Bohu<tag name="br"/>Otci,<br/><tag name="st">Synu jeho téže moci,<br/>Duchu jeho rovné moci,<br/>z též milosti!</tag></lines>
+    </verse>
+  </lyrics>
+</song>

=== added file 'tests/resources/songs/openlyrics_plain.xml'
--- tests/resources/songs/openlyrics_plain.xml	1970-01-01 00:00:00 +0000
+++ tests/resources/songs/openlyrics_plain.xml	2011-10-06 17:09:52 +0000
@@ -0,0 +1,22 @@
+<?xml version='1.0' encoding='utf-8'?>
+<song xmlns="http://openlyrics.info/namespace/2009/song"; version="0.8" createdIn="OpenLP 1.9.5" modifiedIn="OpenLP 1.9.5" modifiedDate="2011-09-06T20:49:59">
+  <properties>
+    <titles>
+      <title>Jezu Kriste, štědrý kněže</title>
+    </titles>
+    <authors>
+      <author>M. Jan Hus</author>
+    </authors>
+    <songbooks>
+      <songbook name="Jistebnický kancionál"/>
+    </songbooks>
+  </properties>
+  <lyrics>
+    <verse name="v1">
+      <lines>Jezu Kriste, štědrý kněže,<br/>s Otcem, Duchem jeden Bože,<br/>štědrost Tvá je naše zboží,<br/>z Tvé milosti.</lines>
+    </verse>
+    <verse name="v2">
+      <lines>Ty jsi v světě, bydlil s námi,<br/>Tvé tělo trpělo rány<br/>za nás za hříšné křesťany,<br/>z Tvé milosti.</lines>
+    </verse>
+  </lyrics>
+</song>

=== added file 'tests/resources/songs/songs.sqlite'
Binary files tests/resources/songs/songs.sqlite	1970-01-01 00:00:00 +0000 and tests/resources/songs/songs.sqlite	2011-10-06 17:09:52 +0000 differ
=== added file 'tests/run.py'
--- tests/run.py	1970-01-01 00:00:00 +0000
+++ tests/run.py	2011-10-06 17:09:52 +0000
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2011 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan      #
+# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan,      #
+# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias     #
+# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,    #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund             #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+
+"""
+This script is used to run set of automated tests of OpenLP. To start tests,
+simply run this script::
+
+    @:~$ ./run.py
+
+"""
+
+import os.path
+import sys
+
+TESTS_PATH = os.path.dirname(os.path.abspath(__file__))
+SRC_PATH = os.path.join(TESTS_PATH, '..')
+
+PYTEST_OPTIONS = [TESTS_PATH]
+
+# Extend python PATH with openlp source.
+sys.path.insert(0, SRC_PATH)
+
+# Python testing framework http://pytest.org.
+import pytest
+
+
+def main():
+    print 'pytest options:', PYTEST_OPTIONS
+    pytest.main(PYTEST_OPTIONS)
+
+
+if __name__ == u'__main__':
+    main()

=== added file 'tests/test_app.py'
--- tests/test_app.py	1970-01-01 00:00:00 +0000
+++ tests/test_app.py	2011-10-06 17:09:52 +0000
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2011 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan      #
+# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan,      #
+# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias     #
+# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,    #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund             #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+
+"""
+GUI tests
+"""
+
+from openlp.core import OpenLP
+from openlp.core.ui.mainwindow import MainWindow
+
+# TODO Uncomment when using custom OpenLP configuration is implemented.
+# Otherwise running this test would mess up user's OpenLP settings.
+#def test_start_app(openlp_runner):
+    #app = openlp_runner.get_app()
+    #assert type(app) == OpenLP
+    #assert type(app.mainWindow) == MainWindow
+    #assert unicode(app.mainWindow.windowTitle()) == u'OpenLP 2.0'

=== added file 'tests/test_openlyrics.py'
--- tests/test_openlyrics.py	1970-01-01 00:00:00 +0000
+++ tests/test_openlyrics.py	2011-10-06 17:09:52 +0000
@@ -0,0 +1,254 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2011 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan      #
+# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan,      #
+# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias     #
+# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,    #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund             #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+
+"""
+OpenLyrics import/export tests.
+"""
+
+import cPickle
+
+from PyQt4 import QtCore
+from lxml import etree
+
+from openlp.core.lib import FormattingTags
+from openlp.plugins.songs.lib.db import Song
+from openlp.plugins.songs.lib import OpenLyrics, SongXML
+
+# Import from xml containing openlyrics 0.7 .
+IMPORTED_VERSES_0_7 = [
+    [
+        {'type': 'v', 'label': '1'},
+        u'Jezu Kriste, štědrý kněže,\n'
+        u's Otcem, Duchem jeden Bože,\n'
+        u'štědrost Tvá je naše zboží,\n'
+        u'z Tvé milosti.'
+    ],
+    [
+        {'type': 'v', 'label': '2'},
+        u'Ty jsi v světě, bydlil s námi,\n'
+        u'Tvé tělo trpělo rány\n'
+        u'za nás za hříšné křesťany,\n'
+        u'z Tvé milosti.'
+    ],
+]
+IMPORTED_VERSES = [
+    [
+        {'type': 'v', 'label': '1'},
+        u'{r}Jezu Kriste{/r}, štědrý kněže,\n'
+        u's {bl}Otcem, Duchem{/bl} jeden {y}Bože{/y},\n'
+        u'štědrost Tvá je naše zboží,\n'
+        u'z {o}{st}Tvé{/st}{/o} {it}milosti{/it}.'
+    ],
+    [
+        {'type': 'v', 'label': '2'},
+        u'{bl}Ty{/bl} jsi v světě, bydlil s námi,\n'
+        u'Tvé tělo trpělo rány\n'
+        u'za nás za hříšné křesťany,\n'
+        u'z {bl}Tvé{/bl} milosti.'
+    ],
+    [
+        {'type': 'v', 'label': '3'},
+        u'Ó, {g}Tvá dobroto{/g} důstojná\n'
+        u'a k nám milosti přehojná!\n'
+        u'Dáváš nám bohatství mnohá\n'
+        u'{st}{y}z Tvé milosti.{/y}{/st}'
+    ],
+    [
+        {'type': 'v', 'label': '4'},
+        u'Ráčils nás sám zastoupiti,\n'
+        u'{it}život za nás položiti,{/it}\n'
+        u'tak smrt věčnou zahladiti,\n'
+        u'z Tvé milosti.'
+    ],
+    [
+        {'type': 'v', 'label': '5'},
+        u'{st}Ó, {r}křesťané{/r}, z bludů{/st} vstaňme,\n'
+        u'dané dobro nám poznejme,\n'
+        # {aq} is user's defined formatting tag.
+        u'k {aq}Synu Božímu{/aq} chvátejme,\n'
+        u'k té milosti!'
+    ],
+    [
+        {'type': 'v', 'label': '6'},
+        u'Chvála{br}budiž{br}Bohu{br}Otci,\n'
+        u'{st}Synu jeho téže moci,\n'
+        u'Duchu jeho rovné moci,\n'
+        u'z též milosti!{/st}'
+    ]
+]
+
+# User's formatting tag.
+user_tags = [
+    {
+        u'desc': u'aq',
+        u'start tag': u'{aq}',
+        u'start html': u'<span style="-webkit-text-fill-color:#10F7E2">',
+        u'end tag': u'{/aq}',
+        u'end html': u'</span>',
+        u'protected': False,
+    }
+]
+
+
+def pytest_generate_tests(metafunc):
+    """
+    Tell pytest to run some test fuctions multiple times. But every time
+    with different value of argument ``import_song`` or ``import_export_song``.
+    """
+    if 'import_song' in metafunc.funcargnames:
+        for song in [u'openlyrics_chords_comments.xml',
+            'openlyrics_0.7_chords_comments.xml',
+            'openlyrics_0.7_import.xml', ]:
+            metafunc.addcall(id=song, param=song)
+    if 'import_export_song' in metafunc.funcargnames:
+        for song in [u'openlyrics_formatting_tags.xml',
+            u'openlyrics_plain.xml', ]:
+            metafunc.addcall(id=song, param=song)
+
+
+def pytest_funcarg__import_song(request):
+    """
+    Allow to pass argument ``import_song`` to pytest test functions.
+    """
+    return request.param
+
+
+def pytest_funcarg__import_export_song(request):
+    """
+    Allow to pass argument ``import_export_song`` to pytest test functions.
+    """
+    return request.param
+
+
+def _cmp_verses(lyrics1, lyrics2):
+    """
+    Compare verses and their content.
+    """
+    for verse_a, verse_b in zip(lyrics1, lyrics2):
+        assert verse_a[0]['type'] == verse_b[0]['type']
+        assert verse_a[0]['label'] == verse_b[0]['label']
+        # compare lyrics
+        assert verse_a[1] == verse_b[1]
+
+
+def _cmp_xml_files(file1, file2):
+    """
+    Compare two xml files line by line.
+    """
+    for line1, line2 in zip(file1.readlines(), file2.readlines()):
+        # skip line with item modifiedDate - it is unique everytime
+        if line1.startswith('<song xmlns='):
+            continue
+        assert line1 == line2
+
+
+def _import_xml(db, xml_file):
+    """
+    Import song from OpenLyrics xml into song database.
+    """
+    parser = etree.XMLParser(remove_blank_text=True)
+    parsed_file = etree.parse(open(xml_file.strpath, u'r'), parser)
+    xml = unicode(etree.tostring(parsed_file))
+    converter = OpenLyrics(db)
+    song = converter.xml_to_song(xml)
+    return song
+
+
+def _export_xml(db, song, xml_file):
+    """
+    Export song from song database into OpenLyrics xml.
+    """
+    converter = OpenLyrics(db)
+    xml = converter.song_to_xml(song)
+    tree = etree.ElementTree(etree.fromstring(xml))
+    tree.write(open(xml_file.strpath, u'w'), encoding=u'utf-8',
+        xml_declaration=True, pretty_print=True)
+
+
+def test_openlyrics_import_formatting_tags(openlp_runner, pth, tmpdir):
+    """
+    Test import of OpenLyrics xml containing formatting tags.
+    """
+    f_in = pth.songs.join('openlyrics_formatting_tags.xml')
+    song = _import_xml(openlp_runner.get_songs_db(empty=True), f_in)
+    sxml = SongXML()
+    verses = sxml.get_verses(song.lyrics)
+    _cmp_verses(verses, IMPORTED_VERSES)
+    # Verify if custom formatting tag was created.
+    tags = QtCore.QSettings().value(u'displayTags/html_tags',
+        QtCore.QVariant(u'')).toString()
+    tags = str(unicode(tags).encode(u'utf8'))
+    tags = cPickle.loads(tags)
+    assert tags == user_tags
+
+
+def test_openlyrics_import(openlp_runner, pth, tmpdir, import_song):
+    """
+    Test import of OpenLyrics xml with different structure.
+    """
+    f_in = pth.songs.join(import_song)
+    song = _import_xml(openlp_runner.get_songs_db(empty=True), f_in)
+    sxml = SongXML()
+    verses = sxml.get_verses(song.lyrics)
+    print verses
+    _cmp_verses(verses, IMPORTED_VERSES_0_7)
+
+
+def test_openlyrics_export_formatting_tags(openlp_runner, openlyrics_validator,
+    pth, tmpdir):
+    """
+    Test import of OpenLyrics xml containing formatting tags.
+    """
+    # Load formatting tags for testing.
+    QtCore.QSettings().setValue(u'displayTags/html_tags',
+        QtCore.QVariant(cPickle.dumps(user_tags)))
+    print repr(FormattingTags.get_html_tags())
+    # Export song to file.
+    db = openlp_runner.get_songs_db()
+    f_out = tmpdir.join('out.xml')
+    song = db.get_all_objects(Song)[0]
+    _export_xml(db, song, f_out)
+    # Validate file.
+    assert openlyrics_validator.validate(f_out.strpath) == True
+    # String comparison with original file line by line.
+    _cmp_xml_files(f_out, pth.songs.join('openlyrics_formatting_tags.xml'))
+
+
+def test_openlyrics_import_and_export(openlp_runner, pth, tmpdir,
+        import_export_song):
+    """
+    Test import of song, then export it and compare it with original
+    OpenLyrics xml.
+    """
+    f_in = pth.songs.join(import_export_song)
+    f_out = tmpdir.join('out.xml')
+    db = openlp_runner.get_songs_db(empty=True)
+    song = _import_xml(db, f_in)
+    _export_xml(db, song, f_out)
+    # Compare files.
+    _cmp_xml_files(f_in, f_out)

=== added file 'tests/test_songs_db.py'
--- tests/test_songs_db.py	1970-01-01 00:00:00 +0000
+++ tests/test_songs_db.py	2011-10-06 17:09:52 +0000
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2011 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan      #
+# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan,      #
+# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias     #
+# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,    #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund             #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+
+"""
+Songs database tests.
+"""
+
+import pytest
+from sqlalchemy.exc import InvalidRequestError
+
+from openlp.plugins.songs.lib.db import Author, Book, MediaFile, Song, Topic
+
+
+def test_empty_songdb(openlp_runner):
+    """
+    Test default values from song database without any songs.
+    """
+    db = openlp_runner.get_songs_db(empty=True)
+    g = db.get_all_objects
+    assert g(Author) == []
+    assert g(Book) == []
+    assert g(MediaFile) == []
+    assert g(Song) == []
+    assert g(Topic) == []
+    c = db.get_object_count
+    assert c(Author) == 0
+    assert c(Book) == 0
+    assert c(MediaFile) == 0
+    assert c(Song) == 0
+    assert c(Topic) == 0
+
+
+class A(object):
+    """
+    Test class not mapped to any sqlalchemy table.
+    """
+    pass
+
+
+def test_unmapped_class(openlp_runner):
+    """
+    Test exceptions raised by sqlalchemy when passing a class not mapped
+    to any database table.
+    """
+    db = openlp_runner.get_songs_db(empty=True)
+    assert db.save_object(A()) == False
+    assert db.save_objects([A(), A()]) == False
+    # No key - new object instance is created from supplied class.
+    assert type(db.get_object(A, key=None)) == A
+    with pytest.raises(InvalidRequestError):
+        db.get_object(A, key=1)
+    with pytest.raises(InvalidRequestError):
+        db.get_object_filtered(A, filter_clause=None)
+    with pytest.raises(InvalidRequestError):
+        db.get_all_objects(A)
+    with pytest.raises(InvalidRequestError):
+        db.get_object_count(A)
+    assert db.delete_object(A, key=None) == False
+    assert db.delete_all_objects(A) == False


Follow ups