Newer
Older
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from test_info import TestSuite, TestSingle, TestProject
from test_tree import TestTreeModel, TreeFilterProxyModel
#==================================================================================================
#==================================================================================================
class TestWorker(QtCore.QThread):
""" Class to run the tests in the background of the GUI"""
def __init__(self, parent = None):
QtCore.QThread.__init__(self, parent)
self.exiting = False
#-----------------------------------------------------------------------------
def set_parameters(self, mainWindow, selected_only=False, make_tests=True, parallel=False):
"""Set the parameters of how the tests will be built and run;
Returns: the number of steps in the runner.
"""
self.selected_only = selected_only
self.make_tests = make_tests
self.parallel = parallel
self.mainWindow = mainWindow
return test_info.all_tests.run_tests_computation_steps(selected_only, make_tests)
#-----------------------------------------------------------------------------
def test_run_callback(self, obj):
""" Simple callback for running tests. This is called into the MainProcess.
Parameters:
obj :: the object, either a TestSuite or TestProject that was just calculated
"""
if isinstance(obj, TestSuite):
suite = obj
text = "%s done." % suite.classname
test_info.all_tests.replace_suite(suite)
elif isinstance(obj, TestProject):
pj = obj
# Replace the project in THIS thread!
test_info.all_tests.replace_project( pj )
text = "Made project %s" % pj.name
text = str(obj)
self.mainWindow.emit( QtCore.SIGNAL("testRun"), text, obj)
#-----------------------------------------------------------------------------
def run(self):
print "Test Run started..."
test_info.all_tests.run_tests_in_parallel(self.selected_only, self.make_tests,
self.parallel, callback_func=self.test_run_callback)
#==================================================================================================
#==================================================================================================
class TestMonitorFilesWorker(QtCore.QThread):
""" Worker that runs in the background
and looks for modified files. """
def __init__(self, mainWindow, parent = None):
QtCore.QThread.__init__(self, parent)
self.mainWindow = mainWindow
self.exiting = False
self.monitor_tests = False
# Hoy many seconds to wait before checking again
self.delay = 1.0
def set_monitor_tests(self, state):
self.monitor_tests = state
#-----------------------------------------------------------------------------
def run(self):
print "Beginning to monitor files and/or libraries..."
while not self.exiting:
if not self.mainWindow.running:
# Don't monitor files while a test suite is building or running
if self.monitor_tests:
if test_info.all_tests.is_source_modified(selected_only=True):
self.mainWindow.emit( QtCore.SIGNAL("testMonitorChanged()") )
time.sleep(self.delay)
#==================================================================================================
#==================================================================================================
class TestViewerMainWindow(QtGui.QMainWindow, ui_main_window.Ui_MainWindow):
"""
The main GUI window of the test viewer.
"""
def __init__(self, settings=None, bin_folder="", source_folder="", parent=None):
# Save the settings object
self.settings = settings
self.bin_folder = bin_folder
self.source_folder = source_folder
# Populate all GUI elements
self.setupUi(self)
self.set_running(False)
# Create the worker
self.worker = TestWorker()
# Now the file monitor
self.monitor_worker = TestMonitorFilesWorker(self)
self.monitor_worker.start()
self.connect(self.worker, QtCore.SIGNAL("finished()"), self.complete_run)
self.connect(self.worker, QtCore.SIGNAL("terminated()"), self.complete_run)
# --- Menu Commands ---
self.connect(self.action_Quit, QtCore.SIGNAL("triggered()"), self.quit)
# -- Checkboxes toggle ----
Janik Zikovsky
committed
self.connect(self.checkShowFailedOnly, QtCore.SIGNAL("stateChanged(int)"), self.checked_show_fail_only)
self.connect(self.checkShowSelected, QtCore.SIGNAL("stateChanged(int)"), self.checked_show_selected_only)
self.connect(self.checkMonitorTests, QtCore.SIGNAL("stateChanged(int)"), self.checked_monitor_tests)
# -- Button commands ----
self.connect(self.buttonRunAll, QtCore.SIGNAL("clicked()"), self.run_all)
self.connect(self.buttonRunSelected, QtCore.SIGNAL("clicked()"), self.run_selected)
self.connect(self.buttonAbort, QtCore.SIGNAL("clicked()"), self.abort)
self.connect(self.buttonRefresh, QtCore.SIGNAL("clicked()"), self.setup_tree) #self.update_tree)
self.connect(self.buttonExpandProjects, QtCore.SIGNAL("clicked()"), self.expand_tree_to_projects)
self.connect(self.buttonExpandSuites, QtCore.SIGNAL("clicked()"), self.expand_tree_to_suites)
self.connect(self.buttonExpandAll, QtCore.SIGNAL("clicked()"), self.expand_tree_to_all)
self.connect(self.buttonTest, QtCore.SIGNAL("clicked()"), self.test)
Janik Zikovsky
committed
self.connect(self.buttonSelectAll, QtCore.SIGNAL("clicked()"), self.select_all)
self.connect(self.buttonSelectNone, QtCore.SIGNAL("clicked()"), self.select_none)
self.connect(self.buttonSelectFailed, QtCore.SIGNAL("clicked()"), self.select_failed)
self.connect(self.buttonSelectSVN, QtCore.SIGNAL("clicked()"), self.select_svn)
self.connect(self.buttonSelectByString, QtCore.SIGNAL("clicked()"), self.select_by_string)
Janik Zikovsky
committed
# -- Text commands ---
self.connect(self.textTimeout, QtCore.SIGNAL("textChanged()"), self.text_settings_changed)
self.connect(self.textMemory, QtCore.SIGNAL("textChanged()"), self.text_settings_changed)
Janik Zikovsky
committed
# --- Tree commands ----
#self.connect(self.treeTests, QtCore.SIGNAL("doubleClicked(QModelIndex)"), self.tree_doubleClicked)
self.connect(self.treeTests, QtCore.SIGNAL("doubleClicked(QModelIndex)"), self.tree_doubleClicked)
self.connect(self.treeTests, QtCore.SIGNAL("customContextMenuRequested(QPoint)"), self.tree_contextMenu)
# Signal that will be called by the worker thread
self.connect(self, QtCore.SIGNAL("testRun"), self.test_run_callback)
# Signal called by the monitor thread
self.connect(self, QtCore.SIGNAL("testMonitorChanged()"), self.run_selected)
self.last_tree_update = time.time()
self.last_console_update = time.time()
# The accumulated stdoutput from commands
self.stdout = ""
self.current_results = None
self.show_results()
#-----------------------------------------------------------------------------
def setup_tree(self):
""" Set up the QTreeWidget of the tree """
# Re-discover all the projects
# TODO: Use arguments for the source paths
test_info.all_tests.discover_CXX_projects(self.bin_folder, self.source_folder)
# Create the tree model and put it in there
# It is important to save the model in self otherwise I think it gets garbage collected without error!
self.model = TestTreeModel()
# It is probably smart to hold a ref. to the proxy object too
self.proxy = TreeFilterProxyModel()
self.proxy.setSourceModel(self.model)
self.proxy.setFilterWildcard("*G*")
# Set the tree to use the SORTING/FILTERING proxy of the real model
tree.setModel(self.proxy)
tree.setAlternatingRowColors(True)
tree.header().setResizeMode(0,QHeaderView.Stretch)
tree.header().setResizeMode(1,QHeaderView.Interactive)
tree.header().setResizeMode(2,QHeaderView.Interactive)
tree.connect( tree, QtCore.SIGNAL("clicked (QModelIndex)"), self.tree_clicked)
tree.connect( tree, QtCore.SIGNAL("activated (QModelIndex)"), self.tree_clicked)
self.connect( tree.selectionModel(), QtCore.SIGNAL("currentChanged (const QModelIndex &, const QModelIndex &)"), self.tree_selected)
#-----------------------------------------------------------------------------
Janik Zikovsky
committed
#-----------------------------------------------------------------------------
def tree_doubleClicked(self, index):
""" Double-clicked on tree at that QModelIndex """
print "CLICK ON MOUSE"
try:
object = self.treeTests.model().data( index, Qt.UserRole).toPyObject()
except:
return
if isinstance(object, TestSingle):
print "Running single test:", object.fullname
object.parent.run_tests(self.test_run_callback, object.name)
Janik Zikovsky
committed
def tree_contextMenu(self, point):
""" Open the context menu (if on an appropriate index
point :: location clicked on the tree.
"""
try:
index = self.treeTests.indexAt(point)
object = self.treeTests.model().data( index, Qt.UserRole).toPyObject()
except:
return
if isinstance(object, TestSuite):
menu = QMenu()
runTestsAction = menu.addAction("Run Tests Individually")
action = menu.exec_(self.treeTests.mapToGlobal(point))
if action == runTestsAction:
print "Running every test in %s individually" % object.name
for test in object.tests:
print "Running %s" % test.fullname
object.run_tests(self.test_run_callback, test.name)
Janik Zikovsky
committed
elif isinstance(object, TestSingle):
menu = QMenu()
runTestsAction = menu.addAction("Run Single Test")
action = menu.exec_(self.treeTests.mapToGlobal(point))
if action == runTestsAction:
print "Running single test:", object.fullname
object.parent.run_tests(self.test_run_callback, object.name)
#-----------------------------------------------------------------------------
def readSettings(self):
s = self.settings
self.checkInParallel.setChecked( s.value("checkInParallel", False).toBool() )
self.checkMonitorTests.setChecked( s.value("checkMonitorTests", False).toBool() )
if s.contains("splitter"): self.splitter.restoreState( s.value("splitter").toByteArray() )
self.resize( s.value("TestViewerMainWindow.width", 1500).toInt()[0],
s.value("TestViewerMainWindow.height", 900).toInt()[0] )
self.move( s.value("TestViewerMainWindow.x", 0).toInt()[0],
s.value("TestViewerMainWindow.y", 0).toInt()[0] )
column_default_width = [200, 230, 100]
for i in [1,2]:
self.treeTests.setColumnWidth( i, s.value("treeTests.columnWidth(%d)"%i, column_default_width[i]).toInt()[0] )
if self.treeTests.columnWidth(i) < 50:
self.treeTests.setColumnWidth( i, 50)
Janik Zikovsky
committed
# --- Text settings ---
memory = s.value("memory_limit_MB", 8000).toInt()[0]
test_info.memory_limit_kb = memory*1024
Janik Zikovsky
committed
self.textMemory.setPlainText("%d" % memory)
timeout = s.value("process_timeout_sec", 30).toInt()[0]
test_info.process_timeout_sec = timeout
Janik Zikovsky
committed
self.textTimeout.setPlainText("%d" % timeout)
self.select_by_string_lastValue = str(s.value("select_by_string_lastValue", "-Performance").toString())
#-----------------------------------------------------------------------------
def saveSettings(self):
s = self.settings
s.setValue("checkInParallel", self.checkInParallel.isChecked() )
s.setValue("checkMonitorTests", self.checkMonitorTests.isChecked() )
s.setValue("splitter", self.splitter.saveState())
for i in [1,2]:
s.setValue("treeTests.columnWidth(%d)"%i, self.treeTests.columnWidth(i) )
s.setValue("TestViewerMainWindow.width", self.width())
s.setValue("TestViewerMainWindow.height", self.height())
s.setValue("TestViewerMainWindow.x", self.x())
s.setValue("TestViewerMainWindow.y", self.y())
s.setValue("select_by_string_lastValue", self.select_by_string_lastValue)
Janik Zikovsky
committed
#-----------------------------------------------------------------------------
def get_int_from_text(self, plainTextBox):
""" Get a value from a text box. Color it red and return None if it is bad."""
try:
s = plainTextBox.toPlainText()
value = int(s)
plainTextBox.setStyleSheet("QPlainTextEdit { background-color: white; }")
Janik Zikovsky
committed
return value
except:
plainTextBox.setStyleSheet("QPlainTextEdit { background-color: tomato; }")
Janik Zikovsky
committed
return None
Janik Zikovsky
committed
#-----------------------------------------------------------------------------
def text_settings_changed(self):
""" called when one of the text boxes changes """
s = self.settings
memory = self.get_int_from_text(self.textMemory)
Janik Zikovsky
committed
if not memory is None:
test_info.memory_limit_kb = memory*1024
Janik Zikovsky
committed
s.setValue("memory_limit_MB", memory)
timeout = self.get_int_from_text(self.textTimeout)
Janik Zikovsky
committed
if not timeout is None:
test_info.process_timeout_sec = timeout
Janik Zikovsky
committed
s.setValue("process_timeout_sec", timeout)
#-----------------------------------------------------------------------------
def closeEvent(self, event):
"""Window is closing """
# Save all settings that haven't been saved before
self.saveSettings()
#-----------------------------------------------------------------------------
def quit(self):
""" Exit the program """
test_info.all_tests.abort()
print "Exiting TestViewer. Happy coding!"
self.close()
#-----------------------------------------------------------------------------
def expand_tree_to_projects(self):
""" Collapse suites so that only the top projects are shown """
# Number of root projects
rowcount = self.treeTests.model().rowCount( QModelIndex() )
for i in xrange(rowcount):
# Collapse each project-level one
self.treeTests.setExpanded( self.treeTests.model().index(i, 0, QModelIndex() ), False)
#-----------------------------------------------------------------------------
def expand_tree_to_suites(self):
""" Collapse single test results but expand projects """
# Number of root projects
rowcount = self.treeTests.model().rowCount( QModelIndex() )
for i in xrange(rowcount):
# Expand each project-level one
indx = self.treeTests.model().index(i, 0, QModelIndex() )
self.treeTests.setExpanded( indx, True)
# And go through the suites and collapse those
num_suites = self.treeTests.model().rowCount( indx)
for j in xrange(num_suites):
suite_indx = self.treeTests.model().index(j, 0, indx )
self.treeTests.setExpanded( suite_indx, False)
#-----------------------------------------------------------------------------
def expand_tree_to_all(self):
""" Expand all tree items """
self.treeTests.expandAll()
#-----------------------------------------------------------------------------
def markup_console(self, in_line):
"""Return a marked-up (with HTML) version of a line
from the consolue. """
Janik Zikovsky
committed
# Replace any < with HTML goodness
line = test_info.html_escape(in_line)
if ("Error" in line) or ("error:" in line) \
or ("terminate called after throwing an instance" in line) \
or ("Segmentation fault" in line) \
or (" what(): " in line) \
or ("Assertion" in line and " failed." in line) \
Janik Zikovsky
committed
or ("undefined reference" in line) \
:
#An error line!
color = "red"
elif "warning:" in line or "Warning:" in line:
#Just a warning
color = "orange"
else:
#Print normally
color = ""
Janik Zikovsky
committed
# Bolden any lines with --- in them
if line.startswith("---"):
line = "<b>%s</b>" % line
# Add the color tag
if color != "":
line = '<font color="%s">%s</font>' % (color, line)
# Add a new line
Janik Zikovsky
committed
#-----------------------------------------------------------------------------
def test_stdout_callback(self, line):
""" Add a line to stdoutput from test running """
pass
#-----------------------------------------------------------------------------
def test_run_callback(self, text, obj):
""" Update the progress bar and label when a calculation is done (callback from test runner.
Parameters:
obj: either a TestSuite or a TestProject;
if it is a string, then it is the stdout of the make command"""
# Update the tree's data for the suite
if not obj is None:
# For test results, increment the progress bar
if isinstance(obj, TestSingle) or isinstance(obj, TestSuite) or isinstance(obj, TestProject):
val = self.progTest.value()+1
self.progTest.setValue( val )
self.progTest.setFormat("%p% : " + text)
# Try to update the current results shown
if not self.current_results is None:
if obj.get_fullname() == self.current_results.get_fullname():
self.show_results()
if isinstance(obj, TestSuite):
Janik Zikovsky
committed
#print "Updating GUI of TestSuite", obj.name
self.model.update_suite(obj)
elif isinstance(obj, TestProject):
Janik Zikovsky
committed
# Updating the project is now redundant since the test status string is made dynamically
pass
#self.model.update_project(obj.name)
elif isinstance(obj, basestring):
# String was returned
if obj == test_info.MSG_ALL_BUILDS_SUCCESSFUL:
# Special message that all builds were good
Janik Zikovsky
committed
# Go back to the test results tab; unless you are NOT running in parallel.
if self.checkInParallel.isChecked():
self.tabWidgetRight.setCurrentIndex(0)
else:
# Accumulated stdout
self.stdout += self.markup_console( unicode(obj) )
if (time.time() - self.last_console_update) > 1.5: # Dont update more often than this:
self.textConsole.setText( u'<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n' +
self.stdout )
sb = self.textConsole.verticalScrollBar()
sb.setValue(sb.maximum())
self.last_console_update = time.time()
#-----------------------------------------------------------------------------
def quick_update_tree(self):
""" Update the tree view without resetting all the model data """
self.treeTests.model().update_all()
#-----------------------------------------------------------------------------
def set_running(self, running):
""" Sets whether a test suite is running. Adjusts the GUI as needed"""
self.running=running
self.buttonAbort.setEnabled(running)
self.buttonRunAll.setEnabled(not running)
self.buttonRunSelected.setEnabled(not running)
#-----------------------------------------------------------------------------
def complete_run(self):
""" Event called when completing/aborting a test run """
self.set_running(False)
self.progTest.setValue(0)
self.progTest.setFormat("")
num_run = test_info.all_tests.num_run
if failed > 0:
self.labelResult.setStyleSheet("QLabel { background-color: red }")
self.labelResult.setText("FAILURE! %d of %d tests failed." % (failed, num_run))
else:
self.labelResult.setStyleSheet("QLabel { background-color: green }")
self.labelResult.setText("All Passed! %d of %d tests failed." % (failed, num_run))
#-----------------------------------------------------------------------------
def update_tree(self):
""" Update the tree view with whatever the current results are """
self.model.setupModelData()
self.show_results()
#-----------------------------------------------------------------------------
def tree_clicked(self, index):
""" A row of the tree was clicked. Show the test results.
@param index :: QAbstracItemModel index into the tree.
"""
# Retrieve the item at that index
# (returns a QVariant that you have to bring back to a python object)
item = self.treeTests.model().data( index, Qt.UserRole).toPyObject()
self.current_results = item
self.show_results()
#-----------------------------------------------------------------------------
def tree_selected(self, current_index, previous_index):
""" Selection in the tree changed from previous_index to current_index """
item = self.treeTests.model().data( current_index, Qt.UserRole).toPyObject()
self.current_results = item
self.show_results()
#-----------------------------------------------------------------------------
def show_results(self):
""" Show the test results.
@param res :: either TestProject, TestSuite, or TestSingle object containing the results to show."""
if self.current_results is None:
self.labelTestType.setText("Test Project Results:")
self.labelTestName.setText( "" )
self.textResults.setText( "" )
return
res = self.current_results
if isinstance(res, TestProject):
self.labelTestType.setText("Test Project Results:")
elif isinstance(res, TestSuite):
self.labelTestType.setText("Test Suite Results:")
elif isinstance(res, TestSingle):
self.labelTestType.setText("Single Test Results:")
raise RuntimeError("Incorrect object passed to show_results; should be TestProject, TestSuite, or TestSingle.")
self.labelTestName.setText( res.get_fullname() )
self.labelTestName.hide()
self.textResults.setText( u'<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n'
Janik Zikovsky
committed
+ res.get_results_text() )
#-----------------------------------------------------------------------------
Janik Zikovsky
committed
def checked_show_fail_only(self, state):
""" Toggle the filtering """
self.proxy.set_filter_failed_only(state > 0)
Janik Zikovsky
committed
def checked_show_selected_only(self, state):
""" Toggle the filtering """
self.proxy.set_filter_selected_only(state > 0)
def checked_monitor_libraries(self, state):
self.monitor_worker.set_monitor_libraries( state > 0 )
def checked_monitor_tests(self, state):
self.monitor_worker.set_monitor_tests( state > 0 )
#-----------------------------------------------------------------------------
def run_all(self):
self.do_run(False)
self.do_run(True)
def do_run(self, selected_only):
""" Start a parallelized test running with the given parameters """
parallel = self.checkInParallel.isChecked()
# Do some setup of the worker and GUI
num_steps = self.worker.set_parameters(self, selected_only=selected_only, make_tests=True, parallel=parallel)
if num_steps < 1:
num_steps = 1
self.progTest.setValue(0)
self.progTest.setMaximum( num_steps )
self.set_running(True)
Janik Zikovsky
committed
self.textConsole.setText(self.stdout)
# Select the console output tab (for the make output) self.running=True
self.tabWidgetRight.setCurrentIndex(1)
# Begin the thread in the background
self.worker.start()
#-----------------------------------------------------------------------------
def abort(self):
test_info.all_tests.abort()
Janik Zikovsky
committed
#-----------------------------------------------------------------------------
def select_all(self):
""" Select all tests """
test_info.all_tests.select_all(True)
self.proxy.invalidateFilter()
self.treeTests.update()
Janik Zikovsky
committed
def select_none(self):
""" De-Select all tests """
test_info.all_tests.select_all(False)
self.proxy.invalidateFilter()
self.treeTests.update()
Janik Zikovsky
committed
def select_failed(self):
""" Select all failing tests """
test_info.all_tests.select_failed()
self.proxy.invalidateFilter()
self.treeTests.update()
def select_svn(self):
""" Select all files modified by SVN/git st """
test_info.all_tests.select_svn()
self.proxy.invalidateFilter()
self.treeTests.update()
def select_by_string(self):
""" Use a string to select """
dlg = QtGui.QInputDialog(self)
dlg.setLabelText("Select tests by strings, separated by spaces. Use a '-' before a word to select all except that word.")
dlg.setTextValue(self.select_by_string_lastValue)
dlg.setModal(True)
dlg.exec_()
self.select_by_string_lastValue = str(dlg.textValue())
test_info.all_tests.select_by_string(self.select_by_string_lastValue)
self.proxy.invalidateFilter()
self.treeTests.update()
# Start the settings object.
# TODO: Change the company name here and in MantidPlot
settings = QSettings("ISIS", "MantidTestViewer")
settings_bin_folder = str(settings.value("bin_folder", "../../Mantid/bin").toString())
settings_source_folder = str(settings.value("source_folder", "../../Mantid/Framework").toString())
if len(sys.argv) > 1:
bin_folder = sys.argv[1]
if len(sys.argv) > 2:
source_folder = sys.argv[2]
if bin_folder == "--help":
print """TestViewer.py [BINFOLDER] [SOURCEFOLDER]
GUI to run and view Mantid tests.
BINFOLDER Path to the bin/ folder that contains the test executables.
Will use the last path used if not specified:
SOURCEFOLDER Path to the root of the framework source folder.
Will use the last path used if not specified:
%s
""" % (settings_bin_folder, settings_source_folder)
sys.exit()
# No command line arguments? Get them from the settings
if bin_folder == "":
bin_folder = settings_bin_folder
source_folder = settings_source_folder
else:
# Use whatever was given.
settings.setValue("bin_folder", bin_folder)
if source_folder == "":
# No source folder given? Use the old one
if settings.contains("source_folder"):
source_folder = str(settings.value("source_folder", "../../Mantid/Framework").toString())
else:
Janik Zikovsky
committed
# Try a relative source folder if not specified
source_folder = os.path.join( bin_folder, "/../Framework" )
else:
# Use whatever was given
settings.setValue("source_folder", source_folder)
print "Starting TestViewer ..."
print "... bin folder is", bin_folder
print "... source folder is", source_folder
print "."
app = QtGui.QApplication([])
app.setOrganizationName("Mantid")
app.setOrganizationDomain("mantidproject.org")
app.setApplicationName("Mantid Test Viewer")
main = TestViewerMainWindow(settings, bin_folder, source_folder)
app.exec_()
if __name__ == '__main__':
start()