| package config_validator |
| |
| import ( |
| "config" |
| "fmt" |
| "testing" |
| "time" |
| ) |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Tests concerning sets of metrics. |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| // Test that repeated ids are rejected. |
| func TestValidateUniqueMetricId(t *testing.T) { |
| m1 := makeSomeValidMetric() |
| m1.MetricName = "name1" |
| m1.Id = 2 |
| |
| m2 := makeSomeValidMetric() |
| m2.MetricName = "name2" |
| m2.Id = 2 |
| |
| metrics := []*config.MetricDefinition{m1, m2} |
| |
| if err := validateConfiguredMetricDefinitions(metrics); err == nil { |
| t.Error("Accepted metric definitions with identical ids.") |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Tests for all/most metric types. |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| // Test that repeated names are rejected. |
| func TestValidateUniqueMetricName(t *testing.T) { |
| m1 := makeSomeValidMetric() |
| m1.MetricName = "name" |
| m1.Id = 2 |
| |
| m2 := makeSomeValidMetric() |
| m2.MetricName = "name" |
| m2.Id = 3 |
| |
| metrics := []*config.MetricDefinition{m1, m2} |
| if err := validateConfiguredMetricDefinitions(metrics); err == nil { |
| t.Error("Accepted metric definitions with identical names.") |
| } |
| } |
| |
| // Test that all metrics accept 0 or more dimensions. |
| func TestValidMetricWithNoDimensions(t *testing.T) { |
| for _, mt := range metricTypesExcept() { |
| m := makeValidMetric(mt) |
| m.MetricDimensions = nil |
| if err := validateMetricDefinition(m); err != nil { |
| t.Errorf("Rejected valid %v metric with no dimension: %v", m.String(), err) |
| } |
| } |
| |
| for _, mt := range metricTypesExcept() { |
| m := makeValidMetric(mt) |
| addDimensions(m, 2) |
| if err := validateMetricDefinition(m); err != nil { |
| t.Errorf("Rejected valid %v metric with 2+ dimensions: %v", m.String(), err) |
| } |
| } |
| } |
| |
| // Test that invalid names are rejected. |
| func TestValidateMetricInvalidMetricName(t *testing.T) { |
| m := makeSomeValidMetric() |
| m.MetricName = "_invalid_name" |
| |
| if err := validateMetricDefinition(m); err == nil { |
| t.Error("Accepted metric definition with invalid name.") |
| } |
| } |
| |
| // Test that metric id 0 is not accepted. |
| func TestValidateZeroMetricId(t *testing.T) { |
| m := makeSomeValidMetric() |
| m.Id = 0 |
| |
| if err := validateMetricDefinition(m); err == nil { |
| t.Error("Accepted metric definition with 0 id.") |
| } |
| } |
| |
| // Test that we do not accept a metric with type UNSET. |
| func TestValidateUnsetMetricType(t *testing.T) { |
| m := makeValidOccurrenceMetric() |
| m.MetricType = config.MetricDefinition_UNSET |
| |
| if err := validateMetricDefinition(m); err == nil { |
| t.Error("Accepted metric definition with unset metric type.") |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Tests concerning metadata. |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| // Test that meta_data must be set. |
| func TestValidatePartsNoMetadata(t *testing.T) { |
| m := makeSomeValidMetric() |
| m.MetaData = nil |
| |
| if err := validateMetricDefinition(m); err == nil { |
| t.Error("Accepted metric definition with no meta_data set.") |
| } |
| } |
| |
| func TestValidateMetadataNoExpirationDate(t *testing.T) { |
| m := makeValidMetadata() |
| m.ExpirationDate = "" |
| |
| if err := validateMetadata(m); err == nil { |
| t.Error("Accepted metadata with no expiration date.") |
| } |
| } |
| |
| func TestValidateMetadataInvalidExpirationDate(t *testing.T) { |
| m := makeValidMetadata() |
| m.ExpirationDate = "abcd" |
| |
| if err := validateMetadata(m); err == nil { |
| t.Error("Accepted invalid expiration date") |
| } |
| } |
| |
| func TestValidateMetadataExpirationDateTooFar(t *testing.T) { |
| m := makeValidMetadata() |
| m.ExpirationDate = time.Now().AddDate(1, 0, 2).Format(dateFormat) |
| |
| if err := validateMetadata(m); err == nil { |
| t.Errorf("Accepted expiration date more than 1 year out: %v", m.ExpirationDate) |
| } |
| } |
| |
| func TestValidateMetadataExpirationDateInPast(t *testing.T) { |
| m := makeValidMetadata() |
| m.ExpirationDate = "2010/01/01" |
| |
| if err := validateMetadata(m); err != nil { |
| t.Errorf("Rejected expiration date in the past: %v", err) |
| } |
| } |
| |
| func TestValidateMetadataReleaseStageNotSet(t *testing.T) { |
| m := makeValidMetadata() |
| m.MaxReleaseStage = config.ReleaseStage_RELEASE_STAGE_NOT_SET |
| |
| if err := validateMetadata(m); err == nil { |
| t.Error("Accepted metadata with no max_release_stage set.") |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Tests concerning event codes. |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| func TestValidateEventCodesMaxEventCodeTooBig(t *testing.T) { |
| for _, mt := range metricTypesExcept() { |
| m := makeValidMetric(mt) |
| addDimensions(m, 1) |
| m.MetricDimensions[0].MaxEventCode = 1024 |
| m.MetricDimensions[0].EventCodes = map[uint32]string{ |
| 1: "hello_world", |
| } |
| |
| if err := validateMetricDimensions(m); err != nil { |
| t.Errorf("Rejected max_event_code with value no less than 1024 for metric: %v", err) |
| } |
| } |
| } |
| |
| func TestValidateMetricDimensionsTooManyVariants(t *testing.T) { |
| for _, mt := range metricTypesExcept() { |
| m := makeValidMetric(mt) |
| manyCodes := map[uint32]string{1: "A", 2: "B", 3: "C", 4: "D", 5: "E", 6: "F", 7: "G", 8: "H", 9: "I", 10: "J", 1055: "K"} |
| m.MetricDimensions = []*config.MetricDefinition_MetricDimension{ |
| &config.MetricDefinition_MetricDimension{Dimension: "Dimension 1", EventCodes: manyCodes}, |
| &config.MetricDefinition_MetricDimension{Dimension: "Dimension 2", EventCodes: manyCodes}, |
| &config.MetricDefinition_MetricDimension{Dimension: "Dimension 3", EventCodes: manyCodes}, |
| &config.MetricDefinition_MetricDimension{Dimension: "Dimension 4", EventCodes: manyCodes}, |
| } |
| |
| if err := validateMetricDimensions(m); err != nil { |
| t.Errorf("Rejected valid metric with many event codes: %v", err) |
| } |
| } |
| } |
| |
| func TestValidateMetricDimensionsDimensionNames(t *testing.T) { |
| m := makeSomeValidMetric() |
| addDimensions(m, 2) |
| m.MetricDimensions[1].Dimension = "" |
| |
| if err := validateMetricDimensions(m); err == nil { |
| t.Error("Accepted invalid metric with an unnamed metric dimension") |
| } |
| |
| m.MetricDimensions[1].Dimension = "Dimension 2" |
| if err := validateMetricDimensions(m); err != nil { |
| t.Error("Rejected valid metric with two distinctly named metric dimensions") |
| } |
| |
| m.MetricDimensions[0].Dimension = "Dimension 1" |
| m.MetricDimensions[1].Dimension = "Dimension 1" |
| if err := validateMetricDimensions(m); err == nil { |
| t.Error("Accepted invalid metric with two identically named metric dimensions") |
| } |
| } |
| |
| func TestValidateMetricDimensionsEventCodeAlias(t *testing.T) { |
| m := makeSomeValidMetric() |
| addDimensions(m, 1) |
| m.MetricDimensions[0].EventCodes = map[uint32]string{ |
| 0: "CodeName", |
| 1: "CodeName", |
| } |
| |
| if err := validateMetricDimensions(m); err == nil { |
| t.Error("Accepted invalid metric with duplicate event code names") |
| } |
| |
| m.MetricDimensions[0].EventCodes = map[uint32]string{ |
| 0: "CodeName", |
| } |
| m.MetricDimensions[0].EventCodeAliases = map[string]string{ |
| "Metric": "Alias", |
| } |
| |
| if err := validateMetricDimensions(m); err == nil { |
| t.Error("Accepted invalid metric with an invalid alias") |
| } |
| |
| m.MetricDimensions[0].EventCodeAliases = map[string]string{ |
| "Alias": "CodeName", |
| } |
| if err := validateMetricDimensions(m); err == nil { |
| t.Error("Accepted invalid metric with an alias in the wrong order") |
| } |
| |
| m.MetricDimensions[0].EventCodeAliases = map[string]string{ |
| "CodeName": "Alias", |
| } |
| if err := validateMetricDimensions(m); err != nil { |
| t.Errorf("Rejected valid metric: %v", err) |
| } |
| |
| m.MetricDimensions[0].EventCodes[1] = "CodeName2" |
| m.MetricDimensions[0].EventCodeAliases = map[string]string{ |
| "CodeName": "CodeName2", |
| } |
| if err := validateMetricDimensions(m); err == nil { |
| t.Error("Accepted invalid metric that maps to an existing event code") |
| } |
| } |
| |
| func TestValidateEventCodesIndexLargerThanMax(t *testing.T) { |
| m := makeSomeValidMetric() |
| addDimensions(m, 1) |
| m.MetricDimensions[0].MaxEventCode = 100 |
| m.MetricDimensions[0].EventCodes = map[uint32]string{ |
| 1: "hello_world", |
| 101: "blah", |
| } |
| |
| if err := validateMetricDimensions(m); err == nil { |
| t.Error("Accepted event type with index larger than max_event_code.") |
| } |
| } |
| |
| func TestAllowNoEventCodesWhenMaxEventCodeIsSet(t *testing.T) { |
| m := makeSomeValidMetric() |
| addDimensions(m, 1) |
| m.MetricDimensions[0].EventCodes = nil |
| |
| if err := validateMetricDimensions(m); err == nil { |
| t.Error("Accepted metric without max_event_code and without any event_codes.") |
| } |
| |
| m.MetricDimensions[0].MaxEventCode = 100 |
| |
| if err := validateMetricDimensions(m); err != nil { |
| t.Error("Did not accept metric with max_event_code set and no event codes defined.") |
| } |
| } |
| |
| func TestValidateEventCodesNoEventCodes(t *testing.T) { |
| m := makeSomeValidMetric() |
| addDimensions(m, 1) |
| m.MetricDimensions[0].EventCodes = map[uint32]string{} |
| |
| if err := validateMetricDimensions(m); err == nil { |
| t.Error("Accepted metric with no event types.") |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Tests concerning metric units. |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| func TestMetricUnitsForIntegerAndIntegerHistogramOnly(t *testing.T) { |
| for _, mt := range metricTypesExcept(config.MetricDefinition_INTEGER, config.MetricDefinition_INTEGER_HISTOGRAM) { |
| m := makeValidMetric(mt) |
| m.MetricUnits = config.MetricUnits_NANOSECONDS |
| m.MetricUnitsOther = "" |
| if err := validateMetricDefinition(m); err == nil { |
| t.Errorf("Accepted %v metric with metric_units set", mt.String()) |
| } |
| |
| m.MetricUnits = config.MetricUnits_METRIC_UNITS_OTHER |
| m.MetricUnitsOther = "hello" |
| if err := validateMetricDefinition(m); err == nil { |
| t.Errorf("Accepted %v metric with metric_units_other set", mt.String()) |
| } |
| } |
| |
| } |
| |
| func TestMetricUnitsForIntegers(t *testing.T) { |
| m := makeValidIntegerMetric() |
| m.MetricUnits = config.MetricUnits_METRIC_UNITS_OTHER |
| m.MetricUnitsOther = "hello" |
| if err := validateMetricDefinition(m); err != nil { |
| t.Errorf("Rejected INTEGER metric with metric_units_other set: %v", err) |
| } |
| |
| m.MetricUnits = config.MetricUnits_NANOSECONDS |
| m.MetricUnitsOther = "" |
| if err := validateMetricDefinition(m); err != nil { |
| t.Errorf("Rejected INTEGER metric with metric_units set: %v", err) |
| } |
| |
| m.MetricUnits = config.MetricUnits_NANOSECONDS |
| m.MetricUnitsOther = "hello" |
| if err := validateMetricDefinition(m); err == nil { |
| t.Error("Accepted INTEGER metric with both metric_units and metric_units_other set.") |
| } |
| |
| m.MetricUnits = config.MetricUnits_METRIC_UNITS_OTHER |
| m.MetricUnitsOther = "" |
| if err := validateMetricDefinition(m); err == nil { |
| t.Error("Accepted INTEGER metric with neither metric_units nor metric_units_other set.") |
| } |
| } |
| |
| func TestMetricUnitsForIntegerHistograms(t *testing.T) { |
| m := makeValidIntegerHistogramMetric() |
| m.MetricUnits = config.MetricUnits_METRIC_UNITS_OTHER |
| m.MetricUnitsOther = "hello" |
| if err := validateMetricDefinition(m); err != nil { |
| t.Errorf("Rejected INTEGER_HISTOGRAM metric with metric_units_other set: %v", err) |
| } |
| |
| m.MetricUnits = config.MetricUnits_NANOSECONDS |
| m.MetricUnitsOther = "" |
| if err := validateMetricDefinition(m); err != nil { |
| t.Errorf("Rejected INTEGER_HISTOGRAM metric with metric_units set: %v", err) |
| } |
| |
| m.MetricUnits = config.MetricUnits_NANOSECONDS |
| m.MetricUnitsOther = "hello" |
| if err := validateMetricDefinition(m); err == nil { |
| t.Error("Accepted INTEGER_HISTOGRAM metric with both metric_units and metric_units_other set.") |
| } |
| |
| m.MetricUnits = config.MetricUnits_METRIC_UNITS_OTHER |
| m.MetricUnitsOther = "" |
| if err := validateMetricDefinition(m); err == nil { |
| t.Error("Accepted INTEGER_HISTOGRAM metric with neither metric_units nor metric_units_other set.") |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Tests concerning metric time zone policy. |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| func TestValidateTimeZonePolicyOtherTimeZoneNotRequired(t *testing.T) { |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| m.TimeZonePolicy = config.MetricDefinition_UTC |
| if err := validateMetricDefinition(m); err != nil { |
| t.Error("Rejected metric with valid UTC TimeZonePolicy") |
| } |
| m.OtherTimeZone = "America/Los_Angeles" |
| if err := validateMetricDefinition(m); err == nil { |
| t.Error("Accepted metric with UTC TimeZonePolicy and with other_time_zone set") |
| } |
| |
| m.TimeZonePolicy = config.MetricDefinition_LOCAL |
| m.OtherTimeZone = "" |
| if err := validateMetricDefinition(m); err != nil { |
| t.Error("Rejected metric with valid LOCAL TimeZonePolicy") |
| } |
| m.OtherTimeZone = "America/Los_Angeles" |
| if err := validateMetricDefinition(m); err == nil { |
| t.Error("Accepted metric with LOCAL TimeZonePolicy and with other_time_zone set") |
| } |
| } |
| |
| func TestValidateTimeZonePolicyOtherTimeZoneRequired(t *testing.T) { |
| m := makeValidMetric(config.MetricDefinition_OCCURRENCE) |
| m.TimeZonePolicy = config.MetricDefinition_OTHER_TIME_ZONE |
| if err := validateMetricDefinition(m); err == nil { |
| t.Error("Accepted metric with OTHER_TIME_ZONE TimeZonePolicy but no other_time_zone set") |
| } |
| |
| m.OtherTimeZone = "PST" |
| if err := validateMetricDefinition(m); err == nil { |
| t.Error("Accepted metric with OTHER_TIME_ZONE TimeZone and invalid time zone identifier as other_time_zone") |
| } |
| |
| // "Local" is accepted by `time.LoadLocation` but is not an IANA time zone identifier |
| // and may not be accepted by all consumers of the `other_time_zone` field, so the config |
| // validator should reject it. |
| m.OtherTimeZone = "Local" |
| if err := validateMetricDefinition(m); err == nil { |
| t.Error("Accepted metric with OTHER_TIME_ZONE TimeZone and invalid time zone identifier `Local` as other_time_zone") |
| } |
| |
| m.OtherTimeZone = "America/Los_Angeles" |
| if err := validateMetricDefinition(m); err != nil { |
| t.Error("Rejected metric with OTHER_TIME_ZONE TimeZonePolicy and valid other_time_zone time zone identifier") |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Tests concerning INTEGER and INTEGER_HISTOGRAM metrics. |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| // Test that int_buckets can only be set if the metric type is INTEGER_HISTOGRAM. |
| func TestValidateIntBucketsRequiredForIntegerHistogramsOnly(t *testing.T) { |
| for _, mt := range metricTypesExcept(config.MetricDefinition_INTEGER_HISTOGRAM) { |
| m := makeValidMetric(mt) |
| m.IntBuckets = &config.IntegerBuckets{} |
| if err := validateMetricDefinition(m); err == nil { |
| t.Errorf("Accepted metric definition with type %s with int_buckets set.", mt) |
| } |
| } |
| |
| m := makeValidIntegerHistogramMetric() |
| m.IntBuckets = nil |
| if err := validateMetricDefinition(m); err == nil { |
| t.Error("Accepted INTEGER_HISTOGRAM metric definition with int_buckets not set.") |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Tests concerning STRING metrics. |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| func TestStringMetrics(t *testing.T) { |
| m := makeValidStringMetric() |
| if err := validateMetricDefinition(m); err != nil { |
| t.Errorf("Rejected valid STRING metric definition: %v", err) |
| } |
| |
| m = makeValidStringMetric() |
| m.StringCandidateFile = "" |
| if err := validateMetricDefinition(m); err == nil { |
| t.Error("Accepted STRING metric definition with string_candidate_file not set.") |
| } |
| } |
| |
| // Test that all metrics are required to have metric_semantics set. |
| func TestValidateMetricDefinitionSemantics(t *testing.T) { |
| for _, mt := range metricTypesExcept() { |
| m := makeValidMetric(mt) |
| m.MetricSemantics = nil |
| if err := validateMetricDefinition(m); err == nil { |
| t.Errorf("Accepted %v metric with no metric_semantics not set.", m.MetricType.String()) |
| } |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Following are utility functions to facilitate testing. |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| // Allows generating a list of MetricTypes for which we can run tests. |
| func metricTypesExcept(remove ...config.MetricDefinition_MetricType) []config.MetricDefinition_MetricType { |
| return metricTypesExceptList(remove) |
| } |
| |
| // Allows generating a list of MetricTypes for which we can run tests. |
| func metricTypesExceptList(remove []config.MetricDefinition_MetricType) (s []config.MetricDefinition_MetricType) { |
| types := map[config.MetricDefinition_MetricType]bool{} |
| for t := range config.MetricDefinition_MetricType_name { |
| types[config.MetricDefinition_MetricType(t)] = true |
| } |
| |
| for _, r := range remove { |
| delete(types, r) |
| } |
| delete(types, config.MetricDefinition_UNSET) |
| |
| for t, _ := range types { |
| s = append(s, t) |
| } |
| |
| return |
| } |
| |
| func makeValidMetadata() *config.MetricDefinition_Metadata { |
| return &config.MetricDefinition_Metadata{ |
| ExpirationDate: time.Now().AddDate(1, 0, 0).Format(dateFormat), |
| MaxReleaseStage: config.ReleaseStage_DEBUG, |
| } |
| } |
| |
| func addDimensions(m *config.MetricDefinition, numDim int) { |
| for i := 0; i < numDim; i++ { |
| m.MetricDimensions = append(m.MetricDimensions, &config.MetricDefinition_MetricDimension{ |
| Dimension: fmt.Sprintf("Dimension %d", i), |
| EventCodes: map[uint32]string{1: "hello_world"}, |
| }) |
| } |
| } |
| |
| func makeValidBaseMetric(t config.MetricDefinition_MetricType) *config.MetricDefinition { |
| metadata := makeValidMetadata() |
| return &config.MetricDefinition{ |
| Id: 1, |
| MetricName: "the_metric_name", |
| MetaData: metadata, |
| MetricType: t, |
| MetricSemantics: []config.MetricSemantics{ |
| config.MetricSemantics_METRIC_SEMANTICS_UNSPECIFIED, |
| }, |
| } |
| } |
| |
| func makeValidOccurrenceMetric() *config.MetricDefinition { |
| return makeValidBaseMetric(config.MetricDefinition_OCCURRENCE) |
| } |
| |
| func makeValidIntegerMetric() *config.MetricDefinition { |
| m := makeValidBaseMetric(config.MetricDefinition_INTEGER) |
| m.MetricUnits = config.MetricUnits_SECONDS |
| return m |
| } |
| |
| func makeValidIntegerHistogramMetric() *config.MetricDefinition { |
| m := makeValidBaseMetric(config.MetricDefinition_INTEGER_HISTOGRAM) |
| m.MetricUnits = config.MetricUnits_SECONDS |
| m.IntBuckets = &config.IntegerBuckets{} |
| return m |
| } |
| |
| func makeValidStringMetric() *config.MetricDefinition { |
| m := makeValidBaseMetric(config.MetricDefinition_STRING) |
| m.StringCandidateFile = "some_file" |
| return m |
| } |
| |
| func makeValidMetric(t config.MetricDefinition_MetricType) *config.MetricDefinition { |
| switch t { |
| case config.MetricDefinition_OCCURRENCE: |
| return makeValidOccurrenceMetric() |
| case config.MetricDefinition_INTEGER: |
| return makeValidIntegerMetric() |
| case config.MetricDefinition_INTEGER_HISTOGRAM: |
| return makeValidIntegerHistogramMetric() |
| case config.MetricDefinition_STRING: |
| return makeValidStringMetric() |
| } |
| return makeValidOccurrenceMetric() |
| } |
| |
| func makeSomeValidMetric() *config.MetricDefinition { |
| return makeValidOccurrenceMetric() |
| } |
| |
| // Test the makeValidMetric functions return valid metrics. |
| func TestMakeValidMetricsValid(t *testing.T) { |
| for _, mt := range metricTypesExcept() { |
| if err := validateMetricDefinition(makeValidMetric(mt)); err != nil { |
| t.Errorf("Rejected valid %v metric: %v", mt.String(), err) |
| } |
| } |
| } |
| |
| // Test the makeValidMetadata does return a valid metadata message. |
| func TestValidateMakeValidMetadata(t *testing.T) { |
| m := makeValidMetadata() |
| if err := validateMetadata(m); err != nil { |
| t.Errorf("Rejected valid metadata: %v", err) |
| } |
| } |