// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/threading/sequence_local_storage_slot.h"

#include <utility>

#include "base/memory/ptr_util.h"
#include "base/threading/sequence_local_storage_map.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {

namespace {

template <class T>
class SequenceLocalStorageSlotTest : public testing::Test {
 public:
  SequenceLocalStorageSlotTest(const SequenceLocalStorageSlotTest&) = delete;
  SequenceLocalStorageSlotTest& operator=(const SequenceLocalStorageSlotTest&) =
      delete;

 protected:
  SequenceLocalStorageSlotTest()
      : scoped_sequence_local_storage_(&sequence_local_storage_) {}

  internal::SequenceLocalStorageMap sequence_local_storage_;
  internal::ScopedSetSequenceLocalStorageMapForCurrentThread
      scoped_sequence_local_storage_;
};

}  // namespace

struct GenericSLS {
  template <class T>
  using Type = GenericSequenceLocalStorageSlot<T>;
};

struct SmallSLS {
  template <class T>
  using Type = GenericSequenceLocalStorageSlot<T>;
};

using StorageTypes = testing::Types<GenericSLS, SmallSLS>;
TYPED_TEST_SUITE(SequenceLocalStorageSlotTest, StorageTypes);

// Verify that a value stored with emplace() can be retrieved with operator*().
TYPED_TEST(SequenceLocalStorageSlotTest, GetEmplace) {
  using SLSType = typename TypeParam::template Type<int>;
  SLSType slot;
  slot.emplace(5);
  EXPECT_EQ(*slot, 5);
}

// Verify that inserting an object in a SequenceLocalStorageSlot creates a copy
// of that object independent of the original one.
TYPED_TEST(SequenceLocalStorageSlotTest, EmplaceObjectIsIndependent) {
  using SLSType = typename TypeParam::template Type<bool>;
  bool should_be_false = false;

  SLSType slot;

  slot.emplace(should_be_false);

  EXPECT_FALSE(*slot);
  *slot = true;
  EXPECT_TRUE(*slot);

  EXPECT_NE(should_be_false, *slot);
}

// Verify that multiple slots work and that calling emplace after overwriting
// a value in a slot yields the new value.
TYPED_TEST(SequenceLocalStorageSlotTest, GetEmplaceMultipleSlots) {
  using SLSType = typename TypeParam::template Type<int>;
  SLSType slot1;
  SLSType slot2;
  SLSType slot3;
  EXPECT_FALSE(slot1);
  EXPECT_FALSE(slot2);
  EXPECT_FALSE(slot3);

  slot1.emplace(1);
  slot2.emplace(2);
  slot3.emplace(3);

  EXPECT_TRUE(slot1);
  EXPECT_TRUE(slot2);
  EXPECT_TRUE(slot3);
  EXPECT_EQ(*slot1, 1);
  EXPECT_EQ(*slot2, 2);
  EXPECT_EQ(*slot3, 3);

  slot3.emplace(4);
  slot2.emplace(5);
  slot1.emplace(6);

  EXPECT_EQ(*slot3, 4);
  EXPECT_EQ(*slot2, 5);
  EXPECT_EQ(*slot1, 6);
}

// Verify that changing the value returned by Get() changes the value
// in sequence local storage.
TYPED_TEST(SequenceLocalStorageSlotTest, GetReferenceModifiable) {
  using SLSType = typename TypeParam::template Type<bool>;
  SLSType slot;
  slot.emplace(false);
  *slot = true;
  EXPECT_TRUE(*slot);
}

// Verify that a move-only type can be stored in sequence local storage.
TYPED_TEST(SequenceLocalStorageSlotTest, EmplaceGetWithMoveOnlyType) {
  struct MoveOnly {
    MoveOnly() = default;
    MoveOnly(const MoveOnly&) = delete;
    MoveOnly& operator=(const MoveOnly&) = delete;
    MoveOnly(MoveOnly&&) = default;
    MoveOnly& operator=(MoveOnly&&) = default;
    int x = 0x12345678;
  };
  using SLSType = typename TypeParam::template Type<MoveOnly>;
  MoveOnly move_only;

  SLSType slot;
  slot.emplace(std::move(move_only));

  EXPECT_EQ(slot->x, 0x12345678);
}

// Verify that a Get() without a previous Set() on a slot returns a
// default-constructed value.
TYPED_TEST(SequenceLocalStorageSlotTest, GetWithoutSetDefaultConstructs) {
  struct DefaultConstructable {
    int x = 0x12345678;
  };
  using SLSType = typename TypeParam::template Type<DefaultConstructable>;

  SLSType slot;

  EXPECT_EQ(slot.GetOrCreateValue().x, 0x12345678);
}

// Verify that a GetOrCreateValue() without a previous emplace() on a slot with
// a POD-type returns a default-constructed value.
// Note: this test could be flaky and give a false pass. If it's flaky, the test
// might've "passed" because the memory for the slot happened to be zeroed.
TYPED_TEST(SequenceLocalStorageSlotTest, GetWithoutSetDefaultConstructsPOD) {
  using SLSType = typename TypeParam::template Type<void*>;
  SLSType slot;

  EXPECT_EQ(slot.GetOrCreateValue(), nullptr);
}

// Verify that the value of a slot is specific to a SequenceLocalStorageMap
TEST(SequenceLocalStorageSlotMultipleMapTest, EmplaceGetMultipleMapsOneSlot) {
  SequenceLocalStorageSlot<unsigned int> slot;
  internal::SequenceLocalStorageMap sequence_local_storage_maps[5];

  // Set the value of the slot to be the index of the current
  // SequenceLocalStorageMaps in the vector
  for (unsigned int i = 0; i < std::size(sequence_local_storage_maps); ++i) {
    internal::ScopedSetSequenceLocalStorageMapForCurrentThread
        scoped_sequence_local_storage(&sequence_local_storage_maps[i]);

    slot.emplace(i);
  }

  for (unsigned int i = 0; i < std::size(sequence_local_storage_maps); ++i) {
    internal::ScopedSetSequenceLocalStorageMapForCurrentThread
        scoped_sequence_local_storage(&sequence_local_storage_maps[i]);

    EXPECT_EQ(*slot, i);
  }
}

TEST(SequenceLocalStorageComPtrTest,
     TestClassesWithNoAddressOfOperatorCanCompile) {
  internal::SequenceLocalStorageMap sequence_local_storage_map;
  internal::ScopedSetSequenceLocalStorageMapForCurrentThread
      scoped_sequence_local_storage(&sequence_local_storage_map);
  // Microsoft::WRL::ComPtr overrides & operator to release the underlying
  // pointer.
  // https://learn.microsoft.com/en-us/cpp/cppcx/wrl/comptr-class?view=msvc-170#operator-ampersand
  // Types stored in SequenceLocalStorage may override `operator&` to have
  // additional side effects, e.g. Microsoft::WRL::ComPtr. Make sure
  // SequenceLocalStorage does not invoke/use custom `operator&`s to avoid
  // triggering those side effects.
  class TestNoAddressOfOperator {
   public:
    TestNoAddressOfOperator() = default;
    ~TestNoAddressOfOperator() {
      // Define a non-trivial destructor so that SequenceLocalStorageSlot
      // will use the external value path.
    }
    // See note above class definition for the reason this operator is deleted.
    TestNoAddressOfOperator* operator&() = delete;
  };
  SequenceLocalStorageSlot<TestNoAddressOfOperator> slot;
  slot.emplace(TestNoAddressOfOperator());
  EXPECT_NE(slot.GetValuePointer(), nullptr);
}

}  // namespace base
