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

[approvaltest] Ignore dynamic fields from sort fields #61

Merged
merged 2 commits into from
Jun 7, 2024
Merged
Changes from 1 commit
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
89 changes: 43 additions & 46 deletions pkg/approvaltest/approvals.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"os"
"path/filepath"
"sort"
"strings"
"testing"

"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -53,16 +54,16 @@ const (
func ApproveEvents(t testing.TB, name string, hits []espoll.SearchHit, dynamic ...string) {
t.Helper()

// Sort events for repeatable diffs.
sort.Slice(hits, func(i, j int) bool {
return compareDocumentFields(hits[i].RawFields, hits[j].RawFields) < 0
})

sources := make([][]byte, len(hits))
for i, hit := range hits {
sources[i] = hit.RawSource
}
approveEventDocs(t, filepath.Join("approvals", name), sources, dynamic...)
rewriteDynamic(t, sources, dynamic...)
// Rewrite dynamic fields and sort them for repeatable diffs.
sort.Slice(sources, func(i, j int) bool {
return compareDocumentFields(sources[i], sources[j]) < 0
})
approveEventDocs(t, filepath.Join("approvals", name), sources)
}

// ApproveFields compares the fields of the search hits with the
Expand All @@ -79,26 +80,21 @@ func ApproveEvents(t testing.TB, name string, hits []espoll.SearchHit, dynamic .
func ApproveFields(t testing.TB, name string, hits []espoll.SearchHit, dynamic ...string) {
t.Helper()

// Sort events for repeatable diffs.
sort.Slice(hits, func(i, j int) bool {
return compareDocumentFields(hits[i].RawFields, hits[j].RawFields) < 0
})

fields := make([][]byte, len(hits))
for i, hit := range hits {
fields[i] = hit.RawFields
}
approveFields(t, filepath.Join("approvals", name), fields, dynamic...)
// Rewrite dynamic fields and sort them for repeatable diffs.
rewriteDynamic(t, fields, dynamic...)
sort.Slice(fields, func(i, j int) bool {
return compareDocumentFields(fields[i], fields[j]) < 0
})
approveFields(t, filepath.Join("approvals", name), fields)
}

// approveEventDocs compares the given event documents with
// the contents of the file in "<name>.approved.json".
//
// Any specified dynamic fields (e.g. @timestamp, observer.id)
// will be replaced with a static string for comparison.
//
// If the events differ, then the test will fail.
func approveEventDocs(t testing.TB, name string, eventDocs [][]byte, dynamic ...string) {
// rewriteDynamic rewrites all dynamic fields to have a known value, so dynamic
// fields don't affect diffs.
func rewriteDynamic(t testing.TB, srcs [][]byte, dynamic ...string) {
t.Helper()

// Fields generated by the server (e.g. observer.*)
Expand All @@ -115,22 +111,42 @@ func approveEventDocs(t testing.TB, name string, eventDocs [][]byte, dynamic ...
"observer.version",
}, dynamic...)

// Rewrite all dynamic fields to have a known value,
// so dynamic fields don't affect diffs.
events := make([]interface{}, len(eventDocs))
for i, doc := range eventDocs {
for i := range srcs {
for _, field := range dynamic {
existing := gjson.GetBytes(doc, field)
field = strings.ReplaceAll(field, ".", "\\.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this going to work for _source as well as fields? We might need to switch based on whether it's ApproveEvents vs. ApproveFields

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@axw You are right, it wouldn't have worked with ApproveEvents, I have fixed it now. Thanks for catching this.

existing := gjson.GetBytes(srcs[i], field)
if !existing.Exists() {
continue
}

var v interface{}
if existing.IsArray() {
v = []any{"dynamic"}
} else {
v = "dynamic"
}

var err error
doc, err = sjson.SetBytes(doc, field, "dynamic")
srcs[i], err = sjson.SetBytes(srcs[i], field, v)
if err != nil {
t.Fatal(err)
}
}
}
}

// approveEventDocs compares the given event documents with
// the contents of the file in "<name>.approved.json".
//
// Any specified dynamic fields (e.g. @timestamp, observer.id)
// will be replaced with a static string for comparison.
//
// If the events differ, then the test will fail.
func approveEventDocs(t testing.TB, name string, eventDocs [][]byte) {
t.Helper()

events := make([]interface{}, len(eventDocs))
for i, doc := range eventDocs {

var event map[string]interface{}
if err := json.Unmarshal(doc, &event); err != nil {
Expand All @@ -143,23 +159,9 @@ func approveEventDocs(t testing.TB, name string, eventDocs [][]byte, dynamic ...
approve(t, name, received)
}

func approveFields(t testing.TB, name string, docs [][]byte, dynamic ...string) {
func approveFields(t testing.TB, name string, docs [][]byte) {
t.Helper()

// Fields generated by the server (e.g. observer.*)
// agent which may change between tests.
//
// Ignore their values in comparisons, but compare
// existence: either the field exists in both, or neither.
dynamic = append([]string{
"ecs.version",
"event.ingested",
"observer.ephemeral_id",
"observer.hostname",
"observer.id",
"observer.version",
}, dynamic...)

// Rewrite all dynamic fields to have a known value,
// so dynamic fields don't affect diffs.
decodedDocs := make([]any, len(docs))
Expand All @@ -168,11 +170,6 @@ func approveFields(t testing.TB, name string, docs [][]byte, dynamic ...string)
if err := json.Unmarshal(doc, &fields); err != nil {
t.Fatal(err)
}
for _, field := range dynamic {
if _, ok := fields[field]; ok {
fields[field] = []any{"dynamic"}
}
}
decodedDocs[i] = fields
}

Expand Down