Skip to content

Conversation

@codeflash-ai
Copy link

@codeflash-ai codeflash-ai bot commented Nov 22, 2025

📄 57% (0.57x) speedup for getpluginversioninfo in src/_pytest/helpconfig.py

⏱️ Runtime : 896 microseconds 571 microseconds (best of 250 runs)

📝 Explanation and details

The optimization achieves a 56% speedup by applying several micro-optimizations that reduce overhead in the plugin iteration loop:

Key Optimizations:

  1. Local variable caching: lines_append = lines.append eliminates repeated attribute lookups on the lines object during each loop iteration.

  2. Smarter getattr usage: Instead of getattr(plugin, "__file__", repr(plugin)) which always evaluates repr(plugin) even when not needed, the code now uses getattr(plugin, "__file__", None) and only calls repr(plugin) when the file attribute is actually missing.

  3. Attribute access optimization: dist.project_name and dist.version are cached to local variables (pn, ver) to avoid repeated attribute lookups within the f-string.

Why This Matters:

The function is called from showversion() and pytest_report_header() during pytest startup and debug output generation. While not in a critical hot path, the optimization provides significant benefits when many plugins are registered. Test results show the most dramatic improvements with larger plugin counts:

  • 100 plugins: 63.6% faster
  • 500 plugins: 28.7% faster
  • 999 plugins: 65.4% faster

Performance Impact by Use Case:

The optimization excels when plugins have __file__ attributes (common case), showing 30-67% improvements. For plugins without __file__ attributes that require repr() calls, the optimization is more modest (5-6% faster) or occasionally slightly slower due to the additional conditional check, but this represents the minority case in typical pytest environments.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 28 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
# imports
# function to test
from _pytest.helpconfig import getpluginversioninfo


# --- Unit Tests ---


class DummyPlugin:
    """A dummy plugin object for testing."""

    def __init__(self, file=None):
        if file is not None:
            self.__file__ = file


class DummyDist:
    """A dummy dist object for testing."""

    def __init__(self, project_name, version):
        self.project_name = project_name
        self.version = version


class DummyPluginManager:
    """A dummy plugin manager to simulate list_plugin_distinfo()."""

    def __init__(self, plugin_distinfo):
        self._plugin_distinfo = plugin_distinfo

    def list_plugin_distinfo(self):
        # Returns the plugin_distinfo as is
        return self._plugin_distinfo


class DummyConfig:
    """A dummy Config object providing a pluginmanager."""

    def __init__(self, plugin_distinfo):
        self.pluginmanager = DummyPluginManager(plugin_distinfo)


# --- Basic Test Cases ---


def test_no_plugins():
    """Test with no plugins registered (empty list)."""
    config = DummyConfig([])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 779ns -> 788ns (1.14% slower)


def test_single_plugin_with_file():
    """Test with a single plugin that has a __file__ attribute."""
    plugin = DummyPlugin(file="/path/to/plugin.py")
    dist = DummyDist("myplugin", "1.2.3")
    config = DummyConfig([(plugin, dist)])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 2.76μs -> 1.65μs (67.8% faster)


def test_single_plugin_without_file():
    """Test with a single plugin that does NOT have a __file__ attribute."""
    plugin = DummyPlugin()  # no file
    dist = DummyDist("otherplugin", "0.9.8")
    config = DummyConfig([(plugin, dist)])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 2.50μs -> 2.77μs (9.81% slower)


def test_multiple_plugins():
    """Test with multiple plugins registered."""
    plugin1 = DummyPlugin(file="/plugin1.py")
    dist1 = DummyDist("plugin1", "1.0")
    plugin2 = DummyPlugin(file="/plugin2.py")
    dist2 = DummyDist("plugin2", "2.0")
    config = DummyConfig([(plugin1, dist1), (plugin2, dist2)])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 3.19μs -> 1.96μs (63.3% faster)


# --- Edge Test Cases ---


