blob: b52d87f22e97e83d030fa6c9bb949c1032c847c0 [file] [log] [blame]
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)
}
}