xref: /aosp_15_r20/external/executorch/extension/evalue_util/test/print_evalue_test.cpp (revision 523fa7a60841cd1ecfb9cc4201f1ca8b03ed023a)
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