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

"""Runs the pthreads test from the upstream posixtest suite in:
   ./tests/third_party/posixtestsuite
See
   https://github.com/emscripten-core/posixtestsuite
"""

import glob
import os

from runner import RunnerCore, path_from_root
from tools import config
from tools.shared import EMCC
import test_posixtest_browser

testsuite_root = path_from_root('tests', 'third_party', 'posixtestsuite')


class posixtest(RunnerCore):
  """Run the suite under node (and in parallel)

  This class get populated dynamically below.
  """
  pass


def filter_tests(all_tests):
  pthread_tests = [t for t in all_tests if t.startswith('pthread_')]
  # filter out some tests we don't support
  pthread_tests = [t for t in pthread_tests if not t.startswith('pthread_atfork')]
  pthread_tests = [t for t in pthread_tests if not t.startswith('pthread_sigmask')]
  return pthread_tests


def get_pthread_tests():
  # For now, we don't require the submodule to exist.  In this case we just report
  # no tests
  pthread_test_root = os.path.join(testsuite_root, 'conformance', 'interfaces')
  if not os.path.exists(pthread_test_root):
    print('posixtestsuite not found (run git submodule update --init?)')
    return []
  pthread_tests = filter_tests(os.listdir(pthread_test_root))
  pthread_tests = [os.path.join(pthread_test_root, t) for t in pthread_tests]
  return pthread_tests


engine = config.NODE_JS + ['--experimental-wasm-threads', '--experimental-wasm-bulk-memory']

