| # Copyright 2013 The Emscripten Authors. All rights reserved. |
| # Emscripten is available under two separate licenses, the MIT license and the |
| # University of Illinois/NCSA Open Source License. Both these licenses can be |
| # found in the LICENSE file. |
| |
| from __future__ import print_function |
| import glob |
| import hashlib |
| import json |
| import os |
| import random |
| import re |
| import shutil |
| import sys |
| import time |
| import unittest |
| from subprocess import PIPE, STDOUT |
| from functools import wraps |
| from textwrap import dedent |
| |
| |
| if __name__ == '__main__': |
| raise Exception('do not run this file directly; do something like: tests/runner.py') |
| |
| from tools.shared import run_js, run_process, try_delete |
| from tools.shared import NODE_JS, V8_ENGINE, JS_ENGINES, SPIDERMONKEY_ENGINE, PYTHON, EMCC, EMAR, WINDOWS, MACOS, AUTODEBUGGER, LLVM_ROOT |
| from tools import jsrun, shared, building |
| from runner import RunnerCore, path_from_root |
| from runner import skip_if, no_wasm_backend, no_fastcomp, needs_dlfcn, no_windows, no_asmjs, is_slow_test, create_test_file, parameterized |
| from runner import js_engines_modify, wasm_engines_modify, env_modify, with_env_modify |
| |
| # decorators for limiting which modes a test can run in |
| |
| |
| def wasm_simd(f): |
| def decorated(self): |
| if not self.is_wasm_backend(): |
| self.skipTest('wasm simd not compatible with asm.js or asm2wasm') |
| if not V8_ENGINE or V8_ENGINE not in JS_ENGINES: |
| self.skipTest('wasm simd only supported in d8 for now') |
| if self.is_wasm_backend() and not self.get_setting('WASM'): |
| self.skipTest('wasm2js only supports MVP for now') |
| self.emcc_args.append('-msimd128') |
| self.emcc_args.append('-fno-lax-vector-conversions') |
| with js_engines_modify([V8_ENGINE + ['--experimental-wasm-simd']]): |
| f(self) |
| return decorated |
| |
| |
| def bleeding_edge_wasm_backend(f): |
| def decorated(self): |
| if not self.is_wasm_backend(): |
| self.skipTest('only works in wasm backend') |
| if not V8_ENGINE or V8_ENGINE not in JS_ENGINES: |
| self.skipTest('only works in d8 for now') |
| if self.is_wasm_backend() and not self.get_setting('WASM'): |
| self.skipTest('wasm2js only supports MVP for now') |
| with js_engines_modify([V8_ENGINE]): |
| f(self) |
| return decorated |
| |
| |
| def also_with_wasm_bigint(f): |
| def decorated(self): |
| self.set_setting('WASM_BIGINT', 0) |
| f(self) |
| if self.is_wasm_backend() and self.get_setting('WASM'): |
| self.set_setting('WASM_BIGINT', 1) |
| with js_engines_modify([NODE_JS + ['--experimental-wasm-bigint']]): |
| f(self) |
| return decorated |
| |
| |
| # without EMTEST_ALL_ENGINES set we only run tests in a single VM by |
| # default. in some tests we know that cross-VM differences may happen and |
| # so are worth testing, and they should be marked with this decorator |
| def all_engines(f): |
| def decorated(self): |
| old = self.use_all_engines |
| self.use_all_engines = True |
| try: |
| f(self) |
| finally: |
| self.use_all_engines = old |
| return decorated |
| |
| |
| # Tests exception handling in emscripten exception handling mode, and if |
| # possible, new wasm EH mode. |
| def with_both_exception_handling(f): |
| def decorated(self): |
| self.set_setting('DISABLE_EXCEPTION_CATCHING', 0) |
| f(self) |
| self.set_setting('DISABLE_EXCEPTION_CATCHING', 1) |
| # Wasm EH is currently supported only in wasm backend and V8 |
| if self.is_wasm_backend() and V8_ENGINE and \ |
| V8_ENGINE in JS_ENGINES and self.get_setting('WASM'): |
| self.emcc_args.append('-fwasm-exceptions') |
| with js_engines_modify([V8_ENGINE + ['--experimental-wasm-eh']]): |
| f(self) |
| return decorated |
| |
| |
| def no_wasm(note=''): |
| assert not callable(note) |
| |
| def decorated(f): |
| return skip_if(f, 'is_wasm', note) |
| return decorated |
| |
| |
| def no_wasm2js(note=''): |
| assert not callable(note) |
| |
| def decorated(f): |
| return skip_if(f, 'is_wasm2js', note) |
| return decorated |
| |
| |
| # Async wasm compilation can't work in some tests, they are set up synchronously |
| def sync(f): |
| assert callable(f) |
| |
| def decorated(self): |
| if self.get_setting('WASM') or self.is_wasm_backend(): |
| self.emcc_args += ['-s', 'WASM_ASYNC_COMPILATION=0'] # test is set up synchronously |
| f(self) |
| return decorated |
| |
| |
| def also_with_noderawfs(func): |
| def decorated(self): |
| orig_args = self.emcc_args[:] |
| func(self) |
| print('noderawfs') |
| self.emcc_args = orig_args + ['-s', 'RAW_OS', '-DNODERAWFS'] |
| with js_engines_modify([NODE_JS]): |
| func(self) |
| return decorated |
| |
| |
| def can_do_standalone(self): |
| return self.is_wasm_backend() and self.get_setting('WASM') and \ |
| not self.get_setting('SAFE_STACK') and \ |
| '-fsanitize=address' not in self.emcc_args |
| |
| |
| # Impure means a test that cannot run in a wasm VM yet, as it is not 100% |
| # standalone. We can still run them with the JS code though. |
| def also_with_standalone_wasm(wasm2c=False, impure=False): |
| def decorated(func): |
| def metafunc(self, standalone): |
| if not standalone: |
| func(self) |
| else: |
| if can_do_standalone(self): |
| self.set_setting('STANDALONE_WASM', 1) |
| # we will not legalize the JS ffi interface, so we must use BigInt |
| # support in order for JS to have a chance to run this without trapping |
| # when it sees an i64 on the ffi. |
| self.set_setting('WASM_BIGINT', 1) |
| # if we are impure, disallow all wasm engines |
| with wasm_engines_modify([] if impure else shared.WASM_ENGINES): |
| with js_engines_modify([NODE_JS + ['--experimental-wasm-bigint']]): |
| func(self) |
| if wasm2c: |
| print('wasm2c') |
| self.set_setting('WASM2C', 1) |
| with wasm_engines_modify([]): |
| func(self) |
| |
| metafunc._parameterize = {'': (False,), |
| 'standalone': (True,)} |
| return metafunc |
| |
| return decorated |
| |
| |
| def node_pthreads(f): |
| def decorated(self): |
| self.set_setting('USE_PTHREADS', 1) |
| if not self.is_wasm_backend(): |
| self.skipTest('node pthreads only supported on wasm backend') |
| if not self.get_setting('WASM'): |
| self.skipTest("pthreads doesn't work in non-wasm yet") |
| if '-fsanitize=address' in self.emcc_args: |
| self.skipTest('asan ends up using atomics that are not yet supported in node 12') |
| with js_engines_modify([NODE_JS + ['--experimental-wasm-threads', '--experimental-wasm-bulk-memory']]): |
| f(self) |
| return decorated |
| |
| |
| # A simple check whether the compiler arguments cause optimization. |
| def is_optimizing(args): |
| return '-O' in str(args) and '-O0' not in args |
| |
| |
| def no_optimize(note=''): |
| assert not callable(note) |
| |
| def decorator(func): |
| assert callable(func) |
| |
| def decorated(self): |
| if is_optimizing(self.emcc_args): |
| self.skipTest(note) |
| func(self) |
| return decorated |
| return decorator |
| |
| |
| def needs_make(note=''): |
| assert not callable(note) |
| if WINDOWS: |
| return unittest.skip('Tool not available on Windows bots (%s)' % note) |
| return lambda f: f |
| |
| |
| def no_asan(note): |
| assert not callable(note) |
| |
| def decorator(f): |
| assert callable(f) |
| |
| @wraps(f) |
| def decorated(self, *args, **kwargs): |
| if '-fsanitize=address' in self.emcc_args: |
| self.skipTest(note) |
| f(self, *args, **kwargs) |
| return decorated |
| return decorator |
| |
| |
| def no_lsan(note): |
| assert not callable(note) |
| |
| def decorator(f): |
| assert callable(f) |
| |
| @wraps(f) |
| def decorated(self, *args, **kwargs): |
| if '-fsanitize=leak' in self.emcc_args: |
| self.skipTest(note) |
| f(self, *args, **kwargs) |
| return decorated |
| return decorator |
| |
| |
| def no_minimal_runtime(note): |
| assert not callable(note) |
| |
| def decorator(f): |
| assert callable(f) |
| |
| @wraps(f) |
| def decorated(self, *args, **kwargs): |
| if 'MINIMAL_RUNTIME=1' in self.emcc_args or self.get_setting('MINIMAL_RUNTIME'): |
| self.skipTest(note) |
| f(self, *args, **kwargs) |
| return decorated |
| return decorator |
| |
| |
| class TestCoreBase(RunnerCore): |
| def is_wasm2js(self): |
| return self.is_wasm_backend() and not self.get_setting('WASM') |
| |
| # whether the test mode supports duplicate function elimination in js |
| def supports_js_dfe(self): |
| # wasm does this when optimizing anyhow, and the wasm backend always |
| # optimizes the wasm even if it does wasm2js later |
| if self.is_wasm() or self.is_wasm_backend(): |
| return False |
| supported_opt_levels = ['-O2', '-O3', '-Oz', '-Os'] |
| for opt_level in supported_opt_levels: |
| if opt_level in self.emcc_args: |
| return True |
| return False |
| |
| # Use closure in some tests for some additional coverage |
| def maybe_closure(self): |
| if '-g' not in self.emcc_args and ('-O2' in self.emcc_args or '-Os' in self.emcc_args): |
| self.emcc_args += ['--closure', '1'] |
| return True |
| return False |
| |
| def verify_in_strict_mode(self, filename): |
| with open(filename) as infile: |
| js = infile.read() |
| filename += '.strict.js' |
| with open(filename, 'w') as outfile: |
| outfile.write('"use strict";\n' + js) |
| run_js(filename) |
| |
| def get_bullet_library(self, use_cmake): |
| if use_cmake: |
| configure_commands = ['cmake', '.'] |
| configure_args = ['-DBUILD_DEMOS=OFF', '-DBUILD_EXTRAS=OFF', '-DUSE_GLUT=OFF'] |
| # Depending on whether 'configure' or 'cmake' is used to build, Bullet |
| # places output files in different directory structures. |
| generated_libs = [os.path.join('src', 'BulletDynamics', 'libBulletDynamics.a'), |
| os.path.join('src', 'BulletCollision', 'libBulletCollision.a'), |
| os.path.join('src', 'LinearMath', 'libLinearMath.a')] |
| else: |
| configure_commands = ['sh', './configure'] |
| # Force a nondefault --host= so that the configure script will interpret |
| # that we are doing cross-compilation |
| # and skip attempting to run the generated executable with './a.out', |
| # which would fail since we are building a .js file. |
| configure_args = ['--disable-shared', '--host=i686-pc-linux-gnu', |
| '--disable-demos', '--disable-dependency-tracking'] |
| generated_libs = [os.path.join('src', '.libs', 'libBulletDynamics.a'), |
| os.path.join('src', '.libs', 'libBulletCollision.a'), |
| os.path.join('src', '.libs', 'libLinearMath.a')] |
| |
| return self.get_library(os.path.join('third_party', 'bullet'), generated_libs, |
| configure=configure_commands, |
| configure_args=configure_args, |
| cache_name_extra=configure_commands[0]) |
| |
| @also_with_standalone_wasm() |
| def test_hello_world(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_hello_world') |
| # must not emit this unneeded internal thing |
| self.assertNotContained('EMSCRIPTEN_GENERATED_FUNCTIONS', open('src.c.o.js').read()) |
| |
| @sync |
| def test_wasm_synchronous_compilation(self): |
| self.set_setting('STRICT_JS') |
| self.do_run_in_out_file_test('tests', 'core', 'test_hello_world') |
| |
| def test_intvars(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_intvars') |
| |
| def test_sintvars(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_sintvars', |
| force_c=True) |
| |
| def test_int53(self): |
| self.emcc_args += ['-s', 'DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=[$convertI32PairToI53,$convertU32PairToI53,$readI53FromU64,$readI53FromI64,$writeI53ToI64,$writeI53ToI64Clamped,$writeI53ToU64Clamped,$writeI53ToI64Signaling,$writeI53ToU64Signaling]'] |
| if not self.is_wasm_backend(): |
| self.emcc_args += ['-s', 'BINARYEN_TRAP_MODE=js'] |
| self.do_run_in_out_file_test('tests', 'core', 'test_int53') |
| |
| def test_i64(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64') |
| |
| def test_i64_2(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64_2') |
| |
| def test_i64_3(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64_3') |
| |
| def test_i64_4(self): |
| # stuff that also needs sign corrections |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64_4') |
| |
| def test_i64_b(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64_b') |
| |
| def test_i64_cmp(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64_cmp') |
| |
| def test_i64_cmp2(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64_cmp2') |
| |
| def test_i64_double(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64_double') |
| |
| def test_i64_umul(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64_umul') |
| |
| @also_with_standalone_wasm() |
| def test_i64_precise(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64_precise') |
| |
| def test_i64_precise_needed(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64_precise_needed') |
| |
| def test_i64_llabs(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64_llabs') |
| |
| def test_i64_zextneg(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64_zextneg') |
| |
| def test_i64_7z(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64_7z', |
| args=['hallo']) |
| |
| def test_i64_i16(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64_i16') |
| |
| def test_i64_qdouble(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64_qdouble') |
| |
| def test_i64_varargs(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64_varargs', |
| args='waka fleefl asdfasdfasdfasdf' |
| .split(' ')) |
| |
| @no_fastcomp('wasm bigint') |
| @no_wasm2js('wasm_bigint') |
| def test_i64_invoke_bigint(self): |
| self.set_setting('WASM_BIGINT', 1) |
| self.emcc_args += ['-fexceptions'] |
| self.do_run_in_out_file_test('tests', 'core', 'test_i64_invoke_bigint', |
| js_engines=[NODE_JS + ['--experimental-wasm-bigint']]) |
| |
| def test_vararg_copy(self): |
| self.do_run_in_out_file_test('tests', 'va_arg', 'test_va_copy') |
| |
| def test_llvm_fabs(self): |
| self.set_setting('PRECISE_F32', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_llvm_fabs') |
| |
| def test_double_varargs(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_double_varargs') |
| |
| def test_trivial_struct_varargs(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_trivial_struct_varargs') |
| |
| def test_struct_varargs(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_struct_varargs') |
| |
| def test_zero_struct_varargs(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_zero_struct_varargs') |
| |
| def zzztest_nested_struct_varargs(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_nested_struct_varargs') |
| |
| def test_i32_mul_precise(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_i32_mul_precise') |
| |
| def test_i16_emcc_intrinsic(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_i16_emcc_intrinsic') |
| |
| def test_double_i64_conversion(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_double_i64_conversion') |
| |
| def test_float32_precise(self): |
| self.set_setting('PRECISE_F32', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_float32_precise') |
| |
| def test_negative_zero(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_negative_zero') |
| |
| def test_line_endings(self): |
| self.build(open(path_from_root('tests', 'hello_world.cpp')).read(), self.get_dir(), 'hello_world.cpp') |
| |
| def test_literal_negative_zero(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_literal_negative_zero') |
| |
| @no_wasm_backend('test uses calls to expected js imports, rather than using llvm intrinsics directly') |
| def test_llvm_intrinsics(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_llvm_intrinsics') |
| |
| @no_wasm_backend('test looks for js impls of intrinsics') |
| def test_lower_intrinsics(self): |
| self.emcc_args += ['-g1'] |
| self.do_run_in_out_file_test('tests', 'core', 'test_lower_intrinsics') |
| # intrinsics should be lowered out |
| js = open('src.c.o.js').read() |
| assert ('llvm_' not in js) == is_optimizing(self.emcc_args) or not self.is_wasm(), 'intrinsics must be lowered when optimizing' |
| |
| @also_with_standalone_wasm() |
| def test_bswap64(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_bswap64') |
| |
| @no_wasm_backend('uses EMULATED_FUNCTION_POINTERS') |
| def test_bswap64_emulate_fps(self): |
| # extra coverages |
| for emulate_casts in [0, 1]: |
| for emulate_fps in [0, 1, 2]: |
| print(emulate_casts, emulate_fps) |
| self.set_setting('EMULATE_FUNCTION_POINTER_CASTS', emulate_casts) |
| self.set_setting('EMULATED_FUNCTION_POINTERS', emulate_fps) |
| self.do_run_in_out_file_test('tests', 'core', 'test_bswap64') |
| |
| def test_sha1(self): |
| self.do_run(open(path_from_root('tests', 'sha1.c')).read(), 'SHA1=15dd99a1991e0b3826fede3deffc1feba42278e6') |
| |
| @no_wasm_backend('test checks that __asmjs__ is #defined') |
| def test_asmjs_unknown_emscripten(self): |
| # No other configuration is supported, so always run this. |
| self.do_run(open(path_from_root('tests', 'asmjs-unknown-emscripten.c')).read(), '') |
| |
| def test_cube2md5(self): |
| self.emcc_args += ['--embed-file', 'cube2md5.txt'] |
| shutil.copyfile(path_from_root('tests', 'cube2md5.txt'), 'cube2md5.txt') |
| self.do_run(open(path_from_root('tests', 'cube2md5.cpp')).read(), open(path_from_root('tests', 'cube2md5.ok')).read(), assert_returncode=None) |
| |
| @also_with_standalone_wasm(wasm2c=True) |
| @needs_make('make') |
| def test_cube2hash(self): |
| # A good test of i64 math |
| self.do_run('', 'Usage: hashstring <seed>', |
| libraries=self.get_library('cube2hash', ['cube2hash.bc'], configure=None), |
| includes=[path_from_root('tests', 'cube2hash')], assert_returncode=None) |
| |
| for text, output in [('fleefl', '892BDB6FD3F62E863D63DA55851700FDE3ACF30204798CE9'), |
| ('fleefl2', 'AA2CC5F96FC9D540CA24FDAF1F71E2942753DB83E8A81B61'), |
| ('64bitisslow', '64D8470573635EC354FEE7B7F87C566FCAF1EFB491041670')]: |
| self.do_run('src.cpp.o.js', 'hash value: ' + output, [text], no_build=True, assert_returncode=None) |
| |
| def test_unaligned(self): |
| self.skipTest('LLVM marks the reads of s as fully aligned, making this test invalid') |
| src = r''' |
| #include <stdio.h> |
| |
| struct S { |
| double x; |
| int y; |
| }; |
| |
| int main() { |
| // the 64-bit value here will not be 8-byte aligned |
| S s0[3] = { {0x12a751f430142, 22}, {0x17a5c85bad144, 98}, {1, 1}}; |
| char buffer[10*sizeof(S)]; |
| int b = int(buffer); |
| S *s = (S*)(b + 4-b%8); |
| s[0] = s0[0]; |
| s[1] = s0[1]; |
| s[2] = s0[2]; |
| |
| printf("*%d : %d : %d\n", sizeof(S), ((unsigned int)&s[0]) % 8 != ((unsigned int)&s[1]) % 8, |
| ((unsigned int)&s[1]) - ((unsigned int)&s[0])); |
| s[0].x++; |
| s[0].y++; |
| s[1].x++; |
| s[1].y++; |
| printf("%.1f,%d,%.1f,%d\n", s[0].x, s[0].y, s[1].x, s[1].y); |
| return 0; |
| } |
| ''' |
| |
| # TODO: A version of this with int64s as well |
| |
| self.do_run(src, '*12 : 1 : 12\n328157500735811.0,23,416012775903557.0,99\n') |
| |
| return # TODO: continue to the next part here |
| |
| # Test for undefined behavior in C. This is not legitimate code, but does exist |
| |
| src = r''' |
| #include <stdio.h> |
| |
| int main() |
| { |
| int x[10]; |
| char *p = (char*)&x[0]; |
| p++; |
| short *q = (short*)p; |
| *q = 300; |
| printf("*%d:%d*\n", *q, ((int)q)%2); |
| int *r = (int*)p; |
| *r = 515559; |
| printf("*%d*\n", *r); |
| long long *t = (long long*)p; |
| *t = 42949672960; |
| printf("*%lld*\n", *t); |
| return 0; |
| } |
| ''' |
| |
| try: |
| self.do_run(src, '*300:1*\n*515559*\n*42949672960*\n') |
| except Exception as e: |
| assert 'must be aligned' in str(e), e # expected to fail without emulation |
| |
| def test_align64(self): |
| src = r''' |
| #include <stdio.h> |
| |
| // inspired by poppler |
| |
| enum Type { |
| A = 10, |
| B = 20 |
| }; |
| |
| struct Object { |
| Type type; |
| union { |
| int intg; |
| double real; |
| char *name; |
| }; |
| }; |
| |
| struct Principal { |
| double x; |
| Object a; |
| double y; |
| }; |
| |
| int main(int argc, char **argv) |
| { |
| int base = argc-1; |
| Object *o = NULL; |
| printf("%d,%d\n", sizeof(Object), sizeof(Principal)); |
| printf("%d,%d,%d,%d\n", (int)&o[base].type, (int)&o[base].intg, (int)&o[base].real, (int)&o[base].name); |
| printf("%d,%d,%d,%d\n", (int)&o[base+1].type, (int)&o[base+1].intg, (int)&o[base+1].real, (int)&o[base+1].name); |
| Principal p, q; |
| p.x = p.y = q.x = q.y = 0; |
| p.a.type = A; |
| p.a.real = 123.456; |
| *(&q.a) = p.a; |
| printf("%.2f,%d,%.2f,%.2f : %.2f,%d,%.2f,%.2f\n", p.x, p.a.type, p.a.real, p.y, q.x, q.a.type, q.a.real, q.y); |
| return 0; |
| } |
| ''' |
| |
| self.do_run(src, '''16,32 |
| 0,8,8,8 |
| 16,24,24,24 |
| 0.00,10,123.46,0.00 : 0.00,10,123.46,0.00 |
| ''') |
| |
| @no_asan('asan errors on corner cases we check') |
| def test_aligned_alloc(self): |
| self.do_run(open(path_from_root('tests', 'test_aligned_alloc.c')).read(), '', assert_returncode=0) |
| |
| def test_unsigned(self): |
| src = ''' |
| #include <stdio.h> |
| const signed char cvals[2] = { -1, -2 }; // compiler can store this is a string, so -1 becomes \\FF, and needs re-signing |
| int main() |
| { |
| { |
| unsigned char x = 200; |
| printf("*%d*\\n", x); |
| unsigned char y = -22; |
| printf("*%d*\\n", y); |
| } |
| |
| int varey = 100; |
| unsigned int MAXEY = -1, MAXEY2 = -77; |
| printf("*%u,%d,%u*\\n", MAXEY, varey >= MAXEY, MAXEY2); // 100 >= -1? not in unsigned! |
| |
| int y = cvals[0]; |
| printf("*%d,%d,%d,%d*\\n", cvals[0], cvals[0] < 0, y, y < 0); |
| y = cvals[1]; |
| printf("*%d,%d,%d,%d*\\n", cvals[1], cvals[1] < 0, y, y < 0); |
| |
| // zext issue - see mathop in jsifier |
| unsigned char x8 = -10; |
| unsigned long hold = 0; |
| hold += x8; |
| int y32 = hold+50; |
| printf("*%u,%u*\\n", hold, y32); |
| |
| // Comparisons |
| x8 = 0; |
| for (int i = 0; i < 254; i++) x8++; // make it an actual 254 in JS - not a -2 |
| printf("*%d,%d*\\n", x8+1 == 0xff, x8+1 != 0xff); // 0xff may be '-1' in the bitcode |
| |
| return 0; |
| } |
| ''' |
| self.do_run(src, '*4294967295,0,4294967219*\n*-1,1,-1,1*\n*-2,1,-2,1*\n*246,296*\n*1,0*') |
| |
| self.emcc_args.append('-Wno-constant-conversion') |
| src = ''' |
| #include <stdio.h> |
| int main() |
| { |
| { |
| unsigned char x; |
| unsigned char *y = &x; |
| *y = -1; |
| printf("*%d*\\n", x); |
| } |
| { |
| unsigned short x; |
| unsigned short *y = &x; |
| *y = -1; |
| printf("*%d*\\n", x); |
| } |
| /*{ // This case is not checked. The hint for unsignedness is just the %u in printf, and we do not analyze that |
| unsigned int x; |
| unsigned int *y = &x; |
| *y = -1; |
| printf("*%u*\\n", x); |
| }*/ |
| { |
| char x; |
| char *y = &x; |
| *y = 255; |
| printf("*%d*\\n", x); |
| } |
| { |
| char x; |
| char *y = &x; |
| *y = 65535; |
| printf("*%d*\\n", x); |
| } |
| { |
| char x; |
| char *y = &x; |
| *y = 0xffffffff; |
| printf("*%d*\\n", x); |
| } |
| return 0; |
| } |
| ''' |
| self.do_run(src, '*255*\n*65535*\n*-1*\n*-1*\n*-1*') |
| |
| def test_bitfields(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_bitfields') |
| |
| def test_floatvars(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_floatvars') |
| |
| def test_closebitcasts(self): |
| self.do_run_in_out_file_test('tests', 'core', 'closebitcasts') |
| |
| def test_fast_math(self): |
| self.emcc_args += ['-ffast-math'] |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_fast_math', |
| args=['5', '6', '8']) |
| |
| def test_zerodiv(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_zerodiv') |
| |
| def test_zero_multiplication(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_zero_multiplication') |
| |
| def test_isnan(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_isnan') |
| |
| def test_globaldoubles(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_globaldoubles') |
| |
| def test_math(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_math') |
| |
| def test_erf(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_erf') |
| |
| def test_math_hyperbolic(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_math_hyperbolic') |
| |
| def test_math_lgamma(self): |
| self.do_run_in_out_file_test('tests', 'math', 'lgamma', assert_returncode=None) |
| |
| if self.get_setting('ALLOW_MEMORY_GROWTH') == 0 and not self.is_wasm() and \ |
| not self.is_wasm_backend(): |
| print('main module') |
| self.set_setting('MAIN_MODULE', 1) |
| self.do_run_in_out_file_test('tests', 'math', 'lgamma', assert_returncode=None) |
| |
| # Test that fmodf with -s PRECISE_F32=1 properly validates as asm.js (% operator cannot take in f32, only f64) |
| def test_math_fmodf(self): |
| self.do_run_in_out_file_test('tests', 'math', 'fmodf') |
| |
| def test_frexp(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_frexp') |
| |
| def test_rounding(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| for precise_f32 in [0, 1]: |
| print(precise_f32) |
| self.set_setting('PRECISE_F32', precise_f32) |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_rounding') |
| |
| def test_fcvt(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_fcvt') |
| |
| def test_llrint(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_llrint') |
| |
| def test_getgep(self): |
| # Generated code includes getelementptr (getelementptr, 0, 1), i.e., GEP as the first param to GEP |
| self.do_run_in_out_file_test('tests', 'core', 'test_getgep') |
| |
| # No compiling from C/C++ - just process an existing .o/.ll/.bc file. |
| def do_run_object(self, obj_file, expected_output=None, **kwargs): |
| js_file = os.path.basename(obj_file) + '.js' |
| building.emcc(obj_file, self.get_emcc_args(), js_file) |
| self.do_run(js_file, expected_output, no_build=True, **kwargs) |
| |
| def do_ll_run(self, filename, expected_output=None, **kwargs): |
| output_base = os.path.basename(filename) |
| objfile = self.prep_ll_file(output_base, filename) |
| self.do_run_object(objfile, expected_output, **kwargs) |
| |
| def test_multiply_defined_symbols(self): |
| create_test_file('a1.c', 'int f() { return 1; }') |
| create_test_file('a2.c', 'void x() {}') |
| create_test_file('b1.c', 'int f() { return 2; }') |
| create_test_file('b2.c', 'void y() {}') |
| create_test_file('main.c', r''' |
| #include <stdio.h> |
| int f(); |
| int main() { |
| printf("result: %d\n", f()); |
| return 0; |
| } |
| ''') |
| |
| building.emcc('a1.c') |
| building.emcc('a2.c') |
| building.emcc('b1.c') |
| building.emcc('b2.c') |
| building.emcc('main.c') |
| |
| building.emar('cr', 'liba.a', ['a1.c.o', 'a2.c.o']) |
| building.emar('cr', 'libb.a', ['b1.c.o', 'b2.c.o']) |
| |
| building.link_to_object(['main.c.o', 'liba.a', 'libb.a'], 'all.o') |
| |
| self.do_run_object('all.o', 'result: 1') |
| |
| def test_if(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_if') |
| |
| def test_if_else(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_if_else') |
| |
| def test_loop(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_loop') |
| |
| def test_stack(self): |
| self.set_setting('INLINING_LIMIT', 50) |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_stack') |
| |
| def test_stack_align(self): |
| src = path_from_root('tests', 'core', 'test_stack_align.cpp') |
| |
| def test(): |
| self.do_run(open(src).read(), ['''align 4: 0 |
| align 8: 0 |
| align 16: 0 |
| align 32: 0 |
| base align: 0, 0, 0, 0''']) |
| |
| test() |
| |
| @no_asan('stack size is too low for asan to work properly') |
| def test_stack_placement(self): |
| self.set_setting('TOTAL_STACK', 1024) |
| self.do_run_in_out_file_test('tests', 'core', 'test_stack_placement') |
| self.set_setting('GLOBAL_BASE', 102400) |
| self.do_run_in_out_file_test('tests', 'core', 'test_stack_placement') |
| |
| @no_asan('asan does not support main modules') |
| @no_wasm2js('MAIN_MODULE support') |
| def test_stack_placement_pic(self): |
| if not self.is_wasm_backend() and self.get_setting('ALLOW_MEMORY_GROWTH'): |
| self.skipTest('memory growth is not compatible with MAIN_MODULE') |
| self.set_setting('TOTAL_STACK', 1024) |
| self.set_setting('MAIN_MODULE') |
| self.do_run_in_out_file_test('tests', 'core', 'test_stack_placement') |
| self.set_setting('GLOBAL_BASE', 102400) |
| self.do_run_in_out_file_test('tests', 'core', 'test_stack_placement') |
| |
| def test_stack_restore(self): |
| if self.get_setting('WASM') or self.is_wasm_backend(): |
| self.skipTest('generated code not available in wasm') |
| self.emcc_args += ['-g3'] # to be able to find the generated code |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_stack_restore') |
| |
| generated = open('src.c.o.js').read() |
| |
| def ensure_stack_restore_count(function_name, expected_count): |
| code = generated[generated.find(function_name):] |
| code = code[:code.find('\n}') + 2] |
| actual_count = code.count('STACKTOP = sp') |
| assert actual_count == expected_count, ('Expected %d stack restorations, got %d' % (expected_count, actual_count)) + ': ' + code |
| |
| ensure_stack_restore_count('function _no_stack_usage', 0) |
| ensure_stack_restore_count('function _alloca_gets_restored', 1) |
| ensure_stack_restore_count('function _stack_usage', 1) |
| |
| def test_strings(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_strings', args=['wowie', 'too', '74']) |
| |
| def test_strcmp_uni(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_strcmp_uni') |
| |
| def test_strndup(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_strndup') |
| |
| def test_errar(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_errar') |
| |
| def test_mainenv(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_mainenv') |
| |
| def test_funcs(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_funcs') |
| |
| def test_structs(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_structs') |
| |
| gen_struct_src = ''' |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include "emscripten.h" |
| |
| struct S |
| { |
| int x, y; |
| }; |
| int main() |
| { |
| S* a = {{gen_struct}}; |
| a->x = 51; a->y = 62; |
| printf("*%d,%d*\\n", a->x, a->y); |
| {{del_struct}}(a); |
| return 0; |
| } |
| ''' |
| |
| def test_mallocstruct(self): |
| self.do_run(self.gen_struct_src.replace('{{gen_struct}}', '(S*)malloc(sizeof(S))').replace('{{del_struct}}', 'free'), '*51,62*') |
| |
| @no_asan('ASan does not support custom memory allocators') |
| @no_lsan('LSan does not support custom memory allocators') |
| @parameterized({ |
| 'normal': [], |
| 'debug': ['-DEMMALLOC_DEBUG'], |
| 'debug_log': ['-DEMMALLOC_DEBUG', '-DEMMALLOC_DEBUG_LOG', '-DRANDOM_ITERS=130'], |
| }) |
| def test_emmalloc(self, *args): |
| # in newer clang+llvm, the internal calls to malloc in emmalloc may be optimized under |
| # the assumption that they are external, so like in system_libs.py where we build |
| # malloc, we need to disable builtin here too |
| self.set_setting('MALLOC', 'none') |
| self.emcc_args += ['-fno-builtin'] + list(args) |
| |
| self.do_run(open(path_from_root('system', 'lib', 'emmalloc.cpp')).read() + |
| open(path_from_root('system', 'lib', 'sbrk.c')).read() + |
| open(path_from_root('tests', 'core', 'test_emmalloc.cpp')).read(), |
| open(path_from_root('tests', 'core', 'test_emmalloc.txt')).read()) |
| |
| @no_asan('ASan does not support custom memory allocators') |
| @no_lsan('LSan does not support custom memory allocators') |
| def test_emmalloc_usable_size(self, *args): |
| self.set_setting('MALLOC', 'emmalloc') |
| self.emcc_args += list(args) |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_malloc_usable_size') |
| |
| @no_fastcomp('this feature works in fastcomp, but test outputs are sensitive to wasm backend') |
| @no_optimize('output is sensitive to optimization flags, so only test unoptimized builds') |
| @no_asan('ASan does not support custom memory allocators') |
| @no_lsan('LSan does not support custom memory allocators') |
| def test_emmalloc_memory_statistics(self, *args): |
| |
| self.set_setting('MALLOC', 'emmalloc') |
| self.emcc_args += ['-s', 'INITIAL_MEMORY=128MB', '-g'] + list(args) |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_emmalloc_memory_statistics') |
| |
| @no_fastcomp('this feature works in fastcomp, but test outputs are sensitive to wasm backend') |
| @no_optimize('output is sensitive to optimization flags, so only test unoptimized builds') |
| @no_asan('ASan does not support custom memory allocators') |
| @no_lsan('LSan does not support custom memory allocators') |
| def test_emmalloc_trim(self, *args): |
| self.set_setting('MALLOC', 'emmalloc') |
| self.emcc_args += ['-s', 'INITIAL_MEMORY=128MB', '-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'MAXIMUM_MEMORY=2147418112'] + list(args) |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_emmalloc_trim') |
| |
| # Test case against https://github.com/emscripten-core/emscripten/issues/10363 |
| def test_emmalloc_memalign_corruption(self, *args): |
| self.set_setting('MALLOC', 'emmalloc') |
| self.do_run_in_out_file_test('tests', 'core', 'emmalloc_memalign_corruption') |
| |
| def test_newstruct(self): |
| self.do_run(self.gen_struct_src.replace('{{gen_struct}}', 'new S').replace('{{del_struct}}', 'delete'), '*51,62*') |
| |
| def test_addr_of_stacked(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_addr_of_stacked') |
| |
| def test_globals(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_globals') |
| |
| def test_linked_list(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_linked_list') |
| |
| def test_sup(self): |
| src = ''' |
| #include <stdio.h> |
| |
| struct S4 { int x; }; // size: 4 |
| struct S4_2 { short x, y; }; // size: 4, but for alignment purposes, 2 |
| struct S6 { short x, y, z; }; // size: 6 |
| struct S6w { char x[6]; }; // size: 6 also |
| struct S6z { int x; short y; }; // size: 8, since we align to a multiple of the biggest - 4 |
| |
| struct C___ { S6 a, b, c; int later; }; |
| struct Carr { S6 a[3]; int later; }; // essentially the same, but differently defined |
| struct C__w { S6 a; S6w b; S6 c; int later; }; // same size, different struct |
| struct Cp1_ { int pre; short a; S6 b, c; int later; }; // fillers for a |
| struct Cp2_ { int a; short pre; S6 b, c; int later; }; // fillers for a (get addr of the other filler) |
| struct Cint { S6 a; int b; S6 c; int later; }; // An int (different size) for b |
| struct C4__ { S6 a; S4 b; S6 c; int later; }; // Same size as int from before, but a struct |
| struct C4_2 { S6 a; S4_2 b; S6 c; int later; }; // Same size as int from before, but a struct with max element size 2 |
| struct C__z { S6 a; S6z b; S6 c; int later; }; // different size, 8 instead of 6 |
| |
| int main() |
| { |
| #define TEST(struc) \\ |
| { \\ |
| struc *s = 0; \\ |
| printf("*%s: %d,%d,%d,%d<%d*\\n", #struc, (int)&(s->a), (int)&(s->b), (int)&(s->c), (int)&(s->later), sizeof(struc)); \\ |
| } |
| #define TEST_ARR(struc) \\ |
| { \\ |
| struc *s = 0; \\ |
| printf("*%s: %d,%d,%d,%d<%d*\\n", #struc, (int)&(s->a[0]), (int)&(s->a[1]), (int)&(s->a[2]), (int)&(s->later), sizeof(struc)); \\ |
| } |
| printf("sizeofs:%d,%d\\n", sizeof(S6), sizeof(S6z)); |
| TEST(C___); |
| TEST_ARR(Carr); |
| TEST(C__w); |
| TEST(Cp1_); |
| TEST(Cp2_); |
| TEST(Cint); |
| TEST(C4__); |
| TEST(C4_2); |
| TEST(C__z); |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'sizeofs:6,8\n*C___: 0,6,12,20<24*\n*Carr: 0,6,12,20<24*\n*C__w: 0,6,12,20<24*\n*Cp1_: 4,6,12,20<24*\n*Cp2_: 0,6,12,20<24*\n*Cint: 0,8,12,20<24*\n*C4__: 0,8,12,20<24*\n*C4_2: 0,6,10,16<20*\n*C__z: 0,8,16,24<28*') |
| |
| @also_with_standalone_wasm() |
| def test_assert(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_assert', assert_returncode=None) |
| |
| def test_wcslen(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_wcslen') |
| |
| def test_regex(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_regex') |
| |
| @also_with_standalone_wasm(wasm2c=True, impure=True) |
| def test_longjmp(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_longjmp') |
| |
| def test_longjmp2(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_longjmp2') |
| |
| @needs_dlfcn |
| def test_longjmp2_main_module(self): |
| # Test for binaryen regression: |
| # https://github.com/WebAssembly/binaryen/issues/2180 |
| self.set_setting('MAIN_MODULE') |
| self.do_run_in_out_file_test('tests', 'core', 'test_longjmp2') |
| |
| def test_longjmp3(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_longjmp3') |
| |
| def test_longjmp4(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_longjmp4') |
| |
| def test_longjmp_funcptr(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_longjmp_funcptr') |
| |
| def test_longjmp_repeat(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_longjmp_repeat') |
| |
| def test_longjmp_stacked(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_longjmp_stacked', assert_returncode=None) |
| |
| def test_longjmp_exc(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_longjmp_exc', assert_returncode=None) |
| |
| def test_longjmp_throw(self): |
| for disable_throw in [0, 1]: |
| print(disable_throw) |
| self.set_setting('DISABLE_EXCEPTION_CATCHING', disable_throw) |
| self.do_run_in_out_file_test('tests', 'core', 'test_longjmp_throw') |
| |
| def test_longjmp_unwind(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_longjmp_unwind', assert_returncode=None) |
| |
| def test_longjmp_i64(self): |
| self.emcc_args += ['-g'] |
| self.do_run_in_out_file_test('tests', 'core', 'test_longjmp_i64', assert_returncode=None) |
| |
| def test_siglongjmp(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_siglongjmp') |
| |
| def test_setjmp_many(self): |
| src = r''' |
| #include <stdio.h> |
| #include <setjmp.h> |
| |
| int main(int argc, char** argv) { |
| jmp_buf buf; |
| for (int i = 0; i < NUM; i++) printf("%d\n", setjmp(buf)); |
| if (argc-- == 1131) longjmp(buf, 11); |
| return 0; |
| } |
| ''' |
| for num in [1, 5, 20, 1000]: |
| print('NUM=%d' % num) |
| self.do_run(src.replace('NUM', str(num)), '0\n' * num) |
| |
| def test_setjmp_many_2(self): |
| src = r''' |
| #include <setjmp.h> |
| #include <stdio.h> |
| |
| jmp_buf env; |
| |
| void luaWork(int d){ |
| int x; |
| printf("d is at %d\n", d); |
| |
| longjmp(env, 1); |
| } |
| |
| int main() |
| { |
| const int ITERATIONS=25; |
| for(int i = 0; i < ITERATIONS; i++){ |
| if(!setjmp(env)){ |
| luaWork(i); |
| } |
| } |
| return 0; |
| } |
| ''' |
| |
| self.do_run(src, r'''d is at 24''') |
| |
| def test_setjmp_noleak(self): |
| src = r''' |
| #include <setjmp.h> |
| #include <stdio.h> |
| #include <assert.h> |
| |
| jmp_buf env; |
| |
| void luaWork(int d){ |
| int x; |
| printf("d is at %d\n", d); |
| |
| longjmp(env, 1); |
| } |
| |
| #include <malloc.h> |
| #include <stdlib.h> |
| |
| void dump() { |
| struct mallinfo m = mallinfo(); |
| printf("dump: %d , %d\n", m.arena, m.uordblks); |
| } |
| |
| void work(int n) |
| { |
| printf("work %d\n", n); |
| dump(); |
| |
| if(!setjmp(env)){ |
| luaWork(n); |
| } |
| |
| if (n > 0) work(n-1); |
| } |
| |
| int main() { |
| struct mallinfo m1 = mallinfo(); |
| dump(); |
| work(10); |
| dump(); |
| struct mallinfo m2 = mallinfo(); |
| assert(m1.uordblks == m2.uordblks); |
| printf("ok.\n"); |
| } |
| ''' |
| self.do_run(src, r'''ok.''') |
| |
| @with_both_exception_handling |
| def test_exceptions(self): |
| self.set_setting('EXCEPTION_DEBUG', 1) |
| self.maybe_closure() |
| for support_longjmp in [0, 1]: |
| self.set_setting('SUPPORT_LONGJMP', support_longjmp) |
| self.do_run_from_file(path_from_root('tests', 'core', 'test_exceptions.cpp'), path_from_root('tests', 'core', 'test_exceptions_caught.out')) |
| |
| def test_exceptions_off(self): |
| for support_longjmp in [0, 1]: |
| self.set_setting('DISABLE_EXCEPTION_CATCHING', 1) |
| self.do_run_from_file(path_from_root('tests', 'core', 'test_exceptions.cpp'), path_from_root('tests', 'core', 'test_exceptions_uncaught.out'), assert_returncode=None) |
| |
| def test_exceptions_minimal_runtime(self): |
| self.set_setting('EXCEPTION_DEBUG', 1) |
| self.maybe_closure() |
| self.set_setting('MINIMAL_RUNTIME', 1) |
| for support_longjmp in [0, 1]: |
| self.set_setting('SUPPORT_LONGJMP', support_longjmp) |
| |
| self.set_setting('DISABLE_EXCEPTION_CATCHING', 0) |
| self.do_run_from_file(path_from_root('tests', 'core', 'test_exceptions.cpp'), path_from_root('tests', 'core', 'test_exceptions_caught.out')) |
| |
| self.set_setting('DISABLE_EXCEPTION_CATCHING', 1) |
| self.do_run_from_file(path_from_root('tests', 'core', 'test_exceptions.cpp'), path_from_root('tests', 'core', 'test_exceptions_uncaught.out'), assert_returncode=None) |
| |
| @with_both_exception_handling |
| def test_exceptions_custom(self): |
| self.set_setting('EXCEPTION_DEBUG', 1) |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.maybe_closure() |
| src = ''' |
| #include <iostream> |
| |
| class MyException |
| { |
| public: |
| MyException(){ std::cout << "Construct..."; } |
| MyException( const MyException & ) { std::cout << "Copy..."; } |
| ~MyException(){ std::cout << "Destruct..."; } |
| }; |
| |
| int function() |
| { |
| std::cout << "Throw..."; |
| throw MyException(); |
| } |
| |
| int function2() |
| { |
| return function(); |
| } |
| |
| int main() |
| { |
| try |
| { |
| function2(); |
| } |
| catch (MyException & e) |
| { |
| std::cout << "Caught..."; |
| } |
| |
| try |
| { |
| function2(); |
| } |
| catch (MyException e) |
| { |
| std::cout << "Caught..."; |
| } |
| |
| return 0; |
| } |
| ''' |
| |
| self.do_run(src, 'Throw...Construct...Caught...Destruct...Throw...Construct...Copy...Caught...Destruct...Destruct...') |
| |
| @with_both_exception_handling |
| def test_exceptions_2(self): |
| for safe in [0, 1]: |
| print(safe) |
| if safe and '-fsanitize=address' in self.emcc_args: |
| # Can't use safe heap with ASan |
| continue |
| self.set_setting('SAFE_HEAP', safe) |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_2') |
| |
| @with_both_exception_handling |
| def test_exceptions_3(self): |
| src = r''' |
| #include <iostream> |
| #include <stdexcept> |
| |
| int main(int argc, char **argv) |
| { |
| if (argc != 2) { |
| std::cout << "need an arg" << std::endl; |
| return 1; |
| } |
| |
| int arg = argv[1][0] - '0'; |
| try { |
| if (arg == 0) throw "a c string"; |
| if (arg == 1) throw std::exception(); |
| if (arg == 2) throw std::runtime_error("Hello"); |
| } catch(const char * ex) { |
| std::cout << "Caught C string: " << ex << std::endl; |
| } catch(const std::exception &ex) { |
| std::cout << "Caught exception: " << ex.what() << std::endl; |
| } catch(...) { |
| std::cout << "Caught something else" << std::endl; |
| } |
| |
| std::cout << "Done.\n"; |
| } |
| ''' |
| |
| print('0') |
| self.do_run(src, 'Caught C string: a c string\nDone.', ['0']) |
| print('1') |
| self.do_run(None, 'Caught exception: std::exception\nDone.', ['1'], no_build=True) |
| print('2') |
| self.do_run(None, 'Caught exception: Hello\nDone.', ['2'], no_build=True) |
| |
| def test_exceptions_white_list(self): |
| self.set_setting('DISABLE_EXCEPTION_CATCHING', 2) |
| # Wasm does not add an underscore to function names. For wasm, the |
| # mismatches are fixed in fixImports() function in JS glue code. |
| if not self.is_wasm_backend(): |
| self.set_setting('EXCEPTION_CATCHING_WHITELIST', ["__Z12somefunctionv"]) |
| else: |
| self.set_setting('EXCEPTION_CATCHING_WHITELIST', ["_Z12somefunctionv"]) |
| # otherwise it is inlined and not identified |
| self.set_setting('INLINING_LIMIT', 50) |
| |
| test_path = path_from_root('tests', 'core', 'test_exceptions_white_list') |
| src, output = (test_path + s for s in ('.cpp', '.out')) |
| self.do_run_from_file(src, output) |
| size = len(open('src.cpp.o.js').read()) |
| shutil.copyfile('src.cpp.o.js', 'orig.js') |
| |
| # check that an empty whitelist works properly (as in, same as exceptions disabled) |
| empty_output = path_from_root('tests', 'core', 'test_exceptions_white_list_empty.out') |
| |
| self.set_setting('EXCEPTION_CATCHING_WHITELIST', []) |
| self.do_run_from_file(src, empty_output, assert_returncode=None) |
| empty_size = len(open('src.cpp.o.js').read()) |
| shutil.copyfile('src.cpp.o.js', 'empty.js') |
| |
| self.set_setting('EXCEPTION_CATCHING_WHITELIST', ['fake']) |
| self.do_run_from_file(src, empty_output, assert_returncode=None) |
| fake_size = len(open('src.cpp.o.js').read()) |
| shutil.copyfile('src.cpp.o.js', 'fake.js') |
| |
| self.set_setting('DISABLE_EXCEPTION_CATCHING', 1) |
| self.do_run_from_file(src, empty_output, assert_returncode=None) |
| disabled_size = len(open('src.cpp.o.js').read()) |
| shutil.copyfile('src.cpp.o.js', 'disabled.js') |
| |
| if not self.is_wasm(): |
| print(size, empty_size, fake_size, disabled_size) |
| assert empty_size == fake_size, [empty_size, fake_size] |
| # big change when we disable exception catching of the function |
| assert size - empty_size > 0.01 * size, [empty_size, size] |
| # full disable can remove a little bit more |
| assert empty_size >= disabled_size, [empty_size, disabled_size] |
| |
| def test_exceptions_white_list_2(self): |
| self.set_setting('DISABLE_EXCEPTION_CATCHING', 2) |
| # Wasm does not add an underscore to function names. For wasm, the |
| # mismatches are fixed in fixImports() function in JS glue code. |
| if not self.is_wasm_backend(): |
| self.set_setting('EXCEPTION_CATCHING_WHITELIST', ["_main"]) |
| else: |
| self.set_setting('EXCEPTION_CATCHING_WHITELIST', ["main"]) |
| # otherwise it is inlined and not identified |
| self.set_setting('INLINING_LIMIT', 1) |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_white_list_2') |
| |
| def test_exceptions_white_list_uncaught(self): |
| self.emcc_args += ['-std=c++11'] |
| self.set_setting('DISABLE_EXCEPTION_CATCHING', 2) |
| # Wasm does not add an underscore to function names. For wasm, the |
| # mismatches are fixed in fixImports() function in JS glue code. |
| if not self.is_wasm_backend(): |
| self.set_setting('EXCEPTION_CATCHING_WHITELIST', ["__Z4testv"]) |
| else: |
| self.set_setting('EXCEPTION_CATCHING_WHITELIST', ["_Z4testv"]) |
| # otherwise it is inlined and not identified |
| self.set_setting('INLINING_LIMIT', 1) |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_white_list_uncaught') |
| |
| @with_both_exception_handling |
| def test_exceptions_uncaught(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| src = r''' |
| #include <stdio.h> |
| #include <exception> |
| struct X { |
| ~X() { |
| printf("exception? %s\n", std::uncaught_exception() ? "yes" : "no"); |
| } |
| }; |
| int main() { |
| printf("exception? %s\n", std::uncaught_exception() ? "yes" : "no"); |
| try { |
| X x; |
| throw 1; |
| } catch(...) { |
| printf("exception? %s\n", std::uncaught_exception() ? "yes" : "no"); |
| } |
| printf("exception? %s\n", std::uncaught_exception() ? "yes" : "no"); |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'exception? no\nexception? yes\nexception? no\nexception? no\n') |
| |
| src = r''' |
| #include <fstream> |
| #include <iostream> |
| int main() { |
| std::ofstream os("test"); |
| os << std::unitbuf << "foo"; // trigger a call to std::uncaught_exception from |
| // std::basic_ostream::sentry::~sentry |
| std::cout << "success"; |
| } |
| ''' |
| self.do_run(src, 'success') |
| |
| @with_both_exception_handling |
| def test_exceptions_uncaught_2(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| src = r''' |
| #include <iostream> |
| #include <exception> |
| |
| int main() { |
| try { |
| throw std::exception(); |
| } catch(std::exception) { |
| try { |
| throw; |
| } catch(std::exception) {} |
| } |
| |
| if (std::uncaught_exception()) |
| std::cout << "ERROR: uncaught_exception still set."; |
| else |
| std::cout << "OK"; |
| } |
| ''' |
| self.do_run(src, 'OK\n') |
| |
| @with_both_exception_handling |
| def test_exceptions_typed(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.emcc_args += ['-s', 'SAFE_HEAP=0'] # Throwing null will cause an ignorable null pointer access. |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_typed') |
| |
| @with_both_exception_handling |
| def test_exceptions_virtual_inheritance(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_virtual_inheritance') |
| |
| @with_both_exception_handling |
| def test_exceptions_convert(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_convert') |
| |
| # TODO Make setjmp-longjmp also use Wasm exception handling |
| @with_both_exception_handling |
| def test_exceptions_multi(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_multi') |
| |
| @with_both_exception_handling |
| def test_exceptions_std(self): |
| self.emcc_args += ['-s', 'SAFE_HEAP=0'] |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_std') |
| |
| @with_both_exception_handling |
| def test_exceptions_alias(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_alias') |
| |
| @with_both_exception_handling |
| def test_exceptions_rethrow(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_rethrow') |
| |
| @with_both_exception_handling |
| def test_exceptions_resume(self): |
| self.set_setting('EXCEPTION_DEBUG', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_resume') |
| |
| @with_both_exception_handling |
| def test_exceptions_destroy_virtual(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_destroy_virtual') |
| |
| @with_both_exception_handling |
| def test_exceptions_refcount(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_refcount') |
| |
| @with_both_exception_handling |
| def test_exceptions_primary(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_primary') |
| |
| @with_both_exception_handling |
| def test_exceptions_simplify_cfg(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_simplify_cfg') |
| |
| @with_both_exception_handling |
| def test_exceptions_libcxx(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_libcxx') |
| |
| @with_both_exception_handling |
| def test_exceptions_multiple_inherit(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_multiple_inherit') |
| |
| @with_both_exception_handling |
| def test_exceptions_multiple_inherit_rethrow(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_exceptions_multiple_inherit_rethrow') |
| |
| @with_both_exception_handling |
| def test_bad_typeid(self): |
| self.do_run(r''' |
| // exception example |
| #include <iostream> // std::cerr |
| #include <typeinfo> // operator typeid |
| #include <exception> // std::exception |
| |
| class Polymorphic {virtual void member(){}}; |
| |
| int main () { |
| try |
| { |
| Polymorphic * pb = 0; |
| const std::type_info& ti = typeid(*pb); // throws a bad_typeid exception |
| } |
| catch (std::exception& e) |
| { |
| std::cerr << "exception caught: " << e.what() << '\n'; |
| } |
| return 0; |
| } |
| ''', 'exception caught: std::bad_typeid') |
| |
| def test_iostream_ctors(self): |
| # iostream stuff must be globally constructed before user global |
| # constructors, so iostream works in global constructors |
| self.do_run(r''' |
| #include <iostream> |
| |
| struct A { |
| A() { std::cout << "bug"; } |
| }; |
| A a; |
| |
| int main() { |
| std::cout << "free code" << std::endl; |
| return 0; |
| } |
| ''', 'bugfree code') |
| |
| # Marked as impure since the WASI reactor modules (modules without main) |
| # are not yet suppored by the wasm engines we test against. |
| @also_with_standalone_wasm(impure=True) |
| def test_ctors_no_main(self): |
| self.emcc_args.append('--no-entry') |
| self.do_run_in_out_file_test('tests', 'core', 'test_ctors_no_main') |
| |
| def test_class(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_class') |
| |
| def test_inherit(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_inherit') |
| |
| def test_isdigit_l(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_isdigit_l') |
| |
| def test_iswdigit(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_iswdigit') |
| |
| def test_polymorph(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_polymorph') |
| |
| def test_complex(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_complex') |
| |
| def test_float_builtins(self): |
| # tests wasm_libc_rt |
| if not self.is_wasm_backend(): |
| self.skipTest('no __builtin_fmin support in JSBackend') |
| self.do_run_in_out_file_test('tests', 'core', 'test_float_builtins') |
| |
| @no_asan('SAFE_HEAP cannot be used with ASan') |
| def test_segfault(self): |
| self.set_setting('SAFE_HEAP', 1) |
| |
| for addr in ['0', 'new D2()']: |
| print(addr) |
| src = r''' |
| #include <stdio.h> |
| |
| struct Classey { |
| virtual void doIt() = 0; |
| }; |
| |
| struct D1 : Classey { |
| virtual void doIt() { printf("fleefl\n"); } |
| }; |
| |
| struct D2 : Classey { |
| virtual void doIt() { printf("marfoosh\n"); } |
| }; |
| |
| int main(int argc, char **argv) |
| { |
| Classey *p = argc == 100 ? new D1() : (Classey*)%s; |
| |
| p->doIt(); |
| |
| return 0; |
| } |
| ''' % addr |
| self.do_run(src, 'segmentation fault' if addr.isdigit() else 'marfoosh', assert_returncode=None) |
| |
| def test_dynamic_cast(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_dynamic_cast') |
| |
| def test_dynamic_cast_b(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_dynamic_cast_b') |
| |
| def test_dynamic_cast_2(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_dynamic_cast_2') |
| |
| def test_funcptr(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_funcptr') |
| |
| def test_mathfuncptr(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_mathfuncptr') |
| |
| def test_funcptrfunc(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_funcptrfunc') |
| |
| def test_funcptr_namecollide(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_funcptr_namecollide') |
| |
| def test_emptyclass(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_emptyclass') |
| |
| def test_alloca(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_alloca') |
| |
| def test_rename(self): |
| self.do_run_in_out_file_test('tests', 'stdio', 'test_rename') |
| |
| def test_remove(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.do_run_in_out_file_test('tests', 'cstdio', 'test_remove') |
| |
| def test_alloca_stack(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_alloca_stack') |
| |
| def test_stack_byval(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_stack_byval') |
| |
| def test_stack_varargs(self): |
| # in node.js we allocate argv[0] on the stack, which means the length |
| # of the program directory influences how much stack we need, and so |
| # long random temp dir names can lead to random failures. The stack |
| # size was increased here to avoid that. |
| self.set_setting('INLINING_LIMIT', 50) |
| self.set_setting('TOTAL_STACK', 8 * 1024) |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_stack_varargs') |
| |
| def test_stack_varargs2(self): |
| # in node.js we allocate argv[0] on the stack, which means the length |
| # of the program directory influences how much stack we need, and so |
| # long random temp dir names can lead to random failures. The stack |
| # size was increased here to avoid that. |
| self.set_setting('TOTAL_STACK', 8 * 1024) |
| src = r''' |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| void func(int i) { |
| } |
| int main() { |
| for (int i = 0; i < 7000; i++) { |
| printf("%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", |
| i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i); |
| } |
| printf("ok!\n"); |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'ok!') |
| |
| print('with return') |
| |
| src = r''' |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| int main() { |
| for (int i = 0; i < 7000; i++) { |
| int j = printf("%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", |
| i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i); |
| printf(" (%d)\n", j); |
| } |
| printf("ok!\n"); |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'ok!') |
| |
| print('with definitely no return') |
| |
| src = r''' |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdarg.h> |
| |
| void vary(const char *s, ...) |
| { |
| va_list v; |
| va_start(v, s); |
| char d[20]; |
| vsnprintf(d, 20, s, v); |
| puts(d); |
| |
| // Try it with copying |
| va_list tempva; |
| va_copy(tempva, v); |
| vsnprintf(d, 20, s, tempva); |
| puts(d); |
| |
| va_end(v); |
| } |
| |
| int main() { |
| for (int i = 0; i < 7000; i++) { |
| int j = printf("%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", |
| i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i, i); |
| printf(" (%d)\n", j); |
| vary("*cheez: %d+%d*", 99, 24); |
| vary("*albeit*"); |
| } |
| printf("ok!\n"); |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'ok!') |
| |
| def test_stack_void(self): |
| self.set_setting('INLINING_LIMIT', 50) |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_stack_void') |
| |
| def test_life(self): |
| self.emcc_args += ['-std=c99'] |
| self.do_run_in_out_file_test('tests', 'life', args=['2']) |
| |
| def test_array2(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_array2') |
| |
| def test_array2b(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_array2b') |
| |
| def test_constglobalstructs(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_constglobalstructs') |
| |
| def test_conststructs(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_conststructs') |
| |
| def test_bigarray(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_bigarray') |
| |
| def test_mod_globalstruct(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_mod_globalstruct') |
| |
| @no_wasm_backend('long doubles are f128s in wasm backend') |
| def test_pystruct(self): |
| def test(): |
| self.do_run_in_out_file_test('tests', 'test_pystruct') |
| |
| test() |
| |
| print('relocatable') # this tests recursive global structs => nontrivial postSets for relocation |
| assert self.get_setting('RELOCATABLE') == self.get_setting('EMULATED_FUNCTION_POINTERS') == 0 |
| self.set_setting('RELOCATABLE', 1) |
| self.set_setting('EMULATED_FUNCTION_POINTERS', 1) |
| test() |
| |
| def test_sizeof(self): |
| # Has invalid writes between printouts |
| self.set_setting('SAFE_HEAP', 0) |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_sizeof') |
| |
| def test_llvm_used(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_llvm_used') |
| |
| @no_asan('SAFE_HEAP cannot be used with ASan') |
| def test_set_align(self): |
| self.set_setting('SAFE_HEAP', 1) |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_set_align') |
| |
| def test_emscripten_api(self): |
| self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_save_me_aimee']) |
| self.do_run_in_out_file_test('tests', 'core', 'test_emscripten_api') |
| |
| if '-fsanitize=address' not in self.emcc_args: |
| # test EXPORT_ALL (this is not compatible with asan, which doesn't |
| # support dynamic linking at all or the LINKING flag) |
| self.set_setting('EXPORTED_FUNCTIONS', []) |
| self.set_setting('EXPORT_ALL', 1) |
| self.set_setting('LINKABLE', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_emscripten_api') |
| |
| def test_emscripten_run_script_string_int(self): |
| src = r''' |
| #include <stdio.h> |
| #include <emscripten.h> |
| |
| int main() { |
| const char *str = emscripten_run_script_string("1+1"); |
| printf("got string: %s\n", str); |
| return 0; |
| } |
| ''' |
| self.do_run(src, '''got string: 2''') |
| |
| def test_emscripten_run_script_string_utf8(self): |
| src = r''' |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <emscripten.h> |
| |
| int main() { |
| const char *str = emscripten_run_script_string("'\\u2603 \\u2603 \\u2603 Hello!'"); |
| printf("length of returned string: %d. Position of substring 'Hello': %d\n", strlen(str), strstr(str, "Hello")-str); |
| return 0; |
| } |
| ''' |
| self.do_run(src, '''length of returned string: 18. Position of substring 'Hello': 12''') |
| |
| def test_emscripten_run_script_string_null(self): |
| src = r''' |
| #include <stdio.h> |
| #include <emscripten.h> |
| |
| int main() { |
| const char *str = emscripten_run_script_string("void(0)"); |
| if (str) { |
| printf("got string: %s\n", str); |
| } else { |
| puts("got null"); |
| } |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'got null') |
| |
| def test_emscripten_get_now(self): |
| self.banned_js_engines = [V8_ENGINE] # timer limitations in v8 shell |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| |
| if self.run_name == 'asm2': |
| self.emcc_args += ['--closure', '1'] # Use closure here for some additional coverage |
| self.do_run(open(path_from_root('tests', 'emscripten_get_now.cpp')).read(), 'Timer resolution is good') |
| |
| def test_emscripten_get_compiler_setting(self): |
| test_path = path_from_root('tests', 'core', 'emscripten_get_compiler_setting') |
| src, output = (test_path + s for s in ('.c', '.out')) |
| old = self.get_setting('ASSERTIONS') |
| # with assertions, a nice message is shown |
| self.set_setting('ASSERTIONS', 1) |
| self.do_run(open(src).read(), 'You must build with -s RETAIN_COMPILER_SETTINGS=1', assert_returncode=None) |
| self.set_setting('ASSERTIONS', old) |
| self.set_setting('RETAIN_COMPILER_SETTINGS', 1) |
| self.do_run(open(src).read(), open(output).read().replace('waka', shared.EMSCRIPTEN_VERSION)) |
| |
| @no_fastcomp('ASYNCIFY has been removed from fastcomp') |
| def test_emscripten_has_asyncify(self): |
| src = r''' |
| #include <stdio.h> |
| #include <emscripten.h> |
| |
| int main() { |
| printf("%d\n", emscripten_has_asyncify()); |
| return 0; |
| } |
| ''' |
| self.set_setting('ASYNCIFY', 0) |
| self.do_run(src, '0') |
| self.set_setting('ASYNCIFY', 1) |
| self.do_run(src, '1') |
| |
| # TODO: test only worked in non-fastcomp |
| def test_inlinejs(self): |
| self.skipTest('non-fastcomp is deprecated and fails in 3.5') # only supports EM_ASM |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_inlinejs') |
| |
| if self.emcc_args == []: |
| # opts will eliminate the comments |
| out = open('src.cpp.o.js').read() |
| for i in range(1, 5): |
| assert ('comment%d' % i) in out |
| |
| # TODO: test only worked in non-fastcomp |
| def test_inlinejs2(self): |
| self.skipTest('non-fastcomp is deprecated and fails in 3.5') # only supports EM_ASM |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_inlinejs2') |
| |
| def test_inlinejs3(self): |
| if self.is_wasm(): |
| self.skipTest('wasm requires a proper asm module') |
| |
| self.emcc_args.append('-Wno-almost-asm') |
| test_path = path_from_root('tests', 'core', 'test_inlinejs3') |
| src, output = (test_path + s for s in ('.c', '.out')) |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_inlinejs3') |
| |
| print('no debugger, check validation') |
| src = open(src).read().replace('emscripten_debugger();', '') |
| self.do_run(src, open(output).read()) |
| |
| def test_inlinejs4(self): |
| self.do_run(r''' |
| #include <emscripten.h> |
| |
| #define TO_STRING_INNER(x) #x |
| #define TO_STRING(x) TO_STRING_INNER(x) |
| #define assert_msg(msg, file, line) EM_ASM( throw 'Assert (' + msg + ') failed in ' + file + ':' + line + '!'; ) |
| #define assert(expr) { \ |
| if (!(expr)) { \ |
| assert_msg(#expr, TO_STRING(__FILE__), TO_STRING(__LINE__)); \ |
| } \ |
| } |
| |
| int main(int argc, char **argv) { |
| assert(argc != 17); |
| assert(false); |
| return 0; |
| } |
| ''', 'false', assert_returncode=None) |
| |
| def test_em_asm(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_em_asm') |
| self.do_run_in_out_file_test('tests', 'core', 'test_em_asm', force_c=True) |
| |
| # Tests various different ways to invoke the EM_ASM(), EM_ASM_INT() and EM_ASM_DOUBLE() macros. |
| @no_asan('Cannot use ASan: test depends exactly on heap size') |
| def test_em_asm_2(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_2') |
| self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_2', force_c=True) |
| |
| # Tests various different ways to invoke the MAIN_THREAD_EM_ASM(), MAIN_THREAD_EM_ASM_INT() and MAIN_THREAD_EM_ASM_DOUBLE() macros. |
| # This test is identical to test_em_asm_2, just search-replaces EM_ASM to MAIN_THREAD_EM_ASM on the test file. That way if new |
| # test cases are added to test_em_asm_2.cpp for EM_ASM, they will also get tested in MAIN_THREAD_EM_ASM form. |
| @no_asan('Cannot use ASan: test depends exactly on heap size') |
| def test_main_thread_em_asm(self): |
| src = open(path_from_root('tests', 'core', 'test_em_asm_2.cpp')).read() |
| create_test_file('src.cpp', src.replace('EM_ASM', 'MAIN_THREAD_EM_ASM')) |
| |
| expected_result = open(path_from_root('tests', 'core', 'test_em_asm_2.out')).read() |
| create_test_file('result.out', expected_result.replace('EM_ASM', 'MAIN_THREAD_EM_ASM')) |
| |
| self.do_run_from_file('src.cpp', 'result.out') |
| self.do_run_from_file('src.cpp', 'result.out', force_c=True) |
| |
| def test_main_thread_async_em_asm(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_main_thread_async_em_asm') |
| self.do_run_in_out_file_test('tests', 'core', 'test_main_thread_async_em_asm', force_c=True) |
| |
| # Tests MAIN_THREAD_EM_ASM_INT() function call with different signatures. |
| def test_main_thread_em_asm_signatures(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_signatures', assert_returncode=None) |
| |
| def test_em_asm_unicode(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_unicode') |
| self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_unicode', force_c=True) |
| |
| def test_em_asm_types(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_types') |
| self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_types', force_c=True) |
| |
| def test_em_asm_unused_arguments(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_unused_arguments') |
| |
| # Verify that EM_ASM macros support getting called with multiple arities. |
| # Maybe tests will later be joined into larger compilation units? |
| # Then this must still be compiled separately from other code using EM_ASM |
| # macros with arities 1-3. Otherwise this may incorrectly report a success. |
| def test_em_asm_parameter_pack(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_parameter_pack') |
| |
| def test_em_asm_arguments_side_effects(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_arguments_side_effects') |
| self.do_run_in_out_file_test('tests', 'core', 'test_em_asm_arguments_side_effects', force_c=True) |
| |
| @parameterized({ |
| 'normal': ([],), |
| 'linked': (['-s', 'MAIN_MODULE'],), |
| }) |
| def test_em_js(self, args): |
| if 'MAIN_MODULE' in args and self.get_setting('WASM') == 0: |
| self.skipTest('main module support for non-wasm') |
| if '-fsanitize=address' in self.emcc_args: |
| self.skipTest('no dynamic library support in asan yet') |
| self.emcc_args += args + ['-s', 'EXPORTED_FUNCTIONS=["_main","_malloc"]'] |
| self.do_run_in_out_file_test('tests', 'core', 'test_em_js') |
| self.do_run_in_out_file_test('tests', 'core', 'test_em_js', force_c=True) |
| |
| def test_runtime_stacksave(self): |
| src = open(path_from_root('tests', 'core', 'test_runtime_stacksave.c')).read() |
| self.do_run(src, 'success') |
| |
| # Tests that -s MINIMAL_RUNTIME=1 builds can utilize -s ALLOW_MEMORY_GROWTH=1 option. |
| def test_minimal_runtime_memorygrowth(self): |
| if self.has_changed_setting('ALLOW_MEMORY_GROWTH'): |
| self.skipTest('test needs to modify memory growth') |
| self.set_setting('MINIMAL_RUNTIME', 1) |
| src = open(path_from_root('tests', 'core', 'test_memorygrowth.c')).read() |
| # Fail without memory growth |
| self.do_run(src, 'OOM', assert_returncode=None) |
| # Win with it |
| self.emcc_args += ['-Wno-almost-asm', '-s', 'ALLOW_MEMORY_GROWTH'] |
| self.do_run(src, '*pre: hello,4.955*\n*hello,4.955*\n*hello,4.955*') |
| |
| def test_memorygrowth(self): |
| if self.has_changed_setting('ALLOW_MEMORY_GROWTH'): |
| self.skipTest('test needs to modify memory growth') |
| if self.maybe_closure(): |
| # verify NO_DYNAMIC_EXECUTION is compatible with closure |
| self.set_setting('DYNAMIC_EXECUTION', 0) |
| # With typed arrays in particular, it is dangerous to use more memory than INITIAL_MEMORY, |
| # since we then need to enlarge the heap(s). |
| src = open(path_from_root('tests', 'core', 'test_memorygrowth.c')).read() |
| |
| # Fail without memory growth |
| self.do_run(src, 'OOM', assert_returncode=None) |
| fail = open('src.cpp.o.js').read() |
| |
| # Win with it |
| self.emcc_args += ['-Wno-almost-asm', '-s', 'ALLOW_MEMORY_GROWTH'] |
| self.do_run(src, '*pre: hello,4.955*\n*hello,4.955*\n*hello,4.955*') |
| win = open('src.cpp.o.js').read() |
| |
| if '-O2' in self.emcc_args and not self.is_wasm(): |
| # Make sure ALLOW_MEMORY_GROWTH generates different code (should be less optimized) |
| possible_starts = ['// EMSCRIPTEN_START_FUNCS', 'var TOTAL_STACK'] |
| code_start = None |
| for s in possible_starts: |
| if fail.find(s) >= 0: |
| code_start = s |
| break |
| assert code_start is not None, 'Generated code must contain one of ' + str(possible_starts) |
| |
| fail = fail[fail.find(code_start):] |
| win = win[win.find(code_start):] |
| assert len(fail) < len(win), 'failing code - without memory growth on - is more optimized, and smaller' + str([len(fail), len(win)]) |
| |
| # Tracing of memory growths should work |
| self.set_setting('EMSCRIPTEN_TRACING', 1) |
| self.emcc_args += ['--tracing'] |
| self.do_run(src, '*pre: hello,4.955*\n*hello,4.955*\n*hello,4.955*') |
| |
| def test_memorygrowth_2(self): |
| if self.has_changed_setting('ALLOW_MEMORY_GROWTH'): |
| self.skipTest('test needs to modify memory growth') |
| |
| # With typed arrays in particular, it is dangerous to use more memory than INITIAL_MEMORY, |
| # since we then need to enlarge the heap(s). |
| src = open(path_from_root('tests', 'core', 'test_memorygrowth_2.c')).read() |
| |
| # Fail without memory growth |
| self.do_run(src, 'OOM', assert_returncode=None) |
| fail = open('src.cpp.o.js').read() |
| |
| # Win with it |
| self.emcc_args += ['-Wno-almost-asm', '-s', 'ALLOW_MEMORY_GROWTH'] |
| self.do_run(src, '*pre: hello,4.955*\n*hello,4.955*\n*hello,4.955*') |
| win = open('src.cpp.o.js').read() |
| |
| if '-O2' in self.emcc_args and not self.is_wasm(): |
| # Make sure ALLOW_MEMORY_GROWTH generates different code (should be less optimized) |
| assert len(fail) < len(win), 'failing code - without memory growth on - is more optimized, and smaller' + str([len(fail), len(win)]) |
| |
| def test_memorygrowth_3(self): |
| if self.has_changed_setting('ALLOW_MEMORY_GROWTH'): |
| self.skipTest('test needs to modify memory growth') |
| |
| # checks handling of malloc failure properly |
| self.emcc_args += ['-s', 'ALLOW_MEMORY_GROWTH=0', '-s', 'ABORTING_MALLOC=0', '-s', 'SAFE_HEAP'] |
| self.do_run_in_out_file_test('tests', 'core', 'test_memorygrowth_3') |
| |
| @also_with_standalone_wasm(impure=True) |
| def test_memorygrowth_MAXIMUM_MEMORY(self): |
| if self.has_changed_setting('ALLOW_MEMORY_GROWTH'): |
| self.skipTest('test needs to modify memory growth') |
| if not self.is_wasm(): |
| self.skipTest('wasm memory specific test') |
| |
| # check that memory growth does not exceed the wasm mem max limit |
| self.emcc_args += ['-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'INITIAL_MEMORY=64Mb', '-s', 'MAXIMUM_MEMORY=100Mb'] |
| self.do_run_in_out_file_test('tests', 'core', 'test_memorygrowth_wasm_mem_max') |
| |
| def test_memorygrowth_linear_step(self): |
| if self.has_changed_setting('ALLOW_MEMORY_GROWTH'): |
| self.skipTest('test needs to modify memory growth') |
| if not self.is_wasm(): |
| self.skipTest('wasm memory specific test') |
| |
| # check that memory growth does not exceed the wasm mem max limit and is exactly or one step below the wasm mem max |
| self.emcc_args += ['-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'TOTAL_STACK=1Mb', '-s', 'INITIAL_MEMORY=64Mb', '-s', 'MAXIMUM_MEMORY=130Mb', '-s', 'MEMORY_GROWTH_LINEAR_STEP=1Mb'] |
| self.do_run_in_out_file_test('tests', 'core', 'test_memorygrowth_memory_growth_step') |
| |
| def test_memorygrowth_geometric_step(self): |
| if self.has_changed_setting('ALLOW_MEMORY_GROWTH'): |
| self.skipTest('test needs to modify memory growth') |
| if not self.is_wasm(): |
| self.skipTest('wasm memory specific test') |
| |
| self.emcc_args += ['-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'MEMORY_GROWTH_GEOMETRIC_STEP=15', '-s', 'MEMORY_GROWTH_GEOMETRIC_CAP=0'] |
| self.do_run_in_out_file_test('tests', 'core', 'test_memorygrowth_geometric_step') |
| |
| def test_memorygrowth_3_force_fail_reallocBuffer(self): |
| if self.has_changed_setting('ALLOW_MEMORY_GROWTH'): |
| self.skipTest('test needs to modify memory growth') |
| |
| self.emcc_args += ['-Wno-almost-asm', '-s', 'ALLOW_MEMORY_GROWTH=1', '-s', 'TEST_MEMORY_GROWTH_FAILS=1'] |
| self.do_run_in_out_file_test('tests', 'core', 'test_memorygrowth_3') |
| |
| @parameterized({ |
| 'nogrow': (['-s', 'ALLOW_MEMORY_GROWTH=0'],), |
| 'grow': (['-s', 'ALLOW_MEMORY_GROWTH=1'],) |
| }) |
| @no_asan('requires more memory when growing') |
| def test_aborting_new(self, args): |
| # test that C++ new properly errors if we fail to malloc when growth is |
| # enabled, with or without growth |
| self.emcc_args += ['-Wno-almost-asm', '-s', 'MAXIMUM_MEMORY=18MB'] + args |
| self.do_run_in_out_file_test('tests', 'core', 'test_aborting_new') |
| |
| @no_asmjs() |
| @no_wasm2js('no WebAssembly.Memory()') |
| @no_asan('ASan alters the memory size') |
| def test_module_wasm_memory(self): |
| self.emcc_args += ['--pre-js', path_from_root('tests', 'core', 'test_module_wasm_memory.js')] |
| src = open(path_from_root('tests', 'core', 'test_module_wasm_memory.c')).read() |
| self.do_run(src, 'success', force_c=True) |
| |
| def test_ssr(self): # struct self-ref |
| src = ''' |
| #include <stdio.h> |
| |
| // see related things in openjpeg |
| typedef struct opj_mqc_state { |
| unsigned int qeval; |
| int mps; |
| struct opj_mqc_state *nmps; |
| struct opj_mqc_state *nlps; |
| } opj_mqc_state_t; |
| |
| static opj_mqc_state_t mqc_states[4] = { |
| {0x5600, 0, &mqc_states[2], &mqc_states[3]}, |
| {0x5602, 1, &mqc_states[3], &mqc_states[2]}, |
| }; |
| |
| int main() { |
| printf("*%d*\\n", (int)(mqc_states+1)-(int)mqc_states); |
| for (int i = 0; i < 2; i++) |
| printf("%d:%d,%d,%d,%d\\n", i, mqc_states[i].qeval, mqc_states[i].mps, |
| (int)mqc_states[i].nmps-(int)mqc_states, (int)mqc_states[i].nlps-(int)mqc_states); |
| return 0; |
| } |
| ''' |
| self.do_run(src, '''*16*\n0:22016,0,32,48\n1:22018,1,48,32\n''') |
| |
| def test_tinyfuncstr(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_tinyfuncstr') |
| |
| def test_llvmswitch(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_llvmswitch') |
| |
| def test_cxx_version(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_cxx_version') |
| |
| @no_wasm2js('massive switches can break js engines') |
| def test_bigswitch(self): |
| src = open(path_from_root('tests', 'bigswitch.cpp')).read() |
| self.do_run(src, '''34962: GL_ARRAY_BUFFER (0x8892) |
| 26214: what? |
| 35040: GL_STREAM_DRAW (0x88E0) |
| 3060: what? |
| ''', args=['34962', '26214', '35040', str(0xbf4)], assert_returncode=None) |
| |
| @no_wasm2js('massive switches can break js engines') |
| @is_slow_test |
| def test_biggerswitch(self): |
| if self.is_wasm_backend(): |
| if not is_optimizing(self.emcc_args): |
| self.skipTest('nodejs takes >6GB to compile this if the wasm is not optimized, which OOMs, see https://github.com/emscripten-core/emscripten/issues/7928#issuecomment-458308453') |
| if '-Os' in self.emcc_args: |
| self.skipTest('hangs in recent upstream clang, see https://bugs.llvm.org/show_bug.cgi?id=43468') |
| num_cases = 20000 |
| switch_case = run_process([PYTHON, path_from_root('tests', 'gen_large_switchcase.py'), str(num_cases)], stdout=PIPE, stderr=PIPE).stdout |
| self.do_run(switch_case, '''58996: 589965899658996 |
| 59297: 592975929759297 |
| 59598: default |
| 59899: 598995989959899 |
| Success!''') |
| |
| def test_indirectbr(self): |
| self.emcc_args = [x for x in self.emcc_args if x != '-g'] |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_indirectbr') |
| |
| @no_asan('local count too large for VMs') |
| @no_wasm2js('extremely deep nesting, hits stack limit on some VMs') |
| def test_indirectbr_many(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_indirectbr_many') |
| |
| def test_pack(self): |
| src = ''' |
| #include <stdio.h> |
| #include <string.h> |
| |
| #pragma pack(push,1) |
| typedef struct header |
| { |
| unsigned char id; |
| unsigned short colour; |
| unsigned char desc; |
| } header; |
| #pragma pack(pop) |
| |
| typedef struct fatheader |
| { |
| unsigned char id; |
| unsigned short colour; |
| unsigned char desc; |
| } fatheader; |
| |
| int main( int argc, const char *argv[] ) { |
| header h, *ph = 0; |
| fatheader fh, *pfh = 0; |
| printf("*%d,%d,%d*\\n", sizeof(header), (int)((int)&h.desc - (int)&h.id), (int)(&ph[1])-(int)(&ph[0])); |
| printf("*%d,%d,%d*\\n", sizeof(fatheader), (int)((int)&fh.desc - (int)&fh.id), (int)(&pfh[1])-(int)(&pfh[0])); |
| return 0; |
| } |
| ''' |
| self.do_run(src, '*4,3,4*\n*6,4,6*') |
| |
| def test_varargs(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_varargs') |
| |
| @no_wasm_backend('Calling varargs across function calls is undefined behavior in C,' |
| ' and asmjs and wasm implement it differently.') |
| def test_varargs_multi(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_varargs_multi') |
| |
| @unittest.skip('clang cannot compile this code with that target yet') |
| def test_varargs_byval(self): |
| src = r''' |
| #include <stdio.h> |
| #include <stdarg.h> |
| |
| typedef struct type_a { |
| union { |
| double f; |
| void *p; |
| int i; |
| short sym; |
| } value; |
| } type_a; |
| |
| enum mrb_vtype { |
| MRB_TT_FALSE = 0, /* 0 */ |
| MRB_TT_CLASS = 9 /* 9 */ |
| }; |
| |
| typedef struct type_b { |
| enum mrb_vtype tt:8; |
| } type_b; |
| |
| void print_type_a(int argc, ...); |
| void print_type_b(int argc, ...); |
| |
| int main(int argc, char *argv[]) |
| { |
| type_a a; |
| type_b b; |
| a.value.p = (void*) 0x12345678; |
| b.tt = MRB_TT_CLASS; |
| |
| printf("The original address of a is: %p\n", a.value.p); |
| printf("The original type of b is: %d\n", b.tt); |
| |
| print_type_a(1, a); |
| print_type_b(1, b); |
| |
| return 0; |
| } |
| |
| void print_type_a(int argc, ...) { |
| va_list ap; |
| type_a a; |
| |
| va_start(ap, argc); |
| a = va_arg(ap, type_a); |
| va_end(ap); |
| |
| printf("The current address of a is: %p\n", a.value.p); |
| } |
| |
| void print_type_b(int argc, ...) { |
| va_list ap; |
| type_b b; |
| |
| va_start(ap, argc); |
| b = va_arg(ap, type_b); |
| va_end(ap); |
| |
| printf("The current type of b is: %d\n", b.tt); |
| } |
| ''' |
| self.do_run(src, '''The original address of a is: 0x12345678 |
| The original type of b is: 9 |
| The current address of a is: 0x12345678 |
| The current type of b is: 9 |
| ''') |
| |
| def test_functionpointer_libfunc_varargs(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_functionpointer_libfunc_varargs') |
| |
| def test_structbyval(self): |
| self.set_setting('INLINING_LIMIT', 50) |
| |
| # part 1: make sure that normally, passing structs by value works |
| |
| src = r''' |
| #include <stdio.h> |
| |
| struct point |
| { |
| int x, y; |
| }; |
| |
| void dump(struct point p) { |
| p.x++; // should not modify |
| p.y++; // anything in the caller! |
| printf("dump: %d,%d\n", p.x, p.y); |
| } |
| |
| void dumpmod(struct point *p) { |
| p->x++; // should not modify |
| p->y++; // anything in the caller! |
| printf("dump: %d,%d\n", p->x, p->y); |
| } |
| |
| int main( int argc, const char *argv[] ) { |
| point p = { 54, 2 }; |
| printf("pre: %d,%d\n", p.x, p.y); |
| dump(p); |
| void (*dp)(point p) = dump; // And, as a function pointer |
| dp(p); |
| printf("post: %d,%d\n", p.x, p.y); |
| dumpmod(&p); |
| dumpmod(&p); |
| printf("last: %d,%d\n", p.x, p.y); |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'pre: 54,2\ndump: 55,3\ndump: 55,3\npost: 54,2\ndump: 55,3\ndump: 56,4\nlast: 56,4') |
| |
| def test_stdlibs(self): |
| # safe heap prints a warning that messes up our output. |
| self.set_setting('SAFE_HEAP', 0) |
| # needs atexit |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_stdlibs') |
| |
| def test_stdbool(self): |
| src = r''' |
| #include <stdio.h> |
| #include <stdbool.h> |
| |
| int main() { |
| bool x = true; |
| bool y = false; |
| printf("*%d*\n", x != y); |
| return 0; |
| } |
| ''' |
| |
| self.do_run(src, '*1*', force_c=True) |
| |
| def test_strtoll_hex(self): |
| # tests strtoll for hex strings (0x...) |
| self.do_run_in_out_file_test('tests', 'core', 'test_strtoll_hex') |
| |
| def test_strtoll_dec(self): |
| # tests strtoll for decimal strings (0x...) |
| self.do_run_in_out_file_test('tests', 'core', 'test_strtoll_dec') |
| |
| def test_strtoll_bin(self): |
| # tests strtoll for binary strings (0x...) |
| self.do_run_in_out_file_test('tests', 'core', 'test_strtoll_bin') |
| |
| def test_strtoll_oct(self): |
| # tests strtoll for decimal strings (0x...) |
| self.do_run_in_out_file_test('tests', 'core', 'test_strtoll_oct') |
| |
| def test_strtol_hex(self): |
| # tests strtoll for hex strings (0x...) |
| self.do_run_in_out_file_test('tests', 'core', 'test_strtol_hex') |
| |
| def test_strtol_dec(self): |
| # tests strtoll for decimal strings (0x...) |
| self.do_run_in_out_file_test('tests', 'core', 'test_strtol_dec') |
| |
| def test_strtol_bin(self): |
| # tests strtoll for binary strings (0x...) |
| self.do_run_in_out_file_test('tests', 'core', 'test_strtol_bin') |
| |
| def test_strtol_oct(self): |
| # tests strtoll for decimal strings (0x...) |
| self.do_run_in_out_file_test('tests', 'core', 'test_strtol_oct') |
| |
| @also_with_standalone_wasm() |
| def test_atexit(self): |
| # Confirms they are called in the proper reverse order |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_atexit') |
| |
| def test_atexit_threads(self): |
| # also tests thread exit (__cxa_thread_atexit) |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_atexit_threads') |
| |
| @no_asan('test relies on null pointer reads') |
| def test_pthread_specific(self): |
| src = open(path_from_root('tests', 'pthread', 'specific.c')).read() |
| expected = open(path_from_root('tests', 'pthread', 'specific.c.txt')).read() |
| self.do_run(src, expected, force_c=True) |
| |
| def test_pthread_equal(self): |
| self.do_run_in_out_file_test('tests', 'pthread', 'test_pthread_equal') |
| |
| def test_tcgetattr(self): |
| src = open(path_from_root('tests', 'termios', 'test_tcgetattr.c')).read() |
| self.do_run(src, 'success', force_c=True) |
| |
| def test_time(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_time') |
| for tz in ['EST+05EDT', 'UTC+0']: |
| print('extra tz test:', tz) |
| with env_modify({'TZ': tz}): |
| # Run the test with different time zone settings if |
| # possible. It seems that the TZ environment variable does not |
| # work all the time (at least it's not well respected by |
| # Node.js on Windows), but it does no harm either. |
| self.do_run_in_out_file_test('tests', 'core', 'test_time') |
| |
| def test_timeb(self): |
| # Confirms they are called in reverse order |
| self.do_run_in_out_file_test('tests', 'core', 'test_timeb') |
| |
| def test_time_c(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_time_c') |
| |
| def test_gmtime(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_gmtime') |
| |
| def test_strptime_tm(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_strptime_tm') |
| |
| def test_strptime_days(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_strptime_days') |
| |
| def test_strptime_reentrant(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_strptime_reentrant') |
| |
| def test_strftime(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_strftime') |
| |
| @no_wasm_backend("wasm backend doesn't compile intentional segfault into an abort() call. " |
| "It also doesn't segfault.") |
| def test_intentional_fault(self): |
| # Some programs intentionally segfault themselves, we should compile that into a throw |
| src = open(path_from_root('tests', 'core', 'test_intentional_fault.c')).read() |
| self.do_run(src, 'abort(' if self.run_name != 'asm2g' else 'abort(segmentation fault', assert_returncode=None) |
| |
| def test_trickystring(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_trickystring') |
| |
| def test_statics(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_statics') |
| |
| def test_copyop(self): |
| # clang generated code is vulnerable to this, as it uses |
| # memcpy for assignments, with hardcoded numbers of bytes |
| # (llvm-gcc copies items one by one). |
| self.do_run_in_out_file_test('tests', 'core', 'test_copyop') |
| |
| def test_memcpy_memcmp(self): |
| self.banned_js_engines = [V8_ENGINE] # Currently broken under V8_ENGINE but not node |
| |
| def check(result, err): |
| result = result.replace('\n \n', '\n') # remove extra node output |
| return hashlib.sha1(result.encode('utf-8')).hexdigest() |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_memcpy_memcmp', output_nicerizer=check) |
| |
| def test_memcpy2(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_memcpy2', assert_returncode=None) |
| |
| def test_memcpy3(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_memcpy3', assert_returncode=None) |
| |
| @also_with_standalone_wasm() |
| def test_memcpy_alignment(self): |
| self.do_run(open(path_from_root('tests', 'test_memcpy_alignment.cpp')).read(), 'OK.') |
| |
| def test_memset_alignment(self): |
| self.do_run(open(path_from_root('tests', 'test_memset_alignment.cpp')).read(), 'OK.') |
| |
| def test_memset(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_memset', assert_returncode=None) |
| |
| def test_getopt(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_getopt', args=['-t', '12', '-n', 'foobar']) |
| |
| def test_getopt_long(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_getopt_long', args=['--file', 'foobar', '-b']) |
| |
| def test_memmove(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_memmove') |
| |
| def test_memmove2(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_memmove2', assert_returncode=None) |
| |
| def test_memmove3(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_memmove3') |
| |
| def test_flexarray_struct(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_flexarray_struct') |
| |
| def test_bsearch(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_bsearch') |
| |
| @no_wasm_backend("https://github.com/emscripten-core/emscripten/issues/9039") |
| def test_stack_overflow(self): |
| self.set_setting('ASSERTIONS', 1) |
| self.do_run(open(path_from_root('tests', 'core', 'stack_overflow.cpp')).read(), 'Stack overflow!', assert_returncode=None) |
| |
| def test_stackAlloc(self): |
| self.do_run_in_out_file_test('tests', 'core', 'stackAlloc') |
| |
| def test_nestedstructs(self): |
| src = ''' |
| #include <stdio.h> |
| #include "emscripten.h" |
| |
| struct base { |
| int x; |
| float y; |
| union { |
| int a; |
| float b; |
| }; |
| char c; |
| }; |
| |
| struct hashtableentry { |
| int key; |
| base data; |
| }; |
| |
| struct hashset { |
| typedef hashtableentry entry; |
| struct chain { entry elem; chain *next; }; |
| // struct chainchunk { chain chains[100]; chainchunk *next; }; |
| }; |
| |
| struct hashtable : hashset { |
| hashtable() { |
| base *b = NULL; |
| entry *e = NULL; |
| chain *c = NULL; |
| printf("*%d,%d,%d,%d,%d,%d|%d,%d,%d,%d,%d,%d,%d,%d|%d,%d,%d,%d,%d,%d,%d,%d,%d,%d*\\n", |
| sizeof(base), |
| int(&(b->x)), int(&(b->y)), int(&(b->a)), int(&(b->b)), int(&(b->c)), |
| sizeof(hashtableentry), |
| int(&(e->key)), int(&(e->data)), int(&(e->data.x)), int(&(e->data.y)), int(&(e->data.a)), int(&(e->data.b)), int(&(e->data.c)), |
| sizeof(hashset::chain), |
| int(&(c->elem)), int(&(c->next)), int(&(c->elem.key)), int(&(c->elem.data)), int(&(c->elem.data.x)), int(&(c->elem.data.y)), int(&(c->elem.data.a)), int(&(c->elem.data.b)), int(&(c->elem.data.c)) |
| ); |
| } |
| }; |
| |
| struct B { char buffer[62]; int last; char laster; char laster2; }; |
| |
| struct Bits { |
| unsigned short A : 1; |
| unsigned short B : 1; |
| unsigned short C : 1; |
| unsigned short D : 1; |
| unsigned short x1 : 1; |
| unsigned short x2 : 1; |
| unsigned short x3 : 1; |
| unsigned short x4 : 1; |
| }; |
| |
| int main() { |
| hashtable t; |
| |
| // Part 2 - the char[] should be compressed, BUT have a padding space at the end so the next |
| // one is aligned properly. Also handle char; char; etc. properly. |
| B *b = NULL; |
| printf("*%d,%d,%d,%d,%d,%d,%d,%d,%d*\\n", int(b), int(&(b->buffer)), int(&(b->buffer[0])), int(&(b->buffer[1])), int(&(b->buffer[2])), |
| int(&(b->last)), int(&(b->laster)), int(&(b->laster2)), sizeof(B)); |
| |
| // Part 3 - bitfields, and small structures |
| Bits *b2 = NULL; |
| printf("*%d*\\n", sizeof(Bits)); |
| |
| return 0; |
| } |
| ''' |
| # Bloated memory; same layout as C/C++ |
| self.do_run(src, '*16,0,4,8,8,12|20,0,4,4,8,12,12,16|24,0,20,0,4,4,8,12,12,16*\n*0,0,0,1,2,64,68,69,72*\n*2*') |
| |
| def prep_dlfcn_lib(self): |
| self.clear_setting('MAIN_MODULE') |
| self.set_setting('SIDE_MODULE') |
| |
| def prep_dlfcn_main(self): |
| self.set_setting('MAIN_MODULE') |
| self.clear_setting('SIDE_MODULE') |
| |
| create_test_file('lib_so_pre.js', ''' |
| if (!Module['preRun']) Module['preRun'] = []; |
| Module['preRun'].push(function() { FS.createDataFile('/', 'liblib.so', %s, true, false, false); }); |
| ''' % str(list(bytearray(open('liblib.so', 'rb').read())))) |
| self.emcc_args += ['--pre-js', 'lib_so_pre.js'] |
| |
| def build_dlfcn_lib(self, lib_src, dirname, filename): |
| if self.get_setting('WASM'): |
| # emcc emits a wasm in this case |
| self.build(lib_src, dirname, filename, js_outfile=False) |
| shutil.move(filename + '.o.wasm', os.path.join(dirname, 'liblib.so')) |
| else: |
| self.build(lib_src, dirname, filename) |
| shutil.move(filename + '.o.js', os.path.join(dirname, 'liblib.so')) |
| |
| @needs_dlfcn |
| def test_dlfcn_missing(self): |
| self.set_setting('MAIN_MODULE', 1) |
| if self.has_changed_setting('ASSERTIONS'): |
| self.skipTest('test needs to customize ASSERTIONS') |
| self.set_setting('ASSERTIONS', 1) |
| src = r''' |
| #include <dlfcn.h> |
| #include <stdio.h> |
| #include <assert.h> |
| |
| int main() { |
| void* lib_handle = dlopen("libfoo.so", RTLD_NOW); |
| assert(!lib_handle); |
| printf("error: %s\n", dlerror()); |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'error: Could not load dynamic lib: libfoo.so\nError: No such file or directory') |
| print('without assertions, the error is less clear') |
| self.set_setting('ASSERTIONS', 0) |
| self.do_run(src, 'error: Could not load dynamic lib: libfoo.so\nError: FS error') |
| |
| @needs_dlfcn |
| def test_dlfcn_basic(self): |
| self.prep_dlfcn_lib() |
| lib_src = ''' |
| #include <cstdio> |
| |
| class Foo { |
| public: |
| Foo() { |
| puts("Constructing lib object."); |
| } |
| }; |
| |
| Foo global; |
| ''' |
| dirname = self.get_dir() |
| filename = os.path.join(dirname, 'liblib.cpp') |
| self.build_dlfcn_lib(lib_src, dirname, filename) |
| |
| self.prep_dlfcn_main() |
| src = ''' |
| #include <cstdio> |
| #include <dlfcn.h> |
| |
| class Bar { |
| public: |
| Bar() { |
| puts("Constructing main object."); |
| } |
| }; |
| |
| Bar global; |
| |
| int main() { |
| dlopen("liblib.so", RTLD_NOW); |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'Constructing main object.\nConstructing lib object.\n') |
| |
| @needs_dlfcn |
| def test_dlfcn_i64(self): |
| self.prep_dlfcn_lib() |
| self.set_setting('EXPORTED_FUNCTIONS', ['_foo']) |
| lib_src = ''' |
| int foo(int x) { |
| return (long long)x / (long long)1234; |
| } |
| ''' |
| dirname = self.get_dir() |
| filename = os.path.join(dirname, 'liblib.c') |
| self.build_dlfcn_lib(lib_src, dirname, filename) |
| |
| self.prep_dlfcn_main() |
| self.clear_setting('EXPORTED_FUNCTIONS') |
| src = r''' |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <dlfcn.h> |
| |
| typedef int (*intfunc)(int); |
| |
| void *p; |
| |
| int main() { |
| p = malloc(1024); |
| void *lib_handle = dlopen("liblib.so", 0); |
| if (!lib_handle) { |
| puts(dlerror()); |
| abort(); |
| } |
| printf("dll handle: %p\n", lib_handle); |
| intfunc x = (intfunc)dlsym(lib_handle, "foo"); |
| printf("foo func handle: %p\n", x); |
| if (p == 0) return 1; |
| if (!x) { |
| printf("dlsym failed: %s\n", dlerror()); |
| return 1; |
| } |
| printf("|%d|\n", x(81234567)); |
| return 0; |
| } |
| ''' |
| self.do_run(src, '|65830|') |
| |
| @needs_dlfcn |
| @no_wasm('EM_ASM in shared wasm modules, stored inside the wasm somehow') |
| def test_dlfcn_em_asm(self): |
| self.prep_dlfcn_lib() |
| lib_src = ''' |
| #include <emscripten.h> |
| class Foo { |
| public: |
| Foo() { |
| EM_ASM( out("Constructing lib object.") ); |
| } |
| }; |
| Foo global; |
| ''' |
| filename = 'liblib.cpp' |
| self.build_dlfcn_lib(lib_src, self.get_dir(), filename) |
| |
| self.prep_dlfcn_main() |
| src = ''' |
| #include <emscripten.h> |
| #include <dlfcn.h> |
| class Bar { |
| public: |
| Bar() { |
| EM_ASM( out("Constructing main object.") ); |
| } |
| }; |
| Bar global; |
| int main() { |
| dlopen("liblib.so", RTLD_NOW); |
| EM_ASM( out("All done.") ); |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'Constructing main object.\nConstructing lib object.\nAll done.\n') |
| |
| @needs_dlfcn |
| def test_dlfcn_qsort(self): |
| self.prep_dlfcn_lib() |
| self.set_setting('EXPORTED_FUNCTIONS', ['_get_cmp']) |
| lib_src = ''' |
| int lib_cmp(const void* left, const void* right) { |
| const int* a = (const int*) left; |
| const int* b = (const int*) right; |
| if(*a > *b) return 1; |
| else if(*a == *b) return 0; |
| else return -1; |
| } |
| |
| typedef int (*CMP_TYPE)(const void*, const void*); |
| |
| extern "C" CMP_TYPE get_cmp() { |
| return lib_cmp; |
| } |
| ''' |
| dirname = self.get_dir() |
| filename = os.path.join(dirname, 'liblib.cpp') |
| self.build_dlfcn_lib(lib_src, dirname, filename) |
| |
| self.prep_dlfcn_main() |
| self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_malloc']) |
| src = ''' |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <dlfcn.h> |
| |
| typedef int (*CMP_TYPE)(const void*, const void*); |
| |
| int main_cmp(const void* left, const void* right) { |
| const int* a = (const int*) left; |
| const int* b = (const int*) right; |
| if(*a < *b) return 1; |
| else if(*a == *b) return 0; |
| else return -1; |
| } |
| |
| int main() { |
| void* lib_handle; |
| CMP_TYPE (*getter_ptr)(); |
| CMP_TYPE lib_cmp_ptr; |
| int arr[5] = {4, 2, 5, 1, 3}; |
| |
| qsort((void*)arr, 5, sizeof(int), main_cmp); |
| printf("Sort with main comparison: "); |
| for (int i = 0; i < 5; i++) { |
| printf("%d ", arr[i]); |
| } |
| printf("\\n"); |
| |
| lib_handle = dlopen("liblib.so", RTLD_NOW); |
| if (lib_handle == NULL) { |
| printf("Could not load lib.\\n"); |
| return 1; |
| } |
| getter_ptr = (CMP_TYPE (*)()) dlsym(lib_handle, "get_cmp"); |
| if (getter_ptr == NULL) { |
| printf("Could not find func.\\n"); |
| return 1; |
| } |
| lib_cmp_ptr = getter_ptr(); |
| qsort((void*)arr, 5, sizeof(int), lib_cmp_ptr); |
| printf("Sort with lib comparison: "); |
| for (int i = 0; i < 5; i++) { |
| printf("%d ", arr[i]); |
| } |
| printf("\\n"); |
| |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'Sort with main comparison: 5 4 3 2 1 *Sort with lib comparison: 1 2 3 4 5 *', |
| output_nicerizer=lambda x, err: x.replace('\n', '*')) |
| |
| if self.get_setting('ASM_JS') and SPIDERMONKEY_ENGINE and os.path.exists(SPIDERMONKEY_ENGINE[0]) and not self.is_wasm(): |
| out = run_js('liblib.so', engine=SPIDERMONKEY_ENGINE, full_output=True, stderr=STDOUT) |
| if 'asm' in out: |
| self.validate_asmjs(out) |
| |
| @needs_dlfcn |
| def test_dlfcn_data_and_fptr(self): |
| # Failing under v8 since: https://chromium-review.googlesource.com/712595 |
| if self.is_wasm(): |
| self.banned_js_engines = [V8_ENGINE] |
| |
| self.prep_dlfcn_lib() |
| lib_src = r''' |
| #include <stdio.h> |
| |
| int theglobal = 42; |
| |
| extern void parent_func(); // a function that is defined in the parent |
| |
| int* lib_get_global_addr() { |
| return &theglobal; |
| } |
| |
| void lib_fptr() { |
| printf("Second calling lib_fptr from main.\n"); |
| parent_func(); |
| // call it also through a pointer, to check indexizing |
| void (*p_f)(); |
| p_f = parent_func; |
| p_f(); |
| } |
| |
| extern "C" void (*func(int x, void(*fptr)()))() { |
| printf("In func: %d\n", x); |
| fptr(); |
| return lib_fptr; |
| } |
| ''' |
| dirname = self.get_dir() |
| filename = os.path.join(dirname, 'liblib.cpp') |
| self.set_setting('EXPORTED_FUNCTIONS', ['_func']) |
| self.build_dlfcn_lib(lib_src, dirname, filename) |
| |
| self.prep_dlfcn_main() |
| src = r''' |
| #include <stdio.h> |
| #include <dlfcn.h> |
| #include <emscripten.h> |
| |
| typedef void (*FUNCTYPE(int, void(*)()))(); |
| |
| FUNCTYPE func; |
| |
| void EMSCRIPTEN_KEEPALIVE parent_func() { |
| printf("parent_func called from child\n"); |
| } |
| |
| void main_fptr() { |
| printf("First calling main_fptr from lib.\n"); |
| } |
| |
| int main() { |
| void* lib_handle; |
| FUNCTYPE* func_fptr; |
| |
| // Test basic lib loading. |
| lib_handle = dlopen("liblib.so", RTLD_NOW); |
| if (lib_handle == NULL) { |
| printf("Could not load lib.\n"); |
| return 1; |
| } |
| |
| // Test looked up function. |
| func_fptr = (FUNCTYPE*) dlsym(lib_handle, "func"); |
| // Load twice to test cache. |
| func_fptr = (FUNCTYPE*) dlsym(lib_handle, "func"); |
| if (func_fptr == NULL) { |
| printf("Could not find func.\n"); |
| return 1; |
| } |
| |
| // Test passing function pointers across module bounds. |
| void (*fptr)() = func_fptr(13, main_fptr); |
| fptr(); |
| |
| // Test global data. |
| int* globaladdr = (int*) dlsym(lib_handle, "theglobal"); |
| if (globaladdr == NULL) { |
| printf("Could not find global.\n"); |
| return 1; |
| } |
| |
| printf("Var: %d\n", *globaladdr); |
| |
| return 0; |
| } |
| ''' |
| self.set_setting('EXPORTED_FUNCTIONS', ['_main']) |
| self.do_run(src, '''\ |
| In func: 13 |
| First calling main_fptr from lib. |
| Second calling lib_fptr from main. |
| parent_func called from child |
| parent_func called from child |
| Var: 42 |
| ''') |
| |
| @needs_dlfcn |
| def test_dlfcn_varargs(self): |
| # this test is not actually valid - it fails natively. the child should fail |
| # to be loaded, not load and successfully see the parent print_ints func |
| |
| self.prep_dlfcn_lib() |
| lib_src = r''' |
| void print_ints(int n, ...); |
| extern "C" void func() { |
| print_ints(2, 13, 42); |
| } |
| ''' |
| dirname = self.get_dir() |
| filename = os.path.join(dirname, 'liblib.cpp') |
| self.set_setting('EXPORTED_FUNCTIONS', ['_func']) |
| self.build_dlfcn_lib(lib_src, dirname, filename) |
| |
| self.prep_dlfcn_main() |
| src = r''' |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <dlfcn.h> |
| #include <assert.h> |
| |
| void print_ints(int n, ...) { |
| va_list args; |
| va_start(args, n); |
| for (int i = 0; i < n; i++) { |
| printf("%d\n", va_arg(args, int)); |
| } |
| va_end(args); |
| } |
| |
| int main() { |
| void* lib_handle; |
| void (*fptr)(); |
| |
| print_ints(2, 100, 200); |
| |
| lib_handle = dlopen("liblib.so", RTLD_NOW); |
| assert(lib_handle); |
| fptr = (void (*)())dlsym(lib_handle, "func"); |
| fptr(); |
| |
| return 0; |
| } |
| ''' |
| self.set_setting('EXPORTED_FUNCTIONS', ['_main']) |
| self.do_run(src, '100\n200\n13\n42\n') |
| |
| @needs_dlfcn |
| def test_dlfcn_alignment_and_zeroing(self): |
| self.prep_dlfcn_lib() |
| self.set_setting('INITIAL_MEMORY', 16 * 1024 * 1024) |
| lib_src = r''' |
| extern "C" { |
| int prezero = 0; |
| __attribute__((aligned(1024))) int superAligned = 12345; |
| int postzero = 0; |
| } |
| ''' |
| dirname = self.get_dir() |
| filename = os.path.join(dirname, 'liblib.cpp') |
| self.build_dlfcn_lib(lib_src, dirname, filename) |
| for i in range(10): |
| curr = '%d.so' % i |
| shutil.copyfile('liblib.so', curr) |
| self.emcc_args += ['--embed-file', curr] |
| |
| self.prep_dlfcn_main() |
| self.set_setting('INITIAL_MEMORY', 128 * 1024 * 1024) |
| src = r''' |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <dlfcn.h> |
| #include <assert.h> |
| #include <emscripten.h> |
| |
| int main() { |
| printf("'prepare' memory with non-zero inited stuff\n"); |
| int num = 120 * 1024 * 1024; // total is 128; we'll use 5*5 = 25 at least, so allocate pretty much all of it |
| void* mem = malloc(num); |
| assert(mem); |
| printf("setting this range to non-zero: %d - %d\n", int(mem), int(mem) + num); |
| memset(mem, 1, num); |
| EM_ASM({ |
| var value = HEAP8[64*1024*1024]; |
| out('verify middle of memory is non-zero: ' + value); |
| assert(value === 1); |
| }); |
| free(mem); |
| for (int i = 0; i < 10; i++) { |
| char curr[] = "?.so"; |
| curr[0] = '0' + i; |
| printf("loading %s\n", curr); |
| void* lib_handle = dlopen(curr, RTLD_NOW); |
| if (!lib_handle) { |
| puts(dlerror()); |
| assert(0); |
| } |
| printf("getting superAligned\n"); |
| int* superAligned = (int*)dlsym(lib_handle, "superAligned"); |
| assert(superAligned); |
| assert(int(superAligned) % 1024 == 0); // alignment |
| printf("checking value of superAligned, at %d\n", superAligned); |
| assert(*superAligned == 12345); // value |
| printf("getting prezero\n"); |
| int* prezero = (int*)dlsym(lib_handle, "prezero"); |
| assert(prezero); |
| printf("checking value of prezero, at %d\n", prezero); |
| assert(*prezero == 0); |
| *prezero = 1; |
| assert(*prezero != 0); |
| printf("getting postzero\n"); |
| int* postzero = (int*)dlsym(lib_handle, "postzero"); |
| printf("checking value of postzero, at %d\n", postzero); |
| assert(postzero); |
| printf("checking value of postzero\n"); |
| assert(*postzero == 0); |
| *postzero = 1; |
| assert(*postzero != 0); |
| } |
| printf("success.\n"); |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'success.\n') |
| |
| @needs_dlfcn |
| def test_dlfcn_self(self): |
| self.set_setting('MAIN_MODULE') |
| self.set_setting('EXPORT_ALL') |
| # TODO(https://github.com/emscripten-core/emscripten/issues/11121) |
| # We link with C++ stdlibs, even when linking with emcc for historical reasons. We can remove |
| # this if this issues is fixed. |
| self.emcc_args.append('-nostdlib++') |
| |
| def post(filename): |
| js = open(filename).read() |
| start = js.find('var NAMED_GLOBALS') |
| first = js.find('{', start) |
| last = js.find('}', start) |
| exports = js[first + 1:last] |
| exports = exports.split(',') |
| # ensure there aren't too many globals; we don't want unnamed_addr |
| exports = [e.split(':')[0].strip('"') for e in exports] |
| exports.sort() |
| self.assertGreater(len(exports), 20) |
| # wasm backend includes alias in NAMED_GLOBALS |
| if self.is_wasm_backend(): |
| self.assertLess(len(exports), 56) |
| else: |
| self.assertLess(len(exports), 33) |
| |
| self.do_run_in_out_file_test('tests', 'core', 'test_dlfcn_self', post_build=post) |
| |
| @needs_dlfcn |
| def test_dlfcn_unique_sig(self): |
| self.prep_dlfcn_lib() |
| lib_src = ''' |
| #include <stdio.h> |
| |
| int myfunc(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m) { |
| return 13; |
| } |
| ''' |
| self.set_setting('EXPORTED_FUNCTIONS', ['_myfunc']) |
| dirname = self.get_dir() |
| filename = os.path.join(dirname, 'liblib.c') |
| self.build_dlfcn_lib(lib_src, dirname, filename) |
| |
| self.prep_dlfcn_main() |
| src = ''' |
| #include <assert.h> |
| #include <stdio.h> |
| #include <dlfcn.h> |
| |
| typedef int (*FUNCTYPE)(int, int, int, int, int, int, int, int, int, int, int, int, int); |
| |
| int main() { |
| void *lib_handle; |
| FUNCTYPE func_ptr; |
| |
| lib_handle = dlopen("liblib.so", RTLD_NOW); |
| assert(lib_handle != NULL); |
| |
| func_ptr = (FUNCTYPE)dlsym(lib_handle, "myfunc"); |
| assert(func_ptr != NULL); |
| assert(func_ptr(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) == 13); |
| |
| puts("success"); |
| |
| return 0; |
| } |
| ''' |
| self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_malloc']) |
| self.do_run(src, 'success', force_c=True) |
| |
| @needs_dlfcn |
| def test_dlfcn_info(self): |
| |
| self.prep_dlfcn_lib() |
| lib_src = ''' |
| #include <stdio.h> |
| |
| int myfunc(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m) { |
| return 13; |
| } |
| ''' |
| self.set_setting('EXPORTED_FUNCTIONS', ['_myfunc']) |
| dirname = self.get_dir() |
| filename = os.path.join(dirname, 'liblib.c') |
| self.build_dlfcn_lib(lib_src, dirname, filename) |
| |
| self.prep_dlfcn_main() |
| src = ''' |
| #include <assert.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <dlfcn.h> |
| |
| typedef int (*FUNCTYPE)(int, int, int, int, int, int, int, int, int, int, int, int, int); |
| |
| int main() { |
| void *lib_handle; |
| FUNCTYPE func_ptr; |
| |
| lib_handle = dlopen("liblib.so", RTLD_NOW); |
| assert(lib_handle != NULL); |
| |
| func_ptr = (FUNCTYPE)dlsym(lib_handle, "myfunc"); |
| assert(func_ptr != NULL); |
| assert(func_ptr(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) == 13); |
| |
| /* Verify that we don't corrupt func_ptr when calling dladdr. */ |
| Dl_info info; |
| memset(&info, 0, sizeof(info)); |
| dladdr(func_ptr, &info); |
| |
| assert(func_ptr != NULL); |
| assert(func_ptr(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) == 13); |
| |
| /* Verify something useful lives in info. */ |
| assert(info.dli_fname != NULL); |
| assert(info.dli_fbase == NULL); |
| assert(info.dli_sname == NULL); |
| assert(info.dli_saddr == NULL); |
| |
| puts("success"); |
| |
| return 0; |
| } |
| ''' |
| self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_malloc']) |
| self.do_run(src, 'success', force_c=True) |
| |
| @needs_dlfcn |
| def test_dlfcn_stacks(self): |
| self.prep_dlfcn_lib() |
| lib_src = ''' |
| #include <assert.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| int myfunc(const char *input) { |
| char bigstack[1024] = { 0 }; |
| |
| // make sure we didn't just trample the stack! |
| assert(!strcmp(input, "foobar")); |
| |
| snprintf(bigstack, sizeof(bigstack), input); |
| return strlen(bigstack); |
| } |
| ''' |
| self.set_setting('EXPORTED_FUNCTIONS', ['_myfunc']) |
| dirname = self.get_dir() |
| filename = os.path.join(dirname, 'liblib.c') |
| self.build_dlfcn_lib(lib_src, dirname, filename) |
| |
| self.prep_dlfcn_main() |
| src = ''' |
| #include <assert.h> |
| #include <stdio.h> |
| #include <dlfcn.h> |
| #include <string.h> |
| |
| typedef int (*FUNCTYPE)(const char *); |
| |
| int main() { |
| void *lib_handle; |
| FUNCTYPE func_ptr; |
| char str[128]; |
| |
| snprintf(str, sizeof(str), "foobar"); |
| |
| // HACK: Use strcmp in the main executable so that it doesn't get optimized out and the dynamic library |
| // is able to use it. |
| assert(!strcmp(str, "foobar")); |
| |
| lib_handle = dlopen("liblib.so", RTLD_NOW); |
| assert(lib_handle != NULL); |
| |
| func_ptr = (FUNCTYPE)dlsym(lib_handle, "myfunc"); |
| assert(func_ptr != NULL); |
| assert(func_ptr(str) == 6); |
| |
| puts("success"); |
| |
| return 0; |
| } |
| ''' |
| self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_malloc', '_strcmp']) |
| self.do_run(src, 'success', force_c=True) |
| |
| @needs_dlfcn |
| def test_dlfcn_funcs(self): |
| self.prep_dlfcn_lib() |
| lib_src = r''' |
| #include <assert.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| typedef void (*voidfunc)(); |
| typedef void (*intfunc)(int); |
| |
| void callvoid(voidfunc f) { f(); } |
| void callint(voidfunc f, int x) { f(x); } |
| |
| void void_0() { printf("void 0\n"); } |
| void void_1() { printf("void 1\n"); } |
| voidfunc getvoid(int i) { |
| switch(i) { |
| case 0: return void_0; |
| case 1: return void_1; |
| default: return NULL; |
| } |
| } |
| |
| void int_0(int x) { printf("int 0 %d\n", x); } |
| void int_1(int x) { printf("int 1 %d\n", x); } |
| intfunc getint(int i) { |
| switch(i) { |
| case 0: return int_0; |
| case 1: return int_1; |
| default: return NULL; |
| } |
| } |
| ''' |
| self.set_setting('EXPORTED_FUNCTIONS', ['_callvoid', '_callint', '_getvoid', '_getint']) |
| dirname = self.get_dir() |
| self.build_dlfcn_lib(lib_src, dirname, os.path.join(dirname, 'liblib.c')) |
| |
| self.prep_dlfcn_main() |
| src = r''' |
| #include <assert.h> |
| #include <stdio.h> |
| #include <dlfcn.h> |
| |
| typedef void (*voidfunc)(); |
| typedef void (*intfunc)(int); |
| |
| typedef void (*voidcaller)(voidfunc); |
| typedef void (*intcaller)(intfunc, int); |
| |
| typedef voidfunc (*voidgetter)(int); |
| typedef intfunc (*intgetter)(int); |
| |
| void void_main() { printf("void_main.\n"); } |
| void int_main(int x) { printf("int_main %d\n", x); } |
| |
| int main() { |
| printf("go\n"); |
| void *lib_handle; |
| lib_handle = dlopen("liblib.so", RTLD_NOW); |
| assert(lib_handle != NULL); |
| |
| voidcaller callvoid = (voidcaller)dlsym(lib_handle, "callvoid"); |
| assert(callvoid != NULL); |
| callvoid(void_main); |
| |
| intcaller callint = (intcaller)dlsym(lib_handle, "callint"); |
| assert(callint != NULL); |
| callint(int_main, 201); |
| |
| voidgetter getvoid = (voidgetter)dlsym(lib_handle, "getvoid"); |
| assert(getvoid != NULL); |
| callvoid(getvoid(0)); |
| callvoid(getvoid(1)); |
| |
| intgetter getint = (intgetter)dlsym(lib_handle, "getint"); |
| assert(getint != NULL); |
| callint(getint(0), 54); |
| callint(getint(1), 9000); |
| |
| assert(getint(1000) == NULL); |
| |
| puts("ok"); |
| return 0; |
| } |
| ''' |
| self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_malloc']) |
| self.do_run(src, '''go |
| void_main. |
| int_main 201 |
| void 0 |
| void 1 |
| int 0 54 |
| int 1 9000 |
| ok |
| ''', force_c=True) |
| |
| @needs_dlfcn |
| def test_dlfcn_mallocs(self): |
| # will be exhausted without functional malloc/free |
| self.set_setting('INITIAL_MEMORY', 64 * 1024 * 1024) |
| |
| self.prep_dlfcn_lib() |
| lib_src = r''' |
| #include <assert.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| |
| void *mallocproxy(int n) { return malloc(n); } |
| void freeproxy(void *p) { free(p); } |
| ''' |
| self.set_setting('EXPORTED_FUNCTIONS', ['_mallocproxy', '_freeproxy']) |
| dirname = self.get_dir() |
| filename = os.path.join(dirname, 'liblib.c') |
| self.build_dlfcn_lib(lib_src, dirname, filename) |
| |
| self.prep_dlfcn_main() |
| src = open(path_from_root('tests', 'dlmalloc_proxy.c')).read() |
| self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_malloc', '_free']) |
| self.do_run(src, '''*294,153*''', force_c=True) |
| |
| @needs_dlfcn |
| def test_dlfcn_longjmp(self): |
| self.prep_dlfcn_lib() |
| lib_src = r''' |
| #include <setjmp.h> |
| #include <stdio.h> |
| |
| void jumpy(jmp_buf buf) { |
| static int i = 0; |
| i++; |
| if (i == 10) longjmp(buf, i); |
| printf("pre %d\n", i); |
| } |
| ''' |
| self.set_setting('EXPORTED_FUNCTIONS', ['_jumpy']) |
| dirname = self.get_dir() |
| filename = os.path.join(dirname, 'liblib.c') |
| self.build_dlfcn_lib(lib_src, dirname, filename) |
| |
| self.prep_dlfcn_main() |
| src = r''' |
| #include <assert.h> |
| #include <stdio.h> |
| #include <dlfcn.h> |
| #include <setjmp.h> |
| |
| typedef void (*jumpfunc)(jmp_buf); |
| |
| int main() { |
| printf("go!\n"); |
| |
| void *lib_handle; |
| lib_handle = dlopen("liblib.so", RTLD_NOW); |
| assert(lib_handle != NULL); |
| |
| jumpfunc jumpy = (jumpfunc)dlsym(lib_handle, "jumpy"); |
| assert(jumpy); |
| |
| jmp_buf buf; |
| int jmpval = setjmp(buf); |
| if (jmpval == 0) { |
| while (1) jumpy(buf); |
| } else { |
| printf("out!\n"); |
| } |
| |
| return 0; |
| } |
| ''' |
| self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_malloc', '_free']) |
| self.do_run(src, '''go! |
| pre 1 |
| pre 2 |
| pre 3 |
| pre 4 |
| pre 5 |
| pre 6 |
| pre 7 |
| pre 8 |
| pre 9 |
| out! |
| ''', force_c=True) |
| |
| # TODO: make this work. need to forward tempRet0 across modules |
| # TODO Enable @with_both_exception_handling (the test is not working now) |
| @needs_dlfcn |
| def zzztest_dlfcn_exceptions(self): |
| self.set_setting('DISABLE_EXCEPTION_CATCHING', 0) |
| |
| self.prep_dlfcn_lib() |
| lib_src = r''' |
| extern "C" { |
| int ok() { |
| return 65; |
| } |
| int fail() { |
| throw 123; |
| } |
| } |
| ''' |
| self.set_setting('EXPORTED_FUNCTIONS', ['_ok', '_fail']) |
| dirname = self.get_dir() |
| filename = os.path.join(dirname, 'liblib.cpp') |
| self.build_dlfcn_lib(lib_src, dirname, filename) |
| |
| self.prep_dlfcn_main() |
| src = r''' |
| #include <assert.h> |
| #include <stdio.h> |
| #include <dlfcn.h> |
| |
| typedef int (*intfunc)(); |
| |
| int main() { |
| printf("go!\n"); |
| |
| void *lib_handle; |
| lib_handle = dlopen("liblib.so", RTLD_NOW); |
| assert(lib_handle != NULL); |
| |
| intfunc okk = (intfunc)dlsym(lib_handle, "ok"); |
| intfunc faill = (intfunc)dlsym(lib_handle, "fail"); |
| assert(okk && faill); |
| |
| try { |
| printf("ok: %d\n", okk()); |
| } catch(...) { |
| printf("wha\n"); |
| } |
| |
| try { |
| printf("fail: %d\n", faill()); |
| } catch(int x) { |
| printf("int %d\n", x); |
| } |
| |
| try { |
| printf("fail: %d\n", faill()); |
| } catch(double x) { |
| printf("caught %f\n", x); |
| } |
| |
| return 0; |
| } |
| ''' |
| self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_malloc', '_free']) |
| self.do_run(src, '''go! |
| ok: 65 |
| int 123 |
| ok |
| ''') |
| |
| @needs_dlfcn |
| def test_dlfcn_handle_alloc(self): |
| # verify that dlopen does not allocate already used handles |
| dirname = self.get_dir() |
| |
| def indir(name): |
| return os.path.join(dirname, name) |
| |
| libecho = r''' |
| #include <stdio.h> |
| |
| static struct %(libname)s { |
| %(libname)s() { |
| puts("%(libname)s: loaded"); |
| } |
| } _; |
| ''' |
| |
| self.prep_dlfcn_lib() |
| self.build_dlfcn_lib(libecho % {'libname': 'a'}, dirname, indir('a.cpp')) |
| shutil.move(indir('liblib.so'), indir('liba.so')) |
| self.build_dlfcn_lib(libecho % {'libname': 'b'}, dirname, indir('b.cpp')) |
| shutil.move(indir('liblib.so'), indir('libb.so')) |
| |
| self.set_setting('MAIN_MODULE') |
| self.clear_setting('SIDE_MODULE') |
| self.set_setting('EXPORT_ALL') |
| self.emcc_args += ['--embed-file', '.@/'] |
| |
| # XXX in wasm each lib load currently takes 5MB; default INITIAL_MEMORY=16MB is thus not enough |
| self.set_setting('INITIAL_MEMORY', 32 * 1024 * 1024) |
| |
| src = r''' |
| #include <dlfcn.h> |
| #include <assert.h> |
| #include <stddef.h> |
| |
| int main() { |
| void *liba, *libb, *liba2; |
| int err; |
| |
| liba = dlopen("liba.so", RTLD_NOW); |
| assert(liba != NULL); |
| libb = dlopen("libb.so", RTLD_NOW); |
| assert(liba != NULL); |
| |
| err = dlclose(liba); |
| assert(!err); |
| |
| liba2 = dlopen("liba.so", RTLD_NOW); |
| assert(liba2 != libb); |
| |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'a: loaded\nb: loaded\na: loaded\n') |
| |
| @needs_dlfcn |
| @bleeding_edge_wasm_backend |
| def test_dlfcn_feature_in_lib(self): |
| self.emcc_args.append('-mnontrapping-fptoint') |
| |
| self.prep_dlfcn_lib() |
| lib_src = r''' |
| extern "C" int magic(float x) { |
| return __builtin_wasm_trunc_saturate_s_i32_f32(x); |
| } |
| ''' |
| dirname = self.get_dir() |
| filename = os.path.join(dirname, 'liblib.cpp') |
| self.build_dlfcn_lib(lib_src, dirname, filename) |
| |
| self.prep_dlfcn_main() |
| src = r''' |
| #include <dlfcn.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| typedef int (*fi)(float); |
| |
| int main() { |
| void *lib_handle = dlopen("liblib.so", 0); |
| if (!lib_handle) { |
| puts(dlerror()); |
| abort(); |
| } |
| fi x = (fi)dlsym(lib_handle, "magic"); |
| if (!x) { |
| puts(dlerror()); |
| abort(); |
| } |
| printf("float: %d.\n", x(42.99)); |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'float: 42.\n') |
| |
| def dylink_test(self, main, side, expected=None, header=None, main_emcc_args=[], force_c=False, need_reverse=True, auto_load=True, **kwargs): |
| if header: |
| create_test_file('header.h', header) |
| |
| old_args = self.emcc_args[:] |
| |
| # side settings |
| self.clear_setting('MAIN_MODULE') |
| self.set_setting('SIDE_MODULE') |
| side_suffix = 'wasm' if self.is_wasm() else 'js' |
| if isinstance(side, list): |
| # side is just a library |
| try_delete('liblib.cpp.o.' + side_suffix) |
| run_process([EMCC] + side + self.get_emcc_args() + ['-o', os.path.join(self.get_dir(), 'liblib.cpp.o.' + side_suffix)]) |
| else: |
| base = 'liblib.cpp' if not force_c else 'liblib.c' |
| try_delete(base + '.o.' + side_suffix) |
| self.build(side, self.get_dir(), base, js_outfile=(side_suffix == 'js')) |
| if force_c: |
| shutil.move(base + '.o.' + side_suffix, 'liblib.cpp.o.' + side_suffix) |
| if SPIDERMONKEY_ENGINE and os.path.exists(SPIDERMONKEY_ENGINE[0]) and not self.is_wasm(): |
| out = run_js('liblib.cpp.o.js', engine=SPIDERMONKEY_ENGINE, full_output=True, stderr=STDOUT) |
| if 'asm' in out: |
| self.validate_asmjs(out) |
| shutil.move('liblib.cpp.o.' + side_suffix, 'liblib.so') |
| |
| # main settings |
| self.set_setting('MAIN_MODULE') |
| self.clear_setting('SIDE_MODULE') |
| if auto_load: |
| self.set_setting('RUNTIME_LINKED_LIBS', ['liblib.so']) |
| self.emcc_args += main_emcc_args |
| |
| if isinstance(main, list): |
| # main is just a library |
| try_delete('src.cpp.o.js') |
| run_process([EMCC] + main + self.emcc_args + self.serialize_settings() + ['-o', 'src.cpp.o.js']) |
| self.do_run(None, expected, no_build=True, **kwargs) |
| else: |
| self.do_run(main, expected, force_c=force_c, **kwargs) |
| |
| self.emcc_args = old_args |
| |
| if need_reverse: |
| # test the reverse as well |
| print('flip') |
| self.dylink_test(side, main, expected, header, main_emcc_args + ['--no-entry'], force_c, need_reverse=False, **kwargs) |
| |
| def do_basic_dylink_test(self, need_reverse=True): |
| self.dylink_test(r''' |
| #include <stdio.h> |
| #include "header.h" |
| |
| int main() { |
| printf("other says %d.\n", sidey()); |
| return 0; |
| } |
| ''', ''' |
| #include "header.h" |
| |
| int sidey() { |
| return 11; |
| } |
| ''', 'other says 11.', 'extern "C" int sidey();', need_reverse=need_reverse) |
| |
| @needs_dlfcn |
| def test_dylink_basics(self): |
| self.do_basic_dylink_test() |
| |
| @needs_dlfcn |
| def test_dylink_no_export(self): |
| self.set_setting('NO_DECLARE_ASM_MODULE_EXPORTS') |
| self.do_basic_dylink_test() |
| |
| @needs_dlfcn |
| def test_dylink_memory_growth(self): |
| if not self.is_wasm(): |
| self.skipTest('wasm only') |
| self.set_setting('ALLOW_MEMORY_GROWTH', 1) |
| self.do_basic_dylink_test() |
| |
| @needs_dlfcn |
| def test_dylink_safe_heap(self): |
| self.set_setting('SAFE_HEAP', 1) |
| self.do_basic_dylink_test() |
| |
| @needs_dlfcn |
| def test_dylink_function_pointer_equality(self): |
| self.dylink_test(r''' |
| #include <stdio.h> |
| #include "header.h" |
| |
| int main() { |
| void* puts_side = get_address(); |
| printf("main module address %p.\n", &puts); |
| printf("side module address address %p.\n", puts_side); |
| if (&puts == puts_side) |
| printf("success\n"); |
| else |
| printf("failure\n"); |
| return 0; |
| } |
| ''', ''' |
| #include <stdio.h> |
| #include "header.h" |
| |
| void* get_address() { |
| return (void*)&puts; |
| } |
| ''', 'success', header='extern "C" void* get_address();') |
| |
| @needs_dlfcn |
| def test_dylink_floats(self): |
| self.dylink_test(r''' |
| #include <stdio.h> |
| extern float sidey(); |
| int main() { |
| printf("other says %.2f.\n", sidey()+1); |
| return 0; |
| } |
| ''', ''' |
| float sidey() { return 11.5; } |
| ''', 'other says 12.50') |
| |
| @needs_dlfcn |
| def test_dylink_printfs(self): |
| self.dylink_test(r''' |
| #include <stdio.h> |
| extern "C" void sidey(); |
| int main() { |
| printf("hello from main\n"); |
| sidey(); |
| return 0; |
| } |
| ''', r''' |
| #include <stdio.h> |
| extern "C" void sidey() { |
| printf("hello from side\n"); |
| } |
| ''', 'hello from main\nhello from side\n') |
| |
| # Verify that a function pointer can be passed back and forth and invoked |
| # on both sides. |
| @needs_dlfcn |
| def test_dylink_funcpointer(self): |
| self.dylink_test( |
| main=r''' |
| #include <stdio.h> |
| #include <assert.h> |
| #include "header.h" |
| intfunc sidey(intfunc f); |
| void a(int arg) { printf("hello from funcptr: %d\n", arg); } |
| int main() { |
| intfunc b = sidey(a); |
| assert(a == b); |
| b(0); |
| return 0; |
| } |
| ''', |
| side=''' |
| #include "header.h" |
| intfunc sidey(intfunc f) { f(1); return f; } |
| ''', |
| expected='hello from funcptr: 1\nhello from funcptr: 0\n', |
| header='typedef void (*intfunc)(int );') |
| |
| @needs_dlfcn |
| # test dynamic linking of a module with multiple function pointers, stored |
| # statically |
| def test_dylink_static_funcpointers(self): |
| self.dylink_test( |
| main=r''' |
| #include <stdio.h> |
| #include "header.h" |
| void areturn0() { printf("hello 0\n"); } |
| void areturn1() { printf("hello 1\n"); } |
| void areturn2() { printf("hello 2\n"); } |
| voidfunc func_ptrs[3] = { areturn0, areturn1, areturn2 }; |
| int main(int argc, char **argv) { |
| sidey(func_ptrs[0]); |
| sidey(func_ptrs[1]); |
| sidey(func_ptrs[2]); |
| return 0; |
| } |
| ''', |
| side=''' |
| #include "header.h" |
| void sidey(voidfunc f) { f(); } |
| ''', |
| expected='hello 0\nhello 1\nhello 2\n', |
| header='typedef void (*voidfunc)(); void sidey(voidfunc f);') |
| |
| @no_wasm('uses function tables in an asm.js specific way') |
| @needs_dlfcn |
| def test_dylink_asmjs_funcpointers(self): |
| self.dylink_test( |
| main=r''' |
| #include "header.h" |
| #include <emscripten.h> |
| void left1() { printf("left1\n"); } |
| void left2() { printf("left2\n"); } |
| voidfunc getleft1() { return left1; } |
| voidfunc getleft2() { return left2; } |
| int main(int argc, char **argv) { |
| printf("main\n"); |
| EM_ASM({ |
| // make the function table sizes a non-power-of-two |
| var newSize = alignFunctionTables(); |
| //out('old size of function tables: ' + newSize); |
| while ((newSize & 3) !== 3) { |
| Module['FUNCTION_TABLE_v'].push(0); |
| newSize = alignFunctionTables(); |
| } |
| //out('new size of function tables: ' + newSize); |
| // when masked, the two function pointers 1 and 2 should not happen to fall back to the right place |
| assert(((newSize+1) & 3) !== 1 || ((newSize+2) & 3) !== 2); |
| loadDynamicLibrary('liblib.so'); |
| }); |
| volatilevoidfunc f; |
| f = (volatilevoidfunc)left1; |
| f(); |
| f = (volatilevoidfunc)left2; |
| f(); |
| f = (volatilevoidfunc)getright1(); |
| f(); |
| f = (volatilevoidfunc)getright2(); |
| f(); |
| second(); |
| return 0; |
| } |
| ''', |
| side=r''' |
| #include "header.h" |
| void right1() { printf("right1\n"); } |
| void right2() { printf("right2\n"); } |
| voidfunc getright1() { return right1; } |
| voidfunc getright2() { return right2; } |
| void second() { |
| printf("second\n"); |
| volatilevoidfunc f; |
| f = (volatilevoidfunc)getleft1(); |
| f(); |
| f = (volatilevoidfunc)getleft2(); |
| f(); |
| f = (volatilevoidfunc)right1; |
| f(); |
| f = (volatilevoidfunc)right2; |
| f(); |
| } |
| ''', |
| expected='main\nleft1\nleft2\nright1\nright2\nsecond\nleft1\nleft2\nright1\nright2\n', |
| header=''' |
| #include <stdio.h> |
| typedef void (*voidfunc)(); |
| typedef volatile voidfunc volatilevoidfunc; |
| voidfunc getleft1(); |
| voidfunc getleft2(); |
| voidfunc getright1(); |
| voidfunc getright2(); |
| void second(); |
| ''', need_reverse=False, auto_load=False) |
| |
| @needs_dlfcn |
| def test_dylink_funcpointers_wrapper(self): |
| self.dylink_test( |
| main=r'''\ |
| #include <stdio.h> |
| #include "header.h" |
| int main(int argc, char **argv) { |
| charfunc f1 = emscripten_run_script; |
| f1("out('one')"); |
| charfunc f2 = get(); |
| f2("out('two')"); |
| return 0; |
| } |
| ''', |
| side='''\ |
| #include "header.h" |
| charfunc get() { |
| return emscripten_run_script; |
| } |
| ''', |
| expected='one\ntwo\n', |
| header='''\ |
| #include <emscripten.h> |
| typedef void (*charfunc)(const char*); |
| extern charfunc get(); |
| ''') |
| |
| @needs_dlfcn |
| def test_dylink_static_funcpointer_float(self): |
| self.dylink_test( |
| main=r'''\ |
| #include <stdio.h> |
| #include "header.h" |
| int sidey(floatfunc f); |
| float func1(float f) { printf("hello 1: %f\n", f); return 0; } |
| floatfunc f1 = &func1; |
| int main(int argc, char **argv) { |
| printf("got: %d\n", sidey(f1)); |
| f1(12.34); |
| return 0; |
| } |
| ''', |
| side='''\ |
| #include "header.h" |
| int sidey(floatfunc f) { f(56.78); return 1; } |
| ''', |
| expected='hello 1: 56.779999\ngot: 1\nhello 1: 12.340000\n', |
| header='typedef float (*floatfunc)(float);') |
| |
| @needs_dlfcn |
| def test_dylink_global_init(self): |
| self.dylink_test(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') |
| |
| @needs_dlfcn |
| def test_dylink_global_inits(self): |
| def test(): |
| self.dylink_test(header=r''' |
| #include <stdio.h> |
| struct Class { |
| Class(const char *name) { printf("new %s\n", name); } |
| }; |
| ''', main=r''' |
| #include "header.h" |
| static Class c("main"); |
| int main() { |
| return 0; |
| } |
| ''', side=r''' |
| #include "header.h" |
| static Class c("side"); |
| ''', expected=['new main\nnew side\n', 'new side\nnew main\n']) |
| test() |
| |
| # TODO: this in wasm |
| if self.get_setting('ASSERTIONS') == 1 and not self.is_wasm(): |
| print('check warnings') |
| self.set_setting('ASSERTIONS', 2) |
| test() |
| full = run_js('src.cpp.o.js', engine=JS_ENGINES[0], full_output=True, stderr=STDOUT) |
| self.assertNotContained("trying to dynamically load symbol '__ZN5ClassC2EPKc' (from 'liblib.so') that already exists", full) |
| |
| @needs_dlfcn |
| def test_dylink_i64(self): |
| self.dylink_test(r''' |
| #include <stdio.h> |
| #include <stdint.h> |
| extern int64_t sidey(); |
| int main() { |
| printf("other says %llx.\n", sidey()); |
| return 0; |
| } |
| ''', ''' |
| #include <stdint.h> |
| int64_t sidey() { |
| volatile int64_t x = 11; |
| x = x * x * x * x; |
| x += x % 17; |
| x += (x * (1 << 30)); |
| x -= 96; |
| x = (x + 1000) / ((x % 5) + 1); |
| volatile uint64_t y = x / 2; |
| x = y / 3; |
| y = y * y * y * y; |
| y += y % 17; |
| y += (y * (1 << 30)); |
| y -= 121; |
| y = (y + 1000) / ((y % 5) + 1); |
| x += y; |
| return x; |
| } |
| ''', 'other says 175a1ddee82b8c31.') |
| |
| @all_engines |
| @needs_dlfcn |
| def test_dylink_i64_b(self): |
| self.dylink_test(r''' |
| #include <stdio.h> |
| #include <stdint.h> |
| extern int64_t sidey(); |
| int64_t testAdd(int64_t a) { |
| return a + 1; |
| } |
| int64_t testAddB(int a) { |
| return a + 1; |
| } |
| typedef int64_t (*testAddHandler)(int64_t); |
| testAddHandler h = &testAdd; |
| typedef int64_t (*testAddBHandler)(int); |
| testAddBHandler hb = &testAddB; |
| int main() { |
| printf("other says %lld.\n", sidey()); |
| int64_t r = h(42); |
| printf("my fp says: %lld.\n", r); |
| int64_t rb = hb(42); |
| printf("my second fp says: %lld.\n", r); |
| } |
| ''', ''' |
| #include <stdint.h> |
| int64_t sidey() { |
| volatile int64_t x = 0x12345678abcdef12LL; |
| x += x % 17; |
| x = 18 - x; |
| return x; |
| } |
| ''', 'other says -1311768467750121224.\nmy fp says: 43.\nmy second fp says: 43.') |
| |
| @needs_dlfcn |
| @also_with_wasm_bigint |
| def test_dylink_i64_c(self): |
| self.dylink_test(r''' |
| #include <cstdio> |
| #include <cinttypes> |
| #include "header.h" |
| |
| typedef int32_t (*fp_type_32)(int32_t, int32_t, int32_t); |
| typedef int64_t (*fp_type_64)(int32_t, int32_t, int32_t); |
| |
| int32_t internal_function_ret_32(int32_t i, int32_t j, int32_t k) { |
| return 32; |
| } |
| int64_t internal_function_ret_64(int32_t i, int32_t j, int32_t k) { |
| return 64; |
| } |
| |
| int main() { |
| fp_type_32 fp32_internal = &internal_function_ret_32; |
| fp_type_32 fp32_external = &function_ret_32; |
| fp_type_64 fp64_external = &function_ret_64; |
| fp_type_64 fp64_internal = &internal_function_ret_64; |
| int32_t ires32 = fp32_internal(0,0,0); |
| printf("res32 - internal %d\n",ires32); |
| int32_t eres32 = fp32_external(0,0,0); |
| printf("res32 - external %d\n",eres32); |
| |
| int64_t ires64 = fp64_internal(0,0,0); |
| printf("res64 - internal %" PRId64 "\n",ires64); |
| int64_t eres64 = fp64_external(0,0,0); |
| printf("res64 - external %" PRId64 "\n",eres64); |
| return 0; |
| } |
| ''', ''' |
| #include "header.h" |
| int32_t function_ret_32(int32_t i, int32_t j, int32_t k) { |
| return 32; |
| } |
| int64_t function_ret_64(int32_t i, int32_t j, int32_t k) { |
| return 64; |
| } |
| ''', '''res32 - internal 32 |
| res32 - external 32 |
| res64 - internal 64 |
| res64 - external 64\n''', header=''' |
| #include <emscripten.h> |
| #include <cstdint> |
| EMSCRIPTEN_KEEPALIVE int32_t function_ret_32(int32_t i, int32_t j, int32_t k); |
| EMSCRIPTEN_KEEPALIVE int64_t function_ret_64(int32_t i, int32_t j, int32_t k); |
| ''') |
| |
| @needs_dlfcn |
| def test_dylink_class(self): |
| self.dylink_test(header=r''' |
| #include <stdio.h> |
| struct Class { |
| Class(const char *name); |
| }; |
| ''', main=r''' |
| #include "header.h" |
| int main() { |
| Class c("main"); |
| return 0; |
| } |
| ''', side=r''' |
| #include "header.h" |
| Class::Class(const char *name) { printf("new %s\n", name); } |
| ''', expected=['new main\n']) |
| |
| @needs_dlfcn |
| def test_dylink_global_var(self): |
| self.dylink_test(main=r''' |
| #include <stdio.h> |
| extern int x; |
| int main() { |
| printf("extern is %d.\n", x); |
| return 0; |
| } |
| ''', side=r''' |
| int x = 123; |
| ''', expected=['extern is 123.\n']) |
| |
| @needs_dlfcn |
| def test_dylink_global_var_modded(self): |
| self.dylink_test(main=r''' |
| #include <stdio.h> |
| extern int x; |
| int main() { |
| printf("extern is %d.\n", x); |
| return 0; |
| } |
| ''', side=r''' |
| int x = 123; |
| struct Initter { |
| Initter() { x = 456; } |
| }; |
| Initter initter; |
| ''', expected=['extern is 456.\n']) |
| |
| @needs_dlfcn |
| def test_dylink_stdlib(self): |
| self.dylink_test(header=r''' |
| #include <math.h> |
| #include <stdlib.h> |
| #include <string.h> |
| char *side(const char *data); |
| double pow_two(double x); |
| ''', main=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); |
| printf("pow_two: %d.\n", int(pow_two(5.9))); |
| return 0; |
| } |
| ''', side=r''' |
| #include "header.h" |
| char *side(const char *data) { |
| char *ret = (char*)malloc(strlen(data)+1); |
| strcpy(ret, data); |
| return ret; |
| } |
| double pow_two(double x) { |
| return pow(2, x); |
| } |
| ''', expected=['hello through side\n\npow_two: 59.']) |
| |
| @needs_dlfcn |
| def test_dylink_jslib(self): |
| create_test_file('lib.js', r''' |
| mergeInto(LibraryManager.library, { |
| test_lib_func: function(x) { |
| return x + 17.2; |
| } |
| }); |
| ''') |
| self.dylink_test(header=r''' |
| extern "C" { extern double test_lib_func(int input); } |
| ''', main=r''' |
| #include <stdio.h> |
| #include "header.h" |
| extern double sidey(); |
| int main2() { return 11; } |
| int main() { |
| int input = sidey(); |
| double temp = test_lib_func(input); |
| printf("other says %.2f\n", temp); |
| printf("more: %.5f, %d\n", temp, input); |
| return 0; |
| } |
| ''', side=r''' |
| #include <stdio.h> |
| #include "header.h" |
| extern int main2(); |
| double sidey() { |
| int temp = main2(); |
| printf("main2 sed: %d\n", temp); |
| printf("main2 sed: %u, %c\n", temp, temp/2); |
| return test_lib_func(temp); |
| } |
| ''', expected='other says 45.2', main_emcc_args=['--js-library', 'lib.js']) |
| |
| @needs_dlfcn |
| def test_dylink_global_var_jslib(self): |
| create_test_file('lib.js', r''' |
| mergeInto(LibraryManager.library, { |
| jslib_x: '{{{ makeStaticAlloc(4) }}}', |
| jslib_x__postset: 'HEAP32[_jslib_x>>2] = 148;', |
| }); |
| ''') |
| self.dylink_test(main=r''' |
| #include <stdio.h> |
| extern "C" int jslib_x; |
| extern void call_side(); |
| int main() { |
| printf("main: jslib_x is %d.\n", jslib_x); |
| call_side(); |
| return 0; |
| } |
| ''', side=r''' |
| #include <stdio.h> |
| extern "C" int jslib_x; |
| void call_side() { |
| printf("side: jslib_x is %d.\n", jslib_x); |
| } |
| ''', expected=['main: jslib_x is 148.\nside: jslib_x is 148.\n'], main_emcc_args=['--js-library', 'lib.js', '-s', 'EXPORTED_FUNCTIONS=["_main", "_jslib_x"]']) |
| |
| @needs_dlfcn |
| def test_dylink_many_postsets(self): |
| NUM = 1234 |
| self.dylink_test(header=r''' |
| #include <stdio.h> |
| typedef void (*voidfunc)(); |
| static void simple() { |
| printf("simple.\n"); |
| } |
| static volatile voidfunc funcs[''' + str(NUM) + '] = { ' + ','.join(['simple'] * NUM) + r''' }; |
| static void test() { |
| volatile int i = ''' + str(NUM - 1) + r'''; |
| funcs[i](); |
| i = 0; |
| funcs[i](); |
| } |
| extern void more(); |
| ''', main=r''' |
| #include "header.h" |
| int main() { |
| test(); |
| more(); |
| return 0; |
| } |
| ''', side=r''' |
| #include "header.h" |
| void more() { |
| test(); |
| } |
| ''', expected=['simple.\nsimple.\nsimple.\nsimple.\n']) |
| |
| @needs_dlfcn |
| def test_dylink_postsets_chunking(self): |
| self.dylink_test(header=r''' |
| extern int global_var; |
| ''', main=r''' |
| #include <stdio.h> |
| #include "header.h" |
| |
| // prepare 99 global variable with local initializer |
| static int p = 1; |
| #define P(x) __attribute__((used)) int *padding##x = &p; |
| P(01) P(02) P(03) P(04) P(05) P(06) P(07) P(08) P(09) P(10) |
| P(11) P(12) P(13) P(14) P(15) P(16) P(17) P(18) P(19) P(20) |
| P(21) P(22) P(23) P(24) P(25) P(26) P(27) P(28) P(29) P(30) |
| P(31) P(32) P(33) P(34) P(35) P(36) P(37) P(38) P(39) P(40) |
| P(41) P(42) P(43) P(44) P(45) P(46) P(47) P(48) P(49) P(50) |
| P(51) P(52) P(53) P(54) P(55) P(56) P(57) P(58) P(59) P(60) |
| P(61) P(62) P(63) P(64) P(65) P(66) P(67) P(68) P(69) P(70) |
| P(71) P(72) P(73) P(74) P(75) P(76) P(77) P(78) P(79) P(80) |
| P(81) P(82) P(83) P(84) P(85) P(86) P(87) P(88) P(89) P(90) |
| P(91) P(92) P(93) P(94) P(95) P(96) P(97) P(98) P(99) |
| |
| // prepare global variable with global initializer |
| int *ptr = &global_var; |
| |
| int main(int argc, char *argv[]) { |
| printf("%d\n", *ptr); |
| } |
| ''', side=r''' |
| #include "header.h" |
| |
| int global_var = 12345; |
| ''', expected=['12345\n']) |
| |
| @needs_dlfcn |
| def test_dylink_syslibs(self): # one module uses libcxx, need to force its inclusion when it isn't the main |
| # https://github.com/emscripten-core/emscripten/issues/10571 |
| return self.skipTest('Currently not working due to duplicate symbol errors in wasm-ld') |
| |
| def test(syslibs, expect_pass=True, need_reverse=True): |
| print('syslibs', syslibs, self.get_setting('ASSERTIONS')) |
| passed = True |
| try: |
| with env_modify({'EMCC_FORCE_STDLIBS': syslibs}): |
| self.dylink_test(main=r''' |
| void side(); |
| int main() { |
| side(); |
| return 0; |
| } |
| ''', side=r''' |
| #include <iostream> |
| void side() { std::cout << "cout hello from side\n"; } |
| ''', expected=['cout hello from side\n'], need_reverse=need_reverse, assert_returncode=None) |
| except Exception as e: |
| if expect_pass: |
| raise |
| print('(seeing expected fail)') |
| passed = False |
| assertion = 'build the MAIN_MODULE with EMCC_FORCE_STDLIBS=1 in the environment' |
| if self.get_setting('ASSERTIONS'): |
| self.assertContained(assertion, str(e)) |
| else: |
| self.assertNotContained(assertion, str(e)) |
| assert passed == expect_pass, ['saw', passed, 'but expected', expect_pass] |
| |
| test('libc++') |
| test('1') |
| if not self.has_changed_setting('ASSERTIONS'): |
| self.set_setting('ASSERTIONS', 0) |
| test('', expect_pass=False, need_reverse=False) |
| self.set_setting('ASSERTIONS', 1) |
| test('', expect_pass=False, need_reverse=False) |
| |
| @needs_dlfcn |
| @with_env_modify({'EMCC_FORCE_STDLIBS': 'libc++'}) |
| def test_dylink_iostream(self): |
| self.dylink_test(header=r''' |
| #include <iostream> |
| #include <string> |
| std::string side(); |
| ''', main=r''' |
| #include "header.h" |
| int main() { |
| std::cout << "hello from main " << side() << std::endl; |
| return 0; |
| } |
| ''', side=r''' |
| #include "header.h" |
| std::string side() { return "and hello from side"; } |
| ''', expected=['hello from main and hello from side\n']) |
| |
| @needs_dlfcn |
| def test_dylink_dynamic_cast(self): # issue 3465 |
| self.dylink_test(header=r''' |
| class Base { |
| public: |
| virtual void printName(); |
| }; |
| |
| class Derived : public Base { |
| public: |
| void printName(); |
| }; |
| ''', main=r''' |
| #include "header.h" |
| #include <iostream> |
| |
| using namespace std; |
| |
| int main() { |
| cout << "starting main" << endl; |
| |
| Base *base = new Base(); |
| Base *derived = new Derived(); |
| base->printName(); |
| derived->printName(); |
| |
| if (dynamic_cast<Derived*>(derived)) { |
| cout << "OK" << endl; |
| } else { |
| cout << "KO" << endl; |
| } |
| |
| return 0; |
| } |
| ''', side=r''' |
| #include "header.h" |
| #include <iostream> |
| |
| using namespace std; |
| |
| void Base::printName() { |
| cout << "Base" << endl; |
| } |
| |
| void Derived::printName() { |
| cout << "Derived" << endl; |
| } |
| ''', expected=['starting main\nBase\nDerived\nOK']) |
| |
| @needs_dlfcn |
| @with_both_exception_handling |
| def test_dylink_raii_exceptions(self): |
| self.dylink_test(main=r''' |
| #include <stdio.h> |
| extern int side(); |
| int main() { |
| printf("from side: %d.\n", side()); |
| } |
| ''', side=r''' |
| #include <stdio.h> |
| typedef int (*ifdi)(float, double, int); |
| int func_with_special_sig(float a, double b, int c) { |
| printf("special %f %f %d\n", a, b, c); |
| return 1337; |
| } |
| struct DestructorCaller { |
| ~DestructorCaller() { printf("destroy\n"); } |
| }; |
| int side() { |
| // d has a destructor that must be called on function |
| // exit, which means an invoke will be used for the |
| // indirect call here - and the signature of that call |
| // is special and not present in the main module, so |
| // it must be generated for the side module. |
| DestructorCaller d; |
| volatile ifdi p = func_with_special_sig; |
| return p(2.18281, 3.14159, 42); |
| } |
| ''', expected=['special 2.182810 3.141590 42\ndestroy\nfrom side: 1337.\n']) |
| |
| @needs_dlfcn |
| @no_wasm_backend('wasm backend resolves symbols greedily on startup') |
| def test_dylink_hyper_dupe(self): |
| self.set_setting('INITIAL_MEMORY', 64 * 1024 * 1024) |
| if not self.has_changed_setting('ASSERTIONS'): |
| self.set_setting('ASSERTIONS', 2) |
| |
| # test hyper-dynamic linking, and test duplicate warnings |
| create_test_file('third.cpp', r''' |
| #include <stdio.h> |
| int sidef() { return 36; } |
| int sideg = 49; |
| int bsidef() { return 536; } |
| extern void only_in_second_1(int x); |
| extern int second_to_third; |
| int third_to_second = 1337; |
| void only_in_third_0() { |
| // note we access our own globals directly, so |
| // it doesn't matter that overriding failed |
| printf("only_in_third_0: %d, %d, %d\n", sidef(), sideg, second_to_third); |
| only_in_second_1(2112); |
| } |
| void only_in_third_1(int x) { |
| printf("only_in_third_1: %d, %d, %d, %d\n", sidef(), sideg, second_to_third, x); |
| } |
| ''') |
| if self.is_wasm(): |
| libname = 'third.wasm' |
| else: |
| libname = 'third.js' |
| run_process([EMCC, 'third.cpp', '-o', libname, '-s', 'SIDE_MODULE', '-s', 'EXPORT_ALL'] + self.get_emcc_args()) |
| |
| self.dylink_test(main=r''' |
| #include <stdio.h> |
| #include <emscripten.h> |
| extern int sidef(); |
| extern int sideg; |
| extern int bsidef(); |
| extern int bsideg; |
| extern void only_in_second_0(); |
| extern void only_in_third_0(); |
| int main() { |
| EM_ASM({ |
| loadDynamicLibrary('%s'); // hyper-dynamic! works at least for functions (and consts not used in same block) |
| }); |
| printf("sidef: %%d, sideg: %%d.\n", sidef(), sideg); |
| printf("bsidef: %%d.\n", bsidef()); |
| only_in_second_0(); |
| only_in_third_0(); |
| } |
| ''' % libname, |
| side=r''' |
| #include <stdio.h> |
| int sidef() { return 10; } // third will try to override these, but fail! |
| int sideg = 20; |
| extern void only_in_third_1(int x); |
| int second_to_third = 500; |
| extern int third_to_second; |
| void only_in_second_0() { |
| printf("only_in_second_0: %d, %d, %d\n", sidef(), sideg, third_to_second); |
| only_in_third_1(1221); |
| } |
| void only_in_second_1(int x) { |
| printf("only_in_second_1: %d, %d, %d, %d\n", sidef(), sideg, third_to_second, x); |
| } |
| ''', |
| expected=['sidef: 10, sideg: 20.\nbsidef: 536.\nonly_in_second_0: 10, 20, 1337\nonly_in_third_1: 36, 49, 500, 1221\nonly_in_third_0: 36, 49, 500\nonly_in_second_1: 10, 20, 1337, 2112\n'], |
| need_reverse=not self.is_wasm()) # in wasm, we can't flip as the side would have an EM_ASM, which we don't support yet TODO |
| |
| if not self.has_changed_setting('ASSERTIONS'): |
| print('check warnings') |
| full = run_js('src.cpp.o.js', engine=JS_ENGINES[0], full_output=True, stderr=STDOUT) |
| self.assertContained("warning: symbol '_sideg' from '%s' already exists" % libname, full) |
| |
| @needs_dlfcn |
| @no_wasm_backend('possible https://github.com/emscripten-core/emscripten/issues/9038') |
| def test_dylink_dso_needed(self): |
| def do_run(src, expected_output): |
| self.do_run(src + 'int main() { return test_main(); }', expected_output) |
| self._test_dylink_dso_needed(do_run) |
| |
| @needs_dlfcn |
| def test_dylink_dot_a(self): |
| # .a linking must force all .o files inside it, when in a shared module |
| create_test_file('third.cpp', 'extern "C" int sidef() { return 36; }') |
| create_test_file('fourth.cpp', 'extern "C" int sideg() { return 17; }') |
| |
| run_process([EMCC, '-c', 'third.cpp', '-o', 'third.o'] + self.get_emcc_args()) |
| run_process([EMCC, '-c', 'fourth.cpp', '-o', 'fourth.o'] + self.get_emcc_args()) |
| run_process([EMAR, 'rc', 'libfourth.a', 'fourth.o']) |
| |
| self.dylink_test(main=r''' |
| #include <stdio.h> |
| #include <emscripten.h> |
| extern "C" int sidef(); |
| extern "C" int sideg(); |
| int main() { |
| printf("sidef: %d, sideg: %d.\n", sidef(), sideg()); |
| } |
| ''', |
| # contents of libfourth.a must be included, even if they aren't referred to! |
| side=['libfourth.a', 'third.o'], |
| expected=['sidef: 36, sideg: 17.\n']) |
| |
| @needs_dlfcn |
| def test_dylink_spaghetti(self): |
| self.dylink_test(main=r''' |
| #include <stdio.h> |
| int main_x = 72; |
| extern int side_x; |
| int adjust = side_x + 10; |
| int *ptr = &side_x; |
| struct Class { |
| Class() { |
| printf("main init sees %d, %d, %d.\n", adjust, *ptr, main_x); |
| } |
| }; |
| Class cm; |
| int main() { |
| printf("main main sees %d, %d, %d.\n", adjust, *ptr, main_x); |
| return 0; |
| } |
| ''', side=r''' |
| #include <stdio.h> |
| extern int main_x; |
| int side_x = -534; |
| int adjust2 = main_x + 10; |
| int *ptr2 = &main_x; |
| struct Class { |
| Class() { |
| printf("side init sees %d, %d, %d.\n", adjust2, *ptr2, side_x); |
| } |
| }; |
| Class cs; |
| ''', expected=['side init sees 82, 72, -534.\nmain init sees -524, -534, 72.\nmain main sees -524, -534, 72.', |
| 'main init sees -524, -534, 72.\nside init sees 82, 72, -534.\nmain main sees -524, -534, 72.']) |
| |
| @needs_make('mingw32-make') |
| @needs_dlfcn |
| def test_dylink_zlib(self): |
| self.emcc_args += ['-I' + path_from_root('tests', 'third_party', 'zlib'), '-s', 'RELOCATABLE'] |
| zlib_archive = self.get_zlib_library() |
| self.dylink_test(main=open(path_from_root('tests', 'third_party', 'zlib', 'example.c')).read(), |
| side=zlib_archive, |
| expected=open(path_from_root('tests', 'core', 'test_zlib.out')).read(), |
| force_c=True) |
| |
| # @needs_dlfcn |
| # def test_dylink_bullet(self): |
| # self.emcc_args += ['-I' + path_from_root('tests', 'bullet', 'src')] |
| # side = self.get_bullet_library(self, True) |
| # self.dylink_test(main=open(path_from_root('tests', 'bullet', 'Demos', 'HelloWorld', 'HelloWorld.cpp')).read(), |
| # side=side, |
| # expected=[open(path_from_root('tests', 'bullet', 'output.txt')).read(), # different roundings |
| # open(path_from_root('tests', 'bullet', 'output2.txt')).read(), |
| # open(path_from_root('tests', 'bullet', 'output3.txt')).read()]) |
| |
| @needs_dlfcn |
| @no_fastcomp('https://github.com/emscripten-core/emscripten/issues/8376') |
| def test_dylink_rtti(self): |
| # Verify that objects created in one module and be dynamic_cast<> correctly |
| # in the another module. |
| # Each module will define its own copy of certain COMDAT symbols such as |
| # each classs's typeinfo, but at runtime they should both use the same one. |
| header = ''' |
| #include <cstddef> |
| |
| class Foo { |
| public: |
| virtual ~Foo() {} |
| }; |
| |
| class Bar : public Foo { |
| public: |
| virtual ~Bar() {} |
| }; |
| |
| bool is_bar(Foo* foo); |
| ''' |
| |
| main = ''' |
| #include <stdio.h> |
| #include "header.h" |
| |
| int main() { |
| Bar bar; |
| if (!is_bar(&bar)) { |
| puts("failure"); |
| return 1; |
| } |
| puts("success"); |
| return 0; |
| } |
| ''' |
| |
| side = ''' |
| #include "header.h" |
| |
| bool is_bar(Foo* foo) { |
| return dynamic_cast<Bar*>(foo) != nullptr; |
| } |
| ''' |
| |
| self.dylink_test(main=main, |
| side=side, |
| header=header, |
| expected='success') |
| |
| def test_random(self): |
| src = r'''#include <stdlib.h> |
| #include <stdio.h> |
| |
| int main() |
| { |
| srandom(0xdeadbeef); |
| printf("%ld\n", random()); |
| } |
| ''' |
| self.do_run(src, '956867869') |
| |
| def test_rand(self): |
| src = r'''#include <stdlib.h> |
| #include <stdio.h> |
| #include <assert.h> |
| int main() |
| { |
| // we need RAND_MAX to be a bitmask (power of 2 minus 1). this assertions guarantees |
| // if RAND_MAX changes the test failure will focus attention on that issue here. |
| assert(RAND_MAX == 0x7fffffff); |
| |
| srand(0xdeadbeef); |
| for(int i = 0; i < 10; ++i) |
| printf("%d\n", rand()); |
| |
| unsigned int seed = 0xdeadbeef; |
| for(int i = 0; i < 10; ++i) |
| printf("%d\n", rand_r(&seed)); |
| |
| bool haveEvenAndOdd = true; |
| for(int i = 1; i <= 30; ++i) |
| { |
| int mask = 1 << i; |
| if (mask > RAND_MAX) break; |
| bool haveEven = false; |
| bool haveOdd = false; |
| for(int j = 0; j < 1000 && (!haveEven || !haveOdd); ++j) |
| { |
| if ((rand() & mask) == 0) |
| haveEven = true; |
| else |
| haveOdd = true; |
| } |
| haveEvenAndOdd = haveEvenAndOdd && haveEven && haveOdd; |
| } |
| if (haveEvenAndOdd) |
| printf("Have even and odd!\n"); |
| |
| return 0; |
| } |
| ''' |
| expected = '''490242850 |
| 2074599277 |
| 1480056542 |
| 1912638067 |
| 931112055 |
| 2110392489 |
| 2053422194 |
| 1614832492 |
| 216117595 |
| 174823244 |
| 760368382 |
| 602359081 |
| 1121118963 |
| 1291018924 |
| 1608306807 |
| 352705809 |
| 958258461 |
| 1182561381 |
| 114276303 |
| 1481323674 |
| Have even and odd! |
| ''' |
| self.do_run(src, expected) |
| |
| def test_strtod(self): |
| src = open(path_from_root('tests', 'core', 'test_strtod.c')).read() |
| expected = open(path_from_root('tests', 'core', 'test_strtod.out')).read() |
| self.do_run(src, expected) |
| |
| def test_strtold(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_strtold') |
| |
| def test_strtok(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_strtok') |
| |
| def test_parseInt(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_parseInt') |
| |
| def test_transtrcase(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_transtrcase') |
| |
| @no_wasm2js('very slow to compile') |
| def test_printf(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.do_run_in_out_file_test('tests', 'printf', 'test') |
| |
| def test_printf_2(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_printf_2') |
| |
| def test_printf_float(self): |
| self.do_run_in_out_file_test('tests', 'printf', 'test_float') |
| |
| def test_printf_octal(self): |
| self.do_run_in_out_file_test('tests', 'printf', 'test_octal') |
| |
| def test_vprintf(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_vprintf') |
| |
| def test_vsnprintf(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_vsnprintf') |
| |
| def test_printf_more(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_printf_more') |
| |
| def test_perrar(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_perrar') |
| |
| def test_atoX(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_atoX') |
| |
| def test_strstr(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_strstr') |
| |
| def test_fnmatch(self): |
| # Run one test without assertions, for additional coverage |
| if self.run_name == 'asm2m': |
| i = self.emcc_args.index('ASSERTIONS=1') |
| assert i > 0 and self.emcc_args[i - 1] == '-s' |
| self.emcc_args[i] = 'ASSERTIONS=0' |
| print('flip assertions off') |
| self.do_run_in_out_file_test('tests', 'core', 'test_fnmatch') |
| |
| def test_sscanf(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_sscanf') |
| |
| def test_sscanf_2(self): |
| # doubles |
| for ftype in ['float', 'double']: |
| src = r''' |
| #include <stdio.h> |
| |
| int main(){ |
| char strval1[] = "1.2345678901"; |
| char strval2[] = "1.23456789e5"; |
| char strval3[] = "1.23456789E5"; |
| char strval4[] = "1.2345678e-5"; |
| char strval5[] = "1.2345678E-5"; |
| double dblval = 1.2345678901; |
| double tstval; |
| |
| sscanf(strval1, "%lf", &tstval); |
| if(dblval != tstval) printf("FAIL: Values are not equal: %lf %lf\n", dblval, tstval); |
| else printf("Pass: %lf %lf\n", tstval, dblval); |
| |
| sscanf(strval2, "%lf", &tstval); |
| dblval = 123456.789; |
| if(dblval != tstval) printf("FAIL: Values are not equal: %lf %lf\n", dblval, tstval); |
| else printf("Pass: %lf %lf\n", tstval, dblval); |
| |
| sscanf(strval3, "%lf", &tstval); |
| dblval = 123456.789; |
| if(dblval != tstval) printf("FAIL: Values are not equal: %lf %lf\n", dblval, tstval); |
| else printf("Pass: %lf %lf\n", tstval, dblval); |
| |
| sscanf(strval4, "%lf", &tstval); |
| dblval = 0.000012345678; |
| if(dblval != tstval) printf("FAIL: Values are not equal: %lf %lf\n", dblval, tstval); |
| else printf("Pass: %lf %lf\n", tstval, dblval); |
| |
| sscanf(strval5, "%lf", &tstval); |
| dblval = 0.000012345678; |
| if(dblval != tstval) printf("FAIL: Values are not equal: %lf %lf\n", dblval, tstval); |
| else printf("Pass: %lf %lf\n", tstval, dblval); |
| |
| return 0; |
| } |
| ''' |
| if ftype == 'float': |
| self.do_run(src.replace('%lf', '%f').replace('double', 'float'), '''Pass: 1.234568 1.234568 |
| Pass: 123456.789062 123456.789062 |
| Pass: 123456.789062 123456.789062 |
| Pass: 0.000012 0.000012 |
| Pass: 0.000012 0.000012''') |
| else: |
| self.do_run(src, '''Pass: 1.234568 1.234568 |
| Pass: 123456.789000 123456.789000 |
| Pass: 123456.789000 123456.789000 |
| Pass: 0.000012 0.000012 |
| Pass: 0.000012 0.000012''') |
| |
| def test_sscanf_n(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_sscanf_n') |
| |
| def test_sscanf_whitespace(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_sscanf_whitespace') |
| |
| def test_sscanf_other_whitespace(self): |
| # use i16s in printf |
| self.set_setting('SAFE_HEAP', 0) |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_sscanf_other_whitespace') |
| |
| def test_sscanf_3(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_sscanf_3') |
| |
| def test_sscanf_4(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_sscanf_4') |
| |
| def test_sscanf_5(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_sscanf_5') |
| |
| def test_sscanf_6(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_sscanf_6') |
| |
| def test_sscanf_skip(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_sscanf_skip') |
| |
| def test_sscanf_caps(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_sscanf_caps') |
| |
| def test_sscanf_hex(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_sscanf_hex') |
| |
| def test_sscanf_float(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_sscanf_float') |
| |
| def test_langinfo(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_langinfo') |
| |
| def test_files(self): |
| self.banned_js_engines = [SPIDERMONKEY_ENGINE] # closure can generate variables called 'gc', which pick up js shell stuff |
| if self.maybe_closure(): # Use closure here, to test we don't break FS stuff |
| self.emcc_args = [x for x in self.emcc_args if x != '-g'] # ensure we test --closure 1 --memory-init-file 1 (-g would disable closure) |
| elif '-O3' in self.emcc_args and not self.is_wasm(): |
| print('closure 2') |
| self.emcc_args += ['--closure', '2', '-Wno-almost-asm'] # Use closure 2 here for some additional coverage |
| return self.skipTest('TODO: currently skipped because CI runs out of memory running Closure in this test!') |
| |
| self.emcc_args += ['-s', 'FORCE_FILESYSTEM=1', '--pre-js', 'pre.js'] |
| |
| print('base', self.emcc_args) |
| |
| create_test_file('pre.js', ''' |
| /** @suppress{checkTypes}*/ |
| Module = { |
| 'noFSInit': true, |
| 'preRun': function() { |
| FS.createLazyFile('/', 'test.file', 'test.file', true, false); |
| // Test FS_* exporting |
| Module['FS_createDataFile']('/', 'somefile.binary', [100, 200, 50, 25, 10, 77, 123], true, false, false); // 200 becomes -56, since signed chars are used in memory |
| var test_files_input = 'hi there!'; |
| var test_files_input_index = 0; |
| FS.init(function() { |
| return test_files_input.charCodeAt(test_files_input_index++) || null; |
| }); |
| } |
| }; |
| ''') |
| |
| create_test_file('test.file', 'some data') |
| |
| src = open(path_from_root('tests', 'files.cpp')).read() |
| |
| mem_file = 'src.cpp.o.js.mem' |
| try_delete(mem_file) |
| |
| def clean(out, err): |
| return '\n'.join([line for line in (out + err).split('\n') if 'binaryen' not in line and 'wasm' not in line and 'so not running' not in line]) |
| |
| self.do_run(src, ('size: 7\ndata: 100,-56,50,25,10,77,123\nloop: 100 -56 50 25 10 77 123 \ninput:hi there!\ntexto\n$\n5 : 10,30,20,11,88\nother=some data.\nseeked=me da.\nseeked=ata.\nseeked=ta.\nfscanfed: 10 - hello\n5 bytes to dev/null: 5\nok.\ntexte\n', 'size: 7\ndata: 100,-56,50,25,10,77,123\nloop: 100 -56 50 25 10 77 123 \ninput:hi there!\ntexto\ntexte\n$\n5 : 10,30,20,11,88\nother=some data.\nseeked=me da.\nseeked=ata.\nseeked=ta.\nfscanfed: 10 - hello\n5 bytes to dev/null: 5\nok.\n'), |
| output_nicerizer=clean) |
| |
| if self.uses_memory_init_file(): |
| self.assertExists(mem_file) |
| |
| def test_files_m(self): |
| # Test for Module.stdin etc. |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| |
| create_test_file('pre.js', ''' |
| Module = { |
| data: [10, 20, 40, 30], |
| stdin: function() { return Module.data.pop() || null }, |
| stdout: function(x) { out('got: ' + x) } |
| }; |
| ''') |
| self.emcc_args += ['--pre-js', 'pre.js'] |
| |
| src = r''' |
| #include <stdio.h> |
| #include <unistd.h> |
| |
| int main () { |
| char c; |
| fprintf(stderr, "isatty? %d,%d,%d\n", isatty(fileno(stdin)), isatty(fileno(stdout)), isatty(fileno(stderr))); |
| while ((c = fgetc(stdin)) != EOF) { |
| putc(c+5, stdout); |
| } |
| return 0; |
| } |
| ''' |
| |
| def clean(out, err): |
| return '\n'.join(l for l in (out + err).splitlines() if 'warning' not in l and 'binaryen' not in l) |
| |
| self.do_run(src, ('got: 35\ngot: 45\ngot: 25\ngot: 15\nisatty? 0,0,1\n', 'got: 35\ngot: 45\ngot: 25\ngot: 15\nisatty? 0,0,1', 'isatty? 0,0,1\ngot: 35\ngot: 45\ngot: 25\ngot: 15'), output_nicerizer=clean) |
| |
| def test_mount(self): |
| self.set_setting('FORCE_FILESYSTEM', 1) |
| src = open(path_from_root('tests', 'fs', 'test_mount.c')).read() |
| self.do_run(src, 'success', force_c=True) |
| |
| def test_getdents64(self): |
| src = open(path_from_root('tests', 'fs', 'test_getdents64.cpp')).read() |
| self.do_run(src, '..') |
| |
| def test_getdents64_special_cases(self): |
| self.banned_js_engines = [V8_ENGINE] # https://bugs.chromium.org/p/v8/issues/detail?id=6881 |
| src = path_from_root('tests', 'fs', 'test_getdents64_special_cases.cpp') |
| out = path_from_root('tests', 'fs', 'test_getdents64_special_cases.out') |
| self.do_run_from_file(src, out, assert_identical=True) |
| |
| def test_getcwd_with_non_ascii_name(self): |
| self.banned_js_engines = [V8_ENGINE] # https://bugs.chromium.org/p/v8/issues/detail?id=6881 |
| src = path_from_root('tests', 'fs', 'test_getcwd_with_non_ascii_name.cpp') |
| out = path_from_root('tests', 'fs', 'test_getcwd_with_non_ascii_name.out') |
| self.do_run_from_file(src, out, assert_identical=True) |
| |
| def test_fwrite_0(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_fwrite_0') |
| |
| def test_fgetc_ungetc(self): |
| print('TODO: update this test once the musl ungetc-on-EOF-stream bug is fixed upstream and reaches us') |
| self.set_setting('SYSCALL_DEBUG', 1) |
| self.clear() |
| orig_compiler_opts = self.emcc_args[:] |
| for fs in ['MEMFS', 'NODEFS']: |
| print(fs) |
| src = open(path_from_root('tests', 'stdio', 'test_fgetc_ungetc.c')).read() |
| self.emcc_args = orig_compiler_opts + ['-D' + fs] |
| if fs == 'NODEFS': |
| self.emcc_args += ['-lnodefs.js'] |
| self.do_run(src, 'success', force_c=True, js_engines=[NODE_JS]) |
| |
| def test_fgetc_unsigned(self): |
| src = r''' |
| #include <stdio.h> |
| int main() { |
| FILE *file = fopen("file_with_byte_234.txt", "rb"); |
| int c = fgetc(file); |
| printf("*%d\n", c); |
| } |
| ''' |
| create_test_file('file_with_byte_234.txt', b'\xea', binary=True) |
| self.emcc_args += ['--embed-file', 'file_with_byte_234.txt'] |
| self.do_run(src, '*234\n') |
| |
| def test_fgets_eol(self): |
| src = r''' |
| #include <stdio.h> |
| char buf[32]; |
| int main() |
| { |
| const char *r = "SUCCESS"; |
| FILE *f = fopen("eol.txt", "r"); |
| while (fgets(buf, 32, f) != NULL) { |
| if (buf[0] == '\0') { |
| r = "FAIL"; |
| break; |
| } |
| } |
| printf("%s\n", r); |
| fclose(f); |
| return 0; |
| } |
| ''' |
| open('eol.txt', 'wb').write(b'\n') |
| self.emcc_args += ['--embed-file', 'eol.txt'] |
| self.do_run(src, 'SUCCESS\n') |
| |
| def test_fscanf(self): |
| create_test_file('three_numbers.txt', '-1 0.1 -.1') |
| src = r''' |
| #include <stdio.h> |
| #include <assert.h> |
| #include <float.h> |
| int main() |
| { |
| float x = FLT_MAX, y = FLT_MAX, z = FLT_MAX; |
| |
| FILE* fp = fopen("three_numbers.txt", "r"); |
| if (fp) { |
| int match = fscanf(fp, " %f %f %f ", &x, &y, &z); |
| printf("match = %d\n", match); |
| printf("x = %0.1f, y = %0.1f, z = %0.1f\n", x, y, z); |
| } else { |
| printf("failed to open three_numbers.txt\n"); |
| } |
| return 0; |
| } |
| ''' |
| self.emcc_args += ['--embed-file', 'three_numbers.txt'] |
| self.do_run(src, 'match = 3\nx = -1.0, y = 0.1, z = -0.1\n') |
| |
| def test_fscanf_2(self): |
| create_test_file('a.txt', '''1/2/3 4/5/6 7/8/9 |
| ''') |
| self.emcc_args += ['--embed-file', 'a.txt'] |
| self.do_run(r'''#include <cstdio> |
| #include <iostream> |
| |
| using namespace std; |
| |
| int |
| main( int argv, char ** argc ) { |
| cout << "fscanf test" << endl; |
| |
| FILE * file; |
| file = fopen("a.txt", "rb"); |
| int vertexIndex[4]; |
| int normalIndex[4]; |
| int uvIndex[4]; |
| |
| int matches = fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d\n", &vertexIndex[0], &uvIndex[0], &normalIndex[0], &vertexIndex [1], &uvIndex[1], &normalIndex[1], &vertexIndex[2], &uvIndex[2], &normalIndex[2], &vertexIndex[3], &uvIndex[3], &normalIndex[3]); |
| |
| cout << matches << endl; |
| |
| return 0; |
| } |
| ''', 'fscanf test\n9\n') |
| |
| def test_fileno(self): |
| create_test_file('empty.txt', '') |
| src = r''' |
| #include <stdio.h> |
| #include <unistd.h> |
| int main() |
| { |
| FILE* fp = fopen("empty.txt", "r"); |
| if (fp) { |
| printf("%d\n", fileno(fp)); |
| } else { |
| printf("failed to open empty.txt\n"); |
| } |
| return 0; |
| } |
| ''' |
| self.emcc_args += ['--embed-file', 'empty.txt'] |
| self.do_run(src, '3\n') |
| |
| def test_readdir(self): |
| self.do_run_in_out_file_test('tests', 'dirent', 'test_readdir') |
| |
| def test_readdir_empty(self): |
| self.do_run_in_out_file_test('tests', 'dirent', 'test_readdir_empty') |
| |
| def test_stat(self): |
| src = open(path_from_root('tests', 'stat', 'test_stat.c')).read() |
| self.do_run(src, 'success', force_c=True) |
| |
| self.verify_in_strict_mode('src.c.o.js') |
| |
| def test_stat_chmod(self): |
| src = open(path_from_root('tests', 'stat', 'test_chmod.c')).read() |
| self.do_run(src, 'success', force_c=True) |
| |
| def test_stat_mknod(self): |
| src = open(path_from_root('tests', 'stat', 'test_mknod.c')).read() |
| self.do_run(src, 'success', force_c=True) |
| |
| def test_fcntl(self): |
| self.add_pre_run("FS.createDataFile('/', 'test', 'abcdef', true, true, false);") |
| self.do_run_in_out_file_test('tests', 'fcntl', 'test_fcntl') |
| |
| def test_fcntl_open(self): |
| self.do_run_in_out_file_test('tests', 'fcntl', 'test_fcntl_open') |
| |
| def test_fcntl_misc(self): |
| self.add_pre_run("FS.createDataFile('/', 'test', 'abcdef', true, true, false);") |
| self.do_run_in_out_file_test('tests', 'fcntl', 'test_fcntl_misc') |
| |
| def test_poll(self): |
| self.add_pre_run(''' |
| var dummy_device = FS.makedev(64, 0); |
| FS.registerDevice(dummy_device, {}); |
| |
| FS.createDataFile('/', 'file', 'abcdef', true, true, false); |
| FS.mkdev('/device', dummy_device); |
| ''') |
| self.do_run_in_out_file_test('tests', 'core', 'test_poll') |
| |
| def test_statvfs(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_statvfs') |
| |
| def test_libgen(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_libgen') |
| |
| def test_utime(self): |
| src = open(path_from_root('tests', 'utime', 'test_utime.c')).read() |
| self.do_run(src, 'success', force_c=True) |
| |
| @no_minimal_runtime('MINIMAL_RUNTIME does not have getValue() and setValue() (TODO add it to a JS library function to get it in)') |
| def test_utf(self): |
| self.banned_js_engines = [SPIDERMONKEY_ENGINE] # only node handles utf well |
| self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_malloc']) |
| self.set_setting('EXTRA_EXPORTED_RUNTIME_METHODS', ['getValue', 'setValue', 'UTF8ToString', 'stringToUTF8']) |
| self.do_run_in_out_file_test('tests', 'core', 'test_utf') |
| |
| def test_utf32(self): |
| if self.get_setting('MINIMAL_RUNTIME'): |
| self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$UTF32ToString', '$stringToUTF32', '$lengthBytesUTF32']) |
| else: |
| self.set_setting('EXTRA_EXPORTED_RUNTIME_METHODS', ['UTF32ToString', 'stringToUTF32', 'lengthBytesUTF32']) |
| self.do_run(open(path_from_root('tests', 'utf32.cpp')).read(), 'OK.') |
| self.do_run(open(path_from_root('tests', 'utf32.cpp')).read(), 'OK.', args=['-fshort-wchar']) |
| |
| def test_utf8(self): |
| if self.get_setting('MINIMAL_RUNTIME'): |
| self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', ['$AsciiToString', '$stringToAscii', '$writeAsciiToMemory']) |
| else: |
| self.set_setting('EXTRA_EXPORTED_RUNTIME_METHODS', |
| ['UTF8ToString', 'stringToUTF8', 'AsciiToString', 'stringToAscii']) |
| self.do_run(open(path_from_root('tests', 'utf8.cpp')).read(), 'OK.') |
| |
| @also_with_wasm_bigint |
| def test_utf8_textdecoder(self): |
| self.set_setting('EXTRA_EXPORTED_RUNTIME_METHODS', ['UTF8ToString', 'stringToUTF8']) |
| self.emcc_args += ['--embed-file', path_from_root('tests/utf8_corpus.txt') + '@/utf8_corpus.txt'] |
| self.do_run(open(path_from_root('tests', 'benchmark_utf8.cpp')).read(), 'OK.') |
| |
| # Test that invalid character in UTF8 does not cause decoding to crash. |
| def test_utf8_invalid(self): |
| self.set_setting('EXTRA_EXPORTED_RUNTIME_METHODS', ['UTF8ToString', 'stringToUTF8']) |
| for decoder_mode in [[], ['-s', 'TEXTDECODER=1']]: |
| self.emcc_args += decoder_mode |
| print(str(decoder_mode)) |
| self.do_run(open(path_from_root('tests', 'utf8_invalid.cpp')).read(), 'OK.') |
| |
| # Test that invalid character in UTF8 does not cause decoding to crash. |
| def test_minimal_runtime_utf8_invalid(self): |
| self.set_setting('EXTRA_EXPORTED_RUNTIME_METHODS', ['UTF8ToString', 'stringToUTF8']) |
| for decoder_mode in [[], ['-s', 'TEXTDECODER=1']]: |
| self.emcc_args += ['-s', 'MINIMAL_RUNTIME=1'] + decoder_mode |
| print(str(decoder_mode)) |
| self.do_run(open(path_from_root('tests', 'utf8_invalid.cpp')).read(), 'OK.') |
| |
| def test_utf16_textdecoder(self): |
| self.set_setting('EXTRA_EXPORTED_RUNTIME_METHODS', ['UTF16ToString', 'stringToUTF16', 'lengthBytesUTF16']) |
| self.emcc_args += ['--embed-file', path_from_root('tests/utf16_corpus.txt') + '@/utf16_corpus.txt'] |
| self.do_run(open(path_from_root('tests', 'benchmark_utf16.cpp')).read(), 'OK.') |
| |
| def test_wprintf(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_wprintf') |
| |
| def test_write_stdout_fileno(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_write_stdout_fileno') |
| self.do_run_in_out_file_test('tests', 'core', 'test_write_stdout_fileno', args=['-s', 'FILESYSTEM=0']) |
| |
| def test_direct_string_constant_usage(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_direct_string_constant_usage') |
| |
| def test_std_cout_new(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_std_cout_new') |
| |
| def test_istream(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| |
| for linkable in [0]: # , 1]: |
| print(linkable) |
| # regression check for issue #273 |
| self.set_setting('LINKABLE', linkable) |
| self.do_run_in_out_file_test('tests', 'core', 'test_istream') |
| |
| def test_fs_base(self): |
| # TODO(sbc): It seems that INCLUDE_FULL_LIBRARY will generally generate |
| # undefined symbols at link time so perhaps have it imply this setting? |
| self.set_setting('WARN_ON_UNDEFINED_SYMBOLS', 0) |
| self.set_setting('INCLUDE_FULL_LIBRARY', 1) |
| self.add_pre_run(open(path_from_root('tests', 'filesystem', 'src.js')).read()) |
| src = 'int main() {return 0;}\n' |
| expected = open(path_from_root('tests', 'filesystem', 'output.txt')).read() |
| self.do_run(src, expected) |
| |
| @also_with_noderawfs |
| @is_slow_test |
| def test_fs_nodefs_rw(self): |
| self.emcc_args += ['-lnodefs.js'] |
| self.set_setting('SYSCALL_DEBUG', 1) |
| src = open(path_from_root('tests', 'fs', 'test_nodefs_rw.c')).read() |
| self.do_run(src, 'success', force_c=True) |
| if '-g' not in self.emcc_args: |
| print('closure') |
| self.emcc_args += ['--closure', '1'] |
| self.do_run(src, 'success', force_c=True) |
| |
| @also_with_noderawfs |
| def test_fs_nodefs_cloexec(self): |
| self.emcc_args += ['-lnodefs.js'] |
| src = open(path_from_root('tests', 'fs', 'test_nodefs_cloexec.c')).read() |
| self.do_run(src, 'success', force_c=True) |
| |
| def test_fs_nodefs_home(self): |
| self.set_setting('FORCE_FILESYSTEM', 1) |
| self.emcc_args += ['-lnodefs.js'] |
| src = open(path_from_root('tests', 'fs', 'test_nodefs_home.c')).read() |
| self.do_run(src, 'success', js_engines=[NODE_JS]) |
| |
| def test_fs_nodefs_nofollow(self): |
| self.emcc_args += ['-lnodefs.js'] |
| src = open(path_from_root('tests', 'fs', 'test_nodefs_nofollow.c')).read() |
| self.do_run(src, 'success', js_engines=[NODE_JS]) |
| |
| def test_fs_trackingdelegate(self): |
| src = path_from_root('tests', 'fs', 'test_trackingdelegate.c') |
| out = path_from_root('tests', 'fs', 'test_trackingdelegate.out') |
| self.do_run_from_file(src, out) |
| |
| @also_with_noderawfs |
| def test_fs_writeFile(self): |
| self.emcc_args += ['-s', 'DISABLE_EXCEPTION_CATCHING=1'] # see issue 2334 |
| src = path_from_root('tests', 'fs', 'test_writeFile.cpp') |
| out = path_from_root('tests', 'fs', 'test_writeFile.out') |
| self.do_run_from_file(src, out) |
| |
| def test_fs_write(self): |
| src = path_from_root('tests', 'fs', 'test_write.cpp') |
| out = path_from_root('tests', 'fs', 'test_write.out') |
| self.do_run_from_file(src, out) |
| |
| @also_with_noderawfs |
| def test_fs_emptyPath(self): |
| src = path_from_root('tests', 'fs', 'test_emptyPath.c') |
| out = path_from_root('tests', 'fs', 'test_emptyPath.out') |
| self.do_run_from_file(src, out) |
| |
| @also_with_noderawfs |
| def test_fs_append(self): |
| src = open(path_from_root('tests', 'fs', 'test_append.c')).read() |
| self.do_run(src, 'success', force_c=True) |
| |
| def test_fs_mmap(self): |
| orig_compiler_opts = self.emcc_args[:] |
| for fs in ['MEMFS', 'NODEFS']: |
| src = path_from_root('tests', 'fs', 'test_mmap.c') |
| out = path_from_root('tests', 'fs', 'test_mmap.out') |
| self.emcc_args = orig_compiler_opts + ['-D' + fs] |
| if fs == 'NODEFS': |
| self.emcc_args += ['-lnodefs.js'] |
| self.do_run_from_file(src, out) |
| |
| @also_with_noderawfs |
| def test_fs_errorstack(self): |
| # Enables strict mode, which may catch some strict-mode-only errors |
| # so that users can safely work with strict JavaScript if enabled. |
| create_test_file('pre.js', '"use strict";') |
| self.emcc_args += ['--pre-js', 'pre.js'] |
| |
| self.set_setting('FORCE_FILESYSTEM', 1) |
| self.set_setting('ASSERTIONS', 1) |
| self.do_run(r''' |
| #include <emscripten.h> |
| #include <iostream> |
| int main(void) { |
| std::cout << "hello world\n"; // should work with strict mode |
| EM_ASM( |
| try { |
| FS.readFile('/dummy.txt'); |
| } catch (err) { |
| err.stack = err.stack; // should be writable |
| throw err; |
| } |
| ); |
| return 0; |
| } |
| ''', 'at Object.readFile', assert_returncode=None) # engines has different error stack format |
| |
| @also_with_noderawfs |
| def test_fs_llseek(self): |
| self.set_setting('FORCE_FILESYSTEM', 1) |
| src = open(path_from_root('tests', 'fs', 'test_llseek.c')).read() |
| self.do_run(src, 'success', force_c=True) |
| |
| def test_fs_64bit(self): |
| src = open(path_from_root('tests', 'fs', 'test_64bit.c')).read() |
| self.do_run(src, 'success', force_c=True) |
| |
| @no_windows('https://github.com/emscripten-core/emscripten/issues/8882') |
| def test_unistd_access(self): |
| self.clear() |
| orig_compiler_opts = self.emcc_args[:] |
| for fs in ['MEMFS', 'NODEFS']: |
| self.emcc_args = orig_compiler_opts + ['-D' + fs] |
| if fs == 'NODEFS': |
| self.emcc_args += ['-lnodefs.js'] |
| self.do_run_in_out_file_test('tests', 'unistd', 'access', js_engines=[NODE_JS]) |
| # Node.js fs.chmod is nearly no-op on Windows |
| if not WINDOWS: |
| self.emcc_args = orig_compiler_opts |
| self.emcc_args += ['-s', 'RAW_OS'] |
| self.do_run_in_out_file_test('tests', 'unistd', 'access', js_engines=[NODE_JS]) |
| |
| def test_unistd_curdir(self): |
| src = open(path_from_root('tests', 'unistd', 'curdir.c')).read() |
| expected = open(path_from_root('tests', 'unistd', 'curdir.out')).read() |
| self.do_run(src, expected) |
| |
| @also_with_noderawfs |
| def test_unistd_close(self): |
| src = open(path_from_root('tests', 'unistd', 'close.c')).read() |
| expected = open(path_from_root('tests', 'unistd', 'close.out')).read() |
| self.do_run(src, expected) |
| |
| def test_unistd_confstr(self): |
| src = open(path_from_root('tests', 'unistd', 'confstr.c')).read() |
| expected = open(path_from_root('tests', 'unistd', 'confstr.out')).read() |
| self.do_run(src, expected) |
| |
| def test_unistd_ttyname(self): |
| src = open(path_from_root('tests', 'unistd', 'ttyname.c')).read() |
| self.do_run(src, 'success', force_c=True) |
| |
| @also_with_noderawfs |
| def test_unistd_pipe(self): |
| src = open(path_from_root('tests', 'unistd', 'pipe.c')).read() |
| self.do_run(src, 'success', force_c=True) |
| |
| @also_with_noderawfs |
| def test_unistd_dup(self): |
| src = open(path_from_root('tests', 'unistd', 'dup.c')).read() |
| expected = open(path_from_root('tests', 'unistd', 'dup.out')).read() |
| self.do_run(src, expected) |
| |
| def test_unistd_pathconf(self): |
| src = open(path_from_root('tests', 'unistd', 'pathconf.c')).read() |
| expected = open(path_from_root('tests', 'unistd', 'pathconf.out')).read() |
| self.do_run(src, expected) |
| |
| def test_unistd_truncate(self): |
| self.clear() |
| orig_compiler_opts = self.emcc_args[:] |
| for fs in ['MEMFS', 'NODEFS']: |
| src = open(path_from_root('tests', 'unistd', 'truncate.c')).read() |
| expected = open(path_from_root('tests', 'unistd', 'truncate.out')).read() |
| self.emcc_args = orig_compiler_opts + ['-D' + fs] |
| if fs == 'NODEFS': |
| self.emcc_args += ['-lnodefs.js'] |
| self.do_run(src, expected, js_engines=[NODE_JS]) |
| |
| @no_windows("Windows throws EPERM rather than EACCES or EINVAL") |
| @unittest.skipIf(WINDOWS or os.geteuid() == 0, "Root access invalidates this test by being able to write on readonly files") |
| def test_unistd_truncate_noderawfs(self): |
| # FIXME |
| self.skipTest('fails on some node versions and OSes, e.g. 10.13.0 on linux') |
| |
| self.emcc_args += ['-s', 'RAW_OS'] |
| self.do_run_in_out_file_test('tests', 'unistd', 'truncate', js_engines=[NODE_JS]) |
| |
| def test_unistd_swab(self): |
| self.do_run_in_out_file_test('tests', 'unistd', 'swab') |
| |
| def test_unistd_isatty(self): |
| src = open(path_from_root('tests', 'unistd', 'isatty.c')).read() |
| self.do_run(src, 'success', force_c=True) |
| |
| @also_with_standalone_wasm() |
| def test_unistd_sysconf(self): |
| self.do_run_in_out_file_test('tests', 'unistd', 'sysconf') |
| |
| @no_asan('ASan alters memory layout') |
| def test_unistd_sysconf_phys_pages(self): |
| src = open(path_from_root('tests', 'unistd', 'sysconf_phys_pages.c')).read() |
| if self.get_setting('ALLOW_MEMORY_GROWTH'): |
| expected = (2 * 1024 * 1024 * 1024) // 16384 |
| else: |
| expected = 16 * 1024 * 1024 // 16384 |
| self.do_run(src, str(expected) + ', errno: 0') |
| |
| def test_unistd_login(self): |
| self.do_run_in_out_file_test('tests', 'unistd', 'login') |
| |
| @no_windows('https://github.com/emscripten-core/emscripten/issues/8882') |
| def test_unistd_unlink(self): |
| self.clear() |
| orig_compiler_opts = self.emcc_args[:] |
| src = open(path_from_root('tests', 'unistd', 'unlink.c')).read() |
| for fs in ['MEMFS', 'NODEFS']: |
| self.emcc_args = orig_compiler_opts + ['-D' + fs] |
| # symlinks on node.js on non-linux behave differently (e.g. on Windows they require administrative privileges) |
| # so skip testing those bits on that combination. |
| if fs == 'NODEFS': |
| self.emcc_args += ['-lnodefs.js'] |
| if WINDOWS: |
| self.emcc_args += ['-DNO_SYMLINK=1'] |
| if MACOS: |
| continue |
| self.do_run(src, 'success', force_c=True, js_engines=[NODE_JS]) |
| # Several differences/bugs on non-linux including https://github.com/nodejs/node/issues/18014 |
| if not WINDOWS and not MACOS: |
| self.emcc_args = orig_compiler_opts + ['-DNODERAWFS'] |
| # 0 if root user |
| if os.geteuid() == 0: |
| self.emcc_args += ['-DSKIP_ACCESS_TESTS'] |
| self.emcc_args += ['-s', 'RAW_OS'] |
| self.do_run(src, 'success', force_c=True, js_engines=[NODE_JS]) |
| |
| def test_unistd_links(self): |
| self.clear() |
| orig_compiler_opts = self.emcc_args[:] |
| for fs in ['MEMFS', 'NODEFS']: |
| if WINDOWS and fs == 'NODEFS': |
| print('Skipping NODEFS part of this test for test_unistd_links on Windows, since it would require administrative privileges.', file=sys.stderr) |
| # Also, other detected discrepancies if you do end up running this test on NODEFS: |
| # test expects /, but Windows gives \ as path slashes. |
| # Calling readlink() on a non-link gives error 22 EINVAL on Unix, but simply error 0 OK on Windows. |
| continue |
| self.emcc_args = orig_compiler_opts + ['-D' + fs] |
| if fs == 'NODEFS': |
| self.emcc_args += ['-lnodefs.js'] |
| self.do_run_in_out_file_test('tests', 'unistd', 'links', js_engines=[NODE_JS]) |
| |
| @no_windows('Skipping NODEFS test, since it would require administrative privileges.') |
| def test_unistd_symlink_on_nodefs(self): |
| # Also, other detected discrepancies if you do end up running this test on NODEFS: |
| # test expects /, but Windows gives \ as path slashes. |
| # Calling readlink() on a non-link gives error 22 EINVAL on Unix, but simply error 0 OK on Windows. |
| self.emcc_args += ['-lnodefs.js'] |
| self.clear() |
| src = open(path_from_root('tests', 'unistd', 'symlink_on_nodefs.c')).read() |
| expected = open(path_from_root('tests', 'unistd', 'symlink_on_nodefs.out')).read() |
| self.do_run(src, expected, js_engines=[NODE_JS]) |
| |
| def test_unistd_sleep(self): |
| src = open(path_from_root('tests', 'unistd', 'sleep.c')).read() |
| expected = open(path_from_root('tests', 'unistd', 'sleep.out')).read() |
| self.do_run(src, expected) |
| |
| @also_with_wasm_bigint |
| def test_unistd_io(self): |
| self.set_setting('INCLUDE_FULL_LIBRARY', 1) # uses constants from ERRNO_CODES |
| self.set_setting('ERROR_ON_UNDEFINED_SYMBOLS', 0) # avoid errors when linking in full library |
| orig_compiler_opts = self.emcc_args[:] |
| src = open(path_from_root('tests', 'unistd', 'io.c')).read() |
| expected = open(path_from_root('tests', 'unistd', 'io.out')).read() |
| for fs in ['MEMFS', 'NODEFS']: |
| self.clear() |
| self.emcc_args = orig_compiler_opts + ['-D' + fs] |
| if fs == 'NODEFS': |
| self.emcc_args += ['-lnodefs.js'] |
| self.do_run(src, expected) |
| |
| @no_windows('https://github.com/emscripten-core/emscripten/issues/8882') |
| def test_unistd_misc(self): |
| orig_compiler_opts = self.emcc_args[:] |
| for fs in ['MEMFS', 'NODEFS']: |
| self.emcc_args = orig_compiler_opts + ['-D' + fs] |
| if fs == 'NODEFS': |
| self.emcc_args += ['-lnodefs.js'] |
| self.do_run_in_out_file_test('tests', 'unistd', 'misc', js_engines=[NODE_JS]) |
| |
| # i64s in the API, which we'd need to legalize for JS, so in standalone mode |
| # all we can test is wasm VMs |
| @also_with_standalone_wasm(wasm2c=True) |
| def test_posixtime(self): |
| test_path = path_from_root('tests', 'core', 'test_posixtime') |
| src, output = (test_path + s for s in ('.c', '.out')) |
| self.banned_js_engines = [V8_ENGINE] # v8 lacks monotonic time |
| self.do_run_from_file(src, output) |
| |
| def test_uname(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_uname') |
| |
| def test_unary_literal(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_unary_literal') |
| |
| def test_env(self): |
| src = open(path_from_root('tests', 'env', 'src.c')).read() |
| expected = open(path_from_root('tests', 'env', 'output.txt')).read() |
| if not self.is_wasm_backend(): |
| # the fastcomp implementation is incorrect in one way |
| expected = expected.replace('after alteration: Qest5', 'after alteration: test5') |
| self.do_run(src, [ |
| expected.replace('{{{ THIS_PROGRAM }}}', self.in_dir('src.cpp.o.js')).replace('\\', '/'), # node, can find itself properly |
| expected.replace('{{{ THIS_PROGRAM }}}', './this.program') # spidermonkey, v8 |
| ]) |
| |
| def test_environ(self): |
| src = open(path_from_root('tests', 'env', 'src-mini.c')).read() |
| expected = open(path_from_root('tests', 'env', 'output-mini.txt')).read() |
| self.do_run(src, [ |
| expected.replace('{{{ THIS_PROGRAM }}}', self.in_dir('src.cpp.o.js')).replace('\\', '/'), # node, can find itself properly |
| expected.replace('{{{ THIS_PROGRAM }}}', './this.program') # spidermonkey, v8 |
| ]) |
| |
| def test_systypes(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_systypes') |
| |
| def test_stddef(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_stddef') |
| self.do_run_in_out_file_test('tests', 'core', 'test_stddef', force_c=True) |
| |
| def test_getloadavg(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_getloadavg') |
| |
| def test_nl_types(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_nl_types') |
| |
| def test_799(self): |
| src = open(path_from_root('tests', '799.cpp')).read() |
| self.do_run(src, '''Set PORT family: 0, port: 3979 |
| Get PORT family: 0 |
| PORT: 3979 |
| ''') |
| |
| def test_ctype(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_ctype') |
| |
| def test_strcasecmp(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_strcasecmp') |
| |
| def test_atomic(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_atomic') |
| |
| def test_atomic_cxx(self): |
| # the wasm backend has lock-free atomics, but not asm.js or asm2wasm |
| is_lock_free = self.is_wasm_backend() |
| self.emcc_args += ['-DIS_64BIT_LOCK_FREE=%d' % is_lock_free] |
| self.do_run_in_out_file_test('tests', 'core', 'test_atomic_cxx') |
| |
| if self.get_setting('ALLOW_MEMORY_GROWTH') == 0 and not self.is_wasm() \ |
| and not self.is_wasm_backend(): |
| print('main module') |
| self.set_setting('MAIN_MODULE', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_atomic_cxx') |
| |
| # TODO: test with USE_PTHREADS in wasm backend as well |
| |
| def test_phiundef(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_phiundef') |
| |
| def test_netinet_in(self): |
| src = open(path_from_root('tests', 'netinet', 'in.cpp')).read() |
| expected = open(path_from_root('tests', 'netinet', 'in.out')).read() |
| self.do_run(src, expected) |
| |
| @needs_dlfcn |
| def test_main_module_static_align(self): |
| if self.get_setting('ALLOW_MEMORY_GROWTH'): |
| self.skipTest('no shared modules with memory growth') |
| self.set_setting('MAIN_MODULE', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_main_module_static_align') |
| |
| # libc++ tests |
| |
| def test_iostream_and_determinism(self): |
| src = ''' |
| #include <iostream> |
| |
| int main() |
| { |
| std::cout << "hello world" << std::endl << 77 << "." << std::endl; |
| return 0; |
| } |
| ''' |
| |
| num = 5 |
| for i in range(num): |
| print('(iteration %d)' % i) |
| |
| # add some timing nondeterminism here, not that we need it, but whatever |
| time.sleep(random.random() / (10 * num)) |
| self.do_run(src, 'hello world\n77.\n') |
| |
| # Verify that this build is identical to the previous one |
| if os.path.exists('src.js.previous'): |
| self.assertBinaryEqual('src.cpp.o.js', 'src.js.previous') |
| shutil.copy2('src.cpp.o.js', 'src.js.previous') |
| |
| # Same but for the wasm file. |
| if self.get_setting('WASM') and not self.get_setting('WASM2JS'): |
| if os.path.exists('src.wasm.previous'): |
| self.assertBinaryEqual('src.cpp.o.wasm', 'src.wasm.previous') |
| shutil.copy2('src.cpp.o.wasm', 'src.wasm.previous') |
| |
| def test_stdvec(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_stdvec') |
| |
| def test_random_device(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_random_device') |
| |
| def test_reinterpreted_ptrs(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_reinterpreted_ptrs') |
| |
| def test_js_libraries(self): |
| create_test_file('main.cpp', ''' |
| #include <stdio.h> |
| extern "C" { |
| extern void printey(); |
| extern int calcey(int x, int y); |
| } |
| int main() { |
| printey(); |
| printf("*%d*\\n", calcey(10, 22)); |
| return 0; |
| } |
| ''') |
| create_test_file('mylib1.js', ''' |
| mergeInto(LibraryManager.library, { |
| printey: function() { |
| out('hello from lib!'); |
| } |
| }); |
| ''') |
| create_test_file('mylib2.js', ''' |
| mergeInto(LibraryManager.library, { |
| calcey: function(x, y) { |
| return x + y; |
| } |
| }); |
| ''') |
| |
| self.emcc_args += ['--js-library', 'mylib1.js', '--js-library', 'mylib2.js'] |
| self.do_run(open('main.cpp').read(), 'hello from lib!\n*32*\n') |
| |
| def test_unicode_js_library(self): |
| create_test_file('main.cpp', ''' |
| #include <stdio.h> |
| extern "C" { |
| extern void printey(); |
| } |
| int main() { |
| printey(); |
| return 0; |
| } |
| ''') |
| self.emcc_args += ['--js-library', path_from_root('tests', 'unicode_library.js')] |
| self.do_run(open('main.cpp').read(), u'Unicode snowman \u2603 says hello!') |
| |
| def test_funcptr_import_type(self): |
| self.emcc_args += ['--js-library', path_from_root('tests', 'core', 'test_funcptr_import_type.js')] |
| self.do_run_in_out_file_test('tests', 'core', 'test_funcptr_import_type') |
| |
| @no_asan('ASan does not work with EXPORT_ALL') |
| def test_constglobalunion(self): |
| self.emcc_args += ['-s', 'EXPORT_ALL=1'] |
| |
| self.do_run(r''' |
| #include <stdio.h> |
| |
| struct one_const { |
| long a; |
| }; |
| |
| struct two_consts { |
| long a; |
| long b; |
| }; |
| |
| union some_consts { |
| struct one_const one; |
| struct two_consts two; |
| }; |
| |
| union some_consts my_consts = {{ |
| 1 |
| }}; |
| |
| struct one_const addr_of_my_consts = { |
| (long)(&my_consts) |
| }; |
| |
| int main(void) { |
| printf("%li\n", (long)!!addr_of_my_consts.a); |
| return 0; |
| } |
| ''', '1') |
| |
| ### 'Medium' tests |
| |
| def test_fannkuch(self): |
| results = [(1, 0), (2, 1), (3, 2), (4, 4), (5, 7), (6, 10), (7, 16), (8, 22)] |
| src = open(path_from_root('tests', 'fannkuch.cpp')).read() |
| self.build(src, self.get_dir(), 'fannkuch.cpp') |
| for i, j in results: |
| print(i, j) |
| self.do_run('fannkuch.cpp.o.js', 'Pfannkuchen(%d) = %d.' % (i, j), [str(i)], no_build=True) |
| |
| def test_raytrace(self): |
| # TODO: Should we remove this test? |
| self.skipTest('Relies on double value rounding, extremely sensitive') |
| |
| src = open(path_from_root('tests', 'raytrace.cpp')).read().replace('double', 'float') |
| output = open(path_from_root('tests', 'raytrace.ppm')).read() |
| self.do_run(src, output, ['3', '16']) |
| |
| def test_fasta(self): |
| results = [(1, '''GG*ctt**tgagc*'''), |
| (20, '''GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTT*cttBtatcatatgctaKggNcataaaSatgtaaaDcDRtBggDtctttataattcBgtcg**tacgtgtagcctagtgtttgtgttgcgttatagtctatttgtggacacagtatggtcaaa**tgacgtcttttgatctgacggcgttaacaaagatactctg*'''), |
| (50, '''GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGA*TCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACAT*cttBtatcatatgctaKggNcataaaSatgtaaaDcDRtBggDtctttataattcBgtcg**tactDtDagcctatttSVHtHttKtgtHMaSattgWaHKHttttagacatWatgtRgaaa**NtactMcSMtYtcMgRtacttctWBacgaa**agatactctgggcaacacacatacttctctcatgttgtttcttcggacctttcataacct**ttcctggcacatggttagctgcacatcacaggattgtaagggtctagtggttcagtgagc**ggaatatcattcgtcggtggtgttaatctatctcggtgtagcttataaatgcatccgtaa**gaatattatgtttatttgtcggtacgttcatggtagtggtgtcgccgatttagacgtaaa**ggcatgtatg*''')] |
| |
| old = self.emcc_args |
| orig_src = open(path_from_root('tests', 'fasta.cpp')).read() |
| |
| def test(extra_args): |
| self.emcc_args = old + extra_args |
| for precision in [0, 1, 2]: |
| self.set_setting('PRECISE_F32', precision) |
| for t in ['float', 'double']: |
| print(precision, t) |
| src = orig_src.replace('double', t) |
| self.build(src, self.get_dir(), 'fasta.cpp') |
| for arg, output in results: |
| self.do_run('fasta.cpp.o.js', output, [str(arg)], lambda x, err: x.replace('\n', '*'), no_build=True) |
| shutil.copyfile('fasta.cpp.o.js', '%d_%s.js' % (precision, t)) |
| |
| test([]) |
| |
| @bleeding_edge_wasm_backend |
| def test_fasta_nontrapping(self): |
| self.emcc_args += ['-mnontrapping-fptoint'] |
| self.test_fasta() |
| |
| def test_whets(self): |
| self.do_run(open(path_from_root('tests', 'whets.cpp')).read(), 'Single Precision C Whetstone Benchmark', assert_returncode=None) |
| |
| def test_dlmalloc_inline(self): |
| self.banned_js_engines = [NODE_JS] # slower, and fail on 64-bit |
| # needed with typed arrays |
| self.set_setting('INITIAL_MEMORY', 128 * 1024 * 1024) |
| |
| src = open(path_from_root('system', 'lib', 'dlmalloc.c')).read() + '\n\n\n' + open(path_from_root('tests', 'dlmalloc_test.c')).read() |
| self.do_run(src, '*1,0*', ['200', '1'], force_c=True) |
| self.do_run(None, '*400,0*', ['400', '400'], force_c=True, no_build=True) |
| |
| def test_dlmalloc(self): |
| self.banned_js_engines = [NODE_JS] # slower, and fail on 64-bit |
| # needed with typed arrays |
| self.set_setting('INITIAL_MEMORY', 128 * 1024 * 1024) |
| |
| # Linked version |
| src = open(path_from_root('tests', 'dlmalloc_test.c')).read() |
| self.do_run(src, '*1,0*', ['200', '1']) |
| self.do_run(None, '*400,0*', ['400', '400'], no_build=True) |
| |
| # TODO: do this in other passes too, passing their opts into emcc |
| if self.emcc_args == []: |
| # emcc should build in dlmalloc automatically, and do all the sign correction etc. for it |
| |
| try_delete('src.cpp.o.js') |
| run_process([EMCC, path_from_root('tests', 'dlmalloc_test.c'), '-s', 'INITIAL_MEMORY=128MB', '-o', 'src.cpp.o.js'], stdout=PIPE, stderr=self.stderr_redirect) |
| |
| self.do_run(None, '*1,0*', ['200', '1'], no_build=True) |
| self.do_run(None, '*400,0*', ['400', '400'], no_build=True) |
| |
| # The same for new and all its variants |
| src = open(path_from_root('tests', 'new.cpp')).read() |
| for new, delete in [ |
| ('malloc(100)', 'free'), |
| ('new char[100]', 'delete[]'), |
| ('new Structy', 'delete'), |
| ('new int', 'delete'), |
| ('new Structy[10]', 'delete[]'), |
| ]: |
| self.do_run(src.replace('{{{ NEW }}}', new).replace('{{{ DELETE }}}', delete), '*1,0*') |
| |
| @no_asan('asan also changes malloc, and that ends up linking in new twice') |
| def test_dlmalloc_partial(self): |
| # present part of the symbols of dlmalloc, not all |
| src = open(path_from_root('tests', 'new.cpp')).read().replace('{{{ NEW }}}', 'new int').replace('{{{ DELETE }}}', 'delete') + ''' |
| #include <new> |
| |
| void * |
| operator new(size_t size) throw(std::bad_alloc) |
| { |
| printf("new %d!\\n", size); |
| return malloc(size); |
| } |
| ''' |
| self.do_run(src, 'new 4!\n*1,0*') |
| |
| @no_asan('asan also changes malloc, and that ends up linking in new twice') |
| def test_dlmalloc_partial_2(self): |
| if 'SAFE_HEAP' in str(self.emcc_args): |
| self.skipTest('we do unsafe stuff here') |
| # present part of the symbols of dlmalloc, not all. malloc is harder to link than new which is weak. |
| self.do_run_in_out_file_test('tests', 'core', 'test_dlmalloc_partial_2', assert_returncode=None) |
| |
| def test_libcxx(self): |
| self.do_run(open(path_from_root('tests', 'hashtest.cpp')).read(), |
| 'june -> 30\nPrevious (in alphabetical order) is july\nNext (in alphabetical order) is march') |
| |
| self.do_run(''' |
| #include <set> |
| #include <stdio.h> |
| int main() { |
| std::set<int> *fetchOriginatorNums = new std::set<int>(); |
| fetchOriginatorNums->insert(171); |
| printf("hello world\\n"); |
| return 0; |
| } |
| ''', 'hello world') |
| |
| def test_typeid(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_typeid') |
| |
| def test_static_variable(self): |
| # needs atexit |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_static_variable') |
| |
| def test_fakestat(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_fakestat') |
| |
| def test_mmap(self): |
| self.set_setting('INITIAL_MEMORY', 128 * 1024 * 1024) |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_mmap') |
| |
| def test_mmap_file(self): |
| for extra_args in [[], ['--no-heap-copy']]: |
| self.emcc_args += ['--embed-file', 'data.dat'] + extra_args |
| x = 'data from the file........' |
| s = '' |
| while len(s) < 9000: |
| if len(s) + len(x) < 9000: |
| s += x |
| continue |
| s += '.' |
| assert len(s) == 9000 |
| create_test_file('data.dat', s) |
| src = open(path_from_root('tests', 'mmap_file.c')).read() |
| self.do_run(src, '*\n' + s[0:20] + '\n' + s[4096:4096 + 20] + '\n*\n') |
| |
| def test_cubescript(self): |
| # uses register keyword |
| self.emcc_args.append('-std=c++03') |
| if self.run_name == 'asm3': |
| self.emcc_args += ['--closure', '1'] # Use closure here for some additional coverage |
| |
| self.emcc_args = [x for x in self.emcc_args if x != '-g'] # remove -g, so we have one test without it by default |
| |
| def test(): |
| self.do_run(path_from_root('tests', 'cubescript'), '*\nTemp is 33\n9\n5\nhello, everyone\n*', main_file='command.cpp') |
| |
| test() |
| |
| def count_relocations(): |
| generated = open('src.cpp.o.js').read() |
| generated = re.sub(r'\n+[ \n]*\n+', '\n', generated) |
| start = '\nfunction __apply_relocations() {' |
| relocs_start = generated.find(start) |
| if relocs_start == -1: |
| return "", 0 |
| relocs_start += len(start) |
| relocs_end = generated.find('\n}', relocs_start) |
| relocs = generated[relocs_start:relocs_end] |
| num_relocs = relocs.count('\n') |
| return relocs, num_relocs |
| |
| # TODO: wrappers for wasm modules |
| if not self.get_setting('WASM') and not self.is_wasm_backend(): |
| print('relocatable') |
| assert self.get_setting('RELOCATABLE') == self.get_setting('EMULATED_FUNCTION_POINTERS') == 0 |
| self.set_setting('RELOCATABLE', 1) |
| self.set_setting('EMULATED_FUNCTION_POINTERS', 1) |
| test() |
| self.set_setting('RELOCATABLE', 0) |
| self.set_setting('EMULATED_FUNCTION_POINTERS', 0) |
| |
| if self.is_wasm_backend(): |
| print('asyncify') # extra coverage |
| self.emcc_args += ['-s', 'ASYNCIFY=1'] |
| test() |
| |
| @needs_dlfcn |
| def test_relocatable_void_function(self): |
| self.set_setting('RELOCATABLE', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_relocatable_void_function') |
| |
| @wasm_simd |
| def test_wasm_builtin_simd(self): |
| # Improves test readability |
| self.emcc_args.append('-Wno-c++11-narrowing') |
| self.do_run(open(path_from_root('tests', 'test_wasm_builtin_simd.cpp')).read(), 'Success!') |
| self.emcc_args.append('-munimplemented-simd128') |
| self.build(open(path_from_root('tests', 'test_wasm_builtin_simd.cpp')).read(), |
| self.get_dir(), os.path.join(self.get_dir(), 'src.cpp')) |
| |
| @wasm_simd |
| def test_wasm_intrinsics_simd(self): |
| def run(): |
| self.do_run( |
| open(path_from_root('tests', 'test_wasm_intrinsics_simd.c')).read(), |
| 'Success!') |
| # Improves test readability |
| self.emcc_args.append('-Wno-c++11-narrowing') |
| self.emcc_args.extend(['-Wpedantic', '-Werror', '-Wall', '-xc++']) |
| run() |
| self.emcc_args.append('-funsigned-char') |
| run() |
| self.emcc_args.extend(['-munimplemented-simd128', '-xc', '-std=c99']) |
| self.build(open(path_from_root('tests', 'test_wasm_intrinsics_simd.c')).read(), |
| self.get_dir(), os.path.join(self.get_dir(), 'src.cpp')) |
| |
| # Tests invoking the SIMD API via x86 SSE1 xmmintrin.h header (_mm_x() functions) |
| @wasm_simd |
| def test_sse1(self): |
| src = path_from_root('tests', 'sse', 'test_sse1.cpp') |
| run_process([shared.CLANG_CXX, src, '-msse', '-o', 'test_sse1', '-D_CRT_SECURE_NO_WARNINGS=1'] + building.get_native_building_args(), stdout=PIPE) |
| native_result = run_process('./test_sse1', stdout=PIPE, env=building.get_building_env(native=True)).stdout |
| |
| orig_args = self.emcc_args |
| self.emcc_args = orig_args + ['-I' + path_from_root('tests', 'sse'), '-msse'] |
| self.maybe_closure() |
| |
| self.do_run(open(src).read(), native_result) |
| |
| # Tests invoking the SIMD API via x86 SSE2 emmintrin.h header (_mm_x() functions) |
| @wasm_simd |
| def test_sse2(self): |
| src = path_from_root('tests', 'sse', 'test_sse2.cpp') |
| run_process([shared.CLANG_CXX, src, '-msse2', '-Wno-argument-outside-range', '-o', 'test_sse2', '-D_CRT_SECURE_NO_WARNINGS=1'] + building.get_native_building_args(), stdout=PIPE) |
| native_result = run_process('./test_sse2', stdout=PIPE, env=building.get_building_env(native=True)).stdout |
| |
| orig_args = self.emcc_args |
| self.emcc_args = orig_args + ['-I' + path_from_root('tests', 'sse'), '-msse2', '-Wno-argument-outside-range'] |
| self.maybe_closure() |
| self.do_run(open(src).read(), native_result) |
| |
| # Tests invoking the SIMD API via x86 SSE3 pmmintrin.h header (_mm_x() functions) |
| @wasm_simd |
| def test_sse3(self): |
| src = path_from_root('tests', 'sse', 'test_sse3.cpp') |
| run_process([shared.CLANG_CXX, src, '-msse3', '-Wno-argument-outside-range', '-o', 'test_sse3', '-D_CRT_SECURE_NO_WARNINGS=1'] + building.get_native_building_args(), stdout=PIPE) |
| native_result = run_process('./test_sse3', stdout=PIPE, env=building.get_building_env(native=True)).stdout |
| |
| orig_args = self.emcc_args |
| self.emcc_args = orig_args + ['-I' + path_from_root('tests', 'sse'), '-msse3', '-Wno-argument-outside-range'] |
| self.maybe_closure() |
| self.do_run(open(src).read(), native_result) |
| |
| # Tests invoking the SIMD API via x86 SSSE3 tmmintrin.h header (_mm_x() functions) |
| @wasm_simd |
| def test_ssse3(self): |
| src = path_from_root('tests', 'sse', 'test_ssse3.cpp') |
| run_process([shared.CLANG_CXX, src, '-mssse3', '-Wno-argument-outside-range', '-o', 'test_ssse3', '-D_CRT_SECURE_NO_WARNINGS=1'] + building.get_native_building_args(), stdout=PIPE) |
| native_result = run_process('./test_ssse3', stdout=PIPE, env=building.get_building_env(native=True)).stdout |
| |
| orig_args = self.emcc_args |
| self.emcc_args = orig_args + ['-I' + path_from_root('tests', 'sse'), '-mssse3', '-Wno-argument-outside-range'] |
| self.maybe_closure() |
| self.do_run(open(src).read(), native_result) |
| |
| # Tests invoking the SIMD API via x86 SSE4.1 smmintrin.h header (_mm_x() functions) |
| @wasm_simd |
| def test_sse4_1(self): |
| src = path_from_root('tests', 'sse', 'test_sse4_1.cpp') |
| run_process([shared.CLANG_CXX, src, '-msse4.1', '-Wno-argument-outside-range', '-o', 'test_sse4_1', '-D_CRT_SECURE_NO_WARNINGS=1'] + building.get_native_building_args(), stdout=PIPE) |
| native_result = run_process('./test_sse4_1', stdout=PIPE, env=building.get_building_env(native=True)).stdout |
| |
| orig_args = self.emcc_args |
| self.emcc_args = orig_args + ['-I' + path_from_root('tests', 'sse'), '-msse4.1', '-Wno-argument-outside-range'] |
| self.maybe_closure() |
| self.do_run(open(src).read(), native_result) |
| |
| # Tests invoking the SIMD API via x86 SSE4.2 nmmintrin.h header (_mm_x() functions) |
| @wasm_simd |
| def test_sse4_2(self): |
| src = path_from_root('tests', 'sse', 'test_sse4_2.cpp') |
| run_process([shared.CLANG_CXX, src, '-msse4.2', '-Wno-argument-outside-range', '-o', 'test_sse4_2', '-D_CRT_SECURE_NO_WARNINGS=1'] + building.get_native_building_args(), stdout=PIPE) |
| native_result = run_process('./test_sse4_2', stdout=PIPE, env=building.get_building_env(native=True)).stdout |
| |
| orig_args = self.emcc_args |
| self.emcc_args = orig_args + ['-I' + path_from_root('tests', 'sse'), '-msse4.2', '-Wno-argument-outside-range'] |
| self.maybe_closure() |
| self.do_run(open(src).read(), native_result) |
| |
| # Tests invoking the SIMD API via x86 AVX avxintrin.h header (_mm_x() functions) |
| @wasm_simd |
| def test_avx(self): |
| src = path_from_root('tests', 'sse', 'test_avx.cpp') |
| run_process([shared.CLANG_CXX, src, '-mavx', '-Wno-argument-outside-range', '-o', 'test_avx', '-D_CRT_SECURE_NO_WARNINGS=1'] + building.get_native_building_args(), stdout=PIPE) |
| native_result = run_process('./test_avx', stdout=PIPE, env=building.get_building_env(native=True)).stdout |
| |
| orig_args = self.emcc_args |
| self.emcc_args = orig_args + ['-I' + path_from_root('tests', 'sse'), '-mavx', '-Wno-argument-outside-range'] |
| self.maybe_closure() |
| self.do_run(open(src).read(), native_result) |
| |
| @no_asan('call stack exceeded on some versions of node') |
| def test_gcc_unmangler(self): |
| self.emcc_args += ['-I' + path_from_root('third_party')] |
| |
| self.do_run(open(path_from_root('third_party', 'gcc_demangler.c')).read(), '*d_demangle(char const*, int, unsigned int*)*', args=['_ZL10d_demanglePKciPj'], assert_returncode=None) |
| |
| @needs_make('make') |
| def test_lua(self): |
| self.emcc_args.remove('-Werror') |
| |
| self.do_run('', |
| 'hello lua world!\n17\n1\n2\n3\n4\n7', |
| args=['-e', '''print("hello lua world!");print(17);for x = 1,4 do print(x) end;print(10-3)'''], |
| libraries=self.get_library(os.path.join('third_party', 'lua'), [os.path.join('src', 'lua.o'), os.path.join('src', 'liblua.a')], make=['make', 'generic'], configure=None), |
| includes=[path_from_root('tests', 'lua')], |
| output_nicerizer=lambda string, err: (string + err).replace('\n\n', '\n').replace('\n\n', '\n')) |
| |
| @no_asan('issues with freetype itself') |
| @needs_make('configure script') |
| @is_slow_test |
| def test_freetype(self): |
| if self.run_name == 'asm2g': |
| # flip for some more coverage here |
| self.set_setting('ALIASING_FUNCTION_POINTERS', 1 - self.get_setting('ALIASING_FUNCTION_POINTERS')) |
| |
| self.add_pre_run("FS.createDataFile('/', 'font.ttf', %s, true, false, false);" % str( |
| list(bytearray(open(path_from_root('tests', 'freetype', 'LiberationSansBold.ttf'), 'rb').read())) |
| )) |
| |
| # Not needed for js, but useful for debugging |
| shutil.copyfile(path_from_root('tests', 'freetype', 'LiberationSansBold.ttf'), 'font.ttf') |
| |
| # Main |
| self.do_run(open(path_from_root('tests', 'freetype', 'main.c')).read(), |
| open(path_from_root('tests', 'freetype', 'ref.txt')).read(), |
| ['font.ttf', 'test!', '150', '120', '25'], |
| libraries=self.get_freetype_library(), |
| includes=[path_from_root('tests', 'third_party', 'freetype', 'include')]) |
| |
| # github issue 324 |
| print('[issue 324]') |
| self.do_run(open(path_from_root('tests', 'freetype', 'main_2.c')).read(), |
| open(path_from_root('tests', 'freetype', 'ref_2.txt')).read(), |
| ['font.ttf', 'w', '32', '32', '25'], |
| libraries=self.get_freetype_library(), |
| includes=[path_from_root('tests', 'third_party', 'freetype', 'include')]) |
| |
| print('[issue 324 case 2]') |
| self.do_run(open(path_from_root('tests', 'freetype', 'main_3.c')).read(), |
| open(path_from_root('tests', 'freetype', 'ref_3.txt')).read(), |
| ['font.ttf', 'W', '32', '32', '0'], |
| libraries=self.get_freetype_library(), |
| includes=[path_from_root('tests', 'third_party', 'freetype', 'include')]) |
| |
| print('[issue 324 case 3]') |
| self.do_run(None, |
| open(path_from_root('tests', 'freetype', 'ref_4.txt')).read(), |
| ['font.ttf', 'ea', '40', '32', '0'], |
| no_build=True) |
| |
| @no_asan('local count too large for VMs') |
| def test_sqlite(self): |
| self.set_setting('DISABLE_EXCEPTION_CATCHING', 1) |
| self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_sqlite3_open', '_sqlite3_close', '_sqlite3_exec', '_sqlite3_free']) |
| if self.get_setting('ASM_JS') == 1 and '-g' in self.emcc_args: |
| print("disabling inlining") # without registerize (which -g disables), we generate huge amounts of code |
| self.set_setting('INLINING_LIMIT', 50) |
| |
| # newer clang has a warning for implicit conversions that lose information, |
| # which happens in sqlite (see #9138) |
| self.emcc_args += ['-Wno-implicit-int-float-conversion'] |
| # temporarily ignore unknown flags, which lets the above flag be used on our CI which doesn't |
| # yet have the new clang with that flag |
| self.emcc_args += ['-Wno-unknown-warning-option'] |
| self.emcc_args += ['-I' + path_from_root('tests', 'third_party', 'sqlite')] |
| |
| src = ''' |
| #define SQLITE_DISABLE_LFS |
| #define LONGDOUBLE_TYPE double |
| #define SQLITE_INT64_TYPE long long int |
| #define SQLITE_THREADSAFE 0 |
| ''' |
| src += open(path_from_root('tests', 'third_party', 'sqlite', 'sqlite3.c')).read() |
| src += open(path_from_root('tests', 'sqlite', 'benchmark.c')).read() |
| self.do_run(src, |
| open(path_from_root('tests', 'sqlite', 'benchmark.txt')).read(), |
| includes=[path_from_root('tests', 'sqlite')], |
| force_c=True) |
| |
| @needs_make('mingw32-make') |
| @is_slow_test |
| @parameterized({ |
| 'cmake': (True,), |
| 'configure': (False,) |
| }) |
| def test_zlib(self, use_cmake): |
| if WINDOWS and not use_cmake: |
| self.skipTest("Windows cannot run configure sh scripts") |
| |
| self.maybe_closure() |
| |
| if self.run_name == 'asm2g': |
| self.emcc_args += ['-g4'] # more source maps coverage |
| if self.run_name == 'asm2f': |
| return self.skipTest('asm2f affects cflags in a way that changes zlib compile flag reporting, so the stdout is different') |
| |
| if use_cmake: |
| make_args = [] |
| configure = [path_from_root('emcmake'), 'cmake', '.'] |
| else: |
| make_args = ['libz.a'] |
| configure = ['sh', './configure'] |
| |
| self.do_run(open(path_from_root('tests', 'third_party', 'zlib', 'example.c')).read(), |
| open(path_from_root('tests', 'core', 'test_zlib.out')).read(), |
| libraries=self.get_library(os.path.join('third_party', 'zlib'), 'libz.a', make_args=make_args, configure=configure), |
| includes=[path_from_root('tests', 'third_party', 'zlib'), 'building', 'zlib'], |
| force_c=True) |
| |
| @needs_make('make') |
| @is_slow_test |
| @parameterized({ |
| 'cmake': (True,), |
| 'autoconf': (False,) |
| }) |
| # Called thus so it runs late in the alphabetical cycle... it is long |
| def test_bullet(self, use_cmake): |
| if WINDOWS and not use_cmake: |
| self.skipTest("Windows cannot run configure sh scripts") |
| |
| self.set_setting('DEAD_FUNCTIONS', ['__ZSt9terminatev']) |
| self.emcc_args += ['-Wno-c++11-narrowing', '-Wno-deprecated-register', '-Wno-writable-strings'] |
| asserts = self.get_setting('ASSERTIONS') |
| |
| # extra testing for ASSERTIONS == 2 |
| self.set_setting('ASSERTIONS', 2 if use_cmake else asserts) |
| |
| self.do_run(open(path_from_root('tests', 'third_party', 'bullet', 'Demos', 'HelloWorld', 'HelloWorld.cpp')).read(), |
| [open(path_from_root('tests', 'bullet', 'output.txt')).read(), # different roundings |
| open(path_from_root('tests', 'bullet', 'output2.txt')).read(), |
| open(path_from_root('tests', 'bullet', 'output3.txt')).read(), |
| open(path_from_root('tests', 'bullet', 'output4.txt')).read()], |
| libraries=self.get_bullet_library(use_cmake), |
| includes=[path_from_root('tests', 'third_party', 'bullet', 'src')]) |
| |
| @no_asan('issues with freetype itself') |
| @needs_make('depends on freetype') |
| @is_slow_test |
| def test_poppler(self): |
| def test(): |
| pdf_data = open(path_from_root('tests', 'poppler', 'paper.pdf'), 'rb').read() |
| create_test_file('paper.pdf.js', str(list(bytearray(pdf_data)))) |
| |
| create_test_file('pre.js', ''' |
| Module.preRun = function() { |
| FS.createDataFile('/', 'paper.pdf', eval(read_('paper.pdf.js')), true, false, false); |
| }; |
| Module.postRun = function() { |
| var FileData = MEMFS.getFileDataAsRegularArray(FS.root.contents['filename-1.ppm']); |
| out("Data: " + JSON.stringify(FileData.map(function(x) { return unSign(x, 8) }))); |
| }; |
| ''') |
| self.emcc_args += ['--pre-js', 'pre.js'] |
| |
| ppm_data = str(list(bytearray(open(path_from_root('tests', 'poppler', 'ref.ppm'), 'rb').read()))) |
| self.do_run('', ppm_data.replace(' ', ''), |
| libraries=self.get_poppler_library(), |
| args=['-scale-to', '512', 'paper.pdf', 'filename']) |
| |
| test() |
| |
| if self.supports_js_dfe(): |
| print("Testing poppler with ELIMINATE_DUPLICATE_FUNCTIONS set to 1", file=sys.stderr) |
| num_original_funcs = self.count_funcs('src.cpp.o.js') |
| self.set_setting('ELIMINATE_DUPLICATE_FUNCTIONS', 1) |
| test() |
| # Make sure that DFE ends up eliminating more than 200 functions (if we can view source) |
| assert (num_original_funcs - self.count_funcs('src.cpp.o.js')) > 200 |
| |
| @needs_make('make') |
| @is_slow_test |
| def test_openjpeg(self): |
| if '-fsanitize=address' in self.emcc_args: |
| self.set_setting('INITIAL_MEMORY', 128 * 1024 * 1024) |
| |
| def line_splitter(data): |
| out = '' |
| counter = 0 |
| |
| for ch in data: |
| out += ch |
| if ch == ' ' and counter > 60: |
| out += '\n' |
| counter = 0 |
| else: |
| counter += 1 |
| |
| return out |
| |
| # remove -g, so we have one test without it by default |
| self.emcc_args = [x for x in self.emcc_args if x != '-g'] |
| |
| original_j2k = path_from_root('tests', 'openjpeg', 'syntensity_lobby_s.j2k') |
| image_bytes = list(bytearray(open(original_j2k, 'rb').read())) |
| create_test_file('pre.js', """ |
| Module.preRun = function() { FS.createDataFile('/', 'image.j2k', %s, true, false, false); }; |
| Module.postRun = function() { |
| out('Data: ' + JSON.stringify(MEMFS.getFileDataAsRegularArray(FS.analyzePath('image.raw').object))); |
| }; |
| """ % line_splitter(str(image_bytes))) |
| |
| shutil.copy(path_from_root('tests', 'third_party', 'openjpeg', 'opj_config.h'), self.get_dir()) |
| |
| lib = self.get_library(os.path.join('third_party', 'openjpeg'), |
| [os.path.sep.join('codec/CMakeFiles/j2k_to_image.dir/index.c.o'.split('/')), |
| os.path.sep.join('codec/CMakeFiles/j2k_to_image.dir/convert.c.o'.split('/')), |
| os.path.sep.join('codec/CMakeFiles/j2k_to_image.dir/__/common/color.c.o'.split('/')), |
| os.path.join('bin', 'libopenjpeg.a')], |
| configure=['cmake', '.'], |
| # configure_args=['--enable-tiff=no', '--enable-jp3d=no', '--enable-png=no'], |
| make_args=[]) # no -j 2, since parallel builds can fail |
| |
| # We use doubles in JS, so we get slightly different values than native code. So we |
| # check our output by comparing the average pixel difference |
| def image_compare(output, err): |
| # Get the image generated by JS, from the JSON.stringify'd array |
| m = re.search(r'\[[\d, -]*\]', output) |
| self.assertIsNotNone(m, 'Failed to find proper image output in: ' + output) |
| # Evaluate the output as a python array |
| js_data = eval(m.group(0)) |
| |
| js_data = [x if x >= 0 else 256 + x for x in js_data] # Our output may be signed, so unsign it |
| |
| # Get the correct output |
| true_data = bytearray(open(path_from_root('tests', 'openjpeg', 'syntensity_lobby_s.raw'), 'rb').read()) |
| |
| # Compare them |
| assert(len(js_data) == len(true_data)) |
| num = len(js_data) |
| diff_total = js_total = true_total = 0 |
| for i in range(num): |
| js_total += js_data[i] |
| true_total += true_data[i] |
| diff_total += abs(js_data[i] - true_data[i]) |
| js_mean = js_total / float(num) |
| true_mean = true_total / float(num) |
| diff_mean = diff_total / float(num) |
| |
| image_mean = 83.265 |
| # print '[image stats:', js_mean, image_mean, true_mean, diff_mean, num, ']' |
| assert abs(js_mean - image_mean) < 0.01, [js_mean, image_mean] |
| assert abs(true_mean - image_mean) < 0.01, [true_mean, image_mean] |
| assert diff_mean < 0.01, diff_mean |
| |
| return output |
| |
| self.emcc_args += ['--minify', '0'] # to compare the versions |
| self.emcc_args += ['--pre-js', 'pre.js'] |
| |
| def do_test(): |
| self.do_run(open(path_from_root('tests', 'third_party', 'openjpeg', 'codec', 'j2k_to_image.c')).read(), |
| 'Successfully generated', # The real test for valid output is in image_compare |
| '-i image.j2k -o image.raw'.split(' '), |
| libraries=lib, |
| includes=[path_from_root('tests', 'third_party', 'openjpeg', 'libopenjpeg'), |
| path_from_root('tests', 'third_party', 'openjpeg', 'codec'), |
| path_from_root('tests', 'third_party', 'openjpeg', 'common'), |
| os.path.join(self.get_build_dir(), 'openjpeg')], |
| force_c=True, |
| assert_returncode=0, |
| output_nicerizer=image_compare) |
| |
| do_test() |
| |
| # extra testing |
| if self.get_setting('ALLOW_MEMORY_GROWTH') == 1: |
| print('no memory growth', file=sys.stderr) |
| self.set_setting('ALLOW_MEMORY_GROWTH', 0) |
| do_test() |
| |
| @no_wasm_backend("uses bitcode compiled with asmjs, and we don't have unified triples") |
| def test_python(self): |
| self.set_setting('EMULATE_FUNCTION_POINTER_CASTS', 1) |
| # The python build contains several undefined symbols |
| self.set_setting('ERROR_ON_UNDEFINED_SYMBOLS', 0) |
| |
| bitcode = path_from_root('tests', 'third_party', 'python', 'python.bc') |
| pyscript = dedent('''\ |
| print '***' |
| print "hello python world!" |
| print [x*2 for x in range(4)] |
| t=2 |
| print 10-3-t |
| print (lambda x: x*2)(11) |
| print '%f' % 5.47 |
| print {1: 2}.keys() |
| print '***' |
| ''') |
| pyoutput = '***\nhello python world!\n[0, 2, 4, 6]\n5\n22\n5.470000\n[1]\n***' |
| |
| for lto in [0, 1]: |
| print('lto:', lto) |
| if lto == 1: |
| self.emcc_args += ['--llvm-lto', '1'] |
| self.do_run_object(bitcode, pyoutput, args=['-S', '-c', pyscript]) |
| |
| def test_lifetime(self): |
| self.do_ll_run(path_from_root('tests', 'lifetime.ll'), 'hello, world!\n') |
| if '-O1' in self.emcc_args or '-O2' in self.emcc_args: |
| # lifetime stuff and their vars must be culled |
| self.assertNotContained('a18', open('lifetime.ll.o.js').read()) |
| |
| # Test cases in separate files. Note that these files may contain invalid .ll! |
| # They are only valid enough for us to read for test purposes, not for llvm-as |
| # to process. |
| @no_wasm_backend("uses bitcode compiled with asmjs, and we don't have unified triples") |
| @is_slow_test |
| def test_zzz_cases(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| |
| # These tests don't end up linking with libc due to a behaviour in emcc |
| # where the llvm-link step is skipped when the input is a single |
| # object file. Since most of them `printf` (which comes from JS) but |
| # depends on `strlen` (which comes from musl) these tests almost all |
| # have an undefined `strlen`, which happens to not get called. |
| # TODO(sbc): Remove the special case from emcc what bypasses llvm-link |
| # and then remove this line? |
| self.set_setting('ERROR_ON_UNDEFINED_SYMBOLS', 0) |
| self.set_setting('WARN_ON_UNDEFINED_SYMBOLS', 0) |
| self.emcc_args.remove('-Werror') |
| |
| emcc_args = self.emcc_args |
| |
| # The following tests link to libc, whereas others link with -nostdlib |
| needs_stdlib = [ |
| 'muli33_ta2', 'philoop_ta2', 'uadd_overflow_64_ta2', 'i64toi8star', |
| 'legalizer_ta2', 'quotedlabel', 'alignedunaligned', 'sillybitcast', |
| 'invokeundef', 'loadbitcastgep', 'sillybitcast2', 'legalizer_b_ta2', |
| 'emptystruct', 'entry3', 'atomicrmw_i64', 'atomicrmw_b_i64', |
| 'invoke_byval', 'i24_ce_fastcomp', |
| ] |
| |
| need_no_error_on_undefined_symbols = [ |
| 'unsanitized_declare' |
| ] |
| |
| skip_tests = [ |
| # invalid ir |
| 'aliasbitcast', 'structparam', 'issue_39', 'phinonexist', 'oob_ta2', 'phiself', 'invokebitcast', |
| # pnacl limitations in ExpandStructRegs |
| 'structphiparam', 'callwithstructural_ta2', 'callwithstructural64_ta2', 'structinparam', |
| # pnacl limitations in ExpandGetElementPtr |
| '2xi40', |
| # current fastcomp limitations FIXME |
| 'quoted', |
| # assumes malloc exists in JS |
| 'llvm_assume', 'longjmp_tiny', 'longjmp_tiny_invoke', 'longjmp_tiny_invoke_phi', |
| 'longjmp_tiny_keepem', 'longjmp_tiny_keepem_cond', 'longjmp_tiny_phi', 'longjmp_tiny_phi2', |
| ] |
| skip_wasm = [ |
| # casts a function pointer from (i32, i32)* to (i64)*, which happens to work in asm.js but is a general function pointer undefined behavior |
| 'call_inttoptr_i64', |
| ] |
| |
| names = glob.glob(path_from_root('tests', 'cases', '*.ll')) |
| names.sort() |
| for name in names: |
| shortname = os.path.splitext(name)[0] |
| # TODO: test only worked in non-fastcomp (well, these cases) |
| basename = os.path.basename(shortname) |
| if basename in skip_tests: |
| continue |
| if self.is_wasm() and basename in skip_wasm: |
| continue |
| |
| if '_noasm' in shortname and self.get_setting('ASM_JS'): |
| print('case "%s" not relevant for asm.js' % shortname) |
| continue |
| |
| if basename in need_no_error_on_undefined_symbols: |
| self.set_setting('ERROR_ON_UNDEFINED_SYMBOLS', 0) |
| |
| print("Testing case '%s'..." % basename) |
| output_file = path_from_root('tests', 'cases', shortname + '.txt') |
| if os.path.exists(output_file): |
| output = open(output_file).read() |
| else: |
| output = 'hello, world!' |
| |
| if output.rstrip() != 'skip': |
| self.emcc_args = list(emcc_args) |
| if basename in needs_stdlib: |
| self.set_setting('FILESYSTEM', 1) |
| else: |
| self.emcc_args.append('-nostdlib') |
| # no libc is linked in; with FILESYSTEM=0 we have a chance at printfing anyhow |
| self.set_setting('FILESYSTEM', 0) |
| if os.path.exists(shortname + '.emcc'): |
| self.emcc_args += json.loads(open(shortname + '.emcc').read()) |
| |
| self.do_ll_run(path_from_root('tests', 'cases', name), output, assert_returncode=None) |
| |
| # Optional source checking, a python script that gets a global generated with the source |
| src_checker = path_from_root('tests', 'cases', shortname + '.py') |
| if os.path.exists(src_checker): |
| generated = open('src.cpp.o.js').read() # noqa |
| exec(open(src_checker).read()) |
| |
| @no_asan('call stack exceeded on some versions of node') |
| @is_slow_test |
| def test_fuzz(self): |
| self.emcc_args += ['-I' + path_from_root('tests', 'fuzz', 'include'), '-w'] |
| |
| skip_lto_tests = [ |
| # LLVM LTO bug |
| '19.c', '18.cpp', |
| # puts exists before LTO, but is not used; LTO cleans it out, but then creates uses to it (printf=>puts) XXX https://llvm.org/bugs/show_bug.cgi?id=23814 |
| '23.cpp' |
| ] |
| |
| def run_all(x): |
| print(x) |
| for name in sorted(glob.glob(path_from_root('tests', 'fuzz', '*.c')) + glob.glob(path_from_root('tests', 'fuzz', '*.cpp'))): |
| # if os.path.basename(name) != '4.c': |
| # continue |
| if 'newfail' in name: |
| continue |
| if os.path.basename(name).startswith('temp_fuzzcode'): |
| continue |
| # pnacl legalization issue, see https://code.google.com/p/nativeclient/issues/detail?id=4027 |
| if x == 'lto' and self.run_name in ['default', 'asm2f'] and os.path.basename(name) in ['8.c']: |
| continue |
| if x == 'lto' and self.run_name == 'default' and os.path.basename(name) in skip_lto_tests: |
| continue |
| if x == 'lto' and os.path.basename(name) in ['21.c']: |
| continue # LLVM LTO bug |
| |
| print(name) |
| if name.endswith('.cpp'): |
| self.emcc_args.append('-std=c++03') |
| self.do_run(open(path_from_root('tests', 'fuzz', name)).read(), |
| open(path_from_root('tests', 'fuzz', name + '.txt')).read(), force_c=name.endswith('.c'), assert_returncode=None) |
| if name.endswith('.cpp'): |
| self.emcc_args.remove('-std=c++03') |
| |
| run_all('normal') |
| |
| self.emcc_args += ['--llvm-lto', '1'] |
| |
| run_all('lto') |
| |
| def test_autodebug_bitcode(self): |
| if self.is_wasm_backend() and '-flto' not in self.get_emcc_args(): |
| return self.skipTest('must use bitcode object files for bitcode autodebug') |
| |
| self.emcc_args += ['--llvm-opts', '0'] |
| |
| # Autodebug the code |
| def do_autodebug(filename): |
| building.llvm_dis(filename + '.o', filename + '.ll') |
| run_process([PYTHON, AUTODEBUGGER, filename + '.ll', filename + '.auto.ll']) |
| # rebuild .bc |
| # TODO: use code in do_autodebug_post for this |
| self.prep_ll_file(filename, filename + '.auto.ll', force_recompile=True) |
| |
| # Run a test that should work, generating some code |
| test_path = path_from_root('tests', 'core', 'test_structs') |
| src = test_path + '.c' |
| output = test_path + '.out' |
| # Add an ll hook, to force ll generation |
| self.do_run_from_file(src, output, build_ll_hook=lambda x: False) |
| |
| filename = 'src.c' |
| do_autodebug(filename) |
| |
| # Compare to each other, and to expected output |
| self.do_ll_run(filename + '.auto.ll', 'AD:-1,1') |
| |
| # Test using build_ll_hook |
| src = ''' |
| #include <stdio.h> |
| |
| char cache[256], *next = cache; |
| |
| int main() |
| { |
| cache[10] = 25; |
| next[20] = 51; |
| int x = cache[10]; |
| double y = 11.52; |
| printf("*%d,%d,%.2f*\\n", x, cache[20], y); |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'AD:-1,1', build_ll_hook=do_autodebug) |
| |
| @also_with_standalone_wasm(wasm2c=True, impure=True) |
| @no_asan('autodebug logging interferes with asan') |
| @no_fastcomp('autodebugging wasm is only supported in the wasm backend') |
| @with_env_modify({'EMCC_AUTODEBUG': '1'}) |
| def test_autodebug_wasm(self): |
| # Autodebug does not work with too much shadow memory. |
| # Memory consumed by autodebug depends on the size of the WASM linear memory. |
| # With a large shadow memory, the JS engine runs out of memory. |
| if '-fsanitize=address' in self.emcc_args: |
| self.set_setting('ASAN_SHADOW_SIZE', 16 * 1024 * 1024) |
| |
| # test that the program both works and also emits some of the logging |
| # (but without the specific output, as it is logging the actual locals |
| # used and so forth, which will change between opt modes and updates of |
| # llvm etc.) |
| def check(out, err): |
| for msg in ['log_execution', 'get_i32', 'set_i32', 'load_ptr', 'load_val', 'store_ptr', 'store_val']: |
| self.assertIn(msg, out) |
| return out + err |
| |
| self.do_run(open(path_from_root('tests', 'core', 'test_autodebug.c')).read(), |
| 'success', output_nicerizer=check) |
| |
| ### Integration tests |
| |
| @sync |
| def test_ccall(self): |
| self.emcc_args.append('-Wno-return-stack-address') |
| self.set_setting('EXTRA_EXPORTED_RUNTIME_METHODS', ['ccall', 'cwrap']) |
| create_test_file('post.js', ''' |
| out('*'); |
| var ret; |
| ret = Module['ccall']('get_int', 'number'); out([typeof ret, ret].join(',')); |
| ret = ccall('get_float', 'number'); out([typeof ret, ret.toFixed(2)].join(',')); |
| ret = ccall('get_bool', 'boolean'); out([typeof ret, ret].join(',')); |
| ret = ccall('get_string', 'string'); out([typeof ret, ret].join(',')); |
| ret = ccall('print_int', null, ['number'], [12]); out(typeof ret); |
| ret = ccall('print_float', null, ['number'], [14.56]); out(typeof ret); |
| ret = ccall('print_bool', null, ['boolean'], [true]); out(typeof ret); |
| ret = ccall('print_string', null, ['string'], ["cheez"]); out(typeof ret); |
| ret = ccall('print_string', null, ['array'], [[97, 114, 114, 45, 97, 121, 0]]); out(typeof ret); // JS array |
| ret = ccall('print_string', null, ['array'], [new Uint8Array([97, 114, 114, 45, 97, 121, 0])]); out(typeof ret); // typed array |
| ret = ccall('multi', 'number', ['number', 'number', 'number', 'string'], [2, 1.4, 3, 'more']); out([typeof ret, ret].join(',')); |
| var p = ccall('malloc', 'pointer', ['number'], [4]); |
| setValue(p, 650, 'i32'); |
| ret = ccall('pointer', 'pointer', ['pointer'], [p]); out([typeof ret, getValue(ret, 'i32')].join(',')); |
| out('*'); |
| // part 2: cwrap |
| var noThirdParam = Module['cwrap']('get_int', 'number'); |
| out(noThirdParam()); |
| var multi = Module['cwrap']('multi', 'number', ['number', 'number', 'number', 'string']); |
| out(multi(2, 1.4, 3, 'atr')); |
| out(multi(8, 5.4, 4, 'bret')); |
| out('*'); |
| // part 3: avoid stack explosion and check it's restored correctly |
| for (var i = 0; i < TOTAL_STACK/60; i++) { |
| ccall('multi', 'number', ['number', 'number', 'number', 'string'], [0, 0, 0, '123456789012345678901234567890123456789012345678901234567890']); |
| } |
| out('stack is ok.'); |
| ccall('call_ccall_again', null); |
| ''') |
| self.emcc_args += ['--post-js', 'post.js'] |
| |
| self.set_setting('EXPORTED_FUNCTIONS', ['_get_int', '_get_float', '_get_bool', '_get_string', '_print_int', '_print_float', '_print_bool', '_print_string', '_multi', '_pointer', '_call_ccall_again', '_malloc']) |
| self.do_run_in_out_file_test('tests', 'core', 'test_ccall') |
| |
| if '-O2' in self.emcc_args and '-g' not in self.emcc_args: |
| print('with closure') |
| self.emcc_args += ['--closure', '1'] |
| self.do_run_in_out_file_test('tests', 'core', 'test_ccall') |
| |
| def test_EXTRA_EXPORTED_RUNTIME_METHODS(self): |
| self.do_run_in_out_file_test('tests', 'core', 'EXTRA_EXPORTED_RUNTIME_METHODS') |
| # test dyncall (and other runtime methods in support.js) can be exported |
| self.emcc_args += ['-DEXPORTED'] |
| self.set_setting('EXTRA_EXPORTED_RUNTIME_METHODS', ['dynCall', 'addFunction', 'lengthBytesUTF8', 'getTempRet0', 'setTempRet0']) |
| self.do_run_in_out_file_test('tests', 'core', 'EXTRA_EXPORTED_RUNTIME_METHODS') |
| |
| @no_fastcomp('fails mysteriously on fastcomp (dynCall_viji is not defined); ignored, because fastcomp is deprecated') |
| @no_minimal_runtime('MINIMAL_RUNTIME does not blindly export all symbols to Module to save code size') |
| def test_dyncall_specific(self): |
| emcc_args = self.emcc_args[:] |
| for which, exported_runtime_methods in [ |
| ('DIRECT', []), |
| ('EXPORTED', []), |
| ('FROM_OUTSIDE', ['dynCall_viji']) |
| ]: |
| print(which) |
| self.emcc_args = emcc_args + ['-D' + which] |
| self.set_setting('EXTRA_EXPORTED_RUNTIME_METHODS', exported_runtime_methods) |
| self.do_run_in_out_file_test('tests', 'core', 'dyncall_specific') |
| |
| def test_getValue_setValue(self): |
| # these used to be exported, but no longer are by default |
| def test(output_prefix='', args=[]): |
| old = self.emcc_args[:] |
| self.emcc_args += args |
| self.do_run(open(path_from_root('tests', 'core', 'getValue_setValue.cpp')).read(), |
| open(path_from_root('tests', 'core', 'getValue_setValue' + output_prefix + '.txt')).read(), assert_returncode=None) |
| self.emcc_args = old |
| # see that direct usage (not on module) works. we don't export, but the use |
| # keeps it alive through JSDCE |
| test(args=['-DDIRECT']) |
| # see that with assertions, we get a nice error message |
| self.set_setting('EXTRA_EXPORTED_RUNTIME_METHODS', []) |
| self.set_setting('ASSERTIONS', 1) |
| test('_assert') |
| self.set_setting('ASSERTIONS', 0) |
| # see that when we export them, things work on the module |
| self.set_setting('EXTRA_EXPORTED_RUNTIME_METHODS', ['getValue', 'setValue']) |
| test() |
| |
| def test_FS_exports(self): |
| # these used to be exported, but no longer are by default |
| for use_files in (0, 1): |
| print(use_files) |
| |
| def test(output_prefix='', args=[], assert_returncode=None): |
| if use_files: |
| args += ['-DUSE_FILES'] |
| print(args) |
| old = self.emcc_args[:] |
| self.emcc_args += args |
| self.do_run(open(path_from_root('tests', 'core', 'FS_exports.cpp')).read(), |
| (open(path_from_root('tests', 'core', 'FS_exports' + output_prefix + '.txt')).read(), |
| open(path_from_root('tests', 'core', 'FS_exports' + output_prefix + '_2.txt')).read()), |
| assert_returncode=assert_returncode) |
| self.emcc_args = old |
| |
| # see that direct usage (not on module) works. we don't export, but the use |
| # keeps it alive through JSDCE |
| test(args=['-DDIRECT', '-s', 'FORCE_FILESYSTEM=1']) |
| # see that with assertions, we get a nice error message |
| self.set_setting('EXTRA_EXPORTED_RUNTIME_METHODS', []) |
| self.set_setting('ASSERTIONS', 1) |
| test('_assert', assert_returncode=None) |
| self.set_setting('ASSERTIONS', 0) |
| # see that when we export them, things work on the module |
| self.set_setting('EXTRA_EXPORTED_RUNTIME_METHODS', ['FS_createDataFile']) |
| test(args=['-s', 'FORCE_FILESYSTEM=1']) |
| |
| def test_legacy_exported_runtime_numbers(self): |
| # these used to be exported, but no longer are by default |
| |
| def test(output_prefix='', args=[]): |
| old = self.emcc_args[:] |
| self.emcc_args += args |
| self.do_run(open(path_from_root('tests', 'core', 'legacy_exported_runtime_numbers.cpp')).read(), |
| open(path_from_root('tests', 'core', 'legacy_exported_runtime_numbers' + output_prefix + '.txt')).read(), assert_returncode=None) |
| self.emcc_args = old |
| |
| # see that direct usage (not on module) works. we don't export, but the use |
| # keeps it alive through JSDCE |
| test(args=['-DDIRECT']) |
| # see that with assertions, we get a nice error message |
| self.set_setting('EXTRA_EXPORTED_RUNTIME_METHODS', []) |
| self.set_setting('ASSERTIONS', 1) |
| test('_assert') |
| self.set_setting('ASSERTIONS', 0) |
| # see that when we export them, things work on the module |
| self.set_setting('EXTRA_EXPORTED_RUNTIME_METHODS', ['ALLOC_DYNAMIC']) |
| test() |
| |
| @no_wasm_backend('DEAD_FUNCTIONS elimination is done by the JSOptimizer') |
| def test_dead_functions(self): |
| src = r''' |
| #include <stdio.h> |
| extern "C" { |
| __attribute__((noinline)) int unused(int x) { |
| volatile int y = x; |
| return y; |
| } |
| } |
| int main(int argc, char **argv) { |
| printf("*%d*\n", argc > 1 ? unused(1) : 2); |
| return 0; |
| } |
| ''' |
| |
| # Sanity check that it works and the dead function is emitted |
| self.do_run(src, '*1*', args=['x']) |
| js = open('src.cpp.o.js').read() |
| if self.run_name in ['default', 'asm2g']: |
| assert 'function _unused($' in js |
| self.do_run(None, '*2*', no_build=True) |
| |
| # Kill off the dead function, and check a code path using it aborts |
| self.set_setting('DEAD_FUNCTIONS', ['_unused']) |
| self.do_run(src, '*2*') |
| self.do_run(None, 'abort(', args=['x'], no_build=True, assert_returncode=None) |
| |
| # Kill off a library function, check code aborts |
| self.set_setting('DEAD_FUNCTIONS', ['_printf']) |
| self.do_run(src, 'abort(', assert_returncode=None) |
| self.do_run(None, 'abort(', args=['x'], no_build=True, assert_returncode=None) |
| |
| def test_response_file(self): |
| response_data = '-o %s/response_file.js %s' % (self.get_dir(), path_from_root('tests', 'hello_world.cpp')) |
| create_test_file('rsp_file', response_data.replace('\\', '\\\\')) |
| run_process([EMCC, "@rsp_file"] + self.get_emcc_args()) |
| self.do_run('response_file.js', 'hello, world', no_build=True) |
| |
| self.assertContained('response file not found: foo.txt', self.expect_fail([EMCC, '@foo.txt'])) |
| |
| def test_linker_response_file(self): |
| objfile = 'response_file.o' |
| run_process([EMCC, '-c', path_from_root('tests', 'hello_world.cpp'), '-o', objfile] + self.get_emcc_args()) |
| # This should expand into -Wl,--start-group <objfile> -Wl,--end-group |
| response_data = '--start-group ' + objfile + ' --end-group' |
| create_test_file('rsp_file', response_data.replace('\\', '\\\\')) |
| run_process([EMCC, "-Wl,@rsp_file", '-o', 'response_file.o.js'] + self.get_emcc_args()) |
| self.do_run('response_file.o.js', 'hello, world', no_build=True) |
| |
| def test_exported_response(self): |
| src = r''' |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <emscripten.h> |
| |
| extern "C" { |
| int other_function() { return 5; } |
| } |
| |
| int main() { |
| int x = EM_ASM_INT({ return Module._other_function() }); |
| emscripten_run_script_string(""); // Add a reference to a symbol that exists in src/deps_info.json to uncover issue #2836 in the test suite. |
| printf("waka %d!\n", x); |
| return 0; |
| } |
| ''' |
| create_test_file('exps', '["_main","_other_function"]') |
| |
| self.emcc_args += ['-s', 'EXPORTED_FUNCTIONS=@exps'] |
| self.do_run(src, '''waka 5!''') |
| assert 'other_function' in open('src.cpp.o.js').read() |
| |
| def test_large_exported_response(self): |
| src = r''' |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <emscripten.h> |
| |
| extern "C" { |
| ''' |
| |
| js_funcs = [] |
| num_exports = 5000 |
| count = 0 |
| while count < num_exports: |
| src += 'int exported_func_from_response_file_%d () { return %d;}\n' % (count, count) |
| js_funcs.append('_exported_func_from_response_file_%d' % count) |
| count += 1 |
| |
| src += r''' |
| } |
| |
| int main() { |
| int x = EM_ASM_INT({ return Module._exported_func_from_response_file_4999() }); |
| emscripten_run_script_string(""); // Add a reference to a symbol that exists in src/deps_info.json to uncover issue #2836 in the test suite. |
| printf("waka %d!\n", x); |
| return 0; |
| } |
| ''' |
| |
| js_funcs.append('_main') |
| exported_func_json_file = 'large_exported_response.json' |
| create_test_file(exported_func_json_file, json.dumps(js_funcs)) |
| |
| self.emcc_args += ['-s', 'EXPORTED_FUNCTIONS=@' + exported_func_json_file] |
| self.do_run(src, '''waka 4999!''') |
| assert '_exported_func_from_response_file_1' in open('src.cpp.o.js').read() |
| |
| @sync |
| def test_add_function(self): |
| self.set_setting('INVOKE_RUN', 0) |
| self.set_setting('RESERVED_FUNCTION_POINTERS', 1) |
| self.set_setting('EXPORTED_RUNTIME_METHODS', ['callMain']) |
| src = path_from_root('tests', 'interop', 'test_add_function.cpp') |
| post_js = path_from_root('tests', 'interop', 'test_add_function_post.js') |
| self.emcc_args += ['--post-js', post_js] |
| |
| print('basics') |
| self.do_run_in_out_file_test('tests', 'interop', 'test_add_function') |
| |
| if '-g' not in self.emcc_args and not self.is_wasm_backend(): |
| print('with --closure') |
| old = list(self.emcc_args) |
| self.emcc_args += ['--closure', '1'] |
| self.do_run_in_out_file_test('tests', 'interop', 'test_add_function') |
| self.emcc_args = old |
| print(old) |
| |
| print('with ALIASING_FUNCTION_POINTERS') |
| self.set_setting('ALIASING_FUNCTION_POINTERS', 1) |
| self.do_run_in_out_file_test('tests', 'interop', 'test_add_function') |
| self.clear_setting('ALIASING_FUNCTION_POINTERS') |
| |
| print('with RESERVED_FUNCTION_POINTERS=0') |
| self.set_setting('RESERVED_FUNCTION_POINTERS', 0) |
| |
| if self.is_wasm_backend(): |
| self.do_run(open(src).read(), 'Unable to grow wasm table', assert_returncode=None) |
| print('- with table growth') |
| self.set_setting('ALLOW_TABLE_GROWTH', 1) |
| self.emcc_args += ['-DGROWTH'] |
| # enable costly assertions to verify correct table behavior |
| self.set_setting('ASSERTIONS', 2) |
| self.do_run_in_out_file_test('tests', 'interop', 'test_add_function') |
| else: |
| self.do_run(open(src).read(), 'Finished up all reserved function pointers. Use a higher value for RESERVED_FUNCTION_POINTERS.', assert_returncode=None) |
| self.assertNotContained('jsCall_', open('src.cpp.o.js').read()) |
| |
| if not self.get_setting('WASM') and not self.is_wasm_backend(): |
| # with emulation, we don't need to reserve, except with wasm where |
| # we still do. |
| print('- with function pointer emulation') |
| self.set_setting('EMULATED_FUNCTION_POINTERS', 1) |
| self.do_run_in_out_file_test('tests', 'interop', 'test_add_function') |
| |
| def test_getFuncWrapper_sig_alias(self): |
| src = r''' |
| #include <stdio.h> |
| #include <emscripten.h> |
| |
| void func1(int a) { |
| printf("func1\n"); |
| } |
| void func2(int a, int b) { |
| printf("func2\n"); |
| } |
| |
| int main() { |
| EM_ASM({ |
| getFuncWrapper($0, 'vi')(0); |
| getFuncWrapper($1, 'vii')(0, 0); |
| }, func1, func2); |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'func1\nfunc2\n') |
| |
| def test_emulate_function_pointer_casts(self): |
| self.set_setting('EMULATE_FUNCTION_POINTER_CASTS', 1) |
| |
| self.do_run(open(path_from_root('tests', 'core', 'test_emulate_function_pointer_casts.cpp')).read(), |
| ('|1.266,1|', # asm.js, double <-> int |
| '|1.266,1413754136|')) # wasm, reinterpret the bits |
| |
| @no_wasm2js('TODO: nicely printed names in wasm2js') |
| @parameterized({ |
| 'normal': ([],), |
| 'noexcept': (['-fno-exceptions'],) |
| }) |
| def test_demangle_stacks(self, extra_args): |
| self.emcc_args += extra_args |
| self.set_setting('DEMANGLE_SUPPORT', 1) |
| self.set_setting('ASSERTIONS', 1) |
| # ensure function names are preserved |
| self.emcc_args += ['--profiling-funcs', '--llvm-opts', '0'] |
| self.do_run_in_out_file_test('tests', 'core', 'test_demangle_stacks', assert_returncode=None) |
| if not self.has_changed_setting('ASSERTIONS'): |
| print('without assertions, the stack is not printed, but a message suggesting assertions is') |
| self.set_setting('ASSERTIONS', 0) |
| self.do_run_in_out_file_test('tests', 'core', 'test_demangle_stacks_noassert', assert_returncode=None) |
| |
| def test_demangle_stacks_symbol_map(self): |
| self.set_setting('DEMANGLE_SUPPORT', 1) |
| if '-O' in str(self.emcc_args) and '-O0' not in self.emcc_args and '-O1' not in self.emcc_args and '-g' not in self.emcc_args: |
| self.emcc_args += ['--llvm-opts', '0'] |
| else: |
| self.skipTest("without opts, we don't emit a symbol map") |
| self.emcc_args += ['--emit-symbol-map'] |
| self.do_run(open(path_from_root('tests', 'core', 'test_demangle_stacks.cpp')).read(), 'abort', assert_returncode=None) |
| # make sure the shortened name is the right one |
| full_aborter = None |
| short_aborter = None |
| for line in open('src.cpp.o.js.symbols').readlines(): |
| if ':' not in line: |
| continue |
| # split by the first ':' (wasm backend demangling may include more :'s later on) |
| short, full = line.split(':', 1) |
| if 'Aborter' in full: |
| short_aborter = short |
| full_aborter = full |
| self.assertIsNotNone(full_aborter) |
| self.assertIsNotNone(short_aborter) |
| print('full:', full_aborter, 'short:', short_aborter) |
| if SPIDERMONKEY_ENGINE and os.path.exists(SPIDERMONKEY_ENGINE[0]): |
| output = run_js('src.cpp.o.js', engine=SPIDERMONKEY_ENGINE, stderr=PIPE, full_output=True, assert_returncode=None) |
| # we may see the full one, if -g, or the short one if not |
| if ' ' + short_aborter + ' ' not in output and ' ' + full_aborter + ' ' not in output: |
| # stack traces may also be ' name ' or 'name@' etc |
| if '\n' + short_aborter + ' ' not in output and '\n' + full_aborter + ' ' not in output and 'wasm-function[' + short_aborter + ']' not in output: |
| if '\n' + short_aborter + '@' not in output and '\n' + full_aborter + '@' not in output: |
| self.assertContained(' ' + short_aborter + ' ' + '\n' + ' ' + full_aborter + ' ', output) |
| |
| def test_tracing(self): |
| self.emcc_args += ['--tracing'] |
| self.do_run_in_out_file_test('tests', 'core', 'test_tracing') |
| |
| @no_wasm_backend('https://github.com/emscripten-core/emscripten/issues/9527') |
| def test_eval_ctors(self): |
| if '-O2' not in str(self.emcc_args) or '-O1' in str(self.emcc_args): |
| self.skipTest('need js optimizations') |
| if not self.get_setting('WASM'): |
| self.skipTest('this test uses wasm binaries') |
| |
| orig_args = self.emcc_args |
| |
| print('leave printf in ctor') |
| self.emcc_args = orig_args + ['-s', 'EVAL_CTORS=1'] |
| self.do_run(r''' |
| #include <stdio.h> |
| struct C { |
| C() { printf("constructing!\n"); } // don't remove this! |
| }; |
| C c; |
| int main() {} |
| ''', "constructing!\n") |
| |
| def get_code_size(): |
| if self.is_wasm(): |
| # Use number of functions as a for code size |
| return self.count_wasm_contents('src.cpp.o.wasm', 'funcs') |
| else: |
| return os.path.getsize('src.cpp.o.js') |
| |
| def get_mem_size(): |
| if self.is_wasm(): |
| # Use number of functions as a for code size |
| return self.count_wasm_contents('src.cpp.o.wasm', 'memory-data') |
| if self.uses_memory_init_file(): |
| return os.path.getsize('src.cpp.o.js.mem') |
| |
| # otherwise we ignore memory size |
| return 0 |
| |
| def do_test(test): |
| self.emcc_args = orig_args + ['-s', 'EVAL_CTORS=1'] |
| test() |
| ec_code_size = get_code_size() |
| ec_mem_size = get_mem_size() |
| self.emcc_args = orig_args |
| test() |
| code_size = get_code_size() |
| mem_size = get_mem_size() |
| if mem_size: |
| print('mem: ', mem_size, '=>', ec_mem_size) |
| self.assertGreater(ec_mem_size, mem_size) |
| print('code:', code_size, '=>', ec_code_size) |
| self.assertLess(ec_code_size, code_size) |
| |
| print('remove ctor of just assigns to memory') |
| |
| def test1(): |
| self.do_run(r''' |
| #include <stdio.h> |
| struct C { |
| int x; |
| C() { |
| volatile int y = 10; |
| y++; |
| x = y; |
| } |
| }; |
| C c; |
| int main() { |
| printf("x: %d\n", c.x); |
| } |
| ''', "x: 11\n") |
| |
| do_test(test1) |
| |
| if self.is_wasm_backend(): |
| # The wasm backend currently exports a single initalizer so the ctor |
| # evaluation is all or nothing. As well as that it doesn't currently |
| # do DCE of libcxx symbols (because the are marked as visibility(defaault) |
| # and because of that we end up not being able to eval ctors unless all |
| # libcxx constrcutors can be eval'd |
| return |
| |
| print('libcxx - remove 2 ctors from iostream code') |
| src = open(path_from_root('tests', 'hello_libcxx.cpp')).read() |
| output = 'hello, world!' |
| |
| def test2(): |
| self.do_run(src, output) |
| do_test(test2) |
| |
| print('assertions too') |
| self.set_setting('ASSERTIONS', 1) |
| self.do_run(src, output) |
| self.set_setting('ASSERTIONS', 0) |
| |
| print('remove just some, leave others') |
| |
| def test3(): |
| self.do_run(r''' |
| #include <iostream> |
| #include <string> |
| |
| class std_string { |
| public: |
| std_string(): ptr(nullptr) { std::cout << "std_string()\n"; } |
| std_string(const char* s): ptr(s) { std::cout << "std_string(const char* s)" << std::endl; } |
| std_string(const std_string& s): ptr(s.ptr) { std::cout << "std_string(const std_string& s) " << std::endl; } |
| const char* data() const { return ptr; } |
| private: |
| const char* ptr; |
| }; |
| |
| const std_string txtTestString("212121\0"); |
| const std::string s2text("someweirdtext"); |
| |
| int main() { |
| std::cout << s2text << std::endl; |
| std::cout << txtTestString.data() << std::endl; |
| std::cout << txtTestString.data() << std::endl; |
| return 0; |
| } |
| ''', '''std_string(const char* s) |
| someweirdtext |
| 212121 |
| 212121 |
| ''') # noqa |
| do_test(test3) |
| |
| def test_embind(self): |
| self.emcc_args += ['--bind'] |
| |
| src = r''' |
| #include <stdio.h> |
| #include <emscripten/val.h> |
| |
| using namespace emscripten; |
| |
| int main() { |
| val Math = val::global("Math"); |
| |
| // two ways to call Math.abs |
| printf("abs(-10): %d\n", Math.call<int>("abs", -10)); |
| printf("abs(-11): %d\n", Math["abs"](-11).as<int>()); |
| |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'abs(-10): 10\nabs(-11): 11') |
| |
| def test_embind_2(self): |
| self.emcc_args += ['--bind', '--post-js', 'post.js'] |
| create_test_file('post.js', ''' |
| function printLerp() { |
| out('lerp ' + Module.lerp(100, 200, 66) + '.'); |
| } |
| ''') |
| src = r''' |
| #include <stdio.h> |
| #include <emscripten.h> |
| #include <emscripten/bind.h> |
| using namespace emscripten; |
| int lerp(int a, int b, int t) { |
| return (100 - t) * a + t * b; |
| } |
| EMSCRIPTEN_BINDINGS(my_module) { |
| function("lerp", &lerp); |
| } |
| int main(int argc, char **argv) { |
| EM_ASM(printLerp()); |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'lerp 166') |
| |
| def test_embind_3(self): |
| self.emcc_args += ['--bind', '--post-js', 'post.js'] |
| create_test_file('post.js', ''' |
| function ready() { |
| try { |
| Module.compute(new Uint8Array([1,2,3])); |
| } catch(e) { |
| out(e); |
| } |
| } |
| ''') |
| src = r''' |
| #include <emscripten.h> |
| #include <emscripten/bind.h> |
| using namespace emscripten; |
| int compute(int array[]) { |
| return 0; |
| } |
| EMSCRIPTEN_BINDINGS(my_module) { |
| function("compute", &compute, allow_raw_pointers()); |
| } |
| int main(int argc, char **argv) { |
| EM_ASM(ready()); |
| return 0; |
| } |
| ''' |
| self.do_run(src, 'UnboundTypeError: Cannot call compute due to unbound types: Pi') |
| |
| @no_wasm_backend('long doubles are f128s in wasm backend') |
| def test_embind_4(self): |
| self.emcc_args += ['--bind', '--post-js', 'post.js'] |
| create_test_file('post.js', ''' |
| function printFirstElement() { |
| out(Module.getBufferView()[0]); |
| } |
| ''') |
| src = r''' |
| #include <emscripten.h> |
| #include <emscripten/bind.h> |
| #include <emscripten/val.h> |
| #include <stdio.h> |
| using namespace emscripten; |
| |
| const size_t kBufferSize = 1024; |
| long double buffer[kBufferSize]; |
| val getBufferView(void) { |
| val v = val(typed_memory_view(kBufferSize, buffer)); |
| return v; |
| } |
| EMSCRIPTEN_BINDINGS(my_module) { |
| function("getBufferView", &getBufferView); |
| } |
| |
| int main(int argc, char **argv) { |
| buffer[0] = 107; |
| EM_ASM(printFirstElement()); |
| return 0; |
| } |
| ''' |
| self.do_run(src, '107') |
| |
| def test_embind_5(self): |
| self.emcc_args += ['--bind'] |
| self.do_run_in_out_file_test('tests', 'core', 'test_embind_5') |
| |
| def test_embind_custom_marshal(self): |
| self.emcc_args += ['--bind', '--pre-js', path_from_root('tests', 'embind', 'test_custom_marshal.js')] |
| self.do_run_in_out_file_test('tests', 'embind', 'test_custom_marshal', assert_identical=True) |
| |
| def test_embind_float_constants(self): |
| self.emcc_args += ['--bind'] |
| self.do_run_from_file(path_from_root('tests', 'embind', 'test_float_constants.cpp'), |
| path_from_root('tests', 'embind', 'test_float_constants.out')) |
| |
| def test_embind_negative_constants(self): |
| self.emcc_args += ['--bind'] |
| self.do_run_from_file(path_from_root('tests', 'embind', 'test_negative_constants.cpp'), |
| path_from_root('tests', 'embind', 'test_negative_constants.out')) |
| |
| def test_embind_unsigned(self): |
| self.emcc_args += ['--bind'] |
| self.do_run_from_file(path_from_root('tests', 'embind', 'test_unsigned.cpp'), path_from_root('tests', 'embind', 'test_unsigned.out')) |
| |
| @no_asan('FIXME #11158') |
| def test_embind_val(self): |
| self.emcc_args += ['--bind'] |
| self.do_run_from_file(path_from_root('tests', 'embind', 'test_val.cpp'), path_from_root('tests', 'embind', 'test_val.out')) |
| |
| def test_embind_no_rtti(self): |
| create_test_file('pre.js', ''' |
| Module = {}; |
| Module['postRun'] = function() { |
| out("dotest retured: " + Module.dotest()); |
| }; |
| ''') |
| src = r''' |
| #include <emscripten/bind.h> |
| #include <emscripten/val.h> |
| #include <stdio.h> |
| |
| int main(int argc, char** argv){ |
| printf("418\n"); |
| return 0; |
| } |
| |
| int test() { |
| return 42; |
| } |
| |
| EMSCRIPTEN_BINDINGS(my_module) { |
| emscripten::function("dotest", &test); |
| } |
| ''' |
| self.emcc_args += ['--bind', '-fno-rtti', '-DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0', '--pre-js', 'pre.js'] |
| self.do_run(src, '418\ndotest retured: 42\n') |
| |
| def test_embind_no_rtti_followed_by_rtti(self): |
| create_test_file('pre.js', ''' |
| Module = {}; |
| Module['postRun'] = function() { |
| out("dotest retured: " + Module.dotest()); |
| }; |
| ''') |
| src = r''' |
| #include <emscripten/bind.h> |
| #include <emscripten/val.h> |
| #include <stdio.h> |
| |
| int main(int argc, char** argv){ |
| printf("418\n"); |
| return 0; |
| } |
| |
| int test() { |
| return 42; |
| } |
| |
| EMSCRIPTEN_BINDINGS(my_module) { |
| emscripten::function("dotest", &test); |
| } |
| ''' |
| self.emcc_args += ['--bind', '-fno-rtti', '-frtti', '--pre-js', 'pre.js'] |
| self.do_run(src, '418\ndotest retured: 42\n') |
| |
| @sync |
| def test_webidl(self): |
| if self.run_name == 'asm2': |
| self.emcc_args += ['--closure', '1', '-g1'] # extra testing |
| # avoid closure minified names competing with our test code in the global name space |
| self.set_setting('MODULARIZE', 1) |
| |
| def do_test_in_mode(mode, allow_memory_growth): |
| print('testing mode', mode, ', memory growth =', allow_memory_growth) |
| # Force IDL checks mode |
| os.environ['IDL_CHECKS'] = mode |
| |
| run_process([PYTHON, path_from_root('tools', 'webidl_binder.py'), |
| path_from_root('tests', 'webidl', 'test.idl'), |
| 'glue']) |
| self.assertExists('glue.cpp') |
| self.assertExists('glue.js') |
| |
| # Export things on "TheModule". This matches the typical use pattern of the bound library |
| # being used as Box2D.* or Ammo.*, and we cannot rely on "Module" being always present (closure may remove it). |
| create_test_file('export.js', ''' |
| // test purposes: remove printErr output, whose order is unpredictable when compared to print |
| err = err = function(){}; |
| ''') |
| self.emcc_args += ['-s', 'EXPORTED_FUNCTIONS=["_malloc"]', '--post-js', 'glue.js', '--post-js', 'export.js'] |
| if allow_memory_growth: |
| self.emcc_args += ['-s', 'ALLOW_MEMORY_GROWTH', '-Wno-almost-asm'] |
| shutil.copyfile(path_from_root('tests', 'webidl', 'test.h'), 'test.h') |
| shutil.copyfile(path_from_root('tests', 'webidl', 'test.cpp'), 'test.cpp') |
| src = open('test.cpp').read() |
| |
| def post(filename): |
| with open(filename, 'a') as f: |
| f.write('\n\n') |
| if self.run_name == 'asm2': |
| f.write('var TheModule = Module();\n') |
| else: |
| f.write('var TheModule = Module;\n') |
| f.write('\n\n') |
| if allow_memory_growth: |
| f.write("var isMemoryGrowthAllowed = true;") |
| else: |
| f.write("var isMemoryGrowthAllowed = false;") |
| f.write(open(path_from_root('tests', 'webidl', 'post.js')).read()) |
| f.write('\n\n') |
| |
| output = open(path_from_root('tests', 'webidl', "output_%s.txt" % mode)).read() |
| self.do_run(src, output, post_build=post, output_nicerizer=(lambda out, err: out)) |
| |
| do_test_in_mode('ALL', False) |
| do_test_in_mode('FAST', False) |
| do_test_in_mode('DEFAULT', False) |
| do_test_in_mode('ALL', True) |
| |
| ### Tests for tools |
| |
| @no_wasm2js('TODO: source maps in wasm2js') |
| def test_source_map(self): |
| if '-g' not in self.emcc_args: |
| self.emcc_args.append('-g') |
| |
| src = ''' |
| #include <stdio.h> |
| #include <assert.h> |
| |
| __attribute__((noinline)) int foo() { |
| printf("hi"); // line 6 |
| return 1; // line 7 |
| } |
| |
| int main() { |
| printf("%d", foo()); // line 11 |
| return 0; // line 12 |
| } |
| ''' |
| create_test_file('src.cpp', src) |
| |
| out_filename = 'a.out.js' |
| wasm_filename = 'a.out.wasm' |
| no_maps_filename = 'no-maps.out.js' |
| |
| assert '-g4' not in self.emcc_args |
| building.emcc('src.cpp', |
| self.serialize_settings() + self.emcc_args + self.emcc_args, |
| out_filename) |
| # the file name may find its way into the generated code, so make sure we |
| # can do an apples-to-apples comparison by compiling with the same file name |
| shutil.move(out_filename, no_maps_filename) |
| with open(no_maps_filename) as f: |
| no_maps_file = f.read() |
| no_maps_file = re.sub(' *//[@#].*$', '', no_maps_file, flags=re.MULTILINE) |
| self.emcc_args.append('-g4') |
| |
| building.emcc(os.path.abspath('src.cpp'), |
| self.serialize_settings() + self.emcc_args + self.emcc_args, |
| out_filename, |
| stderr=PIPE) |
| map_referent = out_filename if not self.get_setting('WASM') else wasm_filename |
| # after removing the @line and @sourceMappingURL comments, the build |
| # result should be identical to the non-source-mapped debug version. |
| # this is worth checking because the parser AST swaps strings for token |
| # objects when generating source maps, so we want to make sure the |
| # optimizer can deal with both types. |
| map_filename = map_referent + '.map' |
| |
| def encode_utf8(data): |
| if isinstance(data, dict): |
| for key in data: |
| data[key] = encode_utf8(data[key]) |
| return data |
| elif isinstance(data, list): |
| for i in range(len(data)): |
| data[i] = encode_utf8(data[i]) |
| return data |
| elif isinstance(data, type(u'')): |
| return data.encode('utf8') |
| else: |
| return data |
| |
| def source_map_file_loc(name): |
| if shared.Settings.WASM_BACKEND: |
| return name |
| # in fastcomp, we have the absolute path, which is not good |
| return os.path.abspath(name) |
| |
| data = json.load(open(map_filename)) |
| if str is bytes: |
| # Python 2 compatibility |
| data = encode_utf8(data) |
| if hasattr(data, 'file'): |
| # the file attribute is optional, but if it is present it needs to refer |
| # the output file. |
| self.assertPathsIdentical(map_referent, data['file']) |
| assert len(data['sources']) == 1, data['sources'] |
| self.assertPathsIdentical(source_map_file_loc('src.cpp'), data['sources'][0]) |
| if hasattr(data, 'sourcesContent'): |
| # the sourcesContent attribute is optional, but if it is present it |
| # needs to containt valid source text. |
| self.assertTextDataIdentical(src, data['sourcesContent'][0]) |
| mappings = json.loads(jsrun.run_js( |
| path_from_root('tools', 'source-maps', 'sourcemap2json.js'), |
| shared.NODE_JS, [map_filename])) |
| if str is bytes: |
| # Python 2 compatibility |
| mappings = encode_utf8(mappings) |
| seen_lines = set() |
| for m in mappings: |
| self.assertPathsIdentical(source_map_file_loc('src.cpp'), m['source']) |
| seen_lines.add(m['originalLine']) |
| # ensure that all the 'meaningful' lines in the original code get mapped |
| # when optimizing, the binaryen optimizer may remove some of them (by inlining, etc.) |
| if is_optimizing(self.emcc_args): |
| assert seen_lines.issuperset([11, 12]), seen_lines |
| else: |
| assert seen_lines.issuperset([6, 7, 11, 12]), seen_lines |
| |
| @no_wasm2js('TODO: source maps in wasm2js') |
| @no_fastcomp('DWARF is only supported in upstream') |
| def test_dwarf(self): |
| self.emcc_args.append('-g') |
| |
| create_test_file('src.cpp', ''' |
| #include <emscripten.h> |
| EM_JS(int, out_to_js, (int x), {}) |
| void foo() { |
| out_to_js(0); // line 5 |
| out_to_js(1); // line 6 |
| out_to_js(2); // line 7 |
| // A silly possible recursion to avoid binaryen doing any inlining. |
| if (out_to_js(3)) foo(); |
| } |
| int main() { |
| foo(); |
| } |
| ''') |
| |
| js_filename = 'a.out.js' |
| wasm_filename = 'a.out.wasm' |
| |
| building.emcc('src.cpp', |
| self.serialize_settings() + self.emcc_args, |
| js_filename) |
| |
| LLVM_DWARFDUMP = os.path.join(LLVM_ROOT, 'llvm-dwarfdump') |
| out = run_process([LLVM_DWARFDUMP, wasm_filename, '-all'], stdout=PIPE).stdout |
| |
| # parse the sections |
| sections = {} |
| curr_section_name = '' |
| curr_section_body = '' |
| |
| def add_section(): |
| if curr_section_name: |
| sections[curr_section_name] = curr_section_body |
| |
| for line in out.splitlines(): |
| if ' contents:' in line: |
| # a new section, a line like ".debug_str contents:" |
| add_section() |
| curr_section_name = line.split(' ')[0] |
| curr_section_body = '' |
| else: |
| # possibly a line in a section |
| if curr_section_name: |
| curr_section_body += line + '\n' |
| add_section() |
| |
| # make sure the right sections exist |
| self.assertIn('.debug_abbrev', sections) |
| self.assertIn('.debug_info', sections) |
| self.assertIn('.debug_line', sections) |
| self.assertIn('.debug_str', sections) |
| self.assertIn('.debug_ranges', sections) |
| |
| # verify some content in the sections |
| self.assertIn('"src.cpp"', sections['.debug_info']) |
| # the line section looks like this: |
| # Address Line Column File ISA Discriminator Flags |
| # ------------------ ------ ------ ------ --- ------------- ------------- |
| # 0x000000000000000b 5 0 3 0 0 is_stmt |
| src_to_addr = {} |
| for line in sections['.debug_line'].splitlines(): |
| if line.startswith('0x'): |
| while ' ' in line: |
| line = line.replace(' ', ' ') |
| addr, line, col = line.split(' ')[:3] |
| key = (int(line), int(col)) |
| src_to_addr.setdefault(key, []).append(addr) |
| |
| # each of the calls must remain in the binary, and be mapped |
| self.assertIn((5, 9), src_to_addr) |
| self.assertIn((6, 9), src_to_addr) |
| self.assertIn((7, 9), src_to_addr) |
| |
| def get_dwarf_addr(line, col): |
| addrs = src_to_addr[(line, col)] |
| assert len(addrs) == 1, 'we assume the simple calls have one address' |
| return int(addrs[0], 0) |
| |
| # the lines must appear in sequence (as calls to JS, the optimizer cannot |
| # reorder them) |
| self.assertLess(get_dwarf_addr(5, 9), get_dwarf_addr(6, 9)) |
| self.assertLess(get_dwarf_addr(6, 9), get_dwarf_addr(7, 9)) |
| |
| # get the wat, printing with -g which has binary offsets |
| wat = run_process([os.path.join(building.get_binaryen_bin(), 'wasm-opt'), |
| wasm_filename, '-g', '--print'], stdout=PIPE).stdout |
| |
| # we expect to see a pattern like this, as in both debug and opt builds |
| # there isn't much that can change with such calls to JS (they can't be |
| # reordered or anything else): |
| # |
| # ;; code offset: 0x? |
| # (drop |
| # ;; code offset: 0x? |
| # (call $out_to_js |
| # ;; code offset: 0x? |
| # (local.get ?) or (i32.const ?) |
| # ) |
| # ) |
| # |
| # In stacky stream of instructions form, it is |
| # local.get or i32.const |
| # call $out_to_js |
| # drop |
| |
| # get_wat_addr gets the address of one of the 3 interesting calls, by its |
| # index (0,1,2). |
| def get_wat_addr(call_index): |
| # find the call_index-th call |
| call_loc = -1 |
| for i in range(call_index + 1): |
| call_loc = wat.find('call $out_to_js', call_loc + 1) |
| assert call_loc > 0 |
| # the call begins with the local.get/i32.const printed below it, which is |
| # the first instruction in the stream, so it has the lowest address |
| start_addr_loc = wat.find('0x', call_loc) |
| assert start_addr_loc > 0 |
| start_addr_loc_end = wat.find('\n', start_addr_loc) |
| start_addr = int(wat[start_addr_loc:start_addr_loc_end], 0) |
| # the call ends with the drop, which is the last in the stream, at the |
| # highest address |
| end_addr_loc = wat.rfind('drop', 0, call_loc) |
| assert end_addr_loc > 0 |
| end_addr_loc = wat.rfind('0x', 0, end_addr_loc) |
| assert end_addr_loc > 0 |
| end_addr_loc_end = wat.find('\n', end_addr_loc) |
| assert end_addr_loc_end > 0 |
| end_addr = int(wat[end_addr_loc:end_addr_loc_end], 0) |
| return (start_addr, end_addr) |
| |
| # match up the DWARF and the wat |
| for i in range(3): |
| dwarf_addr = get_dwarf_addr(5 + i, 9) |
| start_wat_addr, end_wat_addr = get_wat_addr(i) |
| # the dwarf may match any of the 3 instructions that form the stream of |
| # of instructions implementing the call in the source code, in theory |
| self.assertLessEqual(start_wat_addr, dwarf_addr) |
| self.assertLessEqual(dwarf_addr, end_wat_addr) |
| |
| def test_modularize_closure_pre(self): |
| # test that the combination of modularize + closure + pre-js works. in that mode, |
| # closure should not minify the Module object in a way that the pre-js cannot use it. |
| self.emcc_args += [ |
| '--pre-js', path_from_root('tests', 'core', 'modularize_closure_pre.js'), |
| '--closure', '1', |
| '-g1', |
| '-s', |
| 'MODULARIZE=1', |
| ] |
| |
| def post(filename): |
| with open(filename, 'a') as f: |
| f.write('\n\n') |
| f.write('var TheModule = Module();\n') |
| |
| self.do_run_in_out_file_test('tests', 'core', 'modularize_closure_pre', post_build=post) |
| |
| @no_wasm('wasmifying destroys debug info and stack tracability') |
| @no_wasm2js('source maps support') |
| def test_emscripten_log(self): |
| self.banned_js_engines = [V8_ENGINE] # v8 doesn't support console.log |
| self.emcc_args += ['-s', 'DEMANGLE_SUPPORT=1'] |
| if self.get_setting('ASM_JS'): |
| # XXX Does not work in SpiderMonkey since callstacks cannot be captured when running in asm.js, see https://bugzilla.mozilla.org/show_bug.cgi?id=947996 |
| self.banned_js_engines += [SPIDERMONKEY_ENGINE] |
| if '-g' not in self.emcc_args: |
| self.emcc_args.append('-g') |
| self.emcc_args += ['-DRUN_FROM_JS_SHELL'] |
| self.do_run(open(path_from_root('tests', 'emscripten_log', 'emscripten_log.cpp')).read(), '''test print 123 |
| |
| 12.345679 9.123457 1.353180 |
| |
| 12345678 9123456 1353179 |
| |
| 12.345679 9123456 1353179 |
| |
| 12345678 9.123457 1353179 |
| |
| 12345678 9123456 1.353180 |
| |
| 12345678 9.123457 1.353180 |
| |
| 12.345679 9123456 1.353180 |
| |
| 12.345679 9.123457 1353179 |
| |
| Success! |
| ''') |
| # test closure compiler as well |
| if self.run_name == 'asm2': |
| print('closure') |
| self.emcc_args += ['--closure', '1', '-g1'] # extra testing |
| self.do_run_in_out_file_test('tests', 'emscripten_log', 'emscripten_log_with_closure') |
| |
| def test_float_literals(self): |
| self.do_run_in_out_file_test('tests', 'test_float_literals') |
| |
| def test_exit_status(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| src = r''' |
| #include <stdio.h> |
| #include <stdlib.h> |
| static void cleanup() { |
| printf("cleanup\n"); |
| } |
| |
| int main() { |
| atexit(cleanup); // this atexit should still be called |
| printf("hello, world!\n"); |
| // Unusual exit status to make sure it's working! |
| if (CAPITAL_EXIT) { |
| _Exit(118); |
| } else { |
| exit(118); |
| } |
| } |
| ''' |
| create_test_file('pre.js', ''' |
| Module.preInit = function() { |
| addOnExit(function () { |
| out('I see exit status: ' + EXITSTATUS); |
| }); |
| } |
| ''') |
| self.emcc_args += ['--pre-js', 'pre.js'] |
| self.do_run(src.replace('CAPITAL_EXIT', '0'), 'hello, world!\ncleanup\nI see exit status: 118', assert_returncode=None) |
| self.do_run(src.replace('CAPITAL_EXIT', '1'), 'hello, world!\ncleanup\nI see exit status: 118', assert_returncode=None) |
| |
| def test_noexitruntime(self): |
| src = r''' |
| #include <emscripten.h> |
| #include <stdio.h> |
| static int testPre = TEST_PRE; |
| struct Global { |
| Global() { |
| printf("in Global()\n"); |
| if (testPre) { EM_ASM(noExitRuntime = true;); } |
| } |
| ~Global() { printf("ERROR: in ~Global()\n"); } |
| } global; |
| int main() { |
| if (!testPre) { EM_ASM(noExitRuntime = true;); } |
| printf("in main()\n"); |
| } |
| ''' |
| self.do_run(src.replace('TEST_PRE', '0'), 'in Global()\nin main()') |
| self.do_run(src.replace('TEST_PRE', '1'), 'in Global()\nin main()') |
| |
| def test_minmax(self): |
| self.do_run(open(path_from_root('tests', 'test_minmax.c')).read(), 'NAN != NAN\nSuccess!') |
| |
| def test_locale(self): |
| self.do_run_from_file(path_from_root('tests', 'test_locale.c'), path_from_root('tests', 'test_locale.out')) |
| |
| def test_vswprintf_utf8(self): |
| self.do_run_from_file(path_from_root('tests', 'vswprintf_utf8.c'), path_from_root('tests', 'vswprintf_utf8.out')) |
| |
| @no_asan('asan is not compatible with asyncify stack operations; may also need to not instrument asan_c_load_4, TODO') |
| def test_async(self): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| self.banned_js_engines = [SPIDERMONKEY_ENGINE, V8_ENGINE] # needs setTimeout which only node has |
| |
| if self.is_wasm_backend(): |
| self.set_setting('ASYNCIFY', 1) |
| else: |
| self.skipTest('fastcomp Asyncify was removed') |
| |
| src = r''' |
| #include <stdio.h> |
| #include <emscripten.h> |
| void f(void *p) { |
| *(int*)p = 99; |
| printf("!"); |
| } |
| int main() { |
| int i = 0; |
| printf("Hello"); |
| emscripten_async_call(f, &i, 1); |
| printf("World"); |
| emscripten_sleep(100); |
| printf("%d\n", i); |
| } |
| ''' |
| |
| self.do_run(src, 'HelloWorld!99') |
| |
| if self.is_wasm_backend(): |
| print('check bad ccall use') |
| src = r''' |
| #include <stdio.h> |
| #include <emscripten.h> |
| int main() { |
| printf("Hello"); |
| emscripten_sleep(100); |
| printf("World\n"); |
| } |
| ''' |
| self.set_setting('ASSERTIONS', 1) |
| self.set_setting('INVOKE_RUN', 0) |
| create_test_file('pre.js', ''' |
| Module['onRuntimeInitialized'] = function() { |
| try { |
| ccall('main', 'number', ['number', 'string'], [2, 'waka']); |
| var never = true; |
| } catch(e) { |
| out(e); |
| assert(!never); |
| } |
| }; |
| ''') |
| self.emcc_args += ['--pre-js', 'pre.js'] |
| self.do_run(src, 'The call to main is running asynchronously.') |
| |
| print('check reasonable ccall use') |
| src = r''' |
| #include <stdio.h> |
| #include <emscripten.h> |
| int main() { |
| printf("Hello"); |
| emscripten_sleep(100); |
| printf("World\n"); |
| } |
| ''' |
| create_test_file('pre.js', ''' |
| Module['onRuntimeInitialized'] = function() { |
| ccall('main', null, ['number', 'string'], [2, 'waka'], { async: true }); |
| }; |
| ''') |
| self.do_run(src, 'HelloWorld') |
| |
| print('check ccall promise') |
| self.set_setting('EXPORTED_FUNCTIONS', ['_stringf', '_floatf']) |
| src = r''' |
| #include <stdio.h> |
| #include <emscripten.h> |
| extern "C" { |
| const char* stringf(char* param) { |
| emscripten_sleep(20); |
| printf(param); |
| return "second"; |
| } |
| double floatf() { |
| emscripten_sleep(20); |
| emscripten_sleep(20); |
| return 6.4; |
| } |
| } |
| ''' |
| create_test_file('pre.js', r''' |
| Module['onRuntimeInitialized'] = function() { |
| ccall('stringf', 'string', ['string'], ['first\n'], { async: true }) |
| .then(function(val) { |
| console.log(val); |
| ccall('floatf', 'number', null, null, { async: true }).then(console.log); |
| }); |
| }; |
| ''') |
| self.do_run(src, 'first\nsecond\n6.4') |
| |
| @no_wasm_backend('ASYNCIFY coroutines are not yet supported in the LLVM wasm backend') |
| def do_test_coroutine(self, additional_settings): |
| # needs to flush stdio streams |
| self.set_setting('EXIT_RUNTIME', 1) |
| src = open(path_from_root('tests', 'test_coroutines.cpp')).read() |
| for (k, v) in additional_settings.items(): |
| self.set_setting(k, v) |
| self.do_run(src, '*leaf-0-100-1-101-1-102-2-103-3-104-5-105-8-106-13-107-21-108-34-109-*') |
| |
| @no_wasm_backend('ASYNCIFY coroutines are not yet supported in the LLVM wasm backend') |
| @no_fastcomp('ASYNCIFY has been removed from fastcomp') |
| def test_coroutine_asyncify(self): |
| self.do_test_coroutine({'ASYNCIFY': 1}) |
| |
| @no_asan('asyncify stack operations confuse asan') |
| @no_fastcomp('Fibers are not implemented for fastcomp') |
| def test_fibers_asyncify(self): |
| self.set_setting('ASYNCIFY', 1) |
| src = open(path_from_root('tests', 'test_fibers.cpp')).read() |
| self.do_run(src, '*leaf-0-100-1-101-1-102-2-103-3-104-5-105-8-106-13-107-21-108-34-109-*') |
| |
| @no_wasm_backend('ASYNCIFY is not supported in the LLVM wasm backend') |
| @no_fastcomp('ASYNCIFY has been removed from fastcomp') |
| def test_asyncify_unused(self): |
| # test a program not using asyncify, but the pref is set |
| self.set_setting('ASYNCIFY', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_hello_world') |
| |
| @parameterized({ |
| 'normal': ([], True), |
| 'blacklist_a': (['-s', 'ASYNCIFY_BLACKLIST=["foo(int, double)"]'], False), |
| 'blacklist_b': (['-s', 'ASYNCIFY_BLACKLIST=["bar()"]'], True), |
| 'blacklist_c': (['-s', 'ASYNCIFY_BLACKLIST=["baz()"]'], False), |
| 'whitelist_a': (['-s', 'ASYNCIFY_WHITELIST=["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()","bar()"]'], True), |
| 'whitelist_b': (['-s', 'ASYNCIFY_WHITELIST=["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()"]'], True), |
| 'whitelist_c': (['-s', 'ASYNCIFY_WHITELIST=["main","__original_main","foo(int, double)","baz()","c_baz"]'], False), |
| 'whitelist_d': (['-s', 'ASYNCIFY_WHITELIST=["foo(int, double)","baz()","c_baz","Structy::funcy()"]'], False), |
| 'whitelist_b_response': ([], True, '["main","__original_main","foo(int, double)","baz()","c_baz","Structy::funcy()"]'), |
| 'whitelist_c_response': ([], False, '["main","__original_main","foo(int, double)","baz()","c_baz"]'), |
| }) |
| @no_asan('asan is not compatible with asyncify stack operations; may also need to not instrument asan_c_load_4, TODO') |
| @no_fastcomp('new asyncify only') |
| def test_asyncify_lists(self, args, should_pass, response=None): |
| if response is not None: |
| create_test_file('response.file', response) |
| self.emcc_args += ['-s', 'ASYNCIFY_WHITELIST=@response.file'] |
| self.set_setting('ASYNCIFY', 1) |
| self.emcc_args += args |
| try: |
| self.do_run_in_out_file_test('tests', 'core', 'test_asyncify_lists', assert_identical=True) |
| if not should_pass: |
| should_pass = True |
| raise Exception('should not have passed') |
| except Exception: |
| if should_pass: |
| raise |
| |
| @no_asan('asyncify stack operations confuse asan') |
| @no_fastcomp('wasm-backend specific feature') |
| def test_emscripten_scan_registers(self): |
| self.set_setting('ASYNCIFY', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'emscripten_scan_registers') |
| |
| @no_fastcomp('wasm-backend specific feature') |
| def test_asyncify_assertions(self): |
| self.set_setting('ASYNCIFY', 1) |
| self.set_setting('ASYNCIFY_IMPORTS', ['suspend']) |
| self.set_setting('ASSERTIONS', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'asyncify_assertions') |
| |
| @no_asan('asyncify stack operations confuse asan') |
| @no_fastcomp('wasm-backend specific feature') |
| @no_wasm2js('TODO: lazy loading in wasm2js') |
| @parameterized({ |
| 'conditional': (True,), |
| 'unconditional': (False,), |
| }) |
| def test_emscripten_lazy_load_code(self, conditional): |
| self.set_setting('ASYNCIFY', 1) |
| self.set_setting('ASYNCIFY_LAZY_LOAD_CODE', 1) |
| self.set_setting('ASYNCIFY_IGNORE_INDIRECT', 1) |
| self.set_setting('MALLOC', 'emmalloc') |
| self.emcc_args += ['--profiling-funcs'] # so that we can find the functions for the changes below |
| if conditional: |
| self.emcc_args += ['-DCONDITIONAL'] |
| self.do_run_in_out_file_test('tests', 'core', 'emscripten_lazy_load_code', args=['0']) |
| |
| first_size = os.path.getsize('src.cpp.o.wasm') |
| second_size = os.path.getsize('src.cpp.o.wasm.lazy.wasm') |
| print('first wasm size', first_size) |
| print('second wasm size', second_size) |
| if not conditional and is_optimizing(self.emcc_args): |
| # If the call to lazy-load is unconditional, then the optimizer can dce |
| # out more than half |
| self.assertLess(first_size, 0.5 * second_size) |
| |
| with open('src.cpp.o.wasm', 'rb') as f: |
| with open('src.cpp.o.wasm.lazy.wasm', 'rb') as g: |
| self.assertNotEqual(f.read(), g.read()) |
| |
| # attempts to "break" the wasm by adding an unreachable in $foo_end. returns whether we found it. |
| def break_wasm(name): |
| wat = run_process([os.path.join(building.get_binaryen_bin(), 'wasm-dis'), name], stdout=PIPE).stdout |
| lines = wat.splitlines() |
| wat = None |
| for i in range(len(lines)): |
| if '(func $foo_end ' in lines[i]: |
| j = i + 1 |
| while '(local ' in lines[j]: |
| j += 1 |
| # we found the first line after the local defs |
| lines[j] = '(unreachable)' + lines[j] |
| wat = '\n'.join(lines) |
| break |
| if wat is None: |
| # $foo_end is not present in the wasm, nothing to break |
| shutil.copyfile(name, name + '.orig') |
| return False |
| with open('wat.wat', 'w') as f: |
| f.write(wat) |
| shutil.move(name, name + '.orig') |
| run_process([os.path.join(building.get_binaryen_bin(), 'wasm-as'), 'wat.wat', '-o', name, '-g']) |
| return True |
| |
| def verify_working(args=['0']): |
| self.assertContained('foo_end', run_js('src.cpp.o.js', args=args)) |
| |
| def verify_broken(args=['0']): |
| self.assertNotContained('foo_end', run_js('src.cpp.o.js', args=args, stderr=STDOUT, assert_returncode=None)) |
| |
| # the first-loaded wasm will not reach the second call, since we call it after lazy-loading. |
| # verify that by changing the first wasm to throw in that function |
| found_foo_end = break_wasm('src.cpp.o.wasm') |
| if not conditional and is_optimizing(self.emcc_args): |
| self.assertFalse(found_foo_end, 'should have optimizd out $foo_end') |
| verify_working() |
| # but breaking the second wasm actually breaks us |
| break_wasm('src.cpp.o.wasm.lazy.wasm') |
| verify_broken() |
| |
| # restore |
| shutil.copyfile('src.cpp.o.wasm.orig', 'src.cpp.o.wasm') |
| shutil.copyfile('src.cpp.o.wasm.lazy.wasm.orig', 'src.cpp.o.wasm.lazy.wasm') |
| verify_working() |
| |
| if conditional: |
| # if we do not call the lazy load function, then we do not need the lazy wasm, |
| # and we do the second call in the first wasm |
| os.remove('src.cpp.o.wasm.lazy.wasm') |
| verify_broken() |
| verify_working(['42']) |
| break_wasm('src.cpp.o.wasm') |
| verify_broken() |
| |
| # Test basic wasm2js functionality in all core compilation modes. |
| @no_fastcomp('wasm-backend specific feature') |
| @no_asan('no wasm2js support yet in asan') |
| def test_wasm2js(self): |
| if self.get_setting('WASM') == 0: |
| self.skipTest('redundant to test wasm2js in wasm2js* mode') |
| self.set_setting('WASM', 0) |
| self.do_run_in_out_file_test('tests', 'core', 'test_hello_world') |
| # a mem init file is emitted just like with JS |
| expect_memory_init_file = self.uses_memory_init_file() |
| see_memory_init_file = os.path.exists('src.c.o.js.mem') |
| assert expect_memory_init_file == see_memory_init_file, 'memory init file expectation wrong: %s' % expect_memory_init_file |
| if see_memory_init_file: |
| with open('src.c.o.js.mem', 'rb') as f: |
| self.assertTrue(f.read()[-1] != b'\0') |
| |
| @no_fastcomp('wasm-backend specific feature') |
| @no_asan('no wasm2js support yet in asan') |
| def test_maybe_wasm2js(self): |
| if self.get_setting('WASM') == 0: |
| self.skipTest('redundant to test wasm2js in wasm2js* mode') |
| self.set_setting('MAYBE_WASM2JS', 1) |
| # see that running as wasm works |
| self.do_run_in_out_file_test('tests', 'core', 'test_hello_world') |
| # run wasm2js, bundle the code, and use the wasm2js path |
| cmd = [PYTHON, path_from_root('tools', 'maybe_wasm2js.py'), 'src.c.o.js', 'src.c.o.wasm'] |
| if is_optimizing(self.emcc_args): |
| cmd += ['-O2'] |
| run_process(cmd, stdout=open('do_wasm2js.js', 'w')).stdout |
| # remove the wasm to make sure we never use it again |
| os.remove('src.c.o.wasm') |
| # verify that it runs |
| self.assertContained('hello, world!', run_js('do_wasm2js.js')) |
| |
| @no_fastcomp('wasm-backend specific feature') |
| @no_asan('no wasm2js support yet in asan') |
| def test_wasm2js_fallback(self): |
| if self.get_setting('WASM') == 0: |
| self.skipTest('redundant to test wasm2js in wasm2js* mode') |
| |
| for args in [[], ['-s', 'MINIMAL_RUNTIME=1']]: |
| cmd = [EMCC, path_from_root('tests', 'small_hello_world.c'), '-s', 'WASM=2'] + args |
| run_process(cmd) |
| |
| # First run with WebAssembly support enabled |
| # Move the Wasm2js fallback away to test it is not accidentally getting loaded. |
| os.rename('a.out.wasm.js', 'a.out.wasm.js.unused') |
| self.assertContained('hello!', run_js('a.out.js')) |
| os.rename('a.out.wasm.js.unused', 'a.out.wasm.js') |
| |
| # Then disable WebAssembly support in VM, and try again.. Should still work with Wasm2JS fallback. |
| open('b.out.js', 'w').write('WebAssembly = undefined;\n' + open('a.out.js', 'r').read()) |
| os.remove('a.out.wasm') # Also delete the Wasm file to test that it is not attempted to be loaded. |
| self.assertContained('hello!', run_js('b.out.js')) |
| |
| def test_cxx_self_assign(self): |
| # See https://github.com/emscripten-core/emscripten/pull/2688 and http://llvm.org/bugs/show_bug.cgi?id=18735 |
| self.do_run(r''' |
| #include <map> |
| #include <stdio.h> |
| |
| int main() { |
| std::map<int, int> m; |
| m[0] = 1; |
| m = m; |
| // size should still be one after self assignment |
| if (m.size() == 1) { |
| printf("ok.\n"); |
| } |
| } |
| ''', 'ok.') |
| |
| def test_memprof_requirements(self): |
| # This test checks for the global variables required to run the memory |
| # profiler. It would fail if these variables were made no longer global |
| # or if their identifiers were changed. |
| create_test_file('main.cpp', ''' |
| extern "C" { |
| void check_memprof_requirements(); |
| } |
| int main() { |
| check_memprof_requirements(); |
| return 0; |
| } |
| ''') |
| create_test_file('lib.js', ''' |
| mergeInto(LibraryManager.library, { |
| check_memprof_requirements: function() { |
| if (typeof STATIC_BASE === 'number' && |
| typeof STACK_BASE === 'number' && |
| typeof STACK_MAX === 'number' && |
| typeof STACKTOP === 'number' && |
| typeof DYNAMIC_BASE === 'number' && |
| typeof DYNAMICTOP_PTR === 'number') { |
| out('able to run memprof'); |
| } else { |
| out('missing the required variables to run memprof'); |
| } |
| } |
| }); |
| ''') |
| self.emcc_args += ['--js-library', 'lib.js'] |
| self.do_run(open('main.cpp').read(), 'able to run memprof') |
| |
| def test_fs_dict(self): |
| self.set_setting('FORCE_FILESYSTEM', 1) |
| self.emcc_args += ['-lidbfs.js'] |
| self.emcc_args += ['-lnodefs.js'] |
| create_test_file('pre.js', ''' |
| Module = {}; |
| Module['preRun'] = function() { |
| out(typeof FS.filesystems['MEMFS']); |
| out(typeof FS.filesystems['IDBFS']); |
| out(typeof FS.filesystems['NODEFS']); |
| // Globals |
| console.log(typeof MEMFS); |
| console.log(typeof IDBFS); |
| console.log(typeof NODEFS); |
| }; |
| ''') |
| self.emcc_args += ['--pre-js', 'pre.js'] |
| self.do_run('int main() { return 0; }', 'object\nobject\nobject\nobject\nobject\nobject') |
| |
| def test_fs_dict_none(self): |
| # if IDBFS and NODEFS are not enabled, they are not present. |
| self.set_setting('FORCE_FILESYSTEM', 1) |
| self.set_setting('ASSERTIONS', 1) |
| create_test_file('pre.js', ''' |
| Module = {}; |
| Module['preRun'] = function() { |
| out(typeof FS.filesystems['MEMFS']); |
| out(typeof FS.filesystems['IDBFS']); |
| out(typeof FS.filesystems['NODEFS']); |
| // Globals |
| if (ASSERTIONS) { |
| console.log(typeof MEMFS); |
| console.log(IDBFS); |
| console.log(NODEFS); |
| FS.mkdir('/working1'); |
| try { |
| FS.mount(IDBFS, {}, '/working1'); |
| } catch (e) { |
| console.log('|' + e + '|'); |
| } |
| } |
| }; |
| ''') |
| self.emcc_args += ['--pre-js', 'pre.js'] |
| expected = '''\ |
| object |
| undefined |
| undefined |
| object |
| IDBFS is no longer included by default; build with -lidbfs.js |
| NODEFS is no longer included by default; build with -lnodefs.js |
| |IDBFS is no longer included by default; build with -lidbfs.js|''' |
| self.do_run('int main() { return 0; }', expected) |
| |
| @sync |
| @no_wasm_backend("https://github.com/emscripten-core/emscripten/issues/9039") |
| def test_stack_overflow_check(self): |
| args = self.emcc_args + ['-s', 'TOTAL_STACK=1048576'] |
| |
| self.emcc_args = args + ['-s', 'STACK_OVERFLOW_CHECK=2', '-s', 'ASSERTIONS=0'] |
| self.do_run(open(path_from_root('tests', 'stack_overflow.cpp')).read(), 'Stack overflow! Attempted to allocate', assert_returncode=None) |
| |
| self.emcc_args = args + ['-s', 'ASSERTIONS=1'] |
| self.do_run(open(path_from_root('tests', 'stack_overflow.cpp')).read(), 'Stack overflow! Attempted to allocate', assert_returncode=None) |
| |
| @no_wasm_backend('uses BINARYEN_TRAP_MODE (the wasm backend only supports non-trapping)') |
| def test_binaryen_trap_mode(self): |
| if not self.is_wasm(): |
| self.skipTest('wasm test') |
| TRAP_OUTPUTS = ('trap', 'RuntimeError') |
| default = 'allow' |
| print('default is', default) |
| for mode in ['js', 'clamp', 'allow', '']: |
| if mode == 'js' and self.is_wasm_backend(): |
| # wasm backend does not use asm2wasm imports, which js trap mode requires |
| continue |
| print('mode:', mode) |
| self.set_setting('BINARYEN_TRAP_MODE', mode or default) |
| if not mode: |
| mode = default |
| print(' idiv') |
| self.do_run(open(path_from_root('tests', 'wasm', 'trap-idiv.cpp')).read(), { |
| 'js': '|0|', |
| 'clamp': '|0|', |
| 'allow': TRAP_OUTPUTS |
| }[mode], assert_returncode=None) |
| print(' f2i') |
| self.do_run(open(path_from_root('tests', 'wasm', 'trap-f2i.cpp')).read(), { |
| 'js': '|1337|\n|4294967295|', # JS did an fmod 2^32 | normal |
| 'clamp': '|-2147483648|\n|4294967295|', |
| 'allow': TRAP_OUTPUTS |
| }[mode], assert_returncode=None) |
| |
| @also_with_standalone_wasm() |
| def test_sbrk(self): |
| self.do_run(open(path_from_root('tests', 'sbrk_brk.cpp')).read(), 'OK.') |
| |
| def test_brk(self): |
| self.emcc_args += ['-DTEST_BRK=1'] |
| self.do_run(open(path_from_root('tests', 'sbrk_brk.cpp')).read(), 'OK.') |
| |
| # Tests that we can use the dlmalloc mallinfo() function to obtain information |
| # about malloc()ed blocks and compute how much memory is used/freed. |
| @no_asan('mallinfo is not part of ASan malloc') |
| def test_mallinfo(self): |
| self.do_run(open(path_from_root('tests', 'mallinfo.cpp')).read(), 'OK.') |
| |
| @no_asan('cannot replace malloc/free with ASan') |
| def test_wrap_malloc(self): |
| self.do_run(open(path_from_root('tests', 'wrap_malloc.cpp')).read(), 'OK.') |
| |
| def test_environment(self): |
| self.set_setting('ASSERTIONS', 1) |
| |
| def test(): |
| self.do_run_in_out_file_test('tests', 'core', 'test_hello_world', assert_returncode=None) |
| js = open('src.c.o.js').read() |
| assert ('require(' in js) == ('node' in self.get_setting('ENVIRONMENT')), 'we should have require() calls only if node js specified' |
| |
| for engine in JS_ENGINES: |
| print(engine) |
| # set us to test in just this engine |
| self.banned_js_engines = [e for e in JS_ENGINES if e != engine] |
| # tell the compiler to build with just that engine |
| if engine == NODE_JS: |
| right = 'node' |
| wrong = 'shell' |
| else: |
| right = 'shell' |
| wrong = 'node' |
| # test with the right env |
| self.set_setting('ENVIRONMENT', right) |
| print('ENVIRONMENT =', self.get_setting('ENVIRONMENT')) |
| test() |
| # test with the wrong env |
| self.set_setting('ENVIRONMENT', wrong) |
| print('ENVIRONMENT =', self.get_setting('ENVIRONMENT')) |
| try: |
| test() |
| raise Exception('unexpected success') |
| except Exception as e: |
| self.assertContained('not compiled for this environment', str(e)) |
| # test with a combined env |
| self.set_setting('ENVIRONMENT', right + ',' + wrong) |
| print('ENVIRONMENT =', self.get_setting('ENVIRONMENT')) |
| test() |
| |
| def test_dfe(self): |
| if not self.supports_js_dfe(): |
| self.skipTest('dfe-only') |
| self.set_setting('ELIMINATE_DUPLICATE_FUNCTIONS', 1) |
| self.do_run_in_out_file_test('tests', 'core', 'test_hello_world') |
| self.emcc_args += ['-g2'] # test for issue #6331 |
| self.do_run_in_out_file_test('tests', 'core', 'test_hello_world') |
| |
| def test_postrun_exception(self): |
| # verify that an exception thrown in postRun() will not trigger the |
| # compilation failed handler, and will be printed to stderr. |
| self.add_post_run('ThisFunctionDoesNotExist()') |
| src = open(path_from_root('tests', 'core', 'test_hello_world.c')).read() |
| self.build(src, self.get_dir(), 'src.c') |
| output = run_js('src.c.o.js', assert_returncode=None, stderr=STDOUT) |
| self.assertNotContained('failed to asynchronously prepare wasm', output) |
| self.assertContained('hello, world!', output) |
| self.assertContained('ThisFunctionDoesNotExist is not defined', output) |
| |
| # Tests that building with -s DECLARE_ASM_MODULE_EXPORTS=0 works |
| def test_no_declare_asm_module_exports(self): |
| self.set_setting('DECLARE_ASM_MODULE_EXPORTS', 0) |
| self.set_setting('WASM_ASYNC_COMPILATION', 0) |
| self.maybe_closure() |
| self.do_run(open(path_from_root('tests', 'declare_asm_module_exports.cpp')).read(), 'jsFunction: 1') |
| js = open('src.cpp.o.js').read() |
| occurances = js.count('cFunction') |
| if is_optimizing(self.emcc_args) and '-g' not in self.emcc_args: |
| # In optimized builds only the single reference cFunction that exists in the EM_ASM should exist |
| if self.is_wasm(): |
| self.assertEqual(occurances, 1) |
| else: |
| # With js the asm module itself also contains a reference for the cFunction name |
| self.assertEqual(occurances, 2) |
| else: |
| print(occurances) |
| |
| # Tests that building with -s DECLARE_ASM_MODULE_EXPORTS=0 works |
| def test_minimal_runtime_no_declare_asm_module_exports(self): |
| self.set_setting('DECLARE_ASM_MODULE_EXPORTS', 0) |
| self.set_setting('WASM_ASYNC_COMPILATION', 0) |
| self.maybe_closure() |
| self.set_setting('MINIMAL_RUNTIME', 1) |
| self.do_run(open(path_from_root('tests', 'declare_asm_module_exports.cpp')).read(), 'jsFunction: 1') |
| |
| # Tests that -s MINIMAL_RUNTIME=1 works well in different build modes |
| @parameterized({ |
| 'default': ([],), |
| 'streaming': (['-s', 'MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION=1'],), |
| 'streaming_inst': (['-s', 'MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION=1'],), |
| 'no_export': (['-s', 'DECLARE_ASM_MODULE_EXPORTS=0'],) |
| }) |
| def test_minimal_runtime_hello_world(self, args): |
| # TODO: Support for non-Node.js shells has not yet been added to MINIMAL_RUNTIME |
| self.banned_js_engines = [V8_ENGINE, SPIDERMONKEY_ENGINE] |
| self.emcc_args = ['-s', 'MINIMAL_RUNTIME=1'] + args |
| self.set_setting('MINIMAL_RUNTIME', 1) |
| self.maybe_closure() |
| self.do_run(open(path_from_root('tests', 'small_hello_world.c')).read(), 'hello') |
| |
| # Test that printf() works in MINIMAL_RUNTIME=1 |
| @parameterized({ |
| 'fs': (['-s', 'FORCE_FILESYSTEM=1'],), |
| 'nofs': (['-s', 'NO_FILESYSTEM=1'],), |
| }) |
| def test_minimal_runtime_hello_printf(self, args): |
| self.emcc_args = ['-s', 'MINIMAL_RUNTIME=1'] + args |
| self.maybe_closure() |
| self.do_run(open(path_from_root('tests', 'hello_world.c')).read(), 'hello, world!') |
| |
| # Tests that -s MINIMAL_RUNTIME=1 works well with SAFE_HEAP |
| def test_minimal_runtime_safe_heap(self): |
| self.emcc_args = ['-s', 'MINIMAL_RUNTIME=1', '-s', 'SAFE_HEAP=1'] |
| self.maybe_closure() |
| self.do_run(open(path_from_root('tests', 'small_hello_world.c')).read(), 'hello') |
| |
| # Tests global initializer with -s MINIMAL_RUNTIME=1 |
| def test_minimal_runtime_global_initializer(self): |
| self.set_setting('MINIMAL_RUNTIME', 1) |
| self.maybe_closure() |
| self.do_run(open(path_from_root('tests', 'test_global_initializer.cpp')).read(), 't1 > t0: 1') |
| |
| @no_fastcomp('return address not supported on fastcomp') |
| @no_optimize('return address test cannot work with optimizations') |
| def test_return_address(self): |
| self.emcc_args += ['-s', 'USE_OFFSET_CONVERTER'] |
| self.do_run(open(path_from_root('tests', 'core', 'test_return_address.cpp')).read(), 'passed') |
| |
| @no_wasm2js('TODO: sanitizers in wasm2js') |
| @no_fastcomp('ubsan not supported on fastcomp') |
| @no_asan('-fsanitize-minimal-runtime cannot be used with ASan') |
| def test_ubsan_minimal_too_many_errors(self): |
| self.emcc_args += ['-fsanitize=undefined', '-fsanitize-minimal-runtime'] |
| if self.get_setting('WASM') == 0: |
| if is_optimizing(self.emcc_args): |
| self.skipTest('test can only be run without optimizations on asm.js') |
| # Need to use `-g` to get proper line numbers in asm.js |
| self.emcc_args += ['-g'] |
| self.do_run(open(path_from_root('tests', 'core', 'test_ubsan_minimal_too_many_errors.c')).read(), |
| expected_output='ubsan: add-overflow\n' * 20 + 'ubsan: too many errors\n') |
| |
| @no_wasm2js('TODO: sanitizers in wasm2js') |
| @no_fastcomp('ubsan not supported on fastcomp') |
| @no_asan('-fsanitize-minimal-runtime cannot be used with ASan') |
| def test_ubsan_minimal_errors_same_place(self): |
| self.emcc_args += ['-fsanitize=undefined', '-fsanitize-minimal-runtime'] |
| if self.get_setting('WASM') == 0: |
| if is_optimizing(self.emcc_args): |
| self.skipTest('test can only be run without optimizations on asm.js') |
| # Need to use `-g` to get proper line numbers in asm.js |
| self.emcc_args += ['-g'] |
| self.do_run(open(path_from_root('tests', 'core', 'test_ubsan_minimal_errors_same_place.c')).read(), |
| expected_output='ubsan: add-overflow\n' * 5) |
| |
| @parameterized({ |
| 'fsanitize_undefined': (['-fsanitize=undefined'],), |
| 'fsanitize_integer': (['-fsanitize=integer'],), |
| 'fsanitize_overflow': (['-fsanitize=signed-integer-overflow'],), |
| }) |
| @no_fastcomp('ubsan not supported on fastcomp') |
| @no_wasm2js('TODO: sanitizers in wasm2js') |
| def test_ubsan_full_overflow(self, args): |
| self.emcc_args += args |
| self.do_run(open(path_from_root('tests', 'core', 'test_ubsan_full_overflow.c')).read(), |
| assert_all=True, expected_output=[ |
| "src.cpp:3:5: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'", |
| "src.cpp:7:7: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'", |
| ]) |
| |
| @parameterized({ |
| 'fsanitize_undefined': (['-fsanitize=undefined'],), |
| 'fsanitize_return': (['-fsanitize=return'],), |
| }) |
| @no_wasm2js('TODO: sanitizers in wasm2js') |
| @no_fastcomp('ubsan not supported on fastcomp') |
| def test_ubsan_full_no_return(self, args): |
| self.emcc_args += ['-Wno-return-type'] + args |
| self.do_run(open(path_from_root('tests', 'core', 'test_ubsan_full_no_return.c')).read(), |
| expected_output='src.cpp:1:5: runtime error: execution reached the end of a value-returning function without returning a value', assert_returncode=None) |
| |
| @parameterized({ |
| 'fsanitize_undefined': (['-fsanitize=undefined'],), |
| 'fsanitize_integer': (['-fsanitize=integer'],), |
| 'fsanitize_shift': (['-fsanitize=shift'],), |
| }) |
| @no_fastcomp('ubsan not supported on fastcomp') |
| @no_wasm2js('TODO: sanitizers in wasm2js') |
| def test_ubsan_full_left_shift(self, args): |
| self.emcc_args += args |
| self.do_run(open(path_from_root('tests', 'core', 'test_ubsan_full_left_shift.c')).read(), |
| assert_all=True, expected_output=[ |
| 'src.cpp:3:5: runtime error: left shift of negative value -1', |
| "src.cpp:7:5: runtime error: left shift of 16 by 29 places cannot be represented in type 'int'" |
| ]) |
| |
| @parameterized({ |
| 'fsanitize_undefined': (['-fsanitize=undefined'],), |
| 'fsanitize_null': (['-fsanitize=null'],), |
| }) |
| @no_fastcomp('ubsan not supported on fastcomp') |
| @no_wasm2js('TODO: sanitizers in wasm2js') |
| def test_ubsan_full_null_ref(self, args): |
| self.emcc_args += args |
| self.do_run(open(path_from_root('tests', 'core', 'test_ubsan_full_null_ref.cpp')).read(), |
| assert_all=True, expected_output=[ |
| "src.cpp:3:12: runtime error: reference binding to null pointer of type 'int'", |
| "src.cpp:4:13: runtime error: reference binding to null pointer of type 'int'", |
| "src.cpp:5:14: runtime error: reference binding to null pointer of type 'int'", |
| ]) |
| |
| @parameterized({ |
| 'fsanitize_undefined': (['-fsanitize=undefined'],), |
| 'fsanitize_vptr': (['-fsanitize=vptr'],), |
| }) |
| @no_fastcomp('ubsan not supported on fastcomp') |
| @no_wasm2js('TODO: sanitizers in wasm2js') |
| def test_ubsan_full_static_cast(self, args): |
| self.emcc_args += args |
| self.do_run(open(path_from_root('tests', 'core', 'test_ubsan_full_static_cast.cpp')).read(), |
| assert_all=True, expected_output=[ |
| "src.cpp:18:10: runtime error: downcast of address", |
| "which does not point to an object of type 'R'", |
| ]) |
| |
| @parameterized({ |
| 'g': ('-g', [ |
| "src.cpp:3:12: runtime error: reference binding to null pointer of type 'int'", |
| 'in main', |
| ]), |
| 'g4': ('-g4', [ |
| "src.cpp:3:12: runtime error: reference binding to null pointer of type 'int'", |
| 'in main ', |
| 'src.cpp:3:8' |
| ]), |
| }) |
| @no_fastcomp('ubsan not supported on fastcomp') |
| @no_wasm2js('TODO: sanitizers in wasm2js') |
| def test_ubsan_full_stack_trace(self, g_flag, expected_output): |
| self.emcc_args += ['-fsanitize=null', g_flag, '-s', 'ALLOW_MEMORY_GROWTH=1'] |
| |
| if g_flag == '-g4': |
| if not self.get_setting('WASM'): |
| self.skipTest('wasm2js has no source map support') |
| elif '-Oz' in self.emcc_args: |
| self.skipTest('-Oz breaks stack traces') |
| |
| def modify_env(filename): |
| with open(filename) as f: |
| contents = f.read() |
| contents = 'Module = {UBSAN_OPTIONS: "print_stacktrace=1"};' + contents |
| with open(filename, 'w') as f: |
| f.write(contents) |
| |
| self.do_run(open(path_from_root('tests', 'core', 'test_ubsan_full_null_ref.cpp')).read(), |
| post_build=modify_env, assert_all=True, expected_output=expected_output) |
| |
| def test_template_class_deduction(self): |
| self.emcc_args += ['-std=c++17'] |
| self.do_run_in_out_file_test('tests', 'core', 'test_template_class_deduction') |
| |
| @parameterized({ |
| 'c': ['test_asan_no_error.c'], |
| 'cpp': ['test_asan_no_error.cpp'], |
| }) |
| @no_fastcomp('asan not supported on fastcomp') |
| def test_asan_no_error(self, name): |
| self.emcc_args += ['-fsanitize=address', '-s', 'ALLOW_MEMORY_GROWTH=1'] |
| self.do_run(open(path_from_root('tests', 'core', name)).read(), |
| basename=name, expected_output=[''], assert_returncode=None) |
| |
| # note: these tests have things like -fno-builtin-memset in order to avoid |
| # clang optimizing things away. for example, a memset might be optimized into |
| # stores, and then the stores identified as dead, which leaves nothing for |
| # asan to test. here we want to test asan itself, so we work around that. |
| @parameterized({ |
| 'use_after_free_c': ('test_asan_use_after_free.c', [ |
| 'AddressSanitizer: heap-use-after-free on address', |
| ]), |
| 'use_after_free_cpp': ('test_asan_use_after_free.cpp', [ |
| 'AddressSanitizer: heap-use-after-free on address', |
| ]), |
| 'use_after_return': ('test_asan_use_after_return.c', [ |
| 'AddressSanitizer: stack-use-after-return on address', |
| ], ['-Wno-return-stack-address']), |
| 'static_buffer_overflow': ('test_asan_static_buffer_overflow.c', [ |
| 'AddressSanitizer: global-buffer-overflow on address', |
| ], ['-fno-builtin-memset']), |
| 'heap_buffer_overflow_c': ('test_asan_heap_buffer_overflow.c', [ |
| 'AddressSanitizer: heap-buffer-overflow on address', |
| ], ['-fno-builtin-memset']), |
| 'heap_buffer_overflow_cpp': ('test_asan_heap_buffer_overflow.cpp', [ |
| 'AddressSanitizer: heap-buffer-overflow on address', |
| ], ['-fno-builtin-memset']), |
| 'stack_buffer_overflow': ('test_asan_stack_buffer_overflow.c', [ |
| 'AddressSanitizer: stack-buffer-overflow' |
| ], ['-fno-builtin-memset']), |
| 'stack_buffer_overflow_js': ('test_asan_stack_buffer_overflow_js.c', [ |
| 'AddressSanitizer: stack-buffer-overflow' |
| ], ['-fno-builtin-memset']), |
| 'bitfield_unround_size': ('test_asan_bitfield_unround_size.c', [ |
| 'AddressSanitizer: stack-buffer-overflow' |
| ], ['-fno-builtin-memset']), |
| 'bitfield_unround_offset': ('test_asan_bitfield_unround_offset.c', [ |
| 'AddressSanitizer: stack-buffer-overflow' |
| ], ['-fno-builtin-memset']), |
| 'bitfield_round': ('test_asan_bitfield_round.c', [ |
| 'AddressSanitizer: stack-buffer-overflow' |
| ], ['-fno-builtin-memset']), |
| 'memset_null': ('test_asan_memset_null.c', [ |
| 'AddressSanitizer: null-pointer-dereference on address 0x00000001' |
| ], ['-fno-builtin-memset']), |
| 'memset_freed': ('test_asan_memset_freed.c', [ |
| 'AddressSanitizer: heap-use-after-free on address' |
| ], ['-fno-builtin-memset']), |
| 'strcpy': ('test_asan_strcpy.c', [ |
| 'AddressSanitizer: heap-buffer-overflow on address' |
| ], ['-fno-builtin-strcpy']), |
| 'memcpy': ('test_asan_memcpy.c', [ |
| 'AddressSanitizer: heap-buffer-overflow on address' |
| ], ['-fno-builtin-memcpy']), |
| 'memchr': ('test_asan_memchr.c', [ |
| 'AddressSanitizer: global-buffer-overflow on address' |
| ], ['-fno-builtin-memchr']), |
| 'vector': ('test_asan_vector.cpp', [ |
| 'AddressSanitizer: container-overflow on address' |
| ]), |
| }) |
| @no_fastcomp('asan not supported on fastcomp') |
| def test_asan(self, name, expected_output, cflags=None): |
| if '-Oz' in self.emcc_args: |
| self.skipTest('-Oz breaks source maps') |
| |
| if not self.get_setting('WASM'): |
| self.skipTest('wasm2js has no ASan support') |
| |
| self.emcc_args += ['-fsanitize=address', '-s', 'ALLOW_MEMORY_GROWTH=1'] |
| if cflags: |
| self.emcc_args += cflags |
| self.do_run(open(path_from_root('tests', 'core', name)).read(), |
| basename='src.c' if name.endswith('.c') else 'src.cpp', |
| expected_output=expected_output, assert_all=True, |
| check_for_error=False, assert_returncode=None) |
| |
| @no_wasm2js('TODO: ASAN in wasm2js') |
| @no_fastcomp('asan not supported on fastcomp') |
| def test_asan_js_stack_op(self): |
| self.emcc_args += ['-fsanitize=address', '-s', 'ALLOW_MEMORY_GROWTH=1'] |
| self.do_run(open(path_from_root('tests', 'core', 'test_asan_js_stack_op.c')).read(), |
| basename='src.c', expected_output='Hello, World!') |
| |
| @no_fastcomp('WASM backend stack protection') |
| def test_safe_stack(self): |
| self.set_setting('STACK_OVERFLOW_CHECK', 2) |
| self.set_setting('TOTAL_STACK', 65536) |
| self.do_run(open(path_from_root('tests', 'core', 'test_safe_stack.c')).read(), |
| expected_output=['abort(stack overflow)', '__handle_stack_overflow'], assert_returncode=None) |
| |
| @no_fastcomp('WASM backend stack protection') |
| def test_safe_stack_alloca(self): |
| self.set_setting('STACK_OVERFLOW_CHECK', 2) |
| self.set_setting('TOTAL_STACK', 65536) |
| self.do_run(open(path_from_root('tests', 'core', 'test_safe_stack_alloca.c')).read(), |
| expected_output=['abort(stack overflow)', '__handle_stack_overflow'], assert_returncode=None) |
| |
| @needs_dlfcn |
| @no_fastcomp('WASM backend stack protection') |
| def test_safe_stack_dylink(self): |
| self.set_setting('STACK_OVERFLOW_CHECK', 2) |
| self.set_setting('TOTAL_STACK', 65536) |
| self.dylink_test(r''' |
| #include <stdio.h> |
| extern void sidey(); |
| int main() { |
| sidey(); |
| } |
| ''', ''' |
| #include <string.h> |
| |
| int f(int *b) { |
| int a[64]; |
| memset(b, 0, 2048 * sizeof(int)); |
| return f(a); |
| } |
| |
| void sidey() { |
| int a[2048]; |
| f(a); |
| } |
| ''', ['abort(stack overflow)', '__handle_stack_overflow'], assert_returncode=None) |
| |
| def test_fpic_static(self): |
| self.emcc_args.append('-fPIC') |
| self.do_run_in_out_file_test('tests', 'core', 'test_hello_world') |
| |
| @node_pthreads |
| def test_pthreads_create(self): |
| def test(): |
| self.do_run_in_out_file_test('tests', 'core', 'pthread', 'create') |
| test() |
| |
| # with a pool, we can synchronously depend on workers being available |
| self.set_setting('PTHREAD_POOL_SIZE', '2') |
| self.emcc_args += ['-DPOOL'] |
| test() |
| |
| @no_fastcomp('new wasm backend atomics') |
| def test_emscripten_atomics_stub(self): |
| self.do_run_in_out_file_test('tests', 'core', 'pthread', 'emscripten_atomics') |
| |
| @no_asan('incompatibility with atomics') |
| @no_fastcomp('new wasm backend atomics') |
| @node_pthreads |
| def test_emscripten_atomics(self): |
| self.set_setting('USE_PTHREADS', '1') |
| self.do_run_in_out_file_test('tests', 'core', 'pthread', 'emscripten_atomics') |
| |
| # Tests the emscripten_get_exported_function() API. |
| def test_emscripten_get_exported_function(self): |
| # Could also test with -s ALLOW_TABLE_GROWTH=1 |
| self.set_setting('RESERVED_FUNCTION_POINTERS', 2) |
| self.emcc_args += ['-lexports.js'] |
| self.do_run_in_out_file_test('tests', 'core', 'test_get_exported_function') |
| |
| # Tests the emscripten_get_exported_function() API. |
| def test_minimal_runtime_emscripten_get_exported_function(self): |
| # Could also test with -s ALLOW_TABLE_GROWTH=1 |
| self.set_setting('RESERVED_FUNCTION_POINTERS', 2) |
| self.emcc_args += ['-lexports.js', '-s', 'MINIMAL_RUNTIME=1'] |
| self.do_run_in_out_file_test('tests', 'core', 'test_get_exported_function') |
| |
| # Marked as impure since the WASI reactor modules (modules without main) |
| # are not yet suppored by the wasm engines we test against. |
| @also_with_standalone_wasm(impure=True) |
| def test_undefined_main(self): |
| # Traditionally in emscripten we allow main to be undefined. This allows programs with a main |
| # and libraries without a main to be compiled identically. |
| # However we are trying to move away from that model to a more explicit opt-out model. See: |
| # https://github.com/emscripten-core/emscripten/issues/9640 |
| if not self.get_setting('LLD_REPORT_UNDEFINED') and not self.get_setting('STRICT') and not self.get_setting('STANDALONE_WASM'): |
| self.do_run_in_out_file_test('tests', 'core', 'test_ctors_no_main') |
| |
| # Disabling IGNORE_MISSING_MAIN should cause link to fail due to missing main |
| self.set_setting('IGNORE_MISSING_MAIN', 0) |
| err = self.expect_fail([EMCC, path_from_root('tests', 'core', 'test_ctors_no_main.cpp')] + self.get_emcc_args()) |
| self.assertContained('error: entry symbol not defined (pass --no-entry to suppress): main', err) |
| |
| # If we pass --no-entry or set EXPORTED_FUNCTIONS to empty should never see any errors |
| self.emcc_args.append('--no-entry') |
| self.do_run_in_out_file_test('tests', 'core', 'test_ctors_no_main') |
| |
| self.emcc_args.remove('--no-entry') |
| self.set_setting('EXPORTED_FUNCTIONS', []) |
| self.do_run_in_out_file_test('tests', 'core', 'test_ctors_no_main') |
| |
| # Tests the operation of API found in #include <emscripten/math.h> |
| def test_emscripten_math(self): |
| self.do_run_in_out_file_test('tests', 'core', 'test_emscripten_math') |
| |
| |
| # Generate tests for everything |
| def make_run(name, emcc_args, settings=None, env=None): |
| if env is None: |
| env = {} |
| if settings is None: |
| settings = {} |
| |
| TT = type(name, (TestCoreBase,), dict(run_name=name, env=env, __module__=__name__)) # noqa |
| |
| def tearDown(self): |
| try: |
| super(TT, self).tearDown() |
| finally: |
| for k, v in self.env.items(): |
| del os.environ[k] |
| |
| TT.tearDown = tearDown |
| |
| def setUp(self): |
| super(TT, self).setUp() |
| for k, v in self.env.items(): |
| assert k not in os.environ, k + ' should not be in environment' |
| os.environ[k] = v |
| |
| os.chdir(self.get_dir()) # Ensure the directory exists and go there |
| |
| for k, v in settings.items(): |
| self.set_setting(k, v) |
| |
| self.emcc_args += emcc_args |
| # avoid various compiler warnings in our test output |
| self.emcc_args += [ |
| '-Wno-dynamic-class-memaccess', '-Wno-format', |
| '-Wno-format-extra-args', '-Wno-format-security', |
| '-Wno-pointer-bool-conversion', '-Wno-unused-volatile-lvalue', |
| '-Wno-c++11-compat-deprecated-writable-strings', |
| '-Wno-invalid-pp-token', '-Wno-shift-negative-value' |
| ] |
| |
| TT.setUp = setUp |
| |
| return TT |
| |
| |
| # Main asm.js test modes |
| if not shared.Settings.WASM_BACKEND: |
| asm0 = make_run('asm0', emcc_args=[], settings={'ASM_JS': 2, 'WASM': 0}) |
| asm2 = make_run('asm2', emcc_args=['-O2'], settings={'WASM': 0}) |
| asm3 = make_run('asm3', emcc_args=['-O3'], settings={'WASM': 0}) |
| asm2g = make_run('asm2g', emcc_args=['-O2', '-g'], settings={'WASM': 0, 'ASSERTIONS': 1, 'SAFE_HEAP': 1}) |
| |
| # Main wasm test modes |
| wasm0 = make_run('wasm0', emcc_args=['-O0']) |
| wasm0g = make_run('wasm0g', emcc_args=['-O0', '-g']) |
| wasm1 = make_run('wasm1', emcc_args=['-O1']) |
| wasm2 = make_run('wasm2', emcc_args=['-O2']) |
| wasm2g = make_run('wasm2g', emcc_args=['-O2', '-g']) |
| wasm3 = make_run('wasm3', emcc_args=['-O3']) |
| wasms = make_run('wasms', emcc_args=['-Os']) |
| wasmz = make_run('wasmz', emcc_args=['-Oz']) |
| |
| wasmlto0 = make_run('wasmlto0', emcc_args=['-flto', '-O0', '--llvm-lto', '1']) |
| wasmlto1 = make_run('wasmlto1', emcc_args=['-flto', '-O1', '--llvm-lto', '1']) |
| wasmlto2 = make_run('wasmlto2', emcc_args=['-flto', '-O2', '--llvm-lto', '1']) |
| wasmlto3 = make_run('wasmlto3', emcc_args=['-flto', '-O3', '--llvm-lto', '1']) |
| wasmltos = make_run('wasmltos', emcc_args=['-flto', '-Os', '--llvm-lto', '1']) |
| wasmltoz = make_run('wasmltoz', emcc_args=['-flto', '-Oz', '--llvm-lto', '1']) |
| |
| if shared.Settings.WASM_BACKEND: |
| wasm2js0 = make_run('wasm2js0', emcc_args=['-O0'], settings={'WASM': 0}) |
| wasm2js1 = make_run('wasm2js1', emcc_args=['-O1'], settings={'WASM': 0}) |
| wasm2js2 = make_run('wasm2js2', emcc_args=['-O2'], settings={'WASM': 0}) |
| wasm2js3 = make_run('wasm2js3', emcc_args=['-O3'], settings={'WASM': 0}) |
| wasm2jss = make_run('wasm2jss', emcc_args=['-Os'], settings={'WASM': 0}) |
| wasm2jsz = make_run('wasm2jsz', emcc_args=['-Oz'], settings={'WASM': 0}) |
| |
| # Secondary test modes - run directly when there is a specific need |
| |
| # features |
| |
| simd2 = make_run('simd2', emcc_args=['-O2', '-msimd128']) |
| bulkmem2 = make_run('bulkmem2', emcc_args=['-O2', '-mbulk-memory']) |
| |
| # asm.js |
| asm2f = make_run('asm2f', emcc_args=['-Oz', '-Wno-almost-asm'], settings={'PRECISE_F32': 1, 'ALLOW_MEMORY_GROWTH': 1, 'WASM': 0}) |
| asm2nn = make_run('asm2nn', emcc_args=['-O2'], settings={'WASM': 0}, env={'EMCC_NATIVE_OPTIMIZER': '0'}) |
| |
| # wasm |
| wasm2s = make_run('wasm2s', emcc_args=['-O2'], settings={'SAFE_HEAP': 1}) |
| wasm2ss = make_run('wasm2ss', emcc_args=['-O2'], settings={'STACK_OVERFLOW_CHECK': 2}) |
| # Add DEFAULT_TO_CXX=0 |
| strict = make_run('strict', emcc_args=[], settings={'STRICT': 1}) |
| |
| if shared.Settings.WASM_BACKEND: |
| lsan = make_run('lsan', emcc_args=['-fsanitize=leak'], settings={'ALLOW_MEMORY_GROWTH': 1}) |
| asan = make_run('asan', emcc_args=['-fsanitize=address'], settings={'ALLOW_MEMORY_GROWTH': 1, 'ASAN_SHADOW_SIZE': 128 * 1024 * 1024}) |
| asani = make_run('asani', emcc_args=['-fsanitize=address', '--pre-js', os.path.join(os.path.dirname(__file__), 'asan-no-leak.js')], |
| settings={'ALLOW_MEMORY_GROWTH': 1}) |
| |
| # Experimental modes (not tested by CI) |
| lld = make_run('lld', emcc_args=[], settings={'LLD_REPORT_UNDEFINED': 1}) |
| |
| # TestCoreBase is just a shape for the specific subclasses, we don't test it itself |
| del TestCoreBase # noqa |