diff --git a/openlp/core/common/__init__.py b/openlp/core/common/__init__.py index f1abd8fe6..9afc08c8f 100644 --- a/openlp/core/common/__init__.py +++ b/openlp/core/common/__init__.py @@ -195,7 +195,7 @@ def verify_ip_address(addr): return True if verify_ipv4(addr) else verify_ipv6(addr) -def md5_hash(salt, data=None): +def md5_hash(salt=None, data=None): """ Returns the hashed output of md5sum on salt,data using Python3 hashlib @@ -205,8 +205,11 @@ def md5_hash(salt, data=None): :returns: str """ log.debug('md5_hash(salt="{text}")'.format(text=salt)) + if not salt and not data: + return None hash_obj = hashlib.new('md5') - hash_obj.update(salt) + if salt: + hash_obj.update(salt) if data: hash_obj.update(data) hash_value = hash_obj.hexdigest() @@ -214,22 +217,30 @@ def md5_hash(salt, data=None): return hash_value -def qmd5_hash(salt, data=None): +def qmd5_hash(salt=None, data=None): """ Returns the hashed output of MD5Sum on salt, data - using PyQt5.QCryptographicHash. + using PyQt5.QCryptographicHash. Function returns a + QByteArray instead of a text string. + If you need a string instead, call with + + result = str(qmd5_hash(salt=..., data=...), encoding='ascii') :param salt: Initial salt :param data: OPTIONAL Data to hash - :returns: str + :returns: QByteArray """ log.debug('qmd5_hash(salt="{text}"'.format(text=salt)) + if salt is None and data is None: + return None hash_obj = QHash(QHash.Md5) - hash_obj.addData(salt) - hash_obj.addData(data) + if salt: + hash_obj.addData(salt) + if data: + hash_obj.addData(data) hash_value = hash_obj.result().toHex() - log.debug('qmd5_hash() returning "{text}"'.format(text=hash_value)) - return hash_value.data() + log.debug('qmd5_hash() returning "{hash}"'.format(hash=hash_value)) + return hash_value def clean_button_text(button_text): diff --git a/openlp/core/common/actions.py b/openlp/core/common/actions.py index d22ef8fd1..5e5dd2e05 100644 --- a/openlp/core/common/actions.py +++ b/openlp/core/common/actions.py @@ -138,7 +138,7 @@ class CategoryList(object): for category in self.categories: if category.name == key: return category - raise KeyError('Category "{keY}" does not exist.'.format(key=key)) + raise KeyError('Category "{key}" does not exist.'.format(key=key)) def __len__(self): """ diff --git a/openlp/core/common/db.py b/openlp/core/common/db.py index 1e18167ab..1fd7a6521 100644 --- a/openlp/core/common/db.py +++ b/openlp/core/common/db.py @@ -22,11 +22,12 @@ """ The :mod:`db` module provides helper functions for database related methods. """ -import sqlalchemy import logging - from copy import deepcopy +import sqlalchemy + + log = logging.getLogger(__name__) diff --git a/openlp/core/common/languagemanager.py b/openlp/core/common/languagemanager.py index 58262ffb5..099e84af6 100644 --- a/openlp/core/common/languagemanager.py +++ b/openlp/core/common/languagemanager.py @@ -168,7 +168,7 @@ def format_time(text, local_time): """ return local_time.strftime(match.group()) - return re.sub('\%[a-zA-Z]', match_formatting, text) + return re.sub(r'\%[a-zA-Z]', match_formatting, text) def get_locale_key(string): diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index f05e23ec2..25b417217 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -26,7 +26,7 @@ import datetime import logging import os -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5 import QtCore, QtGui from openlp.core.common import ThemeLevel, SlideLimits, UiStrings, is_win, is_linux diff --git a/openlp/core/common/uistrings.py b/openlp/core/common/uistrings.py index 91db10fcf..dccc3bdb4 100644 --- a/openlp/core/common/uistrings.py +++ b/openlp/core/common/uistrings.py @@ -68,7 +68,7 @@ class UiStrings(object): self.Default = translate('OpenLP.Ui', 'Default') self.DefaultColor = translate('OpenLP.Ui', 'Default Color:') self.DefaultServiceName = translate('OpenLP.Ui', 'Service %Y-%m-%d %H-%M', - 'This may not contain any of the following characters: /\\?*|<>\[\]":+\n' + 'This may not contain any of the following characters: /\\?*|<>[]":+\n' 'See http://docs.python.org/library/datetime' '.html#strftime-strptime-behavior for more information.') self.Delete = translate('OpenLP.Ui', '&Delete') diff --git a/openlp/core/common/versionchecker.py b/openlp/core/common/versionchecker.py index fb706968b..25479884f 100644 --- a/openlp/core/common/versionchecker.py +++ b/openlp/core/common/versionchecker.py @@ -10,10 +10,10 @@ from datetime import datetime from distutils.version import LooseVersion from subprocess import Popen, PIPE -from openlp.core.common import AppLocation, Settings - from PyQt5 import QtCore +from openlp.core.common import AppLocation, Settings + log = logging.getLogger(__name__) APPLICATION_VERSION = {} diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 65c2a972f..c3e766433 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -97,7 +97,7 @@ def get_text_file_string(text_file): content = None try: file_handle = open(text_file, 'r', encoding='utf-8') - if not file_handle.read(3) == '\xEF\xBB\xBF': + if file_handle.read(3) != '\xEF\xBB\xBF': # no BOM was found file_handle.seek(0) content = file_handle.read() diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py index 3decb0a3b..f42c3b5fc 100644 --- a/openlp/core/lib/db.py +++ b/openlp/core/lib/db.py @@ -122,6 +122,21 @@ def get_upgrade_op(session): return Operations(context) +class BaseModel(object): + """ + BaseModel provides a base object with a set of generic functions + """ + @classmethod + def populate(cls, **kwargs): + """ + Creates an instance of a class and populates it, returning the instance + """ + instance = cls() + for key, value in kwargs.items(): + instance.__setattr__(key, value) + return instance + + def upgrade_db(url, upgrade): """ Upgrade a database. @@ -178,9 +193,9 @@ def upgrade_db(url, upgrade): version_meta = Metadata.populate(key='version', value=int(upgrade.__version__)) session.commit() upgrade_version = upgrade.__version__ - version_meta = int(version_meta.value) + version = int(version_meta.value) session.close() - return version_meta, upgrade_version + return version, upgrade_version def delete_database(plugin_name, db_file_name=None): @@ -197,21 +212,6 @@ def delete_database(plugin_name, db_file_name=None): return delete_file(db_file_path) -class BaseModel(object): - """ - BaseModel provides a base object with a set of generic functions - """ - @classmethod - def populate(cls, **kwargs): - """ - Creates an instance of a class and populates it, returning the instance - """ - instance = cls() - for key, value in kwargs.items(): - instance.__setattr__(key, value) - return instance - - class Manager(object): """ Provide generic object persistence management diff --git a/openlp/core/lib/htmlbuilder.py b/openlp/core/lib/htmlbuilder.py index dd5f6c3f0..e0afa1d6b 100644 --- a/openlp/core/lib/htmlbuilder.py +++ b/openlp/core/lib/htmlbuilder.py @@ -389,8 +389,8 @@ is the function which has to be called from outside. The generated and returned """ import logging -from PyQt5 import QtWebKit from string import Template +from PyQt5 import QtWebKit from openlp.core.common import Settings from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, VerticalType, HorizontalType @@ -670,7 +670,7 @@ def webkit_version(): webkit_ver = float(QtWebKit.qWebKitVersion()) log.debug('Webkit version = {version}'.format(version=webkit_ver)) except AttributeError: - webkit_ver = 0 + webkit_ver = 0.0 return webkit_ver diff --git a/openlp/core/lib/imagemanager.py b/openlp/core/lib/imagemanager.py index 1c25fca25..7e0cd3212 100644 --- a/openlp/core/lib/imagemanager.py +++ b/openlp/core/lib/imagemanager.py @@ -272,7 +272,7 @@ class ImageManager(QtCore.QObject): Add image to cache if it is not already there. """ log.debug('add_image {path}'.format(path=path)) - if not (path, source, width, height) in self._cache: + if (path, source, width, height) not in self._cache: image = Image(path, source, background, width, height) self._cache[(path, source, width, height)] = image self._conversion_queue.put((image.priority, image.secondary_priority, image)) diff --git a/openlp/core/lib/pluginmanager.py b/openlp/core/lib/pluginmanager.py index 4eb4c2f01..98f49a2c6 100644 --- a/openlp/core/lib/pluginmanager.py +++ b/openlp/core/lib/pluginmanager.py @@ -23,7 +23,6 @@ Provide plugin management """ import os -import sys import imp from openlp.core.lib import Plugin, PluginStatus diff --git a/openlp/core/lib/projector/db.py b/openlp/core/lib/projector/db.py index 98778e695..9d223b0e1 100644 --- a/openlp/core/lib/projector/db.py +++ b/openlp/core/lib/projector/db.py @@ -40,13 +40,12 @@ log.debug('projector.lib.db module loaded') from sqlalchemy import Column, ForeignKey, Integer, MetaData, String, and_ from sqlalchemy.ext.declarative import declarative_base, declared_attr -from sqlalchemy.orm import backref, relationship +from sqlalchemy.orm import relationship from openlp.core.lib.db import Manager, init_db, init_url from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES -metadata = MetaData() -Base = declarative_base(metadata) +Base = declarative_base(MetaData()) class CommonBase(object): @@ -54,8 +53,8 @@ class CommonBase(object): Base class to automate table name and ID column. """ @declared_attr - def __tablename__(cls): - return cls.__name__.lower() + def __tablename__(self): + return self.__name__.lower() id = Column(Integer, primary_key=True) @@ -257,7 +256,7 @@ class ProjectorDB(Manager): projector = self.get_object_filtered(Projector, Projector.id == dbid) if projector is None: # Not found - log.warn('get_projector_by_id() did not find {data}'.format(data=id)) + log.warning('get_projector_by_id() did not find {data}'.format(data=id)) return None log.debug('get_projectorby_id() returning 1 entry for "{entry}" id="{data}"'.format(entry=dbid, data=projector.id)) @@ -290,7 +289,7 @@ class ProjectorDB(Manager): projector = self.get_object_filtered(Projector, Projector.ip == ip) if projector is None: # Not found - log.warn('get_projector_by_ip() did not find {ip}'.format(ip=ip)) + log.warning('get_projector_by_ip() did not find {ip}'.format(ip=ip)) return None log.debug('get_projectorby_ip() returning 1 entry for "{ip}" id="{data}"'.format(ip=ip, data=projector.id)) @@ -307,7 +306,7 @@ class ProjectorDB(Manager): projector = self.get_object_filtered(Projector, Projector.name == name) if projector is None: # Not found - log.warn('get_projector_by_name() did not find "{name}"'.format(name=name)) + log.warning('get_projector_by_name() did not find "{name}"'.format(name=name)) return None log.debug('get_projector_by_name() returning one entry for "{name}" id="{data}"'.format(name=name, data=projector.id)) @@ -324,7 +323,7 @@ class ProjectorDB(Manager): """ old_projector = self.get_object_filtered(Projector, Projector.ip == projector.ip) if old_projector is not None: - log.warn('add_new() skipping entry ip="{ip}" (Already saved)'.format(ip=old_projector.ip)) + log.warning('add_new() skipping entry ip="{ip}" (Already saved)'.format(ip=old_projector.ip)) return False log.debug('add_new() saving new entry') log.debug('ip="{ip}", name="{name}", location="{location}"'.format(ip=projector.ip, @@ -408,10 +407,10 @@ class ProjectorDB(Manager): :param source: ProjectorSource id :returns: ProjetorSource instance or None """ - source_entry = self.get_object_filtered(ProjetorSource, ProjectorSource.id == source) + source_entry = self.get_object_filtered(ProjectorSource, ProjectorSource.id == source) if source_entry is None: # Not found - log.warn('get_source_by_id() did not find "{source}"'.format(source=source)) + log.warning('get_source_by_id() did not find "{source}"'.format(source=source)) return None log.debug('get_source_by_id() returning one entry for "{source}""'.format(source=source)) return source_entry @@ -430,8 +429,8 @@ class ProjectorDB(Manager): if source_entry is None: # Not found - log.warn('get_source_by_id() not found') - log.warn('code="{code}" projector_id="{data}"'.format(code=code, data=projector_id)) + log.warning('get_source_by_id() not found') + log.warning('code="{code}" projector_id="{data}"'.format(code=code, data=projector_id)) return None log.debug('get_source_by_id() returning one entry') log.debug('code="{code}" projector_id="{data}"'.format(code=code, data=projector_id)) diff --git a/openlp/core/lib/projector/pjlink1.py b/openlp/core/lib/projector/pjlink1.py index ce06b3625..8050a6407 100644 --- a/openlp/core/lib/projector/pjlink1.py +++ b/openlp/core/lib/projector/pjlink1.py @@ -297,6 +297,8 @@ class PJLink1(QTcpSocket): Processes the initial connection and authentication (if needed). Starts poll timer if connection is established. + NOTE: Qt md5 hash function doesn't work with projector authentication. Use the python md5 hash function. + :param data: Optional data if called from another routine """ log.debug('({ip}) check_login(data="{data}")'.format(ip=self.ip, data=data)) @@ -310,10 +312,10 @@ class PJLink1(QTcpSocket): read = self.readLine(self.maxSize) dontcare = self.readLine(self.maxSize) # Clean out the trailing \r\n if read is None: - log.warn('({ip}) read is None - socket error?'.format(ip=self.ip)) + log.warning('({ip}) read is None - socket error?'.format(ip=self.ip)) return elif len(read) < 8: - log.warn('({ip}) Not enough data read)'.format(ip=self.ip)) + log.warning('({ip}) Not enough data read)'.format(ip=self.ip)) return data = decode(read, 'ascii') # Possibility of extraneous data on input when reading. @@ -344,23 +346,33 @@ class PJLink1(QTcpSocket): return elif data_check[1] == '0' and self.pin is not None: # Pin set and no authentication needed + log.warning('({ip}) Regular connection but PIN set'.format(ip=self.name)) self.disconnect_from_host() self.change_status(E_AUTHENTICATION) - log.debug('({ip}) emitting projectorNoAuthentication() signal'.format(ip=self.name)) + log.debug('({ip}) Emitting projectorNoAuthentication() signal'.format(ip=self.name)) self.projectorNoAuthentication.emit(self.name) return elif data_check[1] == '1': # Authenticated login with salt - log.debug('({ip}) Setting hash with salt="{data}"'.format(ip=self.ip, data=data_check[2])) - log.debug('({ip}) pin="{data}"'.format(ip=self.ip, data=self.pin)) - salt = qmd5_hash(salt=data_check[2].encode('ascii'), data=self.pin.encode('ascii')) + if self.pin is None: + log.warning('({ip}) Authenticated connection but no pin set'.format(ip=self.name)) + self.disconnect_from_host() + self.change_status(E_AUTHENTICATION) + log.debug('({ip}) Emitting projectorAuthentication() signal'.format(ip=self.name)) + self.projectorAuthentication.emit(self.name) + return + else: + log.debug('({ip}) Setting hash with salt="{data}"'.format(ip=self.ip, data=data_check[2])) + log.debug('({ip}) pin="{data}"'.format(ip=self.ip, data=self.pin)) + data_hash = str(qmd5_hash(salt=data_check[2].encode('utf-8'), data=self.pin.encode('utf-8')), + encoding='ascii') else: - salt = None - # We're connected at this point, so go ahead and do regular I/O + data_hash = None + # We're connected at this point, so go ahead and setup regular I/O self.readyRead.connect(self.get_data) self.projectorReceivedData.connect(self._send_command) # Initial data we should know about - self.send_command(cmd='CLSS', salt=salt) + self.send_command(cmd='CLSS', salt=data_hash) self.waitForReadyRead() if (not self.no_poll) and (self.state() == self.ConnectedState): log.debug('({ip}) Starting timer'.format(ip=self.ip)) @@ -402,7 +414,7 @@ class PJLink1(QTcpSocket): self.projectorReceivedData.emit() return elif '=' not in data: - log.warn('({ip}) get_data(): Invalid packet received'.format(ip=self.ip)) + log.warning('({ip}) get_data(): Invalid packet received'.format(ip=self.ip)) self.send_busy = False self.projectorReceivedData.emit() return @@ -410,15 +422,15 @@ class PJLink1(QTcpSocket): try: (prefix, class_, cmd, data) = (data_split[0][0], data_split[0][1], data_split[0][2:], data_split[1]) except ValueError as e: - log.warn('({ip}) get_data(): Invalid packet - expected header + command + data'.format(ip=self.ip)) - log.warn('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in.strip())) + log.warning('({ip}) get_data(): Invalid packet - expected header + command + data'.format(ip=self.ip)) + log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in.strip())) self.change_status(E_INVALID_DATA) self.send_busy = False self.projectorReceivedData.emit() return if not (self.pjlink_class in PJLINK_VALID_CMD and cmd in PJLINK_VALID_CMD[self.pjlink_class]): - log.warn('({ip}) get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.ip, data=cmd)) + log.warning('({ip}) get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.ip, data=cmd)) self.send_busy = False self.projectorReceivedData.emit() return @@ -461,7 +473,7 @@ class PJLink1(QTcpSocket): :param queue: Option to force add to queue rather than sending directly """ if self.state() != self.ConnectedState: - log.warn('({ip}) send_command(): Not connected - returning'.format(ip=self.ip)) + log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.ip)) self.send_queue = [] return self.projectorNetwork.emit(S_NETWORK_SENDING) @@ -577,7 +589,7 @@ class PJLink1(QTcpSocket): if cmd in self.PJLINK1_FUNC: self.PJLINK1_FUNC[cmd](data) else: - log.warn('({ip}) Invalid command {data}'.format(ip=self.ip, data=cmd)) + log.warning('({ip}) Invalid command {data}'.format(ip=self.ip, data=cmd)) self.send_busy = False self.projectorReceivedData.emit() @@ -596,7 +608,7 @@ class PJLink1(QTcpSocket): fill = {'Hours': int(data_dict[0]), 'On': False if data_dict[1] == '0' else True} except ValueError: # In case of invalid entry - log.warn('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.ip, data=data)) + log.warning('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.ip, data=data)) return lamps.append(fill) data_dict.pop(0) # Remove lamp hours @@ -623,7 +635,7 @@ class PJLink1(QTcpSocket): self.send_command('INST') else: # Log unknown status response - log.warn('({ip}) Unknown power response: {data}'.format(ip=self.ip, data=data)) + log.warning('({ip}) Unknown power response: {data}'.format(ip=self.ip, data=data)) return def process_avmt(self, data): @@ -648,7 +660,7 @@ class PJLink1(QTcpSocket): shutter = True mute = True else: - log.warn('({ip}) Unknown shutter response: {data}'.format(ip=self.ip, data=data)) + log.warning('({ip}) Unknown shutter response: {data}'.format(ip=self.ip, data=data)) update_icons = shutter != self.shutter update_icons = update_icons or mute != self.mute self.shutter = shutter @@ -797,7 +809,7 @@ class PJLink1(QTcpSocket): Initiate connection to projector. """ if self.state() == self.ConnectedState: - log.warn('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.ip)) + log.warning('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.ip)) return self.change_status(S_CONNECTING) self.connectToHost(self.ip, self.port if type(self.port) is int else int(self.port)) @@ -809,9 +821,9 @@ class PJLink1(QTcpSocket): """ if abort or self.state() != self.ConnectedState: if abort: - log.warn('({ip}) disconnect_from_host(): Aborting connection'.format(ip=self.ip)) + log.warning('({ip}) disconnect_from_host(): Aborting connection'.format(ip=self.ip)) else: - log.warn('({ip}) disconnect_from_host(): Not connected - returning'.format(ip=self.ip)) + log.warning('({ip}) disconnect_from_host(): Not connected - returning'.format(ip=self.ip)) self.reset_information() self.disconnectFromHost() try: diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index 95f46ede0..6a176fcc9 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -531,7 +531,7 @@ def words_split(line): :param line: Line to be split """ # this parse we are to be wordy - return re.split('\s+', line) + return re.split(r'\s+', line) def get_start_tags(raw_text): diff --git a/openlp/core/lib/theme.py b/openlp/core/lib/theme.py index 4e84d353b..c27b08cd0 100644 --- a/openlp/core/lib/theme.py +++ b/openlp/core/lib/theme.py @@ -23,7 +23,6 @@ Provide the theme XML and handling functions for OpenLP v2 themes. """ import os -import re import logging import json @@ -165,6 +164,7 @@ class ThemeXML(object): jsn = get_text_file_string(json_file) jsn = json.loads(jsn) self.expand_json(jsn) + self.background_filename = None def expand_json(self, var, prev=None): """ @@ -474,15 +474,16 @@ class ThemeXML(object): if element.startswith('shadow') or element.startswith('outline'): master = 'font_main' # fix bold font + ret_value = None if element == 'weight': element = 'bold' if value == 'Normal': - value = False + ret_value = False else: - value = True + ret_value = True if element == 'proportion': element = 'size' - return False, master, element, value + return False, master, element, ret_value if ret_value is not None else value def _create_attr(self, master, element, value): """ diff --git a/openlp/core/lib/webpagereader.py b/openlp/core/lib/webpagereader.py index 260ef1556..52c98bbaf 100644 --- a/openlp/core/lib/webpagereader.py +++ b/openlp/core/lib/webpagereader.py @@ -179,5 +179,4 @@ def get_web_page(url, header=None, update_openlp=False): return page -__all__ = ['get_application_version', 'check_latest_version', - 'get_web_page'] +__all__ = ['get_web_page'] diff --git a/openlp/core/ui/exceptionform.py b/openlp/core/ui/exceptionform.py index 216780584..7e97cb796 100644 --- a/openlp/core/ui/exceptionform.py +++ b/openlp/core/ui/exceptionform.py @@ -32,8 +32,6 @@ import sqlalchemy from PyQt5 import Qt, QtCore, QtGui, QtWebKit, QtWidgets from lxml import etree -from openlp.core.common import RegistryProperties, is_linux - try: import migrate MIGRATE_VERSION = getattr(migrate, '__version__', '< 0.7') @@ -74,6 +72,7 @@ except ImportError: from openlp.core.common import Settings, UiStrings, translate from openlp.core.common.versionchecker import get_application_version +from openlp.core.common import RegistryProperties, is_linux from .exceptiondialog import Ui_ExceptionDialog diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 9ae2e0898..a0eec54a4 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -666,14 +666,14 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties): if missed_files: file_list = '' for entry in missed_files: - file_list += '{text}
'.format(text=entry) + file_list += '{text}
'.format(text=entry) msg = QtWidgets.QMessageBox() msg.setIcon(QtWidgets.QMessageBox.Warning) msg.setWindowTitle(translate('OpenLP.FirstTimeWizard', 'Network Error')) msg.setText(translate('OpenLP.FirstTimeWizard', 'Unable to download some files')) msg.setInformativeText(translate('OpenLP.FirstTimeWizard', 'The following files were not able to be ' - 'downloaded:
{text}'.format(text=file_list))) + 'downloaded:
{text}'.format(text=file_list))) msg.setStandardButtons(msg.Ok) ans = msg.exec() return True diff --git a/openlp/core/ui/formattingtagcontroller.py b/openlp/core/ui/formattingtagcontroller.py index 161930cb6..5a0511842 100644 --- a/openlp/core/ui/formattingtagcontroller.py +++ b/openlp/core/ui/formattingtagcontroller.py @@ -84,7 +84,7 @@ class FormattingTagController(object): 'desc': desc, 'start tag': '{{{tag}}}'.format(tag=tag), 'start html': start_html, - 'end tag': '{/{tag}}}'.format(tag=tag), + 'end tag': '{{{tag}}}'.format(tag=tag), 'end html': end_html, 'protected': False, 'temporary': False diff --git a/openlp/core/ui/lib/spelltextedit.py b/openlp/core/ui/lib/spelltextedit.py index 8b6b552be..5fd983128 100644 --- a/openlp/core/ui/lib/spelltextedit.py +++ b/openlp/core/ui/lib/spelltextedit.py @@ -164,7 +164,7 @@ class Highlighter(QtGui.QSyntaxHighlighter): """ Provides a text highlighter for pointing out spelling errors in text. """ - WORDS = '(?iu)[\w\']+' + WORDS = r'(?iu)[\w\']+' def __init__(self, *args): """ diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 00f4be7ca..ff5acb975 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -33,7 +33,7 @@ import html import logging import os -from PyQt5 import QtCore, QtWidgets, QtWebKit, QtWebKitWidgets, QtOpenGL, QtGui, QtMultimedia +from PyQt5 import QtCore, QtWidgets, QtWebKit, QtWebKitWidgets, QtGui, QtMultimedia from openlp.core.common import AppLocation, Registry, RegistryProperties, OpenLPMixin, Settings, translate,\ is_macosx, is_win @@ -468,9 +468,9 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties): self.service_item.theme_data.background_filename, ImageSource.Theme) if image_path: image_bytes = self.image_manager.get_image_bytes(image_path, ImageSource.ImagePlugin) - html = build_html(self.service_item, self.screen, self.is_live, background, image_bytes, - plugins=self.plugin_manager.plugins) - self.web_view.setHtml(html) + created_html = build_html(self.service_item, self.screen, self.is_live, background, image_bytes, + plugins=self.plugin_manager.plugins) + self.web_view.setHtml(created_html) if service_item.foot_text: self.footer(service_item.foot_text) # if was hidden keep it hidden diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index ccd12727c..63048eac5 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -46,7 +46,6 @@ from openlp.core.ui.firsttimeform import FirstTimeForm from openlp.core.ui.media import MediaController from openlp.core.ui.printserviceform import PrintServiceForm from openlp.core.ui.projector.manager import ProjectorManager -from openlp.core.ui.lib.toolbar import OpenLPToolbar from openlp.core.ui.lib.dockwidget import OpenLPDockWidget from openlp.core.ui.lib.mediadockmanager import MediaDockManager diff --git a/openlp/core/ui/media/__init__.py b/openlp/core/ui/media/__init__.py index 248aca6f2..b51391583 100644 --- a/openlp/core/ui/media/__init__.py +++ b/openlp/core/ui/media/__init__.py @@ -24,10 +24,10 @@ The :mod:`~openlp.core.ui.media` module contains classes and objects for media p """ import logging -from openlp.core.common import Settings - from PyQt5 import QtCore +from openlp.core.common import Settings + log = logging.getLogger(__name__ + '.__init__') diff --git a/openlp/core/ui/media/mediacontroller.py b/openlp/core/ui/media/mediacontroller.py index 021ea5281..d404ee02e 100644 --- a/openlp/core/ui/media/mediacontroller.py +++ b/openlp/core/ui/media/mediacontroller.py @@ -38,7 +38,6 @@ from openlp.core.ui.media.mediaplayer import MediaPlayer from openlp.core.ui.media import MediaState, MediaInfo, MediaType, get_media_players, set_media_players,\ parse_optical_path from openlp.core.ui.lib.toolbar import OpenLPToolbar -from openlp.core.ui.lib.dockwidget import OpenLPDockWidget log = logging.getLogger(__name__) @@ -175,7 +174,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): log.debug('_check_available_media_players') controller_dir = os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'core', 'ui', 'media') for filename in os.listdir(controller_dir): - if filename.endswith('player.py') and not filename == 'mediaplayer.py': + if filename.endswith('player.py') and filename != 'mediaplayer.py': path = os.path.join(controller_dir, filename) if os.path.isfile(path): module_name = 'openlp.core.ui.media.' + os.path.splitext(filename)[0] @@ -554,7 +553,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): default_player = [used_players[0]] if service_item.processor and service_item.processor != UiStrings().Automatic: # check to see if the player is usable else use the default one. - if not service_item.processor.lower() in used_players: + if service_item.processor.lower() not in used_players: used_players = default_player else: used_players = [service_item.processor.lower()] diff --git a/openlp/core/ui/media/playertab.py b/openlp/core/ui/media/playertab.py index 1fca21450..c76fc3300 100644 --- a/openlp/core/ui/media/playertab.py +++ b/openlp/core/ui/media/playertab.py @@ -224,9 +224,11 @@ class PlayerTab(SettingsTab): self.settings_form.register_post_process('mediaitem_media_rebuild') self.settings_form.register_post_process('config_screen_changed') - def post_set_up(self): + def post_set_up(self, post_update=False): """ Late setup for players as the MediaController has to be initialised first. + + :param post_update: Indicates if called before or after updates. """ for key, player in self.media_players.items(): player = self.media_players[key] diff --git a/openlp/core/ui/media/vlcplayer.py b/openlp/core/ui/media/vlcplayer.py index 9c2110e22..48b0602fe 100644 --- a/openlp/core/ui/media/vlcplayer.py +++ b/openlp/core/ui/media/vlcplayer.py @@ -112,7 +112,6 @@ def get_vlc(): # This needs to happen on module load and not in get_vlc(), otherwise it can cause crashes on some DE on some setups # (reported on Gnome3, Unity, Cinnamon, all GTK+ based) when using native filedialogs... if is_linux() and 'nose' not in sys.argv[0] and get_vlc(): - import ctypes try: try: x11 = ctypes.cdll.LoadLibrary('libX11.so.6') @@ -233,7 +232,7 @@ class VlcPlayer(MediaPlayer): """ vlc = get_vlc() start = datetime.now() - while not media_state == display.vlc_media.get_state(): + while media_state != display.vlc_media.get_state(): if display.vlc_media.get_state() == vlc.State.Error: return False self.application.process_events() diff --git a/openlp/core/ui/media/webkitplayer.py b/openlp/core/ui/media/webkitplayer.py index 19221ace0..5cd982951 100644 --- a/openlp/core/ui/media/webkitplayer.py +++ b/openlp/core/ui/media/webkitplayer.py @@ -22,10 +22,10 @@ """ The :mod:`~openlp.core.ui.media.webkit` module contains our WebKit video player """ -from PyQt5 import QtGui, QtWebKitWidgets - import logging +from PyQt5 import QtGui, QtWebKitWidgets + from openlp.core.common import Settings from openlp.core.lib import translate from openlp.core.ui.media import MediaState diff --git a/openlp/core/ui/projector/sourceselectform.py b/openlp/core/ui/projector/sourceselectform.py index 7d73f6a5a..5ee1ac403 100644 --- a/openlp/core/ui/projector/sourceselectform.py +++ b/openlp/core/ui/projector/sourceselectform.py @@ -141,23 +141,23 @@ def Build_Tab(group, source_key, default, projector, projectordb, edit=False): return widget, button_count, buttonchecked -def set_button_tooltip(bar): +def set_button_tooltip(button_bar): """ Set the toolip for the standard buttons used - :param bar: QDialogButtonBar instance to update + :param button_bar: QDialogButtonBar instance to update """ - for button in bar.buttons(): - if bar.standardButton(button) == QDialogButtonBox.Cancel: + for button in button_bar.buttons(): + if button_bar.standardButton(button) == QDialogButtonBox.Cancel: button.setToolTip(translate('OpenLP.SourceSelectForm', 'Ignoring current changes and return to OpenLP')) - elif bar.standardButton(button) == QDialogButtonBox.Reset: + elif button_bar.standardButton(button) == QDialogButtonBox.Reset: button.setToolTip(translate('OpenLP.SourceSelectForm', 'Delete all user-defined text and revert to PJLink default text')) - elif bar.standardButton(button) == QDialogButtonBox.Discard: + elif button_bar.standardButton(button) == QDialogButtonBox.Discard: button.setToolTip(translate('OpenLP.SourceSelectForm', 'Discard changes and reset to previous user-defined text')) - elif bar.standardButton(button) == QDialogButtonBox.Ok: + elif button_bar.standardButton(button) == QDialogButtonBox.Ok: button.setToolTip(translate('OpenLP.SourceSelectForm', 'Save changes and return to OpenLP')) else: diff --git a/openlp/core/ui/projector/tab.py b/openlp/core/ui/projector/tab.py index 1b87209c0..ff4bdee17 100644 --- a/openlp/core/ui/projector/tab.py +++ b/openlp/core/ui/projector/tab.py @@ -133,7 +133,7 @@ class ProjectorTab(SettingsTab): settings.setValue('socket timeout', self.socket_timeout_spin_box.value()) settings.setValue('poll time', self.socket_poll_spin_box.value()) settings.setValue('source dialog type', self.dialog_type_combo_box.currentIndex()) - settings.endGroup + settings.endGroup() def on_dialog_type_combo_box_changed(self): self.dialog_type = self.dialog_type_combo_box.currentIndex() diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 907cb49a6..b0f023286 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -480,9 +480,10 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa :return: service array """ service = [] - core = {'lite-service': self._save_lite, - 'service-theme': self.service_theme - } + core = { + 'lite-service': self._save_lite, + 'service-theme': self.service_theme + } service.append({'openlp_core': core}) return service @@ -597,7 +598,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa if success: try: shutil.copy(temp_file_name, path_file_name) - except shutil.Error: + except (shutil.Error, PermissionError): return self.save_file_as() except OSError as ose: QtWidgets.QMessageBox.critical(self, translate('OpenLP.ServiceManager', 'Error Saving File'), @@ -658,7 +659,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa if success: try: shutil.copy(temp_file_name, path_file_name) - except shutil.Error: + except (shutil.Error, PermissionError): return self.save_file_as() self.main_window.add_recent_file(path_file_name) self.set_modified(False) @@ -774,7 +775,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa else: critical_error_message_box(message=translate('OpenLP.ServiceManager', 'File is not a valid service.')) self.log_error('File contains no service data') - except (IOError, NameError, zipfile.BadZipfile): + except (IOError, NameError): self.log_exception('Problem loading service file {name}'.format(name=file_name)) critical_error_message_box(message=translate('OpenLP.ServiceManager', 'File could not be opened because it is corrupt.')) @@ -1327,7 +1328,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa """ The theme may have changed in the settings dialog so make sure the theme combo box is in the correct state. """ - visible = not self.renderer.theme_level == ThemeLevel.Global + visible = self.renderer.theme_level != ThemeLevel.Global self.toolbar.actions['theme_combo_box'].setVisible(visible) self.toolbar.actions['theme_label'].setVisible(visible) self.regenerate_service_items() diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 9379bb96f..8b5ce5e34 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -37,7 +37,6 @@ from openlp.core.lib import ItemCapabilities, ServiceItem, ImageSource, ServiceI build_html from openlp.core.lib.ui import create_action from openlp.core.ui.lib.toolbar import OpenLPToolbar -from openlp.core.ui.lib.dockwidget import OpenLPDockWidget from openlp.core.ui.lib.listpreviewwidget import ListPreviewWidget from openlp.core.ui import HideMode, MainDisplay, Display, DisplayControllerType diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index 475bfc0b7..a0ab5f45b 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -249,7 +249,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): NOTE the font_main_override is the inverse of the check box value """ if self.update_theme_allowed: - self.theme.font_main_override = not (value == QtCore.Qt.Checked) + self.theme.font_main_override = (value != QtCore.Qt.Checked) def on_footer_position_check_box_state_changed(self, value): """ @@ -257,7 +257,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): NOTE the font_footer_override is the inverse of the check box value """ if self.update_theme_allowed: - self.theme.font_footer_override = not (value == QtCore.Qt.Checked) + self.theme.font_footer_override = (value != QtCore.Qt.Checked) def exec(self, edit=False): """ diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 70ca9fd88..341a8061e 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -583,7 +583,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage out_file.write(theme_zip.read(name)) out_file.close() except (IOError, zipfile.BadZipfile): - self.log_exception('Importing theme from zip failed {name|'.format(name=file_name)) + self.log_exception('Importing theme from zip failed {name}'.format(name=file_name)) raise ValidationError except ValidationError: critical_error_message_box(translate('OpenLP.ThemeManager', 'Validation Error'), diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index d1465475d..1c55222f2 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -370,7 +370,7 @@ class BibleManager(RegistryProperties): """ log.debug('save_meta data {bible}, {version}, {copyright}, {perms}'.format(bible=bible, version=version, - cr=copyright, + copyright=copyright, perms=permissions)) self.db_cache[bible].save_meta('name', version) self.db_cache[bible].save_meta('copyright', copyright) diff --git a/openlp/plugins/bibles/lib/opensong.py b/openlp/plugins/bibles/lib/opensong.py index af3ef64c0..9c01a1fe0 100644 --- a/openlp/plugins/bibles/lib/opensong.py +++ b/openlp/plugins/bibles/lib/opensong.py @@ -117,7 +117,7 @@ class OpenSongBible(BibleDB): if len(verse_parts) > 1: number = int(verse_parts[0]) except TypeError: - log.warning('Illegal verse number: {verse:d}'.format(verse.attrib['n'])) + log.warning('Illegal verse number: {verse:d}'.format(verse=verse.attrib['n'])) verse_number = number else: verse_number += 1 diff --git a/openlp/plugins/bibles/lib/osis.py b/openlp/plugins/bibles/lib/osis.py index c0a3bec81..85aea70b3 100644 --- a/openlp/plugins/bibles/lib/osis.py +++ b/openlp/plugins/bibles/lib/osis.py @@ -126,8 +126,6 @@ class OSISBible(BibleDB): # Remove div-tags in the book etree.strip_tags(book, ('{http://www.bibletechnologies.net/2003/OSIS/namespace}div')) book_ref_id = self.get_book_ref_id_by_name(book.get('osisID'), num_books, language_id) - if not book_ref_id: - book_ref_id = self.get_book_ref_id_by_localised_name(book.get('osisID')) if not book_ref_id: log.error('Importing books from "{name}" failed'.format(name=self.filename)) return False diff --git a/openlp/plugins/bibles/lib/zefania.py b/openlp/plugins/bibles/lib/zefania.py index 0ea96a2a3..482d98107 100644 --- a/openlp/plugins/bibles/lib/zefania.py +++ b/openlp/plugins/bibles/lib/zefania.py @@ -86,8 +86,6 @@ class ZefaniaBible(BibleDB): continue if bname: book_ref_id = self.get_book_ref_id_by_name(bname, num_books, language_id) - if not book_ref_id: - book_ref_id = self.get_book_ref_id_by_localised_name(bname) else: log.debug('Could not find a name, will use number, basically a guess.') book_ref_id = int(bnumber) diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index 992ba9b5c..097b9dc38 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -28,7 +28,7 @@ from PyQt5 import QtCore from openlp.core.common import Registry from openlp.core.ui import HideMode -from openlp.core.lib import ServiceItemContext, ServiceItem +from openlp.core.lib import ServiceItemContext from openlp.plugins.presentations.lib.pdfcontroller import PDF_CONTROLLER_FILETYPES log = logging.getLogger(__name__) diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index dd67031bd..6045d5326 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -80,7 +80,7 @@ class PdfController(PresentationController): found_mutool = re.search('usage: mutool.*', decoded_line, re.IGNORECASE) if found_mutool: # Test that mutool contains mudraw - if re.search('draw\s+--\s+convert document.*', runlog.decode(), re.IGNORECASE | re.MULTILINE): + if re.search(r'draw\s+--\s+convert document.*', runlog.decode(), re.IGNORECASE | re.MULTILINE): program_type = 'mutool' break found_gs = re.search('GPL Ghostscript.*', decoded_line, re.IGNORECASE) @@ -215,8 +215,8 @@ class PdfDocument(PresentationDocument): height = 0.0 for line in runlog.splitlines(): try: - width = float(re.search('.*Size: x: (\d+\.?\d*), y: \d+.*', line.decode()).group(1)) - height = float(re.search('.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line.decode()).group(1)) + width = float(re.search(r'.*Size: x: (\d+\.?\d*), y: \d+.*', line.decode()).group(1)) + height = float(re.search(r'.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line.decode()).group(1)) break except AttributeError: continue diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index ebc394623..ccc519b68 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -362,7 +362,7 @@ class PowerpointDocument(PresentationDocument): # it is the powerpoint presentation window. (left, top, right, bottom) = win32gui.GetWindowRect(hwnd) window_title = win32gui.GetWindowText(hwnd) - log.debug('window size: left=left:d}, top={top:d}, ' + log.debug('window size: left={left:d}, top={top:d}, ' 'right={right:d}, bottom={bottom:d}'.format(left=left, top=top, right=right, bottom=bottom)) log.debug('compare size: {y:d} and {top:d}, {height:d} and {vertical:d}, ' '{x:d} and {left}, {width:d} and {horizontal:d}'.format(y=size.y(), diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index 54d8f5170..43eb69454 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -181,13 +181,13 @@ class PptviewDocument(PresentationDocument): index = -1 list_to_add = None # check if it is a slide - match = re.search("slides/slide(.+)\.xml", zip_info.filename) + match = re.search(r'slides/slide(.+)\.xml', zip_info.filename) if match: index = int(match.group(1)) - 1 node_type = 'ctrTitle' list_to_add = titles # or a note - match = re.search("notesSlides/notesSlide(.+)\.xml", zip_info.filename) + match = re.search(r'notesSlides/notesSlide(.+)\.xml', zip_info.filename) if match: index = int(match.group(1)) - 1 node_type = 'body' diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index dc0614086..ca0ecba82 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -124,7 +124,7 @@ class PresentationPlugin(Plugin): log.debug('check_pre_conditions') controller_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'presentations', 'lib') for filename in os.listdir(controller_dir): - if filename.endswith('controller.py') and not filename == 'presentationcontroller.py': + if filename.endswith('controller.py') and filename != 'presentationcontroller.py': path = os.path.join(controller_dir, filename) if os.path.isfile(path): module_name = 'openlp.plugins.presentations.lib.' + os.path.splitext(filename)[0] diff --git a/openlp/plugins/songs/lib/importers/cclifile.py b/openlp/plugins/songs/lib/importers/cclifile.py index 800509ab2..58ceefebc 100644 --- a/openlp/plugins/songs/lib/importers/cclifile.py +++ b/openlp/plugins/songs/lib/importers/cclifile.py @@ -255,6 +255,7 @@ class CCLIFileImport(SongImport): song_author = '' verse_start = False for line in text_list: + verse_type = 'v' clean_line = line.strip() if not clean_line: if line_number == 0: diff --git a/openlp/plugins/songs/lib/importers/dreambeam.py b/openlp/plugins/songs/lib/importers/dreambeam.py index 9371adc56..8c435bfd5 100644 --- a/openlp/plugins/songs/lib/importers/dreambeam.py +++ b/openlp/plugins/songs/lib/importers/dreambeam.py @@ -124,8 +124,8 @@ class DreamBeamImport(SongImport): if hasattr(song_xml, 'Sequence'): for lyrics_sequence_item in (song_xml.Sequence.iterchildren()): item = lyrics_sequence_item.get('Type')[:1] - self.verse_order_list.append("{item}{number}".format(item=item), - lyrics_sequence_item.get('Number')) + number = lyrics_sequence_item.get('Number') + self.verse_order_list.append("{item}{number}".format(item=item, number=number)) if hasattr(song_xml, 'Notes'): self.comments = str(song_xml.Notes.text) else: diff --git a/openlp/plugins/songs/lib/importers/easyworship.py b/openlp/plugins/songs/lib/importers/easyworship.py index cdffe9292..9db30838f 100644 --- a/openlp/plugins/songs/lib/importers/easyworship.py +++ b/openlp/plugins/songs/lib/importers/easyworship.py @@ -27,6 +27,7 @@ import os import struct import re import zlib +import logging from openlp.core.lib import translate from openlp.plugins.songs.lib import VerseType @@ -38,6 +39,8 @@ SLIDE_BREAK_REGEX = re.compile(r'\n *?\n[\n ]*') NUMBER_REGEX = re.compile(r'[0-9]+') NOTE_REGEX = re.compile(r'\(.*?\)') +log = logging.getLogger(__name__) + class FieldDescEntry: def __init__(self, name, field_type, size): diff --git a/openlp/plugins/songs/lib/importers/mediashout.py b/openlp/plugins/songs/lib/importers/mediashout.py index cb3a19640..a3bd7bbbc 100644 --- a/openlp/plugins/songs/lib/importers/mediashout.py +++ b/openlp/plugins/songs/lib/importers/mediashout.py @@ -24,15 +24,17 @@ The :mod:`mediashout` module provides the functionality for importing a MediaShout database into the OpenLP database. """ -# WARNING: See https://docs.python.org/2/library/sqlite3.html for value substitution +# WARNING: See https://docs.python.org/3/library/sqlite3.html for value substitution # in SQL statements import pyodbc +import logging from openlp.core.lib import translate from openlp.plugins.songs.lib.importers.songimport import SongImport VERSE_TAGS = ['V', 'C', 'B', 'O', 'P', 'I', 'E'] +log = logging.getLogger(__name__) class MediaShoutImport(SongImport): @@ -44,17 +46,18 @@ class MediaShoutImport(SongImport): """ Initialise the MediaShout importer. """ - SongImport.__init__(self, manager, **kwargs) + super(MediaShoutImport, self).__init__(manager, **kwargs) def do_import(self): """ Receive a single file to import. """ try: - conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ={source};' - 'PWD=6NOZ4eHK7k'.format(sorce=self.import_source)) - except: + conn = pyodbc.connect('DRIVER={{Microsoft Access Driver (*.mdb)}};DBQ={source};' + 'PWD=6NOZ4eHK7k'.format(source=self.import_source)) + except Exception as e: # Unfortunately no specific exception type + log.exception(e) self.log_error(self.import_source, translate('SongsPlugin.MediaShoutImport', 'Unable to open the MediaShout database.')) return @@ -63,17 +66,21 @@ class MediaShoutImport(SongImport): songs = cursor.fetchall() self.import_wizard.progress_bar.setMaximum(len(songs)) for song in songs: + topics = [] if self.stop_import_flag: break - cursor.execute('SELECT Type, Number, Text FROM Verses WHERE Record = ? ORDER BY Type, Number', song.Record) + cursor.execute('SELECT Type, Number, Text FROM Verses WHERE Record = ? ORDER BY Type, Number', + float(song.Record)) verses = cursor.fetchall() - cursor.execute('SELECT Type, Number, POrder FROM PlayOrder WHERE Record = ? ORDER BY POrder', song.Record) + cursor.execute('SELECT Type, Number, POrder FROM PlayOrder WHERE Record = ? ORDER BY POrder', + float(song.Record)) verse_order = cursor.fetchall() - cursor.execute('SELECT Name FROM Themes INNER JOIN SongThemes ON SongThemes.ThemeId = Themes.ThemeId ' - 'WHERE SongThemes.Record = ?', song.Record) - topics = cursor.fetchall() + if cursor.tables(table='TableName', tableType='TABLE').fetchone(): + cursor.execute('SELECT Name FROM Themes INNER JOIN SongThemes ON SongThemes.ThemeId = Themes.ThemeId ' + 'WHERE SongThemes.Record = ?', float(song.Record)) + topics = cursor.fetchall() cursor.execute('SELECT Name FROM Groups INNER JOIN SongGroups ON SongGroups.GroupId = Groups.GroupId ' - 'WHERE SongGroups.Record = ?', song.Record) + 'WHERE SongGroups.Record = ?', float(song.Record)) topics += cursor.fetchall() self.process_song(song, verses, verse_order, topics) diff --git a/openlp/plugins/songs/lib/importers/openlyrics.py b/openlp/plugins/songs/lib/importers/openlyrics.py index 84b667ce4..fcbe8fbfb 100644 --- a/openlp/plugins/songs/lib/importers/openlyrics.py +++ b/openlp/plugins/songs/lib/importers/openlyrics.py @@ -67,7 +67,7 @@ class OpenLyricsImport(SongImport): xml = etree.tostring(parsed_file).decode() self.open_lyrics.xml_to_song(xml) except etree.XMLSyntaxError: - log.exception('XML syntax error in file {path}'.format(file_path)) + log.exception('XML syntax error in file {path}'.format(path=file_path)) self.log_error(file_path, SongStrings.XMLSyntaxError) except OpenLyricsError as exception: log.exception('OpenLyricsException {error:d} in file {name}: {text}'.format(error=exception.type, diff --git a/openlp/plugins/songs/lib/importers/opspro.py b/openlp/plugins/songs/lib/importers/opspro.py index 8f9674deb..03c5001c6 100644 --- a/openlp/plugins/songs/lib/importers/opspro.py +++ b/openlp/plugins/songs/lib/importers/opspro.py @@ -55,7 +55,7 @@ class OPSProImport(SongImport): """ password = self.extract_mdb_password() try: - conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ={source};' + conn = pyodbc.connect('DRIVER={{Microsoft Access Driver (*.mdb)}};DBQ={source};' 'PWD={password}'.format(source=self.import_source, password=password)) except (pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError) as e: log.warning('Unable to connect the OPS Pro database {source}. {error}'.format(source=self.import_source, @@ -74,11 +74,11 @@ class OPSProImport(SongImport): break # Type means: 0=Original, 1=Projection, 2=Own cursor.execute('SELECT Lyrics, Type, IsDualLanguage FROM Lyrics WHERE SongID = ? AND Type < 2 ' - 'ORDER BY Type DESC', song.ID) + 'ORDER BY Type DESC', float(song.ID)) lyrics = cursor.fetchone() cursor.execute('SELECT CategoryName FROM Category INNER JOIN SongCategory ' 'ON Category.ID = SongCategory.CategoryID WHERE SongCategory.SongID = ? ' - 'ORDER BY CategoryName', song.ID) + 'ORDER BY CategoryName', float(song.ID)) topics = cursor.fetchall() try: self.process_song(song, lyrics, topics) diff --git a/openlp/plugins/songs/lib/importers/presentationmanager.py b/openlp/plugins/songs/lib/importers/presentationmanager.py index 06ab3f60d..f10bd0ed6 100644 --- a/openlp/plugins/songs/lib/importers/presentationmanager.py +++ b/openlp/plugins/songs/lib/importers/presentationmanager.py @@ -30,6 +30,7 @@ import re import chardet from lxml import objectify, etree +from openlp.core.common import translate from openlp.core.ui.lib.wizard import WizardStrings from .songimport import SongImport @@ -56,7 +57,13 @@ class PresentationManagerImport(SongImport): # Open file with detected encoding and remove encoding declaration text = open(file_path, mode='r', encoding=encoding).read() text = re.sub('.+\?>\n', '', text) - tree = etree.fromstring(text, parser=etree.XMLParser(recover=True)) + try: + tree = etree.fromstring(text, parser=etree.XMLParser(recover=True)) + except ValueError: + self.log_error(file_path, + translate('SongsPlugin.PresentationManagerImport', + 'File is not in XML-format, which is the only format supported.')) + continue root = objectify.fromstring(etree.tostring(tree)) self.process_song(root) diff --git a/openlp/plugins/songs/lib/importers/songpro.py b/openlp/plugins/songs/lib/importers/songpro.py index b1c8e7fe8..90fe34492 100644 --- a/openlp/plugins/songs/lib/importers/songpro.py +++ b/openlp/plugins/songs/lib/importers/songpro.py @@ -72,7 +72,7 @@ class SongProImport(SongImport): Receive a single file or a list of files to import. """ self.encoding = None - with open(self.import_source, 'rt') as songs_file: + with open(self.import_source, 'rt', errors='ignore') as songs_file: self.import_wizard.progress_bar.setMaximum(0) tag = '' text = '' diff --git a/openlp/plugins/songs/lib/importers/songshowplus.py b/openlp/plugins/songs/lib/importers/songshowplus.py index 02b35fd9e..4d9096c93 100644 --- a/openlp/plugins/songs/lib/importers/songshowplus.py +++ b/openlp/plugins/songs/lib/importers/songshowplus.py @@ -106,6 +106,7 @@ class SongShowPlusImport(SongImport): song_data = open(file, 'rb') while True: block_key, = struct.unpack("I", song_data.read(4)) + log.debug('block_key: %d' % block_key) # The file ends with 4 NULL's if block_key == 0: break @@ -117,7 +118,13 @@ class SongShowPlusImport(SongImport): null, verse_name_length, = struct.unpack("BB", song_data.read(2)) verse_name = self.decode(song_data.read(verse_name_length)) length_descriptor_size, = struct.unpack("B", song_data.read(1)) - log.debug(length_descriptor_size) + log.debug('length_descriptor_size: %d' % length_descriptor_size) + # In the case of song_numbers the number is in the data from the + # current position to the next block starts + if block_key == SONG_NUMBER: + sn_bytes = song_data.read(length_descriptor_size - 1) + self.song_number = int.from_bytes(sn_bytes, byteorder='little') + continue # Detect if/how long the length descriptor is if length_descriptor_size == 12 or length_descriptor_size == 20: length_descriptor, = struct.unpack("I", song_data.read(4)) @@ -127,8 +134,9 @@ class SongShowPlusImport(SongImport): length_descriptor = 0 else: length_descriptor, = struct.unpack("B", song_data.read(1)) - log.debug(length_descriptor_size) + log.debug('length_descriptor: %d' % length_descriptor) data = song_data.read(length_descriptor) + log.debug(data) if block_key == TITLE: self.title = self.decode(data) elif block_key == AUTHOR: @@ -168,8 +176,6 @@ class SongShowPlusImport(SongImport): self.ssp_verse_order_list.append(verse_tag) elif block_key == SONG_BOOK: self.song_book_name = self.decode(data) - elif block_key == SONG_NUMBER: - self.song_number = ord(data) elif block_key == CUSTOM_VERSE: verse_tag = self.to_openlp_verse_tag(verse_name) self.add_verse(self.decode(data), verse_tag) diff --git a/openlp/plugins/songs/lib/importers/videopsalm.py b/openlp/plugins/songs/lib/importers/videopsalm.py index 60e76a067..3c7e20c2e 100644 --- a/openlp/plugins/songs/lib/importers/videopsalm.py +++ b/openlp/plugins/songs/lib/importers/videopsalm.py @@ -122,6 +122,4 @@ class VideoPsalmImport(SongImport): if not self.finish(): self.log_error('Could not import {title}'.format(title=self.title)) except Exception as e: - self.log_error(translate('SongsPlugin.VideoPsalmImport', 'File {name}').format(name=file.name), - translate('SongsPlugin.VideoPsalmImport', 'Error: {error}').format(error=e)) - song_file.close() + self.log_error(song_file.name, translate('SongsPlugin.VideoPsalmImport', 'Error: {error}').format(error=e)) diff --git a/openlp/plugins/songs/lib/importers/worshipcenterpro.py b/openlp/plugins/songs/lib/importers/worshipcenterpro.py index df04823e8..3d5cbe9ba 100644 --- a/openlp/plugins/songs/lib/importers/worshipcenterpro.py +++ b/openlp/plugins/songs/lib/importers/worshipcenterpro.py @@ -49,7 +49,7 @@ class WorshipCenterProImport(SongImport): Receive a single file to import. """ try: - conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};' + conn = pyodbc.connect('DRIVER={{Microsoft Access Driver (*.mdb)}};' 'DBQ={source}'.format(source=self.import_source)) except (pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError) as e: log.warning('Unable to connect the WorshipCenter Pro ' diff --git a/pylintrc b/pylintrc new file mode 100644 index 000000000..92d83ffaa --- /dev/null +++ b/pylintrc @@ -0,0 +1,379 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=vlc.py + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Use multiple processes to speed up Pylint. +#jobs=4 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + +# Allow optimization of some AST trees. This will activate a peephole AST +# optimizer, which will apply various small optimizations. For instance, it can +# be used to obtain the result of joining multiple strings with the addition +# operator. Joining a lot of strings can lead to a maximum recursion error in +# Pylint and this flag can prevent that. It has one side effect, the resulting +# AST will be different than the one from reality. +optimize-ast=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=reload-builtin,too-many-locals,no-name-in-module,hex-method,bad-builtin,old-raise-syntax,bad-continuation,reduce-builtin,unicode-builtin,unused-wildcard-import,apply-builtin,no-member,filter-builtin-not-iterating,execfile-builtin,import-star-module-level,input-builtin,duplicate-code,old-division,print-statement,cmp-method,fixme,no-absolute-import,cmp-builtin,no-self-use,too-many-nested-blocks,standarderror-builtin,long-builtin,raising-string,delslice-method,metaclass-assignment,coerce-builtin,old-octal-literal,basestring-builtin,xrange-builtin,line-too-long,suppressed-message,unused-variable,round-builtin,raw_input-builtin,too-many-instance-attributes,unused-argument,next-method-called,oct-method,attribute-defined-outside-init,too-many-public-methods,too-many-statements,logging-format-interpolation,map-builtin-not-iterating,buffer-builtin,parameter-unpacking,range-builtin-not-iterating,intern-builtin,wrong-import-position,coerce-method,useless-suppression,using-cmp-argument,dict-view-method,backtick,old-ne-operator,missing-docstring,setslice-method,access-member-before-definition,long-suffix,too-few-public-methods,file-builtin,zip-builtin-not-iterating,invalid-name,getslice-method,unpacking-in-except,too-many-arguments,dict-iter-method,unichr-builtin,too-many-lines,too-many-branches,wildcard-import,indexing-exception,nonzero-method + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=no + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: en_ZA (myspell), en_US +# (myspell), en_GB (myspell), en_AU (myspell), da_DK (myspell), en (aspell), +# en_CA (aspell). +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[BASIC] + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[ELIF] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=100 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_$|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). This supports can work +# with qualified names. +ignored-classes= + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=optparse + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/tests/functional/openlp_core_common/test_projector_utilities.py b/tests/functional/openlp_core_common/test_projector_utilities.py index aebdd7509..75ecd582a 100644 --- a/tests/functional/openlp_core_common/test_projector_utilities.py +++ b/tests/functional/openlp_core_common/test_projector_utilities.py @@ -124,7 +124,7 @@ class testProjectorUtilities(TestCase): Test MD5 hash from salt+data pass (python) """ # WHEN: Given a known salt+data - hash_ = md5_hash(salt=salt.encode('ascii'), data=pin.encode('ascii')) + hash_ = md5_hash(salt=salt.encode('utf-8'), data=pin.encode('utf-8')) # THEN: Validate return has is same self.assertEquals(hash_, test_hash, 'MD5 should have returned a good hash') @@ -134,7 +134,7 @@ class testProjectorUtilities(TestCase): Test MD5 hash from salt+data fail (python) """ # WHEN: Given a different salt+hash - hash_ = md5_hash(salt=pin.encode('ascii'), data=salt.encode('ascii')) + hash_ = md5_hash(salt=pin.encode('utf-8'), data=salt.encode('utf-8')) # THEN: return data is different self.assertNotEquals(hash_, test_hash, 'MD5 should have returned a bad hash') @@ -144,20 +144,20 @@ class testProjectorUtilities(TestCase): Test MD5 hash from salt+data pass (Qt) """ # WHEN: Given a known salt+data - hash_ = qmd5_hash(salt=salt.encode('ascii'), data=pin.encode('ascii')) + hash_ = qmd5_hash(salt=salt.encode('utf-8'), data=pin.encode('utf-8')) # THEN: Validate return has is same - self.assertEquals(hash_.decode('ascii'), test_hash, 'Qt-MD5 should have returned a good hash') + self.assertEquals(hash_, test_hash, 'Qt-MD5 should have returned a good hash') def test_qmd5_hash_bad(self): """ Test MD5 hash from salt+hash fail (Qt) """ # WHEN: Given a different salt+hash - hash_ = qmd5_hash(salt=pin.encode('ascii'), data=salt.encode('ascii')) + hash_ = qmd5_hash(salt=pin.encode('utf-8'), data=salt.encode('utf-8')) # THEN: return data is different - self.assertNotEquals(hash_.decode('ascii'), test_hash, 'Qt-MD5 should have returned a bad hash') + self.assertNotEquals(hash_, test_hash, 'Qt-MD5 should have returned a bad hash') def test_md5_non_ascii_string(self): """ @@ -174,7 +174,7 @@ class testProjectorUtilities(TestCase): Test MD5 hash with non-ascii string - bug 1417809 """ # WHEN: Non-ascii string is hashed - hash_ = md5_hash(salt=test_non_ascii_string.encode('utf-8'), data=None) + hash_ = md5_hash(data=test_non_ascii_string.encode('utf-8')) # THEN: Valid MD5 hash should be returned self.assertEqual(hash_, test_non_ascii_hash, 'Qt-MD5 should have returned a valid hash') diff --git a/tests/functional/openlp_core_lib/test_projector_pjlink1.py b/tests/functional/openlp_core_lib/test_projector_pjlink1.py index 4928e5d0c..815b26493 100644 --- a/tests/functional/openlp_core_lib/test_projector_pjlink1.py +++ b/tests/functional/openlp_core_lib/test_projector_pjlink1.py @@ -30,7 +30,7 @@ from openlp.core.lib.projector.constants import E_PARAMETER, ERROR_STRING, S_OFF S_COOLDOWN, PJLINK_POWR_STATUS from tests.functional import patch -from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE +from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH pjlink_test = PJLink1(name='test', ip='127.0.0.1', pin=TEST_PIN, no_poll=True) @@ -332,3 +332,53 @@ class TestPJLink(TestCase): self.assertFalse(pjlink.send_busy, 'Projector send_busy should be False') self.assertTrue(mock_timer.called, 'Projector timer.stop() should have been called') self.assertTrue(mock_socket_timer.called, 'Projector socket_timer.stop() should have been called') + + @patch.object(pjlink_test, 'send_command') + @patch.object(pjlink_test, 'waitForReadyRead') + @patch.object(pjlink_test, 'projectorAuthentication') + @patch.object(pjlink_test, 'timer') + @patch.object(pjlink_test, 'socket_timer') + def test_bug_1593882_no_pin_authenticated_connection(self, mock_socket_timer, + mock_timer, + mock_authentication, + mock_ready_read, + mock_send_command): + """ + Test bug 1593882 no pin and authenticated request exception + """ + # GIVEN: Test object and mocks + pjlink = pjlink_test + pjlink.pin = None + mock_ready_read.return_value = True + + # WHEN: call with authentication request and pin not set + pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) + + # THEN: No Authentication signal should have been sent + mock_authentication.emit.assert_called_with(pjlink.name) + + @patch.object(pjlink_test, 'waitForReadyRead') + @patch.object(pjlink_test, 'state') + @patch.object(pjlink_test, '_send_command') + @patch.object(pjlink_test, 'timer') + @patch.object(pjlink_test, 'socket_timer') + def test_bug_1593883_pjlink_authentication(self, mock_socket_timer, + mock_timer, + mock_send_command, + mock_state, + mock_waitForReadyRead): + """ + Test bugfix 1593883 pjlink authentication + """ + # GIVEN: Test object and data + pjlink = pjlink_test + pjlink.pin = TEST_PIN + mock_state.return_value = pjlink.ConnectedState + mock_waitForReadyRead.return_value = True + + # WHEN: Athenticated connection is called + pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) + + # THEN: send_command should have the proper authentication + self.assertEquals("{test}".format(test=mock_send_command.call_args), + "call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH)) diff --git a/tests/functional/openlp_core_lib/test_serviceitem.py b/tests/functional/openlp_core_lib/test_serviceitem.py index e1c73b3ca..d18a4d049 100644 --- a/tests/functional/openlp_core_lib/test_serviceitem.py +++ b/tests/functional/openlp_core_lib/test_serviceitem.py @@ -112,7 +112,6 @@ class TestServiceItem(TestCase): # WHEN: adding an image from a saved Service and mocked exists line = convert_file_service_item(TEST_PATH, 'serviceitem_image_1.osj') with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists,\ - patch('openlp.core.lib.serviceitem.create_thumb') as mocked_create_thumb,\ patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path') as \ mocked_get_section_data_path: mocked_exists.return_value = True @@ -164,7 +163,6 @@ class TestServiceItem(TestCase): line2 = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj', 1) with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists, \ - patch('openlp.core.lib.serviceitem.create_thumb') as mocked_create_thumb, \ patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path') as \ mocked_get_section_data_path: mocked_exists.return_value = True diff --git a/tests/functional/openlp_core_lib/test_theme.py b/tests/functional/openlp_core_lib/test_theme.py index c006abb78..db6b78d02 100644 --- a/tests/functional/openlp_core_lib/test_theme.py +++ b/tests/functional/openlp_core_lib/test_theme.py @@ -22,43 +22,82 @@ """ Package to test the openlp.core.lib.theme package. """ -from tests.functional import MagicMock, patch from unittest import TestCase +import os from openlp.core.lib.theme import ThemeXML -class TestTheme(TestCase): +class TestThemeXML(TestCase): """ - Test the functions in the Theme module + Test the ThemeXML class """ - def setUp(self): - """ - Create the UI - """ - pass - - def tearDown(self): - """ - Delete all the C++ objects at the end so that we don't have a segfault - """ - pass - def test_new_theme(self): """ - Test the theme creation - basic test + Test the ThemeXML constructor """ - # GIVEN: A new theme - - # WHEN: A theme is created + # GIVEN: The ThemeXML class + # WHEN: A theme object is created default_theme = ThemeXML() - # THEN: We should get some default behaviours - self.assertTrue(default_theme.background_border_color == '#000000', 'The theme should have a black border') - self.assertTrue(default_theme.background_type == 'solid', 'The theme should have a solid backgrounds') - self.assertTrue(default_theme.display_vertical_align == 0, - 'The theme should have a display_vertical_align of 0') - self.assertTrue(default_theme.font_footer_name == "Arial", - 'The theme should have a font_footer_name of Arial') - self.assertTrue(default_theme.font_main_bold is False, 'The theme should have a font_main_bold of false') - self.assertTrue(len(default_theme.__dict__) == 47, 'The theme should have 47 variables') + # THEN: The default values should be correct + self.assertEqual('#000000', default_theme.background_border_color, + 'background_border_color should be "#000000"') + self.assertEqual('solid', default_theme.background_type, 'background_type should be "solid"') + self.assertEqual(0, default_theme.display_vertical_align, 'display_vertical_align should be 0') + self.assertEqual('Arial', default_theme.font_footer_name, 'font_footer_name should be "Arial"') + self.assertFalse(default_theme.font_main_bold, 'font_main_bold should be False') + self.assertEqual(47, len(default_theme.__dict__), 'The theme should have 47 attributes') + + def test_expand_json(self): + """ + Test the expand_json method + """ + # GIVEN: A ThemeXML object and some JSON to "expand" + theme = ThemeXML() + theme_json = { + 'background': { + 'border_color': '#000000', + 'type': 'solid' + }, + 'display': { + 'vertical_align': 0 + }, + 'font': { + 'footer': { + 'bold': False + }, + 'main': { + 'name': 'Arial' + } + } + } + + # WHEN: ThemeXML.expand_json() is run + theme.expand_json(theme_json) + + # THEN: The attributes should be set on the object + self.assertEqual('#000000', theme.background_border_color, 'background_border_color should be "#000000"') + self.assertEqual('solid', theme.background_type, 'background_type should be "solid"') + self.assertEqual(0, theme.display_vertical_align, 'display_vertical_align should be 0') + self.assertFalse(theme.font_footer_bold, 'font_footer_bold should be False') + self.assertEqual('Arial', theme.font_main_name, 'font_main_name should be "Arial"') + + def test_extend_image_filename(self): + """ + Test the extend_image_filename method + """ + # GIVEN: A theme object + theme = ThemeXML() + theme.theme_name = 'MyBeautifulTheme ' + theme.background_filename = ' video.mp4' + theme.background_type = 'video' + path = os.path.expanduser('~') + + # WHEN: ThemeXML.extend_image_filename is run + theme.extend_image_filename(path) + + # THEN: The filename of the background should be correct + expected_filename = os.path.join(path, 'MyBeautifulTheme', 'video.mp4') + self.assertEqual(expected_filename, theme.background_filename) + self.assertEqual('MyBeautifulTheme', theme.theme_name) diff --git a/tests/functional/openlp_core_ui/test_servicemanager.py b/tests/functional/openlp_core_ui/test_servicemanager.py index 7882d8a24..3e4af8c97 100644 --- a/tests/functional/openlp_core_ui/test_servicemanager.py +++ b/tests/functional/openlp_core_ui/test_servicemanager.py @@ -22,10 +22,12 @@ """ Package to test the openlp.core.ui.slidecontroller package. """ -import PyQt5 +import os from unittest import TestCase -from openlp.core.common import Registry, ThemeLevel, Settings +import PyQt5 + +from openlp.core.common import Registry, ThemeLevel from openlp.core.lib import ServiceItem, ServiceItemType, ItemCapabilities from openlp.core.ui import ServiceManager @@ -33,6 +35,9 @@ from tests.functional import MagicMock, patch class TestServiceManager(TestCase): + """ + Test the service manager + """ def setUp(self): """ @@ -108,20 +113,20 @@ class TestServiceManager(TestCase): # WHEN I define a context menu service_manager.context_menu(1) # THEN the following calls should have occurred. - self.assertEquals(service_manager.edit_action.setVisible.call_count, 1, 'Should have been called once') - self.assertEquals(service_manager.rename_action.setVisible.call_count, 1, 'Should have been called once') - self.assertEquals(service_manager.create_custom_action.setVisible.call_count, 1, 'Should have been called once') - self.assertEquals(service_manager.maintain_action.setVisible.call_count, 1, 'Should have been called once') - self.assertEquals(service_manager.notes_action.setVisible.call_count, 1, 'Should have been called once') - self.assertEquals(service_manager.time_action.setVisible.call_count, 1, 'Should have been called once') - self.assertEquals(service_manager.auto_start_action.setVisible.call_count, 1, 'Should have been called once') - self.assertEquals(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 1, - 'Should have been called once') - self.assertEquals(service_manager.auto_play_slides_once.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.auto_play_slides_loop.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.theme_menu.menuAction().setVisible.call_count, 1, - 'Should have been called once') + self.assertEqual(service_manager.edit_action.setVisible.call_count, 1, 'Should have been called once') + self.assertEqual(service_manager.rename_action.setVisible.call_count, 1, 'Should have been called once') + self.assertEqual(service_manager.create_custom_action.setVisible.call_count, 1, 'Should have been called once') + self.assertEqual(service_manager.maintain_action.setVisible.call_count, 1, 'Should have been called once') + self.assertEqual(service_manager.notes_action.setVisible.call_count, 1, 'Should have been called once') + self.assertEqual(service_manager.time_action.setVisible.call_count, 1, 'Should have been called once') + self.assertEqual(service_manager.auto_start_action.setVisible.call_count, 1, 'Should have been called once') + self.assertEqual(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 1, + 'Should have been called once') + self.assertEqual(service_manager.auto_play_slides_once.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.auto_play_slides_loop.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.theme_menu.menuAction().setVisible.call_count, 1, + 'Should have been called once') def test_build_song_context_menu(self): """ @@ -139,12 +144,10 @@ class TestServiceManager(TestCase): service_manager.service_manager_list = MagicMock() service_manager.service_manager_list.itemAt.return_value = item service_item = ServiceItem(None) - service_item.add_capability(ItemCapabilities.CanEdit) - service_item.add_capability(ItemCapabilities.CanPreview) - service_item.add_capability(ItemCapabilities.CanLoop) - service_item.add_capability(ItemCapabilities.OnLoadUpdate) - service_item.add_capability(ItemCapabilities.AddIfNewItem) - service_item.add_capability(ItemCapabilities.CanSoftBreak) + for capability in [ItemCapabilities.CanEdit, ItemCapabilities.CanPreview, ItemCapabilities.CanLoop, + ItemCapabilities.OnLoadUpdate, ItemCapabilities.AddIfNewItem, + ItemCapabilities.CanSoftBreak]: + service_item.add_capability(capability) service_item.service_item_type = ServiceItemType.Text service_item.edit_id = 1 service_item._display_frames.append(MagicMock()) @@ -165,29 +168,29 @@ class TestServiceManager(TestCase): # WHEN I define a context menu service_manager.context_menu(1) # THEN the following calls should have occurred. - self.assertEquals(service_manager.edit_action.setVisible.call_count, 2, 'Should have be called twice') - self.assertEquals(service_manager.rename_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.create_custom_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.maintain_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.notes_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.time_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.auto_start_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 1, - 'Should have be called once') - self.assertEquals(service_manager.auto_play_slides_once.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.auto_play_slides_loop.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.theme_menu.menuAction().setVisible.call_count, 2, - 'Should have be called twice') + self.assertEqual(service_manager.edit_action.setVisible.call_count, 2, 'Should have be called twice') + self.assertEqual(service_manager.rename_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.create_custom_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.maintain_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.notes_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.time_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_start_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 1, + 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_once.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.auto_play_slides_loop.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.theme_menu.menuAction().setVisible.call_count, 2, + 'Should have be called twice') # THEN we add a 2nd display frame service_item._display_frames.append(MagicMock()) service_manager.context_menu(1) # THEN the following additional calls should have occurred. - self.assertEquals(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 2, - 'Should have be called twice') - self.assertEquals(service_manager.auto_play_slides_once.setChecked.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.auto_play_slides_loop.setChecked.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.timed_slide_interval.setChecked.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 2, + 'Should have be called twice') + self.assertEqual(service_manager.auto_play_slides_once.setChecked.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_loop.setChecked.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.timed_slide_interval.setChecked.call_count, 1, 'Should have be called once') def test_build_bible_context_menu(self): """ @@ -205,11 +208,10 @@ class TestServiceManager(TestCase): service_manager.service_manager_list = MagicMock() service_manager.service_manager_list.itemAt.return_value = item service_item = ServiceItem(None) - service_item.add_capability(ItemCapabilities.NoLineBreaks) - service_item.add_capability(ItemCapabilities.CanPreview) - service_item.add_capability(ItemCapabilities.CanLoop) - service_item.add_capability(ItemCapabilities.CanWordSplit) - service_item.add_capability(ItemCapabilities.CanEditTitle) + for capability in [ItemCapabilities.NoLineBreaks, ItemCapabilities.CanPreview, + ItemCapabilities.CanLoop, ItemCapabilities.CanWordSplit, + ItemCapabilities.CanEditTitle]: + service_item.add_capability(capability) service_item.service_item_type = ServiceItemType.Text service_item.edit_id = 1 service_item._display_frames.append(MagicMock()) @@ -230,29 +232,29 @@ class TestServiceManager(TestCase): # WHEN I define a context menu service_manager.context_menu(1) # THEN the following calls should have occurred. - self.assertEquals(service_manager.edit_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.rename_action.setVisible.call_count, 2, 'Should have be called twice') - self.assertEquals(service_manager.create_custom_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.maintain_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.notes_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.time_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.auto_start_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 1, - 'Should have be called once') - self.assertEquals(service_manager.auto_play_slides_once.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.auto_play_slides_loop.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.theme_menu.menuAction().setVisible.call_count, 2, - 'Should have be called twice') + self.assertEqual(service_manager.edit_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.rename_action.setVisible.call_count, 2, 'Should have be called twice') + self.assertEqual(service_manager.create_custom_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.maintain_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.notes_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.time_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_start_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 1, + 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_once.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.auto_play_slides_loop.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.theme_menu.menuAction().setVisible.call_count, 2, + 'Should have be called twice') # THEN we add a 2nd display frame service_item._display_frames.append(MagicMock()) service_manager.context_menu(1) # THEN the following additional calls should have occurred. - self.assertEquals(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 2, - 'Should have be called twice') - self.assertEquals(service_manager.auto_play_slides_once.setChecked.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.auto_play_slides_loop.setChecked.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.timed_slide_interval.setChecked.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 2, + 'Should have be called twice') + self.assertEqual(service_manager.auto_play_slides_once.setChecked.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_loop.setChecked.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.timed_slide_interval.setChecked.call_count, 1, 'Should have be called once') def test_build_custom_context_menu(self): """ @@ -295,29 +297,29 @@ class TestServiceManager(TestCase): # WHEN I define a context menu service_manager.context_menu(1) # THEN the following calls should have occurred. - self.assertEquals(service_manager.edit_action.setVisible.call_count, 2, 'Should have be called twice') - self.assertEquals(service_manager.rename_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.create_custom_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.maintain_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.notes_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.time_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.auto_start_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 1, - 'Should have be called once') - self.assertEquals(service_manager.auto_play_slides_once.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.auto_play_slides_loop.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.theme_menu.menuAction().setVisible.call_count, 2, - 'Should have be called twice') + self.assertEqual(service_manager.edit_action.setVisible.call_count, 2, 'Should have be called twice') + self.assertEqual(service_manager.rename_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.create_custom_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.maintain_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.notes_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.time_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_start_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 1, + 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_once.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.auto_play_slides_loop.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.theme_menu.menuAction().setVisible.call_count, 2, + 'Should have be called twice') # THEN we add a 2nd display frame service_item._display_frames.append(MagicMock()) service_manager.context_menu(1) # THEN the following additional calls should have occurred. - self.assertEquals(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 2, - 'Should have be called twice') - self.assertEquals(service_manager.auto_play_slides_once.setChecked.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.auto_play_slides_loop.setChecked.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.timed_slide_interval.setChecked.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 2, + 'Should have be called twice') + self.assertEqual(service_manager.auto_play_slides_once.setChecked.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_loop.setChecked.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.timed_slide_interval.setChecked.call_count, 1, 'Should have be called once') def test_build_image_context_menu(self): """ @@ -358,29 +360,29 @@ class TestServiceManager(TestCase): # WHEN I define a context menu service_manager.context_menu(1) # THEN the following calls should have occurred. - self.assertEquals(service_manager.edit_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.rename_action.setVisible.call_count, 2, 'Should have be called twice') - self.assertEquals(service_manager.create_custom_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.maintain_action.setVisible.call_count, 2, 'Should have be called twice') - self.assertEquals(service_manager.notes_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.time_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.auto_start_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 1, - 'Should have be called once') - self.assertEquals(service_manager.auto_play_slides_once.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.auto_play_slides_loop.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.theme_menu.menuAction().setVisible.call_count, 1, - 'Should have be called once') + self.assertEqual(service_manager.edit_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.rename_action.setVisible.call_count, 2, 'Should have be called twice') + self.assertEqual(service_manager.create_custom_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.maintain_action.setVisible.call_count, 2, 'Should have be called twice') + self.assertEqual(service_manager.notes_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.time_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_start_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 1, + 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_once.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.auto_play_slides_loop.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.theme_menu.menuAction().setVisible.call_count, 1, + 'Should have be called once') # THEN we add a 2nd display frame and regenerate the menu. service_item._raw_frames.append(MagicMock()) service_manager.context_menu(1) # THEN the following additional calls should have occurred. - self.assertEquals(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 2, - 'Should have be called twice') - self.assertEquals(service_manager.auto_play_slides_once.setChecked.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.auto_play_slides_loop.setChecked.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.timed_slide_interval.setChecked.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 2, + 'Should have be called twice') + self.assertEqual(service_manager.auto_play_slides_once.setChecked.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_loop.setChecked.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.timed_slide_interval.setChecked.call_count, 1, 'Should have be called once') def test_build_media_context_menu(self): """ @@ -419,25 +421,25 @@ class TestServiceManager(TestCase): # WHEN I define a context menu service_manager.context_menu(1) # THEN the following calls should have occurred. - self.assertEquals(service_manager.edit_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.rename_action.setVisible.call_count, 2, 'Should have be called twice') - self.assertEquals(service_manager.create_custom_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.maintain_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.notes_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.time_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.auto_start_action.setVisible.call_count, 2, 'Should have be called twice') - self.assertEquals(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 1, - 'Should have be called once') - self.assertEquals(service_manager.auto_play_slides_once.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.auto_play_slides_loop.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.theme_menu.menuAction().setVisible.call_count, 1, - 'Should have be called once') + self.assertEqual(service_manager.edit_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.rename_action.setVisible.call_count, 2, 'Should have be called twice') + self.assertEqual(service_manager.create_custom_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.maintain_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.notes_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.time_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_start_action.setVisible.call_count, 2, 'Should have be called twice') + self.assertEqual(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 1, + 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_once.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.auto_play_slides_loop.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.theme_menu.menuAction().setVisible.call_count, 1, + 'Should have be called once') # THEN I change the length of the media and regenerate the menu. service_item.set_media_length(5) service_manager.context_menu(1) # THEN the following additional calls should have occurred. - self.assertEquals(service_manager.time_action.setVisible.call_count, 3, 'Should have be called three times') + self.assertEqual(service_manager.time_action.setVisible.call_count, 3, 'Should have be called three times') def test_build_presentation_pdf_context_menu(self): """ @@ -477,20 +479,20 @@ class TestServiceManager(TestCase): # WHEN I define a context menu service_manager.context_menu(1) # THEN the following calls should have occurred. - self.assertEquals(service_manager.edit_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.rename_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.create_custom_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.maintain_action.setVisible.call_count, 2, 'Should have be called twice') - self.assertEquals(service_manager.notes_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.time_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.auto_start_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 1, - 'Should have be called once') - self.assertEquals(service_manager.auto_play_slides_once.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.auto_play_slides_loop.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.theme_menu.menuAction().setVisible.call_count, 1, - 'Should have be called once') + self.assertEqual(service_manager.edit_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.rename_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.create_custom_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.maintain_action.setVisible.call_count, 2, 'Should have be called twice') + self.assertEqual(service_manager.notes_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.time_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_start_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 1, + 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_once.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.auto_play_slides_loop.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.theme_menu.menuAction().setVisible.call_count, 1, + 'Should have be called once') def test_build_presentation_non_pdf_context_menu(self): """ @@ -527,20 +529,20 @@ class TestServiceManager(TestCase): # WHEN I define a context menu service_manager.context_menu(1) # THEN the following calls should have occurred. - self.assertEquals(service_manager.edit_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.rename_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.create_custom_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.maintain_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.notes_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.time_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.auto_start_action.setVisible.call_count, 1, 'Should have be called once') - self.assertEquals(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 1, - 'Should have be called once') - self.assertEquals(service_manager.auto_play_slides_once.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.auto_play_slides_loop.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called') - self.assertEquals(service_manager.theme_menu.menuAction().setVisible.call_count, 1, - 'Should have be called once') + self.assertEqual(service_manager.edit_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.rename_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.create_custom_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.maintain_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.notes_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.time_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_start_action.setVisible.call_count, 1, 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_menu.menuAction().setVisible.call_count, 1, + 'Should have be called once') + self.assertEqual(service_manager.auto_play_slides_once.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.auto_play_slides_loop.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called') + self.assertEqual(service_manager.theme_menu.menuAction().setVisible.call_count, 1, + 'Should have be called once') @patch(u'openlp.core.ui.servicemanager.Settings') @patch(u'PyQt5.QtCore.QTimer.singleShot') @@ -573,7 +575,7 @@ class TestServiceManager(TestCase): # WHEN: on_single_click_preview() is called service_manager.on_single_click_preview() # THEN: timer should not be started - self.assertEquals(mocked_singleShot.call_count, 0, 'Should not be called') + self.assertEqual(mocked_singleShot.call_count, 0, 'Should not be called') @patch(u'openlp.core.ui.servicemanager.Settings') @patch(u'PyQt5.QtCore.QTimer.singleShot') @@ -591,7 +593,8 @@ class TestServiceManager(TestCase): service_manager.on_double_click_live() service_manager.on_single_click_preview() # THEN: timer should not be started - self.assertEquals(mocked_singleShot.call_count, 0, 'Should not be called') + mocked_make_live.assert_called_with() + self.assertEqual(mocked_singleShot.call_count, 0, 'Should not be called') @patch(u'openlp.core.ui.servicemanager.ServiceManager.make_preview') def test_single_click_timeout_single(self, mocked_make_preview): @@ -603,7 +606,8 @@ class TestServiceManager(TestCase): # WHEN: on_single_click_preview() is called service_manager.on_single_click_preview_timeout() # THEN: make_preview() should have been called - self.assertEquals(mocked_make_preview.call_count, 1, 'Should have been called once') + self.assertEqual(mocked_make_preview.call_count, 1, + 'ServiceManager.make_preview() should have been called once') @patch(u'openlp.core.ui.servicemanager.ServiceManager.make_preview') @patch(u'openlp.core.ui.servicemanager.ServiceManager.make_live') @@ -616,5 +620,62 @@ class TestServiceManager(TestCase): # WHEN: on_single_click_preview() is called after a double click service_manager.on_double_click_live() service_manager.on_single_click_preview_timeout() - # THEN: make_preview() should have been called - self.assertEquals(mocked_make_preview.call_count, 0, 'Should not be called') + # THEN: make_preview() should not have been called + self.assertEqual(mocked_make_preview.call_count, 0, 'ServiceManager.make_preview() should not be called') + + @patch(u'openlp.core.ui.servicemanager.shutil.copy') + @patch(u'openlp.core.ui.servicemanager.zipfile') + @patch(u'openlp.core.ui.servicemanager.ServiceManager.save_file_as') + def test_save_file_raises_permission_error(self, mocked_save_file_as, mocked_zipfile, mocked_shutil_copy): + """ + Test that when a PermissionError is raised when trying to save a file, it is handled correctly + """ + # GIVEN: A service manager, a service to save + mocked_main_window = MagicMock() + mocked_main_window.service_manager_settings_section = 'servicemanager' + Registry().register('main_window', mocked_main_window) + Registry().register('application', MagicMock()) + service_manager = ServiceManager(None) + service_manager._file_name = os.path.join('temp', 'filename.osz') + service_manager._save_lite = False + service_manager.service_items = [] + service_manager.service_theme = 'Default' + service_manager.service_manager_list = MagicMock() + mocked_save_file_as.return_value = True + mocked_zipfile.ZipFile.return_value = MagicMock() + mocked_shutil_copy.side_effect = PermissionError + + # WHEN: The service is saved and a PermissionError is thrown + result = service_manager.save_file() + + # THEN: The "save_as" method is called to save the service + self.assertTrue(result) + mocked_save_file_as.assert_called_with() + + @patch(u'openlp.core.ui.servicemanager.shutil.copy') + @patch(u'openlp.core.ui.servicemanager.zipfile') + @patch(u'openlp.core.ui.servicemanager.ServiceManager.save_file_as') + def test_save_local_file_raises_permission_error(self, mocked_save_file_as, mocked_zipfile, mocked_shutil_copy): + """ + Test that when a PermissionError is raised when trying to save a local file, it is handled correctly + """ + # GIVEN: A service manager, a service to save + mocked_main_window = MagicMock() + mocked_main_window.service_manager_settings_section = 'servicemanager' + Registry().register('main_window', mocked_main_window) + Registry().register('application', MagicMock()) + service_manager = ServiceManager(None) + service_manager._file_name = os.path.join('temp', 'filename.osz') + service_manager._save_lite = False + service_manager.service_items = [] + service_manager.service_theme = 'Default' + mocked_save_file_as.return_value = True + mocked_zipfile.ZipFile.return_value = MagicMock() + mocked_shutil_copy.side_effect = PermissionError + + # WHEN: The service is saved and a PermissionError is thrown + result = service_manager.save_local_file() + + # THEN: The "save_as" method is called to save the service + self.assertTrue(result) + mocked_save_file_as.assert_called_with() diff --git a/tests/functional/openlp_plugins/songs/test_songshowplusimport.py b/tests/functional/openlp_plugins/songs/test_songshowplusimport.py index 2dc28d541..e611089cc 100644 --- a/tests/functional/openlp_plugins/songs/test_songshowplusimport.py +++ b/tests/functional/openlp_plugins/songs/test_songshowplusimport.py @@ -52,6 +52,8 @@ class TestSongShowPlusFileImport(SongImportTestHelper): self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json'))) self.file_import([os.path.join(TEST_PATH, 'a mighty fortress is our god.sbsong')], self.load_external_result_data(os.path.join(TEST_PATH, 'a mighty fortress is our god.json'))) + self.file_import([os.path.join(TEST_PATH, 'cleanse-me.sbsong')], + self.load_external_result_data(os.path.join(TEST_PATH, 'cleanse-me.json'))) class TestSongShowPlusImport(TestCase): diff --git a/tests/resources/songshowplussongs/cleanse-me.json b/tests/resources/songshowplussongs/cleanse-me.json new file mode 100644 index 000000000..c88b434f9 --- /dev/null +++ b/tests/resources/songshowplussongs/cleanse-me.json @@ -0,0 +1,38 @@ +{ + "authors": [ + "J. Edwin Orr" + ], + "ccli_number": 56307, + "comments": "", + "copyright": "Public Domain ", + "song_book_name": "", + "song_number": 438, + "title": "Cleanse Me [438]", + "topics": [ + "Cleansing", + "Communion", + "Consecration", + "Holiness", + "Holy Spirit", + "Revival" + ], + "verse_order_list": [], + "verses": [ + [ + "Search me, O God,\r\nAnd know my heart today;\r\nTry me, O Savior,\r\nKnow my thoughts, I pray.\r\nSee if there be\r\nSome wicked way in me;\r\nCleanse me from every sin\r\nAnd set me free.", + "v1" + ], + [ + "I praise Thee, Lord,\r\nFor cleansing me from sin;\r\nFulfill Thy Word,\r\nAnd make me pure within.\r\nFill me with fire\r\nWhere once I burned with shame;\r\nGrant my desire\r\nTo magnify Thy name.", + "v2" + ], + [ + "Lord, take my life,\r\nAnd make it wholly Thine;\r\nFill my poor heart\r\nWith Thy great love divine.\r\nTake all my will,\r\nMy passion, self and pride;\r\nI now surrender, Lord\r\nIn me abide.", + "v3" + ], + [ + "O Holy Ghost,\r\nRevival comes from Thee;\r\nSend a revival,\r\nStart the work in me.\r\nThy Word declares\r\nThou wilt supply our need;\r\nFor blessings now,\r\nO Lord, I humbly plead.", + "v4" + ] + ] +} diff --git a/tests/resources/songshowplussongs/cleanse-me.sbsong b/tests/resources/songshowplussongs/cleanse-me.sbsong new file mode 100644 index 000000000..aa9915f8e Binary files /dev/null and b/tests/resources/songshowplussongs/cleanse-me.sbsong differ diff --git a/tests/utils/test_pylint.py b/tests/utils/test_pylint.py new file mode 100644 index 000000000..dc6c83909 --- /dev/null +++ b/tests/utils/test_pylint.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2016 OpenLP Developers # +# --------------------------------------------------------------------------- # +# 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; version 2 of the License. # +# # +# 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., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +Package to test for proper bzr tags. +""" +import os +import logging +import platform +from unittest import TestCase, SkipTest + +try: + from pylint import epylint as lint + from pylint.__pkginfo__ import version +except ImportError: + raise SkipTest('pylint not installed - skipping tests using pylint.') + +from openlp.core.common import is_win + +TOLERATED_ERRORS = {'registryproperties.py': ['access-member-before-definition'], + 'opensong.py': ['no-name-in-module'], + 'maindisplay.py': ['no-name-in-module']} + + +class TestPylint(TestCase): + + def test_pylint(self): + """ + Test for pylint errors + """ + # GIVEN: Some checks to disable and enable, and the pylint script + disabled_checks = 'import-error,no-member' + enabled_checks = 'missing-format-argument-key,unused-format-string-argument' + if is_win() or 'arch' in platform.dist()[0].lower(): + pylint_script = 'pylint' + else: + pylint_script = 'pylint3' + + # WHEN: Running pylint + (pylint_stdout, pylint_stderr) = \ + lint.py_run('openlp --errors-only --disable={disabled} --enable={enabled} ' + '--reports=no --output-format=parseable'.format(disabled=disabled_checks, + enabled=enabled_checks), + return_std=True, script=pylint_script) + stdout = pylint_stdout.read() + stderr = pylint_stderr.read() + filtered_stdout = self._filter_tolerated_errors(stdout) + print(filtered_stdout) + print(stderr) + + # THEN: The output should be empty + self.assertTrue(filtered_stdout == '', 'PyLint should find no errors') + + def _filter_tolerated_errors(self, pylint_output): + """ + Filter out errors we tolerate. + """ + filtered_output = '' + for line in pylint_output.splitlines(): + # Filter out module info lines + if line.startswith('**'): + continue + # Filter out undefined-variable error releated to WindowsError + elif 'undefined-variable' in line and 'WindowsError' in line: + continue + # Filter out PyQt related errors + elif ('no-name-in-module' in line or 'no-member' in line) and 'PyQt5' in line: + continue + elif self._is_line_tolerated(line): + continue + else: + filtered_output += line + '\n' + return filtered_output.strip() + + def _is_line_tolerated(self, line): + """ + Check if line constains a tolerated error + """ + for filename in TOLERATED_ERRORS: + for tolerated_error in TOLERATED_ERRORS[filename]: + if filename in line and tolerated_error in line: + return True + return False