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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*.swp
__pycache__
.pyc
.hypothesis

# sample repo
tests/fixtures/sample-workspace
Expand Down
2 changes: 1 addition & 1 deletion bough/formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def dependency_graph(analyzer: BoughAnalyzer) -> str:
lines.extend(_render_graph(buildable, "🚀 Buildable Packages:", warning=True))

if libraries:
lines.extend(_render_graph(buildable, "📚 Library Packages:"))
lines.extend(_render_graph(libraries, "📚 Library Packages:"))

if not buildable and not libraries:
lines.append("No packages found in workspace.")
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ source = "vcs"

[dependency-groups]
dev = [
"hypothesis>=6.143.1",
"ipdb>=0.13.13",
"pytest>=8.4.1",
"pytest-cov>=7.0.0",
Expand Down
109 changes: 109 additions & 0 deletions tests/test_formatters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from hypothesis import given, strategies as st
from unittest.mock import Mock
from pathlib import Path
import json
import bough.formatters as sut

package_name = st.text(
min_size=1,
max_size=20,
alphabet=st.characters(blacklist_characters='\n\r')
)

packages = st.sets(package_name, min_size=1)
files = st.sets(package_name, min_size=1)

@given(packages)
def test_github_matrix_always_valid_json(package_names):
"""GitHub matrix output must always be valid JSON."""
analyzer = Mock()
analyzer.packages = {
name: Mock(directory=Path(f"/root/{name}"))
for name in package_names
}
analyzer.workspace_root = Path("/root")

result = sut.github_matrix(analyzer, package_names)
parsed = json.loads(result)

assert len(parsed["include"]) == len(package_names)
assert all("package" in item for item in parsed["include"])


@given(packages)
def test_quiet_output_has_correct_line_count(package_names):
"""Quiet mode outputs exactly one line per package."""
analyzer = Mock()
analyzer.packages = {
name: Mock(directory=Path(f"/root/{name}"))
for name in package_names
}

result = sut.quiet(analyzer, package_names)

assert len(result.split("\n")) == len(package_names)


@given(packages, files)
def test_human_readable_contains_all_packages(packages, files):
"""All package names must appear in human readable output."""
analyzer = Mock()
analyzer.packages = {
name: Mock(directory=Path(f"/root/{name}"))
for name in packages
}
analyzer.workspace_root = Path("/root")

result = sut.human_readable(analyzer, packages, files)

for pkg in packages:
assert pkg in result



@st.composite
def package_graph(draw):
"""Generate a set of packages with dependencies referencing each other."""
num_packages = draw(st.integers(min_value=1, max_value=10))
names = [draw(package_name) for _ in range(num_packages)]
names = list(set(names))

packages = []
for name in names:
deps = draw(st.sets(st.sampled_from(names), max_size=3)) - {name}
is_buildable = draw(st.booleans())
packages.append({
"name": name,
"dependencies": deps,
"is_buildable": is_buildable
})

return packages

@given(package_graph())
def test_dependency_graph_contains_all_packages(packages):
"""All package names must appear in dependency graph output."""
analyzer = Mock()
analyzer.packages = {
pkg["name"]: Mock(
directory=Path(f"/root/{pkg['name']}"),
dependencies=pkg["dependencies"]
)
for pkg in packages
}
analyzer.workspace_root = Path("/root")

# Build reverse dependency graph
analyzer.dependency_graph = {name: set() for name in analyzer.packages}
for pkg in packages:
for dep in pkg["dependencies"]:
if dep in analyzer.dependency_graph:
analyzer.dependency_graph[dep].add(pkg["name"])

buildable_names = {pkg["name"] for pkg in packages if pkg["is_buildable"]}
analyzer._is_buildable_package = lambda p: p.directory.name in buildable_names

result = sut.dependency_graph(analyzer)

for pkg in packages:
assert pkg["name"] in result
35 changes: 34 additions & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.