# 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.


import glob
import hashlib
import importlib
import itertools
import json
import locale
import os
import platform
import random
import re
import select
import shlex
import shutil
import struct
import subprocess
import sys
import tarfile
import time
from datetime import datetime
from functools import wraps
from pathlib import Path
from subprocess import PIPE, STDOUT

if __name__ == '__main__':
  raise Exception('do not run this file directly; do something like: test/runner other')

import clang_native
import common
import jsrun
import line_endings
from common import (
  EMBUILDER,
  EMCMAKE,
  EMCONFIG,
  EMCONFIGURE,
  EMMAKE,
  NON_ZERO,
  PYTHON,
  TEST_ROOT,
  WEBIDL_BINDER,
  RunnerCore,
  check_node_version,
  copy_asset,
  copytree,
  create_file,
  engine_is_node,
  ensure_dir,
  env_modify,
  exe_path_from_root,
  get_nodejs,
  make_executable,
  path_from_root,
  test_file,
)
from decorators import (
  all_engines,
  also_with_asan,
  also_with_minimal_runtime,
  also_with_modularize,
  also_with_nodefs_both,
  also_with_noderawfs,
  also_with_pthreads,
  also_with_standalone_wasm,
  also_with_wasm2js,
  also_with_wasm64,
  also_with_wasmfs,
  also_without_bigint,
  crossplatform,
  disabled,
  flaky,
  is_slow_test,
  no_bun,
  no_deno,
  no_mac,
  no_windows,
  only_windows,
  parameterize,
  parameterized,
  requires_dev_dependency,
  requires_jspi,
  requires_native_clang,
  requires_network,
  requires_node,
  requires_node_25,
  requires_node_26,
  requires_pthreads,
  requires_v8,
  requires_wasm64,
  requires_wasm_eh,
  with_all_eh_sjlj,
  with_all_fs,
  with_all_sjlj,
  with_env_modify,
)

from tools import building, cache, response_file, shared, utils, webassembly
from tools.building import get_building_env
from tools.link import binary_encode
from tools.settings import settings
from tools.shared import (
  CLANG_CC,
  CLANG_CXX,
  EMAR,
  EMCC,
  EMRANLIB,
  EMXX,
  FILE_PACKAGER,
  LLVM_AR,
  LLVM_DWARFDUMP,
  LLVM_DWP,
  LLVM_NM,
  WASM_LD,
  config,
)
from tools.system_libs import DETERMINISTIC_PREFIX
from tools.utils import (
  MACOS,
  WINDOWS,
  delete_file,
  read_binary,
  read_file,
  write_binary,
  write_file,
)

emsize = exe_path_from_root('emsize')
empath_split = exe_path_from_root('empath-split')
emprofile = exe_path_from_root('emprofile')
emstrip = exe_path_from_root('emstrip')
emsymbolizer = exe_path_from_root('emsymbolizer')


def is_bitcode(filename):
  try:
    # look for magic signature
    b = open(filename, 'rb').read(4)
    if b[:2] == b'BC':
      return True
    # on macOS, there is a 20-byte prefix which starts with little endian
    # encoding of 0x0B17C0DE
    elif b == b'\xDE\xC0\x17\x0B':
      b = bytearray(open(filename, 'rb').read(22))
      return b[20:] == b'BC'
  except IndexError:
    # not enough characters in the input
    # note that logging will be done on the caller function
    pass
  return False


def uses_canonical_tmp(func):
  """Signal 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, *args, **kwargs):
    # Before running the test completely remove the canonical_tmp
    if os.path.exists(self.canonical_temp_dir):
      shutil.rmtree(self.canonical_temp_dir)
    try:
      return func(self, *args, **kwargs)
    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 requires_git_checkout(func):
  @wraps(func)
  def decorated(self, *args, **kwargs):
    if not os.path.exists(utils.path_from_root('.git')):
      self.skipTest('test requires git checkout of emscripten')
    return func(self, *args, **kwargs)

  return decorated


def also_with_llvm_libc(func):
  assert callable(func)

  @wraps(func)
  def metafunc(self, llvm_libc, *args, **kwargs):
    if shared.DEBUG:
      print('parameterize:llvm_libc=%d' % llvm_libc)
    if llvm_libc:
      self.cflags += ['-lllvmlibc']
    return func(self, *args, **kwargs)

  parameterize(metafunc, {'': (False,),
                          'llvm_libc': (True,)})
  return metafunc


def with_both_compilers(func):
  assert callable(func)

  parameterize(func, {'': (EMCC,),
                      'emxx': (EMXX,)})
  return func


def wasmfs_all_backends(func):
  assert callable(func)

  @wraps(func)
  def metafunc(self, backend, *args, **kwargs):
    self.setup_wasmfs_test()
    if backend == 'node':
      self.setup_nodefs_test()
    elif backend == 'raw':
      self.setup_noderawfs_test()
    self.cflags.append(f'-D{backend}')
    return func(self, *args, **kwargs)

  parameterize(metafunc, {'': ('memory',),
                          'node': ('node',),
                          'raw': ('raw',)})
  return metafunc


def requires_tool(tool, env_name=None):
  assert not callable(tool)

  def decorate(func):
    assert callable(func)

    @wraps(func)
    def decorated(self, *args, **kwargs):
      if env_name:
        env_var = f'EMTEST_SKIP_{env_name}'
      else:
        env_var = f'EMTEST_SKIP_{tool.upper()}'
      if not shutil.which(tool):
        if env_var in os.environ:
          self.skipTest(f'test requires ccache and {env_var} is set')
        else:
          self.fail(f'{tool} required to run this test.  Use {env_var} to skip')
      return func(self, *args, **kwargs)

    return decorated

  return decorate


def requires_ninja(func):
  assert callable(func)
  return requires_tool('ninja')(func)


def requires_scons(func):
  assert callable(func)
  return requires_tool('scons')(func)


def requires_rust(func):
  assert callable(func)
  return requires_tool('cargo', 'RUST')(func)


def requires_pkg_config(func):
  assert callable(func)

  @wraps(func)
  def decorated(self, *args, **kwargs):
    if not shutil.which('pkg-config'):
      if os.getenv('EMTEST_SKIP_PKG_CONFIG') == '1' or os.getenv('EMTEST_AUTOSKIP') == '1':
        self.skipTest('test requires pkg-config and EMTEST_SKIP_PKG_CONFIG or EMTEST_AUTOSKIP is set')
      else:
        self.fail('pkg-config is required to run this test')
    return func(self, *args, **kwargs)

  return decorated


def llvm_nm(file):
  output = utils.run_process([LLVM_NM, file], stdout=PIPE).stdout

  symbols = {
    'defs': set(),
    'undefs': set(),
    'commons': set(),
  }

  for line in output.splitlines():
    # Skip address, which is always fixed-length 8 chars (plus 2
    # leading chars `: ` and one trailing space)
    status = line[9]
    symbol = line[11:]

    if status == 'U':
      symbols['undefs'].add(symbol)
    elif status == 'C':
      symbols['commons'].add(symbol)
    elif status == status.upper():
      symbols['defs'].add(symbol)

  return symbols


class other(RunnerCore):
  @classmethod
  def setUpClass(cls):
    """SetUpClass included purely so we can verify that is run."""
    super().setUpClass()
    cls.doneSetup = True

  def setUp(self):
    assert self.__class__.doneSetup
    super().setUp()

  def assertIsObjectFile(self, filename):
    self.assertTrue(building.is_wasm(filename))

  def assertIsWasmDylib(self, filename):
    self.assertTrue(building.is_wasm_dylib(filename))

  def do_other_test(self, testname, cflags=None, **kwargs):
    return self.do_runf_out_file(test_file('other', testname), cflags=cflags, **kwargs)

  def run_on_pty(self, cmd):
    master, slave = os.openpty()
    output = []
    print(cmd)

    try:
      with env_modify({'TERM': 'xterm-color'}):
        proc = subprocess.Popen(cmd, stdout=slave, stderr=slave)
        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 create_huge_file(self, name, length):
    f = open(name, "wb")
    f.seek(length - 1)
    f.write(b"\0")
    f.close()

  def run_tsc(self, args):
    # We use skipLibCheck to prevent tsc from type checking the node_modules in the parent directory
    # (e.g. in a single test mode).
    return self.run_process(shared.get_npm_cmd('tsc') + ['--skipLibCheck'] + args)

  # Test that running `emcc -v` always works even in the presence of `EMCC_CFLAGS`.
  # This needs to work because many tools run `emcc -v` internally and it should
  # always work even if the user has `EMCC_CFLAGS` set.
  @with_env_modify({'EMCC_CFLAGS': '-should -be -ignored'})
  @with_both_compilers
  @crossplatform
  def test_emcc_v(self, compiler):
    # -v, without input files
    proc = self.run_process([compiler, '-v'], stdout=PIPE, stderr=PIPE)
    self.assertEqual(proc.stdout, '')
    # assert that the emcc message comes first.  We had a bug where the sub-process output
    # from clang would be flushed to stderr first.
    self.assertContained('emcc (Emscripten gcc/clang-like replacement', proc.stderr)
    self.assertTrue(proc.stderr.startswith('emcc (Emscripten gcc/clang-like replacement'))
    self.assertContained('clang version ', proc.stderr)
    self.assertContained('GNU', proc.stderr)
    self.assertContained('Target: wasm32-unknown-emscripten', proc.stderr)
    self.assertNotContained('this is dangerous', proc.stderr)

  def test_log_subcommands(self):
    # `-v` when combined with other arguments will trace the subcommands
    # that get run
    proc = self.run_process([EMCC, '-v', test_file('hello_world.c')], stdout=PIPE, stderr=PIPE)
    self.assertContained(CLANG_CC, proc.stderr)
    self.assertContained(WASM_LD, proc.stderr)
    self.assertExists('a.out.js')

  def test_skip_subcommands(self):
    # The -### flag is like `-v` but it doesn't actually execute the sub-commands
    proc = self.run_process([EMCC, '-###', '-O3', test_file('hello_world.c')], stdout=PIPE, stderr=PIPE)
    self.assertContained(CLANG_CC, proc.stderr)
    self.assertContained(WASM_LD, proc.stderr)
    self.assertContained('wasm-opt', proc.stderr)
    self.assertContained('acorn-optimizer.mjs', proc.stderr)
    self.assertNotExists('a.out.js')

  def test_emcc_check(self):
    proc = self.run_process([EMCC, '--check'], stdout=PIPE, stderr=PIPE)
    self.assertEqual(proc.stdout, '')
    self.assertContained('emcc (Emscripten gcc/clang-like replacement', proc.stderr)
    self.assertContained('Running sanity checks', proc.stderr)
    proc = self.run_process([EMCC, '--check'], stdout=PIPE, stderr=PIPE)
    self.assertContained('Running sanity checks', proc.stderr)

  @with_both_compilers
  def test_emcc_generate_config(self, compiler):
    config_path = './emscripten_config'
    with env_modify({'EM_CONFIG': config_path}):
      self.assertNotExists(config_path)
      self.run_process([compiler, '--generate-config'])
      self.assertExists(config_path)
      config_contents = read_file(config_path)
      self.assertContained('LLVM_ROOT', config_contents)
      os.remove(config_path)

  @crossplatform
  @parameterized({
    '': ([],),
    'node': (['-sENVIRONMENT=node'],),
    # load a worker before startup to check ES6 modules there as well
    'pthreads': (['-pthread', '-sPTHREAD_POOL_SIZE=1'],),
  })
  def test_esm(self, args):
    self.run_process([EMCC, '-o', 'hello_world.mjs',
                      '--extern-post-js', test_file('modularize_post_js.js'),
                      test_file('hello_world.c')] + args)
    self.assertContained('export default Module;', read_file('hello_world.mjs'))
    self.assertContained('Hello, world!', self.run_js('hello_world.mjs'))

  @requires_node_25
  @parameterized({
    '': ([],),
    'O3': (['-O3'],),
  })
  def test_esm_source_phase_imports(self, args):
    self.run_process([EMCC, '-o', 'hello_world.mjs', '-sSOURCE_PHASE_IMPORTS',
                      '--extern-post-js', test_file('modularize_post_js.js'),
                      test_file('hello_world.c')] + args)
    self.assertContained('import source wasmModule from', read_file('hello_world.mjs'))
    self.assertContained('Hello, world!', self.run_js('hello_world.mjs'))

  @requires_node_25
  @parameterized({
    '': ([],),
    'O3': (['-O3'],),
  })
  def test_esm_source_phase_imports_instance(self, args):
    self.set_setting('SOURCE_PHASE_IMPORTS')
    self.set_setting('MODULARIZE', 'instance')
    self.do_runf_out_file('hello_world.c', cflags=['-Wno-experimental'] + args)
    self.assertContained('import source wasmModule from', read_file(self.output_name('hello_world')))

  @parameterized({
    '': ([],),
    'node': (['-sENVIRONMENT=node'],),
  })
  @requires_pthreads
  def test_esm_worker(self, args):
    os.mkdir('subdir')
    self.run_process([EMCC, '-o', 'subdir/hello_world.mjs',
                      '-sEXIT_RUNTIME', '-sPROXY_TO_PTHREAD', '-pthread', '-O1',
                      '--extern-post-js', test_file('modularize_post_js.js'),
                      test_file('hello_world.c')] + args)
    src = read_file('subdir/hello_world.mjs')
    self.assertContained("new URL('hello_world.wasm', import.meta.url)", src)
    self.assertContained("new Worker(new URL('hello_world.mjs', import.meta.url), {", src)
    self.assertContained('export default Module;', src)
    self.assertContained('Hello, world!', self.run_js('subdir/hello_world.mjs'))

  @requires_pthreads
  def test_esm_worker_single_file(self):
    self.run_process([EMCC, '-o', 'hello_world.mjs', '-pthread',
                      '--extern-post-js', test_file('modularize_post_js.js'),
                      test_file('hello_world.c'), '-sSINGLE_FILE'])
    src = read_file('hello_world.mjs')
    self.assertNotContained("new URL('data:", src)
    self.assertContained("new Worker(new URL('hello_world.mjs', import.meta.url), {", src)
    self.assertContained('Hello, world!', self.run_js('hello_world.mjs'))

  def test_esm_worker_closure(self):
    self.run_process([EMCC, '-o', 'hello_world.mjs',
                      '-sEXIT_RUNTIME', '-sPROXY_TO_PTHREAD', '-pthread', '-O2',
                      test_file('hello_world.c'), '--closure=1'])
    create_file('run.mjs', '''
    import Module from './hello_world.mjs';
    await Module();
    ''')
    self.assertContained('Hello, world!', self.run_js('run.mjs'))

  def test_esm_closure(self):
    self.run_process([EMCC, '-o', 'hello_world.mjs',
                      '--extern-post-js', test_file('modularize_post_js.js'),
                      test_file('hello_world.c'), '--closure=1'])
    src = read_file('hello_world.mjs')
    self.assertContained('new URL("hello_world.wasm", import.meta.url)', src)
    self.assertContained('Hello, world!', self.run_js('hello_world.mjs'))

  def test_esm_implies_modularize(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-sEXPORT_ES6'])
    src = read_file('a.out.js')
    self.assertContained('export default Module;', src)

  def test_esm_requires_modularize(self):
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sEXPORT_ES6', '-sMODULARIZE=0'], 'EXPORT_ES6 requires MODULARIZE to be set')

  def test_emcc_out_file(self):
    # Verify that "-ofile" works in addition to "-o" "file"
    self.run_process([EMCC, '-c', '-ofoo.o', test_file('hello_world.c')])
    self.assertExists('foo.o')
    self.run_process([EMCC, '-ofoo.js', 'foo.o'])
    self.assertExists('foo.js')

  @parameterized({
    'c': [EMCC, '.c'],
    'cxx': [EMXX, '.cpp'],
  })
  def test_emcc_basics(self, compiler, suffix):
    # emcc src.cpp ==> writes a.out.js and a.out.wasm
    self.run_process([compiler, test_file('hello_world' + suffix)])
    self.assertExists('a.out.js')
    self.assertExists('a.out.wasm')
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

    # --version
    output = self.run_process([compiler, '--version'], stdout=PIPE, stderr=PIPE)
    output = output.stdout.replace('\r', '')
    # This test will require updating once per year. Sorry.
    this_year = datetime.now().year
    self.assertContained('emcc (Emscripten gcc/clang-like replacement', output)
    self.assertContained(f'''Copyright (C) {this_year} 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 = self.run_process([compiler, '--help'], stdout=PIPE, stderr=PIPE)
    self.assertContained('Display this information', output.stdout)
    self.assertContained('Most clang options will work', output.stdout)

    # -dumpversion
    output = self.run_process([compiler, '-dumpversion'], stdout=PIPE, stderr=PIPE)
    self.assertEqual(utils.EMSCRIPTEN_VERSION, output.stdout.strip())

    # properly report source code errors, and stop there
    self.clear()
    stderr = self.expect_fail([compiler, test_file('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_dumpmachine(self):
    output = self.run_process([EMCC, '-dumpmachine'], stdout=PIPE, stderr=PIPE).stdout
    self.assertContained('wasm32-unknown-emscripten', output)

    # Test the -print-target-triple llvm alias for -dumpmachine
    output = self.run_process([EMCC, '-print-target-triple'], stdout=PIPE, stderr=PIPE).stdout
    self.assertContained('wasm32-unknown-emscripten', output)

    output = self.run_process([EMCC, '--print-target-triple'], stdout=PIPE, stderr=PIPE).stdout
    self.assertContained('wasm32-unknown-emscripten', output)

    # Test that -m64 / -sMEMORY64 triggers the wasm64 triple
    output = self.run_process([EMCC, '-sMEMORY64', '-dumpmachine'], stdout=PIPE, stderr=PIPE).stdout
    self.assertContained('wasm64-unknown-emscripten', output)

    output = self.run_process([EMCC, '-m64', '-dumpmachine'], stdout=PIPE, stderr=PIPE).stdout
    self.assertContained('wasm64-unknown-emscripten', output)

    output = self.run_process([EMCC, '--target=wasm64', '-dumpmachine'], stdout=PIPE, stderr=PIPE).stdout
    self.assertContained('wasm64-unknown-emscripten', output)

  @parameterized({
    'c': [EMCC, '.c'],
    'cxx': [EMXX, '.cpp'],
  })
  def test_emcc_2(self, compiler, suffix):
    # emcc src.cpp -c    and   emcc -c src.cpp -o src.[o|foo|so] ==> should always give an object file
    for args in ([], ['-o', 'src.o'], ['-o', 'src.foo'], ['-o', 'src.so']):
      print('args:', args)
      target = args[1] if len(args) == 2 else 'hello_world.o'
      self.clear()
      self.run_process([compiler, '-c', test_file('hello_world' + suffix)] + args)
      self.assertIsObjectFile(target)
      syms = llvm_nm(target)
      self.assertIn('main', syms['defs'])
      # we also expect to have the '__original_main' wrapper and __main_void alias.
      # TODO(sbc): Should be 4 once https://reviews.llvm.org/D75277 lands
      self.assertIn(len(syms['defs']), (2, 3))
      self.run_process([compiler, target, '-o', target + '.js'])
      self.assertContained('Hello, world!', self.run_js(target + '.js'))

  def test_bc_output_warning(self):
    err = self.run_process([EMCC, '-c', test_file('hello_world.c'), '-o', 'out.bc'], stderr=PIPE).stderr
    self.assertContained('emcc: warning: .bc output file suffix used without -flto or -emit-llvm', err)

  def test_bc_as_archive(self):
    self.run_process([EMCC, '-c', test_file('hello_world.c'), '-flto', '-o', 'out.a'])
    self.run_process([EMCC, 'out.a'])

  @parameterized({
    'c': [EMCC, '.c'],
    'cxx': [EMXX, '.cpp'],
  })
  def test_emcc_3(self, compiler, suffix):
    # handle singleton archives
    self.run_process([compiler, '-c', test_file('hello_world' + suffix), '-o', 'a.o'])
    self.run_process([LLVM_AR, 'r', 'a.a', 'a.o'], stdout=PIPE, stderr=PIPE)
    self.run_process([compiler, 'a.a'])
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

    # emcc [..] -o [path] ==> should work with absolute paths
    for path in (os.path.abspath('../file1.js'), 'b_dir/file2.js'):
      print(path)
      os.chdir(self.get_dir())
      self.clear()
      print(os.listdir(os.getcwd()))
      ensure_dir('a_dir/b_dir')
      os.chdir('a_dir')
      # use single file so we don't have more files to clean up
      self.run_process([compiler, test_file('hello_world' + suffix), '-o', path, '-sSINGLE_FILE'])
      last = os.getcwd()
      os.chdir(os.path.dirname(path))
      self.assertContained('Hello, world!', self.run_js(os.path.basename(path)))
      os.chdir(last)
      delete_file(path)

  def test_multiple_sources(self):
    # Compiling two sources at a time should work.
    cmd = [EMCC, '-c', test_file('twopart_main.cpp'), test_file('twopart_side.c')]
    self.run_process(cmd)

    # Object files should be generated by default in the current working
    # directory, and not alongside the sources.
    self.assertExists('twopart_main.o')
    self.assertExists('twopart_side.o')
    self.assertNotExists(test_file('twopart_main.o'))
    self.assertNotExists(test_file('twopart_side.o'))

    # But it is an error if '-o' is also specified.
    self.clear()
    err = self.expect_fail(cmd + ['-o', 'out.o'])

    self.assertContained('clang: error: cannot specify -o when generating multiple output files', err)
    self.assertNotExists('twopart_main.o')
    self.assertNotExists('twopart_side.o')
    self.assertNotExists(test_file('twopart_main.o'))
    self.assertNotExists(test_file('twopart_side.o'))

  def test_tsearch(self):
    self.do_other_test('test_tsearch.c')

  @crossplatform
  def test_libc_progname(self):
    self.do_other_test('test_libc_progname.c')

  def test_combining_object_files(self):
    # Compiling two files with -c will generate separate object files
    self.run_process([EMCC, test_file('twopart_main.cpp'), test_file('twopart_side.c'), '-c'])
    self.assertExists('twopart_main.o')
    self.assertExists('twopart_side.o')

    # Linking with just one of them is expected to fail
    self.assert_fail([EMCC, 'twopart_main.o'], 'undefined symbol: theFunc')

    # Linking with both should work
    self.run_process([EMCC, 'twopart_main.o', 'twopart_side.o'])
    self.assertContained('side got: hello from main, over', self.run_js('a.out.js'))

    # Combining object files into another object should also work, using the `-r` flag
    err = self.run_process([EMCC, '-r', 'twopart_main.o', 'twopart_side.o', '-o', 'combined.o'], stderr=PIPE).stderr
    self.assertNotContained('warning:', err)
    # Warn about legacy support for outputting object file without `-r`, `-c` or `-shared`
    err = self.run_process([EMCC, 'twopart_main.o', 'twopart_side.o', '-o', 'combined2.o'], stderr=PIPE).stderr
    self.assertContained('warning: object file output extension (.o) used for non-object output', err)

    # Should be two symbols (and in the wasm backend, also __original_main)
    syms = llvm_nm('combined.o')
    self.assertIn('main', syms['defs'])
    # TODO(sbc): Should be 4 once https://reviews.llvm.org/D75277 lands
    self.assertIn(len(syms['defs']), (4, 3))

    self.run_process([EMCC, 'combined.o', '-o', 'combined.o.js'])
    self.assertContained('side got: hello from main, over', self.run_js('combined.o.js'))

  def test_combining_object_files_from_archive(self):
    # Compiling two files with -c will generate separate object files
    self.run_process([EMCC, test_file('twopart_main.cpp'), test_file('twopart_side.c'), '-c'])
    self.assertExists('twopart_main.o')
    self.assertExists('twopart_side.o')

    # Combining object files into a library archive should work
    self.run_process([EMAR, 'crs', 'combined.a', 'twopart_main.o', 'twopart_side.o'])
    self.assertExists('combined.a')

    # Combining library archive into an object should yield a valid object, using the `-r` flag
    self.run_process([EMXX, '-r', '-o', 'combined.o', '-Wl,--whole-archive', 'combined.a'])
    self.assertIsObjectFile('combined.o')

    # Should be two symbols (and in the wasm backend, also __original_main)
    syms = llvm_nm('combined.o')
    self.assertIn('main', syms['defs'])
    # TODO(sbc): Should be 3 once https://reviews.llvm.org/D75277 lands
    self.assertIn(len(syms['defs']), (3, 4))

    self.run_process([EMXX, 'combined.o', '-o', 'combined.o.js'])
    self.assertContained('side got: hello from main, over', self.run_js('combined.o.js'))

  def test_js_transform(self):
    create_file('t.py', '''
import sys
f = open(sys.argv[1], 'a')
f.write('transformed!')
f.close()
''')

    err = self.run_process([EMCC, test_file('hello_world.c'), '-gsource-map', '--js-transform', '%s t.py' % (PYTHON)], stderr=PIPE).stderr
    self.assertContained('disabling source maps because a js transform is being done', err)
    self.assertIn('transformed!', read_file('a.out.js'))

  @crossplatform
  @also_with_wasm64
  def test_emcc_cflags(self):
    output = self.run_process([EMCC, '--cflags'] + self.get_cflags(), stdout=PIPE)
    flags = output.stdout.strip()
    self.assertContained(r'-target wasm\d\d-unknown-emscripten', flags, regex=True)
    self.assertContained('--sysroot=', flags)
    output = self.run_process([EMXX, '--cflags'] + self.get_cflags(), stdout=PIPE)
    flags = output.stdout.strip()
    self.assertContained(r'-target wasm\d\d-unknown-emscripten', flags, regex=True)
    self.assertContained('--sysroot=', flags)
    # check they work
    cmd = [CLANG_CC, test_file('hello_world.c')] + shlex.split(flags.replace('\\', '\\\\')) + ['-c', '-o', 'out.o']
    self.run_process(cmd)
    self.run_process([EMCC, 'out.o'] + self.get_cflags())
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

  @crossplatform
  @parameterized({
    '': [[]],
    'lto': [['-flto']],
    'wasm64': [['-m64']],
  })
  def test_print_search_dirs(self, args):
    output = self.run_process([EMCC, '-print-search-dirs'] + args, stdout=PIPE).stdout
    output2 = self.run_process([EMCC, '-print-search-dirs'] + args, stdout=PIPE).stdout
    self.assertEqual(output, output2)
    self.assertContained('programs: =', output)
    self.assertContained('libraries: =', output)
    libpath = output.split('libraries: =', 1)[1].strip()
    libpath = libpath.split(os.pathsep)
    libpath = [Path(p) for p in libpath]
    settings.LTO = '-flto' in args
    settings.MEMORY64 = int('-m64' in args)
    expected = cache.get_lib_dir(absolute=True)
    self.assertIn(expected, libpath)

  @crossplatform
  @parameterized({
    '': [[]],
    'lto': [['-flto']],
    'wasm64': [['-m64']],
  })
  def test_print_libgcc_file_name(self, args):
    output = self.run_process([EMCC, '-print-libgcc-file-name'] + args, stdout=PIPE).stdout
    output2 = self.run_process([EMCC, '--print-libgcc-file-name'] + args, stdout=PIPE).stdout
    self.assertEqual(output, output2)
    settings.LTO = '-flto' in args
    settings.MEMORY64 = int('-m64' in args)
    libdir = cache.get_lib_dir(absolute=True)
    expected = os.path.join(libdir, 'libclang_rt.builtins.a')
    self.assertEqual(output.strip(), expected)

  @crossplatform
  def test_print_resource_dir(self):
    output = self.run_process([EMCC, '-print-resource-dir'], stdout=PIPE).stdout
    print(output)
    lines = output.strip().splitlines()
    self.assertEqual(len(lines), 1)
    resource_dir = os.path.normcase(lines[0])
    llvm_root = os.path.normcase(os.path.dirname(config.LLVM_ROOT))
    self.assertContained(llvm_root, resource_dir)

  @crossplatform
  def test_print_prog_name(self):
    output = self.run_process([EMCC, '-sASSERTIONS', '--print-prog-name=clang'], stdout=PIPE).stdout
    expected = CLANG_CC
    if WINDOWS:
      expected = os.path.normpath(utils.unsuffixed(CLANG_CC))
    self.assertContained(expected, output)

  @crossplatform
  @parameterized({
    '': [[]],
    'lto': [['-flto']],
    'wasm64': [['-m64']],
  })
  def test_print_file_name(self, args):
    # make sure the corresponding version of libc exists in the cache
    self.run_process([EMCC, test_file('hello_world.c'), '-O2'] + args)
    output = self.run_process([EMCC, '-print-file-name=libc.a'] + args, stdout=PIPE).stdout.rstrip()
    output2 = self.run_process([EMCC, '--print-file-name=libc.a'] + args, stdout=PIPE).stdout.rstrip()
    self.assertEqual(output, output2)
    filename = Path(output)
    settings.LTO = '-flto' in args
    settings.MEMORY64 = int('-m64' in args)
    self.assertContained(cache.get_lib_name('libc.a'), str(filename))

  @crossplatform
  def test_print_file_name_resdir(self):
    output = self.run_process([EMCC, '-print-file-name=include/wmmintrin.h'], stdout=PIPE).stdout.rstrip()
    print('print-file-name: ' + output)
    self.assertExists(output)

  @crossplatform
  def test_print_file_name_custom_resdir(self):
    output = self.run_process([EMCC, '-print-file-name=emcc.py'], stdout=PIPE).stdout.rstrip()
    self.assertNotExists(output)

    output_relative = self.run_process([EMCC, '-resource-dir=' + path_from_root(), '-print-file-name=emcc.py'], stdout=PIPE).stdout.rstrip()
    self.assertExists(output_relative)

  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)
    conf = os.path.expanduser(config.EM_CONFIG)
    proc = self.run_process([EMAR, '--em-config', conf, '--version'], stdout=PIPE, stderr=PIPE)
    self.assertEqual(proc.stderr, "")
    self.assertContained('LLVM', proc.stdout)

  def test_em_config_missing_arg(self):
    self.assert_fail([EMCC, '--em-config'], 'error: --em-config must be followed by a filename')

  @crossplatform
  def test_em_config_filename(self):
    create_file('myconfig.py', f'''
      LLVM_ROOT = r'{config.LLVM_ROOT}'
      BINARYEN_ROOT = r'{config.BINARYEN_ROOT}'
      NODE_JS = r'{config.NODE_JS[0]}'
      CACHE = r'{os.path.abspath("cache")}'
      print("filename", __file__)
    ''')
    proc = self.run_process([EMAR, '--em-config', 'myconfig.py', '--version'], stdout=PIPE, stderr=PIPE)
    self.assertContained('LLVM', proc.stdout)
    self.assertContained('filename myconfig.py', proc.stdout)

  @crossplatform
  def test_emsize(self):
    # test binaryen generated by running:
    #   emcc test/hello_world.c -Oz --closure 1 -o test/other/test_emsize.js
    expected = read_file(test_file('other/test_emsize.out'))
    cmd = [emsize, test_file('other/test_emsize.js')]
    for command in (cmd, cmd + ['--format=sysv']):
      output = self.run_process(command, stdout=PIPE).stdout
      self.assertContained(expected, output)

  def test_emstrip(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-g', '-o', 'hello.js'])
    output = self.run_process([common.LLVM_OBJDUMP, '-h', 'hello.wasm'], stdout=PIPE).stdout
    self.assertContained('.debug_info', output)
    self.run_process([emstrip, 'hello.wasm'])
    output = self.run_process([common.LLVM_OBJDUMP, '-h', 'hello.wasm'], stdout=PIPE).stdout
    self.assertNotContained('.debug_info', output)

  def test_emstrip_js(self):
    # Test that emstrip on a .js file strips the corresponding .wasm file
    self.run_process([EMCC, test_file('hello_world.c'), '-g', '-o', 'hello.js'])
    output = self.run_process([common.LLVM_OBJDUMP, '-h', 'hello.wasm'], stdout=PIPE).stdout
    self.assertContained('.debug_info', output)
    self.run_process([emstrip, 'hello.js'])
    output = self.run_process([common.LLVM_OBJDUMP, '-h', 'hello.wasm'], stdout=PIPE).stdout
    self.assertNotContained('.debug_info', output)

  @is_slow_test
  @crossplatform
  @parameterized({
    # ('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.
    'js':            ('target_js',      'test_cmake.js',         ['-DCMAKE_BUILD_TYPE=Debug', '-DCPP_LIBRARY_TYPE=STATIC']),
    'html':          ('target_html',    'hello_world_gles.html', ['-DCMAKE_BUILD_TYPE=Release']),
    'library':       ('target_library', 'libtest_cmake.a',       ['-DCMAKE_BUILD_TYPE=MinSizeRel']),
    'static_cpp':    ('target_library', 'libtest_cmake.a',       ['-DCMAKE_BUILD_TYPE=RelWithDebInfo', '-DCPP_LIBRARY_TYPE=STATIC']),
    'whole_archive': ('whole_archive',  'whole.js',              []),
    'stdproperty':   ('stdproperty',    'helloworld.js',         []),
    'post_build':    ('post_build',     'hello.js',              []),
    'cxx20':         ('cxx20',          'cxx20test.js',          []),
  })
  def test_cmake(self, test_dir, output_file, cmake_args):
    if test_dir == 'whole_archive' and 'EMTEST_SKIP_NEW_CMAKE' in os.environ:
      self.skipTest('EMTEST_SKIP_NEW_CMAKE set')

    # Test all supported generators.
    if WINDOWS:
      generators = ['MinGW Makefiles', 'NMake Makefiles']
    else:
      generators = ['Unix Makefiles', 'Ninja', 'Eclipse CDT4 - Ninja']

    configurations = {
      'MinGW Makefiles'     : {'build'   : ['mingw32-make'] }, # ruff: ignore[whitespace-before-close-bracket, whitespace-before-punctuation]
      'NMake Makefiles'     : {'build'   : ['nmake', '/NOLOGO']}, # ruff: ignore[whitespace-before-punctuation]
      'Unix Makefiles'      : {'build'   : ['make']}, # ruff: ignore[whitespace-before-punctuation]
      'Ninja'               : {'build'   : ['ninja']}, # ruff: ignore[whitespace-before-punctuation]
      'Eclipse CDT4 - Ninja': {'build'   : ['ninja']}, # ruff: ignore[whitespace-before-punctuation]
    }
    for generator in generators:
      conf = configurations[generator]

      if not shutil.which(conf['build'][0]):
        # Use simple test if applicable
        print('Skipping %s test for CMake support; build tool not found: %s.' % (generator, conf['build'][0]))
        continue

      cmakelistsdir = test_file('cmake', test_dir)
      builddir = 'out_' + generator.replace(' ', '_').lower()
      os.mkdir(builddir)
      with common.chdir(builddir):
        # Run Cmake

        # Some tests have very old cmake_minimum_version settings which is not supported by cmake 4+.
        # Forcing a slighly more recent cmake_minimum_version works around this issue.
        cmd = [EMCMAKE, 'cmake'] + cmake_args + ['-G', generator, cmakelistsdir, '-DCMAKE_POLICY_VERSION_MINIMUM=3.5']

        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))
        self.run_process(cmd, env=env)

        # Build
        cmd = conf['build']
        if common.EMTEST_VERBOSE and 'Ninja' not in generator:
          cmd += ['VERBOSE=1']
        self.run_process(cmd)
        self.assertExists(output_file, 'building a cmake-generated Makefile failed to produce an output file %s!' % output_file)

        # Run through node, if CMake produced a .js file.
        if output_file.endswith('.js'):
          ret = self.run_js(output_file)
          self.assertFileContents(os.path.join(cmakelistsdir, 'out.txt'), ret)

        if test_dir == 'post_build':
          ret = self.run_process(['ctest'], env=env)

  @crossplatform
  @parameterized({
    'std23': (['-std=c++23'],),
    'std26': (['-std=c++26'],),
  })
  def test_cxx_import(self, args):
    # cflags = ['-stdlib=libc++'] + args or explicitly
    cflags = ['-nostdinc++', '-isystem', cache.get_include_dir('c++/v1')] + args

    for module in ['std', 'std.compat']:
      cflags += [f'-fmodule-file={module}={module}.pcm']
      source = os.path.join(cache.get_sysroot_dir('share/libc++/v1'), f'{module}.cppm')
      self.run_process([EMCC, '-Wno-reserved-module-identifier', '--precompile', source, '-o', f'{module}.pcm'] + cflags)

    self.do_runf('cmake/cxx_import_std/main.cpp', 'Hello, world!\n', cflags=cflags)

  @requires_ninja
  def test_cmake_cxx_import_std(self):
    cmake_minimum = (3, 30)
    output = self.run_process([EMCMAKE, 'cmake', '--version'], stdout=PIPE).stdout
    cmake_version = re.search(r'^cmake version (\d+(?:\.\d+)*)', output).group(1)
    if tuple(map(int, cmake_version.split('.'))) < cmake_minimum:
      self.skipTest(f'CMake > {cmake_minimum} required ({cmake_version})')

    self.run_process([EMCMAKE, 'cmake', '-GNinja', test_file('cmake/cxx_import_std')])
    self.run_process(['cmake', '--build', '.'])

  # 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.
  @parameterized({
    '': ([],),
    'noforce': (['-DEMSCRIPTEN_FORCE_COMPILERS=OFF'],),
  })
  @requires_native_clang
  def test_cmake_compile_features(self, args):
    os.mkdir('build_native')
    cmd = ['cmake',
           '-DCMAKE_C_COMPILER=' + CLANG_CC, '-DCMAKE_C_FLAGS=--target=' + clang_native.get_native_triple(),
           '-DCMAKE_CXX_COMPILER=' + CLANG_CXX, '-DCMAKE_CXX_FLAGS=--target=' + clang_native.get_native_triple(),
           test_file('cmake/stdproperty')]
    print(str(cmd))
    native_features = self.run_process(cmd, stdout=PIPE, cwd='build_native').stdout

    os.mkdir('build_emcc')
    cmd = [EMCMAKE, 'cmake', test_file('cmake/stdproperty')] + args
    print(str(cmd))
    emscripten_features = self.run_process(cmd, stdout=PIPE, cwd='build_emcc').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)

  # Test that the user's explicitly specified generator is always honored
  # Internally we override the generator on windows, unless the user specifies one
  # Test require Ninja to be installed
  @requires_ninja
  def test_cmake_explicit_generator(self):
    # use -Wno-dev to suppress an irrelevant warning about the test files only.
    cmd = [EMCMAKE, 'cmake', '-GNinja', '-Wno-dev', test_file('cmake/cpp_lib')]
    self.run_process(cmd)
    self.assertExists('build.ninja')

  # Tests that it's possible to pass C++17 or GNU++17 build modes to CMake by building code that
  # needs C++17 (embind)
  @requires_ninja
  @parameterized({
    '': [[]],
    'no_gnu': [['-DNO_GNU_EXTENSIONS=1']],
  })
  def test_cmake_with_embind_cpp11_mode(self, args):
    # Use ninja generator here since we assume its always installed on our build/test machines.
    configure = [EMCMAKE, 'cmake', '-GNinja', test_file('cmake/cmake_with_emval')] + args
    self.run_process(configure)
    build = ['cmake', '--build', '.']
    self.run_process(build)

    out = self.run_js('cmake_with_emval.js')
    if '-DNO_GNU_EXTENSIONS=1' in args:
      self.assertContained('Hello! __STRICT_ANSI__: 1, __cplusplus: 201703', out)
    else:
      self.assertContained('Hello! __STRICT_ANSI__: 0, __cplusplus: 201703', out)

  # Tests that the Emscripten CMake toolchain option
  def test_cmake_bitcode_static_libraries(self):
    # Test that this option produces an error
    expected = 'EMSCRIPTEN_GENERATE_BITCODE_STATIC_LIBRARIES is not compatible with the'
    self.assert_fail([EMCMAKE, 'cmake', test_file('cmake/static_lib'), '-DEMSCRIPTEN_GENERATE_BITCODE_STATIC_LIBRARIES=ON'], expected)

  @crossplatform
  @parameterized({
    '': ([],),
    'noforce': (['-DEMSCRIPTEN_FORCE_COMPILERS=OFF'],),
  })
  def test_cmake_compile_commands(self, args):
    self.run_process([EMCMAKE, 'cmake', test_file('cmake/static_lib'), '-DCMAKE_EXPORT_COMPILE_COMMANDS=ON'] + args)
    self.assertExists('compile_commands.json')
    compile_commands = json.loads(utils.read_file('compile_commands.json'))
    command = compile_commands[0]['command']
    # Sometimes cmake puts the include dirs in an RSP file
    rsp = [p for p in command.split() if 'includes_CXX.rsp' in p]
    if rsp:
      command = read_file(rsp[0][1:])
    include_dir = utils.normalize_path(cache.get_sysroot_dir('include'))
    include_dir_cxx = utils.normalize_path(cache.get_sysroot_dir('include/c++/v1'))
    self.assertContained(include_dir, command)
    self.assertContained(include_dir_cxx, command)

  @parameterized({
    '': ['0'],
    'suffix': ['1'],
  })
  def test_cmake_static_lib(self, custom):
    # Test that one is able to use 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.run_process([EMCMAKE, 'cmake', f'-DSET_CUSTOM_SUFFIX_IN_PROJECT={custom}', test_file('cmake/static_lib')])
    self.run_process(['cmake', '--build', '.'])
    if custom == '1':
      self.assertTrue(building.is_ar('myprefix_static_lib.somecustomsuffix'))
    else:
      self.assertTrue(building.is_ar('libstatic_lib.a'))

  # Tests that cmake functions which require evaluation via the node runtime run properly with pthreads
  def test_cmake_pthreads(self):
    self.run_process([EMCMAKE, 'cmake', '-DCMAKE_C_FLAGS=-pthread', test_file('cmake/target_js')])

  # Tests that the CMake variable EMSCRIPTEN_VERSION is properly provided to user CMake scripts
  def test_cmake_emscripten_version(self):
    self.run_process([EMCMAKE, 'cmake', test_file('cmake/emscripten_version')])
    self.clear()
    self.run_process([EMCMAKE, 'cmake', test_file('cmake/emscripten_version'), '-DEMSCRIPTEN_FORCE_COMPILERS=OFF'])

  def test_cmake_emscripten_system_processor(self):
    cmake_dir = test_file('cmake/emscripten_system_processor')
    # The default CMAKE_SYSTEM_PROCESSOR is x86.
    out = self.run_process([EMCMAKE, 'cmake', cmake_dir], stdout=PIPE).stdout
    self.assertContained('CMAKE_SYSTEM_PROCESSOR is x86', out)

    # It can be overridden by setting EMSCRIPTEN_SYSTEM_PROCESSOR.
    out = self.run_process(
      [EMCMAKE, 'cmake', cmake_dir, '-DEMSCRIPTEN_SYSTEM_PROCESSOR=arm'], stdout=PIPE).stdout
    self.assertContained('CMAKE_SYSTEM_PROCESSOR is arm', out)

  @requires_network
  def test_cmake_find_stuff(self):
    # Ensure that zlib exists in the sysroot
    self.run_process([EMCC, test_file('hello_world.c'), '-sUSE_ZLIB'])
    self.run_process([EMCMAKE, 'cmake', test_file('cmake/find_stuff')])

  def test_cmake_install(self):
    # Build and install a library `foo`
    os.mkdir('build1')
    self.run_process([EMCMAKE, 'cmake', test_file('cmake/install_lib')], cwd='build1')
    self.run_process(['cmake', '--build', 'build1'])
    # newer versions of cmake support --install but we currently have 3.10.2 in CI
    # so we using `--build --target install` instead.
    self.run_process(['cmake', '--build', 'build1', '--target', 'install'])
    # Build an application that uses `find_package` to locate and use the above library.
    os.mkdir('build2')
    self.run_process([EMCMAKE, 'cmake', test_file('cmake/find_package')], cwd='build2')
    self.run_process(['cmake', '--build', 'build2'])
    self.assertContained('foo: 42\n', self.run_js('build2/Bar.js'))
    self.run_process(['cmake', '--build', 'build2', '--target', 'install'])

  @requires_network
  @crossplatform
  def test_cmake_find_modules(self):
    # This test expects SDL2 and SDL3 to be installed
    self.run_process([EMBUILDER, 'build', 'sdl2', 'sdl3'])
    output = self.run_process([EMCMAKE, 'cmake', test_file('cmake/find_modules')], stdout=PIPE).stdout
    self.assertContained(' test: OpenGL::GL IMPORTED_LIBNAME: GL', output)
    self.assertContained(' test: OpenGL::GL INTERFACE_INCLUDE_DIRECTORIES: .+/cache/sysroot/include', output, regex=True)
    self.run_process(['cmake', '--build', '.'])
    output = self.run_js('test_prog.js')
    self.assertContained('AL_VERSION: 1.1', output)
    self.assertContained('SDL version: 2.', output)
    output = self.run_js('test_prog_sdl3.js')
    self.assertContained('SDL version: 3.', output)

  def test_cmake_threads(self):
    self.run_process([EMCMAKE, 'cmake', test_file('cmake/threads')])
    self.run_process(['cmake', '--build', '.'])

  @requires_pkg_config
  def test_cmake_find_pkg_config(self):
    out = self.run_process([EMCMAKE, 'cmake', test_file('cmake/find_pkg_config')], stdout=PIPE).stdout
    libdir = cache.get_sysroot_dir('local/lib/pkgconfig')
    libdir += os.path.pathsep + cache.get_sysroot_dir('lib/pkgconfig')
    self.assertContained('PKG_CONFIG_LIBDIR: ' + libdir, out)

  @requires_pkg_config
  @crossplatform
  def test_pkg_config_packages(self):
    packages = [
      ('egl', '10.2.2'),
      ('glesv2', '10.2.2'),
      ('glfw3', '3.2.1'),
      ('sdl', '1.2.15'),
    ]
    for package, version in packages:
      out = self.run_process([EMMAKE, 'pkg-config', '--modversion', package], stdout=PIPE).stdout
      self.assertContained(version, out)

  @requires_pkg_config
  @crossplatform
  def test_pkg_config_ports(self):
    # Use bullet here because it is part of the MINIMAL set of tasks in embuilder.
    self.run_process([EMBUILDER, 'build', 'bullet'])
    out = self.run_process([EMMAKE, 'pkg-config', '--list-all'], stdout=PIPE).stdout
    self.assertContained('bullet', out)
    out = self.run_process([EMMAKE, 'pkg-config', '--cflags', 'bullet'], stdout=PIPE).stdout
    self.assertContained('-sUSE_BULLET', out)

  @parameterized({
    '': [None],
    'wasm64': ['-m64'],
    'pthreads': ['-pthread'],
  })
  def test_cmake_check_type_size(self, cflag):
    if cflag == '-m64':
      self.require_wasm64()
    cmd = [EMCMAKE, 'cmake', test_file('cmake/check_type_size')]
    if cflag:
      cmd += [f'-DCMAKE_CXX_FLAGS={cflag}', f'-DCMAKE_C_FLAGS={cflag}']
    output = self.run_process(cmd, stdout=PIPE).stdout
    if cflag == '-m64':
      self.assertContained('CMAKE_SIZEOF_VOID_P -> 8', output)
    else:
      self.assertContained('CMAKE_SIZEOF_VOID_P -> 4', output)

    # Verify that this test works without needing to run node.  We do this by breaking node
    # execution.
    self.run_process(cmd + ['-DCMAKE_CROSSCOMPILING_EMULATOR=/missing_binary'])

  @crossplatform
  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:]]
      cache.ensure_setup()
      cachedir = os.path.normpath(cache.cachedir)
      llvmroot = os.path.normpath(os.path.dirname(config.LLVM_ROOT))
      for i in includes:
        i = os.path.normpath(i)
        # we also allow for the cache include directory and llvm's own builtin includes.
        # all other include paths should be inside the sysroot.
        if i.startswith((cachedir, llvmroot)):
          continue
        self.assertContained(path_from_root('system'), i)

    err = self.run_process([EMCC, test_file('hello_world.c'), '-v'], stderr=PIPE).stderr
    verify_includes(err)
    err = self.run_process([EMXX, test_file('hello_world.cpp'), '-v'], stderr=PIPE).stderr
    verify_includes(err)

  @with_both_compilers
  def test_failure_error_code(self, compiler):
    # 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([compiler, test_file('hello_world.c'), 'this_file_is_missing.c', '-o', 'out.js'])
    self.assertFalse(os.path.exists('out.js'))

  @with_both_compilers
  def test_failure_modularize_and_catch_rejection(self, compiler):
    # Test that if sMODULARIZE and sNODEJS_CATCH_REJECTION are both enabled, then emcc shouldn't succeed, and shouldn't produce an output file.
    self.expect_fail([compiler, test_file('hello_world.c'), '-sMODULARIZE', '-sNODEJS_CATCH_REJECTION', '-o', 'out.js'])
    self.assertFalse(os.path.exists('out.js'))

  def test_use_cxx(self):
    create_file('empty_file', ' ')
    dash_xc = self.run_process([EMCC, '-v', '-xc', 'empty_file'], stderr=PIPE).stderr
    self.assertNotContained('-x c++', dash_xc)
    dash_xcpp = self.run_process([EMCC, '-v', '-xc++', 'empty_file'], stderr=PIPE).stderr
    self.assertContained('-x c++', dash_xcpp)

  @with_both_compilers
  def test_cxx11(self, compiler):
    for std in ('-std=c++11', '--std=c++11'):
      self.run_process([compiler, std, test_file('hello_cxx11.cpp')])

  # Regression test for issue #4522: Incorrect CC vs CXX detection
  @with_both_compilers
  def test_incorrect_c_detection(self, compiler):
    # This auto-detection only works for the compile phase.
    # For linking you need to use `em++` or pass `-x c++`
    create_file('test.c', 'foo\n')
    self.run_process([compiler, '-c', '-lembind', '--embed-file', 'test.c', test_file('hello_world.cpp')])

  def test_odd_suffixes(self):
    for suffix in ('CPP', 'c++', 'C++', 'cxx', 'CXX', 'cc', 'CC'):
      self.clear()
      print(suffix)
      copy_asset('hello_world.c', 'test.' + suffix)
      self.do_runf('test.' + suffix, 'Hello, world!')

    for suffix in ('lo',):
      self.clear()
      print(suffix)
      self.run_process([EMCC, test_file('hello_world.c'), '-shared', '-fPIC', '-o', 'binary.' + suffix])
      self.run_process([EMCC, 'binary.' + suffix])
      self.assertContained('Hello, world!', self.run_js('a.out.js'))

  def test_preprocessed_input(self):
    # .i and .ii files are assumed to be the output the pre-processor so clang doesn't add include
    # paths.  This means we can only compile and run things that don't contain includes.
    for suffix in ('.i', '.ii'):
      create_file('simple' + suffix, '''
        #ifdef __cplusplus
        extern "C" {
        #endif
        int puts(const char *s);
        #ifdef __cplusplus
        }
        #endif
        int main() { puts("hello"); }
        ''')
      self.do_runf('simple' + suffix, 'hello')

      create_file('with_include' + suffix, '#include <stdio.h>\nint main() { puts("hello"); }')
    self.assert_fail([EMCC, 'with_include' + suffix], 'fatal error: \'stdio.h\' file not found')

  def test_wl_linkflags(self):
    # Test path -L and -l via -Wl, arguments and -Wl, response files
    create_file('main.c', '''
      void printey();
      int main() {
        printey();
        return 0;
      }
    ''')
    create_file('libfile.c', '''
      #include <stdio.h>
      void printey() {
        printf("hello from lib\\n");
      }
    ''')
    create_file('linkflags.txt', '''
    -L.
    -lfoo
    ''')
    self.run_process([EMCC, '-o', 'libfile.o', '-c', 'libfile.c'])
    self.run_process([EMAR, 'cr', 'libfoo.a', 'libfile.o'])
    self.run_process([EMCC, 'main.c', '-L.', '-lfoo'])
    self.run_process([EMCC, 'main.c', '-Wl,-L.', '-Wl,-lfoo'])
    self.run_process([EMCC, 'main.c', '-Wl,@linkflags.txt'])

  def test_wl_stackfirst(self):
    cmd = [EMCC, test_file('hello_world.c'), '-Wl,--stack-first']
    self.run_process(cmd + ['-O0'])
    self.run_process(cmd + ['-O2'])
    self.assert_fail(cmd + ['-fsanitize=address'], 'error: --stack-first is not compatible with asan')
    self.assert_fail(cmd + ['-sGLOBAL_BASE=1024'], 'error: --stack-first is not compatible with -sGLOBAL_BASE')

  def test_side_module_global_base(self):
    expected = 'emcc: error: GLOBAL_BASE is not compatible with SIDE_MODULE'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-Werror', '-sGLOBAL_BASE=1024', '-sSIDE_MODULE'], expected)

  @parameterized({
    # In a simple -O0 build we do not set --low-memory-unused (as the stack is
    # first, which is nice for debugging but bad for code size (larger globals)
    # and bad for the low-memory-unused trick.
    '': ([], False),
    # When we optimize, we do.
    'O2': (['-O2'], True),
    # But a low global base prevents it.
    'O2_GB_512': (['-O2', '-sGLOBAL_BASE=512'], False),
    # A large-enough global base allows it.
    'O2_GB_1024': (['-O2', '-sGLOBAL_BASE=1024'], True),
    # Forcing the stack to be first in the linker prevents it.
    'linker_flag': (['-O2', '-Wl,--stack-first'], False),
  })
  def test_binaryen_low_memory_unused(self, args, low_memory_unused):
    cmd = [EMCC, test_file('hello_world.c'), '-v'] + args
    err = self.run_process(cmd, stdout=PIPE, stderr=PIPE).stderr
    self.assertContainedIf('--low-memory-unused ', err, low_memory_unused)

  def test_l_link(self):
    # Linking with -lLIBNAME and -L/DIRNAME should work, also should work with spaces
    create_file('main.c', '''
      extern void printey();
      int main() {
        printey();
        return 0;
      }
    ''')
    create_file('libfile.c', '''
      #include <stdio.h>
      void printey() {
        printf("hello from lib\\n");
      }
    ''')

    ensure_dir('libdir')

    def build(path, args):
      self.run_process([EMCC, path] + args)

    # Test linking the library built here by emcc
    build('libfile.c', ['-c'])
    shutil.move('libfile.o', 'libdir/libfile.so')
    build('main.c', ['-L' + 'libdir', '-lfile'])

    self.assertContained('hello from lib', self.run_js('a.out.js'))

    # Also test execution with `-l c` and space-separated library linking syntax
    os.remove('a.out.js')
    build('libfile.c', ['-c', '-l', 'c'])
    shutil.move('libfile.o', 'libdir/libfile.so')
    build('main.c', ['-L', 'libdir', '-l', 'file'])

    self.assertContained('hello from lib', self.run_js('a.out.js'))

    # Must not leave unneeded linker stubs
    self.assertNotExists('a.out')
    self.assertNotExists('a.exe')

  def test_commons_link(self):
    create_file('a.h', r'''
      #if !defined(A_H)
      #define A_H
      extern int foo[8];
      #endif
    ''')
    create_file('a.c', r'''
      #include "a.h"
      int foo[8];
    ''')
    create_file('main.c', r'''
      #include <stdio.h>
      #include "a.h"

      int main() {
        printf("|%d|\n", foo[0]);
        return 0;
      }
    ''')

    self.run_process([EMCC, '-o', 'a.o', '-c', 'a.c'])
    self.run_process([EMAR, 'rv', 'library.a', 'a.o'])
    self.run_process([EMCC, '-o', 'main.o', '-c', 'main.c'])
    self.run_process([EMCC, '-o', 'a.js', 'main.o', 'library.a'])
    self.assertContained('|0|', self.run_js('a.js'))

  @parameterized({
    'expand_symlinks': [[]],
    'no_canonical_prefixes': [['-no-canonical-prefixes']],
  })
  @no_windows('Windows does not support symlinks')
  def test_symlink_points_to_bad_suffix(self, flags):
    """Tests compiling a symlink where foobar.c points to foobar.xxx.

    In this case, we should always successfully compile the code.
    """
    create_file('foobar.xxx', 'int main(){ return 0; }')
    os.symlink('foobar.xxx', 'foobar.c')
    self.run_process([EMCC, 'foobar.c', '-c', '-o', 'foobar.o'] + flags)

  @no_windows('Windows does not support symlinks')
  def test_symlink_has_bad_suffix(self):
    """Tests that compiling foobar.xxx fails even if it points to foobar.c."""
    create_file('foobar.c', 'int main(){ return 0; }')
    os.symlink('foobar.c', 'foobar.xxx')
    expected = ['unknown file type: foobar.xxx', "archive member 'native.o' is neither Wasm object file nor LLVM bitcode"]
    self.assert_fail([EMCC, 'foobar.xxx', '-o', 'foobar.js'], expected)

  @parameterized({
    '': (['-lA'], 'libA.so'),
    'suffixed': (['./libA.so.1.2.3'], 'libA.so.1.2.3'),
  })
  def test_multiply_defined_libsymbols(self, link_flags, so_name):
    create_file('libA.c', 'int mult() { return 1; }')
    create_file('a2.c', 'int x() { return 42; }')
    create_file('b2.c', 'int y() { return 43; }')
    create_file('main.c', r'''
      #include <stdio.h>
      int x();
      int y();
      int mult();
      int main() {
        printf("result: %d %d %d\n", x(), y(), mult());
        return 0;
      }
    ''')

    # Check linking libA.so multiple times (both directly and indirectly) does not result
    # any multiply defined symbols.  This is especially important with `FAKE_DYLIBS` where
    # we model shared libraries using regular object files.  Without special handling
    # fake `libA.so` could get linked multiple times.
    self.cflags.remove('-Werror')
    self.emcc('libA.c', ['-sFAKE_DYLIBS', '-shared', '-o', so_name])

    err = self.emcc('a2.c', ['-sFAKE_DYLIBS', '-shared', '-L.'] + link_flags + ['-o', 'liba2.so'], stderr=PIPE).stderr
    self.assertContained(f'emcc: warning: ignoring dynamic library {os.path.basename(so_name)} when generating an object file', err)
    err = self.emcc('b2.c', ['-sFAKE_DYLIBS', '-shared', '-L.'] + link_flags + ['-o', 'libb2.so'], stderr=PIPE).stderr
    self.assertContained(f'emcc: warning: ignoring dynamic library {os.path.basename(so_name)} when generating an object file', err)

    self.do_runf('main.c', 'result: 42 43 1', cflags=['-L.'] + link_flags + ['liba2.so', 'libb2.so'])

  def test_multiply_defined_libsymbols_2(self):
    create_file('a.c', "int x() { return 55; }")
    create_file('b.c', "int y() { return 2; }")
    create_file('c.c', "int z() { return 5; }")
    create_file('main.c', r'''
      #include <stdio.h>
      int x();
      int y();
      int z();
      int main() {
        printf("result: %d\n", x() + y() + z());
        return 0;
      }
    ''')

    self.emcc('a.c', ['-c']) # a.o
    self.emcc('b.c', ['-c']) # b.o
    self.emcc('c.c', ['-c']) # c.o
    self.run_process([EMAR, 'cr', 'libLIB.a', 'a.o', 'b.o']) # libLIB.a with a and b

    # a is in the lib AND in an .o, so should be ignored in the lib. We do still need b from the lib though
    self.do_runf('main.c', 'result: 62', cflags=['a.o', 'c.o', '-L.', '-lLIB'])

  def test_link_group(self):
    create_file('lib.c', 'int x() { return 42; }')

    create_file('main.c', r'''
      #include <stdio.h>
      int x();
      int main() {
        printf("result: %d\n", x());
        return 0;
      }
    ''')

    self.emcc('lib.c', ['-c']) # lib.o
    lib_name = 'libLIB.a'
    self.run_process([EMAR, 'cr', lib_name, 'lib.o']) # libLIB.a with lib.o

    def test(compiler, main_name, lib_args, err_expected):
      print(err_expected)
      output = self.run_process([compiler, main_name, '-o', 'a.out.js'] + lib_args, stderr=PIPE, check=not err_expected)
      if err_expected:
        self.assertContained(err_expected, output.stderr)
      else:
        self.assertNotContained('undefined symbol', output.stderr)
        out_js = 'a.out.js'
        self.assertExists(out_js)
        self.assertContained('result: 42', self.run_js(out_js))

    test(EMCC, 'main.c', ['-Wl,--start-group', lib_name, '-Wl,--end-group'], None)
    test(EMCC, 'main.c', ['-Wl,--start-group', lib_name], None)

    print('embind test with groups')

    create_file('main.cpp', r'''
      #include <stdio.h>
      #include <emscripten/val.h>
      using namespace emscripten;
      extern "C" int x();
      int main() {
        int y = -x();
        y = val::global("Math").call<int>("abs", y);
        printf("result: %d\n", y);
        return 0;
      }
    ''')
    test(EMXX, 'main.cpp', ['-Wl,--start-group', lib_name, '-Wl,--end-group', '-lembind'], None)

  def test_whole_archive(self):
    # Verify that -Wl,--whole-archive includes the static constructor from the
    # otherwise unreferenced library.
    self.run_process([EMCC, '-c', '-o', 'main.o', test_file('test_whole_archive/main.c')])
    self.run_process([EMCC, '-c', '-o', 'testlib.o', test_file('test_whole_archive/testlib.c')])
    self.run_process([EMAR, 'crs', 'libtest.a', 'testlib.o'])

    self.run_process([EMCC, '-Wl,--whole-archive', 'libtest.a', '-Wl,--no-whole-archive', 'main.o'])
    self.assertContained('foo is: 42\n', self.run_js('a.out.js'))

    self.run_process([EMCC, '-Wl,-whole-archive', 'libtest.a', '-Wl,-no-whole-archive', 'main.o'])
    self.assertContained('foo is: 42\n', self.run_js('a.out.js'))

    # Verify the --no-whole-archive prevents the inclusion of the ctor
    self.run_process([EMCC, '-Wl,-whole-archive', '-Wl,--no-whole-archive', 'libtest.a', 'main.o'])
    self.assertContained('foo is: 0\n', self.run_js('a.out.js'))

  def test_whole_archive_48156(self):
    # Regression test for http://llvm.org/PR48156
    # TODO: distill this test further and move to lld
    self.run_process([EMXX, '-c', '-o', 'foo.o', '-O1',
                      test_file('test_whole_archive_foo.cpp')])
    self.run_process([EMXX, '-c', '-o', 'main.o', '-O1',
                      test_file('test_whole_archive_main.cpp')])
    self.run_process([EMAR, 'rc', 'libfoo.a', 'foo.o'])
    self.run_process([EMAR, 'rc', 'libmain.a', 'main.o'])
    self.run_process([
        EMXX, test_file('test_whole_archive_init.cpp'),
        '-O1', 'libfoo.a', '-Wl,--whole-archive', 'libmain.a', '-Wl,--no-whole-archive'])
    self.assertContained('Result: 11', self.run_js('a.out.js'))

  def test_link_group_bitcode(self):
    create_file('1.c', r'''
int f(void);
int main() {
  f();
  return 0;
}
''')
    create_file('2.c', r'''
#include <stdio.h>
int f() {
  printf("Hello\n");
  return 0;
}
''')

    self.run_process([EMCC, '-flto', '-o', '1.o', '-c', '1.c'])
    self.run_process([EMCC, '-flto', '-o', '2.o', '-c', '2.c'])
    self.run_process([EMAR, 'crs', '2.a', '2.o'])
    self.run_process([EMCC, '-r', '-flto', '-o', 'out.o', '-Wl,--start-group', '2.a', '1.o', '-Wl,--end-group'])
    self.run_process([EMCC, 'out.o'])
    self.assertContained('Hello', self.run_js('a.out.js'))

  # We deliberately ignore duplicate input files in order to allow
  # "libA.so" on the command line twice. This is not really .so support
  # and the .so files are really object files.
  @parameterized({
    '': ([],),
    'fake_dylibs': (['-sFAKE_DYLIBS'],),
  })
  def test_redundant_link(self, args):
    self.cflags += args
    create_file('libA.c', 'int mult() { return 1; }')
    create_file('main.c', r'''
      #include <stdio.h>
      int mult();
      int main() {
        printf("result: %d\n", mult());
        return 0;
      }
    ''')

    self.cflags.remove('-Werror')
    self.emcc('libA.c', ['-fPIC', '-shared', '-o', 'libA.so'])
    self.emcc('main.c', ['libA.so', 'libA.so', '-o', 'a.out.js'])
    self.assertContained('result: 1', self.run_js('a.out.js'))

  @no_mac('https://github.com/emscripten-core/emscripten/issues/16649')
  @crossplatform
  def test_dot_a_all_contents_invalid(self):
    # check that we error if an object file in a .a is not valid bitcode.
    # do not silently ignore native object files, which may have been
    # built by mistake
    create_file('native.c', 'int native() { return 5; }')
    create_file('main.c', 'extern int native(); int main() { return native(); }')
    self.run_process([CLANG_CC, 'native.c', '-c', '-o', 'native.o'] +
                     clang_native.get_clang_native_args())
    self.run_process([EMAR, 'crs', 'libfoo.a', 'native.o'])
    expected = ['unknown file type', "libfoo.a: archive member 'native.o' is neither Wasm object file nor LLVM bitcode"]
    self.assert_fail([EMCC, 'main.c', 'libfoo.a'], expected)

  def test_export_all(self):
    create_file('main.c', r'''
      #include <stdio.h>
      void libf1() { printf("libf1\n"); }
      void libf2() { printf("libf2\n"); }
    ''')

    create_file('pre.js', '''
      Module.onRuntimeInitialized = () => {
        _libf1();
        _libf2();
      };
    ''')

    # Explicitly test with -Oz to ensure libc_optz is included alongside
    # libc when `--whole-archive` is used.
    self.do_runf('main.c', 'libf1\nlibf2\n', cflags=['-Oz', '-sEXPORT_ALL', '-sMAIN_MODULE', '--pre-js', 'pre.js'])

    # Without the `-sEXPORT_ALL` these symbols will not be visible from JS
    self.do_runf('main.c', '_libf1 is not defined', assert_returncode=NON_ZERO, cflags=['-Oz', '-sMAIN_MODULE', '--pre-js', 'pre.js'])

  def test_export_keepalive(self):
    create_file('main.c', r'''
      #include <emscripten.h>
      EMSCRIPTEN_KEEPALIVE int libf1() { return 42; }
    ''')

    create_file('pre.js', '''
      Module.onRuntimeInitialized = () => {
        out(Module._libf1 ? Module._libf1() : 'unexported');
      };
    ''')

    # By default, all kept alive functions should be exported.
    self.do_runf('main.c', '42\n', cflags=['--pre-js', 'pre.js'])

    # Ensures that EXPORT_KEEPALIVE=0 remove the exports
    self.do_runf('main.c', 'unexported\n', cflags=['-sEXPORT_KEEPALIVE=0', '--pre-js', 'pre.js'])

  def test_minimal_modularize_export_keepalive(self):
    self.set_setting('MODULARIZE')
    self.set_setting('MINIMAL_RUNTIME')

    create_file('main.c', r'''
      #include <emscripten.h>
      EMSCRIPTEN_KEEPALIVE int libf1() { return 42; }
    ''')

    # With MINIMAL_RUNTIME, the module instantiation function isn't exported neither as a UMD nor as
    # an ES6 module.
    # Thus, it's impossible to use `require` or `import` and instead run the module
    # as part of --extern-post-js.
    create_file('post.js', 'Module().then((mod) => console.log(mod._libf1()));')
    self.cflags += ['--extern-post-js=post.js']

    # By default, no symbols should be exported when using MINIMAL_RUNTIME.
    self.emcc('main.c', [])
    self.assertContained('TypeError: mod._libf1 is not a function', self.run_js('a.out.js', assert_returncode=NON_ZERO))

    # Ensures that EXPORT_KEEPALIVE=1 exports the symbols.
    self.emcc('main.c', ['-sEXPORT_KEEPALIVE=1'])
    self.assertContained('42\n', self.run_js('a.out.js'))

  @crossplatform
  def test_minimal_runtime_export_all_modularize(self):
    # This test ensures that MODULARIZE and EXPORT_ALL work simultaneously.
    #
    # In addition, it ensures that EXPORT_ALL is honored while using MINIMAL_RUNTIME.
    create_file('main.c', r'''
      #include <stdio.h>
      #include <emscripten.h>
      EMSCRIPTEN_KEEPALIVE void libf1() { printf("libf1\n"); }
      EMSCRIPTEN_KEEPALIVE void libf2() { printf("libf2\n"); }
    ''')

    self.emcc('main.c', ['-sMODULARIZE=1', '-sMINIMAL_RUNTIME=2', '-sEXPORT_ALL', '-sEXPORT_ES6', '-o', 'test.mjs'])

    # We must expose __dirname and require globally because emscripten
    # uses those under the hood.
    create_file('main.mjs', '''
      import { dirname } from 'node:path';
      import { createRequire } from 'node:module';
      import { fileURLToPath } from 'node:url';

      // `fileURLToPath` is used to get a valid path on Windows.
      globalThis.__dirname = dirname(fileURLToPath(import.meta.url));
      globalThis.require = createRequire(import.meta.url);

      import Test from './test.mjs';
      async function main() {
        const mod = await Test();
        mod._libf1();
        mod._libf2();
      }
      main();
    ''')
    self.assertContained('libf1\nlibf2\n', self.run_js('main.mjs'))

  def test_minimal_runtime_errors(self):
    expected = 'emcc: error: MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION requires MINIMAL_RUNTIME'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-o', 'out.html', '-sMINIMAL_RUNTIME_STREAMING_WASM_COMPILATION'], expected)

    expected = 'emcc: error: MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION requires MINIMAL_RUNTIME'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-o', 'our.html', '-sMINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION'], expected)

    expected = 'emcc: error: MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION is only compatible with html output'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sMINIMAL_RUNTIME', '-sMINIMAL_RUNTIME_STREAMING_WASM_COMPILATION'], expected)

    expected = 'emcc: error: MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION is not compatible with SINGLE_FILE'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sMINIMAL_RUNTIME', '-sMINIMAL_RUNTIME_STREAMING_WASM_COMPILATION', '-oout.html', '-sSINGLE_FILE'], expected)

    expected = 'emcc: error: MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION is not compatible with SINGLE_FILE'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sMINIMAL_RUNTIME', '-sMINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION', '-oout.html', '-sSINGLE_FILE'], expected)

    expected = 'emcc: error: MINIMAL_RUNTIME is not compatible with --preload-file'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sMINIMAL_RUNTIME', '--preload-file', 'foo'], expected)

  def test_export_all_and_exported_functions(self):
    # EXPORT_ALL should not export library functions by default.
    # This means that to export library function you also need to explicitly
    # list them in EXPORTED_FUNCTIONS.
    lib = r'''
      #include <stdio.h>
      #include <emscripten.h>
      EMSCRIPTEN_KEEPALIVE void libfunc() { puts("libfunc\n"); }
      void libfunc2() { puts("libfunc2\n"); }
    '''
    create_file('lib.c', lib)
    create_file('pre.js', '''
      Module.onRuntimeInitialized = () => {
        _libfunc();
        _libfunc2();
      };
    ''')

    # libfunc2 should not be linked by default, even with EXPORT_ALL
    self.do_runf('lib.c', '_libfunc2 is not defined', cflags=['-sEXPORT_ALL', '--pre-js', 'pre.js'], assert_returncode=NON_ZERO)
    self.do_runf('lib.c', 'libfunc\n', cflags=['-sEXPORTED_FUNCTIONS=_libfunc2', '-sEXPORT_ALL', '--pre-js', 'pre.js'])

  @all_engines
  @with_all_fs
  @crossplatform
  @parameterized({
    '': ([],),
    'closure': (['-O2', '--closure=1'],),
  })
  def test_stdin(self, args):
    self.do_runf('module/test_stdin.c', 'abcdef\nghijkl\neof', input='abcdef\nghijkl\n', cflags=args)

  def test_stdin_cpp(self):
    self.do_runf('module/test_stdin.cpp', 'abcdef\nghijkl\neof', input='abcdef\nghijkl\n')

  @crossplatform
  def test_module_stdin(self):
    create_file('pre.js', r'''
const data = 'Hello\nworld!\n'.split('').map(c => c.charCodeAt(0));
Module['stdin'] = () => data.shift() || null;
''')
    self.do_runf('module/test_stdin.c', 'Hello\nworld!\n', cflags=['--pre-js=pre.js', '-sFORCE_FILESYSTEM'])

  def test_module_stdin_cpp(self):
    create_file('pre.js', r'''
const data = 'Hello\nworld!\n'.split('').map(c => c.charCodeAt(0));
Module['stdin'] = () => data.shift() || null;
''')
    self.do_runf('module/test_stdin.cpp', 'Hello\nworld!\n', cflags=['--pre-js=pre.js', '-sFORCE_FILESYSTEM'])

  @crossplatform
  def test_module_stdout_stderr(self):
    self.set_setting('FORCE_FILESYSTEM')
    create_file('pre.js', '''
let stdout = [];
let stderr = [];

Module['stdout'] = (char) => stdout.push(char);
Module['stderr'] = (char) => stderr.push(char);
Module['postRun'] = () => {
    assert(stderr.length === 0, 'stderr should be empty. \\n' +
        'stderr: \\n' + stderr);
    assert(UTF8ArrayToString(stdout).startsWith('Hello, world!'), 'stdout should start with the famous greeting. \\n' +
        'stdout: \\n' + stdout);
}
''')
    self.cflags += ['--pre-js', 'pre.js']
    self.do_runf('hello_world.c')

  @crossplatform
  def test_module_print_printerr(self):
    self.set_setting('FORCE_FILESYSTEM')
    create_file('pre.js', '''
let stdout = '';
let stderr = '';

Module['print'] = (text) => stdout += text;
Module['printErr'] = (text) => stderr += text;
Module['postRun'] = () => {
    assert(stderr === '', 'stderr should be empty. \\n' +
        'stderr: \\n' + stderr);
    assert(stdout.startsWith('Hello, world!'), 'stdout should start with the famous greeting. \\n' +
        'stdout: \\n' + stdout);
}
''')
    self.cflags += ['--pre-js', 'pre.js']
    self.do_runf('hello_world.c')

  def test_ungetc_fscanf(self):
    create_file('main.c', r'''
      #include <stdio.h>
      int main(int argc, char const *argv[])
      {
          char str[4] = {0};
          FILE* f = fopen("my_test.input", "r");
          if (f == NULL) {
              printf("cannot open file\n");
              return -1;
          }
          ungetc('x', f);
          ungetc('y', f);
          ungetc('z', f);
          fscanf(f, "%3s", str);
          printf("%s\n", str);
          return 0;
      }
    ''')
    create_file('my_test.input', 'abc')
    self.do_runf('main.c', 'zyx', cflags=['--embed-file', 'my_test.input'])

  def test_abspaths(self):
    # Includes with absolute paths are generally dangerous, things like -I/usr/.. will get to system
    # local headers, not our portable ones.
    for args, expected in [(['-I/usr/something', '-Wwarn-absolute-paths'], True),
                           (['-L/usr/something', '-Wwarn-absolute-paths'], True),
                           (['-I/usr/something'], False),
                           (['-L/usr/something'], False),
                           (['-I/usr/something', '-Wno-warn-absolute-paths'], False),
                           (['-L/usr/something', '-Wno-warn-absolute-paths'], False),
                           (['-Isubdir/something', '-Wwarn-absolute-paths'], False),
                           (['-Lsubdir/something', '-Wwarn-absolute-paths'], False),
                           ([], False)]:
      print(args, expected)
      proc = self.run_process([EMCC, test_file('hello_world.c')] + args, stderr=PIPE)
      WARNING = 'encountered. If this is to a local system header/library, it may cause problems (local system files make sense for compiling natively on your system, but not necessarily to JavaScript)'
      self.assertContainedIf(WARNING, proc.stderr, expected)

  def test_identical_basenames(self):
    # Issue 287: files in different dirs but with the same basename get confused as the same,
    # causing multiply defined symbol errors
    ensure_dir('foo')
    ensure_dir('bar')
    create_file('foo/main.c', '''
      extern void printey();
      int main() {
        printey();
        return 0;
      }
    ''')
    create_file('bar/main.c', '''
      #include <stdio.h>
      void printey() { printf("hello there\\n"); }
    ''')

    self.run_process([EMCC, 'foo/main.c', 'bar/main.c'])
    self.assertContained('hello there', self.run_js('a.out.js'))

    # ditto with first creating .o files
    delete_file('a.out.js')
    self.run_process([EMCC, '-c', 'foo/main.c', '-o', 'foo/main.o'])
    self.run_process([EMCC, '-c', 'bar/main.c', '-o', 'bar/main.o'])
    self.run_process([EMCC, 'foo/main.o', 'bar/main.o'])
    self.assertContained('hello there', self.run_js('a.out.js'))

  def test_main_a(self):
    # if main() is in a .a, we need to pull in that .a
    create_file('main.c', r'''
      #include <stdio.h>
      extern int f();
      int main() {
        printf("result: %d.\n", f());
        return 0;
      }
    ''')

    create_file('other.c', r'''
      #include <stdio.h>
      int f() { return 12346; }
    ''')

    self.run_process([EMCC, '-c', 'main.c'])
    self.run_process([EMCC, '-c', 'other.c'])

    self.run_process([EMAR, 'cr', 'libmain.a', 'main.o'])

    self.run_process([EMCC, 'other.o', 'libmain.a'])

    self.assertContained('result: 12346.', self.run_js('a.out.js'))

  def test_multiple_archives_duplicate_basenames(self):
    create_file('common.c', r'''
      #include <stdio.h>
      void a(void) {
        printf("a\n");
      }
    ''')
    self.run_process([EMCC, 'common.c', '-c', '-o', 'common.o'])
    delete_file('liba.a')
    self.run_process([EMAR, 'rc', 'liba.a', 'common.o'])

    create_file('common.c', r'''
      #include <stdio.h>
      void b(void) {
        printf("b\n");
      }
    ''')
    self.run_process([EMCC, 'common.c', '-c', '-o', 'common.o'])
    delete_file('libb.a')
    self.run_process([EMAR, 'rc', 'libb.a', 'common.o'])

    create_file('main.c', r'''
      void a(void);
      void b(void);
      int main() {
        a();
        b();
      }
    ''')

    self.do_runf('main.c', 'a\nb\n', cflags=['-L.', '-la', '-lb'])

  def test_archive_duplicate_basenames(self):
    ensure_dir('a')
    create_file('a/common.c', r'''
      #include <stdio.h>
      void a(void) {
        printf("a\n");
      }
    ''')
    self.run_process([EMCC, 'a/common.c', '-c', '-o', 'a/common.o'])

    ensure_dir('b')
    create_file('b/common.c', r'''
      #include <stdio.h>
      void b(void) {
        printf("b...\n");
      }
    ''')
    self.run_process([EMCC, 'b/common.c', '-c', '-o', 'b/common.o'])

    delete_file('liba.a')
    self.run_process([EMAR, 'rc', 'liba.a', 'a/common.o', 'b/common.o'])

    # Verify that archive contains basenames with hashes to avoid duplication
    text = self.run_process([EMAR, 't', 'liba.a'], stdout=PIPE).stdout
    self.assertEqual(text.count('common'), 2)
    for line in text.split('\n'):
      # should not have huge hash names
      self.assertLess(len(line), 20, line)

    create_file('main.c', r'''
      void a(void);
      void b(void);
      int main() {
        a();
        b();
      }
    ''')
    err = self.run_process([EMCC, 'main.c', '-L.', '-la'], stderr=PIPE).stderr
    self.assertNotIn('archive file contains duplicate entries', err)
    self.assertContained('a\nb...\n', self.run_js('a.out.js'))

    # Using llvm-ar directly should cause duplicate basenames
    delete_file('libdup.a')
    self.run_process([LLVM_AR, 'rc', 'libdup.a', 'a/common.o', 'b/common.o'])
    text = self.run_process([EMAR, 't', 'libdup.a'], stdout=PIPE).stdout
    self.assertEqual(text.count('common.o'), 2)

    # With fastcomp we don't support duplicate members so this should generate
    # a warning.  With the wasm backend (lld) this is fully supported.
    self.do_runf('main.c', 'a\nb...\n', cflags=['-L.', '-ldup'])

  def test_export_from_archive(self):
    export_name = 'this_is_an_entry_point'
    full_export_name = '_this_is_an_entry_point'

    create_file('export.c', r'''
      #include <stdio.h>
      void this_is_an_entry_point(void) {
        printf("Hello, world!\n");
      }
    ''')
    self.run_process([EMCC, 'export.c', '-c', '-o', 'export.o'])
    self.run_process([EMAR, 'rc', 'libexport.a', 'export.o'])

    create_file('main.c', r'''
      int main() {
        return 0;
      }
    ''')

    # Sanity check: the symbol should not be linked in if not requested.
    self.run_process([EMCC, 'main.c', '-L.', '-lexport'])
    self.assertFalse(self.is_exported_in_wasm(export_name, 'a.out.wasm'))

    # Exporting it causes it to appear in the output.
    self.run_process([EMCC, 'main.c', '-L.', '-lexport', '-sEXPORTED_FUNCTIONS=%s' % full_export_name])
    self.assertTrue(self.is_exported_in_wasm(export_name, 'a.out.wasm'))

  @parameterized({
    'embed': (['--embed-file', 'somefile.txt'],),
    'embed_twice': (['--embed-file', 'somefile.txt', '--embed-file', 'somefile.txt'],),
    'preload': (['--preload-file', 'somefile.txt', '-sSTRICT'],),
    'preload_closure': (['--preload-file', 'somefile.txt', '-O2', '--closure=1'],),
  })
  @requires_node
  def test_include_file(self, args):
    create_file('somefile.txt', 'hello from a file with lots of data and stuff in it thank you very much')
    create_file('hello.txt', 'hello world')
    create_file('main.c', r'''
      #include <assert.h>
      #include <stdio.h>
      int main() {
        FILE *f = fopen("somefile.txt", "r");
        assert(f);
        char buf[100];
        int rtn = fread(buf, 1, 20, f);
        assert(rtn == 20);
        buf[20] = 0;
        fclose(f);
        printf("|%s|\n", buf);
        return 0;
      }
    ''')

    self.do_runf('main.c', '|hello from a file wi|', cflags=args)

  @parameterized({
    '': ([],),
    'wasmfs': (['-sWASMFS'],),
  })
  @crossplatform
  def test_embed_file_dup(self, args):
    ensure_dir('tst/test1')
    ensure_dir('tst/test2')

    create_file('tst/aa.txt', 'frist')
    create_file('tst/test1/aa.txt', 'sacond')
    create_file('tst/test2/aa.txt', 'thard')
    create_file('main.c', r'''
      #include <stdio.h>
      #include <string.h>
      void print_file(const char *name) {
        FILE *f = fopen(name, "r");
        char buf[100];
        memset(buf, 0, 100);
        fread(buf, 1, 20, f);
        buf[20] = 0;
        fclose(f);
        printf("|%s|\n", buf);
      }
      int main() {
        print_file("tst/aa.txt");
        print_file("tst/test1/aa.txt");
        print_file("tst/test2/aa.txt");
        return 0;
      }
    ''')

    self.do_runf('main.c', '|frist|\n|sacond|\n|thard|\n',
                 cflags=['--embed-file', 'tst'] + args)

  def test_embed_file_exclude(self):
    ensure_dir('tst/abc.exe')
    ensure_dir('tst/abc.txt')

    create_file('tst/hello.exe', 'hello')
    create_file('tst/hello.txt', 'world')
    create_file('tst/abc.exe/foo', 'emscripten')
    create_file('tst/abc.txt/bar', '!!!')
    create_file('main.c', r'''
      #include <assert.h>
      #include <stdio.h>
      #include <unistd.h>
      int exists(const char* filename) {
        return access(filename, F_OK) == 0;
      }
      int main() {
        assert(exists("tst/hello.txt"));
        assert(exists("tst/abc.txt/bar"));
        assert(!exists("tst/hello.exe"));
        assert(!exists("tst/abc.exe/foo"));
        return 0;
      }
    ''')

    self.do_runf('main.c', cflags=['--embed-file', 'tst', '--exclude-file', '*.exe'])

  def test_embed_file_exclude_negate(self):
    ensure_dir('tst/abc.exe')
    ensure_dir('tst/abc.txt')

    create_file('tst/hello.exe', 'hello')
    create_file('tst/hello.txt', 'world')
    create_file('tst/abc.exe/foo', 'emscripten')
    create_file('tst/abc.txt/bar', '!!!')
    create_file('main.c', r'''
      #include <assert.h>
      #include <stdio.h>
      #include <unistd.h>
      int exists(const char* filename) {
        return access(filename, F_OK) == 0;
      }
      int main() {
        assert(exists("tst/hello.txt"));
        assert(exists("tst/abc.txt/bar"));
        assert(exists("tst/hello.exe"));
        assert(!exists("tst/abc.exe/foo"));
        return 0;
      }
    ''')

    self.do_runf('main.c', cflags=['--embed-file', 'tst', '--exclude-file', '*.exe', '--exclude-file', '!*hello.exe'])

  def test_embed_file_exclude_negate_order(self):
    ensure_dir('tst/abc.exe')
    ensure_dir('tst/abc.txt')

    create_file('tst/hello.exe', 'hello')
    create_file('tst/hello.txt', 'world')
    create_file('tst/abc.exe/foo', 'emscripten')
    create_file('tst/abc.txt/bar', '!!!')
    create_file('main.c', r'''
      #include <assert.h>
      #include <stdio.h>
      #include <unistd.h>
      int exists(const char* filename) {
        return access(filename, F_OK) == 0;
      }
      int main() {
        assert(exists("tst/hello.txt"));
        assert(exists("tst/abc.txt/bar"));
        assert(!exists("tst/hello.exe"));
        assert(!exists("tst/abc.exe/foo"));
        return 0;
      }
    ''')

    self.do_runf('main.c', cflags=['--embed-file', 'tst', '--exclude-file', '!*hello.exe', '--exclude-file', '*.exe'])

  def test_dylink_strict(self):
    self.do_runf_out_file('hello_world.c', cflags=['-sSTRICT', '-sMAIN_MODULE=1'])

  def test_dylink_legacy(self):
    self.do_runf_out_file('hello_world.c', cflags=['-sLEGACY_GL_EMULATION', '-sMAIN_MODULE=2'])

  def test_dylink_exceptions_and_assertions(self):
    # Linking side modules using the STL and exceptions should not abort with
    # "function in Table but not functionsInTableMap" when using ASSERTIONS=2

    # A side module that uses the STL enables exceptions.
    create_file('side.cpp', r'''
      #include <vector>
      std::vector<int> v;
      std::vector<int> side(int n) {
          for (int i=0; i<n; i++) v.push_back(i);
          return v;
      }
      ''')
    self.run_process([
      EMXX,
      '-o', 'side.wasm',
      'side.cpp',
      '-sSIDE_MODULE',
      '-sDISABLE_EXCEPTION_CATCHING=0',
      '-sASSERTIONS=2'])

    create_file('main.cpp', r'''
      #include <stdio.h>
      #include <vector>
      std::vector<int> side(int n);
      int main(void) {
          auto v = side(10);
          for (auto i : v) printf("%d", i);
          printf("\n");
          return 0;
      }
      ''')

    self.do_runf(
      'main.cpp',
      '0123456789',
      cflags=[
        '-sMAIN_MODULE',
        '-sDISABLE_EXCEPTION_CATCHING=0',
        '-sASSERTIONS=2',
        'side.wasm',
      ])

  @requires_pthreads
  @also_with_modularize
  def test_dylink_pthread_static_data(self):
    # Test that a side module uses the same static data region for global objects across all threads

    # A side module with a global object with a constructor.
    # * The global object must have a non-zero initial value to make sure that
    #   the memory is zero-initialized only once (and not once per thread).
    # * The global object must have a constructor to make sure that it is
    #   constructed only once (and not once per thread).
    create_file('side.c', r'''
      int value = 0;

      __attribute__((constructor)) void ctor(void) {
        value = 42;
      }

      int* get_address() {
        return &value;
      }
      ''')
    self.run_process([EMCC, '-o', 'side.wasm', 'side.c', '-pthread', '-Wno-experimental', '-sSIDE_MODULE'])

    create_file('main.c', r'''
      #include <assert.h>
      #include <stdio.h>
      #include <pthread.h>

      int* get_address();

      void* thread_main(void* arg) {
        int* addr = get_address();
        printf("thread_main: %p, %d\n", addr, *addr);
        assert(*addr == 123);
        return NULL;
      }

      int main() {
        int* addr = get_address();
        printf("in main: %p, %d\n", addr, *addr);
        assert(*addr == 42);
        *addr = 123;
        pthread_t t;
        pthread_create(&t, NULL, thread_main, NULL);
        pthread_join(t, NULL);
        return 0;
      }
      ''')

    self.do_runf('main.c', '123', cflags=['-pthread', '-Wno-experimental',
                                          '-sPROXY_TO_PTHREAD',
                                          '-sEXIT_RUNTIME',
                                          '-sMAIN_MODULE=2',
                                          'side.wasm'])

  def test_dylink_pthread_warning(self):
    self.assert_fail([EMCC, '-Werror', '-sMAIN_MODULE', '-pthread', test_file('hello_world.c')], 'error: dynamic linking + pthreads is experimental')

  @requires_pthreads
  def test_dylink_pthread_em_asm(self):
    self.set_setting('MAIN_MODULE', 2)
    self.do_runf('hello_world_em_asm.c', 'Hello, world!\n', cflags=['-Wno-experimental', '-pthread'])

  @requires_pthreads
  def test_dylink_pthread_em_js(self):
    self.set_setting('MAIN_MODULE', 2)
    self.set_setting('EXPORTED_FUNCTIONS', '_malloc,_main')
    self.do_runf('core/test_em_js.cpp', cflags=['-Wno-experimental', '-pthread'])

  @requires_pthreads
  @parameterized({
    '': (False,),
    'flipped': (True,),
  })
  def test_dylink_pthread_comdat(self, flipped):
    # Test that the comdat info for `Foo`, which is defined in the side module,
    # is visible to the main module.
    create_file('foo.h', r'''
    struct Foo {
      Foo() {
        method();
      }
      // Making this method virtual causes the comdat group for the
      // class to only be defined in the side module.
      virtual void method() const;
    };
    ''')
    create_file('main.cpp', r'''
      #include "foo.h"
      #include <typeinfo>
      #include <emscripten/console.h>

      // Foo constructor calls a virtual function, with the vtable defined
      // in the side module. This verifies that the side module's data
      // reloctions are applied before calling static constructors in the
      // main module.
      Foo g_foo;

      int main() {
        emscripten_outf("main: Foo typeid: %s", typeid(Foo).name());

        Foo().method();
        return 0;
      }
    ''')
    create_file('side.cpp', r'''
      #include "foo.h"
      #include <typeinfo>
      #include <emscripten/console.h>

      void Foo::method() const {
        emscripten_outf("side: Foo typeid: %s", typeid(Foo).name());
      }
      ''')
    if flipped:
      side = 'main.cpp'
      main = 'side.cpp'
    else:
      self.skipTest('https://reviews.llvm.org/D128515')
      side = 'side.cpp'
      main = 'main.cpp'
    self.run_process([
      EMCC,
      '-o', 'libside.wasm',
      side,
      '-pthread', '-Wno-experimental',
      '-sSIDE_MODULE'])
    self.do_runf(
      main,
      'main: Foo typeid: 3Foo\nside: Foo typeid: 3Foo\n',
      cflags=[
        '-pthread', '-Wno-experimental',
        '-sPROXY_TO_PTHREAD',
        '-sEXIT_RUNTIME',
        '-sMAIN_MODULE=2',
        'libside.wasm',
      ])

  def test_dylink_no_autoload(self):
    create_file('main.c', r'''
      #include <stdio.h>
      int sidey();
      int main() {
        printf("sidey: %d\n", sidey());
        return 0;
      }''')
    create_file('side.c', 'int sidey() { return 42; }')
    self.run_process([EMCC, '-sSIDE_MODULE', 'side.c', '-o', 'libside.wasm'])

    # First show everything working as expected with AUTOLOAD_DYLIBS
    self.run_process([EMCC, '-sMAIN_MODULE=2', 'main.c', 'libside.wasm'])
    output = self.run_js('a.out.js')
    self.assertContained('sidey: 42\n', output)

    # Same again but with NO_AUTOLOAD_DYLIBS.   This time we expect the call to sidey
    # to fail at runtime.
    self.run_process([EMCC, '-sMAIN_MODULE=2', 'main.c', 'libside.wasm', '-sNO_AUTOLOAD_DYLIBS'])
    output = self.run_js('a.out.js', assert_returncode=NON_ZERO)
    self.assertContained("external symbol 'sidey' is missing. perhaps a side module was not linked in?", output)

    # Now with NO_AUTOLOAD_DYLIBS, but with manual loading of libside.wasm using loadDynamicLibrary
    create_file('pre.js', '''
    Module.preRun = () => loadDynamicLibrary('libside.wasm');
    ''')
    self.run_process([EMCC, '-sMAIN_MODULE=2', 'main.c', 'libside.wasm', '-sNO_AUTOLOAD_DYLIBS', '--pre-js=pre.js'])
    output = self.run_js('a.out.js')
    self.assertContained('sidey: 42\n', output)

  def test_dylink_dependencies(self):
    create_file('side1.c', r'''
    #include <stdio.h>
    #include <stdlib.h>

    void side2();

    void side1() {
        printf("side1\n");
        side2();
    }
    ''')
    create_file('side2.c', r'''
    #include <stdio.h>
    #include <stdlib.h>

    void side2() {
        printf("side2\n");
    }
    ''')
    create_file('main.c', '''
    void side1();

    int main() {
        side1();
        return 0;
    }
    ''')
    self.emcc('side2.c', ['-fPIC', '-sSIDE_MODULE', '-olibside2.so'])
    self.emcc('side1.c', ['-fPIC', '-sSIDE_MODULE', '-olibside1.so', 'libside2.so'])
    cmd = [EMCC, 'main.c', '-fPIC', '-sMAIN_MODULE=2', '-sDYLINK_DEBUG', 'libside1.so']

    # Unless `.` is added to the library path the libside2.so won't be found.
    self.assert_fail(cmd, 'emcc: error: libside1.so: shared library dependency not found in library path: `libside2.so`.')

    # Adding -L. to the library path makes it work.
    self.run_process(cmd + ['-L.'])
    self.run_js('a.out.js')

  def test_dylink_dependencies_rpath(self):
    create_file('side1.c', r'''
    #include <stdio.h>
    #include <stdlib.h>

    void side2();

    void side1() {
        printf("side1\n");
        side2();
    }
    ''')
    create_file('side2.c', r'''
    #include <stdio.h>
    #include <stdlib.h>

    void side2() {
        printf("side2\n");
    }
    ''')
    create_file('main.c', '''
    void side1();

    int main() {
        side1();
        return 0;
    }
    ''')
    self.emcc('side2.c', ['-fPIC', '-sSIDE_MODULE', '-olibside2.so'])
    self.emcc('side1.c', ['-fPIC', '-sSIDE_MODULE', '-Wl,-rpath,$ORIGIN', '-olibside1.so', 'libside2.so'])
    cmd = [EMCC, 'main.c', '-fPIC', '-sMAIN_MODULE=2', '-sDYLINK_DEBUG', 'libside1.so']

    # Unless `.` is added to the library path the libside2.so won't be found.
    self.assert_fail(cmd, 'emcc: error: libside1.so: shared library dependency not found in library path: `libside2.so`.')

    # Adding -L. to the library path makes it work.
    self.run_process(cmd + ['-L.', '-Wl,-rpath,$ORIGIN'])
    self.run_js('a.out.js')

    def get_runtime_paths(path):
      with webassembly.Module(path) as module:
        dylink_section = module.parse_dylink_section()
        return dylink_section.runtime_paths

    self.assertEqual(get_runtime_paths('libside2.so'), [])
    self.assertEqual(get_runtime_paths('libside1.so'), ['$ORIGIN'])
    self.assertEqual(get_runtime_paths('a.out.wasm'), ['$ORIGIN'])

  def test_dylink_LEGACY_GL_EMULATION(self):
    # LEGACY_GL_EMULATION wraps JS library functions. This test ensure that when it does
    # so it preserves the `.sig` attributes needed by dynamic linking.
    create_file('test.c', r'''
#include <GLES2/gl2.h>
#include <stdio.h>

int main() {
  printf("glUseProgram: %p\n", &glUseProgram);
  printf("done\n");
  return 0;
}''')
    self.do_runf('test.c', 'done\n', cflags=['-sLEGACY_GL_EMULATION', '-sMAIN_MODULE=2'])

  def test_dylink_library_search(self):
    # Test library resolution in the case when both static and dynamic library are present.
    create_file('side_dyn.c', r'''
      #include <stdio.h>
      void side_func() {
        printf("dynamic linking used\n");
      }
    ''')
    create_file('side_static.c', r'''
      #include <stdio.h>
      void side_func() {
        printf("static linking used\n");
      }
    ''')
    self.emcc('side_dyn.c', ['-sSIDE_MODULE', '-olibside.so'])
    self.emcc('side_static.c', ['-oside_static.o', '-c'])
    self.run_process([EMAR, 'rc', 'libside.a', 'side_static.o'])

    create_file('main.c', '''
      void side_func();
      int main() {
        side_func();
        return 0;
      }
    ''')

    # By default we use dynamic linking
    self.do_runf('main.c', 'dynamic linking used\n', cflags=['-L.', '-lside'])

    # When using -sMAIN_MODULE=0 we explictly opt out of dynammic linking
    self.do_runf('main.c', 'static linking used\n', cflags=['-sMAIN_MODULE=0', '-L.', '-lside'])

    # Same for `-sFAKE_DYLIBS
    self.do_runf('main.c', 'static linking used\n', cflags=['-sFAKE_DYLIBS', '-L.', '-lside'])

    # With can also force static linking using `-Bstatic` linker falgs
    self.do_runf('main.c', 'static linking used\n', cflags=['-L.', '-Bstatic', '-lside'])

  def test_js_link(self):
    create_file('before.js', '''
      var MESSAGE = 'hello from js';
      // Module is initialized with empty object by default, so if there are no keys - nothing was run yet
      if (Object.keys(Module).length) throw 'This code should run before anything else!';
    ''')
    create_file('after.js', '''
      out(MESSAGE);
    ''')

    self.do_runf('hello_world.c', 'Hello, world!\nhello from js\n',
                 cflags=['--pre-js', 'before.js', '--post-js', 'after.js', '-sWASM_ASYNC_COMPILATION=0'])

  def test_sdl_none(self):
    create_file('main.c', r'''
      #include <stdio.h>
      #include <SDL.h>

      int main() {
        return 0;
      }
    ''')
    self.assert_fail([EMCC, 'main.c'], 'SDL.h:1:2: error: "To use the emscripten port of SDL use -sUSE_SDL or -sUSE_SDL=2"')
    self.run_process([EMCC, 'main.c', '-sUSE_SDL'])

  def test_sdl_endianness(self):
    create_file('main.c', r'''
      #include <stdio.h>
      #include <SDL/SDL.h>

      int main() {
        printf("%d, %d, %d\n", SDL_BYTEORDER, SDL_LIL_ENDIAN, SDL_BIG_ENDIAN);
        return 0;
      }
    ''')
    self.do_runf('main.c', '1234, 1234, 4321\n')

  def test_sdl_scan_code_from_key(self):
    create_file('main.c', r'''
      #include <stdio.h>
      #include <SDL/SDL_keyboard.h>

      int main() {
        printf("%d\n", SDL_GetScancodeFromKey(35));
        return 0;
      }
    ''')
    self.do_runf('main.c', '204\n')

  def test_sdl_get_key_name(self):
    create_file('main.c', r'''
      #include <stdio.h>
      #include <SDL/SDL_keyboard.h>

      int main() {
        printf("a -> '%s'\n", SDL_GetKeyName(SDLK_a));
        printf("z -> '%s'\n", SDL_GetKeyName(SDLK_z));
        printf("0 -> '%s'\n", SDL_GetKeyName(SDLK_0));
        printf("0 -> '%s'\n", SDL_GetKeyName(SDLK_9));
        printf("F1 -> '%s'\n", SDL_GetKeyName(SDLK_F1));
        return 0;
      }
    ''')
    self.do_runf('main.c', '''\
a -> 'a'
z -> 'z'
0 -> '0'
0 -> '9'
F1 -> ''
''')

  @requires_network
  def test_sdl2_mixer_wav(self):
    self.emcc('browser/test_sdl2_mixer_wav.c', ['-sUSE_SDL_MIXER=2', '-o', 'a.out.js'])
    self.emcc('browser/test_sdl2_mixer_wav.c', ['--use-port=sdl2_mixer', '-o', 'a.out.js'])
    self.emcc('browser/test_sdl2_mixer_wav.c', ['--use-port=sdl2_mixer:formats=ogg', '-o', 'a.out.js'])

  def test_sdl2_linkable(self):
    # Ensure that SDL2 can be built with MAIN_MODULE.  This implies there are no undefined
    # symbols in the library (because MAIN_MODULE=1 includes the entire library).
    self.emcc('browser/test_sdl2_misc.c', ['-sMAIN_MODULE', '-sUSE_SDL=2', '-o', 'a.out.js'])
    self.emcc('browser/test_sdl2_misc.c', ['-sMAIN_MODULE', '--use-port=sdl2', '-o', 'a.out.js'])

  def test_sdl3_linkable(self):
    # Ensure that SDL3 can be built with MAIN_MODULE.  This implies there are no undefined
    # symbols in the library (because MAIN_MODULE=1 includes the entire library).
    self.cflags.append('-Wno-experimental')
    self.emcc('browser/test_sdl3_misc.c', ['-sMAIN_MODULE', '-sUSE_SDL=3', '-o', 'a.out.js'])
    self.emcc('browser/test_sdl3_misc.c', ['-sMAIN_MODULE', '--use-port=sdl3', '-o', 'a.out.js'])

  @requires_network
  def test_sdl2_gfx_linkable(self):
    # Same as above but for sdl2_gfx library
    self.emcc('browser/test_sdl2_misc.c', ['-Wl,-fatal-warnings', '-sMAIN_MODULE', '-sUSE_SDL_GFX=2', '-o', 'a.out.js'])
    self.emcc('browser/test_sdl2_misc.c', ['-Wl,-fatal-warnings', '-sMAIN_MODULE', '--use-port=sdl2_gfx', '-o', 'a.out.js'])

  @requires_network
  def test_libpng(self):
    copy_asset('third_party/libpng/pngtest.png')
    self.do_runf('third_party/libpng/pngtest.c', 'libpng passes test',
                 cflags=['--embed-file', 'pngtest.png', '-sUSE_LIBPNG'])
    self.do_runf('third_party/libpng/pngtest.c', 'libpng passes test',
                 cflags=['--embed-file', 'pngtest.png', '--use-port=libpng'])

  @requires_pthreads
  @requires_network
  def test_libpng_with_pthreads(self):
    copy_asset('third_party/libpng/pngtest.png')
    self.do_runf('third_party/libpng/pngtest.c', 'libpng passes test',
                 cflags=['--embed-file', 'pngtest.png', '-sUSE_LIBPNG', '-pthread'])

  @requires_network
  @parameterized({
    '': (['-sUSE_GIFLIB'],),
    # Same again with -sMAIN_MODULE (See #18537)
    'dylink': (['-sUSE_GIFLIB', '-sMAIN_MODULE'],),
    'use_port': (['--use-port=giflib'],),
  })
  def test_giflib(self, args):
    copy_asset('third_party/giflib/treescap.gif')
    self.do_runf('third_party/giflib/giftext.c',
                 'GIF file terminated normally',
                 cflags=['--embed-file', 'treescap.gif', '-Wno-fortify-source'] + args,
                 args=['treescap.gif'])

  @requires_network
  def test_libjpeg(self):
    copy_asset('browser/screenshot.jpg')
    self.do_runf('jpeg_test.c', 'Image is 600 by 450 with 3 components',
                 cflags=['--embed-file', 'screenshot.jpg', '-sUSE_LIBJPEG'],
                 args=['screenshot.jpg'])
    self.do_runf('jpeg_test.c', 'Image is 600 by 450 with 3 components',
                 cflags=['--embed-file', 'screenshot.jpg', '--use-port=libjpeg'],
                 args=['screenshot.jpg'])

  @requires_network
  def test_side_module_with_ports(self):
    # Verify that ports can be used in side modules, and that the resulting
    # side module can be used from a main module.
    create_file('side.c', r'''
      #include <stdio.h>
      #include <gif_lib.h>

      void gif_side_test(void) {
        ColorMapObject* map = GifMakeMapObject(2, NULL);
        if (map) {
          GifFreeMapObject(map);
        }
        puts("gif side ok");
      }
    ''')

    create_file('main.c', r'''
      #include <stdio.h>

      void gif_side_test(void);

      int main() {
        gif_side_test();
        puts("main ok");
        return 0;
      }
    ''')

    self.emcc('side.c', args=['-sSIDE_MODULE', '-sUSE_GIFLIB', '-lgif', '-o', 'libgif_side.so'])
    self.assertExists('libgif_side.so')

    # Linking the side module into the main module should cause it to be loaded
    # automatically at runtime.
    self.do_runf('main.c', 'gif side ok\n', cflags=['-sMAIN_MODULE=2', 'libgif_side.so'])

  @requires_network
  @also_with_wasm64
  def test_bullet(self):
    self.do_runf('test_bullet_hello_world.cpp', 'BULLET RUNNING', cflags=['-sUSE_BULLET'])
    self.do_runf('test_bullet_hello_world.cpp', 'BULLET RUNNING', cflags=['--use-port=bullet'])

  @requires_network
  @is_slow_test
  def test_vorbis(self):
    # This will also test if ogg compiles, because vorbis depends on ogg
    self.do_runf('third_party/vorbis_test.c', 'ALL OK', cflags=['-sUSE_VORBIS'])
    self.do_runf('third_party/vorbis_test.c', 'ALL OK', cflags=['--use-port=vorbis'])

  @requires_network
  def test_bzip2(self):
    self.do_runf('bzip2_test.c', 'usage: unzcrash filename', cflags=['-sUSE_BZIP2', '-Wno-pointer-sign'])
    self.do_runf('bzip2_test.c', 'usage: unzcrash filename', cflags=['--use-port=bzip2', '-Wno-pointer-sign'])

  @with_all_sjlj
  @requires_network
  @crossplatform
  def test_freetype(self):
    copy_asset('freetype/LiberationSansBold.ttf')
    self.cflags += ['--embed-file', 'LiberationSansBold.ttf']
    # the test program will print an ascii representation of a bitmap where the
    # 'w' character has been rendered using the Liberation Sans Bold font.
    # See test_freetype.out
    self.do_runf_out_file('test_freetype.c', cflags=['--use-port=freetype'])

  @requires_network
  def test_freetype_pthreads(self):
    # This test also verifies the `-sUSE_FREETYPE` alternative to --use-port=freetype works.
    copy_asset('freetype/LiberationSansBold.ttf')
    self.do_runf_out_file('test_freetype.c', cflags=['--embed-file=LiberationSansBold.ttf', '-pthread', '-sUSE_FREETYPE'])

  @requires_network
  def test_icu(self):
    self.set_setting('USE_ICU')
    self.do_runf('other/test_icu.cpp')

  @requires_network
  def test_sdl2_ttf(self):
    # This is a compile-only to test to verify that sdl2-ttf (and freetype and harfbuzz) are buildable.
    self.emcc('browser/test_sdl2_ttf.c', args=['-sUSE_SDL=2', '-sUSE_SDL_TTF=2', '-o', 'a.out.js'])
    self.emcc('browser/test_sdl2_ttf.c', args=['--use-port=sdl2', '--use-port=sdl2_ttf', '-o', 'a.out.js'])

  @requires_network
  def test_sdl3_ttf(self):
    # A compile-only test that checks if sdl3-ttf, and dependencies freetype and harfbuzz, are buildable.
    self.emcc(test_file('browser/test_sdl3_ttf.c'), args=['-Wno-experimental', '-sUSE_SDL=3', '-sUSE_SDL_TTF=3'])
    self.emcc(test_file('browser/test_sdl3_ttf.c'), args=['-Wno-experimental', '--use-port=sdl3', '--use-port=sdl3_ttf'])

  @requires_network
  def test_contrib_ports(self):
    # Verify that contrib ports can be used (using the only contrib port available ATM, but can be replaced
    # with a different contrib port when there is another one
    self.emcc('other/test_contrib_ports.cpp', ['--use-port=contrib.glfw3'])

  @requires_network
  def test_remote_ports(self):
    # Emdawnwebgpu uses C++ internally, so we use a cpp file here so emcc defaults to linking C++.
    self.emcc('hello_world.cpp', ['--use-port=emdawnwebgpu'])

  @crossplatform
  def test_external_ports_simple(self):
    if config.FROZEN_CACHE:
      self.skipTest("test doesn't work with frozen cache")
    simple_port_path = test_file("other/ports/simple.py")
    self.do_runf('other/test_external_ports_simple.c', cflags=[f'--use-port={simple_port_path}'])

  @crossplatform
  @requires_network
  def test_external_ports(self):
    if config.FROZEN_CACHE:
      self.skipTest("test doesn't work with frozen cache")
    external_port_path = test_file("other/ports/external.py")
    # testing no option
    self.do_runf('other/test_external_ports.c', 'value1=0&value2=0&value3=v3\n', cflags=[f'--use-port={external_port_path}'])
    # testing 1 option
    self.do_runf('other/test_external_ports.c', 'value1=12&value2=0&value3=v3\n', cflags=[f'--use-port={external_port_path}:value1=12'])
    # testing 2 options
    self.do_runf('other/test_external_ports.c', 'value1=12&value2=36&value3=v3\n', cflags=[f'--use-port={external_port_path}:value1=12:value2=36'])
    # testing ':' escape
    self.do_runf('other/test_external_ports.c', 'value1=12&value2=36&value3=v:3\n', cflags=[f'--use-port={external_port_path}:value1=12:value3=v::3:value2=36'])
    # testing dependency
    self.do_runf('other/test_external_ports.c', 'mpg123=45\n', cflags=[f'--use-port={external_port_path}:dependency=mpg123'])
    # testing invalid dependency
    self.assert_fail([EMCC, test_file('other/test_external_ports.c'), f'--use-port={external_port_path}:dependency=invalid', '-o', 'a4.out.js'], 'unknown dependency `invalid` for port `external`')
    self.assertFalse(os.path.exists('a4.out.js'))
    # testing help
    stdout = self.run_process([EMCC, test_file('other/test_external_ports.c'), f'--use-port={external_port_path}:help'], stdout=PIPE).stdout
    self.assertContained('''external (--use-port=external; Test License)
Test Description
Options:
* value1: Value for define TEST_VALUE_1
* value2: Value for define TEST_VALUE_2
* value3: String value
* dependency: A dependency
More info: https://emscripten.org
''', stdout)

  @requires_network
  def test_port_contrib_lua(self):
    self.do_runf('other/test_port_contrib_lua.c', 'hello world\nHELLO WORLD\nsqrt(16)=4\n', cflags=['--use-port=contrib.lua'])

  def test_link_memcpy(self):
    # memcpy can show up *after* optimizations, so after our opportunity to link in libc, so it must be special-cased
    create_file('main.c', r'''
      #include <stdio.h>

      int main(int argc, char **argv) {
        int num = argc + 10;
        char buf[num], buf2[num];
        for (int i = 0; i < num; i++) {
          buf[i] = i*i+i/3;
        }
        for (int i = 1; i < num; i++) {
          buf[i] += buf[i-1];
        }
        for (int i = 0; i < num; i++) {
          buf2[i] = buf[i];
        }
        for (int i = 1; i < num; i++) {
          buf2[i] += buf2[i-1];
        }
        for (int i = 0; i < num; i++) {
          printf("%d:%d\n", i, buf2[i]);
        }
        return 0;
      }
    ''')
    self.run_process([EMCC, '-O2', 'main.c'])
    output = self.run_js('a.out.js')
    self.assertContained('''0:0
1:1
2:6
3:21
4:53
5:111
6:-49
7:98
8:55
9:96
10:-16
''', output)
    self.assertNotContained('warning: library.js memcpy should not be running, it is only for testing!', output)

  @parameterized({
    '': ('out.js',),
    'standalone': ('out.wasm',),
  })
  def test_undefined_exported_function(self, outfile):
    cmd = [EMCC, test_file('hello_world.c'), '-o', outfile]
    self.run_process(cmd)

    # Adding a missing symbol to EXPORTED_FUNCTIONS should cause a link failure
    cmd += ['-sEXPORTED_FUNCTIONS=_foobar']
    self.assert_fail(cmd, 'wasm-ld: error: symbol exported via --export not found: foobar')

    # Adding -sERROR_ON_UNDEFINED_SYMBOLS=0 means the error gets reported later
    # by emscripten.py.
    cmd += ['-sERROR_ON_UNDEFINED_SYMBOLS=0']
    self.assert_fail(cmd, 'undefined exported symbol: "_foobar"')

    # setting `-Wno-undefined` should suppress the error
    cmd += ['-Wno-undefined']
    self.run_process(cmd)

  def test_undefined_exported_runtime_method(self):
    # Adding a missing symbol to EXPORTED_RUNTIME_METHODS should cause a failure
    expected = 'undefined exported symbol: "foobar" in EXPORTED_RUNTIME_METHODS'
    self.assert_fail([EMCC, '-sEXPORTED_RUNTIME_METHODS=foobar', test_file('hello_world.c')], expected)

  @parameterized({
    '': ('out.js',),
    'standalone': ('out.wasm',),
  })
  def test_undefined_exported_js_function(self, outfile):
    cmd = [EMXX, test_file('hello_world.cpp'), '-o', outfile]
    self.run_process(cmd)

    # adding a missing symbol to EXPORTED_FUNCTIONS should cause failure
    cmd += ['-sEXPORTED_FUNCTIONS=foobar']
    self.assert_fail(cmd, 'undefined exported symbol: "foobar"')

    # setting `-Wno-undefined` should suppress the error
    cmd += ['-Wno-undefined']
    self.run_process(cmd)

  @parameterized({
    '': [[]],
    'O1': [['-O1']],
    'GL2': [['-sMAX_WEBGL_VERSION=2']],
  })
  @parameterized({
    'warn': ['WARN'],
    'error': ['ERROR'],
    'ignore': [None],
  })
  def test_undefined_symbols(self, args, action):
    create_file('main.c', r'''
      #include <stdio.h>
      #include <SDL.h>
      #include "SDL/SDL_opengl.h"

      void something();
      void elsey();

      int main() {
        // pull in gl proc stuff, avoid warnings on emulation funcs
        printf("%p", SDL_GL_GetProcAddress("glGenTextures"));
        something();
        elsey();
        return 0;
      }
      ''')

    for value in ([0, 1]):
      delete_file('a.out.js')
      print('checking %s' % value)
      extra = ['-s', action + '_ON_UNDEFINED_SYMBOLS=%d' % value] if action else []
      proc = self.run_process([EMCC, '-sUSE_SDL', '-sGL_ENABLE_GET_PROC_ADDRESS', 'main.c'] + extra + args, stderr=PIPE, check=False)
      if common.EMTEST_VERBOSE:
        print(proc.stderr)
      if value or action is None:
        # The default is that we error in undefined symbols
        self.assertContained('undefined symbol: something', proc.stderr)
        self.assertContained('undefined symbol: elsey', proc.stderr)
        check_success = False
      elif action == 'ERROR' and not value:
        # Error disables, should only warn
        self.assertContained('warning: undefined symbol: something', proc.stderr)
        self.assertContained('warning: undefined symbol: elsey', proc.stderr)
        self.assertNotContained('undefined symbol: emscripten_', proc.stderr)
        check_success = True
      elif action == 'WARN' and not value:
        # Disabled warning should imply disabling errors
        self.assertNotContained('undefined symbol', proc.stderr)
        check_success = True

      if check_success:
        self.assertEqual(proc.returncode, 0)
        self.assertTrue(os.path.exists('a.out.js'))
      else:
        self.assertNotEqual(proc.returncode, 0)
        self.assertFalse(os.path.exists('a.out.js'))

  def test_undefined_data_symbols(self):
    create_file('main.c', r'''
    extern int foo;

    int main() {
      return foo;
    }
    ''')
    self.assert_fail([EMCC, 'main.c'], 'undefined symbol: foo')

    # With -Wl,--unresolved-symbols=ignore-all or -Wl,--allow-undefined
    # the linker should ignore any undefined data symbols.
    self.run_process([EMCC, 'main.c', '-Wl,--unresolved-symbols=ignore-all'])
    self.run_process([EMCC, 'main.c', '-Wl,--allow-undefined'])

  def test_GetProcAddress_LEGACY_GL_EMULATION(self):
    # without legacy gl emulation, getting a proc from there should fail
    self.do_other_test('test_GetProcAddress_LEGACY_GL_EMULATION.c', args=['0'], cflags=['-sLEGACY_GL_EMULATION=0', '-sGL_ENABLE_GET_PROC_ADDRESS'])
    # with it, it should work
    self.do_other_test('test_GetProcAddress_LEGACY_GL_EMULATION.c', args=['1'], cflags=['-sLEGACY_GL_EMULATION', '-sGL_ENABLE_GET_PROC_ADDRESS'])

  # Verifies that is user is building without -sGL_ENABLE_GET_PROC_ADDRESS, then
  # at link time they should get a helpful error message guiding them to enable
  # the option.
  def test_get_proc_address_error_message(self):
    expected = 'error: linker: Undefined symbol: SDL_GL_GetProcAddress(). Please pass -sGL_ENABLE_GET_PROC_ADDRESS at link time to link in SDL_GL_GetProcAddress().'
    self.assert_fail([EMCC, '-sGL_ENABLE_GET_PROC_ADDRESS=0', test_file('other/test_GetProcAddress_LEGACY_GL_EMULATION.c')], expected)

  @parameterized({
    '': (False, False),
    'no_initial_run': (True, False),
    'run_dep': (False, True),
  })
  def test_prepost(self, no_initial_run, run_dep):
    create_file('pre.js', '''
      Module = {
        "preRun": () => out('pre-run'),
        "postRun": () => out('post-run')
      };
      ''')

    self.do_runf('hello_world.c', 'pre-run\nHello, world!\npost-run\n', cflags=['--pre-js', 'pre.js', '-sWASM_ASYNC_COMPILATION=0'])

    # addRunDependency during preRun should prevent main, and post-run from
    # running.
    with open('pre.js', 'a', encoding='utf-8') as f:
      f.write('Module["preRun"] = () => { out("add-dep"); addRunDependency("dep"); }\n')
    self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', '$addRunDependency')
    output = self.do_runf('hello_world.c', cflags=['--pre-js', 'pre.js', '-sRUNTIME_DEBUG', '-sWASM_ASYNC_COMPILATION=0', '-O2', '--closure=1'])
    self.assertContained('add-dep\n', output)
    self.assertNotContained('Hello, world!\n', output)
    self.assertNotContained('post-run\n', output)

    # noInitialRun prevents run
    args = ['-sWASM_ASYNC_COMPILATION=0', '-sEXPORTED_RUNTIME_METHODS=callMain']
    if no_initial_run:
      args += ['-sINVOKE_RUN=0']
    if run_dep:
      create_file('pre.js', 'Module["preRun"] = () => addRunDependency("test");')
      create_file('post.js', 'removeRunDependency("test");')
      args += ['--pre-js', 'pre.js', '--post-js', 'post.js']

    output = self.do_runf('hello_world.c', cflags=args)
    self.assertContainedIf('Hello, world!', output, not no_initial_run)

    if no_initial_run:
      # Calling main later should still work, filesystem etc. must be set up.
      print('call main later')
      src = read_file('hello_world.js')
      src += '\nout("callMain -> " + Module.callMain());\n'
      create_file('hello_world.js', src)
      self.assertContained('Hello, world!\ncallMain -> 0\n', self.run_js('hello_world.js'))

  def test_prepost2(self):
    create_file('pre.js', 'Module.preRun = () => out("pre-run");')
    create_file('pre2.js', 'Module.postRun = () => out("post-run");')
    self.do_runf('hello_world.c', 'pre-run\nHello, world!\npost-run\n',
                 cflags=['--pre-js', 'pre.js', '--pre-js', 'pre2.js'])

  @requires_jspi
  def test_prepost_jspi(self):
    create_file('pre.js', 'Module.preRun = () => out("pre-run");')
    create_file('pre2.js', 'Module.postRun = () => out("post-run");')
    self.do_runf('other/hello_world_suspend.c', 'pre-run\nHello, world!\npost-run\n',
                 cflags=['--pre-js', 'pre.js', '--pre-js', 'pre2.js', '-sJSPI'])

  def test_prepre(self):
    create_file('pre.js', '''
      Module.preRun = [() => out('pre-run-0'), () => out('pre-run-1')];
    ''')
    create_file('pre2.js', '''
      Module.preRun.push(() => out('pre-run-2'));
    ''')
    self.do_runf('hello_world.c',
                 'pre-run-0\npre-run-1\npre-run-2\nHello, world!\n',
                 cflags=['--pre-js', 'pre.js', '--pre-js', 'pre2.js'])

  def test_extern_prepost(self):
    create_file('extern-pre.js', '// I am an external pre.\n')
    create_file('extern-post.js', '// I am an external post.\n')
    self.run_process([EMCC, '-O2', test_file('hello_world.c'), '--extern-pre-js', 'extern-pre.js', '--extern-post-js', 'extern-post.js', '--closure=1'])
    # the files should be included, and externally - not as part of optimized
    # code, so they are the very first and last things, and they are not
    # minified.
    js = read_file('a.out.js')
    js_size = len(js)
    print('js_size', js_size)
    pre_offset = js.index('// I am an external pre.')
    post_offset = js.index('// I am an external post.')
    # ignore some slack - newlines and other things. we just care about the
    # big picture here
    SLACK = 50
    self.assertLess(pre_offset, post_offset)
    self.assertLess(pre_offset, SLACK)
    self.assertGreater(post_offset, js_size - SLACK)
    # make sure the slack is tiny compared to the whole program
    self.assertGreater(js_size, 50 * SLACK)

  @parameterized({
    'minifyGlobals': (['minifyGlobals'],),
    'minifyLocals': (['minifyLocals'],),
    'JSDCE': (['JSDCE', '--export-es6'],),
    'JSDCE-sourcePhaseImports': (['JSDCE', '--export-es6'],),
    'JSDCE-hasOwnProperty': (['JSDCE'],),
    'JSDCE-defaultArg': (['JSDCE'],),
    'JSDCE-fors': (['JSDCE'],),
    'JSDCE-objectPattern': (['JSDCE'],),
    'AJSDCE': (['AJSDCE'],),
    'emitDCEGraph': (['emitDCEGraph', '--no-print'],),
    'emitDCEGraph-closure': (['emitDCEGraph', '--no-print', '--closure-friendly'], 'emitDCEGraph.js'),
    'emitDCEGraph-dynCall': (['emitDCEGraph', '--no-print'],),
    'emitDCEGraph-eval': (['emitDCEGraph', '--no-print'],),
    'emitDCEGraph-sig': (['emitDCEGraph', '--no-print'],),
    'emitDCEGraph-prefixing': (['emitDCEGraph', '--no-print'],),
    'emitDCEGraph-scopes': (['emitDCEGraph', '--no-print'],),
    'emitDCEGraph-esm': (['emitDCEGraph', '--no-print', '--export-es6'],),
    'emitDCEGraph-sourcePhaseImports': (['emitDCEGraph', '--no-print', '--export-es6'],),
    'minimal-runtime-applyDCEGraphRemovals': (['applyDCEGraphRemovals'],),
    'applyDCEGraphRemovals': (['applyDCEGraphRemovals'],),
    'applyDCEGraphRemovals-esm': (['applyDCEGraphRemovals', '--export-es6'],),
    'applyImportAndExportNameChanges': (['applyImportAndExportNameChanges'],),
    'applyImportAndExportNameChanges2': (['applyImportAndExportNameChanges'],),
    'applyImportAndExportNameChanges-esm': (['applyImportAndExportNameChanges', '--export-es6'],),
    'minimal-runtime-emitDCEGraph': (['emitDCEGraph', '--no-print'],),
    'minimal-runtime-2-emitDCEGraph': (['emitDCEGraph', '--no-print'],),
    'standalone-emitDCEGraph': (['emitDCEGraph', '--no-print'],),
    'emittedJSPreservesParens': ([],),
    'growableHeap': (['growableHeap'],),
    'unsignPointers': (['unsignPointers', '--closure-friendly'],),
    'asanify': (['asanify'],),
    'safeHeap': (['safeHeap'],),
    'object-literals': ([],),
    'LittleEndianHeap': (['littleEndianHeap'],),
    'LittleEndianGrowableHeap': (['growableHeap', 'littleEndianHeap'],),
    'LittleEndianGrowableSafeHeap': (['safeHeap', 'growableHeap', 'littleEndianHeap'],),
  })
  @crossplatform
  def test_js_optimizer(self, passes, filename=None):
    if not filename:
      testname = self.id().split('.')[-1]
      filename = testname.removeprefix('test_js_optimizer_') + '.js'
    filename = test_file('js_optimizer', filename)
    expected_file = utils.unsuffixed(filename) + '-output.js'
    # test calling optimizer
    js = self.run_process(config.NODE_JS + [path_from_root('tools/acorn-optimizer.mjs'), filename] + passes, stdin=PIPE, stdout=PIPE).stdout
    self.assertFileContents(expected_file, js)

  def test_js_optimizer_huge(self):
    # Stress test the chunkifying code in js_optimizer.py
    lines = ['// EMSCRIPTEN_START_FUNCS']
    for i in range(1000_000):
      lines.append('function v%d()\n {\n var someLongNameToMakeThisLineLong = %d\n }' % (i, i))
    lines.append('// EMSCRIPTEN_END_FUNCS\n')
    create_file('huge.js', '\n'.join(lines))
    self.assertGreater(os.path.getsize('huge.js'), 50_000_000)
    self.run_process([PYTHON, path_from_root('tools/js_optimizer.py'), 'huge.js', '--minify-whitespace'])

  @parameterized({
    'wasm2js': ('wasm2js', ['minifyNames']),
    'constructor': ('constructor', ['minifyNames']),
  })
  @crossplatform
  def test_js_optimizer_py(self, name, passes):
    # run the js optimizer python script. this differs from test_js_optimizer
    # which runs the internal js optimizer JS script directly (which the python
    # script calls)
    copy_asset(f'js_optimizer/{name}.js')
    self.run_process([PYTHON, path_from_root('tools/js_optimizer.py'), name + '.js'] + passes)
    self.assertFilesMatch(test_file('js_optimizer', name + '-output.js'), name + '.js.jsopt.js')

  def test_m_mm(self):
    create_file('foo.c', '#include <emscripten.h>')
    for opt in ('M', 'MM'):
      proc = self.run_process([EMCC, 'foo.c', '-' + opt], stdout=PIPE, stderr=PIPE)
      self.assertContained('foo.o: ', proc.stdout)
      self.assertNotContained('error', proc.stderr)

  @uses_canonical_tmp
  @parameterized({
    'O0': ('-O0',),
    'O1': ('-O1',),
    'O2': ('-O2',),
    'O3': ('-O3',),
  })
  def test_emcc_debug_files(self, opt):
    for debug in (None, '1', '2'):
      print('debug =', debug)
      if os.path.exists(self.canonical_temp_dir):
        shutil.rmtree(self.canonical_temp_dir)

      with env_modify({'EMCC_DEBUG': debug}):
        self.run_process([EMCC, test_file('hello_world.c'), opt], stderr=PIPE)
        if debug is None:
          self.assertFalse(os.path.exists(self.canonical_temp_dir))
        else:
          print(sorted(os.listdir(self.canonical_temp_dir)))
          self.assertExists(os.path.join(self.canonical_temp_dir, 'emcc-04-original.js'))

  def test_debuginfo_line_tables_only(self):
    def test(do_compile):
      do_compile([])
      no_size = os.path.getsize('a.out.wasm')
      do_compile(['-gline-tables-only'])
      line_size = os.path.getsize('a.out.wasm')
      do_compile(['-g'])
      full_size = os.path.getsize('a.out.wasm')
      return (no_size, line_size, full_size)

    def compile_to_object(compile_args):
      self.run_process([EMCC, test_file('hello_world.c'), '-c', '-o', 'a.out.wasm'] + compile_args)

    no_size, line_size, full_size = test(compile_to_object)
    self.assertLess(no_size, line_size)
    self.assertLess(line_size, full_size)

    def compile_to_executable(compile_args, link_args):
      # compile with the specified args
      self.run_process([EMCC, test_file('hello_world.c'), '-c', '-o', 'a.o'] + compile_args)
      # link with debug info
      self.run_process([EMCC, 'a.o'] + link_args)

    def compile_to_debug_executable(compile_args):
      return compile_to_executable(compile_args, ['-g'])

    no_size, line_size, full_size = test(compile_to_debug_executable)
    self.assertLess(no_size, line_size)
    self.assertLess(line_size, full_size)

    def compile_to_release_executable(compile_args):
      return compile_to_executable(compile_args, ['-O1'])

    no_size, line_size, full_size = test(compile_to_release_executable)
    self.assertEqual(no_size, line_size)
    self.assertEqual(line_size, full_size)

    # "-O0 executable" means compiling without optimizations but *also* without
    # -g (so, not a true debug build). the results here may change over time,
    # since we are telling emcc both to try to do as little as possible during
    # link (-O0), but also that debug info is not needed (no -g). if we end up
    # doing post-link changes then we will strip the debug info, but if not then
    # we don't.
    def compile_to_O0_executable(compile_args):
      return compile_to_executable(compile_args, [])

    no_size, line_size, full_size = test(compile_to_O0_executable)
    self.assertEqual(no_size, line_size)
    self.assertEqual(line_size, full_size)

  # Verify the existence (or lack thereof) of DWARF info in the given wasm file
  def verify_dwarf(self, wasm_file, verify_func):
    self.assertExists(wasm_file)
    info = self.run_process([LLVM_DWARFDUMP, '--all', wasm_file], stdout=PIPE).stdout
    verify_func('DW_TAG_subprogram', info) # Subprogram entry in .debug_info
    verify_func('debug_line[0x', info) # Line table

  def verify_dwarf_exists(self, wasm_file):
    self.verify_dwarf(wasm_file, self.assertIn)

  # Verify if the given file name contains a source map
  def verify_source_map_exists(self, map_file):
    self.assertExists(map_file)
    data = json.loads(utils.read_file(map_file))
    # Simply check the existence of required sections
    self.assertIn('version', data)
    self.assertIn('sources', data)
    self.assertIn('mappings', data)

  def verify_custom_sec_existence(self, wasm_file, section_name, expect_existence):
    with webassembly.Module(wasm_file) as module:
      section = module.get_custom_section(section_name)
      if expect_existence:
        self.assertIsNotNone(section, f'section {section_name} unexpectedly missing')
      else:
        self.assertIsNone(section, f'section {section_name} unexpectedly found')

  def test_dwarf(self):
    def compile_with_dwarf(args, output):
      # Test that -g enables dwarf info in object files and linked wasm
      self.run_process([EMCC, test_file('hello_world.c'), '-o', output, '-g'] + args)
    compile_with_dwarf(['-c'], 'a.o')
    self.verify_dwarf_exists('a.o')
    compile_with_dwarf([], 'a.js')
    self.verify_dwarf_exists('a.wasm')

  @is_slow_test
  def test_dwarf_sourcemap_names(self):
    source_file = 'hello_world.c'
    js_file = 'a.out.js'
    wasm_file = 'a.out.wasm'
    map_file = 'a.out.wasm.map'

    for (flags, expect_dwarf, expect_sourcemap, expect_names) in [
      ([], False, False, False),
      (['-g0'], False, False, False),
      (['-g1'], False, False, False),
      (['-g1', '-O2'], False, False, False),
      (['-O2'], False, False, False),
      (['--minify=0'], False, False, False),
      # last numeric g flag "wins", so g0 overrides -g
      (['-g', '-g0'], False, False, False),
      (['-g2'], False, False, True),
      (['-gline-tables-only'], True, False, True),
      (['--profiling'], False, False, True),
      (['--profiling-funcs'], False, False, True),
      (['-O2', '--profiling-funcs'], False, False, True),
      (['-g'], True, False, True),
      (['-g3'], True, False, True),
      (['-O1', '-g'], True, False, True),
      (['-O3', '-g'], True, False, True),
      (['-gsplit-dwarf'], True, False, True),
      (['-gsource-map'], False, True, False),
      (['-g2', '-gsource-map'], False, True, True),
      (['-g1', '-Oz', '-gsource-map'], False, True, False),
      (['-gsource-map', '-g0'], False, False, False),
      # --emit-symbol-map should not affect the results
      (['--emit-symbol-map', '-gsource-map'], False, True, False),
      (['--emit-symbol-map'], False, False, False),
      (['--emit-symbol-map', '-Oz'], False, False, False),
      (['-sASYNCIFY=1', '-g0'], False, False, False),
      (['-sASYNCIFY=1', '-gsource-map'], False, True, False),
      (['-sASYNCIFY=1', '-gsource-map', '-g2'], False, True, True),
      (['-g', '-gsource-map'], True, True, True),
      (['-g2', '-gsource-map'], False, True, True),
      (['-gsplit-dwarf', '-gsource-map'], True, True, True),
      (['-Oz', '-gsource-map'], False, True, False),
      (['-gsource-map', '-sERROR_ON_WASM_CHANGES_AFTER_LINK'], False, True, False),
      (['-gsource-map', '-Og', '-sERROR_ON_WASM_CHANGES_AFTER_LINK'], False, True, False),
      (['-sSTACK_OVERFLOW_CHECK=2'], False, False, False),
    ]:
      print(flags, expect_dwarf, expect_sourcemap, expect_names)
      self.emcc(test_file(source_file), flags + ['-o', js_file])
      self.assertExists(js_file)
      assertion = self.assertIn if expect_dwarf else self.assertNotIn
      self.verify_dwarf(wasm_file, assertion)

      self.verify_custom_sec_existence(wasm_file, 'sourceMappingURL', expect_sourcemap)
      if expect_sourcemap:
        self.verify_source_map_exists(map_file)
      else:
        self.assertFalse(os.path.isfile(map_file), 'Sourcemap unexpectedly exists')

      self.verify_custom_sec_existence(wasm_file, 'name', expect_names)

      self.clear()

  @requires_scons
  @with_env_modify({'EMSCRIPTEN_ROOT': path_from_root()})
  def test_scons(self):
    # this test copies the site_scons directory alongside the test
    copytree(test_file('scons/simple'), '.')
    copytree(path_from_root('tools/scons/site_scons'), 'site_scons')
    self.run_process(['scons'])
    output = self.run_js('scons_integration.js', assert_returncode=5)
    self.assertContained('If you see this - the world is all right!', output)

  @requires_scons
  @with_env_modify({
    'EMSCRIPTEN_ROOT': path_from_root(),
    'EMSCONS_PKG_CONFIG_LIBDIR': '/pkg/config/libdir',
    'EMSCONS_PKG_CONFIG_PATH': '/pkg/config/path',
  })
  def test_scons_env(self):
    # this test copies the site_scons directory alongside the test
    copytree(test_file('scons/env'), '.')
    copytree(path_from_root('tools/scons/site_scons'), 'site_scons')

    expected_to_propagate = json.dumps({
      'CC': path_from_root('emcc'),
      'CXX': path_from_root('em++'),
      'AR': path_from_root('emar'),
      'RANLIB': path_from_root('emranlib'),
      'ENV': {
        'PKG_CONFIG_LIBDIR': '/pkg/config/libdir',
        'PKG_CONFIG_PATH': '/pkg/config/path',
      },
    })

    self.run_process(['scons', '--expected-env', expected_to_propagate])

  @requires_scons
  def test_scons_env_no_emscons(self):
    copytree(test_file('scons/env'), '.')
    copytree(path_from_root('tools/scons/site_scons'), 'site_scons')

    expected_to_propagate = json.dumps({
      'CC': 'emcc',
      'CXX': 'em++',
      'AR': 'emar',
      'RANLIB': 'emranlib',
      'ENV': {
        'PKG_CONFIG_LIBDIR': None,
        'PKG_CONFIG_PATH': None,
      },
    })

    self.run_process(['scons', '--expected-env', expected_to_propagate])

  @requires_scons
  def test_emscons(self):
    copytree(test_file('scons/simple'), '.')
    self.run_process([path_from_root('emscons'), 'scons'])
    output = self.run_js('scons_integration.js', assert_returncode=5)
    self.assertContained('If you see this - the world is all right!', output)

  @requires_scons
  def test_emscons_env(self):
    copytree(test_file('scons/env'), '.')

    building_env = get_building_env()
    expected_to_propagate = json.dumps({
      'CC': path_from_root('emcc'),
      'CXX': path_from_root('em++'),
      'AR': path_from_root('emar'),
      'RANLIB': path_from_root('emranlib'),
      'ENV': {
        'PKG_CONFIG_LIBDIR': building_env['PKG_CONFIG_LIBDIR'],
        'PKG_CONFIG_PATH': building_env['PKG_CONFIG_PATH'],
      },
    })

    self.run_process([path_from_root('emscons'), 'scons', '--expected-env', expected_to_propagate])

  def test_embind_fail(self):
    self.assert_fail([EMXX, test_file('embind/test_unsigned.cpp')], 'undefined symbol: _embind_register_function')

  def test_embind_invalid_overload(self):
    expected = 'BindingError: Cannot register multiple overloads of a function with the same number of arguments'
    self.do_runf('embind/test_embind_invalid_overload.cpp', expected, cflags=['-lembind'], assert_returncode=NON_ZERO)

  def test_embind_asyncify(self):
    create_file('post.js', '''
      addOnPostRun(() => {
        Module.sleep(10);
        out('done');
      });
    ''')
    create_file('main.cpp', r'''
      #include <emscripten.h>
      #include <emscripten/bind.h>
      using namespace emscripten;
      EMSCRIPTEN_BINDINGS(asyncify) {
          function("sleep", &emscripten_sleep);
      }
    ''')
    self.do_runf('main.cpp', 'done\n', cflags=['-lembind', '-sASYNCIFY', '--post-js', 'post.js'])

  @also_with_wasm64
  @parameterized({
    '': (['-sDYNAMIC_EXECUTION=1'],),
    'no_dynamic': (['-sDYNAMIC_EXECUTION=0'],),
    'dyncall': (['-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=4GB'],),
  })
  @requires_jspi
  def test_embind_jspi(self, args):
    self.do_runf('embind/embind_jspi_test.cpp', 'done\n', cflags=['-lembind', '-g'] + args)

  def test_embind_no_function(self):
    create_file('post.js', '''
      Module.onRuntimeInitialized = () => {
        out((new Module['MyClass'](42)).x);
      };
    ''')
    create_file('main.cpp', r'''
      #include <emscripten.h>
      #include <emscripten/bind.h>
      using namespace emscripten;
      class MyClass {
      public:
          MyClass(int x) : x(x) {}

          int getX() const {return x;}
          void setX(int newX) {x = newX;}
      private:
          int x;
      };
      EMSCRIPTEN_BINDINGS(my_module) {
          class_<MyClass>("MyClass")
              .constructor<int>()
              .property("x", &MyClass::getX, &MyClass::setX);
      }
    ''')
    self.do_runf('main.cpp', '42', cflags=['-lembind', '--post-js', 'post.js'])

  def test_embind_closure_no_dynamic_execution(self):
    create_file('post.js', '''
      Module['onRuntimeInitialized'] = () => {
        out(Module['foo'](10));
        out(Module['bar']());
      };
    ''')
    create_file('main.cpp', r'''
      #include <string>
      #include <emscripten/console.h>
      #include <emscripten/bind.h>
      int foo(int x) { return x; }
      void bar() {
        emscripten::val(123).call<std::string>("toString");
        emscripten::val jarray = emscripten::val::global("Float32Array").new_(10);
        emscripten_console_log("ok");
      }
      EMSCRIPTEN_BINDINGS(baz) {
        emscripten::function("foo", &foo);
        emscripten::function("bar", &bar);
      }
    ''')
    self.set_setting('INCOMING_MODULE_JS_API', 'onRuntimeInitialized')
    self.set_setting('STRICT')
    self.set_setting('NO_DYNAMIC_EXECUTION')
    self.do_runf('main.cpp', '10\nok\n',
                 cflags=['--no-entry', '-lembind', '-O2', '--closure=1', '--minify=0', '--post-js=post.js'])

  @parameterized({
    'val_1': ['embind/test_embind_no_raw_pointers_val_1.cpp'],
    'val_2': ['embind/test_embind_no_raw_pointers_val_2.cpp'],
    'val_3': ['embind/test_embind_no_raw_pointers_val_3.cpp'],
    'val_invoke': ['embind/test_embind_no_raw_pointers_val_invoke.cpp'],
    'val_call': ['embind/test_embind_no_raw_pointers_val_call.cpp'],
    'val_new': ['embind/test_embind_no_raw_pointers_val_new.cpp'],
    'wrong_ret_allow': ['embind/test_embind_wrong_ret_allow.cpp'],
    'wrong_arg_allow': ['embind/test_embind_wrong_arg_allow.cpp'],
  })
  def test_embind_no_raw_pointers(self, filename):
    self.assert_fail([EMCC, '-lembind', test_file(filename)], 'Implicitly binding raw pointers is illegal')

  def test_embind_allow_raw_pointer(self):
    self.emcc(test_file('embind/test_embind_allow_raw_pointer.cpp'), ['-lembind'])

  def test_embind_subclass_pointer(self):
    self.emcc(test_file('embind/test_embind_subclass_pointer.cpp'), ['-lembind'])

  @is_slow_test
  @parameterized({
    '': [],
    'no_utf8': ['-sEMBIND_STD_STRING_IS_UTF8=0'],
    'no_dynamic': ['-sDYNAMIC_EXECUTION=0'],
    'aot_js': ['-sDYNAMIC_EXECUTION=0', '-sEMBIND_AOT', '-DSKIP_UNBOUND_TYPES'],
    'wasm64': ['-m64'],
    '2gb': ['-sINITIAL_MEMORY=2200mb', '-sGLOBAL_BASE=2gb'],
  })
  @parameterized({
    # With no arguments we are testing the default C++ version provided by clang.
    '': [],
    # Ensure embind compiles under C++17 which is the minimum supported version.
    'cxx17': ['-std=c++17', '-Wno-#warnings'],
    'o1': ['-O1'],
    'o2': ['-O2'],
    'o2_mem_growth': ['-O2', '-sALLOW_MEMORY_GROWTH', test_file('embind/isMemoryGrowthEnabled=true.cpp')],
    'o2_closure': ['-O2', '--closure=1', '--closure-args', '--externs ' + shlex.quote(test_file('embind/underscore-externs.js')), '-sASSERTIONS=1'],
    'strict_js': ['-sSTRICT_JS'],
    # DYNCALLS tests the legacy native function API (ASYNCIFY implicitly enables DYNCALLS)
    'dyncalls': ['-sDYNCALLS=1'],
  })
  def test_embind(self, *extra_args):
    # This test is actually a large set of smaller JS unittest.
    #
    # See test/embind/embind.test.js.
    #
    # By default all of them are run.  If you are debugging and want to run just a subset of the
    # JS tests you can use `EMBIND_TESTS=myregex` to run just the matching tests.
    if '-m64' in extra_args:
      self.require_wasm64()
    self.cflags += [
      '--no-entry',
      # This test explicitly creates std::string from unsigned char pointers
      # which is deprecated in upstream LLVM.
      '-Wno-deprecated-declarations',
      '-lembind',
      '-sRETAIN_COMPILER_SETTINGS',
      '-sEXPORTED_RUNTIME_METHODS=getCompilerSetting,setDelayFunction,flushPendingDeletes,PureVirtualError,HEAP8,InternalError,BindingError,count_emval_handles',
      '-sWASM_ASYNC_COMPILATION=0',
      # This test uses a `CustomSmartPtr` class which has 1MB of data embedded in
      # it which means we need more stack space than normal.
      '-sSTACK_SIZE=2MB',
      '--pre-js', test_file('embind/test.pre.js'),
      '--post-js', test_file('embind/test.post.js'),
    ]
    self.cflags += extra_args

    js_file = self.build('embind/embind_test.cpp')

    testFiles = [
      test_file('embind/underscore-1.4.2.js'),
      test_file('embind/imvu_test_adapter.js'),
      test_file('embind/embind.test.js'),
    ]

    if '-sDYNAMIC_EXECUTION=0' in extra_args:
      js_binary_str = read_file(js_file)
      self.assertNotContained('new Function(', js_binary_str)
      self.assertNotContained('eval(', js_binary_str)

    with open(js_file, 'ab') as f:
      for tf in testFiles:
        f.write(read_binary(tf))

    output = self.run_js(js_file)
    self.assertNotContained('FAIL', output)

  def test_embind_cxx11(self):
    self.assert_fail([EMXX, '-c', '-std=c++11', test_file('embind/test_unsigned.cpp')], '#error "embind requires -std=c++17 or newer"')

  @requires_node
  def test_embind_finalization(self):
    self.run_process(
      [EMXX,
       test_file('embind/test_finalization.cpp'),
       '--pre-js', test_file('embind/test_finalization.js'),
       '-lembind'],
    )
    self.node_args += ['--expose-gc']
    output = self.run_js('a.out.js')
    self.assertContained('Constructed from C++ destructed', output)
    self.assertContained('Constructed from JS destructed', output)
    self.assertNotContained('Foo* destructed', output)

  def test_embind_return_value_policy(self):
    self.do_runf('embind/test_return_value_policy.cpp', cflags=['-lembind'])

  @requires_node_25
  def test_embind_resource_management(self):
    self.node_args.append('--js-explicit-resource-management')

    create_file('main.cpp', r'''
      #include <emscripten.h>
      #include <emscripten/bind.h>

      using namespace emscripten;

      class MyClass {};
      EMSCRIPTEN_BINDINGS(my_module) {
          class_<MyClass>("MyClass")
          .constructor();
      }

      int main() {
        EM_ASM({
          let instanceRef;
          {
            using instance = new Module.MyClass();
            assert(!instance.isDeleted());
            // "Leak" a reference to the instance just to check that it's deleted.
            instanceRef = instance;
          }
          assert(instanceRef.isDeleted());
        });
      }
    ''')
    self.do_runf('main.cpp', cflags=['-lembind'])

  @requires_jspi
  @parameterized({
    '': [['-sJSPI_EXPORTS=async*']],
    'deprecated': [['-Wno-deprecated', '-sASYNCIFY_EXPORTS=async*']],
  })
  def test_jspi_wildcard(self, args):
    self.do_runf('other/test_jspi_wildcard.c', 'done\n', cflags=args)

  @requires_jspi
  def test_jspi_add_function(self):
    # make sure synchronous functions in the wasmTable aren't processed with Asyncify.makeAsyncFunction
    self.cflags += [
      '-sJSPI',
      '-sEXPORTED_RUNTIME_METHODS=addFunction,dynCall',
      '-sALLOW_TABLE_GROWTH=1']
    self.do_runf('other/test_jspi_add_function.c', 'done\n')

  @requires_jspi
  def test_jspi_async_function(self):
    # Make sure async library functions are not automatically JSPI'd.
    create_file('lib.js', r'''
      addToLibrary({
        foo: async function(f) { await Promise.resolve(); },
      });
    ''')
    create_file('main.c', r'''
      #include <emscripten.h>
      extern void foo();
      EMSCRIPTEN_KEEPALIVE void test() {
        foo();
      }
    ''')
    create_file('post.js', r'''
      Module.onRuntimeInitialized = () => {
        _test()
        console.log('done');
      };
    ''')
    self.do_runf('main.c', 'done\n', cflags=['-sJSPI', '--js-library=lib.js', '--post-js=post.js'])

  @requires_dev_dependency('typescript')
  @parameterized({
    # Use `node16` to avoid the deprecated `node` moduleResolution in TS 6.0+. Since there is no
    # `type: "module"` in package.json, this still tests CommonJS.
    'commonjs': [['-sMODULARIZE'], ['--module', 'node16', '--moduleResolution', 'node16']],
    'esm': [['-sEXPORT_ES6'], ['--module', 'NodeNext', '--moduleResolution', 'nodenext']],
    'esm_with_jsgen': [['-sEXPORT_ES6', '-sEMBIND_AOT'], ['--module', 'NodeNext', '--moduleResolution', 'nodenext']],
  })
  def test_embind_tsgen_end_to_end(self, opts, tsc_opts):
    # Check that TypeScript generation works and that the program is runs as
    # expected.
    self.emcc(test_file('other/embind_tsgen.cpp'),
              ['-o', 'embind_tsgen.js', '-lembind', '--emit-tsd', 'embind_tsgen.d.ts'] + opts)

    # Test that the output compiles with a TS file that uses the definitions.
    copy_asset('other/embind_tsgen_main.ts', 'main.ts')
    if '-sEXPORT_ES6' in opts:
      # A package file with type=module is needed to enabled ES modules in TSC and
      # also run the output JS file as a module in node.
      copy_asset('other/embind_tsgen_package.json', 'package.json')

    self.run_tsc(['embind_tsgen.d.ts', 'main.ts', '--target', 'es2021'] + tsc_opts)
    self.assertFilesMatch(test_file('other/embind_tsgen_module.d.ts'), 'embind_tsgen.d.ts')
    self.assertContained('main ran\nts ran', self.run_js('main.js'))

  @is_slow_test
  @requires_dev_dependency('typescript')
  # These extra arguments are not related to TS binding generation but we want to
  # verify that they do not interfere with it.
  @parameterized({
    '1': [['-sALLOW_MEMORY_GROWTH=1',
           '-Wno-pthreads-mem-growth',
           '-sMAXIMUM_MEMORY=4GB',
           '--pre-js', 'fail.js',
           '--post-js', 'fail.js',
           '--extern-pre-js', 'fail.js',
           '--extern-post-js', 'fail.js',
           '-sENVIRONMENT=worker',
           '--use-preload-cache',
           '--preload-file', 'fail.js',
           '-O3',
           '-msimd128',
           '-pthread',
           '-sPROXY_TO_PTHREAD',
           '-sPTHREAD_POOL_SIZE=1',
           '-sSINGLE_FILE',
           '-lembind', # Test duplicated link option.
          ], 'embind_tsgen_ignore_1.d.ts'],
    '2': [['--embed-file', 'fail.js',
           '-sMINIMAL_RUNTIME=2',
           '-sEXPORT_ES6=1',
           '-sASSERTIONS=0',
           '-sSTRICT=1',
          ], 'embind_tsgen_ignore_2.d.ts'],
    '3': [['-sWASM=0'], 'embind_tsgen_ignore_3.d.ts'],
    '4': [['-fsanitize=undefined', '-gsource-map'], 'embind_tsgen_ignore_3.d.ts'],
    '5': [['-sASYNCIFY'], 'embind_tsgen_ignore_3.d.ts'],
    '6': [['-sENVIRONMENT=worker', '-lworkerfs.js'], 'embind_tsgen.d.ts'],
    '7': [['-m64', '-gsource-map'], 'embind_tsgen_ignore_7.d.ts'],
  })
  def test_embind_tsgen_ignore(self, extra_args, expected_ts_file):
    create_file('fail.js', 'assert(false);')
    self.emcc('other/embind_tsgen.cpp', ['-lembind', '--emit-tsd', 'embind_tsgen.d.ts'] + extra_args)
    self.assertFilesMatch(test_file(f'other/{expected_ts_file}'), 'embind_tsgen.d.ts')

  def test_embind_tsgen_remove_relaxed_simd(self):
    self.emcc('other/test_relaxed_simd.cpp', ['-mrelaxed-simd', '-msse', '-lembind', '--emit-tsd', 'embind_tsgen.d.ts'])
    self.assertContained('print_madd()', read_file('embind_tsgen.d.ts'))

  def test_embind_tsgen_worker_env(self):
    # Passing -sWASM_WORKERS requires the 'worker' environment
    # at link time. Verify that TS binding generation still works in this case.
    self.emcc('other/embind_tsgen.cpp', ['-lembind', '--emit-tsd', 'embind_tsgen.d.ts', '-sWASM_WORKERS'])
    self.assertFilesMatch(test_file('other/embind_tsgen.d.ts'), 'embind_tsgen.d.ts')

  def test_embind_tsgen_dylink(self):
    create_file('side.h', r'''
      void someLibraryFunc();
    ''')
    create_file('side.cpp', r'''
      #include "side.h"
      void someLibraryFunc() {}
    ''')
    create_file('main.cpp', r'''
      #include "side.h"
      #include <emscripten/bind.h>
      void mainLibraryFunc() {}

      EMSCRIPTEN_BINDINGS( MainLibrary ) {
        emscripten::function("mainLibraryFunc", &mainLibraryFunc );
        emscripten::function("someLibraryFunc", &someLibraryFunc );
      }
    ''')
    self.run_process([
      EMCC,
      '-o', 'libside.wasm',
      'side.cpp',
      '-sSIDE_MODULE'])
    self.emcc('main.cpp', ['libside.wasm', '-sMAIN_MODULE=2', '-lembind', '--emit-tsd', 'embind_tsgen.d.ts'])

  def test_embind_tsgen_test_embind(self):
    self.run_process([EMXX, test_file('embind/embind_test.cpp'),
                      '-lembind', '--emit-tsd', 'embind_tsgen_test_embind.d.ts',
                      # This test explicitly creates std::string from unsigned char pointers
                      # which is deprecated in upstream LLVM.
                      '-Wno-deprecated-declarations',
                      # TypeScript generation requires all type to be bound.
                      '-DSKIP_UNBOUND_TYPES'] + self.get_cflags())
    self.assertExists('embind_tsgen_test_embind.d.ts')

  def test_embind_tsgen_val(self):
    # Check that any dependencies from val still works with TS generation enabled.
    self.run_process([EMCC, test_file('other/embind_tsgen_val.cpp'),
                      '-lembind', '--emit-tsd', 'embind_tsgen_val.d.ts'])
    self.assertExists('embind_tsgen_val.d.ts')

  def test_embind_tsgen_constant_only(self):
    self.run_process([EMCC, test_file('other/embind_tsgen_constant_only.cpp'),
                      '-lembind', '--emit-tsd', 'out.d.ts'])
    self.assertFilesMatch(test_file('other/embind_tsgen_constant_only.d.ts'), 'out.d.ts')

  def test_embind_tsgen_bigint(self):
    cmd = [EMXX, test_file('other/embind_tsgen_bigint.cpp'), '-lembind', '--emit-tsd', 'embind_tsgen_bigint.d.ts']
    # Check that TypeScript generation fails when code contains bigints but their support is not enabled
    self.assert_fail(cmd + ['-sWASM_BIGINT=0'], "Missing primitive type to TS type for 'long long")
    # Check that TypeScript generation works when bigint support is enabled
    self.run_process(cmd)
    self.assertFilesMatch(test_file('other/embind_tsgen_bigint.d.ts'), 'embind_tsgen_bigint.d.ts')

  @parameterized({
    '': [[]],
    'pthread': [['-pthread']],
    'maximum_memory_over_4gb': [['-Wno-pthreads-mem-growth', '-pthread', '-sALLOW_MEMORY_GROWTH=1', '-sMAXIMUM_MEMORY=16GB']],
  })
  @requires_wasm64
  def test_embind_tsgen_wasm64(self, args):
    # Check that when wasm64 is enabled longs & unsigned longs are mapped to bigint in the generated TS bindings
    self.run_process([EMXX, test_file('other/embind_tsgen_wasm64.cpp'),
                      '-lembind', '--emit-tsd', 'embind_tsgen_wasm64.d.ts', '-m64'] +
                     args +
                     self.get_cflags())
    self.assertFilesMatch(test_file('other/embind_tsgen_wasm64.d.ts'), 'embind_tsgen_wasm64.d.ts')

  @requires_jspi
  def test_embind_tsgen_jspi(self):
    self.run_process([EMXX, test_file('other/embind_tsgen_jspi.cpp'),
                      '-lembind', '--emit-tsd', 'embind_tsgen_jspi.d.ts', '-sJSPI', '-sSTRICT', '--no-entry'] +
                     self.get_cflags())
    self.assertFilesMatch(test_file('other/embind_tsgen_jspi.d.ts'), 'embind_tsgen_jspi.d.ts')

  @parameterized({
    '': [0],
    'legacy': [1],
  })
  def test_embind_tsgen_exceptions(self, legacy):
    if not legacy and shared.get_node_version(config.NODE_JS)[0] < 22:
      self.skipTest('Node version needs to be 22 or greater to run tsgen with Wasm EH')
    self.set_setting('WASM_LEGACY_EXCEPTIONS', legacy)

    # Check that when Wasm exceptions and assertions are enabled bindings still generate.
    self.run_process([EMXX, test_file('other/embind_tsgen.cpp'),
                      '-lembind', '-fwasm-exceptions', '-sASSERTIONS',
                      '--emit-tsd', 'embind_tsgen.d.ts', '-Wno-deprecated'] +
                     self.get_cflags())
    self.assertFilesMatch(test_file('other/embind_tsgen.d.ts'), 'embind_tsgen.d.ts')

  def test_embind_jsgen_method_pointer_stability(self):
    # Test that when method pointers are allocated at different addresses that
    # AOT JS generation still works correctly.
    self.do_runf('other/embind_jsgen_method_pointer_stability.cpp', 'done\n', cflags=['-lembind', '-sEMBIND_AOT'])

  @requires_dev_dependency('typescript')
  @parameterized({
    '': [[], ''],
    'jspi': [['-sJSPI', '-sJSPI_EXPORTS=fooVoid,fooInt'], '_jspi'],
    'jspi_wildcard': [['-sJSPI', '-sJSPI_EXPORTS=foo*'], '_jspi'],
  })
  def test_emit_tsd(self, args, postfix):
    if postfix == '_jspi':
      self.require_jspi()
    self.run_process([EMCC, test_file('other/test_emit_tsd.c'),
                      '--emit-tsd', f'test_emit_tsd{postfix}.d.ts', '-sEXPORT_ES6',
                      '-sMODULARIZE', '-sEXPORTED_RUNTIME_METHODS=POINTER_SIZE,UTF8ArrayToString,wasmTable',
                      '-o', f'test_emit_tsd{postfix}.js'] + args +
                     self.get_cflags())
    self.assertFilesMatch(test_file(f'other/test_emit_tsd{postfix}.d.ts'), f'test_emit_tsd{postfix}.d.ts')
    # Test that the output compiles with a TS file that uses the definitions.
    self.run_tsc([test_file(f'other/test_tsd{postfix}.ts'), '--noEmit'])

  @requires_dev_dependency('typescript')
  def test_emit_tsd_sync_compilation(self):
    self.run_process([EMCC, test_file('other/test_emit_tsd.c'),
                      '--emit-tsd', 'test_emit_tsd_sync.d.ts',
                      '-sMODULARIZE', '-sWASM_ASYNC_COMPILATION=0',
                      '-o', 'test_emit_tsd_sync.js'] +
                     self.get_cflags())
    self.assertFilesMatch(test_file('other/test_emit_tsd_sync.d.ts'), 'test_emit_tsd_sync.d.ts')
    # Test that the output compiles with a TS file that uses the definitions.
    self.run_tsc([test_file('other/test_tsd_sync.ts'), '--noEmit'])

  def test_emit_tsd_wasm_only(self):
    expected = 'Wasm only output is not compatible with --emit-tsd'
    self.assert_fail([EMCC, test_file('other/test_emit_tsd.c'), '--emit-tsd', 'test_emit_tsd_wasm_only.d.ts', '-o', 'out.wasm'], expected)

  @requires_dev_dependency('typescript')
  def test_emit_tsd_heap(self):
    self.run_process([EMCC, test_file('other/test_emit_tsd.c'),
                      '--emit-tsd', 'test_emit_tsd.d.ts',
                      '-sEXPORTED_RUNTIME_METHODS=HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64',
                      '-o', 'test_emit_tsd.js'] +
                     self.get_cflags())
    actual = read_file('test_emit_tsd.d.ts')
    self.assertContained("    let HEAP8: Int8Array;", actual)
    self.assertContained("    let HEAPU8: Uint8Array;", actual)
    self.assertContained("    let HEAP16: Int16Array;", actual)
    self.assertContained("    let HEAPU16: Uint16Array;", actual)
    self.assertContained("    let HEAP32: Int32Array;", actual)
    self.assertContained("    let HEAPU32: Uint32Array;", actual)
    self.assertContained("    let HEAPF32: Float32Array;", actual)
    self.assertContained("    let HEAPF64: Float64Array;", actual)

  def test_emconfig(self):
    output = self.run_process([EMCONFIG, 'LLVM_ROOT'], stdout=PIPE).stdout.strip()
    self.assertEqual(output, config.LLVM_ROOT)
    # EMSCRIPTEN_ROOT is kind of special since it should always report the location of em-config
    # itself (its not configurable via the config file but driven by the location for arg0)
    output = self.run_process([EMCONFIG, 'EMSCRIPTEN_ROOT'], stdout=PIPE).stdout.strip()
    self.assertEqual(output, os.path.dirname(EMCONFIG))
    invalid = 'Usage: em-config VAR_NAME'
    # Don't accept variables that do not exist
    self.assert_fail([EMCONFIG, 'VAR_WHICH_DOES_NOT_EXIST'], invalid)
    # Don't accept no arguments
    self.assert_fail([EMCONFIG], invalid)
    # Don't accept more than one variable
    self.assert_fail([EMCONFIG, 'LLVM_ROOT', 'EMCC'], invalid)
    # Don't accept arbitrary python code
    self.assert_fail([EMCONFIG, 'sys.argv[1]'], invalid)

  def test_link_s(self):
    # -s OPT=VALUE can conflict with -s as a linker option. We warn and ignore
    create_file('main.c', r'''
      void something();

      int main() {
        something();
        return 0;
      }
    ''')
    create_file('supp.c', r'''
      #include <stdio.h>

      void something() {
        printf("yello\n");
      }
    ''')
    self.run_process([EMCC, '-c', 'main.c', '-o', 'main.o'])
    self.run_process([EMCC, '-c', 'supp.c', '-o', 'supp.o'])

    self.run_process([EMCC, 'main.o', '-s', 'supp.o', '-sSAFE_HEAP'])
    self.assertContained('yello', self.run_js('a.out.js'))
    # Check that valid -s option had an effect'
    self.assertContained('SAFE_HEAP', read_file('a.out.js'))

  def test_file_packager(self):
    ensure_dir('subdir')
    create_file('data1.txt', 'data1')

    os.chdir('subdir')
    create_file('data2.txt', 'data2')

    def check(text):
      self.assertGreater(len(text), 0)
      empty_lines = 0
      # check the generated is relatively tidy
      for line in text.splitlines():
        if line and line[-1].isspace():
          self.fail('output contains trailing whitespace: `%s`' % line)

        if line.strip():
          empty_lines = 0
        else:
          empty_lines += 1
          if empty_lines > 1:
            self.fail('output contains more then one empty line in row')

    # relative path must be within/below the current dir
    self.assert_fail([FILE_PACKAGER, 'test.data', '--quiet', '--preload', '../data1.txt'], 'which is not contained within the current directory')

    # relative path that ends up under us is cool
    proc = self.run_process([FILE_PACKAGER, 'test.data', '--quiet', '--preload', '../subdir/data2.txt'], stderr=PIPE, stdout=PIPE)
    self.assertEqual(proc.stderr, '')
    check(proc.stdout)

    # direct path leads to the same code being generated - relative path does not make us do anything different
    proc2 = self.run_process([FILE_PACKAGER, 'test.data', '--quiet', '--preload', 'data2.txt'], stderr=PIPE, stdout=PIPE)
    check(proc2.stdout)
    self.assertEqual(proc2.stderr, '')

    def clean(txt):
      lines = txt.splitlines()
      lines = [l for l in lines if 'PACKAGE_UUID' not in l and 'loadPackage({' not in l]
      return ''.join(lines)

    self.assertTextDataIdentical(clean(proc.stdout), clean(proc2.stdout))

  def test_file_packager_response_file(self):
    filenames = [f'foo.{i:032}' for i in range(4096)]

    create_file('src.c', 'int main() { return 0; }\n')
    create_file('data.txt', '')

    response_data = '\n'.join(f'--preload-file data.txt@{f}' for f in filenames)
    self.assertGreater(len(response_data), (1 << 16))
    create_file('data.rsp', response_data)

    self.run_process([EMCC, 'src.c', '@data.rsp'])
    data = read_file('a.out.js')

    for f in filenames:
      self.assertTrue(f'"/{f}"' in data)

  def test_file_packager_separate_metadata(self):
    # verify '--separate-metadata' option produces separate metadata file
    ensure_dir('subdir')
    create_file('data1.txt', 'data1')
    create_file('subdir/data2.txt', 'data2')

    self.run_process([FILE_PACKAGER, 'test.data', '--quiet', '--preload', 'data1.txt', '--preload', 'subdir/data2.txt', '--js-output=immutable.js', '--separate-metadata', '--use-preload-cache'])
    self.assertExists('immutable.js.metadata')
    # verify js output JS file is not touched when the metadata is separated
    orig_timestamp = os.path.getmtime('immutable.js')
    orig_content = read_file('immutable.js')
    # ensure some time passes before running the packager again so that if it does touch the
    # js file it will end up with the different timestamp.
    time.sleep(1.0)
    self.run_process([FILE_PACKAGER, 'test.data', '--quiet', '--preload', 'data1.txt', '--preload', 'subdir/data2.txt', '--js-output=immutable.js', '--separate-metadata', '--use-preload-cache'])
    # assert both file content and timestamp are the same as reference copy
    self.assertTextDataIdentical(orig_content, read_file('immutable.js'))
    self.assertEqual(orig_timestamp, os.path.getmtime('immutable.js'))
    # verify the content of metadata file is correct
    metadata = json.loads(read_file('immutable.js.metadata'))
    self.assertEqual(len(metadata['files']), 2)
    assert metadata['files'][0]['start'] == 0 and metadata['files'][0]['end'] == len('data1') and metadata['files'][0]['filename'] == '/data1.txt'
    assert metadata['files'][1]['start'] == len('data1') and metadata['files'][1]['end'] == len('data1') + len('data2') and metadata['files'][1]['filename'] == '/subdir/data2.txt'
    assert metadata['remote_package_size'] == len('data1') + len('data2')

    self.assertEqual(metadata['package_uuid'], 'sha256-53ddc03623f867c7d4a631ded19c2613f2cb61d47b6aa214f47ff3cc15445bcd')

    create_file('src.c', r'''
    #include <assert.h>
    #include <sys/stat.h>
    #include <stdio.h>

    int main() {
      struct stat buf;
      assert(stat("data1.txt", &buf) == 0);
      assert(stat("subdir/data2.txt", &buf) == 0);
      printf("done\n");
      return 0;
    }
    ''')
    self.do_runf('src.c', 'done\n', cflags=['--extern-pre-js=immutable.js', '-sFORCE_FILESYSTEM'])

  def test_file_packager_unicode(self):
    unicode_name = 'unicode…☃'
    try:
      ensure_dir(unicode_name)
    except OSError:
      print("we failed to even create a unicode dir, so on this OS, we can't test this")
      return
    full = os.path.join(unicode_name, 'data.txt')
    create_file(full, 'data')
    proc = self.run_process([FILE_PACKAGER, 'test.data', '--preload', full], stdout=PIPE, stderr=PIPE)
    assert len(proc.stdout), proc.stderr
    assert json.dumps(unicode_name) in proc.stdout, proc.stdout
    print(len(proc.stderr))

  def test_file_packager_directory_with_single_quote(self):
    single_quote_name = "direc'tory"
    ensure_dir(single_quote_name)
    full = os.path.join(single_quote_name, 'data.txt')
    create_file(full, 'data')
    proc = self.run_process([FILE_PACKAGER, 'test.data', '--preload', full], stdout=PIPE, stderr=PIPE)
    assert len(proc.stdout), proc.stderr
    # ensure not invalid JavaScript
    assert "'direc'tory'" not in proc.stdout
    assert json.dumps("direc'tory") in proc.stdout

  def test_file_packager_mention_FORCE_FILESYSTEM(self):
    MESSAGE = 'Remember to build the main file with `-sFORCE_FILESYSTEM` so that it includes support for loading this file package'
    create_file('data.txt', 'data1')
    # mention when running standalone
    err = self.run_process([FILE_PACKAGER, 'test.data', '--preload', 'data.txt'], stdout=PIPE, stderr=PIPE).stderr
    self.assertContained(MESSAGE, err)
    # do not mention from emcc
    err = self.run_process([EMCC, test_file('hello_world.c'), '--preload-file', 'data.txt'], stdout=PIPE, stderr=PIPE).stderr
    self.assertEqual(len(err), 0)

  def test_file_packager_returns_error_if_target_equal_to_jsoutput(self):
    MESSAGE = 'error: TARGET should not be the same value of --js-output'
    self.assert_fail([FILE_PACKAGER, 'test.data', '--js-output=test.data'], MESSAGE)

  def test_file_packager_returns_error_if_emcc_and_export_es6(self):
    MESSAGE = 'error: Can\'t use --export-es6 option together with --from-emcc since the code should be embedded within emcc\'s code'
    self.assert_fail([FILE_PACKAGER, 'test.data', '--export-es6', '--from-emcc'], MESSAGE)

  def test_file_packager_embed(self):
    create_file('data.txt', 'hello data')

    # Without --obj-output we issue a warning
    err = self.expect_fail([FILE_PACKAGER, 'test.data', '--embed', 'data.txt', '--js-output=data.js'])
    self.assertContained('error: --obj-output is required when using --embed', err)

    self.run_process([FILE_PACKAGER, 'test.data', '--embed', 'data.txt', '--obj-output=data.o'])

    create_file('test.c', '''
    #include <stdio.h>

    int main() {
      FILE* f = fopen("data.txt", "r");
      char buf[64];
      int rtn = fread(buf, 1, 64, f);
      buf[rtn] = '\\0';
      fclose(f);
      printf("%s\\n", buf);
      return 0;
    }
    ''')
    self.run_process([EMCC, 'test.c', 'data.o', '-sFORCE_FILESYSTEM'])
    output = self.run_js('a.out.js')
    self.assertContained('hello data', output)

  def test_file_packager_preload_and_embed(self):
    create_file('data.txt', 'hello data')
    self.assert_fail([FILE_PACKAGER, 'test.data', '--embed', 'data.txt', '--preload', 'data.txt'], 'file_packager: error: --preload and --embed are mutually exclusive (See https://github.com/emscripten-core/emscripten/issues/24803)')

  def test_file_packager_export_es6(self):
    create_file('smth.txt', 'hello data')
    self.run_process([FILE_PACKAGER, 'test.data', '--export-es6', '--preload', 'smth.txt', '--js-output=dataFileLoader.js'])

    create_file('test.c', '''
    #include <stdio.h>
    #include <emscripten.h>

    EMSCRIPTEN_KEEPALIVE int test_fun() {
      FILE* f = fopen("smth.txt", "r");
      char buf[64] = {0};
      int rtn = fread(buf, 1, 64, f);
      buf[rtn] = '\\0';
      fclose(f);
      printf("%s\\n", buf);
      return 0;
    }
    ''')
    self.run_process([EMCC, 'test.c', '-sFORCE_FILESYSTEM', '-sMODULARIZE', '-sEXPORT_ES6', '-o', 'moduleFile.js'])

    create_file('run.js', '''
    import loadDataFile from './dataFileLoader.js'
    import {default as loadModule} from './moduleFile.js'

    var module = await loadModule();
    await loadDataFile(module);
    module._test_fun();
    ''')

    self.assertContained('hello data', self.run_js('run.js'))

  @crossplatform
  @parameterized({
    '': (False,),
    'embed': (True,),
  })
  def test_file_packager_depfile(self, embed):
    create_file('data1.txt', 'data1')
    ensure_dir('subdir')
    create_file('subdir/data2.txt', 'data2')

    if embed:
      self.run_process([FILE_PACKAGER, 'test.data', '--obj-output=out.o', '--depfile=out.o.d', '--from-emcc', '--embed', '.'])
      output = read_file('out.o.d')
    else:
      self.run_process([FILE_PACKAGER, 'test.data', '--js-output=test.js', '--depfile=test.data.d', '--from-emcc', '--preload', '.'])
      output = read_file('test.data.d')
    file_packager = utils.normalize_path(utils.replace_suffix(FILE_PACKAGER, '.py'))
    file_packager = file_packager.replace(' ', '\\ ')
    lines = output.splitlines()
    split = lines.index(': \\')
    before, after = set(lines[:split]), set(lines[split + 1:])
    # Set comparison used because depfile is not order-sensitive.
    if embed:
      self.assertTrue('out.o \\' in before)
    else:
      self.assertTrue('test.data \\' in before)
      self.assertTrue('test.js \\' in before)
    self.assertTrue(file_packager + ' \\' in after)
    self.assertTrue('. \\' in after)
    self.assertTrue('./data1.txt \\' in after)
    self.assertTrue('./subdir \\' in after)
    self.assertTrue('./subdir/data2.txt \\' in after)

  def test_file_packager_modularize(self):
    create_file('somefile.txt', 'hello world')
    self.run_process([FILE_PACKAGER, 'test.data', '--js-output=embed.js', '--preload', 'somefile.txt'])

    create_file('main.c', r'''
      #include <assert.h>
      #include <stdio.h>
      int main() {
        FILE *f = fopen("somefile.txt", "r");
        assert(f);
        char buf[20] = { 0 };
        int rtn = fread(buf, 1, 20, f);
        fclose(f);
        printf("|%s|\n", buf);
        return 0;
      }
    ''')

    create_file('post.js', 'MyModule(Module).then(() => console.log("done"));')

    self.do_runf('main.c', '|hello world|', cflags=['--extern-pre-js=embed.js', '--extern-post-js=post.js', '-sMODULARIZE', '-sEXPORT_NAME=MyModule', '-sFORCE_FILESYSTEM'])

  def test_preprocess(self):
    # Pass -Werror to prevent regressions such as https://github.com/emscripten-core/emscripten/pull/9661
    out = self.run_process([EMCC, test_file('hello_world.c'), '-E', '-Werror'], stdout=PIPE).stdout
    self.assertNotExists('a.out.js')
    self.assertNotExists('a.out')
    # Test explicitly that the output contains a line typically written by the preprocessor.
    self.assertContained('# 1 ', out)
    self.assertContained('hello_world.c"', out)
    self.assertContained('printf("Hello, world!', out)

  def test_preprocess_multi(self):
    out = self.run_process([EMCC, test_file('hello_world.c'), test_file('hello_world.c'), '-E'], stdout=PIPE).stdout
    self.assertEqual(out.count('printf("Hello, world!'), 2)

  def test_syntax_only_valid(self):
    result = self.run_process([EMCC, test_file('hello_world.c'), '-fsyntax-only'], stdout=PIPE, stderr=STDOUT)
    self.assertEqual(result.stdout, '')
    self.assertNotExists('a.out.js')
    # Even with `-c` and/or `-o`, `-fsyntax-only` should not produce any output
    self.run_process([EMCC, test_file('hello_world.c'), '-fsyntax-only', '-c', '-o', 'out.o'])
    self.assertNotExists('out.o')

  def test_syntax_only_invalid(self):
    create_file('src.c', 'int main() {')
    self.assert_fail([EMCC, 'src.c', '-fsyntax-only'], "src.c:1:13: error: expected '}'")
    self.assertNotExists('a.out.js')

  # `demangle` is a legacy JS function on longer used by emscripten
  # TODO(sbc): Remove `demangle` and this test.
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26197')
  def test_demangle(self):
    create_file('src.cpp', '''
      #include <stdio.h>
      #include <emscripten.h>

      EM_JS_DEPS(deps, "$demangle,$stackTrace");

      void two(char c) {
        EM_ASM(out(stackTrace()));
      }
      void one(int x) {
        two(x % 17);
      }
      int main() {
        EM_ASM(out(demangle('__Znwm'))); // check for no aborts
        EM_ASM(out(demangle('_main')));
        EM_ASM(out(demangle('__Z2f2v')));
        EM_ASM(out(demangle('__Z12abcdabcdabcdi')));
        EM_ASM(out(demangle('__ZL12abcdabcdabcdi')));
        EM_ASM(out(demangle('__Z4testcsifdPvPiPc')));
        EM_ASM(out(demangle('__ZN4test5moarrEcslfdPvPiPc')));
        EM_ASM(out(demangle('__ZN4Waka1f12a234123412345pointEv')));
        EM_ASM(out(demangle('__Z3FooIiEvv')));
        EM_ASM(out(demangle('__Z3FooIidEvi')));
        EM_ASM(out(demangle('__ZN3Foo3BarILi5EEEvv')));
        EM_ASM(out(demangle('__ZNK10__cxxabiv120__si_class_type_info16search_below_dstEPNS_19__dynamic_cast_infoEPKvib')));
        EM_ASM(out(demangle('__Z9parsewordRPKciRi')));
        EM_ASM(out(demangle('__Z5multiwahtjmxyz')));
        EM_ASM(out(demangle('__Z1aA32_iPA5_c')));
        EM_ASM(out(demangle('__ZN21FWakaGLXFleeflsMarfooC2EjjjPKvbjj')));
        EM_ASM(out(demangle('__ZN5wakaw2Cm10RasterBaseINS_6watwat9PolocatorEE8merbine1INS4_2OREEEvPKjj'))); // we get this wrong, but at least emit a '?'
        one(17);
        return 0;
      }
    ''')

    # full demangle support

    self.run_process([EMXX, 'src.cpp', '-sEXPORTED_FUNCTIONS=_main,_free,___cxa_demangle'])
    output = self.run_js('a.out.js')
    self.assertContained('''operator new(unsigned long)
_main
f2()
abcdabcdabcd(int)
abcdabcdabcd(int)
test(char, short, int, float, double, void*, int*, char*)
test::moarr(char, short, long, float, double, void*, int*, char*)
Waka::f::a23412341234::point()
void Foo<int>()
void Foo<int, double>(int)
void Foo::Bar<5>()
__cxxabiv1::__si_class_type_info::search_below_dst(__cxxabiv1::__dynamic_cast_info*, void const*, int, bool) const
parseword(char const*&, int, int&)
multi(wchar_t, signed char, unsigned char, unsigned short, unsigned int, unsigned long, long long, unsigned long long, ...)
a(int [32], char (*) [5])
FWakaGLXFleeflsMarfoo::FWakaGLXFleeflsMarfoo(unsigned int, unsigned int, unsigned int, void const*, bool, unsigned int, unsigned int)
void wakaw::Cm::RasterBase<wakaw::watwat::Polocator>::merbine1<wakaw::Cm::RasterBase<wakaw::watwat::Polocator>::OR>(unsigned int const*, unsigned int)
''', output)
    # test for multiple functions in one stack trace
    self.run_process([EMXX, 'src.cpp', '-sEXPORTED_FUNCTIONS=_main,_free,___cxa_demangle', '-g'])
    output = self.run_js('a.out.js')
    self.assertIn('one(int)', output)
    self.assertIn('two(char)', output)

  def test_demangle_cpp(self):
    create_file('src.cpp', '''
      #include <stdio.h>
      #include <emscripten.h>
      #include <cxxabi.h>
      #include <assert.h>

      int main() {
        char out[256];
        int status = 1;
        size_t length = 255;
        abi::__cxa_demangle("_ZN4Waka1f12a234123412345pointEv", out, &length, &status);
        assert(status == 0);
        printf("%s\\n", out);
        return 0;
      }
    ''')

    self.do_runf('src.cpp', 'Waka::f::a23412341234::point()')

  # Test that malloc() -> OOM -> abort() -> stackTrace() -> jsStackTrace()
  # cycle will not cycle back to malloc to produce an infinite loop.
  def test_demangle_malloc_infinite_loop_crash(self):
    self.run_process([EMXX, test_file('malloc_demangle_infinite_loop.cpp'), '-g', '-sABORTING_MALLOC'])
    output = self.run_js('a.out.js', assert_returncode=NON_ZERO)
    if output.count('Cannot enlarge memory arrays') > 5:
      print(output)
    self.assertLess(output.count('Cannot enlarge memory arrays'), 6)

  @requires_node
  def test_module_exports_with_closure(self):
    # This test checks that module.export is retained when JavaScript
    # is minified by compiling with --closure 1
    # This is important as if module.export is not present the Module
    # object will not be visible to node.js

    # compile without --closure=1
    self.run_process([EMCC, test_file('module_exports/test.c'),
                      '-o', 'test.js', '-O2',
                      '-sEXPORTED_FUNCTIONS=_bufferTest,_malloc,_free',
                      '-sEXPORTED_RUNTIME_METHODS=ccall,cwrap,HEAPU8',
                      '-sWASM_ASYNC_COMPILATION=0'])

    # Check that test.js compiled without --closure=1 contains "module['exports'] = Module;"
    self.assertContained('module["exports"]=Module', read_file('test.js'))

    # Check that main.js (which requires test.js) completes successfully when run in node.js
    # in order to check that the exports are indeed functioning correctly.
    copy_asset('module_exports/main.js')
    self.assertContained('bufferTest finished', self.run_js('main.js'))

    # Delete test.js again and check it's gone.
    delete_file('test.js')

    # compile with --closure=1
    self.run_process([EMCC, test_file('module_exports/test.c'),
                      '-o', 'test.js', '-O2', '--closure=1',
                      '-sEXPORTED_FUNCTIONS=_bufferTest,_malloc,_free',
                      '-sEXPORTED_RUNTIME_METHODS=ccall,cwrap,HEAPU8',
                      '-sWASM_ASYNC_COMPILATION=0'])

    # Check that test.js compiled with --closure 1 contains "module.exports", we want to verify that
    # "module['exports']" got minified to "module.exports" when compiling with --closure 1
    self.assertContained('module.exports=', read_file('test.js'))

    # Check that main.js (which requires test.js) completes successfully when run in node.js
    # in order to check that the exports are indeed functioning correctly.
    self.assertContained('bufferTest finished', self.run_js('main.js'))

  @requires_node
  def test_exported_runtime_methods(self):
    # Test with node.js that the EXPORTED_RUNTIME_METHODS setting is
    # considered by libraries
    create_file('count.c', '''
      #include <string.h>
      int count(const char *str) {
        return (int)strlen(str);
      }
    ''')

    create_file('index.js', '''
      const count = require('./count.js');
      console.log(count.FS_writeFile);
      count.onRuntimeInitialized = () => {
        if (count.wasmExports && 'count' in count.wasmExports) {
          console.log('wasmExports found');
        } else {
          console.log('wasmExports NOT found');
        }
      };
    ''')

    self.run_process([EMCC, 'count.c', '-sFORCE_FILESYSTEM', '-sEXPORTED_FUNCTIONS=_count',
                      '-sEXPORTED_RUNTIME_METHODS=FS_writeFile,wasmExports', '-o', 'count.js'])

    # Check that the Module.FS_writeFile exists
    out = self.run_js('index.js')
    self.assertNotContained('undefined', out)
    self.assertContained('wasmExports found', out)

    self.run_process([EMCC, 'count.c', '-sFORCE_FILESYSTEM', '-o', 'count.js'])

    # Check that the Module.FS_writeFile is not exported
    out = self.run_js('index.js', assert_returncode=NON_ZERO)
    self.assertContained("Aborted('FS_writeFile' was not exported. add it to EXPORTED_RUNTIME_METHODS", out)

  def test_exported_runtime_methods_from_js_library(self):
    create_file('pre.js', '''
      Module.onRuntimeInitialized = () => {
        out(Module.ptrToString(88));
        out('done');
      };
    ''')
    self.do_runf('hello_world.c', 'done\n', cflags=['--pre-js=pre.js', '-sEXPORTED_RUNTIME_METHODS=ptrToString'])

    # Same again but using EXPORTED_FUNCTIONS instead.
    self.do_runf('hello_world.c', 'done\n', cflags=['--pre-js=pre.js', '-sEXPORTED_FUNCTIONS=ptrToString,_main'])

    # Check that when ptrToString is not exported we get a reasonable error message
    err = self.do_runf('hello_world.c', assert_returncode=NON_ZERO, cflags=['--pre-js=pre.js'])
    self.assertContained("Aborted('ptrToString' was not exported. add it to EXPORTED_RUNTIME_METHODS", err)

  @crossplatform
  @all_engines
  def test_fs_stream_proto(self):
    create_file('src.c', br'''
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>

int main()
{
    long file_size = 0;
    int h = open("src.c", O_RDONLY, 0666);
    if (0 != h) {
        FILE* file = fdopen(h, "rb");
        if (0 != file) {
            fseek(file, 0, SEEK_END);
            file_size = ftell(file);
            fseek(file, 0, SEEK_SET);
        } else {
            printf("fdopen() failed: %s\n", strerror(errno));
            return 10;
        }
        close(h);
        printf("File size: %ld\n", file_size);
    } else {
        printf("open() failed: %s\n", strerror(errno));
        return 10;
    }
    return 0;
}
''', binary=True)
    self.do_runf('src.c', 'File size: 682', cflags=['--embed-file', 'src.c'])

  @requires_pthreads
  def test_node_emscripten_num_logical_cores(self):
    # Test with node.js that the emscripten_num_logical_cores method is working
    create_file('src.c', r'''
#include <emscripten/threading.h>
#include <stdio.h>
#include <assert.h>

int main() {
  int num = emscripten_num_logical_cores();
  assert(num != 0);
  puts("ok");
}
''')
    self.do_runf('src.c', 'ok')

  def test_proxyfs(self):
    # This test supposes that 3 different programs share the same directory and files.
    # The same JS object is not used for each of them
    # But 'require' function caches JS objects.
    # If we just load same js-file multiple times like following code,
    # these programs (m0,m1,m2) share the same JS object.
    #
    #   var m0 = require('./test_proxyfs.js');
    #   var m1 = require('./test_proxyfs.js');
    #   var m2 = require('./test_proxyfs.js');
    #
    # To separate js-objects for each of them, following 'require' use different js-files.
    #
    #   var m0 = require('./test_proxyfs.js');
    #   var m1 = require('./test_proxyfs1.js');
    #   var m2 = require('./test_proxyfs2.js');

    create_file('proxyfs_pre.js', r'Module["noInitialRun"]=true;')
    create_file('proxyfs_embed.txt', 'test\n')

    self.run_process([EMCC, '-o', 'test_proxyfs.js', test_file('other/test_proxyfs.c'),
                      '--embed-file', 'proxyfs_embed.txt', '--pre-js', 'proxyfs_pre.js',
                      '-sEXPORTED_RUNTIME_METHODS=ccall,cwrap,FS,PROXYFS',
                      '-lproxyfs.js',
                      '-sWASM_ASYNC_COMPILATION=0'])
    # Following shutil.copy just prevent 'require' of node.js from caching js-object.
    # See https://nodejs.org/api/modules.html
    shutil.copy('test_proxyfs.js', 'test_proxyfs1.js')
    shutil.copy('test_proxyfs.js', 'test_proxyfs2.js')
    copy_asset('other/test_proxyfs_main.js')
    out = self.run_js('test_proxyfs_main.js')
    section = "child m1 reads and writes local file."
    self.assertContained(section + ":m1 read embed:test", out)
    self.assertContained(section + ":m1 write:", out)
    self.assertContained(section + ":m1 read:test0_1", out)
    section = "child m2 reads and writes local file."
    self.assertContained(section + ":m2 read embed:test", out)
    self.assertContained(section + ":m2 write:", out)
    self.assertContained(section + ":m2 read:test0_2", out)
    section = "child m1 reads local file."
    self.assertContained(section + ":m1 read:test0_1", out)
    section = "parent m0 accesses children's file."
    self.assertContained(section + ":m0 access existing:access=0", out)
    self.assertContained(section + ":m0 access absent:access=-1", out)
    section = "child m1 accesses local file."
    self.assertContained(section + ":m1 access existing:access=0", out)
    self.assertContained(section + ":m1 access absent:access=-1", out)
    section = "parent m0 reads and writes local and children's file."
    self.assertContained(section + ":m0 read embed:test", out)
    self.assertContained(section + ":m0 read m1:test0_1", out)
    self.assertContained(section + ":m0 read m2:test0_2", out)
    section = "m0,m1 and m2 verify local files."
    self.assertContained(section + ":m0 write:", out)
    self.assertContained(section + ":m0 read:test0_0", out)
    self.assertContained(section + ":m1 read:test0_1", out)
    self.assertContained(section + ":m2 read:test0_2", out)
    self.assertContained(section + ":m0 read embed:test", out)
    self.assertContained(section + ":m1 read embed:test", out)
    self.assertContained(section + ":m2 read embed:test", out)
    section = "parent m0 writes and reads children's files."
    self.assertContained(section + ":m0 write m1:", out)
    self.assertContained(section + ":m0 read m1:test1", out)
    self.assertContained(section + ":m0 write m2:", out)
    self.assertContained(section + ":m0 read m2:test2", out)
    self.assertContained(section + ":m1 read:test1", out)
    self.assertContained(section + ":m2 read:test2", out)
    self.assertContained(section + ":m0 read m0:test0_0", out)
    section = "parent m0 renames a file in child fs."
    self.assertContained(section + ":renamed file accessible by the new name:true", out)
    self.assertContained(section + ":renamed file accessible by the old name:false", out)
    section = "test seek."
    self.assertContained(section + ":file size:6", out)

  def test_dependency_file(self):
    # Issue 1732: -MMD (and friends) create dependency files that need to be
    # copied from the temporary directory.

    create_file('test.c', r'''
      #include "test.h"

      void my_function()
      {
      }
    ''')
    create_file('test.h', r'''
      void my_function();
    ''')

    self.run_process([EMCC, '-MMD', '-c', 'test.c', '-o', 'test.o'])
    self.assertExists('test.d')
    deps = read_file('test.d')
    # Look for ': ' instead of just ':' to not confuse C:\path\ notation with make "target: deps" rule. Not perfect, but good enough for this test.
    head, tail = deps.split(': ', 2)
    self.assertContained('test.o', head)
    self.assertContained('test.c', tail)
    self.assertContained('test.h', tail)

  def test_dependency_file_2(self):
    copy_asset('hello_world.c', 'a.c')
    self.run_process([EMCC, 'a.c', '-MMD', '-MF', 'test.d', '-c'])
    self.assertContained('a.o: a.c\n', read_file('test.d'))

    copy_asset('hello_world.c', 'a.c')
    self.run_process([EMCC, 'a.c', '-MMD', '-MF', 'test2.d', '-c', '-o', 'test.o'])
    self.assertContained('test.o: a.c\n', read_file('test2.d'))

    copy_asset('hello_world.c', 'a.c')
    ensure_dir('obj')
    self.run_process([EMCC, 'a.c', '-MMD', '-MF', 'test3.d', '-c', '-o', 'obj/test.o'])
    self.assertContained('obj/test.o: a.c\n', read_file('test3.d'))

  def test_compilation_database(self):
    copy_asset('hello_world.c', 'a.c')
    self.run_process([EMCC, 'a.c', '-MJ', 'hello.json', '-c', '-o', 'test.o'])
    self.assertContained('"file": "a.c", "output": "test.o"', read_file('hello.json'))

  def test_override_stub(self):
    self.do_other_test('test_override_stub.c')

  def test_EMCC_BUILD_DIR(self):
    # EMCC_BUILD_DIR was necessary in the past since we used to force the cwd to be src/ for
    # technical reasons.
    create_file('lib.js', r'''
printErr('EMCC_BUILD_DIR: ' + process.env.EMCC_BUILD_DIR);
printErr('CWD: ' + process.cwd());
''')
    err = self.run_process([EMXX, test_file('hello_world.c'), '--js-library', 'lib.js'], stderr=PIPE).stderr
    self.assertContained('EMCC_BUILD_DIR: ' + os.path.realpath(os.path.normpath(self.get_dir())), err)
    self.assertContained('CWD: ' + os.path.realpath(os.path.normpath(self.get_dir())), err)

  def test_gnulib_float_h(self):
    # Check the static assertions presend in gnulib float+.h.
    self.run_process([EMCC, '-o', 'out.pch', '-c', test_file('third_party/gnulib/float+.h')])

  def test_output_is_dir(self):
    ensure_dir('out_dir')
    self.assert_fail([EMCC, '-c', test_file('hello_world.c'), '-o', 'out_dir/'], 'error: unable to open output file')

  def test_doublestart_bug(self):
    create_file('code.c', r'''
#include <stdio.h>
#include <emscripten.h>

void main_loop(void) {
    static int cnt = 0;
    if (++cnt >= 10) emscripten_cancel_main_loop();
}

int main(void) {
    printf("This should only appear once.\n");
    emscripten_set_main_loop(main_loop, 10, 0);
    return 0;
}
''')

    create_file('pre.js', r'''
Module["preRun"] = () => {
  addRunDependency('test_run_dependency');
  removeRunDependency('test_run_dependency');
};
''')

    self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', '$addRunDependency,$removeRunDependency')
    output = self.do_runf('code.c', cflags=['--pre-js=pre.js'])
    self.assertEqual(output.count('This should only appear once.'), 1)

  def test_module_print(self):
    create_file('code.c', r'''
#include <stdio.h>
int main(void) {
  printf("123456789\n");
  return 0;
}
''')

    create_file('pre.js', r'''
Module.print = (x) => { throw '<{(' + x + ')}>' };
''')

    self.run_process([EMCC, 'code.c', '--pre-js', 'pre.js'])
    output = self.run_js('a.out.js', assert_returncode=NON_ZERO)
    self.assertContained(r'<{(123456789)}>', output)

  def test_precompiled_headers_warnings(self):
    # Check that we don't have any underlying warnings from clang, this can happen if we
    # pass any link flags to when building a pch.
    create_file('header.h', '#define X 5\n')
    self.run_process([EMCC, '-Werror', '-xc++-header', 'header.h'])

  @parameterized({
    'gch': ['gch'],
    'pch': ['pch'],
  })
  @crossplatform
  def test_precompiled_headers(self, suffix):
    create_file('header.h', '#define X 5\n')
    self.run_process([EMCC, '-xc++-header', 'header.h', '-c'])
    self.assertExists('header.h.pch') # default output is pch
    if suffix != 'pch':
      self.run_process([EMCC, '-xc++-header', 'header.h', '-o', 'header.h.' + suffix])
      self.assertBinaryEqual('header.h.pch', 'header.h.' + suffix)
      delete_file('header.h.pch')

    create_file('src.cpp', r'''
#include <stdio.h>
int main() {
  printf("|%d|\n", X);
  return 0;
}
''')
    self.run_process([EMCC, 'src.cpp', '-include', 'header.h'])

    output = self.run_js('a.out.js')
    self.assertContained('|5|', output)

    # also verify that the gch is actually used
    err = self.run_process([EMXX, 'src.cpp', '-include', 'header.h', '-Xclang', '-print-stats'], stderr=PIPE).stderr
    self.assertContained(r'PCH/Modules Loaded:\nModule: .*header.h.' + suffix, err, regex=True)
    # and sanity check it is not mentioned when not
    delete_file('header.h.' + suffix)
    err = self.run_process([EMXX, 'src.cpp', '-include', 'header.h', '-Xclang', '-print-stats'], stderr=PIPE).stderr
    self.assertNotContained(r'PCH/Modules Loaded:', err, regex=True)

    # with specified target via -o
    delete_file('header.h.' + suffix)
    self.run_process([EMCC, '-xc++-header', 'header.h', '-o', 'my.' + suffix])
    self.assertExists('my.' + suffix)

    # -include-pch flag
    self.run_process([EMCC, '-xc++-header', 'header.h', '-o', 'header.h.' + suffix])
    self.run_process([EMXX, 'src.cpp', '-include-pch', 'header.h.' + suffix])
    output = self.run_js('a.out.js')
    self.assertContained('|5|', output)

  @crossplatform
  def test_on_abort(self):
    expected_output = 'Module.onAbort was called'

    def add_on_abort_and_verify(extra=''):
      js = read_file('a.out.js')
      with open('a.out.js', 'w', encoding='utf-8') as f:
        f.write("var Module = { onAbort: () => console.log('%s') };\n" % expected_output)
        f.write(extra + '\n')
        f.write(js)
      self.assertContained(expected_output, self.run_js('a.out.js', assert_returncode=NON_ZERO))

    # test direct abort() C call

    create_file('src.c', '''
        #include <stdlib.h>
        int main() {
          abort();
        }
      ''')
    self.run_process([EMCC, 'src.c', '-sWASM_ASYNC_COMPILATION=0'])
    add_on_abort_and_verify()

    # test direct abort() JS call

    create_file('src.c', '''
        #include <emscripten.h>
        int main() {
          EM_ASM({ abort() });
        }
      ''')
    self.run_process([EMCC, 'src.c', '-sWASM_ASYNC_COMPILATION=0'])
    add_on_abort_and_verify()

    # test throwing in an abort handler, and catching that

    create_file('src.c', '''
        #include <emscripten.h>
        int main() {
          EM_ASM({
            try {
              out('first');
              abort();
            } catch (e) {
              out('second');
              abort();
              throw e;
            }
          });
        }
      ''')
    self.run_process([EMCC, 'src.c', '-sWASM_ASYNC_COMPILATION=0'])
    js = read_file('a.out.js')
    with open('a.out.js', 'w', encoding='utf-8') as f:
      f.write("var Module = { onAbort: () => { console.log('%s'); throw 're-throw'; } };\n" % expected_output)
      f.write(js)
    out = self.run_js('a.out.js', assert_returncode=NON_ZERO)
    print(out)
    self.assertContained(expected_output, out)
    self.assertContained('re-throw', out)
    self.assertContained('first', out)
    self.assertContained('second', out)
    self.assertEqual(out.count(expected_output), 2)

    # test an abort during startup
    self.run_process([EMCC, test_file('hello_world.c')])
    os.remove('a.out.wasm') # trigger onAbort by intentionally causing startup to fail
    add_on_abort_and_verify()

  @also_with_wasm2js
  @parameterized({
    '': (1,),
    'disabled': (0,),
  })
  @parameterized({
    '': ([],),
    '01': (['-O1', '-g2'],),
    'O2': (['-O2', '-g2', '-flto'],),
  })
  def test_exit_runtime(self, do_exit, opts):
    create_file('code.cpp', r'''
#include <stdio.h>

template<int x>
struct Waste {
  Waste() {
    printf("coming around %d\n", x);
  }
  ~Waste() {
    printf("going away %d\n", x);
  }
};

Waste<1> w1;
Waste<2> w2;
Waste<3> w3;
Waste<4> w4;
Waste<5> w5;

int main(int argc, char **argv) {
  return 0;
}
    ''')

    cmd = [EMXX, 'code.cpp', f'-sEXIT_RUNTIME={do_exit}'] + opts + self.get_cflags()
    if self.is_wasm():
      cmd += ['--profiling-funcs'] # for function names
    self.run_process(cmd)
    output = self.run_js('a.out.js')
    src = read_file('a.out.js')
    if self.is_wasm():
      src += '\n' + self.get_wasm_text('a.out.wasm')
    self.assertContained('coming around', output)
    self.assertContainedIf('going away', output, do_exit)
    # The wasm backend uses atexit to register destructors when
    # constructors are called  There is currently no way to exclude
    # these destructors from the wasm binary.
    # TODO(sbc): Re-enabled these assertions once the wasm backend
    # is able to eliminate these.
    # assert ('atexit(' in src) == exit, 'atexit should not appear in src when EXIT_RUNTIME=0'
    # assert ('_ZN5WasteILi2EED' in src) == exit, 'destructors should not appear if no exit:\n' + src

  @is_slow_test
  def test_no_exit_runtime_warnings_flush(self):
    # check we warn if there is unflushed info
    create_file('code.c', r'''
#include <stdio.h>
#include <emscripten/emscripten.h>
int main(int argc, char **argv) {
  printf("hello\n");
  printf("world"); // no newline, not flushed
#if FLUSH
  printf("\n");
#endif
#if KEEPALIVE
  emscripten_exit_with_live_runtime();
#endif
}
''')
    create_file('code.cpp', r'''
#include <iostream>
#include <emscripten/emscripten.h>
int main() {
  using namespace std;
  cout << "hello" << std::endl;
  cout << "world"; // no newline, not flushed
#if FLUSH
  std::cout << std::endl;
#endif
#if KEEPALIVE
  emscripten_exit_with_live_runtime();
#endif
}
''')
    warning = 'warning: stdio streams had content in them that was not flushed. you should set EXIT_RUNTIME to 1'

    def test(cxx, no_exit, assertions, flush=0, keepalive=0, filesystem=1):
      if cxx:
        cmd = [EMXX, 'code.cpp']
      else:
        cmd = [EMCC, 'code.c']
      print('%s: no_exit=%d assertions=%d flush=%d keepalive=%d filesystem=%d' % (cmd[1], no_exit, assertions, flush, keepalive, filesystem))
      cmd += ['-sEXIT_RUNTIME=%d' % (1 - no_exit), '-sASSERTIONS=%d' % assertions]
      if flush:
        cmd += ['-DFLUSH']
      if keepalive:
        cmd += ['-DKEEPALIVE']
      if not filesystem:
        cmd += ['-sNO_FILESYSTEM']
      self.run_process(cmd)
      output = self.run_js('a.out.js')
      exit = 1 - no_exit
      self.assertContained('hello', output)
      self.assertContainedIf('world', output, exit or flush)
      self.assertContainedIf(warning, output, no_exit and assertions and not flush and not keepalive)

    # Run just one test with KEEPALIVE set.  In this case we don't expect to see any kind
    # of warning because we are explicitly requesting the runtime stay alive for later use.
    test(cxx=0, no_exit=1, assertions=1, keepalive=1)
    test(cxx=0, no_exit=1, assertions=1, filesystem=0)

    for cxx in (0, 1):
      for no_exit in (0, 1):
        for assertions in (0, 1):
          for flush in (0, 1):
            test(cxx, no_exit, assertions, flush)

  def test_no_exit_runtime_strict(self):
    self.do_other_test('test_no_exit_runtime_strict.c', cflags=['-sSTRICT'])

  def test_extra_opt_levels(self):
    # Opt levels that we don't tend to test elsewhere
    for opt in ('-Og', '-Ofast'):
      print(opt)
      proc = self.run_process([EMCC, '-v', test_file('hello_world.c'), opt], stderr=PIPE)
      self.assertContained(opt, proc.stderr)
      self.assertContained('Hello, world!', self.run_js('a.out.js'))

  @parameterized({
    '': [[]],
    'O1': [['-O1']],
  })
  def test_fs_after_main(self, args):
    self.do_runf('test_fs_after_main.c', 'Test passed.', cflags=args)

  def test_oz_size(self):
    sizes = {}
    for name, args in [
        ('0', []),
        ('1', ['-O1']),
        ('2', ['-O2']),
        ('s', ['-Os']),
        ('z', ['-Oz']),
        ('3', ['-O3']),
        ('g', ['-Og']),
      ]:
      print(name, args)
      self.clear()
      self.run_process([EMCC, '-c', path_from_root('system/lib/dlmalloc.c')] + args)
      sizes[name] = os.path.getsize('dlmalloc.o')
    print(sizes)
    opt_min = min(sizes['1'], sizes['2'], sizes['3'], sizes['s'], sizes['z'], sizes['g'])
    opt_max = max(sizes['1'], sizes['2'], sizes['3'], sizes['s'], sizes['z'], sizes['g'])
    # 'opt builds are all fairly close'
    self.assertLess(opt_min - opt_max, opt_max * 0.1)
    # unopt build is quite larger'
    self.assertGreater(sizes['0'], (1.20 * opt_max))

  @disabled('relies on fastcomp EXIT_RUNTIME=0 optimization not implemented/disabled')
  def test_global_inits(self):
    create_file('inc.h', r'''
#include <stdio.h>

template<int x>
struct Waste {
  int state;
  Waste() : state(10) {}
  void test(int a) {
    printf("%d\n", a + state);
  }
  ~Waste() {
    printf("going away %d\n", x);
  }
};

Waste<3> *getMore();
''')

    create_file('main.cpp', r'''
#include "inc.h"

Waste<1> mw1;
Waste<2> mw2;

int main(int argc, char **argv) {
  printf("argc: %d\n", argc);
  mw1.state += argc;
  mw2.state += argc;
  mw1.test(5);
  mw2.test(6);
  getMore()->test(0);
  return 0;
}
''')

    create_file('side.cpp', r'''
#include "inc.h"

Waste<3> sw3;

Waste<3> *getMore() {
  return &sw3;
}
''')

    for opts, has_global in [
      (['-O2', '-g', '-sEXIT_RUNTIME'], True),
      # no-exit-runtime removes the atexits, and then globalgce can work
      # it's magic to remove the global initializer entirely
      (['-O2', '-g'], False),
      (['-Os', '-g', '-sEXIT_RUNTIME'], True),
      (['-Os', '-g'], False),
      (['-O2', '-g', '-flto', '-sEXIT_RUNTIME'], True),
      (['-O2', '-g', '-flto'], False),
    ]:
      print(opts, has_global)
      self.run_process([EMXX, 'main.cpp', '-c'] + opts)
      self.run_process([EMXX, 'side.cpp', '-c'] + opts)
      self.run_process([EMCC, 'main.o', 'side.o'] + opts)
      self.run_js('a.out.js')
      src = read_file('a.out.js')
      self.assertContained('argc: 1\n16\n17\n10\n', self.run_js('a.out.js'))
      self.assertContainedIf('globalCtors', src, has_global)

  @requires_native_clang
  @crossplatform
  def test_bad_triple(self):
    # compile a minimal program, with as few dependencies as possible, as
    # native building on CI may not always work well
    create_file('minimal.c', 'int main() { return 0; }')
    self.run_process([CLANG_CC, 'minimal.c', '-target', 'x86_64-linux', '-c', '-emit-llvm', '-o', 'a.bc'] + clang_native.get_clang_native_args(), env=clang_native.get_clang_native_env())
    expected = 'error: overriding the module target triple with wasm32-unknown-emscripten [-Werror,-Woverride-module]'
    self.assert_fail([EMCC, '-Werror', 'a.bc'], expected)

  def test_valid_abspath(self):
    # Test whether abspath warning appears
    abs_include_path = os.path.abspath(self.get_dir())
    err = self.run_process([EMCC, '-I%s' % abs_include_path, '-Wwarn-absolute-paths', test_file('hello_world.c')], stderr=PIPE).stderr
    warning = '-I or -L of an absolute path "-I%s" encountered. If this is to a local system header/library, it may cause problems (local system files make sense for compiling natively on your system, but not necessarily to JavaScript).' % abs_include_path
    self.assertContained(warning, err)

    # Passing an absolute path to a directory inside the emscripten tree is always ok and should not issue a warning.
    abs_include_path = TEST_ROOT
    err = self.run_process([EMCC, '-I%s' % abs_include_path, '-Wwarn-absolute-paths', test_file('hello_world.c')], stderr=PIPE).stderr
    warning = '-I or -L of an absolute path "-I%s" encountered. If this is to a local system header/library, it may cause problems (local system files make sense for compiling natively on your system, but not necessarily to JavaScript).' % abs_include_path
    self.assertNotContained(warning, err)

    # Hide warning for this include path
    err = self.run_process([EMCC, '--valid-abspath', abs_include_path, '-I%s' % abs_include_path, '-Wwarn-absolute-paths', test_file('hello_world.c')], stderr=PIPE).stderr
    self.assertNotContained(warning, err)

  def test_valid_abspath_2(self):
    if WINDOWS:
      abs_include_path = 'C:\\nowhere\\at\\all'
    else:
      abs_include_path = '/nowhere/at/all'
    cmd = [EMCC, test_file('hello_world.c'), '--valid-abspath', abs_include_path, '-I%s' % abs_include_path]
    self.run_process(cmd)
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

  @crossplatform
  @parameterized({
    'O2': [['-O2']],
    'O3': [['-O3']],
  })
  @parameterized({
    '': [1],
    'wasm2js': [0],
    'wasm2js_2': [2],
  })
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26197')
  @no_deno('https://github.com/emscripten-core/emscripten/issues/26234')
  def test_symbol_map(self, opts, wasm):
    def read_symbol_map(symbols_file):
      symbols = read_file(symbols_file)
      lines = [line.split(':', 1) for line in symbols.splitlines()]
      return lines

    def get_minified_middle(symbols_file):
      for minified, full in read_symbol_map(symbols_file):
        if full == 'middle':
          return minified
      return None

    def is_js_symbol_map(symbols_file):
      for _minified, full in read_symbol_map(symbols_file):
        # define symbolication file by JS specific entries
        if full in {'FUNCTION_TABLE', 'HEAP32'}:
          return True
      return False

    create_file('src.cpp', r'''
#include <emscripten.h>

namespace foo {

EMSCRIPTEN_KEEPALIVE void* cpp_func(int foo) {
  return 0;
}

}

EM_JS(int, run_js, (), {
  out(new Error().stack);
  return 0;
});

EMSCRIPTEN_KEEPALIVE
extern "C" void middle() {
  if (run_js()) {
    // fake recursion that is never reached, to avoid inlining in binaryen and LLVM
    middle();
  }
}

int main() {
  EM_ASM({ _middle() });
}
''')
    cmd = [EMXX, 'src.cpp', '--emit-symbol-map'] + opts
    if wasm != 1:
      cmd.append(f'-sWASM={wasm}')
    self.run_process(cmd)

    minified_middle = get_minified_middle('a.out.js.symbols')
    self.assertNotEqual(minified_middle, None, "Missing minified 'middle' function")
    if wasm:
      # stack traces are standardized enough that we can easily check that the
      # minified name is actually in the output
      stack_trace_reference = 'wasm-function[%s]' % minified_middle
      out = self.run_js('a.out.js')
      self.assertContained(stack_trace_reference, out)
      # make sure there are no symbols in the wasm itself
      wat = self.get_wasm_text('a.out.wasm')
      for func_start in ('(func $middle', '(func $_middle'):
        self.assertNotContained(func_start, wat)

    # Ensure symbols file type according to `-sWASM=` mode
    if wasm == 0:
      self.assertTrue(is_js_symbol_map('a.out.js.symbols'), 'Primary symbols file should store JS mappings')
    elif wasm == 1:
      self.assertFalse(is_js_symbol_map('a.out.js.symbols'), 'Primary symbols file should store Wasm mappings')
      if '-O2' in opts:
        self.assertFilesMatch(test_file('other/test_symbol_map.O2.symbols'), 'a.out.js.symbols')
      else:
        self.assertFilesMatch(test_file('other/test_symbol_map.O3.symbols'), 'a.out.js.symbols')
    elif wasm == 2:
      # special case when both JS and Wasm targets are created
      minified_middle_2 = get_minified_middle('a.out.wasm.js.symbols')
      self.assertNotEqual(minified_middle_2, None, "Missing minified 'middle' function")
      self.assertFalse(is_js_symbol_map('a.out.js.symbols'), 'Primary symbols file should store Wasm mappings')
      self.assertTrue(is_js_symbol_map('a.out.wasm.js.symbols'), 'Secondary symbols file should store JS mappings')

    # check we don't keep unnecessary debug info with wasm2js when emitting
    # a symbol map
    if wasm == 0:
      UNMINIFIED_HEAP8 = 'var HEAP8 = new '
      UNMINIFIED_MIDDLE = 'function middle'
      js = read_file('a.out.js')
      self.assertNotContained(UNMINIFIED_HEAP8, js)
      self.assertNotContained(UNMINIFIED_MIDDLE, js)
      # verify those patterns would exist with more debug info
      self.run_process(cmd + ['--profiling-funcs'])
      js = read_file('a.out.js')
      self.assertContained(UNMINIFIED_HEAP8, js)
      self.assertContained(UNMINIFIED_MIDDLE, js)

  @parameterized({
    '': [[]],
    # bigint support is interesting to test here because it changes which
    # binaryen tools get run, which can affect how debug info is kept around
    'nobigint': [['-sWASM_BIGINT=0']],
  })
  def test_symbol_map_output_size(self, args):
    # build with and without a symbol map and verify that the sizes are the
    # same. using a symbol map should add the map on the side, but not increase
    # the build size.
    # -Oz is used here to run as many optimizations as possible, to check for
    # any difference in how the optimizer operates
    self.run_process([EMCC, test_file('hello_world.c'), '-Oz', '-o', 'test1.js'] + args)
    self.run_process([EMCC, test_file('hello_world.c'), '-Oz', '-o', 'test2.js', '--emit-symbol-map'] + args)

    self.assertEqual(os.path.getsize('test1.js'), os.path.getsize('test2.js'))

    def get_code_section_size(filename):
      with webassembly.Module(filename) as module:
        return module.get_section(webassembly.SecType.CODE).size

    self.assertEqual(get_code_section_size('test1.wasm'), get_code_section_size('test2.wasm'))

  def test_bitcode_linking(self):
    # emcc used to be able to link bitcode together, but these days partial linking
    # always outputs an object file.
    self.run_process([EMCC, '-flto', '-c', test_file('hello_world.c')])
    self.assertExists('hello_world.o')
    self.run_process([EMCC, '-flto', '-r', 'hello_world.o', '-o', 'hello_world2.o'])
    is_bitcode('hello_world.o')
    building.is_wasm('hello_world2.o')

  @parameterized({
    '': ([],),
    'O1': (['-O2'],),
    'O2': (['-O2'],),
  })
  @parameterized({
    '': (True, False),
    'safe_heap': (True, True),
    'wasm2js': (False, False),
    'wasm2js_safe_heap': (False, True),
  })
  @parameterized({
    '': (False,),
    'emulate_casts': (True,),
  })
  @parameterized({
    '': (False,),
    'dylink': (True,),
  })
  def test_bad_function_pointer_cast(self, opts, wasm, safe, emulate_casts, dylink):
    create_file('src.cpp', r'''
#include <stdio.h>

typedef int (*callback) (int, ...);

int impl(int foo) {
  printf("Hello, world.\n");
  return 0;
}

int main() {
  volatile callback f = (callback) impl;
  f(0); /* This fails with or without additional arguments. */
  return 0;
}
''')

    # wasm2js is not compatible with dylink mode
    if not wasm and dylink:
      self.skipTest("wasm2js + dylink")
    cmd = [EMXX, 'src.cpp'] + opts
    if not wasm:
      cmd += ['-sWASM=0']
    if safe:
      cmd += ['-sSAFE_HEAP']
    if emulate_casts:
      cmd += ['-sEMULATE_FUNCTION_POINTER_CASTS']
    if dylink:
      cmd += ['-sMAIN_MODULE=2'] # disables asm-optimized safe heap
    self.run_process(cmd)
    returncode = 0 if emulate_casts or not wasm else NON_ZERO
    output = self.run_js('a.out.js', assert_returncode=returncode)
    if emulate_casts or wasm == 0:
      # success!
      self.assertContained('Hello, world.', output)
    else:
      # otherwise, the error depends on the mode we are in
      # wasm trap raised by the vm
      self.assertContained(['function signature mismatch', 'RuntimeError: call_indirect to a signature that does not match'], output)

  def test_bad_export(self):
    for exports in ('_main', '_main,foo'):
      self.clear()
      cmd = [EMCC, test_file('hello_world.c'), f'-sEXPORTED_FUNCTIONS={exports}']
      print(cmd)
      stderr = self.run_process(cmd, stderr=PIPE, check=False).stderr
      if 'foo' in exports:
        self.assertContained('undefined exported symbol: "foo"', stderr)
      else:
        self.assertContained('Hello, world!', self.run_js('a.out.js'))

  def test_no_dynamic_execution(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-O1', '-sDYNAMIC_EXECUTION=0'])
    self.assertContained('Hello, world!', self.run_js('a.out.js'))
    src = read_file('a.out.js')
    self.assertNotContained('eval(', src)
    self.assertNotContained('eval.', src)
    self.assertNotContained('new Function', src)
    delete_file('a.out.js')

    # Test that --preload-file doesn't add an use of eval().
    create_file('temp.txt', "foo\n")
    self.run_process([EMCC, test_file('hello_world.c'), '-O1',
                      '-sDYNAMIC_EXECUTION=0', '--preload-file', 'temp.txt'])
    src = read_file('a.out.js')
    self.assertNotContained('eval(', src)
    self.assertNotContained('eval.', src)
    self.assertNotContained('new Function', src)
    delete_file('a.out.js')

    # Test that -sDYNAMIC_EXECUTION=0 and -sMAIN_MODULE are allowed together.
    self.do_runf_out_file('hello_world.c', cflags=['-O1', '-sDYNAMIC_EXECUTION=0', '-sMAIN_MODULE=2'])

    create_file('test.c', r'''
      #include <emscripten/emscripten.h>
      int main() {
        emscripten_run_script("console.log('hello from script');");
        return 0;
      }
      ''')

    # Test that emscripten_run_script() aborts when -sDYNAMIC_EXECUTION=0
    self.run_process([EMCC, 'test.c', '-O1', '-sDYNAMIC_EXECUTION=0'])
    self.assertContained('DYNAMIC_EXECUTION=0 was set, cannot eval', self.run_js('a.out.js', assert_returncode=NON_ZERO))
    delete_file('a.out.js')

    # Test that emscripten_run_script() posts a warning when -sDYNAMIC_EXECUTION=2
    self.run_process([EMCC, 'test.c', '-O1', '-sDYNAMIC_EXECUTION=2'])
    self.assertContained('Warning: DYNAMIC_EXECUTION=2 was set, but calling eval in the following location:', self.run_js('a.out.js'))
    self.assertContained('hello from script', self.run_js('a.out.js'))
    delete_file('a.out.js')

  def test_init_file_at_offset(self):
    create_file('src.c', r'''
      #include <stdio.h>
      int main() {
        int data = 0x12345678;
        FILE *f = fopen("test.dat", "wb");
        fseek(f, 100, SEEK_CUR);
        fwrite(&data, 4, 1, f);
        fclose(f);

        int data2;
        f = fopen("test.dat", "rb");
        fread(&data2, 4, 1, f); // should read 0s, not that int we wrote at an offset
        printf("read: %d\n", data2);
        fseek(f, 0, SEEK_END);
        long size = ftell(f); // should be 104, not 4
        fclose(f);
        printf("file size is %ld\n", size);
      }
    ''')
    self.do_runf('src.c', 'read: 0\nfile size is 104\n')

  @no_mac("TODO: investigate different Node FS semantics on Mac")
  @no_windows("TODO: investigate different Node FS semantics on Windows")
  @with_all_fs
  def test_unlink(self):
    self.do_other_test('test_unlink.cpp')

  @crossplatform
  def test_argv0_node(self):
    create_file('code.c', r'''
#include <stdio.h>
int main(int argc, char **argv) {
  printf("I am %s.\n", argv[0]);
  return 0;
}
''')

    output = self.do_runf('code.c')
    self.assertContained('I am ' + utils.normalize_path(os.path.realpath(self.get_dir())) + '/code.js', utils.normalize_path(output))

  @parameterized({
    'no_exit_runtime': [True],
    '': [False],
  })
  def test_returncode(self, no_exit):
    create_file('src.c', r'''
      #include <stdio.h>
      #include <stdlib.h>
      int main() {
      #if CALL_EXIT
        exit(CODE);
      #else
        return CODE;
      #endif
      }
    ''')
    for code in (0, 123):
      for call_exit in (0, 1):
        for async_compile in (0, 1):
          self.run_process([EMCC, 'src.c', '-sENVIRONMENT=node,shell', '-DCODE=%d' % code, '-sEXIT_RUNTIME=%d' % (1 - no_exit), '-DCALL_EXIT=%d' % call_exit, '-sWASM_ASYNC_COMPILATION=%d' % async_compile])
          for engine in config.JS_ENGINES:
            print(code, call_exit, async_compile, engine)
            proc = self.run_process(engine + ['a.out.js'], stderr=PIPE, check=False)
            msg = 'but keepRuntimeAlive() is set (counter=0) due to an async operation, so halting execution but not exiting the runtime'
            if no_exit and call_exit:
              self.assertContained(msg, proc.stderr)
            else:
              self.assertNotContained(msg, proc.stderr)
            # we always emit the right exit code, whether we exit the runtime or not
            self.assertEqual(proc.returncode, code)

  def test_emscripten_force_exit_NO_EXIT_RUNTIME(self):
    create_file('src.c', r'''
      #include <emscripten.h>
      int main() {
      #if CALL_EXIT
        emscripten_force_exit(0);
      #endif
      }
    ''')
    for no_exit in (0, 1):
      for call_exit in (0, 1):
        self.run_process([EMCC, 'src.c', '-sEXIT_RUNTIME=%d' % (1 - no_exit), '-DCALL_EXIT=%d' % call_exit])
        print(no_exit, call_exit)
        out = self.run_js('a.out.js')
        assert ('emscripten_force_exit cannot actually shut down the runtime, as the build does not have EXIT_RUNTIME set' in out) == (no_exit and call_exit), out

  def test_mkdir_silly(self):
    create_file('src.c', r'''
#include <stdio.h>
#include <dirent.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char **argv) {
  printf("\n");
  for (int i = 1; i < argc; i++) {
    printf("%d:\n", i);
    int ok = mkdir(argv[i], S_IRWXU|S_IRWXG|S_IRWXO);
    printf("  make %s: %d\n", argv[i], ok);
    DIR *dir = opendir(argv[i]);
    printf("  open %s: %d\n", argv[i], dir != NULL);
    if (dir) {
      struct dirent *entry;
      while ((entry = readdir(dir))) {
        printf("  %s, %d\n", entry->d_name, entry->d_type);
      }
    }
  }
}
    ''')
    self.run_process([EMCC, 'src.c'])

    # cannot create /, can open
    self.assertContained(r'''
1:
  make /: -1
  open /: 1
  ., 4
  .., 4
  tmp, 4
  home, 4
  dev, 4
  proc, 4
''', self.run_js('a.out.js', args=['/']))
    # cannot create empty name, cannot open
    self.assertContained(r'''
1:
  make : -1
  open : 0
''', self.run_js('a.out.js', args=['']))
    # can create unnormalized path, can open
    self.assertContained(r'''
1:
  make /a//: 0
  open /a//: 1
  ., 4
  .., 4
''', self.run_js('a.out.js', args=['/a//']))
    # can create child unnormalized
    self.assertContained(r'''
1:
  make /a: 0
  open /a: 1
  ., 4
  .., 4
2:
  make /a//b//: 0
  open /a//b//: 1
  ., 4
  .., 4
''', self.run_js('a.out.js', args=['/a', '/a//b//']))

  def test_stat_silly(self):
    create_file('src.c', r'''
#include <stdio.h>
#include <errno.h>
#include <sys/stat.h>

int main(int argc, char **argv) {
  for (int i = 1; i < argc; i++) {
    const char *path = argv[i];
    struct stat path_stat;
    if (stat(path, &path_stat) != 0) {
      printf("Failed to stat path: '%s'; errno=%d\n", path, errno);
    } else {
      printf("stat success on '%s'\n", path);
    }
  }
}
    ''')
    self.do_runf('src.c', r'''Failed to stat path: '/a'; errno=44
Failed to stat path: ''; errno=44
''', args=['/a', ''])

  def test_symlink_silly(self):
    create_file('src.c', r'''
#include <dirent.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char **argv) {
  if (symlink(argv[1], argv[2]) != 0) {
    printf("Failed to symlink paths: %s, %s; errno=%d\n", argv[1], argv[2], errno);
  } else {
    printf("ok\n");
  }
}
    ''')
    self.run_process([EMCC, 'src.c'])

    # cannot symlink nonexistents
    self.assertContained(r'Failed to symlink paths: , abc; errno=44', self.run_js('a.out.js', args=['', 'abc']))
    self.assertContained(r'Failed to symlink paths: , ; errno=44', self.run_js('a.out.js', args=['', '']))
    self.assertContained(r'ok', self.run_js('a.out.js', args=['123', 'abc']))
    self.assertContained(r'Failed to symlink paths: abc, ; errno=44', self.run_js('a.out.js', args=['abc', '']))

  @with_all_fs
  def test_stat_many_dotdot(self):
    path = "/".join([".."] * 75)
    create_file('main.c', '''
      #include <stdio.h>
      #include <errno.h>
      #include <sys/stat.h>

      int main() {
        struct stat path_stat;
        return stat("%s", &path_stat);
      }
    ''' % path)
    self.do_runf('main.c')

  def test_rename_silly(self):
    create_file('src.c', r'''
#include <stdio.h>
#include <errno.h>

int main(int argc, char **argv) {
  if (rename(argv[1], argv[2]) != 0) {
    printf("Failed to rename paths: %s, %s; errno=%d\n", argv[1], argv[2], errno);
  } else {
    printf("ok\n");
  }
}
''')
    self.run_process([EMCC, 'src.c'])

    # cannot symlink nonexistents
    self.assertContained(r'Failed to rename paths: , abc; errno=44', self.run_js('a.out.js', args=['', 'abc']))
    self.assertContained(r'Failed to rename paths: , ; errno=44', self.run_js('a.out.js', args=['', '']))
    self.assertContained(r'Failed to rename paths: 123, abc; errno=44', self.run_js('a.out.js', args=['123', 'abc']))
    self.assertContained(r'Failed to rename paths: abc, ; errno=44', self.run_js('a.out.js', args=['abc', '']))

  def test_readdir_r_silly(self):
    # cannot symlink nonexistents
    self.do_other_test('test_readdir_r_silly.cpp', args=['', 'abc'])

  def test_emversion(self):
    create_file('src.c', r'''
      #include <stdio.h>
      #include <emscripten/version.h>
      int main() {
        printf("MAJOR: %d\n", __EMSCRIPTEN_MAJOR__);
        printf("MINOR: %d\n", __EMSCRIPTEN_MINOR__);
        printf("TINY: %d\n", __EMSCRIPTEN_TINY__);
        // Also test legacy names:
        printf("major: %d\n", __EMSCRIPTEN_major__);
        printf("minor: %d\n", __EMSCRIPTEN_minor__);
        printf("tiny: %d\n", __EMSCRIPTEN_tiny__);
      }
    ''')
    expected = f'''\
MAJOR: {utils.EMSCRIPTEN_VERSION_MAJOR}
MINOR: {utils.EMSCRIPTEN_VERSION_MINOR}
TINY: {utils.EMSCRIPTEN_VERSION_TINY}
major: {utils.EMSCRIPTEN_VERSION_MAJOR}
minor: {utils.EMSCRIPTEN_VERSION_MINOR}
tiny: {utils.EMSCRIPTEN_VERSION_TINY}
'''
    self.do_runf('src.c', expected, cflags=['-Wno-deprecated'])
    self.do_runf('src.c', expected, cflags=['-Wno-deprecated', '-sSTRICT'])

  def test_libc_files_without_syscalls(self):
    # a program which includes FS due to libc js library support, but has no syscalls,
    # so full FS support would normally be optimized out
    create_file('src.c', r'''
#include <sys/time.h>
#include <stddef.h>
int main() {
  return utimes(NULL, NULL);
}''')
    self.run_process([EMCC, 'src.c'])

  def test_syscall_no_filesystem(self):
    # a program which includes a non-trivial syscall, but disables the filesystem.
    create_file('src.c', r'''
#include <sys/time.h>
#include <fcntl.h>
int main() {
  return openat(0, "foo", 0);
}''')
    self.run_process([EMCC, 'src.c', '-sNO_FILESYSTEM'])

  def test_dylink_no_filesystem(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-sMAIN_MODULE=2', '-sNO_FILESYSTEM'])

  def test_dylink_4gb_max(self):
    # Test main module with 4GB of memory. we need to emit a "maximum"
    # clause then, even though 4GB is the maximum; see
    # https://github.com/emscripten-core/emscripten/issues/14130
    self.set_setting('MAIN_MODULE', '1')
    self.set_setting('ALLOW_MEMORY_GROWTH', '1')
    self.set_setting('MAXIMUM_MEMORY', '4GB')
    self.do_runf_out_file('hello_world.c')

  def test_dashS(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-S'])
    self.assertExists('hello_world.s')

  def assertIsLLVMAsm(self, filename):
    bitcode = read_file(filename)
    self.assertContained('target triple = "', bitcode)

  def test_dashS_ll_input(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-S', '-emit-llvm'])
    self.assertIsLLVMAsm('hello_world.ll')
    self.run_process([EMCC, 'hello_world.ll', '-S', '-emit-llvm', '-o', 'another.ll'])
    self.assertIsLLVMAsm('another.ll')

  def test_dashS_stdout(self):
    stdout = self.run_process([EMCC, test_file('hello_world.c'), '-S', '-o', '-'], stdout=PIPE).stdout
    self.assertEqual(os.listdir('.'), [])
    self.assertContained('hello_world.c', stdout)

  def test_emit_llvm_asm(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-S', '-emit-llvm'])
    self.assertIsLLVMAsm('hello_world.ll')

  def test_emit_llvm(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-c', '-emit-llvm'])
    self.assertTrue(is_bitcode('hello_world.bc'))

  def test_compile_ll_file(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-S', '-emit-llvm'])
    err = self.run_process([EMCC, '-v', '-c', 'hello_world.ll', '-o', 'hello_world.o'], stderr=PIPE).stderr
    # Verify that `-mllvm` flags are passed when compiling `.ll` files.
    self.assertContained('-mllvm -enable-emscripten-sjlj', err)
    self.run_process([EMCC, 'hello_world.o'])
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

  def test_dashE(self):
    create_file('src.cpp', r'''#include <emscripten.h>
__EMSCRIPTEN_MAJOR__ __EMSCRIPTEN_MINOR__ __EMSCRIPTEN_TINY__ EMSCRIPTEN_KEEPALIVE
''')

    def test(args):
      print(args)
      out = self.run_process([EMXX, 'src.cpp', '-E'] + args, stdout=PIPE).stdout
      self.assertContained('%d %d %d __attribute__((used))' % (utils.EMSCRIPTEN_VERSION_MAJOR, utils.EMSCRIPTEN_VERSION_MINOR, utils.EMSCRIPTEN_VERSION_TINY), out)

    test([])
    test(['-lembind'])

  def test_dashE_respect_dashO(self):
    # issue #3365
    with_dash_o = self.run_process([EMCC, test_file('hello_world.c'), '-E', '-o', 'ignored.js'], stdout=PIPE, stderr=PIPE).stdout
    without_dash_o = self.run_process([EMCC, test_file('hello_world.c'), '-E'], stdout=PIPE, stderr=PIPE).stdout
    self.assertEqual(len(with_dash_o), 0)
    self.assertNotEqual(len(without_dash_o), 0)

  def test_dashM(self):
    out = self.run_process([EMCC, test_file('hello_world.c'), '-M'], stdout=PIPE).stdout
    self.assertContained('hello_world.o:', out) # Verify output is just a dependency rule instead of bitcode or js

  def test_dashM_respect_dashO(self):
    # issue #3365
    with_dash_o = self.run_process([EMCC, test_file('hello_world.c'), '-M', '-o', 'ignored.js'], stdout=PIPE).stdout
    without_dash_o = self.run_process([EMCC, test_file('hello_world.c'), '-M'], stdout=PIPE).stdout
    self.assertEqual(len(with_dash_o), 0)
    self.assertNotEqual(len(without_dash_o), 0)

  @with_all_fs
  @crossplatform
  def test_fs_bad_lookup(self):
    self.do_runf('fs/test_fs_bad_lookup.c', 'ok')

  @also_with_nodefs_both
  @crossplatform
  @parameterized({
    '': ([],),
    'pthreads': (['-pthread', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'],),
  })
  def test_fsync(self, args):
    self.do_runf('fs/test_memfs_fsync.c', 'done\n', cflags=args)

  @with_all_fs
  @crossplatform
  def test_fs_dev_random(self):
    if WINDOWS and self.get_setting('NODERAWFS'):
      self.skipTest('Crashes on Windows and NodeFS')
    if self.get_setting('NODERAWFS') and self.get_setting('WASMFS'):
      self.skipTest('https://github.com/emscripten-core/emscripten/issues/24830')
    self.do_runf('fs/test_fs_dev_random.c', 'done\n')

  @parameterized({
    'none': [{'EMCC_FORCE_STDLIBS': None}, False],
    # forced libs is ok, they were there anyhow
    'normal': [{'EMCC_FORCE_STDLIBS': 'libc,libc++abi,libc++'}, False],
    # partial list, but ok since we grab them as needed
    'partial': [{'EMCC_FORCE_STDLIBS': 'libc++'}, False],
    # fail! not enough stdlibs
    'partial_only': [{'EMCC_FORCE_STDLIBS': 'libc++,libc,libc++abi', 'EMCC_ONLY_FORCED_STDLIBS': '1'}, True],
    # force all the needed stdlibs, so this works even though we ignore the input file
    'full_only': [{'EMCC_FORCE_STDLIBS': 'libc,libc++abi,libc++,libmalloc', 'EMCC_ONLY_FORCED_STDLIBS': '1'}, False],
  })
  def test_only_force_stdlibs(self, env, fail):
    cmd = [EMXX, test_file('hello_libcxx.cpp')]
    with env_modify(env):
      if fail:
        self.assert_fail(cmd, 'undefined symbol: emscripten_builtin_memalign')
      else:
        err = self.run_process(cmd, stderr=PIPE).stderr
        if 'EMCC_ONLY_FORCED_STDLIBS' in env:
          self.assertContained('EMCC_ONLY_FORCED_STDLIBS is deprecated', err)
        else:
          self.assertContained('Hello, world!', self.run_js('a.out.js'))

  def test_only_force_stdlibs_2(self):
    create_file('src.cpp', r'''
#include <iostream>
#include <stdexcept>

int main()
{
  try {
    throw std::exception();
    std::cout << "got here" << std::endl;
  }
  catch (const std::exception& ex) {
    std::cout << "Caught exception: " << ex.what() << std::endl;
  }
}
''')
    with env_modify({'EMCC_FORCE_STDLIBS': 'libc,libc++abi,libc++,libmalloc', 'EMCC_ONLY_FORCED_STDLIBS': '1'}):
      self.run_process([EMXX, 'src.cpp', '-sDISABLE_EXCEPTION_CATCHING=0'])
    self.assertContained('Caught exception: std::exception', self.run_js('a.out.js'))

  @with_env_modify({'EMCC_FORCE_STDLIBS': '1'})
  def test_force_stdlibs(self):
    self.do_runf_out_file('hello_world.c')

  @also_with_standalone_wasm(impure=True)
  def test_time(self):
    if self.get_setting('STANDALONE_WASM'):
      self.cflags.append('-DSTANDALONE')
    self.do_other_test('test_time.c', cflags=['-sASSERTIONS=2'])

  @parameterized({
    '1': ('EST+05EDT',),
    '2': ('UTC+0',),
    '3': ('CET',),
  })
  def test_time_tz(self, tz):
    print('testing with TZ=%s' % tz)
    with env_modify({'TZ': tz}):
      # Run the test with different time zone settings if
      # possible. It seems that the TZ environment variable does not
      # work all the time (at least it's not well respected by
      # Node.js on Windows), but it does no harm either.
      self.do_other_test('test_time.c')

  def test_timeb(self):
    # Confirms they are called in reverse order
    self.do_other_test('test_timeb.c')

  def test_time_c(self):
    self.do_other_test('test_time_c.c')

  def test_gmtime(self):
    self.do_other_test('test_gmtime.c')

  @also_with_standalone_wasm()
  def test_strptime_tm(self):
    if self.get_setting('STANDALONE_WASM'):
      self.cflags += ['-DSTANDALONE']
    self.do_other_test('test_strptime_tm.c')

  def test_strptime_days(self):
    self.do_other_test('test_strptime_days.c')

  @also_with_standalone_wasm()
  def test_strptime_reentrant(self):
    if self.get_setting('STANDALONE_WASM'):
      self.cflags += ['-DSTANDALONE']
    self.do_other_test('test_strptime_reentrant.c')

  @crossplatform
  def test_strftime(self):
    self.do_other_test('test_strftime.c')

  @crossplatform
  def test_wcsftime(self):
    self.do_other_test('test_wcsftime.c')

  @crossplatform
  def test_strftime_zZ(self):
    if MACOS:
      self.skipTest('setting LC_ALL is not compatible with macOS python')

    tz_lang_infos = [
      {'env': {'LC_ALL': 'en_GB', 'TZ': 'Europe/London'}, 'expected_utc': 'UTC+0100'},
      {'env': {'LC_ALL': 'th_TH', 'TZ': 'Asia/Bangkok'}, 'expected_utc': 'UTC+0700'},
      {'env': {'LC_ALL': 'ar-AE', 'TZ': 'Asia/Dubai'}, 'expected_utc': 'UTC+0400'},
      {'env': {'LC_ALL': 'en-US', 'TZ': 'America/Los_Angeles'}, 'expected_utc': 'UTC-0700'},
    ]

    for tz_lang_info in tz_lang_infos:
      with env_modify(tz_lang_info['env']):
        self.do_runf('other/test_strftime_zZ.c', 'The current timezone is: %s' % (tz_lang_info['expected_utc']))

  def test_strptime_symmetry(self):
    self.do_other_test('test_strptime_symmetry.c')

  @also_with_wasmfs
  def test_truncate_from_0(self):
    self.do_other_test('test_truncate_from_0.cpp')

  def test_create_readonly(self):
    create_file('src.cpp', r'''
#include <cerrno>
#include <cstring>
#include <iostream>

#include <fcntl.h>
#include <unistd.h>

using std::endl;

//============================================================================
// :: Helpers

namespace
{
  // Helper to create a read-only file with content.
  void readOnlyFile(const std::string& path, const std::string& content)
  {
    std::cout
      << "Creating file: " << path << " with content of size="
      << content.size() << endl;

    const int fd = ::open(path.c_str(), O_CREAT | O_WRONLY, 0400);
    if (fd == -1) {
      const int error = errno;
      std::cout
        << "Failed to open file for writing: " << path << "; errno=" << error
        << "; " << std::strerror(error) << endl;
      return;
    }

    // Write the content to the file.
    ssize_t result = 0;
    if ((result = ::write(fd, content.data(), content.size()))
        != ssize_t(content.size()))
    {
      const int error = errno;
      std::cout
        << "Failed to write to file=" << path << "; errno=" << error
        << "; " << std::strerror(error) << endl;
      // Fall through to close the file.
    }
    else {
      std::cout
        << "Data written to file=" << path << "; successfully wrote "
        << result << " bytes" << endl;
    }

    ::close(fd);
  }
}

//============================================================================
// :: Entry Point

int main() {
  const char* const file = "/tmp/file";
  unlink(file);
  readOnlyFile(file, "This content should get written because the file "
                     "does not yet exist and so, only the mode of the "
                     "containing directory will influence my ability to "
                     "create and open the file. The mode of the file only "
                     "applies to opening of the stream, not subsequent stream "
                     "operations after stream has opened.\n\n");
  readOnlyFile(file, "This should not get written because the file already "
                     "exists and is read-only.\n\n");
}
''')
    self.do_runf('src.cpp', r'''Creating file: /tmp/file with content of size=292
Data written to file=/tmp/file; successfully wrote 292 bytes
Creating file: /tmp/file with content of size=79
Failed to open file for writing: /tmp/file; errno=2; Permission denied
''')

  @all_engines
  def test_embed_file_large(self):
    # If such long files are encoded on one line,
    # they overflow the interpreter's limit
    large_size = 1500000
    create_file('large.txt', 'x' * large_size)
    create_file('src.c', r'''
      #include <stdio.h>
      #include <unistd.h>
      int main() {
          FILE* fp = fopen("large.txt", "r");
          if (fp) {
              printf("ok\n");
              fseek(fp, 0L, SEEK_END);
              printf("%ld\n", ftell(fp));
          } else {
              printf("failed to open large file.txt\n");
          }
          return 0;
      }
    ''')
    self.do_runf('src.c', 'ok\n' + str(large_size) + '\n', cflags=['--embed-file', 'large.txt'])

  def test_force_exit(self):
    create_file('src.c', r'''
#include <emscripten/console.h>
#include <emscripten/emscripten.h>

void callback() {
  emscripten_out("callback pre()");
  emscripten_force_exit(42);
  emscripten_out("callback post()");
}

int main() {
  emscripten_async_call(callback, NULL, 100);
  emscripten_exit_with_live_runtime();
  return 123;
}
''')
    self.run_process([EMCC, 'src.c'])
    output = self.run_js('a.out.js', assert_returncode=42)
    self.assertContained('callback pre()', output)
    self.assertNotContained('callback post()', output)

  def test_bad_locale(self):
    create_file('src.c', r'''
#include <locale.h>
#include <stdio.h>
#include <wctype.h>

int main(int argc, char **argv) {
  const char *locale = (argc > 1 ? argv[1] : "C");
  const char *actual = setlocale(LC_ALL, locale);
  if (actual == NULL) {
    printf("%s locale not supported\n", locale);
    return 0;
  }
  printf("locale set to %s: %s\n", locale, actual);
}
''')
    self.run_process([EMCC, 'src.c'])

    self.assertContained('locale set to C: C',
                         self.run_js('a.out.js', args=['C']))
    self.assertContained('locale set to waka: waka',
                         self.run_js('a.out.js', args=['waka']))

  @crossplatform
  @requires_node
  def test_language_detection(self):
    # Test HTTP Accept-Language parsing by simulating navigator.languages #8751
    # Query the JS engine for what it thinks the language is.
    # We use a file to be independent of engine-specific eval flags.
    create_file('detect_lang.js', 'console.log(globalThis.navigator?.language ?? "C");')
    expected_lang = self.run_js('detect_lang.js').strip()
    expected_lang = expected_lang.replace('-', '_', 1)
    expected_lang = f'{expected_lang}.UTF-8'

    self.do_runf('other/test_language_detection.c', f'LANG={expected_lang}')

    # Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
    create_file('pre.js', 'delete global.navigator; globalThis.navigator = { language: "fr" };')
    self.do_runf('other/test_language_detection.c', 'LANG=fr.UTF-8', cflags=['--pre-js', 'pre.js'])

    # Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.5,en;q=0.3
    create_file('pre.js', r'delete global.navigator; globalThis.navigator = { language: "fr-FR" };')
    self.do_runf('other/test_language_detection.c', 'LANG=fr_FR.UTF-8', cflags=['--pre-js=pre.js'])

  def test_js_main(self):
    # try to add a main() from JS, at runtime. this is not supported (the
    # compiler needs to know at compile time about main).
    create_file('pre_main.js', r'''
      Module['_main'] = () => {};
    ''')
    create_file('src.cpp', '')
    self.cflags += ['--pre-js', 'pre_main.js']
    self.do_runf('src.cpp', 'compiled without a main, but one is present. if you added it from JS, use Module["onRuntimeInitialized"]', assert_returncode=NON_ZERO)

  def test_locale_wrong(self):
    create_file('src.cpp', r'''
#include <locale>
#include <iostream>
#include <stdexcept>

int main(const int argc, const char * const * const argv) {
  const char * const name = argc > 1 ? argv[1] : "C";

  try {
    const std::locale locale(name);
    std::cout
      << "Constructed locale \"" << name << "\"\n"
      << "This locale is "
      << (locale == std::locale::global(locale) ? "" : "not ")
      << "the global locale.\n"
      << "This locale is " << (locale == std::locale::classic() ? "" : "not ")
      << "the C locale." << std::endl;

  } catch(const std::runtime_error &ex) {
    std::cout
      << "Can't construct locale \"" << name << "\": " << ex.what()
      << std::endl;
    return 1;

  } catch(...) {
    std::cout
      << "FAIL: Unexpected exception constructing locale \"" << name << '\"'
      << std::endl;
    return 127;
  }
}
''')
    self.run_process([EMXX, 'src.cpp', '-sDISABLE_EXCEPTION_CATCHING=0'])
    self.assertContained('''\
Constructed locale "C"
This locale is the global locale.
This locale is the C locale.
''', self.run_js('a.out.js', args=['C']))
    self.assertContained('''\
Constructed locale "waka"
This locale is not the global locale.
This locale is not the C locale.
''', self.run_js('a.out.js', args=['waka']))

  def test_cleanup_os(self):
    # issue 2644
    def test(args, be_clean):
      print(args)
      self.clear()
      copy_asset('hello_world.c', 'a.c')
      create_file('b.c', ' ')
      self.run_process([EMCC, 'a.c', 'b.c'] + args)
      clutter = glob.glob('*.o')
      if be_clean:
        assert len(clutter) == 0, 'should not leave clutter ' + str(clutter)
      else:
        assert len(clutter) == 2, 'should leave .o files'
    test(['-o', 'c.so', '-r'], True)
    test(['-o', 'c.js'], True)
    test(['-o', 'c.html'], True)
    test(['-c'], False)

  @parameterized({
    '': ([],),
    'O1': (['-O1'],),
  })
  def test_dash_g_object(self, opts):
    self.run_process([EMCC, '-c', test_file('hello_world.c'), '-o', 'a_.o'] + opts)
    sizes = {'_': os.path.getsize('a_.o')}
    self.run_process([EMCC, '-c', test_file('hello_world.c'), '-g', '-o', 'ag.o'] + opts)
    sizes['g'] = os.path.getsize('ag.o')
    for i in range(5):
      self.run_process([EMCC, '-c', test_file('hello_world.c'), '-g' + str(i), '-o', 'a' + str(i) + '.o'] + opts)
      sizes[i] = os.path.getsize('a' + str(i) + '.o')
    print('  ', sizes)
    assert sizes['_'] == sizes[0] == sizes[1] == sizes[2], 'no debug means no llvm debug info ' + str(sizes)
    assert sizes['g'] == sizes[3] == sizes[4], '-g or -gsource-map means llvm debug info ' + str(sizes)
    assert sizes['_'] < sizes['g'], 'llvm debug info has positive size ' + str(sizes)

  def test_no_filesystem(self):
    FS_MARKER = 'var FS'
    # fopen forces full filesystem support
    self.run_process([EMCC, test_file('hello_world_fopen.c'), '-sASSERTIONS=0'])
    yes_size = os.path.getsize('a.out.js')
    self.assertContained('Hello, world!', self.run_js('a.out.js'))
    self.assertContained(FS_MARKER, read_file('a.out.js'))
    self.run_process([EMCC, test_file('hello_world.c'), '-sASSERTIONS=0'])
    no_size = os.path.getsize('a.out.js')
    self.assertContained('Hello, world!', self.run_js('a.out.js'))
    self.assertNotContained(FS_MARKER, read_file('a.out.js'))
    print('yes fs, no fs:', yes_size, no_size)
    # ~100K of FS code is removed
    self.assertGreater(yes_size - no_size, 90000)
    self.assertLess(no_size, 360000)

  def test_no_filesystem_libcxx(self):
    self.do_runf('hello_libcxx.cpp', 'Hello, world!', cflags=['-sFILESYSTEM=0'])

  # Verifies that filesystem is automatically omitted, and can be
  # manually disabled too, and both improve code size.
  @parameterized({
    'no_assertions': (['-sASSERTIONS=0'], 120000),
    'o1': (['-O1'], 91000),
    'o2': (['-O2'], 46000),
    'o3_closure': (['-O3', '--closure=1'], 17000),
    'o3_closure_js': (['-O3', '--closure=1', '-sWASM=0'], 36000),
    'o3_closure2_js': (['-O3', '--closure=2', '-sWASM=0'], 33000), # might change now and then
  })
  def test_no_filesystem_code_size(self, opts, absolute):
    print('opts, absolute:', opts, absolute)
    sizes = {}

    def do(name, source, moar_opts):
      self.clear()
      # pad the name to a common length so that doesn't affect the size of the
      # output
      padded_name = name + '_' * (20 - len(name))
      self.run_process([EMCC, test_file(source), '-o', padded_name + '.js'] + self.get_cflags() + opts + moar_opts)
      sizes[name] = os.path.getsize(padded_name + '.js')
      if os.path.exists(padded_name + '.wasm'):
        sizes[name] += os.path.getsize(padded_name + '.wasm')
      self.assertContained('Hello, world!', self.run_js(padded_name + '.js'))

    do('normal', 'hello_world_fopen.c', [])
    do('no_fs', 'hello_world.c', []) # without fopen, we should auto-detect we do not need full fs support and can do FILESYSTEM=0
    do('no_fs_manual', 'hello_world.c', ['-sFILESYSTEM=0'])
    print('  ', sizes)
    self.assertLess(sizes['no_fs'], sizes['normal'])
    self.assertLess(sizes['no_fs'], absolute)
    # manual can usually remove a tiny bit more
    self.assertLess(sizes['no_fs_manual'], sizes['no_fs'] + 30)

  def test_no_main_loop(self):
    MAINLOOP = 'var MainLoop'

    self.run_process([EMCC, test_file('hello_world.c')])
    self.assertNotContained(MAINLOOP, read_file('a.out.js'))

    # uses emscripten_set_main_loop, which needs MainLoop
    self.run_process([EMCC, test_file('browser_main_loop.c')])
    self.assertContained(MAINLOOP, read_file('a.out.js'))

  def test_EXPORTED_RUNTIME_METHODS(self):
    def test(opts, has, not_has):
      print(opts, has, not_has)
      self.clear()
      # check without assertions, as with assertions we add stubs for the things we remove (which
      # print nice error messages)
      self.run_process([EMCC, test_file('hello_world.c'), '-sASSERTIONS=0'] + opts)
      self.assertContained('Hello, world!', self.run_js('a.out.js'))
      src = read_file('a.out.js')
      self.assertContained(has, src)
      self.assertNotContained(not_has, src)

    test([], "Module['", "Module['waka")
    test(['-sEXPORTED_RUNTIME_METHODS=[]'], '', "Module['addRunDependency")
    test(['-sEXPORTED_RUNTIME_METHODS=addRunDependency'], "Module['addRunDependency", "Module['waka")
    test(['-sEXPORTED_RUNTIME_METHODS=[]', '-sEXPORTED_RUNTIME_METHODS=addRunDependency'], "Module['addRunDependency", "Module['waka")

  def test_stat_fail_alongtheway(self):
    self.do_other_test('test_stat_fail_alongtheway.c')

  def test_link_with_a_static(self):
    create_file('x.c', r'''
int init_weakref(int a, int b) {
  return a + b;
}
''')
    create_file('y.c', r'''
static int init_weakref(void) { // inlined in -O2, not in -O0 where it shows up in llvm-nm as 't'
  return 150;
}

int testy(void) {
  return init_weakref();
}
''')
    create_file('z.c', r'''
extern int init_weakref(int, int);
extern int testy(void);

int main(void) {
  return testy() + init_weakref(5, 6);
}
''')
    self.run_process([EMCC, '-c', 'x.c', '-o', 'x.o'])
    self.run_process([EMCC, '-c', 'y.c', '-o', 'y.o'])
    self.run_process([EMCC, '-c', 'z.c', '-o', 'z.o'])
    delete_file('libtest.a')
    self.run_process([EMAR, 'rc', 'libtest.a', 'y.o'])
    self.run_process([EMAR, 'rc', 'libtest.a', 'x.o'])
    self.run_process([EMRANLIB, 'libtest.a'])

    for args in ([], ['-O2']):
      print('args:', args)
      self.run_process([EMCC, 'z.o', 'libtest.a'] + args)
      self.run_js('a.out.js', assert_returncode=161)

  def test_link_with_bad_o_in_a(self):
    # when building a .a, we force-include all the objects inside it. but, some
    # may not be valid bitcode, e.g. if it contains metadata or something else
    # weird. we should just ignore those
    self.run_process([EMCC, '-c', test_file('hello_world.c'), '-o', 'hello_world.o'])
    create_file('bad.obj', 'this is not a good file, it should be ignored!')
    self.run_process([LLVM_AR, 'cr', 'libfoo.a', 'hello_world.o', 'bad.obj'])
    self.run_process([EMCC, 'libfoo.a'])
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

  @requires_node
  def test_require(self):
    inname = test_file('hello_world.c')
    self.emcc(inname, args=['-sASSERTIONS=0', '-o', 'a.out.js'])
    create_file('run.js', 'require("./a.out.js")')
    output = self.run_js('run.js')
    self.assertEqual('Hello, world!\n', output)

  @requires_node
  def test_require_modularize(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-sMODULARIZE', '-sASSERTIONS=0'])
    src = read_file('a.out.js')
    self.assertContained('module.exports = Module;', src)
    create_file('run.js', 'var m = require("./a.out.js"); m();')
    output = self.run_js('run.js')
    self.assertEqual(output, 'Hello, world!\n')
    self.run_process([EMCC, test_file('hello_world.c'), '-sMODULARIZE', '-sEXPORT_NAME=NotModule', '-sASSERTIONS=0'])
    self.assertContained('module.exports = NotModule;', read_file('a.out.js'))
    output = self.run_js('run.js')
    self.assertEqual(output, 'Hello, world!\n')
    self.run_process([EMCC, test_file('hello_world.c'), '-sMODULARIZE'])
    # We call require() twice to ensure it returns wrapper function each time
    create_file('require_twice.js', 'require("./a.out.js")();var m = require("./a.out.js"); m();')
    output = self.run_js('require_twice.js')
    self.assertEqual(output, 'Hello, world!\nHello, world!\n')

  def test_modularize_strict(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-sMODULARIZE', '-sSTRICT'])
    create_file('run.js', 'var m = require("./a.out.js"); m();')
    output = self.run_js('run.js')
    self.assertEqual(output, 'Hello, world!\n')

  def test_modularize_new_misuse(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-sMODULARIZE', '-sEXPORT_NAME=Foo'])
    create_file('run.js', 'var m = require("./a.out.js"); new m();')
    err = self.run_js('run.js', assert_returncode=NON_ZERO)
    # Different engines have slightly different error message syntax.
    expected = [
      "TypeError: function is not a constructor (evaluating 'new m')",
      'TypeError: m is not a constructor',
    ]
    self.assertContained(expected, err)

  @parameterized({
    '': ([],),
    'export_name': (['-sEXPORT_NAME=Foo'],),
    'closure': (['-sEXPORT_NAME=Foo', '--closure=1'],),
  })
  @crossplatform
  def test_modularize_incoming(self, args):
    self.run_process([EMCC, test_file('hello_world.c'), '-o', 'out.mjs'] + self.get_cflags() + args)
    create_file('run.mjs', '''
    import Module from './out.mjs';
    await Module({onRuntimeInitialized: () => console.log('done init')});
    console.log('got module');
    ''')
    output = self.run_js('run.mjs')
    self.assertContained('done init\nHello, world!\ngot module\n', output)

  def test_modularize_run_dependency(self):
    # Ensure that dependencies are fulfilled before the module promise is resolved.
    create_file('pre.js', '''
    Module.preRun = () => { dbg("add-dep"); addRunDependency("my dep"); }
    // Remove the run dependency in 100 milliseconds
    setTimeout(() => { dbg("remove-dep"); removeRunDependency("my dep") }, 100);
    ''')
    create_file('run.mjs', '''
    import Module from './hello_world.mjs';
    await Module();
    console.log('got module');
    ''')
    self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', '$addRunDependency,$removeRunDependency')
    self.emcc('hello_world.c', ['-o', 'hello_world.mjs', '-sEXPORT_ES6', '-sMODULARIZE', '-sWASM_ASYNC_COMPILATION=0', '--pre-js=pre.js'])
    self.assertContained('add-dep\nremove-dep\nHello, world!\ngot module\n', self.run_js('run.mjs'))

  def test_modularize_instance_run_dependency(self):
    # Ensure that dependencies are fulfilled before the MODULARIZE=instance ready promise is resolved.
    create_file('pre.js', '''
    Module.preRun = () => {
      dbg("add-dep");
      addRunDependency("my-dep");
      setTimeout(() => { dbg("remove-dep"); removeRunDependency("my-dep"); }, 100);
    }
    ''')
    self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', '$addRunDependency,$removeRunDependency')

    create_file('run.mjs', '''
    import init from './hello_world.mjs';
    await init();
    console.log('got module');
    ''')

    self.emcc('hello_world.c', ['-o', 'hello_world.mjs', '-sMODULARIZE=instance', '-Wno-experimental', '--pre-js=pre.js'])

    expected = 'add-dep\nremove-dep\nHello, world!\ngot module\n'
    self.assertContained(expected, self.run_js('run.mjs'))

  def test_modularize_instance_auto_init(self):
    create_file('library.js', '''\
    addToLibrary({
      $baz: () => console.log('baz'),
      $qux: () => console.log('qux'),
    });''')
    # With AUTO_INIT the module self-initializes via top-level await and does not
    # export `init`.
    self.run_process([EMCC, test_file('modularize_instance.c'),
                      '-sMODULARIZE=instance', '-sAUTO_INIT',
                      '-Wno-experimental',
                      '-sEXPORTED_RUNTIME_METHODS=baz,addOnExit,HEAP32',
                      '-sEXPORTED_FUNCTIONS=_bar,_main,qux',
                      '--js-library', 'library.js',
                      '-o', 'modularize_instance.mjs'] + self.get_cflags())

    # Named exports are usable directly on import; there is no `init` export.
    create_file('runner.mjs', '''
      import { strict as assert } from 'assert';
      import * as mod from "./modularize_instance.mjs";
      import { _foo as foo, _bar as bar, baz, qux, addOnExit, HEAP32 } from "./modularize_instance.mjs";
      assert(mod.default === undefined); // no `init` export when self-initializing
      foo(); // exported with EMSCRIPTEN_KEEPALIVE
      bar(); // exported with EXPORTED_FUNCTIONS
      baz(); // exported library function with EXPORTED_RUNTIME_METHODS
      qux(); // exported library function with EXPORTED_FUNCTIONS
      assert(typeof addOnExit === 'function'); // exported runtime function with EXPORTED_RUNTIME_METHODS
      assert(typeof HEAP32 === 'object'); // exported runtime value by default
    ''')

    self.assertContained('main1\nmain2\nfoo\nbar\nbaz\n', self.run_js('runner.mjs'))

  @also_with_pthreads
  @requires_node_25
  def test_esm_integration_auto_init(self):
    # Instance-phase wasm imports currently generate an ExperimentalWarning under node.
    self.node_args += ['--no-warnings']
    create_file('library.js', '''\
    addToLibrary({
      $baz: () => console.log('baz'),
      $qux: () => console.log('qux'),
    });''')
    self.run_process([EMCC, test_file('modularize_instance.c'),
                      '-Wno-experimental',
                      '-sWASM_ESM_INTEGRATION', '-sAUTO_INIT',
                      '-sEXPORTED_RUNTIME_METHODS=baz,addOnExit,HEAP32',
                      '-sEXPORTED_FUNCTIONS=_bar,_main,qux',
                      '--js-library', 'library.js',
                      '-o', 'modularize_instance.mjs'] + self.get_cflags())

    create_file('runner.mjs', '''
      import { strict as assert } from 'assert';
      import * as mod from "./modularize_instance.mjs";
      import { _foo as foo, _bar as bar, baz, qux, addOnExit, HEAP32 } from "./modularize_instance.mjs";
      assert(mod.default === undefined); // no `init` export when self-initializing
      foo();
      bar();
      baz();
      qux();
      assert(typeof addOnExit === 'function');
      assert(typeof HEAP32 === 'object');
    ''')

    self.assertContained('main1\nmain2\nfoo\nbar\nbaz\n', self.run_js('runner.mjs'))

  def test_modularize_instantiation_error(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-o', 'out.mjs'] + self.get_cflags())
    create_file('run.mjs', '''
    import Module from './out.mjs';
    try {
      await Module({onRuntimeInitialized: () => console.log('done init')});
      console.log('got module');
    } catch(e) {
      console.log('got error:', e);
    }
    ''')

    # First run with wasm file present
    output = self.run_js('run.mjs')
    self.assertContained('got module', output)

    # Now run again after deleting the file and we should still succeed since
    # the factory function is wrapped in a try catch.  This verifies that there
    # are no unhandled rejections internally in this case.
    os.remove('out.wasm')
    output = self.run_js('run.mjs')
    self.assertContained('failed to asynchronously prepare wasm', output)
    self.assertContained('got error:.*RuntimeError: Aborted', output, regex=True)

  @crossplatform
  @requires_pthreads
  @flaky('https://github.com/emscripten-core/emscripten/issues/19683')
  # The flakiness of this test is very high on macOS, and deno so just disable it
  # completely in these cases.
  @no_mac('https://github.com/emscripten-core/emscripten/issues/19683')
  @no_deno('https://github.com/emscripten-core/emscripten/issues/19683')
  def test_pthread_print_override_modularize(self):
    self.set_setting('EXPORT_NAME', 'Test')
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    self.set_setting('MODULARIZE')
    create_file('main.c', '''
      #include <emscripten/console.h>

      int main() {
        emscripten_out("Hello, world!");
        return 0;
      }
    ''')
    create_file('main.js', '''
      const Test = require('./test.js');

      async function main() {
        await Test({
          // world -> earth
          print: (text) => console.log(text.replace('world', 'earth'))
        });
      }
      main();
    ''')

    self.emcc('main.c', ['-o' 'test.js'])
    output = self.run_js('main.js')
    self.assertNotContained('Hello, world!', output)
    self.assertContained('Hello, earth!', output)

  def test_define_modularize(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-sMODULARIZE', '-sASSERTIONS=0'])
    src = 'var module = 0; ' + read_file('a.out.js')
    create_file('a.out.js', src)
    self.assertContained("define([], () => Module);", src)

    create_file('run_module.js', '''
var m;
(global.define = (deps, factory) => { m = factory(); }).amd = true;
require("./a.out.js");
m();
''')
    output = self.run_js('run_module.js')
    self.assertContained('Hello, world!\n', output)

    self.run_process([EMCC, test_file('hello_world.c'), '-sMODULARIZE', '-sEXPORT_NAME=NotModule', '-sASSERTIONS=0'])
    src = 'var module = 0; ' + read_file('a.out.js')
    create_file('a.out.js', src)
    self.assertContained("define([], () => NotModule);", src)

    output = self.run_js('run_module.js')
    self.assertContained('Hello, world!\n', output)

  def test_EXPORT_NAME_with_html(self):
    expected = 'error: customizing EXPORT_NAME requires that the HTML be customized to use that name'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-o', 'a.html', '-sEXPORT_NAME=Other'], expected)

  def test_modularize_sync_compilation(self):
    # Verify that, even when WASM_ASYNC_COMPILATION is disabled, the module factory
    # still returns a promise.   This behaviour was changed in #24727.
    create_file('post.js', r'''
console.log('before');
var result = Module();
// It should be an object.
console.log('typeof result: ' + typeof result);
console.log('typeof result.then: ' + typeof result.then);
result.then((inst) => {
  // And it should have the exports that Module has, showing it is Module in fact.
  console.log('typeof inst._main: ' + typeof inst._main);
  console.log('after');
});
''')
    self.run_process([EMCC, test_file('hello_world.c'),
                      '-sMODULARIZE',
                      '-sWASM_ASYNC_COMPILATION=0',
                      '--extern-post-js', 'post.js'])
    self.assertContained('''\
before
Hello, world!
typeof result: object
typeof result.then: function
typeof inst._main: function
after
''', self.run_js('a.out.js'))

  def test_export_all_3142(self):
    create_file('src.cpp', r'''
typedef unsigned int Bit32u;

struct S_Descriptor {
    Bit32u limit_0_15   :16;
    Bit32u base_0_15    :16;
    Bit32u base_16_23   :8;
};

class Descriptor {
public:
    Descriptor() { saved.fill[0] = saved.fill[1] = 0; }
    union {
        S_Descriptor seg;
        Bit32u fill[2];
    } saved;
};

Descriptor desc;
    ''')
    self.run_process([EMXX, 'src.cpp', '-O2', '-sEXPORT_ALL'])
    self.assertExists('a.out.js')

  def test_modularize_legacy(self):
    self.do_runf_out_file('hello_world.c', cflags=['-sMODULARIZE', '-sLEGACY_VM_SUPPORT', '--extern-post-js', test_file('modularize_post_js.js')])

  def test_emmake_emconfigure(self):
    def check(what, args, fail=True, expect=''):
      args = [what] + args
      print(what, args, fail, expect)
      output = self.run_process(args, stdout=PIPE, stderr=PIPE, check=False)
      assert ('is a helper for' in output.stderr) == fail
      assert ('Typical usage' in output.stderr) == fail
      self.assertContained(expect, output.stdout)
    check(EMMAKE, [])
    check(EMCONFIGURE, [])
    check(EMMAKE, ['--version'])
    check(EMCONFIGURE, ['--version'])
    check(EMMAKE, ['make'], fail=False)
    check(EMCONFIGURE, ['configure'], fail=False)
    check(EMCONFIGURE, ['./configure'], fail=False)
    check(EMCMAKE, ['cmake'], fail=False)

    create_file('test.py', '''
import os
print(os.environ.get('CROSS_COMPILE'))
''')
    check(EMCONFIGURE, [PYTHON, 'test.py'], expect=path_from_root('em'), fail=False)
    check(EMMAKE, [PYTHON, 'test.py'], expect=path_from_root('em'), fail=False)

    create_file('test.py', '''
import os
print(os.environ.get('NM'))
''')
    check(EMCONFIGURE, [PYTHON, 'test.py'], expect=shared.LLVM_NM, fail=False)

    create_file('test.c', 'int main() { return 0; }')
    os.mkdir('test_cache')
    with env_modify({'EM_CACHE': os.path.abspath('test_cache')}):
      check(EMCONFIGURE, [EMCC, 'test.c'], fail=False)

  def test_emmake_python(self):
    # simulates a configure/make script that looks for things like CC, AR, etc., and which we should
    # not confuse by setting those vars to something containing `python X` as the script checks for
    # the existence of an executable.
    self.run_process([EMMAKE, PYTHON, test_file('emmake/make.py')])

  @crossplatform
  @no_windows('sdl2-config is a shell script and cannot run on windows')
  def test_sdl2_config(self):
    for args, expected in [
      [['--version'], '2.0.10'],
      [['--cflags'], '-sUSE_SDL=2'],
      [['--libs'], '-sUSE_SDL=2'],
      [['--cflags', '--libs'], '-sUSE_SDL=2'],
    ]:
      print(args, expected)
      out = self.run_process([cache.get_sysroot_dir('bin/sdl2-config')] + args,
                             stdout=PIPE, stderr=PIPE).stdout
      self.assertContained(expected, out)
      print('via emmake')
      out = self.run_process([EMMAKE, 'sdl2-config'] + args, stdout=PIPE, stderr=PIPE).stdout
      self.assertContained(expected, out)

  def test_module_onexit(self):
    create_file('src.c', r'''
#include <emscripten.h>
int main() {
  EM_ASM({
    Module.onExit = (status) => out('exiting now, status ' + status);
  });
  return 14;
}
''')
    self.run_process([EMCC, 'src.c', '-sEXIT_RUNTIME'])
    self.assertContained('exiting now, status 14', self.run_js('a.out.js', assert_returncode=14))

  def test_NO_aliasing(self):
    # the NO_ prefix flips boolean options
    self.run_process([EMCC, test_file('hello_world.c'), '-sEXIT_RUNTIME'])
    exit_1 = read_file('a.out.js')
    self.run_process([EMCC, test_file('hello_world.c'), '-sNO_EXIT_RUNTIME=0'])
    no_exit_0 = read_file('a.out.js')
    self.run_process([EMCC, test_file('hello_world.c'), '-sEXIT_RUNTIME=0'])
    exit_0 = read_file('a.out.js')

    assert exit_1 == no_exit_0
    assert exit_1 != exit_0

  def test_underscore_exit(self):
    create_file('src.c', r'''
#include <unistd.h>
int main() {
  _exit(0); // should not end up in an infinite loop with non-underscore exit
}
''')
    self.run_process([EMCC, 'src.c'])
    self.assertContained('', self.run_js('a.out.js'))

  def test_file_packager_huge(self):
    MESSAGE = 'warning: file packager is creating an asset bundle of 257 MB. this is very large, and browsers might have trouble loading it'
    self.create_huge_file('huge.dat', 1024 * 1024 * 257)
    create_file('tiny.dat', 'a')
    err = self.run_process([FILE_PACKAGER, 'test.data', '--preload', 'tiny.dat'], stdout=PIPE, stderr=PIPE).stderr
    self.assertNotContained(MESSAGE, err)
    err = self.run_process([FILE_PACKAGER, 'test.data', '--preload', 'huge.dat'], stdout=PIPE, stderr=PIPE).stderr
    self.assertContained(MESSAGE, err)
    self.clear()

  @parameterized({
    'no_split': (False, False),
    'split': (True, False),
    'split_metadata': (True, True),
    'no_split_metadata': (False, True),
  })
  @with_env_modify({'EM_FILE_PACKAGER_MAX_CHUNK_SIZE_MB': '10'})
  def test_file_packager_huge_data(self, split, separate_metadata):
    # Verify the packager's split boundary, with and without separate metadata.
    large_size = 1024 * 1024
    sizes = [large_size] * 10
    if split:
      sizes[-1] += 1
    for i, size in enumerate(sizes):
      self.create_huge_file(f'huge{i}.dat', size)

    args = [FILE_PACKAGER, 'test.data']
    if separate_metadata:
      args += ['--separate-metadata', '--js-output=immutable.js']
    args += ['--preload'] + [f'huge{i}.dat' for i in range(10)]
    result = self.run_process(args, stdout=PIPE, stderr=PIPE)
    self.assertExists('test.data')
    if split:
      self.assertContained('warning: file packager is splitting bundle into 2 chunks', result.stderr)
      self.assertExists('test_1.data')
      self.assertEqual(os.path.getsize('test.data'), large_size * 9)
      self.assertEqual(os.path.getsize('test_1.data'), sizes[-1])
    else:
      self.assertNotExists('test_1.data')
      self.assertEqual(os.path.getsize('test.data'), (large_size * 10))

    cflags = ['-sFORCE_FILESYSTEM']
    if separate_metadata:
      self.assertExists('immutable.js')
      self.assertExists('immutable.js.metadata')
      metadata = json.loads(read_file('immutable.js.metadata'))
      self.assertEqual(len(metadata['files']), 9 if split else 10)
      for i, file_metadata in enumerate(metadata['files']):
        self.assertEqual(file_metadata['start'], i * large_size)
        self.assertEqual(file_metadata['end'], (i * large_size) + large_size)
        self.assertEqual(file_metadata['filename'], f'/huge{i}.dat')
      self.assertEqual(metadata['remote_package_size'], (9 if split else 10) * large_size)

      if split:
        self.assertExists('immutable_1.js')
        self.assertExists('immutable_1.js.metadata')
        metadata = json.loads(read_file('immutable_1.js.metadata'))
        self.assertEqual(len(metadata['files']), 1)
        self.assertEqual(metadata['files'][0]['start'], 0)
        self.assertEqual(metadata['files'][0]['end'], sizes[-1])
        self.assertEqual(metadata['files'][0]['filename'], '/huge9.dat')
        self.assertEqual(metadata['remote_package_size'], sizes[-1])
        cflags[:0] = ['--pre-js=immutable.js', '--pre-js=immutable_1.js']
      else:
        self.assertNotExists('immutable_1.js')
        self.assertNotExists('immutable_1.js.metadata')
        cflags[:0] = ['--pre-js=immutable.js']
    else:
      create_file('load.js', result.stdout)
      cflags.insert(0, '--pre-js=load.js')

    expected_sizes = ',\n    '.join(f'{size}LL' for size in sizes)
    create_file('src.c', f'''
#include <assert.h>
#include <sys/stat.h>
#include <stdio.h>

int main() {{
  static const long long expected_sizes[] = {{
    {expected_sizes}
  }};
  char filename[32];
  struct stat buf;
  const int num_files = sizeof(expected_sizes) / sizeof(expected_sizes[0]);

  for (int i = 0; i < num_files; ++i) {{
    snprintf(filename, sizeof(filename), "huge%d.dat", i);
    assert(stat(filename, &buf) == 0);
    assert(buf.st_size == expected_sizes[i]);
  }}
  printf("done\\n");
  return 0;
}}
''')
    self.do_runf('src.c', cflags=cflags)
    self.clear()

  def test_file_packager_huge_split_too_large_default(self):
    self.create_huge_file('huge.dat', (1024 * 1024 * 1024) + ((1022 * 1024 * 1024) + 1))
    proc = self.run_process([FILE_PACKAGER, 'test.data', '--preload', 'huge.dat'], check=False, stdout=PIPE, stderr=PIPE)
    self.assertEqual(proc.returncode, 1)
    self.assertContained('error: cannot package file huge.dat, which is larger than maximum individual file size limit 2046 MB', proc.stderr)
    self.clear()

  @with_env_modify({'EM_FILE_PACKAGER_MAX_CHUNK_SIZE_MB': '10'})
  def test_file_packager_huge_split_too_large_env(self):
    self.create_huge_file('huge.dat', (10 * 1024 * 1024) + 1)
    proc = self.run_process([FILE_PACKAGER, 'test.data', '--preload', 'huge.dat'], check=False, stdout=PIPE, stderr=PIPE)
    self.assertEqual(proc.returncode, 1)
    self.assertContained('error: cannot package file huge.dat, which is larger than maximum individual file size limit 10 MB', proc.stderr)
    self.clear()

  @crossplatform
  @also_with_wasm2js
  def test_memory_growth(self):
    create_file('main.c', r'''
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <emscripten/em_asm.h>
#include <emscripten/console.h>

int main() {
  // Report whether `toResizableBuffer` is availble on the wasm memory.
  EM_ASM(out('resizable memory buffers:', Boolean(wasmMemory.toResizableBuffer)));

  char* x = malloc(10 * 1024 * 1024);
  assert(x != NULL);
  // Have JS use some of the memory in the expanded range.
  char* str = x + 9 * 1024 * 1024;
  strcpy(str, "Hello, world!");
  emscripten_out(str);
  return 0;
}
''')
    output = self.do_runf('main.c', 'Hello, world!\n', cflags=['-sALLOW_MEMORY_GROWTH', '-sINITIAL_HEAP=1mb'])
    if self.is_wasm2js():
      self.assertContained('Warning: Enlarging memory arrays, this is not fast! 1179648,10616832\n', output)

    # Node versions older than 26 do not support toResizableBuffer
    if self.is_wasm2js() or (self.engine_is_node() and not check_node_version(26)):
      self.assertContained('resizable memory buffers: false\n', output)
    else:
      self.assertContained('resizable memory buffers: true\n', output)

  @also_with_wasm2js
  @parameterized({
    '': (False,),
    'growth': (True,),
  })
  def test_failing_alloc(self, growth):
    # Force memory growth to fail at runtime
    self.add_pre_run('growMemory = (size) => false;')
    for pre_fail, post_fail, opts in [
      ('', '', []),
      ('EM_ASM( Module.temp = _sbrk() );', 'EM_ASM( assert(Module.temp === _sbrk(), "must not adjust brk when an alloc fails!") );', []),
    ]:
      for aborting_args in ([], ['-sABORTING_MALLOC=0']):
        create_file('main.cpp', r'''
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <assert.h>
#include <emscripten.h>

#define CHUNK_SIZE (10 * 1024 * 1024)

int main() {
  std::vector<void*> allocs;
  bool has = false;
  while (1) {
    printf("trying an allocation\n");
    %s
    void* curr = malloc(CHUNK_SIZE);
    if (!curr) {
      %s
      break;
    }
    has = true;
    printf("allocated another chunk, %%zu so far\n", allocs.size());
    allocs.push_back(curr);
  }
  assert(has);
  printf("an allocation failed!\n");
#ifdef SPLIT
  return 0;
#endif
  while (1) {
    assert(allocs.size() > 0);
    void *curr = allocs.back();
    allocs.pop_back();
    free(curr);
    printf("freed one\n");
    if (malloc(CHUNK_SIZE)) break;
  }
  printf("managed another malloc!\n");
}
''' % (pre_fail, post_fail))
        args = [EMXX, 'main.cpp', '-sEXPORTED_FUNCTIONS=_main,_sbrk', '-sINITIAL_MEMORY=16MB'] + opts + aborting_args
        if growth:
          args += ['-sALLOW_MEMORY_GROWTH']
        # growth disables aborting by default, but it can be overridden
        aborting = not aborting_args and not growth
        print('test_failing_alloc', args, pre_fail)
        self.run_process(args)
        # growth also disables aborting
        can_manage_another = not aborting
        split = '-DSPLIT' in args
        print('can manage another:', can_manage_another, 'split:', split, 'aborting:', aborting)
        output = self.run_js('a.out.js', assert_returncode=0 if can_manage_another else NON_ZERO)
        if can_manage_another:
          self.assertContained('an allocation failed!\n', output)
          if not split:
            # split memory allocation may fail due to GC objects no longer being allocatable,
            # and we can't expect to recover from that deterministically. So just check we
            # get to the fail.
            # otherwise, we should fail eventually, then free, then succeed
            self.assertContained('managed another malloc!\n', output)
        else:
          # we should see an abort
          self.assertContained('Aborted(Cannot enlarge memory arrays', output)
          if growth:
            # when growth is enabled, the default is to not abort, so just explain that
            self.assertContained('If you want malloc to return NULL (0) instead of this abort, do not link with -sABORTING_MALLOC', output)
          else:
            # when growth is not enabled, suggest 3 possible solutions (start with more memory, allow growth, or don't abort)
            self.assertContained(('higher than the current value 16777216,', 'higher than the current value 33554432,'), output)
            self.assertContained('compile with -sALLOW_MEMORY_GROWTH', output)
            self.assertContained('compile with -sABORTING_MALLOC=0', output)

  def test_failing_growth_2gb(self):
    create_file('test.c', r'''
#include <stdio.h>
#include <stdlib.h>

void* out;
int main() {
  while (1) {
    puts("loop...");
    out = malloc(1024 * 1024);
    if (!out) {
      puts("done");
      return 0;
    }
  }
}
''')

    self.do_runf('test.c', 'done\n', cflags=['-O1', '-sALLOW_MEMORY_GROWTH'])

  @requires_wasm64
  @requires_node_25
  def test_failing_growth_wasm64(self):
    self.require_wasm64()
    create_file('test.c', r'''
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <emscripten/heap.h>

void* out;

int main() {
  printf("&out = %p\n", &out);
  assert((uintptr_t)&out > (2 * 1024 * 1024 * 1024ll));
  while (1) {
    out = malloc(10 * 1024 * 1024);
    printf("alloc: %p\n", out);
    if (!out) {
      printf("malloc fail with emscripten_get_heap_size: %zu\n", emscripten_get_heap_size());
      printf("done\n");
      return 0;
    }
  }
}
''')
    self.do_runf('test.c', 'done\n', cflags=['-sGLOBAL_BASE=2Gb', '-sTOTAL_MEMORY=4Gb', '-sMAXIMUM_MEMORY=5Gb', '-sALLOW_MEMORY_GROWTH', '-m64'])

  def test_libcxx_minimal(self):
    create_file('vector.cpp', r'''
#include <vector>
int main(int argc, char** argv) {
  std::vector<void*> v;
  for (int i = 0 ; i < argc; i++) {
    v.push_back(nullptr);
  }
  return v.size();
}
''')

    self.run_process([EMXX, '-O2', 'vector.cpp', '-o', 'vector.js'])
    self.run_process([EMXX, '-O2', test_file('hello_libcxx.cpp'), '-o', 'iostream.js'])

    vector = os.path.getsize('vector.js')
    iostream = os.path.getsize('iostream.js')
    print(vector, iostream)

    self.assertGreater(vector, 1000)
    # we can strip out almost all of libcxx when just using vector
    self.assertLess(2.25 * vector, iostream)

  @parameterized({
    '': ('1',),
    # TODO(sbc): make dynamic linking work with wasm2js
    # 'wasm2js': ('0',)
  })
  @is_slow_test
  def test_minimal_dynamic(self, wasm):
    library_file = 'library.wasm' if wasm else 'library.js'

    def test(name, main_args, library_args, expected='hello from main\nhello from library', assert_returncode=0):
      print(f'testing {name}', main_args, library_args)
      self.clear()
      create_file('library.c', r'''
        #include <stdio.h>
        void library_func() {
        #ifdef USE_PRINTF
          printf("hello from library: %p\n", &library_func);
        #else
          puts("hello from library");
        #endif
        }
      ''')
      # -fno-builtin to prevent printf -> iprintf optimization
      self.run_process([EMCC, 'library.c', '-fno-builtin', '-sSIDE_MODULE', '-O2', '-o', library_file, '-sWASM=' + wasm, '-sEXPORT_ALL'] + library_args)
      create_file('main.c', r'''
        #include <dlfcn.h>
        #include <stdio.h>
        int main() {
          puts("hello from main");
          void *lib_handle = dlopen("%s", RTLD_NOW);
          if (!lib_handle) {
            puts("cannot load side module");
            puts(dlerror());
            return 1;
          }
          typedef void (*voidfunc)();
          voidfunc x = (voidfunc)dlsym(lib_handle, "library_func");
          if (!x) puts("cannot find side function");
          else x();
        }
      ''' % library_file)
      self.run_process([EMCC, 'main.c', '--embed-file', library_file, '-O2', '-sWASM=' + wasm] + main_args)
      self.assertContained(expected, self.run_js('a.out.js', assert_returncode=assert_returncode))
      size = os.path.getsize('a.out.js')
      if wasm:
        size += os.path.getsize('a.out.wasm')
      side_size = os.path.getsize(library_file)
      print(f'  sizes {name}: {size}, {side_size}')
      return (size, side_size)

    def percent_diff(x, y):
      small = min(x, y)
      large = max(x, y)
      return float(100 * large) / small - 100

    full = test('full', main_args=['-sMAIN_MODULE'], library_args=[])
    # printf is not used in main, but libc was linked in, so it's there
    printf = test('printf', main_args=['-sMAIN_MODULE'], library_args=['-DUSE_PRINTF'])

    # main module tests

    # dce in main, and it fails since puts is not exported
    test('dce', main_args=['-sMAIN_MODULE=2'], library_args=[], expected=('is not a function', 'cannot', 'undefined'), assert_returncode=NON_ZERO)

    # with exporting, it works
    dce = test('dce', main_args=['-sMAIN_MODULE=2', '-sEXPORTED_FUNCTIONS=_main,_puts'], library_args=[])

    # printf is not used in main, and we dce, so we failz
    dce_fail = test('dce_fail', main_args=['-sMAIN_MODULE=2'], library_args=['-DUSE_PRINTF'], expected=('is not a function', 'cannot', 'undefined'), assert_returncode=NON_ZERO)

    # exporting printf in main keeps it alive for the library
    test('dce_save', main_args=['-sMAIN_MODULE=2', '-sEXPORTED_FUNCTIONS=_main,_printf,_puts'], library_args=['-DUSE_PRINTF'])

    self.assertLess(percent_diff(full[0], printf[0]), 4)
    self.assertLess(percent_diff(dce[0], dce_fail[0]), 4)
    self.assertLess(dce[0], 0.2 * full[0]) # big effect, 80%+ is gone

    # side module tests

    # mode 2, so dce in side, but library_func is not exported, so it is dce'd
    side_dce_fail = test('side_dce_fail', main_args=['-sMAIN_MODULE'], library_args=['-sSIDE_MODULE=2'], expected='cannot find side function')
    # mode 2, so dce in side, and library_func is not exported
    side_dce_work = test('side_dce_fail', main_args=['-sMAIN_MODULE'], library_args=['-sSIDE_MODULE=2', '-sEXPORTED_FUNCTIONS=_library_func'], expected='hello from library')

    self.assertLess(side_dce_fail[1], 0.95 * side_dce_work[1]) # removing that function saves a chunk

  def test_RUNTIME_LINKED_LIBS(self):
    # Verify that the legacy `-sRUNTIME_LINKED_LIBS` option acts the same as passing a
    # library on the command line directly.
    create_file('side.c', 'int foo() { return 42; }')
    create_file('main.c', '#include <assert.h>\nextern int foo(); int main() { assert(foo() == 42); return 0; }')

    self.run_process([EMCC, '-O2', 'side.c', '-sSIDE_MODULE', '-o', 'side.wasm'])
    self.run_process([EMCC, '-O2', 'main.c', '-sMAIN_MODULE', '-o', 'main.js', 'side.wasm'])
    self.run_js('main.js')

    err = self.run_process([EMCC, '-O2', 'main.c', '-sMAIN_MODULE', '-o', 'main2.js', '-sRUNTIME_LINKED_LIBS=side.wasm'], stderr=PIPE).stderr
    self.assertContained('emcc: warning: RUNTIME_LINKED_LIBS is deprecated', err)
    self.run_js('main2.js')

    self.assertBinaryEqual('main.wasm', 'main2.wasm')

  @parameterized({
    '': ([],),
    'wasmfs': (['-sWASMFS'],),
    'pthread': (['-g', '-pthread', '-Wno-experimental', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'],),
  })
  def test_ld_library_path(self, args):
    if '-pthread' in args:
      self.skipTest('Problems with readFile from pthread')
    if '-pthread' in args:
      self.require_pthreads()
    create_file('hello1_dep.c', r'''
#include <stdio.h>

void hello1_dep() {
  printf("Hello1_dep\n");
  return;
}
''')
    create_file('hello1.c', r'''
#include <stdio.h>

void hello1_dep();

void hello1() {
  printf("Hello1\n");
  hello1_dep();
  return;
}
''')
    create_file('hello2.c', r'''
#include <stdio.h>

void hello2() {
  printf("Hello2\n");
  return;
}
''')
    create_file('hello3.c', r'''
#include <stdio.h>

void hello3() {
  printf("Hello3\n");
  return;
}
''')
    create_file('hello4.c', r'''
#include <stdio.h>
#include <math.h>

double hello4(double x) {
  printf("Hello4\n");
  return fmod(x, 2.0);
}
''')
    create_file('pre.js', r'''
Module.preRun = () => {
  ENV['LD_LIBRARY_PATH']='/lib:/usr/lib:/usr/local/lib';
};
''')
    create_file('main.c', r'''
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>

int main() {
  void *h;
  void (*f)();
  double (*f2)(double);

  h = dlopen("libhello1.wasm", RTLD_NOW);
  assert(h);
  f = dlsym(h, "hello1");
  assert(f);
  f();
  dlclose(h);

  h = dlopen("libhello2.wasm", RTLD_NOW);
  assert(h);
  f = dlsym(h, "hello2");
  assert(f);
  f();
  dlclose(h);

  h = dlopen("libhello3.wasm", RTLD_NOW);
  assert(h);
  f = dlsym(h, "hello3");
  assert(f);
  f();
  dlclose(h);

  h = dlopen("/usr/local/lib/libhello4.wasm", RTLD_NOW);
  assert(h);
  f2 = dlsym(h, "hello4");
  assert(f2);
  double result = f2(5.5);
  dlclose(h);

  if (result == 1.5) {
    printf("Ok\n");
  }
  return 0;
}
''')
    os.mkdir('subdir')
    self.run_process([EMCC, '-o', 'subdir/libhello1_dep.so', 'hello1_dep.c', '-sSIDE_MODULE'])
    self.run_process([EMCC, '-o', 'hello1.wasm', 'hello1.c', '-sSIDE_MODULE', 'subdir/libhello1_dep.so'] + args)
    self.run_process([EMCC, '-o', 'hello2.wasm', 'hello2.c', '-sSIDE_MODULE'] + args)
    self.run_process([EMCC, '-o', 'hello3.wasm', 'hello3.c', '-sSIDE_MODULE'] + args)
    self.run_process([EMCC, '-o', 'hello4.wasm', 'hello4.c', '-sSIDE_MODULE'] + args)
    args = ['--profiling-funcs', '-sMAIN_MODULE=2', '-sINITIAL_MEMORY=32Mb',
            '-L./subdir',
            '--embed-file', 'subdir/libhello1_dep.so@/usr/lib/libhello1_dep.so',
            '--embed-file', 'hello1.wasm@/lib/libhello1.wasm',
            '--embed-file', 'hello2.wasm@/usr/lib/libhello2.wasm',
            '--embed-file', 'hello3.wasm@/libhello3.wasm',
            '--embed-file', 'hello4.wasm@/usr/local/lib/libhello4.wasm',
            'hello1.wasm', 'hello2.wasm', 'hello3.wasm', 'hello4.wasm', '-sNO_AUTOLOAD_DYLIBS',
            '--pre-js', 'pre.js'] + args
    self.do_runf('main.c', 'Hello1\nHello1_dep\nHello2\nHello3\nHello4\nOk\n', cflags=args)

  @also_with_wasmfs
  def test_dlopen_rpath(self):
    create_file('hello_nested_dep.c', r'''
    #include <stdio.h>

    void hello_nested_dep() {
      printf("Hello_nested_dep\n");
      return;
    }
    ''')
    create_file('hello_dep.c', r'''
    #include <stdio.h>

    void hello_nested_dep();

    void hello_dep() {
      printf("Hello_dep\n");
      hello_nested_dep();
      return;
    }
    ''')
    create_file('hello.c', r'''
    #include <stdio.h>

    void hello_dep();

    void hello() {
      printf("Hello\n");
      hello_dep();
      return;
    }
    ''')
    create_file('main.c', r'''
    #include <assert.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <dlfcn.h>

    int main() {
      void *h;
      void (*f)();
      double (*f2)(double);

      h = dlopen("/usr/lib/libhello.so", RTLD_NOW);
      assert(h);
      f = dlsym(h, "hello");
      assert(f);
      f();
      dlclose(h);

      printf("Ok\n");

      return 0;
    }
    ''')
    os.mkdir('subdir')

    def _build(rpath_flag, expected, **kwds):
      self.run_process([EMCC, '-o', 'subdir/libhello_nested_dep.so', 'hello_nested_dep.c', '-sSIDE_MODULE'])
      self.run_process([EMCC, '-o', 'subdir/libhello_dep.so', 'hello_dep.c', '-sSIDE_MODULE', 'subdir/libhello_nested_dep.so'] + rpath_flag)
      self.run_process([EMCC, '-o', 'hello.so', 'hello.c', '-sSIDE_MODULE', 'subdir/libhello_dep.so'] + rpath_flag)
      args = ['--profiling-funcs', '-sMAIN_MODULE=2', '-sINITIAL_MEMORY=32Mb',
                        '--embed-file', 'hello.so@/usr/lib/libhello.so',
                        '--embed-file', 'subdir/libhello_dep.so@/usr/lib/subdir/libhello_dep.so',
                        '--embed-file', 'subdir/libhello_nested_dep.so@/usr/lib/subdir/libhello_nested_dep.so',
                        'hello.so', '-sNO_AUTOLOAD_DYLIBS',
                        '-L./subdir', '-lhello_dep', '-lhello_nested_dep']
      self.do_runf('main.c', expected, cflags=args, **kwds)

    # case 1) without rpath: fail to locate the library
    _build([], r"no such file or directory, open '.*libhello_dep\.so'", regex=True, assert_returncode=NON_ZERO)

    # case 2) with rpath: success
    _build(['-Wl,-rpath,$ORIGIN/subdir,-rpath,$ORIGIN'], "Hello\nHello_dep\nHello_nested_dep\nOk\n")

  def test_dlopen_bad_flags(self):
    create_file('main.c', r'''
#include <dlfcn.h>
#include <stdio.h>

int main() {
  void* h = dlopen("lib.so", 0);
  if (h) {
    printf("expected dlopen to fail\n");
    return 1;
  }
  printf("%s\n", dlerror());
  return 0;
}
''')
    self.run_process([EMCC, 'main.c', '-sMAIN_MODULE=2'])
    out = self.run_js('a.out.js')
    self.assertContained('invalid mode for dlopen(): Either RTLD_LAZY or RTLD_NOW is required', out)

  def test_dlopen_constructors(self):
    create_file('side.c', r'''
      #include <stdio.h>
      #include <assert.h>

      static int foo;
      static int* ptr = &foo;

      void check_relocations(void) {
        assert(ptr == &foo);
      }

      __attribute__((constructor)) void ctor(void) {
        printf("foo address: %p\n", ptr);
        // Check that relocations have already been applied by the time
        // constructor functions run.
        check_relocations();
        printf("done ctor\n");
      }
      ''')
    create_file('main.c', r'''
      #include <assert.h>
      #include <stdio.h>
      #include <dlfcn.h>

      int main() {
        void (*check) (void);
        void* h = dlopen("libside.wasm", RTLD_NOW);
        assert(h);
        check = dlsym(h, "check_relocations");
        assert(check);
        check();
        printf("done\n");
        return 0;
      }''')
    self.run_process([EMCC, '-g', '-o', 'libside.wasm', 'side.c', '-sSIDE_MODULE'])
    self.do_runf('main.c', 'done\n', cflags=['-g', '-sMAIN_MODULE=2', 'libside.wasm', '-sNO_AUTOLOAD_DYLIBS'])
    # Repeat the test without NO_AUTOLOAD_DYLIBS
    self.do_runf('main.c', 'done\n', cflags=['-g', '-sMAIN_MODULE=2', 'libside.wasm'])

  def test_dlopen_rtld_global(self):
    # This test checks RTLD_GLOBAL where a module is loaded
    # before the module providing a global it needs is. in asm.js we use JS
    # to create a redirection function. In wasm we just have wasm, so we
    # need to introspect the wasm module. Browsers may add that eventually,
    # or we could ship a little library that does it.
    create_file('hello1.c', r'''
#include <stdio.h>

extern int hello1_val;
int hello1_val = 3;

void hello1(int i) {
  printf("hello1_val by hello1:%d\n",hello1_val);
  printf("Hello%d\n",i);
}
''')
    create_file('hello2.c', r'''
#include <stdio.h>

extern int hello1_val;
extern void hello1(int);

void hello2(int i) {
  void (*f) (int);
  printf("hello1_val by hello2:%d\n",hello1_val);
  f = hello1;
  f(i);
}
''')
    create_file('main.c', r'''
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>

int main(int argc,char** argv) {
  void *h;
  void *h2;
  void (*f) (int);
  h = dlopen("libhello1.wasm", RTLD_NOW|RTLD_GLOBAL);
  h2 = dlopen("libhello2.wasm", RTLD_NOW|RTLD_GLOBAL);
  f = dlsym(h, "hello1");
  f(1);
  f = dlsym(h2, "hello2");
  f(2);
  dlclose(h);
  dlclose(h2);
  return 0;
}
''')

    self.run_process([EMCC, '-o', 'libhello1.wasm', 'hello1.c', '-sSIDE_MODULE', '-sEXPORT_ALL'])
    self.run_process([EMCC, '-o', 'libhello2.wasm', 'hello2.c', '-sSIDE_MODULE', '-sEXPORT_ALL'])
    self.run_process([EMCC, '-o', 'main.js', 'main.c', '-sMAIN_MODULE',
                      '--embed-file', 'libhello1.wasm',
                      '--embed-file', 'libhello2.wasm'])
    out = self.run_js('main.js')
    self.assertContained('Hello1', out)
    self.assertContained('Hello2', out)
    self.assertContained('hello1_val by hello1:3', out)
    self.assertContained('hello1_val by hello2:3', out)

  def test_dlopen_async(self):
    create_file('side.c', 'int foo = 42;\n')
    create_file('pre.js', r'''
Module.preRun = () => {
  ENV['LD_LIBRARY_PATH']='/usr/lib';
};
''')
    self.run_process([EMCC, 'side.c', '-o', 'tmp.so', '-sSIDE_MODULE'])
    self.set_setting('MAIN_MODULE', 2)
    self.do_other_test('test_dlopen_async.c', ['--pre-js=pre.js', '--embed-file', 'tmp.so@/usr/lib/libside.so'])

  def test_dlopen_promise(self):
    create_file('side.c', 'int foo = 42;\n')
    self.run_process([EMCC, 'side.c', '-o', 'libside.so', '-sSIDE_MODULE'])
    self.set_setting('MAIN_MODULE', 2)
    self.do_other_test('test_dlopen_promise.c')

  @parameterized({
    # Under node this should work even without ASYNCIFY because we can do
    # synchronous loading via readBinary
    '': (0,),
    'asyncify': (1,),
    'jspi': (2,),
  })
  def test_dlopen_blocking(self, asyncify):
    self.run_process([EMCC, test_file('other/test_dlopen_blocking_side.c'), '-o', 'libside.so', '-sSIDE_MODULE'])
    self.set_setting('MAIN_MODULE', 2)
    self.set_setting('NO_AUTOLOAD_DYLIBS')
    if asyncify:
      self.set_setting('ASYNCIFY', asyncify)
      if asyncify == 1:
        self.set_setting('EXIT_RUNTIME')
      if asyncify == 2:
        self.require_jspi()
    self.do_other_test('test_dlopen_blocking.c', cflags=['libside.so'])

  def test_dlsym_rtld_default(self):
    create_file('side.c', r'''
    int baz() {
      return 99;
    }
    ''')
    self.run_process([EMCC, '-o', 'libside.so', 'side.c', '-sSIDE_MODULE'])
    create_file('main.c', r'''
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE int foo() {
  return 42;
}

EMSCRIPTEN_KEEPALIVE int64_t foo64() {
  return 64;
}

int main(int argc, char** argv) {
  int (*f)();
  f = dlsym(RTLD_DEFAULT, "foo");
  assert(f);
  printf("foo -> %d\n", f());
  assert(f() == 42);

  int64_t (*f64)();
  f64 = dlsym(RTLD_DEFAULT, "foo64");
  assert(f64);
  printf("foo64 -> %lld\n", f64());
  assert(f64() == 64);

  // Missing function
  f = dlsym(RTLD_DEFAULT, "bar");
  printf("bar -> %p\n", f);
  assert(f == NULL);

  // Function from side module that was loaded at startup
  f = dlsym(RTLD_DEFAULT, "baz");
  assert(f);
  printf("baz -> %p\n", f);
  assert(f() == 99);

  // Check that dlopen()'ing libside.so gives that same
  // address for baz.
  void* handle = dlopen("libside.so", RTLD_NOW);
  assert(handle);
  int (*baz)() = dlsym(handle, "baz");
  assert(baz);
  printf("baz -> %p\n", baz);
  assert(baz() == 99);
  assert(baz == f);

  return 0;
}
''')
    self.do_runf('main.c', cflags=['-sMAIN_MODULE=2', 'libside.so'])

  def test_dlsym_rtld_default_js_symbol(self):
    create_file('lib.js', '''
      addToLibrary({
       foo__sig: 'ii',
       foo: function(f) { return f + 10 },
       bar: function(f) { return f + 10 },
      });
      ''')
    create_file('main.c', r'''
#include <stdio.h>
#include <utime.h>
#include <sys/types.h>
#include <dlfcn.h>

typedef int (*func_type_t)(int arg);

int main(int argc, char** argv) {
  func_type_t fp = (func_type_t)dlsym(RTLD_DEFAULT, argv[1]);
  if (!fp) {
    printf("dlsym failed: %s\n", dlerror());
    return 1;
  }
  printf("%s -> %d\n", argv[1], fp(10));
  return 0;
}
''')
    self.run_process([EMCC, 'main.c',
                      '--js-library=lib.js',
                      '-sMAIN_MODULE=2',
                      '-sEXPORTED_FUNCTIONS=_main,_foo,_bar'])

    # Fist test the successful use of a JS function with dlsym
    out = self.run_js('a.out.js', args=['foo'])
    self.assertContained('foo -> 20', out)

    # Now test the failure case for when __sig is not present
    out = self.run_js('a.out.js', args=['bar'], assert_returncode=NON_ZERO)
    self.assertContained('Missing signature argument to addFunction: function _bar', out)

  def test_main_module_without_exceptions_message(self):
    # A side module that needs exceptions needs a main module with that
    # support enabled; show a clear message in that case.
    create_file('side.cpp', r'''
      #include <exception>
      #include <stdio.h>

      extern "C" void test_throw() {
        try {
          throw 42;
        } catch(int x) {
          printf("catch %d.\n", x);
          return;
        }
        puts("bad location");
      }
    ''')
    create_file('main.cpp', r'''
      #include <assert.h>
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <dlfcn.h>

      typedef void (*voidf)();

      int main() {
        void* h = dlopen("libside.wasm", RTLD_NOW);
        assert(h);
        voidf f = (voidf)dlsym(h, "test_throw");
        assert(f);
        f();
        return 0;
      }
      ''')
    self.run_process([EMXX, '-o', 'libside.wasm', 'side.cpp', '-sSIDE_MODULE', '-fexceptions'])

    def build_main(args):
      print(args)
      with env_modify({'EMCC_FORCE_STDLIBS': 'libc++abi'}):
        self.run_process([EMXX, 'main.cpp', '-sMAIN_MODULE',
                          '--embed-file', 'libside.wasm'] + args)

    build_main([])
    out = self.run_js('a.out.js', assert_returncode=NON_ZERO)
    self.assertContained('Exception thrown, but exception catching is not enabled.', out)
    self.assertContained('note: in dynamic linking, if a side module wants exceptions, the main module must be built with that support', out)

    build_main(['-fexceptions'])
    out = self.run_js('a.out.js')
    self.assertContained('catch 42', out)

  def test_emscripten_print_double(self):
    create_file('src.c', r'''
#include <stdio.h>
#include <assert.h>
#include <emscripten.h>

void test(double d) {
  char buffer[100], buffer2[100];
  unsigned len, len2, len3;
  len = emscripten_print_double(d, NULL, -1);
  len2 = emscripten_print_double(d, buffer, len+1);
  assert(len == len2);
  buffer[len] = 0;
  len3 = snprintf(buffer2, 100, "%g", d);
  printf("|%g : %u : %s : %s : %d|\n", d, len, buffer, buffer2, len3);
}

int main() {
  printf("\n");
  test(0);
  test(1);
  test(-1);
  test(1.234);
  test(-1.234);
  test(1.1234E20);
  test(-1.1234E20);
  test(1.1234E-20);
  test(-1.1234E-20);
  test(1.0/0.0);
  test(-1.0/0.0);
}
''')
    self.run_process([EMCC, 'src.c'])
    out = self.run_js('a.out.js')
    self.assertContained('''
|0 : 1 : 0 : 0 : 1|
|1 : 1 : 1 : 1 : 1|
|-1 : 2 : -1 : -1 : 2|
|1.234 : 5 : 1.234 : 1.234 : 5|
|-1.234 : 6 : -1.234 : -1.234 : 6|
|1.1234e+20 : 21 : 112340000000000000000 : 1.1234e+20 : 10|
|-1.1234e+20 : 22 : -112340000000000000000 : -1.1234e+20 : 11|
|1.1234e-20 : 10 : 1.1234e-20 : 1.1234e-20 : 10|
|-1.1234e-20 : 11 : -1.1234e-20 : -1.1234e-20 : 11|
|inf : 8 : Infinity : inf : 3|
|-inf : 9 : -Infinity : -inf : 4|
''', out)

  def test_emscripten_scan_stack(self):
    create_file('src.cpp', r'''
#include <set>
#include <emscripten.h>
#include <stdio.h>
#include <assert.h>

std::set<int> seenInts;

void scan(void* x, void* y) {
  printf("scan\n");
  int* p = (int*)x;
  int* q = (int*)y;
  // The callback sends us the [low, high) range.
  assert(p < q);
  // The range is of a reasonable size - not all of memory.
  assert(q - p < 100);
  while (p < q) {
    seenInts.insert(*p);
    p++;
  }
}

int main() {
  int x;
  int* y = &x;
  *y = 12345678;
  emscripten_scan_stack(scan);
  assert(seenInts.count(12345678));
  puts("ok");
}
''')
    self.run_process([EMXX, 'src.cpp'])
    self.assertContained('ok', self.run_js('a.out.js'))

  def test_no_warn_exported_jslibfunc(self):
    self.run_process([EMCC, test_file('hello_world.c'),
                      '-sEXPORTED_FUNCTIONS=_main,_alGetError'])

    # Same again but with `_alGet` which does not exist.  This is a regression
    # test for a bug we had where any prefix of a valid function was accepted.
    self.assert_fail([EMCC, test_file('hello_world.c'),
                     '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=alGetError',
                     '-sEXPORTED_FUNCTIONS=_main,_alGet'],
                     'wasm-ld: error: symbol exported via --export not found: alGet')

  def test_musl_syscalls(self):
    self.run_process([EMCC, test_file('hello_world.c')])
    src = read_file('a.out.js')
    # there should be no musl syscalls in hello world output
    self.assertNotContained('__syscall', src)

  @crossplatform
  def test_emcc_dev_null(self):
    out = self.run_process([EMCC, '-dM', '-E', '-x', 'c', os.devnull], stdout=PIPE).stdout
    self.assertContained('#define __EMSCRIPTEN__ 1', out) # all our defines should show up

  def test_umask_0(self):
    create_file('src.c', r'''\
#include <sys/stat.h>
#include <stdio.h>
int main() {
  umask(0);
  printf("Hello, world!\n");
}
''')
    self.run_process([EMCC, 'src.c'])
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

  @crossplatform
  @also_with_wasmfs
  def test_umask(self):
    self.do_runf('other/test_umask.c', 'done\n')

  def test_no_missing_symbols(self):
    # simple hello world should not show any missing symbols
    self.run_process([EMCC, test_file('hello_world.c')])

    # main() is implemented in C, and even if requested from JS, we should not warn
    create_file('library_foo.js', '''
addToLibrary({
  my_js__deps: ['main'],
  my_js: (function() {
      return () => console.log("hello " + _nonexistingvariable);
  }()),
});
''')
    create_file('test.c', '''\
#include <stdio.h>
#include <stdlib.h>

void my_js();

int main() {
  my_js();
  return EXIT_SUCCESS;
}
''')
    self.run_process([EMCC, 'test.c', '--js-library', 'library_foo.js'])

    # but we do error on a missing js var
    create_file('library_foo_missing.js', '''
addToLibrary({
  my_js__deps: ['main', 'nonexistingvariable'],
  my_js: (function() {
      return () => console.log("hello " + _nonexistingvariable);
  }()),
});
''')
    self.assert_fail([EMCC, 'test.c', '--js-library', 'library_foo_missing.js'], 'undefined symbol: nonexistingvariable. Required by my_js')

    # and also for missing C code, of course (without the --js-library, it's just a missing C method)
    self.assert_fail([EMCC, 'test.c'], 'undefined symbol: my_js')

  @crossplatform
  @also_with_wasmfs
  def test_realpath(self):
    ensure_dir('boot')
    create_file('boot/README.txt', ' ')
    self.do_other_test('test_realpath.c', cflags=['-sSAFE_HEAP', '--embed-file', 'boot'])

  @crossplatform
  @also_with_wasmfs
  def test_realpath_nodefs(self):
    if self.get_setting('WASMFS'):
      self.cflags += ['-sFORCE_FILESYSTEM']
    create_file('TEST_NODEFS.txt', ' ')
    self.do_other_test('test_realpath_nodefs.c', cflags=['-lnodefs.js'])

  @also_with_wasmfs
  def test_realpath_2(self):
    ensure_dir('Folder')
    create_file('testfile.txt', '')
    create_file('Folder/testfile.txt', '')
    self.do_other_test('test_realpath_2.c', cflags=['--embed-file', 'testfile.txt', '--embed-file', 'Folder'])

  @requires_node
  @also_with_wasmfs
  def test_resolve_mountpoint_parent(self):
    self.do_other_test('test_resolve_mountpoint_parent.c', cflags=['-sFORCE_FILESYSTEM', '-lnodefs.js'])

  @with_env_modify({'EMCC_LOGGING': '0'})  # this test assumes no emcc output
  def test_no_warnings(self):
    # build once before to make sure system libs etc. exist
    self.run_process([EMXX, test_file('hello_libcxx.cpp')])
    # check that there is nothing in stderr for a regular compile
    err = self.run_process([EMXX, test_file('hello_libcxx.cpp')], stderr=PIPE).stderr
    self.assertEqual(err, '')

  @crossplatform
  def test_dlmalloc_modes(self):
    create_file('src.c', r'''
      #include <stdlib.h>
      #include <stdio.h>
      int main() {
        void* c = malloc(1024);
        free(c);
        free(c);
        printf("double-freed\n");
      }
    ''')
    self.run_process([EMCC, 'src.c', '-O2'])
    self.assertContained('double-freed', self.run_js('a.out.js'))
    # in debug mode, the double-free is caught
    self.run_process([EMCC, 'src.c', '-O0'])
    out = self.run_js('a.out.js', assert_returncode=NON_ZERO)
    self.assertContained('native code called abort()', out)

  @parameterized({
    '': ([], 191000),
    'O2': (['-O2'], 137000),
    'emmalloc': (['-sMALLOC=emmalloc'], 185000),
    'dlmalloc': (['-sMALLOC=dlmalloc'], 191000),
    'mimalloc': (['-sMALLOC=mimalloc'], 256000),
    'emmalloc_O2': (['-sMALLOC=emmalloc', '-O2'], 130000),
    'dlmalloc_O2': (['-sMALLOC=dlmalloc', '-O2'], 137000),
    'mimalloc_O2': (['-sMALLOC=mimalloc', '-O2'], 193000),
  })
  # This test verifies the output code size of the different -sMALLOC= modes.
  def test_malloc_size(self, args, max_size):
    self.emcc('hello_libcxx.cpp', args=args)
    self.assertLess(os.path.getsize('a.out.wasm'), max_size)

  def test_emmalloc_high_align(self):
    self.do_other_test('test_emmalloc_high_align.c',
                       cflags=['-sMALLOC=emmalloc', '-sINITIAL_MEMORY=128MB'])

  def test_2GB_plus(self):
    # when the heap size can be over 2GB, we rewrite pointers to be unsigned
    def test(page_diff):
      args = [EMCC, test_file('hello_world.c'), '-O2', '-sALLOW_MEMORY_GROWTH']
      if page_diff is not None:
        args += ['-sMAXIMUM_MEMORY=%d' % (2**31 + page_diff * 64 * 1024)]
      print(args)
      self.run_process(args)
      return os.path.getsize('a.out.js')

    less = test(-1)
    equal = test(0)
    more = test(1)
    none = test(None)

    # exactly 2GB still doesn't require unsigned pointers, as we can't address
    # the 2GB location in memory
    self.assertEqual(less, equal)
    self.assertLess(equal, more)
    # not specifying maximum memory does not result in unsigned pointers, as the
    # default maximum memory is 2GB.
    self.assertEqual(less, none)

  @parameterized({
    # atm we only test mimalloc here, as we don't need extra coverage for
    # dlmalloc/emmalloc, and this is the main test we have for mimalloc
    'mimalloc':          ('mimalloc', ['-DWORKERS=1']),
    'mimalloc_pthreads': ('mimalloc', ['-DWORKERS=4', '-pthread']),
  })
  def test_malloc_multithreading(self, allocator, args):
    cflags = [
      '-O2',
      '-DTOTAL=10000',
      '-sINITIAL_MEMORY=128mb',
      '-sTOTAL_STACK=1mb',
      f'-sMALLOC={allocator}',
    ]
    self.do_other_test('test_malloc_multithreading.c', cflags=cflags + args)

  @parameterized({
    '': ([], 'testbind.js'),
    'nobigint': (['-sWASM_BIGINT=0'], 'testbind_nobigint.js'),
  })
  @requires_node
  def test_i64_return_value(self, args, bind_js):
    # This test checks that the most significant 32 bits of a 64 bit long are correctly made available
    # to native JavaScript applications that wish to interact with compiled code returning 64 bit longs.
    # The MS 32 bits should be available in getTempRet0() even when compiled with -O2 --closure 1

    # Compile test.c and wrap it in a native JavaScript binding so we can call our compiled function from JS.
    self.run_process([EMCC, test_file('return64bit/test.c'),
                      '--pre-js', test_file('return64bit/testbindstart.js'),
                      '--pre-js', test_file('return64bit', bind_js),
                      '--post-js', test_file('return64bit/testbindend.js'),
                      '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$dynCall',
                      '-sEXPORTED_RUNTIME_METHODS=getTempRet0',
                      '-sEXPORTED_FUNCTIONS=_test_return64', '-o', 'test.js', '-O2',
                      '--closure=1', '-g1', '-sWASM_ASYNC_COMPILATION=0'] + args)

    # Simple test program to load the test.js binding library and call the binding to the
    # C function returning the 64 bit long.
    create_file('testrun.js', '''
      var test = require("./test.js");
      test.runtest();
    ''')

    # Run the test and confirm the output is as expected.
    out = self.run_js('testrun.js')
    self.assertContained('''\
input = 0xaabbccdd11223344
low = 5678
high = 1234
input = 0xabcdef1912345678
low = 5678
high = 1234
''', out)

  def test_lib_include_flags(self):
    self.run_process([EMCC] + '-l m -l c -I'.split() + [test_file('include_test'), test_file('lib_include_flags.c')])

  def test_dash_s_link_flag(self):
    # -s is also a valid link flag.  We try to distinguish between this case and when
    # its used to set a settings based on looking at the argument that follows.

    # Test the case when -s is the last flag
    self.run_process([EMCC, test_file('hello_world.c'), '-s'])
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

    # Test the case when the following flag is all uppercase but starts with a `-`
    self.run_process([EMCC, test_file('hello_world.c'), '-s', '-DFOO'])
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

    # Test that case when the following flag is not all uppercase
    self.run_process([EMCC, '-s', test_file('hello_world.c')])
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

  def test_dash_s_response_file_string(self):
    create_file('response_file.txt', 'MyModule\n')
    create_file('response_file.json', '"MyModule"\n')
    self.run_process([EMCC, test_file('hello_world.c'), '-sEXPORT_NAME=@response_file.txt'])
    self.run_process([EMCC, test_file('hello_world.c'), '-sEXPORT_NAME=@response_file.json'])

  def test_dash_s_response_file_list(self):
    create_file('response_file.txt', '_main\n_malloc\n')
    create_file('response_file.json', '["_main", "_malloc"]\n')
    self.run_process([EMCC, test_file('hello_world.c'), '-sEXPORTED_FUNCTIONS=@response_file.txt'])
    self.run_process([EMCC, test_file('hello_world.c'), '-sEXPORTED_FUNCTIONS=@response_file.json'])

  def test_dash_s_response_file_list_with_comments(self):
    create_file('response_file.txt', '_main\n#_nope_ish_nope\n_malloc\n')
    self.run_process([EMCC, test_file('hello_world.c'), '-sEXPORTED_FUNCTIONS=@response_file.txt'])

  def test_dash_s_response_file_missing(self):
    expected = 'error: foo: file not found parsing argument: EXPORTED_FUNCTIONS=@foo'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sEXPORTED_FUNCTIONS=@foo'], expected)

  def test_dash_s_unclosed_quote(self):
    # Unclosed quote
    err = self.expect_fail([EMCC, test_file('hello_world.c'), '-s', "TEST_KEY='MISSING_QUOTE"])
    self.assertNotContained('AssertionError', err) # Do not mention that it is an assertion error
    self.assertContained('error: error parsing "-s" setting', err)
    self.assertContained('unclosed quoted string. expected final character to be "\'"', err)

  def test_dash_s_single_quote(self):
    # Only one quote
    err = self.expect_fail([EMCC, test_file('hello_world.c'), "-sTEST_KEY='"])
    self.assertNotContained('AssertionError', err) # Do not mention that it is an assertion error
    self.assertContained('error: error parsing "-s" setting', err)
    self.assertContained('unclosed quoted string.', err)

  def test_dash_s_unclosed_list(self):
    # Unclosed list
    err = self.expect_fail([EMCC, test_file('hello_world.cpp'), "-sTEST_KEY=[Value1, Value2"])
    self.assertNotContained('AssertionError', err) # Do not mention that it is an assertion error
    self.assertContained('error: error parsing "-s" setting', err)
    self.assertContained('unterminated string list. expected final character to be "]"', err)

  def test_dash_s_valid_list(self):
    err = self.expect_fail([EMCC, test_file('hello_world.cpp'), "-sTEST_KEY=[Value1, \"Value2\"]"])
    self.assertNotContained('error parsing "-s" setting', err)

  def test_dash_s_wrong_type(self):
    expected = 'error: setting `EXIT_RUNTIME` expects `bool` but got `list`'
    self.assert_fail([EMCC, test_file('hello_world.cpp'), '-sEXIT_RUNTIME=[foo,bar]'], expected)
    expected = 'error: attempt to set `EXIT_RUNTIME` to `true`; use 1/0 to set boolean setting'
    self.assert_fail([EMCC, test_file('hello_world.cpp'), '-sEXIT_RUNTIME=true'], expected)

  def test_dash_s_typo(self):
    # with suggestions
    stderr = self.expect_fail([EMCC, test_file('hello_world.c'), '-sDISABLE_EXCEPTION_CATCH'])
    self.assertContained("Attempt to set a non-existent setting: 'DISABLE_EXCEPTION_CATCH'", stderr)
    self.assertContained('did you mean one of DISABLE_EXCEPTION_CATCHING', stderr)
    # no suggestions
    stderr = self.expect_fail([EMCC, test_file('hello_world.c'), '-sCHEEZ'])
    self.assertContained("perhaps a typo in emcc\'s  -sX=Y  notation?", stderr)
    self.assertContained('(see src/settings.js for valid values)', stderr)
    # suggestions do not include renamed legacy settings
    stderr = self.expect_fail([EMCC, test_file('hello_world.c'), '-sZBINARYEN_ASYNC_COMPILATION'])
    self.assertContained("Attempt to set a non-existent setting: 'ZBINARYEN_ASYNC_COMPILATION'", stderr)
    self.assertNotContained(' BINARYEN_ASYNC_COMPILATION', stderr)

  def test_dash_s_with_space(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-s', 'EXPORT_ALL'])
    self.assert_fail([EMCC, test_file('hello_world.c'), '-s', 'EXPORTED_FUNCTIONS=foo'], 'error: undefined exported symbol: "foo"')

  def test_dash_s_hex(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-nostdlib', '-sERROR_ON_UNDEFINED_SYMBOLS=0'])
    # Ensure that 0x0 is parsed as a zero and not as the string '0x0'.
    self.run_process([EMCC, test_file('hello_world.c'), '-nostdlib', '-sERROR_ON_UNDEFINED_SYMBOLS=0x0'])

  def test_dash_s_bad_json_types(self):
    # Dict rather than string/list
    expected = "settings must be strings or lists (not <class 'dict'>"
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sEXPORTED_FUNCTIONS={"a":1}'], expected)

    # List element is not a string
    expected = "list members in settings must be strings (not <class 'dict'>)"
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sEXPORTED_FUNCTIONS=[{"a":1}]'], expected)

  def test_zeroinit(self):
    create_file('src.c', r'''
#include <stdio.h>
int buf[1048576];
int main() {
  printf("Hello, world! %d\n", buf[123456]);
  return 0;
}
''')
    self.run_process([EMCC, 'src.c', '-O2'])
    size = os.path.getsize('a.out.wasm')
    # size should be much smaller than the size of that zero-initialized buffer
    self.assertLess(size, 123456 / 2)

  def test_canonicalize_nan_warning(self):
    create_file('src.c', r'''
#include <stdio.h>

union U {
  int x;
  float y;
} a;

int main() {
  a.x = 0x7FC01234;
  printf("%f\n", a.y);
  printf("0x%x\n", a.x);
  return 0;
}
''')

    self.run_process([EMCC, 'src.c', '-O1'])
    out = self.run_js('a.out.js')
    self.assertContained('nan\n', out)
    self.assertContained('0x7fc01234\n', out)

  def test_EM_ASM_i64(self):
    self.do_other_test('test_em_asm_i64.cpp')
    self.do_other_test('test_em_asm_i64.cpp', force_c=True)

  def test_EM_ASM_i64_nobigint(self):
    self.set_setting('WASM_BIGINT', 0)
    self.do_runf('other/test_em_asm_i64.cpp', 'Invalid character 106("j") in readEmAsmArgs!', assert_returncode=NON_ZERO)

  def test_eval_ctor_ordering(self):
    # ensure order of execution remains correct, even with a bad ctor
    def test(p1, p2, p3, last, expected):
      src = r'''
        #include <stdio.h>
        #include <stdlib.h>
        volatile int total = 0;
        struct C {
          C(int x) {
            volatile int y = x;
            y++;
            y--;
            if (y == 0xf) {
              // A printf can't be evalled ahead of time, so this will stop
              // us.
              printf("you can't eval me ahead of time\n");
            }
            total <<= 4;
            total += int(y);
          }
        };
        C __attribute__((init_priority(%d))) c1(0x5);
        C __attribute__((init_priority(%d))) c2(0x8);
        C __attribute__((init_priority(%d))) c3(%d);
        int main() {
          printf("total is 0x%%x.\n", total);
        }
      ''' % (p1, p2, p3, last)
      create_file('src.cpp', src)
      self.run_process([EMXX, 'src.cpp', '-O2', '-sEVAL_CTORS', '-profiling-funcs'])
      self.assertContained('total is %s.' % hex(expected), self.run_js('a.out.js'))
      shutil.copy('a.out.js', 'x' + hex(expected) + '.js')
      shutil.copy('a.out.wasm', 'x' + hex(expected) + '.wasm')
      return os.path.getsize('a.out.wasm')

    print('no bad ctor')
    first  = test(1000, 2000, 3000, 0xe, 0x58e) # ruff: ignore[multiple-spaces-before-operator]
    second = test(3000, 1000, 2000, 0xe, 0x8e5)
    third  = test(2000, 3000, 1000, 0xe, 0xe58) # ruff: ignore[multiple-spaces-before-operator]
    print(first, second, third)
    assert first == second and second == third
    print('with bad ctor')
    first  = test(1000, 2000, 3000, 0xf, 0x58f) # ruff: ignore[multiple-spaces-before-operator],  2 will succeed
    second = test(3000, 1000, 2000, 0xf, 0x8f5) # 1 will succeed
    third  = test(2000, 3000, 1000, 0xf, 0xf58) # ruff: ignore[multiple-spaces-before-operator],  0 will succeed
    print(first, second, third)
    self.assertLess(first, second)
    self.assertLess(second, third)

  @uses_canonical_tmp
  @with_env_modify({'EMCC_DEBUG': '1'})
  def test_eval_ctors_debug_output(self):
    create_file('lib.js', r'''
addToLibrary({
  external_thing: () => {}
});
''')
    create_file('src.cpp', r'''
extern "C" void external_thing();
struct C {
  C() { external_thing(); } // don't remove this!
};
C c;
int main() {}
    ''')
    err = self.run_process([EMXX, 'src.cpp', '--js-library', 'lib.js', '-O2', '-sEVAL_CTORS'], stderr=PIPE).stderr
    # logging should show we failed, and why
    self.assertNotContained('ctor_evaller: not successful', err)
    self.assertContained('could not eval: call import: env.external_thing', err)

  @uses_canonical_tmp
  @with_env_modify({'EMCC_DEBUG': '1'})
  @parameterized({
    # StackIR optimizations should happen once at the end, and not multiple
    # times in the middle. In -O2 we simply do them in the main wasm-opt
    # invocation, while in -O3 we run wasm-opt later, and so we should disable
    # StackIR first and enable it at the end.
    'O2': (['-O2'], False),
    'O3': (['-O3'], True),
  })
  def test_binaryen_stack_ir(self, opts, disable_and_enable):
    err = self.run_process([EMXX, test_file('hello_world.c')] + opts, stderr=PIPE).stderr
    DISABLE = '--no-stack-ir'
    ENABLE = '--optimize-stack-ir'
    self.assertContainedIf(DISABLE, err, disable_and_enable)
    self.assertContainedIf(ENABLE, err, disable_and_enable)
    # they should also appear at most once each
    self.assertLess(err.count(DISABLE), 2)
    self.assertLess(err.count(ENABLE), 2)

  @also_with_minimal_runtime
  def test_run_order(self):
    create_file('pre.js', r'''
var Module = {
  preRun: [() => console.log('modulePreRun 0')],
  postRun: [() => console.log('modulePostRun 0')],
  preInit: [() => console.log('modulePreInit 0'), () => console.log('modulePreInit 1')]
};
    ''')
    create_file('post.js', r'''
Module['preRun'].push(() => console.log('modulePreRun 1'));
Module['postRun'].push(() => console.log('modulePostRun 1'));
addOnPreRun(() => console.log("addOnPreRun 0"));
addOnPreRun(() => console.log("addOnPreRun 1"));
addOnInit(() => console.log("addOnInit 0"));
addOnInit(() => console.log("addOnInit 1"));
addOnPostCtor(() => console.log("addOnPostCtor 0"));
addOnPostCtor(() => console.log("addOnPostCtor 1"));
addOnPreMain(() => console.log("addOnPreMain 0"));
addOnPreMain(() => console.log("addOnPreMain 1"));
addOnExit(() => console.log("addOnExit 0"));
addOnExit(() => console.log("addOnExit 1"));
addOnPostRun(() => console.log("addOnPostRun 0"));
addOnPostRun(() => console.log("addOnPostRun 1"));
    ''')
    create_file('lib.js', r'''
addToLibrary({
  foo__postset: () => {
    // Add all the compile time equivalents of the above runtime events.
    addAtPreRun("console.log(`addAtPreRun 0`);");
    addAtPreRun("console.log(`addAtPreRun 1`);");
    addAtInit("console.log(`addAtInit 0`);");
    addAtInit("console.log(`addAtInit 1`);");
    addAtPostCtor("console.log(`addAtPostCtor 0`);");
    addAtPostCtor("console.log(`addAtPostCtor 1`);");
    addAtPreMain("console.log(`addAtPreMain 0`);");
    addAtPreMain("console.log(`addAtPreMain 1`);");
    addAtExit("console.log(`addAtExit 0`);");
    addAtExit("console.log(`addAtExit 1`);");
    addAtPostRun("console.log(`addAtPostRun 0`);");
    addAtPostRun("console.log(`addAtPostRun 1`);");
  },
  foo: () => {},
});
''')
    create_file('src.c', r'''
    #include <stdio.h>
    void foo();
    __attribute__((constructor)) void ctor() {
      printf("ctor\n");
    }
    int main() {
      printf("main\n");
      foo();
    }
    ''')
    # Each element is [<log message>, <applies to minimal runtime>]
    expected_order = [
      ['modulePreInit 0', False], # Not supported in minimal runtime.
      ['modulePreInit 1', False], # Not supported in minimal runtime.
      ['addOnPreRun 0', True],
      ['addOnPreRun 1', True],
      ['modulePreRun 0', False],  # Not supported in minimal runtime.
      ['modulePreRun 1', False],  # Not supported in minimal runtime.
      ['addAtPreRun 0', True],
      ['addAtPreRun 1', True],
      ['addOnInit 0', True],
      ['addOnInit 1', True],
      ['addAtInit 0', True],
      ['addAtInit 1', True],
      ['ctor', True],
      ['addOnPostCtor 0', True],
      ['addOnPostCtor 1', True],
      ['addAtPostCtor 0', True],
      ['addAtPostCtor 1', True],
      ['addOnPreMain 0', True],
      ['addOnPreMain 1', True],
      ['addAtPreMain 0', True],
      ['addAtPreMain 1', True],
      ['main', True],
      ['addOnExit 0', True],
      ['addOnExit 1', True],
      ['addAtExit 0', True],
      ['addAtExit 1', True],
      ['addOnPostRun 0', False],  # not with EXIT_RUNTIME (throws an exception during _proc_exit).
      ['addOnPostRun 1', False],  # not with EXIT_RUNTIME (throws an exception during _proc_exit).
      ['modulePostRun 0', False], # Not supported in minimal runtime.
      ['modulePostRun 1', False], # Not supported in minimal runtime.
      ['addAtPostRun 0', False],  # not with EXIT_RUNTIME (throws an exception during _proc_exit).
      ['addAtPostRun 1', False],   # not with EXIT_RUNTIME (throws an exception during _proc_exit).
    ]
    if self.get_setting('MINIMAL_RUNTIME'):
      expected_order = [item[0] for item in expected_order if item[1]]
    else:
      expected_order = [item[0] for item in expected_order]
    self.cflags += [
      '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$addOnPreRun,$addOnInit,$addOnPostCtor,$addOnPreMain,$addOnExit,$addOnPostRun',
      '--js-library', 'lib.js',
      '--pre-js', 'pre.js',
      '--post-js', 'post.js',
      '-sEXIT_RUNTIME',
    ]
    self.do_runf('src.c', '\n'.join(expected_order))

  def test_override_js_execution_environment(self):
    create_file('main.c', r'''
      #include <emscripten.h>
      int main() {
        EM_ASM({
          out('environment is WEB? ' + ENVIRONMENT_IS_WEB);
          out('environment is WORKER? ' + ENVIRONMENT_IS_WORKER);
          out('environment is NODE? ' + ENVIRONMENT_IS_NODE);
          out('environment is SHELL? ' + ENVIRONMENT_IS_SHELL);
        });
      }
''')
    self.run_process([EMCC, 'main.c', '-sENVIRONMENT=node,shell'])
    src = read_file('a.out.js')
    for env in ['web', 'worker', 'node', 'shell']:
      for engine in config.JS_ENGINES:
        if engine_is_node(engine):
          actual = 'NODE'
        else:
          actual = 'SHELL'
        print(env, actual, engine)
        module = {'ENVIRONMENT': env}
        if env != actual:
          # avoid problems with arguments detection, which may cause very odd failures with the wrong environment code
          module['arguments'] = []
        curr = 'var Module = %s;\n' % str(module)
        print('    ' + curr)
        create_file('test.js', curr + src)
        seen = self.run_js('test.js', engine=engine, assert_returncode=NON_ZERO)
        self.assertContained('Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node', seen)

  @requires_node
  @with_env_modify({'FOO': 'bar'})
  @parameterized({
    '': ([], '|(null)|\n'),
    'rawfs': (['-sNODERAWFS'], '|bar|\n'),
    'rawfs_no_env': (['-sNODERAWFS', '-sNODE_HOST_ENV=0'], '|(null)|\n'),
    'enabled': (['-sNODE_HOST_ENV'], '|bar|\n'),
  })
  def test_node_host_env(self, args, expected):
     create_file('src.c', r'''
      #include <stdlib.h>
      #include <stdio.h>
      int main() {
        printf("|%s|\n", getenv("FOO"));
      }
    ''')
     self.do_runf('src.c', expected, cflags=args)

  def test_override_c_environ(self):
    create_file('pre.js', r'''
      Module.preRun = () => { ENV.hello = '💩 world'; ENV.LANG = undefined; }
    ''')
    create_file('src.c', r'''
      #include <stdlib.h>
      #include <stdio.h>
      int main() {
        printf("|%s|\n", getenv("hello"));
        printf("LANG is %s\n", getenv("LANG") ? "set" : "not set");
      }
    ''')
    self.run_process([EMCC, 'src.c', '--pre-js', 'pre.js'])
    output = self.run_js('a.out.js')
    self.assertContained('|💩 world|', output)
    self.assertContained('LANG is not set', output)

    create_file('pre.js', r'''
      Module.preRun = (module) => { module.ENV.hello = 'world' }
    ''')
    self.run_process([EMCC, 'src.c', '--pre-js', 'pre.js', '-sEXPORTED_RUNTIME_METHODS=ENV'])
    self.assertContained('|world|', self.run_js('a.out.js'))

    self.run_process([EMCC, 'src.c', '--pre-js', 'pre.js', '-sEXPORTED_RUNTIME_METHODS=ENV', '-sMODULARIZE'])
    create_file('run.js', 'require("./a.out.js")();')
    output = self.run_js('run.js')
    self.assertContained('|world|', output)

  @also_with_wasmfs
  def test_warn_no_filesystem(self):
    error = 'Filesystem support (FS) was not included. The problem is that you are using files from JS, but files were not used from C/C++, so filesystem support was not auto-included. You can force-include filesystem support with -sFORCE_FILESYSTEM'

    self.emcc(test_file('hello_world.c'))
    seen = self.run_js('a.out.js')
    self.assertNotContained(error, seen)

    def test(contents):
      create_file('src.c', r'''
  #include <stdio.h>
  #include <emscripten.h>
  int main() {
    EM_ASM({ %s });
    printf("Hello, world!\n");
    return 0;
  }
  ''' % contents)
      self.do_runf('src.c', error, assert_returncode=NON_ZERO)

    # might appear in handwritten code
    test("FS.init()")
    test("FS.createPreloadedFile('waka waka, just warning check')")
    test("FS.createDataFile('waka waka, just warning check')")
    test("FS.analyzePath('waka waka, just warning check')")
    # might appear in filesystem code from a separate script tag
    test("Module['FS_createDataFile']('waka waka, just warning check')")
    test("Module['FS_createPreloadedFile']('waka waka, just warning check')")

    # text is in the source when needed, but when forcing FS, it isn't there
    self.emcc('src.c')
    self.assertContained(error, read_file('a.out.js'))
    self.emcc('src.c', args=['-sFORCE_FILESYSTEM']) # forcing FS means no need
    self.assertNotContained(error, read_file('a.out.js'))
    self.emcc('src.c', args=['-sASSERTIONS=0']) # no assertions, no need
    self.assertNotContained(error, read_file('a.out.js'))
    self.emcc('src.c', args=['-O2']) # optimized, so no assertions
    self.assertNotContained(error, read_file('a.out.js'))

  def test_warn_module_out_err(self):
    def test(contents, expected, args=[], assert_returncode=0):  # ruff: ignore[mutable-argument-default]
      create_file('src.c', r'''
        #include <emscripten.h>
        int main() {
          EM_ASM({ %s });
          return 0;
        }
        ''' % contents)
      self.do_runf('src.c', expected, cflags=args, assert_returncode=assert_returncode)

    # error shown (when assertions are on)
    error = 'was not exported. add it to EXPORTED_RUNTIME_METHODS (see the Emscripten FAQ)'
    test("Module.out('x')", error, assert_returncode=NON_ZERO)
    test("Module['out']('x')", error, assert_returncode=NON_ZERO)
    test("Module.err('x')", error, assert_returncode=NON_ZERO)
    test("Module['err']('x')", error, assert_returncode=NON_ZERO)

    # when exported, all good
    test("Module['out']('print'); Module['err']('err'); ", 'print\nerr', ['-sEXPORTED_RUNTIME_METHODS=out,err'])

    # test backwards compatibility
    test("Module['print']('print'); Module['printErr']('err'); ", 'print\nerr', ['-sEXPORTED_RUNTIME_METHODS=print,printErr', '-Wno-js-compiler'])

  @parameterized({
    '': ('hello_world.c',),
    'argv': ('hello_world_argv.c',),
  })
  @parameterized({
    '': ([],),
    'O2': (['-O2'],),
  })
  def test_warn_unexported_main(self, filename, args):
    warning = 'emcc: warning: `main` is defined in the input files, but `_main` is not in `EXPORTED_FUNCTIONS`. Add it to this list if you want `main` to run. [-Wunused-main]'

    proc = self.run_process([EMCC, test_file(filename), '-sEXPORTED_FUNCTIONS=[]'] + args, stderr=PIPE)
    # This warning only shows up when ASSERTIONS are enabled.
    # We run both ways those to ensure that main doesn't get run in either case.
    if '-O2' in args:
      self.assertNotContained(warning, proc.stderr)
    else:
      self.assertContained(warning, proc.stderr)

    # Verify that main indeed does not run
    output = self.run_js('a.out.js')
    self.assertEqual('', output)

  def test_no_entry_with_assertions(self):
    self.run_process([EMCC, test_file('hello_world.c'), '--no-entry', '-sASSERTIONS'])
    self.assertNotContained('Hello, world!', self.run_js('a.out.js'))

  def test_source_file_with_fixed_language_mode(self):
    create_file('src_tmp_fixed_lang', '''
#include <string>
#include <iostream>

int main() {
  std::cout << "Test_source_fixed_lang_hello" << std::endl;
  return 0;
}
    ''')
    self.run_process([EMXX, '-Wall', '-x', 'c++', 'src_tmp_fixed_lang'])
    self.assertContained('Test_source_fixed_lang_hello', self.run_js('a.out.js'))

    self.assert_fail([EMXX, '-Wall', 'src_tmp_fixed_lang'], 'unknown file type: src_tmp_fixed_lang')

  def test_disable_inlining(self):
    create_file('test.c', r'''
#include <stdio.h>

static void foo() {
  printf("foo\n");
}

int main() {
  foo();
  return 0;
}
''')

    # Without the 'INLINING_LIMIT', -O2 inlines foo() and then DCEs it because it has
    # no callers and is static
    cmd = [EMCC, 'test.c', '-O2', '-o', 'test.o', '-c', '-sINLINING_LIMIT']
    self.run_process(cmd)
    output = self.run_process([common.LLVM_OBJDUMP, '-t', 'test.o'], stdout=PIPE).stdout
    self.assertContained('foo', output)

  @crossplatform
  @parameterized({
    '': ([],),
    'single_file': (['-sSINGLE_FILE'],),
  })
  def test_output_eol(self, params):
    for eol in ('windows', 'linux'):
      self.clear()
      print('checking eol: ', eol)
      if '-sSINGLE_FILE' not in params:
        params.append('-oa.out.html')
      self.run_process([EMCC, test_file('hello_world.c'), '--output-eol', eol] + params)
      out_files = ['a.out.js']
      html_file = 'a.out.html'
      if '-sSINGLE_FILE' in params:
        self.assertNotExists(html_file)
      else:
        out_files.append(html_file)
      for f in out_files:
        self.assertExists(f)
        if eol == 'linux':
          expected_ending = '\n'
        else:
          expected_ending = '\r\n'
        self.assertEqual(line_endings.check_line_endings(f, expect_only=expected_ending), 0, f'expected only {eol} line endings in {f}')

  def test_bad_memory_size(self):
    # if user changes INITIAL_MEMORY at runtime, the wasm module may not accept the memory import if
    # it is too big/small
    create_file('pre.js', 'Module.INITIAL_MEMORY = 50 * 1024 * 1024')
    out = self.do_runf('hello_world.c', cflags=['-sINITIAL_MEMORY=16mb', '--pre-js', 'pre.js', '-sIMPORTED_MEMORY'], assert_returncode=NON_ZERO)
    self.assertContained('LinkError', out)
    # Different engines have slightly different error message syntax.
    expected = [
      "LinkError: Memory import env:memory provided a 'maximum' that is larger than the module's declared 'maximum' import memory size",
      "LinkError: imported Memory with incompatible size",
      "has a larger maximum size 800 than the module's declared maximum",
    ]
    self.assertContained(expected, out)
    self.assertNotContained('Hello, world!', out)
    # and with memory growth, all should be good
    self.do_runf_out_file('hello_world.c', cflags=['-sINITIAL_MEMORY=16mb', '--pre-js', 'pre.js', '-sALLOW_MEMORY_GROWTH', '-sIMPORTED_MEMORY'])

  @parameterized({
    '': ([], 16 * 1024 * 1024), # Default behavior: 16MB initial heap
    'explicit': (['-sINITIAL_HEAP=64KB'], 64 * 1024), # Explicitly set initial heap is passed
    'with_initial_memory': (['-sINITIAL_MEMORY=40MB'], 0), # Backwards compatibility: no initial heap (we can't tell if it'll fit)
    'with_maximum_memory': (['-sMAXIMUM_MEMORY=40MB', '-sALLOW_MEMORY_GROWTH=1'], 0), # Backwards compatibility: no initial heap (we can't tell if it'll fit)
    'with_all': (['-sINITIAL_HEAP=128KB', '-sINITIAL_MEMORY=20MB', '-sMAXIMUM_MEMORY=40MB', '-sALLOW_MEMORY_GROWTH=1'], 128 * 1024),
    'limited_by_initial_memory': (['-sINITIAL_HEAP=10MB', '-sINITIAL_MEMORY=10MB'], None), # Not enough space for stack
    'limited_by_maximum_memory': (['-sINITIAL_HEAP=5MB', '-sMAXIMUM_MEMORY=5MB', '-sALLOW_MEMORY_GROWTH=1'], None), # Not enough space for stack
  })
  def test_initial_heap(self, args, expected_initial_heap):
    cmd = [EMCC, test_file('hello_world.c'), '-v'] + args

    if expected_initial_heap is None:
      self.assert_fail(cmd, 'wasm-ld: error:')
      return

    out = self.run_process(cmd, stderr=PIPE)
    if expected_initial_heap != 0:
      self.assertContained(f'--initial-heap={expected_initial_heap}', out.stderr)
    else:
      self.assertNotContained('--initial-heap=', out.stderr)

  def test_memory_size(self):
    for args, expect_initial, expect_max in [
        ([], 320, 320),
        (['-sALLOW_MEMORY_GROWTH'], 320, 32768),
        (['-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=40MB'], 320, 640),
      ]:
      cmd = [EMCC, test_file('hello_world.c'), '-O2', '-sINITIAL_MEMORY=20MB'] + args
      print(' '.join(cmd))
      self.run_process(cmd)
      wat = self.get_wasm_text('a.out.wasm')
      memories = [l for l in wat.splitlines() if '(memory ' in l]
      self.assertEqual(len(memories), 2)
      line = memories[0]
      parts = line.strip().replace('(', '').replace(')', '').split()
      print(parts)
      self.assertEqual(parts[2], str(expect_initial))
      self.assertEqual(parts[3], str(expect_max))

  def test_invalid_mem(self):
    # A large amount is fine, multiple of 16MB or not
    self.run_process([EMCC, test_file('hello_world.c'), '-sINITIAL_MEMORY=33MB'])
    self.run_process([EMCC, test_file('hello_world.c'), '-sINITIAL_MEMORY=32MB'])

    # A tiny amount is fine in wasm
    self.run_process([EMCC, test_file('hello_world.c'), '-sINITIAL_MEMORY=65536', '-sSTACK_SIZE=1024'])
    # And the program works!
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

    # Must be a multiple of 64KB
    expected = 'INITIAL_HEAP must be a multiple of WebAssembly page size (64KiB)'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sINITIAL_HEAP=32505857', '-sALLOW_MEMORY_GROWTH'], expected) # 31MB + 1 byte

    expected = 'INITIAL_MEMORY must be a multiple of WebAssembly page size (64KiB)'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sINITIAL_MEMORY=33554433'], expected) # 32MB + 1 byte

    self.run_process([EMCC, test_file('hello_world.c'), '-sMAXIMUM_MEMORY=33MB', '-sALLOW_MEMORY_GROWTH'])

    expected = 'MAXIMUM_MEMORY must be a multiple of WebAssembly page size (64KiB)'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sMAXIMUM_MEMORY=34603009', '-sALLOW_MEMORY_GROWTH'], expected) # 33MB + 1 byte

  def test_invalid_memory_max(self):
    expected = 'emcc: error: MAXIMUM_MEMORY is only meaningful with ALLOW_MEMORY_GROWTH'
    self.assert_fail([EMCC, '-Werror', test_file('hello_world.c'), '-sMAXIMUM_MEMORY=41943040'], expected)

  def test_dasho_invalid_dir(self):
    expected = 'specified output file (NONEXISTING_DIRECTORY/out.js) is in a directory that does not exist'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-o', 'NONEXISTING_DIRECTORY/out.js'], expected)

  def test_dasho_is_dir(self):
    self.assert_fail([EMCC, test_file('hello_world.c'), '-o', '.'], 'emcc: error: cannot write output file `.`: Is a directory')

    ret = self.expect_fail([EMCC, test_file('hello_world.c'), '-o', '.', '--oformat=wasm'])
    self.assertContained('wasm-ld: error: cannot open output file .:', ret)
    # Linux/Mac and Windows's error messages are slightly different
    self.assertContained(['Is a directory', 'is a directory'], ret)

    self.assert_fail([EMCC, test_file('hello_world.c'), '-o', '.', '--oformat=html'], 'emcc: error: cannot write output file `.`: Is a directory')

  @parameterized({
    '': ([],),
    'main_module': (['-sMAIN_MODULE'],),
  })
  def test_ctor_ordering(self, args):
    # ctor order must be identical to js builds, deterministically
    create_file('src.cpp', r'''
      #include <stdio.h>
      struct A {
        A() { puts("constructing A!"); }
      };
      A a;
      struct B {
        B() { puts("constructing B!"); }
      };
      B b;
      int main() {}
    ''')
    self.do_runf('src.cpp', 'constructing A!\nconstructing B!\n', cflags=args)

  # test debug info and debuggability of JS output
  @crossplatform
  def test_binaryen_debug(self):
    for args, expect_clean_js, expect_whitespace_js, expect_closured in [
        (['-O0'], False, True, False),
        (['-O0', '-g1'], False, True, False),
        (['-O0', '-g2'], False, True, False),
        (['-O0', '-g'], False, True, False),
        (['-O0', '--profiling-funcs'], False, True, False),
        (['-O0', '-gline-tables-only'], False, True, False),
        (['-O1'], False, True, False),
        (['-O3'], True, False, False),
        (['-Oz', '-gsource-map'], True, False, False),
        (['-O2'], True,  False, False),
        (['-O2', '-gz'], True,  False, False), # -gz means debug compression, it should not enable debugging
        (['-O2', '-g1'], False, True, False),
        (['-O2', '-g'],  False, True, False),
        (['-O2', '--closure=1'], True, False, True),
        (['-O2', '--closure=1', '-g1'], True, True,  True),
        (['-O2', '--minify=0'], False, True, False),
        (['-O2', '--profiling-funcs'], True, False, False),
        (['-O2', '--profiling'], False, True, False),
      ]:
      print(args, expect_clean_js, expect_whitespace_js, expect_closured)
      delete_file('a.out.wat')
      cmd = [EMCC, test_file('hello_world.c')] + args
      print(' '.join(cmd))
      self.run_process(cmd)
      js = read_file('a.out.js')
      assert expect_clean_js == ('// ' not in js), 'cleaned-up js must not have comments'
      assert expect_whitespace_js == ('{\n  ' in js), 'whitespace-minified js must not have excess spacing'
      assert expect_closured == ('var a;' in js or 'var a,' in js or 'var a=' in js or 'var a ' in js), 'closured js must have tiny variable names'

  @uses_canonical_tmp
  def test_binaryen_ignore_implicit_traps(self):
    sizes = []
    with env_modify({'EMCC_DEBUG': '1'}):
      for args, expect in [
          ([], False),
          (['-sBINARYEN_IGNORE_IMPLICIT_TRAPS'], True),
        ]:
        print(args, expect)
        cmd = [EMXX, test_file('hello_libcxx.cpp'), '-O3'] + args
        print(' '.join(cmd))
        err = self.run_process(cmd, stdout=PIPE, stderr=PIPE).stderr
        self.assertContainedIf('--ignore-implicit-traps ', err, expect)
        sizes.append(os.path.getsize('a.out.wasm'))
    print('sizes:', sizes)
    # sizes must be different, as the flag has an impact
    self.assertEqual(len(set(sizes)), 2)

  def test_binaryen_passes_extra(self):
    def build(args):
      return self.run_process([EMCC, test_file('hello_world.c'), '-O3'] + args, stdout=PIPE).stdout

    build([])
    base_size = os.path.getsize('a.out.wasm')
    out = build(['-sBINARYEN_EXTRA_PASSES=--metrics'])
    # and --metrics output appears
    self.assertContained('[funcs]', out)
    # adding --metrics should not affect code size
    self.assertEqual(base_size, os.path.getsize('a.out.wasm'))

  def test_exported_runtime_methods_metadce(self):
    exports = ['stackSave', 'stackRestore', 'stackAlloc', 'FS']
    self.run_process([EMCC, test_file('hello_world.c'), '-Os', '-sEXPORTED_RUNTIME_METHODS=%s' % ','.join(exports)])
    js = read_file('a.out.js')
    for export in exports:
      self.assertContained(f'Module["{export}"]', js)

  @parameterized({
    'legal_side_O1': (['-sLEGALIZE_JS_FFI=1', '-sSIDE_MODULE', '-O1'], True),
    'nolegal_side_O1': (['-sLEGALIZE_JS_FFI=0', '-sSIDE_MODULE', '-O1'], False),
    'nolegal_side_O0': (['-sLEGALIZE_JS_FFI=0', '-sSIDE_MODULE', '-O0'], False),
    'legal_O0': (['-sLEGALIZE_JS_FFI=1', '-sWARN_ON_UNDEFINED_SYMBOLS=0', '-O0'], True),
    'nolegal_O0': (['-sLEGALIZE_JS_FFI=0', '-sWARN_ON_UNDEFINED_SYMBOLS=0', '-O0'], False),
  })
  def test_legalize_js_ffi(self, args, js_ffi):
    # test disabling of JS FFI legalization when not using bigint
    print(args)
    delete_file('a.out.wasm')
    cmd = [EMCC, test_file('other/ffi.c'), '-g', '-o', 'a.out.wasm', '-sWASM_BIGINT=0'] + args
    print(' '.join(cmd))
    self.run_process(cmd)
    text = self.get_wasm_text('a.out.wasm')
    # remove internal comments and extra whitespace
    text = re.sub(r'\(;[^;]+;\)', '', text)
    text = re.sub(r'\$var\$*.', '', text)
    text = re.sub(r'param \$\d+', 'param ', text)
    text = re.sub(r' +', ' ', text)
    e_add_f32 = re.search(r'func \$add_f (\(type .*\) )?\(param f32\) \(param f32\) \(result f32\)', text)
    assert e_add_f32, 'add_f export missing'
    i_i64_i32 = re.search(r'import "env" "import_ll" .*\(param i32 i32\) \(result i32\)', text)
    i_i64_i64 = re.search(r'import "env" "import_ll" .*\(param i64\) \(result i64\)', text)
    e_i64_i32 = re.search(r'func \$legalstub\$add_ll (\(type .*\) )?\(param i32\) \(param i32\) \(param i32\) \(param i32\) \(result i32\)', text)
    if js_ffi:
      assert i_i64_i32,     'i64 not converted to i32 in imports'
      assert not i_i64_i64, 'i64 not converted to i32 in imports'
      assert e_i64_i32,     'i64 not converted to i32 in exports'
    else:
      assert not i_i64_i32, 'i64 converted to i32 in imports'
      assert i_i64_i64,     'i64 converted to i32 in imports'
      assert not e_i64_i32, 'i64 converted to i32 in exports'

  @disabled('https://github.com/WebAssembly/binaryen/pull/6428')
  def test_no_legalize_js_ffi(self):
    for legalizing in (0, 1):
      # test minimal JS FFI legalization for invoke and dyncalls
      args = ['-sMAIN_MODULE=2', '-O3', '-sDISABLE_EXCEPTION_CATCHING=0', '-g']
      if not legalizing:
        args.append('-sLEGALIZE_JS_FFI=0')
      self.run_process([EMXX, test_file('other/noffi.cpp')] + args)
      text = self.get_wasm_text('a.out.wasm')
      # Verify that legalization either did, or did not, occur
      self.assertContainedIf('$legalimport', text, legalizing)
      self.assertContainedIf('$legalstub', text, legalizing)

  def test_export_aliasee(self):
    # build side module
    args = ['-sSIDE_MODULE']
    cmd = [EMCC, test_file('other/alias/side.c'), '-g', '-o', 'side.wasm'] + args
    print(' '.join(cmd))
    self.run_process(cmd)

    # build main module
    args = ['-g', '-sEXPORTED_FUNCTIONS=_main,_foo', '-sMAIN_MODULE=2', '-sNODERAWFS']
    self.do_runf('other/alias/main.c', 'done\n', cflags=args)

  def test_sysconf_phys_pages(self):
    def run(args, expected):
      cmd = [EMCC, test_file('unistd/sysconf_phys_pages.c')] + args
      print(str(cmd))
      self.run_process(cmd)
      result = self.run_js('a.out.js').strip()
      self.assertEqual(result, f'{expected}, errno: 0')

    run([], 258)
    run(['-sINITIAL_MEMORY=32MB'], 512)
    run(['-sINITIAL_MEMORY=32MB', '-sALLOW_MEMORY_GROWTH'], (2 * 1024 * 1024 * 1024) // webassembly.WASM_PAGE_SIZE)
    run(['-sINITIAL_MEMORY=32MB', '-sALLOW_MEMORY_GROWTH', '-sWASM=0'], (2 * 1024 * 1024 * 1024) // webassembly.WASM_PAGE_SIZE)

  def test_wasm_target_and_STANDALONE_WASM(self):
    # STANDALONE_WASM means we never minify imports and exports.
    for opts, potentially_expect_minified_exports_and_imports in (
      ([],                           False),
      (['-sSTANDALONE_WASM'],        False),
      (['-O2'],                      False),
      (['-O3'],                      True),
      (['-O3', '-sSTANDALONE_WASM'], False),
      (['-Os'],                      True),
    ):
      # targeting .wasm (without .js) means we enable STANDALONE_WASM automatically, and don't minify imports/exports
      for target in ('out.js', 'out.wasm'):
        expect_minified_exports_and_imports = potentially_expect_minified_exports_and_imports and target.endswith('.js')
        standalone = target.endswith('.wasm') or 'STANDALONE_WASM' in opts
        print(opts, potentially_expect_minified_exports_and_imports, target, ' => ', expect_minified_exports_and_imports, standalone)

        self.clear()
        self.run_process([EMCC, test_file('hello_world.c'), '-o', target] + opts)
        self.assertExists('out.wasm')
        if target.endswith('.wasm'):
          # only wasm requested
          self.assertNotExists('out.js')
        wat = self.get_wasm_text('out.wasm')
        wat_lines = wat.split('\n')
        exports = [line.strip().split(' ')[1].replace('"', '') for line in wat_lines if "(export " in line]
        imports = [line.strip().split(' ')[2].replace('"', '') for line in wat_lines if "(import " in line]
        exports_and_imports = exports + imports
        print('  exports', exports)
        print('  imports', imports)
        if expect_minified_exports_and_imports:
          self.assertContained('a', exports_and_imports)
        else:
          self.assertNotContained('a', exports_and_imports)
        if standalone:
          self.assertContained('fd_write', exports_and_imports, 'standalone mode preserves import names for WASI APIs')
        # verify the wasm runs with the JS
        if target.endswith('.js'):
          self.assertContained('Hello, world!', self.run_js('out.js'))
        # verify a standalone wasm
        if standalone:
          for engine in config.WASM_ENGINES:
            print(engine)
            self.assertContained('Hello, world!', self.run_js('out.wasm', engine=engine))

  def test_side_module_naming(self):
    # SIDE_MODULE should work with any arbitrary filename
    for opts, target in [([], 'a.out.wasm'),
                         (['-o', 'lib.wasm'], 'lib.wasm'),
                         (['-o', 'lib.so'], 'lib.so'),
                         (['-o', 'foo.bar'], 'foo.bar')]:
      # specified target
      print('building: ' + target)
      self.clear()
      self.run_process([EMCC, test_file('hello_world.c'), '-sSIDE_MODULE', '-Werror'] + opts)
      for x in os.listdir('.'):
        self.assertFalse(x.endswith('.js'))
      self.assertTrue(building.is_wasm_dylib(target))

      create_file('main.c', '')
      self.do_runf('main.c', cflags=['-sMAIN_MODULE=2', 'main.c', '-Werror', target])

  def test_side_module_missing(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-sSIDE_MODULE', '-o', 'libside1.wasm'])
    self.run_process([EMCC, test_file('hello_world.c'), '-sSIDE_MODULE', '-o', 'libside2.wasm', 'libside1.wasm'])
    # When linking against `libside2.wasm` (which depends on libside1.wasm) that library path is used
    # to locate `libside1.wasm`.  Expect the link to fail with an unmodified library path.
    expected = 'libside2.wasm: shared library dependency not found in library path: `libside1.wasm`'
    self.assert_fail([EMCC, '-sMAIN_MODULE=2', test_file('hello_world.c'), 'libside2.wasm'], expected)

    # But succeed if `.` is added the library path.
    self.run_process([EMCC, '-sMAIN_MODULE=2', test_file('hello_world.c'), '-L.', 'libside2.wasm'])

  def test_side_module_transitive_deps(self):
    # Build three side modules in a dependency chain
    self.run_process([EMCC, test_file('hello_world.c'), '-sSIDE_MODULE', '-o', 'libside1.wasm'])
    self.run_process([EMCC, test_file('hello_world.c'), '-sSIDE_MODULE', '-o', 'libside2.wasm', 'libside1.wasm'])
    self.run_process([EMCC, test_file('hello_world.c'), '-sSIDE_MODULE', '-o', 'libside3.wasm', 'libside2.wasm'])

    # Link should succeed if and only if the end of the chain can be found
    final_link = [EMCC, '-sMAIN_MODULE=2', test_file('hello_world.c'), '-L.', 'libside3.wasm']
    self.run_process(final_link)
    os.remove('libside1.wasm')
    self.assert_fail(final_link, 'error: libside2.wasm: shared library dependency not found in library path: `libside1.wasm`')

  def test_side_module_folder_deps(self):
    # Build side modules in a subfolder
    os.mkdir('subdir')
    self.run_process([EMCC, test_file('hello_world.c'), '-sSIDE_MODULE', '-o', 'subdir/libside1.so'])
    self.run_process([EMCC, test_file('hello_world.c'), '-sSIDE_MODULE', '-o', 'subdir/libside2.so', '-L', 'subdir', '-lside1'])
    self.run_process([EMCC, test_file('hello_world.c'), '-sMAIN_MODULE', '-o', 'main.js', '-L', 'subdir', '-lside2'])

  @crossplatform
  def test_side_module_ignore(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-sSIDE_MODULE', '-o', 'libside.so'])

    # Attempting to link statically against a side module (libside.so) should fail.
    self.assert_fail([EMCC, '-L.', '-Bstatic', '-lside'], 'wasm-ld: error: unable to find library -lside')

    # But a static library in the same location (libside.a) should take precedence.
    self.run_process([EMCC, test_file('hello_world.c'), '-c'])
    self.run_process([EMAR, 'cr', 'libside.a', 'hello_world.o'])
    self.run_process([EMCC, '-L.', '-Bstatic', '-lside'])
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

  @is_slow_test
  @parameterized({
    '': ([],),
    '01': (['-O1'],),
    'O2': (['-O2'],),
    'O3': (['-O3'],),
    'Os': (['-Os'],),
    'Oz': (['-Oz'],),
  })
  def test_lto(self, args):
    # test building of non-wasm-object-files libraries, building with them, and running them

    # test codegen in lto mode, and compare to normal (wasm object) mode
    src = test_file('hello_libcxx.cpp')
    # wasm in object
    self.run_process([EMXX, src] + args + ['-c', '-o', 'hello_obj.o'])
    self.assertTrue(building.is_wasm('hello_obj.o'))
    self.assertFalse(is_bitcode('hello_obj.o'))

    # bitcode in object
    self.run_process([EMXX, src] + args + ['-c', '-o', 'hello_bitcode.o', '-flto'])
    self.assertFalse(building.is_wasm('hello_bitcode.o'))
    self.assertTrue(is_bitcode('hello_bitcode.o'))

    # use bitcode object (LTO)
    self.run_process([EMXX, 'hello_bitcode.o'] + args + ['-flto'])
    self.assertContained('Hello, world!', self.run_js('a.out.js'))
    # use bitcode object (non-LTO)
    self.run_process([EMXX, 'hello_bitcode.o'] + args)
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

    # use native object (LTO)
    self.run_process([EMXX, 'hello_obj.o'] + args + ['-flto'])
    self.assertContained('Hello, world!', self.run_js('a.out.js'))
    # use native object (non-LTO)
    self.run_process([EMXX, 'hello_obj.o'] + args)
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

  @parameterized({
    'noexcept': [],
    'except_emscripten': ['-sDISABLE_EXCEPTION_CATCHING=0'],
    'except_wasm': ['-fwasm-exceptions', '-sWASM_LEGACY_EXCEPTIONS=0'],
    'except_wasm_legacy': ['-fwasm-exceptions', '-sWASM_LEGACY_EXCEPTIONS'],
  })
  def test_lto_libcxx(self, *args):
    self.run_process([EMXX, test_file('hello_libcxx.cpp'), '-flto'] + list(args))

  def test_lto_flags(self):
    for flags, expect_bitcode in [
      ([], False),
      (['-flto'], True),
      (['-flto=thin'], True),
    ]:
      self.run_process([EMCC, test_file('hello_world.c')] + flags + ['-c', '-o', 'a.o'])
      seen_bitcode = is_bitcode('a.o')
      self.assertEqual(expect_bitcode, seen_bitcode, 'must emit LTO-capable bitcode when flags indicate so (%s)' % str(flags))

  # We have LTO tests covered in 'wasmltoN' targets in test_core.py, but they
  # don't run as a part of Emscripten CI, so we add a separate LTO test here.
  @requires_wasm_eh
  def test_lto_wasm_exceptions(self):
    self.set_setting('EXCEPTION_DEBUG')
    self.cflags += ['-fwasm-exceptions', '-flto']
    self.set_setting('WASM_LEGACY_EXCEPTIONS', 0)
    self.do_runf_out_file('core/test_exceptions.cpp', out_suffix='_caught')
    self.set_setting('WASM_LEGACY_EXCEPTIONS')
    self.do_runf_out_file('core/test_exceptions.cpp', out_suffix='_caught')

  @parameterized({
    '': ([],),
    'O2': (['-O2'],),
  })
  def test_missing_wasm(self, args):
    # Check that in debug builds we show a good error message if there is no wasm support
    create_file('pre.js', 'WebAssembly = undefined;\n')
    self.run_process([EMCC, test_file('hello_world.c'), '--pre-js', 'pre.js'] + args)
    out = self.run_js('a.out.js', assert_returncode=NON_ZERO)
    self.assertContainedIf('no native wasm support detected', out, not args)

  def test_exceptions_c_linker(self):
    # Test that we don't try to create __cxa_find_matching_catch_xx function automatically
    # when not linking as C++.
    self.assert_fail([EMCC, '-sSTRICT', test_file('other/test_exceptions_c_linker.c')], 'error: undefined symbol: __cxa_find_matching_catch_1')

  @with_all_eh_sjlj
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26197')
  def test_exceptions_stack_trace_and_message(self):
    src = r'''
      #include <stdexcept>

      void bar() {
        throw std::runtime_error("my message");
      }
      void foo() {
        try {
          bar();
        } catch (const std::invalid_argument &err) {}
      }
      int main() {
        foo();
        return 0;
      }
    '''
    self.cflags += ['-g']

    if '-fwasm-exceptions' in self.cflags and self.engine_is_node():
      if not self.try_require_node_version(24):
        # Node versions prior to v24 do not implement the new 'traceStack' option in
        # WebAssembly.Exception constructor.
        # (https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Exception/Exception)
        self.skipTest('test requires node v24 or above')
      # Stack trace and message example for this example code running under node:
      # Exception [WebAssembly.Exception] {
      #    message: [ 'std::runtime_error', 'my message' ]
      # }
      # Thrown at:
      #     at ___throw_exception_with_stack_trace (/usr/local/google/home/sbc/dev/wasm/emscripten/out/test/src.js:702:15)
      #     at $__cxa_throw (wasm://wasm/src.wasm-00512f82:1:15731)
      #     at $bar() (wasm://wasm/src.wasm-00512f82:1:2562)
      #     at $foo() (wasm://wasm/src.wasm-00512f82:1:2598)
      #     at $__original_main (wasm://wasm/src.wasm-00512f82:1:2766)
      #     at $main (wasm://wasm/src.wasm-00512f82:1:2793)
      #     at callMain (/usr/local/google/home/sbc/dev/wasm/emscripten/out/test/src.js:920:15)
      #     at doRun (/usr/local/google/home/sbc/dev/wasm/emscripten/out/test/src.js:959:24)
      #     at run (/usr/local/google/home/sbc/dev/wasm/emscripten/out/test/src.js:972:5)
      #     at removeRunDependency (/usr/local/google/home/sbc/dev/wasm/emscripten/out/test/src.js:527:11)
      stack_trace_checks = [
        'std::runtime_error.*my message',
        r'at \$__cxa_throw \(wasm://',
        r'at \$bar\(\) \(wasm://',
        r'at \$foo\(\) \(wasm://',
        r'at \$main \(wasm://']
    else:
      # Stack trace and message example for this example code running under v8:
      # exiting due to exception: [object WebAssembly.Exception],Error: std::runtime_error,my message
      #     at src.wasm.__cxa_throw (wasm://wasm/009a7c9a:wasm-function[1551]:0x24367)
      #     at src.wasm.bar() (wasm://wasm/009a7c9a:wasm-function[12]:0xf53)
      #     at src.wasm.foo() (wasm://wasm/009a7c9a:wasm-function[19]:0x154e)
      #     at __original_main (wasm://wasm/009a7c9a:wasm-function[20]:0x15a6)
      #     at src.wasm.main (wasm://wasm/009a7c9a:wasm-function[56]:0x25be)
      #     at test.js:833:22
      #     at callMain (test.js:4567:15)
      #     at doRun (test.js:4621:23)
      #     at run (test.js:4636:5)
      stack_trace_checks = [
        'std::runtime_error[:,][ ]?my message',  # 'std::runtime_error: my message' for Emscripten EH
        'at (src.wasm.)?_?__cxa_throw',  # '___cxa_throw' (JS symbol) for Emscripten EH
        'at (src.wasm.)?bar',
        'at (src.wasm.)?foo',
        'at (src.wasm.)?main']

    # Stack traces are enabled when either of ASSERTIONS or
    # EXCEPTION_STACK_TRACES is enabled. You can't disable
    # EXCEPTION_STACK_TRACES when ASSERTIONS is enabled.

    # Prints stack traces
    self.set_setting('ASSERTIONS', 1)
    self.set_setting('EXCEPTION_STACK_TRACES', 1)
    self.do_run(src, assert_all=True, assert_returncode=NON_ZERO,
                expected_output=stack_trace_checks, regex=True)

    # Prints stack traces
    self.set_setting('ASSERTIONS', 0)
    self.set_setting('EXCEPTION_STACK_TRACES', 1)
    self.do_run(src, assert_all=True, assert_returncode=NON_ZERO,
                expected_output=stack_trace_checks, regex=True)

    # Not allowed
    self.set_setting('ASSERTIONS', 1)
    self.set_setting('EXCEPTION_STACK_TRACES', 0)
    create_file('src.cpp', src)
    self.assert_fail([EMCC, 'src.cpp'] + self.get_cflags(), 'error: EXCEPTION_STACK_TRACES cannot be disabled when ASSERTIONS are enabled')

    # Doesn't print stack traces
    self.set_setting('ASSERTIONS', 0)
    self.set_setting('EXCEPTION_STACK_TRACES', 0)
    err = self.do_run(src, assert_returncode=NON_ZERO)
    for check in stack_trace_checks:
      self.assertFalse(re.search(check, err), 'Expected regex "%s" to not match on:\n%s' % (check, err))

  @with_all_eh_sjlj
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26197')
  def test_exceptions_rethrow_stack_trace_and_message(self):
    self.cflags += ['-g']
    # Rethrowing exception currently loses the stack trace before the rethrowing
    # due to how rethrowing is implemented. So in the examples below we don't
    # print 'bar' at the moment.
    # TODO Make rethrow preserve stack traces before rethrowing?
    rethrow_src1 = r'''
      #include <stdexcept>

      void important_function() {
        throw std::runtime_error("my message");
      }
      void foo() {
        try {
          important_function();
        } catch (...) {
          throw; // rethrowing by throw;
        }
      }
      int main() {
        foo();
        return 0;
      }
    '''
    rethrow_src2 = r'''
      #include <stdexcept>

      void important_function() {
        throw std::runtime_error("my message");
      }
      void foo() {
        try {
          important_function();
        } catch (...) {
          auto e = std::current_exception();
          std::rethrow_exception(e); // rethrowing by std::rethrow_exception
        }
      }
      int main() {
        foo();
        return 0;
      }
    '''
    if '-fwasm-exceptions' in self.cflags and self.engine_is_node():
      if not self.try_require_node_version(24):
        # Node versions prior to v24 do not implement the new 'traceStack' option in
        # WebAssembly.Exception constructor.
        # (https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Exception/Exception)
        self.skipTest('test requires node v24 or above')
      rethrow_stack_trace_checks = [
        'std::runtime_error.*my message',
        r'at \$__cxa_rethrow.* \(wasm://',
        r'at \$foo\(\) \(wasm://',
        r'at \$main \(wasm://']
    else:
      rethrow_stack_trace_checks = [
        'std::runtime_error[:,][ ]?my message',  # 'std::runtime_error: my message' for Emscripten EH
        'at ((src.wasm.)?_?__cxa_rethrow|___resumeException)',  # '___resumeException' (JS symbol) for Emscripten EH
        'at (src.wasm.)?foo',
        'at (src.wasm.)?main']

    self.set_setting('ASSERTIONS', 1)
    err = self.do_run(rethrow_src1, assert_all=True, assert_returncode=NON_ZERO,
                      expected_output=rethrow_stack_trace_checks, regex=True)
    self.assertNotContained('important_function', err)
    err = self.do_run(rethrow_src2, assert_all=True, assert_returncode=NON_ZERO,
                      expected_output=rethrow_stack_trace_checks, regex=True)
    self.assertNotContained('important_function', err)

  @with_all_eh_sjlj
  def test_cxa_current_exception_type(self):
    create_file('main.cpp', r'''
    #include <cstdio>
    #include <stdexcept>
    #include <typeinfo>
    #include <cxxabi.h>

    int main() {
      try {
        throw std::runtime_error("ERROR");
      } catch (...) {
        printf("__cxa_current_exception_type: %s\n", abi::__cxa_current_exception_type()->name());
      }
    }''')
    self.do_runf('main.cpp', '__cxa_current_exception_type: St13runtime_error')

  @with_all_eh_sjlj
  def test_exceptions_exit_runtime(self):
    self.do_other_test('test_exceptions_exit_runtime.cpp', cflags=['-sEXIT_RUNTIME'])

  @with_all_eh_sjlj
  def test_multi_inheritance_exception_message(self):
    # Regression test for a bug that getting exception message in the DEBUG mode
    # did not retrieve the correct thrown object pointer in case of multiple
    # inheritance
    create_file('src.cpp', r'''
      #include <exception>
      #include <string>

      class MyException : public virtual std::exception {
      public:
        MyException(const char *msg) : m_message(msg) {}
        const char *what() const noexcept override { return m_message.c_str(); }

      private:
        std::string m_message;
      };

      int main() {
        std::exception_ptr ep;
        try {
          throw MyException("hello");
        } catch (...) {
          ep = std::current_exception();
        }

        try {
          std::rethrow_exception(ep);
        } catch (const std::exception &e) {
          std::printf("caught: %s\n", e.what());
        }
        return 0;
      }
    ''')
    self.set_setting('ASSERTIONS')
    self.do_runf('src.cpp', 'caught: hello\n')

  @with_all_eh_sjlj
  def test_unused_eh_dce(self):
    # Programs that don't use exceptions should not pull in libc++abi or
    # exception formatting helpers when compiled with -fexceptions /
    # -fwasm-exceptions. We verify this by ensuring the output Wasm binary is
    # small.
    create_file('main.cpp', 'int main() { return 0; }')
    self.do_runf('main.cpp', '', cflags=['-O0'])

    self.assertExists('main.wasm')
    size = os.path.getsize('main.wasm')
    # 3KB is a very generous upper bound for an empty program without libc++abi
    self.assertLess(size, 3000)

  @requires_node
  def test_jsrun(self):
    engine = self.get_current_js_engine()
    print(engine)
    jsrun.WORKING_ENGINES = {}
    # Test that engine check passes
    self.assertTrue(jsrun.check_engine(engine))
    # Run it a second time (cache hit)
    self.assertTrue(jsrun.check_engine(engine))

    # Test that engine check fails
    bogus_engine = ['/fake/inline4']
    self.assertFalse(jsrun.check_engine(bogus_engine))
    self.assertFalse(jsrun.check_engine(bogus_engine))

    # Test the other possible way (list vs string) to express an engine
    if type(engine) is list:
      engine2 = engine[0]
    else:
      engine2 = [engine]
    self.assertTrue(jsrun.check_engine(engine2))

    # Test that self.run_js requires the engine
    self.run_js(test_file('hello_world.js'), engine)
    caught_exit = 0
    try:
      self.run_js(test_file('hello_world.js'), bogus_engine)
    except SystemExit as e:
      caught_exit = e.code
    self.assertEqual(1, caught_exit, 'Did not catch SystemExit with bogus JS engine')

  def test_error_on_missing_libraries(self):
    # -llsomenonexistingfile is an error by default
    self.assert_fail([EMCC, test_file('hello_world.c'), '-lsomenonexistingfile'], 'wasm-ld: error: unable to find library -lsomenonexistingfile')

  # Tests that if user accidentally attempts to link native object code, we show an error
  def test_native_link_error_message(self):
    self.run_process([CLANG_CC, '--target=' + clang_native.get_native_triple(), '-c', test_file('hello_123.c'), '-o', 'hello_123.o'])
    self.assert_fail([EMCC, 'hello_123.o', '-o', 'hello_123.js'], 'unknown file type: hello_123.o')

  # Tests that we should give a clear error on INITIAL_MEMORY not being enough for static initialization + stack
  def test_clear_error_on_massive_static_data(self):
    create_file('src.c', '''
        char muchData[128 * 1024];
        int main() {
          return (int)(long)&muchData;
        }
    ''')
    self.assert_fail([EMCC, 'src.c', '-sSTACK_SIZE=1KB', '-sINITIAL_MEMORY=64KB'], 'wasm-ld: error: initial memory too small')

  def test_o_level_clamp(self):
    for level in (3, 4, 20):
      err = self.run_process([EMCC, '-O' + str(level), test_file('hello_world.c')], stderr=PIPE).stderr
      self.assertContainedIf("optimization level '-O" + str(level) + "' is not supported; using '-O3' instead", err, level > 3)

  def test_o_level_invalid(self):
    # Test that string values, and negative integers are not accepted
    self.assert_fail([EMCC, '-Ofoo', test_file('hello_world.c')], "emcc: error: invalid integral value 'foo' in '-Ofoo'")
    stderr = self.run_process([EMCC, '-O-10', test_file('hello_world.c')], stderr=PIPE).stderr
    self.assertContained("emcc: warning: optimization level '-O-10' is not supported; using '-O3' instead", stderr)

  def test_g_level_invalid(self):
    # Bad integer values are handled by emcc
    self.assert_fail([EMCC, '-g5', test_file('hello_world.c')], "emcc: error: unknown argument: '-g5'")
    self.assert_fail([EMCC, '-g-10', test_file('hello_world.c')], "clang: error: unknown argument: '-g-10'")
    # Unknown string values are passed through to clang which will error out
    self.assert_fail([EMCC, '-gfoo', test_file('hello_world.c')], "clang: error: unknown argument: '-gfoo'")

  # Tests that if user specifies multiple -o output directives, then the last one will take precedence
  def test_multiple_o_files(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-o', 'a.js', '-o', 'b.js'])
    assert os.path.isfile('b.js')
    assert not os.path.isfile('a.js')

  # Tests that Emscripten-provided header files can be cleanly included standalone.
  # Also check they can be included in C code (where possible).
  @is_slow_test
  @parameterized({
    'ab': ('ab',),
    'cd': ('cd',),
    'ef': ('ef',),
    'gh': ('gh',),
    'ij': ('ij',),
    'kl': ('kl',),
    'mn': ('mn',),
    'op': ('op',),
    'qr': ('qr',),
    'st': ('st',),
    'uv': ('uv',),
    'wx': ('wx',),
    'yz': ('yz',),
    'other': ('*',),
  })
  def test_standalone_system_headers(self, prefix):
    # Test oldest C standard, and the default C standard
    # This also tests that each header file is self contained and includes
    # everything it needs.
    directories = {'': []}
    for elem in os.listdir(path_from_root('system/include')):
      if elem == 'fakesdl':
        continue
      full = path_from_root('system/include', elem)
      if os.path.isdir(full):
        directories[elem] = os.listdir(full)
      else:
        directories[''].append(elem)

    for directory, headers in directories.items():
      for header in headers:
        if not header.endswith('.h'):
          continue
        # Process files depending on first char of the file in different test cases for parallelization, though
        # special case SDL_ prefix to parallelize better.
        first_char = (header[4] if header.startswith('SDL_') else header[0]).lower()
        if first_char not in prefix or (prefix == '*' and first_char.isalpha()):
          continue

        print('header: ' + header)
        # These headers cannot be included in isolation.
        # e.g: error: unknown type name 'EGLDisplay'
        # Don't include avxintrin.h, avx2intrin.h, fmaintrin.h directly, include immintrin.h instead
        if header in {'eglext.h', 'SDL_config_macosx.h', 'glext.h', 'gl2ext.h', 'avxintrin.h', 'avx2intrin.h', 'fmaintrin.h'}:
          continue
        # These headers are C++ only and cannot be included from C code.
        # But we still want to check they can be included on there own without
        # any errors or warnings.
        cxx_only = header in {
          'wire.h', 'val.h', 'bind.h',
          # Some headers are not yet C compatible
          'arm_neon.h',
        }
        if directory and directory != 'compat':
          header = f'{directory}/{header}'
        inc = f'#include <{header}>\n__attribute__((weak)) int foo;\n'
        cflags = ['-Werror', '-Wall', '-pedantic', '-msimd128', '-msse4']
        if header == 'immintrin.h':
          cflags += ['-mavx2', '-mfma']
        if cxx_only:
          create_file('a.cxx', inc)
          create_file('b.cxx', inc)
          self.run_process([EMXX, '-msse3', 'a.cxx', 'b.cxx'] + cflags)
        else:
          create_file('a.c', inc)
          create_file('b.c', inc)
          for std in ([], ['-std=c89']):
            self.run_process([EMCC, 'a.c', 'b.c'] + std + cflags)

  @is_slow_test
  @also_with_wasm2js
  @also_with_minimal_runtime
  @parameterized({
    '': (False, False),
    'debug': (True, False),
    'closure': (False, True),
  })
  @parameterized({
    '': (True, False),
    'disabled': (False, False),
    'binary_encode': (True, True),
  })
  def test_single_file(self, debug_enabled, closure_enabled, single_file_enabled, single_file_binary_encoded):
    cmd = [EMCC, test_file('hello_world.c')] + self.get_cflags()

    if single_file_enabled:
      expect_wasm = False
      cmd += ['-sSINGLE_FILE']
    else:
      expect_wasm = self.is_wasm()

    cmd += [f'-sSINGLE_FILE_BINARY_ENCODE={int(single_file_binary_encoded)}']

    if debug_enabled:
      cmd += ['-g']
    if closure_enabled:
      cmd += ['--closure=1']

    self.clear()

    def do_test(cmd):
      print(' '.join(cmd))
      self.run_process(cmd)
      print(os.listdir('.'))
      if expect_wasm:
        self.assertExists('a.out.wasm')
      else:
        self.assertNotExists('a.out.wasm')
      self.assertNotExists('a.out.wat')
      self.assertContained('Hello, world!', self.run_js('a.out.js'))

    do_test(cmd)

    # additional combinations that are not part of the big product()

    if debug_enabled:
      separate_dwarf_cmd = cmd + ['-gseparate-dwarf']
      if self.is_wasm2js():
        self.expect_fail(separate_dwarf_cmd)
      else:
        do_test(separate_dwarf_cmd)
        self.assertExists('a.out.wasm.debug.wasm')

  @requires_v8
  def test_single_file_shell(self):
    self.do_runf_out_file('hello_world.c', cflags=['-sSINGLE_FILE'])

  @requires_v8
  def test_single_file_shell_sync_compile(self):
    self.do_runf_out_file('hello_world.c', cflags=['-sSINGLE_FILE', '-sWASM_ASYNC_COMPILATION=0'])

  def test_single_file_no_clobber_wasm(self):
    create_file('hello_world.wasm', 'not wasm')
    self.do_runf_out_file('hello_world.c', cflags=['-sSINGLE_FILE'])
    self.assertExists('hello_world.js')
    self.assertFileContents('hello_world.wasm', 'not wasm')

  def test_single_file_disables_source_map(self):
    cmd = [EMCC, test_file('hello_world.c'), '-sSINGLE_FILE', '-gsource-map']
    stderr = self.run_process(cmd, stderr=PIPE).stderr
    self.assertContained('warning: SINGLE_FILE disables source map support', stderr)

  def test_wasm2js_no_clobber_wasm(self):
    create_file('hello_world.wasm', 'not wasm')
    self.do_runf_out_file('hello_world.c', cflags=['-sWASM=0'])
    self.assertExists('hello_world.js')
    self.assertFileContents('hello_world.wasm', 'not wasm')

  def test_emar_M(self):
    create_file('file1', ' ')
    create_file('file2', ' ')
    self.run_process([EMAR, 'cr', 'file1.a', 'file1'])
    self.run_process([EMAR, 'cr', 'file2.a', 'file2'])
    self.run_process([EMAR, '-M'], input='''create combined.a
addlib file1.a
addlib file2.a
save
end
''')
    result = self.run_process([EMAR, 't', 'combined.a'], stdout=PIPE).stdout
    self.assertContained('file1', result)
    self.assertContained('file2', result)

  def test_emar_duplicate_inputs(self):
    # Verify that we can supply the same input multiple times without
    # confusing emar.py:
    # See https://github.com/emscripten-core/emscripten/issues/9733
    create_file('file1', ' ')
    self.run_process([EMAR, 'cr', 'file1.a', 'file1', 'file1'])

  @crossplatform
  def test_emar_response_file(self):
    # Test that special character such as single quotes in filenames survive being
    # sent via response file
    create_file("file'1", ' ')
    create_file("file'2", ' ')
    create_file("hyvää päivää", ' ')
    create_file("snowman freezes covid ☃ 🦠", ' ')
    create_file("tmp.rsp", response_file.create_response_file_contents(("file'1", "file'2", "hyvää päivää", "snowman freezes covid ☃ 🦠")))
    self.run_process([EMAR, 'cr', 'libfoo.a', '@tmp.rsp'])

  def test_response_file_bom(self):
    # Modern CMake version create response files in UTF-8 but with BOM
    # at the beginning.  Test that we can handle this.
    # https://docs.python.org/3/library/codecs.html#encodings-and-unicode
    create_file('test.rsp', b'\xef\xbb\xbf--version', binary=True)
    self.run_process([EMCC, '@test.rsp'])

  def test_archive_non_objects(self):
    create_file('file.txt', 'test file')
    self.run_process([EMCC, '-c', test_file('hello_world.c')])
    # No index added.
    # --format=darwin (the default on OSX has a strange issue where it add extra
    # newlines to files: https://bugs.llvm.org/show_bug.cgi?id=42562
    self.run_process([EMAR, 'crS', '--format=gnu', 'libfoo.a', 'file.txt', 'hello_world.o'])
    self.run_process([EMCC, test_file('hello_world.c'), 'libfoo.a'])

  def test_archive_thin(self):
    self.run_process([EMCC, '-c', test_file('hello_world.c')])
    # The `T` flag means "thin"
    self.run_process([EMAR, 'crT', 'libhello.a', 'hello_world.o'])
    self.run_process([EMCC, 'libhello.a'])
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

  def test_flag_aliases(self):
    def assert_aliases_match(flag1, flag2, flagarg, extra_args):
      results = {}
      for f in (flag1, flag2):
        self.run_process([EMCC, test_file('hello_world.c'), '-s', f + '=' + flagarg] + extra_args)
        results[f + '.js'] = read_file('a.out.js')
        results[f + '.wasm'] = read_binary('a.out.wasm')
      self.assertEqual(results[flag1 + '.js'], results[flag2 + '.js'], 'js results should be identical')
      self.assertEqual(results[flag1 + '.wasm'], results[flag2 + '.wasm'], 'wasm results should be identical')

    assert_aliases_match('INITIAL_MEMORY', 'TOTAL_MEMORY', '16777216', [])
    assert_aliases_match('INITIAL_MEMORY', 'TOTAL_MEMORY', '64MB', [])
    assert_aliases_match('MAXIMUM_MEMORY', 'WASM_MEM_MAX', '16777216', ['-sALLOW_MEMORY_GROWTH'])
    assert_aliases_match('MAXIMUM_MEMORY', 'BINARYEN_MEM_MAX', '16777216', ['-sALLOW_MEMORY_GROWTH'])

  def test_IGNORE_CLOSURE_COMPILER_ERRORS(self):
    create_file('pre.js', r'''
      // make closure compiler very very angry
      var dupe = 1;
      var dupe = 2;
      function Node() {
        throw 'Node is a DOM thing too, and use the ' + dupe;
      }
      function Node() {
        throw '(duplicate) Node is a DOM thing too, and also use the ' + dupe;
      }
    ''')

    def test(check, extra):
      cmd = [EMCC, test_file('hello_world.c'), '-O2', '--closure=1', '--pre-js', 'pre.js'] + extra
      proc = self.run_process(cmd, check=check, stderr=PIPE)
      if not check:
        self.assertNotEqual(proc.returncode, 0)
      return proc

    WARNING = 'Variable dupe declared more than once'

    proc = test(check=False, extra=[])
    self.assertContained(WARNING, proc.stderr)
    proc = test(check=True, extra=['-sIGNORE_CLOSURE_COMPILER_ERRORS'])
    self.assertNotContained(WARNING, proc.stderr)

  @parameterized({
    '': ([],),
    'asyncify': (['-sASYNCIFY'],),
    # set max_memory to 4GB to test handleI64Signatures() with GL emulation
    'gl_emu': (['-sLEGACY_GL_EMULATION', '-sMAXIMUM_MEMORY=4GB', '-sALLOW_MEMORY_GROWTH'],),
    'no_exception_throwing': (['-sDISABLE_EXCEPTION_THROWING'],),
    'minimal_runtime': (['-sMINIMAL_RUNTIME'],),
    'embind': (['-lembind'],),
  })
  def test_full_js_library(self, args):
    self.run_process([EMCC, test_file('hello_world.c'), '-sSTRICT_JS', '-sINCLUDE_FULL_LIBRARY'] + args)

  def test_full_js_library_undefined(self):
    create_file('main.c', 'void foo(); int main() { foo(); return 0; }')
    self.assert_fail([EMCC, 'main.c', '-sSTRICT_JS', '-sINCLUDE_FULL_LIBRARY'], 'undefined symbol: foo')

  def test_full_js_library_except(self):
    self.set_setting('INCLUDE_FULL_LIBRARY', 1)
    self.set_setting('DISABLE_EXCEPTION_CATCHING', 0)
    self.do_other_test('test_full_js_library_except.cpp')

  @crossplatform
  @parameterized({
    '': [[]],
    # bigint support is interesting to test here because it changes which
    # binaryen tools get run, which can affect how debug info is kept around
    'nobigint': [['-sWASM_BIGINT=0']],
    'pthread': [['-pthread', '-Wno-experimental']],
    'pthread_offscreen': [['-pthread', '-Wno-experimental', '-sOFFSCREEN_FRAMEBUFFER']],
    'wasmfs': [['-sWASMFS']],
    'min_webgl_version': [['-sMIN_WEBGL_VERSION=2', '-sLEGACY_GL_EMULATION=0']],
    'full_es3': [['-sMIN_WEBGL_VERSION=2', '-sLEGACY_GL_EMULATION=0', '-sFULL_ES3']],
  })
  def test_closure_full_js_library(self, args):
    # Test for closure errors and warnings in the entire JS library.
    self.build('hello_world.c', cflags=[
      '--closure=1',
      '--minify=0',
      '-lbase64.js',
      '-Werror=closure',
      '-sSTRICT', '-sASSERTIONS=0',
      '-sAUTO_NATIVE_LIBRARIES',
      '-sAUTO_JS_LIBRARIES',
      '-sINCLUDE_FULL_LIBRARY',
      '-sOFFSCREEN_FRAMEBUFFER',
      # Enable as many features as possible in order to maximise
      # the amount of library code we include here.
      '-sMAIN_MODULE',
      '-sFETCH',
      '-sFETCH_SUPPORT_INDEXEDDB',
      '-sLEGACY_GL_EMULATION',
      '-sMAX_WEBGL_VERSION=2',
    ] + args)

    # Check that closure doesn't minify certain attributes.
    # This list allows us to verify that it's safe to use normal accessors over
    # string accessors.
    glsyms = [
      'compressedTexImage2D',
      'compressedTexSubImage2D',
      'compressedTexImage3D',
      'compressedTexSubImage3D',
      'bindVertexArray',
      'deleteVertexArray',
      'createVertexArray',
      'isVertexArray',
      'getQueryParameter',
      'drawElementsInstanced',
      'drawBuffers',
      'drawArraysInstanced',
      'vertexAttribDivisor',
      'getInternalformatParameter',
      'beginQuery',
      'getQuery',
      'clearBufferfv',
      'clearBufferuiv',
      'getFragDataLocation',
      'vertexAttribIPointer',
      'samplerParameteri',
      'samplerParameterf',
      'isTransformFeedback',
      'deleteTransformFeedback',
      'transformFeedbackVaryings',
      'getSamplerParameter',
      'uniformBlockBindin',
      'vertexAttribIPointer',
    ]
    js = read_file('hello_world.js')
    for sym in glsyms:
      self.assertContained('.' + sym, js)

  @also_with_wasm64
  def test_closure_webgpu(self):
    if config.FROZEN_CACHE and self.is_wasm64():
      # CI configuration doesn't run `embuilder` with wasm64 on ports
      self.skipTest("test doesn't work with frozen cache")
    # Emdawnwebgpu uses C++ internally, so we use a cpp file here so emcc defaults to linking C++.
    self.build('hello_world.cpp', cflags=[
      '--closure=1',
      '-Werror=closure',
      '-sINCLUDE_FULL_LIBRARY',
      '--use-port=emdawnwebgpu',
    ])

  # Tests --closure-args command line flag
  @crossplatform
  def test_closure_externs(self):
    # Test with relocate path to the externs file to ensure that incoming relative paths
    # are translated correctly (Since closure runs with a different CWD)
    copy_asset('test_closure_externs.js', 'local_externs.js')
    test_cases = (
      ['--closure-args', '--externs "local_externs.js"'],
      ['--closure-args', '--externs=local_externs.js'],
      ['--closure-args=--externs=local_externs.js'],
    )
    for args in test_cases:
      self.run_process([EMCC, test_file('hello_world.c'),
                        '--closure=1',
                        '--pre-js', test_file('test_closure_externs_pre_js.js')] +
                       args)

  # Tests that ports can add closure args by using settings.CLOSURE_ARGS
  @crossplatform
  def test_closure_args_from_port(self):
    self.run_process([EMCC, test_file('hello_world.c'),
                      '--use-port', test_file('test_closure_args_port.py'),
                      '--closure=1',
                      '--pre-js', test_file('test_closure_externs_pre_js.js')])

  # Tests that it is possible to enable the Closure compiler via --closure=1 even if any of the input files reside in a path with unicode characters.
  def test_closure_cmdline_utf8_chars(self):
    test = "☃ äö Ć € ' 🦠.c"
    copy_asset('hello_world.c', test)
    externs = '💩' + test
    create_file(externs, '')
    self.run_process([EMCC, test, '--closure=1', '--closure-args', '--externs "' + externs + '"'])

  def test_closure_type_annotations(self):
    # Verify that certain type annotations exist to allow closure to avoid
    # ambiguity and maximize optimization opportunities in user code.
    #
    # Currently we check for a fixed set of known attribute names which
    # have been reported as unannotated in the past:
    attributes = ['put', 'getContext', 'contains', 'stopPropagation', 'pause']
    methods = ''
    for attribute in attributes:
      methods += f'this.{attribute} = () => console.error("my {attribute}");'
    create_file('post.js', '''
      /** @constructor */
      function Foo() {
        this.bar = () => console.error("my bar");
        this.baz = () => console.error("my baz");
        %s
        return this;
      }

      function getObj() {
        return new Foo();
      }

      var testobj = getObj();
      testobj.bar();

      /** Also keep alive certain library functions */
      Module['keepalive'] = [_emscripten_start_fetch, _emscripten_pause_main_loop, _SDL_AudioQuit];
    ''' % methods)

    self.build('hello_world.c', cflags=[
      '--closure=1',
      '-sINCLUDE_FULL_LIBRARY',
      '-sFETCH',
      '-sFETCH_SUPPORT_INDEXEDDB',
      '-Werror=closure',
      '--post-js=post.js',
    ])
    code = read_file('hello_world.js')
    # `bar` method is used so should not be DCE'd
    self.assertContained('my bar', code)
    # `baz` method is not used
    self.assertNotContained('my baz', code)

    for attribute in attributes:
      # None of the attributes in our list should be used either and should therefore
      # be DCE'd unless there is some usage of that name within emscripten that is
      # not annotated.
      if 'my ' + attribute in code:
        self.assertFalse('Attribute `%s` could not be DCEd' % attribute)

  @crossplatform
  @with_env_modify({'EMPROFILE': '1'})
  def test_toolchain_profiler(self):
    # Verify some basic functionality of EMPROFILE
    self.run_process([emprofile, '--reset'])
    self.assert_fail([emprofile, '--graph'], 'No profiler logs were found')

    self.run_process([EMCC, test_file('hello_world.c')])
    self.assertEqual('Hello, world!', self.run_js('a.out.js').strip())

    self.run_process([emprofile, '--graph'])
    self.assertTrue(glob.glob('toolchain_profiler.results*.html'))

  @with_env_modify({'EMPROFILE': '2'})
  def test_toolchain_profiler_stderr(self):
    stderr = self.run_process([EMCC, test_file('hello_world.c')], stderr=PIPE).stderr
    self.assertContained('start block "main"', stderr)
    self.assertContained('block "main" took', stderr)

  @also_with_wasmfs
  @crossplatform
  @parameterized({
    '': ([],),
    'runtime_debug': (['-sRUNTIME_DEBUG', '-sEXIT_RUNTIME', '-pthread'],),
  })
  def test_noderawfs_basics(self, args):
    self.do_runf('fs/test_fopen_write.c', 'read 11 bytes. Result: Hello data!', cflags=['-sNODERAWFS'] + args)

    # NODERAWFS should directly write on OS file system
    self.assertEqual("Hello data!", read_file('hello_file.txt'))

  def test_noderawfs_disables_embedding(self):
    expected = '--preload-file and --embed-file cannot be used with NODERAWFS which disables virtual filesystem'
    base = [EMCC, test_file('hello_world.c'), '-sNODERAWFS']
    create_file('somefile', 'foo')
    self.assert_fail(base + ['--preload-file', 'somefile'], expected)
    self.assert_fail(base + ['--embed-file', 'somefile'], expected)

  def test_noderawfs_access_abspath(self):
    create_file('foo', 'bar')
    create_file('access.c', r'''
      #include <unistd.h>
      int main(int argc, char** argv) {
        return access(argv[1], F_OK);
      }
    ''')
    self.do_runf('access.c', cflags=['-sNODERAWFS'], args=[os.path.abspath('foo')])

  def test_noderawfs_readfile_prerun(self):
    create_file('foo', 'bar')
    self.add_pre_run("console.log(FS.readFile('foo', { encoding: 'utf8' }));")
    self.do_runf('hello_world.c', 'bar', cflags=['-sNODERAWFS', '-sFORCE_FILESYSTEM'])

  @disabled('https://github.com/nodejs/node/issues/18265')
  def test_node_code_caching(self):
    self.run_process([EMCC, test_file('hello_world.c'),
                      '-sNODE_CODE_CACHING',
                      '-sWASM_ASYNC_COMPILATION=0'])

    def get_cached():
      cached = glob.glob('a.out.wasm.*.cached')
      if not cached:
        return None
      self.assertEqual(len(cached), 1)
      return cached[0]

    # running the program makes it cache the code
    self.assertFalse(get_cached())
    self.assertEqual('Hello, world!', self.run_js('a.out.js').strip())
    self.assertTrue(get_cached(), 'should be a cache file')

    # hard to test it actually uses it to speed itself up, but test that it
    # does try to deserialize it at least
    create_file(get_cached(), 'waka waka')
    ERROR = 'NODE_CODE_CACHING: failed to deserialize, bad cache file?'
    self.assertContained(ERROR, self.run_js('a.out.js'))
    # we cached proper code after showing that error
    self.assertEqual(read_binary(get_cached()).count(b'waka'), 0)
    self.assertNotContained(ERROR, self.run_js('a.out.js'))

  @with_env_modify({'LC_ALL': 'C'})
  def test_autotools_shared_check(self):
    expected = ': supported targets:.* elf'
    out = self.run_process([EMCC, '--help'], stdout=PIPE).stdout
    assert re.search(expected, out)

  @also_with_wasmfs
  def test_ioctl_window_size(self):
      self.do_other_test('test_ioctl_window_size.cpp')

  @also_with_wasmfs
  def test_ioctl(self):
    # ioctl requires filesystem
    self.do_other_test('test_ioctl.c', cflags=['-sFORCE_FILESYSTEM'])

  # @also_with_noderawfs # NODERAWFS needs to implement the ioctl syscalls, see issue #22264.
  def test_ioctl_termios(self):
    # ioctl requires filesystem
    self.do_other_test('test_ioctl_termios.c', cflags=['-sFORCE_FILESYSTEM'])

  def test_fd_closed(self):
    self.do_other_test('test_fd_closed.cpp')

  def test_fflush(self):
    # fflush without the full filesystem won't quite work
    self.do_other_test('test_fflush.cpp')

  @also_with_wasmfs
  def test_fflush_fs(self):
    # fflush with the full filesystem will flush from libc, but not the JS logging, which awaits a newline
    self.do_other_test('test_fflush_fs.cpp', cflags=['-sFORCE_FILESYSTEM'])

  @also_with_noderawfs
  def test_fflush_fs_exit(self):
    # on exit, we can send out a newline as no more code will run
    self.do_other_test('test_fflush_fs_exit.cpp', cflags=['-sFORCE_FILESYSTEM', '-sEXIT_RUNTIME'])

  def test_extern_weak(self):
    self.do_other_test('test_extern_weak.c')

  def test_extern_weak_dynamic(self):
    # If the symbols are left undefined we should get the same expected output as with static
    # linking (i.e. null symbol addresses);
    self.do_other_test('test_extern_weak.c', cflags=['-sMAIN_MODULE=2'])
    self.run_process([EMCC, '-o', 'libside.wasm', test_file('other/test_extern_weak_side.c'), '-sSIDE_MODULE'])
    self.do_other_test('test_extern_weak.c', out_suffix='.resolved', cflags=['-sMAIN_MODULE=2', 'libside.wasm'])

  def test_main_module_without_main(self):
    create_file('pre.js', 'Module.onRuntimeInitialized = () => Module._foo();')
    create_file('src.c', r'''
#include <emscripten.h>
#include <emscripten/console.h>
EMSCRIPTEN_KEEPALIVE void foo() {
  emscripten_out("bar");
}
''')
    self.do_runf('src.c', 'bar', cflags=['--pre-js', 'pre.js', '-sMAIN_MODULE=2'])

  @crossplatform
  def test_js_optimizer_parse_error(self):
    # check we show a proper understandable error for JS parse problems
    create_file('src.c', r'''
#include <emscripten.h>
EM_JS(void, js, (void), {
  var x = !<->5.; // wtf... this will error on the '<'
});
int main() {
  js();
}
''')
    expected = ('''
function js() { var x = !<->5.; }
                         ^ ''')
    self.assert_fail([EMCC, 'src.c', '-O2'] + self.get_cflags(), expected)

  @crossplatform
  def test_js_optimizer_chunk_size_determinism(self):
    def build():
      self.run_process([EMCC, test_file('hello_world.c'), '-O3', '-sWASM=0'])
      # FIXME: newline differences can exist, ignore for now
      return read_file('a.out.js').replace('\n', '')

    normal = build()

    with env_modify({
      'EMCC_JSOPT_MIN_CHUNK_SIZE': '1',
      'EMCC_JSOPT_MAX_CHUNK_SIZE': '1',
    }):
      tiny = build()

    with env_modify({
      'EMCC_JSOPT_MIN_CHUNK_SIZE': '4294967296',
      'EMCC_JSOPT_MAX_CHUNK_SIZE': '4294967296',
    }):
      huge = build()

    self.assertIdentical(normal, tiny)
    self.assertIdentical(normal, huge)

  @crossplatform
  def test_js_optimizer_verbose(self):
    # build at -O3 with wasm2js to use as much as possible of the JS
    # optimization code, and verify it works ok in verbose mode
    self.run_process([EMCC, test_file('hello_world.c'), '-O3', '-sWASM=0',
                      '-sVERBOSE'], stdout=PIPE, stderr=PIPE)

  def test_pthreads_growth_and_unsigned(self):
    create_file('src.c', r'''
#include <emscripten.h>

int main() {
  EM_ASM({
    HEAP8.set([1,2,3], $0);
  }, 1024);
}''')
    self.run_process([EMCC, 'src.c', '-O2', '--profiling', '-pthread',
                      '-sMAXIMUM_MEMORY=4GB', '-sALLOW_MEMORY_GROWTH'])
    # growable-heap must not interfere with heap unsigning, and vice versa:
    # we must have both applied, that is
    #   - GROWABLE_HEAP() runs before HEAP8
    #   - $0 gets an >>> 0 unsigning
    self.assertContained('(growMemViews(), HEAP8).set([ 1, 2, 3 ], $0 >>> 0)',
                         read_file('a.out.js'))

  @parameterized({
    '': ([],),
    'O3': (['-O3'],),
    'closure': (['--closure=1'],),
    'closure_O3': (['--closure=1', '-O3'],),
  })
  def test_EM_ASM_ES6(self, args):
    create_file('src.c', r'''
#include <emscripten.h>
int main() {
  EM_ASM({
    let x = (a, b) => 5; // valid ES6
    async function y() {} // valid ES2017
    out('hello!');
    return x;
  });
}
''')
    self.do_runf('src.c', 'hello!', cflags=args)

  def test_check_sourcemapurl(self):
    if self.is_wasm2js():
      self.skipTest('only supported with wasm')
    self.run_process([EMCC, test_file('hello_123.c'), '-gsource-map', '-o', 'a.js', '--source-map-base', 'dir/'])
    output = read_binary('a.wasm')
    # has sourceMappingURL section content and points to 'dir/a.wasm.map' file
    source_mapping_url_content = webassembly.to_leb(len('sourceMappingURL')) + b'sourceMappingURL' + webassembly.to_leb(len('dir/a.wasm.map')) + b'dir/a.wasm.map'
    self.assertEqual(output.count(source_mapping_url_content), 1)
    # make sure no DWARF debug info sections remain - they would just waste space
    self.assertNotIn(b'.debug_', output)

  def test_check_source_map_args(self):
    # -gsource-map is needed for source maps; -g is not enough
    self.run_process([EMCC, test_file('hello_world.c'), '-g'])
    self.assertNotExists('a.out.wasm.map')
    self.run_process([EMCC, test_file('hello_world.c'), '-gsource-map'])
    self.assertExists('a.out.wasm.map')
    os.unlink('a.out.wasm.map')
    err = self.run_process([EMCC, test_file('hello_world.c'), '-g4'], stderr=subprocess.PIPE).stderr
    self.assertIn('please replace -g4 with -gsource-map', err)
    self.assertExists('a.out.wasm.map')

  @parameterized({
    'normal': [],
    'profiling': ['--profiling'], # -gsource-map --profiling should still emit a source map; see #8584
  })
  def test_check_sourcemapurl_default(self, *args):
    if self.is_wasm2js():
      self.skipTest('only supported with wasm')

    self.run_process([EMCC, test_file('hello_123.c'), '-gsource-map', '-o', 'a.js'] + list(args))
    output = read_binary('a.wasm')
    # has sourceMappingURL section content and points to 'a.wasm.map' file
    source_mapping_url_content = webassembly.to_leb(len('sourceMappingURL')) + b'sourceMappingURL' + webassembly.to_leb(len('a.wasm.map')) + b'a.wasm.map'
    self.assertIn(source_mapping_url_content, output)

  @also_with_minimal_runtime
  @parameterized({
    '': ([], [], []),
    'prefix_wildcard': ([], ['--prefix', '=wasm-src://'], []),
    'prefix_partial': ([], ['--prefix', '/emscripten/=wasm-src:///emscripten/'], []),
    'sources': (['--sources'], [], ['--load-prefix', '/emscripten/test/other/wasm_sourcemap=.']),
  })
  @parameterized({
    '': ('/',),
    'basepath': ('/emscripten/test',),
  })
  def test_wasm_sourcemap(self, sources, prefix, load_prefix, basepath):
    # The no_main.c will be read from relative location if necessary (depends
    # on --sources and --load-prefix options).
    copy_asset('other/wasm_sourcemap/no_main.c')
    DW_AT_decl_file = '/emscripten/test/other/wasm_sourcemap/no_main.c'
    wasm_map_cmd = [PYTHON, path_from_root('tools/wasm-sourcemap.py'),
                    *sources, *prefix, *load_prefix,
                    '--dwarfdump-output',
                    test_file('other/wasm_sourcemap/foo.wasm.dump'),
                    '-o', 'a.out.wasm.map',
                    test_file('other/wasm_sourcemap/foo.wasm'),
                    '--basepath=' + basepath]
    self.run_process(wasm_map_cmd)
    output = read_file('a.out.wasm.map')
    # "sourcesContent" contains source code iff --sources is specified.
    self.assertIn('int foo()' if sources else '"sourcesContent":[]', output)
    if prefix: # "sources" contains URL with prefix path substition if provided
      sources_url = 'wasm-src:///emscripten/test/other/wasm_sourcemap/no_main.c'
    else: # otherwise a path relative to the given basepath.
      sources_url = utils.normalize_path(os.path.relpath(DW_AT_decl_file, basepath))
    self.assertIn(sources_url, output)
    # "mappings" contains valid Base64 VLQ segments.
    self.assertRegex(output, r'"mappings":\s*"(?:[A-Za-z0-9+\/]+[,;]?)+"')

  @parameterized({
    '': ([], 0),
    'prefix': ([
      '<cwd>=file:///path/to/src',
      DETERMINISTIC_PREFIX + '=file:///path/to/emscripten',
    ], 0),
    'sources': ([], 1),
  })
  @crossplatform
  def test_emcc_sourcemap_options(self, prefixes, sources):
    wasm_sourcemap = importlib.import_module('tools.wasm-sourcemap')
    cwd = os.getcwd()
    copy_asset('hello_123.c')
    src_file = os.path.abspath('hello_123.c')
    lib_file = DETERMINISTIC_PREFIX + '/system/lib/libc/musl/src/stdio/fflush.c'
    if prefixes:
      prefixes = [p.replace('<cwd>', cwd) for p in prefixes]
    self.set_setting('SOURCE_MAP_PREFIXES', prefixes)
    args = ['-gsource-map=inline' if sources else '-gsource-map']
    self.emcc(src_file, args=args + ['-o', 'test.js'])
    output = read_file('test.wasm.map')
    # Check source file resolution
    p = wasm_sourcemap.Prefixes(prefixes, base_path=cwd)
    self.assertEqual(len(p.prefixes), len(prefixes))
    src_file_url = p.resolve(utils.normalize_path(src_file))
    lib_file_url = p.resolve(utils.normalize_path(lib_file))
    if prefixes:
      self.assertEqual(src_file_url, 'file:///path/to/src/hello_123.c')
      self.assertEqual(lib_file_url, 'file:///path/to/emscripten/system/lib/libc/musl/src/stdio/fflush.c')
    else:
      self.assertEqual(src_file_url, 'hello_123.c')
      self.assertEqual(lib_file_url, '/emsdk/emscripten/system/lib/libc/musl/src/stdio/fflush.c')
    # "sources" contains resolved filepath.
    self.assertIn(f'"{src_file_url}"', output)
    self.assertIn(f'"{lib_file_url}"', output)
    # "sourcesContent" contains source code iff -gsource-map=inline is specified.
    if sources:
      p = wasm_sourcemap.Prefixes(prefixes, preserve_deterministic_prefix=False)
      for filepath in {src_file, lib_file}:
        resolved_path = p.resolve(utils.normalize_path(filepath))
        sources_content = json.dumps(read_file(resolved_path))
        self.assertIn(sources_content, output)
    else:
      self.assertIn('"sourcesContent":[]', output)
    # "mappings" contains valid Base64 VLQ segments.
    self.assertRegex(output, r'"mappings":\s*"(?:[A-Za-z0-9+\/]+[,;]?)+"')

  def test_wasm_sourcemap_dead(self):
    wasm_map_cmd = [PYTHON, path_from_root('tools/wasm-sourcemap.py'),
                    '--dwarfdump-output',
                    test_file('other/wasm_sourcemap_dead/t.wasm.dump'),
                    '-o', 'a.out.wasm.map',
                    test_file('other/wasm_sourcemap_dead/t.wasm'),
                    '--basepath=' + os.getcwd()]
    self.run_process(wasm_map_cmd, stdout=PIPE, stderr=PIPE)
    output = read_file('a.out.wasm.map')
    # has only two entries
    self.assertRegex(output, r'"mappings":\s*"[A-Za-z0-9+/]+,[A-Za-z0-9+/]+"')

  def test_wasm_sourcemap_relative_paths(self):
    ensure_dir('build')

    def test(infile, source_map_added_dir=''):
      expected_source_map_path = 'A ä☃ö Z.cpp'
      if source_map_added_dir:
        expected_source_map_path = source_map_added_dir + '/' + expected_source_map_path
      print(infile, expected_source_map_path)
      copy_asset('hello_123.c', infile)
      infiles = [
        infile,
        os.path.abspath(infile),
        './' + infile,
      ]
      for curr in infiles:
        print('  ', curr)
        print('    ', 'same CWD for compiler and linker')
        self.run_process([EMCC, curr, '-gsource-map'])
        self.assertIn('"%s"' % expected_source_map_path, read_file('a.out.wasm.map'))

        print('    ', 'different CWD for compiler and linker')
        self.run_process([EMCC, curr, '-g', '-c', '-o', 'build/a.o'])
        self.run_process([EMCC, 'a.o', '-gsource-map'], cwd='build')
        self.assertIn('"../%s"' % expected_source_map_path, read_file('build/a.out.wasm.map'))

    test('A ä☃ö Z.cpp')

    # Explicitly test the case of spaces, UTF-8 chars, and a tricky case of a path consisting
    # of only two digits (that could be confused for an octal escape sequence)
    ensure_dir('inner Z ö☃ä A/21')
    test('inner Z ö☃ä A/21/A ä☃ö Z.cpp', 'inner Z ö☃ä A/21')

  def test_wasm_sourcemap_extract_comp_dir_map(self):
    wasm_sourcemap = importlib.import_module('tools.wasm-sourcemap')

    def test(dump_file):
      dwarfdump_output = read_file(
          test_file(
              os.path.join('other/wasm_sourcemap_extract_comp_dir_map',
                           dump_file)))
      map_stmt_list_to_comp_dir = wasm_sourcemap.extract_comp_dir_map(
          dwarfdump_output)
      self.assertEqual(map_stmt_list_to_comp_dir,
                       {'0x00000000': '/emsdk/emscripten'})

    # Make sure we can extract the compilation directories no matter what the
    # order of `DW_AT_*` attributes is.
    test('foo.wasm.dump')
    test('bar.wasm.dump')

  def get_instr_addr(self, text, filename):
    # Runs llvm-objdump to get the address of the first occurrence of the
    # specified line within the given function. llvm-objdump's output format
    # example is as follows:
    # ...
    # 00000004 <foo>:
    #       ...
    #       6: 41 00         i32.const       0
    #       ...
    # The addresses here are the offsets to the start of the file. Returns
    # the address string in hexadecimal.
    out = self.run_process([common.LLVM_OBJDUMP, '-d', filename],
                           stdout=PIPE).stdout.strip()
    out_lines = out.splitlines()
    found = False
    for line in out_lines:
      if text in line:
        offset = line.strip().split(':')[0]
        found = True
        break
    assert found
    return '0x' + offset

  def test_emsymbolizer_srcloc(self):
    # Test emsymbolizer use cases that provide src location granularity info.
    def check_dwarf_loc_info(address, funcs, locs):
      out = self.run_process(
          [emsymbolizer, '-s', 'dwarf', 'test_dwarf.wasm', address],
          stdout=PIPE).stdout
      for func in funcs:
        self.assertIn(func, out)
      for loc in locs:
        self.assertIn(loc, out)

    def check_source_map_loc_info(address, func, loc):
      out = self.run_process(
          [emsymbolizer, '-s', 'sourcemap', 'test_dwarf.wasm', address],
          stdout=PIPE).stdout
      if func:
        self.assertIn(func, out)
      self.assertIn(loc, out)

    def do_tests(src):
      # 1. Test DWARF + source map together
      # For DWARF, we check for the full inlined info for both function names
      # and source locations. Source maps does not provide inlining information
      # and function name, so we only check for the info of the outermost
      # location.
      self.run_process([EMCC, test_file(src), '-g', '-gsource-map', '-O1', '-o',
                        'test_dwarf.js'])
      check_dwarf_loc_info(out_to_js_call_addr, out_to_js_call_func,
                           out_to_js_call_loc)
      check_source_map_loc_info(out_to_js_call_addr, None,
                                out_to_js_call_loc[0])
      check_dwarf_loc_info(unreachable_addr, unreachable_func, unreachable_loc)
      # Source map shows the original (inlined) source location
      check_source_map_loc_info(unreachable_addr, None, unreachable_loc[0])

      # 2. Test source map only
      self.run_process([EMCC, test_file(src), '-gsource-map', '-O1', '-o',
                        'test_dwarf.js'])
      check_source_map_loc_info(out_to_js_call_addr, None,
                                out_to_js_call_loc[0])
      check_source_map_loc_info(unreachable_addr, None, unreachable_loc[0])

      # 3. Test DWARF only
      self.run_process([EMCC, test_file(src), '-g', '-O1', '-o',
                        'test_dwarf.js'])
      check_dwarf_loc_info(out_to_js_call_addr, out_to_js_call_func,
                           out_to_js_call_loc)
      check_dwarf_loc_info(unreachable_addr, unreachable_func, unreachable_loc)

    # -- C program test --
    # We test two locations within test_dwarf.c:
    # out_to_js(0);     // line 6
    # __builtin_trap(); // line 13
    self.run_process([EMCC, test_file('core/test_dwarf.c'),
                      '-g', '-gsource-map', '-O1', '-o', 'test_dwarf.js'])
    # Address of out_to_js(0) within foo(), uninlined
    out_to_js_call_addr = self.get_instr_addr('call\t0', 'test_dwarf.wasm')
    # Address of __builtin_trap() within bar(), inlined into main()
    unreachable_addr = self.get_instr_addr('unreachable', 'test_dwarf.wasm')

    # Function name of out_to_js(0) within foo(), uninlined
    out_to_js_call_func = ['foo']
    # Function names of __builtin_trap() within bar(), inlined into main(). The
    # first one corresponds to the innermost inlined function.
    unreachable_func = ['bar', 'main']

    # Source location of out_to_js(0) within foo(), uninlined
    out_to_js_call_loc = ['test_dwarf.c:6:3']
    # Source locations of __builtin_trap() within bar(), inlined into main().
    # The first one corresponds to the innermost inlined location.
    unreachable_loc = ['test_dwarf.c:13:3', 'test_dwarf.c:18:3']

    do_tests('core/test_dwarf.c')

    # -- C++ program test --
    # We test two locations within test_dwarf.cpp:
    # out_to_js(0);     // line 12
    # __builtin_trap(); // line 19
    self.run_process([EMCC, test_file('core/test_dwarf.cpp'),
                      '-g', '-gsource-map', '-O1', '-o', 'test_dwarf.js'])
    # Address of out_to_js(0) within MyClass::foo(), uninlined
    out_to_js_call_addr = self.get_instr_addr('call\t0', 'test_dwarf.wasm')
    # Address of __builtin_trap() within MyClass::bar(), inlined into main()
    unreachable_addr = self.get_instr_addr('unreachable', 'test_dwarf.wasm')

    # Function name of out_to_js(0) within MyClass::foo(), uninlined
    out_to_js_call_func = ['MyClass::foo()']
    # Function names of __builtin_trap() within MyClass::bar(), inlined into
    # main(). The first one corresponds to the innermost inlined function.
    unreachable_func = ['MyClass::bar()', 'main']

    # Source location of out_to_js(0) within MyClass::foo(), uninlined
    out_to_js_call_loc = ['test_dwarf.cpp:12:3']
    # Source locations of __builtin_trap() within MyClass::bar(), inlined into
    # main(). The first one corresponds to the innermost inlined location.
    unreachable_loc = ['test_dwarf.cpp:19:3', 'test_dwarf.cpp:25:6']

    do_tests('core/test_dwarf.cpp')

  def test_emsymbolizer_functions(self):
    """Test emsymbolizer use cases that only provide function-granularity info."""
    def check_func_info(filename, address, func):
      out = self.run_process(
        [emsymbolizer, filename, address], stdout=PIPE).stdout
      self.assertIn(func, out)

    # 1. Test name section only
    self.run_process([EMCC, test_file('core/test_dwarf.c'),
                      '--profiling-funcs', '-O1', '-o', 'test_dwarf.js'])
    with webassembly.Module('test_dwarf.wasm') as wasm:
      self.assertTrue(wasm.has_name_section())
      self.assertIsNone(wasm.get_custom_section('.debug_info'))
    # Address of out_to_js(0) within foo(), uninlined
    out_to_js_call_addr = self.get_instr_addr('call\t0', 'test_dwarf.wasm')
    # Address of __builtin_trap() within bar(), inlined into main()
    unreachable_addr = self.get_instr_addr('unreachable', 'test_dwarf.wasm')
    check_func_info('test_dwarf.wasm', out_to_js_call_addr, 'foo')
    # The name section will not show bar, as it's inlined into main
    check_func_info('test_dwarf.wasm', unreachable_addr, '__original_main')

    # 2. Test symbol map
    self.run_process([EMCC, test_file('core/test_dwarf.c'),
                      '-O1', '--emit-symbol-map', '-o', 'test_dwarf.js'])
    self.assertExists('test_dwarf.js.symbols')

    def check_symbolmap_info(address, func):
      out = self.run_process([emsymbolizer, '--source=symbolmap', '-f', 'test_dwarf.js.symbols', 'test_dwarf.wasm', address], stdout=PIPE).stdout
      self.assertIn(func, out)

    # Same tests as name section above.
    # Address of out_to_js(0) within foo(), uninlined
    out_to_js_call_addr = self.get_instr_addr('call\t0', 'test_dwarf.wasm')
    # Address of __builtin_trap() within bar(), inlined into main()
    unreachable_addr = self.get_instr_addr('unreachable', 'test_dwarf.wasm')
    check_symbolmap_info(out_to_js_call_addr, 'foo')
    # The name section will not show bar, as it's inlined into main
    check_symbolmap_info(unreachable_addr, '__original_main')

  def test_emsymbolizer_symbol_map_names(self):
    """Test emsymbolizer with symbol map which contains demangled C++ names."""
    create_file('test_symbol_map.cpp', r'''
      #include <emscripten.h>
      EM_JS(int, out_to_js, (), { return 0; });

      namespace Namespace {
        class ClassA{};
        class ClassB{};

        void __attribute__((noinline)) foo(ClassA v) { out_to_js(); }

        template <typename T>
        void __attribute__((noinline)) bar(ClassB t) { __builtin_trap(); }
      };

      int main() {
        // call function to avoid Dead-code elimination
        Namespace::foo({});
        // instantiate template function
        Namespace::bar<Namespace::ClassA>(Namespace::ClassB{});
        return 0;
      }
    ''')
    self.run_process([EMXX, 'test_symbol_map.cpp',
                      '-O1', '--emit-symbol-map', '-o', 'test_symbol_map.js'])
    self.assertExists('test_symbol_map.js.symbols')

    out_to_js_call_addr = self.get_instr_addr('call\t0', 'test_symbol_map.wasm')
    unreachable_addr = self.get_instr_addr('unreachable', 'test_symbol_map.wasm')

    def check_cpp_symbolmap_info(address, func):
      out = self.run_process([emsymbolizer, '--source=symbolmap', '-f', 'test_symbol_map.js.symbols', 'test_symbol_map.wasm', address], stdout=PIPE).stdout
      self.assertIn(func, out)

    def check_symbol_map_contains(func):
      out = read_file('test_symbol_map.js.symbols')
      self.assertIn(func, out)

    # function name: "Namespace::foo(Namespace::ClassA)"
    check_cpp_symbolmap_info(out_to_js_call_addr, 'Namespace::foo')
    check_cpp_symbolmap_info(out_to_js_call_addr, 'Namespace::ClassA')

    # function name: "void Namespace::bar<Namespace::ClassA>(Namespace::ClassB)"
    check_cpp_symbolmap_info(unreachable_addr, 'Namespace::bar')
    check_cpp_symbolmap_info(unreachable_addr, 'Namespace::ClassA')
    check_cpp_symbolmap_info(unreachable_addr, 'Namespace::ClassB')

    # JS imports
    check_symbol_map_contains('out_to_js')

  def test_separate_dwarf(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-g'])
    self.assertExists('a.out.wasm')
    self.assertNotExists('a.out.wasm.debug.wasm')
    self.run_process([EMCC, test_file('hello_world.c'), '-gseparate-dwarf'])
    self.assertExists('a.out.wasm')
    self.assertExists('a.out.wasm.debug.wasm')
    self.assertLess(os.path.getsize('a.out.wasm'), os.path.getsize('a.out.wasm.debug.wasm'))
    # the special section should also exist, that refers to the side debug file
    wasm = read_binary('a.out.wasm')
    self.assertIn(b'external_debug_info', wasm)
    self.assertIn(b'a.out.wasm.debug.wasm', wasm)

    # building to a subdirectory should still leave a relative path, which
    # assumes the debug file is alongside the main one
    os.mkdir('subdir')
    self.run_process([EMCC, test_file('hello_world.c'),
                      '-gseparate-dwarf',
                      '-o', 'subdir/output.js'])
    wasm = read_binary('subdir/output.wasm')
    self.assertIn(b'output.wasm.debug.wasm', wasm)
    # check both unix-style slashes and the system's slashes, so that we don't
    # assume the encoding of the section in this test
    self.assertNotIn(b'subdir/output.wasm.debug.wasm', wasm)
    self.assertNotIn(os.path.join('subdir', 'output.wasm.debug.wasm').encode(), wasm)

    # Check that the dwarf file has only dwarf, name, and non-code sections
    with webassembly.Module('subdir/output.wasm.debug.wasm') as debug_wasm:
      if not debug_wasm.has_name_section():
        self.fail('name section not found in separate dwarf file')
      for _sec in debug_wasm.sections():
        # TODO(https://github.com/emscripten-core/emscripten/issues/13084):
        # Re-enable this code once the debugger extension can handle wasm files
        # with name sections but no code sections.
        # if sec.type == webassembly.SecType.CODE:
        #   self.fail(f'section of type "{sec.type}" found in separate dwarf file')
        pass

    # Check that dwarfdump can dump the debug info
    dwdump = self.run_process(
        [LLVM_DWARFDUMP, 'subdir/output.wasm.debug.wasm', '-name', 'main'],
        stdout=PIPE).stdout
    # Basic check that the debug info is more than a skeleton. If so it will
    # have a subprogram descriptor for main
    self.assertIn('DW_TAG_subprogram', dwdump)
    self.assertIn('DW_AT_name\t("main")', dwdump)

  def test_split_dwarf_dwp(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-g', '-gsplit-dwarf'])
    self.assertExists('a.out.wasm')
    self.assertExists('hello_world.dwo')

    # The wasm will have full debug info for libc (ignore that), but only a
    # skeleton for hello_world.c (no subprogram for main)
    dwdump = self.run_process([LLVM_DWARFDUMP, 'a.out.wasm'], stdout=PIPE).stdout
    self.assertIn('DW_AT_GNU_dwo_name\t("hello_world.dwo")', dwdump)
    self.assertNotIn('DW_AT_name\t("main")', dwdump)

    # The dwo will have a subprogram for main in a section with a .dwo suffix
    dwdump = self.run_process([LLVM_DWARFDUMP, 'hello_world.dwo'],
                              stdout=PIPE).stdout
    self.assertIn('.debug_info.dwo contents:', dwdump)
    self.assertIn('DW_AT_GNU_dwo_name\t("hello_world.dwo")', dwdump)
    self.assertIn('DW_AT_name\t("main")', dwdump)

    # Check that dwp runs, and results in usable output as well
    self.run_process([LLVM_DWP, '-e', 'a.out.wasm', '-o', 'a.out.wasm.dwp'])
    self.assertExists('a.out.wasm.dwp')
    self.run_process([LLVM_DWARFDUMP, 'a.out.wasm.dwp'], stdout=PIPE)
    self.assertIn('.debug_info.dwo contents:', dwdump)
    self.assertIn('DW_AT_GNU_dwo_name\t("hello_world.dwo")', dwdump)
    self.assertIn('DW_AT_name\t("main")', dwdump)

  def test_separate_dwarf_with_filename(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-gseparate-dwarf=with_dwarf.wasm'])
    self.assertNotExists('a.out.wasm.debug.wasm')
    self.assertExists('with_dwarf.wasm')
    # the correct notation is to have exactly one '=' and in the right place
    for invalid in ('-gseparate-dwarf=x=', '-gseparate-dwarfy=', '-gseparate-dwarf-hmm'):
      self.assert_fail([EMCC, test_file('hello_world.c'), invalid], 'invalid -gseparate-dwarf=FILENAME notation')

    # building to a subdirectory, but with the debug file in another place,
    # should leave a relative path to the debug wasm
    os.mkdir('subdir')
    self.run_process([EMCC, test_file('hello_world.c'),
                      '-o', 'subdir/output.js',
                      '-gseparate-dwarf=with_dwarf2.wasm'])
    self.assertExists('with_dwarf2.wasm')
    wasm = read_binary('subdir/output.wasm')
    self.assertIn(b'../with_dwarf2.wasm', wasm)

  def test_separate_dwarf_with_filename_and_path(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-gseparate-dwarf=with_dwarf.wasm'])
    self.assertIn(b'with_dwarf.wasm', read_binary('a.out.wasm'))
    self.run_process([EMCC, test_file('hello_world.c'), '-gseparate-dwarf=with_dwarf.wasm',
                      '-sSEPARATE_DWARF_URL=http://somewhere.com/hosted.wasm'])
    self.assertIn(b'somewhere.com/hosted.wasm', read_binary('a.out.wasm'))

  @crossplatform
  def test_dwarf_system_lib(self):
    if config.FROZEN_CACHE:
      self.skipTest("test doesn't work with frozen cache")
    self.run_process([EMBUILDER, 'build', 'libemmalloc', '--force'])
    libc = os.path.join(config.CACHE, 'sysroot', 'lib', 'wasm32-emscripten', 'libemmalloc.a')
    self.assertExists(libc)

    dwdump = self.run_process(
        [LLVM_DWARFDUMP, libc, '-debug-info', '-debug-line', '--recurse-depth=0'],
        stdout=PIPE).stdout
    # Check that the embedded location of the source file is correct.
    self.assertIn('DW_AT_name\t("/emsdk/emscripten/system/lib/emmalloc.c")', dwdump)
    self.assertIn('DW_AT_comp_dir\t("/emsdk/emscripten")', dwdump)

  @parameterized({
    'O0': (['-O0'],),
    'O1': (['-O1'],),
    'O2': (['-O2'],),
  })
  def test_wasm_producers_section(self, args):
    self.run_process([EMCC, test_file('hello_world.c')] + args)
    # if there is no producers section expected by default, verify that, and
    # see that the flag works to add it.
    self.verify_custom_sec_existence('a.out.wasm', 'producers', False)
    size = os.path.getsize('a.out.wasm')
    self.run_process([EMCC, test_file('hello_world.c'), '-sEMIT_PRODUCERS_SECTION'] + args)
    self.verify_custom_sec_existence('a.out.wasm', 'producers', True)
    size_with_section = os.path.getsize('a.out.wasm')
    self.assertLess(size, size_with_section)

  @parameterized({
    '':       ([],),
    # in some modes we run wasm-emscripten-finalize, which normally strips the
    # features section for us, so add testing for those
    'nobigint': (['-sWASM_BIGINT=0'],),
    'wasm64': (['-m64'],),
  })
  def test_wasm_features_section(self, args):
    # The features section should never be in our output, when we optimize.
    self.run_process([EMCC, test_file('hello_world.c'), '-O2'] + args)
    self.verify_custom_sec_existence('a.out.wasm', 'target_features', False)

  @parameterized({
    '': ([],),
    'nobigint': (['-sWASM_BIGINT=0'],),
    'O2': (['-O2'],),
  })
  def test_func_attr_section(self, args):
    # Verify that clang function annotation don't survive in the final wasm binary.
    create_file('test.c', '''
      [[clang::annotate("foobar")]]
      int main() {
        return 0;
      }
    ''')
    self.run_process([EMCC, 'test.c'] + args)
    with webassembly.Module('a.out.wasm') as module:
      for section in module.sections():
        if section.type == webassembly.SecType.CUSTOM:
          self.assertNotIn('foobar', section.name)

  def test_wasm_features(self):
    # Test that wasm features are explicitly enabled or disabled based on
    # target engine version. Here we are reading the features section and
    # trusting that LLVM and/or Binaryen faithfully represent what features
    #  are used
    def verify_features_sec(feature, expect_in, linked=False):
      with webassembly.Module('a.out.wasm' if linked else 'hello_world.o') as module:
        features = module.get_target_features()
      if expect_in:
        self.assertTrue(feature in features and
                        features[feature] == webassembly.TargetFeaturePrefix.USED,
                        f'{feature} missing from wasm file')
      else:
        self.assertFalse(feature in features,
                         f'{feature} unexpectedly found in wasm file')

    def verify_features_sec_linked(feature, expect_in):
      return verify_features_sec(feature, expect_in, linked=True)

    def compile(flags):
      self.run_process([EMCC, test_file('hello_world.c')] + flags)

    # Default features, unlinked and linked
    compile(['-c'])
    verify_features_sec('bulk-memory', True)
    verify_features_sec('nontrapping-fptoint', True)
    verify_features_sec('sign-ext', True)
    verify_features_sec('mutable-globals', True)
    verify_features_sec('multivalue', True)
    verify_features_sec('reference-types', True)

    compile([])
    verify_features_sec_linked('sign-ext', True)
    verify_features_sec_linked('mutable-globals', True)
    verify_features_sec_linked('multivalue', True)
    verify_features_sec_linked('bulk-memory-opt', True)
    verify_features_sec_linked('nontrapping-fptoint', True)
    verify_features_sec_linked('reference-types', True)

    # Disable a feature
    compile(['-mno-sign-ext', '-c'])
    verify_features_sec('sign-ext', False)

    # Disable via browser selection
    compile(['-sMIN_SAFARI_VERSION=140100'])
    verify_features_sec_linked('bulk-memory-opt', False)
    verify_features_sec_linked('nontrapping-fptoint', False)
    # Flag disabling overrides default browser versions
    compile(['-mno-bulk-memory'])
    verify_features_sec_linked('bulk-memory', False)
    # Flag disabling overrides explicit browser version
    compile(['-sMIN_SAFARI_VERSION=160000', '-mno-bulk-memory'])
    verify_features_sec_linked('bulk-memory', False)
    # Flag enabling overrides explicit browser version
    compile(['-sMIN_SAFARI_VERSION=140100', '-mnontrapping-fptoint'])
    verify_features_sec_linked('nontrapping-fptoint', True)
    # Flag disabling overrides explicit version for bulk memory
    compile(['-sMIN_SAFARI_VERSION=150000', '-mno-bulk-memory'])
    verify_features_sec_linked('bulk-memory-opt', False)

    # Bigint ovrride does not cause other features to enable
    compile(['-sMIN_SAFARI_VERSION=140100', '-sWASM_BIGINT=1'])
    verify_features_sec_linked('bulk-memory-opt', False)

    compile(['-sMIN_SAFARI_VERSION=140100', '-mbulk-memory'])
    verify_features_sec_linked('nontrapping-fptoint', False)

  def test_no_bulk_memory(self):
    # The test_wasm_features test (above) uses the feature section to confirm
    # if a feature is present, but that doesn't work in optimizing builds
    # since we strip the feature section in release builds.
    # This test confirms that no DATACOUNT section is present in the final
    # binary.

    def has_data_count(filename):
      with webassembly.Module(filename) as wasm:
        return wasm.get_section(webassembly.SecType.DATACOUNT)

    self.emcc('hello_world.c', ['-O3', '-o', 'bulk.js'])
    self.assertTrue(has_data_count('bulk.wasm'))

    self.emcc('hello_world.c', ['-O3', '-o', 'nobulk.js', '-mno-bulk-memory', '-mno-bulk-memory-opt'])
    self.assertFalse(has_data_count('nobulk.wasm'))

  @crossplatform
  def test_html_preprocess(self):
    src_file = test_file('module/test_stdin.c')
    output_file = 'test_stdin.html'
    shell_file = test_file('module/test_html_preprocess.html')

    self.run_process([EMCC, '-o', output_file, src_file, '--shell-file', shell_file, '-sASSERTIONS=0'], stdout=PIPE, stderr=PIPE)
    output = read_file(output_file)
    self.assertContained('''<style>
/* Disable preprocessing inside style block as syntax is ambiguous with CSS */
#include {background-color: black;}
#if { background-color: red;}
#else {background-color: blue;}
#endif {background-color: green;}
#xxx {background-color: purple;}
</style>
T1:(else) ASSERTIONS != 1
T2:ASSERTIONS != 1
T3:ASSERTIONS < 2
T4:(else) ASSERTIONS <= 1
T5:(else) ASSERTIONS
T6:!ASSERTIONS''', output)

    self.run_process([EMCC, '-o', output_file, src_file, '--shell-file', shell_file, '-sASSERTIONS'], stdout=PIPE, stderr=PIPE)
    output = read_file(output_file)
    self.assertContained("""<style>
/* Disable preprocessing inside style block as syntax is ambiguous with CSS */
#include {background-color: black;}
#if { background-color: red;}
#else {background-color: blue;}
#endif {background-color: green;}
#xxx {background-color: purple;}
</style>
T1:ASSERTIONS == 1
T2:(else) ASSERTIONS == 1
T3:ASSERTIONS < 2
T4:(else) ASSERTIONS <= 1
T5:ASSERTIONS
T6:(else) !ASSERTIONS""", output)

    self.run_process([EMCC, '-o', output_file, src_file, '--shell-file', shell_file, '-sASSERTIONS=2'], stdout=PIPE, stderr=PIPE)
    output = read_file(output_file)
    self.assertContained("""<style>
/* Disable preprocessing inside style block as syntax is ambiguous with CSS */
#include {background-color: black;}
#if { background-color: red;}
#else {background-color: blue;}
#endif {background-color: green;}
#xxx {background-color: purple;}
</style>
T1:(else) ASSERTIONS != 1
T2:ASSERTIONS != 1
T3:(else) ASSERTIONS >= 2
T4:ASSERTIONS > 1
T5:ASSERTIONS
T6:(else) !ASSERTIONS""", output)

  def test_shell_file_unused(self):
    create_file('foo.html', '')
    expected = 'emcc: error: --shell-file ignored when not generating html output [-Wunused-command-line-argument] [-Werror]'
    self.assert_fail([EMCC, test_file('hello_world.c'), '--shell-file=foo.html', '-Werror'], expected)

  # Tests that Emscripten-compiled applications can be run from a relative path with node command line that is different than the current working directory.
  @requires_node
  def test_node_js_run_from_different_directory(self):
    ensure_dir('subdir')
    self.run_process([EMCC, test_file('hello_world.c'), '-o', 'subdir/a.js', '-O3'])
    ret = self.run_js('subdir/a.js')
    self.assertContained('Hello, world!', ret)

  # Tests that a pthreads + modularize build can be run in node js
  @requires_pthreads
  @parameterized({
    '': (False,),
    'es6': (True,),
  })
  def test_node_js_pthread_module(self, es6):
    # create module loader script
    if es6:
      ext = '.mjs'
      create_file('moduleLoader.mjs', '''
        import test_module from "./subdir/module.mjs";
        test_module().then((test_module_instance) => {
          console.log("done");
        });
        ''')
    else:
      ext = '.js'
      create_file('moduleLoader.js', '''
        const test_module = require("./subdir/module.js");
        test_module().then((test_module_instance) => {
          console.log("done");
        });
        ''')
    ensure_dir('subdir')

    # build hello_world.c
    self.run_process([EMCC, test_file('hello_world.c'), '-o', 'subdir/module' + ext, '-pthread', '-sMODULARIZE', '-sEXPORT_NAME=test_module'] + self.get_cflags())

    # run the module
    ret = self.run_js('moduleLoader' + ext)
    self.assertContained('Hello, world!\ndone\n', ret)

    create_file('workerLoader.js', f'''
      const {{ Worker, isMainThread }} = require('worker_threads');
      new Worker('./moduleLoader{ext}');
      ''')

    # run the same module, but inside of a worker
    ret = self.run_js('workerLoader.js')
    self.assertContained('Hello, world!\ndone\n', ret)

  @crossplatform
  def test_html_preprocess_error(self):
    create_file('shell.html', '''
      #error this is an error
      {{{ SCRIPT }}}
    ''')
    expected = 'error: shell.html:2: #error this is an error'
    self.assert_fail([EMCC, '-o', 'out.html', test_file('hello_world.c'), '--shell-file=shell.html'], expected)

  @no_windows('node system() does not seem to work, see https://github.com/emscripten-core/emscripten/pull/10547')
  @requires_node
  def test_system_node_js(self):
    self.do_runf('test_system.c', 'Hello from echo', cflags=['-DENV_NODE'])

  @requires_node
  def test_node_eval(self):
    if not self.engine_is_node():
      # The `requires_node` decorator above also allows for node-like environments such as
      # deno and bun, but this test requires actual node.
      self.skipTest('requires nodejs')
    self.run_process([EMCC, '-sENVIRONMENT=node', test_file('hello_world.c'), '-o', 'a.js', '-O3'])
    js = read_file('a.js')
    ret = self.run_process(self.get_current_js_engine() + ['-e', js], stdout=PIPE).stdout
    self.assertContained('Hello, world!', ret)

  def test_is_bitcode(self):
    fname = 'tmp.o'

    with open(fname, 'wb') as f:
      f.write(b'foo')
    self.assertFalse(is_bitcode(fname))

    with open(fname, 'wb') as f:
      f.write(b'\xDE\xC0\x17\x0B')
      f.write(16 * b'\x00')
      f.write(b'BC')
    self.assertTrue(is_bitcode(fname))

    with open(fname, 'wb') as f:
      f.write(b'BC')
    self.assertTrue(is_bitcode(fname))

  def test_is_ar(self):
    fname = 'tmp.a'

    with open(fname, 'wb') as f:
      f.write(b'foo')
    self.assertFalse(building.is_ar(fname))

    with open(fname, 'wb') as f:
      f.write(b'!<arch>\n')
    self.assertTrue(building.is_ar(fname))

  def test_dash_s_list_parsing(self):
    create_file('src.c', r'''
        #include <stdio.h>
        void a() { printf("a\n"); }
        void b() { printf("b\n"); }
        void c() { printf("c\n"); }
        void d() { printf("d\n"); }
      ''')
    create_file('response.json', '''\
[
"_a",
"_b",
"_c",
"_d"
]
''')
    create_file('response.txt', '''\
_a
_b
_c
_d
''')

    for export_arg, expected in [
      # No quotes needed
      ('EXPORTED_FUNCTIONS=[_a,_b,_c,_d]', ''),
      # No quotes with spaces
      ('EXPORTED_FUNCTIONS=[_a, _b, _c, _d]', ''),
      # No brackets needed either
      ('EXPORTED_FUNCTIONS=_a,_b,_c,_d', ''),
      # No brackets with spaced
      ('EXPORTED_FUNCTIONS=_a, _b, _c, _d', ''),
      # extra space at end - should be ignored
      ("EXPORTED_FUNCTIONS=['_a', '_b', '_c', '_d' ]", ''),
      # extra newline in response file - should be ignored
      ("EXPORTED_FUNCTIONS=@response.json", ''),
      # Simple one-per-line response file format
      ("EXPORTED_FUNCTIONS=@response.txt", ''),
      # stray slash
      ("EXPORTED_FUNCTIONS=['_a', '_b', \\'_c', '_d']", r'''emcc: error: undefined exported symbol: "\\'_c'"'''),
      # stray slash
      ("EXPORTED_FUNCTIONS=['_a', '_b',\\ '_c', '_d']", r'''emcc: error: undefined exported symbol: "\\ '_c'"'''),
      # stray slash
      ('EXPORTED_FUNCTIONS=["_a", "_b", \\"_c", "_d"]', r'emcc: error: undefined exported symbol: "\\"_c""'),
      # stray slash
      ('EXPORTED_FUNCTIONS=["_a", "_b",\\ "_c", "_d"]', r'emcc: error: undefined exported symbol: "\\ "_c""'),
      # missing comma
      ('EXPORTED_FUNCTIONS=["_a", "_b" "_c", "_d"]', 'wasm-ld: error: symbol exported via --export not found: b" "_c'),
    ]:
      print(export_arg)
      proc = self.run_process([EMCC, 'src.c', '-s', export_arg], stdout=PIPE, stderr=PIPE, check=not expected)
      print(proc.stderr)
      if not expected:
        js = read_file('a.out.js')
        for sym in ('_a', '_b', '_c', '_d'):
          self.assertContained(f'var {sym} = ', js)
      else:
        self.assertNotEqual(proc.returncode, 0)
        self.assertContained(expected, proc.stderr)

  def test_asyncify_escaping(self):
    proc = self.run_process([EMCC, test_file('hello_world.c'), '-sASYNCIFY', '-s', "ASYNCIFY_ONLY=[DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)]"], stdout=PIPE, stderr=PIPE)
    self.assertContained('emcc: ASYNCIFY list contains an item without balanced parentheses', proc.stderr)
    self.assertContained('   DOS_ReadFile(unsigned short', proc.stderr)
    self.assertContained('Try using a response file', proc.stderr)

  def test_asyncify_response_file(self):
    create_file('a.txt', r'''[
  "DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)"
]
''')

    create_file('b.txt', 'DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)')
    for file in ('a.txt', 'b.txt'):
      proc = self.run_process([EMCC, test_file('hello_world.c'), '-sASYNCIFY', f'-sASYNCIFY_ONLY=@{file}'], stdout=PIPE, stderr=PIPE)
      # we should parse the response file properly, and then issue a proper warning for the missing function
      self.assertContained(
          "Asyncify onlylist contained a non-matching pattern: 'DOS_ReadFile(unsigned short, unsigned char*, unsigned short*, bool)'",
          proc.stderr)

  def test_asyncify_advise(self):
    src = test_file('other/asyncify_advise.c')

    self.set_setting('ASYNCIFY')
    self.set_setting('ASYNCIFY_ADVISE')
    self.set_setting('ASYNCIFY_IMPORTS', ['async_func'])
    # Also check that ASYNCIFY_STACK_SIZE can be specified using a memory size suffix
    self.set_setting('ASYNCIFY_STACK_SIZE', ['64kb'])

    out = self.run_process([EMCC, src, '-o', 'asyncify_advise.js'] + self.get_cflags(), stdout=PIPE).stdout
    self.assertContained('[asyncify] main can', out)
    self.assertContained('[asyncify] a can', out)
    self.assertContained('[asyncify] c can', out)
    self.assertContained('[asyncify] e can', out)
    self.assertContained('[asyncify] g can', out)
    self.assertContained('[asyncify] i can', out)

    self.set_setting('ASYNCIFY_REMOVE', ['e'])
    out = self.run_process([EMCC, src, '-o', 'asyncify_advise.js'] + self.get_cflags(), stdout=PIPE).stdout
    self.assertContained('[asyncify] main can', out)
    self.assertNotContained('[asyncify] a can', out)
    self.assertNotContained('[asyncify] c can', out)
    self.assertNotContained('[asyncify] e can', out)
    self.assertContained('[asyncify] g can', out)
    self.assertContained('[asyncify] i can', out)

  def test_asyncify_stack_overflow(self):
    self.cflags += ['-sASYNCIFY', '-sASYNCIFY_STACK_SIZE=4', '-sASYNCIFY_DEBUG=2']

    # The unreachable error on small stack sizes is not super-helpful. Try at
    # least to hint at increasing the stack size.

    def test(args, expected):
      self.do_runf('other/test_asyncify_stack_overflow.cpp',
                   cflags=args,
                   assert_returncode=common.NON_ZERO,
                   regex=True,
                   expected_output=[expected])

    test(['-sASSERTIONS=0'],
         r'Aborted\(RuntimeError: [Uu]nreachable.*\). Build with -sASSERTIONS for more info.')

    test(['-sASSERTIONS=1'],
         r'Aborted\(RuntimeError: [Uu]nreachable.*\). "unreachable" may be due to ASYNCIFY_STACK_SIZE not being large enough \(try increasing it\)')

  def test_async_exit_after_wakeup(self):
    self.do_runf('test_async_exit_after_wakeup.c', cflags=['-sASYNCIFY', '--js-library', test_file('test_async_exit_after_wakeup.js')])

  # Sockets and networking

  def test_inet(self):
    self.do_runf('third_party/sha1.c', 'SHA1=15dd99a1991e0b3826fede3deffc1feba42278e6')
    src = r'''
      #include <stdio.h>
      #include <arpa/inet.h>

      int main() {
        printf("*%x,%x,%x,%x,%x,%x*\n", htonl(0xa1b2c3d4), htonl(0xfe3572e0), htonl(0x07abcdf0), htons(0xabcd), ntohl(0x43211234), ntohs(0xbeaf));
        in_addr_t i = inet_addr("190.180.10.78");
        printf("%x\n", i);
        return 0;
      }
    '''
    self.do_run(src, '*d4c3b2a1,e07235fe,f0cdab07,cdab,34122143,afbe*\n4e0ab4be\n')

  def test_inet2(self):
    src = r'''
      #include <stdio.h>
      #include <arpa/inet.h>

      int main() {
        struct in_addr x, x2;
        int *y = (int*)&x;
        *y = 0x12345678;
        printf("%s\n", inet_ntoa(x));
        int r = inet_aton(inet_ntoa(x), &x2);
        printf("%s\n", inet_ntoa(x2));
        return 0;
      }
    '''
    self.do_run(src, '120.86.52.18\n120.86.52.18\n')

  def test_inet3(self):
    src = r'''
      #include <stdio.h>
      #include <arpa/inet.h>
      #include <sys/socket.h>
      int main() {
        char dst[64];
        struct in_addr x, x2;
        int *y = (int*)&x;
        *y = 0x12345678;
        printf("%s\n", inet_ntop(AF_INET,&x,dst,sizeof dst));
        int r = inet_aton(inet_ntoa(x), &x2);
        printf("%s\n", inet_ntop(AF_INET,&x2,dst,sizeof dst));
        return 0;
      }
    '''
    self.do_run(src, '120.86.52.18\n120.86.52.18\n')

  def test_inet4(self):
    src = r'''
      #include <stdio.h>
      #include <arpa/inet.h>
      #include <sys/socket.h>

      void test(const char *test_addr, bool first=true){
          char str[40];
          struct in6_addr addr;
          unsigned char *p = (unsigned char*)&addr;
          int ret;
          ret = inet_pton(AF_INET6,test_addr,&addr);
          if(ret == -1) return;
          if(ret == 0) return;
          if(inet_ntop(AF_INET6,&addr,str,sizeof(str)) == NULL ) return;
          printf("%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x - %s\n",
               p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7],p[8],p[9],p[10],p[11],p[12],p[13],p[14],p[15],str);
          if (first) test(str, false); // check again, on our output
      }
      int main(){
          test("::");
          test("::1");
          test("::1.2.3.4");
          test("::17.18.19.20");
          test("::ffff:1.2.3.4");
          test("1::ffff");
          test("::255.255.255.255");
          test("0:ff00:1::");
          test("0:ff::");
          test("abcd::");
          test("ffff::a");
          test("ffff::a:b");
          test("ffff::a:b:c");
          test("ffff::a:b:c:d");
          test("ffff::a:b:c:d:e");
          test("::1:2:0:0:0");
          test("0:0:1:2:3::");
          test("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
          test("1::255.255.255.255");

          //below should fail and not produce results..
          test("1.2.3.4");
          test("");
          test("-");

          printf("ok.\n");
      }
    '''
    self.do_run(src, r'''0000:0000:0000:0000:0000:0000:0000:0000 - ::
0000:0000:0000:0000:0000:0000:0000:0000 - ::
0000:0000:0000:0000:0000:0000:0000:0001 - ::1
0000:0000:0000:0000:0000:0000:0000:0001 - ::1
0000:0000:0000:0000:0000:0000:0102:0304 - ::102:304
0000:0000:0000:0000:0000:0000:0102:0304 - ::102:304
0000:0000:0000:0000:0000:0000:1112:1314 - ::1112:1314
0000:0000:0000:0000:0000:0000:1112:1314 - ::1112:1314
0000:0000:0000:0000:0000:ffff:0102:0304 - ::ffff:1.2.3.4
0000:0000:0000:0000:0000:ffff:0102:0304 - ::ffff:1.2.3.4
0001:0000:0000:0000:0000:0000:0000:ffff - 1::ffff
0001:0000:0000:0000:0000:0000:0000:ffff - 1::ffff
0000:0000:0000:0000:0000:0000:ffff:ffff - ::ffff:ffff
0000:0000:0000:0000:0000:0000:ffff:ffff - ::ffff:ffff
0000:ff00:0001:0000:0000:0000:0000:0000 - 0:ff00:1::
0000:ff00:0001:0000:0000:0000:0000:0000 - 0:ff00:1::
0000:00ff:0000:0000:0000:0000:0000:0000 - 0:ff::
0000:00ff:0000:0000:0000:0000:0000:0000 - 0:ff::
abcd:0000:0000:0000:0000:0000:0000:0000 - abcd::
abcd:0000:0000:0000:0000:0000:0000:0000 - abcd::
ffff:0000:0000:0000:0000:0000:0000:000a - ffff::a
ffff:0000:0000:0000:0000:0000:0000:000a - ffff::a
ffff:0000:0000:0000:0000:0000:000a:000b - ffff::a:b
ffff:0000:0000:0000:0000:0000:000a:000b - ffff::a:b
ffff:0000:0000:0000:0000:000a:000b:000c - ffff::a:b:c
ffff:0000:0000:0000:0000:000a:000b:000c - ffff::a:b:c
ffff:0000:0000:0000:000a:000b:000c:000d - ffff::a:b:c:d
ffff:0000:0000:0000:000a:000b:000c:000d - ffff::a:b:c:d
ffff:0000:0000:000a:000b:000c:000d:000e - ffff::a:b:c:d:e
ffff:0000:0000:000a:000b:000c:000d:000e - ffff::a:b:c:d:e
0000:0000:0000:0001:0002:0000:0000:0000 - ::1:2:0:0:0
0000:0000:0000:0001:0002:0000:0000:0000 - ::1:2:0:0:0
0000:0000:0001:0002:0003:0000:0000:0000 - 0:0:1:2:3::
0000:0000:0001:0002:0003:0000:0000:0000 - 0:0:1:2:3::
ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff - ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff - ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
0001:0000:0000:0000:0000:0000:ffff:ffff - 1::ffff:ffff
0001:0000:0000:0000:0000:0000:ffff:ffff - 1::ffff:ffff
ok.
''')

  def test_getsockname_unconnected_socket(self):
    self.do_run(r'''
      #include <sys/socket.h>
      #include <stdio.h>
      #include <assert.h>
      #include <sys/socket.h>
      #include <netinet/in.h>
      #include <arpa/inet.h>
      #include <string.h>
      int main() {
        int fd;
        int z;
        fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
        struct sockaddr_in adr_inet;
        socklen_t len_inet = sizeof adr_inet;
        z = getsockname(fd, (struct sockaddr *)&adr_inet, &len_inet);
        if (z != 0) {
          perror("getsockname error");
          return 1;
        }
        char buffer[1000];
        sprintf(buffer, "%s:%u", inet_ntoa(adr_inet.sin_addr), (unsigned)ntohs(adr_inet.sin_port));
        const char *correct = "0.0.0.0:0";
        printf("got (expected) socket: %s (%s), size %lu (%lu)\n", buffer, correct, strlen(buffer), strlen(correct));
        assert(strlen(buffer) == strlen(correct));
        assert(strcmp(buffer, correct) == 0);
        puts("done");
      }
    ''', 'done\n')

  def test_getpeername_unconnected_socket(self):
    self.do_run(r'''
      #include <sys/socket.h>
      #include <stdio.h>
      #include <assert.h>
      #include <sys/socket.h>
      #include <netinet/in.h>
      #include <arpa/inet.h>
      #include <string.h>
      int main() {
        int fd;
        int z;
        fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
        struct sockaddr_in adr_inet;
        socklen_t len_inet = sizeof adr_inet;
        z = getpeername(fd, (struct sockaddr *)&adr_inet, &len_inet);
        if (z != 0) {
          perror("getpeername error");
          return 1;
        }
        puts("unexpected success.");
      }
    ''', 'getpeername error: Socket not connected', assert_returncode=NON_ZERO)

  def test_getsockname_addrlen(self):
    self.do_runf('sockets/test_getsockname_addrlen.c', 'done\n')

  def test_sin_zero(self):
    self.do_runf('sockets/test_sin_zero.c', 'done\n')

  def test_getaddrinfo(self):
    self.do_runf('sockets/test_getaddrinfo.c', 'done\n')

  def test_getnameinfo(self):
    self.do_runf('sockets/test_getnameinfo.c', 'done\n')

  def test_gethostbyname(self):
    self.do_runf_out_file('sockets/test_gethostbyname.c')

  def test_getprotobyname(self):
    self.do_runf('sockets/test_getprotobyname.c', 'done\n')

  def test_create_socket(self):
    self.do_runf('sockets/test_create_socket.c', 'done\n')

  def test_socketpair(self):
    self.do_run(r'''
      #include <sys/socket.h>
      #include <stdio.h>
      int main() {
        int fd[2];
        int err;
        err = socketpair(AF_INET, SOCK_STREAM, 0, fd);
        if (err != 0) {
          perror("socketpair error");
          return 1;
        }
        puts("unexpected success.");
      }
    ''', 'socketpair error: Function not implemented', assert_returncode=NON_ZERO)

  def test_link(self):
    self.do_run(r'''
#include <netdb.h>

#include <sys/types.h>
#include <sys/socket.h>

int main () {
    void* thing = gethostbyname("bing.com");
    ssize_t rval = recv (0, thing, 0, 0);
    rval = send (0, thing, 0, 0);
    return 0;
}''', '', force_c=True)

  def test_linking_recv(self):
    self.do_run(r'''
      #include <sys/types.h>
      #include <sys/socket.h>
      int main(void) {
        recv(0, 0, 0, 0);
        return 0;
      }
    ''', '', force_c=True)

  def test_linking_send(self):
    self.do_run(r'''
      #include <sys/types.h>
      #include <sys/socket.h>
      int main(void) {
        send(0, 0, 0, 0);
        return 0;
      }
    ''', '', force_c=True)

  # This test verifies that function names embedded into the build with --js-library (JS functions exported to wasm)
  # are minified when -O3 is used
  # Currently we rely on Closure for full minification of every appearance of JS function names.
  # TODO: Add minification also for non-Closure users and add a non-closure config to this test.
  @is_slow_test
  @also_with_wasm2js
  def test_js_function_names_are_minified(self):
    def check_size(f, expected_size):
      if not os.path.isfile(f):
        return # Nonexistent file passes in this check
      obtained_size = os.path.getsize(f)
      print('size of generated ' + f + ': ' + str(obtained_size))
      delete_file(f)
      self.assertLess(obtained_size, expected_size)

    self.run_process([PYTHON, test_file('gen_many_js_functions.py'), 'library_long.js', 'main_long.c'])
    ret = self.do_runf('main_long.c', cflags=['-O3', '--js-library', 'library_long.js', '--closure=1'])
    self.assertTextDataIdentical('Sum of numbers from 1 to 1000: 500500 (expected 500500)', ret.strip())

    check_size('a.out.js', 150000)
    check_size('a.out.wasm', 80000)

  # Checks that C++ exceptions managing invoke_*() wrappers will not be generated if exceptions are disabled
  def test_no_invoke_functions_are_generated_if_exception_catching_is_disabled(self):
    self.skipTest('Skipping other.test_no_invoke_functions_are_generated_if_exception_catching_is_disabled: Enable after new version of fastcomp has been tagged')
    for args in ([], ['-sWASM=0']):
      self.run_process([EMXX, test_file('hello_world.cpp'), '-sDISABLE_EXCEPTION_CATCHING', '-o', 'a.html'] + args)
      output = read_file('a.js')
      self.assertContained('_main', output) # Smoke test that we actually compiled
      self.assertNotContained('invoke_', output)

  # Verifies that only the minimal needed set of invoke_*() functions will be generated when C++ exceptions are enabled
  def test_no_excessive_invoke_functions_are_generated_when_exceptions_are_enabled(self):
    self.skipTest('Skipping other.test_no_excessive_invoke_functions_are_generated_when_exceptions_are_enabled: Enable after new version of fastcomp has been tagged')
    for args in ([], ['-sWASM=0']):
      self.run_process([EMXX, test_file('invoke_i.cpp'), '-sDISABLE_EXCEPTION_CATCHING=0', '-o', 'a.html'] + args)
      output = read_file('a.js')
      self.assertContained('invoke_i', output)
      self.assertNotContained('invoke_ii', output)
      self.assertNotContained('invoke_v', output)

  @parameterized({
    'O0': (False, ['-O0']),
    'O0_emit': (True, ['-O0', '-sEMIT_EMSCRIPTEN_LICENSE']),
    'O2': (False, ['-O2']),
    'O2_emit': (True, ['-O2', '-sEMIT_EMSCRIPTEN_LICENSE']),
    'O2_js_emit': (True, ['-O2', '-sEMIT_EMSCRIPTEN_LICENSE', '-sWASM=0']),
    'O2_closure': (False, ['-O2', '--closure=1']),
    'O2_closure_emit': (True, ['-O2', '-sEMIT_EMSCRIPTEN_LICENSE', '--closure=1']),
    'O2_closure_js_emit': (True, ['-O2', '-sEMIT_EMSCRIPTEN_LICENSE', '--closure=1', '-sWASM=0']),
  })
  def test_emscripten_license(self, expect_license, args):
    # fastcomp does not support the new license flag
    self.run_process([EMCC, test_file('hello_world.c')] + args)
    js = read_file('a.out.js')
    licenses_found = len(re.findall('Copyright [0-9]* The Emscripten Authors', js))
    if expect_license:
      self.assertNotEqual(licenses_found, 0, 'Unable to find license block in output file!')
      self.assertEqual(licenses_found, 1, 'Found too many license blocks in the output file!')
    else:
      self.assertEqual(licenses_found, 0, 'Found a license block in the output file, but it should not have been there!')

  # This test verifies that the generated exports from wasm module only reference the
  # unminified exported name exactly once.  (need to contain the export name once for unminified
  # access from calling code, and should not have the unminified name exist more than once, that
  # would be wasteful for size)
  @parameterized({
    '': ([],),
    'closure': (['--closure=1'],),
  })
  @parameterized({
    'O2': (['-O2'],),
    'O3': (['-O3'],),
    'Os': (['-Os'],),
  })
  @parameterized({
    'sync': (['-sWASM_ASYNC_COMPILATION=0'],),
    'wasm2js': (['-sWASM=0'],),
  })
  def test_function_exports_are_small(self, args, opt, closure):
    extra_args = args + opt + closure
    args = [EMCC, test_file('long_function_name_in_export.c'), '-o', 'a.html', '-sENVIRONMENT=web', '-sDECLARE_ASM_MODULE_EXPORTS=0', '-Werror'] + extra_args
    self.run_process(args)

    output = read_file('a.js')
    delete_file('a.js')
    self.assertNotContained('_thisIsAFunctionExportedFromAsmJsOrWasmWithVeryLongFunction', output)

    # TODO: Add stricter testing when Wasm side is also optimized: (currently Wasm does still need
    # to reference exports multiple times)
    if '-sWASM=0' in args:
      num_times_export_is_referenced = output.count('thisIsAFunctionExportedFromAsmJsOrWasmWithVeryLongFunction')
      self.assertEqual(num_times_export_is_referenced, 1)

  # Tests the library_c_preprocessor.js functionality.
  @crossplatform
  @parameterized({
    '': ([],),
    'closure': (['--closure=1'],),
  })
  def test_c_preprocessor(self, args):
    self.do_runf('test_c_preprocessor.c', cflags=['--js-library', path_from_root('src/lib/libc_preprocessor.js')] + args)

  # Test that legacy settings that have been fixed to a specific value and their value can no longer be changed,
  def test_legacy_settings_forbidden_to_change(self):
    expected = 'emcc: error: invalid command line setting `-sMEMFS_APPEND_TO_TYPED_ARRAYS=0`: Starting from Emscripten 1.38.26, MEMFS_APPEND_TO_TYPED_ARRAYS=0 is no longer supported'
    self.assert_fail([EMCC, '-sMEMFS_APPEND_TO_TYPED_ARRAYS=0', test_file('hello_world.c')], expected)

    self.run_process([EMCC, '-sMEMFS_APPEND_TO_TYPED_ARRAYS', test_file('hello_world.c')])
    self.run_process([EMCC, '-sPRECISE_I64_MATH=2', test_file('hello_world.c')])

  def test_jsmath(self):
    self.run_process([EMXX, test_file('other/jsmath.cpp'), '-Os', '-o', 'normal.js', '--closure', '0'])
    normal_js_size = os.path.getsize('normal.js')
    normal_wasm_size = os.path.getsize('normal.wasm')
    self.run_process([EMXX, test_file('other/jsmath.cpp'), '-Os', '-o', 'jsmath.js', '-sJS_MATH', '--closure', '0'])
    jsmath_js_size = os.path.getsize('jsmath.js')
    jsmath_wasm_size = os.path.getsize('jsmath.wasm')
    # js math increases JS size, but decreases wasm, and wins overall
    # it would win more with closure, but no point in making the test slower)
    self.assertLess(normal_js_size, jsmath_js_size)
    self.assertLess(jsmath_wasm_size, normal_wasm_size)
    self.assertLess(jsmath_js_size + jsmath_wasm_size, 0.90 * (normal_js_size + normal_wasm_size))
    # js math has almost identical output, but misses some corner cases, 4 out of 34
    normal = self.run_js('normal.js').splitlines()
    jsmath = self.run_js('jsmath.js').splitlines()
    self.assertEqual(len(normal), len(jsmath))
    diff = 0
    for i in range(len(normal)):
      if normal[i] != jsmath[i]:
        diff += 1
    self.assertLess(diff, 5)

  def test_strict_mode_hello_world(self):
    # Verify that strict mode can be used for simple hello world program both
    # via the environment EMCC_STRICT=1 and from the command line `-sSTRICT`
    self.do_runf_out_file('hello_world.c', cflags=['-sSTRICT'])
    with env_modify({'EMCC_STRICT': '1'}):
      self.do_runf_out_file('hello_world.c')

  def test_strict_mode_full_library(self):
    self.do_runf_out_file('hello_world.c', cflags=['-sSTRICT', '-sINCLUDE_FULL_LIBRARY'])

  def test_legacy_settings(self):
    cmd = [EMCC, test_file('hello_world.c'), '-sSPLIT_MEMORY=0']

    # By default warnings are not shown
    stderr = self.run_process(cmd, stderr=PIPE).stderr
    self.assertNotContained('warning', stderr)

    # Adding or -Wlegacy-settings enables the warning
    stderr = self.run_process(cmd + ['-Wlegacy-settings'], stderr=PIPE).stderr
    self.assertContained('warning: use of legacy setting: SPLIT_MEMORY', stderr)
    self.assertContained('[-Wlegacy-settings]', stderr)

  def test_strict_mode_legacy_settings(self):
    cmd = [EMCC, test_file('hello_world.c'), '-sSPLIT_MEMORY=0']
    self.run_process(cmd)

    self.assert_fail(cmd + ['-sSTRICT'], 'legacy setting used in strict mode: SPLIT_MEMORY')

    with env_modify({'EMCC_STRICT': '1'}):
      self.assert_fail(cmd, 'legacy setting used in strict mode: SPLIT_MEMORY')

  def test_strict_mode_legacy_settings_runtime(self):
    # Verify that legacy settings are not accessible at runtime under strict
    # mode.
    self.set_setting('RETAIN_COMPILER_SETTINGS')
    src = r'''\
    #include <stdio.h>
    #include <emscripten.h>

    int main() {
      printf("BINARYEN_METHOD: %s\n", (char*)emscripten_get_compiler_setting("BINARYEN_METHOD"));
      return 0;
    }
    '''
    self.do_run(src, 'BINARYEN_METHOD: native-wasm')
    with env_modify({'EMCC_STRICT': '1'}):
      self.do_run(src, 'invalid compiler setting: BINARYEN_METHOD')
    self.set_setting('STRICT')
    self.do_run(src, 'invalid compiler setting: BINARYEN_METHOD')

  def test_renamed_setting(self):
    # Verify that renamed settings are available by either name (when not in
    # strict mode.
    self.set_setting('RETAIN_COMPILER_SETTINGS')
    src = r'''\
    #include <stdio.h>
    #include <emscripten.h>

    int main() {
      printf("%ld %ld\n",
        emscripten_get_compiler_setting("BINARYEN_ASYNC_COMPILATION"),
        emscripten_get_compiler_setting("WASM_ASYNC_COMPILATION"));
      return 0;
    }
    '''

    # Setting the new name should set both
    self.set_setting('WASM_ASYNC_COMPILATION', 0)
    self.do_run(src, '0 0')
    self.set_setting('WASM_ASYNC_COMPILATION')
    self.do_run(src, '1 1')
    self.clear_setting('WASM_ASYNC_COMPILATION')

    # Setting the old name should set both
    self.set_setting('BINARYEN_ASYNC_COMPILATION', 0)
    self.do_run(src, '0 0')
    self.set_setting('BINARYEN_ASYNC_COMPILATION')
    self.do_run(src, '1 1')

  def test_strict_mode_legacy_settings_library(self):
    create_file('lib.js', r'''
#if SPLIT_MEMORY
#endif
''')
    cmd = [EMCC, test_file('hello_world.c'), '-o', 'out.js', '--js-library', 'lib.js']
    self.run_process(cmd)
    self.assert_fail(cmd + ['-sSTRICT'], 'ReferenceError: SPLIT_MEMORY is not defined')
    with env_modify({'EMCC_STRICT': '1'}):
      self.assert_fail(cmd, 'ReferenceError: SPLIT_MEMORY is not defined')

  def test_strict_mode_link_cxx(self):
    # In strict mode C++ programs fail to link unless run with `em++`.
    self.run_process([EMXX, '-sSTRICT', test_file('hello_libcxx.cpp')])
    self.assert_fail([EMCC, '-sSTRICT', test_file('hello_libcxx.cpp')], 'undefined symbol: std::__2::cout')

  def test_strict_mode_override(self):
    create_file('empty.c', '')
    # IGNORE_MISSING_MAIN default to 1 so this works by default
    self.run_process([EMCC, 'empty.c'])
    # strict mode disables it causing a link error
    self.expect_fail([EMCC, '-sSTRICT', 'empty.c'])
    # explicly setting IGNORE_MISSING_MAIN overrides the STRICT setting
    self.run_process([EMCC, '-sSTRICT', '-sIGNORE_MISSING_MAIN', 'empty.c'])

  # Tests the difference between options -sSAFE_HEAP=1 and -sSAFE_HEAP=2.
  @also_with_wasm64
  def test_safe_heap_2(self):
    self.do_runf('safe_heap_2.c', 'alignment fault',
                 cflags=['-sSAFE_HEAP=1'], assert_returncode=NON_ZERO)

    self.do_runf('safe_heap_2.c', '0 1 2 3 4',
                 cflags=['-sSAFE_HEAP=2'])

  @also_with_wasm2js
  def test_safe_heap_log(self):
    self.set_setting('SAFE_HEAP')
    self.set_setting('SAFE_HEAP_LOG')
    self.do_runf('hello_world.c', 'SAFE_HEAP loading: ')

  def test_mini_printfs(self):
    def test(code):
      create_file('src.c', '''
          #include <stdio.h>
          void* unknown_value;
          int main() {
            %s
          }
      ''' % code)
      self.run_process([EMCC, 'src.c', '-O1'])
      return os.path.getsize('a.out.wasm')

    i = test('printf("%d", *(int*)unknown_value);')
    f = test('printf("%f", *(double*)unknown_value);')
    lf = test('printf("%Lf", *(long double*)unknown_value);')
    both = test('printf("%d", *(int*)unknown_value); printf("%Lf", *(long double*)unknown_value);')
    print(f'int:{i} float:{f} double:{lf}: both{both}')

    # iprintf is much smaller than printf with float support
    self.assertGreater(i, f - 3800)
    self.assertLess(i, f - 3000)
    # __small_printf is somewhat smaller than printf with long double support
    self.assertGreater(f, lf - 900)
    self.assertLess(f, lf - 500)
    # both is a little bigger still
    self.assertGreater(lf, both - 150)
    self.assertLess(lf, both - 50)

  @parameterized({
    'normal': ([], '''\
0.000051 => -5.123719529365189373493194580078e-05
0.000051 => -5.123719300544352718866300544498e-05
0.000051 => -5.123719300544352718866300544498e-05
'''),
    'full_long_double': (['-sPRINTF_LONG_DOUBLE'], '''\
0.000051 => -5.123719529365189373493194580078e-05
0.000051 => -5.123719300544352718866300544498e-05
0.000051 => -5.123719300544352710023893104250e-05
'''),
  })
  def test_long_double_printing(self, args, expected):
    create_file('src.c', r'''
#include <stdio.h>

int main(void) {
    float f = 5.123456789e-5;
    double d = 5.123456789e-5;
    long double ld = 5.123456789e-5;
    printf("%f => %.30e\n", f, f  / (f - 1));
    printf("%f => %.30e\n", d, d / (d - 1));
    printf("%Lf => %.30Le\n", ld, ld / (ld - 1));
}
    ''')
    self.do_runf('src.c', expected, cflags=args)

  # Tests that passing -sMALLOC=none will not include system malloc() to the build.
  def test_malloc_none(self):
    self.assert_fail([EMCC, test_file('malloc_none.c'), '-sMALLOC=none'], 'undefined symbol: malloc')

  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  @also_with_wasm64
  @parameterized({
    '': ('c', []),
    'cpp': ('cpp', []),
    'growth': ('cpp', ['-sALLOW_MEMORY_GROWTH']),
    'wasmfs': ('c', ['-sWASMFS']),
  })
  def test_lsan_leaks(self, ext, args):
    self.do_runf(
      'other/test_lsan_leaks.' + ext,
      cflags=['-fsanitize=leak'] + args,
      assert_returncode=NON_ZERO,
      assert_all=True,
      expected_output=[
        'Direct leak of 2048 byte(s) in 1 object(s) allocated from',
        'Direct leak of 1337 byte(s) in 1 object(s) allocated from',
        'Direct leak of 42 byte(s) in 1 object(s) allocated from',
      ])

  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  @parameterized({
    'c': ['c', [
      r'in malloc .*lsan_interceptors\.cpp:\d+:\d+',
      r'(?im)in f (|[/a-z\.]:).*/test_lsan_leaks\.c:6:21$',
      r'(?im)in main (|[/a-z\.]:).*/test_lsan_leaks\.c:10:16$',
      r'(?im)in main (|[/a-z\.]:).*/test_lsan_leaks\.c:12:3$',
      r'(?im)in main (|[/a-z\.]:).*/test_lsan_leaks\.c:13:3$',
    ]],
    'cpp': ['cpp', [
      r'in operator new\[\]\(unsigned long\) .*lsan_interceptors\.cpp:\d+:\d+',
      r'(?im)in f\(\) (|[/a-z\.]:).*/test_lsan_leaks\.cpp:4:21$',
      r'(?im)in main (|[/a-z\.]:).*/test_lsan_leaks\.cpp:8:16$',
      r'(?im)in main (|[/a-z\.]:).*/test_lsan_leaks\.cpp:10:3$',
      r'(?im)in main (|[/a-z\.]:).*/test_lsan_leaks\.cpp:11:3$',
    ]],
  })
  def test_lsan_stack_trace(self, ext, regexes):
    self.do_runf(
      'other/test_lsan_leaks.' + ext,
      cflags=['-fsanitize=leak', '-gsource-map', '-g2'],
      regex=True,
      assert_all=True,
      assert_returncode=NON_ZERO,
      expected_output=[
        r'Direct leak of 2048 byte\(s\) in 1 object\(s\) allocated from',
        r'Direct leak of 1337 byte\(s\) in 1 object\(s\) allocated from',
        r'Direct leak of 42 byte\(s\) in 1 object\(s\) allocated from',
      ] + regexes)

  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  @parameterized({
    'c': ['c'],
    'cpp': ['cpp'],
  })
  def test_lsan_no_leak(self, ext):
    self.do_runf('other/test_lsan_no_leak.' + ext,
                 regex=True,
                 cflags=['-fsanitize=leak', '-sASSERTIONS=0'],
                 expected_output=[r'^\s*$'])

  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  def test_lsan_no_stack_trace(self):
    self.do_runf(
      'other/test_lsan_leaks.c',
      assert_all=True,
      cflags=['-fsanitize=leak', '-DDISABLE_CONTEXT'],
      assert_returncode=NON_ZERO,
      expected_output=[
        'Direct leak of 3427 byte(s) in 3 object(s) allocated from:',
        'SUMMARY: LeakSanitizer: 3427 byte(s) leaked in 3 allocation(s).',
      ])

  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  @also_with_standalone_wasm()
  def test_asan_null_deref(self):
    self.do_runf(
      'other/test_asan_null_deref.c',
      cflags=['-fsanitize=address'],
      assert_returncode=NON_ZERO,
      expected_output=[
        'AddressSanitizer: null-pointer-dereference on address',
      ])

  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  @also_with_wasm64
  def test_asan_sync_compilation(self):
    self.set_setting('WASM_ASYNC_COMPILATION', 0)
    self.do_runf(
      'other/test_asan_null_deref.c',
      cflags=['-fsanitize=address', '-gsource-map'],
      assert_returncode=NON_ZERO,
      expected_output=[
        'AddressSanitizer: null-pointer-dereference on address',
      ])

  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  def test_asan_memory_growth(self):
    self.do_runf(
      'other/test_asan_null_deref.c',
      cflags=['-fsanitize=address', '-sALLOW_MEMORY_GROWTH'],
      assert_returncode=NON_ZERO,
      expected_output=[
        'AddressSanitizer: null-pointer-dereference on address',
      ])

  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  def test_asan_no_stack_trace(self):
    self.do_runf(
      'other/test_lsan_leaks.c',
      assert_all=True,
      cflags=['-fsanitize=address', '-DDISABLE_CONTEXT'],
      assert_returncode=NON_ZERO,
      expected_output=[
        'Direct leak of 3427 byte(s) in 3 object(s) allocated from:',
        'SUMMARY: AddressSanitizer: 3427 byte(s) leaked in 3 allocation(s).',
      ])

  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  def test_asan_pthread_stubs(self):
    self.do_runf('other/test_asan_pthread_stubs.c', cflags=['-fsanitize=address'])

  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  def test_asan_strncpy(self):
    # Regression test for asan false positives in strncpy:
    # https://github.com/emscripten-core/emscripten/issues/14618
    self.do_runf('other/test_asan_strncpy.c', cflags=['-fsanitize=address'])

  @parameterized({
    'asan': ['AddressSanitizer: null-pointer-dereference', '-fsanitize=address'],
    'safe_heap': ['Aborted(segmentation fault storing 1 bytes at address 0)', '-sSAFE_HEAP'],
  })
  @parameterized({
    '': [],
    'memgrowth': ['-pthread', '-sALLOW_MEMORY_GROWTH', '-Wno-pthreads-mem-growth'],
  })
  def test_null_deref_via_js(self, expected_output, *args):
    # Multiple JS transforms look for pattern like `HEAPxx[...]` and transform it.
    # This test ensures that one of the transforms doesn't produce a pattern that
    # another pass can't find anymore, that is that features can work in conjunction.
    self.do_runf(
      'other/test_safe_heap_user_js.c',
      cflags=args,
      assert_returncode=NON_ZERO,
      expected_output=[expected_output])

  @requires_pthreads
  def test_proxy_to_pthread_stack(self):
    # Check that the proxied main gets run with STACK_SIZE setting and not
    # DEFAULT_PTHREAD_STACK_SIZE.
    self.do_runf('other/test_proxy_to_pthread_stack.c', 'done\n',
                 cflags=['-pthread', '-sPROXY_TO_PTHREAD',
                            '-sDEFAULT_PTHREAD_STACK_SIZE=64kb',
                            '-sSTACK_SIZE=128kb', '-sEXIT_RUNTIME',
                            '--profiling-funcs'])

  @crossplatform
  @no_windows('ptys and select are not available on windows')
  def test_color_diagnostics(self):
    create_file('src.c', 'int main() {')
    returncode, output = self.run_on_pty([EMCC, 'src.c'])
    self.assertNotEqual(returncode, 0)
    self.assertIn(b"\x1b[1msrc.c:1:13: \x1b[0m\x1b[0;1;31merror: \x1b[0m\x1b[1mexpected '}'\x1b[0m", output)
    # Verify that emcc errors show up as red and bold
    self.assertIn(b"emcc: \x1b[31m\x1b[1m", output)

  @crossplatform
  @parameterized({
    '': ['-fno-diagnostics-color'],
    'never': ['-fdiagnostics-color=never'],
  })
  @no_windows('ptys and select are not available on windows')
  def test_color_diagnostics_disable(self, flag):
    create_file('src.c', 'int main() {')

    returncode, output = self.run_on_pty([EMCC, flag, 'src.c'])
    self.assertNotEqual(returncode, 0)
    self.assertNotIn(b'\x1b', output)

  @crossplatform
  #  There are 3 different ways for force color output in clang
  @parameterized({
    '': ['-fcolor-diagnostics'],
    'alt': ['-fdiagnostics-color'],
    'always': ['-fdiagnostics-color=always'],
  })
  def test_color_diagnostics_force(self, flag):
    create_file('src.c', 'int main() {')
    # -fansi-escape-codes is needed on windows in order to get clang to emit ANSI colors
    output = self.expect_fail([EMCC, flag, '-fansi-escape-codes', 'src.c'])
    self.assertIn("\x1b[1msrc.c:1:13: \x1b[0m\x1b[0;1;31merror: \x1b[0m\x1b[1mexpected '}'\x1b[0m", output)
    # Verify that emcc errors show up as red and bold from emcc
    self.assertIn('emcc: \x1b[31m\x1b[1m', output)

  def test_sanitizer_color(self):
    create_file('src.c', '''
      #include <emscripten.h>
      int main() {
        int *p = 0, q;
        EM_ASM({ Module.printWithColors = true; });
        q = *p;
      }
    ''')
    self.do_runf('src.c', '\x1b[1msrc.c', cflags=['-fsanitize=null'])

  def test_main_reads_params(self):
    create_file('no.c', '''
      int main() {
        return 42;
      }
    ''')
    self.run_process([EMCC, 'no.c', '-O3', '-o', 'no.js'])
    no = os.path.getsize('no.js')
    create_file('yes.c', '''
      int main(int argc, char **argv) {
        return (long)argv[argc-1];
      }
    ''')
    self.run_process([EMCC, 'yes.c', '-O3', '-o', 'yes.js'])
    yes = os.path.getsize('yes.js')
    # not having to set up argc/argv allows us to avoid including a
    # significant amount of JS for string support (which is not needed
    # otherwise in such a trivial program).
    self.assertLess(no, 0.95 * yes)

  @all_engines
  def test_INCOMING_MODULE_JS_API(self):
    def test(args):
      self.do_runf_out_file('hello_world.c', cflags=['-O3', '--closure=1', '-sENVIRONMENT=node,shell', '--output-eol=linux'] + args)
      return os.path.getsize('hello_world.js')
    normal = test([])
    changed = test(['-sINCOMING_MODULE_JS_API=[]'])
    print('sizes', normal, changed)
    # Changing this option to [] should decrease code size.
    self.assertLess(changed, normal)

  def test_INCOMING_MODULE_JS_API_missing(self):
    create_file('pre.js', 'Module.onRuntimeInitialized = () => out("initialized");')
    self.cflags += ['--pre-js=pre.js']
    self.do_runf('hello_world.c', 'initialized')

    # The INCOMING_MODULE_JS_API setting can limit the incoming module
    # API, and we assert if the incoming module has a property that
    # is ignored due to this setting.
    self.set_setting('INCOMING_MODULE_JS_API', [])

    self.do_runf('hello_world.c', 'Aborted(`Module.onRuntimeInitialized` was supplied but `onRuntimeInitialized` not included in INCOMING_MODULE_JS_API)', assert_returncode=NON_ZERO)

  def test_INCOMING_MODULE_JS_API_invalid(self):
    expected = 'emcc: error: invalid entry in INCOMING_MODULE_JS_API: foo [-Wunused-command-line-argument] [-Werror]'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sINCOMING_MODULE_JS_API=foo', '-Werror'], expected)

  def test_llvm_includes(self):
    create_file('atomics.c', '#include <stdatomic.h>')
    self.build('atomics.c')

  def test_mmap_and_munmap(self):
    cflags = []
    for f in ('data_ro.dat', 'data_rw.dat'):
      create_file(f, 'Test file')
      cflags.extend(['--embed-file', f])
    self.do_other_test('test_mmap_and_munmap.c', cflags)

  @also_with_asan
  def test_mmap_and_munmap_anonymous(self):
    self.do_other_test('test_mmap_and_munmap_anonymous.cpp', cflags=['-sNO_FILESYSTEM'])

  def test_mmap_memorygrowth(self):
    self.do_other_test('test_mmap_memorygrowth.cpp', ['-sALLOW_MEMORY_GROWTH'])

  @also_with_noderawfs
  def test_mmap_empty(self):
    self.do_other_test('test_mmap_empty.c')

  def test_mmap_empty_wasmfs(self):
    self.do_other_test('test_mmap_empty.c', cflags=['-sWASMFS'])

  def test_files_and_module_assignment(self):
    # a pre-js can set Module to a new object or otherwise undo file preloading/
    # embedding changes to Module.preRun. we show an error to avoid confusion
    create_file('pre.js', 'Module = {};')
    create_file('src.c', r'''
      #include <stdio.h>
      int main() {
        printf("file exists: %p\n", fopen("src.cpp", "rb"));
      }
    ''')
    expected = 'Module.preRun should exist because file support used it; did a pre-js delete it?'
    self.do_runf('src.c', expected, cflags=['--pre-js=pre.js', '--preload-file=src.c'],
                 assert_returncode=NON_ZERO)

    def test_error(pre):
      create_file('pre.js', pre)
      expected = 'All preRun tasks that exist before user pre-js code should remain after; did you replace Module or modify Module.preRun?'
      self.do_runf('src.c', expected, cflags=['--pre-js=pre.js', '--preload-file=src.c'], assert_returncode=NON_ZERO)

    # error if the user replaces Module or Module.preRun
    test_error('Module = { preRun: [] };')
    test_error('Module.preRun = [];')

  def test_EMSCRIPTEN_macro(self):
    create_file('src.c', '''
      #include <emscripten.h>

      #ifdef EMSCRIPTEN
      int foo;
      #endif
    ''')
    self.assert_fail([EMCC, '-Werror', 'src.c', '-c'], "'EMSCRIPTEN' has been marked as deprecated: use __EMSCRIPTEN__ instead")
    self.assert_fail([EMCC, '-sSTRICT', '-Werror', 'src.c', '-c'], "'EMSCRIPTEN' has been marked as deprecated: use __EMSCRIPTEN__ instead")

  def test_exception_settings(self):
    for catching, throwing, opts in itertools.product([0, 1], repeat=3):
      cmd = [EMXX, test_file('other/exceptions_modes_symbols_defined.cpp'), '-sDISABLE_EXCEPTION_THROWING=%d' % (1 - throwing), '-sDISABLE_EXCEPTION_CATCHING=%d' % (1 - catching), '-O%d' % opts]
      print(cmd)
      if not throwing and not catching:
        self.assert_fail(cmd, 'DISABLE_EXCEPTION_THROWING was set (likely due to -fno-exceptions), which means no C++ exception throwing support code is linked in, but such support is required')
      elif not throwing and catching:
        self.assert_fail(cmd, 'DISABLE_EXCEPTION_THROWING was set (probably from -fno-exceptions) but is not compatible with enabling exception catching (DISABLE_EXCEPTION_CATCHING=0)')
      else:
        self.run_process(cmd)

  def test_fignore_exceptions(self):
    # the new clang flag -fignore-exceptions basically is the same as -sDISABLE_EXCEPTION_CATCHING,
    # that is, it allows throwing, but emits no support code for catching.
    self.run_process([EMXX, test_file('other/exceptions_modes_symbols_defined.cpp'), '-sDISABLE_EXCEPTION_CATCHING=0'])
    enable_size = os.path.getsize('a.out.wasm')
    self.run_process([EMXX, test_file('other/exceptions_modes_symbols_defined.cpp'), '-sDISABLE_EXCEPTION_CATCHING'])
    disable_size = os.path.getsize('a.out.wasm')
    self.run_process([EMXX, test_file('other/exceptions_modes_symbols_defined.cpp'), '-s', '-fignore-exceptions'])
    ignore_size = os.path.getsize('a.out.wasm')
    self.assertGreater(enable_size, disable_size)
    self.assertEqual(disable_size, ignore_size)

  @parameterized({
    # exceptions are off by default
    'off':  ([], [], False),
    # enabling exceptions at link and compile works
    'on': (['-fexceptions'], ['-fexceptions'], True),
    # just compile isn't enough as the JS runtime lacks support
    'compile_only': (['-fexceptions'], [], False),
    # just link isn't enough as codegen didn't emit exceptions support
    'link_only': ([], ['-fexceptions'], False),
    'standalone': (['-fexceptions'], ['-fexceptions', '-sSTANDALONE_WASM'], True),
  })
  def test_f_exception(self, compile_flags, link_flags, expect_caught):
    create_file('src.cpp', r'''
      #include <stdio.h>
      int main () {
        try {
          throw 42;
        } catch (int e) {
          printf("CAUGHT: %d\n", e);
        }
        return 0;
      }
    ''')
    self.run_process([EMXX, 'src.cpp', '-c', '-o', 'src.o'] + compile_flags)
    self.run_process([EMXX, 'src.o'] + link_flags)
    result = self.run_js('a.out.js', assert_returncode=0 if expect_caught else NON_ZERO)
    if not expect_caught:
      self.assertContainedIf('exception catching is disabled, this exception cannot be caught', result, expect_caught)
    self.assertContainedIf('CAUGHT', result, expect_caught)

  def test_exceptions_with_closure_and_without_catching(self):
    # using invokes will require setThrew(), and closure will error if it is not
    # defined. this test checks that we define it even without catching any
    # exceptions (if we did catch exceptions, that would include library code
    # that would use setThrew() anyhow)
    create_file('src.cpp', r'''
      #include <stdio.h>
      #include <emscripten.h>

      struct A {
        ~A() {
          puts("~A");
        }
      };

      int main() {
        // Construct an instance of a class with a destructor, which will cause the
        // use of invokes to ensure its destructor runs.
        A a;
        throw 5;
      }
      ''')
    self.run_process([EMCC, 'src.cpp', '-fexceptions', '--closure=1'])

  def test_assertions_on_incoming_module_api_changes(self):
    create_file('pre.js', 'Module.read = () => {};')
    self.do_runf('hello_world.c', 'Module.read option was removed',
                 cflags=['-sASSERTIONS', '--pre-js', 'pre.js'],
                 assert_returncode=NON_ZERO)

  def test_modularize_assertions_on_reject_promise(self):
    # Check that there is an uncaught exception in modularize mode.
    # Once we added an `uncaughtException` handler to the global process
    # but after a breaking change in #18743 it is now expected that the
    # user will handle the exception themselves in Modularize mode.
    create_file('test.js', r'''
      Promise.reject("oops");
    ''')
    self.run_process([EMCC, test_file('hello_world.c'), '-sMODULARIZE', '-sASSERTIONS', '--extern-post-js', 'test.js'])
    # A return code of 1 is from an uncaught exception not handled by
    # the domain or the 'uncaughtException' event handler.
    out = self.run_js('a.out.js', assert_returncode=1)
    expected = [
      'UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "oops".',
      'error: oops',
    ]
    self.assertContained(expected, out)

  def test_assertions_on_reject_promise(self):
    # Check that promise rejections give the correct error code
    # when -sASSERTIONS are enabled
    create_file('test.js', r'''
      Promise.reject();
    ''')
    self.run_process([EMCC, test_file('hello_world.c'), '-sASSERTIONS', '--extern-post-js', 'test.js'])
    # Node exits with 1 on Uncaught Fatal Exception (including unhandled rejections)
    self.run_js('a.out.js', assert_returncode=1)

  def test_on_reject_promise(self):
    # Check that promise rejections give the correct error code
    create_file('test.js', r'''
      Promise.reject("oops");
    ''')
    self.run_process([EMCC, test_file('hello_world.c'), '--extern-post-js', 'test.js'])
    # Node exits with 1 on Uncaught Fatal Exception (including unhandled rejections)
    err = self.run_js('a.out.js', assert_returncode=1)
    self.assertContained(['UnhandledPromiseRejection: .*oops', 'error: oops'], err, regex=True)

  def test_em_asm_duplicate_strings(self):
    # We had a regression where two different EM_ASM strings from two different
    # object files were de-duplicated in wasm-emscripten-finalize.  This used to
    # work when we used zero-based index for store the JS strings, but once we
    # switched to absolute addresses the string needs to exist twice in the JS
    # file.
    create_file('foo.c', '''
      #include <emscripten.h>
      void foo() {
        EM_ASM({ out('Hello, world!'); });
      }
    ''')
    create_file('main.c', '''
      #include <emscripten.h>

      void foo();

      int main() {
        foo();
        EM_ASM({ out('Hello, world!'); });
        return 0;
      }
    ''')
    self.run_process([EMCC, '-c', 'foo.c'])
    self.run_process([EMCC, '-c', 'main.c'])
    self.run_process([EMCC, 'foo.o', 'main.o'])
    self.assertContained('Hello, world!\nHello, world!\n', self.run_js('a.out.js'))

  def test_em_asm_c89(self):
    create_file('src.c', '''
      #include <emscripten/em_asm.h>
      int main(void) {
        EM_ASM({ out('hello'); });
      }\n''')
    self.run_process([EMCC, '-c', 'src.c',
                      '-pedantic', '-Wall', '-Werror',
                      '-Wno-gnu-zero-variadic-macro-arguments',
                      '-Wno-c23-extensions'])

  def test_em_asm_strict_c(self):
    create_file('src.c', '''
      #include <emscripten/em_asm.h>
      int main() {
        EM_ASM({ out('Hello, world!'); });
      }
    ''')
    self.assert_fail([EMCC, '-std=c11', 'src.c'], 'EM_ASM does not work in -std=c* modes, use -std=gnu* modes instead')

  def test_em_asm_invalid(self):
    # Test that invalid EM_ASM in side modules since is detected at build time.
    err = self.expect_fail([EMCC, '-sSIDE_MODULE', test_file('other/test_em_asm_invalid.c')])
    # Check for the error message syntax produced by either Node or Bun
    self.assertContained(["SyntaxError: Unexpected token '*'", 'error: Unexpected *'], err)
    self.assertContained('emcc: error: EM_ASM function validation failed', err)

  def test_boost_graph(self):
    self.do_runf('test_boost_graph.cpp', cflags=['-std=c++14', '-sUSE_BOOST_HEADERS'])
    self.do_runf('test_boost_graph.cpp', cflags=['-std=c++14', '--use-port=boost_headers'])

  def test_setjmp_em_asm(self):
    create_file('src.c', '''
      #include <emscripten.h>
      #include <setjmp.h>

      int main() {
        jmp_buf buf;
        setjmp(buf);
        EM_ASM({
          out("Hello, world!");
        });
      }
    ''')
    err = self.expect_fail([EMCC, 'src.c'])
    self.assertIn('Cannot use EM_ASM* alongside setjmp/longjmp', err)
    self.assertIn('Please consider using EM_JS, or move the EM_ASM into another function.', err)

  def test_setjmp_emulated_casts(self):
    # using setjmp causes invokes(), and EMULATE_FUNCTION_POINTER_CASTS changes
    # how the wasm table works; test that they work together properly
    create_file('src.c', r'''
      #include <stdio.h>
      #include <setjmp.h>
      int main() {
        jmp_buf jb;
        if (!setjmp(jb)) {
          printf("ok\n");
          longjmp(jb, 1);
        } else {
          printf("done\n");
        }
      }
    ''')
    self.do_runf('src.c', 'ok\ndone\n', cflags=['-sEMULATE_FUNCTION_POINTER_CASTS'])

  def test_no_lto(self):
    # This used to fail because settings.LTO didn't reflect `-fno-lto`.
    # See bug https://github.com/emscripten-core/emscripten/issues/20308
    create_file('src.c', r'''
      #include <stdio.h>
      #include <setjmp.h>
      int main() {
        jmp_buf jb;
        if (!setjmp(jb)) {
          printf("ok\n");
          longjmp(jb, 1);
        } else {
          printf("done\n");
        }
      }
    ''')
    self.do_runf('src.c', 'ok\ndone\n', cflags=['-flto', '-fno-lto'])

  def test_missing_stdlibs(self):
    # Certain standard libraries are expected to be useable via -l flags but
    # don't actually exist in our standard library path.  Make sure we don't
    # error out when linking with these flags.
    self.run_process([EMCC, test_file('hello_world.c'), '-lm', '-ldl', '-lrt', '-lpthread'])

  def test_supported_linker_flags(self):
    out = self.run_process([EMCC, test_file('hello_world.c'), '-Wl,-rpath-link,foo'], stderr=PIPE).stderr
    self.assertContained('warning: ignoring unsupported linker flag: `-rpath-link`', out)

    out = self.run_process([EMCC, test_file('hello_world.c'),
                            '-Wl,--no-check-features,-mllvm,--data-sections'], stderr=PIPE).stderr
    self.assertNotContained('warning: ignoring unsupported linker flag', out)

    out = self.run_process([EMCC, test_file('hello_world.c'), '-Wl,-allow-shlib-undefined'], stderr=PIPE).stderr
    self.assertContained('warning: ignoring unsupported linker flag: `-allow-shlib-undefined`', out)

    out = self.run_process([EMCC, test_file('hello_world.c'), '-Wl,--allow-shlib-undefined'], stderr=PIPE).stderr
    self.assertContained('warning: ignoring unsupported linker flag: `--allow-shlib-undefined`', out)

    out = self.run_process([EMCC, test_file('hello_world.c'), '-Wl,-version-script,foo'], stderr=PIPE).stderr
    self.assertContained('warning: ignoring unsupported linker flag: `-version-script`', out)

  def test_supported_linker_flag_skip_next(self):
    # Regression test for a bug where skipping an unsupported linker flag
    # could skip the next unrelated linker flag.
    self.assert_fail([EMCC, test_file('hello_world.c'), '-Wl,-version-script,foo', '-lbar'], 'error: unable to find library -lbar')

  def test_linker_flags_pass_through(self):
    self.assert_fail([EMCC, test_file('hello_world.c'), '-Wl,--waka'], 'wasm-ld: error: unknown argument: --waka')

    self.assert_fail([EMCC, test_file('hello_world.c'), '-Xlinker', '--waka'], 'wasm-ld: error: unknown argument: --waka')

    # Explicitly check that emcc doesn't try to process passthrough args
    self.assert_fail([EMCC, test_file('hello_world.c'), '-Xlinker', '--post-link'], 'wasm-ld: error: unknown argument: --post-link')

    err = self.run_process([EMCC, test_file('hello_world.c'), '-z', 'foo'], stderr=PIPE).stderr
    self.assertContained('wasm-ld: warning: unknown -z value: foo', err)

    err = self.run_process([EMCC, test_file('hello_world.c'), '-zfoo'], stderr=PIPE).stderr
    self.assertContained('wasm-ld: warning: unknown -z value: foo', err)

  def test_linker_flags_pass_through_u(self):
    # Create a static library that contains a reference to an undefined symbol: bar
    create_file('test.c', '''
    extern int bar();
    int foo() {
      return bar();
    }
    ''')
    self.run_process([EMCC, '-c', 'test.c'])
    self.run_process([EMAR, 'crs', 'libtest.a', 'test.o'])
    # This test uses --no-gc-sections otherwise the foo symbol never actually gets included
    # in the link.
    cmd = [EMCC, test_file('hello_world.c'), '-Wl,--no-gc-sections']

    # Verify that the library is normally ignored and that the link succeeds
    self.run_process(cmd + ['libtest.a'])
    # Adding `-u baz` makes no difference
    self.run_process(cmd + ['-ubaz', 'libtest.a'])

    # But adding `-ufoo` should fail because it loads foo, which depends on bar
    self.assert_fail(cmd + ['-ufoo', 'libtest.a'], 'wasm-ld: error: libtest.a(test.o): undefined symbol: bar')

  def test_linker_flags_unused(self):
    err = self.run_process([EMCC, test_file('hello_world.c'), '-c', '-lbar'], stderr=PIPE).stderr
    self.assertContained("warning: -lbar: 'linker' input unused [-Wunused-command-line-argument]", err)

    # Check that we don't see these "input unused" errors for linker flags when
    # compiling and linking in single step (i.e. ensure that we don't pass them to clang when
    # compiling internally).
    err = self.run_process([EMCC, test_file('hello_world.c'), '-Wl,-static', '-Xlinker', '-static'], stderr=PIPE).stderr
    self.assertNotContained("input unused", err)

  def test_linker_flags_missing(self):
    self.assert_fail([EMCC, test_file('hello_world.c'), '-Xlinker'], "emcc: error: option '-Xlinker' requires an argument")

  def test_linker_input_unused(self):
    self.run_process([EMCC, '-c', test_file('hello_world.c')])
    err = self.run_process([EMCC, 'hello_world.o', '-c', '-o', 'out.o'], stderr=PIPE).stderr
    self.assertContained("clang: warning: hello_world.o: 'linker' input unused [-Wunused-command-line-argument]", err)
    # In this case the compiler does not produce any output file.
    self.assertNotExists('out.o')

  @all_engines
  def test_non_wasm_without_wasm_in_vm(self):
    create_file('pre.js', 'var WebAssembly = null;\n')
    # Test that our non-wasm output does not depend on wasm support in the vm.
    self.do_runf_out_file('hello_world.c', cflags=['-sWASM=0', '-sENVIRONMENT=node,shell', '--extern-pre-js=pre.js'])

  def test_empty_output_extension(self):
    # Default to JS output when no extension is present
    self.run_process([EMCC, test_file('hello_world.c'), '-Werror', '-o', 'hello'])
    self.assertContained('Hello, world!', self.run_js('hello'))

  def test_backwards_deps_in_archive(self):
    # Test that JS dependencies on native code work for code linked via
    # static archives using -l<name>
    self.run_process([EMCC, '-c', test_file('sockets/test_gethostbyname.c'), '-o', 'a.o'])
    self.run_process([LLVM_AR, 'cr', 'liba.a', 'a.o'])
    create_file('empty.c', 'static int foo = 0;')
    self.do_runf('empty.c', 'done\n', cflags=['-la', '-L.'])

  def test_warning_flags(self):
    self.run_process([EMCC, '-c', '-o', 'hello.o', test_file('hello_world.c')])
    # -g4 will generate a deprecated warning
    cmd = [EMCC, 'hello.o', '-o', 'a.js', '-g4']

    # warning that is enabled by default
    stderr = self.run_process(cmd, stderr=PIPE).stderr
    self.assertContained('emcc: warning: please replace -g4 with -gsource-map [-Wdeprecated]', stderr)

    # -w to suppress warnings
    stderr = self.run_process(cmd + ['-w'], stderr=PIPE).stderr
    self.assertNotContained('warning', stderr)

    # -Wno-emcc to suppress just this one warning
    stderr = self.run_process(cmd + ['-Wno-deprecated'], stderr=PIPE).stderr
    self.assertNotContained('warning', stderr)

    # with -Werror should fail
    expected = 'error: please replace -g4 with -gsource-map [-Wdeprecated] [-Werror]'
    self.assert_fail(cmd + ['-Werror'], expected)

    # with -Werror + -Wno-error=<type> should only warn
    stderr = self.run_process(cmd + ['-Werror', '-Wno-error=deprecated'], stderr=PIPE).stderr
    self.assertContained('emcc: warning: please replace -g4 with -gsource-map [-Wdeprecated]', stderr)

    # check that `-Werror=foo` also enables foo
    expected = 'error: use of legacy setting: TOTAL_MEMORY (setting renamed to INITIAL_MEMORY) [-Wlegacy-settings] [-Werror]'
    self.assert_fail(cmd + ['-Werror=legacy-settings', '-sTOTAL_MEMORY'], expected)

    # check that `-Wno-pthreads-mem` disables pthread + ALLOW_GROWTH_MEMORY warning
    stderr = self.run_process(cmd + ['-Wno-pthreads-mem-growth', '-pthread', '-sALLOW_MEMORY_GROWTH'], stderr=PIPE).stderr
    self.assertNotContained('pthreads + ALLOW_MEMORY_GROWTH may run non-wasm code slowly, see https://github.com/WebAssembly/design/issues/1271', stderr)

  def test_emranlib(self):
    create_file('foo.c', 'int foo = 1;')
    create_file('bar.c', 'int bar = 2;')
    self.run_process([EMCC, '-c', 'foo.c', 'bar.c'])

    # Create a library with no archive map
    self.run_process([EMAR, 'crS', 'liba.a', 'foo.o', 'bar.o'])
    output = self.run_process([shared.LLVM_NM, '--print-armap', 'liba.a'], stdout=PIPE).stdout
    self.assertNotContained('Archive map', output)

    # Add an archive map
    self.run_process([EMRANLIB, 'liba.a'])
    output = self.run_process([shared.LLVM_NM, '--print-armap', 'liba.a'], stdout=PIPE).stdout
    self.assertContained('Archive map', output)

  def test_pthread_stub(self):
    # Verify that programs containing pthread code can still work even
    # without enabling threads.  This is possible because we link in
    # libpthread_stub.a
    self.do_other_test('test_pthread_stub.c')

  @requires_pthreads
  def test_main_pthread_join_detach(self):
    # Verify that we're unable to join the main thread
    self.do_other_test('test_pthread_self_join_detach.c')

  @requires_pthreads
  def test_proxy_pthread_join_detach(self):
    # Verify that we're unable to detach or join the proxied main thread
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    output = self.do_other_test('test_pthread_self_join_detach.c')
    self.assertNotContained('user callback triggered after runtime exited', output)

  @requires_pthreads
  def test_pthread_asyncify(self):
    # We had a infinite recursion bug when enabling PTHREADS_DEBUG + ASYNCIFY.
    # This was because PTHREADS_DEBUG calls back into WebAssembly for each call to `err()`.
    self.set_setting('PTHREADS_DEBUG')
    self.set_setting('ASYNCIFY')
    self.do_other_test('test_pthread_asyncify.c')

  @requires_pthreads
  def test_pthread_reuse(self):
    self.do_other_test('test_pthread_reuse.c', cflags=['-sPTHREAD_POOL_SIZE=1'])

  @parameterized({
    '': ([],),
    'offscreen_canvas': (['-sOFFSCREENCANVAS_SUPPORT', '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$GL'],),
  })
  @requires_pthreads
  def test_pthread_hello(self, args):
    self.do_other_test('test_pthread_hello.c', args)

  @crossplatform
  @requires_pthreads
  def test_pthread_mutex_deadlock(self):
    self.do_runf('other/test_pthread_mutex_deadlock.c', 'pthread mutex deadlock detected',
                 cflags=['-g'], assert_returncode=NON_ZERO)

  @requires_pthreads
  def test_pthread_unavailable(self):
    # Run a simple hello world program that uses pthreads
    self.cflags += ['-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME', '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$stringToNewUTF8,$UTF8ToString']
    self.do_runf_out_file('hello_world.c')

    # Now run the same program but with SharedArrayBuffer undefined, it should run
    # fine and then fail on the first call to pthread_create.
    #
    # We specifically test that we can call UTF8ToString, which in older emscripten
    # versions had an instanceof check against SharedArrayBuffer which would cause
    # a crash when SharedArrayBuffer was undefined.
    create_file('pre.js', '''
      SharedArrayBuffer = undefined;
      Module.onRuntimeInitialized = () => {
        var addr = stringToNewUTF8("hello world string, longer than 16 chars");
        assert(addr);
        var str = UTF8ToString(addr);
        console.log("got: " + str);
        assert(str == "hello world string, longer than 16 chars");
      };''')
    expected = ['got: hello world string, longer than 16 chars', 'pthread_create: environment does not support SharedArrayBuffer, pthreads are not available']
    self.do_runf('hello_world.c', expected, assert_all=True, assert_returncode=NON_ZERO, cflags=['--pre-js=pre.js'])

  @requires_node
  @requires_pthreads
  def test_pthread_mem_leak(self):
    self.set_setting('MODULARIZE')
    self.set_setting('EXIT_RUNTIME')
    self.set_setting('EXPORTED_RUNTIME_METHODS', ['wasmMemory'])
    self.node_args.append('--expose-gc')
    self.cflags += ['--extern-post-js', test_file('pthread/test_pthread_mem_leak_post.js')]
    self.do_runf('hello_world.c', 'SUCCESS: No leak detected', cflags=['-pthread'])

  def test_stdin_preprocess(self):
    create_file('temp.h', '#include <string>')
    outputStdin = self.run_process([EMCC, '-x', 'c++', '-dM', '-E', '-'], input="#include <string>", stdout=PIPE).stdout
    outputFile = self.run_process([EMCC, '-x', 'c++', '-dM', '-E', 'temp.h'], stdout=PIPE).stdout
    self.assertTextDataIdentical(outputStdin, outputFile)

  def test_stdin_compile_only(self):
    # Should fail without -x lang specifier
    src = read_file(test_file('hello_world.cpp'))
    expected = 'error: -E or -x required when input is from standard input'
    self.assert_fail([EMCC, '-c', '-'], expected, input=src)

    self.run_process([EMCC, '-c', '-o', 'out.o', '-x', 'c++', '-'], input=src)
    self.assertExists('out.o')

    # Same again but without an explicit output filename
    self.run_process([EMCC, '-c', '-x', 'c++', '-'], input=src)
    self.assertExists('-.o')

  def test_stdin_compile_and_link(self):
    self.run_process([EMCC, '-x', 'c++', '-'], input=read_file(test_file('hello_world.cpp')))
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

  def test_stdout_link(self):
    # linking to stdout `-` doesn't work, we have no way to pass such an output filename
    # through post-link tools such as binaryen.
    self.assert_fail([EMCC, '-o', '-', test_file('hello_world.c')], 'invalid output filename: `-`')
    self.assertNotExists('-')

    self.assert_fail([EMCC, '-o', '-foo', test_file('hello_world.c')], 'invalid output filename: `-foo`')
    self.assertNotExists('-foo')

  def test_immutable_after_link(self):
    # some builds are guaranteed to not require any binaryen work after wasm-ld
    def ok(args, filename='hello_world.cpp', expected='Hello, world!'):
      print('ok', args, filename)
      args += ['-sERROR_ON_WASM_CHANGES_AFTER_LINK']
      self.do_runf(filename, expected, cflags=args)

    # -O0 with BigInt support (now on by default)
    ok([])
    # Same with DWARF
    ok(['-g'])
    # Function pointer calls from JS work too
    ok([], filename='hello_world_main_loop.cpp')
    # -O1 is ok as we don't run wasm-opt there (but no higher, see below)
    ok(['-O1'])
    # Exception support shouldn't require changes after linking
    ok(['-fexceptions'])
    # Standalone mode should not do anything special to the wasm.
    ok(['-sSTANDALONE_WASM'])

    # other builds fail with a standard message + extra details
    def fail(args, details):
      print('fail', args, details)
      args += ['-sERROR_ON_WASM_CHANGES_AFTER_LINK']
      err = self.expect_fail([EMCC, test_file('hello_world.c')] + args)
      self.assertContained('changes to the wasm are required after link, but disallowed by ERROR_ON_WASM_CHANGES_AFTER_LINK', err)
      self.assertContained(details, err)

    # plain -O0
    legalization_message = 'to disable int64 legalization (which requires changes after link) use -sWASM_BIGINT'
    fail(['-sWASM_BIGINT=0'], legalization_message)
    fail(['-sMIN_SAFARI_VERSION=140100'], legalization_message)
    # optimized builds even without legalization
    optimization_message = '-O2+ optimizations always require changes, build with -O0 or -O1 instead'
    fail(['-O2'], optimization_message)
    fail(['-O3'], optimization_message)

  @crossplatform
  def test_output_to_nowhere(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-o', os.devnull, '-c'])

  # Test that passing -sMIN_X_VERSION=-1 on the command line will result in browser X being not supported at all.
  # I.e. -sMIN_X_VERSION=-1 is equal to -sMIN_X_VERSION=Infinity
  def test_drop_support_for_browser(self):
    # Test that -1 means "not supported"
    self.run_process([EMCC, test_file('browser/test_html5_core.c')])
    self.assertContained('document.webkitFullscreenEnabled', read_file('a.out.js'))
    self.run_process([EMCC, test_file('browser/test_html5_core.c'), '-sMIN_SAFARI_VERSION=-1'])
    self.assertNotContained('document.webkitFullscreenEnabled', read_file('a.out.js'))

  def test_errno_type(self):
    create_file('errno_type.c', '''
#include <errno.h>

// Use of these constants in C preprocessor comparisons should work.
#if EPERM > 0
#define DAV1D_ERR(e) (-(e))
#else
#define DAV1D_ERR(e) (e)
#endif
''')
    self.run_process([EMCC, 'errno_type.c'])

  @also_with_wasmfs
  def test_standalone_syscalls(self):
    self.run_process([EMXX, test_file('other/test_standalone_syscalls.cpp'), '-o', 'test.wasm'])
    expected = read_file(test_file('other/test_standalone_syscalls.out'))
    for engine in config.WASM_ENGINES:
      self.assertContained(expected, self.run_js('test.wasm', engine))

  @flaky('https://github.com/emscripten-core/emscripten/issues/25343')
  @crossplatform
  @also_with_wasm64
  @parameterized({
    '': ([],),
    'assertions': (['-sASSERTIONS'],),
    'closure': (['--closure=1', '-Werror=closure'],),
    'closure_assertions': (['--closure=1', '-Werror=closure', '-sASSERTIONS'],),
    'dylink': (['-sMAIN_MODULE=2'],),
  })
  def test_emdawnwebgpu_link_test(self, args):
    if config.FROZEN_CACHE and (self.is_wasm64() or '-sMAIN_MODULE=2' in args):
      # CI configuration doesn't run `embuilder` with wasm64 on ports
      self.skipTest("test doesn't work with frozen cache")
    self.emcc(test_file('test_emdawnwebgpu_link_test.cpp'), ['--use-port=emdawnwebgpu', '-sASYNCIFY'] + args)

  def test_signature_mismatch(self):
    create_file('a.c', 'void foo(); int main() { foo(); return 0; }')
    create_file('b.c', 'int foo() { return 1; }')
    stderr = self.run_process([EMCC, 'a.c', 'b.c'], stderr=PIPE).stderr
    self.assertContained('function signature mismatch: foo', stderr)
    self.expect_fail([EMCC, '-Wl,--fatal-warnings', 'a.c', 'b.c'])
    # STRICT mode implies fatal warnings
    self.expect_fail([EMCC, '-sSTRICT', 'a.c', 'b.c'])
    # Unless `--no-fatal-warnings` is explicitly passed
    stderr = self.run_process([EMCC, '-sSTRICT', '-Wl,--no-fatal-warnings', 'a.c', 'b.c'], stderr=PIPE).stderr
    self.assertContained('function signature mismatch: foo', stderr)

  # Verifies that warning messages that Closure outputs are recorded to console
  @parameterized({
    '': ([], None),
    'wno_closure': (['-Wno-closure'], None),
    'wclosure': (['-Wclosure'], 'WARNING - [JSC_REFERENCE_BEFORE_DECLARE] Variable referenced before declaration'),
    'error_closure': (['-Werror=closure'], 'FAIL'),
    'wno_deprecated': (['-Wno-deprecated'], None),
    'wno_deprecated_quiet': (['-sCLOSURE_WARNINGS=quiet', '-Wno-deprecated'], None),
    'wno_deprecated_warn': (['-sCLOSURE_WARNINGS=warn', '-Wno-deprecated'], 'WARNING - [JSC_REFERENCE_BEFORE_DECLARE] Variable referenced before declaration'),
    'wno_deprecated_error': (['-sCLOSURE_WARNINGS=error', '-Wno-deprecated'], 'FAIL'),
  })
  def test_closure_warnings(self, flags, outcome):
    cmd = [EMCC, test_file('test_closure_warning.c'), '-O3', '--closure=1'] + flags
    if outcome == 'FAIL':
      self.expect_fail(cmd)
    else:
      proc = self.run_process(cmd, stderr=PIPE)
      if outcome is None:
        self.assertNotContained('WARNING', proc.stderr)
      else:
        self.assertContained(outcome, proc.stderr)

  def test_bitcode_input(self):
    # Verify that bitcode files are accepted as input
    create_file('main.c', 'void foo(); int main() { return 0; }')
    self.run_process([EMCC, '-emit-llvm', '-c', '-o', 'main.bc', 'main.c'])
    self.assertTrue(is_bitcode('main.bc'))
    self.run_process([EMCC, '-c', '-o', 'main.o', 'main.bc'])
    self.assertTrue(building.is_wasm('main.o'))

  @with_env_modify({'EMCC_LOGGING': '0'})  # this test assumes no emcc output
  def test_nostdlib(self):
    err = 'undefined symbol'
    self.assertContained(err, self.expect_fail([EMCC, test_file('unistd/close.c'), '-nostdlib']))
    self.assertContained(err, self.expect_fail([EMCC, test_file('unistd/close.c'), '-nodefaultlibs']))

    # Build again but with explicit system libraries
    libs = ['-lc', '-lclang_rt.builtins']
    self.run_process([EMCC, test_file('unistd/close.c'), '-nostdlib'] + libs)
    self.run_process([EMCC, test_file('unistd/close.c'), '-nodefaultlibs'] + libs)
    self.run_process([EMCC, test_file('unistd/close.c'), '-nolibc', '-lc'])
    self.run_process([EMCC, test_file('unistd/close.c'), '-nostartfiles'])

  def test_argument_match(self):
    # Verify that emcc arguments match precisely.  We had a bug where only the prefix
    # was matched
    self.run_process([EMCC, test_file('hello_world.c'), '--minify=0'])
    err = self.expect_fail([EMCC, test_file('hello_world.c'), '--minifyXX'])
    # The clang error message changed from 'unsupported' to 'unknown' so
    # for now handle both options.
    self.assertContained("clang: error: (unsupported option|unknown argument:) '--minifyXX'", err, regex=True)

  def test_argument_missing(self):
    self.assert_fail([EMCC, test_file('hello_world.c'), '--minify'], "error: option '--minify' requires an argument")

  def test_argument_missing_file(self):
    self.assert_fail([EMCC, test_file('hello_world.c'), '--pre-js', 'foo.js'], "emcc: error: '--pre-js': file not found: 'foo.js'")

  def test_default_to_cxx(self):
    create_file('foo.h', '#include <string.h>')
    create_file('cxxfoo.h', '#include <string>')

    # Compiling a C++ header using `em++` works.
    self.run_process([EMXX, '-c', 'cxxfoo.h'])

    # Compiling the same header using `emcc` fails, just like `clang`
    self.assert_fail([EMCC, '-c', 'cxxfoo.h', '-sSTRICT'], "'string' file not found")

    # But it works if we pass and explicit language mode.
    self.run_process([EMCC, '-x', 'c++-header', '-c', 'cxxfoo.h'])
    self.run_process([EMCC, '-x', 'c++', '-c', 'cxxfoo.h'])

  def test_assembly(self):
    self.run_process([EMCC, '-c', test_file('other/test_asm.s'), '-o', 'foo.o'])
    self.do_other_test('test_asm.c', libraries=['foo.o'])

  def test_assembly_preprocessed(self):
    self.run_process([EMCC, '-c', test_file('other/test_asm_cpp.S'), '-o', 'foo.o'])
    self.do_other_test('test_asm.c', libraries=['foo.o'])

  @also_with_wasm64
  @parameterized({
    '': (['-DUSE_KEEPALIVE'],),
    'minimal': (['-DUSE_KEEPALIVE', '-sMINIMAL_RUNTIME'],),
    'command_line': (['-sEXPORTED_FUNCTIONS=_g_foo,_main'],),
    'himem': (['-sEXPORTED_FUNCTIONS=_g_foo,_main', '-sGLOBAL_BASE=2gb', '-sINITIAL_MEMORY=3gb'],),
  })
  def test_export_global_address(self, args):
    self.do_other_test('test_export_global_address.c', cflags=args)

  def test_linker_version(self):
    out = self.run_process([EMCC, '-Wl,--version'], stdout=PIPE).stdout
    self.assertContained('LLD ', out)

  # Tests that if a JS library function is missing, the linker will print out which function
  # depended on the missing function.
  def test_chained_js_error_diagnostics(self):
    expected = 'emscripten_js_symbols.so: undefined symbol: nonexistent_function. Required by foo'
    self.assert_fail([EMCC, test_file('test_chained_js_error_diagnostics.c'), '--js-library', test_file('test_chained_js_error_diagnostics.js')], expected)

    # Test without chaining.  In this case we don't include the JS library at
    # all resulting in `foo` being undefined in the native code.
    err = self.assert_fail([EMCC, test_file('test_chained_js_error_diagnostics.c')], 'undefined symbol: foo')
    self.assertNotContained('referenced by top-level compiled C/C++ code', err)

  def test_xclang_flag(self):
    create_file('foo.h', ' ')
    self.run_process([EMCC, '-c', '-o', 'out.o', '-Xclang', '-include', '-Xclang', 'foo.h', test_file('hello_world.c')])

  def test_emcc_size_parsing(self):
    create_file('foo.h', ' ')
    self.assert_fail([EMCC, '-sTOTAL_MEMORY=X', 'foo.h'], 'error: invalid byte size `X`.  Valid suffixes are: kb, mb, gb, tb')
    self.assert_fail([EMCC, '-sTOTAL_MEMORY=11PB', 'foo.h'], 'error: invalid byte size `11PB`.  Valid suffixes are: kb, mb, gb, tb')

  def test_native_call_before_init(self):
    self.set_setting('ASSERTIONS')
    self.set_setting('EXPORTED_FUNCTIONS', ['_foo'])
    self.add_pre_run('out("calling foo"); Module["_foo"]();')
    create_file('foo.c', '#include <stdio.h>\nint foo() { puts("foo called"); return 3; }')
    self.build('foo.c')
    out = self.run_js('foo.js', assert_returncode=NON_ZERO)
    self.assertContained('native function `foo` called before runtime initialization', out)

  def test_native_call_after_exit(self):
    self.set_setting('ASSERTIONS')
    self.set_setting('EXIT_RUNTIME')
    self.add_on_exit('out("calling main again"); Module["_main"]();')
    create_file('foo.c', '#include <stdio.h>\nint main() { puts("foo called"); return 0; }')
    self.build('foo.c')
    out = self.run_js('foo.js', assert_returncode=NON_ZERO)
    self.assertContained('native function `main` called after runtime exit', out)

  def test_native_call_nargs(self):
    self.set_setting('ASSERTIONS')
    self.set_setting('EXPORTED_FUNCTIONS', ['_main', '_foo'])
    create_file('foo.c', r'''
      #include <emscripten.h>
      void foo(int arg) {}
      int main() {
        EM_ASM(_foo(99, 100));
      }
    ''')
    self.build('foo.c')
    out = self.run_js('foo.js', assert_returncode=NON_ZERO)
    self.assertContained('native function `foo` called with 2 args but expects 1', out)

  def test_metadce_wasm2js_i64(self):
    # handling i64 unsigned remainder brings in some i64 support code. metadce
    # must not remove it.
    create_file('src.c', r'''
int main(int argc, char **argv) {
  // Intentionally do not print anything, to not bring in more code than we
  // need to test - this only tests that we do not crash, which we would if
  // metadce broke us.
  unsigned long long x = argc;
  // do some i64 math, but return 0
  return (x % (x - 20)) == 42;
}''')
    self.do_runf('src.c', cflags=['-O3', '-sWASM=0'])

  @crossplatform
  def test_deterministic(self):
    # test some things that may not be nondeterministic
    create_file('src.c', r'''
#include <emscripten.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main () {
  struct timespec now;
  clock_gettime(CLOCK_REALTIME, &now);
  printf("C now: %lld %ld\n", now.tv_sec, now.tv_nsec);
  printf("js now: %f\n", emscripten_get_now());
  printf("C randoms: %d %d %d\n", rand(), rand(), rand());
  printf("JS random: %d\n", EM_ASM_INT({ return Math.random() }));
}
''')
    self.run_process([EMCC, 'src.c', '--pre-js', path_from_root('src/deterministic.js')] + self.get_cflags())
    one = self.run_js('a.out.js')
    # ensure even if the time resolution is 1 second, that if we see the real
    # time we'll see a difference
    time.sleep(2)
    two = self.run_js('a.out.js')
    self.assertIdentical(one, two)

  def test_err(self):
    self.do_other_test('test_err.c')

  def test_euidaccess(self):
    self.do_other_test('test_euidaccess.c')

  def test_fake_dylibs(self):
    create_file('other.c', 'int foo = 10;')
    self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-fPIC', 'other.c', '-o', 'libother.so'])
    self.assertIsObjectFile('libother.so')

    # Test that `-sFAKE_DYLIBS` flag causes object file generation and will generate a warning about
    # dylink dependencies being ignored.
    err = self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-fPIC', test_file('hello_world.c'), '-o', 'out.foo', 'libother.so'], stderr=PIPE).stderr
    self.assertContained('emcc: warning: ignoring dynamic library libother.so when generating an object file, this will need to be included explicitly in the final link', err)
    self.assertIsObjectFile('out.foo')

    # Test that adding `-sFAKE_DYLIBS=0` build a real side module
    err = self.run_process([EMCC, '-shared', '-fPIC', '-sFAKE_DYLIBS=0', test_file('hello_world.c'), '-o', 'out.foo', 'libother.so'], stderr=PIPE).stderr
    self.assertNotContained('emcc: warning: ignoring dynamic library libother.so when generating an object file, this will need to be included explicitly in the final link', err)
    self.assertIsWasmDylib('out.foo')

    # Test that using an executable output name overrides the `-shared` flag, but produces a warning.
    err = self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-fPIC', test_file('hello_world.c'), '-o', 'out.js'],
                           stderr=PIPE).stderr
    self.assertContained('warning: -shared/-r used with executable output suffix', err)
    self.run_js('out.js')

  def test_shared_soname(self):
    self.run_process([EMCC, '-shared', '-sFAKE_DYLIBS', '-Wl,-soname', '-Wl,libfoo.so.13', test_file('hello_world.c'), '-lc', '-o', 'libfoo.so'])
    self.run_process([EMCC, '-sSTRICT', 'libfoo.so'])
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

  def test_shared_flag(self):
    # Test that `-shared` flag causes wasm dylib generation
    self.run_process([EMCC, '-shared', '-fPIC', test_file('hello_world.c'), '-o', 'out.foo'])
    self.assertIsWasmDylib('out.foo')

    # Test that `-shared` causes wasm dylib generation warning even if given executable output name.
    err = self.run_process([EMCC, '-shared', '-fPIC', test_file('hello_world.c'), '-o', 'out.wasm'], stderr=PIPE).stderr
    self.assertNotContained('warning: -shared/-r used with executable output suffix', err)
    self.assertIsWasmDylib('out.wasm')

  @no_windows('windows does not support shbang syntax')
  @with_env_modify({'EMMAKEN_JUST_CONFIGURE': '1'})
  def test_autoconf_mode(self):
    self.run_process([EMCC, test_file('hello_world.c')])
    # Test that output name is just `a.out` and that it is directly executable
    output = self.run_process([os.path.abspath('a.out')], stdout=PIPE).stdout
    self.assertContained('Hello, world!', output)

  def test_standalone_export_main(self):
    # Tests that explicitly exported `_main` does not fail, even though `_start` is the entry
    # point.
    # We should consider making this a warning since the `_main` export is redundant.
    self.run_process([EMCC, '-sEXPORTED_FUNCTIONS=_main', '-sSTANDALONE_WASM', test_file('core/test_hello_world.c')])

  @requires_wasm_eh
  def test_standalone_wasm_exceptions(self):
    self.set_setting('STANDALONE_WASM')
    self.wasm_engines = []
    self.cflags += ['-fwasm-exceptions']
    self.set_setting('WASM_LEGACY_EXCEPTIONS', 0)
    self.do_runf_out_file('core/test_exceptions.cpp', out_suffix='_caught')
    self.set_setting('WASM_LEGACY_EXCEPTIONS')
    self.do_runf_out_file('core/test_exceptions.cpp', out_suffix='_caught')

  def test_missing_malloc_export(self):
    # we used to include malloc by default. show a clear error in builds with
    # ASSERTIONS to help with any confusion when the user calls malloc/free
    # directly
    create_file('unincluded_malloc.c', r'''
      #include <emscripten.h>
      int main() {
        EM_ASM({
          try {
            _malloc(1);
          } catch(e) {
            out('exception:', e);
          }
          try {
            _free();
          } catch(e) {
            out('exception:', e);
          }
        });
      }
    ''')
    self.do_runf('unincluded_malloc.c', (
      'malloc() called but not included in the build - add `_malloc` to EXPORTED_FUNCTIONS',
      'free() called but not included in the build - add `_free` to EXPORTED_FUNCTIONS'), assert_all=True)

  @also_with_asan
  def test_getrusage(self):
    self.do_other_test('test_getrusage.c')

  @with_env_modify({'EMMAKEN_COMPILER': shared.CLANG_CC})
  def test_emmaken_compiler(self):
    self.assert_fail([EMCC, '-c', test_file('core/test_hello_world.c')], 'emcc: error: `EMMAKEN_COMPILER` is no longer supported')

  @with_env_modify({'EMMAKEN_CFLAGS': '-O2'})
  def test_emmaken_cflags(self):
    self.assert_fail([EMCC, '-c', test_file('core/test_hello_world.c')], 'emcc: error: `EMMAKEN_CFLAGS` is no longer supported')

  @no_windows('relies on a shell script')
  def test_compiler_wrapper(self):
    create_file('wrapper.sh', '''\
#!/bin/sh
echo "wrapping compiler call: $@"
exec "$@"
    ''')
    make_executable('wrapper.sh')
    with env_modify({'EM_COMPILER_WRAPPER': './wrapper.sh'}):
      stdout = self.run_process([EMCC, '-c', test_file('core/test_hello_world.c')], stdout=PIPE).stdout
    self.assertContained('wrapping compiler call: ', stdout)
    self.assertExists('test_hello_world.o')

    stdout = self.run_process([EMCC, '-c', test_file('core/test_hello_world.c'), '--compiler-wrapper=./wrapper.sh'], stdout=PIPE).stdout
    self.assertContained('wrapping compiler call: ', stdout)
    self.assertExists('test_hello_world.o')

  @requires_tool('ccache')
  @with_env_modify({'EM_COMPILER_WRAPPER': 'ccache'})
  def test_compiler_wrapper_ccache(self):
    self.do_runf_out_file('hello_world.c')

  def test_llvm_option_dash_o(self):
    # emcc used to interpret -mllvm's option value as the output file if it
    # began with -o
    stderr = self.run_process(
      [EMCC, '-v', '-o', 'llvm_option_dash_o_output', '-mllvm',
       '-opt-bisect-limit=1', test_file('hello_world.c')],
      stderr=PIPE).stderr

    self.assertExists('llvm_option_dash_o_output')
    self.assertNotExists('pt-bisect-limit=1')
    self.assertContained(' -mllvm -opt-bisect-limit=1 ', stderr)

    # Regression test for #12236: the '-mllvm' argument was indexed instead of
    # its value, and the index was out of bounds if the argument was sixth or
    # further on the command line
    self.run_process(
      [EMCC, '-DFOO', '-DBAR', '-DFOOBAR', '-DBARFOO',
       '-o', 'llvm_option_dash_o_output', '-mllvm', '-opt-bisect-limit=1',
       test_file('hello_world.c')])

  def test_SYSCALL_DEBUG(self):
    self.set_setting('SYSCALL_DEBUG')
    self.do_runf('hello_world.c', 'syscall! fd_write: [1,')

    # Check that we can disable debug output by setting runtimeDebug to false
    create_file('post.js', 'runtimeDebug = false;')
    output = self.do_runf('hello_world.c', cflags=['--post-js=post.js'])
    self.assertNotContained('fd_write', output)

  def test_LIBRARY_DEBUG(self):
    self.set_setting('LIBRARY_DEBUG')
    self.do_runf('hello_world.c', '[library call:_fd_write: 0x00000001 (1)')

  def test_SUPPORT_LONGJMP_executable(self):
    expected = 'error: longjmp support was disabled (SUPPORT_LONGJMP=0), but it is required by the code (either set SUPPORT_LONGJMP=1, or remove uses of it in the project)'
    self.assert_fail([EMCC, test_file('core/test_longjmp.c'), '-sSUPPORT_LONGJMP=0'], expected)

  def test_SUPPORT_LONGJMP_object(self):
    # compile the object *with* support, but link without
    self.run_process([EMCC, test_file('core/test_longjmp.c'), '-c', '-sSUPPORT_LONGJMP', '-o', 'a.o'])
    expected = 'error: longjmp support was disabled (SUPPORT_LONGJMP=0), but it is required by the code (either set SUPPORT_LONGJMP=1, or remove uses of it in the project)'
    self.assert_fail([EMCC, 'a.o', '-sSUPPORT_LONGJMP=0'], expected)

  def test_SUPPORT_LONGJMP_wasm(self):
    # Tests if -sSUPPORT_LONGJMP=wasm alone is enough to use Wasm SjLj, i.e., it
    # automatically sets DISABLE_EXCEPTION_THROWING to 1, which is 0 by default,
    # because Emscripten EH and Wasm SjLj cannot be used at the same time.
    self.run_process([EMCC, test_file('core/test_longjmp.c'), '-c', '-sSUPPORT_LONGJMP=wasm', '-o', 'a.o'])
    self.assertContained('try', self.get_wasm_text('a.o'))

    # If -sWASM_LEGACY_EXCEPTIONS=0 is provided, it uses the standard Wasm EH.
    self.run_process([EMCC, test_file('core/test_longjmp.c'), '-c', '-sSUPPORT_LONGJMP=wasm', '-sWASM_LEGACY_EXCEPTIONS=0', '-o', 'b.o'])
    self.assertContained('try_table', self.get_wasm_text('b.o'))

  @parameterized({
    '': [[]],
    'trusted': [['-sTRUSTED_TYPES']],
  })
  def test_pthread_export_es6(self, args):
    self.run_process([EMCC, test_file('hello_world.c'), '-o', 'out.mjs', '-pthread', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'] + args)
    create_file('runner.mjs', '''
      import Hello from "./out.mjs";
      Hello();
    ''')
    output = self.run_js('runner.mjs')
    self.assertContained('Hello, world!', output)

  def test_wasm2js_no_dylink(self):
    for arg in ('-sMAIN_MODULE', '-sSIDE_MODULE'):
      print(arg)
      err = self.expect_fail([EMCC, test_file('hello_world.c'), '-sWASM=0', arg])
      self.assertContained(r'emcc: error: WASM2JS is not compatible with .*_MODULE \(wasm2js does not support dynamic linking\)', err, regex=True)

  def test_wasm2js_standalone(self):
    self.do_runf_out_file('hello_world.c', cflags=['-sSTANDALONE_WASM', '-sWASM=0'])

  def test_oformat(self):
    self.run_process([EMCC, test_file('hello_world.c'), '--oformat=wasm', '-o', 'out.foo'])
    self.assertTrue(building.is_wasm('out.foo'))
    self.clear()

    self.run_process([EMCC, test_file('hello_world.c'), '--oformat=html', '-o', 'out.foo'])
    self.assertFalse(building.is_wasm('out.foo'))
    self.assertContained('<html ', read_file('out.foo'))
    self.clear()

    self.run_process([EMCC, test_file('hello_world.c'), '--oformat=js', '-o', 'out.foo'])
    self.assertFalse(building.is_wasm('out.foo'))
    self.assertContained('new ExitStatus', read_file('out.foo'))
    self.clear()

    expected = "error: invalid output format: `foo` (must be one of ['object', 'wasm', 'js', 'mjs', 'html', 'bare']"
    self.assert_fail([EMCC, test_file('hello_world.c'), '--oformat=foo'], expected)

  # Tests that the old format of {{{ makeDynCall('sig') }}}(func, param1) works
  def test_old_makeDynCall_syntax(self):
    err = self.run_process([EMCC, test_file('test_old_dyncall_format.c'), '--js-library', test_file('library_test_old_dyncall_format.js')], stderr=PIPE).stderr
    self.assertContained('syntax for makeDynCall has changed', err)

  # Test that {{{ makeDynCall('sig', 'this.foo') }}} macro works, i.e. when 'this.' is referenced inside the macro block.
  # For this test verify the different build options that generate anonymous enclosing function scopes. (DYNCALLS and MEMORY64)
  @also_with_wasm64
  @parameterized({
    '': ([],),
    'dyncalls': (['-sDYNCALLS'],),
  })
  def test_this_in_dyncall(self, args):
    self.do_runf_out_file('test_this_in_dyncall.c', cflags=['--js-library', test_file('test_this_in_dyncall.js')] + args)

  # Tests that dynCalls are produced in Closure-safe way in DYNCALLS mode when no actual dynCalls are used
  @parameterized({
    '': [[]],
    'asyncify': [['-sASYNCIFY']],
    'asyncify_nobigint': [['-sASYNCIFY', '-sWASM_BIGINT=0']],
  })
  def test_closure_safe(self, args):
    self.run_process([EMCC, test_file('hello_world.c'), '--closure=1'] + args)

  def test_post_link(self):
    err = self.run_process([EMCC, test_file('hello_world.c'), '--oformat=bare', '-o', 'bare.wasm'], stderr=PIPE).stderr
    self.assertContained('--oformat=bare/--post-link are experimental and subject to change', err)
    # Explicitly test with `-s RUNTIME_DEBUG`, including the space, to verify parsing of `-s` flags
    # See https://github.com/emscripten-core/emscripten/issues/24250
    err = self.run_process([EMCC, '--post-link', 'bare.wasm', '-s', 'RUNTIME_DEBUG'], stderr=PIPE).stderr
    self.assertContained('--oformat=bare/--post-link are experimental and subject to change', err)
    err = self.assertContained('Hello, world!', self.run_js('a.out.js'))

  def compile_with_wasi_sdk(self, filename, output):
    sysroot = os.environ.get('EMTEST_WASI_SYSROOT')
    if not sysroot:
      self.skipTest('EMTEST_WASI_SYSROOT not found in environment')
    sysroot = os.path.expanduser(sysroot)
    self.run_process([CLANG_CC, '--sysroot=' + sysroot, '--target=wasm32-wasi', filename, '-o', output])

  def test_run_wasi_sdk_output(self):
    self.compile_with_wasi_sdk(test_file('hello_world.c'), 'hello.wasm')
    self.run_process([EMCC, '--post-link', '-sPURE_WASI', 'hello.wasm'])
    self.assertContained('Hello, world!', self.run_js('a.out.js'))

  # Test that Closure prints out clear readable error messages when there are errors.
  def test_closure_errors(self):
    err = self.expect_fail([EMCC, test_file('closure_error.c'), '-O2', '--closure=1'])
    lines = err.split('\n')

    def find_substr_index(s):
      for i, line in enumerate(lines):
        if s in line:
          return i
      return -1

    idx1 = find_substr_index('[JSC_UNDEFINED_VARIABLE] variable thisVarDoesNotExist is undeclared')
    idx2 = find_substr_index('[JSC_UNDEFINED_VARIABLE] variable thisVarDoesNotExistEither is undeclared')
    self.assertNotEqual(idx1, -1)
    self.assertNotEqual(idx2, -1)
    # The errors must be present on distinct lines.
    self.assertNotEqual(idx1, idx2)

  # Make sure that --cpuprofiler compiles with --closure 1
  def test_cpuprofiler_closure(self):
    # TODO: Enable '-Werror=closure' in the following, but that has currently regressed.
    self.run_process([EMCC, test_file('hello_world.c'), '-O2', '--closure=1', '--cpuprofiler'])

  # Make sure that --memoryprofiler compiles with --closure 1
  def test_memoryprofiler_closure(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-O2', '--closure=1', '--memoryprofiler'] + self.get_cflags())

  # Make sure that --threadprofiler compiles with --closure 1
  def test_threadprofiler_closure(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-O2', '-pthread', '--closure=1', '--threadprofiler', '-sASSERTIONS'] + self.get_cflags())

  @requires_pthreads
  def test_threadprofiler(self):
    self.run_process([EMCC, test_file('test_threadprofiler.cpp'), '-pthread', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME', '--threadprofiler', '-sASSERTIONS'])
    output = self.run_js('a.out.js')
    self.assertRegex(output, r'Thread "Browser main thread" \(0x.*\) now: running.')
    self.assertRegex(output, r'Thread "Application main thread" \(0x.*\) now: waiting for a futex.')
    self.assertRegex(output, r'Thread "test worker" \(0x.*\) now: sleeping.')

  def test_syslog(self):
    self.do_other_test('test_syslog.c')

  def test_syscall_stubs(self):
    self.do_other_test('test_syscall_stubs.c')

  @also_with_wasm64
  @parameterized({
    '': (False, False, False),
    'custom': (True, False, False),
    'jspi': (False, True, False),
    'O3': (False, False, True),
  })
  def test_split_module(self, customLoader, jspi, opt):
    self.set_setting('SPLIT_MODULE')
    self.cflags += ['-Wno-experimental']
    if opt:
      # Test that it works in the presence of export minification
      self.cflags += ['-O3']
    else:
      self.cflags += ['-g']
    self.cflags += ['--post-js', test_file('other/test_split_module.post.js')]
    if customLoader:
      self.cflags += ['--pre-js', test_file('other/test_load_split_module.pre.js')]
    if jspi:
      self.require_jspi()
      self.cflags += ['-g', '-sJSPI_EXPORTS=say_hello']
    self.cflags += ['-sEXPORTED_FUNCTIONS=_malloc,_free']
    self.do_other_test('test_split_module.c')
    self.assertExists('test_split_module.wasm')
    self.assertExists('test_split_module.wasm.orig')
    self.assertExists('profile.data')

    wasm_split = os.path.join(building.get_binaryen_bin(), 'wasm-split')
    wasm_split_run = [wasm_split, '-g',
                      '--enable-mutable-globals', '--enable-bulk-memory', '--enable-nontrapping-float-to-int',
                      '--export-prefix=%', 'test_split_module.wasm.orig', '-o1', 'primary.wasm', '-o2', 'secondary.wasm', '--profile=profile.data']
    if self.is_wasm64():
      wasm_split_run += ['--enable-memory64']
    self.run_process(wasm_split_run)

    os.remove('test_split_module.wasm')
    os.rename('primary.wasm', 'test_split_module.wasm')
    os.rename('secondary.wasm', 'test_split_module.deferred.wasm')
    result = self.run_js('test_split_module.js')
    self.assertNotIn('profile', result)
    self.assertContainedIf('Custom handler for loading split module.', result, condition=customLoader)
    self.assertIn('Hello! answer: 42', result)
    if jspi:
      self.assertIn('result is promise', result)

  def test_split_main_module(self):
    # Set and reasonably large initial table size to avoid test fragility.
    # The actual number of slots needed is closer to 18 but we don't want
    # this test to fail every time that changes.
    initialTableSize = 100

    side_src = test_file('other/lib_hello.c')
    post_js = test_file('other/test_split_module.post.js')

    self.run_process([EMCC, side_src, '-sSIDE_MODULE', '-g', '-o', 'libhello.wasm'])

    self.cflags += ['-g']
    self.cflags += ['-sMAIN_MODULE=2']
    self.cflags += ['-sEXPORTED_FUNCTIONS=_printf,_malloc,_free']
    self.cflags += ['-sSPLIT_MODULE', '-Wno-experimental']
    self.cflags += ['--embed-file', 'libhello.wasm']
    self.cflags += ['--post-js', post_js]
    self.cflags += [f'-sINITIAL_TABLE={initialTableSize}']

    self.do_other_test('test_split_main_module.c')

    self.assertExists('test_split_main_module.wasm')
    self.assertExists('test_split_main_module.wasm.orig')
    self.assertExists('profile.data')

    wasm_split = os.path.join(building.get_binaryen_bin(), 'wasm-split')
    self.run_process([wasm_split, '-g',
                      'test_split_main_module.wasm.orig',
                      '--export-prefix=%',
                      f'--initial-table={initialTableSize}',
                      '--profile=profile.data',
                      '-o1', 'primary.wasm',
                      '-o2', 'secondary.wasm',
                      '--enable-mutable-globals'])

    os.remove('test_split_main_module.wasm')
    os.rename('primary.wasm', 'test_split_main_module.wasm')
    os.rename('secondary.wasm', 'test_split_main_module.deferred.wasm')
    result = self.run_js('test_split_main_module.js')
    self.assertNotIn('profile', result)
    self.assertIn('Hello from main!', result)
    self.assertIn('Hello from lib!', result)

  @also_with_wasm64
  @requires_jspi
  def test_split_module_embind_jspi(self):
    self.set_setting('SPLIT_MODULE')
    self.cflags += ['-Wno-experimental']
    self.cflags += ['--post-js', test_file('other/test_split_module_embind_jspi.post.js')]
    self.cflags += ['--pre-js', test_file('other/test_split_module_embind_jspi.pre.js')]
    self.cflags += ['-sEXPORTED_FUNCTIONS=_malloc,_free']
    self.cflags += ['-lembind']
    expected_pre_split_output = 'primary_function: 42\n'
    expected_post_split_output = ('deferred_function: [object Promise]\n'
                                  'deferred_function await: 82\n')
    expected_output = (expected_pre_split_output +
                       'wrote profile data\n' +
                       expected_post_split_output)
    result = self.do_runf('other/test_split_module_embind_jspi.cpp', expected_output=expected_output)
    self.assertExists('test_split_module_embind_jspi.wasm')
    self.assertExists('test_split_module_embind_jspi.wasm.orig')
    self.assertExists('profile.data')

    wasm_split = os.path.join(building.get_binaryen_bin(), 'wasm-split')
    wasm_split_run = [wasm_split, '-g',
                      '--enable-mutable-globals', '--enable-bulk-memory', '--enable-nontrapping-float-to-int',
                      '--export-prefix=%', 'test_split_module_embind_jspi.wasm.orig', '-o1', 'test_split_module_embind_jspi.wasm', '-o2', 'test_split_module_embind_jspi.deferred.wasm', '--profile=profile.data']
    self.run_process(wasm_split_run)
    result = self.run_js('test_split_module_embind_jspi.js')
    self.assertNotIn('profile', result)
    self.assertIn((expected_pre_split_output +
                   'Custom handler for loading split module.\n' +
                   expected_post_split_output),
                  result)

  @crossplatform
  @flaky('https://github.com/emscripten-core/emscripten/issues/25206')
  def test_gen_struct_info(self):
    # This test will start failing whenever the struct info changes (e.g. offset or defines
    # change).  However it's easy to rebaseline with --rebaseline.
    self.run_process([PYTHON, path_from_root('tools/gen_struct_info.py'), '-o', 'out.json'])
    self.assertFilesMatch(path_from_root('src/struct_info_generated.json'), 'out.json')

    # Same again for wasm64
    self.run_process([PYTHON, path_from_root('tools/gen_struct_info.py'), '--wasm64', '-o', 'out.json'])
    self.assertFilesMatch(path_from_root('src/struct_info_generated_wasm64.json'), 'out.json')

  @crossplatform
  def test_gen_sig_info(self):
    # This tests is fragile and will need updating any time a JS library
    # function is added or its signature changed.  However it's easy to
    # rebaseline with --rebaseline.
    self.run_process([PYTHON, path_from_root('tools/maint/gen_sig_info.py'), '-o', 'out.js'])
    self.assertFilesMatch(path_from_root('src/lib/libsigs.js'), 'out.js')

  @crossplatform
  def test_gen_native_sig_info(self):
    self.run_process([PYTHON, path_from_root('tools/maint/gen_native_sig_info.py'), '-o', 'out.py'])
    self.assertFilesMatch(path_from_root('tools/native_sigs.py'), 'out.py')

  def test_gen_struct_info_env(self):
    # gen_struct_info.py builds C code in a very specific and low level way.  We don't want
    # EMCC_CFLAGS (or any of the other environment variables that might effect compilation or
    # linking) to effect the internal building and running of this code.
    # For example -O2 causes printf -> iprintf which will fail with undefined symbol iprintf.
    with env_modify({'EMCC_CFLAGS': '-O2 BAD_ARG', 'EMCC_FORCE_STDLIBS': '1', 'EMCC_ONLY_FORCED_STDLIBS': '1'}):
      self.run_process([PYTHON, path_from_root('tools/gen_struct_info.py'), '-o', 'out.json'])

  def test_dylink_limited_exports(self):
    # Building with MAIN_MODULE=2 should *not* automatically export all symbols.
    self.run_process([EMCC, test_file('hello_world.c'), '-sMAIN_MODULE=2', '-o', 'out.wasm'])

    # Building with MAIN_MODULE=1 should include and export all of the standard library
    self.run_process([EMCC, test_file('hello_world.c'), '-sMAIN_MODULE', '-o', 'out_linkable.wasm'])

    exports = self.parse_wasm('out.wasm')[1]
    exports_linkable = self.parse_wasm('out_linkable.wasm')[1]

    self.assertLess(len(exports), 20)
    self.assertGreater(len(exports_linkable), 1000)
    self.assertIn('sendmsg', exports_linkable)
    self.assertNotIn('sendmsg', exports)

  @requires_v8
  def test_shell_Oz(self):
    # regression test for -Oz working on non-web, non-node environments that
    # lack TextDecoder
    self.do_runf_out_file('hello_world.c', cflags=['-Oz'])

  def test_runtime_keepalive(self):
    # Depends on Module['onExit']
    self.set_setting('EXIT_RUNTIME')
    self.do_other_test('test_runtime_keepalive.cpp')

  @crossplatform
  def test_em_js_invalid(self):
    # Test that invalid EM_JS in side modules since is detected at build time.
    err = self.expect_fail([EMCC, '-sSIDE_MODULE', test_file('other/test_em_js_invalid.c')])
    # Check for the error message syntax produced by either Node or Bun
    self.assertContained(["SyntaxError: Unexpected token '*'", 'error: Unexpected *'], err)
    self.assertContained('emcc: error: EM_JS function validation failed', err)

  @crossplatform
  def test_em_js_side_module(self):
    self.build('other/test_em_js_side.c', output_suffix='.wasm', cflags=['-sSIDE_MODULE'], output_basename='side')
    self.do_other_test('test_em_js_main.c', cflags=['-sMAIN_MODULE=2', 'side.wasm'])

  def test_em_js_main_module(self):
    self.set_setting('MAIN_MODULE', 2)
    self.set_setting('EXPORTED_FUNCTIONS', '_main,_malloc')
    self.do_runf('core/test_em_js.cpp')

  def test_em_js_dylink_address(self):
    # This works under static linking but is known to fail with dynamic linking
    # See https://github.com/emscripten-core/emscripten/issues/18494
    self.do_runf('other/test_em_js_main_module_address.c')

    self.emcc('other/test_em_js_main_module_address.c', ['-sSIDE_MODULE', '-o', 'libside.so'])
    expected = 'Aborted(Assertion failed: Missing signature argument to addFunction: () => { err("hello"); })'
    self.do_run('', expected, cflags=['-sMAIN_MODULE=2', 'libside.so'], assert_returncode=NON_ZERO)

  def test_em_js_external_usage(self):
    # Verify that EM_JS functions can be called from other source files, even in the case
    # when they are not used within the defining file.
    create_file('em_js.c', r'''
      #include <emscripten/em_js.h>

      EM_JS(void, js_func, (), {
        out('js_func called');
      });

      // js_func is unused within this file
    ''')
    create_file('main.c', '''
      #include <stdio.h>

      void js_func();
      int main() {
        js_func();
      }
    ''')
    self.run_process([EMCC, 'em_js.c', '-c'])
    self.do_runf('main.c', 'js_func called\n', cflags=['em_js.o'])

  def test_em_js_top_level(self):
    # It turns out that EM_JS can be used to inject top level JS code.
    # This test verifies that it works, despite it not being an officially
    # supported feature.
    # See https://github.com/emscripten-core/emscripten/issues/23884
    create_file('main.c', r'''
      #include <emscripten/em_js.h>

      EM_JS_DEPS(deps, "$addOnPreRun");

      EM_JS(void, js_func, (), {
        out('js_func called');
      }
      console.log("Top level code");
      addOnPreRun(() => console.log("hello from pre-run"));
      );

      int main() {
        js_func();
      }
    ''')
    self.do_runf('main.c', 'Top level code\nhello from pre-run\njs_func called\n')

  # On Windows maximum command line length is 32767 characters. Create such a long build line by linking together
  # several .o files to test that emcc internally uses response files properly when calling wasm-ld.
  @is_slow_test
  def test_windows_long_link_response_file(self):
    decls = ''
    calls = ''
    files = []

    def create_o(name, i):
      nonlocal decls, calls, files
      f = name + '.c'
      create_file(f, 'int %s() { return %d; }' % (name, i))
      files += [f]
      decls += 'int %s();' % name
      calls += 'value += %s();' % name

    count = 300
    for i in range(count):
      name = 'a' + str(i)
      name *= 32
      create_o(name, i)

    create_file('main.c', '#include<stdio.h>\n%s int main() { int value = 0; %s printf("%%d\\n", value); }' % (decls, calls))

    total_len = sum(len(f) for f in files)
    print('Command line lower bound:', total_len)
    assert total_len > 32767

    self.run_process(building.get_command_with_possible_response_file([EMCC, 'main.c'] + files))
    self.assertContained(str(count * (count - 1) // 2), self.run_js('a.out.js'))

  @crossplatform
  def test_response_file(self):
    out_js = self.output_name('response_file')
    response_data = '-o "%s" "%s"' % (out_js, test_file('hello_world.cpp'))
    create_file('rsp_file', response_data.replace('\\', '\\\\'))
    self.run_process([EMCC, "@rsp_file"] + self.get_cflags())
    self.do_run(out_js, 'Hello, world!', no_build=True)

    self.assertContained('emcc: error: @foo.txt: No such file or directory', self.expect_fail([EMCC, '@foo.txt']))

  # Tests that the filename suffix of the response files can be used to detect which encoding the file is.
  @crossplatform
  def test_response_file_encoding(self):
    create_file('äö.c', 'int main(){}')

    open('a.rsp', 'w', encoding='utf-8').write('äö.c') # Write a response file with unicode contents ...
    self.run_process([EMCC, '@a.rsp']) # ... and test that in the absence of a file suffix, it is autodetected to utf-8.

    open('a.rsp.cp437', 'w', encoding='cp437').write('äö.c') # Write a response file with Windows CP-437 encoding ...
    self.run_process([EMCC, '@a.rsp.cp437']) # ... and test that with the explicit suffix present, it is properly decoded

    preferred_encoding = locale.getpreferredencoding(do_setlocale=False)
    print('Python locale preferredencoding: ' + preferred_encoding)
    open('a.rsp', 'w', encoding=preferred_encoding).write('äö.c') # Write a response file using Python preferred encoding
    self.run_process([EMCC, '@a.rsp']) # ... and test that it is properly autodetected.

  @crossplatform
  def test_response_file_recursive(self):
    create_file('rsp2.txt', response_file.create_response_file_contents([test_file('hello_world.c'), '-o', 'hello.js']))
    create_file('rsp1.txt', '@rsp2.txt\n')
    self.run_process([EMCC, '@rsp1.txt'])
    self.assertContained('Hello, world!', self.run_js('hello.js'))

  def test_output_name_collision(self):
    # Ensure that the secondary filenames never collide with the primary output filename
    # In this case we explicitly ask for JS to be created in a file with the `.wasm` suffix.
    # Even though this doesn't make much sense the `--oformat` flag is designed to override
    # any implicit type that we might infer from the output name.
    self.run_process([EMCC, '-o', 'hello.wasm', '--oformat=js', test_file('hello_world.c')])
    self.assertExists('hello.wasm')
    self.assertExists('hello_.wasm')
    self.assertFalse(building.is_wasm('hello.wasm'))
    self.assertTrue(building.is_wasm('hello_.wasm'))
    # Node cannot actually run the generated JS if it's in a file with the .wasm extension
    os.rename('hello.wasm', 'hello.js')
    self.assertContained('Hello, world!', self.run_js('hello.js'))

  def test_main_module_no_undefined(self):
    # Test that ERROR_ON_UNDEFINED_SYMBOLS works with MAIN_MODULE.
    self.do_runf_out_file('hello_world.c', cflags=['-sMAIN_MODULE', '-sERROR_ON_UNDEFINED_SYMBOLS'])

  def test_reverse_deps_allow_undefined(self):
    # Check that reverse deps are still included even when -sERROR_ON_UNDEFINED_SYMBOLS=0.
    create_file('test.c', '''
    #include <assert.h>
    #include <stdio.h>
    #include <netdb.h>

    int main() {
      // Reference in getaddrinfo which has reverse deps on malloc and htons
      // We expect these to be exported even when -sERROR_ON_UNDEFINED_SYMBOLS=0.
      printf("%p\\n", &getaddrinfo);
      return 0;
    }
    ''')
    self.do_runf('test.c', cflags=['-sERROR_ON_UNDEFINED_SYMBOLS=0'])

  def test_dylink_undefined(self):
    # positive case: no undefined symbols
    self.run_process([EMCC, '-sMAIN_MODULE', test_file('hello_world.c')])
    self.run_js('a.out.js')

    # negative case: foo is undefined in test_check_undefined.c
    self.assert_fail([EMCC, '-sMAIN_MODULE', test_file('other/test_check_undefined.c')], 'undefined symbol: foo')

  @also_with_wasm64
  @parameterized({
    'asyncify': (['-sASYNCIFY'],),
  })
  def test_missing_symbols_at_runtime(self, args):
    # We deliberately pick a symbol there that takes a pointer as an argument.
    # We had a regression where the pointer-handling wrapper function could
    # not be created because the "missing functions" stubs don't take any
    # arguments.
    create_file('test.c', '''
    #include <GL/gl.h>

    int main() {
      glGetTexLevelParameteriv(0, 0, 0, 0);
    }
    ''')

    expected = 'Aborted(missing function: glGetTexLevelParameteriv)'
    self.do_runf('test.c', expected,
                 cflags=['-sWARN_ON_UNDEFINED_SYMBOLS=0', '-sAUTO_JS_LIBRARIES=0'] + args,
                 assert_returncode=NON_ZERO)

  @with_env_modify({'EMMAKEN_NO_SDK': '1'})
  def test_EMMAKEN_NO_SDK(self):
    self.assert_fail([EMCC, test_file('hello_world.c')], 'emcc: error: EMMAKEN_NO_SDK is no longer supported')

  @parameterized({
    'default': ('', '2147483648'),
    '1GB': ('-sMAXIMUM_MEMORY=1GB', '1073741824'),
    # for 4GB we return 1 wasm page less than 4GB, as 4GB cannot fit in a 32bit
    # integer
    '4GB': ('-sMAXIMUM_MEMORY=4GB', '4294901760'),
  })
  def test_emscripten_get_heap_max(self, arg, expected):
    create_file('get.c', r'''
      #include <emscripten/heap.h>
      #include <stdio.h>
      int main() {
        printf("max: |%zu|\n", emscripten_get_heap_max());
      }
    ''')
    self.do_runf('get.c', f'max: |{expected}|', cflags=['-sALLOW_MEMORY_GROWTH', arg])

  def test_auto_ptr_cxx17(self):
    # Test that its still possible to use auto_ptr, even in C++17
    self.do_other_test('test_auto_ptr_cxx17.cpp', cflags=[
      '-std=c++17',
      '-D_LIBCPP_ENABLE_CXX17_REMOVED_AUTO_PTR',
      '-Wno-deprecated-declarations'])

  @crossplatform
  def test_special_chars_in_arguments(self):
    # We had some regressions where the windows `.bat` files that run the compiler
    # driver were failing to accept certain special characters such as `(`, `)` and `!`.
    # See https://github.com/emscripten-core/emscripten/issues/14063
    create_file('test(file).c', 'int main() { return 0; }')
    create_file('test!.c', 'int main() { return 0; }')
    self.run_process([EMCC, 'test(file).c'])
    self.run_process([EMCC, 'test!.c'])

  @no_windows('relies on a shell script')
  def test_report_subprocess_signals(self):
    # Test that when subprocess is killed by signal we report the signal name
    create_file('die.sh', '''\
#!/bin/sh
kill -9 $$
    ''')
    make_executable('die.sh')
    with env_modify({'EM_COMPILER_WRAPPER': './die.sh'}):
      self.assert_fail([EMCC, test_file('hello_world.c')], 'failed (received SIGKILL (-9))')

  def test_concepts(self):
    self.do_runf('other/test_concepts.cpp', '', cflags=['-std=c++20'])

  def test_std_cmp(self):
    self.do_runf('other/test_std_cmp.cpp', '', cflags=['-std=c++20'])

  def test_link_only_setting_warning(self):
    err = self.run_process([EMCC, '-sALLOW_MEMORY_GROWTH', '-c', test_file('hello_world.c')], stderr=PIPE).stderr
    self.assertContained("warning: linker setting ignored during compilation: 'ALLOW_MEMORY_GROWTH' [-Wunused-command-line-argument]", err)

  def test_link_only_flag_warning(self):
    err = self.run_process([EMCC, '--embed-file', 'file', '-c', test_file('hello_world.c')], stderr=PIPE).stderr
    self.assertContained("warning: linker flag ignored during compilation: '--embed-file' [-Wunused-command-line-argument]", err)

    # Also test for the format that includes an =arg suffix
    err = self.run_process([EMCC, '--embed-file=file', '-c', test_file('hello_world.c')], stderr=PIPE).stderr
    self.assertContained("warning: linker flag ignored during compilation: '--embed-file=file' [-Wunused-command-line-argument]", err)

  def test_no_deprecated(self):
    # Test that -Wno-deprecated is passed on to clang driver
    create_file('test.c', '''\
        __attribute__((deprecated)) int foo();
        int main() { return foo(); }
    ''')
    self.assert_fail([EMCC, '-c', '-Werror', 'test.c'], "error: 'foo' is deprecated")
    self.run_process([EMCC, '-c', '-Werror', '-Wno-deprecated', 'test.c'])

  def test_bad_export_name(self):
    self.assert_fail([EMCC, '-sEXPORT_NAME=foo bar', test_file('hello_world.c')], 'error: EXPORT_NAME is not a valid JS identifier: `foo bar`')

  def test_standard_library_mapping(self):
    # Test the `-l` flags on the command line get mapped the correct libraries variant
    libs = ['-lc', '-lclang_rt.builtins', '-lmalloc']
    err = self.run_process([EMCC, test_file('hello_world.c'), '-pthread', '-nodefaultlibs', '-v'] + libs, stderr=PIPE).stderr

    # Check that the linker was run with `-mt` variants because `-pthread` was passed.
    self.assertContained(' -lc-mt-debug ', err)
    self.assertContained(' -ldlmalloc-mt-debug ', err)
    self.assertContained(' -lclang_rt.builtins-mt ', err)

  def test_explicit_gl_linking(self):
    # Test that libGL can be linked explicitly via `-lGL` rather than implicitly.
    # Here we use NO_AUTO_NATIVE_LIBRARIES to disable the implicitly linking that normally
    # includes the native GL library.
    self.run_process([EMCC, test_file('other/test_explicit_gl_linking.c'), '-sNO_AUTO_NATIVE_LIBRARIES', '-lGL', '-sGL_ENABLE_GET_PROC_ADDRESS'])

  def test_no_main_with_PROXY_TO_PTHREAD(self):
    create_file('lib.c', r'''
#include <emscripten.h>
EMSCRIPTEN_KEEPALIVE
void foo() {}
''')
    self.assert_fail([EMCC, 'lib.c', '-pthread', '-sPROXY_TO_PTHREAD'], 'crt1_proxy_main.o: undefined symbol: main')

  def test_archive_bad_extension(self):
    self.run_process([EMCC, '-c', test_file('hello_world.c')])
    self.run_process([EMAR, 'crs', 'libtest.bc', 'hello_world.o'])
    self.assert_fail([EMCC, 'libtest.bc'], 'libtest.bc:1:2: error: expected integer')

  def test_split_dwarf_implicit_compile(self):
    # Verify that the dwo file is generated in the current working directory, even when implicitly
    # compiling (compile+link).
    self.run_process([EMCC, test_file('hello_world.c'), '-g', '-gsplit-dwarf'])
    self.assertExists('hello_world.dwo')

  @parameterized({
    '': [[]],
    'strict': [['-sSTRICT']],
    'no_allow': [['-sALLOW_UNIMPLEMENTED_SYSCALLS=0']],
  })
  def test_unimplemented_syscalls(self, args):
    create_file('main.c', '''
    #include <assert.h>
    #include <errno.h>
    #include <sys/mman.h>

    int main() {
      assert(mincore(0, 0, 0) == -1);
      assert(errno == ENOSYS);
      return 0;
    }
    ''')
    cmd = [EMCC, 'main.c', '-sASSERTIONS'] + args
    if args:
      self.assert_fail(cmd, 'libc-debug.a(mincore.o): undefined symbol: __syscall_mincore')
    else:
      self.run_process(cmd)
      err = self.run_js('a.out.js')
      self.assertContained('warning: unsupported syscall: __syscall_mincore', err)

      # Setting ASSERTIONS=0 should avoid the runtime warning
      self.run_process(cmd + ['-sASSERTIONS=0'])
      err = self.run_js('a.out.js')
      self.assertNotContained('warning: unsupported syscall', err)

  @also_with_wasm64
  def test_unimplemented_syscalls_dlopen(self):
    cmd = [EMCC, test_file('other/test_dlopen_blocking.c')] + self.get_cflags()
    self.run_process(cmd)
    err = self.run_js('a.out.js', assert_returncode=NON_ZERO)
    self.assertContained('dlopen failed: dynamic linking not enabled', err)

    # If we build the same thing with ALLOW_UNIMPLEMENTED_SYSCALLS=0 we
    # expect a link-time failure rather than a runtime one.
    cmd += ['-sALLOW_UNIMPLEMENTED_SYSCALLS=0']
    self.assert_fail(cmd, 'undefined symbol: dlopen')

  def test_unimplemented_syscalls_dladdr(self):
    create_file('main.c', r'''
    #include <assert.h>
    #include <stdio.h>
    #include <dlfcn.h>

    int main() {
      Dl_info info;
      int rtn = dladdr(&main, &info);
      assert(rtn == 0);
      puts("done\n");
      return 0;
    }
    ''')

    self.do_runf('main.c', 'done\n')
    self.do_runf('main.c', 'done\n', cflags=['-sMAIN_MODULE=2'])

  @requires_v8
  def test_missing_shell_support(self):
    # By default shell support is not included
    self.run_process([EMCC, test_file('hello_world.c')])
    err = self.run_js('a.out.js', assert_returncode=NON_ZERO)
    self.assertContained('shell environment detected but not enabled at build time', err)

  def test_removed_runtime_function(self):
    create_file('post.js', 'alignMemory(100, 4);')
    self.run_process([EMCC, test_file('hello_world.c'), '--post-js=post.js'])
    err = self.run_js('a.out.js', assert_returncode=NON_ZERO)
    self.assertContained('`alignMemory` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line', err)

  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  @requires_pthreads
  def test_pthread_lsan_no_leak(self):
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    self.cflags += ['-gsource-map', '-pthread']
    self.do_runf_out_file('pthread/test_pthread_lsan_no_leak.cpp', cflags=['-fsanitize=leak'])
    self.do_runf_out_file('pthread/test_pthread_lsan_no_leak.cpp', cflags=['-fsanitize=address'])

  @no_bun('https://github.com/emscripten-core/emscripten/issues/26198')
  @requires_pthreads
  @also_with_wasm64
  def test_pthread_lsan_leak(self):
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    self.add_pre_run("Module['LSAN_OPTIONS'] = 'exitcode=0'")
    self.cflags += ['-gsource-map', '-pthread']
    expected = [
      'Direct leak of 3432 byte(s) in 1 object(s) allocated from',
      'test_pthread_lsan_leak.cpp:18:17',
      'Direct leak of 2048 byte(s) in 1 object(s) allocated from',
      'test_pthread_lsan_leak.cpp:36:10',
      'Direct leak of 1337 byte(s) in 1 object(s) allocated from',
      'test_pthread_lsan_leak.cpp:30:16',
      'Direct leak of 1234 byte(s) in 1 object(s) allocated from',
      'test_pthread_lsan_leak.cpp:20:13',
      'Direct leak of 420 byte(s) in 1 object(s) allocated from',
      'test_pthread_lsan_leak.cpp:31:13',
      'Direct leak of 42 byte(s) in 1 object(s) allocated from',
      'test_pthread_lsan_leak.cpp:13:21',
      'test_pthread_lsan_leak.cpp:35:3',
      '8513 byte(s) leaked in 6 allocation(s).',
    ]
    self.do_runf('pthread/test_pthread_lsan_leak.cpp', expected, assert_all=True, cflags=['-fsanitize=leak'])
    self.do_runf('pthread/test_pthread_lsan_leak.cpp', expected, assert_all=True, cflags=['-fsanitize=address'])

  @no_bun('https://github.com/emscripten-core/emscripten/issues/26199')
  @requires_pthreads
  def test_pthread_js_exception(self):
    # Ensure that JS exceptions propagate back to the main main thread and cause node
    # to exit with an error.
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    self.do_runf('other/test_pthread_js_exception.c', 'missing is not defined', assert_returncode=NON_ZERO, cflags=['-pthread'])

  @crossplatform
  def test_config_closure_compiler(self):
    self.run_process([EMCC, test_file('hello_world.c'), '--closure=1'])
    with env_modify({'EM_CLOSURE_COMPILER': sys.executable}):
      err = self.expect_fail([EMCC, test_file('hello_world.c'), '--closure=1'])
    self.assertContained('closure compiler', err)
    self.assertContained(sys.executable, err)
    self.assertContained('not execute properly!', err)

  def test_default_pthread_stack_size(self):
    self.do_runf('other/test_default_pthread_stack_size.c')

    # Same again with pthreads enabled
    self.require_pthreads()
    self.do_other_test('test_default_pthread_stack_size.c')

    # Same again but with a custom stack size
    self.do_other_test('test_default_pthread_stack_size.c', cflags=['-DEXPECTED_STACK_SIZE=1024', '-sDEFAULT_PTHREAD_STACK_SIZE=1024'])

  def test_emscripten_set_immediate(self):
    self.do_runf('emscripten_set_immediate.c')

  def test_emscripten_set_immediate_loop(self):
    self.do_runf('emscripten_set_immediate_loop.c')

  @parameterized({
    '': ([],),
    'pthreads': (['-pthread', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'],),
  })
  def test_emscripten_main_loop(self, args):
    self.do_runf('test_emscripten_main_loop.c', cflags=args)

  def test_emscripten_main_loop_and_blocker(self):
    self.do_runf('test_emscripten_main_loop_and_blocker.c')

  def test_emscripten_main_loop_settimeout(self):
    self.do_runf('test_emscripten_main_loop_settimeout.c')

  def test_emscripten_main_loop_setimmediate(self):
    self.do_runf('test_emscripten_main_loop_setimmediate.c')

  @requires_pthreads
  @no_bun('https://github.com/emscripten-core/emscripten/issues/26197')
  def test_pthread_trap(self):
    # TODO(https://github.com/emscripten-core/emscripten/issues/15161):
    # Make this work without PROXY_TO_PTHREAD
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    output = self.do_runf('pthread/test_pthread_trap.c', assert_returncode=NON_ZERO, cflags=['--profiling-funcs', '-pthread'])
    self.assertContained('sent an error!', output)
    self.assertContained('at (test_pthread_trap.wasm.)?thread_main', output, regex=True)

  @requires_pthreads
  @flaky('https://github.com/emscripten-core/emscripten/issues/24725')
  def test_pthread_kill(self):
    self.do_runf_out_file('pthread/test_pthread_kill.c')

  @parameterized({
    '': (['-pthread'],),
    'stub': ([],),
  })
  def test_pthread_kill_self(self, args):
    self.do_runf('pthread/test_pthread_kill_self.c', 'main\n', assert_returncode=NON_ZERO, cflags=args)

  @requires_pthreads
  @parameterized({
    '': ([],),
    'proxied': (['-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'],),
  })
  def test_pthread_sigmask(self, args):
    self.do_runf('pthread/test_pthread_sigmask.c', 'done\n', cflags=args)

  # Tests memory growth in pthreads mode, but still on the main thread.
  @crossplatform
  @requires_pthreads
  @parameterized({
    '': ([],),
    'growable_arraybuffers': (['-sGROWABLE_ARRAYBUFFERS=2', '-Wno-experimental'],),
    'proxy': (['-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'],),
  })
  def test_pthread_growth_mainthread(self, cflags):
    if '-sGROWABLE_ARRAYBUFFERS=2' in cflags:
      if self.engine_is_node():
        self.require_node_26()
    else:
      self.cflags.append('-Wno-pthreads-mem-growth')
    self.do_runf('pthread/test_pthread_memory_growth_mainthread.c', cflags=['-pthread', '-sALLOW_MEMORY_GROWTH', '-sINITIAL_MEMORY=32MB', '-sMAXIMUM_MEMORY=256MB'] + cflags)

  @requires_pthreads
  def test_pthread_join_interrupted(self):
    self.do_runf('pthread/test_pthread_join_interrupted.c', cflags=['-pthread'])

  @requires_node_26
  def test_growable_arraybuffers(self):
    self.do_runf('hello_world.c',
                 cflags=['-O2', '-pthread', '-sALLOW_MEMORY_GROWTH', '-sGROWABLE_ARRAYBUFFERS=2', '-Wno-experimental'],
                 output_basename='growable')
    self.do_runf('hello_world.c',
                 cflags=['-O2', '-pthread', '-sALLOW_MEMORY_GROWTH', '-Wno-pthreads-mem-growth'],
                 output_basename='no_growable')
    growable_size = os.path.getsize('growable.js')
    no_growable_size = os.path.getsize('no_growable.js')
    print('growable:', growable_size, 'no_growable:', no_growable_size)
    self.assertLess(growable_size, no_growable_size)

  # Tests memory growth in a pthread.
  @crossplatform
  @no_deno('https://github.com/denoland/deno/issues/35658')
  @requires_pthreads
  @parameterized({
    '': ([],),
    'growable_arraybuffers': (['-sGROWABLE_ARRAYBUFFERS=2', '-Wno-experimental'],),
    'assert': (['-sASSERTIONS'],),
    'proxy': (['-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'],),
    'minimal': (['-sMINIMAL_RUNTIME', '-sMODULARIZE', '-sEXPORT_NAME=MyModule'],),
  })
  def test_pthread_growth(self, cflags):
    if WINDOWS and platform.machine() == 'ARM64':
      # https://github.com/emscripten-core/emscripten/issues/25627
      # TODO: Switch this to a "require Node.js 24" check
      self.require_node_25()

    if '-sGROWABLE_ARRAYBUFFERS=2' in cflags:
      if self.engine_is_node():
        self.require_node_26()
    else:
      self.cflags.append('-Wno-pthreads-mem-growth')
    self.do_runf('pthread/test_pthread_memory_growth.c', cflags=['-pthread', '-sALLOW_MEMORY_GROWTH', '-sINITIAL_MEMORY=32MB', '-sMAXIMUM_MEMORY=256MB'] + cflags)

  @requires_pthreads
  def test_emscripten_set_interval(self):
    self.do_runf('emscripten_set_interval.c', args=['-pthread', '-sPROXY_TO_PTHREAD'])

  # Test emscripten_console_log(), emscripten_console_warn() and emscripten_console_error()
  def test_emscripten_console_log(self):
    self.do_runf_out_file('emscripten_console_log.c', cflags=['--pre-js', test_file('emscripten_console_log_pre.js')])

  # Tests emscripten_unwind_to_js_event_loop() behavior
  def test_emscripten_unwind_to_js_event_loop(self):
    self.do_runf('test_emscripten_unwind_to_js_event_loop.c')

  @requires_pthreads
  def test_emscripten_set_timeout(self):
    self.do_runf('emscripten_set_timeout.c', args=['-pthread', '-sPROXY_TO_PTHREAD'])

  @requires_pthreads
  def test_emscripten_set_timeout_loop(self):
    self.do_runf('emscripten_set_timeout_loop.c', args=['-pthread', '-sPROXY_TO_PTHREAD'])

  @parameterized({
    '': ([],),
    'asyncify': (['-sASYNCIFY'],),
    'jspi': (['-sJSPI'],),
    'pthreads': (['-pthread', '-sPROXY_TO_PTHREAD'],),
  })
  def test_poll_nonblocking(self, args):
    if '-sJSPI' in args:
      self.require_jspi()
    if '-pthread' in args:
      self.require_pthreads()
    self.do_runf('test_poll_nonblocking.c',
                 'poll probe ok from main\npoll probe ok from callback\n',
                 cflags=args)

  # Verify that we are able to successfully compile a script when the Windows 7
  # and Python workaround env. vars are enabled.
  # See https://bugs.python.org/issue34780
  @with_env_modify({'EM_WORKAROUND_PYTHON_BUG_34780': '1',
                    'EM_WORKAROUND_WIN7_BAD_ERRORLEVEL_BUG': '1'})
  def test_windows_batch_script_workaround(self):
    self.run_process([EMCC, test_file('hello_world.c')])
    self.assertExists('a.out.js')

  @requires_pthreads
  def test_pthread_out_err(self):
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    self.do_other_test('test_pthread_out_err.c', cflags=['-pthread'])

  @requires_pthreads
  def test_pthread_icu(self):
    self.set_setting('USE_ICU')
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    self.do_other_test('test_pthread_icu.cpp', cflags=['-pthread'])

  @requires_pthreads
  @parameterized({
    '': ([],),
    'strict': (['-sSTRICT'],),
  })
  def test_pthread_set_main_loop(self, args):
    self.do_other_test('test_pthread_set_main_loop.c', cflags=args)

  @requires_pthreads
  def test_pthread_fd_close_wasmfs(self):
    create_file('node_warnings', '')
    self.node_args += ['--trace-warnings', '--redirect-warnings=node_warnings']
    self.do_other_test('test_pthread_fd_close.c', cflags=['-sWASMFS', '-sNODERAWFS'])
    self.assertNotContained('closed but not opened in unmanaged mode', read_file('node_warnings'))

  # unistd tests

  def test_unistd_confstr(self):
    self.do_runf_out_file('unistd/confstr.c')

  def test_unistd_ttyname(self):
    self.do_runf('unistd/ttyname.c', 'done\n')

  def test_unistd_pathconf(self):
    self.do_runf_out_file('unistd/pathconf.c')

  def test_unistd_swab(self):
    self.do_runf_out_file('unistd/swab.c')

  @also_with_noderawfs
  @crossplatform
  def test_unistd_isatty(self):
    if '-DNODERAWFS' in self.cflags:
      # Under NODERAWFS istty reports accurate information about the file descriptors
      # of the node process. When we run tests we always capture stdout so we never expect
      # stdout to be a tty.
      stdin_isatty = os.isatty(0)
      self.cflags += ['-DEXPECT_STDOUT=0', f'-DEXPECT_STDIN={int(stdin_isatty)}']
      if WINDOWS:
        self.skipTest('depends on /dev filesystem')
    self.do_runf('unistd/isatty.c', 'done\n')

  def test_unistd_login(self):
    self.do_runf_out_file('unistd/login.c')

  def test_unistd_sleep(self):
    self.do_runf_out_file('unistd/sleep.c')

  @crossplatform
  @with_all_fs
  def test_unistd_fstatfs(self):
    if '-DNODERAWFS' in self.cflags and WINDOWS:
      self.skipTest('depends on /dev filesystem')
    self.do_runf_out_file('unistd/fstatfs.c', cflags=['-sASSERTIONS=2'])

  @no_windows("test is Linux-specific")
  @no_mac("test is Linux-specific")
  @requires_node
  def test_unistd_close_noderawfs(self):
    self.set_setting('NODERAWFS')
    create_file('pre.js', f'''
const {{ execSync }} = require('child_process');

const cmd = 'find /proc/' + process.pid + '/fd -lname "{self.get_dir()}*" -printf "%l\\\\n" || true';
let openFilesPre;

Module.preRun = () => {{
  openFilesPre = execSync(cmd, {{ stdio: ['ignore', 'pipe', 'ignore'] }}).toString();
}}
Module.postRun = () => {{
  const openFilesPost = execSync(cmd, {{ stdio: ['ignore', 'pipe', 'ignore'] }}).toString();
  assert(openFilesPre == openFilesPost, 'File descriptors should not leak. \\n' +
    'Pre: \\n' + openFilesPre +
    'Post: \\n' + openFilesPost);
}}
''')
    self.do_runf_out_file('unistd/close.c', cflags=['--pre-js', 'pre.js'])

  @also_with_wasmfs
  def test_unistd_dup(self):
    self.do_runf_out_file('wasmfs/wasmfs_dup.c')

  @also_with_wasmfs
  def test_unistd_open(self):
    self.do_runf_out_file('wasmfs/wasmfs_open.c')

  @also_with_wasmfs
  def test_unistd_open_append(self):
    self.do_runf_out_file('wasmfs/wasmfs_open_append.c')

  @also_with_wasmfs
  def test_unistd_stat(self):
    self.do_runf_out_file('wasmfs/wasmfs_stat.c')

  @also_with_wasmfs
  def test_unistd_create(self):
    self.set_setting('WASMFS')
    self.do_runf_out_file('wasmfs/wasmfs_create.c')

  def test_unistd_fdatasync(self):
    # TODO: Remove this test in favor of unistd/misc.c
    self.set_setting('WASMFS')
    self.do_runf_out_file('wasmfs/wasmfs_fdatasync.c')

  @also_with_wasmfs
  def test_unistd_seek(self):
    self.do_runf_out_file('wasmfs/wasmfs_seek.c')

  @also_with_wasmfs
  def test_unistd_mkdir(self):
    self.do_runf_out_file('wasmfs/wasmfs_mkdir.c')

  @also_with_wasmfs
  def test_unistd_cwd(self):
    self.do_runf_out_file('wasmfs/wasmfs_chdir.c')

  def test_unistd_chown(self):
    # TODO: Remove this test in favor of unistd/misc.c
    self.set_setting('WASMFS')
    self.do_runf_out_file('wasmfs/wasmfs_chown.c')

  @wasmfs_all_backends
  def test_wasmfs_getdents(self):
    if self.get_setting('NODERAWFS'):
      self.skipTest('test expectations assumes /dev is virtualized')
    # Run only in WASMFS for now.
    self.set_setting('FORCE_FILESYSTEM')
    self.do_runf_out_file('wasmfs/wasmfs_getdents.c')

  def test_wasmfs_jsfile(self):
    self.set_setting('WASMFS')
    self.do_runf_out_file('wasmfs/wasmfs_jsfile.c')

  def test_wasmfs_before_preload(self):
    self.set_setting('WASMFS')
    os.mkdir('js_backend_files')
    create_file('js_backend_files/file.dat', 'data')
    self.do_runf_out_file('wasmfs/wasmfs_before_preload.c', cflags=['--preload-file', 'js_backend_files/file.dat'])

  def test_hello_world_above_2gb(self):
    self.do_runf_out_file('hello_world.c', cflags=['-sGLOBAL_BASE=2GB', '-sINITIAL_MEMORY=3GB'])

  def test_unistd_strerror(self):
    self.do_runf_out_file('unistd/strerror.c')

  def test_hello_function(self):
    # hello_function.cpp is referenced/used in the docs.  This test ensures that it
    # at least compiles.
    # (It seems odd that we ship the entire test/ directory to all our users and
    # reference them in our docs.  Should we move this file to somewhere else such
    # as `examples/`?)
    self.run_process([EMCC, test_file('hello_function.cpp'), '-o', 'function.html', '-sEXPORTED_FUNCTIONS=_int_sqrt', '-sEXPORTED_RUNTIME_METHODS=ccall,cwrap'])

  def test_gmtime_noleak(self):
    # Confirm that gmtime_r does not leak when called in isolation.
    self.do_other_test('test_gmtime_noleak.c', cflags=['-fsanitize=leak'])

  def test_build_fetch_tests(self):
    # We can't run these outside of the browser, but at least we can
    # make sure they build.
    self.cflags.append('-DSERVER="localhost"')
    self.set_setting('FETCH')
    self.build('fetch/test_fetch_to_memory_sync.c')
    self.build('fetch/test_fetch_to_memory_async.c')
    self.build('fetch/test_fetch_persist.c')
    self.build('fetch/test_fetch_idb_delete.c')
    self.build('fetch/test_fetch_idb_store.c')
    self.build('fetch/test_fetch_redirect.c')
    self.build('fetch/test_fetch_stream_async.c')
    self.build('fetch/test_fetch_sync.c')
    self.build('fetch/test_fetch_progress.c')

  def test_fetch_init_node(self):
    # Make sure that `Fetch` initialises correctly under Node where
    # IndexedDB isn't available.
    create_file('src.c', r'''
#include <stdio.h>
int main() {
  puts("ok");
}
''')
    self.do_runf('src.c', 'ok', cflags=['-sFETCH', '-sEXPORTED_RUNTIME_METHODS=Fetch'])

  # Test that using llvm-nm works when response files are in use, and inputs are linked using relative paths.
  # llvm-nm has a quirk that it does not remove escape chars when printing out filenames.
  @with_env_modify({'EM_FORCE_RESPONSE_FILES': '1'})
  def test_llvm_nm_relative_paths_works_with_response_files(self):
    os.mkdir('foo')
    # Test creating a library file with a relative path in a subdir, so it gets a double backslash "\\" in the generated llvm-nm output.
    # Also add a space to stress space escaping, e.g. "\ ".
    create_file(os.path.join('foo', 'foo bar.c'), r'''
      #include <time.h>
      #include <stdint.h>
      time_t foo()
      {
        int64_t secondsSinceEpoch = 0;
        struct tm* utcTime = gmtime((time_t*)&secondsSinceEpoch);
        return mktime(utcTime);
      }
    ''')
    create_file('main.c', r'''
      #include <stdio.h>
      #include <time.h>
      time_t foo(void);
      int main()
      {
        printf("%d\n", (int)foo());
      }
    ''')
    self.run_process([EMCC, 'main.c', os.path.join('foo', 'foo bar.c')])

  def test_tutorial(self):
    # Ensure that files referenced in Tutorial.rst are buildable
    self.run_process([EMCC, test_file('hello_world_file.cpp')])

  @crossplatform
  @also_with_wasm64
  def test_stdint_limits(self):
    if self.is_wasm64():
      suffix = '.64'
    else:
      suffix = ''
    print(suffix)
    self.do_other_test('test_stdint_limits.c', out_suffix=suffix)

  def test_legacy_runtime(self):
    self.set_setting('EXPORTED_FUNCTIONS', ['_malloc', '_main'])

    # By default `LEGACY_RUNTIME` is disabled and `stringToNewUTF8` is not available.
    self.cflags += ['-Wno-deprecated']
    self.do_runf('other/test_legacy_runtime.c',
                 '`stringToNewUTF8` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line',
                 assert_returncode=NON_ZERO)

    # When we enable `LEGACY_RUNTIME`, `stringToNewUTF8` should be available.
    self.do_runf('other/test_legacy_runtime.c', 'hello from js', cflags=['-sLEGACY_RUNTIME'])

    # Adding it to EXPORTED_RUNTIME_METHODS should also make it available.
    self.do_runf('other/test_legacy_runtime.c', 'hello from js', cflags=['-sEXPORTED_RUNTIME_METHODS=stringToNewUTF8'])

  @parameterized({
    '': (['-O0'],),
    'O3': (['-O3'],),
  })
  def test_legacy_jslib(self, args):
    # Warn about usage of legacy library function
    err = self.run_process([EMCC, test_file('hello_world.c'), '-sEXPORTED_RUNTIME_METHODS=allocateUTF8'] + args, stderr=PIPE).stderr
    self.assertContained("warning: JS library symbol '$allocateUTF8' is deprecated. Please open a bug if you have a continuing need for this symbol", err)

    self.assertContained('emcc: warning: warnings in JS library compilation [-Wjs-compiler]', err)

    # In strict mode legacy library functions are not available, so we get a build time error
    expected = 'undefined exported symbol: "allocateUTF8" in EXPORTED_RUNTIME_METHODS'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sEXPORTED_RUNTIME_METHODS=allocateUTF8', '-sSTRICT'] + args, expected)

  def test_fetch_settings(self):
    create_file('pre.js', '''
    Module.fetchSettings = { cache: 'no-store' };
    ''')
    self.cflags += ['--pre-js=pre.js']
    self.do_runf('hello_world.c', '`Module.fetchSettings` was supplied but `fetchSettings` not included in INCOMING_MODULE_JS_API', assert_returncode=NON_ZERO)

    # Try again with INCOMING_MODULE_JS_API set
    self.set_setting('INCOMING_MODULE_JS_API', 'fetchSettings')
    self.do_runf_out_file('hello_world.c')
    src = read_file('hello_world.js')
    self.assertContained("fetch(binaryFile, Module['fetchSettings'] || ", src)

  # Tests building with -sSHARED_MEMORY
  @also_with_minimal_runtime
  def test_shared_memory(self):
    self.do_runf('wasm_worker/shared_memory.c', '0', cflags=[])
    self.do_runf('wasm_worker/shared_memory.c', '1', cflags=['-sSHARED_MEMORY'])
    self.do_runf('wasm_worker/shared_memory.c', '1', cflags=['-sWASM_WORKERS'])
    self.do_runf('wasm_worker/shared_memory.c', '1', cflags=['-pthread'])

  # Tests C preprocessor flags with -sSHARED_MEMORY
  @also_with_minimal_runtime
  def test_shared_memory_preprocessor_flags(self):
    self.run_process([EMCC, '-c', test_file('wasm_worker/shared_memory_preprocessor_flags.c'), '-sSHARED_MEMORY'])

  # Tests C preprocessor flags with -sWASM_WORKERS
  @also_with_minimal_runtime
  def test_wasm_worker_preprocessor_flags(self):
    self.run_process([EMCC, '-c', test_file('wasm_worker/wasm_worker_preprocessor_flags.c'), '-sWASM_WORKERS'])

  @also_with_minimal_runtime
  def test_wasm_worker_pthread_api_usage(self):
    self.assert_fail([EMCC, test_file('wasm_worker/wasm_worker_pthread_api_usage.c'), '-sWASM_WORKERS'], 'undefined symbol: pthread_mutex_lock')

  @also_with_minimal_runtime
  def test_wasm_worker_and_pthread(self):
    self.set_setting('STACK_OVERFLOW_CHECK', 2)
    self.set_setting('SAFE_HEAP', 2)
    self.do_runf('wasm_worker/wasm_worker_and_pthread.c', 'done\n', cflags=['-sWASM_WORKERS', '-pthread'])

  @also_with_minimal_runtime
  def test_wasm_worker_cxx_init(self):
    self.do_runf_out_file('wasm_worker/wasm_worker_cxx_init.cpp', cflags=['-sWASM_WORKERS'])

  @parameterized({
    # we will warn here since -O2 runs the optimizer and -g enables DWARF
    'O2_g': (True, ['-O2', '-g']),
    # asyncify will force wasm-opt to run as well, but without optimizations, so
    # we do not warn about the lack of optimization
    'asyncify_g': (False, ['-sASYNCIFY', '-g']),
    # with --profiling-funcs however we do not use DWARF (we just emit the
    # names section) and will not warn.
    'O2_pfuncs': (False, ['-O2', '--profiling-funcs']),
  })
  def test_debug_opt_warning(self, should_fail, args):
    if should_fail:
      err = self.expect_fail([EMCC, test_file('hello_world.c'), '-Werror'] + args)
      self.assertContained('error: running limited binaryen optimizations because DWARF info requested (or indirectly required) [-Wlimited-postlink-optimizations]', err)
    else:
      self.run_process([EMCC, test_file('hello_world.c'), '-Werror'] + args)

  def test_wasm_worker_trusted_types(self):
    self.do_runf_out_file('wasm_worker/hello_wasm_worker.c', cflags=['-sWASM_WORKERS', '-sTRUSTED_TYPES'])

  def test_wasm_worker_export_es6(self):
    self.do_runf_out_file('wasm_worker/hello_wasm_worker.c', cflags=['-sWASM_WORKERS',
                                                                     '-sEXPORT_ES6',
                                                                     '--extern-post-js',
                                                                     test_file('modularize_post_js.js')])

  def test_wasm_worker_terminate(self):
    self.do_runf('wasm_worker/terminate_wasm_worker.c', cflags=['-sWASM_WORKERS'])

  def test_wasm_worker_dbg(self):
    self.do_runf_out_file('wasm_worker/test_wasm_worker_dbg.c', cflags=['-sWASM_WORKERS'])

  @also_with_minimal_runtime
  def test_wasm_worker_closure(self):
    self.run_process([EMCC, test_file('wasm_worker/lock_async_acquire.c'), '-O2', '-sWASM_WORKERS', '--closure=1'])

  def test_wasm_worker_errors(self):
    expected = '-sSINGLE_FILE is not supported with -sWASM_WORKERS'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sWASM_WORKERS', '-sSINGLE_FILE'], expected)
    expected = 'dynamic linking is not supported with -sWASM_WORKERS'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sWASM_WORKERS', '-sMAIN_MODULE'], expected)

  def test_clock_nanosleep(self):
    self.do_runf('other/test_clock_nanosleep.c')

  # Tests the internal test suite of tools/unsafe_optimizations.js
  def test_unsafe_optimizations(self):
    # Note: Here we use config.NODE_JS (the version of node used by the compiler) rather than the
    # version of node in JS_ENGINES.
    self.run_process(config.NODE_JS + [path_from_root('tools/unsafe_optimizations.mjs'), '--test'])

  @requires_v8
  def test_extended_const(self):
    # Export at least one global so that we exercise the parsing of the global section.
    create_file('test.c', r'''
      #include <stdio.h>
      int g_data[10] = {1};
      void test() {
        printf("some rodata %d\n", g_data[0]);
      }
    ''')
    self.emcc('test.c', ['-mextended-const', '-sSIDE_MODULE'])
    wat = self.get_wasm_text('a.out.wasm')
    # Test that extended-const expressions are used in the data segments.
    self.assertContained(r'\(data (\$\S+ )?\(offset \(i32.add\s+\(global.get \$\S+\)\s+\(i32.const \d+\)', wat, regex=True)

  # Smoketest for wasm64.  Most of the testing of wasm64 is by way of the wasm64
  # variant of the core test suite.
  @parameterized({
    'O0': (['-O0'],),
    'O1': (['-O1'],),
    'O2': (['-O2'],),
    'O3': (['-O3'],),
    'Oz': (['-Oz'],),
  })
  @requires_wasm64
  def test_wasm64(self, args):
    self.do_runf_out_file('core/test_hello_argc.c', args=['hello', 'world'], cflags=['-m64'] + args)

  # Verfy that MAIN_MODULE=1 (which includes all symbols from all libraries)
  # works with -sPROXY_POSIX_SOCKETS and -Oz, both of which affect linking of
  # system libraries in different ways.
  @also_with_wasmfs
  def test_dylink_proxy_posix_sockets_oz(self):
    self.do_runf_out_file('hello_world.cpp', cflags=['-lwebsocket.js', '-sMAIN_MODULE=1', '-sPROXY_POSIX_SOCKETS', '-Oz'])

  def test_in_tree_header_usage(self):
    # Using headers directly from where they live in the source tree does not work.
    # Verify that we generate a useful warning when folks try to do this.
    create_file('test.c', '#include <emscripten.h>')
    expected = '#error "Including files directly from the emscripten source tree is not supported'
    self.assert_fail([EMCC, '-I' + path_from_root('system/include'), 'test.c'], expected)

  def test_multiple_g_flags(self):
    # Verify that a second -g argument overrides the first
    self.run_process([EMCC, test_file('hello_world.c'), '-c', '-g'])
    self.assertIn(b'.debug', read_binary('hello_world.o'))
    self.run_process([EMCC, test_file('hello_world.c'), '-c', '-g', '-g0'])
    self.assertNotIn(b'.debug', read_binary('hello_world.o'))

  @requires_v8
  @is_slow_test
  def test_jspi_code_size(self):
    # use iostream code here to purposefully get a fairly large wasm file, so
    # that our size comparisons later are meaningful
    create_file('main.cpp', r'''
      #include <emscripten.h>
      #include <iostream>
      int main() {
        std::cout << "nap time\n";
        emscripten_sleep(1);
        std::cout << "i am awake\n";
      }
    ''')
    expected = 'nap time\ni am awake\n'

    shared_args = ['-Os', '-sENVIRONMENT=shell']
    self.run_process([EMXX, 'main.cpp', '-sASYNCIFY'] + shared_args)
    self.assertContained(expected, self.run_js('a.out.js'))
    asyncify_size = os.path.getsize('a.out.wasm')

    self.run_process([EMXX, 'main.cpp', '-sJSPI'] + shared_args)

    self.assertContained(expected, self.run_js('a.out.js'))
    stack_switching_size = os.path.getsize('a.out.wasm')

    # also compare to code size without asyncify or stack switching
    self.run_process([EMXX, 'main.cpp'] + shared_args)
    nothing_size = os.path.getsize('a.out.wasm')

    # stack switching does not asyncify the code, which means it is very close
    # to the normal "nothing" size, and much smaller than the asyncified size
    self.assertLess(stack_switching_size, 0.60 * asyncify_size)
    self.assertLess(abs(stack_switching_size - nothing_size), 0.01 * nothing_size)

  def test_no_cfi(self):
    self.assert_fail([EMCC, '-fsanitize=cfi', '-flto', test_file('hello_world.c')], 'emcc: error: emscripten does not currently support -fsanitize=cfi')

  @also_without_bigint
  def test_parseTools(self):
    # Suppress js compiler warnings because we deliberately use legacy parseTools functions
    self.cflags += ['-Wno-js-compiler', '--js-library', test_file('other/test_parseTools.js')]
    self.do_other_test('test_parseTools.c')

    # If we run ths same test with -sASSERTIONS=2 we expect it to fail because it
    # involves writing numbers that are exceed the side of the type.
    expected = 'Aborted(Assertion failed: value (316059037807746200000) too large to write as 64-bit value)'
    self.do_runf('other/test_parseTools.c', expected, cflags=['-sASSERTIONS=2'], assert_returncode=NON_ZERO)

  def test_lto_atexit(self):
    # Without EXIT_RUNTIME we don't expect the dtor to run at all
    output = self.do_runf('other/test_lto_atexit.c', 'main done', cflags=['-flto'])
    self.assertNotContained('my_dtor', output)

    # With EXIT_RUNTIME we expect to see the dtor running.
    self.do_runf('other/test_lto_atexit.c', 'main done\nmy_dtor\n', cflags=['-flto', '-sEXIT_RUNTIME'])

  @crossplatform
  def test_prejs_unicode(self):
    create_file('script.js', r'''
      console.log('↓');
    ''')
    self.do_runf('hello_world.c', '↓', cflags=['--pre-js=script.js'])

  def test_xlocale(self):
    # Test for xlocale.h compatibility header
    self.do_other_test('test_xlocale.c')
    self.do_other_test('test_xlocale.c', cflags=['-x', 'c++'])

  def test_print_map(self):
    self.run_process([EMCC, '-c', test_file('hello_world.c')])
    out = self.run_process([EMCC, 'hello_world.o', '-Wl,--print-map'], stdout=PIPE).stdout
    self.assertContained('hello_world.o:(__original_main)', out)
    out2 = self.run_process([EMCC, 'hello_world.o', '-Wl,-M'], stdout=PIPE).stdout
    self.assertEqual(out, out2)

  def test_rust_gxx_personality_v0(self):
    self.do_run(r'''
      #include <stdio.h>
      #include <stdint.h>
      extern "C" {
        int __gxx_personality_v0(int version, void* actions, uint64_t exception_class, void* exception_object, void* context);
      }
      int main() {
        __gxx_personality_v0(0, NULL, 0, NULL, NULL);
        return 0;
      }
    ''', assert_returncode=NON_ZERO, cflags=['-fexceptions'])

  def test_bigint64array_polyfill(self):
    bigint64array = read_file(path_from_root('src/polyfill/bigint64array.js'))
    test_code = read_file(test_file('test_bigint64array_polyfill.js'))
    bigint_list = [
      0,
      1,
      -1,
      5,
      (1 << 64),
      (1 << 64) - 1,
      (1 << 64) + 1,
      (1 << 63),
      (1 << 63) - 1,
      (1 << 63) + 1,
    ]
    bigint_list_strs = [str(x) for x in bigint_list]

    bigint_list_unsigned = [x % (1 << 64) for x in bigint_list]
    bigint_list_signed = [
      x if x < 0 else (x % (1 << 64)) - 2 * (x & (1 << 63)) for x in bigint_list
    ]
    bigint_list_unsigned_n = [f'{x}n' for x in bigint_list_unsigned]
    bigint_list_signed_n = [f'{x}n' for x in bigint_list_signed]

    bigint64array = '\n'.join(bigint64array.splitlines()[3:])

    create_file(
      'test.js',
      f'''
      let bigint_list = {bigint_list_strs}.map(x => BigInt(x));
      let arr1signed = new BigInt64Array(20);
      let arr1unsigned = new BigUint64Array(20);
      delete globalThis.BigInt64Array;
      ''' + bigint64array + test_code,
    )
    output = json.loads(self.run_js('test.js'))
    self.assertEqual(output['BigInt64Array_name'], 'createBigInt64Array')
    for key in ('arr1_to_arr1', 'arr1_to_arr2', 'arr2_to_arr1'):
      print(key + '_unsigned')
      self.assertEqual(output[key + '_unsigned'], bigint_list_unsigned_n)
    for key in ('arr1_to_arr1', 'arr1_to_arr2', 'arr2_to_arr1'):
      print(key + '_signed')
      self.assertEqual(output[key + '_signed'], bigint_list_signed_n)

    self.assertEqual(output['arr2_slice'], ['2n', '3n', '4n', '5n'])
    self.assertEqual(output['arr2_subarray'], ['2n', '3n', '4n', '5n'])

    for m, [v1, v2] in output['assertEquals']:
      self.assertEqual(v1, v2, msg=m)

  def test_warn_once(self):
    create_file('main.c', r'''\
      #include <stdio.h>
      #include <emscripten.h>

      EM_JS_DEPS(main, "$warnOnce");

      int main() {
        EM_ASM({
          warnOnce("foo");
          // Second call should not output anything
          warnOnce("foo");
        });
        printf("done\n");
      }
    ''')
    self.do_runf('main.c', 'warning: foo\ndone\n')

  def test_compile_with_cache_lock(self):
    # Verify that, after warming the cache, running emcc does not require the cache lock.
    # Previously we would acquire the lock during sanity checking (even when the check
    # passed) which meant the second process here would deadlock.
    if config.FROZEN_CACHE:
      self.skipTest("test doesn't work with frozen cache")
    self.run_process([EMCC, '-c', test_file('hello_world.c')])
    with cache.lock('testing'):
      self.run_process([EMCC, '-c', test_file('hello_world.c')])

  def test_recursive_cache_lock(self):
    if config.FROZEN_CACHE:
      self.skipTest("test doesn't work with frozen cache")
    with cache.lock('testing'):
      err = self.expect_fail([EMBUILDER, 'build', 'libc', '--force'], expect_traceback=True)
    self.assertContained('AssertionError: attempt to lock the cache while a parent process is holding the lock', err)

  @also_with_wasmfs
  def test_fs_icase(self):
    # c++20 for ends_with().
    self.do_other_test('test_fs_icase.cpp', cflags=['-sCASE_INSENSITIVE_FS', '-std=c++20'])

  @crossplatform
  @with_all_fs
  def test_std_filesystem(self):
    if self.get_setting('NODERAWFS') and self.get_setting('WASMFS'):
      self.skipTest('https://github.com/emscripten-core/emscripten/issues/24830')
    self.do_other_test('test_std_filesystem.cpp')

  @crossplatform
  @with_all_fs
  def test_std_filesystem_tempdir(self):
    if self.get_setting('NODERAWFS') and self.get_setting('WASMFS'):
      self.skipTest('https://github.com/emscripten-core/emscripten/issues/24830')
    self.do_other_test('test_std_filesystem_tempdir.cpp', cflags=['-g'])

  def test_strict_js_closure(self):
    self.do_runf_out_file('hello_world.c', cflags=['-sSTRICT_JS', '-Werror=closure', '--closure=1', '-O3'])

  def test_em_js_deps(self):
    # Check that EM_JS_DEPS works. Specifically, multiple different instances in different
    # object files.
    create_file('f1.c', '''
    #include <emscripten.h>

    EM_JS_DEPS(other, "$stringToUTF8OnStack");
    ''')
    create_file('f2.c', '''
    #include <emscripten.h>

    EM_JS_DEPS(main, "$getHeapMax");

    int main() {
      EM_ASM({
        err(getHeapMax());
        var x = stackSave();
        stringToUTF8OnStack("hello");
        stackRestore(x);
      });
      return 0;
    }
    ''')
    self.do_runf('f2.c', cflags=['f1.c'])

  def test_em_js_deps_anon_ns(self):
    # Check that EM_JS_DEPS is not eliminated in
    # an anonymous C++ namespace.
    create_file('test_em_js_deps.cpp', '''
    #include <emscripten.h>

    namespace {
      EM_JS_DEPS(test, "$stringToUTF8OnStack");
    }

    int main() {
      EM_ASM({
        var x = stackSave();
        stringToUTF8OnStack("hello");
        stackRestore(x);
      });
    }
    ''')
    self.do_runf('test_em_js_deps.cpp')

  @no_mac('https://github.com/emscripten-core/emscripten/issues/18175')
  @crossplatform
  def test_stack_overflow(self):
    self.set_setting('STACK_OVERFLOW_CHECK', 1)
    self.do_runf('core/stack_overflow.c',
                 'Stack overflow detected.  You can try increasing -sSTACK_SIZE',
                 cflags=['-O1', '--profiling-funcs'],
                 assert_returncode=NON_ZERO)

  @also_with_wasm64
  @parameterized({
    '': ([],),
    'no_stack_first': (['-Wl,--no-stack-first'],),
  })
  def test_stack_cookie_curruption(self, args):
    create_file('test.c', r'''
      #include <emscripten/stack.h>
      #include <stdint.h>

      int main() {
        uint32_t *stack_end = (uint32_t *)emscripten_stack_get_end();
        if (stack_end == 0) stack_end++;
        stack_end[0] = 0xfffffff0;
        stack_end[1] = 0xaaaaaaa0;
        return 0;
      }
    ''')
    expected = 'Stack overflow! Stack cookie has been overwritten at 0x[a-f0-9]*, expected hex dwords 0x89bacdfe and 0x02135467, but received 0xaaaaaaa0 0xfffffff0'
    self.do_runf('test.c', expected, regex=True, cflags=args + ['-sSTACK_OVERFLOW_CHECK=1'], assert_returncode=NON_ZERO)

  @crossplatform
  def test_reproduce(self):
    ensure_dir('tmp')
    self.run_process([EMCC, '-sASSERTIONS=1', '--reproduce=foo.tar', '-otmp/out.js', test_file('hello_world.c')])
    self.assertExists('foo.tar')
    names = []
    root = os.path.splitdrive(path_from_root())[1][1:]
    root = utils.normalize_path(root)
    print('root: %s' % root)
    with tarfile.open('foo.tar') as f:
      for name in f.getnames():
        print('name: %s' % name)
        names.append(name.replace(root, '<root>'))
      f.extractall()
    names = '\n'.join(sorted(names)) + '\n'
    expected = '''\
foo/<root>/test/hello_world.c
foo/response.txt
foo/version.txt
'''
    self.assertTextDataIdentical(expected, names)
    expected = '''\
-sASSERTIONS=1
-o
out.js
<root>/test/hello_world.c
'''
    response = read_file('foo/response.txt')
    response = utils.normalize_path(response)
    response = response.replace(root, '<root>')
    self.assertTextDataIdentical(expected, response)

  def test_min_browser_version(self):
    expected = 'emcc: error: MIN_SAFARI_VERSION=140100 is not compatible with WASM_BIGINT (MIN_SAFARI_VERSION=150000 or above required)'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-Werror', '-sWASM_BIGINT', '-sMIN_SAFARI_VERSION=140100'], expected)

  # Test that using two different ways to disable a target environment at the same time will not produce a warning.
  def test_double_disable_environment(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-Werror', '-sENVIRONMENT=web', '-sMIN_NODE_VERSION=-1'])
    self.run_process([EMCC, test_file('hello_world.c'), '-Werror', '-sENVIRONMENT=node', '-sMIN_FIREFOX_VERSION=-1'])
    self.run_process([EMCC, test_file('hello_world.c'), '-Werror', '-sENVIRONMENT=node', '-sMIN_CHROME_VERSION=-1'])
    self.run_process([EMCC, test_file('hello_world.c'), '-Werror', '-sENVIRONMENT=node', '-sMIN_SAFARI_VERSION=-1'])

  # Test that passing "-sENVIRONMENT=node -pthread" will generate code that only targets Node.js multithreading, and
  # does not pull in code that supports browser multithreading.
  def test_only_target_node_pthreads(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-Werror', '-sENVIRONMENT=node', '-pthread'])
    content = read_file('a.out.js')
    self.assertContained('This page was compiled without support for Safari browser', content)
    self.assertContained('This page was compiled without support for Firefox browser', content)
    self.assertContained('This page was compiled without support for Chrome browser', content)

  @flaky('https://github.com/emscripten-core/emscripten/issues/20125')
  def test_itimer(self):
    self.do_other_test('test_itimer.c')

  @requires_pthreads
  @flaky('https://github.com/emscripten-core/emscripten/issues/20125')
  def test_itimer_pthread(self):
    self.do_other_test('test_itimer.c')

  def test_itimer_standalone(self):
    self.do_other_test('test_itimer_standalone.c', cflags=['-sSTANDALONE_WASM', '-sWASM_BIGINT'])
    for engine in config.WASM_ENGINES:
      print('wasm engine', engine)
      self.assertContained('done\n', self.run_js('test_itimer_standalone.wasm', engine))

  @requires_pthreads
  @flaky('https://github.com/emscripten-core/emscripten/issues/20125')
  def test_itimer_proxy_to_pthread(self):
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    self.do_other_test('test_itimer.c')

  @requires_pthreads
  def test_dbg(self):
    create_file('pre.js', '''
    dbg('start', { foo: 1});
    Module.onRuntimeInitialized = () => dbg('done init', { bar: 1});
    ''')
    expected = '''\
start { foo: 1 }
w:0,t:0x[0-9a-fA-F]+: done init { bar: 1 }
Hello, world!
w:0,t:0x[0-9a-fA-F]+: native dbg message
w:0,t:0x[0-9a-fA-F]+: Hello
w:0,t:0x[0-9a-fA-F]+: formatted: 42
'''
    self.cflags.append('--pre-js=pre.js')
    # Verify that, after initialization, dbg() messages are prefixed with
    # worker and thread ID.
    self.do_runf('other/test_dbg.c', expected, interleaved_output=True, regex=True)

    # Verify that stdout does not contain dbg() messages (interleaved_output=True
    # means we return stdout followed by stderr)
    self.do_runf('other/test_dbg.c', 'Hello, world!\nstart { foo: 1 }\n', interleaved_output=False)

    # When assertions are disabled `dbg` function is not defined
    self.do_runf('other/test_dbg.c',
                 'ReferenceError: dbg is not defined',
                 cflags=['-DNDEBUG', '-sASSERTIONS=0'],
                 assert_returncode=NON_ZERO)

  def test_standalone_settings(self):
    base_cmd = [EMCC, test_file('hello_world.c'), '-sSTANDALONE_WASM']

    expected = 'emcc: error: STANDALONE_WASM is not compatible with MINIMAL_RUNTIME'
    self.assert_fail(base_cmd + ['-sMINIMAL_RUNTIME'], expected)

    expected = 'error: MEMORY_GROWTH_GEOMETRIC_CAP is not compatible with STANDALONE_WASM'
    self.assert_fail(base_cmd + ['-sMEMORY_GROWTH_GEOMETRIC_CAP=1mb'], expected)

    expected = 'error: MEMORY_GROWTH_LINEAR_STEP is not compatible with STANDALONE_WASM'
    self.assert_fail(base_cmd + ['-sMEMORY_GROWTH_LINEAR_STEP=1mb'], expected)

  def test_standalone_imports(self):
    # Ensure standalone binary will not have __throw_exception_with_stack_trace
    # debug helper dependency, caused by exception-related code.
    self.do_runf('core/test_exceptions.cpp', cflags=['-fwasm-exceptions', '-sSTANDALONE_WASM'])
    imports = self.parse_wasm('test_exceptions.wasm')[0]
    for name in imports:
      self.assertTrue(name.startswith('wasi_'), 'Unexpected import %s' % name)

  @is_slow_test
  @parameterized({
    '': ([],),
    # See https://github.com/google/googletest/issues/5004
    'cxx20': (['-std=c++20', '-Wno-character-conversion'],),
  })
  def test_googletest(self, args):
    # TODO(sbc): Should we package gtest as an emscripten "port"?  I guess we should if
    # we plan on using it in more places.
    self.do_other_test('test_googletest.cc', cflags=args + [
      '-I' + test_file('third_party/googletest/googletest'),
      '-I' + test_file('third_party/googletest/googletest/include'),
      test_file('third_party/googletest/googletest/src/gtest-all.cc'),
      test_file('third_party/googletest/googletest/src/gtest_main.cc'),
    ])

  def test_parseTools_legacy(self):
    create_file('post.js', '''
      err(_foo());
    ''')
    create_file('lib.js', '''
      addToLibrary({
        foo: () => {{{ Runtime.POINTER_SIZE }}}
      });
    ''')
    self.set_setting('DEFAULT_LIBRARY_FUNCS_TO_INCLUDE', 'foo')
    self.do_runf('hello_world.c', '4\nHello, world!',
                 cflags=['--post-js=post.js', '--js-library=lib.js'])

  @requires_node
  def test_min_node_version(self):
    if not self.engine_is_node():
      # The `requires_node` decorator above also allows for node-like environments such as
      # deno and bun, but this test requires actual node.
      self.skipTest('requires nodejs')
    node_version = shared.get_node_version(get_nodejs())
    node_version = '.'.join(str(x) for x in node_version)
    self.set_setting('MIN_NODE_VERSION', 300000)
    expected = 'This emscripten-generated code requires node v30.0.0 (detected v%s' % node_version
    self.do_runf('hello_world.c', expected, assert_returncode=NON_ZERO)

  def test_deprecated_macros(self):
    create_file('main.c', '''
    #include <assert.h>
    #include <stdio.h>
    #include <emscripten/threading.h>

    int main() {
      printf("%d\\n", emscripten_main_browser_thread_id());
      assert(emscripten_main_browser_thread_id());
      return 0;
    }
    ''')
    err = self.run_process([EMCC, 'main.c'], stderr=PIPE).stderr
    expected = "warning: macro 'emscripten_main_browser_thread_id' has been marked as deprecated: use emscripten_main_runtime_thread_id instead [-Wdeprecated-pragma]"
    self.assertContained(expected, err)

  @requires_pthreads
  def test_USE_PTHREADS(self):
    self.cflags.remove('-pthread')
    self.set_setting('USE_PTHREADS')
    self.set_setting('PROXY_TO_PTHREAD')
    self.set_setting('EXIT_RUNTIME')
    self.do_runf('pthread/test_pthread_create.c', cflags=['-Wno-deprecated'])

  def test_cpp_module(self):
    self.run_process([EMXX, '-std=c++20', test_file('other/hello_world.cppm'), '--precompile', '-o', 'hello_world.pcm'])
    self.do_other_test('test_cpp_module.cpp', cflags=['-std=c++20', '-fprebuilt-module-path=.', 'hello_world.pcm'])

  @crossplatform
  def test_pthreads_flag(self):
    # We support just the singular form of `-pthread`, like gcc
    # Clang supports the plural form too but I think just due to historical accident:
    # See https://github.com/llvm/llvm-project/commit/c800391fb974cdaaa62bd74435f76408c2e5ceae
    self.assert_fail([EMCC, '-pthreads', '-c', test_file('hello_world.c')], 'emcc: error: unrecognized command-line option `-pthreads`; did you mean `-pthread`?')

  def test_missing_struct_info(self):
    create_file('lib.js', '''
      {{{ C_STRUCTS.Foo }}}
    ''')
    expected = 'Error: Missing C struct Foo! If you just added it to struct_info.json, you need to run ./tools/gen_struct_info.py (then run a second time with --wasm64)'
    self.assert_fail([EMCC, test_file('hello_world.c'), '--js-library=lib.js'], expected)

    create_file('lib.js', '''
      {{{ C_DEFINES.Foo }}}
    ''')
    expected = 'Error: Missing C define Foo! If you just added it to struct_info.json, you need to run ./tools/gen_struct_info.py (then run a second time with --wasm64)'
    self.assert_fail([EMCC, test_file('hello_world.c'), '--js-library=lib.js'], expected)

  def run_wasi_test_suite_test(self, name):
    if not os.path.exists(path_from_root('test/third_party/wasi-test-suite')):
      self.fail('wasi-testsuite not found; run `git submodule update --init`')
    wasm = path_from_root(f'test/third_party/wasi-test-suite/{name}.wasm')
    json_data = utils.read_file(path_from_root(f'test/third_party/wasi-test-suite/{name}.json'))
    config = json.loads(json_data)
    exit_code = config.get('exitCode', 0)
    args = config.get('args', [])
    env = config.get('env', [])
    if env:
      env = [f'ENV["{key}"] = "{value}";' for key, value in env.items()]
      env = '\n'.join(env)
      create_file('env.js', 'Module.preRun = () => { %s };' % env)
      self.cflags.append('--pre-js=env.js')
    self.run_process([EMCC, '-Wno-experimental', '--post-link', '-g',
                      '-sPURE_WASI', '-lnodefs.js', '-lnoderawfs.js',
                      wasm, '-o', name + '.js'] + self.get_cflags(main_file=True))

    output = self.run_js(name + '.js', args=args, assert_returncode=exit_code)
    if 'stdout' in config:
      self.assertContained(config['stdout'], output)

  @requires_node
  def test_wasi_std_env_args(self):
    create_file('pre.js', 'Module["thisProgram"] = "std_env_args.wasm"')
    self.cflags += ['--pre-js', 'pre.js']
    self.run_wasi_test_suite_test('std_env_args')

  @requires_node
  def test_wasi_std_env_vars(self):
    self.run_wasi_test_suite_test('std_env_vars')

  @requires_node
  def test_wasi_std_io_stdout(self):
    self.run_wasi_test_suite_test('std_io_stdout')

  @requires_node
  def test_wasi_std_io_stderr(self):
    self.run_wasi_test_suite_test('std_io_stderr')

  @also_with_wasmfs
  @requires_node
  def test_wasi_clock_res_get(self):
    self.run_wasi_test_suite_test('wasi_clock_res_get')

  @requires_node
  def test_wasi_clock_time_get(self):
    self.run_wasi_test_suite_test('wasi_clock_time_get')

  @requires_node
  def test_wasi_fd_fdstat_get(self):
    self.run_wasi_test_suite_test('wasi_fd_fdstat_get')

  @requires_node
  def test_wasi_wasi_fd_write_file(self):
    self.run_wasi_test_suite_test('wasi_fd_write_file')
    self.assertEqual(read_file('new_file'), 'new_file')

  @requires_node
  def test_wasi_wasi_fd_write_stdout(self):
    self.run_wasi_test_suite_test('wasi_fd_write_stdout')

  @requires_node
  def test_wasi_wasi_fd_write_stderr(self):
    self.run_wasi_test_suite_test('wasi_fd_write_stderr')

  @requires_node
  def test_wasi_proc_exit(self):
    self.run_wasi_test_suite_test('wasi_proc_exit')

  @requires_node
  def test_wasi_random_get(self):
    self.run_wasi_test_suite_test('wasi_random_get')

  @requires_node
  def test_wasi_sched_yield(self):
    self.run_wasi_test_suite_test('wasi_sched_yield')

  def test_wasi_with_sjlj(self):
    # When PURE_WASI is set and Wasm exception is not being used, we turn off
    # SUPPORT_LONGJMP by default because it uses a JS-based simulation of
    # longjmp.
    self.set_setting('PURE_WASI')
    self.assert_fail([EMCC, test_file('core/test_longjmp.c')] + self.get_cflags(), 'error: longjmp support was disabled (SUPPORT_LONGJMP=0)')

    # When using Wasm exception, SUPPORT_LONGJMP defaults to 'wasm', which does
    # not use the JS-based support. This should succeed.
    # -fwasm-exceptions exports __cpp_exception, so this is necessary
    self.set_setting('DEFAULT_TO_CXX')
    self.do_runf('core/test_longjmp.c', cflags=['-fwasm-exceptions'])

  def test_memory_init_file_unsupported(self):
    self.assert_fail([EMCC, test_file('hello_world.c'), '-Werror', '--memory-init-file=1'], 'error: --memory-init-file is no longer supported')

  @requires_pthreads
  def test_node_pthreads_err_out(self):
    create_file('post.js', 'err(1, 2, "hello"); out("foo", 42);')
    self.do_runf('hello_world.c', '1 2 hello\nfoo 42\n', cflags=['--post-js=post.js'])

  @only_windows('This test verifies Windows batch script behavior against bug https://github.com/microsoft/terminal/issues/15212')
  @with_env_modify({'PATH': path_from_root() + os.pathsep + os.getenv('PATH')})
  def test_windows_batch_file_dp0_expansion_bug(self):
    create_file('build_with_quotes.bat', f'@"emcc" "{test_file("hello_world.c")}"')
    self.run_process(['build_with_quotes.bat'])

  @only_windows('Check that directory permissions are properly retrieved on Windows')
  @requires_node
  def test_windows_nodefs_execution_permission(self):
    src = r'''
    #include <assert.h>
    #include <emscripten.h>
    #include <sys/stat.h>
    #include <string.h>
    #include <stdio.h>

    void setup() {
      EM_ASM(
        FS.mkdir('new-dir');
        FS.writeFile('new-dir/test.txt', 'test');
      );
    }

    void test() {
      int err;
      struct stat s;
      memset(&s, 0, sizeof(s));
      err = stat("new-dir", &s);
      assert(S_ISDIR(s.st_mode));
      assert(s.st_mode & S_IXUSR);
      assert(s.st_mode & S_IXGRP);
      assert(s.st_mode & S_IXOTH);

      err = stat("new-dir/test.txt", &s);
      assert(s.st_mode & S_IXUSR);
      assert(s.st_mode & S_IXGRP);
      assert(s.st_mode & S_IXOTH);

      puts("done");
    }

    int main(int argc, char * argv[]) {
      setup();
      test();
      return 0;
    }
    '''
    self.setup_nodefs_test()
    self.do_run(src)

  @parameterized({
    '': ([],),
    'wasm2js': (['-sWASM=0'],),
    'wasm2js_fallback': (['-sWASM=2'],),
  })
  def test_add_js_function(self, args):
    self.set_setting('INVOKE_RUN', 0)
    self.set_setting('WASM_ASYNC_COMPILATION', 0)
    self.set_setting('ALLOW_TABLE_GROWTH')
    self.set_setting('EXPORTED_RUNTIME_METHODS', ['callMain'])
    self.cflags += args + ['--post-js', test_file('interop/test_add_function_post.js')]

    print('basics')
    self.do_runf_out_file('interop/test_add_function.cpp')

    print('with ALLOW_TABLE_GROWTH=0')
    self.set_setting('ALLOW_TABLE_GROWTH', 0)
    expected = 'Unable to grow wasm table'
    if '-sWASM=0' in args:
      # in wasm2js the error message doesn't come from the VM, but from our
      # emulation code. when ASSERTIONS are enabled we show a clear message, but
      # in optimized builds we don't waste code size on that, and the JS engine
      # shows a generic error.
      expected = 'wasmTable.grow is not a function'

    self.do_runf('interop/test_add_function.cpp', expected, assert_returncode=NON_ZERO)

    print('- with table growth')
    self.set_setting('ALLOW_TABLE_GROWTH')
    # enable costly assertions to verify correct table behavior
    self.set_setting('ASSERTIONS', 2)
    self.do_runf_out_file('interop/test_add_function.cpp', interleaved_output=False, cflags=['-DGROWTH'])

  @parameterized({
    'memory64_wasm_function': (True, True),
    'wasm_function': (False, True),
    'memory64': (True, False),
    '': (False, False),
  })
  def test_add_js_function_bigint(self, memory64, wasm_function):
    if memory64:
      self.require_wasm64()

    if not wasm_function:
      create_file('pre.js', 'delete WebAssembly.Function;')
      self.cflags.append('--pre-js=pre.js')

    self.set_setting('ALLOW_TABLE_GROWTH')
    create_file('main.c', r'''
      #include <emscripten.h>
      #include <assert.h>

      EM_JS_DEPS(deps, "$addFunction");

      typedef long long (functype)(long long);

      int main() {
        functype* f = (functype *)EM_ASM_INT({
          return addFunction((num) => {
              return num + 4294967296n;
          }, 'jj');
        });
        assert(f(26) == 26 + 4294967296);
        assert(f(493921253191) == 493921253191 + 4294967296);
      }
    ''')

    self.do_runf('main.c', '')

  @parameterized({
    '': ([],),
    'lz4': (['-sLZ4'],),
    'pthread': (['-g', '-pthread', '-Wno-experimental', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'],),
  })
  def test_preload_module(self, args):
    if '-pthread' in args:
      self.require_pthreads()
    # TODO(sbc): This test is copyied from test_browser.py.  Perhaps find a better way to
    # share code between them.
    create_file('library.c', r'''
      #include <stdio.h>
      int library_func() {
        return 42;
      }
    ''')
    self.run_process([EMCC, 'library.c', '-sSIDE_MODULE', '-o', 'tmp.so'] + args)
    create_file('main.c', r'''
      #include <assert.h>
      #include <dlfcn.h>
      #include <stdio.h>
      #include <sys/stat.h>
      #include <emscripten.h>
      #include <emscripten/threading.h>

      int main() {
        // Check the file exists in the VFS
        struct stat statbuf;
        assert(stat("/library.so", &statbuf) == 0);

        // Check that it was preloaded.
        // The preloading actually only happens on the main thread where the filesystem
        // lives.  On worker threads the module object is shared via sharedModules.
        if (emscripten_is_main_runtime_thread()) {
          int found = EM_ASM_INT(
            return preloadedWasm['/library.so'] !== undefined;
          );
          assert(found);
        } else {
          int found = EM_ASM_INT(
            err('sharedModules:', sharedModules);
            return sharedModules['/library.so'] !== undefined;
          );
          assert(found);
        }
        void *lib_handle = dlopen("/library.so", RTLD_NOW);
        assert(lib_handle);
        typedef int (*voidfunc)();
        voidfunc x = (voidfunc)dlsym(lib_handle, "library_func");
        assert(x);
        assert(x() == 42);
        printf("done\n");
        return 0;
      }
    ''')
    expected = 'done\n'
    if '-pthread' in args:
      expected = "sharedModules: { '/library.so': Module [WebAssembly.Module] {} }\ndone\n"
    self.do_runf('main.c', expected, cflags=['-sMAIN_MODULE=2', '--preload-file', 'tmp.so@library.so', '--use-preload-plugins'] + args)

  @requires_pthreads
  def test_standalone_whole_archive(self):
    self.do_runf_out_file('hello_world.c', cflags=['-sSTANDALONE_WASM', '-pthread', '-Wl,--whole-archive', '-lstandalonewasm', '-Wl,--no-whole-archive'])

  @also_with_standalone_wasm(impure=True)
  def test_console_out(self):
    self.do_other_test('test_console_out.c', regex=True)

  @requires_wasm64
  def test_explicit_target(self):
    def is_64(path):
      with webassembly.Module(path) as wasm:
        m = wasm.get_memories()[0]
        return m.limits.flags & webassembly.LIMITS_IS_64
      assert False

    self.build('hello_world.c', cflags=['-target', 'wasm32'])
    self.assertFalse(is_64('hello_world.wasm'))

    self.build('hello_world.c', cflags=['-target', 'wasm64'])
    self.assertTrue(is_64('hello_world.wasm'))

    self.build('hello_world.c', cflags=['-target', 'wasm64-unknown-emscripten'])
    self.assertTrue(is_64('hello_world.wasm'))

    self.build('hello_world.c', cflags=['--target=wasm32'])
    self.assertFalse(is_64('hello_world.wasm'))

    self.build('hello_world.c', cflags=['--target=wasm64'])
    self.assertTrue(is_64('hello_world.wasm'))

    self.build('hello_world.c', cflags=['--target=wasm64-unknown-emscripten'])
    self.assertTrue(is_64('hello_world.wasm'))

    # Test -m32 and -m64 flags
    self.build('hello_world.c', cflags=['-m64'])
    self.assertTrue(is_64('hello_world.wasm'))

    self.build('hello_world.c', cflags=['-m64', '-m32'])
    self.assertFalse(is_64('hello_world.wasm'))

    self.assert_fail([EMCC, test_file('hello_world.c'), '-target', 'wasm32', '-sMEMORY64'], 'emcc: error: wasm32 target is not compatible with -sMEMORY64')
    self.assert_fail([EMCC, test_file('hello_world.c'), '-target', 'wasm32', '-m64'], 'emcc: error: wasm32 target is not compatible with -sMEMORY64')

    self.assert_fail([EMCC, test_file('hello_world.c'), '--target=arm64'], 'emcc: error: unsupported target: arm64 (emcc only supports wasm64-unknown-emscripten and wasm32-unknown-emscripten')

  def test_quick_exit(self):
    self.do_other_test('test_quick_exit.c')

  def test_no_minify(self):
    # Test that comments are preserved with `--minify=0` is used, even in `-Oz` builds.
    # This allows the output of emscripten to be run through the closure compiler as
    # as a separate build step.
    create_file('pre.js', '''
    /**
     * This comment should be preserved
     */
    console.log('hello');
    ''')
    comment = 'This comment should be preserved'

    self.run_process([EMCC, test_file('hello_world.c'), '--pre-js=pre.js', '-Oz'])
    content = read_file('a.out.js')
    self.assertNotContained(comment, content)

    self.run_process([EMCC, test_file('hello_world.c'), '--pre-js=pre.js', '-Oz', '--minify=0'])
    content = read_file('a.out.js')
    self.assertContained(comment, content)

  def test_no_minify_and_later_closure(self):
    # test that running closure after --minify=0 works
    self.run_process([EMCC, test_file('hello_libcxx.cpp'), '-O2', '--minify=0'])
    temp = building.closure_compiler('a.out.js',
                                     advanced=True,
                                     extra_closure_args=['--formatting', 'PRETTY_PRINT'])
    shutil.copy(temp, 'closured.js')
    self.assertContained('Hello, world!', self.run_js('closured.js'))

  def test_table_base(self):
    create_file('test.c', r'''
    #include <stdio.h>
    int main() {
      printf("addr = %p\n", &printf);
    }''')
    self.do_runf('test.c', 'addr = 0x1\n')
    self.do_runf('test.c', 'addr = 0x400\n', cflags=['-sTABLE_BASE=1024'])

  def test_webidl_empty(self):
    create_file('test.idl', '')
    self.run_process([WEBIDL_BINDER, 'test.idl', 'glue'])
    self.assertExists('glue.cpp')
    self.assertExists('glue.js')
    self.emcc('glue.cpp', ['-c', '-Wall', '-Werror'])

  def test_noExitRuntime(self):
    onexit_called = 'onExit called'
    create_file('pre.js', f'Module.onExit = () => console.log("${onexit_called}");\n')
    self.cflags += ['--pre-js=pre.js']
    self.set_setting('EXIT_RUNTIME')

    # Normally, with EXIT_RUNTIME set we expect onExit to be called.
    output = self.do_runf_out_file('hello_world.c')
    self.assertContained(onexit_called, output)

    # However, if we set `Module.noExitRuntime = true`, then it should
    # not be called.
    create_file('noexit.js', 'Module.noExitRuntime = true;\n')
    output = self.do_runf_out_file('hello_world.c', cflags=['--pre-js=noexit.js'])
    self.assertNotContained(onexit_called, output)

    # Setting the internal `noExitRuntime` after startup should have the
    # same effect.
    create_file('noexit_oninit.js', 'Module.preRun = () => { noExitRuntime = true; }')
    output = self.do_runf_out_file('hello_world.c', cflags=['--pre-js=noexit_oninit.js'])
    self.assertNotContained(onexit_called, output)

  def test_noExitRuntime_deps(self):
    create_file('lib.js', r'''
addToLibrary({
  foo__deps: ['$noExitRuntime'],
  foo: () => {
    return 0;
  }
});
''')
    expected = 'error: noExitRuntime cannot be referenced via __deps mechanism'
    self.assert_fail([EMCC, test_file('hello_world.c'), '--js-library=lib.js', '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=foo'], expected)

  def test_hello_world_argv(self):
    self.do_runf('hello_world_argv.c', 'Hello, world! (1)')

  def test_strict_closure(self):
    self.emcc('hello_world.c', ['-sSTRICT', '--closure=1'])

  def test_closure_debug(self):
    self.emcc('hello_world.c', ['-sSTRICT', '--closure=1', '-g'])
    src = read_file('a.out.js')
    self.assertContained('$Module$$', src)

  def test_arguments_global(self):
    self.emcc('hello_world_argv.c', ['-sENVIRONMENT=web', '-sSTRICT', '--closure=1', '-O2'])

  @parameterized({
    'no_std_exp':   (['-DEMMALLOC_NO_STD_EXPORTS'],),
    # When we let emmalloc build with the standard exports like malloc,
    # emmalloc == malloc.
    'with_std_exp': (['-DTEST_EMMALLOC_IS_MALLOC'],),
  })
  def test_emmalloc_in_addition(self, args):
    # Test that we can use emmalloc in addition to another malloc impl. When we
    # build emmalloc using -DEMMALLOC_NO_STD_EXPORTS it will not export malloc
    # etc., and only provide the emmalloc_malloc etc. family of functions that
    # we can use.
    emmalloc = path_from_root('system', 'lib', 'emmalloc.c')
    self.do_runf('other/test_emmalloc_in_addition.c', 'done\n', cflags=[emmalloc] + args)

  def test_unused_destructor(self):
    self.do_runf('other/test_unused_destructor.c', cflags=['-flto', '-O2'])
    # Verify that the string constant in the destructor is not included in the binary
    self.assertNotIn(b'hello from dtor', read_binary('test_unused_destructor.wasm'))

  def test_strip_all(self):
    def has_debug_section(wasm):
      with webassembly.Module('hello_world.wasm') as wasm:
        return wasm.get_custom_section('.debug_info') is not None

    # Use -O2 to ensure wasm-opt gets run
    self.cflags += ['-g', '-O2']

    # First, verify that `-g` produces a debug section
    self.do_runf_out_file('hello_world.c')
    self.assertTrue(has_debug_section('hello_world.wasm'))

    # Test `-Wl,--strip-all` will strip the debug section, but that the
    # the target features section is preserved so that later phases
    # (e.g. wasm-opt) can read it.
    self.do_runf_out_file('hello_world.c', cflags=['-Wl,--strip-all', '-pthread'])
    self.assertFalse(has_debug_section('hello_world.wasm'))

    # Verify that `-Wl,-s` and `-s` also both have the same effect
    self.do_runf_out_file('hello_world.c', cflags=['-Wl,-s', '-pthread'])
    self.assertFalse(has_debug_section('hello_world.wasm'))

    self.do_runf_out_file('hello_world.c', cflags=['-s', '-pthread'])
    self.assertFalse(has_debug_section('hello_world.wasm'))

  def test_embind_no_duplicate_symbols(self):
    # Embind implementation lives almost entirely in headers, which have special rules
    # around symbol deduplication during linking. Ensure that including Embind headers
    # in two different object files doesn't lead to linking errors.
    create_file('a.cpp', '#include <emscripten/bind.h>')
    create_file('b.cpp', '#include <emscripten/bind.h>')
    self.run_process([EMXX, '-std=c++23', '-lembind', 'a.cpp', 'b.cpp'])

  def test_embind_no_exceptions(self):
    # Test disabling exceptions and redefining try/catch with preprocessor
    # macros.
    create_file('a.cpp', '#define try\n#define catch if (0)\n#include <emscripten/bind.h>')
    self.run_process([EMXX, '-fno-exceptions', '-std=c++23', '-lembind', 'a.cpp'])

  def test_embind_optional_val_no_bind(self):
    # Ensure passing std::optional to emscripten::val works if <emscripten/bind.h>
    # was not included in the compilation unit using val.
    self.do_runf('embind/test_optional_val_main.cpp', 'done\n', cflags=['-lembind', test_file('embind/test_optional_val_lib.cpp')])

  def test_no_pthread(self):
    self.do_runf_out_file('hello_world.c', cflags=['-pthread', '-no-pthread'])
    self.assertExists('hello_world.js')
    self.assertNotContained('new Worker(', read_file('hello_world.js'))

  def test_sysroot_includes_first(self):
    self.do_other_test('test_stdint_limits.c', cflags=['-iwithsysroot/include'])

  def test_force_filesystem_error(self):
    expected = 'emcc: error: `-sFORCE_FILESYSTEM` cannot be used with `-sFILESYSTEM=0`'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sFILESYSTEM=0', '-sFORCE_FILESYSTEM'], expected)

  def test_aligned_alloc(self):
    self.do_runf('test_aligned_alloc.c', '',
                 cflags=['-Wno-non-power-of-two-alignment'])

  def test_erf(self):
    self.do_other_test('test_erf.c')

  def test_math_hyperbolic(self):
    self.do_other_test('test_math_hyperbolic.c')

  def test_frexp(self):
    self.do_other_test('test_frexp.c')

  def test_fcvt(self):
    self.do_other_test('test_fcvt.cpp')

  def test_llrint(self):
    self.do_other_test('test_llrint.c')

  @also_with_llvm_libc
  def test_strings(self):
    self.do_other_test('test_strings.c', args=['wowie', 'too', '74'])

  def test_strcmp_uni(self):
    self.do_other_test('test_strcmp_uni.c')

  def test_strndup(self):
    self.do_other_test('test_strndup.c')

  def test_errar(self):
    self.do_other_test('test_errar.c')

  def test_wcslen(self):
    self.do_other_test('test_wcslen.c')

  def test_regex(self):
    self.do_other_test('test_regex.c')

  def test_isdigit_l(self):
    self.do_other_test('test_isdigit_l.cpp')

  def test_iswdigit(self):
    self.do_other_test('test_iswdigit.c')

  def test_complex(self):
    self.do_other_test('test_complex.c')

  def test_dynamic_cast(self):
    self.do_other_test('test_dynamic_cast.cpp')

  def test_dynamic_cast_b(self):
    self.do_other_test('test_dynamic_cast_b.cpp')

  def test_dynamic_cast_2(self):
    self.do_other_test('test_dynamic_cast_2.cpp')

  def test_no_input_files(self):
    self.assert_fail([EMCC, '-c'], 'clang: error: no input files')

    self.assert_fail([EMCC], 'emcc: error: no input files')

  def test_embind_negative_enum_values(self):
    # Test if negative enum values are printed correctly and not overflown to
    # large values when CAN_ADDRESS_2GB is true.
    # Test both default enums and enums as string
    src = r'''
      #include <stdio.h>
      #include <emscripten.h>
      #include <emscripten/bind.h>

      using namespace emscripten;

      int main() {
        EM_ASM(
        console.log(Module.value.neg.value);
        console.log(Module.value.zero.value);
        console.log(Module.value.pos.value);
        );
      }

      enum class value {
        neg = -1,
        zero = 0,
        pos = 1,
      };

      EMSCRIPTEN_BINDINGS(utility) {
        enum_<value>("value")
          .value("neg", value::neg)
          .value("zero", value::zero)
          .value("pos", value::pos);
      }
    '''
    expected = '-1\n0\n1\n'
    self.do_run(src, expected_output=expected,
                cflags=['-lembind', '-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=4GB'])

  @crossplatform
  def test_no_extra_output(self):
    # Run this build command first to warm the cache (since caching can produce stdout).
    self.run_process([EMCC, '-c', test_file('hello_world.c')])
    output = self.run_process([EMCC, '-c', test_file('hello_world.c')], stdout=PIPE, stderr=STDOUT).stdout
    self.assertEqual(output, '')

  @crossplatform
  def test_shell_cmd_with_quotes(self):
    file = test_file('hello_world.c')
    # Run this build command first to warm the cache (since caching can produce stdout).
    self.run_process([EMCC, '-c', file])

    # Verify that "emcc" is found in the PATH correctly in shell scripts (shell=True).
    # (Specifically when quotes are around the "emcc" command itself).
    # See https://github.com/microsoft/terminal/issues/15212
    old_path = os.environ['PATH']
    new_path = old_path + os.pathsep + os.path.dirname(EMCC)
    cmd = f'"emcc" -c "{file}"'
    print('running cmd:', cmd)
    with env_modify({'PATH': new_path}):
      proc = subprocess.run(cmd, capture_output=True, text=True, shell=True, check=True)
      # There is currently a bug in the windows .bat files that leads to stdout
      # not being empty in this case.
      # See https://github.com/emscripten-core/emscripten/pull/25416
      if not WINDOWS:
        self.assertEqual(proc.stdout, '')
      self.assertEqual(proc.stderr, '')

  def test_browser_too_old(self):
    expected = 'emcc: error: MIN_CHROME_VERSION older than 85 is not supported'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sMIN_CHROME_VERSION=10'], expected)

  def test_js_only_settings(self):
    err = self.run_process([EMCC, test_file('hello_world.c'), '-o', 'foo.wasm', '-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=emscripten_get_heap_max'], stderr=PIPE).stderr
    self.assertContained('emcc: warning: DEFAULT_LIBRARY_FUNCS_TO_INCLUDE is only valid when generating JavaScript output', err)

  def test_uuid(self):
    # We run this test in Node/SPIDERMONKEY and browser environments because we
    # try to make use of high quality crypto random number generators such as
    # `getRandomValues` or `randomFillSync`.
    self.do_runf('test_uuid.c', cflags=['-O2', '--closure=1', '-luuid'])

    # Check that test.js compiled with --closure 1 contains `randomFillSync` and
    # `getRandomValues`
    js_out = read_file('test_uuid.js')
    self.assertContained('.randomFillSync(', js_out)
    self.assertContained('.getRandomValues(', js_out)

  def test_mimalloc_no_asan(self):
    # See https://github.com/emscripten-core/emscripten/issues/23288#issuecomment-2571648258
    expected = 'error: mimalloc is not compatible with -fsanitize=address'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sMALLOC=mimalloc', '-fsanitize=address'], expected)

  @crossplatform
  def test_js_preprocess_pre_post(self):
    create_file('pre.js', '''
    #preprocess
    #if ASSERTIONS
    console.log('assertions enabled')
    #else
    console.log('assertions disabled')
    #endif
    ''')
    create_file('post.js', '''
    #preprocess
    console.log({{{ POINTER_SIZE }}});
    ''')
    self.cflags += ['--pre-js', 'pre.js', '--post-js', 'post.js']
    self.do_runf('hello_world.c', 'assertions enabled\n4', cflags=['-sASSERTIONS=1'])
    self.do_runf('hello_world.c', 'assertions disabled\n4', cflags=['-sASSERTIONS=0'])
    self.assertNotContained('#preprocess', read_file('hello_world.js'))

  @crossplatform
  def test_js_preprocess_huge_file(self):
    # Check that huge files can be preprocessed.  We once had issues with files
    # larger then 64k being sent over stdout using python's subprocess.
    huge_js = '#preprocess\nfunction hugeFunc() {\n'
    huge_js += 100000 * '  console.log("huge function");\n'
    huge_js += '}\n'
    create_file('huge.js', huge_js)
    assert len(huge_js) > (1024 * 1024)
    self.do_runf_out_file('hello_world.c', cflags=['--pre-js=huge.js'])
    self.assertContained('function hugeFunc', read_file('hello_world.js'))

  @with_both_compilers
  def test_use_port_errors(self, compiler):
    stderr = self.expect_fail([compiler, test_file('hello_world.c'), '--use-port=invalid', '-o', 'out.js'])
    self.assertFalse(os.path.exists('out.js'))
    self.assertContained('error with `--use-port=invalid` | invalid port name: `invalid`', stderr)
    stderr = self.expect_fail([compiler, test_file('hello_world.c'), '--use-port=sdl2:opt1=v1', '-o', 'out.js'])
    self.assertFalse(os.path.exists('out.js'))
    self.assertContained('error with `--use-port=sdl2:opt1=v1` | no options available for port `sdl2`', stderr)
    stderr = self.expect_fail([compiler, test_file('hello_world.c'), '--use-port=sdl2_image:format=jpg', '-o', 'out.js'])
    self.assertFalse(os.path.exists('out.js'))
    self.assertContained('error with `--use-port=sdl2_image:format=jpg` | `format` is not supported', stderr)
    stderr = self.expect_fail([compiler, test_file('hello_world.c'), '--use-port=sdl2_image:formats', '-o', 'out.js'])
    self.assertFalse(os.path.exists('out.js'))
    self.assertContained('error with `--use-port=sdl2_image:formats` | `formats` is missing a value', stderr)
    stderr = self.expect_fail([compiler, test_file('hello_world.c'), '--use-port=sdl2_image:formats=jpg:formats=png', '-o', 'out.js'])
    self.assertFalse(os.path.exists('out.js'))
    self.assertContained('error with `--use-port=sdl2_image:formats=jpg:formats=png` | duplicate option `formats`', stderr)

  def test_mimalloc_headers(self):
    src = r'''
      #include <mimalloc.h>

      int main() {
          mi_option_enable(mi_option_verbose);
          return 0;
      }
    '''
    self.do_run(src, cflags=['-sMALLOC=mimalloc'])

  def test_SUPPORT_BIG_ENDIAN(self):
    # Just a simple build-only test for now
    self.run_process([EMCC, '-sSUPPORT_BIG_ENDIAN', test_file('hello_world.c')])

  @parameterized({
    'noexcept': ['-fno-exceptions'],
    'default': [],
    'except': ['-sDISABLE_EXCEPTION_CATCHING=0'],
    'except_wasm': ['-fwasm-exceptions', '-sWASM_LEGACY_EXCEPTIONS=0'],
    'except_wasm_legacy': ['-fwasm-exceptions', '-sWASM_LEGACY_EXCEPTIONS'],
  })
  def test_std_promise_link(self, *args):
    # Regression test for a bug where std::promise's destructor caused a link
    # error with __cxa_init_primary_exception when no exception argument was
    # given (which defaults to -fignore-exceptions)
    create_file('src.cpp', r'''
      #include <future>
      int main() {
        std::promise<int> p;
        return 0;
      }
    ''')
    self.run_process([EMXX, 'src.cpp', '-pthread'] + list(args))

  def test_stack_protector(self):
    self.do_other_test('test_stack_protector.c', cflags=['-fstack-protector'], assert_returncode=NON_ZERO)

  def test_save_temp(self):
    self.run_process([EMCC, '--save-temps', test_file('hello_world.c')])
    self.assertExists('a.out.js')
    # clang itself takes care of creating these three
    self.assertExists('hello_world.i')
    self.assertExists('hello_world.s')
    self.assertExists('hello_world.bc')
    # emcc takes care of creating the .o
    self.assertExists('hello_world.o')

  def test_extra_struct_info(self):
    self.run_process([PYTHON, path_from_root('tools/gen_struct_info.py'), test_file('other/test_extra_struct_info.json'), '-o', 'generated.json', '-I', test_file('other')])
    stderr = self.run_process([EMCC, test_file('hello_world.c'), '--js-library', test_file('other/test_extra_struct_info.js')], stderr=PIPE).stderr
    self.assertContained('(before) AF_INET=2', stderr)
    self.assertContained('(after) AF_INET=42', stderr)

  @also_with_wasmfs
  def test_fs_writev_partial_write(self):
    self.set_setting('FORCE_FILESYSTEM')
    self.do_runf_out_file('fs/test_writev_partial_write.c')

  def test_fs_lzfs(self):
    # generate data
    ensure_dir('subdir')
    create_file('file1.txt', '0123456789' * (1024 * 128))
    create_file('subdir/file2.txt', '1234567890' * (1024 * 128))
    random_data = bytearray(random.randint(0, 255) for x in range(1024 * 128 * 10 + 1))
    random_data[17] = ord('X')
    create_file('file3.txt', random_data, binary=True)

    # compress in emcc, -sLZ4 tells it to tell the file packager
    self.do_runf('fs/test_lz4fs.c', cflags=['-sLZ4', '--preload-file', 'file1.txt', '--preload-file', 'subdir/file2.txt', '--preload-file', 'file3.txt'])
    self.assertEqual(os.path.getsize('file1.txt') + os.path.getsize('subdir/file2.txt') + os.path.getsize('file3.txt'), 3 * 1024 * 128 * 10 + 1)
    self.assertLess(os.path.getsize('test_lz4fs.data'), (3 * 1024 * 128 * 10) / 2)  # over half is gone

  @requires_v8
  @parameterized({
    '': [[]],
    'O3': [['-O3']],
  })
  def test_fp16(self, args):
    self.v8_args += ['--wasm-fp16']
    self.do_runf('test_fp16.c', cflags=['-msimd128', '-mfp16', '-mrelaxed-simd', '-sENVIRONMENT=shell'] + args)

  def test_embool(self):
    self.do_other_test('test_embool.c')

  @requires_rust
  def test_rust_integration_basics(self):
    copytree(test_file('rust/basics'), '.')
    self.run_process(['cargo', 'build', '--target=wasm32-unknown-emscripten'])
    lib = 'target/wasm32-unknown-emscripten/debug/libbasics.a'
    self.assertExists(lib)

    create_file('main.cpp', '''
    extern "C" void say_hello();
    int main() {
       say_hello();
       return 0;
    }''')
    self.do_runf('main.cpp', 'Hello from rust!', cflags=[lib])

  @requires_rust
  def test_wasm_bindgen_integration(self):
    copytree(test_file('rust/bindgen_integration'), '.')
    self.run_process(['cargo', 'add', 'wasm-bindgen'])
    self.run_process(['cargo', 'build'])
    lib = 'target/wasm32-unknown-emscripten/debug/libbindgen_integration.a'
    self.assertExists(lib)

    create_file('empty.c', '')
    create_file('post.js', '''
      Module.onRuntimeInitialized = () => out(Module.rs_add(17, 25));
    ''')

    self.run_process(['cargo', 'install', 'wasm-bindgen-cli'])
    self.do_runf('empty.c', '42', cflags=[lib, '-sWASM_BINDGEN', '--post-js=post.js', '-lexports.js'])

  @requires_rust
  @requires_dev_dependency('typescript')
  def test_wasm_bindgen_tsd_multi_return(self):
    copytree(test_file('rust/bindgen_integration'), '.')
    create_file('src/lib.rs', '''
      use wasm_bindgen::prelude::*;
      #[wasm_bindgen]
      pub fn multi_value_return() -> Result<i32, JsValue> {
          Ok(42)
      }
    ''')
    self.run_process(['cargo', 'add', 'wasm-bindgen'])
    self.run_process(['cargo', 'build'])
    lib = 'target/wasm32-unknown-emscripten/debug/libbindgen_integration.a'
    create_file('empty.c', '')
    self.run_process(['cargo', 'install', 'wasm-bindgen-cli'])
    self.run_process([EMCC, 'empty.c', '--emit-tsd', 'test_multi.d.ts', '-sWASM_BINDGEN', '-o', 'test_multi.js'] + [lib] + self.get_cflags())
    actual = read_file('test_multi.d.ts')
    self.assertContained("multi_value_return(): [number, number, number];", actual)

  def test_relative_em_cache(self):
    with env_modify({'EM_CACHE': 'foo'}):
      self.assert_fail([EMCC, '-c', test_file('hello_world.c')], 'emcc: error: environment variable EM_CACHE must be an absolute path: foo')

  @crossplatform
  def test_create_cache_directory(self):
    if config.FROZEN_CACHE:
      self.skipTest("test doesn't work with frozen cache")

    # Test that the cache directory (including parent directories) is
    # created on demand.
    with env_modify({'EM_CACHE': os.path.abspath('foo/bar')}):
      self.run_process([EMCC, '-c', test_file('hello_world.c')])
      self.assertExists('foo/bar/sysroot_install.stamp')

    if not WINDOWS and os.getuid() != 0:
      # Test that we generate a nice error when we cannot create the cache
      # because it is in a read-only location.
      # For some reason this doesn't work on windows, at least not in CI.
      # This also does not work when running as root since os.access will
      # always return true for os.W_OK.
      os.mkdir('rodir')
      os.chmod('rodir', 0o444)
      self.assertFalse(os.access('rodir', os.W_OK))
      with env_modify({'EM_CACHE': os.path.abspath('rodir/foo')}):
        err = self.expect_fail([EMCC, '-c', test_file('hello_world.c')])
        self.assertContained('emcc: error: unable to create cache directory', err)

  def test_cxx20_modules(self):
    # Test basic use of modules by user code
    create_file('Hello.cppm', r'''
    module;
    #include <iostream>
    export module Hello;
    export void hello() {
      std::cout << "Hello Module!\n";
    }
    ''')

    create_file('main.cpp', '''
    import Hello;
    int main() {
      hello();
      return 0;
    }
    ''')

    self.run_process([EMXX, '-std=c++20', 'Hello.cppm', '--precompile', '-o', 'Hello.pcm'])
    # Use explict `-fmodule-file=`
    self.do_runf('main.cpp', 'Hello Module!', cflags=['-std=c++20', '-fmodule-file=Hello=Hello.pcm', 'Hello.pcm'])
    # Same again but with `-fprebuilt-module-path=`
    self.do_runf('main.cpp', 'Hello Module!', cflags=['-std=c++20', '-fprebuilt-module-path=.', 'Hello.pcm'])

  def test_cxx20_modules_std_headers(self):
    # Test import <header_name> usage
    create_file('main.cpp', r'''
    import <iostream>;

    int main() {
      std::cout << "Hello Module!\n";
      return 0;
    }
    ''')
    self.do_runf('main.cpp', 'Hello Module!', cflags=['-std=c++20', '-fmodules'])

  def test_invalid_export_name(self):
    create_file('main.c', r'''
      #include <emscripten.h>
      #include <stdio.h>

      __attribute__((export_name("my.func"))) int myfunc() { return 42; }

      int main() {
        int rtn = EM_ASM_INT(return Module['_my.func']());
        printf("got: %d\n", rtn);

        int rtn2 = EM_ASM_INT(return wasmExports['my.func']());
        printf("got2: %d\n", rtn2);
        return 0;
      }
    ''')
    expected = 'emcc: error: export name is not a valid JS symbol: "_my.func".  Use `Module` or `wasmExports` to access this symbol'
    self.assert_fail([EMCC, '-Werror', 'main.c'], expected)

    # With warning suppressed the above program should work.
    self.do_runf('main.c', 'got: 42\ngot2: 42\n', cflags=['-Wno-js-compiler'])

    # When we are generating only wasm and not JS we don't need exports to
    # be valid JS symbols.
    self.run_process([EMCC, '-Werror', 'main.c', '--no-entry', '-o', 'out.wasm'])

  def test_export_with_dollarsign(self):
    # GCC (and clang) and JavaScript both allow $ in symbol names
    create_file('main.c', r'''
      #include <emscripten.h>
      #include <stdio.h>

      EMSCRIPTEN_KEEPALIVE int my$func() { return 42; }

      int main() {
        int rtn = EM_ASM_INT(return _my$func());
        printf("got: %d\n", rtn);
        return 0;
      }''')
    self.do_runf('main.c', 'got: 42\n')

  @also_with_modularize
  def test_instantiate_wasm(self):
    create_file('pre.js', '''
      Module['instantiateWasm'] = (imports, successCallback) => {
        var wasmFile = findWasmBinary();
        getWasmBinary(wasmFile).then((bytes) => {
          WebAssembly.instantiate(bytes, imports).then((res) => {
            out('wasm instantiation succeeded');
            Module['testWasmInstantiationSucceeded'] = 1;
            successCallback(res.instance, res.module);
          });
        });
        return {}; // Compiling asynchronously, no exports.
      }''')
    # Test with ASYNCIFY here to ensure that that wasmExports gets set to the wrapped version of the wasm exports.
    self.do_runf('test_manual_wasm_instantiate.c', cflags=['--pre-js=pre.js', '-sASYNCIFY', '-DASYNCIFY_ENABLED'])

  def test_late_module_api_assignment(self):
    # When sync instantiation is used (or when async/await is used in MODULARIZE mode) certain
    # Module properties cannot be assigned in `--post-js` code because its too late by the time
    # it runs.
    for prop in ('onRuntimeInitialized', 'postRun', 'preRun', 'preInit'):
      create_file('post.js', f'Module.{prop} = () => console.log("will never fire since assigned too late")')
      expected = f"Aborted(Attempt to modify `Module.{prop}` after it has already been processed.  This can happen, for example, when code is injected via '--post-js' rather than '--pre-js')"
      self.do_runf('hello_world.c', expected, cflags=['--post-js=post.js', '-sWASM_ASYNC_COMPILATION=0'], assert_returncode=NON_ZERO)

  @crossplatform
  @requires_dev_dependency('rollup')
  def test_rollup(self):
    copytree(test_file('rollup_node'), '.')
    self.run_process([EMCC, test_file('hello_world.c'), '-sEXPORT_ES6', '-sEXIT_RUNTIME', '-sENVIRONMENT=node', '-sMODULARIZE', '-o', 'hello.mjs'])
    self.run_process(shared.get_npm_cmd('rollup') + ['--config'])
    # Rollup doesn't bundle the wasm file by default so we need to copy it
    # TODO(sbc): Look into plugins that do bundling.
    shutil.copy('hello.wasm', 'dist/')
    self.assertContained('Hello, world!', self.run_js('dist/bundle.mjs'))

  def test_rlimit(self):
    self.do_other_test('test_rlimit.c', cflags=['-O1'])

  @parameterized({
    '': (False,),
    'es6': (True,),
  })
  def test_mainScriptUrlOrBlob(self, es6):
    ext = "js"
    args = ['-sEXIT_RUNTIME', '-sINCOMING_MODULE_JS_API=mainScriptUrlOrBlob', '-sPROXY_TO_PTHREAD', '-pthread']
    if es6:
      ext = "mjs"
      args += ['-sEXPORT_ES6', '--extern-post-js', test_file('modularize_post_js.js')]
    outfile = ('a.out.%s' % ext)
    # Use `foo.js` instead of the current script name when creating new threads
    create_file('pre.js', 'Module = { mainScriptUrlOrBlob: "./foo.%s" }' % ext)

    self.run_process([EMCC, test_file('hello_world.c'), '--pre-js=pre.js', '-o', outfile] + args)

    # First run without foo.[m]js present to verify that the pthread creation fails
    err = self.run_js(outfile, assert_returncode=NON_ZERO)
    # Different engines have slightly different error message syntax.
    expected = [
      r'Cannot find module.*foo\.m?js',
      r'BuildMessage: ModuleNotFound resolving "\./foo\.m?js" \(entry point\)',
    ]
    self.assertContained(expected, err, regex=True)

    # Now create foo.[m]js and the program should run as expected.
    shutil.copy(outfile, ('foo.%s' % ext))
    self.assertContained('Hello, world!', self.run_js(outfile))

  @parameterized({
    '': ([],),
    'node': (['-sENVIRONMENT=node'],),
    'pthread': (['-pthread', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'],),
  })
  def test_locate_file_abspath(self, args):
    # Verify that `scriptDirectory` is an absolute path
    create_file('pre.js', '''
      Module['locateFile'] = (fileName, scriptDirectory) => {
        assert(require('path')['isAbsolute'](scriptDirectory), `scriptDirectory (${scriptDirectory}) should be an absolute path`);
        return scriptDirectory + fileName;
      };
      ''')
    self.do_runf_out_file('hello_world.c', cflags=['--pre-js', 'pre.js'] + args)

  @parameterized({
    '': ([],),
    'node': (['-sENVIRONMENT=node'],),
  })
  def test_locate_file_abspath_esm(self, args):
    # Verify that `scriptDirectory` is an absolute path when `EXPORT_ES6`
    create_file('pre.js', '''
      Module['locateFile'] = (fileName, scriptDirectory) => {
        assert(require('path')['isAbsolute'](scriptDirectory), `scriptDirectory (${scriptDirectory}) should be an absolute path`);
        return scriptDirectory + fileName;
      };
      ''')
    self.do_runf_out_file('hello_world.c',
                          output_suffix='.mjs',
                          cflags=['--pre-js', 'pre.js',
                                  '--extern-post-js', test_file('modularize_post_js.js')] + args)

  @requires_node_25
  def test_js_base64_api(self):
    self.node_args += ['--js_base_64']
    # JS_BASE64_API only has an effect when base64 is being used so we need to
    # disable SINGLE_FILE_BINARY_ENCODE for this test.
    self.cflags += ['-sSINGLE_FILE', '-sSINGLE_FILE_BINARY_ENCODE=0']
    self.do_runf_out_file('hello_world.c', output_basename='baseline')
    self.do_runf_out_file('hello_world.c', cflags=['-sJS_BASE64_API', '-Wno-experimental'])
    # We expect the resulting JS file to be smaller because it doesn't contain the
    # base64 decoding code
    baseline_size = os.path.getsize('baseline.js')
    js_api_size = os.path.getsize('hello_world.js')
    self.assertLess(js_api_size, baseline_size)

  @requires_v8
  def test_getentropy_d8(self):
    create_file('main.c', '''
      #include <assert.h>
      #include <unistd.h>

      int main() {
        char buf[100];
        assert(getentropy(buf, sizeof(buf)) == 0);
        return 0;
      }
    ''')

    msg = 'randomFill not supported on d8 unless --enable-os-system is passed'
    self.do_runf('main.c', msg, assert_returncode=1)
    self.v8_args += ['--enable-os-system']
    self.do_runf('main.c')

  @also_with_minimal_runtime
  def test_getentropy(self):
    # Regression test for `getentropy`/`random_get` returning a spurious
    # non-zero (errno) result for small requests.  A single-byte buffer is the
    # important case: if the JS implementation returns the filled buffer rather
    # than the `0` success code, the buffer coerces to its (non-zero) byte value
    # and the call appears to fail.
    create_file('main.c', r'''
      #include <assert.h>
      #include <stdio.h>
      #include <unistd.h>

      int main() {
        for (int i = 0; i < 256; i++) {
          unsigned char buf[1];
          assert(getentropy(buf, sizeof(buf)) == 0);
        }
        printf("done\n");
        return 0;
      }
    ''')
    self.do_runf('main.c', 'done\n')

  def test_em_js_bool_macro_expansion(self):
    # Normally macros like `true` and `false` are not expanded inside
    # of `EM_JS` or `EM_ASM` blocks.  However, in the case that an
    # additional macro layer is added these will be expanded and we want
    # to make sure the resulting expansion doesn't break the expectations
    # of JS code.
    create_file('main.c', '''
      #include <emscripten.h>

      #ifndef true
      #error "expected true to be defined"
      #endif

      #define EM_JS_MACROS(ret, func_name, args, body...)    \
        EM_JS(ret, func_name, args, body)

      EM_JS_MACROS(void, check_bool_type, (void), {
        if (typeof true !== "boolean") {
          throw new Error(`typeof true is ${typeof true} not boolean`);
        }
      })

      int main() {
        check_bool_type();
        return 0;
      }
    ''')
    self.do_runf('main.c')

  def test_getifaddrs(self):
    self.do_other_test('test_getifaddrs.c')

  @parameterized({
    'web': ('web',),
    'node': ('node',),
  })
  def test_unsupported_min_version_when_unsupported_env(self, env):
    create_file('pre.js', '''
      #preprocess
      var MIN_NODE_VERSION = {{{ MIN_NODE_VERSION }}};
      var MIN_CHROME_VERSION = {{{ MIN_CHROME_VERSION }}};
      var MIN_SAFARI_VERSION = {{{ MIN_SAFARI_VERSION }}};
      var MIN_FIREFOX_VERSION = {{{ MIN_FIREFOX_VERSION }}};
    ''')
    self.emcc('hello_world.c', [f'-sENVIRONMENT={env}', '--pre-js=pre.js'])
    src = read_file('a.out.js')
    unsupported = 0x7FFFFFFF
    self.assertContainedIf(f'var MIN_NODE_VERSION = {unsupported};', src, env == 'web')
    self.assertContainedIf(f'var MIN_CHROME_VERSION = {unsupported};', src, env == 'node')
    self.assertContainedIf(f'var MIN_SAFARI_VERSION = {unsupported};', src, env == 'node')
    self.assertContainedIf(f'var MIN_FIREFOX_VERSION = {unsupported};', src, env == 'node')

  @parameterized({
    'web': ('web',),
    'node': ('node',),
  })
  @parameterized({
    'pthread': (['-pthread'],),
    'wasm_workers': (['-sWASM_WORKERS'],),
  })
  def test_automatic_env_worker(self, env, args):
    self.emcc('hello_world.c', [f'-sENVIRONMENT={env}'] + args)

  def test_libcxx_errors(self):
    create_file('main.cpp', '''
    #include <thread>
    void func() {
    }

    int main() {
      std::thread t(func);
      t.join();
    }
    ''')

    # Since we are building without -pthread the thread constructor will fail,
    # and in debug mode at least we expect to see the error message from libc++
    expected = 'system_error was thrown in -fno-exceptions mode with error 138 and message "thread constructor failed"'
    self.do_runf('main.cpp', expected, assert_returncode=NON_ZERO)

  def test_parsetools_make_removed_fs_assert(self):
    """Test that parseTools.mjs `makeRemovedFSAssert()` works as intended.

    It creates a stub when a builtin library isn't included, but not when
    it is.
    """
    removed_fs_assert_content = "IDBFS is no longer included by default"

    self.emcc('hello_world.c', ['-o', 'hello_world.js'])
    self.assertContained(removed_fs_assert_content, read_file('hello_world.js'))

    self.emcc('hello_world.c', ['-lidbfs.js', '-o', 'hello_world.js'])
    self.assertNotContained(removed_fs_assert_content, read_file('hello_world.js'))

  @crossplatform
  @requires_git_checkout
  def test_install(self):
    self.run_process([PYTHON, path_from_root('tools/install.py'), 'newdir'], env=shared.env_with_node_in_path())
    self.assertExists('newdir/emcc.py')
    # Some files, such as as maintenance tools should not be part of the
    # install.
    self.assertNotExists('newdir/tools/maint/')

  @requires_node
  @also_with_pthreads
  @parameterized({
    '': ([],),
    'closure': (['--closure=1'],),
  })
  def test_TextDecoder(self, args):
    self.do_runf_out_file('hello_world.c', cflags=args)
    td_with_fallback = os.path.getsize('hello_world.js')
    print('td_with_fallback:\t%s' % td_with_fallback)

    self.do_runf_out_file('hello_world.c', cflags=args + ['-sTEXTDECODER=2'])
    td_without_fallback = os.path.getsize('hello_world.js')
    print('td_without_fallback:\t%s' % td_without_fallback)

    # td_with_fallback should always be largest of all three in terms of code side
    self.assertGreater(td_with_fallback, td_without_fallback)

  def test_TextDecoder_invalid(self):
    expected = '#error "TEXTDECODER must be either 1 or 2"'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sTEXTDECODER=0'], expected)

    expected = '#error "TEXTDECODER must be either 1 or 2"'
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sTEXTDECODER=3'], expected)

  # Verify that strings can be decoded when the heap is backed by a resizable
  # ArrayBuffer (ALLOW_MEMORY_GROWTH + GROWABLE_ARRAYBUFFERS).
  # TextDecoder.decode() rejects views of resizable ArrayBuffers, so we must
  # pass it a copy of the data instead.  Node's TextDecoder happens to accept
  # such views, so emulate the browser behaviour in a --pre-js.
  # See https://github.com/emscripten-core/emscripten/issues/27241
  @requires_node_26
  @parameterized({
    '': ([],),
    'textdecoder_2': (['-sTEXTDECODER=2'],),
  })
  def test_TextDecoder_resizable_buffer(self, args):
    create_file('pre.js', '''
      var realDecode = TextDecoder.prototype.decode;
      TextDecoder.prototype.decode = function(data) {
        if (data && data.buffer && (data.buffer.resizable || data.buffer.growable)) {
          throw new TypeError('The provided ArrayBuffer value must not be resizable');
        }
        return realDecode.call(this, data);
      };
    ''')
    create_file('main.c', r'''
      #include <emscripten/console.h>

      int main() {
        // Long enough (> 16 bytes) that UTF8ToString on the string takes the
        // TextDecoder path.
        emscripten_out("the quick brown fox jumps over the lazy dog");
        return 0;
      }
    ''')
    self.do_runf('main.c', 'the quick brown fox jumps over the lazy dog',
                 cflags=['--pre-js=pre.js', '-sALLOW_MEMORY_GROWTH', '-sGROWABLE_ARRAYBUFFERS=1'] + args)

  def test_reallocarray(self):
    self.do_other_test('test_reallocarray.c')

  def test_create_preloaded_file(self):
    # Test that the FS.createPreloadedFile API works
    create_file('post.js', "FS.createPreloadedFile('/', 'someotherfile.txt', 'somefile.txt', true, false);")
    create_file('somefile.txt', 'hello')
    create_file('main.c', r'''
      #include <stdio.h>
      #include <assert.h>
      #include <sys/stat.h>

      int main() {
        struct stat buf;
        int rtn = stat("someotherfile.txt", &buf);
        assert(rtn == 0);
        printf("done\n");
        return 0;
      }''')
    self.do_runf('main.c', 'done\n', cflags=['-sFORCE_FILESYSTEM', '--post-js=post.js'])

  @crossplatform
  def test_empath_split(self):
    create_file('main.cpp', r'''
      #include <iostream>
      void foo();
      int main() {
        std::cout << "main" << std::endl;
        foo();
        return 0;
      }
    ''')
    create_file('foo.cpp', r'''
      #include <iostream>
      void foo() { std::cout << "foo" << std::endl; }
    ''')
    create_file('path_list.txt', r'''
      myapp:
      main.cpp
      foo.cpp

      lib1:
      /emsdk/emscripten/system

      lib2:
      /emsdk/emscripten/system/lib/libc/musl
      /emsdk/emscripten/system/lib/libcxx
    ''')

    self.run_process([EMCC, 'main.cpp', 'foo.cpp', '-gsource-map', '-g2', '-o', 'test.js'])
    empath_split_cmd = [empath_split, 'test.wasm', 'path_list.txt', '-g', '-o', 'test_primary.wasm', '--out-prefix=test_', '-v']
    out = self.run_process(empath_split_cmd, stdout=PIPE).stdout

    # Check if functions are correctly assigned and split with the specified
    # paths. When one path contains another, the inner path should take its
    # functions first, and the rest is split with the outer path.
    def has_defined_function(file, func):
      func = ''.join('\\' + c if c in {'(', ')'} else c for c in func)
      self.run_process([common.WASM_DIS, file, '-o', 'test.wast'])
      pattern = re.compile(r'^\s*\(\s*func\s+\$("?)' + func + r'\1[\s\(\)]', flags=re.MULTILINE)
      return pattern.search(utils.read_file('test.wast')) is not None

    # main.cpp
    self.assertTrue(has_defined_function('test_myapp.wasm', '__original_main'))
    # foo.cpp
    self.assertTrue(has_defined_function('test_myapp.wasm', 'foo()'))
    # /emsdk/emscripten/system
    self.assertTrue(has_defined_function('test_lib1.wasm', '__abort_message'))
    self.assertTrue(has_defined_function('test_lib1.wasm', 'pthread_cond_wait'))
    # /emsdk/emscripten/system/lib/libc/musl
    self.assertTrue(has_defined_function('test_lib2.wasm', 'strcmp'))
    # /emsdk/emscripten/system/lib/libcxx
    self.assertTrue(has_defined_function('test_lib2.wasm', 'std::__2::ios_base::getloc() const'))
    self.assertTrue(has_defined_function('test_lib2.wasm', 'std::uncaught_exceptions()'))

    # When --preserve-manifest is NOT given, the files should be deleted
    match = re.search(r'wasm-split(?:\.exe)?\s+.*--manifest\s+(\S+)', out)
    manifest = match.group(1)
    self.assertNotExists(manifest)

    # When --preserve-manifest is given, the files should be preserved
    out = self.run_process(empath_split_cmd + ['--preserve-manifest'], stdout=PIPE, stderr=subprocess.DEVNULL).stdout
    match = re.search(r'wasm-split(?:\.exe)?\s+.*--manifest\s+(\S+)', out)
    manifest = match.group(1)
    self.assertExists(manifest)
    delete_file(manifest)

    # Check --print-sources option
    out = self.run_process([empath_split, 'test.wasm', '--print-sources'], stdout=PIPE).stdout
    self.assertIn('main.cpp', out)
    self.assertIn('foo.cpp', out)
    self.assertIn('/emsdk/emscripten/system/lib/libc/musl/src/string/strcmp.c', out)

  def test_binaryen_fast_math(self):
    # Use a simple input; contents don't matter for -v flag inspection
    err = self.run_process([EMCC, test_file('hello_world.c'), '-v', '-O2', '-ffast-math'], stderr=PIPE).stderr
    self.assertContained('--fast-math', err)

    err_no_fast = self.run_process([EMCC, test_file('hello_world.c'), '-v', '-O2'], stderr=PIPE).stderr
    self.assertNotContained('--fast-math', err_no_fast)

  def test_linkable(self):
    # This setting is due for removal:
    # https://github.com/emscripten-core/emscripten/issues/25262
    self.do_runf_out_file('hello_world.c', cflags=['-Wno-deprecated', '-sLINKABLE'])

  # Tests encoding of all byte pairs for binary encoding in SINGLE_FILE mode.
  @parameterized({
    '': ('',),
    'strict': ('"use strict";',),
  })
  def test_binary_encode(self, extra):
    # Encode values 0 .. 65535 into test data
    test_data = bytearray(struct.pack('<' + 'H' * 65536, *range(65536)))
    write_binary('data.tmp', test_data)
    binary_encoded = binary_encode('data.tmp')
    test_js = extra + '''
var u16 = new Uint16Array(binaryDecode(src).buffer);
for(var i = 0; i < 65536; ++i)
  if (u16[i] != i) throw i;
console.log('OK');'''
    write_file('test.js', read_file(path_from_root('src/binaryDecode.js')) + '\nvar src = ' + binary_encoded + ';\n' + test_js)
    self.assertContained('OK', self.run_js('test.js'))

  @crossplatform
  def test_executable_output_default(self):
    # By default there should not be a #! line included
    self.run_process([EMCC, test_file('hello_world.c')])
    self.assertFalse(read_file('a.out.js').startswith('#!'))
    self.assertNotContained('#!/usr/bin/env node', read_file('a.out.js'))

  @crossplatform
  @no_windows('depends on UNIX shbang feature')
  @parameterized({
    '': (['-sEXECUTABLE', '-o', 'out.js'],),
    # -sEXECUTABLE is implied if output filename has no extension
    'no_extension': (['-o', 'foo'],),
    # -sEXECUTABLE is implied for `.out` extension, e.g. a.out.
    'a_out': (['-o', 'a.out'],),
  })
  def test_executable_output(self, args):
    js_filename = args[-1]
    self.run_process([EMCC, test_file('hello_world.c')] + args)
    self.assertContained('#!/usr/bin/env node', read_file(js_filename))
    output = self.run_process([os.path.abspath(js_filename)], stdout=PIPE).stdout
    self.assertContained('Hello, world!', output)

  def test_executable_requires_node(self):
    self.assert_fail([EMCC, test_file('hello_world.c'), '-sEXECUTABLE', '-sENVIRONMENT=web'], 'emcc: error: EXECUTABLE requires `node` in ENVRIONMENT')

  def test_logReadFiles(self):
    create_file('test.txt', 'hello')
    self.cflags += ['--preload-file=test.txt', '-sINCOMING_MODULE_JS_API=logReadFiles']

    output = self.do_runf('checksummer.c', args=['test.txt'])
    self.assertNotContained('read file:', output)

    create_file('pre.js', 'Module.logReadFiles = 1;')
    output = self.do_runf('checksummer.c', args=['test.txt'], cflags=['--pre-js=pre.js'])
    self.assertContained('read file: /test.txt', output)

  def test_deprecated_settings(self):
    err = self.run_process([EMCC, '-sMEMORY64', test_file('hello_world.c')], stderr=PIPE).stderr
    self.assertContained('emcc: warning: MEMORY64 is deprecated (prefer the standard -m64 or --target=wasm64 flags). Please open a bug if you have a continuing need for this setting [-Wdeprecated]', err)

    err = self.run_process([EMCC, '-sUSE_PTHREADS', test_file('hello_world.c')], stderr=PIPE).stderr
    self.assertContained('emcc: warning: USE_PTHREADS is deprecated (prefer the standard -pthread flag). Please open a bug if you have a continuing need for this setting [-Wdeprecated]', err)

  def test_cross_origin_storage(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-sCROSS_ORIGIN_STORAGE', '-o', 'hello.js'])
    js = read_file('hello.js')
    m = re.search(r"algorithm:\s*'SHA-256',\s*value:\s*'([0-9a-f]{64})'", js)
    self.assertTrue(m, 'could not find a 64-char hex hash value in JS output')
    embedded_hash = m.group(1)
    expected_hash = hashlib.sha256(open('hello.wasm', 'rb').read()).hexdigest()
    self.assertEqual(embedded_hash, expected_hash,
                     'embedded wasm hash does not match actual .wasm SHA-256')
    self.run_process([EMCC, test_file('hello_world.c'), '-o', 'hello.js'])
    js = read_file('hello.js')
    self.assertNotContained('crossOriginStorage', js)
    self.assertNotContained("Module['wasmHash']", js)

  def test_cross_origin_storage_errors(self):
    self.assert_fail([EMCC, test_file('hello_world.c'),
                      '-sCROSS_ORIGIN_STORAGE',
                      '-sENVIRONMENT=node'],
                     'CROSS_ORIGIN_STORAGE requires a web environment')
    self.assert_fail([EMCC, test_file('hello_world.c'),
                      '-sCROSS_ORIGIN_STORAGE',
                      '-sSINGLE_FILE'],
                     'CROSS_ORIGIN_STORAGE is not compatible with SINGLE_FILE')
    self.assert_fail([EMCC, test_file('hello_world.c'),
                      '-sCROSS_ORIGIN_STORAGE',
                      '-sWASM_ASYNC_COMPILATION=0'],
                     'CROSS_ORIGIN_STORAGE is not compatible with WASM_ASYNC_COMPILATION=0')
    self.assert_fail([EMCC, test_file('hello_world.c'),
                      '-sCROSS_ORIGIN_STORAGE',
                      '-sSIDE_MODULE'],
                     'CROSS_ORIGIN_STORAGE is not compatible with SIDE_MODULE')

  def test_cross_origin_storage_origins(self):
    self.run_process([EMCC, test_file('hello_world.c'),
                      '-sCROSS_ORIGIN_STORAGE',
                      '-sCROSS_ORIGIN_STORAGE_ORIGINS=https://app.example.com,https://api.example.com',
                      '-o', 'hello.js'])
    js = read_file('hello.js')
    self.assertContained('"https://app.example.com"', js)
    self.assertContained('"https://api.example.com"', js)
    self.assertNotContained("origins: '*'", js)
    self.run_process([EMCC, test_file('hello_world.c'),
                      '-sCROSS_ORIGIN_STORAGE',
                      '-sCROSS_ORIGIN_STORAGE_ORIGINS=[]',
                      '-o', 'hello.js'])
    js = read_file('hello.js')
    self.assertContained('{ create: true }', js)
    self.assertNotContained('origins:', js)
    self.assert_fail(
      [EMCC, test_file('hello_world.c'),
       '-sCROSS_ORIGIN_STORAGE',
       '-sCROSS_ORIGIN_STORAGE_ORIGINS=*,https://example.com'],
      "'*' must not be mixed with explicit origins")
    self.assert_fail(
      [EMCC, test_file('hello_world.c'),
       '-sCROSS_ORIGIN_STORAGE',
       '-sCROSS_ORIGIN_STORAGE_ORIGINS=http://example.com'],
      'is not a valid HTTPS origin')
    self.assert_fail(
      [EMCC, test_file('hello_world.c'),
       '-sCROSS_ORIGIN_STORAGE',
       '-sCROSS_ORIGIN_STORAGE_ORIGINS=https://example.com/path'],
      'is not a valid HTTPS origin')

  @disabled('https://github.com/emscripten-core/emscripten/issues/27189')
  @requires_dev_dependency('rollup')
  @requires_dev_dependency('prettier')
  def test_dead_code_esm(self):
    self.run_process([EMCC, test_file('hello_world.c'), '-sEXPORT_ES6', '-O2', '-o', 'hello.mjs'])
    rollup_cmd = shared.get_npm_cmd('rollup') + [
        'hello.mjs', '--format', 'es', '--file', 'hello.rolled.mjs',
        '--external', 'node:module',
    ]
    self.run_process(rollup_cmd)
    prettier_cmd = shared.get_npm_cmd('prettier')
    self.run_process(prettier_cmd + ['hello.mjs', '--write'])
    self.run_process(prettier_cmd + ['hello.rolled.mjs', '--write'])
    original = read_file('hello.mjs')
    rolled = read_file('hello.rolled.mjs')

    # We need to normalize the JS before diffing because Rollup's code generator
    # introduces structural and syntax changes that Prettier (which only formats
    # without changing the AST) cannot resolve. Specifically:
    # 1. Export syntax: Rollup normalizes `export default Module;` to `export { Module as default };`.
    # 2. Empty blocks: Rollup simplifies empty blocks like `else {}` to `else;`.
    # 3. Comments: Rollup strips comments or moves them around. We strip them completely to focus
    #    strictly on executable logic.
    def normalize(code):
      # Strip comments to avoid diffs from comment displacement/removal
      code = re.sub(r'//.*', '', code)
      code = re.sub(r'/\*.*?\*/', '', code, flags=re.DOTALL)
      # Normalize export syntax to a standard form
      code = re.sub(r'export\s+default\s+(\w+);', r'export { \1 as default };', code)
      code = re.sub(r'export\s*\{\s*(\w+)\s*as\s*default\s*\}\s*;', r'export {\1 as default};', code)
      # Normalize empty else blocks
      code = re.sub(r'else\s*\{\s*\}', 'else;', code)

      # Normalize whitespace and empty lines
      lines = [line.rstrip() for line in code.splitlines() if line.strip()]
      return '\n'.join(lines)

    normalized_original = normalize(original)
    normalized_rolled = normalize(rolled)

    # Write both normalized files to CWD so they can be inspected manually on failure
    write_file('hello.normalized.mjs', normalized_original)
    write_file('hello.rolled.normalized.mjs', normalized_rolled)

    self.assertTextDataIdentical(normalized_original, normalized_rolled,
                                 fromfile='hello.normalized.mjs', tofile='hello.rolled.normalized.mjs')
