Make DB utils more general
This commit is contained in:
parent
55bdbfacaa
commit
ce3aa4a685
1
tests/helpers/__init__.py
Normal file
1
tests/helpers/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
import db_utils
|
||||||
@ -3,38 +3,23 @@ import os
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from helpers.file_utils import FileUtils
|
|
||||||
|
|
||||||
|
def from_sql(path, sql):
|
||||||
class DBUtils(object):
|
|
||||||
"""Provides methods for creating and comparing sqlite databases."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.fileutils = FileUtils()
|
|
||||||
|
|
||||||
def clean_up(self):
|
|
||||||
self.fileutils.clean_up()
|
|
||||||
|
|
||||||
def create_sqlite_db_with_sql(self, sql_string):
|
|
||||||
"""
|
"""
|
||||||
Creates an SQLite db and executes the passed sql statements on it.
|
Creates an SQLite db and executes the passed sql statements on it.
|
||||||
|
|
||||||
:param sql_string: the sql statements to execute on the newly created
|
:param path: the path to the created db file
|
||||||
db
|
:param sql: 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(path)
|
||||||
connection = sqlite3.connect(db_path)
|
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.executescript(sql_string)
|
cursor.executescript(sql)
|
||||||
connection.commit()
|
connection.commit()
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
return db_path
|
|
||||||
|
|
||||||
@staticmethod
|
def to_sql(database):
|
||||||
def sqlite_db_to_sql_string(database):
|
|
||||||
"""
|
"""
|
||||||
Returns a string containing the sql export of the database. Used for
|
Returns a string containing the sql export of the database. Used for
|
||||||
debugging.
|
debugging.
|
||||||
@ -56,44 +41,8 @@ class DBUtils(object):
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
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
|
def diff(left_db_path, right_db_path):
|
||||||
: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.
|
Uses the sqldiff cli tool to compare two sqlite files for equality.
|
||||||
Returns True if the databases differ, False if they don't.
|
Returns True if the databases differ, False if they don't.
|
||||||
@ -105,7 +54,6 @@ class DBUtils(object):
|
|||||||
|
|
||||||
command = ["/bin/sqldiff", left_db_path, right_db_path]
|
command = ["/bin/sqldiff", left_db_path, right_db_path]
|
||||||
|
|
||||||
try:
|
|
||||||
child_process = subprocess.Popen(command,
|
child_process = subprocess.Popen(command,
|
||||||
shell=False,
|
shell=False,
|
||||||
stdout=subprocess.PIPE)
|
stdout=subprocess.PIPE)
|
||||||
@ -121,5 +69,3 @@ class DBUtils(object):
|
|||||||
|
|
||||||
# Any output from sqldiff means the databases differ.
|
# Any output from sqldiff means the databases differ.
|
||||||
return stdout != ""
|
return stdout != ""
|
||||||
except OSError as err:
|
|
||||||
raise err
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@ from webtest import TestApp
|
|||||||
|
|
||||||
from ankisyncd.users import SqliteUserManager
|
from ankisyncd.users import SqliteUserManager
|
||||||
from helpers.collection_utils import CollectionUtils
|
from helpers.collection_utils import CollectionUtils
|
||||||
from helpers.db_utils import DBUtils
|
|
||||||
from helpers.file_utils import FileUtils
|
from helpers.file_utils import FileUtils
|
||||||
from helpers.mock_servers import MockRemoteServer
|
from helpers.mock_servers import MockRemoteServer
|
||||||
from helpers.monkey_patches import monkeypatch_db, unpatch_db
|
from helpers.monkey_patches import monkeypatch_db, unpatch_db
|
||||||
@ -19,7 +18,6 @@ class SyncAppFunctionalTestBase(unittest.TestCase):
|
|||||||
cls.fileutils = FileUtils()
|
cls.fileutils = FileUtils()
|
||||||
cls.colutils = CollectionUtils()
|
cls.colutils = CollectionUtils()
|
||||||
cls.serverutils = ServerUtils()
|
cls.serverutils = ServerUtils()
|
||||||
cls.dbutils = DBUtils()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
@ -32,9 +30,6 @@ class SyncAppFunctionalTestBase(unittest.TestCase):
|
|||||||
cls.serverutils.clean_up()
|
cls.serverutils.clean_up()
|
||||||
cls.serverutils = None
|
cls.serverutils = None
|
||||||
|
|
||||||
cls.dbutils.clean_up()
|
|
||||||
cls.dbutils = None
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
monkeypatch_db()
|
monkeypatch_db()
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import tempfile
|
||||||
import filecmp
|
import filecmp
|
||||||
|
import sqlite3
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
import helpers.db_utils
|
||||||
from anki.sync import MediaSyncer
|
from anki.sync import MediaSyncer
|
||||||
from helpers.mock_servers import MockRemoteMediaServer
|
from helpers.mock_servers import MockRemoteMediaServer
|
||||||
from helpers.monkey_patches import monkeypatch_mediamanager, unpatch_mediamanager
|
from helpers.monkey_patches import monkeypatch_mediamanager, unpatch_mediamanager
|
||||||
@ -34,6 +37,43 @@ class SyncAppFunctionalMediaTest(SyncAppFunctionalTestBase):
|
|||||||
server=mock_remote_server)
|
server=mock_remote_server)
|
||||||
return media_syncer
|
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):
|
def test_sync_empty_media_dbs(self):
|
||||||
# With both the client and the server having no media to sync,
|
# With both the client and the server having no media to sync,
|
||||||
# syncing should change nothing.
|
# syncing should change nothing.
|
||||||
@ -105,8 +145,7 @@ class SyncAppFunctionalMediaTest(SyncAppFunctionalTestBase):
|
|||||||
# Except for timestamps, the media databases of client and server
|
# Except for timestamps, the media databases of client and server
|
||||||
# should be identical.
|
# should be identical.
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
self.dbutils.media_dbs_differ(client.col.media.db._path,
|
self.media_dbs_differ(client.col.media.db._path, server.col.media.db._path)
|
||||||
server.col.media.db._path)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_sync_different_files(self):
|
def test_sync_different_files(self):
|
||||||
@ -301,11 +340,13 @@ class SyncAppFunctionalMediaTest(SyncAppFunctionalTestBase):
|
|||||||
CREATE INDEX idx_media_dirty on media (dirty);
|
CREATE INDEX idx_media_dirty on media (dirty);
|
||||||
""" % chksum)
|
""" % 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
|
# Except for timestamps, the client's db after sync should be identical
|
||||||
# to the expected data.
|
# to the expected data.
|
||||||
self.assertFalse(self.dbutils.media_dbs_differ(
|
self.assertFalse(self.media_dbs_differ(
|
||||||
client.col.media.db._path,
|
client.col.media.db._path,
|
||||||
temp_db_path
|
dbpath
|
||||||
))
|
))
|
||||||
|
os.unlink(dbpath)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user