Path: blob/master/scripts/check_regression_tests.py
4201 views
#!/usr/bin/env python31# SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <[email protected]>2# SPDX-License-Identifier: CC-BY-NC-ND-4.034import argparse5import glob6import sys7import os8import re9import hashlib1011from pathlib import Path1213FILE_HEADER = """14<!DOCTYPE html>15<html>16<head>17<title>Comparison</title>18<style>19.modal {20display: none;21position: fixed;22z-index: 1;23padding-top: 100px;24left: 0;25top: 0;26width: 100%;27height: 100%;28overflow: auto;29background-color: black;30}3132.modal-content {33position: relative;34margin: auto;35padding: 0;36width: 90%;37max-width: 1200px;38}3940.close {41color: white;42position: absolute;43top: 10px;44right: 25px;45font-size: 35px;46font-weight: bold;47}4849.close:hover,50.close:focus {51color: #999;52text-decoration: none;53cursor: pointer;54}5556.prev,57.next {58cursor: pointer;59position: absolute;60top: 50%;61width: auto;62padding: 16px;63margin-top: -50px;64color: white;65font-weight: bold;66font-size: 20px;67transition: 0.6s ease;68border-radius: 0 3px 3px 0;69user-select: none;70-webkit-user-select: none;71}7273.next {74right: 0;75border-radius: 3px 0 0 3px;76}7778.prev:hover,79.next:hover {80background-color: rgba(0, 0, 0, 0.8);81}8283.item img {84cursor: pointer;85}8687#compareTitle {88color: white;89font-size: 20px;90font-family: sans-serif;91margin-bottom: 10px;92}9394#compareCaption {95color: white;96font-family: sans-serif;97}9899#compareState {100display: block;101position: absolute;102right: 0;103top: 0;104color: red;105font-family: sans-serif;106font-size: 20px;107font-weight: bold;108}109110#compareImage {111display: block;112margin: 0 auto;113width: 100%;114height: auto;115}116</style>117</head>118<body>119"""120121FILE_FOOTER = """122<div id="myModal" class="modal" tabindex="0">123<span class="close cursor" onclick="closeModal()">×</span>124<div class="modal-content">125<div id="compareTitle"></div>126<img id="compareImage" />127<div id="compareState"></div>128<a class="prev">❮</a>129<a class="next">❯</a>130<div id="compareCaption"></div>131</div>132</div>133<script>134/* Worst script known to man */135/* Sources:136https://www.w3schools.com/howto/howto_js_lightbox.asp137https://css-tricks.com/prevent-page-scrolling-when-a-modal-is-open/138*/139140function openModal() {141document.body.style.position = 'fixed';142document.body.style.top = `-${window.scrollY}px`;143document.getElementById("myModal").style.display = "block";144document.getElementById("myModal").focus();145setImageIndex(0);146}147148function closeModal() {149document.getElementById("myModal").style.display = "none";150const scrollY = document.body.style.top;151document.body.style.position = '';152document.body.style.top = '';153window.scrollTo(0, parseInt(scrollY || '0') * -1);154}155156function isModalOpen() {157return (document.getElementById("myModal").style.display == "block");158}159160function formatLines(str) {161let lines = str.split("\\n")162lines = lines.filter(line => !line.startsWith("Difference in frames"))163return lines.join("<br>")164}165166function extractItem(elem) {167var beforeSel = elem.querySelector(".before");168var afterSel = elem.querySelector(".after");169var preSel = elem.querySelector("pre");170return {171name: elem.querySelector("h1").innerText,172beforeImg: beforeSel ? beforeSel.getAttribute("src") : null,173afterImg: afterSel ? afterSel.getAttribute("src") : null,174details: formatLines(preSel ? preSel.innerText : "")175};176}177178const items = [...document.querySelectorAll(".item")].map(extractItem)179let currentImage = 0;180let currentState = 0;181182function getImageIndexForUri(uri) {183for (let i = 0; i < items.length; i++) {184if (items[i].beforeImg == uri || items[i].afterImg == uri)185return i;186}187return -1;188}189190function setImageState(state) {191const item = items[currentImage]192const uri = (state === 0) ? item.beforeImg : item.afterImg;193const stateText = (state === 0) ? "BEFORE" : "AFTER";194const posText = "(" + (currentImage + 1).toString() + "/" + (items.length).toString() + ") ";195document.getElementById("compareImage").setAttribute("src", uri);196document.getElementById("compareState").innerText = stateText;197document.getElementById("compareTitle").innerText = posText + item.name;198document.getElementById("compareCaption").innerHTML = item.details;199currentState = state;200}201202function setImageIndex(index) {203if (index < 0 || index > items.length)204return;205206currentImage = index;207setImageState(0);208}209210function handleKey(key) {211if (key == " ") {212setImageState((currentState === 0) ? 1 : 0);213return true;214} else if (key == "ArrowLeft") {215setImageIndex(currentImage - 1);216return true;217} else if (key == "ArrowRight") {218setImageIndex(currentImage + 1);219return true;220} else if (key == "Escape") {221closeModal();222return true;223} else {224console.log(key);225return false;226}227}228229document.getElementById("myModal").addEventListener("keydown", function(ev) {230if (ev.defaultPrevented)231return;232233if (handleKey(ev.key))234ev.preventDefault();235});236237document.querySelector("#myModal .prev").addEventListener("click", function() {238setImageIndex(currentImage - 1);239});240document.querySelector("#myModal .next").addEventListener("click", function() {241setImageIndex(currentImage + 1);242});243document.querySelectorAll(".item img").forEach(elem => elem.addEventListener("click", function() {244if (!isModalOpen())245openModal();246setImageIndex(getImageIndexForUri(this.getAttribute("src")));247}));248</script>249</body>250</html>251"""252253MAX_DIFF_FRAMES = 9999254255outfile = None256def write(line):257outfile.write(line + "\n")258259260def compare_frames(path1, path2):261try:262if os.stat(path1).st_size != os.stat(path2).st_size:263return False264265with open(path1, "rb") as f:266hash1 = hashlib.md5(f.read()).digest()267with open(path2, "rb") as f:268hash2 = hashlib.md5(f.read()).digest()269270#print(hash1, hash2)271return hash1 == hash2272except:273return False274275276def check_regression_test(baselinedir, testdir, name):277#print("Checking '%s'..." % name)278279dir1 = os.path.join(baselinedir, name)280dir2 = os.path.join(testdir, name)281if not os.path.isdir(dir2):282#print("*** %s is missing in test set" % name)283return False284285images = glob.glob(os.path.join(dir1, "frame_*.png"))286diff_frames = []287first_fail = True288has_any = False289290for imagepath in images:291imagename = Path(imagepath).name292matches = re.match("frame_([0-9]+).png", imagename)293if matches is None:294continue295296framenum = int(matches[1])297298path1 = os.path.join(dir1, imagename)299path2 = os.path.join(dir2, imagename)300if not os.path.isfile(path2):301print("--- Frame %u for %s is missing in test set" % (framenum, name))302if first_fail:303write("<div class=\"item\">")304write("<h1>{}</h1>".format(name))305write("<table width=\"100%\">")306first_fail = False307write("<p>--- Frame %u for %s is missing in test set</p>" % (framenum, name))308continue309310has_any = True311if not compare_frames(path1, path2):312diff_frames.append((framenum, path1, path2))313314for (framenum, path1, path2) in diff_frames[-MAX_DIFF_FRAMES:]:315if first_fail:316write("<div class=\"item\">")317write("<h1>{}</h1>".format(name))318write("<table width=\"100%\">")319first_fail = False320321imguri1 = Path(path1).as_uri()322imguri2 = Path(path2).as_uri()323write("<tr><td colspan=\"2\">Frame %d</td></tr>" % (framenum))324write("<tr><td><img class=\"before\" src=\"%s\" /></td><td><img class=\"after\" src=\"%s\" /></td></tr>" % (imguri1, imguri2))325326if not first_fail:327write("</table>")328write("<pre>Difference in frames [%s] for %s</pre>" % (",".join(map(lambda x: str(x[0]), diff_frames)), name))329write("</div>")330print("*** Difference in frames [%s] for %s" % (",".join(map(lambda x: str(x[0]), diff_frames)), name))331#assert has_any332333return len(diff_frames) == 0334335336def check_regression_tests(baselinedir, testdir):337gamedirs = glob.glob(baselinedir + "/*", recursive=False)338gamedirs.sort(key=lambda x: os.path.basename(x))339340success = 0341failure = 0342343for gamedir in gamedirs:344name = Path(gamedir).name345if check_regression_test(baselinedir, testdir, name):346success += 1347else:348failure += 1349350return (failure == 0)351352353if __name__ == "__main__":354parser = argparse.ArgumentParser(description="Check frame dump images for regression tests")355parser.add_argument("-baselinedir", action="store", required=True, help="Directory containing baseline frames to check against")356parser.add_argument("-testdir", action="store", required=True, help="Directory containing frames to check")357parser.add_argument("-maxframes", type=int, action="store", required=False, default=9999, help="Max frames to compare")358parser.add_argument("outfile", action="store", help="The file to write the output to")359360args = parser.parse_args()361MAX_DIFF_FRAMES = args.maxframes362363outfile = open(args.outfile, "w")364write(FILE_HEADER)365366if not check_regression_tests(os.path.realpath(args.baselinedir), os.path.realpath(args.testdir)):367write(FILE_FOOTER)368outfile.close()369sys.exit(1)370else:371outfile.close()372os.remove(args.outfile)373sys.exit(0)374375376377