Skip to content

Commit

Permalink
Store diffs in JSON report, HTML export for reccmp-aggregate (#94)
Browse files Browse the repository at this point in the history
* Include diffs in JSON report. HTML output for aggregate script

* Add diff to deserialize

* Remove TODO
  • Loading branch information
disinvite authored Mar 9, 2025
1 parent f5d8ca3 commit 1379ce7
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 22 deletions.
7 changes: 5 additions & 2 deletions reccmp/isledecomp/compare/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,10 @@ def combine_reports(samples: list[ReccmpStatusReport]) -> ReccmpStatusReport:

output.entities[addr] = e_list[0]

# Recomp addr will most likely vary between samples, so clear it
output.entities[addr].recomp_addr = None
# Keep the recomp_addr if it is the same across all samples.
# i.e. to detect where function alignment ends
if not all(e_list[0].recomp_addr == e.recomp_addr for e in e_list):
output.entities[addr].recomp_addr = "various"

return output

Expand Down Expand Up @@ -165,6 +167,7 @@ def _deserialize_version_1(obj: JSONReportVersion1) -> ReccmpStatusReport:
recomp_addr=e.recomp,
is_stub=e.stub,
is_effective_match=e.effective,
diff=e.diff,
)

return report
Expand Down
26 changes: 25 additions & 1 deletion reccmp/isledecomp/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,31 @@
from datetime import datetime
import logging
import colorama
from reccmp.isledecomp.compare.report import ReccmpStatusReport, ReccmpComparedEntity
from pystache import Renderer # type: ignore[import-untyped]
from reccmp.assets import get_asset_file
from reccmp.isledecomp.compare.report import (
ReccmpStatusReport,
ReccmpComparedEntity,
serialize_reccmp_report,
)


def write_html_report(html_file: str, report: ReccmpStatusReport):
"""Create the interactive HTML diff viewer with the given report."""
js_path = get_asset_file("../assets/reccmp.js")
with open(js_path, "r", encoding="utf-8") as f:
reccmp_js = f.read()

# Convert the report to a JSON string to insert in the HTML template.
report_str = serialize_reccmp_report(report, diff_included=True)

output_data = Renderer().render_path(
get_asset_file("../assets/template.html"),
{"report": report_str, "reccmp_js": reccmp_js},
)

with open(html_file, "w", encoding="utf-8") as htmlfile:
htmlfile.write(output_data)


def print_combined_diff(udiff, plain: bool = False, show_both: bool = False):
Expand Down
15 changes: 12 additions & 3 deletions reccmp/tools/aggregate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import logging
from typing import Sequence
from pathlib import Path
from reccmp.isledecomp.utils import diff_json
from reccmp.isledecomp.utils import diff_json, write_html_report
from reccmp.isledecomp.compare.report import (
ReccmpStatusReport,
combine_reports,
Expand Down Expand Up @@ -90,6 +90,12 @@ def main():
action=TwoOrFewerArgsAction,
help="Report files to diff.",
)
parser.add_argument(
"--html",
type=Path,
metavar="<file>",
help="Location for HTML report based on aggregate.",
)
parser.add_argument(
"--output",
"-o",
Expand All @@ -116,9 +122,9 @@ def main():
"exepected arguments for --samples or --diff. (No input files specified)"
)

if not (args.output or args.diff):
if not (args.output or args.diff or args.html):
parser.error(
"expected arguments for --output or --diff. (No output action specified)"
"expected arguments for --output, --html, or --diff. (No output action specified)"
)

agg_report: ReccmpStatusReport | None = None
Expand All @@ -143,6 +149,9 @@ def main():
if args.output is not None:
write_report_file(args.output, agg_report)

if args.html is not None:
write_html_report(args.html, agg_report)

# If --diff has at least one file and we aggregated some samples this run, diff the first file and the aggregate.
# If --diff has two files and we did not aggregate this run, diff the files in the list.
if args.diff is not None:
Expand Down
28 changes: 12 additions & 16 deletions reccmp/tools/asmcmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
print_combined_diff,
diff_json,
percent_string,
write_html_report,
)

from reccmp.isledecomp.compare import Compare as IsleCompare
Expand Down Expand Up @@ -44,20 +45,6 @@ def gen_json(json_file: str, json_str: str):
f.write(json_str)


def gen_html(html_file: str, report: str):
js_path = get_asset_file("../assets/reccmp.js")
with open(js_path, "r", encoding="utf-8") as f:
reccmp_js = f.read()

output_data = Renderer().render_path(
get_asset_file("../assets/template.html"),
{"report": report, "reccmp_js": reccmp_js},
)

with open(html_file, "w", encoding="utf-8") as htmlfile:
htmlfile.write(output_data)


def gen_svg(svg_file, name_svg, icon, svg_implemented_funcs, total_funcs, raw_accuracy):
icon_data = None
if icon:
Expand Down Expand Up @@ -162,6 +149,11 @@ def virtual_address(value) -> int:
metavar="<file>",
help="Generate JSON file with match summary",
)
parser.add_argument(
"--json-diet",
action="store_true",
help="Exclude diff from JSON report.",
)
parser.add_argument(
"--diff",
metavar="<file>",
Expand Down Expand Up @@ -301,10 +293,14 @@ def main():
## Generate files and show summary.

if args.json is not None:
gen_json(args.json, serialize_reccmp_report(report))
# If we're on a diet, hold the diff.
diff_included = not bool(args.json_diet)
gen_json(
args.json, serialize_reccmp_report(report, diff_included=diff_included)
)

if args.html is not None:
gen_html(args.html, serialize_reccmp_report(report, diff_included=True))
write_html_report(args.html, report)

implemented_funcs = function_count

Expand Down
18 changes: 18 additions & 0 deletions tests/test_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,21 @@ def test_aggregate_different_files():

with pytest.raises(ReccmpReportSameSourceError):
combine_reports([x, y])


def test_aggregate_recomp_addr():
"""We combine the entity data based on the orig addr because this will not change.
The recomp addr may vary a lot. If it is the same in all samples, use the value.
Otherwise use a placeholder value."""
x = create_report([("100", 0.8), ("200", 0.2)])
y = create_report([("100", 0.2), ("200", 0.8)])
# These recomp addrs match:
x.entities["100"].recomp_addr = "500"
y.entities["100"].recomp_addr = "500"
# Y report has no addr for this
x.entities["200"].recomp_addr = "600"

combined = combine_reports([x, y])
assert combined.entities["100"].recomp_addr == "500"
assert combined.entities["200"].recomp_addr != "600"
assert combined.entities["200"].recomp_addr == "various"

0 comments on commit 1379ce7

Please sign in to comment.