Skip to content

feat: load fixtures from pytest entry_points#78

Merged
bellini666 merged 1 commit intobellini666:masterfrom
benediktziegler:load-plugins-from-pytest11-hooks
Feb 14, 2026
Merged

feat: load fixtures from pytest entry_points#78
bellini666 merged 1 commit intobellini666:masterfrom
benediktziegler:load-plugins-from-pytest11-hooks

Conversation

@benediktziegler
Copy link
Copy Markdown
Contributor

Fixed #75.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the fixture scanner to discover and scan pytest plugins dynamically via pytest11 entry points (instead of relying on a hardcoded plugin-name list), aligning plugin discovery with pytest’s standard mechanism and addressing #75.

Changes:

  • Add parsing of entry_points.txt to extract [pytest11] plugin registrations.
  • Resolve discovered plugin module paths to files under site-packages and scan them for fixtures.
  • Add unit/integration tests covering entry point parsing, module resolution, and discovery behavior.
Comments suppressed due to low confidence (1)

src/fixtures/scanner.rs:598

  • std::fs::read_dir(site_packages).into_iter().flatten() silently turns a read_dir error (e.g., permission denied) into an empty iteration, which makes plugin discovery fail without any warning. Consider explicitly handling the Err(e) from read_dir and logging (and/or surfacing) the failure.
        // Iterate over ALL dist-info directories and check for pytest11 entry points
        for entry in std::fs::read_dir(site_packages).into_iter().flatten() {
            let entry = match entry {
                Ok(e) => e,
                Err(_) => continue,
            };

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +552 to +553
self.scan_single_plugin_file(&path);
scanned_count += 1;
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load_plugin_from_entry_point only scans the resolved module file (often __init__.py when the entry point points at a package). Since the analyzer doesn’t follow imports, this will miss fixtures defined in submodules that the entry-point module imports (a common plugin layout). Consider scanning the owning package directory (e.g., the directory containing __init__.py) with scan_plugin_directory, or adding an import-following pass for plugin modules.

Suggested change
self.scan_single_plugin_file(&path);
scanned_count += 1;
// If the resolved path is a package's __init__.py, scan the whole package directory
let scanned = if path.is_file() {
let is_init = path
.file_name()
.and_then(|name| name.to_str())
.map(|name| name == "__init__.py")
.unwrap_or(false);
if is_init {
if let Some(package_dir) = path.parent() {
info!(
"Scanning pytest plugin package directory for {}: {:?}",
entry.name, package_dir
);
self.scan_plugin_directory(package_dir);
true
} else {
// Fallback: scan the __init__.py file itself
self.scan_single_plugin_file(&path);
true
}
} else {
self.scan_single_plugin_file(&path);
true
}
} else if path.is_dir() {
info!(
"Scanning pytest plugin directory for {}: {:?}",
entry.name, path
);
self.scan_plugin_directory(&path);
true
} else {
debug!(
"Resolved module path for plugin {} is neither file nor directory: {:?}",
entry.name, path
);
false
};
if scanned {
scanned_count += 1;
}

Copilot uses AI. Check for mistakes.
Comment on lines +603 to +606
// Only process .dist-info directories
if !filename.ends_with(".dist-info") {
continue;
}
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plugin discovery currently only considers *.dist-info metadata directories. Some environments still install distributions with *.egg-info/entry_points.txt (including some non-editable/legacy installs), which would cause pytest plugins to be missed. If supporting those environments matters, consider also checking *.egg-info for entry points (or at least documenting this limitation).

Copilot uses AI. Check for mistakes.
Comment on lines +427 to +428
/// Parse entry_points.txt content and extract pytest11 entries.
/// Returns empty vec if no pytest11 section or file is malformed.
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comment says this returns an empty vec when the file is malformed, but the implementation currently just skips lines that don’t match name = value and returns any successfully parsed entries. Either tighten the parsing to treat malformed [pytest11] content as an error (and return empty), or adjust the comment to match the current behavior.

Suggested change
/// Parse entry_points.txt content and extract pytest11 entries.
/// Returns empty vec if no pytest11 section or file is malformed.
/// Parse `entry_points.txt` content and extract pytest11 entries.
///
/// Returns all successfully parsed entries from the `[pytest11]` section.
/// Returns an empty vec if there is no `[pytest11]` section or no valid
/// `name = value` lines within that section. Malformed lines are ignored.

Copilot uses AI. Check for mistakes.
Comment on lines +496 to +503
// Try as a single-file module at top level (for single-part module paths)
if parts.len() == 1 {
let single_file = site_packages.join(format!("{}.py", parts[0]));
if single_file.exists() {
return Some(single_file);
}
}

Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parts.len() == 1 fallback is redundant: for single-part module paths the earlier path.with_extension("py") check already looks for site_packages/<module>.py. Removing this branch would simplify the resolver and avoid extra filesystem calls.

Suggested change
// Try as a single-file module at top level (for single-part module paths)
if parts.len() == 1 {
let single_file = site_packages.join(format!("{}.py", parts[0]));
if single_file.exists() {
return Some(single_file);
}
}

Copilot uses AI. Check for mistakes.
@codecov
Copy link
Copy Markdown

codecov bot commented Feb 14, 2026

Codecov Report

❌ Patch coverage is 95.65217% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 52.62%. Comparing base (cd601c4) to head (f29482a).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
src/fixtures/scanner.rs 95.65% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #78      +/-   ##
==========================================
+ Coverage   50.55%   52.62%   +2.07%     
==========================================
  Files          26       26              
  Lines        2702     2757      +55     
==========================================
+ Hits         1366     1451      +85     
+ Misses       1336     1306      -30     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Owner

@bellini666 bellini666 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@bellini666 bellini666 merged commit 6cde671 into bellini666:master Feb 14, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Discover Pytest Plugins via Entry Points (pytest11)

3 participants