From e26180a1c4fb04e26fc216d7f3248635da253f3e Mon Sep 17 00:00:00 2001 From: Ge Gao Date: Sat, 25 Nov 2023 12:09:53 -0500 Subject: [PATCH] Add unit tests for struct Converter Add unit tests for struct Converter and Conversions.h. struct Converter holds the core logic for casting between scalar types. Source of the test cases are CastExprTest.cpp and https://facebookincubator.github.io/velox/functions/presto/conversion.html --- velox/expression/tests/CastExprTest.cpp | 7 +- velox/type/tests/CMakeLists.txt | 3 +- velox/type/tests/ConversionsTest.cpp | 1007 +++++++++++++++++++++++ 3 files changed, 1012 insertions(+), 5 deletions(-) create mode 100644 velox/type/tests/ConversionsTest.cpp diff --git a/velox/expression/tests/CastExprTest.cpp b/velox/expression/tests/CastExprTest.cpp index e37fd5536a61b..7c35dfe8a163d 100644 --- a/velox/expression/tests/CastExprTest.cpp +++ b/velox/expression/tests/CastExprTest.cpp @@ -1265,10 +1265,9 @@ TEST_F(CastExprTest, truncateVsRound) { "tinyint", {1111111, 2, 3, 1000, -100101}, {71, 2, 3, -24, -5}); setCastIntByTruncate(false); - EXPECT_THROW( - (testCast( - "tinyint", {1111111, 2, 3, 1000, -100101}, {71, 2, 3, -24, -5})), - VeloxUserError); + testCast("tinyint", {2, 3}, {2, 3}); + testCast( + "tinyint", {1111111, 1000, -100101}, {0, 0, 0}, true); } TEST_F(CastExprTest, nullInputs) { diff --git a/velox/type/tests/CMakeLists.txt b/velox/type/tests/CMakeLists.txt index 1106ed087ec70..4b71c38e32e61 100644 --- a/velox/type/tests/CMakeLists.txt +++ b/velox/type/tests/CMakeLists.txt @@ -22,7 +22,8 @@ add_executable( SubfieldTest.cpp TimestampConversionTest.cpp VariantTest.cpp - TimestampTest.cpp) + TimestampTest.cpp + ConversionsTest.cpp) add_test(velox_type_test velox_type_test) diff --git a/velox/type/tests/ConversionsTest.cpp b/velox/type/tests/ConversionsTest.cpp new file mode 100644 index 0000000000000..c18440e14932d --- /dev/null +++ b/velox/type/tests/ConversionsTest.cpp @@ -0,0 +1,1007 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "velox/type/Conversions.h" +#include "gtest/gtest.h" +#include "velox/common/base/VeloxException.h" +#include "velox/common/base/tests/GTestUtils.h" + +using namespace facebook::velox; + +namespace facebook::velox::util { +namespace { + +constexpr float kInf = std::numeric_limits::infinity(); +constexpr float kNan = std::numeric_limits::quiet_NaN(); + +class ConversionsTest : public testing::Test { + protected: + template + static void testConversion( + std::vector input, + std::vector expectedResult, + bool truncate = false, + bool legacyCast = false, + bool expectError = false) { + if (!expectError) { + VELOX_CHECK_EQ(input.size(), expectedResult.size()); + } + const TypeKind toTypeKind = CppToType::typeKind; + + auto cast = [&](TFrom input) -> TTo { + if (truncate & legacyCast) { + return Converter::cast(input); + } else if (!truncate & legacyCast) { + return Converter::cast(input); + } else if (truncate & !legacyCast) { + return Converter::cast(input); + } else { + return Converter::cast(input); + } + }; + + for (auto i = 0; i < input.size(); i++) { + if (expectError) { + ASSERT_ANY_THROW(cast(input[i])) << "Converting input at " << i; + } else { + TTo actual = cast(input[i]); + TTo expected = expectedResult[i]; + if constexpr (std::is_floating_point_v) { + if (std::isnan(actual)) { + ASSERT_TRUE(std::isnan(expected)) + << ", when actual is NaN, and converting input at " << i; + } else if (std::isnan(expected)) { + ASSERT_TRUE(std::isnan(actual)) + << ", when expected is NaN, and converting input at " << i; + } else { + ASSERT_EQ(actual, expected) << "Converting input at " << i; + } + } else { + ASSERT_EQ(actual, expected) << "Converting input at " << i; + } + } + } + } +}; + +TEST_F(ConversionsTest, toBoolean) { + // From integral types. + { + // When TRUNCATE = false. + testConversion( + { + 1, + 0, + 12, + -1, + }, + { + true, + false, + true, + true, + }, + /*truncate*/ false); + + // When TRUNCATE = true. + testConversion( + { + 1, + 0, + 12, + -1, + }, + { + true, + false, + true, + true, + }, + /*truncate*/ true); + } + + // From double. + { + // When TRUNCATE = false. + testConversion( + { + 1.0, + 1.1, + -1.0, + 0.0000000000001, + }, + { + true, + true, + true, + true, + }, + /*truncate*/ false); + + // When TRUNCATE = true. + testConversion( + { + 1.0, + 1.1, + -1.0, + 0.0000000000001, + }, + { + true, + true, + true, + true, + }, + /*truncate*/ true); + } + + // From float. + { + // When TRUNCATE = false. + testConversion( + { + 0.1, + 0.0, + -0.1, + kInf, + kNan, + }, + { + true, + false, + true, + true, + true, + }, + /*truncate*/ false); + + // When TRUNCATE = true. + testConversion( + { + 0.1, + 0.0, + -0.1, + kInf, + kNan, + }, + { + true, + false, + true, + true, + false, + }, + /*truncate*/ true); + } + + // From string. + { + // When TRUNCATE = false. + testConversion( + { + "1", + "0", + "t", + "true", + "f", + "false", + }, + { + true, + false, + true, + true, + false, + false, + }, + /*truncate*/ false); + + // When TRUNCATE = false, invalid cases. + testConversion( + { + "1.7E308", + "nan", + "infinity", + "12", + "-1", + "tr", + "tru", + }, + {}, + /*truncate*/ false, + false, + /*expectError*/ true); + + // When TRUNCATE = true. + testConversion( + { + "1", + "0", + "t", + "true", + "f", + "false", + }, + { + true, + false, + true, + true, + false, + false, + }, + /*truncate*/ true); + + // When TRUNCATE = true, invalid cases. + testConversion( + { + "1.7E308", + "nan", + "infinity", + "12", + "-1", + "tr", + "tru", + }, + {}, + /*truncate*/ true, + false, + /*expectError*/ true); + } + + // From timestamp. + { + // When TRUNCATE = false, invalid cases. + testConversion( + {Timestamp(946729316, 123)}, + {}, + /*truncate*/ false, + false, + /*expectError*/ true); + + // When TRUNCATE = true, invalid cases. + testConversion( + {Timestamp(946729316, 123)}, + {}, + /*truncate*/ true, + false, + /*expectError*/ true); + } +} + +TEST_F(ConversionsTest, toIntegeralTypes) { + // From double. + { + // When TRUNCATE = false. + testConversion( + { + 12345.12, + 12345.67, + }, + { + 12345, + 12346, + }, + /*truncate*/ false); + testConversion( + { + 1.888, + 2.5, + 3.6, + 100.44, + -100.101, + }, + { + 2, + 3, + 4, + 100, + -100, + }, + /*truncate*/ false); + + // When TRUNCATE = false, invalid cases. + testConversion( + { + 12345.67, + -12345.67, + 127.8, + }, + {}, + /*truncate*/ false, + false, + /*expectError*/ true); + + // When TRUNCATE = true. + testConversion( + { + 12345.12, + 12345.67, + }, + { + 12345, + 12345, + }, + /*truncate*/ true); + testConversion( + { + 1.888, + 2.5, + 3.6, + 100.44, + -100.101, + }, + { + 1, + 2, + 3, + 100, + -100, + }, + /*truncate*/ true); + testConversion( + { + 12345.67, + -12345.67, + 127.8, + }, + { + 57, + -57, + 127, + }, + /*truncate*/ true); + testConversion( + { + 1, + 256, + 257, + 2147483646, + 2147483647, + 2147483648, + -2147483646, + -2147483647, + -2147483648, + -2147483649, + }, + { + 1, + 0, + 1, + -2, + -1, + -1, + 2, + 1, + 0, + 0, + }, + /*truncate*/ true); + } + + // From float. + { + // When TRUNCATE = false, invalid cases. + testConversion( + {kInf}, {}, /*truncate*/ false, false, /*expectError*/ true); + testConversion( + {kNan}, {0}, /*truncate*/ false, false, /*expectError*/ true); + testConversion( + {kNan}, {0}, /*truncate*/ false, false, /*expectError*/ true); + testConversion( + {kNan}, {0}, /*truncate*/ false, false, /*expectError*/ true); + testConversion( + {kNan}, {0}, /*truncate*/ false, false, /*expectError*/ true); + + // When TRUNCATE = true. + testConversion( + {kInf}, {9223372036854775807}, /*truncate*/ true); + testConversion({kNan}, {0}, /*truncate*/ true); + testConversion({kNan}, {0}, /*truncate*/ true); + testConversion({kNan}, {0}, /*truncate*/ true); + testConversion({kNan}, {0}, /*truncate*/ true); + } + + // From string. + { + // When TRUNCATE = false. + testConversion( + { + "1", + "+1", + "-100", + }, + { + 1, + 1, + -100, + }, + /*truncate*/ false); + + // When TRUNCATE = false, invalid cases. + testConversion( + { + "1.2", + "1.23444", + ".2355", + "-1.8", + "1.", + "-1.", + "0.", + ".", + "-.", + }, + {}, + /*truncate*/ false, + false, + /*expectError*/ true); + testConversion( + {"1234567"}, {}, /*truncate*/ false, false, /*expectError*/ true); + testConversion( + { + "1a", + "", + "1'234'567", + "1,234,567", + "infinity", + "nan", + }, + {}, + /*truncate*/ false, + false, + /*expectError*/ true); + + // When TRUNCATE = true. + testConversion( + { + "1.2", + "1.23444", + ".2355", + "-1.8", + "1.", + "-1.", + "0.", + ".", + "-.", + }, + { + 1, + 1, + 0, + -1, + 1, + -1, + 0, + 0, + 0, + }, + /*truncate*/ true); + + // When TRUNCATE = true, invalid cases. + testConversion( + {"1234567", "+1"}, {}, /*truncate*/ true, false, /*expectError*/ true); + testConversion( + { + "1a", + "", + "1'234'567", + "1,234,567", + "infinity", + "nan", + }, + {}, + /*truncate*/ true, + false, + /*expectError*/ true); + } + + // From integral types. + { + // When TRUNCATE = false. + testConversion( + { + 2, + 3, + }, + { + 2, + 3, + }, + /*truncate*/ false); + + // When TRUNCATE = false, invalid cases. + testConversion( + { + 1234567, + -1234567, + 1111111, + 1000, + -100101, + }, + {}, + /*truncate*/ false, + false, + /*expectError*/ true); + + // When TRUNCATE = true. + testConversion( + { + 1234567, + -1234567, + 1111111, + 2, + 3, + 1000, + -100101, + }, + { + -121, + 121, + 71, + 2, + 3, + -24, + -5, + }, + /*truncate*/ true); + } + + // From boolean + { + testConversion( + { + true, + false, + }, + { + 1, + 0, + }); + } +} + +TEST_F(ConversionsTest, toString) { + // From integral types. + { + testConversion( + { + 1, + 2, + 3, + 100, + -100, + }, + { + "1", + "2", + "3", + "100", + "-100", + }); + } + + // From double. + { + // When LEGACY_CAST = false. + testConversion( + { + 12345678901234567000.0, + 123456789.01234567, + 10'000'000.0, + 12345.0, + 0.001, + 0.00012, + 0.0, + -0.0, + -0.00012, + -0.001, + -12345.0, + -10'000'000.0, + -123456789.01234567, + -12345678901234567000.0, + std::numeric_limits::infinity(), + -std::numeric_limits::infinity(), + std::numeric_limits::quiet_NaN(), + -std::numeric_limits::quiet_NaN(), + }, + { + "1.2345678901234567E19", + "1.2345678901234567E8", + "1.0E7", + "12345.0", + "0.001", + "1.2E-4", + "0.0", + "-0.0", + "-1.2E-4", + "-0.001", + "-12345.0", + "-1.0E7", + "-1.2345678901234567E8", + "-1.2345678901234567E19", + "Infinity", + "-Infinity", + "NaN", + "NaN", + }, + false, + /*legacyCast*/ false); + + // When LEGACY_CAST = true. + testConversion( + { + 12345678901234567000.0, + 123456789.01234567, + 10'000'000.0, + 12345.0, + 0.001, + 0.00012, + 0.0, + -0.0, + -0.00012, + -0.001, + -12345.0, + -10'000'000.0, + -123456789.01234567, + -12345678901234567000.0, + std::numeric_limits::infinity(), + -std::numeric_limits::infinity(), + std::numeric_limits::quiet_NaN(), + -std::numeric_limits::quiet_NaN(), + }, + { + "12345678901234567000.0", + "123456789.01234567", + "10000000.0", + "12345.0", + "0.001", + "0.00012", + "0.0", + "-0.0", + "-0.00012", + "-0.001", + "-12345.0", + "-10000000.0", + "-123456789.01234567", + "-12345678901234567000.0", + "Infinity", + "-Infinity", + "NaN", + "NaN", + }, + false, + /*legacyCast*/ true); + } + + // From float. + { + // When LEGACY_CAST = false. + testConversion( + { + 12345678000000000000.0, + 123456780.0, + 10'000'000.0, + 12345.0, + 0.001, + 0.00012, + 0.0, + -0.0, + -0.00012, + -0.001, + -12345.0, + -10'000'000.0, + -123456780.0, + -12345678000000000000.0, + std::numeric_limits::infinity(), + -std::numeric_limits::infinity(), + std::numeric_limits::quiet_NaN(), + -std::numeric_limits::quiet_NaN(), + }, + { + "1.2345678E19", + "1.2345678E8", + "1.0E7", + "12345.0", + "0.001", + "1.2E-4", + "0.0", + "-0.0", + "-1.2E-4", + "-0.001", + "-12345.0", + "-1.0E7", + "-1.2345678E8", + "-1.2345678E19", + "Infinity", + "-Infinity", + "NaN", + "NaN", + }, + false, + /*legacyCast*/ false); + + // When LEGACY_CAST = true. + testConversion( + { + 12345678000000000000.0, + 123456780.0, + 10'000'000.0, + 12345.0, + 0.001, + 0.00012, + 0.0, + -0.0, + -0.00012, + -0.001, + -12345.0, + -10'000'000.0, + -123456780.0, + -12345678000000000000.0, + std::numeric_limits::infinity(), + -std::numeric_limits::infinity(), + std::numeric_limits::quiet_NaN(), + -std::numeric_limits::quiet_NaN(), + }, + { + "12345678295994466000.0", + "123456784.0", + "10000000.0", + "12345.0", + "0.0010000000474974513", + "0.00011999999696854502", + "0.0", + "-0.0", + "-0.00011999999696854502", + "-0.0010000000474974513", + "-12345.0", + "-10000000.0", + "-123456784.0", + "-12345678295994466000.0", + "Infinity", + "-Infinity", + "NaN", + "NaN", + }, + false, + /*legacyCast*/ true); + } + + // From Timestamp. + { + // When LEGACY_CAST = false. + testConversion( + { + Timestamp(-946684800, 0), + Timestamp(-7266, 0), + Timestamp(0, 0), + Timestamp(946684800, 0), + Timestamp(9466848000, 0), + Timestamp(94668480000, 0), + Timestamp(946729316, 0), + Timestamp(946729316, 123), + Timestamp(946729316, 129900000), + Timestamp(7266, 0), + Timestamp(-50049331200, 0), + Timestamp(253405036800, 0), + Timestamp(-62480037600, 0), + }, + { + "1940-01-02 00:00:00.000", + "1969-12-31 21:58:54.000", + "1970-01-01 00:00:00.000", + "2000-01-01 00:00:00.000", + "2269-12-29 00:00:00.000", + "4969-12-04 00:00:00.000", + "2000-01-01 12:21:56.000", + "2000-01-01 12:21:56.000", + "2000-01-01 12:21:56.129", + "1970-01-01 02:01:06.000", + "0384-01-01 08:00:00.000", + "10000-02-01 16:00:00.000", + "-0010-02-01 10:00:00.000", + }, + false, + /*legacyCast*/ false); + + // When LEGACY_CAST = true. + testConversion( + { + Timestamp(946729316, 123), + Timestamp(-50049331200, 0), + Timestamp(253405036800, 0), + Timestamp(-62480037600, 0), + }, + { + "2000-01-01T12:21:56.000", + "384-01-01T08:00:00.000", + "10000-02-01T16:00:00.000", + "-10-02-01T10:00:00.000", + }, + false, + /*legacyCast*/ true); + } +} + +TEST_F(ConversionsTest, toRealAndDouble) { + // From integral types. + { + testConversion({1}, {1.0}, /*truncate*/ false); + testConversion( + { + 1, + 2, + 3, + 100, + -100, + }, + { + 1.0, + 2.0, + 3.0, + 100.0, + -100.0, + }); + } + + // From double. + { + // When TRUNCATE = false. + testConversion( + { + 1.888, + 2.5, + 3.6, + 100.44, + -100.101, + 1.0, + -2.0, + }, + { + 1.888, + 2.5, + 3.6, + 100.44, + -100.101, + 1.0, + -2.0, + }, + /*truncate*/ false); + testConversion( + { + 1.888, + 2.5, + 3.6, + 100.44, + -100.101, + 1.0, + -2.0, + }, + { + 1.888, + 2.5, + 3.6, + 100.44, + -100.101, + 1.0, + -2.0, + }, + /*truncate*/ false); + + // When TRUNCATE = false, invalid cases. + testConversion( + {1.7E308}, {}, /*truncate*/ false, false, /*expectError*/ true); + + // When TRUNCATE = true. + testConversion({1.7E308}, {kInf}, /*truncate*/ true); + } + + // From string. + { + // When TRUNCATE = false. + testConversion( + { + "1.7E308", + "1.", + "1", + "infinity", + "-infinity", + "InfiNiTy", + "-InfiNiTy", + "nan", + "nAn", + }, + { + kInf, + 1.0, + 1.0, + kInf, + -kInf, + kInf, + -kInf, + kNan, + kNan, + }, + /*truncate*/ false); + + // When TRUNCATE = false, invalid cases. + testConversion( + { + "1.2a", + "1.2.3", + }, + {}, + /*truncate*/ false, + false, + /*expectError*/ true); + } + + // From Timestamp. + { + // Invalid cases. + testConversion( + {Timestamp(946729316, 123)}, + {}, + /*truncate*/ false, + false, + /*expectError*/ true); + } +} + +TEST_F(ConversionsTest, toTimestamp) { + // From string. + { + testConversion( + { + "1970-01-01", + "2000-01-01", + "1970-01-01 00:00:00", + "2000-01-01 12:21:56", + "1970-01-01 00:00:00-02:00", + }, + { + Timestamp(0, 0), + Timestamp(946684800, 0), + Timestamp(0, 0), + Timestamp(946729316, 0), + Timestamp(7200, 0), + }); + + // Invalid case. + testConversion( + {"2012-Oct-01"}, {}, false, false, /*expectError*/ true); + } + + // From integral types, invalid cases. + { + testConversion( + {123}, {}, false, false, /*expectError*/ true); + testConversion( + {12345}, {}, false, false, /*expectError*/ true); + testConversion( + {123456}, {}, false, false, /*expectError*/ true); + testConversion( + {123456}, {}, false, false, /*expectError*/ true); + } + + // From floating-point types, invalid cases. + { + testConversion( + {123456.78}, {}, false, false, /*expectError*/ true); + testConversion( + {123456.78}, {}, false, false, /*expectError*/ true); + } +} +} // namespace +} // namespace facebook::velox::util