[tracker-miners/sam/functional-tests-shared: 23/23] functional-tests: Use the trackertestutils module from tracker.git



commit 5a98b2909b5d3368c9b40677e51314a6f7bbb505
Author: Sam Thursfield <sam afuera me uk>
Date:   Wed Aug 7 20:56:28 2019 +0200

    functional-tests: Use the trackertestutils module from tracker.git
    
    There is a lot of test framework code duplicated between
    tracker-miners.git and tracker.git. This has been a problem since
    tracker-miners.git was split out from tracker.git.
    
    Now, the majority of the test framework lives in tracker.git and
    is installed as a private Python library, which is used by the
    tests in this repository.
    
    With this change, the tests are launched via D-Bus auto activation
    rather than being explicitly launched by the tests  Logging is much
    improved. See HACKING.md in tracker.git for details.

 meson.build                                        |  16 +-
 tests/functional-tests/300-miner-basic-ops.py      |  37 +-
 .../functional-tests/301-miner-resource-removal.py |   7 +-
 tests/functional-tests/310-fts-basic.py            |   6 +-
 tests/functional-tests/311-fts-file-operations.py  |   6 +-
 tests/functional-tests/312-fts-stopwords.py        |  24 +-
 tests/functional-tests/400-extractor-metadata.py   |  20 +-
 .../401-extractor-flac-cuesheet.py                 |   9 +-
 tests/functional-tests/410-extractor-decorator.py  |  47 +-
 tests/functional-tests/500-writeback-images.py     |  11 +-
 .../501-writeback-image-details.py                 |   9 +-
 tests/functional-tests/502-writeback-audio.py      |   8 +-
 tests/functional-tests/600-applications-camera.py  |   5 +-
 tests/functional-tests/601-applications-sync.py    |  13 +-
 tests/functional-tests/__init__.py                 |   1 -
 .../{common/utils => }/applicationstest.py         |  38 +-
 tests/functional-tests/common/utils/__init__.py    |   1 -
 tests/functional-tests/common/utils/dconf.py       |  80 ---
 tests/functional-tests/common/utils/helpers.py     | 688 ---------------------
 tests/functional-tests/common/utils/html.py        |  63 --
 tests/functional-tests/common/utils/mainloop.py    |  58 --
 tests/functional-tests/common/utils/options.py     |  28 -
 tests/functional-tests/common/utils/storetest.py   |  43 --
 tests/functional-tests/common/utils/system.py      | 181 ------
 tests/functional-tests/configuration.json.in       |  17 +-
 .../{common/utils => }/configuration.py            | 123 ++--
 .../{common/utils => }/extractor.py                |  21 +-
 tests/functional-tests/meson.build                 |  61 +-
 tests/functional-tests/minerfshelper.py            | 132 ++++
 .../{common/utils => }/minertest.py                |  93 +--
 tests/functional-tests/system.py                   | 259 ++++++++
 tests/functional-tests/test-runner.sh.in           |  21 -
 .../{common/utils => }/writebacktest.py            | 101 ++-
 tests/test-bus.conf.in                             |   1 +
 34 files changed, 731 insertions(+), 1497 deletions(-)
---
diff --git a/meson.build b/meson.build
index a7d38e3e9..54437f4a1 100644
--- a/meson.build
+++ b/meson.build
@@ -35,9 +35,12 @@ if get_option('tracker_core') == 'system'
   # If we are building against an installed version of tracker core rather than
   # having it as a subproject, these 'uninstalled' locations point to the actual
   # installed locations.
+  tracker_test_dbus_services_dir = join_paths(tracker_sparql.get_pkgconfig_variable('dbus_services_dir'))
   tracker_uninstalled_domain_rule = join_paths 
(tracker_sparql.get_pkgconfig_variable('domain_ontologies_dir'), 'default.rule')
   tracker_uninstalled_nepomuk_ontologies_dir = 
