forked from bartvdbraak/blender
e33e58bf23
Works similar to regular Cycles tests, just does OpenGL render to get output image. Seems to work fine with the only funny effect: Blender window will pop up for each of the tests. This is current limitation of our OpenGL context. Might be changed in the future.
263 lines
7.4 KiB
Python
Executable File
263 lines
7.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Apache License, Version 2.0
|
|
|
|
import argparse
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
import tempfile
|
|
|
|
|
|
class COLORS_ANSI:
|
|
RED = '\033[00;31m'
|
|
GREEN = '\033[00;32m'
|
|
ENDC = '\033[0m'
|
|
|
|
|
|
class COLORS_DUMMY:
|
|
RED = ''
|
|
GREEN = ''
|
|
ENDC = ''
|
|
|
|
COLORS = COLORS_DUMMY
|
|
|
|
|
|
def printMessage(type, status, message):
|
|
if type == 'SUCCESS':
|
|
print(COLORS.GREEN, end="")
|
|
elif type == 'FAILURE':
|
|
print(COLORS.RED, end="")
|
|
status_text = ...
|
|
if status == 'RUN':
|
|
status_text = " RUN "
|
|
elif status == 'OK':
|
|
status_text = " OK "
|
|
elif status == 'PASSED':
|
|
status_text = " PASSED "
|
|
elif status == 'FAILED':
|
|
status_text = " FAILED "
|
|
else:
|
|
status_text = status
|
|
print("[{}]" . format(status_text), end="")
|
|
print(COLORS.ENDC, end="")
|
|
print(" {}" . format(message))
|
|
sys.stdout.flush()
|
|
|
|
|
|
def render_file(filepath):
|
|
dirname = os.path.dirname(filepath)
|
|
basedir = os.path.dirname(dirname)
|
|
subject = os.path.basename(dirname)
|
|
if subject == 'opengl':
|
|
command = (
|
|
BLENDER,
|
|
"--window-geometry", "0", "0", "1", "1",
|
|
"-noaudio",
|
|
"--factory-startup",
|
|
"--enable-autoexec",
|
|
filepath,
|
|
"-E", "CYCLES",
|
|
# Run with OSL enabled
|
|
# "--python-expr", "import bpy; bpy.context.scene.cycles.shading_system = True",
|
|
"-o", TEMP_FILE_MASK,
|
|
"-F", "PNG",
|
|
'--python', os.path.join(basedir,
|
|
"util",
|
|
"render_opengl.py")
|
|
)
|
|
else:
|
|
command = (
|
|
BLENDER,
|
|
"--background",
|
|
"-noaudio",
|
|
"--factory-startup",
|
|
"--enable-autoexec",
|
|
filepath,
|
|
"-E", "CYCLES",
|
|
# Run with OSL enabled
|
|
# "--python-expr", "import bpy; bpy.context.scene.cycles.shading_system = True",
|
|
"-o", TEMP_FILE_MASK,
|
|
"-F", "PNG",
|
|
"-f", "1",
|
|
)
|
|
try:
|
|
output = subprocess.check_output(command)
|
|
if VERBOSE:
|
|
print(output.decode("utf-8"))
|
|
return None
|
|
except subprocess.CalledProcessError as e:
|
|
if os.path.exists(TEMP_FILE):
|
|
os.remove(TEMP_FILE)
|
|
if VERBOSE:
|
|
print(e.output.decode("utf-8"))
|
|
if b"Error: engine not found" in e.output:
|
|
return "NO_CYCLES"
|
|
elif b"blender probably wont start" in e.output:
|
|
return "NO_START"
|
|
return "CRASH"
|
|
except BaseException as e:
|
|
if os.path.exists(TEMP_FILE):
|
|
os.remove(TEMP_FILE)
|
|
if VERBOSE:
|
|
print(e)
|
|
return "CRASH"
|
|
|
|
|
|
def test_get_name(filepath):
|
|
filename = os.path.basename(filepath)
|
|
return os.path.splitext(filename)[0]
|
|
|
|
|
|
def verify_output(filepath):
|
|
testname = test_get_name(filepath)
|
|
dirpath = os.path.dirname(filepath)
|
|
reference_dirpath = os.path.join(dirpath, "reference_renders")
|
|
reference_image = os.path.join(reference_dirpath, testname + ".png")
|
|
failed_image = os.path.join(reference_dirpath, testname + ".fail.png")
|
|
if not os.path.exists(reference_image):
|
|
return False
|
|
command = (
|
|
IDIFF,
|
|
"-fail", "0.015",
|
|
"-failpercent", "1",
|
|
reference_image,
|
|
TEMP_FILE,
|
|
)
|
|
try:
|
|
subprocess.check_output(command)
|
|
failed = False
|
|
except subprocess.CalledProcessError as e:
|
|
if VERBOSE:
|
|
print(e.output.decode("utf-8"))
|
|
failed = e.returncode != 1
|
|
if failed:
|
|
shutil.copy(TEMP_FILE, failed_image)
|
|
elif os.path.exists(failed_image):
|
|
os.remove(failed_image)
|
|
return not failed
|
|
|
|
|
|
def run_test(filepath):
|
|
testname = test_get_name(filepath)
|
|
spacer = "." * (32 - len(testname))
|
|
printMessage('SUCCESS', 'RUN', testname)
|
|
time_start = time.time()
|
|
error = render_file(filepath)
|
|
status = "FAIL"
|
|
if not error:
|
|
if not verify_output(filepath):
|
|
error = "VERIFY"
|
|
time_end = time.time()
|
|
elapsed_ms = int((time_end - time_start) * 1000)
|
|
if not error:
|
|
printMessage('SUCCESS', 'OK', "{} ({} ms)" .
|
|
format(testname, elapsed_ms))
|
|
else:
|
|
if error == "NO_CYCLES":
|
|
print("Can't perform tests because Cycles failed to load!")
|
|
return False
|
|
elif error == "NO_START":
|
|
print('Can not perform tests because blender fails to start.',
|
|
'Make sure INSTALL target was run.')
|
|
return False
|
|
elif error == 'VERIFY':
|
|
print("Rendered result is different from reference image")
|
|
else:
|
|
print("Unknown error %r" % error)
|
|
printMessage('FAILURE', 'FAILED', "{} ({} ms)" .
|
|
format(testname, elapsed_ms))
|
|
return error
|
|
|
|
|
|
def blend_list(path):
|
|
for dirpath, dirnames, filenames in os.walk(path):
|
|
for filename in filenames:
|
|
if filename.lower().endswith(".blend"):
|
|
filepath = os.path.join(dirpath, filename)
|
|
yield filepath
|
|
|
|
|
|
def run_all_tests(dirpath):
|
|
passed_tests = []
|
|
failed_tests = []
|
|
all_files = list(blend_list(dirpath))
|
|
all_files.sort()
|
|
printMessage('SUCCESS', "==========",
|
|
"Running {} tests from 1 test case." . format(len(all_files)))
|
|
time_start = time.time()
|
|
for filepath in all_files:
|
|
error = run_test(filepath)
|
|
testname = test_get_name(filepath)
|
|
if error:
|
|
if error == "NO_CYCLES":
|
|
return False
|
|
elif error == "NO_START":
|
|
return False
|
|
failed_tests.append(testname)
|
|
else:
|
|
passed_tests.append(testname)
|
|
time_end = time.time()
|
|
elapsed_ms = int((time_end - time_start) * 1000)
|
|
print("")
|
|
printMessage('SUCCESS', "==========",
|
|
"{} tests from 1 test case ran. ({} ms total)" .
|
|
format(len(all_files), elapsed_ms))
|
|
printMessage('SUCCESS', 'PASSED', "{} tests." .
|
|
format(len(passed_tests)))
|
|
if failed_tests:
|
|
printMessage('FAILURE', 'FAILED', "{} tests, listed below:" .
|
|
format(len(failed_tests)))
|
|
failed_tests.sort()
|
|
for test in failed_tests:
|
|
printMessage('FAILURE', "FAILED", "{}" . format(test))
|
|
return False
|
|
return True
|
|
|
|
|
|
def create_argparse():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("-blender", nargs="+")
|
|
parser.add_argument("-testdir", nargs=1)
|
|
parser.add_argument("-idiff", nargs=1)
|
|
return parser
|
|
|
|
|
|
def main():
|
|
parser = create_argparse()
|
|
args = parser.parse_args()
|
|
|
|
global COLORS
|
|
global BLENDER, ROOT, IDIFF
|
|
global TEMP_FILE, TEMP_FILE_MASK, TEST_SCRIPT
|
|
global VERBOSE
|
|
|
|
if os.environ.get("CYCLESTEST_COLOR") is not None:
|
|
COLORS = COLORS_ANSI
|
|
|
|
BLENDER = args.blender[0]
|
|
ROOT = args.testdir[0]
|
|
IDIFF = args.idiff[0]
|
|
|
|
TEMP = tempfile.mkdtemp()
|
|
TEMP_FILE_MASK = os.path.join(TEMP, "test")
|
|
TEMP_FILE = TEMP_FILE_MASK + "0001.png"
|
|
|
|
TEST_SCRIPT = os.path.join(os.path.dirname(__file__), "runtime_check.py")
|
|
|
|
VERBOSE = os.environ.get("BLENDER_VERBOSE") is not None
|
|
|
|
ok = run_all_tests(ROOT)
|
|
|
|
# Cleanup temp files and folders
|
|
if os.path.exists(TEMP_FILE):
|
|
os.remove(TEMP_FILE)
|
|
os.rmdir(TEMP)
|
|
|
|
sys.exit(not ok)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|