From 0f3f093d3b3d1b2fb9593233f23665d3135bea2d Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Thu, 3 Aug 2017 16:41:50 +0200 Subject: Cycles: add HTML report to inspect failed test images. Shows new, reference and diff renders, with mouse hover to flip between new and ref for easy comparison. This generates a report.html in build_dir/tests/cycles, stored along with the new and diff images. Differential Revision: https://developer.blender.org/D2770 --- tests/python/cycles_render_tests.py | 227 +++++++++++++++++++++++++++++------- 1 file changed, 182 insertions(+), 45 deletions(-) (limited to 'tests/python/cycles_render_tests.py') diff --git a/tests/python/cycles_render_tests.py b/tests/python/cycles_render_tests.py index a030cc5e0de..ea84f27ab7e 100755 --- a/tests/python/cycles_render_tests.py +++ b/tests/python/cycles_render_tests.py @@ -2,7 +2,9 @@ # Apache License, Version 2.0 import argparse +import glob import os +import pathlib import shutil import subprocess import sys @@ -24,7 +26,7 @@ class COLORS_DUMMY: COLORS = COLORS_DUMMY -def printMessage(type, status, message): +def print_message(message, type=None, status=''): if type == 'SUCCESS': print(COLORS.GREEN, end="") elif type == 'FAILURE': @@ -109,20 +111,126 @@ def test_get_name(filepath): filename = os.path.basename(filepath) return os.path.splitext(filename)[0] - -def verify_output(filepath): +def test_get_images(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): + ref_dirpath = os.path.join(dirpath, "reference_renders") + ref_img = os.path.join(ref_dirpath, testname + ".png") + new_dirpath = os.path.join(OUTDIR, os.path.basename(dirpath)) + if not os.path.exists(new_dirpath): + os.makedirs(new_dirpath) + new_img = os.path.join(new_dirpath, testname + ".png") + diff_dirpath = os.path.join(OUTDIR, os.path.basename(dirpath), "diff") + if not os.path.exists(diff_dirpath): + os.makedirs(diff_dirpath) + diff_img = os.path.join(diff_dirpath, testname + ".diff.png") + return ref_img, new_img, diff_img + + +class Report: + def __init__(self, testname): + self.failed_tests = "" + self.passed_tests = "" + self.testname = testname + + def output(self): + # write intermediate data for single test + outdir = os.path.join(OUTDIR, self.testname) + f = open(os.path.join(outdir, "failed.data"), "w") + f.write(self.failed_tests) + f.close() + + f = open(os.path.join(outdir, "passed.data"), "w") + f.write(self.passed_tests) + f.close() + + # gather intermediate data for all tests + failed_data = sorted(glob.glob(os.path.join(OUTDIR, "*/failed.data"))) + passed_data = sorted(glob.glob(os.path.join(OUTDIR, "*/passed.data"))) + + failed_tests = "" + passed_tests = "" + + for filename in failed_data: + failed_tests += open(os.path.join(OUTDIR, filename), "r").read() + for filename in passed_data: + passed_tests += open(os.path.join(OUTDIR, filename), "r").read() + + # write html for all tests + self.html = """ + + + Cycles Test Report + + + + +
+
+

Cycles Test Report

