350 lines
10 KiB
Go
350 lines
10 KiB
Go
package integration_tests_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const (
|
|
infoLog = "this is a info log line"
|
|
warningLog = "this is a warning log line"
|
|
errorLog = "this is a error log line"
|
|
fatalLog = "this is a fatal log line"
|
|
)
|
|
|
|
// res is a type alias to a slice of pointers to regular expressions.
|
|
type res = []*regexp.Regexp
|
|
|
|
var (
|
|
infoLogRE = regexp.MustCompile(regexp.QuoteMeta(infoLog))
|
|
warningLogRE = regexp.MustCompile(regexp.QuoteMeta(warningLog))
|
|
errorLogRE = regexp.MustCompile(regexp.QuoteMeta(errorLog))
|
|
fatalLogRE = regexp.MustCompile(regexp.QuoteMeta(fatalLog))
|
|
|
|
stackTraceRE = regexp.MustCompile(`\ngoroutine \d+ \[[^]]+\]:\n`)
|
|
|
|
allLogREs = res{infoLogRE, warningLogRE, errorLogRE, fatalLogRE, stackTraceRE}
|
|
|
|
defaultExpectedInDirREs = map[int]res{
|
|
0: {stackTraceRE, fatalLogRE, errorLogRE, warningLogRE, infoLogRE},
|
|
1: {stackTraceRE, fatalLogRE, errorLogRE, warningLogRE},
|
|
2: {stackTraceRE, fatalLogRE, errorLogRE},
|
|
3: {stackTraceRE, fatalLogRE},
|
|
}
|
|
|
|
defaultNotExpectedInDirREs = map[int]res{
|
|
0: {},
|
|
1: {infoLogRE},
|
|
2: {infoLogRE, warningLogRE},
|
|
3: {infoLogRE, warningLogRE, errorLogRE},
|
|
}
|
|
)
|
|
|
|
func TestDestinationsWithDifferentFlags(t *testing.T) {
|
|
tests := map[string]struct {
|
|
// logfile states if the flag -log_file should be set
|
|
logfile bool
|
|
// logdir states if the flag -log_dir should be set
|
|
logdir bool
|
|
// flags is for additional flags to pass to the klog'ed executable
|
|
flags []string
|
|
|
|
// expectedLogFile states if we generally expect the log file to exist.
|
|
// If this is not set, we expect the file not to exist and will error if it
|
|
// does.
|
|
expectedLogFile bool
|
|
// expectedLogDir states if we generally expect the log files in the log
|
|
// dir to exist.
|
|
// If this is not set, we expect the log files in the log dir not to exist and
|
|
// will error if they do.
|
|
expectedLogDir bool
|
|
|
|
// expectedOnStderr is a list of REs we expect to find on stderr
|
|
expectedOnStderr res
|
|
// notExpectedOnStderr is a list of REs that we must not find on stderr
|
|
notExpectedOnStderr res
|
|
// expectedInFile is a list of REs we expect to find in the log file
|
|
expectedInFile res
|
|
// notExpectedInFile is a list of REs we must not find in the log file
|
|
notExpectedInFile res
|
|
|
|
// expectedInDir is a list of REs we expect to find in the log files in the
|
|
// log dir, specified by log severity (0 = warning, 1 = info, ...)
|
|
expectedInDir map[int]res
|
|
// notExpectedInDir is a list of REs we must not find in the log files in
|
|
// the log dir, specified by log severity (0 = warning, 1 = info, ...)
|
|
notExpectedInDir map[int]res
|
|
}{
|
|
"default flags": {
|
|
// Everything, including the trace on fatal, goes to stderr
|
|
|
|
expectedOnStderr: allLogREs,
|
|
},
|
|
"everything disabled": {
|
|
// Nothing, including the trace on fatal, is showing anywhere
|
|
|
|
flags: []string{"-logtostderr=false", "-alsologtostderr=false", "-stderrthreshold=1000"},
|
|
|
|
notExpectedOnStderr: allLogREs,
|
|
},
|
|
"everything disabled but low stderrthreshold": {
|
|
// Everything above -stderrthreshold, including the trace on fatal, will
|
|
// be logged to stderr, even if we set -logtostderr to false.
|
|
|
|
flags: []string{"-logtostderr=false", "-alsologtostderr=false", "-stderrthreshold=1"},
|
|
|
|
expectedOnStderr: res{warningLogRE, errorLogRE, stackTraceRE},
|
|
notExpectedOnStderr: res{infoLogRE},
|
|
},
|
|
"with logtostderr only": {
|
|
// Everything, including the trace on fatal, goes to stderr
|
|
|
|
flags: []string{"-logtostderr=true", "-alsologtostderr=false", "-stderrthreshold=1000"},
|
|
|
|
expectedOnStderr: allLogREs,
|
|
},
|
|
"with log file only": {
|
|
// Everything, including the trace on fatal, goes to the single log file
|
|
|
|
logfile: true,
|
|
flags: []string{"-logtostderr=false", "-alsologtostderr=false", "-stderrthreshold=1000"},
|
|
|
|
expectedLogFile: true,
|
|
|
|
notExpectedOnStderr: allLogREs,
|
|
expectedInFile: allLogREs,
|
|
},
|
|
"with log dir only": {
|
|
// Everything, including the trace on fatal, goes to the log files in the log dir
|
|
|
|
logdir: true,
|
|
flags: []string{"-logtostderr=false", "-alsologtostderr=false", "-stderrthreshold=1000"},
|
|
|
|
expectedLogDir: true,
|
|
|
|
notExpectedOnStderr: allLogREs,
|
|
expectedInDir: defaultExpectedInDirREs,
|
|
notExpectedInDir: defaultNotExpectedInDirREs,
|
|
},
|
|
"with log dir and logtostderr": {
|
|
// Everything, including the trace on fatal, goes to stderr. The -log_dir is
|
|
// ignored, nothing goes to the log files in the log dir.
|
|
|
|
logdir: true,
|
|
flags: []string{"-logtostderr=true", "-alsologtostderr=false", "-stderrthreshold=1000"},
|
|
|
|
expectedOnStderr: allLogREs,
|
|
},
|
|
"with log file and log dir": {
|
|
// Everything, including the trace on fatal, goes to the single log file.
|
|
// The -log_dir is ignored, nothing goes to the log file in the log dir.
|
|
|
|
logdir: true,
|
|
logfile: true,
|
|
flags: []string{"-logtostderr=false", "-alsologtostderr=false", "-stderrthreshold=1000"},
|
|
|
|
expectedLogFile: true,
|
|
|
|
notExpectedOnStderr: allLogREs,
|
|
expectedInFile: allLogREs,
|
|
},
|
|
"with log file and alsologtostderr": {
|
|
// Everything, including the trace on fatal, goes to the single log file
|
|
// AND to stderr.
|
|
|
|
flags: []string{"-alsologtostderr=true", "-logtostderr=false", "-stderrthreshold=1000"},
|
|
logfile: true,
|
|
|
|
expectedLogFile: true,
|
|
|
|
expectedOnStderr: allLogREs,
|
|
expectedInFile: allLogREs,
|
|
},
|
|
"with log dir and alsologtostderr": {
|
|
// Everything, including the trace on fatal, goes to the log file in the
|
|
// log dir AND to stderr.
|
|
|
|
logdir: true,
|
|
flags: []string{"-alsologtostderr=true", "-logtostderr=false", "-stderrthreshold=1000"},
|
|
|
|
expectedLogDir: true,
|
|
|
|
expectedOnStderr: allLogREs,
|
|
expectedInDir: defaultExpectedInDirREs,
|
|
notExpectedInDir: defaultNotExpectedInDirREs,
|
|
},
|
|
}
|
|
|
|
binaryFileExtention := ""
|
|
if runtime.GOOS == "windows" {
|
|
binaryFileExtention = ".exe"
|
|
}
|
|
|
|
for tcName, tc := range tests {
|
|
tc := tc
|
|
t.Run(tcName, func(t *testing.T) {
|
|
t.Parallel()
|
|
withTmpDir(t, func(logdir string) {
|
|
// :: Setup
|
|
flags := tc.flags
|
|
stderr := &bytes.Buffer{}
|
|
logfile := filepath.Join(logdir, "the_single_log_file") // /some/tmp/dir/the_single_log_file
|
|
|
|
if tc.logfile {
|
|
flags = append(flags, "-log_file="+logfile)
|
|
}
|
|
if tc.logdir {
|
|
flags = append(flags, "-log_dir="+logdir)
|
|
}
|
|
|
|
// :: Execute
|
|
klogRun(t, flags, stderr)
|
|
|
|
// :: Assert
|
|
// check stderr
|
|
checkForLogs(t, tc.expectedOnStderr, tc.notExpectedOnStderr, stderr.String(), "stderr")
|
|
|
|
// check log_file
|
|
if tc.expectedLogFile {
|
|
content := getFileContent(t, logfile)
|
|
checkForLogs(t, tc.expectedInFile, tc.notExpectedInFile, content, "logfile")
|
|
} else {
|
|
assertFileIsAbsent(t, logfile)
|
|
}
|
|
|
|
// check files in log_dir
|
|
for level, levelName := range logFileLevels {
|
|
binaryName := "main" + binaryFileExtention
|
|
logfile, err := getLogFilePath(logdir, binaryName, levelName)
|
|
if tc.expectedLogDir {
|
|
if err != nil {
|
|
t.Errorf("Unable to find log file: %v", err)
|
|
}
|
|
content := getFileContent(t, logfile)
|
|
checkForLogs(t, tc.expectedInDir[level], tc.notExpectedInDir[level], content, "logfile["+logfile+"]")
|
|
} else {
|
|
if err == nil {
|
|
t.Errorf("Unexpectedly found log file %s", logfile)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
const klogExampleGoFile = "./internal/main.go"
|
|
|
|
// klogRun spawns a simple executable that uses klog, to later inspect its
|
|
// stderr and potentially created log files
|
|
func klogRun(t *testing.T, flags []string, stderr io.Writer) {
|
|
callFlags := []string{"run", klogExampleGoFile}
|
|
callFlags = append(callFlags, flags...)
|
|
|
|
cmd := exec.Command("go", callFlags...)
|
|
cmd.Stderr = stderr
|
|
cmd.Env = append(os.Environ(),
|
|
"KLOG_INFO_LOG="+infoLog,
|
|
"KLOG_WARNING_LOG="+warningLog,
|
|
"KLOG_ERROR_LOG="+errorLog,
|
|
"KLOG_FATAL_LOG="+fatalLog,
|
|
)
|
|
|
|
err := cmd.Run()
|
|
|
|
if _, ok := err.(*exec.ExitError); !ok {
|
|
t.Fatalf("Run failed: %v", err)
|
|
}
|
|
}
|
|
|
|
var logFileLevels = map[int]string{
|
|
0: "INFO",
|
|
1: "WARNING",
|
|
2: "ERROR",
|
|
3: "FATAL",
|
|
}
|
|
|
|
func getFileContent(t *testing.T, filePath string) string {
|
|
content, err := ioutil.ReadFile(filePath)
|
|
if err != nil {
|
|
t.Errorf("Could not read file '%s': %v", filePath, err)
|
|
}
|
|
return string(content)
|
|
}
|
|
|
|
func assertFileIsAbsent(t *testing.T, filePath string) {
|
|
if _, err := os.Stat(filePath); !os.IsNotExist(err) {
|
|
t.Errorf("Expected file '%s' not to exist", filePath)
|
|
}
|
|
}
|
|
|
|
func checkForLogs(t *testing.T, expected, disallowed res, content, name string) {
|
|
for _, re := range expected {
|
|
checkExpected(t, true, name, content, re)
|
|
}
|
|
for _, re := range disallowed {
|
|
checkExpected(t, false, name, content, re)
|
|
}
|
|
}
|
|
|
|
func checkExpected(t *testing.T, expected bool, where string, haystack string, needle *regexp.Regexp) {
|
|
found := needle.MatchString(haystack)
|
|
|
|
if expected && !found {
|
|
t.Errorf("Expected to find '%s' in %s", needle, where)
|
|
}
|
|
if !expected && found {
|
|
t.Errorf("Expected not to find '%s' in %s", needle, where)
|
|
}
|
|
}
|
|
|
|
func withTmpDir(t *testing.T, f func(string)) {
|
|
tmpDir, err := ioutil.TempDir("", "klog_e2e_")
|
|
if err != nil {
|
|
t.Fatalf("Could not create temp directory: %v", err)
|
|
}
|
|
defer func() {
|
|
if err := os.RemoveAll(tmpDir); err != nil {
|
|
t.Fatalf("Could not remove temp directory '%s': %v", tmpDir, err)
|
|
}
|
|
}()
|
|
|
|
f(tmpDir)
|
|
}
|
|
|
|
// getLogFileFromDir returns the path of either the symbolic link to the logfile, or the the logfile itself. This must
|
|
// be done as the creation of a symlink is not guaranteed on any platform. On Windows, only users with administration
|
|
// privileges can create a symlink.
|
|
func getLogFilePath(dir, binaryName, levelName string) (string, error) {
|
|
symlink := filepath.Join(dir, binaryName+"."+levelName)
|
|
if _, err := os.Stat(symlink); err == nil {
|
|
return symlink, nil
|
|
}
|
|
files, err := ioutil.ReadDir(dir)
|
|
if err != nil {
|
|
return "", fmt.Errorf("could not read directory %s: %v", dir, err)
|
|
}
|
|
var foundFile string
|
|
for _, file := range files {
|
|
if strings.HasPrefix(file.Name(), binaryName) && strings.Contains(file.Name(), levelName) {
|
|
if foundFile != "" {
|
|
return "", fmt.Errorf("found multiple matching files")
|
|
}
|
|
foundFile = file.Name()
|
|
}
|
|
}
|
|
if foundFile != "" {
|
|
return filepath.Join(dir, foundFile), nil
|
|
}
|
|
return "", fmt.Errorf("file missing from directory")
|
|
}
|