← Back to team overview

zeitgeist team mailing list archive

lp:~thekorn/zeitgeist/fix-634740-634744-testrunner-improvements into lp:zeitgeist

 

Markus Korn has proposed merging lp:~thekorn/zeitgeist/fix-634740-634744-testrunner-improvements into lp:zeitgeist.

Requested reviews:
  Zeitgeist Framework Team (zeitgeist)
Related bugs:
  #634740 explicitly define on a per testcase basis which extension needs to be loaded
  https://bugs.launchpad.net/bugs/634740
  #634744 test suite fails if zeitgeist is installed on the system
  https://bugs.launchpad.net/bugs/634744


* Make sure to run all tests using its own temporary ZEITGEIST_DATA_PATH, ZEITGEIST_DATABASE set to ":memory:", and as much isolated from other tests as possible. Unfortunately this requires some lazy imports (LP: #634740)
* `make check` (or `test/run-all-tests.py`) runs all tests now on a private DBUs bus (LP: #634744)
-- 
https://code.launchpad.net/~thekorn/zeitgeist/fix-634740-634744-testrunner-improvements/+merge/36134
Your team Zeitgeist Framework Team is requested to review the proposed merge of lp:~thekorn/zeitgeist/fix-634740-634744-testrunner-improvements into lp:zeitgeist.
=== modified file 'test/engine-extension-test.py'
--- test/engine-extension-test.py	2010-08-02 10:13:12 +0000
+++ test/engine-extension-test.py	2010-09-21 11:46:43 +0000
@@ -7,30 +7,23 @@
 import weakref
 sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
 
-import _zeitgeist.engine
-from _zeitgeist.engine import constants
-from _zeitgeist.engine import get_engine
-from _zeitgeist.engine.extension import Extension
-
 import unittest
 from testutils import import_events
 
-class _Extension1(Extension):
-	PUBLIC_METHODS = ["return_hallo", "return_engine"]
-		
-	def return_hallo(self):
-		return "Hallo"
-		
-	def return_boo(self):
-		return "boo"
-		
-	def return_engine(self):
-		return self.engine
-
+Extension = None
 
 class _engineTestClass(unittest.TestCase):
 	
 	def setUp (self):
+		global Extension
+		
+		from _zeitgeist.engine import constants
+		from _zeitgeist.engine import get_engine
+		
+		if Extension is None:
+			from _zeitgeist.engine.extension import Extension as _Extension
+			Extension = _Extension
+		
 		constants.DATABASE_FILE = ":memory:"
 		self.save_default_ext = os.environ.get("ZEITGEIST_DEFAULT_EXTENSIONS")
 		self.save_extra_ext = os.environ.get("ZEITGEIST_EXTRA_EXTENSIONS")
@@ -39,6 +32,7 @@
 		self.engine = get_engine()
 		
 	def tearDown (self):
+		import _zeitgeist.engine
 		if self.save_default_ext is not None:
 			os.environ["ZEITGEIST_DEFAULT_EXTENSIONS"] = self.save_default_ext
 		else:
@@ -54,13 +48,27 @@
 class TestExtensions(_engineTestClass):
 	
 	def testCreateEngine(self):
-		engine = get_engine()
+
+		class _Extension1(Extension):
+			PUBLIC_METHODS = ["return_hallo", "return_engine"]
+				
+			def return_hallo(self):
+				return "Hallo"
+				
+			def return_boo(self):
+				return "boo"
+				
+			def return_engine(self):
+				return self.engine
+		
+		engine = self.engine
 		self.assertEqual(len(engine.extensions), 0)
 		self.assertRaises(AttributeError, engine.extensions.__getattr__, "return_hallo")
 		engine.extensions.load(_Extension1)
 		self.assertEqual(engine.extensions.return_hallo(), "Hallo")
 		self.assertRaises(AttributeError, engine.extensions.__getattr__, "return_boo")
 		self.assertEqual(engine.extensions.return_engine(), weakref.proxy(engine))
+		
 
 class TestExtensionHooks(_engineTestClass):
 	

=== modified file 'test/engine-test.py'
--- test/engine-test.py	2010-08-31 20:48:33 +0000
+++ test/engine-test.py	2010-09-21 11:46:43 +0000
@@ -6,9 +6,6 @@
 import os
 sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
 
-import _zeitgeist.engine
-from _zeitgeist.engine import constants
-from _zeitgeist.engine import get_engine
 from zeitgeist.datamodel import *
 from testutils import import_events
 
@@ -40,6 +37,9 @@
 class _engineTestClass(unittest.TestCase):
 	
 	def setUp (self):
+		from _zeitgeist.engine import constants
+		from _zeitgeist.engine import get_engine
+		
 		self.save_default_ext = os.environ.get("ZEITGEIST_DEFAULT_EXTENSIONS")
 		self.save_extra_ext = os.environ.get("ZEITGEIST_EXTRA_EXTENSIONS")
 		os.environ["ZEITGEIST_DEFAULT_EXTENSIONS"] = ""
@@ -57,6 +57,7 @@
 		self.engine = get_engine()
 	
 	def tearDown (self):
+		import _zeitgeist.engine
 		if self.save_default_ext is not None:
 			os.environ["ZEITGEIST_DEFAULT_EXTENSIONS"] = self.save_default_ext
 		else:

=== modified file 'test/loggers-datasources-recent-test.py'
--- test/loggers-datasources-recent-test.py	2009-11-30 07:57:58 +0000
+++ test/loggers-datasources-recent-test.py	2010-09-21 11:46:43 +0000
@@ -7,16 +7,28 @@
 import unittest
 
 sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
-from _zeitgeist.loggers.datasources.recent import SimpleMatch, MimeTypeSet
-
-class SimpleMatchTest(unittest.TestCase):
+
+SimpleMatch = None
+MimeTypeSet = None
+
+class BaseTestCase(unittest.TestCase):
+	
+	def setUp(self):
+		global SimpleMatch
+		global MimeTypeSet
+		if None in (SimpleMatch, MimeTypeSet):
+			from _zeitgeist.loggers.datasources.recent import SimpleMatch as _SM, MimeTypeSet as _MTS
+			SimpleMatch = _SM
+			MimeTypeSet = _MTS
+
+class SimpleMatchTest(BaseTestCase):
 	
 	def testmatch(self):
 		self.assertTrue(SimpleMatch("boo/*").match("boo/bar"))
 		self.assertTrue(SimpleMatch("boo/bar.*").match("boo/bar.foo"))
 		self.assertFalse(SimpleMatch("boo/bar.*").match("boo/barfoo"))
 
-class MimeTypeSetTest(unittest.TestCase):
+class MimeTypeSetTest(BaseTestCase):
 	
 	def testinit(self):
 		self.assertEquals(repr(MimeTypeSet("boo", "bar", "foo")), "MimeTypeSet('bar', 'boo', 'foo')")

=== modified file 'test/remote-test.py'
--- test/remote-test.py	2010-09-15 14:20:21 +0000
+++ test/remote-test.py	2010-09-21 11:46:43 +0000
@@ -6,6 +6,8 @@
 import logging
 import signal
 import time
+import tempfile
+import shutil
 from subprocess import Popen, PIPE
 
 # DBus setup
@@ -18,7 +20,6 @@
 from zeitgeist.client import ZeitgeistDBusInterface, ZeitgeistClient
 from zeitgeist.datamodel import (Event, Subject, Interpretation, Manifestation,
 	TimeRange, StorageState)
-from _zeitgeist.engine.remote import RemoteInterface
 
 import testutils
 from testutils import parse_events
@@ -322,9 +323,31 @@
 		
 class ZeitgeistRemoteInterfaceTest(unittest.TestCase):
 	
+	def setUp(self):
+		from _zeitgeist import engine
+		from _zeitgeist.engine import sql, constants
+		engine._engine = None
+		sql.unset_cursor()
+		self.saved_data = {
+			"datapath": constants.DATA_PATH,
+			"database": constants.DATABASE_FILE,
+			"extensions": constants.USER_EXTENSION_PATH,
+		}
+		constants.DATA_PATH = tempfile.mkdtemp(prefix="zeitgeist.datapath.")
+		constants.DATABASE_FILE = ":memory:"
+		constants.USER_EXTENSION_PATH = os.path.join(constants.DATA_PATH, "extensions")
+		
+	def tearDown(self):
+		from _zeitgeist.engine import constants
+		shutil.rmtree(constants.DATA_PATH)
+		constants.DATA_PATH = self.saved_data["datapath"]
+		constants.DATABASE_FILE = self.saved_data["database"]
+		constants.USER_EXTENSION_PATH = self.saved_data["extensions"]
+	
 	def testQuit(self):
 		"""calling Quit() on the remote interface should shutdown the
 		engine in a clean way"""
+		from _zeitgeist.engine.remote import RemoteInterface
 		interface = RemoteInterface()
 		self.assertEquals(interface._engine.is_closed(), False)
 		interface.Quit()
@@ -332,14 +355,25 @@
 		
 class ZeitgeistDaemonTest(unittest.TestCase):
 	
+	def setUp(self):
+		self.env = os.environ.copy()
+		self.datapath = tempfile.mkdtemp(prefix="zeitgeist.datapath.")
+		self.env.update({
+			"ZEITGEIST_DATABASE_PATH": ":memory:",
+			"ZEITGEIST_DATA_PATH": self.datapath,
+		})
+		
+	def tearDown(self):
+		shutil.rmtree(self.datapath)
+	
 	def testSIGHUP(self):
 		"""sending a SIGHUP signal to a running deamon instance results
 		in a clean shutdown"""
 		daemon = Popen(
-			["./zeitgeist-daemon.py", "--no-datahub"], stderr=PIPE, stdout=PIPE
+			["./zeitgeist-daemon.py", "--no-datahub"], stderr=PIPE, stdout=PIPE, env=self.env
 		)
 		# give the daemon some time to wake up
-		time.sleep(3)
+		time.sleep(1)
 		err = daemon.poll()
 		if err:
 			raise RuntimeError("Could not start daemon,  got err=%i" % err)

=== modified file 'test/run-all-tests.py'
--- test/run-all-tests.py	2010-09-02 14:33:04 +0000
+++ test/run-all-tests.py	2010-09-21 11:46:43 +0000
@@ -6,36 +6,67 @@
 import doctest
 import logging
 import sys
+import tempfile
+import shutil
 
 from optparse import OptionParser
-parser = OptionParser()
-parser.add_option("-v", action="count", dest="verbosity")
-(options, args) = parser.parse_args()
-
-if options.verbosity:
-	# do more fine grained stuff later
-	# redirect all debugging output to stderr
-	logging.basicConfig(stream=sys.stderr)
-else:
-	logging.basicConfig(filename="/dev/null")
 
 sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
 
 # Find the test/ directory
-testdir = os.path.dirname(os.path.abspath(__file__))
-doctests = glob.glob(os.path.join(testdir, "*.rst"))
-
-# Create a test suite to run all tests
-# first, add all doctests
-arguments = {"module_relative": False, "globs": {"sys": sys}}
-suite = doctest.DocFileSuite(*doctests, **arguments)
-
-# Add all of the tests from each file that ends with "-test.py"
-for fname in os.listdir(testdir):
-	if fname.endswith("-test.py"):
-		fname = os.path.basename(fname)[:-3] # Get the filename and chop off ".py"
-		module = __import__(fname)
-		suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(module))
-
-# Run all of the tests
-unittest.TextTestRunner(stream=sys.stdout, verbosity=2).run(suite)
+TESTDIR = os.path.dirname(os.path.abspath(__file__))
+DOCTESTS = glob.glob(os.path.join(TESTDIR, "*.rst"))
+
+def doctest_setup(test):
+	test._datapath = tempfile.mkdtemp(prefix="zeitgeist.datapath.")
+	test._env = os.environ.copy()
+	os.environ.update({
+		"ZEITGEIST_DATABASE_PATH": ":memory:",
+		"ZEITGEIST_DATA_PATH": test._datapath
+	})
+	
+def doctest_teardown(test):
+	shutil.rmtree(test._datapath)
+	os.environ = test._env
+
+def compile_suite():
+	# Create a test suite to run all tests
+	
+	# first, add all doctests
+	arguments = {
+		"module_relative": False,
+		"globs": {"sys": sys},
+		"setUp": doctest_setup,
+		"tearDown": doctest_teardown,
+	}
+	suite = doctest.DocFileSuite(*DOCTESTS, **arguments)
+
+	# Add all of the tests from each file that ends with "-test.py"
+	for fname in os.listdir(TESTDIR):
+		if fname.endswith("-test.py"):
+			fname = os.path.basename(fname)[:-3] # Get the filename and chop off ".py"
+			module = __import__(fname)
+			suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(module))
+	return suite
+
+if __name__ == "__main__":
+	parser = OptionParser()
+	parser.add_option("-v", action="count", dest="verbosity")
+	(options, args) = parser.parse_args()
+
+	if options.verbosity:
+		# do more fine grained stuff later
+		# redirect all debugging output to stderr
+		logging.basicConfig(stream=sys.stderr)
+	else:
+		logging.basicConfig(filename="/dev/null")
+	
+	from testutils import DBusPrivateMessageBus
+	bus = DBusPrivateMessageBus()
+	bus.run()
+	try:
+		suite = compile_suite()
+		# Run all of the tests
+		unittest.TextTestRunner(stream=sys.stdout, verbosity=2).run(suite)
+	finally:
+		bus.quit()

