Merge branch 'master' of gitlab.com:openlp/openlp into master

This commit is contained in:
Tim 2020-11-17 08:49:42 +00:00
commit 8b9c3a681c
No known key found for this signature in database
GPG Key ID: 3D454289AF831A6D
16 changed files with 1079 additions and 705 deletions

View File

@ -308,9 +308,11 @@ def sha256_file_hash(filename):
:param filename: Name of the file to hash
:returns: str
"""
log.debug('sha256_hash(filename="{filename}")'.format(filename=filename))
log.debug('sha256_file_hash(filename="{filename}")'.format(filename=filename))
hash_obj = hashlib.sha256()
with open(filename, 'rb') as f:
if not filename.exists():
return None
with filename.open('rb') as f:
for chunk in iter(lambda: f.read(65536), b''):
hash_obj.update(chunk)
return hash_obj.hexdigest()

View File

@ -1016,9 +1016,6 @@ var Display = {
targetElement.style.cssText = "";
targetElement.setAttribute("data-background", backgroundContent);
targetElement.setAttribute("data-background-size", "cover");
if (!!backgroundHtml) {
background.innerHTML = backgroundHtml;
}
// set up the main area
if (!is_text) {

View File

@ -41,8 +41,7 @@ from openlp.core.common.registry import Registry, RegistryBase
from openlp.core.lib.serviceitem import ItemCapabilities
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui import DisplayControllerType, HideMode
from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path, parse_stream_path, \
VIDEO_EXT, AUDIO_EXT
from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path, parse_stream_path
from openlp.core.ui.media.remote import register_views
from openlp.core.ui.media.vlcplayer import VlcPlayer, get_vlc
@ -50,6 +49,7 @@ from openlp.core.ui.media.vlcplayer import VlcPlayer, get_vlc
log = logging.getLogger(__name__)
TICK_TIME = 200
HIDE_DELAY_TIME = 2500
class MediaController(RegistryBase, LogMixin, RegistryProperties):
@ -68,10 +68,16 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
# Timer for video state
self.live_timer = QtCore.QTimer()
self.live_timer.setInterval(TICK_TIME)
self.live_hide_timer = QtCore.QTimer()
self.live_hide_timer.setSingleShot(True)
self.live_kill_timer = QtCore.QTimer()
self.live_kill_timer.setSingleShot(True)
self.preview_timer = QtCore.QTimer()
self.preview_timer.setInterval(TICK_TIME)
# Signals
self.live_timer.timeout.connect(self._media_state_live)
self.live_hide_timer.timeout.connect(self._on_media_hide_live)
self.live_kill_timer.timeout.connect(self._on_media_kill_live)
self.preview_timer.timeout.connect(self._media_state_preview)
Registry().register_function('playbackPlay', self.media_play_msg)
Registry().register_function('playbackPause', self.media_pause_msg)
@ -154,31 +160,29 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
Check if there is a running Live media Player and do updating stuff (e.g. update the UI)
"""
display = self._define_display(self._display_controllers(DisplayControllerType.Live))
if DisplayControllerType.Live in self.current_media_players:
self.current_media_players[DisplayControllerType.Live].resize(self.live_controller)
self.current_media_players[DisplayControllerType.Live].update_ui(self.live_controller, display)
self.tick(self._display_controllers(DisplayControllerType.Live))
if self.current_media_players[DisplayControllerType.Live].get_live_state() is not MediaState.Playing:
media_player = self.current_media_players[DisplayControllerType.Live]
media_player.resize(self.live_controller)
media_player.update_ui(self.live_controller, self._define_display(self.live_controller))
if not self.tick(self.live_controller):
self.live_timer.stop()
else:
self.live_timer.stop()
self.media_stop(self._display_controllers(DisplayControllerType.Live))
self.media_stop(self.live_controller)
def _media_state_preview(self):
"""
Check if there is a running Preview media Player and do updating stuff (e.g. update the UI)
"""
display = self._define_display(self._display_controllers(DisplayControllerType.Preview))
if DisplayControllerType.Preview in self.current_media_players:
self.current_media_players[DisplayControllerType.Preview].resize(self.live_controller)
self.current_media_players[DisplayControllerType.Preview].update_ui(self.preview_controller, display)
self.tick(self._display_controllers(DisplayControllerType.Preview))
if self.current_media_players[DisplayControllerType.Preview].get_preview_state() is not MediaState.Playing:
media_player = self.current_media_players[DisplayControllerType.Preview]
media_player.resize(self.preview_controller)
media_player.update_ui(self.preview_controller, self._define_display(self.preview_controller))
if not self.tick(self.preview_controller):
self.preview_timer.stop()
else:
self.preview_timer.stop()
self.media_stop(self._display_controllers(DisplayControllerType.Preview))
self.media_stop(self.preview_controller)
def setup_display(self, controller, preview):
"""
@ -224,9 +228,11 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
is_valid = True
controller = self._display_controllers(source)
log.debug(f'load_video is_live:{controller.is_live}')
# stop running videos
self.media_reset(controller)
controller.media_info = ItemMediaInfo()
controller.media_info.media_type = MediaType.Video
if controller.is_live:
controller.media_info.volume = self.settings.value('media/live volume')
else:
@ -234,6 +240,9 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
# background will always loop video.
if service_item.is_capable(ItemCapabilities.HasBackgroundAudio):
controller.media_info.file_info = service_item.background_audio
controller.media_info.media_type = MediaType.Audio
# is_background indicates we shouldn't override the normal display
controller.media_info.is_background = True
else:
if service_item.is_capable(ItemCapabilities.HasBackgroundStream):
(name, mrl, options) = parse_stream_path(service_item.stream_mrl)
@ -268,6 +277,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.media_info.length = service_item.media_length
is_valid = self._check_file_type(controller, display)
controller.media_info.start_time = service_item.start_time
controller.media_info.timer = service_item.start_time
controller.media_info.end_time = service_item.end_time
elif controller.preview_display:
if service_item.is_capable(ItemCapabilities.IsOptical):
@ -292,11 +302,21 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
translate('MediaPlugin.MediaItem', 'Unsupported File'))
return False
self.log_debug('video media type: {tpe} '.format(tpe=str(controller.media_info.media_type)))
# If both the preview and live view have a stream, make sure only the live view continues streaming
if controller.media_info.media_type == MediaType.Stream:
if controller.is_live:
if self.preview_controller.media_info.media_type == MediaType.Stream:
self.log_warning('stream can only be displayed in one instance, killing preview stream')
self.preview_controller.on_media_close()
else:
if self.live_controller.media_info.media_type == MediaType.Stream:
self.log_warning('stream cannot be previewed while also streaming live')
return
autoplay = False
if service_item.requires_media():
if service_item.requires_media() and hidden == HideMode.Theme:
autoplay = True
# Preview requested
if not controller.is_live:
elif not controller.is_live:
autoplay = True
# Visible or background requested or Service Item wants to autostart
elif not hidden or service_item.will_auto_start:
@ -355,6 +375,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
else:
controller.media_info.media_type = MediaType.DVD
controller.media_info.start_time = start
controller.media_info.timer = start
controller.media_info.end_time = end
controller.media_info.length = (end - start)
controller.media_info.title_track = title
@ -386,38 +407,23 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
return True
return False
for file in controller.media_info.file_info:
if file.is_file:
suffix = '*%s' % file.suffix.lower()
file = str(file)
if suffix in VIDEO_EXT:
self.resize(controller, self.vlc_player)
if self.vlc_player.load(controller, display, file):
self.current_media_players[controller.controller_type] = self.vlc_player
controller.media_info.media_type = MediaType.Video
return True
if suffix in AUDIO_EXT:
if self.vlc_player.load(controller, display, file):
self.current_media_players[controller.controller_type] = self.vlc_player
controller.media_info.media_type = MediaType.Audio
return True
else:
file = str(file)
if self.vlc_player.can_folder:
self.resize(controller, self.vlc_player)
if self.vlc_player.load(controller, display, file):
self.current_media_players[controller.controller_type] = self.vlc_player
controller.media_info.media_type = MediaType.Video
return True
if not file.is_file and not self.vlc_player.can_folder:
return False
file = str(file)
self.resize(controller, self.vlc_player)
if self.vlc_player.load(controller, display, file):
self.current_media_players[controller.controller_type] = self.vlc_player
return True
return False
def media_play_msg(self, msg, status=True):
def media_play_msg(self, msg):
"""
Responds to the request to play a loaded video
:param msg: First element is the controller which should be used
:param status:
"""
return self.media_play(msg[0], status)
return self.media_play(msg[0])
def on_media_play(self):
"""
@ -425,14 +431,13 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
return self.media_play(self.live_controller)
def media_play(self, controller, first_time=True):
def media_play(self, controller):
"""
Responds to the request to play a loaded video
:param controller: The controller to be played
:param first_time:
"""
self.log_debug(f"media_play with {first_time}")
self.log_debug(f'media_play is_live:{controller.is_live}')
controller.seek_slider.blockSignals(True)
controller.volume_slider.blockSignals(True)
display = self._define_display(controller)
@ -441,25 +446,29 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.volume_slider.blockSignals(False)
return False
self.media_volume(controller, controller.media_info.volume)
if first_time:
self.current_media_players[controller.controller_type].set_visible(controller, True)
controller.mediabar.actions['playbackPlay'].setVisible(False)
controller.mediabar.actions['playbackPause'].setVisible(True)
controller.mediabar.actions['playbackStop'].setDisabled(False)
self._media_set_visibility(controller, True)
controller.mediabar.actions['playbackPlay'].setVisible(False)
controller.mediabar.actions['playbackPause'].setVisible(True)
controller.mediabar.actions['playbackStop'].setDisabled(False)
controller.mediabar.actions['playbackLoop'].setChecked(controller.media_info.is_looping_playback)
controller.mediabar.actions['playbackStop'].setVisible(not controller.media_info.is_background or
controller.media_info.media_type is MediaType.Audio)
controller.mediabar.actions['playbackLoop'].setVisible((not controller.media_info.is_background and
controller.media_info.media_type is not MediaType.Stream)
or controller.media_info.media_type is MediaType.Audio)
# Start Timer for ui updates
if controller.is_live:
if controller.hide_menu.defaultAction().isChecked():
controller.hide_menu.defaultAction().trigger()
# Start Timer for ui updates
if not self.live_timer.isActive():
self.live_timer.start()
else:
# Start Timer for ui updates
if not self.preview_timer.isActive():
self.preview_timer.start()
controller.seek_slider.blockSignals(False)
controller.volume_slider.blockSignals(False)
controller.media_info.is_playing = True
if not controller.media_info.is_background:
if controller.is_live:
controller.set_hide_mode(None)
display = self._define_display(controller)
display.hide_display(HideMode.Screen)
controller._set_theme(controller.service_item)
@ -472,30 +481,35 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
Add a tick while the media is playing but only count if not paused
:param controller: The Controller to be processed
:return: Is the video still running?
"""
start_again = False
stopped = False
if controller.media_info.is_playing and controller.media_info.length > 0:
if controller.media_info.timer > controller.media_info.length:
if controller.media_info.timer + TICK_TIME >= controller.media_info.length:
if controller.media_info.is_looping_playback:
start_again = True
else:
self.media_stop(controller)
elif controller.media_info.timer > controller.media_info.length - TICK_TIME * 4:
if not controller.media_info.is_looping_playback:
display = self._define_display(controller)
display.show_display()
stopped = True
controller.media_info.timer += TICK_TIME
seconds = controller.media_info.timer // 1000
minutes = seconds // 60
seconds %= 60
total_seconds = controller.media_info.length // 1000
total_minutes = total_seconds // 60
total_seconds %= 60
controller.position_label.setText(' %02d:%02d / %02d:%02d' %
(minutes, seconds, total_minutes, total_seconds))
self._update_seek_ui(controller)
else:
stopped = True
if start_again:
controller.seek_slider.setSliderPosition(0)
self.media_play(controller, False)
return not stopped
def _update_seek_ui(self, controller):
seconds = controller.media_info.timer // 1000
minutes = seconds // 60
seconds %= 60
total_seconds = controller.media_info.length // 1000
total_minutes = total_seconds // 60
total_seconds %= 60
controller.position_label.setText(' %02d:%02d / %02d:%02d' %
(minutes, seconds, total_minutes, total_seconds))
def media_pause_msg(self, msg):
"""
@ -517,12 +531,16 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
:param controller: The Controller to be paused
"""
self.log_debug(f'media_stop is_live:{controller.is_live}')
if controller.controller_type in self.current_media_players:
self.current_media_players[controller.controller_type].pause(controller)
controller.mediabar.actions['playbackPlay'].setVisible(True)
controller.mediabar.actions['playbackStop'].setDisabled(False)
controller.mediabar.actions['playbackPause'].setVisible(False)
controller.media_info.is_playing = False
# Add a tick to the timer to prevent it finishing the video before it can loop back or stop
# If the clip finishes, we hit a bug where we cannot start the video
controller.media_info.timer += TICK_TIME
controller.output_has_changed()
return True
return False
@ -565,9 +583,19 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
:param controller: The controller that needs to be stopped
"""
self.log_debug(f'media_stop is_live:{controller.is_live}')
if controller.controller_type in self.current_media_players:
self.current_media_players[controller.controller_type].stop(controller)
self.current_media_players[controller.controller_type].set_visible(controller, False)
if controller.is_live:
self.live_hide_timer.start(HIDE_DELAY_TIME)
if not controller.media_info.is_background:
display = self._define_display(controller)
if display.hide_mode == HideMode.Screen:
controller.set_hide_mode(HideMode.Blank)
else:
controller.set_hide_mode(display.hide_mode or HideMode.Blank)
else:
self._media_set_visibility(controller, False)
controller.seek_slider.setSliderPosition(0)
total_seconds = controller.media_info.length // 1000
total_minutes = total_seconds // 60
@ -577,10 +605,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.mediabar.actions['playbackStop'].setDisabled(True)
controller.mediabar.actions['playbackPause'].setVisible(False)
controller.media_info.is_playing = False
controller.media_info.timer = 1000
controller.media_timer = 0
display = self._define_display(controller)
display.show_display()
controller.media_info.timer = 0
controller.output_has_changed()
return True
return False
@ -631,33 +656,76 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
self.current_media_players[controller.controller_type].seek(controller, seek_value)
controller.media_info.timer = seek_value
self._update_seek_ui(controller)
def media_reset(self, controller):
def media_reset(self, controller, delayed=False):
"""
Responds to the request to reset a loaded video
:param controller: The controller to use.
:param delayed: Should the controller briefly remain visible.
"""
self.log_debug('media_reset')
self.set_controls_visible(controller, False)
if controller.controller_type in self.current_media_players:
display = self._define_display(controller)
display.show_display()
hide_mode = controller.get_hide_mode()
if hide_mode is None:
display.show_display()
else:
display.hide_display(hide_mode)
self.current_media_players[controller.controller_type].reset(controller)
self.current_media_players[controller.controller_type].set_visible(controller, False)
del self.current_media_players[controller.controller_type]
if controller.is_live and delayed:
self.live_kill_timer.start(HIDE_DELAY_TIME)
else:
if controller.is_live:
self.live_kill_timer.stop()
else:
self._media_set_visibility(controller, False)
del self.current_media_players[controller.controller_type]
def media_hide(self, msg):
def media_hide_msg(self, msg):
"""
Hide the related video Widget
:param msg: First element is the boolean for Live indication
"""
is_live = msg[1]
if not is_live:
self.media_hide(is_live)
def media_hide(self, is_live, delayed=False):
"""
Pause and hide the related video Widget if is_live
:param is_live: Live indication
:param delayed: Should the controller briefly remain visible.
"""
self.log_debug(f'media_hide is_live:{is_live}')
if not is_live or self.live_kill_timer.isActive():
return
if self.live_controller.controller_type in self.current_media_players and \
self.current_media_players[self.live_controller.controller_type].get_live_state() == MediaState.Playing:
self.media_pause(self.live_controller)
self.current_media_players[self.live_controller.controller_type].set_visible(self.live_controller, False)
if delayed:
self.live_hide_timer.start(HIDE_DELAY_TIME)
else:
self.media_pause(self.live_controller)
self._media_set_visibility(self.live_controller, False)
def _on_media_hide_live(self):
self.media_pause(self.live_controller)
self._media_set_visibility(self.live_controller, False)
def _on_media_kill_live(self):
self._media_set_visibility(self.live_controller, False)
del self.current_media_players[self.live_controller.controller_type]
def _media_set_visibility(self, controller, visible):
"""
Set the live video Widget visibility
"""
if controller.is_live:
self.live_hide_timer.stop()
visible = visible and controller.media_info.media_type is not MediaType.Audio
self.current_media_players[controller.controller_type].set_visible(controller, visible)
def media_blank(self, msg):
"""
@ -668,13 +736,30 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
is_live = msg[1]
hide_mode = msg[2]
if not is_live:
self.log_debug(f'media_blank is_live:{is_live}')
if not is_live or self.live_controller.controller_type not in self.current_media_players:
return
Registry().execute('live_display_hide', hide_mode)
if self.live_controller.controller_type in self.current_media_players and \
self.current_media_players[self.live_controller.controller_type].get_live_state() == MediaState.Playing:
self.media_pause(self.live_controller)
self.current_media_players[self.live_controller.controller_type].set_visible(self.live_controller, False)
if self.live_kill_timer.isActive():
# If pressing blank when the video is being removed, remove it instantly
self._media_set_visibility(self.live_controller, False)
self.media_reset(self.live_controller)
return
if not self.live_controller.media_info.is_background:
Registry().execute('live_display_hide', hide_mode)
controller_type = self.live_controller.controller_type
playing = self.current_media_players[controller_type].get_live_state() == MediaState.Playing
if hide_mode == HideMode.Theme:
if not playing:
self.media_play(self.live_controller)
else:
self.live_hide_timer.stop()
else:
if hide_mode == HideMode.Screen:
if playing:
self.media_pause(self.live_controller)
self._media_set_visibility(self.live_controller, False)
else:
self.live_hide_timer.start(HIDE_DELAY_TIME)
def media_unblank(self, msg):
"""
@ -683,24 +768,28 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
:param msg: First element is not relevant in this context
Second element is the boolean for Live indication
"""
Registry().execute('live_display_show')
is_live = msg[1]
if not is_live:
self.log_debug(f'media_blank is_live:{is_live}')
if not is_live or self.live_kill_timer.isActive():
return
if self.live_controller.controller_type in self.current_media_players and \
self.current_media_players[self.live_controller.controller_type].get_live_state() != \
MediaState.Playing:
if self.media_play(self.live_controller):
self.current_media_players[self.live_controller.controller_type].set_visible(self.live_controller, True)
# Start Timer for ui updates
if not self.live_timer.isActive():
self.live_timer.start()
Registry().execute('live_display_show')
if self.live_controller.controller_type in self.current_media_players:
if self.current_media_players[self.live_controller.controller_type].get_live_state() != \
MediaState.Playing:
self.media_play(self.live_controller)
else:
self._media_set_visibility(self.live_controller, True)
if not self.live_controller.media_info.is_background:
display = self._define_display(self.live_controller)
display.hide_display(HideMode.Screen)
def finalise(self):
"""
Reset all the media controllers when OpenLP shuts down
"""
self.live_timer.stop()
self.live_hide_timer.stop()
self.live_kill_timer.stop()
self.preview_timer.stop()
self.media_reset(self._display_controllers(DisplayControllerType.Live))
self.media_reset(self._display_controllers(DisplayControllerType.Preview))

View File

@ -134,7 +134,6 @@ class VlcPlayer(MediaPlayer):
# creating an empty vlc media player
controller.vlc_media_player = controller.vlc_instance.media_player_new()
controller.vlc_widget.resize(controller.size())
controller.vlc_widget.raise_()
controller.vlc_widget.hide()
# The media player has to be 'connected' to the QFrame.
# (otherwise a video would be displayed in it's own window)
@ -245,22 +244,22 @@ class VlcPlayer(MediaPlayer):
start_time = 0
log.debug('vlc play')
if controller.is_live:
if self.get_live_state() != MediaState.Paused and controller.media_info.start_time > 0:
start_time = controller.media_info.start_time
if self.get_live_state() != MediaState.Paused and controller.media_info.timer > 0:
start_time = controller.media_info.timer
else:
if self.get_preview_state() != MediaState.Paused and controller.media_info.start_time > 0:
start_time = controller.media_info.start_time
if self.get_preview_state() != MediaState.Paused and controller.media_info.timer > 0:
start_time = controller.media_info.timer
threading.Thread(target=controller.vlc_media_player.play).start()
if not self.media_state_wait(controller, vlc.State.Playing):
return False
if controller.is_live:
if self.get_live_state() != MediaState.Paused and controller.media_info.start_time > 0:
if self.get_live_state() != MediaState.Paused and controller.media_info.timer > 0:
log.debug('vlc play, start time set')
start_time = controller.media_info.start_time
start_time = controller.media_info.timer
else:
if self.get_preview_state() != MediaState.Paused and controller.media_info.start_time > 0:
if self.get_preview_state() != MediaState.Paused and controller.media_info.timer > 0:
log.debug('vlc play, start time set')
start_time = controller.media_info.start_time
start_time = controller.media_info.timer
log.debug('mediatype: ' + str(controller.media_info.media_type))
# Set tracks for the optical device
if controller.media_info.media_type == MediaType.DVD and \
@ -278,10 +277,10 @@ class VlcPlayer(MediaPlayer):
if controller.media_info.subtitle_track > 0:
controller.vlc_media_player.video_set_spu(controller.media_info.subtitle_track)
log.debug('vlc play, subtitle_track set: ' + str(controller.media_info.subtitle_track))
if controller.media_info.start_time > 0:
log.debug('vlc play, starttime set: ' + str(controller.media_info.start_time))
start_time = controller.media_info.start_time
controller.media_info.length = controller.media_info.end_time - controller.media_info.start_time
if controller.media_info.timer > 0:
log.debug('vlc play, starttime set: ' + str(controller.media_info.timer))
start_time = controller.media_info.timer
controller.media_info.length = controller.media_info.end_time - controller.media_info.timer
self.volume(controller, controller.media_info.volume)
if start_time > 0 and controller.vlc_media_player.is_seekable():
controller.vlc_media_player.set_time(int(start_time))
@ -343,7 +342,6 @@ class VlcPlayer(MediaPlayer):
:param controller: The controller where the media is
"""
controller.vlc_media_player.stop()
controller.vlc_widget.setVisible(False)
self.set_state(MediaState.Off, controller)
def set_visible(self, controller, status):
@ -362,14 +360,6 @@ class VlcPlayer(MediaPlayer):
:param controller: Which Controller is running the show.
:param output_display: The display where the media is
"""
vlc = get_vlc()
# Stop video if playback is finished.
if controller.vlc_media.get_state() == vlc.State.Ended:
self.stop(controller)
if controller.media_info.end_time > 0:
if controller.vlc_media_player.get_time() > controller.media_info.end_time:
self.stop(controller)
self.set_visible(controller, False)
if not controller.seek_slider.isSliderDown():
controller.seek_slider.blockSignals(True)
if controller.media_info.media_type == MediaType.CD \

View File

@ -525,6 +525,10 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
self.song_menu.setPopupMode(QtWidgets.QToolButton.InstantPopup)
self.song_menu.setMenu(QtWidgets.QMenu(translate('OpenLP.SlideController', 'Go To'), self.toolbar))
def _raise_displays(self):
for display in self.displays:
display.raise_()
def _slide_shortcut_activated(self):
"""
Called, when a shortcut has been activated to jump to a chorus, verse, etc.
@ -946,8 +950,12 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
self._reset_blank(self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay))
self.info_label.setText(self.service_item.title)
self.slide_list = {}
if old_item and old_item.requires_media():
self.on_media_close()
if old_item:
# Close the old item if it's not to be used by the new sevice item
if not self.service_item.is_media() and not self.service_item.requires_media():
self.on_media_close()
if old_item.is_command() and not old_item.is_media():
Registry().execute('{name}_stop'.format(name=old_item.name.lower()), [old_item, self.is_live])
row = 0
width = self.main_window.control_splitter.sizes()[self.split]
if self.service_item.is_text():
@ -989,26 +997,15 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
if self.service_item.is_command():
self.preview_display.load_verses(media_empty_song, True)
self.on_media_start(self.service_item)
# Let media window init, then put webengine back on top
self.application.process_events()
for display in self.displays:
display.raise_()
# Try to get display back on top of media window asap. If the media window
# is not loaded by the time _raise_displays is run, lyrics (web display)
# will be under the media window (not good).
QtCore.QTimer.singleShot(100, self._raise_displays)
QtCore.QTimer.singleShot(500, self._raise_displays)
QtCore.QTimer.singleShot(1000, self._raise_displays)
self.slide_selected(True)
if self.service_item.from_service:
self.preview_widget.setFocus()
if old_item:
# Close the old item after the new one is opened
# This avoids the service theme/desktop flashing on screen
# However opening a new item of the same type will automatically
# close the previous, so make sure we don't close the new one.
if old_item.is_command() and not self.service_item.is_command() or \
old_item.is_command() and not old_item.is_media() and self.service_item.is_media():
if old_item.is_media():
self.on_media_close()
else:
Registry().execute('{name}_stop'.format(name=old_item.name.lower()), [old_item, self.is_live])
if old_item.is_media() and not self.service_item.is_media():
self.on_media_close()
if self.is_live:
Registry().execute('slidecontroller_{item}_started'.format(item=self.type_prefix), [self.service_item])
# Need to process events four times to get correct controller width
@ -1528,7 +1525,9 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
:param item: The service item to be processed
"""
if State().check_preconditions('media'):
if self.is_live and self.get_hide_mode() == HideMode.Theme:
if self.is_live and not item.is_media() and item.requires_media():
self.media_controller.load_video(self.controller_type, item, self.get_hide_mode())
elif self.is_live and self.get_hide_mode() == HideMode.Theme:
self.media_controller.load_video(self.controller_type, item, HideMode.Blank)
self.set_hide_mode(HideMode.Blank)
elif self.is_live or item.is_media():
@ -1542,7 +1541,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
Respond to a request to close the Video if we have media
"""
if State().check_preconditions('media'):
self.media_controller.media_reset(self)
self.media_controller.media_reset(self, delayed=True)
def _reset_blank(self, no_theme):
"""

View File

@ -68,7 +68,7 @@ class StartTimeForm(QtWidgets.QDialog, Ui_StartTimeDialog, RegistryProperties):
start = self.hour_spin_box.value() * 3600 + self.minute_spin_box.value() * 60 + self.second_spin_box.value()
end = self.hour_finish_spin_box.value() * 3600 + \
self.minute_finish_spin_box.value() * 60 + self.second_finish_spin_box.value()
if end > self.item['service_item'].media_length:
if end > self.item['service_item'].media_length // 1000:
critical_error_message_box(title=translate('OpenLP.StartTime_form', 'Time Validation Error'),
message=translate('OpenLP.StartTime_form',
'Finish time is set after the end of the media item'))
@ -78,14 +78,15 @@ class StartTimeForm(QtWidgets.QDialog, Ui_StartTimeDialog, RegistryProperties):
message=translate('OpenLP.StartTime_form',
'Start time is after the finish time of the media item'))
return
self.item['service_item'].start_time = start
self.item['service_item'].end_time = end
self.item['service_item'].start_time = start * 1000
self.item['service_item'].end_time = end * 1000
return QtWidgets.QDialog.accept(self)
def _time_split(self, seconds):
def _time_split(self, milliseconds):
"""
Split time up into hours minutes and seconds from seconds
Split time up into hours minutes and seconds from milliseconds
"""
seconds = milliseconds // 1000
hours = seconds // 3600
seconds -= 3600 * hours
minutes = seconds // 60

View File

@ -21,10 +21,7 @@
"""
This module contains some helpers for serializing Path objects in Pyro4
"""
try:
from openlp.core.common.path import Path
except ImportError:
from pathlib import Path
from openlp.core.common.path import Path
from Pyro4.util import SerializerBase
@ -40,7 +37,7 @@ def path_class_to_dict(obj):
def path_dict_to_class(classname, d):
return Path(d['parts'])
return Path(*d['parts'])
def register_classes():

View File

@ -1,339 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
Functional tests to test the AppLocation class and related methods.
"""
from pathlib import Path
from unittest import skipUnless
from unittest.mock import MagicMock, call, patch
from openlp.core.common import Singleton, clean_button_text, de_hump, extension_loader, is_linux, is_macosx, is_win, \
is_64bit_instance, normalize_str, path_to_module, trace_error_handler
def test_extension_loader_no_files_found():
"""
Test the `extension_loader` function when no files are found
"""
# GIVEN: A mocked `Path.glob` method which does not match any files
with patch('openlp.core.common.applocation.AppLocation.get_directory',
return_value=Path('/', 'app', 'dir', 'openlp')), \
patch.object(Path, 'glob', return_value=[]), \
patch('openlp.core.common.importlib.import_module') as mocked_import_module:
# WHEN: Calling `extension_loader`
extension_loader('glob', ['file2.py', 'file3.py'])
# THEN: `extension_loader` should not try to import any files
assert mocked_import_module.called is False
def test_extension_loader_files_found():
"""
Test the `extension_loader` function when it successfully finds and loads some files
"""
# GIVEN: A mocked `Path.glob` method which returns a list of files
with patch('openlp.core.common.applocation.AppLocation.get_directory',
return_value=Path('/', 'app', 'dir', 'openlp')), \
patch.object(Path, 'glob', return_value=[
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file1.py'),
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file2.py'),
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file3.py'),
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file4.py')]), \
patch('openlp.core.common.importlib.import_module') as mocked_import_module:
# WHEN: Calling `extension_loader` with a list of files to exclude
extension_loader('glob', ['file2.py', 'file3.py'])
# THEN: `extension_loader` should only try to import the files that are matched by the blob, excluding the
# files listed in the `excluded_files` argument
mocked_import_module.assert_has_calls([call('openlp.import_dir.file1'),
call('openlp.import_dir.file4')])
def test_extension_loader_import_error():
"""
Test the `extension_loader` function when `SourceFileLoader` raises a `ImportError`
"""
# GIVEN: A mocked `import_module` which raises an `ImportError`
with patch('openlp.core.common.applocation.AppLocation.get_directory',
return_value=Path('/', 'app', 'dir', 'openlp')), \
patch.object(Path, 'glob', return_value=[
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file1.py')]), \
patch('openlp.core.common.importlib.import_module', side_effect=ImportError()), \
patch('openlp.core.common.log') as mocked_logger:
# WHEN: Calling `extension_loader`
extension_loader('glob')
# THEN: The `ImportError` should be caught and logged
assert mocked_logger.exception.called
def test_extension_loader_os_error():
"""
Test the `extension_loader` function when `import_module` raises a `ImportError`
"""
# GIVEN: A mocked `SourceFileLoader` which raises an `OSError`
with patch('openlp.core.common.applocation.AppLocation.get_directory',
return_value=Path('/', 'app', 'dir', 'openlp')), \
patch.object(Path, 'glob', return_value=[
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file1.py')]), \
patch('openlp.core.common.importlib.import_module', side_effect=OSError()), \
patch('openlp.core.common.log') as mocked_logger:
# WHEN: Calling `extension_loader`
extension_loader('glob')
# THEN: The `OSError` should be caught and logged
assert mocked_logger.exception.called
def test_de_hump_conversion():
"""
Test the de_hump function with a class name
"""
# GIVEN: a Class name in Camel Case
string = "MyClass"
# WHEN: we call de_hump
new_string = de_hump(string)
# THEN: the new string should be converted to python format
assert new_string == "my_class", 'The class name should have been converted'
def test_de_hump_static():
"""
Test the de_hump function with a python string
"""
# GIVEN: a Class name in Camel Case
string = "my_class"
# WHEN: we call de_hump
new_string = de_hump(string)
# THEN: the new string should be converted to python format
assert new_string == "my_class", 'The class name should have been preserved'
def test_path_to_module():
"""
Test `path_to_module` when supplied with a `Path` object
"""
# GIVEN: A `Path` object
path = Path('core', 'ui', 'media', 'vlcplayer.py')
# WHEN: Calling path_to_module with the `Path` object
result = path_to_module(path)
# THEN: path_to_module should return the module name
assert result == 'openlp.core.ui.media.vlcplayer'
def test_trace_error_handler():
"""
Test the trace_error_handler() method
"""
# GIVEN: Mocked out objects
with patch('openlp.core.common.traceback') as mocked_traceback:
mocked_traceback.extract_stack.return_value = [('openlp.fake', 56, None, 'trace_error_handler_test')]
mocked_logger = MagicMock()
# WHEN: trace_error_handler() is called
trace_error_handler(mocked_logger)
# THEN: The mocked_logger.error() method should have been called with the correct parameters
mocked_logger.error.assert_called_with(
'OpenLP Error trace\n File openlp.fake at line 56 \n\t called trace_error_handler_test')
def test_singleton_metaclass_multiple_init():
"""
Test that a class using the Singleton Metaclass is only initialised once despite being called several times and
that the same instance is returned each time..
"""
# GIVEN: The Singleton Metaclass and a test class using it
class SingletonClass(metaclass=Singleton):
def __init__(self):
pass
with patch.object(SingletonClass, '__init__', return_value=None) as patched_init:
# WHEN: Initialising the class multiple times
inst_1 = SingletonClass()
inst_2 = SingletonClass()
# THEN: The __init__ method of the SingletonClass should have only been called once, and both returned values
# should be the same instance.
assert inst_1 is inst_2
assert patched_init.call_count == 1
def test_singleton_metaclass_multiple_classes():
"""
Test that multiple classes using the Singleton Metaclass return the different an appropriate instances.
"""
# GIVEN: Two different classes using the Singleton Metaclass
class SingletonClass1(metaclass=Singleton):
def __init__(self):
pass
class SingletonClass2(metaclass=Singleton):
def __init__(self):
pass
# WHEN: Initialising both classes
s_c1 = SingletonClass1()
s_c2 = SingletonClass2()
# THEN: The instances should be an instance of the appropriate class
assert isinstance(s_c1, SingletonClass1)
assert isinstance(s_c2, SingletonClass2)
def test_is_win():
"""
Test the is_win() function
"""
# GIVEN: Mocked out objects
with patch('openlp.core.common.os') as mocked_os, patch('openlp.core.common.sys') as mocked_sys:
# WHEN: The mocked os.name and sys.platform are set to 'nt' and 'win32' repectivly
mocked_os.name = 'nt'
mocked_sys.platform = 'win32'
# THEN: The three platform functions should perform properly
assert is_win() is True, 'is_win() should return True'
assert is_macosx() is False, 'is_macosx() should return False'
assert is_linux() is False, 'is_linux() should return False'
def test_is_macosx():
"""
Test the is_macosx() function
"""
# GIVEN: Mocked out objects
with patch('openlp.core.common.os') as mocked_os, patch('openlp.core.common.sys') as mocked_sys:
# WHEN: The mocked os.name and sys.platform are set to 'posix' and 'darwin' repectivly
mocked_os.name = 'posix'
mocked_sys.platform = 'darwin'
# THEN: The three platform functions should perform properly
assert is_macosx() is True, 'is_macosx() should return True'
assert is_win() is False, 'is_win() should return False'
assert is_linux() is False, 'is_linux() should return False'
def test_is_linux():
"""
Test the is_linux() function
"""
# GIVEN: Mocked out objects
with patch('openlp.core.common.os') as mocked_os, patch('openlp.core.common.sys') as mocked_sys:
# WHEN: The mocked os.name and sys.platform are set to 'posix' and 'linux3' repectively
mocked_os.name = 'posix'
mocked_sys.platform = 'linux3'
# THEN: The three platform functions should perform properly
assert is_linux() is True, 'is_linux() should return True'
assert is_win() is False, 'is_win() should return False'
assert is_macosx() is False, 'is_macosx() should return False'
@skipUnless(is_linux(), 'This can only run on Linux')
def test_is_linux_distro():
"""
Test the is_linux() function for a particular Linux distribution
"""
# GIVEN: Mocked out objects
with patch('openlp.core.common.os') as mocked_os, \
patch('openlp.core.common.sys') as mocked_sys, \
patch('openlp.core.common.distro_id') as mocked_distro_id:
# WHEN: The mocked os.name and sys.platform are set to 'posix' and 'linux3' repectively
# and the distro is Fedora
mocked_os.name = 'posix'
mocked_sys.platform = 'linux3'
mocked_distro_id.return_value = 'fedora'
# THEN: The three platform functions should perform properly
assert is_linux(distro='fedora') is True, 'is_linux(distro="fedora") should return True'
assert is_win() is False, 'is_win() should return False'
assert is_macosx() is False, 'is_macosx() should return False'
def test_is_64bit_instance():
"""
Test the is_64bit_instance() function
"""
# GIVEN: Mocked out objects
with patch('openlp.core.common.sys') as mocked_sys:
# WHEN: The mocked sys.maxsize is set to 32-bit
mocked_sys.maxsize = 2**32
# THEN: The result should be False
assert is_64bit_instance() is False, 'is_64bit_instance() should return False'
def test_normalize_str_leaves_newlines():
# GIVEN: a string containing newlines
string = 'something\nelse'
# WHEN: normalize is called
normalized_string = normalize_str(string)
# THEN: string is unchanged
assert normalized_string == string
def test_normalize_str_removes_null_byte():
# GIVEN: a string containing a null byte
string = 'somet\x00hing'
# WHEN: normalize is called
normalized_string = normalize_str(string)
# THEN: nullbyte is removed
assert normalized_string == 'something'
def test_normalize_str_replaces_crlf_with_lf():
# GIVEN: a string containing crlf
string = 'something\r\nelse'
# WHEN: normalize is called
normalized_string = normalize_str(string)
# THEN: crlf is replaced with lf
assert normalized_string == 'something\nelse'
def test_clean_button_text():
"""
Test the clean_button_text() function.
"""
# GIVEN: Button text
input_text = '&Next >'
expected_text = 'Next'
# WHEN: The button caption is sent through the clean_button_text function
actual_text = clean_button_text(input_text)
# THEN: The text should have been cleaned
assert expected_text == actual_text, 'The text should be clean'

View File

@ -23,10 +23,534 @@ Functional tests to test the AppLocation class and related methods.
"""
from io import BytesIO
from pathlib import Path
from unittest import skipUnless
from unittest.mock import MagicMock, PropertyMock, call, patch
from openlp.core.common import add_actions, clean_filename, delete_file, get_file_encoding, get_filesystem_encoding, \
get_uno_command, get_uno_instance
import pytest
from openlp.core.common import Singleton, add_actions, clean_filename, clean_button_text, de_hump, delete_file, \
extension_loader, get_file_encoding, get_filesystem_encoding, get_uno_command, get_uno_instance, is_linux, \
is_macosx, is_win, is_64bit_instance, md5_hash, normalize_str, path_to_module, qmd5_hash, sha256_file_hash, \
trace_error_handler, verify_ip_address
from tests.resources.projector.data import TEST_HASH, TEST_PIN, TEST_SALT
test_non_ascii_string = '이것은 한국어 시험 문자열'
test_non_ascii_hash = 'fc00c7912976f6e9c19099b514ced201'
ip4_loopback = '127.0.0.1'
ip4_local = '192.168.1.1'
ip4_broadcast = '255.255.255.255'
ip4_bad = '192.168.1.256'
ip6_loopback = '::1'
ip6_link_local = 'fe80::223:14ff:fe99:d315'
ip6_bad = 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'
def test_extension_loader_no_files_found():
"""
Test the `extension_loader` function when no files are found
"""
# GIVEN: A mocked `Path.glob` method which does not match any files
with patch('openlp.core.common.applocation.AppLocation.get_directory',
return_value=Path('/', 'app', 'dir', 'openlp')), \
patch.object(Path, 'glob', return_value=[]), \
patch('openlp.core.common.importlib.import_module') as mocked_import_module:
# WHEN: Calling `extension_loader`
extension_loader('glob', ['file2.py', 'file3.py'])
# THEN: `extension_loader` should not try to import any files
assert mocked_import_module.called is False
def test_extension_loader_files_found():
"""
Test the `extension_loader` function when it successfully finds and loads some files
"""
# GIVEN: A mocked `Path.glob` method which returns a list of files
with patch('openlp.core.common.applocation.AppLocation.get_directory',
return_value=Path('/', 'app', 'dir', 'openlp')), \
patch.object(Path, 'glob', return_value=[
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file1.py'),
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file2.py'),
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file3.py'),
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file4.py')]), \
patch('openlp.core.common.importlib.import_module') as mocked_import_module:
# WHEN: Calling `extension_loader` with a list of files to exclude
extension_loader('glob', ['file2.py', 'file3.py'])
# THEN: `extension_loader` should only try to import the files that are matched by the blob, excluding the
# files listed in the `excluded_files` argument
mocked_import_module.assert_has_calls([call('openlp.import_dir.file1'),
call('openlp.import_dir.file4')])
def test_extension_loader_import_error():
"""
Test the `extension_loader` function when `SourceFileLoader` raises a `ImportError`
"""
# GIVEN: A mocked `import_module` which raises an `ImportError`
with patch('openlp.core.common.applocation.AppLocation.get_directory',
return_value=Path('/', 'app', 'dir', 'openlp')), \
patch.object(Path, 'glob', return_value=[
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file1.py')]), \
patch('openlp.core.common.importlib.import_module', side_effect=ImportError()), \
patch('openlp.core.common.log') as mocked_logger:
# WHEN: Calling `extension_loader`
extension_loader('glob')
# THEN: The `ImportError` should be caught and logged
assert mocked_logger.exception.called
def test_extension_loader_os_error():
"""
Test the `extension_loader` function when `import_module` raises a `ImportError`
"""
# GIVEN: A mocked `SourceFileLoader` which raises an `OSError`
with patch('openlp.core.common.applocation.AppLocation.get_directory',
return_value=Path('/', 'app', 'dir', 'openlp')), \
patch.object(Path, 'glob', return_value=[
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file1.py')]), \
patch('openlp.core.common.importlib.import_module', side_effect=OSError()), \
patch('openlp.core.common.log') as mocked_logger:
# WHEN: Calling `extension_loader`
extension_loader('glob')
# THEN: The `OSError` should be caught and logged
assert mocked_logger.exception.called
def test_de_hump_conversion():
"""
Test the de_hump function with a class name
"""
# GIVEN: a Class name in Camel Case
string = "MyClass"
# WHEN: we call de_hump
new_string = de_hump(string)
# THEN: the new string should be converted to python format
assert new_string == "my_class", 'The class name should have been converted'
def test_de_hump_static():
"""
Test the de_hump function with a python string
"""
# GIVEN: a Class name in Camel Case
string = "my_class"
# WHEN: we call de_hump
new_string = de_hump(string)
# THEN: the new string should be converted to python format
assert new_string == "my_class", 'The class name should have been preserved'
def test_path_to_module():
"""
Test `path_to_module` when supplied with a `Path` object
"""
# GIVEN: A `Path` object
path = Path('core', 'ui', 'media', 'vlcplayer.py')
# WHEN: Calling path_to_module with the `Path` object
result = path_to_module(path)
# THEN: path_to_module should return the module name
assert result == 'openlp.core.ui.media.vlcplayer'
def test_trace_error_handler():
"""
Test the trace_error_handler() method
"""
# GIVEN: Mocked out objects
with patch('openlp.core.common.traceback') as mocked_traceback:
mocked_traceback.extract_stack.return_value = [('openlp.fake', 56, None, 'trace_error_handler_test')]
mocked_logger = MagicMock()
# WHEN: trace_error_handler() is called
trace_error_handler(mocked_logger)
# THEN: The mocked_logger.error() method should have been called with the correct parameters
mocked_logger.error.assert_called_with(
'OpenLP Error trace\n File openlp.fake at line 56 \n\t called trace_error_handler_test')
def test_singleton_metaclass_multiple_init():
"""
Test that a class using the Singleton Metaclass is only initialised once despite being called several times and
that the same instance is returned each time..
"""
# GIVEN: The Singleton Metaclass and a test class using it
class SingletonClass(metaclass=Singleton):
def __init__(self):
pass
with patch.object(SingletonClass, '__init__', return_value=None) as patched_init:
# WHEN: Initialising the class multiple times
inst_1 = SingletonClass()
inst_2 = SingletonClass()
# THEN: The __init__ method of the SingletonClass should have only been called once, and both returned values
# should be the same instance.
assert inst_1 is inst_2
assert patched_init.call_count == 1
def test_singleton_metaclass_multiple_classes():
"""
Test that multiple classes using the Singleton Metaclass return the different an appropriate instances.
"""
# GIVEN: Two different classes using the Singleton Metaclass
class SingletonClass1(metaclass=Singleton):
def __init__(self):
pass
class SingletonClass2(metaclass=Singleton):
def __init__(self):
pass
# WHEN: Initialising both classes
s_c1 = SingletonClass1()
s_c2 = SingletonClass2()
# THEN: The instances should be an instance of the appropriate class
assert isinstance(s_c1, SingletonClass1)
assert isinstance(s_c2, SingletonClass2)
def test_is_win():
"""
Test the is_win() function
"""
# GIVEN: Mocked out objects
with patch('openlp.core.common.os') as mocked_os, patch('openlp.core.common.sys') as mocked_sys:
# WHEN: The mocked os.name and sys.platform are set to 'nt' and 'win32' repectivly
mocked_os.name = 'nt'
mocked_sys.platform = 'win32'
# THEN: The three platform functions should perform properly
assert is_win() is True, 'is_win() should return True'
assert is_macosx() is False, 'is_macosx() should return False'
assert is_linux() is False, 'is_linux() should return False'
def test_is_macosx():
"""
Test the is_macosx() function
"""
# GIVEN: Mocked out objects
with patch('openlp.core.common.os') as mocked_os, patch('openlp.core.common.sys') as mocked_sys:
# WHEN: The mocked os.name and sys.platform are set to 'posix' and 'darwin' repectivly
mocked_os.name = 'posix'
mocked_sys.platform = 'darwin'
# THEN: The three platform functions should perform properly
assert is_macosx() is True, 'is_macosx() should return True'
assert is_win() is False, 'is_win() should return False'
assert is_linux() is False, 'is_linux() should return False'
def test_is_linux():
"""
Test the is_linux() function
"""
# GIVEN: Mocked out objects
with patch('openlp.core.common.os') as mocked_os, patch('openlp.core.common.sys') as mocked_sys:
# WHEN: The mocked os.name and sys.platform are set to 'posix' and 'linux3' repectively
mocked_os.name = 'posix'
mocked_sys.platform = 'linux3'
# THEN: The three platform functions should perform properly
assert is_linux() is True, 'is_linux() should return True'
assert is_win() is False, 'is_win() should return False'
assert is_macosx() is False, 'is_macosx() should return False'
@skipUnless(is_linux(), 'This can only run on Linux')
def test_is_linux_distro():
"""
Test the is_linux() function for a particular Linux distribution
"""
# GIVEN: Mocked out objects
with patch('openlp.core.common.os') as mocked_os, \
patch('openlp.core.common.sys') as mocked_sys, \
patch('openlp.core.common.distro_id') as mocked_distro_id:
# WHEN: The mocked os.name and sys.platform are set to 'posix' and 'linux3' repectively
# and the distro is Fedora
mocked_os.name = 'posix'
mocked_sys.platform = 'linux3'
mocked_distro_id.return_value = 'fedora'
# THEN: The three platform functions should perform properly
assert is_linux(distro='fedora') is True, 'is_linux(distro="fedora") should return True'
assert is_win() is False, 'is_win() should return False'
assert is_macosx() is False, 'is_macosx() should return False'
def test_is_64bit_instance():
"""
Test the is_64bit_instance() function
"""
# GIVEN: Mocked out objects
with patch('openlp.core.common.sys') as mocked_sys:
# WHEN: The mocked sys.maxsize is set to 32-bit
mocked_sys.maxsize = 2**32
# THEN: The result should be False
assert is_64bit_instance() is False, 'is_64bit_instance() should return False'
def test_normalize_str_leaves_newlines():
# GIVEN: a string containing newlines
string = 'something\nelse'
# WHEN: normalize is called
normalized_string = normalize_str(string)
# THEN: string is unchanged
assert normalized_string == string
def test_normalize_str_removes_null_byte():
# GIVEN: a string containing a null byte
string = 'somet\x00hing'
# WHEN: normalize is called
normalized_string = normalize_str(string)
# THEN: nullbyte is removed
assert normalized_string == 'something'
def test_normalize_str_replaces_crlf_with_lf():
# GIVEN: a string containing crlf
string = 'something\r\nelse'
# WHEN: normalize is called
normalized_string = normalize_str(string)
# THEN: crlf is replaced with lf
assert normalized_string == 'something\nelse'
def test_clean_button_text():
"""
Test the clean_button_text() function.
"""
# GIVEN: Button text
input_text = '&Next >'
expected_text = 'Next'
# WHEN: The button caption is sent through the clean_button_text function
actual_text = clean_button_text(input_text)
# THEN: The text should have been cleaned
assert expected_text == actual_text, 'The text should be clean'
def test_ip4_loopback_valid():
"""
Test IPv4 loopbackvalid
"""
# WHEN: Test with a local loopback test
valid = verify_ip_address(addr=ip4_loopback)
# THEN: Verify we received True
assert valid, 'IPv4 loopback address should have been valid'
def test_ip4_local_valid():
"""
Test IPv4 local valid
"""
# WHEN: Test with a local loopback test
valid = verify_ip_address(addr=ip4_local)
# THEN: Verify we received True
assert valid is True, 'IPv4 local address should have been valid'
def test_ip4_broadcast_valid():
"""
Test IPv4 broadcast valid
"""
# WHEN: Test with a local loopback test
valid = verify_ip_address(addr=ip4_broadcast)
# THEN: Verify we received True
assert valid is True, 'IPv4 broadcast address should have been valid'
def test_ip4_address_invalid():
"""
Test IPv4 address invalid
"""
# WHEN: Test with a local loopback test
valid = verify_ip_address(addr=ip4_bad)
# THEN: Verify we received True
assert valid is False, 'Bad IPv4 address should not have been valid'
def test_ip6_loopback_valid():
"""
Test IPv6 loopback valid
"""
# WHEN: Test IPv6 loopback address
valid = verify_ip_address(addr=ip6_loopback)
# THEN: Validate return
assert valid is True, 'IPv6 loopback address should have been valid'
def test_ip6_local_valid():
"""
Test IPv6 link-local valid
"""
# WHEN: Test IPv6 link-local address
valid = verify_ip_address(addr=ip6_link_local)
# THEN: Validate return
assert valid is True, 'IPv6 link-local address should have been valid'
def test_ip6_address_invalid():
"""
Test NetworkUtils IPv6 address invalid
"""
# WHEN: Given an invalid IPv6 address
valid = verify_ip_address(addr=ip6_bad)
# THEN: Validate bad return
assert valid is False, 'IPv6 bad address should have been invalid'
def test_sha256_file_hash():
"""
Test SHA256 file hash
"""
# GIVEN: A mocked Path object
filename = Path('tests/resources/presentations/test.ppt')
# WHEN: Given a known salt+data
result = sha256_file_hash(filename)
# THEN: Validate return has is same
assert result == 'be6d7bdca25d1662d7faa1f856bfc224646dbad3b65ebff800d9ae70537968f9'
def test_sha256_file_hash_no_exist():
"""
Test SHA256 file hash when the file doesn't exist
"""
# GIVEN: A mocked Path object
mocked_path = MagicMock()
mocked_path.exists.return_value = False
# WHEN: Given a known salt+data
result = sha256_file_hash(mocked_path)
# THEN: Validate return has is same
assert result is None
def test_md5_hash():
"""
Test MD5 hash from salt+data pass (python)
"""
# WHEN: Given a known salt+data
hash_ = md5_hash(salt=TEST_SALT.encode('utf-8'), data=TEST_PIN.encode('utf-8'))
# THEN: Validate return has is same
assert hash_ == TEST_HASH, 'MD5 should have returned a good hash'
def test_md5_hash_no_salt_data():
"""
Test MD5 hash with no salt or data (Python)
"""
# WHEN: Given a known salt+data
hash_ = md5_hash(None, None)
# THEN: Validate return has is same
assert hash_ is None, 'MD5 should have returned None'
def test_md5_hash_bad():
"""
Test MD5 hash from salt+data fail (python)
"""
# WHEN: Given a different salt+hash
hash_ = md5_hash(salt=TEST_PIN.encode('utf-8'), data=TEST_SALT.encode('utf-8'))
# THEN: return data is different
assert hash_ is not TEST_HASH, 'MD5 should have returned a bad hash'
def test_qmd5_hash():
"""
Test MD5 hash from salt+data pass (Qt)
"""
# WHEN: Given a known salt+data
hash_ = qmd5_hash(salt=TEST_SALT.encode('utf-8'), data=TEST_PIN.encode('utf-8'))
# THEN: Validate return has is same
assert hash_ == TEST_HASH, 'Qt-MD5 should have returned a good hash'
def test_qmd5_hash_no_salt_data():
"""
Test MD5 hash with no salt or data (Qt)
"""
# WHEN: Given a known salt+data
hash_ = qmd5_hash(None, None)
# THEN: Validate return has is same
assert hash_ is None, 'Qt-MD5 should have returned None'
def test_qmd5_hash_bad():
"""
Test MD5 hash from salt+hash fail (Qt)
"""
# WHEN: Given a different salt+hash
hash_ = qmd5_hash(salt=TEST_PIN.encode('utf-8'), data=TEST_SALT.encode('utf-8'))
# THEN: return data is different
assert hash_ is not TEST_HASH, 'Qt-MD5 should have returned a bad hash'
def test_md5_non_ascii_string():
"""
Test MD5 hash with non-ascii string - bug 1417809
"""
# WHEN: Non-ascii string is hashed
hash_ = md5_hash(salt=test_non_ascii_string.encode('utf-8'), data=None)
# THEN: Valid MD5 hash should be returned
assert hash_ == test_non_ascii_hash, 'MD5 should have returned a valid hash'
def test_qmd5_non_ascii_string():
"""
Test MD5 hash with non-ascii string - bug 1417809
"""
# WHEN: Non-ascii string is hashed
hash_ = md5_hash(data=test_non_ascii_string.encode('utf-8'))
# THEN: Valid MD5 hash should be returned
assert hash_ == test_non_ascii_hash, 'Qt-MD5 should have returned a valid hash'
def test_add_actions_empty_list():
@ -165,11 +689,11 @@ def test_get_uno_command_when_no_command_exists():
"""
# GIVEN: A patched 'which' method which returns None
with patch('openlp.core.common.which', **{'return_value': None}):
with pytest.raises(FileNotFoundError), \
patch('openlp.core.common.which', **{'return_value': None}):
# WHEN: Calling get_uno_command
# THEN: a FileNotFoundError exception should be raised
assert FileNotFoundError, get_uno_command
get_uno_command()
def test_get_uno_command_connection_type():

View File

@ -26,9 +26,9 @@ import pytest
from unittest.mock import MagicMock, patch
from openlp.core.common.registry import Registry
from openlp.core.ui import DisplayControllerType
from openlp.core.ui import DisplayControllerType, HideMode
from openlp.core.ui.media.mediacontroller import MediaController
from openlp.core.ui.media import ItemMediaInfo
from openlp.core.ui.media import ItemMediaInfo, MediaState
from tests.utils.constants import RESOURCE_PATH
@ -60,6 +60,36 @@ def test_resize(media_env):
mocked_player.resize.assert_called_with(mocked_display)
def test_load_video(media_env, settings):
"""
Test that the load_video runs correctly
"""
# GIVEN: A media controller and a service item
mocked_slide_controller = MagicMock()
mocked_service_item = MagicMock()
mocked_service_item.is_capable.return_value = False
settings.setValue('media/live volume', 1)
media_env.media_controller.current_media_players = MagicMock()
media_env.media_controller._check_file_type = MagicMock(return_value=True)
media_env.media_controller._display_controllers = MagicMock(return_value=mocked_slide_controller)
media_env.media_controller._define_display = MagicMock()
media_env.media_controller.media_reset = MagicMock()
media_env.media_controller.media_play = MagicMock()
media_env.media_controller.set_controls_visible = MagicMock()
# WHEN: load_video() is called
media_env.media_controller.load_video(DisplayControllerType.Live, mocked_service_item)
# THEN: The current controller's media should be reset
# The volume should be set from the settings
# The video should have autoplayed
# The controls should have been made visible
media_env.media_controller.media_reset.assert_called_once_with(mocked_slide_controller)
assert mocked_slide_controller.media_info.volume == 1
media_env.media_controller.media_play.assert_called_once_with(mocked_slide_controller)
media_env.media_controller.set_controls_visible.assert_called_once_with(mocked_slide_controller, True)
def test_check_file_type_null(media_env):
"""
Test that we don't try to play media when no players available
@ -125,10 +155,10 @@ def test_media_play_msg(media_env):
# WHEN: media_play_msg() is called
with patch.object(media_env.media_controller, u'media_play') as mocked_media_play:
media_env.media_controller.media_play_msg(message, False)
media_env.media_controller.media_play_msg(message)
# THEN: The underlying method is called
mocked_media_play.assert_called_with(1, False)
mocked_media_play.assert_called_with(1)
def test_media_pause_msg(media_env):
@ -161,6 +191,33 @@ def test_media_stop_msg(media_env):
mocked_media_stop.assert_called_with(1)
def test_media_stop(media_env):
"""
Test that the media controller takes the correct actions when stopping media
"""
# GIVEN: A live media controller and a message with two elements
mocked_slide_controller = MagicMock()
mocked_media_player = MagicMock()
mocked_display = MagicMock(hide_mode=None)
mocked_slide_controller.controller_type = 'media player'
mocked_slide_controller.media_info = MagicMock(is_background=False)
mocked_slide_controller.set_hide_mode = MagicMock()
mocked_slide_controller.is_live = True
media_env.media_controller.current_media_players = {'media player': mocked_media_player}
media_env.media_controller.live_hide_timer = MagicMock()
media_env.media_controller._define_display = MagicMock(return_value=mocked_display)
# WHEN: media_stop() is called
result = media_env.media_controller.media_stop(mocked_slide_controller)
# THEN: Result should be successful, media player should be stopped and the hide timer should have started
# The controller's hide mode should be set to Blank
assert result is True
mocked_media_player.stop.assert_called_once_with(mocked_slide_controller)
media_env.media_controller.live_hide_timer.start.assert_called_once()
mocked_slide_controller.set_hide_mode.assert_called_once_with(HideMode.Blank)
def test_media_volume_msg(media_env):
"""
Test that the media controller responds to the request to change the volume
@ -191,6 +248,59 @@ def test_media_seek_msg(media_env):
mocked_media_seek.assert_called_with(1, 800)
def test_media_reset(media_env):
"""
Test that the media controller conducts the correct actions when resetting
"""
# GIVEN: A media controller, mocked slide controller, mocked media player and mocked display
mocked_slide_controller = MagicMock()
mocked_media_player = MagicMock()
mocked_display = MagicMock(hide_mode=None)
mocked_slide_controller.controller_type = 'media player'
mocked_slide_controller.media_info = MagicMock(is_background=False)
mocked_slide_controller.get_hide_mode = MagicMock(return_value=None)
mocked_slide_controller.is_live = False
media_env.media_controller.current_media_players = {'media player': mocked_media_player}
media_env.media_controller.live_hide_timer = MagicMock()
media_env.media_controller._define_display = MagicMock(return_value=mocked_display)
media_env.media_controller._media_set_visibility = MagicMock()
# WHEN: media_reset() is called
media_env.media_controller.media_reset(mocked_slide_controller)
# THEN: The display should be shown, media should be hidden and removed
mocked_display.show_display.assert_called_once_with()
media_env.media_controller._media_set_visibility.assert_called_once_with(mocked_slide_controller, False)
assert 'media player' not in media_env.media_controller.current_media_players
def test_media_hide(media_env, registry):
"""
Test that the media controller conducts the correct actions when hiding
"""
# GIVEN: A media controller, mocked slide controller, mocked media player and mocked display
mocked_slide_controller = MagicMock()
mocked_media_player = MagicMock()
mocked_media_player.get_live_state.return_value = MediaState.Playing
mocked_slide_controller.controller_type = 'media player'
mocked_slide_controller.media_info = MagicMock(is_background=False)
mocked_slide_controller.get_hide_mode = MagicMock(return_value=None)
mocked_slide_controller.is_live = False
Registry().register('live_controller', mocked_slide_controller)
media_env.media_controller.current_media_players = {'media player': mocked_media_player}
media_env.media_controller.live_kill_timer = MagicMock(isActive=MagicMock(return_value=False))
media_env.media_controller._media_set_visibility = MagicMock()
media_env.media_controller.media_pause = MagicMock()
# WHEN: media_hide() is called
media_env.media_controller.media_hide(is_live=True)
# THEN: media should be paused and hidden, but the player should still exist
media_env.media_controller.media_pause.assert_called_once_with(mocked_slide_controller)
media_env.media_controller._media_set_visibility.assert_called_once_with(mocked_slide_controller, False)
assert 'media player' in media_env.media_controller.current_media_players
def test_media_length(media_env):
"""
Test the Media Info basic functionality
@ -334,6 +444,7 @@ def test_media_play(media_env):
media_env.current_media_players = MagicMock()
Registry().register('settings', MagicMock())
media_env.live_timer = MagicMock()
media_env.live_hide_timer = MagicMock()
mocked_controller = MagicMock()
mocked_controller.media_info.is_background = False
@ -343,4 +454,5 @@ def test_media_play(media_env):
# THEN: The web display should become transparent (only tests that the theme is reset here)
# And the function should return true to indicate success
assert result is True
media_env.live_hide_timer.stop.assert_called_once_with()
mocked_controller._set_theme.assert_called_once()

View File

@ -122,7 +122,6 @@ def test_setup(MockedQtWidgets, mocked_get_vlc, mocked_is_macosx, mocked_is_win,
assert mocked_output_display.vlc_media_player == mocked_media_player_new
mocked_output_display.size.assert_called_with()
mocked_qframe.resize.assert_called_with((10, 10))
mocked_qframe.raise_.assert_called_with()
mocked_qframe.hide.assert_called_with()
mocked_media_player_new.set_xwindow.assert_called_with(2)
assert vlc_player.has_own_widget is True
@ -848,7 +847,7 @@ def test_reset():
# THEN: The media should be stopped and invisible
mocked_display.vlc_media_player.stop.assert_called_with()
mocked_display.vlc_widget.setVisible.assert_called_with(False)
mocked_display.vlc_widget.setVisible.assert_not_called()
assert MediaState.Off == vlc_player.get_live_state()
@ -886,15 +885,10 @@ def test_update_ui(mocked_get_vlc):
vlc_player = VlcPlayer(None)
# WHEN: update_ui() is called
with patch.object(vlc_player, 'stop') as mocked_stop, \
patch.object(vlc_player, 'set_visible') as mocked_set_visible:
vlc_player.update_ui(mocked_controller, mocked_display)
vlc_player.update_ui(mocked_controller, mocked_display)
# THEN: Certain methods should be called
mocked_stop.assert_called_with(mocked_controller)
assert 2 == mocked_stop.call_count
mocked_controller.vlc_media_player.get_time.assert_called_with()
mocked_set_visible.assert_called_with(mocked_controller, False)
mocked_controller.seek_slider.setSliderPosition.assert_called_with(400000)
expected_calls = [call(True), call(False)]
assert expected_calls == mocked_controller.seek_slider.blockSignals.call_args_list
@ -921,12 +915,9 @@ def test_update_ui_dvd(mocked_get_vlc):
vlc_player = VlcPlayer(None)
# WHEN: update_ui() is called
with patch.object(vlc_player, 'stop') as mocked_stop:
vlc_player.update_ui(mocked_controller, mocked_display)
vlc_player.update_ui(mocked_controller, mocked_display)
# THEN: Certain methods should be called
mocked_stop.assert_called_with(mocked_controller)
assert 1 == mocked_stop.call_count
mocked_controller.vlc_media_player.get_time.assert_called_with()
mocked_controller.seek_slider.setSliderPosition.assert_called_with(200)
expected_calls = [call(True), call(False)]

View File

@ -899,11 +899,137 @@ def test_process_item(mocked_execute, registry, state_media):
slide_controller._process_item(mocked_media_item, 0)
# THEN: Registry.execute should have been called to stop the presentation
assert 1 == mocked_execute.call_count, 'Execute should have been called 2 times'
assert 1 == mocked_execute.call_count, 'Execute should have been called once'
assert 'mocked_presentation_item_stop' == mocked_execute.call_args_list[0][0][0], \
'The presentation should have been stopped.'
@patch.object(Registry, 'execute')
def test_process_item_transition(mocked_execute, registry, state_media):
"""
Test that the correct actions are taken when a media service-item is closed followed by a image service-item
"""
# GIVEN: A mocked presentation service item, a mocked media service item, a mocked Registry.execute
# and a slide controller with many mocks.
# and the setting 'themes/item transitions' = True
mocked_pres_item = MagicMock()
mocked_pres_item.name = 'mocked_image_item'
mocked_pres_item.is_command.return_value = True
mocked_pres_item.is_media.return_value = True
mocked_pres_item.is_image.return_value = False
mocked_pres_item.from_service = False
mocked_pres_item.get_frames.return_value = []
mocked_media_item = MagicMock()
mocked_media_item.name = 'mocked_media_item'
mocked_media_item.get_transition_delay.return_value = 0
mocked_media_item.is_text.return_value = False
mocked_media_item.is_command.return_value = False
mocked_media_item.is_media.return_value = False
mocked_media_item.requires_media.return_value = False
mocked_media_item.is_image.return_value = True
mocked_media_item.from_service = False
mocked_media_item.get_frames.return_value = []
mocked_settings = MagicMock()
mocked_settings.value.return_value = True
mocked_main_window = MagicMock()
Registry().register('main_window', mocked_main_window)
Registry().register('media_controller', MagicMock())
Registry().register('application', MagicMock())
Registry().register('settings', mocked_settings)
slide_controller = SlideController(None)
slide_controller.service_item = mocked_pres_item
slide_controller.is_live = True
slide_controller._reset_blank = MagicMock()
slide_controller.preview_widget = MagicMock()
slide_controller.preview_display = MagicMock()
slide_controller.enable_tool_bar = MagicMock()
slide_controller.on_controller_size_changed = MagicMock()
slide_controller.on_media_start = MagicMock()
slide_controller.on_media_close = MagicMock()
slide_controller.slide_selected = MagicMock()
slide_controller.new_song_menu = MagicMock()
slide_controller.on_stop_loop = MagicMock()
slide_controller.info_label = MagicMock()
slide_controller.song_menu = MagicMock()
slide_controller.displays = [MagicMock()]
slide_controller.toolbar = MagicMock()
slide_controller.split = 0
slide_controller.type_prefix = 'test'
slide_controller.screen_capture = 'old_capture'
# WHEN: _process_item is called
slide_controller._process_item(mocked_media_item, 0)
# THEN: Registry.execute should have been called to start the live item
# Media should be closed
# Controller size change should be called (because it's a live item and the interface might have changed)
# The screen capture should have been reset to none
assert 1 == mocked_execute.call_count, 'Execute should have been called once'
slide_controller.on_media_close.assert_called_once_with()
slide_controller.on_controller_size_changed.assert_called_once()
assert slide_controller.screen_capture is None
@patch.object(Registry, 'execute')
def test_process_item_text(mocked_execute, registry, state_media):
"""
Test that the correct actions are taken a text item is processed
"""
# GIVEN: A mocked presentation service item, a mocked media service item, a mocked Registry.execute
# and a slide controller with many mocks.
# and the setting 'themes/item transitions' = True
mocked_media_item = MagicMock()
mocked_media_item.name = 'mocked_media_item'
mocked_media_item.get_transition_delay.return_value = 0
mocked_media_item.is_text.return_value = True
mocked_media_item.is_command.return_value = False
mocked_media_item.is_media.return_value = False
mocked_media_item.requires_media.return_value = False
mocked_media_item.is_image.return_value = False
mocked_media_item.from_service = False
mocked_media_item.get_frames.return_value = []
mocked_media_item.display_slides = [{'verse': 'Verse name'}]
mocked_settings = MagicMock()
mocked_settings.value.return_value = True
mocked_main_window = MagicMock()
Registry().register('main_window', mocked_main_window)
Registry().register('media_controller', MagicMock())
Registry().register('application', MagicMock())
Registry().register('settings', mocked_settings)
slide_controller = SlideController(None)
slide_controller.service_item = None
slide_controller.is_live = True
slide_controller._reset_blank = MagicMock()
slide_controller.preview_widget = MagicMock()
slide_controller.preview_display = MagicMock()
slide_controller.enable_tool_bar = MagicMock()
slide_controller.on_controller_size_changed = MagicMock()
slide_controller.on_media_start = MagicMock()
slide_controller.on_media_close = MagicMock()
slide_controller.slide_selected = MagicMock()
slide_controller.new_song_menu = MagicMock()
slide_controller.on_stop_loop = MagicMock()
slide_controller.info_label = MagicMock()
slide_controller.song_menu = MagicMock()
slide_controller.displays = [MagicMock()]
slide_controller.toolbar = MagicMock()
slide_controller.split = 0
slide_controller.type_prefix = 'test'
slide_controller.screen_capture = 'old_capture'
# WHEN: _process_item is called
slide_controller._process_item(mocked_media_item, 0)
# THEN: Registry.execute should have been called to start the live item
# Controller size change should be called (because it's a live item and the interface might have changed)
# The screen capture should have been reset to none
# The slide should have been added to the slide list with the correct index
assert 1 == mocked_execute.call_count, 'Execute should have been called once'
slide_controller.on_controller_size_changed.assert_called_once()
assert slide_controller.screen_capture is None
assert slide_controller.slide_list['Verse name'] == 0
@patch.object(Registry, 'execute')
def test_process_item_song_vlc(mocked_execute, registry, state_media):
"""

View File

@ -59,7 +59,7 @@ def test_is_not_image_with_none_image_file():
Test the method handles a non image file
"""
# Given and empty string
file_path = RESOURCE_PATH / 'serviceitem_custom_1.osj'
file_path = RESOURCE_PATH / 'presentations' / 'test.ppt'
# WHEN testing for it
result = is_not_image_file(file_path)

View File

@ -65,9 +65,9 @@ def test_time_display(form):
"""
# GIVEN: A service item with with time
mocked_serviceitem = MagicMock()
mocked_serviceitem.start_time = 61
mocked_serviceitem.end_time = 3701
mocked_serviceitem.media_length = 3701
mocked_serviceitem.start_time = 61000
mocked_serviceitem.end_time = 3701000
mocked_serviceitem.media_length = 3701000
# WHEN displaying the UI and pressing enter
form.item = {'service_item': mocked_serviceitem}
@ -80,7 +80,7 @@ def test_time_display(form):
assert form.hour_spin_box.value() == 0
assert form.minute_spin_box.value() == 1
assert form.second_spin_box.value() == 1
assert form.item['service_item'].start_time == 61, 'The start time should stay the same'
assert form.item['service_item'].start_time == 61000, 'The start time should stay the same'
# WHEN displaying the UI, changing the time to 2min 3secs and pressing enter
form.item = {'service_item': mocked_serviceitem}
@ -95,4 +95,4 @@ def test_time_display(form):
assert form.hour_spin_box.value() == 0
assert form.minute_spin_box.value() == 2
assert form.second_spin_box.value() == 3
assert form.item['service_item'].start_time == 123, 'The start time should have changed'
assert form.item['service_item'].start_time == 123000, 'The start time should have changed'

View File

@ -1,185 +0,0 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
Package to test the openlp.core.ui.projector.networkutils package.
"""
from openlp.core.common import md5_hash, qmd5_hash, verify_ip_address
from tests.resources.projector.data import TEST_HASH, TEST_PIN, TEST_SALT
salt = TEST_SALT
pin = TEST_PIN
test_hash = TEST_HASH
test_non_ascii_string = '이것은 한국어 시험 문자열'
test_non_ascii_hash = 'fc00c7912976f6e9c19099b514ced201'
ip4_loopback = '127.0.0.1'
ip4_local = '192.168.1.1'
ip4_broadcast = '255.255.255.255'
ip4_bad = '192.168.1.256'
ip6_loopback = '::1'
ip6_link_local = 'fe80::223:14ff:fe99:d315'
ip6_bad = 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'
def test_ip4_loopback_valid():
"""
Test IPv4 loopbackvalid
"""
# WHEN: Test with a local loopback test
valid = verify_ip_address(addr=ip4_loopback)
# THEN: Verify we received True
assert valid, 'IPv4 loopback address should have been valid'
def test_ip4_local_valid():
"""
Test IPv4 local valid
"""
# WHEN: Test with a local loopback test
valid = verify_ip_address(addr=ip4_local)
# THEN: Verify we received True
assert valid is True, 'IPv4 local address should have been valid'
def test_ip4_broadcast_valid():
"""
Test IPv4 broadcast valid
"""
# WHEN: Test with a local loopback test
valid = verify_ip_address(addr=ip4_broadcast)
# THEN: Verify we received True
assert valid is True, 'IPv4 broadcast address should have been valid'
def test_ip4_address_invalid():
"""
Test IPv4 address invalid
"""
# WHEN: Test with a local loopback test
valid = verify_ip_address(addr=ip4_bad)
# THEN: Verify we received True
assert valid is False, 'Bad IPv4 address should not have been valid'
def test_ip6_loopback_valid():
"""
Test IPv6 loopback valid
"""
# WHEN: Test IPv6 loopback address
valid = verify_ip_address(addr=ip6_loopback)
# THEN: Validate return
assert valid is True, 'IPv6 loopback address should have been valid'
def test_ip6_local_valid():
"""
Test IPv6 link-local valid
"""
# WHEN: Test IPv6 link-local address
valid = verify_ip_address(addr=ip6_link_local)
# THEN: Validate return
assert valid is True, 'IPv6 link-local address should have been valid'
def test_ip6_address_invalid():
"""
Test NetworkUtils IPv6 address invalid
"""
# WHEN: Given an invalid IPv6 address
valid = verify_ip_address(addr=ip6_bad)
# THEN: Validate bad return
assert valid is False, 'IPv6 bad address should have been invalid'
def test_md5_hash():
"""
Test MD5 hash from salt+data pass (python)
"""
# WHEN: Given a known salt+data
hash_ = md5_hash(salt=salt.encode('utf-8'), data=pin.encode('utf-8'))
# THEN: Validate return has is same
assert hash_ == test_hash, 'MD5 should have returned a good hash'
def test_md5_hash_bad():
"""
Test MD5 hash from salt+data fail (python)
"""
# WHEN: Given a different salt+hash
hash_ = md5_hash(salt=pin.encode('utf-8'), data=salt.encode('utf-8'))
# THEN: return data is different
assert hash_ is not test_hash, 'MD5 should have returned a bad hash'
def test_qmd5_hash():
"""
Test MD5 hash from salt+data pass (Qt)
"""
# WHEN: Given a known salt+data
hash_ = qmd5_hash(salt=salt.encode('utf-8'), data=pin.encode('utf-8'))
# THEN: Validate return has is same
assert hash_ == test_hash, 'Qt-MD5 should have returned a good hash'
def test_qmd5_hash_bad():
"""
Test MD5 hash from salt+hash fail (Qt)
"""
# WHEN: Given a different salt+hash
hash_ = qmd5_hash(salt=pin.encode('utf-8'), data=salt.encode('utf-8'))
# THEN: return data is different
assert hash_ is not test_hash, 'Qt-MD5 should have returned a bad hash'
def test_md5_non_ascii_string():
"""
Test MD5 hash with non-ascii string - bug 1417809
"""
# WHEN: Non-ascii string is hashed
hash_ = md5_hash(salt=test_non_ascii_string.encode('utf-8'), data=None)
# THEN: Valid MD5 hash should be returned
assert hash_ == test_non_ascii_hash, 'MD5 should have returned a valid hash'
def test_qmd5_non_ascii_string():
"""
Test MD5 hash with non-ascii string - bug 1417809
"""
# WHEN: Non-ascii string is hashed
hash_ = md5_hash(data=test_non_ascii_string.encode('utf-8'))
# THEN: Valid MD5 hash should be returned
assert hash_ == test_non_ascii_hash, 'Qt-MD5 should have returned a valid hash'

View File

@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
# Copyright (c) 2008-2020 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
"""
Package to test the openlp.core.lib package.
"""
from pathlib import Path
from unittest.mock import patch
try:
import Pyro4 # noqa
from openlp.plugins.presentations.lib.serializers import path_class_to_dict, path_dict_to_class, register_classes
except ImportError:
import pytest
pytestmark = pytest.mark.skip('Pyro4 not installed')
def test_path_class_to_dict():
"""Test that the path_class_to_dict() method returns a correctly formatted dictionary"""
# GIVEN: A Path object
path = Path('openlp/core/ui/aboutform.py')
# WHEN: path_class_to_dict() is called with the given Path object
result = path_class_to_dict(path)
# THEN: The dictionary should be formatted correctly
assert result == {'__class__': 'Path', 'parts': ('openlp', 'core', 'ui', 'aboutform.py')}
def test_path_dict_to_class():
"""Test that the path_dict_to_class() method returns the right object"""
# GIVEN: A dictionary that was created from a Path object
path_dict = {'__class__': 'Path', 'parts': ('openlp', 'core', 'app.py')}
# WHEN: path_dict_to_class() is called with the given dictionary
result = path_dict_to_class('Path', path_dict)
# THEN: The correct Path object should have been created
assert isinstance(result, Path)
assert result == Path('openlp/core/app.py')
@patch('openlp.plugins.presentations.lib.serializers.SerializerBase')
def test_register_classes(MockSerializerBase):
"""Test that the register_classes() method registers the two functions"""
# GIVEN: A mocked out SerializerBase
# WHEN: register_classes() is run
register_classes()
# THEN: The functions should have been registered
MockSerializerBase.register_class_to_dict.assert_called_once_with(Path, path_class_to_dict)
MockSerializerBase.register_dict_to_class.assert_called_once_with('Path', path_dict_to_class)