|  | 
| 4 | 4 | import pstats | 
| 5 | 5 | import unittest | 
| 6 | 6 | import os | 
|  | 7 | +import subprocess | 
| 7 | 8 | from difflib import unified_diff | 
| 8 | 9 | from io import StringIO | 
| 9 | 10 | from test.support.os_helper import TESTFN, unlink, temp_dir, change_cwd | 
| @@ -130,6 +131,112 @@ def test_output_file_when_changing_directory(self): | 
| 130 | 131 | 
 | 
| 131 | 132 |             self.assertTrue(os.path.exists('out.pstats')) | 
| 132 | 133 | 
 | 
|  | 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) | 
| 133 | 240 | 
 | 
| 134 | 241 | def regenerate_expected_output(filename, cls): | 
| 135 | 242 |     filename = filename.rstrip('co') | 
|  | 
0 commit comments