diff --git a/tests/test_compare_imports_to_dependencies.py b/tests/test_compare_imports_to_dependencies.py index cb5b4dbb..fc1bb988 100644 --- a/tests/test_compare_imports_to_dependencies.py +++ b/tests/test_compare_imports_to_dependencies.py @@ -1,4 +1,5 @@ """Test the imports to dependencies comparison function.""" +from dataclasses import dataclass, field from pathlib import Path from typing import Dict, List @@ -6,6 +7,8 @@ from fawltydeps.check import ( LocalPackageLookup, + calculate_undeclared, + calculate_unused, compare_imports_to_dependencies, resolve_dependencies, ) @@ -53,9 +56,158 @@ def unused_factory(*deps: str) -> List[UnusedDependency]: return [UnusedDependency(dep, [Location(Path("foo"))]) for dep in deps] +@dataclass +class FDTestVector: # pylint: disable=too-many-instance-attributes + """Test vectors for various parts of the FawltyDeps core logic.""" + + id: str + imports: List[ParsedImport] = field(default_factory=list) + declared_deps: List[DeclaredDependency] = field(default_factory=list) + ignore_unused: List[str] = field(default_factory=list) + ignore_undeclared: List[str] = field(default_factory=list) + expect_resolved_deps: Dict[str, Package] = field(default_factory=dict) + expect_undeclared_deps: List[UndeclaredDependency] = field(default_factory=list) + expect_unused_deps: List[UnusedDependency] = field(default_factory=list) + + +testdata = [ + FDTestVector("no_imports_no_deps"), + FDTestVector( + "one_import_no_deps", + imports=imports_factory("pandas"), + expect_undeclared_deps=undeclared_factory("pandas"), + ), + FDTestVector( + "no_imports_one_dep", + declared_deps=deps_factory("pandas"), + expect_resolved_deps=resolved_factory("pandas"), + expect_unused_deps=unused_factory("pandas"), + ), + FDTestVector( + "matched_import_with_dep", + imports=imports_factory("pandas"), + declared_deps=deps_factory("pandas"), + expect_resolved_deps=resolved_factory("pandas"), + ), + FDTestVector( + "mixed_imports_with_unused_and_undeclared_deps", + imports=imports_factory("pandas", "numpy"), + declared_deps=deps_factory("pandas", "scipy"), + expect_resolved_deps=resolved_factory("pandas", "scipy"), + expect_undeclared_deps=undeclared_factory("numpy"), + expect_unused_deps=unused_factory("scipy"), + ), + FDTestVector( + "mixed_imports_from_diff_files_with_unused_and_undeclared_deps", + imports=imports_factory("pandas") + + [ParsedImport("numpy", Location(Path("my_file.py"), lineno=3))], + declared_deps=deps_factory("pandas", "scipy"), + expect_resolved_deps=resolved_factory("pandas", "scipy"), + expect_undeclared_deps=[ + UndeclaredDependency( + "numpy", + [Location(Path("my_file.py"), lineno=3)], + ) + ], + expect_unused_deps=unused_factory("scipy"), + ), + FDTestVector( + "unused_dep_that_is_ignore_unused__not_reported_as_unused", + declared_deps=deps_factory("pip"), + ignore_unused=["pip"], + expect_resolved_deps=resolved_factory("pip"), + ), + FDTestVector( + "used_dep_that_is_ignore_unused__not_reported_as_unused", + imports=imports_factory("isort"), + declared_deps=deps_factory("isort"), + ignore_unused=["isort"], + expect_resolved_deps=resolved_factory("isort"), + ), + FDTestVector( + "undeclared_dep_that_is_ignore_unused__reported_as_undeclared", + imports=imports_factory("isort"), + ignore_unused=["isort"], + expect_undeclared_deps=undeclared_factory("isort"), + ), + FDTestVector( + "mixed_deps__report_undeclared_and_non_ignored_unused", + imports=imports_factory("pandas", "numpy"), + declared_deps=deps_factory("pandas", "isort", "flake8"), + ignore_unused=["isort"], + expect_resolved_deps=resolved_factory("pandas", "isort", "flake8"), + expect_undeclared_deps=undeclared_factory("numpy"), + expect_unused_deps=unused_factory("flake8"), + ), + FDTestVector( + "undeclared_dep_that_is_ignore_undeclared__not_reported_as_undeclared", + imports=imports_factory("invalid_import"), + ignore_undeclared=["invalid_import"], + ), + FDTestVector( + "declared_dep_that_is_ignore_undeclared__not_reported_as_undeclared", + imports=imports_factory("isort"), + declared_deps=deps_factory("isort"), + ignore_undeclared=["isort"], + expect_resolved_deps=resolved_factory("isort"), + ), + FDTestVector( + "unused_dep_that_is_ignore_undeclared__reported_as_unused", + declared_deps=deps_factory("isort"), + ignore_undeclared=["isort"], + expect_resolved_deps=resolved_factory("isort"), + expect_unused_deps=unused_factory("isort"), + ), + FDTestVector( + "mixed_deps__report_unused_and_non_ignored_undeclared", + imports=imports_factory("pandas", "numpy", "not_valid"), + declared_deps=deps_factory("pandas", "flake8"), + ignore_undeclared=["not_valid"], + expect_resolved_deps=resolved_factory("pandas", "flake8"), + expect_undeclared_deps=undeclared_factory("numpy"), + expect_unused_deps=unused_factory("flake8"), + ), + FDTestVector( + "mixed_deps__report_only_non_ignored_unused_and_non_ignored_undeclared", + imports=imports_factory("pandas", "numpy", "not_valid"), + declared_deps=deps_factory("pandas", "flake8", "isort"), + ignore_undeclared=["not_valid"], + ignore_unused=["isort"], + expect_resolved_deps=resolved_factory("pandas", "flake8", "isort"), + expect_undeclared_deps=undeclared_factory("numpy"), + expect_unused_deps=unused_factory("flake8"), + ), + FDTestVector( + "deps_with_diff_name_for_the_same_import", + declared_deps=[ + DeclaredDependency(name="Pip", source=Location(Path("requirements1.txt"))), + DeclaredDependency(name="pip", source=Location(Path("requirements2.txt"))), + ], + expect_resolved_deps={ + "Pip": Package("pip", {DependenciesMapping.LOCAL_ENV: {"pip"}}), + "pip": Package("pip", {DependenciesMapping.LOCAL_ENV: {"pip"}}), + }, + expect_unused_deps=[ + UnusedDependency("Pip", [Location(Path("requirements1.txt"))]), + UnusedDependency("pip", [Location(Path("requirements2.txt"))]), + ], + ), +] + + @pytest.mark.parametrize( "dep_names,expected", [ + pytest.param([], {}, id="no_deps__empty_dict"), + pytest.param( + ["pandas", "numpy", "other"], + { + "pandas": Package("pandas", {DependenciesMapping.IDENTITY: {"pandas"}}), + "numpy": Package("numpy", {DependenciesMapping.IDENTITY: {"numpy"}}), + "other": Package("other", {DependenciesMapping.IDENTITY: {"other"}}), + }, + id="uninstalled_deps__use_identity_mapping", + ), pytest.param( ["pandas", "numpy", "other"], { @@ -85,185 +237,41 @@ def unused_factory(*deps: str) -> List[UnusedDependency]: ), ], ) -def test_resolve_dependencies(dep_names, expected): +def test_resolve_dependencies__focus_on_mappings(dep_names, expected): assert resolve_dependencies(dep_names) == expected -@pytest.mark.parametrize( - "imports,dependencies,ignore_unused,ignore_undeclared,expected", - [ - pytest.param([], [], [], [], ({}, [], []), id="no_import_no_dependencies"), - pytest.param( - imports_factory("pandas"), - [], - [], - [], - ({}, undeclared_factory("pandas"), []), - id="one_import_no_dependencies", - ), - pytest.param( - [], - deps_factory("pandas"), - [], - [], - (resolved_factory("pandas"), [], unused_factory("pandas")), - id="no_imports_one_dependency", - ), - pytest.param( - imports_factory("pandas"), - deps_factory("pandas"), - [], - [], - (resolved_factory("pandas"), [], []), - id="matched_import_with_dependency", - ), - pytest.param( - imports_factory("pandas", "numpy"), - deps_factory("pandas", "scipy"), - [], - [], - ( - resolved_factory("pandas", "scipy"), - undeclared_factory("numpy"), - unused_factory("scipy"), - ), - id="mixed_imports_with_unused_and_undeclared_dependencies", - ), - pytest.param( - imports_factory("pandas") - + [ParsedImport("numpy", Location(Path("my_file.py"), lineno=3))], - deps_factory("pandas", "scipy"), - [], - [], - ( - resolved_factory("pandas", "scipy"), - [ - UndeclaredDependency( - "numpy", - [Location(Path("my_file.py"), lineno=3)], - ) - ], - unused_factory("scipy"), - ), - id="mixed_imports_from_diff_files_with_unused_and_undeclared_dependencies", - ), - pytest.param( - [], - deps_factory("pip"), - ["pip"], - [], - (resolved_factory("pip"), [], []), - id="one_ignored_and_unused_dep__not_reported_as_unused", - ), - pytest.param( - imports_factory("isort"), - deps_factory("isort"), - ["isort"], - [], - (resolved_factory("isort"), [], []), - id="one_ignored_and_used_dep__not_reported_as_unused", - ), - pytest.param( - imports_factory("isort"), - deps_factory(), - ["isort"], - [], - ({}, undeclared_factory("isort"), []), - id="one_ignored_and_undeclared_dep__reported_as_undeclared", - ), - pytest.param( - imports_factory("pandas", "numpy"), - deps_factory("pandas", "isort", "flake8"), - ["isort"], - [], - ( - resolved_factory("pandas", "isort", "flake8"), - undeclared_factory("numpy"), - unused_factory("flake8"), - ), - id="mixed_dependencies__report_undeclared_and_non_ignored_unused", - ), - pytest.param( - imports_factory("invalid_import"), - [], - [], - ["invalid_import"], - ({}, [], []), - id="one_ignored_undeclared_dep__not_reported_as_undeclared", - ), - pytest.param( - imports_factory("isort"), - deps_factory("isort"), - [], - ["isort"], - (resolved_factory("isort"), [], []), - id="one_ignored_and_declared_dep__not_reported_as_undeclared", - ), - pytest.param( - [], - deps_factory("isort"), - [], - ["isort"], - (resolved_factory("isort"), [], unused_factory("isort")), - id="one_ignored_import_declared_as_dep__reported_as_unused", - ), - pytest.param( - imports_factory("pandas", "numpy", "not_valid"), - deps_factory("pandas", "flake8"), - [], - ["not_valid"], - ( - resolved_factory("pandas", "flake8"), - undeclared_factory("numpy"), - unused_factory("flake8"), - ), - id="mixed_dependencies__report_unused_and_only_non_ignored_undeclared", - ), - pytest.param( - imports_factory("pandas", "numpy", "not_valid"), - deps_factory("pandas", "flake8", "isort"), - ["isort"], - ["not_valid"], - ( - resolved_factory("pandas", "flake8", "isort"), - undeclared_factory("numpy"), - unused_factory("flake8"), - ), - id="mixed_dependencies__report_only_non_ignored_unused_and_non_ignored_undeclared", - ), - pytest.param( - [], - [ - DeclaredDependency( - name="Pip", source=Location(Path("requirements1.txt")) - ), - DeclaredDependency( - name="pip", source=Location(Path("requirements2.txt")) - ), - ], - [], - [], - ( - { - "Pip": Package("pip", {DependenciesMapping.LOCAL_ENV: {"pip"}}), - "pip": Package("pip", {DependenciesMapping.LOCAL_ENV: {"pip"}}), - }, - [], - [ - UnusedDependency("Pip", [Location(Path("requirements1.txt"))]), - UnusedDependency("pip", [Location(Path("requirements2.txt"))]), - ], - ), - id="deps_with_diff_name_for_the_same_import", - ), - ], -) -def test_compare_imports_to_dependencies( - imports, dependencies, ignore_unused, ignore_undeclared, expected -): - """Ensures the comparison method returns the expected unused and undeclared dependencies""" +@pytest.mark.parametrize("vector", [pytest.param(v, id=v.id) for v in testdata]) +def test_resolve_dependencies(vector): + dep_names = [dd.name for dd in vector.declared_deps] + assert resolve_dependencies(dep_names) == vector.expect_resolved_deps + + +@pytest.mark.parametrize("vector", [pytest.param(v, id=v.id) for v in testdata]) +def test_calculate_undeclared(vector): + settings = Settings(ignore_undeclared=vector.ignore_undeclared) + actual = calculate_undeclared(vector.imports, vector.expect_resolved_deps, settings) + assert actual == vector.expect_undeclared_deps + + +@pytest.mark.parametrize("vector", [pytest.param(v, id=v.id) for v in testdata]) +def test_calculate_unused(vector): + settings = Settings(ignore_unused=vector.ignore_unused) + actual = calculate_unused( + vector.imports, vector.declared_deps, vector.expect_resolved_deps, settings + ) + assert actual == vector.expect_unused_deps + + +@pytest.mark.parametrize("vector", [pytest.param(v, id=v.id) for v in testdata]) +def test_compare_imports_to_dependencies(vector): settings = Settings( - ignore_unused=ignore_unused, ignore_undeclared=ignore_undeclared + ignore_unused=vector.ignore_unused, ignore_undeclared=vector.ignore_undeclared + ) + actual = compare_imports_to_dependencies( + vector.imports, vector.declared_deps, settings ) - obtained = compare_imports_to_dependencies(imports, dependencies, settings) - assert obtained == expected + actual_resolved_deps, actual_undeclared_deps, actual_unused_deps = actual + assert actual_resolved_deps == vector.expect_resolved_deps + assert actual_undeclared_deps == vector.expect_undeclared_deps + assert actual_unused_deps == vector.expect_unused_deps