anki-sync-server/tests/helpers/db_utils.py
Christoph Mack cb574aa0a7 Add test helper classes for creating and managing temporary files, working with anki collections and sqlite dbs. Add class for
managing users so users can be added programmatically without using ankiserverctl.
2017-10-28 21:55:19 +02:00

129 lines
4.4 KiB
Python

# -*- coding: utf-8 -*-
import os
import sqlite3
import subprocess
from helpers.file_utils import FileUtils
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.
: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()
connection.close()
return db_path
@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
"""
if type(database) == str:
connection = sqlite3.connect(database)
else:
connection = database
res = '\n'.join(connection.iterdump())
if type(database) == str:
connection.close()
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
: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