← 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/78321

These are tests for openlyrics stuff.

Added docstrings and reformated some comments.

added more comments and fixed some code formatting issues as requested
-- 
https://code.launchpad.net/~mzibricky/openlp/openlyrics_tests/+merge/78321
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-05 21:39:27 +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.

=== added directory 'testing'
=== renamed directory 'testing' => 'testing.moved'
=== renamed file 'testing/conftest.py' => 'testing.moved/conftest.py.THIS'
=== renamed file 'testing/test_app.py' => 'testing.moved/test_app.py.THIS'
=== added file 'testing/conftest.py'
--- testing/conftest.py	1970-01-01 00:00:00 +0000
+++ testing/conftest.py	2011-10-05 21:39:27 +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 Millar, 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 'testing/resources'
=== added directory 'testing/resources/openlyrics'
=== added file 'testing/resources/openlyrics/openlyrics_schema.rng'
--- testing/resources/openlyrics/openlyrics_schema.rng	1970-01-01 00:00:00 +0000
+++ testing/resources/openlyrics/openlyrics_schema.rng	2011-10-05 21:39:27 +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 'testing/resources/openlyrics/validate.py'
--- testing/resources/openlyrics/validate.py	1970-01-01 00:00:00 +0000
+++ testing/resources/openlyrics/validate.py	2011-10-05 21:39:27 +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 'testing/resources/songs'
=== added file 'testing/resources/songs/openlyrics_0.7_chords_comments.xml'
--- testing/resources/songs/openlyrics_0.7_chords_comments.xml	1970-01-01 00:00:00 +0000
+++ testing/resources/songs/openlyrics_0.7_chords_comments.xml	2011-10-05 21:39:27 +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 'testing/resources/songs/openlyrics_0.7_import.xml'
--- testing/resources/songs/openlyrics_0.7_import.xml	1970-01-01 00:00:00 +0000
+++ testing/resources/songs/openlyrics_0.7_import.xml	2011-10-05 21:39:27 +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 'testing/resources/songs/openlyrics_chords_comments.xml'
--- testing/resources/songs/openlyrics_chords_comments.xml	1970-01-01 00:00:00 +0000
+++ testing/resources/songs/openlyrics_chords_comments.xml	2011-10-05 21:39:27 +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 'testing/resources/songs/openlyrics_formatting_tags.xml'
--- testing/resources/songs/openlyrics_formatting_tags.xml	1970-01-01 00:00:00 +0000
+++ testing/resources/songs/openlyrics_formatting_tags.xml	2011-10-05 21:39:27 +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 'testing/resources/songs/openlyrics_plain.xml'
--- testing/resources/songs/openlyrics_plain.xml	1970-01-01 00:00:00 +0000
+++ testing/resources/songs/openlyrics_plain.xml	2011-10-05 21:39:27 +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 'testing/resources/songs/songs.sqlite'
Binary files testing/resources/songs/songs.sqlite	1970-01-01 00:00:00 +0000 and testing/resources/songs/songs.sqlite	2011-10-05 21:39:27 +0000 differ
=== added file 'testing/run.py'
--- testing/run.py	1970-01-01 00:00:00 +0000
+++ testing/run.py	2011-10-05 21:39:27 +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()

=== 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()

=== added file 'testing/test_app.py'
--- testing/test_app.py	1970-01-01 00:00:00 +0000
+++ testing/test_app.py	2011-10-05 21:39:27 +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 Millar, 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 'testing/test_openlyrics.py'
--- testing/test_openlyrics.py	1970-01-01 00:00:00 +0000
+++ testing/test_openlyrics.py	2011-10-05 21:39:27 +0000
@@ -0,0 +1,253 @@
+#!/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 Millar, 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 s in [u'openlyrics_chords_comments.xml',
+            'openlyrics_0.7_chords_comments.xml',
+            'openlyrics_0.7_import.xml', ]:
+            metafunc.addcall(id=s, param=s)
+    if 'import_export_song' in metafunc.funcargnames:
+        for s in [u'openlyrics_formatting_tags.xml',
+            u'openlyrics_plain.xml', ]:
+            metafunc.addcall(id=s, param=s)
+
+
+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 a, b in zip(lyrics1, lyrics2):
+        assert a[0]['type'] == b[0]['type']
+        assert a[0]['label'] == b[0]['label']
+        # compare lyrics
+        assert a[1] == b[1]
+
+
+def _cmp_xml_files(file1, file2):
+    """
+    Compare two xml files line by line.
+    """
+    for l1, l2 in zip(file1.readlines(), file2.readlines()):
+        # skip line with item modifiedDate - it is unique everytime
+        if l1.startswith('<song xmlns='):
+            continue
+        assert l1 == l2
+
+
+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))
+    o = OpenLyrics(db)
+    song = o.xml_to_song(xml)
+    return song
+
+
+def _export_xml(db, song, xml_file):
+    """
+    Export song from song database into OpenLyrics xml.
+    """
+    o = OpenLyrics(db)
+    xml = o.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 = pth.songs.join('openlyrics_formatting_tags.xml')
+    song = _import_xml(openlp_runner.get_songs_db(empty=True), f)
+    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 = pth.songs.join(import_song)
+    song = _import_xml(openlp_runner.get_songs_db(empty=True), f)
+    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 = tmpdir.join('out.xml')
+    s = db.get_all_objects(Song)[0]
+    _export_xml(db, s, f)
+    # Validate file.
+    assert openlyrics_validator.validate(f.strpath) == True
+    # String comparison with original file line by line.
+    _cmp_xml_files(f, 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 = pth.songs.join(import_export_song)
+    f_exp = tmpdir.join('out.xml')
+    db = openlp_runner.get_songs_db(empty=True)
+    song = _import_xml(db, f)
+    _export_xml(db, song, f_exp)
+    # Compare files.
+    _cmp_xml_files(f, f_exp)

=== added file 'testing/test_songs_db.py'
--- testing/test_songs_db.py	1970-01-01 00:00:00 +0000
+++ testing/test_songs_db.py	2011-10-05 21:39:27 +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 Millar, 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