mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-12 13:00:32 +00:00
a9151a8c0b
* Built entirely independently of the main renderdoc build (for now). * Contains python scripts & framework for running tests on a renderdoc build, which will be run nightly.
219 lines
5.8 KiB
Python
219 lines
5.8 KiB
Python
import sys
|
|
import os
|
|
import re
|
|
import time
|
|
import hashlib
|
|
import zipfile
|
|
from PIL import Image, ImageChops, ImageStat
|
|
|
|
|
|
def _timestr():
|
|
return time.strftime("%Y%m%d_%H_%M_%S", time.gmtime()) + "_" + str(round(time.time() % 1000))
|
|
|
|
|
|
# Thanks to https://stackoverflow.com/a/3431838 for this file definition
|
|
def _md5_file(fname):
|
|
hash_md5 = hashlib.md5()
|
|
with open(fname, "rb") as f:
|
|
for chunk in iter(lambda: f.read(4096), b""):
|
|
hash_md5.update(chunk)
|
|
return hash_md5.hexdigest()
|
|
|
|
|
|
_root_dir = os.getcwd()
|
|
_artifact_dir = os.path.realpath('artifacts')
|
|
_data_dir = os.path.realpath('data')
|
|
_data_extra_dir = os.path.realpath('data_extra')
|
|
_temp_dir = os.path.realpath('tmp')
|
|
_test_name = 'Unknown_Test'
|
|
|
|
|
|
def set_root_dir(path: str):
|
|
global _root_dir
|
|
_root_dir = path
|
|
|
|
|
|
def set_data_dir(path: str):
|
|
global _data_dir
|
|
_data_dir = os.path.abspath(path)
|
|
|
|
|
|
def set_data_extra_dir(path: str):
|
|
global _data_extra_dir
|
|
_data_extra_dir = os.path.abspath(path)
|
|
|
|
|
|
def set_artifact_dir(path: str):
|
|
global _artifact_dir
|
|
_artifact_dir = os.path.abspath(path)
|
|
|
|
|
|
def set_temp_dir(path: str):
|
|
global _temp_dir
|
|
_temp_dir = os.path.abspath(path)
|
|
|
|
|
|
def set_current_test(name: str):
|
|
global _test_name
|
|
_test_name = name
|
|
|
|
|
|
def get_root_dir():
|
|
return _root_dir
|
|
|
|
|
|
def get_data_dir():
|
|
return _data_dir
|
|
|
|
|
|
def get_data_path(name: str):
|
|
return os.path.join(_data_dir, name)
|
|
|
|
|
|
def get_data_extra_dir():
|
|
return _data_extra_dir
|
|
|
|
|
|
def get_data_extra_path(name: str):
|
|
return os.path.join(_data_extra_dir, name)
|
|
|
|
|
|
def get_artifact_dir():
|
|
return _artifact_dir
|
|
|
|
|
|
def get_artifact_path(name: str):
|
|
return os.path.join(_artifact_dir, name)
|
|
|
|
|
|
def get_tmp_dir():
|
|
return _temp_dir
|
|
|
|
|
|
def get_tmp_path(name: str):
|
|
os.makedirs(os.path.join(_temp_dir, _test_name), exist_ok=True)
|
|
return os.path.join(_temp_dir, _test_name, name)
|
|
|
|
|
|
def sanitise_filename(name: str):
|
|
name = name.replace(_artifact_dir, '') \
|
|
.replace(get_tmp_dir(), '') \
|
|
.replace(get_root_dir(), '') \
|
|
.replace('\\', '/')
|
|
|
|
return re.sub('^/', '', name)
|
|
|
|
|
|
def image_compare(test_img: str, ref_img: str, tolerance: int = 2):
|
|
try:
|
|
out = Image.open(test_img)
|
|
except Exception as ex:
|
|
raise FileNotFoundError("Can't open {}".format(sanitise_filename(test_img)))
|
|
|
|
try:
|
|
ref = Image.open(ref_img)
|
|
except Exception as ex:
|
|
raise FileNotFoundError("Can't open {}".format(sanitise_filename(ref_img)))
|
|
|
|
if out.mode != ref.mode or out.size != ref.size:
|
|
return False
|
|
|
|
# Generate the difference
|
|
diff = ImageChops.difference(out, ref)
|
|
|
|
# Subtract N from the difference, to allow for off-by-N errors that can be caused by rounding.
|
|
# For example clearing to 0.5, 0.5, 0.5 has two valid representations: 127,127,127 and 128,128,128
|
|
# which are equally far from true 0.5.
|
|
diff = ImageChops.subtract(diff, Image.new(diff.mode, (diff.width, diff.height),
|
|
(tolerance, tolerance, tolerance, tolerance)))
|
|
|
|
# If the diff fails, dump the difference to a file
|
|
diff_file = get_tmp_path('diff.png')
|
|
|
|
if os.path.exists(diff_file):
|
|
os.remove(diff_file)
|
|
|
|
if sum(ImageStat.Stat(diff).sum) > 0:
|
|
# this does (img1 + img2) / scale, so scale=0.5 means we multiply the image by 2/0.5 = 4
|
|
diff = ImageChops.add(diff, diff, scale=0.5)
|
|
diff.convert("RGB").save(diff_file)
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def md5_compare(test_file: str, ref_file: str):
|
|
return _md5_file(test_file) == _md5_file(ref_file)
|
|
|
|
|
|
def zip_compare(test_file: str, ref_file: str):
|
|
test = zipfile.ZipFile(test_file)
|
|
ref = zipfile.ZipFile(ref_file)
|
|
|
|
test_files = []
|
|
for file in test.infolist():
|
|
hash_md5 = hashlib.md5()
|
|
with test.open(file.filename) as f:
|
|
for chunk in iter(lambda: f.read(4096), b""):
|
|
hash_md5.update(chunk)
|
|
test_files.append((file.filename, file.file_size, hash_md5.hexdigest()))
|
|
|
|
ref_files = []
|
|
for file in ref.infolist():
|
|
hash_md5 = hashlib.md5()
|
|
with test.open(file.filename) as f:
|
|
for chunk in iter(lambda: f.read(4096), b""):
|
|
hash_md5.update(chunk)
|
|
ref_files.append((file.filename, file.file_size, hash_md5.hexdigest()))
|
|
|
|
test.close()
|
|
ref.close()
|
|
|
|
return test_files == ref_files
|
|
|
|
|
|
# Use the 32-bit float epsilon, not sys.float_info.epsilon which is for double floats
|
|
FLT_EPSILON = 2.0*1.19209290E-07
|
|
|
|
|
|
def value_compare(ref, data):
|
|
if type(ref) == float:
|
|
if type(data) != float:
|
|
return False
|
|
|
|
# Floats are equal if the absolute difference is less than epsilon times the largest.
|
|
largest = max(abs(ref), abs(data))
|
|
eps = largest * FLT_EPSILON if largest > 1.0 else FLT_EPSILON
|
|
return abs(ref-data) < eps
|
|
elif type(ref) == list or type(ref) == tuple:
|
|
# tuples and lists can be treated interchangeably
|
|
if type(data) != list and type(data) != tuple:
|
|
return False
|
|
|
|
# Lists are equal if they have the same length and all members have value_compare(i, j) == True
|
|
if len(ref) != len(data):
|
|
return False
|
|
|
|
for i in range(len(ref)):
|
|
if not value_compare(ref[i], data[i]):
|
|
return False
|
|
|
|
return True
|
|
elif type(ref) == dict:
|
|
if type(data) != dict:
|
|
return False
|
|
|
|
# Similarly, dicts are equal if both have the same set of keys and
|
|
# corresponding values are value_compare(i, j) == True
|
|
if ref.keys() != data.keys():
|
|
return False
|
|
|
|
for i in ref.keys():
|
|
if not value_compare(ref[i], data[i]):
|
|
return False
|
|
|
|
return True
|
|
else:
|
|
# For other types, just use normal comparison
|
|
return ref == data
|