diff --git a/metricbeat/mb/testing/data/config.yml b/metricbeat/mb/testing/data/config.yml deleted file mode 100644 index 2286e554e9e8..000000000000 --- a/metricbeat/mb/testing/data/config.yml +++ /dev/null @@ -1,9 +0,0 @@ -type: http -url: "/server-status?auto=" -suffix: plain -omit_documented_fields_check: - - "apache.status.hostname" -remove_fields_from_comparison: - - "apache.status.hostname" -module: - namespace: test diff --git a/metricbeat/mb/testing/data/data_test.go b/metricbeat/mb/testing/data/data_test.go index de71c43d1ec0..3102780e66fa 100644 --- a/metricbeat/mb/testing/data/data_test.go +++ b/metricbeat/mb/testing/data/data_test.go @@ -18,83 +18,18 @@ package data import ( - "encoding/json" - "flag" - "io/ioutil" - "log" - "net/http" - "net/http/httptest" + "fmt" "os" "path/filepath" - "sort" "strings" "testing" - "github.com/pkg/errors" - - "github.com/mitchellh/hashstructure" - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v2" - - "github.com/elastic/beats/libbeat/asset" - "github.com/elastic/beats/libbeat/common" - "github.com/elastic/beats/libbeat/mapping" - "github.com/elastic/beats/metricbeat/mb" - mbtesting "github.com/elastic/beats/metricbeat/mb/testing" + mbtest "github.com/elastic/beats/metricbeat/mb/testing" _ "github.com/elastic/beats/metricbeat/include" - _ "github.com/elastic/beats/metricbeat/include/fields" -) - -const ( - expectedExtension = "-expected.json" ) -var ( - // Use `go test -generate` to update files. - generateFlag = flag.Bool("generate", false, "Write golden files") - moduleFlag = flag.String("module", "", "Choose a module to test") -) - -type Config struct { - // The type of the test to run, usually `http`. - Type string - - // URL of the endpoint that must be tested depending on each module - URL string - - // Suffix is the extension of the source file with the input contents. Defaults to `json`, `plain` is also a common use. - Suffix string - - // Module is a map of specific configs that will be appended to a module configuration prior initializing it. - // For example, the following config in yaml: - // module: - // namespace: test - // foo: bar - // - // Will produce the following module config: - // - module: http - // metricsets: - // - json - // period: 10s - // hosts: ["localhost:80"] - // path: "/" - // namespace: "test" - // foo: bar - // - // (notice last two lines) - Module map[string]interface{} `yaml:"module"` - - // OmitDocumentedFieldsCheck is a list of fields that must be omitted from the function that checks if the field - // is contained in {metricset}/_meta/fields.yml - OmitDocumentedFieldsCheck []string `yaml:"omit_documented_fields_check"` - - // RemoveFieldsForComparison - RemoveFieldsForComparison []string `yaml:"remove_fields_from_comparison"` -} - func TestAll(t *testing.T) { - configFiles, _ := filepath.Glob(getModulesPath() + "/*/*/_meta/testdata/config.yml") for _, f := range configFiles { @@ -103,306 +38,13 @@ func TestAll(t *testing.T) { moduleName := s[4] metricSetName := s[5] - if *moduleFlag != "" { - if *moduleFlag != moduleName { - continue - } - } - - configFile, err := ioutil.ReadFile(f) - if err != nil { - log.Printf("yamlFile.Get err #%v ", err) - } - var config Config - err = yaml.Unmarshal(configFile, &config) - if err != nil { - log.Fatalf("Unmarshal: %v", err) - } - - if config.Suffix == "" { - config.Suffix = "json" - } - - getTestdataFiles(t, moduleName, metricSetName, config) - } -} - -func getTestdataFiles(t *testing.T, module, metricSet string, config Config) { - ff, err := filepath.Glob(getMetricsetPath(module, metricSet) + "/_meta/testdata/*." + config.Suffix) - if err != nil { - t.Fatal(err) - } - - var files []string - for _, f := range ff { - // Exclude all the expected files - if strings.HasSuffix(f, expectedExtension) { - continue - } - files = append(files, f) - } - - for _, f := range files { - t.Run(f, func(t *testing.T) { - runTest(t, f, module, metricSet, config) + t.Run(fmt.Sprintf("%s.%s", moduleName, metricSetName), func(t *testing.T) { + config := mbtest.ReadDataConfig(t, f) + mbtest.TestDataFilesWithConfig(t, moduleName, metricSetName, config) }) } } -func runTest(t *testing.T, file string, module, metricSetName string, config Config) { - - // starts a server serving the given file under the given url - s := server(t, file, config.URL) - defer s.Close() - - moduleConfig := getConfig(module, metricSetName, s.URL, config) - metricSet := mbtesting.NewMetricSet(t, moduleConfig) - - var events []mb.Event - var errs []error - - switch v := metricSet.(type) { - case mb.ReportingMetricSetV2: - metricSet := mbtesting.NewReportingMetricSetV2(t, moduleConfig) - events, errs = mbtesting.ReportingFetchV2(metricSet) - case mb.ReportingMetricSetV2Error: - metricSet := mbtesting.NewReportingMetricSetV2Error(t, moduleConfig) - events, errs = mbtesting.ReportingFetchV2Error(metricSet) - default: - t.Fatalf("unknown type: %T", v) - } - - // Gather errors to build also error events - for _, e := range errs { - // TODO: for errors strip out and standardise the URL error as it would create a different diff every time - events = append(events, mb.Event{Error: e}) - } - - var data []common.MapStr - - for _, e := range events { - beatEvent := mbtesting.StandardizeEvent(metricSet, e, mb.AddMetricSetInfo) - // Overwrite service.address as the port changes every time - beatEvent.Fields.Put("service.address", "127.0.0.1:55555") - data = append(data, beatEvent.Fields) - } - - // Sorting the events is necessary as events are not necessarily sent in the same order - sort.SliceStable(data, func(i, j int) bool { - h1, _ := hashstructure.Hash(data[i], nil) - h2, _ := hashstructure.Hash(data[j], nil) - return h1 < h2 - }) - - if err := checkDocumented(t, data, config.OmitDocumentedFieldsCheck); err != nil { - t.Errorf("'%v' check if fields are documented in `metricbeat/{module}/{metricset}/_meta/fields.yml` "+ - "file or run 'make update' on Metricbeat folder to update root `metricbeat/fields.yml` in ", err) - } - - // Overwrites the golden files if run with -generate - if *generateFlag { - outputIndented, err := json.MarshalIndent(&data, "", " ") - if err != nil { - t.Fatal(err) - } - if err = ioutil.WriteFile(file+expectedExtension, outputIndented, 0644); err != nil { - t.Fatal(err) - } - } - - // Read expected file - expected, err := ioutil.ReadFile(file + expectedExtension) - if err != nil { - t.Fatalf("could not read file: %s", err) - } - - expectedMap := []common.MapStr{} - if err := json.Unmarshal(expected, &expectedMap); err != nil { - t.Fatal(err) - } - - for _, fieldToRemove := range config.RemoveFieldsForComparison { - for eventIndex := range data { - if err := data[eventIndex].Delete(fieldToRemove); err != nil { - t.Fatal(err) - } - } - - for eventIndex := range expectedMap { - if err := expectedMap[eventIndex].Delete(fieldToRemove); err != nil { - t.Fatal(err) - } - } - } - - output, err := json.Marshal(&data) - if err != nil { - t.Fatal(err) - } - - expectedJSON, err := json.Marshal(&expectedMap) - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, string(expectedJSON), string(output)) - - if strings.HasSuffix(file, "docs."+config.Suffix) { - writeDataJSON(t, data[0], module, metricSetName) - } -} - -func writeDataJSON(t *testing.T, data common.MapStr, module, metricSet string) { - // Add hardcoded timestamp - data.Put("@timestamp", "2019-03-01T08:05:34.853Z") - output, err := json.MarshalIndent(&data, "", " ") - if err = ioutil.WriteFile(getMetricsetPath(module, metricSet)+"/_meta/data.json", output, 0644); err != nil { - t.Fatal(err) - } -} - -// checkDocumented checks that all fields which show up in the events are documented -func checkDocumented(t *testing.T, data []common.MapStr, omitFields []string) error { - fieldsData, err := asset.GetFields("metricbeat") - if err != nil { - return err - } - - fields, err := mapping.LoadFields(fieldsData) - if err != nil { - return err - } - - documentedFields := fields.GetKeys() - keys := map[string]interface{}{} - - for _, k := range documentedFields { - keys[k] = struct{}{} - } - - for _, d := range data { - flat := d.Flatten() - if err := documentedFieldCheck(flat, keys, omitFields); err != nil { - return err - } - } - - return nil -} - -func documentedFieldCheck(foundKeys common.MapStr, knownKeys map[string]interface{}, omitFields []string) error { - for foundKey := range foundKeys { - if _, ok := knownKeys[foundKey]; !ok { - for _, omitField := range omitFields { - if omitDocumentedField(foundKey, omitField) { - return nil - } - } - // If a field is defined as object it can also be defined as `status_codes.*` - // So this checks if such a key with the * exists by removing the last part. - splits := strings.Split(foundKey, ".") - prefix := strings.Join(splits[0:len(splits)-1], ".") - if _, ok := knownKeys[prefix+".*"]; ok { - continue - } - return errors.Errorf("field missing '%s'", foundKey) - } - } - - return nil -} - -// omitDocumentedField returns true if 'field' is exactly like 'omitField' or if 'field' equals the prefix of 'omitField' -// if the latter contains a dot.wildcard ".*". For example: -// field: hello, omitField: world false -// field: hello, omitField: hello true -// field: elasticsearch.stats omitField: elasticsearch.stats true -// field: elasticsearch.stats.hello.world omitField: elasticsearch.* true -// field: elasticsearch.stats.hello.world omitField: * true -func omitDocumentedField(field, omitField string) bool { - if strings.Contains(omitField, "*") { - // Omit every key prefixed with chars before "*" - prefixedField := strings.Trim(omitField, ".*") - if strings.Contains(field, prefixedField) { - return true - } - } else { - // Omit only if key matches exactly - if field == omitField { - return true - } - } - - return false -} - -func TestOmitDocumentedField(t *testing.T) { - tts := []struct { - a, b string - result bool - }{ - {a: "hello", b: "world", result: false}, - {a: "hello", b: "hello", result: true}, - {a: "elasticsearch.stats", b: "elasticsearch.stats", result: true}, - {a: "elasticsearch.stats.hello.world", b: "elasticsearch.*", result: true}, - {a: "elasticsearch.stats.hello.world", b: "*", result: true}, - } - - for _, tt := range tts { - result := omitDocumentedField(tt.a, tt.b) - assert.Equal(t, tt.result, result) - } -} - -// GetConfig returns config for elasticsearch module -func getConfig(module, metricSet, url string, config Config) map[string]interface{} { - moduleConfig := map[string]interface{}{ - "module": module, - "metricsets": []string{metricSet}, - "hosts": []string{url}, - } - - for k, v := range config.Module { - moduleConfig[k] = v - } - - return moduleConfig -} - -// server starts a server with a mock output -func server(t *testing.T, path string, url string) *httptest.Server { - - body, err := ioutil.ReadFile(path) - if err != nil { - t.Fatalf("could not read file: %s", err) - } - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - query := "" - v := r.URL.Query() - if len(v) > 0 { - query += "?" + v.Encode() - } - - if r.URL.Path+query == url { - w.Header().Set("Content-Type", "application/json;") - w.WriteHeader(200) - w.Write(body) - } else { - w.WriteHeader(404) - } - })) - return server -} - func getModulesPath() string { return "../../../module" } - -func getModulePath(module string) string { - return getModulesPath() + "/" + module -} - -func getMetricsetPath(module, metricSet string) string { - return getModulePath(module) + "/" + metricSet -} diff --git a/metricbeat/mb/testing/testdata.go b/metricbeat/mb/testing/testdata.go new file mode 100644 index 000000000000..48dee7909371 --- /dev/null +++ b/metricbeat/mb/testing/testdata.go @@ -0,0 +1,406 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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 testing + +import ( + "encoding/json" + "flag" + "io/ioutil" + "net/http" + "net/http/httptest" + "path/filepath" + "sort" + "strings" + "testing" + + "github.com/pkg/errors" + + "github.com/mitchellh/hashstructure" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" + + "github.com/elastic/beats/libbeat/asset" + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/mapping" + "github.com/elastic/beats/metricbeat/mb" + + _ "github.com/elastic/beats/metricbeat/include/fields" +) + +const ( + expectedExtension = "-expected.json" +) + +var ( + // Use `go test -generate` to update files. + generateFlag = flag.Bool("generate", false, "Write golden files") +) + +// DataConfig is the configuration for testdata tests +// +// For example for an http service that mimics the apache status page the following +// configuration could be used: +// ``` +// type: http +// url: "/server-status?auto=" +// suffix: plain +// omit_documented_fields_check: +// - "apache.status.hostname" +// remove_fields_from_comparison: +// - "apache.status.hostname" +// module: +// namespace: test +// ``` +// A test will be run for each file with the `plain` extension in the same directory +// where a file with this configuration is placed. +type DataConfig struct { + // Path is the directory containing this configuration + Path string + + // WritePath is the path where to write the generated files + WritePath string + + // The type of the test to run, usually `http`. + Type string + + // URL of the endpoint that must be tested depending on each module + URL string + + // Suffix is the extension of the source file with the input contents. Defaults to `json`, `plain` is also a common use. + Suffix string + + // Module is a map of specific configs that will be appended to a module configuration prior initializing it. + // For example, the following config in yaml: + // module: + // namespace: test + // foo: bar + // + // Will produce the following module config: + // - module: http + // metricsets: + // - json + // period: 10s + // hosts: ["localhost:80"] + // path: "/" + // namespace: "test" + // foo: bar + // + // (notice last two lines) + Module map[string]interface{} `yaml:"module"` + + // OmitDocumentedFieldsCheck is a list of fields that must be omitted from the function that checks if the field + // is contained in {metricset}/_meta/fields.yml + OmitDocumentedFieldsCheck []string `yaml:"omit_documented_fields_check"` + + // RemoveFieldsForComparison + RemoveFieldsForComparison []string `yaml:"remove_fields_from_comparison"` +} + +func defaultDataConfig() DataConfig { + return DataConfig{ + Path: ".", + WritePath: ".", + Suffix: "json", + } +} + +// ReadDataConfig reads the testdataconfig from a path +func ReadDataConfig(t *testing.T, f string) DataConfig { + t.Helper() + config := defaultDataConfig() + config.Path = filepath.Dir(f) + config.WritePath = filepath.Dir(config.Path) + configFile, err := ioutil.ReadFile(f) + if err != nil { + t.Fatalf("failed to read '%s': %v", f, err) + } + err = yaml.Unmarshal(configFile, &config) + if err != nil { + t.Fatalf("failed to parse test configuration file '%s': %v", f, err) + } + return config +} + +// TestDataConfig is a convenience helper function to read the testdata config +// from the usual path +func TestDataConfig(t *testing.T) DataConfig { + t.Helper() + return ReadDataConfig(t, "_meta/testdata/config.yml") +} + +// TestDataFiles run tests with config from the usual path (`_meta/testdata`) +func TestDataFiles(t *testing.T, module, metricSet string) { + t.Helper() + config := TestDataConfig(t) + TestDataFilesWithConfig(t, module, metricSet, config) +} + +// TestDataFilesWithConfig run tests for a testdata config +func TestDataFilesWithConfig(t *testing.T, module, metricSet string, config DataConfig) { + t.Helper() + ff, err := filepath.Glob(filepath.Join(config.Path, "*."+config.Suffix)) + if err != nil { + t.Fatal(err) + } + if len(ff) == 0 { + t.Fatalf("test path with config but without data files: %s", config.Path) + } + + var files []string + for _, f := range ff { + // Exclude all the expected files + if strings.HasSuffix(f, expectedExtension) { + continue + } + files = append(files, f) + } + + for _, f := range files { + t.Run(filepath.Base(f), func(t *testing.T) { + runTest(t, f, module, metricSet, config) + }) + } +} + +func runTest(t *testing.T, file string, module, metricSetName string, config DataConfig) { + // starts a server serving the given file under the given url + s := server(t, file, config.URL) + defer s.Close() + + moduleConfig := getConfig(module, metricSetName, s.URL, config) + metricSet := NewMetricSet(t, moduleConfig) + + var events []mb.Event + var errs []error + + switch v := metricSet.(type) { + case mb.ReportingMetricSetV2: + metricSet := NewReportingMetricSetV2(t, moduleConfig) + events, errs = ReportingFetchV2(metricSet) + case mb.ReportingMetricSetV2Error: + metricSet := NewReportingMetricSetV2Error(t, moduleConfig) + events, errs = ReportingFetchV2Error(metricSet) + default: + t.Fatalf("unknown type: %T", v) + } + + // Gather errors to build also error events + for _, e := range errs { + // TODO: for errors strip out and standardise the URL error as it would create a different diff every time + events = append(events, mb.Event{Error: e}) + } + + var data []common.MapStr + + for _, e := range events { + beatEvent := StandardizeEvent(metricSet, e, mb.AddMetricSetInfo) + // Overwrite service.address as the port changes every time + beatEvent.Fields.Put("service.address", "127.0.0.1:55555") + data = append(data, beatEvent.Fields) + } + + // Sorting the events is necessary as events are not necessarily sent in the same order + sort.SliceStable(data, func(i, j int) bool { + h1, _ := hashstructure.Hash(data[i], nil) + h2, _ := hashstructure.Hash(data[j], nil) + return h1 < h2 + }) + + if err := checkDocumented(t, data, config.OmitDocumentedFieldsCheck); err != nil { + t.Errorf("%v: check if fields are documented in `metricbeat/%s/%s/_meta/fields.yml` "+ + "file or run 'make update' on Metricbeat folder to update fields in `metricbeat/fields.yml`", + err, module, metricSetName) + } + + // Overwrites the golden files if run with -generate + if *generateFlag { + outputIndented, err := json.MarshalIndent(&data, "", " ") + if err != nil { + t.Fatal(err) + } + if err = ioutil.WriteFile(file+expectedExtension, outputIndented, 0644); err != nil { + t.Fatal(err) + } + } + + // Read expected file + expected, err := ioutil.ReadFile(file + expectedExtension) + if err != nil { + t.Fatalf("could not read file: %s", err) + } + + expectedMap := []common.MapStr{} + if err := json.Unmarshal(expected, &expectedMap); err != nil { + t.Fatal(err) + } + + for _, fieldToRemove := range config.RemoveFieldsForComparison { + for eventIndex := range data { + if err := data[eventIndex].Delete(fieldToRemove); err != nil { + t.Fatal(err) + } + } + + for eventIndex := range expectedMap { + if err := expectedMap[eventIndex].Delete(fieldToRemove); err != nil { + t.Fatal(err) + } + } + } + + output, err := json.Marshal(&data) + if err != nil { + t.Fatal(err) + } + + expectedJSON, err := json.Marshal(&expectedMap) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, string(expectedJSON), string(output)) + + if strings.HasSuffix(file, "docs."+config.Suffix) { + writeDataJSON(t, data[0], filepath.Join(config.WritePath, "data.json")) + } +} + +func writeDataJSON(t *testing.T, data common.MapStr, path string) { + // Add hardcoded timestamp + data.Put("@timestamp", "2019-03-01T08:05:34.853Z") + output, err := json.MarshalIndent(&data, "", " ") + if err = ioutil.WriteFile(path, output, 0644); err != nil { + t.Fatal(err) + } +} + +// checkDocumented checks that all fields which show up in the events are documented +func checkDocumented(t *testing.T, data []common.MapStr, omitFields []string) error { + fieldsData, err := asset.GetFields("metricbeat") + if err != nil { + return err + } + + fields, err := mapping.LoadFields(fieldsData) + if err != nil { + return err + } + + documentedFields := fields.GetKeys() + keys := map[string]interface{}{} + + for _, k := range documentedFields { + keys[k] = struct{}{} + } + + for _, d := range data { + flat := d.Flatten() + if err := documentedFieldCheck(flat, keys, omitFields); err != nil { + return err + } + } + + return nil +} + +func documentedFieldCheck(foundKeys common.MapStr, knownKeys map[string]interface{}, omitFields []string) error { + for foundKey := range foundKeys { + if _, ok := knownKeys[foundKey]; !ok { + for _, omitField := range omitFields { + if omitDocumentedField(foundKey, omitField) { + return nil + } + } + // If a field is defined as object it can also be defined as `status_codes.*` + // So this checks if such a key with the * exists by removing the last part. + splits := strings.Split(foundKey, ".") + prefix := strings.Join(splits[0:len(splits)-1], ".") + if _, ok := knownKeys[prefix+".*"]; ok { + continue + } + return errors.Errorf("field missing '%s'", foundKey) + } + } + + return nil +} + +// omitDocumentedField returns true if 'field' is exactly like 'omitField' or if 'field' equals the prefix of 'omitField' +// if the latter contains a dot.wildcard ".*". For example: +// field: hello, omitField: world false +// field: hello, omitField: hello true +// field: elasticsearch.stats omitField: elasticsearch.stats true +// field: elasticsearch.stats.hello.world omitField: elasticsearch.* true +// field: elasticsearch.stats.hello.world omitField: * true +func omitDocumentedField(field, omitField string) bool { + if strings.Contains(omitField, "*") { + // Omit every key prefixed with chars before "*" + prefixedField := strings.Trim(omitField, ".*") + if strings.Contains(field, prefixedField) { + return true + } + } else { + // Omit only if key matches exactly + if field == omitField { + return true + } + } + + return false +} + +// getConfig returns config for elasticsearch module +func getConfig(module, metricSet, url string, config DataConfig) map[string]interface{} { + moduleConfig := map[string]interface{}{ + "module": module, + "metricsets": []string{metricSet}, + "hosts": []string{url}, + } + + for k, v := range config.Module { + moduleConfig[k] = v + } + + return moduleConfig +} + +// server starts a server with a mock output +func server(t *testing.T, path string, url string) *httptest.Server { + + body, err := ioutil.ReadFile(path) + if err != nil { + t.Fatalf("could not read file: %s", err) + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + query := "" + v := r.URL.Query() + if len(v) > 0 { + query += "?" + v.Encode() + } + + if r.URL.Path+query == url { + w.Header().Set("Content-Type", "application/json;") + w.WriteHeader(200) + w.Write(body) + } else { + w.WriteHeader(404) + } + })) + return server +} diff --git a/metricbeat/mb/testing/testdata_test.go b/metricbeat/mb/testing/testdata_test.go new file mode 100644 index 000000000000..39cbf5cf8f3a --- /dev/null +++ b/metricbeat/mb/testing/testdata_test.go @@ -0,0 +1,42 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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 testing + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestOmitDocumentedField(t *testing.T) { + tts := []struct { + a, b string + result bool + }{ + {a: "hello", b: "world", result: false}, + {a: "hello", b: "hello", result: true}, + {a: "elasticsearch.stats", b: "elasticsearch.stats", result: true}, + {a: "elasticsearch.stats.hello.world", b: "elasticsearch.*", result: true}, + {a: "elasticsearch.stats.hello.world", b: "*", result: true}, + } + + for _, tt := range tts { + result := omitDocumentedField(tt.a, tt.b) + assert.Equal(t, tt.result, result) + } +} diff --git a/metricbeat/module/apache/status/_meta/testdata/docs.plain-expected.json b/metricbeat/module/apache/status/_meta/testdata/docs.plain-expected.json index cfc827d23a4f..df2d090668fe 100644 --- a/metricbeat/module/apache/status/_meta/testdata/docs.plain-expected.json +++ b/metricbeat/module/apache/status/_meta/testdata/docs.plain-expected.json @@ -19,7 +19,7 @@ "system": 0.2, "user": 0.16 }, - "hostname": "127.0.0.1:43985", + "hostname": "127.0.0.1:34545", "load": { "1": 2, "15": 1.91, diff --git a/metricbeat/module/apache/status/status_test.go b/metricbeat/module/apache/status/status_test.go index 6c07ef14f369..e0eba14252a2 100644 --- a/metricbeat/module/apache/status/status_test.go +++ b/metricbeat/module/apache/status/status_test.go @@ -35,6 +35,8 @@ import ( mbtest "github.com/elastic/beats/metricbeat/mb/testing" "github.com/stretchr/testify/assert" + + _ "github.com/elastic/beats/metricbeat/module/apache" ) // response is a raw response copied from an Apache web server. @@ -269,3 +271,7 @@ func TestStatusOutputs(t *testing.T) { assert.NoError(t, err, "error mapping "+filename) } } + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "apache", "status") +} diff --git a/metricbeat/module/ceph/monitor_health/monitor_health_test.go b/metricbeat/module/ceph/monitor_health/monitor_health_test.go index 93c8e3bac0e1..6790e258f077 100644 --- a/metricbeat/module/ceph/monitor_health/monitor_health_test.go +++ b/metricbeat/module/ceph/monitor_health/monitor_health_test.go @@ -28,6 +28,8 @@ import ( mbtest "github.com/elastic/beats/metricbeat/mb/testing" "github.com/stretchr/testify/assert" + + _ "github.com/elastic/beats/metricbeat/module/ceph" ) func TestFetchEventContents(t *testing.T) { @@ -88,3 +90,7 @@ func TestFetchEventContents(t *testing.T) { total = store_stats["total"].(common.MapStr) assert.EqualValues(t, 8488943, total["bytes"]) } + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "ceph", "monitor_health") +} diff --git a/metricbeat/module/couchbase/cluster/cluster_test.go b/metricbeat/module/couchbase/cluster/cluster_test.go new file mode 100644 index 000000000000..0733e9fbc7f4 --- /dev/null +++ b/metricbeat/module/couchbase/cluster/cluster_test.go @@ -0,0 +1,32 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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. + +// +build !integration + +package cluster + +import ( + "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + + _ "github.com/elastic/beats/metricbeat/module/couchbase" +) + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "couchbase", "cluster") +} diff --git a/metricbeat/module/couchbase/node/node_test.go b/metricbeat/module/couchbase/node/node_test.go new file mode 100644 index 000000000000..4245bd3589c3 --- /dev/null +++ b/metricbeat/module/couchbase/node/node_test.go @@ -0,0 +1,32 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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. + +// +build !integration + +package node + +import ( + "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + + _ "github.com/elastic/beats/metricbeat/module/couchbase" +) + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "couchbase", "node") +} diff --git a/metricbeat/module/dropwizard/collector/collector_test.go b/metricbeat/module/dropwizard/collector/collector_test.go new file mode 100644 index 000000000000..ff0e7a41e0d2 --- /dev/null +++ b/metricbeat/module/dropwizard/collector/collector_test.go @@ -0,0 +1,32 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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. + +// +build !integration + +package collector + +import ( + "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + + _ "github.com/elastic/beats/metricbeat/module/dropwizard" +) + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "dropwizard", "collector") +} diff --git a/metricbeat/module/etcd/metrics/metrics_integration_test.go b/metricbeat/module/etcd/metrics/metrics_integration_test.go index 35a7e64e4d5a..9127df2e83ba 100644 --- a/metricbeat/module/etcd/metrics/metrics_integration_test.go +++ b/metricbeat/module/etcd/metrics/metrics_integration_test.go @@ -45,21 +45,6 @@ func TestFetch(t *testing.T) { t.Logf("%s/%s event: %+v", f.Module().Name(), f.Name(), events[0]) } -func TestData(t *testing.T) { - compose.EnsureUp(t, "etcd") - - f := mbtest.NewReportingMetricSetV2(t, getConfig()) - events, errs := mbtest.ReportingFetchV2(f) - if len(errs) > 0 { - t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) - } - assert.NotEmpty(t, events) - - if err := mbtest.WriteEventsReporterV2(f, t, ""); err != nil { - t.Fatal("write", err) - } -} - func getConfig() map[string]interface{} { return map[string]interface{}{ "module": "etcd", diff --git a/metricbeat/module/etcd/metrics/metrics_test.go b/metricbeat/module/etcd/metrics/metrics_test.go index 9dfc5888f30d..68198bdf49a2 100644 --- a/metricbeat/module/etcd/metrics/metrics_test.go +++ b/metricbeat/module/etcd/metrics/metrics_test.go @@ -23,6 +23,9 @@ import ( "testing" "github.com/elastic/beats/metricbeat/helper/prometheus/ptest" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + _ "github.com/elastic/beats/metricbeat/module/etcd" ) const testFile = "_meta/test/metrics" @@ -37,3 +40,7 @@ func TestEventMapping(t *testing.T) { }, ) } + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "etcd", "metrics") +} diff --git a/metricbeat/module/http/json/json_test.go b/metricbeat/module/http/json/json_test.go new file mode 100644 index 000000000000..b8f21b4ae9e7 --- /dev/null +++ b/metricbeat/module/http/json/json_test.go @@ -0,0 +1,32 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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. + +// +build !integration + +package json + +import ( + "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + + _ "github.com/elastic/beats/metricbeat/module/http" +) + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "http", "json") +} diff --git a/metricbeat/module/kibana/status/status_test.go b/metricbeat/module/kibana/status/status_test.go new file mode 100644 index 000000000000..8bfb45fa6c6e --- /dev/null +++ b/metricbeat/module/kibana/status/status_test.go @@ -0,0 +1,32 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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. + +// +build !integration + +package status + +import ( + "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + + _ "github.com/elastic/beats/metricbeat/module/kibana" +) + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "kibana", "status") +} diff --git a/metricbeat/module/kubernetes/apiserver/apiserver_test.go b/metricbeat/module/kubernetes/apiserver/apiserver_test.go index 58e521f40254..ddb96e4b1396 100644 --- a/metricbeat/module/kubernetes/apiserver/apiserver_test.go +++ b/metricbeat/module/kubernetes/apiserver/apiserver_test.go @@ -23,6 +23,9 @@ import ( "testing" "github.com/elastic/beats/metricbeat/helper/prometheus/ptest" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + _ "github.com/elastic/beats/metricbeat/module/kubernetes" ) const testFile = "_meta/test/metrics" @@ -37,3 +40,7 @@ func TestEventMapping(t *testing.T) { }, ) } + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "kubernetes", "apiserver") +} diff --git a/metricbeat/module/kubernetes/state_container/state_container_test.go b/metricbeat/module/kubernetes/state_container/state_container_test.go index 8d738ac75499..0770bb56f58d 100644 --- a/metricbeat/module/kubernetes/state_container/state_container_test.go +++ b/metricbeat/module/kubernetes/state_container/state_container_test.go @@ -23,6 +23,9 @@ import ( "testing" "github.com/elastic/beats/metricbeat/helper/prometheus/ptest" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + _ "github.com/elastic/beats/metricbeat/module/kubernetes" ) func TestEventMapping(t *testing.T) { @@ -39,3 +42,7 @@ func TestEventMapping(t *testing.T) { }, ) } + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "kubernetes", "state_container") +} diff --git a/metricbeat/module/kubernetes/state_deployment/state_deployment_test.go b/metricbeat/module/kubernetes/state_deployment/state_deployment_test.go index ead3a67a61eb..9f418d5c3369 100644 --- a/metricbeat/module/kubernetes/state_deployment/state_deployment_test.go +++ b/metricbeat/module/kubernetes/state_deployment/state_deployment_test.go @@ -30,6 +30,8 @@ import ( mbtest "github.com/elastic/beats/metricbeat/mb/testing" "github.com/stretchr/testify/assert" + + _ "github.com/elastic/beats/metricbeat/module/kubernetes" ) const testFile = "../_meta/test/kube-state-metrics" @@ -143,3 +145,7 @@ func testCases() map[string]map[string]interface{} { }, } } + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "kubernetes", "state_deployment") +} diff --git a/metricbeat/module/kubernetes/state_node/state_node_test.go b/metricbeat/module/kubernetes/state_node/state_node_test.go index e624b4bc0402..3d17ccf33479 100644 --- a/metricbeat/module/kubernetes/state_node/state_node_test.go +++ b/metricbeat/module/kubernetes/state_node/state_node_test.go @@ -23,6 +23,9 @@ import ( "testing" "github.com/elastic/beats/metricbeat/helper/prometheus/ptest" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + _ "github.com/elastic/beats/metricbeat/module/kubernetes" ) func TestEventMapping(t *testing.T) { @@ -39,3 +42,7 @@ func TestEventMapping(t *testing.T) { }, ) } + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "kubernetes", "state_node") +} diff --git a/metricbeat/module/kubernetes/state_pod/state_pod_test.go b/metricbeat/module/kubernetes/state_pod/state_pod_test.go index 923d738ea083..59f6d418ee6f 100644 --- a/metricbeat/module/kubernetes/state_pod/state_pod_test.go +++ b/metricbeat/module/kubernetes/state_pod/state_pod_test.go @@ -30,6 +30,8 @@ import ( mbtest "github.com/elastic/beats/metricbeat/mb/testing" "github.com/stretchr/testify/assert" + + _ "github.com/elastic/beats/metricbeat/module/kubernetes" ) const testFile = "../_meta/test/kube-state-metrics" @@ -130,3 +132,7 @@ func testCases() map[string]map[string]interface{} { }, } } + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "kubernetes", "state_pod") +} diff --git a/metricbeat/module/kubernetes/state_replicaset/state_replicaset_test.go b/metricbeat/module/kubernetes/state_replicaset/state_replicaset_test.go index c215860f2db2..9ed17a401aa3 100644 --- a/metricbeat/module/kubernetes/state_replicaset/state_replicaset_test.go +++ b/metricbeat/module/kubernetes/state_replicaset/state_replicaset_test.go @@ -30,6 +30,8 @@ import ( mbtest "github.com/elastic/beats/metricbeat/mb/testing" "github.com/stretchr/testify/assert" + + _ "github.com/elastic/beats/metricbeat/module/kubernetes" ) const testFile = "../_meta/test/kube-state-metrics" @@ -125,3 +127,7 @@ func testCases() map[string]map[string]interface{} { }, } } + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "kubernetes", "state_replicaset") +} diff --git a/metricbeat/module/kubernetes/state_statefulset/state_statefulset_test.go b/metricbeat/module/kubernetes/state_statefulset/state_statefulset_test.go index 3989dd8150dc..f19cc3326b9f 100644 --- a/metricbeat/module/kubernetes/state_statefulset/state_statefulset_test.go +++ b/metricbeat/module/kubernetes/state_statefulset/state_statefulset_test.go @@ -30,6 +30,8 @@ import ( "github.com/elastic/beats/libbeat/common" mbtest "github.com/elastic/beats/metricbeat/mb/testing" + + _ "github.com/elastic/beats/metricbeat/module/kubernetes" ) const testFile = "../_meta/test/kube-state-metrics" @@ -123,3 +125,7 @@ func testCases() map[string]map[string]interface{} { }, } } + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "kubernetes", "state_statefulset") +} diff --git a/metricbeat/module/php_fpm/pool/pool_test.go b/metricbeat/module/php_fpm/pool/pool_test.go new file mode 100644 index 000000000000..026cb72c04b8 --- /dev/null +++ b/metricbeat/module/php_fpm/pool/pool_test.go @@ -0,0 +1,32 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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. + +// +build !integration + +package pool + +import ( + "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + + _ "github.com/elastic/beats/metricbeat/module/php_fpm" +) + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "php_fpm", "pool") +} diff --git a/metricbeat/module/php_fpm/process/process_test.go b/metricbeat/module/php_fpm/process/process_test.go new file mode 100644 index 000000000000..490a3bd2c201 --- /dev/null +++ b/metricbeat/module/php_fpm/process/process_test.go @@ -0,0 +1,32 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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. + +// +build !integration + +package process + +import ( + "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + + _ "github.com/elastic/beats/metricbeat/module/php_fpm" +) + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "php_fpm", "process") +} diff --git a/metricbeat/module/prometheus/collector/collector_test.go b/metricbeat/module/prometheus/collector/collector_test.go index f87ecfe58467..d8a96b925175 100644 --- a/metricbeat/module/prometheus/collector/collector_test.go +++ b/metricbeat/module/prometheus/collector/collector_test.go @@ -22,11 +22,14 @@ package collector import ( "testing" - "github.com/elastic/beats/libbeat/common" - "github.com/golang/protobuf/proto" dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/assert" + + "github.com/elastic/beats/libbeat/common" + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + + _ "github.com/elastic/beats/metricbeat/module/prometheus" ) func TestGetPromEventsFromMetricFamily(t *testing.T) { @@ -196,3 +199,7 @@ func TestGetPromEventsFromMetricFamily(t *testing.T) { assert.Equal(t, test.Event, event) } } + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "prometheus", "collector") +} diff --git a/metricbeat/module/rabbitmq/connection/connection_test.go b/metricbeat/module/rabbitmq/connection/connection_test.go index 198b6a577cc4..9dac8fb0661d 100644 --- a/metricbeat/module/rabbitmq/connection/connection_test.go +++ b/metricbeat/module/rabbitmq/connection/connection_test.go @@ -25,6 +25,8 @@ import ( "github.com/elastic/beats/metricbeat/module/rabbitmq/mtest" "github.com/stretchr/testify/assert" + + _ "github.com/elastic/beats/metricbeat/module/rabbitmq" ) func TestFetchEventContents(t *testing.T) { @@ -72,3 +74,7 @@ func getConfig(url string) map[string]interface{} { "hosts": []string{url}, } } + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "rabbitmq", "connection") +} diff --git a/metricbeat/module/traefik/health/health_test.go b/metricbeat/module/traefik/health/health_test.go index bec941dffea1..a0de08d6a7fa 100644 --- a/metricbeat/module/traefik/health/health_test.go +++ b/metricbeat/module/traefik/health/health_test.go @@ -30,6 +30,8 @@ import ( "github.com/elastic/beats/libbeat/common" mbtest "github.com/elastic/beats/metricbeat/mb/testing" + + _ "github.com/elastic/beats/metricbeat/module/traefik" ) func TestFetchEventContents(t *testing.T) { @@ -72,3 +74,7 @@ func TestFetchEventContents(t *testing.T) { assert.EqualValues(t, 17, statusCodes["200"]) assert.EqualValues(t, 1, statusCodes["404"]) } + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "traefik", "health") +} diff --git a/x-pack/metricbeat/module/coredns/stats/stats_test.go b/x-pack/metricbeat/module/coredns/stats/stats_test.go new file mode 100644 index 000000000000..31c7384de4d6 --- /dev/null +++ b/x-pack/metricbeat/module/coredns/stats/stats_test.go @@ -0,0 +1,19 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// +build !integration + +package stats + +import ( + "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + + _ "github.com/elastic/beats/x-pack/metricbeat/module/coredns" +) + +func TestData(t *testing.T) { + mbtest.TestDataFiles(t, "coredns", "stats") +}