|
| 1 | +import contextlib |
| 2 | +import io |
1 | 3 | import os |
| 4 | +import pathlib |
2 | 5 | import shlex |
3 | 6 | import shutil |
4 | 7 | import subprocess |
5 | | -import tempfile |
6 | 8 | import textwrap |
| 9 | +import typing |
7 | 10 | from collections import defaultdict |
| 11 | +from dataclasses import dataclass |
8 | 12 | from functools import partial |
9 | 13 | from io import StringIO |
10 | 14 | from operator import attrgetter |
11 | 15 | from unittest.mock import patch |
12 | 16 |
|
13 | 17 | import pytest |
14 | | -from pkgcheck import __title__ as project |
15 | | -from pkgcheck import base |
16 | | -from pkgcheck import checks as checks_mod |
17 | | -from pkgcheck import const, objects, reporters, scan |
18 | | -from pkgcheck.scripts import run |
19 | 18 | from pkgcore import const as pkgcore_const |
20 | 19 | from pkgcore.ebuild import atom, restricts |
21 | 20 | from pkgcore.restrictions import packages |
|
24 | 23 | from snakeoil.formatters import PlainTextFormatter |
25 | 24 | from snakeoil.osutils import pjoin |
26 | 25 |
|
| 26 | +from pkgcheck import __title__ as project |
| 27 | +from pkgcheck import base, const, objects, reporters, scan |
| 28 | +from pkgcheck import checks as checks_mod |
| 29 | +from pkgcheck.results import Result |
| 30 | +from pkgcheck.scripts import run |
| 31 | + |
27 | 32 | from ..misc import Profile |
28 | 33 |
|
29 | 34 |
|
@@ -587,63 +592,85 @@ def _scan_results(self, repo, tmp_path, verbosity): |
587 | 592 | assert len(results) == len(results_set) |
588 | 593 | return results_set |
589 | 594 |
|
590 | | - def _get_results(self, path: str): |
| 595 | + @dataclass |
| 596 | + class _expected_data_result: |
| 597 | + expected: typing.Iterable[Result] |
| 598 | + expected_verbose: typing.Iterable[Result] |
| 599 | + |
| 600 | + def _load_expected_data(self, path: str) -> _expected_data_result: |
591 | 601 | """Return the set of result objects from a given json stream file.""" |
592 | | - try: |
593 | | - with (self.repos_data / path).open() as f: |
594 | | - return set(reporters.JsonStream.from_iter(f)) |
595 | | - except FileNotFoundError: |
596 | | - return set() |
597 | 602 |
|
598 | | - def _render_results(self, results, **kwargs): |
| 603 | + def boilerplate(path, allow_missing: bool) -> list[Result]: |
| 604 | + try: |
| 605 | + with path.open() as f: |
| 606 | + data = list(reporters.JsonStream.from_iter(f)) |
| 607 | + |
| 608 | + uniqued = set(data) |
| 609 | + duplicates = [ |
| 610 | + x for x in data if (False, None) == (x in uniqued, uniqued.discard(x)) |
| 611 | + ] |
| 612 | + assert [] == duplicates, f"duplicate results exist in {path!r}" |
| 613 | + |
| 614 | + # Remove this after cleaning the scan/fix logic up to not force duplicate |
| 615 | + # renders, and instead just work with a result stream directly. |
| 616 | + assert self._render_results(data), f"failed rendering results {data!r}" |
| 617 | + return data |
| 618 | + |
| 619 | + except FileNotFoundError: |
| 620 | + if not allow_missing: |
| 621 | + raise |
| 622 | + return [] |
| 623 | + |
| 624 | + expected_path = self.repos_data / path / "expected.json" |
| 625 | + |
| 626 | + expected = boilerplate(expected_path, False) |
| 627 | + assert expected, f"regular results must always exist if the file exists: {expected_path}" |
| 628 | + |
| 629 | + expected_verbose_path = self.repos_data / path / "expected-verbose.json" |
| 630 | + expected_verbose = boilerplate(expected_verbose_path, True) |
| 631 | + |
| 632 | + return self._expected_data_result(expected, expected_verbose=expected_verbose) |
| 633 | + |
| 634 | + def _render_results(self, results, **kwargs) -> str: |
599 | 635 | """Render a given set of result objects into their related string form.""" |
600 | | - with tempfile.TemporaryFile() as f: |
| 636 | + with io.BytesIO() as f: |
601 | 637 | with reporters.FancyReporter(out=PlainTextFormatter(f), **kwargs) as reporter: |
602 | 638 | for result in sorted(results): |
603 | 639 | reporter.report(result) |
604 | | - f.seek(0) |
605 | | - output = f.read().decode() |
606 | | - return output |
| 640 | + return f.getvalue().decode() |
607 | 641 |
|
608 | 642 | @pytest.mark.parametrize("repo", repos) |
609 | 643 | def test_scan_repo(self, repo, tmp_path_factory): |
610 | 644 | """Run pkgcheck against test pkgs in bundled repo, verifying result output.""" |
611 | | - results = set() |
612 | | - verbose_results = set() |
| 645 | + expected_results = set() |
613 | 646 | scan_results = self._scan_results(repo, tmp_path_factory.mktemp("scan"), verbosity=0) |
| 647 | + |
| 648 | + expected_verbose_results = set() |
614 | 649 | scan_verbose_results = self._scan_results(repo, tmp_path_factory.mktemp("ver"), verbosity=1) |
| 650 | + |
615 | 651 | for check, keywords in self._checks[repo].items(): |
616 | 652 | for keyword in keywords: |
617 | | - # verify the expected results were seen during the repo scans |
618 | | - expected_results = self._get_results(f"{repo}/{check}/{keyword}/expected.json") |
619 | | - assert expected_results, "regular results must always exist" |
620 | | - assert self._render_results(expected_results), "failed rendering results" |
621 | | - results.update(expected_results) |
622 | | - |
623 | | - # when expected verbose results exist use them, otherwise fallback to using the regular ones |
624 | | - expected_verbose_results = self._get_results( |
625 | | - f"{repo}/{check}/{keyword}/expected-verbose.json" |
626 | | - ) |
627 | | - if expected_verbose_results: |
628 | | - assert self._render_results(expected_verbose_results), ( |
629 | | - "failed rendering verbose results" |
630 | | - ) |
631 | | - verbose_results.update(expected_verbose_results) |
| 653 | + data = self._load_expected_data(f"{repo}/{check}/{keyword}") |
| 654 | + expected_results.update(data.expected) |
| 655 | + |
| 656 | + if data.expected_verbose: |
| 657 | + expected_verbose_results.update(data.expected_verbose) |
632 | 658 | else: |
633 | | - verbose_results.update(expected_results) |
| 659 | + expected_verbose_results.update(data.expected) |
634 | 660 |
|
635 | | - if results != scan_results: |
636 | | - missing = self._render_results(results - scan_results) |
637 | | - unknown = self._render_results(scan_results - results) |
| 661 | + if expected_results != scan_results: |
| 662 | + missing = self._render_results(expected_results - scan_results) |
| 663 | + unknown = self._render_results(scan_results - expected_results) |
638 | 664 | error = ["unmatched repo scan results:\n\n"] |
639 | 665 | if missing: |
640 | 666 | error.append(f"{repo} repo missing expected results:\n{missing}") |
641 | 667 | if unknown: |
642 | 668 | error.append(f"{repo} repo unknown results:\n{unknown}") |
643 | 669 | pytest.fail("\n".join(error), pytrace=False) |
644 | | - if verbose_results != scan_verbose_results: |
645 | | - missing = self._render_results(verbose_results - scan_verbose_results) |
646 | | - unknown = self._render_results(scan_verbose_results - verbose_results) |
| 670 | + |
| 671 | + if expected_verbose_results != scan_verbose_results: |
| 672 | + missing = self._render_results(expected_verbose_results - scan_verbose_results) |
| 673 | + unknown = self._render_results(scan_verbose_results - expected_verbose_results) |
647 | 674 | error = ["unmatched verbose repo scan results:\n\n"] |
648 | 675 | if missing: |
649 | 676 | error.append(f"{repo} repo missing expected results:\n{missing}") |
|
0 commit comments