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 ability to merge coverage reports #2435

Merged
merged 1 commit into from
May 28, 2024
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
8 changes: 4 additions & 4 deletions cardano_node_tests/chang_us_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ def get_args() -> argparse.Namespace:


def _get_color(status: str) -> str:
if status == requirements.Statuses.SUCCESS:
if status == requirements.Statuses.success.name:
return "green"
if status == requirements.Statuses.FAILURE:
if status == requirements.Statuses.failure.name:
return "red"
if status == requirements.Statuses.PARTIAL_SUCCESS:
if status == requirements.Statuses.partial_success.name:
return "yellow"
return "grey"

Expand All @@ -62,7 +62,7 @@ def main() -> None:

for req_id, req_data in chang_group.items():
# Partial or uncovered requirements should be ignored
if req_id.startswith("int") or req_data["status"] == requirements.Statuses.UNCOVERED:
if req_id.startswith("int") or req_data["status"] == requirements.Statuses.uncovered.name:
continue

color = _get_color(req_data["status"])
Expand Down
40 changes: 31 additions & 9 deletions cardano_node_tests/dump_requirements_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"""Generate coverage results for external requirements."""

import argparse
import json
import logging

from cardano_node_tests.utils import helpers
Expand All @@ -13,13 +14,6 @@
def get_args() -> argparse.Namespace:
"""Get command line arguments."""
parser = argparse.ArgumentParser(description=__doc__.split("\n", maxsplit=1)[0])
parser.add_argument(
"-a",
"--artifacts-base-dir",
required=True,
type=helpers.check_dir_arg,
help="Path to a directory with testing artifacts",
)
parser.add_argument(
"-m",
"--requirements-mapping",
Expand All @@ -32,6 +26,19 @@ def get_args() -> argparse.Namespace:
required=True,
help="File where to save coverage results",
)
parser.add_argument(
"-a",
"--artifacts-base-dir",
type=helpers.check_dir_arg,
help="Path to a directory with testing artifacts",
)
parser.add_argument(
"-i",
"--input-files",
nargs="+",
type=helpers.check_file_arg,
help="Path to coverage results to merge into a final result",
)
return parser.parse_args()


Expand All @@ -42,9 +49,24 @@ def main() -> None:
)
args = get_args()

executed_req = requirements.collect_executed_req(base_dir=args.artifacts_base_dir)
if not (args.artifacts_base_dir or args.input_files):
LOGGER.error("Either `--artifacts-base-dir` or `--input-files` must be provided")
return

executed_req = {}
if args.artifacts_base_dir:
executed_req = requirements.collect_executed_req(base_dir=args.artifacts_base_dir)

input_reqs = []
if args.input_files:
for input_file in args.input_files:
with open(input_file, encoding="utf-8") as in_fp:
input_reqs.append(json.load(in_fp))

merged_reqs = requirements.merge_reqs(executed_req, *input_reqs)

report = requirements.get_mapped_req(
mapping=args.requirements_mapping, executed_req=executed_req
mapping=args.requirements_mapping, executed_req=merged_reqs
)

helpers.write_json(out_file=args.output_file, content=report)
Expand Down
55 changes: 41 additions & 14 deletions cardano_node_tests/utils/requirements.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Functionality for tracking execution of external requirements."""

import enum
import json
import logging
import pathlib as pl
Expand All @@ -14,11 +15,11 @@ class GroupsKnown:
CHANG_US: tp.Final[str] = "chang_us"


class Statuses:
SUCCESS: tp.Final[str] = "success"
FAILURE: tp.Final[str] = "failure"
UNCOVERED: tp.Final[str] = "uncovered"
PARTIAL_SUCCESS: tp.Final[str] = "partial_success"
class Statuses(enum.Enum):
success = 1
failure = 2
partial_success = 3
uncovered = 4


class Req:
Expand Down Expand Up @@ -48,14 +49,24 @@ def _get_dest_dir(self) -> pl.Path:
return dest_dir

def success(self) -> bool:
content = {"id": self.id, "group": self.group, "url": self.url, "status": Statuses.SUCCESS}
content = {
"id": self.id,
"group": self.group,
"url": self.url,
"status": Statuses.success.name,
}
helpers.write_json(
out_file=self._get_dest_dir() / f"{self.basename}_success.json", content=content
)
return True

def failure(self) -> bool:
content = {"id": self.id, "group": self.group, "url": self.url, "status": Statuses.FAILURE}
content = {
"id": self.id,
"group": self.group,
"url": self.url,
"status": Statuses.failure.name,
}
helpers.write_json(
out_file=self._get_dest_dir() / f"{self.basename}_init.json", content=content
)
Expand Down Expand Up @@ -86,7 +97,7 @@ def collect_executed_req(base_dir: pl.Path) -> dict:

req_id = req_rec["id"]
id_collected = group_collected.get(req_id)
if id_collected and id_collected["status"] == Statuses.SUCCESS:
if id_collected and id_collected["status"] == Statuses.success.name:
continue
if not id_collected:
id_collected = {}
Expand All @@ -97,6 +108,22 @@ def collect_executed_req(base_dir: pl.Path) -> dict:
return collected


def merge_reqs(*reqs: tp.Dict[str, dict]) -> dict:
"""Merge requirements."""
merged: tp.Dict[str, dict] = {}
for report in reqs:
for gname, greqs in report.items():
merged_group = merged.get(gname) or {}
for req_id, req_data in greqs.items():
merged_rec = merged_group.get(req_id) or {}
merged_status_val = Statuses[merged_rec.get("status") or "uncovered"].value
req_status_val = Statuses[req_data["status"]].value
if not merged_rec or req_status_val < merged_status_val:
merged_group[req_id] = req_data
merged[gname] = merged_group
return merged


def get_mapped_req(mapping: pl.Path, executed_req: dict) -> dict:
"""Get mapped requirements."""
with open(mapping, encoding="utf-8") as in_fp:
Expand All @@ -116,22 +143,22 @@ def get_mapped_req(mapping: pl.Path, executed_req: dict) -> dict:
if not url:
url = executed_group.get(p_req, {}).get("url")

if p_status == Statuses.SUCCESS:
if p_status == Statuses.success.name:
dependencies_success.append(p_req)
elif p_status == Statuses.FAILURE:
elif p_status == Statuses.failure.name:
dependencies_failures.append(p_req)

# If any partial requirement failed, the overall outcome would be failed
if dependencies_failures:
status = Statuses.FAILURE
status = Statuses.failure.name
# If none partial requirement is covered, the overall outcome would be uncovered
elif not (dependencies_success or dependencies_failures):
status = Statuses.UNCOVERED
status = Statuses.uncovered.name
# If all partial requirements are successful, the overall outcome would be success
elif len(dependencies_success) == len(dependencies):
status = Statuses.SUCCESS
status = Statuses.success.name
else:
status = Statuses.PARTIAL_SUCCESS
status = Statuses.partial_success.name

executed_req[group][req_id] = {"status": status, "url": url}

Expand Down
Loading