join_paths(tracker_sparql.get_pkgconfig_variable('ontologies_dir'), 'nepomuk')
   tracker_uninstalled_stop_words_dir = join_paths(tracker_sparql.get_pkgconfig_variable('datadir'), 
'tracker', 'stop-words')
+  tracker_uninstalled_testutils_dir = join_paths(tracker_sparql.get_pkgconfig_variable('libdir', 
'tracker-2.0', 'trackertestutils'))
+
 else
   tracker_subproject = subproject('tracker',
     default_options: [
@@ -52,9 +55,11 @@ else
   tracker_store = tracker_subproject.get_variable('tracker_store')
   tracker_store_path = tracker_store.full_path()
 
+  tracker_test_dbus_services_dir = tracker_subproject.get_variable('tracker_test_dbus_services_dir')
   tracker_uninstalled_domain_rule = tracker_subproject.get_variable('tracker_uninstalled_domain_rule')
   tracker_uninstalled_nepomuk_ontologies_dir = 
tracker_subproject.get_variable('tracker_uninstalled_nepomuk_ontologies_dir')
   tracker_uninstalled_stop_words_dir = tracker_subproject.get_variable('tracker_uninstalled_stop_words_dir')
+  tracker_uninstalled_testutils_dir = tracker_subproject.get_variable('tracker_uninstalled_testutils_dir')
 
   tracker_common_enums_header = tracker_subproject.get_variable('tracker_common_enums_header')
   tracker_gsettings_schemas = tracker_subproject.get_variable('tracker_gsettings_schemas')
@@ -360,16 +365,10 @@ conf.set('includedir', join_paths(get_option('prefix'), get_option('includedir')
 conf.set('libdir', libdir)
 conf.set('libexecdir', join_paths(get_option('prefix'), get_option('libexecdir')))
 conf.set('prefix', get_option('prefix'))
+conf.set('tracker_test_dbus_services_dir', tracker_test_dbus_services_dir)
 conf.set('TRACKER_API_VERSION', '1.0')
 conf.set('VERSION', meson.project_version())
 
-# Configure functional tests to run completely from source tree.
-conf.set('FUNCTIONAL_TESTS_TRACKER_EXTRACT_PATH', join_paths(meson.current_build_dir(), 'src', 
'tracker-extract', 'tracker-extract'))
-conf.set('FUNCTIONAL_TESTS_TRACKER_MINER_FS_PATH', join_paths(meson.current_build_dir(), 'src', 'miners', 
'fs', 'tracker-miner-fs'))
-conf.set('FUNCTIONAL_TESTS_TRACKER_WRITEBACK_PATH', join_paths(meson.current_build_dir(), 'src', 
'tracker-writeback', 'tracker-writeback'))
-
-conf.set('FUNCTIONAL_TESTS_TRACKER_STORE_PATH', tracker_store_path)
-
 configure_file(input: 'config-miners.h.meson.in',
                output: 'config-miners.h',
                configuration: conf)
@@ -387,6 +386,8 @@ tracker_c_args = [
 configinc = include_directories('./')
 srcinc = include_directories('src/')
 
+build_root = meson.current_build_dir()
+
 tracker_internal_libs_dir = join_paths(get_option('prefix'), get_option('libdir'), 'tracker-miners-' + 
tracker_api_version)
 tracker_install_rpath = ':'.join([tracker_internal_libs_dir, libdir])
 
@@ -399,6 +400,7 @@ tracker_writeback_modules_dir = join_paths(get_option('prefix'), get_option('lib
 
 tracker_uninstalled_extract_rules_dir = join_paths(meson.current_build_dir(), 'src', 'tracker-extract', 
'uninstalled-rules')
 tracker_uninstalled_writeback_modules_dir = join_paths(meson.current_build_dir(), 'src', 'tracker-writeback')
+uninstalled_tracker_extract_path = join_paths(meson.current_build_dir(), 'src', 'tracker-extract', 
'tracker-extract')
 
 gsettings_schema_dir = join_paths(get_option('prefix'), get_option('datadir'), 'glib-2.0', 'schemas')
 
diff --git a/tests/functional-tests/300-miner-basic-ops.py b/tests/functional-tests/300-miner-basic-ops.py
index f783d9fbb..d6d945b61 100755
--- a/tests/functional-tests/300-miner-basic-ops.py
+++ b/tests/functional-tests/300-miner-basic-ops.py
@@ -1,6 +1,5 @@
-#!/usr/bin/env python3
-
 # Copyright (C) 2010, Nokia (ivan frade nokia com)
+# Copyright (C) 2019, Sam Thursfield (sam afuera me uk)
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -31,7 +30,7 @@ import shutil
 import time
 
 import unittest as ut
-from common.utils.minertest import CommonTrackerMinerTest
+from minertest import CommonTrackerMinerTest
 
 log = logging.getLogger(__name__)
 
@@ -104,7 +103,7 @@ class MinerCrawlTest (CommonTrackerMinerTest):
         dest = os.path.join(self.workdir, "test-monitored", "file0.txt")
         shutil.copyfile(source, dest)
 
-        dest_id, dest_urn = self.system.store.await_resource_inserted(NFO_DOCUMENT, self.uri(dest))
+        dest_id, dest_urn = self.tracker.await_resource_inserted(NFO_DOCUMENT, self.uri(dest))
 
         # verify if miner indexed this file.
         result = self.__get_text_documents()
@@ -118,7 +117,7 @@ class MinerCrawlTest (CommonTrackerMinerTest):
         # Clean the new file so the test directory is as before
         log.debug("Remove and wait")
         os.remove(dest)
-        self.system.store.await_resource_deleted(NFO_DOCUMENT, dest_id)
+        self.tracker.await_resource_deleted(NFO_DOCUMENT, dest_id)
 
     def test_03_copy_from_monitored_to_unmonitored(self):
         """
@@ -151,7 +150,7 @@ class MinerCrawlTest (CommonTrackerMinerTest):
         dest = os.path.join(self.workdir, "test-monitored", "dir1", "dir2", "file-test04.txt")
         shutil.copyfile(source, dest)
 
-        dest_id, dest_urn = self.system.store.await_resource_inserted(NFO_DOCUMENT, self.uri(dest))
+        dest_id, dest_urn = self.tracker.await_resource_inserted(NFO_DOCUMENT, self.uri(dest))
 
         result = self.__get_text_documents()
         self.assertEqual(len(result), 4)
@@ -163,7 +162,7 @@ class MinerCrawlTest (CommonTrackerMinerTest):
 
         # Clean the file
         os.remove(dest)
-        self.system.store.await_resource_deleted(NFO_DOCUMENT, dest_id)
+        self.tracker.await_resource_deleted(NFO_DOCUMENT, dest_id)
         self.assertEqual(3, self.tracker.count_instances("nfo:TextDocument"))
 
     @ut.skip("https://gitlab.gnome.org/GNOME/tracker-miners/issues/56";)
@@ -174,7 +173,7 @@ class MinerCrawlTest (CommonTrackerMinerTest):
         source = os.path.join(self.workdir, "test-no-monitored", "file0.txt")
         dest = os.path.join(self.workdir, "test-monitored", "dir1", "file-test05.txt")
         shutil.move(source, dest)
-        dest_id, dest_urn = self.system.store.await_resource_inserted(NFO_DOCUMENT, self.uri(dest))
+        dest_id, dest_urn = self.tracker.await_resource_inserted(NFO_DOCUMENT, self.uri(dest))
 
         result = self.__get_text_documents()
         self.assertEqual(len(result), 4)
@@ -186,7 +185,7 @@ class MinerCrawlTest (CommonTrackerMinerTest):
 
         # Clean the file
         os.remove(dest)
-        self.system.store.await_resource_deleted(NFO_DOCUMENT, dest_id)
+        self.tracker.await_resource_deleted(NFO_DOCUMENT, dest_id)
         self.assertEqual(3, self.tracker.count_instances("nfo:TextDocument"))
 
 ## """ move operation and tracker-miner response test cases """
@@ -198,9 +197,9 @@ class MinerCrawlTest (CommonTrackerMinerTest):
         """
         source = self.path("test-monitored/dir1/file2.txt")
         dest = self.path("test-no-monitored/file2.txt")
-        source_id = self.system.store.get_resource_id(self.uri(source))
+        source_id = self.tracker.get_resource_id(self.uri(source))
         shutil.move(source, dest)
-        self.system.store.await_resource_deleted(NFO_DOCUMENT, source_id)
+        self.tracker.await_resource_deleted(NFO_DOCUMENT, source_id)
 
         result = self.__get_text_documents()
         self.assertEqual(len(result), 2)
@@ -210,7 +209,7 @@ class MinerCrawlTest (CommonTrackerMinerTest):
 
         # Restore the file
         shutil.move(dest, source)
-        self.system.store.await_resource_inserted(NFO_DOCUMENT, self.uri(source))
+        self.tracker.await_resource_inserted(NFO_DOCUMENT, self.uri(source))
         self.assertEqual(3, self.tracker.count_instances("nfo:TextDocument"))
 
     def test_07_move_from_monitored_to_monitored(self):
@@ -258,9 +257,9 @@ class MinerCrawlTest (CommonTrackerMinerTest):
         Delete one of the files
         """
         victim = self.path("test-monitored/dir1/file2.txt")
-        victim_id = self.system.store.get_resource_id(self.uri(victim))
+        victim_id = self.tracker.get_resource_id(self.uri(victim))
         os.remove(victim)
-        self.system.store.await_resource_deleted(NFO_DOCUMENT, victim_id)
+        self.tracker.await_resource_deleted(NFO_DOCUMENT, victim_id)
 
         result = self.__get_text_documents()
         self.assertEqual(len(result), 2)
@@ -272,19 +271,19 @@ class MinerCrawlTest (CommonTrackerMinerTest):
         f = open(victim, "w")
         f.write("Don't panic, everything is fine")
         f.close()
-        self.system.store.await_resource_inserted(NFO_DOCUMENT, self.uri(victim))
+        self.tracker.await_resource_inserted(NFO_DOCUMENT, self.uri(victim))
 
     def test_09_deletion_directory(self):
         """
         Delete a directory
         """
         victim = self.path("test-monitored/dir1")
-        victim_id = self.system.store.get_resource_id(self.uri(victim))
+        victim_id = self.tracker.get_resource_id(self.uri(victim))
         shutil.rmtree(victim)
 
         file_inside_victim_url = self.uri(os.path.join(victim, "file2.txt"))
-        file_inside_victim_id = self.system.store.get_resource_id(file_inside_victim_url)
-        self.system.store.await_resource_deleted(NFO_DOCUMENT, file_inside_victim_id)
+        file_inside_victim_id = self.tracker.get_resource_id(file_inside_victim_url)
+        self.tracker.await_resource_deleted(NFO_DOCUMENT, file_inside_victim_id)
 
         result = self.__get_text_documents()
         self.assertEqual(len(result), 1)
@@ -300,7 +299,7 @@ class MinerCrawlTest (CommonTrackerMinerTest):
             writer = open(filename, "w")
             writer.write("Don't panic, everything is fine")
             writer.close()
-            self.system.store.await_resource_inserted(NFO_DOCUMENT, self.uri(f))
+            self.tracker.await_resource_inserted(NFO_DOCUMENT, self.uri(f))
 
         # Check everything is fine
         result = self.__get_text_documents()
diff --git a/tests/functional-tests/301-miner-resource-removal.py 
b/tests/functional-tests/301-miner-resource-removal.py
index fd365430d..3245f9f1d 100755
--- a/tests/functional-tests/301-miner-resource-removal.py
+++ b/tests/functional-tests/301-miner-resource-removal.py
@@ -1,6 +1,5 @@
-#!/usr/bin/env python3
-
 # Copyright (C) 2010, Nokia (ivan frade nokia com)
+# Copyright (C) 2019, Sam Thursfield (sam afuera me uk)
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -22,8 +21,8 @@ Test that resource removal does not leave debris or clobber too much,
 especially in the case where nie:InformationElement != nie:DataObject
 """
 
-from common.utils import configuration as cfg
-from common.utils.minertest import CommonTrackerMinerTest
+import configuration as cfg
+from minertest import CommonTrackerMinerTest
 
 from gi.repository import GLib
 
diff --git a/tests/functional-tests/310-fts-basic.py b/tests/functional-tests/310-fts-basic.py
index 4fc59fb06..88ec52c06 100755
--- a/tests/functional-tests/310-fts-basic.py
+++ b/tests/functional-tests/310-fts-basic.py
@@ -1,7 +1,7 @@
-#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
 # Copyright (C) 2010, Nokia (ivan frade nokia com)
+# Copyright (C) 2019, Sam Thursfield (sam afuera me uk)
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -32,8 +32,8 @@ import locale
 import time
 
 import unittest as ut
-from common.utils.minertest import CommonTrackerMinerFTSTest, DEFAULT_TEXT
-from common.utils import configuration as cfg
+from minertest import CommonTrackerMinerFTSTest, DEFAULT_TEXT
+import configuration as cfg
 
 
 class MinerFTSBasicTest (CommonTrackerMinerFTSTest):
diff --git a/tests/functional-tests/311-fts-file-operations.py 
b/tests/functional-tests/311-fts-file-operations.py
index 32373f1bf..58efcaaff 100755
--- a/tests/functional-tests/311-fts-file-operations.py
+++ b/tests/functional-tests/311-fts-file-operations.py
@@ -1,7 +1,7 @@
-#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
 # Copyright (C) 2010, Nokia (ivan frade nokia com)
+# Copyright (C) 2019, Sam Thursfield (sam afuera me uk)
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -32,8 +32,8 @@ import locale
 import time
 
 import unittest as ut
-from common.utils.minertest import CommonTrackerMinerFTSTest, DEFAULT_TEXT
-from common.utils import configuration as cfg
+from minertest import CommonTrackerMinerFTSTest, DEFAULT_TEXT
+import configuration as cfg
 
 
 NFO_DOCUMENT = 'http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Document'
diff --git a/tests/functional-tests/312-fts-stopwords.py b/tests/functional-tests/312-fts-stopwords.py
index 6ba8ac591..b1e0eff51 100755
--- a/tests/functional-tests/312-fts-stopwords.py
+++ b/tests/functional-tests/312-fts-stopwords.py
@@ -1,7 +1,7 @@
-#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
 # Copyright (C) 2010, Nokia (ivan frade nokia com)
+# Copyright (C) 2019, Sam Thursfield (sam afuera me uk)
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -32,8 +32,8 @@ import locale
 import time
 
 import unittest as ut
-from common.utils.minertest import CommonTrackerMinerFTSTest, DEFAULT_TEXT
-from common.utils import configuration as cfg
+from minertest import CommonTrackerMinerFTSTest, DEFAULT_TEXT
+import configuration as cfg
 
 
 class MinerFTSStopwordsTest (CommonTrackerMinerFTSTest):
@@ -47,7 +47,8 @@ class MinerFTSStopwordsTest (CommonTrackerMinerFTSTest):
         if "_" in langcode:
             langcode = langcode.split("_")[0]
 
-        stopwordsfile = os.path.join(cfg.DATADIR, "tracker", "stop-words", "stopwords." + langcode)
+        stopwordsdir = os.environ['TRACKER_LANGUAGE_STOP_WORDS_DIR']
+        stopwordsfile = os.path.join(stopwordsdir, "stopwords." + langcode)
 
         if not os.path.exists(stopwordsfile):
             self.skipTest("No stopwords for the current locale ('%s' doesn't exist)" % (stopwordsfile))
@@ -55,13 +56,14 @@ class MinerFTSStopwordsTest (CommonTrackerMinerFTSTest):
 
         stopwords = []
         counter = 0
-        for line in open(stopwordsfile, "r"):
-            if len(line) > 4:
-                stopwords.append(line[:-1])
-                counter += 1
-
-            if counter > 5:
-                break
+        with open(stopwordsfile) as f:
+            for line in f:
+                if len(line) > 4:
+                    stopwords.append(line[:-1])
+                    counter += 1
+
+                if counter > 5:
+                    break
 
         return stopwords
 
diff --git a/tests/functional-tests/400-extractor-metadata.py 
b/tests/functional-tests/400-extractor-metadata.py
index 628b063de..5de4035b3 100755
--- a/tests/functional-tests/400-extractor-metadata.py
+++ b/tests/functional-tests/400-extractor-metadata.py
@@ -1,7 +1,6 @@
-#!/usr/bin/env python3
 #
 # Copyright (C) 2010, Nokia <ivan frade nokia com>
-# Copyright (C) 2018, Sam Thursfield <sam afuera me uk>
+# Copyright (C) 2018-2019, Sam Thursfield <sam afuera me uk>
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -24,12 +23,14 @@ metadata is extracted. Load dynamically the test information from a data
 directory (containing xxx.expected files)
 """
 
-from common.utils import configuration as cfg
-from common.utils.extractor import get_tracker_extract_jsonld_output, TrackerExtractTestCase
+import configuration as cfg
+from extractor import get_tracker_extract_jsonld_output, TrackerExtractTestCase
 import unittest as ut
 import json
 import os
+import shutil
 import sys
+import tempfile
 
 
 class GenericExtractionTestCase(TrackerExtractTestCase):
@@ -82,8 +83,13 @@ class GenericExtractionTestCase(TrackerExtractTestCase):
         filename_to_extract = self.spec['test']['Filename']
         self.file_to_extract = os.path.join(desc_root, filename_to_extract)
 
-        result = get_tracker_extract_jsonld_output(self.file_to_extract)
-        self.__assert_extraction_ok(result)
+        tmpdir = tempfile.mkdtemp(prefix='tracker-extract-test-')
+        try:
+            extra_env = cfg.test_environment(tmpdir)
+            result = get_tracker_extract_jsonld_output(extra_env, self.file_to_extract)
+            self.__assert_extraction_ok(result)
+        finally:
+            shutil.rmtree(tmpdir, ignore_errors=True)
 
     @ut.expectedFailure
     def expected_failure_test_extraction(self):
@@ -97,7 +103,7 @@ class GenericExtractionTestCase(TrackerExtractTestCase):
     def __assert_extraction_ok(self, result):
         try:
             self.assert_extract_result_matches_spec(self.spec['metadata'], result, self.file_to_extract, 
self.descfile)
-        except AssertionError as e:
+        except AssertionError:
             print("\ntracker-extract returned: %s" % json.dumps(result, indent=4))
             raise
 
diff --git a/tests/functional-tests/401-extractor-flac-cuesheet.py 
b/tests/functional-tests/401-extractor-flac-cuesheet.py
index 301a6868d..84386f494 100755
--- a/tests/functional-tests/401-extractor-flac-cuesheet.py
+++ b/tests/functional-tests/401-extractor-flac-cuesheet.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python3
-
 # Copyright (C) 2019, Sam Thursfield (sam afuera me uk)
 #
 # This library is free software; you can redistribute it and/or
@@ -27,8 +25,8 @@ import shutil
 import tempfile
 import unittest as ut
 
-import common.utils.configuration as cfg
-from common.utils.extractor import get_tracker_extract_jsonld_output, create_test_flac, 
TrackerExtractTestCase
+import configuration as cfg
+from extractor import get_tracker_extract_jsonld_output, create_test_flac, TrackerExtractTestCase
 
 
 class FlacCuesheetTest(TrackerExtractTestCase):
@@ -90,7 +88,8 @@ class FlacCuesheetTest(TrackerExtractTestCase):
             audio_path = os.path.join(tmpdir, 'cuesheet-test.flac')
             create_test_flac(audio_path, duration=6*60)
 
-            result = get_tracker_extract_jsonld_output(audio_path)
+            result = get_tracker_extract_jsonld_output(
+                cfg.test_environment(tmpdir), audio_path)
 
         self.assert_extract_result_matches_spec(
             self.spec(audio_path), result, audio_path, __file__)
diff --git a/tests/functional-tests/410-extractor-decorator.py 
b/tests/functional-tests/410-extractor-decorator.py
index 338e64fdb..fbf80dd40 100755
--- a/tests/functional-tests/410-extractor-decorator.py
+++ b/tests/functional-tests/410-extractor-decorator.py
@@ -1,6 +1,5 @@
-#!/usr/bin/env python3
-
 # Copyright (C) 2016, Sam Thursfield (sam afuera me uk)
+# Copyright (C) 2019, Sam Thursfield (sam afuera me uk)
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -21,17 +20,11 @@
 Tests failure cases of tracker-extract.
 """
 
-import unittest as ut
-
-from gi.repository import GLib
-
 import os
 import shutil
-import tempfile
-import time
+import unittest as ut
 
-import common.utils.configuration as cfg
-from common.utils.system import TrackerSystemAbstraction
+import minertest
 
 
 CORRUPT_FILE = os.path.join(
@@ -47,38 +40,14 @@ VALID_FILE_TITLE = 'Simply Juvenile'
 TRACKER_EXTRACT_FAILURE_DATA_SOURCE = 'tracker:extractor-failure-data-source'
 
 
-class ExtractorDecoratorTest(ut.TestCase):
-    def setUp(self):
-        self.datadir = cfg.create_monitored_test_dir()
-
-        config = {
-            cfg.DCONF_MINER_SCHEMA: {
-                'enable-writeback': GLib.Variant.new_boolean(False),
-                'index-recursive-directories': GLib.Variant.new_strv([]),
-                'index-single-directories': GLib.Variant.new_strv([self.datadir]),
-                'index-optical-discs': GLib.Variant.new_boolean(False),
-                'index-removable-devices': GLib.Variant.new_boolean(False),
-            },
-            'org.freedesktop.Tracker.Store': {
-                'graphupdated-delay': GLib.Variant('i', 100)
-            }
-        }
-
-        self.system = TrackerSystemAbstraction(config)
-        self.system.tracker_miner_fs_testing_start()
-
-    def tearDown(self):
-        self.system.finish()
-
-        cfg.remove_monitored_test_dir(self.datadir)
-
+class ExtractorDecoratorTest(minertest.CommonTrackerMinerTest):
     def test_reextraction(self):
         """Tests whether known files are still re-extracted on user request."""
-        miner_fs = self.system.miner_fs
-        store = self.system.store
+        miner_fs = self.miner_fs
+        store = self.tracker
 
         # Insert a valid file and wait extraction of its metadata.
-        file_path = os.path.join(self.datadir, os.path.basename(VALID_FILE))
+        file_path = os.path.join(self.indexed_dir, os.path.basename(VALID_FILE))
         shutil.copy(VALID_FILE, file_path)
         try:
             file_id, file_urn = store.await_resource_inserted(
@@ -94,7 +63,7 @@ class ExtractorDecoratorTest(ut.TestCase):
             assert not store.ask('ASK { <%s> nie:title ?title }' % file_urn)
 
             # Request re-indexing (same as `tracker index --file ...`)
-            miner_fs.index_file('file://' + os.path.join(self.datadir, file_path))
+            miner_fs.index_file('file://' + os.path.join(self.indexed_dir, file_path))
 
             # The extractor should reindex the file and re-add the metadata that we
             # deleted, so we should see the nie:title property change.
diff --git a/tests/functional-tests/500-writeback-images.py b/tests/functional-tests/500-writeback-images.py
index b9a5b080d..abf5194e9 100755
--- a/tests/functional-tests/500-writeback-images.py
+++ b/tests/functional-tests/500-writeback-images.py
@@ -1,6 +1,5 @@
-#!/usr/bin/env python3
-
 # Copyright (C) 2010, Nokia (ivan frade nokia com)
+# Copyright (C) 2019, Sam Thursfield (sam afuera me uk)
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -26,8 +25,8 @@ import os
 import sys
 import time
 
-from common.utils.extractor import get_tracker_extract_jsonld_output
-from common.utils.writebacktest import CommonTrackerWritebackTest
+from extractor import get_tracker_extract_jsonld_output
+from writebacktest import CommonTrackerWritebackTest
 import unittest as ut
 
 log = logging.getLogger(__name__)
@@ -67,7 +66,7 @@ class WritebackImagesTest (CommonTrackerWritebackTest):
         self.wait_for_file_change(path, initial_mtime)
         log.debug("Got the change")
 
-        results = get_tracker_extract_jsonld_output(path, mimetype)
+        results = get_tracker_extract_jsonld_output(self.extra_env, path, mimetype)
         keyDict = expectedKey or prop
         self.assertIn(TEST_VALUE, results[keyDict])
 
@@ -97,7 +96,7 @@ class WritebackImagesTest (CommonTrackerWritebackTest):
 
         time.sleep(REASONABLE_TIMEOUT)
 
-        results = get_tracker_extract_jsonld_output(filename, mimetype)
+        results = get_tracker_extract_jsonld_output(self.extra_env, filename, mimetype)
         self.assertIn("testTag", results["nao:hasTag"])
 
     # JPEG test
diff --git a/tests/functional-tests/501-writeback-image-details.py 
b/tests/functional-tests/501-writeback-image-details.py
index cfe416b72..cdbc22f67 100755
--- a/tests/functional-tests/501-writeback-image-details.py
+++ b/tests/functional-tests/501-writeback-image-details.py
@@ -1,6 +1,5 @@
-#!/usr/bin/env python3
-
 # Copyright (C) 2011, Nokia (ivan frade nokia com)
+# Copyright (C) 2019, Sam Thursfield (sam afuera me uk)
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -24,8 +23,8 @@ import sys
 import time
 import unittest as ut
 
-from common.utils.writebacktest import CommonTrackerWritebackTest as CommonTrackerWritebackTest
-from common.utils.extractor import get_tracker_extract_jsonld_output
+from writebacktest import CommonTrackerWritebackTest as CommonTrackerWritebackTest
+from extractor import get_tracker_extract_jsonld_output
 
 
 log = logging.getLogger(__name__)
@@ -89,7 +88,7 @@ class WritebackKeepDateTest (CommonTrackerWritebackTest):
         self.wait_for_file_change(jpeg_path, initial_mtime)
 
         # Check the value is written in the file
-        metadata = get_tracker_extract_jsonld_output(jpeg_path, "")
+        metadata = get_tracker_extract_jsonld_output(self.extra_env, jpeg_path, "")
 
         tags = metadata.get('nao:hasTag', [])
         tag_names = [tag['nao:prefLabel'] for tag in tags]
diff --git a/tests/functional-tests/502-writeback-audio.py b/tests/functional-tests/502-writeback-audio.py
index 92b31bed3..b157b28cf 100755
--- a/tests/functional-tests/502-writeback-audio.py
+++ b/tests/functional-tests/502-writeback-audio.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python3
-
 # Copyright (C) 2019, Sam Thursfield (sam afuera me uk)
 #
 # This library is free software; you can redistribute it and/or
@@ -20,8 +18,8 @@
 
 import unittest
 
-from common.utils.extractor import get_tracker_extract_jsonld_output
-from common.utils.writebacktest import CommonTrackerWritebackTest
+from extractor import get_tracker_extract_jsonld_output
+from writebacktest import CommonTrackerWritebackTest
 
 
 class WritebackAudioTest(CommonTrackerWritebackTest):
@@ -41,7 +39,7 @@ class WritebackAudioTest(CommonTrackerWritebackTest):
 
         self.wait_for_file_change(path, initial_mtime)
 
-        results = get_tracker_extract_jsonld_output(path)
+        results = get_tracker_extract_jsonld_output(self.extra_env, path)
         self.assertIn(TEST_VALUE, results[prop])
 
     def test_writeback_mp3(self):
diff --git a/tests/functional-tests/600-applications-camera.py 
b/tests/functional-tests/600-applications-camera.py
index c5322d06c..907a2f7b9 100755
--- a/tests/functional-tests/600-applications-camera.py
+++ b/tests/functional-tests/600-applications-camera.py
@@ -1,6 +1,5 @@
-#!/usr/bin/env python3
-#
 # Copyright (C) 2011, Nokia Corporation <ivan frade nokia com>
+# Copyright (C) 2019, Sam Thursfield (sam afuera me uk)
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -27,7 +26,7 @@ import os
 import random
 
 import unittest as ut
-from common.utils.applicationstest import CommonTrackerApplicationTest as CommonTrackerApplicationTest
+from applicationstest import CommonTrackerApplicationTest as CommonTrackerApplicationTest
 
 
 log = logging.getLogger(__name__)
diff --git a/tests/functional-tests/601-applications-sync.py b/tests/functional-tests/601-applications-sync.py
index c0000deca..092971117 100755
--- a/tests/functional-tests/601-applications-sync.py
+++ b/tests/functional-tests/601-applications-sync.py
@@ -1,6 +1,5 @@
-#!/usr/bin/env python3
-#
 # Copyright (C) 2011, Nokia Corporation <ivan frade nokia com>
+# Copyright (C) 2019, Sam Thursfield (sam afuera me uk)
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -28,7 +27,7 @@ import random
 import shutil
 
 import unittest as ut
-from common.utils.applicationstest import CommonTrackerApplicationTest as CommonTrackerApplicationTest
+from applicationstest import CommonTrackerApplicationTest as CommonTrackerApplicationTest
 
 
 log = logging.getLogger(__name__)
@@ -61,7 +60,7 @@ class TrackerSyncApplicationTests (CommonTrackerApplicationTest):
         This is because the test already inserted the resource in the store.
         """
 
-        self.system.miner_fs.await_wakeup_count(1)
+        self.miner_fs.start_watching_progress()
 
         origin_filepath = os.path.join(self.get_data_dir(), self.get_test_music())
         dest_filepath = os.path.join(self.get_dest_dir(), self.get_test_music())
@@ -104,13 +103,13 @@ class TrackerSyncApplicationTests (CommonTrackerApplicationTest):
 
         resource_id = self.tracker.get_resource_id(dest_fileuri)
 
-        miner_wakeup_count = self.system.miner_fs.wakeup_count()
+        miner_wakeup_count = self.miner_fs.wakeup_count()
 
         # Copy the image to the dest path
         self.slowcopy_file(origin_filepath, dest_filepath)
         assert os.path.exists(dest_filepath)
 
-        self.system.miner_fs.await_wakeup_count(miner_wakeup_count + 1)
+        self.miner_fs.await_wakeup_count(miner_wakeup_count + 1)
 
         self.assertEqual(self.get_urn_count_by_url(dest_fileuri), 1)
 
@@ -120,6 +119,8 @@ class TrackerSyncApplicationTests (CommonTrackerApplicationTest):
         self.tracker.await_resource_deleted(NMM_MUSICPIECE, resource_id)
         self.assertEqual(self.get_urn_count_by_url(dest_fileuri), 0)
 
+        self.miner_fs.stop_watching_progress()
+
 
 if __name__ == "__main__":
     ut.main()
diff --git a/tests/functional-tests/__init__.py b/tests/functional-tests/__init__.py
index e5a0d9b48..e69de29bb 100644
--- a/tests/functional-tests/__init__.py
+++ b/tests/functional-tests/__init__.py
@@ -1 +0,0 @@
-#!/usr/bin/env python3
diff --git a/tests/functional-tests/common/utils/applicationstest.py 
b/tests/functional-tests/applicationstest.py
similarity index 71%
rename from tests/functional-tests/common/utils/applicationstest.py
rename to tests/functional-tests/applicationstest.py
index acdb6f015..8b2322e9b 100644
--- a/tests/functional-tests/common/utils/applicationstest.py
+++ b/tests/functional-tests/applicationstest.py
@@ -1,6 +1,6 @@
-#!/usr/bin/env python3
 #
 # Copyright (C) 2010, Nokia <ivan frade nokia com>
+# Copyright (C) 2019, Sam Thursfield (sam afuera me uk)
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -17,24 +17,21 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301, USA.
 #
-from common.utils import configuration as cfg
-from common.utils.system import TrackerSystemAbstraction
-import unittest as ut
-
-from gi.repository import GLib
 
 import logging
 import os
-import shutil
 import time
 
+import configuration as cfg
+from writebacktest import CommonTrackerWritebackTest
+
 # Copy rate, 10KBps (1024b/100ms)
 SLOWCOPY_RATE = 1024
 
 log = logging.getLogger(__name__)
 
 
-class CommonTrackerApplicationTest (ut.TestCase):
+class CommonTrackerApplicationTest (CommonTrackerWritebackTest):
 
     def get_urn_count_by_url(self, url):
         select = """
@@ -58,7 +55,7 @@ class CommonTrackerApplicationTest (ut.TestCase):
         return self.datadir
 
     def get_dest_dir(self):
-        return self.workdir
+        return self.indexed_dir
 
     def slowcopy_file_fd(self, src, fdest, rate=SLOWCOPY_RATE):
         """
@@ -81,21 +78,7 @@ class CommonTrackerApplicationTest (ut.TestCase):
         self.slowcopy_file_fd(src, fdest, rate)
         fdest.close()
 
-    @classmethod
     def setUp(self):
-        self.workdir = cfg.create_monitored_test_dir()
-
-        index_dirs = [self.workdir]
-
-        CONF_OPTIONS = {
-            cfg.DCONF_MINER_SCHEMA: {
-                'index-recursive-directories': GLib.Variant.new_strv(index_dirs),
-                'index-single-directories': GLib.Variant.new_strv([]),
-                'index-optical-discs': GLib.Variant.new_boolean(False),
-                'index-removable-devices': GLib.Variant.new_boolean(False),
-            }
-        }
-
         # Use local directory if available. Installation otherwise.
         if os.path.exists(os.path.join(os.getcwd(),
                                        "test-apps-data")):
@@ -106,12 +89,5 @@ class CommonTrackerApplicationTest (ut.TestCase):
                                         "tracker-tests",
                                         "test-apps-data")
 
-        self.system = TrackerSystemAbstraction()
-        self.system.tracker_all_testing_start(CONF_OPTIONS)
-        self.tracker = self.system.store
-
-    @classmethod
-    def tearDown(self):
-        self.system.finish()
 
-        cfg.remove_monitored_test_dir(self.workdir)
+        super(CommonTrackerApplicationTest, self).setUp()
diff --git a/tests/functional-tests/configuration.json.in b/tests/functional-tests/configuration.json.in
index ff8b0b58e..dd0adc22e 100644
--- a/tests/functional-tests/configuration.json.in
+++ b/tests/functional-tests/configuration.json.in
@@ -1,9 +1,12 @@
 {
-    "PREFIX": "@prefix@",
-    "RAW_EXEC_PREFIX": "@exec_prefix@",
-    "RAW_DATAROOT_DIR": "@datarootdir@",
-    "TRACKER_EXTRACT_PATH": "@FUNCTIONAL_TESTS_TRACKER_EXTRACT_PATH@",
-    "TRACKER_MINER_FS_PATH": "@FUNCTIONAL_TESTS_TRACKER_MINER_FS_PATH@",
-    "TRACKER_STORE_PATH": "@FUNCTIONAL_TESTS_TRACKER_STORE_PATH@",
-    "TRACKER_WRITEBACK_PATH": "@FUNCTIONAL_TESTS_TRACKER_WRITEBACK_PATH@"
+    "TEST_DBUS_DAEMON_CONFIG_FILE": "@TEST_DBUS_DAEMON_CONFIG_FILE@",
+    "TEST_DCONF_PROFILE": "@TEST_DCONF_PROFILE@",
+    "TEST_DOMAIN_ONTOLOGY_RULE": "@TEST_DOMAIN_ONTOLOGY_RULE@",
+    "TEST_EXTRACTOR_RULES_DIR": "@TEST_EXTRACTOR_RULES_DIR@",
+    "TEST_EXTRACTORS_DIR": "@TEST_EXTRACTORS_DIR@",
+    "TEST_GSETTINGS_SCHEMA_DIR": "@TEST_GSETTINGS_SCHEMA_DIR@",
+    "TEST_LANGUAGE_STOP_WORDS_DIR": "@TEST_LANGUAGE_STOP_WORDS_DIR@",
+    "TEST_ONTOLOGIES_DIR": "@TEST_ONTOLOGIES_DIR@",
+    "TEST_WRITEBACK_MODULES_DIR": "@TEST_WRITEBACK_MODULES_DIR@",
+    "TRACKER_EXTRACT_PATH": "@TRACKER_EXTRACT_PATH@"
 }
diff --git a/tests/functional-tests/common/utils/configuration.py b/tests/functional-tests/configuration.py
similarity index 50%
rename from tests/functional-tests/common/utils/configuration.py
rename to tests/functional-tests/configuration.py
index 28a006e10..514f44801 100644
--- a/tests/functional-tests/common/utils/configuration.py
+++ b/tests/functional-tests/configuration.py
@@ -1,6 +1,6 @@
-#!/usr/bin/env python3
 #
 # Copyright (C) 2010, Nokia <jean-luc lamadon nokia com>
+# Copyright (C) 2019, Sam Thursfield (sam afuera me uk)
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -18,17 +18,15 @@
 # 02110-1301, USA.
 #
 
-"Constants describing Tracker D-Bus services"
 
 import errno
 import json
 import logging
 import os
+import shutil
 import tempfile
 import sys
 
-from . import options
-
 
 if 'TRACKER_FUNCTIONAL_TEST_CONFIG' not in os.environ:
     raise RuntimeError("The TRACKER_FUNCTIONAL_TEST_CONFIG environment "
@@ -38,64 +36,26 @@ if 'TRACKER_FUNCTIONAL_TEST_CONFIG' not in os.environ:
 with open(os.environ['TRACKER_FUNCTIONAL_TEST_CONFIG']) as f:
     config = json.load(f)
 
-TRACKER_BUSNAME = 'org.freedesktop.Tracker1'
-TRACKER_OBJ_PATH = '/org/freedesktop/Tracker1/Resources'
-RESOURCES_IFACE = "org.freedesktop.Tracker1.Resources"
-
-MINERFS_BUSNAME = "org.freedesktop.Tracker1.Miner.Files"
-MINERFS_OBJ_PATH = "/org/freedesktop/Tracker1/Miner/Files"
-MINER_IFACE = "org.freedesktop.Tracker1.Miner"
-MINERFS_INDEX_OBJ_PATH = "/org/freedesktop/Tracker1/Miner/Files/Index"
-MINER_INDEX_IFACE = "org.freedesktop.Tracker1.Miner.Files.Index"
-
-TRACKER_BACKUP_OBJ_PATH = "/org/freedesktop/Tracker1/Backup"
-BACKUP_IFACE = "org.freedesktop.Tracker1.Backup"
-
-TRACKER_STATS_OBJ_PATH = "/org/freedesktop/Tracker1/Statistics"
-STATS_IFACE = "org.freedesktop.Tracker1.Statistics"
-
-TRACKER_STATUS_OBJ_PATH = "/org/freedesktop/Tracker1/Status"
-STATUS_IFACE = "org.freedesktop.Tracker1.Status"
-
-TRACKER_EXTRACT_BUSNAME = "org.freedesktop.Tracker1.Miner.Extract"
-TRACKER_EXTRACT_OBJ_PATH = "/org/freedesktop/Tracker1/Miner/Extract"
-
-WRITEBACK_BUSNAME = "org.freedesktop.Tracker1.Writeback"
-
-
-DCONF_MINER_SCHEMA = "org.freedesktop.Tracker.Miner.Files"
-
-# Autoconf substitutes paths in the configuration.json file without
-# expanding variables, so we need to manually insert these.
-
-
-def expandvars(variable):
-    # Note: the order matters!
-    result = variable
-    for var, value in [("${datarootdir}", RAW_DATAROOT_DIR),
-                       ("${exec_prefix}", RAW_EXEC_PREFIX),
-                       ("${prefix}", PREFIX),
-                       ("@top_builddir@", TOP_BUILDDIR)]:
-        result = result.replace(var, value)
-
-    return result
 
+TEST_DBUS_DAEMON_CONFIG_FILE = config['TEST_DBUS_DAEMON_CONFIG_FILE']
+TRACKER_EXTRACT_PATH = config['TRACKER_EXTRACT_PATH']
 
-PREFIX = config['PREFIX']
-RAW_EXEC_PREFIX = config['RAW_EXEC_PREFIX']
-RAW_DATAROOT_DIR = config['RAW_DATAROOT_DIR']
-TOP_BUILDDIR = os.environ['TRACKER_FUNCTIONAL_TEST_BUILD_DIR']
 
-TRACKER_EXTRACT_PATH = os.path.normpath(expandvars(config['TRACKER_EXTRACT_PATH']))
-TRACKER_MINER_FS_PATH = os.path.normpath(expandvars(config['TRACKER_MINER_FS_PATH']))
-TRACKER_STORE_PATH = os.path.normpath(expandvars(config['TRACKER_STORE_PATH']))
-TRACKER_WRITEBACK_PATH = os.path.normpath(expandvars(config['TRACKER_WRITEBACK_PATH']))
-
-DATADIR = os.path.normpath(expandvars(config['RAW_DATAROOT_DIR']))
-
-
-def generated_ttl_dir():
-    return os.path.join(TOP_BUILD_DIR, 'tests', 'functional-tests', 'ttl')
+def test_environment(tmpdir):
+    return {
+        'DCONF_PROFILE': config['TEST_DCONF_PROFILE'],
+        'TRACKER_TEST_DOMAIN_ONTOLOGY_RULE': config['TEST_DOMAIN_ONTOLOGY_RULE'],
+        'TRACKER_EXTRACTOR_RULES_DIR': config['TEST_EXTRACTOR_RULES_DIR'],
+        'TRACKER_EXTRACTORS_DIR': config['TEST_EXTRACTORS_DIR'],
+        'GSETTINGS_SCHEMA_DIR': config['TEST_GSETTINGS_SCHEMA_DIR'],
+        'TRACKER_LANGUAGE_STOP_WORDS_DIR': config['TEST_LANGUAGE_STOP_WORDS_DIR'],
+        'TRACKER_DB_ONTOLOGIES_DIR': config['TEST_ONTOLOGIES_DIR'],
+        'TRACKER_WRITEBACK_MODULES_DIR': config['TEST_WRITEBACK_MODULES_DIR'],
+        'XDG_CACHE_HOME': os.path.join(tmpdir, 'cache'),
+        'XDG_CONFIG_HOME': os.path.join(tmpdir, 'config'),
+        'XDG_DATA_HOME': os.path.join(tmpdir, 'data'),
+        'XDG_RUNTIME_DIR': os.path.join(tmpdir, 'run'),
+    }
 
 
 # This path is used for test data for tests which expect filesystem monitoring
@@ -116,19 +76,12 @@ if _TEST_MONITORED_TMP_DIR.startswith('/tmp'):
 
 def create_monitored_test_dir():
     '''Returns a unique tmpdir which supports filesystem monitor events.'''
-    try:
-        os.makedirs(_TEST_MONITORED_TMP_DIR)
-    except OSError as e:
-        if e.errno == errno.EEXIST:
-            pass
-        else:
-            raise
+    os.makedirs(_TEST_MONITORED_TMP_DIR, exist_ok=True)
     return tempfile.mkdtemp(dir=_TEST_MONITORED_TMP_DIR)
 
 
 def remove_monitored_test_dir(path):
-    # This will fail if the directory is not empty.
-    os.rmdir(path)
+    shutil.rmtree(path)
 
     # We delete the parent directory if possible, to avoid cluttering the user's
     # home dir, but there may be other tests running in parallel so we ignore
@@ -140,5 +93,37 @@ def remove_monitored_test_dir(path):
             pass
 
 
-if options.get_environment_boolean('TRACKER_TESTS_VERBOSE'):
+def get_environment_boolean(variable):
+    '''Parse a yes/no boolean passed through the environment.'''
+
+    value = os.environ.get(variable, 'no').lower()
+    if value in ['no', '0', 'false']:
+        return False
+    elif value in ['yes', '1', 'true']:
+        return True
+    else:
+        raise RuntimeError('Unexpected value for %s: %s' %
+                           (variable, value))
+
+
+def get_environment_int(variable, default=0):
+    try:
+        return int(os.environ.get(variable))
+    except (TypeError, ValueError):
+        return default
+
+
+if get_environment_boolean('TRACKER_TESTS_VERBOSE'):
+    # Output all logs to stderr
     logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+else:
+    # Output some messages from D-Bus daemon to stderr by default. In practice,
+    # only errors and warnings should be output here unless the environment
+    # contains G_MESSAGES_DEBUG= and/or TRACKER_VERBOSITY=1 or more.
+    handler_stderr = logging.StreamHandler(stream=sys.stderr)
+    handler_stderr.addFilter(logging.Filter('trackertestutils.dbusdaemon.stderr'))
+    handler_stdout = logging.StreamHandler(stream=sys.stderr)
+    handler_stdout.addFilter(logging.Filter('trackertestutils.dbusdaemon.stdout'))
+    logging.basicConfig(level=logging.INFO,
+                        handlers=[handler_stderr, handler_stdout],
+                        format='%(message)s')
diff --git a/tests/functional-tests/common/utils/extractor.py b/tests/functional-tests/extractor.py
similarity index 95%
rename from tests/functional-tests/common/utils/extractor.py
rename to tests/functional-tests/extractor.py
index 5a66c6117..9f692652d 100644
--- a/tests/functional-tests/common/utils/extractor.py
+++ b/tests/functional-tests/extractor.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python3
 #
 # Copyright (C) 2010, Nokia <ivan frade nokia com>
 # Copyright (C) 2018-2019, Sam Thursfield <sam afuera me uk>
@@ -19,7 +18,7 @@
 # 02110-1301, USA.
 #
 
-from common.utils import configuration as cfg
+import configuration as cfg
 import errno
 import json
 import logging
@@ -36,7 +35,7 @@ from gi.repository import GLib, Gst
 log = logging.getLogger(__name__)
 
 
-def get_tracker_extract_jsonld_output(filename, mime_type=None):
+def get_tracker_extract_jsonld_output(extra_env, filename, mime_type=None):
     """
     Runs `tracker-extract --file` to extract metadata from a file.
     """
@@ -47,10 +46,13 @@ def get_tracker_extract_jsonld_output(filename, mime_type=None):
         command.extend(['--mime', mime_type])
 
     # We depend on parsing the output, so verbosity MUST be 0.
-    env = os.environ.copy()
-    env['TRACKER_VERBOSITY'] = '0'
+    extra_env['TRACKER_VERBOSITY'] = '0'
     # Tell GStreamer not to fork to create the registry
-    env['GST_REGISTRY_FORK'] = 'no'
+    extra_env['GST_REGISTRY_FORK'] = 'no'
+    log.debug('Adding to environment: %s', ' '.join('%s=%s' % (k, v) for k, v in extra_env.items()))
+
+    env = os.environ.copy()
+    env.update(extra_env)
 
     log.debug('Running: %s', ' '.join(command))
     try:
@@ -65,10 +67,10 @@ def get_tracker_extract_jsonld_output(filename, mime_type=None):
     if p.returncode != 0:
         raise RuntimeError(
             "tracker-extract returned non-zero exit code: %s\n"
-            "Error output:\n%s\n" % (p.returncode, stderr.decode('unicode-escape').strip()))
+            "Error output:\n%s\n" % (p.returncode, stderr.decode('utf-8').strip()))
 
     if len(stderr) > 0:
-        error_output = stderr.decode('unicode-escape').strip()
+        error_output = stderr.decode('utf-8').strip()
         log.debug("Error output from tracker-extract:\n%s", error_output)
 
     try:
@@ -91,6 +93,9 @@ class TrackerExtractTestCase(ut.TestCase):
         if not isinstance(d, dict):
             self.fail("Expected dict, got %s" % d)
         if key not in d:
+            import pdb
+            pdb.set_trace()
+
             standardMsg = "Missing: %s" % (key)
             self.fail(self._formatMessage(msg, standardMsg))
         else:
diff --git a/tests/functional-tests/meson.build b/tests/functional-tests/meson.build
index d3c4bd3ce..02612509d 100644
--- a/tests/functional-tests/meson.build
+++ b/tests/functional-tests/meson.build
@@ -1,8 +1,28 @@
-test_runner = configure_file(
-    input: 'test-runner.sh.in',
-    output: 'test-runner.sh',
-    configuration: conf)
-test_runner = find_program(test_runner)
+python = find_program('python3')
+
+# Configure functional tests to run completely from source tree.
+testconf = configuration_data()
+
+config_json_full_path = join_paths(meson.current_build_dir(), 'configuration.json')
+dconf_profile_full_path = join_paths(meson.current_source_dir(), 'trackertest')
+tracker_extractors_dir = join_paths(meson.current_build_dir(), '..', '..', 'src', 'tracker-extract')
+
+testconf.set('TEST_DBUS_DAEMON_CONFIG_FILE', join_paths(build_root, 'tests', 'test-bus.conf'))
+testconf.set('TEST_DCONF_PROFILE', dconf_profile_full_path)
+testconf.set('TEST_DOMAIN_ONTOLOGY_RULE', tracker_uninstalled_domain_rule)
+testconf.set('TEST_EXTRACTOR_RULES_DIR', tracker_uninstalled_extract_rules_dir)
+testconf.set('TEST_EXTRACTORS_DIR', tracker_extractors_dir)
+testconf.set('TEST_GSETTINGS_SCHEMA_DIR', tracker_miners_uninstalled_gsettings_schema_dir)
+testconf.set('TEST_LANGUAGE_STOP_WORDS_DIR', tracker_uninstalled_stop_words_dir)
+testconf.set('TEST_ONTOLOGIES_DIR', tracker_uninstalled_nepomuk_ontologies_dir)
+testconf.set('TEST_WRITEBACK_MODULES_DIR', tracker_uninstalled_writeback_modules_dir)
+testconf.set('TRACKER_EXTRACT_PATH', uninstalled_tracker_extract_path)
+
+config_json = configure_file(
+  input: 'configuration.json.in',
+  output: 'configuration.json',
+  configuration: testconf
+)
 
 extractor_tests = [
   'audio/audio-test-1',
@@ -46,12 +66,6 @@ functional_tests = [
   '601-applications-sync',
 ]
 
-config_json = configure_file(
-  input: 'configuration.json.in',
-  output: 'configuration.json',
-  configuration: conf
-)
-
 detect_h264_codec = join_paths(meson.current_source_dir(), 'detect-h264-codec.sh')
 detect_h264_codec_result = run_command(detect_h264_codec)
 
@@ -67,35 +81,22 @@ else
   warning('No GStreamer h264 codec was detected. Some extractor tests will be disabled.')
 endif
 
-config_json_full_path = join_paths(meson.current_build_dir(), 'configuration.json')
-dconf_profile_full_path = join_paths(meson.current_source_dir(), 'trackertest')
-tracker_extractors_dir = join_paths(meson.current_build_dir(), '..', '..', 'src', 'tracker-extract')
-
 test_env = environment()
-test_env.set('DCONF_PROFILE', dconf_profile_full_path)
-test_env.set('GSETTINGS_SCHEMA_DIR', tracker_miners_uninstalled_gsettings_schema_dir)
-
-test_env.set('TRACKER_DB_ONTOLOGIES_DIR', tracker_uninstalled_nepomuk_ontologies_dir)
-test_env.set('TRACKER_EXTRACTORS_DIR', tracker_extractors_dir)
-test_env.set('TRACKER_EXTRACTOR_RULES_DIR', tracker_uninstalled_extract_rules_dir)
-test_env.set('TRACKER_LANGUAGE_STOP_WORDS_DIR', tracker_uninstalled_stop_words_dir)
-test_env.set('TRACKER_FUNCTIONAL_TEST_BUILD_DIR', meson.build_root())
+test_env.prepend('PYTHONPATH', tracker_uninstalled_testutils_dir)
 test_env.set('TRACKER_FUNCTIONAL_TEST_CONFIG', config_json_full_path)
-test_env.set('TRACKER_TEST_DOMAIN_ONTOLOGY_RULE', tracker_uninstalled_domain_rule)
-test_env.set('TRACKER_WRITEBACK_MODULES_DIR', tracker_uninstalled_writeback_modules_dir)
-test_env.set('TRACKER_TESTS_VERBOSE', '1')
 
 foreach t: extractor_tests
   data = join_paths('test-extraction-data', t) + '.expected.json'
-  test('functional/extract/' + t, test_runner,
-    args: ['./400-extractor-metadata.py', data],
+  test('functional/extract/' + t, python,
+    args: ['400-extractor-metadata.py', data],
     env: test_env,
     workdir: meson.current_source_dir())
 endforeach
 
 foreach t: functional_tests
-  test('functional-' + t, test_runner,
-    args: './' + t + '.py',
+  file = '@0@.py'.format(t)
+  test('functional-' + t, python,
+    args: [file],
     env: test_env,
     workdir: meson.current_source_dir(),
     timeout: 120)
diff --git a/tests/functional-tests/minerfshelper.py b/tests/functional-tests/minerfshelper.py
new file mode 100644
index 000000000..4dd6cf326
--- /dev/null
+++ b/tests/functional-tests/minerfshelper.py
@@ -0,0 +1,132 @@
+#
+# Copyright (C) 2010, Nokia <ivan frade nokia com>
+# Copyright (C) 2018, Sam Thursfield <sam afuera me uk>
+#
+# 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; either version 2
+# of the License, or (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+import logging
+
+from gi.repository import Gio
+from gi.repository import GLib
+
+import trackertestutils.mainloop
+
+
+log = logging.getLogger(__name__)
+
+REASONABLE_TIMEOUT = 5
+
+class WakeupCycleTimeoutException(RuntimeError):
+    pass
+
+
+class MinerFsHelper ():
+
+    MINERFS_BUSNAME = "org.freedesktop.Tracker1.Miner.Files"
+    MINERFS_OBJ_PATH = "/org/freedesktop/Tracker1/Miner/Files"
+    MINER_IFACE = "org.freedesktop.Tracker1.Miner"
+    MINERFS_INDEX_OBJ_PATH = "/org/freedesktop/Tracker1/Miner/Files/Index"
+    MINER_INDEX_IFACE = "org.freedesktop.Tracker1.Miner.Files.Index"
+
+    def __init__(self, dbus_connection):
+        self.log = logging.getLogger(__name__)
+
+        self.bus = dbus_connection
+
+        self.loop = trackertestutils.mainloop.MainLoop()
+
+        self.miner_fs = Gio.DBusProxy.new_sync(
+            self.bus, Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION, None,
+            self.MINERFS_BUSNAME, self.MINERFS_OBJ_PATH, self.MINER_IFACE)
+
+        self.index = Gio.DBusProxy.new_sync(
+            self.bus, Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION, None,
+            self.MINERFS_BUSNAME, self.MINERFS_INDEX_OBJ_PATH, self.MINER_INDEX_IFACE)
+
+    def start(self):
+        self.miner_fs.Start()
+
+    def stop(self):
+        self.miner_fs.Stop()
+
+    def start_watching_progress(self):
+        self._previous_status = None
+        self._target_wakeup_count = None
+        self._wakeup_count = 0
+
+        def signal_handler(proxy, sender_name, signal_name, parameters):
+            if signal_name == 'Progress':
+                self._progress_cb(*parameters.unpack())
+
+        self._progress_handler_id = self.miner_fs.connect('g-signal', signal_handler)
+
+    def stop_watching_progress(self):
+        if self._progress_handler_id != 0:
+            self.miner_fs.disconnect(self._progress_handler_id)
+
+    def _progress_cb(self, status, progress, remaining_time):
+        if self._previous_status is None:
+            self._previous_status = status
+        if self._previous_status != 'Idle' and status == 'Idle':
+            self._wakeup_count += 1
+
+        if self._target_wakeup_count is not None and self._wakeup_count >= self._target_wakeup_count:
+            self.loop.quit()
+
+    def wakeup_count(self):
+        """Return the number of wakeup-to-idle cycles the miner-fs completed."""
+        return self._wakeup_count
+
+    def await_wakeup_count(self, target_wakeup_count, timeout=REASONABLE_TIMEOUT):
+        """Block until the miner has completed N wakeup-and-idle cycles.
+
+        This function is for use by miner-fs tests that should trigger an
+        operation in the miner, but which do not cause a new resource to be
+        inserted. These tests can instead wait for the status to change from
+        Idle to Processing... and then back to Idle.
+
+        The miner may change its status any number of times, but you can use
+        this function reliably as follows:
+
+            wakeup_count = miner_fs.wakeup_count()
+            # Trigger a miner-fs operation somehow ...
+            miner_fs.await_wakeup_count(wakeup_count + 1)
+            # The miner has probably finished processing the operation now.
+
+        If the timeout is reached before enough wakeup cycles complete, an
+        exception will be raised.
+
+        """
+
+        assert self._target_wakeup_count is None
+
+        if self._wakeup_count >= target_wakeup_count:
+            log.debug("miner-fs wakeup count is at %s (target is %s). No need to wait", self._wakeup_count, 
target_wakeup_count)
+        else:
+            def _timeout_cb():
+                raise WakeupCycleTimeoutException()
+            timeout_id = GLib.timeout_add_seconds(timeout, _timeout_cb)
+
+            log.debug("Waiting for miner-fs wakeup count of %s (currently %s)", target_wakeup_count, 
self._wakeup_count)
+            self._target_wakeup_count = target_wakeup_count
+            self.loop.run_checked()
+
+            self._target_wakeup_count = None
+            GLib.source_remove(timeout_id)
+
+    def index_file(self, uri):
+        return self.index.IndexFile('(s)', uri)
diff --git a/tests/functional-tests/common/utils/minertest.py b/tests/functional-tests/minertest.py
similarity index 68%
rename from tests/functional-tests/common/utils/minertest.py
rename to tests/functional-tests/minertest.py
index 650c03eb1..f516a945a 100644
--- a/tests/functional-tests/common/utils/minertest.py
+++ b/tests/functional-tests/minertest.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python3
 #
 # Copyright (C) 2010, Nokia <ivan frade nokia com>
 # Copyright (C) 2018, Sam Thursfield <sam afuera me uk>
@@ -18,8 +17,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301, USA.
 #
-from common.utils import configuration as cfg
-from common.utils.system import TrackerSystemAbstraction
+import configuration as cfg
 import unittest as ut
 
 from gi.repository import GLib
@@ -27,9 +25,12 @@ from gi.repository import GLib
 import logging
 import os
 import shutil
-import tempfile
 from itertools import chain
 
+import trackertestutils.dconf
+import trackertestutils.helpers
+
+from minerfshelper import MinerFsHelper
 
 DEFAULT_TEXT = "Some stupid content, to have a test file"
 
@@ -37,13 +38,15 @@ NFO_DOCUMENT = 'http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Documen
 
 log = logging.getLogger(__name__)
 
+REASONABLE_TIMEOUT = 5
+
 
 def ensure_dir_exists(dirname):
     if not os.path.exists(dirname):
         os.makedirs(dirname)
 
 
-class CommonTrackerMinerTest (ut.TestCase):
+class CommonTrackerMinerTest(ut.TestCase):
     def setUp(self):
         self.workdir = cfg.create_monitored_test_dir()
 
@@ -55,40 +58,60 @@ class CommonTrackerMinerTest (ut.TestCase):
         # function.
         ensure_dir_exists(self.indexed_dir)
 
-        self.system = TrackerSystemAbstraction(
-            settings={
-                'org.freedesktop.Tracker.Store': {
-                    'graphupdated-delay': GLib.Variant('i', 100)
-                }
-            }
-        )
-
-        config = {
-            cfg.DCONF_MINER_SCHEMA: {
-                'index-recursive-directories': GLib.Variant.new_strv([self.indexed_dir]),
-                'index-single-directories': GLib.Variant.new_strv([]),
-                'index-optical-discs': GLib.Variant.new_boolean(False),
-                'index-removable-devices': GLib.Variant.new_boolean(False),
-                'throttle': GLib.Variant.new_int32(5),
-            }
-        }
-
         try:
-            self.system.tracker_miner_fs_testing_start(config)
-        except RuntimeError as e:
-            self.fail(e)
-
-        self.tracker = self.system.store
+            extra_env = cfg.test_environment(self.workdir)
+
+            self.sandbox = trackertestutils.helpers.TrackerDBusSandbox(
+                dbus_daemon_config_file=cfg.TEST_DBUS_DAEMON_CONFIG_FILE, extra_env=extra_env)
+
+            self.sandbox.start()
+
+            try:
+                settings = {
+                    'org.freedesktop.Tracker.Store': {
+                        'graphupdated-delay': GLib.Variant('i', 100)
+                    },
+                    'org.freedesktop.Tracker.Miner.Files': {
+                        'enable-writeback': GLib.Variant.new_boolean(False),
+                        'index-recursive-directories': GLib.Variant.new_strv([self.indexed_dir]),
+                        'index-single-directories': GLib.Variant.new_strv([]),
+                        'index-optical-discs': GLib.Variant.new_boolean(False),
+                        'index-removable-devices': GLib.Variant.new_boolean(False),
+                        'throttle': GLib.Variant.new_int32(5),
+                    }
+                }
 
-        try:
-            self.create_test_data()
-            self.tracker.reset_graph_updates_tracking()
-        except Exception as e:
-            self.tearDown()
+                for schema_name, contents in settings.items():
+                    dconf = trackertestutils.dconf.DConfClient(self.sandbox)
+                    for key, value in contents.items():
+                        dconf.write(schema_name, key, value)
+
+                self.tracker = trackertestutils.helpers.StoreHelper(
+                    self.sandbox.get_connection())
+                self.tracker.start_and_wait_for_ready()
+                self.tracker.start_watching_updates()
+
+                self.miner_fs = MinerFsHelper(
+                    self.sandbox.get_connection())
+                self.miner_fs.start()
+                self.miner_fs.start_watching_progress()
+
+                self.create_test_data()
+                self.tracker.stop_watching_updates()
+
+                # We reset update-tracking, so that updates for data created in the
+                # fixture can't be mixed up with updates created by the test case.
+                self.tracker.start_watching_updates()
+            except Exception:
+                self.sandbox.stop()
+                raise
+        except Exception:
+            self.remove_test_data()
+            cfg.remove_monitored_test_dir(self.workdir)
             raise
 
     def tearDown(self):
-        self.system.finish()
+        self.sandbox.stop()
         self.remove_test_data()
         cfg.remove_monitored_test_dir(self.workdir)
 
@@ -186,7 +209,7 @@ class CommonTrackerMinerFTSTest (CommonTrackerMinerTest):
         and assert the testfile is only result.
 
         Be careful with the default contents of the text files
-        ( see common/utils/minertest.py DEFAULT_TEXT )
+        ( see minertest.py DEFAULT_TEXT )
         """
         self.set_text(text)
         results = self.search_word(word)
diff --git a/tests/functional-tests/system.py b/tests/functional-tests/system.py
new file mode 100644
index 000000000..94649ace1
--- /dev/null
+++ b/tests/functional-tests/system.py
@@ -0,0 +1,259 @@
+#
+# Copyright (C) 2010, Nokia (ivan frade nokia com)
+# Copyright (C) 2019, Sam Thursfield (sam afuera me uk)
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA  02110-1301, USA.
+#
+
+
+import logging
+import os
+import shutil
+import tempfile
+
+from gi.repository import Gio
+from gi.repository import GLib
+
+import trackertestutils.dconf
+import trackertestutils.helpers
+import configuration as cfg
+
+TEST_ENV_VARS = {"LC_COLLATE": "en_GB.utf8"}
+
+REASONABLE_TIMEOUT = 5
+
+log = logging.getLogger(__name__)
+
+
+class WakeupCycleTimeoutException(RuntimeError):
+    pass
+
+
+class UnableToBootException (Exception):
+    pass
+
+
+class MinerFsHelper (trackertestutils.helpers.Helper):
+
+    MINERFS_BUSNAME = "org.freedesktop.Tracker1.Miner.Files"
+    MINERFS_OBJ_PATH = "/org/freedesktop/Tracker1/Miner/Files"
+    MINER_IFACE = "org.freedesktop.Tracker1.Miner"
+    MINERFS_INDEX_OBJ_PATH = "/org/freedesktop/Tracker1/Miner/Files/Index"
+    MINER_INDEX_IFACE = "org.freedesktop.Tracker1.Miner.Files.Index"
+
+    def __init__(self, process_path):
+        trackertestutils.helpers.Helper.__init__(self, "tracker-miner-fs", self.MINERFS_BUSNAME, 
process_path)
+        self._progress_handler_id = 0
+        self._wakeup_count = 0
+        self._previous_status = None
+        self._target_wakeup_count = None
+
+    def start(self, command_args=None, extra_env=None):
+        command_args = command_args or []
+
+        trackertestutils.helpers.Helper.start(self, command_args + ['--initial-sleep=0'], extra_env)
+
+        self.miner_fs = Gio.DBusProxy.new_sync(
+            self.bus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None,
+            self.MINERFS_BUSNAME, self.MINERFS_OBJ_PATH, self.MINER_IFACE)
+        self.index = Gio.DBusProxy.new_sync(
+            self.bus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None,
+            self.MINERFS_BUSNAME, self.MINERFS_INDEX_OBJ_PATH, self.MINER_INDEX_IFACE)
+
+        def signal_handler(proxy, sender_name, signal_name, parameters):
+            if signal_name == 'Progress':
+                self._progress_cb(*parameters.unpack())
+
+        self._progress_handler_id = self.miner_fs.connect('g-signal', signal_handler)
+
+    def stop(self):
+        trackertestutils.helpers.Helper.stop(self)
+
+        if self._progress_handler_id != 0:
+            self.miner_fs.disconnect(self._progress_handler_id)
+
+    def _progress_cb(self, status, progress, remaining_time):
+        if self._previous_status is None:
+            self._previous_status = status
+        if self._previous_status != 'Idle' and status == 'Idle':
+            self._wakeup_count += 1
+
+        if self._target_wakeup_count is not None and self._wakeup_count >= self._target_wakeup_count:
+            self.loop.quit()
+
+    def wakeup_count(self):
+        """Return the number of wakeup-to-idle cycles the miner-fs completed."""
+        return self._wakeup_count
+
+    def await_wakeup_count(self, target_wakeup_count, timeout=REASONABLE_TIMEOUT):
+        """Block until the miner has completed N wakeup-and-idle cycles.
+
+        This function is for use by miner-fs tests that should trigger an
+        operation in the miner, but which do not cause a new resource to be
+        inserted. These tests can instead wait for the status to change from
+        Idle to Processing... and then back to Idle.
+
+        The miner may change its status any number of times, but you can use
+        this function reliably as follows:
+
+            wakeup_count = miner_fs.wakeup_count()
+            # Trigger a miner-fs operation somehow ...
+            miner_fs.await_wakeup_count(wakeup_count + 1)
+            # The miner has probably finished processing the operation now.
+
+        If the timeout is reached before enough wakeup cycles complete, an
+        exception will be raised.
+
+        """
+
+        assert self._target_wakeup_count is None
+
+        if self._wakeup_count >= target_wakeup_count:
+            log.debug("miner-fs wakeup count is at %s (target is %s). No need to wait", self._wakeup_count, 
target_wakeup_count)
+        else:
+            def _timeout_cb():
+                raise WakeupCycleTimeoutException()
+            timeout_id = GLib.timeout_add_seconds(timeout, _timeout_cb)
+
+            log.debug("Waiting for miner-fs wakeup count of %s (currently %s)", target_wakeup_count, 
self._wakeup_count)
+            self._target_wakeup_count = target_wakeup_count
+            self.loop.run_checked()
+
+            self._target_wakeup_count = None
+            GLib.source_remove(timeout_id)
+
+    def index_file(self, uri):
+        return self.index.IndexFile('(s)', uri)
+
+
+class ExtractorHelper (trackertestutils.helpers.Helper):
+
+    BUSNAME = "org.freedesktop.Tracker1.Miner.Extract"
+
+    def __init__(self, process_path):
+        trackertestutils.helpers.Helper.__init__(self, "tracker-extract", self.BUSNAME, process_path)
+
+
+class WritebackHelper (trackertestutils.helpers.Helper):
+
+    BUSNAME = "org.freedesktop.Tracker1.Writeback"
+
+    def __init__(self, process_path):
+        trackertestutils.helpers.Helper.__init__(self, "tracker-writeback", self.BUSNAME, process_path)
+
+
+class TrackerSystemAbstraction (object):
+    def __init__(self, settings=None, ontodir=None):
+        self.extractor = None
+        self.miner_fs = None
+        self.store = None
+        self.writeback = None
+
+        self._dconf_settings = settings
+        self._ontologies_dir = ontodir
+
+        self._basedir = tempfile.mkdtemp()
+
+        self._dirs = {
+            "XDG_DATA_HOME": self.xdg_data_home(),
+            "XDG_CACHE_HOME": self.xdg_cache_home()
+        }
+
+    def xdg_data_home(self):
+        return os.path.join(self._basedir, 'data')
+
+    def xdg_cache_home(self):
+        return os.path.join(self._basedir, 'cache')
+
+    def environment(self):
+        """Returns extra environment variables to set for the daemons."""
+        extra_env = {}
+
+        for var, directory in self._dirs.items():
+            extra_env[var] = directory
+
+        if self._ontologies_dir:
+            extra_env["TRACKER_DB_ONTOLOGIES_DIR"] = self._ontologies_dir
+
+        for var, value in TEST_ENV_VARS.items():
+            extra_env[var] = value
+
+        return extra_env
+
+    def _create_dirs(self):
+        # Make sure the XDG_*_HOME directories exist
+        for var, directory in self._dirs.items():
+            os.makedirs(directory)
+            os.makedirs(os.path.join(directory, 'tracker'))
+
+    def _setup_dconf(self):
+        # Initialize the DConf profile with our settings.
+        # (The profile we use is defined in meson.build by setting
+        # DCONF_PROFILE in the test environment).
+        for schema_name, contents in self._dconf_settings.items():
+            dconf = trackertestutils.dconf.DConfClient(schema_name)
+            dconf.reset()
+            for key, value in contents.items():
+                dconf.write(key, value)
+
+    def tracker_miner_fs_testing_start(self):
+        """
+        Stops any previous instance of the store and miner, calls set_up_environment,
+        and starts a new instance of the store and miner-fs
+        """
+        self._create_dirs()
+        self._setup_dconf()
+
+        # Start also the store. DBus autoactivation ignores the env variables.
+        self.store = trackertestutils.helpers.StoreHelper(cfg.TRACKER_STORE_PATH)
+        self.store.start(extra_env=self.environment())
+
+        self.extractor = ExtractorHelper(cfg.TRACKER_EXTRACT_PATH)
+        self.extractor.start(extra_env=self.environment())
+
+        self.miner_fs = MinerFsHelper(cfg.TRACKER_MINER_FS_PATH)
+        self.miner_fs.start(extra_env=self.environment())
+
+    def tracker_writeback_testing_start(self):
+        # Start the miner-fs (and store) and then the writeback process
+        self.tracker_miner_fs_testing_start()
+        self.writeback = WritebackHelper(cfg.TRACKER_WRITEBACK_PATH)
+        self.writeback.start()
+
+    def tracker_all_testing_start(self):
+        # This will start all miner-fs, store and writeback
+        self.tracker_writeback_testing_start()
+
+    def finish(self):
+        """
+        Stop all running processes and remove all test data.
+        """
+
+        if self.writeback:
+            self.writeback.stop()
+
+        if self.extractor:
+            self.extractor.stop()
+
+        if self.miner_fs:
+            self.miner_fs.stop()
+
+        if self.store:
+            self.store.stop()
+
+        for path in list(self._dirs.values()):
+            shutil.rmtree(path)
+        os.rmdir(self._basedir)
diff --git a/tests/functional-tests/common/utils/writebacktest.py b/tests/functional-tests/writebacktest.py
similarity index 50%
rename from tests/functional-tests/common/utils/writebacktest.py
rename to tests/functional-tests/writebacktest.py
index 0a31b44ff..8fececdde 100644
--- a/tests/functional-tests/common/utils/writebacktest.py
+++ b/tests/functional-tests/writebacktest.py
@@ -1,6 +1,6 @@
-#!/usr/bin/env python3
-
+#
 # Copyright (C) 2010, Nokia (ivan frade nokia com)
+# Copyright (C) 2019, Sam Thursfield <sam afuera me uk>
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -27,8 +27,12 @@ import shutil
 import time
 import unittest as ut
 
-from common.utils.system import TrackerSystemAbstraction
-from common.utils import configuration as cfg
+import trackertestutils.dconf
+import trackertestutils.helpers
+
+import configuration as cfg
+from minerfshelper import MinerFsHelper
+
 
 TEST_FILE_JPEG = "writeback-test-1.jpeg"
 TEST_FILE_TIFF = "writeback-test-2.tif"
@@ -37,6 +41,11 @@ TEST_FILE_PNG = "writeback-test-4.png"
 log = logging.getLogger(__name__)
 
 
+def ensure_dir_exists(dirname):
+    if not os.path.exists(dirname):
+        os.makedirs(dirname)
+
+
 class CommonTrackerWritebackTest (ut.TestCase):
     """
     Superclass to share methods. Shouldn't be run by itself.
@@ -46,36 +55,68 @@ class CommonTrackerWritebackTest (ut.TestCase):
     def setUp(self):
         self.workdir = cfg.create_monitored_test_dir()
 
-        index_dirs = [self.workdir]
-
-        CONF_OPTIONS = {
-            cfg.DCONF_MINER_SCHEMA: {
-                'index-recursive-directories': GLib.Variant.new_strv(index_dirs),
-                'index-single-directories': GLib.Variant.new_strv([]),
-                'index-optical-discs': GLib.Variant.new_boolean(False),
-                'index-removable-devices': GLib.Variant.new_boolean(False),
-            },
-            'org.freedesktop.Tracker.Store': {
-                'graphupdated-delay': GLib.Variant.new_int32(100)
-            }
-        }
-
-        self.system = TrackerSystemAbstraction()
-        self.system.tracker_writeback_testing_start(CONF_OPTIONS)
-
-        self.tracker = self.system.store
-        self.extractor = self.system.extractor
+        self.indexed_dir = os.path.join(self.workdir, 'test-monitored')
+
+        # It's important that this directory exists BEFORE we start Tracker:
+        # it won't monitor an indexing root for changes if it doesn't exist,
+        # it'll silently ignore it instead. See the tracker_crawler_start()
+        # function.
+        ensure_dir_exists(self.indexed_dir)
+
+        try:
+            self.extra_env = cfg.test_environment(self.workdir)
+
+            self.sandbox = trackertestutils.helpers.TrackerDBusSandbox(
+                dbus_daemon_config_file=cfg.TEST_DBUS_DAEMON_CONFIG_FILE, extra_env=self.extra_env)
+
+            self.sandbox.start()
+
+            try:
+                settings = {
+                    'org.freedesktop.Tracker.Store': {
+                        'graphupdated-delay': GLib.Variant('i', 100)
+                    },
+                    'org.freedesktop.Tracker.Miner.Files': {
+                        'index-recursive-directories': GLib.Variant.new_strv([self.indexed_dir]),
+                        'index-single-directories': GLib.Variant.new_strv([]),
+                        'index-optical-discs': GLib.Variant.new_boolean(False),
+                        'index-removable-devices': GLib.Variant.new_boolean(False),
+                        'throttle': GLib.Variant.new_int32(5),
+                    }
+                }
+
+                for schema_name, contents in settings.items():
+                    dconf = trackertestutils.dconf.DConfClient(self.sandbox)
+                    for key, value in contents.items():
+                        dconf.write(schema_name, key, value)
+
+                self.tracker = trackertestutils.helpers.StoreHelper(
+                    self.sandbox.get_connection())
+                self.tracker.start_and_wait_for_ready()
+                self.tracker.start_watching_updates()
+
+                self.miner_fs = MinerFsHelper(
+                    self.sandbox.get_connection())
+                self.miner_fs.start()
+            except Exception:
+                self.sandbox.stop()
+                raise
+        except Exception:
+            self.remove_test_data()
+            cfg.remove_monitored_test_dir(self.workdir)
+            raise
 
     def tearDown(self):
-        self.system.finish()
+        self.sandbox.stop()
 
-        for test_file in pathlib.Path(self.workdir).iterdir():
+        for test_file in pathlib.Path(self.indexed_dir).iterdir():
             test_file.unlink()
+
         cfg.remove_monitored_test_dir(self.workdir)
 
     def datadir_path(self, filename):
         """Returns the full path to a writeback test file."""
-        datadir = os.path.join(os.path.dirname(__file__), '..', '..', 'test-writeback-data')
+        datadir = os.path.join(os.path.dirname(__file__), 'test-writeback-data')
         return pathlib.Path(os.path.join(datadir, filename))
 
     def prepare_test_file(self, path, expect_mime_type, expect_property):
@@ -85,16 +126,16 @@ class CommonTrackerWritebackTest (ut.TestCase):
         miner before returning.
 
         """
-        log.debug("Copying %s -> %s", path, self.workdir)
-        shutil.copy(path, self.workdir)
+        log.debug("Copying %s -> %s", path, self.indexed_dir)
+        shutil.copy(path, self.indexed_dir)
 
-        output_path = pathlib.Path(os.path.join(self.workdir, os.path.basename(path)))
+        output_path = pathlib.Path(os.path.join(self.indexed_dir, os.path.basename(path)))
 
         # Make sure a resource has been crawled by the FS miner and by
         # tracker-extract. The extractor adds nie:contentCreated for
         # image resources, so know once this property is set the
         # extraction is complete.
-        self.system.store.await_resource_inserted(expect_mime_type, url=output_path.as_uri(), 
required_property=expect_property)
+        self.tracker.await_resource_inserted(expect_mime_type, url=output_path.as_uri(), 
required_property=expect_property)
         return output_path
 
     def prepare_test_audio(self, filename):
diff --git a/tests/test-bus.conf.in b/tests/test-bus.conf.in
index 2f4b2ef1b..606c3a226 100644
--- a/tests/test-bus.conf.in
+++ b/tests/test-bus.conf.in
@@ -7,6 +7,7 @@
   <listen>unix:tmpdir=./</listen>
 
   <servicedir>@abs_top_builddir@/tests/services/</servicedir>
+  <servicedir>@tracker_test_dbus_services_dir@</servicedir>
   <standard_session_servicedirs/>
 
   <policy context="default">


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