refactor: move loader to new fiel

- barebones TestResult class
 - removed debugging statements
This commit is contained in:
ruthra kumar 2025-05-29 16:08:51 +05:30
parent c659f9021a
commit 4640a78587
3 changed files with 129 additions and 81 deletions

View file

@ -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
View 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

View file

@ -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)}")