def test_plugin_with_empty_project_name_and_version():
    """Test plugin with empty project_name and version."""
    plugin = DummyPlugin(file="/empty.py")
    dist = DummyDist("", "")
    config = DummyConfig([(plugin, dist)])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 2.37μs -> 1.55μs (53.0% faster)


def test_plugin_with_non_str_project_name_and_version():
    """Test plugin with non-string project_name and version."""
    plugin = DummyPlugin(file="/weird.py")
    dist = DummyDist(123, None)
    config = DummyConfig([(plugin, dist)])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 2.70μs -> 1.82μs (48.1% faster)


def test_plugin_with_file_is_none():
    """Test plugin with __file__ attribute set to None."""
    plugin = DummyPlugin(file=None)
    dist = DummyDist("noneplugin", "0.0")
    config = DummyConfig([(plugin, dist)])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 2.35μs -> 2.67μs (12.1% slower)


def test_plugin_is_builtin_type():
    """Test plugin is a built-in type (e.g., int), no __file__."""
    plugin = 42
    dist = DummyDist("builtin", "42")
    config = DummyConfig([(plugin, dist)])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 1.75μs -> 1.74μs (0.287% faster)


def test_plugin_with_long_names():
    """Test plugin with very long project_name and version."""
    long_name = "x" * 200
    long_version = "y" * 100
    plugin = DummyPlugin(file="/long.py")
    dist = DummyDist(long_name, long_version)
    config = DummyConfig([(plugin, dist)])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 2.54μs -> 1.60μs (58.5% faster)


def test_plugin_with_special_characters():
    """Test plugin with special characters in name/version."""
    plugin = DummyPlugin(file="/special.py")
    dist = DummyDist("plug!@#", "v$%^")
    config = DummyConfig([(plugin, dist)])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 2.41μs -> 1.53μs (57.5% faster)


# --- Large Scale Test Cases ---


def test_large_number_of_plugins():
    """Test with a large number of plugins (up to 1000)."""
    num_plugins = 1000
    plugins = [
        (DummyPlugin(file=f"/plugin{i}.py"), DummyDist(f"plugin{i}", str(i)))
        for i in range(num_plugins)
    ]
    config = DummyConfig(plugins)
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 305μs -> 128μs (138% faster)


def test_large_number_of_plugins_no_file():
    """Test with many plugins, none have __file__ attribute."""
    num_plugins = 500
    plugins = [
        (DummyPlugin(), DummyDist(f"plug{i}", f"v{i}")) for i in range(num_plugins)
    ]
    config = DummyConfig(plugins)
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 150μs -> 142μs (5.60% faster)
    # All locations should be repr(plugin)
    for i in range(1, len(result)):
        pass


def test_large_number_of_mixed_plugins():
    """Test with a mix of plugins, some with __file__, some without."""
    num_plugins = 100
    plugins = []
    for i in range(num_plugins):
        if i % 2 == 0:
            plugins.append(
                (DummyPlugin(file=f"/mix{i}.py"), DummyDist(f"mix{i}", f"v{i}"))
            )
        else:
            plugins.append((DummyPlugin(), DummyDist(f"mix{i}", f"v{i}")))
    config = DummyConfig(plugins)
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 33.7μs -> 23.9μs (40.9% faster)
    # Even indices should have file, odd should have repr
    for i in range(1, len(result)):
        if (i - 1) % 2 == 0:
            pass
        else:
            pass


# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
# function to test

# imports
from _pytest.helpconfig import getpluginversioninfo
import pytest


class DummyDist:
    def __init__(self, project_name, version):
        self.project_name = project_name
        self.version = version


class DummyPlugin:
    def __init__(self, file=None, repr_str=None):
        if file is not None:
            self.__file__ = file
        self._repr_str = repr_str

    def __repr__(self):
        if self._repr_str is not None:
            return self._repr_str
        return "<DummyPlugin>"


class DummyPluginManager:
    def __init__(self, plugininfo):
        self._plugininfo = plugininfo

    def list_plugin_distinfo(self):
        return self._plugininfo


