blob: 427898226f83087641f03ab555cf224db84a6371 [file] [log] [blame]
//
// Copyright 2020 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.
//
package pbeam
import (
"reflect"
"testing"
"github.com/google/differential-privacy/go/v2/dpagg"
"github.com/google/differential-privacy/go/v2/noise"
"github.com/google/differential-privacy/privacy-on-beam/v2/pbeam/testutils"
"github.com/apache/beam/sdks/v2/go/pkg/beam"
"github.com/apache/beam/sdks/v2/go/pkg/beam/testing/ptest"
"github.com/apache/beam/sdks/v2/go/pkg/beam/transforms/stats"
)
// Checks that Count returns a correct answer by verifying:
// - partition selection is applied
// - per-partition contribution bounding is applied
// - duplicate user contributions within per-partition contribution bound aren't dropped
func TestCountNoNoise(t *testing.T) {
// In this test, we set the per-partition l1Sensitivity to 2, and:
// - value 0 is associated with 7 privacy units, so it should be thresholded;
// - value 1 is associated with 30 privacy units appearing twice each, so each of
// them should be counted twice;
// - value 2 is associated with 50 privacy units appearing 3 times each, but the
// l1Sensitivity is 2, so each should only be counted twice.
// Each privacy unit contributes to at most 1 partition.
pairs := testutils.ConcatenatePairs(
testutils.MakePairsWithFixedVStartingFromKey(0, 7, 0),
testutils.MakePairsWithFixedVStartingFromKey(7, 30, 1),
testutils.MakePairsWithFixedVStartingFromKey(7, 30, 1),
testutils.MakePairsWithFixedVStartingFromKey(7+30, 50, 2),
testutils.MakePairsWithFixedVStartingFromKey(7+30, 50, 2),
testutils.MakePairsWithFixedVStartingFromKey(7+30, 50, 2),
)
result := []testutils.TestInt64Metric{
{1, 60}, // 30*2
{2, 100}, // 50*2
}
p, s, col, want := ptest.CreateList2(pairs, result)
col = beam.ParDo(s, testutils.PairToKV, col)
// ε=25, δ=10⁻²⁰⁰ and l0Sensitivity=1 gives a threshold of ≈21.
// We have 3 partitions. So, to get an overall flakiness of 10⁻²³,
// we need to have each partition pass with 1-10⁻²⁵ probability (k=25).
// To see the logic and the math behind flakiness and tolerance calculation,
// See https://github.com/google/differential-privacy/blob/main/privacy-on-beam/docs/Tolerance_Calculation.pdf.
epsilon, delta, k, l1Sensitivity := 25.0, 1e-200, 25.0, 2.0
// ε is split by 2 for noise and for partition selection, so we use 2*ε to get a Laplace noise with ε.
pcol := MakePrivate(s, col, NewPrivacySpec(2*epsilon, delta))
got := Count(s, pcol, CountParams{MaxValue: 2, MaxPartitionsContributed: 1, NoiseKind: LaplaceNoise{}})
want = beam.ParDo(s, testutils.Int64MetricToKV, want)
if err := testutils.ApproxEqualsKVInt64(s, got, want, testutils.RoundedLaplaceTolerance(k, l1Sensitivity, epsilon)); err != nil {
t.Fatalf("TestCountNoNoise: %v", err)
}
if err := ptest.Run(p); err != nil {
t.Errorf("TestCountNoNoise: Count(%v) = %v, expected %v: %v", col, got, want, err)
}
}
// Checks that Count with partitions returns a correct answer.
func TestCountWithPartitionsNoNoise(t *testing.T) {
// We have two test cases, one for public partitions as a PCollection and one for public partitions as a slice (i.e., in-memory).
for _, tc := range []struct {
inMemory bool
}{
{true},
{false},
} {
var pairs []testutils.PairII
for i := 0; i < 10; i++ {
pairs = append(pairs, testutils.PairII{1, i})
}
result := []testutils.TestInt64Metric{
// Drop partitions 0 to 8 as they are not in public
// partitions.
{9, 1}, // Keep partition 9.
{10, 0}, // Add partition 10.
}
p, s, col, want := ptest.CreateList2(pairs, result)
col = beam.ParDo(s, testutils.PairToKV, col)
publicPartitionsSlice := []int{9, 10}
var publicPartitions interface{}
if tc.inMemory {
publicPartitions = publicPartitionsSlice
} else {
publicPartitions = beam.CreateList(s, publicPartitionsSlice)
}
// We use ε=50, δ=0 and l1Sensitivity=2.
// We have 2 partitions. So, to get an overall flakiness of 10⁻²³,
// we need to have each partition pass with 1-10⁻²⁵ probability (k=25).
epsilon, delta, k, l1Sensitivity := 50.0, 0.0, 25.0, 2.0
pcol := MakePrivate(s, col, NewPrivacySpec(epsilon, delta))
countParams := CountParams{MaxValue: 2, MaxPartitionsContributed: 1, NoiseKind: LaplaceNoise{}, PublicPartitions: publicPartitions}
got := Count(s, pcol, countParams)
want = beam.ParDo(s, testutils.Int64MetricToKV, want)
if err := testutils.ApproxEqualsKVInt64(s, got, want, testutils.RoundedLaplaceTolerance(k, l1Sensitivity, epsilon)); err != nil {
t.Fatalf("TestCountWithPartitionsNoNoise in-memory=%t: %v", tc.inMemory, err)
}
if err := ptest.Run(p); err != nil {
t.Errorf("TestCountWithPartitionsNoNoise in-memory=%t: Count(%v) = %v, expected %v: %v", tc.inMemory, col, got, want, err)
}
}
}
// Checks that Count applies partition selection.
func TestCountPartitionSelection(t *testing.T) {
for _, tc := range []struct {
name string
noiseKind NoiseKind
epsilon float64
delta float64
numPartitions int
countPerValue int
}{
{
name: "Gaussian",
noiseKind: GaussianNoise{},
// After splitting the (ε, δ) budget between the noise and partition
// selection portions of the privacy algorithm, this results in a ε=1,
// δ=0.3 partition selection budget.
epsilon: 2,
delta: 0.6,
// countPerValue=1 yields a 30% chance of emitting any particular partition
// (since δ_emit=0.3).
countPerValue: 1,
// 143 distinct partitions implies that some (but not all) partitions are
// emitted with high probability (at least 1 - 1e-20).
numPartitions: 143,
},
{
name: "Laplace",
noiseKind: LaplaceNoise{},
// After splitting the (ε, δ) budget between the noise and partition
// selection portions of the privacy algorithm, this results in the
// partition selection portion of the budget being ε_selectPartition=1,
// δ_selectPartition=0.3.
epsilon: 2,
delta: 0.3,
// countPerValue=1 yields a 30% chance of emitting any particular partition
// (since δ_emit=0.3).
countPerValue: 1,
numPartitions: 143,
},
} {
t.Run(tc.name, func(t *testing.T) {
// Verify that countPerValue is sensical.
if tc.countPerValue <= 0 {
t.Fatalf("Invalid test case: countPerValue must be positive. Got: %d", tc.countPerValue)
}
// Build up {ID, Value} pairs such that tc.countPerValue privacy units
// contribute to each of the tc.numPartitions partitions:
// {0,0}, {1,0}, …, {countPerValue-1,0}
// {countPerValue,1}, {countPerValue+1,1}, …, {countPerValue*2-1,1}
// …
// {countPerValue*(numPartitions-1),numPartitions-1}, …, {countPerValue*numPartitions-1, numPartitions-1}
var (
pairs []testutils.PairII
kOffset = 0
)
for i := 0; i < tc.numPartitions; i++ {
for j := 0; j < tc.countPerValue; j++ {
pairs = append(pairs, testutils.PairII{kOffset + j, i})
}
}
p, s, col := ptest.CreateList(pairs)
col = beam.ParDo(s, testutils.PairToKV, col)
// Run Count on pairs
pcol := MakePrivate(s, col, NewPrivacySpec(tc.epsilon, tc.delta))
got := Count(s, pcol, CountParams{MaxValue: 1, MaxPartitionsContributed: 1, NoiseKind: tc.noiseKind})
got = beam.ParDo(s, testutils.KVToInt64Metric, got)
// Validate that partition selection is applied (i.e., some emitted and some dropped).
testutils.CheckSomePartitionsAreDropped(s, got, tc.numPartitions)
if err := ptest.Run(p); err != nil {
t.Errorf("%v", err)
}
})
}
}
// Checks that Count adds noise to its output.
func TestCountAddsNoise(t *testing.T) {
for _, tc := range []struct {
name string
noiseKind NoiseKind
// Differential privacy params used.
epsilon float64
delta float64
}{
{
name: "Gaussian",
noiseKind: GaussianNoise{},
epsilon: 2 * 1e-15, // It is split by 2: 1e-15 for the noise and 1e-15 for the partition selection.
delta: 2 * 1e-5, // It is split by 2: 1e-5 for the noise and 1e-5 for the partition selection.
},
{
name: "Laplace",
noiseKind: LaplaceNoise{},
epsilon: 2 * 1e-15, // It is split by 2: 1e-15 for the noise and 1e-15 for the partition selection.
delta: 0.01,
},
} {
// Because this is an integer aggregation, we can't use the regular complementary
// tolerance computations. Instead, we do the following:
//
// If generated noise is between -0.5 and 0.5, it will be rounded to 0 and the
// test will fail. For Laplace, this will happen with probability
// P ~= Laplace_CDF(0.5) - Laplace_CDF(-0.5).
// Given that Laplace scale = l1_sensitivity / ε = 10¹⁵, P ~= 5e-16.
// For Gaussian, this will happen with probability
// P ~= Gaussian_CDF(0.5) - Gaussian_CDF(-0.5).
// For given ε=1e-15, δ=1e-5 => sigma = 39904, P ~= 1e-5.
//
// We want to keep numIDs low (otherwise the tests take a long time) while
// also keeping P low. We use magic partition selection here, meaning that
// numIDs cap at 1/δ. So, we can have tiny epsilon without having to worry
// about tests taking long.
tolerance := 0.0
l0Sensitivity, lInfSensitivity := int64(1), int64(1)
partitionSelectionEpsilon, partitionSelectionDelta := tc.epsilon/2, tc.delta
if tc.noiseKind == gaussianNoise {
partitionSelectionDelta = tc.delta / 2
}
// Compute the number of IDs needed to keep the partition.
sp, err := dpagg.NewPreAggSelectPartition(
&dpagg.PreAggSelectPartitionOptions{
Epsilon: partitionSelectionEpsilon,
Delta: partitionSelectionDelta,
MaxPartitionsContributed: l0Sensitivity,
})
if err != nil {
t.Fatalf("Couldn't initialize PreAggSelectPartition necessary to compute the number of IDs needed: %v", err)
}
numIDs, err := sp.GetHardThreshold()
if err != nil {
t.Fatalf("Couldn't compute hard threshold: %v", err)
}
// pairs contains {1,0}, {2,0}, …, {numIDs,0}.
pairs := testutils.MakePairsWithFixedV(numIDs, 0)
p, s, col := ptest.CreateList(pairs)
col = beam.ParDo(s, testutils.PairToKV, col)
pcol := MakePrivate(s, col, NewPrivacySpec(tc.epsilon, tc.delta))
got := Count(s, pcol, CountParams{MaxPartitionsContributed: l0Sensitivity, MaxValue: lInfSensitivity, NoiseKind: tc.noiseKind})
got = beam.ParDo(s, testutils.KVToInt64Metric, got)
testutils.CheckInt64MetricsAreNoisy(s, got, numIDs, tolerance)
if err := ptest.Run(p); err != nil {
t.Errorf("CountPerKey didn't add any %s noise: %v", tc.name, err)
}
}
}
// Checks that Count with partitions adds noise to its output.
func TestCountAddsNoiseWithPartitions(t *testing.T) {
for _, tc := range []struct {
desc string
noiseKind NoiseKind
// Differential privacy params used.
epsilon float64
delta float64
inMemory bool
}{
// ε & δ are not split because partitions are public. All of them are used for the noise.
{
desc: "as PCollection w/ Gaussian",
noiseKind: GaussianNoise{},
epsilon: 1e-15,
delta: 1e-15,
inMemory: false,
},
{
desc: "as slice w/ Gaussian",
noiseKind: GaussianNoise{},
epsilon: 1e-15,
delta: 1e-15,
inMemory: true,
},
{
desc: "as PCollection w/ Laplace",
noiseKind: LaplaceNoise{},
epsilon: 1e-15,
delta: 0, // It is 0 because partitions are public and we are using Laplace noise.
inMemory: false,
},
{
desc: "as slice w/ Laplace",
noiseKind: LaplaceNoise{},
epsilon: 1e-15,
delta: 0, // It is 0 because partitions are public and we are using Laplace noise.
inMemory: true,
},
} {
// Because this is an integer aggregation, we can't use the regular complementary
// tolerance computations. Instead, we do the following:
//
// If generated noise is between -0.5 and 0.5, it will be rounded to 0 and the
// test will fail. For Laplace, this will happen with probability
// P ~= Laplace_CDF(0.5) - Laplace_CDF(-0.5).
// Given that Laplace scale = l1_sensitivity / ε = 10¹⁵, P ~= 5e-16.
// For Gaussian, this will happen with probability
// P ~= Gaussian_CDF(0.5) - Gaussian_CDF(-0.5).
// For given ε=1e-15, δ=1e-15 => sigma = 261134011596800, P ~= 1e-15.
//
// Since no partitions selection / thresholding happens, numIDs doesn't depend
// on ε & δ. We can use arbitrarily small ε & δ.
tolerance := 0.0
l0Sensitivity, lInfSensitivity := int64(1), int64(1)
numIDs := 10
// pairs contains {1,0}, {2,0}, …, {10,0}.
pairs := testutils.MakePairsWithFixedV(numIDs, 0)
p, s, col := ptest.CreateList(pairs)
publicPartitionsSlice := []int{0}
var publicPartitions interface{}
if tc.inMemory {
publicPartitions = publicPartitionsSlice
} else {
publicPartitions = beam.CreateList(s, publicPartitionsSlice)
}
col = beam.ParDo(s, testutils.PairToKV, col)
pcol := MakePrivate(s, col, NewPrivacySpec(tc.epsilon, tc.delta))
countParams := CountParams{MaxPartitionsContributed: l0Sensitivity, MaxValue: lInfSensitivity, NoiseKind: tc.noiseKind, PublicPartitions: publicPartitions}
got := Count(s, pcol, countParams)
got = beam.ParDo(s, testutils.KVToInt64Metric, got)
testutils.CheckInt64MetricsAreNoisy(s, got, 10, tolerance)
if err := ptest.Run(p); err != nil {
t.Errorf("CountPerKey with public partitions %s didn't add any noise: %v", tc.desc, err)
}
}
}
// Checks that Count bounds cross-partition contributions correctly.
func TestCountCrossPartitionContributionBounding(t *testing.T) {
// pairs contains {1,0}, {2,0}, …, {50,0}, {1,1}, …, {50,1}, {1,2}, …, {50,9}.
var pairs []testutils.PairII
for i := 0; i < 10; i++ {
pairs = append(pairs, testutils.MakePairsWithFixedV(50, i)...)
}
result := []testutils.TestInt64Metric{
{0, 150},
}
p, s, col, want := ptest.CreateList2(pairs, result)
col = beam.ParDo(s, testutils.PairToKV, col)
// ε=50, δ=0.01 and l0Sensitivity=3 gives a threshold of 3.
// We have 10 partitions. So, to get an overall flakiness of 10⁻²³,
// we need to have each partition pass with 1-10⁻²⁵ probability (k=25).
epsilon, delta, k, l1Sensitivity := 50.0, 0.01, 25.0, 3.0
// ε is split by 2 for noise and for partition selection, so we use 2*ε to get a Laplace noise with ε.
pcol := MakePrivate(s, col, NewPrivacySpec(2*epsilon, delta))
got := Count(s, pcol, CountParams{MaxPartitionsContributed: 3, MaxValue: 1, NoiseKind: LaplaceNoise{}})
// With a max contribution of 3, 70% of the data should be
// dropped. The sum of all elements must then be 150.
counts := beam.DropKey(s, got)
sumOverPartitions := stats.Sum(s, counts)
got = beam.AddFixedKey(s, sumOverPartitions) // Adds a fixed key of 0.
want = beam.ParDo(s, testutils.Int64MetricToKV, want)
if err := testutils.ApproxEqualsKVInt64(s, got, want, testutils.RoundedLaplaceTolerance(k, l1Sensitivity, epsilon)); err != nil {
t.Fatalf("TestCountCrossPartitionContributionBounding: %v", err)
}
if err := ptest.Run(p); err != nil {
t.Errorf("TestCountCrossPartitionContributionBounding: Metric(%v) = %v, expected elements to sum to 150: %v", col, got, err)
}
}
// Checks that Count with partitions bounds per-user contributions correctly.
func TestCountWithPartitionsCrossPartitionContributionBounding(t *testing.T) {
// We have two test cases, one for public partitions as a PCollection and one for public partitions as a slice (i.e., in-memory).
for _, tc := range []struct {
inMemory bool
}{
{true},
{false},
} {
// pairs contains {1,0}, {2,0}, …, {50,0}, {1,1}, …, {50,1}, {1,2}, …, {50,9}.
var pairs []testutils.PairII
for i := 0; i < 10; i++ {
pairs = append(pairs, testutils.MakePairsWithFixedV(50, i)...)
}
result := []testutils.TestInt64Metric{
{0, 150},
}
p, s, col, want := ptest.CreateList2(pairs, result)
col = beam.ParDo(s, testutils.PairToKV, col)
publicPartitionsSlice := []int{0, 1, 2, 3, 4}
var publicPartitions interface{}
if tc.inMemory {
publicPartitions = publicPartitionsSlice
} else {
publicPartitions = beam.CreateList(s, publicPartitionsSlice)
}
// We have 5 partitions. So, to get an overall flakiness of 10⁻²³,
// we need to have each partition pass with 1-10⁻²⁵ probability (k=25).
epsilon, delta, k, l1Sensitivity := 50.0, 0.0, 25.0, 3.0
pcol := MakePrivate(s, col, NewPrivacySpec(epsilon, delta))
countParams := CountParams{MaxPartitionsContributed: 3, MaxValue: 1, NoiseKind: LaplaceNoise{}, PublicPartitions: publicPartitions}
got := Count(s, pcol, countParams)
// With a max contribution of 3, 40% of the data from the public partitions should be dropped.
// The sum of all elements must then be 150.
counts := beam.DropKey(s, got)
sumOverPartitions := stats.Sum(s, counts)
got = beam.AddFixedKey(s, sumOverPartitions) // Adds a fixed key of 0.
want = beam.ParDo(s, testutils.Int64MetricToKV, want)
if err := testutils.ApproxEqualsKVInt64(s, got, want, testutils.RoundedLaplaceTolerance(k, l1Sensitivity, epsilon)); err != nil {
t.Fatalf("TestCountWithPartitionsCrossPartitionContributionBounding in-memory=%t: %v", tc.inMemory, err)
}
if err := ptest.Run(p); err != nil {
t.Errorf("TestCountWithPartitionsCrossPartitionContributionBounding in-memory=%t: Metric(%v) = %v, expected elements to sum to 150: %v", tc.inMemory, col, got, err)
}
}
}
// Check that no negative values are returned from Count.
func TestCountReturnsNonNegative(t *testing.T) {
var pairs []testutils.PairII
for i := 0; i < 100; i++ {
pairs = append(pairs, testutils.PairII{i, i})
}
p, s, col := ptest.CreateList(pairs)
col = beam.ParDo(s, testutils.PairToKV, col)
// Using a low epsilon and high maxValue adds a lot of noise and using
// a high delta keeps many partitions.
epsilon, delta, maxValue := 0.001, 0.999, int64(1e8)
pcol := MakePrivate(s, col, NewPrivacySpec(epsilon, delta))
counts := Count(s, pcol, CountParams{MaxValue: maxValue, MaxPartitionsContributed: 1, NoiseKind: GaussianNoise{}})
values := beam.DropKey(s, counts)
// Check if we have negative elements.
beam.ParDo0(s, testutils.CheckNoNegativeValuesInt64Fn, values)
if err := ptest.Run(p); err != nil {
t.Errorf("TestCountReturnsNonNegative returned errors: %v", err)
}
}
// Check that no negative values are returned from Count with partitions.
func TestCountWithPartitionsReturnsNonNegative(t *testing.T) {
// We have two test cases, one for public partitions as a PCollection and one for public partitions as a slice (i.e., in-memory).
for _, tc := range []struct {
inMemory bool
}{
{true},
{false},
} {
var pairs []testutils.PairII
var publicPartitionsSlice []int
for i := 0; i < 100; i++ {
pairs = append(pairs, testutils.PairII{i, i})
}
for i := 0; i < 200; i++ {
publicPartitionsSlice = append(publicPartitionsSlice, i)
}
p, s, col := ptest.CreateList(pairs)
var publicPartitions interface{}
if tc.inMemory {
publicPartitions = publicPartitionsSlice
} else {
publicPartitions = beam.CreateList(s, publicPartitionsSlice)
}
col = beam.ParDo(s, testutils.PairToKV, col)
// Using a low epsilon and high maxValue adds a lot of noise and using
// a high delta keeps many partitions.
epsilon, delta, maxValue := 0.001, 0.999, int64(1e8)
pcol := MakePrivate(s, col, NewPrivacySpec(epsilon, delta))
countParams := CountParams{MaxValue: maxValue, MaxPartitionsContributed: 1, NoiseKind: GaussianNoise{}, PublicPartitions: publicPartitions}
counts := Count(s, pcol, countParams)
values := beam.DropKey(s, counts)
// Check if we have negative elements.
beam.ParDo0(s, testutils.CheckNoNegativeValuesInt64Fn, values)
if err := ptest.Run(p); err != nil {
t.Errorf("TestCountWithPartitionsReturnsNonNegative in-memory=%t returned errors: %v", tc.inMemory, err)
}
}
}
func TestCheckCountParams(t *testing.T) {
_, _, partitions := ptest.CreateList([]int{0})
for _, tc := range []struct {
desc string
params CountParams
epsilon float64
delta float64
noiseKind noise.Kind
partitionType reflect.Type
wantErr bool
}{
{
desc: "valid parameters w/o public partitions",
params: CountParams{MaxValue: 1},
epsilon: 1,
delta: 1e-10,
noiseKind: noise.LaplaceNoise,
partitionType: nil,
wantErr: false,
},
{
desc: "valid parameters w/ public partitions",
params: CountParams{MaxValue: 1, PublicPartitions: []int{0}},
epsilon: 1,
delta: 0,
noiseKind: noise.LaplaceNoise,
partitionType: reflect.TypeOf(0),
wantErr: false,
},
{
desc: "negative epsilon",
params: CountParams{MaxValue: 1},
epsilon: -1,
delta: 1e-10,
noiseKind: noise.LaplaceNoise,
partitionType: nil,
wantErr: true,
},
{
desc: "zero delta w/o public partitions",
params: CountParams{MaxValue: 1},
epsilon: 1,
delta: 0,
noiseKind: noise.LaplaceNoise,
partitionType: nil,
wantErr: true,
},
{
desc: "non-zero delta w/ public partitions & laplace noise",
params: CountParams{MaxValue: 1, PublicPartitions: []int{}},
epsilon: 1,
delta: 1e-10,
noiseKind: noise.LaplaceNoise,
partitionType: reflect.TypeOf(0),
wantErr: true,
},
{
desc: "wrong partition type w/ public partitions as beam.PCollection",
params: CountParams{MaxValue: 1, PublicPartitions: partitions},
epsilon: 1,
delta: 0,
noiseKind: noise.LaplaceNoise,
partitionType: reflect.TypeOf(""),
wantErr: true,
},
{
desc: "wrong partition type w/ public partitions as slice",
params: CountParams{MaxValue: 1, PublicPartitions: []int{0}},
epsilon: 1,
delta: 0,
noiseKind: noise.LaplaceNoise,
partitionType: reflect.TypeOf(""),
wantErr: true,
},
{
desc: "wrong partition type w/ public partitions as array",
params: CountParams{MaxValue: 1, PublicPartitions: [1]int{0}},
epsilon: 1,
delta: 0,
noiseKind: noise.LaplaceNoise,
partitionType: reflect.TypeOf(""),
wantErr: true,
},
{
desc: "public partitions as something other than beam.PCollection, slice or array",
params: CountParams{MaxValue: 1, PublicPartitions: ""},
epsilon: 1,
delta: 0,
noiseKind: noise.LaplaceNoise,
partitionType: reflect.TypeOf(""),
wantErr: true,
},
{
desc: "negative max value",
params: CountParams{MaxValue: -1},
epsilon: 1,
delta: 1e-10,
noiseKind: noise.LaplaceNoise,
partitionType: nil,
wantErr: true,
},
} {
if err := checkCountParams(tc.params, tc.epsilon, tc.delta, tc.noiseKind, tc.partitionType); (err != nil) != tc.wantErr {
t.Errorf("With %s, got=%v error, wantErr=%t", tc.desc, err, tc.wantErr)
}
}
}