diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py new file mode 100644 index 0000000..e118e2d --- /dev/null +++ b/tests/helpers/__init__.py @@ -0,0 +1 @@ +import db_utils \ No newline at end of file diff --git a/tests/helpers/db_utils.py b/tests/helpers/db_utils.py index 2b1a278..0bae2ed 100644 --- a/tests/helpers/db_utils.py +++ b/tests/helpers/db_utils.py @@ -3,123 +3,69 @@ import os import sqlite3 import subprocess -from helpers.file_utils import FileUtils + +def from_sql(path, sql): + """ + Creates an SQLite db and executes the passed sql statements on it. + + :param path: the path to the created db file + :param sql: the sql statements to execute on the newly created db + """ + + connection = sqlite3.connect(path) + cursor = connection.cursor() + cursor.executescript(sql) + connection.commit() + connection.close() -class DBUtils(object): - """Provides methods for creating and comparing sqlite databases.""" +def to_sql(database): + """ + Returns a string containing the sql export of the database. Used for + debugging. - def __init__(self): - self.fileutils = FileUtils() + :param database: either the path to the SQLite db file or an open + connection to it + :return: a string representing the sql export of the database + """ - def clean_up(self): - self.fileutils.clean_up() + if type(database) == str: + connection = sqlite3.connect(database) + else: + connection = database - def create_sqlite_db_with_sql(self, sql_string): - """ - Creates an SQLite db and executes the passed sql statements on it. + res = '\n'.join(connection.iterdump()) - :param sql_string: the sql statements to execute on the newly created - db - :return: the path to the created db file - """ - - db_path = self.fileutils.create_file_path(suffix=".anki2") - connection = sqlite3.connect(db_path) - cursor = connection.cursor() - cursor.executescript(sql_string) - connection.commit() + if type(database) == str: connection.close() - return db_path + return res - @staticmethod - def sqlite_db_to_sql_string(database): - """ - Returns a string containing the sql export of the database. Used for - debugging. - :param database: either the path to the SQLite db file or an open - connection to it - :return: a string representing the sql export of the database - """ +def diff(left_db_path, right_db_path): + """ + Uses the sqldiff cli tool to compare two sqlite files for equality. + Returns True if the databases differ, False if they don't. - if type(database) == str: - connection = sqlite3.connect(database) - else: - connection = database + :param left_db_path: path to the left db file + :param right_db_path: path to the right db file + :return: True if the specified databases differ, False else + """ - res = '\n'.join(connection.iterdump()) + command = ["/bin/sqldiff", left_db_path, right_db_path] - if type(database) == str: - connection.close() + child_process = subprocess.Popen(command, + shell=False, + stdout=subprocess.PIPE) + stdout, stderr = child_process.communicate() + exit_code = child_process.returncode - return res + if exit_code != 0 or stderr is not None: + raise RuntimeError("Command {} encountered an error, exit " + "code: {}, stderr: {}" + .format(" ".join(command), + exit_code, + stderr)) - def media_dbs_differ(self, left_db_path, right_db_path, compare_timestamps=False): - """ - Compares two media sqlite database files for equality. mtime and dirMod - timestamps are not considered when comparing. - - :param left_db_path: path to the left db file - :param right_db_path: path to the right db file - :param compare_timestamps: flag determining if timestamp values - (media.mtime and meta.dirMod) are included - in the comparison - :return: True if the specified databases differ, False else - """ - - if not os.path.isfile(left_db_path): - raise IOError("file '" + left_db_path + "' does not exist") - elif not os.path.isfile(right_db_path): - raise IOError("file '" + right_db_path + "' does not exist") - - # Create temporary copies of the files to act on. - left_db_path = self.fileutils.create_file_copy(left_db_path) - right_db_path = self.fileutils.create_file_copy(right_db_path) - - if not compare_timestamps: - # Set all timestamps that are not NULL to 0. - for dbPath in [left_db_path, right_db_path]: - connection = sqlite3.connect(dbPath) - - connection.execute("""UPDATE media SET mtime=0 - WHERE mtime IS NOT NULL""") - - connection.execute("""UPDATE meta SET dirMod=0 - WHERE rowid=1""") - connection.commit() - connection.close() - - return self.__sqlite_dbs_differ(left_db_path, right_db_path) - - def __sqlite_dbs_differ(self, left_db_path, right_db_path): - """ - Uses the sqldiff cli tool to compare two sqlite files for equality. - Returns True if the databases differ, False if they don't. - - :param left_db_path: path to the left db file - :param right_db_path: path to the right db file - :return: True if the specified databases differ, False else - """ - - command = ["/bin/sqldiff", left_db_path, right_db_path] - - try: - child_process = subprocess.Popen(command, - shell=False, - stdout=subprocess.PIPE) - stdout, stderr = child_process.communicate() - exit_code = child_process.returncode - - if exit_code != 0 or stderr is not None: - raise RuntimeError("Command {} encountered an error, exit " - "code: {}, stderr: {}" - .format(" ".join(command), - exit_code, - stderr)) - - # Any output from sqldiff means the databases differ. - return stdout != "" - except OSError as err: - raise err + # Any output from sqldiff means the databases differ. + return stdout != "" diff --git a/tests/sync_app_functional_test_base.py b/tests/sync_app_functional_test_base.py index c9fa225..22d94ee 100644 --- a/tests/sync_app_functional_test_base.py +++ b/tests/sync_app_functional_test_base.py @@ -5,7 +5,6 @@ from webtest import TestApp from ankisyncd.users import SqliteUserManager from helpers.collection_utils import CollectionUtils -from helpers.db_utils import DBUtils from helpers.file_utils import FileUtils from helpers.mock_servers import MockRemoteServer from helpers.monkey_patches import monkeypatch_db, unpatch_db @@ -19,7 +18,6 @@ class SyncAppFunctionalTestBase(unittest.TestCase): cls.fileutils = FileUtils() cls.colutils = CollectionUtils() cls.serverutils = ServerUtils() - cls.dbutils = DBUtils() @classmethod def tearDownClass(cls): @@ -32,9 +30,6 @@ class SyncAppFunctionalTestBase(unittest.TestCase): cls.serverutils.clean_up() cls.serverutils = None - cls.dbutils.clean_up() - cls.dbutils = None - def setUp(self): monkeypatch_db() diff --git a/tests/test_web_media.py b/tests/test_web_media.py index 56db276..72446d1 100644 --- a/tests/test_web_media.py +++ b/tests/test_web_media.py @@ -1,7 +1,10 @@ # -*- coding: utf-8 -*- +import tempfile import filecmp +import sqlite3 import os +import helpers.db_utils from anki.sync import MediaSyncer from helpers.mock_servers import MockRemoteMediaServer from helpers.monkey_patches import monkeypatch_mediamanager, unpatch_mediamanager @@ -34,6 +37,43 @@ class SyncAppFunctionalMediaTest(SyncAppFunctionalTestBase): server=mock_remote_server) return media_syncer + def media_dbs_differ(self, left_db_path, right_db_path, compare_timestamps=False): + """ + Compares two media sqlite database files for equality. mtime and dirMod + timestamps are not considered when comparing. + + :param left_db_path: path to the left db file + :param right_db_path: path to the right db file + :param compare_timestamps: flag determining if timestamp values + (media.mtime and meta.dirMod) are included + in the comparison + :return: True if the specified databases differ, False else + """ + + if not os.path.isfile(right_db_path): + raise IOError("file '" + left_db_path + "' does not exist") + elif not os.path.isfile(right_db_path): + raise IOError("file '" + right_db_path + "' does not exist") + + # Create temporary copies of the files to act on. + left_db_path = self.fileutils.create_file_copy(left_db_path) + right_db_path = self.fileutils.create_file_copy(right_db_path) + + if not compare_timestamps: + # Set all timestamps that are not NULL to 0. + for dbPath in [left_db_path, right_db_path]: + connection = sqlite3.connect(dbPath) + + connection.execute("""UPDATE media SET mtime=0 + WHERE mtime IS NOT NULL""") + + connection.execute("""UPDATE meta SET dirMod=0 + WHERE rowid=1""") + connection.commit() + connection.close() + + return helpers.db_utils.diff(left_db_path, right_db_path) + def test_sync_empty_media_dbs(self): # With both the client and the server having no media to sync, # syncing should change nothing. @@ -105,8 +145,7 @@ class SyncAppFunctionalMediaTest(SyncAppFunctionalTestBase): # Except for timestamps, the media databases of client and server # should be identical. self.assertFalse( - self.dbutils.media_dbs_differ(client.col.media.db._path, - server.col.media.db._path) + self.media_dbs_differ(client.col.media.db._path, server.col.media.db._path) ) def test_sync_different_files(self): @@ -301,11 +340,13 @@ class SyncAppFunctionalMediaTest(SyncAppFunctionalTestBase): CREATE INDEX idx_media_dirty on media (dirty); """ % chksum) - temp_db_path = self.dbutils.create_sqlite_db_with_sql(sql) + _, dbpath = tempfile.mkstemp(suffix=".anki2") + helpers.db_utils.from_sql(dbpath, sql) # Except for timestamps, the client's db after sync should be identical # to the expected data. - self.assertFalse(self.dbutils.media_dbs_differ( + self.assertFalse(self.media_dbs_differ( client.col.media.db._path, - temp_db_path + dbpath )) + os.unlink(dbpath)