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