blob: cb9df1cd7479f3b0d9054d8d64f6f13f0f6fcaa4 [file] [log] [blame] [edit]
//
// Copyright 2024 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 "algorithms/approx-bounds-as-bounds-provider.h"
#include <cstdint>
#include <memory>
#include <utility>
#include "base/testing/status_matchers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/status/statusor.h"
#include "algorithms/approx-bounds.h"
#include "algorithms/bounds-provider.h"
#include "algorithms/internal/clamped-calculation-without-bounds.h"
namespace differential_privacy {
namespace {
using ::absl_testing::IsOkAndHolds;
using ::absl_testing::StatusIs;
using ::testing::AllOf;
using ::testing::Eq;
using ::testing::Field;
using ::testing::Gt;
using ::testing::HasSubstr;
using ::testing::Lt;
template <typename T>
class ApproxBoundsAsBoundsProviderTypedTest : public ::testing::Test {};
using TestingTypes = ::testing::Types<int64_t, double>;
TYPED_TEST_SUITE(ApproxBoundsAsBoundsProviderTypedTest, TestingTypes);
TYPED_TEST(ApproxBoundsAsBoundsProviderTypedTest,
FinalizeAndCalculateBoundsReturnsErrorOnEmptyInput) {
absl::StatusOr<std::unique_ptr<ApproxBounds<TypeParam>>> approx_bounds =
typename ApproxBounds<TypeParam>::Builder().SetEpsilon(1).Build();
ASSERT_OK(approx_bounds.status());
ApproxBoundsAsBoundsProvider<TypeParam> bounds_provider(
std::move(approx_bounds).value());
EXPECT_THAT(bounds_provider.FinalizeAndCalculateBounds(),
StatusIs(absl::StatusCode::kFailedPrecondition,
HasSubstr("run over a larger dataset")));
}
TYPED_TEST(ApproxBoundsAsBoundsProviderTypedTest,
FinalizeAndCalculateBoundsReturnsResultOnLargeInput) {
absl::StatusOr<std::unique_ptr<ApproxBounds<TypeParam>>> approx_bounds =
typename ApproxBounds<TypeParam>::Builder().SetEpsilon(1e20).Build();
ASSERT_OK(approx_bounds.status());
ApproxBoundsAsBoundsProvider<TypeParam> bounds_provider(
std::move(approx_bounds).value());
for (int i = -10000; i < 10000; ++i) {
bounds_provider.AddEntry(i);
}
EXPECT_THAT(
bounds_provider.FinalizeAndCalculateBounds(),
IsOkAndHolds(AllOf(
Field("lower_bound", &BoundsResult<TypeParam>::lower_bound, Lt(0)),
Field("upper_bound", &BoundsResult<TypeParam>::upper_bound, Gt(0)))));
}
TYPED_TEST(ApproxBoundsAsBoundsProviderTypedTest,
GetBoundingReportReturnsReportWithSameBounds) {
absl::StatusOr<std::unique_ptr<ApproxBounds<TypeParam>>> approx_bounds =
typename ApproxBounds<TypeParam>::Builder().SetEpsilon(1e20).Build();
ASSERT_OK(approx_bounds.status());
ApproxBoundsAsBoundsProvider<TypeParam> bounds_provider(
std::move(approx_bounds).value());
for (int i = -10000; i < 10000; ++i) {
bounds_provider.AddEntry(i);
}
const absl::StatusOr<BoundsResult<TypeParam>> result =
bounds_provider.FinalizeAndCalculateBounds();
ASSERT_OK(result.status());
const BoundingReport report =
bounds_provider.GetBoundingReport(result.value());
EXPECT_THAT(GetValue<TypeParam>(report.lower_bound()),
Eq(result->lower_bound));
EXPECT_THAT(GetValue<TypeParam>(report.upper_bound()),
Eq(result->upper_bound));
}
TYPED_TEST(ApproxBoundsAsBoundsProviderTypedTest,
FinalizeAndCalculateBoundsAfterResetReturnsError) {
absl::StatusOr<std::unique_ptr<ApproxBounds<TypeParam>>> approx_bounds =
typename ApproxBounds<TypeParam>::Builder().SetEpsilon(1).Build();
ASSERT_OK(approx_bounds.status());
ApproxBoundsAsBoundsProvider<TypeParam> bounds_provider(
std::move(approx_bounds).value());
for (int i = -10000; i < 10000; ++i) {
bounds_provider.AddEntry(i);
}
bounds_provider.Reset();
EXPECT_THAT(bounds_provider.FinalizeAndCalculateBounds(),
StatusIs(absl::StatusCode::kFailedPrecondition,
HasSubstr("run over a larger dataset")));
}
TYPED_TEST(ApproxBoundsAsBoundsProviderTypedTest,
MemoryUsedReturnsValueLargerThanObjectSize) {
absl::StatusOr<std::unique_ptr<ApproxBounds<TypeParam>>> approx_bounds =
typename ApproxBounds<TypeParam>::Builder().SetEpsilon(1).Build();
ASSERT_OK(approx_bounds.status());
ApproxBoundsAsBoundsProvider<TypeParam> bounds_provider(
std::move(approx_bounds).value());
EXPECT_THAT(bounds_provider.MemoryUsed(),
Gt(sizeof(ApproxBoundsAsBoundsProvider<TypeParam>)));
}
TYPED_TEST(ApproxBoundsAsBoundsProviderTypedTest,
GetEpsilonReturnsExpectedValue) {
absl::StatusOr<std::unique_ptr<ApproxBounds<TypeParam>>> approx_bounds =
typename ApproxBounds<TypeParam>::Builder().SetEpsilon(1.234).Build();
ASSERT_OK(approx_bounds.status());
ApproxBoundsAsBoundsProvider<TypeParam> bounds_provider(
std::move(approx_bounds).value());
EXPECT_THAT(bounds_provider.GetEpsilon(), Eq(1.234));
}
TYPED_TEST(ApproxBoundsAsBoundsProviderTypedTest, GetDeltaReturnsZero) {
// Delta is currently ignored for approx bounds.
absl::StatusOr<std::unique_ptr<ApproxBounds<TypeParam>>> approx_bounds =
typename ApproxBounds<TypeParam>::Builder()
.SetEpsilon(1)
.SetDelta(0)
.Build();
ASSERT_OK(approx_bounds.status());
ApproxBoundsAsBoundsProvider<TypeParam> bounds_provider(
std::move(approx_bounds).value());
EXPECT_THAT(bounds_provider.GetDelta(), Eq(0));
}
TYPED_TEST(ApproxBoundsAsBoundsProviderTypedTest,
MergeWithEmptyBoundsSummaryFails) {
absl::StatusOr<std::unique_ptr<ApproxBounds<TypeParam>>> approx_bounds =
typename ApproxBounds<TypeParam>::Builder().SetEpsilon(1e20).Build();
ASSERT_OK(approx_bounds.status());
ApproxBoundsAsBoundsProvider<TypeParam> bounds_provider(
std::move(approx_bounds).value());
EXPECT_THAT(bounds_provider.Merge(BoundsSummary()),
StatusIs(absl::StatusCode::kInternal,
HasSubstr("approx_bounds_summary must be set")));
}
TYPED_TEST(ApproxBoundsAsBoundsProviderTypedTest,
SerialzieFromPopulatedAndMergeEmptyBoundsProviderReturnsBounds) {
absl::StatusOr<std::unique_ptr<ApproxBounds<TypeParam>>>
populated_approx_bounds =
typename ApproxBounds<TypeParam>::Builder().SetEpsilon(1e20).Build();
ASSERT_OK(populated_approx_bounds.status());
ApproxBoundsAsBoundsProvider<TypeParam> populated_bounds_provider(
std::move(populated_approx_bounds).value());
for (int i = -10000; i < 10000; ++i) {
populated_bounds_provider.AddEntry(i);
}
absl::StatusOr<std::unique_ptr<ApproxBounds<TypeParam>>> empty_approx_bounds =
typename ApproxBounds<TypeParam>::Builder().SetEpsilon(1e20).Build();
ASSERT_OK(empty_approx_bounds.status());
ApproxBoundsAsBoundsProvider<TypeParam> empty_bounds_provider(
std::move(empty_approx_bounds).value());
ASSERT_OK(empty_bounds_provider.Merge(populated_bounds_provider.Serialize()));
EXPECT_THAT(
empty_bounds_provider.FinalizeAndCalculateBounds(),
IsOkAndHolds(AllOf(
Field("lower_bound", &BoundsResult<TypeParam>::lower_bound, Lt(0)),
Field("upper_bound", &BoundsResult<TypeParam>::upper_bound, Gt(0)))));
}
TYPED_TEST(ApproxBoundsAsBoundsProviderTypedTest,
CreateClampedCalculationWithoutBoundsHasSameProperties) {
absl::StatusOr<std::unique_ptr<ApproxBounds<TypeParam>>> approx_bounds =
typename ApproxBounds<TypeParam>::Builder()
.SetEpsilon(1)
.SetNumBins(10)
.SetBase(3)
.SetScale(2)
.Build();
ASSERT_OK(approx_bounds.status());
ApproxBoundsAsBoundsProvider<TypeParam> bounds_provider(
std::move(approx_bounds).value());
std::unique_ptr<internal::ClampedCalculationWithoutBounds<TypeParam>>
clamped_calculation =
bounds_provider.CreateClampedCalculationWithoutBounds();
EXPECT_THAT(clamped_calculation->GetNumBins(), Eq(10));
EXPECT_THAT(clamped_calculation->GetBaseForTesting(), Eq(3));
EXPECT_THAT(clamped_calculation->GetScaleForTesting(), Eq(2));
}
} // namespace
} // namespace differential_privacy