Skip to content

Commit 07971e1

Browse files
committed
pythongh-131178: Add CLI tests for profile module
1 parent d94b1e9 commit 07971e1

File tree

2 files changed

+109
-2
lines changed

2 files changed

+109
-2
lines changed

Lib/profile.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ def f(m, f1=f1):
551551

552552
#****************************************************************************
553553

554-
def main():
554+
def main(args=None):
555555
import os
556556
from optparse import OptionParser
557557

@@ -570,7 +570,7 @@ def main():
570570
parser.print_usage()
571571
sys.exit(2)
572572

573-
(options, args) = parser.parse_args()
573+
(options, args) = parser.parse_args(args)
574574
sys.argv[:] = args
575575

576576
# The script that we're profiling may chdir, so capture the absolute path

Lib/test/test_profile.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pstats
55
import unittest
66
import os
7+
import subprocess
78
from difflib import unified_diff
89
from io import StringIO
910
from test.support.os_helper import TESTFN, unlink, temp_dir, change_cwd
@@ -130,6 +131,112 @@ def test_output_file_when_changing_directory(self):
130131

131132
self.assertTrue(os.path.exists('out.pstats'))
132133

134+
class ProfileCLITests(unittest.TestCase):
135+
"""Tests for the profile module's command line interface."""
136+
137+
def setUp(self):
138+
# Create a simple Python script to profile
139+
self.script_content = """\
140+
def factorial(n):
141+
if n <= 1:
142+
return 1
143+
return n * factorial(n-1)
144+
145+
if __name__ == "__main__":
146+
factorial(10)
147+
"""
148+
self.script_file = TESTFN
149+
with open(self.script_file, "w") as f:
150+
f.write(self.script_content)
151+
self.addCleanup(unlink, self.script_file)
152+
153+
def _run_profile_cli(self, *args):
154+
"""Helper to run the profile CLI with given arguments."""
155+
cmd = [sys.executable, '-m', 'profile'] + list(args)
156+
proc = subprocess.Popen(
157+
cmd,
158+
stdout=subprocess.PIPE,
159+
stderr=subprocess.PIPE,
160+
universal_newlines=True
161+
)
162+
stdout, stderr = proc.communicate()
163+
return proc.returncode, stdout, stderr
164+
165+
def test_basic_profile(self):
166+
"""Test basic profiling of a script."""
167+
returncode, stdout, stderr = self._run_profile_cli(self.script_file)
168+
self.assertEqual(returncode, 0)
169+
self.assertIn("function calls", stdout)
170+
self.assertIn("factorial", stdout)
171+
172+
def test_sort_options(self):
173+
"""Test different sort options."""
174+
# List of sort options known to work
175+
sort_options = ['calls', 'cumulative', 'cumtime', 'file',
176+
'filename', 'module', 'ncalls', 'pcalls',
177+
'line', 'stdname', 'time', 'tottime']
178+
179+
# Test each sort option individually
180+
for option in sort_options:
181+
with self.subTest(sort_option=option):
182+
returncode, stdout, stderr = self._run_profile_cli(
183+
'-s', option, self.script_file
184+
)
185+
self.assertEqual(returncode, 0)
186+
self.assertIn("function calls", stdout)
187+
188+
def test_output_file(self):
189+
"""Test writing profile results to a file."""
190+
output_file = TESTFN + '.prof'
191+
self.addCleanup(unlink, output_file)
192+
193+
returncode, stdout, stderr = self._run_profile_cli(
194+
'-o', output_file, self.script_file
195+
)
196+
self.assertEqual(returncode, 0)
197+
198+
# Check that the output file exists and contains profile data
199+
self.assertTrue(os.path.exists(output_file))
200+
stats = pstats.Stats(output_file)
201+
self.assertGreater(stats.total_calls, 0)
202+
203+
def test_invalid_option(self):
204+
"""Test behavior with an invalid option."""
205+
returncode, stdout, stderr = self._run_profile_cli(
206+
'--invalid-option', self.script_file
207+
)
208+
self.assertNotEqual(returncode, 0)
209+
self.assertIn("error", stderr.lower())
210+
211+
def test_no_arguments(self):
212+
"""Test behavior with no arguments."""
213+
returncode, stdout, stderr = self._run_profile_cli()
214+
self.assertNotEqual(returncode, 0)
215+
216+
# Check either stdout or stderr for usage information
217+
combined_output = stdout.lower() + stderr.lower()
218+
self.assertTrue("usage:" in combined_output or
219+
"error:" in combined_output or
220+
"no script filename specified" in combined_output,
221+
"Expected usage information or error message not found")
222+
223+
def test_run_module(self):
224+
"""Test profiling a module with -m option."""
225+
# Create a small module
226+
module_name = "test_profile_module"
227+
module_file = f"{module_name}.py"
228+
229+
with open(module_file, "w") as f:
230+
f.write("print('Module executed')\n")
231+
232+
self.addCleanup(unlink, module_file)
233+
234+
returncode, stdout, stderr = self._run_profile_cli(
235+
'-m', module_name
236+
)
237+
self.assertEqual(returncode, 0)
238+
self.assertIn("Module executed", stdout)
239+
self.assertIn("function calls", stdout)
133240

134241
def regenerate_expected_output(filename, cls):
135242
filename = filename.rstrip('co')

0 commit comments

Comments
 (0)