From 5fe84e41305ade6f32fe2dde6b392be0c8c6e68c Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sat, 5 Nov 2016 14:17:36 +0200 Subject: [PATCH] More tests --- .../presentations/lib/libreofficeserver.py | 96 ++-- .../presentations/test_libreofficeserver.py | 471 +++++++++++++++++- 2 files changed, 527 insertions(+), 40 deletions(-) diff --git a/openlp/plugins/presentations/lib/libreofficeserver.py b/openlp/plugins/presentations/lib/libreofficeserver.py index 55359d8ca..e2017a23b 100644 --- a/openlp/plugins/presentations/lib/libreofficeserver.py +++ b/openlp/plugins/presentations/lib/libreofficeserver.py @@ -1,3 +1,24 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2016 OpenLP Developers # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ This module runs a Pyro4 server using LibreOffice's version of Python """ @@ -54,6 +75,41 @@ class LibreOfficeServer(object): self._document = None self._presentation = None self._process = None + self._manager = None + + def _create_property(self, name, value): + """ + Create an OOo style property object which are passed into some Uno methods. + """ + log.debug('create property') + property_object = PropertyValue() + property_object.Name = name + property_object.Value = value + return property_object + + def _get_text_from_page(self, slide_no, text_type=TextType.SlideText): + """ + Return any text extracted from the presentation page. + + :param slide_no: The slide the notes are required for, starting at 1 + :param notes: A boolean. If set the method searches the notes of the slide. + :param text_type: A TextType. Enumeration of the types of supported text. + """ + text = '' + if TextType.Title <= text_type <= TextType.Notes: + pages = self._document.getDrawPages() + if 0 < slide_no <= pages.getCount(): + page = pages.getByIndex(slide_no - 1) + if text_type == TextType.Notes: + page = page.getNotesPage() + for index in range(page.getCount()): + shape = page.getByIndex(index) + shape_type = shape.getShapeType() + if shape.supportsService('com.sun.star.drawing.Text'): + # if they requested title, make sure it is the title + if text_type != TextType.Title or shape_type == 'com.sun.star.presentation.TitleTextShape': + text += shape.getString() + '\n' + return text def start_process(self): """ @@ -93,40 +149,6 @@ class LibreOfficeServer(object): except Exception as e: log.warning('Failed to get UNO desktop') - def _create_property(self, name, value): - """ - Create an OOo style property object which are passed into some Uno methods. - """ - log.debug('create property') - property_object = PropertyValue() - property_object.Name = name - property_object.Value = value - return property_object - - def _get_text_from_page(self, slide_no, text_type=TextType.SlideText): - """ - Return any text extracted from the presentation page. - - :param slide_no: The slide the notes are required for, starting at 1 - :param notes: A boolean. If set the method searches the notes of the slide. - :param text_type: A TextType. Enumeration of the types of supported text. - """ - text = '' - if TextType.Title <= text_type <= TextType.Notes: - pages = self._document.getDrawPages() - if 0 < slide_no <= pages.getCount(): - page = pages.getByIndex(slide_no - 1) - if text_type == TextType.Notes: - page = page.getNotesPage() - for index in range(page.getCount()): - shape = page.getByIndex(index) - shape_type = shape.getShapeType() - if shape.supportsService('com.sun.star.drawing.Text'): - # if they requested title, make sure it is the title - if text_type != TextType.Title or shape_type == 'com.sun.star.presentation.TitleTextShape': - text += shape.getString() + '\n' - return text - def has_desktop(self): """ Say if we have a desktop object @@ -206,8 +228,7 @@ class LibreOfficeServer(object): def get_titles_and_notes(self): """ - Writes the list of titles (one per slide) to 'titles.txt' and the notes to 'slideNotes[x].txt' - in the thumbnails directory + Extract the titles and the notes from the slides. """ titles = [] notes = [] @@ -222,8 +243,7 @@ class LibreOfficeServer(object): def close_presentation(self): """ - Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being - shutdown. + Close presentation and clean up objects. """ log.debug('close Presentation LibreOffice') if self._document: diff --git a/tests/functional/openlp_plugins/presentations/test_libreofficeserver.py b/tests/functional/openlp_plugins/presentations/test_libreofficeserver.py index b0ba05826..2f59639dc 100644 --- a/tests/functional/openlp_plugins/presentations/test_libreofficeserver.py +++ b/tests/functional/openlp_plugins/presentations/test_libreofficeserver.py @@ -22,8 +22,6 @@ """ Functional tests to test the LibreOffice Pyro server """ -from unittest import TestCase - from openlp.plugins.presentations.lib.libreofficeserver import LibreOfficeServer, TextType from tests.functional import MagicMock, patch, call @@ -44,6 +42,7 @@ def test_constructor(): assert server._presentation is None assert server._process is None + @patch('openlp.plugins.presentations.lib.libreofficeserver.Popen') def test_start_process(MockedPopen): """ @@ -69,6 +68,23 @@ def test_start_process(MockedPopen): ]) assert server._process is mocked_process + +@patch('openlp.plugins.presentations.lib.libreofficeserver.uno') +def test_setup_desktop_already_has_desktop(mocked_uno): + """ + Test that setup_desktop() exits early when there's already a desktop + """ + # GIVEN: A LibreOfficeServer instance + server = LibreOfficeServer() + server._desktop = MagicMock() + + # WHEN: setup_desktop() is called + server.setup_desktop() + + # THEN: setup_desktop() exits early + assert server._manager is None + + @patch('openlp.plugins.presentations.lib.libreofficeserver.uno') def test_setup_desktop(mocked_uno): """ @@ -104,6 +120,7 @@ def test_setup_desktop(mocked_uno): assert server._manager is MockedServiceManager assert server._desktop is mocked_desktop + @patch('openlp.plugins.presentations.lib.libreofficeserver.PropertyValue') def test_create_property(MockedPropertyValue): """ @@ -121,6 +138,7 @@ def test_create_property(MockedPropertyValue): assert prop.Name == name assert prop.Value == value + def test_get_text_from_page_slide_text(): """ Test that the _get_text_from_page() method gives us nothing for slide text @@ -147,6 +165,7 @@ def test_get_text_from_page_slide_text(): # THE: The text is correct assert text == 'Page Text\n' + def test_get_text_from_page_title(): """ Test that the _get_text_from_page() method gives us the text from the titles @@ -173,6 +192,7 @@ def test_get_text_from_page_title(): # THEN: The text should be correct assert text == 'Page Title\n' + def test_get_text_from_page_notes(): """ Test that the _get_text_from_page() method gives us the text from the notes @@ -201,6 +221,7 @@ def test_get_text_from_page_notes(): # THEN: The text should be correct assert text == 'Page Notes\n' + def test_has_desktop_no_desktop(): """ Test the has_desktop() method when there's no desktop @@ -214,6 +235,7 @@ def test_has_desktop_no_desktop(): # THEN: The result should be False assert result is False + def test_has_desktop(): """ Test the has_desktop() method @@ -228,6 +250,7 @@ def test_has_desktop(): # THEN: The result should be True assert result is True + def test_shutdown(): """ Test the shutdown method @@ -266,6 +289,7 @@ def test_shutdown(): mocked_desktop.terminate.assert_called_once_with() server._process.kill.assert_called_once_with() + @patch('openlp.plugins.presentations.lib.libreofficeserver.uno') def test_load_presentation(mocked_uno): """ @@ -300,6 +324,7 @@ def test_load_presentation(mocked_uno): assert server._presentation.Display == screen_number assert server._control is None + @patch('openlp.plugins.presentations.lib.libreofficeserver.uno') @patch('openlp.plugins.presentations.lib.libreofficeserver.os') def test_extract_thumbnails(mocked_os, mocked_uno): @@ -340,3 +365,445 @@ def test_extract_thumbnails(mocked_os, mocked_uno): [call('/tmp/1.png', ({'FilterName': 'impress_png_Export'},)), call('/tmp/2.png', ({'FilterName': 'impress_png_Export'},))] assert thumbnails == ['/tmp/1.png', '/tmp/2.png'] + + +def test_get_titles_and_notes(): + """ + Test the get_titles_and_notes() method + """ + # GIVEN: A LibreOfficeServer object and a bunch of mocks + server = LibreOfficeServer() + mocked_document = MagicMock() + mocked_pages = MagicMock() + server._document = mocked_document + mocked_document.getDrawPages.return_value = mocked_pages + mocked_pages.getCount.return_value = 2 + + # WHEN: get_titles_and_notes() is called + with patch.object(server, '_get_text_from_page') as mocked_get_text_from_page: + mocked_get_text_from_page.side_effect = [ + 'OpenLP on Mac OS X', + '', + '', + 'Installing is a drag-and-drop affair' + ] + titles, notes = server.get_titles_and_notes() + + # THEN: The right calls are made and the right stuff returned + mocked_document.getDrawPages.assert_called_once_with() + mocked_pages.getCount.assert_called_once_with() + assert mocked_get_text_from_page.call_count == 4 + expected_calls = [ + call(1, TextType.Title), call(1, TextType.Notes), + call(2, TextType.Title), call(2, TextType.Notes), + ] + assert mocked_get_text_from_page.call_args_list == expected_calls + assert titles == ['OpenLP on Mac OS X\n', '\n'], titles + assert notes == [' ', 'Installing is a drag-and-drop affair'], notes + + +def test_close_presentation(): + """ + Test that closing the presentation cleans things up correctly + """ + # GIVEN: A LibreOfficeServer instance and a bunch of mocks + server = LibreOfficeServer() + mocked_document = MagicMock() + mocked_presentation = MagicMock() + server._document = mocked_document + server._presentation = mocked_presentation + + # WHEN: close_presentation() is called + server.close_presentation() + + # THEN: The presentation and document should be closed + mocked_presentation.end.assert_called_once_with() + mocked_document.dispose.assert_called_once_with() + assert server._document is None + assert server._presentation is None + + +def test_is_loaded_no_objects(): + """ + Test the is_loaded() method when there's no document or presentation + """ + # GIVEN: A LibreOfficeServer instance and a bunch of mocks + server = LibreOfficeServer() + + # WHEN: The is_loaded() method is called + result = server.is_loaded() + + # THEN: The result should be false + assert result is False + + +def test_is_loaded_no_presentation(): + """ + Test the is_loaded() method when there's no presentation + """ + # GIVEN: A LibreOfficeServer instance and a bunch of mocks + server = LibreOfficeServer() + mocked_document = MagicMock() + server._document = mocked_document + server._presentation = MagicMock() + mocked_document.getPresentation.return_value = None + + # WHEN: The is_loaded() method is called + result = server.is_loaded() + + # THEN: The result should be false + assert result is False + mocked_document.getPresentation.assert_called_once_with() + + +def test_is_loaded(): + """ + Test the is_loaded() method + """ + # GIVEN: A LibreOfficeServer instance and a bunch of mocks + server = LibreOfficeServer() + mocked_document = MagicMock() + mocked_presentation = MagicMock() + server._document = mocked_document + server._presentation = mocked_presentation + mocked_document.getPresentation.return_value = mocked_presentation + + # WHEN: The is_loaded() method is called + result = server.is_loaded() + + # THEN: The result should be false + assert result is True + mocked_document.getPresentation.assert_called_once_with() + + +def test_is_active_not_loaded(): + """ + Test is_active() when is_loaded() returns False + """ + # GIVEN: A LibreOfficeServer instance and a bunch of mocks + server = LibreOfficeServer() + + # WHEN: is_active() is called with is_loaded() returns False + result = server.is_loaded() + + # THEN: It should have returned False + assert result is False + + +def test_is_active_no_control(): + """ + Test is_active() when is_loaded() returns True but there's no control + """ + # GIVEN: A LibreOfficeServer instance and a bunch of mocks + server = LibreOfficeServer() + + # WHEN: is_active() is called with is_loaded() returns False + with patch.object(server, 'is_loaded') as mocked_is_loaded: + mocked_is_loaded.return_value = True + result = server.is_active() + + # THEN: The result should be False + assert result is False + mocked_is_loaded.assert_called_once_with() + + +def test_is_active(): + """ + Test is_active() + """ + # GIVEN: A LibreOfficeServer instance and a bunch of mocks + server = LibreOfficeServer() + mocked_control = MagicMock() + server._control = mocked_control + mocked_control.isRunning.return_value = True + + # WHEN: is_active() is called with is_loaded() returns False + with patch.object(server, 'is_loaded') as mocked_is_loaded: + mocked_is_loaded.return_value = True + result = server.is_active() + + # THEN: The result should be False + assert result is True + mocked_is_loaded.assert_called_once_with() + mocked_control.isRunning.assert_called_once_with() + + +def test_unblank_screen(): + """ + Test the unblank_screen() method + """ + # GIVEN: A LibreOfficeServer instance and a bunch of mocks + server = LibreOfficeServer() + mocked_control = MagicMock() + server._control = mocked_control + + # WHEN: unblank_screen() is run + server.unblank_screen() + + # THEN: The resume method should have been called + mocked_control.resume.assert_called_once_with() + + +def test_blank_screen(): + """ + Test the blank_screen() method + """ + # GIVEN: A LibreOfficeServer instance and a bunch of mocks + server = LibreOfficeServer() + mocked_control = MagicMock() + server._control = mocked_control + + # WHEN: blank_screen() is run + server.blank_screen() + + # THEN: The resume method should have been called + mocked_control.blankScreen.assert_called_once_with(0) + + +def test_is_blank_no_control(): + """ + Test the is_blank() method when there's no control + """ + # GIVEN: A LibreOfficeServer instance and a bunch of mocks + server = LibreOfficeServer() + + # WHEN: is_blank() is called + result = server.is_blank() + + # THEN: It should have returned False + assert result is False + + +def test_is_blank_control_is_running(): + """ + Test the is_blank() method when the control is running + """ + # GIVEN: A LibreOfficeServer instance and a bunch of mocks + server = LibreOfficeServer() + mocked_control = MagicMock() + server._control = mocked_control + mocked_control.isRunning.return_value = True + mocked_control.isPaused.return_value = True + + # WHEN: is_blank() is called + result = server.is_blank() + + # THEN: It should have returned False + assert result is True + mocked_control.isRunning.assert_called_once_with() + mocked_control.isPaused.assert_called_once_with() + + +def test_stop_presentation(): + """ + Test the stop_presentation() method + """ + # GIVEN: A LibreOfficeServer instance and a mocked presentation + server = LibreOfficeServer() + mocked_presentation = MagicMock() + mocked_control = MagicMock() + server._presentation = mocked_presentation + server._control = mocked_control + + # WHEN: stop_presentation() is called + server.stop_presentation() + + # THEN: The presentation is ended and the control is removed + mocked_presentation.end.assert_called_once_with() + assert server._control is None + + +@patch('openlp.plugins.presentations.lib.libreofficeserver.time.sleep') +def test_start_presentation_no_control(mocked_sleep): + """ + Test the start_presentation() method when there's no control + """ + # GIVEN: A LibreOfficeServer instance and some mocks + server = LibreOfficeServer() + mocked_control = MagicMock() + mocked_document = MagicMock() + mocked_presentation = MagicMock() + mocked_controller = MagicMock() + mocked_frame = MagicMock() + mocked_window = MagicMock() + server._document = mocked_document + server._presentation = mocked_presentation + mocked_document.getCurrentController.return_value = mocked_controller + mocked_controller.getFrame.return_value = mocked_frame + mocked_frame.getContainerWindow.return_value = mocked_window + mocked_presentation.getController.side_effect = [None, mocked_control] + + # WHEN: start_presentation() is called + server.start_presentation() + + # THEN: The slide number should be correct + mocked_document.getCurrentController.assert_called_once_with() + mocked_controller.getFrame.assert_called_once_with() + mocked_frame.getContainerWindow.assert_called_once_with() + mocked_presentation.start.assert_called_once_with() + assert mocked_presentation.getController.call_count == 2 + mocked_sleep.assert_called_once_with(0.1) + assert mocked_window.setVisible.call_args_list == [call(True), call(False)] + assert server._control is mocked_control + + +def test_start_presentation(): + """ + Test the start_presentation() method when there's a control + """ + # GIVEN: A LibreOfficeServer instance and some mocks + server = LibreOfficeServer() + mocked_control = MagicMock() + server._control = mocked_control + + # WHEN: start_presentation() is called + with patch.object(server, 'goto_slide') as mocked_goto_slide: + server.start_presentation() + + # THEN: The control should have been activated and the first slide selected + mocked_control.activate.assert_called_once_with() + mocked_goto_slide.assert_called_once_with(1) + + +def test_get_slide_number(): + """ + Test the get_slide_number() method + """ + # GIVEN: A LibreOfficeServer instance and some mocks + server = LibreOfficeServer() + mocked_control = MagicMock() + mocked_control.getCurrentSlideIndex.return_value = 3 + server._control = mocked_control + + # WHEN: get_slide_number() is called + result = server.get_slide_number() + + # THEN: The slide number should be correct + assert result == 4 + + +def test_get_slide_count(): + """ + Test the get_slide_count() method + """ + # GIVEN: A LibreOfficeServer instance and some mocks + server = LibreOfficeServer() + mocked_document = MagicMock() + mocked_pages = MagicMock() + server._document = mocked_document + mocked_document.getDrawPages.return_value = mocked_pages + mocked_pages.getCount.return_value = 2 + + # WHEN: get_slide_count() is called + result = server.get_slide_count() + + # THEN: The slide count should be correct + assert result == 2 + + +def test_goto_slide(): + """ + Test the goto_slide() method + """ + # GIVEN: A LibreOfficeServer instance and some mocks + server = LibreOfficeServer() + mocked_control = MagicMock() + server._control = mocked_control + + # WHEN: goto_slide() is called + result = server.goto_slide(1) + + # THEN: The slide number should be correct + mocked_control.gotoSlideIndex.assert_called_once_with(0) + + +@patch('openlp.plugins.presentations.lib.libreofficeserver.time.sleep') +def test_next_step_when_paused(mocked_sleep): + """ + Test the next_step() method when paused + """ + # GIVEN: A LibreOfficeServer instance and a mocked control + server = LibreOfficeServer() + mocked_control = MagicMock() + server._control = mocked_control + mocked_control.isPaused.side_effect = [False, True] + + # WHEN: next_step() is called + result = server.next_step() + + # THEN: The correct call should be made + mocked_control.gotoNextEffect.assert_called_once_with() + mocked_sleep.assert_called_once_with(0.1) + assert mocked_control.isPaused.call_count == 2 + mocked_control.gotoPreviousEffect.assert_called_once_with() + + +@patch('openlp.plugins.presentations.lib.libreofficeserver.time.sleep') +def test_next_step(mocked_sleep): + """ + Test the next_step() method when paused + """ + # GIVEN: A LibreOfficeServer instance and a mocked control + server = LibreOfficeServer() + mocked_control = MagicMock() + server._control = mocked_control + mocked_control.isPaused.side_effect = [True, True] + + # WHEN: next_step() is called + result = server.next_step() + + # THEN: The correct call should be made + mocked_control.gotoNextEffect.assert_called_once_with() + mocked_sleep.assert_called_once_with(0.1) + assert mocked_control.isPaused.call_count == 1 + assert mocked_control.gotoPreviousEffect.call_count == 0 + + +def test_previous_step(): + """ + Test the previous_step() method + """ + # GIVEN: A LibreOfficeServer instance and a mocked control + server = LibreOfficeServer() + mocked_control = MagicMock() + server._control = mocked_control + + # WHEN: previous_step() is called + result = server.previous_step() + + # THEN: The correct call should be made + mocked_control.gotoPreviousEffect.assert_called_once_with() + + +def test_get_slide_text(): + """ + Test the get_slide_text() method + """ + # GIVEN: A LibreOfficeServer instance + server = LibreOfficeServer() + + # WHEN: get_slide_text() is called for a particular slide + with patch.object(server, '_get_text_from_page') as mocked_get_text_from_page: + mocked_get_text_from_page.return_value = 'OpenLP on Mac OS X' + result = server.get_slide_text(5) + + # THEN: The text should be returned + mocked_get_text_from_page.assert_called_once_with(5) + assert result == 'OpenLP on Mac OS X' + + +def test_get_slide_notes(): + """ + Test the get_slide_notes() method + """ + # GIVEN: A LibreOfficeServer instance + server = LibreOfficeServer() + + # WHEN: get_slide_notes() is called for a particular slide + with patch.object(server, '_get_text_from_page') as mocked_get_text_from_page: + mocked_get_text_from_page.return_value = 'Installing is a drag-and-drop affair' + result = server.get_slide_notes(3) + + # THEN: The text should be returned + mocked_get_text_from_page.assert_called_once_with(3, TextType.Notes) + assert result == 'Installing is a drag-and-drop affair'