=== modified file 'test/sql-test.py'
--- test/sql-test.py	2010-09-19 10:42:30 +0000
+++ test/sql-test.py	2010-09-21 11:46:43 +0000
@@ -23,10 +23,16 @@
 sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
 
 import unittest
-from _zeitgeist.engine.sql import *
+WhereClause = None
 
 class SQLTest (unittest.TestCase):
 	
+	def setUp(self):
+		global WhereClause
+		if WhereClause is None:
+			from _zeitgeist.engine.sql import WhereClause as _WhereClause
+			WhereClause = _WhereClause
+	
 	def testFlat (self):
 		where = WhereClause(WhereClause.AND)
 		where.add ("foo = %s", 10)

=== modified file 'test/testutils.py'
--- test/testutils.py	2010-07-22 09:52:53 +0000
+++ test/testutils.py	2010-09-21 11:46:43 +0000
@@ -3,6 +3,7 @@
 # Zeitgeist
 #
 # Copyright © 2009 Mikkel Kamstrup Erlandsen <mikkel.kamstrup@xxxxxxxxx>
+# Copyright © 2009-2010 Markus Korn <thekorn@xxxxxx>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
@@ -22,6 +23,8 @@
 import time
 import sys
 import signal
+import tempfile
+import shutil
 from subprocess import Popen, PIPE
 
 # DBus setup
