diff options
Diffstat (limited to 'libs/assimp/test/regression')
-rw-r--r-- | libs/assimp/test/regression/.project | 17 | ||||
-rw-r--r-- | libs/assimp/test/regression/.pydevproject | 7 | ||||
-rw-r--r-- | libs/assimp/test/regression/README.txt | 95 | ||||
-rw-r--r-- | libs/assimp/test/regression/ai_regression_ui.py | 293 | ||||
-rw-r--r-- | libs/assimp/test/regression/gen_db.py | 225 | ||||
-rw-r--r-- | libs/assimp/test/regression/result_checker.py | 113 | ||||
-rwxr-xr-x | libs/assimp/test/regression/run.py | 320 | ||||
-rw-r--r-- | libs/assimp/test/regression/settings.py | 134 | ||||
-rw-r--r-- | libs/assimp/test/regression/utils.py | 62 | ||||
-rw-r--r-- | libs/assimp/test/regression/whitelist.csv | 32 |
10 files changed, 1298 insertions, 0 deletions
diff --git a/libs/assimp/test/regression/.project b/libs/assimp/test/regression/.project new file mode 100644 index 0000000..04b40e5 --- /dev/null +++ b/libs/assimp/test/regression/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>assimp-regression</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.python.pydev.PyDevBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.python.pydev.pythonNature</nature> + </natures> +</projectDescription> diff --git a/libs/assimp/test/regression/.pydevproject b/libs/assimp/test/regression/.pydevproject new file mode 100644 index 0000000..841206b --- /dev/null +++ b/libs/assimp/test/regression/.pydevproject @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<?eclipse-pydev version="1.0"?> + +<pydev_project> +<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 3.0</pydev_property> +<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property> +</pydev_project> diff --git a/libs/assimp/test/regression/README.txt b/libs/assimp/test/regression/README.txt new file mode 100644 index 0000000..a37da92 --- /dev/null +++ b/libs/assimp/test/regression/README.txt @@ -0,0 +1,95 @@ +Asset Importer Lib Regression Test Suite +======================================== + +1) How does it work? +--------------------------------------------------------------------------------- +run.py checks all model in the <root>/test/models* folders and compares the result +against a regression database provided with assimp (db.zip). A few failures +are totally fine (see sections 7+). You need to worry if a huge +majority of all files in a particular format (or post-processing configuration) +fails as this might be a sign of a recent regression in assimp's codebase or +gross incompatibility with your system or compiler. + +2) What do I need? +--------------------------------------------------------------------------------- + - You need Python installed (2.7+, 3.x). On Windows, run the scripts using "py". + - You need to build the assimp command line tool (ASSIMP_BUILD_ASSIMP_TOOLS + CMake build flag). Both run.py and gen_db.py take the full path to the binary + as first command line parameter. + +3) How to add more test files? +--------------------------------------------------------------------------------- +Use the following procedure: + - Verify the correctness of your assimp build - run the regression suite. + DO NOT continue if more tests fail than usual. + - Add your additional test files to <root>/test/models/<fileformat>, where + <fileformat> is the file type (typically the file extension). + - If you test file does not meet the BSD license requirements, add it to + <root>/test/models-nonbsd/<fileformat> so people know to be careful with it. + - Rebuild the regression database: + "gen_db.py <binary> -ixyz" where .xyz is the file extension of the new file. + - Run the regression suite again. There should be no new failures and the new + file should not be among the failures. + - Include the db.zip file with your Pull Request. Travis CI enforces a passing + regression suite (with offenders whitelisted as a last resort). + +4) I made a change/fix/patch to a loader, how to update the database? +--------------------------------------------------------------------------------- + - Rebuild the regression database using "gen_db.py <binary> -ixyz" + where .xyz is the file extension for which the loader was patched. + - Run the regression suite again. There should be no new failures and the new + file should not be among the failures. + - Include the db.zip file with your Pull Request. Travis CI enforces a passing + regression suite (with offenders whitelisted as a last resort). + +5) How to add my whole model repository to the database? +--------------------------------------------------------------------------------- + +Edit the reg_settings.py file and add the path to your repository to +<<model_directories>>. Then, rebuild the database. + +6) So what is actually tested? +--------------------------------------------------------------------------------- +The regression database includes mini dumps of the aiScene data structure, i.e. +the scene hierarchy plus the sizes of all data arrays MUST match. Floating-point +data buffers, such as vertex positions are handled less strictly: min, max and +average values are stored with low precision. This takes hardware- or +compiler-specific differences in floating-point computations into account. +Generally, almost all significant regressions will be detected while the +number of false positives is relatively low. + +7) The test suite fails, what do do? +--------------------------------------------------------------------------------- +Get back to <root>/test/results and look at regression_suite_failures.txt. +It contains a list of all files which failed the test. Failing dumps are copied to +<root>/test/results/tmp. Both an EXPECTED and an ACTUAL file is produced per test. +The output of "assimp cmpdump" is written to regressions_suite_output.txt. Grep +for the file name in question and locate the log for the failed comparison. It +contains a full trace of which scene elements have been compared before, which +makes it reasonably easy to locate the offending field. + +8) fp:fast vs fp:precise fails the test suite (same for gcc equivalents) +--------------------------------------------------------------------------------- +As mentioned above, floating-point inaccuracies between differently optimized +builds are not considered regressions and all float comparisons done by the test +suite involve an epsilon to accommodate. However compiler settings that allow +compilers to perform non-IEEE754 compliant optimizations can cause arbitrary +failures in the test suite. Even if the compiler is configured to be IEE754 +comformant, there is lots of code in assimp that leaves the compiler a choice +and different compilers make different choices (for example the precision of +float intermediaries is implementation-specified). + + + + + + + + + + + + + + + diff --git a/libs/assimp/test/regression/ai_regression_ui.py b/libs/assimp/test/regression/ai_regression_ui.py new file mode 100644 index 0000000..30c9b68 --- /dev/null +++ b/libs/assimp/test/regression/ai_regression_ui.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python3 +# -*- Coding: UTF-8 -*- + +# --------------------------------------------------------------------------- +# Open Asset Import Library (ASSIMP) +# --------------------------------------------------------------------------- +# +# Copyright (c) 2006-2020, ASSIMP Development Team +# +# All rights reserved. +# +# Redistribution and use of this software in source and binary forms, +# with or without modification, are permitted provided that the following +# conditions are met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the +# following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the +# following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# * Neither the name of the ASSIMP team, nor the names of its +# contributors may be used to endorse or promote products +# derived from this software without specific prior +# written permission of the ASSIMP Development Team. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# --------------------------------------------------------------------------- +from tkinter import * +import sys +import os +import platform +import run +import subprocess +import result_checker as rc + +INFO = 0 +WARN = 1 +ERROR = 2 + +# ------------------------------------------------------------------------------- +def log( sev, msg ): + """ + This function is used to log info, warnings and errors. + """ + logEntry = "" + if sev == 0: + logEntry = logEntry + "[INFO]: " + elif sev == 1: + logEntry = logEntry + "[WARN]: " + elif sev == 2: + logEntry = logEntry + "[ERR] : " + logEntry = logEntry + str( msg ) + print( logEntry ) + +# ------------------------------------------------------------------------------- +class BaseDialog( Toplevel ): + """ + Helper base class for dialogs used in the UI. + """ + + def __init__(self, parent, title = None, buttons=""): + """ + Constructor + """ + Toplevel.__init__( self, parent ) + self.transient(parent) + + if title: + self.title(title) + + self.parent = parent + self.result = None + body = Frame(self) + self.initial_focus = self.body(body) + body.pack(padx=5, pady=5) + self.buttonbox(buttons) + self.grab_set() + if not self.initial_focus: + self.initial_focus = self + self.protocol("WM_DELETE_WINDOW", self.cancel) + self.geometry("+%d+%d" % (parent.winfo_rootx() + 50, + parent.winfo_rooty() + 50)) + self.initial_focus.focus_set() + self.wait_window(self) + + def body(self, master): + # create dialog body. return widget that should have + # initial focus. this method should be overridden + pass + + def buttonbox(self, buttons): + # add standard button box. override if you don't want the + # standard buttons + box = Frame(self) + w = Button(box, text="OK", width=40, command=self.ok, default=ACTIVE) + w.pack(side=LEFT, padx=5, pady=5) + self.bind("<Return>", self.ok) + box.pack() + + def ok(self, event=None): + if not self.validate(): + self.initial_focus.focus_set() # put focus back + return + + self.withdraw() + self.update_idletasks() + self.apply() + self.cancel() + + def cancel(self, event=None): + # put focus back to the parent window + self.parent.focus_set() + self.destroy() + + def validate(self): + return 1 # override + + def apply(self): + pass # override + +# ------------------------------------------------------------------------------- +class VersionDialog( BaseDialog ): + """ + This class is used to create the info dialog. + """ + def body(self, master): + # info will be read from assimp command line tool + version = "Asset importer lib version unknown" + exe = run.getEnvVar( "assimp_path" ) + if len( exe ) != 0: + command = [exe, "version" ] + log( INFO, "command = " + str(command)) + stdout = subprocess.check_output(command) + for line in stdout.splitlines(): + pos = str(line).find( "Version" ) + if -1 != pos: + version = line + Label(master, text=version).pack() + + def apply(self): + pass + +# ------------------------------------------------------------------------------- +class SetupDialog( BaseDialog ): + """ + This class is used to create the setup dialog. + """ + def body(self, master): + Label(master, justify=LEFT, text="Assimp: " ).grid(row=0, column=0) + Label(master, justify=LEFT, text=run.getEnvVar("assimp_path")).grid(row=0, column=1) + Label(master, text="New executable:").grid(row=1) + self.e1 = Entry(master) + self.e1.grid(row=1, column=1) + return self.e1 # initial focus + + def apply(self): + exe = str( self.e1.get() ) + if len( exe ) == 0: + return 0 + if os.path.isfile( exe ): + log( INFO, "Set executable at " + exe) + self.assimp_bin_path = exe + run.setEnvVar("assimp_path", self.assimp_bin_path) + else: + log( ERROR, "Executable not found at "+exe ) + return 0 + +# ------------------------------------------------------------------------------- +class RegDialog( object ): + """ + This class is used to create a simplified user interface for running the regression test suite. + """ + + def __init__(self, bin_path ): + """ + Constructs the dialog, you can define which executable shal be used. + @param bin_path [in] Path to assimp binary. + """ + run.setEnvVar( "assimp_path", bin_path ) + self.b_run_ = None + self.b_update_ = None + self.b_res_checker_ = None + self.b_quit_ = None + if platform.system() == "Windows": + self.editor = "notepad" + elif platform.system() == "Linux": + self.editor = "vim" + self.root = None + self.width=40 + + def run_reg(self): + log(INFO, "Starting regression test suite.") + run.run_test() + rc.run() + self.b_update_.config( state=ACTIVE ) + return 0 + + def reg_update(self): + assimp_exe = run.getEnvVar( "assimp_path" ) + if len( assimp_exe ) == 0: + return 1 + exe = "python" + command = [ exe, "gen_db.py", assimp_exe ] + log(INFO, "command = " + str(command)) + stdout = subprocess.call(command) + + log(INFO, stdout) + return 0 + + def shop_diff( self ): + log(WARN, "ToDo!") + return 0 + + def open_log(self): + command = [ self.editor, "../results/run_regression_suite_output.txt", ] + log(INFO, "command = " + str( command ) ) + r = subprocess.call(command) + return 0 + + def show_version( self ): + d = VersionDialog( self.root ) + return 0 + + def setup(self): + d = SetupDialog( self.root ) + return 0 + + def quit(self): + log( INFO, "quit" ) + sys.exit( 0 ) + + def initUi(self): + # create the frame with buttons + self.root = Tk() + self.root.title( "Assimp-Regression UI") + self.b_run_ = Button( self.root, text="Run regression ", command=self.run_reg, width = self.width ) + self.b_update_ = Button( self.root, text="Update database", command=self.reg_update, width = self.width ) + self.b_show_diff_ = Button( self.root, text="Show diff", command=self.shop_diff, width = self.width ) + self.b_log_ = Button( self.root, text="Open log", command=self.open_log, width = self.width ) + self.b_setup_ = Button( self.root, text="Setup", command=self.setup, width = self.width ) + self.b_version_ = Button( self.root, text="Show version", command=self.show_version, width = self.width ) + self.b_quit_ = Button( self.root, text="Quit", command=self.quit, width = self.width ) + + # define the used grid + self.b_run_.grid( row=0, column=0, sticky=W+E ) + self.b_update_.grid( row=1, column=0, sticky=W+E ) + self.b_show_diff_.grid( row=2, column=0, sticky=W+E ) + self.b_log_.grid( row=3, column=0, sticky=W+E ) + self.b_setup_.grid( row=4, column=0, sticky=W+E ) + self.b_version_.grid( row=5, column=0, sticky=W+E ) + self.b_quit_.grid( row=6, column=0, sticky=W+E ) + + #self.b_update_.config( state=DISABLED ) + self.b_show_diff_.config( state=DISABLED ) + + # run mainloop + self.root.mainloop() + +# ------------------------------------------------------------------------------- +def getDefaultExecutable(): + assimp_bin_path = "" + if platform.system() == "Windows": + assimp_bin_path = '..\\..\\bin\\debug\\assimpd.exe' + elif platform.system() == "Linux": + assimp_bin_path = '../../bin/assimp' + + return assimp_bin_path + +# ------------------------------------------------------------------------------- +if __name__ == "__main__": + if len(sys.argv) > 1: + assimp_bin_path = sys.argv[1] + else: + assimp_bin_path = getDefaultExecutable() + log( INFO, 'Using assimp binary: ' + assimp_bin_path ) + dlg = RegDialog(assimp_bin_path) + dlg.initUi() + +# vim: ai ts=4 sts=4 et sw=4 diff --git a/libs/assimp/test/regression/gen_db.py b/libs/assimp/test/regression/gen_db.py new file mode 100644 index 0000000..a704c3a --- /dev/null +++ b/libs/assimp/test/regression/gen_db.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 +# -*- Coding: UTF-8 -*- + +# --------------------------------------------------------------------------- +# Open Asset Import Library (ASSIMP) +# --------------------------------------------------------------------------- +# +# Copyright (c) 2006-2020, ASSIMP Development Team +# +# All rights reserved. +# +# Redistribution and use of this software in source and binary forms, +# with or without modification, are permitted provided that the following +# conditions are met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the +# following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the +# following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# * Neither the name of the ASSIMP team, nor the names of its +# contributors may be used to endorse or promote products +# derived from this software without specific prior +# written permission of the ASSIMP Development Team. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# --------------------------------------------------------------------------- + +""" +Generate the regression database db.zip from the files in the <root>/test/models +directory. Older databases are overwritten with no prompt but can be restored +using Git as needed. + +Use --help for usage. + +On Windows, use ``py run.py <arguments>`` to make sure command line parameters +are forwarded to the script. +""" + +import sys +import os +import subprocess +import zipfile + +import settings +import utils + +usage = """gen_db [assimp_binary] [-i=...] [-e=...] [-p] [-n] + +The assimp_cmd (or assimp) binary to use is specified by the first +command line argument and defaults to ``assimp``. + +To build, set ``ASSIMP_BUILD_ASSIMP_TOOLS=ON`` in CMake. If generating +configs for an IDE, make sure to build the assimp_cmd project. + +-i,--include: List of file extensions to update dumps for. If omitted, + all file extensions are updated except those in `exclude`. + Example: -ixyz,abc + -i.xyz,.abc + --include=xyz,abc + +-e,--exclude: Merged with settings.exclude_extensions to produce a + list of all file extensions to ignore. If dumps exist, + they are not altered. If not, theu are not created. + +-p,--preview: Preview list of file extensions touched by the update. + Dont' change anything. + +-n,--nozip: Don't pack to ZIP archive. Keep all dumps in individual files. +""" + +# ------------------------------------------------------------------------------- +def process_dir(d, outfile, file_filter): + """ Generate small dump records for all files in 'd' """ + print("Processing directory " + d) + + num = 0 + for f in os.listdir(d): + fullp = os.path.join(d, f) + if os.path.isdir(fullp) and not f == ".svn": + num += process_dir(fullp, outfile, file_filter) + continue + + if file_filter(f): + for pp in settings.pp_configs_to_test: + num += 1 + print("DUMP " + fullp + "\n post-processing: " + pp) + outf = os.path.join(os.getcwd(), settings.database_name, + utils.hashing(fullp, pp)) + + cmd = [ assimp_bin_path, "dump", fullp, outf, "-b", "-s", "-l" ] + pp.split() + outfile.write("assimp dump "+"-"*80+"\n") + outfile.flush() + if subprocess.call(cmd, stdout=outfile, stderr=outfile, shell=False): + print("Failure processing " + fullp) + + # spit out an empty file to indicate that this failure is expected + with open(outf,'wb') as f: + pass + return num + + +# ------------------------------------------------------------------------------- +def make_zip(): + """Zip the contents of ./<settings.database_name> + to <settings.database_name>.zip using DEFLATE + compression to minimize the file size. """ + + num = 0 + zipout = zipfile.ZipFile(settings.database_name + ".zip", "w", zipfile.ZIP_DEFLATED) + for f in os.listdir(settings.database_name): + p = os.path.join(settings.database_name, f) + zipout.write(p, f) + if settings.remove_old: + os.remove(p) + + num += 1 + + if settings.remove_old: + os.rmdir(settings.database_name) + + bad = zipout.testzip() + assert bad is None + + print("="*60) + print("Database contains {0} entries".format(num)) + + +# ------------------------------------------------------------------------------- +def extract_zip(): + """Unzip <settings.database_name>.zip to + ./<settings.database_name>""" + try: + zipout = zipfile.ZipFile(settings.database_name + ".zip", "r", 0) + zipout.extractall(path=settings.database_name) + except (RuntimeError,IOError) as r: + print(r) + print("failed to extract previous ZIP contents. "\ + "DB is generated from scratch.") + + +# ------------------------------------------------------------------------------- +def gen_db(ext_list,outfile): + """Generate the crash dump database in + ./<settings.database_name>""" + try: + os.mkdir(settings.database_name) + except OSError: + pass + + num = 0 + for tp in settings.model_directories: + num += process_dir(tp, outfile, + lambda x: os.path.splitext(x)[1].lower() in ext_list and not x in settings.files_to_ignore) + + print("="*60) + print("Updated {0} entries".format(num)) + + +# ------------------------------------------------------------------------------- +if __name__ == "__main__": + def clean(f): + f = f.strip("* \'") + return "."+f if f[:1] != '.' else f + + if len(sys.argv) <= 1 or sys.argv[1] == "--help" or sys.argv[1] == "-h": + print(usage) + sys.exit(0) + + assimp_bin_path = sys.argv[1] + ext_list, preview, nozip = None, False, False + for m in sys.argv[2:]: + if m[:10]=="--exclude=": + settings.exclude_extensions += map(clean, m[10:].split(",")) + elif m[:2]=="-e": + settings.exclude_extensions += map(clean, m[2:].split(",")) + elif m[:10]=="--include=": + ext_list = m[10:].split(",") + elif m[:2]=="-i": + ext_list = m[2:].split(",") + elif m=="-p" or m == "--preview": + preview = True + elif m=="-n" or m == "--nozip": + nozip = True + else: + print("Unrecognized parameter: " + m) + sys.exit(-1) + + outfile = open(os.path.join("..", "results", "gen_regression_db_output.txt"), "w") + if ext_list is None: + (ext_list, err) = subprocess.Popen([assimp_bin_path, "listext"], + stdout=subprocess.PIPE).communicate() + ext_list = str(ext_list.strip()).lower().split(";") + + # todo: Fix for multi dot extensions like .skeleton.xml + ext_list = list(filter(lambda f: not f in settings.exclude_extensions, + map(clean, ext_list))) + print('File extensions processed: ' + ', '.join(ext_list)) + if preview: + sys.exit(1) + + extract_zip() + gen_db(ext_list,outfile) + make_zip() + + print("="*60) + input("Press any key to continue") + sys.exit(0) + +# vim: ai ts=4 sts=4 et sw=4 + diff --git a/libs/assimp/test/regression/result_checker.py b/libs/assimp/test/regression/result_checker.py new file mode 100644 index 0000000..1b01c52 --- /dev/null +++ b/libs/assimp/test/regression/result_checker.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# -*- Coding: UTF-8 -*- + +# --------------------------------------------------------------------------- +# Open Asset Import Library (ASSIMP) +# --------------------------------------------------------------------------- +# +# Copyright (c) 2006-2020, ASSIMP Development Team +# +# All rights reserved. +# +# Redistribution and use of this software in source and binary forms, +# with or without modification, are permitted provided that the following +# conditions are met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the +# following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the +# following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# * Neither the name of the ASSIMP team, nor the names of its +# contributors may be used to endorse or promote products +# derived from this software without specific prior +# written permission of the ASSIMP Development Team. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# --------------------------------------------------------------------------- + +""" +This script runs as part of the Travis CI build on Github and controls +whether a patch passes the regression test suite. + +Given the failures encountered by the regression suite runner (run.py) in + ../results/whitelist.csv +and the current whitelist of failures considered acceptable in + ./run_regression_suite_failures_whitelisted.csv +determine PASSED or FAILED. + +""" + +import sys +import os + +RESULTS_FILE = os.path.join('..', 'results', 'run_regression_suite_failures.csv') +WHITELIST_FILE = os.path.join('whitelist.csv') + +BANNER = """ +***************************************************************** +Regression suite result checker +(test/regression/result_checker.py) +***************************************************************** +""" + +def passed(message): + print('\n\n**PASSED: {0}.\n'.format(message)) + return 0 + +def failed(message): + print('\n\n**FAILED: {0}. \nFor more information see test/regression/README.\n' + .format(message)) + return -1 + +def read_results_csv(filename): + parse = lambda line: map(str.strip, line.split(';')[:2]) + try: + with open(filename, 'rt') as results: + return dict(parse(line) for line in results.readlines()[1:]) + except IOError: + print('Failed to read {0}.'.format(filename)) + return None + +def run(): + print(BANNER) + print('Reading input files.') + + result_dict = read_results_csv(RESULTS_FILE) + whitelist_dict = read_results_csv(WHITELIST_FILE) + if result_dict is None or whitelist_dict is None: + return failed('Could not locate input files') + + if not result_dict: + return passed('No failures encountered') + + print('Failures:\n' + '\n'.join(sorted(result_dict.keys()))) + print('Whitelisted:\n' + '\n'.join(sorted(whitelist_dict.keys()))) + non_whitelisted_failures = set(result_dict.keys()) - set(whitelist_dict.keys()) + print('Failures not whitelisted:\n' + '\n'.join(sorted(non_whitelisted_failures))) + if not non_whitelisted_failures: + return passed('All failures are whitelisted and considered acceptable \n' + + 'due to implementation differences, library shortcomings and bugs \n' + + 'that have not been fixed for a long time') + return failed('Encountered new regression failures that are not whitelisted. \n' + + 'Please carefully review the changes you made and use the gen_db.py script\n' + + 'to update the regression database for the affected files') + +if __name__ == "__main__": + sys.exit(run()) + +# vim: ai ts=4 sts=4 et sw=4 diff --git a/libs/assimp/test/regression/run.py b/libs/assimp/test/regression/run.py new file mode 100755 index 0000000..151d7a8 --- /dev/null +++ b/libs/assimp/test/regression/run.py @@ -0,0 +1,320 @@ +#!/usr/bin/env python3 +# -*- Coding: UTF-8 -*- + +# --------------------------------------------------------------------------- +# Open Asset Import Library (ASSIMP) +# --------------------------------------------------------------------------- +# +# Copyright (c) 2006-2020, ASSIMP Development Team +# +# All rights reserved. +# +# Redistribution and use of this software in source and binary forms, +# with or without modification, are permitted provided that the following +# conditions are met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the +# following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the +# following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# * Neither the name of the ASSIMP team, nor the names of its +# contributors may be used to endorse or promote products +# derived from this software without specific prior +# written permission of the ASSIMP Development Team. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# --------------------------------------------------------------------------- + +""" +Run the regression test suite using settings from settings.py. + +The assimp_cmd (or assimp) binary to use is specified by the first +command line argument and defaults to ``assimp``. + +To build, set ``ASSIMP_BUILD_ASSIMP_TOOLS=ON`` in CMake. If generating +configs for an IDE, make sure to build the assimp_cmd project. + +On Windows, use ``py run.py <path to assimp>`` to make sure the command +line parameter is forwarded to the script. +""" + +import sys +import os +import subprocess +import zipfile +import collections +import multiprocessing + +import settings +import utils + +# ------------------------------------------------------------------------------- +EXPECTED_FAILURE_NOT_MET, DATABASE_LENGTH_MISMATCH, \ +DATABASE_VALUE_MISMATCH, IMPORT_FAILURE, \ +FILE_NOT_READABLE, COMPARE_SUCCESS, EXPECTED_FAILURE = range(7) + +messages = collections.defaultdict(lambda: "<unknown", { + EXPECTED_FAILURE_NOT_MET: +"""Unexpected success during import\n\ +\tReturn code was 0""", + + DATABASE_LENGTH_MISMATCH: +"""Database mismatch: lengths don't match\n\ +\tExpected: {0} Actual: {1}""", + + DATABASE_VALUE_MISMATCH: +"""Database mismatch: """, + + IMPORT_FAILURE: +"""Unexpected failure during import\n\ +\tReturn code was {0}""", + + FILE_NOT_READABLE: +"""Unexpected failure reading file""", + + COMPARE_SUCCESS: +"""Results match archived reference dump in database\n\ +\tNumber of bytes compared: {0}""", + + EXPECTED_FAILURE: +"""Expected failure was met.""", +}) + +outfilename_output = "run_regression_suite_output.txt" +outfilename_failur = "run_regression_suite_failures.csv" +Environment = {} + +# ------------------------------------------------------------------------------- +class results: + + """ Handle formatting of results""" + + def __init__(self, zipin): + """Init, given a ZIPed database """ + self.failures = [] + self.success = [] + self.zipin = zipin + + + def fail(self, failfile, filename_expect, pp, msg, *args): + """ + Report failure of a sub-test + + File f failed a test for pp config pp, failure notice is msg, + *args is format()ting args for msg + + """ + print("[FAILURE] " + messages[msg].format(*args)) + self.failures.append((failfile, filename_expect, pp)) + + + def ok(self, f, pp, msg, *args): + """ + Report success of a sub-test + + File f passed the test, msg is a happy success note, + *args is format()ing args for msg. + + """ + print("[SUCCESS] " + messages[msg].format(*args)) + self.success.append(f) + + + def report_results(self): + """Write results to ../results/run_regression_suite_failures.txt""" + + count_success = len(self.success) + count_fail = len(self.failures) + percent_good = float(count_success) / (count_success + count_fail) + print("\n" + ('='*60) + "\n" + "SUCCESS: {0}\nFAILURE: {1}\nPercentage good: {2}".format( + count_success, count_fail, percent_good) + + "\n" + ('='*60) + "\n") + + with open(os.path.join('..', 'results',outfilename_failur), "wt") as f: + f.write("ORIGINAL FILE;EXPECTED DUMP\n") + f.writelines(map( + lambda x: x[0] + ' ' + x[2] + ";" + x[1] + "\n", self.failures)) + + if self.failures: + print("\nSee " + settings.results + "\\" + outfilename_failur + + " for more details\n\n") + + def hasFailures( self ): + """ Return True, if any failures there. """ + return 0 != len( self.failures ) + +# ------------------------------------------------------------------------------- +def setEnvVar( var, value ): + print ( "set var " + var +" to" + value) + Environment[ var ] = value + +# ------------------------------------------------------------------------------- +def getEnvVar( var ): + if var in Environment: + return Environment[ var ] + else: + print ( "Error: cannot find " + var ) + return "" + +# ------------------------------------------------------------------------------- +def prepare_output_dir(fullpath, myhash, app): + outfile = os.path.join(settings.results, "tmp", os.path.split(fullpath)[1] + "_" + myhash) + try: + os.mkdir(outfile) + except OSError: + pass + + outfile = os.path.join(outfile, app) + return outfile + +# ------------------------------------------------------------------------------- +def process_dir(d, outfile_results, zipin, result ): + shellparams = {'stdout':outfile_results, 'stderr':outfile_results, 'shell':False} + + print("Processing directory " + d) + all = "" + for f in sorted(os.listdir(d)): + fullpath = os.path.join(d, f) + if os.path.isdir(fullpath) and not f[:1] == '.': + process_dir(fullpath, outfile_results, zipin, result) + continue + + if f in settings.files_to_ignore or os.path.splitext(f)[1] in settings.exclude_extensions: + print("Ignoring " + f) + return + + for pppreset in settings.pp_configs_to_test: + filehash = utils.hashing(fullpath, pppreset) + failure = False + + try: + input_expected = zipin.open(filehash, "r").read() + # empty dump files indicate 'expected import failure' + if not len(input_expected): + failure = True + except KeyError: + # TODO(acgessler): Keep track of this and report as error in the end. + print("Didn't find "+fullpath+" (Hash is "+filehash+") in database. Outdated "+\ + "regression database? Use gen_db.zip to re-generate.") + continue + + print("-"*60 + "\n " + os.path.realpath(fullpath) + " pp: " + pppreset) + + outfile_actual = prepare_output_dir(fullpath, filehash, "ACTUAL") + outfile_expect = prepare_output_dir(fullpath, filehash, "EXPECT") + outfile_results.write("assimp dump "+"-"*80+"\n") + outfile_results.flush() + assimp_bin_path = getEnvVar("assimp_path") + command = [assimp_bin_path, + "dump", + fullpath, outfile_actual, "-b", "-s", "-l" ] +\ + pppreset.split() + print( "command = " + str( command ) ) + r = subprocess.call(command, **shellparams) + outfile_results.flush() + + if r and not failure: + result.fail(fullpath, outfile_expect, pppreset, IMPORT_FAILURE, r) + outfile_results.write("Failed to import\n") + continue + elif failure and not r: + result.fail(fullpath, outfile_expect, pppreset, EXPECTED_FAILURE_NOT_MET) + outfile_results.write("Expected import to fail\n") + continue + elif failure and r: + result.ok(fullpath, pppreset, EXPECTED_FAILURE) + outfile_results.write("Failed as expected, skipping.\n") + continue + + with open(outfile_expect, "wb") as s: + s.write(input_expected) + + try: + with open(outfile_actual, "rb") as s: + input_actual = s.read() + except IOError: + continue + + outfile_results.write("Expected data length: {0}\n".format(len(input_expected))) + outfile_results.write("Actual data length: {0}\n".format(len(input_actual))) + failed = False + if len(input_expected) != len(input_actual): + result.fail(fullpath, outfile_expect, pppreset, DATABASE_LENGTH_MISMATCH, + len(input_expected), len(input_actual)) + # Still compare the dumps to see what the difference is + failed = True + + outfile_results.write("assimp cmpdump "+"-"*80+"\n") + outfile_results.flush() + command = [ assimp_bin_path, 'cmpdump', outfile_actual, outfile_expect ] + if subprocess.call(command, **shellparams) != 0: + if not failed: + result.fail(fullpath, outfile_expect, pppreset, DATABASE_VALUE_MISMATCH) + continue + + result.ok(fullpath, pppreset, COMPARE_SUCCESS, len(input_expected)) + +# ------------------------------------------------------------------------------- +def del_folder_with_contents(folder): + for root, dirs, files in os.walk(folder, topdown=False): + for name in files: + os.remove(os.path.join(root, name)) + for name in dirs: + os.rmdir(os.path.join(root, name)) + + +# ------------------------------------------------------------------------------- +def run_test(): + tmp_target_path = os.path.join(settings.results, "tmp") + try: + print( "try to make " + tmp_target_path ) + os.mkdir(tmp_target_path) + except OSError as oerr: + # clear contents if tmp folder exists already + del_folder_with_contents(tmp_target_path) + + try: + zipin = zipfile.ZipFile(settings.database_name + ".zip", + "r", zipfile.ZIP_STORED) + except IOError: + print("Regression database ", settings.database_name, + ".zip was not found") + return + + res = results(zipin) + with open(os.path.join(settings.results, outfilename_output), "wt") as outfile: + for tp in settings.model_directories: + process_dir(tp, outfile, zipin, res) + + res.report_results() + if res.hasFailures(): + return 1 + + return 0 + +# ------------------------------------------------------------------------------- +if __name__ == "__main__": + if len(sys.argv) > 1: + assimp_bin_path = sys.argv[1] + else: + assimp_bin_path = 'assimp' + setEnvVar("assimp_path", assimp_bin_path) + print('Using assimp binary: ' + assimp_bin_path) + sys.exit( run_test() ) + +# vim: ai ts=4 sts=4 et sw=4 diff --git a/libs/assimp/test/regression/settings.py b/libs/assimp/test/regression/settings.py new file mode 100644 index 0000000..9d0a036 --- /dev/null +++ b/libs/assimp/test/regression/settings.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +# -*- Coding: UTF-8 -*- + +# --------------------------------------------------------------------------- +# Open Asset Import Library (ASSIMP) +# --------------------------------------------------------------------------- +# +# Copyright (c) 2006-2020, ASSIMP Development Team +# +# All rights reserved. +# +# Redistribution and use of this software in source and binary forms, +# with or without modification, are permitted provided that the following +# conditions are met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the +# following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the +# following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# * Neither the name of the ASSIMP team, nor the names of its +# contributors may be used to endorse or promote products +# derived from this software without specific prior +# written permission of the ASSIMP Development Team. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# --------------------------------------------------------------------------- + +"""Shared settings for the regression suite (bold builder and +test scripts rely on this) + +""" + +import os + +# ------------------------------------------------------------------------------- +# Files to ignore (with reason) +# +# pond.0.ply - loads with 40k identical triangles, causing postprocessing +# to have quadratic runtime. +# ------------------------------------------------------------------------------- +files_to_ignore = ["pond.0.ply"] + +# ------------------------------------------------------------------------------- +# List of file extensions to be excluded from the regression suite +# File extensions are case insensitive +# ------------------------------------------------------------------------------- +exclude_extensions = [ + ".assbin", ".assxml", ".txt", ".md", + ".jpeg", ".jpg", ".png", ".gif", ".tga", ".bmp", + ".skeleton", ".skeleton.xml", ".license", ".mtl", ".material", ".pk3" +] + +# ------------------------------------------------------------------------------- +# Post processing configurations to be included in the test. The +# strings are parameters for assimp_cmd, see assimp_cmd's doxydoc +# for more details. + +# The defaults are (validate-data-structure is always enabled, for +# self-explanatory reasons :-): +# +# '-cfull' :apply all post processing except 'og' and 'ptv' (optimize-scenegraph) +# '-og -om' :run optimize-scenegraph in combination with optimize-meshes. +# '-vds -jiv' :join-identical-vertices alone. This is a hotspot where +# floating-point inaccuracies can cause severe damage. +# '-ptv': transform all meshes to world-space + +# As you can see, not all possible combinations of pp steps are covered - +# but at least each step is executed at least once on each model. +# ------------------------------------------------------------------------------- +pp_configs_to_test = [ + "-cfull", + "-og -om -vds", + "-vds -jiv", + "-ptv -gsn -cts -db", + + # this is especially important: if no failures are present with this + # preset, the regression is most likely caused by the post + # processing pipeline. + "" +] +# ------------------------------------------------------------------------------- +# Name of the regression database file to be used +# gen_db.py writes to this directory, run.py checks against this directory. +# If a zip file with the same name exists, its contents are favoured to a +# normal directory, so in order to test against unzipped files the ZIP needs +# to be deleted. +# ------------------------------------------------------------------------------- +database_name = "db" + +# ------------------------------------------------------------------------------- +# List of directories to be processed. Paths are processed recursively. +# ------------------------------------------------------------------------------- +model_directories = [ + os.path.join("..","models"), + os.path.join("..","models-nonbsd") +] + +# ------------------------------------------------------------------------------- +# Remove the original database files after the ZIP has been built? +# ------------------------------------------------------------------------------- +remove_old = True + +# ------------------------------------------------------------------------------- +# Bytes to skip at the beginning of a dump. This skips the file header, which +# is currently the same 500 bytes header for both assbin, assxml and minidumps. +# ------------------------------------------------------------------------------- +dump_header_skip = 500 + +# ------------------------------------------------------------------------------- +# Directory to write all results and logs to. The dumps pertaining to failed +# tests are written to a subfolder of this directory ('tmp'). +# ------------------------------------------------------------------------------- +results = os.path.join("..","results") + +# Create results directory if it does not exist +if not os.path.exists(results): + os.makedirs(results) + +# vim: ai ts=4 sts=4 et sw=4 diff --git a/libs/assimp/test/regression/utils.py b/libs/assimp/test/regression/utils.py new file mode 100644 index 0000000..f2c0c26 --- /dev/null +++ b/libs/assimp/test/regression/utils.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- Coding: UTF-8 -*- + +# --------------------------------------------------------------------------- +# Open Asset Import Library (ASSIMP) +# --------------------------------------------------------------------------- +# +# Copyright (c) 2006-2020, ASSIMP Development Team +# +# All rights reserved. +# +# Redistribution and use of this software in source and binary forms, +# with or without modification, are permitted provided that the following +# conditions are met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the +# following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the +# following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# * Neither the name of the ASSIMP team, nor the names of its +# contributors may be used to endorse or promote products +# derived from this software without specific prior +# written permission of the ASSIMP Development Team. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# --------------------------------------------------------------------------- + +"""Shared stuff for the gen_db and run scripts""" + +# ------------------------------------------------------------------------------- +def hashing(file,pp): + """ Map an input file and a postprocessing config to an unique hash. + + The hash is used to store the item in the database. It + needs to be persistent across different python implementations + and platforms, so we implement the hashing manually. + """ + file = file.lower() + file = file.replace('\\','/')+":"+pp + # SDBM hash + res = 0 + for t in file: + res = (ord(t) + (res<<6) + (res<<16) - res) % 2**32 + return '{:x}'.format(res) + + + # vim: ai ts=4 sts=4 et sw=4 diff --git a/libs/assimp/test/regression/whitelist.csv b/libs/assimp/test/regression/whitelist.csv new file mode 100644 index 0000000..43e0928 --- /dev/null +++ b/libs/assimp/test/regression/whitelist.csv @@ -0,0 +1,32 @@ +ORIGINAL FILE;EXPECTED DUMP +../models/AC/SphereWithLightUvScaling4X.ac -cfull;../results/tmp/SphereWithLightUvScaling4X.ac_30edbbcd/EXPECT +../models/AC/SphereWithLightUvScaling4X.ac -vds -jiv;../results/tmp/SphereWithLightUvScaling4X.ac_a627320a/EXPECT +../models/AC/sample_subdiv.ac -cfull;../results/tmp/sample_subdiv.ac_669c529e/EXPECT +../models/AC/sample_subdiv.ac -vds -jiv;../results/tmp/sample_subdiv.ac_d5987f9/EXPECT +../models/BLEND/CubeHierarchy_248.blend -cfull;../results/tmp/CubeHierarchy_248.blend_4e04cc99/EXPECT +../models/BLEND/CubeHierarchy_248.blend -og -om -vds;../results/tmp/CubeHierarchy_248.blend_5e78ea9c/EXPECT +../models/BLEND/CubeHierarchy_248.blend -vds -jiv;../results/tmp/CubeHierarchy_248.blend_8aca7a3e/EXPECT +../models/BLEND/CubeHierarchy_248.blend -ptv -gsn -cts -db;../results/tmp/CubeHierarchy_248.blend_1f2b8aa4/EXPECT +../models/BLEND/CubeHierarchy_248.blend ;../results/tmp/CubeHierarchy_248.blend_b827f814/EXPECT +../models/BLEND/SuzanneSubdiv_252.blend -cfull;../results/tmp/SuzanneSubdiv_252.blend_4a8dd3d3/EXPECT +../models/BLEND/SuzanneSubdiv_252.blend -vds -jiv;../results/tmp/SuzanneSubdiv_252.blend_7f41fe84/EXPECT +../models/STL/Spider_ascii.stl -cfull;../results/tmp/Spider_ascii.stl_8047fd91/EXPECT +../models/STL/Spider_ascii.stl -vds -jiv;../results/tmp/Spider_ascii.stl_d4858346/EXPECT +../models/STL/Spider_binary.stl -cfull;../results/tmp/Spider_binary.stl_dd4fe21/EXPECT +../models/STL/Spider_binary.stl -vds -jiv;../results/tmp/Spider_binary.stl_2c8deeb6/EXPECT +../models-nonbsd/BLEND/Bob.blend -cfull;../results/tmp/Bob.blend_7f8349ba/EXPECT +../models-nonbsd/BLEND/Bob.blend -og -om -vds;../results/tmp/Bob.blend_4f4a63d/EXPECT +../models-nonbsd/BLEND/Bob.blend -vds -jiv;../results/tmp/Bob.blend_ab1aa5dd/EXPECT +../models-nonbsd/BLEND/Bob.blend -ptv -gsn -cts -db;../results/tmp/Bob.blend_6a5ec4c5/EXPECT +../models-nonbsd/BLEND/Bob.blend ;../results/tmp/Bob.blend_2fc376b5/EXPECT +../models-nonbsd/BLEND/fleurOptonl.blend -cfull;../results/tmp/fleurOptonl.blend_6e87e039/EXPECT +../models-nonbsd/BLEND/fleurOptonl.blend -og -om -vds;../results/tmp/fleurOptonl.blend_95048e3c/EXPECT +../models-nonbsd/BLEND/fleurOptonl.blend -vds -jiv;../results/tmp/fleurOptonl.blend_52481e9e/EXPECT +../models-nonbsd/BLEND/fleurOptonl.blend -ptv -gsn -cts -db;../results/tmp/fleurOptonl.blend_5e7be44/EXPECT +../models-nonbsd/BLEND/fleurOptonl.blend ;../results/tmp/fleurOptonl.blend_18ca7bb4/EXPECT +../models-nonbsd/LWS/QuickDraw v.2.2_oldformat_56.lws -ptv -gsn -cts -db;../results/tmp/QuickDraw v.2.2_oldformat_56.lws_da6fa6e2/EXPECT +../models-nonbsd/LWS/QuickDraw v.2.2_oldformat_56.lws ;../results/tmp/QuickDraw v.2.2_oldformat_56.lws_d37dcb52/EXPECT +../models-nonbsd/Ogre/OgreSDK/ninja.mesh.xml -cfull;../results/tmp/ninja.mesh.xml_2a5e7eb6/EXPECT +../models-nonbsd/PK3/SGDTT3.pk3 -ptv -gsn -cts -db;../results/tmp/SGDTT3.pk3_591f74f1/EXPECT +../models-nonbsd/PK3/SGDTT3.pk3 ;../results/tmp/SGDTT3.pk3_ad4b2ce1/EXPECT +../models-nonbsd/PK3/SGDTT3.pk3 -cfull;../results/tmp/SGDTT3.pk3_ad4b2ce1/EXPECT |