feat: better test & dumpster output (#28044)
This commit is contained in:
parent
7e8290b05f
commit
db93823001
5 changed files with 69 additions and 34 deletions
|
|
@ -33,6 +33,10 @@ class Color:
|
|||
CYAN = 96
|
||||
|
||||
|
||||
class FrappeDeprecationWarning(Warning):
|
||||
...
|
||||
|
||||
|
||||
try:
|
||||
# since python 3.13, PEP 702
|
||||
from warnings import deprecated as _deprecated
|
||||
|
|
@ -44,7 +48,7 @@ except ImportError:
|
|||
|
||||
T = TypeVar("T", bound=Callable)
|
||||
|
||||
def _deprecated(message: str, category=DeprecationWarning, stacklevel=1) -> Callable[[T], T]:
|
||||
def _deprecated(message: str, category=FrappeDeprecationWarning, stacklevel=1) -> Callable[[T], T]:
|
||||
def decorator(func: T) -> T:
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
|
|
@ -86,18 +90,14 @@ def deprecated(original: str, marked: str, graduation: str, msg: str, stacklevel
|
|||
+ colorize(caller_filepath, Color.CYAN)
|
||||
)
|
||||
|
||||
return functools.wraps(func)(
|
||||
_deprecated(
|
||||
colorize(f"`{original}`", Color.CYAN)
|
||||
+ colorize(
|
||||
f" was moved (DATE: {marked}) to frappe/deprecation_dumpster.py"
|
||||
f" for removal (from {graduation} onwards); note:\n ",
|
||||
Color.RED,
|
||||
)
|
||||
+ colorize(f"{msg}\n", Color.YELLOW),
|
||||
stacklevel=stacklevel,
|
||||
)
|
||||
)(func)
|
||||
func.__name__ = original
|
||||
wrapper = _deprecated(
|
||||
colorize(f"It was marked on {marked} for removal from {graduation} with note: ", Color.RED)
|
||||
+ colorize(f"{msg}", Color.YELLOW),
|
||||
stacklevel=stacklevel,
|
||||
)
|
||||
|
||||
return functools.update_wrapper(wrapper, func)(func)
|
||||
|
||||
return decorator
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
from dataclasses import dataclass, field
|
||||
from typing import Optional, Union
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
|
|
@ -136,5 +136,3 @@ def _add_module_tests(runner, app: str, module: str):
|
|||
|
||||
class TestRunnerError(Exception):
|
||||
"""Custom exception for test runner errors"""
|
||||
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ This TestResult class is typically used by the TestRunner to collect and display
|
|||
test results during test execution in the Frappe framework.
|
||||
"""
|
||||
|
||||
import io
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
|
||||
|
|
@ -27,6 +29,20 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class TestResult(unittest.TextTestResult):
|
||||
def __init__(self, stream, descriptions, verbosity):
|
||||
super().__init__(stream, descriptions, verbosity)
|
||||
self._old_stdout = []
|
||||
self._old_stderr = []
|
||||
|
||||
def startTestRun(self):
|
||||
if not sys.warnoptions:
|
||||
import warnings
|
||||
|
||||
from frappe.deprecation_dumpster import FrappeDeprecationWarning
|
||||
|
||||
warnings.simplefilter("ignore")
|
||||
warnings.filterwarnings("module", category=FrappeDeprecationWarning)
|
||||
|
||||
def startTest(self, test):
|
||||
self.tb_locals = True
|
||||
self._started_at = time.monotonic()
|
||||
|
|
@ -34,15 +50,32 @@ class TestResult(unittest.TextTestResult):
|
|||
test_class = unittest.util.strclass(test.__class__)
|
||||
if getattr(self, "current_test_class", None) != test_class:
|
||||
self.current_test_class = test_class
|
||||
click.echo(f"\n{unittest.util.strclass(test.__class__)}")
|
||||
logger.info(f"{unittest.util.strclass(test.__class__)}")
|
||||
self.stream.write(f"\n{test_class}\n")
|
||||
logger.info(f"{test_class}")
|
||||
if new_doctypes := getattr(test.__class__, "_newly_created_test_records", None):
|
||||
records = [f"{name} ({qty})" for name, qty in reversed(new_doctypes)]
|
||||
click.secho(
|
||||
f" Test Records created: {', '.join(records)}",
|
||||
fg="bright_black",
|
||||
)
|
||||
hint = click.style(f" Test Records created: {', '.join(records)}", fg="bright_black")
|
||||
self.stream.write(hint + "\n")
|
||||
logger.info(f"records created: {', '.join(records)}")
|
||||
self.stream.flush()
|
||||
|
||||
self._old_stderr.append(sys.stderr)
|
||||
self._old_stdout.append(sys.stdout)
|
||||
self._test_stdout_capture = io.StringIO()
|
||||
self._test_stderr_capture = io.StringIO()
|
||||
sys.stdout = self._test_stdout_capture
|
||||
sys.stderr = self._test_stderr_capture
|
||||
|
||||
def stopTest(self, test):
|
||||
super().stopTest(test)
|
||||
sys.stdout = self._old_stderr.pop()
|
||||
sys.stderr = self._old_stdout.pop()
|
||||
for line in self._test_stdout_capture.getvalue().splitlines():
|
||||
self.stream.write(f" ▹ {line}\n")
|
||||
self.stream.flush()
|
||||
for line in self._test_stderr_capture.getvalue().splitlines():
|
||||
self.stream.write(f" ▸ {line}\n")
|
||||
self.stream.flush()
|
||||
|
||||
def getTestMethodName(self, test):
|
||||
return test._testMethodName if hasattr(test, "_testMethodName") else str(test)
|
||||
|
|
@ -52,32 +85,34 @@ class TestResult(unittest.TextTestResult):
|
|||
elapsed = time.monotonic() - self._started_at
|
||||
threshold_passed = elapsed >= SLOW_TEST_THRESHOLD
|
||||
long_elapsed = click.style(f" ({elapsed:.03}s)", fg="red") if threshold_passed else ""
|
||||
click.echo(f" {click.style(' ✔ ', fg='green')} {self.getTestMethodName(test)}{long_elapsed}")
|
||||
self._write_result(test, " ✔ ", "green", long_elapsed)
|
||||
logger.debug(f"{test!s:<200} {'[success]':>20} ⌛{elapsed}")
|
||||
|
||||
def addError(self, test, err):
|
||||
super(unittest.TextTestResult, self).addError(test, err)
|
||||
click.echo(f" {click.style(' ✖ ', fg='red')} {self.getTestMethodName(test)}")
|
||||
self._write_result(test, " ✖ ", "red")
|
||||
logger.debug(f"{test!s:<200} {'[error]':>20}")
|
||||
|
||||
def addFailure(self, test, err):
|
||||
super(unittest.TextTestResult, self).addFailure(test, err)
|
||||
click.echo(f" {click.style(' ✖ ', fg='red')} {self.getTestMethodName(test)}")
|
||||
self._write_result(test, " ✖ ", "red")
|
||||
logger.debug(f"{test!s:<200} {'[failure]':>20}")
|
||||
|
||||
def addSkip(self, test, reason):
|
||||
super(unittest.TextTestResult, self).addSkip(test, reason)
|
||||
click.echo(f" {click.style(' = ', fg='white')} {self.getTestMethodName(test)}")
|
||||
self._write_result(test, " = ", "white")
|
||||
logger.debug(f"{test!s:<200} {'[skipped]':>20}")
|
||||
|
||||
def addExpectedFailure(self, test, err):
|
||||
super(unittest.TextTestResult, self).addExpectedFailure(test, err)
|
||||
click.echo(f" {click.style(' ✖ ', fg='red')} {self.getTestMethodName(test)}")
|
||||
self.stream.write("x")
|
||||
self._write_result(test, "✔ ", "green")
|
||||
logger.debug(f"{test!s:<200} {'[expected failure]':>20}")
|
||||
|
||||
def addUnexpectedSuccess(self, test):
|
||||
super(unittest.TextTestResult, self).addUnexpectedSuccess(test)
|
||||
click.echo(f" {click.style(' ✔ ', fg='green')} {self.getTestMethodName(test)}")
|
||||
self.stream.write("u")
|
||||
self._write_result(test, "✖ ", "red")
|
||||
logger.debug(f"{test!s:<200} {'[unexpected success]':>20}")
|
||||
|
||||
def printErrors(self):
|
||||
|
|
@ -95,5 +130,13 @@ class TestResult(unittest.TextTestResult):
|
|||
def __str__(self):
|
||||
return f"Tests: {self.testsRun}, Failing: {len(self.failures)}, Errors: {len(self.errors)}"
|
||||
|
||||
def _write_result(self, test, status, color, suffix=""):
|
||||
test_method = self.getTestMethodName(test)
|
||||
result = f" {click.style(status, fg=color)} {test_method}"
|
||||
result += f" {suffix}" if suffix else ""
|
||||
result += "\n"
|
||||
self.stream.write(result)
|
||||
self.stream.flush()
|
||||
|
||||
|
||||
SLOW_TEST_THRESHOLD = 2
|
||||
|
|
|
|||
|
|
@ -21,17 +21,12 @@ It can be customized through the TestConfig object passed during initialization.
|
|||
import contextlib
|
||||
import cProfile
|
||||
import logging
|
||||
import os
|
||||
import pstats
|
||||
import unittest
|
||||
from collections import defaultdict
|
||||
from collections.abc import Iterator
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
import frappe
|
||||
from frappe.tests.classes.context_managers import debug_on
|
||||
|
||||
from .config import TestConfig
|
||||
|
|
@ -59,7 +54,7 @@ class TestRunner(unittest.TextTestRunner):
|
|||
failfast=False,
|
||||
buffer=False,
|
||||
resultclass=None,
|
||||
warnings=None,
|
||||
warnings="module",
|
||||
*,
|
||||
tb_locals=False,
|
||||
cfg: TestConfig,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue