--- a/src/eric7/Testing/Interfaces/TestExecutorBase.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/Testing/Interfaces/TestExecutorBase.py Wed Jul 13 14:55:47 2022 +0200 @@ -21,6 +21,7 @@ """ Class defining the supported result categories. """ + RUNNING = 0 FAIL = 1 OK = 2 @@ -33,17 +34,18 @@ """ 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 + + 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 @@ -51,22 +53,23 @@ """ 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 + + 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. @@ -87,6 +90,7 @@ @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) @@ -96,29 +100,29 @@ 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 @@ -130,14 +134,14 @@ 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 @@ -147,14 +151,14 @@ 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 @@ -163,13 +167,13 @@ 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 @@ -178,13 +182,13 @@ 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 @@ -193,25 +197,24 @@ @rtype QProcess """ process = QProcess(self) - process.setProcessChannelMode( - QProcess.ProcessChannelMode.MergedChannels) + process.setProcessChannelMode(QProcess.ProcessChannelMode.MergedChannels) process.setWorkingDirectory(workDir) process.finished.connect(self.finished) if pythonpath: env = QProcessEnvironment.systemEnvironment() - currentPythonPath = env.value('PYTHONPATH', None) + currentPythonPath = env.value("PYTHONPATH", None) newPythonPath = os.pathsep.join(pythonpath) if currentPythonPath: newPythonPath += os.pathsep + currentPythonPath - env.insert('PYTHONPATH', newPythonPath) + 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 @@ -220,8 +223,8 @@ """ workDir = ( config.discoveryStart - if config.discover else - os.path.dirname(config.testFilename) + if config.discover + else os.path.dirname(config.testFilename) ) self.__process = self._prepareProcess(workDir, pythonpath) testArgs = self.createArguments(config) @@ -230,23 +233,23 @@ 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 @@ -255,25 +258,24 @@ if process is None: process = self.__process output = ( - str(process.readAllStandardOutput(), + str( + process.readAllStandardOutput(), Preferences.getSystem("IOEncoding"), - 'replace').strip() - if process else - "" + "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 - ): + 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()