diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 000000000..477ae067f --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,86 @@ +// Copyright 2013 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "fmt" + "io/ioutil" + "path" + "strings" + "testing" +) + +var fixturesPath = "fixtures" + +var configTests = []struct { + inputFile string + printedFile string + shouldFail bool + errContains string +}{ + { + inputFile: "minimal.conf", + printedFile: "minimal.conf.printed", + }, { + inputFile: "sample.conf", + printedFile: "sample.conf.printed", + }, { + // TODO: Options that are not provided should be set to sane defaults or + // create errors during config loading (as appropriate). Right now, these + // options remain at their zero-values, which is probably not what we want. + inputFile: "empty.conf", + printedFile: "empty.conf.printed", + }, + // TODO: To enable testing of bad configs, we first need to change config + // loading so that it doesn't exit when loading a bad config. Instead, the + // configuration error should be passed back all the way to the caller. + // + //{ + // inputFile: "bad_job_option.conf", + // shouldFail: true, + // errContains: "Missing job name", + //}, +} + +func TestConfigs(t *testing.T) { + for _, configTest := range configTests { + testConfig, err := LoadFromFile(path.Join(fixturesPath, configTest.inputFile)) + + if err != nil { + if !configTest.shouldFail { + t.Errorf("Error parsing config %v: %v", configTest.inputFile, err) + } else { + if !strings.Contains(err.Error(), configTest.errContains) { + t.Errorf("Expected error containing '%v', got: %v", configTest.errContains, err) + } + } + } else { + printedConfig, err := ioutil.ReadFile(path.Join(fixturesPath, configTest.printedFile)) + if err != nil { + t.Errorf("Error reading config %v: %v", configTest.inputFile, err) + continue + } + expected := string(printedConfig) + actual := testConfig.ToString(0) + + if actual != expected { + t.Errorf("%v: printed config doesn't match expected output", configTest.inputFile) + t.Errorf("Expected:\n%v\n\nActual:\n%v\n", expected, actual) + t.Errorf("Writing expected and actual printed configs to /tmp for diffing (see test source for paths)") + ioutil.WriteFile(fmt.Sprintf("/tmp/%s.expected", configTest.printedFile), []byte(expected), 0600) + ioutil.WriteFile(fmt.Sprintf("/tmp/%s.actual", configTest.printedFile), []byte(actual), 0600) + } + } + } +} diff --git a/config/fixtures/empty.conf.printed b/config/fixtures/empty.conf.printed new file mode 100644 index 000000000..b2c9c8738 --- /dev/null +++ b/config/fixtures/empty.conf.printed @@ -0,0 +1,8 @@ +global { + scrape_interval = "0y" + evaluation_interval = "0y" + rule_files = [ + + ] +} + diff --git a/config/fixtures/minimal.conf.printed b/config/fixtures/minimal.conf.printed new file mode 100644 index 000000000..833f635bf --- /dev/null +++ b/config/fixtures/minimal.conf.printed @@ -0,0 +1,20 @@ +global { + scrape_interval = "30s" + evaluation_interval = "30s" + labels { + monitor = "test" + } + rule_files = [ + "prometheus.rules" + ] +} + +job { + name = "prometheus" + scrape_interval = "15s" + targets { + endpoints = [ + "http://localhost:9090/metrics.json" + ] + } +} diff --git a/config/fixtures/sample.conf.printed b/config/fixtures/sample.conf.printed new file mode 100644 index 000000000..aa46fa82f --- /dev/null +++ b/config/fixtures/sample.conf.printed @@ -0,0 +1,49 @@ +global { + scrape_interval = "30s" + evaluation_interval = "30s" + labels { + monitor = "test" + } + rule_files = [ + "prometheus.rules" + ] +} + +job { + name = "prometheus" + scrape_interval = "15s" + targets { + endpoints = [ + "http://localhost:9090/metrics.json" + ] + labels { + group = "canary" + } + } +} + +job { + name = "random" + scrape_interval = "30s" + targets { + endpoints = [ + "http://random.com:8080/metrics.json", + "http://random.com:8081/metrics.json", + "http://random.com:8082/metrics.json", + "http://random.com:8083/metrics.json", + "http://random.com:8084/metrics.json" + ] + labels { + group = "production" + } + } + targets { + endpoints = [ + "http://random.com:8085/metrics.json", + "http://random.com:8086/metrics.json" + ] + labels { + group = "canary" + } + } +} diff --git a/config/load.go b/config/load.go index f998cb337..f0b740797 100644 --- a/config/load.go +++ b/config/load.go @@ -27,7 +27,7 @@ import ( var yylval *yySymType // For storing extra token information, like the contents of a string. var yyline int // Line number within the current file or buffer. var yypos int // Character position within the current line. -var parsedConfig = New() // Temporary variable for storing the parsed configuration. +var parsedConfig *Config // Temporary variable for storing the parsed configuration. type ConfigLexer struct { errors []string @@ -45,6 +45,7 @@ func (lexer *ConfigLexer) Error(errorStr string) { } func LoadFromReader(configReader io.Reader) (*Config, error) { + parsedConfig = New() yyin = configReader yypos = 1 yyline = 1 diff --git a/config/printer.go b/config/printer.go index 00e4c7dc1..c977d945b 100644 --- a/config/printer.go +++ b/config/printer.go @@ -34,29 +34,39 @@ func (config *Config) ToString(indent int) string { for _, job := range config.Jobs { jobs = append(jobs, job.ToString(indent)) } - return indentStr(indent, "%v\n%v\n", global, strings.Join(jobs, "\n")) + return indentStr(indent, "%v\n%v", global, strings.Join(jobs, "\n")) } func labelsToString(indent int, labels model.LabelSet) string { str := indentStr(indent, "labels {\n") + labelStrings := []string{} for label, value := range labels { - str += indentStr(indent+1, "%v = \"%v\",\n", label, value) + labelStrings = append(labelStrings, indentStr(indent+1, "%v = \"%v\"", label, value)) } + str += strings.Join(labelStrings, ",\n") + "\n" str += indentStr(indent, "}\n") return str } +func stringListToString(indent int, list []string) string { + listString := []string{} + for _, item := range list { + listString = append(listString, indentStr(indent, "\"%v\"", item)) + } + return strings.Join(listString, ",\n") + "\n" +} + func (global *GlobalConfig) ToString(indent int) string { str := indentStr(indent, "global {\n") str += indentStr(indent+1, "scrape_interval = \"%s\"\n", utility.DurationToString(global.ScrapeInterval)) str += indentStr(indent+1, "evaluation_interval = \"%s\"\n", utility.DurationToString(global.EvaluationInterval)) - str += labelsToString(indent+1, global.Labels) - str += indentStr(indent, "}\n") - str += indentStr(indent+1, "rule_files = [\n") - for _, ruleFile := range global.RuleFiles { - str += indentStr(indent+2, "\"%v\",\n", ruleFile) + if len(global.Labels) > 0 { + str += labelsToString(indent+1, global.Labels) } + str += indentStr(indent+1, "rule_files = [\n") + str += stringListToString(indent+2, global.RuleFiles) str += indentStr(indent+1, "]\n") + str += indentStr(indent, "}\n") return str } @@ -67,11 +77,11 @@ func (job *JobConfig) ToString(indent int) string { for _, targets := range job.Targets { str += indentStr(indent+1, "targets {\n") str += indentStr(indent+2, "endpoints = [\n") - for _, endpoint := range targets.Endpoints { - str += indentStr(indent+3, "\"%v\",\n", endpoint) - } + str += stringListToString(indent+3, targets.Endpoints) str += indentStr(indent+2, "]\n") - str += labelsToString(indent+2, targets.Labels) + if len(targets.Labels) > 0 { + str += labelsToString(indent+2, targets.Labels) + } str += indentStr(indent+1, "}\n") } str += indentStr(indent, "}\n")