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.
 
 
 
 
 
 

1248 lines
43 KiB

#!/usr/bin/env python2
#
# Python commit test run script.
#
# Intended to work on Linux, OS X, and Windows (both Cygwin and command
# prompt). Some notes for portability:
#
# * Use os.path.join() to join paths.
#
# * Use tarfile, zipfile, etc instead of native commands to avoid awkward
# situations on Windows, e.g. we might be running from the command prompt
# and executing 'tar' which is provided by Cygwin. Paths will work very
# badly in such situations. In general, don't mix Cygwin / non-Cygwin
# commands on Windows.
#
# * Avoid Duktape Makefile targets: they sometimes depend on /tmp which
# interferes with parallel runs.
#
# * Avoid mixing Cygwin and non-Cygwin repo snapshots (and git commands),
# as there are issues with mixed use like permissions causing unintended
# diffs.
#
import os
import sys
import re
import json
import optparse
import subprocess
import time
import datetime
import traceback
import tarfile
import zipfile
import md5
#
# Parameters and option parsing, some control globals
#
# Whitelisted repos, limit to main repo for now.
repo_whitelist = [
'svaarala/duktape'
]
# Strict reponame filter.
re_reponame = re.compile(r'^[a-zA-Z0-9/-]+$')
# Parse arguments.
parser = optparse.OptionParser()
parser.add_option('--repo-full-name', dest='repo_full_name', help='Full name of repository, e.g. "svaarala/duktape"')
parser.add_option('--repo-clone-url', dest='repo_clone_url', help='Repo HTTPS clone URI, e.g. "https://github.com/svaarala/duktape.git"')
parser.add_option('--commit-name', dest='commit_name', help='Commit SHA hash or tag name')
parser.add_option('--fetch-ref', dest='fetch_ref', default=None, help='Ref to fetch before checkout out SHA (e.g. +refs/pull/NNN/head)')
parser.add_option('--context', dest='context', help='Context identifying test type, e.g. "linux-x64-ecmatest"')
parser.add_option('--temp-dir', dest='temp_dir', help='Automatic temp dir created by testclient, automatically deleted (recursively) by testclient when test is done')
parser.add_option('--repo-snapshot-dir', dest='repo_snapshot_dir', help='Directory for repo tar.gz snapshots for faster test init')
(opts, args) = parser.parse_args()
repo_full_name = opts.repo_full_name
assert(repo_full_name is not None)
repo_clone_url = opts.repo_clone_url
assert(repo_clone_url is not None)
commit_name = opts.commit_name
assert(commit_name is not None)
context = opts.context
assert(context is not None)
temp_dir = opts.temp_dir
assert(temp_dir is not None)
repo_snapshot_dir = opts.repo_snapshot_dir
assert(repo_snapshot_dir is not None)
#
# Helpers
#
def newenv(**kw):
ret = {}
for k in os.environ.keys():
ret[k] = str(os.environ[k])
for k in kw.keys():
ret[k] = str(kw[k])
#print('Final environment: %r' % ret)
return ret
def execute(cmd, env=None, catch=False, input='', dump_stdout=True, dump_stderr=True):
print(' - ' + repr(cmd))
success = True
def dump(x):
if isinstance(x, unicode):
x = x.encode('utf-8')
if len(x) == 0 or x[-1] != '\n':
x = x + '\n'
sys.stdout.write(x)
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
ret = proc.communicate(input=input)
if ret[0] != '' and dump_stdout:
dump(ret[0])
if ret[1] != '' and dump_stderr:
dump(ret[1])
if proc.returncode != 0:
if catch:
success = False
else:
raise Exception('command failed: %r' % cmd)
return {
'returncode': proc.returncode,
'stdout': ret[0],
'stderr': ret[1],
'success': success
}
def unpack_targz(fn):
print('Extracting %s to %s' % (fn, os.getcwd()))
t = tarfile.open(fn)
t.extractall()
t.close()
def unpack_zip(fn):
print('Extracting %s to %s' % (fn, os.getcwd()))
z = zipfile.ZipFile(fn, 'r')
z.extractall()
z.close()
def get_binary_size(fn):
# Pattern works for Linux and OS X.
res = execute([ 'size', fn ])
m = re.compile(r'.*?^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+).*?', re.MULTILINE | re.DOTALL).match(res['stdout'])
if m is None:
raise Exception('cannot figure out size for binary %r' % fn)
return {
'text': int(m.group(1)),
'data': int(m.group(2)),
'bss': int(m.group(3)),
'total': int(m.group(4))
}
def format_size_diff(newsz, oldsz):
return '%d %d %d (%d %d %d): %d' % (
newsz['text'], newsz['data'], newsz['bss'],
oldsz['text'], oldsz['data'], oldsz['bss'],
newsz['total'] - oldsz['total']
)
def format_size(sz):
return '%d %d %d' % (sz['text'], sz['data'], sz['bss'])
output_result_json = {}
def set_output_result(doc):
for k in doc.keys():
output_result_json[k] = doc[k]
def set_output_field(key, value):
output_result_json[key] = value
def prep(options=None, options_yaml=None):
cwd = os.getcwd()
execute([ 'rm', '-rf', os.path.join(cwd, 'prep') ])
cmd = [
'python2', os.path.join(cwd, 'tools', 'configure.py'),
'--source-directory', os.path.join(cwd, 'src-input'),
'--output-directory', os.path.join(cwd, 'prep'),
'--config-metadata', os.path.join(cwd, 'config')
]
cmd += [ '--line-directives' ]
if options is not None:
cmd += options
if options_yaml is not None:
with open(os.path.join(cwd, 'prep_options.yaml'), 'wb') as f:
f.write(options_yaml)
cmd += [ '--option-file', os.path.join(cwd, 'prep_options.yaml') ]
print('Prep options:')
execute([ 'cat', os.path.join(cwd, 'prep_options.yaml') ])
execute(cmd)
execute([ 'ls', '-l', os.path.join(cwd, 'prep') ])
#
# Test context handlers
#
def genconfig_dist_src(genconfig_opts):
cwd = os.getcwd()
execute([
'python2', os.path.join(cwd, 'tools', 'genconfig.py'),
'--metadata', os.path.join(cwd, 'config'),
'--output', os.path.join(cwd, 'dist', 'src', 'duk_config.h')
] + genconfig_opts + [
'duk-config-header'
])
def context_codepolicycheck():
return execute([ 'make', 'codepolicycheck' ], env=newenv(TRAVIS=1), catch=True)['success']
def context_helper_x64_ecmatest(env=None, genconfig_opts=[], valgrind=False):
cwd = os.getcwd()
execute([ 'make', 'dist' ])
genconfig_dist_src(genconfig_opts)
execute([ 'make', 'duk', 'runtestsdeps' ])
opts = []
if valgrind:
opts.append('--valgrind')
return execute([
'node',
os.path.join(cwd, 'runtests', 'runtests.js'),
'--prep-test-path', os.path.join(cwd, 'util', 'prep_test.py'),
'--minify-uglifyjs2', os.path.join(cwd, 'UglifyJS2', 'bin', 'uglifyjs'),
'--util-include-path', os.path.join(cwd, 'tests', 'ecmascript'),
'--known-issues', os.path.join(cwd, 'doc', 'testcase-known-issues.yaml'),
'--run-duk', '--cmd-duk', os.path.join(cwd, 'duk'),
'--num-threads', '1',
'--log-file', os.path.join(cwd, 'test.out')
] + opts + [
os.path.join(cwd, 'tests', 'ecmascript')
], env=env, catch=True)['success']
def context_linux_x64_ecmatest():
return context_helper_x64_ecmatest(env=newenv())
def context_linux_arm_ecmatest():
return context_helper_x64_ecmatest(env=newenv()) # no difference to x64 now
def context_linux_x64_ecmatest_assert():
return context_helper_x64_ecmatest(env=newenv(), genconfig_opts=[ '-DDUK_USE_ASSERTIONS' ])
def context_linux_x64_ecmatest_valgrind():
return context_helper_x64_ecmatest(env=newenv(), valgrind=True)
def context_helper_x64_apitest(env=None, genconfig_opts=[], valgrind=False):
cwd = os.getcwd()
execute([ 'make', 'dist' ])
genconfig_dist_src(genconfig_opts)
execute([ 'make', 'apiprep' ])
opts = []
if valgrind:
opts.append('--valgrind')
return execute([
'node',
os.path.join(cwd, 'runtests', 'runtests.js'),
'--prep-test-path', os.path.join(cwd, 'util', 'prep_test.py'),
'--minify-uglifyjs2', os.path.join(cwd, 'UglifyJS2', 'bin', 'uglifyjs'),
'--util-include-path', os.path.join(cwd, 'tests', 'ecmascript'),
'--known-issues', os.path.join(cwd, 'doc', 'testcase-known-issues.yaml'),
'--run-duk', '--cmd-duk', os.path.join(cwd, 'duk'),
'--num-threads', '1',
'--log-file', os.path.join(cwd, 'test.out'),
os.path.join(cwd, 'tests', 'api')
] + opts + [
], env=env, catch=True)['success']
def context_linux_x64_apitest():
return context_helper_x64_apitest(env=newenv())
def context_linux_arm_apitest():
return context_helper_x64_apitest(env=newenv()) # no difference to x64 now
def context_linux_x64_apitest_assert():
return context_helper_x64_apitest(env=newenv(), genconfig_opts=[ '-DDUK_USE_ASSERTIONS' ])
def context_linux_x64_apitest_valgrind():
return context_helper_x64_apitest(env=newenv(), valgrind=True)
def context_linux_x64_v8_bench_pass():
cwd = os.getcwd()
print('NOTE: This performance test is executed as a functional')
print('test because it also stress GC etc; the benchmark score')
print('is meaningless unless executed on dedicated hardware.')
print('')
unpack_targz(os.path.join(repo_snapshot_dir, 'google-v8-benchmark-v7.tar.gz'))
execute([ 'make', 'duk' ])
os.chdir(os.path.join(cwd, 'tests', 'google-v8-benchmark-v7'))
execute([ 'make', 'combined.js' ])
os.chdir(cwd)
execute([ os.path.join(cwd, 'duk'), os.path.join('tests', 'google-v8-benchmark-v7', 'combined.js') ])
return True
def context_linux_x64_octane():
cwd = os.getcwd()
execute([ 'make', 'duk.O2' ])
os.chdir(os.path.join(cwd, 'tests', 'octane'))
scores = []
for i in xrange(5):
res = execute([ 'make', 'test' ])
m = re.match(r'.*?SCORE (\d+)', res['stdout'], re.DOTALL)
if m is not None:
scores.append(float(m.group(1)))
print('scores so far: min=%f, max=%f, avg=%f: %r' % (min(scores), max(scores), sum(scores) / float(len(scores)), scores))
set_output_result({
'description': '%.1f (%d-%d)' % (float(sum(scores) / float(len(scores))), int(min(scores)), int(max(scores))),
'score_avg': float(sum(scores) / float(len(scores))),
'score_min': int(min(scores)),
'score_max': int(max(scores))
})
return True
def context_linux_x64_duk_clang():
cwd = os.getcwd()
execute([ 'make', 'duk-clang' ])
res = execute([
os.path.join(cwd, 'duk-clang'),
'-e', 'print("hello world!");'
])
return res['stdout'] == 'hello world!\n'
def context_linux_x64_duk_gxx():
cwd = os.getcwd()
execute([ 'make', 'duk-g++' ])
res = execute([
os.path.join(cwd, 'duk-g++'),
'-e', 'print("hello world!");'
])
return res['stdout'] == 'hello world!\n'
def context_helper_get_binary_size_diff(compfn):
cwd = os.getcwd()
execute([ 'git', 'clean', '-f' ])
execute([ 'git', 'reset', '--quiet', '--hard' ])
compfn()
newsz = get_binary_size(os.path.join(cwd, 'duk'))
execute([ 'git', 'clean', '-f' ])
execute([ 'git', 'reset', '--quiet', '--hard' ])
execute([ 'git', 'checkout', '--quiet', 'master' ])
execute([ 'git', 'clean', '-f' ])
execute([ 'git', 'reset', '--quiet', '--hard' ])
execute([ 'make', 'clean' ])
compfn()
oldsz = get_binary_size(os.path.join(cwd, 'duk'))
set_output_result({
'description': format_size_diff(newsz, oldsz),
'oldsz': oldsz,
'newsz': newsz
})
return True
def context_linux_x64_gcc_defsize_makeduk():
cwd = os.getcwd()
def comp():
execute([ 'make', 'duk' ])
return context_helper_get_binary_size_diff(comp)
def context_helper_defsize_fltoetc(archopt):
cwd = os.getcwd()
def comp():
execute([ 'make', 'dist' ])
execute([
'gcc', '-oduk', archopt,
'-Os', '-fomit-frame-pointer',
'-fno-stack-protector',
'-flto', '-fno-asynchronous-unwind-tables',
'-ffunction-sections', '-Wl,--gc-sections',
'-I' + os.path.join('dist', 'src'),
'-I' + os.path.join('dist', 'examples', 'cmdline'),
os.path.join(cwd, 'dist', 'src', 'duktape.c'),
os.path.join(cwd, 'dist', 'examples', 'cmdline', 'duk_cmdline.c'),
'-lm'
])
return context_helper_get_binary_size_diff(comp)
def context_linux_x64_gcc_defsize_fltoetc():
return context_helper_defsize_fltoetc('-m64')
def context_linux_x86_gcc_defsize_fltoetc():
return context_helper_defsize_fltoetc('-m32')
def context_linux_x32_gcc_defsize_fltoetc():
return context_helper_defsize_fltoetc('-mx32')
def context_linux_arm_gcc_defsize_fltoetc():
return context_helper_defsize_fltoetc('-marm')
def context_linux_thumb_gcc_defsize_fltoetc():
return context_helper_defsize_fltoetc('-mthumb')
def context_helper_minsize_fltoetc(archopt, strip):
cwd = os.getcwd()
def comp():
execute([ 'make', 'clean' ])
execute([ 'rm', '-rf', os.path.join(cwd, 'prep') ])
cmd = [
'python2', os.path.join(cwd, 'tools', 'configure.py'),
'--source-directory', os.path.join(cwd, 'src-input'),
'--output-directory', os.path.join(cwd, 'prep'),
'--config-metadata', os.path.join(cwd, 'config'),
'--option-file', os.path.join(cwd, 'config', 'examples', 'low_memory.yaml')
]
if strip:
cmd += [
'--option-file', os.path.join(cwd, 'config', 'examples', 'low_memory_strip.yaml'),
'--unicode-data', os.path.join(cwd, 'src-input', 'UnicodeData-8bit.txt'),
'--special-casing', os.path.join(cwd, 'src-input', 'SpecialCasing-8bit.txt')
]
execute(cmd)
execute([
'gcc', '-oduk', archopt,
'-Os', '-fomit-frame-pointer',
'-fno-stack-protector',
'-flto', '-fno-asynchronous-unwind-tables',
'-ffunction-sections', '-Wl,--gc-sections',
'-I' + os.path.join('prep'),
'-I' + os.path.join('examples', 'cmdline'),
os.path.join(cwd, 'prep', 'duktape.c'),
os.path.join(cwd, 'examples', 'cmdline', 'duk_cmdline.c'),
'-lm'
])
res = execute([
os.path.join(cwd, 'duk')
], input='1+2, "hello world!"')
return 'hello world' in res['stdout']
return context_helper_get_binary_size_diff(comp)
def context_linux_x64_gcc_minsize_fltoetc():
return context_helper_minsize_fltoetc('-m64', False)
def context_linux_x86_gcc_minsize_fltoetc():
return context_helper_minsize_fltoetc('-m32', False)
def context_linux_x32_gcc_minsize_fltoetc():
return context_helper_minsize_fltoetc('-mx32', False)
def context_linux_arm_gcc_minsize_fltoetc():
return context_helper_minsize_fltoetc('-marm', False)
def context_linux_thumb_gcc_minsize_fltoetc():
return context_helper_minsize_fltoetc('-mthumb', False)
def context_linux_x64_gcc_stripsize_fltoetc():
return context_helper_minsize_fltoetc('-m64', True)
def context_linux_x86_gcc_stripsize_fltoetc():
return context_helper_minsize_fltoetc('-m32', True)
def context_linux_x32_gcc_stripsize_fltoetc():
return context_helper_minsize_fltoetc('-mx32', True)
def context_linux_arm_gcc_stripsize_fltoetc():
return context_helper_minsize_fltoetc('-marm', True)
def context_linux_thumb_gcc_stripsize_fltoetc():
return context_helper_minsize_fltoetc('-mthumb', True)
def context_linux_x64_cpp_exceptions():
# For now rather simple: compile, run, and grep for my_class
# destruction prints. There are only 3 without C++ exceptions
# and 15 with them.
cwd = os.getcwd()
prep(options=[ '-DDUK_USE_CPP_EXCEPTIONS' ])
execute([
'g++', '-oduk-cpp-exc',
'-I' + os.path.join(cwd, 'prep'),
os.path.join(cwd, 'prep', 'duktape.c'),
os.path.join(cwd, 'examples', 'cpp-exceptions', 'cpp_exceptions.cpp'),
'-lm'
])
res = execute([
os.path.join(cwd, 'duk-cpp-exc')
])
count = 0
for line in res['stdout'].split('\n'):
if 'my_class instance destroyed' in line:
count += 1
print('Destruct count: %d' % count)
if count >= 15:
print('C++ exceptions seem to be working')
return True
else:
print('C++ exceptions don\'t seem to be working')
return False
def context_linux_x86_duklow():
cwd = os.getcwd()
execute([ 'make', 'duk-low' ])
res = execute([
os.path.join(cwd, 'duk-low'),
'-e', 'print("hello world!");'
])
return 'hello world!\n' in res['stdout']
def context_linux_x86_duklow_norefc():
cwd = os.getcwd()
execute([ 'make', 'duk-low-norefc' ])
res = execute([
os.path.join(cwd, 'duk-low-norefc'),
'-e', 'print("hello world!");'
])
return 'hello world!\n' in res['stdout']
def context_linux_x86_duklow_rombuild():
cwd = os.getcwd()
execute([ 'make', 'duk-low-rom' ])
got_hello = False
got_startrek = False
res = execute([
os.path.join(cwd, 'duk-low-rom'),
'-e', 'print("hello world!");'
])
got_hello = ('hello world!\n' in res['stdout']) # duk-low-rom stdout has pool dumps etc
print('Got hello: %r' % got_hello)
res = execute([
os.path.join(cwd, 'duk-low-rom'),
'-e', 'print("StarTrek.ent:", StarTrek.ent);'
])
got_startrek = ('StarTrek.ent: true\n' in res['stdout'])
print('Got StarTrek: %r' % got_startrek)
return got_hello and got_startrek
def context_linux_x64_test262test():
cwd = os.getcwd()
execute([ 'make', 'duk' ])
# Unpack separately, 'make clean' wipes this.
unpack_targz(os.path.join(repo_snapshot_dir, 'test262-es5-tests.tar.gz'))
unpack_zip(os.path.join(cwd, 'es5-tests.zip'))
os.chdir(os.path.join(cwd, 'test262-es5-tests'))
res = execute([
'python2',
os.path.join(cwd, 'test262-es5-tests', 'tools', 'packaging', 'test262.py'),
'--command', os.path.join(cwd, 'duk') + ' {{path}}'
], dump_stdout=False, dump_stderr=True)
test262_log = res['stdout']
os.chdir(cwd)
res = execute([
'python2',
os.path.join(cwd, 'util', 'filter_test262_log.py'),
os.path.join(cwd, 'doc', 'test262-known-issues.yaml')
], input=test262_log)
# Test result plumbing a bit awkward but works for now.
# Known and diagnosed issues are considered a "pass" for
# GitHub status.
return 'TEST262 SUCCESS\n' in res['stdout']
def context_linux_x64_duk_dddprint():
cwd = os.getcwd()
prep(options_yaml=r"""
DUK_USE_ASSERTIONS: true
DUK_USE_SELF_TESTS: true
DUK_USE_DEBUG: true
DUK_USE_DEBUG_LEVEL: 2
DUK_USE_DEBUG_WRITE:
verbatim: "#define DUK_USE_DEBUG_WRITE(level,file,line,func,msg) do {fprintf(stderr, \"%ld %s:%ld (%s): %s\\n\", (long) (level), (file), (long) (line), (func), (msg));} while(0)"
""")
res = execute([
'gcc', '-oduk',
'-DDUK_CMDLINE_PRINTALERT_SUPPORT',
'-I' + os.path.join(cwd, 'prep'),
'-I' + os.path.join(cwd, 'examples', 'cmdline'),
'-I' + os.path.join(cwd, 'extras', 'print-alert'),
os.path.join(cwd, 'prep', 'duktape.c'),
os.path.join(cwd, 'examples', 'cmdline', 'duk_cmdline.c'),
os.path.join(cwd, 'extras', 'print-alert', 'duk_print_alert.c'),
'-lm'
], catch=True)
if not res['success']:
print('Compilation failed.')
return False
res = execute([
os.path.join(cwd, 'duk'),
'-e', 'print("Hello world!");'
], dump_stderr=False)
return 'Hello world!\n' in res['stdout']
def context_linux_x64_duk_separate_src():
cwd = os.getcwd()
execute([ 'make', 'dist' ])
os.chdir(os.path.join(cwd, 'dist'))
cfiles =[]
for fn in os.listdir(os.path.join(cwd, 'dist', 'src-separate')):
if fn[-2:] == '.c':
cfiles.append(os.path.join(cwd, 'dist', 'src-separate', fn))
cfiles.append(os.path.join(cwd, 'dist', 'examples', 'cmdline', 'duk_cmdline.c'))
cfiles.append(os.path.join(cwd, 'dist', 'extras', 'print-alert', 'duk_print_alert.c'))
execute([
'gcc', '-oduk',
'-DDUK_CMDLINE_PRINTALERT_SUPPORT',
'-I' + os.path.join(cwd, 'dist', 'src-separate'),
'-I' + os.path.join(cwd, 'dist', 'examples', 'cmdline'),
'-I' + os.path.join(cwd, 'dist', 'extras', 'print-alert')
] + cfiles + [
'-lm'
])
res = execute([
os.path.join(cwd, 'dist', 'duk'),
'-e', 'print("Hello world!");'
])
return 'Hello world!\n' in res['stdout']
def context_linux_x86_packed_tval():
cwd = os.getcwd()
execute([ 'make', 'dist' ])
os.chdir(os.path.join(cwd, 'dist'))
execute([
'gcc', '-oduk', '-m32',
'-DDUK_CMDLINE_PRINTALERT_SUPPORT',
'-I' + os.path.join(cwd, 'dist', 'src'),
'-I' + os.path.join(cwd, 'dist', 'examples', 'cmdline'),
'-I' + os.path.join(cwd, 'dist', 'extras', 'print-alert'),
os.path.join(cwd, 'dist', 'src', 'duktape.c'),
os.path.join(cwd, 'dist', 'examples', 'cmdline', 'duk_cmdline.c'),
os.path.join(cwd, 'dist', 'extras', 'print-alert', 'duk_print_alert.c'),
'-lm'
])
# Size of a 3-element array is 25 + 3x16 = 73 on x64 and
# 13 + 3x8 = 37 on x86.
res = execute([
os.path.join(cwd, 'dist', 'duk'),
'-e',
'var arr = Duktape.compact([1,2,3]); ' +
'print(Duktape.info(true).itag >= 0xf000); ' + # packed internal tag
'print(Duktape.info(arr).pbytes <= 40)' # array size (1 element + .length property)
]);
return res['stdout'] == 'true\ntrue\n'
def context_linux_x86_dist_genconfig():
cwd = os.getcwd()
execute([ 'make', 'dist' ])
os.chdir(os.path.join(cwd, 'dist'))
execute([
'python2', os.path.join(cwd, 'dist', 'tools', 'genconfig.py'),
'--metadata', os.path.join(cwd, 'dist', 'config'),
'--output', os.path.join(cwd, 'dist', 'src', 'duk_config.h'), # overwrite default duk_config.h
'-DDUK_USE_FASTINT', '-UDUK_USE_JX', '-UDUK_USE_JC',
'duk-config-header'
])
os.chdir(os.path.join(cwd, 'dist'))
execute([
'gcc', '-oduk',
'-DDUK_CMDLINE_PRINTALERT_SUPPORT',
'-I' + os.path.join(cwd, 'dist', 'src'),
'-I' + os.path.join(cwd, 'dist', 'examples', 'cmdline'),
'-I' + os.path.join(cwd, 'dist', 'extras', 'print-alert'),
os.path.join(cwd, 'dist', 'src', 'duktape.c'),
os.path.join(cwd, 'dist', 'examples', 'cmdline', 'duk_cmdline.c'),
os.path.join(cwd, 'dist', 'extras', 'print-alert', 'duk_print_alert.c'),
'-lm'
])
res = execute([
os.path.join(cwd, 'dist', 'duk'),
'-e', 'try { print(Duktape.enc("jx", {})); } catch (e) { print("ERROR: " + e.name); }'
])
return 'ERROR: TypeError\n' in res['stdout']
def context_linux_x64_error_variants():
# Test Duktape build using:
# (1) verbose and non-paranoid errors
# (2) verbose and paranoid errors
# (3) non-verbose errors
cwd = os.getcwd()
retval = True
for params in [
{ 'genconfig_opts': [ '-DDUK_USE_VERBOSE_ERRORS', '-UDUK_USE_PARANOID_ERRORS' ],
'binary_name': 'duk.verbose_nonparanoid' },
{ 'genconfig_opts': [ '-DDUK_USE_VERBOSE_ERRORS', '-DDUK_USE_PARANOID_ERRORS' ],
'binary_name': 'duk.verbose_paranoid' },
{ 'genconfig_opts': [ '-UDUK_USE_VERBOSE_ERRORS', '-UDUK_USE_PARANOID_ERRORS' ],
'binary_name': 'duk.nonverbose' },
]:
os.chdir(cwd)
execute([ 'make', 'clean', 'dist' ])
os.chdir(os.path.join(cwd, 'dist'))
execute([
'python2', os.path.join(cwd, 'dist', 'tools', 'genconfig.py'),
'--metadata', os.path.join(cwd, 'dist', 'config'),
'--output', os.path.join(cwd, 'dist', 'src', 'duk_config.h') # overwrite default duk_config.h
] + params['genconfig_opts'] + [
'duk-config-header'
])
execute([
'gcc', '-o' + params['binary_name'],
'-DDUK_CMDLINE_PRINTALERT_SUPPORT',
'-I' + os.path.join(cwd, 'dist', 'src'),
'-I' + os.path.join(cwd, 'dist', 'examples', 'cmdline'),
'-I' + os.path.join(cwd, 'dist', 'extras', 'print-alert'),
os.path.join(cwd, 'dist', 'src', 'duktape.c'),
os.path.join(cwd, 'dist', 'examples', 'cmdline', 'duk_cmdline.c'),
os.path.join(cwd, 'dist', 'extras', 'print-alert', 'duk_print_alert.c'),
'-lm'
])
execute([ 'size', params['binary_name'] ])
with open('test.js', 'wb') as f:
f.write("""\
try {
(undefined).foo = 123;
} catch (e) {
print('ERRORNAME: ' + e.name);
print('ERRORMESSAGE: ' + e);
print(e.stack);
}
""")
res = execute([
os.path.join(cwd, 'dist', params['binary_name']),
'test.js'
])
if 'ERRORNAME: TypeError\n' not in res['stdout']:
print('Cannot find error name in output')
retval = False
# For now, just check that the code compiles and error Type is
# correct. XXX: add check for error message too.
return retval
def context_helper_hello_ram(archopt):
cwd = os.getcwd()
def test(genconfig_opts):
os.chdir(cwd)
execute([ 'make', 'clean' ])
execute([ 'rm', '-rf', os.path.join(cwd, 'prep') ])
cmd = [
'python2', os.path.join(cwd, 'tools', 'configure.py'),
'--source-directory', os.path.join(cwd, 'src-input'),
'--output-directory', os.path.join(cwd, 'prep'),
'--config-metadata', os.path.join(cwd, 'config'),
'--rom-support'
] + genconfig_opts
print(repr(cmd))
execute(cmd)
execute([
'gcc', '-ohello', archopt,
'-Os', '-fomit-frame-pointer',
'-fno-stack-protector',
'-flto', '-fno-asynchronous-unwind-tables',
'-ffunction-sections', '-Wl,--gc-sections',
'-I' + os.path.join('prep'),
os.path.join(cwd, 'prep', 'duktape.c'),
os.path.join(cwd, 'examples', 'hello', 'hello.c'),
'-lm'
])
execute([
'size',
os.path.join(cwd, 'hello')
])
execute([
'valgrind', '--tool=massif',
'--massif-out-file=' + os.path.join(cwd, 'massif.out'),
'--peak-inaccuracy=0.0',
os.path.join(cwd, 'hello')
])
res = execute([
'ms_print',
os.path.join(cwd, 'massif.out')
], dump_stdout=False)
lines = res['stdout'].split('\n')
print('\n'.join(lines[0:50])) # print 50 first lines only
# KB
#107.5^ :
# | @#::::@:: :::@::: : :
kb = '???'
re_kb = re.compile(r'^([0-9\.]+)\^.*?$')
for line in lines[0:10]:
m = re_kb.match(line)
if m is not None:
kb = m.group(1)
print(' --> KB: ' + kb)
return kb
print('--- Default')
print('')
kb_default = test([])
print('')
print('--- No bufferobject support')
print('')
kb_nobufobj = test([
'-UDUK_USE_BUFFEROBJECT_SUPPORT'
])
print('')
print('--- ROM built-ins, global object inherits from ROM global')
print('--- No other low memory options (fast paths, pointer compression, etc)')
print('')
kb_rom = test([
'-DDUK_USE_ROM_OBJECTS',
'-DDUK_USE_ROM_STRINGS',
'-DDUK_USE_ROM_GLOBAL_INHERIT',
'-UDUK_USE_HSTRING_ARRIDX'
])
set_output_result({
'description': '%s %s %s (kB)' % (kb_default, kb_nobufobj, kb_rom),
'kb_default': kb_default,
'kb_nobufobj': kb_nobufobj,
'kb_rom': kb_rom
})
return True
def context_linux_x64_hello_ram():
return context_helper_hello_ram('-m64')
def context_linux_x86_hello_ram():
return context_helper_hello_ram('-m32')
def context_linux_x32_hello_ram():
return context_helper_hello_ram('-mx32')
def mandel_test(archopt, genconfig_opts):
cwd = os.getcwd()
execute([ 'make', 'dist' ])
execute([
'python2', os.path.join(cwd, 'tools', 'genconfig.py'),
'--metadata', os.path.join(cwd, 'config'),
'--output', os.path.join(cwd, 'dist', 'src', 'duk_config.h')
] + genconfig_opts + [
'duk-config-header'
])
execute([
'gcc', '-oduk', archopt,
'-Os', '-fomit-frame-pointer',
'-fno-stack-protector',
'-flto', '-fno-asynchronous-unwind-tables',
'-ffunction-sections', '-Wl,--gc-sections',
'-DDUK_CMDLINE_PRINTALERT_SUPPORT',
'-I' + os.path.join('dist', 'src'),
'-I' + os.path.join(cwd, 'dist', 'examples', 'cmdline'),
'-I' + os.path.join(cwd, 'dist', 'extras', 'print-alert'),
'-I' + os.path.join('dist', 'examples', 'cmdline'),
os.path.join(cwd, 'dist', 'src', 'duktape.c'),
os.path.join(cwd, 'dist', 'examples', 'cmdline', 'duk_cmdline.c'),
os.path.join(cwd, 'dist', 'extras', 'print-alert', 'duk_print_alert.c'),
'-lm'
])
execute([ 'size', os.path.join(cwd, 'duk') ])
res = execute([
os.path.join(cwd, 'duk'),
'-e', 'print(Duktape.version); print(Duktape.env); print(Math.PI)'
])
res = execute([
os.path.join(cwd, 'duk'),
os.path.join('dist', 'mandel.js')
])
md5_stdout = md5.md5(res['stdout']).digest().encode('hex')
md5_expect = '627cd86f0a4255e018c564f86c6d0ab3'
print(md5_stdout)
print(md5_expect)
return md5_stdout == md5_expect
def context_linux_regconst_variants():
res = True
res = res and mandel_test('-m64', [ '-DDUK_USE_EXEC_REGCONST_OPTIMIZE' ])
res = res and mandel_test('-m64', [ '-UDUK_USE_EXEC_REGCONST_OPTIMIZE' ])
res = res and mandel_test('-m32', [ '-DDUK_USE_EXEC_REGCONST_OPTIMIZE' ])
res = res and mandel_test('-m32', [ '-UDUK_USE_EXEC_REGCONST_OPTIMIZE' ])
return res
def context_linux_tval_variants():
# Cover most duk_tval.h cases, but only for little endian now.
res = True
for archopt in [ '-m64', '-m32' ]:
optsets = []
optsets.append([ '-UDUK_USE_PACKED_TVAL', '-DDUK_USE_64BIT_OPS', '-DDUK_USE_FASTINT' ])
optsets.append([ '-UDUK_USE_PACKED_TVAL', '-DDUK_USE_64BIT_OPS', '-UDUK_USE_FASTINT' ])
optsets.append([ '-UDUK_USE_PACKED_TVAL', '-UDUK_USE_64BIT_OPS', '-UDUK_USE_FASTINT' ])
if archopt == '-m32':
optsets.append([ '-DDUK_USE_PACKED_TVAL', '-DDUK_USE_64BIT_OPS', '-DDUK_USE_FASTINT' ])
optsets.append([ '-DDUK_USE_PACKED_TVAL', '-DDUK_USE_64BIT_OPS', '-UDUK_USE_FASTINT' ])
optsets.append([ '-DDUK_USE_PACKED_TVAL', '-UDUK_USE_64BIT_OPS', '-UDUK_USE_FASTINT' ])
for optset in optsets:
res = res and mandel_test(archopt, optset)
return res
def context_linux_x64_minisphere():
cwd = os.getcwd()
execute([ 'make', 'dist' ])
# Unpack minisphere snapshot and copy Duktape files over.
unpack_targz(os.path.join(repo_snapshot_dir, 'minisphere-20160516.tar.gz'))
prep(options=[ '--fixup-file', os.path.join(cwd, 'minisphere', 'src', 'engine', 'duk_custom.h') ])
for i in [ 'duktape.c', 'duktape.h', 'duk_config.h' ]:
execute([
'cp',
os.path.join(cwd, 'prep', i),
os.path.join(cwd, 'minisphere', 'src', 'shared', i)
])
# sudo apt-get install liballegro5-dev libmng-dev
os.chdir(os.path.join(cwd, 'minisphere'))
return execute([ 'make' ], catch=True)['success']
def context_linux_x64_dukluv():
cwd = os.getcwd()
execute([ 'make', 'dist' ])
# Unpack dukluv snapshot and symlink dukluv/lib/duktape to dist.
unpack_targz(os.path.join(repo_snapshot_dir, 'dukluv-20160528.tar.gz'))
execute([
'mv',
os.path.join(cwd, 'dukluv', 'lib', 'duktape'),
os.path.join(cwd, 'dukluv', 'lib', 'duktape-moved')
])
execute([
'ln',
'-s',
os.path.join(cwd, 'dist'),
os.path.join(cwd, 'dukluv', 'lib', 'duktape')
])
os.chdir(os.path.join(cwd, 'dukluv'))
execute([ 'mkdir', 'build' ])
os.chdir(os.path.join(cwd, 'dukluv', 'build'))
execute([ 'cmake', '..' ])
res = execute([ 'make' ], catch=True)
if not res['success']:
print('Build failed!')
return False
# Binary is in dukluv/build/dukluv.
execute([
os.path.join(cwd, 'dukluv', 'build', 'dukluv'),
os.path.join(cwd, 'dukluv', 'test-argv.js')
])
return True
def context_linux_graph_hello_size_helper(archopt):
cwd = os.getcwd()
cmd = [
'python2', os.path.join(cwd, 'tools', 'configure.py'),
'--source-directory', os.path.join(cwd, 'src-input'),
'--output-directory', os.path.join(cwd, 'prep'),
'--config-metadata', os.path.join(cwd, 'config'),
'--option-file', os.path.join(cwd, 'config', 'examples', 'low_memory.yaml')
]
execute(cmd)
execute([
'gcc', '-ohello', archopt,
'-std=c99', '-Wall',
'-Os', '-fomit-frame-pointer',
'-flto', '-fno-asynchronous-unwind-tables',
'-ffunction-sections', '-Wl,--gc-sections',
'-fno-stack-protector',
'-I' + os.path.join('prep'),
os.path.join(cwd, 'prep', 'duktape.c'),
os.path.join(cwd, 'examples', 'hello', 'hello.c'),
'-lm'
])
sz = get_binary_size(os.path.join(cwd, 'hello'))
set_output_result({
'description': format_size(sz),
'newsz': sz
})
return True
def context_linux_x64_graph_hello_size():
return context_linux_graph_hello_size_helper('-m64')
def context_linux_x86_graph_hello_size():
return context_linux_graph_hello_size_helper('-m32')
def context_linux_x32_graph_hello_size():
return context_linux_graph_hello_size_helper('-mx32')
def context_linux_arm_graph_hello_size():
return context_linux_graph_hello_size_helper('-marm')
def context_linux_thumb_graph_hello_size():
return context_linux_graph_hello_size_helper('-mthumb')
def context_codemetrics():
def scandir(path):
count = 0
lines = 0
for fn in os.listdir(path):
count += 1
with open(os.path.join(path, fn), 'rb') as f:
data = f.read()
lines += data.count('\n') # assume trailing newline on last line
return count, lines
if os.path.exists(os.path.join('.', 'src')):
source_files, source_lines = scandir(os.path.join('.', 'src'))
else:
source_files, source_lines = scandir(os.path.join('.', 'src-input'))
ecma_test_files, ecma_test_lines = scandir(os.path.join('.', 'tests/ecmascript'))
api_test_files, api_test_lines = scandir(os.path.join('.', 'tests/api'))
set_output_result({
'source_files': source_files,
'source_lines': source_lines,
'ecma_test_files': ecma_test_files,
'ecma_test_lines': ecma_test_lines,
'api_test_files': api_test_files,
'api_test_lines': api_test_lines
})
# Counts for markers like XXX
# Repo clone size
return True
context_handlers = {
# Linux
'codepolicycheck': context_codepolicycheck,
'linux-x64-ecmatest': context_linux_x64_ecmatest,
'linux-arm-ecmatest': context_linux_arm_ecmatest,
'linux-x64-ecmatest-assert': context_linux_x64_ecmatest_assert,
'linux-x64-ecmatest-valgrind': context_linux_x64_ecmatest_valgrind,
# old names
'linux-x64-qecmatest': context_linux_x64_ecmatest,
'linux-arm-qecmatest': context_linux_arm_ecmatest,
'linux-x64-qecmatest-assert': context_linux_x64_ecmatest_assert,
'linux-x64-qecmatest-valgrind': context_linux_x64_ecmatest_valgrind,
# XXX: torture options
'linux-x64-apitest': context_linux_x64_apitest,
'linux-arm-apitest': context_linux_x64_apitest,
'linux-x64-apitest-assert': context_linux_x64_apitest_assert,
'linux-x64-apitest-valgrind': context_linux_x64_apitest_valgrind,
'linux-x64-test262test': context_linux_x64_test262test,
# XXX: torture options
# XXX: regfuzztest
# XXX: luajstest
# XXX: jsinterpretertest
# XXX: bluebirdtest
# XXX: emscripteninceptiontest
'linux-x64-duk-clang': context_linux_x64_duk_clang,
'linux-x64-duk-gxx': context_linux_x64_duk_gxx,
'linux-x64-gcc-defsize-makeduk': context_linux_x64_gcc_defsize_makeduk,
'linux-x64-gcc-defsize-fltoetc': context_linux_x64_gcc_defsize_fltoetc,
'linux-x86-gcc-defsize-fltoetc': context_linux_x86_gcc_defsize_fltoetc,
'linux-x32-gcc-defsize-fltoetc': context_linux_x32_gcc_defsize_fltoetc,
'linux-arm-gcc-defsize-fltoetc': context_linux_arm_gcc_defsize_fltoetc,
'linux-thumb-gcc-defsize-fltoetc': context_linux_thumb_gcc_defsize_fltoetc,
'linux-x64-gcc-minsize-fltoetc': context_linux_x64_gcc_minsize_fltoetc,
'linux-x86-gcc-minsize-fltoetc': context_linux_x86_gcc_minsize_fltoetc,
'linux-x32-gcc-minsize-fltoetc': context_linux_x32_gcc_minsize_fltoetc,
'linux-arm-gcc-minsize-fltoetc': context_linux_arm_gcc_minsize_fltoetc,
'linux-thumb-gcc-minsize-fltoetc': context_linux_thumb_gcc_minsize_fltoetc,
'linux-x64-gcc-stripsize-fltoetc': context_linux_x64_gcc_stripsize_fltoetc,
'linux-x86-gcc-stripsize-fltoetc': context_linux_x86_gcc_stripsize_fltoetc,
'linux-x32-gcc-stripsize-fltoetc': context_linux_x32_gcc_stripsize_fltoetc,
'linux-arm-gcc-stripsize-fltoetc': context_linux_arm_gcc_stripsize_fltoetc,
'linux-thumb-gcc-stripsize-fltoetc': context_linux_thumb_gcc_stripsize_fltoetc,
# Jobs matching previous graphs.html data points.
'linux-x64-graph-hello-size': context_linux_x64_graph_hello_size,
'linux-x86-graph-hello-size': context_linux_x86_graph_hello_size,
'linux-x32-graph-hello-size': context_linux_x32_graph_hello_size,
'linux-arm-graph-hello-size': context_linux_arm_graph_hello_size,
'linux-thumb-graph-hello-size': context_linux_thumb_graph_hello_size,
'linux-x64-cpp-exceptions': context_linux_x64_cpp_exceptions,
'linux-x86-duklow': context_linux_x86_duklow,
'linux-x86-duklow-norefc': context_linux_x86_duklow_norefc,
'linux-x86-duklow-rombuild': context_linux_x86_duklow_rombuild,
'linux-x64-v8-bench-pass': context_linux_x64_v8_bench_pass,
'linux-x64-octane': context_linux_x64_octane,
'linux-x64-duk-dddprint': context_linux_x64_duk_dddprint,
'linux-x64-duk-separate-src': context_linux_x64_duk_separate_src,
'linux-x86-packed-tval': context_linux_x86_packed_tval,
'linux-x86-dist-genconfig': context_linux_x86_dist_genconfig,
'linux-x64-error-variants': context_linux_x64_error_variants,
'linux-x64-hello-ram': context_linux_x64_hello_ram,
'linux-x86-hello-ram': context_linux_x86_hello_ram,
'linux-x32-hello-ram': context_linux_x32_hello_ram,
'linux-regconst-variants': context_linux_regconst_variants,
'linux-tval-variants': context_linux_tval_variants,
'linux-x64-minisphere': context_linux_x64_minisphere,
'linux-x64-dukluv': context_linux_x64_dukluv,
'codemetrics': context_codemetrics,
# OS X: can currently share Linux handlers
'osx-x64-ecmatest': context_linux_x64_ecmatest,
'osx-x64-qecmatest': context_linux_x64_ecmatest,
'osx-x64-duk-clang': context_linux_x64_duk_clang,
'osx-x64-duk-gxx': context_linux_x64_duk_gxx,
'osx-x64-gcc-minsize-makeduk': context_linux_x64_gcc_defsize_makeduk
}
#
# Main
#
def main():
print('*** Running test %r on %s' % (context, datetime.datetime.utcnow().isoformat() + 'Z'))
print('')
print('repo_full_name: ' + repo_full_name)
print('repo_clone_url: ' + repo_clone_url)
if opts.fetch_ref is not None:
print('fetch_ref: ' + opts.fetch_ref)
print('commit_name: ' + commit_name)
print('context: ' + context);
if not os.path.isdir(temp_dir):
raise Exception('missing or invalid temporary directory: %r' % temp_dir)
m = re_reponame.match(repo_full_name)
if m is None:
raise Exception('invalid repo name: %r' % repo_full_name)
if repo_full_name not in repo_whitelist:
raise Exception('repo name is not whitelisted: %r' % repo_full_name)
# Replace full repo forward slashes with platform separator
repo_targz = apply(os.path.join, [ repo_snapshot_dir ] + (repo_full_name + '.tar.gz').split('/'))
if repo_targz[0:len(repo_snapshot_dir)] != repo_snapshot_dir:
raise Exception('internal error figuring out repo_targz: %r' % repo_targz)
repo_dir = os.path.join(temp_dir, 'repo')
os.chdir(temp_dir)
os.mkdir(repo_dir)
print('')
print('*** GCC and Clang versions')
print('')
execute([ 'gcc', '-v' ], catch=True)
execute([ 'clang', '-v' ], catch=True)
print('')
print('*** Unpack repos and helpers')
print('')
os.chdir(repo_dir)
unpack_targz(repo_targz)
execute([ 'git', 'config', 'core.filemode', 'false' ]) # avoid perm issues on Windows
for fn in [
'closure-20160317.tar.gz',
'uglifyjs2-20160317.tar.gz',
'runtests-node-modules-20160320.tar.gz'
]:
unpack_targz(os.path.join(repo_snapshot_dir, fn))
execute([ 'git', 'clean', '-f' ])
execute([ 'git', 'reset', '--quiet', '--hard' ])
execute([ 'git', 'checkout', '--quiet', 'master' ])
execute([ 'git', 'pull', '--rebase' ])
execute([ 'git', 'clean', '-f' ])
execute([ 'git', 'reset', '--quiet', '--hard' ])
if opts.fetch_ref is not None:
# For pull requests, fetch pull request head and hope the commit
# still exists.
execute([ 'git', 'fetch', '--quiet', '--force', 'origin', opts.fetch_ref ])
execute([ 'git', 'checkout', '--quiet', commit_name ])
execute([ 'git', 'describe', '--always', '--dirty' ])
fn = context_handlers.get(context)
if fn is None:
print('Unknown context %s, supported contexts:')
for ctx in sorted(context_handlers.keys()):
print(' ' + ctx)
raise Exception('context unknown: ' + context)
print('')
print('*** Running test for context: ' + context)
print('')
test_start_time = time.time()
success = fn()
test_end_time = time.time()
set_output_field('test_time', test_end_time - test_start_time)
print('')
print('*** Finished test for context: ' + context + ', success: ' + repr(success))
print('')
if success == True:
# Testcase successful
print('Test succeeded')
sys.exit(0)
elif success == False:
# Testcase failed, but no test script error (= don't rerun automatically)
print('Test failed')
sys.exit(1)
else:
raise Exception('context handler returned a non-boolean: %r' % success)
if __name__ == '__main__':
total_start_time = time.time()
try:
try:
main()
raise Exception('internal error, should never be here')
except SystemExit:
# Test script success.
raise
except:
# Test script failed, automatic retry is useful.
print('')
print('*** Test script failed')
print('')
traceback.print_exc()
set_output_result({
'description': 'Test script error',
'error': True,
'traceback': traceback.format_exc()
})
sys.exit(2)
finally:
total_end_time = time.time()
set_output_field('total_time', total_end_time - total_start_time)
print('')
print('Test took %.2f minutes' % ((total_end_time - total_start_time) / 60.0))
if output_result_json is not None:
print('TESTRUNNER_DESCRIPTION: ' + output_result_json.get('description', ''))
print('TESTRUNNER_RESULT_JSON: ' + json.dumps(output_result_json))