diff options
author | Sam Thursfield <sam@afuera.me.uk> | 2021-08-12 15:02:23 +0000 |
---|---|---|
committer | Sam Thursfield <sam@afuera.me.uk> | 2021-08-12 15:02:23 +0000 |
commit | eea3220f3787b934a628f897f0fd41cb59e4bfe2 (patch) | |
tree | 92312226568c9ab4aa37dde781af5980f97379bb | |
parent | fc797a925f2dd680425ac64766d2a2000ad27caf (diff) | |
parent | 08e55cc1f2f59a0b9d8488f6b5d6ef10c255411f (diff) | |
download | tracker-eea3220f3787b934a628f897f0fd41cb59e4bfe2.tar.gz |
Merge branch 'prevent_saving_half_completed_db' into 'master'
Ignore saving half completed db
See merge request GNOME/tracker!457
15 files changed, 664 insertions, 119 deletions
diff --git a/src/libtracker-data/tracker-data-manager.c b/src/libtracker-data/tracker-data-manager.c index fce5b44cc..89deafc42 100644 --- a/src/libtracker-data/tracker-data-manager.c +++ b/src/libtracker-data/tracker-data-manager.c @@ -1819,19 +1819,21 @@ load_ontology_file (TrackerDataManager *manager, static TrackerOntology* get_ontology_from_file (TrackerDataManager *manager, - GFile *file) + GFile *file, + GError **error) { const gchar *subject, *predicate, *object; TrackerTurtleReader *reader; - GError *error = NULL; + GError *internal_error = NULL; GHashTable *ontology_uris; TrackerOntology *ret = NULL; - reader = tracker_turtle_reader_new_for_file (file, &error); + reader = tracker_turtle_reader_new_for_file (file, &internal_error); - if (error) { - g_critical ("Turtle parse error: %s", error->message); - g_error_free (error); + if (internal_error) { + g_propagate_prefixed_error (error, internal_error, + "Turtle parse error: %s", + internal_error->message); return NULL; } @@ -1842,7 +1844,7 @@ get_ontology_from_file (TrackerDataManager *manager, while (tracker_turtle_reader_next (reader, &subject, &predicate, &object, - NULL, NULL, &error)) { + NULL, NULL, &internal_error)) { if (g_strcmp0 (predicate, RDF_TYPE) == 0) { if (g_strcmp0 (object, TRACKER_PREFIX_NRL "Ontology") == 0) { TrackerOntology *ontology; @@ -1859,21 +1861,21 @@ get_ontology_from_file (TrackerDataManager *manager, } else if (g_strcmp0 (predicate, NRL_LAST_MODIFIED) == 0) { TrackerOntology *ontology; GDateTime *datetime; - GError *error = NULL; + GError *parsing_error; ontology = g_hash_table_lookup (ontology_uris, subject); if (ontology == NULL) { gchar *uri = g_file_get_uri (file); g_critical ("%s: Unknown ontology %s", uri, subject); g_free (uri); - return NULL; + continue; } - datetime = tracker_date_new_from_iso8601 (object, NULL); + datetime = tracker_date_new_from_iso8601 (object, &parsing_error); if (!datetime) { - g_critical ("%s: error parsing nrl:lastModified: %s", - subject, error->message); - g_error_free (error); + g_propagate_prefixed_error (error, parsing_error, + "%s: error parsing nrl:lastModified: %s", + subject, parsing_error->message); return NULL; } @@ -1891,15 +1893,19 @@ get_ontology_from_file (TrackerDataManager *manager, g_hash_table_unref (ontology_uris); g_object_unref (reader); - if (error) { - g_critical ("Turtle parse error: %s", error->message); - g_error_free (error); + if (internal_error) { + g_propagate_prefixed_error (error, internal_error, + "Turtle parse error: %s", + internal_error->message); + return NULL; } if (ret == NULL) { gchar *uri = g_file_get_uri (file); - g_critical ("Ontology file has no nrl:lastModified header: %s", uri); + g_propagate_prefixed_error (error, internal_error, + "Ontology file has no nrl:lastModified header: %s", uri); g_free (uri); + return NULL; } return ret; @@ -3810,7 +3816,6 @@ tracker_data_manager_initable_init (GInitable *initable, manager->db_manager = tracker_db_manager_new (manager->flags, manager->cache_location, - &is_create, FALSE, manager->select_cache_size, manager->update_cache_size, @@ -3823,6 +3828,8 @@ tracker_data_manager_initable_init (GInitable *initable, return FALSE; } + is_create = tracker_db_manager_is_first_time (manager->db_manager); + g_signal_connect (manager->db_manager, "setup-interface", G_CALLBACK (setup_interface_cb), manager); g_signal_connect (manager->db_manager, "update-interface", @@ -3887,12 +3894,12 @@ tracker_data_manager_initable_init (GInitable *initable, NULL, NULL, &ontology_error); + g_free (uri); + if (ontology_error) { g_propagate_error (error, ontology_error); - return FALSE; + goto rollback_newly_created_db; } - - g_free (uri); } tracker_data_ontology_setup_db (manager, iface, "main", FALSE, @@ -3906,18 +3913,18 @@ tracker_data_manager_initable_init (GInitable *initable, if (internal_error) { g_propagate_error (error, internal_error); - return FALSE; + goto rollback_newly_created_db; } if (!tracker_data_manager_init_fts (manager, iface, "main", TRUE, &internal_error)) { g_propagate_error (error, internal_error); - return FALSE; + goto rollback_newly_created_db; } tracker_data_manager_initialize_iface (manager, iface, &internal_error); if (internal_error) { g_propagate_error (error, internal_error); - return FALSE; + goto rollback_newly_created_db; } /* store ontology in database */ @@ -3925,13 +3932,6 @@ tracker_data_manager_initable_init (GInitable *initable, import_ontology_file (manager, l->data, FALSE); } - tracker_data_commit_transaction (manager->data_update, &internal_error); - - if (internal_error) { - g_propagate_error (error, internal_error); - return FALSE; - } - write_ontologies_gvdb (manager, TRUE /* overwrite */, NULL); ontologies = tracker_ontologies_get_ontologies (manager->ontologies, &n_ontologies); @@ -3947,6 +3947,13 @@ tracker_data_manager_initable_init (GInitable *initable, } } + tracker_data_commit_transaction (manager->data_update, &internal_error); + + if (internal_error) { + g_propagate_error (error, internal_error); + goto rollback_newly_created_db; + } + g_list_free_full (sorted, g_object_unref); sorted = NULL; } else { @@ -4059,16 +4066,14 @@ tracker_data_manager_initable_init (GInitable *initable, gint last_mod; /* Parse a TrackerOntology from ontology_file */ - ontology = get_ontology_from_file (manager, ontology_file); + ontology = get_ontology_from_file (manager, ontology_file, &internal_error); - if (!ontology) { - /* TODO: cope with full custom .ontology files: deal with this - * error gracefully. App devs might install wrong ontology files - * and we shouldn't critical() due to this. */ + if (internal_error) { gchar *uri = g_file_get_uri (ontology_file); - g_critical ("Can't get ontology from file: %s", uri); + g_propagate_prefixed_error (error, internal_error, + "Can't get ontology from file: %s", uri); g_free (uri); - continue; + return FALSE; } ontology_uri = tracker_ontology_get_uri (ontology); @@ -4127,34 +4132,17 @@ tracker_data_manager_initable_init (GInitable *initable, seen_properties, &ontology_error); - if (g_error_matches (ontology_error, - TRACKER_DATA_ONTOLOGY_ERROR, - TRACKER_DATA_UNSUPPORTED_ONTOLOGY_CHANGE)) { - g_warning ("%s", ontology_error->message); - g_error_free (ontology_error); + if (ontology_error) { + g_propagate_error (error, ontology_error); g_clear_pointer (&seen_classes, g_ptr_array_unref); g_clear_pointer (&seen_properties, g_ptr_array_unref); - tracker_data_ontology_import_finished (manager); - - /* as we're processing an ontology change, - transaction is guaranteed to be started */ - tracker_data_rollback_transaction (manager->data_update); - if (ontos_table) { - g_hash_table_unref (ontos_table); - } if (ontos) { g_list_free_full (ontos, g_object_unref); } - g_object_unref (manager->ontology_location); - - goto skip_ontology_check; - } - - if (ontology_error) { - g_critical ("Fatal error dealing with ontology changes: %s", ontology_error->message); - g_error_free (ontology_error); + + goto rollback_db_changes; } to_reload = g_list_prepend (to_reload, l->data); @@ -4184,28 +4172,17 @@ tracker_data_manager_initable_init (GInitable *initable, seen_properties, &ontology_error); - if (g_error_matches (ontology_error, - TRACKER_DATA_ONTOLOGY_ERROR, - TRACKER_DATA_UNSUPPORTED_ONTOLOGY_CHANGE)) { - g_warning ("%s", ontology_error->message); - g_error_free (ontology_error); + if (ontology_error) { + g_propagate_error (error, ontology_error); g_clear_pointer (&seen_classes, g_ptr_array_unref); g_clear_pointer (&seen_properties, g_ptr_array_unref); - tracker_data_ontology_import_finished (manager); - - /* as we're processing an ontology change, - transaction is guaranteed to be started */ - tracker_data_rollback_transaction (manager->data_update); - if (ontos_table) { - g_hash_table_unref (ontos_table); - } if (ontos) { g_list_free_full (ontos, g_object_unref); } - - goto skip_ontology_check; + + goto rollback_db_changes; } if (ontology_error) { @@ -4302,28 +4279,17 @@ tracker_data_manager_initable_init (GInitable *initable, } } - if (g_error_matches (ontology_error, - TRACKER_DATA_ONTOLOGY_ERROR, - TRACKER_DATA_UNSUPPORTED_ONTOLOGY_CHANGE)) { - g_warning ("%s", ontology_error->message); - g_error_free (ontology_error); + if (ontology_error) { + g_propagate_error (error, ontology_error); g_clear_pointer (&seen_classes, g_ptr_array_unref); g_clear_pointer (&seen_properties, g_ptr_array_unref); - tracker_data_ontology_import_finished (manager); - /* as we're processing an ontology change, - transaction is guaranteed to be started */ - tracker_data_rollback_transaction (manager->data_update); - - if (ontos_table) { - g_hash_table_unref (ontos_table); - } if (ontos) { g_list_free_full (ontos, g_object_unref); } - - goto skip_ontology_check; + + goto rollback_db_changes; } if (ontology_error) { @@ -4362,8 +4328,6 @@ tracker_data_manager_initable_init (GInitable *initable, } - -skip_ontology_check: if (!read_only && is_create) { tracker_db_manager_set_current_locale (manager->db_manager); tracker_db_manager_tokenizer_update (manager->db_manager); @@ -4398,6 +4362,21 @@ skip_ontology_check: tracker_data_manager_update_status (manager, "Idle"); return TRUE; + +rollback_db_changes: + tracker_data_ontology_import_finished (manager); + tracker_data_rollback_transaction (manager->data_update); + + if (ontos_table) { + g_hash_table_unref (ontos_table); + } + + return FALSE; + +rollback_newly_created_db: + tracker_data_rollback_transaction (manager->data_update); + tracker_db_manager_rollback_db_creation (manager->db_manager, NULL); + return FALSE; } static gboolean diff --git a/src/libtracker-data/tracker-db-manager.c b/src/libtracker-data/tracker-db-manager.c index 8485af62e..63a7989b0 100644 --- a/src/libtracker-data/tracker-db-manager.c +++ b/src/libtracker-data/tracker-db-manager.c @@ -128,6 +128,7 @@ struct _TrackerDBManager { TrackerDBManagerFlags flags; guint s_cache_size; guint u_cache_size; + gboolean first_time; gpointer vtab_data; @@ -152,6 +153,12 @@ static TrackerDBInterface *tracker_db_manager_create_db_interface (TrackerDBMa static TrackerDBInterface * init_writable_db_interface (TrackerDBManager *db_manager); +gboolean +tracker_db_manager_is_first_time (TrackerDBManager *db_manager) +{ + return db_manager->first_time; +} + TrackerDBManagerFlags tracker_db_manager_get_flags (TrackerDBManager *db_manager, guint *select_cache_size, @@ -456,6 +463,29 @@ tracker_db_manager_db_exists (GFile *cache_location) return db_exists; } +int +tracker_db_manager_rollback_db_creation (TrackerDBManager *db_manager, + GError **error) +{ + gchar *dir; + gchar *filename; + int ret; + + g_return_val_if_fail (db_manager->first_time, -1); + + if ((db_manager->flags & TRACKER_DB_MANAGER_IN_MEMORY) != 0) + return 0; + + dir = g_file_get_path (db_manager->cache_location); + filename = g_build_filename (dir, db_base.file, NULL); + + ret = g_unlink (filename); + + g_free (dir); + g_free (filename); + return ret; +} + static gboolean db_check_integrity (TrackerDBManager *db_manager) { @@ -513,16 +543,15 @@ db_check_integrity (TrackerDBManager *db_manager) TrackerDBManager * tracker_db_manager_new (TrackerDBManagerFlags flags, - GFile *cache_location, - gboolean *first_time, - gboolean shared_cache, - guint select_cache_size, - guint update_cache_size, - TrackerBusyCallback busy_callback, - gpointer busy_user_data, + GFile *cache_location, + gboolean shared_cache, + guint select_cache_size, + guint update_cache_size, + TrackerBusyCallback busy_callback, + gpointer busy_user_data, GObject *iface_data, gpointer vtab_data, - GError **error) + GError **error) { TrackerDBManager *db_manager; TrackerDBVersion version; @@ -534,10 +563,8 @@ tracker_db_manager_new (TrackerDBManagerFlags flags, db_manager = g_object_new (TRACKER_TYPE_DB_MANAGER, NULL); db_manager->vtab_data = vtab_data; - /* First set defaults for return values */ - if (first_time) { - *first_time = FALSE; - } + /* Set default value for first_time */ + db_manager->first_time = FALSE; /* Set up locations */ db_manager->flags = flags; @@ -618,9 +645,7 @@ tracker_db_manager_new (TrackerDBManagerFlags flags, } if (need_to_create) { - if (first_time) { - *first_time = TRUE; - } + db_manager->first_time = TRUE; if ((db_manager->flags & TRACKER_DB_MANAGER_IN_MEMORY) == 0 && !tracker_file_system_has_enough_space (db_manager->data_dir, TRACKER_DB_MIN_REQUIRED_SPACE, TRUE)) { diff --git a/src/libtracker-data/tracker-db-manager.h b/src/libtracker-data/tracker-db-manager.h index b8fd04aa4..62bf3b944 100644 --- a/src/libtracker-data/tracker-db-manager.h +++ b/src/libtracker-data/tracker-db-manager.h @@ -50,11 +50,13 @@ typedef enum { TRACKER_DB_MANAGER_SKIP_VERSION_CHECK = 1 << 9, } TrackerDBManagerFlags; +int tracker_db_manager_rollback_db_creation (TrackerDBManager *db_manager, + GError **error); + gboolean tracker_db_manager_db_exists (GFile *cache_location); TrackerDBManager *tracker_db_manager_new (TrackerDBManagerFlags flags, GFile *cache_location, - gboolean *first_time, gboolean shared_cache, guint select_cache_size, guint update_cache_size, @@ -69,6 +71,8 @@ TrackerDBInterface *tracker_db_manager_get_writable_db_interface (TrackerDBManag gboolean tracker_db_manager_has_enough_space (TrackerDBManager *db_manager); +gboolean tracker_db_manager_is_first_time (TrackerDBManager *db_manager); + TrackerDBManagerFlags tracker_db_manager_get_flags (TrackerDBManager *db_manager, guint *select_cache_size, diff --git a/src/tracker/tracker-export.c b/src/tracker/tracker-export.c index ead5de036..38c05f941 100644 --- a/src/tracker/tracker-export.c +++ b/src/tracker/tracker-export.c @@ -390,8 +390,7 @@ export_2to3_with_query (const gchar *query, db_manager = tracker_db_manager_new (TRACKER_DB_MANAGER_READONLY | TRACKER_DB_MANAGER_SKIP_VERSION_CHECK, - store, - NULL, FALSE, + store, FALSE, 1, 1, NULL, NULL, NULL, NULL, &inner_error); if (inner_error) { diff --git a/tests/functional-tests/meson.build b/tests/functional-tests/meson.build index 934a59833..35c98c0db 100644 --- a/tests/functional-tests/meson.build +++ b/tests/functional-tests/meson.build @@ -34,6 +34,7 @@ functional_tests = [ 'notifier', 'collation', 'ontology-changes', + 'ontology-rollback', 'cli', 'portal', ] diff --git a/tests/functional-tests/ontology-changes.py b/tests/functional-tests/ontology-changes.py index 22cf767ac..f1021a805 100644 --- a/tests/functional-tests/ontology-changes.py +++ b/tests/functional-tests/ontology-changes.py @@ -181,7 +181,8 @@ class PropertyRangeStringToDate (OntologyChangeTestTemplate): Change the range of a property from string to date. There shouldn't be any data loss. """ - @ut.skip("Fails with: basic-future/91-test.ontology: Unsupported ontology change for http://example.org/ns#a_string: can't change rdfs:range (old=http://www.w3.org/2001/XMLSchema#dateTime, attempted new=http://www.w3.org/2001/XMLSchema#string)") + # Conversion from string to dateTime is not allowed + @ut.expectedFailure def test_property_range_string_to_date(self): self.template_test_ontology_change() @@ -211,7 +212,8 @@ class PropertyRangeDateToString (OntologyChangeTestTemplate): Change the range of a property from date to string. There shouldn't be any data loss. """ - @ut.skip("fails with: basic-future/91-test.ontology: Unsupported ontology change for http://example.org/ns#a_string: can't change rdfs:range (old=http://www.w3.org/2001/XMLSchema#dateTime, attempted new=http://www.w3.org/2001/XMLSchema#string)") + # Conversion from dateTime to string is not supported yet + @ut.expectedFailure def test_property_range_date_to_string(self): self.template_test_ontology_change() @@ -240,7 +242,7 @@ class PropertyRangeIntToString (OntologyChangeTestTemplate): """ Change the range of a property from int to string. There shouldn't be any data loss. """ - @ut.skip("Fails with: Unable to insert multiple values for subject `http://example.org/ns#a_int' and single valued property `rdfs:comment' (old_value: 'This property is integer in basic here is string', new value: 'Property to test the changes string/int')") + def test_property_range_int_to_str(self): self.template_test_ontology_change() @@ -269,7 +271,6 @@ class PropertyRangeStringToInt (OntologyChangeTestTemplate): Change the range of a property from string to int. There shouldn't be any data loss. """ - @ut.skip("Fails with: Unable to insert multiple values for subject `http://example.org/ns#a_int' and single valued property `rdfs:comment' (old_value: 'Property to test the changes string/int', new value: 'This property is integer in basic here is string')") def test_property_range_str_to_int(self): self.template_test_ontology_change() @@ -573,7 +574,7 @@ class OntologyAddPropertyTest (OntologyChangeTestTemplate): """ Add new properties in the ontology, with/without super prop and different ranges and cardinalities """ - @ut.skip("Fails with:Unable to insert multiple values for subject `http://example.org/ns#a_int' and single valued property `rdfs:comment' (old_value: 'This property is integer in basic here is string', new value: 'Property to test the changes string/int')") + def test_ontology_add_property(self): self.template_test_ontology_change() @@ -668,7 +669,7 @@ class DomainIndexAddTest (OntologyChangeTestTemplate): """ Add nrl:domainIndex to a class and check there is no data loss. """ - @ut.skip("Fails with: basic-future/91-test.ontology: Unsupported ontology change for test:b_property: can't change rdfs:domain (old=test:A, attempted new=test:B) ") + def test_domain_index_add(self): self.template_test_ontology_change() @@ -758,7 +759,9 @@ class SuperclassRemovalTest (OntologyChangeTestTemplate): """ Remove the superclass relation between two classes """ - @ut.skip("Fails with: Unsupported ontology change for http://example.org/ns#B: can't change rdfs:subClassOf (old=-, attempted new=-)") + + # Changes to rdfs:subClassOf are not allowed + @ut.expectedFailure def test_superclass_removal(self): self.template_test_ontology_change() @@ -801,7 +804,9 @@ class SuperclassAdditionTest (OntologyChangeTestTemplate): """ Add a superclass to a class with no superclass previously """ - @ut.skip("Fails with: basic-future/91-test.ontology: Unsupported ontology change for test:B: can't change rdfs:subClassOf (old=-, attempted new=test:A)") + + # Changes to rdfs:subClassOf are not allowed + @ut.expectedFailure def test_superclass_addition(self): self.template_test_ontology_change() @@ -844,7 +849,9 @@ class PropertyPromotionTest (OntologyChangeTestTemplate): """ Move a property to the superclass """ - @ut.skip("Fails with: basic-future/91-test.ontology: Unsupported ontology change for test:b_property: can't change rdfs:domain (old=test:A, attempted new=test:B)") + + # Changes to rdfs:domain are not allowed + @ut.expectedFailure def test_property_promotion(self): self.template_test_ontology_change() @@ -880,7 +887,9 @@ class PropertyRelegationTest (OntologyChangeTestTemplate): """ Move a property to the subclass """ - @ut.skip("Fails") + + # Changes to rdfs:domain are not allowed + @ut.expectedFailure def test_property_relegation(self): self.template_test_ontology_change() diff --git a/tests/functional-tests/ontology-rollback.py b/tests/functional-tests/ontology-rollback.py new file mode 100644 index 000000000..cb3e8307d --- /dev/null +++ b/tests/functional-tests/ontology-rollback.py @@ -0,0 +1,328 @@ +# Copyright (C) 2021, Abanoub Ghadban <abanoub.gdb@gmail.com> +# +# 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. +# + +""" +Test how the database is kept in consistent state when errors occur. +""" + +import gi +gi.require_version('Tracker', '3.0') +from gi.repository import GLib +from gi.repository import Gio +from gi.repository import Tracker + +import os +import pathlib +import shutil +import re +import tempfile +import time +import glob +import unittest as ut + +import trackertestutils.dconf +import trackertestutils.helpers + +import configuration as cfg +import fixtures + + +RDFS_RANGE = "http://www.w3.org/2000/01/rdf-schema#range" +XSD_DATETIME = "http://www.w3.org/2001/XMLSchema#dateTime" +XSD_STRING = "http://www.w3.org/2001/XMLSchema#string" +XSD_INTEGER = "http://www.w3.org/2001/XMLSchema#integer" + +TEST_PREFIX = "http://example.org/ns#" +TEST2_PREFIX = "http://example2.org/ns#" + + +class OntologyRollbackTestTemplate (ut.TestCase): + """ + Template class for the ontology rollback tests. It ensures that the db + is left in a consistent state when building or updating the ontology fails. + The tests are subclasses of this, implementing these methods: + + * set_ontology_dirs + * insert_data_into_first_ontology + * validate_first_ontology_status + * insert_data_into_second_ontology + * validate_second_ontology_status + + and adding a method 'test_x_y_z' to be invoked by unittest. + + Check doc in those methods for the specific details. + """ + + def setUp(self): + self.tmpdir = tempfile.mkdtemp(prefix='tracker-test-') + + def tearDown(self): + shutil.rmtree(self.tmpdir, ignore_errors=True) + + def ontology_path(self, param): + return pathlib.Path(__file__).parent.joinpath('test-ontologies', param) + + def template_test_ontology_rollback(self): + self.set_ontology_dirs() + + self.__assert_different_ontology_dates(self.FIRST_ONTOLOGY_DIR, self.SECOND_ONTOLOGY_DIR) + self.__assert_same_ontology_dates_if_exist(self.FIRST_ONTOLOGY_DIR, self.FIRST_MALFORMED_ONTOLOGY_DIR) + self.__assert_same_ontology_dates_if_exist(self.SECOND_ONTOLOGY_DIR, self.SECOND_MALFORMED_ONTOLOGY_DIR) + + # Make sure that the connection fails when the malformed ontology is used + with self.assertRaises(GLib.GError): + Tracker.SparqlConnection.new( + Tracker.SparqlConnectionFlags.NONE, + Gio.File.new_for_path(self.tmpdir), + Gio.File.new_for_path(str(self.ontology_path(self.FIRST_MALFORMED_ONTOLOGY_DIR))), + None) + + # Use the error-free first ontology. It should work now + conn1 = Tracker.SparqlConnection.new( + Tracker.SparqlConnectionFlags.NONE, + Gio.File.new_for_path(self.tmpdir), + Gio.File.new_for_path(str(self.ontology_path(self.FIRST_ONTOLOGY_DIR))), + None) + + self.tracker = trackertestutils.helpers.StoreHelper(conn1) + self.insert_data_into_first_ontology() + self.validate_first_ontology_status() + + conn1.close() + + # Reopen the local store with the second malformed set of ontologies. + # The connection should fail + with self.assertRaises(GLib.GError): + Tracker.SparqlConnection.new( + Tracker.SparqlConnectionFlags.NONE, + Gio.File.new_for_path(self.tmpdir), + Gio.File.new_for_path(str(self.ontology_path(self.SECOND_MALFORMED_ONTOLOGY_DIR))), + None) + + conn2 = Tracker.SparqlConnection.new( + Tracker.SparqlConnectionFlags.NONE, + Gio.File.new_for_path(self.tmpdir), + Gio.File.new_for_path(str(self.ontology_path(self.SECOND_ONTOLOGY_DIR))), + None) + self.tracker = trackertestutils.helpers.StoreHelper(conn2) + + self.insert_data_into_second_ontology() + self.validate_second_ontology_status() + + conn2.close() + + def set_ontology_dirs(self): + """ + Implement this method in the subclass setting values for: + self.FIRST_MALFORMED_ONTOLOGY_DIR, + self.FIRST_ONTOLOGY_DIR, + self.SECOND_MALFORMED_ONTOLOGY_DIR and + self.SECOND_ONTOLOGY_DIR + """ + raise Exception("Subclasses must implement 'set_ontology_dir'") + + def insert_data_into_first_ontology(self): + """ + Store some data with the FIRST ontology + Make sure that it can't insert data into properties and classes that + exist in the malformed ontology only + """ + raise Exception("Subclasses must implement 'insert_data_into_first_ontology'") + + def validate_first_ontology_status(self): + """ + This is called after inserting the data into the first ontology + Check that the data is inserted successfully and the database schema + matches that of the correct ontology and the schema of the malformed + ontology is completely rolled back + """ + raise Exception("Subclasses must implement 'validate_first_ontology_status'") + + def insert_data_into_second_ontology(slef): + """ + Store some data with the SECOND ontology + Make sure that it can't insert data into properties and classes that + exist in the malformed second ontology only + """ + raise Exception("Subclasses must implement 'insert_data_into_second_ontology'") + + def validate_second_ontology_status(self): + """ + This is called after inserting the data into the second ontology + Check that the data is inserted successfully and the database schema + matches that of the correct second ontology and the schema of the malformed + second ontology is completely rolled back + """ + raise Exception("Subclasses must implement 'validate_second_ontology_status'") + + def assertInDbusResult(self, member, dbus_result, column=0): + """ + Convenience assertion used in these tests + """ + for row in dbus_result: + if member == row[column]: + return + # This is going to fail with pretty printing + self.assertIn(member, dbus_result) + + def assertNotInDbusResult(self, member, dbus_result, column=0): + """ + Convenience assertion used in these tests + """ + for row in dbus_result: + if member == str(row[column]): + # This is going to fail with pretty printing + self.fail("'%s' wasn't supposed to be in '%s'" % + (member, dbus_result)) + return + + def __get_ontology_date(self, ontology_path): + """ + Returns the value of nrl:lastModified in the ontology file + """ + ISO9601_REGEX = "(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)" + + with open(ontology_path, 'r') as f: + for line in f: + if "nrl:lastModified" in line: + getmodtime = re.compile( + 'nrl:lastModified\ \"' + ISO9601_REGEX + '\"') + modtime_match = getmodtime.search(line) + + if (modtime_match): + nao_date = modtime_match.group(1) + return time.strptime(nao_date, "%Y-%m-%dT%H:%M:%SZ") + else: + return None + + def __assert_all_ontology_dates(self, first_dir, second_dir, assertion_fn): + """ + Compare between the nrl:lastModified of all ontology files in first_dir and second_dir + assertion_fn is used to compare between the two dates and make sure they are valid + """ + ontology_files = glob.glob(str(self.ontology_path(first_dir).joinpath("*.ontology"))) + + for first_ontology in ontology_files: + ontology_fname = os.path.basename(first_ontology) + second_ontology = self.ontology_path(second_dir).joinpath(ontology_fname) + + first_date = self.__get_ontology_date(first_ontology) + second_date = self.__get_ontology_date(second_ontology) + + try: + assertion_fn(first_date, second_date) + except AssertionError as e: + self.fail("%s: %s" % (first_ontology, e.msg)) + + def __assert_different_ontology_dates(self, first_dir, second_dir): + """ + Asserts that nrl:lastModified of ontologies in second_dir are more recent + than that in first_dir + """ + def assert_different_dates(first_date, second_date): + if first_date >= second_date: + self.fail("nrl:lastModified is not more recent in the second ontology") + + self.__assert_all_ontology_dates(first_dir, second_dir, assert_different_dates) + + def __assert_same_ontology_dates_if_exist(self, first_dir, second_dir): + """ + Asserts that nrl:lastModified of ontologies in first_dir are the same as + that in second_dir if they exist and are valid + """ + def assert_different_dates(first_date, second_date): + if second_date is not None and first_date != second_date: + self.fail("nrl:lastModified is not the same as in the second ontology") + + self.__assert_all_ontology_dates(first_dir, second_dir, assert_different_dates) + +class SimpleOntologyRollback (OntologyRollbackTestTemplate): + def test_simple_ontology_rollback(self): + self.template_test_ontology_rollback() + + def set_ontology_dirs(self): + self.FIRST_MALFORMED_ONTOLOGY_DIR = "simple-with-errors" + self.FIRST_ONTOLOGY_DIR = "simple" + self.SECOND_MALFORMED_ONTOLOGY_DIR = "simple-updated-with-errors" + self.SECOND_ONTOLOGY_DIR = "simple-updated" + + def insert_data_into_first_ontology(self): + # test:a_tmp_prop only appeared in the malformed ontology + with self.assertRaises(GLib.GError): + self.tracker.update( + "INSERT { <t1.1> a test:A ; test:a_tmp_prop 5. }") + + # The domain of test:b_a_domain should be test:A no test:B + with self.assertRaises(GLib.GError): + self.tracker.update( + "INSERT { <t1.2> a test:B ; test:b_a_domain 5. }") + + # The domain should be test:B and range be test:A + with self.assertRaises(GLib.GError): + self.tracker.update( + "INSERT { <t1.3> a test:B . <t1.4> a test:A ; test:a_b_domain_range <t1.3>. }") + + # test2:C should be subclass of test:B not test:A + with self.assertRaises(GLib.GError): + self.tracker.update( + "INSERT { <t1.5> a test2:C ; test:b_a_domain 5. }") + + self.tracker.update( + "INSERT { <t1.6> a test:A ; test:b_a_domain 5. }") + + self.tracker.update( + "INSERT { <t1.7> a test:B ; test:a_b_domain_range <t1.6>. }") + + self.tracker.update( + "INSERT { <t1.8> a test2:C ; test:b_range_boolean_string \"String\". }") + + def validate_first_ontology_status(self): + result = self.tracker.query( + "SELECT ?p WHERE { ?p a rdf:Property. }") + self.assertNotInDbusResult(TEST_PREFIX + "a_tmp_prop", result) + + result = self.tracker.query( + "SELECT ?d ?r WHERE { test:a_b_domain_range rdfs:domain ?d ; rdfs:range ?r }") + self.assertEqual(result[0][0], TEST_PREFIX + "B") + self.assertEqual(result[0][1], TEST_PREFIX + "A") + + self.assertFalse(self.tracker.ask("ASK { <%s> a rdfs:Class}" % ( + TEST2_PREFIX + "D")), "test2:D class is not rolled back on failure") + + def insert_data_into_second_ontology(self): + # Domain was test:B in the malformed ontology + # and became test:A in the error-free ontology + with self.assertRaises(GLib.GError): + self.tracker.update( + "INSERT { <t2.1> a test:B ; test:a_b_domain 5. }") + + self.tracker.update( + "INSERT { <t2.2> a test:A ; test:a_b_domain 5. }") + + def validate_second_ontology_status(self): + result = self.tracker.query( + "SELECT ?d ?r WHERE { test:a_b_domain rdfs:domain ?d ; rdfs:range ?r }") + self.assertEqual(result[0][0], TEST_PREFIX + "A") + self.assertEqual(result[0][1], XSD_INTEGER) + + result = self.tracker.query( + "SELECT ?v WHERE { <t2.2> test:a_b_domain ?v }") + self.assertEqual(result[0][0], "5") + +if __name__ == "__main__": + fixtures.tracker_test_main() diff --git a/tests/functional-tests/test-ontologies/simple-updated-with-errors/91-test.ontology b/tests/functional-tests/test-ontologies/simple-updated-with-errors/91-test.ontology new file mode 100644 index 000000000..e62f6981b --- /dev/null +++ b/tests/functional-tests/test-ontologies/simple-updated-with-errors/91-test.ontology @@ -0,0 +1,33 @@ +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . +@prefix test: <http://example.org/ns#> . +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . +@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> . + +test: a nrl:Namespace, nrl:Ontology ; + nrl:lastModified "2011-03-24T11:00:04Z" ; + nrl:prefix "test" . + +test:A a rdfs:Class ; + rdfs:subClassOf rdfs:Resource . + +test:B a rdfs:Class ; + rdfs:subClassOf rdfs:Resource . + +test:b_range_boolean_string a rdf:Property ; + rdfs:range xsd:string ; + rdfs:domain test:B . + +test:b_a_domain a rdf:Property ; + rdfs:range xsd:integer ; + rdfs:domain test:A . + +test:a_b_domain_range a rdf:Property ; + rdfs:range test:A ; + rdfs:domain test:B . + +# ******* UPDATES ******* +test:a_b_domain a rdf:Property ; + rdfs:range xsd:integer ; + rdfs:domain test:B . # Will be changed to test:A +# *********************** diff --git a/tests/functional-tests/test-ontologies/simple-updated-with-errors/92-test.ontology b/tests/functional-tests/test-ontologies/simple-updated-with-errors/92-test.ontology new file mode 100644 index 000000000..436a2cdf8 --- /dev/null +++ b/tests/functional-tests/test-ontologies/simple-updated-with-errors/92-test.ontology @@ -0,0 +1,17 @@ +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . +@prefix test: <http://example.org/ns#> . +@prefix test2: <http://example2.org/ns#> . +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . +@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> . + +test2: a nrl:Namespace, nrl:Ontology ; + nrl:lastModified "2011-03-24T11:00:04Z" ; + nrl:prefix "test2" . + +test2:C a rdfs:Class ; + rdfs:subClassOf test:B . # Was test:A + +# ******* MALFORMED PREFIX ******* +malformed:x a rdfs:Class . +# ******************************** diff --git a/tests/functional-tests/test-ontologies/simple-updated/91-test.ontology b/tests/functional-tests/test-ontologies/simple-updated/91-test.ontology new file mode 100644 index 000000000..c63cf232d --- /dev/null +++ b/tests/functional-tests/test-ontologies/simple-updated/91-test.ontology @@ -0,0 +1,33 @@ +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . +@prefix test: <http://example.org/ns#> . +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . +@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> . + +test: a nrl:Namespace, nrl:Ontology ; + nrl:lastModified "2011-03-24T11:00:04Z" ; + nrl:prefix "test" . + +test:A a rdfs:Class ; + rdfs:subClassOf rdfs:Resource . + +test:B a rdfs:Class ; + rdfs:subClassOf rdfs:Resource . + +test:b_range_boolean_string a rdf:Property ; + rdfs:range xsd:string ; + rdfs:domain test:B . + +test:b_a_domain a rdf:Property ; + rdfs:range xsd:integer ; + rdfs:domain test:A . + +test:a_b_domain_range a rdf:Property ; + rdfs:range test:A ; + rdfs:domain test:B . + +# ******* UPDATES ******* +test:a_b_domain a rdf:Property ; + rdfs:range xsd:integer ; + rdfs:domain test:A . # Was test:B +# *********************** diff --git a/tests/functional-tests/test-ontologies/simple-updated/92-test.ontology b/tests/functional-tests/test-ontologies/simple-updated/92-test.ontology new file mode 100644 index 000000000..c1bfb549e --- /dev/null +++ b/tests/functional-tests/test-ontologies/simple-updated/92-test.ontology @@ -0,0 +1,13 @@ +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . +@prefix test: <http://example.org/ns#> . +@prefix test2: <http://example2.org/ns#> . +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . +@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> . + +test2: a nrl:Namespace, nrl:Ontology ; + nrl:lastModified "2011-03-24T11:00:04Z" ; + nrl:prefix "test2" . + +test2:C a rdfs:Class ; + rdfs:subClassOf test:B . diff --git a/tests/functional-tests/test-ontologies/simple-with-errors/91-test.ontology b/tests/functional-tests/test-ontologies/simple-with-errors/91-test.ontology new file mode 100644 index 000000000..daabc6a6b --- /dev/null +++ b/tests/functional-tests/test-ontologies/simple-with-errors/91-test.ontology @@ -0,0 +1,40 @@ +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . +@prefix test: <http://example.org/ns#> . +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . +@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> . + +test: a nrl:Namespace, nrl:Ontology ; + nrl:lastModified "2010-03-24T11:00:04Z" ; + nrl:prefix "test" . + +test:A a rdfs:Class ; + rdfs:subClassOf rdfs:Resource . + +test:B a rdfs:Class ; + rdfs:subClassOf rdfs:Resource ; + rdfs:comment "Class B" . # This will be removed + +# The property will be removed in the error-free ontology +test:a_tmp_prop a rdf:Property ; + rdfs:range xsd:integer ; + rdfs:domain test:A . + +# The range will be changed +test:b_range_boolean_string a rdf:Property ; + rdfs:range xsd:boolean ; + rdfs:domain test:B . + +# The domain will be changed +test:b_a_domain a rdf:Property ; + rdfs:range xsd:integer ; + rdfs:domain test:B . + +# ***** MALFORMED STATEMENT ****** +test:x a rdf:Property rdfs:Class ; +# ******************************** + +# The domain and range will be changed +test:a_b_domain_range a rdf:Property ; + rdfs:range test:B ; + rdfs:domain test:A . diff --git a/tests/functional-tests/test-ontologies/simple-with-errors/92-test.ontology b/tests/functional-tests/test-ontologies/simple-with-errors/92-test.ontology new file mode 100644 index 000000000..942436435 --- /dev/null +++ b/tests/functional-tests/test-ontologies/simple-with-errors/92-test.ontology @@ -0,0 +1,16 @@ +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . +@prefix test: <http://example.org/ns#> . +@prefix test2: <http://example2.org/ns#> . +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . +@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> . + +test2: a nrl:Namespace, nrl:Ontology ; + nrl:lastModified "2010-03-24T11:00:04Z" ; + nrl:prefix "test2" . + +test2:C a rdfs:Class ; + rdfs:subClassOf test:A . # Will be changed to test:B + +# Will be removed +test2:D a rdfs:Class ; diff --git a/tests/functional-tests/test-ontologies/simple/91-test.ontology b/tests/functional-tests/test-ontologies/simple/91-test.ontology new file mode 100644 index 000000000..30b045ad6 --- /dev/null +++ b/tests/functional-tests/test-ontologies/simple/91-test.ontology @@ -0,0 +1,35 @@ +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . +@prefix test: <http://example.org/ns#> . +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . +@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> . + +test: a nrl:Namespace, nrl:Ontology ; + nrl:lastModified "2010-03-24T11:00:04Z" ; + nrl:prefix "test" . + +test:A a rdfs:Class ; + rdfs:subClassOf rdfs:Resource . + +test:B a rdfs:Class ; + rdfs:subClassOf rdfs:Resource . + +# The property is removed +# test:a_tmp_prop a rdf:Property ; +# rdfs:range xsd:integer ; +# rdfs:domain test:A . + +# The range changed from xsd:boolean to xsd:string +test:b_range_boolean_string a rdf:Property ; + rdfs:range xsd:string ; + rdfs:domain test:B . + +# The domain is changed from test:B to test:A +test:b_a_domain a rdf:Property ; + rdfs:range xsd:integer ; + rdfs:domain test:A . + +# The domain and range are exchanged +test:a_b_domain_range a rdf:Property ; + rdfs:range test:A ; + rdfs:domain test:B . diff --git a/tests/functional-tests/test-ontologies/simple/92-test.ontology b/tests/functional-tests/test-ontologies/simple/92-test.ontology new file mode 100644 index 000000000..b554009af --- /dev/null +++ b/tests/functional-tests/test-ontologies/simple/92-test.ontology @@ -0,0 +1,13 @@ +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . +@prefix test: <http://example.org/ns#> . +@prefix test2: <http://example2.org/ns#> . +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . +@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> . + +test2: a nrl:Namespace, nrl:Ontology ; + nrl:lastModified "2010-03-24T11:00:04Z" ; + nrl:prefix "test2" . + +test2:C a rdfs:Class ; + rdfs:subClassOf test:B . # Was test:A |