コード例 #1
0
    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)

        tree = self.treeTests
        #@type tree QTreeWidget

        # 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)
コード例 #2
0
 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)
     
     tree = self.treeTests
     #@type tree QTreeWidget
     
     # 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)
コード例 #3
0
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):      
        QtGui.QWidget.__init__(self, parent)
        
        # 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.buttonTest.hide()
        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  ----
        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.checkMonitorLibraries, QtCore.SIGNAL("stateChanged(int)"), self.checked_monitor_libraries)
        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)
        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)
        
        # -- Text commands ---
        self.connect(self.textTimeout, QtCore.SIGNAL("textChanged()"), self.text_settings_changed) 
        self.connect(self.textMemory, QtCore.SIGNAL("textChanged()"), self.text_settings_changed) 
        
        # --- 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.setup_tree()
        
        self.current_results = None
        self.show_results()
        
        self.readSettings()
        
    #-----------------------------------------------------------------------------
    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)
        
        tree = self.treeTests
        #@type tree QTreeWidget
        
        # 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)

    #-----------------------------------------------------------------------------
    def test(self):
        pass
            
    #-----------------------------------------------------------------------------
    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)
            
    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)
                    
        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() )
        self.checkMonitorLibraries.setChecked( s.value("checkMonitorLibraries", 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)
                
        # --- Text settings ---
        memory = s.value("memory_limit_MB", 8000).toInt()[0]
        test_info.memory_limit_kb = memory*1024;
        self.textMemory.setPlainText("%d" % memory)

        timeout = s.value("process_timeout_sec", 30).toInt()[0]
        test_info.process_timeout_sec = timeout;
        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("checkMonitorLibraries", self.checkMonitorLibraries.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)
        
    #-----------------------------------------------------------------------------
    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; }");
            return value
        except:
            plainTextBox.setStyleSheet("QPlainTextEdit { background-color: tomato; }");
            return None
    
        
        
    #-----------------------------------------------------------------------------
    def text_settings_changed(self):
        """ called when one of the text boxes changes """
        s = self.settings
        
        memory = self.get_int_from_text(self.textMemory);
        if not memory is None:
            test_info.memory_limit_kb = memory*1024;
            s.setValue("memory_limit_MB", memory)
            
        timeout = self.get_int_from_text(self.textTimeout);
        if not timeout is None:
            test_info.process_timeout_sec = timeout;
            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. """
        # 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) \
            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 = ""
            
        # 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 
        return line + "<br>\n"
    
    #-----------------------------------------------------------------------------
    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):
                #print "Updating GUI of TestSuite", obj.name
                self.model.update_suite(obj)
            elif isinstance(obj, TestProject):
                # 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
                    # 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("")
        self.update_tree()
        
        failed = test_info.all_tests.failed
        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.treeTests.update()
        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:") 
        else:
            raise "Incorrect object passed to show_results; should be TestProject, TestSuite, or TestSingle."
        self.labelTestName.setText( res.get_fullname() ) 
        self.textResults.setText( u'<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n' 
                                  + res.get_results_text() )

                            
    #-----------------------------------------------------------------------------
    def checked_show_fail_only(self, state):
        """ Toggle the filtering """
        self.proxy.set_filter_failed_only(state > 0)
        
    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)

    def run_selected(self):
        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)
        self.stdout = ""
        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()
        
    #-----------------------------------------------------------------------------
    def select_all(self):
        """ Select all tests """
        test_info.all_tests.select_all(True)
        self.proxy.invalidateFilter()
        self.treeTests.update()
        
    def select_none(self):
        """ De-Select all tests """
        test_info.all_tests.select_all(False)
        self.proxy.invalidateFilter()
        self.treeTests.update()
        
    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 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()
コード例 #4
0
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):
        QtGui.QWidget.__init__(self, parent)

        # 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.buttonTest.hide()
        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  ----
        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)
        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)

        # -- Text commands ---
        self.connect(self.textTimeout, QtCore.SIGNAL("textChanged()"), self.text_settings_changed)
        self.connect(self.textMemory, QtCore.SIGNAL("textChanged()"), self.text_settings_changed)

        # --- 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.setup_tree()

        self.current_results = None
        self.show_results()

        self.readSettings()

    #-----------------------------------------------------------------------------
    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)

        tree = self.treeTests
        #@type tree QTreeWidget

        # 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)

    #-----------------------------------------------------------------------------
    def test(self):
        pass

    #-----------------------------------------------------------------------------
    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)

    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)

        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)

        # --- Text settings ---
        memory = s.value("memory_limit_MB", 8000).toInt()[0]
        test_info.memory_limit_kb = memory*1024;
        self.textMemory.setPlainText("%d" % memory)

        timeout = s.value("process_timeout_sec", 30).toInt()[0]
        test_info.process_timeout_sec = timeout;
        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)

    #-----------------------------------------------------------------------------
    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; }");
            return value
        except:
            plainTextBox.setStyleSheet("QPlainTextEdit { background-color: tomato; }");
            return None



    #-----------------------------------------------------------------------------
    def text_settings_changed(self):
        """ called when one of the text boxes changes """
        s = self.settings

        memory = self.get_int_from_text(self.textMemory);
        if not memory is None:
            test_info.memory_limit_kb = memory*1024;
            s.setValue("memory_limit_MB", memory)

        timeout = self.get_int_from_text(self.textTimeout);
        if not timeout is None:
            test_info.process_timeout_sec = timeout;
            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. """
        # 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) \
            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 = ""

        # 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
        return line + "<br>\n"

    #-----------------------------------------------------------------------------
    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):
                #print "Updating GUI of TestSuite", obj.name
                self.model.update_suite(obj)
            elif isinstance(obj, TestProject):
                # 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
                    # 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("")
        self.update_tree()

        failed = test_info.all_tests.failed
        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.treeTests.update()
        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:")
        else:
            raise "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'
                                  + res.get_results_text() )


    #-----------------------------------------------------------------------------
    def checked_show_fail_only(self, state):
        """ Toggle the filtering """
        self.proxy.set_filter_failed_only(state > 0)

    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)

    def run_selected(self):
        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)
        self.stdout = ""
        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()

    #-----------------------------------------------------------------------------
    def select_all(self):
        """ Select all tests """
        test_info.all_tests.select_all(True)
        self.proxy.invalidateFilter()
        self.treeTests.update()

    def select_none(self):
        """ De-Select all tests """
        test_info.all_tests.select_all(False)
        self.proxy.invalidateFilter()
        self.treeTests.update()

    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()