Package test_suite :: Package gui_tests :: Module base_classes
[hide private]
[frames] | no frames]

Source Code for Module test_suite.gui_tests.base_classes

  1  ############################################################################### 
  2  #                                                                             # 
  3  # Copyright (C) 2006-2008,2011-2012,2014-2015 Edward d'Auvergne               # 
  4  #                                                                             # 
  5  # This file is part of the program relax (http://www.nmr-relax.com).          # 
  6  #                                                                             # 
  7  # This program is free software: you can redistribute it and/or modify        # 
  8  # it under the terms of the GNU General Public License as published by        # 
  9  # the Free Software Foundation, either version 3 of the License, or           # 
 10  # (at your option) any later version.                                         # 
 11  #                                                                             # 
 12  # This program 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               # 
 15  # GNU General Public License for more details.                                # 
 16  #                                                                             # 
 17  # You should have received a copy of the GNU General Public License           # 
 18  # along with this program.  If not, see <http://www.gnu.org/licenses/>.       # 
 19  #                                                                             # 
 20  ############################################################################### 
 21   
 22  # Module docstring. 
 23  """Base classes for the GUI tests.""" 
 24   
 25  # Python module imports. 
 26  from math import pi    # This is needed for relax scripts as pi is located in the relax prompt namespace. 
 27  from os import sep 
 28  from tempfile import mkdtemp, mkstemp 
 29  from unittest import TestCase 
 30  import wx 
 31   
 32  # relax module imports. 
 33  from data_store import Relax_data_store; ds = Relax_data_store() 
 34  from gui.controller import Controller 
 35  from gui.relax_gui import Main 
 36  from gui.string_conv import str_to_gui 
 37  from gui.uf_objects import Uf_storage; uf_store = Uf_storage() 
 38  from gui.wizards.wiz_objects import Wiz_window 
 39  from lib.compat import queue 
 40  from lib.errors import RelaxError 
 41  from pipe_control.reset import reset 
 42  from prompt.interpreter import exec_script 
 43  from status import Status; status = Status() 
 44  from test_suite.clean_up import deletion 
 45  from user_functions.data import Uf_info; uf_info = Uf_info() 
 46   
 47   
