diff --git a/velox/expression/tests/ArgGenerator.cpp b/velox/expression/tests/ArgGenerator.cpp new file mode 100644 index 000000000000..0324b022b0cc --- /dev/null +++ b/velox/expression/tests/ArgGenerator.cpp @@ -0,0 +1,82 @@ +/* + * 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/expression/tests/ArgGenerator.h" + +namespace facebook::velox::test { +namespace { +int32_t rand32(FuzzerGenerator& rng) { + return boost::random::uniform_int_distribution()(rng); +} +} // namespace + +std::vector DecimalArgGeneratorBase::generateArgs( + const exec::FunctionSignature& /*signature*/, + const TypePtr& returnType, + FuzzerGenerator& rng) override { + auto inputs = findInputs(returnType, rng); + for (const auto& input : inputs) { + if (input == nullptr) { + return {}; + } + } + return inputs; +} + +const Inputs& DecimalArgGeneratorBase::findInputs( + const TypePtr& returnType, + FuzzerGenerator& rng) const { + auto [p, s] = getDecimalPrecisionScale(*returnType); + auto it = inputs_.find({p, s}); + if (it == inputs_.end()) { + LOG(ERROR) << "Cannot find input types for " << returnType->toString(); + return {nullptr, nullptr}; + } + + auto index = rand32(rng) % it->second.size(); + return it->second[index]; +} + +const std::vector& DecimalArgGeneratorBase::getAllTypes() const { + const auto generateAllTypes = []() { + std::vector allTypes; + for (auto p = 1; p < 38; ++p) { + for (auto s = 0; s <= p; ++s) { + allTypes.push_back(DECIMAL(p, s)); + } + } + return allTypes; + }; + + static std::vector allTypes = generateAllTypes(); + return allTypes; +} + +void DecimalArgGeneratorBase::initialize() { + // By default, the result type is considered to be calculated from two input + // decimal types. + for (const auto& a : getAllTypes()) { + for (const auto& b : getAllTypes()) { + auto [p1, s1] = getDecimalPrecisionScale(*a); + auto [p2, s2] = getDecimalPrecisionScale(*b); + + if (auto returnType = toReturnType(4, p1, s1, p2, s2)) { + inputs_[returnType.value()].push_back({a, b}); + } + } + } +} +} // namespace facebook::velox::test diff --git a/velox/expression/tests/ArgGenerator.h b/velox/expression/tests/ArgGenerator.h new file mode 100644 index 000000000000..fa4584abd9e6 --- /dev/null +++ b/velox/expression/tests/ArgGenerator.h @@ -0,0 +1,63 @@ +/* + * 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/expression/FunctionSignature.h" +#include "velox/vector/fuzzer/Utils.h" + +namespace facebook::velox::test { + +class ArgGenerator { + public: + virtual ~ArgGenerator() = default; + + /// Given a signature and a concrete return type return randomly selected + /// valid input types. Returns empty vector if no input types can + /// produce the specified result type. + virtual std::vector generateArgs( + const exec::FunctionSignature& signature, + const TypePtr& returnType, + FuzzerGenerator& rng) = 0; +}; + +class DecimalArgGeneratorBase : public ArgGenerator { + public: + std::vector generateArgs( + const exec::FunctionSignature& /*signature*/, + const TypePtr& returnType, + FuzzerGenerator& rng) override; + + protected: + using Inputs = std::vector; + + // Return randomly selected pair of input types that produce the specified + // result type. + const Inputs& findInputs(const TypePtr& returnType, FuzzerGenerator& rng) + const; + + const std::vector& getAllTypes() const; + + // Compute result type for all possible pairs of decimal input types. Store + // the results in 'inputs_' maps keyed by return type. + void initialize(); + + // Given precisions and scales of the inputs, return precision and scale of + // the result. + virtual std::optional> toReturnType(int count, ...) = 0; + + std::map, std::vector> inputs_; +}; + +} // namespace facebook::velox::test diff --git a/velox/expression/tests/CMakeLists.txt b/velox/expression/tests/CMakeLists.txt index 6958e22f9276..01bc6fd68ecb 100644 --- a/velox/expression/tests/CMakeLists.txt +++ b/velox/expression/tests/CMakeLists.txt @@ -88,7 +88,7 @@ target_link_libraries( velox_expression_verifier velox_vector_test_lib velox_vector_fuzzer velox_type velox_expression_test_utility) -add_library(velox_expression_fuzzer ExpressionFuzzer.cpp FuzzerRunner.cpp +add_library(velox_expression_fuzzer ArgGenerator.cpp ExpressionFuzzer.cpp FuzzerRunner.cpp ExpressionFuzzerVerifier.cpp) target_link_libraries( diff --git a/velox/expression/tests/ExpressionFuzzer.cpp b/velox/expression/tests/ExpressionFuzzer.cpp index 3c16aea4866e..fba1fb5ba75b 100644 --- a/velox/expression/tests/ExpressionFuzzer.cpp +++ b/velox/expression/tests/ExpressionFuzzer.cpp @@ -26,88 +26,13 @@ #include "velox/expression/FunctionSignature.h" #include "velox/expression/ReverseSignatureBinder.h" #include "velox/expression/SimpleFunctionRegistry.h" +#include "velox/expression/tests/ArgGenerator.h" #include "velox/expression/tests/ExpressionFuzzer.h" #include "velox/expression/tests/utils/ArgumentTypeFuzzer.h" namespace facebook::velox::test { - namespace { -int32_t rand(FuzzerGenerator& rng) { - return boost::random::uniform_int_distribution()(rng); -} - -class DecimalArgGeneratorBase : public ArgGenerator { - public: - std::vector generateArgs( - const exec::FunctionSignature& /*signature*/, - const TypePtr& returnType, - FuzzerGenerator& rng) override { - auto inputs = findInputs(returnType, rng); - for (const auto& input : inputs) { - if (input == nullptr) { - return {}; - } - } - return inputs; - } - - protected: - // Compute result type for all possible pairs of decimal input types. Store - // the results in 'inputs_' maps keyed by return type. - void initialize() { - // By default, the result type is considered to be calculated from two input - // decimal types. - for (const auto& a : getAllTypes()) { - for (const auto& b : getAllTypes()) { - auto [p1, s1] = getDecimalPrecisionScale(*a); - auto [p2, s2] = getDecimalPrecisionScale(*b); - - if (auto returnType = toReturnType(4, p1, s1, p2, s2)) { - inputs_[returnType.value()].push_back({a, b}); - } - } - } - } - - using Inputs = std::vector; - - // Return randomly selected pair of input types that produce the specified - // result type. - Inputs findInputs(const TypePtr& returnType, FuzzerGenerator& rng) const { - auto [p, s] = getDecimalPrecisionScale(*returnType); - auto it = inputs_.find({p, s}); - if (it == inputs_.end()) { - LOG(ERROR) << "Cannot find input types for " << returnType->toString(); - return {nullptr, nullptr}; - } - - auto index = rand(rng) % it->second.size(); - return it->second[index]; - } - - const std::vector& getAllTypes() const { - const auto generateAllTypes = []() { - std::vector allTypes; - for (auto p = 1; p < 38; ++p) { - for (auto s = 0; s <= p; ++s) { - allTypes.push_back(DECIMAL(p, s)); - } - } - return allTypes; - }; - - static std::vector allTypes = generateAllTypes(); - return allTypes; - } - - // Given precisions and scales of the inputs, return precision and scale of - // the result. - virtual std::optional> toReturnType(int count, ...) = 0; - - std::map, std::vector> inputs_; -}; - class PlusMinusArgGenerator : public DecimalArgGeneratorBase { public: PlusMinusArgGenerator() { @@ -211,7 +136,11 @@ class FloorAndRoundArgGenerator : public ArgGenerator { return {}; } - auto s1 = rand(rng) % (38 - p + 1); + const auto rand32 = [](FuzzerGenerator& rng) { + return boost::random::uniform_int_distribution()(rng); + }; + + auto s1 = rand32(rng) % (38 - p + 1); if (s1 == 0) { return {DECIMAL(p, 0)}; } @@ -818,7 +747,7 @@ ExpressionFuzzer::ExpressionFuzzer( } if (!isDeterministic(function.first, argTypes)) { LOG(WARNING) << "Skipping non-deterministic function: " - << function.first << signature->toString(); + << function.first << signature->toString(); continue; } diff --git a/velox/expression/tests/ExpressionFuzzer.h b/velox/expression/tests/ExpressionFuzzer.h index bfae96a92bbe..d991400a8ed8 100644 --- a/velox/expression/tests/ExpressionFuzzer.h +++ b/velox/expression/tests/ExpressionFuzzer.h @@ -27,19 +27,6 @@ namespace facebook::velox::test { -class ArgGenerator { - public: - virtual ~ArgGenerator() = default; - - /// Given a signature and a concrete return type return randomly selected - /// valid input types. Returns empty vector if no input types can - /// produce the specified result type. - virtual std::vector generateArgs( - const exec::FunctionSignature& signature, - const TypePtr& returnType, - FuzzerGenerator& rng) = 0; -}; - // A tool that can be used to generate random expressions. class ExpressionFuzzer { public: