#!/usr/bin/env python ''' Simple test runner See settings.py file for options¶ms. Edit as needed. These tests can be run in parallel using nose, for example nosetests --processes=4 -v -s tests/runner.py will use 4 processes. To install nose do something like |pip install nose| or |sudo apt-get install python-nose|. ''' from subprocess import Popen, PIPE, STDOUT import os, unittest, tempfile, shutil, time, inspect, sys, math, glob, tempfile, re, difflib, webbrowser, hashlib, BaseHTTPServer, threading, platform # Setup __rootpath__ = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) def path_from_root(*pathelems): return os.path.join(__rootpath__, *pathelems) sys.path += [path_from_root('')] import tools.shared from tools.shared import * # Sanity check for config try: assert COMPILER_OPTS != None except: raise Exception('Cannot find "COMPILER_OPTS" definition. Is %s set up properly? You may need to copy the template from settings.py into it.' % EM_CONFIG) # Core test runner class, shared between normal tests and benchmarks class RunnerCore(unittest.TestCase): save_dir = os.environ.get('EM_SAVE_DIR') save_JS = 0 stderr_redirect = STDOUT # This avoids cluttering the test runner output, which is stderr too, with compiler warnings etc. # Change this to None to get stderr reporting, for debugging purposes def setUp(self): global Settings Settings.reset() Settings = tools.shared.Settings self.banned_js_engines = [] if not self.save_dir: dirname = tempfile.mkdtemp(prefix='emscripten_test_' + self.__class__.__name__ + '_', dir=TEMP_DIR) else: dirname = EMSCRIPTEN_TEMP_DIR if not os.path.exists(dirname): os.makedirs(dirname) self.working_dir = dirname os.chdir(dirname) def tearDown(self): if self.save_JS: for name in os.listdir(self.get_dir()): if name.endswith(('.o.js', '.cc.js')): suff = '.'.join(name.split('.')[-2:]) shutil.copy(os.path.join(self.get_dir(), name), os.path.join(TEMP_DIR, self.id().replace('__main__.', '').replace('.test_', '.')+'.'+suff)) if not self.save_dir: shutil.rmtree(self.get_dir()) def skip(self, why): print >> sys.stderr, ' ' % why, def get_dir(self): return self.working_dir def get_shared_library_name(self, linux_name): if platform.system() == 'Linux': return linux_name elif platform.system() == 'Darwin': return linux_name.replace('.so', '') + '.dylib' else: print >> sys.stderr, 'get_shared_library_name needs to be implemented on %s' % platform.system() return linux_name def get_stdout_path(self): return os.path.join(self.get_dir(), 'stdout') def prep_ll_run(self, filename, ll_file, force_recompile=False, build_ll_hook=None): if ll_file.endswith(('.bc', '.o')): if ll_file != filename + '.o': shutil.copy(ll_file, filename + '.o') Building.llvm_dis(filename) else: shutil.copy(ll_file, filename + '.o.ll') #force_recompile = force_recompile or os.stat(filename + '.o.ll').st_size > 50000 # if the file is big, recompile just to get ll_opts # Recompiling just for dfe in ll_opts is too costly if Building.LLVM_OPTS or force_recompile or build_ll_hook: Building.ll_opts(filename) if build_ll_hook: need_post = build_ll_hook(filename) Building.llvm_as(filename) shutil.move(filename + '.o.ll', filename + '.o.ll.pre') # for comparisons later if Building.LLVM_OPTS: Building.llvm_opts(filename) Building.llvm_dis(filename) if build_ll_hook and need_post: build_ll_hook(filename) Building.llvm_as(filename) shutil.move(filename + '.o.ll', filename + '.o.ll.post') # for comparisons later Building.llvm_dis(filename) # Generate JS from ll, and optionally modify the generated JS with a post_build function. Note # that post_build is called on unoptimized JS, so we send it to emcc (otherwise, if run after # emcc, it would not apply on the optimized/minified JS) def ll_to_js(self, filename, extra_emscripten_args, post_build): if self.emcc_args is None: Building.emscripten(filename, append_ext=True, extra_args=extra_emscripten_args) if post_build is not None: exec post_build in locals() shutil.copyfile(filename + '.o.js', filename + '.o.js.prepost.js') process(filename + '.o.js') else: transform_args = [] if post_build: transform_filename = os.path.join(self.get_dir(), 'transform.py') transform = open(transform_filename, 'w') transform.write(''' import sys sys.path += ['%s'] ''' % path_from_root('')) transform.write(post_build) transform.write(''' process(sys.argv[1]) ''') transform.close() transform_args = ['--js-transform', "python %s" % transform_filename] Building.emcc(filename + '.o.ll', Settings.serialize() + self.emcc_args + transform_args, filename + '.o.js') # Build JavaScript code from source code def build(self, src, dirname, filename, output_processor=None, main_file=None, additional_files=[], libraries=[], includes=[], build_ll_hook=None, extra_emscripten_args=[], post_build=None): Building.pick_llvm_opts(3) # pick llvm opts here, so we include changes to Settings in the test case code # Copy over necessary files for compiling the source if main_file is None: f = open(filename, 'w') f.write(src) f.close() final_additional_files = [] for f in additional_files: final_additional_files.append(os.path.join(dirname, os.path.basename(f))) shutil.copyfile(f, final_additional_files[-1]) additional_files = final_additional_files else: # copy whole directory, and use a specific main .cpp file shutil.rmtree(dirname) shutil.copytree(src, dirname) shutil.move(os.path.join(dirname, main_file), filename) # the additional files were copied; alter additional_files to point to their full paths now additional_files = map(lambda f: os.path.join(dirname, f), additional_files) os.chdir(self.get_dir()) # C++ => LLVM binary for f in [filename] + additional_files: try: # Make sure we notice if compilation steps failed os.remove(f + '.o') except: pass args = [Building.COMPILER, '-emit-llvm'] + COMPILER_OPTS + Building.COMPILER_TEST_OPTS + \ ['-I', dirname, '-I', os.path.join(dirname, 'include')] + \ map(lambda include: '-I' + include, includes) + \ ['-c', f, '-o', f + '.o'] output = Popen(args, stdout=PIPE, stderr=self.stderr_redirect).communicate()[0] assert os.path.exists(f + '.o'), 'Source compilation error: ' + output # Link all files if len(additional_files) + len(libraries) > 0: shutil.move(filename + '.o', filename + '.o.alone') Building.link([filename + '.o.alone'] + map(lambda f: f + '.o', additional_files) + libraries, filename + '.o') if not os.path.exists(filename + '.o'): print "Failed to link LLVM binaries:\n\n", output raise Exception("Linkage error"); # Finalize self.prep_ll_run(filename, filename + '.o', build_ll_hook=build_ll_hook) # BC => JS self.ll_to_js(filename, extra_emscripten_args, post_build) if output_processor is not None: output_processor(open(filename + '.o.js').read()) def run_generated_code(self, engine, filename, args=[], check_timeout=True): stdout = os.path.join(self.get_dir(), 'stdout') # use files, as PIPE can get too full and hang us stderr = os.path.join(self.get_dir(), 'stderr') try: cwd = os.getcwd() except: cwd = None os.chdir(self.get_dir()) run_js(filename, engine, args, check_timeout, stdout=open(stdout, 'w'), stderr=open(stderr, 'w')) if cwd is not None: os.chdir(cwd) ret = open(stdout, 'r').read() + open(stderr, 'r').read() assert 'strict warning:' not in ret, 'We should pass all strict mode checks: ' + ret return ret def build_native(self, filename): Popen([CLANG, '-O2', filename, '-o', filename+'.native'], stdout=PIPE).communicate()[0] def run_native(self, filename, args): Popen([filename+'.native'] + args, stdout=PIPE).communicate()[0] def assertIdentical(self, x, y): if x != y: raise Exception("Expected to have '%s' == '%s', diff:\n\n%s" % ( limit_size(x), limit_size(y), limit_size(''.join([a.rstrip()+'\n' for a in difflib.unified_diff(x.split('\n'), y.split('\n'), fromfile='expected', tofile='actual')])) )) def assertContained(self, values, string, additional_info=''): if type(values) not in [list, tuple]: values = [values] for value in values: if type(string) is not str: string = string() if value in string: return # success raise Exception("Expected to find '%s' in '%s', diff:\n\n%s\n%s" % ( limit_size(values[0]), limit_size(string), limit_size(''.join([a.rstrip()+'\n' for a in difflib.unified_diff(values[0].split('\n'), string.split('\n'), fromfile='expected', tofile='actual')])), additional_info )) def assertNotContained(self, value, string): if type(value) is not str: value = value() # lazy loading if type(string) is not str: string = string() if value in string: raise Exception("Expected to NOT find '%s' in '%s', diff:\n\n%s" % ( limit_size(value), limit_size(string), limit_size(''.join([a.rstrip()+'\n' for a in difflib.unified_diff(value.split('\n'), string.split('\n'), fromfile='expected', tofile='actual')])) )) library_cache = {} def get_library(self, name, generated_libs, configure=['./configure'], configure_args=[], make=['make'], make_args=['-j', '2'], cache=True): build_dir = self.get_build_dir() output_dir = self.get_dir() cache_name = name + '|' + Building.COMPILER if self.library_cache is not None: if cache and self.library_cache.get(cache_name): print >> sys.stderr, ' ', bc_file = os.path.join(output_dir, 'lib' + name + '.bc') f = open(bc_file, 'wb') f.write(self.library_cache[cache_name]) f.close() return bc_file print >> sys.stderr, ' ', return Building.build_library(name, build_dir, output_dir, generated_libs, configure, configure_args, make, make_args, self.library_cache, cache_name, copy_project=True) ################################################################################################### sys.argv = map(lambda arg: arg if not arg.startswith('test_') else 'default.' + arg, sys.argv) Cache.erase() # Wipe the cache, so that we always test populating it in the tests, benchmarks, etc. if 'benchmark' not in str(sys.argv) and 'sanity' not in str(sys.argv): # Tests print "Running Emscripten tests..." class T(RunnerCore): # Short name, to make it more fun to use manually on the commandline ## Does a complete test - builds, runs, checks output, etc. def do_run(self, src, expected_output, args=[], output_nicerizer=None, output_processor=None, no_build=False, main_file=None, additional_files=[], js_engines=None, post_build=None, basename='src.cpp', libraries=[], includes=[], force_c=False, build_ll_hook=None, extra_emscripten_args=[]): if force_c or (main_file is not None and main_file[-2:]) == '.c': basename = 'src.c' Building.COMPILER = to_cc(Building.COMPILER) dirname = self.get_dir() filename = os.path.join(dirname, basename) if not no_build: self.build(src, dirname, filename, main_file=main_file, additional_files=additional_files, libraries=libraries, includes=includes, build_ll_hook=build_ll_hook, extra_emscripten_args=extra_emscripten_args, post_build=post_build) # Run in both JavaScript engines, if optimizing - significant differences there (typed arrays) if js_engines is None: js_engines = JS_ENGINES if Settings.USE_TYPED_ARRAYS: js_engines = filter(lambda engine: engine != V8_ENGINE, js_engines) # V8 issue 1822 js_engines = filter(lambda engine: engine not in self.banned_js_engines, js_engines) if len(js_engines) == 0: return self.skip('No JS engine present to run this test with. Check %s and settings.py and the paths therein.' % EM_CONFIG) for engine in js_engines: engine = filter(lambda arg: arg != '-n', engine) # SpiderMonkey issue 716255 js_output = self.run_generated_code(engine, filename + '.o.js', args) if output_nicerizer is not None: js_output = output_nicerizer(js_output) self.assertContained(expected_output, js_output) self.assertNotContained('ERROR', js_output) #shutil.rmtree(dirname) # TODO: leave no trace in memory. But for now nice for debugging # No building - just process an existing .ll file (or .bc, which we turn into .ll) def do_ll_run(self, ll_file, expected_output=None, args=[], js_engines=None, output_nicerizer=None, post_build=None, force_recompile=False, build_ll_hook=None, extra_emscripten_args=[]): filename = os.path.join(self.get_dir(), 'src.cpp') self.prep_ll_run(filename, ll_file, force_recompile, build_ll_hook) self.ll_to_js(filename, extra_emscripten_args, post_build) self.do_run(None, expected_output, args, no_build=True, js_engines=js_engines, output_nicerizer=output_nicerizer, post_build=None) # post_build was already done in ll_to_js, this do_run call is just to test the output def test_hello_world(self): src = ''' #include int main() { printf("hello, world!\\n"); return 0; } ''' self.do_run(src, 'hello, world!') def test_intvars(self): src = ''' #include int global = 20; int *far; int main() { int x = 5; int y = x+17; int z = (y-1)/2; // Should stay an integer after division! y += 1; int w = x*3+4; int k = w < 15 ? 99 : 101; far = &k; *far += global; int i = k > 100; // Should be an int, not a bool! int j = i << 6; j >>= 1; j = j ^ 5; int h = 1; h |= 0; int p = h; p &= 0; printf("*%d,%d,%d,%d,%d,%d,%d,%d,%d*\\n", x, y, z, w, k, i, j, h, p); long hash = -1; size_t perturb; int ii = 0; for (perturb = hash; ; perturb >>= 5) { printf("%d:%d", ii, perturb); ii++; if (ii == 9) break; printf(","); } printf("*\\n"); printf("*%.1d,%.2d*\\n", 56, 9); // Fixed-point math on 64-bit ints. Tricky to support since we have no 64-bit shifts in JS { struct Fixed { static int Mult(int a, int b) { return ((long long)a * (long long)b) >> 16; } }; printf("fixed:%d\\n", Fixed::Mult(150000, 140000)); } printf("*%ld*%p\\n", (long)21, &hash); // The %p should not enter an infinite loop! return 0; } ''' self.do_run(src, '*5,23,10,19,121,1,37,1,0*\n0:-1,1:134217727,2:4194303,3:131071,4:4095,5:127,6:3,7:0,8:0*\n*56,09*\nfixed:320434\n*21*') def test_sintvars(self): Settings.CORRECT_SIGNS = 1 # Relevant to this test src = ''' #include struct S { char *match_start; char *strstart; }; int main() { struct S _s; struct S *s = &_s; unsigned short int sh; s->match_start = (char*)32522; s->strstart = (char*)(32780); printf("*%d,%d,%d*\\n", (int)s->strstart, (int)s->match_start, (int)(s->strstart - s->match_start)); sh = s->strstart - s->match_start; printf("*%d,%d*\\n", sh, sh>>7); s->match_start = (char*)32999; s->strstart = (char*)(32780); printf("*%d,%d,%d*\\n", (int)s->strstart, (int)s->match_start, (int)(s->strstart - s->match_start)); sh = s->strstart - s->match_start; printf("*%d,%d*\\n", sh, sh>>7); } ''' output = '*32780,32522,258*\n*258,2*\n*32780,32999,-219*\n*65317,510*' Settings.CORRECT_OVERFLOWS = 0 # We should not need overflow correction to get this right self.do_run(src, output, force_c=True) def test_i64(self): for i64_mode in [0,1]: if i64_mode == 0 and Settings.USE_TYPED_ARRAYS != 0: continue # Typed arrays truncate i64 if i64_mode == 1 and Settings.QUANTUM_SIZE == 1: continue # TODO: i64 mode 1 for q1 Settings.I64_MODE = i64_mode src = ''' #include int main() { long long a = 0x2b00505c10; long long b = a >> 29; long long c = a >> 32; long long d = a >> 34; printf("*%Ld,%Ld,%Ld,%Ld*\\n", a, b, c, d); unsigned long long ua = 0x2b00505c10; unsigned long long ub = ua >> 29; unsigned long long uc = ua >> 32; unsigned long long ud = ua >> 34; printf("*%Ld,%Ld,%Ld,%Ld*\\n", ua, ub, uc, ud); long long x = 0x0000def123450789ULL; // any bigger than this, and we long long y = 0x00020ef123456089ULL; // start to run into the double precision limit! printf("*%Ld,%Ld,%Ld,%Ld,%Ld*\\n", x, y, x | y, x & y, x ^ y, x >> 2, y << 2); printf("*"); long long z = 13; int n = 0; while (z > 1) { printf("%.2f,", (float)z); // these must be integers! z = z >> 1; n++; } printf("*%d*\\n", n); return 0; } ''' self.do_run(src, '*184688860176,344,43,10*\n*184688860176,344,43,10*\n*245127260211081,579378795077769,808077213656969,16428841631881,791648372025088*\n*13.00,6.00,3.00,*3*') if Settings.QUANTUM_SIZE == 1: return self.skip('TODO: i64 mode 1 for q1') # Stuff that only works in i64_mode = 1 Settings.I64_MODE = 1 src = r''' #include #include #include int64_t returner1() { return 0x0000def123450789ULL; } int64_t returner2(int test) { while (test > 10) test /= 2; // confuse the compiler so it doesn't eliminate this function return test > 5 ? 0x0000def123450123ULL : 0ULL; } void modifier1(int64_t t) { t |= 12; printf("m1: %Ld\n", t); } void modifier2(int64_t &t) { t |= 12; } int truthy() { int x = time(0); while (x > 10) { x |= 7; x /= 2; } return x < 3; } struct IUB { int c; long long d; }; IUB iub[] = { { 55, 17179869201 }, { 122, 25769803837 }, }; int main() { int64_t x1 = 0x1234def123450789ULL; int64_t x2 = 0x1234def123450788ULL; int64_t x3 = 0x1234def123450789ULL; printf("*%Ld\n%d,%d,%d,%d,%d\n%d,%d,%d,%d,%d*\n", x1, x1==x2, x1x2, x1>=x2, // note: some rounding in the printing! x1==x3, x1x3, x1>=x3); printf("*%Ld*\n", returner1()); printf("*%Ld*\n", returner2(30)); uint64_t maxx = -1ULL; printf("*%Lu*\n*%Lu*\n", maxx, maxx >> 5); // Make sure params are not modified if they shouldn't be int64_t t = 123; modifier1(t); printf("*%Ld*\n", t); modifier2(t); printf("*%Ld*\n", t); // global structs with i64s printf("*%d,%Ld*\n*%d,%Ld*\n", iub[0].c, iub[0].d, iub[1].c, iub[1].d); // Bitshifts { int64_t a = -1; int64_t b = a >> 29; int64_t c = a >> 32; int64_t d = a >> 34; printf("*%Ld,%Ld,%Ld,%Ld*\n", a, b, c, d); uint64_t ua = -1; int64_t ub = ua >> 29; int64_t uc = ua >> 32; int64_t ud = ua >> 34; printf("*%Ld,%Ld,%Ld,%Ld*\n", ua, ub, uc, ud); } // Math mixtures with doubles { uint64_t a = 5; double b = 6.8; uint64_t c = a * b; printf("*prod:%llu*\n*%d,%d,%d*\n", c, (int)&a, (int)&b, (int)&c); // printing addresses prevents optimizations } // Basic (rounded, for now) math. Just check compilation. int64_t a = 0x1234def123450789ULL; a--; if (truthy()) a--; // confuse optimizer int64_t b = 0x1234000000450789ULL; b++; if (truthy()) b--; // confuse optimizer printf("*%Ld,%Ld,%Ld,%Ld*\n", (a+b)/5000, (a-b)/5000, (a*3)/5000, (a/5)/5000); return 0; } ''' self.do_run(src, '*1311918518731868200\n' + '0,0,0,1,1\n' + '1,0,1,0,1*\n' + '*245127260211081*\n' + '*245127260209443*\n' + '*18446744073709552000*\n' + '*576460752303423500*\n' + 'm1: 127\n' + '*123*\n' + '*127*\n' + '*55,17179869201*\n' + '*122,25769803837*\n' + '*-1,-1,-1,-1*\n' + '*-1,34359738367,4294967295,1073741823*\n' + '*prod:34*') Settings.CORRECT_SIGNS = 1 src = r''' #include #include int main() { // i32 vs i64 int32_t small = -1; int64_t large = -1; printf("*%d*\n", small == large); small++; printf("*%d*\n", small == large); uint32_t usmall = -1; uint64_t ularge = -1; printf("*%d*\n", usmall == ularge); return 0; } ''' self.do_run(src, '*1*\n*0*\n*0*\n') def test_unaligned(self): if Settings.QUANTUM_SIZE == 1: return self.skip('No meaning to unaligned addresses in q1') src = r''' #include struct S { double x; int y; }; int main() { // the 64-bit value here will not always be 8-byte aligned S s[3] = { {0x12a751f430142, 22}, {0x17a5c85bad144, 98}, {1, 1}}; 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 if Settings.USE_TYPED_ARRAYS != 2: return self.skip('No meaning to unaligned addresses without t2') src = r''' #include 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("*%Ld*\n", *t); return 0; } ''' Settings.EMULATE_UNALIGNED_ACCESSES = 0 try: self.do_run(src, '*300:1*\n*515559*\n*42949672960*\n') except Exception, e: assert 'must be aligned' in str(e), e # expected to fail without emulation # XXX TODO Settings.EMULATE_UNALIGNED_ACCESSES = 1 #self.do_run(src, '*300:1*\n*515559*\n*42949672960*\n') # but succeeds with it def test_unsigned(self): Settings.CORRECT_SIGNS = 1 # We test for exactly this sort of thing here Settings.CHECK_SIGNS = 0 src = ''' #include 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*') # Now let's see some code that should just work in USE_TYPED_ARRAYS == 2, but requires # corrections otherwise if Settings.USE_TYPED_ARRAYS == 2: Settings.CORRECT_SIGNS = 0 Settings.CHECK_SIGNS = 1 else: Settings.CORRECT_SIGNS = 1 Settings.CHECK_SIGNS = 0 src = ''' #include 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): if self.emcc_args is None: Settings.SAFE_HEAP = 0 # bitfields do loads on invalid areas, by design src = ''' #include struct bitty { unsigned x : 1; unsigned y : 1; unsigned z : 1; }; int main() { bitty b; printf("*"); for (int i = 0; i <= 1; i++) for (int j = 0; j <= 1; j++) for (int k = 0; k <= 1; k++) { b.x = i; b.y = j; b.z = k; printf("%d,%d,%d,", b.x, b.y, b.z); } printf("*\\n"); return 0; } ''' self.do_run(src, '*0,0,0,0,0,1,0,1,0,0,1,1,1,0,0,1,0,1,1,1,0,1,1,1,*') def test_floatvars(self): src = ''' #include int main() { float x = 1.234, y = 3.5, q = 0.00000001; y *= 3; int z = x < y; printf("*%d,%d,%.1f,%d,%.4f,%.2f*\\n", z, int(y), y, (int)x, x, q); /* // Rounding behavior float fs[6] = { -2.75, -2.50, -2.25, 2.25, 2.50, 2.75 }; double ds[6] = { -2.75, -2.50, -2.25, 2.25, 2.50, 2.75 }; for (int i = 0; i < 6; i++) printf("*int(%.2f)=%d,%d*\\n", fs[i], int(fs[i]), int(ds[i])); */ return 0; } ''' self.do_run(src, '*1,10,10.5,1,1.2340,0.00*') def test_math(self): src = ''' #include #include #include int main() { printf("*%.2f,%.2f,%d", M_PI, -M_PI, (1/0.0) > 1e300); // could end up as infinity, or just a very very big number printf(",%d", finite(NAN) != 0); printf(",%d", finite(INFINITY) != 0); printf(",%d", finite(-INFINITY) != 0); printf(",%d", finite(12.3) != 0); printf(",%d", isinf(NAN) != 0); printf(",%d", isinf(INFINITY) != 0); printf(",%d", isinf(-INFINITY) != 0); printf(",%d", isinf(12.3) != 0); div_t div_result = div(23, 10); printf(",%d", div_result.quot); printf(",%d", div_result.rem); double sine = -1.0, cosine = -1.0; sincos(0.0, &sine, &cosine); printf(",%1.1lf", sine); printf(",%1.1lf", cosine); float fsine = -1.0f, fcosine = -1.0f; sincosf(0.0, &fsine, &fcosine); printf(",%1.1f", fsine); printf(",%1.1f", fcosine); printf("*\\n"); return 0; } ''' self.do_run(src, '*3.14,-3.14,1,0,0,0,1,0,1,1,0,2,3,0.0,1.0,0.0,1.0*') def test_math_hyperbolic(self): src = open(path_from_root('tests', 'hyperbolic', 'src.c'), 'r').read() expected = open(path_from_root('tests', 'hyperbolic', 'output.txt'), 'r').read() self.do_run(src, expected) def test_getgep(self): # Generated code includes getelementptr (getelementptr, 0, 1), i.e., GEP as the first param to GEP src = ''' #include struct { int y[10]; int z[10]; } commonblock; int main() { for (int i = 0; i < 10; ++i) { commonblock.y[i] = 1; commonblock.z[i] = 2; } printf("*%d %d*\\n", commonblock.y[0], commonblock.z[0]); return 0; } ''' self.do_run(src, '*1 2*') def test_if(self): src = ''' #include int main() { int x = 5; if (x > 3) { printf("*yes*\\n"); } return 0; } ''' self.do_run(src, '*yes*') def test_if_else(self): src = ''' #include int main() { int x = 5; if (x > 10) { printf("*yes*\\n"); } else { printf("*no*\\n"); } return 0; } ''' self.do_run(src, '*no*') def test_loop(self): src = ''' #include int main() { int x = 5; for (int i = 0; i < 6; i++) { x += x*i; if (x > 1000) { if (x % 7 == 0) printf("cheez\\n"); x /= 2; break; } } printf("*%d*\\n", x); return 0; } ''' self.do_run(src, '*1800*') generated = open('src.cpp.o.js', 'r').read() assert '__label__ ==' not in generated, 'We should hoist into the loop' def test_stack(self): src = ''' #include int test(int i) { int x = 10; if (i > 0) { return test(i-1); } return int(&x); // both for the number, and forces x to not be nativized } int main() { // We should get the same value for the first and last - stack has unwound int x1 = test(0); int x2 = test(100); int x3 = test(0); printf("*%d,%d*\\n", x3-x1, x2 != x1); return 0; } ''' self.do_run(src, '*0,1*') def test_strings(self): src = ''' #include #include #include int main(int argc, char **argv) { int x = 5, y = 9, magic = 7; // fool compiler with magic memmove(&x, &y, magic-7); // 0 should not crash us int xx, yy, zz; char s[32]; int cc = sscanf("abc_10.b1_xyz_543_defg", "abc_%d.%2x_xyz_%3d_%3s", &xx, &yy, &zz, s); printf("%d:%d,%d,%d,%s\\n", cc, xx, yy, zz, s); printf("%d\\n", argc); puts(argv[1]); puts(argv[2]); printf("%d\\n", atoi(argv[3])+2); const char *foolingthecompiler = "\\rabcd"; printf("%d\\n", strlen(foolingthecompiler)); // Tests parsing /0D in llvm - should not be a 0 (end string) then a D! printf("%s\\n", NULL); // Should print '(null)', not the string at address 0, which is a real address for us! printf("/* a comment */\\n"); // Should not break the generated code! printf("// another\\n"); // Should not break the generated code! char* strdup_val = strdup("test"); printf("%s\\n", strdup_val); free(strdup_val); return 0; } ''' self.do_run(src, '4:10,177,543,def\n4\nwowie\ntoo\n76\n5\n(null)\n/* a comment */\n// another\ntest\n', ['wowie', 'too', '74']) def test_errar(self): src = r''' #include #include #include int main() { char* err; char buffer[200]; err = strerror(EDOM); strerror_r(EWOULDBLOCK, buffer, 200); printf("<%s>\n", err); printf("<%s>\n", buffer); printf("<%d>\n", strerror_r(EWOULDBLOCK, buffer, 0)); errno = 123; printf("<%d>\n", errno); return 0; } ''' expected = ''' <34> <123> ''' self.do_run(src, re.sub('(^|\n)\s+', '\\1', expected)) def test_mainenv(self): src = ''' #include int main(int argc, char **argv, char **envp) { printf("*%p*\\n", envp); return 0; } ''' self.do_run(src, '*(nil)*') def test_funcs(self): src = ''' #include int funcy(int x) { return x*9; } int main() { printf("*%d,%d*\\n", funcy(8), funcy(10)); return 0; } ''' self.do_run(src, '*72,90*') def test_structs(self): src = ''' #include struct S { int x, y; }; int main() { S a, b; a.x = 5; a.y = 6; b.x = 101; b.y = 7009; S *c, *d; c = &a; c->x *= 2; c = &b; c->y -= 1; d = c; d->y += 10; printf("*%d,%d,%d,%d,%d,%d,%d,%d*\\n", a.x, a.y, b.x, b.y, c->x, c->y, d->x, d->y); return 0; } ''' self.do_run(src, '*10,6,101,7018,101,7018,101,7018*') gen_struct_src = ''' #include #include #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*') 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): src = ''' #include void alter(int *y) { *y += 5; } int main() { int x = 2; alter(&x); printf("*%d*\\n", x); return 0; } ''' self.do_run(src, '*7*') def test_globals(self): src = ''' #include char cache[256], *next = cache; int main() { cache[10] = 25; next[20] = 51; printf("*%d,%d*\\n", next[10], cache[20]); return 0; } ''' self.do_run(src, '*25,51*') def test_linked_list(self): src = ''' #include struct worker_args { int value; struct worker_args *next; }; int main() { worker_args a; worker_args b; a.value = 60; a.next = &b; b.value = 900; b.next = NULL; worker_args* c = &a; int total = 0; while (c) { total += c->value; c = c->next; } // Chunk of em worker_args chunk[10]; for (int i = 0; i < 9; i++) { chunk[i].value = i*10; chunk[i].next = &chunk[i+1]; } chunk[9].value = 90; chunk[9].next = &chunk[0]; c = chunk; do { total += c->value; c = c->next; } while (c != chunk); printf("*%d,%d*\\n", total, b.next); // NULL *is* 0, in C/C++. No JS null! (null == 0 is false, etc.) return 0; } ''' self.do_run(src, '*1410,0*') def test_sup(self): src = ''' #include 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 1; } ''' if Settings.QUANTUM_SIZE == 1: self.do_run(src, 'sizeofs:6,8\n*C___: 0,3,6,9<24*\n*Carr: 0,3,6,9<24*\n*C__w: 0,3,9,12<24*\n*Cp1_: 1,2,5,8<24*\n*Cp2_: 0,2,5,8<24*\n*Cint: 0,3,4,7<24*\n*C4__: 0,3,4,7<24*\n*C4_2: 0,3,5,8<20*\n*C__z: 0,3,5,8<28*') else: 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*') def test_assert(self): src = ''' #include #include int main() { assert(1 == true); // pass assert(1 == false); // fail return 1; } ''' self.do_run(src, 'Assertion failed: 1 == false') def test_exceptions(self): if Settings.QUANTUM_SIZE == 1: return self.skip("we don't support libcxx in q1") self.banned_js_engines = [NODE_JS] # node issue 1669, exception causes stdout not to be flushed Settings.DISABLE_EXCEPTION_CATCHING = 0 if self.emcc_args is None: if Building.LLVM_OPTS: return self.skip('optimizing bitcode before emcc can confuse libcxx inclusion') self.emcc_args = [] # libc++ auto-inclusion is only done if we use emcc src = ''' #include void thrower() { printf("infunc..."); throw(99); printf("FAIL"); } int main() { try { printf("*throw..."); throw(1); printf("FAIL"); } catch(...) { printf("caught!"); } try { thrower(); } catch(...) { printf("done!*\\n"); } return 1; } ''' self.do_run(src, '*throw...caught!infunc...done!*') Settings.DISABLE_EXCEPTION_CATCHING = 1 self.do_run(src, 'Compiled code throwing an exception') src = ''' #include 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 << "Catched..."; } try { function2(); } catch (MyException e) { std::cout << "Catched..."; } return 0; } ''' Settings.DISABLE_EXCEPTION_CATCHING = 0 self.do_run(src, 'Throw...Construct...Catched...Destruct...Throw...Construct...Copy...Catched...Destruct...Destruct...') def test_typed_exceptions(self): return self.skip('TODO: fix this for llvm 3.0') Settings.SAFE_HEAP = 0 # Throwing null will cause an ignorable null pointer access. Settings.EXCEPTION_DEBUG = 0 # Messes up expected output. src = open(path_from_root('tests', 'exceptions', 'typed.cpp'), 'r').read() expected = open(path_from_root('tests', 'exceptions', 'output.txt'), 'r').read() self.do_run(src, expected) def test_class(self): src = ''' #include struct Random { enum { IM = 139968, IA = 3877, IC = 29573 }; Random() : last(42) {} float get( float max = 1.0f ) { last = ( last * IA + IC ) % IM; return max * last / IM; } protected: unsigned int last; } rng1; int main() { Random rng2; int count = 0; for (int i = 0; i < 100; i++) { float x1 = rng1.get(); float x2 = rng2.get(); printf("%f, %f\\n", x1, x2); if (x1 != x2) count += 1; } printf("*%d*\\n", count); return 0; } ''' self.do_run(src, '*0*') def test_inherit(self): src = ''' #include struct Parent { int x1, x2; }; struct Child : Parent { int y; }; int main() { Parent a; a.x1 = 50; a.x2 = 87; Child b; b.x1 = 78; b.x2 = 550; b.y = 101; Child* c = (Child*)&a; c->x1 ++; c = &b; c->y --; printf("*%d,%d,%d,%d,%d,%d,%d*\\n", a.x1, a.x2, b.x1, b.x2, b.y, c->x1, c->x2); return 0; } ''' self.do_run(src, '*51,87,78,550,100,78,550*') def test_polymorph(self): src = ''' #include struct Pure { virtual int implme() = 0; }; struct Parent : Pure { virtual int getit() { return 11; }; int implme() { return 32; } }; struct Child : Parent { int getit() { return 74; } int implme() { return 1012; } }; struct Other { int one() { return 11; } int two() { return 22; } }; int main() { Parent *x = new Parent(); Parent *y = new Child(); printf("*%d,%d,%d,%d*\\n", x->getit(), y->getit(), x->implme(), y->implme()); Other *o = new Other; int (Other::*Ls)() = &Other::one; printf("*%d*\\n", (o->*(Ls))()); Ls = &Other::two; printf("*%d*\\n", (o->*(Ls))()); return 0; } ''' self.do_run(src, '*11,74,32,1012*\n*11*\n*22*') def test_dynamic_cast(self): src = ''' #include class CBase { virtual void dummy() {} }; class CDerived : public CBase { int a; }; class CDerivedest : public CDerived { float b; }; int main () { CBase *pa = new CBase; CBase *pb = new CDerived; CBase *pc = new CDerivedest; printf("a1: %d\\n", dynamic_cast(pa) != NULL); printf("a2: %d\\n", dynamic_cast(pa) != NULL); printf("a3: %d\\n", dynamic_cast(pa) != NULL); printf("b1: %d\\n", dynamic_cast(pb) != NULL); printf("b2: %d\\n", dynamic_cast(pb) != NULL); printf("b3: %d\\n", dynamic_cast(pb) != NULL); printf("c1: %d\\n", dynamic_cast(pc) != NULL); printf("c2: %d\\n", dynamic_cast(pc) != NULL); printf("c3: %d\\n", dynamic_cast(pc) != NULL); return 0; } ''' self.do_run(src, 'a1: 0\na2: 0\na3: 1\nb1: 0\nb2: 1\nb3: 1\nc1: 1\nc2: 1\nc3: 1\n') def test_funcptr(self): src = ''' #include int calc1() { return 26; } int calc2() { return 90; } typedef int (*fp_t)(); fp_t globally1 = calc1; fp_t globally2 = calc2; int nothing(const char *str) { return 0; } int main() { fp_t fp = calc1; void *vp = (void*)fp; fp_t fpb = (fp_t)vp; fp_t fp2 = calc2; void *vp2 = (void*)fp2; fp_t fpb2 = (fp_t)vp2; printf("*%d,%d,%d,%d,%d,%d*\\n", fp(), fpb(), fp2(), fpb2(), globally1(), globally2()); fp_t t = calc1; printf("*%d,%d", t == calc1, t == calc2); t = calc2; printf(",%d,%d*\\n", t == calc1, t == calc2); int (*other)(const char *str); other = nothing; other("*hello!*"); other = puts; other("*goodbye!*"); return 0; } ''' self.do_run(src, '*26,26,90,90,26,90*\n*1,0,0,1*\n*goodbye!*') def test_mathfuncptr(self): src = ''' #include #include int main(void) { float (*fn)(float) = &sqrtf; float (*fn2)(float) = &fabsf; printf("fn2(-5) = %d, fn(10) = %f\\n", (int)fn2(-5), fn(10)); return 0; } ''' self.do_run(src, 'fn2(-5) = 5, fn(10) = 3.16') def test_emptyclass(self): src = ''' #include struct Randomized { Randomized(int x) { printf("*zzcheezzz*\\n"); } }; int main( int argc, const char *argv[] ) { new Randomized(55); return 0; } ''' self.do_run(src, '*zzcheezzz*') def test_alloca(self): src = ''' #include #include int main() { char *pc; pc = (char *)alloca(5); printf("z:%d*%d*\\n", pc > 0, (int)pc); return 0; } ''' self.do_run(src, 'z:1*', force_c=True) if self.emcc_args is not None: # too slow in other modes # We should not blow up the stack with numerous allocas src = ''' #include #include func(int i) { char *pc = (char *)alloca(100); *pc = i; (*pc)++; return (*pc) % 10; } int main() { int total = 0; for (int i = 0; i < 1024*1024; i++) total += func(i); printf("ok:%d*\\n", total); return 0; } ''' self.do_run(src, 'ok:-32768*', force_c=True) # We should also not blow up the stack with byval arguments src = r''' #include struct vec { int x, y, z; vec(int x_, int y_, int z_) : x(x_), y(y_), z(z_) {} static vec add(vec a, vec b) { return vec(a.x+b.x, a.y+b.y, a.z+b.z); } }; int main() { int total = 0; for (int i = 0; i < 1000; i++) { for (int j = 0; j < 1000; j++) { vec c(i+i%10, j*2, i%255); vec d(j*2, j%255, i%120); vec f = vec::add(c, d); total += (f.x + f.y + f.z) % 100; total %= 10240; } } printf("sum:%d*\n", total); return 1; } ''' self.do_run(src, 'sum:9780*') # We should not blow up the stack with numerous varargs src = r''' #include #include void func(int 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); } int main() { for (int i = 0; i < 1024; i++) func(i); printf("ok!\n"); return 0; } ''' Settings.TOTAL_STACK = 1024 self.do_run(src, 'ok!') def test_array2(self): src = ''' #include static const double grid[4][2] = { {-3/3.,-1/3.},{+1/3.,-3/3.}, {-1/3.,+3/3.},{+3/3.,+1/3.} }; int main() { for (int i = 0; i < 4; i++) printf("%d:%.2f,%.2f ", i, grid[i][0], grid[i][1]); printf("\\n"); return 0; } ''' self.do_run(src, '0:-1.00,-0.33 1:0.33,-1.00 2:-0.33,1.00 3:1.00,0.33') def test_array2b(self): src = ''' #include static const struct { unsigned char left; unsigned char right; } prioritah[] = { {6, 6}, {6, 6}, {7, 95}, {7, 7} }; int main() { printf("*%d,%d\\n", prioritah[1].left, prioritah[1].right); printf("%d,%d*\\n", prioritah[2].left, prioritah[2].right); return 0; } ''' self.do_run(src, '*6,6\n7,95*') def test_constglobalstructs(self): src = ''' #include struct IUB { int c; double p; unsigned int pi; }; IUB iub[] = { { 'a', 0.27, 5 }, { 'c', 0.15, 4 }, { 'g', 0.12, 3 }, { 't', 0.27, 2 }, }; const unsigned char faceedgesidx[6][4] = { { 4, 5, 8, 10 }, { 6, 7, 9, 11 }, { 0, 2, 8, 9 }, { 1, 3, 10,11 }, { 0, 1, 4, 6 }, { 2, 3, 5, 7 }, }; int main( int argc, const char *argv[] ) { printf("*%d,%d,%d,%d*\\n", iub[0].c, int(iub[1].p*100), iub[2].pi, faceedgesidx[3][2]); return 0; } ''' self.do_run(src, '*97,15,3,10*') def test_conststructs(self): src = ''' #include struct IUB { int c; double p; unsigned int pi; }; int main( int argc, const char *argv[] ) { int before = 70; IUB iub[] = { { 'a', 0.3029549426680, 5 }, { 'c', 0.15, 4 }, { 'g', 0.12, 3 }, { 't', 0.27, 2 }, }; int after = 90; printf("*%d,%d,%d,%d,%d,%d*\\n", before, iub[0].c, int(iub[1].p*100), iub[2].pi, int(iub[0].p*10000), after); return 0; } ''' self.do_run(src, '*70,97,15,3,3029,90*') def test_mod_globalstruct(self): src = ''' #include struct malloc_params { size_t magic, page_size; }; malloc_params mparams; #define SIZE_T_ONE ((size_t)1) #define page_align(S) (((S) + (mparams.page_size - SIZE_T_ONE)) & ~(mparams.page_size - SIZE_T_ONE)) int main() { mparams.page_size = 4096; printf("*%d,%d,%d,%d*\\n", mparams.page_size, page_align(1000), page_align(6000), page_align(66474)); return 0; } ''' self.do_run(src, '*4096,4096,8192,69632*') def test_pystruct(self): src = ''' #include // Based on CPython code union PyGC_Head { struct { union PyGC_Head *gc_next; union PyGC_Head *gc_prev; size_t gc_refs; } gc; long double dummy; /* force worst-case alignment */ } ; struct gc_generation { PyGC_Head head; int threshold; /* collection threshold */ int count; /* count of allocations or collections of younger generations */ }; #define NUM_GENERATIONS 3 #define GEN_HEAD(n) (&generations[n].head) /* linked lists of container objects */ static struct gc_generation generations[NUM_GENERATIONS] = { /* PyGC_Head, threshold, count */ {{{GEN_HEAD(0), GEN_HEAD(0), 0}}, 700, 0}, {{{GEN_HEAD(1), GEN_HEAD(1), 0}}, 10, 0}, {{{GEN_HEAD(2), GEN_HEAD(2), 0}}, 10, 0}, }; int main() { gc_generation *n = NULL; printf("*%d,%d,%d,%d,%d,%d,%d,%d*\\n", (int)(&n[0]), (int)(&n[0].head), (int)(&n[0].head.gc.gc_next), (int)(&n[0].head.gc.gc_prev), (int)(&n[0].head.gc.gc_refs), (int)(&n[0].threshold), (int)(&n[0].count), (int)(&n[1]) ); printf("*%d,%d,%d*\\n", (int)(&generations[0]) == (int)(&generations[0].head.gc.gc_next), (int)(&generations[0]) == (int)(&generations[0].head.gc.gc_prev), (int)(&generations[0]) == (int)(&generations[1]) ); int x1 = (int)(&generations[0]); int x2 = (int)(&generations[1]); printf("*%d*\\n", x1 == x2); for (int i = 0; i < NUM_GENERATIONS; i++) { PyGC_Head *list = GEN_HEAD(i); printf("%d:%d,%d\\n", i, (int)list == (int)(list->gc.gc_prev), (int)list ==(int)(list->gc.gc_next)); } printf("*%d,%d,%d*\\n", sizeof(PyGC_Head), sizeof(gc_generation), int(GEN_HEAD(2)) - int(GEN_HEAD(1))); } ''' if Settings.QUANTUM_SIZE == 1: # Compressed memory. Note that sizeof() does give the fat sizes, however! self.do_run(src, '*0,0,0,1,2,3,4,5*\n*1,0,0*\n*0*\n0:1,1\n1:1,1\n2:1,1\n*12,20,5*') else: self.do_run(src, '*0,0,0,4,8,12,16,20*\n*1,0,0*\n*0*\n0:1,1\n1:1,1\n2:1,1\n*12,20,20*') def test_ptrtoint(self): src = ''' #include int main( int argc, const char *argv[] ) { char *a = new char[10]; char *a0 = a+0; char *a5 = a+5; int *b = new int[10]; int *b0 = b+0; int *b5 = b+5; int c = (int)b5-(int)b0; // Emscripten should warn! int d = (int)b5-(int)b0; // Emscripten should warn! printf("*%d*\\n", (int)a5-(int)a0); return 0; } ''' runner = self def check_warnings(output): runner.assertEquals(filter(lambda line: 'Warning' in line, output.split('\n')).__len__(), 4) self.do_run(src, '*5*', output_processor=check_warnings) def test_sizeof(self): # Has invalid writes between printouts Settings.SAFE_HEAP = 0 src = ''' #include #include #include "emscripten.h" struct A { int x, y; }; int main( int argc, const char *argv[] ) { int *a = new int[10]; int *b = new int[1]; int *c = new int[10]; for (int i = 0; i < 10; i++) a[i] = 2; *b = 5; for (int i = 0; i < 10; i++) c[i] = 8; printf("*%d,%d,%d,%d,%d*\\n", a[0], a[9], *b, c[0], c[9]); // Should overwrite a, but not touch b! memcpy(a, c, 10*sizeof(int)); printf("*%d,%d,%d,%d,%d*\\n", a[0], a[9], *b, c[0], c[9]); // Part 2 A as[3] = { { 5, 12 }, { 6, 990 }, { 7, 2 } }; memcpy(&as[0], &as[2], sizeof(A)); printf("*%d,%d,%d,%d,%d,%d*\\n", as[0].x, as[0].y, as[1].x, as[1].y, as[2].x, as[2].y); return 0; } ''' self.do_run(src, '*2,2,5,8,8***8,8,5,8,8***7,2,6,990,7,2*', [], lambda x: x.replace('\n', '*')) def test_emscripten_api(self): #if Settings.MICRO_OPTS or Settings.RELOOP or Building.LLVM_OPTS: return self.skip('FIXME') src = r''' #include #include "emscripten.h" int main() { // EMSCRIPTEN_COMMENT("hello from the source"); emscripten_run_script("print('hello world' + '!')"); printf("*%d*\n", emscripten_run_script_int("5*20")); return 0; } ''' check = ''' def process(filename): src = open(filename, 'r').read() # TODO: restore this (see comment in emscripten.h) assert '// hello from the source' in src ''' self.do_run(src, 'hello world!\n*100*', post_build=check) def test_memorygrowth(self): # With typed arrays in particular, it is dangerous to use more memory than TOTAL_MEMORY, # since we then need to enlarge the heap(s). src = r''' #include #include #include #include #include "emscripten.h" int main() { char *buf1 = (char*)malloc(100); char *data1 = "hello"; memcpy(buf1, data1, strlen(data1)+1); float *buf2 = (float*)malloc(100); float pie = 4.955; memcpy(buf2, &pie, sizeof(float)); printf("*pre: %s,%.3f*\n", buf1, buf2[0]); int totalMemory = emscripten_run_script_int("TOTAL_MEMORY"); char *buf3 = (char*)malloc(totalMemory+1); char *buf4 = (char*)malloc(100); float *buf5 = (float*)malloc(100); //printf("totalMemory: %d bufs: %d,%d,%d,%d,%d\n", totalMemory, buf1, buf2, buf3, buf4, buf5); assert((int)buf4 > (int)totalMemory && (int)buf5 > (int)totalMemory); printf("*%s,%.3f*\n", buf1, buf2[0]); // the old heap data should still be there memcpy(buf4, buf1, strlen(data1)+1); memcpy(buf5, buf2, sizeof(float)); printf("*%s,%.3f*\n", buf4, buf5[0]); // and the new heap space should work too return 0; } ''' self.do_run(src, '*pre: hello,4.955*\n*hello,4.955*\n*hello,4.955*') def test_ssr(self): # struct self-ref src = ''' #include // 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[2] = { {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; } ''' if Settings.QUANTUM_SIZE == 1: self.do_run(src, '''*4*\n0:22016,0,8,12\n1:22018,1,12,8\n''') else: self.do_run(src, '''*16*\n0:22016,0,32,48\n1:22018,1,48,32\n''') def test_tinyfuncstr(self): src = ''' #include struct Class { static char *name1() { return "nameA"; } char *name2() { return "nameB"; } }; int main() { printf("*%s,%s*\\n", Class::name1(), (new Class())->name2()); return 0; } ''' self.do_run(src, '*nameA,nameB*') def test_llvmswitch(self): src = ''' #include #include int switcher(int p) { switch(p) { case 'a': case 'b': case 'c': return p-1; case 'd': return p+1; } return p; } int main( int argc, const char *argv[] ) { printf("*%d,%d,%d,%d,%d*\\n", switcher('a'), switcher('b'), switcher('c'), switcher('d'), switcher('e')); return 0; } ''' self.do_run(src, '*96,97,98,101,101*') def test_indirectbr(self): if Settings.USE_TYPED_ARRAYS == 2: Settings.I64_MODE = 1 # Unsafe optimizations use 64-bit load/store on two i32s src = ''' #include int main(void) { const void *addrs[2] = { &&FOO, &&BAR }; // confuse the optimizer so it doesn't hardcode the jump and avoid generating an |indirectbr| instruction int which = 0; for (int x = 0; x < 1000; x++) which = (which + x*x) % 7; which = (which % 2) + 1; goto *addrs[which]; FOO: printf("bad\\n"); return 1; BAR: printf("good\\n"); const void *addr = &&FOO; goto *addr; } ''' self.do_run(src, 'good\nbad') def test_pack(self): src = ''' #include #include #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; } ''' if Settings.QUANTUM_SIZE == 1: self.do_run(src, '*4,2,3*\n*6,2,3*') else: self.do_run(src, '*4,3,4*\n*6,4,6*') def test_varargs(self): if Settings.QUANTUM_SIZE == 1: return self.skip('FIXME: Add support for this') src = ''' #include #include 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); } void vary2(char color, const char *s, ...) { va_list v; va_start(v, s); char d[21]; d[0] = color; vsnprintf(d+1, 20, s, v); puts(d); va_end(v); } #define GETMAX(pref, type) \ type getMax##pref(int num, ...) \ { \ va_list vv; \ va_start(vv, num); \ type maxx = va_arg(vv, type); \ for (int i = 1; i < num; i++) \ { \ type curr = va_arg(vv, type); \ maxx = curr > maxx ? curr : maxx; \ } \ va_end(vv); \ return maxx; \ } GETMAX(i, int); GETMAX(D, double); int main() { vary("*cheez: %d+%d*", 0, 24); // Also tests that '0' is not special as an array ender vary("*albeit*"); // Should not fail with no var args in vararg function vary2('Q', "%d*", 85); int maxxi = getMaxi(6, 2, 5, 21, 4, -10, 19); printf("maxxi:%d*\\n", maxxi); double maxxD = getMaxD(6, (double)2.1, (double)5.1, (double)22.1, (double)4.1, (double)-10.1, (double)19.1); printf("maxxD:%.2f*\\n", (float)maxxD); // And, as a function pointer void (*vfp)(const char *s, ...) = vary; vfp("*vfp:%d,%d*", 22, 199); return 0; } ''' self.do_run(src, '*cheez: 0+24*\n*cheez: 0+24*\n*albeit*\n*albeit*\nQ85*\nmaxxi:21*\nmaxxD:22.10*\n*vfp:22,199*\n*vfp:22,199*\n') def test_structbyval(self): # part 1: make sure that normally, passing structs by value works src = r''' #include 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') # Check for lack of warning in the generated code (they should appear in part 2) generated = open(os.path.join(self.get_dir(), 'src.cpp.o.js')).read() assert 'Casting a function pointer type to another with a different number of arguments.' not in generated, 'Unexpected warning' # part 2: make sure we warn about mixing c and c++ calling conventions here header = r''' struct point { int x, y; }; ''' open(os.path.join(self.get_dir(), 'header.h'), 'w').write(header) supp = r''' #include #include "header.h" 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); } ''' supp_name = os.path.join(self.get_dir(), 'supp.c') open(supp_name, 'w').write(supp) main = r''' #include #include "header.h" #ifdef __cplusplus extern "C" { #endif void dump(struct point p); #ifdef __cplusplus } #endif int main( int argc, const char *argv[] ) { struct point p = { 54, 2 }; printf("pre: %d,%d\n", p.x, p.y); dump(p); void (*dp)(struct point p) = dump; // And, as a function pointer dp(p); printf("post: %d,%d\n", p.x, p.y); return 0; } ''' main_name = os.path.join(self.get_dir(), 'main.cpp') open(main_name, 'w').write(main) Building.emcc(supp_name) Building.emcc(main_name) all_name = os.path.join(self.get_dir(), 'all.bc') Building.link([supp_name + '.o', main_name + '.o'], all_name) try: # This will fail! See explanation near the warning we check for, in the compiler source code self.do_ll_run(all_name, 'pre: 54,2\ndump: 55,3\ndump: 55,3\npost: 54,2') except Exception, e: # Check for warning in the generated code generated = open(os.path.join(self.get_dir(), 'src.cpp.o.js')).read() if self.emcc_args is None or self.emcc_args == []: # Optimized code is missing the warning comments assert 'Casting a function pointer type to another with a different number of arguments.' in generated, 'Missing expected warning' assert 'void (i32, i32)* ==> void (%struct.point.0*)*' in generated, 'Missing expected warning details' return raise Exception('We should not have gotten to here!') def test_stdlibs(self): if Settings.USE_TYPED_ARRAYS == 2: # Typed arrays = 2 + safe heap prints a warning that messes up our output. Settings.SAFE_HEAP = 0 src = ''' #include #include #include void clean() { printf("*cleaned*\\n"); } int comparer(const void *a, const void *b) { int aa = *((int*)a); int bb = *((int*)b); return aa - bb; } int main() { // timeofday timeval t; gettimeofday(&t, NULL); printf("*%d,%d\\n", int(t.tv_sec), int(t.tv_usec)); // should not crash // atexit atexit(clean); // qsort int values[6] = { 3, 2, 5, 1, 5, 6 }; qsort(values, 5, sizeof(int), comparer); printf("*%d,%d,%d,%d,%d,%d*\\n", values[0], values[1], values[2], values[3], values[4], values[5]); printf("*stdin==0:%d*\\n", stdin == 0); // check that external values are at least not NULL printf("*%%*\\n"); printf("*%.1ld*\\n", 5); printf("*%.1f*\\n", strtod("66", NULL)); // checks dependency system, as our strtod needs _isspace etc. printf("*%ld*\\n", strtol("10", NULL, 0)); printf("*%ld*\\n", strtol("0", NULL, 0)); printf("*%ld*\\n", strtol("-10", NULL, 0)); printf("*%ld*\\n", strtol("12", NULL, 16)); printf("*%lu*\\n", strtoul("10", NULL, 0)); printf("*%lu*\\n", strtoul("0", NULL, 0)); printf("*%lu*\\n", strtoul("-10", NULL, 0)); printf("*malloc(0)!=0:%d*\\n", malloc(0) != 0); // We should not fail horribly return 0; } ''' self.do_run(src, '*1,2,3,5,5,6*\n*stdin==0:0*\n*%*\n*5*\n*66.0*\n*10*\n*0*\n*-10*\n*18*\n*10*\n*0*\n*4294967286*\n*malloc(0)!=0:1*\n*cleaned*') src = r''' #include #include 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_time(self): # XXX Not sure what the right output is here. Looks like the test started failing with daylight savings changes. Modified it to pass again. src = open(path_from_root('tests', 'time', 'src.c'), 'r').read() expected = open(path_from_root('tests', 'time', 'output.txt'), 'r').read() self.do_run(src, expected, extra_emscripten_args=['-H', 'libc/time.h']) #extra_emscripten_args=['-H', 'libc/fcntl.h,libc/sys/unistd.h,poll.h,libc/math.h,libc/langinfo.h,libc/time.h']) def test_statics(self): # static initializers save i16 but load i8 for some reason if Settings.SAFE_HEAP: Settings.SAFE_HEAP = 3 Settings.SAFE_HEAP_LINES = ['src.cpp:19', 'src.cpp:26'] src = ''' #include #include #define CONSTRLEN 32 void conoutfv(const char *fmt) { static char buf[CONSTRLEN]; strcpy(buf, fmt); puts(buf); } struct XYZ { float x, y, z; XYZ(float a, float b, float c) : x(a), y(b), z(c) { } static const XYZ& getIdentity() { static XYZ iT(1,2,3); return iT; } }; struct S { static const XYZ& getIdentity() { static const XYZ iT(XYZ::getIdentity()); return iT; } }; int main() { conoutfv("*staticccz*"); printf("*%.2f,%.2f,%.2f*\\n", S::getIdentity().x, S::getIdentity().y, S::getIdentity().z); return 0; } ''' self.do_run(src, '*staticccz*\n*1.00,2.00,3.00*') 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). See QUANTUM_SIZE in # settings.js. src = ''' #include #include #include struct vec { double x,y,z; vec() : x(0), y(0), z(0) { }; vec(const double a, const double b, const double c) : x(a), y(b), z(c) { }; }; struct basis { vec a, b, c; basis(const vec& v) { a=v; // should not touch b! printf("*%.2f,%.2f,%.2f*\\n", b.x, b.y, b.z); } }; int main() { basis B(vec(1,0,0)); // Part 2: similar problem with memset and memmove int x = 1, y = 77, z = 2; memset((void*)&x, 0, sizeof(int)); memset((void*)&z, 0, sizeof(int)); printf("*%d,%d,%d*\\n", x, y, z); memcpy((void*)&x, (void*)&z, sizeof(int)); memcpy((void*)&z, (void*)&x, sizeof(int)); printf("*%d,%d,%d*\\n", x, y, z); memmove((void*)&x, (void*)&z, sizeof(int)); memmove((void*)&z, (void*)&x, sizeof(int)); printf("*%d,%d,%d*\\n", x, y, z); return 0; } ''' self.do_run(src, '*0.00,0.00,0.00*\n*0,77,0*\n*0,77,0*\n*0,77,0*') def test_memcpy(self): src = ''' #include #include #define MAXX 48 void reset(unsigned char *buffer) { for (int i = 0; i < MAXX; i++) buffer[i] = i+1; } void dump(unsigned char *buffer) { for (int i = 0; i < MAXX-1; i++) printf("%2d,", buffer[i]); printf("%d\\n", buffer[MAXX-1]); } int main() { unsigned char buffer[MAXX]; for (int i = MAXX/4; i < MAXX-MAXX/4; i++) { for (int j = MAXX/4; j < MAXX-MAXX/4; j++) { for (int k = 1; k < MAXX/4; k++) { if (i == j) continue; if (i < j && i+k > j) continue; if (j < i && j+k > i) continue; printf("[%d,%d,%d] ", i, j, k); reset(buffer); memcpy(buffer+i, buffer+j, k); dump(buffer); } } } return 0; } ''' def check(result): return hashlib.sha1(result).hexdigest() self.do_run(src, '6c9cdfe937383b79e52ca7a2cce83a21d9f5422c', output_nicerizer = check) def test_memmove(self): src = ''' #include #include int main() { char str[] = "memmove can be very useful....!"; memmove (str+20, str+15, 11); puts(str); return 0; } ''' self.do_run(src, 'memmove can be very very useful') def test_bsearch(self): if Settings.QUANTUM_SIZE == 1: return self.skip('Test cannot work with q1') src = ''' #include #include int cmp(const void* key, const void* member) { return *(int *)key - *(int *)member; } void printResult(int* needle, int* haystack, unsigned int len) { void *result = bsearch(needle, haystack, len, sizeof(unsigned int), cmp); if (result == NULL) { printf("null\\n"); } else { printf("%d\\n", *(unsigned int *)result); } } int main() { int a[] = { -2, -1, 0, 6, 7, 9 }; int b[] = { 0, 1 }; /* Find all keys that exist. */ for(int i = 0; i < 6; i++) { int val = a[i]; printResult(&val, a, 6); } /* Keys that are covered by the range of the array but aren't in * the array cannot be found. */ int v1 = 3; int v2 = 8; printResult(&v1, a, 6); printResult(&v2, a, 6); /* Keys outside the range of the array cannot be found. */ int v3 = -1; int v4 = 2; printResult(&v3, b, 2); printResult(&v4, b, 2); return 0; } ''' self.do_run(src, '-2\n-1\n0\n6\n7\n9\nnull\nnull\nnull\nnull') def test_nestedstructs(self): src = ''' #include #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; } ''' if Settings.QUANTUM_SIZE == 1: # Compressed memory. Note that sizeof() does give the fat sizes, however! self.do_run(src, '*16,0,1,2,2,3|20,0,1,1,2,3,3,4|24,0,5,0,1,1,2,3,3,4*\n*0,0,0,1,2,62,63,64,72*\n*2*') else: # 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 test_runtimelink(self): if Building.LLVM_OPTS: return self.skip('LLVM opts will optimize printf into puts in the parent, and the child will still look for puts') Settings.LINKABLE = 1 self.banned_js_engines = [NODE_JS] # node's global scope behaves differently than everything else, needs investigation FIXME header = r''' struct point { int x, y; }; ''' open(os.path.join(self.get_dir(), 'header.h'), 'w').write(header) supp = r''' #include #include "header.h" extern void mainFunc(int x); extern int mainInt; void suppFunc(struct point &p) { printf("supp: %d,%d\n", p.x, p.y); mainFunc(p.x+p.y); printf("supp see: %d\n", mainInt); } int suppInt = 76; ''' supp_name = os.path.join(self.get_dir(), 'supp.c') open(supp_name, 'w').write(supp) main = r''' #include #include "header.h" extern void suppFunc(struct point &p); extern int suppInt; void mainFunc(int x) { printf("main: %d\n", x); } int mainInt = 543; int main( int argc, const char *argv[] ) { struct point p = { 54, 2 }; suppFunc(p); printf("main see: %d\nok.\n", suppInt); return 0; } ''' Settings.BUILD_AS_SHARED_LIB = 2 dirname = self.get_dir() self.build(supp, dirname, supp_name) shutil.move(supp_name + '.o.js', os.path.join(dirname, 'liblib.so')) Settings.BUILD_AS_SHARED_LIB = 0 Settings.RUNTIME_LINKED_LIBS = ['liblib.so']; self.do_run(main, 'supp: 54,2\nmain: 56\nsupp see: 543\nmain see: 76\nok.') def test_dlfcn_basic(self): Settings.LINKABLE = 1 lib_src = ''' #include class Foo { public: Foo() { printf("Constructing lib object.\\n"); } }; Foo global; ''' dirname = self.get_dir() filename = os.path.join(dirname, 'liblib.cpp') Settings.BUILD_AS_SHARED_LIB = 1 self.build(lib_src, dirname, filename) shutil.move(filename + '.o.js', os.path.join(dirname, 'liblib.so')) src = ''' #include #include class Bar { public: Bar() { printf("Constructing main object.\\n"); } }; Bar global; int main() { dlopen("liblib.so", RTLD_NOW); return 0; } ''' Settings.BUILD_AS_SHARED_LIB = 0 add_pre_run_and_checks = ''' def process(filename): src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', "FS.createLazyFile('/', 'liblib.so', 'liblib.so', true, false);" ) open(filename, 'w').write(src) ''' self.do_run(src, 'Constructing main object.\nConstructing lib object.\n', post_build=add_pre_run_and_checks) def test_dlfcn_qsort(self): Settings.LINKABLE = 1 if Settings.USE_TYPED_ARRAYS == 2: Settings.CORRECT_SIGNS = 1 # Needed for unsafe optimizations 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') Settings.BUILD_AS_SHARED_LIB = 1 Settings.EXPORTED_FUNCTIONS = ['_get_cmp'] self.build(lib_src, dirname, filename) shutil.move(filename + '.o.js', os.path.join(dirname, 'liblib.so')) src = ''' #include #include #include 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}; 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), main_cmp); printf("Sort with main comparison: "); for (int i = 0; i < 5; i++) { printf("%d ", arr[i]); } printf("\\n"); 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; } ''' Settings.BUILD_AS_SHARED_LIB = 0 Settings.EXPORTED_FUNCTIONS = ['_main'] add_pre_run_and_checks = ''' def process(filename): src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', "FS.createLazyFile('/', 'liblib.so', 'liblib.so', true, false);" ) open(filename, 'w').write(src) ''' 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: x.replace('\n', '*'), post_build=add_pre_run_and_checks) def test_dlfcn_data_and_fptr(self): if Building.LLVM_OPTS: return self.skip('LLVM opts will optimize out parent_func') Settings.LINKABLE = 1 lib_src = ''' #include int global = 42; extern void parent_func(); // a function that is defined in the parent 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') Settings.BUILD_AS_SHARED_LIB = 1 Settings.EXPORTED_FUNCTIONS = ['_func'] Settings.EXPORTED_GLOBALS = ['_global'] self.build(lib_src, dirname, filename) shutil.move(filename + '.o.js', os.path.join(dirname, 'liblib.so')) src = ''' #include #include typedef void (*FUNCTYPE(int, void(*)()))(); FUNCTYPE func; void 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* global = (int*) dlsym(lib_handle, "global"); if (global == NULL) { printf("Could not find global.\\n"); return 1; } printf("Var: %d\\n", *global); return 0; } ''' Settings.BUILD_AS_SHARED_LIB = 0 Settings.EXPORTED_FUNCTIONS = ['_main'] Settings.EXPORTED_GLOBALS = [] add_pre_run_and_checks = ''' def process(filename): src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', "FS.createLazyFile('/', 'liblib.so', 'liblib.so', true, false);" ) open(filename, 'w').write(src) ''' 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*', output_nicerizer=lambda x: x.replace('\n', '*'), post_build=add_pre_run_and_checks) def test_dlfcn_alias(self): Settings.LINKABLE = 1 if Building.LLVM_OPTS == 2: return self.skip('LLVM LTO will optimize away stuff we expect from the shared library') lib_src = r''' #include extern int parent_global; extern "C" void func() { printf("Parent global: %d.\n", parent_global); } ''' dirname = self.get_dir() filename = os.path.join(dirname, 'liblib.cpp') Settings.BUILD_AS_SHARED_LIB = 1 Settings.EXPORTED_FUNCTIONS = ['_func'] self.build(lib_src, dirname, filename) shutil.move(filename + '.o.js', os.path.join(dirname, 'liblib.so')) src = r''' #include int parent_global = 123; int main() { void* lib_handle; void (*fptr)(); lib_handle = dlopen("liblib.so", RTLD_NOW); fptr = (void (*)())dlsym(lib_handle, "func"); fptr(); parent_global = 456; fptr(); return 0; } ''' Settings.BUILD_AS_SHARED_LIB = 0 Settings.INCLUDE_FULL_LIBRARY = 1 Settings.EXPORTED_FUNCTIONS = ['_main'] add_pre_run_and_checks = ''' def process(filename): src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', "FS.createLazyFile('/', 'liblib.so', 'liblib.so', true, false);" ) open(filename, 'w').write(src) ''' self.do_run(src, 'Parent global: 123.*Parent global: 456.*', output_nicerizer=lambda x: x.replace('\n', '*'), post_build=add_pre_run_and_checks, extra_emscripten_args=['-H', 'libc/fcntl.h,libc/sys/unistd.h,poll.h,libc/math.h,libc/time.h,libc/langinfo.h']) Settings.INCLUDE_FULL_LIBRARY = 0 def test_dlfcn_varargs(self): Settings.LINKABLE = 1 if Building.LLVM_OPTS == 2: return self.skip('LLVM LTO will optimize things that prevent shared objects from working') if Settings.QUANTUM_SIZE == 1: return self.skip('FIXME: Add support for this') 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') Settings.BUILD_AS_SHARED_LIB = 1 Settings.EXPORTED_FUNCTIONS = ['_func'] self.build(lib_src, dirname, filename) shutil.move(filename + '.o.js', os.path.join(dirname, 'liblib.so')) src = r''' #include #include #include 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); fptr = (void (*)())dlsym(lib_handle, "func"); fptr(); return 0; } ''' Settings.BUILD_AS_SHARED_LIB = 0 Settings.EXPORTED_FUNCTIONS = ['_main'] add_pre_run_and_checks = ''' def process(filename): src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', "FS.createLazyFile('/', 'liblib.so', 'liblib.so', true, false);" ) open(filename, 'w').write(src) ''' self.do_run(src, '100\n200\n13\n42\n', post_build=add_pre_run_and_checks) def test_rand(self): src = r''' #include #include int main() { printf("%d\n", rand()); printf("%d\n", rand()); srand(123); printf("%d\n", rand()); printf("%d\n", rand()); srand(123); printf("%d\n", rand()); printf("%d\n", rand()); unsigned state = 0; int r; r = rand_r(&state); printf("%d, %u\n", r, state); r = rand_r(&state); printf("%d, %u\n", r, state); state = 0; r = rand_r(&state); printf("%d, %u\n", r, state); return 0; } ''' expected = ''' 1250496027 1116302336 440917656 1476150784 440917656 1476150784 12345, 12345 1406932606, 3554416254 12345, 12345 ''' self.do_run(src, re.sub(r'(^|\n)\s+', r'\1', expected)) def test_strtod(self): src = r''' #include #include int main() { char* endptr; printf("\n"); printf("%g\n", strtod("0", &endptr)); printf("%g\n", strtod("0.", &endptr)); printf("%g\n", strtod("0.0", &endptr)); printf("%g\n", strtod("1", &endptr)); printf("%g\n", strtod("1.", &endptr)); printf("%g\n", strtod("1.0", &endptr)); printf("%g\n", strtod("123", &endptr)); printf("%g\n", strtod("123.456", &endptr)); printf("%g\n", strtod("-123.456", &endptr)); printf("%g\n", strtod("1234567891234567890", &endptr)); printf("%g\n", strtod("1234567891234567890e+50", &endptr)); printf("%g\n", strtod("84e+220", &endptr)); printf("%g\n", strtod("123e-50", &endptr)); printf("%g\n", strtod("123e-250", &endptr)); printf("%g\n", strtod("123e-450", &endptr)); char str[] = " 12.34e56end"; printf("%g\n", strtod(str, &endptr)); printf("%d\n", endptr - str); printf("%g\n", strtod("84e+420", &endptr)); return 0; } ''' expected = ''' 0 0 0 1 1 1 123 123.456 -123.456 1.23457e+18 1.23457e+68 8.4e+221 1.23e-48 1.23e-248 0 1.234e+57 10 inf ''' self.do_run(src, re.sub(r'\n\s+', '\n', expected)) def test_strtok(self): src = r''' #include #include int main() { char test[80], blah[80]; char *sep = "\\/:;=-"; char *word, *phrase, *brkt, *brkb; strcpy(test, "This;is.a:test:of=the/string\\tokenizer-function."); for (word = strtok_r(test, sep, &brkt); word; word = strtok_r(NULL, sep, &brkt)) { strcpy(blah, "blah:blat:blab:blag"); for (phrase = strtok_r(blah, sep, &brkb); phrase; phrase = strtok_r(NULL, sep, &brkb)) { printf("at %s:%s\n", word, phrase); } } return 1; } ''' expected = '''at This:blah at This:blat at This:blab at This:blag at is.a:blah at is.a:blat at is.a:blab at is.a:blag at test:blah at test:blat at test:blab at test:blag at of:blah at of:blat at of:blab at of:blag at the:blah at the:blat at the:blab at the:blag at string:blah at string:blat at string:blab at string:blag at tokenizer:blah at tokenizer:blat at tokenizer:blab at tokenizer:blag at function.:blah at function.:blat at function.:blab at function.:blag ''' self.do_run(src, expected) def test_parseInt(self): if Settings.QUANTUM_SIZE == 1: return self.skip('Q1 and I64_1 do not mix well yet') Settings.I64_MODE = 1 # Necessary to prevent i64s being truncated into i32s, but we do still get doubling # FIXME: The output here is wrong, due to double rounding of i64s! src = open(path_from_root('tests', 'parseInt', 'src.c'), 'r').read() expected = open(path_from_root('tests', 'parseInt', 'output.txt'), 'r').read() self.do_run(src, expected) def test_printf(self): if Settings.QUANTUM_SIZE == 1: return self.skip('Q1 and I64_1 do not mix well yet') Settings.I64_MODE = 1 self.banned_js_engines = [NODE_JS, V8_ENGINE] # SpiderMonkey and V8 do different things to float64 typed arrays, un-NaNing, etc. src = open(path_from_root('tests', 'printf', 'test.c'), 'r').read() expected = [open(path_from_root('tests', 'printf', 'output.txt'), 'r').read(), open(path_from_root('tests', 'printf', 'output_i64_1.txt'), 'r').read()] self.do_run(src, expected) def test_printf_types(self): src = r''' #include int main() { char c = '1'; short s = 2; int i = 3; long long l = 4; float f = 5.5; double d = 6.6; printf("%c,%hd,%d,%lld,%.1f,%.1llf\n", c, s, i, l, f, d); return 0; } ''' self.do_run(src, '1,2,3,4,5.5,6.6\n') def test_vprintf(self): src = r''' #include #include void print(char* format, ...) { va_list args; va_start (args, format); vprintf (format, args); va_end (args); } int main () { print("Call with %d variable argument.\n", 1); print("Call with %d variable %s.\n", 2, "arguments"); return 0; } ''' expected = ''' Call with 1 variable argument. Call with 2 variable arguments. ''' self.do_run(src, re.sub('(^|\n)\s+', '\\1', expected)) def test_atoi(self): src = r''' #include #include int main () { printf("%d*", atoi("")); printf("%d*", atoi("a")); printf("%d*", atoi(" b")); printf("%d*", atoi(" c ")); printf("%d*", atoi("6")); printf("%d*", atoi(" 5")); printf("%d*", atoi("4 ")); printf("%d*", atoi("3 6")); printf("%d*", atoi(" 3 7")); printf("%d*", atoi("9 d")); printf("%d\n", atoi(" 8 e")); return 0; } ''' self.do_run(src, '0*0*0*0*6*5*4*3*3*9*8') def test_sscanf(self): src = r''' #include #include #include int main () { #define CHECK(str) \ { \ char name[1000]; \ memset(name, 0, 1000); \ int prio = 99; \ sscanf(str, "%s %d", name, &prio); \ printf("%s : %d\n", name, prio); \ } CHECK("en-us 2"); CHECK("en-r"); CHECK("en 3"); printf("%f, %f\n", atof("1.234567"), atof("cheez")); return 0; } ''' self.do_run(src, 'en-us : 2*en-r : 99*en : 3*1.234567, 0.000000', output_nicerizer = lambda x: x.replace('\n', '*')) # Part 2: doubles (issue 148) if Settings.USE_TYPED_ARRAYS == 2: for ftype in ['float', 'double']: src = r''' #include 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.789063 123456.789063 Pass: 123456.789063 123456.789063 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_langinfo(self): src = open(path_from_root('tests', 'langinfo', 'test.c'), 'r').read() expected = open(path_from_root('tests', 'langinfo', 'output.txt'), 'r').read() self.do_run(src, expected, extra_emscripten_args=['-H', 'libc/langinfo.h']) def test_files(self): if self.emcc_args is not None and '-O2' in self.emcc_args: self.emcc_args += ['--closure', '1'] # Use closure here, to test we don't break FS stuff Settings.CORRECT_SIGNS = 1 # Just so our output is what we expect. Can flip them both. post = ''' def process(filename): src = open(filename, 'r').read().replace('FS.init();', '').replace( # Disable normal initialization, replace with ours '// {{PRE_RUN_ADDITIONS}}', \'\'\' FS.createDataFile('/', 'somefile.binary', [100, 200, 50, 25, 10, 77, 123], true, false); // 200 becomes -56, since signed chars are used in memory FS.createLazyFile('/', 'test.file', 'test.file', true, false); 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; }); \'\'\' ) open(filename, 'w').write(src) ''' other = open(os.path.join(self.get_dir(), 'test.file'), 'w') other.write('some data'); other.close() src = open(path_from_root('tests', 'files.cpp'), 'r').read() 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\ntexte\n$\n5 : 10,30,20,11,88\nother=some data.\nseeked=me da.\nseeked=ata.\nseeked=ta.\nfscanfed: 10 - hello\n', post_build=post, extra_emscripten_args=['-H', 'libc/fcntl.h']) def test_folders(self): add_pre_run = ''' def process(filename): src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', \'\'\' FS.createFolder('/', 'test', true, false); FS.createPath('/', 'test/hello/world/', true, false); FS.createPath('/test', 'goodbye/world/', true, false); FS.createPath('/test/goodbye', 'noentry', false, false); FS.createDataFile('/test', 'freeforall.ext', 'abc', true, true); FS.createDataFile('/test', 'restricted.ext', 'def', false, false); \'\'\' ) open(filename, 'w').write(src) ''' src = r''' #include #include #include int main() { struct dirent *e; // Basic correct behaviour. DIR* d = opendir("/test"); printf("--E: %d\n", errno); while ((e = readdir(d))) puts(e->d_name); printf("--E: %d\n", errno); // Empty folder; tell/seek. puts("****"); d = opendir("/test/hello/world/"); e = readdir(d); puts(e->d_name); int pos = telldir(d); e = readdir(d); puts(e->d_name); seekdir(d, pos); e = readdir(d); puts(e->d_name); // Errors. puts("****"); printf("--E: %d\n", errno); d = opendir("/test/goodbye/noentry"); printf("--E: %d, D: %d\n", errno, d); d = opendir("/i/dont/exist"); printf("--E: %d, D: %d\n", errno, d); d = opendir("/test/freeforall.ext"); printf("--E: %d, D: %d\n", errno, d); while ((e = readdir(d))) puts(e->d_name); printf("--E: %d\n", errno); return 0; } ''' expected = ''' --E: 0 . .. hello goodbye freeforall.ext restricted.ext --E: 0 **** . .. .. **** --E: 0 --E: 13, D: 0 --E: 2, D: 0 --E: 20, D: 0 --E: 9 ''' self.do_run(src, re.sub('(^|\n)\s+', '\\1', expected), post_build=add_pre_run) def test_stat(self): add_pre_run = ''' def process(filename): src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', \'\'\' var f1 = FS.createFolder('/', 'test', true, true); var f2 = FS.createDataFile(f1, 'file', 'abcdef', true, true); var f3 = FS.createLink(f1, 'link', 'file', true, true); var f4 = FS.createDevice(f1, 'device', function(){}, function(){}); f1.timestamp = f2.timestamp = f3.timestamp = f4.timestamp = new Date(1200000000000); \'\'\' ) open(filename, 'w').write(src) ''' src = open(path_from_root('tests', 'stat', 'src.c'), 'r').read() expected = open(path_from_root('tests', 'stat', 'output.txt'), 'r').read() self.do_run(src, expected, post_build=add_pre_run, extra_emscripten_args=['-H', 'libc/fcntl.h']) def test_fcntl(self): add_pre_run = ''' def process(filename): src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', "FS.createDataFile('/', 'test', 'abcdef', true, true);" ) open(filename, 'w').write(src) ''' src = open(path_from_root('tests', 'fcntl', 'src.c'), 'r').read() expected = open(path_from_root('tests', 'fcntl', 'output.txt'), 'r').read() self.do_run(src, expected, post_build=add_pre_run, extra_emscripten_args=['-H', 'libc/fcntl.h']) def test_fcntl_open(self): add_pre_run = ''' def process(filename): src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', \'\'\' FS.createDataFile('/', 'test-file', 'abcdef', true, true); FS.createFolder('/', 'test-folder', true, true); \'\'\' ) open(filename, 'w').write(src) ''' src = open(path_from_root('tests', 'fcntl-open', 'src.c'), 'r').read() expected = open(path_from_root('tests', 'fcntl-open', 'output.txt'), 'r').read() self.do_run(src, expected, post_build=add_pre_run, extra_emscripten_args=['-H', 'libc/fcntl.h']) def test_fcntl_misc(self): add_pre_run = ''' def process(filename): src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', "FS.createDataFile('/', 'test', 'abcdef', true, true);" ) open(filename, 'w').write(src) ''' src = open(path_from_root('tests', 'fcntl-misc', 'src.c'), 'r').read() expected = open(path_from_root('tests', 'fcntl-misc', 'output.txt'), 'r').read() self.do_run(src, expected, post_build=add_pre_run, extra_emscripten_args=['-H', 'libc/fcntl.h']) def test_poll(self): add_pre_run = ''' def process(filename): src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', \'\'\' FS.createDataFile('/', 'file', 'abcdef', true, true); FS.createDevice('/', 'device', function() {}, function() {}); \'\'\' ) open(filename, 'w').write(src) ''' src = r''' #include #include #include #include int main() { struct pollfd multi[5]; multi[0].fd = open("/file", O_RDONLY, 0777); multi[1].fd = open("/device", O_RDONLY, 0777); multi[2].fd = 123; multi[3].fd = open("/file", O_RDONLY, 0777); multi[4].fd = open("/file", O_RDONLY, 0777); multi[0].events = POLLIN | POLLOUT | POLLNVAL | POLLERR; multi[1].events = POLLIN | POLLOUT | POLLNVAL | POLLERR; multi[2].events = POLLIN | POLLOUT | POLLNVAL | POLLERR; multi[3].events = 0x00; multi[4].events = POLLOUT | POLLNVAL | POLLERR; printf("ret: %d\n", poll(multi, 5, 123)); printf("errno: %d\n", errno); printf("multi[0].revents: %d\n", multi[0].revents == (POLLIN | POLLOUT)); printf("multi[1].revents: %d\n", multi[1].revents == (POLLIN | POLLOUT)); printf("multi[2].revents: %d\n", multi[2].revents == POLLNVAL); printf("multi[3].revents: %d\n", multi[3].revents == 0); printf("multi[4].revents: %d\n", multi[4].revents == POLLOUT); return 0; } ''' expected = r''' ret: 4 errno: 0 multi[0].revents: 1 multi[1].revents: 1 multi[2].revents: 1 multi[3].revents: 1 multi[4].revents: 1 ''' self.do_run(src, re.sub('(^|\n)\s+', '\\1', expected), post_build=add_pre_run, extra_emscripten_args=['-H', 'libc/fcntl.h,poll.h']) def test_statvfs(self): src = r''' #include #include #include int main() { struct statvfs s; printf("result: %d\n", statvfs("/test", &s)); printf("errno: %d\n", errno); printf("f_bsize: %lu\n", s.f_bsize); printf("f_frsize: %lu\n", s.f_frsize); printf("f_blocks: %lu\n", s.f_blocks); printf("f_bfree: %lu\n", s.f_bfree); printf("f_bavail: %lu\n", s.f_bavail); printf("f_files: %lu\n", s.f_files); printf("f_ffree: %lu\n", s.f_ffree); printf("f_favail: %lu\n", s.f_favail); printf("f_fsid: %lu\n", s.f_fsid); printf("f_flag: %lu\n", s.f_flag); printf("f_namemax: %lu\n", s.f_namemax); return 0; } ''' expected = r''' result: 0 errno: 0 f_bsize: 4096 f_frsize: 4096 f_blocks: 1000000 f_bfree: 500000 f_bavail: 500000 f_files: 8 f_ffree: 1000000 f_favail: 1000000 f_fsid: 42 f_flag: 2 f_namemax: 255 ''' self.do_run(src, re.sub('(^|\n)\s+', '\\1', expected)) def test_libgen(self): src = r''' #include #include int main() { char p1[16] = "/usr/lib", p1x[16] = "/usr/lib"; printf("%s -> ", p1); printf("%s : %s\n", dirname(p1x), basename(p1)); char p2[16] = "/usr", p2x[16] = "/usr"; printf("%s -> ", p2); printf("%s : %s\n", dirname(p2x), basename(p2)); char p3[16] = "/usr/", p3x[16] = "/usr/"; printf("%s -> ", p3); printf("%s : %s\n", dirname(p3x), basename(p3)); char p4[16] = "/usr/lib///", p4x[16] = "/usr/lib///"; printf("%s -> ", p4); printf("%s : %s\n", dirname(p4x), basename(p4)); char p5[16] = "/", p5x[16] = "/"; printf("%s -> ", p5); printf("%s : %s\n", dirname(p5x), basename(p5)); char p6[16] = "///", p6x[16] = "///"; printf("%s -> ", p6); printf("%s : %s\n", dirname(p6x), basename(p6)); char p7[16] = "/usr/../lib/..", p7x[16] = "/usr/../lib/.."; printf("%s -> ", p7); printf("%s : %s\n", dirname(p7x), basename(p7)); char p8[16] = "", p8x[16] = ""; printf("(empty) -> %s : %s\n", dirname(p8x), basename(p8)); printf("(null) -> %s : %s\n", dirname(0), basename(0)); return 0; } ''' expected = ''' /usr/lib -> /usr : lib /usr -> / : usr /usr/ -> / : usr /usr/lib/// -> /usr : lib / -> / : / /// -> / : / /usr/../lib/.. -> /usr/../lib : .. (empty) -> . : . (null) -> . : . ''' self.do_run(src, re.sub('(^|\n)\s+', '\\1', expected)) def test_utime(self): add_pre_run_and_checks = ''' def process(filename): src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', \'\'\' var TEST_F1 = FS.createFolder('/', 'writeable', true, true); var TEST_F2 = FS.createFolder('/', 'unwriteable', true, false); \'\'\' ).replace( '// {{POST_RUN_ADDITIONS}}', \'\'\' print('first changed: ' + (TEST_F1.timestamp == 1200000000000)); print('second changed: ' + (TEST_F2.timestamp == 1200000000000)); \'\'\' ) open(filename, 'w').write(src) ''' src = r''' #include #include #include int main() { struct utimbuf t = {1000000000, 1200000000}; char* writeable = "/writeable"; char* unwriteable = "/unwriteable"; utime(writeable, &t); printf("writeable errno: %d\n", errno); utime(unwriteable, &t); printf("unwriteable errno: %d\n", errno); return 0; } ''' expected = ''' writeable errno: 0 unwriteable errno: 1 first changed: true second changed: false ''' self.do_run(src, re.sub('(^|\n)\s+', '\\1', expected), post_build=add_pre_run_and_checks) def test_direct_string_constant_usage(self): if self.emcc_args is None: return self.skip('requires libcxx') src = ''' #include template void printText( const char (&text)[ i ] ) { std::cout << text; } int main() { printText( "some string constant" ); return 0; } ''' self.do_run(src, "some string constant") def test_fs_base(self): Settings.INCLUDE_FULL_LIBRARY = 1 try: addJS = ''' def process(filename): import tools.shared as shared src = open(filename, 'r').read().replace('FS.init();', '').replace( # Disable normal initialization, replace with ours '// {{PRE_RUN_ADDITIONS}}', open(shared.path_from_root('tests', 'filesystem', 'src.js'), 'r').read()) open(filename, 'w').write(src) ''' src = 'int main() {return 0;}\n' expected = open(path_from_root('tests', 'filesystem', 'output.txt'), 'r').read() self.do_run(src, expected, post_build=addJS, extra_emscripten_args=['-H', 'libc/fcntl.h,libc/sys/unistd.h,poll.h,libc/math.h,libc/langinfo.h,libc/time.h']) finally: Settings.INCLUDE_FULL_LIBRARY = 0 def test_unistd_access(self): add_pre_run = ''' def process(filename): import tools.shared as shared src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', open(shared.path_from_root('tests', 'unistd', 'access.js'), 'r').read() ) open(filename, 'w').write(src) ''' src = open(path_from_root('tests', 'unistd', 'access.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'access.out'), 'r').read() self.do_run(src, expected, post_build=add_pre_run) def test_unistd_curdir(self): add_pre_run = ''' def process(filename): import tools.shared as shared src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', open(shared.path_from_root('tests', 'unistd', 'curdir.js'), 'r').read() ) open(filename, 'w').write(src) ''' src = open(path_from_root('tests', 'unistd', 'curdir.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'curdir.out'), 'r').read() self.do_run(src, expected, post_build=add_pre_run) def test_unistd_close(self): src = open(path_from_root('tests', 'unistd', 'close.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'close.out'), 'r').read() self.do_run(src, expected) def test_unistd_confstr(self): src = open(path_from_root('tests', 'unistd', 'confstr.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'confstr.out'), 'r').read() self.do_run(src, expected, extra_emscripten_args=['-H', 'libc/unistd.h']) def test_unistd_ttyname(self): add_pre_run = ''' def process(filename): import tools.shared as shared src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', open(shared.path_from_root('tests', 'unistd', 'ttyname.js'), 'r').read() ) open(filename, 'w').write(src) ''' src = open(path_from_root('tests', 'unistd', 'ttyname.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'ttyname.out'), 'r').read() self.do_run(src, expected, post_build=add_pre_run) def test_unistd_dup(self): src = open(path_from_root('tests', 'unistd', 'dup.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'dup.out'), 'r').read() self.do_run(src, expected) def test_unistd_pathconf(self): src = open(path_from_root('tests', 'unistd', 'pathconf.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'pathconf.out'), 'r').read() self.do_run(src, expected) def test_unistd_truncate(self): add_pre_run = ''' def process(filename): import tools.shared as shared src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', open(shared.path_from_root('tests', 'unistd', 'truncate.js'), 'r').read() ) open(filename, 'w').write(src) ''' src = open(path_from_root('tests', 'unistd', 'truncate.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'truncate.out'), 'r').read() self.do_run(src, expected, post_build=add_pre_run) def test_unistd_swab(self): src = open(path_from_root('tests', 'unistd', 'swab.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'swab.out'), 'r').read() self.do_run(src, expected) def test_unistd_isatty(self): add_pre_run = ''' def process(filename): import tools.shared as shared src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', open(shared.path_from_root('tests', 'unistd', 'isatty.js'), 'r').read() ) open(filename, 'w').write(src) ''' src = open(path_from_root('tests', 'unistd', 'isatty.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'isatty.out'), 'r').read() self.do_run(src, expected, post_build=add_pre_run) def test_unistd_sysconf(self): src = open(path_from_root('tests', 'unistd', 'sysconf.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'sysconf.out'), 'r').read() self.do_run(src, expected) def test_unistd_login(self): src = open(path_from_root('tests', 'unistd', 'login.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'login.out'), 'r').read() self.do_run(src, expected) def test_unistd_unlink(self): add_pre_run = ''' def process(filename): import tools.shared as shared src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', open(shared.path_from_root('tests', 'unistd', 'unlink.js'), 'r').read() ) open(filename, 'w').write(src) ''' src = open(path_from_root('tests', 'unistd', 'unlink.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'unlink.out'), 'r').read() self.do_run(src, expected, post_build=add_pre_run) def test_unistd_links(self): add_pre_run = ''' def process(filename): import tools.shared as shared src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', open(shared.path_from_root('tests', 'unistd', 'links.js'), 'r').read() ) open(filename, 'w').write(src) ''' src = open(path_from_root('tests', 'unistd', 'links.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'links.out'), 'r').read() self.do_run(src, expected, post_build=add_pre_run) def test_unistd_sleep(self): src = open(path_from_root('tests', 'unistd', 'sleep.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'sleep.out'), 'r').read() self.do_run(src, expected) def test_unistd_io(self): add_pre_run = ''' def process(filename): import tools.shared as shared src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', open(shared.path_from_root('tests', 'unistd', 'io.js'), 'r').read() ) open(filename, 'w').write(src) ''' src = open(path_from_root('tests', 'unistd', 'io.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'io.out'), 'r').read() self.do_run(src, expected, post_build=add_pre_run) def test_unistd_misc(self): src = open(path_from_root('tests', 'unistd', 'misc.c'), 'r').read() expected = open(path_from_root('tests', 'unistd', 'misc.out'), 'r').read() self.do_run(src, expected) def test_uname(self): src = r''' #include #include int main() { struct utsname u; printf("ret: %d\n", uname(&u)); printf("sysname: %s\n", u.sysname); printf("nodename: %s\n", u.nodename); printf("release: %s\n", u.release); printf("version: %s\n", u.version); printf("machine: %s\n", u.machine); printf("invalid: %d\n", uname(0)); return 0; } ''' expected = ''' ret: 0 sysname: Emscripten nodename: emscripten release: 1.0 version: #1 machine: x86-JS ''' self.do_run(src, re.sub('(^|\n)\s+', '\\1', expected)) def test_env(self): src = open(path_from_root('tests', 'env', 'src.c'), 'r').read() expected = open(path_from_root('tests', 'env', 'output.txt'), 'r').read() self.do_run(src, expected) def test_systypes(self): src = open(path_from_root('tests', 'systypes', 'src.c'), 'r').read() expected = open(path_from_root('tests', 'systypes', 'output.txt'), 'r').read() self.do_run(src, expected) def test_getloadavg(self): src = r''' #include #include int main() { double load[5] = {42.13, 42.13, 42.13, 42.13, 42.13}; printf("ret: %d\n", getloadavg(load, 5)); printf("load[0]: %.3lf\n", load[0]); printf("load[1]: %.3lf\n", load[1]); printf("load[2]: %.3lf\n", load[2]); printf("load[3]: %.3lf\n", load[3]); printf("load[4]: %.3lf\n", load[4]); return 0; } ''' expected = ''' ret: 3 load[0]: 0.100 load[1]: 0.100 load[2]: 0.100 load[3]: 42.130 load[4]: 42.130 ''' self.do_run(src, re.sub('(^|\n)\s+', '\\1', expected)) def test_inet(self): src = r''' #include #include int main() { printf("*%x,%x,%x,%x*\n", htonl(0x12345678), htons(0xabcd), ntohl(0x43211234), ntohs(0xbeaf)); return 0; } ''' self.do_run(src, '*78563412,cdab,34122143,afbe*') def test_ctype(self): # The bit fiddling done by the macros using __ctype_b_loc requires this. Settings.CORRECT_SIGNS = 1 src = open(path_from_root('tests', 'ctype', 'src.c'), 'r').read() expected = open(path_from_root('tests', 'ctype', 'output.txt'), 'r').read() self.do_run(src, expected) CORRECT_SIGNS = 0 def test_atomic(self): src = ''' #include int main() { int x = 10; int y = __sync_add_and_fetch(&x, 5); printf("*%d,%d*\\n", x, y); x = 10; y = __sync_fetch_and_add(&x, 5); printf("*%d,%d*\\n", x, y); x = 10; y = __sync_lock_test_and_set(&x, 6); printf("*%d,%d*\\n", x, y); x = 10; y = __sync_bool_compare_and_swap(&x, 9, 7); printf("*%d,%d*\\n", x, y); y = __sync_bool_compare_and_swap(&x, 10, 7); printf("*%d,%d*\\n", x, y); return 0; } ''' self.do_run(src, '*15,15*\n*15,10*\n*6,10*\n*10,0*\n*7,1*') # libc++ tests def test_iostream(self): if Settings.QUANTUM_SIZE == 1: return self.skip("we don't support libcxx in q1") if self.emcc_args is None: if Building.LLVM_OPTS: return self.skip('optimizing bitcode before emcc can confuse libcxx inclusion') self.emcc_args = [] # libc++ auto-inclusion is only done if we use emcc Settings.SAFE_HEAP = 0 # Some spurious warnings from libc++ internals src = ''' #include int main() { std::cout << "hello world" << std::endl << 77 << "." << std::endl; return 0; } ''' # FIXME: should not have so many newlines in output here self.do_run(src, 'hello world\n\n77.\n') def test_stdvec(self): src = ''' #include #include struct S { int a; float b; }; void foo(int a, float b) { printf("%d:%.2f\\n", a, b); } int main ( int argc, char *argv[] ) { std::vector ar; S s; s.a = 789; s.b = 123.456f; ar.push_back(s); s.a = 0; s.b = 100.1f; ar.push_back(s); foo(ar[0].a, ar[0].b); foo(ar[1].a, ar[1].b); } ''' self.do_run(src, '789:123.46\n0:100.1') ### 'Medium' tests def test_fannkuch(self): results = [ (1,0), (2,1), (3,2), (4,4), (5,7), (6,10), (7, 16), (8,22) ] for i, j in results: src = open(path_from_root('tests', 'fannkuch.cpp'), 'r').read() self.do_run(src, 'Pfannkuchen(%d) = %d.' % (i,j), [str(i)], no_build=i>1) def test_raytrace(self): if Settings.USE_TYPED_ARRAYS == 2: return self.skip('Relies on double value rounding, extremely sensitive') src = open(path_from_root('tests', 'raytrace.cpp'), 'r').read().replace('double', 'float') output = open(path_from_root('tests', 'raytrace.ppm'), 'r').read() self.do_run(src, output, ['3', '16'])#, build_ll_hook=self.do_autodebug) 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*''') ] for i, j in results: src = open(path_from_root('tests', 'fasta.cpp'), 'r').read() self.do_run(src, j, [str(i)], lambda x: x.replace('\n', '*'), no_build=i>1) def test_dlmalloc(self): if self.emcc_args is None: self.emcc_args = [] # dlmalloc auto-inclusion is only done if we use emcc Settings.CORRECT_SIGNS = 2 Settings.CORRECT_SIGNS_LINES = ['src.cpp:' + str(i+4) for i in [4816, 4191, 4246, 4199, 4205, 4235, 4227]] Settings.TOTAL_MEMORY = 100*1024*1024 # needed with typed arrays src = open(path_from_root('system', 'lib', 'dlmalloc.c'), 'r').read() + '\n\n\n' + open(path_from_root('tests', 'dlmalloc_test.c'), 'r').read() self.do_run(src, '*1,0*', ['200', '1']) self.do_run(src, '*400,0*', ['400', '400'], no_build=True) # Linked version src = open(path_from_root('tests', 'dlmalloc_test.c'), 'r').read() self.do_run(src, '*1,0*', ['200', '1'], extra_emscripten_args=['-m']) self.do_run(src, '*400,0*', ['400', '400'], extra_emscripten_args=['-m'], no_build=True) if self.emcc_args == []: # TODO: do this in other passes too, passing their opts into emcc # emcc should build in dlmalloc automatically, and do all the sign correction etc. for it try_delete(os.path.join(self.get_dir(), 'src.cpp.o.js')) output = Popen([EMCC, path_from_root('tests', 'dlmalloc_test.c'), '-o', os.path.join(self.get_dir(), 'src.cpp.o.js')], stdout=PIPE, stderr=self.stderr_redirect).communicate() self.do_run('x', '*1,0*', ['200', '1'], no_build=True) self.do_run('x', '*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*') 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 #include int main() { std::set *fetchOriginatorNums = new std::set(); fetchOriginatorNums->insert(171); printf("hello world\\n"); return 1; } ''', 'hello world'); def test_static_variable(self): if self.emcc_args is None: Settings.SAFE_HEAP = 0 # LLVM mixes i64 and i8 in the guard check src = ''' #include struct DATA { int value; DATA() { value = 0; } }; DATA & GetData() { static DATA data; return data; } int main() { GetData().value = 10; printf( "value:%i", GetData().value ); } ''' self.do_run(src, 'value:10') def test_mmap(self): src = ''' #include #include #include int main(int argc, char *argv[]) { const int NUM_BYTES = 8 * 1024 * 1024; const int NUM_INTS = NUM_BYTES / sizeof(int); int* map = (int*)mmap(0, NUM_BYTES, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); assert(map != MAP_FAILED); int i; for (i = 0; i < NUM_INTS; i++) { map[i] = i; } for (i = 0; i < NUM_INTS; i++) { assert(map[i] == i); } assert(munmap(map, NUM_BYTES) == 0); printf("hello,world"); return 0; } ''' self.do_run(src, 'hello,world') self.do_run(src, 'hello,world', force_c=True) def test_cubescript(self): if self.emcc_args is not None and '-O2' in self.emcc_args: self.emcc_args += ['--closure', '1'] # Use closure here for some additional coverage Building.COMPILER_TEST_OPTS = [] # remove -g, so we have one test without it by default if self.emcc_args is None: Settings.SAFE_HEAP = 0 # Has some actual loads of unwritten-to places, in the C++ code... # Overflows happen in hash loop Settings.CORRECT_OVERFLOWS = 1 Settings.CHECK_OVERFLOWS = 0 if Settings.USE_TYPED_ARRAYS == 2: Settings.CORRECT_SIGNS = 1 self.do_run(path_from_root('tests', 'cubescript'), '*\nTemp is 33\n9\n5\nhello, everyone\n*', main_file='command.cpp') def test_gcc_unmangler(self): self.do_run(path_from_root('third_party'), '*d_demangle(char const*, int, unsigned int*)*', args=['_ZL10d_demanglePKciPj'], main_file='gcc_demangler.c') #### Code snippet that is helpful to search for nonportable optimizations #### #global LLVM_OPT_OPTS #for opt in ['-aa-eval', '-adce', '-always-inline', '-argpromotion', '-basicaa', '-basiccg', '-block-placement', '-break-crit-edges', '-codegenprepare', '-constmerge', '-constprop', '-correlated-propagation', '-count-aa', '-dce', '-deadargelim', '-deadtypeelim', '-debug-aa', '-die', '-domfrontier', '-domtree', '-dse', '-extract-blocks', '-functionattrs', '-globaldce', '-globalopt', '-globalsmodref-aa', '-gvn', '-indvars', '-inline', '-insert-edge-profiling', '-insert-optimal-edge-profiling', '-instcombine', '-instcount', '-instnamer', '-internalize', '-intervals', '-ipconstprop', '-ipsccp', '-iv-users', '-jump-threading', '-lazy-value-info', '-lcssa', '-lda', '-libcall-aa', '-licm', '-lint', '-live-values', '-loop-deletion', '-loop-extract', '-loop-extract-single', '-loop-index-split', '-loop-reduce', '-loop-rotate', '-loop-unroll', '-loop-unswitch', '-loops', '-loopsimplify', '-loweratomic', '-lowerinvoke', '-lowersetjmp', '-lowerswitch', '-mem2reg', '-memcpyopt', '-memdep', '-mergefunc', '-mergereturn', '-module-debuginfo', '-no-aa', '-no-profile', '-partial-inliner', '-partialspecialization', '-pointertracking', '-postdomfrontier', '-postdomtree', '-preverify', '-prune-eh', '-reassociate', '-reg2mem', '-regions', '-scalar-evolution', '-scalarrepl', '-sccp', '-scev-aa', '-simplify-libcalls', '-simplify-libcalls-halfpowr', '-simplifycfg', '-sink', '-split-geps', '-sretpromotion', '-strip', '-strip-dead-debug-info', '-strip-dead-prototypes', '-strip-debug-declare', '-strip-nondebug', '-tailcallelim', '-tailduplicate', '-targetdata', '-tbaa']: # LLVM_OPT_OPTS = [opt] # try: # self.do_run(path_from_root(['third_party']), '*d_demangle(char const*, int, unsigned int*)*', args=['_ZL10d_demanglePKciPj'], main_file='gcc_demangler.c') # print opt, "ok" # except: # print opt, "FAIL" def test_lua(self): try: os.environ['EMCC_LEAVE_INPUTS_RAW'] = '1' if Settings.QUANTUM_SIZE == 1: return self.skip('TODO: make this work') # Overflows in luaS_newlstr hash loop if self.emcc_args is None: Settings.SAFE_HEAP = 0 # Has various warnings, with copied HEAP_HISTORY values (fixed if we copy 'null' as the type) Settings.CORRECT_OVERFLOWS = 1 Settings.CHECK_OVERFLOWS = 0 Settings.CORRECT_SIGNS = 1 # Not sure why, but needed Settings.INIT_STACK = 1 # TODO: Investigate why this is necessary self.do_ll_run(path_from_root('tests', 'lua', 'lua.ll'), '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)'''], output_nicerizer=lambda string: string.replace('\n\n', '\n').replace('\n\n', '\n'), extra_emscripten_args=['-H', 'libc/fcntl.h,libc/sys/unistd.h,poll.h,libc/math.h,libc/langinfo.h,libc/time.h']) finally: del os.environ['EMCC_LEAVE_INPUTS_RAW'] def get_build_dir(self): return os.path.join(self.get_dir(), 'building') def get_freetype(self): Settings.INIT_STACK = 1 # TODO: Investigate why this is necessary return self.get_library('freetype', os.path.join('objs', '.libs', 'libfreetype.a')) def test_freetype(self): if Settings.QUANTUM_SIZE == 1: return self.skip('TODO: Figure out and try to fix') if Settings.CORRECT_SIGNS == 0: Settings.CORRECT_SIGNS = 1 # Not sure why, but needed post = ''' def process(filename): import tools.shared as shared # Embed the font into the document src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', "FS.createDataFile('/', 'font.ttf', %s, true, false);" % str( map(ord, open(shared.path_from_root('tests', 'freetype', 'LiberationSansBold.ttf'), 'rb').read()) ) ) open(filename, 'w').write(src) ''' # Main self.do_run(open(path_from_root('tests', 'freetype', 'main.c'), 'r').read(), open(path_from_root('tests', 'freetype', 'ref.txt'), 'r').read(), ['font.ttf', 'test!', '150', '120', '25'], libraries=[self.get_freetype()], includes=[path_from_root('tests', 'freetype', 'include')], post_build=post) #build_ll_hook=self.do_autodebug) def test_sqlite(self): # gcc -O3 -I/home/alon/Dev/emscripten/tests/sqlite -ldl src.c if self.emcc_args is None: return self.skip('Very slow without ta2, and we would also need to include dlmalloc manually without emcc') if Settings.QUANTUM_SIZE == 1: return self.skip('TODO FIXME') pgo_data = read_pgo_data(path_from_root('tests', 'sqlite', 'sqlite-autooptimize.fails.txt')) Settings.CORRECT_SIGNS = 1 # XXX: in default, we fail with 2 here, even though the pgo_data should be correct (and works in s_0_0). Investigate this. Settings.CORRECT_SIGNS_LINES = pgo_data['signs_lines'] Settings.CORRECT_OVERFLOWS = 0 Settings.CORRECT_ROUNDINGS = 0 if self.emcc_args is None: Settings.SAFE_HEAP = 0 # uses time.h to set random bytes, other stuff Settings.DISABLE_EXCEPTION_CATCHING = 1 Settings.FAST_MEMORY = 4*1024*1024 Settings.EXPORTED_FUNCTIONS = ['_main', '_sqlite3_open', '_sqlite3_close', '_sqlite3_exec', '_sqlite3_free', '_callback']; Settings.INVOKE_RUN = 0 # We append code that does run() ourselves post = ''' def process(filename): src = open(filename, 'a') src.write(\'\'\' FS.init(); FS.createPath('/', 'dev/shm/tmp', true, true); FS.currentPath = '/dev/shm/tmp'; run(); \'\'\') src.close() ''' self.do_run(r''' #define SQLITE_DISABLE_LFS #define LONGDOUBLE_TYPE double #define SQLITE_INT64_TYPE int #define SQLITE_THREADSAFE 0 ''' + open(path_from_root('tests', 'sqlite', 'sqlite3.c'), 'r').read() + open(path_from_root('tests', 'sqlite', 'benchmark.c'), 'r').read(), open(path_from_root('tests', 'sqlite', 'benchmark.txt'), 'r').read(), includes=[path_from_root('tests', 'sqlite')], force_c=True, js_engines=[SPIDERMONKEY_ENGINE], # V8 is slow post_build=post)#,build_ll_hook=self.do_autodebug) def test_zlib(self): if self.emcc_args is not None and '-O2' in self.emcc_args: self.emcc_args += ['--closure', '1'] # Use closure here for some additional coverage Settings.CORRECT_SIGNS = 1 self.do_run(open(path_from_root('tests', 'zlib', 'example.c'), 'r').read(), open(path_from_root('tests', 'zlib', 'ref.txt'), 'r').read(), libraries=[self.get_library('zlib', os.path.join('libz.a'), make_args=['libz.a'])], includes=[path_from_root('tests', 'zlib')], force_c=True) def test_the_bullet(self): # Called thus so it runs late in the alphabetical cycle... it is long if Building.LLVM_OPTS and self.emcc_args is None: Settings.SAFE_HEAP = 0 # Optimizations make it so we do not have debug info on the line we need to ignore # Note: this is also a good test of per-file and per-line changes (since we have multiple files, and correct specific lines) if Settings.SAFE_HEAP: # Ignore bitfield warnings Settings.SAFE_HEAP = 3 Settings.SAFE_HEAP_LINES = ['btVoronoiSimplexSolver.h:40', 'btVoronoiSimplexSolver.h:41', 'btVoronoiSimplexSolver.h:42', 'btVoronoiSimplexSolver.h:43'] self.do_run(open(path_from_root('tests', 'bullet', 'Demos', 'HelloWorld', 'HelloWorld.cpp'), 'r').read(), [open(path_from_root('tests', 'bullet', 'output.txt'), 'r').read(), # different roundings open(path_from_root('tests', 'bullet', 'output2.txt'), 'r').read()], libraries=[self.get_library('bullet', [os.path.join('src', '.libs', 'libBulletCollision.a'), os.path.join('src', '.libs', 'libBulletDynamics.a'), os.path.join('src', '.libs', 'libLinearMath.a')], configure_args=['--disable-demos','--disable-dependency-tracking'])], includes=[path_from_root('tests', 'bullet', 'src')], js_engines=[SPIDERMONKEY_ENGINE]) # V8 issue 1407 def test_poppler(self): if self.emcc_args is None: return self.skip('very slow, we only do this in emcc runs') Settings.CORRECT_OVERFLOWS = 1 Settings.CORRECT_SIGNS = 1 Building.COMPILER_TEST_OPTS += [ '-I' + path_from_root('tests', 'freetype', 'include'), '-I' + path_from_root('tests', 'poppler', 'include'), ] Settings.INVOKE_RUN = 0 # We append code that does run() ourselves # See post(), below input_file = open(os.path.join(self.get_dir(), 'paper.pdf.js'), 'w') input_file.write(str(map(ord, open(path_from_root('tests', 'poppler', 'paper.pdf'), 'rb').read()))) input_file.close() post = ''' def process(filename): # To avoid loading this large file to memory and altering it, we simply append to the end src = open(filename, 'a') src.write( \'\'\' FS.createDataFile('/', 'paper.pdf', eval(read('paper.pdf.js')), true, false); run(); print("Data: " + JSON.stringify(FS.root.contents['filename-1.ppm'].contents.map(function(x) { return unSign(x, 8) }))); \'\'\' ) src.close() ''' #fontconfig = self.get_library('fontconfig', [os.path.join('src', '.libs', 'libfontconfig.a')]) # Used in file, but not needed, mostly freetype = self.get_freetype() poppler = self.get_library('poppler', [os.path.join('poppler', '.libs', self.get_shared_library_name('libpoppler.so.13')), os.path.join('goo', '.libs', 'libgoo.a'), os.path.join('fofi', '.libs', 'libfofi.a'), os.path.join('splash', '.libs', 'libsplash.a'), os.path.join('utils', 'pdftoppm.o'), os.path.join('utils', 'parseargs.o')], configure_args=['--disable-libjpeg', '--disable-libpng', '--disable-poppler-qt', '--disable-poppler-qt4', '--disable-cms']) # Combine libraries combined = os.path.join(self.get_dir(), 'poppler-combined.bc') Building.link([freetype, poppler], combined) self.do_ll_run(combined, map(ord, open(path_from_root('tests', 'poppler', 'ref.ppm'), 'r').read()).__str__().replace(' ', ''), args='-scale-to 512 paper.pdf filename'.split(' '), post_build=post) #, build_ll_hook=self.do_autodebug) def test_openjpeg(self): if Settings.USE_TYPED_ARRAYS == 2: Settings.CORRECT_SIGNS = 1 else: Settings.CORRECT_SIGNS = 2 Settings.CORRECT_SIGNS_LINES = ["mqc.c:566", "mqc.c:317"] post = ''' def process(filename): import tools.shared as shared original_j2k = shared.path_from_root('tests', 'openjpeg', 'syntensity_lobby_s.j2k') src = open(filename, 'r').read().replace( '// {{PRE_RUN_ADDITIONS}}', "FS.createDataFile('/', 'image.j2k', %s, true, false);" % shared.line_splitter(str( map(ord, open(original_j2k, 'rb').read()) )) ).replace( '// {{POST_RUN_ADDITIONS}}', "print('Data: ' + JSON.stringify(FS.root.contents['image.raw'].contents));" ) open(filename, 'w').write(src) ''' shutil.copy(path_from_root('tests', 'openjpeg', 'opj_config.h'), self.get_dir()) lib = self.get_library('openjpeg', [os.path.join('bin', self.get_shared_library_name('libopenjpeg.so.1.4.0')), 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.sep.join('codec/CMakeFiles/j2k_to_image.dir/__/common/getopt.c.o'.split('/'))], 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): # Get the image generated by JS, from the JSON.stringify'd array m = re.search('\[[\d, -]*\]', output) try: js_data = eval(m.group(0)) except AttributeError: print 'Failed to find proper image output in: ' + output raise js_data = map(lambda x: x if x >= 0 else 256+x, js_data) # Our output may be signed, so unsign it # Get the correct output true_data = 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 += ord(true_data[i]) diff_total += abs(js_data[i] - ord(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 assert abs(true_mean - image_mean) < 0.01 assert diff_mean < 0.01 return output self.do_run(open(path_from_root('tests', 'openjpeg', 'codec', 'j2k_to_image.c'), 'r').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', 'openjpeg', 'libopenjpeg'), path_from_root('tests', 'openjpeg', 'codec'), path_from_root('tests', 'openjpeg', 'common'), os.path.join(self.get_build_dir(), 'openjpeg')], force_c=True, post_build=post, output_nicerizer=image_compare)#, build_ll_hook=self.do_autodebug) def test_python(self): if Settings.QUANTUM_SIZE == 1: return self.skip('TODO: make this work') # Overflows in string_hash Settings.CORRECT_OVERFLOWS = 1 Settings.CHECK_OVERFLOWS = 0 if self.emcc_args is None: Settings.SAFE_HEAP = 0 # Has bitfields which are false positives. Also the PyFloat_Init tries to detect endianness. Settings.CORRECT_SIGNS = 1 # Not sure why, but needed Settings.EXPORTED_FUNCTIONS = ['_main', '_PyRun_SimpleStringFlags'] # for the demo self.do_ll_run(path_from_root('tests', 'python', 'python.small.bc'), 'hello python world!\n[0, 2, 4, 6]\n5\n22\n5.470000', args=['-S', '-c' '''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''']) # 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. def test_cases(self): try: self.banned_js_engines = [NODE_JS] # node issue 1669, exception causes stdout not to be flushed os.environ['EMCC_LEAVE_INPUTS_RAW'] = '1' Settings.CHECK_OVERFLOWS = 0 if Building.LLVM_OPTS: return self.skip("Our code is not exactly 'normal' llvm assembly") for name in glob.glob(path_from_root('tests', 'cases', '*.ll')): shortname = name.replace('.ll', '') if '' not in shortname: continue if '_ta2' in shortname and not Settings.USE_TYPED_ARRAYS == 2: print self.skip('case "%s" only relevant for ta2' % shortname) continue print >> sys.stderr, "Testing case '%s'..." % shortname output_file = path_from_root('tests', 'cases', shortname + '.txt') if Settings.QUANTUM_SIZE == 1: q1_output_file = path_from_root('tests', 'cases', shortname + '_q1.txt') if os.path.exists(q1_output_file): output_file = q1_output_file if os.path.exists(output_file): output = open(output_file, 'r').read() else: output = 'hello, world!' if output.rstrip() != 'skip': self.do_ll_run(path_from_root('tests', 'cases', name), output) # 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() exec(open(src_checker).read()) finally: del os.environ['EMCC_LEAVE_INPUTS_RAW'] # Autodebug the code def do_autodebug(self, filename): output = Popen(['python', AUTODEBUGGER, filename+'.o.ll', filename+'.o.ll.ll'], stdout=PIPE, stderr=self.stderr_redirect).communicate()[0] assert 'Success.' in output, output self.prep_ll_run(filename, filename+'.o.ll.ll', force_recompile=True) # rebuild .bc # TODO: use code in do_autodebug_post for this # Autodebug the code, after LLVM opts. Will only work once! def do_autodebug_post(self, filename): if not hasattr(self, 'post'): print 'Asking for post re-call' self.post = True return True print 'Autodebugging during post time' delattr(self, 'post') output = Popen(['python', AUTODEBUGGER, filename+'.o.ll', filename+'.o.ll.ll'], stdout=PIPE, stderr=self.stderr_redirect).communicate()[0] assert 'Success.' in output, output shutil.copyfile(filename + '.o.ll.ll', filename + '.o.ll') Building.llvm_as(filename) Building.llvm_dis(filename) def test_autodebug(self): if Building.LLVM_OPTS: return self.skip('LLVM opts mess us up') # Run a test that should work, generating some code self.test_structs() filename = os.path.join(self.get_dir(), 'src.cpp') self.do_autodebug(filename) # Compare to each other, and to expected output self.do_ll_run(path_from_root('tests', filename+'.o.ll.ll'), '''AD:-1,1''') assert open('stdout').read().startswith('AD:-1'), 'We must note when we enter functions' # Test using build_ll_hook src = ''' #include 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=self.do_autodebug) def test_profiling(self): src = ''' #include #include int main() { EMSCRIPTEN_PROFILE_INIT(3); EMSCRIPTEN_PROFILE_BEGIN(0); usleep(10 * 1000); EMSCRIPTEN_PROFILE_END(0); EMSCRIPTEN_PROFILE_BEGIN(1); usleep(50 * 1000); EMSCRIPTEN_PROFILE_END(1); EMSCRIPTEN_PROFILE_BEGIN(2); usleep(250 * 1000); EMSCRIPTEN_PROFILE_END(2); return 0; } ''' post1 = ''' def process(filename): src = open(filename, 'a') src.write(\'\'\' Profiling.dump(); \'\'\') src.close() ''' self.do_run(src, '''Profiling data: Block 0: ''', post_build=post1) ### Integration tests def test_scriptaclass(self): header_filename = os.path.join(self.get_dir(), 'header.h') header = ''' struct ScriptMe { int value; ScriptMe(int val); int getVal(); // XXX Sadly, inlining these will result in LLVM not // producing any code for them (when just building // as a library) void mulVal(int mul); }; ''' h = open(header_filename, 'w') h.write(header) h.close() src = ''' #include "header.h" ScriptMe::ScriptMe(int val) : value(val) { } int ScriptMe::getVal() { return value; } void ScriptMe::mulVal(int mul) { value *= mul; } ''' # Way 1: use demangler and namespacer script_src = ''' var sme = Module._.ScriptMe.__new__(83); // malloc(sizeof(ScriptMe)), ScriptMe::ScriptMe(sme, 83) / new ScriptMe(83) (at addr sme) Module._.ScriptMe.mulVal(sme, 2); // ScriptMe::mulVal(sme, 2) sme.mulVal(2) print('*' + Module._.ScriptMe.getVal(sme) + '*'); _free(sme); print('*ok*'); ''' post = ''' def process(filename): Popen(['python', DEMANGLER, filename], stdout=open(filename + '.tmp', 'w')).communicate() Popen(['python', NAMESPACER, filename, filename + '.tmp'], stdout=open(filename + '.tmp2', 'w')).communicate() src = open(filename, 'r').read().replace( '// {{MODULE_ADDITIONS}', 'Module["_"] = ' + open(filename + '.tmp2', 'r').read().replace('var ModuleNames = ', '').rstrip() + ';\n\n' + script_src + '\n\n' + '// {{MODULE_ADDITIONS}' ) open(filename, 'w').write(src) ''' # XXX disable due to possible v8 bug -- self.do_run(src, '*166*\n*ok*', post_build=post) # Way 2: use CppHeaderParser Settings.RUNTIME_TYPE_INFO = 1 header = ''' #include class Parent { protected: int value; public: Parent(int val); int getVal() { return value; }; // inline should work just fine here, unlike Way 1 before void mulVal(int mul); }; class Child1 : public Parent { public: Child1() : Parent(7) { printf("Child1:%d\\n", value); }; Child1(int val) : Parent(val*2) { value -= 1; printf("Child1:%d\\n", value); }; int getValSqr() { return value*value; } int getValSqr(int more) { return value*value*more; } int getValTimes(int times=1) { return value*times; } }; class Child2 : public Parent { public: Child2() : Parent(9) { printf("Child2:%d\\n", value); }; int getValCube() { return value*value*value; } static void printStatic() { printf("*static*\\n"); } virtual void virtualFunc() { printf("*virtualf*\\n"); } virtual void virtualFunc2() { printf("*virtualf2*\\n"); } static void runVirtualFunc(Child2 *self) { self->virtualFunc(); }; private: void doSomethingSecret() { printf("security breached!\\n"); }; // we should not be able to do this }; ''' open(header_filename, 'w').write(header) basename = os.path.join(self.get_dir(), 'bindingtest') output = Popen([BINDINGS_GENERATOR, basename, header_filename], stdout=PIPE, stderr=self.stderr_redirect).communicate()[0] #print output assert 'Traceback' not in output, 'Failure in binding generation: ' + output src = ''' #include "header.h" Parent::Parent(int val) : value(val) { printf("Parent:%d\\n", val); } void Parent::mulVal(int mul) { value *= mul; } #include "bindingtest.cpp" ''' post2 = ''' def process(filename): script_src_2 = \'\'\' var sme = new Parent(42); sme.mulVal(2); print('*') print(sme.getVal()); print('c1'); var c1 = new Child1(); print(c1.getVal()); c1.mulVal(2); print(c1.getVal()); print(c1.getValSqr()); print(c1.getValSqr(3)); print(c1.getValTimes()); // default argument should be 1 print(c1.getValTimes(2)); print('c1 v2'); c1 = new Child1(8); // now with a parameter, we should handle the overloading automatically and properly and use constructor #2 print(c1.getVal()); c1.mulVal(2); print(c1.getVal()); print(c1.getValSqr()); print(c1.getValSqr(3)); print('c2') var c2 = new Child2(); print(c2.getVal()); c2.mulVal(2); print(c2.getVal()); print(c2.getValCube()); var succeeded; try { succeeded = 0; print(c2.doSomethingSecret()); // should fail since private succeeded = 1; } catch(e) {} print(succeeded); try { succeeded = 0; print(c2.getValSqr()); // function from the other class succeeded = 1; } catch(e) {} print(succeeded); try { succeeded = 0; c2.getValCube(); // sanity succeeded = 1; } catch(e) {} print(succeeded); Child2.prototype.printStatic(); // static calls go through the prototype // virtual function c2.virtualFunc(); Child2.prototype.runVirtualFunc(c2); c2.virtualFunc2(); // extend the class from JS var c3 = new Child2; customizeVTable(c3, [{ original: Child2.prototype.virtualFunc, replacement: function() { print('*js virtualf replacement*'); } }, { original: Child2.prototype.virtualFunc2, replacement: function() { print('*js virtualf2 replacement*'); } }]); c3.virtualFunc(); Child2.prototype.runVirtualFunc(c3); c3.virtualFunc2(); c2.virtualFunc(); // original should remain the same Child2.prototype.runVirtualFunc(c2); c2.virtualFunc2(); print('*ok*'); \'\'\' src = open(filename, 'r').read().replace( '// {{MODULE_ADDITIONS}', open('bindingtest.js').read() + '\\n\\n' + script_src_2 + '\\n\\n' + '// {{MODULE_ADDITIONS}' ) open(filename, 'w').write(src) ''' self.do_run(src, '''* 84 c1 Parent:7 Child1:7 7 14 196 588 14 28 c1 v2 Parent:16 Child1:15 15 30 900 2700 c2 Parent:9 Child2:9 9 18 5832 0 0 1 *static* *virtualf* *virtualf* *virtualf2* Parent:9 Child2:9 *js virtualf replacement* *js virtualf replacement* *js virtualf2 replacement* *virtualf* *virtualf* *virtualf2* *ok* ''', post_build=post2) def test_typeinfo(self): if self.emcc_args is not None and self.emcc_args != []: return self.skip('full LLVM opts optimize out all the code that uses the type') Settings.RUNTIME_TYPE_INFO = 1 if Settings.QUANTUM_SIZE != 4: return self.skip('We assume normal sizes in the output here') src = ''' #include struct UserStruct { int x; char y; short z; }; struct Encloser { short x; UserStruct us; int y; }; int main() { Encloser e; e.us.y = 5; printf("*ok:%d*\\n", e.us.y); return 0; } ''' post = ''' def process(filename): src = open(filename, 'r').read().replace( '// {{POST_RUN_ADDITIONS}}', \'\'\' if (Runtime.typeInfo) { print('|' + Runtime.typeInfo.UserStruct.fields + '|' + Runtime.typeInfo.UserStruct.flatIndexes + '|'); var t = Runtime.generateStructInfo(['x', { us: ['x', 'y', 'z'] }, 'y'], 'Encloser') print('|' + [t.x, t.us.x, t.us.y, t.us.z, t.y] + '|'); print('|' + JSON.stringify(Runtime.generateStructInfo(['x', 'y', 'z'], 'UserStruct')) + '|'); } else { print('No type info.'); } \'\'\' ) open(filename, 'w').write(src) ''' self.do_run(src, '*ok:5*\n|i32,i8,i16|0,4,6|\n|0,4,8,10,12|\n|{"__size__":8,"x":0,"y":4,"z":6}|', post_build=post) # Make sure that without the setting, we don't spam the .js with the type info Settings.RUNTIME_TYPE_INFO = 0 self.do_run(src, 'No type info.', post_build=post) ### Tests for tools def test_safe_heap(self): if not Settings.SAFE_HEAP: return self.skip('We need SAFE_HEAP to test SAFE_HEAP') if Settings.USE_TYPED_ARRAYS == 2: return self.skip('It is ok to violate the load-store assumption with TA2') if Building.LLVM_OPTS: return self.skip('LLVM can optimize away the intermediate |x|') src = ''' #include int main() { int *x = new int; *x = 20; float *y = (float*)x; printf("%f\\n", *y); printf("*ok*\\n"); return 0; } ''' try: self.do_run(src, '*nothingatall*') except Exception, e: # This test *should* fail, by throwing this exception assert 'Assertion failed: Load-store consistency assumption failure!' in str(e), str(e) # And we should not fail if we disable checking on that line Settings.SAFE_HEAP = 3 Settings.SAFE_HEAP_LINES = ["src.cpp:7"] self.do_run(src, '*ok*') # But if we disable the wrong lines, we still fail Settings.SAFE_HEAP_LINES = ["src.cpp:99"] try: self.do_run(src, '*nothingatall*') except Exception, e: # This test *should* fail, by throwing this exception assert 'Assertion failed: Load-store consistency assumption failure!' in str(e), str(e) # And reverse the checks with = 2 Settings.SAFE_HEAP = 2 Settings.SAFE_HEAP_LINES = ["src.cpp:99"] self.do_run(src, '*ok*') Settings.SAFE_HEAP = 1 # Linking multiple files should work too module = ''' #include void callFunc() { int *x = new int; *x = 20; float *y = (float*)x; printf("%f\\n", *y); } ''' module_name = os.path.join(self.get_dir(), 'module.cpp') open(module_name, 'w').write(module) main = ''' #include extern void callFunc(); int main() { callFunc(); int *x = new int; *x = 20; float *y = (float*)x; printf("%f\\n", *y); printf("*ok*\\n"); return 0; } ''' main_name = os.path.join(self.get_dir(), 'main.cpp') open(main_name, 'w').write(main) Building.emcc(module_name, ['-g']) Building.emcc(main_name, ['-g']) all_name = os.path.join(self.get_dir(), 'all.bc') Building.link([module_name + '.o', main_name + '.o'], all_name) try: self.do_ll_run(all_name, '*nothingatall*') except Exception, e: # This test *should* fail, by throwing this exception assert 'Assertion failed: Load-store consistency assumption failure!' in str(e), str(e) # And we should not fail if we disable checking on those lines Settings.SAFE_HEAP = 3 Settings.SAFE_HEAP_LINES = ["module.cpp:7", "main.cpp:9"] self.do_ll_run(all_name, '*ok*') # But we will fail if we do not disable exactly what we need to - any mistake leads to error for lines in [["module.cpp:22", "main.cpp:9"], ["module.cpp:7", "main.cpp:29"], ["module.cpp:127", "main.cpp:449"], ["module.cpp:7"], ["main.cpp:9"]]: Settings.SAFE_HEAP_LINES = lines try: self.do_ll_run(all_name, '*nothingatall*') except Exception, e: # This test *should* fail, by throwing this exception assert 'Assertion failed: Load-store consistency assumption failure!' in str(e), str(e) def test_check_overflow(self): Settings.CHECK_OVERFLOWS = 1 Settings.CORRECT_OVERFLOWS = 0 src = ''' #include int main() { int t = 77; for (int i = 0; i < 30; i++) { //t = (t << 2) + t + 1; // This would have worked, since << forces into 32-bit int... t = t*5 + 1; // Python lookdict_string has ~the above line, which turns into this one with optimizations... printf("%d,%d\\n", t, t & 127); } return 0; } ''' try: self.do_run(src, '*nothingatall*') except Exception, e: # This test *should* fail, by throwing this exception assert 'Too many corrections' in str(e), str(e) def test_debug(self): src = ''' #include #include void checker(int x) { x += 20; assert(x < 15); // this is line 7! } int main() { checker(10); return 0; } ''' try: post = r''' def process(filename): lines = open(filename, 'r').readlines() lines = filter(lambda line: '___assert_fail(' in line or '___assert_func(' in line, lines) found_line_num = any(('//@line 7 "' in line) for line in lines) found_filename = any(('src.cpp"\n' in line) for line in lines) assert found_line_num, 'Must have debug info with the line number' assert found_filename, 'Must have debug info with the filename' ''' self.do_run(src, '*nothingatall*', post_build=post) except Exception, e: # This test *should* fail assert 'Assertion failed' in str(e), str(e) def test_linespecific(self): if self.emcc_args: self.emcc_args += ['--llvm-opts', '0'] # llvm full opts make the expected failures here not happen Settings.CHECK_SIGNS = 0 Settings.CHECK_OVERFLOWS = 0 # Signs src = ''' #include #include int main() { int varey = 100; unsigned int MAXEY = -1; printf("*%d*\\n", varey >= MAXEY); // 100 >= -1? not in unsigned! } ''' Settings.CORRECT_SIGNS = 0 self.do_run(src, '*1*') # This is a fail - we expect 0 Settings.CORRECT_SIGNS = 1 self.do_run(src, '*0*') # Now it will work properly # And now let's fix just that one line Settings.CORRECT_SIGNS = 2 Settings.CORRECT_SIGNS_LINES = ["src.cpp:9"] self.do_run(src, '*0*') # Fixing the wrong line should not work Settings.CORRECT_SIGNS = 2 Settings.CORRECT_SIGNS_LINES = ["src.cpp:3"] self.do_run(src, '*1*') # And reverse the checks with = 2 Settings.CORRECT_SIGNS = 3 Settings.CORRECT_SIGNS_LINES = ["src.cpp:3"] self.do_run(src, '*0*') Settings.CORRECT_SIGNS = 3 Settings.CORRECT_SIGNS_LINES = ["src.cpp:9"] self.do_run(src, '*1*') Settings.CORRECT_SIGNS = 0 # Overflows src = ''' #include int main() { int t = 77; for (int i = 0; i < 30; i++) { t = t*5 + 1; } printf("*%d,%d*\\n", t, t & 127); return 0; } ''' correct = '*186854335,63*' Settings.CORRECT_OVERFLOWS = 0 try: self.do_run(src, correct) raise Exception('UNEXPECTED-PASS') except Exception, e: assert 'UNEXPECTED' not in str(e), str(e) assert 'Expected to find' in str(e), str(e) Settings.CORRECT_OVERFLOWS = 1 self.do_run(src, correct) # Now it will work properly # And now let's fix just that one line Settings.CORRECT_OVERFLOWS = 2 Settings.CORRECT_OVERFLOWS_LINES = ["src.cpp:6"] self.do_run(src, correct) # Fixing the wrong line should not work Settings.CORRECT_OVERFLOWS = 2 Settings.CORRECT_OVERFLOWS_LINES = ["src.cpp:3"] try: self.do_run(src, correct) raise Exception('UNEXPECTED-PASS') except Exception, e: assert 'UNEXPECTED' not in str(e), str(e) assert 'Expected to find' in str(e), str(e) # And reverse the checks with = 2 Settings.CORRECT_OVERFLOWS = 3 Settings.CORRECT_OVERFLOWS_LINES = ["src.cpp:3"] self.do_run(src, correct) Settings.CORRECT_OVERFLOWS = 3 Settings.CORRECT_OVERFLOWS_LINES = ["src.cpp:6"] try: self.do_run(src, correct) raise Exception('UNEXPECTED-PASS') except Exception, e: assert 'UNEXPECTED' not in str(e), str(e) assert 'Expected to find' in str(e), str(e) Settings.CORRECT_OVERFLOWS = 0 # Roundings src = ''' #include #include int main() { TYPE x = -5; printf("*%d*", x/2); x = 5; printf("*%d*", x/2); float y = -5.33; x = y; printf("*%d*", x); y = 5.33; x = y; printf("*%d*", x); printf("\\n"); } ''' if Settings.I64_MODE == 0: # the errors here are very specific to non-i64 mode 1 Settings.CORRECT_ROUNDINGS = 0 self.do_run(src.replace('TYPE', 'long long'), '*-3**2**-6**5*') # JS floor operations, always to the negative. This is an undetected error here! self.do_run(src.replace('TYPE', 'int'), '*-2**2**-5**5*') # We get these right, since they are 32-bit and we can shortcut using the |0 trick self.do_run(src.replace('TYPE', 'unsigned int'), '*-3**2**-6**5*') # We fail, since no fast shortcut for 32-bit unsigneds Settings.CORRECT_ROUNDINGS = 1 Settings.CORRECT_SIGNS = 1 # To be correct here, we need sign corrections as well self.do_run(src.replace('TYPE', 'long long'), '*-2**2**-5**5*') # Correct self.do_run(src.replace('TYPE', 'int'), '*-2**2**-5**5*') # Correct self.do_run(src.replace('TYPE', 'unsigned int'), '*2147483645**2**-5**5*') # Correct Settings.CORRECT_SIGNS = 0 if Settings.I64_MODE == 0: # the errors here are very specific to non-i64 mode 1 Settings.CORRECT_ROUNDINGS = 2 Settings.CORRECT_ROUNDINGS_LINES = ["src.cpp:13"] # Fix just the last mistake self.do_run(src.replace('TYPE', 'long long'), '*-3**2**-5**5*') self.do_run(src.replace('TYPE', 'int'), '*-2**2**-5**5*') # Here we are lucky and also get the first one right self.do_run(src.replace('TYPE', 'unsigned int'), '*-3**2**-5**5*') # No such luck here # And reverse the check with = 2 if Settings.I64_MODE == 0: # the errors here are very specific to non-i64 mode 1 Settings.CORRECT_ROUNDINGS = 3 Settings.CORRECT_ROUNDINGS_LINES = ["src.cpp:999"] self.do_run(src.replace('TYPE', 'long long'), '*-2**2**-5**5*') self.do_run(src.replace('TYPE', 'int'), '*-2**2**-5**5*') Settings.CORRECT_SIGNS = 1 # To be correct here, we need sign corrections as well self.do_run(src.replace('TYPE', 'unsigned int'), '*2147483645**2**-5**5*') Settings.CORRECT_SIGNS = 0 def test_pgo(self): Settings.PGO = Settings.CHECK_OVERFLOWS = Settings.CORRECT_OVERFLOWS = Settings.CHECK_SIGNS = Settings.CORRECT_SIGNS = 1 src = ''' #include int main() { int t = 77; for (int i = 0; i < 30; i++) { t = t*5 + 1; } printf("*%d,%d*\\n", t, t & 127); int varey = 100; unsigned int MAXEY = -1; for (int j = 0; j < 2; j++) { printf("*%d*\\n", varey >= MAXEY); // 100 >= -1? not in unsigned! MAXEY = 1; // So we succeed the second time around } return 0; } ''' def check(output): # TODO: check the line # if self.emcc_args is None or self.emcc_args == []: # LLVM full opts optimize out some corrections assert 'Overflow|src.cpp:6 : 60 hits, %20 failures' in output, 'no indication of Overflow corrections: ' + output assert 'UnSign|src.cpp:13 : 6 hits, %17 failures' in output, 'no indication of Sign corrections: ' + output return output self.do_run(src, '*186854335,63*\n', output_nicerizer=check) Settings.PGO = Settings.CHECK_OVERFLOWS = Settings.CORRECT_OVERFLOWS = Settings.CHECK_SIGNS = Settings.CORRECT_SIGNS = 0 # Now, recompile with the PGO data, and it should work pgo_data = read_pgo_data(self.get_stdout_path()) Settings.CORRECT_SIGNS = 2 Settings.CORRECT_SIGNS_LINES = pgo_data['signs_lines'] Settings.CORRECT_OVERFLOWS = 2 Settings.CORRECT_OVERFLOWS_LINES = pgo_data['overflows_lines'] self.do_run(src, '*186854335,63*\n') # Sanity check: Without PGO, we will fail try: self.do_run(src, '*186854335,63*\n') except: pass def test_exit_status(self): Settings.CATCH_EXIT_CODE = 1 src = ''' #include #include int main() { printf("hello, world!\\n"); exit(118); // Unusual exit status to make sure it's working! } ''' self.do_run(src, 'hello, world!\nExit Status: 118') # Generate tests for everything def make_run(fullname, name=-1, compiler=-1, llvm_opts=0, embetter=0, quantum_size=0, typed_arrays=0, emcc_args=None): exec(''' class %s(T): def tearDown(self): super(%s, self).tearDown() def setUp(self): super(%s, self).setUp() Building.COMPILER_TEST_OPTS = ['-g'] os.chdir(self.get_dir()) # Ensure the directory exists and go there Building.COMPILER = %r self.emcc_args = %s if self.emcc_args is not None: Settings.load(self.emcc_args) Building.LLVM_OPTS = 0 return llvm_opts = %d # 1 is yes, 2 is yes and unsafe embetter = %d quantum_size = %d # TODO: Move much of these to a init() function in shared.py, and reuse that Settings.USE_TYPED_ARRAYS = %d Settings.INVOKE_RUN = 1 Settings.RELOOP = 0 # we only do them in the "o2" pass Settings.MICRO_OPTS = embetter Settings.QUANTUM_SIZE = quantum_size Settings.ASSERTIONS = 1-embetter Settings.SAFE_HEAP = 1-(embetter and llvm_opts) Building.LLVM_OPTS = llvm_opts Settings.PGO = 0 Settings.CHECK_OVERFLOWS = 1-(embetter or llvm_opts) Settings.CORRECT_OVERFLOWS = 1-(embetter and llvm_opts) Settings.CORRECT_SIGNS = 0 Settings.CORRECT_ROUNDINGS = 0 Settings.CORRECT_OVERFLOWS_LINES = CORRECT_SIGNS_LINES = CORRECT_ROUNDINGS_LINES = SAFE_HEAP_LINES = [] Settings.CHECK_SIGNS = 0 #1-(embetter or llvm_opts) Settings.INIT_STACK = 0 Settings.RUNTIME_TYPE_INFO = 0 Settings.DISABLE_EXCEPTION_CATCHING = 0 Settings.PROFILE = 0 Settings.INCLUDE_FULL_LIBRARY = 0 Settings.BUILD_AS_SHARED_LIB = 0 Settings.RUNTIME_LINKED_LIBS = [] Settings.CATCH_EXIT_CODE = 0 Settings.EMULATE_UNALIGNED_ACCESSES = int(Settings.USE_TYPED_ARRAYS == 2 and Building.LLVM_OPTS == 2) Settings.DOUBLE_MODE = 1 if Settings.USE_TYPED_ARRAYS and Building.LLVM_OPTS == 0 else 0 if Settings.USE_TYPED_ARRAYS == 2: Settings.I64_MODE = 1 Settings.SAFE_HEAP = 1 # only checks for alignment problems, which is very important with unsafe opts else: Settings.I64_MODE = 0 Building.pick_llvm_opts(3) TT = %s ''' % (fullname, fullname, fullname, compiler, str(emcc_args), llvm_opts, embetter, quantum_size, typed_arrays, fullname)) return TT # Make one run with the defaults exec('default = make_run("default", compiler=CLANG, emcc_args=[])') # Make one run with -O1, with safe heap exec('o1 = make_run("o1", compiler=CLANG, emcc_args=["-O1", "-s", "SAFE_HEAP=1"])') # Make one run with -O2, but without closure (we enable closure in specific tests, otherwise on everything it is too slow) exec('o2 = make_run("o2", compiler=CLANG, emcc_args=["-O2", "--closure", "0"])') # Make custom runs with various options for compiler, quantum, embetter, typed_arrays, llvm_opts in [ (CLANG, 1, 1, 0, 0), (CLANG, 1, 1, 1, 1), (CLANG, 4, 0, 0, 0), (CLANG, 4, 0, 0, 1), (CLANG, 4, 1, 1, 0), (CLANG, 4, 1, 1, 1), ]: fullname = 's_%d_%d%s%s' % ( llvm_opts, embetter, '' if quantum == 4 else '_q' + str(quantum), '' if typed_arrays in [0, 1] else '_t' + str(typed_arrays) ) exec('%s = make_run(fullname, %r,%r,%d,%d,%d,%d)' % (fullname, fullname, compiler, llvm_opts, embetter, quantum, typed_arrays)) del T # T is just a shape for the specific subclasses, we don't test it itself class other(RunnerCore): def test_emcc(self): emcc_debug = os.environ.get('EMCC_DEBUG') def clear(): for name in os.listdir(self.get_dir()): try_delete(name) if emcc_debug: for name in os.listdir(EMSCRIPTEN_TEMP_DIR): try_delete(os.path.join(EMSCRIPTEN_TEMP_DIR, name)) for compiler in [EMCC, EMXX]: shortcompiler = os.path.basename(compiler) suffix = '.c' if compiler == EMCC else '.cpp' # --version output = Popen([compiler, '--version'], stdout=PIPE, stderr=PIPE).communicate() self.assertContained('''emcc (Emscripten GCC-like replacement) 2.0 Copyright (C) 2011 the Emscripten authors. This is free and open source software under the MIT license. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ''', output[0], output[1]) # --help output = Popen([compiler, '--help'], stdout=PIPE, stderr=PIPE).communicate() self.assertContained('''%s [options] file... Most normal gcc/g++ options will work, for example: --help Display this information --version Display compiler version information Options that are modified or new in %s include: -O0 No optimizations (default) ''' % (shortcompiler, shortcompiler), output[0], output[1]) # emcc src.cpp ==> writes a.out.js clear() output = Popen([compiler, path_from_root('tests', 'hello_world' + suffix)], stdout=PIPE, stderr=PIPE).communicate() assert len(output[0]) == 0, output[0] assert os.path.exists('a.out.js'), '\n'.join(output) self.assertContained('hello, world!', run_js('a.out.js')) # properly report source code errors, and stop there clear() assert not os.path.exists('a.out.js') output = Popen([compiler, path_from_root('tests', 'hello_world_error' + suffix)], stdout=PIPE, stderr=PIPE).communicate() assert not os.path.exists('a.out.js'), 'compilation failed, so no output file is expected' assert len(output[0]) == 0, output[0] self.assertNotContained('IOError', output[1]) # no python stack self.assertNotContained('Traceback', output[1]) # no python stack self.assertContained('error: invalid preprocessing directive', output[1]) self.assertContained("error: use of undeclared identifier 'cheez", output[1]) self.assertContained('2 errors generated', output[1]) assert output[1].split('2 errors generated.')[1].replace('\n', '') == 'emcc: compiler frontend failed to generate LLVM bitcode, halting' # emcc src.cpp -c and emcc src.cpp -o src.[o|bc] ==> should give a .bc file # regression check: -o js should create "js", with bitcode content for args in [['-c'], ['-o', 'src.o'], ['-o', 'src.bc'], ['-o', 'js']]: target = args[1] if len(args) == 2 else 'hello_world.o' clear() Popen([compiler, path_from_root('tests', 'hello_world' + suffix)] + args, stdout=PIPE, stderr=PIPE).communicate() syms = Building.llvm_nm(target) assert len(syms.defs) == 1 and 'main' in syms.defs, 'Failed to generate valid bitcode' if target == 'js': # make sure emcc can recognize the target as a bitcode file shutil.move(target, target + '.bc') target += '.bc' output = Popen([compiler, target, '-o', target + '.js'], stdout = PIPE, stderr = PIPE).communicate() assert len(output[0]) == 0, output[0] assert os.path.exists(target + '.js'), 'Expected %s to exist since args are %s : %s' % (target + '.js', str(args), '\n'.join(output)) self.assertContained('hello, world!', run_js(target + '.js')) # emcc src.ll ==> generates .js clear() output = Popen([compiler, path_from_root('tests', 'hello_world.ll')], stdout=PIPE, stderr=PIPE).communicate() assert len(output[0]) == 0, output[0] assert os.path.exists('a.out.js'), '\n'.join(output) self.assertContained('hello, world!', run_js('a.out.js')) # emcc [..] -o [path] ==> should work with absolute paths try: os.mkdir('a_dir') os.chdir('a_dir') os.mkdir('b_dir') for path in [os.path.abspath(os.path.join('..', 'file1.js')), os.path.join('b_dir', 'file2.js')]: clear() output = Popen([compiler, path_from_root('tests', 'hello_world.ll'), '-o', path], stdout=PIPE, stderr=PIPE).communicate() assert os.path.exists(path), path + ' does not exist; ' + '\n'.join(output) self.assertContained('hello, world!', run_js(path)) finally: os.chdir(self.get_dir()) try: shutil.rmtree('a_dir') except: pass # dlmalloc. dlmalloc is special in that it is the only part of libc that is (1) hard to write well, and # very speed-sensitive. So we do not implement it in JS in library.js, instead we compile it from source for source, has_malloc in [('hello_world' + suffix, False), ('hello_malloc.cpp', True)]: clear() output = Popen([compiler, path_from_root('tests', source)], stdout=PIPE, stderr=PIPE).communicate() assert os.path.exists('a.out.js'), '\n'.join(output) self.assertContained('hello, world!', run_js('a.out.js')) generated = open('a.out.js').read() assert ('function _malloc(bytes) {' in generated) == (not has_malloc), 'If malloc is needed, it should be there, if not not' # Optimization: emcc src.cpp -o something.js [-Ox]. -O0 is the same as not specifying any optimization setting for params, opt_level, bc_params, closure, has_malloc in [ # bc params are used after compiling to bitcode (['-o', 'something.js'], 0, None, 0, 1), (['-o', 'something.js', '-O0'], 0, None, 0, 0), (['-o', 'something.js', '-O1'], 1, None, 0, 0), (['-o', 'something.js', '-O1', '--closure', '1'], 1, None, 1, 0), (['-o', 'something.js', '-O2'], 2, None, 1, 1), (['-o', 'something.js', '-O2', '--closure', '0'], 2, None, 0, 0), (['-o', 'something.js', '-O3'], 3, None, 1, 1), (['-o', 'something.js', '-O3', '--closure', '0'], 3, None, 0, 0), # and, test compiling to bitcode first (['-o', 'something.bc'], 0, [], 0, 0), (['-o', 'something.bc'], 0, ['-O0'], 0, 0), (['-o', 'something.bc'], 1, ['-O1'], 0, 0), (['-o', 'something.bc'], 2, ['-O2'], 1, 0), (['-o', 'something.bc'], 3, ['-O3'], 1, 0), (['-O1', '-o', 'something.bc'], 0, [], 0, 0), # -Ox is ignored and warned about ]: #print params, opt_level, bc_params, closure clear() output = Popen([compiler, path_from_root('tests', 'hello_world_loop' + ('_malloc' if has_malloc else '') + '.cpp')] + params, stdout=PIPE, stderr=PIPE).communicate() assert len(output[0]) == 0, output[0] if bc_params is not None: if '-O1' in params and 'something.bc' in params: assert 'warning: -Ox flags ignored, since not generating JavaScript' in output[1] assert os.path.exists('something.bc'), output[1] output = Popen([compiler, 'something.bc', '-o', 'something.js'] + bc_params, stdout=PIPE, stderr=PIPE).communicate() assert os.path.exists('something.js'), output[1] assert ('warning: The relooper optimization can be very slow.' in output[1]) == (opt_level >= 2), 'relooper warning should appear in opt >= 2' assert ('Warning: Applying some potentially unsafe optimizations!' in output[1]) == (opt_level >= 3), 'unsafe warning should appear in opt >= 3' self.assertContained('hello, world!', run_js('something.js')) # Verify optimization level etc. in the generated code # XXX these are quite sensitive, and will need updating when code generation changes generated = open('something.js').read() # TODO: parse out the _main function itself, not support code, if the tests below need that some day assert 'new Uint16Array' in generated and 'new Uint32Array' in generated, 'typed arrays 2 should be used by default' assert 'SAFE_HEAP' not in generated, 'safe heap should not be used by default' assert ': while(' not in generated, 'when relooping we also js-optimize, so there should be no labelled whiles' if closure: assert 'Module._main=' in generated, 'closure compiler should have been run (and output should be minified)' else: # closure has not been run, we can do some additional checks. TODO: figure out how to do these even with closure assert 'Module._main = ' not in generated, 'closure compiler should not have been run' # XXX find a way to test this: assert ('& 255' in generated or '&255' in generated) == (opt_level <= 2), 'corrections should be in opt <= 2' assert ('(__label__)' in generated) == (opt_level <= 1), 'relooping should be in opt >= 2' assert ('assert(STACKTOP < STACK_MAX' in generated) == (opt_level == 0), 'assertions should be in opt == 0' assert 'var $i;' in generated or 'var $storemerge3;' in generated or 'var $storemerge4;' in generated or 'var $i_04;' in generated, 'micro opts should always be on' if opt_level >= 1: assert 'HEAP8[HEAP32[' in generated or 'HEAP8[$vla1 + $storemerge4 / 2 | 0]' in generated or 'HEAP8[$vla1 + ($storemerge4 / 2 | 0)]' in generated or 'HEAP8[$vla1 + $i_04 / 2 | 0]' in generated or 'HEAP8[$vla1 + ($i_04 / 2 | 0)]' in generated, 'eliminator should create compound expressions, and fewer one-time vars' assert ('_puts(' in generated) == (opt_level >= 1), 'with opt >= 1, llvm opts are run and they should optimize printf to puts' assert ('function _malloc(bytes) {' in generated) == (not has_malloc), 'If malloc is needed, it should be there, if not not' assert 'function _main() {' in generated, 'Should be unminified, including whitespace' # emcc -s RELOOP=1 src.cpp ==> should pass -s to emscripten.py. --typed-arrays is a convenient alias for -s USE_TYPED_ARRAYS for params, test, text in [ (['-s', 'USE_TYPED_ARRAYS=0'], lambda generated: 'new Int32Array' not in generated, 'disable typed arrays'), (['-s', 'USE_TYPED_ARRAYS=1'], lambda generated: 'IHEAPU = ' in generated, 'typed arrays 1 selected'), ([], lambda generated: 'Module["_dump"]' not in generated, 'dump is not exported by default'), (['-s', 'EXPORTED_FUNCTIONS=["_main", "_dump"]'], lambda generated: 'Module["_dump"]' in generated, 'dump is now exported'), (['--typed-arrays', '0'], lambda generated: 'new Int32Array' not in generated, 'disable typed arrays'), (['--typed-arrays', '1'], lambda generated: 'IHEAPU = ' in generated, 'typed arrays 1 selected'), (['--typed-arrays', '2'], lambda generated: 'new Uint16Array' in generated and 'new Uint32Array' in generated, 'typed arrays 2 selected'), (['--llvm-opts', '1'], lambda generated: '_puts(' in generated, 'llvm opts requested'), ]: clear() output = Popen([compiler, path_from_root('tests', 'hello_world_loop.cpp'), '-o', 'a.out.js'] + params, stdout=PIPE, stderr=PIPE).communicate() assert len(output[0]) == 0, output[0] assert os.path.exists('a.out.js'), '\n'.join(output) self.assertContained('hello, world!', run_js('a.out.js')) assert test(open('a.out.js').read()), text # Compiling two source files into a final JS. for args, target in [([], 'a.out.js'), (['-o', 'combined.js'], 'combined.js')]: clear() output = Popen([compiler, path_from_root('tests', 'twopart_main.cpp'), path_from_root('tests', 'twopart_side.cpp')] + args, stdout=PIPE, stderr=PIPE).communicate() assert len(output[0]) == 0, output[0] assert os.path.exists(target), '\n'.join(output) self.assertContained('side got: hello from main, over', run_js(target)) # Compiling two files with -c will generate separate .bc files clear() output = Popen([compiler, path_from_root('tests', 'twopart_main.cpp'), path_from_root('tests', 'twopart_side.cpp'), '-c'] + args, stdout=PIPE, stderr=PIPE).communicate() if '-o' in args: # specifying -o and -c is an error assert 'fatal error' in output[1], output[1] continue assert os.path.exists('twopart_main.o'), '\n'.join(output) assert os.path.exists('twopart_side.o'), '\n'.join(output) assert not os.path.exists(target), 'We should only have created bitcode here: ' + '\n'.join(output) # Compiling one of them alone is expected to fail output = Popen([compiler, 'twopart_main.o'] + args, stdout=PIPE, stderr=PIPE).communicate() assert os.path.exists(target), '\n'.join(output) #print '\n'.join(output) self.assertContained('is not a function', run_js(target, stderr=STDOUT)) try_delete(target) # Combining those bc files into js should work output = Popen([compiler, 'twopart_main.o', 'twopart_side.o'] + args, stdout=PIPE, stderr=PIPE).communicate() assert os.path.exists(target), '\n'.join(output) self.assertContained('side got: hello from main, over', run_js(target)) # Combining bc files into another bc should also work try_delete(target) assert not os.path.exists(target) output = Popen([compiler, 'twopart_main.o', 'twopart_side.o', '-o', 'combined.bc'] + args, stdout=PIPE, stderr=PIPE).communicate() syms = Building.llvm_nm('combined.bc') assert len(syms.defs) == 2 and 'main' in syms.defs, 'Failed to generate valid bitcode' output = Popen([compiler, 'combined.bc', '-o', 'combined.bc.js'], stdout = PIPE, stderr = PIPE).communicate() assert len(output[0]) == 0, output[0] assert os.path.exists('combined.bc.js'), 'Expected %s to exist' % ('combined.bc.js') self.assertContained('side got: hello from main, over', run_js('combined.bc.js')) # --js-transform clear() trans = os.path.join(self.get_dir(), 't.py') trans_file = open(trans, 'w') trans_file.write(''' import sys f = open(sys.argv[1], 'w') f.write('transformed!') f.close() ''') trans_file.close() output = Popen([compiler, path_from_root('tests', 'hello_world' + suffix), '--js-transform', 'python t.py'], stdout=PIPE, stderr=PIPE).communicate() assert open('a.out.js').read() == 'transformed!', 'Transformed output must be as expected' # TODO: Add in files test a clear example of using disablePermissions, and link to it from the wiki # TODO: test normal project linking, static and dynamic: get_library should not need to be told what to link! # TODO: deprecate llvm optimizations, dlmalloc, etc. in emscripten.py. # For browser tests which report their success def run_test_server(expectedResult): class TestServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(s): assert s.path == expectedResult, 'Expected %s, got %s' % (expectedResult, s.path) httpd.shutdown() def handle_timeout(): assert False, 'Timed out while waiting for the browser test to finish' httpd.shutdown() httpd = BaseHTTPServer.HTTPServer(('localhost', 8888), TestServerHandler) httpd.timeout = 5; server_thread = threading.Thread(target=httpd.serve_forever) server_thread.daemon = True server_thread.start() server_thread.join(5.5) # Finally, do some web browser tests def run_browser(html_file, message, expectedResult = None): webbrowser.open_new(os.path.abspath(html_file)) print 'A web browser window should have opened a page containing the results of a part of this test.' print 'You need to manually look at the page to see that it works ok: ' + message print '(sleeping for a bit to keep the directory alive for the web browser..)' if expectedResult is not None: run_test_server(expectedResult) else: time.sleep(5) print '(moving on..)' # test HTML generation. clear() output = Popen([EMCC, path_from_root('tests', 'hello_world_sdl.cpp'), '-o', 'something.html'], stdout=PIPE, stderr=PIPE).communicate() assert len(output[0]) == 0, output[0] assert os.path.exists('something.html'), output run_browser('something.html', 'You should see "hello, world!" and a colored cube.') # And test running in a web worker clear() output = Popen([EMCC, path_from_root('tests', 'hello_world_worker.cpp'), '-o', 'worker.js'], stdout=PIPE, stderr=PIPE).communicate() assert len(output[0]) == 0, output[0] assert os.path.exists('worker.js'), output self.assertContained('you should not see this text when in a worker!', run_js('worker.js')) # code should run standalone html_file = open('main.html', 'w') html_file.write(''' ''') html_file.close() run_browser('main.html', 'You should see that the worker was called, and said "hello from worker!"') # test the OpenGL ES implementation clear() output = Popen([EMCC, path_from_root('tests', 'hello_world_gles.c'), '-o', 'something.html', '-DHAVE_BUILTIN_SINCOS', '--shell-file', path_from_root('tests', 'hello_world_gles_shell.html')], stdout=PIPE, stderr=PIPE).communicate() assert len(output[0]) == 0, output[0] assert os.path.exists('something.html'), output run_browser('something.html', 'You should see animating gears.', '/report_gl_result?true') def test_eliminator(self): input = open(path_from_root('tools', 'eliminator', 'eliminator-test.js')).read() expected = open(path_from_root('tools', 'eliminator', 'eliminator-test-output.js')).read() output = Popen([NODE_JS, COFFEESCRIPT, VARIABLE_ELIMINATOR], stdin=PIPE, stdout=PIPE).communicate(input)[0] self.assertIdentical(expected, output) def test_fix_closure(self): input = path_from_root('tests', 'test-fix-closure.js') expected = path_from_root('tests', 'test-fix-closure.out.js') Popen(['python', path_from_root('tools', 'fix_closure.py'), input, 'out.js']).communicate(input) output = open('out.js').read() assert '0,zzz_Q_39fa,0' in output assert 'function(a,c)' not in output # should be uninlined, so it gets a name assert run_js(input) == run_js('out.js') def test_js_optimizer(self): for input, expected, passes in [ (path_from_root('tools', 'test-js-optimizer.js'), open(path_from_root('tools', 'test-js-optimizer-output.js')).read(), ['hoistMultiples', 'loopOptimizer', 'unGlobalize', 'removeAssignsToUndefined', 'simplifyExpressionsPre', 'simplifyExpressionsPost']), (path_from_root('tools', 'test-js-optimizer-t2c.js'), open(path_from_root('tools', 'test-js-optimizer-t2c-output.js')).read(), ['simplifyExpressionsPre', 'optimizeShiftsConservative']), (path_from_root('tools', 'test-js-optimizer-t2.js'), open(path_from_root('tools', 'test-js-optimizer-t2-output.js')).read(), ['simplifyExpressionsPre', 'optimizeShiftsAggressive']), ]: output = Popen([NODE_JS, JS_OPTIMIZER, input] + passes, stdin=PIPE, stdout=PIPE).communicate()[0] self.assertIdentical(expected, output.replace('\n\n', '\n')) def test_reminder(self): assert False, 'Optimize makeGet/SetValue to do 16-bit reads/writes when possible, not just 8' assert False, 'Ensure all opts including linktime apply to bindings generator. might need to adjust visibility of bindings C funcs' assert False, 'Optimize double version of fasta benchmark' elif 'benchmark' in str(sys.argv): # Benchmarks. Run them with argument |benchmark|. To run a specific test, do # |benchmark.test_X|. fingerprint = [time.asctime()] try: fingerprint.append('em: ' + Popen(['git', 'show'], stdout=PIPE).communicate()[0].split('\n')[0]) except: pass try: d = os.getcwd() os.chdir(os.path.expanduser('~/Dev/mozilla-central')) fingerprint.append('sm: ' + filter(lambda line: 'changeset' in line, Popen(['hg', 'tip'], stdout=PIPE).communicate()[0].split('\n'))[0]) except: pass finally: os.chdir(d) print 'Running Emscripten benchmarks... [ %s ]' % ' | '.join(fingerprint) sys.argv = filter(lambda x: x != 'benchmark', sys.argv) assert(os.path.exists(CLOSURE_COMPILER)) try: index = SPIDERMONKEY_ENGINE.index("options('strict')") SPIDERMONKEY_ENGINE = SPIDERMONKEY_ENGINE[:index-1] + SPIDERMONKEY_ENGINE[index+1:] # closure generates non-strict except: pass Building.COMPILER = CLANG # Pick the JS engine to benchmark. If you specify one, it will be picked. For example, python tests/runner.py benchmark SPIDERMONKEY_ENGINE JS_ENGINE = JS_ENGINES[0] for i in range(1, len(sys.argv)): arg = sys.argv[i] if not arg.startswith('benchmark.test_'): JS_ENGINE = eval(arg) sys.argv[i] = None sys.argv = filter(lambda arg: arg is not None, sys.argv) print 'Benchmarking JS engine:', JS_ENGINE Building.COMPILER_TEST_OPTS = [] TEST_REPS = 10 TOTAL_TESTS = 8 tests_done = 0 total_times = map(lambda x: 0., range(TOTAL_TESTS)) total_native_times = map(lambda x: 0., range(TOTAL_TESTS)) class benchmark(RunnerCore): def print_stats(self, times, native_times, last=False): mean = sum(times)/len(times) squared_times = map(lambda x: x*x, times) mean_of_squared = sum(squared_times)/len(times) std = math.sqrt(mean_of_squared - mean*mean) sorted_times = times[:] sorted_times.sort() median = sum(sorted_times[len(sorted_times)/2 - 1:len(sorted_times)/2 + 1])/2 mean_native = sum(native_times)/len(native_times) squared_native_times = map(lambda x: x*x, native_times) mean_of_squared_native = sum(squared_native_times)/len(native_times) std_native = math.sqrt(mean_of_squared_native - mean_native*mean_native) sorted_native_times = native_times[:] sorted_native_times.sort() median_native = sum(sorted_native_times[len(sorted_native_times)/2 - 1:len(sorted_native_times)/2 + 1])/2 final = mean / mean_native if last: norm = 0 for i in range(len(times)): norm += times[i]/native_times[i] norm /= len(times) print print ' JavaScript: %.3f Native: %.3f Ratio: %.3f Normalized ratio: %.3f' % (mean, mean_native, final, norm) return print print ' JavaScript: mean: %.3f (+-%.3f) secs median: %.3f range: %.3f-%.3f (noise: %3.3f%%) (%d runs)' % (mean, std, median, min(times), max(times), 100*std/mean, TEST_REPS) print ' Native : mean: %.3f (+-%.3f) secs median: %.3f range: %.3f-%.3f (noise: %3.3f%%) JS is %.2f X slower' % (mean_native, std_native, median_native, min(native_times), max(native_times), 100*std_native/mean_native, final) def do_benchmark(self, src, args=[], expected_output='FAIL', emcc_args=[]): dirname = self.get_dir() filename = os.path.join(dirname, 'src.cpp') f = open(filename, 'w') f.write(src) f.close() final_filename = os.path.join(dirname, 'src.js') try_delete(final_filename) output = Popen([EMCC, filename, '-O3', '-s', 'USE_TYPED_ARRAYS=1', '-s', 'QUANTUM_SIZE=1', '-s', 'TOTAL_MEMORY=100*1024*1024', '-s', 'FAST_MEMORY=10*1024*1024', '-o', final_filename] + emcc_args, stdout=PIPE, stderr=self.stderr_redirect).communicate() assert os.path.exists(final_filename), 'Failed to compile file: ' + '\n'.join(output) # Run JS global total_times, tests_done times = [] for i in range(TEST_REPS): start = time.time() js_output = self.run_generated_code(JS_ENGINE, final_filename, args, check_timeout=False) curr = time.time()-start times.append(curr) total_times[tests_done] += curr if i == 0: # Sanity check on output self.assertContained(expected_output, js_output) # Run natively self.build_native(filename) global total_native_times native_times = [] for i in range(TEST_REPS): start = time.time() self.run_native(filename, args) curr = time.time()-start native_times.append(curr) total_native_times[tests_done] += curr self.print_stats(times, native_times) tests_done += 1 if tests_done == TOTAL_TESTS: print 'Total stats:', self.print_stats(total_times, total_native_times, last=True) def test_primes(self): src = ''' #include #include int main() { int primes = 0, curri = 2; while (primes < 100000) { int ok = true; for (int j = 2; j < sqrtf(curri); j++) { if (curri % j == 0) { ok = false; break; } } if (ok) { primes++; } curri++; } printf("lastprime: %d.\\n", curri-1); return 1; } ''' self.do_benchmark(src, [], 'lastprime: 1297001.') def test_memops(self): src = ''' #include #include #include int main() { int N = 1024*1024; int M = 190; int final = 0; char *buf = (char*)malloc(N); for (int t = 0; t < M; t++) { for (int i = 0; i < N; i++) buf[i] = (i + final)%256; for (int i = 0; i < N; i++) final += buf[i] & 1; final = final % 1000; } printf("final: %d.\\n", final); return 1; } ''' self.do_benchmark(src, [], 'final: 720.') def test_copy(self): src = r''' #include struct vec { int x, y, z; int r, g, b; vec(int x_, int y_, int z_, int r_, int g_, int b_) : x(x_), y(y_), z(z_), r(r_), g(g_), b(b_) {} static vec add(vec a, vec b) { return vec(a.x+b.x, a.y+b.y, a.z+b.z, a.r+b.r, a.g+b.g, a.b+b.b); } void norm() { x %= 1024; y %= 1024; z %= 1024; r %= 1024; b %= 1024; g %= 1024; } int sum() { return x + y + z + r + g + b; } }; int main() { int total = 0; for (int i = 0; i < 1250; i++) { for (int j = 0; j < 1000; j++) { vec c(i, i+i%10, j*2, i%255, j%120, i%15); vec d(j+i%10, j*2, j%255, i%120, j%15, j); vec e = c; c.norm(); d.norm(); vec f = vec::add(c, d); f = vec::add(e, f); f.norm(); f = vec::add(d, f); total += f.sum() % 100; total %= 10240; } } printf("sum:%d\n", total); return 1; } ''' self.do_benchmark(src, [], 'sum:9928\n', emcc_args=['-s', 'QUANTUM_SIZE=4', '-s', 'USE_TYPED_ARRAYS=2']) def test_fannkuch(self): src = open(path_from_root('tests', 'fannkuch.cpp'), 'r').read() self.do_benchmark(src, ['10'], 'Pfannkuchen(10) = 38.') def test_corrections(self): src = r''' #include #include int main() { int N = 4100; int M = 4100; unsigned int f = 0; unsigned short s = 0; for (int t = 0; t < M; t++) { for (int i = 0; i < N; i++) { f += i / ((t % 5)+1); if (f > 1000) f /= (t % 3)+1; if (i % 4 == 0) f += sqrtf(i) * (i % 8 == 0 ? 1 : -1); s += (short(f)*short(f)) % 256; } } printf("final: %d:%d.\n", f, s); return 1; } ''' self.do_benchmark(src, [], 'final: 826:14324.', emcc_args=['-s', 'CORRECT_SIGNS=1', '-s', 'CORRECT_OVERFLOWS=1', '-s', 'CORRECT_ROUNDINGS=1']) def fasta(self, double_rep): src = open(path_from_root('tests', 'fasta.cpp'), 'r').read().replace('double', double_rep) self.do_benchmark(src, ['2100000'], '''GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGA\nTCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACT\nAAAAATACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAG\nGCTGAGGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCG\nCCACTGCACTCCAGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAAGGCCGGGCGCGGT\nGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCA\nGGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAATACAAAAA\nTTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAG\nAATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTGCACTCCA\nGCCTGGGCGA''') def test_fasta_float(self): self.fasta('float') def zzztest_fasta_double(self): self.fasta('double') def test_skinning(self): src = open(path_from_root('tests', 'skinning_test_no_simd.cpp'), 'r').read() self.do_benchmark(src, ['10000', '1000'], 'blah=0.000000') def test_dlmalloc(self): # XXX This seems to have regressed slightly with emcc. Are -g and the signs lines passed properly? src = open(path_from_root('system', 'lib', 'dlmalloc.c'), 'r').read() + '\n\n\n' + open(path_from_root('tests', 'dlmalloc_test.c'), 'r').read() self.do_benchmark(src, ['400', '400'], '*400,0*', emcc_args=['-g', '-s', 'CORRECT_SIGNS=2', '-s', 'CORRECT_SIGNS_LINES=[4820, 4195, 4250, 4203, 4209, 4239, 4231]']) elif 'sanity' in str(sys.argv): # Run some sanity checks on the test runner and emcc. sys.argv = filter(lambda x: x != 'sanity', sys.argv) print print 'Running sanity checks.' print 'WARNING: This will modify %s, and in theory can break it although it should be restored properly. A backup will be saved in %s_backup' % (EM_CONFIG, EM_CONFIG) print assert os.path.exists(CONFIG_FILE), 'To run these tests, we need a (working!) %s file to already exist' % EM_CONFIG shutil.copyfile(CONFIG_FILE, CONFIG_FILE + '_backup') def restore(): shutil.copyfile(CONFIG_FILE + '_backup', CONFIG_FILE) SANITY_FILE = CONFIG_FILE + '_sanity' def wipe(): try_delete(CONFIG_FILE) try_delete(SANITY_FILE) commands = [[EMCC], ['python', path_from_root('tests', 'runner.py'), 'blahblah']] def mtime(filename): return os.stat(filename).st_mtime class sanity(RunnerCore): def setUp(self): wipe() def tearDown(self): restore() def do(self, command): if type(command) is not list: command = [command] return Popen(command, stdout=PIPE, stderr=STDOUT).communicate()[0] def check_working(self, command, expected=None): if type(command) is not list: command = [command] if expected is None: if command[0] == EMCC: expected = 'no input files' else: expected = "has no attribute 'blahblah'" output = self.do(command) self.assertContained(expected, output) return output def test_aaa_normal(self): # this should be the very first thing that runs. if this fails, everything else is irrelevant! for command in commands: # Your existing EM_CONFIG should work! restore() self.check_working(command) def test_firstrun(self): for command in commands: wipe() output = self.do(command) self.assertContained('Welcome to Emscripten!', output) self.assertContained('This is the first time any of the Emscripten tools has been run.', output) self.assertContained('A settings file has been copied to %s, at absolute path: %s' % (EM_CONFIG, CONFIG_FILE), output) self.assertContained('Please edit that file and change the paths to fit your system', output) self.assertContained('make sure LLVM_ROOT and NODE_JS are correct', output) self.assertContained('This command will now exit. When you are done editing those paths, re-run it.', output) assert output.replace('\n', '').endswith('===='), 'We should have stopped: ' + output assert (open(CONFIG_FILE).read() == open(path_from_root('settings.py')).read()), 'Settings should be copied from settings.py' # Second run, with bad EM_CONFIG for settings in ['blah', 'LLVM_ROOT="blah"; JS_ENGINES=[]; COMPILER_ENGINE=NODE_JS=SPIDERMONKEY_ENGINE=[]']: f = open(CONFIG_FILE, 'w') f.write(settings) f.close() output = self.do(command) if 'LLVM_ROOT' not in settings: self.assertContained('Error in evaluating %s' % EM_CONFIG, output) else: self.assertContained('FATAL', output) # sanity check should fail def test_closure_compiler(self): CLOSURE_FATAL = 'fatal: Closure compiler' CLOSURE_WARNING = 'WARNING: Closure compiler' # Sanity check should find closure restore() output = self.check_working(EMCC) self.assertNotContained(CLOSURE_FATAL, output) self.assertNotContained(CLOSURE_WARNING, output) # Append a bad path for closure, will warn f = open(CONFIG_FILE, 'a') f.write('CLOSURE_COMPILER = "/tmp/nowhere/nothingtoseehere/kjadsfkjwelkjsdfkqgas/nonexistent.txt"\n') f.close() output = self.check_working(EMCC, CLOSURE_WARNING) # And if you actually try to use the bad path, will be fatal f = open(CONFIG_FILE, 'a') f.write('CLOSURE_COMPILER = "/tmp/nowhere/nothingtoseehere/kjadsfkjwelkjsdfkqgas/nonexistent.txt"\n') f.close() output = self.check_working([EMCC, '-O2', 'tests/hello_world.cpp'], CLOSURE_FATAL) # With a working path, all is well restore() try_delete('a.out.js') output = self.check_working([EMCC, '-O2', 'tests/hello_world.cpp'], 'The relooper optimization can be very slow') assert os.path.exists('a.out.js') def test_emcc(self): SANITY_MESSAGE = 'Emscripten: Running sanity checks' SANITY_FAIL_MESSAGE = 'sanity check failed to run' # emcc should check sanity if no ${EM_CONFIG}_sanity restore() time.sleep(0.1) assert not os.path.exists(SANITY_FILE) # restore is just the settings, not the sanity output = self.check_working(EMCC) self.assertContained(SANITY_MESSAGE, output) assert os.path.exists(SANITY_FILE) # EMCC should have checked sanity successfully assert mtime(SANITY_FILE) >= mtime(CONFIG_FILE) self.assertNotContained(SANITY_FAIL_MESSAGE, output) # emcc run again should not sanity check, because the sanity file is newer output = self.check_working(EMCC) self.assertNotContained(SANITY_MESSAGE, output) self.assertNotContained(SANITY_FAIL_MESSAGE, output) # But the test runner should output = self.check_working(commands[1]) self.assertContained(SANITY_MESSAGE, output) self.assertNotContained(SANITY_FAIL_MESSAGE, output) # Make sure the test runner didn't do anything to the setup output = self.check_working(EMCC) self.assertNotContained(SANITY_MESSAGE, output) self.assertNotContained(SANITY_FAIL_MESSAGE, output) # emcc should also check sanity if the file is outdated time.sleep(0.1) restore() assert mtime(SANITY_FILE) < mtime(CONFIG_FILE) output = self.check_working(EMCC) self.assertContained(SANITY_MESSAGE, output) assert mtime(SANITY_FILE) >= mtime(CONFIG_FILE) self.assertNotContained(SANITY_FAIL_MESSAGE, output) def test_emcc_caching(self): INCLUDING_MESSAGE = 'emcc: including X' BUILDING_MESSAGE = 'emcc: building X for cache' EMCC_CACHE = Cache.dirname restore() Cache.erase() assert not os.path.exists(EMCC_CACHE) try: emcc_debug = os.environ.get('EMCC_DEBUG') os.environ['EMCC_DEBUG'] ='1' # Building a file that doesn't need cached stuff should not trigger cache generation output = self.do([EMCC, path_from_root('tests', 'hello_world.cpp')]) assert INCLUDING_MESSAGE.replace('X', 'dlmalloc') not in output assert BUILDING_MESSAGE.replace('X', 'dlmalloc') not in output self.assertContained('hello, world!', run_js('a.out.js')) assert not os.path.exists(EMCC_CACHE) try_delete('a.out.js') basebc_name = os.path.join(EMSCRIPTEN_TEMP_DIR, 'emcc-0-basebc.bc') dcebc_name = os.path.join(EMSCRIPTEN_TEMP_DIR, 'emcc-1-dce.bc') # Building a file that *does* need dlmalloc *should* trigger cache generation, but only the first time for filename, libname in [('hello_malloc.cpp', 'dlmalloc'), ('hello_libcxx.cpp', 'libcxx')]: for i in range(3): try_delete(basebc_name) # we might need to check this file later try_delete(dcebc_name) # we might need to check this file later output = self.do([EMCC, path_from_root('tests', filename)]) assert INCLUDING_MESSAGE.replace('X', libname) in output if libname == 'dlmalloc': assert INCLUDING_MESSAGE.replace('X', 'libcxx') not in output # we don't need libcxx in this code else: assert INCLUDING_MESSAGE.replace('X', 'dlmalloc') in output # libcxx always forces inclusion of dlmalloc assert (BUILDING_MESSAGE.replace('X', libname) in output) == (i == 0), 'Must only build the first time' self.assertContained('hello, world!', run_js('a.out.js')) assert os.path.exists(EMCC_CACHE) assert os.path.exists(os.path.join(EMCC_CACHE, libname + '.bc')) if libname == 'libcxx': assert os.stat(os.path.join(EMCC_CACHE, libname + '.bc')).st_size > 4000000, 'libc++ is big' assert os.stat(basebc_name).st_size > 4000000, 'libc++ is indeed big' assert os.stat(dcebc_name).st_size < 2000000, 'Dead code elimination must remove most of libc++' finally: if emcc_debug: os.environ['EMCC_DEBUG'] = emcc_debug else: raise Exception('Test runner is confused: ' + str(sys.argv)) if __name__ == '__main__': sys.argv = [sys.argv[0]] + ['-v'] + sys.argv[1:] # Verbose output by default # Sanity checks check_sanity(force=True) total_engines = len(JS_ENGINES) JS_ENGINES = filter(check_engine, JS_ENGINES) if len(JS_ENGINES) == 0: print 'WARNING: None of the JS engines in JS_ENGINES appears to work.' elif len(JS_ENGINES) < total_engines: print 'WARNING: Not all the JS engines in JS_ENGINES appears to work, ignoring those.' # Go unittest.main()