Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion bases/polylith/cli/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,20 @@
projects: Annotated[
bool, Option(help="Projects affected by changes in tests")
] = False,
strategy: Annotated[
str, Option(help="By 'imports' (the bricks used in tests) or by 'path' (the corresponding bricks).")
] = "imports",
):
"""Shows the Polylith projects and bricks that are affected by changes in tests."""
root = repo.get_workspace_root(Path.cwd())
ns = configuration.get_namespace_from_config(root)

tag = diff.collect.get_latest_tag(root, since) or since

if not tag:
print("No matching tags or commits found in repository.")
return

options = {"short": short, "bricks": bricks, "projects": projects}
options = {"short": short, "bricks": bricks, "projects": projects, "strategy": strategy}

Check warning on line 33 in bases/polylith/cli/test.py

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ New issue: Excess Number of Function Arguments

diff_command has 5 arguments, max arguments = 4 This function has too many arguments, indicating a lack of encapsulation. Avoid adding more arguments.

commands.test.run(root, ns, tag, options)
35 changes: 32 additions & 3 deletions components/polylith/commands/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ def get_imported_bricks_in_tests(
return set().union(*brick_imports.values())


def extract_brick_names(bricks_data: List[dict], imported_bricks: Set[str]) -> Set[str]:
return {v for b in bricks_data for v in b.values() if v in imported_bricks}
def extract_brick_names(bricks_data: List[dict], possible_bricks: Set[str]) -> Set[str]:
return {v for b in bricks_data for v in b.values() if v in possible_bricks}


def get_affected_bricks(
Expand All @@ -28,6 +28,18 @@ def get_affected_bricks(
return bases, components


def get_related_bricks(
root: Path, ns: str, tag_name: Union[str, None], theme: str
) -> Tuple[Set[str], Set[str]]:
files = test.get_changed_files(root, tag_name)
related = test.get_related_bricks(root, ns, theme, files)

bases = extract_brick_names(dirs.get_bases_data(root, ns), related["bases"])
components = extract_brick_names(dirs.get_components_data(root, ns), related["components"])

return bases, components


def get_affected_projects(
root: Path, ns: str, bases: Set[str], components: Set[str]
) -> List[dict]:
Expand All @@ -40,10 +52,27 @@ def get_affected_projects(
return [p for p in projects_data if p["path"].name in names]


def parse_strategy(strategy: str) -> List[str]:
strategies = str.split(strategy, ",")

return [str.lower(s) for s in strategies]


def run(root: Path, ns: str, tag: str, options: dict) -> None:
theme = configuration.get_theme_from_config(root)

bases, components = get_affected_bricks(root, ns, tag, theme)
strategy = parse_strategy(options["strategy"])

by_imports = "imports" in strategy
by_path = "path" in strategy

fallback: Tuple[Set[str], Set[str]] = set(), set()
affected = get_affected_bricks(root, ns, tag, theme) if by_imports else fallback
related = get_related_bricks(root, ns, tag, theme) if by_path else fallback

bases = set().union(affected[0], related[0])
components = set().union(affected[1], related[1])

projects_data = get_affected_projects(root, ns, bases, components)

if options.get("bricks"):
Expand Down
7 changes: 7 additions & 0 deletions components/polylith/poetry/commands/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ class TestDiffCommand(Command):
description="Projects affected by changes in tests",
flag=True,
),
option(
long_name="strategy",
description="By 'imports' (the bricks used in tests) or by 'path' (the corresponding bricks)",
flag=False,
default="imports",
),
]

def handle(self) -> int:
Expand All @@ -34,6 +40,7 @@ def handle(self) -> int:
"short": self.option("short"),
"bricks": self.option("bricks"),
"projects": self.option("projects"),
"strategy": self.option("strategy"),
}

root = repo.get_workspace_root(Path.cwd())
Expand Down
10 changes: 8 additions & 2 deletions components/polylith/test/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
from polylith.test import report
from polylith.test.core import get_brick_imports_in_tests, get_changed_files
from polylith.test.core import get_brick_imports_in_tests, get_changed_files, get_related_bricks
from polylith.test.tests import create_test

__all__ = ["report", "create_test", "get_brick_imports_in_tests", "get_changed_files"]
__all__ = [
"report",
"create_test",
"get_brick_imports_in_tests",
"get_changed_files",
"get_related_bricks",
]
44 changes: 42 additions & 2 deletions components/polylith/test/core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from typing import List, Union
from typing import List, Set, Union

from polylith import diff, imports

Expand All @@ -16,6 +16,29 @@ def is_test(root: Path, ns: str, path: Path, theme: str) -> bool:
return f"/{expected}/{ns}" in file_path


def extract_parts_from_test_path(root: Path, path: Path) -> List[str]:
relative_path = str.replace(path.as_posix(), root.as_posix(), "")
parts = str.split(relative_path, "/")

return [p for p in parts if p]


def extract_brick_type_from_test(root: Path, path: Path, theme: str) -> str:
parts = extract_parts_from_test_path(root, path)

return parts[1] if theme == "loose" else parts[0]


def extract_brick_name_from_test(root: Path, path: Path, theme: str) -> str:
parts = extract_parts_from_test_path(root, path)

return parts[3] if theme == "loose" else parts[1]


def find_tests(root: Path, ns: str, theme: str, files: List[Path]) -> Set[Path]:
return {f for f in files if is_test(root, ns, f, theme)}


def get_changed_files(root: Path, tag_name: Union[str, None]) -> List[Path]:
tag = diff.collect.get_latest_tag(root, tag_name) or tag_name

Expand All @@ -28,10 +51,27 @@ def get_changed_files(root: Path, tag_name: Union[str, None]) -> List[Path]:
def get_brick_imports_in_tests(
root: Path, ns: str, theme: str, files: List[Path]
) -> dict:
matched = {f for f in files if is_test(root, ns, f, theme)}
matched = find_tests(root, ns, theme, files)

listed_imports = [imports.list_imports(m) for m in matched]

all_imports = dict(enumerate(listed_imports))

return imports.extract_brick_imports(all_imports, ns)


def get_related_brick(root: Path, path: Path, theme: str) -> dict:
brick_name = extract_brick_name_from_test(root, path, theme)
brick_type = extract_brick_type_from_test(root, path, theme)

return {"name": brick_name, "type": brick_type}


def get_related_bricks(root: Path, ns: str, theme: str, files: List[Path]) -> dict:
matched = find_tests(root, ns, theme, files)

bricks = [get_related_brick(root, m, theme) for m in matched]
bases = {b["name"] for b in bricks if b["type"] == "bases"}
components = {b["name"] for b in bricks if b["type"] == "components"}

return {"bases": bases, "components": components}
2 changes: 1 addition & 1 deletion projects/poetry_polylith_plugin/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "poetry-polylith-plugin"
version = "1.53.1"
version = "1.54.0"
description = "A Poetry plugin that adds tooling support for the Polylith Architecture"
authors = ["David Vujic"]
homepage = "https://davidvujic.github.io/python-polylith-docs/"
Expand Down
2 changes: 1 addition & 1 deletion projects/polylith_cli/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "polylith-cli"
version = "1.49.1"
version = "1.50.0"
description = "Python tooling support for the Polylith Architecture"
authors = ['David Vujic']
homepage = "https://davidvujic.github.io/python-polylith-docs/"
Expand Down
39 changes: 39 additions & 0 deletions test/components/polylith/test/test_core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from pathlib import Path

from polylith.test import core


def test_extract_brick_name_from_test() -> None:
expected = "hello_world"
root = Path.cwd()

first_test = root / f"test/components/my_namespace/{expected}/the_test.py"
second_test = root / f"components/{expected}/my_namespace/{expected}/test/the_test.py"

first = core.extract_brick_name_from_test(root, first_test, theme="loose")
second = core.extract_brick_name_from_test(root, second_test, theme="tdd")

assert first == expected
assert second == expected


def test_extract_brick_type_from_test() -> None:
root = Path.cwd()

loose_base_test = root / "test/bases/my_namespace/hello/the_test.py"
loose_comp_test = root / "test/components/my_namespace/world/the_test.py"

tdd_base_test = root / "bases/hello/my_namespace/hello/test/the_test.py"
tdd_comp_test = root / "components/world/my_namespace/world/test/the_test.py"

first = core.extract_brick_type_from_test(root, loose_base_test, theme="loose")
second = core.extract_brick_type_from_test(root, loose_comp_test, theme="loose")

third = core.extract_brick_type_from_test(root, tdd_base_test, theme="tdd")
fourth = core.extract_brick_type_from_test(root, tdd_comp_test, theme="tdd")

assert first == "bases"
assert second == "components"

assert third == "bases"
assert fourth == "components"