mirror of https://github.com/svaarala/duktape.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
428 lines
12 KiB
428 lines
12 KiB
#!/usr/bin/python
|
|
#
|
|
# Compile test for a lot of option combinations
|
|
#
|
|
|
|
# XXX: rewrite as nodejs and parallelize (large indices handling need bigint)
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
import datetime
|
|
import json
|
|
import random
|
|
import optparse
|
|
import subprocess
|
|
import StringIO # no need for cStringIO
|
|
|
|
#
|
|
# Test matrix helper: given a specification of combinations, count the
|
|
# total number of combinations and allow a specific combination to be
|
|
# fetched using an index. This avoids creating the combinations explicitly
|
|
# and also allows random sampling of the combination space (which can be
|
|
# very large).
|
|
#
|
|
|
|
# Select one: Select([ 1, 2, 3 ]) -> [ 1 ], [ 2 ], [ 3 ]
|
|
class Select:
|
|
val = None
|
|
|
|
def __init__(self, val):
|
|
self.val = val
|
|
|
|
# Combine: Combine([ 1, 2 ], 'foo') -> [ 1 'foo' ], [ 2 'foo' ]
|
|
class Combine:
|
|
val = None
|
|
|
|
def __init__(self, val):
|
|
self.val = val
|
|
|
|
# Subset: Subset([ 'foo', 'bar' ]) -> Combine([ [ '', 'foo' ], [ '', 'bar' ] ])
|
|
# -> [ '' '' ], [ 'foo' '' ], [ '' 'bar' ], [ 'foo' 'bar' ]
|
|
class Subset:
|
|
val = None
|
|
|
|
def __init__(self, val):
|
|
self.val = val
|
|
|
|
# Sequence: Sequence([ 'foo', 'bar', 'quux' ]) -> [ 'foo', 'bar', 'quux' ]
|
|
# Plain list is also interpreted as a Sequence.
|
|
class Sequence:
|
|
val = None
|
|
|
|
def __init__(self, val):
|
|
self.val = val
|
|
|
|
# Prepare a combination lookup structure.
|
|
def prepcomb(val):
|
|
if isinstance(val, (str, unicode)):
|
|
return { 'size': 1, 'value': val, 'type': 'terminal' }
|
|
if isinstance(val, Sequence):
|
|
return { 'size': 1, 'value': val.val, 'type': 'sequence' }
|
|
if isinstance(val, list):
|
|
# interpret as Sequence
|
|
return { 'size': 1, 'value': val, 'type': 'sequence' }
|
|
if isinstance(val, Select):
|
|
nodes = []
|
|
size = 0
|
|
for i in val.val:
|
|
node = prepcomb(i)
|
|
nodes.append(node)
|
|
size += node['size']
|
|
return { 'size': size, 'value': nodes, 'type': 'select' }
|
|
if isinstance(val, Combine):
|
|
nodes = []
|
|
size = 1
|
|
for i in val.val:
|
|
node = prepcomb(i)
|
|
nodes.append(node)
|
|
size *= node['size']
|
|
return { 'size': size, 'value': nodes, 'type': 'combine' }
|
|
if isinstance(val, Subset):
|
|
nodes = []
|
|
size = 1
|
|
for i in val.val:
|
|
node = prepcomb(i)
|
|
nodes.append(node)
|
|
size *= (node['size'] + 1) # value or not present
|
|
return { 'size': size, 'value': nodes, 'type': 'subset' }
|
|
raise Exception('invalid argument')
|
|
|
|
# Return number of combinations for input lists.
|
|
def countcombinations(prepped):
|
|
return prepped['size']
|
|
|
|
# Return a combination for index, for index in [0,countcombinations(lists)[.
|
|
# This allows random selection of combinations using a PRNG.
|
|
def getcomb(prepped, index):
|
|
if prepped['type'] == 'terminal':
|
|
return [ prepped['value'] ], index
|
|
if prepped['type'] == 'sequence':
|
|
return prepped['value'], index
|
|
if prepped['type'] == 'select':
|
|
idx = index % prepped['size']
|
|
index = index / prepped['size']
|
|
|
|
for i in prepped['value']:
|
|
if idx >= i['size']:
|
|
idx -= i['size']
|
|
continue
|
|
ret, ign_index = getcomb(i, idx)
|
|
return ret, index
|
|
|
|
raise Exception('should not be here')
|
|
if prepped['type'] == 'combine':
|
|
ret = []
|
|
for i in prepped['value']:
|
|
idx = index % i['size']
|
|
index = index / i['size']
|
|
tmp, tmp_index = getcomb(i, idx)
|
|
ret.append(tmp)
|
|
return ret, index
|
|
if prepped['type'] == 'subset':
|
|
ret = []
|
|
for i in prepped['value']:
|
|
idx = index % (i['size'] + 1)
|
|
index = index / (i['size'] + 1)
|
|
if idx == 0:
|
|
# no value
|
|
ret.append('')
|
|
else:
|
|
tmp, tmp_index = getcomb(i, idx - 1)
|
|
ret.append(tmp)
|
|
return ret, index
|
|
raise Exception('invalid prepped value')
|
|
|
|
def flatten(v):
|
|
if isinstance(v, (str, unicode)):
|
|
return [ v ]
|
|
if isinstance(v, list):
|
|
ret = []
|
|
for i in v:
|
|
ret += flatten(i)
|
|
return ret
|
|
raise Exception('invalid value')
|
|
|
|
|
|
def getcombination(val, index):
|
|
res, res_index = getcomb(val, index)
|
|
if res_index != 0:
|
|
sys.stderr.write('WARNING: index not consumed entirely, invalid index? (input index %d, output index %d)\n' % (index, res_index))
|
|
|
|
return res
|
|
|
|
# Generate all combinations.
|
|
def getcombinations(val):
|
|
res = []
|
|
for i in xrange(countcombinations(val)):
|
|
res.append(getcombination(val, i))
|
|
return res
|
|
|
|
#
|
|
# Test matrix
|
|
#
|
|
|
|
def create_matrix():
|
|
gcc_language_options = Select([
|
|
'',
|
|
#'-std=c99',
|
|
[ '-std=c99', '-pedantic' ]
|
|
])
|
|
gxx_language_options = Select([
|
|
'',
|
|
'-std=c++98',
|
|
'-std=c++11',
|
|
'-std=c++1y',
|
|
'-std=gnu++1y'
|
|
])
|
|
|
|
gcc_gxx_debug_options = Select([
|
|
'',
|
|
[ '-g', '-ggdb' ]
|
|
])
|
|
gcc_gxx_warning_options = Select([
|
|
'',
|
|
#'-Wall',
|
|
[ '-Wall', '-Wextra' ] # FIXME
|
|
# [ '-Wall', '-Wextra', '-Werror' ]
|
|
])
|
|
gcc_gxx_optimization_options = Select([
|
|
#'-O0',
|
|
#'-O1',
|
|
#'-O2',
|
|
|
|
# -O3 and -O4 produces spurious warnings on gcc 4.8.1, e.g. "error: assuming signed overflow does not occur when assuming that (X - c) > X is always false [-Werror=strict-overflow]"
|
|
# Not sure what causes these, but perhaps GCC converts signed comparisons into subtractions and then runs into: https://gcc.gnu.org/wiki/FAQ#signed_overflow
|
|
|
|
[ '-O3', '-fno-strict-overflow' ],
|
|
#'-O3'
|
|
|
|
#[ '-O4', '-fno-strict-overflow' ],
|
|
#'-O4'
|
|
'-Os'
|
|
])
|
|
|
|
clang_language_options = Select([
|
|
'',
|
|
'-std=c99'
|
|
])
|
|
clang_debug_options = Select([
|
|
'',
|
|
[ '-g', '-ggdb' ]
|
|
])
|
|
clang_warning_options = Select([
|
|
'',
|
|
[ '-Wall', '-Wextra' ] # FIXME
|
|
#[ '-Wall', '-Wextra', '-Werror' ]
|
|
])
|
|
clang_optimization_options = Select([
|
|
#'-O0'
|
|
#'-O1',
|
|
#'-O2',
|
|
'-O3',
|
|
#'-O4',
|
|
'-Os'
|
|
])
|
|
|
|
# Feature options in suitable chunks that can be subsetted arbitrarilt.
|
|
|
|
duktape_options = Subset([
|
|
Select([ '-DDUK_OPT_NO_REFERENCE_COUNTING',
|
|
'-DDUK_OPT_NO_MARK_AND_SWEEP',
|
|
'-DDUK_OPT_GC_TORTURE' ]),
|
|
'-DDUK_OPT_NO_VOLUNTARY_GC',
|
|
'-DDUK_OPT_SEGFAULT_ON_PANIC',
|
|
'-DDUK_OPT_DPRINT_COLORS',
|
|
'-DDUK_OPT_NO_PACKED_TVAL',
|
|
Select([ '', '-DDUK_OPT_FORCE_ALIGN=4', '-DDUK_OPT_FORCE_ALIGN=8' ]),
|
|
'-DDUK_OPT_DEEP_C_STACK',
|
|
'-DDUK_OPT_NO_TRACEBACKS',
|
|
'-DDUK_OPT_NO_PC2LINE',
|
|
'-DDUK_OPT_NO_VERBOSE_ERRORS',
|
|
'-DDUK_OPT_NO_MS_RESIZE_STRINGTABLE',
|
|
'-DDUK_OPT_NO_STRICT_DECL',
|
|
'-DDUK_OPT_NO_REGEXP_SUPPORT',
|
|
'-DDUK_OPT_NO_OCTAL_SUPPORT',
|
|
'-DDUK_OPT_NO_SOURCE_NONBMP',
|
|
'-DDUK_OPT_STRICT_UTF8_SOURCE',
|
|
#'-DDUK_OPT_NO_BROWSER_LIKE', # FIXME: no print()
|
|
#'-DDUK_OPT_NO_FILE_IO', # FIXME: no print()
|
|
'-DDUK_OPT_NO_SECTION_B',
|
|
'-DDUK_OPT_NO_INTERRUPT_COUNTER',
|
|
'-DDUK_OPT_NO_JX',
|
|
'-DDUK_OPT_NO_JC',
|
|
'-DDUK_OPT_NO_NONSTD_ACCESSOR_KEY_ARGUMENT',
|
|
'-DDUK_OPT_NO_NONSTD_FUNC_STMT',
|
|
'-DDUK_OPT_NONSTD_FUNC_CALLER_PROPERTY',
|
|
'-DDUK_OPT_NONSTD_FUNC_SOURCE_PROPERTY',
|
|
'-DDUK_OPT_NO_NONSTD_ARRAY_SPLICE_DELCOUNT',
|
|
'-DDUK_OPT_NO_NONSTD_ARRAY_CONCAT_TRAILER',
|
|
'-DDUK_OPT_NO_NONSTD_ARRAY_MAP_TRAILER',
|
|
'-DDUK_OPT_NO_NONSTD_JSON_ESC_U2028_U2029',
|
|
'-DDUK_OPT_NO_ES6_OBJECT_PROTO_PROPERTY',
|
|
'-DDUK_OPT_NO_ES6_OBJECT_SETPROTOTYPEOF',
|
|
'-DDUK_OPT_NO_ES6_PROXY',
|
|
'-DDUK_OPT_NO_ZERO_BUFFER_DATA',
|
|
'''-DDUK_OPT_USER_INITJS="Math.MEANING_OF_LIFE=42"''',
|
|
Select([ '', '-DDUK_OPT_SETJMP', '-DDUK_OPT_UNDERSCORE_SETJMP', '-DDUK_OPT_SIGSETJMP' ]),
|
|
'-DDUK_OPT_LIGHTFUNC_BUILTINS',
|
|
'-DDUK_OPT_ASSERTIONS',
|
|
[ '-DDUK_OPT_DEBUG', '-DDUK_OPT_DPRINT', '-DDUK_OPT_DDDPRINT' ],
|
|
'-DDUK_OPT_SELF_TESTS',
|
|
[ '-DDUK_OPT_STRTAB_CHAIN', '-DDUK_OPT_STRTAB_CHAIN_SIZE=64' ]
|
|
|
|
# XXX: 16-bit options
|
|
])
|
|
|
|
# The final command is compiler specific because e.g. include path
|
|
# and link option syntax could (in principle) differ between compilers.
|
|
|
|
gcc_cmd_matrix = Combine([
|
|
'gcc',
|
|
gcc_language_options,
|
|
gcc_gxx_debug_options,
|
|
gcc_gxx_warning_options,
|
|
gcc_gxx_optimization_options,
|
|
duktape_options,
|
|
[ '-Isrc', 'src/duktape.c', 'examples/cmdline/duk_cmdline.c', '-o', '/tmp/duk', '-lm' ]
|
|
])
|
|
|
|
gxx_cmd_matrix = Combine([
|
|
'g++',
|
|
gxx_language_options,
|
|
gcc_gxx_debug_options,
|
|
gcc_gxx_warning_options,
|
|
gcc_gxx_optimization_options,
|
|
duktape_options,
|
|
[ '-Isrc', 'src/duktape.c', 'examples/cmdline/duk_cmdline.c', '-o', '/tmp/duk', '-lm' ]
|
|
])
|
|
|
|
clang_cmd_matrix = Combine([
|
|
'clang',
|
|
clang_language_options,
|
|
clang_debug_options,
|
|
clang_warning_options,
|
|
clang_optimization_options,
|
|
duktape_options,
|
|
[ '-Isrc', 'src/duktape.c', 'examples/cmdline/duk_cmdline.c', '-o', '/tmp/duk', '-lm' ]
|
|
])
|
|
|
|
matrix = Select([ gcc_cmd_matrix, gxx_cmd_matrix, clang_cmd_matrix ])
|
|
return matrix
|
|
|
|
#
|
|
# Main
|
|
#
|
|
|
|
def check_unlink(filename):
|
|
if os.path.exists(filename):
|
|
os.unlink(filename)
|
|
|
|
def main():
|
|
# XXX: add option for testcase(s) to run?
|
|
parser = optparse.OptionParser()
|
|
parser.add_option('--count', dest='count', default='1000')
|
|
parser.add_option('--seed', dest='seed', default='default_seed_' + str(long(time.time() * 1000.0)))
|
|
parser.add_option('--out-results-json', dest='out_results_json', default='/tmp/matrix_results.json')
|
|
parser.add_option('--out-failed', dest='out_failed', default='/tmp/matrix_failed.txt')
|
|
(opts, args) = parser.parse_args()
|
|
|
|
# Avoid any optional features (like JSON or RegExps) in the test
|
|
f = open('/tmp/test.js', 'wb')
|
|
f.write('''print('Hello world', 1 + 2, Math.PI, JSON.stringify({ foo: 'bar' }))''')
|
|
f.close()
|
|
expect = 'Hello world 3 3.141592653589793 {"foo":"bar"}\n'
|
|
|
|
print('Using seed: ' + repr(opts.seed))
|
|
random.seed(opts.seed)
|
|
matrix = create_matrix()
|
|
prepped = prepcomb(matrix)
|
|
# print(json.dumps(prepped, indent=4))
|
|
# print(json.dumps(getcombinations(prepped), indent=4))
|
|
numcombinations = countcombinations(prepped)
|
|
|
|
# The number of combinations is large so do (pseudo) random
|
|
# testing over the matrix. Ideally we'd avoid re-testing the
|
|
# same combination twice, but with the matrix space in billions
|
|
# this doesn't need to be checked.
|
|
|
|
res = []
|
|
failed = []
|
|
for i in xrange(long(opts.count)):
|
|
fail = False
|
|
idx = random.randrange(0, numcombinations)
|
|
cmd = getcombination(prepped, idx)
|
|
#cmd = getcombination(prepped, idx)
|
|
compile_command = flatten(cmd)
|
|
compile_command = [ elem for elem in compile_command if elem != '' ] # remove empty strings
|
|
|
|
print('%d/%d (combination %d, count %d)' % (i + 1, long(opts.count), idx, numcombinations))
|
|
#print('%d/%d (combination %d, count %d) %s' % (i + 1, long(opts.count), idx, numcombinations, repr(compile_command)))
|
|
|
|
check_unlink('/tmp/duk')
|
|
compile_p = subprocess.Popen(compile_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
compile_stdout, compile_stderr = compile_p.communicate()
|
|
compile_exitcode = compile_p.returncode
|
|
|
|
if compile_exitcode != 0:
|
|
fail = True
|
|
else:
|
|
if not os.path.exists('/tmp/duk'):
|
|
print('*** WARNING: compile success but no /tmp/duk ***')
|
|
|
|
check_unlink('/tmp/test.out')
|
|
run_command = [ '/tmp/duk', '/tmp/test.js' ]
|
|
if fail:
|
|
run_stdout = None
|
|
run_stderr = None
|
|
run_exitcode = 1
|
|
else:
|
|
run_p = subprocess.Popen(run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
run_stdout, run_stderr = run_p.communicate()
|
|
run_exitcode = run_p.returncode
|
|
|
|
if run_exitcode != 0:
|
|
fail = True
|
|
if run_stdout != expect:
|
|
fail = True
|
|
|
|
if fail:
|
|
print('------------------------------------------------------------------------------')
|
|
print('*** FAILED: %s' % repr(compile_command))
|
|
print(' '.join(compile_command))
|
|
failed.append(' '.join(compile_command))
|
|
|
|
print('COMPILE STDOUT:')
|
|
print(compile_stdout)
|
|
print('COMPILE STDERR:')
|
|
print(compile_stderr)
|
|
print('RUN STDOUT:')
|
|
print(run_stdout)
|
|
print('RUN STDERR:')
|
|
print(run_stderr)
|
|
print('------------------------------------------------------------------------------')
|
|
|
|
res.append({
|
|
'compile_command': compile_command,
|
|
'compile_stdout': compile_stdout,
|
|
'compile_stderr': compile_stderr,
|
|
'compile_exitcode': compile_exitcode,
|
|
'run_command': run_command,
|
|
'run_stdout': run_stdout,
|
|
'run_stderr': run_stderr,
|
|
'run_exitcode': run_exitcode,
|
|
'run_expect': expect,
|
|
'success': not fail
|
|
})
|
|
|
|
|
|
f = open(opts.out_results_json, 'wb')
|
|
f.write(json.dumps(res, indent=4, sort_keys=True))
|
|
f.close()
|
|
|
|
f = open(opts.out_failed, 'wb')
|
|
f.write('\n'.join(failed) + '\n')
|
|
f.close()
|
|
|
|
if __name__ == '__main__':
|
|
main()
|
|
|