Skip to content

Commit 354227a

Browse files
mariocj89ncoghlan
authored andcommitted
Add option to trace to run modules (GH-5134)
Adds a new option in trace that allows tracing runnable modules. It is exposed as `--module module_name` as `-m` is already in use for another argument.
1 parent 664fe39 commit 354227a

File tree

4 files changed

+41
-16
lines changed

4 files changed

+41
-16
lines changed

Doc/library/trace.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ all Python modules imported during the execution into the current directory.
4242

4343
Display the version of the module and exit.
4444

45+
.. versionadded:: 3.8
46+
Added ``--module`` option that allows to run an executable module.
47+
4548
Main options
4649
^^^^^^^^^^^^
4750

Lib/test/test_trace.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ class TestCommandLine(unittest.TestCase):
474474

475475
def test_failures(self):
476476
_errors = (
477-
(b'filename is missing: required with the main options', '-l', '-T'),
477+
(b'progname is missing: required with the main options', '-l', '-T'),
478478
(b'cannot specify both --listfuncs and (--trace or --count)', '-lc'),
479479
(b'argument -R/--no-report: not allowed with argument -r/--report', '-rR'),
480480
(b'must specify one of --trace, --count, --report, --listfuncs, or --trackcalls', '-g'),
@@ -524,5 +524,10 @@ def f():
524524
self.assertIn('lines cov% module (path)', stdout)
525525
self.assertIn(f'6 100% {TESTFN} ({filename})', stdout)
526526

527+
def test_run_as_module(self):
528+
assert_python_ok('-m', 'trace', '-l', '--module', 'timeit', '-n', '1')
529+
assert_python_failure('-m', 'trace', '-l', '--module', 'not_a_module_zzz')
530+
531+
527532
if __name__ == '__main__':
528533
unittest.main()

Lib/trace.py

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,9 @@ def main():
666666
help='Ignore files in the given directory '
667667
'(multiple directories can be joined by os.pathsep).')
668668

669-
parser.add_argument('filename', nargs='?',
669+
parser.add_argument('--module', action='store_true', default=False,
670+
help='Trace a module. ')
671+
parser.add_argument('progname', nargs='?',
670672
help='file to run as main program')
671673
parser.add_argument('arguments', nargs=argparse.REMAINDER,
672674
help='arguments to the program')
@@ -704,26 +706,40 @@ def parse_ignore_dir(s):
704706
if opts.summary and not opts.count:
705707
parser.error('--summary can only be used with --count or --report')
706708

707-
if opts.filename is None:
708-
parser.error('filename is missing: required with the main options')
709-
710-
sys.argv = [opts.filename, *opts.arguments]
711-
sys.path[0] = os.path.dirname(opts.filename)
709+
if opts.progname is None:
710+
parser.error('progname is missing: required with the main options')
712711

713712
t = Trace(opts.count, opts.trace, countfuncs=opts.listfuncs,
714713
countcallers=opts.trackcalls, ignoremods=opts.ignore_module,
715714
ignoredirs=opts.ignore_dir, infile=opts.file,
716715
outfile=opts.file, timing=opts.timing)
717716
try:
718-
with open(opts.filename) as fp:
719-
code = compile(fp.read(), opts.filename, 'exec')
720-
# try to emulate __main__ namespace as much as possible
721-
globs = {
722-
'__file__': opts.filename,
723-
'__name__': '__main__',
724-
'__package__': None,
725-
'__cached__': None,
726-
}
717+
if opts.module:
718+
import runpy
719+
module_name = opts.progname
720+
mod_name, mod_spec, code = runpy._get_module_details(module_name)
721+
sys.argv = [code.co_filename, *opts.arguments]
722+
globs = {
723+
'__name__': '__main__',
724+
'__file__': code.co_filename,
725+
'__package__': mod_spec.parent,
726+
'__loader__': mod_spec.loader,
727+
'__spec__': mod_spec,
728+
'__cached__': None,
729+
}
730+
else:
731+
sys.argv = [opts.progname, *opts.arguments]
732+
sys.path[0] = os.path.dirname(opts.progname)
733+
734+
with open(opts.progname) as fp:
735+
code = compile(fp.read(), opts.progname, 'exec')
736+
# try to emulate __main__ namespace as much as possible
737+
globs = {
738+
'__file__': opts.progname,
739+
'__name__': '__main__',
740+
'__package__': None,
741+
'__cached__': None,
742+
}
727743
t.runctx(code, globs, globs)
728744
except OSError as err:
729745
sys.exit("Cannot run file %r because: %s" % (sys.argv[0], err))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
trace.py can now run modules via python3 -m trace -t --module module_name

0 commit comments

Comments
 (0)