+
+ + + + + {}{} +
NameNewReferenceDiff
+
+
+ + + """ . format(failed_tests, passed_tests) + + filepath = os.path.join(OUTDIR, "report.html") + f = open(filepath, "w") + f.write(self.html) + f.close() + + print_message("Report saved to: " + pathlib.Path(filepath).as_uri()) + + def add_test(self, filepath, error): + name = test_get_name(filepath) + + ref_img, new_img, diff_img = test_get_images(filepath) + + status = error if error else "" + style = """ style="background-color: #f99;" """ if error else "" + + new_url = pathlib.Path(new_img).as_uri() + ref_url = pathlib.Path(ref_img).as_uri() + diff_url = pathlib.Path(diff_img).as_uri() + + test_html = """ + + {}
{}
{} + + + + """ . format(style, name, self.testname, status, + new_url, ref_url, new_url, + ref_url, new_url, ref_url, + diff_url) + + if error: + self.failed_tests += test_html + else: + self.passed_tests += test_html + + +def verify_output(report, filepath): + ref_img, new_img, diff_img = test_get_images(filepath) + if not os.path.exists(ref_img): return False + + # diff test with threshold command = ( IDIFF, - "-fail", "0.015", + "-fail", "0.016", "-failpercent", "1", - reference_image, + ref_img, TEMP_FILE, ) try: @@ -130,47 +238,66 @@ def verify_output(filepath): failed = False except subprocess.CalledProcessError as e: if VERBOSE: - print(e.output.decode("utf-8")) + print_message(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) + + # generate diff image + command = ( + IDIFF, + "-o", diff_img, + "-abs", "-scale", "16", + ref_img, + TEMP_FILE + ) + + try: + subprocess.check_output(command) + except subprocess.CalledProcessError as e: + if VERBOSE: + print_message(e.output.decode("utf-8")) + + # copy new image + if os.path.exists(new_img): + os.remove(new_img) + if os.path.exists(TEMP_FILE): + shutil.copy(TEMP_FILE, new_img) + return not failed -def run_test(filepath): +def run_test(report, filepath): testname = test_get_name(filepath) spacer = "." * (32 - len(testname)) - printMessage('SUCCESS', 'RUN', testname) + print_message(testname, 'SUCCESS', 'RUN') time_start = time.time() error = render_file(filepath) status = "FAIL" if not error: - if not verify_output(filepath): + if not verify_output(report, 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)) + print_message("{} ({} ms)" . format(testname, elapsed_ms), + 'SUCCESS', 'OK') else: if error == "NO_CYCLES": - print("Can't perform tests because Cycles failed to load!") - return False + print_message("Can't perform tests because Cycles failed to load!") + return error elif error == "NO_START": - print('Can not perform tests because blender fails to start.', + print_message('Can not perform tests because blender fails to start.', 'Make sure INSTALL target was run.') - return False + return error elif error == 'VERIFY': - print("Rendered result is different from reference image") + print_message("Rendered result is different from reference image") else: - print("Unknown error %r" % error) - printMessage('FAILURE', 'FAILED', "{} ({} ms)" . - format(testname, elapsed_ms)) + print_message("Unknown error %r" % error) + print_message("{} ({} ms)" . format(testname, elapsed_ms), + 'FAILURE', 'FAILED') return error + def blend_list(path): for dirpath, dirnames, filenames in os.walk(path): for filename in filenames: @@ -178,17 +305,18 @@ def blend_list(path): 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))) + report = Report(os.path.basename(dirpath)) + print_message("Running {} tests from 1 test case." . + format(len(all_files)), + 'SUCCESS', "==========") time_start = time.time() for filepath in all_files: - error = run_test(filepath) + error = run_test(report, filepath) testname = test_get_name(filepath) if error: if error == "NO_CYCLES": @@ -198,28 +326,33 @@ def run_all_tests(dirpath): failed_tests.append(testname) else: passed_tests.append(testname) + report.add_test(filepath, error) 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))) + print_message("") + print_message("{} tests from 1 test case ran. ({} ms total)" . + format(len(all_files), elapsed_ms), + 'SUCCESS', "==========") + print_message("{} tests." . + format(len(passed_tests)), + 'SUCCESS', 'PASSED') if failed_tests: - printMessage('FAILURE', 'FAILED', "{} tests, listed below:" . - format(len(failed_tests))) + print_message("{} tests, listed below:" . + format(len(failed_tests)), + 'FAILURE', 'FAILED') failed_tests.sort() for test in failed_tests: - printMessage('FAILURE', "FAILED", "{}" . format(test)) - return False - return True + print_message("{}" . format(test), 'FAILURE', "FAILED") + + report.output() + return not bool(failed_tests) def create_argparse(): parser = argparse.ArgumentParser() parser.add_argument("-blender", nargs="+") parser.add_argument("-testdir", nargs=1) + parser.add_argument("-outdir", nargs=1) parser.add_argument("-idiff", nargs=1) return parser @@ -229,7 +362,7 @@ def main(): args = parser.parse_args() global COLORS - global BLENDER, ROOT, IDIFF + global BLENDER, TESTDIR, IDIFF, OUTDIR global TEMP_FILE, TEMP_FILE_MASK, TEST_SCRIPT global VERBOSE @@ -237,8 +370,12 @@ def main(): COLORS = COLORS_ANSI BLENDER = args.blender[0] - ROOT = args.testdir[0] + TESTDIR = args.testdir[0] IDIFF = args.idiff[0] + OUTDIR = args.outdir[0] + + if not os.path.exists(OUTDIR): + os.makedirs(OUTDIR) TEMP = tempfile.mkdtemp() TEMP_FILE_MASK = os.path.join(TEMP, "test") @@ -248,7 +385,7 @@ def main(): VERBOSE = os.environ.get("BLENDER_VERBOSE") is not None - ok = run_all_tests(ROOT) + ok = run_all_tests(TESTDIR) # Cleanup temp files and folders if os.path.exists(TEMP_FILE): -- cgit v1.2.3 From 977e7b68cb6e27d23d97485adad1eeb7ae5fe226 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 11 Aug 2017 00:38:39 +0200 Subject: Cycles: add denoising tests, keep new image even if no reference exists. --- tests/python/cycles_render_tests.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'tests/python/cycles_render_tests.py') diff --git a/tests/python/cycles_render_tests.py b/tests/python/cycles_render_tests.py index ea84f27ab7e..ffd8627dbf2 100755 --- a/tests/python/cycles_render_tests.py +++ b/tests/python/cycles_render_tests.py @@ -222,6 +222,14 @@ class Report: def verify_output(report, filepath): ref_img, new_img, diff_img = test_get_images(filepath) + + # copy new image + if os.path.exists(new_img): + os.remove(new_img) + if os.path.exists(TEMP_FILE): + shutil.copy(TEMP_FILE, new_img) + + if not os.path.exists(ref_img): return False @@ -256,12 +264,6 @@ def verify_output(report, filepath): if VERBOSE: print_message(e.output.decode("utf-8")) - # copy new image - if os.path.exists(new_img): - os.remove(new_img) - if os.path.exists(TEMP_FILE): - shutil.copy(TEMP_FILE, new_img) - return not failed -- cgit v1.2.3 From 596ee4b50559eddcc16f01a8fa76e692aac157c1 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Fri, 11 Aug 2017 09:34:34 +0200 Subject: Cycles tests: Draw images on top of checkerboard This way it's easier to see alpha-channel only images, such as shadow catcher images on transparent film. --- tests/python/cycles_render_tests.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) (limited to 'tests/python/cycles_render_tests.py') diff --git a/tests/python/cycles_render_tests.py b/tests/python/cycles_render_tests.py index ffd8627dbf2..0b90ab5b55f 100755 --- a/tests/python/cycles_render_tests.py +++ b/tests/python/cycles_render_tests.py @@ -163,6 +163,25 @@ class Report: Cycles Test Report @@ -206,8 +225,8 @@ class Report: test_html = """ {}
{}
{} - - + + """ . format(style, name, self.testname, status, new_url, ref_url, new_url, -- cgit v1.2.3 From 5cf36c0f05c9681dbc4d6344686d72ca8fe969b3 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 18 Aug 2017 00:37:45 +0200 Subject: Cycles tests: make page less wide, use relative URLs for serving through http. --- tests/python/cycles_render_tests.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) (limited to 'tests/python/cycles_render_tests.py') diff --git a/tests/python/cycles_render_tests.py b/tests/python/cycles_render_tests.py index 0b90ab5b55f..77bee4953ba 100755 --- a/tests/python/cycles_render_tests.py +++ b/tests/python/cycles_render_tests.py @@ -114,16 +114,27 @@ def test_get_name(filepath): def test_get_images(filepath): testname = test_get_name(filepath) dirpath = os.path.dirname(filepath) - ref_dirpath = os.path.join(dirpath, "reference_renders") + + old_dirpath = os.path.join(dirpath, "reference_renders") + old_img = os.path.join(old_dirpath, testname + ".png") + + ref_dirpath = os.path.join(OUTDIR, os.path.basename(dirpath), "ref") ref_img = os.path.join(ref_dirpath, testname + ".png") + if not os.path.exists(ref_dirpath): + os.makedirs(ref_dirpath) + if os.path.exists(old_img): + shutil.copy(old_img, ref_img) + new_dirpath = os.path.join(OUTDIR, os.path.basename(dirpath)) if not os.path.exists(new_dirpath): os.makedirs(new_dirpath) new_img = os.path.join(new_dirpath, testname + ".png") + diff_dirpath = os.path.join(OUTDIR, os.path.basename(dirpath), "diff") if not os.path.exists(diff_dirpath): os.makedirs(diff_dirpath) diff_img = os.path.join(diff_dirpath, testname + ".diff.png") + return ref_img, new_img, diff_img @@ -162,7 +173,7 @@ class Report: Cycles Test Report @@ -210,17 +221,22 @@ class Report: print_message("Report saved to: " + pathlib.Path(filepath).as_uri()) + def relative_url(self, filepath): + relpath = os.path.relpath(filepath, OUTDIR) + return pathlib.Path(relpath).as_posix() + def add_test(self, filepath, error): name = test_get_name(filepath) + name = name.replace('_', ' ') ref_img, new_img, diff_img = test_get_images(filepath) status = error if error else "" style = """ style="background-color: #f99;" """ if error else "" - new_url = pathlib.Path(new_img).as_uri() - ref_url = pathlib.Path(ref_img).as_uri() - diff_url = pathlib.Path(diff_img).as_uri() + new_url = self.relative_url(new_img) + ref_url = self.relative_url(ref_img) + diff_url = self.relative_url(diff_img) test_html = """ -- cgit v1.2.3 From 4218b9367e11559bdcc7a7b98625dd2984eef3d4 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Sat, 19 Aug 2017 12:09:28 +0200 Subject: Cycles tests: pass Blender custom arguments from CYCLESTEST_ARGS. This is useful for testing with different devices, split kernel, OSL, impact of integrator settings, etc. --- tests/python/cycles_render_tests.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) (limited to 'tests/python/cycles_render_tests.py') diff --git a/tests/python/cycles_render_tests.py b/tests/python/cycles_render_tests.py index 77bee4953ba..d4e796f35ae 100755 --- a/tests/python/cycles_render_tests.py +++ b/tests/python/cycles_render_tests.py @@ -5,6 +5,7 @@ import argparse import glob import os import pathlib +import shlex import shutil import subprocess import sys @@ -52,38 +53,44 @@ def render_file(filepath): dirname = os.path.dirname(filepath) basedir = os.path.dirname(dirname) subject = os.path.basename(dirname) + + custom_args = os.getenv('CYCLESTEST_ARGS') + custom_args = shlex.split(custom_args) if custom_args else [] + + # OSL and GPU examples + # custom_args += ["--python-expr", "import bpy; bpy.context.scene.cycles.shading_system = True"] + # custom_args += ["--python-expr", "import bpy; bpy.context.scene.cycles.device = 'GPU'"] + if subject == 'opengl': - command = ( + 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", + "-E", "CYCLES"] + command += custom_args + command += [ "-o", TEMP_FILE_MASK, "-F", "PNG", '--python', os.path.join(basedir, "util", - "render_opengl.py") - ) + "render_opengl.py")] else: - command = ( + 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", + "-E", "CYCLES"] + command += custom_args + command += [ "-o", TEMP_FILE_MASK, "-F", "PNG", - "-f", "1", - ) + "-f", "1"] try: output = subprocess.check_output(command) if VERBOSE: -- cgit v1.2.3 From 58d92cefbdf77c0e1b1882a02fabd6627a8f41ee Mon Sep 17 00:00:00 2001 From: Ray Molenkamp Date: Fri, 25 Aug 2017 17:17:49 -0600 Subject: [cycles/ctest] fix failing tests when output folder doesn't exist yet. --- tests/python/cycles_render_tests.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'tests/python/cycles_render_tests.py') diff --git a/tests/python/cycles_render_tests.py b/tests/python/cycles_render_tests.py index d4e796f35ae..ba4c04f7cf3 100755 --- a/tests/python/cycles_render_tests.py +++ b/tests/python/cycles_render_tests.py @@ -154,6 +154,9 @@ class Report: def output(self): # write intermediate data for single test outdir = os.path.join(OUTDIR, self.testname) + if not os.path.exists(outdir): + os.makedirs(outdir) + f = open(os.path.join(outdir, "failed.data"), "w") f.write(self.failed_tests) f.close() -- cgit v1.2.3 From 28532f18672b1a2a8d3190f6ec1e7ec861699d66 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Sun, 3 Sep 2017 00:15:14 +0200 Subject: Cycles tests: add environment variable to update references renders. This will copy new renders over references renders: CYCLESTEST_UPDATE=1 ctest -R cycles --- tests/python/cycles_render_tests.py | 48 ++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 19 deletions(-) (limited to 'tests/python/cycles_render_tests.py') diff --git a/tests/python/cycles_render_tests.py b/tests/python/cycles_render_tests.py index ba4c04f7cf3..2bdadced80f 100755 --- a/tests/python/cycles_render_tests.py +++ b/tests/python/cycles_render_tests.py @@ -142,7 +142,7 @@ def test_get_images(filepath): os.makedirs(diff_dirpath) diff_img = os.path.join(diff_dirpath, testname + ".diff.png") - return ref_img, new_img, diff_img + return old_img, ref_img, new_img, diff_img class Report: @@ -239,7 +239,7 @@ class Report: name = test_get_name(filepath) name = name.replace('_', ' ') - ref_img, new_img, diff_img = test_get_images(filepath) + old_img, ref_img, new_img, diff_img = test_get_images(filepath) status = error if error else "" style = """ style="background-color: #f99;" """ if error else "" @@ -266,7 +266,7 @@ class Report: def verify_output(report, filepath): - ref_img, new_img, diff_img = test_get_images(filepath) + old_img, ref_img, new_img, diff_img = test_get_images(filepath) # copy new image if os.path.exists(new_img): @@ -274,25 +274,35 @@ def verify_output(report, filepath): if os.path.exists(TEMP_FILE): shutil.copy(TEMP_FILE, new_img) + update = os.getenv('CYCLESTEST_UPDATE') + + if os.path.exists(ref_img): + # diff test with threshold + command = ( + IDIFF, + "-fail", "0.016", + "-failpercent", "1", + ref_img, + TEMP_FILE, + ) + try: + subprocess.check_output(command) + failed = False + except subprocess.CalledProcessError as e: + if VERBOSE: + print_message(e.output.decode("utf-8")) + failed = e.returncode != 1 + else: + if not update: + return False - if not os.path.exists(ref_img): - return False + failed = True - # diff test with threshold - command = ( - IDIFF, - "-fail", "0.016", - "-failpercent", "1", - ref_img, - TEMP_FILE, - ) - try: - subprocess.check_output(command) + if failed and update: + # update reference + shutil.copy(new_img, ref_img) + shutil.copy(new_img, old_img) failed = False - except subprocess.CalledProcessError as e: - if VERBOSE: - print_message(e.output.decode("utf-8")) - failed = e.returncode != 1 # generate diff image command = ( -- cgit v1.2.3 From e7b5bbae6a1594f6e21a5734cdc7e6e4e2e43eb6 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Tue, 12 Sep 2017 16:22:02 +0500 Subject: Cycles tests: Add baking features tests --- tests/python/cycles_render_tests.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'tests/python/cycles_render_tests.py') diff --git a/tests/python/cycles_render_tests.py b/tests/python/cycles_render_tests.py index 2bdadced80f..fde0b6bdcba 100755 --- a/tests/python/cycles_render_tests.py +++ b/tests/python/cycles_render_tests.py @@ -77,6 +77,22 @@ def render_file(filepath): '--python', os.path.join(basedir, "util", "render_opengl.py")] + elif subject == 'bake': + command = [ + BLENDER, + "-b", + "-noaudio", + "--factory-startup", + "--enable-autoexec", + filepath, + "-E", "CYCLES"] + command += custom_args + command += [ + "-o", TEMP_FILE_MASK, + "-F", "PNG", + '--python', os.path.join(basedir, + "util", + "render_bake.py")] else: command = [ BLENDER, -- cgit v1.2.3 From 8289b47e3a425207a2b1fb8e47b7002e90444ce2 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Wed, 20 Sep 2017 19:12:26 +0200 Subject: Fix Cycles test report not closing files properly. --- tests/python/cycles_render_tests.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) (limited to 'tests/python/cycles_render_tests.py') diff --git a/tests/python/cycles_render_tests.py b/tests/python/cycles_render_tests.py index fde0b6bdcba..2122150467c 100755 --- a/tests/python/cycles_render_tests.py +++ b/tests/python/cycles_render_tests.py @@ -173,13 +173,11 @@ class Report: if not os.path.exists(outdir): os.makedirs(outdir) - f = open(os.path.join(outdir, "failed.data"), "w") - f.write(self.failed_tests) - f.close() + filepath = os.path.join(outdir, "failed.data") + pathlib.Path(filepath).write_text(self.failed_tests) - f = open(os.path.join(outdir, "passed.data"), "w") - f.write(self.passed_tests) - f.close() + filepath = os.path.join(outdir, "passed.data") + pathlib.Path(filepath).write_text(self.passed_tests) # gather intermediate data for all tests failed_data = sorted(glob.glob(os.path.join(OUTDIR, "*/failed.data"))) @@ -189,9 +187,11 @@ class Report: passed_tests = "" for filename in failed_data: - failed_tests += open(os.path.join(OUTDIR, filename), "r").read() + filepath = os.path.join(OUTDIR, filename) + failed_tests += pathlib.Path(filepath).read_text() for filename in passed_data: - passed_tests += open(os.path.join(OUTDIR, filename), "r").read() + filepath = os.path.join(OUTDIR, filename) + passed_tests += pathlib.Path(filepath).read_text() # write html for all tests self.html = """ @@ -241,9 +241,7 @@ class Report: """ . format(failed_tests, passed_tests) filepath = os.path.join(OUTDIR, "report.html") - f = open(filepath, "w") - f.write(self.html) - f.close() + pathlib.Path(filepath).write_text(self.html) print_message("Report saved to: " + pathlib.Path(filepath).as_uri()) -- cgit v1.2.3