/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <folly/synchronization/RelaxedAtomic.h>

#include <folly/portability/GTest.h>
#include <folly/synchronization/AtomicUtil.h>

static_assert( //
    std::is_same_v< //
        int,
        folly::atomic_value_type_t<folly::relaxed_atomic<int>>>);
static_assert( //
    std::is_same_v< //
        bool,
        folly::atomic_value_type_t<folly::relaxed_atomic<bool>>>);
static_assert( //
    std::is_same_v< //
        void*,
        folly::atomic_value_type_t<folly::relaxed_atomic<void*>>>);

class RelaxedAtomicTest : public testing::Test {};

TEST_F(RelaxedAtomicTest, deduce_bool) {
  folly::relaxed_atomic v{false};
  EXPECT_EQ(false, v.load());
  EXPECT_TRUE((std::is_same_v<bool, decltype(v)::value_type>));
}

TEST_F(RelaxedAtomicTest, deduce_int) {
  folly::relaxed_atomic v{0};
  EXPECT_EQ(0, v.load());
  EXPECT_TRUE((std::is_same_v<int, decltype(v)::value_type>));
}

TEST_F(RelaxedAtomicTest, deduce_float) {
  folly::relaxed_atomic v{0.f};
  EXPECT_EQ(0, v.load());
  EXPECT_TRUE((std::is_same_v<float, decltype(v)::value_type>));
}

TEST_F(RelaxedAtomicTest, deduce_ptr) {
  struct foo {};
  foo f;
  folly::relaxed_atomic v{&f};
  EXPECT_EQ(&f, v.load());
  EXPECT_TRUE((std::is_same_v<foo*, decltype(v)::value_type>));
}

template <typename AtomicType>
struct RelaxedAtomicBooleanTest : testing::Test {};

using RelaxedAtomicBooleanTestTypes = testing::Types<
    std::atomic<bool>,
    std::atomic<bool> volatile,
    folly::relaxed_atomic<bool>,
    folly::relaxed_atomic<bool> volatile>;
TYPED_TEST_SUITE(RelaxedAtomicBooleanTest, RelaxedAtomicBooleanTestTypes);

TYPED_TEST(RelaxedAtomicBooleanTest, isLockFree) {
  TypeParam v{true};
  EXPECT_TRUE(v.is_lock_free());
}

TYPED_TEST(RelaxedAtomicBooleanTest, operatorValue) {
  TypeParam v{true};
  EXPECT_EQ(true, v);
}

TYPED_TEST(RelaxedAtomicBooleanTest, load) {
  TypeParam v{true};
  EXPECT_EQ(true, v.load());
}

TYPED_TEST(RelaxedAtomicBooleanTest, operatorAssign) {
  TypeParam v{true};
  EXPECT_EQ(false, (v = false));
  EXPECT_EQ(false, v);
}

TYPED_TEST(RelaxedAtomicBooleanTest, store) {
  TypeParam v{true};
  EXPECT_EQ(false, (v.store(false), v));
}

TYPED_TEST(RelaxedAtomicBooleanTest, exchange) {
  TypeParam v{true};
  EXPECT_EQ(true, v.exchange(false));
  EXPECT_EQ(false, v);
}

TYPED_TEST(RelaxedAtomicBooleanTest, compareExchangeWeak) {
  TypeParam v{true};
  bool e = false;
  EXPECT_FALSE(v.compare_exchange_weak(e, false));
  EXPECT_TRUE(v.compare_exchange_weak(e, false));
  EXPECT_EQ(true, e);
  EXPECT_EQ(false, v);
}

TYPED_TEST(RelaxedAtomicBooleanTest, compareExchangeStrong) {
  TypeParam v{true};
  bool e = false;
  EXPECT_FALSE(v.compare_exchange_strong(e, false));
  EXPECT_TRUE(v.compare_exchange_strong(e, false));
  EXPECT_EQ(true, e);
  EXPECT_EQ(false, v);
}

template <typename AtomicType>
struct RelaxedAtomicPointerTest : testing::Test {};

using RelaxedAtomicPointerTestTypes = testing::Types<
    std::atomic<int*>,
    std::atomic<int*> volatile,
    folly::relaxed_atomic<int*>,
    folly::relaxed_atomic<int*> volatile>;
TYPED_TEST_SUITE(RelaxedAtomicPointerTest, RelaxedAtomicPointerTestTypes);

TYPED_TEST(RelaxedAtomicPointerTest, isLockFree) {
  int n[] = {-1};
  TypeParam v{n + 0};
  EXPECT_TRUE(v.is_lock_free());
}

TYPED_TEST(RelaxedAtomicPointerTest, operatorValue) {
  int n[] = {-1};
  TypeParam v{n + 0};
  EXPECT_EQ(-1, *v);
}

TYPED_TEST(RelaxedAtomicPointerTest, load) {
  int n[] = {-1};
  TypeParam v{n + 0};
  EXPECT_EQ(-1, *v.load());
}

TYPED_TEST(RelaxedAtomicPointerTest, operatorAssign) {
  int n[] = {-1, -2, -3};
  TypeParam v{n + 0};
  EXPECT_EQ(-3, *(v = n + 2));
  EXPECT_EQ(-3, *v);
}

TYPED_TEST(RelaxedAtomicPointerTest, store) {
  int n[] = {-1, -2, -3};
  TypeParam v{n + 0};
  EXPECT_EQ(-3, *(v.store(n + 2), v));
}

TYPED_TEST(RelaxedAtomicPointerTest, exchange) {
  int n[] = {-1, -2, -3};
  TypeParam v{n + 0};
  EXPECT_EQ(-1, *v.exchange(n + 2));
  EXPECT_EQ(-3, *v);
}

TYPED_TEST(RelaxedAtomicPointerTest, compareExchangeWeak) {
  int n[] = {-1, -2, -3};
  TypeParam v{n + 0};
  int* e = n + 1;
  EXPECT_FALSE(v.compare_exchange_weak(e, n + 2));
  EXPECT_TRUE(v.compare_exchange_weak(e, n + 2));
  EXPECT_EQ(-1, *e);
  EXPECT_EQ(-3, *v);
}

TYPED_TEST(RelaxedAtomicPointerTest, compareExchangeStrong) {
  int n[] = {-1, -2, -3};
  TypeParam v{n + 0};
  int* e = n + 1;
  EXPECT_FALSE(v.compare_exchange_strong(e, n + 2));
  EXPECT_TRUE(v.compare_exchange_strong(e, n + 2));
  EXPECT_EQ(-1, *e);
  EXPECT_EQ(-3, *v);
}

TYPED_TEST(RelaxedAtomicPointerTest, fetchAdd) {
  int n[] = {-1, -2, -3};
  TypeParam v{n + 0};
  EXPECT_EQ(-1, *v.fetch_add(2));
  EXPECT_EQ(-3, *v);
}

TYPED_TEST(RelaxedAtomicPointerTest, fetchSub) {
  int n[] = {-1, -2, -3};
  TypeParam v{n + 2};
  EXPECT_EQ(-3, *v.fetch_sub(2));
  EXPECT_EQ(-1, *v);
}

TYPED_TEST(RelaxedAtomicPointerTest, operatorIncrPre) {
  int n[] = {-1, -2, -3};
  TypeParam v{n + 1};
  EXPECT_EQ(-3, *++v);
  EXPECT_EQ(-3, *v);
}

TYPED_TEST(RelaxedAtomicPointerTest, operatorIncrPost) {
  int n[] = {-1, -2, -3};
  TypeParam v{n + 1};
  EXPECT_EQ(-2, *v++);
  EXPECT_EQ(-3, *v);
}

TYPED_TEST(RelaxedAtomicPointerTest, operatorDecrPre) {
  int n[] = {-1, -2, -3};
  TypeParam v{n + 1};
  EXPECT_EQ(-1, *--v);
  EXPECT_EQ(-1, *v);
}

TYPED_TEST(RelaxedAtomicPointerTest, operatorDecrPost) {
  int n[] = {-1, -2, -3};
  TypeParam v{n + 1};
  EXPECT_EQ(-2, *v--);
  EXPECT_EQ(-1, *v);
}

TYPED_TEST(RelaxedAtomicPointerTest, operatorAddAssign) {
  int n[] = {-1, -2, -3};
  TypeParam v{n + 0};
  EXPECT_EQ(-3, *(v += 2));
  EXPECT_EQ(-3, *v);
}

