refactor: move loader to new fiel
- barebones TestResult class - removed debugging statements
This commit is contained in:
parent
c659f9021a
commit
4640a78587
3 changed files with 129 additions and 81 deletions
|
|
@ -1,17 +1,17 @@
|
|||
import importlib
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
from collections import deque
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import click
|
||||
|
||||
import frappe
|
||||
from frappe.commands import get_site, pass_context
|
||||
from frappe.testing.config import TestParameters
|
||||
from frappe.testing.loader import FrappeTestLoader
|
||||
from frappe.testing.result import FrappeTestResult
|
||||
from frappe.utils.bench_helper import CliCtxObj
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -40,8 +40,6 @@ def main(
|
|||
) -> None:
|
||||
"""Main function to run tests"""
|
||||
if light:
|
||||
from frappe.modules.utils import get_module_name
|
||||
from frappe.testing.config import TestParameters
|
||||
from frappe.testing.environment import _disable_scheduler_if_needed
|
||||
|
||||
test_params = TestParameters(
|
||||
|
|
@ -69,83 +67,8 @@ def main(
|
|||
_disable_scheduler_if_needed()
|
||||
frappe.clear_cache()
|
||||
|
||||
class FrappeTestLoader(unittest.TestLoader):
|
||||
def recursive_load_suites_in_pymodule(self, suite):
|
||||
suites_queue = deque([suite])
|
||||
while suites_queue:
|
||||
suite = suites_queue.popleft()
|
||||
for elem in suite:
|
||||
if elem.countTestCases():
|
||||
if isinstance(elem, unittest.TestSuite):
|
||||
suites_queue.append(elem)
|
||||
elif isinstance(elem, unittest.TestCase):
|
||||
if self.params.tests:
|
||||
if elem._testMethodName in self.params.tests:
|
||||
self.testsuite.addTest(elem)
|
||||
else:
|
||||
self.testsuite.addTest(elem)
|
||||
|
||||
def load_testsuites_in_pymodule(self, file_modules):
|
||||
for module in file_modules:
|
||||
suite = unittest.defaultTestLoader.loadTestsFromModule(module)
|
||||
self.recursive_load_suites_in_pymodule(suite)
|
||||
|
||||
def load_pymodule_for_files(self, files: list):
|
||||
"""
|
||||
files: list of tuple of (Path, str)
|
||||
"""
|
||||
_file_modules = []
|
||||
for app_path, test_file in files:
|
||||
module_name = (
|
||||
f"{'.'.join(test_file.relative_to(app_path.parent).parent.parts)}.{test_file.stem}"
|
||||
)
|
||||
module = importlib.import_module(module_name)
|
||||
_file_modules.append(module)
|
||||
return _file_modules
|
||||
|
||||
def get_files(self, apps: list) -> list:
|
||||
files = []
|
||||
for app in apps:
|
||||
app_path = Path(frappe.get_app_path(app))
|
||||
for test_file in app_path.glob("**/test_*.py"):
|
||||
files.append((app_path, test_file))
|
||||
return files
|
||||
|
||||
def discover_tests(self, params: TestParameters) -> unittest.TestSuite:
|
||||
self.params = params
|
||||
self.testsuite = unittest.TestSuite()
|
||||
|
||||
if self.params.tests:
|
||||
# handle --test; highest priority; will ignore --doctype and --app
|
||||
files = self.get_files(frappe.get_installed_apps())
|
||||
file_pymodules = self.load_pymodule_for_files(files)
|
||||
self.load_testsuites_in_pymodule(file_pymodules)
|
||||
|
||||
elif self.params.doctype:
|
||||
# handle --doctype; will ignore --app
|
||||
module = frappe.get_cached_value("DocType", self.params.doctype, "module")
|
||||
app = frappe.get_cached_value("Module Def", module, "app_name")
|
||||
pymodule_name = get_module_name(self.params.doctype, module, "test_", app=app)
|
||||
pymodule = importlib.import_module(pymodule_name)
|
||||
self.load_testsuites_in_pymodule([pymodule])
|
||||
|
||||
elif self.params.app:
|
||||
# handle --app
|
||||
files = self.get_files([self.params.app])
|
||||
file_pymodules = self.load_pymodule_for_files(files)
|
||||
self.load_testsuites_in_pymodule(file_pymodules)
|
||||
|
||||
elif self.params.module:
|
||||
# handle --module; supports --test as well
|
||||
pymodule = importlib.import_module(self.params.module)
|
||||
self.load_testsuites_in_pymodule([pymodule])
|
||||
|
||||
return self.testsuite
|
||||
|
||||
suite = FrappeTestLoader().discover_tests(test_params)
|
||||
print("Test Cases:", suite.countTestCases())
|
||||
res = unittest.TextTestRunner().run(suite)
|
||||
print("Result:", res)
|
||||
res = unittest.TextTestRunner(resultclass=FrappeTestResult).run(suite)
|
||||
|
||||
else:
|
||||
import logging
|
||||
|
|
|
|||
80
frappe/testing/loader.py
Normal file
80
frappe/testing/loader.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import importlib
|
||||
import unittest
|
||||
from collections import deque
|
||||
from pathlib import Path
|
||||
|
||||
import frappe
|
||||
from frappe.modules.utils import get_module_name
|
||||
from frappe.testing.config import TestParameters
|
||||
|
||||
|
||||
class FrappeTestLoader(unittest.TestLoader):
|
||||
def recursive_load_suites_in_pymodule(self, suite):
|
||||
suites_queue = deque([suite])
|
||||
while suites_queue:
|
||||
suite = suites_queue.popleft()
|
||||
for elem in suite:
|
||||
if elem.countTestCases():
|
||||
if isinstance(elem, unittest.TestSuite):
|
||||
suites_queue.append(elem)
|
||||
elif isinstance(elem, unittest.TestCase):
|
||||
if self.params.tests:
|
||||
if elem._testMethodName in self.params.tests:
|
||||
self.testsuite.addTest(elem)
|
||||
else:
|
||||
self.testsuite.addTest(elem)
|
||||
|
||||
def load_testsuites_in_pymodule(self, file_modules):
|
||||
for module in file_modules:
|
||||
suite = unittest.defaultTestLoader.loadTestsFromModule(module)
|
||||
self.recursive_load_suites_in_pymodule(suite)
|
||||
|
||||
def load_pymodule_for_files(self, files: list):
|
||||
"""
|
||||
files: list of tuple of (Path, str)
|
||||
"""
|
||||
_file_modules = []
|
||||
for app_path, test_file in files:
|
||||
module_name = f"{'.'.join(test_file.relative_to(app_path.parent).parent.parts)}.{test_file.stem}"
|
||||
module = importlib.import_module(module_name)
|
||||
_file_modules.append(module)
|
||||
return _file_modules
|
||||
|
||||
def get_files(self, apps: list) -> list:
|
||||
files = []
|
||||
for app in apps:
|
||||
app_path = Path(frappe.get_app_path(app))
|
||||
for test_file in app_path.glob("**/test_*.py"):
|
||||
files.append((app_path, test_file))
|
||||
return files
|
||||
|
||||
def discover_tests(self, params: TestParameters) -> unittest.TestSuite:
|
||||
self.params = params
|
||||
self.testsuite = unittest.TestSuite()
|
||||
|
||||
if self.params.tests:
|
||||
# handle --test; highest priority; will ignore --doctype and --app
|
||||
files = self.get_files(frappe.get_installed_apps())
|
||||
file_pymodules = self.load_pymodule_for_files(files)
|
||||
self.load_testsuites_in_pymodule(file_pymodules)
|
||||
|
||||
elif self.params.doctype:
|
||||
# handle --doctype; will ignore --app
|
||||
module = frappe.get_cached_value("DocType", self.params.doctype, "module")
|
||||
app = frappe.get_cached_value("Module Def", module, "app_name")
|
||||
pymodule_name = get_module_name(self.params.doctype, module, "test_", app=app)
|
||||
pymodule = importlib.import_module(pymodule_name)
|
||||
self.load_testsuites_in_pymodule([pymodule])
|
||||
|
||||
elif self.params.app:
|
||||
# handle --app
|
||||
files = self.get_files([self.params.app])
|
||||
file_pymodules = self.load_pymodule_for_files(files)
|
||||
self.load_testsuites_in_pymodule(file_pymodules)
|
||||
|
||||
elif self.params.module:
|
||||
# handle --module; supports --test as well
|
||||
pymodule = importlib.import_module(self.params.module)
|
||||
self.load_testsuites_in_pymodule([pymodule])
|
||||
|
||||
return self.testsuite
|
||||
|
|
@ -178,3 +178,48 @@ class TestResult(unittest.TextTestResult):
|
|||
|
||||
|
||||
SLOW_TEST_THRESHOLD = 2
|
||||
|
||||
|
||||
class FrappeTestResult(unittest.TextTestResult):
|
||||
def __init__(self, stream, descriptions, verbosity):
|
||||
super().__init__(stream, descriptions, verbosity)
|
||||
|
||||
def startTest(self, test):
|
||||
self.tb_locals = True
|
||||
self._started_at = time.monotonic()
|
||||
super().startTest(test)
|
||||
super(unittest.TextTestResult, self).startTest(test)
|
||||
test_class = unittest.util.strclass(test.__class__)
|
||||
if not hasattr(self, "current_test_class") or self.current_test_class != test_class:
|
||||
click.echo(f"\n{unittest.util.strclass(test.__class__)}")
|
||||
self.current_test_class = test_class
|
||||
|
||||
def getTestMethodName(self, test):
|
||||
return test._testMethodName if hasattr(test, "_testMethodName") else str(test)
|
||||
|
||||
def addSuccess(self, test):
|
||||
super(unittest.TextTestResult, self).addSuccess(test)
|
||||
elapsed = time.time() - self._started_at
|
||||
threshold_passed = elapsed >= SLOW_TEST_THRESHOLD
|
||||
elapsed = click.style(f" ({elapsed:.03}s)", fg="red") if threshold_passed else ""
|
||||
click.echo(f" {click.style(' ✔ ', fg='green')} {self.getTestMethodName(test)}{elapsed}")
|
||||
|
||||
def addError(self, test, err):
|
||||
super(unittest.TextTestResult, self).addError(test, err)
|
||||
click.echo(f" {click.style(' ✖ ', fg='red')} {self.getTestMethodName(test)}")
|
||||
|
||||
def addFailure(self, test, err):
|
||||
super(unittest.TextTestResult, self).addFailure(test, err)
|
||||
click.echo(f" {click.style(' ✖ ', fg='red')} {self.getTestMethodName(test)}")
|
||||
|
||||
def addSkip(self, test, reason):
|
||||
super(unittest.TextTestResult, self).addSkip(test, reason)
|
||||
click.echo(f" {click.style(' = ', fg='white')} {self.getTestMethodName(test)}")
|
||||
|
||||
def addExpectedFailure(self, test, err):
|
||||
super(unittest.TextTestResult, self).addExpectedFailure(test, err)
|
||||
click.echo(f" {click.style(' ✖ ', fg='red')} {self.getTestMethodName(test)}")
|
||||
|
||||
def addUnexpectedSuccess(self, test):
|
||||
super(unittest.TextTestResult, self).addUnexpectedSuccess(test)
|
||||
click.echo(f" {click.style(' ✔ ', fg='green')} {self.getTestMethodName(test)}")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue