1 /*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9 #include <executorch/extension/evalue_util/print_evalue.h>
10
11 #include <executorch/runtime/core/evalue.h>
12 #include <executorch/runtime/core/exec_aten/exec_aten.h>
13 #include <executorch/runtime/core/exec_aten/testing_util/tensor_factory.h>
14
15 #include <array>
16 #include <cmath>
17 #include <memory>
18 #include <sstream>
19
20 #include <gtest/gtest.h>
21
22 using namespace ::testing;
23 using exec_aten::ArrayRef;
24 using exec_aten::Scalar;
25 using exec_aten::ScalarType;
26 using torch::executor::BoxedEvalueList;
27 using torch::executor::EValue;
28 using torch::executor::testing::TensorFactory;
29
expect_output(const EValue & value,const char * expected)30 void expect_output(const EValue& value, const char* expected) {
31 std::ostringstream os;
32 os << value;
33 EXPECT_STREQ(expected, os.str().c_str());
34 }
35
36 //
37 // None
38 //
39
TEST(PrintEvalueTest,None)40 TEST(PrintEvalueTest, None) {
41 EValue value;
42 expect_output(value, "None");
43 }
44
45 //
46 // Bool
47 //
48
TEST(PrintEvalueTest,TrueBool)49 TEST(PrintEvalueTest, TrueBool) {
50 EValue value(exec_aten::Scalar(true));
51 expect_output(value, "True");
52 }
53
TEST(PrintEvalueTest,FalseBool)54 TEST(PrintEvalueTest, FalseBool) {
55 EValue value(exec_aten::Scalar(false));
56 expect_output(value, "False");
57 }
58
59 //
60 // Int
61 //
62
TEST(PrintEvalueTest,ZeroInt)63 TEST(PrintEvalueTest, ZeroInt) {
64 EValue value(exec_aten::Scalar(0));
65 expect_output(value, "0");
66 }
67
TEST(PrintEvalueTest,PositiveInt)68 TEST(PrintEvalueTest, PositiveInt) {
69 EValue value(exec_aten::Scalar(10));
70 expect_output(value, "10");
71 }
72
TEST(PrintEvalueTest,NegativeInt)73 TEST(PrintEvalueTest, NegativeInt) {
74 EValue value(exec_aten::Scalar(-10));
75 expect_output(value, "-10");
76 }
77
TEST(PrintEvalueTest,LargePositiveInt)78 TEST(PrintEvalueTest, LargePositiveInt) {
79 // A value that can't fit in 32 bits. Saying Scalar(<literal-long-long>) is
80 // ambiguous with c10::Scalar, so use a non-literal value.
81 constexpr int64_t i = 1152921504606846976;
82 EValue value = {exec_aten::Scalar(i)};
83 expect_output(value, "1152921504606846976");
84 }
85
TEST(PrintEvalueTest,LargeNegativeInt)86 TEST(PrintEvalueTest, LargeNegativeInt) {
87 // A value that can't fit in 32 bits. Saying Scalar(<literal-long-long>) is
88 // ambiguous with c10::Scalar, so use a non-literal value.
89 constexpr int64_t i = -1152921504606846976;
90 EValue value = {exec_aten::Scalar(i)};
91 expect_output(value, "-1152921504606846976");
92 }
93
94 //
95 // Double
96 //
97
TEST(PrintEvalueTest,ZeroDouble)98 TEST(PrintEvalueTest, ZeroDouble) {
99 EValue value(exec_aten::Scalar(0.0));
100 expect_output(value, "0.");
101 }
102
TEST(PrintEvalueTest,PositiveZeroDouble)103 TEST(PrintEvalueTest, PositiveZeroDouble) {
104 EValue value(exec_aten::Scalar(+0.0));
105 expect_output(value, "0.");
106 }
107
TEST(PrintEvalueTest,NegativeZeroDouble)108 TEST(PrintEvalueTest, NegativeZeroDouble) {
109 EValue value(exec_aten::Scalar(-0.0));
110 expect_output(value, "-0.");
111 }
112
TEST(PrintEvalueTest,PositiveIntegralDouble)113 TEST(PrintEvalueTest, PositiveIntegralDouble) {
114 EValue value(exec_aten::Scalar(10.0));
115 expect_output(value, "10.");
116 }
117
TEST(PrintEvalueTest,PositiveFractionalDouble)118 TEST(PrintEvalueTest, PositiveFractionalDouble) {
119 EValue value(exec_aten::Scalar(10.1));
120 expect_output(value, "10.1");
121 }
122
TEST(PrintEvalueTest,NegativeIntegralDouble)123 TEST(PrintEvalueTest, NegativeIntegralDouble) {
124 EValue value(exec_aten::Scalar(-10.0));
125 expect_output(value, "-10.");
126 }
127
TEST(PrintEvalueTest,NegativeFractionalDouble)128 TEST(PrintEvalueTest, NegativeFractionalDouble) {
129 EValue value(exec_aten::Scalar(-10.1));
130 expect_output(value, "-10.1");
131 }
132
TEST(PrintEvalueTest,PositiveInfinityDouble)133 TEST(PrintEvalueTest, PositiveInfinityDouble) {
134 EValue value((exec_aten::Scalar(INFINITY)));
135 expect_output(value, "inf");
136 }
137
TEST(PrintEvalueTest,NegativeInfinityDouble)138 TEST(PrintEvalueTest, NegativeInfinityDouble) {
139 EValue value((exec_aten::Scalar(-INFINITY)));
140 expect_output(value, "-inf");
141 }
142
TEST(PrintEvalueTest,NaNDouble)143 TEST(PrintEvalueTest, NaNDouble) {
144 EValue value((exec_aten::Scalar(NAN)));
145 expect_output(value, "nan");
146 }
147
148 // Don't test exponents or values with larger numbers of truncated decimal
149 // digits since their formatting may be system-dependent.
150
151 //
152 // String
153 //
154
TEST(PrintEvalueTest,EmptyString)155 TEST(PrintEvalueTest, EmptyString) {
156 std::string str = "";
157 EValue value(str.c_str(), str.size());
158 expect_output(value, "\"\"");
159 }
160
TEST(PrintEvalueTest,BasicString)161 TEST(PrintEvalueTest, BasicString) {
162 // No escaping required.
163 std::string str = "Test Data";
164 EValue value(str.c_str(), str.size());
165 expect_output(value, "\"Test Data\"");
166 }
167
TEST(PrintEvalueTest,EscapedString)168 TEST(PrintEvalueTest, EscapedString) {
169 // Contains characters that need to be escaped.
170 std::string str = "double quote: \" backslash: \\";
171 EValue value(str.c_str(), str.size());
172 expect_output(value, "\"double quote: \\\" backslash: \\\\\"");
173 }
174
175 //
176 // Tensor
177 //
178
TEST(PrintEvalueTest,BoolTensor)179 TEST(PrintEvalueTest, BoolTensor) {
180 TensorFactory<ScalarType::Bool> tf;
181 {
182 // Unelided
183 EValue value(tf.make({2, 2}, {true, false, true, false}));
184 expect_output(value, "tensor(sizes=[2, 2], [True, False, True, False])");
185 }
186 {
187 // Elided
188 EValue value(tf.make(
189 {5, 2},
190 {true, false, true, false, true, false, true, false, true, false}));
191 expect_output(
192 value,
193 "tensor(sizes=[5, 2], [True, False, True, ..., False, True, False])");
194 }
195 }
196
197 template <ScalarType DTYPE>
test_print_integer_tensor()198 void test_print_integer_tensor() {
199 TensorFactory<DTYPE> tf;
200 {
201 // Unelided
202 EValue value(tf.make({2, 2}, {1, 2, 3, 4}));
203 expect_output(value, "tensor(sizes=[2, 2], [1, 2, 3, 4])");
204 }
205 {
206 // Elided
207 EValue value(tf.make({5, 2}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}));
208 expect_output(value, "tensor(sizes=[5, 2], [1, 2, 3, ..., 8, 9, 10])");
209 }
210 }
211
TEST(PrintEvalueTest,ByteTensor)212 TEST(PrintEvalueTest, ByteTensor) {
213 test_print_integer_tensor<ScalarType::Byte>();
214 }
215
TEST(PrintEvalueTest,CharTensor)216 TEST(PrintEvalueTest, CharTensor) {
217 test_print_integer_tensor<ScalarType::Char>();
218 }
219
TEST(PrintEvalueTest,ShortTensor)220 TEST(PrintEvalueTest, ShortTensor) {
221 test_print_integer_tensor<ScalarType::Short>();
222 }
223
TEST(PrintEvalueTest,IntTensor)224 TEST(PrintEvalueTest, IntTensor) {
225 test_print_integer_tensor<ScalarType::Int>();
226 }
227
TEST(PrintEvalueTest,LongTensor)228 TEST(PrintEvalueTest, LongTensor) {
229 test_print_integer_tensor<ScalarType::Long>();
230 }
231
232 template <ScalarType DTYPE>
test_print_float_tensor()233 void test_print_float_tensor() {
234 TensorFactory<DTYPE> tf;
235 {
236 // Unelided
237 EValue value(tf.make({2, 2}, {1.0, 2.2, 3.3, 4.0}));
238 expect_output(value, "tensor(sizes=[2, 2], [1., 2.2, 3.3, 4.])");
239 }
240 {
241 // Elided
242 EValue value(
243 tf.make({5, 2}, {1.0, 2.2, 3.3, 4.0, 5.5, 6.6, 7.0, 8.8, 9.0, 10.1}));
244 expect_output(
245 value, "tensor(sizes=[5, 2], [1., 2.2, 3.3, ..., 8.8, 9., 10.1])");
246 }
247 }
248
TEST(PrintEvalueTest,FloatTensor)249 TEST(PrintEvalueTest, FloatTensor) {
250 test_print_float_tensor<ScalarType::Float>();
251 }
252
TEST(PrintEvalueTest,DoubleTensor)253 TEST(PrintEvalueTest, DoubleTensor) {
254 test_print_float_tensor<ScalarType::Double>();
255 }
256
257 //
258 // BoolList
259 //
260
TEST(PrintEvalueTest,UnelidedBoolLists)261 TEST(PrintEvalueTest, UnelidedBoolLists) {
262 // Default edge items is 3, so the longest unelided list length is 6.
263 std::array<bool, 6> list = {true, false, true, false, true, false};
264
265 // Important to test the cases where the length is less than or equal to the
266 // number of edge items (3 by default). Use bool as a proxy for this edge
267 // case; the other scalar types use the same underlying code, so they don't
268 // need to test this again.
269 {
270 EValue value(ArrayRef<bool>(list.data(), 0ul));
271 expect_output(value, "(len=0)[]");
272 }
273 {
274 EValue value(ArrayRef<bool>(list.data(), 1));
275 expect_output(value, "(len=1)[True]");
276 }
277 {
278 EValue value(ArrayRef<bool>(list.data(), 2));
279 expect_output(value, "(len=2)[True, False]");
280 }
281 {
282 EValue value(ArrayRef<bool>(list.data(), 3));
283 expect_output(value, "(len=3)[True, False, True]");
284 }
285 {
286 EValue value(ArrayRef<bool>(list.data(), 4));
287 expect_output(value, "(len=4)[True, False, True, False]");
288 }
289 {
290 EValue value(ArrayRef<bool>(list.data(), 5));
291 expect_output(value, "(len=5)[True, False, True, False, True]");
292 }
293 {
294 EValue value(ArrayRef<bool>(list.data(), 6));
295 expect_output(value, "(len=6)[True, False, True, False, True, False]");
296 }
297 }
298
TEST(PrintEvalueTest,ElidedBoolLists)299 TEST(PrintEvalueTest, ElidedBoolLists) {
300 std::array<bool, 10> list = {
301 true, false, true, false, true, false, true, false, true, false};
302
303 {
304 // Default edge items is 3, so the shortest elided list length is 7.
305 EValue value(ArrayRef<bool>(list.data(), 7));
306 expect_output(value, "(len=7)[True, False, True, ..., True, False, True]");
307 }
308 {
309 EValue value(ArrayRef<bool>(list.data(), 8));
310 expect_output(value, "(len=8)[True, False, True, ..., False, True, False]");
311 }
312 {
313 // Multi-digit length.
314 EValue value(ArrayRef<bool>(list.data(), 10));
315 expect_output(
316 value, "(len=10)[True, False, True, ..., False, True, False]");
317 }
318 }
319
320 //
321 // IntList
322 //
323
TEST(PrintEvalueTest,UnelidedIntLists)324 TEST(PrintEvalueTest, UnelidedIntLists) {
325 // Default edge items is 3, so the longest unelided list length is 6. EValue
326 // treats int lists specially, and must be constructed from a BoxedEvalueList
327 // instead of from an ArrayRef.
328 std::array<EValue, 6> values = {
329 Scalar(-2), Scalar(-1), Scalar(0), Scalar(1), Scalar(2), Scalar(3)};
330 std::array<EValue*, values.size()> wrapped_values = {
331 &values[0],
332 &values[1],
333 &values[2],
334 &values[3],
335 &values[4],
336 &values[5],
337 };
338 // Memory that BoxedEvalueList will use to unpack wrapped_values into an
339 // ArrayRef.
340 std::array<int64_t, wrapped_values.size()> unwrapped_values;
341
342 {
343 BoxedEvalueList<int64_t> list(
344 wrapped_values.data(), unwrapped_values.data(), 0);
345 EValue value(list);
346 expect_output(value, "(len=0)[]");
347 }
348 {
349 BoxedEvalueList<int64_t> list(
350 wrapped_values.data(), unwrapped_values.data(), 3);
351 EValue value(list);
352 expect_output(value, "(len=3)[-2, -1, 0]");
353 }
354 {
355 BoxedEvalueList<int64_t> list(
356 wrapped_values.data(), unwrapped_values.data(), 6);
357 EValue value(list);
358 expect_output(value, "(len=6)[-2, -1, 0, 1, 2, 3]");
359 }
360 }
361
TEST(PrintEvalueTest,ElidedIntLists)362 TEST(PrintEvalueTest, ElidedIntLists) {
363 std::array<EValue, 10> values = {
364 Scalar(-4),
365 Scalar(-3),
366 Scalar(-2),
367 Scalar(-1),
368 Scalar(0),
369 Scalar(1),
370 Scalar(2),
371 Scalar(3),
372 Scalar(4),
373 Scalar(5),
374 };
375 std::array<EValue*, values.size()> wrapped_values = {
376 &values[0],
377 &values[1],
378 &values[2],
379 &values[3],
380 &values[4],
381 &values[5],
382 &values[6],
383 &values[7],
384 &values[8],
385 &values[9],
386 };
387 // Memory that BoxedEvalueList will use to unpack wrapped_values into an
388 // ArrayRef.
389 std::array<int64_t, wrapped_values.size()> unwrapped_values;
390
391 {
392 // Default edge items is 3, so the shortest elided list length is 7.
393 BoxedEvalueList<int64_t> list(
394 wrapped_values.data(), unwrapped_values.data(), 7);
395 EValue value(list);
396 expect_output(value, "(len=7)[-4, -3, -2, ..., 0, 1, 2]");
397 }
398 {
399 BoxedEvalueList<int64_t> list(
400 wrapped_values.data(), unwrapped_values.data(), 8);
401 EValue value(list);
402 expect_output(value, "(len=8)[-4, -3, -2, ..., 1, 2, 3]");
403 }
404 {
405 // Multi-digit length.
406 BoxedEvalueList<int64_t> list(
407 wrapped_values.data(), unwrapped_values.data(), 10);
408 EValue value(list);
409 expect_output(value, "(len=10)[-4, -3, -2, ..., 3, 4, 5]");
410 }
411 }
412
413 //
414 // DoubleList
415 //
416
TEST(PrintEvalueTest,UnelidedDoubleLists)417 TEST(PrintEvalueTest, UnelidedDoubleLists) {
418 // Default edge items is 3, so the longest unelided list length is 6.
419 std::array<double, 6> list = {-2.2, -1, 0, INFINITY, NAN, 3.3};
420
421 {
422 EValue value(ArrayRef<double>(list.data(), 0ul));
423 expect_output(value, "(len=0)[]");
424 }
425 {
426 EValue value(ArrayRef<double>(list.data(), 3));
427 expect_output(value, "(len=3)[-2.2, -1., 0.]");
428 }
429 {
430 EValue value(ArrayRef<double>(list.data(), 6));
431 expect_output(value, "(len=6)[-2.2, -1., 0., inf, nan, 3.3]");
432 }
433 }
434
TEST(PrintEvalueTest,ElidedDoubleLists)435 TEST(PrintEvalueTest, ElidedDoubleLists) {
436 std::array<double, 10> list = {
437 -4.4, -3.0, -2.2, -1, 0, INFINITY, NAN, 3.3, 4.0, 5.5};
438
439 {
440 // Default edge items is 3, so the shortest elided list length is 7.
441 EValue value(ArrayRef<double>(list.data(), 7));
442 expect_output(value, "(len=7)[-4.4, -3., -2.2, ..., 0., inf, nan]");
443 }
444 {
445 EValue value(ArrayRef<double>(list.data(), 8));
446 expect_output(value, "(len=8)[-4.4, -3., -2.2, ..., inf, nan, 3.3]");
447 }
448 {
449 // Multi-digit length.
450 EValue value(ArrayRef<double>(list.data(), 10));
451 expect_output(value, "(len=10)[-4.4, -3., -2.2, ..., 3.3, 4., 5.5]");
452 }
453 }
454
455 //
456 // TensorList
457 //
458
expect_tensor_list_output(size_t num_tensors,const char * expected)459 void expect_tensor_list_output(size_t num_tensors, const char* expected) {
460 TensorFactory<ScalarType::Float> tf;
461
462 std::array<EValue, 10> values = {
463 tf.make({2, 2}, {0, 0, 0, 0}),
464 tf.make({2, 2}, {1, 1, 1, 1}),
465 tf.make({2, 2}, {2, 2, 2, 2}),
466 tf.make({2, 2}, {3, 3, 3, 3}),
467 tf.make({2, 2}, {4, 4, 4, 4}),
468 tf.make({2, 2}, {5, 5, 5, 5}),
469 tf.make({2, 2}, {6, 6, 6, 6}),
470 tf.make({2, 2}, {7, 7, 7, 7}),
471 tf.make({2, 2}, {8, 8, 8, 8}),
472 tf.make({2, 2}, {9, 9, 9, 9}),
473 };
474 std::array<EValue*, values.size()> wrapped_values = {
475 &values[0],
476 &values[1],
477 &values[2],
478 &values[3],
479 &values[4],
480 &values[5],
481 &values[6],
482 &values[7],
483 &values[8],
484 &values[9],
485 };
486 // Memory that BoxedEvalueList will use to assemble a contiguous array of
487 // Tensor entries. It's important not to destroy these entries, because the
488 // values list will own the underlying Tensors.
489 auto unwrapped_values_memory = std::make_unique<uint8_t[]>(
490 sizeof(exec_aten::Tensor) * wrapped_values.size());
491 exec_aten::Tensor* unwrapped_values =
492 reinterpret_cast<exec_aten::Tensor*>(unwrapped_values_memory.get());
493 #if USE_ATEN_LIB
494 // Must be initialized because BoxedEvalueList will use operator=() on each
495 // entry. But we can't do this in non-ATen mode because
496 // torch::executor::Tensor doesn't have a default constructor.
497 for (int i = 0; i < wrapped_values.size(); ++i) {
498 new (&unwrapped_values[i]) at::Tensor();
499 }
500 #endif
501
502 ASSERT_LE(num_tensors, wrapped_values.size());
503 BoxedEvalueList<exec_aten::Tensor> list(
504 wrapped_values.data(), unwrapped_values, num_tensors);
505 EValue value(list);
506 expect_output(value, expected);
507 }
508
TEST(PrintEvalueTest,EmptyTensorListIsOnOneLine)509 TEST(PrintEvalueTest, EmptyTensorListIsOnOneLine) {
510 expect_tensor_list_output(0, "(len=0)[]");
511 }
512
TEST(PrintEvalueTest,SingleTensorListIsOnOneLine)513 TEST(PrintEvalueTest, SingleTensorListIsOnOneLine) {
514 expect_tensor_list_output(
515 1, "(len=1)[tensor(sizes=[2, 2], [0., 0., 0., 0.])]");
516 }
517
TEST(PrintEvalueTest,AllTensorListEntriesArePrinted)518 TEST(PrintEvalueTest, AllTensorListEntriesArePrinted) {
519 expect_tensor_list_output(
520 10,
521 "(len=10)[\n"
522 " [0]: tensor(sizes=[2, 2], [0., 0., 0., 0.]),\n"
523 " [1]: tensor(sizes=[2, 2], [1., 1., 1., 1.]),\n"
524 " [2]: tensor(sizes=[2, 2], [2., 2., 2., 2.]),\n"
525 // Inner entries are never elided.
526 " [3]: tensor(sizes=[2, 2], [3., 3., 3., 3.]),\n"
527 " [4]: tensor(sizes=[2, 2], [4., 4., 4., 4.]),\n"
528 " [5]: tensor(sizes=[2, 2], [5., 5., 5., 5.]),\n"
529 " [6]: tensor(sizes=[2, 2], [6., 6., 6., 6.]),\n"
530 " [7]: tensor(sizes=[2, 2], [7., 7., 7., 7.]),\n"
531 " [8]: tensor(sizes=[2, 2], [8., 8., 8., 8.]),\n"
532 " [9]: tensor(sizes=[2, 2], [9., 9., 9., 9.]),\n"
533 "]");
534 }
535
536 //
537 // ListOptionalTensor
538 //
539
expect_list_optional_tensor_output(size_t num_tensors,const char * expected)540 void expect_list_optional_tensor_output(
541 size_t num_tensors,
542 const char* expected) {
543 TensorFactory<ScalarType::Float> tf;
544
545 std::array<EValue, 5> values = {
546 tf.make({2, 2}, {0, 0, 0, 0}),
547 tf.make({2, 2}, {2, 2, 2, 2}),
548 tf.make({2, 2}, {4, 4, 4, 4}),
549 tf.make({2, 2}, {6, 6, 6, 6}),
550 tf.make({2, 2}, {8, 8, 8, 8}),
551 };
552 std::array<EValue*, 10> wrapped_values = {
553 nullptr, // None is represented by a nullptr in the wrapped array.
554 &values[0],
555 nullptr,
556 &values[1],
557 nullptr,
558 &values[2],
559 nullptr,
560 &values[3],
561 nullptr,
562 &values[4],
563 };
564 // Memory that BoxedEvalueList will use to assemble a contiguous array of
565 // optional<Tensor> entries. It's important not to destroy these entries,
566 // because the values list will own the underlying Tensors.
567 auto unwrapped_values_memory = std::make_unique<uint8_t[]>(
568 sizeof(exec_aten::optional<exec_aten::Tensor>) * wrapped_values.size());
569 exec_aten::optional<exec_aten::Tensor>* unwrapped_values =
570 reinterpret_cast<exec_aten::optional<exec_aten::Tensor>*>(
571 unwrapped_values_memory.get());
572 // Must be initialized because BoxedEvalueList will use operator=() on each
573 // entry.
574 for (int i = 0; i < wrapped_values.size(); ++i) {
575 new (&unwrapped_values[i]) exec_aten::optional<exec_aten::Tensor>();
576 }
577
578 ASSERT_LE(num_tensors, wrapped_values.size());
579 BoxedEvalueList<exec_aten::optional<exec_aten::Tensor>> list(
580 wrapped_values.data(), unwrapped_values, num_tensors);
581 EValue value(list);
582 expect_output(value, expected);
583 }
584
TEST(PrintEvalueTest,EmptyListOptionalTensorIsOnOneLine)585 TEST(PrintEvalueTest, EmptyListOptionalTensorIsOnOneLine) {
586 expect_list_optional_tensor_output(0, "(len=0)[]");
587 }
588
TEST(PrintEvalueTest,SingleListOptionalTensorIsOnOneLine)589 TEST(PrintEvalueTest, SingleListOptionalTensorIsOnOneLine) {
590 expect_list_optional_tensor_output(1, "(len=1)[None]");
591 }
592
TEST(PrintEvalueTest,AllListOptionalTensorEntriesArePrinted)593 TEST(PrintEvalueTest, AllListOptionalTensorEntriesArePrinted) {
594 expect_list_optional_tensor_output(
595 10,
596 "(len=10)[\n"
597 " [0]: None,\n"
598 " [1]: tensor(sizes=[2, 2], [0., 0., 0., 0.]),\n"
599 " [2]: None,\n"
600 // Inner entries are never elided.
601 " [3]: tensor(sizes=[2, 2], [2., 2., 2., 2.]),\n"
602 " [4]: None,\n"
603 " [5]: tensor(sizes=[2, 2], [4., 4., 4., 4.]),\n"
604 " [6]: None,\n"
605 " [7]: tensor(sizes=[2, 2], [6., 6., 6., 6.]),\n"
606 " [8]: None,\n"
607 " [9]: tensor(sizes=[2, 2], [8., 8., 8., 8.]),\n"
608 "]");
609 }
610
611 //
612 // Unknown tag
613 //
614
TEST(PrintEvalueTest,UnknownTag)615 TEST(PrintEvalueTest, UnknownTag) {
616 EValue value;
617 value.tag = static_cast<torch::executor::Tag>(5555);
618 expect_output(value, "<Unknown EValue tag 5555>");
619 }
620
621 //
622 // evalue_edge_items
623 //
624 // Use double as a proxy for testing the edge_items logic; the other scalar
625 // types use the same underlying code, so they don't need to test this again.
626 //
627
TEST(PrintEvalueTest,EdgeItemsOverride)628 TEST(PrintEvalueTest, EdgeItemsOverride) {
629 std::array<double, 7> list = {-3.0, -2.2, -1, 0, 3.3, 4.0, 5.5};
630 EValue value(ArrayRef<double>(list.data(), 7));
631
632 {
633 // Default edge items is 3, so this should elide.
634 std::ostringstream os;
635 os << value;
636 EXPECT_STREQ(
637 os.str().c_str(), "(len=7)[-3., -2.2, -1., ..., 3.3, 4., 5.5]");
638 }
639 {
640 // Override to one edge item.
641 std::ostringstream os;
642 os << torch::executor::util::evalue_edge_items(1) << value;
643 EXPECT_STREQ(os.str().c_str(), "(len=7)[-3., ..., 5.5]");
644 }
645 {
646 // Override to more edge items than the list contains, removing the elision.
647 std::ostringstream os;
648 os << torch::executor::util::evalue_edge_items(20) << value;
649 EXPECT_STREQ(os.str().c_str(), "(len=7)[-3., -2.2, -1., 0., 3.3, 4., 5.5]");
650 }
651 }
652
TEST(PrintEvalueTest,EdgeItemsDefaults)653 TEST(PrintEvalueTest, EdgeItemsDefaults) {
654 std::array<double, 7> list = {-3.0, -2.2, -1, 0, 3.3, 4.0, 5.5};
655 EValue value(ArrayRef<double>(list.data(), 7));
656
657 {
658 // Default edge items is 3, so this should elide.
659 std::ostringstream os;
660 os << value;
661 EXPECT_STREQ(
662 os.str().c_str(), "(len=7)[-3., -2.2, -1., ..., 3.3, 4., 5.5]");
663 }
664 {
665 // A value of zero should be the same as the default.
666 std::ostringstream os;
667 os << torch::executor::util::evalue_edge_items(0) << value;
668 EXPECT_STREQ(
669 os.str().c_str(), "(len=7)[-3., -2.2, -1., ..., 3.3, 4., 5.5]");
670 }
671 {
672 // A negative should be the same as the default.
673 std::ostringstream os;
674 os << torch::executor::util::evalue_edge_items(-5) << value;
675 EXPECT_STREQ(
676 os.str().c_str(), "(len=7)[-3., -2.2, -1., ..., 3.3, 4., 5.5]");
677 }
678 }
679
TEST(PrintEvalueTest,EdgeItemsSingleStream)680 TEST(PrintEvalueTest, EdgeItemsSingleStream) {
681 std::array<double, 7> list = {-3.0, -2.2, -1, 0, 3.3, 4.0, 5.5};
682 EValue value(ArrayRef<double>(list.data(), 7));
683 std::ostringstream os_before;
684
685 // Print to the same stream multiple times, showing that evalue_edge_items
686 // can be changed, and is sticky.
687 std::ostringstream os;
688 os << "default: " << value << "\n";
689 os << " 1: " << torch::executor::util::evalue_edge_items(1) << value
690 << "\n";
691 os << "still 1: " << value << "\n";
692 os << "default: " << torch::executor::util::evalue_edge_items(0) << value
693 << "\n";
694 os << " 20: " << torch::executor::util::evalue_edge_items(20) << value
695 << "\n";
696 EXPECT_STREQ(
697 os.str().c_str(),
698 "default: (len=7)[-3., -2.2, -1., ..., 3.3, 4., 5.5]\n"
699 " 1: (len=7)[-3., ..., 5.5]\n"
700 "still 1: (len=7)[-3., ..., 5.5]\n"
701 "default: (len=7)[-3., -2.2, -1., ..., 3.3, 4., 5.5]\n"
702 " 20: (len=7)[-3., -2.2, -1., 0., 3.3, 4., 5.5]\n");
703
704 // The final value of 20 does not affect other streams, whether they were
705 // created before or after the modified stream.
706 os_before << value;
707 EXPECT_STREQ(
708 os_before.str().c_str(), "(len=7)[-3., -2.2, -1., ..., 3.3, 4., 5.5]");
709
710 std::ostringstream os_after;
711 os_after << value;
712 EXPECT_STREQ(
713 os_after.str().c_str(), "(len=7)[-3., -2.2, -1., ..., 3.3, 4., 5.5]");
714 }
715
716 // Demonstrate the evalue_edge_items affects the data lists inside tensors.
TEST(PrintEvalueTest,EdgeItemsAffectsTensorData)717 TEST(PrintEvalueTest, EdgeItemsAffectsTensorData) {
718 TensorFactory<ScalarType::Double> tf;
719 EValue value(tf.make(
720 {5, 1, 1, 2}, {1.0, 2.2, 3.3, 4.0, 5.5, 6.6, 7.0, 8.8, 9.0, 10.1}));
721
722 std::ostringstream os;
723 os << value << "\n";
724 os << torch::executor::util::evalue_edge_items(1) << value << "\n";
725 EXPECT_STREQ(
726 os.str().c_str(),
727 "tensor(sizes=[5, 1, 1, 2], [1., 2.2, 3.3, ..., 8.8, 9., 10.1])\n"
728 // Notice that it doesn't affect the sizes list, which is always printed
729 // in full.
730 "tensor(sizes=[5, 1, 1, 2], [1., ..., 10.1])\n");
731 }
732
733 //
734 // Long list wrapping.
735 //
736 // Use double as a proxy for testing the wrapping logic; the other scalar
737 // types use the same underlying code, so they don't need to test this again.
738 //
739
740 // Duplicates the internal value in the cpp file under test.
741 constexpr size_t kItemsPerLine = 10;
742
TEST(PrintEvalueTest,ListWrapping)743 TEST(PrintEvalueTest, ListWrapping) {
744 // A large list of scalars.
745 std::array<double, 100> list;
746 for (int i = 0; i < list.size(); ++i) {
747 list[i] = static_cast<double>(i);
748 }
749
750 {
751 // Should elide by default and print on a single line.
752 EValue value(ArrayRef<double>(list.data(), list.size()));
753
754 std::ostringstream os;
755 os << value;
756 EXPECT_STREQ(os.str().c_str(), "(len=100)[0., 1., 2., ..., 97., 98., 99.]");
757 }
758 {
759 // Exactly the per-line length should not wrap when increasing the number of
760 // edge items to disable elision.
761 EValue value(ArrayRef<double>(list.data(), kItemsPerLine));
762
763 std::ostringstream os;
764 os << torch::executor::util::evalue_edge_items(1000) << value;
765 EXPECT_STREQ(
766 os.str().c_str(), "(len=10)[0., 1., 2., 3., 4., 5., 6., 7., 8., 9.]");
767 }
768 {
769 // One more than the per-line length should wrap; no elision.
770 EValue value(ArrayRef<double>(list.data(), kItemsPerLine + 1));
771
772 std::ostringstream os;
773 os << torch::executor::util::evalue_edge_items(1000) << value;
774 EXPECT_STREQ(
775 os.str().c_str(),
776 "(len=11)[\n"
777 " 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., \n"
778 " 10., \n"
779 "]");
780 }
781 {
782 // Exactly twice the per-line length, without elision.
783 EValue value(ArrayRef<double>(list.data(), kItemsPerLine * 2));
784
785 std::ostringstream os;
786 os << torch::executor::util::evalue_edge_items(1000) << value;
787 EXPECT_STREQ(
788 os.str().c_str(),
789 "(len=20)[\n"
790 " 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., \n"
791 " 10., 11., 12., 13., 14., 15., 16., 17., 18., 19., \n"
792 // Make sure there is no extra newline here.
793 "]");
794 }
795 {
796 // Exactly one whole line, with elision.
797 EValue value(ArrayRef<double>(list.data(), kItemsPerLine * 3));
798
799 std::ostringstream os;
800 os << torch::executor::util::evalue_edge_items(kItemsPerLine) << value;
801 EXPECT_STREQ(
802 os.str().c_str(),
803 "(len=30)[\n"
804 " 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., \n"
805 // Elision always on its own line when wrapping.
806 " ...,\n"
807 " 20., 21., 22., 23., 24., 25., 26., 27., 28., 29., \n"
808 "]");
809 }
810 {
811 // Edge item count slightly larger than per-line length, with elision.
812 EValue value(ArrayRef<double>(list.data(), kItemsPerLine * 3));
813
814 std::ostringstream os;
815 os << torch::executor::util::evalue_edge_items(kItemsPerLine + 1) << value;
816 EXPECT_STREQ(
817 os.str().c_str(),
818 "(len=30)[\n"
819 " 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., \n"
820 " 10., \n"
821 // Elision always on its own line when wrapping.
822 " ...,\n"
823 // The ragged line always comes just after the elision so that
824 // we will end on a full line.
825 " 19., \n"
826 " 20., 21., 22., 23., 24., 25., 26., 27., 28., 29., \n"
827 "]");
828 }
829 {
830 // Large wrapped, ragged, elided example.
831 EValue value(ArrayRef<double>(list.data(), list.size()));
832
833 std::ostringstream os;
834 os << torch::executor::util::evalue_edge_items(33) << value;
835 EXPECT_STREQ(
836 os.str().c_str(),
837 "(len=100)[\n"
838 " 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., \n"
839 " 10., 11., 12., 13., 14., 15., 16., 17., 18., 19., \n"
840 " 20., 21., 22., 23., 24., 25., 26., 27., 28., 29., \n"
841 " 30., 31., 32., \n"
842 " ...,\n"
843 " 67., 68., 69., \n"
844 " 70., 71., 72., 73., 74., 75., 76., 77., 78., 79., \n"
845 " 80., 81., 82., 83., 84., 85., 86., 87., 88., 89., \n"
846 " 90., 91., 92., 93., 94., 95., 96., 97., 98., 99., \n"
847 "]");
848 }
849 }
850
TEST(PrintEvalueTest,WrappedTensorData)851 TEST(PrintEvalueTest, WrappedTensorData) {
852 TensorFactory<ScalarType::Double> tf;
853 // A tensor with a large number of elements.
854 EValue value(tf.ones({10, 10}));
855
856 std::ostringstream os;
857 os << torch::executor::util::evalue_edge_items(33) << value;
858 EXPECT_STREQ(
859 os.str().c_str(),
860 "tensor(sizes=[10, 10], [\n"
861 " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., \n"
862 " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., \n"
863 " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., \n"
864 " 1., 1., 1., \n"
865 " ...,\n"
866 " 1., 1., 1., \n"
867 " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., \n"
868 " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., \n"
869 " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., \n"
870 "])");
871 }
872
TEST(PrintEvalueTest,WrappedTensorSizes)873 TEST(PrintEvalueTest, WrappedTensorSizes) {
874 TensorFactory<ScalarType::Double> tf;
875
876 {
877 // A tensor with enough dimensions that the sizes list is wrapped, but
878 // the data is not.
879 std::vector<int32_t> sizes(kItemsPerLine + 1, 1);
880 sizes[0] = 5;
881 EValue value(tf.ones(sizes));
882
883 std::ostringstream os;
884 os << value;
885 EXPECT_STREQ(
886 os.str().c_str(),
887 "tensor(sizes=[\n"
888 " 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, \n"
889 " 1, \n"
890 "], [1., 1., 1., 1., 1.])");
891 }
892 {
893 // Both sizes and data are wrapped.
894 std::vector<int32_t> sizes(kItemsPerLine + 1, 1);
895 sizes[0] = 100;
896 EValue value(tf.ones(sizes));
897
898 std::ostringstream os;
899 os << torch::executor::util::evalue_edge_items(15) << value;
900 EXPECT_STREQ(
901 os.str().c_str(),
902 "tensor(sizes=[\n"
903 " 100, 1, 1, 1, 1, 1, 1, 1, 1, 1, \n"
904 " 1, \n"
905 // TODO(T159700776): Indent this further to look more like python.
906 "], [\n"
907 " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., \n"
908 " 1., 1., 1., 1., 1., \n"
909 " ...,\n"
910 " 1., 1., 1., 1., 1., \n"
911 " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., \n"
912 "])");
913 }
914 }
915
TEST(PrintEvalueTest,WrappedTensorLists)916 TEST(PrintEvalueTest, WrappedTensorLists) {
917 TensorFactory<ScalarType::Float> tf;
918
919 std::array<EValue, 2> values = {
920 // Tensors that are large enough for their data to wrap.
921 tf.ones({10, 10}),
922 tf.ones({11, 11}),
923 };
924 std::array<EValue*, values.size()> wrapped_values = {
925 &values[0],
926 &values[1],
927 };
928 // Memory that BoxedEvalueList will use to assemble a contiguous array of
929 // Tensor entries. It's important not to destroy these entries, because the
930 // values list will own the underlying Tensors.
931 auto unwrapped_values_memory = std::make_unique<uint8_t[]>(
932 sizeof(exec_aten::Tensor) * wrapped_values.size());
933 exec_aten::Tensor* unwrapped_values =
934 reinterpret_cast<exec_aten::Tensor*>(unwrapped_values_memory.get());
935 #if USE_ATEN_LIB
936 // Must be initialized because BoxedEvalueList will use operator=() on each
937 // entry. But we can't do this in non-ATen mode because
938 // torch::executor::Tensor doesn't have a default constructor.
939 for (int i = 0; i < wrapped_values.size(); ++i) {
940 new (&unwrapped_values[i]) at::Tensor();
941 }
942 #endif
943
944 // Demonstrate the formatting when printing a list with multiple tensors.
945 BoxedEvalueList<exec_aten::Tensor> list(
946 wrapped_values.data(), unwrapped_values, wrapped_values.size());
947 EValue value(list);
948
949 std::ostringstream os;
950 os << torch::executor::util::evalue_edge_items(15) << value;
951 EXPECT_STREQ(
952 os.str().c_str(),
953 "(len=2)[\n"
954 " [0]: tensor(sizes=[10, 10], [\n"
955 // TODO(T159700776): Indent these entries further to look more like
956 // python.
957 " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., \n"
958 " 1., 1., 1., 1., 1., \n"
959 " ...,\n"
960 " 1., 1., 1., 1., 1., \n"
961 " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., \n"
962 "]),\n"
963 " [1]: tensor(sizes=[11, 11], [\n"
964 " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., \n"
965 " 1., 1., 1., 1., 1., \n"
966 " ...,\n"
967 " 1., 1., 1., 1., 1., \n"
968 " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., \n"
969 "]),\n"
970 "]");
971 }
972