| // 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) |
| } |
| } |
| } |