use std::path::Path;
use super::super::resolvers::has_py_typed_marker;
pub(super) fn is_python_test_file(path: &Path, content: &str) -> bool {
let path_str = path.to_string_lossy().to_lowercase();
if path_str.contains("/tests/")
|| path_str.contains("/test/")
|| path_str.contains("/__tests__/")
|| path_str.ends_with("_test.py")
|| path_str.ends_with("_tests.py")
|| path_str.ends_with("test_.py")
|| path_str.contains("/test_")
|| path_str.contains("conftest.py")
|| path_str.contains("pytest_")
{
return true;
}
if content.contains("import pytest")
|| content.contains("from pytest")
|| content.contains("import unittest")
|| content.contains("from unittest")
|| content.contains("@pytest.fixture")
|| content.contains("@pytest.mark")
|| content.contains("class Test")
|| content.contains("def test_")
{
return true;
}
false
}
pub(super) fn check_typed_package(path: &Path, root: &Path) -> bool {
let mut current = path.parent();
while let Some(dir) = current {
if has_py_typed_marker(dir) {
return true;
}
if dir == root || !dir.starts_with(root) {
break;
}
current = dir.parent();
}
false
}
pub(super) fn check_namespace_package(path: &Path, root: &Path) -> bool {
let mut current = path.parent();
while let Some(dir) = current {
if dir.join("__init__.py").exists() || dir.join("__init__.pyi").exists() {
return false;
}
if dir == root {
break;
}
current = dir.parent();
}
path.parent().is_some_and(|p| {
p.read_dir().ok().is_some_and(|entries| {
entries.flatten().any(|e| {
e.path()
.extension()
.is_some_and(|ext| ext == "py" || ext == "pyi")
})
})
})
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn detects_test_file_by_path() {
let dir = tempdir().expect("tempdir");
let root = dir.path();
std::fs::create_dir_all(root.join("tests")).expect("mkdir");
let test_path = root.join("tests/test_utils.py");
std::fs::write(&test_path, "def test_foo(): pass").expect("write");
assert!(is_python_test_file(&test_path, "def test_foo(): pass"));
}
#[test]
fn detects_test_file_by_content() {
let dir = tempdir().expect("tempdir");
let root = dir.path();
let path = root.join("my_module.py");
let content = r#"
import pytest
@pytest.fixture
def sample_fixture():
return 42
def test_something(sample_fixture):
assert sample_fixture == 42
"#;
assert!(is_python_test_file(&path, content));
}
#[test]
fn non_test_file() {
let dir = tempdir().expect("tempdir");
let root = dir.path();
let path = root.join("utils.py");
let content = "def helper(): return 42";
assert!(!is_python_test_file(&path, content));
}
#[test]
fn detects_typed_package() {
let dir = tempdir().expect("tempdir");
let root = dir.path();
std::fs::create_dir_all(root.join("mypackage")).expect("mkdir");
std::fs::write(root.join("mypackage/__init__.py"), "").expect("write __init__");
std::fs::write(root.join("mypackage/py.typed"), "").expect("write py.typed");
let module_path = root.join("mypackage/utils.py");
assert!(check_typed_package(&module_path, root));
}
#[test]
fn detects_non_typed_package() {
let dir = tempdir().expect("tempdir");
let root = dir.path();
std::fs::create_dir_all(root.join("mypackage")).expect("mkdir");
std::fs::write(root.join("mypackage/__init__.py"), "").expect("write __init__");
let module_path = root.join("mypackage/utils.py");
assert!(!check_typed_package(&module_path, root));
}
#[test]
fn detects_namespace_package() {
let dir = tempdir().expect("tempdir");
let root = dir.path();
std::fs::create_dir_all(root.join("namespace_pkg")).expect("mkdir");
std::fs::write(root.join("namespace_pkg/module.py"), "VALUE = 1").expect("write module");
let module_path = root.join("namespace_pkg/module.py");
assert!(check_namespace_package(&module_path, root));
}
#[test]
fn traditional_package_not_namespace() {
let dir = tempdir().expect("tempdir");
let root = dir.path();
std::fs::create_dir_all(root.join("pkg")).expect("mkdir");
std::fs::write(root.join("pkg/__init__.py"), "").expect("write __init__");
std::fs::write(root.join("pkg/module.py"), "VALUE = 1").expect("write module");
let module_path = root.join("pkg/module.py");
assert!(!check_namespace_package(&module_path, root));
}
}