Corrected the VCS tracing state of the recently merged files. eric7-maintenance

Sun, 05 Jun 2022 17:17:44 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 05 Jun 2022 17:17:44 +0200
branch
eric7-maintenance
changeset 9131
bc0c1b6d6adb
parent 9112
9967ae9f0906
child 9136
9e0cf68f727b

Corrected the VCS tracing state of the recently merged files.

eric7/DataViews/PyCoverageHtmlReportDialog.py file | annotate | diff | comparison | revisions
eric7/DataViews/PyCoverageHtmlReportDialog.ui file | annotate | diff | comparison | revisions
eric7/DataViews/PyCoverageJsonReportDialog.py file | annotate | diff | comparison | revisions
eric7/DataViews/PyCoverageJsonReportDialog.ui file | annotate | diff | comparison | revisions
eric7/DebugClients/Python/DCTestResult.py file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.DataViews.PyCoverageHtmlReportDialog.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.DataViews.PyCoverageJsonReportDialog.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.DebugClients.Python.DCTestResult.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.DocumentationTools.TemplatesListsStyle.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.EricNetwork.EricJsonStreamReader.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.EricNetwork.EricJsonStreamWriter.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.PyUnit.UnittestDialog.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.Testing.Interfaces.PytestExecutor.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.Testing.Interfaces.PytestRunner.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.Testing.Interfaces.TestExecutorBase.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.Testing.Interfaces.TestFrameworkRegistry.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.Testing.Interfaces.UnittestExecutor.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.Testing.Interfaces.UnittestRunner.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.Testing.TestResultsTree.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.Testing.TestingWidget.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.Testing.__init__.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.eric7_testing.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.eric7_unittest.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/index-eric7.PyUnit.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/index-eric7.Testing.Interfaces.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/index-eric7.Testing.html file | annotate | diff | comparison | revisions
eric7/EricNetwork/EricJsonStreamReader.py file | annotate | diff | comparison | revisions
eric7/EricNetwork/EricJsonStreamWriter.py file | annotate | diff | comparison | revisions
eric7/PyUnit/UnittestDialog.py file | annotate | diff | comparison | revisions
eric7/PyUnit/UnittestDialog.ui file | annotate | diff | comparison | revisions
eric7/PyUnit/UnittestStacktraceDialog.ui file | annotate | diff | comparison | revisions
eric7/PyUnit/__init__.py file | annotate | diff | comparison | revisions
eric7/Testing/Interfaces/PytestExecutor.py file | annotate | diff | comparison | revisions
eric7/Testing/Interfaces/PytestRunner.py file | annotate | diff | comparison | revisions
eric7/Testing/Interfaces/TestExecutorBase.py file | annotate | diff | comparison | revisions
eric7/Testing/Interfaces/TestFrameworkRegistry.py file | annotate | diff | comparison | revisions
eric7/Testing/Interfaces/UnittestExecutor.py file | annotate | diff | comparison | revisions
eric7/Testing/Interfaces/UnittestRunner.py file | annotate | diff | comparison | revisions
eric7/Testing/Interfaces/__init__.py file | annotate | diff | comparison | revisions
eric7/Testing/TestResultsTree.py file | annotate | diff | comparison | revisions
eric7/Testing/TestingWidget.py file | annotate | diff | comparison | revisions
eric7/Testing/TestingWidget.ui file | annotate | diff | comparison | revisions
eric7/Testing/__init__.py file | annotate | diff | comparison | revisions
eric7/eric7_testing.py file | annotate | diff | comparison | revisions
eric7/eric7_testing.pyw file | annotate | diff | comparison | revisions
eric7/eric7_unittest.py file | annotate | diff | comparison | revisions
eric7/eric7_unittest.pyw file | annotate | diff | comparison | revisions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/DataViews/PyCoverageHtmlReportDialog.py	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to enter the parameters for a coverage HTML
+report.
+"""
+
+from PyQt6.QtCore import pyqtSlot
+from PyQt6.QtWidgets import QDialog, QDialogButtonBox
+
+from EricWidgets.EricPathPicker import EricPathPickerModes
+
+from .Ui_PyCoverageHtmlReportDialog import Ui_PyCoverageHtmlReportDialog
+
+
+class PyCoverageHtmlReportDialog(QDialog, Ui_PyCoverageHtmlReportDialog):
+    """
+    Class implementing a dialog to enter the parameters for a coverage HTML
+    report.
+    """
+    def __init__(self, defaultDirectory, parent=None):
+        """
+        Constructor
+        
+        @param defaultDirectory default directory for selecting the output
+            directory
+        @type str
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+        
+        self.outputDirectoryPicker.setMode(
+            EricPathPickerModes.DIRECTORY_SHOW_FILES_MODE)
+        self.outputDirectoryPicker.setDefaultDirectory(defaultDirectory)
+        
+        self.extraCssPicker.setMode(
+            EricPathPickerModes.OPEN_FILE_MODE)
+        
+        self.buttonBox.button(
+            QDialogButtonBox.StandardButton.Ok).setEnabled(False)
+        
+        msh = self.minimumSizeHint()
+        self.resize(max(self.width(), msh.width()), msh.height())
+    
+    @pyqtSlot(str)
+    def on_outputDirectoryPicker_textChanged(self, directory):
+        """
+        Private slot handling a change of the output directory.
+        
+        @param directory current text of the directory picker
+        @type str
+        """
+        self.buttonBox.button(
+            QDialogButtonBox.StandardButton.Ok).setEnabled(bool(directory))
+    
+    def getData(self):
+        """
+        Public method to get the entered data.
+        
+        @return tuple containing the report title, the output directory, the
+            path of a file containing extra CSS and a flag indicating to open
+            the generated report in a browser
+        
+        @rtype tuple of (str, str, str, bool)
+        """
+        title = self.titleEdit.text()
+        return (
+            title if bool(title) else None,
+            self.outputDirectoryPicker.currentText(),
+            self.extraCssPicker.currentText(),
+            self.openReportCheckBox.isChecked(),
+        )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/DataViews/PyCoverageHtmlReportDialog.ui	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PyCoverageHtmlReportDialog</class>
+ <widget class="QDialog" name="PyCoverageHtmlReportDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>500</width>
+    <height>154</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>HTML Report</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Title:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1">
+    <widget class="EricPathPicker" name="extraCssPicker" native="true">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="focusPolicy">
+      <enum>Qt::StrongFocus</enum>
+     </property>
+     <property name="toolTip">
+      <string>Enter the path of a file containing additional CSS definitions</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="0">
+    <widget class="QLabel" name="label_3">
+     <property name="text">
+      <string>Extra CSS:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="1">
+    <widget class="QLineEdit" name="titleEdit">
+     <property name="toolTip">
+      <string>Enter the title for the HTML report</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>Output Directory:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="1">
+    <widget class="EricPathPicker" name="outputDirectoryPicker" native="true">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="focusPolicy">
+      <enum>Qt::StrongFocus</enum>
+     </property>
+     <property name="toolTip">
+      <string>Enter the path of the output directory</string>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="0" colspan="2">
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="0" colspan="2">
+    <widget class="QCheckBox" name="openReportCheckBox">
+     <property name="toolTip">
+      <string>Select to open the generated report</string>
+     </property>
+     <property name="text">
+      <string>Open Report</string>
+     </property>
+     <property name="checked">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>EricPathPicker</class>
+   <extends>QWidget</extends>
+   <header>EricWidgets/EricPathPicker.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>titleEdit</tabstop>
+  <tabstop>outputDirectoryPicker</tabstop>
+  <tabstop>extraCssPicker</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>PyCoverageHtmlReportDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>PyCoverageHtmlReportDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/DataViews/PyCoverageJsonReportDialog.py	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to enter the parameters for a coverage JSON
+report.
+"""
+
+import os
+
+from PyQt6.QtCore import pyqtSlot
+from PyQt6.QtWidgets import QDialog, QDialogButtonBox
+
+from EricWidgets.EricPathPicker import EricPathPickerModes
+
+from .Ui_PyCoverageJsonReportDialog import Ui_PyCoverageJsonReportDialog
+
+
+class PyCoverageJsonReportDialog(QDialog, Ui_PyCoverageJsonReportDialog):
+    """
+    Class implementing a dialog to enter the parameters for a coverage JSON
+    report.
+    """
+    def __init__(self, defaultDirectory, parent=None):
+        """
+        Constructor
+        
+        @param defaultDirectory default directory for selecting the output
+            directory
+        @type str
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+        
+        self.outputFilePicker.setMode(
+            EricPathPickerModes.SAVE_FILE_ENSURE_EXTENSION_MODE)
+        self.outputFilePicker.setDefaultDirectory(defaultDirectory)
+        self.outputFilePicker.setFilters(
+            self.tr("JSON Files (*.json);;All Files (*)"))
+        self.outputFilePicker.setText(
+            os.path.join(defaultDirectory, "coverage.json"))
+        
+        msh = self.minimumSizeHint()
+        self.resize(max(self.width(), msh.width()), msh.height())
+    
+    @pyqtSlot(str)
+    def on_outputFilePicker_textChanged(self, filename):
+        """
+        Private slot handling a change of the output file.
+        
+        @param filename current text of the file picker
+        @type str
+        """
+        self.buttonBox.button(
+            QDialogButtonBox.StandardButton.Ok).setEnabled(bool(filename))
+    
+    def getData(self):
+        """
+        Public method to get the entered data.
+        
+        @return tuple containing the output file and a flag indicating the
+            creation of a compact JSON file
+        
+        @rtype tuple of (str, bool)
+        """
+        return (
+            self.outputFilePicker.currentText(),
+            self.compactCheckBox.isChecked(),
+        )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/DataViews/PyCoverageJsonReportDialog.ui	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PyCoverageJsonReportDialog</class>
+ <widget class="QDialog" name="PyCoverageJsonReportDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>500</width>
+    <height>98</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>JSON Report</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="1" column="0" colspan="2">
+    <widget class="QCheckBox" name="compactCheckBox">
+     <property name="toolTip">
+      <string>Select to create the report using the compact format</string>
+     </property>
+     <property name="text">
+      <string>Compact Format</string>
+     </property>
+     <property name="checked">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="0">
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>Output File:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="1">
+    <widget class="EricPathPicker" name="outputFilePicker" native="true">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="focusPolicy">
+      <enum>Qt::StrongFocus</enum>
+     </property>
+     <property name="toolTip">
+      <string>Enter the path of the output file</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="0" colspan="2">
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>EricPathPicker</class>
+   <extends>QWidget</extends>
+   <header>EricWidgets/EricPathPicker.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>outputFilePicker</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>PyCoverageJsonReportDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>PyCoverageJsonReportDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- a/eric7/DebugClients/Python/DCTestResult.py	Wed Jun 01 13:49:13 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,154 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2003 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
-#
-
-"""
-Module implementing a TestResult derivative for the eric debugger.
-"""
-
-import select
-from unittest import TestResult
-
-
-class DCTestResult(TestResult):
-    """
-    A TestResult derivative to work with eric's debug client.
-    
-    For more details see unittest.py of the standard python distribution.
-    """
-    def __init__(self, dbgClient, failfast):
-        """
-        Constructor
-        
-        @param dbgClient reference to the debug client
-        @type DebugClientBase
-        @param failfast flag indicating to stop at the first error
-        @type bool
-        """
-        TestResult.__init__(self)
-        self.__dbgClient = dbgClient
-        self.failfast = failfast
-    
-    def addFailure(self, test, err):
-        """
-        Public method called if a test failed.
-        
-        @param test Reference to the test object
-        @param err The error traceback
-        """
-        TestResult.addFailure(self, test, err)
-        tracebackLines = self._exc_info_to_string(err, test)
-        self.__dbgClient.sendJsonCommand("ResponseUTTestFailed", {
-            "testname": str(test),
-            "traceback": tracebackLines,
-            "id": test.id(),
-        })
-    
-    def addError(self, test, err):
-        """
-        Public method called if a test errored.
-        
-        @param test Reference to the test object
-        @param err The error traceback
-        """
-        TestResult.addError(self, test, err)
-        tracebackLines = self._exc_info_to_string(err, test)
-        self.__dbgClient.sendJsonCommand("ResponseUTTestErrored", {
-            "testname": str(test),
-            "traceback": tracebackLines,
-            "id": test.id(),
-        })
-    
-    def addSubTest(self, test, subtest, err):
-        """
-        Public method called for each subtest to record its result.
-        
-        @param test reference to the test object
-        @param subtest reference to the subtest object
-        @param err error traceback
-        """
-        if err is not None:
-            TestResult.addSubTest(self, test, subtest, err)
-            tracebackLines = self._exc_info_to_string(err, test)
-            if issubclass(err[0], test.failureException):
-                self.__dbgClient.sendJsonCommand("ResponseUTTestFailed", {
-                    "testname": str(subtest),
-                    "traceback": tracebackLines,
-                    "id": test.id(),
-                })
-            else:
-                self.__dbgClient.sendJsonCommand("ResponseUTTestErrored", {
-                    "testname": str(subtest),
-                    "traceback": tracebackLines,
-                    "id": test.id(),
-                })
-    
-    def addSkip(self, test, reason):
-        """
-        Public method called if a test was skipped.
-        
-        @param test reference to the test object
-        @param reason reason for skipping the test (string)
-        """
-        TestResult.addSkip(self, test, reason)
-        self.__dbgClient.sendJsonCommand("ResponseUTTestSkipped", {
-            "testname": str(test),
-            "reason": reason,
-            "id": test.id(),
-        })
-    
-    def addExpectedFailure(self, test, err):
-        """
-        Public method called if a test failed expected.
-        
-        @param test reference to the test object
-        @param err error traceback
-        """
-        TestResult.addExpectedFailure(self, test, err)
-        tracebackLines = self._exc_info_to_string(err, test)
-        self.__dbgClient.sendJsonCommand("ResponseUTTestFailedExpected", {
-            "testname": str(test),
-            "traceback": tracebackLines,
-            "id": test.id(),
-        })
-    
-    def addUnexpectedSuccess(self, test):
-        """
-        Public method called if a test succeeded expectedly.
-        
-        @param test reference to the test object
-        """
-        TestResult.addUnexpectedSuccess(self, test)
-        self.__dbgClient.sendJsonCommand("ResponseUTTestSucceededUnexpected", {
-            "testname": str(test),
-            "id": test.id(),
-        })
-    
-    def startTest(self, test):
-        """
-        Public method called at the start of a test.
-        
-        @param test Reference to the test object
-        """
-        TestResult.startTest(self, test)
-        self.__dbgClient.sendJsonCommand("ResponseUTStartTest", {
-            "testname": str(test),
-            "description": test.shortDescription(),
-        })
-    
-    def stopTest(self, test):
-        """
-        Public method called at the end of a test.
-        
-        @param test Reference to the test object
-        """
-        TestResult.stopTest(self, test)
-        self.__dbgClient.sendJsonCommand("ResponseUTStopTest", {})
-        
-        # ensure that pending input is processed
-        rrdy, wrdy, xrdy = select.select(
-            [self.__dbgClient.readstream], [], [], 0.01)
-
-        if self.__dbgClient.readstream in rrdy:
-            self.__dbgClient.readReady(self.__dbgClient.readstream)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/eric7.DataViews.PyCoverageHtmlReportDialog.html	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.DataViews.PyCoverageHtmlReportDialog</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.DataViews.PyCoverageHtmlReportDialog</h1>
+
+<p>
+Module implementing a dialog to enter the parameters for a coverage HTML
+report.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#PyCoverageHtmlReportDialog">PyCoverageHtmlReportDialog</a></td>
+<td>Class implementing a dialog to enter the parameters for a coverage HTML report.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="PyCoverageHtmlReportDialog" ID="PyCoverageHtmlReportDialog"></a>
+<h2>PyCoverageHtmlReportDialog</h2>
+
+<p>
+    Class implementing a dialog to enter the parameters for a coverage HTML
+    report.
+</p>
+<h3>Derived from</h3>
+QDialog, Ui_PyCoverageHtmlReportDialog
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#PyCoverageHtmlReportDialog.__init__">PyCoverageHtmlReportDialog</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#PyCoverageHtmlReportDialog.getData">getData</a></td>
+<td>Public method to get the entered data.</td>
+</tr>
+<tr>
+<td><a href="#PyCoverageHtmlReportDialog.on_outputDirectoryPicker_textChanged">on_outputDirectoryPicker_textChanged</a></td>
+<td>Private slot handling a change of the output directory.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="PyCoverageHtmlReportDialog.__init__" ID="PyCoverageHtmlReportDialog.__init__"></a>
+<h4>PyCoverageHtmlReportDialog (Constructor)</h4>
+<b>PyCoverageHtmlReportDialog</b>(<i>defaultDirectory, parent=None</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>defaultDirectory</i> (str)</dt>
+<dd>
+default directory for selecting the output
+            directory
+</dd>
+<dt><i>parent</i> (QWidget (optional))</dt>
+<dd>
+reference to the parent widget (defaults to None)
+</dd>
+</dl>
+<a NAME="PyCoverageHtmlReportDialog.getData" ID="PyCoverageHtmlReportDialog.getData"></a>
+<h4>PyCoverageHtmlReportDialog.getData</h4>
+<b>getData</b>(<i></i>)
+
+<p>
+        Public method to get the entered data.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+tuple containing the report title, the output directory, the
+            path of a file containing extra CSS and a flag indicating to open
+            the generated report in a browser
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+tuple of (str, str, str, bool)
+</dd>
+</dl>
+<a NAME="PyCoverageHtmlReportDialog.on_outputDirectoryPicker_textChanged" ID="PyCoverageHtmlReportDialog.on_outputDirectoryPicker_textChanged"></a>
+<h4>PyCoverageHtmlReportDialog.on_outputDirectoryPicker_textChanged</h4>
+<b>on_outputDirectoryPicker_textChanged</b>(<i>directory</i>)
+
+<p>
+        Private slot handling a change of the output directory.
+</p>
+<dl>
+
+<dt><i>directory</i> (str)</dt>
+<dd>
+current text of the directory picker
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/eric7.DataViews.PyCoverageJsonReportDialog.html	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.DataViews.PyCoverageJsonReportDialog</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.DataViews.PyCoverageJsonReportDialog</h1>
+
+<p>
+Module implementing a dialog to enter the parameters for a coverage JSON
+report.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#PyCoverageJsonReportDialog">PyCoverageJsonReportDialog</a></td>
+<td>Class implementing a dialog to enter the parameters for a coverage JSON report.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="PyCoverageJsonReportDialog" ID="PyCoverageJsonReportDialog"></a>
+<h2>PyCoverageJsonReportDialog</h2>
+
+<p>
+    Class implementing a dialog to enter the parameters for a coverage JSON
+    report.
+</p>
+<h3>Derived from</h3>
+QDialog, Ui_PyCoverageJsonReportDialog
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#PyCoverageJsonReportDialog.__init__">PyCoverageJsonReportDialog</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#PyCoverageJsonReportDialog.getData">getData</a></td>
+<td>Public method to get the entered data.</td>
+</tr>
+<tr>
+<td><a href="#PyCoverageJsonReportDialog.on_outputFilePicker_textChanged">on_outputFilePicker_textChanged</a></td>
+<td>Private slot handling a change of the output file.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="PyCoverageJsonReportDialog.__init__" ID="PyCoverageJsonReportDialog.__init__"></a>
+<h4>PyCoverageJsonReportDialog (Constructor)</h4>
+<b>PyCoverageJsonReportDialog</b>(<i>defaultDirectory, parent=None</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>defaultDirectory</i> (str)</dt>
+<dd>
+default directory for selecting the output
+            directory
+</dd>
+<dt><i>parent</i> (QWidget (optional))</dt>
+<dd>
+reference to the parent widget (defaults to None)
+</dd>
+</dl>
+<a NAME="PyCoverageJsonReportDialog.getData" ID="PyCoverageJsonReportDialog.getData"></a>
+<h4>PyCoverageJsonReportDialog.getData</h4>
+<b>getData</b>(<i></i>)
+
+<p>
+        Public method to get the entered data.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+tuple containing the output file and a flag indicating the
+            creation of a compact JSON file
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+tuple of (str, bool)
+</dd>
+</dl>
+<a NAME="PyCoverageJsonReportDialog.on_outputFilePicker_textChanged" ID="PyCoverageJsonReportDialog.on_outputFilePicker_textChanged"></a>
+<h4>PyCoverageJsonReportDialog.on_outputFilePicker_textChanged</h4>
+<b>on_outputFilePicker_textChanged</b>(<i>filename</i>)
+
+<p>
+        Private slot handling a change of the output file.
+</p>
+<dl>
+
+<dt><i>filename</i> (str)</dt>
+<dd>
+current text of the file picker
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- a/eric7/Documentation/Source/eric7.DebugClients.Python.DCTestResult.html	Wed Jun 01 13:49:13 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,259 +0,0 @@
-<!DOCTYPE html>
-<html><head>
-<title>eric7.DebugClients.Python.DCTestResult</title>
-<meta charset="UTF-8">
-<link rel="stylesheet" href="styles.css">
-</head>
-<body>
-<a NAME="top" ID="top"></a>
-<h1>eric7.DebugClients.Python.DCTestResult</h1>
-
-<p>
-Module implementing a TestResult derivative for the eric debugger.
-</p>
-<h3>Global Attributes</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-<h3>Classes</h3>
-
-<table>
-
-<tr>
-<td><a href="#DCTestResult">DCTestResult</a></td>
-<td>A TestResult derivative to work with eric's debug client.</td>
-</tr>
-</table>
-<h3>Functions</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-<hr />
-<hr />
-<a NAME="DCTestResult" ID="DCTestResult"></a>
-<h2>DCTestResult</h2>
-
-<p>
-    A TestResult derivative to work with eric's debug client.
-</p>
-<p>
-    For more details see unittest.py of the standard python distribution.
-</p>
-<h3>Derived from</h3>
-TestResult
-<h3>Class Attributes</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-<h3>Class Methods</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-<h3>Methods</h3>
-
-<table>
-
-<tr>
-<td><a href="#DCTestResult.__init__">DCTestResult</a></td>
-<td>Constructor</td>
-</tr>
-<tr>
-<td><a href="#DCTestResult.addError">addError</a></td>
-<td>Public method called if a test errored.</td>
-</tr>
-<tr>
-<td><a href="#DCTestResult.addExpectedFailure">addExpectedFailure</a></td>
-<td>Public method called if a test failed expected.</td>
-</tr>
-<tr>
-<td><a href="#DCTestResult.addFailure">addFailure</a></td>
-<td>Public method called if a test failed.</td>
-</tr>
-<tr>
-<td><a href="#DCTestResult.addSkip">addSkip</a></td>
-<td>Public method called if a test was skipped.</td>
-</tr>
-<tr>
-<td><a href="#DCTestResult.addSubTest">addSubTest</a></td>
-<td>Public method called for each subtest to record its result.</td>
-</tr>
-<tr>
-<td><a href="#DCTestResult.addUnexpectedSuccess">addUnexpectedSuccess</a></td>
-<td>Public method called if a test succeeded expectedly.</td>
-</tr>
-<tr>
-<td><a href="#DCTestResult.startTest">startTest</a></td>
-<td>Public method called at the start of a test.</td>
-</tr>
-<tr>
-<td><a href="#DCTestResult.stopTest">stopTest</a></td>
-<td>Public method called at the end of a test.</td>
-</tr>
-</table>
-<h3>Static Methods</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-
-<a NAME="DCTestResult.__init__" ID="DCTestResult.__init__"></a>
-<h4>DCTestResult (Constructor)</h4>
-<b>DCTestResult</b>(<i>dbgClient, failfast</i>)
-
-<p>
-        Constructor
-</p>
-<dl>
-
-<dt><i>dbgClient</i> (DebugClientBase)</dt>
-<dd>
-reference to the debug client
-</dd>
-<dt><i>failfast</i> (bool)</dt>
-<dd>
-flag indicating to stop at the first error
-</dd>
-</dl>
-<a NAME="DCTestResult.addError" ID="DCTestResult.addError"></a>
-<h4>DCTestResult.addError</h4>
-<b>addError</b>(<i>test, err</i>)
-
-<p>
-        Public method called if a test errored.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-Reference to the test object
-</dd>
-<dt><i>err</i></dt>
-<dd>
-The error traceback
-</dd>
-</dl>
-<a NAME="DCTestResult.addExpectedFailure" ID="DCTestResult.addExpectedFailure"></a>
-<h4>DCTestResult.addExpectedFailure</h4>
-<b>addExpectedFailure</b>(<i>test, err</i>)
-
-<p>
-        Public method called if a test failed expected.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-reference to the test object
-</dd>
-<dt><i>err</i></dt>
-<dd>
-error traceback
-</dd>
-</dl>
-<a NAME="DCTestResult.addFailure" ID="DCTestResult.addFailure"></a>
-<h4>DCTestResult.addFailure</h4>
-<b>addFailure</b>(<i>test, err</i>)
-
-<p>
-        Public method called if a test failed.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-Reference to the test object
-</dd>
-<dt><i>err</i></dt>
-<dd>
-The error traceback
-</dd>
-</dl>
-<a NAME="DCTestResult.addSkip" ID="DCTestResult.addSkip"></a>
-<h4>DCTestResult.addSkip</h4>
-<b>addSkip</b>(<i>test, reason</i>)
-
-<p>
-        Public method called if a test was skipped.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-reference to the test object
-</dd>
-<dt><i>reason</i></dt>
-<dd>
-reason for skipping the test (string)
-</dd>
-</dl>
-<a NAME="DCTestResult.addSubTest" ID="DCTestResult.addSubTest"></a>
-<h4>DCTestResult.addSubTest</h4>
-<b>addSubTest</b>(<i>test, subtest, err</i>)
-
-<p>
-        Public method called for each subtest to record its result.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-reference to the test object
-</dd>
-<dt><i>subtest</i></dt>
-<dd>
-reference to the subtest object
-</dd>
-<dt><i>err</i></dt>
-<dd>
-error traceback
-</dd>
-</dl>
-<a NAME="DCTestResult.addUnexpectedSuccess" ID="DCTestResult.addUnexpectedSuccess"></a>
-<h4>DCTestResult.addUnexpectedSuccess</h4>
-<b>addUnexpectedSuccess</b>(<i>test</i>)
-
-<p>
-        Public method called if a test succeeded expectedly.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-reference to the test object
-</dd>
-</dl>
-<a NAME="DCTestResult.startTest" ID="DCTestResult.startTest"></a>
-<h4>DCTestResult.startTest</h4>
-<b>startTest</b>(<i>test</i>)
-
-<p>
-        Public method called at the start of a test.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-Reference to the test object
-</dd>
-</dl>
-<a NAME="DCTestResult.stopTest" ID="DCTestResult.stopTest"></a>
-<h4>DCTestResult.stopTest</h4>
-<b>stopTest</b>(<i>test</i>)
-
-<p>
-        Public method called at the end of a test.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-Reference to the test object
-</dd>
-</dl>
-<div align="right"><a href="#top">Up</a></div>
-<hr />
-</body></html>
\ No newline at end of file
--- a/eric7/Documentation/Source/eric7.DocumentationTools.TemplatesListsStyle.html	Wed Jun 01 13:49:13 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<html><head>
-<title>eric7.DocumentationTools.TemplatesListsStyle</title>
-<meta charset="UTF-8">
-<link rel="stylesheet" href="styles.css">
-</head>
-<body>
-<a NAME="top" ID="top"></a>
-<h1>eric7.DocumentationTools.TemplatesListsStyle</h1>
-
-<p>
-Module implementing templates for the documentation generator (lists style).
-</p>
-<h3>Global Attributes</h3>
-
-<table>
-<tr><td>authorInfoTemplate</td></tr><tr><td>classTemplate</td></tr><tr><td>constructorTemplate</td></tr><tr><td>deprecatedTemplate</td></tr><tr><td>eventsListEntryTemplate</td></tr><tr><td>eventsListTemplate</td></tr><tr><td>exceptionsListEntryTemplate</td></tr><tr><td>exceptionsListTemplate</td></tr><tr><td>footerTemplate</td></tr><tr><td>functionTemplate</td></tr><tr><td>headerTemplate</td></tr><tr><td>indexBodyTemplate</td></tr><tr><td>indexListEntryTemplate</td></tr><tr><td>indexListModulesTemplate</td></tr><tr><td>indexListPackagesTemplate</td></tr><tr><td>listEntryDeprecatedTemplate</td></tr><tr><td>listEntryNoneTemplate</td></tr><tr><td>listEntrySimpleTemplate</td></tr><tr><td>listEntryTemplate</td></tr><tr><td>listTemplate</td></tr><tr><td>methodTemplate</td></tr><tr><td>moduleTemplate</td></tr><tr><td>paragraphTemplate</td></tr><tr><td>parameterTypesListEntryTemplate</td></tr><tr><td>parametersListEntryTemplate</td></tr><tr><td>parametersListTemplate</td></tr><tr><td>rbFileTemplate</td></tr><tr><td>rbModuleTemplate</td></tr><tr><td>rbModulesClassTemplate</td></tr><tr><td>returnTypesTemplate</td></tr><tr><td>returnsTemplate</td></tr><tr><td>seeLinkTemplate</td></tr><tr><td>seeListEntryTemplate</td></tr><tr><td>seeListTemplate</td></tr><tr><td>signalsListEntryTemplate</td></tr><tr><td>signalsListTemplate</td></tr><tr><td>sinceInfoTemplate</td></tr><tr><td>yieldTypesTemplate</td></tr><tr><td>yieldsTemplate</td></tr>
-</table>
-<h3>Classes</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-<h3>Functions</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-<hr />
-</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/eric7.EricNetwork.EricJsonStreamReader.html	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,183 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.EricNetwork.EricJsonStreamReader</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.EricNetwork.EricJsonStreamReader</h1>
+
+<p>
+Module implementing a JSON based reader class.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#EricJsonReader">EricJsonReader</a></td>
+<td>Class implementing a JSON based reader class.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="EricJsonReader" ID="EricJsonReader"></a>
+<h2>EricJsonReader</h2>
+
+<p>
+    Class implementing a JSON based reader class.
+</p>
+<p>
+    The reader is responsible for opening a socket to listen for writer
+    connections.
+</p>
+<h3>Signals</h3>
+<dl>
+
+<dt>dataReceived(object)</dt>
+<dd>
+emitted after a data object was received
+</dd>
+</dl>
+<h3>Derived from</h3>
+QTcpServer
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#EricJsonReader.__init__">EricJsonReader</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#EricJsonReader.__handleDisconnect">__handleDisconnect</a></td>
+<td>Private slot handling a disconnect of the writer.</td>
+</tr>
+<tr>
+<td><a href="#EricJsonReader.__receiveJson">__receiveJson</a></td>
+<td>Private slot handling received data from the writer.</td>
+</tr>
+<tr>
+<td><a href="#EricJsonReader.address">address</a></td>
+<td>Public method to get the host address.</td>
+</tr>
+<tr>
+<td><a href="#EricJsonReader.handleNewConnection">handleNewConnection</a></td>
+<td>Public slot for new incoming connections from a writer.</td>
+</tr>
+<tr>
+<td><a href="#EricJsonReader.port">port</a></td>
+<td>Public method to get the port number to connect to.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="EricJsonReader.__init__" ID="EricJsonReader.__init__"></a>
+<h4>EricJsonReader (Constructor)</h4>
+<b>EricJsonReader</b>(<i>name="", ip=None, parent=None</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>name</i> (str)</dt>
+<dd>
+name of the server (used for output only)
+</dd>
+<dt><i>ip</i> (str)</dt>
+<dd>
+IP address to listen at
+</dd>
+<dt><i>parent</i> (QObject)</dt>
+<dd>
+parent object
+</dd>
+</dl>
+<a NAME="EricJsonReader.__handleDisconnect" ID="EricJsonReader.__handleDisconnect"></a>
+<h4>EricJsonReader.__handleDisconnect</h4>
+<b>__handleDisconnect</b>(<i></i>)
+
+<p>
+        Private slot handling a disconnect of the writer.
+</p>
+<a NAME="EricJsonReader.__receiveJson" ID="EricJsonReader.__receiveJson"></a>
+<h4>EricJsonReader.__receiveJson</h4>
+<b>__receiveJson</b>(<i></i>)
+
+<p>
+        Private slot handling received data from the writer.
+</p>
+<a NAME="EricJsonReader.address" ID="EricJsonReader.address"></a>
+<h4>EricJsonReader.address</h4>
+<b>address</b>(<i></i>)
+
+<p>
+        Public method to get the host address.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+host address
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+str
+</dd>
+</dl>
+<a NAME="EricJsonReader.handleNewConnection" ID="EricJsonReader.handleNewConnection"></a>
+<h4>EricJsonReader.handleNewConnection</h4>
+<b>handleNewConnection</b>(<i></i>)
+
+<p>
+        Public slot for new incoming connections from a writer.
+</p>
+<a NAME="EricJsonReader.port" ID="EricJsonReader.port"></a>
+<h4>EricJsonReader.port</h4>
+<b>port</b>(<i></i>)
+
+<p>
+        Public method to get the port number to connect to.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+port number
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+int
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/eric7.EricNetwork.EricJsonStreamWriter.html	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.EricNetwork.EricJsonStreamWriter</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.EricNetwork.EricJsonStreamWriter</h1>
+
+<p>
+Module implementing a JSON based writer class.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#EricJsonWriter">EricJsonWriter</a></td>
+<td>Class implementing a JSON based writer class.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="EricJsonWriter" ID="EricJsonWriter"></a>
+<h2>EricJsonWriter</h2>
+
+<p>
+    Class implementing a JSON based writer class.
+</p>
+<h3>Derived from</h3>
+None
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#EricJsonWriter.__init__">EricJsonWriter</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#EricJsonWriter.close">close</a></td>
+<td>Public method to close the stream.</td>
+</tr>
+<tr>
+<td><a href="#EricJsonWriter.write">write</a></td>
+<td>Public method to send JSON serializable data.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="EricJsonWriter.__init__" ID="EricJsonWriter.__init__"></a>
+<h4>EricJsonWriter (Constructor)</h4>
+<b>EricJsonWriter</b>(<i>host, port</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>host</i> (str)</dt>
+<dd>
+IP address the reader is listening on
+</dd>
+<dt><i>port</i> (int)</dt>
+<dd>
+port the reader is listening on
+</dd>
+</dl>
+<a NAME="EricJsonWriter.close" ID="EricJsonWriter.close"></a>
+<h4>EricJsonWriter.close</h4>
+<b>close</b>(<i></i>)
+
+<p>
+        Public method to close the stream.
+</p>
+<a NAME="EricJsonWriter.write" ID="EricJsonWriter.write"></a>
+<h4>EricJsonWriter.write</h4>
+<b>write</b>(<i>data</i>)
+
+<p>
+        Public method to send JSON serializable data.
+</p>
+<dl>
+
+<dt><i>data</i> (object)</dt>
+<dd>
+JSON serializable object to be sent
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- a/eric7/Documentation/Source/eric7.PyUnit.UnittestDialog.html	Wed Jun 01 13:49:13 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1236 +0,0 @@
-<!DOCTYPE html>
-<html><head>
-<title>eric7.PyUnit.UnittestDialog</title>
-<meta charset="UTF-8">
-<link rel="stylesheet" href="styles.css">
-</head>
-<body>
-<a NAME="top" ID="top"></a>
-<h1>eric7.PyUnit.UnittestDialog</h1>
-
-<p>
-Module implementing the UI to the pyunit package.
-</p>
-<h3>Global Attributes</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-<h3>Classes</h3>
-
-<table>
-
-<tr>
-<td><a href="#QtTestResult">QtTestResult</a></td>
-<td>A TestResult derivative to work with a graphical GUI.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog">UnittestDialog</a></td>
-<td>Class implementing the UI to the pyunit package.</td>
-</tr>
-<tr>
-<td><a href="#UnittestWindow">UnittestWindow</a></td>
-<td>Main window class for the standalone dialog.</td>
-</tr>
-</table>
-<h3>Functions</h3>
-
-<table>
-
-<tr>
-<td><a href="#clearSavedHistories">clearSavedHistories</a></td>
-<td>Function to clear the saved history lists.</td>
-</tr>
-</table>
-<hr />
-<hr />
-<a NAME="QtTestResult" ID="QtTestResult"></a>
-<h2>QtTestResult</h2>
-
-<p>
-    A TestResult derivative to work with a graphical GUI.
-</p>
-<p>
-    For more details see pyunit.py of the standard Python distribution.
-</p>
-<h3>Derived from</h3>
-unittest.TestResult
-<h3>Class Attributes</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-<h3>Class Methods</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-<h3>Methods</h3>
-
-<table>
-
-<tr>
-<td><a href="#QtTestResult.__init__">QtTestResult</a></td>
-<td>Constructor</td>
-</tr>
-<tr>
-<td><a href="#QtTestResult.addError">addError</a></td>
-<td>Public method called if a test errored.</td>
-</tr>
-<tr>
-<td><a href="#QtTestResult.addExpectedFailure">addExpectedFailure</a></td>
-<td>Public method called if a test failed expected.</td>
-</tr>
-<tr>
-<td><a href="#QtTestResult.addFailure">addFailure</a></td>
-<td>Public method called if a test failed.</td>
-</tr>
-<tr>
-<td><a href="#QtTestResult.addSkip">addSkip</a></td>
-<td>Public method called if a test was skipped.</td>
-</tr>
-<tr>
-<td><a href="#QtTestResult.addSubTest">addSubTest</a></td>
-<td>Public method called for each subtest to record its result.</td>
-</tr>
-<tr>
-<td><a href="#QtTestResult.addUnexpectedSuccess">addUnexpectedSuccess</a></td>
-<td>Public method called if a test succeeded expectedly.</td>
-</tr>
-<tr>
-<td><a href="#QtTestResult.startTest">startTest</a></td>
-<td>Public method called at the start of a test.</td>
-</tr>
-<tr>
-<td><a href="#QtTestResult.stopTest">stopTest</a></td>
-<td>Public method called at the end of a test.</td>
-</tr>
-</table>
-<h3>Static Methods</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-
-<a NAME="QtTestResult.__init__" ID="QtTestResult.__init__"></a>
-<h4>QtTestResult (Constructor)</h4>
-<b>QtTestResult</b>(<i>parent, failfast</i>)
-
-<p>
-        Constructor
-</p>
-<dl>
-
-<dt><i>parent</i> (UnittestDialog)</dt>
-<dd>
-reference to the parent widget
-</dd>
-<dt><i>failfast</i> (bool)</dt>
-<dd>
-flag indicating to stop at the first error
-</dd>
-</dl>
-<a NAME="QtTestResult.addError" ID="QtTestResult.addError"></a>
-<h4>QtTestResult.addError</h4>
-<b>addError</b>(<i>test, err</i>)
-
-<p>
-        Public method called if a test errored.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-reference to the test object
-</dd>
-<dt><i>err</i></dt>
-<dd>
-error traceback
-</dd>
-</dl>
-<a NAME="QtTestResult.addExpectedFailure" ID="QtTestResult.addExpectedFailure"></a>
-<h4>QtTestResult.addExpectedFailure</h4>
-<b>addExpectedFailure</b>(<i>test, err</i>)
-
-<p>
-        Public method called if a test failed expected.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-reference to the test object
-</dd>
-<dt><i>err</i></dt>
-<dd>
-error traceback
-</dd>
-</dl>
-<a NAME="QtTestResult.addFailure" ID="QtTestResult.addFailure"></a>
-<h4>QtTestResult.addFailure</h4>
-<b>addFailure</b>(<i>test, err</i>)
-
-<p>
-        Public method called if a test failed.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-reference to the test object
-</dd>
-<dt><i>err</i></dt>
-<dd>
-error traceback
-</dd>
-</dl>
-<a NAME="QtTestResult.addSkip" ID="QtTestResult.addSkip"></a>
-<h4>QtTestResult.addSkip</h4>
-<b>addSkip</b>(<i>test, reason</i>)
-
-<p>
-        Public method called if a test was skipped.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-reference to the test object
-</dd>
-<dt><i>reason</i></dt>
-<dd>
-reason for skipping the test (string)
-</dd>
-</dl>
-<a NAME="QtTestResult.addSubTest" ID="QtTestResult.addSubTest"></a>
-<h4>QtTestResult.addSubTest</h4>
-<b>addSubTest</b>(<i>test, subtest, err</i>)
-
-<p>
-        Public method called for each subtest to record its result.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-reference to the test object
-</dd>
-<dt><i>subtest</i></dt>
-<dd>
-reference to the subtest object
-</dd>
-<dt><i>err</i></dt>
-<dd>
-error traceback
-</dd>
-</dl>
-<a NAME="QtTestResult.addUnexpectedSuccess" ID="QtTestResult.addUnexpectedSuccess"></a>
-<h4>QtTestResult.addUnexpectedSuccess</h4>
-<b>addUnexpectedSuccess</b>(<i>test</i>)
-
-<p>
-        Public method called if a test succeeded expectedly.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-reference to the test object
-</dd>
-</dl>
-<a NAME="QtTestResult.startTest" ID="QtTestResult.startTest"></a>
-<h4>QtTestResult.startTest</h4>
-<b>startTest</b>(<i>test</i>)
-
-<p>
-        Public method called at the start of a test.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-Reference to the test object
-</dd>
-</dl>
-<a NAME="QtTestResult.stopTest" ID="QtTestResult.stopTest"></a>
-<h4>QtTestResult.stopTest</h4>
-<b>stopTest</b>(<i>test</i>)
-
-<p>
-        Public method called at the end of a test.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-Reference to the test object
-</dd>
-</dl>
-<div align="right"><a href="#top">Up</a></div>
-<hr />
-<hr />
-<a NAME="UnittestDialog" ID="UnittestDialog"></a>
-<h2>UnittestDialog</h2>
-
-<p>
-    Class implementing the UI to the pyunit package.
-</p>
-<h3>Signals</h3>
-<dl>
-
-<dt>unittestFile(str, int, bool)</dt>
-<dd>
-emitted to show the source of a
-        unittest file
-</dd>
-<dt>unittestStopped()</dt>
-<dd>
-emitted after a unit test was run
-</dd>
-</dl>
-<h3>Derived from</h3>
-QWidget, Ui_UnittestDialog
-<h3>Class Attributes</h3>
-
-<table>
-<tr><td>ErrorsInfoRole</td></tr><tr><td>FailedExpectedColorDarkTheme</td></tr><tr><td>FailedExpectedColorLightTheme</td></tr><tr><td>SkippedColorDarkTheme</td></tr><tr><td>SkippedColorLightTheme</td></tr><tr><td>SucceededUnexpectedColorDarkTheme</td></tr><tr><td>SucceededUnexpectedColorLightTheme</td></tr><tr><td>TestCaseFileRole</td></tr><tr><td>TestCaseNameRole</td></tr>
-</table>
-<h3>Class Methods</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-<h3>Methods</h3>
-
-<table>
-
-<tr>
-<td><a href="#UnittestDialog.__init__">UnittestDialog</a></td>
-<td>Constructor</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.__UTDiscovered">__UTDiscovered</a></td>
-<td>Private slot to handle the utDiscovered signal.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.__UTPrepared">__UTPrepared</a></td>
-<td>Private slot to handle the utPrepared signal.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.__assembleTestCasesList">__assembleTestCasesList</a></td>
-<td>Private method to assemble a list of test cases included in a test suite.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.__discover">__discover</a></td>
-<td>Private slot to discover unit test but don't run them.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.__findDiscoveryItem">__findDiscoveryItem</a></td>
-<td>Private method to find an item given the module path.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.__loadRecent">__loadRecent</a></td>
-<td>Private method to load the most recently used lists.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.__openEditor">__openEditor</a></td>
-<td>Private method to open an editor window for the given file.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.__populateDiscoveryResults">__populateDiscoveryResults</a></td>
-<td>Private method to populate the test discovery results list.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.__populateVenvComboBox">__populateVenvComboBox</a></td>
-<td>Private method to (re-)populate the virtual environments selector.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.__saveRecent">__saveRecent</a></td>
-<td>Private method to save the most recently used lists.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.__selectedTestCases">__selectedTestCases</a></td>
-<td>Private method to assemble the list of selected test cases and suites.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.__setProgressColor">__setProgressColor</a></td>
-<td>Private method to set the color of the progress color label.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.__setRunningMode">__setRunningMode</a></td>
-<td>Private method to set the GUI in running mode.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.__setStoppedMode">__setStoppedMode</a></td>
-<td>Private method to set the GUI in stopped mode.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.__showSource">__showSource</a></td>
-<td>Private slot to show the source of a traceback in an eric editor.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.__stopTests">__stopTests</a></td>
-<td>Private slot to stop the test.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.clearRecent">clearRecent</a></td>
-<td>Public slot to clear the recently used lists.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.closeEvent">closeEvent</a></td>
-<td>Protected method to handle the close event.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.hasFailedTests">hasFailedTests</a></td>
-<td>Public method to check, if there are failed tests from the last run.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.insertDiscovery">insertDiscovery</a></td>
-<td>Public slot to insert the discovery start directory into the discoveryPicker object.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.insertProg">insertProg</a></td>
-<td>Public slot to insert the filename prog into the testsuitePicker object.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.insertTestName">insertTestName</a></td>
-<td>Public slot to insert a test name into the testComboBox object.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.keyPressEvent">keyPressEvent</a></td>
-<td>Protected slot to handle key press events.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.on_buttonBox_clicked">on_buttonBox_clicked</a></td>
-<td>Private slot called by a button of the button box clicked.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.on_discoverCheckBox_toggled">on_discoverCheckBox_toggled</a></td>
-<td>Private slot handling state changes of the 'discover' checkbox.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.on_discoveryList_itemChanged">on_discoveryList_itemChanged</a></td>
-<td>Private slot handling the user checking or unchecking an item.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.on_discoveryList_itemDoubleClicked">on_discoveryList_itemDoubleClicked</a></td>
-<td>Private slot handling the user double clicking an item.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.on_errorsListWidget_currentTextChanged">on_errorsListWidget_currentTextChanged</a></td>
-<td>Private slot to handle the highlighted signal.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.on_errorsListWidget_itemDoubleClicked">on_errorsListWidget_itemDoubleClicked</a></td>
-<td>Private slot called by doubleclicking an errorlist entry.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.on_testsuitePicker_aboutToShowPathPickerDialog">on_testsuitePicker_aboutToShowPathPickerDialog</a></td>
-<td>Private slot called before the test suite selection dialog is shown.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.on_testsuitePicker_editTextChanged">on_testsuitePicker_editTextChanged</a></td>
-<td>Private slot handling changes of the test suite path.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.on_testsuitePicker_pathSelected">on_testsuitePicker_pathSelected</a></td>
-<td>Private slot called after a test suite has been selected.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.setProjectMode">setProjectMode</a></td>
-<td>Public method to set the project mode of the dialog.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.startTests">startTests</a></td>
-<td>Public slot to start the test.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.testErrored">testErrored</a></td>
-<td>Public method called if a test errors.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.testFailed">testFailed</a></td>
-<td>Public method called if a test fails.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.testFailedExpected">testFailedExpected</a></td>
-<td>Public method called if a test fails as expected.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.testFinished">testFinished</a></td>
-<td>Public method called if a test has finished.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.testSkipped">testSkipped</a></td>
-<td>Public method called if a test was skipped.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.testStarted">testStarted</a></td>
-<td>Public method called if a test is about to be run.</td>
-</tr>
-<tr>
-<td><a href="#UnittestDialog.testSucceededUnexpected">testSucceededUnexpected</a></td>
-<td>Public method called if a test succeeds unexpectedly.</td>
-</tr>
-</table>
-<h3>Static Methods</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-
-<a NAME="UnittestDialog.__init__" ID="UnittestDialog.__init__"></a>
-<h4>UnittestDialog (Constructor)</h4>
-<b>UnittestDialog</b>(<i>prog=None, dbs=None, ui=None, parent=None, name=None</i>)
-
-<p>
-        Constructor
-</p>
-<dl>
-
-<dt><i>prog</i> (str)</dt>
-<dd>
-filename of the program to open
-</dd>
-<dt><i>dbs</i> (DebugServer)</dt>
-<dd>
-reference to the debug server object. It is an indication
-            whether we were called from within the eric IDE.
-</dd>
-<dt><i>ui</i> (UserInterface)</dt>
-<dd>
-reference to the UI object
-</dd>
-<dt><i>parent</i> (QWidget)</dt>
-<dd>
-parent widget of this dialog
-</dd>
-<dt><i>name</i> (str)</dt>
-<dd>
-name of this dialog
-</dd>
-</dl>
-<a NAME="UnittestDialog.__UTDiscovered" ID="UnittestDialog.__UTDiscovered"></a>
-<h4>UnittestDialog.__UTDiscovered</h4>
-<b>__UTDiscovered</b>(<i>testCases, exc_type, exc_value</i>)
-
-<p>
-        Private slot to handle the utDiscovered signal.
-</p>
-<p>
-        If the unittest suite was loaded successfully, we ask the
-        client to run the test suite.
-</p>
-<dl>
-
-<dt><i>testCases</i> (str)</dt>
-<dd>
-list of detected test cases
-</dd>
-<dt><i>exc_type</i> (str)</dt>
-<dd>
-exception type occured during discovery
-</dd>
-<dt><i>exc_value</i> (str)</dt>
-<dd>
-value of exception occured during discovery
-</dd>
-</dl>
-<a NAME="UnittestDialog.__UTPrepared" ID="UnittestDialog.__UTPrepared"></a>
-<h4>UnittestDialog.__UTPrepared</h4>
-<b>__UTPrepared</b>(<i>nrTests, exc_type, exc_value</i>)
-
-<p>
-        Private slot to handle the utPrepared signal.
-</p>
-<p>
-        If the unittest suite was loaded successfully, we ask the
-        client to run the test suite.
-</p>
-<dl>
-
-<dt><i>nrTests</i></dt>
-<dd>
-number of tests contained in the test suite (integer)
-</dd>
-<dt><i>exc_type</i></dt>
-<dd>
-type of exception occured during preparation (string)
-</dd>
-<dt><i>exc_value</i></dt>
-<dd>
-value of exception occured during preparation (string)
-</dd>
-</dl>
-<a NAME="UnittestDialog.__assembleTestCasesList" ID="UnittestDialog.__assembleTestCasesList"></a>
-<h4>UnittestDialog.__assembleTestCasesList</h4>
-<b>__assembleTestCasesList</b>(<i>suite, start</i>)
-
-<p>
-        Private method to assemble a list of test cases included in a test
-        suite.
-</p>
-<dl>
-
-<dt><i>suite</i> (unittest.TestSuite)</dt>
-<dd>
-test suite to be inspected
-</dd>
-<dt><i>start</i> (str)</dt>
-<dd>
-name of directory discovery was started at
-</dd>
-</dl>
-<dl>
-<dt>Return:</dt>
-<dd>
-list of tuples containing the test case ID, a short description
-            and the path of the test file name
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-list of tuples of (str, str, str)
-</dd>
-</dl>
-<a NAME="UnittestDialog.__discover" ID="UnittestDialog.__discover"></a>
-<h4>UnittestDialog.__discover</h4>
-<b>__discover</b>(<i></i>)
-
-<p>
-        Private slot to discover unit test but don't run them.
-</p>
-<a NAME="UnittestDialog.__findDiscoveryItem" ID="UnittestDialog.__findDiscoveryItem"></a>
-<h4>UnittestDialog.__findDiscoveryItem</h4>
-<b>__findDiscoveryItem</b>(<i>modulePath</i>)
-
-<p>
-        Private method to find an item given the module path.
-</p>
-<dl>
-
-<dt><i>modulePath</i> (str)</dt>
-<dd>
-path of the module in dotted notation
-</dd>
-</dl>
-<dl>
-<dt>Return:</dt>
-<dd>
-reference to the item or None
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-QTreeWidgetItem or None
-</dd>
-</dl>
-<a NAME="UnittestDialog.__loadRecent" ID="UnittestDialog.__loadRecent"></a>
-<h4>UnittestDialog.__loadRecent</h4>
-<b>__loadRecent</b>(<i></i>)
-
-<p>
-        Private method to load the most recently used lists.
-</p>
-<a NAME="UnittestDialog.__openEditor" ID="UnittestDialog.__openEditor"></a>
-<h4>UnittestDialog.__openEditor</h4>
-<b>__openEditor</b>(<i>filename, linenumber</i>)
-
-<p>
-        Private method to open an editor window for the given file.
-</p>
-<p>
-        Note: This method opens an editor window when the unittest dialog
-        is called as a standalone application.
-</p>
-<dl>
-
-<dt><i>filename</i> (str)</dt>
-<dd>
-path of the file to be opened
-</dd>
-<dt><i>linenumber</i> (int)</dt>
-<dd>
-line number to place the cursor at
-</dd>
-</dl>
-<a NAME="UnittestDialog.__populateDiscoveryResults" ID="UnittestDialog.__populateDiscoveryResults"></a>
-<h4>UnittestDialog.__populateDiscoveryResults</h4>
-<b>__populateDiscoveryResults</b>(<i>tests</i>)
-
-<p>
-        Private method to populate the test discovery results list.
-</p>
-<dl>
-
-<dt><i>tests</i> (list of tuples of (str, str, str))</dt>
-<dd>
-list of tuples containing the discovery results
-</dd>
-</dl>
-<a NAME="UnittestDialog.__populateVenvComboBox" ID="UnittestDialog.__populateVenvComboBox"></a>
-<h4>UnittestDialog.__populateVenvComboBox</h4>
-<b>__populateVenvComboBox</b>(<i></i>)
-
-<p>
-        Private method to (re-)populate the virtual environments selector.
-</p>
-<a NAME="UnittestDialog.__saveRecent" ID="UnittestDialog.__saveRecent"></a>
-<h4>UnittestDialog.__saveRecent</h4>
-<b>__saveRecent</b>(<i></i>)
-
-<p>
-        Private method to save the most recently used lists.
-</p>
-<a NAME="UnittestDialog.__selectedTestCases" ID="UnittestDialog.__selectedTestCases"></a>
-<h4>UnittestDialog.__selectedTestCases</h4>
-<b>__selectedTestCases</b>(<i>parent=None</i>)
-
-<p>
-        Private method to assemble the list of selected test cases and suites.
-</p>
-<dl>
-
-<dt><i>parent</i> (QTreeWidgetItem)</dt>
-<dd>
-reference to the parent item
-</dd>
-</dl>
-<dl>
-<dt>Return:</dt>
-<dd>
-list of selected test cases
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-list of str
-</dd>
-</dl>
-<a NAME="UnittestDialog.__setProgressColor" ID="UnittestDialog.__setProgressColor"></a>
-<h4>UnittestDialog.__setProgressColor</h4>
-<b>__setProgressColor</b>(<i>color</i>)
-
-<p>
-        Private method to set the color of the progress color label.
-</p>
-<dl>
-
-<dt><i>color</i></dt>
-<dd>
-colour to be shown (string)
-</dd>
-</dl>
-<a NAME="UnittestDialog.__setRunningMode" ID="UnittestDialog.__setRunningMode"></a>
-<h4>UnittestDialog.__setRunningMode</h4>
-<b>__setRunningMode</b>(<i></i>)
-
-<p>
-        Private method to set the GUI in running mode.
-</p>
-<a NAME="UnittestDialog.__setStoppedMode" ID="UnittestDialog.__setStoppedMode"></a>
-<h4>UnittestDialog.__setStoppedMode</h4>
-<b>__setStoppedMode</b>(<i></i>)
-
-<p>
-        Private method to set the GUI in stopped mode.
-</p>
-<a NAME="UnittestDialog.__showSource" ID="UnittestDialog.__showSource"></a>
-<h4>UnittestDialog.__showSource</h4>
-<b>__showSource</b>(<i></i>)
-
-<p>
-        Private slot to show the source of a traceback in an eric editor.
-</p>
-<a NAME="UnittestDialog.__stopTests" ID="UnittestDialog.__stopTests"></a>
-<h4>UnittestDialog.__stopTests</h4>
-<b>__stopTests</b>(<i></i>)
-
-<p>
-        Private slot to stop the test.
-</p>
-<a NAME="UnittestDialog.clearRecent" ID="UnittestDialog.clearRecent"></a>
-<h4>UnittestDialog.clearRecent</h4>
-<b>clearRecent</b>(<i></i>)
-
-<p>
-        Public slot to clear the recently used lists.
-</p>
-<a NAME="UnittestDialog.closeEvent" ID="UnittestDialog.closeEvent"></a>
-<h4>UnittestDialog.closeEvent</h4>
-<b>closeEvent</b>(<i>event</i>)
-
-<p>
-        Protected method to handle the close event.
-</p>
-<dl>
-
-<dt><i>event</i> (QCloseEvent)</dt>
-<dd>
-close event
-</dd>
-</dl>
-<a NAME="UnittestDialog.hasFailedTests" ID="UnittestDialog.hasFailedTests"></a>
-<h4>UnittestDialog.hasFailedTests</h4>
-<b>hasFailedTests</b>(<i></i>)
-
-<p>
-        Public method to check, if there are failed tests from the last run.
-</p>
-<dl>
-<dt>Return:</dt>
-<dd>
-flag indicating the presence of failed tests (boolean)
-</dd>
-</dl>
-<a NAME="UnittestDialog.insertDiscovery" ID="UnittestDialog.insertDiscovery"></a>
-<h4>UnittestDialog.insertDiscovery</h4>
-<b>insertDiscovery</b>(<i>start</i>)
-
-<p>
-        Public slot to insert the discovery start directory into the
-        discoveryPicker object.
-</p>
-<dl>
-
-<dt><i>start</i> (str)</dt>
-<dd>
-start directory name to be inserted
-</dd>
-</dl>
-<a NAME="UnittestDialog.insertProg" ID="UnittestDialog.insertProg"></a>
-<h4>UnittestDialog.insertProg</h4>
-<b>insertProg</b>(<i>prog</i>)
-
-<p>
-        Public slot to insert the filename prog into the testsuitePicker
-        object.
-</p>
-<dl>
-
-<dt><i>prog</i></dt>
-<dd>
-filename to be inserted (string)
-</dd>
-</dl>
-<a NAME="UnittestDialog.insertTestName" ID="UnittestDialog.insertTestName"></a>
-<h4>UnittestDialog.insertTestName</h4>
-<b>insertTestName</b>(<i>testName</i>)
-
-<p>
-        Public slot to insert a test name into the testComboBox object.
-</p>
-<dl>
-
-<dt><i>testName</i></dt>
-<dd>
-name of the test to be inserted (string)
-</dd>
-</dl>
-<a NAME="UnittestDialog.keyPressEvent" ID="UnittestDialog.keyPressEvent"></a>
-<h4>UnittestDialog.keyPressEvent</h4>
-<b>keyPressEvent</b>(<i>evt</i>)
-
-<p>
-        Protected slot to handle key press events.
-</p>
-<dl>
-
-<dt><i>evt</i></dt>
-<dd>
-key press event to handle (QKeyEvent)
-</dd>
-</dl>
-<a NAME="UnittestDialog.on_buttonBox_clicked" ID="UnittestDialog.on_buttonBox_clicked"></a>
-<h4>UnittestDialog.on_buttonBox_clicked</h4>
-<b>on_buttonBox_clicked</b>(<i>button</i>)
-
-<p>
-        Private slot called by a button of the button box clicked.
-</p>
-<dl>
-
-<dt><i>button</i></dt>
-<dd>
-button that was clicked (QAbstractButton)
-</dd>
-</dl>
-<a NAME="UnittestDialog.on_discoverCheckBox_toggled" ID="UnittestDialog.on_discoverCheckBox_toggled"></a>
-<h4>UnittestDialog.on_discoverCheckBox_toggled</h4>
-<b>on_discoverCheckBox_toggled</b>(<i>checked</i>)
-
-<p>
-        Private slot handling state changes of the 'discover' checkbox.
-</p>
-<dl>
-
-<dt><i>checked</i> (bool)</dt>
-<dd>
-state of the checkbox
-</dd>
-</dl>
-<a NAME="UnittestDialog.on_discoveryList_itemChanged" ID="UnittestDialog.on_discoveryList_itemChanged"></a>
-<h4>UnittestDialog.on_discoveryList_itemChanged</h4>
-<b>on_discoveryList_itemChanged</b>(<i>item, column</i>)
-
-<p>
-        Private slot handling the user checking or unchecking an item.
-</p>
-<dl>
-
-<dt><i>item</i> (QTreeWidgetItem)</dt>
-<dd>
-reference to the item
-</dd>
-<dt><i>column</i> (int)</dt>
-<dd>
-changed column
-</dd>
-</dl>
-<a NAME="UnittestDialog.on_discoveryList_itemDoubleClicked" ID="UnittestDialog.on_discoveryList_itemDoubleClicked"></a>
-<h4>UnittestDialog.on_discoveryList_itemDoubleClicked</h4>
-<b>on_discoveryList_itemDoubleClicked</b>(<i>item, column</i>)
-
-<p>
-        Private slot handling the user double clicking an item.
-</p>
-<dl>
-
-<dt><i>item</i> (QTreeWidgetItem)</dt>
-<dd>
-reference to the item
-</dd>
-<dt><i>column</i> (int)</dt>
-<dd>
-column of the double click
-</dd>
-</dl>
-<a NAME="UnittestDialog.on_errorsListWidget_currentTextChanged" ID="UnittestDialog.on_errorsListWidget_currentTextChanged"></a>
-<h4>UnittestDialog.on_errorsListWidget_currentTextChanged</h4>
-<b>on_errorsListWidget_currentTextChanged</b>(<i>text</i>)
-
-<p>
-        Private slot to handle the highlighted signal.
-</p>
-<dl>
-
-<dt><i>text</i></dt>
-<dd>
-current text (string)
-</dd>
-</dl>
-<a NAME="UnittestDialog.on_errorsListWidget_itemDoubleClicked" ID="UnittestDialog.on_errorsListWidget_itemDoubleClicked"></a>
-<h4>UnittestDialog.on_errorsListWidget_itemDoubleClicked</h4>
-<b>on_errorsListWidget_itemDoubleClicked</b>(<i>lbitem</i>)
-
-<p>
-        Private slot called by doubleclicking an errorlist entry.
-</p>
-<p>
-        It will popup a dialog showing the stacktrace.
-        If called from eric, an additional button is displayed
-        to show the python source in an eric source viewer (in
-        erics main window.
-</p>
-<dl>
-
-<dt><i>lbitem</i></dt>
-<dd>
-the listbox item that was double clicked
-</dd>
-</dl>
-<a NAME="UnittestDialog.on_testsuitePicker_aboutToShowPathPickerDialog" ID="UnittestDialog.on_testsuitePicker_aboutToShowPathPickerDialog"></a>
-<h4>UnittestDialog.on_testsuitePicker_aboutToShowPathPickerDialog</h4>
-<b>on_testsuitePicker_aboutToShowPathPickerDialog</b>(<i></i>)
-
-<p>
-        Private slot called before the test suite selection dialog is shown.
-</p>
-<a NAME="UnittestDialog.on_testsuitePicker_editTextChanged" ID="UnittestDialog.on_testsuitePicker_editTextChanged"></a>
-<h4>UnittestDialog.on_testsuitePicker_editTextChanged</h4>
-<b>on_testsuitePicker_editTextChanged</b>(<i>path</i>)
-
-<p>
-        Private slot handling changes of the test suite path.
-</p>
-<dl>
-
-<dt><i>path</i> (str)</dt>
-<dd>
-path of the test suite file
-</dd>
-</dl>
-<a NAME="UnittestDialog.on_testsuitePicker_pathSelected" ID="UnittestDialog.on_testsuitePicker_pathSelected"></a>
-<h4>UnittestDialog.on_testsuitePicker_pathSelected</h4>
-<b>on_testsuitePicker_pathSelected</b>(<i>suite</i>)
-
-<p>
-        Private slot called after a test suite has been selected.
-</p>
-<dl>
-
-<dt><i>suite</i> (str)</dt>
-<dd>
-file name of the test suite
-</dd>
-</dl>
-<a NAME="UnittestDialog.setProjectMode" ID="UnittestDialog.setProjectMode"></a>
-<h4>UnittestDialog.setProjectMode</h4>
-<b>setProjectMode</b>(<i>forProject</i>)
-
-<p>
-        Public method to set the project mode of the dialog.
-</p>
-<dl>
-
-<dt><i>forProject</i> (bool)</dt>
-<dd>
-flag indicating to run for the open project
-</dd>
-</dl>
-<a NAME="UnittestDialog.startTests" ID="UnittestDialog.startTests"></a>
-<h4>UnittestDialog.startTests</h4>
-<b>startTests</b>(<i>failedOnly=False</i>)
-
-<p>
-        Public slot to start the test.
-</p>
-<dl>
-
-<dt><i>failedOnly</i></dt>
-<dd>
-flag indicating to run only failed tests (boolean)
-</dd>
-</dl>
-<a NAME="UnittestDialog.testErrored" ID="UnittestDialog.testErrored"></a>
-<h4>UnittestDialog.testErrored</h4>
-<b>testErrored</b>(<i>test, exc, testId</i>)
-
-<p>
-        Public method called if a test errors.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-name of the test (string)
-</dd>
-<dt><i>exc</i></dt>
-<dd>
-string representation of the exception (string)
-</dd>
-<dt><i>testId</i></dt>
-<dd>
-id of the test (string)
-</dd>
-</dl>
-<a NAME="UnittestDialog.testFailed" ID="UnittestDialog.testFailed"></a>
-<h4>UnittestDialog.testFailed</h4>
-<b>testFailed</b>(<i>test, exc, testId</i>)
-
-<p>
-        Public method called if a test fails.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-name of the test (string)
-</dd>
-<dt><i>exc</i></dt>
-<dd>
-string representation of the exception (string)
-</dd>
-<dt><i>testId</i></dt>
-<dd>
-id of the test (string)
-</dd>
-</dl>
-<a NAME="UnittestDialog.testFailedExpected" ID="UnittestDialog.testFailedExpected"></a>
-<h4>UnittestDialog.testFailedExpected</h4>
-<b>testFailedExpected</b>(<i>test, exc, testId</i>)
-
-<p>
-        Public method called if a test fails as expected.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-name of the test (string)
-</dd>
-<dt><i>exc</i></dt>
-<dd>
-string representation of the exception (string)
-</dd>
-<dt><i>testId</i></dt>
-<dd>
-id of the test (string)
-</dd>
-</dl>
-<a NAME="UnittestDialog.testFinished" ID="UnittestDialog.testFinished"></a>
-<h4>UnittestDialog.testFinished</h4>
-<b>testFinished</b>(<i></i>)
-
-<p>
-        Public method called if a test has finished.
-</p>
-<p>
-        <b>Note</b>: It is also called if it has already failed or errored.
-</p>
-<a NAME="UnittestDialog.testSkipped" ID="UnittestDialog.testSkipped"></a>
-<h4>UnittestDialog.testSkipped</h4>
-<b>testSkipped</b>(<i>test, reason, testId</i>)
-
-<p>
-        Public method called if a test was skipped.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-name of the test (string)
-</dd>
-<dt><i>reason</i></dt>
-<dd>
-reason for skipping the test (string)
-</dd>
-<dt><i>testId</i></dt>
-<dd>
-id of the test (string)
-</dd>
-</dl>
-<a NAME="UnittestDialog.testStarted" ID="UnittestDialog.testStarted"></a>
-<h4>UnittestDialog.testStarted</h4>
-<b>testStarted</b>(<i>test, doc</i>)
-
-<p>
-        Public method called if a test is about to be run.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-name of the started test (string)
-</dd>
-<dt><i>doc</i></dt>
-<dd>
-documentation of the started test (string)
-</dd>
-</dl>
-<a NAME="UnittestDialog.testSucceededUnexpected" ID="UnittestDialog.testSucceededUnexpected"></a>
-<h4>UnittestDialog.testSucceededUnexpected</h4>
-<b>testSucceededUnexpected</b>(<i>test, testId</i>)
-
-<p>
-        Public method called if a test succeeds unexpectedly.
-</p>
-<dl>
-
-<dt><i>test</i></dt>
-<dd>
-name of the test (string)
-</dd>
-<dt><i>testId</i></dt>
-<dd>
-id of the test (string)
-</dd>
-</dl>
-<div align="right"><a href="#top">Up</a></div>
-<hr />
-<hr />
-<a NAME="UnittestWindow" ID="UnittestWindow"></a>
-<h2>UnittestWindow</h2>
-
-<p>
-    Main window class for the standalone dialog.
-</p>
-<h3>Derived from</h3>
-EricMainWindow
-<h3>Class Attributes</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-<h3>Class Methods</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-<h3>Methods</h3>
-
-<table>
-
-<tr>
-<td><a href="#UnittestWindow.__init__">UnittestWindow</a></td>
-<td>Constructor</td>
-</tr>
-<tr>
-<td><a href="#UnittestWindow.eventFilter">eventFilter</a></td>
-<td>Public method to filter events.</td>
-</tr>
-</table>
-<h3>Static Methods</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-
-<a NAME="UnittestWindow.__init__" ID="UnittestWindow.__init__"></a>
-<h4>UnittestWindow (Constructor)</h4>
-<b>UnittestWindow</b>(<i>prog=None, parent=None</i>)
-
-<p>
-        Constructor
-</p>
-<dl>
-
-<dt><i>prog</i></dt>
-<dd>
-filename of the program to open
-</dd>
-<dt><i>parent</i></dt>
-<dd>
-reference to the parent widget (QWidget)
-</dd>
-</dl>
-<a NAME="UnittestWindow.eventFilter" ID="UnittestWindow.eventFilter"></a>
-<h4>UnittestWindow.eventFilter</h4>
-<b>eventFilter</b>(<i>obj, event</i>)
-
-<p>
-        Public method to filter events.
-</p>
-<dl>
-
-<dt><i>obj</i></dt>
-<dd>
-reference to the object the event is meant for (QObject)
-</dd>
-<dt><i>event</i></dt>
-<dd>
-reference to the event object (QEvent)
-</dd>
-</dl>
-<dl>
-<dt>Return:</dt>
-<dd>
-flag indicating, whether the event was handled (boolean)
-</dd>
-</dl>
-<div align="right"><a href="#top">Up</a></div>
-<hr />
-<hr />
-<a NAME="clearSavedHistories" ID="clearSavedHistories"></a>
-<h2>clearSavedHistories</h2>
-<b>clearSavedHistories</b>(<i>self</i>)
-
-<p>
-    Function to clear the saved history lists.
-</p>
-<div align="right"><a href="#top">Up</a></div>
-<hr />
-</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/eric7.Testing.Interfaces.PytestExecutor.html	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,297 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.Testing.Interfaces.PytestExecutor</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.Testing.Interfaces.PytestExecutor</h1>
+
+<p>
+Module implementing the executor for the 'pytest' framework.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#PytestExecutor">PytestExecutor</a></td>
+<td>Class implementing the executor for the 'pytest' framework.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="PytestExecutor" ID="PytestExecutor"></a>
+<h2>PytestExecutor</h2>
+
+<p>
+    Class implementing the executor for the 'pytest' framework.
+</p>
+<h3>Derived from</h3>
+TestExecutorBase
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>module</td></tr><tr><td>name</td></tr><tr><td>runner</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#PytestExecutor.__init__">PytestExecutor</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#PytestExecutor.__nodeid2testname">__nodeid2testname</a></td>
+<td>Private method to convert a nodeid to a test name.</td>
+</tr>
+<tr>
+<td><a href="#PytestExecutor.__normalizeModuleName">__normalizeModuleName</a></td>
+<td>Private method to convert a module name reported by pytest to Python conventions.</td>
+</tr>
+<tr>
+<td><a href="#PytestExecutor.__processData">__processData</a></td>
+<td>Private slot to process the received data.</td>
+</tr>
+<tr>
+<td><a href="#PytestExecutor.createArguments">createArguments</a></td>
+<td>Public method to create the arguments needed to start the test process.</td>
+</tr>
+<tr>
+<td><a href="#PytestExecutor.finished">finished</a></td>
+<td>Public method handling the unit test process been finished.</td>
+</tr>
+<tr>
+<td><a href="#PytestExecutor.getVersions">getVersions</a></td>
+<td>Public method to get the test framework version and version information of its installed plugins.</td>
+</tr>
+<tr>
+<td><a href="#PytestExecutor.hasCoverage">hasCoverage</a></td>
+<td>Public method to get the test framework version and version information of its installed plugins.</td>
+</tr>
+<tr>
+<td><a href="#PytestExecutor.start">start</a></td>
+<td>Public method to start the testing process.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="PytestExecutor.__init__" ID="PytestExecutor.__init__"></a>
+<h4>PytestExecutor (Constructor)</h4>
+<b>PytestExecutor</b>(<i>testWidget</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>testWidget</i> (TestingWidget)</dt>
+<dd>
+reference to the unit test widget
+</dd>
+</dl>
+<a NAME="PytestExecutor.__nodeid2testname" ID="PytestExecutor.__nodeid2testname"></a>
+<h4>PytestExecutor.__nodeid2testname</h4>
+<b>__nodeid2testname</b>(<i>nodeid</i>)
+
+<p>
+        Private method to convert a nodeid to a test name.
+</p>
+<dl>
+
+<dt><i>nodeid</i> (str)</dt>
+<dd>
+nodeid to be converted
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+test name
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+str
+</dd>
+</dl>
+<a NAME="PytestExecutor.__normalizeModuleName" ID="PytestExecutor.__normalizeModuleName"></a>
+<h4>PytestExecutor.__normalizeModuleName</h4>
+<b>__normalizeModuleName</b>(<i>name</i>)
+
+<p>
+        Private method to convert a module name reported by pytest to Python
+        conventions.
+</p>
+<p>
+        This method strips the extensions '.pyw' and '.py' first and replaces
+        '/' and '\' thereafter.
+</p>
+<dl>
+
+<dt><i>name</i> (str)</dt>
+<dd>
+module name reported by pytest
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+module name iaw. Python conventions
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+str
+</dd>
+</dl>
+<a NAME="PytestExecutor.__processData" ID="PytestExecutor.__processData"></a>
+<h4>PytestExecutor.__processData</h4>
+<b>__processData</b>(<i>data</i>)
+
+<p>
+        Private slot to process the received data.
+</p>
+<dl>
+
+<dt><i>data</i> (dict)</dt>
+<dd>
+data object received
+</dd>
+</dl>
+<a NAME="PytestExecutor.createArguments" ID="PytestExecutor.createArguments"></a>
+<h4>PytestExecutor.createArguments</h4>
+<b>createArguments</b>(<i>config</i>)
+
+<p>
+        Public method to create the arguments needed to start the test process.
+</p>
+<dl>
+
+<dt><i>config</i> (TestConfig)</dt>
+<dd>
+configuration for the test execution
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+list of process arguments
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+list of str
+</dd>
+</dl>
+<a NAME="PytestExecutor.finished" ID="PytestExecutor.finished"></a>
+<h4>PytestExecutor.finished</h4>
+<b>finished</b>(<i></i>)
+
+<p>
+        Public method handling the unit test process been finished.
+</p>
+<p>
+        This method should read the results (if necessary) and emit the signal
+        testFinished.
+</p>
+<a NAME="PytestExecutor.getVersions" ID="PytestExecutor.getVersions"></a>
+<h4>PytestExecutor.getVersions</h4>
+<b>getVersions</b>(<i>interpreter</i>)
+
+<p>
+        Public method to get the test framework version and version information
+        of its installed plugins.
+</p>
+<dl>
+
+<dt><i>interpreter</i> (str)</dt>
+<dd>
+interpreter to be used for the test
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+dictionary containing the framework name and version and the
+            list of available plugins with name and version each
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+dict
+</dd>
+</dl>
+<a NAME="PytestExecutor.hasCoverage" ID="PytestExecutor.hasCoverage"></a>
+<h4>PytestExecutor.hasCoverage</h4>
+<b>hasCoverage</b>(<i>interpreter</i>)
+
+<p>
+        Public method to get the test framework version and version information
+        of its installed plugins.
+</p>
+<dl>
+
+<dt><i>interpreter</i> (str)</dt>
+<dd>
+interpreter to be used for the test
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+flag indicating the availability of coverage functionality
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+bool
+</dd>
+</dl>
+<a NAME="PytestExecutor.start" ID="PytestExecutor.start"></a>
+<h4>PytestExecutor.start</h4>
+<b>start</b>(<i>config, pythonpath</i>)
+
+<p>
+        Public method to start the testing process.
+</p>
+<dl>
+
+<dt><i>config</i> (TestConfig)</dt>
+<dd>
+configuration for the test execution
+</dd>
+<dt><i>pythonpath</i> (list of str)</dt>
+<dd>
+list of directories to be added to the Python path
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/eric7.Testing.Interfaces.PytestRunner.html	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,364 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.Testing.Interfaces.PytestRunner</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.Testing.Interfaces.PytestRunner</h1>
+
+<p>
+Module implementing the test runner script for the 'pytest' framework.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#EricPlugin">EricPlugin</a></td>
+<td>Class implementing a pytest plugin which reports the data in a format suitable for the PytestExecutor.</td>
+</tr>
+<tr>
+<td><a href="#GetPluginVersionsPlugin">GetPluginVersionsPlugin</a></td>
+<td>Class implementing a pytest plugin to extract the version info of all installed plugins.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+
+<tr>
+<td><a href="#getVersions">getVersions</a></td>
+<td>Function to determine the framework version and versions of all available plugins.</td>
+</tr>
+</table>
+<hr />
+<hr />
+<a NAME="EricPlugin" ID="EricPlugin"></a>
+<h2>EricPlugin</h2>
+
+<p>
+    Class implementing a pytest plugin which reports the data in a format
+    suitable for the PytestExecutor.
+</p>
+<h3>Derived from</h3>
+None
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#EricPlugin.__init__">EricPlugin</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#EricPlugin.__initializeReportData">__initializeReportData</a></td>
+<td>Private method to initialize attributes for data collection.</td>
+</tr>
+<tr>
+<td><a href="#EricPlugin.pytest_collectreport">pytest_collectreport</a></td>
+<td>Public method called by pytest after the tests have been collected.</td>
+</tr>
+<tr>
+<td><a href="#EricPlugin.pytest_itemcollected">pytest_itemcollected</a></td>
+<td>Public malled by pytest after a test item has been collected.</td>
+</tr>
+<tr>
+<td><a href="#EricPlugin.pytest_report_header">pytest_report_header</a></td>
+<td>Public method called by pytest before any reporting.</td>
+</tr>
+<tr>
+<td><a href="#EricPlugin.pytest_runtest_logfinish">pytest_runtest_logfinish</a></td>
+<td>Public method called by pytest after a test has been completed.</td>
+</tr>
+<tr>
+<td><a href="#EricPlugin.pytest_runtest_logreport">pytest_runtest_logreport</a></td>
+<td>Public method called by pytest when a test phase (setup, call and teardown) has been completed.</td>
+</tr>
+<tr>
+<td><a href="#EricPlugin.pytest_runtest_logstart">pytest_runtest_logstart</a></td>
+<td>Public method called by pytest before running a test.</td>
+</tr>
+<tr>
+<td><a href="#EricPlugin.pytest_sessionfinish">pytest_sessionfinish</a></td>
+<td>Public method called by pytest after the whole test run finished.</td>
+</tr>
+<tr>
+<td><a href="#EricPlugin.pytest_sessionstart">pytest_sessionstart</a></td>
+<td>Public method called by pytest before performing collection and entering the run test loop.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="EricPlugin.__init__" ID="EricPlugin.__init__"></a>
+<h4>EricPlugin (Constructor)</h4>
+<b>EricPlugin</b>(<i>writer</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>writer</i> (EricJsonWriter)</dt>
+<dd>
+reference to the object to write the results to
+</dd>
+</dl>
+<a NAME="EricPlugin.__initializeReportData" ID="EricPlugin.__initializeReportData"></a>
+<h4>EricPlugin.__initializeReportData</h4>
+<b>__initializeReportData</b>(<i></i>)
+
+<p>
+        Private method to initialize attributes for data collection.
+</p>
+<a NAME="EricPlugin.pytest_collectreport" ID="EricPlugin.pytest_collectreport"></a>
+<h4>EricPlugin.pytest_collectreport</h4>
+<b>pytest_collectreport</b>(<i>report</i>)
+
+<p>
+        Public method called by pytest after the tests have been collected.
+</p>
+<dl>
+
+<dt><i>report</i> (CollectReport)</dt>
+<dd>
+reference to the report object
+</dd>
+</dl>
+<a NAME="EricPlugin.pytest_itemcollected" ID="EricPlugin.pytest_itemcollected"></a>
+<h4>EricPlugin.pytest_itemcollected</h4>
+<b>pytest_itemcollected</b>(<i>item</i>)
+
+<p>
+        Public malled by pytest after a test item has been collected.
+</p>
+<dl>
+
+<dt><i>item</i> (Item)</dt>
+<dd>
+reference to the collected test item
+</dd>
+</dl>
+<a NAME="EricPlugin.pytest_report_header" ID="EricPlugin.pytest_report_header"></a>
+<h4>EricPlugin.pytest_report_header</h4>
+<b>pytest_report_header</b>(<i>config, startdir</i>)
+
+<p>
+        Public method called by pytest before any reporting.
+</p>
+<dl>
+
+<dt><i>config</i> (Config)</dt>
+<dd>
+reference to the configuration object
+</dd>
+<dt><i>startdir</i> (LocalPath)</dt>
+<dd>
+starting directory
+</dd>
+</dl>
+<a NAME="EricPlugin.pytest_runtest_logfinish" ID="EricPlugin.pytest_runtest_logfinish"></a>
+<h4>EricPlugin.pytest_runtest_logfinish</h4>
+<b>pytest_runtest_logfinish</b>(<i>nodeid, location</i>)
+
+<p>
+        Public method called by pytest after a test has been completed.
+</p>
+<dl>
+
+<dt><i>nodeid</i> (str)</dt>
+<dd>
+node id of the test item
+</dd>
+<dt><i>location</i> (tuple of (str, int, str))</dt>
+<dd>
+tuple containing the file name, the line number and
+            the test name
+</dd>
+</dl>
+<a NAME="EricPlugin.pytest_runtest_logreport" ID="EricPlugin.pytest_runtest_logreport"></a>
+<h4>EricPlugin.pytest_runtest_logreport</h4>
+<b>pytest_runtest_logreport</b>(<i>report</i>)
+
+<p>
+        Public method called by pytest when a test phase (setup, call and
+            teardown) has been completed.
+</p>
+<dl>
+
+<dt><i>report</i> (TestReport)</dt>
+<dd>
+reference to the test report object
+</dd>
+</dl>
+<a NAME="EricPlugin.pytest_runtest_logstart" ID="EricPlugin.pytest_runtest_logstart"></a>
+<h4>EricPlugin.pytest_runtest_logstart</h4>
+<b>pytest_runtest_logstart</b>(<i>nodeid, location</i>)
+
+<p>
+        Public method called by pytest before running a test.
+</p>
+<dl>
+
+<dt><i>nodeid</i> (str)</dt>
+<dd>
+node id of the test item
+</dd>
+<dt><i>location</i> (tuple of (str, int, str))</dt>
+<dd>
+tuple containing the file name, the line number and
+            the test name
+</dd>
+</dl>
+<a NAME="EricPlugin.pytest_sessionfinish" ID="EricPlugin.pytest_sessionfinish"></a>
+<h4>EricPlugin.pytest_sessionfinish</h4>
+<b>pytest_sessionfinish</b>(<i>session, exitstatus</i>)
+
+<p>
+        Public method called by pytest after the whole test run finished.
+</p>
+<dl>
+
+<dt><i>session</i> (Session)</dt>
+<dd>
+reference to the session object
+</dd>
+<dt><i>exitstatus</i> (int or ExitCode)</dt>
+<dd>
+exit status
+</dd>
+</dl>
+<a NAME="EricPlugin.pytest_sessionstart" ID="EricPlugin.pytest_sessionstart"></a>
+<h4>EricPlugin.pytest_sessionstart</h4>
+<b>pytest_sessionstart</b>(<i>session</i>)
+
+<p>
+        Public method called by pytest before performing collection and
+        entering the run test loop.
+</p>
+<dl>
+
+<dt><i>session</i> (Session)</dt>
+<dd>
+reference to the session object
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="GetPluginVersionsPlugin" ID="GetPluginVersionsPlugin"></a>
+<h2>GetPluginVersionsPlugin</h2>
+
+<p>
+    Class implementing a pytest plugin to extract the version info of all
+    installed plugins.
+</p>
+<h3>Derived from</h3>
+None
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#GetPluginVersionsPlugin.__init__">GetPluginVersionsPlugin</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#GetPluginVersionsPlugin.getVersions">getVersions</a></td>
+<td>Public method to get the assembled list of plugin versions.</td>
+</tr>
+<tr>
+<td><a href="#GetPluginVersionsPlugin.pytest_cmdline_main">pytest_cmdline_main</a></td>
+<td>Public method called for performing the main command line action.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="GetPluginVersionsPlugin.__init__" ID="GetPluginVersionsPlugin.__init__"></a>
+<h4>GetPluginVersionsPlugin (Constructor)</h4>
+<b>GetPluginVersionsPlugin</b>(<i></i>)
+
+<p>
+        Constructor
+</p>
+<a NAME="GetPluginVersionsPlugin.getVersions" ID="GetPluginVersionsPlugin.getVersions"></a>
+<h4>GetPluginVersionsPlugin.getVersions</h4>
+<b>getVersions</b>(<i></i>)
+
+<p>
+        Public method to get the assembled list of plugin versions.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+list of collected plugin versions
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+list of dict
+</dd>
+</dl>
+<a NAME="GetPluginVersionsPlugin.pytest_cmdline_main" ID="GetPluginVersionsPlugin.pytest_cmdline_main"></a>
+<h4>GetPluginVersionsPlugin.pytest_cmdline_main</h4>
+<b>pytest_cmdline_main</b>(<i>config</i>)
+
+<p>
+        Public method called for performing the main command line action.
+</p>
+<dl>
+
+<dt><i>config</i> (Config)</dt>
+<dd>
+pytest config object
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="getVersions" ID="getVersions"></a>
+<h2>getVersions</h2>
+<b>getVersions</b>(<i></i>)
+
+<p>
+    Function to determine the framework version and versions of all available
+    plugins.
+</p>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/eric7.Testing.Interfaces.TestExecutorBase.html	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,520 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.Testing.Interfaces.TestExecutorBase</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.Testing.Interfaces.TestExecutorBase</h1>
+
+<p>
+Module implementing the executor base class for the various testing frameworks
+and supporting classes.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#TestConfig">TestConfig</a></td>
+<td>Class containing the test run configuration.</td>
+</tr>
+<tr>
+<td><a href="#TestExecutorBase">TestExecutorBase</a></td>
+<td>Base class for test framework specific implementations.</td>
+</tr>
+<tr>
+<td><a href="#TestResult">TestResult</a></td>
+<td>Class containing the test result data.</td>
+</tr>
+<tr>
+<td><a href="#TestResultCategory">TestResultCategory</a></td>
+<td>Class defining the supported result categories.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="TestConfig" ID="TestConfig"></a>
+<h2>TestConfig</h2>
+
+<p>
+    Class containing the test run configuration.
+</p>
+<h3>Derived from</h3>
+None
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="TestExecutorBase" ID="TestExecutorBase"></a>
+<h2>TestExecutorBase</h2>
+
+<p>
+    Base class for test framework specific implementations.
+</p>
+<h3>Signals</h3>
+<dl>
+
+<dt>collectError(list of tuple of (str, str)</dt>
+<dd>
+) emitted when errors
+        are encountered during test collection. Tuple elements are the
+        test name and the error message.
+</dd>
+<dt>collected(list of tuple of (str, str, str)</dt>
+<dd>
+) emitted after all tests
+        have been collected. Tuple elements are the test id, the test name and
+        a short description of the test.
+</dd>
+<dt>coverageDataSaved(str)</dt>
+<dd>
+emitted after the coverage data was saved.
+        The element is the absolute path of the coverage data file.
+</dd>
+<dt>startTest(tuple of (str, str, str)</dt>
+<dd>
+emitted before tests are run.
+        Tuple elements are test id, test name and short description.
+</dd>
+<dt>stop()</dt>
+<dd>
+emitted when the test process is being stopped.
+</dd>
+<dt>testFinished(list, str)</dt>
+<dd>
+emitted when the test has finished.
+        The elements are the list of test results and the captured output
+        of the test worker (if any).
+</dd>
+<dt>testResult(TestResult)</dt>
+<dd>
+emitted when a test result is ready
+</dd>
+<dt>testRunAboutToBeStarted()</dt>
+<dd>
+emitted just before the test run will
+        be started.
+</dd>
+<dt>testRunFinished(int, float)</dt>
+<dd>
+emitted when the test run has finished.
+        The elements are the number of tests run and the duration in seconds
+</dd>
+</dl>
+<h3>Derived from</h3>
+QObject
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>module</td></tr><tr><td>name</td></tr><tr><td>runner</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#TestExecutorBase.isInstalled">isInstalled</a></td>
+<td>Class method to check whether a test framework is installed.</td>
+</tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#TestExecutorBase.__init__">TestExecutorBase</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#TestExecutorBase._prepareProcess">_prepareProcess</a></td>
+<td>Protected method to prepare a process object to be started.</td>
+</tr>
+<tr>
+<td><a href="#TestExecutorBase.createArguments">createArguments</a></td>
+<td>Public method to create the arguments needed to start the test process.</td>
+</tr>
+<tr>
+<td><a href="#TestExecutorBase.finished">finished</a></td>
+<td>Public method handling the unit test process been finished.</td>
+</tr>
+<tr>
+<td><a href="#TestExecutorBase.getVersions">getVersions</a></td>
+<td>Public method to get the test framework version and version information of its installed plugins.</td>
+</tr>
+<tr>
+<td><a href="#TestExecutorBase.hasCoverage">hasCoverage</a></td>
+<td>Public method to get the test framework version and version information of its installed plugins.</td>
+</tr>
+<tr>
+<td><a href="#TestExecutorBase.readAllOutput">readAllOutput</a></td>
+<td>Public method to read all output of the test process.</td>
+</tr>
+<tr>
+<td><a href="#TestExecutorBase.start">start</a></td>
+<td>Public method to start the testing process.</td>
+</tr>
+<tr>
+<td><a href="#TestExecutorBase.stopIfRunning">stopIfRunning</a></td>
+<td>Public method to stop the testing process, if it is running.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="TestExecutorBase.isInstalled" ID="TestExecutorBase.isInstalled"></a>
+<h4>TestExecutorBase.isInstalled (class method)</h4>
+<b>isInstalled</b>(<i>interpreter</i>)
+
+<p>
+        Class method to check whether a test framework is installed.
+</p>
+<p>
+        The test is performed by checking, if a module loader can found.
+</p>
+<dl>
+
+<dt><i>interpreter</i> (str)</dt>
+<dd>
+interpreter to be used for the test
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+flag indicating the test framework module is installed
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+bool
+</dd>
+</dl>
+<a NAME="TestExecutorBase.__init__" ID="TestExecutorBase.__init__"></a>
+<h4>TestExecutorBase (Constructor)</h4>
+<b>TestExecutorBase</b>(<i>testWidget</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>testWidget</i> (TestingWidget)</dt>
+<dd>
+reference to the unit test widget
+</dd>
+</dl>
+<a NAME="TestExecutorBase._prepareProcess" ID="TestExecutorBase._prepareProcess"></a>
+<h4>TestExecutorBase._prepareProcess</h4>
+<b>_prepareProcess</b>(<i>workDir, pythonpath</i>)
+
+<p>
+        Protected method to prepare a process object to be started.
+</p>
+<dl>
+
+<dt><i>workDir</i> (str)</dt>
+<dd>
+working directory
+</dd>
+<dt><i>pythonpath</i> (list of str)</dt>
+<dd>
+list of directories to be added to the Python path
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+prepared process object
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+QProcess
+</dd>
+</dl>
+<a NAME="TestExecutorBase.createArguments" ID="TestExecutorBase.createArguments"></a>
+<h4>TestExecutorBase.createArguments</h4>
+<b>createArguments</b>(<i>config</i>)
+
+<p>
+        Public method to create the arguments needed to start the test process.
+</p>
+<dl>
+
+<dt><i>config</i> (TestConfig)</dt>
+<dd>
+configuration for the test execution
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+list of process arguments
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+list of str
+</dd>
+</dl>
+<dl>
+
+<dt>Raises <b>NotImplementedError</b>:</dt>
+<dd>
+this method needs to be implemented by
+            derived classes
+</dd>
+</dl>
+<a NAME="TestExecutorBase.finished" ID="TestExecutorBase.finished"></a>
+<h4>TestExecutorBase.finished</h4>
+<b>finished</b>(<i></i>)
+
+<p>
+        Public method handling the unit test process been finished.
+</p>
+<p>
+        This method should read the results (if necessary) and emit the signal
+        testFinished.
+</p>
+<dl>
+
+<dt>Raises <b>NotImplementedError</b>:</dt>
+<dd>
+this method needs to be implemented by
+            derived classes
+</dd>
+</dl>
+<a NAME="TestExecutorBase.getVersions" ID="TestExecutorBase.getVersions"></a>
+<h4>TestExecutorBase.getVersions</h4>
+<b>getVersions</b>(<i>interpreter</i>)
+
+<p>
+        Public method to get the test framework version and version information
+        of its installed plugins.
+</p>
+<dl>
+
+<dt><i>interpreter</i> (str)</dt>
+<dd>
+interpreter to be used for the test
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+dictionary containing the framework name and version and the
+            list of available plugins with name and version each
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+dict
+</dd>
+</dl>
+<dl>
+
+<dt>Raises <b>NotImplementedError</b>:</dt>
+<dd>
+this method needs to be implemented by
+            derived classes
+</dd>
+</dl>
+<a NAME="TestExecutorBase.hasCoverage" ID="TestExecutorBase.hasCoverage"></a>
+<h4>TestExecutorBase.hasCoverage</h4>
+<b>hasCoverage</b>(<i>interpreter</i>)
+
+<p>
+        Public method to get the test framework version and version information
+        of its installed plugins.
+</p>
+<dl>
+
+<dt><i>interpreter</i> (str)</dt>
+<dd>
+interpreter to be used for the test
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+flag indicating the availability of coverage functionality
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+bool
+</dd>
+</dl>
+<dl>
+
+<dt>Raises <b>NotImplementedError</b>:</dt>
+<dd>
+this method needs to be implemented by
+            derived classes
+</dd>
+</dl>
+<a NAME="TestExecutorBase.readAllOutput" ID="TestExecutorBase.readAllOutput"></a>
+<h4>TestExecutorBase.readAllOutput</h4>
+<b>readAllOutput</b>(<i>process=None</i>)
+
+<p>
+        Public method to read all output of the test process.
+</p>
+<dl>
+
+<dt><i>process</i> (QProcess)</dt>
+<dd>
+reference to the process object
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+test process output
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+str
+</dd>
+</dl>
+<a NAME="TestExecutorBase.start" ID="TestExecutorBase.start"></a>
+<h4>TestExecutorBase.start</h4>
+<b>start</b>(<i>config, pythonpath</i>)
+
+<p>
+        Public method to start the testing process.
+</p>
+<dl>
+
+<dt><i>config</i> (TestConfig)</dt>
+<dd>
+configuration for the test execution
+</dd>
+<dt><i>pythonpath</i> (list of str)</dt>
+<dd>
+list of directories to be added to the Python path
+</dd>
+</dl>
+<dl>
+
+<dt>Raises <b>RuntimeError</b>:</dt>
+<dd>
+raised if the the testing process did not start
+</dd>
+</dl>
+<a NAME="TestExecutorBase.stopIfRunning" ID="TestExecutorBase.stopIfRunning"></a>
+<h4>TestExecutorBase.stopIfRunning</h4>
+<b>stopIfRunning</b>(<i></i>)
+
+<p>
+        Public method to stop the testing process, if it is running.
+</p>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="TestResult" ID="TestResult"></a>
+<h2>TestResult</h2>
+
+<p>
+    Class containing the test result data.
+</p>
+<h3>Derived from</h3>
+None
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="TestResultCategory" ID="TestResultCategory"></a>
+<h2>TestResultCategory</h2>
+
+<p>
+    Class defining the supported result categories.
+</p>
+<h3>Derived from</h3>
+IntEnum
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>FAIL</td></tr><tr><td>OK</td></tr><tr><td>PENDING</td></tr><tr><td>RUNNING</td></tr><tr><td>SKIP</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/eric7.Testing.Interfaces.TestFrameworkRegistry.html	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,166 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.Testing.Interfaces.TestFrameworkRegistry</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.Testing.Interfaces.TestFrameworkRegistry</h1>
+
+<p>
+Module implementing a simple registry containing the available test framework
+interfaces.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#TestFrameworkRegistry">TestFrameworkRegistry</a></td>
+<td>Class implementing a simple registry of test framework interfaces.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="TestFrameworkRegistry" ID="TestFrameworkRegistry"></a>
+<h2>TestFrameworkRegistry</h2>
+
+<p>
+    Class implementing a simple registry of test framework interfaces.
+</p>
+<p>
+    The test executor for a framework is responsible for running the tests,
+    receiving the results and preparing them for display. It must implement
+    the interface of TestExecutorBase.
+</p>
+<p>
+    Frameworks must first be registered using '.register()'. This registry
+    can then create the assoicated test executor when '.createExecutor()' is
+    called.
+</p>
+<h3>Derived from</h3>
+None
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#TestFrameworkRegistry.__init__">TestFrameworkRegistry</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#TestFrameworkRegistry.createExecutor">createExecutor</a></td>
+<td>Public method to create a test framework executor.</td>
+</tr>
+<tr>
+<td><a href="#TestFrameworkRegistry.getFrameworks">getFrameworks</a></td>
+<td>Public method to get a copy of the registered frameworks.</td>
+</tr>
+<tr>
+<td><a href="#TestFrameworkRegistry.register">register</a></td>
+<td>Public method to register a test framework executor.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="TestFrameworkRegistry.__init__" ID="TestFrameworkRegistry.__init__"></a>
+<h4>TestFrameworkRegistry (Constructor)</h4>
+<b>TestFrameworkRegistry</b>(<i></i>)
+
+<p>
+        Constructor
+</p>
+<a NAME="TestFrameworkRegistry.createExecutor" ID="TestFrameworkRegistry.createExecutor"></a>
+<h4>TestFrameworkRegistry.createExecutor</h4>
+<b>createExecutor</b>(<i>framework, widget</i>)
+
+<p>
+        Public method to create a test framework executor.
+</p>
+<p>
+        Note: The executor classes have to be registered first.
+</p>
+<dl>
+
+<dt><i>framework</i> (str)</dt>
+<dd>
+name of the test framework
+</dd>
+<dt><i>widget</i> (TestingWidget)</dt>
+<dd>
+reference to the unit test widget
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+test framework executor object
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+TestExecutorBase
+</dd>
+</dl>
+<a NAME="TestFrameworkRegistry.getFrameworks" ID="TestFrameworkRegistry.getFrameworks"></a>
+<h4>TestFrameworkRegistry.getFrameworks</h4>
+<b>getFrameworks</b>(<i></i>)
+
+<p>
+        Public method to get a copy of the registered frameworks.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+copy of the registered frameworks
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+dict
+</dd>
+</dl>
+<a NAME="TestFrameworkRegistry.register" ID="TestFrameworkRegistry.register"></a>
+<h4>TestFrameworkRegistry.register</h4>
+<b>register</b>(<i>executorClass</i>)
+
+<p>
+        Public method to register a test framework executor.
+</p>
+<dl>
+
+<dt><i>executorClass</i> (TestExecutorBase)</dt>
+<dd>
+class implementing the test framework executor
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/eric7.Testing.Interfaces.UnittestExecutor.html	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,232 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.Testing.Interfaces.UnittestExecutor</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.Testing.Interfaces.UnittestExecutor</h1>
+
+<p>
+Module implementing the executor for the standard 'unittest' framework.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#UnittestExecutor">UnittestExecutor</a></td>
+<td>Class implementing the executor for the standard 'unittest' framework.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="UnittestExecutor" ID="UnittestExecutor"></a>
+<h2>UnittestExecutor</h2>
+
+<p>
+    Class implementing the executor for the standard 'unittest' framework.
+</p>
+<h3>Derived from</h3>
+TestExecutorBase
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>module</td></tr><tr><td>name</td></tr><tr><td>runner</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#UnittestExecutor.__init__">UnittestExecutor</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#UnittestExecutor.__processData">__processData</a></td>
+<td>Private slot to process the received data.</td>
+</tr>
+<tr>
+<td><a href="#UnittestExecutor.createArguments">createArguments</a></td>
+<td>Public method to create the arguments needed to start the test process.</td>
+</tr>
+<tr>
+<td><a href="#UnittestExecutor.finished">finished</a></td>
+<td>Public method handling the unit test process been finished.</td>
+</tr>
+<tr>
+<td><a href="#UnittestExecutor.getVersions">getVersions</a></td>
+<td>Public method to get the test framework version and version information of its installed plugins.</td>
+</tr>
+<tr>
+<td><a href="#UnittestExecutor.hasCoverage">hasCoverage</a></td>
+<td>Public method to get the test framework version and version information of its installed plugins.</td>
+</tr>
+<tr>
+<td><a href="#UnittestExecutor.start">start</a></td>
+<td>Public method to start the testing process.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="UnittestExecutor.__init__" ID="UnittestExecutor.__init__"></a>
+<h4>UnittestExecutor (Constructor)</h4>
+<b>UnittestExecutor</b>(<i>testWidget</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>testWidget</i> (TestingWidget)</dt>
+<dd>
+reference to the unit test widget
+</dd>
+</dl>
+<a NAME="UnittestExecutor.__processData" ID="UnittestExecutor.__processData"></a>
+<h4>UnittestExecutor.__processData</h4>
+<b>__processData</b>(<i>data</i>)
+
+<p>
+        Private slot to process the received data.
+</p>
+<dl>
+
+<dt><i>data</i> (dict)</dt>
+<dd>
+data object received
+</dd>
+</dl>
+<a NAME="UnittestExecutor.createArguments" ID="UnittestExecutor.createArguments"></a>
+<h4>UnittestExecutor.createArguments</h4>
+<b>createArguments</b>(<i>config</i>)
+
+<p>
+        Public method to create the arguments needed to start the test process.
+</p>
+<dl>
+
+<dt><i>config</i> (TestConfig)</dt>
+<dd>
+configuration for the test execution
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+list of process arguments
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+list of str
+</dd>
+</dl>
+<a NAME="UnittestExecutor.finished" ID="UnittestExecutor.finished"></a>
+<h4>UnittestExecutor.finished</h4>
+<b>finished</b>(<i></i>)
+
+<p>
+        Public method handling the unit test process been finished.
+</p>
+<p>
+        This method should read the results (if necessary) and emit the signal
+        testFinished.
+</p>
+<a NAME="UnittestExecutor.getVersions" ID="UnittestExecutor.getVersions"></a>
+<h4>UnittestExecutor.getVersions</h4>
+<b>getVersions</b>(<i>interpreter</i>)
+
+<p>
+        Public method to get the test framework version and version information
+        of its installed plugins.
+</p>
+<dl>
+
+<dt><i>interpreter</i> (str)</dt>
+<dd>
+interpreter to be used for the test
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+dictionary containing the framework name and version and the
+            list of available plugins with name and version each
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+dict
+</dd>
+</dl>
+<a NAME="UnittestExecutor.hasCoverage" ID="UnittestExecutor.hasCoverage"></a>
+<h4>UnittestExecutor.hasCoverage</h4>
+<b>hasCoverage</b>(<i>interpreter</i>)
+
+<p>
+        Public method to get the test framework version and version information
+        of its installed plugins.
+</p>
+<dl>
+
+<dt><i>interpreter</i> (str)</dt>
+<dd>
+interpreter to be used for the test
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+flag indicating the availability of coverage functionality
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+bool
+</dd>
+</dl>
+<a NAME="UnittestExecutor.start" ID="UnittestExecutor.start"></a>
+<h4>UnittestExecutor.start</h4>
+<b>start</b>(<i>config, pythonpath</i>)
+
+<p>
+        Public method to start the testing process.
+</p>
+<dl>
+
+<dt><i>config</i> (TestConfig)</dt>
+<dd>
+configuration for the test execution
+</dd>
+<dt><i>pythonpath</i> (list of str)</dt>
+<dd>
+list of directories to be added to the Python path
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/eric7.Testing.Interfaces.UnittestRunner.html	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,339 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.Testing.Interfaces.UnittestRunner</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.Testing.Interfaces.UnittestRunner</h1>
+
+<p>
+Module implementing the test runner script for the 'unittest' framework.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#EricTestResult">EricTestResult</a></td>
+<td>Class implementing a TestResult derivative to send the data via a network connection.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+
+<tr>
+<td><a href="#_assembleTestCasesList">_assembleTestCasesList</a></td>
+<td>Protected function to assemble a list of test cases included in a test suite.</td>
+</tr>
+<tr>
+<td><a href="#runtest">runtest</a></td>
+<td>Function to run the tests.</td>
+</tr>
+</table>
+<hr />
+<hr />
+<a NAME="EricTestResult" ID="EricTestResult"></a>
+<h2>EricTestResult</h2>
+
+<p>
+    Class implementing a TestResult derivative to send the data via a network
+    connection.
+</p>
+<h3>Derived from</h3>
+unittest.TestResult
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#EricTestResult.__init__">EricTestResult</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#EricTestResult.addError">addError</a></td>
+<td>Public method called if a test errored.</td>
+</tr>
+<tr>
+<td><a href="#EricTestResult.addExpectedFailure">addExpectedFailure</a></td>
+<td>Public method called if a test failed expected.</td>
+</tr>
+<tr>
+<td><a href="#EricTestResult.addFailure">addFailure</a></td>
+<td>Public method called if a test failed.</td>
+</tr>
+<tr>
+<td><a href="#EricTestResult.addSkip">addSkip</a></td>
+<td>Public method called if a test was skipped.</td>
+</tr>
+<tr>
+<td><a href="#EricTestResult.addSubTest">addSubTest</a></td>
+<td>Public method called for each subtest to record its result.</td>
+</tr>
+<tr>
+<td><a href="#EricTestResult.addUnexpectedSuccess">addUnexpectedSuccess</a></td>
+<td>Public method called if a test succeeded expectedly.</td>
+</tr>
+<tr>
+<td><a href="#EricTestResult.startTest">startTest</a></td>
+<td>Public method called at the start of a test.</td>
+</tr>
+<tr>
+<td><a href="#EricTestResult.startTestRun">startTestRun</a></td>
+<td>Public method called once before any tests are executed.</td>
+</tr>
+<tr>
+<td><a href="#EricTestResult.stopTest">stopTest</a></td>
+<td>Public method called at the end of a test.</td>
+</tr>
+<tr>
+<td><a href="#EricTestResult.stopTestRun">stopTestRun</a></td>
+<td>Public method called once after all tests are executed.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="EricTestResult.__init__" ID="EricTestResult.__init__"></a>
+<h4>EricTestResult (Constructor)</h4>
+<b>EricTestResult</b>(<i>writer, failfast</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>writer</i> (EricJsonWriter)</dt>
+<dd>
+reference to the object to write the results to
+</dd>
+<dt><i>failfast</i> (bool)</dt>
+<dd>
+flag indicating to stop at the first error
+</dd>
+</dl>
+<a NAME="EricTestResult.addError" ID="EricTestResult.addError"></a>
+<h4>EricTestResult.addError</h4>
+<b>addError</b>(<i>test, err</i>)
+
+<p>
+        Public method called if a test errored.
+</p>
+<dl>
+
+<dt><i>test</i> (TestCase)</dt>
+<dd>
+reference to the test object
+</dd>
+<dt><i>err</i> (tuple)</dt>
+<dd>
+tuple containing the exception data like sys.exc_info
+            (exception type, exception instance, traceback)
+</dd>
+</dl>
+<a NAME="EricTestResult.addExpectedFailure" ID="EricTestResult.addExpectedFailure"></a>
+<h4>EricTestResult.addExpectedFailure</h4>
+<b>addExpectedFailure</b>(<i>test, err</i>)
+
+<p>
+        Public method called if a test failed expected.
+</p>
+<dl>
+
+<dt><i>test</i> (TestCase)</dt>
+<dd>
+reference to the test object
+</dd>
+<dt><i>err</i> (tuple)</dt>
+<dd>
+tuple containing the exception data like sys.exc_info
+            (exception type, exception instance, traceback)
+</dd>
+</dl>
+<a NAME="EricTestResult.addFailure" ID="EricTestResult.addFailure"></a>
+<h4>EricTestResult.addFailure</h4>
+<b>addFailure</b>(<i>test, err</i>)
+
+<p>
+        Public method called if a test failed.
+</p>
+<dl>
+
+<dt><i>test</i> (TestCase)</dt>
+<dd>
+reference to the test object
+</dd>
+<dt><i>err</i> (tuple)</dt>
+<dd>
+tuple containing the exception data like sys.exc_info
+            (exception type, exception instance, traceback)
+</dd>
+</dl>
+<a NAME="EricTestResult.addSkip" ID="EricTestResult.addSkip"></a>
+<h4>EricTestResult.addSkip</h4>
+<b>addSkip</b>(<i>test, reason</i>)
+
+<p>
+        Public method called if a test was skipped.
+</p>
+<dl>
+
+<dt><i>test</i> (TestCase)</dt>
+<dd>
+reference to the test object
+</dd>
+<dt><i>reason</i> (str)</dt>
+<dd>
+reason for skipping the test
+</dd>
+</dl>
+<a NAME="EricTestResult.addSubTest" ID="EricTestResult.addSubTest"></a>
+<h4>EricTestResult.addSubTest</h4>
+<b>addSubTest</b>(<i>test, subtest, err</i>)
+
+<p>
+        Public method called for each subtest to record its result.
+</p>
+<dl>
+
+<dt><i>test</i> (TestCase)</dt>
+<dd>
+reference to the test object
+</dd>
+<dt><i>subtest</i> (TestCase)</dt>
+<dd>
+reference to the subtest object
+</dd>
+<dt><i>err</i> (tuple)</dt>
+<dd>
+tuple containing the exception data like sys.exc_info
+            (exception type, exception instance, traceback)
+</dd>
+</dl>
+<a NAME="EricTestResult.addUnexpectedSuccess" ID="EricTestResult.addUnexpectedSuccess"></a>
+<h4>EricTestResult.addUnexpectedSuccess</h4>
+<b>addUnexpectedSuccess</b>(<i>test</i>)
+
+<p>
+        Public method called if a test succeeded expectedly.
+</p>
+<dl>
+
+<dt><i>test</i> (TestCase)</dt>
+<dd>
+reference to the test object
+</dd>
+</dl>
+<a NAME="EricTestResult.startTest" ID="EricTestResult.startTest"></a>
+<h4>EricTestResult.startTest</h4>
+<b>startTest</b>(<i>test</i>)
+
+<p>
+        Public method called at the start of a test.
+</p>
+<dl>
+
+<dt><i>test</i> (TestCase)</dt>
+<dd>
+reference to the test object
+</dd>
+</dl>
+<a NAME="EricTestResult.startTestRun" ID="EricTestResult.startTestRun"></a>
+<h4>EricTestResult.startTestRun</h4>
+<b>startTestRun</b>(<i></i>)
+
+<p>
+        Public method called once before any tests are executed.
+</p>
+<a NAME="EricTestResult.stopTest" ID="EricTestResult.stopTest"></a>
+<h4>EricTestResult.stopTest</h4>
+<b>stopTest</b>(<i>test</i>)
+
+<p>
+        Public method called at the end of a test.
+</p>
+<dl>
+
+<dt><i>test</i> (TestCase)</dt>
+<dd>
+reference to the test object
+</dd>
+</dl>
+<a NAME="EricTestResult.stopTestRun" ID="EricTestResult.stopTestRun"></a>
+<h4>EricTestResult.stopTestRun</h4>
+<b>stopTestRun</b>(<i></i>)
+
+<p>
+        Public method called once after all tests are executed.
+</p>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="_assembleTestCasesList" ID="_assembleTestCasesList"></a>
+<h2>_assembleTestCasesList</h2>
+<b>_assembleTestCasesList</b>(<i>suite</i>)
+
+<p>
+    Protected function to assemble a list of test cases included in a test
+    suite.
+</p>
+<dl>
+
+<dt><i>suite</i> (unittest.TestSuite)</dt>
+<dd>
+test suite to be inspected
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+list of tuples containing the test case ID, the string
+        representation and the short description
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+list of tuples of (str, str)
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="runtest" ID="runtest"></a>
+<h2>runtest</h2>
+<b>runtest</b>(<i>argv</i>)
+
+<p>
+    Function to run the tests.
+</p>
+<dl>
+
+<dt><i>argv</i> (list of str)</dt>
+<dd>
+list of command line parameters.
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/eric7.Testing.TestResultsTree.html	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,770 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.Testing.TestResultsTree</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.Testing.TestResultsTree</h1>
+
+<p>
+Module implementing a tree view and associated model to show the test result
+data.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>TopLevelId</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#TestResultsModel">TestResultsModel</a></td>
+<td>Class implementing the item model containing the test data.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsTreeView">TestResultsTreeView</a></td>
+<td>Class implementing a tree view to show the test result data.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="TestResultsModel" ID="TestResultsModel"></a>
+<h2>TestResultsModel</h2>
+
+<p>
+    Class implementing the item model containing the test data.
+</p>
+<h3>Signals</h3>
+<dl>
+
+<dt>summary(str)</dt>
+<dd>
+emitted whenever the model data changes. The element
+        is a summary of the test results of the model.
+</dd>
+</dl>
+<h3>Derived from</h3>
+QAbstractItemModel
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>DurationColumn</td></tr><tr><td>Headers</td></tr><tr><td>MessageColumn</td></tr><tr><td>NameColumn</td></tr><tr><td>StatusColumn</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#TestResultsModel.__init__">TestResultsModel</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#TestResultsModel.__summary">__summary</a></td>
+<td>Private method to generate a test results summary text.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsModel.addTestResults">addTestResults</a></td>
+<td>Public method to add test results to the ones already managed by the model.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsModel.clear">clear</a></td>
+<td>Public method to clear the model data.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsModel.columnCount">columnCount</a></td>
+<td>Public method to get the number of columns.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsModel.data">data</a></td>
+<td>Public method to get the data for the various columns and roles.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsModel.durationKey">durationKey</a></td>
+<td>Function to generate a key for duration sorting</td>
+</tr>
+<tr>
+<td><a href="#TestResultsModel.getFailedTests">getFailedTests</a></td>
+<td>Public method to extract the test ids of all failed tests.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsModel.getTestResults">getTestResults</a></td>
+<td>Public method to get the list of test results managed by the model.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsModel.headerData">headerData</a></td>
+<td>Public method to get the header string for the various sections.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsModel.index">index</a></td>
+<td>Public method to generate an index for the given row and column to identify the item.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsModel.parent">parent</a></td>
+<td>Public method to get the parent of the item pointed to by index.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsModel.rowCount">rowCount</a></td>
+<td>Public method to get the number of row for a given parent index.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsModel.setTestResults">setTestResults</a></td>
+<td>Public method to set the list of test results of the model.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsModel.sort">sort</a></td>
+<td>Public method to sort the model data by column in order.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsModel.updateTestResults">updateTestResults</a></td>
+<td>Public method to update the data of managed test result items.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="TestResultsModel.__init__" ID="TestResultsModel.__init__"></a>
+<h4>TestResultsModel (Constructor)</h4>
+<b>TestResultsModel</b>(<i>parent=None</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>parent</i> (QObject (optional))</dt>
+<dd>
+reference to the parent object (defaults to None)
+</dd>
+</dl>
+<a NAME="TestResultsModel.__summary" ID="TestResultsModel.__summary"></a>
+<h4>TestResultsModel.__summary</h4>
+<b>__summary</b>(<i></i>)
+
+<p>
+        Private method to generate a test results summary text.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+test results summary text
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+str
+</dd>
+</dl>
+<a NAME="TestResultsModel.addTestResults" ID="TestResultsModel.addTestResults"></a>
+<h4>TestResultsModel.addTestResults</h4>
+<b>addTestResults</b>(<i>testResults</i>)
+
+<p>
+        Public method to add test results to the ones already managed by the
+        model.
+</p>
+<dl>
+
+<dt><i>testResults</i> (list of TestResult)</dt>
+<dd>
+test results to be added to the model
+</dd>
+</dl>
+<a NAME="TestResultsModel.clear" ID="TestResultsModel.clear"></a>
+<h4>TestResultsModel.clear</h4>
+<b>clear</b>(<i></i>)
+
+<p>
+        Public method to clear the model data.
+</p>
+<a NAME="TestResultsModel.columnCount" ID="TestResultsModel.columnCount"></a>
+<h4>TestResultsModel.columnCount</h4>
+<b>columnCount</b>(<i>parent=QModelIndex()</i>)
+
+<p>
+        Public method to get the number of columns.
+</p>
+<dl>
+
+<dt><i>parent</i> (QModelIndex (optional))</dt>
+<dd>
+index of the parent item (defaults to QModelIndex())
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+number of columns
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+int
+</dd>
+</dl>
+<a NAME="TestResultsModel.data" ID="TestResultsModel.data"></a>
+<h4>TestResultsModel.data</h4>
+<b>data</b>(<i>index, role</i>)
+
+<p>
+        Public method to get the data for the various columns and roles.
+</p>
+<dl>
+
+<dt><i>index</i> (QModelIndex)</dt>
+<dd>
+index of the data to be returned
+</dd>
+<dt><i>role</i> (Qt.ItemDataRole)</dt>
+<dd>
+role designating the data to return
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+requested data item
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+Any
+</dd>
+</dl>
+<a NAME="TestResultsModel.durationKey" ID="TestResultsModel.durationKey"></a>
+<h4>TestResultsModel.durationKey</h4>
+<b>durationKey</b>(<i></i>)
+
+<p>
+            Function to generate a key for duration sorting
+</p>
+<dl>
+
+<dt><i>result</i> (TestResult)</dt>
+<dd>
+result object
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+sort key
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+float
+</dd>
+</dl>
+<a NAME="TestResultsModel.getFailedTests" ID="TestResultsModel.getFailedTests"></a>
+<h4>TestResultsModel.getFailedTests</h4>
+<b>getFailedTests</b>(<i></i>)
+
+<p>
+        Public method to extract the test ids of all failed tests.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+test ids of all failed tests
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+list of str
+</dd>
+</dl>
+<a NAME="TestResultsModel.getTestResults" ID="TestResultsModel.getTestResults"></a>
+<h4>TestResultsModel.getTestResults</h4>
+<b>getTestResults</b>(<i></i>)
+
+<p>
+        Public method to get the list of test results managed by the model.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+list of test results managed by the model
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+list of TestResult
+</dd>
+</dl>
+<a NAME="TestResultsModel.headerData" ID="TestResultsModel.headerData"></a>
+<h4>TestResultsModel.headerData</h4>
+<b>headerData</b>(<i>section, orientation, role=Qt.ItemDataRole.DisplayRole</i>)
+
+<p>
+        Public method to get the header string for the various sections.
+</p>
+<dl>
+
+<dt><i>section</i> (int)</dt>
+<dd>
+section number
+</dd>
+<dt><i>orientation</i> (Qt.Orientation)</dt>
+<dd>
+orientation of the header
+</dd>
+<dt><i>role</i> (Qt.ItemDataRole (optional))</dt>
+<dd>
+data role (defaults to Qt.ItemDataRole.DisplayRole)
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+header string of the section
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+str
+</dd>
+</dl>
+<a NAME="TestResultsModel.index" ID="TestResultsModel.index"></a>
+<h4>TestResultsModel.index</h4>
+<b>index</b>(<i>row, column, parent=QModelIndex()</i>)
+
+<p>
+        Public method to generate an index for the given row and column to
+        identify the item.
+</p>
+<dl>
+
+<dt><i>row</i> (int)</dt>
+<dd>
+row for the index
+</dd>
+<dt><i>column</i> (int)</dt>
+<dd>
+column for the index
+</dd>
+<dt><i>parent</i> (QModelIndex (optional))</dt>
+<dd>
+index of the parent item (defaults to QModelIndex())
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+index for the item
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+QModelIndex
+</dd>
+</dl>
+<a NAME="TestResultsModel.parent" ID="TestResultsModel.parent"></a>
+<h4>TestResultsModel.parent</h4>
+<b>parent</b>(<i>index</i>)
+
+<p>
+        Public method to get the parent of the item pointed to by index.
+</p>
+<dl>
+
+<dt><i>index</i> (QModelIndex)</dt>
+<dd>
+index of the item
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+index of the parent item
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+QModelIndex
+</dd>
+</dl>
+<a NAME="TestResultsModel.rowCount" ID="TestResultsModel.rowCount"></a>
+<h4>TestResultsModel.rowCount</h4>
+<b>rowCount</b>(<i>parent=QModelIndex()</i>)
+
+<p>
+        Public method to get the number of row for a given parent index.
+</p>
+<dl>
+
+<dt><i>parent</i> (QModelIndex (optional))</dt>
+<dd>
+index of the parent item (defaults to QModelIndex())
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+number of rows
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+int
+</dd>
+</dl>
+<a NAME="TestResultsModel.setTestResults" ID="TestResultsModel.setTestResults"></a>
+<h4>TestResultsModel.setTestResults</h4>
+<b>setTestResults</b>(<i>testResults</i>)
+
+<p>
+        Public method to set the list of test results of the model.
+</p>
+<dl>
+
+<dt><i>testResults</i> (list of TestResult)</dt>
+<dd>
+test results to be managed by the model
+</dd>
+</dl>
+<a NAME="TestResultsModel.sort" ID="TestResultsModel.sort"></a>
+<h4>TestResultsModel.sort</h4>
+<b>sort</b>(<i>column, order</i>)
+
+<p>
+        Public method to sort the model data by column in order.
+</p>
+<dl>
+
+<dt><i>column</i> (int)</dt>
+<dd>
+sort column number
+</dd>
+<dt><i>order</i> (Qt.SortOrder)</dt>
+<dd>
+sort order
+</dd>
+</dl>
+<a NAME="TestResultsModel.updateTestResults" ID="TestResultsModel.updateTestResults"></a>
+<h4>TestResultsModel.updateTestResults</h4>
+<b>updateTestResults</b>(<i>testResults</i>)
+
+<p>
+        Public method to update the data of managed test result items.
+</p>
+<dl>
+
+<dt><i>testResults</i> (list of TestResult)</dt>
+<dd>
+test results to be updated
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="TestResultsTreeView" ID="TestResultsTreeView"></a>
+<h2>TestResultsTreeView</h2>
+
+<p>
+    Class implementing a tree view to show the test result data.
+</p>
+<h3>Signals</h3>
+<dl>
+
+<dt>goto(str, int)</dt>
+<dd>
+emitted to go to the position given by file name
+        and line number
+</dd>
+</dl>
+<h3>Derived from</h3>
+QTreeView
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#TestResultsTreeView.__init__">TestResultsTreeView</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#TestResultsTreeView.__canonicalIndex">__canonicalIndex</a></td>
+<td>Private method to create the canonical index for a given index.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsTreeView.__createBackgroundContextMenu">__createBackgroundContextMenu</a></td>
+<td>Private method to create a context menu for the background.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsTreeView.__createContextMenu">__createContextMenu</a></td>
+<td>Private method to create a context menu for the item pointed to by the given index.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsTreeView.__gotoTestDefinition">__gotoTestDefinition</a></td>
+<td>Private slot to show the test definition.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsTreeView.__showContextMenu">__showContextMenu</a></td>
+<td>Private slot to show the context menu.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsTreeView.dataChanged">dataChanged</a></td>
+<td>Public method called when the model data has changed.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsTreeView.reset">reset</a></td>
+<td>Public method to reset the internal state of the view.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsTreeView.resizeColumns">resizeColumns</a></td>
+<td>Public method to resize the columns to their contents.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsTreeView.rowsInserted">rowsInserted</a></td>
+<td>Public method called when rows are inserted.</td>
+</tr>
+<tr>
+<td><a href="#TestResultsTreeView.spanFirstColumn">spanFirstColumn</a></td>
+<td>Public method to make the first column span the row for second level items.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="TestResultsTreeView.__init__" ID="TestResultsTreeView.__init__"></a>
+<h4>TestResultsTreeView (Constructor)</h4>
+<b>TestResultsTreeView</b>(<i>parent=None</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>parent</i> (QWidget (optional))</dt>
+<dd>
+reference to the parent widget (defaults to None)
+</dd>
+</dl>
+<a NAME="TestResultsTreeView.__canonicalIndex" ID="TestResultsTreeView.__canonicalIndex"></a>
+<h4>TestResultsTreeView.__canonicalIndex</h4>
+<b>__canonicalIndex</b>(<i>index</i>)
+
+<p>
+        Private method to create the canonical index for a given index.
+</p>
+<p>
+        The canonical index is the index of the first column of the test
+        result entry (i.e. the top-level item). If the index is invalid,
+        None is returned.
+</p>
+<dl>
+
+<dt><i>index</i> (QModelIndex)</dt>
+<dd>
+index to determine the canonical index for
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+index of the firt column of the associated top-level item index
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+QModelIndex
+</dd>
+</dl>
+<a NAME="TestResultsTreeView.__createBackgroundContextMenu" ID="TestResultsTreeView.__createBackgroundContextMenu"></a>
+<h4>TestResultsTreeView.__createBackgroundContextMenu</h4>
+<b>__createBackgroundContextMenu</b>(<i></i>)
+
+<p>
+        Private method to create a context menu for the background.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+created context menu
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+QMenu
+</dd>
+</dl>
+<a NAME="TestResultsTreeView.__createContextMenu" ID="TestResultsTreeView.__createContextMenu"></a>
+<h4>TestResultsTreeView.__createContextMenu</h4>
+<b>__createContextMenu</b>(<i>index</i>)
+
+<p>
+        Private method to create a context menu for the item pointed to by the
+        given index.
+</p>
+<dl>
+
+<dt><i>index</i> (QModelIndex)</dt>
+<dd>
+index of the item
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+created context menu
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+QMenu
+</dd>
+</dl>
+<a NAME="TestResultsTreeView.__gotoTestDefinition" ID="TestResultsTreeView.__gotoTestDefinition"></a>
+<h4>TestResultsTreeView.__gotoTestDefinition</h4>
+<b>__gotoTestDefinition</b>(<i>index</i>)
+
+<p>
+        Private slot to show the test definition.
+</p>
+<dl>
+
+<dt><i>index</i> (QModelIndex)</dt>
+<dd>
+index for the double-clicked item
+</dd>
+</dl>
+<a NAME="TestResultsTreeView.__showContextMenu" ID="TestResultsTreeView.__showContextMenu"></a>
+<h4>TestResultsTreeView.__showContextMenu</h4>
+<b>__showContextMenu</b>(<i>pos</i>)
+
+<p>
+        Private slot to show the context menu.
+</p>
+<dl>
+
+<dt><i>pos</i> (QPoint)</dt>
+<dd>
+relative position for the context menu
+</dd>
+</dl>
+<a NAME="TestResultsTreeView.dataChanged" ID="TestResultsTreeView.dataChanged"></a>
+<h4>TestResultsTreeView.dataChanged</h4>
+<b>dataChanged</b>(<i>topLeft, bottomRight, roles=[]</i>)
+
+<p>
+        Public method called when the model data has changed.
+</p>
+<dl>
+
+<dt><i>topLeft</i> (QModelIndex)</dt>
+<dd>
+index of the top left element
+</dd>
+<dt><i>bottomRight</i> (QModelIndex)</dt>
+<dd>
+index of the bottom right element
+</dd>
+<dt><i>roles</i> (list of Qt.ItemDataRole (optional))</dt>
+<dd>
+list of roles changed (defaults to [])
+</dd>
+</dl>
+<a NAME="TestResultsTreeView.reset" ID="TestResultsTreeView.reset"></a>
+<h4>TestResultsTreeView.reset</h4>
+<b>reset</b>(<i></i>)
+
+<p>
+        Public method to reset the internal state of the view.
+</p>
+<a NAME="TestResultsTreeView.resizeColumns" ID="TestResultsTreeView.resizeColumns"></a>
+<h4>TestResultsTreeView.resizeColumns</h4>
+<b>resizeColumns</b>(<i></i>)
+
+<p>
+        Public method to resize the columns to their contents.
+</p>
+<a NAME="TestResultsTreeView.rowsInserted" ID="TestResultsTreeView.rowsInserted"></a>
+<h4>TestResultsTreeView.rowsInserted</h4>
+<b>rowsInserted</b>(<i>parent, startRow, endRow</i>)
+
+<p>
+        Public method called when rows are inserted.
+</p>
+<dl>
+
+<dt><i>parent</i> (QModelIndex)</dt>
+<dd>
+model index of the parent item
+</dd>
+<dt><i>startRow</i> (int)</dt>
+<dd>
+first row been inserted
+</dd>
+<dt><i>endRow</i> (int)</dt>
+<dd>
+last row been inserted
+</dd>
+</dl>
+<a NAME="TestResultsTreeView.spanFirstColumn" ID="TestResultsTreeView.spanFirstColumn"></a>
+<h4>TestResultsTreeView.spanFirstColumn</h4>
+<b>spanFirstColumn</b>(<i>startRow, endRow</i>)
+
+<p>
+        Public method to make the first column span the row for second level
+        items.
+</p>
+<p>
+        These items contain the test results.
+</p>
+<dl>
+
+<dt><i>startRow</i> (QModelIndex)</dt>
+<dd>
+index of the first row to span
+</dd>
+<dt><i>endRow</i> (QModelIndex)</dt>
+<dd>
+index of the last row (including) to span
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/eric7.Testing.TestingWidget.html	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,970 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.Testing.TestingWidget</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.Testing.TestingWidget</h1>
+
+<p>
+Module implementing a widget to orchestrate unit test execution.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#TestingWidget">TestingWidget</a></td>
+<td>Class implementing a widget to orchestrate unit test execution.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidgetModes">TestingWidgetModes</a></td>
+<td>Class defining the various modes of the testing widget.</td>
+</tr>
+<tr>
+<td><a href="#TestingWindow">TestingWindow</a></td>
+<td>Main window class for the standalone dialog.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+
+<tr>
+<td><a href="#clearSavedHistories">clearSavedHistories</a></td>
+<td>Function to clear the saved history lists.</td>
+</tr>
+</table>
+<hr />
+<hr />
+<a NAME="TestingWidget" ID="TestingWidget"></a>
+<h2>TestingWidget</h2>
+
+<p>
+    Class implementing a widget to orchestrate unit test execution.
+</p>
+<h3>Signals</h3>
+<dl>
+
+<dt>testFile(str, int, bool)</dt>
+<dd>
+emitted to show the source of a
+       test file
+</dd>
+<dt>testRunStopped()</dt>
+<dd>
+emitted after a test run has finished
+</dd>
+</dl>
+<h3>Derived from</h3>
+QWidget, Ui_TestingWidget
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#TestingWidget.__init__">TestingWidget</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__adjustPendingState">__adjustPendingState</a></td>
+<td>Private method to change the status indicator of all still pending tests to "not run".</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__coverageData">__coverageData</a></td>
+<td>Private slot to handle the 'coverageData' signal of the executor.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__insertDiscovery">__insertDiscovery</a></td>
+<td>Private slot to insert the discovery start directory into the discoveryPicker object.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__insertHistory">__insertHistory</a></td>
+<td>Private slot to insert an item into a history object.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__insertTestFile">__insertTestFile</a></td>
+<td>Private slot to insert a test file name into the testsuitePicker object.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__insertTestName">__insertTestName</a></td>
+<td>Private slot to insert a test name into the testComboBox object.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__loadRecent">__loadRecent</a></td>
+<td>Private method to load the most recently used lists.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__openEditor">__openEditor</a></td>
+<td>Private method to open an editor window for the given file.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__populateTestFrameworkComboBox">__populateTestFrameworkComboBox</a></td>
+<td>Private method to (re-)populate the test framework selector.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__populateVenvComboBox">__populateVenvComboBox</a></td>
+<td>Private method to (re-)populate the virtual environments selector.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__processTestResult">__processTestResult</a></td>
+<td>Private slot to handle the receipt of a test result object.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__projectClosed">__projectClosed</a></td>
+<td>Private slot to handle a project being closed.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__projectOpened">__projectOpened</a></td>
+<td>Private slot to handle a project being opened.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__resetResults">__resetResults</a></td>
+<td>Private slot to reset the test results tab and data.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__saveRecent">__saveRecent</a></td>
+<td>Private method to save the most recently used lists.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__setIdleMode">__setIdleMode</a></td>
+<td>Private slot to switch the widget to idle mode.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__setRunningMode">__setRunningMode</a></td>
+<td>Private slot to switch the widget to running mode.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__setStatusLabel">__setStatusLabel</a></td>
+<td>Private slot to set the status label to the text sent by the model.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__setStoppedMode">__setStoppedMode</a></td>
+<td>Private slot to switch the widget to stopped mode.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__showCoverageDialog">__showCoverageDialog</a></td>
+<td>Private slot to show a code coverage dialog for the most recent test run.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__showLogOutput">__showLogOutput</a></td>
+<td>Private slot to show the output of the most recent test run.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__showSource">__showSource</a></td>
+<td>Private slot to show the source of a traceback in an editor.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__stopTests">__stopTests</a></td>
+<td>Private slot to stop the current test run.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__testProcessFinished">__testProcessFinished</a></td>
+<td>Private slot to handle the 'testFinished' signal of the executor.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__testRunAboutToBeStarted">__testRunAboutToBeStarted</a></td>
+<td>Private slot to handle the 'testRunAboutToBeStarted' signal of the executor.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__testRunFinished">__testRunFinished</a></td>
+<td>Private slot to handle the 'testRunFinished' signal of the executor.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__testStarted">__testStarted</a></td>
+<td>Private slot handling the 'startTest' signal of the executor.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__testsCollectError">__testsCollectError</a></td>
+<td>Private slot handling the 'collectError' signal of the executor.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__testsCollected">__testsCollected</a></td>
+<td>Private slot handling the 'collected' signal of the executor.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__testsStopped">__testsStopped</a></td>
+<td>Private slot to handle the 'stop' signal of the executor.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__updateButtonBoxButtons">__updateButtonBoxButtons</a></td>
+<td>Private slot to update the state of the buttons of the button box.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__updateCoverage">__updateCoverage</a></td>
+<td>Private slot to update the state of the coverage checkbox depending on the selected framework's capabilities.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.__updateProgress">__updateProgress</a></td>
+<td>Private slot update the progress indicators.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.clearRecent">clearRecent</a></td>
+<td>Public slot to clear the recently used lists.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.closeEvent">closeEvent</a></td>
+<td>Protected method to handle the close event.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.getFailedTests">getFailedTests</a></td>
+<td>Public method to get the list of failed tests (if any).</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.getResultsModel">getResultsModel</a></td>
+<td>Public method to get a reference to the model containing the test result data.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.hasFailedTests">hasFailedTests</a></td>
+<td>Public method to check for failed tests.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.on_buttonBox_clicked">on_buttonBox_clicked</a></td>
+<td>Private slot called by a button of the button box clicked.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.on_discoverCheckBox_toggled">on_discoverCheckBox_toggled</a></td>
+<td>Private slot handling state changes of the 'discover' checkbox.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.on_frameworkComboBox_currentIndexChanged">on_frameworkComboBox_currentIndexChanged</a></td>
+<td>Private slot handling the selection of a test framework.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.on_testsuitePicker_aboutToShowPathPickerDialog">on_testsuitePicker_aboutToShowPathPickerDialog</a></td>
+<td>Private slot called before the test file selection dialog is shown.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.on_venvComboBox_currentIndexChanged">on_venvComboBox_currentIndexChanged</a></td>
+<td>Private slot handling the selection of a virtual environment.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.on_versionsButton_clicked">on_versionsButton_clicked</a></td>
+<td>Private slot to show the versions of available plugins.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.setTestFile">setTestFile</a></td>
+<td>Public slot to set the given test file as the current one.</td>
+</tr>
+<tr>
+<td><a href="#TestingWidget.startTests">startTests</a></td>
+<td>Public slot to start the test run.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="TestingWidget.__init__" ID="TestingWidget.__init__"></a>
+<h4>TestingWidget (Constructor)</h4>
+<b>TestingWidget</b>(<i>testfile=None, parent=None</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>testfile</i> (str)</dt>
+<dd>
+file name of the test to load
+</dd>
+<dt><i>parent</i> (QWidget (optional))</dt>
+<dd>
+reference to the parent widget (defaults to None)
+</dd>
+</dl>
+<a NAME="TestingWidget.__adjustPendingState" ID="TestingWidget.__adjustPendingState"></a>
+<h4>TestingWidget.__adjustPendingState</h4>
+<b>__adjustPendingState</b>(<i></i>)
+
+<p>
+        Private method to change the status indicator of all still pending
+        tests to "not run".
+</p>
+<a NAME="TestingWidget.__coverageData" ID="TestingWidget.__coverageData"></a>
+<h4>TestingWidget.__coverageData</h4>
+<b>__coverageData</b>(<i>coverageFile</i>)
+
+<p>
+        Private slot to handle the 'coverageData' signal of the executor.
+</p>
+<dl>
+
+<dt><i>coverageFile</i> (str)</dt>
+<dd>
+file containing the coverage data
+</dd>
+</dl>
+<a NAME="TestingWidget.__insertDiscovery" ID="TestingWidget.__insertDiscovery"></a>
+<h4>TestingWidget.__insertDiscovery</h4>
+<b>__insertDiscovery</b>(<i>start</i>)
+
+<p>
+        Private slot to insert the discovery start directory into the
+        discoveryPicker object.
+</p>
+<dl>
+
+<dt><i>start</i> (str)</dt>
+<dd>
+start directory name to be inserted
+</dd>
+</dl>
+<a NAME="TestingWidget.__insertHistory" ID="TestingWidget.__insertHistory"></a>
+<h4>TestingWidget.__insertHistory</h4>
+<b>__insertHistory</b>(<i>widget, history, item</i>)
+
+<p>
+        Private slot to insert an item into a history object.
+</p>
+<dl>
+
+<dt><i>widget</i> (QComboBox or EricComboPathPicker)</dt>
+<dd>
+reference to the widget
+</dd>
+<dt><i>history</i> (list of str)</dt>
+<dd>
+array containing the history
+</dd>
+<dt><i>item</i> (str)</dt>
+<dd>
+item to be inserted
+</dd>
+</dl>
+<a NAME="TestingWidget.__insertTestFile" ID="TestingWidget.__insertTestFile"></a>
+<h4>TestingWidget.__insertTestFile</h4>
+<b>__insertTestFile</b>(<i>prog</i>)
+
+<p>
+        Private slot to insert a test file name into the testsuitePicker
+        object.
+</p>
+<dl>
+
+<dt><i>prog</i> (str)</dt>
+<dd>
+test file name to be inserted
+</dd>
+</dl>
+<a NAME="TestingWidget.__insertTestName" ID="TestingWidget.__insertTestName"></a>
+<h4>TestingWidget.__insertTestName</h4>
+<b>__insertTestName</b>(<i>testName</i>)
+
+<p>
+        Private slot to insert a test name into the testComboBox object.
+</p>
+<dl>
+
+<dt><i>testName</i> (str)</dt>
+<dd>
+name of the test to be inserted
+</dd>
+</dl>
+<a NAME="TestingWidget.__loadRecent" ID="TestingWidget.__loadRecent"></a>
+<h4>TestingWidget.__loadRecent</h4>
+<b>__loadRecent</b>(<i></i>)
+
+<p>
+        Private method to load the most recently used lists.
+</p>
+<a NAME="TestingWidget.__openEditor" ID="TestingWidget.__openEditor"></a>
+<h4>TestingWidget.__openEditor</h4>
+<b>__openEditor</b>(<i>filename, linenumber=1</i>)
+
+<p>
+        Private method to open an editor window for the given file.
+</p>
+<p>
+        Note: This method opens an editor window when the testing dialog
+        is called as a standalone application.
+</p>
+<dl>
+
+<dt><i>filename</i> (str)</dt>
+<dd>
+path of the file to be opened
+</dd>
+<dt><i>linenumber</i> (int (optional))</dt>
+<dd>
+line number to place the cursor at (defaults to 1)
+</dd>
+</dl>
+<a NAME="TestingWidget.__populateTestFrameworkComboBox" ID="TestingWidget.__populateTestFrameworkComboBox"></a>
+<h4>TestingWidget.__populateTestFrameworkComboBox</h4>
+<b>__populateTestFrameworkComboBox</b>(<i></i>)
+
+<p>
+        Private method to (re-)populate the test framework selector.
+</p>
+<a NAME="TestingWidget.__populateVenvComboBox" ID="TestingWidget.__populateVenvComboBox"></a>
+<h4>TestingWidget.__populateVenvComboBox</h4>
+<b>__populateVenvComboBox</b>(<i></i>)
+
+<p>
+        Private method to (re-)populate the virtual environments selector.
+</p>
+<a NAME="TestingWidget.__processTestResult" ID="TestingWidget.__processTestResult"></a>
+<h4>TestingWidget.__processTestResult</h4>
+<b>__processTestResult</b>(<i>result</i>)
+
+<p>
+        Private slot to handle the receipt of a test result object.
+</p>
+<dl>
+
+<dt><i>result</i> (TestResult)</dt>
+<dd>
+test result object
+</dd>
+</dl>
+<a NAME="TestingWidget.__projectClosed" ID="TestingWidget.__projectClosed"></a>
+<h4>TestingWidget.__projectClosed</h4>
+<b>__projectClosed</b>(<i></i>)
+
+<p>
+        Private slot to handle a project being closed.
+</p>
+<a NAME="TestingWidget.__projectOpened" ID="TestingWidget.__projectOpened"></a>
+<h4>TestingWidget.__projectOpened</h4>
+<b>__projectOpened</b>(<i></i>)
+
+<p>
+        Private slot to handle a project being opened.
+</p>
+<a NAME="TestingWidget.__resetResults" ID="TestingWidget.__resetResults"></a>
+<h4>TestingWidget.__resetResults</h4>
+<b>__resetResults</b>(<i></i>)
+
+<p>
+        Private slot to reset the test results tab and data.
+</p>
+<a NAME="TestingWidget.__saveRecent" ID="TestingWidget.__saveRecent"></a>
+<h4>TestingWidget.__saveRecent</h4>
+<b>__saveRecent</b>(<i></i>)
+
+<p>
+        Private method to save the most recently used lists.
+</p>
+<a NAME="TestingWidget.__setIdleMode" ID="TestingWidget.__setIdleMode"></a>
+<h4>TestingWidget.__setIdleMode</h4>
+<b>__setIdleMode</b>(<i></i>)
+
+<p>
+        Private slot to switch the widget to idle mode.
+</p>
+<a NAME="TestingWidget.__setRunningMode" ID="TestingWidget.__setRunningMode"></a>
+<h4>TestingWidget.__setRunningMode</h4>
+<b>__setRunningMode</b>(<i></i>)
+
+<p>
+        Private slot to switch the widget to running mode.
+</p>
+<a NAME="TestingWidget.__setStatusLabel" ID="TestingWidget.__setStatusLabel"></a>
+<h4>TestingWidget.__setStatusLabel</h4>
+<b>__setStatusLabel</b>(<i>statusText</i>)
+
+<p>
+        Private slot to set the status label to the text sent by the model.
+</p>
+<dl>
+
+<dt><i>statusText</i> (str)</dt>
+<dd>
+text to be shown
+</dd>
+</dl>
+<a NAME="TestingWidget.__setStoppedMode" ID="TestingWidget.__setStoppedMode"></a>
+<h4>TestingWidget.__setStoppedMode</h4>
+<b>__setStoppedMode</b>(<i></i>)
+
+<p>
+        Private slot to switch the widget to stopped mode.
+</p>
+<a NAME="TestingWidget.__showCoverageDialog" ID="TestingWidget.__showCoverageDialog"></a>
+<h4>TestingWidget.__showCoverageDialog</h4>
+<b>__showCoverageDialog</b>(<i></i>)
+
+<p>
+        Private slot to show a code coverage dialog for the most recent test
+        run.
+</p>
+<a NAME="TestingWidget.__showLogOutput" ID="TestingWidget.__showLogOutput"></a>
+<h4>TestingWidget.__showLogOutput</h4>
+<b>__showLogOutput</b>(<i></i>)
+
+<p>
+        Private slot to show the output of the most recent test run.
+</p>
+<a NAME="TestingWidget.__showSource" ID="TestingWidget.__showSource"></a>
+<h4>TestingWidget.__showSource</h4>
+<b>__showSource</b>(<i>filename, lineno</i>)
+
+<p>
+        Private slot to show the source of a traceback in an editor.
+</p>
+<dl>
+
+<dt><i>filename</i> (str)</dt>
+<dd>
+file name of the file to be shown
+</dd>
+<dt><i>lineno</i> (int)</dt>
+<dd>
+line number to go to in the file
+</dd>
+</dl>
+<a NAME="TestingWidget.__stopTests" ID="TestingWidget.__stopTests"></a>
+<h4>TestingWidget.__stopTests</h4>
+<b>__stopTests</b>(<i></i>)
+
+<p>
+        Private slot to stop the current test run.
+</p>
+<a NAME="TestingWidget.__testProcessFinished" ID="TestingWidget.__testProcessFinished"></a>
+<h4>TestingWidget.__testProcessFinished</h4>
+<b>__testProcessFinished</b>(<i>results, output</i>)
+
+<p>
+        Private slot to handle the 'testFinished' signal of the executor.
+</p>
+<dl>
+
+<dt><i>results</i> (list of TestResult)</dt>
+<dd>
+list of test result objects (if not sent via the
+            'testResult' signal
+</dd>
+<dt><i>output</i> (str)</dt>
+<dd>
+string containing the test process output (if any)
+</dd>
+</dl>
+<a NAME="TestingWidget.__testRunAboutToBeStarted" ID="TestingWidget.__testRunAboutToBeStarted"></a>
+<h4>TestingWidget.__testRunAboutToBeStarted</h4>
+<b>__testRunAboutToBeStarted</b>(<i></i>)
+
+<p>
+        Private slot to handle the 'testRunAboutToBeStarted' signal of the
+        executor.
+</p>
+<a NAME="TestingWidget.__testRunFinished" ID="TestingWidget.__testRunFinished"></a>
+<h4>TestingWidget.__testRunFinished</h4>
+<b>__testRunFinished</b>(<i>noTests, duration</i>)
+
+<p>
+        Private slot to handle the 'testRunFinished' signal of the executor.
+</p>
+<dl>
+
+<dt><i>noTests</i> (int)</dt>
+<dd>
+number of tests run by the executor
+</dd>
+<dt><i>duration</i> (float)</dt>
+<dd>
+time needed in seconds to run the tests
+</dd>
+</dl>
+<a NAME="TestingWidget.__testStarted" ID="TestingWidget.__testStarted"></a>
+<h4>TestingWidget.__testStarted</h4>
+<b>__testStarted</b>(<i>test</i>)
+
+<p>
+        Private slot handling the 'startTest' signal of the executor.
+</p>
+<dl>
+
+<dt><i>test</i> (tuple of (str, str, str))</dt>
+<dd>
+tuple containing the id, name and short description of the
+            tests about to be run
+</dd>
+</dl>
+<a NAME="TestingWidget.__testsCollectError" ID="TestingWidget.__testsCollectError"></a>
+<h4>TestingWidget.__testsCollectError</h4>
+<b>__testsCollectError</b>(<i>errors</i>)
+
+<p>
+        Private slot handling the 'collectError' signal of the executor.
+</p>
+<dl>
+
+<dt><i>errors</i> (list of tuple of (str, str))</dt>
+<dd>
+list of tuples containing the test name and a description
+            of the error
+</dd>
+</dl>
+<a NAME="TestingWidget.__testsCollected" ID="TestingWidget.__testsCollected"></a>
+<h4>TestingWidget.__testsCollected</h4>
+<b>__testsCollected</b>(<i>testNames</i>)
+
+<p>
+        Private slot handling the 'collected' signal of the executor.
+</p>
+<dl>
+
+<dt><i>testNames</i> (list of tuple of (str, str, str))</dt>
+<dd>
+list of tuples containing the test id, the test name
+            and a description of collected tests
+</dd>
+</dl>
+<a NAME="TestingWidget.__testsStopped" ID="TestingWidget.__testsStopped"></a>
+<h4>TestingWidget.__testsStopped</h4>
+<b>__testsStopped</b>(<i></i>)
+
+<p>
+        Private slot to handle the 'stop' signal of the executor.
+</p>
+<a NAME="TestingWidget.__updateButtonBoxButtons" ID="TestingWidget.__updateButtonBoxButtons"></a>
+<h4>TestingWidget.__updateButtonBoxButtons</h4>
+<b>__updateButtonBoxButtons</b>(<i></i>)
+
+<p>
+        Private slot to update the state of the buttons of the button box.
+</p>
+<a NAME="TestingWidget.__updateCoverage" ID="TestingWidget.__updateCoverage"></a>
+<h4>TestingWidget.__updateCoverage</h4>
+<b>__updateCoverage</b>(<i></i>)
+
+<p>
+        Private slot to update the state of the coverage checkbox depending on
+        the selected framework's capabilities.
+</p>
+<a NAME="TestingWidget.__updateProgress" ID="TestingWidget.__updateProgress"></a>
+<h4>TestingWidget.__updateProgress</h4>
+<b>__updateProgress</b>(<i></i>)
+
+<p>
+        Private slot update the progress indicators.
+</p>
+<a NAME="TestingWidget.clearRecent" ID="TestingWidget.clearRecent"></a>
+<h4>TestingWidget.clearRecent</h4>
+<b>clearRecent</b>(<i></i>)
+
+<p>
+        Public slot to clear the recently used lists.
+</p>
+<a NAME="TestingWidget.closeEvent" ID="TestingWidget.closeEvent"></a>
+<h4>TestingWidget.closeEvent</h4>
+<b>closeEvent</b>(<i>event</i>)
+
+<p>
+        Protected method to handle the close event.
+</p>
+<dl>
+
+<dt><i>event</i> (QCloseEvent)</dt>
+<dd>
+close event
+</dd>
+</dl>
+<a NAME="TestingWidget.getFailedTests" ID="TestingWidget.getFailedTests"></a>
+<h4>TestingWidget.getFailedTests</h4>
+<b>getFailedTests</b>(<i></i>)
+
+<p>
+        Public method to get the list of failed tests (if any).
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+list of IDs of failed tests
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+list of str
+</dd>
+</dl>
+<a NAME="TestingWidget.getResultsModel" ID="TestingWidget.getResultsModel"></a>
+<h4>TestingWidget.getResultsModel</h4>
+<b>getResultsModel</b>(<i></i>)
+
+<p>
+        Public method to get a reference to the model containing the test
+        result data.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+reference to the test results model
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+TestResultsModel
+</dd>
+</dl>
+<a NAME="TestingWidget.hasFailedTests" ID="TestingWidget.hasFailedTests"></a>
+<h4>TestingWidget.hasFailedTests</h4>
+<b>hasFailedTests</b>(<i></i>)
+
+<p>
+        Public method to check for failed tests.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+flag indicating the existence of failed tests
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+bool
+</dd>
+</dl>
+<a NAME="TestingWidget.on_buttonBox_clicked" ID="TestingWidget.on_buttonBox_clicked"></a>
+<h4>TestingWidget.on_buttonBox_clicked</h4>
+<b>on_buttonBox_clicked</b>(<i>button</i>)
+
+<p>
+        Private slot called by a button of the button box clicked.
+</p>
+<dl>
+
+<dt><i>button</i> (QAbstractButton)</dt>
+<dd>
+button that was clicked
+</dd>
+</dl>
+<a NAME="TestingWidget.on_discoverCheckBox_toggled" ID="TestingWidget.on_discoverCheckBox_toggled"></a>
+<h4>TestingWidget.on_discoverCheckBox_toggled</h4>
+<b>on_discoverCheckBox_toggled</b>(<i>checked</i>)
+
+<p>
+        Private slot handling state changes of the 'discover' checkbox.
+</p>
+<dl>
+
+<dt><i>checked</i> (bool)</dt>
+<dd>
+state of the checkbox
+</dd>
+</dl>
+<a NAME="TestingWidget.on_frameworkComboBox_currentIndexChanged" ID="TestingWidget.on_frameworkComboBox_currentIndexChanged"></a>
+<h4>TestingWidget.on_frameworkComboBox_currentIndexChanged</h4>
+<b>on_frameworkComboBox_currentIndexChanged</b>(<i>index</i>)
+
+<p>
+        Private slot handling the selection of a test framework.
+</p>
+<dl>
+
+<dt><i>index</i> (int)</dt>
+<dd>
+index of the selected framework
+</dd>
+</dl>
+<a NAME="TestingWidget.on_testsuitePicker_aboutToShowPathPickerDialog" ID="TestingWidget.on_testsuitePicker_aboutToShowPathPickerDialog"></a>
+<h4>TestingWidget.on_testsuitePicker_aboutToShowPathPickerDialog</h4>
+<b>on_testsuitePicker_aboutToShowPathPickerDialog</b>(<i></i>)
+
+<p>
+        Private slot called before the test file selection dialog is shown.
+</p>
+<a NAME="TestingWidget.on_venvComboBox_currentIndexChanged" ID="TestingWidget.on_venvComboBox_currentIndexChanged"></a>
+<h4>TestingWidget.on_venvComboBox_currentIndexChanged</h4>
+<b>on_venvComboBox_currentIndexChanged</b>(<i>index</i>)
+
+<p>
+        Private slot handling the selection of a virtual environment.
+</p>
+<dl>
+
+<dt><i>index</i> (int)</dt>
+<dd>
+index of the selected environment
+</dd>
+</dl>
+<a NAME="TestingWidget.on_versionsButton_clicked" ID="TestingWidget.on_versionsButton_clicked"></a>
+<h4>TestingWidget.on_versionsButton_clicked</h4>
+<b>on_versionsButton_clicked</b>(<i></i>)
+
+<p>
+        Private slot to show the versions of available plugins.
+</p>
+<a NAME="TestingWidget.setTestFile" ID="TestingWidget.setTestFile"></a>
+<h4>TestingWidget.setTestFile</h4>
+<b>setTestFile</b>(<i>testFile, forProject=False</i>)
+
+<p>
+        Public slot to set the given test file as the current one.
+</p>
+<dl>
+
+<dt><i>testFile</i> (str)</dt>
+<dd>
+path of the test file
+</dd>
+<dt><i>forProject</i> (bool (optional))</dt>
+<dd>
+flag indicating that this call is for a project
+            (defaults to False)
+</dd>
+</dl>
+<a NAME="TestingWidget.startTests" ID="TestingWidget.startTests"></a>
+<h4>TestingWidget.startTests</h4>
+<b>startTests</b>(<i>failedOnly=False</i>)
+
+<p>
+        Public slot to start the test run.
+</p>
+<dl>
+
+<dt><i>failedOnly</i> (bool)</dt>
+<dd>
+flag indicating to run only failed tests
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="TestingWidgetModes" ID="TestingWidgetModes"></a>
+<h2>TestingWidgetModes</h2>
+
+<p>
+    Class defining the various modes of the testing widget.
+</p>
+<h3>Derived from</h3>
+enum.Enum
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>IDLE</td></tr><tr><td>RUNNING</td></tr><tr><td>STOPPED</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="TestingWindow" ID="TestingWindow"></a>
+<h2>TestingWindow</h2>
+
+<p>
+    Main window class for the standalone dialog.
+</p>
+<h3>Derived from</h3>
+EricMainWindow
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#TestingWindow.__init__">TestingWindow</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#TestingWindow.eventFilter">eventFilter</a></td>
+<td>Public method to filter events.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="TestingWindow.__init__" ID="TestingWindow.__init__"></a>
+<h4>TestingWindow (Constructor)</h4>
+<b>TestingWindow</b>(<i>testfile=None, parent=None</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>testfile</i> (str)</dt>
+<dd>
+file name of the test script to open
+</dd>
+<dt><i>parent</i> (QWidget)</dt>
+<dd>
+reference to the parent widget
+</dd>
+</dl>
+<a NAME="TestingWindow.eventFilter" ID="TestingWindow.eventFilter"></a>
+<h4>TestingWindow.eventFilter</h4>
+<b>eventFilter</b>(<i>obj, event</i>)
+
+<p>
+        Public method to filter events.
+</p>
+<dl>
+
+<dt><i>obj</i></dt>
+<dd>
+reference to the object the event is meant for (QObject)
+</dd>
+<dt><i>event</i></dt>
+<dd>
+reference to the event object (QEvent)
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+flag indicating, whether the event was handled (boolean)
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="clearSavedHistories" ID="clearSavedHistories"></a>
+<h2>clearSavedHistories</h2>
+<b>clearSavedHistories</b>(<i>self</i>)
+
+<p>
+    Function to clear the saved history lists.
+</p>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/eric7.Testing.__init__.html	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.Testing.__init__</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.Testing.__init__</h1>
+
+<p>
+Package implementing testing functionality and interface to various test
+frameworks.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+
+<tr>
+<td><a href="#isLanguageSupported">isLanguageSupported</a></td>
+<td>Function to check, if the given programming language is supported by any testing framework.</td>
+</tr>
+<tr>
+<td><a href="#supportedLanguages">supportedLanguages</a></td>
+<td>Function to get a list of supported programming languages.</td>
+</tr>
+</table>
+<hr />
+<hr />
+<a NAME="isLanguageSupported" ID="isLanguageSupported"></a>
+<h2>isLanguageSupported</h2>
+<b>isLanguageSupported</b>(<i>language</i>)
+
+<p>
+    Function to check, if the given programming language is supported by any
+    testing framework.
+</p>
+<dl>
+
+<dt><i>language</i> (str)</dt>
+<dd>
+programming language
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+flag indicating support
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+bool
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="supportedLanguages" ID="supportedLanguages"></a>
+<h2>supportedLanguages</h2>
+<b>supportedLanguages</b>(<i></i>)
+
+<p>
+    Function to get a list of supported programming languages.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+list of supported programming languages
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+list of str
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/eric7.eric7_testing.html	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.eric7_testing</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.eric7_testing</h1>
+
+<p>
+eric testing.
+</p>
+<p>
+This is the main Python script that performs the necessary initialization
+of the testing module and starts the Qt event loop. This is a standalone
+version of the integrated testing module.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+
+<tr>
+<td><a href="#createMainWidget">createMainWidget</a></td>
+<td>Function to create the main widget.</td>
+</tr>
+<tr>
+<td><a href="#main">main</a></td>
+<td>Main entry point into the application.</td>
+</tr>
+</table>
+<hr />
+<hr />
+<a NAME="createMainWidget" ID="createMainWidget"></a>
+<h2>createMainWidget</h2>
+<b>createMainWidget</b>(<i>argv</i>)
+
+<p>
+    Function to create the main widget.
+</p>
+<dl>
+
+<dt><i>argv</i> (list of str)</dt>
+<dd>
+list of commandline parameters
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+reference to the main widget
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+QWidget
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="main" ID="main"></a>
+<h2>main</h2>
+<b>main</b>(<i></i>)
+
+<p>
+    Main entry point into the application.
+</p>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- a/eric7/Documentation/Source/eric7.eric7_unittest.html	Wed Jun 01 13:49:13 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-<!DOCTYPE html>
-<html><head>
-<title>eric7.eric7_unittest</title>
-<meta charset="UTF-8">
-<link rel="stylesheet" href="styles.css">
-</head>
-<body>
-<a NAME="top" ID="top"></a>
-<h1>eric7.eric7_unittest</h1>
-
-<p>
-eric Unittest.
-</p>
-<p>
-This is the main Python script that performs the necessary initialization
-of the unittest module and starts the Qt event loop. This is a standalone
-version of the integrated unittest module.
-</p>
-<h3>Global Attributes</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-<h3>Classes</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-<h3>Functions</h3>
-
-<table>
-
-<tr>
-<td><a href="#createMainWidget">createMainWidget</a></td>
-<td>Function to create the main widget.</td>
-</tr>
-<tr>
-<td><a href="#main">main</a></td>
-<td>Main entry point into the application.</td>
-</tr>
-</table>
-<hr />
-<hr />
-<a NAME="createMainWidget" ID="createMainWidget"></a>
-<h2>createMainWidget</h2>
-<b>createMainWidget</b>(<i>argv</i>)
-
-<p>
-    Function to create the main widget.
-</p>
-<dl>
-
-<dt><i>argv</i></dt>
-<dd>
-list of commandline parameters (list of strings)
-</dd>
-</dl>
-<dl>
-<dt>Return:</dt>
-<dd>
-reference to the main widget (QWidget)
-</dd>
-</dl>
-<div align="right"><a href="#top">Up</a></div>
-<hr />
-<hr />
-<a NAME="main" ID="main"></a>
-<h2>main</h2>
-<b>main</b>(<i></i>)
-
-<p>
-    Main entry point into the application.
-</p>
-<div align="right"><a href="#top">Up</a></div>
-<hr />
-</body></html>
\ No newline at end of file
--- a/eric7/Documentation/Source/index-eric7.PyUnit.html	Wed Jun 01 13:49:13 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-<!DOCTYPE html>
-<html><head>
-<title>eric7.PyUnit</title>
-<meta charset="UTF-8">
-<link rel="stylesheet" href="styles.css">
-</head>
-<body>
-<h1>eric7.PyUnit</h1>
-
-<p>
-Package implementing an interface to the pyunit unittest package.
-</p>
-<p>
-The package consist of a single dialog, which may be called as a
-standalone version using the eric7_unittest script or from within the eric
-IDE. If it is called from within eric, it has the additional function to
-open a source file that failed a test.
-</p>
-
-
-<h3>Modules</h3>
-<table>
-
-<tr>
-<td><a href="eric7.PyUnit.UnittestDialog.html">UnittestDialog</a></td>
-<td>Module implementing the UI to the pyunit package.</td>
-</tr>
-</table>
-</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/index-eric7.Testing.Interfaces.html	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.Testing.Interfaces</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<h1>eric7.Testing.Interfaces</h1>
+
+<p>
+Package containg the various test framework interfaces.
+</p>
+
+
+<h3>Modules</h3>
+<table>
+
+<tr>
+<td><a href="eric7.Testing.Interfaces.PytestExecutor.html">PytestExecutor</a></td>
+<td>Module implementing the executor for the 'pytest' framework.</td>
+</tr>
+<tr>
+<td><a href="eric7.Testing.Interfaces.PytestRunner.html">PytestRunner</a></td>
+<td>Module implementing the test runner script for the 'pytest' framework.</td>
+</tr>
+<tr>
+<td><a href="eric7.Testing.Interfaces.TestExecutorBase.html">TestExecutorBase</a></td>
+<td>Module implementing the executor base class for the various testing frameworks and supporting classes.</td>
+</tr>
+<tr>
+<td><a href="eric7.Testing.Interfaces.TestFrameworkRegistry.html">TestFrameworkRegistry</a></td>
+<td>Module implementing a simple registry containing the available test framework interfaces.</td>
+</tr>
+<tr>
+<td><a href="eric7.Testing.Interfaces.UnittestExecutor.html">UnittestExecutor</a></td>
+<td>Module implementing the executor for the standard 'unittest' framework.</td>
+</tr>
+<tr>
+<td><a href="eric7.Testing.Interfaces.UnittestRunner.html">UnittestRunner</a></td>
+<td>Module implementing the test runner script for the 'unittest' framework.</td>
+</tr>
+</table>
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/index-eric7.Testing.html	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.Testing</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<h1>eric7.Testing</h1>
+
+<p>
+Package implementing testing functionality and interface to various test
+frameworks.
+</p>
+
+<h3>Packages</h3>
+<table>
+
+<tr>
+<td><a href="index-eric7.Testing.Interfaces.html">Interfaces</a></td>
+<td>Package containg the various test framework interfaces.</td>
+</tr>
+</table>
+
+<h3>Modules</h3>
+<table>
+
+<tr>
+<td><a href="eric7.Testing.TestResultsTree.html">TestResultsTree</a></td>
+<td>Module implementing a tree view and associated model to show the test result data.</td>
+</tr>
+<tr>
+<td><a href="eric7.Testing.TestingWidget.html">TestingWidget</a></td>
+<td>Module implementing a widget to orchestrate unit test execution.</td>
+</tr>
+<tr>
+<td><a href="eric7.Testing.__init__.html">Testing</a></td>
+<td>Package implementing testing functionality and interface to various test frameworks.</td>
+</tr>
+</table>
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/EricNetwork/EricJsonStreamReader.py	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,141 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a JSON based reader class.
+"""
+
+import json
+
+from PyQt6.QtCore import pyqtSignal, pyqtSlot
+from PyQt6.QtNetwork import QTcpServer, QHostAddress
+
+from EricWidgets import EricMessageBox
+
+import Preferences
+import Utilities
+
+
+class EricJsonReader(QTcpServer):
+    """
+    Class implementing a JSON based reader class.
+    
+    The reader is responsible for opening a socket to listen for writer
+    connections.
+    
+    @signal dataReceived(object) emitted after a data object was received
+    """
+    dataReceived = pyqtSignal(object)
+    
+    def __init__(self, name="", ip=None, parent=None):
+        """
+        Constructor
+        
+        @param name name of the server (used for output only)
+        @type str
+        @param ip IP address to listen at
+        @type str
+        @param parent parent object
+        @type QObject
+        """
+        super().__init__(parent)
+        
+        self.__name = name
+        self.__connection = None
+        
+        # setup the network interface
+        if ip is None:
+            networkInterface = Preferences.getDebugger("NetworkInterface")
+            if networkInterface == "all" or '.' in networkInterface:
+                # IPv4
+                self.__hostAddress = '127.0.0.1'
+            else:
+                # IPv6
+                self.__hostAddress = '::1'
+        else:
+            self.__hostAddress = ip
+        self.listen(QHostAddress(self.__hostAddress))
+
+        self.newConnection.connect(self.handleNewConnection)
+        
+        ## Note: Need the port if writer is started external in debugger.
+        print('JSON reader ({1}) listening on: {0:d}'   # __IGNORE_WARNING__
+              .format(self.serverPort(), self.__name))
+    
+    def address(self):
+        """
+        Public method to get the host address.
+        
+        @return host address
+        @rtype str
+        """
+        return self.__hostAddress
+    
+    def port(self):
+        """
+        Public method to get the port number to connect to.
+        
+        @return port number
+        @rtype int
+        """
+        return self.serverPort()
+    
+    @pyqtSlot()
+    def handleNewConnection(self):
+        """
+        Public slot for new incoming connections from a writer.
+        """
+        connection = self.nextPendingConnection()
+        if not connection.isValid():
+            return
+        
+        if self.__connection is not None:
+            self.__connection.close()
+        
+        self.__connection = connection
+        
+        connection.readyRead.connect(self.__receiveJson)
+        connection.disconnected.connect(self.__handleDisconnect)
+    
+    @pyqtSlot()
+    def __handleDisconnect(self):
+        """
+        Private slot handling a disconnect of the writer.
+        """
+        if self.__connection is not None:
+            self.__receiveJson()    # read all buffered data first
+            self.__connection.close()
+        
+        self.__connection = None
+    
+    @pyqtSlot()
+    def __receiveJson(self):
+        """
+        Private slot handling received data from the writer.
+        """
+        while self.__connection and self.__connection.canReadLine():
+            dataStr = self.__connection.readLine()
+            jsonLine = bytes(dataStr).decode("utf-8", 'backslashreplace')
+            
+            #- print("JSON Reader ({0}): {1}".format(self.__name, jsonLine))
+            #- this is for debugging only
+            
+            try:
+                data = json.loads(jsonLine.strip())
+            except (TypeError, ValueError) as err:
+                EricMessageBox.critical(
+                    None,
+                    self.tr("JSON Protocol Error"),
+                    self.tr("""<p>The data received from the writer"""
+                            """ could not be decoded. Please report"""
+                            """ this issue with the received data to the"""
+                            """ eric bugs email address.</p>"""
+                            """<p>Error: {0}</p>"""
+                            """<p>Data:<br/>{1}</p>""").format(
+                        str(err), Utilities.html_encode(jsonLine.strip())),
+                    EricMessageBox.Ok)
+                return
+            
+            self.dataReceived.emit(data)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/EricNetwork/EricJsonStreamWriter.py	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a JSON based writer class.
+"""
+
+import json
+import socket
+
+
+class EricJsonWriter:
+    """
+    Class implementing a JSON based writer class.
+    """
+    def __init__(self, host, port):
+        """
+        Constructor
+        
+        @param host IP address the reader is listening on
+        @type str
+        @param port port the reader is listening on
+        @type int
+        """
+        self.__connection = socket.create_connection((host, port))
+    
+    def write(self, data):
+        """
+        Public method to send JSON serializable data.
+        
+        @param data JSON serializable object to be sent
+        @type object
+        """
+        dataStr = json.dumps(data) + '\n'
+        self.__connection.sendall(dataStr.encode('utf8', 'backslashreplace'))
+    
+    def close(self):
+        """
+        Public method to close the stream.
+        """
+        self.__connection.close()
--- a/eric7/PyUnit/UnittestDialog.py	Wed Jun 01 13:49:13 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1506 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
-#
-
-"""
-Module implementing the UI to the pyunit package.
-"""
-
-import unittest
-import sys
-import time
-import re
-import os
-import contextlib
-
-from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent, QFileInfo
-from PyQt6.QtGui import QColor
-from PyQt6.QtWidgets import (
-    QWidget, QDialog, QApplication, QDialogButtonBox, QListWidgetItem,
-    QComboBox, QTreeWidgetItem
-)
-
-from EricWidgets.EricApplication import ericApp
-from EricWidgets import EricMessageBox
-from EricWidgets.EricMainWindow import EricMainWindow
-from EricWidgets.EricPathPicker import EricPathPickerModes
-
-from .Ui_UnittestDialog import Ui_UnittestDialog
-
-import UI.PixmapCache
-
-import Preferences
-
-from Globals import (
-    recentNameUnittestDiscoverHistory, recentNameUnittestFileHistory,
-    recentNameUnittestTestnameHistory
-)
-
-
-class UnittestDialog(QWidget, Ui_UnittestDialog):
-    """
-    Class implementing the UI to the pyunit package.
-    
-    @signal unittestFile(str, int, bool) emitted to show the source of a
-        unittest file
-    @signal unittestStopped() emitted after a unit test was run
-    """
-    unittestFile = pyqtSignal(str, int, bool)
-    unittestStopped = pyqtSignal()
-    
-    TestCaseNameRole = Qt.ItemDataRole.UserRole
-    TestCaseFileRole = Qt.ItemDataRole.UserRole + 1
-    
-    ErrorsInfoRole = Qt.ItemDataRole.UserRole
-    
-    SkippedColorDarkTheme = QColor("#00aaff")
-    FailedExpectedColorDarkTheme = QColor("#ccaaff")
-    SucceededUnexpectedColorDarkTheme = QColor("#ff99dd")
-    SkippedColorLightTheme = QColor("#0000ff")
-    FailedExpectedColorLightTheme = QColor("#7700bb")
-    SucceededUnexpectedColorLightTheme = QColor("#ff0000")
-    
-    def __init__(self, prog=None, dbs=None, ui=None, parent=None, name=None):
-        """
-        Constructor
-        
-        @param prog filename of the program to open
-        @type str
-        @param dbs reference to the debug server object. It is an indication
-            whether we were called from within the eric IDE.
-        @type DebugServer
-        @param ui reference to the UI object
-        @type UserInterface
-        @param parent parent widget of this dialog
-        @type QWidget
-        @param name name of this dialog
-        @type str
-        """
-        super().__init__(parent)
-        if name:
-            self.setObjectName(name)
-        self.setupUi(self)
-        
-        self.clearHistoriesButton.setIcon(
-            UI.PixmapCache.getIcon("clearPrivateData"))
-        
-        self.testsuitePicker.setMode(EricPathPickerModes.OPEN_FILE_MODE)
-        self.testsuitePicker.setInsertPolicy(
-            QComboBox.InsertPolicy.InsertAtTop)
-        self.testsuitePicker.setSizeAdjustPolicy(
-            QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon)
-        
-        self.discoveryPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
-        self.discoveryPicker.setInsertPolicy(
-            QComboBox.InsertPolicy.InsertAtTop)
-        self.discoveryPicker.setSizeAdjustPolicy(
-            QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon)
-        
-        self.testComboBox.lineEdit().setClearButtonEnabled(True)
-        
-        self.discoverButton = self.buttonBox.addButton(
-            self.tr("Discover"), QDialogButtonBox.ButtonRole.ActionRole)
-        self.discoverButton.setToolTip(self.tr(
-            "Discover tests"))
-        self.discoverButton.setWhatsThis(self.tr(
-            """<b>Discover</b>"""
-            """<p>This button starts a discovery of available tests.</p>"""))
-        self.startButton = self.buttonBox.addButton(
-            self.tr("Start"), QDialogButtonBox.ButtonRole.ActionRole)
-        
-        self.startButton.setToolTip(self.tr(
-            "Start the selected testsuite"))
-        self.startButton.setWhatsThis(self.tr(
-            """<b>Start Test</b>"""
-            """<p>This button starts the selected testsuite.</p>"""))
-        
-        self.startFailedButton = self.buttonBox.addButton(
-            self.tr("Rerun Failed"), QDialogButtonBox.ButtonRole.ActionRole)
-        self.startFailedButton.setToolTip(
-            self.tr("Reruns failed tests of the selected testsuite"))
-        self.startFailedButton.setWhatsThis(self.tr(
-            """<b>Rerun Failed</b>"""
-            """<p>This button reruns all failed tests of the selected"""
-            """ testsuite.</p>"""))
-        
-        self.stopButton = self.buttonBox.addButton(
-            self.tr("Stop"), QDialogButtonBox.ButtonRole.ActionRole)
-        self.stopButton.setToolTip(self.tr("Stop the running unittest"))
-        self.stopButton.setWhatsThis(self.tr(
-            """<b>Stop Test</b>"""
-            """<p>This button stops a running unittest.</p>"""))
-        
-        self.discoverButton.setEnabled(False)
-        self.stopButton.setEnabled(False)
-        self.startButton.setDefault(True)
-        self.startFailedButton.setEnabled(False)
-        
-        self.__dbs = dbs
-        self.__forProject = False
-        
-        self.setWindowFlags(
-            self.windowFlags() |
-            Qt.WindowType.WindowContextHelpButtonHint
-        )
-        self.setWindowIcon(UI.PixmapCache.getIcon("eric"))
-        self.setWindowTitle(self.tr("Unittest"))
-        if dbs:
-            self.ui = ui
-            
-            self.debuggerCheckBox.setChecked(True)
-            
-            # virtual environment manager is only used in the integrated
-            # variant
-            self.__venvManager = ericApp().getObject("VirtualEnvManager")
-            self.__populateVenvComboBox()
-            self.__venvManager.virtualEnvironmentAdded.connect(
-                self.__populateVenvComboBox)
-            self.__venvManager.virtualEnvironmentRemoved.connect(
-                self.__populateVenvComboBox)
-            self.__venvManager.virtualEnvironmentChanged.connect(
-                self.__populateVenvComboBox)
-        else:
-            self.__venvManager = None
-            self.debuggerCheckBox.setVisible(False)
-        self.venvComboBox.setVisible(bool(self.__venvManager))
-        self.venvLabel.setVisible(bool(self.__venvManager))
-        
-        self.__setProgressColor("green")
-        self.progressLed.setDarkFactor(150)
-        self.progressLed.off()
-        
-        self.discoverHistory = []
-        self.fileHistory = []
-        self.testNameHistory = []
-        self.running = False
-        self.savedModulelist = None
-        self.savedSysPath = sys.path
-        self.savedCwd = os.getcwd()
-        
-        self.rxPatterns = [
-            self.tr("^Failure: "),
-            self.tr("^Error: "),
-            # These are for untranslated/partially translated situations
-            "^Failure: ",
-            "^Error: ",
-        ]
-        
-        self.__failedTests = set()
-        
-        # now connect the debug server signals if called from the eric IDE
-        if self.__dbs:
-            self.__dbs.utDiscovered.connect(self.__UTDiscovered)
-            self.__dbs.utPrepared.connect(self.__UTPrepared)
-            self.__dbs.utFinished.connect(self.__setStoppedMode)
-            self.__dbs.utStartTest.connect(self.testStarted)
-            self.__dbs.utStopTest.connect(self.testFinished)
-            self.__dbs.utTestFailed.connect(self.testFailed)
-            self.__dbs.utTestErrored.connect(self.testErrored)
-            self.__dbs.utTestSkipped.connect(self.testSkipped)
-            self.__dbs.utTestFailedExpected.connect(self.testFailedExpected)
-            self.__dbs.utTestSucceededUnexpected.connect(
-                self.testSucceededUnexpected)
-        
-        self.__editors = []
-        
-        self.__loadRecent()
-        
-        self.insertProg(prog)
-        self.insertTestName("")
-        
-        self.clearHistoriesButton.clicked.connect(self.clearRecent)
-    
-    def keyPressEvent(self, evt):
-        """
-        Protected slot to handle key press events.
-        
-        @param evt key press event to handle (QKeyEvent)
-        """
-        if evt.key() == Qt.Key.Key_Escape and self.__dbs:
-            self.close()
-    
-    def __populateVenvComboBox(self):
-        """
-        Private method to (re-)populate the virtual environments selector.
-        """
-        currentText = self.venvComboBox.currentText()
-        self.venvComboBox.clear()
-        self.venvComboBox.addItem("")
-        self.venvComboBox.addItems(
-            sorted(self.__venvManager.getVirtualenvNames()))
-        index = self.venvComboBox.findText(currentText)
-        if index < 0:
-            index = 0
-        self.venvComboBox.setCurrentIndex(index)
-    
-    def __setProgressColor(self, color):
-        """
-        Private method to set the color of the progress color label.
-        
-        @param color colour to be shown (string)
-        """
-        self.progressLed.setColor(QColor(color))
-    
-    def setProjectMode(self, forProject):
-        """
-        Public method to set the project mode of the dialog.
-        
-        @param forProject flag indicating to run for the open project
-        @type bool
-        """
-        self.__forProject = forProject
-        if forProject:
-            project = ericApp().getObject("Project")
-            if project.isOpen():
-                self.insertDiscovery(project.getProjectPath())
-            else:
-                self.insertDiscovery("")
-        else:
-            self.insertDiscovery("")
-        
-        self.discoveryList.clear()
-        self.tabWidget.setCurrentIndex(0)
-    
-    def insertDiscovery(self, start):
-        """
-        Public slot to insert the discovery start directory into the
-        discoveryPicker object.
-        
-        @param start start directory name to be inserted
-        @type str
-        """
-        current = self.discoveryPicker.currentText()
-        
-        # prepend the given directory to the discovery picker
-        if start is None:
-            start = ""
-        if start in self.discoverHistory:
-            self.discoverHistory.remove(start)
-        self.discoverHistory.insert(0, start)
-        self.discoveryPicker.clear()
-        self.discoveryPicker.addItems(self.discoverHistory)
-        
-        if current:
-            self.discoveryPicker.setText(current)
-    
-    def insertProg(self, prog):
-        """
-        Public slot to insert the filename prog into the testsuitePicker
-        object.
-        
-        @param prog filename to be inserted (string)
-        """
-        current = self.testsuitePicker.currentText()
-        
-        # prepend the selected file to the testsuite picker
-        if prog is None:
-            prog = ""
-        if prog in self.fileHistory:
-            self.fileHistory.remove(prog)
-        self.fileHistory.insert(0, prog)
-        self.testsuitePicker.clear()
-        self.testsuitePicker.addItems(self.fileHistory)
-        
-        if current:
-            self.testsuitePicker.setText(current)
-    
-    def insertTestName(self, testName):
-        """
-        Public slot to insert a test name into the testComboBox object.
-        
-        @param testName name of the test to be inserted (string)
-        """
-        current = self.testComboBox.currentText()
-        
-        # prepend the selected file to the testsuite combobox
-        if testName is None:
-            testName = ""
-        if testName in self.testNameHistory:
-            self.testNameHistory.remove(testName)
-        self.testNameHistory.insert(0, testName)
-        self.testComboBox.clear()
-        self.testComboBox.addItems(self.testNameHistory)
-        
-        if current:
-            self.testComboBox.setCurrentText(current)
-    
-    @pyqtSlot()
-    def on_testsuitePicker_aboutToShowPathPickerDialog(self):
-        """
-        Private slot called before the test suite selection dialog is shown.
-        """
-        if self.__dbs:
-            py3Extensions = ' '.join(
-                ["*{0}".format(ext)
-                 for ext in self.__dbs.getExtensions('Python3')]
-            )
-            fileFilter = self.tr(
-                "Python3 Files ({0});;All Files (*)"
-            ).format(py3Extensions)
-        else:
-            fileFilter = self.tr("Python Files (*.py);;All Files (*)")
-        self.testsuitePicker.setFilters(fileFilter)
-        
-        defaultDirectory = Preferences.getMultiProject("Workspace")
-        if not defaultDirectory:
-            defaultDirectory = os.path.expanduser("~")
-        if self.__dbs:
-            project = ericApp().getObject("Project")
-            if self.__forProject and project.isOpen():
-                defaultDirectory = project.getProjectPath()
-        self.testsuitePicker.setDefaultDirectory(defaultDirectory)
-    
-    @pyqtSlot(str)
-    def on_testsuitePicker_pathSelected(self, suite):
-        """
-        Private slot called after a test suite has been selected.
-        
-        @param suite file name of the test suite
-        @type str
-        """
-        self.insertProg(suite)
-    
-    @pyqtSlot(str)
-    def on_testsuitePicker_editTextChanged(self, path):
-        """
-        Private slot handling changes of the test suite path.
-        
-        @param path path of the test suite file
-        @type str
-        """
-        self.startFailedButton.setEnabled(False)
-    
-    @pyqtSlot(bool)
-    def on_discoverCheckBox_toggled(self, checked):
-        """
-        Private slot handling state changes of the 'discover' checkbox.
-        
-        @param checked state of the checkbox
-        @type bool
-        """
-        self.discoverButton.setEnabled(checked)
-        self.discoveryList.clear()
-        
-        if not bool(self.discoveryPicker.currentText()):
-            if self.__forProject:
-                project = ericApp().getObject("Project")
-                if project.isOpen():
-                    self.insertDiscovery(project.getProjectPath())
-                    return
-            
-            self.insertDiscovery(Preferences.getMultiProject("Workspace"))
-    
-    def on_buttonBox_clicked(self, button):
-        """
-        Private slot called by a button of the button box clicked.
-        
-        @param button button that was clicked (QAbstractButton)
-        """
-        if button == self.discoverButton:
-            self.__discover()
-            self.__saveRecent()
-        elif button == self.startButton:
-            self.startTests()
-            self.__saveRecent()
-        elif button == self.stopButton:
-            self.__stopTests()
-        elif button == self.startFailedButton:
-            self.startTests(failedOnly=True)
-    
-    def __loadRecent(self):
-        """
-        Private method to load the most recently used lists.
-        """
-        Preferences.Prefs.rsettings.sync()
-        
-        # 1. discovery history
-        self.discoverHistory = []
-        rs = Preferences.Prefs.rsettings.value(
-            recentNameUnittestDiscoverHistory)
-        if rs is not None:
-            recent = [f
-                      for f in Preferences.toList(rs)
-                      if QFileInfo(f).exists()]
-            self.discoverHistory = recent[
-                :Preferences.getDebugger("RecentNumber")]
-        
-        # 2. test file history
-        self.fileHistory = []
-        rs = Preferences.Prefs.rsettings.value(
-            recentNameUnittestFileHistory)
-        if rs is not None:
-            recent = [f
-                      for f in Preferences.toList(rs)
-                      if QFileInfo(f).exists()]
-            self.fileHistory = recent[
-                :Preferences.getDebugger("RecentNumber")]
-        
-        # 3. test name history
-        self.testNameHistory = []
-        rs = Preferences.Prefs.rsettings.value(
-            recentNameUnittestTestnameHistory)
-        if rs is not None:
-            recent = [n for n in Preferences.toList(rs) if n]
-            self.testNameHistory = recent[
-                :Preferences.getDebugger("RecentNumber")]
-    
-    def __saveRecent(self):
-        """
-        Private method to save the most recently used lists.
-        """
-        Preferences.Prefs.rsettings.setValue(
-            recentNameUnittestDiscoverHistory, self.discoverHistory)
-        Preferences.Prefs.rsettings.setValue(
-            recentNameUnittestFileHistory, self.fileHistory)
-        Preferences.Prefs.rsettings.setValue(
-            recentNameUnittestTestnameHistory, self.testNameHistory)
-        
-        Preferences.Prefs.rsettings.sync()
-    
-    @pyqtSlot()
-    def clearRecent(self):
-        """
-        Public slot to clear the recently used lists.
-        """
-        # clear histories
-        self.discoverHistory = []
-        self.fileHistory = []
-        self.testNameHistory = []
-        
-        # clear widgets with histories
-        self.discoveryPicker.clear()
-        self.testsuitePicker.clear()
-        self.testComboBox.clear()
-        
-        # sync histories
-        self.__saveRecent()
-    
-    @pyqtSlot()
-    def __discover(self):
-        """
-        Private slot to discover unit test but don't run them.
-        """
-        if self.running:
-            return
-        
-        self.discoveryList.clear()
-        
-        discoveryStart = self.discoveryPicker.currentText()
-        self.insertDiscovery(discoveryStart)
-        self.sbLabel.setText(self.tr("Discovering Tests"))
-        QApplication.processEvents()
-        
-        self.testName = self.tr("Unittest with auto-discovery")
-        if self.__dbs:
-            venvName = self.venvComboBox.currentText()
-            
-            # we are cooperating with the eric IDE
-            project = ericApp().getObject("Project")
-            if self.__forProject:
-                mainScript = project.getMainScript(True)
-                clientType = project.getProjectLanguage()
-                if mainScript:
-                    workdir = os.path.dirname(os.path.abspath(mainScript))
-                else:
-                    workdir = project.getProjectPath()
-                sysPath = [workdir]
-                if not discoveryStart:
-                    discoveryStart = workdir
-            else:
-                if not discoveryStart:
-                    EricMessageBox.critical(
-                        self,
-                        self.tr("Unittest"),
-                        self.tr("You must enter a start directory for"
-                                " auto-discovery."))
-                    return
-                
-                workdir = ""
-                clientType = "Python3"
-                sysPath = []
-            self.__dbs.remoteUTDiscover(clientType, self.__forProject,
-                                        venvName, sysPath, workdir,
-                                        discoveryStart)
-        else:
-            # we are running as an application
-            if not discoveryStart:
-                EricMessageBox.critical(
-                    self,
-                    self.tr("Unittest"),
-                    self.tr("You must enter a start directory for"
-                            " auto-discovery."))
-                return
-            
-            if discoveryStart:
-                sys.path = (
-                    [os.path.abspath(discoveryStart)] +
-                    self.savedSysPath
-                )
-            
-            # clean up list of imported modules to force a reimport upon
-            # running the test
-            if self.savedModulelist:
-                for modname in list(sys.modules.keys()):
-                    if modname not in self.savedModulelist:
-                        # delete it
-                        del(sys.modules[modname])
-            self.savedModulelist = sys.modules.copy()
-            
-            # now try to discover the testsuite
-            os.chdir(discoveryStart)
-            try:
-                testLoader = unittest.TestLoader()
-                test = testLoader.discover(discoveryStart)
-                if hasattr(testLoader, "errors") and bool(testLoader.errors):
-                    EricMessageBox.critical(
-                        self,
-                        self.tr("Unittest"),
-                        self.tr(
-                            "<p>Unable to discover tests.</p>"
-                            "<p>{0}</p>"
-                        ).format("<br/>".join(testLoader.errors)
-                                 .replace("\n", "<br/>"))
-                    )
-                    self.sbLabel.clear()
-                else:
-                    testsList = self.__assembleTestCasesList(
-                        test, discoveryStart)
-                    self.__populateDiscoveryResults(testsList)
-                    self.sbLabel.setText(
-                        self.tr("Discovered %n Test(s)", "",
-                                len(testsList)))
-                    self.tabWidget.setCurrentIndex(0)
-            except Exception:
-                exc_type, exc_value, exc_tb = sys.exc_info()
-                EricMessageBox.critical(
-                    self,
-                    self.tr("Unittest"),
-                    self.tr(
-                        "<p>Unable to discover tests.</p>"
-                        "<p>{0}<br/>{1}</p>")
-                    .format(str(exc_type),
-                            str(exc_value).replace("\n", "<br/>"))
-                )
-                self.sbLabel.clear()
-            
-            sys.path = self.savedSysPath
-    
-    def __assembleTestCasesList(self, suite, start):
-        """
-        Private method to assemble a list of test cases included in a test
-        suite.
-        
-        @param suite test suite to be inspected
-        @type unittest.TestSuite
-        @param start name of directory discovery was started at
-        @type str
-        @return list of tuples containing the test case ID, a short description
-            and the path of the test file name
-        @rtype list of tuples of (str, str, str)
-        """
-        testCases = []
-        for test in suite:
-            if isinstance(test, unittest.TestSuite):
-                testCases.extend(self.__assembleTestCasesList(test, start))
-            else:
-                testId = test.id()
-                if (
-                    "ModuleImportFailure" not in testId and
-                    "LoadTestsFailure" not in testId and
-                    "_FailedTest" not in testId
-                ):
-                    filename = os.path.join(
-                        start,
-                        test.__module__.replace(".", os.sep) + ".py")
-                    testCases.append(
-                        (test.id(), test.shortDescription(), filename)
-                    )
-        return testCases
-    
-    def __findDiscoveryItem(self, modulePath):
-        """
-        Private method to find an item given the module path.
-        
-        @param modulePath path of the module in dotted notation
-        @type str
-        @return reference to the item or None
-        @rtype QTreeWidgetItem or None
-        """
-        itm = self.discoveryList.topLevelItem(0)
-        while itm is not None:
-            if itm.data(0, UnittestDialog.TestCaseNameRole) == modulePath:
-                return itm
-            itm = self.discoveryList.itemBelow(itm)
-        
-        return None
-    
-    def __populateDiscoveryResults(self, tests):
-        """
-        Private method to populate the test discovery results list.
-        
-        @param tests list of tuples containing the discovery results
-        @type list of tuples of (str, str, str)
-        """
-        for test, _testDescription, filename in tests:
-            testPath = test.split(".")
-            pitm = None
-            for index in range(1, len(testPath) + 1):
-                modulePath = ".".join(testPath[:index])
-                itm = self.__findDiscoveryItem(modulePath)
-                if itm is not None:
-                    pitm = itm
-                else:
-                    if pitm is None:
-                        itm = QTreeWidgetItem(self.discoveryList,
-                                              [testPath[index - 1]])
-                    else:
-                        itm = QTreeWidgetItem(pitm,
-                                              [testPath[index - 1]])
-                        pitm.setExpanded(True)
-                    itm.setFlags(Qt.ItemFlag.ItemIsUserCheckable |
-                                 Qt.ItemFlag.ItemIsEnabled)
-                    itm.setCheckState(0, Qt.CheckState.Unchecked)
-                    itm.setData(0, UnittestDialog.TestCaseNameRole, modulePath)
-                    if (
-                        os.path.splitext(os.path.basename(filename))[0] ==
-                        itm.text(0)
-                    ):
-                        itm.setData(0, UnittestDialog.TestCaseFileRole,
-                                    filename)
-                    elif pitm:
-                        fn = pitm.data(0, UnittestDialog.TestCaseFileRole)
-                        if fn:
-                            itm.setData(0, UnittestDialog.TestCaseFileRole, fn)
-                    pitm = itm
-    
-    def __selectedTestCases(self, parent=None):
-        """
-        Private method to assemble the list of selected test cases and suites.
-        
-        @param parent reference to the parent item
-        @type QTreeWidgetItem
-        @return list of selected test cases
-        @rtype list of str
-        """
-        selectedTests = []
-        itemsList = [
-            self.discoveryList.topLevelItem(index)
-            for index in range(self.discoveryList.topLevelItemCount())
-        ] if parent is None else [
-            parent.child(index)
-            for index in range(parent.childCount())
-        ]
-        
-        for itm in itemsList:
-            if (
-                itm.checkState(0) == Qt.CheckState.Checked and
-                itm.childCount() == 0
-            ):
-                selectedTests.append(
-                    itm.data(0, UnittestDialog.TestCaseNameRole))
-            if itm.childCount():
-                # recursively check children
-                selectedTests.extend(self.__selectedTestCases(itm))
-        
-        return selectedTests
-    
-    def __UTDiscovered(self, testCases, exc_type, exc_value):
-        """
-        Private slot to handle the utDiscovered signal.
-        
-        If the unittest suite was loaded successfully, we ask the
-        client to run the test suite.
-        
-        @param testCases list of detected test cases
-        @type str
-        @param exc_type exception type occured during discovery
-        @type str
-        @param exc_value value of exception occured during discovery
-        @type str
-        """
-        if testCases:
-            self.__populateDiscoveryResults(testCases)
-            self.sbLabel.setText(
-                self.tr("Discovered %n Test(s)", "",
-                        len(testCases)))
-            self.tabWidget.setCurrentIndex(0)
-        else:
-            EricMessageBox.critical(
-                self,
-                self.tr("Unittest"),
-                self.tr("<p>Unable to discover tests.</p>"
-                        "<p>{0}<br/>{1}</p>")
-                .format(exc_type, exc_value.replace("\n", "<br/>"))
-            )
-    
-    @pyqtSlot(QTreeWidgetItem, int)
-    def on_discoveryList_itemChanged(self, item, column):
-        """
-        Private slot handling the user checking or unchecking an item.
-
-        @param item reference to the item
-        @type QTreeWidgetItem
-        @param column changed column
-        @type int
-        """
-        if column == 0:
-            for index in range(item.childCount()):
-                item.child(index).setCheckState(0, item.checkState(0))
-
-    @pyqtSlot(QTreeWidgetItem, int)
-    def on_discoveryList_itemDoubleClicked(self, item, column):
-        """
-        Private slot handling the user double clicking an item.
-        
-        @param item reference to the item
-        @type QTreeWidgetItem
-        @param column column of the double click
-        @type int
-        """
-        if item:
-            filename = item.data(0, UnittestDialog.TestCaseFileRole)
-            if filename:
-                if self.__dbs:
-                    # running as part of eric IDE
-                    self.unittestFile.emit(filename, 1, False)
-                else:
-                    self.__openEditor(filename, 1)
-    
-    @pyqtSlot()
-    def startTests(self, failedOnly=False):
-        """
-        Public slot to start the test.
-        
-        @param failedOnly flag indicating to run only failed tests (boolean)
-        """
-        if self.running:
-            return
-        
-        discover = self.discoverCheckBox.isChecked()
-        if discover:
-            discoveryStart = self.discoveryPicker.currentText()
-            testFileName = ""
-            testName = ""
-            
-            if discoveryStart:
-                self.insertDiscovery(discoveryStart)
-        else:
-            discoveryStart = ""
-            testFileName = self.testsuitePicker.currentText()
-            testName = self.testComboBox.currentText()
-            if testName:
-                self.insertTestName(testName)
-            if testFileName and not testName:
-                testName = "suite"
-        
-        if not discover and not testFileName and not testName:
-            EricMessageBox.critical(
-                self,
-                self.tr("Unittest"),
-                self.tr("You must select auto-discovery or enter a test suite"
-                        " file or a dotted test name."))
-            return
-        
-        # prepend the selected file to the testsuite combobox
-        self.insertProg(testFileName)
-        self.sbLabel.setText(self.tr("Preparing Testsuite"))
-        QApplication.processEvents()
-        
-        if discover:
-            self.testName = self.tr("Unittest with auto-discovery")
-        else:
-            # build the module name from the filename without extension
-            if testFileName:
-                self.testName = os.path.splitext(
-                    os.path.basename(testFileName))[0]
-            elif testName:
-                self.testName = testName
-            else:
-                self.testName = self.tr("<Unnamed Test>")
-        
-        if failedOnly:
-            testCases = []
-        else:
-            testCases = self.__selectedTestCases()
-        
-            if not testCases and self.discoveryList.topLevelItemCount():
-                ok = EricMessageBox.yesNo(
-                    self,
-                    self.tr("Unittest"),
-                    self.tr("""No test case has been selected. Shall all"""
-                            """ test cases be run?"""))
-                if not ok:
-                    return
-        
-        if self.__dbs:
-            venvName = self.venvComboBox.currentText()
-            
-            # we are cooperating with the eric IDE
-            project = ericApp().getObject("Project")
-            if self.__forProject:
-                mainScript = project.getMainScript(True)
-                clientType = project.getProjectLanguage()
-                if mainScript:
-                    workdir = os.path.dirname(os.path.abspath(mainScript))
-                    coverageFile = os.path.splitext(mainScript)[0]
-                else:
-                    workdir = project.getProjectPath()
-                    coverageFile = os.path.join(discoveryStart, "unittest")
-                sysPath = [workdir]
-                if discover and not discoveryStart:
-                    discoveryStart = workdir
-            else:
-                if discover:
-                    if not discoveryStart:
-                        EricMessageBox.critical(
-                            self,
-                            self.tr("Unittest"),
-                            self.tr("You must enter a start directory for"
-                                    " auto-discovery."))
-                        return
-                    
-                    coverageFile = os.path.join(discoveryStart, "unittest")
-                    workdir = ""
-                    clientType = "Python3"
-                elif testFileName:
-                    mainScript = os.path.abspath(testFileName)
-                    workdir = os.path.dirname(mainScript)
-                    clientType = "Python3"
-                    coverageFile = os.path.splitext(mainScript)[0]
-                else:
-                    coverageFile = os.path.abspath("unittest")
-                    workdir = ""
-                    clientType = "Python3"
-                sysPath = []
-            if failedOnly and self.__failedTests:
-                failed = list(self.__failedTests)
-                if discover:
-                    workdir = discoveryStart
-                    discover = False
-            else:
-                failed = []
-            self.__failedTests = set()
-            self.__dbs.remoteUTPrepare(
-                testFileName, self.testName, testName, failed,
-                self.coverageCheckBox.isChecked(), coverageFile,
-                self.coverageEraseCheckBox.isChecked(), clientType=clientType,
-                forProject=self.__forProject, workdir=workdir,
-                venvName=venvName, syspath=sysPath,
-                discover=discover, discoveryStart=discoveryStart,
-                testCases=testCases, debug=self.debuggerCheckBox.isChecked())
-        else:
-            # we are running as an application
-            if discover and not discoveryStart:
-                EricMessageBox.critical(
-                    self,
-                    self.tr("Unittest"),
-                    self.tr("You must enter a start directory for"
-                            " auto-discovery."))
-                return
-            
-            if testFileName:
-                sys.path = (
-                    [os.path.dirname(os.path.abspath(testFileName))] +
-                    self.savedSysPath
-                )
-            elif discoveryStart:
-                sys.path = (
-                    [os.path.abspath(discoveryStart)] +
-                    self.savedSysPath
-                )
-            
-            # clean up list of imported modules to force a reimport upon
-            # running the test
-            if self.savedModulelist:
-                for modname in list(sys.modules.keys()):
-                    if modname not in self.savedModulelist:
-                        # delete it
-                        del(sys.modules[modname])
-            self.savedModulelist = sys.modules.copy()
-            
-            os.chdir(self.savedCwd)
-            
-            # now try to generate the testsuite
-            try:
-                testLoader = unittest.TestLoader()
-                if failedOnly and self.__failedTests:
-                    failed = list(self.__failedTests)
-                    if discover:
-                        os.chdir(discoveryStart)
-                        discover = False
-                else:
-                    failed = []
-                if discover:
-                    if testCases:
-                        test = testLoader.loadTestsFromNames(testCases)
-                    else:
-                        test = testLoader.discover(discoveryStart)
-                else:
-                    if testFileName:
-                        module = __import__(self.testName)
-                    else:
-                        module = None
-                    if failedOnly and self.__failedTests:
-                        if module:
-                            failed = [t.split(".", 1)[1]
-                                      for t in self.__failedTests]
-                        else:
-                            failed = list(self.__failedTests)
-                        test = testLoader.loadTestsFromNames(
-                            failed, module)
-                    else:
-                        test = testLoader.loadTestsFromName(
-                            testName, module)
-            except Exception:
-                exc_type, exc_value, exc_tb = sys.exc_info()
-                EricMessageBox.critical(
-                    self,
-                    self.tr("Unittest"),
-                    self.tr(
-                        "<p>Unable to run test <b>{0}</b>.</p>"
-                        "<p>{1}<br/>{2}</p>")
-                    .format(self.testName, str(exc_type),
-                            str(exc_value).replace("\n", "<br/>"))
-                )
-                return
-                
-            # now set up the coverage stuff
-            if self.coverageCheckBox.isChecked():
-                if discover:
-                    covname = os.path.join(discoveryStart, "unittest")
-                elif testFileName:
-                    covname = os.path.splitext(
-                        os.path.abspath(testFileName))[0]
-                else:
-                    covname = "unittest"
-                
-                from DebugClients.Python.coverage import coverage
-                cover = coverage(data_file="{0}.coverage".format(covname))
-                if self.coverageEraseCheckBox.isChecked():
-                    cover.erase()
-            else:
-                cover = None
-            
-            self.testResult = QtTestResult(
-                self, self.failfastCheckBox.isChecked())
-            self.totalTests = test.countTestCases()
-            if self.totalTests == 0:
-                EricMessageBox.warning(
-                    self,
-                    self.tr("Unittest"),
-                    self.tr("""No unittest were found. Aborting..."""))
-            else:
-                self.__failedTests = set()
-                self.__setRunningMode()
-                if cover:
-                    cover.start()
-                test.run(self.testResult)
-                if cover:
-                    cover.stop()
-                    cover.save()
-                self.__setStoppedMode()
-                sys.path = self.savedSysPath
-    
-    def __UTPrepared(self, nrTests, exc_type, exc_value):
-        """
-        Private slot to handle the utPrepared signal.
-        
-        If the unittest suite was loaded successfully, we ask the
-        client to run the test suite.
-        
-        @param nrTests number of tests contained in the test suite (integer)
-        @param exc_type type of exception occured during preparation (string)
-        @param exc_value value of exception occured during preparation (string)
-        """
-        if nrTests == 0:
-            EricMessageBox.critical(
-                self,
-                self.tr("Unittest"),
-                self.tr(
-                    "<p>Unable to run test <b>{0}</b>.</p>"
-                    "<p>{1}<br/>{2}</p>")
-                .format(self.testName, exc_type,
-                        exc_value.replace("\n", "<br/>"))
-            )
-            return
-        
-        self.totalTests = nrTests
-        self.__setRunningMode()
-        self.__dbs.remoteUTRun(debug=self.debuggerCheckBox.isChecked(),
-                               failfast=self.failfastCheckBox.isChecked())
-    
-    @pyqtSlot()
-    def __stopTests(self):
-        """
-        Private slot to stop the test.
-        """
-        if self.__dbs:
-            self.__dbs.remoteUTStop()
-        elif self.testResult:
-            self.testResult.stop()
-    
-    def on_errorsListWidget_currentTextChanged(self, text):
-        """
-        Private slot to handle the highlighted signal.
-        
-        @param text current text (string)
-        """
-        if text:
-            for pattern in self.rxPatterns:
-                text = re.sub(pattern, "", text)
-            
-            foundItems = self.testsListWidget.findItems(
-                text, Qt.MatchFlag.MatchExactly)
-            if len(foundItems) > 0:
-                itm = foundItems[0]
-                self.testsListWidget.setCurrentItem(itm)
-                self.testsListWidget.scrollToItem(itm)
-    
-    def __setRunningMode(self):
-        """
-        Private method to set the GUI in running mode.
-        """
-        self.running = True
-        self.tabWidget.setCurrentIndex(1)
-        
-        # reset counters and error infos
-        self.runCount = 0
-        self.failCount = 0
-        self.errorCount = 0
-        self.skippedCount = 0
-        self.expectedFailureCount = 0
-        self.unexpectedSuccessCount = 0
-        self.remainingCount = self.totalTests
-        
-        # reset the GUI
-        self.progressCounterRunCount.setText(str(self.runCount))
-        self.progressCounterRemCount.setText(str(self.remainingCount))
-        self.progressCounterFailureCount.setText(str(self.failCount))
-        self.progressCounterErrorCount.setText(str(self.errorCount))
-        self.progressCounterSkippedCount.setText(str(self.skippedCount))
-        self.progressCounterExpectedFailureCount.setText(
-            str(self.expectedFailureCount))
-        self.progressCounterUnexpectedSuccessCount.setText(
-            str(self.unexpectedSuccessCount))
-        
-        self.errorsListWidget.clear()
-        self.testsListWidget.clear()
-        
-        self.progressProgressBar.setRange(0, self.totalTests)
-        self.__setProgressColor("green")
-        self.progressProgressBar.reset()
-        
-        self.stopButton.setEnabled(True)
-        self.startButton.setEnabled(False)
-        self.startFailedButton.setEnabled(False)
-        self.stopButton.setDefault(True)
-        
-        self.sbLabel.setText(self.tr("Running"))
-        self.progressLed.on()
-        QApplication.processEvents()
-        
-        self.startTime = time.time()
-    
-    def __setStoppedMode(self):
-        """
-        Private method to set the GUI in stopped mode.
-        """
-        self.stopTime = time.time()
-        self.timeTaken = float(self.stopTime - self.startTime)
-        self.running = False
-        
-        failedAvailable = bool(self.__failedTests)
-        self.startButton.setEnabled(True)
-        self.startFailedButton.setEnabled(failedAvailable)
-        self.stopButton.setEnabled(False)
-        if failedAvailable:
-            self.startFailedButton.setDefault(True)
-            self.startButton.setDefault(False)
-        else:
-            self.startFailedButton.setDefault(False)
-            self.startButton.setDefault(True)
-        self.sbLabel.setText(
-            self.tr("Ran %n test(s) in {0:.3f}s", "", self.runCount)
-            .format(self.timeTaken))
-        self.progressLed.off()
-        
-        self.unittestStopped.emit()
-        
-        self.raise_()
-        self.activateWindow()
-    
-    def testFailed(self, test, exc, testId):
-        """
-        Public method called if a test fails.
-        
-        @param test name of the test (string)
-        @param exc string representation of the exception (string)
-        @param testId id of the test (string)
-        """
-        self.failCount += 1
-        self.progressCounterFailureCount.setText(str(self.failCount))
-        itm = QListWidgetItem(self.tr("Failure: {0}").format(test))
-        itm.setData(UnittestDialog.ErrorsInfoRole, (test, exc))
-        self.errorsListWidget.insertItem(0, itm)
-        self.__failedTests.add(testId)
-    
-    def testErrored(self, test, exc, testId):
-        """
-        Public method called if a test errors.
-        
-        @param test name of the test (string)
-        @param exc string representation of the exception (string)
-        @param testId id of the test (string)
-        """
-        self.errorCount += 1
-        self.progressCounterErrorCount.setText(str(self.errorCount))
-        itm = QListWidgetItem(self.tr("Error: {0}").format(test))
-        itm.setData(UnittestDialog.ErrorsInfoRole, (test, exc))
-        self.errorsListWidget.insertItem(0, itm)
-        self.__failedTests.add(testId)
-    
-    def testSkipped(self, test, reason, testId):
-        """
-        Public method called if a test was skipped.
-        
-        @param test name of the test (string)
-        @param reason reason for skipping the test (string)
-        @param testId id of the test (string)
-        """
-        self.skippedCount += 1
-        self.progressCounterSkippedCount.setText(str(self.skippedCount))
-        itm = QListWidgetItem(self.tr("    Skipped: {0}").format(reason))
-        if ericApp().usesDarkPalette():
-            itm.setForeground(self.SkippedColorDarkTheme)
-        else:
-            itm.setForeground(self.SkippedColorLightTheme)
-        self.testsListWidget.insertItem(1, itm)
-    
-    def testFailedExpected(self, test, exc, testId):
-        """
-        Public method called if a test fails as expected.
-        
-        @param test name of the test (string)
-        @param exc string representation of the exception (string)
-        @param testId id of the test (string)
-        """
-        self.expectedFailureCount += 1
-        self.progressCounterExpectedFailureCount.setText(
-            str(self.expectedFailureCount))
-        itm = QListWidgetItem(self.tr("    Expected Failure"))
-        if ericApp().usesDarkPalette():
-            itm.setForeground(self.FailedExpectedColorDarkTheme)
-        else:
-            itm.setForeground(self.FailedExpectedColorLightTheme)
-        self.testsListWidget.insertItem(1, itm)
-    
-    def testSucceededUnexpected(self, test, testId):
-        """
-        Public method called if a test succeeds unexpectedly.
-        
-        @param test name of the test (string)
-        @param testId id of the test (string)
-        """
-        self.unexpectedSuccessCount += 1
-        self.progressCounterUnexpectedSuccessCount.setText(
-            str(self.unexpectedSuccessCount))
-        itm = QListWidgetItem(self.tr("    Unexpected Success"))
-        if ericApp().usesDarkPalette():
-            itm.setForeground(self.SucceededUnexpectedColorDarkTheme)
-        else:
-            itm.setForeground(self.SucceededUnexpectedColorLightTheme)
-        self.testsListWidget.insertItem(1, itm)
-    
-    def testStarted(self, test, doc):
-        """
-        Public method called if a test is about to be run.
-        
-        @param test name of the started test (string)
-        @param doc documentation of the started test (string)
-        """
-        if doc:
-            self.testsListWidget.insertItem(0, "    {0}".format(doc))
-        self.testsListWidget.insertItem(0, test)
-        if self.__dbs is None:
-            QApplication.processEvents()
-    
-    def testFinished(self):
-        """
-        Public method called if a test has finished.
-        
-        <b>Note</b>: It is also called if it has already failed or errored.
-        """
-        # update the counters
-        self.remainingCount -= 1
-        self.runCount += 1
-        self.progressCounterRunCount.setText(str(self.runCount))
-        self.progressCounterRemCount.setText(str(self.remainingCount))
-        
-        # update the progressbar
-        if self.errorCount:
-            self.__setProgressColor("red")
-        elif self.failCount:
-            self.__setProgressColor("orange")
-        self.progressProgressBar.setValue(self.runCount)
-    
-    def on_errorsListWidget_itemDoubleClicked(self, lbitem):
-        """
-        Private slot called by doubleclicking an errorlist entry.
-        
-        It will popup a dialog showing the stacktrace.
-        If called from eric, an additional button is displayed
-        to show the python source in an eric source viewer (in
-        erics main window.
-        
-        @param lbitem the listbox item that was double clicked
-        """
-        self.errListIndex = self.errorsListWidget.row(lbitem)
-        text = lbitem.text()
-        self.on_errorsListWidget_currentTextChanged(text)
-        
-        # get the error info
-        test, tracebackText = lbitem.data(UnittestDialog.ErrorsInfoRole)
-        
-        # now build the dialog
-        from .Ui_UnittestStacktraceDialog import Ui_UnittestStacktraceDialog
-        self.dlg = QDialog(self)
-        ui = Ui_UnittestStacktraceDialog()
-        ui.setupUi(self.dlg)
-        self.dlg.traceback = ui.traceback
-        
-        ui.showButton = ui.buttonBox.addButton(
-            self.tr("Show Source"), QDialogButtonBox.ButtonRole.ActionRole)
-        ui.showButton.clicked.connect(self.__showSource)
-        
-        ui.buttonBox.button(
-            QDialogButtonBox.StandardButton.Close).setDefault(True)
-        
-        self.dlg.setWindowTitle(text)
-        ui.testLabel.setText(test)
-        ui.traceback.setPlainText(tracebackText)
-        
-        # and now fire it up
-        self.dlg.show()
-        self.dlg.exec()
-    
-    def __showSource(self):
-        """
-        Private slot to show the source of a traceback in an eric editor.
-        """
-        # get the error info
-        tracebackLines = self.dlg.traceback.toPlainText().splitlines()
-        # find the last entry matching the pattern
-        for index in range(len(tracebackLines) - 1, -1, -1):
-            fmatch = re.search(r'File "(.*?)", line (\d*?),.*',
-                               tracebackLines[index])
-            if fmatch:
-                break
-        if fmatch:
-            fn, ln = fmatch.group(1, 2)
-            if self.__dbs:
-                # running as part of eric IDE
-                self.unittestFile.emit(fn, int(ln), True)
-            else:
-                self.__openEditor(fn, int(ln))
-    
-    def hasFailedTests(self):
-        """
-        Public method to check, if there are failed tests from the last run.
-        
-        @return flag indicating the presence of failed tests (boolean)
-        """
-        return bool(self.__failedTests)
-    
-    def __openEditor(self, filename, linenumber):
-        """
-        Private method to open an editor window for the given file.
-        
-        Note: This method opens an editor window when the unittest dialog
-        is called as a standalone application.
-        
-        @param filename path of the file to be opened
-        @type str
-        @param linenumber line number to place the cursor at
-        @type int
-        """
-        from QScintilla.MiniEditor import MiniEditor
-        editor = MiniEditor(filename, "Python3", self)
-        editor.gotoLine(linenumber)
-        editor.show()
-        
-        self.__editors.append(editor)
-    
-    def closeEvent(self, event):
-        """
-        Protected method to handle the close event.
-        
-        @param event close event
-        @type QCloseEvent
-        """
-        event.accept()
-        
-        for editor in self.__editors:
-            with contextlib.suppress(Exception):
-                editor.close()
-
-
-class QtTestResult(unittest.TestResult):
-    """
-    A TestResult derivative to work with a graphical GUI.
-    
-    For more details see pyunit.py of the standard Python distribution.
-    """
-    def __init__(self, parent, failfast):
-        """
-        Constructor
-        
-        @param parent reference to the parent widget
-        @type UnittestDialog
-        @param failfast flag indicating to stop at the first error
-        @type bool
-        """
-        super().__init__()
-        self.parent = parent
-        self.failfast = failfast
-    
-    def addFailure(self, test, err):
-        """
-        Public method called if a test failed.
-        
-        @param test reference to the test object
-        @param err error traceback
-        """
-        super().addFailure(test, err)
-        tracebackLines = self._exc_info_to_string(err, test)
-        self.parent.testFailed(str(test), tracebackLines, test.id())
-    
-    def addError(self, test, err):
-        """
-        Public method called if a test errored.
-        
-        @param test reference to the test object
-        @param err error traceback
-        """
-        super().addError(test, err)
-        tracebackLines = self._exc_info_to_string(err, test)
-        self.parent.testErrored(str(test), tracebackLines, test.id())
-    
-    def addSubTest(self, test, subtest, err):
-        """
-        Public method called for each subtest to record its result.
-        
-        @param test reference to the test object
-        @param subtest reference to the subtest object
-        @param err error traceback
-        """
-        if err is not None:
-            super().addSubTest(test, subtest, err)
-            tracebackLines = self._exc_info_to_string(err, test)
-            if issubclass(err[0], test.failureException):
-                self.parent.testFailed(
-                    str(subtest), tracebackLines, test.id())
-            else:
-                self.parent.testErrored(
-                    str(subtest), tracebackLines, test.id())
-    
-    def addSkip(self, test, reason):
-        """
-        Public method called if a test was skipped.
-        
-        @param test reference to the test object
-        @param reason reason for skipping the test (string)
-        """
-        super().addSkip(test, reason)
-        self.parent.testSkipped(str(test), reason, test.id())
-    
-    def addExpectedFailure(self, test, err):
-        """
-        Public method called if a test failed expected.
-        
-        @param test reference to the test object
-        @param err error traceback
-        """
-        super().addExpectedFailure(test, err)
-        tracebackLines = self._exc_info_to_string(err, test)
-        self.parent.testFailedExpected(str(test), tracebackLines, test.id())
-    
-    def addUnexpectedSuccess(self, test):
-        """
-        Public method called if a test succeeded expectedly.
-        
-        @param test reference to the test object
-        """
-        super().addUnexpectedSuccess(test)
-        self.parent.testSucceededUnexpected(str(test), test.id())
-    
-    def startTest(self, test):
-        """
-        Public method called at the start of a test.
-        
-        @param test Reference to the test object
-        """
-        super().startTest(test)
-        self.parent.testStarted(str(test), test.shortDescription())
-
-    def stopTest(self, test):
-        """
-        Public method called at the end of a test.
-        
-        @param test Reference to the test object
-        """
-        super().stopTest(test)
-        self.parent.testFinished()
-
-
-class UnittestWindow(EricMainWindow):
-    """
-    Main window class for the standalone dialog.
-    """
-    def __init__(self, prog=None, parent=None):
-        """
-        Constructor
-        
-        @param prog filename of the program to open
-        @param parent reference to the parent widget (QWidget)
-        """
-        super().__init__(parent)
-        self.cw = UnittestDialog(prog, parent=self)
-        self.cw.installEventFilter(self)
-        size = self.cw.size()
-        self.setCentralWidget(self.cw)
-        self.resize(size)
-        
-        self.setStyle(Preferences.getUI("Style"),
-                      Preferences.getUI("StyleSheet"))
-        
-        self.cw.buttonBox.accepted.connect(self.close)
-        self.cw.buttonBox.rejected.connect(self.close)
-    
-    def eventFilter(self, obj, event):
-        """
-        Public method to filter events.
-        
-        @param obj reference to the object the event is meant for (QObject)
-        @param event reference to the event object (QEvent)
-        @return flag indicating, whether the event was handled (boolean)
-        """
-        if event.type() == QEvent.Type.Close:
-            QApplication.exit()
-            return True
-        
-        return False
-
-
-def clearSavedHistories(self):
-    """
-    Function to clear the saved history lists.
-    """
-    Preferences.Prefs.rsettings.setValue(
-        recentNameUnittestDiscoverHistory, [])
-    Preferences.Prefs.rsettings.setValue(
-        recentNameUnittestFileHistory, [])
-    Preferences.Prefs.rsettings.setValue(
-        recentNameUnittestTestnameHistory, [])
-    
-    Preferences.Prefs.rsettings.sync()
--- a/eric7/PyUnit/UnittestDialog.ui	Wed Jun 01 13:49:13 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,694 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>UnittestDialog</class>
- <widget class="QWidget" name="UnittestDialog">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>650</width>
-    <height>700</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Unittest</string>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout_5">
-   <item>
-    <widget class="QTabWidget" name="tabWidget">
-     <property name="currentIndex">
-      <number>0</number>
-     </property>
-     <widget class="QWidget" name="parametersTab">
-      <attribute name="title">
-       <string>Parameters</string>
-      </attribute>
-      <layout class="QVBoxLayout" name="verticalLayout_6">
-       <item>
-        <widget class="QGroupBox" name="groupBox">
-         <property name="title">
-          <string>Test Parameters</string>
-         </property>
-         <layout class="QGridLayout" name="gridLayout">
-          <item row="0" column="0" colspan="2">
-           <layout class="QHBoxLayout" name="horizontalLayout_4">
-            <item>
-             <widget class="QCheckBox" name="discoverCheckBox">
-              <property name="sizePolicy">
-               <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-                <horstretch>0</horstretch>
-                <verstretch>0</verstretch>
-               </sizepolicy>
-              </property>
-              <property name="toolTip">
-               <string>Select to discover tests automatically</string>
-              </property>
-              <property name="text">
-               <string>&amp;Discover tests (test modules must be importable)</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QToolButton" name="clearHistoriesButton">
-              <property name="toolTip">
-               <string>Press to clear the various histories</string>
-              </property>
-             </widget>
-            </item>
-           </layout>
-          </item>
-          <item row="1" column="0">
-           <widget class="QLabel" name="label_3">
-            <property name="text">
-             <string>Discovery &amp;Start:</string>
-            </property>
-            <property name="buddy">
-             <cstring>discoveryPicker</cstring>
-            </property>
-           </widget>
-          </item>
-          <item row="1" column="1">
-           <widget class="EricComboPathPicker" name="discoveryPicker" native="true">
-            <property name="enabled">
-             <bool>false</bool>
-            </property>
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
-              <horstretch>0</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
-            </property>
-            <property name="focusPolicy">
-             <enum>Qt::WheelFocus</enum>
-            </property>
-            <property name="toolTip">
-             <string>Enter name of the directory at which to start the test file discovery</string>
-            </property>
-            <property name="whatsThis">
-             <string>&lt;b&gt;Discovery Start&lt;/b&gt;
-&lt;p&gt;Enter name of the directory at which to start the test file discovery.
-Note that all test modules must be importable from this directory.&lt;/p&gt;</string>
-            </property>
-           </widget>
-          </item>
-          <item row="2" column="0">
-           <widget class="QLabel" name="testsuiteLabel">
-            <property name="text">
-             <string>Test &amp;Filename:</string>
-            </property>
-            <property name="buddy">
-             <cstring>testsuitePicker</cstring>
-            </property>
-           </widget>
-          </item>
-          <item row="2" column="1">
-           <widget class="EricComboPathPicker" name="testsuitePicker" native="true">
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
-              <horstretch>0</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
-            </property>
-            <property name="focusPolicy">
-             <enum>Qt::WheelFocus</enum>
-            </property>
-            <property name="toolTip">
-             <string>Enter name of file defining the testsuite</string>
-            </property>
-            <property name="whatsThis">
-             <string>&lt;b&gt;Testsuite&lt;/b&gt;
-&lt;p&gt;Enter the name of the file defining the testsuite.
-It should have a method with a name given below. If no name is given, the suite() method will be tried. If no such method can be
-found, the module will be inspected for proper test
-cases.&lt;/p&gt;</string>
-            </property>
-           </widget>
-          </item>
-          <item row="3" column="0">
-           <widget class="QLabel" name="label">
-            <property name="text">
-             <string>&amp;Test Name:</string>
-            </property>
-            <property name="buddy">
-             <cstring>testComboBox</cstring>
-            </property>
-           </widget>
-          </item>
-          <item row="3" column="1">
-           <widget class="QComboBox" name="testComboBox">
-            <property name="toolTip">
-             <string>Enter the test name. Leave empty to use the default name &quot;suite&quot;.</string>
-            </property>
-            <property name="whatsThis">
-             <string>&lt;b&gt;Testname&lt;/b&gt;&lt;p&gt;Enter the name of the test to be performed. This name must follow the rules given by Python's unittest module. If this field is empty, the default name of &quot;suite&quot; will be used.&lt;/p&gt;</string>
-            </property>
-            <property name="editable">
-             <bool>true</bool>
-            </property>
-           </widget>
-          </item>
-         </layout>
-        </widget>
-       </item>
-       <item>
-        <widget class="QGroupBox" name="optionsGroup">
-         <property name="title">
-          <string>Run Parameters</string>
-         </property>
-         <layout class="QVBoxLayout" name="verticalLayout_2">
-          <item>
-           <layout class="QHBoxLayout" name="horizontalLayout_3">
-            <item>
-             <widget class="QLabel" name="venvLabel">
-              <property name="text">
-               <string>&amp;Virtual Environment:</string>
-              </property>
-              <property name="buddy">
-               <cstring>venvComboBox</cstring>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QComboBox" name="venvComboBox">
-              <property name="sizePolicy">
-               <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-                <horstretch>0</horstretch>
-                <verstretch>0</verstretch>
-               </sizepolicy>
-              </property>
-              <property name="toolTip">
-               <string>Select the virtual environment to be used</string>
-              </property>
-              <property name="whatsThis">
-               <string>&lt;b&gt;Virtual Environment&lt;/b&gt;\n&lt;p&gt;Enter the virtual environment to be used. Leave it empty to use the default environment, i.e. the one configured globally or per project.&lt;/p&gt;</string>
-              </property>
-             </widget>
-            </item>
-           </layout>
-          </item>
-          <item>
-           <layout class="QGridLayout" name="gridLayout_2">
-            <item row="0" column="0">
-             <widget class="QCheckBox" name="coverageCheckBox">
-              <property name="toolTip">
-               <string>Select whether coverage data should be collected</string>
-              </property>
-              <property name="text">
-               <string>C&amp;ollect coverage data</string>
-              </property>
-             </widget>
-            </item>
-            <item row="0" column="1">
-             <widget class="QCheckBox" name="coverageEraseCheckBox">
-              <property name="enabled">
-               <bool>false</bool>
-              </property>
-              <property name="toolTip">
-               <string>Select whether old coverage data should be erased</string>
-              </property>
-              <property name="text">
-               <string>&amp;Erase coverage data</string>
-              </property>
-             </widget>
-            </item>
-            <item row="1" column="0">
-             <widget class="QCheckBox" name="failfastCheckBox">
-              <property name="toolTip">
-               <string>Select to stop the test run on the first error or failure</string>
-              </property>
-              <property name="text">
-               <string>Stop on First Error or Failure</string>
-              </property>
-             </widget>
-            </item>
-            <item row="1" column="1">
-             <widget class="QCheckBox" name="debuggerCheckBox">
-              <property name="toolTip">
-               <string>Select to run the unittest with debugger support enabled</string>
-              </property>
-              <property name="text">
-               <string>Run with Debugger</string>
-              </property>
-             </widget>
-            </item>
-           </layout>
-          </item>
-         </layout>
-        </widget>
-       </item>
-       <item>
-        <widget class="QGroupBox" name="discoveryGroup">
-         <property name="sizePolicy">
-          <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
-           <horstretch>0</horstretch>
-           <verstretch>0</verstretch>
-          </sizepolicy>
-         </property>
-         <property name="title">
-          <string>Discovery Results</string>
-         </property>
-         <layout class="QVBoxLayout" name="verticalLayout_3">
-          <item>
-           <widget class="QTreeWidget" name="discoveryList">
-            <property name="alternatingRowColors">
-             <bool>true</bool>
-            </property>
-            <property name="sortingEnabled">
-             <bool>true</bool>
-            </property>
-            <property name="headerHidden">
-             <bool>true</bool>
-            </property>
-            <property name="expandsOnDoubleClick">
-             <bool>false</bool>
-            </property>
-            <column>
-             <property name="text">
-              <string notr="true">1</string>
-             </property>
-            </column>
-           </widget>
-          </item>
-         </layout>
-        </widget>
-       </item>
-      </layout>
-     </widget>
-     <widget class="QWidget" name="resultsTab">
-      <attribute name="title">
-       <string>Results</string>
-      </attribute>
-      <layout class="QVBoxLayout" name="verticalLayout_4">
-       <item>
-        <widget class="QGroupBox" name="progressGroupBox">
-         <property name="title">
-          <string>Progress</string>
-         </property>
-         <layout class="QVBoxLayout" name="verticalLayout">
-          <item>
-           <layout class="QHBoxLayout" name="_8">
-            <item>
-             <widget class="QLabel" name="progressTextLabel">
-              <property name="text">
-               <string>Progress:</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <spacer>
-              <property name="orientation">
-               <enum>Qt::Horizontal</enum>
-              </property>
-              <property name="sizeHint" stdset="0">
-               <size>
-                <width>371</width>
-                <height>20</height>
-               </size>
-              </property>
-             </spacer>
-            </item>
-            <item>
-             <widget class="EricLed" name="progressLed"/>
-            </item>
-           </layout>
-          </item>
-          <item>
-           <widget class="QProgressBar" name="progressProgressBar">
-            <property name="value">
-             <number>0</number>
-            </property>
-            <property name="orientation">
-             <enum>Qt::Horizontal</enum>
-            </property>
-            <property name="format">
-             <string>%v/%m Tests</string>
-            </property>
-           </widget>
-          </item>
-          <item>
-           <layout class="QHBoxLayout" name="horizontalLayout_2">
-            <item>
-             <widget class="QLabel" name="progressCounterRunLabel">
-              <property name="text">
-               <string>Run:</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QLabel" name="progressCounterRunCount">
-              <property name="toolTip">
-               <string>Number of tests run</string>
-              </property>
-              <property name="text">
-               <string notr="true">0</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QLabel" name="progressCounterRemLabel">
-              <property name="text">
-               <string>Remaining:</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QLabel" name="progressCounterRemCount">
-              <property name="toolTip">
-               <string>Number of tests to be run</string>
-              </property>
-              <property name="text">
-               <string notr="true">0</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <spacer name="horizontalSpacer">
-              <property name="orientation">
-               <enum>Qt::Horizontal</enum>
-              </property>
-              <property name="sizeHint" stdset="0">
-               <size>
-                <width>40</width>
-                <height>20</height>
-               </size>
-              </property>
-             </spacer>
-            </item>
-           </layout>
-          </item>
-          <item>
-           <layout class="QHBoxLayout" name="horizontalLayout">
-            <item>
-             <widget class="QLabel" name="progressCounterFailureLabel">
-              <property name="text">
-               <string>Failures:</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QLabel" name="progressCounterFailureCount">
-              <property name="toolTip">
-               <string>Number of test failures</string>
-              </property>
-              <property name="text">
-               <string notr="true">0</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QLabel" name="progressCounterErrorLabel">
-              <property name="text">
-               <string>Errors:</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QLabel" name="progressCounterErrorCount">
-              <property name="toolTip">
-               <string>Number of test errors</string>
-              </property>
-              <property name="text">
-               <string notr="true">0</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QLabel" name="progressCounterSkippedLabel">
-              <property name="text">
-               <string>Skipped:</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QLabel" name="progressCounterSkippedCount">
-              <property name="toolTip">
-               <string>Number of tests skipped</string>
-              </property>
-              <property name="text">
-               <string notr="true">0</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QLabel" name="progressCounterExpectedFailureLabel">
-              <property name="text">
-               <string>Expected Failures:</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QLabel" name="progressCounterExpectedFailureCount">
-              <property name="toolTip">
-               <string>Number of tests with expected failure</string>
-              </property>
-              <property name="text">
-               <string notr="true">0</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QLabel" name="progressCounterUnexpectedSuccessLabel">
-              <property name="text">
-               <string>Unexpected Successes:</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QLabel" name="progressCounterUnexpectedSuccessCount">
-              <property name="toolTip">
-               <string>Number of tests with unexpected success</string>
-              </property>
-              <property name="text">
-               <string notr="true">0</string>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <spacer>
-              <property name="orientation">
-               <enum>Qt::Horizontal</enum>
-              </property>
-              <property name="sizeType">
-               <enum>QSizePolicy::Expanding</enum>
-              </property>
-              <property name="sizeHint" stdset="0">
-               <size>
-                <width>20</width>
-                <height>20</height>
-               </size>
-              </property>
-             </spacer>
-            </item>
-           </layout>
-          </item>
-         </layout>
-        </widget>
-       </item>
-       <item>
-        <widget class="QLabel" name="textLabel1">
-         <property name="text">
-          <string>Tests performed:</string>
-         </property>
-        </widget>
-       </item>
-       <item>
-        <widget class="QListWidget" name="testsListWidget"/>
-       </item>
-       <item>
-        <widget class="QLabel" name="listboxLabel">
-         <property name="text">
-          <string>Failures and errors:</string>
-         </property>
-        </widget>
-       </item>
-       <item>
-        <widget class="QListWidget" name="errorsListWidget">
-         <property name="toolTip">
-          <string>Failures and Errors list</string>
-         </property>
-         <property name="whatsThis">
-          <string>&lt;b&gt;Failures and Errors list&lt;/b&gt;
-&lt;p&gt;This list shows all failed and errored tests.
-Double clicking on an entry will show the respective traceback.&lt;/p&gt;</string>
-         </property>
-        </widget>
-       </item>
-      </layout>
-     </widget>
-    </widget>
-   </item>
-   <item>
-    <widget class="QDialogButtonBox" name="buttonBox">
-     <property name="orientation">
-      <enum>Qt::Horizontal</enum>
-     </property>
-     <property name="standardButtons">
-      <set>QDialogButtonBox::Close</set>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <layout class="QHBoxLayout" name="_3">
-     <item>
-      <widget class="QLabel" name="sbLabel">
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-         <horstretch>0</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
-       </property>
-       <property name="text">
-        <string>Idle</string>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <spacer>
-       <property name="orientation">
-        <enum>Qt::Horizontal</enum>
-       </property>
-       <property name="sizeType">
-        <enum>QSizePolicy::Expanding</enum>
-       </property>
-       <property name="sizeHint" stdset="0">
-        <size>
-         <width>20</width>
-         <height>20</height>
-        </size>
-       </property>
-      </spacer>
-     </item>
-    </layout>
-   </item>
-  </layout>
- </widget>
- <customwidgets>
-  <customwidget>
-   <class>EricComboPathPicker</class>
-   <extends>QWidget</extends>
-   <header>EricWidgets/EricPathPicker.h</header>
-   <container>1</container>
-  </customwidget>
-  <customwidget>
-   <class>EricLed</class>
-   <extends>QFrame</extends>
-   <header>EricWidgets/EricLed.h</header>
-   <container>1</container>
-  </customwidget>
- </customwidgets>
- <tabstops>
-  <tabstop>tabWidget</tabstop>
-  <tabstop>discoverCheckBox</tabstop>
-  <tabstop>clearHistoriesButton</tabstop>
-  <tabstop>discoveryPicker</tabstop>
-  <tabstop>testsuitePicker</tabstop>
-  <tabstop>testComboBox</tabstop>
-  <tabstop>venvComboBox</tabstop>
-  <tabstop>coverageCheckBox</tabstop>
-  <tabstop>coverageEraseCheckBox</tabstop>
-  <tabstop>failfastCheckBox</tabstop>
-  <tabstop>debuggerCheckBox</tabstop>
-  <tabstop>discoveryList</tabstop>
-  <tabstop>testsListWidget</tabstop>
-  <tabstop>errorsListWidget</tabstop>
- </tabstops>
- <resources/>
- <connections>
-  <connection>
-   <sender>coverageCheckBox</sender>
-   <signal>toggled(bool)</signal>
-   <receiver>coverageEraseCheckBox</receiver>
-   <slot>setEnabled(bool)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>321</x>
-     <y>294</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>616</x>
-     <y>294</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>buttonBox</sender>
-   <signal>accepted()</signal>
-   <receiver>UnittestDialog</receiver>
-   <slot>close()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>67</x>
-     <y>662</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>72</x>
-     <y>667</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>buttonBox</sender>
-   <signal>rejected()</signal>
-   <receiver>UnittestDialog</receiver>
-   <slot>close()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>157</x>
-     <y>662</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>148</x>
-     <y>668</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>discoverCheckBox</sender>
-   <signal>toggled(bool)</signal>
-   <receiver>discoveryPicker</receiver>
-   <slot>setEnabled(bool)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>209</x>
-     <y>93</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>209</x>
-     <y>120</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>discoverCheckBox</sender>
-   <signal>toggled(bool)</signal>
-   <receiver>testsuitePicker</receiver>
-   <slot>setDisabled(bool)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>96</x>
-     <y>93</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>150</x>
-     <y>143</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>discoverCheckBox</sender>
-   <signal>toggled(bool)</signal>
-   <receiver>testComboBox</receiver>
-   <slot>setDisabled(bool)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>321</x>
-     <y>97</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>327</x>
-     <y>166</y>
-    </hint>
-   </hints>
-  </connection>
- </connections>
-</ui>
--- a/eric7/PyUnit/UnittestStacktraceDialog.ui	Wed Jun 01 13:49:13 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-<ui version="4.0" >
- <class>UnittestStacktraceDialog</class>
- <widget class="QDialog" name="UnittestStacktraceDialog" >
-  <property name="geometry" >
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>600</width>
-    <height>250</height>
-   </rect>
-  </property>
-  <layout class="QVBoxLayout" >
-   <item>
-    <widget class="QLabel" name="testLabel" />
-   </item>
-   <item>
-    <widget class="QTextEdit" name="traceback" >
-     <property name="readOnly" >
-      <bool>true</bool>
-     </property>
-     <property name="acceptRichText" >
-      <bool>false</bool>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <widget class="QDialogButtonBox" name="buttonBox" >
-     <property name="orientation" >
-      <enum>Qt::Horizontal</enum>
-     </property>
-     <property name="standardButtons" >
-      <set>QDialogButtonBox::Close</set>
-     </property>
-    </widget>
-   </item>
-  </layout>
- </widget>
- <resources/>
- <connections>
-  <connection>
-   <sender>buttonBox</sender>
-   <signal>accepted()</signal>
-   <receiver>UnittestStacktraceDialog</receiver>
-   <slot>accept()</slot>
-   <hints>
-    <hint type="sourcelabel" >
-     <x>33</x>
-     <y>235</y>
-    </hint>
-    <hint type="destinationlabel" >
-     <x>33</x>
-     <y>249</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>buttonBox</sender>
-   <signal>rejected()</signal>
-   <receiver>UnittestStacktraceDialog</receiver>
-   <slot>reject()</slot>
-   <hints>
-    <hint type="sourcelabel" >
-     <x>94</x>
-     <y>226</y>
-    </hint>
-    <hint type="destinationlabel" >
-     <x>95</x>
-     <y>249</y>
-    </hint>
-   </hints>
-  </connection>
- </connections>
-</ui>
--- a/eric7/PyUnit/__init__.py	Wed Jun 01 13:49:13 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
-#
-
-"""
-Package implementing an interface to the pyunit unittest package.
-
-The package consist of a single dialog, which may be called as a
-standalone version using the eric7_unittest script or from within the eric
-IDE. If it is called from within eric, it has the additional function to
-open a source file that failed a test.
-"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Testing/Interfaces/PytestExecutor.py	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,310 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the executor for the 'pytest' framework.
+"""
+
+import contextlib
+import json
+import os
+
+from PyQt6.QtCore import pyqtSlot, QProcess
+
+from EricNetwork.EricJsonStreamReader import EricJsonReader
+
+from .TestExecutorBase import TestExecutorBase, TestResult, TestResultCategory
+
+
+class PytestExecutor(TestExecutorBase):
+    """
+    Class implementing the executor for the 'pytest' framework.
+    """
+    module = "pytest"
+    name = "pytest"
+    
+    runner = os.path.join(os.path.dirname(__file__), "PytestRunner.py")
+    
+    def __init__(self, testWidget):
+        """
+        Constructor
+        
+        @param testWidget reference to the unit test widget
+        @type TestingWidget
+        """
+        super().__init__(testWidget)
+        
+        self.__statusDisplayMapping = {
+            "failed": self.tr("Failure"),
+            "skipped": self.tr("Skipped"),
+            "xfailed": self.tr("Expected Failure"),
+            "xpassed": self.tr("Unexpected Success"),
+            "passed": self.tr("Success"),
+        }
+        
+        self.__config = None
+    
+    def getVersions(self, interpreter):
+        """
+        Public method to get the test framework version and version information
+        of its installed plugins.
+        
+        @param interpreter interpreter to be used for the test
+        @type str
+        @return dictionary containing the framework name and version and the
+            list of available plugins with name and version each
+        @rtype dict
+        """
+        proc = QProcess()
+        proc.start(interpreter, [PytestExecutor.runner, "versions"])
+        if proc.waitForFinished(3000):
+            exitCode = proc.exitCode()
+            if exitCode == 0:
+                outputLines = self.readAllOutput(proc).splitlines()
+                for line in outputLines:
+                    if line.startswith("{") and line.endswith("}"):
+                        with contextlib.suppress(json.JSONDecodeError):
+                            return json.loads(line)
+        
+        return {}
+    
+    def hasCoverage(self, interpreter):
+        """
+        Public method to get the test framework version and version information
+        of its installed plugins.
+        
+        @param interpreter interpreter to be used for the test
+        @type str
+        @return flag indicating the availability of coverage functionality
+        @rtype bool
+        """
+        versions = self.getVersions(interpreter)
+        if "plugins" in versions:
+            return any(plugin["name"] == "pytest-cov"
+                       for plugin in versions["plugins"])
+        
+        return False
+    
+    def createArguments(self, config):
+        """
+        Public method to create the arguments needed to start the test process.
+        
+        @param config configuration for the test execution
+        @type TestConfig
+        @return list of process arguments
+        @rtype list of str
+        """
+        #
+        # collectCoverage: --cov= + --cov-report= to suppress report generation
+        # eraseCoverage: --cov-append if eraseCoverage is False
+        # coverageFile
+        args = [
+            PytestExecutor.runner,
+            "runtest",
+            self.reader.address(),
+            str(self.reader.port()),
+            "--quiet",
+        ]
+        
+        if config.failFast:
+            args.append("--exitfirst")
+        
+        if config.failedOnly:
+            args.append("--last-failed")
+        else:
+            args.append("--cache-clear")
+        
+        if config.collectCoverage:
+            args.extend([
+                "--cov=.",
+                "--cov-report="
+            ])
+            if not config.eraseCoverage:
+                args.append("--cov-append")
+        
+        if config.testFilename:
+            if config.testName:
+                args.append("{0}::{1}".format(
+                    config.testFilename,
+                    config.testName.replace(".", "::")
+                ))
+            else:
+                args.append(config.testFilename)
+        
+        return args
+    
+    def start(self, config, pythonpath):
+        """
+        Public method to start the testing process.
+        
+        @param config configuration for the test execution
+        @type TestConfig
+        @param pythonpath list of directories to be added to the Python path
+        @type list of str
+        """
+        self.reader = EricJsonReader(name="Unittest Reader", parent=self)
+        self.reader.dataReceived.connect(self.__processData)
+        
+        self.__config = config
+        
+        if config.discoveryStart:
+            pythonpath.insert(0, os.path.abspath(config.discoveryStart))
+        elif config.testFilename:
+            pythonpath.insert(
+                0, os.path.abspath(os.path.dirname(config.testFilename)))
+        
+        if config.discover:
+            self.__rootdir = config.discoveryStart
+        elif config.testFilename:
+            self.__rootdir = os.path.dirname(config.testFilename)
+        else:
+            self.__rootdir = ""
+        
+        super().start(config, pythonpath)
+    
+    def finished(self):
+        """
+        Public method handling the unit test process been finished.
+        
+        This method should read the results (if necessary) and emit the signal
+        testFinished.
+        """
+        if self.__config.collectCoverage:
+            self.coverageDataSaved.emit(
+                os.path.join(self.__rootdir, ".coverage"))
+        
+        self.__config = None
+        
+        self.reader.close()
+        
+        output = self.readAllOutput()
+        self.testFinished.emit([], output)
+    
+    @pyqtSlot(object)
+    def __processData(self, data):
+        """
+        Private slot to process the received data.
+        
+        @param data data object received
+        @type dict
+        """
+        # test configuration
+        if data["event"] == "config":
+            self.__rootdir = data["root"]
+        
+        # error collecting tests
+        elif data["event"] == "collecterror":
+            name = self.__normalizeModuleName(data["nodeid"])
+            self.collectError.emit([(name, data["report"])])
+        
+        # tests collected
+        elif data["event"] == "collected":
+            self.collected.emit([
+                (data["nodeid"],
+                 self.__nodeid2testname(data["nodeid"]),
+                 "")
+            ])
+        
+        # test started
+        elif data["event"] == "starttest":
+            self.startTest.emit(
+                (data["nodeid"],
+                 self.__nodeid2testname(data["nodeid"]),
+                 "")
+            )
+        
+        # test result
+        elif data["event"] == "result":
+            if data["status"] in ("failed", "xpassed") or data["with_error"]:
+                category = TestResultCategory.FAIL
+            elif data["status"] in ("passed", "xfailed"):
+                category = TestResultCategory.OK
+            else:
+                category = TestResultCategory.SKIP
+            
+            status = (
+                self.tr("Error")
+                if data["with_error"] else
+                self.__statusDisplayMapping[data["status"]]
+            )
+            
+            message = data.get("message", "")
+            extraText = data.get("report", "")
+            reportPhase = data.get("report_phase")
+            if reportPhase in ("setup", "teardown"):
+                message = (
+                    self.tr("ERROR at {0}: {1}", "phase, message")
+                    .format(reportPhase, message)
+                )
+                extraText = (
+                    self.tr("ERROR at {0}: {1}", "phase, extra text")
+                    .format(reportPhase, extraText)
+                )
+            sections = data.get("sections", [])
+            if sections:
+                extraText += "\n"
+                for heading, text in sections:
+                    extraText += "----- {0} -----\n{1}".format(heading, text)
+            
+            duration = data.get("duration_s", None)
+            if duration:
+                # convert to ms
+                duration *= 1000
+            
+            filename = data["filename"]
+            if self.__rootdir:
+                filename = os.path.join(self.__rootdir, filename)
+            
+            self.testResult.emit(TestResult(
+                category=category,
+                status=status,
+                name=self.__nodeid2testname(data["nodeid"]),
+                id=data["nodeid"],
+                description="",
+                message=message,
+                extra=extraText.rstrip().splitlines(),
+                duration=duration,
+                filename=filename,
+                lineno=data.get("linenumber", 0) + 1,
+                # pytest reports 0-based line numbers
+            ))
+        
+        # test run finished
+        elif data["event"] == "finished":
+            self.testRunFinished.emit(data["tests"], data["duration_s"])
+    
+    def __normalizeModuleName(self, name):
+        r"""
+        Private method to convert a module name reported by pytest to Python
+        conventions.
+        
+        This method strips the extensions '.pyw' and '.py' first and replaces
+        '/' and '\' thereafter.
+        
+        @param name module name reported by pytest
+        @type str
+        @return module name iaw. Python conventions
+        @rtype str
+        """
+        return (name
+                .replace(".pyw", "")
+                .replace(".py", "")
+                .replace("/", ".")
+                .replace("\\", "."))
+    
+    def __nodeid2testname(self, nodeid):
+        """
+        Private method to convert a nodeid to a test name.
+        
+        @param nodeid nodeid to be converted
+        @type str
+        @return test name
+        @rtype str
+        """
+        module, name = nodeid.split("::", 1)
+        module = self.__normalizeModuleName(module)
+        name = name.replace("::", ".")
+        testname, name = "{0}.{1}".format(module, name).rsplit(".", 1)
+        return "{0} ({1})".format(name, testname)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Testing/Interfaces/PytestRunner.py	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,302 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the test runner script for the 'pytest' framework.
+"""
+
+import json
+import os
+import sys
+import time
+
+sys.path.insert(
+    2,
+    os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
+)
+
+
+class GetPluginVersionsPlugin():
+    """
+    Class implementing a pytest plugin to extract the version info of all
+    installed plugins.
+    """
+    def __init__(self):
+        """
+        Constructor
+        """
+        super().__init__()
+        
+        self.versions = []
+    
+    def pytest_cmdline_main(self, config):
+        """
+        Public method called for performing the main command line action.
+        
+        @param config pytest config object
+        @type Config
+        """
+        pluginInfo = config.pluginmanager.list_plugin_distinfo()
+        if pluginInfo:
+            for _plugin, dist in pluginInfo:
+                self.versions.append({
+                    "name": dist.project_name,
+                    "version": dist.version
+                })
+    
+    def getVersions(self):
+        """
+        Public method to get the assembled list of plugin versions.
+        
+        @return list of collected plugin versions
+        @rtype list of dict
+        """
+        return self.versions
+
+
+class EricPlugin():
+    """
+    Class implementing a pytest plugin which reports the data in a format
+    suitable for the PytestExecutor.
+    """
+    def __init__(self, writer):
+        """
+        Constructor
+        
+        @param writer reference to the object to write the results to
+        @type EricJsonWriter
+        """
+        self.__writer = writer
+        
+        self.__testsRun = 0
+    
+    def __initializeReportData(self):
+        """
+        Private method to initialize attributes for data collection.
+        """
+        self.__status = '---'
+        self.__duration = 0
+        self.__report = []
+        self.__reportPhase = ""
+        self.__sections = []
+        self.__hadError = False
+        self.__wasSkipped = False
+        self.__wasXfail = False
+    
+    def pytest_report_header(self, config, startdir):
+        """
+        Public method called by pytest before any reporting.
+        
+        @param config reference to the configuration object
+        @type Config
+        @param startdir starting directory
+        @type LocalPath
+        """
+        self.__writer.write({
+            "event": "config",
+            "root": str(config.rootdir)
+        })
+    
+    def pytest_collectreport(self, report):
+        """
+        Public method called by pytest after the tests have been collected.
+        
+        @param report reference to the report object
+        @type CollectReport
+        """
+        if report.outcome == "failed":
+            self.__writer.write({
+                "event": "collecterror",
+                "nodeid": report.nodeid,
+                "report": str(report.longrepr),
+            })
+    
+    def pytest_itemcollected(self, item):
+        """
+        Public malled by pytest after a test item has been collected.
+        
+        @param item reference to the collected test item
+        @type Item
+        """
+        self.__writer.write({
+            "event": "collected",
+            "nodeid": item.nodeid,
+            "name": item.name,
+        })
+    
+    def pytest_runtest_logstart(self, nodeid, location):
+        """
+        Public method called by pytest before running a test.
+        
+        @param nodeid node id of the test item
+        @type str
+        @param location tuple containing the file name, the line number and
+            the test name
+        @type tuple of (str, int, str)
+        """
+        self.__testsRun += 1
+        
+        self.__writer.write({
+            "event": "starttest",
+            "nodeid": nodeid,
+        })
+        
+        self.__initializeReportData()
+    
+    def pytest_runtest_logreport(self, report):
+        """
+        Public method called by pytest when a test phase (setup, call and
+            teardown) has been completed.
+        
+        @param report reference to the test report object
+        @type TestReport
+        """
+        if report.when == "call":
+            self.__status = report.outcome
+            self.__duration = report.duration
+        else:
+            if report.outcome == "failed":
+                self.__hadError = True
+            elif report.outcome == "skipped":
+                self.__wasSkipped = True
+        
+        if hasattr(report, "wasxfail"):
+            self.__wasXfail = True
+            self.__report.append(report.wasxfail)
+            self.__reportPhase = report.when
+        
+        self.__sections = report.sections
+        
+        if report.longrepr:
+            self.__reportPhase = report.when
+            if (
+                hasattr(report.longrepr, "reprcrash") and
+                report.longrepr.reprcrash is not None
+            ):
+                self.__report.append(
+                    report.longrepr.reprcrash.message)
+            if isinstance(report.longrepr, tuple):
+                self.__report.append(report.longrepr[2])
+            elif isinstance(report.longrepr, str):
+                self.__report.append(report.longrepr)
+            else:
+                self.__report.append(str(report.longrepr))
+    
+    def pytest_runtest_logfinish(self, nodeid, location):
+        """
+        Public method called by pytest after a test has been completed.
+        
+        @param nodeid node id of the test item
+        @type str
+        @param location tuple containing the file name, the line number and
+            the test name
+        @type tuple of (str, int, str)
+        """
+        if self.__wasXfail:
+            self.__status = (
+                "xpassed"
+                if self.__status == "passed" else
+                "xfailed"
+            )
+        elif self.__wasSkipped:
+            self.__status = "skipped"
+        
+        data = {
+            "event": "result",
+            "status": self.__status,
+            "with_error": self.__hadError,
+            "sections": self.__sections,
+            "duration_s": self.__duration,
+            "nodeid": nodeid,
+            "filename": location[0],
+            "linenumber": location[1],
+            "report_phase": self.__reportPhase,
+        }
+        if self.__report:
+            messageLines = self.__report[0].rstrip().splitlines()
+            data["message"] = messageLines[0]
+        data["report"] = "\n".join(self.__report)
+        
+        self.__writer.write(data)
+    
+    def pytest_sessionstart(self, session):
+        """
+        Public method called by pytest before performing collection and
+        entering the run test loop.
+        
+        @param session reference to the session object
+        @type Session
+        """
+        self.__totalStartTime = time.monotonic_ns()
+        self.__testsRun = 0
+    
+    def pytest_sessionfinish(self, session, exitstatus):
+        """
+        Public method called by pytest after the whole test run finished.
+        
+        @param session reference to the session object
+        @type Session
+        @param exitstatus exit status
+        @type int or ExitCode
+        """
+        stopTime = time.monotonic_ns()
+        duration = (stopTime - self.__totalStartTime) / 1_000_000_000   # s
+        
+        self.__writer.write({
+            "event": "finished",
+            "duration_s": duration,
+            "tests": self.__testsRun,
+        })
+
+
+def getVersions():
+    """
+    Function to determine the framework version and versions of all available
+    plugins.
+    """
+    try:
+        import pytest
+        versions = {
+            "name": "pytest",
+            "version": pytest.__version__,
+            "plugins": [],
+        }
+        
+        # --capture=sys needed on Windows to avoid
+        # ValueError: saved filedescriptor not valid anymore
+        plugin = GetPluginVersionsPlugin()
+        pytest.main(['--version', '--capture=sys'], plugins=[plugin])
+        versions["plugins"] = plugin.getVersions()
+    except ImportError:
+        versions = {}
+    
+    print(json.dumps(versions))
+    sys.exit(0)
+
+
+if __name__ == '__main__':
+    command = sys.argv[1]
+    if command == "installed":
+        try:
+            import pytest           # __IGNORE_WARNING__
+            sys.exit(0)
+        except ImportError:
+            sys.exit(1)
+    
+    elif command == "versions":
+        getVersions()
+    
+    elif command == "runtest":
+        import pytest
+        from EricNetwork.EricJsonStreamWriter import EricJsonWriter
+        writer = EricJsonWriter(sys.argv[2], int(sys.argv[3]))
+        pytest.main(sys.argv[4:], plugins=[EricPlugin(writer)])
+        writer.close()
+        sys.exit(0)
+    
+    sys.exit(42)
+
+#
+# eflag: noqa = M801
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Testing/Interfaces/TestExecutorBase.py	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,279 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the executor base class for the various testing frameworks
+and supporting classes.
+"""
+
+import os
+from dataclasses import dataclass
+from enum import IntEnum
+
+from PyQt6.QtCore import pyqtSignal, QObject, QProcess, QProcessEnvironment
+
+import Preferences
+
+
+class TestResultCategory(IntEnum):
+    """
+    Class defining the supported result categories.
+    """
+    RUNNING = 0
+    FAIL = 1
+    OK = 2
+    SKIP = 3
+    PENDING = 4
+
+
+@dataclass
+class TestResult:
+    """
+    Class containing the test result data.
+    """
+    category: TestResultCategory    # result category
+    status: str                     # test status
+    name: str                       # test name
+    id: str                         # test id
+    description: str = ""           # short description of test
+    message: str = ""               # short result message
+    extra: list = None              # additional information text
+    duration: float = None          # test duration
+    filename: str = None            # file name of a failed test
+    lineno: int = None              # line number of a failed test
+    subtestResult: bool = False     # flag indicating the result of a subtest
+
+
+@dataclass
+class TestConfig:
+    """
+    Class containing the test run configuration.
+    """
+    interpreter: str                # path of the Python interpreter
+    discover: bool                  # auto discovery flag
+    discoveryStart: str             # start directory for auto discovery
+    testFilename: str               # name of the test script
+    testName: str                   # name of the test function
+    failFast: bool                  # stop on first fail
+    failedOnly: bool                # run failed tests only
+    collectCoverage: bool           # coverage collection flag
+    eraseCoverage: bool             # erase coverage data first
+    coverageFile: str               # name of the coverage data file
+
+
+class TestExecutorBase(QObject):
+    """
+    Base class for test framework specific implementations.
+    
+    @signal collected(list of tuple of (str, str, str)) emitted after all tests
+        have been collected. Tuple elements are the test id, the test name and
+        a short description of the test.
+    @signal collectError(list of tuple of (str, str)) emitted when errors
+        are encountered during test collection. Tuple elements are the
+        test name and the error message.
+    @signal startTest(tuple of (str, str, str) emitted before tests are run.
+        Tuple elements are test id, test name and short description.
+    @signal testResult(TestResult) emitted when a test result is ready
+    @signal testFinished(list, str) emitted when the test has finished.
+        The elements are the list of test results and the captured output
+        of the test worker (if any).
+    @signal testRunAboutToBeStarted() emitted just before the test run will
+        be started.
+    @signal testRunFinished(int, float) emitted when the test run has finished.
+        The elements are the number of tests run and the duration in seconds
+    @signal stop() emitted when the test process is being stopped.
+    @signal coverageDataSaved(str) emitted after the coverage data was saved.
+        The element is the absolute path of the coverage data file.
+    """
+    collected = pyqtSignal(list)
+    collectError = pyqtSignal(list)
+    startTest = pyqtSignal(tuple)
+    testResult = pyqtSignal(TestResult)
+    testFinished = pyqtSignal(list, str)
+    testRunAboutToBeStarted = pyqtSignal()
+    testRunFinished = pyqtSignal(int, float)
+    stop = pyqtSignal()
+    coverageDataSaved = pyqtSignal(str)
+    
+    module = ""
+    name = ""
+    runner = ""
+    
+    def __init__(self, testWidget):
+        """
+        Constructor
+        
+        @param testWidget reference to the unit test widget
+        @type TestingWidget
+        """
+        super().__init__(testWidget)
+        
+        self.__process = None
+    
+    @classmethod
+    def isInstalled(cls, interpreter):
+        """
+        Class method to check whether a test framework is installed.
+        
+        The test is performed by checking, if a module loader can found.
+        
+        @param interpreter interpreter to be used for the test
+        @type str
+        @return flag indicating the test framework module is installed
+        @rtype bool
+        """
+        if cls.runner:
+            proc = QProcess()
+            proc.start(interpreter, [cls.runner, "installed"])
+            if proc.waitForFinished(3000):
+                exitCode = proc.exitCode()
+                return exitCode == 0
+        
+        return False
+    
+    def getVersions(self, interpreter):
+        """
+        Public method to get the test framework version and version information
+        of its installed plugins.
+        
+        @param interpreter interpreter to be used for the test
+        @type str
+        @return dictionary containing the framework name and version and the
+            list of available plugins with name and version each
+        @rtype dict
+        @exception NotImplementedError this method needs to be implemented by
+            derived classes
+        """
+        raise NotImplementedError
+        
+        return {}
+    
+    def hasCoverage(self, interpreter):
+        """
+        Public method to get the test framework version and version information
+        of its installed plugins.
+        
+        @param interpreter interpreter to be used for the test
+        @type str
+        @return flag indicating the availability of coverage functionality
+        @rtype bool
+        @exception NotImplementedError this method needs to be implemented by
+            derived classes
+        """
+        raise NotImplementedError
+        
+        return False
+    
+    def createArguments(self, config):
+        """
+        Public method to create the arguments needed to start the test process.
+        
+        @param config configuration for the test execution
+        @type TestConfig
+        @return list of process arguments
+        @rtype list of str
+        @exception NotImplementedError this method needs to be implemented by
+            derived classes
+        """
+        raise NotImplementedError
+        
+        return []
+    
+    def _prepareProcess(self, workDir, pythonpath):
+        """
+        Protected method to prepare a process object to be started.
+        
+        @param workDir working directory
+        @type str
+        @param pythonpath list of directories to be added to the Python path
+        @type list of str
+        @return prepared process object
+        @rtype QProcess
+        """
+        process = QProcess(self)
+        process.setProcessChannelMode(
+            QProcess.ProcessChannelMode.MergedChannels)
+        process.setWorkingDirectory(workDir)
+        process.finished.connect(self.finished)
+        if pythonpath:
+            env = QProcessEnvironment.systemEnvironment()
+            currentPythonPath = env.value('PYTHONPATH', None)
+            newPythonPath = os.pathsep.join(pythonpath)
+            if currentPythonPath:
+                newPythonPath += os.pathsep + currentPythonPath
+            env.insert('PYTHONPATH', newPythonPath)
+            process.setProcessEnvironment(env)
+        
+        return process
+    
+    def start(self, config, pythonpath):
+        """
+        Public method to start the testing process.
+        
+        @param config configuration for the test execution
+        @type TestConfig
+        @param pythonpath list of directories to be added to the Python path
+        @type list of str
+        @exception RuntimeError raised if the the testing process did not start
+        """
+        workDir = (
+            config.discoveryStart
+            if config.discover else
+            os.path.dirname(config.testFilename)
+        )
+        self.__process = self._prepareProcess(workDir, pythonpath)
+        testArgs = self.createArguments(config)
+        self.testRunAboutToBeStarted.emit()
+        self.__process.start(config.interpreter, testArgs)
+        running = self.__process.waitForStarted()
+        if not running:
+            raise RuntimeError
+    
+    def finished(self):
+        """
+        Public method handling the unit test process been finished.
+        
+        This method should read the results (if necessary) and emit the signal
+        testFinished.
+        
+        @exception NotImplementedError this method needs to be implemented by
+            derived classes
+        """
+        raise NotImplementedError
+    
+    def readAllOutput(self, process=None):
+        """
+        Public method to read all output of the test process.
+        
+        @param process reference to the process object
+        @type QProcess
+        @return test process output
+        @rtype str
+        """
+        if process is None:
+            process = self.__process
+        output = (
+            str(process.readAllStandardOutput(),
+                Preferences.getSystem("IOEncoding"),
+                'replace').strip()
+            if process else
+            ""
+        )
+        return output
+    
+    def stopIfRunning(self):
+        """
+        Public method to stop the testing process, if it is running.
+        """
+        if (
+            self.__process and
+            self.__process.state() == QProcess.ProcessState.Running
+        ):
+            self.__process.terminate()
+            self.__process.waitForFinished(2000)
+            self.__process.kill()
+            self.__process.waitForFinished(3000)
+            
+            self.stop.emit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Testing/Interfaces/TestFrameworkRegistry.py	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a simple registry containing the available test framework
+interfaces.
+"""
+
+import copy
+
+
+class TestFrameworkRegistry():
+    """
+    Class implementing a simple registry of test framework interfaces.
+    
+    The test executor for a framework is responsible for running the tests,
+    receiving the results and preparing them for display. It must implement
+    the interface of TestExecutorBase.
+
+    Frameworks must first be registered using '.register()'. This registry
+    can then create the assoicated test executor when '.createExecutor()' is
+    called.
+    """
+    def __init__(self):
+        """
+        Constructor
+        """
+        self.__frameworks = {}
+    
+    def register(self, executorClass):
+        """
+        Public method to register a test framework executor.
+        
+        @param executorClass class implementing the test framework executor
+        @type TestExecutorBase
+        """
+        self.__frameworks[executorClass.name] = executorClass
+    
+    def createExecutor(self, framework, widget):
+        """
+        Public method to create a test framework executor.
+        
+        Note: The executor classes have to be registered first.
+        
+        @param framework name of the test framework
+        @type str
+        @param widget reference to the unit test widget
+        @type TestingWidget
+        @return test framework executor object
+        @rtype TestExecutorBase
+        """
+        cls = self.__frameworks[framework]
+        return cls(widget)
+    
+    def getFrameworks(self):
+        """
+        Public method to get a copy of the registered frameworks.
+        
+        @return  copy of the registered frameworks
+        @rtype dict
+        """
+        return copy.copy(self.__frameworks)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Testing/Interfaces/UnittestExecutor.py	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,228 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the executor for the standard 'unittest' framework.
+"""
+
+import contextlib
+import json
+import os
+import re
+
+from PyQt6.QtCore import pyqtSlot, QProcess
+
+from EricNetwork.EricJsonStreamReader import EricJsonReader
+
+from .TestExecutorBase import TestExecutorBase, TestResult, TestResultCategory
+
+
+class UnittestExecutor(TestExecutorBase):
+    """
+    Class implementing the executor for the standard 'unittest' framework.
+    """
+    module = "unittest"
+    name = "unittest"
+    
+    runner = os.path.join(os.path.dirname(__file__), "UnittestRunner.py")
+    
+    def __init__(self, testWidget):
+        """
+        Constructor
+        
+        @param testWidget reference to the unit test widget
+        @type TestingWidget
+        """
+        super().__init__(testWidget)
+        
+        self.__statusCategoryMapping = {
+            "failure": TestResultCategory.FAIL,
+            "error": TestResultCategory.FAIL,
+            "skipped": TestResultCategory.SKIP,
+            "expected failure": TestResultCategory.OK,
+            "unexpected success": TestResultCategory.FAIL,
+            "success": TestResultCategory.OK,
+        }
+        
+        self.__statusDisplayMapping = {
+            "failure": self.tr("Failure"),
+            "error": self.tr("Error"),
+            "skipped": self.tr("Skipped"),
+            "expected failure": self.tr("Expected Failure"),
+            "unexpected success": self.tr("Unexpected Success"),
+            "success": self.tr("Success"),
+        }
+        
+        self.__testWidget = testWidget
+    
+    def getVersions(self, interpreter):
+        """
+        Public method to get the test framework version and version information
+        of its installed plugins.
+        
+        @param interpreter interpreter to be used for the test
+        @type str
+        @return dictionary containing the framework name and version and the
+            list of available plugins with name and version each
+        @rtype dict
+        """
+        proc = QProcess()
+        proc.start(interpreter, [UnittestExecutor.runner, "versions"])
+        if proc.waitForFinished(3000):
+            exitCode = proc.exitCode()
+            if exitCode == 0:
+                versionsStr = self.readAllOutput(proc)
+                with contextlib.suppress(json.JSONDecodeError):
+                    return json.loads(versionsStr)
+        
+        return {}
+    
+    def hasCoverage(self, interpreter):
+        """
+        Public method to get the test framework version and version information
+        of its installed plugins.
+        
+        @param interpreter interpreter to be used for the test
+        @type str
+        @return flag indicating the availability of coverage functionality
+        @rtype bool
+        """
+        return True
+    
+    def createArguments(self, config):
+        """
+        Public method to create the arguments needed to start the test process.
+        
+        @param config configuration for the test execution
+        @type TestConfig
+        @return list of process arguments
+        @rtype list of str
+        """
+        args = [
+            UnittestExecutor.runner,
+            "runtest",
+            self.reader.address(),
+            str(self.reader.port()),
+        ]
+        
+        if config.discover:
+            args.extend([
+                "discover",
+                "--start-directory",
+                config.discoveryStart,
+            ])
+        
+        if config.failFast:
+            args.append("--failfast")
+        
+        if config.collectCoverage:
+            args.append("--cover")
+            if config.eraseCoverage:
+                args.append("--cover-erase")
+            if config.coverageFile:
+                args.append("--cover-file")
+                args.append(config.coverageFile)
+        
+        if config.failedOnly:
+            args.append("--failed-only")
+            if config.testFilename:
+                args.append(config.testFilename)
+            args.extend(self.__testWidget.getFailedTests())
+        elif config.testFilename:
+            args.append(config.testFilename)
+            args.append(config.testName if config.testName else "suite")
+        
+        return args
+    
+    def start(self, config, pythonpath):
+        """
+        Public method to start the testing process.
+        
+        @param config configuration for the test execution
+        @type TestConfig
+        @param pythonpath list of directories to be added to the Python path
+        @type list of str
+        """
+        self.reader = EricJsonReader(name="Unittest Reader", parent=self)
+        self.reader.dataReceived.connect(self.__processData)
+        
+        super().start(config, pythonpath)
+    
+    def finished(self):
+        """
+        Public method handling the unit test process been finished.
+        
+        This method should read the results (if necessary) and emit the signal
+        testFinished.
+        """
+        self.reader.close()
+        
+        output = self.readAllOutput()
+        self.testFinished.emit([], output)
+    
+    @pyqtSlot(object)
+    def __processData(self, data):
+        """
+        Private slot to process the received data.
+        
+        @param data data object received
+        @type dict
+        """
+        # error collecting tests
+        if data["event"] == "collecterror":
+            self.collectError.emit([("", data["error"])])
+        
+        # tests collected
+        elif data["event"] == "collected":
+            self.collected.emit([
+                (t["id"], t["name"], t["description"]) for t in data["tests"]
+            ])
+        
+        # test started
+        elif data["event"] == "started":
+            self.startTest.emit(
+                (data["id"], data["name"], data["description"])
+            )
+        
+        # test result
+        elif data["event"] == "result":
+            filename, lineno = None, None
+            tracebackLines = data.get("traceback", "").splitlines()
+            if tracebackLines:
+                # find the last entry matching the pattern
+                for index in range(len(tracebackLines) - 1, -1, -1):
+                    fmatch = re.search(r'File "(.*?)", line (\d*?),.*',
+                                       tracebackLines[index])
+                    if fmatch:
+                        break
+                if fmatch:
+                    filename = fmatch.group(1)
+                    lineno = int(fmatch.group(2))
+                
+            message = data.get("shortmsg", "")
+            if not message and tracebackLines:
+                message = tracebackLines[-1].split(":", 1)[1].strip()
+            
+            self.testResult.emit(TestResult(
+                category=self.__statusCategoryMapping[data["status"]],
+                status=self.__statusDisplayMapping[data["status"]],
+                name=data["name"],
+                id=data["id"],
+                description=data["description"],
+                message=message,
+                extra=tracebackLines,
+                duration=data.get("duration_ms", None),
+                filename=filename,
+                lineno=lineno,
+                subtestResult=data.get("subtest", False)
+            ))
+        
+        # test run finished
+        elif data["event"] == "finished":
+            self.testRunFinished.emit(data["tests"], data["duration_s"])
+        
+        # coverage data
+        elif data["event"] == "coverage":
+            self.coverageDataSaved.emit(data["file"])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Testing/Interfaces/UnittestRunner.py	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,434 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the test runner script for the 'unittest' framework.
+"""
+
+import json
+import os
+import sys
+import time
+import unittest
+
+sys.path.insert(
+    2,
+    os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
+)
+
+
+class EricTestResult(unittest.TestResult):
+    """
+    Class implementing a TestResult derivative to send the data via a network
+    connection.
+    """
+    def __init__(self, writer, failfast):
+        """
+        Constructor
+        
+        @param writer reference to the object to write the results to
+        @type EricJsonWriter
+        @param failfast flag indicating to stop at the first error
+        @type bool
+        """
+        super().__init__()
+        self.__writer = writer
+        self.failfast = failfast
+        self.__testsRun = 0
+        
+        self.__currentTestStatus = {}
+    
+    def addFailure(self, test, err):
+        """
+        Public method called if a test failed.
+        
+        @param test reference to the test object
+        @type TestCase
+        @param err tuple containing the exception data like sys.exc_info
+            (exception type, exception instance, traceback)
+        @type tuple
+        """
+        super().addFailure(test, err)
+        tracebackLines = self._exc_info_to_string(err, test)
+        
+        self.__currentTestStatus.update({
+            "status": "failure",
+            "traceback": tracebackLines,
+        })
+    
+    def addError(self, test, err):
+        """
+        Public method called if a test errored.
+        
+        @param test reference to the test object
+        @type TestCase
+        @param err tuple containing the exception data like sys.exc_info
+            (exception type, exception instance, traceback)
+        @type tuple
+        """
+        super().addError(test, err)
+        tracebackLines = self._exc_info_to_string(err, test)
+        
+        self.__currentTestStatus.update({
+            "status": "error",
+            "traceback": tracebackLines,
+        })
+    
+    def addSkip(self, test, reason):
+        """
+        Public method called if a test was skipped.
+        
+        @param test reference to the test object
+        @type TestCase
+        @param reason reason for skipping the test
+        @type str
+        """
+        super().addSkip(test, reason)
+        
+        self.__currentTestStatus.update({
+            "status": "skipped",
+            "shortmsg": reason,
+        })
+    
+    def addExpectedFailure(self, test, err):
+        """
+        Public method called if a test failed expected.
+        
+        @param test reference to the test object
+        @type TestCase
+        @param err tuple containing the exception data like sys.exc_info
+            (exception type, exception instance, traceback)
+        @type tuple
+        """
+        super().addExpectedFailure(test, err)
+        tracebackLines = self._exc_info_to_string(err, test)
+        
+        self.__currentTestStatus.update({
+            "status": "expected failure",
+            "traceback": tracebackLines,
+        })
+    
+    def addUnexpectedSuccess(self, test):
+        """
+        Public method called if a test succeeded expectedly.
+        
+        @param test reference to the test object
+        @type TestCase
+        """
+        super().addUnexpectedSuccess(test)
+        
+        self.__currentTestStatus["status"] = "unexpected success"
+    
+    def addSubTest(self, test, subtest, err):
+        """
+        Public method called for each subtest to record its result.
+        
+        @param test reference to the test object
+        @type TestCase
+        @param subtest reference to the subtest object
+        @type TestCase
+        @param err tuple containing the exception data like sys.exc_info
+            (exception type, exception instance, traceback)
+        @type tuple
+        """
+        if err is not None:
+            super().addSubTest(test, subtest, err)
+            tracebackLines = self._exc_info_to_string(err, test)
+            status = (
+                "failure"
+                if issubclass(err[0], test.failureException) else
+                "error"
+            )
+            
+            # record the last subtest fail status as the overall status
+            self.__currentTestStatus["status"] = status
+            
+            self.__writer.write({
+                "event": "result",
+                "status": status,
+                "name": str(subtest),
+                "id": subtest.id(),
+                "description": subtest.shortDescription(),
+                "traceback": tracebackLines,
+                "subtest": True,
+            })
+            
+            if self.failfast:
+                self.stop()
+        else:
+            self.__writer.write({
+                "event": "result",
+                "status": "success",
+                "name": str(subtest),
+                "id": subtest.id(),
+                "description": subtest.shortDescription(),
+                "subtest": True,
+            })
+    
+    def startTest(self, test):
+        """
+        Public method called at the start of a test.
+        
+        @param test reference to the test object
+        @type TestCase
+        """
+        super().startTest(test)
+        
+        self.__testsRun += 1
+        self.__currentTestStatus = {
+            "event": "result",
+            "status": "success",
+            "name": str(test),
+            "id": test.id(),
+            "description": test.shortDescription(),
+            "subtest": False,
+        }
+        
+        self.__writer.write({
+            "event": "started",
+            "name": str(test),
+            "id": test.id(),
+            "description": test.shortDescription(),
+        })
+        
+        self.__startTime = time.monotonic_ns()
+    
+    def stopTest(self, test):
+        """
+        Public method called at the end of a test.
+        
+        @param test reference to the test object
+        @type TestCase
+        """
+        stopTime = time.monotonic_ns()
+        duration = (stopTime - self.__startTime) / 1_000_000     # ms
+        
+        super().stopTest(test)
+        
+        self.__currentTestStatus["duration_ms"] = duration
+        self.__writer.write(self.__currentTestStatus)
+    
+    def startTestRun(self):
+        """
+        Public method called once before any tests are executed.
+        """
+        self.__totalStartTime = time.monotonic_ns()
+        self.__testsRun = 0
+    
+    def stopTestRun(self):
+        """
+        Public method called once after all tests are executed.
+        """
+        stopTime = time.monotonic_ns()
+        duration = (stopTime - self.__totalStartTime) / 1_000_000_000   # s
+        
+        self.__writer.write({
+            "event": "finished",
+            "duration_s": duration,
+            "tests": self.__testsRun,
+        })
+
+
+def _assembleTestCasesList(suite):
+    """
+    Protected function to assemble a list of test cases included in a test
+    suite.
+    
+    @param suite test suite to be inspected
+    @type unittest.TestSuite
+    @return list of tuples containing the test case ID, the string
+        representation and the short description
+    @rtype list of tuples of (str, str)
+    """
+    testCases = []
+    for test in suite:
+        if isinstance(test, unittest.TestSuite):
+            testCases.extend(_assembleTestCasesList(test))
+        else:
+            testId = test.id()
+            if (
+                "ModuleImportFailure" not in testId and
+                "LoadTestsFailure" not in testId and
+                "_FailedTest" not in testId
+            ):
+                testCases.append(
+                    (testId, str(test), test.shortDescription())
+                )
+    return testCases
+
+
+def runtest(argv):
+    """
+    Function to run the tests.
+    
+    @param argv list of command line parameters.
+    @type list of str
+    """
+    from EricNetwork.EricJsonStreamWriter import EricJsonWriter
+    writer = EricJsonWriter(argv[0], int(argv[1]))
+    del argv[:2]
+    
+    # process arguments
+    if argv[0] == "discover":
+        discover = True
+        argv.pop(0)
+        if argv[0] == "--start-directory":
+            discoveryStart = argv[1]
+            del argv[:2]
+    else:
+        discover = False
+        discoveryStart = ""
+    
+    failfast = "--failfast" in argv
+    if failfast:
+        argv.remove("--failfast")
+    
+    collectCoverage = "--cover" in argv
+    if collectCoverage:
+        argv.remove("--cover")
+    coverageErase = "--cover-erase" in argv
+    if coverageErase:
+        argv.remove("--cover-erase")
+    if "--cover-file" in argv:
+        index = argv.index("--cover-file")
+        covDataFile = argv[index + 1]
+        del argv[index:index + 2]
+    else:
+        covDataFile = ""
+    
+    if argv and argv[0] == "--failed-only":
+        if discover:
+            testFileName = ""
+            failed = argv[1:]
+        else:
+            testFileName = argv[1]
+            failed = argv[2:]
+    else:
+        failed = []
+        if discover:
+            testFileName = testName = ""
+        else:
+            testFileName, testName = argv[:2]
+            del argv[:2]
+        
+        testCases = argv[:]
+    
+    if testFileName:
+        sys.path.insert(1, os.path.dirname(os.path.abspath(testFileName)))
+    elif discoveryStart:
+        sys.path.insert(1, os.path.abspath(discoveryStart))
+    
+    # setup test coverage
+    if collectCoverage:
+        if not covDataFile:
+            if discover:
+                covname = os.path.join(discoveryStart, "test")
+            elif testFileName:
+                covname = os.path.splitext(
+                    os.path.abspath(testFileName))[0]
+            else:
+                covname = "test"
+            covDataFile = "{0}.coverage".format(covname)
+        if not os.path.isabs(covDataFile):
+            covDataFile = os.path.abspath(covDataFile)
+        
+        sys.path.insert(
+            2,
+            os.path.abspath(os.path.join(
+                os.path.dirname(__file__), "..", "..", "DebugClients", "Python"
+            ))
+        )
+        from DebugClients.Python.coverage import Coverage
+        cover = Coverage(data_file=covDataFile)
+        if coverageErase:
+            cover.erase()
+        cover.start()
+    else:
+        cover = None
+    
+    try:
+        testLoader = unittest.TestLoader()
+        if discover and not failed:
+            if testCases:
+                test = testLoader.loadTestsFromNames(testCases)
+            else:
+                test = testLoader.discover(discoveryStart)
+        else:
+            if testFileName:
+                module = __import__(os.path.splitext(
+                    os.path.basename(testFileName))[0])
+            else:
+                module = None
+            if failed:
+                if module:
+                    failed = [t.split(".", 1)[1]
+                              for t in failed]
+                test = testLoader.loadTestsFromNames(
+                    failed, module)
+            else:
+                test = testLoader.loadTestsFromName(
+                    testName, module)
+    except Exception as err:
+        print("Exception:", str(err))
+        writer.write({
+            "event": "collecterror",
+            "error": str(err),
+        })
+        sys.exit(1)
+    
+    collectedTests = {
+        "event": "collected",
+        "tests": [
+            {"id": id, "name": name, "description": desc}
+            for id, name, desc in _assembleTestCasesList(test)
+        ]
+    }
+    writer.write(collectedTests)
+    
+    testResult = EricTestResult(writer, failfast)
+    startTestRun = getattr(testResult, 'startTestRun', None)
+    if startTestRun is not None:
+        startTestRun()
+    try:
+        test.run(testResult)
+    finally:
+        if cover:
+            cover.stop()
+            cover.save()
+            writer.write({
+                "event": "coverage",
+                "file": covDataFile,
+            })
+        stopTestRun = getattr(testResult, 'stopTestRun', None)
+        if stopTestRun is not None:
+            stopTestRun()
+    
+    writer.close()
+    sys.exit(0)
+
+if __name__ == '__main__':
+    if len(sys.argv) > 1:
+        command = sys.argv[1]
+        if command == "installed":
+            sys.exit(0)
+        
+        elif command == "versions":
+            import platform
+            versions = {
+                "name": "unittest",
+                "version": platform.python_version(),
+                "plugins": [],
+            }
+            print(json.dumps(versions))
+            sys.exit(0)
+        
+        elif command == "runtest":
+            runtest(sys.argv[2:])
+            sys.exit(0)
+    
+    sys.exit(42)
+
+#
+# eflag: noqa = M801
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Testing/Interfaces/__init__.py	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package containg the various test framework interfaces.
+"""
+
+from .PytestExecutor import PytestExecutor
+from .UnittestExecutor import UnittestExecutor
+
+Frameworks = (
+    UnittestExecutor,
+    PytestExecutor,
+)
+
+FrameworkNames = {
+    "MicroPython": (
+        UnittestExecutor.name,
+        PytestExecutor.name,
+    ),
+    "Python3": (
+        UnittestExecutor.name,
+        PytestExecutor.name,
+    ),
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Testing/TestResultsTree.py	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,611 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a tree view and associated model to show the test result
+data.
+"""
+
+import contextlib
+import copy
+import locale
+
+from collections import Counter
+from operator import attrgetter
+
+from PyQt6.QtCore import (
+    pyqtSignal, pyqtSlot, Qt, QAbstractItemModel, QCoreApplication,
+    QModelIndex, QPoint
+)
+from PyQt6.QtGui import QBrush, QColor
+from PyQt6.QtWidgets import QMenu, QTreeView
+
+from EricWidgets.EricApplication import ericApp
+
+import Preferences
+
+from .Interfaces.TestExecutorBase import TestResultCategory
+
+TopLevelId = 2 ** 32 - 1
+
+
+class TestResultsModel(QAbstractItemModel):
+    """
+    Class implementing the item model containing the test data.
+    
+    @signal summary(str) emitted whenever the model data changes. The element
+        is a summary of the test results of the model.
+    """
+    summary = pyqtSignal(str)
+    
+    Headers = [
+        QCoreApplication.translate("TestResultsModel", "Status"),
+        QCoreApplication.translate("TestResultsModel", "Name"),
+        QCoreApplication.translate("TestResultsModel", "Message"),
+        QCoreApplication.translate("TestResultsModel", "Duration [ms]"),
+    ]
+    
+    StatusColumn = 0
+    NameColumn = 1
+    MessageColumn = 2
+    DurationColumn = 3
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent object (defaults to None)
+        @type QObject (optional)
+        """
+        super().__init__(parent)
+        
+        if ericApp().usesDarkPalette():
+            self.__backgroundColors = {
+                TestResultCategory.RUNNING: None,
+                TestResultCategory.FAIL: QBrush(QColor("#880000")),
+                TestResultCategory.OK: QBrush(QColor("#005500")),
+                TestResultCategory.SKIP: QBrush(QColor("#3f3f3f")),
+                TestResultCategory.PENDING: QBrush(QColor("#004768")),
+            }
+        else:
+            self.__backgroundColors = {
+                TestResultCategory.RUNNING: None,
+                TestResultCategory.FAIL: QBrush(QColor("#ff8080")),
+                TestResultCategory.OK: QBrush(QColor("#c1ffba")),
+                TestResultCategory.SKIP: QBrush(QColor("#c5c5c5")),
+                TestResultCategory.PENDING: QBrush(QColor("#6fbaff")),
+            }
+        
+        self.__testResults = []
+    
+    def index(self, row, column, parent=QModelIndex()):
+        """
+        Public method to generate an index for the given row and column to
+        identify the item.
+        
+        @param row row for the index
+        @type int
+        @param column column for the index
+        @type int
+        @param parent index of the parent item (defaults to QModelIndex())
+        @type QModelIndex (optional)
+        @return index for the item
+        @rtype QModelIndex
+        """
+        if not self.hasIndex(row, column, parent):  # check bounds etc.
+            return QModelIndex()
+        
+        if not parent.isValid():
+            # top level item
+            return self.createIndex(row, column, TopLevelId)
+        else:
+            testResultIndex = parent.row()
+            return self.createIndex(row, column, testResultIndex)
+    
+    def data(self, index, role):
+        """
+        Public method to get the data for the various columns and roles.
+        
+        @param index index of the data to be returned
+        @type QModelIndex
+        @param role role designating the data to return
+        @type Qt.ItemDataRole
+        @return requested data item
+        @rtype Any
+        """
+        if not index.isValid():
+            return None
+        
+        row = index.row()
+        column = index.column()
+        idx = index.internalId()
+        
+        if role == Qt.ItemDataRole.DisplayRole:
+            if idx != TopLevelId:
+                if bool(self.__testResults[idx].extra):
+                    return self.__testResults[idx].extra[index.row()]
+                else:
+                    return None
+            elif column == TestResultsModel.StatusColumn:
+                return self.__testResults[row].status
+            elif column == TestResultsModel.NameColumn:
+                return self.__testResults[row].name
+            elif column == TestResultsModel.MessageColumn:
+                return self.__testResults[row].message
+            elif column == TestResultsModel.DurationColumn:
+                duration = self.__testResults[row].duration
+                return (
+                    ""
+                    if duration is None else
+                    locale.format_string("%.2f", duration, grouping=True)
+                )
+        elif role == Qt.ItemDataRole.ToolTipRole:
+            if idx == TopLevelId and column == TestResultsModel.NameColumn:
+                return self.__testResults[row].name
+        elif role == Qt.ItemDataRole.FontRole:
+            if idx != TopLevelId:
+                return Preferences.getEditorOtherFonts("MonospacedFont")
+        elif role == Qt.ItemDataRole.BackgroundRole:
+            if idx == TopLevelId:
+                testResult = self.__testResults[row]
+                with contextlib.suppress(KeyError):
+                    return self.__backgroundColors[testResult.category]
+        elif role == Qt.ItemDataRole.TextAlignmentRole:
+            if idx == TopLevelId and column == TestResultsModel.DurationColumn:
+                return Qt.AlignmentFlag.AlignRight
+        elif role == Qt.ItemDataRole.UserRole:      # __IGNORE_WARNING_Y102__
+            if idx == TopLevelId:
+                testresult = self.__testResults[row]
+                return (testresult.filename, testresult.lineno)
+        
+        return None
+    
+    def headerData(self, section, orientation,
+                   role=Qt.ItemDataRole.DisplayRole):
+        """
+        Public method to get the header string for the various sections.
+        
+        @param section section number
+        @type int
+        @param orientation orientation of the header
+        @type Qt.Orientation
+        @param role data role (defaults to Qt.ItemDataRole.DisplayRole)
+        @type Qt.ItemDataRole (optional)
+        @return header string of the section
+        @rtype str
+        """
+        if (
+            orientation == Qt.Orientation.Horizontal and
+            role == Qt.ItemDataRole.DisplayRole
+        ):
+            return TestResultsModel.Headers[section]
+        else:
+            return None
+    
+    def parent(self, index):
+        """
+        Public method to get the parent of the item pointed to by index.
+        
+        @param index index of the item
+        @type QModelIndex
+        @return index of the parent item
+        @rtype QModelIndex
+        """
+        if not index.isValid():
+            return QModelIndex()
+        
+        idx = index.internalId()
+        if idx == TopLevelId:
+            return QModelIndex()
+        else:
+            return self.index(idx, 0)
+    
+    def rowCount(self, parent=QModelIndex()):
+        """
+        Public method to get the number of row for a given parent index.
+        
+        @param parent index of the parent item (defaults to QModelIndex())
+        @type QModelIndex (optional)
+        @return number of rows
+        @rtype int
+        """
+        if not parent.isValid():
+            return len(self.__testResults)
+        
+        if (
+            parent.internalId() == TopLevelId and
+            parent.column() == 0 and
+            self.__testResults[parent.row()].extra is not None
+        ):
+            return len(self.__testResults[parent.row()].extra)
+        
+        return 0
+
+    def columnCount(self, parent=QModelIndex()):
+        """
+        Public method to get the number of columns.
+        
+        @param parent index of the parent item (defaults to QModelIndex())
+        @type QModelIndex (optional)
+        @return number of columns
+        @rtype int
+        """
+        if not parent.isValid():
+            return len(TestResultsModel.Headers)
+        else:
+            return 1
+    
+    def clear(self):
+        """
+        Public method to clear the model data.
+        """
+        self.beginResetModel()
+        self.__testResults.clear()
+        self.endResetModel()
+        
+        self.summary.emit("")
+    
+    def sort(self, column, order):
+        """
+        Public method to sort the model data by column in order.
+        
+        @param column sort column number
+        @type int
+        @param order sort order
+        @type Qt.SortOrder
+        """             # __IGNORE_WARNING_D234r__
+        def durationKey(result):
+            """
+            Function to generate a key for duration sorting
+            
+            @param result result object
+            @type TestResult
+            @return sort key
+            @rtype float
+            """
+            return result.duration or -1.0
+
+        self.beginResetModel()
+        reverse = order == Qt.SortOrder.DescendingOrder
+        if column == TestResultsModel.StatusColumn:
+            self.__testResults.sort(key=attrgetter('category', 'status'),
+                                    reverse=reverse)
+        elif column == TestResultsModel.NameColumn:
+            self.__testResults.sort(key=attrgetter('name'), reverse=reverse)
+        elif column == TestResultsModel.MessageColumn:
+            self.__testResults.sort(key=attrgetter('message'), reverse=reverse)
+        elif column == TestResultsModel.DurationColumn:
+            self.__testResults.sort(key=durationKey, reverse=reverse)
+        self.endResetModel()
+    
+    def getTestResults(self):
+        """
+        Public method to get the list of test results managed by the model.
+        
+        @return list of test results managed by the model
+        @rtype list of TestResult
+        """
+        return copy.deepcopy(self.__testResults)
+    
+    def setTestResults(self, testResults):
+        """
+        Public method to set the list of test results of the model.
+        
+        @param testResults test results to be managed by the model
+        @type list of TestResult
+        """
+        self.beginResetModel()
+        self.__testResults = copy.deepcopy(testResults)
+        self.endResetModel()
+        
+        self.summary.emit(self.__summary())
+    
+    def addTestResults(self, testResults):
+        """
+        Public method to add test results to the ones already managed by the
+        model.
+        
+        @param testResults test results to be added to the model
+        @type list of TestResult
+        """
+        firstRow = len(self.__testResults)
+        lastRow = firstRow + len(testResults) - 1
+        self.beginInsertRows(QModelIndex(), firstRow, lastRow)
+        self.__testResults.extend(testResults)
+        self.endInsertRows()
+        
+        self.summary.emit(self.__summary())
+    
+    def updateTestResults(self, testResults):
+        """
+        Public method to update the data of managed test result items.
+        
+        @param testResults test results to be updated
+        @type list of TestResult
+        """
+        minIndex = None
+        maxIndex = None
+        
+        testResultsToBeAdded = []
+        
+        for testResult in testResults:
+            for (index, currentResult) in enumerate(self.__testResults):
+                if currentResult.id == testResult.id:
+                    self.__testResults[index] = testResult
+                    if minIndex is None:
+                        minIndex = index
+                        maxIndex = index
+                    else:
+                        minIndex = min(minIndex, index)
+                        maxIndex = max(maxIndex, index)
+                    
+                    break
+            else:
+                # Test result with given id was not found.
+                # Just add it to the list (could be a sub test)
+                testResultsToBeAdded.append(testResult)
+        
+        if minIndex is not None:
+            self.dataChanged.emit(
+                self.index(minIndex, 0),
+                self.index(maxIndex, len(TestResultsModel.Headers) - 1)
+            )
+            
+            self.summary.emit(self.__summary())
+        
+        if testResultsToBeAdded:
+            self.addTestResults(testResultsToBeAdded)
+    
+    def getFailedTests(self):
+        """
+        Public method to extract the test ids of all failed tests.
+        
+        @return test ids of all failed tests
+        @rtype list of str
+        """
+        failedIds = [
+            res.id for res in self.__testResults if (
+                res.category == TestResultCategory.FAIL and
+                not res.subtestResult
+            )
+        ]
+        return failedIds
+    
+    def __summary(self):
+        """
+        Private method to generate a test results summary text.
+        
+        @return test results summary text
+        @rtype str
+        """
+        if len(self.__testResults) == 0:
+            return self.tr("No results to show")
+        
+        counts = Counter(res.category for res in self.__testResults)
+        if all(
+            counts[category] == 0
+            for category in (TestResultCategory.FAIL, TestResultCategory.OK,
+                             TestResultCategory.SKIP)
+        ):
+            return self.tr("Collected %n test(s)", "", len(self.__testResults))
+        
+        return self.tr(
+            "%n test(s)/subtest(s) total, {0} failed, {1} passed,"
+            " {2} skipped, {3} pending",
+            "", len(self.__testResults)
+        ).format(
+            counts[TestResultCategory.FAIL],
+            counts[TestResultCategory.OK],
+            counts[TestResultCategory.SKIP],
+            counts[TestResultCategory.PENDING]
+        )
+
+
+class TestResultsTreeView(QTreeView):
+    """
+    Class implementing a tree view to show the test result data.
+    
+    @signal goto(str, int) emitted to go to the position given by file name
+        and line number
+    """
+    goto = pyqtSignal(str, int)
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        
+        self.setItemsExpandable(True)
+        self.setExpandsOnDoubleClick(False)
+        self.setSortingEnabled(True)
+        
+        self.header().setDefaultAlignment(Qt.AlignmentFlag.AlignCenter)
+        self.header().setSortIndicatorShown(False)
+        
+        self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
+        
+        # connect signals and slots
+        self.doubleClicked.connect(self.__gotoTestDefinition)
+        self.customContextMenuRequested.connect(self.__showContextMenu)
+        
+        self.header().sortIndicatorChanged.connect(self.sortByColumn)
+        self.header().sortIndicatorChanged.connect(
+            lambda column, order: self.header().setSortIndicatorShown(True))
+    
+    def reset(self):
+        """
+        Public method to reset the internal state of the view.
+        """
+        super().reset()
+        
+        self.resizeColumns()
+        self.spanFirstColumn(0, self.model().rowCount() - 1)
+    
+    def rowsInserted(self, parent, startRow, endRow):
+        """
+        Public method called when rows are inserted.
+        
+        @param parent model index of the parent item
+        @type QModelIndex
+        @param startRow first row been inserted
+        @type int
+        @param endRow last row been inserted
+        @type int
+        """
+        super().rowsInserted(parent, startRow, endRow)
+        
+        self.resizeColumns()
+        self.spanFirstColumn(startRow, endRow)
+    
+    def dataChanged(self, topLeft, bottomRight, roles=[]):
+        """
+        Public method called when the model data has changed.
+        
+        @param topLeft index of the top left element
+        @type QModelIndex
+        @param bottomRight index of the bottom right element
+        @type QModelIndex
+        @param roles list of roles changed (defaults to [])
+        @type list of Qt.ItemDataRole (optional)
+        """
+        super().dataChanged(topLeft, bottomRight, roles)
+        
+        self.resizeColumns()
+        while topLeft.parent().isValid():
+            topLeft = topLeft.parent()
+        while bottomRight.parent().isValid():
+            bottomRight = bottomRight.parent()
+        self.spanFirstColumn(topLeft.row(), bottomRight.row())
+    
+    def resizeColumns(self):
+        """
+        Public method to resize the columns to their contents.
+        """
+        for column in range(self.model().columnCount()):
+            self.resizeColumnToContents(column)
+    
+    def spanFirstColumn(self, startRow, endRow):
+        """
+        Public method to make the first column span the row for second level
+        items.
+        
+        These items contain the test results.
+        
+        @param startRow index of the first row to span
+        @type QModelIndex
+        @param endRow index of the last row (including) to span
+        @type QModelIndex
+        """
+        model = self.model()
+        for row in range(startRow, endRow + 1):
+            index = model.index(row, 0)
+            for i in range(model.rowCount(index)):
+                self.setFirstColumnSpanned(i, index, True)
+    
+    def __canonicalIndex(self, index):
+        """
+        Private method to create the canonical index for a given index.
+        
+        The canonical index is the index of the first column of the test
+        result entry (i.e. the top-level item). If the index is invalid,
+        None is returned.
+        
+        @param index index to determine the canonical index for
+        @type QModelIndex
+        @return index of the firt column of the associated top-level item index
+        @rtype QModelIndex
+        """
+        if not index.isValid():
+            return None
+        
+        while index.parent().isValid():  # find the top-level node
+            index = index.parent()
+        index = index.sibling(index.row(), 0)  # go to first column
+        return index
+    
+    @pyqtSlot(QModelIndex)
+    def __gotoTestDefinition(self, index):
+        """
+        Private slot to show the test definition.
+        
+        @param index index for the double-clicked item
+        @type QModelIndex
+        """
+        cindex = self.__canonicalIndex(index)
+        filename, lineno = self.model().data(cindex, Qt.ItemDataRole.UserRole)
+        if filename is not None:
+            if lineno is None:
+                lineno = 1
+            self.goto.emit(filename, lineno)
+    
+    @pyqtSlot(QPoint)
+    def __showContextMenu(self, pos):
+        """
+        Private slot to show the context menu.
+        
+        @param pos relative position for the context menu
+        @type QPoint
+        """
+        index = self.indexAt(pos)
+        cindex = self.__canonicalIndex(index)
+        
+        contextMenu = (
+            self.__createContextMenu(cindex)
+            if cindex else
+            self.__createBackgroundContextMenu()
+        )
+        contextMenu.exec(self.mapToGlobal(pos))
+    
+    def __createContextMenu(self, index):
+        """
+        Private method to create a context menu for the item pointed to by the
+        given index.
+        
+        @param index index of the item
+        @type QModelIndex
+        @return created context menu
+        @rtype QMenu
+        """
+        menu = QMenu(self)
+        if self.isExpanded(index):
+            menu.addAction(self.tr("Collapse"),
+                           lambda: self.collapse(index))
+        else:
+            act = menu.addAction(self.tr("Expand"),
+                                 lambda: self.expand(index))
+            act.setEnabled(self.model().hasChildren(index))
+        menu.addSeparator()
+        
+        act = menu.addAction(self.tr("Show Source"),
+                             lambda: self.__gotoTestDefinition(index))
+        act.setEnabled(
+            self.model().data(index, Qt.ItemDataRole.UserRole) is not None
+        )
+        menu.addSeparator()
+        
+        menu.addAction(self.tr("Collapse All"), self.collapseAll)
+        menu.addAction(self.tr("Expand All"), self.expandAll)
+        
+        return menu
+    
+    def __createBackgroundContextMenu(self):
+        """
+        Private method to create a context menu for the background.
+        
+        @return created context menu
+        @rtype QMenu
+        """
+        menu = QMenu(self)
+        menu.addAction(self.tr("Collapse All"), self.collapseAll)
+        menu.addAction(self.tr("Expand All"), self.expandAll)
+        
+        return menu
+
+#
+# eflag: noqa = M821, M822
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Testing/TestingWidget.py	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,1162 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a widget to orchestrate unit test execution.
+"""
+
+import contextlib
+import enum
+import locale
+import os
+
+from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent, QCoreApplication
+from PyQt6.QtWidgets import (
+    QAbstractButton, QComboBox, QDialogButtonBox, QWidget
+)
+
+from EricWidgets import EricMessageBox
+from EricWidgets.EricApplication import ericApp
+from EricWidgets.EricMainWindow import EricMainWindow
+from EricWidgets.EricPathPicker import EricPathPickerModes
+
+from .Ui_TestingWidget import Ui_TestingWidget
+
+from .TestResultsTree import TestResultsModel, TestResultsTreeView
+from .Interfaces import Frameworks
+from .Interfaces.TestExecutorBase import (
+    TestConfig, TestResult, TestResultCategory
+)
+from .Interfaces.TestFrameworkRegistry import TestFrameworkRegistry
+
+import Preferences
+import UI.PixmapCache
+
+from Globals import (
+    recentNameTestDiscoverHistory, recentNameTestFileHistory,
+    recentNameTestNameHistory, recentNameTestFramework,
+    recentNameTestEnvironment
+)
+
+
+class TestingWidgetModes(enum.Enum):
+    """
+    Class defining the various modes of the testing widget.
+    """
+    IDLE = 0            # idle, no test were run yet
+    RUNNING = 1         # test run being performed
+    STOPPED = 2         # test run finished
+
+
+class TestingWidget(QWidget, Ui_TestingWidget):
+    """
+    Class implementing a widget to orchestrate unit test execution.
+    
+    @signal testFile(str, int, bool) emitted to show the source of a
+       test file
+    @signal testRunStopped() emitted after a test run has finished
+    """
+    testFile = pyqtSignal(str, int, bool)
+    testRunStopped = pyqtSignal()
+    
+    def __init__(self, testfile=None, parent=None):
+        """
+        Constructor
+        
+        @param testfile file name of the test to load
+        @type str
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+        
+        self.__resultsModel = TestResultsModel(self)
+        self.__resultsModel.summary.connect(self.__setStatusLabel)
+        self.__resultsTree = TestResultsTreeView(self)
+        self.__resultsTree.setModel(self.__resultsModel)
+        self.__resultsTree.goto.connect(self.__showSource)
+        self.resultsGroupBox.layout().addWidget(self.__resultsTree)
+        
+        self.versionsButton.setIcon(
+            UI.PixmapCache.getIcon("info"))
+        self.clearHistoriesButton.setIcon(
+            UI.PixmapCache.getIcon("clearPrivateData"))
+        
+        self.testsuitePicker.setMode(EricPathPickerModes.OPEN_FILE_MODE)
+        self.testsuitePicker.setInsertPolicy(
+            QComboBox.InsertPolicy.InsertAtTop)
+        self.testsuitePicker.setSizeAdjustPolicy(
+            QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon)
+        
+        self.discoveryPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
+        self.discoveryPicker.setInsertPolicy(
+            QComboBox.InsertPolicy.InsertAtTop)
+        self.discoveryPicker.setSizeAdjustPolicy(
+            QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon)
+        
+        self.testComboBox.lineEdit().setClearButtonEnabled(True)
+        
+        # create some more dialog buttons for orchestration
+        self.__showLogButton = self.buttonBox.addButton(
+            self.tr("Show Output..."),
+            QDialogButtonBox.ButtonRole.ActionRole)
+        self.__showLogButton.setToolTip(
+            self.tr("Show the output of the test runner process"))
+        self.__showLogButton.setWhatsThis(self.tr(
+            """<b>Show Output...</b"""
+            """<p>This button opens a dialog containing the output of the"""
+            """ test runner process of the most recent run.</p>"""))
+        
+        self.__showCoverageButton = self.buttonBox.addButton(
+            self.tr("Show Coverage..."),
+            QDialogButtonBox.ButtonRole.ActionRole)
+        self.__showCoverageButton.setToolTip(
+            self.tr("Show code coverage in a new dialog"))
+        self.__showCoverageButton.setWhatsThis(self.tr(
+            """<b>Show Coverage...</b>"""
+            """<p>This button opens a dialog containing the collected code"""
+            """ coverage data.</p>"""))
+        
+        self.__startButton = self.buttonBox.addButton(
+            self.tr("Start"), QDialogButtonBox.ButtonRole.ActionRole)
+        
+        self.__startButton.setToolTip(self.tr(
+            "Start the selected testsuite"))
+        self.__startButton.setWhatsThis(self.tr(
+            """<b>Start Test</b>"""
+            """<p>This button starts the test run.</p>"""))
+        
+        self.__startFailedButton = self.buttonBox.addButton(
+            self.tr("Rerun Failed"), QDialogButtonBox.ButtonRole.ActionRole)
+        self.__startFailedButton.setToolTip(
+            self.tr("Reruns failed tests of the selected testsuite"))
+        self.__startFailedButton.setWhatsThis(self.tr(
+            """<b>Rerun Failed</b>"""
+            """<p>This button reruns all failed tests of the most recent"""
+            """ test run.</p>"""))
+        
+        self.__stopButton = self.buttonBox.addButton(
+            self.tr("Stop"), QDialogButtonBox.ButtonRole.ActionRole)
+        self.__stopButton.setToolTip(self.tr("Stop the running test"))
+        self.__stopButton.setWhatsThis(self.tr(
+            """<b>Stop Test</b>"""
+            """<p>This button stops a running test.</p>"""))
+        
+        self.setWindowFlags(
+            self.windowFlags() |
+            Qt.WindowType.WindowContextHelpButtonHint
+        )
+        self.setWindowIcon(UI.PixmapCache.getIcon("eric"))
+        self.setWindowTitle(self.tr("Testing"))
+        
+        try:
+            # we are called from within the eric IDE
+            self.__venvManager = ericApp().getObject("VirtualEnvManager")
+            self.__project = ericApp().getObject("Project")
+            self.__project.projectOpened.connect(self.__projectOpened)
+            self.__project.projectClosed.connect(self.__projectClosed)
+        except KeyError:
+            # we were called as a standalone application
+            from VirtualEnv.VirtualenvManager import VirtualenvManager
+            self.__venvManager = VirtualenvManager(self)
+            self.__venvManager.virtualEnvironmentAdded.connect(
+                self.__populateVenvComboBox)
+            self.__venvManager.virtualEnvironmentRemoved.connect(
+                self.__populateVenvComboBox)
+            self.__venvManager.virtualEnvironmentChanged.connect(
+                self.__populateVenvComboBox)
+            ericApp().registerObject("VirtualEnvManager", self.__venvManager)
+            
+            self.__project = None
+        
+        self.__discoverHistory = []
+        self.__fileHistory = []
+        self.__testNameHistory = []
+        self.__recentFramework = ""
+        self.__recentEnvironment = ""
+        self.__failedTests = []
+        
+        self.__coverageFile = ""
+        self.__coverageDialog = None
+        
+        self.__editors = []
+        self.__testExecutor = None
+        self.__recentLog = ""
+        
+        # connect some signals
+        self.discoveryPicker.editTextChanged.connect(
+            self.__resetResults)
+        self.testsuitePicker.editTextChanged.connect(
+            self.__resetResults)
+        self.testComboBox.editTextChanged.connect(
+            self.__resetResults)
+        
+        self.__frameworkRegistry = TestFrameworkRegistry()
+        for framework in Frameworks:
+            self.__frameworkRegistry.register(framework)
+        
+        self.__setIdleMode()
+        
+        self.__loadRecent()
+        self.__populateVenvComboBox()
+        
+        if self.__project and self.__project.isOpen():
+            self.venvComboBox.setCurrentText(self.__project.getProjectVenv())
+            self.frameworkComboBox.setCurrentText(
+                self.__project.getProjectTestingFramework())
+            self.__insertDiscovery(self.__project.getProjectPath())
+        else:
+            self.__insertDiscovery("")
+        
+        self.__insertTestFile(testfile)
+        self.__insertTestName("")
+        
+        self.clearHistoriesButton.clicked.connect(self.clearRecent)
+        
+        self.tabWidget.setCurrentIndex(0)
+    
+    def __populateVenvComboBox(self):
+        """
+        Private method to (re-)populate the virtual environments selector.
+        """
+        currentText = self.venvComboBox.currentText()
+        if not currentText:
+            currentText = self.__recentEnvironment
+        
+        self.venvComboBox.clear()
+        self.venvComboBox.addItem("")
+        self.venvComboBox.addItems(
+            sorted(self.__venvManager.getVirtualenvNames()))
+        self.venvComboBox.setCurrentText(currentText)
+    
+    def __populateTestFrameworkComboBox(self):
+        """
+        Private method to (re-)populate the test framework selector.
+        """
+        currentText = self.frameworkComboBox.currentText()
+        if not currentText:
+            currentText = self.__recentFramework
+        
+        self.frameworkComboBox.clear()
+        
+        if bool(self.venvComboBox.currentText()):
+            interpreter = self.__venvManager.getVirtualenvInterpreter(
+                self.venvComboBox.currentText())
+            self.frameworkComboBox.addItem("")
+            for index, (name, executor) in enumerate(
+                sorted(self.__frameworkRegistry.getFrameworks().items()),
+                start=1
+            ):
+                isInstalled = executor.isInstalled(interpreter)
+                entry = (
+                    name
+                    if isInstalled else
+                    self.tr("{0} (not available)").format(name)
+                )
+                self.frameworkComboBox.addItem(entry)
+                self.frameworkComboBox.model().item(index).setEnabled(
+                    isInstalled)
+            
+            self.frameworkComboBox.setCurrentText(self.__recentFramework)
+    
+    def getResultsModel(self):
+        """
+        Public method to get a reference to the model containing the test
+        result data.
+        
+        @return reference to the test results model
+        @rtype TestResultsModel
+        """
+        return self.__resultsModel
+    
+    def hasFailedTests(self):
+        """
+        Public method to check for failed tests.
+        
+        @return flag indicating the existence of failed tests
+        @rtype bool
+        """
+        return bool(self.__resultsModel.getFailedTests())
+        
+    def getFailedTests(self):
+        """
+        Public method to get the list of failed tests (if any).
+        
+        @return list of IDs of failed tests
+        @rtype list of str
+        """
+        return self.__failedTests[:]
+    
+    @pyqtSlot(str)
+    def __insertHistory(self, widget, history, item):
+        """
+        Private slot to insert an item into a history object.
+        
+        @param widget reference to the widget
+        @type QComboBox or EricComboPathPicker
+        @param history array containing the history
+        @type list of str
+        @param item item to be inserted
+        @type str
+        """
+        # prepend the given directory to the discovery picker
+        if item is None:
+            item = ""
+        if item in history:
+            history.remove(item)
+        history.insert(0, item)
+        widget.clear()
+        widget.addItems(history)
+        widget.setEditText(item)
+    
+    @pyqtSlot(str)
+    def __insertDiscovery(self, start):
+        """
+        Private slot to insert the discovery start directory into the
+        discoveryPicker object.
+        
+        @param start start directory name to be inserted
+        @type str
+        """
+        self.__insertHistory(self.discoveryPicker, self.__discoverHistory,
+                             start)
+    
+    @pyqtSlot(str)
+    def setTestFile(self, testFile, forProject=False):
+        """
+        Public slot to set the given test file as the current one.
+        
+        @param testFile path of the test file
+        @type str
+        @param forProject flag indicating that this call is for a project
+            (defaults to False)
+        @type bool (optional)
+        """
+        if testFile:
+            self.__insertTestFile(testFile)
+        
+        self.discoverCheckBox.setChecked(forProject or not bool(testFile))
+        
+        if forProject:
+            self.__projectOpened()
+        
+        self.tabWidget.setCurrentIndex(0)
+    
+    @pyqtSlot(str)
+    def __insertTestFile(self, prog):
+        """
+        Private slot to insert a test file name into the testsuitePicker
+        object.
+        
+        @param prog test file name to be inserted
+        @type str
+        """
+        self.__insertHistory(self.testsuitePicker, self.__fileHistory,
+                             prog)
+    
+    @pyqtSlot(str)
+    def __insertTestName(self, testName):
+        """
+        Private slot to insert a test name into the testComboBox object.
+        
+        @param testName name of the test to be inserted
+        @type str
+        """
+        self.__insertHistory(self.testComboBox, self.__testNameHistory,
+                             testName)
+    
+    def __loadRecent(self):
+        """
+        Private method to load the most recently used lists.
+        """
+        Preferences.Prefs.rsettings.sync()
+        
+        # 1. recently selected test framework and virtual environment
+        self.__recentEnvironment = Preferences.Prefs.rsettings.value(
+            recentNameTestEnvironment, "")
+        self.__recentFramework = Preferences.Prefs.rsettings.value(
+            recentNameTestFramework, "")
+        
+        # 2. discovery history
+        self.__discoverHistory = []
+        rs = Preferences.Prefs.rsettings.value(
+            recentNameTestDiscoverHistory)
+        if rs is not None:
+            recent = [f for f in Preferences.toList(rs) if os.path.exists(f)]
+            self.__discoverHistory = recent[
+                :Preferences.getDebugger("RecentNumber")]
+        
+        # 3. test file history
+        self.__fileHistory = []
+        rs = Preferences.Prefs.rsettings.value(
+            recentNameTestFileHistory)
+        if rs is not None:
+            recent = [f for f in Preferences.toList(rs) if os.path.exists(f)]
+            self.__fileHistory = recent[
+                :Preferences.getDebugger("RecentNumber")]
+        
+        # 4. test name history
+        self.__testNameHistory = []
+        rs = Preferences.Prefs.rsettings.value(
+            recentNameTestNameHistory)
+        if rs is not None:
+            recent = [n for n in Preferences.toList(rs) if n]
+            self.__testNameHistory = recent[
+                :Preferences.getDebugger("RecentNumber")]
+    
+    def __saveRecent(self):
+        """
+        Private method to save the most recently used lists.
+        """
+        Preferences.Prefs.rsettings.setValue(
+            recentNameTestEnvironment, self.__recentEnvironment)
+        Preferences.Prefs.rsettings.setValue(
+            recentNameTestFramework, self.__recentFramework)
+        Preferences.Prefs.rsettings.setValue(
+            recentNameTestDiscoverHistory, self.__discoverHistory)
+        Preferences.Prefs.rsettings.setValue(
+            recentNameTestFileHistory, self.__fileHistory)
+        Preferences.Prefs.rsettings.setValue(
+            recentNameTestNameHistory, self.__testNameHistory)
+        
+        Preferences.Prefs.rsettings.sync()
+    
+    @pyqtSlot()
+    def clearRecent(self):
+        """
+        Public slot to clear the recently used lists.
+        """
+        # clear histories
+        self.__discoverHistory = []
+        self.__fileHistory = []
+        self.__testNameHistory = []
+        
+        # clear widgets with histories
+        self.discoveryPicker.clear()
+        self.testsuitePicker.clear()
+        self.testComboBox.clear()
+        
+        # sync histories
+        self.__saveRecent()
+    
+    @pyqtSlot()
+    def __resetResults(self):
+        """
+        Private slot to reset the test results tab and data.
+        """
+        self.__totalCount = 0
+        self.__runCount = 0
+        
+        self.progressCounterRunCount.setText("0")
+        self.progressCounterRemCount.setText("0")
+        self.progressProgressBar.setMaximum(100)
+        self.progressProgressBar.setValue(0)
+        
+        self.statusLabel.clear()
+        
+        self.__resultsModel.clear()
+        self.__updateButtonBoxButtons()
+    
+    @pyqtSlot()
+    def __updateButtonBoxButtons(self):
+        """
+        Private slot to update the state of the buttons of the button box.
+        """
+        failedAvailable = bool(self.__resultsModel.getFailedTests())
+        
+        # Start button
+        if self.__mode in (
+            TestingWidgetModes.IDLE, TestingWidgetModes.STOPPED
+        ):
+            self.__startButton.setEnabled(
+                bool(self.venvComboBox.currentText()) and
+                bool(self.frameworkComboBox.currentText()) and
+                (
+                    (self.discoverCheckBox.isChecked() and
+                     bool(self.discoveryPicker.currentText())) or
+                    bool(self.testsuitePicker.currentText())
+                )
+            )
+            self.__startButton.setDefault(
+                self.__mode == TestingWidgetModes.IDLE or
+                not failedAvailable
+            )
+        else:
+            self.__startButton.setEnabled(False)
+            self.__startButton.setDefault(False)
+        
+        # Start Failed button
+        self.__startFailedButton.setEnabled(
+            self.__mode == TestingWidgetModes.STOPPED and
+            failedAvailable
+        )
+        self.__startFailedButton.setDefault(
+            self.__mode == TestingWidgetModes.STOPPED and
+            failedAvailable
+        )
+        
+        # Stop button
+        self.__stopButton.setEnabled(
+            self.__mode == TestingWidgetModes.RUNNING)
+        self.__stopButton.setDefault(
+            self.__mode == TestingWidgetModes.RUNNING)
+        
+        # Code coverage button
+        self.__showCoverageButton.setEnabled(
+            self.__mode == TestingWidgetModes.STOPPED and
+            bool(self.__coverageFile) and
+            (
+                (self.discoverCheckBox.isChecked() and
+                 bool(self.discoveryPicker.currentText())) or
+                bool(self.testsuitePicker.currentText())
+            )
+        )
+        
+        # Log output button
+        self.__showLogButton.setEnabled(bool(self.__recentLog))
+        
+        # Close button
+        self.buttonBox.button(
+            QDialogButtonBox.StandardButton.Close
+        ).setEnabled(self.__mode in (
+            TestingWidgetModes.IDLE, TestingWidgetModes.STOPPED
+        ))
+    
+    @pyqtSlot()
+    def __updateProgress(self):
+        """
+        Private slot update the progress indicators.
+        """
+        self.progressCounterRunCount.setText(
+            str(self.__runCount))
+        self.progressCounterRemCount.setText(
+            str(self.__totalCount - self.__runCount))
+        self.progressProgressBar.setMaximum(self.__totalCount)
+        self.progressProgressBar.setValue(self.__runCount)
+    
+    @pyqtSlot()
+    def __setIdleMode(self):
+        """
+        Private slot to switch the widget to idle mode.
+        """
+        self.__mode = TestingWidgetModes.IDLE
+        self.__updateButtonBoxButtons()
+        self.progressGroupBox.hide()
+        self.tabWidget.setCurrentIndex(0)
+    
+    @pyqtSlot()
+    def __setRunningMode(self):
+        """
+        Private slot to switch the widget to running mode.
+        """
+        self.__mode = TestingWidgetModes.RUNNING
+        
+        self.__totalCount = 0
+        self.__runCount = 0
+        
+        self.__coverageFile = ""
+        
+        self.sbLabel.setText(self.tr("Running"))
+        self.tabWidget.setCurrentIndex(1)
+        self.__updateButtonBoxButtons()
+        self.__updateProgress()
+        
+        self.progressGroupBox.show()
+    
+    @pyqtSlot()
+    def __setStoppedMode(self):
+        """
+        Private slot to switch the widget to stopped mode.
+        """
+        self.__mode = TestingWidgetModes.STOPPED
+        if self.__totalCount == 0:
+            self.progressProgressBar.setMaximum(100)
+        
+        self.progressGroupBox.hide()
+        
+        self.__updateButtonBoxButtons()
+        
+        self.testRunStopped.emit()
+        
+        self.raise_()
+        self.activateWindow()
+    
+    @pyqtSlot(bool)
+    def on_discoverCheckBox_toggled(self, checked):
+        """
+        Private slot handling state changes of the 'discover' checkbox.
+        
+        @param checked state of the checkbox
+        @type bool
+        """
+        if not bool(self.discoveryPicker.currentText()):
+            if self.__project and self.__project.isOpen():
+                self.__insertDiscovery(self.__project.getProjectPath())
+            else:
+                self.__insertDiscovery(
+                    Preferences.getMultiProject("Workspace"))
+        
+        self.__resetResults()
+    
+    @pyqtSlot()
+    def on_testsuitePicker_aboutToShowPathPickerDialog(self):
+        """
+        Private slot called before the test file selection dialog is shown.
+        """
+        if self.__project:
+            # we were called from within eric
+            py3Extensions = ' '.join([
+                "*{0}".format(ext)
+                for ext in
+                ericApp().getObject("DebugServer").getExtensions('Python3')
+            ])
+            fileFilter = self.tr(
+                "Python3 Files ({0});;All Files (*)"
+            ).format(py3Extensions)
+        else:
+            # standalone application
+            fileFilter = self.tr("Python Files (*.py);;All Files (*)")
+        self.testsuitePicker.setFilters(fileFilter)
+        
+        defaultDirectory = (
+            self.__project.getProjectPath()
+            if self.__project and self.__project.isOpen() else
+            Preferences.getMultiProject("Workspace")
+        )
+        if not defaultDirectory:
+            defaultDirectory = os.path.expanduser("~")
+        self.testsuitePicker.setDefaultDirectory(defaultDirectory)
+    
+    @pyqtSlot(QAbstractButton)
+    def on_buttonBox_clicked(self, button):
+        """
+        Private slot called by a button of the button box clicked.
+        
+        @param button button that was clicked
+        @type QAbstractButton
+        """
+        if button == self.__startButton:
+            self.startTests()
+            self.__saveRecent()
+        elif button == self.__stopButton:
+            self.__stopTests()
+        elif button == self.__startFailedButton:
+            self.startTests(failedOnly=True)
+        elif button == self.__showCoverageButton:
+            self.__showCoverageDialog()
+        elif button == self.__showLogButton:
+            self.__showLogOutput()
+    
+    @pyqtSlot(int)
+    def on_venvComboBox_currentIndexChanged(self, index):
+        """
+        Private slot handling the selection of a virtual environment.
+        
+        @param index index of the selected environment
+        @type int
+        """
+        self.__populateTestFrameworkComboBox()
+        self.__updateButtonBoxButtons()
+        
+        self.versionsButton.setEnabled(bool(self.venvComboBox.currentText()))
+        
+        self.__updateCoverage()
+    
+    @pyqtSlot(int)
+    def on_frameworkComboBox_currentIndexChanged(self, index):
+        """
+        Private slot handling the selection of a test framework.
+        
+        @param index index of the selected framework
+        @type int
+        """
+        self.__resetResults()
+        self.__updateCoverage()
+    
+    @pyqtSlot()
+    def __updateCoverage(self):
+        """
+        Private slot to update the state of the coverage checkbox depending on
+        the selected framework's capabilities.
+        """
+        hasCoverage = False
+        
+        venvName = self.venvComboBox.currentText()
+        if venvName:
+            framework = self.frameworkComboBox.currentText()
+            if framework:
+                interpreter = self.__venvManager.getVirtualenvInterpreter(
+                    venvName)
+                executor = self.__frameworkRegistry.createExecutor(
+                    framework, self)
+                hasCoverage = executor.hasCoverage(interpreter)
+        
+        self.coverageCheckBox.setEnabled(hasCoverage)
+        if not hasCoverage:
+            self.coverageCheckBox.setChecked(False)
+    
+    @pyqtSlot()
+    def on_versionsButton_clicked(self):
+        """
+        Private slot to show the versions of available plugins.
+        """
+        venvName = self.venvComboBox.currentText()
+        if venvName:
+            headerText = self.tr("<h3>Versions of Frameworks and their"
+                                 " Plugins</h3>")
+            versionsText = ""
+            interpreter = self.__venvManager.getVirtualenvInterpreter(venvName)
+            for framework in sorted(
+                self.__frameworkRegistry.getFrameworks().keys()
+            ):
+                executor = self.__frameworkRegistry.createExecutor(
+                    framework, self)
+                versions = executor.getVersions(interpreter)
+                if versions:
+                    txt = "<p><strong>{0} {1}</strong>".format(
+                        versions["name"], versions["version"])
+                    
+                    if versions["plugins"]:
+                        txt += "<table>"
+                        for pluginVersion in versions["plugins"]:
+                            txt += self.tr(
+                                "<tr><td>{0}</td><td>{1}</td></tr>"
+                            ).format(
+                                pluginVersion["name"], pluginVersion["version"]
+                            )
+                        txt += "</table>"
+                    txt += "</p>"
+                    
+                    versionsText += txt
+            
+            if not versionsText:
+                versionsText = self.tr("No version information available.")
+            
+            EricMessageBox.information(
+                self,
+                self.tr("Versions"),
+                headerText + versionsText
+            )
+    
+    @pyqtSlot()
+    def startTests(self, failedOnly=False):
+        """
+        Public slot to start the test run.
+        
+        @param failedOnly flag indicating to run only failed tests
+        @type bool
+        """
+        if self.__mode == TestingWidgetModes.RUNNING:
+            return
+        
+        self.__recentLog = ""
+        
+        self.__recentEnvironment = self.venvComboBox.currentText()
+        self.__recentFramework = self.frameworkComboBox.currentText()
+        
+        self.__failedTests = (
+            self.__resultsModel.getFailedTests()
+            if failedOnly else
+            []
+        )
+        discover = self.discoverCheckBox.isChecked()
+        if discover:
+            discoveryStart = self.discoveryPicker.currentText()
+            testFileName = ""
+            testName = ""
+            
+            if discoveryStart:
+                self.__insertDiscovery(discoveryStart)
+        else:
+            discoveryStart = ""
+            testFileName = self.testsuitePicker.currentText()
+            if testFileName:
+                self.__insertTestFile(testFileName)
+            testName = self.testComboBox.currentText()
+            if testName:
+                self.__insertTestName(testName)
+        
+        self.sbLabel.setText(self.tr("Preparing Testsuite"))
+        QCoreApplication.processEvents()
+        
+        if self.__project:
+            mainScript = self.__project.getMainScript(True)
+            coverageFile = (
+                os.path.splitext(mainScript)[0] + ".coverage"
+                if mainScript else
+                ""
+            )
+        else:
+            coverageFile = ""
+        interpreter = self.__venvManager.getVirtualenvInterpreter(
+            self.__recentEnvironment)
+        config = TestConfig(
+            interpreter=interpreter,
+            discover=discover,
+            discoveryStart=discoveryStart,
+            testFilename=testFileName,
+            testName=testName,
+            failFast=self.failfastCheckBox.isChecked(),
+            failedOnly=failedOnly,
+            collectCoverage=self.coverageCheckBox.isChecked(),
+            eraseCoverage=self.coverageEraseCheckBox.isChecked(),
+            coverageFile=coverageFile,
+        )
+        
+        self.__testExecutor = self.__frameworkRegistry.createExecutor(
+            self.__recentFramework, self)
+        self.__testExecutor.collected.connect(self.__testsCollected)
+        self.__testExecutor.collectError.connect(self.__testsCollectError)
+        self.__testExecutor.startTest.connect(self.__testStarted)
+        self.__testExecutor.testResult.connect(self.__processTestResult)
+        self.__testExecutor.testFinished.connect(self.__testProcessFinished)
+        self.__testExecutor.testRunFinished.connect(self.__testRunFinished)
+        self.__testExecutor.stop.connect(self.__testsStopped)
+        self.__testExecutor.coverageDataSaved.connect(self.__coverageData)
+        self.__testExecutor.testRunAboutToBeStarted.connect(
+            self.__testRunAboutToBeStarted)
+        
+        self.__setRunningMode()
+        self.__testExecutor.start(config, [])
+    
+    @pyqtSlot()
+    def __stopTests(self):
+        """
+        Private slot to stop the current test run.
+        """
+        self.__testExecutor.stopIfRunning()
+    
+    @pyqtSlot(list)
+    def __testsCollected(self, testNames):
+        """
+        Private slot handling the 'collected' signal of the executor.
+        
+        @param testNames list of tuples containing the test id, the test name
+            and a description of collected tests
+        @type list of tuple of (str, str, str)
+        """
+        testResults = [
+            TestResult(
+                category=TestResultCategory.PENDING,
+                status=self.tr("pending"),
+                name=name,
+                id=id,
+                message=desc,
+            ) for id, name, desc in testNames
+        ]
+        self.__resultsModel.addTestResults(testResults)
+        
+        self.__totalCount += len(testResults)
+        self.__updateProgress()
+    
+    @pyqtSlot(list)
+    def __testsCollectError(self, errors):
+        """
+        Private slot handling the 'collectError' signal of the executor.
+        
+        @param errors list of tuples containing the test name and a description
+            of the error
+        @type list of tuple of (str, str)
+        """
+        testResults = []
+        
+        for testFile, error in errors:
+            if testFile:
+                testResults.append(TestResult(
+                    category=TestResultCategory.FAIL,
+                    status=self.tr("Failure"),
+                    name=testFile,
+                    id=testFile,
+                    message=self.tr("Collection Error"),
+                    extra=error.splitlines()
+                ))
+            else:
+                EricMessageBox.critical(
+                    self,
+                    self.tr("Collection Error"),
+                    self.tr(
+                        "<p>There was an error while collecting tests."
+                        "</p><p>{0}</p>"
+                    ).format("<br/>".join(error.splitlines()))
+                )
+        
+        if testResults:
+            self.__resultsModel.addTestResults(testResults)
+    
+    @pyqtSlot(tuple)
+    def __testStarted(self, test):
+        """
+        Private slot handling the 'startTest' signal of the executor.
+        
+        @param test tuple containing the id, name and short description of the
+            tests about to be run
+        @type tuple of (str, str, str)
+        """
+        self.__resultsModel.updateTestResults([
+            TestResult(
+                category=TestResultCategory.RUNNING,
+                status=self.tr("running"),
+                id=test[0],
+                name=test[1],
+                message="" if test[2] is None else test[2],
+            )
+        ])
+    
+    @pyqtSlot(TestResult)
+    def __processTestResult(self, result):
+        """
+        Private slot to handle the receipt of a test result object.
+        
+        @param result test result object
+        @type TestResult
+        """
+        if not result.subtestResult:
+            self.__runCount += 1
+        self.__updateProgress()
+        
+        self.__resultsModel.updateTestResults([result])
+    
+    @pyqtSlot(list, str)
+    def __testProcessFinished(self, results, output):
+        """
+        Private slot to handle the 'testFinished' signal of the executor.
+        
+        @param results list of test result objects (if not sent via the
+            'testResult' signal
+        @type list of TestResult
+        @param output string containing the test process output (if any)
+        @type str
+        """
+        self.__recentLog = output
+        
+        self.__setStoppedMode()
+        self.__testExecutor = None
+        
+        self.__adjustPendingState()
+    
+    @pyqtSlot(int, float)
+    def __testRunFinished(self, noTests, duration):
+        """
+        Private slot to handle the 'testRunFinished' signal of the executor.
+        
+        @param noTests number of tests run by the executor
+        @type int
+        @param duration time needed in seconds to run the tests
+        @type float
+        """
+        self.sbLabel.setText(
+            self.tr("Ran %n test(s) in {0}s", "", noTests).format(
+                locale.format_string("%.3f", duration, grouping=True)
+            )
+        )
+        
+        self.__setStoppedMode()
+    
+    @pyqtSlot()
+    def __testsStopped(self):
+        """
+        Private slot to handle the 'stop' signal of the executor.
+        """
+        self.sbLabel.setText(self.tr("Ran %n test(s)", "", self.__runCount))
+        
+        self.__setStoppedMode()
+    
+    @pyqtSlot()
+    def __testRunAboutToBeStarted(self):
+        """
+        Private slot to handle the 'testRunAboutToBeStarted' signal of the
+        executor.
+        """
+        self.__resultsModel.clear()
+    
+    def __adjustPendingState(self):
+        """
+        Private method to change the status indicator of all still pending
+        tests to "not run".
+        """
+        newResults = []
+        for result in self.__resultsModel.getTestResults():
+            if result.category == TestResultCategory.PENDING:
+                result.category = TestResultCategory.SKIP
+                result.status = self.tr("not run")
+                newResults.append(result)
+        
+        if newResults:
+            self.__resultsModel.updateTestResults(newResults)
+    
+    @pyqtSlot(str)
+    def __coverageData(self, coverageFile):
+        """
+        Private slot to handle the 'coverageData' signal of the executor.
+        
+        @param coverageFile file containing the coverage data
+        @type str
+        """
+        self.__coverageFile = coverageFile
+    
+    @pyqtSlot()
+    def __showCoverageDialog(self):
+        """
+        Private slot to show a code coverage dialog for the most recent test
+        run.
+        """
+        if self.__coverageDialog is None:
+            from DataViews.PyCoverageDialog import PyCoverageDialog
+            self.__coverageDialog = PyCoverageDialog(self)
+            self.__coverageDialog.openFile.connect(self.__openEditor)
+        
+        testDir = (
+            self.discoveryPicker.currentText()
+            if self.discoverCheckBox.isChecked() else
+            os.path.dirname(self.testsuitePicker.currentText())
+        )
+        if testDir:
+            self.__coverageDialog.show()
+            self.__coverageDialog.start(self.__coverageFile, testDir)
+    
+    @pyqtSlot()
+    def __showLogOutput(self):
+        """
+        Private slot to show the output of the most recent test run.
+        """
+        from EricWidgets.EricPlainTextDialog import EricPlainTextDialog
+        dlg = EricPlainTextDialog(
+            title=self.tr("Test Run Output"),
+            text=self.__recentLog
+        )
+        dlg.exec()
+    
+    @pyqtSlot(str)
+    def __setStatusLabel(self, statusText):
+        """
+        Private slot to set the status label to the text sent by the model.
+        
+        @param statusText text to be shown
+        @type str
+        """
+        self.statusLabel.setText(f"<b>{statusText}</b>")
+    
+    @pyqtSlot()
+    def __projectOpened(self):
+        """
+        Private slot to handle a project being opened.
+        """
+        self.venvComboBox.setCurrentText(self.__project.getProjectVenv())
+        self.frameworkComboBox.setCurrentText(
+            self.__project.getProjectTestingFramework())
+        self.__insertDiscovery(self.__project.getProjectPath())
+    
+    @pyqtSlot()
+    def __projectClosed(self):
+        """
+        Private slot to handle a project being closed.
+        """
+        self.venvComboBox.setCurrentText("")
+        self.frameworkComboBox.setCurrentText("")
+        self.__insertDiscovery("")
+    
+    @pyqtSlot(str, int)
+    def __showSource(self, filename, lineno):
+        """
+        Private slot to show the source of a traceback in an editor.
+        
+        @param filename file name of the file to be shown
+        @type str
+        @param lineno line number to go to in the file
+        @type int
+        """
+        if self.__project:
+            # running as part of eric IDE
+            self.testFile.emit(filename, lineno, True)
+        else:
+            self.__openEditor(filename, lineno)
+    
+    def __openEditor(self, filename, linenumber=1):
+        """
+        Private method to open an editor window for the given file.
+        
+        Note: This method opens an editor window when the testing dialog
+        is called as a standalone application.
+        
+        @param filename path of the file to be opened
+        @type str
+        @param linenumber line number to place the cursor at (defaults to 1)
+        @type int (optional)
+        """
+        from QScintilla.MiniEditor import MiniEditor
+        editor = MiniEditor(filename, "Python3", self)
+        editor.gotoLine(linenumber)
+        editor.show()
+        
+        self.__editors.append(editor)
+    
+    def closeEvent(self, event):
+        """
+        Protected method to handle the close event.
+        
+        @param event close event
+        @type QCloseEvent
+        """
+        event.accept()
+        
+        for editor in self.__editors:
+            with contextlib.suppress(Exception):
+                editor.close()
+
+
+class TestingWindow(EricMainWindow):
+    """
+    Main window class for the standalone dialog.
+    """
+    def __init__(self, testfile=None, parent=None):
+        """
+        Constructor
+        
+        @param testfile file name of the test script to open
+        @type str
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super().__init__(parent)
+        self.__cw = TestingWidget(testfile=testfile, parent=self)
+        self.__cw.installEventFilter(self)
+        size = self.__cw.size()
+        self.setCentralWidget(self.__cw)
+        self.resize(size)
+        
+        self.setStyle(Preferences.getUI("Style"),
+                      Preferences.getUI("StyleSheet"))
+        
+        self.__cw.buttonBox.accepted.connect(self.close)
+        self.__cw.buttonBox.rejected.connect(self.close)
+    
+    def eventFilter(self, obj, event):
+        """
+        Public method to filter events.
+        
+        @param obj reference to the object the event is meant for (QObject)
+        @param event reference to the event object (QEvent)
+        @return flag indicating, whether the event was handled (boolean)
+        """
+        if event.type() == QEvent.Type.Close:
+            QCoreApplication.exit(0)
+            return True
+        
+        return False
+
+
+def clearSavedHistories(self):
+    """
+    Function to clear the saved history lists.
+    """
+    Preferences.Prefs.rsettings.setValue(
+        recentNameTestDiscoverHistory, [])
+    Preferences.Prefs.rsettings.setValue(
+        recentNameTestFileHistory, [])
+    Preferences.Prefs.rsettings.setValue(
+        recentNameTestNameHistory, [])
+    
+    Preferences.Prefs.rsettings.sync()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Testing/TestingWidget.ui	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,543 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TestingWidget</class>
+ <widget class="QWidget" name="TestingWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>850</width>
+    <height>700</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Testing</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_3">
+   <item>
+    <widget class="QTabWidget" name="tabWidget">
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <widget class="QWidget" name="parametersTab">
+      <attribute name="title">
+       <string>Parameters</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout">
+       <item>
+        <layout class="QGridLayout" name="gridLayout_3">
+         <item row="0" column="0">
+          <widget class="QLabel" name="venvLabel">
+           <property name="text">
+            <string>Virtual Environment:</string>
+           </property>
+           <property name="buddy">
+            <cstring>venvComboBox</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="0" column="1" colspan="2">
+          <widget class="QComboBox" name="venvComboBox">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="toolTip">
+            <string>Select the virtual environment to be used</string>
+           </property>
+           <property name="whatsThis">
+            <string>&lt;b&gt;Virtual Environment&lt;/b&gt;\n&lt;p&gt;Enter the virtual environment to be used. Leave it empty to use the default environment, i.e. the one configured globally or per project.&lt;/p&gt;</string>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="0">
+          <widget class="QLabel" name="label">
+           <property name="text">
+            <string>Test Framework:</string>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="1">
+          <widget class="QComboBox" name="frameworkComboBox">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="toolTip">
+            <string>Select the test framwork to be used</string>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="2">
+          <widget class="QToolButton" name="versionsButton">
+           <property name="toolTip">
+            <string>Press to show the test framework versions</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <widget class="QGroupBox" name="groupBox">
+         <property name="title">
+          <string>Test Parameters</string>
+         </property>
+         <layout class="QGridLayout" name="gridLayout">
+          <item row="0" column="0" colspan="2">
+           <layout class="QHBoxLayout" name="horizontalLayout_4">
+            <item>
+             <widget class="QCheckBox" name="discoverCheckBox">
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+                <horstretch>0</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
+              </property>
+              <property name="toolTip">
+               <string>Select to discover tests automatically</string>
+              </property>
+              <property name="text">
+               <string>Discover tests (test modules must be importable)</string>
+              </property>
+              <property name="checked">
+               <bool>true</bool>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QToolButton" name="clearHistoriesButton">
+              <property name="toolTip">
+               <string>Press to clear the various histories</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+          <item row="1" column="0">
+           <widget class="QLabel" name="label_3">
+            <property name="text">
+             <string>Discovery Start:</string>
+            </property>
+            <property name="buddy">
+             <cstring>discoveryPicker</cstring>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="1">
+           <widget class="EricComboPathPicker" name="discoveryPicker" native="true">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="focusPolicy">
+             <enum>Qt::WheelFocus</enum>
+            </property>
+            <property name="toolTip">
+             <string>Enter name of the directory at which to start the test file discovery</string>
+            </property>
+            <property name="whatsThis">
+             <string>&lt;b&gt;Discovery Start&lt;/b&gt;
+&lt;p&gt;Enter name of the directory at which to start the test file discovery.
+Note that all test modules must be importable from this directory.&lt;/p&gt;</string>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="0">
+           <widget class="QLabel" name="testsuiteLabel">
+            <property name="text">
+             <string>Test Filename:</string>
+            </property>
+            <property name="buddy">
+             <cstring>testsuitePicker</cstring>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="1">
+           <widget class="EricComboPathPicker" name="testsuitePicker" native="true">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="focusPolicy">
+             <enum>Qt::WheelFocus</enum>
+            </property>
+            <property name="toolTip">
+             <string>Enter name of file defining the testsuite</string>
+            </property>
+            <property name="whatsThis">
+             <string>&lt;b&gt;Testsuite&lt;/b&gt;&lt;p&gt;Enter the name of the file defining the testsuite. It should contain a method with a name given below. If no name is given, the 'suite()' method will be tried. If no such method can be found, the module will be inspected for proper test cases.&lt;/p&gt;</string>
+            </property>
+           </widget>
+          </item>
+          <item row="3" column="0">
+           <widget class="QLabel" name="label_2">
+            <property name="text">
+             <string>Test Name:</string>
+            </property>
+            <property name="buddy">
+             <cstring>testComboBox</cstring>
+            </property>
+           </widget>
+          </item>
+          <item row="3" column="1">
+           <widget class="QComboBox" name="testComboBox">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+            <property name="toolTip">
+             <string>Enter the test name. Leave empty to use the default name &quot;suite&quot;.</string>
+            </property>
+            <property name="whatsThis">
+             <string>&lt;b&gt;Testname&lt;/b&gt;&lt;p&gt;Enter the name of the test to be performed. This name must follow the rules given by selecte test framework. If this field is empty, the default name of &quot;suite&quot; will be used.&lt;/p&gt;</string>
+            </property>
+            <property name="editable">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <widget class="QGroupBox" name="optionsGroup">
+         <property name="title">
+          <string>Run Parameters</string>
+         </property>
+         <layout class="QVBoxLayout" name="verticalLayout_2">
+          <item>
+           <layout class="QGridLayout" name="gridLayout_2">
+            <item row="0" column="0">
+             <widget class="QCheckBox" name="coverageCheckBox">
+              <property name="toolTip">
+               <string>Select whether coverage data should be collected</string>
+              </property>
+              <property name="text">
+               <string>Collect coverage data</string>
+              </property>
+             </widget>
+            </item>
+            <item row="0" column="1">
+             <widget class="QCheckBox" name="coverageEraseCheckBox">
+              <property name="enabled">
+               <bool>false</bool>
+              </property>
+              <property name="toolTip">
+               <string>Select whether old coverage data should be erased</string>
+              </property>
+              <property name="text">
+               <string>Erase coverage data</string>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="0">
+             <widget class="QCheckBox" name="failfastCheckBox">
+              <property name="toolTip">
+               <string>Select to stop the test run on the first error or failure</string>
+              </property>
+              <property name="text">
+               <string>Stop on First Error or Failure</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <spacer name="verticalSpacer">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>239</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="resultsTab">
+      <attribute name="title">
+       <string>Results</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_6">
+       <item>
+        <widget class="QGroupBox" name="progressGroupBox">
+         <property name="title">
+          <string>Progress</string>
+         </property>
+         <layout class="QVBoxLayout" name="verticalLayout_4">
+          <item>
+           <widget class="QProgressBar" name="progressProgressBar">
+            <property name="value">
+             <number>0</number>
+            </property>
+            <property name="orientation">
+             <enum>Qt::Horizontal</enum>
+            </property>
+            <property name="format">
+             <string>%v/%m Tests</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <layout class="QHBoxLayout" name="horizontalLayout_2">
+            <item>
+             <widget class="QLabel" name="progressCounterRunLabel">
+              <property name="text">
+               <string>Run:</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLabel" name="progressCounterRunCount">
+              <property name="toolTip">
+               <string>Number of tests run</string>
+              </property>
+              <property name="text">
+               <string notr="true">0</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLabel" name="progressCounterRemLabel">
+              <property name="text">
+               <string>Remaining:</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLabel" name="progressCounterRemCount">
+              <property name="toolTip">
+               <string>Number of tests to be run</string>
+              </property>
+              <property name="text">
+               <string notr="true">0</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <spacer name="horizontalSpacer">
+              <property name="orientation">
+               <enum>Qt::Horizontal</enum>
+              </property>
+              <property name="sizeHint" stdset="0">
+               <size>
+                <width>40</width>
+                <height>20</height>
+               </size>
+              </property>
+             </spacer>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <widget class="QGroupBox" name="resultsGroupBox">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="title">
+          <string>Results</string>
+         </property>
+         <layout class="QVBoxLayout" name="verticalLayout_5">
+          <item>
+           <widget class="QLabel" name="statusLabel">
+            <property name="text">
+             <string/>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="_4">
+     <item>
+      <widget class="QLabel" name="sbLabel">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string>Idle</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer>
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeType">
+        <enum>QSizePolicy::Expanding</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>20</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Close</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>EricComboPathPicker</class>
+   <extends>QWidget</extends>
+   <header>EricWidgets/EricPathPicker.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>tabWidget</tabstop>
+  <tabstop>venvComboBox</tabstop>
+  <tabstop>frameworkComboBox</tabstop>
+  <tabstop>versionsButton</tabstop>
+  <tabstop>discoverCheckBox</tabstop>
+  <tabstop>clearHistoriesButton</tabstop>
+  <tabstop>discoveryPicker</tabstop>
+  <tabstop>testsuitePicker</tabstop>
+  <tabstop>testComboBox</tabstop>
+  <tabstop>coverageCheckBox</tabstop>
+  <tabstop>coverageEraseCheckBox</tabstop>
+  <tabstop>failfastCheckBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>TestingWidget</receiver>
+   <slot>close()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>31</x>
+     <y>648</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>1</x>
+     <y>510</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>TestingWidget</receiver>
+   <slot>close()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>80</x>
+     <y>649</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>3</x>
+     <y>580</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>discoverCheckBox</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>discoveryPicker</receiver>
+   <slot>setEnabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>168</x>
+     <y>164</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>170</x>
+     <y>191</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>discoverCheckBox</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>testsuitePicker</receiver>
+   <slot>setDisabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>222</x>
+     <y>162</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>222</x>
+     <y>209</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>discoverCheckBox</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>testComboBox</receiver>
+   <slot>setDisabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>301</x>
+     <y>163</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>300</x>
+     <y>238</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>coverageCheckBox</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>coverageEraseCheckBox</receiver>
+   <slot>setEnabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>160</x>
+     <y>320</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>369</x>
+     <y>319</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Testing/__init__.py	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing testing functionality and interface to various test
+frameworks.
+"""
+
+from .Interfaces import FrameworkNames
+
+
+def supportedLanguages():
+    """
+    Function to get a list of supported programming languages.
+    
+    @return list of supported programming languages
+    @rtype list of str
+    """
+    return list(FrameworkNames.keys())
+
+
+def isLanguageSupported(language):
+    """
+    Function to check, if the given programming language is supported by any
+    testing framework.
+    
+    @param language programming language
+    @type str
+    @return flag indicating support
+    @rtype bool
+    """
+    return language in FrameworkNames
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/eric7_testing.py	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+eric testing.
+
+This is the main Python script that performs the necessary initialization
+of the testing module and starts the Qt event loop. This is a standalone
+version of the integrated testing module.
+"""
+
+import sys
+import os
+
+sys.path.insert(1, os.path.dirname(__file__))
+
+for arg in sys.argv[:]:
+    if arg.startswith("--config="):
+        import Globals
+        configDir = arg.replace("--config=", "")
+        Globals.setConfigDir(configDir)
+        sys.argv.remove(arg)
+    elif arg.startswith("--settings="):
+        from PyQt6.QtCore import QSettings
+        settingsDir = os.path.expanduser(arg.replace("--settings=", ""))
+        if not os.path.isdir(settingsDir):
+            os.makedirs(settingsDir)
+        QSettings.setPath(
+            QSettings.Format.IniFormat, QSettings.Scope.UserScope, settingsDir)
+        sys.argv.remove(arg)
+
+from Globals import AppInfo
+
+from Toolbox import Startup
+
+
+def createMainWidget(argv):
+    """
+    Function to create the main widget.
+    
+    @param argv list of commandline parameters
+    @type list of str
+    @return reference to the main widget
+    @rtype QWidget
+    """
+    from Testing.TestingWidget import TestingWindow
+    try:
+        fn = argv[1]
+    except IndexError:
+        fn = None
+    return TestingWindow(fn)
+
+
+def main():
+    """
+    Main entry point into the application.
+    """
+    from PyQt6.QtGui import QGuiApplication
+    QGuiApplication.setDesktopFileName("eric7_testing.desktop")
+    
+    options = [
+        ("--config=configDir",
+         "use the given directory as the one containing the config files"),
+        ("--settings=settingsDir",
+         "use the given directory to store the settings files"),
+    ]
+    appinfo = AppInfo.makeAppInfo(sys.argv,
+                                  "eric Testing",
+                                  "file",
+                                  "Graphical test application",
+                                  options)
+    res = Startup.simpleAppStartup(sys.argv,
+                                   appinfo,
+                                   createMainWidget)
+    sys.exit(res)
+
+if __name__ == '__main__':
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/eric7_testing.pyw	Sun Jun 05 17:17:44 2022 +0200
@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2011 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the Windows entry point.
+"""
+
+from eric7_testing import main
+
+main()
--- a/eric7/eric7_unittest.py	Wed Jun 01 13:49:13 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
-#
-
-"""
-eric Unittest.
-
-This is the main Python script that performs the necessary initialization
-of the unittest module and starts the Qt event loop. This is a standalone
-version of the integrated unittest module.
-"""
-
-import sys
-import os
-
-sys.path.insert(1, os.path.dirname(__file__))
-
-for arg in sys.argv[:]:
-    if arg.startswith("--config="):
-        import Globals
-        configDir = arg.replace("--config=", "")
-        Globals.setConfigDir(configDir)
-        sys.argv.remove(arg)
-    elif arg.startswith("--settings="):
-        from PyQt6.QtCore import QSettings
-        settingsDir = os.path.expanduser(arg.replace("--settings=", ""))
-        if not os.path.isdir(settingsDir):
-            os.makedirs(settingsDir)
-        QSettings.setPath(
-            QSettings.Format.IniFormat, QSettings.Scope.UserScope, settingsDir)
-        sys.argv.remove(arg)
-
-from Globals import AppInfo
-
-from Toolbox import Startup
-
-
-def createMainWidget(argv):
-    """
-    Function to create the main widget.
-    
-    @param argv list of commandline parameters (list of strings)
-    @return reference to the main widget (QWidget)
-    """
-    from PyUnit.UnittestDialog import UnittestWindow
-    try:
-        fn = argv[1]
-    except IndexError:
-        fn = None
-    return UnittestWindow(fn)
-
-
-def main():
-    """
-    Main entry point into the application.
-    """
-    from PyQt6.QtGui import QGuiApplication
-    QGuiApplication.setDesktopFileName("eric7_unittest.desktop")
-    
-    options = [
-        ("--config=configDir",
-         "use the given directory as the one containing the config files"),
-        ("--settings=settingsDir",
-         "use the given directory to store the settings files"),
-    ]
-    appinfo = AppInfo.makeAppInfo(sys.argv,
-                                  "eric Unittest",
-                                  "file",
-                                  "Graphical unit test application",
-                                  options)
-    res = Startup.simpleAppStartup(sys.argv,
-                                   appinfo,
-                                   createMainWidget)
-    sys.exit(res)
-
-if __name__ == '__main__':
-    main()
--- a/eric7/eric7_unittest.pyw	Wed Jun 01 13:49:13 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2011 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
-#
-
-"""
-Module implementing the Windows entry point.
-"""
-
-from eric7_unittest import main
-
-main()

eric ide

mercurial