Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unit tests and partially rewrite method to pass tests. #45

Closed
wants to merge 5 commits into from
Closed
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
python -m site
python -m pip install --upgrade pip
# setuptools needed for 3.12+ because of https://github.com/mesonbuild/meson/issues/7702.
python -m pip install meson ninja setuptools
python -m pip install meson ninja setuptools pytest
- name: Install portage
run: |
mkdir portage
Expand Down
8 changes: 2 additions & 6 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,8 @@ endif
subdir('bin')
subdir('pym')

test(
'python-unittest',
py,
args : ['-m', 'unittest', 'discover', meson.current_source_dir() / 'pym'],
timeout : 0
)
pytest = find_program('pytest')
test('pytest', pytest, args : ['-v', meson.current_source_dir() / 'pym'])

if get_option('code-only')
subdir_done()
Expand Down
102 changes: 36 additions & 66 deletions pym/gentoolkit/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
import itertools
from functools import cache
from enum import Enum
from typing import List, Dict
from typing import List, Dict, Iterable, Iterator, Set, Optional, Any, Union

import portage
from portage.dep import paren_reduce

from gentoolkit import errors
from gentoolkit.atom import Atom
from gentoolkit.query import Query
from gentoolkit.cpv import CPV

# =======
# Classes
Expand Down Expand Up @@ -52,10 +53,11 @@ class Dependencies(Query):

"""

def __init__(self, query, parser=None):
def __init__(self, query: Any, parser: Any = None) -> None:
Query.__init__(self, query)
self.use = []
self.depatom = ""
self.use: List[str] = []
self.depatom: Optional[Atom] = None
self.depth: Optional[int] = None

# Allow a custom parser function:
self.parser = parser if parser else self._parser
Expand Down Expand Up @@ -185,109 +187,77 @@ def graph_depends(

def graph_reverse_depends(
self,
pkgset=None,
max_depth=-1,
only_direct=True,
printer_fn=None,
pkgset: Iterable[Union[str, CPV]],
max_depth: Optional[int] = None,
only_direct: bool = True,
# The rest of these are only used internally:
depth=0,
seen=None,
result=None,
):
depth: int = 0,
seen: Optional[Set[str]] = None,
) -> Iterator["Dependencies"]:
"""Graph direct reverse dependencies for self.

Example usage:
>>> from gentoolkit.dependencies import Dependencies
>>> ffmpeg = Dependencies('media-video/ffmpeg-9999')
>>> # I only care about installed packages that depend on me:
... from gentoolkit.helpers import get_installed_cpvs
>>> # I want to pass in a sorted list. We can pass strings or
... # Package or Atom types, so I'll use Package to sort:
>>> # I want to pass in a list. We can pass strings or
... # Package or Atom types.
... from gentoolkit.package import Package
>>> installed = sorted(get_installed_cpvs())
>>> installed = get_installed_cpvs()
>>> deptree = ffmpeg.graph_reverse_depends(
... only_direct=False, # Include indirect revdeps
... pkgset=installed) # from installed pkgset
>>> len(deptree)
24

@type pkgset: iterable
@keyword pkgset: sorted pkg cpv strings or anything sublassing
@keyword pkgset: pkg cpv strings or anything sublassing
L{gentoolkit.cpv.CPV} to use for calculate our revdep graph.
@type max_depth: int
@type max_depth: optional
@keyword max_depth: Maximum depth to recurse if only_direct=False.
-1 means no maximum depth;
0 is the same as only_direct=True;
None means no maximum depth;
0 is the same as only_direct=True;
>0 means recurse only this many times;
@type only_direct: bool
@keyword only_direct: to recurse or not to recurse
@type printer_fn: callable
@keyword printer_fn: If None, no effect. If set, it will be applied to
each L{gentoolkit.atom.Atom} object as it is added to the results.
@rtype: list
@rtype: iterable
@return: L{gentoolkit.dependencies.Dependencies} objects
"""
if not pkgset:
err = (
"%s kwarg 'pkgset' must be set. "
"Can be list of cpv strings or any 'intersectable' object."
)
raise errors.GentoolkitFatalError(err % (self.__class__.__name__,))

if seen is None:
seen = set()
if result is None:
result = list()

if depth == 0:
pkgset = tuple(Dependencies(x) for x in pkgset)

pkgdep = None
for pkgdep in pkgset:
for pkgdep in (Dependencies(pkg) for pkg in pkgset):
if self.cp not in pkgdep.get_raw_depends():
# fast path for obviously non-matching packages. This saves
# us the work of instantiating a whole Atom() for *every*
# dependency of *every* package in pkgset.
continue

all_depends = pkgdep.get_all_depends()
dep_is_displayed = False
for dep in all_depends:
# TODO: Add ability to determine if dep is enabled by USE flag.
# Check portage.dep.use_reduce
found_match = False
for dep in pkgdep.get_all_depends():
if dep.intersects(self):
pkgdep.depatom = dep
pkgdep.depth = depth
pkgdep.matching_dep = dep
if printer_fn is not None:
printer_fn(pkgdep, dep_is_displayed=dep_is_displayed)
result.append(pkgdep)
dep_is_displayed = True

# if --indirect specified, call ourselves again with the dep
# Do not call if we have already called ourselves.
yield pkgdep
found_match = True

if (
dep_is_displayed
and not only_direct
found_match
and pkgdep.cpv not in seen
and (depth < max_depth or max_depth == -1)
and only_direct is False
and (max_depth is None or depth < max_depth)
):
seen.add(pkgdep.cpv)
result.append(
pkgdep.graph_reverse_depends(
pkgset=pkgset,
max_depth=max_depth,
only_direct=only_direct,
printer_fn=printer_fn,
depth=depth + 1,
seen=seen,
result=result,
)
yield from pkgdep.graph_reverse_depends(
pkgset=pkgset,
only_direct=False,
max_depth=max_depth,
depth=depth + 1,
seen=seen,
)

if depth == 0:
return result
return pkgdep

def _parser(self, deps, use_conditional=None, depth=0):
"""?DEPEND file parser.

