diff --git a/pytest-profiling/README.md b/pytest-profiling/README.md index 96bf6bf6198..5384c0fdebe 100644 --- a/pytest-profiling/README.md +++ b/pytest-profiling/README.md @@ -89,6 +89,15 @@ pstats files (one per test item) are retained for later analysis in `prof` direc test_import.prof ``` +Profiling plugin by default names pstats filename as corresponding test name. If full path is longer that operation system allows then plugin renames it to first 4 bytes of md5 hash of test name: + +```bash + $ ls -1 prof/ + combined.prof + test_not_longer_than_max_allowed.prof + 68b329da.prof +``` + If the ``--profile-svg`` option is given, along with the prof files and tabular output a svg file will be generated: ```bash diff --git a/pytest-profiling/pytest_profiling.py b/pytest-profiling/pytest_profiling.py index a5fa4c1b2f6..4ba61f3d058 100644 --- a/pytest-profiling/pytest_profiling.py +++ b/pytest-profiling/pytest_profiling.py @@ -5,6 +5,11 @@ import cProfile import pstats import pipes +import errno +from hashlib import md5 + + +LARGE_FILENAME_HASH_LEN = 8 class Profiling(object): @@ -50,9 +55,22 @@ def pytest_pyfunc_call(self, __multicall__, pyfuncitem): """Hook into pytest_pyfunc_call; marked as a tryfirst hook so that we can call everyone else inside `cProfile.runctx`. """ - prof = os.path.join("prof", pyfuncitem.name + ".prof") - cProfile.runctx("fn()", globals(), dict(fn=__multicall__.execute), filename=prof) - self.profs.append(prof) + prof = cProfile.Profile() + prof.runctx("fn()", globals(), dict(fn=__multicall__.execute)) + prof_filename = os.path.join("prof", pyfuncitem.name + ".prof") + try: + prof.dump_stats(prof_filename) + except EnvironmentError as err: + if err.errno != errno.ENAMETOOLONG: + raise + + if len(pyfuncitem.name) < LARGE_FILENAME_HASH_LEN: + raise + + hash_str = md5(pyfuncitem.name).hexdigest()[:LARGE_FILENAME_HASH_LEN] + prof_filename = os.path.join("prof", hash_str + ".prof") + prof.dump_stats(prof_filename) + self.profs.append(prof_filename) def pytest_addoption(parser): diff --git a/pytest-profiling/tests/unit/test_profile.py b/pytest-profiling/tests/unit/test_profile.py index 2dfe7127fef..4567ab8f49c 100644 --- a/pytest-profiling/tests/unit/test_profile.py +++ b/pytest-profiling/tests/unit/test_profile.py @@ -22,13 +22,16 @@ def test_hooks_pyfunc_call(): multicall, pyfuncitem, plugin = Mock(), Mock(), Profiling(False) pyfuncitem.name.__add__ = Mock() with patch('os.path.join', return_value=sentinel.join): - with patch('pytest_profiling.cProfile') as cProfile: + with patch('pytest_profiling.cProfile.Profile') as Profile: plugin.pytest_pyfunc_call(multicall, pyfuncitem) - assert cProfile.runctx.called - args, kwargs = cProfile.runctx.call_args - assert kwargs['filename'] == sentinel.join + prof = Profile() # mock instances are singletons + assert prof.runctx.called + assert prof.dump_stats.called + runctx_args, _ = prof.runctx.call_args + (dump_stats_filename, ), _ = prof.dump_stats.call_args + assert dump_stats_filename == sentinel.join assert not multicall.execute.called - eval(*args) + eval(*runctx_args) assert multicall.execute.called assert plugin.profs == [sentinel.join]