Fixed a few exit crashes, added unit tests to cover them

This commit is contained in:
Luke Campagnola 2014-11-26 21:25:17 -05:00
parent 2bf4a0eb7b
commit f6ded808ef
7 changed files with 75 additions and 9 deletions

View File

@ -84,8 +84,8 @@ class GraphicsScene(QtGui.QGraphicsScene):
cls._addressCache[sip.unwrapinstance(sip.cast(obj, QtGui.QGraphicsItem))] = obj
def __init__(self, clickRadius=2, moveDistance=5):
QtGui.QGraphicsScene.__init__(self)
def __init__(self, clickRadius=2, moveDistance=5, parent=None):
QtGui.QGraphicsScene.__init__(self, parent)
self.setClickRadius(clickRadius)
self.setMoveDistance(moveDistance)
self.exportDirectory = None

View File

@ -270,7 +270,12 @@ from .Qt import isQObjectAlive
## Attempts to work around exit crashes:
import atexit
_cleanupCalled = False
def cleanup():
global _cleanupCalled
if _cleanupCalled:
return
if not getConfigOption('exitCleanup'):
return
@ -295,8 +300,22 @@ def cleanup():
s.addItem(o)
except RuntimeError: ## occurs if a python wrapper no longer has its underlying C++ object
continue
_cleanupCalled = True
atexit.register(cleanup)
# Call cleanup when QApplication quits. This is necessary because sometimes
# the QApplication will quit before the atexit callbacks are invoked.
# Note: cannot connect this function until QApplication has been created, so
# instead we have GraphicsView.__init__ call this for us.
_cleanupConnected = False
def _connectCleanup():
global _cleanupConnected
if _cleanupConnected:
return
QtGui.QApplication.instance().aboutToQuit.connect(cleanup)
_cleanupConnected = True
## Optional function for exiting immediately (with some manual teardown)
def exit():

View File

@ -49,7 +49,7 @@ class HistogramLUTItem(GraphicsWidget):
self.setLayout(self.layout)
self.layout.setContentsMargins(1,1,1,1)
self.layout.setSpacing(0)
self.vb = ViewBox()
self.vb = ViewBox(parent=self)
self.vb.setMaximumWidth(152)
self.vb.setMinimumWidth(45)
self.vb.setMouseEnabled(x=False, y=True)
@ -59,7 +59,7 @@ class HistogramLUTItem(GraphicsWidget):
self.region = LinearRegionItem([0, 1], LinearRegionItem.Horizontal)
self.region.setZValue(1000)
self.vb.addItem(self.region)
self.axis = AxisItem('left', linkView=self.vb, maxTickLength=-10)
self.axis = AxisItem('left', linkView=self.vb, maxTickLength=-10, parent=self)
self.layout.addItem(self.axis, 0, 0)
self.layout.addItem(self.vb, 0, 1)
self.layout.addItem(self.gradient, 0, 2)

View File

@ -145,7 +145,7 @@ class PlotItem(GraphicsWidget):
self.layout.setVerticalSpacing(0)
if viewBox is None:
viewBox = ViewBox()
viewBox = ViewBox(parent=self)
self.vb = viewBox
self.vb.sigStateChanged.connect(self.viewStateChanged)
self.setMenuEnabled(enableMenu, enableMenu) ## en/disable plotitem and viewbox menus
@ -168,14 +168,14 @@ class PlotItem(GraphicsWidget):
axisItems = {}
self.axes = {}
for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))):
axis = axisItems.get(k, AxisItem(orientation=k))
axis = axisItems.get(k, AxisItem(orientation=k, parent=self))
axis.linkToView(self.vb)
self.axes[k] = {'item': axis, 'pos': pos}
self.layout.addItem(axis, *pos)
axis.setZValue(-1000)
axis.setFlag(axis.ItemNegativeZStacksBehindParent)
self.titleLabel = LabelItem('', size='11pt')
self.titleLabel = LabelItem('', size='11pt', parent=self)
self.layout.addItem(self.titleLabel, 0, 1)
self.setTitle(None) ## hide

View File

@ -1696,6 +1696,8 @@ class ViewBox(GraphicsWidget):
def forgetView(vid, name):
if ViewBox is None: ## can happen as python is shutting down
return
if QtGui.QApplication.instance() is None:
return
## Called with ID and name of view (the view itself is no longer available)
for v in list(ViewBox.AllViews.keys()):
if id(v) == vid:

View File

@ -0,0 +1,38 @@
import os, sys, subprocess, tempfile
import pyqtgraph as pg
code = """
import sys
sys.path.insert(0, '{path}')
import pyqtgraph as pg
app = pg.mkQApp()
w = pg.{classname}({args})
"""
def test_exit_crash():
# For each Widget subclass, run a simple python script that creates an
# instance and then shuts down. The intent is to check for segmentation
# faults when each script exits.
tmp = tempfile.mktemp(".py")
path = os.path.dirname(pg.__file__)
initArgs = {
'CheckTable': "[]",
'ProgressDialog': '"msg"',
'VerticalLabel': '"msg"',
}
for name in dir(pg):
obj = getattr(pg, name)
if not isinstance(obj, type) or not issubclass(obj, pg.QtGui.QWidget):
continue
print name
argstr = initArgs.get(name, "")
open(tmp, 'w').write(code.format(path=path, classname=name, args=argstr))
proc = subprocess.Popen([sys.executable, tmp])
assert proc.wait() == 0
os.remove(tmp)

View File

@ -71,6 +71,13 @@ class GraphicsView(QtGui.QGraphicsView):
QtGui.QGraphicsView.__init__(self, parent)
# This connects a cleanup function to QApplication.aboutToQuit. It is
# called from here because we have no good way to react when the
# QApplication is created by the user.
# See pyqtgraph.__init__.py
from .. import _connectCleanup
_connectCleanup()
if useOpenGL is None:
useOpenGL = getConfigOption('useOpenGL')
@ -102,7 +109,8 @@ class GraphicsView(QtGui.QGraphicsView):
self.currentItem = None
self.clearMouse()
self.updateMatrix()
self.sceneObj = GraphicsScene()
# GraphicsScene must have parent or expect crashes!
self.sceneObj = GraphicsScene(parent=self)
self.setScene(self.sceneObj)
## Workaround for PySide crash
@ -143,7 +151,6 @@ class GraphicsView(QtGui.QGraphicsView):
def paintEvent(self, ev):
self.scene().prepareForPaint()
#print "GV: paint", ev.rect()
return QtGui.QGraphicsView.paintEvent(self, ev)
def render(self, *args, **kwds):