@@ -41,8 +44,6 @@
 	# maybe the user is using python < 2.6
 	import simplejson as json
 
-from zeitgeist.datamodel import Event, Subject
-
 def dict2event(d):
 	ev = Event()
 	ev[0][Event.Id] = d.get("id", "").encode("UTF-8")
@@ -92,12 +93,11 @@
 		self.client = None
 	
 	def spawn_daemon(self):
-		os.environ.update({"ZEITGEIST_DATABASE_PATH": ":memory:"})
 		self.daemon = Popen(
-			["./zeitgeist-daemon.py", "--no-datahub"], stderr=sys.stderr, stdout=sys.stderr
+			["./zeitgeist-daemon.py", "--no-datahub"], stderr=sys.stderr, stdout=sys.stderr, env=self.env
 		)
 		# give the daemon some time to wake up
-		time.sleep(3)
+		time.sleep(1)
 		err = self.daemon.poll()
 		if err:
 			raise RuntimeError("Could not start daemon,  got err=%i" % err)
@@ -109,6 +109,12 @@
 	def setUp(self):
 		assert self.daemon is None
 		assert self.client is None
+		self.env = os.environ.copy()
+		self.datapath = tempfile.mkdtemp(prefix="zeitgeist.datapath.")
+		self.env.update({
+			"ZEITGEIST_DATABASE_PATH": ":memory:",
+			"ZEITGEIST_DATA_PATH": self.datapath,
+		})
 		self.spawn_daemon()
 		
 		# hack to clear the state of the interface
