blob: 9e5222af3d5bd3628789bb86b9dd9f4534aa4a8f [file] [log] [blame]
# Copyright 2013 The Emscripten Authors. All rights reserved.
# Emscripten is available under two separate licenses, the MIT license and the
# University of Illinois/NCSA Open Source License. Both these licenses can be
# found in the LICENSE file.
from __future__ import print_function
import os
import platform
import shutil
import stat
import time
import re
import tempfile
from runner import RunnerCore, path_from_root, env_modify, chdir, create_test_file
from tools.shared import NODE_JS, PYTHON, EMCC, SPIDERMONKEY_ENGINE, V8_ENGINE, CONFIG_FILE, PIPE, STDOUT, EM_CONFIG, LLVM_ROOT, CANONICAL_TEMP_DIR
from tools.shared import run_process, try_delete, run_js, safe_ensure_dirs, expected_llvm_version, generate_sanity
from tools.shared import Cache, Settings
from tools import jsrun, shared
SANITY_FILE = CONFIG_FILE + '_sanity'
commands = [[PYTHON, EMCC], [PYTHON, path_from_root('tests', 'runner.py'), 'blahblah']]
def restore():
shutil.copyfile(CONFIG_FILE + '_backup', CONFIG_FILE)
# restore the config file and set it up for our uses
def restore_and_set_up():
restore()
with open(CONFIG_FILE, 'a') as f:
# don't use the native optimizer from the emsdk - we want to test how it builds
f.write('\nEMSCRIPTEN_NATIVE_OPTIMIZER = ""\n')
# make LLVM_ROOT sensitive to the LLVM env var, as we test that
f.write('\nLLVM_ROOT = os.path.expanduser(os.getenv("LLVM", "%s"))\n' % LLVM_ROOT)
# unfreeze the cache, so we can test that
f.write('\nFROZEN_CACHE = False\n')
# wipe the config and sanity files, creating a blank slate
def wipe():
try_delete(CONFIG_FILE)
try_delete(SANITY_FILE)
def add_to_config(content):
with open(CONFIG_FILE, 'a') as f:
f.write('\n' + content + '\n')
def mtime(filename):
return os.path.getmtime(filename)
def make_fake_clang(filename, version):
"""Create a fake clang that only handles --version
--version writes to stdout (unlike -v which writes to stderr)
"""
if not os.path.exists(os.path.dirname(filename)):
os.makedirs(os.path.dirname(filename))
with open(filename, 'w') as f:
f.write('#!/bin/sh\n')
f.write('echo "clang version %s"\n' % version)
f.write('echo "..."\n')
shutil.copyfile(filename, filename + '++')
os.chmod(filename, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
os.chmod(filename + '++', stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
def make_fake_llc(filename, targets):
"""Create a fake llc that only handles --version and writes target
list to stdout.
"""
if not os.path.exists(os.path.dirname(filename)):
os.makedirs(os.path.dirname(filename))
with open(filename, 'w') as f:
f.write('#!/bin/sh\n')
f.write('echo "llc fake output\nRegistered Targets:\n%s"' % targets)
os.chmod(filename, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
SANITY_MESSAGE = 'Emscripten: Running sanity checks'
EMBUILDER = path_from_root('embuilder.py')
# arguments to build a minimal hello world program, without even libc
# (-O1 avoids -O0's default assertions which bring in checking code;
# FILESYSTEM=0 avoids bringing libc for that)
# (ERROR_ON_UNDEFINED_SYMBOLS=0 is needed because __errno_location is
# not included on the native side but needed by a lot of JS libraries.)
MINIMAL_HELLO_WORLD = [path_from_root('tests', 'hello_world_em_asm.c'), '-O1', '-s', 'FILESYSTEM=0', '-s', 'ERROR_ON_UNDEFINED_SYMBOLS=0']
class sanity(RunnerCore):
@classmethod
def setUpClass(cls):
super(sanity, cls).setUpClass()
shutil.copyfile(CONFIG_FILE, CONFIG_FILE + '_backup')
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()
print('>>> the original settings file is:')
print(open(os.path.expanduser('~/.emscripten')).read())
print('<<<')
print()
assert os.path.exists(CONFIG_FILE), 'To run these tests, we need a (working!) %s file to already exist' % EM_CONFIG
assert 'EMCC_DEBUG' not in os.environ, 'do not run sanity checks in debug mode!'
assert 'EMCC_WASM_BACKEND' not in os.environ, 'do not force wasm backend either way in sanity checks!'
@classmethod
def tearDownClass(cls):
super(sanity, cls).tearDownClass()
restore()
def setUp(self):
super(sanity, self).setUp()
wipe()
self.start_time = time.time()
def tearDown(self):
super(sanity, self).tearDown()
print('time:', time.time() - self.start_time)
def do(self, command):
print('Running: ' + ' '.join(command))
if type(command) is not list:
command = [command]
if command[0] == EMCC:
command = [PYTHON] + command
return run_process(command, stdout=PIPE, stderr=STDOUT, check=False).stdout
def check_working(self, command, expected=None):
if type(command) is not list:
command = [command]
if expected is None:
if command[0] == EMCC or (len(command) >= 2 and command[1] == EMCC):
expected = 'no input files'
else:
expected = "could not find the following tests: blahblah"
output = self.do(command)
self.assertContained(expected, output)
return output
# this should be the very first thing that runs. if this fails, everything else is irrelevant!
def test_aaa_normal(self):
for command in commands:
# Your existing EM_CONFIG should work!
restore_and_set_up()
self.check_working(command)
def test_firstrun(self):
for command in commands:
wipe()
def make_executable(name):
with open(os.path.join(temp_bin, name), 'w') as f:
os.fchmod(f.fileno(), stat.S_IRWXU)
try:
temp_bin = tempfile.mkdtemp()
old_environ_path = os.environ['PATH']
os.environ['PATH'] = temp_bin + os.pathsep + old_environ_path
make_executable('llvm-dis')
make_executable('node')
make_executable('python2')
output = self.do(command)
finally:
os.environ['PATH'] = old_environ_path
shutil.rmtree(temp_bin)
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('It contains our best guesses for the important paths, which are:', output)
self.assertContained('LLVM_ROOT', output)
self.assertContained('NODE_JS', output)
if platform.system() is not 'Windows':
# os.chmod can't make files executable on Windows
self.assertIdentical(temp_bin, re.search("^ *LLVM_ROOT *= (.*)$", output, re.M).group(1))
possible_nodes = [os.path.join(temp_bin, 'node')]
if os.path.exists('/usr/bin/nodejs'):
possible_nodes.append('/usr/bin/nodejs')
self.assertIdentical(possible_nodes, re.search("^ *NODE_JS *= (.*)$", output, re.M).group(1))
self.assertContained('Please edit the file if any of those are incorrect', output)
self.assertContained('This command will now exit. When you are done editing those paths, re-run it.', output)
assert output.split()[-1].endswith('===='), 'We should have stopped: ' + output
config_file = open(CONFIG_FILE).read()
template_file = open(path_from_root('tools', 'settings_template_readonly.py')).read()
self.assertNotContained('~/.emscripten', config_file)
self.assertContained('~/.emscripten', template_file)
self.assertNotContained('{{{', config_file)
self.assertNotContained('}}}', config_file)
self.assertContained('{{{', template_file)
self.assertContained('}}}', template_file)
for content in ['EMSCRIPTEN_ROOT', 'LLVM_ROOT', 'NODE_JS', 'TEMP_DIR', 'COMPILER_ENGINE', 'JS_ENGINES']:
self.assertContained(content, config_file)
# The guessed config should be ok
# XXX This depends on your local system! it is possible `which` guesses wrong
# try_delete('a.out.js')
# output = run_process([PYTHON, EMCC, path_from_root('tests', 'hello_world.c')], stdout=PIPE, stderr=PIPE).output
# self.assertContained('hello, world!', run_js('a.out.js'), output)
# Second run, with bad EM_CONFIG
for settings in ['blah', 'LLVM_ROOT="blarg"; 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)
elif 'runner.py' not in ' '.join(command):
self.assertContained('ERROR', output) # sanity check should fail
def test_closure_compiler(self):
CLOSURE_FATAL = 'fatal: closure compiler'
CLOSURE_WARNING = 'does not exist'
# Sanity check should find closure
restore_and_set_up()
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, '-s', '--closure', '1'] + MINIMAL_HELLO_WORLD + ['-O2'], CLOSURE_FATAL)
# With a working path, all is well
restore_and_set_up()
try_delete('a.out.js')
output = self.check_working([EMCC, '-s', '--closure', '1'] + MINIMAL_HELLO_WORLD + ['-O2'], '')
assert os.path.exists('a.out.js'), output
def test_llvm(self):
LLVM_WARNING = 'LLVM version appears incorrect'
restore_and_set_up()
# Clang should report the version number we expect, and emcc should not warn
assert shared.check_llvm_version()
output = self.check_working(EMCC)
assert LLVM_WARNING not in output, output
# Fake a different llvm version
restore_and_set_up()
with open(CONFIG_FILE, 'a') as f:
f.write('LLVM_ROOT = "' + path_from_root('tests', 'fake') + '"')
with env_modify({'EM_IGNORE_SANITY': '1'}):
for x in range(-2, 3):
for y in range(-2, 3):
expected_x, expected_y = (int(x) for x in expected_llvm_version().split('.'))
expected_x += x
expected_y += y
if expected_x < 0 or expected_y < 0:
continue # must be a valid llvm version
print(expected_llvm_version(), x, y, expected_x, expected_y)
make_fake_clang(path_from_root('tests', 'fake', 'clang'), '%s.%s' % (expected_x, expected_y))
if x != 0 or y != 0:
output = self.check_working(EMCC, LLVM_WARNING)
else:
output = self.check_working(EMCC)
assert LLVM_WARNING not in output, output
def test_emscripten_root(self):
# The correct path
restore_and_set_up()
add_to_config("EMSCRIPTEN_ROOT = '%s'" % path_from_root())
self.check_working(EMCC)
# The correct path with extra stuff
restore_and_set_up()
add_to_config("EMSCRIPTEN_ROOT = '%s'" % (path_from_root() + os.path.sep))
self.check_working(EMCC)
def test_llvm_fastcomp(self):
WARNING = 'fastcomp in use, but LLVM has not been built with the JavaScript backend as a target'
restore_and_set_up()
# Should see js backend during sanity check
self.assertTrue(shared.check_llvm())
output = self.check_working(EMCC)
self.assertNotIn(WARNING, output)
# Fake incorrect llc output, no mention of js backend
restore_and_set_up()
with open(CONFIG_FILE, 'a') as f:
f.write('LLVM_ROOT = "' + path_from_root('tests', 'fake', 'bin') + '"')
# print '1', open(CONFIG_FILE).read()
make_fake_clang(path_from_root('tests', 'fake', 'bin', 'clang'), expected_llvm_version())
make_fake_llc(path_from_root('tests', 'fake', 'bin', 'llc'), 'no j-s backend for you!')
self.check_working(EMCC, WARNING)
# fake some more
for fake in ['llvm-link', 'llvm-ar', 'opt', 'llvm-as', 'llvm-dis', 'llvm-nm', 'lli']:
open(path_from_root('tests', 'fake', 'bin', fake), 'w').write('.')
try_delete(SANITY_FILE)
self.check_working(EMCC, WARNING)
# make sure sanity checks notice there is no source dir with version #
make_fake_llc(path_from_root('tests', 'fake', 'bin', 'llc'), 'there IZ a js backend: JavaScript (asm.js, emscripten) backend')
try_delete(SANITY_FILE)
self.check_working(EMCC, 'clang version does not appear to include fastcomp')
VERSION_WARNING = 'Emscripten, llvm and clang build versions do not match, this is dangerous'
# add version number
make_fake_clang(path_from_root('tests', 'fake', 'bin', 'clang'), '%s (emscripten waka : waka)' % expected_llvm_version())
try_delete(SANITY_FILE)
self.check_working(EMCC, VERSION_WARNING)
restore_and_set_up()
self.check_working([EMCC] + MINIMAL_HELLO_WORLD + ['-s', 'ASM_JS=0'], '''ASM_JS must be enabled in fastcomp''')
def test_node(self):
NODE_WARNING = 'node version appears too old'
NODE_WARNING_2 = 'cannot check node version'
restore_and_set_up()
# Clang should report the version number we expect, and emcc should not warn
assert shared.check_node_version()
output = self.check_working(EMCC)
self.assertNotIn(NODE_WARNING, output)
# Fake a different node version
restore_and_set_up()
f = open(CONFIG_FILE, 'a')
f.write('NODE_JS = "' + path_from_root('tests', 'fake', 'nodejs') + '"')
f.close()
with env_modify({'EM_IGNORE_SANITY': '1'}):
for version, succeed in [('v0.8.0', False),
('v4.1.0', False),
('v4.1.1', True),
('v4.2.3-pre', True),
('cheez', False)]:
print(version, succeed)
f = open(path_from_root('tests', 'fake', 'nodejs'), 'w')
f.write('#!/bin/sh\n')
f.write('''if [ $1 = "--version" ]; then
echo "%s"
else
%s $@
fi
''' % (version, NODE_JS))
f.close()
os.chmod(path_from_root('tests', 'fake', 'nodejs'), stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
if not succeed:
if version[0] == 'v':
self.check_working(EMCC, NODE_WARNING)
else:
self.check_working(EMCC, NODE_WARNING_2)
else:
output = self.check_working(EMCC)
assert NODE_WARNING not in output, output
def test_emcc(self):
SANITY_FAIL_MESSAGE = 'sanity check failed to run'
# emcc should check sanity if no ${EM_CONFIG}_sanity
restore_and_set_up()
time.sleep(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)
assert generate_sanity() == open(SANITY_FILE).read()
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)
# correct sanity contents mean we need not check
open(SANITY_FILE, 'w').write(generate_sanity())
output = self.check_working(EMCC)
self.assertNotContained(SANITY_MESSAGE, output)
# incorrect sanity contents mean we *must* check
open(SANITY_FILE, 'w').write('wakawaka')
output = self.check_working(EMCC)
self.assertContained(SANITY_MESSAGE, output)
# but with EMCC_DEBUG=1 we should check
with env_modify({'EMCC_DEBUG': '1'}):
output = self.check_working(EMCC)
try_delete(CANONICAL_TEMP_DIR)
self.assertContained(SANITY_MESSAGE, output)
output = self.check_working(EMCC)
self.assertNotContained(SANITY_MESSAGE, output)
# also with -v, with or without inputs
output = self.check_working([EMCC, '-v'], SANITY_MESSAGE)
output = self.check_working([EMCC, '-v'] + MINIMAL_HELLO_WORLD + [], SANITY_MESSAGE)
# 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_and_set_up()
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)
# emcc should be configurable directly from EM_CONFIG without any config file
restore_and_set_up()
config = open(CONFIG_FILE, 'r').read()
open('main.cpp', 'w').write('''
#include <stdio.h>
int main() {
printf("hello from emcc with no config file\\n");
return 0;
}
''')
wipe()
with env_modify({'EM_CONFIG': config}):
run_process([PYTHON, EMCC, 'main.cpp', '-o', 'a.out.js'])
self.assertContained('hello from emcc with no config file', run_js('a.out.js'))
def erase_cache(self):
Cache.erase()
assert not os.path.exists(Cache.dirname)
def ensure_cache(self):
self.do([PYTHON, EMCC, '-O2', path_from_root('tests', 'hello_world.c')])
def test_emcc_caching(self):
INCLUDING_MESSAGE = 'including X'
BUILDING_MESSAGE = 'building X for cache'
ERASING_MESSAGE = 'clearing cache'
restore_and_set_up()
self.erase_cache()
with env_modify({'EMCC_DEBUG': '1'}):
# Building a file that *does* need something *should* trigger cache
# generation, but only the first time
for filename, libname in [('hello_libcxx.cpp', 'libc++')]:
for i in range(3):
print(filename, libname, i)
self.clear()
output = self.do([EMCC, '-O' + str(i), '-s', '--llvm-lto', '0', path_from_root('tests', filename), '--save-bc', 'a.bc', '-s', 'DISABLE_EXCEPTION_CATCHING=0'])
# print '\n\n\n', output
assert INCLUDING_MESSAGE.replace('X', libname) in output
if libname == 'libc':
assert INCLUDING_MESSAGE.replace('X', 'libc++') not in output # we don't need libc++ in this code
else:
assert INCLUDING_MESSAGE.replace('X', 'libc') in output # libc++ always forces inclusion of libc
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(Cache.dirname)
full_libname = libname + '.bc' if libname != 'libc++' else libname + '.a'
assert os.path.exists(os.path.join(Cache.dirname, full_libname))
try_delete(CANONICAL_TEMP_DIR)
restore_and_set_up()
# Manual cache clearing
self.ensure_cache()
self.assertTrue(os.path.exists(Cache.dirname))
self.assertTrue(os.path.exists(Cache.root_dirname))
output = self.do([PYTHON, EMCC, '--clear-cache'])
self.assertIn(ERASING_MESSAGE, output)
self.assertFalse(os.path.exists(Cache.dirname))
self.assertFalse(os.path.exists(Cache.root_dirname))
self.assertIn(SANITY_MESSAGE, output)
# Changing LLVM_ROOT, even without altering .emscripten, clears the cache
self.ensure_cache()
make_fake_clang(path_from_root('tests', 'fake', 'bin', 'clang'), expected_llvm_version())
with env_modify({'LLVM': path_from_root('tests', 'fake', 'bin')}):
self.assertTrue(os.path.exists(Cache.dirname))
output = self.do([PYTHON, EMCC])
self.assertIn(ERASING_MESSAGE, output)
self.assertFalse(os.path.exists(Cache.dirname))
# FROZEN_CACHE prevents cache clears, and prevents building
def test_FROZEN_CACHE(self):
restore_and_set_up()
self.erase_cache()
self.ensure_cache()
self.assertTrue(os.path.exists(Cache.dirname))
self.assertTrue(os.path.exists(Cache.root_dirname))
# changing config file should not clear cache
add_to_config('FROZEN_CACHE = True')
self.do([PYTHON, EMCC])
self.assertTrue(os.path.exists(Cache.dirname))
self.assertTrue(os.path.exists(Cache.root_dirname))
# building libraries is disallowed
output = self.do([PYTHON, EMBUILDER, 'build', 'emmalloc'])
self.assertIn('FROZEN_CACHE disallows building system libs', output)
# Test that if multiple processes attempt to access or build stuff to the
# cache on demand, that exactly one of the processes will, and the other
# processes will block to wait until that process finishes.
def test_emcc_multiprocess_cache_access(self):
restore_and_set_up()
create_test_file('test.c', r'''
#include <stdio.h>
int main() {
printf("hello, world!\n");
return 0;
}
''')
cache_dir_name = self.in_dir('emscripten_cache')
tasks = []
num_times_libc_was_built = 0
for i in range(3):
p = run_process([PYTHON, EMCC, 'test.c', '--cache', cache_dir_name, '-o', '%d.js' % i], stderr=STDOUT, stdout=PIPE)
tasks += [p]
for p in tasks:
print('stdout:\n', p.stdout)
if 'generating system library: libc' in p.stdout:
num_times_libc_was_built += 1
# The cache directory must exist after the build
self.assertTrue(os.path.exists(cache_dir_name))
# The cache directory must contain a built libc
if self.is_wasm_backend():
self.assertTrue(os.path.exists(os.path.join(cache_dir_name, 'wasm_o', 'libc.a')))
else:
self.assertTrue(os.path.exists(os.path.join(cache_dir_name, 'asmjs', 'libc.bc')))
# Exactly one child process should have triggered libc build!
self.assertEqual(num_times_libc_was_built, 1)
def test_emcc_cache_flag(self):
restore_and_set_up()
cache_dir_name = self.in_dir('emscripten_cache')
self.assertFalse(os.path.exists(cache_dir_name))
create_test_file('test.c', r'''
#include <stdio.h>
int main() {
printf("hello, world!\n");
return 0;
}
''')
run_process([PYTHON, EMCC, 'test.c', '--cache', cache_dir_name], stderr=PIPE)
# The cache directory must exist after the build
self.assertTrue(os.path.exists(cache_dir_name))
# The cache directory must contain a built libc'
if self.is_wasm_backend():
self.assertTrue(os.path.exists(os.path.join(cache_dir_name, 'wasm_o', 'libc.a')))
else:
self.assertTrue(os.path.exists(os.path.join(cache_dir_name, 'asmjs', 'libc.bc')))
def test_emconfig(self):
restore_and_set_up()
fd, custom_config_filename = tempfile.mkstemp(prefix='.emscripten_config_')
orig_config = open(CONFIG_FILE, 'r').read()
# Move the ~/.emscripten to a custom location.
with os.fdopen(fd, "w") as f:
f.write(orig_config)
# Make a syntax error in the original config file so that attempting to access it would fail.
open(CONFIG_FILE, 'w').write('asdfasdfasdfasdf\n\'\'\'' + orig_config)
temp_dir = tempfile.mkdtemp(prefix='emscripten_temp_')
with chdir(temp_dir):
self.do([PYTHON, EMCC, '--em-config', custom_config_filename] + MINIMAL_HELLO_WORLD + ['-O2'])
result = run_js('a.out.js')
self.assertContained('hello, world!', result)
# Clean up created temp files.
os.remove(custom_config_filename)
if Settings.WASM_BACKEND:
os.remove(custom_config_filename + "_sanity_wasm")
else:
os.remove(custom_config_filename + "_sanity")
shutil.rmtree(temp_dir)
def test_emcc_ports(self):
restore_and_set_up()
# listing ports
out = self.do([PYTHON, EMCC, '--show-ports'])
assert 'Available ports:' in out, out
assert 'SDL2' in out, out
assert 'SDL2_image' in out, out
assert 'SDL2_net' in out, out
# using ports
RETRIEVING_MESSAGE = 'retrieving port'
BUILDING_MESSAGE = 'generating port'
from tools import system_libs
PORTS_DIR = system_libs.Ports.get_dir()
for compiler in [EMCC]:
print(compiler)
for i in [0, 1]:
self.do([PYTHON, EMCC, '--clear-cache'])
print(i)
if i == 0:
try_delete(PORTS_DIR)
else:
self.do([PYTHON, compiler, '--clear-ports'])
assert not os.path.exists(PORTS_DIR)
# Building a file that doesn't need ports should not trigger anything
# (avoid wasm to avoid the binaryen port)
output = self.do([compiler, path_from_root('tests', 'hello_world_sdl.cpp'), '-s', 'WASM=0'])
print('no', output)
assert RETRIEVING_MESSAGE not in output, output
assert BUILDING_MESSAGE not in output
assert not os.path.exists(PORTS_DIR)
# Building a file that need a port does trigger stuff
output = self.do([compiler, path_from_root('tests', 'hello_world_sdl.cpp'), '-s', 'WASM=0', '-s', 'USE_SDL=2'])
print('yes', output)
assert RETRIEVING_MESSAGE in output, output
assert BUILDING_MESSAGE in output, output
assert os.path.exists(PORTS_DIR)
def second_use():
# Using it again avoids retrieve and build
output = self.do([compiler, path_from_root('tests', 'hello_world_sdl.cpp'), '-s', 'WASM=0', '-s', 'USE_SDL=2'])
assert RETRIEVING_MESSAGE not in output, output
assert BUILDING_MESSAGE not in output, output
second_use()
# if the version isn't sufficient, we retrieve and rebuild
subdir = os.listdir(os.path.join(PORTS_DIR, 'sdl2'))[0]
os.rename(os.path.join(PORTS_DIR, 'sdl2', subdir), os.path.join(PORTS_DIR, 'sdl2', 'old-subdir'))
import zipfile
z = zipfile.ZipFile(os.path.join(PORTS_DIR, 'sdl2' + '.zip'), 'w')
if not os.path.exists('old-sub'):
os.mkdir('old-sub')
open(os.path.join('old-sub', 'a.txt'), 'w').write('waka')
open(os.path.join('old-sub', 'b.txt'), 'w').write('waka')
z.write(os.path.join('old-sub', 'a.txt'))
z.write(os.path.join('old-sub', 'b.txt'))
z.close()
output = self.do([compiler, path_from_root('tests', 'hello_world_sdl.cpp'), '-s', 'WASM=0', '-s', 'USE_SDL=2'])
assert RETRIEVING_MESSAGE in output, output
assert BUILDING_MESSAGE in output, output
assert os.path.exists(PORTS_DIR)
second_use()
def test_d8_path(self):
""" Test that running JS commands works for node, d8, and jsc and is not path dependent """
# Fake some JS engines
restore_and_set_up()
sample_script = path_from_root('tests', 'print_args.js')
# Note that the path contains 'd8'.
test_path = path_from_root('tests', 'fake', 'abcd8765')
if not os.path.exists(test_path):
os.makedirs(test_path)
with env_modify({'EM_IGNORE_SANITY': '1'}):
jsengines = [('d8', V8_ENGINE),
('d8_g', V8_ENGINE),
('js', SPIDERMONKEY_ENGINE),
('node', NODE_JS),
('nodejs', NODE_JS)]
for filename, engine in jsengines:
if type(engine) is list:
engine = engine[0]
if engine == '':
print('WARNING: Not testing engine %s, not configured.' % (filename))
continue
print(filename, engine)
test_engine_path = os.path.join(test_path, filename)
f = open(test_engine_path, 'w')
f.write('#!/bin/sh\n')
f.write('%s $@\n' % (engine))
f.close()
os.chmod(test_engine_path, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
try:
out = jsrun.run_js(sample_script, engine=test_engine_path, args=['--foo'], full_output=True, assert_returncode=0, skip_check=True)
except Exception as e:
if 'd8' in filename:
assert False, 'Your d8 version does not correctly parse command-line arguments, please upgrade or delete from ~/.emscripten config file: %s' % (e)
else:
assert False, 'Error running script command: %s' % (e)
self.assertEqual('0: --foo', out.strip())
def test_wacky_env(self):
restore_and_set_up()
def build():
return self.check_working([EMCC] + MINIMAL_HELLO_WORLD, '')
def test():
self.assertContained('hello, world!', run_js('a.out.js'))
print('normal build')
with env_modify({'EMCC_FORCE_STDLIBS': None}):
Cache.erase()
build()
test()
print('wacky env vars, these should not mess our bootstrapping')
with env_modify({'EMCC_FORCE_STDLIBS': '1'}):
Cache.erase()
build()
test()
def test_vanilla(self):
restore_and_set_up()
Cache.erase()
with env_modify({'EMCC_DEBUG': '1'}):
# see that we test vanilla status, and just once
TESTING = 'testing for asm.js target'
self.check_working(EMCC, TESTING)
for i in range(3):
output = self.check_working(EMCC, 'check tells us to use')
assert TESTING not in output
# if env var tells us, do what it says
with env_modify({'EMCC_WASM_BACKEND': '1'}):
self.check_working(EMCC, 'EMCC_WASM_BACKEND tells us to use wasm backend')
with env_modify({'EMCC_WASM_BACKEND': '0'}):
self.check_working(EMCC, 'EMCC_WASM_BACKEND tells us to use asm.js backend')
def make_fake(report):
with open(CONFIG_FILE, 'a') as f:
f.write('LLVM_ROOT = "' + path_from_root('tests', 'fake', 'bin') + '"\n')
# BINARYEN_ROOT needs to exist in the config, even though this test
# doesn't actually use it.
f.write('BINARYEN_ROOT= "%s"\n' % path_from_root('tests', 'fake', 'bin'))
make_fake_llc(path_from_root('tests', 'fake', 'bin', 'llc'), report)
with open(path_from_root('tests', 'fake', 'bin', 'wasm-ld'), 'w') as f:
f.write('#!/bin/sh\n')
f.write('exit 0\n')
os.chmod(path_from_root('tests', 'fake', 'bin', 'wasm-ld'), stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
with env_modify({'EMCC_DEBUG': '1'}):
make_fake('wasm32-unknown-unknown-elf')
# see that we request the right backend from llvm
with env_modify({'EMCC_WASM_BACKEND': '1'}):
self.check_working([EMCC] + MINIMAL_HELLO_WORLD + ['-c'], 'wasm32-unknown-unknown-elf')
make_fake('asmjs-unknown-emscripten')
with env_modify({'EMCC_WASM_BACKEND': '0'}):
self.check_working([EMCC] + MINIMAL_HELLO_WORLD + ['-c'], 'asmjs-unknown-emscripten')
# check the current installed one is ok
restore_and_set_up()
self.check_working(EMCC)
output = self.check_working(EMCC, 'check tells us to use')
if 'wasm backend' in output:
self.check_working([EMCC] + MINIMAL_HELLO_WORLD + ['-c'], 'wasm32-unknown-unknown-elf')
else:
assert 'asm.js backend' in output
self.check_working([EMCC] + MINIMAL_HELLO_WORLD + ['-c'], 'asmjs-unknown-emscripten')
# fake llc output
def test_with_fake(report, expected):
make_fake(report)
with env_modify({'EMCC_DEBUG': '1'}):
output = self.check_working([EMCC] + MINIMAL_HELLO_WORLD + ['-c'], expected)
self.assertContained('config file changed since we checked vanilla', output)
test_with_fake('got js backend! JavaScript (asm.js, emscripten) backend', 'check tells us to use asm.js backend')
test_with_fake('got wasm32 backend! WebAssembly 32-bit', 'check tells us to use wasm backend')
# use LLVM env var to modify LLVM between vanilla checks
assert not os.environ.get('LLVM'), 'we need to modify LLVM env var for this'
f = open(CONFIG_FILE, 'a')
f.write('LLVM_ROOT = os.getenv("LLVM", "' + path_from_root('tests', 'fake1', 'bin') + '")\n')
f.close()
safe_ensure_dirs(path_from_root('tests', 'fake1', 'bin'))
f = open(path_from_root('tests', 'fake1', 'bin', 'llc'), 'w')
f.write('#!/bin/sh\n')
f.write('echo "llc fake1 output\nRegistered Targets:\n%s"' % 'got js backend! JavaScript (asm.js, emscripten) backend')
f.close()
os.chmod(path_from_root('tests', 'fake1', 'bin', 'llc'), stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
safe_ensure_dirs(path_from_root('tests', 'fake2', 'bin'))
f = open(path_from_root('tests', 'fake2', 'bin', 'llc'), 'w')
f.write('#!/bin/sh\n')
f.write('echo "llc fake2 output\nRegistered Targets:\n%s"' % 'got wasm32 backend! WebAssembly 32-bit')
f.close()
os.chmod(path_from_root('tests', 'fake2', 'bin', 'llc'), stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
with env_modify({'EMCC_DEBUG': '1'}):
self.check_working([EMCC] + MINIMAL_HELLO_WORLD + ['-c'], 'use asm.js backend')
with env_modify({'LLVM': path_from_root('tests', 'fake2', 'bin')}):
self.check_working([EMCC] + MINIMAL_HELLO_WORLD + ['-c'], 'regenerating vanilla check since other llvm')
try_delete(CANONICAL_TEMP_DIR)
return # TODO: the rest of this
# check separate cache dirs are used
restore_and_set_up()
self.check_working([EMCC], '')
root_cache = os.path.expanduser('~/.emscripten_cache')
if os.path.exists(os.path.join(root_cache, 'asmjs')):
shutil.rmtree(os.path.join(root_cache, 'asmjs'))
if os.path.exists(os.path.join(root_cache, 'wasm')):
shutil.rmtree(os.path.join(root_cache, 'wasm'))
with env_modify({'EMCC_WASM_BACKEND': '1'}):
self.check_working([EMCC] + MINIMAL_HELLO_WORLD, '')
assert os.path.exists(os.path.join(root_cache, 'wasm'))
with env_modify({'EMCC_WASM_BACKEND': '0'}):
self.check_working([EMCC] + MINIMAL_HELLO_WORLD, '')
assert os.path.exists(os.path.join(root_cache, 'asmjs'))
shutil.rmtree(os.path.join(root_cache, 'asmjs'))
self.check_working([EMCC] + MINIMAL_HELLO_WORLD, '')
assert os.path.exists(os.path.join(root_cache, 'asmjs'))
def test_wasm_backend_builds(self):
# we can build a program using the wasm backend, rebuilding binaryen etc. as needed
restore_and_set_up()
def check():
print(self.do([PYTHON, EMCC, '--clear-cache']))
print(self.do([PYTHON, EMCC, '--clear-ports']))
with env_modify({'EMCC_WASM_BACKEND': '1'}):
self.check_working([EMCC, path_from_root('tests', 'hello_world.c')], '')
print('normally')
check()
print('with no BINARYEN_ROOT')
open(CONFIG_FILE, 'a').write('''
BINARYEN_ROOT = ''
''')
print(open(CONFIG_FILE).read())
check()
def test_binaryen(self):
import tools.ports.binaryen as binaryen
tag_file = Cache.get_path('binaryen_tag_' + binaryen.TAG + '.txt')
assert not os.environ.get('BINARYEN') # must not have binaryen env var set
# test with BINARYEN_ROOT in the config file, which is how developers usually
# have things set up. testing without it in the config file (which makes
# it get fetched from ports) is how the bots work, so it is tested there
def prep():
restore_and_set_up()
print('clearing ports...')
print(self.do([PYTHON, EMCC, '--clear-ports']))
wipe()
self.do([PYTHON, EMCC]) # first run stage
try_delete(tag_file)
config = open(CONFIG_FILE).read()
assert '''BINARYEN_ROOT = os.path.expanduser(os.getenv('BINARYEN', ''))''' in config, config # setup created it to be ''
print('created config:')
print(config)
restore_and_set_up()
config = open(CONFIG_FILE).read()
config = config.replace('BINARYEN_ROOT', '''BINARYEN_ROOT = os.path.expanduser(os.getenv('BINARYEN', '')) # ''')
print('modified config:')
print(config)
open(CONFIG_FILE, 'w').write(config)
print('build using embuilder')
prep()
run_process([PYTHON, EMBUILDER, 'build', 'binaryen'])
assert os.path.exists(tag_file)
run_process([PYTHON, EMCC] + MINIMAL_HELLO_WORLD + ['-s', 'BINARYEN=1'])
self.assertContained('hello, world!', run_js('a.out.js'))
print('see we show an error for emmake (we cannot build natively under emmake)')
prep()
try_delete('a.out.js')
out = self.do([PYTHON, path_from_root('emmake.py'), EMCC] + MINIMAL_HELLO_WORLD + ['-s', 'BINARYEN=1'])
assert not os.path.exists(tag_file)
assert not os.path.exists('a.out.js')
self.assertContained('For example, for binaryen, do "python embuilder.py build binaryen"', out)
def test_embuilder_wasm_backend(self):
if not Settings.WASM_BACKEND:
self.skipTest('wasm backend only')
restore_and_set_up()
root_cache = os.path.expanduser('~/.emscripten_cache')
# the --lto flag makes us build wasm_bc
self.do([PYTHON, EMCC, '--clear-cache'])
run_process([PYTHON, EMBUILDER, 'build', 'emmalloc'])
assert os.path.exists(os.path.join(root_cache, 'wasm_o'))
self.do([PYTHON, EMCC, '--clear-cache'])
run_process([PYTHON, EMBUILDER, 'build', 'emmalloc', '--lto'])
assert os.path.exists(os.path.join(root_cache, 'wasm_bc'))