Skip to content

Commit 18cd69d

Browse files
yoneymeta-codesync[bot]
authored andcommitted
Add unit tests for FrozenList
Summary: Add tests covering FrozenList initialization, copy/move behavior, destruction, and copy failures. Reviewed By: alexmalyshev Differential Revision: D115584973 fbshipit-source-id: d5fdbe2985c97437f4e8691d008ede0b4cbce501
1 parent b40b9cc commit 18cd69d

1 file changed

Lines changed: 171 additions & 0 deletions

File tree

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// Copyright (c) Meta Platforms, Inc. and affiliates.
2+
#include <gtest/gtest.h>
3+
4+
#include "cinderx/Common/frozen_list.h"
5+
6+
#include <stdexcept>
7+
#include <string>
8+
#include <utility>
9+
#include <vector>
10+
11+
namespace {
12+
13+
using namespace cinderx;
14+
15+
// Records how the list builds and tears down its elements, so tests can tell
16+
// in-place construction from construct-then-copy.
17+
struct Tracked {
18+
static inline int constructions = 0;
19+
static inline int copies = 0;
20+
static inline int live = 0;
21+
// Copies to allow before the next one throws; -1 to never throw.
22+
static inline int copies_before_throw = -1;
23+
24+
Tracked() {
25+
++constructions;
26+
++live;
27+
}
28+
Tracked(const Tracked&) {
29+
countCopy();
30+
++live;
31+
}
32+
Tracked& operator=(const Tracked&) {
33+
countCopy();
34+
return *this;
35+
}
36+
~Tracked() {
37+
--live;
38+
}
39+
40+
static void reset() {
41+
constructions = 0;
42+
copies = 0;
43+
live = 0;
44+
copies_before_throw = -1;
45+
}
46+
47+
private:
48+
static void countCopy() {
49+
if (copies == copies_before_throw) {
50+
throw std::runtime_error("element copy failed");
51+
}
52+
++copies;
53+
}
54+
};
55+
56+
} // namespace
57+
58+
TEST(FrozenListTest, EmptyListHasNoElements) {
59+
FrozenList<int> list;
60+
EXPECT_EQ(list.size(), 0);
61+
EXPECT_EQ(list.begin(), list.end());
62+
63+
list.initialize(0);
64+
EXPECT_EQ(list.size(), 0);
65+
EXPECT_EQ(list.begin(), list.end());
66+
}
67+
68+
TEST(FrozenListTest, InitializeFillsWithDefaultValues) {
69+
FrozenList<int> list;
70+
list.initialize(3);
71+
72+
const std::vector<int> expected{0, 0, 0};
73+
EXPECT_EQ(std::vector<int>(list.begin(), list.end()), expected);
74+
}
75+
76+
TEST(FrozenListTest, InitializeFillsWithGivenValue) {
77+
FrozenList<std::string> list;
78+
list.initialize(2, "x");
79+
80+
const std::vector<std::string> expected{"x", "x"};
81+
EXPECT_EQ(std::vector<std::string>(list.begin(), list.end()), expected);
82+
}
83+
84+
TEST(FrozenListTest, SubscriptReadsAndWritesElements) {
85+
FrozenList<int> list;
86+
list.initialize(3);
87+
list[0] = 7;
88+
list[2] = 9;
89+
90+
const std::vector<int> expected{7, 0, 9};
91+
EXPECT_EQ(std::vector<int>(list.begin(), list.end()), expected);
92+
93+
const FrozenList<int>& const_list = list;
94+
EXPECT_EQ(const_list[0], 7);
95+
}
96+
97+
TEST(FrozenListTest, InitializeTwiceThrows) {
98+
FrozenList<int> list;
99+
list.initialize(1);
100+
101+
EXPECT_THROW(list.initialize(1), std::runtime_error);
102+
}
103+
104+
TEST(FrozenListTest, CopyConstructorCopiesElements) {
105+
const FrozenList<std::string> source{"a", "b"};
106+
FrozenList<std::string> copy{source};
107+
copy[0] = "z";
108+
109+
// Writing through the copy proves it owns its elements rather than sharing
110+
// the source's.
111+
const std::vector<std::string> copied{"z", "b"};
112+
EXPECT_EQ(std::vector<std::string>(copy.begin(), copy.end()), copied);
113+
114+
const std::vector<std::string> untouched{"a", "b"};
115+
EXPECT_EQ(std::vector<std::string>(source.begin(), source.end()), untouched);
116+
}
117+
118+
TEST(FrozenListTest, MoveConstructorTransfersOwnership) {
119+
FrozenList<std::string> source{"a", "b"};
120+
const FrozenList<std::string> moved{std::move(source)};
121+
122+
const std::vector<std::string> expected{"a", "b"};
123+
EXPECT_EQ(std::vector<std::string>(moved.begin(), moved.end()), expected);
124+
125+
// Reading the moved-from list is the point of the test: the move
126+
// constructor empties it, rather than leaving it merely unspecified.
127+
// NOLINTNEXTLINE(bugprone-use-after-move)
128+
EXPECT_EQ(source.size(), 0);
129+
}
130+
131+
TEST(FrozenListTest, DestructorDestroysEveryElement) {
132+
Tracked::reset();
133+
{
134+
FrozenList<Tracked> list;
135+
list.initialize(3);
136+
EXPECT_EQ(Tracked::live, 3);
137+
}
138+
EXPECT_EQ(Tracked::live, 0);
139+
}
140+
141+
// Pins current behaviour, which is not what we want: initialize(size) routes
142+
// through initialize(size, T{}), so it builds a temporary, default-initializes
143+
// the whole array, then copies the temporary over every element.
144+
TEST(FrozenListTest, InitializeConstructionCounts) {
145+
Tracked::reset();
146+
FrozenList<Tracked> list;
147+
list.initialize(4);
148+
149+
EXPECT_EQ(Tracked::constructions, 5); // 4 elements plus the temporary
150+
EXPECT_EQ(Tracked::copies, 4);
151+
}
152+
153+
// Pins current behaviour, which is not what we want: the size and the storage
154+
// are both published before the elements are filled in, so a throwing element
155+
// copy leaves the list reporting a full-size range that is only partially
156+
// assigned, and refusing to be initialized again.
157+
TEST(FrozenListTest, InitializeWhenElementCopyThrows) {
158+
Tracked::reset();
159+
const Tracked probe;
160+
Tracked::copies_before_throw = 2;
161+
162+
FrozenList<Tracked> list;
163+
EXPECT_THROW(list.initialize(5, probe), std::runtime_error);
164+
Tracked::copies_before_throw = -1;
165+
166+
// Nothing is leaked: the 5 elements are still owned and readable. Only the
167+
// first 2 received the probe; the rest are left default-constructed.
168+
EXPECT_EQ(Tracked::live, 6); // the probe plus the 5 elements
169+
EXPECT_EQ(list.size(), 5);
170+
EXPECT_THROW(list.initialize(2, probe), std::runtime_error);
171+
}

0 commit comments

Comments
 (0)