src/eric7/Testing/Interfaces/TestExecutorBase.py

branch
eric7-maintenance
changeset 10460
3b34efa2857c
parent 10079
0222a480e93d
parent 10439
21c28b0f9e41
child 10694
f46c1e224e8a
diff -r 411df92e881f -r 3b34efa2857c src/eric7/Testing/Interfaces/TestExecutorBase.py
--- a/src/eric7/Testing/Interfaces/TestExecutorBase.py	Sun Dec 03 14:54:00 2023 +0100
+++ b/src/eric7/Testing/Interfaces/TestExecutorBase.py	Mon Jan 01 11:10:45 2024 +0100
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright (c) 2022 - 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+# Copyright (c) 2022 - 2024 Detlev Offenbach <detlev@die-offenbachs.de>
 #
 
 """
@@ -10,7 +10,7 @@
 
 import os
 
-from dataclasses import dataclass
+from dataclasses import dataclass, field
 from enum import IntEnum
 
 from PyQt6.QtCore import QObject, QProcess, QProcessEnvironment, pyqtSignal
@@ -44,8 +44,8 @@
     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
+    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
 
 
@@ -56,26 +56,30 @@
     """
 
     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
-    testMarkerExpression: str  # marker expression for test selection
-    testNamePattern: str  # test name pattern expression or list
-    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
+    discover: bool = False  # auto discovery flag
+    discoveryStart: str = ""  # start directory for auto discovery
+    testCases: list = field(default_factory=list)  # list of selected test cases
+    testFilename: str = ""  # name of the test script
+    testName: str = ""  # name of the test function
+    testMarkerExpression: str = ""  # marker expression for test selection
+    testNamePattern: str = ""  # test name pattern expression or list
+    failFast: bool = False  # stop on first fail
+    failedOnly: bool = False  # run failed tests only
+    collectCoverage: bool = False  # coverage collection flag
+    eraseCoverage: bool = False  # erase coverage data first
+    coverageFile: str = ""  # name of the coverage data file
+    discoverOnly: bool = False  # test discovery only
+    venvName: str = ""  # name of the virtual environment
 
 
 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 collected(list of tuple of (str, str, str, str, int, list)) emitted after
+        all tests have been collected. Tuple elements are the test id, the test name,
+        a short description of the test, the test file name, the line number of
+        the test and the elements of the test path as a list.
     @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.
@@ -88,10 +92,14 @@
     @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
+        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.
+    @signal discoveryAboutToBeStarted() emitted just before the test discovery
+        will be started
+    @signal discoveryFinished(int, float) emitted when the discovery has finished.
+        The elements are the number of discovered tests and the duration in seconds.
     """
 
     collected = pyqtSignal(list)
@@ -103,6 +111,8 @@
     testRunFinished = pyqtSignal(int, float)
     stop = pyqtSignal()
     coverageDataSaved = pyqtSignal(str)
+    discoveryAboutToBeStarted = pyqtSignal()
+    discoveryFinished = pyqtSignal(int, float)
 
     module = ""
     name = ""
@@ -118,6 +128,9 @@
         super().__init__(testWidget)
 
         self.__process = None
+        self.__debugger = None
+
+        self._language = "Python3"
 
     @classmethod
     def isInstalled(cls, interpreter):
@@ -155,8 +168,7 @@
 
     def hasCoverage(self, interpreter):  # noqa: U100
         """
-        Public method to get the test framework version and version information
-        of its installed plugins.
+        Public method to check, if the collection of coverage data is available.
 
         @param interpreter interpreter to be used for the test
         @type str
@@ -244,6 +256,29 @@
 
         return process
 
+    def discover(self, config, pythonpath):
+        """
+        Public method to start the test discovery process.
+
+        @param config configuration for the test discovery
+        @type TestConfig
+        @param pythonpath list of directories to be added to the Python path
+        @type list of str
+        @exception RuntimeError raised if the the test discovery process did not start
+        @exception ValueError raised if no start directory for the test discovery was
+            given
+        """
+        if not config.discoveryStart:
+            raise ValueError("No discovery start directory given.")
+
+        self.__process = self._prepareProcess(config.discoveryStart, pythonpath)
+        discoveryArgs = self.createArguments(config)
+        self.discoveryAboutToBeStarted.emit()
+        self.__process.start(config.interpreter, discoveryArgs)
+        running = self.__process.waitForStarted()
+        if not running:
+            raise RuntimeError("Test discovery process did not start.")
+
     def start(self, config, pythonpath):
         """
         Public method to start the testing process.
@@ -267,17 +302,56 @@
         if not running:
             raise RuntimeError("Test process did not start.")
 
+    def startDebug(self, config, pythonpath, debugger):
+        """
+        Public method to start the test run with debugger support.
+
+        @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
+        @param debugger refference to the debugger interface
+        @type DebugUI
+        """
+        workDir = (
+            config.discoveryStart
+            if config.discover
+            else os.path.dirname(config.testFilename)
+        )
+        testArgs = self.createArguments(config)
+        if pythonpath:
+            currentPythonPath = os.environ.get("PYTHONPATH")
+            newPythonPath = os.pathsep.join(pythonpath)
+            if currentPythonPath:
+                newPythonPath += os.pathsep + currentPythonPath
+            environment = {"PYTHONPATH": newPythonPath}
+        else:
+            environment = {}
+
+        self.__debugger = debugger
+        self.__debugger.debuggingFinished.connect(self.finished)
+        self.testRunAboutToBeStarted.emit()
+
+        self.__debugger.debugInternalScript(
+            venvName=config.venvName,
+            scriptName=testArgs[0],
+            argv=testArgs[1:],
+            workDir=workDir,
+            environment=environment,
+            clientType=self._language,
+            forProject=False,
+        )
+
     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
+        if self.__debugger is not None:
+            self.__debugger.debuggingFinished.disconnect(self.finished)
+            self.__debugger = None
 
     def readAllOutput(self, process=None):
         """

eric ide

mercurial