From 6354e01307592ef0ffbae3f7410114f90fa48b80 Mon Sep 17 00:00:00 2001 From: Florian Sattler Date: Mon, 17 Jul 2023 22:25:39 +0200 Subject: [PATCH] Implements a MultiPatchReport The multi patch report groups together reports of the same type that where produced with multiple different patches on a project. --- tests/report/test_multi_patch_report.py | 76 +++++++++++++++++ .../varats/report/multi_patch_report.py | 82 +++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 tests/report/test_multi_patch_report.py create mode 100644 varats-core/varats/report/multi_patch_report.py diff --git a/tests/report/test_multi_patch_report.py b/tests/report/test_multi_patch_report.py new file mode 100644 index 000000000..e192e1a79 --- /dev/null +++ b/tests/report/test_multi_patch_report.py @@ -0,0 +1,76 @@ +"""Test MultiPatchReport.""" + +import unittest +from pathlib import Path + +from varats.provider.patch.patch_provider import Patch +from varats.report.multi_patch_report import MultiPatchReport + + +class TestMultiPatchReport(unittest.TestCase): + """Tests if the basic components of MultiPatchReport are working.""" + + def test_baseline_report_name(self) -> None: + """Tests if baseline report names are correctly created and checked.""" + baseline_report_name = MultiPatchReport.create_baseline_report_name( + "my_base.txt" + ) + + self.assertEqual(baseline_report_name, "baseline_my_base.txt") + self.assertTrue( + MultiPatchReport.is_baseline_report(baseline_report_name) + ) + + self.assertFalse( + MultiPatchReport.is_baseline_report(baseline_report_name[1:]) + ) + + def test_patched_report_name(self) -> None: + """Tests if patched report names are correctly created and checked.""" + patch_shortname = "shortname" + patch = Patch("MyPatch", patch_shortname, "desc", Path()) + patched_report_name = MultiPatchReport.create_patched_report_name( + patch, "my_base.txt" + ) + + self.assertEqual( + patched_report_name, + f"patched_{len(patch_shortname)}_{patch_shortname}_my_base.txt" + ) + self.assertTrue(MultiPatchReport.is_patched_report(patched_report_name)) + self.assertFalse( + MultiPatchReport.is_baseline_report(patched_report_name) + ) + + self.assertFalse( + MultiPatchReport.is_baseline_report(patched_report_name[1:]) + ) + + def test_patched_report_parsing(self) -> None: + """Test if we can correctly parse patch shortnames.""" + patch_shortname = "shortname" + patch = Patch("MyPatch", patch_shortname, "desc", Path()) + patched_report_name = MultiPatchReport.create_patched_report_name( + patch, "my_base.txt" + ) + + self.assertEqual( + MultiPatchReport. + _parse_patch_shorthand_from_report_name(patched_report_name), + patch_shortname + ) + + def test_patched_report_parsing_with_extra_underscores(self) -> None: + """Test special parsing case where the patch shortname contains + underscores.""" + patch_shortname = "sh_ort_name" + patch = Patch("MyPatch", patch_shortname, "desc", Path()) + patched_report_name = MultiPatchReport.create_patched_report_name( + patch, "my_base.txt" + ) + + self.assertEqual( + MultiPatchReport. + _parse_patch_shorthand_from_report_name(patched_report_name), + patch_shortname + ) diff --git a/varats-core/varats/report/multi_patch_report.py b/varats-core/varats/report/multi_patch_report.py new file mode 100644 index 000000000..e0f7a1d5b --- /dev/null +++ b/varats-core/varats/report/multi_patch_report.py @@ -0,0 +1,82 @@ +"""MultiPatchReport to group together similar reports that where produced for +differently patched projects.""" +import shutil +import tempfile +import typing as tp +from pathlib import Path + +from varats.provider.patch.patch_provider import Patch +from varats.report.report import ReportTy, BaseReport + + +class MultiPatchReport( + BaseReport, tp.Generic[ReportTy], shorthand="MPR", file_type=".zip" +): + """Meta report to group together reports of the same type that where + produced with differently patched projects.""" + + def __init__(self, path: Path, report_type: tp.Type[ReportTy]) -> None: + super().__init__(path) + self.__patched_reports: tp.Dict[str, ReportTy] = {} + + with tempfile.TemporaryDirectory() as tmp_result_dir: + shutil.unpack_archive(path, extract_dir=tmp_result_dir) + + for report in Path(tmp_result_dir).iterdir(): + if self.is_baseline_report(report.name): + self.__base = report_type(report) + elif self.is_patched_report(report.name): + self.__patched_reports[ + self._parse_patch_shorthand_from_report_name( + report.name + )] = report_type(report) + + if not self.__base or not self.__patched_reports: + raise AssertionError( + "Reports where missing in the file {report_path=}" + ) + + def get_baseline_report(self) -> ReportTy: + return self.__base + + def get_report_for_patch(self, + patch_shortname: str) -> tp.Optional[ReportTy]: + """Get the report for a given patch shortname.""" + if patch_shortname in self.__patched_reports: + return self.__patched_reports[patch_shortname] + + return None + + def get_patch_names(self) -> tp.List[str]: + return list(self.__patched_reports.keys()) + + def get_patched_reports(self) -> tp.ValuesView[ReportTy]: + return self.__patched_reports.values() + + @staticmethod + def create_baseline_report_name(base_file_name: str) -> str: + return f"baseline_{base_file_name}" + + @staticmethod + def is_baseline_report(file_name: str) -> bool: + return file_name.startswith("baseline_") + + @staticmethod + def create_patched_report_name(patch: Patch, base_file_name: str) -> str: + return ( + f"patched_{len(patch.shortname)}_" + + f"{patch.shortname}_{base_file_name}" + ) + + @staticmethod + def is_patched_report(file_name: str) -> bool: + return file_name.startswith("patched_") + + @staticmethod + def _parse_patch_shorthand_from_report_name(file_name: str) -> str: + """Parse the patch shorthand from a given patched report.""" + fn_without_prefix = file_name[len("patched_"):] + split_leftover_fn = fn_without_prefix.partition("_") + shortname_length = int(split_leftover_fn[0]) + patch_shortname = "".join(split_leftover_fn[2:])[:shortname_length] + return patch_shortname