Coverage for tests / unit / test_package_imports.py: 89%

37 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-04-03 18:53 +0000

1"""Smoke tests to verify all package modules are importable. 

2 

3This test ensures that: 

41. All modules listed in pyproject.toml are actually included in the package build 

52. All packages in the source tree are listed in pyproject.toml (catches forgotten packages) 

6 

7This prevents packaging errors where a module exists in the source tree but is 

8missing from the packages list (like the 0.43.0 bug with lintro.utils.environment). 

9""" 

10 

11import importlib 

12from pathlib import Path 

13 

14import pytest 

15 

16# Project root directory 

17PROJECT_ROOT = Path(__file__).parent.parent.parent 

18 

19 

20def _discover_packages_from_source() -> set[str]: 

21 """Discover all Python packages in the lintro source tree. 

22 

23 Returns: 

24 Set of package names (e.g., "lintro.utils.environment"). 

25 """ 

26 lintro_dir = PROJECT_ROOT / "lintro" 

27 packages: set[str] = set() 

28 

29 for path in lintro_dir.rglob("__init__.py"): 

30 # Convert path to package name 

31 relative = path.parent.relative_to(PROJECT_ROOT) 

32 package_name = ".".join(relative.parts) 

33 packages.add(package_name) 

34 

35 return packages 

36 

37 

38def _get_packages_from_pyproject() -> set[str]: 

39 """Read the packages list from pyproject.toml. 

40 

41 Returns: 

42 Set of package names listed in [tool.setuptools].packages. 

43 """ 

44 import tomllib 

45 

46 pyproject_path = PROJECT_ROOT / "pyproject.toml" 

47 with pyproject_path.open("rb") as f: 

48 data = tomllib.load(f) 

49 

50 packages = data.get("tool", {}).get("setuptools", {}).get("packages", []) 

51 return set(packages) 

52 

53 

54def _get_configured_packages() -> list[str]: 

55 """Get packages from pyproject.toml for parametrized tests.""" 

56 return sorted(_get_packages_from_pyproject()) 

57 

58 

59@pytest.mark.parametrize("package", _get_configured_packages()) 

60def test_package_importable(package: str) -> None: 

61 """Verify each configured package can be imported successfully.""" 

62 # Note: We intentionally don't clear sys.modules here because doing so 

63 # would reinitialize global singletons (like tool_manager in lintro.tools) 

64 # which breaks other tests that depend on monkeypatching those singletons. 

65 # The import test is still valid - if the package is missing from 

66 # pyproject.toml, it won't be importable in a fresh install. 

67 try: 

68 # nosemgrep: python.lang.security.audit.non-literal-import.non-literal-import 

69 importlib.import_module(package) 

70 except ImportError as e: 

71 pytest.fail( 

72 f"Failed to import '{package}': {e}\n" 

73 f"This likely means the package is missing from " 

74 f"[tool.setuptools].packages in pyproject.toml", 

75 ) 

76 

77 

78def test_all_source_packages_are_configured() -> None: 

79 """Verify all packages in the source tree are listed in pyproject.toml. 

80 

81 This catches the case where a developer adds a new package directory 

82 but forgets to add it to [tool.setuptools].packages. 

83 """ 

84 source_packages = _discover_packages_from_source() 

85 configured_packages = _get_packages_from_pyproject() 

86 

87 missing = source_packages - configured_packages 

88 if missing: 

89 missing_list = "\n - ".join(sorted(missing)) 

90 pytest.fail( 

91 f"Found {len(missing)} package(s) in source tree not listed in " 

92 f"pyproject.toml [tool.setuptools].packages:\n - {missing_list}\n\n" 

93 f"Add these packages to pyproject.toml to include them in the build.", 

94 ) 

95 

96 

97def test_doctor_command_imports() -> None: 

98 """Verify the doctor command and its dependencies are importable. 

99 

100 This is a regression test for the 0.43.0 packaging bug where 

101 lintro.utils.environment was missing from the package. 

102 """ 

103 from lintro.cli_utils.commands import doctor # noqa: F401 

104 from lintro.utils.environment import ( # noqa: F401 

105 CIEnvironment, 

106 EnvironmentReport, 

107 GoInfo, 

108 LintroInfo, 

109 NodeInfo, 

110 ProjectInfo, 

111 PythonInfo, 

112 RubyInfo, 

113 RustInfo, 

114 SystemInfo, 

115 collect_full_environment, 

116 render_environment_report, 

117 )