Remove fuzztest support from OR-Tools (#4938)

* Remove fuzztest support from OR-Tools
This commit is contained in:
Guillaume Chatelet
2025-12-07 10:13:41 +01:00
committed by Corentin Le Molgat
parent e09ed7a33a
commit 7345481dfe
14 changed files with 18 additions and 444 deletions

View File

@@ -435,3 +435,20 @@ cc_library(
"@abseil-cpp//absl/time",
],
)
cc_test(
name = "n_choose_k_test",
srcs = ["n_choose_k_test.cc"],
deps = [
":n_choose_k",
"//ortools/base:dump_vars",
"//ortools/base:gmock_main",
"//ortools/util:flat_matrix",
"@abseil-cpp//absl/numeric:int128",
"@abseil-cpp//absl/random",
"@abseil-cpp//absl/random:distributions",
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/status:statusor",
"@google_benchmark//:benchmark",
],
)

View File

@@ -52,20 +52,6 @@ if(BUILD_TESTING)
GTest::gmock
)
endforeach()
if(USE_fuzztest)
ortools_cxx_test(
NAME
algorithms_n_choose_k_test
SOURCES
"./n_choose_k_test.cc"
LINK_LIBRARIES
benchmark::benchmark
fuzztest::fuzztest
fuzztest::fuzztest_gtest_main
GTest::gmock
)
endif()
# These tests are too long so we disable them.
set_tests_properties(
cxx_algorithms_radix_sort_test

View File

@@ -16,26 +16,19 @@
#include <cmath>
#include <cstdint>
#include <limits>
#include <random>
#include <utility>
#include <vector>
#include "absl/numeric/int128.h"
#include "absl/random/distributions.h"
#include "absl/random/random.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "benchmark/benchmark.h"
#include "gtest/gtest.h"
#include "ortools/base/dump_vars.h"
//#include "ortools/base/fuzztest.h"
#include "ortools/base/gmock.h"
#include "ortools/base/mathutil.h"
#include "ortools/util/flat_matrix.h"
namespace operations_research {
namespace {
//using ::fuzztest::NonNegative;
using ::testing::HasSubstr;
using ::testing::status::IsOkAndHolds;
using ::testing::status::StatusIs;
@@ -248,74 +241,5 @@ TEST(NChooseKTest, ComparisonAgainstPascalTriangleForK5OrAbove) {
}
}
void MatchesLogCombinations(int n, int k) {
if (n < k) {
std::swap(k, n);
}
const auto exact = NChooseK(n, k);
const double log_approx = MathUtil::LogCombinations(n, k);
if (exact.ok()) {
// We accepted to compute the exact value, make sure that it matches the
// approximation.
ASSERT_NEAR(log(exact.value()), log_approx, 0.0001);
} else {
// We declined to compute the exact value, make sure that we had a good
// reason to, i.e. that the result did indeed overflow.
ASSERT_THAT(exact, StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("overflows int64")));
const double approx = exp(log_approx);
ASSERT_GE(std::nextafter(approx, std::numeric_limits<double>::infinity()),
std::numeric_limits<int64_t>::max())
<< "we declined to compute the exact value of NChooseK(" << n << ", "
<< k << "), but the log value is " << log_approx
<< " (value: " << approx << "), which fits in int64_t";
}
}
/*
FUZZ_TEST(NChooseKTest, MatchesLogCombinations)
// Ideally we'd test with `uint64_t`, but `LogCombinations` only accepts
// `int`.
.WithDomains(NonNegative<int>(), NonNegative<int>());
*/
template <int kMaxN, auto algo>
void BM_NChooseK(benchmark::State& state) {
static constexpr int kNumInputs = 1000;
// Use deterministic random numbers to avoid noise.
std::mt19937 gen(42);
std::uniform_int_distribution<int64_t> random(0, kMaxN);
std::vector<std::pair<int64_t, int64_t>> inputs;
inputs.reserve(kNumInputs);
for (int i = 0; i < kNumInputs; ++i) {
int64_t n = random(gen);
int64_t k = random(gen);
if (n < k) {
std::swap(n, k);
}
inputs.emplace_back(n, k);
}
// Force the one-time, costly static initializations of NChooseK() to happen
// before the benchmark starts.
auto result = NChooseK(62, 31);
benchmark::DoNotOptimize(result);
// Start the benchmark.
for (auto _ : state) {
for (const auto [n, k] : inputs) {
auto result = algo(n, k);
benchmark::DoNotOptimize(result);
}
}
state.SetItemsProcessed(state.iterations() * kNumInputs);
}
// int32_t domain.
BENCHMARK(BM_NChooseK<30, operations_research::NChooseK>);
// int{32,64} domain.
BENCHMARK(BM_NChooseK<60, operations_research::NChooseK>);
// int{32,64,128} domain.
BENCHMARK(BM_NChooseK<100, operations_research::NChooseK>);
BENCHMARK(BM_NChooseK<100, MathUtil::LogCombinations>);
} // namespace
} // namespace operations_research

