Skip to content
Prev Previous commit
Next Next commit
Address Laszlo's review
  • Loading branch information
pablogsal committed Nov 23, 2025
commit 6e4c0d90bc8d659085548ec758be93abc8804c7d
35 changes: 26 additions & 9 deletions Lib/profiling/sampling/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,15 +263,15 @@ def _add_pstats_options(parser):
"nsamples-cumul",
"name",
],
default="nsamples",
help="Sort order for pstats output",
default=None,
help="Sort order for pstats output (default: nsamples)",
)
pstats_group.add_argument(
"-l",
"--limit",
type=int,
default=15,
help="Limit the number of rows in the output",
default=None,
help="Limit the number of rows in the output (default: 15)",
)
pstats_group.add_argument(
"--no-summary",
Expand Down Expand Up @@ -343,10 +343,12 @@ def _handle_output(collector, args, pid, mode):
if args.outfile:
collector.export(args.outfile)
else:
# Print to stdout
sort_mode = _sort_to_mode(args.sort)
# Print to stdout with defaults applied
sort_choice = args.sort if args.sort is not None else "nsamples"
limit = args.limit if args.limit is not None else 15
sort_mode = _sort_to_mode(sort_choice)
collector.print_stats(
sort_mode, args.limit, not args.no_summary, mode
sort_mode, limit, not args.no_summary, mode
)
else:
# Export to file
Expand Down Expand Up @@ -374,6 +376,21 @@ def _validate_args(args, parser):
parser.error(
f"--live is incompatible with {format_flag}. Live mode uses a TUI interface."
)

# Live mode is also incompatible with pstats-specific options
issues = []
if args.sort is not None:
issues.append("--sort")
if args.limit is not None:
issues.append("--limit")
if args.no_summary:
issues.append("--no-summary")

if issues:
parser.error(
f"Options {', '.join(issues)} are incompatible with --live. "
"Live mode uses a TUI interface with its own controls."
)
return

# Validate gecko mode doesn't use non-wall mode
Expand All @@ -386,9 +403,9 @@ def _validate_args(args, parser):
# Validate pstats-specific options are only used with pstats format
if args.format != "pstats":
issues = []
if args.sort != "nsamples":
if args.sort is not None:
issues.append("--sort")
if args.limit != 15:
if args.limit is not None:
issues.append("--limit")
if args.no_summary:
issues.append("--no-summary")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -730,3 +730,53 @@ def test_script_error_treatment(self):
self.assertIn(
"No such file or directory: 'nonexistent_file.txt'", output
)

def test_live_incompatible_with_pstats_options(self):
"""Test that --live is incompatible with individual pstats options."""
test_cases = [
(["--sort", "tottime"], "--sort"),
(["--limit", "30"], "--limit"),
(["--no-summary"], "--no-summary"),
]

for args, expected_flag in test_cases:
with self.subTest(args=args):
test_args = ["profiling.sampling.cli", "run", "--live"] + args + ["test.py"]
with mock.patch("sys.argv", test_args):
with self.assertRaises(SystemExit) as cm:
from profiling.sampling.cli import main
main()
self.assertNotEqual(cm.exception.code, 0)

def test_live_incompatible_with_multiple_pstats_options(self):
"""Test that --live is incompatible with multiple pstats options."""
test_args = [
"profiling.sampling.cli", "run", "--live",
"--sort", "cumtime", "--limit", "25", "--no-summary", "test.py"
]

with mock.patch("sys.argv", test_args):
with self.assertRaises(SystemExit) as cm:
from profiling.sampling.cli import main
main()
self.assertNotEqual(cm.exception.code, 0)

def test_live_incompatible_with_pstats_default_values(self):
"""Test that --live blocks pstats options even with default values."""
# Test with --sort=nsamples (the default value)
test_args = ["profiling.sampling.cli", "run", "--live", "--sort=nsamples", "test.py"]

with mock.patch("sys.argv", test_args):
with self.assertRaises(SystemExit) as cm:
from profiling.sampling.cli import main
main()
self.assertNotEqual(cm.exception.code, 0)

# Test with --limit=15 (the default value)
test_args = ["profiling.sampling.cli", "run", "--live", "--limit=15", "test.py"]

with mock.patch("sys.argv", test_args):
with self.assertRaises(SystemExit) as cm:
from profiling.sampling.cli import main
main()
self.assertNotEqual(cm.exception.code, 0)
Loading