@@ -119,6 +125,7 @@
 		assert self.daemon is not None
 		assert self.client is not None
 		self.kill_daemon()
+		shutil.rmtree(self.datapath)
 	
 	def insertEventsAndWait(self, events):
 		"""
@@ -220,3 +227,29 @@
 			num_events=num_events, result_type=result_type)
 		mainloop.run()
 		return result
+
+class DBusPrivateMessageBus(object):
+    DISPLAY = ":27"
+    
+    def run(self):
+        os.environ.update({"DISPLAY": self.DISPLAY})
+        self.display = Popen(["Xvfb", self.DISPLAY, "-screen", "0", "1024x768x8"])
+        # give the display some time to wake up
+        time.sleep(1)
+        err = self.display.poll()
+        if err:
+            raise RuntimeError("Could not start Xvfb on display %s, got err=%i" %(self.DISPLAY, err))
+        dbus = Popen(["dbus-launch"], stdout=PIPE)
+        time.sleep(1)
+        self.dbus_config = dict(l.split("=", 1) for l in dbus.communicate()[0].split("\n") if l)
+        os.environ.update(self.dbus_config)
+        
+    def quit(self):
+        os.kill(self.display.pid, signal.SIGKILL)
+        self.display.wait()
+        pid = int(self.dbus_config["DBUS_SESSION_BUS_PID"])
+        os.kill(pid, signal.SIGKILL)
+        try:
+            os.waitpid(pid, 0)
+        except OSError:
+            pass


Follow ups