Skip to content
Snippets Groups Projects
Commit a1ed48e9 authored by Paul McCarthy's avatar Paul McCarthy
Browse files

Merged hotfix from v0.11.0a. Squashed commit of the following:

commit 27f214ac
Author: Paul McCarthy <pauld.mccarthy@gmail.com>
Date:   Wed Apr 26 18:20:08 2017 +0100

    Version bump

commit 52f759f0
Author: Paul McCarthy <pauld.mccarthy@gmail.com>
Date:   Wed Apr 26 18:05:11 2017 +0100

    More robust unit tests for async.idle(alwaysQueue=True). Also using a single
    wx.App object for complex reasons

commit d87aae89
Author: Paul McCarthy <pauld.mccarthy@gmail.com>
Date:   Wed Apr 26 18:00:13 2017 +0100

    Adjustment to async.idle(alwaysQueue=True) - now, a second idle call is only
    necessary if the first call came before a wx.App had been created.
parent d7639631
No related branches found
No related tags found
No related merge requests found
......@@ -96,14 +96,6 @@ except: import Queue as queue
log = logging.getLogger(__name__)
def _haveWX():
"""Returns ``True`` if we are running within a ``wx`` application,
``False`` otherwise.
"""
import fsl.utils.platform as fslplatform
return fslplatform.platform.haveGui
def run(task, onFinish=None, onError=None, name=None):
"""Run the given ``task`` in a separate thread.
......@@ -125,10 +117,12 @@ def run(task, onFinish=None, onError=None, name=None):
the return value will be ``None``.
"""
from fsl.utils.platform import platform as fslplatform
if name is None:
name = getattr(task, '__name__', '<unknown>')
haveWX = _haveWX()
haveWX = fslplatform.haveGui
# Calls the onFinish or onError handler
def callback(cb, *args, **kwargs):
......@@ -397,9 +391,11 @@ def idle(task, *args, **kwargs):
argument. If ``True``, and a ``wx.MainLoop`` is not
running, the task is enqueued anyway, under the
assumption that a ``wx.MainLoop`` will be started in
the future. Note that another call to ``idle`` must
be made after the ``MainLoop`` has started for the
original task to be executed.
the future. Note that, if ``wx.App`` has not yet been
created, another call to ``idle`` must be made after
the app has been created for the original task to be
executed. If ``wx`` is not available, this parameter
will be ignored, and the task executed directly.
All other arguments are passed through to the task function.
......@@ -426,6 +422,8 @@ def idle(task, *args, **kwargs):
``alwaysQueue``.
"""
from fsl.utils.platform import platform as fslplatform
global _idleRegistered
global _idleTimer
global _idleQueue
......@@ -439,13 +437,29 @@ def idle(task, *args, **kwargs):
skipIfQueued = kwargs.pop('skipIfQueued', False)
alwaysQueue = kwargs.pop('alwaysQueue', False)
havewx = _haveWX()
canHaveGui = fslplatform.canHaveGui
haveGui = fslplatform.haveGui
# If there is no possibility of a
# gui being available in the future,
# then alwaysQueue is ignored.
if haveGui or (alwaysQueue and canHaveGui):
if havewx or alwaysQueue:
import wx
app = wx.GetApp()
# Register on the idle event
# if an app is available
#
# n.b. The 'app is not None' test will
# potentially fail in scenarios where
# multiple wx.Apps have been instantiated,
# as it may return a previously created
# app.
if (not _idleRegistered) and (app is not None):
log.debug('Registering async idle loop')
if havewx and (not _idleRegistered):
app = wx.GetApp()
app.Bind(wx.EVT_IDLE, _wxIdleLoop)
_idleTimer = wx.Timer(app)
......@@ -546,12 +560,14 @@ def wait(threads, task, *args, **kwargs):
a keyword argument called ``wait_direct``.
"""
from fsl.utils.platform import platform as fslplatform
direct = kwargs.pop('wait_direct', False)
if not isinstance(threads, collections.Sequence):
threads = [threads]
haveWX = _haveWX()
haveWX = fslplatform.haveGui
def joinAll():
log.debug('Wait thread joining on all targets')
......
......@@ -9,29 +9,46 @@ import time
import threading
import random
from six.moves import reload_module
import pytest
import mock
import fsl.utils.async as async
from fsl.utils.platform import platform as fslplatform
# We use a single wx.App object because wx.GetApp()
# will still return an old App objectd after its
# mainloop has exited. and therefore async.idle
# will potentially register on EVT_IDLE with the
# wrong wx.App object.
_wxapp = None
def _run_with_wx(func, *args, **kwargs):
global _wxapp
propagateRaise = kwargs.pop('propagateRaise', True)
startingDelay = kwargs.pop('startingDelay', 500)
finishingDelay = kwargs.pop('finishingDelay', 500)
import wx
callAfterApp = kwargs.pop('callAfterApp', None)
import wx
result = [None]
raised = [None]
app = wx.App()
if _wxapp is None:
_wxapp = wx.App()
frame = wx.Frame(None)
if callAfterApp is not None:
callAfterApp()
def wrap():
try:
result[0] = func(*args, **kwargs)
if func is not None:
result[0] = func(*args, **kwargs)
except Exception as e:
print(e)
......@@ -40,14 +57,14 @@ def _run_with_wx(func, *args, **kwargs):
finally:
def finish():
frame.Destroy()
app.ExitMainLoop()
_wxapp.ExitMainLoop()
wx.CallLater(finishingDelay, finish)
frame.Show()
wx.CallLater(startingDelay, wrap)
app.MainLoop()
_wxapp.MainLoop()
async.idleReset()
if raised[0] and propagateRaise:
......@@ -244,25 +261,98 @@ def test_idle_dropIfQueued():
assert task2called[0]
def test_idle_alwaysQueue():
def test_idle_alwaysQueue1():
# Test scheduling the task before
# a wx.App has been created.
called = [False]
def task():
called[0] = True
# In this scenario, an additional call
# to idle (after the App has been created)
# is necessary, otherwise the originally
# queued task will not be called.
def nop():
pass
# The task should be run
# when the mainloop starts
async.idle(task, alwaysQueue=True)
# We need to queue another task
# for the first task to be executed
# Second call to async.idle
_run_with_wx(async.idle, nop)
assert called[0]
def test_idle_alwaysQueue2():
# Test scheduling the task
# after a wx.App has been craeted,
# but before MainLoop has started
called = [False]
def task():
called[0] = True
def queue():
async.idle(task, alwaysQueue=True)
_run_with_wx(None, callAfterApp=queue)
assert called[0]
def test_idle_alwaysQueue3():
# Test scheduling the task
# after a wx.App has been craeted
# and the MainLoop has started.
# In this case, alwaysQueue should
# have no effect - the task should
# just be queued and executed as
# normal.
called = [False]
def task():
called[0] = True
_run_with_wx(async.idle, task, alwaysQueue=True)
assert called[0]
def test_idle_alwaysQueue4():
# Test scheduling the task when
# wx is not present - the task
# should just be executed immediately
called = [False]
def task():
called[0] = True
import fsl.utils.platform
with mock.patch.dict('sys.modules', {'wx' : None}):
# async uses the platform module to
# determine whether a GUI is available,
# so we have to reload it
reload_module(fsl.utils.platform)
async.idle(task, alwaysQueue=True)
with pytest.raises(ImportError):
import wx
reload_module(fsl.utils.platform)
assert called[0]
def test_idle_timeout():
called = [False]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment