44import json
55import os
66import sys
7- from contextlib import redirect_stderr , redirect_stdout , suppress
7+ from contextlib import contextmanager , redirect_stderr , redirect_stdout , suppress
88from dataclasses import asdict , dataclass
9+ from inspect import getmodule as inspect_getmodule
910from io import StringIO
1011from pathlib import Path
12+ from types import ModuleType
1113from typing import Callable , Literal , Optional
1214from unittest .mock import patch
1315
@@ -29,10 +31,11 @@ def get_cli_stdout(*args, **kwargs) -> str:
2931# failure cases
3032
3133
34+ @pytest .mark .parametrize ("cli_fn" , [CLI , auto_cli ])
3235@pytest .mark .parametrize ("components" , [0 , [], {"x" : 0 }])
33- def test_unexpected_components (components ):
36+ def test_unexpected_components (cli_fn , components ):
3437 with pytest .raises (ValueError ):
35- auto_cli (components )
38+ cli_fn (components )
3639
3740
3841class ConflictingSubcommandKey :
@@ -340,40 +343,63 @@ def test_function_and_class_function_without_parameters():
340343# automatic components tests
341344
342345
343- def test_automatic_components_empty_context ():
346+ @contextmanager
347+ def mock_getmodule_locals (parent_fn , locals_list = []):
348+ module_name = "_" + parent_fn .__name__
349+
350+ mock_module = ModuleType (module_name )
351+ for obj in locals_list + [CLI , auto_cli ]:
352+ setattr (mock_module , obj .__name__ , obj )
353+ sys .modules [module_name ] = mock_module
354+
355+ for obj in locals_list :
356+ obj .__module__ = module_name
357+
358+ def patched_getmodule (obj , * args ):
359+ if obj in locals_list or (parent_fn .__name__ in str (obj )):
360+ return mock_module
361+ return inspect_getmodule (obj , * args )
362+
363+ with patch ("inspect.getmodule" , side_effect = patched_getmodule ):
364+ yield
365+ del sys .modules [module_name ]
366+
367+
368+ @pytest .mark .parametrize ("cli_fn" , [CLI , auto_cli ])
369+ def test_automatic_components_empty_context (cli_fn ):
344370 def empty_context ():
345- auto_cli ()
371+ cli_fn ()
346372
347- with patch ( "inspect.getmodule" ) as mock_getmodule :
348- mock_getmodule . return_value = sys . modules [ "jsonargparse._core" ]
349- pytest . raises ( ValueError , empty_context )
373+ with mock_getmodule_locals ( empty_context ) :
374+ with pytest . raises ( ValueError , match = "Either components parameter must be given or" ):
375+ empty_context ( )
350376
351377
352- def test_automatic_components_context_function ():
353- def non_empty_context_function ( ):
354- def function (a1 : float ):
355- return a1
378+ @ pytest . mark . parametrize ( "cli_fn" , [ CLI , auto_cli ])
379+ def test_automatic_components_context_function ( cli_fn ):
380+ def function (a1 : float ):
381+ return a1
356382
357- return auto_cli (args = ["6.7" ])
383+ def non_empty_context_function ():
384+ return cli_fn (args = ["6.7" ])
358385
359- with patch ("inspect.getmodule" ) as mock_getmodule :
360- mock_getmodule .return_value = sys .modules ["jsonargparse._core" ]
386+ with mock_getmodule_locals (non_empty_context_function , [function ]):
361387 assert 6.7 == non_empty_context_function ()
362388
363389
364- def test_automatic_components_context_class ():
365- def non_empty_context_class ( ):
366- class ClassX :
367- def __init__ (self , i1 : str ):
368- self .i1 = i1
390+ @ pytest . mark . parametrize ( "cli_fn" , [ CLI , auto_cli ])
391+ def test_automatic_components_context_class ( cli_fn ):
392+ class ClassX :
393+ def __init__ (self , i1 : str ):
394+ self .i1 = i1
369395
370- def method (self , m1 : int ):
371- return self .i1 , m1
396+ def method (self , m1 : int ):
397+ return self .i1 , m1
372398
373- return auto_cli (args = ["a" , "method" , "2" ])
399+ def non_empty_context_class ():
400+ return cli_fn (args = ["a" , "method" , "2" ])
374401
375- with patch ("inspect.getmodule" ) as mock_getmodule :
376- mock_getmodule .return_value = sys .modules ["jsonargparse._core" ]
402+ with mock_getmodule_locals (non_empty_context_class , [ClassX ]):
377403 assert ("a" , 2 ) == non_empty_context_class ()
378404
379405
0 commit comments