Skip to content

Commit 10a3534

Browse files
committed
refactor: split with_test_context into smaller helper functions to reduce complexity
1 parent 1ba1a36 commit 10a3534

File tree

1 file changed

+156
-106
lines changed

1 file changed

+156
-106
lines changed

tests/test_dpi.py

Lines changed: 156 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,152 @@ def create_mock_root_without_dpi_scaling():
610610

611611

612612
# Comprehensive test context helper
613+
def _setup_wintypes_mock(wintypes_mock):
614+
"""Setup wintypes mock context if specified."""
615+
if not wintypes_mock:
616+
return None
617+
return WintypesPointerMockContext(
618+
wintypes_mock.get('hwnd', 12345),
619+
wintypes_mock.get('monitor_handle', 2),
620+
wintypes_mock.get('x_dpi', 96),
621+
wintypes_mock.get('y_dpi', 96)
622+
)
623+
624+
625+
def _setup_windll_config(patch_ctx, windll_config):
626+
"""Setup windll configuration if specified."""
627+
if not windll_config:
628+
return
629+
patch_ctx.get_windll().shcore.GetDpiForMonitor.return_value = windll_config.get('get_dpi_return')
630+
patch_ctx.get_windll().shcore.GetScaleFactorForDevice.return_value = windll_config.get('get_scale_return', 200)
631+
632+
633+
def _setup_tkinter_root():
634+
"""Setup tkinter root if needed."""
635+
try:
636+
# Check if Tkinter is already running
637+
try:
638+
existing_root = tk._default_root
639+
if existing_root is not None:
640+
# If there's already a root, use it
641+
root = existing_root
642+
else:
643+
# Create a new root
644+
root = tk.Tk()
645+
root.withdraw()
646+
except Exception as exc:
647+
# If there's any issue, try to create a new root
648+
logging.debug("Failed to use existing root, creating new one: %s", exc)
649+
root = tk.Tk()
650+
root.withdraw()
651+
652+
# Test if the root is actually working
653+
root.update_idletasks()
654+
return root
655+
except (tk.TclError, RuntimeError, Exception) as e:
656+
# If Tkinter is not available, skip the test
657+
pytest.skip(f"Tkinter not available: {e}")
658+
659+
660+
def _apply_patches(manager, patches):
661+
"""Apply patches to manager methods."""
662+
patch_contexts = []
663+
if not patches:
664+
return patch_contexts
665+
666+
for patch_config in patches:
667+
target = patch_config['target']
668+
method_name = target.split('.')[-1]
669+
patch_ctx_obj = patch.object(
670+
manager, method_name,
671+
return_value=patch_config.get('return_value'),
672+
side_effect=patch_config.get('side_effect')
673+
)
674+
patch_contexts.append(patch_ctx_obj)
675+
return patch_contexts
676+
677+
678+
def _apply_tkinter_patches(manager, tkinter_patches, original_methods):
679+
"""Apply tkinter patches and store original methods."""
680+
if not tkinter_patches:
681+
return
682+
683+
for patch_config in tkinter_patches:
684+
tk_class = patch_config['class']
685+
method_name = patch_config['method']
686+
scaling_factor = patch_config.get('scaling_factor', 2.0)
687+
688+
original_method = getattr(tk_class, method_name)
689+
original_methods[(tk_class, method_name)] = original_method
690+
691+
# Apply the patch
692+
if method_name == '__init__':
693+
# For constructors, use the unified method
694+
if tk_class.__name__ == 'Button':
695+
patch_method = getattr(manager, '_patch_button_constructor')
696+
patch_method(scaling_factor)
697+
else:
698+
# For other constructors, use the unified method
699+
patch_method = getattr(manager, '_apply_standard_widget_patch')
700+
patch_method(tk_class, scaling_factor)
701+
else:
702+
# For layout methods, add '_method' suffix
703+
patch_method = getattr(manager, f'_patch_{method_name}_method')
704+
patch_method(scaling_factor)
705+
706+
707+
def _apply_treeview_patches(treeview_patches, original_methods):
708+
"""Apply treeview patches and store original methods."""
709+
if not treeview_patches:
710+
return
711+
712+
from tkinter import ttk
713+
if 'column' in treeview_patches:
714+
original_methods[(ttk.Treeview, 'column')] = ttk.Treeview.column
715+
if 'style' in treeview_patches:
716+
original_methods[(ttk.Style, 'configure')] = ttk.Style.configure
717+
718+
719+
def _prepare_test_args(self, manager, patch_ctx, wintypes_ctx, root, args, test_func, is_windows):
720+
"""Prepare test arguments based on test function signature."""
721+
test_args = [self, manager, patch_ctx]
722+
if wintypes_ctx:
723+
test_args.append(wintypes_ctx)
724+
if root:
725+
test_args.insert(1, root) # Insert root after self
726+
727+
# Add commonly needed mock objects if test function expects them
728+
import inspect
729+
sig = inspect.signature(test_func)
730+
param_names = list(sig.parameters.keys())
731+
732+
# Add mock objects if they are expected by the test function
733+
if 'mock_root' in param_names:
734+
test_args.append(create_basic_mock_root())
735+
if 'mock_windll' in param_names:
736+
test_args.append(patch_ctx.get_windll() if is_windows else None)
737+
if 'mock_dpi_values' in param_names:
738+
test_args.append((192, 192, 2.0) if is_windows else (96, 96, 1.0))
739+
740+
test_args.extend(args)
741+
return test_args
742+
743+
744+
def _cleanup_test_context(original_methods, root):
745+
"""Cleanup test context by restoring original methods and destroying root."""
746+
# Restore original methods
747+
for (tk_class, method_name), original_method in original_methods.items():
748+
setattr(tk_class, method_name, original_method)
749+
750+
# Cleanup root
751+
if root:
752+
try:
753+
root.destroy()
754+
except Exception as e:
755+
# Log cleanup errors but don't fail the test
756+
print(f"Warning: Failed to destroy root in cleanup: {e}")
757+
758+
613759
def with_test_context(
614760
is_windows=True,
615761
wintypes_mock=None,
@@ -652,97 +798,28 @@ def wrapper(self, *args, **kwargs):
652798
manager = create_dpi_manager_for_test()
653799

654800
# Setup wintypes mock if specified
655-
wintypes_ctx = None
656-
if wintypes_mock:
657-
wintypes_ctx = WintypesPointerMockContext(
658-
wintypes_mock.get('hwnd', 12345),
659-
wintypes_mock.get('monitor_handle', 2),
660-
wintypes_mock.get('x_dpi', 96),
661-
wintypes_mock.get('y_dpi', 96)
662-
)
801+
wintypes_ctx = _setup_wintypes_mock(wintypes_mock)
663802

664803
# Setup windll config if specified
665-
windll_ctx = None
666-
if windll_config:
667-
# Configure windll directly in patch_ctx
668-
patch_ctx.get_windll().shcore.GetDpiForMonitor.return_value = windll_config.get('get_dpi_return')
669-
patch_ctx.get_windll().shcore.GetScaleFactorForDevice.return_value = windll_config.get('get_scale_return', 200)
804+
_setup_windll_config(patch_ctx, windll_config)
670805

671806
# Setup tkinter root if specified
672807
root = None
673808
if real_tkinter_root:
674-
try:
675-
# Check if Tkinter is already running
676-
try:
677-
existing_root = tk._default_root
678-
if existing_root is not None:
679-
# If there's already a root, use it
680-
root = existing_root
681-
else:
682-
# Create a new root
683-
root = tk.Tk()
684-
root.withdraw()
685-
except Exception as exc:
686-
# If there's any issue, try to create a new root
687-
logging.debug("Failed to use existing root, creating new one: %s", exc)
688-
root = tk.Tk()
689-
root.withdraw()
690-
691-
# Test if the root is actually working
692-
root.update_idletasks()
693-
except (tk.TclError, RuntimeError, Exception) as e:
694-
# If Tkinter is not available, skip the test
695-
pytest.skip(f"Tkinter not available: {e}")
809+
root = _setup_tkinter_root()
696810

697811
# Store original methods for restoration
698812
original_methods = {}
699813

700814
try:
701815
# Apply patches
702-
patch_contexts = []
703-
if patches:
704-
for patch_config in patches:
705-
target = patch_config['target']
706-
method_name = target.split('.')[-1]
707-
patch_ctx_obj = patch.object(
708-
manager, method_name,
709-
return_value=patch_config.get('return_value'),
710-
side_effect=patch_config.get('side_effect')
711-
)
712-
patch_contexts.append(patch_ctx_obj)
816+
patch_contexts = _apply_patches(manager, patches)
713817

714818
# Apply tkinter patches
715-
if tkinter_patches:
716-
for patch_config in tkinter_patches:
717-
tk_class = patch_config['class']
718-
method_name = patch_config['method']
719-
scaling_factor = patch_config.get('scaling_factor', 2.0)
720-
721-
original_method = getattr(tk_class, method_name)
722-
original_methods[(tk_class, method_name)] = original_method
723-
724-
# Apply the patch
725-
if method_name == '__init__':
726-
# For constructors, use the unified method
727-
if tk_class.__name__ == 'Button':
728-
patch_method = getattr(manager, '_patch_button_constructor')
729-
patch_method(scaling_factor)
730-
else:
731-
# For other constructors, use the unified method
732-
patch_method = getattr(manager, '_apply_standard_widget_patch')
733-
patch_method(tk_class, scaling_factor)
734-
else:
735-
# For layout methods, add '_method' suffix
736-
patch_method = getattr(manager, f'_patch_{method_name}_method')
737-
patch_method(scaling_factor)
819+
_apply_tkinter_patches(manager, tkinter_patches, original_methods)
738820

739821
# Apply treeview patches
740-
if treeview_patches:
741-
from tkinter import ttk
742-
if 'column' in treeview_patches:
743-
original_methods[(ttk.Treeview, 'column')] = ttk.Treeview.column
744-
if 'style' in treeview_patches:
745-
original_methods[(ttk.Style, 'configure')] = ttk.Style.configure
822+
_apply_treeview_patches(treeview_patches, original_methods)
746823

747824
# Execute test with all contexts
748825
contexts = [patch_ctx]
@@ -755,41 +832,14 @@ def wrapper(self, *args, **kwargs):
755832
stack.enter_context(ctx)
756833

757834
# Prepare test arguments
758-
test_args = [self, manager, patch_ctx]
759-
if wintypes_ctx:
760-
test_args.append(wintypes_ctx)
761-
if root:
762-
test_args.insert(1, root) # Insert root after self
763-
764-
# Add commonly needed mock objects if test function expects them
765-
import inspect
766-
sig = inspect.signature(test_func)
767-
param_names = list(sig.parameters.keys())
768-
769-
# Add mock objects if they are expected by the test function
770-
if 'mock_root' in param_names:
771-
test_args.append(create_basic_mock_root())
772-
if 'mock_windll' in param_names:
773-
test_args.append(patch_ctx.get_windll() if is_windows else None)
774-
if 'mock_dpi_values' in param_names:
775-
test_args.append((192, 192, 2.0) if is_windows else (96, 96, 1.0))
776-
777-
test_args.extend(args)
835+
test_args = _prepare_test_args(
836+
self, manager, patch_ctx, wintypes_ctx, root, args, test_func, is_windows
837+
)
778838

779839
return test_func(*test_args, **kwargs)
780840

781841
finally:
782-
# Restore original methods
783-
for (tk_class, method_name), original_method in original_methods.items():
784-
setattr(tk_class, method_name, original_method)
785-
786-
# Cleanup root
787-
if root:
788-
try:
789-
root.destroy()
790-
except Exception as e:
791-
# Log cleanup errors but don't fail the test
792-
print(f"Warning: Failed to destroy root in cleanup: {e}")
842+
_cleanup_test_context(original_methods, root)
793843

794844
return wrapper
795845
return decorator

0 commit comments

Comments
 (0)