--- a/src/eric7/Testing/Interfaces/PytestExecutor.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/Testing/Interfaces/PytestExecutor.py Wed Jul 13 14:55:47 2022 +0200 @@ -22,20 +22,21 @@ """ 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"), @@ -43,14 +44,14 @@ "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 @@ -67,14 +68,14 @@ 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 @@ -82,15 +83,14 @@ """ versions = self.getVersions(interpreter) if "plugins" in versions: - return any(plugin["name"] == "pytest-cov" - for plugin in versions["plugins"]) - + 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 @@ -108,38 +108,36 @@ 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=" - ]) + 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(".", "::") - )) + 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 @@ -147,75 +145,69 @@ """ 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))) - + 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.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"]), - "") - ]) - + self.collected.emit( + [(data["nodeid"], self.__nodeid2testname(data["nodeid"]), "")] + ) + # test started elif data["event"] == "starttest": self.startTest.emit( - (data["nodeid"], - self.__nodeid2testname(data["nodeid"]), - "") + (data["nodeid"], self.__nodeid2testname(data["nodeid"]), "") ) - + # test result elif data["event"] == "result": if data["status"] in ("failed", "xpassed") or data["with_error"]: @@ -224,81 +216,82 @@ category = TestResultCategory.OK else: category = TestResultCategory.SKIP - + status = ( self.tr("Error") - if data["with_error"] else - self.__statusDisplayMapping[data["status"]] + 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) + 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) + 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 - )) - + + 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("\\", ".")) - + 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