@@ -525,3 +525,150 @@ def test_autoprofile_exec_module(
525525 assert ('Function: add_four' in raw_output ) == add_four
526526 assert ('Function: add_operator' in raw_output ) == add_operator
527527 assert ('Function: _main' in raw_output ) == main
528+
529+
530+ @pytest .mark .parametrize (
531+ ['prof_mod' , 'eager_preimports' ,
532+ 'add_one' , 'add_two' , 'add_three' , 'add_four' , 'add_operator' , 'main' ],
533+ # Test that `--eager-preimports` know to exclude the script run
534+ # (so as not to inadvertantly run it twice)
535+ [('script.py' , None , False , False , False , False , False , True ),
536+ (None , 'script.py' , False , False , False , False , False , True ),
537+ # Test explicitly passing targets to `--eager-preimports`
538+ (['test_mod.submod1,test_mod.submod2' , 'test_mod.subpkg.submod4' ], None ,
539+ True , True , False , False , True , False ),
540+ (['test_mod.submod1,test_mod.submod2' ], ['test_mod.subpkg.submod4' ],
541+ True , True , False , True , True , False ),
542+ (None , ['test_mod.submod1,test_mod.submod2' , 'test_mod.subpkg.submod4' ],
543+ True , True , False , True , True , False ),
544+ # Test implicitly passing targets to `--eager-preimports`
545+ (['test_mod.submod1,test_mod.submod2' , 'test_mod.subpkg.submod4' ], True ,
546+ True , True , False , True , True , False )])
547+ def test_autoprofile_eager_preimports (
548+ prof_mod , eager_preimports ,
549+ add_one , add_two , add_three , add_four , add_operator , main ):
550+ """
551+ Test eager imports with the `-e`/`--eager-preimports` flag.
552+ """
553+ with tempfile .TemporaryDirectory () as tmpdir :
554+ temp_dpath = ub .Path (tmpdir )
555+ _write_demo_module (temp_dpath )
556+
557+ args = [sys .executable , '-m' , 'kernprof' ]
558+ if prof_mod is not None :
559+ if isinstance (prof_mod , str ):
560+ prof_mod = [prof_mod ]
561+ for target in prof_mod :
562+ args .extend (['-p' , target ])
563+ if eager_preimports in (True ,):
564+ args .append ('-e' )
565+ elif eager_preimports is not None :
566+ if isinstance (eager_preimports , str ):
567+ eager_preimports = [eager_preimports ]
568+ for target in eager_preimports :
569+ args .extend (['-e' , target ])
570+ args .extend (['-l' , 'script.py' ])
571+ proc = ub .cmd (args , cwd = temp_dpath , verbose = 2 )
572+ # Check that pre-imports don't accidentally run the code twice
573+ assert proc .stdout .count ('7.9' ) == 1
574+ print (proc .stdout )
575+ print (proc .stderr )
576+ proc .check_returncode ()
577+
578+ prof = temp_dpath / 'script.py.lprof'
579+
580+ args = [sys .executable , '-m' , 'line_profiler' , os .fspath (prof )]
581+ proc = ub .cmd (args , cwd = temp_dpath )
582+ raw_output = proc .stdout
583+ print (raw_output )
584+ proc .check_returncode ()
585+
586+ assert ('Function: add_one' in raw_output ) == add_one
587+ assert ('Function: add_two' in raw_output ) == add_two
588+ assert ('Function: add_three' in raw_output ) == add_three
589+ assert ('Function: add_four' in raw_output ) == add_four
590+ assert ('Function: add_operator' in raw_output ) == add_operator
591+ assert ('Function: main' in raw_output ) == main
592+
593+
594+ @pytest .mark .parametrize (
595+ ('eager_preimports, function, method, class_method, static_method, '
596+ 'descriptor' ),
597+ [('my_module' , True , True , True , True , True ),
598+ # `function()` included in profiling via `Class.partial_method()`
599+ ('my_module.Class' , True , True , True , True , True ),
600+ ('my_module.Class.descriptor' , False , False , False , False , True )])
601+ def test_autoprofile_callable_wrapper_objects (
602+ eager_preimports , function , method , class_method , static_method ,
603+ descriptor ):
604+ """
605+ Test that on-import profiling catches various callable-wrapper
606+ object types:
607+ - properties
608+ - staticmethod
609+ - classmethod
610+ - partialmethod
611+ Like it does regular methods and functions.
612+ """
613+ with tempfile .TemporaryDirectory () as tmpdir :
614+ temp_dpath = ub .Path (tmpdir )
615+ path = temp_dpath / 'path'
616+ path .mkdir ()
617+ (path / 'my_module.py' ).write_text (ub .codeblock ("""
618+ import functools
619+
620+
621+ def function(x):
622+ return
623+
624+
625+ class Class:
626+ def method(self):
627+ return
628+
629+ @classmethod
630+ def class_method(cls):
631+ return
632+
633+ @staticmethod
634+ def static_method():
635+ return
636+
637+ partial_method = functools.partial(function)
638+
639+ @property
640+ def descriptor(self):
641+ return
642+ """ ))
643+ (temp_dpath / 'script.py' ).write_text (ub .codeblock ("""
644+ import my_module
645+
646+
647+ if __name__ == '__main__':
648+ pass
649+ """ ))
650+
651+ with ub .ChDir (temp_dpath ):
652+ args = [sys .executable , '-m' , 'kernprof' ,
653+ '-e' , eager_preimports , '-lv' , 'script.py' ]
654+ python_path = os .environ .get ('PYTHONPATH' )
655+ if python_path :
656+ python_path = '{}:{}' .format (path , python_path )
657+ else :
658+ python_path = str (path )
659+ proc = ub .cmd (args ,
660+ env = {** os .environ , 'PYTHONPATH' : python_path },
661+ verbose = 2 )
662+ raw_output = proc .stdout
663+ print (raw_output )
664+ print (proc .stderr )
665+ proc .check_returncode ()
666+
667+ assert ('Function: function' in raw_output ) == function
668+ assert ('Function: method' in raw_output ) == method
669+ assert ('Function: class_method' in raw_output ) == class_method
670+ assert ('Function: static_method' in raw_output ) == static_method
671+ # `partial_method()` not included as its own item because it's a
672+ # wrapper around `function()`
673+ assert 'Function: partial_method' not in raw_output
674+ assert ('Function: descriptor' in raw_output ) == descriptor
0 commit comments