class DummyConfig:
    def __init__(self, plugininfo):
        self.pluginmanager = DummyPluginManager(plugininfo)


# unit tests

# -------------------------------
# Basic Test Cases
# -------------------------------


def test_no_plugins_returns_empty_list():
    # No plugins registered
    config = DummyConfig(plugininfo=[])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 716ns -> 786ns (8.91% slower)


def test_single_plugin_with_file():
    # Single plugin with __file__ attribute
    plugin = DummyPlugin(file="/path/to/plugin.py")
    dist = DummyDist("pytest-foo", "1.2.3")
    config = DummyConfig(plugininfo=[(plugin, dist)])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 2.25μs -> 1.65μs (36.2% faster)


def test_single_plugin_without_file():
    # Single plugin without __file__, so repr is used
    plugin = DummyPlugin(repr_str="<PluginBar>")
    dist = DummyDist("pytest-bar", "2.4.6")
    config = DummyConfig(plugininfo=[(plugin, dist)])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 2.08μs -> 2.21μs (6.02% slower)


def test_multiple_plugins():
    # Multiple plugins, mix of with/without __file__
    plugin1 = DummyPlugin(file="/a/b/c.py")
    dist1 = DummyDist("p1", "0.1")
    plugin2 = DummyPlugin(repr_str="<PluginTwo>")
    dist2 = DummyDist("p2", "0.2")
    config = DummyConfig(plugininfo=[(plugin1, dist1), (plugin2, dist2)])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 2.73μs -> 2.41μs (13.6% faster)


# -------------------------------
# Edge Test Cases
# -------------------------------


def test_plugin_with_empty_project_name_and_version():
    # Plugin with empty project name and version
    plugin = DummyPlugin(file="/x/y/z.py")
    dist = DummyDist("", "")
    config = DummyConfig(plugininfo=[(plugin, dist)])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 1.93μs -> 1.48μs (30.1% faster)


def test_plugin_with_none_file_and_repr_returns_none():
    # Plugin with neither __file__ nor custom __repr__, repr returns default
    plugin = DummyPlugin()
    dist = DummyDist("foo", "1.0")
    config = DummyConfig(plugininfo=[(plugin, dist)])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 2.00μs -> 1.91μs (5.09% faster)


def test_plugin_with_nonstring_file():
    # __file__ is not a string (e.g., int), should be converted to str
    plugin = DummyPlugin(file=12345)
    dist = DummyDist("foo", "2.0")
    config = DummyConfig(plugininfo=[(plugin, dist)])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 2.09μs -> 1.70μs (23.1% faster)


def test_plugin_with_unicode_characters():
    # Unicode in project name, version, and file
    plugin = DummyPlugin(file="/путь/к/плагину.py")
    dist = DummyDist("плагин", "версия1")
    config = DummyConfig(plugininfo=[(plugin, dist)])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 2.48μs -> 2.03μs (22.3% faster)


def test_plugin_with_special_characters():
    # Special characters in project name/version
    plugin = DummyPlugin(file="/weird/path.py")
    dist = DummyDist("foo!@#", "v$%^&*")
    config = DummyConfig(plugininfo=[(plugin, dist)])
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 1.84μs -> 1.59μs (15.7% faster)


def test_plugin_with_none_in_plugininfo():
    # Plugininfo contains None as plugin or dist
    plugin = None
    dist = None
    config = DummyConfig(plugininfo=[(plugin, dist)])
    # getattr(None, "__file__", repr(None)) -> repr(None) == 'None'
    # dist.project_name and dist.version will raise AttributeError
    with pytest.raises(AttributeError):
        getpluginversioninfo(config)  # 2.50μs -> 2.52μs (0.873% slower)


def test_plugininfo_is_none():
    # plugininfo is None
    class DummyPluginManagerNone:
        def list_plugin_distinfo(self):
            return None

    class DummyConfigNone:
        def __init__(self):
            self.pluginmanager = DummyPluginManagerNone()

    config = DummyConfigNone()
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 768ns -> 877ns (12.4% slower)