# Mark certain tests as unsupported
# TODO: Investigate failing semaphores tests.
unsupported = {
  'test_pthread_atfork_1_1': 'fork() and multiple processes are not supported',
  'test_pthread_atfork_1_2': 'fork() and multiple processes are not supported',
  'test_pthread_atfork_2_1': 'fork() and multiple processes are not supported',
  'test_pthread_atfork_2_2': 'fork() and multiple processes are not supported',
  'test_pthread_atfork_3_2': 'fork() and multiple processes are not supported',
  'test_pthread_atfork_4_1': 'fork() and multiple processes are not supported',
  'test_pthread_attr_setinheritsched_2_3': 'scheduling policy/parameters are not supported',
  'test_pthread_attr_setinheritsched_2_4': 'scheduling policy/parameters are not supported',
  'test_pthread_attr_setschedparam_1_3': 'scheduling policy/parameters are not supported',
  'test_pthread_attr_setschedparam_1_4': 'scheduling policy/parameters are not supported',
  'test_pthread_attr_setschedpolicy_4_1': 'scheduling policy/parameters are not supported',
  'test_pthread_barrierattr_getpshared_2_1': 'shm_open and shm_unlink are not supported',
  'test_pthread_barrier_wait_3_1': 'signals are not supported',
  'test_pthread_barrier_wait_3_2': 'signals are not supported',
  'test_pthread_cancel_5_2': 'signals are not supported',
  'test_pthread_cond_broadcast_1_2': 'lacking necessary mmap() support',
  'test_pthread_cond_broadcast_2_3': 'lacking necessary mmap() support',
  'test_pthread_cond_broadcast_4_2': 'signals are not supported',
  'test_pthread_cond_destroy_2_1': 'lacking necessary mmap() support',
  'test_pthread_cond_init_1_2': 'clock_settime() is not supported',
  'test_pthread_cond_init_1_3': 'lacking necessary mmap() support',
  'test_pthread_cond_init_2_2': 'clock_settime() is not supported',
  'test_pthread_cond_init_4_1': 'fork() and multiple processes are not supported',
  'test_pthread_cond_init_4_2': 'signals are not supported',
  'test_pthread_cond_signal_1_2': 'lacking necessary mmap() support',
  'test_pthread_cond_signal_4_2': 'signals are not supported',
  'test_pthread_cond_timedwait_2_4': 'lacking necessary mmap() support',
  'test_pthread_cond_timedwait_2_7': 'lacking necessary mmap() support',
  'test_pthread_cond_timedwait_4_2': 'lacking necessary mmap() support',
  'test_pthread_cond_wait_2_2': 'lacking necessary mmap() support',
  'test_pthread_cond_wait_4_1': 'fork() and multiple processes are not supported',
  'test_pthread_create_1_5': 'semaphores are not supported',
  'test_pthread_create_1_6': 'semaphores are not supported',
  'test_pthread_create_8_1': 'signals are not supported',
  'test_pthread_create_8_2': 'signals are not supported',
  'test_pthread_create_10_1': 'signals are not supported',
  'test_pthread_create_14_1': 'signals are not supported',
  'test_pthread_create_15_1': 'signals are not supported',
  'test_pthread_detach_4_3': 'signals are not supported',
  'test_pthread_equal_2_1': 'signals are not supported',
  'test_pthread_exit_6_1': 'lacking necessary mmap() support',
  'test_pthread_exit_6_2': 'semaphores are not supported',
  'test_pthread_getschedparam_1_3': 'scheduling policy/parameters are not supported',
  'test_pthread_getschedparam_4_1': 'signals are not supported',
  'test_pthread_join_6_3': 'signals are not supported',
  'test_pthread_kill_1_1': 'signals are not supported',
  'test_pthread_kill_1_2': 'signals are not supported',
  'test_pthread_kill_8_1': 'signals are not supported',
  'test_pthread_mutexattr_getprioceiling_1_2': 'pthread_mutexattr_setprioceiling is not supported',
  'test_pthread_mutexattr_getprotocol_1_2': 'pthread_mutexattr_setprotocol is not supported',
  'test_pthread_mutexattr_setprioceiling_1_1': 'pthread_mutexattr_setprioceiling is not supported',
  'test_pthread_mutexattr_setprioceiling_3_1': 'pthread_mutexattr_setprioceiling is not supported',
  'test_pthread_mutexattr_setprioceiling_3_2': 'pthread_mutexattr_setprioceiling is not supported',
  'test_pthread_mutexattr_setprotocol_1_1': 'setting pthread_mutexattr_setprotocol to a nonzero value is not supported',
  'test_pthread_mutexattr_settype_2_1': 'interrupting pthread_mutex_lock wait via SIGALRM is not supported',
  'test_pthread_mutex_getprioceiling_1_1': 'pthread_mutex_getprioceiling is not supported',
  'test_pthread_mutex_init_1_2': 'signals are not supported',
  'test_pthread_mutex_init_5_1': 'fork() and multiple processes are not supported',
  'test_pthread_mutex_lock_3_1': 'signals are not supported',
  'test_pthread_mutex_lock_5_1': 'signals are not supported',
  'test_pthread_mutex_trylock_1_2': 'lacking necessary mmap() support',
  'test_pthread_mutex_trylock_2_1': 'lacking necessary mmap() support',
  'test_pthread_mutex_trylock_4_2': 'lacking necessary mmap() support',
  'test_pthread_mutex_trylock_4_3': 'signals are not supported',
  'test_pthread_once_6_1': 'signals are not supported',
  'test_pthread_rwlockattr_getpshared_2_1': 'shm_open and shm_unlink are not supported',
  'test_pthread_rwlock_rdlock_2_1': 'thread priorities not supported, cannot test rwlocking in priority order',
  'test_pthread_rwlock_rdlock_2_2': 'thread priorities not supported, cannot test rwlocking in priority order',
  'test_pthread_rwlock_rdlock_4_1': 'signals are not supported',
  'test_pthread_rwlock_timedrdlock_6_1': 'signals are not supported',
  'test_pthread_rwlock_timedrdlock_6_2': 'signals are not supported',
  'test_pthread_rwlock_timedwrlock_6_1': 'signals are not supported',
  'test_pthread_rwlock_timedwrlock_6_2': 'signals are not supported',
  'test_pthread_rwlock_unlock_3_1': 'thread priorities not supported, cannot test rwlocking in priority order',
  'test_pthread_rwlock_wrlock_2_1': 'signals are not supported',
  'test_pthread_setschedparam_5_1': 'signals are not supported',
  'test_pthread_spin_init_2_1': 'shm_open and shm_unlink are not supported',
  'test_pthread_spin_init_2_2': 'shm_open and shm_unlink are not supported',
  'test_pthread_spin_lock_1_1': 'signals are not supported',
  'test_pthread_spin_lock_3_1': 'signals are not supported',
}

