Merge branch 'stability_tests' into develop
This commit is contained in:
commit
255b2405d1
@ -84,6 +84,7 @@ pyqtgraph-0.9.9 [unreleased]
|
|||||||
- Fixed AxisItem.__init__(showValues=False)
|
- Fixed AxisItem.__init__(showValues=False)
|
||||||
- Fixed TableWidget append / sort issues
|
- Fixed TableWidget append / sort issues
|
||||||
- Fixed AxisItem not resizing text area when setTicks() is used
|
- Fixed AxisItem not resizing text area when setTicks() is used
|
||||||
|
- Removed a few cyclic references
|
||||||
|
|
||||||
pyqtgraph-0.9.8 2013-11-24
|
pyqtgraph-0.9.8 2013-11-24
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
from .Qt import QtCore
|
from .Qt import QtCore
|
||||||
from .ptime import time
|
from .ptime import time
|
||||||
from . import ThreadsafeTimer
|
from . import ThreadsafeTimer
|
||||||
|
import weakref
|
||||||
|
|
||||||
__all__ = ['SignalProxy']
|
__all__ = ['SignalProxy']
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ class SignalProxy(QtCore.QObject):
|
|||||||
self.timer = ThreadsafeTimer.ThreadsafeTimer()
|
self.timer = ThreadsafeTimer.ThreadsafeTimer()
|
||||||
self.timer.timeout.connect(self.flush)
|
self.timer.timeout.connect(self.flush)
|
||||||
self.block = False
|
self.block = False
|
||||||
self.slot = slot
|
self.slot = weakref.ref(slot)
|
||||||
self.lastFlushTime = None
|
self.lastFlushTime = None
|
||||||
if slot is not None:
|
if slot is not None:
|
||||||
self.sigDelayed.connect(slot)
|
self.sigDelayed.connect(slot)
|
||||||
@ -80,7 +81,7 @@ class SignalProxy(QtCore.QObject):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
self.sigDelayed.disconnect(self.slot)
|
self.sigDelayed.disconnect(self.slot())
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ from .. import functions as fn
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from .. import debug as debug
|
from .. import debug as debug
|
||||||
|
|
||||||
|
import weakref
|
||||||
|
|
||||||
__all__ = ['HistogramLUTItem']
|
__all__ = ['HistogramLUTItem']
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ class HistogramLUTItem(GraphicsWidget):
|
|||||||
"""
|
"""
|
||||||
GraphicsWidget.__init__(self)
|
GraphicsWidget.__init__(self)
|
||||||
self.lut = None
|
self.lut = None
|
||||||
self.imageItem = None
|
self.imageItem = lambda: None # fake a dead weakref
|
||||||
|
|
||||||
self.layout = QtGui.QGraphicsGridLayout()
|
self.layout = QtGui.QGraphicsGridLayout()
|
||||||
self.setLayout(self.layout)
|
self.setLayout(self.layout)
|
||||||
@ -138,7 +139,7 @@ class HistogramLUTItem(GraphicsWidget):
|
|||||||
#self.region.setBounds([vr.top(), vr.bottom()])
|
#self.region.setBounds([vr.top(), vr.bottom()])
|
||||||
|
|
||||||
def setImageItem(self, img):
|
def setImageItem(self, img):
|
||||||
self.imageItem = img
|
self.imageItem = weakref.ref(img)
|
||||||
img.sigImageChanged.connect(self.imageChanged)
|
img.sigImageChanged.connect(self.imageChanged)
|
||||||
img.setLookupTable(self.getLookupTable) ## send function pointer, not the result
|
img.setLookupTable(self.getLookupTable) ## send function pointer, not the result
|
||||||
#self.gradientChanged()
|
#self.gradientChanged()
|
||||||
@ -150,11 +151,11 @@ class HistogramLUTItem(GraphicsWidget):
|
|||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def gradientChanged(self):
|
def gradientChanged(self):
|
||||||
if self.imageItem is not None:
|
if self.imageItem() is not None:
|
||||||
if self.gradient.isLookupTrivial():
|
if self.gradient.isLookupTrivial():
|
||||||
self.imageItem.setLookupTable(None) #lambda x: x.astype(np.uint8))
|
self.imageItem().setLookupTable(None) #lambda x: x.astype(np.uint8))
|
||||||
else:
|
else:
|
||||||
self.imageItem.setLookupTable(self.getLookupTable) ## send function pointer, not the result
|
self.imageItem().setLookupTable(self.getLookupTable) ## send function pointer, not the result
|
||||||
|
|
||||||
self.lut = None
|
self.lut = None
|
||||||
#if self.imageItem is not None:
|
#if self.imageItem is not None:
|
||||||
@ -178,14 +179,14 @@ class HistogramLUTItem(GraphicsWidget):
|
|||||||
#self.update()
|
#self.update()
|
||||||
|
|
||||||
def regionChanging(self):
|
def regionChanging(self):
|
||||||
if self.imageItem is not None:
|
if self.imageItem() is not None:
|
||||||
self.imageItem.setLevels(self.region.getRegion())
|
self.imageItem().setLevels(self.region.getRegion())
|
||||||
self.sigLevelsChanged.emit(self)
|
self.sigLevelsChanged.emit(self)
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def imageChanged(self, autoLevel=False, autoRange=False):
|
def imageChanged(self, autoLevel=False, autoRange=False):
|
||||||
profiler = debug.Profiler()
|
profiler = debug.Profiler()
|
||||||
h = self.imageItem.getHistogram()
|
h = self.imageItem().getHistogram()
|
||||||
profiler('get histogram')
|
profiler('get histogram')
|
||||||
if h[0] is None:
|
if h[0] is None:
|
||||||
return
|
return
|
||||||
|
77
pyqtgraph/tests/test_ref_cycles.py
Normal file
77
pyqtgraph/tests/test_ref_cycles.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
"""
|
||||||
|
Test for unwanted reference cycles
|
||||||
|
|
||||||
|
"""
|
||||||
|
import pyqtgraph as pg
|
||||||
|
import numpy as np
|
||||||
|
import gc, weakref
|
||||||
|
app = pg.mkQApp()
|
||||||
|
|
||||||
|
def assert_alldead(refs):
|
||||||
|
for ref in refs:
|
||||||
|
assert ref() is None
|
||||||
|
|
||||||
|
def qObjectTree(root):
|
||||||
|
"""Return root and its entire tree of qobject children"""
|
||||||
|
childs = [root]
|
||||||
|
for ch in pg.QtCore.QObject.children(root):
|
||||||
|
childs += qObjectTree(ch)
|
||||||
|
return childs
|
||||||
|
|
||||||
|
def mkrefs(*objs):
|
||||||
|
"""Return a list of weakrefs to each object in *objs.
|
||||||
|
QObject instances are expanded to include all child objects.
|
||||||
|
"""
|
||||||
|
allObjs = {}
|
||||||
|
for obj in objs:
|
||||||
|
if isinstance(obj, pg.QtCore.QObject):
|
||||||
|
obj = qObjectTree(obj)
|
||||||
|
else:
|
||||||
|
obj = [obj]
|
||||||
|
for o in obj:
|
||||||
|
allObjs[id(o)] = o
|
||||||
|
|
||||||
|
return map(weakref.ref, allObjs.values())
|
||||||
|
|
||||||
|
def test_PlotWidget():
|
||||||
|
def mkobjs(*args, **kwds):
|
||||||
|
w = pg.PlotWidget(*args, **kwds)
|
||||||
|
data = pg.np.array([1,5,2,4,3])
|
||||||
|
c = w.plot(data, name='stuff')
|
||||||
|
w.addLegend()
|
||||||
|
|
||||||
|
# test that connections do not keep objects alive
|
||||||
|
w.plotItem.vb.sigRangeChanged.connect(mkrefs)
|
||||||
|
app.focusChanged.connect(w.plotItem.vb.invertY)
|
||||||
|
|
||||||
|
# return weakrefs to a bunch of objects that should die when the scope exits.
|
||||||
|
return mkrefs(w, c, data, w.plotItem, w.plotItem.vb, w.plotItem.getMenu(), w.plotItem.getAxis('left'))
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
assert_alldead(mkobjs())
|
||||||
|
|
||||||
|
def test_ImageView():
|
||||||
|
def mkobjs():
|
||||||
|
iv = pg.ImageView()
|
||||||
|
data = np.zeros((10,10,5))
|
||||||
|
iv.setImage(data)
|
||||||
|
|
||||||
|
return mkrefs(iv, iv.imageItem, iv.view, iv.ui.histogram, data)
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
assert_alldead(mkobjs())
|
||||||
|
|
||||||
|
def test_GraphicsWindow():
|
||||||
|
def mkobjs():
|
||||||
|
w = pg.GraphicsWindow()
|
||||||
|
p1 = w.addPlot()
|
||||||
|
v1 = w.addViewBox()
|
||||||
|
return mkrefs(w, p1, v1)
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
assert_alldead(mkobjs())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
ot = test_PlotItem()
|
160
pyqtgraph/tests/test_stability.py
Normal file
160
pyqtgraph/tests/test_stability.py
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
"""
|
||||||
|
PyQt/PySide stress test:
|
||||||
|
|
||||||
|
Create lots of random widgets and graphics items, connect them together randomly,
|
||||||
|
the tear them down repeatedly.
|
||||||
|
|
||||||
|
The purpose of this is to attempt to generate segmentation faults.
|
||||||
|
"""
|
||||||
|
from PyQt4.QtTest import QTest
|
||||||
|
import pyqtgraph as pg
|
||||||
|
from random import seed, randint
|
||||||
|
import sys, gc, weakref
|
||||||
|
|
||||||
|
app = pg.mkQApp()
|
||||||
|
|
||||||
|
seed(12345)
|
||||||
|
|
||||||
|
widgetTypes = [
|
||||||
|
pg.PlotWidget,
|
||||||
|
pg.ImageView,
|
||||||
|
pg.GraphicsView,
|
||||||
|
pg.QtGui.QWidget,
|
||||||
|
pg.QtGui.QTreeWidget,
|
||||||
|
pg.QtGui.QPushButton,
|
||||||
|
]
|
||||||
|
|
||||||
|
itemTypes = [
|
||||||
|
pg.PlotCurveItem,
|
||||||
|
pg.ImageItem,
|
||||||
|
pg.PlotDataItem,
|
||||||
|
pg.ViewBox,
|
||||||
|
pg.QtGui.QGraphicsRectItem
|
||||||
|
]
|
||||||
|
|
||||||
|
widgets = []
|
||||||
|
items = []
|
||||||
|
allWidgets = weakref.WeakSet()
|
||||||
|
|
||||||
|
|
||||||
|
def crashtest():
|
||||||
|
global allWidgets
|
||||||
|
try:
|
||||||
|
gc.disable()
|
||||||
|
actions = [
|
||||||
|
createWidget,
|
||||||
|
#setParent,
|
||||||
|
forgetWidget,
|
||||||
|
showWidget,
|
||||||
|
processEvents,
|
||||||
|
#raiseException,
|
||||||
|
#addReference,
|
||||||
|
]
|
||||||
|
|
||||||
|
thread = WorkThread()
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
action = randItem(actions)
|
||||||
|
action()
|
||||||
|
print('[%d widgets alive, %d zombie]' % (len(allWidgets), len(allWidgets) - len(widgets)))
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Caught interrupt; send another to exit.")
|
||||||
|
try:
|
||||||
|
for i in range(100):
|
||||||
|
QTest.qWait(100)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
thread.terminate()
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
sys.excepthook(*sys.exc_info())
|
||||||
|
finally:
|
||||||
|
gc.enable()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class WorkThread(pg.QtCore.QThread):
|
||||||
|
'''Intended to give the gc an opportunity to run from a non-gui thread.'''
|
||||||
|
def run(self):
|
||||||
|
i = 0
|
||||||
|
while True:
|
||||||
|
i += 1
|
||||||
|
if (i % 1000000) == 0:
|
||||||
|
print('--worker--')
|
||||||
|
|
||||||
|
|
||||||
|
def randItem(items):
|
||||||
|
return items[randint(0, len(items)-1)]
|
||||||
|
|
||||||
|
def p(msg):
|
||||||
|
print(msg)
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def createWidget():
|
||||||
|
p('create widget')
|
||||||
|
global widgets, allWidgets
|
||||||
|
if len(widgets) > 50:
|
||||||
|
return
|
||||||
|
widget = randItem(widgetTypes)()
|
||||||
|
widget.setWindowTitle(widget.__class__.__name__)
|
||||||
|
widgets.append(widget)
|
||||||
|
allWidgets.add(widget)
|
||||||
|
p(" %s" % widget)
|
||||||
|
return widget
|
||||||
|
|
||||||
|
def setParent():
|
||||||
|
p('set parent')
|
||||||
|
global widgets
|
||||||
|
if len(widgets) < 2:
|
||||||
|
return
|
||||||
|
child = parent = None
|
||||||
|
while child is parent:
|
||||||
|
child = randItem(widgets)
|
||||||
|
parent = randItem(widgets)
|
||||||
|
p(" %s parent of %s" % (parent, child))
|
||||||
|
child.setParent(parent)
|
||||||
|
|
||||||
|
def forgetWidget():
|
||||||
|
p('forget widget')
|
||||||
|
global widgets
|
||||||
|
if len(widgets) < 1:
|
||||||
|
return
|
||||||
|
widget = randItem(widgets)
|
||||||
|
p(' %s' % widget)
|
||||||
|
widgets.remove(widget)
|
||||||
|
|
||||||
|
def showWidget():
|
||||||
|
p('show widget')
|
||||||
|
global widgets
|
||||||
|
if len(widgets) < 1:
|
||||||
|
return
|
||||||
|
widget = randItem(widgets)
|
||||||
|
p(' %s' % widget)
|
||||||
|
widget.show()
|
||||||
|
|
||||||
|
def processEvents():
|
||||||
|
p('process events')
|
||||||
|
QTest.qWait(25)
|
||||||
|
|
||||||
|
class TstException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def raiseException():
|
||||||
|
p('raise exception')
|
||||||
|
raise TstException("A test exception")
|
||||||
|
|
||||||
|
def addReference():
|
||||||
|
p('add reference')
|
||||||
|
global widgets
|
||||||
|
if len(widgets) < 1:
|
||||||
|
return
|
||||||
|
obj1 = randItem(widgets)
|
||||||
|
obj2 = randItem(widgets)
|
||||||
|
p(' %s -> %s' % (obj1, obj2))
|
||||||
|
obj1._testref = obj2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test_stability()
|
Loading…
Reference in New Issue
Block a user