diff options
Diffstat (limited to 'tests/test_other.py')
-rw-r--r-- | tests/test_other.py | 1913 |
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') |