mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-28 00:20:47 +01:00
redo vrts.bat as vrts.py
- portable and easier to modify - better colored output - uses fuzzy matching for +-1 PCM samples
This commit is contained in:
parent
86c4d81506
commit
63b96bc53c
509
cli/tools/vrts.py
Normal file
509
cli/tools/vrts.py
Normal file
@ -0,0 +1,509 @@
|
|||||||
|
# VRTS - VGMSTREAM REGRESSION TESTING SCRIPT
|
||||||
|
#
|
||||||
|
# Searches for files in a directory (or optionally subdirs) and compares
|
||||||
|
# the output of two CLI versions, both wav and stdout, for regression
|
||||||
|
# testing. This creates and deletes temp files, trying to process all
|
||||||
|
# extensions found unless specified (except a few).
|
||||||
|
|
||||||
|
# TODO reject some .wav but not all (detect created by v)
|
||||||
|
# TODO capture stdout and enable fuzzy depending on codec
|
||||||
|
# TODO fix -l option, add decode reset option
|
||||||
|
import os, argparse, time, datetime, glob, subprocess, struct
|
||||||
|
|
||||||
|
|
||||||
|
# don't try to decode common stuff
|
||||||
|
IGNORED_EXTENSIONS = ['.exe', '.dll', '.zip', '.7z', '.rar', '.bat', '.sh', '.txt', '.lnk', '.wav', '.py', '.md', '.idb']
|
||||||
|
#TODO others
|
||||||
|
FUZZY_CODECS = ['ffmpeg', 'vorbis', 'mpeg', 'speex', 'celt']
|
||||||
|
|
||||||
|
DEFAULT_CLI_NEW = 'vgmstream-cli'
|
||||||
|
DEFAULT_CLI_OLD = 'vgmstream-cli_old'
|
||||||
|
|
||||||
|
# result codes, where >= 0: ok (acceptable), <0: ko (not good)
|
||||||
|
RESULT_SAME = 0 # no diffs
|
||||||
|
RESULT_FUZZY = 1 # no duffs allowing +-N
|
||||||
|
RESULT_NONE = 2 # neither exists
|
||||||
|
RESULT_DIFFS = -3 # different
|
||||||
|
RESULT_SIZES = -4 # different sizes
|
||||||
|
RESULT_MISSING_NEW = -5 # new does not exist
|
||||||
|
RESULT_MISSING_OLD = 6 # old does not exist
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
description = (
|
||||||
|
"Compares new vs old vgmstream CLI output, for regression testing"
|
||||||
|
)
|
||||||
|
epilog = (
|
||||||
|
"examples:\n"
|
||||||
|
"%(prog)s *.ogg -r -n\n"
|
||||||
|
"- checks for differences in ogg of this and subfolders\n"
|
||||||
|
"%(prog)s *.adx -nd\n"
|
||||||
|
"- checks for differences in adx and doesn't delete wav output\n"
|
||||||
|
"%(prog)s -p\n"
|
||||||
|
"- compares performance performance of all files\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
ap = argparse.ArgumentParser(description=description, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter)
|
||||||
|
ap.add_argument("files", help="files to match", nargs='*', default=["*.*"])
|
||||||
|
ap.add_argument("-r","--recursive", help="search files in subfolders", action='store_true')
|
||||||
|
ap.add_argument("-z","--fuzzy", help="fuzzy threshold of +-N PCM16LE", type=int, default=1)
|
||||||
|
ap.add_argument("-nd","--no-delete", help="don't delete output", action='store_true')
|
||||||
|
ap.add_argument("-rd","--result-diffs", help="only report full diffs", action='store_true')
|
||||||
|
ap.add_argument("-rz","--result-fuzzy", help="only report full and fuzzy diffs", action='store_true')
|
||||||
|
ap.add_argument("-p","--performance-both", help="compare decode performance", action='store_true')
|
||||||
|
ap.add_argument("-pn","--performance-new", help="test performance of new CLI", action='store_true')
|
||||||
|
ap.add_argument("-po","--performance-old", help="test performance of old CLI", action='store_true')
|
||||||
|
ap.add_argument("-pw","--performance-write", help="compare decode+write performance", action='store_true')
|
||||||
|
ap.add_argument("-pr","--performance-repeat", help="repeat decoding files N times\n(more files makes easier to see performance changes)", type=int, default=0)
|
||||||
|
ap.add_argument("-l","--looping", help="compare looping files (slower)", action='store_true')
|
||||||
|
ap.add_argument("-cn","--cli-new", help="sets name of new CLI (can be a path)")
|
||||||
|
ap.add_argument("-co","--cli-old", help="sets name of old CLI (can be a path)")
|
||||||
|
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
# derived defaults to simplify
|
||||||
|
args.performance = args.performance_both or args.performance_new or args.performance_old or args.performance_write
|
||||||
|
args.compare = not args.performance
|
||||||
|
if args.performance_both:
|
||||||
|
args.performance_new = True
|
||||||
|
args.performance_old = True
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
S16_UNPACK = struct.Struct('<h').unpack_from
|
||||||
|
|
||||||
|
# Compares 2 files and returns if contents are the same.
|
||||||
|
# If fuzzy is set detects +- PCM changes (slower).
|
||||||
|
class VrtsComparator:
|
||||||
|
CHUNK_HEADER = 0x50
|
||||||
|
CHUNK_SIZE = 0x00100000
|
||||||
|
|
||||||
|
def __init__(self, path1, path2, fuzzy_max=0):
|
||||||
|
self._path1 = path1
|
||||||
|
self._path2 = path2
|
||||||
|
self._fuzzy_max = fuzzy_max
|
||||||
|
self._offset = 0
|
||||||
|
self.fuzzy_count = 0
|
||||||
|
self.fuzzy_diff = 0
|
||||||
|
self.fuzzy_offset = 0
|
||||||
|
|
||||||
|
def _compare_fuzzy(self, b1, b2):
|
||||||
|
len1 = len(b1)
|
||||||
|
len2 = len(b2)
|
||||||
|
if len1 != len2:
|
||||||
|
return RESULT_SAME
|
||||||
|
|
||||||
|
# compares PCM16LE bytes allowing +-N diffs between PCM bytes
|
||||||
|
# useful when comparing output from floats, that can change slightly due to compiler optimizations
|
||||||
|
max = self._fuzzy_max
|
||||||
|
pos = 0
|
||||||
|
while pos < len1:
|
||||||
|
# slower than struct unpack
|
||||||
|
#pcm1 = b1[pos+0] | (b1[pos+1] << 8)
|
||||||
|
#if pcm1 & 0x8000:
|
||||||
|
# pcm1 -= 0x10000
|
||||||
|
#pcm2 = b2[pos+0] | (b2[pos+1] << 8)
|
||||||
|
#if pcm2 & 0x8000:
|
||||||
|
# pcm2 -= 0x10000
|
||||||
|
|
||||||
|
pcm1, = S16_UNPACK(b1, pos)
|
||||||
|
pcm2, = S16_UNPACK(b2, pos)
|
||||||
|
|
||||||
|
if not (pcm1 >= pcm2 - max and pcm1 <= pcm2 + max):
|
||||||
|
print("%i vs %i +- %i at %x" % (pcm1, pcm2, max, self._offset + pos))
|
||||||
|
self.fuzzy_diff = pcm1 - pcm2
|
||||||
|
self.fuzzy_offset = self._offset + pos
|
||||||
|
return RESULT_DIFFS
|
||||||
|
|
||||||
|
pos += 2
|
||||||
|
|
||||||
|
self.fuzzy_count = 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def _compare_bytes(self, b1, b2):
|
||||||
|
# even though python is much slower than C this test is reasonably fast
|
||||||
|
if b1 == b2:
|
||||||
|
return RESULT_SAME
|
||||||
|
|
||||||
|
# different: fuzzy check if same
|
||||||
|
if self._fuzzy_max:
|
||||||
|
return self._compare_fuzzy(b1, b2)
|
||||||
|
|
||||||
|
return RESULT_DIFFS
|
||||||
|
|
||||||
|
def _compare_files(self, f1, f2):
|
||||||
|
|
||||||
|
# header not part of fuzzyness (no need to get exact with sizes)
|
||||||
|
if self._fuzzy_max:
|
||||||
|
b1 = f1.read(self.CHUNK_HEADER)
|
||||||
|
b2 = f2.read(self.CHUNK_HEADER)
|
||||||
|
cmp = self._compare_bytes(b1, b2)
|
||||||
|
if cmp < 0:
|
||||||
|
return cmp
|
||||||
|
self._offset += self.CHUNK_HEADER
|
||||||
|
|
||||||
|
while True:
|
||||||
|
b1 = f1.read(self.CHUNK_SIZE)
|
||||||
|
b2 = f2.read(self.CHUNK_SIZE)
|
||||||
|
if not b1 or not b2:
|
||||||
|
break
|
||||||
|
|
||||||
|
cmp = self._compare_bytes(b1, b2)
|
||||||
|
if cmp < 0:
|
||||||
|
return cmp
|
||||||
|
self._offset += self.CHUNK_SIZE
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def compare(self):
|
||||||
|
try:
|
||||||
|
f1_len = os.path.getsize(self._path1)
|
||||||
|
except FileNotFoundError:
|
||||||
|
f1_len = -1
|
||||||
|
try:
|
||||||
|
f2_len = os.path.getsize(self._path2)
|
||||||
|
except FileNotFoundError:
|
||||||
|
f2_len = -1
|
||||||
|
|
||||||
|
if f1_len < 0 and f2_len < 0:
|
||||||
|
return RESULT_NONE
|
||||||
|
|
||||||
|
if f1_len < 0:
|
||||||
|
return RESULT_MISSING_NEW
|
||||||
|
if f2_len < 0:
|
||||||
|
return RESULT_MISSING_OLD
|
||||||
|
|
||||||
|
if f1_len != f2_len:
|
||||||
|
return RESULT_SIZES
|
||||||
|
|
||||||
|
with open(self._path1, 'rb') as f1, open(self._path2, 'rb') as f2:
|
||||||
|
cmp = self._compare_files(f1, f2)
|
||||||
|
if cmp < 0:
|
||||||
|
return cmp
|
||||||
|
|
||||||
|
if self.fuzzy_count > 0:
|
||||||
|
return RESULT_FUZZY
|
||||||
|
return RESULT_SAME
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
# prints colored text in CLI
|
||||||
|
# https://pkg.go.dev/github.com/whitedevops/colors
|
||||||
|
# https://stackoverflow.com/questions/287871/
|
||||||
|
class VrtsPrinter:
|
||||||
|
RESET = '\033[0m'
|
||||||
|
BOLD = '\033[1m'
|
||||||
|
|
||||||
|
LIGHT_RED = '\033[91m'
|
||||||
|
LIGHT_GREEN = '\033[92m'
|
||||||
|
LIGHT_YELLOW = '\033[93m'
|
||||||
|
LIGHT_BLUE = '\033[94m'
|
||||||
|
LIGHT_CYAN = '\033[96m'
|
||||||
|
WHITE = '\033[97m'
|
||||||
|
LIGHT_GRAY = "\033[37m"
|
||||||
|
DARK_GRAY = "\033[90m"
|
||||||
|
|
||||||
|
COLOR_RESULT = {
|
||||||
|
RESULT_SAME: WHITE,
|
||||||
|
RESULT_FUZZY: LIGHT_GREEN,
|
||||||
|
RESULT_NONE: LIGHT_YELLOW,
|
||||||
|
RESULT_DIFFS: LIGHT_RED,
|
||||||
|
RESULT_SIZES: LIGHT_RED,
|
||||||
|
RESULT_MISSING_NEW: LIGHT_RED,
|
||||||
|
RESULT_MISSING_OLD: LIGHT_YELLOW,
|
||||||
|
}
|
||||||
|
TEXT_RESULT = {
|
||||||
|
RESULT_SAME: 'same',
|
||||||
|
RESULT_FUZZY: 'fuzzy same',
|
||||||
|
RESULT_NONE: 'neither works',
|
||||||
|
RESULT_DIFFS: 'diffs',
|
||||||
|
RESULT_SIZES: 'wrong sizes',
|
||||||
|
RESULT_MISSING_NEW: 'missing new',
|
||||||
|
RESULT_MISSING_OLD: 'missing old',
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
try:
|
||||||
|
os.system('color') #win only?
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _print(self, msg, color=None):
|
||||||
|
if color:
|
||||||
|
print("%s%s%s" % (color, msg, self.RESET))
|
||||||
|
else:
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def result(self, msg, code, fuzzy_diff, fuzzy_offset):
|
||||||
|
text = self.TEXT_RESULT.get(code)
|
||||||
|
color = self.COLOR_RESULT.get(code)
|
||||||
|
if not text:
|
||||||
|
text = code
|
||||||
|
msg = "%s: %s" % (msg, text)
|
||||||
|
if fuzzy_diff != 0:
|
||||||
|
msg += " (%s @0x%x)" % (fuzzy_diff, fuzzy_offset)
|
||||||
|
self._print(msg, color)
|
||||||
|
|
||||||
|
|
||||||
|
def info(self, msg):
|
||||||
|
msg = "%s (%s)" % (msg, self._get_date())
|
||||||
|
self._print(msg, self.DARK_GRAY)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _get_date(self):
|
||||||
|
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
class VrtsFiles:
|
||||||
|
def __init__(self, args):
|
||||||
|
self._args = args
|
||||||
|
self.filenames = []
|
||||||
|
|
||||||
|
def prepare(self):
|
||||||
|
for fpattern in self._args.files:
|
||||||
|
|
||||||
|
recursive = self._args.recursive
|
||||||
|
if recursive:
|
||||||
|
fpattern = '**/' + fpattern
|
||||||
|
|
||||||
|
files = glob.glob(fpattern, recursive=recursive)
|
||||||
|
for file in files:
|
||||||
|
if not os.path.isfile(file):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ignores non useful files
|
||||||
|
_, ext = os.path.splitext(file)
|
||||||
|
if ext.lower() in IGNORED_EXTENSIONS:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.filenames.append(file)
|
||||||
|
|
||||||
|
# same file N times
|
||||||
|
if self._args.performance and self._args.performance_repeat:
|
||||||
|
for i in range(self._args.performance_repeat):
|
||||||
|
self.filenames.append(file)
|
||||||
|
|
||||||
|
|
||||||
|
# calling subprocess with python:
|
||||||
|
# - os.system(command)
|
||||||
|
# - not recommended by docs (less flexible and spawns a new process?)
|
||||||
|
# - subprocess.call
|
||||||
|
# - wait till complete and returns code
|
||||||
|
# - subprocess.check_call
|
||||||
|
# - wait till complete and raise CalledProcessError on nonzero return code
|
||||||
|
# - subprocess.check_output
|
||||||
|
# - call without wait, raise CalledProcessError on nonzero return code
|
||||||
|
# - subprocess.run
|
||||||
|
# - recommended but python 3.5+
|
||||||
|
# (check=True: raise exceptions like check_*, capture_output: return STDOUT/STDERR)
|
||||||
|
class VrtsProcess:
|
||||||
|
|
||||||
|
def call(self, args, stdout=False):
|
||||||
|
try:
|
||||||
|
#with open(os.devnull, 'wb') as DEVNULL: #python2
|
||||||
|
# res = subprocess.check_call(args, stdout=DEVNULL, stderr=DEVNULL)
|
||||||
|
|
||||||
|
res = subprocess.run(args, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) #capture_output=stdout,
|
||||||
|
#print("result:", res.returncode)
|
||||||
|
#print("result:", res.strout, res.strerr)
|
||||||
|
|
||||||
|
if stdout:
|
||||||
|
return res.stdout
|
||||||
|
|
||||||
|
return True #exists and returns ok
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
#print("call error: ", e) #, e.stderr: disable DEVNULL
|
||||||
|
return False #non-zero, exists but returns strerr (ex. ran with no args)
|
||||||
|
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
#print("file error: ", e)
|
||||||
|
return None #doesn't exists/etc
|
||||||
|
|
||||||
|
|
||||||
|
class VrtsApp:
|
||||||
|
|
||||||
|
def __init__(self, args):
|
||||||
|
self._args = args
|
||||||
|
self._files = VrtsFiles(args)
|
||||||
|
self._prc = VrtsProcess()
|
||||||
|
self._p = VrtsPrinter()
|
||||||
|
self._cli_new = None
|
||||||
|
self._cli_old = None
|
||||||
|
|
||||||
|
def _find_cli(self, arg_cli, default_cli):
|
||||||
|
|
||||||
|
if arg_cli and os.path.isdir(arg_cli):
|
||||||
|
cli = os.path.join(arg_cli, default_cli)
|
||||||
|
|
||||||
|
elif arg_cli: #is file
|
||||||
|
cli = arg_cli
|
||||||
|
|
||||||
|
else:
|
||||||
|
cli = default_cli
|
||||||
|
|
||||||
|
args = [cli] #plain call to see if program is in PATH
|
||||||
|
res = self._prc.call(args)
|
||||||
|
if res is not None:
|
||||||
|
return cli
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
# detects CLI location:
|
||||||
|
# - defaults to (cli) [new] + (cli)_old [old] assumed to be in PATH
|
||||||
|
# - can be passed a dir or file for old/new
|
||||||
|
# - old is optional in performance mode
|
||||||
|
def _detect_cli(self):
|
||||||
|
cli = self._find_cli(self._args.cli_new, DEFAULT_CLI_NEW)
|
||||||
|
if cli:
|
||||||
|
self._cli_new = cli
|
||||||
|
|
||||||
|
cli = self._find_cli(self._args.cli_old, DEFAULT_CLI_OLD)
|
||||||
|
if cli:
|
||||||
|
self._cli_old = cli
|
||||||
|
|
||||||
|
if not self._cli_new and (self._args.compare or self._args.performance_new):
|
||||||
|
raise ValueError("new CLI not found")
|
||||||
|
if not self._cli_old and (self._args.compare or self._args.performance_old):
|
||||||
|
raise ValueError("old CLI not found")
|
||||||
|
|
||||||
|
def _get_performance_args(self, cli):
|
||||||
|
args = [cli, '-O'] #flag to not write files
|
||||||
|
if self._args.looping:
|
||||||
|
args.append('-i')
|
||||||
|
args.extend(self._files.filenames)
|
||||||
|
return args
|
||||||
|
|
||||||
|
def _performance(self):
|
||||||
|
flag_looping = ''
|
||||||
|
if self._args.looping:
|
||||||
|
flag_looping = '-i'
|
||||||
|
|
||||||
|
# pases all files at once, as it's faster than 1 by 1 (that has to init program every time)
|
||||||
|
if self._performance_new:
|
||||||
|
self._p.info("testing new performance")
|
||||||
|
ts_st = time.time()
|
||||||
|
|
||||||
|
args = self._get_performance_args(self._cli_new)
|
||||||
|
res = self._prc.call(args)
|
||||||
|
|
||||||
|
ts_ed = time.time()
|
||||||
|
self._p.info("done: elapsed %ss" % (ts_ed - ts_st))
|
||||||
|
|
||||||
|
if self._performance_old:
|
||||||
|
self._p.info("testing old performance")
|
||||||
|
ts_st = time.time()
|
||||||
|
|
||||||
|
args = self._get_performance_args(self._cli_old)
|
||||||
|
res = self._prc.call(args)
|
||||||
|
|
||||||
|
ts_ed = time.time()
|
||||||
|
self._p.info("done: elapsed %ss (%s)" % (ts_ed - ts_st))
|
||||||
|
|
||||||
|
#if self._performance_both:
|
||||||
|
# ...
|
||||||
|
|
||||||
|
|
||||||
|
# returns max fuzzy count, except for non-fuzzable files (that use int math)
|
||||||
|
def _get_fuzzy_count(self, stdout):
|
||||||
|
fuzzy = self._args.fuzzy
|
||||||
|
if self._args.fuzzy <= 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if not stdout:
|
||||||
|
return fuzzy
|
||||||
|
|
||||||
|
try:
|
||||||
|
pos = stdout.index(b'encoding:')
|
||||||
|
codec_line = stdout[0:].split('\n', 1)[0]
|
||||||
|
for fuzzy_codec in FUZZY_CODECS:
|
||||||
|
if fuzzy_codec in codec_line:
|
||||||
|
return fuzzy
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return 0 #non-fuzable
|
||||||
|
|
||||||
|
def _get_compare_args(self, cli, outwav, filename):
|
||||||
|
args = [cli, '-o', outwav] #flag to not write files
|
||||||
|
if self._args.looping:
|
||||||
|
args.append('-i')
|
||||||
|
args.append(filename)
|
||||||
|
return args
|
||||||
|
|
||||||
|
def _compare(self):
|
||||||
|
ts_st = time.time()
|
||||||
|
self._p.info("comparing files")
|
||||||
|
|
||||||
|
flag_looping = ''
|
||||||
|
if self._args.looping:
|
||||||
|
flag_looping = '-i'
|
||||||
|
|
||||||
|
total_ok = 0
|
||||||
|
total_ko = 0
|
||||||
|
for filename in self._files.filenames:
|
||||||
|
filename_newwav = filename + ".new.wav"
|
||||||
|
filename_oldwav = filename + ".old.wav"
|
||||||
|
|
||||||
|
# main decode (ignores errors, comparator already checks them)
|
||||||
|
args = self._get_compare_args(self._cli_new, filename_newwav, filename)
|
||||||
|
stdout = self._prc.call(args, stdout=True)
|
||||||
|
|
||||||
|
args = self._get_compare_args(self._cli_old, filename_oldwav, filename)
|
||||||
|
self._prc.call(args, stdout=False)
|
||||||
|
|
||||||
|
# test results
|
||||||
|
fuzzy = self._get_fuzzy_count(stdout)
|
||||||
|
cmp = VrtsComparator(filename_newwav, filename_oldwav, fuzzy)
|
||||||
|
code = cmp.compare()
|
||||||
|
|
||||||
|
self._p.result(filename, code, cmp.fuzzy_diff, cmp.fuzzy_offset)
|
||||||
|
|
||||||
|
if code < 0:
|
||||||
|
total_ko += 1
|
||||||
|
else:
|
||||||
|
total_ok += 1
|
||||||
|
|
||||||
|
# post cleanup
|
||||||
|
if not self._args.no_delete:
|
||||||
|
try:
|
||||||
|
os.remove(filename_newwav)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
os.remove(filename_oldwav)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
ts_ed = time.time()
|
||||||
|
self._p.info("done: ok=%s, ko=%s, elapsed %ss" % (total_ok, total_ko, ts_ed - ts_st))
|
||||||
|
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self._detect_cli()
|
||||||
|
self._files.prepare()
|
||||||
|
if self._args.performance:
|
||||||
|
self._performance()
|
||||||
|
else:
|
||||||
|
self._compare()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = parse_args()
|
||||||
|
if not args:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
VrtsApp(args).start()
|
||||||
|
except ValueError as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
285
cli/vrts.bat
285
cli/vrts.bat
@ -1,285 +0,0 @@
|
|||||||
@echo off
|
|
||||||
chcp 65001
|
|
||||||
REM #-------------------------------------------------------------------------
|
|
||||||
REM # VGMSTREAM REGRESSION TESTING SCRIPT
|
|
||||||
REM #
|
|
||||||
REM # Searches for files in a directory (or optionally subdirs) and compares
|
|
||||||
REM # the output of two test.exe versions, both wav and stdout, for regression
|
|
||||||
REM # testing. This creates and deletes temp files, trying to process all
|
|
||||||
REM # extensions found unless specified (except a few).
|
|
||||||
REM #
|
|
||||||
REM # Options: see below.
|
|
||||||
REM #-------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
REM #-------------------------------------------------------------------------
|
|
||||||
REM #options
|
|
||||||
REM #-------------------------------------------------------------------------
|
|
||||||
REM # -vo <exe> -vn <exe>: path to old/new exe
|
|
||||||
set OP_CMD_OLD=test_old.exe
|
|
||||||
set OP_CMD_NEW=test.exe
|
|
||||||
REM # -f <filename>: search wildcard (ex. -f "*.adx")
|
|
||||||
set OP_SEARCH="*.*"
|
|
||||||
REM # -r: recursive subfolders
|
|
||||||
set OP_RECURSIVE=
|
|
||||||
REM # -nd: don't delete compared files
|
|
||||||
set OP_NODELETE=
|
|
||||||
REM # -nc: don't report correct files
|
|
||||||
set OP_NOCORRECT=
|
|
||||||
REM # -p: performance test new (decode with new exe and no comparison done)
|
|
||||||
REM # -P: performance test new (same but also don't write file)
|
|
||||||
REM # -po: performance test old (decode with new old and no comparison done)
|
|
||||||
REM # -Po: performance test old (same but also don't write file)
|
|
||||||
REM # -Pm: performance test new (only parse meta)
|
|
||||||
REM # -Pmo: performance test old (only parse meta)
|
|
||||||
set OP_PERFORMANCE=
|
|
||||||
REM # -fc <exe>: file comparer
|
|
||||||
REM # Windows's FC is slow, try -fc ffc.exe (https://github.com/bnnm/vgm-tools/tree/master/misc/ffc)
|
|
||||||
set OP_CMD_FC=fc /a /b
|
|
||||||
|
|
||||||
|
|
||||||
REM # parse options
|
|
||||||
:set_options
|
|
||||||
if "%~1"=="" goto end_options
|
|
||||||
if "%~1"=="-vo" set OP_CMD_OLD=%2
|
|
||||||
if "%~1"=="-vn" set OP_CMD_NEW=%2
|
|
||||||
if "%~1"=="-f" set OP_SEARCH=%2
|
|
||||||
if "%~1"=="-r" set OP_RECURSIVE=/s
|
|
||||||
if "%~1"=="-nd" set OP_NODELETE=true
|
|
||||||
if "%~1"=="-nc" set OP_NOCORRECT=true
|
|
||||||
if "%~1"=="-p" set OP_PERFORMANCE=1
|
|
||||||
if "%~1"=="-P" set OP_PERFORMANCE=2
|
|
||||||
if "%~1"=="-po" set OP_PERFORMANCE=3
|
|
||||||
if "%~1"=="-Po" set OP_PERFORMANCE=4
|
|
||||||
if "%~1"=="-Pm" set OP_PERFORMANCE=5
|
|
||||||
if "%~1"=="-Pmo" set OP_PERFORMANCE=6
|
|
||||||
if "%~1"=="-fc" set OP_CMD_FC=%2
|
|
||||||
shift
|
|
||||||
goto set_options
|
|
||||||
:end_options
|
|
||||||
|
|
||||||
REM # output color defs
|
|
||||||
set COLOR_WN=0e
|
|
||||||
set COLOR_ER=0c
|
|
||||||
set COLOR_OK=0f
|
|
||||||
|
|
||||||
REM # remove command options and possibly "
|
|
||||||
for /f "tokens=1-1 delims= " %%A in ("%OP_CMD_OLD%") do set CHECK_OLD=%%A
|
|
||||||
for /f "tokens=1-1 delims= " %%A in ("%OP_CMD_NEW%") do set CHECK_NEW=%%A
|
|
||||||
set CHECK_OLD=%CHECK_OLD:"=%
|
|
||||||
set CHECK_NEW=%CHECK_NEW:"=%
|
|
||||||
|
|
||||||
REM # check exe
|
|
||||||
set CMD_CHECK=where "%CHECK_OLD%" "%CHECK_NEW%"
|
|
||||||
%CMD_CHECK% > nul
|
|
||||||
if %ERRORLEVEL% NEQ 0 (
|
|
||||||
echo Old/new exe not found
|
|
||||||
goto error
|
|
||||||
)
|
|
||||||
|
|
||||||
if %OP_SEARCH%=="" (
|
|
||||||
echo Search wildcard not specified
|
|
||||||
goto error
|
|
||||||
)
|
|
||||||
|
|
||||||
REM # process start
|
|
||||||
set TIME_START=%time%
|
|
||||||
set FILES_OK=0
|
|
||||||
set FILES_KO=0
|
|
||||||
echo VRTS: start @%TIME_START%
|
|
||||||
|
|
||||||
REM # search for files
|
|
||||||
set CMD_DIR=dir /a:-d /b %OP_RECURSIVE% %OP_SEARCH%
|
|
||||||
set CMD_FIND=findstr /i /v "\.exe$ \.dll$ \.zip$ \.7z$ \.rar$ \.bat$ \.sh$ \.txt$ \.lnk$ \.wav$"
|
|
||||||
|
|
||||||
REM # process files
|
|
||||||
for /f "delims=" %%x in ('%CMD_DIR% ^| %CMD_FIND%') do (
|
|
||||||
set CMD_FILE=%%x
|
|
||||||
|
|
||||||
REM # must enable/disable after setting CMD as ! & in dir names would become expanded/removed otherwise
|
|
||||||
setlocal EnableDelayedExpansion
|
|
||||||
|
|
||||||
if "%OP_PERFORMANCE%" == "" (
|
|
||||||
call :process_file "!CMD_FILE!"
|
|
||||||
) else (
|
|
||||||
call :performance_file "!CMD_FILE!"
|
|
||||||
)
|
|
||||||
|
|
||||||
REM this doesn't seem to properly disable it and will error out after N files
|
|
||||||
REM but using endlocal will remove vars and counters won't work
|
|
||||||
REM setlocal DisableDelayedExpansion
|
|
||||||
endlocal
|
|
||||||
)
|
|
||||||
|
|
||||||
REM # find time elapsed
|
|
||||||
set TIME_END=%time%
|
|
||||||
for /F "tokens=1-4 delims=:.," %%a in ("%TIME_START%") do (
|
|
||||||
set /A "TIME_START_S=(((%%a*60)+1%%b %% 100)*60+1%%c %% 100)*100+1%%d %% 100"
|
|
||||||
)
|
|
||||||
for /F "tokens=1-4 delims=:.," %%a in ("%TIME_END%") do (
|
|
||||||
set /A "TIME_END_S=(((%%a*60)+1%%b %% 100)*60+1%%c %% 100)*100+1%%d %% 100"
|
|
||||||
)
|
|
||||||
set /A TIME_ELAPSED_S=(TIME_END_S-TIME_START_S)/100
|
|
||||||
set /A TIME_ELAPSED_C=(TIME_END_S-TIME_START_S)%%100
|
|
||||||
|
|
||||||
|
|
||||||
REM # process end (ok)
|
|
||||||
echo VRTS: done @%TIME_END% (%TIME_ELAPSED_S%,%TIME_ELAPSED_C%s)
|
|
||||||
echo VRTS: ok=%FILES_OK%, ko=%FILES_KO%
|
|
||||||
|
|
||||||
goto exit
|
|
||||||
|
|
||||||
|
|
||||||
REM # ########################################################################
|
|
||||||
REM # test a single file
|
|
||||||
REM # ########################################################################
|
|
||||||
:process_file outer
|
|
||||||
REM # ignore files starting with dot (no filename)
|
|
||||||
set CMD_SHORTNAME=%~n1
|
|
||||||
if "%CMD_SHORTNAME%" == "" goto process_file_continue
|
|
||||||
|
|
||||||
|
|
||||||
REM # get file (not from arg %1)
|
|
||||||
set CMD_FILE=!CMD_FILE!
|
|
||||||
set CMD_FILE=!CMD_FILE:"=!
|
|
||||||
REM echo VTRS: file !CMD_FILE!
|
|
||||||
|
|
||||||
REM # old/new temp output
|
|
||||||
set WAV_OLD=!CMD_FILE!.old.wav
|
|
||||||
set TXT_OLD=!CMD_FILE!.old.txt
|
|
||||||
set CMD_VGM_OLD="%OP_CMD_OLD%" -o "!WAV_OLD!" "!CMD_FILE!"
|
|
||||||
!CMD_VGM_OLD! 1> "!TXT_OLD!" 2>&1 & REM || goto error
|
|
||||||
|
|
||||||
set WAV_NEW=!CMD_FILE!.new.wav
|
|
||||||
set TXT_NEW=!CMD_FILE!.new.txt
|
|
||||||
set CMD_VGM_NEW="%OP_CMD_NEW%" -o "!WAV_NEW!" "!CMD_FILE!"
|
|
||||||
!CMD_VGM_NEW! 1> "!TXT_NEW!" 2>&1 & REM || goto error
|
|
||||||
|
|
||||||
REM # ignore if no files are created (unsupported formats)
|
|
||||||
if not exist "!WAV_NEW!" (
|
|
||||||
if not exist "!WAV_OLD!" (
|
|
||||||
REM echo VRTS: nothing created for file !CMD_FILE!
|
|
||||||
if exist "!TXT_NEW!" del /a:a "!TXT_NEW!"
|
|
||||||
if exist "!TXT_OLD!" del /a:a "!TXT_OLD!"
|
|
||||||
goto process_file_continue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
REM # compare files (without /b may to be faster for small files?)
|
|
||||||
set CMP_WAV=%OP_CMD_FC% "!WAV_OLD!" "!WAV_NEW!"
|
|
||||||
set CMP_TXT=%OP_CMD_FC% "!TXT_OLD!" "!TXT_NEW!"
|
|
||||||
|
|
||||||
!CMP_WAV! 1> nul 2>&1
|
|
||||||
set CMP_WAV_ERROR=0
|
|
||||||
if %ERRORLEVEL% NEQ 0 set CMP_WAV_ERROR=1
|
|
||||||
|
|
||||||
!CMP_TXT! 1> nul 2>&1
|
|
||||||
set CMP_TXT_ERROR=0
|
|
||||||
if %ERRORLEVEL% NEQ 0 set CMP_TXT_ERROR=1
|
|
||||||
|
|
||||||
REM # print output
|
|
||||||
if %CMP_WAV_ERROR% EQU 1 (
|
|
||||||
if %CMP_TXT_ERROR% EQU 1 (
|
|
||||||
call :echo_color %COLOR_ER% "!CMD_FILE!" "wav and txt diffs"
|
|
||||||
) else (
|
|
||||||
call :echo_color %COLOR_ER% "!CMD_FILE!" "wav diffs"
|
|
||||||
)
|
|
||||||
set /a FILES_KO=FILES_KO + 1
|
|
||||||
) else (
|
|
||||||
if %CMP_TXT_ERROR% EQU 1 (
|
|
||||||
call :echo_color %COLOR_WN% "!CMD_FILE!" "txt diffs"
|
|
||||||
) else (
|
|
||||||
if "%OP_NOCORRECT%" == "" (
|
|
||||||
call :echo_color %COLOR_OK% "!CMD_FILE!" "no diffs"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
set /a FILES_OK=FILES_OK + 1
|
|
||||||
)
|
|
||||||
|
|
||||||
REM # delete temp files
|
|
||||||
if "%OP_NODELETE%" == "" (
|
|
||||||
if exist "!WAV_OLD!" del /a:a "!WAV_OLD!"
|
|
||||||
if exist "!TXT_OLD!" del /a:a "!TXT_OLD!"
|
|
||||||
if exist "!WAV_NEW!" del /a:a "!WAV_NEW!"
|
|
||||||
if exist "!TXT_NEW!" del /a:a "!TXT_NEW!"
|
|
||||||
)
|
|
||||||
|
|
||||||
:process_file_continue
|
|
||||||
exit /B
|
|
||||||
REM :process_file end, continue from last call
|
|
||||||
|
|
||||||
|
|
||||||
REM # ########################################################################
|
|
||||||
REM # decode only (no comparisons done), for performance testing
|
|
||||||
REM # ########################################################################
|
|
||||||
:performance_file outer
|
|
||||||
REM # ignore files starting with dot (no filename)
|
|
||||||
set CMD_SHORTNAME=%~n1
|
|
||||||
if "%CMD_SHORTNAME%" == "" goto performance_file_continue
|
|
||||||
|
|
||||||
REM # get file (not from arg %1)
|
|
||||||
set CMD_FILE=!CMD_FILE!
|
|
||||||
set CMD_FILE=!CMD_FILE:"=!
|
|
||||||
REM echo VTRS: file !CMD_FILE!
|
|
||||||
|
|
||||||
set WAV_NEW=!CMD_FILE!.test.wav
|
|
||||||
if "%OP_PERFORMANCE%" == "1" (
|
|
||||||
set CMD_VGM="%OP_CMD_NEW%" -o "!WAV_NEW!" "!CMD_FILE!"
|
|
||||||
)
|
|
||||||
if "%OP_PERFORMANCE%" == "2" (
|
|
||||||
set CMD_VGM="%OP_CMD_NEW%" -O "!CMD_FILE!"
|
|
||||||
)
|
|
||||||
if "%OP_PERFORMANCE%" == "3" (
|
|
||||||
set CMD_VGM="%OP_CMD_OLD%" -o "!WAV_OLD!" "!CMD_FILE!"
|
|
||||||
)
|
|
||||||
if "%OP_PERFORMANCE%" == "4" (
|
|
||||||
set CMD_VGM="%OP_CMD_OLD%" -O "!CMD_FILE!"
|
|
||||||
)
|
|
||||||
if "%OP_PERFORMANCE%" == "5" (
|
|
||||||
set CMD_VGM="%OP_CMD_NEW%" -m "!CMD_FILE!"
|
|
||||||
)
|
|
||||||
if "%OP_PERFORMANCE%" == "6" (
|
|
||||||
set CMD_VGM="%OP_CMD_OLD%" -m "!CMD_FILE!"
|
|
||||||
)
|
|
||||||
|
|
||||||
!CMD_VGM! 1> nul 2>&1 & REM || goto error
|
|
||||||
set CMP_WAV_ERROR=0
|
|
||||||
if %ERRORLEVEL% NEQ 0 set CMP_WAV_ERROR=1
|
|
||||||
|
|
||||||
call :echo_color %COLOR_OK% "!CMD_FILE!" "done"
|
|
||||||
|
|
||||||
REM # ignore output
|
|
||||||
if exist "!WAV_NEW!" del /a:a "!WAV_NEW!"
|
|
||||||
|
|
||||||
:performance_file_continue
|
|
||||||
exit /B
|
|
||||||
REM :performance_file end, continue from last call
|
|
||||||
|
|
||||||
|
|
||||||
REM # ########################################################################
|
|
||||||
REM # hack to get colored output in Windows CMD using findstr + temp file
|
|
||||||
REM # ########################################################################
|
|
||||||
:echo_color
|
|
||||||
REM # don't use %2 but !CMD_FILE!
|
|
||||||
set TEMP_FILE=!CMD_FILE!-result
|
|
||||||
set TEMP_FILE=!TEMP_FILE:"=!
|
|
||||||
set TEMP_TEXT=%3
|
|
||||||
set TEMP_TEXT=!TEMP_TEXT:"=!
|
|
||||||
echo !TEMP_TEXT! > "!TEMP_FILE!"
|
|
||||||
|
|
||||||
REM # show colored filename + any text in temp file
|
|
||||||
findstr /a:%1 /r ".*" "!TEMP_FILE!" nul
|
|
||||||
del "!TEMP_FILE!"
|
|
||||||
|
|
||||||
exit /B
|
|
||||||
REM :echo_color end, continue from last call
|
|
||||||
|
|
||||||
|
|
||||||
REM # ########################################################################
|
|
||||||
|
|
||||||
:error
|
|
||||||
echo VRTS: error
|
|
||||||
goto exit
|
|
||||||
|
|
||||||
:exit
|
|
Loading…
Reference in New Issue
Block a user