//===-- Tests for TSS API like pthread_setspecific etc. -------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/pthread/pthread_create.h"
#include "src/pthread/pthread_exit.h"
#include "src/pthread/pthread_getspecific.h"
#include "src/pthread/pthread_join.h"
#include "src/pthread/pthread_key_create.h"
#include "src/pthread/pthread_key_delete.h"
#include "src/pthread/pthread_setspecific.h"
#include "test/IntegrationTest/test.h"

#include <pthread.h>

static constexpr int THREAD_DATA_INITVAL = 0x1234;
static constexpr int THREAD_DATA_FINIVAL = 0x4321;
static constexpr int THREAD_RUN_VAL = 0x600D;

static int child_thread_data = THREAD_DATA_INITVAL;
static int main_thread_data = THREAD_DATA_INITVAL;

static pthread_key_t key;
static void dtor(void *data) {
  auto *v = reinterpret_cast<int *>(data);
  *v = THREAD_DATA_FINIVAL;
}

// Used to test that we don't call the destructor when the mapped value in NULL.
static void dtor_failure(void *) { ASSERT_TRUE(false); }

static void *func(void *obj) {
  ASSERT_EQ(LIBC_NAMESPACE::pthread_setspecific(key, &child_thread_data), 0);
  int *d = reinterpret_cast<int *>(LIBC_NAMESPACE::pthread_getspecific(key));
  ASSERT_TRUE(d != nullptr);
  ASSERT_EQ(&child_thread_data, d);
  ASSERT_EQ(*d, THREAD_DATA_INITVAL);
  *reinterpret_cast<int *>(obj) = THREAD_RUN_VAL;
  return nullptr;
}

static void *func_null_val(void *) {
  // null value, we should not call dtor
  ASSERT_EQ(LIBC_NAMESPACE::pthread_setspecific(key, nullptr), 0);
  ASSERT_EQ(LIBC_NAMESPACE::pthread_getspecific(key), nullptr);
  return nullptr;
}

static void standard_usage_test() {
  ASSERT_EQ(LIBC_NAMESPACE::pthread_key_create(&key, &dtor), 0);
  ASSERT_EQ(LIBC_NAMESPACE::pthread_setspecific(key, &main_thread_data), 0);
  int *d = reinterpret_cast<int *>(LIBC_NAMESPACE::pthread_getspecific(key));
  ASSERT_TRUE(d != nullptr);
  ASSERT_EQ(&main_thread_data, d);
  ASSERT_EQ(*d, THREAD_DATA_INITVAL);

  pthread_t th;
  int arg = 0xBAD;
  ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr, &func, &arg), 0);
  void *retval = &child_thread_data; // Init to some non-nullptr val.
  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
  ASSERT_EQ(retval, nullptr);
  ASSERT_EQ(arg, THREAD_RUN_VAL);
  ASSERT_EQ(child_thread_data, THREAD_DATA_FINIVAL);
  ASSERT_EQ(LIBC_NAMESPACE::pthread_key_delete(key), 0);
}

static void null_value_test() {
  pthread_t th;
  ASSERT_EQ(LIBC_NAMESPACE::pthread_key_create(&key, &dtor_failure), 0);
  ASSERT_EQ(
      LIBC_NAMESPACE::pthread_create(&th, nullptr, &func_null_val, nullptr), 0);
  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, nullptr), 0);
  ASSERT_EQ(LIBC_NAMESPACE::pthread_key_delete(key), 0);
}

TEST_MAIN() {
  standard_usage_test();
  null_value_test();
  return 0;
}