def test_plugininfo_is_not_iterable():
    # plugininfo is not iterable
    class DummyPluginManagerNotIterable:
        def list_plugin_distinfo(self):
            return 123

    class DummyConfigNotIterable:
        def __init__(self):
            self.pluginmanager = DummyPluginManagerNotIterable()

    config = DummyConfigNotIterable()
    with pytest.raises(TypeError):
        getpluginversioninfo(config)  # 1.60μs -> 1.78μs (10.3% slower)


# -------------------------------
# Large Scale Test Cases
# -------------------------------


def test_many_plugins():
    # Test with 100 plugins
    plugins = []
    for i in range(100):
        plugin = DummyPlugin(file=f"/plugin/{i}.py")
        dist = DummyDist(f"plugin{i}", f"{i}.0")
        plugins.append((plugin, dist))
    config = DummyConfig(plugininfo=plugins)
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 24.8μs -> 15.1μs (63.6% faster)
    for i in range(100):
        expected = f"  plugin{i}-{i}.0 at /plugin/{i}.py"


def test_large_plugininfo_with_mixed_attributes():
    # 500 plugins, alternating with/without __file__
    plugins = []
    for i in range(500):
        if i % 2 == 0:
            plugin = DummyPlugin(file=f"/even/{i}.py")
        else:
            plugin = DummyPlugin(repr_str=f"<OddPlugin{i}>")
        dist = DummyDist(f"p{i}", f"v{i}")
        plugins.append((plugin, dist))
    config = DummyConfig(plugininfo=plugins)
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 111μs -> 87.0μs (28.7% faster)
    for i in range(500):
        if i % 2 == 0:
            expected = f"  p{i}-v{i} at /even/{i}.py"
        else:
            expected = f"  p{i}-v{i} at <OddPlugin{i}>"


def test_performance_with_maximum_allowed_plugins():
    # 999 plugins to test upper bound
    plugins = []
    for i in range(999):
        plugin = DummyPlugin(file=f"/max/{i}.py")
        dist = DummyDist(f"maxp{i}", f"{i}")
        plugins.append((plugin, dist))
    config = DummyConfig(plugininfo=plugins)
    codeflash_output = getpluginversioninfo(config)
    result = codeflash_output  # 223μs -> 135μs (65.4% faster)


# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

To edit these changes git checkout codeflash/optimize-getpluginversioninfo-mi9v02d9 and push.

Codeflash Static Badge

The optimization achieves a **56% speedup** by applying several micro-optimizations that reduce overhead in the plugin iteration loop:

**Key Optimizations:**

1. **Local variable caching**: `lines_append = lines.append` eliminates repeated attribute lookups on the `lines` object during each loop iteration.

2. **Smarter `getattr` usage**: Instead of `getattr(plugin, "__file__", repr(plugin))` which always evaluates `repr(plugin)` even when not needed, the code now uses `getattr(plugin, "__file__", None)` and only calls `repr(plugin)` when the file attribute is actually missing.

3. **Attribute access optimization**: `dist.project_name` and `dist.version` are cached to local variables (`pn`, `ver`) to avoid repeated attribute lookups within the f-string.

**Why This Matters:**

The function is called from `showversion()` and `pytest_report_header()` during pytest startup and debug output generation. While not in a critical hot path, the optimization provides significant benefits when many plugins are registered. Test results show the most dramatic improvements with larger plugin counts:
- 100 plugins: 63.6% faster 
- 500 plugins: 28.7% faster
- 999 plugins: 65.4% faster

**Performance Impact by Use Case:**

The optimization excels when plugins have `__file__` attributes (common case), showing 30-67% improvements. For plugins without `__file__` attributes that require `repr()` calls, the optimization is more modest (5-6% faster) or occasionally slightly slower due to the additional conditional check, but this represents the minority case in typical pytest environments.
@codeflash-ai codeflash-ai bot requested a review from mashraf-222 November 22, 2025 05:39
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Nov 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant