1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2007-2023 CEA/DEN, EDF R&D
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # Lesser General Public License for more details.
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
20 # Author : A. Bruneton
22 import unittest, sys, os, filecmp, shutil, tempfile
23 from pyqtside.QtWidgets import QApplication
24 from pyqtside.QtGui import QPixmap, QPainter
25 from pyqtside.QtCore import QTimer
27 from curveplot.PlotController import PlotController
28 from curveplot.XYView import XYView
29 from PyQt5.Qt import QMainWindow
32 func.__runOnly__ = True
35 def processDecorator(mod_name):
36 """ Little trick to be able to mark a test with the decorator
38 If one or more tests bear this decorator, only them will be run
42 for name, obj in inspect.getmembers(sys.modules[mod_name]):
43 if name == "PlotTest" and inspect.isclass(obj):
45 if p.startswith("test") and hasattr(obj.__dict__[p], "__runOnly__"):
49 for name, obj in inspect.getmembers(sys.modules[mod_name]):
50 if name == "PlotTest" and inspect.isclass(obj):
53 if p.startswith("test") and not hasattr(obj.__dict__[p], "__runOnly__"):
56 class PlotTestBase(unittest.TestCase):
57 """ Unit test suite for the curve plotter. This class deals with the set up and the screenshot generation/
58 comparison. The tests themselves are stored in the derived class PlotTest below.
60 The class variable below can be turned on to regenerate base line files (reference images).
61 All baselines start with the name of the corresponding test, plus a possible suffix.
62 The baselines directory is set relative to the path of this script.
64 REBUILD_BASELINES = False
66 __BASE_LINE_DIR = "baselines"
69 def __init__(self, methodName):
70 unittest.TestCase.__init__(self, methodName)
72 if self.REBUILD_BASELINES:
73 self.tmpBaselineDir = os.path.join(tempfile.gettempdir(), "curveplot_baselines")
74 if not os.path.isdir(self.tmpBaselineDir):
75 os.mkdir(self.tmpBaselineDir)
76 print("### Rebuilding base lines. Reference files will be saved to '%s'" % self.tmpBaselineDir)
78 PlotController.WITH_CURVE_BROWSER = True
79 XYView._DEFAULT_LEGEND_STATE = True # always show legend by default
80 self._this_dir = os.path.dirname(os.path.realpath(__file__))
84 from curveplot.SalomePyQt_MockUp import SalomePyQt
85 from curveplot.TableModel import TableModel
86 from curveplot.CurveModel import CurveModel
87 from curveplot.XYPlotSetModel import XYPlotSetModel
88 from curveplot.TestDesktop import TestDesktop
92 if not self.REBUILD_BASELINES:
93 self.tmpDir = tempfile.mkdtemp(prefix="curveplot_tests")
98 self.app = QApplication(sys.argv)
99 desktop = TestDesktop(None)
100 self.sgPyQt = SalomePyQt(desktop)
101 desktop._sgPyQt = self.sgPyQt
103 self.plotController = PlotController.GetInstance(self.sgPyQt)
104 desktop.resize(800, 600)
106 self._execQtWasCalled = False # Used to automatically finish Qt execution loop on tests not doing a screenshot
108 # Reset some class var to make sure IDs appearing in screenshots do not depend on test seq order:
109 CurveModel.START_ID = -1
110 TableModel.START_ID = -1
111 XYPlotSetModel.START_ID = -1
114 if not self.REBUILD_BASELINES:
115 # Clean up temp dir where the file comparison has been made:
117 shutil.rmtree(self.tmpDir, False)
118 if not self._execQtWasCalled:
119 self._execQt(withShot=False)
120 PlotController.Destroy()
122 def getTestName(self):
123 return self.id().split(".")[-1]
125 def saveCurrentPix(self, direct, suffix):
126 fileName = os.path.join(direct, self.getTestName() + suffix + "." + self.__FORMAT)
127 self.qpixmap.save(fileName, self.__FORMAT)
130 def _execQt(self, withShot=False):
132 QTimer.singleShot(50, self._shotEvent) # take picture
133 QTimer.singleShot(200, self.app.quit) # quit
134 self.app.exec_() # will hang till quit is fired
136 # Important make sure previous app is properly killed before launching next test!
137 # Qt doesn't like multiple running instance
141 def areScreenshotEqual(self, widget):
142 """ Finish the launching of the Qt application so that the widgets and the curve have a chance to display
143 and trigger snapshot comparison """
145 self._execQtWasCalled = True
146 self._execQt(withShot=True)
149 def _shotEvent(self):
150 self.retValue = self._snapAndCompare(self.saveW)
152 def _snapAndCompare(self, widget, suffix=""):
153 """ Test equality between a reference file saved in the baseline directory, and whose name is built as
154 "<test_name><suffix>.png"
155 and the file generated on the fly by taking a snapshot of the widget provided in argument.
156 The comparison is made in a temp dir which is kept if the file differ.
160 self.qpixmap = QPixmap(widget.size())
162 widget.render(self.qpixmap)
163 #self.qpixmap = widget.grab()
165 # Nothing to compare if rebuilding base lines, just saving file:
166 if self.REBUILD_BASELINES:
167 self.saveCurrentPix(self.tmpBaselineDir, suffix)
170 gen_path = self.saveCurrentPix(self.tmpDir, suffix)
171 base_ref = os.path.join(self._this_dir, self.__BASE_LINE_DIR, self.getTestName() + suffix)
173 for ref_path in glob.glob("%s_*.%s" % (base_ref, self.__FORMAT)):
175 ret = filecmp.cmp(ref_path, gen_path, shallow=False)
181 # Keep file if assert is false
183 print("[%s] -- Failed screenshot equality, or unable to open baseline file - directory is kept alive: %s" % (self.getTestName(), self.tmpDir))
186 def showTabWidget(self):
187 tabW = self.plotController._sgPyQt._tabWidget
188 # No simpler way found so far:
192 def getBrowserWidget(self):
193 return self.plotController._curveBrowserView._treeWidget