Expand Down
37 changes: 22 additions & 15 deletions pym/gentoolkit/equery/depends.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from gentoolkit.dependencies import Dependencies
from gentoolkit.equery import format_options, mod_usage, CONFIG
from gentoolkit.helpers import get_cpvs, get_installed_cpvs
from gentoolkit.cpv import CPV
from gentoolkit.package import PackageFormatter, Package

# =======
Expand All @@ -27,7 +26,7 @@
QUERY_OPTS = {
"include_masked": False,
"only_direct": True,
"max_depth": -1,
"max_depth": None,
"package_format": None,
}

Expand All @@ -36,8 +35,8 @@
# =======


class DependPrinter:
"""Output L{gentoolkit.dependencies.Dependencies} objects."""
class Printer:
"""Output L{gentoolkit.dependencies.Dependencies} objects for equery depends."""

def __init__(self, verbose=True):
self.verbose = verbose
Expand Down Expand Up @@ -84,7 +83,7 @@ def print_formated(pkg):
)

def format_depend(self, dep, dep_is_displayed):
"""Format a dependency for printing.
"""Format a dependency for printing for equery depends.

@type dep: L{gentoolkit.dependencies.Dependencies}
@param dep: the dependency to display
Expand All @@ -94,9 +93,9 @@ def format_depend(self, dep, dep_is_displayed):
if dep_is_displayed and not self.verbose:
return

depth = getattr(dep, "depth", 0)
depth = dep.depth
indent = " " * depth
mdep = dep.matching_dep
mdep = dep.depatom
use_conditional = ""

if QUERY_OPTS["package_format"] != None:
Expand Down Expand Up @@ -210,7 +209,7 @@ def main(input_args):
# Output
#

dep_print = DependPrinter(verbose=CONFIG["verbose"])
printer = Printer(verbose=CONFIG["verbose"])

first_run = True
got_match = False
Expand All @@ -226,17 +225,25 @@ def main(input_args):

if CONFIG["verbose"]:
print(" * These packages depend on %s:" % pp.emph(pkg.cpv))
if pkg.graph_reverse_depends(
pkgset=sorted(pkggetter(), key=CPV),
max_depth=QUERY_OPTS["max_depth"],

first_run = False

last_seen = None
for pkgdep in pkg.graph_reverse_depends(
pkgset=sorted(pkggetter()),
only_direct=QUERY_OPTS["only_direct"],
printer_fn=dep_print,
max_depth=QUERY_OPTS["max_depth"],
):
if last_seen is None or last_seen != pkgdep:
seen = False
else:
seen = True
printer(pkgdep, dep_is_displayed=seen)
last_seen = pkgdep
if last_seen is not None:
got_match = True

first_run = False

if not got_match:
if got_match is None:
sys.exit(1)


Expand Down
71 changes: 71 additions & 0 deletions pym/gentoolkit/test/test_dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import portage
from typing import List, Dict, Optional
from pytest import MonkeyPatch
from gentoolkit.dependencies import Dependencies


def is_cp_in_cpv(cp: str, cpv: str) -> bool:
other_cp, _, _ = portage.pkgsplit(cpv)
return cp == other_cp


def environment(
self: Dependencies,
env_vars: List[str],
fake_depends: Dict[str, Optional[Dict[str, str]]],
fake_pkgs: List[str],
) -> List[str]:
metadata = None
for pkg in fake_pkgs:
if is_cp_in_cpv(self.cp, pkg):
if (metadata := fake_depends[pkg]) is not None:
break
else:
return [""]
results = list()
for env_var in env_vars:
try:
value = metadata[env_var]
except KeyError:
value = ""
results.append(value)
return results


def test_basic_revdeps(monkeypatch: MonkeyPatch) -> None:
fake_depends = {
"app-misc/root-1.0": None,
"app-misc/a-1.0": {"DEPEND": "app-misc/root"},
"app-misc/b-1.0": {"DEPEND": "app-misc/a"},
"app-misc/c-1.0": {"DEPEND": "app-misc/b"},
"app-misc/d-1.0": None,
}
fake_pkgs = list(fake_depends.keys())

def e(self, env_vars):
return environment(self, env_vars, fake_depends, fake_pkgs)

monkeypatch.setattr(Dependencies, "environment", e)

# confirm that monkeypatch is working as expected
assert Dependencies("app-misc/root").environment(["DEPEND"]) == [""]
assert Dependencies("app-misc/a").environment(["DEPEND"]) == ["app-misc/root"]
assert Dependencies("app-misc/b").environment(["DEPEND"]) == ["app-misc/a"]
assert Dependencies("app-misc/c").environment(["DEPEND"]) == ["app-misc/b"]
assert Dependencies("app-misc/d").environment(["DEPEND"]) == [""]

assert sorted(
pkg.cpv
for pkg in Dependencies("app-misc/root").graph_reverse_depends(pkgset=fake_pkgs)
) == ["app-misc/a-1.0"]

assert sorted(
pkg.cpv
for pkg in Dependencies("app-misc/root").graph_reverse_depends(
pkgset=fake_pkgs, only_direct=False
)
) == [
"app-misc/a-1.0",
"app-misc/b-1.0",
"app-misc/c-1.0",
]
Loading