# Mark certain tests as flaky, which may sometimes fail.
# TODO invesigate these tests.
flaky = {
  'test_pthread_cond_signal_1_1': 'flaky: https://github.com/emscripten-core/emscripten/issues/13283',
}

# Mark certain tests as not passing
disabled = {
  **unsupported,
  **flaky,
  'test_pthread_create_11_1': 'never returns',
  'test_pthread_barrier_wait_2_1': 'never returns',
  'test_pthread_cond_timedwait_2_6': 'never returns',
  'test_pthread_cond_timedwait_4_3': 'never returns',
  'test_pthread_attr_setscope_5_1': 'internally skipped (PTS_UNTESTED)',
  'test_pthread_cond_wait_2_3': 'never returns',
  'test_pthread_create_5_1': 'never returns',
  'test_pthread_exit_1_2': 'never returns',
  'test_pthread_exit_2_2': 'never returns',
  'test_pthread_exit_3_2': 'never returns',
  'test_pthread_exit_4_1': 'never returns',
  'test_pthread_getcpuclockid_1_1': 'never returns',
  'test_pthread_key_create_1_2': 'never returns',
  'test_pthread_rwlock_rdlock_1_1': 'fails with "main: Unexpected thread state"',
  'test_pthread_rwlock_timedrdlock_1_1': 'fails with "main: Unexpected thread state"',
  'test_pthread_rwlock_timedrdlock_3_1': 'fails with "main: Unexpected thread state"',
  'test_pthread_rwlock_timedrdlock_5_1': 'fails with "main: Unexpected thread state"',
  'test_pthread_rwlock_timedwrlock_1_1': 'fails with "main: Unexpected thread state"',
  'test_pthread_rwlock_timedwrlock_3_1': 'fails with "main: Unexpected thread state"',
  'test_pthread_rwlock_timedwrlock_5_1': 'fails with "main: Unexpected thread state"',
  'test_pthread_rwlock_wrlock_1_1': 'fails with "main: Unexpected thread state"',
  'test_pthread_rwlock_trywrlock_1_1': 'fails with "main: Unexpected thread state"',
  'test_pthread_spin_destroy_3_1': 'never returns',
  'test_pthread_spin_init_4_1': 'never returns',
}


def make_test(name, testfile, browser):

  def f(self):
    if name in disabled:
      self.skipTest(disabled[name])
    args = ['-I' + os.path.join(testsuite_root, 'include'),
            '-Werror',
            '-Wno-format-security',
            '-Wno-int-conversion',
            '-sUSE_PTHREADS',
            '-sEXIT_RUNTIME',
            '-sTOTAL_MEMORY=268435456',
            '-sPTHREAD_POOL_SIZE=40']
    if browser:
      # Only are only needed for browser tests of the was btest
      # injects headers using `-include` flag.
      args += ['-Wno-macro-redefined', '-D_GNU_SOURCE']
      self.btest(testfile, args=args, expected='exit:0')
    else:
      self.run_process([EMCC, testfile, '-o', 'test.js'] + args)
      self.run_js('test.js', engine=engine)

  return f


for testdir in get_pthread_tests():
  basename = os.path.basename(testdir)
  for test_file in glob.glob(os.path.join(testdir, '*.c')):
    if not os.path.basename(test_file)[0].isdigit():
      continue
    test_suffix = os.path.splitext(os.path.basename(test_file))[0]
    test_suffix = test_suffix.replace('-', '_')
    test_name = 'test_' + basename + '_' + test_suffix
    setattr(posixtest, test_name, make_test(test_name, test_file, browser=False))
    setattr(test_posixtest_browser.posixtest_browser, test_name, make_test(test_name, test_file, browser=True))
