aboutsummaryrefslogtreecommitdiff
path: root/tests/test_other.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_other.py')
-rw-r--r--tests/test_other.py1913
1 files changed, 1913 insertions, 0 deletions
diff --git a/tests/test_other.py b/tests/test_other.py
new file mode 100644
index 00000000..b3e8a2a1
--- /dev/null
+++ b/tests/test_other.py
@@ -0,0 +1,1913 @@
+import multiprocessing, os, re, shutil, subprocess, sys
+import tools.shared
+from tools.shared import *
+from runner import RunnerCore, path_from_root
+
+class other(RunnerCore):
+ def test_emcc(self):
+ for compiler in [EMCC, EMXX]:
+ shortcompiler = os.path.basename(compiler)
+ suffix = '.c' if compiler == EMCC else '.cpp'
+
+ # --version
+ output = Popen([PYTHON, compiler, '--version'], stdout=PIPE, stderr=PIPE).communicate()
+ output = output[0].replace('\r', '')
+ self.assertContained('''emcc (Emscripten GCC-like replacement)''', output)
+ self.assertContained('''Copyright (C) 2013 the Emscripten authors (see AUTHORS.txt)
+This is free and open source software under the MIT license.
+There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+''', output)
+
+ # -v, without input files
+ output = Popen([PYTHON, compiler, '-v'], stdout=PIPE, stderr=PIPE).communicate()
+ self.assertContained('''clang version''', output[1].replace('\r', ''), output[1].replace('\r', ''))
+
+ # --help
+ output = Popen([PYTHON, compiler, '--help'], stdout=PIPE, stderr=PIPE).communicate()
+ self.assertContained('''%s [options] file...
+
+Most normal gcc/g++ options will work, for example:
+ --help Display this information
+ --version Display compiler version information
+
+Options that are modified or new in %s include:
+ -O0 No optimizations (default)
+''' % (shortcompiler, shortcompiler), output[0].replace('\r', ''), output[1].replace('\r', ''))
+
+ # emcc src.cpp ==> writes a.out.js
+ self.clear()
+ output = Popen([PYTHON, compiler, path_from_root('tests', 'hello_world' + suffix)], stdout=PIPE, stderr=PIPE).communicate()
+ assert len(output[0]) == 0, output[0]
+ assert os.path.exists('a.out.js'), '\n'.join(output)
+ self.assertContained('hello, world!', run_js('a.out.js'))
+
+ # properly report source code errors, and stop there
+ self.clear()
+ assert not os.path.exists('a.out.js')
+ process = Popen([PYTHON, compiler, path_from_root('tests', 'hello_world_error' + suffix)], stdout=PIPE, stderr=PIPE)
+ output = process.communicate()
+ assert not os.path.exists('a.out.js'), 'compilation failed, so no output file is expected'
+ assert len(output[0]) == 0, output[0]
+ assert process.returncode is not 0, 'Failed compilation must return a nonzero error code!'
+ self.assertNotContained('IOError', output[1]) # no python stack
+ self.assertNotContained('Traceback', output[1]) # no python stack
+ self.assertContained('error: invalid preprocessing directive', output[1])
+ self.assertContained(["error: use of undeclared identifier 'cheez", "error: unknown type name 'cheez'"], output[1])
+ self.assertContained('errors generated', output[1])
+ assert 'compiler frontend failed to generate LLVM bitcode, halting' in output[1].split('errors generated.')[1]
+
+ # emcc src.cpp -c and emcc src.cpp -o src.[o|bc] ==> should give a .bc file
+ # regression check: -o js should create "js", with bitcode content
+ for args in [['-c'], ['-o', 'src.o'], ['-o', 'src.bc'], ['-o', 'src.so'], ['-o', 'js']]:
+ target = args[1] if len(args) == 2 else 'hello_world.o'
+ self.clear()
+ Popen([PYTHON, compiler, path_from_root('tests', 'hello_world' + suffix)] + args, stdout=PIPE, stderr=PIPE).communicate()
+ syms = Building.llvm_nm(target)
+ assert len(syms.defs) == 1 and 'main' in syms.defs, 'Failed to generate valid bitcode'
+ if target == 'js': # make sure emcc can recognize the target as a bitcode file
+ shutil.move(target, target + '.bc')
+ target += '.bc'
+ output = Popen([PYTHON, compiler, target, '-o', target + '.js'], stdout = PIPE, stderr = PIPE).communicate()
+ assert len(output[0]) == 0, output[0]
+ assert os.path.exists(target + '.js'), 'Expected %s to exist since args are %s : %s' % (target + '.js', str(args), '\n'.join(output))
+ self.assertContained('hello, world!', run_js(target + '.js'))
+
+ # handle singleton archives
+ self.clear()
+ Popen([PYTHON, compiler, path_from_root('tests', 'hello_world' + suffix), '-o', 'a.bc'], stdout=PIPE, stderr=PIPE).communicate()
+ Popen([LLVM_AR, 'r', 'a.a', 'a.bc'], stdout=PIPE, stderr=PIPE).communicate()
+ assert os.path.exists('a.a')
+ output = Popen([PYTHON, compiler, 'a.a']).communicate()
+ assert os.path.exists('a.out.js'), output
+ self.assertContained('hello, world!', run_js('a.out.js'))
+
+ # emcc src.ll ==> generates .js
+ self.clear()
+ output = Popen([PYTHON, compiler, path_from_root('tests', 'hello_world.ll')], stdout=PIPE, stderr=PIPE).communicate()
+ assert len(output[0]) == 0, output[0]
+ assert os.path.exists('a.out.js'), '\n'.join(output)
+ self.assertContained('hello, world!', run_js('a.out.js'))
+
+ # emcc [..] -o [path] ==> should work with absolute paths
+ try:
+ for path in [os.path.abspath(os.path.join('..', 'file1.js')), os.path.join('b_dir', 'file2.js')]:
+ print path
+ self.clear(in_curr=True)
+ os.chdir(self.get_dir())
+ if not os.path.exists('a_dir'): os.mkdir('a_dir')
+ os.chdir('a_dir')
+ if not os.path.exists('b_dir'): os.mkdir('b_dir')
+ output = Popen([PYTHON, compiler, path_from_root('tests', 'hello_world.ll'), '-o', path], stdout=PIPE, stderr=PIPE).communicate()
+ print output
+ assert os.path.exists(path), path + ' does not exist; ' + '\n'.join(output)
+ last = os.getcwd()
+ os.chdir(os.path.dirname(path))
+ self.assertContained('hello, world!', run_js(os.path.basename(path)))
+ os.chdir(last)
+ finally:
+ os.chdir(self.get_dir())
+ self.clear()
+
+ # dlmalloc. dlmalloc is special in that it is the only part of libc that is (1) hard to write well, and
+ # very speed-sensitive. So we do not implement it in JS in library.js, instead we compile it from source
+ for source, has_malloc in [('hello_world' + suffix, False), ('hello_malloc.cpp', True)]:
+ print source, has_malloc
+ self.clear()
+ output = Popen([PYTHON, compiler, path_from_root('tests', source)], stdout=PIPE, stderr=PIPE).communicate()
+ assert os.path.exists('a.out.js'), '\n'.join(output)
+ self.assertContained('hello, world!', run_js('a.out.js'))
+ generated = open('a.out.js').read()
+ assert ('function _malloc(bytes) {' in generated) == (not has_malloc), 'If malloc is needed, it should be there, if not not'
+
+ # Optimization: emcc src.cpp -o something.js [-Ox]. -O0 is the same as not specifying any optimization setting
+ for params, opt_level, bc_params, closure, has_malloc in [ # bc params are used after compiling to bitcode
+ (['-o', 'something.js'], 0, None, 0, 1),
+ (['-o', 'something.js', '-O0'], 0, None, 0, 0),
+ (['-o', 'something.js', '-O1'], 1, None, 0, 0),
+ (['-o', 'something.js', '-O1', '-g'], 1, None, 0, 0), # no closure since debug
+ (['-o', 'something.js', '-O1', '--closure', '1'], 1, None, 1, 0),
+ (['-o', 'something.js', '-O1', '--closure', '1', '-s', 'ASM_JS=0'], 1, None, 1, 0),
+ (['-o', 'something.js', '-O2'], 2, None, 0, 1),
+ (['-o', 'something.js', '-O2', '-g'], 2, None, 0, 0),
+ (['-o', 'something.js', '-Os'], 2, None, 0, 1),
+ (['-o', 'something.js', '-O3', '-s', 'ASM_JS=0'], 3, None, 1, 1),
+ # and, test compiling to bitcode first
+ (['-o', 'something.bc'], 0, [], 0, 0),
+ (['-o', 'something.bc', '-O0'], 0, [], 0, 0),
+ (['-o', 'something.bc', '-O1'], 1, ['-O1'], 0, 0),
+ (['-o', 'something.bc', '-O2'], 2, ['-O2'], 0, 0),
+ (['-o', 'something.bc', '-O3'], 3, ['-O3', '-s', 'ASM_JS=0'], 1, 0),
+ (['-O1', '-o', 'something.bc'], 1, [], 0, 0),
+ ]:
+ print params, opt_level, bc_params, closure, has_malloc
+ self.clear()
+ keep_debug = '-g' in params
+ args = [PYTHON, compiler, path_from_root('tests', 'hello_world_loop' + ('_malloc' if has_malloc else '') + '.cpp')] + params
+ print '..', args
+ output = Popen(args,
+ stdout=PIPE, stderr=PIPE).communicate()
+ assert len(output[0]) == 0, output[0]
+ if bc_params is not None:
+ assert os.path.exists('something.bc'), output[1]
+ bc_args = [PYTHON, compiler, 'something.bc', '-o', 'something.js'] + bc_params
+ print '....', bc_args
+ output = Popen(bc_args, stdout=PIPE, stderr=PIPE).communicate()
+ assert os.path.exists('something.js'), output[1]
+ assert ('Applying some potentially unsafe optimizations!' in output[1]) == (opt_level >= 3), 'unsafe warning should appear in opt >= 3'
+ self.assertContained('hello, world!', run_js('something.js'))
+
+ # Verify optimization level etc. in the generated code
+ # XXX these are quite sensitive, and will need updating when code generation changes
+ generated = open('something.js').read() # TODO: parse out the _main function itself, not support code, if the tests below need that some day
+ assert 'new Uint16Array' in generated and 'new Uint32Array' in generated, 'typed arrays 2 should be used by default'
+ assert 'SAFE_HEAP' not in generated, 'safe heap should not be used by default'
+ assert ': while(' not in generated, 'when relooping we also js-optimize, so there should be no labelled whiles'
+ if closure:
+ if opt_level == 0: assert '._main =' in generated, 'closure compiler should have been run'
+ elif opt_level >= 1: assert '._main=' in generated, 'closure compiler should have been run (and output should be minified)'
+ else:
+ # closure has not been run, we can do some additional checks. TODO: figure out how to do these even with closure
+ assert '._main = ' not in generated, 'closure compiler should not have been run'
+ if keep_debug:
+ assert ('(label)' in generated or '(label | 0)' in generated) == (opt_level <= 1), 'relooping should be in opt >= 2'
+ assert ('assert(STACKTOP < STACK_MAX' in generated) == (opt_level == 0), 'assertions should be in opt == 0'
+ assert 'var $i;' in generated or 'var $i_0' in generated or 'var $storemerge3;' in generated or 'var $storemerge4;' in generated or 'var $i_04;' in generated or 'var $original = 0' in generated, 'micro opts should always be on'
+ if opt_level >= 2 and '-g' in params:
+ assert re.search('HEAP8\[\$?\w+ ?\+ ?\(+\$?\w+ ?', generated) or re.search('HEAP8\[HEAP32\[', generated), 'eliminator should create compound expressions, and fewer one-time vars' # also in -O1, but easier to test in -O2
+ assert ('_puts(' in generated) == (opt_level >= 1), 'with opt >= 1, llvm opts are run and they should optimize printf to puts'
+ if opt_level == 0 or '-g' in params: assert 'function _main() {' in generated, 'Should be unminified, including whitespace'
+ elif opt_level >= 2: assert ('function _main(){' in generated or '"use asm";var a=' in generated), 'Should be whitespace-minified'
+
+ # emcc -s RELOOP=1 src.cpp ==> should pass -s to emscripten.py. --typed-arrays is a convenient alias for -s USE_TYPED_ARRAYS
+ for params, test, text in [
+ (['-O2'], lambda generated: 'function intArrayToString' in generated, 'shell has unminified utilities'),
+ (['-O2', '--closure', '1'], lambda generated: 'function intArrayToString' not in generated, 'closure minifies the shell'),
+ (['-O2'], lambda generated: 'var b=0' in generated and not 'function _main' in generated, 'registerize/minify is run by default in -O2'),
+ (['-O2', '--minify', '0'], lambda generated: 'var b = 0' in generated and not 'function _main' in generated, 'minify is cancelled, but not registerize'),
+ (['-O2', '--js-opts', '0'], lambda generated: 'var b=0' not in generated and 'var b = 0' not in generated and 'function _main' in generated, 'js opts are cancelled'),
+ (['-O2', '-g'], lambda generated: 'var b=0' not in generated and 'var b = 0' not in generated and 'function _main' in generated, 'registerize/minify is cancelled by -g'),
+ (['-O2', '-g0'], lambda generated: 'var b=0' in generated and not 'function _main' in generated, 'registerize/minify is run by default in -O2 -g0'),
+ (['-O2', '-g1'], lambda generated: 'var b = 0' in generated and not 'function _main' in generated, 'compress is cancelled by -g1'),
+ (['-O2', '-g2'], lambda generated: ('var b = 0' in generated or 'var i1 = 0' in generated) and 'function _main' in generated, 'minify is cancelled by -g2'),
+ (['-O2', '-g3'], lambda generated: 'var b=0' not in generated and 'var b = 0' not in generated and 'function _main' in generated, 'registerize is cancelled by -g3'),
+ #(['-O2', '-g4'], lambda generated: 'var b=0' not in generated and 'var b = 0' not in generated and 'function _main' in generated, 'same as -g3 for now'),
+ (['-s', 'INLINING_LIMIT=0'], lambda generated: 'function _dump' in generated, 'no inlining without opts'),
+ (['-O3', '-s', 'INLINING_LIMIT=0', '--closure', '0'], lambda generated: 'function _dump' not in generated, 'lto/inlining'),
+ (['-Os', '--llvm-lto', '1', '-s', 'ASM_JS=0'], lambda generated: 'function _dump' in generated, '-Os disables inlining'),
+ (['-s', 'USE_TYPED_ARRAYS=0'], lambda generated: 'new Int32Array' not in generated, 'disable typed arrays'),
+ (['-s', 'USE_TYPED_ARRAYS=1'], lambda generated: 'IHEAPU = ' in generated, 'typed arrays 1 selected'),
+ ([], lambda generated: 'Module["_dump"]' not in generated, 'dump is not exported by default'),
+ (['-s', 'EXPORTED_FUNCTIONS=["_main", "_dump"]'], lambda generated: 'Module["_dump"]' in generated, 'dump is now exported'),
+ (['--typed-arrays', '0'], lambda generated: 'new Int32Array' not in generated, 'disable typed arrays'),
+ (['--typed-arrays', '1'], lambda generated: 'IHEAPU = ' in generated, 'typed arrays 1 selected'),
+ (['--typed-arrays', '2'], lambda generated: 'new Uint16Array' in generated and 'new Uint32Array' in generated, 'typed arrays 2 selected'),
+ (['--llvm-opts', '1'], lambda generated: '_puts(' in generated, 'llvm opts requested'),
+ ]:
+ print params, text
+ self.clear()
+ output = Popen([PYTHON, compiler, path_from_root('tests', 'hello_world_loop.cpp'), '-o', 'a.out.js'] + params, stdout=PIPE, stderr=PIPE).communicate()
+ assert len(output[0]) == 0, output[0]
+ assert os.path.exists('a.out.js'), '\n'.join(output)
+ self.assertContained('hello, world!', run_js('a.out.js'))
+ assert test(open('a.out.js').read()), text
+
+ # Compiling two source files into a final JS.
+ for args, target in [([], 'a.out.js'), (['-o', 'combined.js'], 'combined.js')]:
+ self.clear()
+ output = Popen([PYTHON, compiler, path_from_root('tests', 'twopart_main.cpp'), path_from_root('tests', 'twopart_side.cpp')] + args,
+ stdout=PIPE, stderr=PIPE).communicate()
+ assert len(output[0]) == 0, output[0]
+ assert os.path.exists(target), '\n'.join(output)
+ self.assertContained('side got: hello from main, over', run_js(target))
+
+ # Compiling two files with -c will generate separate .bc files
+ self.clear()
+ output = Popen([PYTHON, compiler, path_from_root('tests', 'twopart_main.cpp'), path_from_root('tests', 'twopart_side.cpp'), '-c'] + args,
+ stdout=PIPE, stderr=PIPE).communicate()
+ if '-o' in args:
+ # specifying -o and -c is an error
+ assert 'fatal error' in output[1], output[1]
+ continue
+
+ assert os.path.exists('twopart_main.o'), '\n'.join(output)
+ assert os.path.exists('twopart_side.o'), '\n'.join(output)
+ assert not os.path.exists(target), 'We should only have created bitcode here: ' + '\n'.join(output)
+
+ # Compiling one of them alone is expected to fail
+ output = Popen([PYTHON, compiler, 'twopart_main.o', '-O1', '-g'] + args, stdout=PIPE, stderr=PIPE).communicate()
+ assert os.path.exists(target), '\n'.join(output)
+ #print '\n'.join(output)
+ self.assertContained('missing function', run_js(target, stderr=STDOUT))
+ try_delete(target)
+
+ # Combining those bc files into js should work
+ output = Popen([PYTHON, compiler, 'twopart_main.o', 'twopart_side.o'] + args, stdout=PIPE, stderr=PIPE).communicate()
+ assert os.path.exists(target), '\n'.join(output)
+ self.assertContained('side got: hello from main, over', run_js(target))
+
+ # Combining bc files into another bc should also work
+ try_delete(target)
+ assert not os.path.exists(target)
+ output = Popen([PYTHON, compiler, 'twopart_main.o', 'twopart_side.o', '-o', 'combined.bc'] + args, stdout=PIPE, stderr=PIPE).communicate()
+ syms = Building.llvm_nm('combined.bc')
+ assert len(syms.defs) == 2 and 'main' in syms.defs, 'Failed to generate valid bitcode'
+ output = Popen([PYTHON, compiler, 'combined.bc', '-o', 'combined.bc.js'], stdout = PIPE, stderr = PIPE).communicate()
+ assert len(output[0]) == 0, output[0]
+ assert os.path.exists('combined.bc.js'), 'Expected %s to exist' % ('combined.bc.js')
+ self.assertContained('side got: hello from main, over', run_js('combined.bc.js'))
+
+ # --js-transform <transform>
+ self.clear()
+ trans = os.path.join(self.get_dir(), 't.py')
+ trans_file = open(trans, 'w')
+ trans_file.write('''
+import sys
+f = open(sys.argv[1], 'w')
+f.write('transformed!')
+f.close()
+''')
+ trans_file.close()
+ output = Popen([PYTHON, compiler, path_from_root('tests', 'hello_world' + suffix), '--js-transform', '%s t.py' % (PYTHON)], stdout=PIPE, stderr=PIPE).communicate()
+ assert open('a.out.js').read() == 'transformed!', 'Transformed output must be as expected'
+
+ # TODO: Add in files test a clear example of using disablePermissions, and link to it from the wiki
+ # TODO: test normal project linking, static and dynamic: get_library should not need to be told what to link!
+ # TODO: deprecate llvm optimizations, dlmalloc, etc. in emscripten.py.
+
+ def test_cmake(self):
+ # On Windows, we want to build cmake-generated Makefiles with mingw32-make instead of e.g. cygwin make, since mingw32-make
+ # understands Windows paths, and cygwin make additionally produces a cryptic 'not valid bitcode file' errors on files that
+ # *are* valid bitcode files.
+
+ if os.name == 'nt':
+ make_command = 'mingw32-make'
+ emscriptencmaketoolchain = path_from_root('cmake', 'Platform', 'Emscripten.cmake')
+ else:
+ make_command = 'make'
+ emscriptencmaketoolchain = path_from_root('cmake', 'Platform', 'Emscripten_unix.cmake')
+
+ cmake_cases = ['target_js', 'target_html']
+ cmake_outputs = ['hello_world.js', 'hello_world_gles.html']
+ for i in range(0, 2):
+ for configuration in ['Debug', 'Release']:
+
+ # Create a temp workspace folder
+ cmakelistsdir = path_from_root('tests', 'cmake', cmake_cases[i])
+ tempdirname = tempfile.mkdtemp(prefix='emscripten_test_' + self.__class__.__name__ + '_', dir=TEMP_DIR)
+ try:
+ os.chdir(tempdirname)
+
+ # Run Cmake
+ cmd = ['cmake', '-DCMAKE_TOOLCHAIN_FILE='+emscriptencmaketoolchain,
+ '-DCMAKE_BUILD_TYPE=' + configuration,
+ '-DCMAKE_MODULE_PATH=' + path_from_root('cmake').replace('\\', '/'),
+ '-G' 'Unix Makefiles', cmakelistsdir]
+ ret = Popen(cmd, stdout=PIPE, stderr=PIPE).communicate()
+ if ret[1] != None and len(ret[1].strip()) > 0:
+ print >> sys.stderr, ret[1] # If there were any errors, print them directly to console for diagnostics.
+ if 'error' in ret[1].lower():
+ print >> sys.stderr, 'Failed command: ' + ' '.join(cmd)
+ print >> sys.stderr, 'Result:\n' + ret[1]
+ raise Exception('cmake call failed!')
+ assert os.path.exists(tempdirname + '/Makefile'), 'CMake call did not produce a Makefile!'
+
+ # Build
+ cmd = [make_command]
+ ret = Popen(cmd, stdout=PIPE).communicate()
+ if ret[1] != None and len(ret[1].strip()) > 0:
+ print >> sys.stderr, ret[1] # If there were any errors, print them directly to console for diagnostics.
+ if 'error' in ret[0].lower() and not '0 error(s)' in ret[0].lower():
+ print >> sys.stderr, 'Failed command: ' + ' '.join(cmd)
+ print >> sys.stderr, 'Result:\n' + ret[0]
+ raise Exception('make failed!')
+ assert os.path.exists(tempdirname + '/' + cmake_outputs[i]), 'Building a cmake-generated Makefile failed to produce an output file %s!' % tempdirname + '/' + cmake_outputs[i]
+
+ # Run through node, if CMake produced a .js file.
+ if cmake_outputs[i].endswith('.js'):
+ ret = Popen(listify(NODE_JS) + [tempdirname + '/' + cmake_outputs[i]], stdout=PIPE).communicate()[0]
+ assert 'hello, world!' in ret, 'Running cmake-based .js application failed!'
+ finally:
+ os.chdir(path_from_root('tests')) # Move away from the directory we are about to remove.
+ shutil.rmtree(tempdirname)
+
+ def test_nostdincxx(self):
+ try:
+ old = os.environ.get('EMCC_LLVM_TARGET') or ''
+ for compiler in [EMCC, EMXX]:
+ for target in ['i386-pc-linux-gnu', 'le32-unknown-nacl']:
+ print compiler, target
+ os.environ['EMCC_LLVM_TARGET'] = target
+ out, err = Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world.cpp'), '-v'], stdout=PIPE, stderr=PIPE).communicate()
+ out2, err2 = Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world.cpp'), '-v', '-nostdinc++'], stdout=PIPE, stderr=PIPE).communicate()
+ assert out == out2
+ def focus(e):
+ assert 'search starts here:' in e, e
+ assert e.count('End of search list.') == 1, e
+ return e[e.index('search starts here:'):e.index('End of search list.')+20]
+ err = focus(err)
+ err2 = focus(err2)
+ assert err == err2, err + '\n\n\n\n' + err2
+ finally:
+ if old:
+ os.environ['EMCC_LLVM_TARGET'] = old
+
+ def test_failure_error_code(self):
+ for compiler in [EMCC, EMXX]:
+ # Test that if one file is missing from the build, then emcc shouldn't succeed, and shouldn't try to produce an output file.
+ process = Popen([PYTHON, compiler, path_from_root('tests', 'hello_world.c'), 'this_file_is_missing.c', '-o', 'this_output_file_should_never_exist.js'], stdout=PIPE, stderr=PIPE)
+ process.communicate()
+ assert process.returncode is not 0, 'Trying to compile a nonexisting file should return with a nonzero error code!'
+ assert os.path.exists('this_output_file_should_never_exist.js') == False, 'Emcc should not produce an output file when build fails!'
+
+ def test_cxx03(self):
+ for compiler in [EMCC, EMXX]:
+ process = Popen([PYTHON, compiler, path_from_root('tests', 'hello_cxx03.cpp')], stdout=PIPE, stderr=PIPE)
+ process.communicate()
+ assert process.returncode is 0, 'By default, emscripten should build using -std=c++03!'
+
+ def test_cxx11(self):
+ for compiler in [EMCC, EMXX]:
+ process = Popen([PYTHON, compiler, '-std=c++11', path_from_root('tests', 'hello_cxx11.cpp')], stdout=PIPE, stderr=PIPE)
+ process.communicate()
+ assert process.returncode is 0, 'User should be able to specify custom -std= on the command line!'
+
+ def test_catch_undef(self):
+ open(os.path.join(self.get_dir(), 'test.cpp'), 'w').write(r'''
+ #include <vector>
+ #include <stdio.h>
+
+ class Test {
+ public:
+ std::vector<int> vector;
+ };
+
+ Test globalInstance;
+
+ int main() {
+ printf("hello, world!\n");
+ return 0;
+ }
+ ''')
+ Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'test.cpp'), '-fsanitize=undefined']).communicate()
+ self.assertContained('hello, world!', run_js(os.path.join(self.get_dir(), 'a.out.js')))
+
+ def test_unaligned_memory(self):
+ open(os.path.join(self.get_dir(), 'test.cpp'), 'w').write(r'''
+ #include <stdio.h>
+
+ typedef unsigned char Bit8u;
+ typedef unsigned short Bit16u;
+ typedef unsigned int Bit32u;
+
+ int main()
+ {
+ Bit8u data[4] = {0x01,0x23,0x45,0x67};
+
+ printf("data: %x\n", *(Bit32u*)data);
+ printf("data[0,1] 16bit: %x\n", *(Bit16u*)data);
+ printf("data[1,2] 16bit: %x\n", *(Bit16u*)(data+1));
+ }
+ ''')
+ Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'test.cpp'), '-s', 'UNALIGNED_MEMORY=1']).communicate()
+ self.assertContained('data: 67452301\ndata[0,1] 16bit: 2301\ndata[1,2] 16bit: 4523', run_js(os.path.join(self.get_dir(), 'a.out.js')))
+
+ def test_unaligned_memory_2(self):
+ open(os.path.join(self.get_dir(), 'test.cpp'), 'w').write(r'''
+ #include <string>
+ #include <stdio.h>
+
+ int main( int argc, char ** argv )
+ {
+ std::string testString( "Hello, World!" );
+
+ printf( "testString = %s\n", testString.c_str() );
+ return 0;
+ }
+ ''')
+ Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'test.cpp'), '-s', 'UNALIGNED_MEMORY=1']).communicate()
+ self.assertContained('testString = Hello, World!', run_js(os.path.join(self.get_dir(), 'a.out.js')))
+
+ def test_asm_minify(self):
+ def test(args):
+ Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world_loop_malloc.cpp')] + args).communicate()
+ self.assertContained('hello, world!', run_js(self.in_dir('a.out.js')))
+ return open(self.in_dir('a.out.js')).read()
+
+ src = test([])
+ assert 'function _malloc' in src
+
+ src = test(['-O2', '-s', 'ASM_JS=1'])
+ normal_size = len(src)
+ print 'normal', normal_size
+ assert 'function _malloc' not in src
+
+ src = test(['-O2', '-s', 'ASM_JS=1', '--minify', '0'])
+ unminified_size = len(src)
+ print 'unminified', unminified_size
+ assert unminified_size > normal_size
+ assert 'function _malloc' not in src
+
+ src = test(['-O2', '-s', 'ASM_JS=1', '-g'])
+ debug_size = len(src)
+ print 'debug', debug_size
+ assert debug_size > unminified_size
+ assert 'function _malloc' in src
+
+ def test_dangerous_func_cast(self):
+ src = r'''
+ #include <stdio.h>
+ typedef void (*voidfunc)();
+ int my_func() {
+ printf("my func\n");
+ return 10;
+ }
+ int main(int argc, char **argv) {
+ voidfunc fps[10];
+ for (int i = 0; i < 10; i++) fps[i] = (i == argc) ? (void (*)())my_func : NULL;
+ fps[2*(argc-1) + 1]();
+ return 0;
+ }
+ '''
+ open('src.c', 'w').write(src)
+ def test(args, expected, err_expected=None):
+ out, err = Popen([PYTHON, EMCC, 'src.c'] + args, stderr=PIPE).communicate()
+ if err_expected: self.assertContained(err_expected, err)
+ self.assertContained(expected, run_js(self.in_dir('a.out.js'), stderr=PIPE, full_output=True))
+ return open(self.in_dir('a.out.js')).read()
+
+ test([], 'my func') # no asm, so casting func works
+ test(['-O2'], 'abort', ['Casting potentially incompatible function pointer i32 ()* to void (...)*, for my_func',
+ 'Incompatible function pointer casts are very dangerous with ASM_JS=1, you should investigate and correct these']) # asm, so failure
+ test(['-O2', '-s', 'ASSERTIONS=1'],
+ 'Invalid function pointer called. Perhaps a miscast function pointer (check compilation warnings) or bad vtable lookup (maybe due to derefing a bad pointer, like NULL)?',
+ ['Casting potentially incompatible function pointer i32 ()* to void (...)*, for my_func',
+ 'Incompatible function pointer casts are very dangerous with ASM_JS=1, you should investigate and correct these']) # asm, so failure
+
+ def test_l_link(self):
+ # Linking with -lLIBNAME and -L/DIRNAME should work
+
+ open(os.path.join(self.get_dir(), 'main.cpp'), 'w').write('''
+ extern void printey();
+ int main() {
+ printey();
+ return 0;
+ }
+ ''')
+
+ try:
+ os.makedirs(os.path.join(self.get_dir(), 'libdir'));
+ except:
+ pass
+
+ open(os.path.join(self.get_dir(), 'libdir', 'libfile.cpp'), 'w').write('''
+ #include <stdio.h>
+ void printey() {
+ printf("hello from lib\\n");
+ }
+ ''')
+
+ Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'libdir', 'libfile.cpp'), '-c']).communicate()
+ shutil.move(os.path.join(self.get_dir(), 'libfile.o'), os.path.join(self.get_dir(), 'libdir', 'libfile.so'))
+ Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'main.cpp'), '-L' + os.path.join(self.get_dir(), 'libdir'), '-lfile']).communicate()
+ self.assertContained('hello from lib', run_js(os.path.join(self.get_dir(), 'a.out.js')))
+ assert not os.path.exists('a.out') and not os.path.exists('a.exe'), 'Must not leave unneeded linker stubs'
+
+ def test_static_link(self):
+ def test(name, header, main, side, expected, args=[], suffix='cpp', first=True):
+ print name
+ #t = main ; main = side ; side = t
+ original_main = main
+ original_side = side
+ if header: open(os.path.join(self.get_dir(), 'header.h'), 'w').write(header)
+ if type(main) == str:
+ open(os.path.join(self.get_dir(), 'main.' + suffix), 'w').write(main)
+ main = ['main.' + suffix]
+ if type(side) == str:
+ open(os.path.join(self.get_dir(), 'side.' + suffix), 'w').write(side)
+ side = ['side.' + suffix]
+ Popen([PYTHON, EMCC] + side + ['-o', 'side.js', '-s', 'SIDE_MODULE=1', '-O2'] + args).communicate()
+ # TODO: test with and without DISABLE_GL_EMULATION, check that file sizes change
+ Popen([PYTHON, EMCC] + main + ['-o', 'main.js', '-s', 'MAIN_MODULE=1', '-O2', '-s', 'DISABLE_GL_EMULATION=1'] + args).communicate()
+ Popen([PYTHON, EMLINK, 'main.js', 'side.js', 'together.js'], stdout=PIPE).communicate()
+ assert os.path.exists('together.js')
+ for engine in JS_ENGINES:
+ out = run_js('together.js', engine=engine, stderr=PIPE, full_output=True)
+ self.assertContained(expected, out)
+ if engine == SPIDERMONKEY_ENGINE: self.validate_asmjs(out)
+ if first:
+ shutil.copyfile('together.js', 'first.js')
+ test(name + ' (reverse)', header, original_side, original_main, expected, args, suffix, False) # test reverse order
+
+ # test a simple call from one module to another. only one has a string (and constant memory initialization for it)
+ test('basics', '', '''
+ #include <stdio.h>
+ extern int sidey();
+ int main() {
+ printf("other says %d.", sidey());
+ return 0;
+ }
+ ''', '''
+ int sidey() { return 11; }
+ ''', 'other says 11.')
+
+ # finalization of float variables should pass asm.js validation
+ test('floats', '', '''
+ #include <stdio.h>
+ extern float sidey();
+ int main() {
+ printf("other says %.2f.", sidey()+1);
+ return 0;
+ }
+ ''', '''
+ float sidey() { return 11.5; }
+ ''', 'other says 12.50')
+
+ # memory initialization in both
+ test('multiple memory inits', '', r'''
+ #include <stdio.h>
+ extern void sidey();
+ int main() {
+ printf("hello from main\n");
+ sidey();
+ return 0;
+ }
+ ''', r'''
+ #include <stdio.h>
+ void sidey() { printf("hello from side\n"); }
+ ''', 'hello from main\nhello from side\n')
+
+ # function pointers
+ test('fp1', 'typedef void (*voidfunc)();', r'''
+ #include <stdio.h>
+ #include "header.h"
+ voidfunc sidey(voidfunc f);
+ void a() { printf("hello from funcptr\n"); }
+ int main() {
+ sidey(a)();
+ return 0;
+ }
+ ''', '''
+ #include "header.h"
+ voidfunc sidey(voidfunc f) { return f; }
+ ''', 'hello from funcptr\n')
+
+ # function pointers with 'return' in the name
+ test('fp2', 'typedef void (*voidfunc)();', r'''
+ #include <stdio.h>
+ #include "header.h"
+ int sidey(voidfunc f);
+ void areturn0() { printf("hello 0\n"); }
+ void areturn1() { printf("hello 1\n"); }
+ void areturn2() { printf("hello 2\n"); }
+ int main(int argc, char **argv) {
+ voidfunc table[3] = { areturn0, areturn1, areturn2 };
+ table[sidey(NULL)]();
+ return 0;
+ }
+ ''', '''
+ #include "header.h"
+ int sidey(voidfunc f) { if (f) f(); return 1; }
+ ''', 'hello 1\n')
+
+ # Global initializer
+ test('global init', '', r'''
+ #include <stdio.h>
+ struct Class {
+ Class() { printf("a new Class\n"); }
+ };
+ static Class c;
+ int main() {
+ return 0;
+ }
+ ''', r'''
+ void nothing() {}
+ ''', 'a new Class\n')
+
+ # Multiple global initializers (LLVM generates overlapping names for them)
+ test('global inits', r'''
+ #include <stdio.h>
+ struct Class {
+ Class(const char *name) { printf("new %s\n", name); }
+ };
+ ''', r'''
+ #include "header.h"
+ static Class c("main");
+ int main() {
+ return 0;
+ }
+ ''', r'''
+ #include "header.h"
+ static Class c("side");
+ ''', ['new main\nnew side\n', 'new side\nnew main\n'])
+
+ # Class code used across modules
+ test('codecall', r'''
+ #include <stdio.h>
+ struct Class {
+ Class(const char *name);
+ };
+ ''', r'''
+ #include "header.h"
+ int main() {
+ Class c("main");
+ return 0;
+ }
+ ''', r'''
+ #include "header.h"
+ Class::Class(const char *name) { printf("new %s\n", name); }
+ ''', ['new main\n'])
+
+ # malloc usage in both modules
+ test('malloc', r'''
+ #include <stdlib.h>
+ #include <string.h>
+ char *side(const char *data);
+ ''', r'''
+ #include <stdio.h>
+ #include "header.h"
+ int main() {
+ char *temp = side("hello through side\n");
+ char *ret = (char*)malloc(strlen(temp)+1);
+ strcpy(ret, temp);
+ temp[1] = 'x';
+ puts(ret);
+ return 0;
+ }
+ ''', r'''
+ #include "header.h"
+ char *side(const char *data) {
+ char *ret = (char*)malloc(strlen(data)+1);
+ strcpy(ret, data);
+ return ret;
+ }
+ ''', ['hello through side\n'])
+
+ # libc usage in one modules. must force libc inclusion in the main module if that isn't the one using mallinfo()
+ try:
+ os.environ['EMCC_FORCE_STDLIBS'] = 'libc'
+ test('malloc-1', r'''
+ #include <string.h>
+ int side();
+ ''', r'''
+ #include <stdio.h>
+ #include "header.h"
+ int main() {
+ printf("|%d|\n", side());
+ return 0;
+ }
+ ''', r'''
+ #include <stdlib.h>
+ #include <malloc.h>
+ #include "header.h"
+ int side() {
+ struct mallinfo m = mallinfo();
+ return m.arena > 1;
+ }
+ ''', ['|1|\n'])
+ finally:
+ del os.environ['EMCC_FORCE_STDLIBS']
+
+ # iostream usage in one and std::string in both
+ test('iostream', r'''
+ #include <iostream>
+ #include <string>
+ std::string side();
+ ''', r'''
+ #include "header.h"
+ int main() {
+ std::cout << "hello from main " << side() << std::endl;
+ return 0;
+ }
+ ''', r'''
+ #include "header.h"
+ std::string side() { return "and hello from side"; }
+ ''', ['hello from main and hello from side\n'])
+
+ # followup to iostream test: a second linking
+ print 'second linking of a linking output'
+ open('moar.cpp', 'w').write(r'''
+ #include <iostream>
+ struct Moar {
+ Moar() { std::cout << "moar!" << std::endl; }
+ };
+ Moar m;
+ ''')
+ Popen([PYTHON, EMCC, 'moar.cpp', '-o', 'moar.js', '-s', 'SIDE_MODULE=1', '-O2']).communicate()
+ Popen([PYTHON, EMLINK, 'together.js', 'moar.js', 'triple.js'], stdout=PIPE).communicate()
+ assert os.path.exists('triple.js')
+ for engine in JS_ENGINES:
+ out = run_js('triple.js', engine=engine, stderr=PIPE, full_output=True)
+ self.assertContained('moar!\nhello from main and hello from side\n', out)
+ if engine == SPIDERMONKEY_ENGINE: self.validate_asmjs(out)
+
+ # zlib compression library. tests function pointers in initializers and many other things
+ test('zlib', '', open(path_from_root('tests', 'zlib', 'example.c'), 'r').read(),
+ self.get_library('zlib', os.path.join('libz.a'), make_args=['libz.a']),
+ open(path_from_root('tests', 'zlib', 'ref.txt'), 'r').read(),
+ args=['-I' + path_from_root('tests', 'zlib')], suffix='c')
+
+ # bullet physics engine. tests all the things
+ test('bullet', '', open(path_from_root('tests', 'bullet', 'Demos', 'HelloWorld', 'HelloWorld.cpp'), 'r').read(),
+ self.get_library('bullet', [os.path.join('src', '.libs', 'libBulletDynamics.a'),
+ os.path.join('src', '.libs', 'libBulletCollision.a'),
+ os.path.join('src', '.libs', 'libLinearMath.a')]),
+ [open(path_from_root('tests', 'bullet', 'output.txt'), 'r').read(), # different roundings
+ open(path_from_root('tests', 'bullet', 'output2.txt'), 'r').read(),
+ open(path_from_root('tests', 'bullet', 'output3.txt'), 'r').read()],
+ args=['-I' + path_from_root('tests', 'bullet', 'src')])
+
+
+ def test_outline(self):
+ def test(name, src, libs, expected, expected_ranges, args=[], suffix='cpp'):
+ print name
+
+ def measure_funcs(filename):
+ i = 0
+ start = -1
+ curr = None
+ ret = {}
+ for line in open(filename):
+ i += 1
+ if line.startswith('function '):
+ start = i
+ curr = line
+ elif line.startswith('}') and curr:
+ size = i - start
+ ret[curr] = size
+ curr = None
+ return ret
+
+ for debug, outlining_limits in [
+ ([], (1000,)),
+ (['-g1'], (1000,)),
+ (['-g2'], (1000,)),
+ (['-g'], (100, 250, 500, 1000, 2000, 5000, 0))
+ ]:
+ for outlining_limit in outlining_limits:
+ print '\n', Building.COMPILER_TEST_OPTS, debug, outlining_limit, '\n'
+ # TODO: test without -g3, tell all sorts
+ Popen([PYTHON, EMCC, src] + libs + ['-o', 'test.js', '-O2'] + debug + ['-s', 'OUTLINING_LIMIT=%d' % outlining_limit] + args).communicate()
+ assert os.path.exists('test.js')
+ shutil.copyfile('test.js', '%d_test.js' % outlining_limit)
+ for engine in JS_ENGINES:
+ out = run_js('test.js', engine=engine, stderr=PIPE, full_output=True)
+ self.assertContained(expected, out)
+ if engine == SPIDERMONKEY_ENGINE: self.validate_asmjs(out)
+ if debug == ['-g']:
+ low = expected_ranges[outlining_limit][0]
+ seen = max(measure_funcs('test.js').values())
+ high = expected_ranges[outlining_limit][1]
+ print Building.COMPILER_TEST_OPTS, outlining_limit, ' ', low, '<=', seen, '<=', high
+ assert low <= seen <= high
+
+ for test_opts, expected_ranges in [
+ ([], {
+ 100: (190, 250),
+ 250: (200, 330),
+ 500: (250, 500),
+ 1000: (230, 1000),
+ 2000: (380, 2000),
+ 5000: (800, 5000),
+ 0: (1500, 5000)
+ }),
+ (['-O2'], {
+ 100: (0, 1500),
+ 250: (0, 1500),
+ 500: (0, 1500),
+ 1000: (0, 1500),
+ 2000: (0, 2000),
+ 5000: (0, 5000),
+ 0: (0, 5000)
+ }),
+ ]:
+ Building.COMPILER_TEST_OPTS = test_opts
+ test('zlib', path_from_root('tests', 'zlib', 'example.c'),
+ self.get_library('zlib', os.path.join('libz.a'), make_args=['libz.a']),
+ open(path_from_root('tests', 'zlib', 'ref.txt'), 'r').read(),
+ expected_ranges,
+ args=['-I' + path_from_root('tests', 'zlib')], suffix='c')
+
+ def test_symlink(self):
+ if os.name == 'nt':
+ return self.skip('Windows FS does not need to be tested for symlinks support, since it does not have them.')
+ open(os.path.join(self.get_dir(), 'foobar.xxx')