Files
percona-toolkit/src/go/pt-galera-log-explainer/regex/regex_test.go
2023-12-22 23:10:34 +03:00

171 lines
5.8 KiB
Go

package regex
import (
"io/ioutil"
"os/exec"
"testing"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/percona/percona-toolkit/src/go/pt-galera-log-explainer/translate"
"github.com/percona/percona-toolkit/src/go/pt-galera-log-explainer/types"
"github.com/percona/percona-toolkit/src/go/pt-galera-log-explainer/utils"
"github.com/pkg/errors"
)
type regexTestState struct {
State string
LogCtx types.LogCtx
HashToNodeNames map[string]string
HashToIP map[string]string
IPToMethods map[string]string
IPToNodeNames map[string]string
}
type regexTest struct {
name string
log, expectedOut string
input regexTestState
expected regexTestState
displayerExpectedNil bool
expectedErr bool
key string
}
func iterateRegexTest(t *testing.T, regexmap types.RegexMap, tests []regexTest) {
utils.SkipColor = true
for _, test := range tests {
if test.name == "" {
test.name = "default"
}
// Test 1
// it's defined in maps
if _, ok := regexmap[test.key]; !ok {
t.Fatalf("key %s does not exist in maps", test.key)
}
// Add the "input" translations
// some regexes will react on current state of translations
translate.ResetDB()
for hash, nodename := range test.input.HashToNodeNames {
translate.AddHashToNodeName(hash, nodename, time.Time{})
}
for hash, ip := range test.input.HashToIP {
translate.AddHashToIP(hash, ip, time.Time{})
}
for ip, methods := range test.input.IPToMethods {
translate.AddIPToMethod(ip, methods, time.Time{})
}
for ip, nodename := range test.input.IPToNodeNames {
translate.AddIPToNodeName(ip, nodename, time.Time{})
}
// Test 2
// Launch the test against grep on tmp file
err := testRegexFromMap(t, test.log, regexmap[test.key])
if err != nil {
if test.expectedErr {
continue
}
t.Fatalf("key: %s\ntestname: %s\nregex string: \"%s\"\nlog: %s\n", test.key, test.name, regexmap[test.key].Regex.String(), err)
}
if test.input.State != "" {
test.input.LogCtx.SetState(test.input.State)
}
// Test 3
// Get the message to display using the input context
logCtx, displayer := regexmap[test.key].Handle(test.input.LogCtx, test.log, time.Time{})
msg := ""
if displayer != nil {
msg = displayer(logCtx)
} else if !test.displayerExpectedNil {
t.Errorf("key: %s\ntestname: %s\ndisplayer is nil\nexpected: not nil", test.key, test.name)
}
// Test 4
// Making sure the updated context is what we expect
// alternative to reflect.deepequal, it enables to avoid comparing "states" map
res := cmp.Equal(logCtx, test.expected.LogCtx, cmpopts.IgnoreUnexported(types.LogCtx{}))
if !res || logCtx.State() != test.expected.State {
t.Errorf("context is not as expected: \nkey: %s\ntestname: %s\nlogCtx: %+v\nexpected logCtx: %+v\nout: %s\nexpected out: %s\nstate: %s\nexpected state: %s", test.key, test.name, spew.Sdump(logCtx), spew.Sdump(test.expected.LogCtx), msg, test.expectedOut, logCtx.State(), test.expected.State)
t.Fail()
}
// Test 5
// Making sure the displayed message is correct
if msg != test.expectedOut {
t.Errorf("displayed message is not as expected: \nkey: %s\ntestname: %s\nlogCtx: %+v\nout: %s\nexpected out: %s\nstate: %s", test.key, test.name, spew.Sdump(logCtx), msg, test.expectedOut, logCtx.State())
t.Fail()
}
// Test 6
// Making sure the translations map have the expected values
// There is a limitation here: "extra" translations stored will not be seen
maxTime, _ := time.Parse(time.RFC3339, "2100-01-01T01:01:01Z")
for hash, expectedValue := range test.expected.HashToNodeNames {
if value := translate.GetNodeNameFromHash(hash, maxTime); value != expectedValue {
t.Errorf("wrong HashToNodeNames\ntest key: %s\ntestname: %s\nlogCtx: %+v\nout: %s\nhash: %s\nexpectedValue: %s\nvalue: %s", test.key, test.name, spew.Sdump(logCtx), msg, hash, expectedValue, value)
t.Fail()
}
}
for hash, expectedValue := range test.expected.HashToIP {
if value := translate.GetIPFromHash(hash); value != expectedValue {
t.Errorf("wrong HashToIP\ntest key: %s\ntestname: %s\nlogCtx: %+v\nout: %s\nhash: %s\nexpectedValue: %s\nvalue: %s", test.key, test.name, spew.Sdump(logCtx), msg, hash, expectedValue, value)
t.Fail()
}
}
for ip, expectedValue := range test.expected.IPToMethods {
if value := translate.GetMethodFromIP(ip, maxTime); value != expectedValue {
t.Errorf("wrong IPToMethods\ntest key: %s\ntestname: %s\nlogCtx: %+v\nout: %s\nhash: %s\nexpectedValue: %s\nvalue: %s", test.key, test.name, spew.Sdump(logCtx), msg, ip, expectedValue, value)
t.Fail()
}
}
for ip, expectedValue := range test.expected.IPToNodeNames {
if value := translate.GetNodeNameFromIP(ip, maxTime); value != expectedValue {
t.Errorf("wrong IPToNodeNames\ntest key: %s\ntestname: %s\nlogCtx: %+v\nout: %s\nhash: %s\nexpectedValue: %s\nvalue: %s", test.key, test.name, spew.Sdump(logCtx), msg, ip, expectedValue, value)
t.Fail()
}
}
}
}
func timeMustParse(s string) *time.Time {
t, _, _ := SearchDateFromLog(s)
return &t
}
func testRegexFromMap(t *testing.T, log string, regex *types.LogRegex) error {
m := types.RegexMap{"test": regex}
return testActualGrepOnLog(t, log, m.Compile()[0])
}
func testActualGrepOnLog(t *testing.T, log, regex string) error {
f, err := ioutil.TempFile(t.TempDir(), "test_log")
if err != nil {
return errors.Wrap(err, "failed to create tmp file")
}
defer f.Sync()
_, err = f.WriteString(log)
if err != nil {
return errors.Wrap(err, "failed to write in tmp file")
}
out, err := exec.Command("grep", "-P", regex, f.Name()).Output()
if err != nil {
return errors.Wrap(err, "failed to grep in tmp file")
}
if string(out) == "" {
return errors.Wrap(err, "empty results when grepping in tmp file")
}
return nil
}