From a144ae4033ad6b2b8cd21d429697f250a57f81b2 Mon Sep 17 00:00:00 2001 From: Mizux Seiha Date: Thu, 28 Aug 2025 14:41:39 +0200 Subject: [PATCH] math_opt: export few cpp/ tests --- ortools/math_opt/cpp/BUILD.bazel | 52 ++ ortools/math_opt/cpp/CMakeLists.txt | 1 + ortools/math_opt/cpp/basis_status_test.cc | 25 + ortools/math_opt/cpp/enums_testing.h | 108 ++++ .../cpp/remote_streaming_mode_test.cc | 72 +++ .../math_opt/cpp/sparse_containers_test.cc | 513 ++++++++++++++++++ 6 files changed, 771 insertions(+) create mode 100644 ortools/math_opt/cpp/basis_status_test.cc create mode 100644 ortools/math_opt/cpp/enums_testing.h create mode 100644 ortools/math_opt/cpp/remote_streaming_mode_test.cc create mode 100644 ortools/math_opt/cpp/sparse_containers_test.cc diff --git a/ortools/math_opt/cpp/BUILD.bazel b/ortools/math_opt/cpp/BUILD.bazel index 82931c20cf..acb7d2005a 100644 --- a/ortools/math_opt/cpp/BUILD.bazel +++ b/ortools/math_opt/cpp/BUILD.bazel @@ -43,6 +43,17 @@ cc_library( ], ) +cc_test( + name = "basis_status_test", + srcs = ["basis_status_test.cc"], + deps = [ + ":basis_status", + ":enums_testing", + "//ortools/base:gmock", + "//ortools/base:gmock_main", + ], +) + cc_library( name = "sparse_containers", srcs = ["sparse_containers.cc"], @@ -70,6 +81,21 @@ cc_library( ], ) +cc_test( + name = "sparse_containers_test", + srcs = ["sparse_containers_test.cc"], + deps = [ + ":matchers", + ":math_opt", + ":sparse_containers", + "//ortools/base:gmock", + "//ortools/base:gmock_main", + "//ortools/math_opt/constraints/quadratic:quadratic_constraint", + "@abseil-cpp//absl/container:flat_hash_map", + "@abseil-cpp//absl/status", + ], +) + cc_library( name = "model", srcs = ["model.cc"], @@ -457,6 +483,21 @@ cc_library( ], ) +cc_library( + name = "enums_testing", + testonly = True, + hdrs = ["enums_testing.h"], + deps = [ + ":enums", + "//ortools/base:gmock", + "//ortools/base:logging", + "@abseil-cpp//absl/numeric:int128", + "@abseil-cpp//absl/strings", + ], + # Make sure the tests are included when using --dynamic_mode=off. + alwayslink = 1, +) + cc_library( name = "statistics", srcs = ["statistics.cc"], @@ -583,3 +624,14 @@ cc_library( hdrs = ["remote_streaming_mode.h"], deps = ["@abseil-cpp//absl/strings:string_view"], ) + +cc_test( + name = "remote_streaming_mode_test", + srcs = ["remote_streaming_mode_test.cc"], + deps = [ + ":remote_streaming_mode", + "//ortools/base:gmock_main", + "@abseil-cpp//absl/flags:marshalling", + "@abseil-cpp//absl/strings", + ], +) diff --git a/ortools/math_opt/cpp/CMakeLists.txt b/ortools/math_opt/cpp/CMakeLists.txt index 323cc6c99b..a0f57d1808 100644 --- a/ortools/math_opt/cpp/CMakeLists.txt +++ b/ortools/math_opt/cpp/CMakeLists.txt @@ -15,6 +15,7 @@ set(NAME ${PROJECT_NAME}_math_opt_cpp) add_library(${NAME} OBJECT) file(GLOB _SRCS "*.h" "*.cc") +list(FILTER _SRCS EXCLUDE REGEX ".*_test.cc") list(FILTER _SRCS EXCLUDE REGEX "/matchers\\.") target_sources(${NAME} PRIVATE ${_SRCS}) diff --git a/ortools/math_opt/cpp/basis_status_test.cc b/ortools/math_opt/cpp/basis_status_test.cc new file mode 100644 index 0000000000..ca602c7639 --- /dev/null +++ b/ortools/math_opt/cpp/basis_status_test.cc @@ -0,0 +1,25 @@ +// 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 "ortools/math_opt/cpp/basis_status.h" + +#include "gtest/gtest.h" +#include "ortools/math_opt/cpp/enums_testing.h" + +namespace operations_research::math_opt { +namespace { + +INSTANTIATE_TYPED_TEST_SUITE_P(BasisStatus, EnumTest, BasisStatus); + +} +} // namespace operations_research::math_opt diff --git a/ortools/math_opt/cpp/enums_testing.h b/ortools/math_opt/cpp/enums_testing.h new file mode 100644 index 0000000000..db9880e2b1 --- /dev/null +++ b/ortools/math_opt/cpp/enums_testing.h @@ -0,0 +1,108 @@ +// 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. + +// Typed tests for enums that use enums.h. +#ifndef OR_TOOLS_MATH_OPT_CPP_ENUMS_TESTING_H_ +#define OR_TOOLS_MATH_OPT_CPP_ENUMS_TESTING_H_ + +#include +#include +#include +#include + +#include "absl/numeric/int128.h" +#include "absl/strings/string_view.h" +#include "gtest/gtest.h" +#include "ortools/base/gmock.h" +#include "ortools/base/logging.h" +#include "ortools/math_opt/cpp/enums.h" + +namespace operations_research::math_opt { + +// A type parameterized test suite for testing the correct implementation of +// Enum for a given enum. +// +// Usage: +// +// INSTANTIATE_TYPED_TEST_SUITE_P(, EnumTest, ); +// +template +class EnumTest : public testing::Test { + public: +}; + +TYPED_TEST_SUITE_P(EnumTest); + +TYPED_TEST_P(EnumTest, UnderlyingTypeFits) { + // We could static_assert that but using a gUnit test works too. + using underlying_type_limits = + std::numeric_limits>; + using Proto = typename Enum::Proto; + CHECK_GE(EnumProto::kMin, underlying_type_limits::min()); + CHECK_LE(EnumProto::kMax, underlying_type_limits::max()); +} + +TYPED_TEST_P(EnumTest, AllProtoValues) { + using Proto = typename Enum::Proto; + + bool found_unspecified = false; + std::vector found_values; + for (int i = EnumProto::kMin; i <= EnumProto::kMax; ++i) { + if (!EnumProto::kIsValid(i)) { + continue; + } + + const auto proto_value = static_cast(i); + SCOPED_TRACE(proto_value); + + const std::optional cpp_enum = EnumFromProto(proto_value); + if (proto_value == Enum::kProtoUnspecifiedValue) { + found_unspecified = true; + ASSERT_FALSE(cpp_enum.has_value()); + } else { + ASSERT_TRUE(cpp_enum.has_value()); + found_values.push_back(*cpp_enum); + } + + const Proto reconverted_proto_value = EnumToProto(cpp_enum); + EXPECT_EQ(proto_value, reconverted_proto_value); + } + + // We should have found all C++ enum + nullopt when traversing all possible + // Proto enums. And the AllValues() of C++ should not contain any additional + // value. + ASSERT_TRUE(found_unspecified); + ASSERT_THAT(found_values, ::testing::UnorderedElementsAreArray( + Enum::AllValues())); +} + +TYPED_TEST_P(EnumTest, AllCppValuesString) { + for (const TypeParam cpp_value : Enum::AllValues()) { + const auto cpp_underlying_value = + static_cast>(cpp_value); + const std::optional opt_str_value = + EnumToOptString(cpp_value); + ASSERT_TRUE(opt_str_value.has_value()) + << "cpp_value: " << cpp_underlying_value; + EXPECT_THAT(EnumFromString(*opt_str_value), + ::testing::Optional(cpp_value)) + << "cpp_value: " << cpp_underlying_value; + } +} + +REGISTER_TYPED_TEST_SUITE_P(EnumTest, UnderlyingTypeFits, AllProtoValues, + AllCppValuesString); + +} // namespace operations_research::math_opt + +#endif // OR_TOOLS_MATH_OPT_CPP_ENUMS_TESTING_H_ diff --git a/ortools/math_opt/cpp/remote_streaming_mode_test.cc b/ortools/math_opt/cpp/remote_streaming_mode_test.cc new file mode 100644 index 0000000000..9c72350b10 --- /dev/null +++ b/ortools/math_opt/cpp/remote_streaming_mode_test.cc @@ -0,0 +1,72 @@ +// 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 "ortools/math_opt/cpp/remote_streaming_mode.h" + +#include +#include + +#include "absl/flags/marshalling.h" +#include "absl/strings/escaping.h" +#include "absl/strings/str_cat.h" +#include "gtest/gtest.h" +#include "ortools/base/gmock.h" + +namespace operations_research::math_opt { +namespace { + +using ::testing::HasSubstr; + +// Tests that the AbslParseFlag() and AbslUnparseFlag() functions properly +// roundtrips. +class RemoteStreamingSolveModeRoundtripTest + : public testing::TestWithParam {}; + +TEST_P(RemoteStreamingSolveModeRoundtripTest, Roundtrip) { + RemoteStreamingSolveMode output = RemoteStreamingSolveMode::kDefault; + std::string error; + EXPECT_TRUE(absl::ParseFlag(absl::UnparseFlag(GetParam()), &output, &error)) + << "error: '" << absl::CEscape(error) << "'"; + EXPECT_EQ(output, GetParam()); +} + +INSTANTIATE_TEST_SUITE_P( + All, RemoteStreamingSolveModeRoundtripTest, + testing::Values(RemoteStreamingSolveMode::kDefault, + RemoteStreamingSolveMode::kSubprocess, + RemoteStreamingSolveMode::kInProcess), + [](const testing::TestParamInfo< + RemoteStreamingSolveModeRoundtripTest::ParamType>& info) { + return AbslUnparseFlag(info.param); + }); + +TEST(RemoteStreamingSolveModeTest, InvalidValue) { + RemoteStreamingSolveMode output = RemoteStreamingSolveMode::kDefault; + std::string error; + EXPECT_FALSE(absl::ParseFlag("unknown", &output, &error)) + << "output: " << output; + EXPECT_THAT(error, HasSubstr("unknown value")); +} + +TEST(RemoteStreamingSolveModeTest, PrintToStdStream) { + std::ostringstream oss; + oss << RemoteStreamingSolveMode::kDefault; + EXPECT_EQ(oss.str(), "default"); +} + +TEST(RemoteStreamingSolveModeTest, PrintToAbsl) { + EXPECT_EQ(absl::StrCat(RemoteStreamingSolveMode::kDefault), "default"); +} + +} // namespace +} // namespace operations_research::math_opt diff --git a/ortools/math_opt/cpp/sparse_containers_test.cc b/ortools/math_opt/cpp/sparse_containers_test.cc new file mode 100644 index 0000000000..a81b0d3ac0 --- /dev/null +++ b/ortools/math_opt/cpp/sparse_containers_test.cc @@ -0,0 +1,513 @@ +// 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 "ortools/math_opt/cpp/sparse_containers.h" + +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" +#include "gtest/gtest.h" +#include "ortools/base/gmock.h" +#include "ortools/math_opt/constraints/quadratic/quadratic_constraint.h" +#include "ortools/math_opt/cpp/matchers.h" +#include "ortools/math_opt/cpp/math_opt.h" + +namespace operations_research::math_opt { +namespace { + +using ::testing::HasSubstr; +using ::testing::IsEmpty; +using ::testing::Pair; +using ::testing::UnorderedElementsAre; +using ::testing::status::IsOkAndHolds; +using ::testing::status::StatusIs; + +TEST(DoubleVariableValuesFromProtoTest, Empty) { + ModelStorage model; + model.AddVariable("x"); + + const SparseDoubleVectorProto proto; + + EXPECT_THAT(VariableValuesFromProto(&model, proto), IsOkAndHolds(IsEmpty())); +} + +TEST(DoubleVariableValuesFromProtoTest, NonEmpty) { + ModelStorage model; + const VariableId x = model.AddVariable("x"); + model.AddVariable("y"); + const VariableId z = model.AddVariable("z"); + + SparseDoubleVectorProto proto; + proto.add_ids(0); + proto.add_ids(2); + proto.add_values(3.0); + proto.add_values(-2.0); + + VariableMap expected = {{Variable(&model, x), 3.0}, + {Variable(&model, z), -2.0}}; + + EXPECT_THAT(VariableValuesFromProto(&model, proto), + IsOkAndHolds(IsNear(expected, /*tolerance=*/0.0))); +} + +TEST(DoubleVariableValuesFromProtoTest, UnequalSize) { + ModelStorage model; + model.AddVariable("x"); + + SparseDoubleVectorProto proto; + proto.add_ids(0); + + EXPECT_THAT(VariableValuesFromProto(&model, proto), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(DoubleVariableValuesFromProtoTest, OutOfOrder) { + ModelStorage model; + model.AddVariable("x"); + model.AddVariable("y"); + + SparseDoubleVectorProto proto; + proto.add_ids(1); + proto.add_ids(0); + proto.add_values(1.0); + proto.add_values(1.0); + + EXPECT_THAT(VariableValuesFromProto(&model, proto), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(DoubleVariableValuesFromProtoTest, NotInModel) { + ModelStorage model; + + SparseDoubleVectorProto proto; + proto.add_ids(0); + proto.add_values(1.0); + + EXPECT_THAT(VariableValuesFromProto(&model, proto), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("no variable with id"))); +} + +TEST(DoubleVariableValuesFromProtoTest, SmallMap) { + ModelStorage model; + const VariableId x = model.AddVariable("x"); + model.AddVariable("y"); + const VariableId z = model.AddVariable("z"); + + const VariableMap input = {{Variable(&model, x), 3.0}, + {Variable(&model, z), -2.0}}; + + EXPECT_THAT(VariableValuesFromProto(&model, VariableValuesToProto(input)), + IsOkAndHolds(IsNear(input, /*tolerance=*/0.0))); +} + +TEST(Int32VariableValuesFromProtoTest, UnequalSize) { + Model model; + model.AddVariable("x"); + + SparseInt32VectorProto proto; + proto.add_ids(0); + + EXPECT_THAT(VariableValuesFromProto(model.storage(), proto), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(Int32VariableValuesFromProtoTest, VariableDoesNotExist) { + Model model; + model.AddVariable("x"); + + SparseInt32VectorProto proto; + proto.add_ids(1); + proto.add_values(12); + + EXPECT_THAT(VariableValuesFromProto(model.storage(), proto), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(Int32VariableValuesFromProtoTest, SmallMap) { + Model model; + const Variable x = model.AddVariable("x"); + model.AddVariable("y"); + const Variable z = model.AddVariable("z"); + + SparseInt32VectorProto proto; + proto.add_ids(0); + proto.add_ids(2); + proto.add_values(12); + proto.add_values(11); + + EXPECT_THAT(VariableValuesFromProto(model.storage(), proto), + IsOkAndHolds(UnorderedElementsAre(Pair(testing::Eq(x), 12), + Pair(testing::Eq(z), 11)))); +} + +TEST(AuxiliaryObjectiveValuesFromProtoTest, Empty) { + ModelStorage model; + EXPECT_THAT(AuxiliaryObjectiveValuesFromProto( + &model, google::protobuf::Map()), + IsOkAndHolds(IsEmpty())); +} + +TEST(AuxiliaryObjectiveValuesFromProtoTest, NonEmpty) { + ModelStorage model; + const Objective o = + Objective::Auxiliary(&model, model.AddAuxiliaryObjective(2)); + + google::protobuf::Map proto; + proto[o.id().value()] = 3.0; + + EXPECT_THAT(AuxiliaryObjectiveValuesFromProto(&model, proto), + IsOkAndHolds(UnorderedElementsAre(Pair(o, 3.0)))); +} + +TEST(AuxiliaryObjectiveValuesFromProtoTest, NotInModel) { + ModelStorage model; + + google::protobuf::Map proto; + proto[0] = 3.0; + + EXPECT_THAT(AuxiliaryObjectiveValuesFromProto(&model, proto), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("no auxiliary objective with id"))); +} + +TEST(AuxiliaryObjectiveValuesToProtoTest, Empty) { + EXPECT_THAT(AuxiliaryObjectiveValuesToProto({}), IsEmpty()); +} + +TEST(AuxiliaryObjectiveValuesToProtoTest, NonEmpty) { + ModelStorage model; + const Objective o = + Objective::Auxiliary(&model, model.AddAuxiliaryObjective(2)); + + const absl::flat_hash_map input = {{o, 3.0}}; + + EXPECT_THAT(AuxiliaryObjectiveValuesToProto(input), + UnorderedElementsAre(Pair(o.id().value(), 3.0))); +} + +TEST(AuxiliaryObjectiveValuesToProtoDeathTest, PrimaryObjectiveInMap) { + ModelStorage model; + const Objective o = Objective::Primary(&model); + + const absl::flat_hash_map input = {{o, 3.0}}; + EXPECT_DEATH(AuxiliaryObjectiveValuesToProto(input), "primary"); +} + +TEST(LinearConstraintValuesFromProtoTest, EmptySuccess) { + ModelStorage model; + model.AddLinearConstraint("c"); + + SparseDoubleVectorProto proto; + + EXPECT_THAT(LinearConstraintValuesFromProto(&model, proto), + IsOkAndHolds(IsEmpty())); +} + +TEST(LinearConstraintValuesFromProtoTest, NonEmptySuccess) { + ModelStorage model; + const LinearConstraintId c = model.AddLinearConstraint("c"); + model.AddLinearConstraint("d"); + const LinearConstraintId e = model.AddLinearConstraint("e"); + + SparseDoubleVectorProto proto; + proto.add_ids(0); + proto.add_ids(2); + proto.add_values(3.0); + proto.add_values(-2.0); + + LinearConstraintMap expected = {{LinearConstraint(&model, c), 3.0}, + {LinearConstraint(&model, e), -2.0}}; + + EXPECT_THAT(LinearConstraintValuesFromProto(&model, proto), + IsOkAndHolds(IsNear(expected, /*tolerance=*/0.0))); +} + +TEST(LinearConstraintValuesFromProtoTest, UnequalSize) { + ModelStorage model; + model.AddLinearConstraint("c"); + + SparseDoubleVectorProto proto; + proto.add_ids(0); + + EXPECT_THAT(LinearConstraintValuesFromProto(&model, proto), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(LinearConstraintValuesFromProtoTest, NotInModel) { + ModelStorage model; + + SparseDoubleVectorProto proto; + proto.add_ids(0); + proto.add_values(1.0); + + EXPECT_THAT(LinearConstraintValuesFromProto(&model, proto), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("no linear constraint with id"))); +} + +TEST(LinearConstraintValuesToProtoTest, Empty) { + ModelStorage model; + EXPECT_THAT(LinearConstraintValuesFromProto( + &model, LinearConstraintValuesToProto({})), + IsOkAndHolds(IsEmpty())); +} + +TEST(LinearConstraintValuesToProtoTest, SmallMap) { + ModelStorage model; + const LinearConstraintId c = model.AddLinearConstraint("c"); + model.AddLinearConstraint("d"); + const LinearConstraintId e = model.AddLinearConstraint("e"); + + const LinearConstraintMap input = { + {LinearConstraint(&model, c), 3.0}, {LinearConstraint(&model, e), -2.0}}; + + EXPECT_THAT(LinearConstraintValuesFromProto( + &model, LinearConstraintValuesToProto(input)), + IsOkAndHolds(IsNear(input, /*tolerance=*/0.0))); +} + +TEST(QuadraticConstraintValuesFromProtoTest, EmptySuccess) { + ModelStorage model; + model.AddAtomicConstraint(QuadraticConstraintData{.name = "c"}); + + SparseDoubleVectorProto proto; + + EXPECT_THAT(QuadraticConstraintValuesFromProto(&model, proto), + IsOkAndHolds(IsEmpty())); +} + +TEST(QuadraticConstraintValuesFromProtoTest, NonEmptySuccess) { + ModelStorage model; + const QuadraticConstraintId c = + model.AddAtomicConstraint(QuadraticConstraintData{.name = "c"}); + model.AddAtomicConstraint(QuadraticConstraintData{.name = "d"}); + const QuadraticConstraintId e = + model.AddAtomicConstraint(QuadraticConstraintData{.name = "e"}); + + SparseDoubleVectorProto proto; + proto.add_ids(0); + proto.add_ids(2); + proto.add_values(3.0); + proto.add_values(-2.0); + + absl::flat_hash_map expected = { + {QuadraticConstraint(&model, c), 3.0}, + {QuadraticConstraint(&model, e), -2.0}}; + + EXPECT_THAT(QuadraticConstraintValuesFromProto(&model, proto), + IsOkAndHolds(IsNear(expected, /*tolerance=*/0.0))); +} + +TEST(QuadraticConstraintValuesFromProtoTest, UnequalSize) { + ModelStorage model; + model.AddAtomicConstraint(QuadraticConstraintData{.name = "c"}); + + SparseDoubleVectorProto proto; + proto.add_ids(0); + + EXPECT_THAT(QuadraticConstraintValuesFromProto(&model, proto), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(QuadraticConstraintValuesFromProtoTest, NotInModel) { + ModelStorage model; + + SparseDoubleVectorProto proto; + proto.add_ids(0); + proto.add_values(1.0); + + EXPECT_THAT(QuadraticConstraintValuesFromProto(&model, proto), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("no quadratic constraint with id"))); +} + +TEST(QuadraticConstraintValuesToProtoTest, Empty) { + ModelStorage model; + EXPECT_THAT(QuadraticConstraintValuesFromProto( + &model, QuadraticConstraintValuesToProto({})), + IsOkAndHolds(IsEmpty())); +} + +TEST(QuadraticConstraintValuesToProtoTest, SmallMap) { + ModelStorage model; + const QuadraticConstraintId c = + model.AddAtomicConstraint(QuadraticConstraintData{.name = "c"}); + model.AddAtomicConstraint(QuadraticConstraintData{.name = "d"}); + const QuadraticConstraintId e = + model.AddAtomicConstraint(QuadraticConstraintData{.name = "e"}); + + const absl::flat_hash_map input = { + {QuadraticConstraint(&model, c), 3.0}, + {QuadraticConstraint(&model, e), -2.0}}; + + EXPECT_THAT(QuadraticConstraintValuesFromProto( + &model, QuadraticConstraintValuesToProto(input)), + IsOkAndHolds(IsNear(input, /*tolerance=*/0.0))); +} + +TEST(VariableBasis, EmptyRoundTrip) { + ModelStorage model; + const VariableMap variable_basis; + SparseBasisStatusVector proto; + + // Test one way + EXPECT_THAT(VariableBasisFromProto(&model, proto), + IsOkAndHolds(variable_basis)); + // Test round trip + EXPECT_THAT( + VariableBasisFromProto(&model, VariableBasisToProto(variable_basis)), + IsOkAndHolds(variable_basis)); +} + +TEST(VariableBasis, RoundTrip) { + ModelStorage model; + const VariableId x = model.AddVariable("x"); + const VariableId y = model.AddVariable("y"); + + const VariableMap variable_basis = { + {Variable(&model, x), BasisStatus::kAtUpperBound}, + {Variable(&model, y), BasisStatus::kFree}}; + + SparseBasisStatusVector proto; + proto.add_ids(0); + proto.add_ids(1); + proto.add_values(BASIS_STATUS_AT_UPPER_BOUND); + proto.add_values(BASIS_STATUS_FREE); + + // Test one way + EXPECT_THAT(VariableBasisFromProto(&model, proto), + IsOkAndHolds(variable_basis)); + // Test round trip + EXPECT_THAT( + VariableBasisFromProto(&model, VariableBasisToProto(variable_basis)), + IsOkAndHolds(variable_basis)); +} + +TEST(VariableBasisFromProto, UnequalSize) { + ModelStorage storage; + storage.AddVariable("x"); + storage.AddVariable("y"); + SparseBasisStatusVector proto; + proto.add_ids(0); + proto.add_ids(1); + proto.add_values(BASIS_STATUS_AT_LOWER_BOUND); + EXPECT_THAT(VariableBasisFromProto(&storage, proto), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(VariableBasisFromProto, VariableDoesNotExist) { + ModelStorage storage; + storage.AddVariable("x"); + SparseBasisStatusVector proto; + proto.add_ids(0); + proto.add_ids(1); + proto.add_values(BASIS_STATUS_AT_LOWER_BOUND); + proto.add_values(BASIS_STATUS_AT_UPPER_BOUND); + EXPECT_THAT(VariableBasisFromProto(&storage, proto), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("no variable with id"))); +} + +TEST(VariableBasisFromProto, UnspecifiedStatus) { + ModelStorage storage; + storage.AddVariable("x"); + SparseBasisStatusVector proto; + proto.add_ids(0); + proto.add_values(BASIS_STATUS_UNSPECIFIED); + EXPECT_THAT(VariableBasisFromProto(&storage, proto), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("basis status not specified"))); +} + +TEST(LinearConstraintBasis, EmptyRoundTrip) { + ModelStorage model; + + const LinearConstraintMap linear_constraint_basis; + + SparseBasisStatusVector proto; + + // Test one way + EXPECT_THAT(LinearConstraintBasisFromProto(&model, proto), + IsOkAndHolds(linear_constraint_basis)); + // Test round trip + EXPECT_THAT( + LinearConstraintBasisFromProto( + &model, LinearConstraintBasisToProto(linear_constraint_basis)), + IsOkAndHolds(linear_constraint_basis)); +} + +TEST(LinearConstraintBasis, RoundTrip) { + ModelStorage model; + const LinearConstraintId c = model.AddLinearConstraint("c"); + const LinearConstraintId d = model.AddLinearConstraint("d"); + + const LinearConstraintMap linear_constraint_basis = { + {LinearConstraint(&model, c), BasisStatus::kAtLowerBound}, + {LinearConstraint(&model, d), BasisStatus::kFixedValue}}; + + SparseBasisStatusVector proto; + proto.add_ids(0); + proto.add_ids(1); + proto.add_values(BASIS_STATUS_AT_LOWER_BOUND); + proto.add_values(BASIS_STATUS_FIXED_VALUE); + + // Test one way + EXPECT_THAT(LinearConstraintBasisFromProto(&model, proto), + IsOkAndHolds(linear_constraint_basis)); + // Test round trip + EXPECT_THAT( + LinearConstraintBasisFromProto( + &model, LinearConstraintBasisToProto(linear_constraint_basis)), + IsOkAndHolds(linear_constraint_basis)); +} + +TEST(LinearConstraintBasisFromProto, UnequalSize) { + ModelStorage storage; + storage.AddLinearConstraint("c"); + storage.AddLinearConstraint("d"); + SparseBasisStatusVector proto; + proto.add_ids(0); + proto.add_ids(1); + proto.add_values(BASIS_STATUS_AT_LOWER_BOUND); + EXPECT_THAT(LinearConstraintBasisFromProto(&storage, proto), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(LinearConstraintBasisFromProto, LinearConstraintDoesNotExist) { + ModelStorage storage; + storage.AddLinearConstraint("c"); + SparseBasisStatusVector proto; + proto.add_ids(0); + proto.add_ids(1); + proto.add_values(BASIS_STATUS_AT_LOWER_BOUND); + proto.add_values(BASIS_STATUS_AT_UPPER_BOUND); + EXPECT_THAT(LinearConstraintBasisFromProto(&storage, proto), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("no linear constraint with id"))); +} + +TEST(LinearConstraintBasisFromProto, UnspecifiedStatus) { + ModelStorage storage; + storage.AddLinearConstraint("c"); + SparseBasisStatusVector proto; + proto.add_ids(0); + proto.add_values(BASIS_STATUS_UNSPECIFIED); + EXPECT_THAT(LinearConstraintBasisFromProto(&storage, proto), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("basis status not specified"))); +} + +} // namespace +} // namespace operations_research::math_opt