/
test_other.py
9659 lines (8549 loc) · 385 KB
/
test_other.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# coding=utf-8
# 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.
# noqa: E241
from __future__ import print_function
from functools import wraps
import filecmp
import glob
import itertools
import json
import os
import pipes
import re
import select
import shlex
import shutil
import struct
import subprocess
import sys
import time
import tempfile
import unittest
import uuid
if __name__ == '__main__':
raise Exception('do not run this file directly; do something like: tests/runner.py other')
from tools.shared import Building, PIPE, run_js, run_process, STDOUT, try_delete, listify
from tools.shared import EMCC, EMXX, EMAR, EMRANLIB, PYTHON, FILE_PACKAGER, WINDOWS, MACOS, LLVM_ROOT, EMCONFIG, EM_BUILD_VERBOSE
from tools.shared import CLANG, CLANG_CC, CLANG_CPP, LLVM_AR
from tools.shared import COMPILER_ENGINE, NODE_JS, SPIDERMONKEY_ENGINE, JS_ENGINES, V8_ENGINE
from tools.shared import WebAssembly
from runner import RunnerCore, path_from_root, no_wasm_backend, no_fastcomp, is_slow_test
from runner import needs_dlfcn, env_modify, no_windows, chdir, with_env_modify, create_test_file, parameterized
from tools import jsrun, shared
import tools.line_endings
import tools.js_optimizer
import tools.tempfiles
import tools.duplicate_function_eliminator
scons_path = Building.which('scons')
class temp_directory(object):
def __init__(self, dirname):
self.dir = dirname
def __enter__(self):
self.directory = tempfile.mkdtemp(prefix='emtest_temp_', dir=self.dir)
self.prev_cwd = os.getcwd()
os.chdir(self.directory)
print('temp_directory: ' + self.directory)
return self.directory
def __exit__(self, type, value, traceback):
os.chdir(self.prev_cwd)
def uses_canonical_tmp(func):
"""Decorator that signals the use of the canonical temp by a test method.
This decorator takes care of cleaning the directory after the
test to satisfy the leak detector.
"""
@wraps(func)
def decorated(self):
# Before running the test completely remove the canonical_tmp
if os.path.exists(self.canonical_temp_dir):
shutil.rmtree(self.canonical_temp_dir)
try:
func(self)
finally:
# Make sure the test isn't lying about the fact that it uses
# canonical_tmp
self.assertTrue(os.path.exists(self.canonical_temp_dir))
# Remove the temp dir in a try-finally, as otherwise if the
# test fails we would not clean it up, and if leak detection
# is set we will show that error instead of the actual one.
shutil.rmtree(self.canonical_temp_dir)
return decorated
def is_python3_version_supported():
"""Retuns True if the installed python3 version is supported by emscripten.
Note: Emscripten requires python3.5 or above since python3.4 and below do not
support circular dependencies."""
python3 = Building.which('python3')
if not python3:
return False
output = run_process([python3, '--version'], stdout=PIPE).stdout
output = output.split(' ')[1]
# ignore final component which can contains non-integers (e.g 'rc1')
version = [int(x) for x in output.split('.')[:2]]
return version >= [3, 5]
def encode_leb(number):
# TODO(sbc): handle larger numbers
assert(number < 255)
# pack the integer then take only the first (little end) byte
return struct.pack('<i', number)[:1]
def get_fastcomp_src_dir():
"""Locate fastcomp source tree by searching realtive to LLVM_ROOT."""
d = LLVM_ROOT
key_file = 'readme-emscripten-fastcomp.txt'
while d != os.path.dirname(d):
d = os.path.abspath(d)
# when the build directory lives below the source directory
if os.path.exists(os.path.join(d, key_file)):
return d
# when the build directory lives alongside the source directory
elif os.path.exists(os.path.join(d, 'src', key_file)):
return os.path.join(d, 'src')
else:
d = os.path.dirname(d)
return None
class other(RunnerCore):
# Utility to run a simple test in this suite. This receives a directory which
# should contain a test.cpp and test.out files, compiles the cpp, and runs it
# to verify the output, with optional compile and run arguments.
# TODO: use in more places
def do_other_test(self, dirname, emcc_args=[], run_args=[]):
shutil.copyfile(path_from_root('tests', dirname, 'test.cpp'), 'test.cpp')
run_process([PYTHON, EMCC, 'test.cpp'] + emcc_args)
expected = open(path_from_root('tests', dirname, 'test.out')).read()
seen = run_js('a.out.js', args=run_args, stderr=PIPE, full_output=True) + '\n'
self.assertContained(expected, seen)
# Another utility to run a test in this suite. This receives a source file
# to compile, with optional compiler and execution flags.
# Output can be checked by seeing if literals are contained, and that a list
# of regexes match. The return code can also be checked.
def do_smart_test(self, source, literals=[], regexes=[],
emcc_args=[], run_args=[], assert_returncode=0):
run_process([PYTHON, EMCC, source] + emcc_args)
seen = run_js('a.out.js', args=run_args, stderr=PIPE, full_output=True,
assert_returncode=assert_returncode) + '\n'
for literal in literals:
self.assertContained([literal], seen)
for regex in regexes:
self.assertTrue(re.search(regex, seen), 'Expected regex "%s" to match on:\n%s' % (regex, seen))
def run_on_pty(self, cmd):
master, slave = os.openpty()
output = []
try:
env = os.environ.copy()
env['TERM'] = 'xterm-color'
proc = subprocess.Popen(cmd, stdout=slave, stderr=slave, env=env)
while proc.poll() is None:
r, w, x = select.select([master], [], [], 1)
if r:
output.append(os.read(master, 1024))
return (proc.returncode, b''.join(output))
finally:
os.close(master)
os.close(slave)
def expect_fail(self, cmd, **args):
"""Run a subprocess and assert that it returns non-zero.
Return the stderr of the subprocess.
"""
proc = run_process(cmd, check=False, stderr=PIPE, **args)
self.assertNotEqual(proc.returncode, 0)
return proc.stderr
def test_emcc_v(self):
for compiler in [EMCC, EMXX]:
# -v, without input files
proc = run_process([PYTHON, compiler, '-v'], stdout=PIPE, stderr=PIPE)
self.assertContained('clang version %s' % shared.expected_llvm_version(), proc.stderr)
self.assertContained('GNU', proc.stderr)
self.assertNotContained('this is dangerous', proc.stdout)
self.assertNotContained('this is dangerous', proc.stderr)
def test_emcc_generate_config(self):
for compiler in [EMCC, EMXX]:
config_path = './emscripten_config'
run_process([PYTHON, compiler, '--generate-config', config_path])
self.assertExists(config_path, 'A config file should have been created at %s' % config_path)
config_contents = open(config_path).read()
self.assertContained('EMSCRIPTEN_ROOT', config_contents)
self.assertContained('LLVM_ROOT', config_contents)
os.remove(config_path)
def test_emcc_output_mjs(self):
run_process([PYTHON, EMCC, '-o', 'hello_world.mjs', path_from_root('tests', 'hello_world.c')])
with open('hello_world.mjs') as f:
output = f.read()
self.assertContained('export default Module;', output)
# TODO(sbc): Test that this is actually runnable. We currently don't have
# any tests for EXPORT_ES6 but once we do this should be enabled.
# self.assertContained('hello, world!', run_js('hello_world.mjs'))
def test_emcc_out_file(self):
# Verify that "-ofile" works in addition to "-o" "file"
run_process([PYTHON, EMCC, '-c', '-ofoo.o', path_from_root('tests', 'hello_world.c')])
assert os.path.exists('foo.o')
run_process([PYTHON, EMCC, '-ofoo.js', 'foo.o'])
assert os.path.exists('foo.js')
def test_emcc_basics(self):
for compiler, suffix in [(EMCC, '.c'), (EMXX, '.cpp')]:
# --version
output = run_process([PYTHON, compiler, '--version'], stdout=PIPE, stderr=PIPE)
output = output.stdout.replace('\r', '')
self.assertContained('emcc (Emscripten gcc/clang-like replacement)', output)
self.assertContained('''Copyright (C) 2014 the Emscripten authors (see AUTHORS.txt)
This is free and open source software under the MIT license.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
''', output)
# --help
output = run_process([PYTHON, compiler, '--help'], stdout=PIPE, stderr=PIPE)
self.assertContained('Display this information', output.stdout)
self.assertContained('Most clang options will work', output.stdout)
# -dumpmachine
output = run_process([PYTHON, compiler, '-dumpmachine'], stdout=PIPE, stderr=PIPE)
self.assertContained(shared.get_llvm_target(), output.stdout)
# -dumpversion
output = run_process([PYTHON, compiler, '-dumpversion'], stdout=PIPE, stderr=PIPE)
self.assertEqual(shared.EMSCRIPTEN_VERSION, output.stdout.strip(), 'results should be identical')
# emcc src.cpp ==> writes a.out.js and a.out.wasm
self.clear()
run_process([PYTHON, compiler, path_from_root('tests', 'hello_world' + suffix)])
self.assertExists('a.out.js')
self.assertExists('a.out.wasm')
self.assertContained('hello, world!', run_js('a.out.js'))
# properly report source code errors, and stop there
self.clear()
stderr = self.expect_fail([PYTHON, compiler, path_from_root('tests', 'hello_world_error' + suffix)])
self.assertNotContained('IOError', stderr) # no python stack
self.assertNotContained('Traceback', stderr) # no python stack
self.assertContained('error: invalid preprocessing directive', stderr)
self.assertContained(["error: use of undeclared identifier 'cheez", "error: unknown type name 'cheez'"], stderr)
self.assertContained('errors generated.', stderr.splitlines()[-2])
def test_emcc_2(self):
for compiler in [EMCC, EMXX]:
suffix = '.c' if compiler == EMCC else '.cpp'
# emcc src.cpp -c and emcc src.cpp -o src.[o|bc] ==> should give a .bc file
# regression check: -o js should create "js", with bitcode content
for args in [['-c'], ['-o', 'src.o'], ['-o', 'src.bc'], ['-o', 'src.so'], ['-o', 'js'], ['-O1', '-c', '-o', '/dev/null'], ['-O1', '-o', '/dev/null']]:
print('-c stuff', args)
if '/dev/null' in args and WINDOWS:
print('skip because windows')
continue
target = args[1] if len(args) == 2 else 'hello_world.o'
self.clear()
run_process([PYTHON, compiler, path_from_root('tests', 'hello_world' + suffix)] + args)
if args[-1] == '/dev/null':
print('(no output)')
continue
syms = Building.llvm_nm(target)
assert 'main' in syms.defs
if self.is_wasm_backend():
# wasm backend will also have '__original_main' or such
assert len(syms.defs) == 2
else:
assert len(syms.defs) == 1
if target == 'js': # make sure emcc can recognize the target as a bitcode file
shutil.move(target, target + '.bc')
target += '.bc'
run_process([PYTHON, compiler, target, '-o', target + '.js'])
self.assertContained('hello, world!', run_js(target + '.js'))
def test_emcc_3(self):
for compiler in [EMCC, EMXX]:
suffix = '.c' if compiler == EMCC else '.cpp'
os.chdir(self.get_dir())
self.clear()
# handle singleton archives
run_process([PYTHON, compiler, path_from_root('tests', 'hello_world' + suffix), '-o', 'a.bc'])
run_process([LLVM_AR, 'r', 'a.a', 'a.bc'], stdout=PIPE, stderr=PIPE)
run_process([PYTHON, compiler, 'a.a'])
self.assertContained('hello, world!', run_js('a.out.js'))
if not self.is_wasm_backend():
# emcc src.ll ==> generates .js
self.clear()
run_process([PYTHON, compiler, path_from_root('tests', 'hello_world.ll')])
self.assertContained('hello, world!', run_js('a.out.js'))
# emcc [..] -o [path] ==> should work with absolute paths
for path in [os.path.abspath(os.path.join('..', 'file1.js')), os.path.join('b_dir', 'file2.js')]:
print(path)
os.chdir(self.get_dir())
self.clear()
print(os.listdir(os.getcwd()))
os.makedirs('a_dir')
os.chdir('a_dir')
os.makedirs('b_dir')
# use single file so we don't have more files to clean up
run_process([PYTHON, compiler, path_from_root('tests', 'hello_world' + suffix), '-o', path, '-s', 'SINGLE_FILE=1'])
last = os.getcwd()
os.chdir(os.path.dirname(path))
self.assertContained('hello, world!', run_js(os.path.basename(path)))
os.chdir(last)
try_delete(path)
def test_emcc_4(self):
for compiler in [EMCC, EMXX]:
# Optimization: emcc src.cpp -o something.js [-Ox]. -O0 is the same as not specifying any optimization setting
for params, opt_level, bc_params, closure, has_malloc in [ # bc params are used after compiling to bitcode
(['-o', 'something.js'], 0, None, 0, 1),
(['-o', 'something.js', '-O0'], 0, None, 0, 0),
(['-o', 'something.js', '-O1'], 1, None, 0, 0),
(['-o', 'something.js', '-O1', '-g'], 1, None, 0, 0), # no closure since debug
(['-o', 'something.js', '-O2'], 2, None, 0, 1),
(['-o', 'something.js', '-O2', '-g'], 2, None, 0, 0),
(['-o', 'something.js', '-Os'], 2, None, 0, 1),
(['-o', 'something.js', '-O3'], 3, None, 0, 1),
# and, test compiling to bitcode first
(['-o', 'something.bc'], 0, [], 0, 0),
(['-o', 'something.bc', '-O0'], 0, [], 0, 0),
(['-o', 'something.bc', '-O1'], 1, ['-O1'], 0, 0),
(['-o', 'something.bc', '-O2'], 2, ['-O2'], 0, 0),
(['-o', 'something.bc', '-O3'], 3, ['-O3'], 0, 0),
(['-O1', '-o', 'something.bc'], 1, [], 0, 0),
# non-wasm
(['-s', 'WASM=0', '-o', 'something.js'], 0, None, 0, 1),
(['-s', 'WASM=0', '-o', 'something.js', '-O0'], 0, None, 0, 0),
(['-s', 'WASM=0', '-o', 'something.js', '-O1'], 1, None, 0, 0),
(['-s', 'WASM=0', '-o', 'something.js', '-O1', '-g'], 1, None, 0, 0), # no closure since debug
(['-s', 'WASM=0', '-o', 'something.js', '-O2'], 2, None, 0, 1),
(['-s', 'WASM=0', '-o', 'something.js', '-O2', '-g'], 2, None, 0, 0),
(['-s', 'WASM=0', '-o', 'something.js', '-Os'], 2, None, 0, 1),
(['-s', 'WASM=0', '-o', 'something.js', '-O3'], 3, None, 0, 1),
# and, test compiling to bitcode first
(['-s', 'WASM=0', '-o', 'something.bc'], 0, ['-s', 'WASM=0'], 0, 0),
(['-s', 'WASM=0', '-o', 'something.bc', '-O0'], 0, ['-s', 'WASM=0'], 0, 0),
(['-s', 'WASM=0', '-o', 'something.bc', '-O1'], 1, ['-s', 'WASM=0', '-O1'], 0, 0),
(['-s', 'WASM=0', '-o', 'something.bc', '-O2'], 2, ['-s', 'WASM=0', '-O2'], 0, 0),
(['-s', 'WASM=0', '-o', 'something.bc', '-O3'], 3, ['-s', 'WASM=0', '-O3'], 0, 0),
(['-s', 'WASM=0', '-O1', '-o', 'something.bc'], 1, ['-s', 'WASM=0'], 0, 0),
]:
if 'WASM=0' in params and self.is_wasm_backend():
continue
print(params, opt_level, bc_params, closure, has_malloc)
self.clear()
keep_debug = '-g' in params
args = [PYTHON, compiler, path_from_root('tests', 'hello_world_loop' + ('_malloc' if has_malloc else '') + '.cpp')] + params
print('..', args)
output = run_process(args, stdout=PIPE, stderr=PIPE)
assert len(output.stdout) == 0, output.stdout
if bc_params is not None:
self.assertExists('something.bc', output.stderr)
bc_args = [PYTHON, compiler, 'something.bc', '-o', 'something.js'] + bc_params
print('....', bc_args)
output = run_process(bc_args, stdout=PIPE, stderr=PIPE)
self.assertExists('something.js', output.stderr)
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()
main = self.get_func(generated, '_main') if 'function _main' in generated else generated
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 main, 'when relooping we also js-optimize, so there should be no labelled whiles'
if closure:
if opt_level == 0:
assert '._main =' in generated, 'closure compiler should have been run'
elif opt_level >= 1:
assert '._main=' in generated, 'closure compiler should have been run (and output should be minified)'
else:
# closure has not been run, we can do some additional checks. TODO: figure out how to do these even with closure
assert '._main = ' not in generated, 'closure compiler should not have been run'
if keep_debug:
assert ('switch (label)' in generated or 'switch (label | 0)' in generated) == (opt_level <= 0), 'relooping should be in opt >= 1'
assert ('assert(STACKTOP < STACK_MAX' in generated) == (opt_level == 0), 'assertions should be in opt == 0'
if 'WASM=0' in params:
if opt_level >= 2 and '-g' in params:
assert re.search('HEAP8\[\$?\w+ ?\+ ?\(+\$?\w+ ?', generated) or re.search('HEAP8\[HEAP32\[', generated) or re.search('[i$]\d+ & ~\(1 << [i$]\d+\)', generated), 'eliminator should create compound expressions, and fewer one-time vars' # also in -O1, but easier to test in -O2
looks_unminified = ' = {}' in generated and ' = []' in generated
looks_minified = '={}' in generated and '=[]' and ';var' in generated
assert not (looks_minified and looks_unminified)
if opt_level == 0 or '-g' in params:
assert looks_unminified
elif opt_level >= 2:
assert looks_minified
@no_wasm_backend('tests for asmjs optimzer')
def test_emcc_5(self):
for compiler in [EMCC, EMXX]:
# asm.js optimization levels
for params, test, text in [
(['-O2'], lambda generated: 'function addRunDependency' in generated, 'shell has unminified utilities'),
(['-O2', '--closure', '1'], lambda generated: 'function addRunDependency' not in generated and ';function' in generated, 'closure minifies the shell, removes whitespace'),
(['-O2', '--closure', '1', '-g1'], lambda generated: 'function addRunDependency' not in generated and ';function' not in generated, 'closure minifies the shell, -g1 makes it keep whitespace'),
(['-O2'], lambda generated: 'var b=0' in generated and 'function _main' not in generated, 'registerize/minify is run by default in -O2'),
(['-O2', '--minify', '0'], lambda generated: 'var b = 0' in generated and 'function _main' not in generated, 'minify is cancelled, but not registerize'),
(['-O2', '--js-opts', '0'], lambda generated: 'var b=0' not in generated and 'var b = 0' not in generated and 'function _main' in generated, 'js opts are cancelled'),
(['-O2', '-g'], lambda generated: 'var b=0' not in generated and 'var b = 0' not in generated and 'function _main' in generated, 'registerize/minify is cancelled by -g'),
(['-O2', '-g0'], lambda generated: 'var b=0' in generated and 'function _main' not in generated, 'registerize/minify is run by default in -O2 -g0'),
(['-O2', '-g1'], lambda generated: 'var b = 0' in generated and 'function _main' not in generated, 'compress is cancelled by -g1'),
(['-O2', '-g2'], lambda generated: ('var b = 0' in generated or 'var i1 = 0' in generated) and 'function _main' in generated, 'minify is cancelled by -g2'),
(['-O2', '-g3'], lambda generated: 'var b=0' not in generated and 'var b = 0' not in generated and 'function _main' in generated, 'registerize is cancelled by -g3'),
(['-O2', '--profiling'], lambda generated: ('var b = 0' in generated or 'var i1 = 0' in generated) and 'function _main' in generated, 'similar to -g2'),
(['-O2', '-profiling'], lambda generated: ('var b = 0' in generated or 'var i1 = 0' in generated) and 'function _main' in generated, 'similar to -g2'),
(['-O2', '--profiling-funcs'], lambda generated: 'var b=0' in generated and '"use asm";var a=' in generated and 'function _main' in generated, 'very minified, but retain function names'),
(['-O2', '-profiling-funcs'], lambda generated: 'var b=0' in generated and '"use asm";var a=' in generated and 'function _main' in generated, 'very minified, but retain function names'),
(['-O2'], lambda generated: 'var b=0' in generated and '"use asm";var a=' in generated and 'function _main' not in generated, 'very minified, no function names'),
# (['-O2', '-g4'], lambda generated: 'var b=0' not in generated and 'var b = 0' not in generated and 'function _main' in generated, 'same as -g3 for now'),
(['-s', 'INLINING_LIMIT=0'], lambda generated: 'function _dump' in generated, 'no inlining without opts'),
([], lambda generated: 'Module["_dump"]' not in generated, 'dump is not exported by default'),
(['-s', 'EXPORTED_FUNCTIONS=["_main", "_dump"]'], lambda generated: 'asm["_dump"];' in generated, 'dump is now exported'),
(['--llvm-opts', '1'], lambda generated: '_puts(' in generated, 'llvm opts requested'),
([], lambda generated: '// Sometimes an existing Module' in generated, 'without opts, comments in shell code'),
(['-O2'], lambda generated: '// Sometimes an existing Module' not in generated, 'with opts, no comments in shell code'),
(['-O2', '-g2'], lambda generated: '// Sometimes an existing Module' not in generated, 'with -g2, no comments in shell code'),
(['-O2', '-g3'], lambda generated: '// Sometimes an existing Module' in generated, 'with -g3, yes comments in shell code'),
]:
print(params, text)
self.clear()
run_process([PYTHON, compiler, path_from_root('tests', 'hello_world_loop.cpp'), '-o', 'a.out.js', '-s', 'WASM=0'] + params)
self.assertContained('hello, world!', run_js('a.out.js'))
assert test(open('a.out.js').read()), text
def test_emcc_6(self):
for compiler in [EMCC, EMXX]:
# Compiling two source files into a final JS.
for args, target in [([], 'a.out.js'), (['-o', 'combined.js'], 'combined.js')]:
self.clear()
run_process([PYTHON, compiler, path_from_root('tests', 'twopart_main.cpp'), path_from_root('tests', 'twopart_side.cpp')] + args)
self.assertContained('side got: hello from main, over', run_js(target))
# Compiling two files with -c will generate separate .bc files
self.clear()
expect_error = '-o' in args # specifying -o and -c is an error
cmd = [PYTHON, compiler, path_from_root('tests', 'twopart_main.cpp'), path_from_root('tests', 'twopart_side.cpp'), '-c'] + args
if expect_error:
err = self.expect_fail(cmd)
self.assertContained('fatal error', err)
continue
run_process(cmd)
try_delete(target)
# Compiling one of them alone is expected to fail
run_process([PYTHON, compiler, 'twopart_main.o', '-O1', '-g', '-s', 'WARN_ON_UNDEFINED_SYMBOLS=0'] + args)
self.assertContained('missing function', run_js(target, stderr=STDOUT, assert_returncode=None))
try_delete(target)
# Combining those object files into js should work
run_process([PYTHON, compiler, 'twopart_main.o', 'twopart_side.o'] + args)
self.assertContained('side got: hello from main, over', run_js(target))
# Combining object files into another object should also work
try_delete(target)
run_process([PYTHON, compiler, 'twopart_main.o', 'twopart_side.o', '-o', 'combined.o'] + args)
syms = Building.llvm_nm('combined.o')
assert len(syms.defs) in (2, 3) and 'main' in syms.defs, 'Should be two functions (and in the wasm backend, also __original_main)'
run_process([PYTHON, compiler, 'combined.o', '-o', 'combined.o.js'])
self.assertExists('combined.o.js')
self.assertContained('side got: hello from main, over', run_js('combined.o.js'))
def test_emcc_7(self):
for compiler in [EMCC, EMXX]:
suffix = '.c' if compiler == EMCC else '.cpp'
# --js-transform <transform>
self.clear()
trans = 't.py'
with open(trans, 'w') as f:
f.write('''
import sys
f = open(sys.argv[1], 'a')
f.write('transformed!')
f.close()
''')
run_process([PYTHON, compiler, path_from_root('tests', 'hello_world' + suffix), '--js-transform', '%s t.py' % (PYTHON)])
self.assertIn('transformed!', open('a.out.js').read())
if self.is_wasm_backend():
# The remainder of this test requires asmjs support
return
for opts in [0, 1, 2, 3]:
print('mem init in', opts)
self.clear()
run_process([PYTHON, compiler, path_from_root('tests', 'hello_world.c'), '-s', 'WASM=0', '-O' + str(opts)])
if opts >= 2:
self.assertTrue(os.path.exists('a.out.js.mem'))
else:
self.assertFalse(os.path.exists('a.out.js.mem'))
def test_emcc_asm_v_wasm(self):
for opts in ([], ['-O1'], ['-O2'], ['-O3']):
print('opts', opts)
for mode in ([], ['-s', 'WASM=0'], ['-s', 'WASM=1']):
self.clear()
wasm = '=0' not in str(mode)
print(' mode', mode, 'wasm?', wasm)
run_process([PYTHON, EMCC, path_from_root('tests', 'hello_world.c')] + opts + mode)
self.assertExists('a.out.js')
if wasm:
self.assertExists('a.out.wasm')
for engine in JS_ENGINES:
print(' engine', engine)
out = run_js('a.out.js', engine=engine, stderr=PIPE, full_output=True)
self.assertContained('hello, world!', out)
if not wasm and engine == SPIDERMONKEY_ENGINE:
self.validate_asmjs(out)
if not wasm and not self.is_wasm_backend():
src = open('a.out.js').read()
if opts == []:
assert 'almost asm' in src
else:
assert 'use asm' in src
def test_emcc_cflags(self):
output = run_process([PYTHON, EMCC, '--cflags'], stdout=PIPE)
flags = output.stdout.strip()
self.assertContained(' '.join(Building.doublequote_spaces(shared.COMPILER_OPTS)), flags)
# check they work
cmd = [CLANG, path_from_root('tests', 'hello_world.cpp')] + shlex.split(flags.replace('\\', '\\\\')) + ['-c', '-emit-llvm', '-o', 'a.bc']
run_process(cmd)
run_process([PYTHON, EMCC, 'a.bc'])
self.assertContained('hello, world!', run_js('a.out.js'))
def test_emcc_print_search_dirs(self):
result = run_process([PYTHON, EMCC, '-print-search-dirs'], stdout=PIPE, stderr=PIPE)
self.assertContained(['programs: =', 'libraries: ='], result.stdout, check_all=True)
def test_emar_em_config_flag(self):
# Test that the --em-config flag is accepted but not passed down do llvm-ar.
# We expand this in case the EM_CONFIG is ~/.emscripten (default)
config = os.path.expanduser(shared.EM_CONFIG)
proc = run_process([PYTHON, EMAR, '--em-config', config, '-version'], stdout=PIPE, stderr=PIPE)
self.assertEqual(proc.stderr, "")
self.assertContained('LLVM', proc.stdout)
def test_cmake(self):
# Test all supported generators.
if WINDOWS:
generators = ['MinGW Makefiles', 'NMake Makefiles']
else:
generators = ['Unix Makefiles', 'Ninja', 'Eclipse CDT4 - Ninja']
def nmake_detect_error(configuration):
if Building.which(configuration['build'][0]):
return None
else:
return 'Skipping NMake test for CMake support, since nmake was not found in PATH. Run this test in Visual Studio command prompt to easily access nmake.'
def check_makefile(dirname):
self.assertExists(dirname + '/Makefile', 'CMake call did not produce a Makefile!')
configurations = {'MinGW Makefiles' : {'prebuild': check_makefile, # noqa
'build' : ['mingw32-make'], # noqa
},
'NMake Makefiles' : {'detect' : nmake_detect_error, # noqa
'prebuild': check_makefile, # noqa
'build' : ['nmake', '/NOLOGO'], # noqa
},
'Unix Makefiles' : {'prebuild': check_makefile, # noqa
'build' : ['make'], # noqa
},
'Ninja' : {'build' : ['ninja'], # noqa
},
'Eclipse CDT4 - Ninja': {'build' : ['ninja'], # noqa
}
}
if WINDOWS:
emconfigure = path_from_root('emconfigure.bat')
else:
emconfigure = path_from_root('emconfigure')
for generator in generators:
conf = configurations[generator]
make = conf['build']
detector = conf.get('detect')
prebuild = conf.get('prebuild')
if detector:
error = detector(conf)
elif len(make) == 1 and not Building.which(make[0]):
# Use simple test if applicable
error = 'Skipping %s test for CMake support, since it could not be detected.' % generator
else:
error = None
if error:
print(error)
continue
# ('directory to the test', 'output filename', ['extra args to pass to
# CMake']) Testing all combinations would be too much work and the test
# would take 10 minutes+ to finish (CMake feature detection is slow), so
# combine multiple features into one to try to cover as much as possible
# while still keeping this test in sensible time limit.
cases = [
('target_js', 'test_cmake.js', ['-DCMAKE_BUILD_TYPE=Debug']),
('target_html', 'hello_world_gles.html', ['-DCMAKE_BUILD_TYPE=Release']),
('target_library', 'libtest_cmake.a', ['-DCMAKE_BUILD_TYPE=MinSizeRel']),
('target_library', 'libtest_cmake.a', ['-DCMAKE_BUILD_TYPE=RelWithDebInfo', '-DCPP_LIBRARY_TYPE=STATIC']),
('stdproperty', 'helloworld.js', [])
]
for test_dir, output_file, cmake_args in cases:
cmakelistsdir = path_from_root('tests', 'cmake', test_dir)
with temp_directory(self.get_dir()) as tempdirname:
# Run Cmake
cmd = [emconfigure, 'cmake'] + cmake_args + ['-G', generator, cmakelistsdir]
env = os.environ.copy()
# https://github.com/emscripten-core/emscripten/pull/5145: Check that CMake works even if EMCC_SKIP_SANITY_CHECK=1 is passed.
if test_dir == 'target_html':
env['EMCC_SKIP_SANITY_CHECK'] = '1'
print(str(cmd))
ret = run_process(cmd, env=env, stdout=None if EM_BUILD_VERBOSE >= 2 else PIPE, stderr=None if EM_BUILD_VERBOSE >= 1 else PIPE)
if ret.stderr is not None and len(ret.stderr.strip()):
print(ret.stderr) # If there were any errors, print them directly to console for diagnostics.
if ret.stderr is not None and 'error' in ret.stderr.lower():
print('Failed command: ' + ' '.join(cmd))
print('Result:\n' + ret.stderr)
self.fail('cmake call failed!')
if prebuild:
prebuild(tempdirname)
# Build
cmd = make
if EM_BUILD_VERBOSE >= 3 and 'Ninja' not in generator:
cmd += ['VERBOSE=1']
ret = run_process(cmd, stdout=None if EM_BUILD_VERBOSE >= 2 else PIPE)
if ret.stderr is not None and len(ret.stderr.strip()):
print(ret.stderr) # If there were any errors, print them directly to console for diagnostics.
if ret.stdout is not None and 'error' in ret.stdout.lower() and '0 error(s)' not in ret.stdout.lower():
print('Failed command: ' + ' '.join(cmd))
print('Result:\n' + ret.stdout)
self.fail('make failed!')
self.assertExists(tempdirname + '/' + output_file, 'Building a cmake-generated Makefile failed to produce an output file %s!' % tempdirname + '/' + output_file)
# Run through node, if CMake produced a .js file.
if output_file.endswith('.js'):
ret = run_process(NODE_JS + [tempdirname + '/' + output_file], stdout=PIPE).stdout
self.assertTextDataIdentical(open(cmakelistsdir + '/out.txt').read().strip(), ret.strip())
# Test that the various CMAKE_xxx_COMPILE_FEATURES that are advertised for the Emscripten toolchain match with the actual language features that Clang supports.
# If we update LLVM version and this test fails, copy over the new advertised features from Clang and place them to cmake/Modules/Platform/Emscripten.cmake.
@no_windows('Skipped on Windows because CMake does not configure native Clang builds well on Windows.')
def test_cmake_compile_features(self):
with temp_directory(self.get_dir()):
cmd = ['cmake', '-DCMAKE_C_COMPILER=' + CLANG_CC, '-DCMAKE_CXX_COMPILER=' + CLANG_CPP, path_from_root('tests', 'cmake', 'stdproperty')]
print(str(cmd))
native_features = run_process(cmd, stdout=PIPE).stdout
if WINDOWS:
emconfigure = path_from_root('emcmake.bat')
else:
emconfigure = path_from_root('emcmake')
with temp_directory(self.get_dir()):
cmd = [emconfigure, 'cmake', path_from_root('tests', 'cmake', 'stdproperty')]
print(str(cmd))
emscripten_features = run_process(cmd, stdout=PIPE).stdout
native_features = '\n'.join([x for x in native_features.split('\n') if '***' in x])
emscripten_features = '\n'.join([x for x in emscripten_features.split('\n') if '***' in x])
self.assertTextDataIdentical(native_features, emscripten_features)
# Tests that it's possible to pass C++11 or GNU++11 build modes to CMake by building code that needs C++11 (embind)
def test_cmake_with_embind_cpp11_mode(self):
for args in [[], ['-DNO_GNU_EXTENSIONS=1']]:
with temp_directory(self.get_dir()) as tempdirname:
configure = [path_from_root('emcmake.bat' if WINDOWS else 'emcmake'), 'cmake', path_from_root('tests', 'cmake', 'cmake_with_emval')] + args
print(str(configure))
run_process(configure)
build = ['cmake', '--build', '.']
print(str(build))
run_process(build)
ret = run_process(NODE_JS + [os.path.join(tempdirname, 'cpp_with_emscripten_val.js')], stdout=PIPE).stdout.strip()
if '-DNO_GNU_EXTENSIONS=1' in args:
self.assertTextDataIdentical('Hello! __STRICT_ANSI__: 1, __cplusplus: 201103', ret)
else:
self.assertTextDataIdentical('Hello! __STRICT_ANSI__: 0, __cplusplus: 201103', ret)
# Tests that the Emscripten CMake toolchain option
# -DEMSCRIPTEN_GENERATE_BITCODE_STATIC_LIBRARIES=ON works.
def test_cmake_bitcode_static_libraries(self):
if WINDOWS:
emcmake = path_from_root('emcmake.bat')
else:
emcmake = path_from_root('emcmake')
# Test that building static libraries by default generates UNIX archives (.a, with the emar tool)
self.clear()
run_process([emcmake, 'cmake', path_from_root('tests', 'cmake', 'static_lib')])
run_process([Building.which('cmake'), '--build', '.'])
assert Building.is_ar('libstatic_lib.a')
run_process([PYTHON, EMAR, 'x', 'libstatic_lib.a'])
found = False # hashing makes the object name random
for x in os.listdir('.'):
if x.endswith('.o'):
found = True
if self.is_wasm_backend():
assert Building.is_wasm(x)
else:
assert Building.is_bitcode(x)
assert found
# Test that passing the -DEMSCRIPTEN_GENERATE_BITCODE_STATIC_LIBRARIES=ON
# directive causes CMake to generate LLVM bitcode files as static libraries
# (.bc)
self.clear()
run_process([emcmake, 'cmake', '-DEMSCRIPTEN_GENERATE_BITCODE_STATIC_LIBRARIES=ON', path_from_root('tests', 'cmake', 'static_lib')])
run_process([Building.which('cmake'), '--build', '.'])
if self.is_wasm_backend():
assert Building.is_wasm('libstatic_lib.bc')
else:
assert Building.is_bitcode('libstatic_lib.bc')
assert not Building.is_ar('libstatic_lib.bc')
# Test that one is able to fake custom suffixes for static libraries.
# (sometimes projects want to emulate stuff, and do weird things like files
# with ".so" suffix which are in fact either ar archives or bitcode files)
self.clear()
run_process([emcmake, 'cmake', '-DSET_FAKE_SUFFIX_IN_PROJECT=1', path_from_root('tests', 'cmake', 'static_lib')])
run_process([Building.which('cmake'), '--build', '.'])
assert Building.is_ar('myprefix_static_lib.somecustomsuffix')
# Tests that the CMake variable EMSCRIPTEN_VERSION is properly provided to user CMake scripts
def test_cmake_emscripten_version(self):
if WINDOWS:
emcmake = path_from_root('emcmake.bat')
else:
emcmake = path_from_root('emcmake')
run_process([emcmake, 'cmake', path_from_root('tests', 'cmake', 'emscripten_version')])
def test_system_include_paths(self):
# Verify that all default include paths are within `emscripten/system`
def verify_includes(stderr):
self.assertContained('<...> search starts here:', stderr)
assert stderr.count('End of search list.') == 1, stderr
start = stderr.index('<...> search starts here:')
end = stderr.index('End of search list.')
includes = stderr[start:end]
includes = [i.strip() for i in includes.splitlines()[1:-1]]
for i in includes:
self.assertContained(path_from_root('system'), i)
err = run_process([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-v'], stderr=PIPE).stderr
verify_includes(err)
err = run_process([PYTHON, EMXX, path_from_root('tests', 'hello_world.cpp'), '-v'], stderr=PIPE).stderr
verify_includes(err)
def test_failure_error_code(self):
for compiler in [EMCC, EMXX]:
# Test that if one file is missing from the build, then emcc shouldn't succeed, and shouldn't produce an output file.
self.expect_fail([PYTHON, compiler, path_from_root('tests', 'hello_world.c'), 'this_file_is_missing.c', '-o', 'out.js'])
self.assertFalse(os.path.exists('out.js'))
def test_use_cxx(self):
create_test_file('empty_file', ' ')
dash_xc = run_process([PYTHON, EMCC, '-v', '-xc', 'empty_file'], stderr=PIPE).stderr
self.assertNotContained('-std=c++03', dash_xc)
dash_xcpp = run_process([PYTHON, EMCC, '-v', '-xc++', 'empty_file'], stderr=PIPE).stderr
self.assertContained('-std=c++03', dash_xcpp)
def test_cxx03(self):
for compiler in [EMCC, EMXX]:
run_process([PYTHON, compiler, path_from_root('tests', 'hello_cxx03.cpp')])
def test_cxx11(self):
for std in ['-std=c++11', '--std=c++11']:
for compiler in [EMCC, EMXX]:
run_process([PYTHON, compiler, std, path_from_root('tests', 'hello_cxx11.cpp')])
# Regression test for issue #4522: Incorrect CC vs CXX detection
def test_incorrect_c_detection(self):
create_test_file('test.c', 'foo\n')
for compiler in [EMCC, EMXX]:
run_process([PYTHON, compiler, '--bind', '--embed-file', 'test.c', path_from_root('tests', 'hello_world.cpp')])
def test_odd_suffixes(self):
for suffix in ['CPP', 'c++', 'C++', 'cxx', 'CXX', 'cc', 'CC', 'i', 'ii']:
if self.is_wasm_backend() and suffix == 'ii':
# wasm backend treats .i and .ii specially and considers them already
# pre-processed. Because if this is strips all the -D command line
# flags, including the __EMSCRIPTEN__ define, which makes this fail
# to compile since libcxx/__config depends in __EMSCRIPTEN__.
continue
self.clear()
print(suffix)
shutil.copyfile(path_from_root('tests', 'hello_world.c'), 'test.' + suffix)
run_process([PYTHON, EMCC, self.in_dir('test.' + suffix)])
self.assertContained('hello, world!', run_js('a.out.js'))
for suffix in ['lo']:
self.clear()
print(suffix)
run_process([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-o', 'binary.' + suffix])
run_process([PYTHON, EMCC, 'binary.' + suffix])
self.assertContained('hello, world!', run_js('a.out.js'))
@no_wasm_backend('asm.js minification')
def test_asm_minify(self):
def test(args):
run_process([PYTHON, EMCC, path_from_root('tests', 'hello_world_loop_malloc.cpp'), '-s', 'WASM=0'] + args)
self.assertContained('hello, world!', run_js('a.out.js'))
return open('a.out.js').read()
src = test([])
assert 'function _malloc' in src
src = test(['-O2', '-s', 'ASM_JS=1'])
normal_size = len(src)
print('normal', normal_size)
assert 'function _malloc' not in src
src = test(['-O2', '-s', 'ASM_JS=1', '--minify', '0'])
unminified_size = len(src)
print('unminified', unminified_size)
assert unminified_size > normal_size
assert 'function _malloc' not in src
src = test(['-O2', '-s', 'ASM_JS=1', '-g'])
debug_size = len(src)
print('debug', debug_size)
assert debug_size > unminified_size
assert 'function _malloc' in src
@no_wasm_backend('tests fastcomp extra assertions for function pointer errors - do we need these?')
def test_dangerous_func_cast(self):
src = r'''
#include <stdio.h>
typedef void (*voidfunc)();
int my_func() {
printf("my func\n");
return 10;
}
int main(int argc, char **argv) {
voidfunc fps[10];
for (int i = 0; i < 10; i++)
fps[i] = (i == argc) ? (void (*)())my_func : NULL;
fps[2 * (argc-1) + 1]();
return 0;
}
'''
create_test_file('src.c', src)
def test(args, expected):
print(args, expected)
run_process([PYTHON, EMCC, 'src.c'] + args, stderr=PIPE)
self.assertContained(expected, run_js('a.out.js', stderr=PIPE, full_output=True, assert_returncode=None))
if self.is_wasm_backend():
return
print('in asm.js')
run_process([PYTHON, EMCC, 'src.c', '-s', 'WASM=0'] + args, stderr=PIPE)
self.assertContained(expected, run_js('a.out.js', stderr=PIPE, full_output=True, assert_returncode=None))
# TODO: emulation function support in wasm is imperfect
print('with emulated function pointers in asm.js')
run_process([PYTHON, EMCC, '-s', 'WASM=0', 'src.c', '-s', 'ASSERTIONS=1'] + args + ['-s', 'EMULATED_FUNCTION_POINTERS=1'], stderr=PIPE)
out = run_js('a.out.js', stderr=PIPE, full_output=True, assert_returncode=None)
self.assertContained(expected, out)
# fastcomp. all asm, so it can't just work with wrong sigs. but,
# ASSERTIONS=2 gives much better info to debug
# Case 1: No useful info, but does mention ASSERTIONS
test(['-O1'], 'ASSERTIONS')
# Case 2: Some useful text
test(['-O1', '-s', 'ASSERTIONS=1'], [
'Invalid function pointer',
"called with signature 'v'. Perhaps this is an invalid value",
'Build with ASSERTIONS=2 for more info'
])
# Case 3: actually useful identity of the bad pointer, with comparisons to
# what it would be in other types/tables
test(['-O1', '-s', 'ASSERTIONS=2'], [
'Invalid function pointer',
"called with signature 'v'. Perhaps this is an invalid value",
'This pointer might make sense in another type signature:',
'Invalid function pointer',
"called with signature 'v'. Perhaps this is an invalid value",
"i: asm['_my_func']"
])
# Case 4: emulate so it works
test(['-O1', '-s', 'EMULATE_FUNCTION_POINTER_CASTS=1'], 'my func\n')
@no_wasm_backend('uses EMULATED_FUNCTION_POINTERS')
def test_emulate_function_pointer_casts_assertions_2(self):
# check empty tables work with assertions 2 in this mode (#6554)
run_process([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-s', 'EMULATED_FUNCTION_POINTERS=1', '-s', 'ASSERTIONS=2'])
def test_wl_linkflags(self):
# Test path -L and -l via -Wl, arguments and -Wl, response files
create_test_file('main.cpp', '''
extern void printey();
int main() {
printey();
return 0;
}
''')
create_test_file('libfile.cpp', '''
#include <stdio.h>
void printey() {
printf("hello from lib\\n");
}
''')
create_test_file('linkflags.txt', '''
-L.
-lfoo
''')
run_process([PYTHON, EMCC, '-o', 'libfile.o', 'libfile.cpp'])
run_process([PYTHON, EMAR, 'rv', 'libfoo.a', 'libfile.o'])
run_process([PYTHON, EMCC, 'main.cpp', '-L.', '-lfoo'])
run_process([PYTHON, EMCC, 'main.cpp', '-Wl,-L.', '-Wl,-lfoo'])
run_process([PYTHON, EMCC, 'main.cpp', '-Wl,@linkflags.txt'])
def test_l_link(self):
# Linking with -lLIBNAME and -L/DIRNAME should work, also should work with spaces
create_test_file('main.cpp', '''
extern void printey();
int main() {
printey();
return 0;
}
''')
create_test_file('libfile.cpp', '''
#include <stdio.h>
void printey() {
printf("hello from lib\\n");
}
''')
try:
os.makedirs('libdir')
except:
pass
libfile = self.in_dir('libdir', 'libfile.so')
aout = 'a.out.js'
def build(path, args):
run_process([PYTHON, EMCC, path] + args)
# Test linking the library built here by emcc
build('libfile.cpp', ['-c'])
shutil.move('libfile.o', libfile)
build('main.cpp', ['-L' + 'libdir', '-lfile'])
self.assertContained('hello from lib', run_js(aout))
# Also test execution with `-l c` and space-separated library linking syntax
os.remove(aout)
build('libfile.cpp', ['-c', '-l', 'c'])
shutil.move('libfile.o', libfile)
build('main.cpp', ['-L', 'libdir', '-l', 'file'])
self.assertContained('hello from lib', run_js(aout))
assert not os.path.exists('a.out') and not os.path.exists('a.exe'), 'Must not leave unneeded linker stubs'
def test_commons_link(self):
create_test_file('a.h', r'''
#if !defined(A_H)
#define A_H
extern int foo[8];
#endif
''')
create_test_file('a.c', r'''
#include "a.h"
int foo[8];
''')
create_test_file('main.c', r'''
#include <stdio.h>
#include "a.h"
int main() {
printf("|%d|\n", foo[0]);
return 0;
}
''')