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