2012-12-02 03:31:36 +04:00
#!/usr/bin/env python2
2012-07-15 06:01:09 +04:00
# This Python file uses the following encoding: utf-8
2012-01-13 02:08:09 +04:00
2010-08-26 08:01:10 +04:00
'''
Simple test runner
2011-10-14 04:33:04 +04:00
These tests can be run in parallel using nose , for example
2011-10-16 20:48:15 +04:00
nosetests - - processes = 4 - v - s tests / runner . py
2011-10-14 04:33:04 +04:00
will use 4 processes . To install nose do something like
| pip install nose | or | sudo apt - get install python - nose | .
2012-11-04 23:05:49 +04:00
Note however that emcc now uses multiple cores when optimizing ,
so you may prefer to use fewer cores here .
2010-08-26 08:01:10 +04:00
'''
from subprocess import Popen , PIPE , STDOUT
2013-08-08 05:58:12 +04:00
import os , unittest , tempfile , shutil , time , inspect , sys , math , glob , re , difflib , webbrowser , hashlib , threading , platform , BaseHTTPServer , multiprocessing , functools , stat , string
2012-03-16 01:00:51 +04:00
2011-01-15 09:44:52 +03:00
# Setup
2010-08-26 08:01:10 +04:00
2012-01-31 02:10:57 +04:00
__rootpath__ = os . path . dirname ( os . path . dirname ( os . path . abspath ( __file__ ) ) )
2011-01-15 09:44:52 +03:00
def path_from_root ( * pathelems ) :
2011-10-05 21:50:13 +04:00
return os . path . join ( __rootpath__ , * pathelems )
2012-11-01 00:51:23 +04:00
sys . path + = [ path_from_root ( ' ' ) , path_from_root ( ' third_party/websockify ' ) ]
2012-01-31 23:07:55 +04:00
import tools . shared
2012-01-31 02:10:57 +04:00
from tools . shared import *
2011-12-21 09:22:05 +04:00
2011-01-15 09:44:52 +03:00
# Sanity check for config
2010-09-10 07:03:24 +04:00
2011-01-15 09:44:52 +03:00
try :
2011-03-19 20:00:57 +03:00
assert COMPILER_OPTS != None
2011-01-15 09:44:52 +03:00
except :
2012-09-22 23:12:25 +04:00
raise Exception ( ' Cannot find " COMPILER_OPTS " definition. Is %s set up properly? You may need to copy the template settings file into it. ' % EM_CONFIG )
2010-08-26 08:01:10 +04:00
2011-01-30 08:52:21 +03:00
# Core test runner class, shared between normal tests and benchmarks
2013-05-03 02:31:52 +04:00
checked_sanity = False
2013-08-20 02:23:48 +04:00
test_modes = [ ' default ' , ' o1 ' , ' o2 ' , ' asm1 ' , ' asm2 ' , ' asm2g ' , ' asm2x86 ' , ' s_0_0 ' , ' s_0_1 ' ]
2013-08-12 08:48:58 +04:00
test_index = 0
2013-05-03 02:31:52 +04:00
2010-10-10 03:54:23 +04:00
class RunnerCore ( unittest . TestCase ) :
2013-08-12 08:48:58 +04:00
emcc_args = None
2011-11-25 08:50:45 +04:00
save_dir = os . environ . get ( ' EM_SAVE_DIR ' )
2011-10-23 00:13:51 +04:00
save_JS = 0
2012-01-03 07:21:57 +04:00
stderr_redirect = STDOUT # This avoids cluttering the test runner output, which is stderr too, with compiler warnings etc.
# Change this to None to get stderr reporting, for debugging purposes
2011-10-23 00:13:51 +04:00
2013-05-01 05:07:22 +04:00
env = { }
2013-08-13 20:25:13 +04:00
def skipme ( self ) : # used by tests we ask on the commandline to be skipped, see right before call to unittest.main
return self . skip ( ' requested to be skipped ' )
2011-10-13 03:36:50 +04:00
def setUp ( self ) :
2011-12-06 22:39:21 +04:00
Settings . reset ( )
2011-12-04 08:09:11 +04:00
self . banned_js_engines = [ ]
2011-10-23 00:13:51 +04:00
if not self . save_dir :
2011-12-23 05:59:33 +04:00
dirname = tempfile . mkdtemp ( prefix = ' emscripten_test_ ' + self . __class__ . __name__ + ' _ ' , dir = TEMP_DIR )
2011-10-16 20:48:15 +04:00
else :
2012-04-13 01:05:21 +04:00
dirname = CANONICAL_TEMP_DIR
2011-10-13 03:36:50 +04:00
if not os . path . exists ( dirname ) :
os . makedirs ( dirname )
self . working_dir = dirname
2011-11-20 01:37:26 +04:00
os . chdir ( dirname )
2012-11-14 03:25:21 +04:00
2013-08-24 20:31:29 +04:00
# Use emscripten root for node module lookup
scriptdir = os . path . dirname ( os . path . abspath ( __file__ ) )
os . environ [ ' NODE_PATH ' ] = os . path . join ( scriptdir , ' .. ' , ' node_modules ' )
2012-11-14 03:25:21 +04:00
if not self . save_dir :
self . has_prev_ll = False
for temp_file in os . listdir ( TEMP_DIR ) :
if temp_file . endswith ( ' .ll ' ) :
self . has_prev_ll = True
2013-02-21 22:28:28 +04:00
2011-04-23 04:25:01 +04:00
def tearDown ( self ) :
2011-10-23 00:13:51 +04:00
if not self . save_dir :
2012-03-20 17:26:50 +04:00
# rmtree() fails on Windows if the current working directory is inside the tree.
os . chdir ( os . path . join ( self . get_dir ( ) , ' .. ' ) )
2011-10-16 20:48:15 +04:00
shutil . rmtree ( self . get_dir ( ) )
2011-04-23 04:25:01 +04:00
2012-11-14 03:25:21 +04:00
# Make sure we don't leave stuff around
2013-01-24 22:21:40 +04:00
#if not self.has_prev_ll:
# for temp_file in os.listdir(TEMP_DIR):
# assert not temp_file.endswith('.ll'), temp_file
# # TODO assert not temp_file.startswith('emscripten_'), temp_file
2012-11-14 03:25:21 +04:00
2011-09-03 03:03:33 +04:00
def skip ( self , why ) :
print >> sys . stderr , ' <skipping: %s > ' % why ,
2011-04-23 00:23:37 +04:00
2010-10-10 03:54:23 +04:00
def get_dir ( self ) :
2011-10-13 03:36:50 +04:00
return self . working_dir
2010-10-10 03:54:23 +04:00
2012-10-02 21:53:12 +04:00
def in_dir ( self , * pathelems ) :
return os . path . join ( self . get_dir ( ) , * pathelems )
2011-11-17 22:16:42 +04:00
def get_stdout_path ( self ) :
return os . path . join ( self . get_dir ( ) , ' stdout ' )
2013-02-21 22:28:28 +04:00
def hardcode_arguments ( self , filename , args ) :
# Hardcode in the arguments, so js is portable without manual commandlinearguments
if not args : return
js = open ( filename ) . read ( )
2013-06-20 06:09:19 +04:00
open ( filename , ' w ' ) . write ( js . replace ( ' run(); ' , ' run( %s + Module[ " arguments " ]); ' % str ( args ) ) )
2013-02-21 22:28:28 +04:00
2011-10-13 18:40:29 +04:00
def prep_ll_run ( self , filename , ll_file , force_recompile = False , build_ll_hook = None ) :
2011-04-25 04:57:01 +04:00
if ll_file . endswith ( ( ' .bc ' , ' .o ' ) ) :
if ll_file != filename + ' .o ' :
shutil . copy ( ll_file , filename + ' .o ' )
2011-10-27 07:21:11 +04:00
Building . llvm_dis ( filename )
2011-04-25 04:57:01 +04:00
else :
shutil . copy ( ll_file , filename + ' .o.ll ' )
2011-11-27 22:48:06 +04:00
#force_recompile = force_recompile or os.stat(filename + '.o.ll').st_size > 50000 # if the file is big, recompile just to get ll_opts # Recompiling just for dfe in ll_opts is too costly
2011-04-25 04:57:01 +04:00
2011-10-27 07:41:51 +04:00
if Building . LLVM_OPTS or force_recompile or build_ll_hook :
2011-10-27 07:21:11 +04:00
Building . ll_opts ( filename )
2011-04-25 04:57:01 +04:00
if build_ll_hook :
2011-11-12 05:21:20 +04:00
need_post = build_ll_hook ( filename )
Building . llvm_as ( filename )
shutil . move ( filename + ' .o.ll ' , filename + ' .o.ll.pre ' ) # for comparisons later
2011-12-14 00:20:45 +04:00
if Building . LLVM_OPTS :
Building . llvm_opts ( filename )
2011-10-27 07:21:11 +04:00
Building . llvm_dis ( filename )
2011-11-12 05:21:20 +04:00
if build_ll_hook and need_post :
build_ll_hook ( filename )
Building . llvm_as ( filename )
shutil . move ( filename + ' .o.ll ' , filename + ' .o.ll.post ' ) # for comparisons later
Building . llvm_dis ( filename )
2011-04-25 04:57:01 +04:00
2011-12-22 03:15:25 +04:00
# Generate JS from ll, and optionally modify the generated JS with a post_build function. Note
# that post_build is called on unoptimized JS, so we send it to emcc (otherwise, if run after
# emcc, it would not apply on the optimized/minified JS)
def ll_to_js ( self , filename , extra_emscripten_args , post_build ) :
2012-02-04 03:00:45 +04:00
if type ( post_build ) in ( list , tuple ) :
post1 , post2 = post_build
else :
post1 = post_build
post2 = None
2011-12-22 03:15:25 +04:00
if self . emcc_args is None :
Building . emscripten ( filename , append_ext = True , extra_args = extra_emscripten_args )
2013-06-08 02:37:47 +04:00
if post1 :
exec post1 in locals ( )
shutil . copyfile ( filename + ' .o.js ' , filename + ' .o.js.prepost.js ' )
process ( filename + ' .o.js ' )
if post2 : post2 ( filename + ' .o.js ' )
2011-12-22 03:15:25 +04:00
else :
2012-01-05 02:36:02 +04:00
transform_args = [ ]
2012-02-04 03:00:45 +04:00
if post1 :
2012-01-05 02:36:02 +04:00
transform_filename = os . path . join ( self . get_dir ( ) , ' transform.py ' )
transform = open ( transform_filename , ' w ' )
transform . write ( '''
import sys
2012-03-20 17:26:50 +04:00
sys . path + = [ % r ]
2012-01-05 03:15:49 +04:00
''' % path_from_root( ' ' ))
2012-02-04 03:00:45 +04:00
transform . write ( post1 )
2012-01-05 02:36:02 +04:00
transform . write ( '''
process ( sys . argv [ 1 ] )
''' )
transform . close ( )
2012-12-12 03:13:16 +04:00
transform_args = [ ' --js-transform ' , " %s %s " % ( PYTHON , transform_filename ) ]
2012-11-08 00:07:19 +04:00
Building . emcc ( filename + ' .o.ll ' , Settings . serialize ( ) + self . emcc_args + transform_args + Building . COMPILER_TEST_OPTS , filename + ' .o.js ' )
2013-06-08 02:37:47 +04:00
if post2 : post2 ( filename + ' .o.js ' )
2011-12-22 03:15:25 +04:00
2011-01-08 07:44:14 +03:00
# Build JavaScript code from source code
2011-12-21 06:49:42 +04:00
def build ( self , src , dirname , filename , output_processor = None , main_file = None , additional_files = [ ] , libraries = [ ] , includes = [ ] , build_ll_hook = None , extra_emscripten_args = [ ] , post_build = None ) :
2011-12-22 03:15:25 +04:00
2012-01-24 02:11:41 +04:00
Building . pick_llvm_opts ( 3 ) # pick llvm opts here, so we include changes to Settings in the test case code
2012-01-21 22:33:08 +04:00
2010-10-10 03:54:23 +04:00
# Copy over necessary files for compiling the source
if main_file is None :
f = open ( filename , ' w ' )
f . write ( src )
f . close ( )
2012-01-17 07:30:57 +04:00
final_additional_files = [ ]
for f in additional_files :
final_additional_files . append ( os . path . join ( dirname , os . path . basename ( f ) ) )
shutil . copyfile ( f , final_additional_files [ - 1 ] )
additional_files = final_additional_files
2010-10-10 03:54:23 +04:00
else :
# copy whole directory, and use a specific main .cpp file
2012-03-20 17:26:50 +04:00
# (rmtree() fails on Windows if the current working directory is inside the tree.)
if os . getcwd ( ) . startswith ( os . path . abspath ( dirname ) ) :
os . chdir ( os . path . join ( dirname , ' .. ' ) )
2011-01-18 02:36:26 +03:00
shutil . rmtree ( dirname )
shutil . copytree ( src , dirname )
2010-10-10 03:54:23 +04:00
shutil . move ( os . path . join ( dirname , main_file ) , filename )
2011-01-18 02:36:26 +03:00
# the additional files were copied; alter additional_files to point to their full paths now
additional_files = map ( lambda f : os . path . join ( dirname , f ) , additional_files )
2012-01-20 04:18:57 +04:00
os . chdir ( self . get_dir ( ) )
2010-10-10 03:54:23 +04:00
# C++ => LLVM binary
2011-09-04 21:52:59 +04:00
2011-01-18 02:36:26 +03:00
for f in [ filename ] + additional_files :
try :
# Make sure we notice if compilation steps failed
os . remove ( f + ' .o ' )
except :
pass
2013-06-28 03:35:26 +04:00
args = [ PYTHON , EMCC ] + Building . COMPILER_TEST_OPTS + Settings . serialize ( ) + \
2012-01-03 00:44:49 +04:00
[ ' -I ' , dirname , ' -I ' , os . path . join ( dirname , ' include ' ) ] + \
map ( lambda include : ' -I ' + include , includes ) + \
[ ' -c ' , f , ' -o ' , f + ' .o ' ]
2013-06-28 03:35:26 +04:00
output = Popen ( args , stdout = PIPE , stderr = self . stderr_redirect if not DEBUG else None ) . communicate ( ) [ 0 ]
2011-02-28 03:55:53 +03:00
assert os . path . exists ( f + ' .o ' ) , ' Source compilation error: ' + output
2011-01-18 02:36:26 +03:00
# Link all files
2011-01-24 05:23:44 +03:00
if len ( additional_files ) + len ( libraries ) > 0 :
2011-01-18 02:36:26 +03:00
shutil . move ( filename + ' .o ' , filename + ' .o.alone ' )
2011-10-27 07:16:22 +04:00
Building . link ( [ filename + ' .o.alone ' ] + map ( lambda f : f + ' .o ' , additional_files ) + libraries ,
2011-10-25 02:20:01 +04:00
filename + ' .o ' )
2011-01-18 02:36:26 +03:00
if not os . path . exists ( filename + ' .o ' ) :
print " Failed to link LLVM binaries: \n \n " , output
raise Exception ( " Linkage error " ) ;
# Finalize
2011-10-13 18:40:29 +04:00
self . prep_ll_run ( filename , filename + ' .o ' , build_ll_hook = build_ll_hook )
2011-03-03 18:39:33 +03:00
2011-12-21 04:38:14 +04:00
# BC => JS
2011-12-22 03:15:25 +04:00
self . ll_to_js ( filename , extra_emscripten_args , post_build )
2011-12-21 04:38:14 +04:00
if output_processor is not None :
output_processor ( open ( filename + ' .o.js ' ) . read ( ) )
2010-10-10 03:54:23 +04:00
2013-04-15 21:43:01 +04:00
if self . emcc_args is not None and ' ASM_JS=1 ' in self . emcc_args :
2013-04-06 04:58:53 +04:00
if ' --memory-init-file ' in self . emcc_args :
memory_init_file = int ( self . emcc_args [ self . emcc_args . index ( ' --memory-init-file ' ) + 1 ] )
else :
2013-04-15 21:43:01 +04:00
memory_init_file = 0
2013-04-10 01:13:09 +04:00
if memory_init_file :
assert ' /* memory initializer */ ' not in open ( filename + ' .o.js ' ) . read ( )
else :
assert ' memory initializer */ ' in open ( filename + ' .o.js ' ) . read ( )
2013-04-06 04:58:53 +04:00
2013-06-26 03:10:37 +04:00
def validate_asmjs ( self , err ) :
if ' uccessfully compiled asm.js code ' in err and ' asm.js link error ' not in err :
print >> sys . stderr , " [was asm.js ' ified] "
elif ' asm.js ' in err : # if no asm.js error, then not an odin build
raise Exception ( " did NOT asm.js ' ify " )
2013-08-11 08:01:10 +04:00
err = ' \n ' . join ( filter ( lambda line : ' uccessfully compiled asm.js code ' not in line , err . split ( ' \n ' ) ) )
2013-07-23 23:29:22 +04:00
return err
2013-06-26 03:10:37 +04:00
2013-01-10 03:47:16 +04:00
def run_generated_code ( self , engine , filename , args = [ ] , check_timeout = True , output_nicerizer = None ) :
2011-02-28 03:54:21 +03:00
stdout = os . path . join ( self . get_dir ( ) , ' stdout ' ) # use files, as PIPE can get too full and hang us
stderr = os . path . join ( self . get_dir ( ) , ' stderr ' )
2011-03-17 01:31:12 +03:00
try :
cwd = os . getcwd ( )
except :
cwd = None
os . chdir ( self . get_dir ( ) )
2011-12-12 06:31:10 +04:00
run_js ( filename , engine , args , check_timeout , stdout = open ( stdout , ' w ' ) , stderr = open ( stderr , ' w ' ) )
2011-03-17 01:31:12 +03:00
if cwd is not None :
os . chdir ( cwd )
2013-01-10 03:47:16 +04:00
out = open ( stdout , ' r ' ) . read ( )
err = open ( stderr , ' r ' ) . read ( )
2013-01-15 04:31:39 +04:00
if engine == SPIDERMONKEY_ENGINE and Settings . ASM_JS :
2013-07-23 23:29:22 +04:00
err = self . validate_asmjs ( err )
2013-01-10 03:47:16 +04:00
if output_nicerizer :
ret = output_nicerizer ( out , err )
else :
ret = out + err
2011-02-28 03:54:21 +03:00
assert ' strict warning: ' not in ret , ' We should pass all strict mode checks: ' + ret
2011-02-21 06:03:14 +03:00
return ret
2010-10-10 03:54:23 +04:00
2013-01-26 02:27:16 +04:00
def build_native ( self , filename , args = [ ] ) :
compiler = CLANG if filename . endswith ( ' cpp ' ) else CLANG_CC
process = Popen ( [ compiler , ' -O2 ' , ' -fno-math-errno ' , filename , ' -o ' , filename + ' .native ' ] + args , stdout = PIPE , stderr = self . stderr_redirect )
2012-11-28 22:09:32 +04:00
output = process . communicate ( )
if process . returncode is not 0 :
print >> sys . stderr , " Building native executable with command ' %s ' failed with a return code %d ! " % ( ' ' . join ( [ CLANG , ' -O2 ' , filename , ' -o ' , filename + ' .native ' ] ) , process . returncode )
print " Output: " + output [ 0 ]
2011-06-12 00:52:03 +04:00
def run_native ( self , filename , args ) :
2012-11-28 22:09:32 +04:00
process = Popen ( [ filename + ' .native ' ] + args , stdout = PIPE ) ;
output = process . communicate ( )
if process . returncode is not 0 :
print >> sys . stderr , " Running native executable with command ' %s ' failed with a return code %d ! " % ( ' ' . join ( [ filename + ' .native ' ] + args ) , process . returncode )
print " Output: " + output [ 0 ]
2013-01-26 02:27:16 +04:00
return output [ 0 ]
2011-06-12 00:52:03 +04:00
2013-08-17 23:00:16 +04:00
# Tests that the given two paths are identical, modulo path delimiters. E.g. "C:/foo" is equal to "C:\foo".
def assertPathsIdentical ( self , path1 , path2 ) :
path1 = path1 . replace ( ' \\ ' , ' / ' )
path2 = path2 . replace ( ' \\ ' , ' / ' )
return self . assertIdentical ( path1 , path2 )
# Tests that the given two multiline text content are identical, modulo line ending differences (\r\n on Windows, \n on Unix).
def assertTextDataIdentical ( self , text1 , text2 ) :
text1 = text1 . replace ( ' \r \n ' , ' \n ' )
text2 = text2 . replace ( ' \r \n ' , ' \n ' )
return self . assertIdentical ( text1 , text2 )
2012-06-13 08:23:13 +04:00
def assertIdentical ( self , values , y ) :
if type ( values ) not in [ list , tuple ] : values = [ values ]
for x in values :
if x == y : return # success
raise Exception ( " Expected to have ' %s ' == ' %s ' , diff: \n \n %s " % (
limit_size ( values [ 0 ] ) , limit_size ( y ) ,
limit_size ( ' ' . join ( [ a . rstrip ( ) + ' \n ' for a in difflib . unified_diff ( x . split ( ' \n ' ) , y . split ( ' \n ' ) , fromfile = ' expected ' , tofile = ' actual ' ) ] ) )
) )
2011-11-01 04:38:27 +04:00
2011-12-11 23:54:22 +04:00
def assertContained ( self , values , string , additional_info = ' ' ) :
2011-12-08 03:36:35 +04:00
if type ( values ) not in [ list , tuple ] : values = [ values ]
for value in values :
if type ( string ) is not str : string = string ( )
if value in string : return # success
2011-12-11 23:54:22 +04:00
raise Exception ( " Expected to find ' %s ' in ' %s ' , diff: \n \n %s \n %s " % (
2011-12-08 03:36:35 +04:00
limit_size ( values [ 0 ] ) , limit_size ( string ) ,
2011-12-11 23:54:22 +04:00
limit_size ( ' ' . join ( [ a . rstrip ( ) + ' \n ' for a in difflib . unified_diff ( values [ 0 ] . split ( ' \n ' ) , string . split ( ' \n ' ) , fromfile = ' expected ' , tofile = ' actual ' ) ] ) ) ,
additional_info
2011-12-08 03:36:35 +04:00
) )
2010-10-15 10:07:23 +04:00
def assertNotContained ( self , value , string ) :
2011-03-16 06:13:57 +03:00
if type ( value ) is not str : value = value ( ) # lazy loading
if type ( string ) is not str : string = string ( )
2010-10-15 10:07:23 +04:00
if value in string :
2011-09-07 07:40:20 +04:00
raise Exception ( " Expected to NOT find ' %s ' in ' %s ' , diff: \n \n %s " % (
limit_size ( value ) , limit_size ( string ) ,
limit_size ( ' ' . join ( [ a . rstrip ( ) + ' \n ' for a in difflib . unified_diff ( value . split ( ' \n ' ) , string . split ( ' \n ' ) , fromfile = ' expected ' , tofile = ' actual ' ) ] ) )
) )
2010-10-15 10:07:23 +04:00
2011-10-27 07:16:22 +04:00
library_cache = { }
2012-04-01 02:20:14 +04:00
def get_build_dir ( self ) :
ret = os . path . join ( self . get_dir ( ) , ' building ' )
if not os . path . exists ( ret ) :
os . makedirs ( ret )
return ret
2013-01-26 02:27:16 +04:00
def get_library ( self , name , generated_libs , configure = [ ' sh ' , ' ./configure ' ] , configure_args = [ ] , make = [ ' make ' ] , make_args = [ ' -j ' , ' 2 ' ] , cache = True , env_init = { } , cache_name_extra = ' ' , native = False ) :
2011-10-27 07:16:22 +04:00
build_dir = self . get_build_dir ( )
output_dir = self . get_dir ( )
2013-08-07 00:54:59 +04:00
cache_name = name + str ( Building . COMPILER_TEST_OPTS ) + cache_name_extra + ( self . env . get ( ' EMCC_LLVM_TARGET ' ) or ' ' )
2013-05-01 04:48:36 +04:00
2013-08-08 05:58:12 +04:00
valid_chars = " _ %s %s " % ( string . ascii_letters , string . digits )
cache_name = ' ' . join ( [ ( c if c in valid_chars else ' _ ' ) for c in cache_name ] )
2011-10-27 07:16:22 +04:00
if self . library_cache is not None :
if cache and self . library_cache . get ( cache_name ) :
2012-11-07 06:03:37 +04:00
print >> sys . stderr , ' <load %s from cache> ' % cache_name ,
2012-02-08 04:54:51 +04:00
generated_libs = [ ]
2012-02-10 05:49:43 +04:00
for basename , contents in self . library_cache [ cache_name ] :
2013-05-19 02:43:17 +04:00
bc_file = os . path . join ( build_dir , cache_name + ' _ ' + basename )
2012-02-08 04:54:51 +04:00
f = open ( bc_file , ' wb ' )
2012-02-10 05:49:43 +04:00
f . write ( contents )
2012-02-08 04:54:51 +04:00
f . close ( )
generated_libs . append ( bc_file )
return generated_libs
2011-10-27 07:16:22 +04:00
2012-11-07 06:03:37 +04:00
print >> sys . stderr , ' <building and saving %s into cache> ' % cache_name ,
2011-10-27 07:16:22 +04:00
2011-10-27 07:41:51 +04:00
return Building . build_library ( name , build_dir , output_dir , generated_libs , configure , configure_args , make , make_args , self . library_cache , cache_name ,
2013-01-26 02:27:16 +04:00
copy_project = True , env_init = env_init , native = native )
2011-10-27 07:16:22 +04:00
2012-11-08 23:55:09 +04:00
def clear ( self , in_curr = False ) :
2012-04-01 02:03:55 +04:00
for name in os . listdir ( self . get_dir ( ) ) :
2012-11-08 23:55:09 +04:00
try_delete ( os . path . join ( self . get_dir ( ) , name ) if not in_curr else name )
2012-04-01 02:03:55 +04:00
emcc_debug = os . environ . get ( ' EMCC_DEBUG ' )
2012-11-09 23:59:40 +04:00
if emcc_debug and not in_curr :
2012-04-01 02:03:55 +04:00
for name in os . listdir ( EMSCRIPTEN_TEMP_DIR ) :
try_delete ( os . path . join ( EMSCRIPTEN_TEMP_DIR , name ) )
2012-10-12 00:21:51 +04:00
# Shared test code between main suite and others
def setup_runtimelink_test ( self ) :
header = r '''
struct point
{
int x , y ;
} ;
'''
open ( os . path . join ( self . get_dir ( ) , ' header.h ' ) , ' w ' ) . write ( header )
supp = r '''
#include <stdio.h>
#include "header.h"
extern void mainFunc ( int x ) ;
extern int mainInt ;
void suppFunc ( struct point & p ) {
printf ( " supp: %d , %d \n " , p . x , p . y ) ;
mainFunc ( p . x + p . y ) ;
printf ( " supp see: %d \n " , mainInt ) ;
}
int suppInt = 76 ;
'''
2013-05-03 05:46:08 +04:00
supp_name = os . path . join ( self . get_dir ( ) , ' supp.cpp ' )
2012-10-12 00:21:51 +04:00
open ( supp_name , ' w ' ) . write ( supp )
main = r '''
#include <stdio.h>
#include "header.h"
extern void suppFunc ( struct point & p ) ;
extern int suppInt ;
void mainFunc ( int x ) {
printf ( " main: %d \n " , x ) ;
}
int mainInt = 543 ;
int main ( int argc , const char * argv [ ] ) {
struct point p = { 54 , 2 } ;
suppFunc ( p ) ;
printf ( " main see: %d \n ok. \n " , suppInt ) ;
2012-10-12 03:15:07 +04:00
#ifdef BROWSER
int result = suppInt ;
REPORT_RESULT ( ) ;
#endif
2012-10-12 00:21:51 +04:00
return 0 ;
}
'''
return ( main , supp )
2011-10-27 07:16:22 +04:00
2013-08-12 08:48:58 +04:00
## Does a complete test - builds, runs, checks output, etc.
def do_run ( self , src , expected_output , args = [ ] , output_nicerizer = None , output_processor = None , no_build = False , main_file = None , additional_files = [ ] , js_engines = None , post_build = None , basename = ' src.cpp ' , libraries = [ ] , includes = [ ] , force_c = False , build_ll_hook = None , extra_emscripten_args = [ ] ) :
if force_c or ( main_file is not None and main_file [ - 2 : ] ) == ' .c ' :
basename = ' src.c '
Building . COMPILER = to_cc ( Building . COMPILER )
dirname = self . get_dir ( )
filename = os . path . join ( dirname , basename )
if not no_build :
self . build ( src , dirname , filename , main_file = main_file , additional_files = additional_files , libraries = libraries , includes = includes ,
build_ll_hook = build_ll_hook , extra_emscripten_args = extra_emscripten_args , post_build = post_build )
# Run in both JavaScript engines, if optimizing - significant differences there (typed arrays)
if js_engines is None :
js_engines = JS_ENGINES
if Settings . USE_TYPED_ARRAYS :
js_engines = filter ( lambda engine : engine != V8_ENGINE , js_engines ) # V8 issue 1822
js_engines = filter ( lambda engine : engine not in self . banned_js_engines , js_engines )
if len ( js_engines ) == 0 : return self . skip ( ' No JS engine present to run this test with. Check %s and the paths therein. ' % EM_CONFIG )
for engine in js_engines :
js_output = self . run_generated_code ( engine , filename + ' .o.js ' , args , output_nicerizer = output_nicerizer )
self . assertContained ( expected_output , js_output . replace ( ' \r \n ' , ' \n ' ) )
self . assertNotContained ( ' ERROR ' , js_output )
#shutil.rmtree(dirname) # TODO: leave no trace in memory. But for now nice for debugging
if self . save_JS :
global test_index
self . hardcode_arguments ( filename + ' .o.js ' , args )
shutil . copyfile ( filename + ' .o.js ' , os . path . join ( TEMP_DIR , str ( test_index ) + ' .js ' ) )
test_index + = 1
# No building - just process an existing .ll file (or .bc, which we turn into .ll)
def do_ll_run ( self , ll_file , expected_output = None , args = [ ] , js_engines = None , output_nicerizer = None , post_build = None , force_recompile = False , build_ll_hook = None , extra_emscripten_args = [ ] ) :
filename = os . path . join ( self . get_dir ( ) , ' src.cpp ' )
self . prep_ll_run ( filename , ll_file , force_recompile , build_ll_hook )
2010-08-26 08:01:10 +04:00
2013-08-12 08:48:58 +04:00
self . ll_to_js ( filename , extra_emscripten_args , post_build )
2011-11-18 08:28:32 +04:00
2013-08-12 08:48:58 +04:00
self . do_run ( None ,
expected_output ,
args ,
no_build = True ,
js_engines = js_engines ,
output_nicerizer = output_nicerizer ,
post_build = None ) # post_build was already done in ll_to_js, this do_run call is just to test the output
# Run a server and a web page. When a test runs, we tell the server about it,
# which tells the web page, which then opens a window with the test. Doing
# it this way then allows the page to close() itself when done.
def harness_server_func ( q ) :
class TestServerHandler ( BaseHTTPServer . BaseHTTPRequestHandler ) :
def do_GET ( s ) :
s . send_response ( 200 )
s . send_header ( " Content-type " , " text/html " )
s . end_headers ( )
if s . path == ' /run_harness ' :
s . wfile . write ( open ( path_from_root ( ' tests ' , ' browser_harness.html ' ) ) . read ( ) )
else :
result = ' False '
if not q . empty ( ) :
result = q . get ( )
s . wfile . write ( result )
s . wfile . close ( )
def log_request ( code = 0 , size = 0 ) :
# don't log; too noisy
pass
httpd = BaseHTTPServer . HTTPServer ( ( ' localhost ' , 9999 ) , TestServerHandler )
httpd . serve_forever ( ) # test runner will kill us
def server_func ( dir , q ) :
class TestServerHandler ( BaseHTTPServer . BaseHTTPRequestHandler ) :
def do_GET ( s ) :
if ' report_ ' in s . path :
q . put ( s . path )
else :
2013-08-14 08:58:28 +04:00
filename = s . path . split ( ' ? ' ) [ 0 ] [ 1 : ]
2013-08-12 08:48:58 +04:00
if os . path . exists ( filename ) :
s . send_response ( 200 )
s . send_header ( " Content-type " , " text/html " )
s . end_headers ( )
s . wfile . write ( open ( filename ) . read ( ) )
s . wfile . close ( )
else :
s . send_response ( 500 )
s . send_header ( " Content-type " , " text/html " )
s . end_headers ( )
def log_request ( code = 0 , size = 0 ) :
# don't log; too noisy
pass
os . chdir ( dir )
httpd = BaseHTTPServer . HTTPServer ( ( ' localhost ' , 8888 ) , TestServerHandler )
httpd . serve_forever ( ) # test runner will kill us
class BrowserCore ( RunnerCore ) :
def __init__ ( self , * args , * * kwargs ) :
super ( BrowserCore , self ) . __init__ ( * args , * * kwargs )
@classmethod
def setUpClass ( self ) :
super ( BrowserCore , self ) . setUpClass ( )
self . harness_queue = multiprocessing . Queue ( )
self . harness_server = multiprocessing . Process ( target = harness_server_func , args = ( self . harness_queue , ) )
self . harness_server . start ( )
print ' [Browser harness server on process %d ] ' % self . harness_server . pid
webbrowser . open_new ( ' http://localhost:9999/run_harness ' )
@classmethod
def tearDownClass ( self ) :
super ( BrowserCore , self ) . tearDownClass ( )
self . harness_server . terminate ( )
print ' [Browser harness server terminated] '
# On Windows, shutil.rmtree() in tearDown() raises this exception if we do not wait a bit:
# WindowsError: [Error 32] The process cannot access the file because it is being used by another process.
time . sleep ( 0.1 )
def run_browser ( self , html_file , message , expectedResult = None ) :
if expectedResult is not None :
try :
queue = multiprocessing . Queue ( )
server = multiprocessing . Process ( target = functools . partial ( server_func , self . get_dir ( ) ) , args = ( queue , ) )
server . start ( )
self . harness_queue . put ( ' http://localhost:8888/ ' + html_file )
output = ' [no http server activity] '
start = time . time ( )
while time . time ( ) - start < 60 :
if not queue . empty ( ) :
output = queue . get ( )
break
time . sleep ( 0.1 )
2011-11-11 01:01:44 +04:00
2013-08-12 08:48:58 +04:00
self . assertIdentical ( expectedResult , output )
finally :
server . terminate ( )
time . sleep ( 0.1 ) # see comment about Windows above
else :
webbrowser . open_new ( os . path . abspath ( html_file ) )
print ' A web browser window should have opened a page containing the results of a part of this test. '
print ' You need to manually look at the page to see that it works ok: ' + message
print ' (sleeping for a bit to keep the directory alive for the web browser..) '
time . sleep ( 5 )
print ' (moving on..) '
def with_report_result ( self , code ) :
return r '''
#if EMSCRIPTEN
#include <emscripten.h>
#define REPORT_RESULT_INTERNAL(sync) \
char output [ 1000 ] ; \
sprintf ( output , \
" xhr = new XMLHttpRequest(); " \
" xhr.open( ' GET ' , ' http://localhost:8888/report_result? %d ' %s ); " \
" xhr.send(); " , result , sync ? " , false " : " " ) ; \
emscripten_run_script ( output ) ; \
emscripten_run_script ( " setTimeout(function() { window.close() }, 1000) " ) ; / / comment this out to keep the test runner window open to debug
#define REPORT_RESULT() REPORT_RESULT_INTERNAL(0)
#endif
''' + code
2011-11-11 01:01:44 +04:00
2013-08-12 08:48:58 +04:00
def reftest ( self , expected ) :
# make sure the pngs used here have no color correction, using e.g.
# pngcrush -rem gAMA -rem cHRM -rem iCCP -rem sRGB infile outfile
basename = os . path . basename ( expected )
shutil . copyfile ( expected , os . path . join ( self . get_dir ( ) , basename ) )
open ( os . path . join ( self . get_dir ( ) , ' reftest.js ' ) , ' w ' ) . write ( '''
var Module = eval ( ' Module ' ) ;
function doReftest ( ) {
if ( doReftest . done ) return ;
doReftest . done = true ;
var img = new Image ( ) ;
img . onload = function ( ) {
assert ( img . width == Module . canvas . width , ' Invalid width: ' + Module . canvas . width + ' , should be ' + img . width ) ;
assert ( img . height == Module . canvas . height , ' Invalid height: ' + Module . canvas . height + ' , should be ' + img . height ) ;
var canvas = document . createElement ( ' canvas ' ) ;
canvas . width = img . width ;
canvas . height = img . height ;
var ctx = canvas . getContext ( ' 2d ' ) ;
ctx . drawImage ( img , 0 , 0 ) ;
var expected = ctx . getImageData ( 0 , 0 , img . width , img . height ) . data ;
var actualUrl = Module . canvas . toDataURL ( ) ;
var actualImage = new Image ( ) ;
actualImage . onload = function ( ) {
/ *
document . body . appendChild ( img ) ; / / for comparisons
var div = document . createElement ( ' div ' ) ;
div . innerHTML = ' ^=expected, v=actual ' ;
document . body . appendChild ( div ) ;
document . body . appendChild ( actualImage ) ; / / to grab it for creating the test reference
* /
2011-11-11 01:36:13 +04:00
2013-08-12 08:48:58 +04:00
var actualCanvas = document . createElement ( ' canvas ' ) ;
actualCanvas . width = actualImage . width ;
actualCanvas . height = actualImage . height ;
var actualCtx = actualCanvas . getContext ( ' 2d ' ) ;
actualCtx . drawImage ( actualImage , 0 , 0 ) ;
var actual = actualCtx . getImageData ( 0 , 0 , actualImage . width , actualImage . height ) . data ;
var total = 0 ;
var width = img . width ;
var height = img . height ;
for ( var x = 0 ; x < width ; x + + ) {
for ( var y = 0 ; y < height ; y + + ) {
total + = Math . abs ( expected [ y * width * 4 + x * 4 + 0 ] - actual [ y * width * 4 + x * 4 + 0 ] ) ;
total + = Math . abs ( expected [ y * width * 4 + x * 4 + 1 ] - actual [ y * width * 4 + x * 4 + 1 ] ) ;
total + = Math . abs ( expected [ y * width * 4 + x * 4 + 2 ] - actual [ y * width * 4 + x * 4 + 2 ] ) ;
}
2011-11-12 03:30:21 +04:00
}
2013-08-12 08:48:58 +04:00
var wrong = Math . floor ( total / ( img . width * img . height * 3 ) ) ; / / floor , to allow some margin of error for antialiasing
2011-11-12 03:30:21 +04:00
2013-08-12 08:48:58 +04:00
xhr = new XMLHttpRequest ( ) ;
xhr . open ( ' GET ' , ' http://localhost:8888/report_result? ' + wrong ) ;
xhr . send ( ) ;
setTimeout ( function ( ) { window . close ( ) } , 1000 ) ;
2011-11-27 09:14:00 +04:00
} ;
2013-08-12 08:48:58 +04:00
actualImage . src = actualUrl ;
2013-05-21 03:45:27 +04:00
}
2013-08-12 08:48:58 +04:00
img . src = ' %s ' ;
} ;
Module [ ' postRun ' ] = doReftest ;
Module [ ' preRun ' ] . push ( function ( ) {
setTimeout ( doReftest , 1000 ) ; / / if run ( ) throws an exception and postRun is not called , this will kick in
} ) ;
''' % basename)
2013-05-21 03:45:27 +04:00
2013-08-12 08:48:58 +04:00
def btest ( self , filename , expected = None , reference = None , force_c = False , reference_slack = 0 ,
args = [ ] , outfile = ' test.html ' , message = ' . ' ) : # TODO: use in all other tests
# if we are provided the source and not a path, use that
filename_is_src = ' \n ' in filename
src = filename if filename_is_src else ' '
filepath = path_from_root ( ' tests ' , filename ) if not filename_is_src else ( ' main.c ' if force_c else ' main.cpp ' )
temp_filepath = os . path . join ( self . get_dir ( ) , os . path . basename ( filepath ) )
if filename_is_src :
with open ( temp_filepath , ' w ' ) as f : f . write ( src )
if not reference :
if not src :
with open ( filepath ) as f : src = f . read ( )
with open ( temp_filepath , ' w ' ) as f : f . write ( self . with_report_result ( src ) )
else :
expected = [ str ( i ) for i in range ( 0 , reference_slack + 1 ) ]
shutil . copyfile ( filepath , temp_filepath )
self . reftest ( path_from_root ( ' tests ' , reference ) )
args = args + [ ' --pre-js ' , ' reftest.js ' , ' -s ' , ' GL_TESTING=1 ' ]
Popen ( [ PYTHON , EMCC , temp_filepath , ' -o ' , outfile ] + args ) . communicate ( )
2013-08-15 21:50:51 +04:00
assert os . path . exists ( outfile )
2013-08-12 08:48:58 +04:00
if type ( expected ) is str : expected = [ expected ]
self . run_browser ( outfile , message , [ ' /report_result? ' + e for e in expected ] )
2012-08-25 22:26:31 +04:00
2013-08-12 08:48:58 +04:00
###################################################################################################
2012-08-25 22:26:31 +04:00
2013-08-12 08:48:58 +04:00
if __name__ == ' __main__ ' :
# Sanity checks
total_engines = len ( JS_ENGINES )
JS_ENGINES = filter ( check_engine , JS_ENGINES )
if len ( JS_ENGINES ) == 0 :
print ' WARNING: None of the JS engines in JS_ENGINES appears to work. '
elif len ( JS_ENGINES ) < total_engines :
print ' WARNING: Not all the JS engines in JS_ENGINES appears to work, ignoring those. '
2013-02-22 22:12:09 +04:00
2013-08-13 20:25:13 +04:00
# Create a list of modules to load tests from
modules = [ ]
for filename in glob . glob ( os . path . join ( os . path . dirname ( __file__ ) , ' test*.py ' ) ) :
module_dir , module_file = os . path . split ( filename )
module_name , module_ext = os . path . splitext ( module_file )
__import__ ( module_name )
modules . append ( sys . modules [ module_name ] )
# Extract the JS engine override from the arguments (used by benchmarks)
for i in range ( 1 , len ( sys . argv ) ) :
arg = sys . argv [ i ]
if arg . isupper ( ) :
print ' Interpreting all capital argument " %s " as JS_ENGINE override ' % arg
Building . JS_ENGINE_OVERRIDE = eval ( arg )
sys . argv [ i ] = None
sys . argv = filter ( lambda arg : arg is not None , sys . argv )
2013-08-12 08:48:58 +04:00
# If an argument comes in as test_*, treat it as a test of the default suite
sys . argv = map ( lambda arg : arg if not arg . startswith ( ' test_ ' ) else ' default. ' + arg , sys . argv )
2013-01-22 22:16:28 +04:00
2013-08-13 20:25:13 +04:00
# If a test (e.g. test_html) is specified as ALL.test_html, add an entry for each test_mode
if len ( sys . argv ) == 2 and sys . argv [ 1 ] . startswith ( ' ALL. ' ) :
ignore , test = sys . argv [ 1 ] . split ( ' . ' )
print ' Running all test modes on test " %s " ' % test
sys . argv = [ sys . argv [ 0 ] ] + map ( lambda mode : mode + ' . ' + test , test_modes )
# Skip requested tests
for i in range ( len ( sys . argv ) ) :
arg = sys . argv [ i ]
if arg . startswith ( ' skip: ' ) :
which = arg . split ( ' skip: ' ) [ 1 ]
if which . startswith ( ' ALL. ' ) :
ignore , test = which . split ( ' . ' )
which = map ( lambda mode : mode + ' . ' + test , test_modes )
else :
which = [ which ]
print >> sys . stderr , ' , ' . join ( which )
for test in which :
print >> sys . stderr , ' will skip " %s " ' % test
for m in modules :
try :
exec ( ' m. ' + test + ' = RunnerCore( " skipme " ) ' )
break
except :
pass
sys . argv [ i ] = None
sys . argv = filter ( lambda arg : arg is not None , sys . argv )
2013-08-12 08:48:58 +04:00
# If no tests were specified, run the core suite
if len ( sys . argv ) == 1 :
sys . argv = [ sys . argv [ 0 ] ] + map ( lambda mode : mode , test_modes )
print '''
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == ==
Running the main part of the test suite . Don ' t forget to run the other parts!
2013-01-22 22:16:28 +04:00
2013-08-20 02:23:48 +04:00
other - tests separate from the main suite
2013-08-12 08:48:58 +04:00
sanity - tests for first run , etc . , modifies ~ / . emscripten
benchmark - run before and after each set of changes before pushing to
master , verify no regressions
browser - runs pages in a web browser
sockets - runs websocket networking tests
2013-01-22 22:16:28 +04:00
2013-08-12 08:48:58 +04:00
There are also commands to run specific subsets of the test suite :
2013-01-22 22:16:28 +04:00
2013-08-12 08:48:58 +04:00
browser . audio - runs audio tests in a web browser ( requires human verification )
2013-01-22 22:16:28 +04:00
2013-08-12 08:48:58 +04:00
To run one of those parts , do something like
2013-01-22 22:16:28 +04:00
2013-08-12 08:48:58 +04:00
python tests / runner . py sanity
2013-01-22 22:16:28 +04:00
2013-08-12 08:48:58 +04:00
To run a specific set of tests , you can do things like
2012-05-29 14:43:49 +04:00
2013-08-12 08:48:58 +04:00
python tests / runner . py o1
2012-05-29 14:43:49 +04:00
2013-08-12 08:48:58 +04:00
( that runs the o1 ( - O1 ) tests ) . You can run individual tests with
2012-05-29 14:43:49 +04:00
2013-08-12 08:48:58 +04:00
python tests / runner . py test_hello_world
2012-05-29 14:43:49 +04:00
2013-08-12 08:48:58 +04:00
Combinations work too , for example
2013-03-20 07:04:38 +04:00
2013-08-12 08:48:58 +04:00
python tests / runner . py browser . test_sdl_image
2013-03-20 07:04:38 +04:00
2013-08-12 08:48:58 +04:00
In the main test suite , you can run all variations ( O0 , O1 , O2 , etc . ) of
an individual test with
2012-07-17 02:04:42 +04:00
2013-08-12 08:48:58 +04:00
python tests / runner . py ALL . test_hello_world
2013-01-18 08:21:32 +04:00
2013-08-12 08:48:58 +04:00
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == ==
2012-07-17 02:04:42 +04:00
2013-08-12 08:48:58 +04:00
'''
time . sleep ( 2 )
2012-07-17 02:04:42 +04:00
2013-08-12 08:48:58 +04:00
# Filter and load tests from the discovered modules
loader = unittest . TestLoader ( )
names = sys . argv [ 1 : ]
suites = [ ]
for m in modules :
try :
suites . append ( loader . loadTestsFromNames ( names , m ) )
except :
pass
2013-08-16 20:10:01 +04:00
numFailures = 0 # Keep count of the total number of failing tests.
2013-08-12 08:48:58 +04:00
# Run the discovered tests
if not len ( suites ) :
print >> sys . stderr , ' No tests found for %s ' % str ( sys . argv [ 1 : ] )
2013-08-16 20:10:01 +04:00
numFailures = 1
2013-08-12 08:48:58 +04:00
else :
testRunner = unittest . TextTestRunner ( verbosity = 2 )
for suite in suites :
2013-08-16 20:10:01 +04:00
results = testRunner . run ( suite )
numFailures + = len ( results . errors ) + len ( results . failures )
2013-08-15 21:50:51 +04:00
2013-08-16 20:10:01 +04:00
# Return the number of failures as the process exit code for automating success/failure reporting.
exit ( numFailures )