blob: 68fafb285f392854e349b7e3f4bf57e943980252 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package privacy
import (
"config"
"testing"
"github.com/golang/glog"
)
// The try-bots expect glog to be imported, but we do not use it.
var _ = glog.Info
var testParamRecords = [][]string{
{"1.0", "10000", "1", "0.0024182694032788277", "9"},
{"1.0", "20000", "1", "0.0013450710102915764", "10"},
{"1.0", "10000", "2", "0.004249398596584797", "7"},
{"1.0", "20000", "2", "0.002368534915149212", "9"},
{"1.0", "10000", "10", "0.019537480548024178", "4"},
{"1.0", "20000", "10", "0.011127800680696964", "5"},
{"5.0", "10000", "1", "0.000906200148165226", "12"},
{"5.0", "20000", "1", "0.000491277314722538", "15"},
{"5.0", "10000", "2", "0.0009743086993694305", "12"},
{"5.0", "20000", "2", "0.0005256505683064461", "14"},
{"5.0", "10000", "10", "0.0014028092846274376", "10"},
{"5.0", "20000", "10", "0.0007524723187088966", "13"},
}
func TestParsePrivacyEncodingRecord(t *testing.T) {
var tests = []struct {
input []string
valid bool
expectedKey paramsMapKey
expectedParams PrivacyEncodingParams
}{
// Valid records:
{[]string{"1.0", "160000", "1", "0.00022002216428518295", "19"}, true, paramsMapKey{1.0, 160000, 1}, PrivacyEncodingParams{0.00022002216428518295, 19}},
{[]string{"1.0", "2560000", "1", "1.821480691432953e-05", "44"}, true, paramsMapKey{1.0, 2560000, 1}, PrivacyEncodingParams{1.821480691432953e-05, 44}},
// Invalid records:
// Wrong number of columns.
{[]string{"1.0", "2560000", "1", "1.821480691432953e-05"}, false, paramsMapKey{}, PrivacyEncodingParams{}},
{[]string{"1.0", "2560000", "1", "1.821480691432953e-05", "44", "42"}, false, paramsMapKey{}, PrivacyEncodingParams{}},
// Unparseable entry.
{[]string{"1.0", "2560000.1234", "1", "1.821480691432953e-05", "44"}, false, paramsMapKey{}, PrivacyEncodingParams{}},
}
for _, test := range tests {
key, params, err := parsePrivacyEncodingRecord(test.input)
if test.valid && err != nil {
t.Errorf("parsePrivacyEncodingRecord(%v) failed: %v", test.input, err)
} else if !test.valid && err == nil {
t.Errorf("parsePrivacyEncodingRecord accepted invalid input: %v", test.input)
} else if test.valid && (key != test.expectedKey || params != test.expectedParams) {
t.Errorf("parsePrivacyEncodingRecord(%v): expected (%v, %v), got (%v, %v)", test.input, test.expectedKey, test.expectedParams, key, params)
}
}
}
func TestMapPrivacyEncodingParams(t *testing.T) {
m, lists, err := mapPrivacyEncodingParams(testParamRecords)
if err != nil {
t.Errorf("Failed to parse and map records from list.")
}
// Check that the lookup table has the expected size.
if len(m) != len(testParamRecords) {
t.Errorf("Expected %v elements in lookup table, found %v", len(testParamRecords), len(m))
}
// Check that the lists of mapped values have the expected sizes.
if len(lists.epsilons) != 2 {
t.Errorf("Expected 2 mapped epsilon values, found %v", len(lists.epsilons))
}
if len(lists.populations) != 2 {
t.Errorf("Expected 2 mapped population values, found %v", len(lists.populations))
}
if len(lists.sparsities) != 3 {
t.Errorf("Expected 3 mapped sparsity values, found %v", len(lists.sparsities))
}
}
func TestGetIntegerRangeForReport(t *testing.T) {
var minValue int64 = -5
var maxValue int64 = 10
var maxCount uint64 = 20
var smallMaxCount uint64 = 5
// Reports with MinValue and MaxValue set
fleetwideOccurrenceCountsReport := config.ReportDefinition{
ReportName: "FleetwideOccurrenceCounts",
ReportType: config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
MinValue: minValue,
MaxValue: maxValue,
}
uniqueDeviceNumericStatsReport := config.ReportDefinition{
ReportName: "UniqueDeviceNumericStats",
ReportType: config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS,
MinValue: minValue,
MaxValue: maxValue,
}
hourlyValueNumericStatsReport := config.ReportDefinition{
ReportName: "HourlyValueNumericStats",
ReportType: config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS,
MinValue: minValue,
MaxValue: maxValue,
}
// Reports with MaxCount set
fleetwideHistogramsReport := config.ReportDefinition{
ReportName: "FleetwideHistograms",
ReportType: config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
MaxCount: maxCount,
}
stringCountsReport := config.ReportDefinition{
ReportName: "StringCounts",
ReportType: config.ReportDefinition_STRING_COUNTS,
MaxCount: maxCount,
}
// Reports with MinValue, MaxValue, and MaxCount set
fleetwideMeansLargerSumRangeReport := config.ReportDefinition{
ReportName: "FleetwideMeansLargerSumRange",
ReportType: config.ReportDefinition_FLEETWIDE_MEANS,
MinValue: minValue,
MaxValue: maxValue,
MaxCount: smallMaxCount,
}
fleetwideMeansLargerCountRangeReport := config.ReportDefinition{
ReportName: "FleetwideMeansLargerCountRange",
ReportType: config.ReportDefinition_FLEETWIDE_MEANS,
MinValue: minValue,
MaxValue: maxValue,
MaxCount: maxCount,
}
// Reports without an integer range
uniqueDeviceCountsReport := config.ReportDefinition{
ReportName: "UniqueDeviceCounts",
ReportType: config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
}
uniqueDeviceHistogramsReport := config.ReportDefinition{
ReportName: "UniqueDeviceHistograms",
ReportType: config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS,
}
hourlyValueHistogramsReport := config.ReportDefinition{
ReportName: "HourlyValueHistograms",
ReportType: config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS,
}
// Invalid report type
unsetReportTypeReport := config.ReportDefinition{
ReportName: "UnsetReportType",
}
var tests = []struct {
report *config.ReportDefinition
valid bool
expected uint64
}{
// Valid input:
{&fleetwideOccurrenceCountsReport, true, uint64(maxValue - minValue + 1)},
{&uniqueDeviceNumericStatsReport, true, uint64(maxValue - minValue + 1)},
{&hourlyValueNumericStatsReport, true, uint64(maxValue - minValue + 1)},
{&fleetwideHistogramsReport, true, maxCount + 1},
{&stringCountsReport, true, maxCount + 1},
{&fleetwideMeansLargerSumRangeReport, true, uint64(maxValue - minValue + 1)},
{&fleetwideMeansLargerCountRangeReport, true, maxCount + 1},
{&uniqueDeviceCountsReport, true, 1},
{&uniqueDeviceHistogramsReport, true, 1},
{&hourlyValueHistogramsReport, true, 1},
// Invalid input:
{&unsetReportTypeReport, false, 0},
}
for _, test := range tests {
result, err := GetIntegerRangeSizeForReport(test.report)
if test.valid && err != nil {
t.Errorf("getIntegerRangeSizeForReport() failed for report %s: %v", test.report.ReportName, err)
} else if !test.valid && err == nil {
t.Errorf("getIntegerRangeSizeForReport() accepted invalid report: %s", test.report.ReportName)
} else if test.valid && result != test.expected {
t.Errorf("getIntegerRangeSizeForReport() for report %s: expected %d, got %d", test.report.ReportName, test.expected, result)
}
}
}
func TestGetEventCodeBufferMax(t *testing.T) {
var eventCodeBufferMax uint64 = 5
var maxEventCode uint32 = 2
dimensionMaxEventCode := config.MetricDefinition_MetricDimension{
Dimension: "dim0",
MaxEventCode: maxEventCode,
}
dimensionNoMaxEventCode := config.MetricDefinition_MetricDimension{
Dimension: "dim1",
EventCodes: map[uint32]string{0: "event0", 1: "event1", 5: "event5"},
}
metricDimensions := []*config.MetricDefinition_MetricDimension{
&dimensionMaxEventCode,
&dimensionNoMaxEventCode,
}
metricMaxSet := config.MetricDefinition{
MetricName: "MetricWithEventCodeBufferMaxSet",
EventCodeBufferMax: eventCodeBufferMax,
MetricDimensions: metricDimensions,
}
metricMaxUnset := config.MetricDefinition{
MetricName: "MetricWithEventCodeBufferMaxUnset",
MetricDimensions: metricDimensions,
}
var tests = []struct {
metric *config.MetricDefinition
expected uint64
}{
{&metricMaxSet, uint64(eventCodeBufferMax)},
{&metricMaxUnset, uint64((maxEventCode + 1) * 3)},
}
for _, test := range tests {
result := getEventCodeBufferMax(test.metric)
if result != test.expected {
t.Errorf("eventCodeBufferMax() for metric %s: expected %d, got %d",
test.metric.MetricName, test.expected, result)
}
}
}
func TestGetSparsityForReport(t *testing.T) {
var eventCodeBufferMax uint64 = 5
var stringBufferMax uint32 = 3
var numLinearBuckets uint32 = 7
var maxEventCode uint32 = 2
// The hard-coded dimensions of a CountMin sketch for StringCounts reports.
var numCellsPerHash uint64 = 10
var numHashes uint64 = 5
linearBuckets := config.LinearIntegerBuckets{NumBuckets: numLinearBuckets}
buckets := config.IntegerBuckets{
Buckets: &config.IntegerBuckets_Linear{&linearBuckets},
}
dimension := config.MetricDefinition_MetricDimension{
Dimension: "dim0",
MaxEventCode: maxEventCode,
}
// Metrics
occurrenceMetric := config.MetricDefinition{
MetricName: "OccurrenceMetric",
MetricType: config.MetricDefinition_OCCURRENCE,
EventCodeBufferMax: eventCodeBufferMax,
}
occurrenceMetricEventCodeBufferMaxUnset := config.MetricDefinition{
MetricName: "OccurenceMetricEventCodeBufferMaxUnset",
MetricType: config.MetricDefinition_OCCURRENCE,
MetricDimensions: []*config.MetricDefinition_MetricDimension{&dimension},
}
integerMetric := config.MetricDefinition{
MetricName: "IntegerMetric",
MetricType: config.MetricDefinition_INTEGER,
EventCodeBufferMax: eventCodeBufferMax,
}
integerHistogramMetric := config.MetricDefinition{
MetricName: "IntegerHistogramMetric",
MetricType: config.MetricDefinition_INTEGER_HISTOGRAM,
EventCodeBufferMax: eventCodeBufferMax,
IntBuckets: &buckets,
}
stringMetricNoStringBufferMax := config.MetricDefinition{
MetricName: "StringMetricNoStringBufferMax",
MetricType: config.MetricDefinition_STRING,
EventCodeBufferMax: eventCodeBufferMax,
}
stringMetricWithStringBufferMax := config.MetricDefinition{
MetricName: "StringMetricWithStringBufferMax",
MetricType: config.MetricDefinition_STRING,
EventCodeBufferMax: eventCodeBufferMax,
StringBufferMax: stringBufferMax,
}
// Reports
fleetwideOccurrenceCountsReport := config.ReportDefinition{
ReportName: "FleetwideOccurrenceCounts",
ReportType: config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
}
atLeastOnceUniqueDeviceCountsReport := config.ReportDefinition{
ReportName: "AtLeastOnceUniqueDeviceCounts",
ReportType: config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
LocalAggregationProcedure: config.ReportDefinition_AT_LEAST_ONCE,
}
selectFirstUniqueDeviceCountsReport := config.ReportDefinition{
ReportName: "SelectFirstUniqueDeviceCounts",
ReportType: config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
LocalAggregationProcedure: config.ReportDefinition_SELECT_FIRST,
}
selectMostCommonUniqueDeviceCountsReport := config.ReportDefinition{
ReportName: "SelectMostCommonUniqueDeviceCounts",
ReportType: config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
LocalAggregationProcedure: config.ReportDefinition_SELECT_MOST_COMMON,
}
uniqueDeviceNumericStatsReport := config.ReportDefinition{
ReportName: "UniqueDeviceNumericStats",
ReportType: config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS,
}
hourlyValueNumericStatsReport := config.ReportDefinition{
ReportName: "HourlyValueNumericStats",
ReportType: config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS,
}
uniqueDeviceHistogramsReport := config.ReportDefinition{
ReportName: "UniqueDeviceHistograms",
ReportType: config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS,
}
hourlyValueHistogramsReport := config.ReportDefinition{
ReportName: "HourlyValueHistograms",
ReportType: config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS,
}
fleetwideHistogramsForIntegerReport := config.ReportDefinition{
ReportName: "FleetwideHistogramsForInteger",
ReportType: config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
IntBuckets: &buckets,
}
fleetwideMeansReport := config.ReportDefinition{
ReportName: "FleetwideMeans",
ReportType: config.ReportDefinition_FLEETWIDE_MEANS,
}
fleetwideHistogramsForIntHistogramReport := config.ReportDefinition{
ReportName: "FleetwideHistogramsForIntHistogram",
ReportType: config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
}
stringCountsReport := config.ReportDefinition{
ReportName: "StringCounts",
ReportType: config.ReportDefinition_STRING_COUNTS,
}
unsetReportTypeReport := config.ReportDefinition{
ReportName: "UnsetReportType",
}
type args struct {
metric *config.MetricDefinition
report *config.ReportDefinition
}
var tests = []struct {
input args
valid bool
expected uint64
}{
// Valid input:
{args{&occurrenceMetric, &fleetwideOccurrenceCountsReport}, true, eventCodeBufferMax},
{args{&occurrenceMetricEventCodeBufferMaxUnset, &fleetwideOccurrenceCountsReport}, true,
uint64(maxEventCode) + 1},
{args{&occurrenceMetric, &atLeastOnceUniqueDeviceCountsReport}, true, eventCodeBufferMax},
{args{&occurrenceMetric, &selectFirstUniqueDeviceCountsReport}, true, 1},
{args{&occurrenceMetric, &selectMostCommonUniqueDeviceCountsReport}, true, 1},
{args{&occurrenceMetricEventCodeBufferMaxUnset, &selectMostCommonUniqueDeviceCountsReport}, true, 1},
{args{&occurrenceMetric, &uniqueDeviceHistogramsReport}, true, eventCodeBufferMax},
{args{&occurrenceMetric, &hourlyValueHistogramsReport}, true, eventCodeBufferMax},
{args{&occurrenceMetric, &uniqueDeviceNumericStatsReport}, true, eventCodeBufferMax},
{args{&occurrenceMetric, &hourlyValueNumericStatsReport}, true, eventCodeBufferMax},
{args{&integerMetric, &uniqueDeviceHistogramsReport}, true, eventCodeBufferMax},
{args{&integerMetric, &hourlyValueHistogramsReport}, true, eventCodeBufferMax},
{args{&integerMetric, &fleetwideHistogramsForIntegerReport}, true, eventCodeBufferMax * uint64(numLinearBuckets)},
{args{&integerMetric, &fleetwideMeansReport}, true, eventCodeBufferMax * 2},
{args{&integerMetric, &uniqueDeviceNumericStatsReport}, true, eventCodeBufferMax},
{args{&integerMetric, &hourlyValueNumericStatsReport}, true, eventCodeBufferMax},
{args{&integerHistogramMetric, &fleetwideHistogramsForIntHistogramReport}, true,
eventCodeBufferMax * uint64(numLinearBuckets)},
{args{&stringMetricNoStringBufferMax, &stringCountsReport}, true, eventCodeBufferMax * numCellsPerHash * numHashes},
{args{&stringMetricWithStringBufferMax, &stringCountsReport}, true, eventCodeBufferMax * uint64(stringBufferMax) * numHashes},
// Invalid input:
// This report does not have a report type set.
{args{&occurrenceMetric, &unsetReportTypeReport}, false, 0},
}
for _, test := range tests {
result, err := getSparsityForReport(test.input.metric, test.input.report)
if test.valid && err != nil {
t.Errorf("getSparsityForReport() failed for report %s: %v", test.input.report.ReportName, err)
} else if !test.valid && err == nil {
t.Errorf("getSparsityForReport() accepted invalid report: %s", test.input.report.ReportName)
} else if test.valid && result != test.expected {
t.Errorf("getSparsityForReport() for metric %s and report %s: expected %d, got %d",
test.input.metric.MetricName, test.input.report.ReportName, test.expected, result)
}
}
}
func TestGetBestMappedEpsilon(t *testing.T) {
goodLists := paramsKeyLists{epsilons: []float64{1.0, 5.0, 10.0}}
emptyLists := paramsKeyLists{}
type args struct {
epsilon float64
lists paramsKeyLists
}
var tests = []struct {
input args
valid bool
expected float64
}{
// Valid input:
{args{1.0, goodLists}, true, 1.0},
{args{2.0, goodLists}, true, 1.0},
{args{10.0, goodLists}, true, 10.0},
{args{11.0, goodLists}, true, 10.0},
// Invalid input:
// |epsilon| is smaller than all elements of |lists.epsilons|.
{args{0.5, goodLists}, false, 0.0},
// |lists.epsilons| is empty.
{args{1.0, emptyLists}, false, 0.0},
}
for _, test := range tests {
result, err := test.input.lists.getBestMappedEpsilon(test.input.epsilon)
if test.valid && err != nil {
t.Errorf("getBestMappedEpsilon(%v) failed for epsilon list %v with error: %v", test.input.epsilon, test.input.lists.epsilons, err)
} else if !test.valid && err == nil {
t.Errorf("getBestMappedEpsilon accepted invalid input %v for epsilon list %v", test.input.epsilon, test.input.lists.epsilons)
} else if test.valid && result != test.expected {
t.Errorf("getBestMapped(%v) for epsilon list %v: expected %v, got %v", test.input.epsilon, test.input.lists.epsilons, test.expected, result)
}
}
}
func TestGetBestMappedPopulation(t *testing.T) {
goodLists := paramsKeyLists{populations: []uint64{10000, 50000, 100000}}
emptyLists := paramsKeyLists{}
type args struct {
population uint64
lists paramsKeyLists
}
var tests = []struct {
input args
valid bool
expected uint64
}{
// Valid input:
{args{10000, goodLists}, true, 10000},
{args{20000, goodLists}, true, 10000},
{args{100000, goodLists}, true, 100000},
{args{110000, goodLists}, true, 100000},
// Invalid input:
// |population| is smaller than all elements of |lists.populations|.
{args{5000, goodLists}, false, 0},
// |vals| is empty.
{args{10000, emptyLists}, false, 0},
}
for _, test := range tests {
result, err := test.input.lists.getBestMappedPopulation(test.input.population)
if test.valid && err != nil {
t.Errorf("getBestMappedPopulation(%v) failed for population list %v with error: %v", test.input.population, test.input.lists.populations, err)
} else if !test.valid && err == nil {
t.Errorf("getBestMappedPopulation accepted invalid input %v for population list %v", test.input.population, test.input.lists.populations)
} else if test.valid && result != test.expected {
t.Errorf("getBestMappedPopulation(%v) for population list %v: expected %v, got %v", test.input.population, test.input.lists.populations, test.expected, result)
}
}
}
func TestGetBestMappedSparsity(t *testing.T) {
goodLists := paramsKeyLists{sparsities: []uint64{1, 5, 10}}
emptyLists := paramsKeyLists{}
type args struct {
sparsity uint64
lists paramsKeyLists
}
var tests = []struct {
input args
valid bool
expected uint64
}{
// Valid input:
{args{0, goodLists}, true, 1},
{args{1, goodLists}, true, 1},
{args{2, goodLists}, true, 5},
{args{10, goodLists}, true, 10},
// Invalid input:
// |sparsity| is larger than all elements of |lists.sparsities|.
{args{11, goodLists}, false, 0},
// |lists.sparsities| is empty.
{args{1, emptyLists}, false, 0},
}
for _, test := range tests {
result, err := test.input.lists.getBestMappedSparsity(test.input.sparsity)
if test.valid && err != nil {
t.Errorf("getBestMappedSparsity(%v) failed for population list %v with error: %v", test.input.sparsity, test.input.lists.sparsities, err)
} else if !test.valid && err == nil {
t.Errorf("getBestMappedSparsity accepted invalid input %v for sparsity list %v", test.input.sparsity, test.input.lists.sparsities)
} else if test.valid && result != test.expected {
t.Errorf("getBestMappedSparsity(%v) for sparsity list %v: expected %v, got %v", test.input.sparsity, test.input.lists.sparsities, test.expected, result)
}
}
}
func TestGetBestMappedKey(t *testing.T) {
type args struct {
epsilon float64
population uint64
sparsity uint64
mapped *paramsKeyLists
}
var vals = paramsKeyLists{[]float64{1.0, 5.0, 10.0}, []uint64{10000, 20000, 40000, 80000}, []uint64{1, 2, 3, 4, 5, 10}}
var tests = []struct {
input args
valid bool
expected paramsMapKey
}{
// Valid args:
// All args are present in a mapped key.
{args{1.0, 10000, 1, &vals}, true, paramsMapKey{1.0, 10000, 1}},
{args{5.0, 40000, 3, &vals}, true, paramsMapKey{5.0, 40000, 3}},
{args{10.0, 80000, 10, &vals}, true, paramsMapKey{10.0, 80000, 10}},
// Some arg is not present in a mapped key, but the input is valid.
{args{2.0, 48000, 6, &vals}, true, paramsMapKey{1.0, 40000, 10}},
{args{6.0, 60000, 9, &vals}, true, paramsMapKey{5.0, 40000, 10}},
{args{12.0, 80001, 1, &vals}, true, paramsMapKey{10.0, 80000, 1}},
// Invalid args:
// Epsilon is too small.
{args{0.5, 40000, 10, &vals}, false, paramsMapKey{}},
// Population is too small.
{args{6.0, 5000, 10, &vals}, false, paramsMapKey{}},
// Sparsity is too large.
{args{6.0, 40000, 11, &vals}, false, paramsMapKey{}},
}
for _, test := range tests {
result, err := getBestMappedKey(test.input.epsilon, test.input.population, test.input.sparsity, test.input.mapped)
if test.valid && err != nil {
t.Errorf("getBestMappedKey(%v) failed: %v", test.input, err)
} else if !test.valid && err == nil {
t.Errorf("getBestMappedKey accepted invalid input: %v", test.input)
} else if test.valid && result != test.expected {
t.Errorf("getBestMappedKey(%v): expected %v, got %v", test.input, test.expected, result)
}
}
}
func TestGetPrivacyEncodingParams(t *testing.T) {
calc, err := NewPrivacyEncodingParamsCalculatorForTesting(testParamRecords)
if err != nil {
t.Errorf("NewPrivacyEncodingParamsCalculatorForTesting failed with valid input. Error message: %v", err)
}
type args struct {
epsilon float64
population uint64
sparsity uint64
rangeSize uint64
}
var tests = []struct {
input args
valid bool
expected PrivacyEncodingParams
}{
// Valid input:
// The best-match key is {1.0, 10000, 10}.
{args{1.0, 15000, 5, 4}, true, PrivacyEncodingParams{0.019537480548024178, 4}},
// The best-match key is {5.0, 10000, 1}.
{args{10.0, 15000, 1, 12}, true, PrivacyEncodingParams{0.000906200148165226, 12}},
// The rangeSize is smaller than numIndexPoints.
{args{10.0, 15000, 1, 6}, true, PrivacyEncodingParams{0.000906200148165226, 6}},
// Invalid input:
// The target epsilon is smaller than all mapped epsilons.
{args{0.5, 10000, 1, 1}, false, PrivacyEncodingParams{}},
// The target population is smaller than all mapped populations.
{args{1.0, 5000, 1, 1}, false, PrivacyEncodingParams{}},
// The target sparsity is larger than all mapped sparsities.
{args{1.0, 10000, 100, 1}, false, PrivacyEncodingParams{}},
}
for _, test := range tests {
result, err := calc.GetPrivacyEncodingParams(test.input.epsilon, test.input.population, test.input.sparsity, test.input.rangeSize)
if test.valid && err != nil {
t.Errorf("GetPrivacyEncodingParams(%v, %v, %v) failed: %v", test.input.epsilon, test.input.population, test.input.sparsity, err)
} else if !test.valid && err == nil {
t.Errorf("GetPrivacyEncodingParams() accepted invalid input: (%v, %v, %v)", test.input.epsilon, test.input.population, test.input.sparsity)
} else if test.valid && result != test.expected {
t.Errorf("GetPrivacyEncodingParams(%v, %v, %v): expected %v, got %v", test.input.epsilon, test.input.population, test.input.sparsity, test.expected, result)
}
}
}
func TestGetPrivacyEncodingParamsForReport(t *testing.T) {
calc, err := NewPrivacyEncodingParamsCalculatorForTesting(testParamRecords)
if err != nil {
t.Errorf("NewPrivacyEncodingParamsCalculatorForTesting failed with valid input. Error message: %v", err)
}
var eventCodeBufferMax uint64 = 5
var minValue int64 = 0
var smallMaxValue int64 = 1
var largeMaxValue int64 = 100
var smallMaxCount uint64 = 1
var largeMaxCount uint64 = 100
var numLinearBuckets uint32 = 2
linearBuckets := config.LinearIntegerBuckets{NumBuckets: numLinearBuckets}
buckets := config.IntegerBuckets{
Buckets: &config.IntegerBuckets_Linear{&linearBuckets},
}
// Metrics
occurrenceMetric := config.MetricDefinition{
MetricType: config.MetricDefinition_OCCURRENCE,
EventCodeBufferMax: eventCodeBufferMax,
}
integerMetric := config.MetricDefinition{
MetricType: config.MetricDefinition_INTEGER,
EventCodeBufferMax: eventCodeBufferMax,
}
// Reports
highPrivacyAtLeastOnceReport := config.ReportDefinition{
ReportName: "HighPrivacyAtLeastOnceReport",
ReportType: config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
PrivacyLevel: config.ReportDefinition_HIGH_PRIVACY,
LocalAggregationProcedure: config.ReportDefinition_AT_LEAST_ONCE,
}
lowPrivacySelectFirstReport := config.ReportDefinition{
ReportName: "LowPrivacySelectFirstReport",
ReportType: config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
PrivacyLevel: config.ReportDefinition_LOW_PRIVACY,
LocalAggregationProcedure: config.ReportDefinition_SELECT_FIRST,
}
smallRangeSizeFleetwideOccurrenceCountsReport := config.ReportDefinition{
ReportName: "smallRangeSizeFleetwideOccurrenceCountsReport",
ReportType: config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
PrivacyLevel: config.ReportDefinition_LOW_PRIVACY,
MinValue: minValue,
MaxValue: smallMaxValue,
}
noMaxValueFleetwideOccurrenceCountsReport := config.ReportDefinition{
ReportName: "noMaxValueFleetwideOccurrenceCountsReport",
ReportType: config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
PrivacyLevel: config.ReportDefinition_LOW_PRIVACY,
MinValue: 0,
MaxValue: 0,
}
largeRangeSizeFleetwideOccurrenceCountsReport := config.ReportDefinition{
ReportName: "largeRangeSizeFleetwideOccurrenceCountsReport",
ReportType: config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
PrivacyLevel: config.ReportDefinition_LOW_PRIVACY,
MinValue: minValue,
MaxValue: largeMaxValue,
}
smallRangeSizeFleetwideHistogramsReport := config.ReportDefinition{
ReportName: "smallRangeSizeFleetwideHistogramsReport",
ReportType: config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
IntBuckets: &buckets,
PrivacyLevel: config.ReportDefinition_LOW_PRIVACY,
MaxCount: smallMaxCount,
}
largeRangeSizeFleetwideHistogramsReport := config.ReportDefinition{
ReportName: "largeRangeSizeFleetwideHistogramsReport",
ReportType: config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
IntBuckets: &buckets,
PrivacyLevel: config.ReportDefinition_LOW_PRIVACY,
MaxCount: largeMaxCount,
}
unsetReportTypeReport := config.ReportDefinition{
ReportName: "UnsetReportTypeReport",
}
type args struct {
metric *config.MetricDefinition
report *config.ReportDefinition
}
var tests = []struct {
input args
valid bool
expected PrivacyEncodingParams
}{
// Valid input:
// The target epsilon is 1.0 and the sparsity is |eventCodeBufferMax|. The best-match key is
// {1.0, 10000, 10}. The report has type UniqueDeviceCounts, so the NumIndexPoints field of
// PrivacyEncodingParams is set to 1.
{args{&occurrenceMetric, &highPrivacyAtLeastOnceReport}, true,
PrivacyEncodingParams{0.019537480548024178, 1}},
// The target epsilon is 10.0 and the sparsity is 1. The best-match key is {5.0, 10000, 1}.
// The report has type UniqueDeviceCounts, so the NumIndexPoints field of PrivacyEncodingParams is
// set to 1.
{args{&occurrenceMetric, &lowPrivacySelectFirstReport}, true,
PrivacyEncodingParams{0.000906200148165226, 1}},
// The target epsilon is 10.0 and the sparsity is |eventCodeBufferMax|.
// The best-match key is {5.0, 10000, 10}.
// The integer range size of the report (= |smallMaxValue|) is smaller than the number of index
// points computed by |calc|, so the returned NumIndexPoints should be equal to |smallMaxValue| + 1.
{args{&occurrenceMetric, &smallRangeSizeFleetwideOccurrenceCountsReport}, true,
PrivacyEncodingParams{0.0014028092846274376, uint32(smallMaxValue + 1)}},
// The target epsilon is 10.0 and the sparsity is |eventCodeBufferMax|.
// The best-match key is {5.0, 10000, 10}.
// The integer range size of the report (= 1) is smaller than the number of index
// points computed by |calc|, so the returned NumIndexPoints should be equal to 1.
{args{&occurrenceMetric, &noMaxValueFleetwideOccurrenceCountsReport}, true,
PrivacyEncodingParams{0.0014028092846274376, 1}},
// The target epsilon is 10.0 and the sparsity is |eventCodeBufferMax|.
// The best-match key is {5.0, 10000, 10}.
// The integer range size of the report (= largeMaxValue) is larger than the number of index points
// computed by |calc|, so the returned NumIndexPoints is given by the value at the best-match key.
{args{&occurrenceMetric, &largeRangeSizeFleetwideOccurrenceCountsReport}, true,
PrivacyEncodingParams{0.0014028092846274376, 10}},
// The target epsilon is 10.0 and the sparsity is |eventCodeBufferMax| * |numLinearBuckets|.
// The best-match key is {5.0, 10000, 10}.
// The integer range size of the report (= |smallMaxCount|) is smaller than the number of index
// points computed by |calc|, so the returned NumIndexPoints should be equal to |smallMaxCount + 1|.
{args{&integerMetric, &smallRangeSizeFleetwideHistogramsReport}, true,
PrivacyEncodingParams{0.0014028092846274376, uint32(smallMaxCount + 1)}},
// The target epsilon is 10.0 and the sparsity is |eventCodeBufferMax| * |numLinearBuckets|.
// The best-match key is {5.0, 10000, 10}.
// The integer range size of the report (= |largeMaxCount|) is larger than the number of index
// points computed by |calc|, so the returned NumIndexPoints is given by the value at the best-match
// key.
{args{&integerMetric, &largeRangeSizeFleetwideHistogramsReport}, true,
PrivacyEncodingParams{0.0014028092846274376, 10}},
// Invalid input:
// This report does not have a report type set.
{args{&occurrenceMetric, &unsetReportTypeReport}, false, PrivacyEncodingParams{}},
}
for _, test := range tests {
result, err := calc.GetPrivacyEncodingParamsForReport(test.input.metric, test.input.report)
if test.valid && err != nil {
t.Errorf("GetPrivacyEncodingParamsForReport() failed for report %v: %v", test.input.report.ReportName, err)
} else if !test.valid && err == nil {
t.Errorf("GetPrivacyEncodingParamsForReport() accepted invalid report: %v", test.input.report.ReportName)
} else if test.valid && result != test.expected {
t.Errorf("GetPrivacyEncodingParamsForReport() for report %v: expected %v, got %v", test.input.report.ReportName, test.expected, result)
}
}
}