xref: /aosp_15_r20/external/executorch/kernels/test/op_clamp_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 <cmath>
10 #include <ostream>
11 
12 #include <executorch/kernels/test/FunctionHeaderWrapper.h> // Declares the operator
13 #include <executorch/kernels/test/TestUtil.h>
14 #include <executorch/kernels/test/supported_features.h>
15 #include <executorch/runtime/core/exec_aten/exec_aten.h>
16 #include <executorch/runtime/core/exec_aten/testing_util/tensor_factory.h>
17 #include <executorch/runtime/core/exec_aten/testing_util/tensor_util.h>
18 #include <executorch/runtime/core/exec_aten/util/scalar_type_util.h>
19 #include <executorch/runtime/core/exec_aten/util/tensor_util.h>
20 
21 #include <gtest/gtest.h>
22 
23 using namespace ::testing;
24 using exec_aten::ArrayRef;
25 using exec_aten::nullopt;
26 using exec_aten::optional;
27 using exec_aten::Scalar;
28 using exec_aten::ScalarType;
29 using exec_aten::Tensor;
30 using torch::executor::testing::TensorFactory;
31 
32 using OptScalar = exec_aten::optional<Scalar>;
33 
34 class OpClampOutTest : public OperatorTest {
35  protected:
op_clamp_out(const Tensor & self,const optional<Scalar> & min,const optional<Scalar> & max,Tensor & out)36   Tensor& op_clamp_out(
37       const Tensor& self,
38       const optional<Scalar>& min,
39       const optional<Scalar>& max,
40       Tensor& out) {
41     return torch::executor::aten::clamp_outf(context_, self, min, max, out);
42   }
43 
44   template <ScalarType DTYPE>
45   struct ClampTestCase {
46     using ctype = typename TensorFactory<DTYPE>::ctype;
47 
48     // Human-readable, unique title for the test case. Printed if the test
49     // fails.
50     const std::string title;
51     // Size vector for the input/output tensors.
52     const std::vector<int32_t> sizes;
53     // Data for the input tensor; must agree with `sizes`.
54     const std::vector<ctype> input_data;
55     // The (optional) min value to clamp to. Can be of any Scalar type.
56     const OptScalar min;
57     // The (optional) max value to clamp to. Can be of any Scalar type.
58     const OptScalar max;
59     // The expected output data when clamping `input_data` to `min`/`max`.
60     const std::vector<ctype> expected_data;
61   };
62 
63   /// Runs the provided test cases.
64   template <ScalarType DTYPE>
run_test_cases(std::vector<ClampTestCase<DTYPE>> test_cases)65   void run_test_cases(std::vector<ClampTestCase<DTYPE>> test_cases) {
66     TensorFactory<DTYPE> tf;
67     for (const auto& test_case : test_cases) {
68       SCOPED_TRACE(test_case.title); // Printed if the test fails
69 
70       Tensor in = tf.make(test_case.sizes, test_case.input_data);
71       Tensor out = tf.zeros(test_case.sizes);
72       Tensor ret = op_clamp_out(in, test_case.min, test_case.max, out);
73       EXPECT_TENSOR_EQ(out, ret);
74 
75       Tensor expected = tf.make(test_case.sizes, test_case.expected_data);
76       ET_CHECK_SAME_SHAPE_AND_DTYPE2(out, expected);
77       EXPECT_TENSOR_EQ(out, expected);
78     }
79   }
80 
81   template <ScalarType DTYPE>
run_unsigned_integer_test_cases()82   void run_unsigned_integer_test_cases() {
83     const std::vector<ClampTestCase<DTYPE>> test_cases = {
84         {
85             std::string(__func__) + ": Simple clamp",
86             {2, 2}, // sizes
87             {0, 1, 10, 100}, // input_data
88             OptScalar(1), // min
89             OptScalar(6), // max
90             {1, 1, 6, 6}, // expected_data
91         },
92         {
93             std::string(__func__) + ": No max",
94             {2, 2}, // sizes
95             {0, 1, 10, 100}, // input_data
96             OptScalar(1), // min
97             nullopt, // max
98             {1, 1, 10, 100}, // expected_data
99         },
100         {
101             std::string(__func__) + ": No min",
102             {2, 2}, // sizes
103             {0, 1, 10, 100}, // input_data
104             nullopt, // min
105             OptScalar(6), // max
106             {0, 1, 6, 6}, // expected_data
107         },
108         {
109             std::string(__func__) + ": min > max",
110             {2, 2}, // sizes
111             {0, 1, 10, 100}, // input_data
112             OptScalar(10), // min
113             OptScalar(6), // max
114             // Should set all elements to max.
115             {6, 6, 6, 6}, // expected_data
116         },
117     };
118 
119     run_test_cases(test_cases);
120   }
121 
122   // types.
123   template <ScalarType DTYPE>
run_signed_integer_test_cases()124   void run_signed_integer_test_cases() {
125     std::vector<ClampTestCase<DTYPE>> test_cases = {
126         {
127             std::string(__func__) + ": Simple negative/positive clamp",
128             {2, 2}, // sizes
129             {-10, -1, 1, 10}, // input_data
130             OptScalar(-5), // min
131             OptScalar(5), // max
132             {-5, -1, 1, 5}, // expected_data
133         },
134         {
135             std::string(__func__) + ": Simple negative-only clamp",
136             {2, 2}, // sizes
137             {-10, -5, 1, 10}, // input_data
138             OptScalar(-6), // min
139             OptScalar(-1), // max
140             {-6, -5, -1, -1}, // expected_data
141         },
142     };
143 
144     run_test_cases(test_cases);
145   }
146 
147   // Test cases that are compatible with float and double.
148   template <ScalarType DTYPE>
run_floating_point_test_cases()149   void run_floating_point_test_cases() {
150     using ctype = typename TensorFactory<DTYPE>::ctype;
151     using opt_infinity_type = std::conditional_t<
152         std::is_same<ctype, exec_aten::Half>::value,
153         float,
154         ctype>;
155     constexpr auto kInfinity = std::numeric_limits<ctype>::infinity();
156     const auto kOptInfinity =
157         OptScalar(static_cast<opt_infinity_type>(kInfinity));
158     const auto kOptMinusInfinity =
159         OptScalar(static_cast<opt_infinity_type>(-kInfinity));
160     std::vector<ClampTestCase<DTYPE>> test_cases = {
161         {
162             std::string(__func__) + ": Simple negative/positive clamp",
163             {2, 2}, // sizes
164             {-10.1, -1.1, 1.1, 10.1}, // input_data
165             OptScalar(-5.5), // min
166             OptScalar(5.5), // max
167             {-5.5, -1.1, 1.1, 5.5}, // expected_data
168         },
169         {
170             std::string(__func__) + ": Simple negative-only clamp",
171             {2, 2}, // sizes
172             {-10.1, -5.5, 1.1, 10.1}, // input_data
173             OptScalar(-6.6), // min
174             OptScalar(-1.1), // max
175             {-6.6, -5.5, -1.1, -1.1}, // expected_data
176         },
177         {
178             std::string(__func__) + ": Infinities are clamped",
179             {2, 2}, // sizes
180             {-kInfinity, -1.1, 1.1, kInfinity}, // input_data
181             OptScalar(-5.5), // min
182             OptScalar(5.5), // max
183             {-5.5, -1.1, 1.1, 5.5}, // expected_data
184         },
185         {
186             std::string(__func__) + ": Infinite min",
187             {2, 2}, // sizes
188             {-10.1, -1.1, 1.1, 10.1}, // input_data
189             kOptMinusInfinity, // min
190             OptScalar(5.5), // max
191             {-10.1, -1.1, 1.1, 5.5}, // expected_data
192         },
193         {
194             std::string(__func__) + ": Infinite max",
195             {2, 2}, // sizes
196             {-10.1, -1.1, 1.1, 10.1}, // input_data
197             OptScalar(-5.5), // min
198             kOptInfinity, // max
199             {-5.5, -1.1, 1.1, 10.1}, // expected_data
200         },
201         {
202             std::string(__func__) + ": NaN entries preserved",
203             {2, 2}, // sizes
204             {-10.1, NAN, NAN, 10.1}, // input_data
205             OptScalar(0.0), // min
206             OptScalar(0.0), // max
207             {0.0, NAN, NAN, 0.0}, // expected_data
208         },
209         {
210             std::string(__func__) + ": NaN min produces all NaN output",
211             {2, 2}, // sizes
212             {-10.1, -1.1, 1.1, 10.1}, // input_data
213             OptScalar(NAN), // min
214             OptScalar(5.5), // max
215             {NAN, NAN, NAN, NAN}, // expected_data
216         },
217         {
218             std::string(__func__) + ": NaN max produces all NaN output",
219             {2, 2}, // sizes
220             {-10.1, -1.1, 1.1, 10.1}, // input_data
221             OptScalar(-5.5), // min
222             OptScalar(NAN), // max
223             {NAN, NAN, NAN, NAN}, // expected_data
224         },
225     };
226 
227     run_test_cases(test_cases);
228   }
229 
230   // Tries clamping a DTYPE tensor to the provided value and expects it to die.
231   template <ScalarType DTYPE>
expect_bad_clamp_value_dies(Scalar bad_value)232   void expect_bad_clamp_value_dies(Scalar bad_value) {
233     TensorFactory<DTYPE> tf;
234     Tensor in = tf.ones({2, 2});
235     Tensor out = tf.zeros({2, 2});
236 
237     ET_EXPECT_KERNEL_FAILURE(
238         context_, op_clamp_out(in, /*min=*/bad_value, /*max=*/nullopt, out));
239     ET_EXPECT_KERNEL_FAILURE(
240         context_, op_clamp_out(in, /*min=*/nullopt, /*max=*/bad_value, out));
241     ET_EXPECT_KERNEL_FAILURE(
242         context_, op_clamp_out(in, /*min=*/bad_value, /*max=*/bad_value, out));
243   }
244 
245   // One of min and max should be non-null
expect_both_min_max_null_die()246   void expect_both_min_max_null_die() {
247     TensorFactory<ScalarType::Float> tf;
248     Tensor in = tf.ones({2, 2});
249     Tensor out = tf.zeros({2, 2});
250 
251     ET_EXPECT_KERNEL_FAILURE(
252         context_, op_clamp_out(in, /*min=*/nullopt, /*max=*/nullopt, out));
253   }
254 };
255 
256 class OpClampTensorOutTest : public OperatorTest {
257  protected:
op_clamp_tensor_out(const Tensor & self,const optional<Tensor> & min,const optional<Tensor> & max,Tensor & out)258   Tensor& op_clamp_tensor_out(
259       const Tensor& self,
260       const optional<Tensor>& min,
261       const optional<Tensor>& max,
262       Tensor& out) {
263     executorch::runtime::KernelRuntimeContext context{};
264     return torch::executor::aten::clamp_outf(context, self, min, max, out);
265   }
266 };
267 
268 /// Describes a test case, using tensors of the specified DTYPE.
269 // Runs test cases that are compatible with uint8_t, and thus all other real
270 // types. Cover the most cases here, since it's compatible with the most types.
271 // Runs test cases that are compatible with int8_t, and thus all signed real
TEST_F(OpClampOutTest,ByteTensors)272 TEST_F(OpClampOutTest, ByteTensors) {
273   run_unsigned_integer_test_cases<ScalarType::Byte>();
274 }
275 
TEST_F(OpClampOutTest,CharTensors)276 TEST_F(OpClampOutTest, CharTensors) {
277   run_unsigned_integer_test_cases<ScalarType::Char>();
278   run_signed_integer_test_cases<ScalarType::Char>();
279 }
280 
TEST_F(OpClampOutTest,ShortTensors)281 TEST_F(OpClampOutTest, ShortTensors) {
282   run_unsigned_integer_test_cases<ScalarType::Short>();
283   run_signed_integer_test_cases<ScalarType::Short>();
284 }
285 
TEST_F(OpClampOutTest,IntTensors)286 TEST_F(OpClampOutTest, IntTensors) {
287   run_unsigned_integer_test_cases<ScalarType::Int>();
288   run_signed_integer_test_cases<ScalarType::Int>();
289 }
290 
TEST_F(OpClampOutTest,LongTensors)291 TEST_F(OpClampOutTest, LongTensors) {
292   run_unsigned_integer_test_cases<ScalarType::Long>();
293   run_signed_integer_test_cases<ScalarType::Long>();
294 }
295 
TEST_F(OpClampOutTest,HalfTensors)296 TEST_F(OpClampOutTest, HalfTensors) {
297   // Note that the integer test cases test the situation where the min/max value
298   // Scalars are integer types, demonstrating that floating point types can be
299   // clamped to integer values.
300   run_unsigned_integer_test_cases<ScalarType::Half>();
301   run_signed_integer_test_cases<ScalarType::Half>();
302   run_floating_point_test_cases<ScalarType::Half>();
303 }
304 
TEST_F(OpClampOutTest,FloatTensors)305 TEST_F(OpClampOutTest, FloatTensors) {
306   // Note that the integer test cases test the situation where the min/max value
307   // Scalars are integer types, demonstrating that floating point types can be
308   // clamped to integer values.
309   run_unsigned_integer_test_cases<ScalarType::Float>();
310   run_signed_integer_test_cases<ScalarType::Float>();
311   run_floating_point_test_cases<ScalarType::Float>();
312 }
313 
TEST_F(OpClampOutTest,DoubleTensors)314 TEST_F(OpClampOutTest, DoubleTensors) {
315   // Note that the integer test cases test the situation where the min/max value
316   // Scalars are integer types, demonstrating that floating point types can be
317   // clamped to integer values.
318   run_unsigned_integer_test_cases<ScalarType::Double>();
319   run_signed_integer_test_cases<ScalarType::Double>();
320   run_floating_point_test_cases<ScalarType::Double>();
321 }
322 
323 //
324 // Don't test every type, just a representative sample: unsigned int, signed
325 // int, floating point.
326 //
327 
TEST_F(OpClampOutTest,ByteTensorNegativeClampDies)328 TEST_F(OpClampOutTest, ByteTensorNegativeClampDies) {
329   if (torch::executor::testing::SupportedFeatures::get()->is_aten) {
330     GTEST_SKIP() << "ATen kernel can handle negative clamp on byte tensor";
331   }
332   // Cannot be represented by a uint8_t.
333   expect_bad_clamp_value_dies<ScalarType::Byte>(-1);
334 }
335 
TEST_F(OpClampOutTest,ByteTensorTooLargeClampDies)336 TEST_F(OpClampOutTest, ByteTensorTooLargeClampDies) {
337   // Cannot be represented by a uint8_t.
338   expect_bad_clamp_value_dies<ScalarType::Byte>(256);
339 }
340 
TEST_F(OpClampOutTest,ByteTensorFloatingPointClampDies)341 TEST_F(OpClampOutTest, ByteTensorFloatingPointClampDies) {
342   // Cannot be represented by a uint8_t.
343   expect_bad_clamp_value_dies<ScalarType::Byte>(2.2);
344 }
345 
346 #ifndef USE_ATEN_LIB
TEST_F(OpClampOutTest,IntTensorTooSmallClampDies)347 TEST_F(OpClampOutTest, IntTensorTooSmallClampDies) {
348   // Cannot be represented by a int32_t.
349   expect_bad_clamp_value_dies<ScalarType::Int>(-2147483649);
350 }
351 
TEST_F(OpClampOutTest,IntTensorTooLargeClampDies)352 TEST_F(OpClampOutTest, IntTensorTooLargeClampDies) {
353   // Cannot be represented by a int32_t.
354   expect_bad_clamp_value_dies<ScalarType::Int>(2147483648);
355 }
356 #endif
357 
TEST_F(OpClampOutTest,IntTensorFloatingPointClampDies)358 TEST_F(OpClampOutTest, IntTensorFloatingPointClampDies) {
359   // Cannot be represented by a uint32_t.
360   expect_bad_clamp_value_dies<ScalarType::Int>(2.2);
361 }
362 
TEST_F(OpClampOutTest,FloatTensorTooSmallClampDies)363 TEST_F(OpClampOutTest, FloatTensorTooSmallClampDies) {
364   // Cannot be represented by a float.
365   expect_bad_clamp_value_dies<ScalarType::Float>(-3.41e+38);
366 }
367 
TEST_F(OpClampOutTest,FloatTensorTooLargeClampDies)368 TEST_F(OpClampOutTest, FloatTensorTooLargeClampDies) {
369   // Cannot be represented by a float.
370   expect_bad_clamp_value_dies<ScalarType::Float>(3.41e+38);
371 }
372 
TEST_F(OpClampOutTest,SimpleGeneratedCase)373 TEST_F(OpClampOutTest, SimpleGeneratedCase) {
374   TensorFactory<ScalarType::Float> tf;
375 
376   auto x = tf.make(
377       {10, 10},
378       {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
379        1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
380        1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
381        1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
382        1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
383        1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
384        1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
385        1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0});
386   auto y = OptScalar(-0.5);
387   auto z = OptScalar(0.5);
388   Tensor expected_result = tf.make(
389       {10, 10},
390       {0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
391        0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
392        0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
393        0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
394        0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
395        0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
396        0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
397        0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5});
398 
399   Tensor out = tf.zeros({10, 10});
400   Tensor ret = op_clamp_out(x, y, z, out);
401   EXPECT_TENSOR_CLOSE(out, expected_result);
402 }
403 
TEST_F(OpClampOutTest,DynamicShapeUpperBoundSameAsExpected)404 TEST_F(OpClampOutTest, DynamicShapeUpperBoundSameAsExpected) {
405   TensorFactory<ScalarType::Float> tf;
406 
407   auto x = tf.make(
408       {3, 2},
409       {0.6984410881996155,
410        0.5675464272499084,
411        0.8352431654930115,
412        0.2055988311767578,
413        0.593172013759613,
414        0.11234724521636963});
415   auto y = OptScalar(-0.5);
416   auto z = OptScalar(0.5);
417   Tensor expected_result = tf.make(
418       {3, 2}, {0.5, 0.5, 0.5, 0.2055988311767578, 0.5, 0.11234724521636963});
419 
420   Tensor out =
421       tf.zeros({3, 2}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND);
422   Tensor ret = op_clamp_out(x, y, z, out);
423   EXPECT_TENSOR_CLOSE(out, expected_result);
424 }
425 
TEST_F(OpClampOutTest,DynamicShapeUpperBoundLargerThanExpected)426 TEST_F(OpClampOutTest, DynamicShapeUpperBoundLargerThanExpected) {
427   GTEST_SKIP() << "Dynamic shape not supported";
428   TensorFactory<ScalarType::Float> tf;
429 
430   auto x = tf.make(
431       {3, 2},
432       {0.6984410881996155,
433        0.5675464272499084,
434        0.8352431654930115,
435        0.2055988311767578,
436        0.593172013759613,
437        0.11234724521636963});
438   auto y = OptScalar(-0.5);
439   auto z = OptScalar(0.5);
440   Tensor expected_result = tf.make(
441       {3, 2}, {0.5, 0.5, 0.5, 0.2055988311767578, 0.5, 0.11234724521636963});
442 
443   Tensor out =
444       tf.zeros({6, 4}, torch::executor::TensorShapeDynamism::DYNAMIC_BOUND);
445   Tensor ret = op_clamp_out(x, y, z, out);
446   EXPECT_TENSOR_CLOSE(out, expected_result);
447 }
448 
TEST_F(OpClampOutTest,DynamicShapeUnbound)449 TEST_F(OpClampOutTest, DynamicShapeUnbound) {
450   GTEST_SKIP() << "Dynamic shape not supported";
451   TensorFactory<ScalarType::Float> tf;
452 
453   auto x = tf.make(
454       {3, 2},
455       {0.6984410881996155,
456        0.5675464272499084,
457        0.8352431654930115,
458        0.2055988311767578,
459        0.593172013759613,
460        0.11234724521636963});
461   auto y = OptScalar(-0.5);
462   auto z = OptScalar(0.5);
463   Tensor expected_result = tf.make(
464       {3, 2}, {0.5, 0.5, 0.5, 0.2055988311767578, 0.5, 0.11234724521636963});
465 
466   Tensor out =
467       tf.zeros({1, 1}, torch::executor::TensorShapeDynamism::DYNAMIC_UNBOUND);
468   Tensor ret = op_clamp_out(x, y, z, out);
469   EXPECT_TENSOR_CLOSE(out, expected_result);
470 }
471 
TEST_F(OpClampTensorOutTest,SmokeTest)472 TEST_F(OpClampTensorOutTest, SmokeTest) {
473   TensorFactory<ScalarType::Byte> tf_in;
474   TensorFactory<ScalarType::Int> tf_min;
475   TensorFactory<ScalarType::Char> tf_max;
476   TensorFactory<ScalarType::Short> tf_out;
477 
478   Tensor in = tf_in.make({1, 1}, {3});
479   Tensor min = tf_min.make({1, 3}, {0, 1, 4});
480   Tensor max = tf_max.make({2, 1}, {2, 5});
481   Tensor out = tf_out.zeros({2, 3});
482   Tensor expected = tf_out.make({2, 3}, {2, 2, 2, 3, 3, 4});
483 
484   op_clamp_tensor_out(in, min, max, out);
485   EXPECT_TENSOR_EQ(out, expected);
486 }
487 
TEST_F(OpClampTensorOutTest,DowncastingSmokeTest)488 TEST_F(OpClampTensorOutTest, DowncastingSmokeTest) {
489   TensorFactory<ScalarType::Byte> tf_in;
490   TensorFactory<ScalarType::Short> tf_min;
491   TensorFactory<ScalarType::Int> tf_max;
492   TensorFactory<ScalarType::Char> tf_out;
493 
494   Tensor in = tf_in.make({}, {5});
495   Tensor min = tf_min.make({}, {-129});
496   Tensor max = tf_max.make({}, {300});
497   Tensor out = tf_out.zeros({});
498   Tensor expected = tf_out.make({}, {5});
499 
500   op_clamp_tensor_out(in, min, max, out);
501   EXPECT_TENSOR_EQ(out, expected);
502 }
503 
TEST_F(OpClampTensorOutTest,DowncastingSmokeTest2)504 TEST_F(OpClampTensorOutTest, DowncastingSmokeTest2) {
505   TensorFactory<ScalarType::Short> tf_in;
506   TensorFactory<ScalarType::Short> tf_min;
507   TensorFactory<ScalarType::Int> tf_max;
508   TensorFactory<ScalarType::Char> tf_out;
509 
510   Tensor in = tf_in.make({}, {301});
511   Tensor min = tf_min.make({}, {-129});
512   Tensor max = tf_max.make({}, {300});
513   Tensor out = tf_out.zeros({});
514   Tensor expected = tf_out.make({}, {44});
515 
516   op_clamp_tensor_out(in, min, max, out);
517   EXPECT_TENSOR_EQ(out, expected);
518 }
519 
TEST_F(OpClampTensorOutTest,DowncastingSmokeTest3)520 TEST_F(OpClampTensorOutTest, DowncastingSmokeTest3) {
521   TensorFactory<ScalarType::Short> tf_in;
522   TensorFactory<ScalarType::Short> tf_min;
523   TensorFactory<ScalarType::Int> tf_max;
524   TensorFactory<ScalarType::Char> tf_out;
525 
526   Tensor in = tf_in.make({}, {45});
527   Tensor min = tf_min.make({}, {-129});
528   Tensor max = tf_max.make({}, {300});
529   Tensor out = tf_out.zeros({});
530   Tensor expected = tf_out.make({}, {45});
531 
532   op_clamp_tensor_out(in, min, max, out);
533   EXPECT_TENSOR_EQ(out, expected);
534 }
535