TYPED_TEST(RelaxedAtomicPointerTest, operatorSubAssign) {
  int n[] = {-1, -2, -3};
  TypeParam v{n + 2};
  EXPECT_EQ(-1, *(v -= 2));
  EXPECT_EQ(-1, *v);
}

template <typename AtomicType>
struct RelaxedAtomicIntegralTest : testing::Test {};

using RelaxedAtomicIntegralTestTypes = testing::Types<
    std::atomic<int>,
    std::atomic<int> volatile,
    folly::relaxed_atomic<int>,
    folly::relaxed_atomic<int> volatile>;
TYPED_TEST_SUITE(RelaxedAtomicIntegralTest, RelaxedAtomicIntegralTestTypes);

TYPED_TEST(RelaxedAtomicIntegralTest, isLockFree) {
  TypeParam v{3};
  EXPECT_TRUE(v.is_lock_free());
}

TYPED_TEST(RelaxedAtomicIntegralTest, operatorValue) {
  TypeParam v{3};
  EXPECT_EQ(3, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, load) {
  TypeParam v{3};
  EXPECT_EQ(3, v.load());
}

TYPED_TEST(RelaxedAtomicIntegralTest, operatorAssign) {
  TypeParam v{3};
  EXPECT_EQ(4, (v = 4));
  EXPECT_EQ(4, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, store) {
  TypeParam v{3};
  EXPECT_EQ(4, (v.store(4), v));
}

TYPED_TEST(RelaxedAtomicIntegralTest, exchange) {
  TypeParam v{3};
  EXPECT_EQ(3, v.exchange(4));
  EXPECT_EQ(4, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, compareExchangeWeak) {
  TypeParam v{3};
  int e = 2;
  EXPECT_FALSE(v.compare_exchange_weak(e, 4));
  EXPECT_TRUE(v.compare_exchange_weak(e, 4));
  EXPECT_EQ(3, e);
  EXPECT_EQ(4, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, compareExchangeStrong) {
  TypeParam v{3};
  int e = 2;
  EXPECT_FALSE(v.compare_exchange_strong(e, 4));
  EXPECT_TRUE(v.compare_exchange_strong(e, 4));
  EXPECT_EQ(3, e);
  EXPECT_EQ(4, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, fetchAdd) {
  TypeParam v{3};
  EXPECT_EQ(3, v.fetch_add(2));
  EXPECT_EQ(5, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, fetchSub) {
  TypeParam v{3};
  EXPECT_EQ(3, v.fetch_sub(2));
  EXPECT_EQ(1, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, fetchAnd) {
  TypeParam v{6};
  EXPECT_EQ(6, v.fetch_and(12));
  EXPECT_EQ(4, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, fetchOr) {
  TypeParam v{5};
  EXPECT_EQ(5, v.fetch_or(14));
  EXPECT_EQ(15, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, fetchXor) {
  TypeParam v{6};
  EXPECT_EQ(6, v.fetch_xor(10));
  EXPECT_EQ(12, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, operatorIncrPre) {
  TypeParam v{3};
  EXPECT_EQ(4, ++v);
  EXPECT_EQ(4, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, operatorIncrPost) {
  TypeParam v{3};
  EXPECT_EQ(3, v++);
  EXPECT_EQ(4, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, operatorDecrPre) {
  TypeParam v{3};
  EXPECT_EQ(2, --v);
  EXPECT_EQ(2, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, operatorDecrPost) {
  TypeParam v{3};
  EXPECT_EQ(3, v--);
  EXPECT_EQ(2, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, operatorAddAssign) {
  TypeParam v{3};
  EXPECT_EQ(5, v += 2);
  EXPECT_EQ(5, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, operatorSubAssign) {
  TypeParam v{3};
  EXPECT_EQ(1, v -= 2);
  EXPECT_EQ(1, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, operatorAndAssign) {
  TypeParam v{6};
  EXPECT_EQ(4, v &= 12);
  EXPECT_EQ(4, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, operatorOrAssign) {
  TypeParam v{5};
  EXPECT_EQ(15, v |= 14);
  EXPECT_EQ(15, v);
}

TYPED_TEST(RelaxedAtomicIntegralTest, operatorXorAssign) {
  TypeParam v{6};
  EXPECT_EQ(12, v ^= 10);
  EXPECT_EQ(12, v);
}
