From d3c7681096985230f0c4acc286abd116899a904c Mon Sep 17 00:00:00 2001 From: Dominic Breuker Date: Tue, 6 Mar 2018 09:42:10 +0100 Subject: [PATCH] add specs for logging and proc scanner --- internal/logging/logging.go | 21 +++++++- internal/logging/logging_test.go | 34 ++++++++---- internal/pspy/pspy.go | 18 ++----- internal/psscanner/proclist.go | 2 +- internal/psscanner/proclist_test.go | 84 +++++++++++++++++++++++++++++ 5 files changed, 134 insertions(+), 25 deletions(-) diff --git a/internal/logging/logging.go b/internal/logging/logging.go index 188cb0c..23ab6f0 100644 --- a/internal/logging/logging.go +++ b/internal/logging/logging.go @@ -1,10 +1,17 @@ package logging import ( + "fmt" "log" "os" ) +const ( + ColorNone = iota + ColorRed + ColorGreen +) + type Logger struct { infoLogger *log.Logger errorLogger *log.Logger @@ -30,6 +37,16 @@ func (l *Logger) Errorf(format string, v ...interface{}) { } // Eventf writes an event with timestamp to stdout -func (l *Logger) Eventf(format string, v ...interface{}) { - l.eventLogger.Printf(format, v...) +func (l *Logger) Eventf(color int, format string, v ...interface{}) { + msg := fmt.Sprintf(format, v...) + + switch color { + case ColorRed: + msg = fmt.Sprintf("\x1b[31;1m%s\x1b[0m", msg) + case ColorGreen: + msg = fmt.Sprintf("\x1b[32;1m%s\x1b[0m", msg) + default: + } + + l.eventLogger.Printf("%s", msg) } diff --git a/internal/logging/logging_test.go b/internal/logging/logging_test.go index dca8fa0..f70230c 100644 --- a/internal/logging/logging_test.go +++ b/internal/logging/logging_test.go @@ -3,11 +3,15 @@ package logging import ( "bytes" "log" + "reflect" "regexp" "testing" ) const dateFormatPattern = `[\d]{4}/[\d]{2}/[\d]{2} [\d]{2}:[\d]{2}:[\d]{2}` +const ansiPattern = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))" + +var ansiMatcher = regexp.MustCompile(ansiPattern) var l = NewLogger() @@ -15,27 +19,39 @@ var logTests = []struct { logger *log.Logger test func() expectation string + colors [][]byte }{ - {l.infoLogger, func() { l.Infof("Info message no. %d", 1) }, "Info message no. 1\n"}, - {l.infoLogger, func() { l.Infof("Info message no. %d with a string %s\n", 2, "appended to it") }, "Info message no. 2 with a string appended to it\n"}, - {l.errorLogger, func() { l.Errorf("Error message") }, "Error message\n"}, - {l.errorLogger, func() { l.Errorf("Error message\n") }, "Error message\n"}, - {l.eventLogger, func() { l.Eventf("Event message") }, dateFormatPattern + " Event message\n"}, + {l.infoLogger, func() { l.Infof("Info message no. %d", 1) }, "Info message no. 1\n", nil}, + {l.infoLogger, func() { l.Infof("Info message no. %d with a string %s\n", 2, "appended to it") }, "Info message no. 2 with a string appended to it\n", nil}, + {l.errorLogger, func() { l.Errorf("Error message") }, "Error message\n", nil}, + {l.errorLogger, func() { l.Errorf("Error message\n") }, "Error message\n", nil}, + {l.eventLogger, func() { l.Eventf(ColorNone, "Event message") }, dateFormatPattern + " Event message\n", nil}, + {l.eventLogger, func() { l.Eventf(ColorRed, "Event message") }, dateFormatPattern + " Event message\n", [][]byte{[]byte("\x1b[31;1m"), []byte("\x1b[0m")}}, + {l.eventLogger, func() { l.Eventf(ColorGreen, "Event message") }, dateFormatPattern + " Event message\n", [][]byte{[]byte("\x1b[32;1m"), []byte("\x1b[0m")}}, } func TestLogging(t *testing.T) { for i, tt := range logTests { actual := captureOutput(tt.logger, tt.test) + + // check colors and remove afterwards + colors := ansiMatcher.FindAll(actual, 2) + if !reflect.DeepEqual(colors, tt.colors) { + t.Errorf("[%d] Wrong colors: got %+v but want %+v", i, colors, tt.colors) + } + actual = ansiMatcher.ReplaceAll(actual, []byte("")) + + // check contents matcher := regexp.MustCompile(tt.expectation) - if !matcher.Match([]byte(actual)) { - t.Fatalf("[%d] Wrong message logged!: %s", i, actual) + if !matcher.Match(actual) { + t.Errorf("[%d] Wrong message logged!: got %s but wanted %v", i, actual, matcher) } } } -func captureOutput(logger *log.Logger, f func()) string { +func captureOutput(logger *log.Logger, f func()) []byte { var buf bytes.Buffer logger.SetOutput(&buf) f() - return buf.String() + return buf.Bytes() } diff --git a/internal/pspy/pspy.go b/internal/pspy/pspy.go index b0ba1a9..0416efa 100644 --- a/internal/pspy/pspy.go +++ b/internal/pspy/pspy.go @@ -5,6 +5,7 @@ import ( "time" "github.com/dominicbreuker/pspy/internal/config" + "github.com/dominicbreuker/pspy/internal/logging" ) type Bindings struct { @@ -16,7 +17,7 @@ type Bindings struct { type Logger interface { Infof(format string, v ...interface{}) Errorf(format string, v ...interface{}) - Eventf(format string, v ...interface{}) + Eventf(color int, format string, v ...interface{}) } type FSWatcher interface { @@ -48,15 +49,15 @@ func Start(cfg *config.Config, b *Bindings, sigCh chan os.Signal) chan struct{} for { select { case se := <-sigCh: - b.Logger.Infof("Exiting program... (%s)\n", se) + b.Logger.Infof("Exiting program... (%s)", se) exit <- struct{}{} case fe := <-fsEventCh: if cfg.LogFS { - b.Logger.Eventf("FS: %+v\n", fe) + b.Logger.Eventf(logging.ColorGreen, "FS: %+v", fe) } case pe := <-psEventCh: if cfg.LogPS { - b.Logger.Eventf("CMD: %+v\n", pe) + b.Logger.Eventf(logging.ColorRed, "CMD: %+v", pe) } } } @@ -110,12 +111,3 @@ func drainEventsFor(triggerCh chan struct{}, eventCh chan string, d time.Duratio } } } - -// func refresh(in *inotify.Inotify, pl *process.ProcList, print bool) { -// in.Pause() -// if err := pl.Refresh(print); err != nil { -// log.Printf("ERROR refreshing process list: %v", err) -// } -// time.Sleep(5 * time.Millisecond) -// in.UnPause() -// } diff --git a/internal/psscanner/proclist.go b/internal/psscanner/proclist.go index fbe9418..443e500 100644 --- a/internal/psscanner/proclist.go +++ b/internal/psscanner/proclist.go @@ -42,7 +42,7 @@ func (pl procList) refresh(eventCh chan string) error { if err != nil { uid = "???" } - eventCh <- fmt.Sprintf("CMD: UID=%-4s PID=%-6d | %s", uid, pid, cmd) + eventCh <- fmt.Sprintf("UID=%-4s PID=%-6d | %s", uid, pid, cmd) // if print { // log.Printf("\x1b[31;1mCMD: UID=%-4s PID=%-6d | %s\x1b[0m\n", uid, pid, cmd) // } diff --git a/internal/psscanner/proclist_test.go b/internal/psscanner/proclist_test.go index 004202a..4628268 100644 --- a/internal/psscanner/proclist_test.go +++ b/internal/psscanner/proclist_test.go @@ -171,3 +171,87 @@ func mockProcStatusReader(stat []byte, err error) (restore func()) { } // refresh + +func TestRefresh(t *testing.T) { + tests := []struct { + eventCh chan string + pl procList + newPids []int + pidsAfter []int + events []string + }{ + {eventCh: make(chan string), pl: procList{}, newPids: []int{1, 2, 3}, pidsAfter: []int{3, 2, 1}, events: []string{ + "UID=??? PID=3 | the-command", + "UID=??? PID=2 | the-command", + "UID=??? PID=1 | the-command", + }}, + {eventCh: make(chan string), pl: procList{1: "pid-found-before"}, newPids: []int{1, 2, 3}, pidsAfter: []int{1, 3, 2}, events: []string{ + "UID=??? PID=3 | the-command", + "UID=??? PID=2 | the-command", + }}, // no events emitted for PIDs already known + } + + for _, tt := range tests { + restoreGetPIDs := mockPidList(tt.newPids) + restoreCmdLineReader := mockCmdLineReader([]byte("the-command"), nil) + restoreProcStatusReader := mockProcStatusReader([]byte(""), nil) // don't mock read value since it's not worth it + + events := make([]string, 0) + done := make(chan struct{}) + go func() { + for e := range tt.eventCh { + events = append(events, e) + } + done <- struct{}{} + }() + tt.pl.refresh(tt.eventCh) + close(tt.eventCh) + <-done + + restoreProcStatusReader() + restoreCmdLineReader() + restoreGetPIDs() + + pidsAfter := getPids(&tt.pl) + + for _, pid := range tt.pidsAfter { + if !contains(pidsAfter, pid) { + t.Errorf("PID %d should be in list %v but was not!", pid, pidsAfter) + } + } + for _, pid := range pidsAfter { + if !contains(tt.pidsAfter, pid) { + t.Errorf("PID %d should be in list %v but was not!", pid, pidsAfter) + } + } + if !reflect.DeepEqual(events, tt.events) { + t.Errorf("Wrong events returned: got %v but want %v", events, tt.events) + } + } +} + +func contains(list []int, v int) bool { + for _, i := range list { + if i == v { + return true + } + } + return false +} + +func mockPidList(pids []int) func() { + dirs := make([]os.FileInfo, 0) + for _, pid := range pids { + dirs = append(dirs, newMockDir(fmt.Sprintf("%d", pid))) + } + restore := mockProcDirReader(dirs, nil) + return restore +} + +func getPids(pl *procList) []int { + pids := make([]int, 0) + for pid := range *pl { + pids = append(pids, pid) + } + return pids +}