NameTests.cpp 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <gtest/gtest.h>
  9. #include <AzCore/std/utils.h>
  10. #include <AzCore/UnitTest/TestTypes.h>
  11. #include <AzCore/Asset/AssetManagerComponent.h>
  12. #include <AzCore/RTTI/BehaviorContext.h>
  13. #include <AzCore/Script/ScriptAsset.h>
  14. #include <AzCore/Script/ScriptSystemComponent.h>
  15. #include <AzCore/Script/ScriptContext.h>
  16. #include <AzCore/Name/NameDictionary.h>
  17. #include <AzCore/Name/Name.h>
  18. #include <AzCore/Name/Internal/NameData.h>
  19. #include <AzCore/Component/ComponentApplication.h>
  20. #include <AzCore/EBus/EBus.h>
  21. #include <AzCore/Serialization/ObjectStream.h>
  22. #include <AzCore/Serialization/Utils.h>
  23. #include <AzCore/Math/Random.h>
  24. #include <AzCore/std/time.h>
  25. #include <thread>
  26. #include <stdlib.h>
  27. #include <time.h>
  28. #include <cstring>
  29. namespace UnitTest
  30. {
  31. static AZ::Name globalName = AZ::Name::FromStringLiteral("global", AZ::Interface<AZ::NameDictionary>::Get());
  32. class NameDictionaryTester
  33. {
  34. public:
  35. static void Create()
  36. {
  37. AZ::NameDictionary::Create();
  38. ASSERT_EQ(0, GetEntryCount());
  39. }
  40. static void Destroy()
  41. {
  42. AZ::NameDictionary::Destroy();
  43. }
  44. static const auto& GetDictionary()
  45. {
  46. return AZ::NameDictionary::Instance().m_dictionary;
  47. }
  48. static size_t GetEntryCount()
  49. {
  50. // Subtract any static scope names hanging around
  51. AZ::Name* head = &AZ::NameDictionary::Instance().m_deferredHead;
  52. size_t staticNameCount = 0;
  53. AZ::Name* current = head;
  54. AZStd::set<AZ::Name::Hash> recordedNames;
  55. while (current != nullptr)
  56. {
  57. // Add one entry to the count for every unique literal in the dictionary, compare by hash (which the name dictionary has guaranteed deduplicated for us)
  58. if (current->m_data != nullptr && !recordedNames.contains(current->m_data->GetHash()))
  59. {
  60. recordedNames.insert(current->m_data->GetHash());
  61. ++staticNameCount;
  62. }
  63. current = current->m_nextName;
  64. if (current == head)
  65. {
  66. break;
  67. }
  68. }
  69. return GetDictionary().size() - staticNameCount;
  70. }
  71. //! Directly calculate the hash value for a string without collision resolution
  72. static AZ::Name::Hash CalcDirectHashValue(AZStd::string_view name, const uint32_t maxUniqueHashes = std::numeric_limits<uint32_t>::max())
  73. {
  74. uint32_t hash = AZ::NameDictionary::Instance().CalcHash(name);
  75. if (maxUniqueHashes < std::numeric_limits<uint32_t>::max())
  76. {
  77. hash %= maxUniqueHashes;
  78. // Rather than use this modded value as the hash, we run a string hash again to
  79. // get spread-out hash values that are similar to the real ones, and avoid clustering.
  80. AZStd::fixed_string<16> buffer;
  81. AZStd::to_string(buffer, hash);
  82. hash = AZStd::hash<AZStd::string_view>{}(buffer) & 0xFFFFFFFF;
  83. }
  84. return hash;
  85. }
  86. };
  87. class ThreadCreatesOneName
  88. {
  89. public:
  90. ThreadCreatesOneName(AZStd::string_view name) : m_originalName(name)
  91. { }
  92. void Start()
  93. {
  94. m_thread = AZStd::thread([this]() { ThreadJob(); });
  95. }
  96. void Join()
  97. {
  98. m_thread.join();
  99. }
  100. const AZ::Name& GetAzName() const
  101. {
  102. return m_azName;
  103. }
  104. void ThreadJob()
  105. {
  106. m_azName = AZ::Name(m_originalName);
  107. }
  108. private:
  109. AZStd::thread m_thread;
  110. AZ::Name m_azName;
  111. AZStd::string m_originalName;
  112. };
  113. template<size_t N>
  114. class ThreadRepeatedlyCreatesAndReleasesOneName
  115. {
  116. public:
  117. ThreadRepeatedlyCreatesAndReleasesOneName(AZStd::string_view name) : m_originalName(name)
  118. { }
  119. void Start()
  120. {
  121. m_thread = AZStd::thread([this]() { ThreadJob(); });
  122. }
  123. void Join()
  124. {
  125. m_thread.join();
  126. }
  127. const AZ::Name& GetAzName() const
  128. {
  129. return m_azName;
  130. }
  131. void ThreadJob()
  132. {
  133. m_azName = AZ::Name(m_originalName);
  134. for (size_t i = 0; i < N; ++i)
  135. {
  136. m_azName = AZ::Name();
  137. m_azName = AZ::Name(m_originalName);
  138. }
  139. }
  140. private:
  141. AZStd::thread m_thread;
  142. AZ::Name m_azName;
  143. AZStd::string m_originalName;
  144. };
  145. class NameTest : public LeakDetectionFixture
  146. {
  147. protected:
  148. void SetUp() override
  149. {
  150. LeakDetectionFixture::SetUp();
  151. NameDictionaryTester::Create();
  152. }
  153. void TearDown() override
  154. {
  155. NameDictionaryTester::Destroy();
  156. LeakDetectionFixture::TearDown();
  157. }
  158. static constexpr int RandomStringBufferSize = 16;
  159. char* MakeRandomString(char buffer[RandomStringBufferSize])
  160. {
  161. azsnprintf(buffer, RandomStringBufferSize, "%d", m_random.GetRandom());
  162. return buffer;
  163. }
  164. AZ::Internal::NameData* GetNameData(AZ::Name& name)
  165. {
  166. return name.m_data.get();
  167. }
  168. void FreeMemoryFromNameData(AZ::Internal::NameData* nameData)
  169. {
  170. delete nameData;
  171. }
  172. AZ::SimpleLcgRandom m_random;
  173. };
  174. // Implemented in script.cpp
  175. void AZTestAssert(bool check);
  176. class NameScriptingTest : public LeakDetectionFixture
  177. {
  178. public:
  179. void run()
  180. {
  181. NameDictionaryTester::Create();
  182. {
  183. AZ::BehaviorContext* behaviorContext = aznew AZ::BehaviorContext();
  184. behaviorContext->Method("AZTestAssert", &AZTestAssert);
  185. AZ::Name::Reflect(behaviorContext);
  186. AZ::ScriptContext* scriptContext = aznew AZ::ScriptContext();
  187. scriptContext->BindTo(behaviorContext);
  188. scriptContext->Execute("azName = Name(\"First\");");
  189. scriptContext->Execute("AZTestAssert(azName:IsEmpty() == false);");
  190. scriptContext->Execute("azName2 = Name(\"First\");");
  191. scriptContext->Execute("AZTestAssert(azName == azName2);");
  192. scriptContext->Execute("azName3 = Name(\"Second\");");
  193. scriptContext->Execute("AZTestAssert(azName3 ~= azName2);");
  194. AZ_TEST_START_ASSERTTEST;
  195. scriptContext->Execute("azName4 = Name(0);");
  196. scriptContext->Execute("azName5 = Name(\"a\", \"b\");");
  197. AZ_TEST_STOP_ASSERTTEST(2);
  198. delete scriptContext;
  199. delete behaviorContext;
  200. }
  201. NameDictionaryTester::Destroy();
  202. }
  203. };
  204. TEST_F(NameScriptingTest, Test)
  205. {
  206. run();
  207. }
  208. template<class ConcurrencyTestThreadT>
  209. void RunConcurrencyTest(uint32_t nameCount, uint32_t threadsPerName)
  210. {
  211. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 0);
  212. // Use a fixed seed.
  213. srand(123456);
  214. AZStd::set<AZStd::string> localDictionary;
  215. for (uint32_t i = 0; i < nameCount; i++)
  216. {
  217. const int length = rand() % 5 + 10;
  218. AZStd::string name(length, '\0');
  219. AZStd::generate(name.begin(), name.end(), [] {
  220. const char asciiBase = ((rand() % 2) ? 'a' : 'A');
  221. // Generate random Ascii values.
  222. return static_cast<char>(rand() % 26 + asciiBase);
  223. });
  224. localDictionary.emplace(AZStd::move(name));
  225. }
  226. AZStd::vector<ConcurrencyTestThreadT> threads;
  227. for (const AZStd::string& nameString : localDictionary)
  228. {
  229. for (uint32_t i = 0; i < threadsPerName; ++i)
  230. {
  231. threads.push_back(ConcurrencyTestThreadT(nameString));
  232. }
  233. }
  234. for (ConcurrencyTestThreadT& th : threads)
  235. {
  236. th.Start();
  237. }
  238. for (ConcurrencyTestThreadT& th : threads)
  239. {
  240. th.Join();
  241. }
  242. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), localDictionary.size());
  243. // Make sure all entries in the localDictionary got copied into the globalDictionary
  244. for (const AZStd::string& nameString : localDictionary)
  245. {
  246. auto& globalDictionary = NameDictionaryTester::GetDictionary();
  247. // Workaround VS2022 17.3 issue with incorrect detection of unused lambda captures assigning the nameString reference to a same type
  248. auto it = AZStd::find_if(globalDictionary.begin(), globalDictionary.end(), [&nameString = nameString](const AZStd::pair<AZ::Name::Hash, AZ::NameDictionary::ScopedNameDataWrapper>& entry)
  249. {
  250. return entry.second.m_nameData->GetName() == nameString;
  251. });
  252. EXPECT_TRUE(it != globalDictionary.end()) << "Can't find '" << nameString.data() << "' in local dictionary.";
  253. }
  254. // Make sure all the threads got an accurate Name object
  255. for (ConcurrencyTestThreadT& th : threads)
  256. {
  257. AZ::Name lookupName = AZ::NameDictionary::Instance().FindName(th.GetAzName().GetHash());
  258. EXPECT_EQ(th.GetAzName().GetStringView(), lookupName.GetStringView());
  259. }
  260. }
  261. TEST_F(NameTest, NameConstructorTest)
  262. {
  263. AZ::Name a("a");
  264. EXPECT_FALSE(a.IsEmpty());
  265. EXPECT_EQ(a.GetStringView(), "a");
  266. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 1);
  267. AZ::Name a2;
  268. EXPECT_TRUE(a2.IsEmpty());
  269. EXPECT_TRUE(a2.IsEmpty());
  270. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 1);
  271. a2 = a;
  272. EXPECT_FALSE(a2.IsEmpty());
  273. EXPECT_EQ(a2.GetStringView().data(), a.GetStringView().data());
  274. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 1);
  275. a = AZStd::move(a2);
  276. EXPECT_TRUE(a2.IsEmpty());
  277. EXPECT_FALSE(a.IsEmpty());
  278. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 1);
  279. AZ::Name a3(AZStd::move(a));
  280. EXPECT_FALSE(a3.IsEmpty());
  281. EXPECT_TRUE(a.IsEmpty());
  282. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 1);
  283. AZ::Name a4(a3);
  284. EXPECT_FALSE(a3.IsEmpty());
  285. EXPECT_FALSE(a4.IsEmpty());
  286. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 1);
  287. AZ::Name b;
  288. EXPECT_TRUE(b.IsEmpty());
  289. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 1);
  290. b = "b";
  291. EXPECT_EQ(b.GetStringView().compare("b"), 0);
  292. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 2);
  293. b = AZStd::string{"b2"};
  294. EXPECT_EQ(b.GetStringView().compare("b2"), 0);
  295. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 2);
  296. a3 = AZ::Name{};
  297. EXPECT_TRUE(a3.IsEmpty());
  298. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 2);
  299. a4 = AZ::Name{};
  300. EXPECT_TRUE(a4.IsEmpty());
  301. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 1);
  302. b = AZ::Name{};
  303. EXPECT_TRUE(b.IsEmpty());
  304. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 0);
  305. b = "q";
  306. EXPECT_EQ(b.GetStringView().compare("q"), 0);
  307. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 1);
  308. b = a;
  309. EXPECT_TRUE(b.IsEmpty());
  310. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 0);
  311. // Test specific construction case that was failing.
  312. // The constructor calls Name::SetName() which does a move assignment
  313. // Name& Name::operator=(Name&& rhs) was leaving m_view pointing to the m_data in a temporary Name object.
  314. AZ::Name emptyName(AZStd::string_view{});
  315. EXPECT_TRUE(emptyName.IsEmpty());
  316. EXPECT_EQ(0, emptyName.GetStringView().data()[0]);
  317. }
  318. TEST_F(NameTest, EmptyNameIsNotInDictionary)
  319. {
  320. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 0);
  321. AZ::Name a;
  322. AZ::Name b{""};
  323. AZ::Name c{"temp"};
  324. c = "";
  325. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 0);
  326. }
  327. TEST_F(NameTest, NameConstructFromHash)
  328. {
  329. // Limit the number of hashes to make sure construction from hash works when there are hash collisions.
  330. const uint32_t maxUniqueHashes = 10;
  331. AZ::NameDictionary::Destroy();
  332. AZ::NameDictionary::Create();
  333. AZ::Name nameA{"a"};
  334. AZ::Name nameB{"B"};
  335. AZ::Name name;
  336. name = AZ::Name{AZ::Name::Hash{0u}};
  337. EXPECT_TRUE(name.IsEmpty());
  338. name = AZ::Name{nameA.GetHash()};
  339. EXPECT_TRUE(name.GetStringView() == nameA.GetStringView());
  340. EXPECT_TRUE(name == nameA);
  341. name = AZ::Name{nameB.GetHash()};
  342. EXPECT_TRUE(name.GetStringView() == nameB.GetStringView());
  343. EXPECT_TRUE(name == nameB);
  344. AZStd::array<AZ::Name, maxUniqueHashes * 10> nameList;
  345. for (size_t i = 0; i < nameList.size(); ++i)
  346. {
  347. nameList[i] = AZ::Name{AZStd::string::format("name %zu", i)};
  348. AZ::Name nameFromHash{nameList[i].GetHash()};
  349. EXPECT_TRUE(nameFromHash.GetStringView() == nameList[i].GetStringView());
  350. EXPECT_TRUE(nameFromHash == nameList[i]);
  351. }
  352. // Check again after the dictionary contents have stabilized
  353. for (AZ::Name& originalName : nameList)
  354. {
  355. AZ::Name nameFromHash{originalName.GetHash()};
  356. EXPECT_TRUE(nameFromHash.GetStringView() == originalName.GetStringView());
  357. EXPECT_TRUE(nameFromHash == originalName);
  358. }
  359. }
  360. TEST_F(NameTest, NameComparisonTest)
  361. {
  362. AZ::Name a{"a"};
  363. AZ::Name b{"b"};
  364. // == is true
  365. EXPECT_TRUE(AZ::Name{} == AZ::Name{});
  366. EXPECT_TRUE(a == a);
  367. // != is false
  368. EXPECT_FALSE(AZ::Name{} != AZ::Name{});
  369. EXPECT_FALSE(a != a);
  370. // == is false
  371. EXPECT_FALSE(a == b);
  372. EXPECT_FALSE(a == AZ::Name{});
  373. EXPECT_FALSE(b == AZ::Name{});
  374. // != is true
  375. EXPECT_TRUE(a != b);
  376. EXPECT_TRUE(a != AZ::Name{});
  377. EXPECT_TRUE(b != AZ::Name{});
  378. }
  379. TEST_F(NameTest, CollisionResolutionsArePersistent)
  380. {
  381. // When hash calculations collide, the resolved hash value is order-dependent and therefore is not guaranteed
  382. // be the same between runs of the application. However, the resolved hash values ARE guaranteed to be preserved
  383. // during the dictionary lifetime. This is a precautionary measure to avoid possible edge cases.
  384. const uint32_t maxUniqueHashes = 50;
  385. AZ::NameDictionary::Destroy();
  386. AZ::NameDictionary::Create();
  387. // We need this number of names to collide for the test to be valid
  388. const size_t requiredCollisionCount = 3;
  389. // Find a set of names that collide
  390. AZStd::map<uint32_t, AZStd::vector<AZStd::string>> hashTable;
  391. AZStd::vector<AZStd::string>* collidingNameSet = nullptr;
  392. for (int i = 0; i < 1000; ++i)
  393. {
  394. AZStd::string nameText = AZStd::string::format("name%d", i);
  395. uint32_t hash = NameDictionaryTester::CalcDirectHashValue(nameText, maxUniqueHashes);
  396. collidingNameSet = &hashTable[hash];
  397. collidingNameSet->push_back(nameText);
  398. if (collidingNameSet->size() >= requiredCollisionCount)
  399. {
  400. break;
  401. }
  402. }
  403. ASSERT_NE(collidingNameSet, nullptr);
  404. ASSERT_GE(collidingNameSet->size(), requiredCollisionCount);
  405. AZStd::unique_ptr<AZ::Name> nameA = AZStd::make_unique<AZ::Name>((*collidingNameSet)[0]);
  406. AZStd::unique_ptr<AZ::Name> nameB = AZStd::make_unique<AZ::Name>((*collidingNameSet)[1]);
  407. AZStd::unique_ptr<AZ::Name> nameC = AZStd::make_unique<AZ::Name>((*collidingNameSet)[2]);
  408. // Even though we destroy nameA and nameB, we should still be able to retrieve nameC.
  409. nameA.reset();
  410. nameB.reset();
  411. AZ::Name newNameC{(*collidingNameSet)[2]};
  412. EXPECT_EQ(newNameC, *nameC);
  413. EXPECT_EQ(newNameC.GetStringView(), nameC->GetStringView());
  414. }
  415. TEST_F(NameTest, ReportLeakedNames)
  416. {
  417. AZ::Internal::NameData* leakedNameData = nullptr;
  418. {
  419. constexpr AZStd::string_view leakedNameContents{ "hello" };
  420. AZ::Name leakedName{ leakedNameContents };
  421. AZ_TEST_START_TRACE_SUPPRESSION;
  422. AZ::NameDictionary::Destroy();
  423. AZ_TEST_STOP_TRACE_SUPPRESSION(1);
  424. leakedNameData = GetNameData(leakedName);
  425. // Create the dictionary again to avoid crash when the intrusive_ptr in Name tries to access NameDictionary to free it
  426. AZ::NameDictionary::Create();
  427. }
  428. FreeMemoryFromNameData(leakedNameData); // free it to avoid memory system reporting the leak
  429. }
  430. TEST_F(NameTest, NullTerminatedTest)
  431. {
  432. const char* buffer = "There is a name in here";
  433. AZ::Name a{AZStd::string_view(&buffer[9], 1)};
  434. AZ::Name b;
  435. b = AZStd::string_view(&buffer[11], 4);
  436. EXPECT_TRUE(a.GetStringView() == "a");
  437. EXPECT_TRUE(a.GetStringView()[1] == '\0');
  438. EXPECT_TRUE(a.GetStringView().data() == a.GetCStr());
  439. EXPECT_TRUE(b.GetStringView() == "name");
  440. EXPECT_TRUE(b.GetStringView()[4] == '\0');
  441. EXPECT_TRUE(b.GetStringView().data() == b.GetCStr());
  442. }
  443. TEST_F(NameTest, SerializationTest)
  444. {
  445. AZ::SerializeContext* serializeContext = aznew AZ::SerializeContext();
  446. AZ::Name::Reflect(serializeContext);
  447. const char* testString = "MyReflectedString";
  448. auto doSerialization = [&](AZ::ObjectStream::StreamType streamType)
  449. {
  450. AZ::Name reflectedName(testString);
  451. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 1);
  452. AZStd::vector<char, AZ::OSStdAllocator> nameBuffer;
  453. AZ::IO::ByteContainerStream<AZStd::vector<char, AZ::OSStdAllocator> > outStream(&nameBuffer);
  454. {
  455. AZ::ObjectStream* objStream = AZ::ObjectStream::Create(&outStream, *serializeContext, streamType);
  456. bool writeOK = objStream->WriteClass(&reflectedName);
  457. ASSERT_TRUE(writeOK);
  458. bool finalizeOK = objStream->Finalize();
  459. ASSERT_TRUE(finalizeOK);
  460. }
  461. outStream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
  462. reflectedName = AZ::Name{};
  463. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 0);
  464. AZ::ObjectStream::FilterDescriptor filterDesc;
  465. AZ::Name* serializedName = AZ::Utils::LoadObjectFromStream<AZ::Name>(outStream, serializeContext, filterDesc);
  466. reflectedName = *serializedName;
  467. delete serializedName;
  468. EXPECT_EQ(NameDictionaryTester::GetEntryCount(), 1);
  469. EXPECT_EQ(reflectedName.GetStringView(), testString);
  470. };
  471. doSerialization(AZ::ObjectStream::ST_BINARY);
  472. doSerialization(AZ::ObjectStream::ST_XML);
  473. doSerialization(AZ::ObjectStream::ST_JSON);
  474. delete serializeContext;
  475. }
  476. TEST_F(NameTest, NameContainerTest)
  477. {
  478. AZStd::unordered_set<AZ::Name> nameSet;
  479. nameSet.insert(AZ::Name{ "c" });
  480. nameSet.insert(AZ::Name{ "a" });
  481. nameSet.insert(AZ::Name{ "b" });
  482. nameSet.insert(AZ::Name{ "b" });
  483. nameSet.insert(AZ::Name{ "a" });
  484. nameSet.insert(AZ::Name{ "b" });
  485. EXPECT_EQ(3, nameSet.size());
  486. EXPECT_EQ(1, nameSet.count(AZ::Name{ "a" }));
  487. EXPECT_EQ(1, nameSet.count(AZ::Name{ "b" }));
  488. EXPECT_EQ(1, nameSet.count(AZ::Name{ "c" }));
  489. EXPECT_EQ(0, nameSet.count(AZ::Name{}));
  490. EXPECT_EQ(0, nameSet.count(AZ::Name{ "d" }));
  491. }
  492. TEST_F(NameTest, ConcurrencyDataTest_EachThreadCreatesOneName_NoCollision)
  493. {
  494. AZ::NameDictionary::Destroy();
  495. AZ::NameDictionary::Create();
  496. // 3 threads per name effectively makes two readers and one writer (the first to run will write in the dictionary)
  497. RunConcurrencyTest<ThreadCreatesOneName>(AZStd::thread::hardware_concurrency(), 3);
  498. }
  499. TEST_F(NameTest, ConcurrencyDataTest_EachThreadCreatesOneName_HighCollisions)
  500. {
  501. AZ::NameDictionary::Destroy();
  502. AZ::NameDictionary::Create();
  503. // 3 threads per name effectively makes two readers and one writer (the first to run will write in the dictionary)
  504. RunConcurrencyTest<ThreadCreatesOneName>(AZStd::thread::hardware_concurrency() / 2, 3);
  505. }
  506. TEST_F(NameTest, ConcurrencyDataTest_EachThreadRepeatedlyCreatesAndReleasesOneName_NoCollision)
  507. {
  508. AZ::NameDictionary::Destroy();
  509. AZ::NameDictionary::Create();
  510. // We use only 2 threads for each name to try and maximize how much names are added and removed,
  511. // instead of letting shared references cause the name to stay in the dictionary.
  512. RunConcurrencyTest<ThreadRepeatedlyCreatesAndReleasesOneName<100>>(100, 2);
  513. }
  514. TEST_F(NameTest, ConcurrencyDataTest_EachThreadRepeatedlyCreatesAndReleasesOneName_HighCollisions)
  515. {
  516. AZ::NameDictionary::Destroy();
  517. // Create an instance of the NameDictionary with the max hash slots set to 1
  518. // to produce collisions on every name.
  519. ASSERT_EQ(nullptr, AZ::Interface<AZ::NameDictionary>::Get());
  520. constexpr AZ::u64 maxHashSlots = 1;
  521. AZStd::unique_ptr<AZ::NameDictionary> nameDictionary = AZStd::make_unique<AZ::NameDictionary>(maxHashSlots);
  522. AZ::Interface<AZ::NameDictionary>::Register(nameDictionary.get());
  523. // We use only 2 threads for each name to try and maximize how much names are added and removed,
  524. // instead of letting shared references cause the name to stay in the dictionary.
  525. RunConcurrencyTest<ThreadRepeatedlyCreatesAndReleasesOneName<100>>(100, 2);
  526. AZ::Interface<AZ::NameDictionary>::Unregister(nameDictionary.get());
  527. }
  528. TEST_F(NameTest, ConcurrencyDataTest_EachThreadRepeatedlyCreatesAndReleasesOneName_AndAccessItOnManyThread)
  529. {
  530. AZ::NameDictionary::Destroy();
  531. AZ::NameDictionary::Create();
  532. // We use only 1 name, but 200 threads to catch any race condtions in the NameData::release function
  533. // Once that function decrements the m_useCount to 0, it should never be incremented again.
  534. RunConcurrencyTest<ThreadRepeatedlyCreatesAndReleasesOneName<100>>(1, 2);
  535. }
  536. TEST_F(NameTest, NameRef)
  537. {
  538. AZ::NameRef fromRValue = AZ::Name("test");
  539. EXPECT_EQ("test", fromRValue.GetStringView());
  540. AZ::Name name("foo");
  541. AZ::NameRef fromLValue = name;
  542. EXPECT_EQ("foo", fromLValue.GetStringView());
  543. AZ::Name fromRefLValue = fromLValue;
  544. EXPECT_EQ("foo", fromRefLValue.GetStringView());
  545. AZ::Name fromRefRValue = AZStd::move(fromRValue);
  546. EXPECT_EQ("test", fromRefRValue.GetStringView());
  547. EXPECT_TRUE(AZ::Name(fromLValue) == fromRefLValue);
  548. EXPECT_TRUE(fromLValue == fromRefLValue);
  549. EXPECT_TRUE(fromRefLValue == AZ::Name(fromLValue));
  550. EXPECT_TRUE(fromLValue == AZ::Name("foo"));
  551. EXPECT_TRUE(AZ::Name("foo") == fromLValue);
  552. }
  553. TEST_F(NameTest, NameLiteral)
  554. {
  555. static AZ::Name staticName = AZ::Name::FromStringLiteral("static", AZ::Interface<AZ::NameDictionary>::Get());
  556. EXPECT_EQ("literal", AZ_NAME_LITERAL("literal").GetStringView());
  557. EXPECT_EQ("static", staticName.GetStringView());
  558. EXPECT_EQ("global", globalName.GetStringView());
  559. }
  560. TEST_F(NameTest, DISABLED_NameVsStringPerf_Creation)
  561. {
  562. constexpr int CreateCount = 1000;
  563. char buffer[RandomStringBufferSize];
  564. AZStd::sys_time_t newNameTime;
  565. [[maybe_unused]] AZStd::sys_time_t existingNameTime;
  566. AZStd::sys_time_t stringTime;
  567. {
  568. const size_t dictionaryNoiseSize = 1000;
  569. AZStd::vector<AZ::Name> existingNames;
  570. existingNames.reserve(dictionaryNoiseSize);
  571. for (int i = 0; i < dictionaryNoiseSize; ++i)
  572. {
  573. existingNames.push_back(AZ::Name{ MakeRandomString(buffer) });
  574. }
  575. EXPECT_EQ(dictionaryNoiseSize, NameDictionaryTester::GetEntryCount());
  576. {
  577. const AZStd::sys_time_t startTime = AZStd::GetTimeNowMicroSecond();
  578. for (int i = 0; i < CreateCount; ++i)
  579. {
  580. AZ::Name name{ MakeRandomString(buffer) };
  581. }
  582. newNameTime = AZStd::GetTimeNowMicroSecond() - startTime;
  583. }
  584. {
  585. const AZStd::sys_time_t startTime = AZStd::GetTimeNowMicroSecond();
  586. for (int i = 0; i < CreateCount; ++i)
  587. {
  588. const int randomEntry = m_random.GetRandom() % dictionaryNoiseSize;
  589. // Make a copy of the string just in case there are optimizations in the Name system when
  590. // same string pointer is used to construct a new Name. What we want to test here is looking
  591. // up a name in the dictionary from new data.
  592. azstrcpy(buffer, RandomStringBufferSize, existingNames[randomEntry].GetCStr());
  593. AZ::Name name{ buffer };
  594. }
  595. existingNameTime = AZStd::GetTimeNowMicroSecond() - startTime;
  596. }
  597. // This isn't relevant to the performance test, but we might as well check this while we're here
  598. existingNames.clear();
  599. EXPECT_EQ(0, NameDictionaryTester::GetEntryCount());
  600. }
  601. {
  602. const AZStd::sys_time_t startTime = AZStd::GetTimeNowMicroSecond();
  603. for (int i = 0; i < CreateCount; ++i)
  604. {
  605. AZStd::string s{ MakeRandomString(buffer) };
  606. }
  607. stringTime = AZStd::GetTimeNowMicroSecond() - startTime;
  608. }
  609. AZ_TracePrintf("NameTest", "Create %d strings: %d us\n", CreateCount, stringTime);
  610. AZ_TracePrintf("NameTest", "Create %d new names: %d us\n", CreateCount, newNameTime);
  611. AZ_TracePrintf("NameTest", "Create %d existing names: %d us\n", CreateCount, existingNameTime);
  612. // It is known that NameDictionary is slower at creation time for existing entries.
  613. EXPECT_GT(newNameTime, stringTime);
  614. }
  615. TEST_F(NameTest, DISABLED_NameVsStringPerf_Comparison)
  616. {
  617. constexpr int CompareCount = 10000;
  618. AZStd::string stringA = "BlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlah 1";
  619. AZStd::string stringB = "BlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlahBlah 2";
  620. AZStd::string stringC = "Blah";
  621. AZStd::sys_time_t nameTime;
  622. AZStd::sys_time_t stringTime;
  623. {
  624. AZ::Name a1{ stringA };
  625. AZ::Name a2{ stringA };
  626. AZ::Name b{ stringB };
  627. AZ::Name c{ stringC };
  628. const AZStd::sys_time_t startTime = AZStd::GetTimeNowMicroSecond();
  629. bool result = true;
  630. for (int i = 0; i < CompareCount; ++i)
  631. {
  632. result &= (a1 == a2);
  633. result &= (a1 != b);
  634. result &= (a1 != c);
  635. }
  636. nameTime = AZStd::GetTimeNowMicroSecond() - startTime;
  637. EXPECT_TRUE(result);
  638. }
  639. {
  640. AZStd::string a1{ stringA };
  641. AZStd::string a2{ stringA };
  642. AZStd::string b{ stringB };
  643. AZStd::string c{ stringC };
  644. const AZStd::sys_time_t startTime = AZStd::GetTimeNowMicroSecond();
  645. bool result = true;
  646. for (int i = 0; i < CompareCount; ++i)
  647. {
  648. result &= (a1 == a2);
  649. result &= (a1 != b);
  650. result &= (a1 != c);
  651. }
  652. stringTime = AZStd::GetTimeNowMicroSecond() - startTime;
  653. EXPECT_TRUE(result);
  654. }
  655. AZ_TracePrintf("NameTest", "Compare %d strings: %d us\n", CompareCount, stringTime);
  656. AZ_TracePrintf("NameTest", "Compare %d names: %d us\n", CompareCount, nameTime);
  657. EXPECT_LE(nameTime, stringTime);
  658. }
  659. // LY-115684 This test is unstable to run in parallel since the load during the first part of the loop could be different
  660. // than the load during the second part
  661. TEST_F(NameTest, DISABLED_NameVsStringPerf_UnorderedSearch)
  662. {
  663. constexpr int SearchCount = 10000;
  664. constexpr int EntryCount = 10000;
  665. char buffer[RandomStringBufferSize];
  666. AZStd::sys_time_t nameTime;
  667. AZStd::sys_time_t stringTime;
  668. {
  669. AZStd::unordered_map<AZ::Name, int> nameMap;
  670. AZStd::vector<AZ::Name> nameList;
  671. for (int i = 0; i < EntryCount; ++i)
  672. {
  673. AZ::Name name{ MakeRandomString(buffer) };
  674. nameMap[name] = i;
  675. nameList.push_back(name);
  676. }
  677. bool result = true;
  678. const AZStd::sys_time_t startTime = AZStd::GetTimeNowMicroSecond();
  679. for (int i = 0; i < SearchCount; ++i)
  680. {
  681. const int randomEntry = m_random.GetRandom() % EntryCount;
  682. auto& name = nameList[randomEntry];
  683. auto iter = nameMap.find(name);
  684. result &= (randomEntry == iter->second);
  685. }
  686. nameTime = AZStd::GetTimeNowMicroSecond() - startTime;
  687. EXPECT_TRUE(result);
  688. nameMap.clear();
  689. nameList.clear();
  690. }
  691. {
  692. AZStd::unordered_map<AZStd::string, int> nameMap;
  693. AZStd::vector<AZStd::string> nameList;
  694. for (int i = 0; i < EntryCount; ++i)
  695. {
  696. AZStd::string name{ MakeRandomString(buffer) };
  697. nameMap[name] = i;
  698. nameList.push_back(name);
  699. }
  700. bool result = true;
  701. const AZStd::sys_time_t startTime = AZStd::GetTimeNowMicroSecond();
  702. for (int i = 0; i < SearchCount; ++i)
  703. {
  704. const int randomEntry = m_random.GetRandom() % EntryCount;
  705. auto& name = nameList[randomEntry];
  706. auto iter = nameMap.find(name);
  707. result &= (randomEntry == iter->second);
  708. }
  709. stringTime = AZStd::GetTimeNowMicroSecond() - startTime;
  710. EXPECT_TRUE(result);
  711. }
  712. AZ_TracePrintf("NameTest", "Search for %d strings: %d us\n", SearchCount, stringTime);
  713. AZ_TracePrintf("NameTest", "Search for %d names: %d us\n", SearchCount, nameTime);
  714. EXPECT_LT(nameTime, stringTime);
  715. }
  716. // Fixture for validating that multiple NameDictionarys can exist at once.
  717. class MultiNameDictionaryFixture
  718. : public LeakDetectionFixture
  719. {
  720. public:
  721. MultiNameDictionaryFixture()
  722. {
  723. m_nameDictionary1 = AZStd::make_unique<AZ::NameDictionary>();
  724. m_nameDictionary2 = AZStd::make_unique<AZ::NameDictionary>();
  725. }
  726. ~MultiNameDictionaryFixture()
  727. {
  728. m_nameDictionary2.reset();
  729. m_nameDictionary1.reset();
  730. }
  731. protected:
  732. AZStd::unique_ptr<AZ::NameDictionary> m_nameDictionary1;
  733. AZStd::unique_ptr<AZ::NameDictionary> m_nameDictionary2;
  734. };
  735. TEST_F(MultiNameDictionaryFixture, MultipleDictionaries_Contains_DifferentNameInstances)
  736. {
  737. // Dictionary 1 will contain the names of "Name1" and "Dictionary1NameOnly"
  738. AZ::Name name1FromDict1 = m_nameDictionary1->MakeName("Name1");
  739. AZ::Name dict1OnlyName = m_nameDictionary1->MakeName("Dictionary1NameOnly");
  740. // Dictionary 2 will contain the names of "Name1" and "Dictionary2NameOnly"
  741. AZ::Name name1FromDict2 = m_nameDictionary2->MakeName("Name1");
  742. AZ::Name dict2OnlyName = m_nameDictionary2->MakeName("Dictionary2NameOnly");
  743. // First dictionary EXPECTS
  744. EXPECT_FALSE(m_nameDictionary1->FindName(name1FromDict1.GetHash()).IsEmpty());
  745. EXPECT_FALSE(m_nameDictionary1->FindName(dict1OnlyName.GetHash()).IsEmpty());
  746. // The first dictionary should NOT contain Dictionary2NameOnly
  747. EXPECT_TRUE(m_nameDictionary1->FindName(dict2OnlyName.GetHash()).IsEmpty());
  748. // Second dictionary EXPECTS
  749. EXPECT_FALSE(m_nameDictionary2->FindName(name1FromDict1.GetHash()).IsEmpty());
  750. EXPECT_FALSE(m_nameDictionary2->FindName(dict2OnlyName.GetHash()).IsEmpty());
  751. // The second dictionary should NOT contain Dictionary1NameOnly
  752. EXPECT_TRUE(m_nameDictionary2->FindName(dict1OnlyName.GetHash()).IsEmpty());
  753. // reset the name1FromDict1 variable
  754. // Only the reference to "Name1" entry in first dictionary should be removed
  755. const AZ::Name::Hash name1Hash = name1FromDict1.GetHash();
  756. name1FromDict1 = AZ::Name{};
  757. EXPECT_TRUE(m_nameDictionary1->FindName(name1Hash).IsEmpty());
  758. EXPECT_FALSE(m_nameDictionary2->FindName(name1FromDict2.GetHash()).IsEmpty());
  759. }
  760. TEST_F(MultiNameDictionaryFixture, NameLiteral_IsOnlyLinkedToSingleNameDictionary)
  761. {
  762. // When run with --gtest_repeat=2 the literal is not relinked to the NameDictionary
  763. // if a static variable is used due to not running the constructor
  764. const AZ::Name staticName = AZ::Name::FromStringLiteral("firstStatic", m_nameDictionary1.get());
  765. const AZ::Name literalRef = AZ::Name::FromStringLiteral("secondLiteral", m_nameDictionary2.get());
  766. // "firstStatic" should be in dictionary1, but not dictionary2
  767. EXPECT_FALSE(m_nameDictionary1->FindName(staticName.GetHash()).IsEmpty());
  768. EXPECT_TRUE(m_nameDictionary2->FindName(staticName.GetHash()).IsEmpty());
  769. // "secondLiteral" should be in dictionary2, but not dictionary1
  770. EXPECT_FALSE(m_nameDictionary2->FindName(literalRef.GetHash()).IsEmpty());
  771. EXPECT_TRUE(m_nameDictionary1->FindName(literalRef.GetHash()).IsEmpty());
  772. // Add a "firstStatic" to second dictionary
  773. const AZ::Name staticNameDict2{ AZ::Name::FromStringLiteral("firstStatic", m_nameDictionary2.get()) };
  774. EXPECT_FALSE(m_nameDictionary1->FindName(staticName.GetHash()).IsEmpty());
  775. EXPECT_FALSE(m_nameDictionary2->FindName(staticNameDict2.GetHash()).IsEmpty());
  776. // Now create a second name literal for the string literal of "secondLiteral"
  777. // There should be two different "secondLiteral" instance each associated with a different name dictionary
  778. const AZ::Name literalRefForDict1 = AZ::Name::FromStringLiteral("secondLiteral", m_nameDictionary1.get());
  779. EXPECT_EQ(literalRef, literalRefForDict1) << "The values of the names should be equal";
  780. EXPECT_NE(&literalRef, &literalRefForDict1) << "The memory address of the names should not be equal";
  781. EXPECT_FALSE(m_nameDictionary1->FindName(literalRef.GetHash()).IsEmpty());
  782. EXPECT_FALSE(m_nameDictionary2->FindName(literalRefForDict1.GetHash()).IsEmpty());
  783. }
  784. }