View File

@@ -1,51 +0,0 @@
// Copyright 2010-2025 Google LLC
// 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.
#ifndef ORTOOLS_BASE_FUZZTEST_H_
#define ORTOOLS_BASE_FUZZTEST_H_
#include <string_view>
#include <tuple>
#include <vector>
// #include "fuzztest/googletest_fixture_adapter.h"
#include "fuzztest/domain.h"
#include "fuzztest/fuzztest.h"
#include "fuzztest/init_fuzztest.h"
#include "google/protobuf/text_format.h"
namespace fuzztest {
// Reads protos from directory and returns a vector usable by the .WithSeeds()
// function to aid in fuzz test migrations. `is_text_format` should be true iff
// the protos are in text format.
template <class ProtoType>
std::vector<std::tuple<ProtoType>> ReadFilesFromDirectory(
std::string_view dir) {
std::vector<std::tuple<ProtoType>> corpus;
for (std::tuple<std::string>& proto_tuple : ReadFilesFromDirectory(dir)) {
std::string text_proto = std::get<0>(proto_tuple);
ProtoType proto;
bool was_parsed =
google::protobuf::TextFormat::ParseFromString(text_proto, &proto);
if (was_parsed) {
corpus.push_back(std::make_tuple(proto));
}
}
return corpus;
}
} // namespace fuzztest
#endif // ORTOOLS_BASE_FUZZTEST_H_

View File

@@ -50,8 +50,6 @@ if(BUILD_TESTING)
file(GLOB _TEST_SRCS "*_test.cc")
list(FILTER _TEST_SRCS
EXCLUDE REGEX "elemental_export_model_update_test.cc$")
list(FILTER _TEST_SRCS
EXCLUDE REGEX "elemental_from_proto_fuzz_test.cc$") # need fuzztest
foreach(_FULL_FILE_NAME IN LISTS _TEST_SRCS)
get_filename_component(_NAME ${_FULL_FILE_NAME} NAME_WE)
get_filename_component(_FILE_NAME ${_FULL_FILE_NAME} NAME)

View File

