1 /*
2 * Copyright 2023 Google LLC
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include "fcp/aggregation/core/input_tensor_list.h"
17
18 #include <algorithm>
19 #include <cstdint>
20 #include <memory>
21 #include <utility>
22
23 #include "gmock/gmock.h"
24 #include "gtest/gtest.h"
25 #include "fcp/aggregation/core/tensor.h"
26 #include "fcp/aggregation/core/tensor_shape.h"
27 #include "fcp/aggregation/testing/test_data.h"
28
29 namespace fcp {
30 namespace aggregation {
31 namespace {
32
33 using testing::Eq;
34 using testing::Not;
35
36 class InputTensorListTest : public testing::Test {
37 protected:
InputTensorListTest()38 InputTensorListTest()
39 : t1_(Tensor::Create(DT_FLOAT, {1}, CreateTestData<float>({1})).value()),
40 t2_(Tensor::Create(DT_INT32, {2}, CreateTestData<int32_t>({2, 3}))
41 .value()),
42 t3_(Tensor::Create(DT_INT64, {3}, CreateTestData<int64_t>({4, 5, 6}))
43 .value()),
44 t4_(Tensor::Create(DT_FLOAT, {4}, CreateTestData<float>({7, 8, 9, 10}))
45 .value()),
46 t5_(Tensor::Create(DT_INT32, {5},
47 CreateTestData<int32_t>({11, 12, 13, 14, 15}))
48 .value()),
49 t6_(Tensor::Create(DT_INT64, {6},
50 CreateTestData<int64_t>({16, 17, 18, 19, 20, 21}))
51 .value()) {}
52
CreateInlined()53 InputTensorList CreateInlined() {
54 return InputTensorList({&t1_, &t2_, &t3_});
55 }
56
CreateAllocated()57 InputTensorList CreateAllocated() {
58 return InputTensorList({&t1_, &t2_, &t3_, &t4_, &t5_, &t6_});
59 }
60
61 Tensor t1_;
62 Tensor t2_;
63 Tensor t3_;
64 Tensor t4_;
65 Tensor t5_;
66 Tensor t6_;
67 };
68
TEST_F(InputTensorListTest,Inlined_Size)69 TEST_F(InputTensorListTest, Inlined_Size) {
70 InputTensorList tensor_list = CreateInlined();
71 EXPECT_THAT(tensor_list.size(), Eq(3));
72 }
73
TEST_F(InputTensorListTest,Inlined_Iterate)74 TEST_F(InputTensorListTest, Inlined_Iterate) {
75 InputTensorList tensor_list = CreateInlined();
76 auto iter = tensor_list.begin();
77 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{1}));
78 iter++;
79 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{2}));
80 iter++;
81 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{3}));
82 iter++;
83 EXPECT_THAT(iter, Eq(tensor_list.end()));
84 }
85
TEST_F(InputTensorListTest,Inlined_MoveConstructor_Iterate)86 TEST_F(InputTensorListTest, Inlined_MoveConstructor_Iterate) {
87 InputTensorList moved_tensor_list = CreateInlined();
88 InputTensorList tensor_list(std::move(moved_tensor_list));
89 auto iter = tensor_list.begin();
90 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{1}));
91 iter++;
92 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{2}));
93 iter++;
94 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{3}));
95 iter++;
96 EXPECT_THAT(iter, Eq(tensor_list.end()));
97 }
98
TEST_F(InputTensorListTest,Inlined_MoveAssignment_Iterate)99 TEST_F(InputTensorListTest, Inlined_MoveAssignment_Iterate) {
100 InputTensorList moved_tensor_list = CreateInlined();
101 // Initially, create the tensor list as an allocated tensor list before
102 // assigning it to an inlined InputTensorList via move assignment.
103 InputTensorList tensor_list = CreateAllocated();
104 tensor_list = std::move(moved_tensor_list);
105 auto iter = tensor_list.begin();
106 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{1}));
107 iter++;
108 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{2}));
109 iter++;
110 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{3}));
111 iter++;
112 EXPECT_THAT(iter, Eq(tensor_list.end()));
113
114 // Assigning back to the moved variable is valid.
115 moved_tensor_list = std::move(tensor_list);
116 iter = moved_tensor_list.begin();
117 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{1}));
118 iter++;
119 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{2}));
120 iter++;
121 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{3}));
122 iter++;
123 EXPECT_THAT(iter, Eq(moved_tensor_list.end()));
124 }
125
TEST_F(InputTensorListTest,Inlined_ForEachLoop)126 TEST_F(InputTensorListTest, Inlined_ForEachLoop) {
127 InputTensorList tensor_list = CreateInlined();
128 uint64_t expected_size = 1;
129 for (const Tensor* t : tensor_list) {
130 EXPECT_THAT(t->shape().NumElements(), Eq(expected_size));
131 expected_size++;
132 }
133 }
134
TEST_F(InputTensorListTest,Inlined_Iterate_MultiPassGuarantee)135 TEST_F(InputTensorListTest, Inlined_Iterate_MultiPassGuarantee) {
136 // Ensure the iterator meets the multi-pass guarantee requirements required
137 // by forward iterators.
138 // (https://en.cppreference.com/w/cpp/iterator/forward_iterator)
139 InputTensorList tensor_list = CreateInlined();
140 auto iterI = tensor_list.begin();
141 auto iterJ = tensor_list.begin();
142 EXPECT_THAT(iterI, Eq(iterJ));
143 EXPECT_THAT(*iterI, Eq(*iterJ));
144 const Tensor* elem = *iterI;
145 iterI++;
146 // iterJ points to the same element as before even though iterI was moved
147 // forward.
148 EXPECT_THAT(elem, Eq(*iterJ));
149 EXPECT_THAT(*iterI, Not(Eq(*iterJ)));
150 // After both iterators are incremented the same number of times they should
151 // again point to the same element.
152 iterI++;
153 iterJ++;
154 iterJ++;
155 EXPECT_THAT(*iterI, Eq(*iterJ));
156 }
157
TEST_F(InputTensorListTest,Inlined_Iterate_PostincrementAndPreincrement)158 TEST_F(InputTensorListTest, Inlined_Iterate_PostincrementAndPreincrement) {
159 InputTensorList tensor_list = CreateInlined();
160 auto iterI = tensor_list.begin();
161 // If postincrement works as expected, iterJ will be set to the value of iterI
162 // before it is incremented.
163 auto iterJ = iterI++;
164 EXPECT_THAT(iterJ, Eq(tensor_list.begin()));
165 // If preincrement works as expected, iterK should be set to the value of
166 // iterJ after it is incremented, which is now the same as iterI.
167 auto iterK = ++iterJ;
168 EXPECT_THAT(iterK, Eq(iterJ));
169 EXPECT_THAT(iterK, Eq(iterI));
170 }
171
TEST_F(InputTensorListTest,Inlined_Index)172 TEST_F(InputTensorListTest, Inlined_Index) {
173 InputTensorList tensor_list = CreateInlined();
174 EXPECT_THAT(tensor_list[0]->shape(), Eq(TensorShape{1}));
175 EXPECT_THAT(tensor_list[1]->shape(), Eq(TensorShape{2}));
176 EXPECT_THAT(tensor_list[2]->shape(), Eq(TensorShape{3}));
177 }
178
TEST_F(InputTensorListTest,Inlined_SizeConstructorAndMutableIndex)179 TEST_F(InputTensorListTest, Inlined_SizeConstructorAndMutableIndex) {
180 InputTensorList tensor_list(3);
181 tensor_list[0] = &t1_;
182 tensor_list[1] = &t2_;
183 tensor_list[2] = &t3_;
184
185 EXPECT_THAT(tensor_list[0]->shape(), Eq(TensorShape{1}));
186 EXPECT_THAT(tensor_list[1]->shape(), Eq(TensorShape{2}));
187 EXPECT_THAT(tensor_list[2]->shape(), Eq(TensorShape{3}));
188 }
189
TEST_F(InputTensorListTest,Inlined_SizeConstructor_InitializesPointersToNull)190 TEST_F(InputTensorListTest, Inlined_SizeConstructor_InitializesPointersToNull) {
191 InputTensorList tensor_list(3);
192
193 EXPECT_THAT(tensor_list[0], Eq(nullptr));
194 EXPECT_THAT(tensor_list[1], Eq(nullptr));
195 EXPECT_THAT(tensor_list[2], Eq(nullptr));
196 }
197
TEST_F(InputTensorListTest,Allocated_Size)198 TEST_F(InputTensorListTest, Allocated_Size) {
199 InputTensorList tensor_list = CreateAllocated();
200 EXPECT_THAT(tensor_list.size(), Eq(6));
201 }
202
TEST_F(InputTensorListTest,Allocated_Iterate)203 TEST_F(InputTensorListTest, Allocated_Iterate) {
204 InputTensorList tensor_list = CreateAllocated();
205 auto iter = tensor_list.begin();
206 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{1}));
207 iter++;
208 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{2}));
209 iter++;
210 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{3}));
211 iter++;
212 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{4}));
213 iter++;
214 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{5}));
215 iter++;
216 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{6}));
217 iter++;
218 EXPECT_THAT(iter, Eq(tensor_list.end()));
219 }
220
TEST_F(InputTensorListTest,Allocated_MoveConstructor_Iterate)221 TEST_F(InputTensorListTest, Allocated_MoveConstructor_Iterate) {
222 InputTensorList moved_tensor_list = CreateAllocated();
223 InputTensorList tensor_list(std::move(moved_tensor_list));
224 auto iter = tensor_list.begin();
225 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{1}));
226 iter++;
227 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{2}));
228 iter++;
229 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{3}));
230 iter++;
231 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{4}));
232 iter++;
233 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{5}));
234 iter++;
235 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{6}));
236 iter++;
237 EXPECT_THAT(iter, Eq(tensor_list.end()));
238 }
239
TEST_F(InputTensorListTest,Allocated_MoveAssignment_Iterate)240 TEST_F(InputTensorListTest, Allocated_MoveAssignment_Iterate) {
241 InputTensorList moved_tensor_list = CreateAllocated();
242 // Initially, create the tensor list as an inlined tensor list before
243 // assigning it to an inlined InputTensorList via move assignment.
244 InputTensorList tensor_list = CreateInlined();
245 tensor_list = std::move(moved_tensor_list);
246 auto iter = tensor_list.begin();
247 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{1}));
248 iter++;
249 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{2}));
250 iter++;
251 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{3}));
252 iter++;
253 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{4}));
254 iter++;
255 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{5}));
256 iter++;
257 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{6}));
258 iter++;
259 EXPECT_THAT(iter, Eq(tensor_list.end()));
260
261 // Assigning back to the moved variable is valid.
262 moved_tensor_list = std::move(tensor_list);
263 iter = moved_tensor_list.begin();
264 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{1}));
265 iter++;
266 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{2}));
267 iter++;
268 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{3}));
269 iter++;
270 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{4}));
271 iter++;
272 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{5}));
273 iter++;
274 EXPECT_THAT((*iter)->shape(), Eq(TensorShape{6}));
275 iter++;
276 EXPECT_THAT(iter, Eq(moved_tensor_list.end()));
277 }
278
TEST_F(InputTensorListTest,Allocated_ForEachLoop)279 TEST_F(InputTensorListTest, Allocated_ForEachLoop) {
280 InputTensorList tensor_list = CreateAllocated();
281 uint64_t expected_size = 1;
282 for (const Tensor* t : tensor_list) {
283 EXPECT_THAT(t->shape().NumElements(), Eq(expected_size));
284 expected_size++;
285 }
286 }
287
TEST_F(InputTensorListTest,Allocated_Iterate_MultiPassGuarantee)288 TEST_F(InputTensorListTest, Allocated_Iterate_MultiPassGuarantee) {
289 // Ensure the iterator meets the multi-pass guarantee requirements required
290 // by forward iterators
291 // (https://en.cppreference.com/w/cpp/iterator/forward_iterator)
292 InputTensorList tensor_list = CreateAllocated();
293 auto iterI = tensor_list.begin();
294 auto iterJ = tensor_list.begin();
295 EXPECT_THAT(iterI, Eq(iterJ));
296 EXPECT_THAT(*iterI, Eq(*iterJ));
297 const Tensor* elem = *iterI;
298 iterI++;
299 // iterJ points to the same element as before even though iterI was moved
300 // forward.
301 EXPECT_THAT(elem, Eq(*iterJ));
302 EXPECT_THAT(*iterI, Not(Eq(*iterJ)));
303 // After both iterators are incremented the same number of times they should
304 // again point to the same element.
305 iterI++;
306 iterJ++;
307 iterJ++;
308 EXPECT_THAT(*iterI, Eq(*iterJ));
309 }
310
TEST_F(InputTensorListTest,Allocated_Iterate_PostincrementAndPreincrement)311 TEST_F(InputTensorListTest, Allocated_Iterate_PostincrementAndPreincrement) {
312 InputTensorList tensor_list = CreateAllocated();
313 auto iterI = tensor_list.begin();
314 // If postincrement works as expected, iterJ will be set to the value of iterI
315 // before it is incremented.
316 auto iterJ = iterI++;
317 EXPECT_THAT(iterJ, Eq(tensor_list.begin()));
318 // If preincrement works as expected, iterK should be set to the value of
319 // iterJ after it is incremented, which is now the same as iterI.
320 auto iterK = ++iterJ;
321 EXPECT_THAT(iterK, Eq(iterJ));
322 EXPECT_THAT(iterK, Eq(iterI));
323 }
324
TEST_F(InputTensorListTest,Allocated_Index)325 TEST_F(InputTensorListTest, Allocated_Index) {
326 InputTensorList tensor_list = CreateAllocated();
327 EXPECT_THAT(tensor_list[0]->shape(), Eq(TensorShape{1}));
328 EXPECT_THAT(tensor_list[1]->shape(), Eq(TensorShape{2}));
329 EXPECT_THAT(tensor_list[2]->shape(), Eq(TensorShape{3}));
330 EXPECT_THAT(tensor_list[3]->shape(), Eq(TensorShape{4}));
331 EXPECT_THAT(tensor_list[4]->shape(), Eq(TensorShape{5}));
332 EXPECT_THAT(tensor_list[5]->shape(), Eq(TensorShape{6}));
333 }
334
TEST_F(InputTensorListTest,Allocated_SizeConstructorAndMutableIndex)335 TEST_F(InputTensorListTest, Allocated_SizeConstructorAndMutableIndex) {
336 InputTensorList tensor_list(6);
337 tensor_list[0] = &t1_;
338 tensor_list[1] = &t2_;
339 tensor_list[2] = &t3_;
340 tensor_list[3] = &t4_;
341 tensor_list[4] = &t5_;
342 tensor_list[5] = &t6_;
343
344 EXPECT_THAT(tensor_list[0]->shape(), Eq(TensorShape{1}));
345 EXPECT_THAT(tensor_list[1]->shape(), Eq(TensorShape{2}));
346 EXPECT_THAT(tensor_list[2]->shape(), Eq(TensorShape{3}));
347 EXPECT_THAT(tensor_list[3]->shape(), Eq(TensorShape{4}));
348 EXPECT_THAT(tensor_list[4]->shape(), Eq(TensorShape{5}));
349 EXPECT_THAT(tensor_list[5]->shape(), Eq(TensorShape{6}));
350 }
351
TEST_F(InputTensorListTest,Allocated_SizeConstructor_InitializesPointersToNull)352 TEST_F(InputTensorListTest,
353 Allocated_SizeConstructor_InitializesPointersToNull) {
354 InputTensorList tensor_list(6);
355
356 EXPECT_THAT(tensor_list[0], Eq(nullptr));
357 EXPECT_THAT(tensor_list[1], Eq(nullptr));
358 EXPECT_THAT(tensor_list[2], Eq(nullptr));
359 EXPECT_THAT(tensor_list[3], Eq(nullptr));
360 EXPECT_THAT(tensor_list[4], Eq(nullptr));
361 EXPECT_THAT(tensor_list[5], Eq(nullptr));
362 }
363
364 } // namespace
365 } // namespace aggregation
366 } // namespace fcp
367