1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207

base / threading / sequence_local_storage_slot_unittest.cc [blame]

// 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 <array>
#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;
  std::array<internal::SequenceLocalStorageMap, 5> sequence_local_storage_maps;

  // 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