@@ -1,81 +0,0 @@
// Copyright 2010-2025 Google LLC
// 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 <optional>
#include "absl/status/statusor.h"
#include "ortools/base/fuzztest.h"
#include "ortools/base/gmock.h"
#include "ortools/math_opt/elemental/elemental.h"
#include "ortools/math_opt/elemental/elemental_matcher.h"
#include "ortools/math_opt/model_update.pb.h"
namespace operations_research::math_opt {
namespace {
using ::testing::status::IsOkAndHolds;
void FromProtoNeverCrashes(const ModelProto& proto) {
Elemental::FromModelProto(proto).IgnoreError();
}
FUZZ_TEST(ElementalFromProtoNoCrashTest, FromProtoNeverCrashes);
void RoundTripsIfParses(const ModelProto& proto) {
absl::StatusOr<Elemental> e1 = Elemental::FromModelProto(proto);
if (!e1.ok()) {
return;
}
ASSERT_OK_AND_ASSIGN(ModelProto p2, e1->ExportModel());
EXPECT_THAT(Elemental::FromModelProto(p2),
IsOkAndHolds(EquivToElemental(*e1)));
}
FUZZ_TEST(ElementalRoundTripTest, RoundTripsIfParses);
void ApplyUpdateProtoNoCrash(const ModelProto& proto,
const ModelUpdateProto& u1,
const ModelUpdateProto& u2) {
absl::StatusOr<Elemental> elemental = Elemental::FromModelProto(proto);
if (!elemental.ok()) {
return;
}
elemental->ApplyUpdateProto(u1).IgnoreError();
elemental->ApplyUpdateProto(u2).IgnoreError();
}
FUZZ_TEST(ElementalApplyUpdateProtoNoCrashTest, ApplyUpdateProtoNoCrash);
void UpdateRoundTripsIfParses(const ModelProto& proto,
const ModelUpdateProto& update) {
absl::StatusOr<Elemental> model = Elemental::FromModelProto(proto);
if (!model.ok()) {
return;
}
const Elemental::DiffHandle diff = model->AddDiff();
if (!model->ApplyUpdateProto(update).ok()) {
return;
}
ASSERT_OK_AND_ASSIGN(const std::optional<ModelUpdateProto> canonical_update,
model->ExportModelUpdate(diff));
if (canonical_update.has_value()) {
ASSERT_OK_AND_ASSIGN(Elemental model2, Elemental::FromModelProto(proto));
ASSERT_OK(model2.ApplyUpdateProto(*canonical_update));
EXPECT_THAT(*model, EquivToElemental(model2));
}
}
FUZZ_TEST(ElementalUpdateRoundTripTest, UpdateRoundTripsIfParses);
} // namespace
} // namespace operations_research::math_opt

View File

@@ -3843,6 +3843,7 @@ cc_test(
":integer_base",
":util",
"//ortools/base:gmock",
"//ortools/base:gmock_main",
"//ortools/base:logging",
"//ortools/graph:connected_components",
"//ortools/graph:strongly_connected_components",
@@ -3856,7 +3857,6 @@ cc_test(
"@abseil-cpp//absl/random:distributions",
"@abseil-cpp//absl/strings",
"@abseil-cpp//absl/types:span",
"@fuzztest//fuzztest:fuzztest_gtest_main",
"@google_benchmark//:benchmark",
],
)

View File

@@ -1105,27 +1105,6 @@ TEST(FindPartialIntersections, Random) {
}
}
void CheckFuzzedRectangles(
absl::Span<const std::tuple<int64_t, int64_t, int64_t, int64_t>> tuples) {
std::vector<Rectangle> rectangles;
rectangles.reserve(tuples.size());
for (const auto& [x_min, x_size, y_min, y_size] : tuples) {
rectangles.push_back({.x_min = x_min,
.x_max = CapAdd(x_min, x_size),
.y_min = y_min,
.y_max = CapAdd(y_min, y_size)});
}
const std::vector<std::pair<int, int>> result =
FindPartialRectangleIntersections(rectangles);
for (const auto& [i, j] : result) {
CHECK(!rectangles[i].IsDisjoint(rectangles[j])) << i << " " << j;
}
const std::vector<std::pair<int, int>> naive_result =
GetAllIntersections(rectangles);
CHECK(GraphsDefineSameConnectedComponents(naive_result, result))
<< RenderRectGraph(std::nullopt, rectangles, result);
}
void BM_FindRectangles(benchmark::State& state) {
absl::BitGen random;
std::vector<std::vector<RectangleInRange>> problems;