def test_circular_level(self): """ Simple circular dependency with different USE levels A → B → C ↖←←←←←↙ """ graph = nx.DiGraph() graph.add_nodes_from('ABC') graph.add_edge('A', 'B', dep=DepType.DEPEND, level=0) graph.add_edge('B', 'C', dep=DepType.DEPEND, level=0) graph.add_edge('C', 'A', dep=DepType.DEPEND, level=1) # check both forward and reverse graph to make sure it is # actually resolving circular dependencies and not just # producing incidentally correct output cycles: typing.List[CycleTuple] = [] self.assertEqual( list(get_ordered_nodes(graph.copy(), cycles.append)), ['C', 'B', 'A']) self.assertEqual( cycles, [([('A', 'B'), ('B', 'C'), ('C', 'A')], ('C', 'A'))]) cycles = [] self.assertEqual( list(get_ordered_nodes(graph.reverse())), ['A', 'B', 'C']) self.assertEqual( cycles, [])
def test_cross(self): """ Test graph with crossed subtrees (common dependencies) A ↙↓↘ B ↓ G ↙↓↘↓ ↑ C D E ↑ ↓↗ F """ graph = nx.DiGraph() graph.add_nodes_from('ABCDEFG') graph.add_edge('A', 'B', dep=DepType.DEPEND, level=0) graph.add_edge('B', 'C', dep=DepType.DEPEND, level=0) graph.add_edge('B', 'D', dep=DepType.DEPEND, level=0) graph.add_edge('A', 'E', dep=DepType.DEPEND, level=0) graph.add_edge('B', 'E', dep=DepType.DEPEND, level=0) graph.add_edge('E', 'F', dep=DepType.DEPEND, level=0) graph.add_edge('A', 'G', dep=DepType.DEPEND, level=0) graph.add_edge('F', 'G', dep=DepType.DEPEND, level=0) out = list(get_ordered_nodes(graph)) # verify that all nodes are present self.assertEqual(sorted(out), list('ABCDEFG')) # A must always come last self.assertEqual(out[-1], 'A') # verify relative order self.assertLess(out.index('C'), out.index('B')) self.assertLess(out.index('D'), out.index('B')) self.assertLess(out.index('E'), out.index('B')) self.assertLess(out.index('F'), out.index('E')) self.assertLess(out.index('G'), out.index('F'))
def test_simple(self): """Test simple A -> B -> C graph""" graph = nx.DiGraph() graph.add_nodes_from('ABC') graph.add_edge('A', 'B', dep=DepType.DEPEND, level=0) graph.add_edge('B', 'C', dep=DepType.DEPEND, level=0) self.assertEqual( list(get_ordered_nodes(graph)), ['C', 'B', 'A'])
def test_loose(self): """Test graph with unconnected nodes""" graph = nx.DiGraph() graph.add_nodes_from('ABCD') graph.add_edge('A', 'B', dep=DepType.DEPEND, level=0) out = list(get_ordered_nodes(graph)) # verify that all nodes are present self.assertEqual(sorted(out), list('ABCD')) # verify relative order self.assertLess(out.index('B'), out.index('A'))
def test_multi_circular(self): """ Complex dependency graph with multiple circular dependencies B←A ↓↗↓↘ C D→F ↕↖↓ E G """ graph = nx.DiGraph() graph.add_nodes_from('ABCDEFG') graph.add_edge('A', 'B', dep=DepType.DEPEND, level=0) graph.add_edge('B', 'C', dep=DepType.DEPEND, level=0) graph.add_edge('C', 'A', dep=DepType.RDEPEND, level=0) graph.add_edge('A', 'D', dep=DepType.DEPEND, level=0) graph.add_edge('D', 'E', dep=DepType.DEPEND, level=0) graph.add_edge('D', 'F', dep=DepType.RDEPEND, level=0) graph.add_edge('E', 'D', dep=DepType.PDEPEND, level=0) graph.add_edge('A', 'F', dep=DepType.RDEPEND, level=0) graph.add_edge('F', 'G', dep=DepType.DEPEND, level=0) graph.add_edge('G', 'D', dep=DepType.DEPEND, level=0) cycles: typing.List[CycleTuple] = [] out = list(get_ordered_nodes(graph, cycles.append)) # verify that all nodes are present self.assertEqual(sorted(out), list('ABCDEFG')) # A must always come last self.assertEqual(out[-1], 'A') # verify relative order self.assertLess(out.index('C'), out.index('B')) self.assertLess(out.index('D'), out.index('G')) self.assertLess(out.index('E'), out.index('D')) self.assertLess(out.index('G'), out.index('F')) # verify cycle resolution self.assertEqual( sorted(cycles), [([('A', 'B'), ('B', 'C'), ('C', 'A')], ('C', 'A')), ([('D', 'E'), ('E', 'D')], ('E', 'D')), ([('D', 'F'), ('F', 'G'), ('G', 'D')], ('D', 'F')), ])
def commit(self) -> int: repo, git_repo = self.get_git_repository() arch = self.get_arch() if not have_nattka_depgraph: log.warning('Unable to import nattka.depgraph, dependency sorting ' 'will not be available') log.warning('(nattka.depgraph requires networkx)') ret = 0 bugnos, bugs = self.find_bugs() for bno in bugnos: b = bugs[bno] if b.category is None: log.error(f'Bug {bno}: neither stablereq nor keywordreq') ret = 1 continue try: plist = dict( match_package_list( repo, b, filter_arch=arch, permit_allarches=not self.args.ignore_allarches)) except PackageMatchException as e: log.error(f'Bug {bno}: {e}') ret = 1 continue if have_nattka_depgraph: graph = get_depgraph_for_packages(plist) ordered_nodes = list(get_ordered_nodes(graph)) order = sorted(plist, key=lambda x: (ordered_nodes.index(x.key), x.fullver)) else: order = list(plist) allarches = (not self.args.ignore_allarches and 'ALLARCHES' in b.keywords) log.info(f'Bug {bno} ({b.category.name})' f'{" ALLARCHES" if allarches else ""}') for p in order: keywords = [k for k in plist[p] if k in arch] if not keywords: continue ebuild_path = Path(p.path).relative_to(repo.location) pfx = f'{p.category}/{p.package}' act = ('Stabilize' if b.category == BugCategory.STABLEREQ else 'Keyword') if allarches: kws = 'ALLARCHES' else: kws = ' '.join(keywords) msg = f'{pfx}: {act} {p.fullver} {kws}, #{bno}' try: print(git_commit(git_repo.path, msg, [str(ebuild_path)])) except GitCommitNoChanges: pass return ret
def apply(self) -> int: repo = self.get_repository() arch = self.get_arch() if not have_nattka_depgraph: log.warning('Unable to import nattka.depgraph, dependency sorting ' 'will not be available') log.warning('(nattka.depgraph requires networkx)') ret = 0 bugnos, bugs = self.find_bugs(arch=arch) for bno in bugnos: b = bugs[bno] if b.category is None: print(f'# bug {bno}: neither stablereq nor keywordreq\n') ret = 1 continue try: plist = dict( match_package_list( repo, b, only_new=True, filter_arch=arch, permit_allarches=not self.args.ignore_allarches)) except PackageMatchException as e: print(f'# bug {bno}: {e}\n') ret = 1 continue if (b.sanity_check is not True and not self.args.ignore_sanity_check): if b.sanity_check is False: print(f'# bug {bno}: sanity check failed\n') else: print(f'# bug {bno}: no sanity check result\n') ret = 1 continue all_keywords = frozenset( itertools.chain.from_iterable(plist.values())) unresolved_deps = [] for depno in b.depends: depb = bugs[depno] if depb.resolved: continue if depb.category == b.category: try: for depp, depkw in match_package_list( repo, depb, only_new=True, filter_arch=all_keywords): pass except PackageListEmpty: # ignore dependent bugs with empty package list # or mismatched keywords # (assuming the bug passed sanity-check anyway) continue except PackageMatchException: pass unresolved_deps.append(depno) if unresolved_deps and not self.args.ignore_dependencies: print(f'# bug {bno}: unresolved dependency on ' f'{", ".join(str(x) for x in unresolved_deps)}\n') ret = 1 continue if have_nattka_depgraph: graph = get_depgraph_for_packages(plist) ordered_nodes = list(get_ordered_nodes(graph)) order = sorted(plist, key=lambda x: (ordered_nodes.index(x.key), x.fullver)) else: order = list(plist) allarches = (not self.args.ignore_allarches and 'ALLARCHES' in b.keywords) print(f'# bug {bno} ({b.category.name})' f'{" ALLARCHES" if allarches else ""}') for p in order: kws = ' '.join(f'~{k}' for k in plist[p]) if b.category == BugCategory.STABLEREQ: print(f'={p.cpvstr} {kws}') else: print(f'={p.cpvstr} ** # -> {kws}') print() if not self.args.no_update: add_keywords(plist.items(), b.category == BugCategory.STABLEREQ) return ret