Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add moduleFlag, omitDocumentedFieldsCheck and ModuleConfig to http testing framework #11660

Merged
merged 4 commits into from
Apr 8, 2019
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 89 additions & 17 deletions metricbeat/mb/testing/data/data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,41 @@ const (
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 {
Type string
URL string
Suffix string
// The type of the test to run, usually `http`.
Type string
sayden marked this conversation as resolved.
Show resolved Hide resolved

// 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"`
sayden marked this conversation as resolved.
Show resolved Hide resolved
}

func TestAll(t *testing.T) {
Expand All @@ -83,13 +112,18 @@ func TestAll(t *testing.T) {
config.Suffix = "json"
}

getTestdataFiles(t, config.URL, moduleName, metricSetName, config.Suffix)
getTestdataFiles(t, moduleName, metricSetName, config)
sayden marked this conversation as resolved.
Show resolved Hide resolved
}
}

func getTestdataFiles(t *testing.T, url, module, metricSet, suffix string) {
func getTestdataFiles(t *testing.T, module, metricSet string, config Config) {
if *moduleFlag != "" {
sayden marked this conversation as resolved.
Show resolved Hide resolved
if *moduleFlag != module {
return
}
}

ff, err := filepath.Glob(getMetricsetPath(module, metricSet) + "/_meta/testdata/*." + suffix)
ff, err := filepath.Glob(getMetricsetPath(module, metricSet) + "/_meta/testdata/*." + config.Suffix)
if err != nil {
t.Fatal(err)
}
Expand All @@ -105,28 +139,29 @@ func getTestdataFiles(t *testing.T, url, module, metricSet, suffix string) {

for _, f := range files {
t.Run(f, func(t *testing.T) {
runTest(t, f, module, metricSet, url, suffix)
runTest(t, f, module, metricSet, config)
})
}
}

func runTest(t *testing.T, file string, module, metricSetName, url, suffix string) {
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, url)
s := server(t, file, config.URL)
defer s.Close()

metricSet := mbtesting.NewMetricSet(t, getConfig(module, metricSetName, s.URL))
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, getConfig(module, metricSetName, s.URL))
metricSet := mbtesting.NewReportingMetricSetV2(t, moduleConfig)
events, errs = mbtesting.ReportingFetchV2(metricSet)
case mb.ReportingMetricSetV2Error:
metricSet := mbtesting.NewReportingMetricSetV2Error(t, getConfig(module, metricSetName, s.URL))
metricSet := mbtesting.NewReportingMetricSetV2Error(t, moduleConfig)
events, errs = mbtesting.ReportingFetchV2Error(metricSet)
default:
t.Fatalf("unknown type: %T", v)
Expand Down Expand Up @@ -154,7 +189,7 @@ func runTest(t *testing.T, file string, module, metricSetName, url, suffix strin
return h1 < h2
})

checkDocumented(t, data)
checkDocumented(t, data, config.OmitDocumentedFieldsCheck)

output, err := json.MarshalIndent(&data, "", " ")
if err != nil {
Expand All @@ -176,7 +211,7 @@ func runTest(t *testing.T, file string, module, metricSetName, url, suffix strin

assert.Equal(t, string(expected), string(output))

if strings.HasSuffix(file, "docs."+suffix) {
if strings.HasSuffix(file, "docs."+config.Suffix) {
writeDataJSON(t, data[0], module, metricSetName)
}
}
Expand All @@ -191,7 +226,7 @@ func writeDataJSON(t *testing.T, data common.MapStr, module, metricSet string) {
}

// checkDocumented checks that all fields which show up in the events are documented
func checkDocumented(t *testing.T, data []common.MapStr) {
func checkDocumented(t *testing.T, data []common.MapStr, omitFields []string) {
fieldsData, err := asset.GetFields("metricbeat")
if err != nil {
t.Fatal(err)
Expand All @@ -201,6 +236,7 @@ func checkDocumented(t *testing.T, data []common.MapStr) {
if err != nil {
t.Fatal(err)
}

documentedFields := fields.GetKeys()
keys := map[string]interface{}{}

Expand All @@ -210,8 +246,14 @@ func checkDocumented(t *testing.T, data []common.MapStr) {

for _, d := range data {
flat := d.Flatten()
keys:
for k := range flat {
if _, ok := keys[k]; !ok {
for _, omitField := range omitFields {
sayden marked this conversation as resolved.
Show resolved Hide resolved
if omitDocumentedField(k, omitField) {
continue keys
}
}
// 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(k, ".")
Expand All @@ -225,13 +267,43 @@ func checkDocumented(t *testing.T, data []common.MapStr) {
}
}

// 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:
sayden marked this conversation as resolved.
Show resolved Hide resolved
// 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 "*"
sayden marked this conversation as resolved.
Show resolved Hide resolved
prefixedField := strings.Trim(omitField, ".*")
if strings.Contains(field, prefixedField) {
return true
}
} else {
//Omit only if key matches exactly
sayden marked this conversation as resolved.
Show resolved Hide resolved
if field == omitField {
return true
}
}

return false
}

// GetConfig returns config for elasticsearch module
func getConfig(module, metricSet, url string) map[string]interface{} {
return map[string]interface{}{
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
Expand Down