From ff421df2bf1d6aa30988f7da41cb459e10673da2 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Fri, 12 Jan 2018 11:29:32 -0700 Subject: [PATCH 1/7] Fix bug #1742910 Fixes: https://launchpad.net/bugs/1742910 --- openlp/core/app.py | 2 +- openlp/core/threading.py | 18 ++++++++-------- openlp/core/ui/mainwindow.py | 7 +++---- .../functional/openlp_core/test_threading.py | 21 ++++++++++--------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/openlp/core/app.py b/openlp/core/app.py index b6b3a73f9..2a6cf8494 100644 --- a/openlp/core/app.py +++ b/openlp/core/app.py @@ -63,8 +63,8 @@ class OpenLP(QtWidgets.QApplication, LogMixin): The core application class. This class inherits from Qt's QApplication class in order to provide the core of the application. """ - args = [] + worker_threads = {} def exec(self): """ diff --git a/openlp/core/threading.py b/openlp/core/threading.py index 73e11cdf5..6f5f2b4af 100644 --- a/openlp/core/threading.py +++ b/openlp/core/threading.py @@ -50,12 +50,12 @@ def run_thread(worker, thread_name, can_start=True): """ if not thread_name: raise ValueError('A thread_name is required when calling the "run_thread" function') - main_window = Registry().get('main_window') - if thread_name in main_window.threads: + application = Registry().get('application') + if thread_name in application.worker_threads: raise KeyError('A thread with the name "{}" has already been created, please use another'.format(thread_name)) # Create the thread and add the thread and the worker to the parent thread = QtCore.QThread() - main_window.threads[thread_name] = { + application.worker_threads[thread_name] = { 'thread': thread, 'worker': worker } @@ -78,7 +78,7 @@ def get_thread_worker(thread_name): :param str thread_name: The name of the thread :returns: The worker for this thread name """ - return Registry().get('main_window').threads.get(thread_name) + return Registry().get('application').worker_threads.get(thread_name) def is_thread_finished(thread_name): @@ -88,8 +88,8 @@ def is_thread_finished(thread_name): :param str thread_name: The name of the thread :returns: True if the thread is finished, False if it is still running """ - main_window = Registry().get('main_window') - return thread_name not in main_window.threads or main_window.threads[thread_name]['thread'].isFinished() + app = Registry().get('application') + return thread_name not in app.worker_threads or app.worker_threads[thread_name]['thread'].isFinished() def make_remove_thread(thread_name): @@ -105,7 +105,7 @@ def make_remove_thread(thread_name): :param str thread_name: The name of the thread to stop and remove """ - main_window = Registry().get('main_window') - if thread_name in main_window.threads: - del main_window.threads[thread_name] + application = Registry().get('application') + if thread_name in application.worker_threads: + del application.worker_threads[thread_name] return remove_thread diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 54d91d36e..43357c837 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -477,7 +477,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): """ super(MainWindow, self).__init__() Registry().register('main_window', self) - self.threads = {} self.clipboard = self.application.clipboard() self.arguments = ''.join(self.application.args) # Set up settings sections for the main application (not for use by plugins). @@ -557,11 +556,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): wait_dialog.setAutoClose(False) wait_dialog.setCancelButton(None) wait_dialog.show() - for thread_name in self.threads.keys(): + for thread_name in self.application.worker_threads.keys(): log.debug('Waiting for thread %s', thread_name) self.application.processEvents() - thread = self.threads[thread_name]['thread'] - worker = self.threads[thread_name]['worker'] + thread = self.application.worker_threads[thread_name]['thread'] + worker = self.application.worker_threads[thread_name]['worker'] try: if worker and hasattr(worker, 'stop'): # If the worker has a stop method, run it diff --git a/tests/functional/openlp_core/test_threading.py b/tests/functional/openlp_core/test_threading.py index 382774e51..bd8ff4891 100644 --- a/tests/functional/openlp_core/test_threading.py +++ b/tests/functional/openlp_core/test_threading.py @@ -47,9 +47,9 @@ def test_run_thread_exists(MockRegistry): Test that trying to run a thread with a name that already exists will throw a KeyError """ # GIVEN: A mocked registry with a main window object - mocked_main_window = MagicMock() - mocked_main_window.threads = {'test_thread': MagicMock()} - MockRegistry.return_value.get.return_value = mocked_main_window + mocked_application = MagicMock() + mocked_application.worker_threads = {'test_thread': MagicMock()} + MockRegistry.return_value.get.return_value = mocked_application # WHEN: run_thread() is called try: @@ -66,18 +66,19 @@ def test_run_thread(MockRegistry, MockQThread): Test that running a thread works correctly """ # GIVEN: A mocked registry with a main window object - mocked_main_window = MagicMock() - mocked_main_window.threads = {} - MockRegistry.return_value.get.return_value = mocked_main_window + mocked_application = MagicMock() + mocked_application.worker_threads = {} + MockRegistry.return_value.get.return_value = mocked_application # WHEN: run_thread() is called run_thread(MagicMock(), 'test_thread') # THEN: The thread should be in the threads list and the correct methods should have been called - assert len(mocked_main_window.threads.keys()) == 1, 'There should be 1 item in the list of threads' - assert list(mocked_main_window.threads.keys()) == ['test_thread'], 'The test_thread item should be in the list' - mocked_worker = mocked_main_window.threads['test_thread']['worker'] - mocked_thread = mocked_main_window.threads['test_thread']['thread'] + assert len(mocked_application.worker_threads.keys()) == 1, 'There should be 1 item in the list of threads' + assert list(mocked_application.worker_threads.keys()) == ['test_thread'], \ + 'The test_thread item should be in the list' + mocked_worker = mocked_application.worker_threads['test_thread']['worker'] + mocked_thread = mocked_application.worker_threads['test_thread']['thread'] mocked_worker.moveToThread.assert_called_once_with(mocked_thread) mocked_thread.started.connect.assert_called_once_with(mocked_worker.start) expected_quit_calls = [call(mocked_thread.quit), call(mocked_worker.deleteLater)] From 6f9f68a99bb9c3d39e5040987d17f7820236780c Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Fri, 12 Jan 2018 16:45:06 -0700 Subject: [PATCH 2/7] Add some more tests for openlp.core.api.deploy --- .../functional/openlp_core/api/test_deploy.py | 101 ++++++++++++++++-- 1 file changed, 90 insertions(+), 11 deletions(-) diff --git a/tests/functional/openlp_core/api/test_deploy.py b/tests/functional/openlp_core/api/test_deploy.py index 2b544702c..d46ac8d43 100644 --- a/tests/functional/openlp_core/api/test_deploy.py +++ b/tests/functional/openlp_core/api/test_deploy.py @@ -21,11 +21,12 @@ ############################################################################### from tempfile import mkdtemp from unittest import TestCase +from unittest.mock import MagicMock, patch -from openlp.core.api.deploy import deploy_zipfile -from openlp.core.common.path import Path, copyfile +from openlp.core.api.deploy import deploy_zipfile, download_sha256, download_and_check +from openlp.core.common.path import Path -TEST_PATH = (Path(__file__).parent / '..' / '..' / '..' / 'resources').resolve() +CONFIG_FILE = '2c266badff1e3d140664c50fd1460a2b332b24d5ad8c267fa62e506b5eb6d894 deploy/site.zip\n2017_06_27' class TestRemoteDeploy(TestCase): @@ -45,17 +46,95 @@ class TestRemoteDeploy(TestCase): """ self.app_root_path.rmtree() - def test_deploy_zipfile(self): + @patch('openlp.core.api.deploy.ZipFile') + def test_deploy_zipfile(self, MockZipFile): """ Remote Deploy tests - test the dummy zip file is processed correctly """ # GIVEN: A new downloaded zip file - zip_path = TEST_PATH / 'remotes' / 'site.zip' - app_root_path = self.app_root_path / 'site.zip' - copyfile(zip_path, app_root_path) + mocked_zipfile = MagicMock() + MockZipFile.return_value = mocked_zipfile + root_path = Path('/tmp/remotes') - # WHEN: I process the zipfile - deploy_zipfile(self.app_root_path, 'site.zip') + # WHEN: deploy_zipfile() is called + deploy_zipfile(root_path, 'site.zip') - # THEN: test if www directory has been created - assert (self.app_root_path / 'www').is_dir(), 'We should have a www directory' + # THEN: the zip file should have been extracted to the right location + MockZipFile.assert_called_once_with('/tmp/remotes/site.zip') + mocked_zipfile.extractall.assert_called_once_with('/tmp/remotes') + + @patch('openlp.core.api.deploy.Registry') + @patch('openlp.core.api.deploy.get_web_page') + def test_download_sha256_connection_error(self, mocked_get_web_page, MockRegistry): + """ + Test that if a ConnectionError occurs while downloading a sha256 False is returned + """ + # GIVEN: A bunch of mocks + MockRegistry.return_value.get.return_value.applicationVersion.return_value = '1.0' + mocked_get_web_page.side_effect = ConnectionError() + + # WHEN: download_sha256() is called + result = download_sha256() + + # THEN: The result should be False + assert result is False, 'download_sha256() should return False when encountering ConnectionError' + + @patch('openlp.core.api.deploy.Registry') + @patch('openlp.core.api.deploy.get_web_page') + def test_download_sha256_no_config(self, mocked_get_web_page, MockRegistry): + """ + Test that if there's no config when downloading a sha256 None is returned + """ + # GIVEN: A bunch of mocks + MockRegistry.return_value.get.return_value.applicationVersion.return_value = '1.0' + mocked_get_web_page.return_value = None + + # WHEN: download_sha256() is called + result = download_sha256() + + # THEN: The result should be Nonw + assert result is None, 'download_sha256() should return None when there is a problem downloading the page' + + @patch('openlp.core.api.deploy.Registry') + @patch('openlp.core.api.deploy.get_web_page') + def test_download_sha256(self, mocked_get_web_page, MockRegistry): + """ + Test that the sha256 and the version are returned + """ + # GIVEN: A bunch of mocks + MockRegistry.return_value.get.return_value.applicationVersion.return_value = '1.0' + mocked_get_web_page.return_value = CONFIG_FILE + + # WHEN: download_sha256() is called + result = download_sha256() + + # THEN: The result should be Nonw + assert result == ('2c266badff1e3d140664c50fd1460a2b332b24d5ad8c267fa62e506b5eb6d894', '2017_06_27'), \ + 'download_sha256() should return a tuple of sha256 and version' + + @patch('openlp.core.api.deploy.Registry') + @patch('openlp.core.api.deploy.download_sha256') + @patch('openlp.core.api.deploy.get_url_file_size') + @patch('openlp.core.api.deploy.download_file') + @patch('openlp.core.api.deploy.AppLocation.get_section_data_path') + @patch('openlp.core.api.deploy.deploy_zipfile') + def test_download_and_check(self, mocked_deploy_zipfile, mocked_get_data_path, mocked_download_file, + mocked_get_url_file_size, mocked_download_sha256, MockRegistry): + # GIVEN: A bunch of mocks + mocked_get_data_path.return_value = Path('/tmp/remotes') + mocked_download_file.return_value = True + mocked_get_url_file_size.return_value = 5 + mocked_download_sha256.return_value = ('asdfgh', '0.1') + MockRegistry.return_value.get.return_value.applicationVersion.return_value = '1.0' + mocked_callback = MagicMock() + + # WHEN: download_and_check() is called + download_and_check(mocked_callback) + + # THEN: The correct things should have been done + mocked_download_sha256.assert_called_once_with() + mocked_get_url_file_size.assert_called_once_with('https://get.openlp.org/webclient/site.zip') + mocked_callback.setRange.assert_called_once_with(0, 5) + mocked_download_file.assert_called_once_with(mocked_callback, 'https://get.openlp.org/webclient/site.zip', + Path('/tmp/remotes/site.zip'), sha256='asdfgh') + mocked_deploy_zipfile.assert_called_once_with(Path('/tmp/remotes'), 'site.zip') From 83bc19520a856f9126939056ae5f4a7dab5ff977 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Fri, 12 Jan 2018 21:57:16 -0700 Subject: [PATCH 3/7] Removed a duplicate test file, fixed up some tests, added some more tests --- openlp/core/common/path.py | 4 +- openlp/core/threading.py | 14 ++- .../openlp_core/api/http/test_error.py | 58 +++++----- .../openlp_core/common/test_path.py | 15 ++- tests/functional/openlp_core/lib/test_path.py | 87 -------------- tests/functional/openlp_core/lib/test_ui.py | 83 +++++++++----- .../functional/openlp_core/test_threading.py | 107 +++++++++++++++++- 7 files changed, 217 insertions(+), 151 deletions(-) delete mode 100644 tests/functional/openlp_core/lib/test_path.py diff --git a/openlp/core/common/path.py b/openlp/core/common/path.py index 7d0c386db..ec251c5f1 100644 --- a/openlp/core/common/path.py +++ b/openlp/core/common/path.py @@ -26,9 +26,9 @@ from contextlib import suppress from openlp.core.common import is_win if is_win(): - from pathlib import WindowsPath as PathVariant + from pathlib import WindowsPath as PathVariant # pragma: nocover else: - from pathlib import PosixPath as PathVariant + from pathlib import PosixPath as PathVariant # pragma: nocover log = logging.getLogger(__name__) diff --git a/openlp/core/threading.py b/openlp/core/threading.py index 6f5f2b4af..55232e828 100644 --- a/openlp/core/threading.py +++ b/openlp/core/threading.py @@ -78,7 +78,10 @@ def get_thread_worker(thread_name): :param str thread_name: The name of the thread :returns: The worker for this thread name """ - return Registry().get('application').worker_threads.get(thread_name) + thread_info = Registry().get('application').worker_threads.get(thread_name) + if not thread_info: + raise KeyError('No thread named "{}" exists'.format(thread_name)) + return thread_info.get('worker') def is_thread_finished(thread_name): @@ -88,8 +91,10 @@ def is_thread_finished(thread_name): :param str thread_name: The name of the thread :returns: True if the thread is finished, False if it is still running """ - app = Registry().get('application') - return thread_name not in app.worker_threads or app.worker_threads[thread_name]['thread'].isFinished() + thread_info = Registry().get('application').worker_threads.get(thread_name) + if not thread_info: + raise KeyError('No thread named "{}" exists'.format(thread_name)) + return thread_info['thread'].isFinished() def make_remove_thread(thread_name): @@ -99,7 +104,8 @@ def make_remove_thread(thread_name): :param str thread_name: The name of the thread which should be removed from the thread registry. :returns: A function which will remove the thread from the thread registry. """ - def remove_thread(): + + def remove_thread(): # pragma: nocover """ Stop and remove a registered thread diff --git a/tests/functional/openlp_core/api/http/test_error.py b/tests/functional/openlp_core/api/http/test_error.py index 376cdef8b..8e13a1dcc 100644 --- a/tests/functional/openlp_core/api/http/test_error.py +++ b/tests/functional/openlp_core/api/http/test_error.py @@ -22,38 +22,40 @@ """ Functional tests to test the API Error Class. """ - -from unittest import TestCase - -from openlp.core.api.http.errors import NotFound, ServerError +from openlp.core.api.http.errors import HttpError, NotFound, ServerError -class TestApiError(TestCase): +def test_http_error(): """ - A test suite to test out the Error in the API code + Test the HTTPError class """ - def test_not_found(self): - """ - Test the Not Found error displays the correct information - """ - # GIVEN: - # WHEN: I raise an exception - with self.assertRaises(Exception) as context: - raise NotFound() + # GIVEN: An HTTPError class + # WHEN: An instance is created + error = HttpError(400, 'Access Denied') - # THEN: we get an error and a status - assert 'Not Found' == context.exception.message, 'A Not Found exception should be thrown' - assert 404 == context.exception.status, 'A 404 status should be thrown' + # THEN: The to_response() method should return the correct information + assert error.to_response() == ('Access Denied', 400), 'to_response() should have returned the correct info' - def test_server_error(self): - """ - Test the server error displays the correct information - """ - # GIVEN: - # WHEN: I raise an exception - with self.assertRaises(Exception) as context: - raise ServerError() - # THEN: we get an error and a status - assert'Server Error' == context.exception.message, 'A Not Found exception should be thrown' - assert 500 == context.exception.status, 'A 500 status should be thrown' +def test_not_found(): + """ + Test the Not Found error displays the correct information + """ + # GIVEN: A NotFound class + # WHEN: An instance is created + error = NotFound() + + # THEN: The to_response() method should return the correct information + assert error.to_response() == ('Not Found', 404), 'to_response() should have returned the correct info' + + +def test_server_error(): + """ + Test the server error displays the correct information + """ + # GIVEN: A ServerError class + # WHEN: An instance of the class is created + error = ServerError() + + # THEN: The to_response() method should return the correct information + assert error.to_response() == ('Server Error', 500), 'to_response() should have returned the correct info' diff --git a/tests/functional/openlp_core/common/test_path.py b/tests/functional/openlp_core/common/test_path.py index 305713bc3..39bb68f92 100644 --- a/tests/functional/openlp_core/common/test_path.py +++ b/tests/functional/openlp_core/common/test_path.py @@ -27,7 +27,7 @@ from unittest import TestCase from unittest.mock import ANY, MagicMock, patch from openlp.core.common.path import Path, copy, copyfile, copytree, create_paths, path_to_str, replace_params, \ - str_to_path, which + str_to_path, which, files_to_paths class TestShutil(TestCase): @@ -401,3 +401,16 @@ class TestPath(TestCase): except: # THEN: `create_paths` raises an exception pass + + def test_files_to_paths(self): + """ + Test the files_to_paths() method + """ + # GIVEN: A list of string filenames + test_files = ['/tmp/openlp/file1.txt', '/tmp/openlp/file2.txt'] + + # WHEN: files_to_paths is called + result = files_to_paths(test_files) + + # THEN: The result should be a list of Paths + assert result == [Path('/tmp/openlp/file1.txt'), Path('/tmp/openlp/file2.txt')] diff --git a/tests/functional/openlp_core/lib/test_path.py b/tests/functional/openlp_core/lib/test_path.py deleted file mode 100644 index ce6f655df..000000000 --- a/tests/functional/openlp_core/lib/test_path.py +++ /dev/null @@ -1,87 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2017 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 the openlp.core.lib.path package. -""" -import os -from unittest import TestCase - -from openlp.core.common.path import Path, path_to_str, str_to_path - - -class TestPath(TestCase): - """ - Tests for the :mod:`openlp.core.lib.path` module - """ - - def test_path_to_str_type_error(self): - """ - Test that `path_to_str` raises a type error when called with an invalid type - """ - # GIVEN: The `path_to_str` function - # WHEN: Calling `path_to_str` with an invalid Type - # THEN: A TypeError should have been raised - with self.assertRaises(TypeError): - path_to_str(str()) - - def test_path_to_str_none(self): - """ - Test that `path_to_str` correctly converts the path parameter when passed with None - """ - # GIVEN: The `path_to_str` function - # WHEN: Calling the `path_to_str` function with None - result = path_to_str(None) - - # THEN: `path_to_str` should return an empty string - assert result == '' - - def test_path_to_str_path_object(self): - """ - Test that `path_to_str` correctly converts the path parameter when passed a Path object - """ - # GIVEN: The `path_to_str` function - # WHEN: Calling the `path_to_str` function with a Path object - result = path_to_str(Path('test/path')) - - # THEN: `path_to_str` should return a string representation of the Path object - assert result == os.path.join('test', 'path') - - def test_str_to_path_type_error(self): - """ - Test that `str_to_path` raises a type error when called with an invalid type - """ - # GIVEN: The `str_to_path` function - # WHEN: Calling `str_to_path` with an invalid Type - # THEN: A TypeError should have been raised - with self.assertRaises(TypeError): - str_to_path(Path()) - - def test_str_to_path_empty_str(self): - """ - Test that `str_to_path` correctly converts the string parameter when passed with and empty string - """ - # GIVEN: The `str_to_path` function - # WHEN: Calling the `str_to_path` function with None - result = str_to_path('') - - # THEN: `path_to_str` should return None - assert result is None diff --git a/tests/functional/openlp_core/lib/test_ui.py b/tests/functional/openlp_core/lib/test_ui.py index 7c1e94f36..42fab0191 100644 --- a/tests/functional/openlp_core/lib/test_ui.py +++ b/tests/functional/openlp_core/lib/test_ui.py @@ -23,14 +23,14 @@ Package to test the openlp.core.lib.ui package. """ from unittest import TestCase -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, patch, call from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common.i18n import UiStrings, translate from openlp.core.lib.ui import add_welcome_page, create_button_box, create_horizontal_adjusting_combo_box, \ create_button, create_action, create_valign_selection_widgets, find_and_set_in_combo_box, create_widget_action, \ - set_case_insensitive_completer + set_case_insensitive_completer, critical_error_message_box class TestUi(TestCase): @@ -80,6 +80,34 @@ class TestUi(TestCase): assert 1 == len(btnbox.buttons()) assert QtWidgets.QDialogButtonBox.HelpRole, btnbox.buttonRole(btnbox.buttons()[0]) + @patch('openlp.core.lib.ui.Registry') + def test_critical_error_message_box(self, MockRegistry): + """ + Test the critical_error_message_box() function + """ + # GIVEN: A mocked Registry + # WHEN: critical_error_message_box() is called + critical_error_message_box('Error', 'This is an error') + + # THEN: The error_message() method on the main window should be called + MockRegistry.return_value.get.return_value.error_message.assert_called_once_with('Error', 'This is an error') + + @patch('openlp.core.lib.ui.QtWidgets.QMessageBox.critical') + def test_critical_error_question(self, mocked_critical): + """ + Test the critical_error_message_box() function + """ + # GIVEN: A mocked critical() method and some other mocks + mocked_parent = MagicMock() + + # WHEN: critical_error_message_box() is called + critical_error_message_box(None, 'This is a question', mocked_parent, True) + + # THEN: The error_message() method on the main window should be called + mocked_critical.assert_called_once_with(mocked_parent, 'Error', 'This is a question', + QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | + QtWidgets.QMessageBox.No)) + def test_create_horizontal_adjusting_combo_box(self): """ Test creating a horizontal adjusting combo box @@ -92,65 +120,64 @@ class TestUi(TestCase): # THEN: We should get a ComboBox assert isinstance(combo, QtWidgets.QComboBox) - assert 'combo1' == combo.objectName() + assert combo.objectName() == 'combo1' assert QtWidgets.QComboBox.AdjustToMinimumContentsLength == combo.sizeAdjustPolicy() - def test_create_button(self): + @patch('openlp.core.lib.ui.log') + def test_create_button(self, mocked_log): """ Test creating a button """ # GIVEN: A dialog dialog = QtWidgets.QDialog() - # WHEN: We create the button - btn = create_button(dialog, 'my_btn') - - # THEN: We should get a button with a name - assert isinstance(btn, QtWidgets.QPushButton) - assert 'my_btn' == btn.objectName() - assert btn.isEnabled() is True - # WHEN: We create a button with some attributes - btn = create_button(dialog, 'my_btn', text='Hello', tooltip='How are you?', enabled=False) + btn = create_button(dialog, 'my_btn', text='Hello', tooltip='How are you?', enabled=False, role='test', test=1) # THEN: We should get a button with those attributes assert isinstance(btn, QtWidgets.QPushButton) - assert 'Hello' == btn.text() - assert 'How are you?' == btn.toolTip() + assert btn.objectName() == 'my_btn' + assert btn.text() == 'Hello' + assert btn.toolTip() == 'How are you?' assert btn.isEnabled() is False + assert mocked_log.warning.call_args_list == [call('The role "test" is not defined in create_push_button().'), + call('Parameter test was not consumed in create_button().')] + + def test_create_tool_button(self): + """ + Test creating a toolbutton + """ + # GIVEN: A dialog + dialog = QtWidgets.QDialog() # WHEN: We create a toolbutton btn = create_button(dialog, 'my_btn', btn_class='toolbutton') # THEN: We should get a toolbutton assert isinstance(btn, QtWidgets.QToolButton) - assert 'my_btn' == btn.objectName() + assert btn.objectName() == 'my_btn' assert btn.isEnabled() is True - def test_create_action(self): + @patch('openlp.core.lib.ui.log') + def test_create_action(self, mocked_log): """ Test creating an action """ # GIVEN: A dialog dialog = QtWidgets.QDialog() - # WHEN: We create an action - action = create_action(dialog, 'my_action') - - # THEN: We should get a QAction - assert isinstance(action, QtWidgets.QAction) - assert 'my_action' == action.objectName() - # WHEN: We create an action with some properties action = create_action(dialog, 'my_action', text='my text', icon=':/wizards/wizard_firsttime.bmp', - tooltip='my tooltip', statustip='my statustip') + tooltip='my tooltip', statustip='my statustip', test=1) # THEN: These properties should be set assert isinstance(action, QtWidgets.QAction) - assert 'my text' == action.text() + assert action.objectName() == 'my_action' + assert action.text() == 'my text' assert isinstance(action.icon(), QtGui.QIcon) - assert 'my tooltip' == action.toolTip() - assert 'my statustip' == action.statusTip() + assert action.toolTip() == 'my tooltip' + assert action.statusTip() == 'my statustip' + mocked_log.warning.assert_called_once_with('Parameter test was not consumed in create_action().') def test_create_action_on_mac_osx(self): """ diff --git a/tests/functional/openlp_core/test_threading.py b/tests/functional/openlp_core/test_threading.py index bd8ff4891..34f75c4b1 100644 --- a/tests/functional/openlp_core/test_threading.py +++ b/tests/functional/openlp_core/test_threading.py @@ -22,9 +22,10 @@ """ Package to test the openlp.core.threading package. """ +from inspect import isfunction from unittest.mock import MagicMock, call, patch -from openlp.core.version import run_thread +from openlp.core.threading import ThreadWorker, run_thread, get_thread_worker, is_thread_finished, make_remove_thread def test_run_thread_no_name(): @@ -88,3 +89,107 @@ def test_run_thread(MockRegistry, MockQThread): 'The threads finished signal should be connected to its deleteLater slot' assert mocked_thread.finished.connect.call_count == 2, 'The signal should have been connected twice' mocked_thread.start.assert_called_once_with() + + +def test_thread_worker(): + """ + Test that creating a thread worker object and calling start throws and NotImplementedError + """ + # GIVEN: A ThreadWorker class + worker = ThreadWorker() + + try: + # WHEN: calling start() + worker.start() + assert False, 'A NotImplementedError should have been thrown' + except NotImplementedError: + # A NotImplementedError should be thrown + pass + except Exception: + assert False, 'A NotImplementedError should have been thrown' + + +@patch('openlp.core.threading.Registry') +def test_get_thread_worker(MockRegistry): + """ + Test that calling the get_thread_worker() function returns the correct worker + """ + # GIVEN: A mocked thread worker + mocked_worker = MagicMock() + MockRegistry.return_value.get.return_value.worker_threads = {'test_thread': {'worker': mocked_worker}} + + # WHEN: get_thread_worker() is called + worker = get_thread_worker('test_thread') + + # THEN: The mocked worker is returned + assert worker is mocked_worker, 'The mocked worker should have been returned' + + +@patch('openlp.core.threading.Registry') +def test_get_thread_worker_mising(MockRegistry): + """ + Test that calling the get_thread_worker() function raises a KeyError if it does not exist + """ + # GIVEN: A mocked thread worker + MockRegistry.return_value.get.return_value.worker_threads = {} + + try: + # WHEN: get_thread_worker() is called + get_thread_worker('test_thread') + assert False, 'A KeyError should have been raised' + except KeyError: + # THEN: The mocked worker is returned + pass + except Exception: + assert False, 'A KeyError should have been raised' + + +@patch('openlp.core.threading.Registry') +def test_is_thread_finished(MockRegistry): + """ + Test the is_thread_finished() function + """ + # GIVEN: A mock thread and worker + mocked_thread = MagicMock() + mocked_thread.isFinished.return_value = True + MockRegistry.return_value.get.return_value.worker_threads = {'test': {'thread': mocked_thread}} + + # WHEN: is_thread_finished() is called + result = is_thread_finished('test') + + # THEN: The result should be correct + assert result is True, 'is_thread_finished should have returned True' + + +@patch('openlp.core.threading.Registry') +def test_is_thread_finished_mising(MockRegistry): + """ + Test that calling the is_thread_finished() function raises a KeyError if it does not exist + """ + # GIVEN: A mocked thread worker + MockRegistry.return_value.get.return_value.worker_threads = {} + + try: + # WHEN: get_thread_worker() is called + is_thread_finished('test_thread') + assert False, 'A KeyError should have been raised when calling is_thread_finished' + except KeyError: + # THEN: The mocked worker is returned + pass + except Exception: + assert False, 'A KeyError should have been raised when calling is_thread_finished' + + +def test_make_remove_thread(): + """ + Test the make_remove_thread() function + """ + # GIVEN: A thread name + thread_name = 'test_thread' + + # WHEN: make_remove_thread() is called + rm_func = make_remove_thread(thread_name) + + # THEN: The result should be a function + assert isfunction(rm_func), 'make_remove_thread should return a function' + assert rm_func.__name__ == 'remove_thread' From 11f528d09c6f8630bc104d006de4ad4278178fb3 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Fri, 12 Jan 2018 22:24:53 -0700 Subject: [PATCH 4/7] Change is_thread_finished() to return True if a thread is missing --- openlp/core/threading.py | 3 ++- .../functional/openlp_core/test_threading.py | 22 ++++++++----------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/openlp/core/threading.py b/openlp/core/threading.py index 55232e828..7af78cbf8 100644 --- a/openlp/core/threading.py +++ b/openlp/core/threading.py @@ -93,7 +93,8 @@ def is_thread_finished(thread_name): """ thread_info = Registry().get('application').worker_threads.get(thread_name) if not thread_info: - raise KeyError('No thread named "{}" exists'.format(thread_name)) + # If the thread doesnt exist anymore, it's probably because it is finished + return True return thread_info['thread'].isFinished() diff --git a/tests/functional/openlp_core/test_threading.py b/tests/functional/openlp_core/test_threading.py index 34f75c4b1..177f4bf99 100644 --- a/tests/functional/openlp_core/test_threading.py +++ b/tests/functional/openlp_core/test_threading.py @@ -151,33 +151,29 @@ def test_is_thread_finished(MockRegistry): """ # GIVEN: A mock thread and worker mocked_thread = MagicMock() - mocked_thread.isFinished.return_value = True + mocked_thread.isFinished.return_value = False MockRegistry.return_value.get.return_value.worker_threads = {'test': {'thread': mocked_thread}} # WHEN: is_thread_finished() is called result = is_thread_finished('test') # THEN: The result should be correct - assert result is True, 'is_thread_finished should have returned True' + assert result is False, 'is_thread_finished should have returned False' @patch('openlp.core.threading.Registry') -def test_is_thread_finished_mising(MockRegistry): +def test_is_thread_finished_missing(MockRegistry): """ - Test that calling the is_thread_finished() function raises a KeyError if it does not exist + Test that calling the is_thread_finished() function returns True if the thread doesn't exist """ # GIVEN: A mocked thread worker MockRegistry.return_value.get.return_value.worker_threads = {} - try: - # WHEN: get_thread_worker() is called - is_thread_finished('test_thread') - assert False, 'A KeyError should have been raised when calling is_thread_finished' - except KeyError: - # THEN: The mocked worker is returned - pass - except Exception: - assert False, 'A KeyError should have been raised when calling is_thread_finished' + # WHEN: get_thread_worker() is called + result = is_thread_finished('test_thread') + + # THEN: The result should be correct + assert result is True, 'is_thread_finished should return True when a thread is missing' def test_make_remove_thread(): From 17ff6a7d31954f46a2ea67788e8c545705a12c96 Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Fri, 12 Jan 2018 21:41:42 -0800 Subject: [PATCH 5/7] PJLink2 update o --- openlp/core/projectors/db.py | 5 +- openlp/core/projectors/manager.py | 30 +- openlp/core/projectors/pjlink.py | 388 +++++------ openlp/core/projectors/upgrade.py | 22 +- .../projectors/test_projector_bugfixes_01.py | 68 +- .../projectors/test_projector_db.py | 78 +-- .../projectors/test_projector_pjlink_base.py | 520 +++++++++++++-- .../test_projector_pjlink_cmd_routing.py | 57 +- .../test_projector_pjlink_commands_01.py | 626 ++++-------------- .../test_projector_pjlink_commands_02.py | 144 ++-- 10 files changed, 920 insertions(+), 1018 deletions(-) diff --git a/openlp/core/projectors/db.py b/openlp/core/projectors/db.py index a2f060d75..db8f4ac4b 100644 --- a/openlp/core/projectors/db.py +++ b/openlp/core/projectors/db.py @@ -152,6 +152,7 @@ class Projector(Base, CommonMixin): location: Column(String(30)) notes: Column(String(200)) pjlink_name: Column(String(128)) # From projector + pjlink_class Column(String(1)) # From projector manufacturer: Column(String(128)) # From projector model: Column(String(128)) # From projector other: Column(String(128)) # From projector @@ -168,7 +169,7 @@ class Projector(Base, CommonMixin): Return basic representation of Source table entry. """ return '< Projector(id="{data}", ip="{ip}", port="{port}", mac_adx="{mac}", pin="{pin}", name="{name}", ' \ - 'location="{location}", notes="{notes}", pjlink_name="{pjlink_name}", ' \ + 'location="{location}", notes="{notes}", pjlink_name="{pjlink_name}", pjlink_class="{pjlink_class}", ' \ 'manufacturer="{manufacturer}", model="{model}", serial_no="{serial}", other="{other}", ' \ 'sources="{sources}", source_list="{source_list}", model_filter="{mfilter}", ' \ 'model_lamp="{mlamp}", sw_version="{sw_ver}") >'.format(data=self.id, @@ -180,6 +181,7 @@ class Projector(Base, CommonMixin): location=self.location, notes=self.notes, pjlink_name=self.pjlink_name, + pjlink_class=self.pjlink_class, manufacturer=self.manufacturer, model=self.model, other=self.other, @@ -197,6 +199,7 @@ class Projector(Base, CommonMixin): location = Column(String(30)) notes = Column(String(200)) pjlink_name = Column(String(128)) + pjlink_class = Column(String(1)) manufacturer = Column(String(128)) model = Column(String(128)) other = Column(String(128)) diff --git a/openlp/core/projectors/manager.py b/openlp/core/projectors/manager.py index 3144f7274..3bc7373fa 100644 --- a/openlp/core/projectors/manager.py +++ b/openlp/core/projectors/manager.py @@ -522,8 +522,8 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM except (AttributeError, TypeError): pass try: - projector.timer.stop() - projector.timer.timeout.disconnect(projector.link.poll_loop) + projector.poll_timer.stop() + projector.poll_timer.timeout.disconnect(projector.link.poll_loop) except (AttributeError, TypeError): pass try: @@ -531,7 +531,6 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM projector.socket_timer.timeout.disconnect(projector.link.socket_abort) except (AttributeError, TypeError): pass - projector.thread.quit() new_list = [] for item in self.projector_list: if item.link.db_item.id == projector.link.db_item.id: @@ -733,39 +732,18 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM """ item = ProjectorItem(link=self._add_projector(projector)) item.db_item = projector - icon = QtGui.QIcon(QtGui.QPixmap(STATUS_ICONS[S_NOT_CONNECTED])) - item.icon = icon - widget = QtWidgets.QListWidgetItem(icon, + item.icon = QtGui.QIcon(QtGui.QPixmap(STATUS_ICONS[S_NOT_CONNECTED])) + widget = QtWidgets.QListWidgetItem(item.icon, item.link.name, self.projector_list_widget ) widget.setData(QtCore.Qt.UserRole, item) item.link.db_item = item.db_item item.widget = widget - thread = QtCore.QThread(parent=self) - thread.my_parent = self - item.moveToThread(thread) - thread.started.connect(item.link.thread_started) - thread.finished.connect(item.link.thread_stopped) - thread.finished.connect(thread.deleteLater) item.link.changeStatus.connect(self.update_status) item.link.projectorAuthentication.connect(self.authentication_error) item.link.projectorNoAuthentication.connect(self.no_authentication_error) item.link.projectorUpdateIcons.connect(self.update_icons) - timer = QtCore.QTimer(self) - timer.setInterval(self.poll_time) - timer.timeout.connect(item.link.poll_loop) - item.timer = timer - # Timeout in case of brain-dead projectors or cable disconnected - socket_timer = QtCore.QTimer(self) - socket_timer.setInterval(11000) - socket_timer.timeout.connect(item.link.socket_abort) - item.socket_timer = socket_timer - thread.start() - item.thread = thread - item.link.timer = timer - item.link.socket_timer = socket_timer - item.link.widget = item.widget self.projector_list.append(item) if start: item.link.connect_to_host() diff --git a/openlp/core/projectors/pjlink.py b/openlp/core/projectors/pjlink.py index 3e3bb3d2f..3517fb1e4 100644 --- a/openlp/core/projectors/pjlink.py +++ b/openlp/core/projectors/pjlink.py @@ -58,8 +58,8 @@ from openlp.core.projectors.constants import CONNECTION_ERRORS, PJLINK_CLASS, PJ PJLINK_ERST_DATA, PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PREFIX, PJLINK_PORT, PJLINK_POWR_STATUS, \ PJLINK_SUFFIX, PJLINK_VALID_CMD, PROJECTOR_STATE, STATUS_CODE, STATUS_MSG, QSOCKET_STATE, \ E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, \ - E_OK, E_SOCKET_TIMEOUT, \ - S_CONNECTED, S_CONNECTING, S_INFO, S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_STATUS + E_SOCKET_TIMEOUT, \ + S_CONNECTED, S_CONNECTING, S_NOT_CONNECTED, S_OFF, S_OK, S_ON log = logging.getLogger(__name__) log.debug('pjlink loaded') @@ -121,8 +121,6 @@ class PJLinkCommands(object): 'LAMP': self.process_lamp, 'NAME': self.process_name, 'PJLINK': self.process_pjlink, - # TODO: Part of check_login refactor - remove when done - # 'PJLINK': self.check_login, 'POWR': self.process_powr, 'SNUM': self.process_snum, 'SVER': self.process_sver, @@ -135,7 +133,7 @@ class PJLinkCommands(object): Initialize instance variables. Also used to reset projector-specific information to default. """ conn_state = STATUS_CODE[QSOCKET_STATE[self.state()]] - log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip, + log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.entry.name, state=conn_state)) self.fan = None # ERST self.filter_time = None # FILT @@ -147,7 +145,6 @@ class PJLinkCommands(object): self.model_lamp = None # RLMP self.mute = None # AVMT self.other_info = None # INFO - self.pjlink_class = PJLINK_CLASS # Default class self.pjlink_name = None # NAME self.power = S_OFF # POWR self.serial_no = None # SNUM @@ -158,11 +155,11 @@ class PJLinkCommands(object): self.source_available = None # INST self.source = None # INPT # These should be part of PJLink() class, but set here for convenience - if hasattr(self, 'timer'): - log.debug('({ip}): Calling timer.stop()'.format(ip=self.ip)) - self.timer.stop() + if hasattr(self, 'poll_timer'): + log.debug('({ip}): Calling poll_timer.stop()'.format(ip=self.entry.name)) + self.poll_timer.stop() if hasattr(self, 'socket_timer'): - log.debug('({ip}): Calling socket_timer.stop()'.format(ip=self.ip)) + log.debug('({ip}): Calling socket_timer.stop()'.format(ip=self.entry.name)) self.socket_timer.stop() self.send_busy = False self.send_queue = [] @@ -175,7 +172,7 @@ class PJLinkCommands(object): :param cmd: Command to process :param data: Data being processed """ - log.debug('({ip}) Processing command "{cmd}" with data "{data}"'.format(ip=self.ip, + log.debug('({ip}) Processing command "{cmd}" with data "{data}"'.format(ip=self.entry.name, cmd=cmd, data=data)) # cmd should already be in uppercase, but data may be in mixed-case. @@ -183,15 +180,15 @@ class PJLinkCommands(object): _data = data.upper() # Check if we have a future command not available yet if cmd not in self.pjlink_functions: - log.warning('({ip}) Unable to process command="{cmd}" (Future option?)'.format(ip=self.ip, cmd=cmd)) + log.warning('({ip}) Unable to process command="{cmd}" (Future option?)'.format(ip=self.entry.name, cmd=cmd)) return elif _data == 'OK': - log.debug('({ip}) Command "{cmd}" returned OK'.format(ip=self.ip, cmd=cmd)) + log.debug('({ip}) Command "{cmd}" returned OK'.format(ip=self.entry.name, cmd=cmd)) # A command returned successfully, so do a query on command to verify status return self.send_command(cmd=cmd) elif _data in PJLINK_ERRORS: # Oops - projector error - log.error('({ip}) {cmd}: {err}'.format(ip=self.ip, + log.error('({ip}) {cmd}: {err}'.format(ip=self.entry.name, cmd=cmd, err=STATUS_MSG[PJLINK_ERRORS[_data]])) if PJLINK_ERRORS[_data] == E_AUTHENTICATION: @@ -199,7 +196,7 @@ class PJLinkCommands(object): self.projectorAuthentication.emit(self.name) return self.change_status(status=E_AUTHENTICATION) # Command checks already passed - log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd)) + log.debug('({ip}) Calling function for {cmd}'.format(ip=self.entry.name, cmd=cmd)) self.pjlink_functions[cmd](data=data) def process_avmt(self, data): @@ -219,7 +216,7 @@ class PJLinkCommands(object): '31': {'shutter': True, 'mute': True} } if data not in settings: - log.warning('({ip}) Invalid shutter response: {data}'.format(ip=self.ip, data=data)) + log.warning('({ip}) Invalid shutter response: {data}'.format(ip=self.entry.name, data=data)) return shutter = settings[data]['shutter'] mute = settings[data]['mute'] @@ -243,7 +240,7 @@ class PJLinkCommands(object): # : Received: '%1CLSS=Class 1' (Optoma) # : Received: '%1CLSS=Version1' (BenQ) if len(data) > 1: - log.warning('({ip}) Non-standard CLSS reply: "{data}"'.format(ip=self.ip, data=data)) + log.warning('({ip}) Non-standard CLSS reply: "{data}"'.format(ip=self.entry.name, data=data)) # Due to stupid projectors not following standards (Optoma, BenQ comes to mind), # AND the different responses that can be received, the semi-permanent way to # fix the class reply is to just remove all non-digit characters. @@ -251,17 +248,23 @@ class PJLinkCommands(object): clss = re.findall('\d', data)[0] # Should only be the first match except IndexError: log.error('({ip}) No numbers found in class version reply "{data}" - ' - 'defaulting to class "1"'.format(ip=self.ip, data=data)) + 'defaulting to class "1"'.format(ip=self.entry.name, data=data)) clss = '1' elif not data.isdigit(): log.error('({ip}) NAN CLSS version reply "{data}" - ' - 'defaulting to class "1"'.format(ip=self.ip, data=data)) + 'defaulting to class "1"'.format(ip=self.entry.name, data=data)) clss = '1' else: clss = data self.pjlink_class = clss - log.debug('({ip}) Setting pjlink_class for this projector to "{data}"'.format(ip=self.ip, + log.debug('({ip}) Setting pjlink_class for this projector to "{data}"'.format(ip=self.entry.name, data=self.pjlink_class)) + # Since we call this one on first connect, setup polling from here + if not self.no_poll: + log.debug('({ip}) process_pjlink(): Starting timer'.format(ip=self.entry.name)) + self.poll_timer.setInterval(1000) # Set 1 second for initial information + self.poll_timer.start() + return def process_erst(self, data): @@ -273,15 +276,15 @@ class PJLinkCommands(object): """ if len(data) != PJLINK_ERST_DATA['DATA_LENGTH']: count = PJLINK_ERST_DATA['DATA_LENGTH'] - log.warning('{ip}) Invalid error status response "{data}": length != {count}'.format(ip=self.ip, - data=data, - count=count)) + log.warning('({ip}) Invalid error status response "{data}": length != {count}'.format(ip=self.entry.name, + data=data, + count=count)) return try: datacheck = int(data) except ValueError: # Bad data - ignore - log.warning('({ip}) Invalid error status response "{data}"'.format(ip=self.ip, data=data)) + log.warning('({ip}) Invalid error status response "{data}"'.format(ip=self.entry.name, data=data)) return if datacheck == 0: self.projector_errors = None @@ -323,7 +326,8 @@ class PJLinkCommands(object): :param data: Projector manufacturer """ self.manufacturer = data - log.debug('({ip}) Setting projector manufacturer data to "{data}"'.format(ip=self.ip, data=self.manufacturer)) + log.debug('({ip}) Setting projector manufacturer data to "{data}"'.format(ip=self.entry.name, + data=self.manufacturer)) return def process_inf2(self, data): @@ -334,7 +338,7 @@ class PJLinkCommands(object): :param data: Model name """ self.model = data - log.debug('({ip}) Setting projector model to "{data}"'.format(ip=self.ip, data=self.model)) + log.debug('({ip}) Setting projector model to "{data}"'.format(ip=self.entry.name, data=self.model)) return def process_info(self, data): @@ -345,7 +349,7 @@ class PJLinkCommands(object): :param data: Projector other info """ self.other_info = data - log.debug('({ip}) Setting projector other_info to "{data}"'.format(ip=self.ip, data=self.other_info)) + log.debug('({ip}) Setting projector other_info to "{data}"'.format(ip=self.entry.name, data=self.other_info)) return def process_inpt(self, data): @@ -359,14 +363,15 @@ class PJLinkCommands(object): if self.source_available is not None: # We have available inputs, so verify it's in the list if data not in self.source_available: - log.warn('({ip}) Input source not listed in available sources - ignoring'.format(ip=self.ip)) + log.warn('({ip}) Input source not listed in available sources - ignoring'.format(ip=self.entry.name)) return elif data not in PJLINK_DEFAULT_CODES: # Hmm - no sources available yet, so check with PJLink defaults - log.warn('({ip}) Input source not listed as a PJLink available source - ignoring'.format(ip=self.ip)) + log.warn('({ip}) Input source not listed as a PJLink available source ' + '- ignoring'.format(ip=self.entry.name)) return self.source = data - log.debug('({ip}) Setting data source to "{data}"'.format(ip=self.ip, data=self.source)) + log.debug('({ip}) Setting data source to "{data}"'.format(ip=self.entry.name, data=self.source)) return def process_inst(self, data): @@ -382,7 +387,7 @@ class PJLinkCommands(object): sources.append(source) sources.sort() self.source_available = sources - log.debug('({ip}) Setting projector sources_available to "{data}"'.format(ip=self.ip, + log.debug('({ip}) Setting projector sources_available to "{data}"'.format(ip=self.entry.name, data=self.source_available)) return @@ -404,7 +409,7 @@ class PJLinkCommands(object): fill = {'Hours': int(lamp_list[0]), 'On': False if lamp_list[1] == '0' else True} except ValueError: # In case of invalid entry - log.warning('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.ip, data=data)) + log.warning('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.entry.name, data=data)) return lamps.append(fill) lamp_list.pop(0) # Remove lamp hours @@ -420,7 +425,7 @@ class PJLinkCommands(object): :param data: Projector name """ self.pjlink_name = data - log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.ip, data=self.pjlink_name)) + log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.entry.name, data=self.pjlink_name)) return def process_pjlink(self, data): @@ -429,42 +434,38 @@ class PJLinkCommands(object): :param data: Initial packet with authentication scheme """ - log.debug('({ip}) Processing PJLINK command'.format(ip=self.ip)) + log.debug('({ip}) Processing PJLINK command'.format(ip=self.entry.name)) chk = data.split(' ') if len(chk[0]) != 1: # Invalid - after splitting, first field should be 1 character, either '0' or '1' only - log.error('({ip}) Invalid initial authentication scheme - aborting'.format(ip=self.ip)) + log.error('({ip}) Invalid initial authentication scheme - aborting'.format(ip=self.entry.name)) return self.disconnect_from_host() elif chk[0] == '0': # Normal connection no authentication if len(chk) > 1: # Invalid data - there should be nothing after a normal authentication scheme - log.error('({ip}) Normal connection with extra information - aborting'.format(ip=self.ip)) + log.error('({ip}) Normal connection with extra information - aborting'.format(ip=self.entry.name)) return self.disconnect_from_host() elif self.pin: - log.error('({ip}) Normal connection but PIN set - aborting'.format(ip=self.ip)) + log.error('({ip}) Normal connection but PIN set - aborting'.format(ip=self.entry.name)) return self.disconnect_from_host() else: data_hash = None elif chk[0] == '1': if len(chk) < 2: # Not enough information for authenticated connection - log.error('({ip}) Authenticated connection but not enough info - aborting'.format(ip=self.ip)) + log.error('({ip}) Authenticated connection but not enough info - aborting'.format(ip=self.entry.name)) return self.disconnect_from_host() elif not self.pin: - log.error('({ip}) Authenticate connection but no PIN - aborting'.format(ip=self.ip)) + log.error('({ip}) Authenticate connection but no PIN - aborting'.format(ip=self.entry.name)) return self.disconnect_from_host() else: data_hash = str(qmd5_hash(salt=chk[1].encode('utf-8'), data=self.pin.encode('utf-8')), encoding='ascii') # Passed basic checks, so start connection self.readyRead.connect(self.get_socket) - if not self.no_poll: - log.debug('({ip}) process_pjlink(): Starting timer'.format(ip=self.ip)) - self.timer.setInterval(2000) # Set 2 seconds for initial information - self.timer.start() self.change_status(S_CONNECTED) - log.debug('({ip}) process_pjlink(): Sending "CLSS" initial command'.format(ip=self.ip)) + log.debug('({ip}) process_pjlink(): Sending "CLSS" initial command'.format(ip=self.entry.name)) # Since this is an initial connection, make it a priority just in case return self.send_command(cmd="CLSS", salt=data_hash, priority=True) @@ -475,7 +476,7 @@ class PJLinkCommands(object): :param data: Power status """ - log.debug('({ip}: Processing POWR command'.format(ip=self.ip)) + log.debug('({ip}: Processing POWR command'.format(ip=self.entry.name)) if data in PJLINK_POWR_STATUS: power = PJLINK_POWR_STATUS[data] update_icons = self.power != power @@ -488,7 +489,7 @@ class PJLinkCommands(object): self.send_command('INST') else: # Log unknown status response - log.warning('({ip}) Unknown power response: "{data}"'.format(ip=self.ip, data=data)) + log.warning('({ip}) Unknown power response: "{data}"'.format(ip=self.entry.name, data=data)) return def process_rfil(self, data): @@ -498,9 +499,9 @@ class PJLinkCommands(object): if self.model_filter is None: self.model_filter = data else: - log.warning('({ip}) Filter model already set'.format(ip=self.ip)) - log.warning('({ip}) Saved model: "{old}"'.format(ip=self.ip, old=self.model_filter)) - log.warning('({ip}) New model: "{new}"'.format(ip=self.ip, new=data)) + log.warning('({ip}) Filter model already set'.format(ip=self.entry.name)) + log.warning('({ip}) Saved model: "{old}"'.format(ip=self.entry.name, old=self.model_filter)) + log.warning('({ip}) New model: "{new}"'.format(ip=self.entry.name, new=data)) def process_rlmp(self, data): """ @@ -509,9 +510,9 @@ class PJLinkCommands(object): if self.model_lamp is None: self.model_lamp = data else: - log.warning('({ip}) Lamp model already set'.format(ip=self.ip)) - log.warning('({ip}) Saved lamp: "{old}"'.format(ip=self.ip, old=self.model_lamp)) - log.warning('({ip}) New lamp: "{new}"'.format(ip=self.ip, new=data)) + log.warning('({ip}) Lamp model already set'.format(ip=self.entry.name)) + log.warning('({ip}) Saved lamp: "{old}"'.format(ip=self.entry.name, old=self.model_lamp)) + log.warning('({ip}) New lamp: "{new}"'.format(ip=self.entry.name, new=data)) def process_snum(self, data): """ @@ -520,16 +521,17 @@ class PJLinkCommands(object): :param data: Serial number from projector. """ if self.serial_no is None: - log.debug('({ip}) Setting projector serial number to "{data}"'.format(ip=self.ip, data=data)) + log.debug('({ip}) Setting projector serial number to "{data}"'.format(ip=self.entry.name, data=data)) self.serial_no = data self.db_update = False else: # Compare serial numbers and see if we got the same projector if self.serial_no != data: - log.warning('({ip}) Projector serial number does not match saved serial number'.format(ip=self.ip)) - log.warning('({ip}) Saved: "{old}"'.format(ip=self.ip, old=self.serial_no)) - log.warning('({ip}) Received: "{new}"'.format(ip=self.ip, new=data)) - log.warning('({ip}) NOT saving serial number'.format(ip=self.ip)) + log.warning('({ip}) Projector serial number does not match saved serial ' + 'number'.format(ip=self.entry.name)) + log.warning('({ip}) Saved: "{old}"'.format(ip=self.entry.name, old=self.serial_no)) + log.warning('({ip}) Received: "{new}"'.format(ip=self.entry.name, new=data)) + log.warning('({ip}) NOT saving serial number'.format(ip=self.entry.name)) self.serial_no_received = data def process_sver(self, data): @@ -541,14 +543,14 @@ class PJLinkCommands(object): log.warning('Invalid software version - too long') return elif self.sw_version is None: - log.debug('({ip}) Setting projector software version to "{data}"'.format(ip=self.ip, data=data)) + log.debug('({ip}) Setting projector software version to "{data}"'.format(ip=self.entry.name, data=data)) else: if self.sw_version != data: log.warning('({ip}) Projector software version does not match saved ' - 'software version'.format(ip=self.ip)) - log.warning('({ip}) Saved: "{old}"'.format(ip=self.ip, old=self.sw_version)) - log.warning('({ip}) Received: "{new}"'.format(ip=self.ip, new=data)) - log.warning('({ip}) Updating software version'.format(ip=self.ip)) + 'software version'.format(ip=self.entry.name)) + log.warning('({ip}) Saved: "{old}"'.format(ip=self.entry.name, old=self.sw_version)) + log.warning('({ip}) Received: "{new}"'.format(ip=self.entry.name, new=data)) + log.warning('({ip}) Updating software version'.format(ip=self.entry.name)) self.sw_version = data self.db_update = True @@ -588,14 +590,14 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): self.notes = self.entry.notes self.pin = self.entry.pin self.port = self.entry.port + self.pjlink_class = PJLINK_CLASS if self.entry.pjlink_class is None else self.entry.pjlink_class self.db_update = False # Use to check if db needs to be updated prior to exiting # Poll time 20 seconds unless called with something else self.poll_time = 20000 if 'poll_time' not in kwargs else kwargs['poll_time'] * 1000 - # Timeout 5 seconds unless called with something else + # Socket timeout (in case of brain-dead projectors) 5 seconds unless called with something else self.socket_timeout = 5000 if 'socket_timeout' not in kwargs else kwargs['socket_timeout'] * 1000 # In case we're called from somewhere that only wants information self.no_poll = 'no_poll' in kwargs - self.i_am_running = False self.status_connect = S_NOT_CONNECTED self.last_command = '' self.projector_status = S_NOT_CONNECTED @@ -605,61 +607,29 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): self.max_size = PJLINK_MAX_PACKET + 2 self.setReadBufferSize(self.max_size) self.reset_information() - # Set from ProjectorManager.add_projector() - self.widget = None # QListBox entry - self.timer = None # Timer that calls the poll_loop self.send_queue = [] self.priority_queue = [] self.send_busy = False - # Socket timer for some possible brain-dead projectors or network cable pulled - self.socket_timer = None - - def thread_started(self): - """ - Connects signals to methods when thread is started. - """ - log.debug('({ip}) Thread starting'.format(ip=self.ip)) - self.i_am_running = True + # Poll timer for status updates + self.poll_timer = QtCore.QTimer(self) # Timer that calls the poll_loop + self.poll_timer.setInterval(self.poll_time) + self.poll_timer.timeout.connect(self.poll_loop) + # Socket timer for some possible brain-dead projectors or network issues + self.socket_timer = QtCore.QTimer(self) + self.socket_timer.setInterval(self.socket_timeout) + self.socket_timer.timeout.connect(self.socket_abort) + # Socket status signals self.connected.connect(self.check_login) self.disconnected.connect(self.disconnect_from_host) self.error.connect(self.get_error) self.projectorReceivedData.connect(self._send_command) - def thread_stopped(self): - """ - Cleanups when thread is stopped. - """ - log.debug('({ip}) Thread stopped'.format(ip=self.ip)) - try: - self.connected.disconnect(self.check_login) - except TypeError: - pass - try: - self.disconnected.disconnect(self.disconnect_from_host) - except TypeError: - pass - try: - self.error.disconnect(self.get_error) - except TypeError: - pass - try: - self.projectorReceivedData.disconnect(self._send_command) - except TypeError: - pass - try: - self.readyRead.disconnect(self.get_socket) # Set in process_pjlink - except TypeError: - pass - self.disconnect_from_host() - self.deleteLater() - self.i_am_running = False - def socket_abort(self): """ Aborts connection and closes socket in case of brain-dead projectors. Should normally be called by socket_timer(). """ - log.debug('({ip}) socket_abort() - Killing connection'.format(ip=self.ip)) + log.debug('({ip}) socket_abort() - Killing connection'.format(ip=self.entry.name)) self.disconnect_from_host(abort=True) def poll_loop(self): @@ -668,15 +638,11 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): Normally called by timer(). """ if QSOCKET_STATE[self.state()] != S_CONNECTED: - log.warning('({ip}) poll_loop(): Not connected - returning'.format(ip=self.ip)) + log.warning('({ip}) poll_loop(): Not connected - returning'.format(ip=self.entry.name)) + # Stop timer just in case it's missed elsewhere + self.poll_timer.stop() return - log.debug('({ip}) poll_loop(): Updating projector status'.format(ip=self.ip)) - # Reset timer in case we were called from a set command - if self.timer.interval() < self.poll_time: - # Reset timer to 5 seconds - self.timer.setInterval(self.poll_time) - # Restart timer - self.timer.start() + log.debug('({ip}) poll_loop(): Updating projector status'.format(ip=self.entry.name)) # The following commands do not change, so only check them once # Call them first in case other functions rely on something here if self.power == S_ON and self.source_available is None: @@ -705,6 +671,8 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): check_list.extend(['FILT', 'FREZ']) for command in check_list: self.send_command(command) + # Reset the poll_timer for normal operations in case of initial connection + self.poll_timer.setInterval(self.poll_time) def _get_status(self, status): """ @@ -732,11 +700,11 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): """ if status in STATUS_CODE: log.debug('({ip}) Changing status to {status} ' - '"{msg}"'.format(ip=self.ip, + '"{msg}"'.format(ip=self.entry.name, status=STATUS_CODE[status], msg=msg if msg is not None else STATUS_MSG[status])) else: - log.warning('({ip}) Unknown status change code: {code}'.format(ip=self.ip, + log.warning('({ip}) Unknown status change code: {code}'.format(ip=self.entry.name, code=status)) return if status in CONNECTION_ERRORS: @@ -752,15 +720,15 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): # These log entries are for troubleshooting only (status_code, status_message) = self._get_status(self.status_connect) - log.debug('({ip}) status_connect: {code}: "{message}"'.format(ip=self.ip, + log.debug('({ip}) status_connect: {code}: "{message}"'.format(ip=self.entry.name, code=status_code, message=status_message if msg is None else msg)) (status_code, status_message) = self._get_status(self.projector_status) - log.debug('({ip}) projector_status: {code}: "{message}"'.format(ip=self.ip, + log.debug('({ip}) projector_status: {code}: "{message}"'.format(ip=self.entry.name, code=status_code, message=status_message if msg is None else msg)) (status_code, status_message) = self._get_status(self.error_status) - log.debug('({ip}) error_status: {code}: "{message}"'.format(ip=self.ip, + log.debug('({ip}) error_status: {code}: "{message}"'.format(ip=self.entry.name, code=status_code, message=status_message if msg is None else msg)) @@ -782,27 +750,27 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): :param data: Optional data if called from another routine """ - log.debug('({ip}) check_login(data="{data}")'.format(ip=self.ip, data=data)) + log.debug('({ip}) check_login(data="{data}")'.format(ip=self.entry.name, data=data)) if data is None: # Reconnected setup? if not self.waitForReadyRead(2000): # Possible timeout issue - log.error('({ip}) Socket timeout waiting for login'.format(ip=self.ip)) + log.error('({ip}) Socket timeout waiting for login'.format(ip=self.entry.name)) self.change_status(E_SOCKET_TIMEOUT) return read = self.readLine(self.max_size) self.readLine(self.max_size) # Clean out any trailing whitespace if read is None: - log.warning('({ip}) read is None - socket error?'.format(ip=self.ip)) + log.warning('({ip}) read is None - socket error?'.format(ip=self.entry.name)) return elif len(read) < 8: - log.warning('({ip}) Not enough data read - skipping'.format(ip=self.ip)) + log.warning('({ip}) Not enough data read - skipping'.format(ip=self.entry.name)) return data = decode(read, 'utf-8') # Possibility of extraneous data on input when reading. # Clean out extraneous characters in buffer. self.read(1024) - log.debug('({ip}) check_login() read "{data}"'.format(ip=self.ip, data=data.strip())) + log.debug('({ip}) check_login() read "{data}"'.format(ip=self.entry.name, data=data.strip())) # At this point, we should only have the initial login prompt with # possible authentication # PJLink initial login will be: @@ -810,76 +778,26 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): # 'PJLink 1 XXXXXX' Authenticated login - extra processing required. if not data.startswith('PJLINK'): # Invalid initial packet - close socket - log.error('({ip}) Invalid initial packet received - closing socket'.format(ip=self.ip)) + log.error('({ip}) Invalid initial packet received - closing socket'.format(ip=self.entry.name)) return self.disconnect_from_host() # Convert the initial login prompt with the expected PJLink normal command format for processing - log.debug('({ip}) check_login(): Formatting initial connection prompt to PJLink packet'.format(ip=self.ip)) + log.debug('({ip}) check_login(): Formatting initial connection prompt' + 'to PJLink packet'.format(ip=self.entry.name)) return self.get_data('{start}{clss}{data}'.format(start=PJLINK_PREFIX, clss='1', data=data.replace(' ', '=', 1)).encode('utf-8')) - # TODO: The below is replaced by process_pjlink() - remove when working properly - """ - if '=' in data: - # Processing a login reply - data_check = data.strip().split('=') - else: - # Process initial connection - data_check = data.strip().split(' ') - log.debug('({ip}) data_check="{data}"'.format(ip=self.ip, data=data_check)) - # Check for projector reporting an error - if data_check[1].upper() == 'ERRA': - # Authentication error - self.disconnect_from_host() - self.change_status(E_AUTHENTICATION) - log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.ip)) - return - elif (data_check[1] == '0') and (self.pin): - # 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.ip)) - self.projectorNoAuthentication.emit(self.name) - return - elif data_check[1] == '1': - # Authenticated login with salt - if not self.pin: - log.warning('({ip}) Authenticated connection but no pin set'.format(ip=self.ip)) - self.disconnect_from_host() - self.change_status(E_AUTHENTICATION) - log.debug('({ip}) Emitting projectorAuthentication() signal'.format(ip=self.ip)) - self.projectorAuthentication.emit(self.ip) - 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: - data_hash = None - # We're connected at this point, so go ahead and setup regular I/O - self.readyRead.connect(self.get_socket) - self.projectorReceivedData.connect(self._send_command) - # Initial data we should know about - 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)) - self.timer.setInterval(2000) # Set 2 seconds for initial information - self.timer.start() - """ def _trash_buffer(self, msg=None): """ Clean out extraneous stuff in the buffer. """ - log.warning('({ip}) {message}'.format(ip=self.ip, message='Invalid packet' if msg is None else msg)) + log.warning('({ip}) {message}'.format(ip=self.entry.name, message='Invalid packet' if msg is None else msg)) self.send_busy = False trash_count = 0 while self.bytesAvailable() > 0: trash = self.read(self.max_size) trash_count += len(trash) - log.debug('({ip}) Finished cleaning buffer - {count} bytes dropped'.format(ip=self.ip, + log.debug('({ip}) Finished cleaning buffer - {count} bytes dropped'.format(ip=self.entry.name, count=trash_count)) return @@ -891,9 +809,9 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): :param data: Data to process. buffer must be formatted as a proper PJLink packet. :param ip: Destination IP for buffer. """ - log.debug('({ip}) get_buffer(data="{buff}" ip="{ip_in}"'.format(ip=self.ip, buff=data, ip_in=ip)) + log.debug('({ip}) get_buffer(data="{buff}" ip="{ip_in}"'.format(ip=self.entry.name, buff=data, ip_in=ip)) if ip is None: - log.debug("({ip}) get_buffer() Don't know who data is for - exiting".format(ip=self.ip)) + log.debug("({ip}) get_buffer() Don't know who data is for - exiting".format(ip=self.entry.name)) return return self.get_data(buff=data, ip=ip) @@ -902,17 +820,17 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): """ Get data from TCP socket. """ - log.debug('({ip}) get_socket(): Reading data'.format(ip=self.ip)) + log.debug('({ip}) get_socket(): Reading data'.format(ip=self.entry.name)) if QSOCKET_STATE[self.state()] != S_CONNECTED: - log.debug('({ip}) get_socket(): Not connected - returning'.format(ip=self.ip)) + log.debug('({ip}) get_socket(): Not connected - returning'.format(ip=self.entry.name)) self.send_busy = False return # Although we have a packet length limit, go ahead and use a larger buffer read = self.readLine(1024) - log.debug('({ip}) get_socket(): "{buff}"'.format(ip=self.ip, buff=read)) + log.debug('({ip}) get_socket(): "{buff}"'.format(ip=self.entry.name, buff=read)) if read == -1: # No data available - log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.ip)) + log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.entry.name)) return self.receive_data_signal() self.socket_timer.stop() return self.get_data(buff=read, ip=self.ip) @@ -928,7 +846,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): # set to default here if ip is None: ip = self.ip - log.debug('({ip}) get_data(ip="{ip_in}" buffer="{buff}"'.format(ip=self.ip, ip_in=ip, buff=buff)) + log.debug('({ip}) get_data(ip="{ip_in}" buffer="{buff}"'.format(ip=self.entry.name, ip_in=ip, buff=buff)) # NOTE: Class2 has changed to some values being UTF-8 data_in = decode(buff, 'utf-8') data = data_in.strip() @@ -945,7 +863,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): elif '=' not in data: self._trash_buffer(msg='get_data(): Invalid reply - Does not have "="') return self.receive_data_signal() - log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.ip, data=data)) + log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.entry.name, data=data)) header, data = data.split('=') # At this point, the header should contain: # "PVCCCC" @@ -957,16 +875,17 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): version, cmd = header[1], header[2:].upper() except ValueError as e: self.change_status(E_INVALID_DATA) - log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in)) + log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.entry.name, data=data_in)) self._trash_buffer('get_data(): Expected header + command + data') return self.receive_data_signal() if cmd not in PJLINK_VALID_CMD: - log.warning('({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.entry.name, + data=cmd)) self._trash_buffer(msg='get_data(): Unknown command "{data}"'.format(data=cmd)) return self.receive_data_signal() if int(self.pjlink_class) < int(version): log.warning('({ip}) get_data(): Projector returned class reply higher ' - 'than projector stated class'.format(ip=self.ip)) + 'than projector stated class'.format(ip=self.entry.name)) self.process_command(cmd, data) return self.receive_data_signal() @@ -978,12 +897,14 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): :param err: Error code """ - log.debug('({ip}) get_error(err={error}): {data}'.format(ip=self.ip, error=err, data=self.errorString())) + log.debug('({ip}) get_error(err={error}): {data}'.format(ip=self.entry.name, + error=err, + data=self.errorString())) if err <= 18: # QSocket errors. Redefined in projector.constants so we don't mistake # them for system errors check = err + E_CONNECTION_REFUSED - self.timer.stop() + self.poll_timer.stop() else: check = err if check < E_GENERAL: @@ -1007,12 +928,12 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): :param priority: Option to send packet now rather than queue it up """ if QSOCKET_STATE[self.state()] != S_CONNECTED: - log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.ip)) + log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.entry.name)) return self.reset_information() if cmd not in PJLINK_VALID_CMD: - log.error('({ip}) send_command(): Invalid command requested - ignoring.'.format(ip=self.ip)) + log.error('({ip}) send_command(): Invalid command requested - ignoring.'.format(ip=self.entry.name)) return - log.debug('({ip}) send_command(): Building cmd="{command}" opts="{data}"{salt}'.format(ip=self.ip, + log.debug('({ip}) send_command(): Building cmd="{command}" opts="{data}"{salt}'.format(ip=self.entry.name, command=cmd, data=opts, salt='' if salt is None @@ -1025,7 +946,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): header = PJLINK_HEADER.format(linkclass=cmd_ver[0]) else: # NOTE: Once we get to version 3 then think about looping - log.error('({ip}): send_command(): PJLink class check issue? Aborting'.format(ip=self.ip)) + log.error('({ip}): send_command(): PJLink class check issue? Aborting'.format(ip=self.entry.name)) return out = '{salt}{header}{command} {options}{suffix}'.format(salt="" if salt is None else salt, header=header, @@ -1033,15 +954,15 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): options=opts, suffix=PJLINK_SUFFIX) if out in self.priority_queue: - log.debug('({ip}) send_command(): Already in priority queue - skipping'.format(ip=self.ip)) + log.debug('({ip}) send_command(): Already in priority queue - skipping'.format(ip=self.entry.name)) elif out in self.send_queue: - log.debug('({ip}) send_command(): Already in normal queue - skipping'.format(ip=self.ip)) + log.debug('({ip}) send_command(): Already in normal queue - skipping'.format(ip=self.entry.name)) else: if priority: - log.debug('({ip}) send_command(): Adding to priority queue'.format(ip=self.ip)) + log.debug('({ip}) send_command(): Adding to priority queue'.format(ip=self.entry.name)) self.priority_queue.append(out) else: - log.debug('({ip}) send_command(): Adding to normal queue'.format(ip=self.ip)) + log.debug('({ip}) send_command(): Adding to normal queue'.format(ip=self.entry.name)) self.send_queue.append(out) if self.priority_queue or self.send_queue: # May be some initial connection setup so make sure we send data @@ -1056,44 +977,45 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): :param utf8: Send as UTF-8 string otherwise send as ASCII string """ # Funny looking data check, but it's a quick check for data=None - log.debug('({ip}) _send_command(data="{data}")'.format(ip=self.ip, data=data.strip() if data else data)) + log.debug('({ip}) _send_command(data="{data}")'.format(ip=self.entry.name, data=data.strip() if data else data)) conn_state = STATUS_CODE[QSOCKET_STATE[self.state()]] - log.debug('({ip}) _send_command(): Connection status: {data}'.format(ip=self.ip, + log.debug('({ip}) _send_command(): Connection status: {data}'.format(ip=self.entry.name, data=conn_state)) if QSOCKET_STATE[self.state()] != S_CONNECTED: - log.debug('({ip}) _send_command() Not connected - abort'.format(ip=self.ip)) + log.debug('({ip}) _send_command() Not connected - abort'.format(ip=self.entry.name)) self.send_busy = False return self.disconnect_from_host() if data and data not in self.priority_queue: - log.debug('({ip}) _send_command(): Priority packet - adding to priority queue'.format(ip=self.ip)) + log.debug('({ip}) _send_command(): Priority packet - adding to priority queue'.format(ip=self.entry.name)) self.priority_queue.append(data) if self.send_busy: # Still waiting for response from last command sent - log.debug('({ip}) _send_command(): Still busy, returning'.format(ip=self.ip)) - log.debug('({ip}) _send_command(): Priority queue = {data}'.format(ip=self.ip, data=self.priority_queue)) - log.debug('({ip}) _send_command(): Normal queue = {data}'.format(ip=self.ip, data=self.send_queue)) + log.debug('({ip}) _send_command(): Still busy, returning'.format(ip=self.entry.name)) + log.debug('({ip}) _send_command(): Priority queue = {data}'.format(ip=self.entry.name, + data=self.priority_queue)) + log.debug('({ip}) _send_command(): Normal queue = {data}'.format(ip=self.entry.name, data=self.send_queue)) return if len(self.priority_queue) != 0: out = self.priority_queue.pop(0) - log.debug('({ip}) _send_command(): Getting priority queued packet'.format(ip=self.ip)) + log.debug('({ip}) _send_command(): Getting priority queued packet'.format(ip=self.entry.name)) elif len(self.send_queue) != 0: out = self.send_queue.pop(0) - log.debug('({ip}) _send_command(): Getting normal queued packet'.format(ip=self.ip)) + log.debug('({ip}) _send_command(): Getting normal queued packet'.format(ip=self.entry.name)) else: # No data to send - log.debug('({ip}) _send_command(): No data to send'.format(ip=self.ip)) + log.debug('({ip}) _send_command(): No data to send'.format(ip=self.entry.name)) self.send_busy = False return self.send_busy = True - log.debug('({ip}) _send_command(): Sending "{data}"'.format(ip=self.ip, data=out.strip())) + log.debug('({ip}) _send_command(): Sending "{data}"'.format(ip=self.entry.name, data=out.strip())) self.socket_timer.start() sent = self.write(out.encode('{string_encoding}'.format(string_encoding='utf-8' if utf8 else 'ascii'))) self.waitForBytesWritten(2000) # 2 seconds should be enough if sent == -1: # Network error? - log.warning('({ip}) _send_command(): -1 received - disconnecting from host'.format(ip=self.ip)) + log.warning('({ip}) _send_command(): -1 received - disconnecting from host'.format(ip=self.entry.name)) self.change_status(E_NETWORK, translate('OpenLP.PJLink', 'Error while sending data to projector')) self.disconnect_from_host() @@ -1102,9 +1024,9 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): """ Initiate connection to projector. """ - log.debug('({ip}) connect_to_host(): Starting connection'.format(ip=self.ip)) + log.debug('({ip}) connect_to_host(): Starting connection'.format(ip=self.entry.name)) if QSOCKET_STATE[self.state()] == S_CONNECTED: - log.warning('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.ip)) + log.warning('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.entry.name)) return self.error_status = S_OK self.change_status(S_CONNECTING) @@ -1117,17 +1039,17 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): """ if abort or QSOCKET_STATE[self.state()] != S_NOT_CONNECTED: if abort: - log.warning('({ip}) disconnect_from_host(): Aborting connection'.format(ip=self.ip)) + log.warning('({ip}) disconnect_from_host(): Aborting connection'.format(ip=self.entry.name)) self.abort() else: - log.warning('({ip}) disconnect_from_host(): Not connected'.format(ip=self.ip)) + log.warning('({ip}) disconnect_from_host(): Not connected'.format(ip=self.entry.name)) self.disconnectFromHost() try: self.readyRead.disconnect(self.get_socket) except TypeError: pass log.debug('({ip}) disconnect_from_host() ' - 'Current status {data}'.format(ip=self.ip, data=self._get_status(self.status_connect)[0])) + 'Current status {data}'.format(ip=self.entry.name, data=self._get_status(self.status_connect)[0])) if abort: self.change_status(E_NOT_CONNECTED) else: @@ -1138,70 +1060,70 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): """ Send command to retrieve shutter status. """ - log.debug('({ip}) Sending AVMT command'.format(ip=self.ip)) + log.debug('({ip}) Sending AVMT command'.format(ip=self.entry.name)) return self.send_command(cmd='AVMT') def get_available_inputs(self): """ Send command to retrieve available source inputs. """ - log.debug('({ip}) Sending INST command'.format(ip=self.ip)) + log.debug('({ip}) Sending INST command'.format(ip=self.entry.name)) return self.send_command(cmd='INST') def get_error_status(self): """ Send command to retrieve currently known errors. """ - log.debug('({ip}) Sending ERST command'.format(ip=self.ip)) + log.debug('({ip}) Sending ERST command'.format(ip=self.entry.name)) return self.send_command(cmd='ERST') def get_input_source(self): """ Send command to retrieve currently selected source input. """ - log.debug('({ip}) Sending INPT command'.format(ip=self.ip)) + log.debug('({ip}) Sending INPT command'.format(ip=self.entry.name)) return self.send_command(cmd='INPT') def get_lamp_status(self): """ Send command to return the lap status. """ - log.debug('({ip}) Sending LAMP command'.format(ip=self.ip)) + log.debug('({ip}) Sending LAMP command'.format(ip=self.entry.name)) return self.send_command(cmd='LAMP') def get_manufacturer(self): """ Send command to retrieve manufacturer name. """ - log.debug('({ip}) Sending INF1 command'.format(ip=self.ip)) + log.debug('({ip}) Sending INF1 command'.format(ip=self.entry.name)) return self.send_command(cmd='INF1') def get_model(self): """ Send command to retrieve the model name. """ - log.debug('({ip}) Sending INF2 command'.format(ip=self.ip)) + log.debug('({ip}) Sending INF2 command'.format(ip=self.entry.name)) return self.send_command(cmd='INF2') def get_name(self): """ Send command to retrieve name as set by end-user (if set). """ - log.debug('({ip}) Sending NAME command'.format(ip=self.ip)) + log.debug('({ip}) Sending NAME command'.format(ip=self.entry.name)) return self.send_command(cmd='NAME') def get_other_info(self): """ Send command to retrieve extra info set by manufacturer. """ - log.debug('({ip}) Sending INFO command'.format(ip=self.ip)) + log.debug('({ip}) Sending INFO command'.format(ip=self.entry.name)) return self.send_command(cmd='INFO') def get_power_status(self): """ Send command to retrieve power status. """ - log.debug('({ip}) Sending POWR command'.format(ip=self.ip)) + log.debug('({ip}) Sending POWR command'.format(ip=self.entry.name)) return self.send_command(cmd='POWR') def set_input_source(self, src=None): @@ -1211,12 +1133,12 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): :param src: Video source to select in projector """ - log.debug('({ip}) set_input_source(src="{data}")'.format(ip=self.ip, data=src)) + log.debug('({ip}) set_input_source(src="{data}")'.format(ip=self.entry.name, data=src)) if self.source_available is None: return elif src not in self.source_available: return - log.debug('({ip}) Setting input source to "{data}"'.format(ip=self.ip, data=src)) + log.debug('({ip}) Setting input source to "{data}"'.format(ip=self.entry.name, data=src)) self.send_command(cmd='INPT', opts=src, priority=True) self.poll_loop() @@ -1224,7 +1146,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): """ Send command to turn power to on. """ - log.debug('({ip}) Setting POWR to 1 (on)'.format(ip=self.ip)) + log.debug('({ip}) Setting POWR to 1 (on)'.format(ip=self.entry.name)) self.send_command(cmd='POWR', opts='1', priority=True) self.poll_loop() @@ -1232,7 +1154,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): """ Send command to turn power to standby. """ - log.debug('({ip}) Setting POWR to 0 (standby)'.format(ip=self.ip)) + log.debug('({ip}) Setting POWR to 0 (standby)'.format(ip=self.entry.name)) self.send_command(cmd='POWR', opts='0', priority=True) self.poll_loop() @@ -1240,7 +1162,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): """ Send command to set shutter to closed position. """ - log.debug('({ip}) Setting AVMT to 11 (shutter closed)'.format(ip=self.ip)) + log.debug('({ip}) Setting AVMT to 11 (shutter closed)'.format(ip=self.entry.name)) self.send_command(cmd='AVMT', opts='11', priority=True) self.poll_loop() @@ -1248,7 +1170,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands): """ Send command to set shutter to open position. """ - log.debug('({ip}) Setting AVMT to "10" (shutter open)'.format(ip=self.ip)) + log.debug('({ip}) Setting AVMT to "10" (shutter open)'.format(ip=self.entry.name)) self.send_command(cmd='AVMT', opts='10', priority=True) self.poll_loop() self.projectorUpdateIcons.emit() diff --git a/openlp/core/projectors/upgrade.py b/openlp/core/projectors/upgrade.py index 1bcd756dc..b02667638 100644 --- a/openlp/core/projectors/upgrade.py +++ b/openlp/core/projectors/upgrade.py @@ -33,7 +33,7 @@ from openlp.core.lib.db import get_upgrade_op log = logging.getLogger(__name__) # Initial projector DB was unversioned -__version__ = 2 +__version__ = 3 log.debug('Projector DB upgrade module loading') @@ -71,3 +71,23 @@ def upgrade_2(session, metadata): new_op.add_column('projector', Column('model_filter', types.String(30), server_default=null())) new_op.add_column('projector', Column('model_lamp', types.String(30), server_default=null())) log.debug('{status} projector DB upgrade to version 2'.format(status='Updated' if upgrade_db else 'Skipping')) + + +def upgrade_3(session, metadata): + """ + Version 3 upgrade. + + Update Projector() table to inlcude PJLink class as part of record. + + pjlink_version: Column(String(1)) + + :param session: DB Session instance + :param metadata: Metadata of current DB + """ + log.debug('Checking projector DB upgrade to version 3') + projector_table = Table('projector', metadata, autoload=True) + upgrade_db = 'pjlink_class' not in [col.name for col in projector_table.c.values()] + if upgrade_db: + new_op = get_upgrade_op(session) + new_op.add_column('projector', Column('pjlink_class', types.String(1), server_default=null())) + log.debug('{status} projector DB upgrade to version 3'.format(status='Updated' if upgrade_db else 'Skipping')) diff --git a/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py b/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py index 8cceb6ef6..0f5307b53 100644 --- a/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py +++ b/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py @@ -24,57 +24,32 @@ Package to test the openlp.core.projectors.pjlink base package. """ from unittest import TestCase -from openlp.core.projectors.db import Projector -from openlp.core.projectors.pjlink import PJLink -from tests.resources.projector.data import TEST1_DATA +# from openlp.core.projectors.db import Projector +# from openlp.core.projectors.pjlink import PJLink +# from tests.resources.projector.data import TEST1_DATA class TestPJLinkBugs(TestCase): """ Tests for the PJLink module bugfixes """ - def setUp(self): - ''' - Initialization - ''' - self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) - - def tearDown(self): - ''' - Cleanups - ''' - self.pjlink_test = None - - def test_bug_1550891_process_clss_nonstandard_reply_1(self): + def bug_1550891_process_clss_nonstandard_reply_1(self): """ Bugfix 1550891: CLSS request returns non-standard reply with Optoma/Viewsonic projector """ - # GIVEN: Test object - pjlink = self.pjlink_test + # Test now part of test_projector_pjlink_commands_01 + # Keeping here for bug reference + pass - # WHEN: Process non-standard reply - pjlink.process_clss('Class 1') - - # THEN: Projector class should be set with proper value - self.assertEqual(pjlink.pjlink_class, '1', - 'Non-standard class reply should have set class=1') - - def test_bug_1550891_process_clss_nonstandard_reply_2(self): + def bug_1550891_process_clss_nonstandard_reply_2(self): """ Bugfix 1550891: CLSS request returns non-standard reply with BenQ projector """ - # GIVEN: Test object - pjlink = self.pjlink_test + # Test now part of test_projector_pjlink_commands_01 + # Keeping here for bug reference + pass - # WHEN: Process non-standard reply - pjlink.process_clss('Version2') - - # THEN: Projector class should be set with proper value - # NOTE: At this time BenQ is Class 1, but we're trying a different value to verify - self.assertEqual(pjlink.pjlink_class, '2', - 'Non-standard class reply should have set class=2') - - def test_bug_1593882_no_pin_authenticated_connection(self): + def bug_1593882_no_pin_authenticated_connection(self): """ Test bug 1593882 no pin and authenticated request exception """ @@ -82,7 +57,7 @@ class TestPJLinkBugs(TestCase): # Keeping here for bug reference pass - def test_bug_1593883_pjlink_authentication(self): + def bug_1593883_pjlink_authentication(self): """ Test bugfix 1593883 pjlink authentication and ticket 92187 """ @@ -90,17 +65,10 @@ class TestPJLinkBugs(TestCase): # Keeping here for bug reference pass - def test_bug_1734275_process_lamp_nonstandard_reply(self): + def bug_1734275_process_lamp_nonstandard_reply(self): """ - Test bugfix 17342785 non-standard LAMP response + Test bugfix 17342785 non-standard LAMP response with one lamp hours only """ - # GIVEN: Test object - pjlink = self.pjlink_test - - # WHEN: Process lamp command called with only hours and no lamp power state - pjlink.process_lamp("45") - - # THEN: Lamp should show hours as 45 and lamp power as Unavailable - self.assertEqual(len(pjlink.lamp), 1, 'There should only be 1 lamp available') - self.assertEqual(pjlink.lamp[0]['Hours'], 45, 'Lamp hours should have equalled 45') - self.assertIsNone(pjlink.lamp[0]['On'], 'Lamp power should be "None"') + # Test now part of test_projector_pjlink_commands_01 + # Keeping here for bug reference + pass diff --git a/tests/functional/openlp_core/projectors/test_projector_db.py b/tests/functional/openlp_core/projectors/test_projector_db.py index 619f16bf2..81bd3f006 100644 --- a/tests/functional/openlp_core/projectors/test_projector_db.py +++ b/tests/functional/openlp_core/projectors/test_projector_db.py @@ -107,7 +107,7 @@ class TestProjectorDBUpdate(TestCase): def test_upgrade_old_projector_db(self): """ - Test that we can upgrade an old song db to the current schema + Test that we can upgrade a version 1 db to the current schema """ # GIVEN: An old prjector db old_db = os.path.join(TEST_RESOURCES_PATH, "projector", TEST_DB_PJLINK1) @@ -119,8 +119,7 @@ class TestProjectorDBUpdate(TestCase): updated_to_version, latest_version = upgrade_db(db_url, upgrade) # THEN: the song db should have been upgraded to the latest version - self.assertEqual(updated_to_version, latest_version, - 'The projector DB should have been upgrade to the latest version') + assert updated_to_version == latest_version, 'The projector DB should have been upgrade to the latest version' class TestProjectorDB(TestCase): @@ -157,8 +156,7 @@ class TestProjectorDB(TestCase): record = self.projector.get_projector_by_ip(TEST2_DATA['ip']) # THEN: Verify proper record returned - self.assertTrue(compare_data(Projector(**TEST2_DATA), record), - 'Record found should have been test_2 data') + assert compare_data(Projector(**TEST2_DATA), record) is True, 'Record found should have been test_2 data' def test_find_record_by_name(self): """ @@ -171,8 +169,7 @@ class TestProjectorDB(TestCase): record = self.projector.get_projector_by_name(TEST2_DATA['name']) # THEN: Verify proper record returned - self.assertTrue(compare_data(Projector(**TEST2_DATA), record), - 'Record found should have been test_2 data') + assert compare_data(Projector(**TEST2_DATA), record) is True, 'Record found should have been test_2 data' def test_record_delete(self): """ @@ -187,7 +184,7 @@ class TestProjectorDB(TestCase): # THEN: Verify record not retrievable found = self.projector.get_projector_by_ip(TEST3_DATA['ip']) - self.assertFalse(found, 'test_3 record should have been deleted') + assert found is None, 'test_3 record should have been deleted' def test_record_edit(self): """ @@ -212,12 +209,12 @@ class TestProjectorDB(TestCase): record.model_filter = TEST3_DATA['model_filter'] record.model_lamp = TEST3_DATA['model_lamp'] updated = self.projector.update_projector(record) - self.assertTrue(updated, 'Save updated record should have returned True') + assert updated is True, 'Save updated record should have returned True' record = self.projector.get_projector_by_ip(TEST3_DATA['ip']) # THEN: Record ID should remain the same, but data should be changed - self.assertEqual(record_id, record.id, 'Edited record should have the same ID') - self.assertTrue(compare_data(Projector(**TEST3_DATA), record), 'Edited record should have new data') + assert record_id == record.id, 'Edited record should have the same ID' + assert compare_data(Projector(**TEST3_DATA), record) is True, 'Edited record should have new data' def test_source_add(self): """ @@ -235,7 +232,7 @@ class TestProjectorDB(TestCase): # THEN: Projector should have the same source entry item = self.projector.get_projector_by_id(item_id) - self.assertTrue(compare_source(item.source_list[0], source)) + assert compare_source(item.source_list[0], source) is True, 'Source entry should be the same' def test_manufacturer_repr(self): """ @@ -248,8 +245,8 @@ class TestProjectorDB(TestCase): manufacturer.name = 'OpenLP Test' # THEN: __repr__ should return a proper string - self.assertEqual(str(manufacturer), '', - 'Manufacturer.__repr__() should have returned a proper representation string') + assert str(manufacturer) == '', \ + 'Manufacturer.__repr__() should have returned a proper representation string' def test_model_repr(self): """ @@ -262,8 +259,8 @@ class TestProjectorDB(TestCase): model.name = 'OpenLP Test' # THEN: __repr__ should return a proper string - self.assertEqual(str(model), '', - 'Model.__repr__() should have returned a proper representation string') + assert str(model) == '', \ + 'Model.__repr__() should have returned a proper representation string' def test_source_repr(self): """ @@ -278,19 +275,25 @@ class TestProjectorDB(TestCase): source.text = 'Input text' # THEN: __repr__ should return a proper string - self.assertEqual(str(source), '', - 'Source.__repr__() should have returned a proper representation string') + assert str(source) == '', \ + 'Source.__repr__() should have returned a proper representation string' def test_projector_repr(self): """ Test Projector.__repr__() text """ # GIVEN: Test object + test_string = '< Projector(id="0", ip="127.0.0.1", port="4352", mac_adx="None", pin="None", ' \ + 'name="Test One", location="Somewhere over the rainbow", notes="Not again", ' \ + 'pjlink_name="TEST", pjlink_class="None", manufacturer="IN YOUR DREAMS", model="OpenLP", ' \ + 'serial_no="None", other="None", sources="None", source_list="[]", model_filter="None", ' \ + 'model_lamp="None", sw_version="None") >' + projector = Projector() # WHEN: projector() is populated # NOTE: projector.[pin, other, sources, sw_version, serial_no, sw_version, model_lamp, model_filter] - # should all return None. + # should all return None. # projector.source_list should return an empty list projector.id = 0 projector.ip = '127.0.0.1' @@ -303,13 +306,7 @@ class TestProjectorDB(TestCase): projector.model = 'OpenLP' # THEN: __repr__ should return a proper string - self.assertEqual(str(projector), - '< Projector(id="0", ip="127.0.0.1", port="4352", mac_adx="None", pin="None", ' - 'name="Test One", location="Somewhere over the rainbow", notes="Not again", ' - 'pjlink_name="TEST", manufacturer="IN YOUR DREAMS", model="OpenLP", serial_no="None", ' - 'other="None", sources="None", source_list="[]", model_filter="None", model_lamp="None", ' - 'sw_version="None") >', - 'Projector.__repr__() should have returned a proper representation string') + assert str(projector) == test_string, 'Projector.__repr__() should have returned a proper string' def test_projectorsource_repr(self): """ @@ -326,9 +323,8 @@ class TestProjectorDB(TestCase): self.projector.add_source(source) # THEN: __repr__ should return a proper string - self.assertEqual(str(source), - '', - 'ProjectorSource.__repr__)_ should have returned a proper representation string') + assert str(source) == '', \ + 'ProjectorSource.__repr__)_ should have returned a proper representation string' def test_get_projector_by_id_none(self): """ @@ -341,7 +337,7 @@ class TestProjectorDB(TestCase): results = projector.get_projector_by_id(dbid=123134556409824506) # THEN: Verify return was None - self.assertEqual(results, None, 'Returned results should have equaled None') + assert results is None, 'Returned results should have equaled None' def test_get_projector_all_none(self): """ @@ -354,7 +350,7 @@ class TestProjectorDB(TestCase): results = projector.get_projector_all() # THEN: Verify results is None - self.assertEqual(results, [], 'Returned results should have returned an empty list') + assert [] == results, 'Returned results should have returned an empty list' def test_get_projector_all_one(self): """ @@ -368,8 +364,8 @@ class TestProjectorDB(TestCase): results = self.projector.get_projector_all() # THEN: We should have a list with one entry - self.assertEqual(len(results), 1, 'Returned results should have returned a list with one entry') - self.assertTrue((projector in results), 'Result should have been equal to TEST1_DATA') + assert 1 == len(results), 'Returned results should have returned a list with one entry' + assert (projector in results) is True, 'Result should have been equal to TEST1_DATA' def test_get_projector_all_many(self): """ @@ -387,11 +383,9 @@ class TestProjectorDB(TestCase): results = self.projector.get_projector_all() # THEN: We should have a list with three entries - self.assertEqual(len(results), len(projector_list), - 'Returned results should have returned a list with three entries') + assert len(results) == len(projector_list), 'Returned results should have returned a list with three entries' for projector in results: - self.assertTrue((projector in projector_list), - 'Projector DB entry should have been in expected list') + assert (projector in projector_list) is True, 'Projector DB entry should have been in expected list' def test_get_projector_by_name_fail(self): """ @@ -404,7 +398,7 @@ class TestProjectorDB(TestCase): results = self.projector.get_projector_by_name(name=TEST2_DATA['name']) # THEN: We should have None - self.assertEqual(results, None, 'projector.get_projector_by_name() should have returned None') + assert results is None, 'projector.get_projector_by_name() should have returned None' def test_add_projector_fail(self): """ @@ -417,7 +411,7 @@ class TestProjectorDB(TestCase): results = self.projector.add_projector(Projector(**TEST1_DATA)) # THEN: We should have failed to add new entry - self.assertFalse(results, 'add_projector() should have failed') + assert results is False, 'add_projector() should have failed' def test_update_projector_default_fail(self): """ @@ -430,7 +424,7 @@ class TestProjectorDB(TestCase): results = projector.update_projector() # THEN: We should have failed - self.assertFalse(results, 'update_projector(projector=None) should have returned False') + assert results is False, 'update_projector(projector=None) should have returned False' def test_update_projector_not_in_db_fail(self): """ @@ -444,7 +438,7 @@ class TestProjectorDB(TestCase): results = self.projector.update_projector(projector) # THEN: Results should be False - self.assertFalse(results, 'update_projector(projector=projector) should have returned False') + assert results is False, 'update_projector(projector=projector) should have returned False' def test_delete_projector_fail(self): """ @@ -457,4 +451,4 @@ class TestProjectorDB(TestCase): results = self.projector.delete_projector(Projector(**TEST2_DATA)) # THEN: Results should be False - self.assertFalse(results, 'delete_projector() should have returned False') + assert results is False, 'delete_projector() should have returned False' diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py index ca699e4eb..b3d45fb68 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py @@ -25,7 +25,19 @@ Package to test the openlp.core.projectors.pjlink base package. from unittest import TestCase from unittest.mock import call, patch, MagicMock -from openlp.core.projectors.constants import E_PARAMETER, STATUS_CODE, S_ON, S_CONNECTED, QSOCKET_STATE +import openlp.core.projectors.pjlink +from openlp.core.projectors.constants import \ + E_NOT_CONNECTED, \ + E_PARAMETER, \ + E_UNKNOWN_SOCKET_ERROR, \ + STATUS_CODE, \ + STATUS_MSG, \ + S_CONNECTED, \ + S_CONNECTING, \ + S_NOT_CONNECTED, \ + S_OK, \ + S_ON, \ + QSOCKET_STATE from openlp.core.projectors.db import Projector from openlp.core.projectors.pjlink import PJLink from tests.resources.projector.data import TEST1_DATA @@ -37,55 +49,44 @@ class TestPJLinkBase(TestCase): """ Tests for the PJLink module """ - def setUp(self): - ''' - TestPJLinkCommands part 2 initialization - ''' - self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) - - def tearDown(self): - ''' - TestPJLinkCommands part 2 cleanups - ''' - self.pjlink_test = None - - @patch.object(pjlink_test, 'change_status') - def test_status_change(self, mock_change_status): + def test_status_change(self): """ Test process_command call with ERR2 (Parameter) status """ # GIVEN: Test object - pjlink = pjlink_test + with patch('openlp.core.projectors.pjlink.PJLink.changeStatus') as mock_changeStatus: - # WHEN: process_command is called with "ERR2" status from projector - pjlink.process_command('POWR', 'ERR2') + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - # THEN: change_status should have called change_status with E_UNDEFINED - # as first parameter - mock_change_status.called_with(E_PARAMETER, - 'change_status should have been called with "{}"'.format( - STATUS_CODE[E_PARAMETER])) + # WHEN: process_command is called with "ERR2" status from projector + pjlink.process_command('POWR', 'ERR2') - @patch.object(pjlink_test, 'disconnect_from_host') - def test_socket_abort(self, mock_disconnect): + # THEN: change_status should have called change_status with E_UNDEFINED + # as first parameter + mock_changeStatus.called_with(E_PARAMETER, + 'change_status should have been called with "{}"'.format( + STATUS_CODE[E_PARAMETER])) + + def test_socket_abort(self): """ Test PJLink.socket_abort calls disconnect_from_host """ # GIVEN: Test object - pjlink = pjlink_test + with patch('openlp.core.projectors.pjlink.PJLink.disconnect_from_host') as mock_disconnect: + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - # WHEN: Calling socket_abort - pjlink.socket_abort() + # WHEN: Calling socket_abort + pjlink.socket_abort() - # THEN: disconnect_from_host should be called - self.assertTrue(mock_disconnect.called, 'Should have called disconnect_from_host') + # THEN: disconnect_from_host should be called + assert mock_disconnect.called is True, 'Should have called disconnect_from_host' def test_poll_loop_not_connected(self): """ Test PJLink.poll_loop not connected return """ # GIVEN: Test object and mocks - pjlink = pjlink_test + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.state = MagicMock() pjlink.timer = MagicMock() pjlink.state.return_value = False @@ -95,47 +96,432 @@ class TestPJLinkBase(TestCase): pjlink.poll_loop() # THEN: poll_loop should exit without calling any other method - self.assertFalse(pjlink.timer.called, 'Should have returned without calling any other method') + assert pjlink.timer.called is False, 'Should have returned without calling any other method' - def test_poll_loop_start(self): + def test_poll_loop_set_interval(self): """ Test PJLink.poll_loop makes correct calls """ # GIVEN: Mocks and test data - mock_state = patch.object(self.pjlink_test, 'state').start() - mock_state.return_value = QSOCKET_STATE[S_CONNECTED] - mock_timer = patch.object(self.pjlink_test, 'timer').start() - mock_timer.interval.return_value = 10 - mock_send_command = patch.object(self.pjlink_test, 'send_command').start() + with patch('openlp.core.projectors.pjlink.PJLink.send_command') as mock_send_command: - pjlink = self.pjlink_test - pjlink.poll_time = 20 - pjlink.power = S_ON - pjlink.source_available = None - pjlink.other_info = None - pjlink.manufacturer = None - pjlink.model = None - pjlink.pjlink_name = None - call_list = [ - call('POWR'), - call('ERST'), - call('LAMP'), - call('AVMT'), - call('INPT'), - call('INST'), - call('INFO'), - call('INF1'), - call('INF2'), - call('NAME'), - ] + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + pjlink.state = MagicMock() + pjlink.state.return_value = QSOCKET_STATE[S_CONNECTED] + pjlink.poll_timer = MagicMock() + pjlink.poll_timer.interval.return_value = 10 - # WHEN: PJLink.poll_loop is called - pjlink.poll_loop() + pjlink.poll_time = 20 + pjlink.power = S_ON + pjlink.source_available = None + pjlink.other_info = None + pjlink.manufacturer = None + pjlink.model = None + pjlink.pjlink_name = None + call_list = [ + call('POWR'), + call('ERST'), + call('LAMP'), + call('AVMT'), + call('INPT'), + call('INST'), + call('INFO'), + call('INF1'), + call('INF2'), + call('NAME'), + ] - # THEN: proper calls were made to retrieve projector data - # First, call to update the timer with the next interval - self.assertTrue(mock_timer.setInterval.called) - # Next, should have called the timer to start - self.assertTrue(mock_timer.start.called, 'Should have started the timer') - # Finally, should have called send_command with a list of projetctor status checks - mock_send_command.assert_has_calls(call_list, 'Should have queued projector queries') + # WHEN: PJLink.poll_loop is called + pjlink.poll_loop() + + # THEN: proper calls were made to retrieve projector data + # First, call to update the timer with the next interval + assert pjlink.poll_timer.setInterval.called is True, 'Timer update interval should have been called' + # Finally, should have called send_command with a list of projetctor status checks + mock_send_command.assert_has_calls(call_list, 'Should have queued projector queries') + + def test_projector_change_status_unknown_socket_error(self): + """ + Test change_status with connection error + """ + # GIVEN: Test object and mocks + with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ + patch.object(openlp.core.projectors.pjlink.PJLink, 'changeStatus') as mock_changeStatus, \ + patch.object(openlp.core.projectors.pjlink.PJLink, 'projectorUpdateIcons') as mock_UpdateIcons: + + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + pjlink.projector_status = 0 + pjlink.status_connect = 0 + log_debug_calls = [ + call('({ip}) Changing status to {status} "{msg}"'.format(ip=pjlink.name, + status=STATUS_CODE[E_UNKNOWN_SOCKET_ERROR], + msg=STATUS_MSG[E_UNKNOWN_SOCKET_ERROR])), + call('({ip}) status_connect: {code}: "{msg}"'.format(ip=pjlink.name, + code=STATUS_CODE[E_NOT_CONNECTED], + msg=STATUS_MSG[E_NOT_CONNECTED])), + call('({ip}) projector_status: {code}: "{msg}"'.format(ip=pjlink.name, + code=STATUS_CODE[S_OK], + msg=STATUS_MSG[S_OK])), + call('({ip}) error_status: {code}: "{msg}"'.format(ip=pjlink.name, + code=STATUS_CODE[E_UNKNOWN_SOCKET_ERROR], + msg=STATUS_MSG[E_UNKNOWN_SOCKET_ERROR]))] + + # WHEN: change_status called with unknown socket error + pjlink.change_status(status=E_UNKNOWN_SOCKET_ERROR) + + # THEN: Proper settings should change and signals sent + mock_log.debug.assert_has_calls(log_debug_calls) + assert pjlink.projector_status == S_OK, 'Projector status should not have changed' + assert pjlink.status_connect == E_NOT_CONNECTED, 'Status connect should be NOT CONNECTED' + assert mock_UpdateIcons.emit.called is True, 'Should have called UpdateIcons' + mock_changeStatus.emit.assert_called_once_with(pjlink.ip, E_UNKNOWN_SOCKET_ERROR, + STATUS_MSG[E_UNKNOWN_SOCKET_ERROR]) + + def test_projector_change_status_connection_status_connecting(self): + """ + Test change_status with connecting status + """ + # GIVEN: Test object and mocks + with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ + patch.object(openlp.core.projectors.pjlink.PJLink, 'changeStatus') as mock_changeStatus, \ + patch.object(openlp.core.projectors.pjlink.PJLink, 'projectorUpdateIcons') as mock_UpdateIcons: + + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + pjlink.projector_status = 0 + pjlink.status_connect = 0 + log_debug_calls = [ + call('({ip}) Changing status to {status} "{msg}"'.format(ip=pjlink.name, + status=STATUS_CODE[S_CONNECTING], + msg=STATUS_MSG[S_CONNECTING])), + call('({ip}) status_connect: {code}: "{msg}"'.format(ip=pjlink.name, + code=STATUS_CODE[S_CONNECTING], + msg=STATUS_MSG[S_CONNECTING])), + call('({ip}) projector_status: {code}: "{msg}"'.format(ip=pjlink.name, + code=STATUS_CODE[S_OK], + msg=STATUS_MSG[S_OK])), + call('({ip}) error_status: {code}: "{msg}"'.format(ip=pjlink.name, + code=STATUS_CODE[S_OK], + msg=STATUS_MSG[S_OK]))] + + # WHEN: change_status called with CONNECTING + pjlink.change_status(status=S_CONNECTING) + + # THEN: Proper settings should change and signals sent + mock_log.debug.assert_has_calls(log_debug_calls) + mock_changeStatus.emit.assert_called_once_with(pjlink.ip, S_CONNECTING, STATUS_MSG[S_CONNECTING]) + assert pjlink.projector_status == S_OK, 'Projector status should not have changed' + assert pjlink.status_connect == S_CONNECTING, 'Status connect should be CONNECTING' + assert mock_UpdateIcons.emit.called is True, 'Should have called UpdateIcons' + + def test_projector_change_status_connection_status_connected(self): + """ + Test change_status with connected status + """ + # GIVEN: Test object and mocks + with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ + patch.object(openlp.core.projectors.pjlink.PJLink, 'changeStatus') as mock_changeStatus: + + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + pjlink.projector_status = 0 + pjlink.status_connect = 0 + log_debug_calls = [ + call('({ip}) Changing status to {status} "{msg}"'.format(ip=pjlink.name, + status=STATUS_CODE[S_CONNECTED], + msg=STATUS_MSG[S_CONNECTED])), + call('({ip}) status_connect: {code}: "{msg}"'.format(ip=pjlink.name, + code=STATUS_CODE[S_CONNECTED], + msg=STATUS_MSG[S_CONNECTED])), + call('({ip}) projector_status: {code}: "{msg}"'.format(ip=pjlink.name, + code=STATUS_CODE[S_OK], + msg=STATUS_MSG[S_OK])), + call('({ip}) error_status: {code}: "{msg}"'.format(ip=pjlink.name, + code=STATUS_CODE[S_OK], + msg=STATUS_MSG[S_OK]))] + + # WHEN: change_status called with CONNECTED + pjlink.change_status(status=S_CONNECTED) + + # THEN: Proper settings should change and signals sent + mock_log.debug.assert_has_calls(log_debug_calls) + mock_changeStatus.emit.assert_called_once_with(pjlink.ip, S_CONNECTED, 'Connected') + assert pjlink.projector_status == S_OK, 'Projector status should not have changed' + assert pjlink.status_connect == S_CONNECTED, 'Status connect should be CONNECTED' + + def test_projector_change_status_connection_status_with_message(self): + """ + Test change_status with connection status + """ + # GIVEN: Test object and mocks + with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ + patch.object(openlp.core.projectors.pjlink.PJLink, 'changeStatus') as mock_changeStatus: + + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + pjlink.projector_status = 0 + pjlink.status_connect = 0 + test_message = 'Different Status Message than default' + log_debug_calls = [ + call('({ip}) Changing status to {status} "{msg}"'.format(ip=pjlink.name, + status=STATUS_CODE[S_ON], + msg=test_message)), + call('({ip}) status_connect: {code}: "{msg}"'.format(ip=pjlink.name, + code=STATUS_CODE[S_OK], + msg=test_message)), + call('({ip}) projector_status: {code}: "{msg}"'.format(ip=pjlink.name, + code=STATUS_CODE[S_ON], + msg=test_message)), + call('({ip}) error_status: {code}: "{msg}"'.format(ip=pjlink.name, + code=STATUS_CODE[S_OK], + msg=test_message))] + + # WHEN: change_status called with projector ON status + pjlink.change_status(status=S_ON, msg=test_message) + + # THEN: Proper settings should change and signals sent + mock_log.debug.assert_has_calls(log_debug_calls) + mock_changeStatus.emit.assert_called_once_with(pjlink.ip, S_ON, test_message) + assert pjlink.projector_status == S_ON, 'Projector status should be ON' + assert pjlink.status_connect == S_OK, 'Status connect should not have changed' + + def test_projector_get_av_mute_status(self): + """ + Test sending command to retrieve shutter/audio state + """ + # GIVEN: Test object and mocks + with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ + patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + test_data = 'AVMT' + log_debug_calls = [call('({ip}) reset_information() connect status is ' + '{state}'.format(ip=pjlink.name, state=STATUS_CODE[S_NOT_CONNECTED])), + call('({ip}) Sending {cmd} command'.format(ip=pjlink.name, cmd=test_data))] + + # WHEN: get_av_mute_status is called + pjlink.get_av_mute_status() + + # THEN: log data and send_command should have been called + mock_log.debug.assert_has_calls(log_debug_calls) + mock_send_command.assert_called_once_with(cmd=test_data) + + def test_projector_get_available_inputs(self): + """ + Test sending command to retrieve avaliable inputs + """ + # GIVEN: Test object and mocks + with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ + patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + test_data = 'INST' + log_debug_calls = [call('({ip}) reset_information() connect status is ' + '{state}'.format(ip=pjlink.name, state=STATUS_CODE[S_NOT_CONNECTED])), + call('({ip}) Sending {cmd} command'.format(ip=pjlink.name, cmd=test_data))] + + # WHEN: get_available_inputs is called + pjlink.get_available_inputs() + + # THEN: log data and send_command should have been called + mock_log.debug.assert_has_calls(log_debug_calls) + mock_send_command.assert_called_once_with(cmd=test_data) + + def test_projector_get_error_status(self): + """ + Test sending command to retrieve projector error status + """ + # GIVEN: Test object and mocks + with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ + patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + test_data = 'ERST' + log_debug_calls = [call('({ip}) reset_information() connect status is ' + '{state}'.format(ip=pjlink.name, state=STATUS_CODE[S_NOT_CONNECTED])), + call('({ip}) Sending {cmd} command'.format(ip=pjlink.name, cmd=test_data))] + + # WHEN: get_error_status is called + pjlink.get_error_status() + + # THEN: log data and send_command should have been called + mock_log.debug.assert_has_calls(log_debug_calls) + mock_send_command.assert_called_once_with(cmd=test_data) + + def test_projector_get_input_source(self): + """ + Test sending command to retrieve current input + """ + # GIVEN: Test object and mocks + with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ + patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + test_data = 'INPT' + log_debug_calls = [call('({ip}) reset_information() connect status is ' + '{state}'.format(ip=pjlink.name, state=STATUS_CODE[S_NOT_CONNECTED])), + call('({ip}) Sending {cmd} command'.format(ip=pjlink.name, cmd=test_data))] + + # WHEN: get_input_source is called + pjlink.get_input_source() + + # THEN: log data and send_command should have been called + mock_log.debug.assert_has_calls(log_debug_calls) + mock_send_command.assert_called_once_with(cmd=test_data) + + def test_projector_get_lamp_status(self): + """ + Test sending command to retrieve lamp(s) status + """ + # GIVEN: Test object and mocks + with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ + patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + test_data = 'LAMP' + log_debug_calls = [call('({ip}) reset_information() connect status is ' + '{state}'.format(ip=pjlink.name, state=STATUS_CODE[S_NOT_CONNECTED])), + call('({ip}) Sending {cmd} command'.format(ip=pjlink.name, cmd=test_data))] + + # WHEN: get_input_source is called + pjlink.get_lamp_status() + + # THEN: log data and send_command should have been called + mock_log.debug.assert_has_calls(log_debug_calls) + mock_send_command.assert_called_once_with(cmd=test_data) + + def test_projector_get_manufacturer(self): + """ + Test sending command to retrieve manufacturer name + """ + # GIVEN: Test object and mocks + with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ + patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + test_data = 'INF1' + log_debug_calls = [call('({ip}) reset_information() connect status is ' + '{state}'.format(ip=pjlink.name, state=STATUS_CODE[S_NOT_CONNECTED])), + call('({ip}) Sending {cmd} command'.format(ip=pjlink.name, cmd=test_data))] + + # WHEN: get_input_source is called + pjlink.get_manufacturer() + + # THEN: log data and send_command should have been called + mock_log.debug.assert_has_calls(log_debug_calls) + mock_send_command.assert_called_once_with(cmd=test_data) + + def test_projector_get_model(self): + """ + Test sending command to get model information + """ + # GIVEN: Test object and mocks + with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ + patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + test_data = 'INF2' + log_debug_calls = [call('({ip}) reset_information() connect status is ' + '{state}'.format(ip=pjlink.name, state=STATUS_CODE[S_NOT_CONNECTED])), + call('({ip}) Sending {cmd} command'.format(ip=pjlink.name, cmd=test_data))] + + # WHEN: get_input_source is called + pjlink.get_model() + + # THEN: log data and send_command should have been called + mock_log.debug.assert_has_calls(log_debug_calls) + mock_send_command.assert_called_once_with(cmd=test_data) + + def test_projector_get_name(self): + """ + Test sending command to get user-assigned name + """ + # GIVEN: Test object and mocks + with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ + patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + test_data = 'NAME' + log_debug_calls = [call('({ip}) reset_information() connect status is ' + '{state}'.format(ip=pjlink.name, state=STATUS_CODE[S_NOT_CONNECTED])), + call('({ip}) Sending {cmd} command'.format(ip=pjlink.name, cmd=test_data))] + + # WHEN: get_input_source is called + pjlink.get_name() + + # THEN: log data and send_command should have been called + mock_log.debug.assert_has_calls(log_debug_calls) + mock_send_command.assert_called_once_with(cmd=test_data) + + def test_projector_get_other_info(self): + """ + Test sending command to retrieve other information + """ + # GIVEN: Test object and mocks + with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ + patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + test_data = 'INFO' + log_debug_calls = [call('({ip}) reset_information() connect status is ' + '{state}'.format(ip=pjlink.name, state=STATUS_CODE[S_NOT_CONNECTED])), + call('({ip}) Sending {cmd} command'.format(ip=pjlink.name, cmd=test_data))] + + # WHEN: get_input_source is called + pjlink.get_other_info() + + # THEN: log data and send_command should have been called + mock_log.debug.assert_has_calls(log_debug_calls) + mock_send_command.assert_called_once_with(cmd=test_data) + + def test_projector_get_power_status(self): + """ + Test sending command to retrieve current power state + """ + # GIVEN: Test object and mocks + with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ + patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + test_data = 'POWR' + log_debug_calls = [call('({ip}) reset_information() connect status is ' + '{state}'.format(ip=pjlink.name, state=STATUS_CODE[S_NOT_CONNECTED])), + call('({ip}) Sending {cmd} command'.format(ip=pjlink.name, cmd=test_data))] + + # WHEN: get_input_source is called + pjlink.get_power_status() + + # THEN: log data and send_command should have been called + mock_log.debug.assert_has_calls(log_debug_calls) + mock_send_command.assert_called_once_with(cmd=test_data) + + def test_projector_get_status_invalid(self): + """ + Test to check returned information for error code + """ + # GIVEN: Test object + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + test_string = 'NaN test' + + # WHEN: get_status called + code, message = pjlink._get_status(status=test_string) + + # THEN: Proper data should have been returned + assert code == -1, 'Should have returned -1 as a bad status check' + assert message is None, 'Invalid code type should have returned None for message' + + def test_projector_get_status_valid(self): + """ + Test to check returned information for status codes + """ + # GIVEN: Test object + test_message = 'Not Connected' + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + + # WHEN: get_status called + code, message = pjlink._get_status(status=S_NOT_CONNECTED) + + # THEN: Proper strings should have been returned + assert code == 'S_NOT_CONNECTED', 'Code returned should have been the same code that was sent' + assert message == test_message, 'Description of code should have been returned' + + def test_projector_get_status_unknown(self): + """ + Test to check returned information for unknown code + """ + # GIVEN: Test object + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + + # WHEN: get_status called + code, message = pjlink._get_status(status=9999) + + # THEN: Proper strings should have been returned + assert code is None, 'Code returned should have been the same code that was sent' + assert message is None, 'Should have returned None as message' diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py index 74623b9c3..d6c549177 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py @@ -44,15 +44,16 @@ class TestPJLinkRouting(TestCase): Test not a valid command """ # GIVEN: Test object - log_warning_text = [call('(111.111.111.111) get_data(): Invalid packet - unknown command "UNK"')] - log_debug_text = [call('(111.111.111.111) get_data(ip="111.111.111.111" buffer="b\'%1UNK=Huh?\'"'), - call('(111.111.111.111) get_data(): Checking new data "%1UNK=Huh?"')] - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ patch.object(openlp.core.projectors.pjlink.PJLink, '_trash_buffer') as mock_buffer: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.pjlink_functions = MagicMock() + log_warning_text = [call('({ip}) get_data(): Invalid packet - ' + 'unknown command "UNK"'.format(ip=pjlink.name))] + log_debug_text = [call('({ip}) get_data(ip="111.111.111.111" ' + 'buffer="b\'%1UNK=Huh?\'"'.format(ip=pjlink.name)), + call('({ip}) get_data(): Checking new data "%1UNK=Huh?"'.format(ip=pjlink.name))] # WHEN: get_data called with an unknown command pjlink.get_data(buff='{prefix}1UNK=Huh?'.format(prefix=PJLINK_PREFIX).encode('utf-8')) @@ -68,13 +69,12 @@ class TestPJLinkRouting(TestCase): Test process_command calls proper function """ # GIVEN: Test object and mocks - log_debug_calls = [call('(111.111.111.111) Processing command "CLSS" with data "1"'), - call('(111.111.111.111) Calling function for CLSS')] - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + log_debug_calls = [call('({ip}) Processing command "CLSS" with data "1"'.format(ip=pjlink.name)), + call('({ip}) Calling function for CLSS'.format(ip=pjlink.name))] # WHEN: process_command is called with valid function and data pjlink.process_command(cmd='CLSS', data='1') @@ -88,9 +88,6 @@ class TestPJLinkRouting(TestCase): Test ERRA - Authentication Error """ # GIVEN: Test object - log_error_calls = [call('(111.111.111.111) PJLINK: {msg}'.format(msg=STATUS_MSG[E_AUTHENTICATION]))] - log_debug_calls = [call('(111.111.111.111) Processing command "PJLINK" with data "ERRA"')] - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ patch.object(openlp.core.projectors.pjlink.PJLink, 'process_pjlink') as mock_process_pjlink, \ patch.object(openlp.core.projectors.pjlink.PJLink, 'change_status') as mock_change_status, \ @@ -98,6 +95,8 @@ class TestPJLinkRouting(TestCase): patch.object(openlp.core.projectors.pjlink.PJLink, 'projectorAuthentication') as mock_authentication: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + log_error_calls = [call('({ip}) PJLINK: {msg}'.format(ip=pjlink.name, msg=STATUS_MSG[E_AUTHENTICATION]))] + log_debug_calls = [call('({ip}) Processing command "PJLINK" with data "ERRA"'.format(ip=pjlink.name))] # WHEN: process_command called with ERRA pjlink.process_command(cmd='PJLINK', data=PJLINK_ERRORS[E_AUTHENTICATION]) @@ -115,14 +114,13 @@ class TestPJLinkRouting(TestCase): Test ERR1 - Undefined projector function """ # GIVEN: Test object - log_error_text = [call('(111.111.111.111) CLSS: {msg}'.format(msg=STATUS_MSG[E_UNDEFINED]))] - log_debug_text = [call('(111.111.111.111) Processing command "CLSS" with data "ERR1"'), - call('(111.111.111.111) Calling function for CLSS')] - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + log_error_text = [call('({ip}) CLSS: {msg}'.format(ip=pjlink.name, msg=STATUS_MSG[E_UNDEFINED]))] + log_debug_text = [call('({ip}) Processing command "CLSS" with data "ERR1"'.format(ip=pjlink.name)), + call('({ip}) Calling function for CLSS'.format(ip=pjlink.name))] # WHEN: process_command called with ERR1 pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_UNDEFINED]) @@ -137,14 +135,13 @@ class TestPJLinkRouting(TestCase): Test ERR2 - Parameter Error """ # GIVEN: Test object - log_error_text = [call('(111.111.111.111) CLSS: {msg}'.format(msg=STATUS_MSG[E_PARAMETER]))] - log_debug_text = [call('(111.111.111.111) Processing command "CLSS" with data "ERR2"'), - call('(111.111.111.111) Calling function for CLSS')] - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + log_error_text = [call('({ip}) CLSS: {msg}'.format(ip=pjlink.name, msg=STATUS_MSG[E_PARAMETER]))] + log_debug_text = [call('({ip}) Processing command "CLSS" with data "ERR2"'.format(ip=pjlink.name)), + call('({ip}) Calling function for CLSS'.format(ip=pjlink.name))] # WHEN: process_command called with ERR2 pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_PARAMETER]) @@ -159,14 +156,13 @@ class TestPJLinkRouting(TestCase): Test ERR3 - Unavailable error """ # GIVEN: Test object - log_error_text = [call('(111.111.111.111) CLSS: {msg}'.format(msg=STATUS_MSG[E_UNAVAILABLE]))] - log_debug_text = [call('(111.111.111.111) Processing command "CLSS" with data "ERR3"'), - call('(111.111.111.111) Calling function for CLSS')] - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + log_error_text = [call('({ip}) CLSS: {msg}'.format(ip=pjlink.name, msg=STATUS_MSG[E_UNAVAILABLE]))] + log_debug_text = [call('({ip}) Processing command "CLSS" with data "ERR3"'.format(ip=pjlink.name)), + call('({ip}) Calling function for CLSS'.format(ip=pjlink.name))] # WHEN: process_command called with ERR3 pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_UNAVAILABLE]) @@ -181,14 +177,13 @@ class TestPJLinkRouting(TestCase): Test ERR3 - Unavailable error """ # GIVEN: Test object - log_error_text = [call('(111.111.111.111) CLSS: {msg}'.format(msg=STATUS_MSG[E_PROJECTOR]))] - log_debug_text = [call('(111.111.111.111) Processing command "CLSS" with data "ERR4"'), - call('(111.111.111.111) Calling function for CLSS')] - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + log_error_text = [call('({ip}) CLSS: {msg}'.format(ip=pjlink.name, msg=STATUS_MSG[E_PROJECTOR]))] + log_debug_text = [call('({ip}) Processing command "CLSS" with data "ERR4"'.format(ip=pjlink.name)), + call('({ip}) Calling function for CLSS'.format(ip=pjlink.name))] # WHEN: process_command called with ERR4 pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_PROJECTOR]) @@ -203,14 +198,13 @@ class TestPJLinkRouting(TestCase): Test command valid but no method to process yet """ # GIVEN: Test object - log_warning_text = [call('(111.111.111.111) Unable to process command="CLSS" (Future option?)')] - log_debug_text = [call('(111.111.111.111) Processing command "CLSS" with data "Huh?"')] - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.pjlink_functions = MagicMock() + log_warning_text = [call('({ip}) Unable to process command="CLSS" (Future option?)'.format(ip=pjlink.name))] + log_debug_text = [call('({ip}) Processing command "CLSS" with data "Huh?"'.format(ip=pjlink.name))] # WHEN: Processing a possible future command pjlink.process_command(cmd='CLSS', data="Huh?") @@ -227,14 +221,13 @@ class TestPJLinkRouting(TestCase): """ # GIVEN: Initial mocks and data # GIVEN: Test object and mocks - log_debug_calls = [call('(111.111.111.111) Processing command "CLSS" with data "OK"'), - call('(111.111.111.111) Command "CLSS" returned OK')] - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command, \ patch.object(openlp.core.projectors.pjlink.PJLink, 'process_clss') as mock_process_clss: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + log_debug_calls = [call('({ip}) Processing command "CLSS" with data "OK"'.format(ip=pjlink.name)), + call('({ip}) Command "CLSS" returned OK'.format(ip=pjlink.name))] # WHEN: process_command is called with valid function and data pjlink.process_command(cmd='CLSS', data='OK') diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py index c8551b594..42d46f735 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_01.py @@ -26,9 +26,17 @@ from unittest import TestCase from unittest.mock import call, patch import openlp.core.projectors.pjlink -from openlp.core.projectors.constants import PJLINK_ERST_DATA, PJLINK_ERST_STATUS, PJLINK_POWR_STATUS, \ - STATUS_CODE, STATUS_MSG, E_ERROR, E_NOT_CONNECTED, E_UNKNOWN_SOCKET_ERROR, E_WARN, \ - S_CONNECTED, S_CONNECTING, S_OFF, S_OK, S_ON, S_NOT_CONNECTED, S_STANDBY +from openlp.core.projectors.constants import \ + PJLINK_ERST_DATA, \ + PJLINK_ERST_STATUS, \ + PJLINK_POWR_STATUS, \ + STATUS_CODE, \ + E_ERROR, \ + E_WARN, \ + S_OK, \ + S_ON, \ + S_NOT_CONNECTED, \ + S_STANDBY from openlp.core.projectors.db import Projector from openlp.core.projectors.pjlink import PJLink @@ -39,401 +47,6 @@ class TestPJLinkCommands(TestCase): """ Tests for the PJLinkCommands class part 1 """ - def test_projector_change_status_unknown_socket_error(self): - """ - Test change_status with connection error - """ - log_debug_calls = [ - call('(111.111.111.111) Changing status to ' - '{status} "{msg}"'.format(status=STATUS_CODE[E_UNKNOWN_SOCKET_ERROR], - msg=STATUS_MSG[E_UNKNOWN_SOCKET_ERROR])), - call('(111.111.111.111) status_connect: ' - '{code}: "{msg}"'.format(code=STATUS_CODE[E_NOT_CONNECTED], - msg=STATUS_MSG[E_NOT_CONNECTED])), - call('(111.111.111.111) projector_status: ' - '{code}: "{msg}"'.format(code=STATUS_CODE[S_OK], - msg=STATUS_MSG[S_OK])), - call('(111.111.111.111) error_status: ' - '{code}: "{msg}"'.format(code=STATUS_CODE[E_UNKNOWN_SOCKET_ERROR], - msg=STATUS_MSG[E_UNKNOWN_SOCKET_ERROR]))] - - # GIVEN: Test object and mocks - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ - patch.object(openlp.core.projectors.pjlink.PJLink, 'changeStatus') as mock_changeStatus, \ - patch.object(openlp.core.projectors.pjlink.PJLink, 'projectorUpdateIcons') as mock_UpdateIcons: - - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - pjlink.projector_status = 0 - pjlink.status_connect = 0 - - # WHEN: change_status called with unknown socket error - pjlink.change_status(status=E_UNKNOWN_SOCKET_ERROR) - - # THEN: Proper settings should change and signals sent - mock_log.debug.assert_has_calls(log_debug_calls) - assert pjlink.projector_status == S_OK, 'Projector status should not have changed' - assert pjlink.status_connect == E_NOT_CONNECTED, 'Status connect should be NOT CONNECTED' - assert mock_UpdateIcons.emit.called is True, 'Should have called UpdateIcons' - mock_changeStatus.emit.assert_called_once_with(pjlink.ip, E_UNKNOWN_SOCKET_ERROR, - STATUS_MSG[E_UNKNOWN_SOCKET_ERROR]) - - def test_projector_change_status_connection_status_connecting(self): - """ - Test change_status with connecting status - """ - log_debug_calls = [ - call('(111.111.111.111) Changing status to ' - '{status} "{msg}"'.format(status=STATUS_CODE[S_CONNECTING], - msg=STATUS_MSG[S_CONNECTING])), - call('(111.111.111.111) status_connect: ' - '{code}: "{msg}"'.format(code=STATUS_CODE[S_CONNECTING], - msg=STATUS_MSG[S_CONNECTING])), - call('(111.111.111.111) projector_status: ' - '{code}: "{msg}"'.format(code=STATUS_CODE[S_OK], - msg=STATUS_MSG[S_OK])), - call('(111.111.111.111) error_status: ' - '{code}: "{msg}"'.format(code=STATUS_CODE[S_OK], - msg=STATUS_MSG[S_OK]))] - - # GIVEN: Test object and mocks - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ - patch.object(openlp.core.projectors.pjlink.PJLink, 'changeStatus') as mock_changeStatus, \ - patch.object(openlp.core.projectors.pjlink.PJLink, 'projectorUpdateIcons') as mock_UpdateIcons: - - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - pjlink.projector_status = 0 - pjlink.status_connect = 0 - - # WHEN: change_status called with CONNECTING - pjlink.change_status(status=S_CONNECTING) - - # THEN: Proper settings should change and signals sent - mock_log.debug.assert_has_calls(log_debug_calls) - mock_changeStatus.emit.assert_called_once_with(pjlink.ip, S_CONNECTING, STATUS_MSG[S_CONNECTING]) - assert pjlink.projector_status == S_OK, 'Projector status should not have changed' - assert pjlink.status_connect == S_CONNECTING, 'Status connect should be CONNECTING' - assert mock_UpdateIcons.emit.called is True, 'Should have called UpdateIcons' - - def test_projector_change_status_connection_status_connected(self): - """ - Test change_status with connected status - """ - log_debug_calls = [ - call('(111.111.111.111) Changing status to ' - '{status} "{msg}"'.format(status=STATUS_CODE[S_CONNECTED], - msg=STATUS_MSG[S_CONNECTED])), - call('(111.111.111.111) status_connect: ' - '{code}: "{msg}"'.format(code=STATUS_CODE[S_CONNECTED], - msg=STATUS_MSG[S_CONNECTED])), - call('(111.111.111.111) projector_status: ' - '{code}: "{msg}"'.format(code=STATUS_CODE[S_OK], - msg=STATUS_MSG[S_OK])), - call('(111.111.111.111) error_status: ' - '{code}: "{msg}"'.format(code=STATUS_CODE[S_OK], - msg=STATUS_MSG[S_OK]))] - - # GIVEN: Test object and mocks - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ - patch.object(openlp.core.projectors.pjlink.PJLink, 'changeStatus') as mock_changeStatus: - - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - pjlink.projector_status = 0 - pjlink.status_connect = 0 - - # WHEN: change_status called with CONNECTED - pjlink.change_status(status=S_CONNECTED) - - # THEN: Proper settings should change and signals sent - mock_log.debug.assert_has_calls(log_debug_calls) - mock_changeStatus.emit.assert_called_once_with(pjlink.ip, S_CONNECTED, 'Connected') - assert pjlink.projector_status == S_OK, 'Projector status should not have changed' - assert pjlink.status_connect == S_CONNECTED, 'Status connect should be CONNECTED' - - def test_projector_change_status_connection_status_with_message(self): - """ - Test change_status with connection status - """ - test_message = 'Different Status Message than default' - log_debug_calls = [ - call('(111.111.111.111) Changing status to {status} "{msg}"'.format(status=STATUS_CODE[S_ON], - msg=test_message)), - call('(111.111.111.111) status_connect: {code}: "{msg}"'.format(code=STATUS_CODE[S_OK], - msg=test_message)), - call('(111.111.111.111) projector_status: {code}: "{msg}"'.format(code=STATUS_CODE[S_ON], - msg=test_message)), - call('(111.111.111.111) error_status: {code}: "{msg}"'.format(code=STATUS_CODE[S_OK], - msg=test_message))] - - # GIVEN: Test object and mocks - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ - patch.object(openlp.core.projectors.pjlink.PJLink, 'changeStatus') as mock_changeStatus: - - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - pjlink.projector_status = 0 - pjlink.status_connect = 0 - - # WHEN: change_status called with projector ON status - pjlink.change_status(status=S_ON, msg=test_message) - - # THEN: Proper settings should change and signals sent - mock_log.debug.assert_has_calls(log_debug_calls) - mock_changeStatus.emit.assert_called_once_with(pjlink.ip, S_ON, test_message) - assert pjlink.projector_status == S_ON, 'Projector status should be ON' - assert pjlink.status_connect == S_OK, 'Status connect should not have changed' - - def test_projector_get_av_mute_status(self): - """ - Test sending command to retrieve shutter/audio state - """ - test_data = 'AVMT' - log_debug_calls = [call('(111.111.111.111) reset_information() connect status is ' - '{state}'.format(state=STATUS_CODE[S_NOT_CONNECTED])), - call('(111.111.111.111) Sending {cmd} command'.format(cmd=test_data))] - - # GIVEN: Test object and mocks - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ - patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - - # WHEN: get_av_mute_status is called - pjlink.get_av_mute_status() - - # THEN: log data and send_command should have been called - mock_log.debug.assert_has_calls(log_debug_calls) - mock_send_command.assert_called_once_with(cmd=test_data) - - def test_projector_get_available_inputs(self): - """ - Test sending command to retrieve avaliable inputs - """ - test_data = 'INST' - log_debug_calls = [call('(111.111.111.111) reset_information() connect status is ' - '{state}'.format(state=STATUS_CODE[S_NOT_CONNECTED])), - call('(111.111.111.111) Sending {cmd} command'.format(cmd=test_data))] - - # GIVEN: Test object and mocks - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ - patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - - # WHEN: get_available_inputs is called - pjlink.get_available_inputs() - - # THEN: log data and send_command should have been called - mock_log.debug.assert_has_calls(log_debug_calls) - mock_send_command.assert_called_once_with(cmd=test_data) - - def test_projector_get_error_status(self): - """ - Test sending command to retrieve projector error status - """ - test_data = 'ERST' - log_debug_calls = [call('(111.111.111.111) reset_information() connect status is ' - '{state}'.format(state=STATUS_CODE[S_NOT_CONNECTED])), - call('(111.111.111.111) Sending {cmd} command'.format(cmd=test_data))] - # GIVEN: Test object and mocks - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ - patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - - # WHEN: get_error_status is called - pjlink.get_error_status() - - # THEN: log data and send_command should have been called - mock_log.debug.assert_has_calls(log_debug_calls) - mock_send_command.assert_called_once_with(cmd=test_data) - - def test_projector_get_input_source(self): - """ - Test sending command to retrieve current input - """ - test_data = 'INPT' - log_debug_calls = [call('(111.111.111.111) reset_information() connect status is ' - '{state}'.format(state=STATUS_CODE[S_NOT_CONNECTED])), - call('(111.111.111.111) Sending {cmd} command'.format(cmd=test_data))] - - # GIVEN: Test object and mocks - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ - patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - - # WHEN: get_input_source is called - pjlink.get_input_source() - - # THEN: log data and send_command should have been called - mock_log.debug.assert_has_calls(log_debug_calls) - mock_send_command.assert_called_once_with(cmd=test_data) - - def test_projector_get_lamp_status(self): - """ - Test sending command to retrieve lamp(s) status - """ - test_data = 'LAMP' - log_debug_calls = [call('(111.111.111.111) reset_information() connect status is ' - '{state}'.format(state=STATUS_CODE[S_NOT_CONNECTED])), - call('(111.111.111.111) Sending {cmd} command'.format(cmd=test_data))] - - # GIVEN: Test object and mocks - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ - patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - - # WHEN: get_input_source is called - pjlink.get_lamp_status() - - # THEN: log data and send_command should have been called - mock_log.debug.assert_has_calls(log_debug_calls) - mock_send_command.assert_called_once_with(cmd=test_data) - - def test_projector_get_manufacturer(self): - """ - Test sending command to retrieve manufacturer name - """ - test_data = 'INF1' - log_debug_calls = [call('(111.111.111.111) reset_information() connect status is ' - '{state}'.format(state=STATUS_CODE[S_NOT_CONNECTED])), - call('(111.111.111.111) Sending {cmd} command'.format(cmd=test_data))] - - # GIVEN: Test object and mocks - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ - patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - - # WHEN: get_input_source is called - pjlink.get_manufacturer() - - # THEN: log data and send_command should have been called - mock_log.debug.assert_has_calls(log_debug_calls) - mock_send_command.assert_called_once_with(cmd=test_data) - - def test_projector_get_model(self): - """ - Test sending command to get model information - """ - test_data = 'INF2' - log_debug_calls = [call('(111.111.111.111) reset_information() connect status is ' - '{state}'.format(state=STATUS_CODE[S_NOT_CONNECTED])), - call('(111.111.111.111) Sending {cmd} command'.format(cmd=test_data))] - - # GIVEN: Test object and mocks - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ - patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - - # WHEN: get_input_source is called - pjlink.get_model() - - # THEN: log data and send_command should have been called - mock_log.debug.assert_has_calls(log_debug_calls) - mock_send_command.assert_called_once_with(cmd=test_data) - - def test_projector_get_name(self): - """ - Test sending command to get user-assigned name - """ - test_data = 'NAME' - log_debug_calls = [call('(111.111.111.111) reset_information() connect status is ' - '{state}'.format(state=STATUS_CODE[S_NOT_CONNECTED])), - call('(111.111.111.111) Sending {cmd} command'.format(cmd=test_data))] - - # GIVEN: Test object and mocks - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ - patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - - # WHEN: get_input_source is called - pjlink.get_name() - - # THEN: log data and send_command should have been called - mock_log.debug.assert_has_calls(log_debug_calls) - mock_send_command.assert_called_once_with(cmd=test_data) - - def test_projector_get_other_info(self): - """ - Test sending command to retrieve other information - """ - test_data = 'INFO' - log_debug_calls = [call('(111.111.111.111) reset_information() connect status is ' - '{state}'.format(state=STATUS_CODE[S_NOT_CONNECTED])), - call('(111.111.111.111) Sending {cmd} command'.format(cmd=test_data))] - - # GIVEN: Test object and mocks - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ - patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - - # WHEN: get_input_source is called - pjlink.get_other_info() - - # THEN: log data and send_command should have been called - mock_log.debug.assert_has_calls(log_debug_calls) - mock_send_command.assert_called_once_with(cmd=test_data) - - def test_projector_get_power_status(self): - """ - Test sending command to retrieve current power state - """ - test_data = 'POWR' - log_debug_calls = [call('(111.111.111.111) reset_information() connect status is ' - '{state}'.format(state=STATUS_CODE[S_NOT_CONNECTED])), - call('(111.111.111.111) Sending {cmd} command'.format(cmd=test_data))] - - # GIVEN: Test object and mocks - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ - patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command: - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - - # WHEN: get_input_source is called - pjlink.get_power_status() - - # THEN: log data and send_command should have been called - mock_log.debug.assert_has_calls(log_debug_calls) - mock_send_command.assert_called_once_with(cmd=test_data) - - def test_projector_get_status_invalid(self): - """ - Test to check returned information for error code - """ - # GIVEN: Test object - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - test_string = 'NaN test' - - # WHEN: get_status called - code, message = pjlink._get_status(status=test_string) - - # THEN: Proper data should have been returned - assert code == -1, 'Should have returned -1 as a bad status check' - assert message is None, 'Invalid code type should have returned None for message' - - def test_projector_get_status_valid(self): - """ - Test to check returned information for status codes - """ - # GIVEN: Test object - test_message = 'Not Connected' - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - - # WHEN: get_status called - code, message = pjlink._get_status(status=S_NOT_CONNECTED) - - # THEN: Proper strings should have been returned - assert code == 'S_NOT_CONNECTED', 'Code returned should have been the same code that was sent' - assert message == test_message, 'Description of code should have been returned' - - def test_projector_get_status_unknown(self): - """ - Test to check returned information for unknown code - """ - # GIVEN: Test object - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - - # WHEN: get_status called - code, message = pjlink._get_status(status=9999) - - # THEN: Proper strings should have been returned - assert code is None, 'Code returned should have been the same code that was sent' - assert message is None, 'Should have returned None as message' - def test_projector_process_inf1(self): """ Test saving INF1 data (manufacturer) @@ -602,14 +215,14 @@ class TestPJLinkCommands(TestCase): """ Test CLSS reply has no class number """ - log_debug_calls = [call('(111.111.111.111) reset_information() connect status is ' - '{state}'.format(state=STATUS_CODE[S_NOT_CONNECTED])), - call('(111.111.111.111) Setting pjlink_class for this projector to "1"')] - log_error_calls = [call('(111.111.111.111) NAN CLSS version reply "Z" - defaulting to class "1"')] - # GIVEN: Test object with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + log_debug_calls = [call('({ip}) reset_information() connect status is ' + '{state}'.format(ip=pjlink.name, state=STATUS_CODE[S_NOT_CONNECTED])), + call('({ip}) Setting pjlink_class for this projector to "1"'.format(ip=pjlink.name))] + log_error_calls = [call('({ip}) NAN CLSS version reply "Z" - ' + 'defaulting to class "1"'.format(ip=pjlink.name))] # WHEN: Process invalid reply pjlink.process_clss('Z') @@ -623,15 +236,14 @@ class TestPJLinkCommands(TestCase): """ Test CLSS reply has no class number """ - log_debug_calls = [call('(111.111.111.111) reset_information() connect status is ' - '{state}'.format(state=STATUS_CODE[S_NOT_CONNECTED])), - call('(111.111.111.111) Setting pjlink_class for this projector to "1"')] - log_error_calls = [call('(111.111.111.111) No numbers found in class version reply "Invalid" ' - '- defaulting to class "1"')] - # GIVEN: Test object with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + log_debug_calls = [call('({ip}) reset_information() connect status is ' + '{state}'.format(ip=pjlink.name, state=STATUS_CODE[S_NOT_CONNECTED])), + call('({ip}) Setting pjlink_class for this projector to "1"'.format(ip=pjlink.name))] + log_error_calls = [call('({ip}) No numbers found in class version reply "Invalid" ' + '- defaulting to class "1"'.format(ip=pjlink.name))] # WHEN: Process invalid reply pjlink.process_clss('Invalid') @@ -641,6 +253,32 @@ class TestPJLinkCommands(TestCase): mock_log.error.assert_has_calls(log_error_calls) mock_log.debug.assert_has_calls(log_debug_calls) + def test_projector_process_clss_nonstandard_reply_1(self): + """ + Test CLSS request returns non-standard reply 1 + """ + # GIVEN: Test object + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + + # WHEN: Process non-standard reply + pjlink.process_clss('Class 1') + + # THEN: Projector class should be set with proper value + assert '1' == pjlink.pjlink_class, 'Non-standard class reply should have set class=1' + + def test_projector_process_clss_nonstandard_reply_2(self): + """ + Test CLSS request returns non-standard reply 2 + """ + # GIVEN: Test object + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + + # WHEN: Process non-standard reply + pjlink.process_clss('Version2') + + # THEN: Projector class should be set with proper value + assert '2' == pjlink.pjlink_class, 'Non-standard class reply should have set class=2' + def test_projector_process_erst_all_ok(self): """ Test to verify pjlink.projector_errors is set to None when no errors @@ -660,16 +298,15 @@ class TestPJLinkCommands(TestCase): """ Test test_projector_process_erst_data_invalid_length """ - chk_data = '0' * (PJLINK_ERST_DATA['DATA_LENGTH'] + 1) - log_debug_calls = [call('(111.111.111.111) reset_information() connect status is ' - '{state}'.format(state=STATUS_CODE[S_NOT_CONNECTED]))] - log_warn_calls = [call('111.111.111.111) Invalid error status response "0000000": ' - 'length != {chk}'.format(chk=PJLINK_ERST_DATA['DATA_LENGTH']))] - # GIVEN: Test object with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.projector_errors = None + chk_data = '0' * (PJLINK_ERST_DATA['DATA_LENGTH'] + 1) + log_debug_calls = [call('({ip}) reset_information() connect status is ' + '{state}'.format(ip=pjlink.name, state=STATUS_CODE[S_NOT_CONNECTED]))] + log_warn_calls = [call('({ip}) Invalid error status response "0000000": ' + 'length != {chk}'.format(ip=pjlink.name, chk=PJLINK_ERST_DATA['DATA_LENGTH']))] # WHEN: process_erst called with invalid data (too many values pjlink.process_erst(chk_data) @@ -683,15 +320,14 @@ class TestPJLinkCommands(TestCase): """ Test test_projector_process_erst_data_invalid_nan """ - chk_data = 'Z' + ('0' * (PJLINK_ERST_DATA['DATA_LENGTH'] - 1)) - log_debug_calls = [call('(111.111.111.111) reset_information() connect status is ' - '{state}'.format(state=STATUS_CODE[S_NOT_CONNECTED]))] - log_warn_calls = [call('(111.111.111.111) Invalid error status response "Z00000"')] - # GIVEN: Test object with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.projector_errors = None + chk_data = 'Z' + ('0' * (PJLINK_ERST_DATA['DATA_LENGTH'] - 1)) + log_debug_calls = [call('({ip}) reset_information() connect status is ' + '{state}'.format(ip=pjlink.name, state=STATUS_CODE[S_NOT_CONNECTED]))] + log_warn_calls = [call('({ip}) Invalid error status response "Z00000"'.format(ip=pjlink.name))] # WHEN: process_erst called with invalid data (too many values pjlink.process_erst(chk_data) @@ -784,14 +420,14 @@ class TestPJLinkCommands(TestCase): """ Test input source status shows current input """ - log_debug_calls = [call('(111.111.111.111) reset_information() connect status is S_NOT_CONNECTED')] - chk_source_available = ['11', '12', '21', '22', '31', '32'] - # GIVEN: Test object with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - pjlink.source_available = chk_source_available pjlink.source = '11' + log_debug_calls = [call('({ip}) reset_information() connect status is ' + 'S_NOT_CONNECTED'.format(ip=pjlink.name))] + chk_source_available = ['11', '12', '21', '22', '31', '32'] + pjlink.source_available = chk_source_available # WHEN: Called with input source pjlink.process_inpt('21') @@ -826,15 +462,14 @@ class TestPJLinkCommands(TestCase): """ Test saving video source available information """ - log_debug_calls = [call('(111.111.111.111) Setting projector sources_available to ' - '"[\'11\', \'12\', \'21\', \'22\', \'31\', \'32\']"')] - chk_data = '21 12 11 22 32 31' # Although they should already be sorted, use unsorted to verify method - chk_test = ['11', '12', '21', '22', '31', '32'] - # GIVEN: Test object with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.source_available = [] + log_debug_calls = [call('({ip}) Setting projector sources_available to ' + '"[\'11\', \'12\', \'21\', \'22\', \'31\', \'32\']"'.format(ip=pjlink.name))] + chk_data = '21 12 11 22 32 31' # Although they should already be sorted, use unsorted to verify method + chk_test = ['11', '12', '21', '22', '31', '32'] # WHEN: process_inst called with test data pjlink.process_inst(data=chk_data) @@ -847,13 +482,12 @@ class TestPJLinkCommands(TestCase): """ Test status multiple lamp on/off and hours """ - log_data = [call('(111.111.111.111) process_lamp(): Invalid data "11111 1 22222 0 333A3 1"')] - # GIVEN: Test object with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.lamp = [{'Hours': 00000, 'On': True}, {'Hours': 11111, 'On': False}] + log_data = [call('({ip}) process_lamp(): Invalid data "11111 1 22222 0 333A3 1"'.format(ip=pjlink.name))] # WHEN: Call process_command with invalid lamp data pjlink.process_lamp('11111 1 22222 0 333A3 1') @@ -903,17 +537,32 @@ class TestPJLinkCommands(TestCase): assert pjlink.lamp[0]['On'] is True, 'Lamp power status should have been set to TRUE' assert 22222 == pjlink.lamp[0]['Hours'], 'Lamp hours should have been set to 22222' + def test_projector_process_lamp_single_hours_only(self): + """ + Test process lamp with 1 lamp reply hours only and no on/off status + """ + # GIVEN: Test object + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + pjlink.lamp = [] + + # WHEN: Process lamp command called with only hours and no lamp power state + pjlink.process_lamp("45") + + # THEN: Lamp should show hours as 45 and lamp power as Unavailable + assert 1 == len(pjlink.lamp), 'There should only be 1 lamp available' + assert 45 == pjlink.lamp[0]['Hours'], 'Lamp hours should have equalled 45' + assert pjlink.lamp[0]['On'] is None, 'Lamp power should be "None"' + def test_projector_process_name(self): """ Test saving NAME data from projector """ - chk_data = "Some Name the End-User Set IN Projector" - log_debug_calls = [call('(111.111.111.111) Setting projector PJLink name to ' - '"Some Name the End-User Set IN Projector"')] - # GIVEN: Test object with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + chk_data = "Some Name the End-User Set IN Projector" + log_debug_calls = [call('({ip}) Setting projector PJLink name to ' + '"Some Name the End-User Set IN Projector"'.format(ip=pjlink.name))] # WHEN: process_name called with test data pjlink.process_name(data=chk_data) @@ -947,8 +596,6 @@ class TestPJLinkCommands(TestCase): """ Test process_powr invalid call """ - log_warn_calls = [call('(111.111.111.111) Unknown power response: "99"')] - # GIVEN: Test object with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log, \ patch.object(openlp.core.projectors.pjlink.PJLink, 'send_command') as mock_send_command, \ @@ -957,6 +604,7 @@ class TestPJLinkCommands(TestCase): pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.power = S_STANDBY + log_warn_calls = [call('({ip}) Unknown power response: "99"'.format(ip=pjlink.name))] # WHEN: process_name called with test data pjlink.process_powr(data='99') @@ -1009,16 +657,15 @@ class TestPJLinkCommands(TestCase): """ Test saving filter type previously saved """ - filter_model = 'Filter Type Test' - log_warn_calls = [call('(111.111.111.111) Filter model already set'), - call('(111.111.111.111) Saved model: "Old filter type"'), - call('(111.111.111.111) New model: "Filter Type Test"')] - # GIVEN: Test object with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.model_filter = 'Old filter type' + filter_model = 'Filter Type Test' + log_warn_calls = [call('({ip}) Filter model already set'.format(ip=pjlink.name)), + call('({ip}) Saved model: "Old filter type"'.format(ip=pjlink.name)), + call('({ip}) New model: "Filter Type Test"'.format(ip=pjlink.name))] # WHEN: Filter model is received pjlink.process_rfil(data=filter_model) @@ -1047,16 +694,15 @@ class TestPJLinkCommands(TestCase): """ Test saving lamp type previously saved """ - lamp_model = 'Lamp Type Test' - log_warn_calls = [call('(111.111.111.111) Lamp model already set'), - call('(111.111.111.111) Saved lamp: "Old lamp type"'), - call('(111.111.111.111) New lamp: "Lamp Type Test"')] - # GIVEN: Test object with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.model_lamp = 'Old lamp type' + lamp_model = 'Lamp Type Test' + log_warn_calls = [call('({ip}) Lamp model already set'.format(ip=pjlink.name)), + call('({ip}) Saved lamp: "Old lamp type"'.format(ip=pjlink.name)), + call('({ip}) New lamp: "Lamp Type Test"'.format(ip=pjlink.name))] # WHEN: Filter model is received pjlink.process_rlmp(data=lamp_model) @@ -1069,14 +715,14 @@ class TestPJLinkCommands(TestCase): """ Test saving serial number from projector """ - log_debug_calls = [call('(111.111.111.111) Setting projector serial number to "Test Serial Number"')] - test_number = 'Test Serial Number' - # GIVEN: Test object with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.serial_no = None + log_debug_calls = [call('({ip}) Setting projector serial number to ' + '"Test Serial Number"'.format(ip=pjlink.name))] + test_number = 'Test Serial Number' # WHEN: No serial number is set and we receive serial number command pjlink.process_snum(data=test_number) @@ -1089,16 +735,16 @@ class TestPJLinkCommands(TestCase): """ Test projector serial number different than saved serial number """ - log_warn_calls = [call('(111.111.111.111) Projector serial number does not match saved serial number'), - call('(111.111.111.111) Saved: "Previous serial number"'), - call('(111.111.111.111) Received: "Test Serial Number"'), - call('(111.111.111.111) NOT saving serial number')] - test_number = 'Test Serial Number' - # GIVEN: Test object with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.serial_no = 'Previous serial number' + log_warn_calls = [call('({ip}) Projector serial number does not match ' + 'saved serial number'.format(ip=pjlink.name)), + call('({ip}) Saved: "Previous serial number"'.format(ip=pjlink.name)), + call('({ip}) Received: "Test Serial Number"'.format(ip=pjlink.name)), + call('({ip}) NOT saving serial number'.format(ip=pjlink.name))] + test_number = 'Test Serial Number' # WHEN: No serial number is set and we receive serial number command pjlink.process_snum(data=test_number) @@ -1111,14 +757,14 @@ class TestPJLinkCommands(TestCase): """ Test invalid software version information - too long """ - test_data = 'Test 1 Subtest 1' - log_debug_calls = [call('(111.111.111.111) Setting projector software version to "Test 1 Subtest 1"')] - # GIVEN: Test object with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.sw_version = None pjlink.sw_version_received = None + test_data = 'Test 1 Subtest 1' + log_debug_calls = [call('({ip}) Setting projector software version to ' + '"Test 1 Subtest 1"'.format(ip=pjlink.name))] # WHEN: process_sver called with invalid data pjlink.process_sver(data=test_data) @@ -1131,16 +777,16 @@ class TestPJLinkCommands(TestCase): """ Test invalid software version information - Received different than saved """ - test_data_old = 'Test 1 Subtest 1' - test_data_new = 'Test 1 Subtest 2' - log_warn_calls = [call('(111.111.111.111) Projector software version does not match saved software version'), - call('(111.111.111.111) Saved: "Test 1 Subtest 1"'), - call('(111.111.111.111) Received: "Test 1 Subtest 2"'), - call('(111.111.111.111) Updating software version')] - # GIVEN: Test object with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log: pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + test_data_old = 'Test 1 Subtest 1' + test_data_new = 'Test 1 Subtest 2' + log_warn_calls = [call('({ip}) Projector software version does not match ' + 'saved software version'.format(ip=pjlink.name)), + call('({ip}) Saved: "Test 1 Subtest 1"'.format(ip=pjlink.name)), + call('({ip}) Received: "Test 1 Subtest 2"'.format(ip=pjlink.name)), + call('({ip}) Updating software version'.format(ip=pjlink.name))] pjlink.sw_version = test_data_old # WHEN: process_sver called with invalid data @@ -1169,51 +815,3 @@ class TestPJLinkCommands(TestCase): assert pjlink.sw_version is None, 'Software version should not have changed' assert pjlink.sw_version_received is None, 'Received software version should not have changed' mock_log.warning.assert_has_calls(log_warn_calls) - - def test_projector_reset_information(self): - """ - Test reset_information() resets all information and stops timers - """ - log_debug_calls = [call('(111.111.111.111): Calling timer.stop()'), - call('(111.111.111.111): Calling socket_timer.stop()')] - - # GIVEN: Test object - with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log: - pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) - # timer and socket_timer not available until instantiation, so mock here - with patch.object(pjlink, 'socket_timer') as mock_socket_timer, \ - patch.object(pjlink, 'timer') as mock_timer: - - pjlink.power = S_ON - pjlink.pjlink_name = 'OPENLPTEST' - pjlink.manufacturer = 'PJLINK' - pjlink.model = '1' - pjlink.shutter = True - pjlink.mute = True - pjlink.lamp = True - pjlink.fan = True - pjlink.source_available = True - pjlink.other_info = 'ANOTHER TEST' - pjlink.send_queue = True - pjlink.send_busy = True - - # WHEN: reset_information() is called - pjlink.reset_information() - - # THEN: All information should be reset and timers stopped - assert pjlink.power == S_OFF, 'Projector power should be OFF' - assert pjlink.pjlink_name is None, 'Projector pjlink_name should be None' - assert pjlink.manufacturer is None, 'Projector manufacturer should be None' - assert pjlink.model is None, 'Projector model should be None' - assert pjlink.shutter is None, 'Projector shutter should be None' - assert pjlink.mute is None, 'Projector shuttter should be None' - assert pjlink.lamp is None, 'Projector lamp should be None' - assert pjlink.fan is None, 'Projector fan should be None' - assert pjlink.source_available is None, 'Projector source_available should be None' - assert pjlink.source is None, 'Projector source should be None' - assert pjlink.other_info is None, 'Projector other_info should be None' - assert pjlink.send_queue == [], 'Projector send_queue should be an empty list' - assert pjlink.send_busy is False, 'Projector send_busy should be False' - assert mock_timer.stop.called is True, 'Projector timer.stop() should have been called' - assert mock_socket_timer.stop.called is True, 'Projector socket_timer.stop() should have been called' - mock_log.debug.assert_has_calls(log_debug_calls) diff --git a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py index c9ccc2543..132a43df1 100644 --- a/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py +++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands_02.py @@ -26,7 +26,7 @@ from unittest import TestCase from unittest.mock import patch, call import openlp.core.projectors.pjlink -from openlp.core.projectors.constants import S_CONNECTED +from openlp.core.projectors.constants import S_CONNECTED, S_OFF, S_ON from openlp.core.projectors.db import Projector from openlp.core.projectors.pjlink import PJLink from tests.resources.projector.data import TEST_HASH, TEST_PIN, TEST_SALT, TEST1_DATA @@ -36,17 +36,52 @@ class TestPJLinkCommands(TestCase): """ Tests for the PJLinkCommands class part 2 """ - def setUp(self): - ''' - TestPJLinkCommands part 2 initialization - ''' - self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) + def test_projector_reset_information(self): + """ + Test reset_information() resets all information and stops timers + """ + # GIVEN: Test object + with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log: + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + log_debug_calls = [call('({ip}): Calling poll_timer.stop()'.format(ip=pjlink.name)), + call('({ip}): Calling socket_timer.stop()'.format(ip=pjlink.name))] + # timer and socket_timer not available until instantiation, so mock here + with patch.object(pjlink, 'socket_timer') as mock_socket_timer, \ + patch.object(pjlink, 'poll_timer') as mock_timer: - def tearDown(self): - ''' - TestPJLinkCommands part 2 cleanups - ''' - self.pjlink_test = None + pjlink.power = S_ON + pjlink.pjlink_name = 'OPENLPTEST' + pjlink.manufacturer = 'PJLINK' + pjlink.model = '1' + pjlink.shutter = True + pjlink.mute = True + pjlink.lamp = True + pjlink.fan = True + pjlink.source_available = True + pjlink.other_info = 'ANOTHER TEST' + pjlink.send_queue = True + pjlink.send_busy = True + + # WHEN: reset_information() is called + pjlink.reset_information() + + # THEN: All information should be reset and timers stopped + assert pjlink.power == S_OFF, 'Projector power should be OFF' + assert pjlink.pjlink_name is None, 'Projector pjlink_name should be None' + assert pjlink.manufacturer is None, 'Projector manufacturer should be None' + assert pjlink.model is None, 'Projector model should be None' + assert pjlink.shutter is None, 'Projector shutter should be None' + assert pjlink.mute is None, 'Projector shuttter should be None' + assert pjlink.lamp is None, 'Projector lamp should be None' + assert pjlink.fan is None, 'Projector fan should be None' + assert pjlink.source_available is None, 'Projector source_available should be None' + assert pjlink.source is None, 'Projector source should be None' + assert pjlink.other_info is None, 'Projector other_info should be None' + assert pjlink.send_queue == [], 'Projector send_queue should be an empty list' + assert pjlink.send_busy is False, 'Projector send_busy should be False' + assert mock_timer.stop.called is True, 'Projector timer.stop() should have been called' + assert mock_socket_timer.stop.called is True, 'Projector socket_timer.stop() should have been called' + mock_log.debug.assert_has_calls(log_debug_calls) def test_process_pjlink_normal(self): """ @@ -54,13 +89,14 @@ class TestPJLinkCommands(TestCase): """ # GIVEN: Initial mocks and data mock_log = patch.object(openlp.core.projectors.pjlink, "log").start() - mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() - mock_send_command = patch.object(self.pjlink_test, 'send_command').start() - mock_readyRead = patch.object(self.pjlink_test, 'readyRead').start() - mock_change_status = patch.object(self.pjlink_test, 'change_status').start() - pjlink = self.pjlink_test + mock_disconnect_from_host = patch('openlp.core.projectors.pjlink.PJLink.disconnect_from_host').start() + mock_send_command = patch('openlp.core.projectors.pjlink.PJLink.send_command').start() + mock_readyRead = patch('openlp.core.projectors.pjlink.PJLink.readyRead').start() + mock_change_status = patch('openlp.core.projectors.pjlink.PJLink.change_status').start() + + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.pin = None - log_check = [call("({111.111.111.111}) process_pjlink(): Sending 'CLSS' initial command'"), ] + log_check = [call('({ip}) process_pjlink(): Sending "CLSS" initial command'.format(ip=pjlink.name)), ] # WHEN: process_pjlink called with no authentication required pjlink.process_pjlink(data="0") @@ -68,7 +104,7 @@ class TestPJLinkCommands(TestCase): # THEN: proper processing should have occured mock_log.debug.has_calls(log_check) mock_disconnect_from_host.assert_not_called() - self.assertEqual(mock_readyRead.connect.call_count, 1, 'Should have only been called once') + assert 1 == mock_readyRead.connect.call_count, 'Should have only been called once' mock_change_status.assert_called_once_with(S_CONNECTED) mock_send_command.assert_called_with(cmd='CLSS', priority=True, salt=None) @@ -78,13 +114,14 @@ class TestPJLinkCommands(TestCase): """ # GIVEN: Initial mocks and data mock_log = patch.object(openlp.core.projectors.pjlink, "log").start() - mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() - mock_send_command = patch.object(self.pjlink_test, 'send_command').start() - mock_readyRead = patch.object(self.pjlink_test, 'readyRead').start() - mock_change_status = patch.object(self.pjlink_test, 'change_status').start() - pjlink = self.pjlink_test + mock_disconnect_from_host = patch('openlp.core.projectors.pjlink.PJLink.disconnect_from_host').start() + mock_send_command = patch('openlp.core.projectors.pjlink.PJLink.send_command').start() + mock_readyRead = patch('openlp.core.projectors.pjlink.PJLink.readyRead').start() + mock_change_status = patch('openlp.core.projectors.pjlink.PJLink.change_status').start() + + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.pin = TEST_PIN - log_check = [call("({111.111.111.111}) process_pjlink(): Sending 'CLSS' initial command'"), ] + log_check = [call('({ip}) process_pjlink(): Sending "CLSS" initial command'.format(ip=pjlink.name)), ] # WHEN: process_pjlink called with no authentication required pjlink.process_pjlink(data='1 {salt}'.format(salt=TEST_SALT)) @@ -92,7 +129,7 @@ class TestPJLinkCommands(TestCase): # THEN: proper processing should have occured mock_log.debug.has_calls(log_check) mock_disconnect_from_host.assert_not_called() - self.assertEqual(mock_readyRead.connect.call_count, 1, 'Should have only been called once') + assert 1 == mock_readyRead.connect.call_count, 'Should have only been called once' mock_change_status.assert_called_once_with(S_CONNECTED) mock_send_command.assert_called_with(cmd='CLSS', priority=True, salt=TEST_HASH) @@ -101,20 +138,20 @@ class TestPJLinkCommands(TestCase): Test process_pjlinnk called with no authentication but pin is set """ # GIVEN: Initial mocks and data - # GIVEN: Initial mocks and data mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() - mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() - mock_send_command = patch.object(self.pjlink_test, 'send_command').start() - pjlink = self.pjlink_test + mock_disconnect_from_host = patch('openlp.core.projectors.pjlink.PJLink.disconnect_from_host').start() + mock_send_command = patch('openlp.core.projectors.pjlink.PJLink.send_command').start() + + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.pin = TEST_PIN - log_check = [call('(111.111.111.111) Normal connection but PIN set - aborting'), ] + log_check = [call('({ip}) Normal connection but PIN set - aborting'.format(ip=pjlink.name)), ] # WHEN: process_pjlink called with invalid authentication scheme pjlink.process_pjlink(data='0') # THEN: Proper calls should be made mock_log.error.assert_has_calls(log_check) - self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') + assert 1 == mock_disconnect_from_host.call_count, 'Should have only been called once' mock_send_command.assert_not_called() def test_process_pjlink_normal_with_salt_error(self): @@ -122,20 +159,20 @@ class TestPJLinkCommands(TestCase): Test process_pjlinnk called with no authentication but pin is set """ # GIVEN: Initial mocks and data - # GIVEN: Initial mocks and data mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() - mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() - mock_send_command = patch.object(self.pjlink_test, 'send_command').start() - pjlink = self.pjlink_test + mock_disconnect_from_host = patch('openlp.core.projectors.pjlink.PJLink.disconnect_from_host').start() + mock_send_command = patch('openlp.core.projectors.pjlink.PJLink.send_command').start() + + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.pin = TEST_PIN - log_check = [call('(111.111.111.111) Normal connection with extra information - aborting'), ] + log_check = [call('({ip}) Normal connection with extra information - aborting'.format(ip=pjlink.name)), ] # WHEN: process_pjlink called with invalid authentication scheme pjlink.process_pjlink(data='0 {salt}'.format(salt=TEST_SALT)) # THEN: Proper calls should be made mock_log.error.assert_has_calls(log_check) - self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') + assert 1 == mock_disconnect_from_host.call_count, 'Should have only been called once' mock_send_command.assert_not_called() def test_process_pjlink_invalid_authentication_scheme_length_error(self): @@ -144,17 +181,18 @@ class TestPJLinkCommands(TestCase): """ # GIVEN: Initial mocks and data mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() - mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() - mock_send_command = patch.object(self.pjlink_test, 'send_command').start() - pjlink = self.pjlink_test - log_check = [call('(111.111.111.111) Invalid initial authentication scheme - aborting'), ] + mock_disconnect_from_host = patch('openlp.core.projectors.pjlink.PJLink.disconnect_from_host').start() + mock_send_command = patch('openlp.core.projectors.pjlink.PJLink.send_command').start() + + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + log_check = [call('({ip}) Invalid initial authentication scheme - aborting'.format(ip=pjlink.name)), ] # WHEN: process_pjlink called with invalid authentication scheme pjlink.process_pjlink(data='01') # THEN: socket should be closed and invalid data logged mock_log.error.assert_has_calls(log_check) - self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') + assert 1 == mock_disconnect_from_host.call_count, 'Should have only been called once' mock_send_command.assert_not_called() def test_process_pjlink_invalid_authentication_data_length_error(self): @@ -163,17 +201,18 @@ class TestPJLinkCommands(TestCase): """ # GIVEN: Initial mocks and data mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() - mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() - mock_send_command = patch.object(self.pjlink_test, 'send_command').start() - log_check = [call('(111.111.111.111) Authenticated connection but not enough info - aborting'), ] - pjlink = self.pjlink_test + mock_disconnect_from_host = patch('openlp.core.projectors.pjlink.PJLink.disconnect_from_host').start() + mock_send_command = patch('openlp.core.projectors.pjlink.PJLink.send_command').start() + + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) + log_check = [call('({ip}) Authenticated connection but not enough info - aborting'.format(ip=pjlink.name)), ] # WHEN: process_pjlink called with no salt pjlink.process_pjlink(data='1') # THEN: socket should be closed and invalid data logged mock_log.error.assert_has_calls(log_check) - self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') + assert 1 == mock_disconnect_from_host.call_count, 'Should have only been called once' mock_send_command.assert_not_called() def test_process_pjlink_authenticate_pin_not_set_error(self): @@ -182,16 +221,17 @@ class TestPJLinkCommands(TestCase): """ # GIVEN: Initial mocks and data mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start() - mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start() - mock_send_command = patch.object(self.pjlink_test, 'send_command').start() - log_check = [call('(111.111.111.111) Authenticate connection but no PIN - aborting'), ] - pjlink = self.pjlink_test + mock_disconnect_from_host = patch('openlp.core.projectors.pjlink.PJLink.disconnect_from_host').start() + mock_send_command = patch('openlp.core.projectors.pjlink.PJLink.send_command').start() + + pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink.pin = None + log_check = [call('({ip}) Authenticate connection but no PIN - aborting'.format(ip=pjlink.name)), ] # WHEN: process_pjlink called with no salt pjlink.process_pjlink(data='1 {salt}'.format(salt=TEST_SALT)) # THEN: socket should be closed and invalid data logged mock_log.error.assert_has_calls(log_check) - self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once') + assert 1 == mock_disconnect_from_host.call_count, 'Should have only been called once' mock_send_command.assert_not_called() From 9e184ebbfdcf8d0441df3c6c7f714f79a4d061c6 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Fri, 12 Jan 2018 22:59:44 -0700 Subject: [PATCH 6/7] Go back to the way I was doing it originally. Much more succint. --- openlp/core/threading.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openlp/core/threading.py b/openlp/core/threading.py index 7af78cbf8..626bbb93e 100644 --- a/openlp/core/threading.py +++ b/openlp/core/threading.py @@ -91,11 +91,8 @@ def is_thread_finished(thread_name): :param str thread_name: The name of the thread :returns: True if the thread is finished, False if it is still running """ - thread_info = Registry().get('application').worker_threads.get(thread_name) - if not thread_info: - # If the thread doesnt exist anymore, it's probably because it is finished - return True - return thread_info['thread'].isFinished() + app = Registry().get('application') + return thread_name not in app.worker_threads or app.worker_threads[thread_name]['thread'].isFinished() def make_remove_thread(thread_name): From 372051067de2b63dc314b718489d5bc18fdca87a Mon Sep 17 00:00:00 2001 From: Ken Roberts Date: Fri, 12 Jan 2018 23:17:15 -0800 Subject: [PATCH 7/7] Update pjlink_class define in db --- openlp/core/projectors/db.py | 4 ++-- openlp/core/projectors/upgrade.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openlp/core/projectors/db.py b/openlp/core/projectors/db.py index db8f4ac4b..f9297fae9 100644 --- a/openlp/core/projectors/db.py +++ b/openlp/core/projectors/db.py @@ -152,7 +152,7 @@ class Projector(Base, CommonMixin): location: Column(String(30)) notes: Column(String(200)) pjlink_name: Column(String(128)) # From projector - pjlink_class Column(String(1)) # From projector + pjlink_class Column(String(5)) # From projector manufacturer: Column(String(128)) # From projector model: Column(String(128)) # From projector other: Column(String(128)) # From projector @@ -199,7 +199,7 @@ class Projector(Base, CommonMixin): location = Column(String(30)) notes = Column(String(200)) pjlink_name = Column(String(128)) - pjlink_class = Column(String(1)) + pjlink_class = Column(String(5)) manufacturer = Column(String(128)) model = Column(String(128)) other = Column(String(128)) diff --git a/openlp/core/projectors/upgrade.py b/openlp/core/projectors/upgrade.py index b02667638..d3837eda0 100644 --- a/openlp/core/projectors/upgrade.py +++ b/openlp/core/projectors/upgrade.py @@ -89,5 +89,5 @@ def upgrade_3(session, metadata): upgrade_db = 'pjlink_class' not in [col.name for col in projector_table.c.values()] if upgrade_db: new_op = get_upgrade_op(session) - new_op.add_column('projector', Column('pjlink_class', types.String(1), server_default=null())) + new_op.add_column('projector', Column('pjlink_class', types.String(5), server_default=null())) log.debug('{status} projector DB upgrade to version 3'.format(status='Updated' if upgrade_db else 'Skipping'))