check_regression_tests.py (9523B)
1 import argparse 2 import glob 3 import sys 4 import os 5 import re 6 import hashlib 7 8 from pathlib import Path 9 10 FILE_HEADER = """ 11 <!DOCTYPE html> 12 <html> 13 <head> 14 <title>Comparison</title> 15 <style> 16 .modal { 17 display: none; 18 position: fixed; 19 z-index: 1; 20 padding-top: 100px; 21 left: 0; 22 top: 0; 23 width: 100%; 24 height: 100%; 25 overflow: auto; 26 background-color: black; 27 } 28 29 .modal-content { 30 position: relative; 31 margin: auto; 32 padding: 0; 33 width: 90%; 34 max-width: 1200px; 35 } 36 37 .close { 38 color: white; 39 position: absolute; 40 top: 10px; 41 right: 25px; 42 font-size: 35px; 43 font-weight: bold; 44 } 45 46 .close:hover, 47 .close:focus { 48 color: #999; 49 text-decoration: none; 50 cursor: pointer; 51 } 52 53 .prev, 54 .next { 55 cursor: pointer; 56 position: absolute; 57 top: 50%; 58 width: auto; 59 padding: 16px; 60 margin-top: -50px; 61 color: white; 62 font-weight: bold; 63 font-size: 20px; 64 transition: 0.6s ease; 65 border-radius: 0 3px 3px 0; 66 user-select: none; 67 -webkit-user-select: none; 68 } 69 70 .next { 71 right: 0; 72 border-radius: 3px 0 0 3px; 73 } 74 75 .prev:hover, 76 .next:hover { 77 background-color: rgba(0, 0, 0, 0.8); 78 } 79 80 .item img { 81 cursor: pointer; 82 } 83 84 #compareTitle { 85 color: white; 86 font-size: 20px; 87 font-family: sans-serif; 88 margin-bottom: 10px; 89 } 90 91 #compareCaption { 92 color: white; 93 font-family: sans-serif; 94 } 95 96 #compareState { 97 display: block; 98 position: absolute; 99 right: 0; 100 top: 0; 101 color: red; 102 font-family: sans-serif; 103 font-size: 20px; 104 font-weight: bold; 105 } 106 107 #compareImage { 108 display: block; 109 margin: 0 auto; 110 width: 100%; 111 height: auto; 112 } 113 </style> 114 </head> 115 <body> 116 """ 117 118 FILE_FOOTER = """ 119 <div id="myModal" class="modal" tabindex="0"> 120 <span class="close cursor" onclick="closeModal()">×</span> 121 <div class="modal-content"> 122 <div id="compareTitle"></div> 123 <img id="compareImage" /> 124 <div id="compareState"></div> 125 <a class="prev">❮</a> 126 <a class="next">❯</a> 127 <div id="compareCaption"></div> 128 </div> 129 </div> 130 <script> 131 /* Worst script known to man */ 132 /* Sources: 133 https://www.w3schools.com/howto/howto_js_lightbox.asp 134 https://css-tricks.com/prevent-page-scrolling-when-a-modal-is-open/ 135 */ 136 137 function openModal() { 138 document.body.style.position = 'fixed'; 139 document.body.style.top = `-${window.scrollY}px`; 140 document.getElementById("myModal").style.display = "block"; 141 document.getElementById("myModal").focus(); 142 setImageIndex(0); 143 } 144 145 function closeModal() { 146 document.getElementById("myModal").style.display = "none"; 147 const scrollY = document.body.style.top; 148 document.body.style.position = ''; 149 document.body.style.top = ''; 150 window.scrollTo(0, parseInt(scrollY || '0') * -1); 151 } 152 153 function isModalOpen() { 154 return (document.getElementById("myModal").style.display == "block"); 155 } 156 157 function formatLines(str) { 158 let lines = str.split("\\n") 159 lines = lines.filter(line => !line.startsWith("Difference in frames")) 160 return lines.join("<br>") 161 } 162 163 function extractItem(elem) { 164 var beforeSel = elem.querySelector(".before"); 165 var afterSel = elem.querySelector(".after"); 166 var preSel = elem.querySelector("pre"); 167 return { 168 name: elem.querySelector("h1").innerText, 169 beforeImg: beforeSel ? beforeSel.getAttribute("src") : null, 170 afterImg: afterSel ? afterSel.getAttribute("src") : null, 171 details: formatLines(preSel ? preSel.innerText : "") 172 }; 173 } 174 175 const items = [...document.querySelectorAll(".item")].map(extractItem) 176 let currentImage = 0; 177 let currentState = 0; 178 179 function getImageIndexForUri(uri) { 180 for (let i = 0; i < items.length; i++) { 181 if (items[i].beforeImg == uri || items[i].afterImg == uri) 182 return i; 183 } 184 return -1; 185 } 186 187 function setImageState(state) { 188 const item = items[currentImage] 189 const uri = (state === 0) ? item.beforeImg : item.afterImg; 190 const stateText = (state === 0) ? "BEFORE" : "AFTER"; 191 const posText = "(" + (currentImage + 1).toString() + "/" + (items.length).toString() + ") "; 192 document.getElementById("compareImage").setAttribute("src", uri); 193 document.getElementById("compareState").innerText = stateText; 194 document.getElementById("compareTitle").innerText = posText + item.name; 195 document.getElementById("compareCaption").innerHTML = item.details; 196 currentState = state; 197 } 198 199 function setImageIndex(index) { 200 if (index < 0 || index > items.length) 201 return; 202 203 currentImage = index; 204 setImageState(0); 205 } 206 207 function handleKey(key) { 208 if (key == " ") { 209 setImageState((currentState === 0) ? 1 : 0); 210 return true; 211 } else if (key == "ArrowLeft") { 212 setImageIndex(currentImage - 1); 213 return true; 214 } else if (key == "ArrowRight") { 215 setImageIndex(currentImage + 1); 216 return true; 217 } else if (key == "Escape") { 218 closeModal(); 219 return true; 220 } else { 221 console.log(key); 222 return false; 223 } 224 } 225 226 document.getElementById("myModal").addEventListener("keydown", function(ev) { 227 if (ev.defaultPrevented) 228 return; 229 230 if (handleKey(ev.key)) 231 ev.preventDefault(); 232 }); 233 234 document.querySelector("#myModal .prev").addEventListener("click", function() { 235 setImageIndex(currentImage - 1); 236 }); 237 document.querySelector("#myModal .next").addEventListener("click", function() { 238 setImageIndex(currentImage + 1); 239 }); 240 document.querySelectorAll(".item img").forEach(elem => elem.addEventListener("click", function() { 241 if (!isModalOpen()) 242 openModal(); 243 setImageIndex(getImageIndexForUri(this.getAttribute("src"))); 244 })); 245 </script> 246 </body> 247 </html> 248 """ 249 250 MAX_DIFF_FRAMES = 9999 251 252 outfile = None 253 def write(line): 254 outfile.write(line + "\n") 255 256 257 def compare_frames(path1, path2): 258 try: 259 with open(path1, "rb") as f: 260 hash1 = hashlib.md5(f.read()).digest() 261 with open(path2, "rb") as f: 262 hash2 = hashlib.md5(f.read()).digest() 263 264 #print(hash1, hash2) 265 return hash1 == hash2 266 except: 267 return False 268 269 270 def check_regression_test(baselinedir, testdir, name): 271 #print("Checking '%s'..." % name) 272 273 dir1 = os.path.join(baselinedir, name) 274 dir2 = os.path.join(testdir, name) 275 if not os.path.isdir(dir2): 276 #print("*** %s is missing in test set" % name) 277 return False 278 279 images = glob.glob(os.path.join(dir1, "frame_*.png")) 280 diff_frames = [] 281 first_fail = True 282 has_any = False 283 284 for imagepath in images: 285 imagename = Path(imagepath).name 286 matches = re.match("frame_([0-9]+).png", imagename) 287 if matches is None: 288 continue 289 290 framenum = int(matches[1]) 291 292 path1 = os.path.join(dir1, imagename) 293 path2 = os.path.join(dir2, imagename) 294 if not os.path.isfile(path2): 295 print("--- Frame %u for %s is missing in test set" % (framenum, name)) 296 if first_fail: 297 write("<div class=\"item\">") 298 write("<h1>{}</h1>".format(name)) 299 write("<table width=\"100%\">") 300 first_fail = False 301 write("<p>--- Frame %u for %s is missing in test set</p>" % (framenum, name)) 302 continue 303 304 has_any = True 305 if not compare_frames(path1, path2): 306 diff_frames.append(framenum) 307 308 if first_fail: 309 write("<div class=\"item\">") 310 write("<h1>{}</h1>".format(name)) 311 write("<table width=\"100%\">") 312 first_fail = False 313 314 imguri1 = Path(path1).as_uri() 315 imguri2 = Path(path2).as_uri() 316 write("<tr><td colspan=\"2\">Frame %d</td></tr>" % (framenum)) 317 write("<tr><td><img class=\"before\" src=\"%s\" /></td><td><img class=\"after\" src=\"%s\" /></td></tr>" % (imguri1, imguri2)) 318 319 if len(diff_frames) == MAX_DIFF_FRAMES: 320 break 321 322 if not first_fail: 323 write("</table>") 324 write("<pre>Difference in frames [%s] for %s</pre>" % (",".join(map(str, diff_frames)), name)) 325 write("</div>") 326 print("*** Difference in frames [%s] for %s" % (",".join(map(str, diff_frames)), name)) 327 #assert has_any 328 329 return len(diff_frames) == 0 330 331 332 def check_regression_tests(baselinedir, testdir): 333 gamedirs = glob.glob(baselinedir + "/*", recursive=False) 334 335 success = 0 336 failure = 0 337 338 for gamedir in gamedirs: 339 name = Path(gamedir).name 340 if check_regression_test(baselinedir, testdir, name): 341 success += 1 342 else: 343 failure += 1 344 345 return (failure == 0) 346 347 348 if __name__ == "__main__": 349 parser = argparse.ArgumentParser(description="Check frame dump images for regression tests") 350 parser.add_argument("-baselinedir", action="store", required=True, help="Directory containing baseline frames to check against") 351 parser.add_argument("-testdir", action="store", required=True, help="Directory containing frames to check") 352 parser.add_argument("-maxframes", type=int, action="store", required=False, default=9999, help="Max frames to compare") 353 parser.add_argument("outfile", action="store", help="The file to write the output to") 354 355 args = parser.parse_args() 356 MAX_DIFF_FRAMES = args.maxframes 357 358 outfile = open(args.outfile, "w") 359 write(FILE_HEADER) 360 361 if not check_regression_tests(os.path.realpath(args.baselinedir), os.path.realpath(args.testdir)): 362 write(FILE_FOOTER) 363 outfile.close() 364 sys.exit(1) 365 else: 366 outfile.close() 367 os.remove(args.outfile) 368 sys.exit(0) 369