forked from openlp/openlp
Merge branch 'master' of gitlab.com:openlp/openlp into master
This commit is contained in:
commit
8b9c3a681c
@ -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()
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
if not file.is_file and not self.vlc_player.can_folder:
|
||||
return False
|
||||
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
|
||||
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)
|
||||
self._media_set_visibility(controller, True)
|
||||
controller.mediabar.actions['playbackPlay'].setVisible(False)
|
||||
controller.mediabar.actions['playbackPause'].setVisible(True)
|
||||
controller.mediabar.actions['playbackStop'].setDisabled(False)
|
||||
if controller.is_live:
|
||||
if controller.hide_menu.defaultAction().isChecked():
|
||||
controller.hide_menu.defaultAction().trigger()
|
||||
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 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,19 +481,27 @@ 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
|
||||
self._update_seek_ui(controller)
|
||||
else:
|
||||
stopped = True
|
||||
|
||||
if start_again:
|
||||
controller.seek_slider.setSliderPosition(0)
|
||||
return not stopped
|
||||
|
||||
def _update_seek_ui(self, controller):
|
||||
seconds = controller.media_info.timer // 1000
|
||||
minutes = seconds // 60
|
||||
seconds %= 60
|
||||
@ -493,9 +510,6 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
||||
total_seconds %= 60
|
||||
controller.position_label.setText(' %02d:%02d / %02d:%02d' %
|
||||
(minutes, seconds, total_minutes, total_seconds))
|
||||
if start_again:
|
||||
controller.seek_slider.setSliderPosition(0)
|
||||
self.media_play(controller, False)
|
||||
|
||||
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)
|
||||
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)
|
||||
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:
|
||||
if delayed:
|
||||
self.live_hide_timer.start(HIDE_DELAY_TIME)
|
||||
else:
|
||||
self.media_pause(self.live_controller)
|
||||
self.current_media_players[self.live_controller.controller_type].set_visible(self.live_controller, False)
|
||||
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
|
||||
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)
|
||||
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:
|
||||
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.current_media_players[self.live_controller.controller_type].set_visible(self.live_controller, False)
|
||||
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() != \
|
||||
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:
|
||||
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()
|
||||
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))
|
||||
|
@ -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 \
|
||||
|
@ -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():
|
||||
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):
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -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 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():
|
||||
|
@ -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'
|
@ -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():
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)]
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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'
|
||||
|
@ -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'
|
70
tests/openlp_plugins/presentations/lib/test_serializers.py
Normal file
70
tests/openlp_plugins/presentations/lib/test_serializers.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user