# Copyright 2025 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 subprocess import PIPE

from common import RunnerCore, copy_asset, create_file, read_file, test_file
from decorators import also_with_wasm64, also_without_bigint, parameterized

from tools.shared import EMCC
from tools.utils import delete_file


class jslib(RunnerCore):
  def test_jslib_no_override(self):
    create_file('duplicated_func.c', '''
      #include <stdio.h>
      extern int duplicatedFunc();

      int main() {
        int res = duplicatedFunc();
        printf("*%d*\\n", res);
        return 0;
      }
    ''')
    create_file('duplicated_func_1.js', '''
      addToLibrary(
        { duplicatedFunc: () => 1 },
        { noOverride: true }
      );
    ''')
    create_file('duplicated_func_2.js', '''
      addToLibrary(
        { duplicatedFunc: () => 2 },
        { noOverride: true }
      );
    ''')

    self.cflags += ['--js-library', 'duplicated_func_1.js', '--js-library', 'duplicated_func_2.js']
    self.assert_fail([EMCC, 'duplicated_func.c'] + self.get_cflags(),
                     'duplicated_func_2.js: Symbol re-definition in JavaScript library: duplicatedFunc. Do not use noOverride if this is intended')

  def test_jslib_missing_sig(self):
    create_file('some_func.c', '''
      #include <stdio.h>
      extern int someFunc();

      int main() {
        int res = someFunc();
        printf("*%d*\\n", res);
        return 0;
      }
    ''')
    create_file('some_func.js', '''
      addToLibrary(
        { someFunc: () => 1 },
        { checkSig: true }
      );
    ''')

    self.cflags += ['--js-library', 'some_func.js']
    self.assert_fail([EMCC, 'some_func.c'] + self.get_cflags(),
                     'some_func.js: __sig is missing for function: someFunc. Do not use checkSig if this is intended')

  def test_jslib_extra_args(self):
    # Verify that extra arguments in addition to those listed in `__sig` are still present
    # in the generated JS library function.
    # See https://github.com/emscripten-core/emscripten/issues/21056
    create_file('some_func.js', '''
      addToLibrary({
        someFunc: (arg1, arg2) => {
          err('arg1:' + arg1);
          err('arg2:' + arg2);
        },
        someFunc__sig: 'pp',
      });
    ''')
    create_file('test.c', '''
    void someFunc(long p);
    int main() {
      someFunc(42);
    }
    ''')
    self.cflags += ['--js-library', 'some_func.js', '-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=4Gb']
    self.do_runf('test.c', 'arg1:42\narg2:undefined\n')

  def test_jslib_quoted_key(self):
    create_file('lib.js', r'''
addToLibrary({
  __internal_data:{
    '<' : 0,
    'white space' : 1
  },
  foo__deps: ['__internal_data'],
  foo: () => {
    return 0;
  }
});
''')

    self.do_run_in_out_file_test('hello_world.c', cflags=['--js-library', 'lib.js'])

  def test_jslib_proxying(self):
    # Regression test for a bug we had where jsifier would find and use
    # the inner function in a library function consisting of a single
    # line arrow function.
    # See https://github.com/emscripten-core/emscripten/issues/20264
    create_file('lib.js', r'''
addToLibrary({
  $doNotCall: (x) => {},
  foo__deps: ['$doNotCall'],
  foo__proxy: 'sync',
  foo: () => doNotCall(() => {
    out('should never see this');
  }),
});
''')
    create_file('src.c', r'''
    #include <stdio.h>
    void foo();
    int main() {
      printf("main\n");
      foo();
      printf("done\n");
    }
    ''')
    self.do_runf('src.c', 'main\ndone\n', cflags=['-sEXIT_RUNTIME', '-pthread', '-sPROXY_TO_PTHREAD', '--js-library', 'lib.js'])

  def test_jslib_method_syntax(self):
    create_file('lib.js', r'''
addToLibrary({
  foo() {
    out('foo');
  },
});
''')
    create_file('src.c', r'''
    #include <stdio.h>
    void foo();
    int main() {
      foo();
    }
    ''')
    self.do_runf('src.c', 'foo', cflags=['--js-library', 'lib.js'])

  def test_jslib_exported(self):
    create_file('lib.js', r'''
addToLibrary({
 jslibfunc: (x) => 2 * x
});
''')
    create_file('src.c', r'''
#include <emscripten.h>
#include <stdio.h>
int jslibfunc(int x);
int main() {
  printf("c calling: %d\n", jslibfunc(6));
  EM_ASM({
    out('js calling: ' + Module['_jslibfunc'](5) + '.');
  });
}
''')
    self.do_runf('src.c', 'c calling: 12\njs calling: 10.',
                 cflags=['--js-library', 'lib.js', '-sEXPORTED_FUNCTIONS=_main,_jslibfunc'])

  def test_jslib_using_asm_lib(self):
    create_file('lib.js', r'''
addToLibrary({
  jslibfunc__deps: ['asmlibfunc'],
  jslibfunc: (x) => 2 * _asmlibfunc(x),

  asmlibfunc__asm: true,
  asmlibfunc__sig: 'ii',
  asmlibfunc: (x) => {
    x = x | 0;
    return x + 1 | 0;
  }
});
''')
    create_file('src.c', r'''
#include <stdio.h>
int jslibfunc(int x);
int main() {
  printf("c calling: %d\n", jslibfunc(6));
}
''')
    self.do_runf('src.c', 'c calling: 14\n', cflags=['--js-library', 'lib.js'])

  def test_jslib_preprocessor_errors(self):
    create_file('lib.js', '''\
// This is a library file
#endif // line 2
''')
    self.assert_fail([EMCC, test_file('hello_world.c'), '--js-library', 'lib.js'], 'lib.js:2: #endif without matching #if')

    create_file('lib.js', '''\
// This is a library file

#else // line 3
''')
    self.assert_fail([EMCC, test_file('hello_world.c'), '--js-library', 'lib.js'], 'lib.js:3: #else without matching #if')

  def test_jslib_internal_deps(self):
    create_file('lib.js', r'''
addToLibrary({
  jslibfunc__deps: ['$callRuntimeCallbacks'],
  jslibfunc: (x) => {
    callRuntimeCallbacks();
  },
});
''')
    create_file('src.c', r'''
#include <stdio.h>
int jslibfunc();
int main() {
  printf("c calling: %d\n", jslibfunc());
}
''')
    err = self.run_process([EMCC, 'src.c', '--js-library', 'lib.js'], stderr=PIPE).stderr
    self.assertContained("warning: user library symbol 'jslibfunc' depends on internal symbol '$callRuntimeCallbacks'", err)

  def test_jslib_sig_redefinition(self):
    create_file('lib.js', r'''
addToLibrary({
  jslibfunc__sig: 'ii',
  jslibfunc: (x) => {},
});

addToLibrary({
  jslibfunc__sig: 'ii',
  jslibfunc: (x) => {},
});
''')
    create_file('src.c', r'''
#include <stdio.h>
int jslibfunc();
int main() {
  printf("c calling: %d\n", jslibfunc());
}
''')
    err = self.run_process([EMCC, 'src.c', '--js-library', 'lib.js'], stderr=PIPE).stderr
    self.assertContained('lib.js: signature redefinition for: jslibfunc__sig', err)

    # Add another redefinition, this time not matching
    create_file('lib2.js', r'''
addToLibrary({
  jslibfunc__sig: 'pp',
  jslibfunc: (x) => {},
});
''')
    self.assert_fail([EMCC, 'src.c', '--js-library', 'lib.js', '--js-library', 'lib2.js'],
                     'lib2.js: signature redefinition for: jslibfunc__sig. (old=ii vs new=pp)')

  def test_jslib_invalid_deps(self):
    create_file('lib.js', r'''
addToLibrary({
  jslibfunc__deps: 'hello',
  jslibfunc: (x) => {},
});
''')

    self.assert_fail([EMCC, test_file('hello_world.c'), '--js-library', 'lib.js'],
                     'lib.js: JS library directive jslibfunc__deps=hello is of type \'string\', but it should be an array')

    create_file('lib2.js', r'''
addToLibrary({
  jslibfunc__deps: [1,2,3],
  jslibfunc: (x) => {},
});
''')

    self.assert_fail([EMCC, test_file('hello_world.c'), '--js-library', 'lib2.js'],
                     "lib2.js: __deps entries must be of type 'string' or 'function' not 'number': jslibfunc__deps")

  def test_jslib_invalid_decorator(self):
    create_file('lib.js', r'''
addToLibrary({
  jslibfunc__internal: 'hello',
  jslibfunc: (x) => {},
});
''')
    self.assert_fail([EMCC, test_file('hello_world.c'), '--js-library', 'lib.js'],
                     "lib.js: Decorator (jslibfunc__internal) has wrong type. Expected 'boolean' not 'string'")

  @also_with_wasm64
  @also_without_bigint
  def test_jslib_i53abi(self):
    create_file('lib.js', r'''
addToLibrary({
  jslibfunc__i53abi: true,
  jslibfunc__sig: 'j',
  jslibfunc: (x) => { return 42 },
});
''')
    create_file('test.c', r'''
#include <stdio.h>
int64_t jslibfunc();
int main() {
  printf("main: %lld\n", jslibfunc());
}
''')
    self.do_runf('test.c', 'main: 42\n', cflags=['--js-library', 'lib.js'])

  def test_jslib_i53abi_errors(self):
    create_file('lib.js', r'''
addToLibrary({
  jslibfunc__i53abi: true,
  jslibfunc: (x) => { return 42 },
});
''')
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=jslibfunc', '--js-library', 'lib.js'],
                     "error: JS library error: '__i53abi' decorator requires '__sig' decorator: 'jslibfunc'")

    create_file('lib.js', r'''
addToLibrary({
  jslibfunc__i53abi: true,
  jslibfunc__sig: 'ii',
  jslibfunc: (x) => { return 42 },
});
''')
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=jslibfunc', '--js-library', 'lib.js'],
                     "error: JS library error: '__i53abi' only makes sense when '__sig' includes 'j' (int64): 'jslibfunc'")

  def test_jslib_invalid_proxy_mode(self):
    create_file('lib.js', r'''
addToLibrary({
  jslibfunc__proxy: 'foo',
  jslibfunc: (x) => 42,
});
''')
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=jslibfunc', '--js-library', 'lib.js'],
                     "error: JS library error: invalid proxying mode 'jslibfunc__proxy: foo' specified")

  def test_jslib_legacy(self):
    # Test that the legacy `mergeInfo` function work instead of `addToLibrary`
    create_file('lib.js', r'''
mergeInto(LibraryManager.library, {
  jslibfunc: (x) => { return 42 },
});
''')
    create_file('src.c', r'''
#include <stdio.h>
int jslibfunc();
int main() {
  printf("main: %d\n", jslibfunc());
}
''')
    self.do_runf('src.c', 'main: 42\n', cflags=['--js-library', 'lib.js'])

  # Tests that users can pass custom JS options from command line using
  # the -jsDfoo=val syntax:
  # See https://github.com/emscripten-core/emscripten/issues/10580.
  def test_jslib_custom_settings(self):
    test_file_path = test_file('jslib/test_jslib_custom_settings.c')
    js_lib = test_file('jslib/test_jslib_custom_settings.js')

    self.do_runf(test_file_path, '1\n', cflags=['--js-library', js_lib, '-jsDCUSTOM_JS_OPTION=1'])

    # verify that the settings can be specified more than once, and that the last one wins.
    self.do_runf(test_file_path, '2\n', cflags=['--js-library', js_lib, '-jsDCUSTOM_JS_OPTION=1', '-jsDCUSTOM_JS_OPTION=2'])

    self.assert_fail([EMCC, '-jsDWASM=1'], 'cannot change built-in settings values with a -jsD directive')

  def test_jslib_native_deps(self):
    # Verify that memset (which lives in compiled code), can be specified as a JS library
    # dependency.
    create_file('lib.js', r'''
addToLibrary({
  depper__deps: ['memset'],
  depper: (ptr) => {
    _memset(ptr, 'd'.charCodeAt(0), 10);
  },
});
''')
    create_file('test.c', r'''
#include <stdio.h>

void depper(char*);

int main(int argc, char** argv) {
  char buffer[11] = { 0 };
  depper(buffer);
  puts(buffer);
}
''')

    self.do_runf('test.c', 'dddddddddd\n', cflags=['--js-library', 'lib.js'])

  def test_jslib_native_deps_extra(self):
    # Similar to above but the JS symbol is not used by the native code.
    # Instead is it explicitly injected using `extraLibraryFuncs`.
    create_file('lib.js', r'''
addToLibrary({
  jsfunc__deps: ['raise'],
  jsfunc: (ptr) => {
    _raise(1);
  },
});
extraLibraryFuncs.push('jsfunc');
''')
    self.do_runf('hello_world.c', cflags=['--js-library', 'lib.js'])

  def test_jslib_clobber_i(self):
    # Regression check for an issue we have where a library clobbering the global `i` variable could
    # prevent processing of further libraries.
    create_file('lib1.js', 'for (var i = 0; i < 100; i++) {}')
    create_file('lib2.js', '''
      addToLibrary({
        foo: () => {}
      });
      ''')
    self.run_process([EMCC, test_file('hello_world.c'),
                      '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=foo',
                      '--js-library=lib1.js',
                      '--js-library=lib2.js'])

  def test_jslib_bad_config(self):
    create_file('lib.js', '''
      addToLibrary({
        foo__sig: 'ii',
      });
      ''')
    self.assert_fail([EMCC, test_file('hello_world.c'), '--js-library=lib.js'], "lib.js: Missing library element 'foo' for library config 'foo__sig'")

  def test_jslib_ifdef(self):
    create_file('lib.js', '''
      #ifdef ASSERTIONS
      var foo;
      #endif
      ''')
    proc = self.run_process([EMCC, test_file('hello_world.c'), '--js-library=lib.js'], stderr=PIPE)
    self.assertContained('lib.js: use of #ifdef in js library.  Use #if instead.', proc.stderr)

  def test_jslib_mangling(self):
    create_file('lib.js', '''
      addToLibrary({
        $__foo: () => 43,
      });
      ''')
    self.run_process([EMCC, test_file('hello_world.c'), '--js-library=lib.js', '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$__foo'])

  def test_jslib_exported_functions(self):
    create_file('lib.js', '''
      addToLibrary({
        $Foo: () => 43,
      });
      ''')
    create_file('post.js', 'console.log("Foo:", Module.Foo())')
    self.do_runf('hello_world.c', cflags=['--post-js=post.js', '--js-library=lib.js', '-sEXPORTED_FUNCTIONS=Foo,_main'])

  def test_jslib_search_path(self):
    create_file('libfoo.js', '''
      addToLibrary({
        foo: () => 42,
      });
      ''')
    create_file('main.c', r'''
      #include <stdio.h>
      int foo();
      int main() {
        printf("%d\n", foo());
        return 0;
      }''')
    self.do_runf('main.c', '42\n', cflags=['-L.', '-lfoo.js'])

    # If the library path is not included with `-L` we expect the command to fail
    self.assert_fail([EMCC, 'main.c', '-lfoo.js'], 'emcc: error: unable to find library -lfoo.js')

  # Tests using the #warning directive in JS library files
  def test_jslib_warnings(self):
    copy_asset('warning_in_js_libraries.js')
    proc = self.run_process([EMCC, test_file('hello_world.c'), '--js-library', 'warning_in_js_libraries.js'], stdout=PIPE, stderr=PIPE)
    self.assertNotContained('This warning should not be present!', proc.stderr)
    self.assertContained('warning: warning_in_js_libraries.js:5: #warning This is a warning string!', proc.stderr)
    self.assertContained('warning: warning_in_js_libraries.js:7: #warning This is a second warning string!', proc.stderr)
    self.assertContained('emcc: warning: warnings in JS library compilation [-Wjs-compiler]', proc.stderr)

    err = self.expect_fail([EMCC, test_file('hello_world.c'), '--js-library', 'warning_in_js_libraries.js', '-Werror'])
    self.assertNotContained('This warning should not be present!', err)
    self.assertContained('warning: warning_in_js_libraries.js:5: #warning This is a warning string!', err)
    self.assertContained('warning: warning_in_js_libraries.js:7: #warning This is a second warning string!', err)
    self.assertContained('emcc: error: warnings in JS library compilation [-Wjs-compiler] [-Werror]', err)

  # Tests using the #error directive in JS library files
  def test_jslib_errors(self):
    copy_asset('jslib/error_in_js_libraries.js')
    err = self.expect_fail([EMCC, test_file('hello_world.c'), '--js-library', 'error_in_js_libraries.js'])
    self.assertNotContained('This error should not be present!', err)
    self.assertContained('error: error_in_js_libraries.js:5: #error This is an error string!', err)
    self.assertContained('error: error_in_js_libraries.js:7: #error This is a second error string!', err)

  def test_jslib_include(self):
    create_file('inc.js', '''
    let MY_VAR = 10;
    ''')
    create_file('foo.js', '''
    // Include a file from system directory
    #include "IDBStore.js"
    // Include a local file.
    #include "inc.js"
    ''')
    self.run_process([EMCC, test_file('hello_world.c'), '--js-library', 'foo.js'])

    delete_file('inc.js')
    self.assert_fail([EMCC, test_file('hello_world.c'), '--js-library', 'foo.js'], 'foo.js:5: file not found: inc.js')

  @also_with_wasm64
  @also_without_bigint
  @parameterized({
    '': ([],),
    'closure': (['--closure=1'],),
  })
  def test_jslib_aliases(self, args):
    create_file('foo.js', '''
      addToLibrary({
        foo: () => 42,
        foo__sig: 'i',

        foo_alias: 'foo',

        foo_alias_i64: 'foo',
        foo_alias_i64__sig: 'j',
        foo_alias_i64__i53abi: true,

        foo_alias_ptr: 'foo',
        foo_alias_ptr__sig: 'p',

        // Normal JS function that calls a native function
        call_native__deps: ['native_func'],
        call_native: () => _native_func(),

        // JS function that calls nativeAlias
        call_native_alias__deps: ['$nativeAlias'],
        call_native_alias: () => nativeAlias(),

        // This is a JS-only symbol (i.e. no leading underscore) that
        // aliases a native symbol.
        $nativeAlias__deps: ['native_func2'],
        $nativeAlias: 'native_func2',
      });
    ''')
    create_file('main.c', r'''
      #include <stdio.h>
      #include <emscripten.h>
      int foo();
      int foo_alias();
      void* foo_alias_ptr();
      int64_t foo_alias_i64();
      int call_native();
      int call_native_alias();

      int native_func() {
        return 43;
      }

      int native_func2() {
        return 44;
      }

      int main() {
        printf("foo: %d\n", foo());
        printf("foo_alias: %d\n", foo_alias());
        printf("foo_alias_i64: %lld\n", foo_alias_i64());
        printf("foo_alias_ptr: %p\n", foo_alias_ptr());
        printf("call_native: %d\n", call_native());
        printf("call_native_alias: %d\n", call_native_alias());
        return 0;
      }
    ''')
    expected = '''\
foo: 42
foo_alias: 42
foo_alias_i64: 42
foo_alias_ptr: 0x2a
call_native: 43
call_native_alias: 44
'''
    self.do_runf('main.c', expected, cflags=['--js-library', 'foo.js'] + args)

  @parameterized({
    '': ([],),
    'closure': (['--closure=1'],),
  })
  def test_jslib_export_alias(self, args):
    create_file('lib.js', '''
      addToLibrary({
        $foo: 'main',
        $bar: '__indirect_function_table',
        $baz: 'memory',
      });
    ''')
    create_file('extern_pre.js', r'''
      Module = {
        onRuntimeInitialized: () => {
          const assert = require('assert');
          console.log("onRuntimeInitialized");
          console.log('foo:', typeof Module.foo)
          console.log('bar:', typeof Module.bar)
          console.log('baz:', typeof Module.baz)
          assert(Module.foo instanceof Function);
          assert(Module.bar instanceof WebAssembly.Table);
          assert(Module.baz instanceof WebAssembly.Memory);
          console.log('done');
        }
      };
    ''')
    self.do_runf('hello_world.c', 'done\n', cflags=['--js-library=lib.js', '--extern-pre-js=extern_pre.js', '-sEXPORTED_RUNTIME_METHODS=foo,bar,baz'] + args)

  def test_postjs_errors(self):
    create_file('post.js', '#preprocess\n#error This is an error')
    self.assert_fail([EMCC, test_file('hello_world.c'), '--post-js', 'post.js'], 'post.js:2: #error This is an error')

  def test_jslib_has_library(self):
    create_file('libfoo.js', '''
    // libwasi.js should be included.
    #if !LibraryManager.has('libwasi.js')
    #error "oops 1"
    #endif

    // Checking for the legacy name should also work
    #if !LibraryManager.has('library_wasi.js')
    #error "oops 2"
    #endif

    #if LibraryManager.has('libbar.js')
    #error "oops 3"
    #endif
    ''')
    self.do_runf('hello_world.c', cflags=['-L', '-lfoo.js'])

  def test_jslib_new_objects_basic(self):
    create_file('lib.js', '''
      addToLibrary({
        $obj: {
          a: new Map(),
          b: new Set(),
          c: new WeakMap(),
          d: new WeakSet()
        }
      });
      ''')
    self.run_process([EMCC, test_file('hello_world.c'), '--js-library=lib.js', '-sEXPORTED_FUNCTIONS=obj,_main'])
    self.assertContained("a:new Map,", read_file('a.out.js'))
    self.assertContained("b:new Set,", read_file('a.out.js'))
    self.assertContained("c:new WeakMap,", read_file('a.out.js'))
    self.assertContained("d:new WeakSet,", read_file('a.out.js'))

  def test_jslib_new_objects_non_empty(self):
    create_file('lib.js', '''
      addToLibrary({
        $obj: {
          bad: new Map([[1,2],[3,4]])
        }
      });
      ''')
    self.assert_fail([EMCC, test_file('hello_world.c'), '--js-library=lib.js', '-sEXPORTED_FUNCTIONS=obj,_main'], 'cannot stringify Map with data')

  def test_jslib_system_lib_name(self):
    create_file('libcore.js', r'''
addToLibrary({
 jslibfunc: (x) => 2 * x
});
''')
    create_file('src.c', r'''
#include <emscripten.h>
#include <stdio.h>
int jslibfunc(int x);
int main() {
  printf("jslibfunc: %d\n", jslibfunc(6));
  return 0;
}
''')
    self.do_runf('src.c', 'jslibfunc: 12', cflags=['--js-library', 'libcore.js'])

  def test_jslib_preprocess(self):
    # Use stderr rather than stdout here because stdout is redirected to the output JS file itself.
    create_file('lib.js', '''
#if MAIN_MODULE == 1
console.error('JSLIB: MAIN_MODULE=1');
#elif MAIN_MODULE == 2
console.error('JSLIB: MAIN_MODULE=2');
#elif EXIT_RUNTIME
console.error('JSLIB: EXIT_RUNTIME');
#else
console.error('JSLIB: none of the above');
#endif
''')

    err = self.run_process([EMCC, test_file('hello_world.c'), '--js-library', 'lib.js'], stderr=PIPE).stderr
    self.assertContained('JSLIB: none of the above', err)
    self.assertNotContained('JSLIB: MAIN_MODULE', err)
    self.assertNotContained('JSLIB: EXIT_RUNTIME', err)

    err = self.run_process([EMCC, test_file('hello_world.c'), '--js-library', 'lib.js', '-sMAIN_MODULE'], stderr=PIPE).stderr
    self.assertContained('JSLIB: MAIN_MODULE=1', err)
    self.assertNotContained('JSLIB: EXIT_RUNTIME', err)

    err = self.run_process([EMCC, test_file('hello_world.c'), '--js-library', 'lib.js', '-sMAIN_MODULE=2'], stderr=PIPE).stderr
    self.assertContained('JSLIB: MAIN_MODULE=2', err)
    self.assertNotContained('JSLIB: EXIT_RUNTIME', err)

    err = self.run_process([EMCC, test_file('hello_world.c'), '--js-library', 'lib.js', '-sEXIT_RUNTIME'], stderr=PIPE).stderr
    self.assertContained('JSLIB: EXIT_RUNTIME', err)
    self.assertNotContained('JSLIB: MAIN_MODULE', err)

  # Tests that it is possible to hook into/override a symbol defined in a system library.
  def test_jslib_override_system_symbol(self):
    # This test verifies it is possible to override a symbol from WebGL library.

    # When WebGL is implicitly linked in, the implicit linking should happen before any user
    # --js-libraries, so that they can adjust the behavior afterwards.
    self.do_run_in_out_file_test('jslib/test_jslib_override_system_symbol.c', cflags=['--js-library', test_file('jslib/test_jslib_override_system_symbol.js'), '-sMAX_WEBGL_VERSION=2'])

    # When WebGL is explicitly linked to in strict mode, the linking order on command line should enable overriding.
    self.cflags += ['-sAUTO_JS_LIBRARIES=0', '-sMAX_WEBGL_VERSION=2', '-lwebgl.js', '--js-library', test_file('jslib/test_jslib_override_system_symbol.js')]
    self.do_run_in_out_file_test('jslib/test_jslib_override_system_symbol.c')

  def test_jslib_version_check(self):
    create_file('libfoo.js', '''
      #if parseInt(EMSCRIPTEN_VERSION.split('.')[0]) > 3
      #error "library does not support emscripten > 3.0.0"
      #endif
    ''')
    self.assert_fail([EMCC, '--js-library=libfoo.js'], 'error: libfoo.js:3: #error "library does not support emscripten > 3.0.0"')

  def test_jslib_named_class(self):
    create_file('lib.js', r'''
      class ParentClass {}
      addToLibrary({
        $ParentClass: ParentClass,
        $MyClass__deps: ['$ParentClass'],
        $MyClass: class extends ParentClass {
          constructor() { super(); this.x = 42; }
        },
        log_class__deps: ['$MyClass'],
        log_class: () => {
          var i = new MyClass();
          out('MyClass: ' + i.x);
        }
      });
    ''')
    create_file('src.c', r'''
      #include <stdio.h>
      #include <emscripten.h>

      extern void log_class();

      int main() {
        log_class();
        return 0;
      }
    ''')
    self.do_runf('src.c', 'MyClass: 42', cflags=['--js-library', 'lib.js'])

  # Tests that JS library functions containing multiline strings are not disturbed by e.g. inserting indentation into the output.
  @parameterized({
    '': ([],),
    'single_file': (['-sSINGLE_FILE'],),
    'closure': (['--closure', '1'],),
  })
  def test_multiline_string(self, args):
    self.do_run_in_out_file_test('jslib/test_multiline_string.c', cflags=['--js-library', test_file('jslib/test_multiline_string.js')] + args)

  def test_export(self):
    create_file('post.js', 'Module.myFunc();')
    self.do_runf('hello_world.c', 'myFunc included\nmyFunc called\n', cflags=['--js-library', test_file('jslib/test_export.js'), '--extern-post-js=post.js'])
