blob: 420e3a17d8a39fdc54279f9f544a2c5a766fe8c1 [file] [log] [blame]
// Copyright 2018 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 config_validator
import (
"config"
"testing"
)
// makeValidReport returns a valid instance of config.ReportDefinition which
// can be modified to fail various validation checks for testing purposes.
func makeValidReport() config.ReportDefinition {
return makeValidReportWithNameAndType("the_report_name", config.ReportDefinition_EVENT_COMPONENT_OCCURRENCE_COUNT)
}
func makeValidReportWithType(t config.ReportDefinition_ReportType) config.ReportDefinition {
return makeValidReportWithNameAndType("the_report_name", t)
}
func makeValidReportWithName(name string) config.ReportDefinition {
return makeValidReportWithNameAndType(name, config.ReportDefinition_EVENT_COMPONENT_OCCURRENCE_COUNT)
}
func makeValidReportWithNameAndType(name string, t config.ReportDefinition_ReportType) config.ReportDefinition {
return config.ReportDefinition{
Id: 10,
ReportName: name,
ReportType: t,
}
}
// Given a metric type |mt|, returns a valid report type for that metric type.
// Prefers to return a non-histogram report type if the given metric type supports
// one, to aid in checking where int_buckets get set in tests.
func getMatchingReportType(mt config.MetricDefinition_MetricType) config.ReportDefinition_ReportType {
switch mt {
case config.MetricDefinition_EVENT_OCCURRED:
return config.ReportDefinition_SIMPLE_OCCURRENCE_COUNT
case config.MetricDefinition_EVENT_COUNT:
return config.ReportDefinition_EVENT_COMPONENT_OCCURRENCE_COUNT
case config.MetricDefinition_ELAPSED_TIME:
return config.ReportDefinition_NUMERIC_AGGREGATION
case config.MetricDefinition_FRAME_RATE:
return config.ReportDefinition_NUMERIC_AGGREGATION
case config.MetricDefinition_MEMORY_USAGE:
return config.ReportDefinition_NUMERIC_AGGREGATION
case config.MetricDefinition_INT_HISTOGRAM:
return config.ReportDefinition_INT_RANGE_HISTOGRAM
case config.MetricDefinition_CUSTOM:
return config.ReportDefinition_CUSTOM_RAW_DUMP
case config.MetricDefinition_OCCURRENCE:
return config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS
case config.MetricDefinition_INTEGER:
return config.ReportDefinition_FLEETWIDE_MEANS
case config.MetricDefinition_INTEGER_HISTOGRAM:
return config.ReportDefinition_FLEETWIDE_HISTOGRAMS
case config.MetricDefinition_STRING:
return config.ReportDefinition_STRING_COUNTS
}
return config.ReportDefinition_REPORT_TYPE_UNSET
}
// Test that makeValidReport returns a valid report.
func TestValidateMakeValidReport(t *testing.T) {
m := makeValidMetric(config.MetricDefinition_EVENT_COUNT)
r := makeValidReport()
if err := validateReportDefinition(m, r); err != nil {
t.Errorf("Rejected valid report: %v", err)
}
}
func TestDifferentReportId(t *testing.T) {
m := makeValidMetric(config.MetricDefinition_EVENT_COUNT)
r := makeValidReport()
r.Id += 1
if err := validateReportDefinition(m, r); err != nil {
t.Error("Reject report with different report id.")
}
}
func TestValidateInvalidName(t *testing.T) {
m := makeValidMetric(config.MetricDefinition_EVENT_COUNT)
r := makeValidReportWithName("_invalid_name")
if err := validateReportDefinition(m, r); err == nil {
t.Error("Accepted report with invalid name.")
}
}
func TestValidateZeroReportId(t *testing.T) {
m := makeValidMetric(config.MetricDefinition_EVENT_COUNT)
r := makeValidReportWithName("NRaMinLNcqiYmgEypLLVGnXymNpxJzqabtbbjLycCMEohvVzZtAYpah")
r.Id = 0
if err := validateReportDefinition(m, r); err == nil {
t.Error("Accepted report with 0 id.")
}
}
func TestValidateUnsetReportType(t *testing.T) {
if err := validateReportType(config.MetricDefinition_EVENT_OCCURRED, config.ReportDefinition_REPORT_TYPE_UNSET); err == nil {
t.Error("Accepted report with no report type set.")
}
}
// Test that validateLocalPrivacyNoiseLevel accepts a ReportDefinition if and only if it has a local_privacy_noise_level field.
func TestValidateLocalPrivacyNoiseLevel(t *testing.T) {
r := makeValidReport()
if err := validateLocalPrivacyNoiseLevel(r); err == nil {
t.Error("Accepted report definition with local_privacy_noise_level unset.")
}
r.LocalPrivacyNoiseLevel = config.ReportDefinition_SMALL
if err := validateLocalPrivacyNoiseLevel(r); err != nil {
t.Errorf("Rejected report definition with local_privacy_noise_level set: %v", err)
}
}
// Test that validateWindowSize rejects a ReportDefinition with no window_size field, and a ReportDefinition with a
// window_size field where one of the window sizes is UNSET.
func TestValidateWindowSize(t *testing.T) {
r := makeValidReport()
if err := validateWindowSize(r); err == nil {
t.Error("Accepted report definition without window_size field.")
}
r.WindowSize = []config.WindowSize{config.WindowSize_WINDOW_1_DAY, config.WindowSize_UNSET}
if err := validateWindowSize(r); err == nil {
t.Error("Accepted report definition with an UNSET window size.")
}
r.WindowSize = []config.WindowSize{config.WindowSize_WINDOW_1_DAY, config.WindowSize_WINDOW_7_DAYS}
if err := validateWindowSize(r); err != nil {
t.Errorf("Rejected report definition with valid window_size field: %v", err)
}
}
func TestValidatePoissonFields(t *testing.T) {
r := makeValidReport()
r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY
r.UsePoissonMechanismForPrivacy = true
if err := validatePoissonFields(r); err == nil {
t.Error("Accepted report definition with `use_poisson_mechanism_for_privacy` set but with NO_ADDED_PRIVACY privacy level")
}
r.PrivacyLevel = config.ReportDefinition_HIGH_PRIVACY
if err := validatePoissonFields(r); err == nil {
t.Error("Accepted report definition with a nontrivial privacy level and `use_poisson_mechanism_for_privacy` set, but without `poisson_mean`")
}
r.UsePoissonMechanismForPrivacy = false
r.PoissonMean = 1.0
if err := validatePoissonFields(r); err == nil {
t.Error("Accepted report definition with `poisson_mean` set while `use_poisson_mechanism_for_privacy` was not selected")
}
r.UsePoissonMechanismForPrivacy = true
r.PoissonMean = -1.0
if err := validatePoissonFields(r); err == nil {
t.Error("Accepted report definition with negative `poisson_mean`")
}
r.PoissonMean = 1.0
if err := validatePoissonFields(r); err != nil {
t.Error("Rejected report definition with valid `use_poisson_mechanism_for_privacy` and `poisson_mean` settings")
}
}
func TestValidateMinValueMaxValueNoAddedPrivacy(t *testing.T) {
var reportTypes = []config.ReportDefinition_ReportType{
config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS,
config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS,
config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS,
config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS,
config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
config.ReportDefinition_FLEETWIDE_MEANS,
config.ReportDefinition_STRING_COUNTS,
config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS,
}
type args struct {
minValue int64
maxValue int64
}
var tests = []struct {
input args
valid bool
}{
// Valid reports: no added privacy, no bounds set
{args{0, 0}, true},
// Invalid reports: no added privacy, at least one of min_value and max_value set
{args{0, 1}, false},
{args{1, 0}, false},
{args{1, 1}, false},
}
for _, reportType := range reportTypes {
for _, test := range tests {
r := makeValidReportWithType(reportType)
r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY
r.MinValue = test.input.minValue
r.MaxValue = test.input.maxValue
err := validateMinValueMaxValue(r)
if test.valid && err != nil {
t.Errorf("rejected valid report with type %v and (min_value, max_value) = %v error: %v",
reportType, test.input, err)
} else if !test.valid && err == nil {
t.Errorf("accepted invalid report with type %v and (min_value, max_value) = %v",
reportType, test.input)
}
}
}
}
func TestValidateMinValueMaxValueWithAddedPrivacy(t *testing.T) {
var required = []config.ReportDefinition_ReportType{
config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS,
config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS,
config.ReportDefinition_FLEETWIDE_MEANS,
}
var notAllowed = []config.ReportDefinition_ReportType{
config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS,
config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS,
config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
config.ReportDefinition_STRING_COUNTS,
config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS,
}
type args struct {
reportTypes []config.ReportDefinition_ReportType
minValue int64
maxValue int64
}
var tests = []struct {
input args
valid bool
}{
// Valid reports: min value and max value should not be set
{args{notAllowed, 0, 0}, true},
// Valid reports: at least one of min value and max value should be set
{args{required, 0, 1}, true},
{args{required, -1, 1}, true},
{args{required, 1, 1}, true},
// Invalid reports: min value and max value should not be set
{args{notAllowed, 0, 1}, false},
{args{notAllowed, -1, 0}, false},
// Invalid reports: at least one of min value and max value should be set
{args{required, 0, 0}, false},
// Invalid reports: min value is larger than max value
{args{required, 2, 1}, false},
}
for _, test := range tests {
for _, reportType := range test.input.reportTypes {
r := makeValidReportWithType(reportType)
r.PrivacyLevel = config.ReportDefinition_LOW_PRIVACY
r.MinValue = test.input.minValue
r.MaxValue = test.input.maxValue
err := validateMinValueMaxValue(r)
if test.valid && err != nil {
t.Errorf("rejected valid report of type %v with error: %v", reportType, err)
} else if !test.valid && err == nil {
t.Errorf("accepted invalid report of type %v and (min_value, max_value) = (%d, %d)",
reportType, test.input.minValue, test.input.maxValue)
}
}
}
}
func TestValidateMaxCountNoAddedPrivacy(t *testing.T) {
var reportTypes = []config.ReportDefinition_ReportType{
config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS,
config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS,
config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS,
config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS,
config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
config.ReportDefinition_FLEETWIDE_MEANS,
config.ReportDefinition_STRING_COUNTS,
config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS,
}
var tests = []struct {
maxCount uint64
valid bool
}{
// Valid reports: no added privacy, no bounds set
{0, true},
// Invalid reports: no added privacy, max_count set
{1, false},
}
for _, reportType := range reportTypes {
for _, test := range tests {
r := makeValidReportWithType(reportType)
r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY
r.MaxCount = test.maxCount
err := validateMaxCount(r)
if test.valid && err != nil {
t.Errorf("rejected valid report with type %v and max_count = %d error: %v",
reportType, test.maxCount, err)
} else if !test.valid && err == nil {
t.Errorf("accepted invalid report with type %v and max_count = %d",
reportType, test.maxCount)
}
}
}
}
func TestValidateMaxCountWithAddedPrivacy(t *testing.T) {
var required = []config.ReportDefinition_ReportType{
config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
config.ReportDefinition_FLEETWIDE_MEANS,
config.ReportDefinition_STRING_COUNTS,
}
var notAllowed = []config.ReportDefinition_ReportType{
config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS,
config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS,
config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS,
config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS,
config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS,
}
type args struct {
reportTypes []config.ReportDefinition_ReportType
maxCount uint64
}
var tests = []struct {
input args
valid bool
}{
// Valid reports: max count should be set
{args{required, 1}, true},
// Valid reports: max count should not be set
{args{notAllowed, 0}, true},
// Valid reports: max count should be set
{args{required, 1}, true},
// Invalid reports: max count should not be set
{args{notAllowed, 1}, false},
// Invalid reports: max count should be set
{args{required, 0}, false},
}
for _, test := range tests {
for _, reportType := range test.input.reportTypes {
r := makeValidReportWithType(reportType)
r.PrivacyLevel = config.ReportDefinition_LOW_PRIVACY
r.MaxCount = test.input.maxCount
err := validateMaxCount(r)
if test.valid && err != nil {
t.Errorf("rejected valid report of type %v with error: %v", reportType, err)
} else if !test.valid && err == nil {
t.Errorf("accepted invalid report of type %v and max_count = %d",
reportType, test.input.maxCount)
}
}
}
}
// Test that local_privacy_noise_level must be set if the report type is SIMPLE_OCCURRENCE_COUNT.
func TestValidateReportDefinitionForSimpleOccurrenceCount(t *testing.T) {
m := makeValidMetric(config.MetricDefinition_EVENT_COUNT)
r := makeValidReport()
r.ReportType = config.ReportDefinition_SIMPLE_OCCURRENCE_COUNT
if err := validateReportDefinition(m, r); err == nil {
t.Error("Accepted report definition of type SIMPLE_OCCURRENCE_COUNT with local_privacy_noise_level unset.")
}
}
// Test that local_privacy_noise_level and window_size must be set if the report type is UNIQUE_N_DAY_ACTIVES.
func TestValidateReportDefinitionForUniqueActives(t *testing.T) {
m := makeValidMetric(config.MetricDefinition_EVENT_COUNT)
r := makeValidReport()
r.ReportType = config.ReportDefinition_UNIQUE_N_DAY_ACTIVES
// Add a valid window_size, but not a local_privacy_noise_level.
r.WindowSize = []config.WindowSize{config.WindowSize_WINDOW_1_DAY}
if err := validateReportDefinition(m, r); err == nil {
t.Error("Accepted report definition of type UNIQUE_N_DAY_ACTIVES with local_privacy_noise_level unset.")
}
// Remove window_size and add a local_privacy_noise_level.
r.WindowSize = []config.WindowSize{}
r.LocalPrivacyNoiseLevel = config.ReportDefinition_SMALL
if err := validateReportDefinition(m, r); err == nil {
t.Error("Accepted report definition of type UNIQUE_N_DAY_ACTIVES without window_size field.")
}
// Add a valid window size to window_size, but also add an UNSET window size.
r.WindowSize = []config.WindowSize{config.WindowSize_WINDOW_1_DAY, config.WindowSize_UNSET}
if err := validateReportDefinition(m, r); err == nil {
t.Error("Accepted report definition of type UNIQUE_N_DAY_ACTIVES with window_size field containing an UNSET window size.")
}
}
// Test that window_size must be set if the report type is PER_DEVICE_NUMERIC_STATS.
func TestValidateReportDefinitionForPerDeviceNumericStats(t *testing.T) {
m := makeValidMetric(config.MetricDefinition_EVENT_COUNT)
r := makeValidReport()
r.ReportType = config.ReportDefinition_PER_DEVICE_NUMERIC_STATS
if err := validateReportDefinition(m, r); err == nil {
t.Error("Accepted report definition of type PER_DEVICE_NUMERIC_STATS without window_size field.")
}
// Add a valid window size to window_size, but also add an UNSET window size.
r.WindowSize = []config.WindowSize{config.WindowSize_WINDOW_1_DAY, config.WindowSize_UNSET}
if err := validateReportDefinition(m, r); err == nil {
t.Error("Accepted report definition of type PER_DEVICE_NUMERIC_STATS with window_size field containing an UNSET window size.")
}
}
// Test that window_size must be set if the report type is PER_DEVICE_HISTOGRAM.
func TestValidateReportDefinitionForPerDeviceHistogram(t *testing.T) {
m := makeValidMetric(config.MetricDefinition_EVENT_COUNT)
r := makeValidReport()
r.ReportType = config.ReportDefinition_PER_DEVICE_HISTOGRAM
if err := validateReportDefinition(m, r); err == nil {
t.Error("No int_buckets specified for report of type PER_DEVICE_HISTOGRAM.")
}
r.IntBuckets = &config.IntegerBuckets{}
if err := validateReportDefinition(m, r); err == nil {
t.Error("Accepted report definition of type PER_DEVICE_HISTOGRAM without window_size field.")
}
// Add a valid window size to window_size, but also add an UNSET window size.
r.WindowSize = []config.WindowSize{config.WindowSize_WINDOW_1_DAY, config.WindowSize_UNSET}
if err := validateReportDefinition(m, r); err == nil {
t.Error("Accepted report definition of type PER_DEVICE_HISTOGRAM with window_size field containing an UNSET window size.")
}
}
// Test percentiles range for NUMERIC_AGGREGATION report types.
func TestValidateReportDefinitionForNumericAggregation(t *testing.T) {
m := makeValidMetric(config.MetricDefinition_EVENT_COUNT)
r := makeValidReport()
r.ReportType = config.ReportDefinition_NUMERIC_AGGREGATION
// Test that percentiles are optional.
if err := validateReportDefinition(m, r); err != nil {
t.Error("Rejected report definition of type NUMERIC_AGGREGATION with no percentiles set.")
}
// Test that percentiles between 0 and 100 are accepted.
r.Percentiles = []uint32{0, 25, 50, 75, 90, 99, 100}
if err := validateReportDefinition(m, r); err != nil {
t.Error("Rejected report definition of type NUMERIC_AGGREGATION with valid percentiles.")
}
// Test that out of bounds percentiles are rejected.
r.Percentiles = []uint32{0, 25, 50, 75, 100, 101}
if err := validateReportDefinition(m, r); err == nil {
t.Error("Accepted report definition of type NUMERIC_AGGREGATION with invalid percentiles.")
}
}
// Test that int_buckets is not set if the report type is STRING_COUNTS.
func TestValidateReportDefinitionForStringCounts(t *testing.T) {
m := makeValidMetric(config.MetricDefinition_EVENT_COUNT)
r := makeValidReport()
r.ReportType = config.ReportDefinition_STRING_COUNTS
r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY
r.StringBufferMax = 10
if err := validateReportDefinition(m, r); err != nil {
t.Errorf("Failed validation for valid report of type STRING_COUNTS: %v", err)
}
r.IntBuckets = &config.IntegerBuckets{}
if err := validateReportDefinition(m, r); err == nil {
t.Error("Accepted report definition of type STRING_COUNTS with int_buckets specified.")
}
}
// Test that int_buckets is not set if the report type is UNIQUE_DEVICE_STRING_COUNTS.
func TestValidateReportDefitionForUniqueDeviceStringCounts(t *testing.T) {
m := makeValidMetric(config.MetricDefinition_EVENT_COUNT)
r := makeValidReport()
r.ReportType = config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS
r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY
r.LocalAggregationPeriod = config.WindowSize_WINDOW_1_DAY
r.StringBufferMax = 10
if err := validateReportDefinition(m, r); err != nil {
t.Errorf("Failed validation for valid report of type UNIQUE_DEVICE_STRING_COUNTS: %v", err)
}
r.IntBuckets = &config.IntegerBuckets{}
if err := validateReportDefinition(m, r); err == nil {
t.Errorf("Accepted report definition of type UNIQUE_DEVICE_STRING_COUNTS with int_buckets specified.")
}
}
// Test that Histogram report types get properly validated for int_buckets in main validation functions.
func TestHistogramReportsHaveIntBuckets(t *testing.T) {
// ================================================================================
// Test PER_DEVICE_HISTOGRAM reports with int_buckets set in the report definition.
// ================================================================================
m := makeValidCobalt10BaseMetric(config.MetricDefinition_EVENT_COUNT)
r := makeValidReportWithType(config.ReportDefinition_PER_DEVICE_HISTOGRAM)
r.WindowSize = []config.WindowSize{config.WindowSize_WINDOW_1_DAY, config.WindowSize_WINDOW_7_DAYS}
// Test that no IntBuckets defined returns an error.
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted PER_DEVICE_HISTOGRAM report with no int_bucket defined: %v", err)
}
// Test that an IntBuckets without a linear or exponential bucket defined returns an error.
r.IntBuckets = &config.IntegerBuckets{}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted PER_DEVICE_HISTOGRAM report with an uninitialized int_buckets set in the report: %v", err)
}
// Test that an empty Linear bucket config returns an error.
r.IntBuckets.Buckets = &config.IntegerBuckets_Linear{}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted PER_DEVICE_HISTOGRAM report with an empty linear bucket config in the report: %v", err)
}
// Test that a Linear bucket config with invalid num_buckets returns an error.
linear := &config.LinearIntegerBuckets{Floor: 0, NumBuckets: 0, StepSize: 10}
r.IntBuckets.Buckets = &config.IntegerBuckets_Linear{Linear: linear}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted PER_DEVICE_HISTOGRAM report with an invalid linear num_buckets in the report: %v", err)
}
// Test that a Linear bucket config with invalid step_size returns an error.
linear = &config.LinearIntegerBuckets{Floor: 0, NumBuckets: 10, StepSize: 0}
r.IntBuckets.Buckets = &config.IntegerBuckets_Linear{Linear: linear}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted PER_DEVICE_HISTOGRAM report with an invalid linear step_size in the report: %v", err)
}
// Test that a valid Linear bucket config passes.
linear = &config.LinearIntegerBuckets{Floor: 0, NumBuckets: 20, StepSize: 10}
r.IntBuckets.Buckets = &config.IntegerBuckets_Linear{Linear: linear}
if err := validateReportDefinitionForMetric(m, r); err != nil {
t.Errorf("Rejected PER_DEVICE_HISTOGRAM report with a valid linear int_config in the report: %v", err)
}
// Test that an empty Exponential bucket config returns an error.
r.IntBuckets.Buckets = &config.IntegerBuckets_Exponential{}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted PER_DEVICE_HISTOGRAM report with an empty exponential bucket config in the report: %v", err)
}
// Test that an Exponential bucket config with invalid num_buckets returns an error.
exponential := &config.ExponentialIntegerBuckets{Floor: 0, NumBuckets: 0, InitialStep: 10, StepMultiplier: 2}
r.IntBuckets.Buckets = &config.IntegerBuckets_Exponential{Exponential: exponential}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted PER_DEVICE_HISTOGRAM report with an invalid exponential num_buckets in the report: %v", err)
}
// Test that an Exponential bucket config with invalid initial_step returns an error.
exponential = &config.ExponentialIntegerBuckets{Floor: 0, NumBuckets: 10, InitialStep: 0, StepMultiplier: 2}
r.IntBuckets.Buckets = &config.IntegerBuckets_Exponential{Exponential: exponential}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted PER_DEVICE_HISTOGRAM report with an invalid exponential initial_step in the report: %v", err)
}
// Test that an Exponential bucket config with invalid step_multiplier returns an error.
exponential = &config.ExponentialIntegerBuckets{Floor: 0, NumBuckets: 20, InitialStep: 2, StepMultiplier: 0}
r.IntBuckets.Buckets = &config.IntegerBuckets_Exponential{Exponential: exponential}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted PER_DEVICE_HISTOGRAM report with an invalid exponential step_multiplier in the report: %v", err)
}
// Test that a valid Exponential bucket config passes.
exponential = &config.ExponentialIntegerBuckets{Floor: 0, NumBuckets: 30, InitialStep: 2, StepMultiplier: 2}
r.IntBuckets.Buckets = &config.IntegerBuckets_Exponential{Exponential: exponential}
if err := validateReportDefinitionForMetric(m, r); err != nil {
t.Errorf("Accepted PER_DEVICE_HISTOGRAM report with an invalid exponential step_multiplier in the report: %v", err)
}
// ================================================================================
// Test INT_RANGE_HISTOGRAM reports with int_buckets set in the report definition.
// ================================================================================
r = makeValidReportWithType(config.ReportDefinition_INT_RANGE_HISTOGRAM)
// Test that no IntBuckets defined returns an error.
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with no int_bucket defined: %v", err)
}
// Test that an IntBuckets without a linear or exponential bucket defined returns an error.
r.IntBuckets = &config.IntegerBuckets{}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an uninitialized int_buckets set in the report: %v", err)
}
// Test that an empty Linear bucket config returns an error.
r.IntBuckets.Buckets = &config.IntegerBuckets_Linear{}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an empty linear bucket config in the report: %v", err)
}
// Test that a Linear bucket config with invalid num_buckets returns an error.
linear = &config.LinearIntegerBuckets{Floor: 0, NumBuckets: 0, StepSize: 10}
r.IntBuckets.Buckets = &config.IntegerBuckets_Linear{Linear: linear}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an invalid linear num_buckets in the report: %v", err)
}
// Test that a Linear bucket config with invalid step_size returns an error.
linear = &config.LinearIntegerBuckets{Floor: 0, NumBuckets: 10, StepSize: 0}
r.IntBuckets.Buckets = &config.IntegerBuckets_Linear{Linear: linear}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an invalid linear step_size in the report: %v", err)
}
// Test that a valid Linear bucket config passes.
linear = &config.LinearIntegerBuckets{Floor: 0, NumBuckets: 20, StepSize: 10}
r.IntBuckets.Buckets = &config.IntegerBuckets_Linear{Linear: linear}
if err := validateReportDefinitionForMetric(m, r); err != nil {
t.Errorf("Rejected INT_RANGE_HISTOGRAM report with a valid linear int_config in the report: %v", err)
}
// Test that an empty Exponential bucket config returns an error.
r.IntBuckets.Buckets = &config.IntegerBuckets_Exponential{}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an empty exponential bucket config in the report: %v", err)
}
// Test that an Exponential bucket config with invalid num_buckets returns an error.
exponential = &config.ExponentialIntegerBuckets{Floor: 0, NumBuckets: 0, InitialStep: 10, StepMultiplier: 2}
r.IntBuckets.Buckets = &config.IntegerBuckets_Exponential{Exponential: exponential}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an invalid exponential num_buckets in the report: %v", err)
}
// Test that an Exponential bucket config with invalid initial_step returns an error.
exponential = &config.ExponentialIntegerBuckets{Floor: 0, NumBuckets: 10, InitialStep: 0, StepMultiplier: 2}
r.IntBuckets.Buckets = &config.IntegerBuckets_Exponential{Exponential: exponential}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an invalid exponential initial_step in the report: %v", err)
}
// Test that an Exponential bucket config with invalid step_multiplier returns an error.
exponential = &config.ExponentialIntegerBuckets{Floor: 0, NumBuckets: 20, InitialStep: 2, StepMultiplier: 0}
r.IntBuckets.Buckets = &config.IntegerBuckets_Exponential{Exponential: exponential}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an invalid exponential step_multiplier in the report: %v", err)
}
// Test that a valid Exponential bucket config passes.
exponential = &config.ExponentialIntegerBuckets{Floor: 0, NumBuckets: 30, InitialStep: 2, StepMultiplier: 2}
r.IntBuckets.Buckets = &config.IntegerBuckets_Exponential{Exponential: exponential}
if err := validateReportDefinitionForMetric(m, r); err != nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an invalid exponential step_multiplier in the report: %v", err)
}
// ================================================================================
// Test INT_RANGE_HISTOGRAM reports with int_buckets set in the metric definition.
// ================================================================================
m = makeValidCobalt10BaseMetric(config.MetricDefinition_INT_HISTOGRAM)
r = makeValidReportWithType(config.ReportDefinition_INT_RANGE_HISTOGRAM)
// Test that an IntBuckets without a linear or exponential bucket defined returns an error.
m.IntBuckets = &config.IntegerBuckets{}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an uninitialized int_buckets set in the metric: %v", err)
}
// Test that an empty Linear bucket config returns an error.
m.IntBuckets.Buckets = &config.IntegerBuckets_Linear{}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an empty linear bucket config in the metric: %v", err)
}
// Test that a Linear bucket config with invalid num_buckets returns an error.
linear = &config.LinearIntegerBuckets{Floor: 0, NumBuckets: 0, StepSize: 10}
m.IntBuckets.Buckets = &config.IntegerBuckets_Linear{Linear: linear}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an invalid linear num_buckets in the metric: %v", err)
}
// Test that a Linear bucket config with invalid step_size returns an error.
linear = &config.LinearIntegerBuckets{Floor: 0, NumBuckets: 10, StepSize: 0}
m.IntBuckets.Buckets = &config.IntegerBuckets_Linear{Linear: linear}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an invalid linear step_size in the metric: %v", err)
}
// Test that a valid Linear bucket config passes.
linear = &config.LinearIntegerBuckets{Floor: 0, NumBuckets: 20, StepSize: 10}
m.IntBuckets.Buckets = &config.IntegerBuckets_Linear{Linear: linear}
if err := validateReportDefinitionForMetric(m, r); err != nil {
t.Errorf("Rejected INT_RANGE_HISTOGRAM report with a valid linear int_config in the metric: %v", err)
}
// Test that an empty Exponential bucket config returns an error.
m.IntBuckets.Buckets = &config.IntegerBuckets_Exponential{}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an empty exponential bucket config in the metric: %v", err)
}
// Test that an Exponential bucket config with invalid num_buckets returns an error.
exponential = &config.ExponentialIntegerBuckets{Floor: 0, NumBuckets: 0, InitialStep: 10, StepMultiplier: 2}
m.IntBuckets.Buckets = &config.IntegerBuckets_Exponential{Exponential: exponential}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an invalid exponential num_buckets in the metric: %v", err)
}
// Test that an Exponential bucket config with invalid initial_step returns an error.
exponential = &config.ExponentialIntegerBuckets{Floor: 0, NumBuckets: 10, InitialStep: 0, StepMultiplier: 2}
m.IntBuckets.Buckets = &config.IntegerBuckets_Exponential{Exponential: exponential}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an invalid exponential initial_step in the metric: %v", err)
}
// Test that an Exponential bucket config with invalid step_multiplier returns an error.
exponential = &config.ExponentialIntegerBuckets{Floor: 0, NumBuckets: 20, InitialStep: 2, StepMultiplier: 0}
m.IntBuckets.Buckets = &config.IntegerBuckets_Exponential{Exponential: exponential}
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an invalid exponential step_multiplier in the metric: %v", err)
}
// Test that a valid Exponential bucket config passes.
exponential = &config.ExponentialIntegerBuckets{Floor: 0, NumBuckets: 30, InitialStep: 2, StepMultiplier: 2}
m.IntBuckets.Buckets = &config.IntegerBuckets_Exponential{Exponential: exponential}
if err := validateReportDefinitionForMetric(m, r); err != nil {
t.Errorf("Accepted INT_RANGE_HISTOGRAM report with an invalid exponential step_multiplier in the metric: %v", err)
}
}
func TestValidateHasNoCobalt11OnlyFields(t *testing.T) {
r := config.ReportDefinition{
PrivacyLevel: config.ReportDefinition_NO_ADDED_PRIVACY,
}
if err := validateHasNoCobalt11OnlyFields(r); err == nil {
t.Error("Accepted non-Cobalt 1.1 report with privacy_level set.")
}
r = config.ReportDefinition{
MinValue: 10,
}
if err := validateHasNoCobalt11OnlyFields(r); err == nil {
t.Error("Accepted non-Cobalt 1.1 report with min_value set.")
}
r = config.ReportDefinition{
MaxValue: 10,
}
if err := validateHasNoCobalt11OnlyFields(r); err == nil {
t.Error("Accepted non-Cobalt 1.1 report with max_value set.")
}
r = config.ReportDefinition{
MaxCount: 10,
}
if err := validateHasNoCobalt11OnlyFields(r); err == nil {
t.Error("Accepted non-Cobalt 1.1 report with max_count set.")
}
r = config.ReportDefinition{
LocalAggregationProcedure: config.ReportDefinition_MEAN,
}
if err := validateHasNoCobalt11OnlyFields(r); err == nil {
t.Error("Accepted non-Cobalt 1.1 report with local_aggregation_procedure set.")
}
r = config.ReportDefinition{
LocalAggregationProcedurePercentileN: 2,
}
if err := validateHasNoCobalt11OnlyFields(r); err == nil {
t.Error("Accepted non-Cobalt 1.1 report with local_aggregation_procedure_percentile_n set.")
}
r = config.ReportDefinition{
LocalAggregationPeriod: config.WindowSize_WINDOW_1_DAY,
}
if err := validateHasNoCobalt11OnlyFields(r); err == nil {
t.Error("Accepted non-Cobalt 1.1 report with local_aggregation_period set.")
}
r = config.ReportDefinition{
ReportingThreshold: 2,
}
if err := validateHasNoCobalt11OnlyFields(r); err == nil {
t.Error("Accepted non-Cobalt 1.1 report with reporting_threshold set.")
}
}
func TestValidateHasNoFieldsDeprecatedInCobalt11(t *testing.T) {
r := config.ReportDefinition{
LocalPrivacyNoiseLevel: config.ReportDefinition_NONE,
}
if err := validateHasNoFieldsDeprecatedInCobalt11(r); err == nil {
t.Error("Accepted non-Cobalt 1.1 report with local_privacy_noise_level set.")
}
r = config.ReportDefinition{
CandidateList: []string{"hello"},
}
if err := validateHasNoFieldsDeprecatedInCobalt11(r); err == nil {
t.Error("Accepted non-Cobalt 1.1 report with candidate_list set.")
}
r = config.ReportDefinition{
WindowSize: []config.WindowSize{config.WindowSize_WINDOW_1_DAY},
}
if err := validateHasNoFieldsDeprecatedInCobalt11(r); err == nil {
t.Error("Accepted non-Cobalt 1.1 report with window_size set.")
}
r = config.ReportDefinition{
AggregationWindow: []*config.OnDeviceAggregationWindow{
&config.OnDeviceAggregationWindow{},
},
}
if err := validateHasNoFieldsDeprecatedInCobalt11(r); err == nil {
t.Error("Accepted non-Cobalt 1.1 report with aggregation_window set.")
}
}
func TestValidateLocalAggregationPeriod(t *testing.T) {
dailyReportTypes := []config.ReportDefinition_ReportType{
config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS,
config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS,
config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS,
}
hourlyReportTypes := []config.ReportDefinition_ReportType{
config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS,
config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
config.ReportDefinition_FLEETWIDE_MEANS,
config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS,
config.ReportDefinition_STRING_COUNTS,
}
for _, reportType := range dailyReportTypes {
r := makeValidReportWithType(reportType)
if err := validateLocalAggregationPeriod(r); err == nil {
t.Error("Accepted daily report with no local_aggregation_period")
}
r.LocalAggregationPeriod = config.WindowSize_WINDOW_1_DAY
if err := validateLocalAggregationPeriod(r); err != nil {
t.Errorf("Rejected daily report with valid local_aggregation_period: %v", err)
}
}
for _, reportType := range hourlyReportTypes {
r := makeValidReportWithType(reportType)
r.LocalAggregationPeriod = config.WindowSize_WINDOW_1_DAY
if err := validateLocalAggregationPeriod(r); err == nil {
t.Error("Accepted hourly report with local_aggregation_period")
}
r.LocalAggregationPeriod = config.WindowSize_UNSET
if err := validateLocalAggregationPeriod(r); err != nil {
t.Errorf("Rejected hourly report with no local_aggregation_period: %v", err)
}
}
}
func TestValidateLocalAggregationProcedure(t *testing.T) {
intLocalAggregationProcedureTypes := []config.ReportDefinition_ReportType{
config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS,
config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS,
config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS,
config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS,
}
for _, reportType := range intLocalAggregationProcedureTypes {
m := makeValidMetric(config.MetricDefinition_INTEGER)
r := makeValidReportWithType(reportType)
if err := validateLocalAggregationProcedure(m, r); err == nil {
t.Error("Accepted invalid report with no local_aggregation_procedure")
}
r.LocalAggregationProcedure = config.ReportDefinition_AT_LEAST_ONCE
if err := validateLocalAggregationProcedure(m, r); err == nil {
t.Error("Accepted report with invalid local_aggregation_procedure")
}
r.LocalAggregationProcedure = config.ReportDefinition_MEDIAN
if err := validateLocalAggregationProcedure(m, r); err != nil {
t.Errorf("Rejected report with valid local_aggregation_procedure: %v", err)
}
r.LocalAggregationProcedure = config.ReportDefinition_PERCENTILE_N
if err := validateLocalAggregationProcedure(m, r); err == nil {
t.Error("Accepted PERCENTILE_N report with no local_aggregation_procedure_percentile_n set")
}
r.LocalAggregationProcedurePercentileN = 110
if err := validateLocalAggregationProcedure(m, r); err == nil {
t.Error("Accepted PERCENTILE_N report with invalid local_aggregation_procedure_percentile_n set")
}
r.LocalAggregationProcedurePercentileN = 5
if err := validateLocalAggregationProcedure(m, r); err != nil {
t.Errorf("Rejected PERCENTILE_N report with valid local_aggregation_procedure_percentile_n: %v", err)
}
m = makeValidMetric(config.MetricDefinition_OCCURRENCE)
if err := validateLocalAggregationProcedure(m, r); err == nil {
t.Error("Accepted daily report with no local_aggregation_procedure")
}
r.LocalAggregationProcedure = config.ReportDefinition_LOCAL_AGGREGATION_PROCEDURE_UNSET
if err := validateLocalAggregationProcedure(m, r); err != nil {
t.Errorf("Rejected daily report with valid local_aggregation_procedure: %v", err)
}
}
m := makeValidMetric(config.MetricDefinition_OCCURRENCE)
r := makeValidReportWithType(config.ReportDefinition_UNIQUE_DEVICE_COUNTS)
if err := validateLocalAggregationProcedure(m, r); err == nil {
t.Error("Accepted invalid report with no local_aggregation_procedure")
}
r.LocalAggregationProcedure = config.ReportDefinition_MEDIAN
if err := validateLocalAggregationProcedure(m, r); err == nil {
t.Error("Accepted report with invalid local_aggregation_procedure")
}
r.LocalAggregationProcedure = config.ReportDefinition_AT_LEAST_ONCE
if err := validateLocalAggregationProcedure(m, r); err != nil {
t.Errorf("Rejected report with valid local_aggregation_procedure: %v", err)
}
r.LocalAggregationProcedure = config.ReportDefinition_SELECT_FIRST
r.EventVectorBufferMax = 1
if err := validateLocalAggregationProcedure(m, r); err != nil {
t.Errorf("Rejected report with valid event_vector_buffer_max config: %v", err)
}
r.EventVectorBufferMax = 100
if err := validateLocalAggregationProcedure(m, r); err == nil {
t.Errorf("Accepted report with invalid event_vector_buffer_max config")
}
r = makeValidReportWithType(config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS)
r.LocalAggregationProcedure = config.ReportDefinition_AT_LEAST_ONCE
if err := validateLocalAggregationProcedure(m, r); err == nil {
t.Error("Accepted report with unnecessary local_aggregation_procedure")
}
}
func TestValidateExpeditedSending(t *testing.T) {
m := makeValidMetric(config.MetricDefinition_OCCURRENCE)
r := makeValidReportWithType(config.ReportDefinition_UNIQUE_DEVICE_COUNTS)
r.ExpeditedSending = true
r.LocalAggregationProcedure = config.ReportDefinition_SELECT_MOST_COMMON
r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY
if err := validateExpeditedSending(m, r); err == nil {
t.Error("Accepted report with invalid local_aggregation_procedure for expedited_sending")
}
r.LocalAggregationProcedure = config.ReportDefinition_AT_LEAST_ONCE
if err := validateExpeditedSending(m, r); err != nil {
t.Errorf("Rejected report with expedited_sending and valid local_aggregation_procedure: %v", err)
}
r.LocalAggregationProcedure = config.ReportDefinition_SELECT_FIRST
if err := validateExpeditedSending(m, r); err != nil {
t.Errorf("Rejected report with expedited_sending and valid local_aggregation_procedure: %v", err)
}
r.PrivacyLevel = config.ReportDefinition_LOW_PRIVACY
if err := validateExpeditedSending(m, r); err == nil {
t.Error("Accepted report with invalid privacy_level for expedited_sending")
}
r = makeValidReportWithType(config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS)
r.ExpeditedSending = true
r.LocalAggregationProcedure = config.ReportDefinition_AT_LEAST_ONCE
if err := validateExpeditedSending(m, r); err == nil {
t.Error("Accepted report with invalid type for expedited_sending")
}
r.ExpeditedSending = false
r.LocalAggregationProcedure = config.ReportDefinition_SELECT_MOST_COMMON
if err := validateExpeditedSending(m, r); err != nil {
t.Errorf("Rejected report with expedited_sending unset: %v", err)
}
m = makeValidMetric(config.MetricDefinition_STRING)
r = makeValidReportWithType(config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS)
r.ExpeditedSending = true
r.LocalAggregationProcedure = config.ReportDefinition_LOCAL_AGGREGATION_PROCEDURE_UNSET
r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY
if err := validateExpeditedSending(m, r); err != nil {
t.Errorf("Rejected report with expedited_sending and valid report type: %v", err)
}
}
// Test that deleted report IDs are not reused.
func TestDeletedReportIds(t *testing.T) {
m := makeValidMetric(config.MetricDefinition_OCCURRENCE)
r := makeValidReportWithType(config.ReportDefinition_UNIQUE_DEVICE_COUNTS)
r.LocalAggregationPeriod = config.WindowSize_WINDOW_1_DAY
r.LocalAggregationProcedure = config.ReportDefinition_AT_LEAST_ONCE
r.PrivacyLevel = config.ReportDefinition_NO_ADDED_PRIVACY
m.DeletedReportIds = append(m.DeletedReportIds, r.Id+10)
// Test that using a new report ID returns no error.
if err := validateReportDefinitionForMetric(m, r); err != nil {
t.Errorf("Rejected report with new report ID: %v", err)
}
// Test that reusing a previously deleted report ID returns an error.
m.DeletedReportIds = append(m.DeletedReportIds, r.Id)
if err := validateReportDefinitionForMetric(m, r); err == nil {
t.Errorf("Accepted invalid report with reused deleted report ID")
}
}