48 -class GuiTestCase(TestCase):
49 """The GUI test base class.""" 50
51 - def __init__(self, methodName=None):
52 """Set up the test case class for the system tests.""" 53 54 # A string used for classifying skipped tests. 55 if not hasattr(self, '_skip_type'): 56 self._skip_type = 'gui' 57 58 # Execute the TestCase __init__ method. 59 super(GuiTestCase, self).__init__(methodName)
60 61
62 - def _execute_uf(self, *args, **kargs):
63 """Execute the given user function. 64 65 @keyword uf_name: The name of the user function. 66 @type uf_name: str 67 """ 68 69 # Checks. 70 if 'uf_name' not in kargs: 71 raise RelaxError("The user function name argument 'uf_name' has not been supplied.") 72 73 # Process the user function name. 74 uf_name = kargs.pop('uf_name') 75 76 # Get the user function data object. 77 uf_data = uf_info.get_uf(uf_name) 78 79 # Convert the args into keyword args. 80 for i in range(len(args)): 81 # The keyword name for this arg. 82 name = uf_data.kargs[i]['name'] 83 84 # Check. 85 if name in kargs: 86 raise RelaxError("The argument '%s' clashes with the %s keyword argument of '%s'." % (arg[i], name, kargs[name])) 87 88 # Set the keyword arg. 89 kargs[name] = args[i] 90 91 # Add the keyword args not supplied, using the default value. 92 for i in range(len(uf_data.kargs)): 93 # Alias. 94 arg = uf_data.kargs[i] 95 96 # Already set. 97 if arg['name'] in kargs: 98 continue 99 100 # Set the default. 101 kargs[arg['name']] = arg['default'] 102 103 # Merge the file and directory args, as needed. 104 for i in range(len(uf_data.kargs)): 105 # Alias. 106 arg = uf_data.kargs[i] 107 108 # File selection and associated directory arg. 109 if arg['arg_type'] == 'dir' and arg['name'] in kargs: 110 # Find the associated file selection arg name. 111 for j in range(len(uf_data.kargs)): 112 if uf_data.kargs[j]['arg_type'] in ['file sel read', 'file sel write']: 113 file_sel_name = uf_data.kargs[j]['name'] 114 115 # Prepend the directory to the file, if needed and supplied. 116 if file_sel_name in kargs and kargs[arg['name']]: 117 kargs[file_sel_name] = kargs[arg['name']] + sep + kargs[file_sel_name] 118 119 # Remove the directory argument. 120 kargs.pop(arg['name']) 121 122 # The user function object. 123 uf = uf_store[uf_name] 124 125 # Force synchronous operation of the user functions. 126 status.gui_uf_force_sync = True 127 128 # Call the GUI user function object with all keyword args, but do not execute the wizard. 129 uf(wx_wizard_run=False, **kargs) 130 131 # Execute the user function, by mimicking a click on 'ok'. 132 uf.wizard._ok() 133 134 # Restore the synchronous or asynchronous operation of the user functions so the GUI can return to normal. 135 status.gui_uf_force_sync = False 136 137 # Destroy the user function object. 138 uf.Destroy()
139 140
141 - def check_exceptions(self):
142 """Check that no exception has occurred.""" 143 144 # Check. 145 try: 146 # Get the exception from the queue. 147 index, exc = status.exception_queue.get(block=False) 148 149 # Print out. 150 print("Exception found, failing the test with an AssertionError:\n") 151 152 # Fail. 153 self.fail() 154 155 # No exception. 156 except queue.Empty: 157 pass
158 159
160 - def clean_up_windows(self):
161 """Kill all windows.""" 162 163 # Close all windows to unregister the observer objects. 164 if hasattr(self.app.gui, 'pipe_editor'): 165 self.app.gui.pipe_editor.Close() 166 if hasattr(self.app.gui, 'results_viewer'): 167 self.app.gui.results_viewer.Close() 168 if hasattr(self.app.gui, 'relax_prompt'): 169 self.app.gui.relax_prompt.Close() 170 wx.Yield() 171 172 # Destroy all user function windows to save memory (specifically to avoid the 10,000 USER Object limit in MS Windows). 173 for name in uf_store: 174 uf_store[name].Destroy() 175 176 # Kill the spin viewer window. 177 if hasattr(self.app.gui, 'spin_viewer'): 178 self.app.gui.spin_viewer.Destroy() 179 wx.Yield() 180 del self.app.gui.spin_viewer 181 182 # Kill the pipe editor window. 183 if hasattr(self.app.gui, 'pipe_editor'): 184 self.app.gui.pipe_editor.Destroy() 185 wx.Yield() 186 del self.app.gui.pipe_editor 187 188 # Kill the results viewer window. 189 if hasattr(self.app.gui, 'results_viewer'): 190 self.app.gui.results_viewer.Destroy() 191 wx.Yield() 192 del self.app.gui.results_viewer 193 194 # Kill the relax prompt window. 195 if hasattr(self.app.gui, 'relax_prompt'): 196 self.app.gui.relax_prompt.Destroy() 197 wx.Yield() 198 del self.app.gui.relax_prompt
199 200
201 - def new_analysis_wizard(self, analysis_type=None, analysis_name=None, pipe_name=None, pipe_bundle=None):
202 """Simulate the new analysis wizard, and return the analysis page. 203 204 @keyword analysis_type: The type of the analysis to use in the first wizard page. 205 @type analysis_type: str 206 @keyword analysis_name: The name of the analysis to use in the first wizard page. 207 @type analysis_name: str 208 @keyword pipe_name: The name of the data pipe to create, if different from the default. 209 @type pipe_name: None or str 210 @keyword pipe_bundle: The name of the data pipe bundle to create, if different from the default. 211 @type pipe_bundle: None or str 212 """ 213 214 # Simulate the menu selection, but don't destroy the GUI element. 215 self.app.gui.analysis.menu_new(None, destroy=False) 216 217 # The first page. 218 page = self.app.gui.analysis.new_wizard.wizard.get_page(0) 219 if analysis_type == 'noe': 220 page.select_noe(None) 221 elif analysis_type == 'r1': 222 page.select_r1(None) 223 elif analysis_type == 'r2': 224 page.select_r2(None) 225 elif analysis_type == 'mf': 226 page.select_mf(None) 227 elif analysis_type == 'disp': 228 page.select_disp(None) 229 else: 230 raise RelaxError("Unknown analysis type '%s'." % analysis_type) 231 if analysis_name: 232 page.analysis_name.SetValue(str_to_gui(analysis_name)) 233 self.app.gui.analysis.new_wizard.wizard._go_next(None) 234 235 # The second page. 236 page = self.app.gui.analysis.new_wizard.wizard.get_page(1) 237 if pipe_name: 238 page.pipe_name.SetValue(str_to_gui(pipe_name)) 239 if pipe_bundle: 240 page.pipe_bundle.SetValue(str_to_gui(pipe_bundle)) 241 self.app.gui.analysis.new_wizard.wizard._go_next(None) 242 243 # Get the data, then clean up. 244 analysis_type, analysis_name, pipe_name, pipe_bundle, uf_exec = self.app.gui.analysis.new_wizard.get_data() 245 246 # Wizard cleanup. 247 wx.Yield() 248 self.app.gui.analysis.new_wizard.Destroy() 249 del self.app.gui.analysis.new_wizard 250 251 # Set up the analysis. 252 self.app.gui.analysis.new_analysis(analysis_type=analysis_type, analysis_name=analysis_name, pipe_name=pipe_name, pipe_bundle=pipe_bundle) 253 254 # Return the analysis page. 255 return self.app.gui.analysis.get_page_from_name(analysis_name)
256 257
258 - def script_exec(self, script):
259 """Execute a GUI script within the GUI test framework. 260 261 @param script: The full path of the script to execute. 262 @type script: str 263 """ 264 265 # The namespace to pass into the script execution environment. 266 space = locals() 267 268 # Place some objects in the local namespace. 269 space.update({'pi': pi}) 270 271 # Execute the script. 272 exec_script(script, space)
273 274
275 - def setUp(self):
276 """Set up for all the functional tests.""" 277 278 # Create a temporary file for the tests that need it. 279 ds.tmpfile_handle, ds.tmpfile = mkstemp() 280 281 # Create a temporary directory for the results. 282 ds.tmpdir = mkdtemp() 283 284 # Get the wx app. 285 self.app = wx.GetApp()
286 287
288 - def tearDown(self):
289 """Default tearDown operation - delete temp directories and files and reset relax.""" 290 291 # Flush all wx events prior to the clean up operations of this method. This prevents these events from occurring after the GUI elements have been deleted. 292 wx.Yield() 293 294 # Remove the temporary directory and variable. 295 deletion(obj=ds, name='tmpdir', dir=True) 296 deletion(obj=self, name='tmpdir', dir=True) 297 298 # Remove temporary file and variable. 299 deletion(obj=ds, name='tmpfile', dir=False) 300 deletion(obj=self, name='tmpfile', dir=False) 301 302 # Reset relax. 303 reset() 304 305 # Get the wx app. 306 self.app = wx.GetApp() 307 308 # Kill all windows. 309 self.clean_up_windows() 310 311 # Print out a list of all living windows to help ensure that custom Close() and Destroy() methods are cleaning up all objects. 312 print("\n\nList of all living GUI elements - this must only include the main GUI window and the relax controller:") 313 all_destroyed = True 314 for window in wx.GetTopLevelWindows(): 315 # Printout. 316 print(" Window: %s" % window) 317 if isinstance(window, Wiz_window): 318 print(" Wizard title: %s" % window.title) 319 print(" Wizard pages: %s" % window._pages) 320 321 # Skip the main GUI window and the relax controller. 322 if isinstance(window, Main) or isinstance(window, Controller): 323 continue 324 325 # Failure of memory management. 326 all_destroyed = False 327 print("\n\n\n")
328 329 # Memory management check. 330 #if not all_destroyed: 331 # raise RelaxError("Memory management failure - not all top level windows have been destroyed.") 332