mirror of
https://github.com/DominicBreuker/pspy.git
synced 2025-12-21 11:44:51 +00:00
add specs for logging and proc scanner
This commit is contained in:
@@ -1,10 +1,17 @@
|
|||||||
package logging
|
package logging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ColorNone = iota
|
||||||
|
ColorRed
|
||||||
|
ColorGreen
|
||||||
|
)
|
||||||
|
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
infoLogger *log.Logger
|
infoLogger *log.Logger
|
||||||
errorLogger *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
|
// Eventf writes an event with timestamp to stdout
|
||||||
func (l *Logger) Eventf(format string, v ...interface{}) {
|
func (l *Logger) Eventf(color int, format string, v ...interface{}) {
|
||||||
l.eventLogger.Printf(format, v...)
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,15 @@ package logging
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"log"
|
"log"
|
||||||
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dateFormatPattern = `[\d]{4}/[\d]{2}/[\d]{2} [\d]{2}:[\d]{2}:[\d]{2}`
|
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()
|
var l = NewLogger()
|
||||||
|
|
||||||
@@ -15,27 +19,39 @@ var logTests = []struct {
|
|||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
test func()
|
test func()
|
||||||
expectation string
|
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", 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"},
|
{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"},
|
{l.errorLogger, func() { l.Errorf("Error message") }, "Error message\n", nil},
|
||||||
{l.errorLogger, func() { l.Errorf("Error message\n") }, "Error message\n"},
|
{l.errorLogger, func() { l.Errorf("Error message\n") }, "Error message\n", nil},
|
||||||
{l.eventLogger, func() { l.Eventf("Event message") }, dateFormatPattern + " Event message\n"},
|
{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) {
|
func TestLogging(t *testing.T) {
|
||||||
for i, tt := range logTests {
|
for i, tt := range logTests {
|
||||||
actual := captureOutput(tt.logger, tt.test)
|
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)
|
matcher := regexp.MustCompile(tt.expectation)
|
||||||
if !matcher.Match([]byte(actual)) {
|
if !matcher.Match(actual) {
|
||||||
t.Fatalf("[%d] Wrong message logged!: %s", i, 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
|
var buf bytes.Buffer
|
||||||
logger.SetOutput(&buf)
|
logger.SetOutput(&buf)
|
||||||
f()
|
f()
|
||||||
return buf.String()
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dominicbreuker/pspy/internal/config"
|
"github.com/dominicbreuker/pspy/internal/config"
|
||||||
|
"github.com/dominicbreuker/pspy/internal/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bindings struct {
|
type Bindings struct {
|
||||||
@@ -16,7 +17,7 @@ type Bindings struct {
|
|||||||
type Logger interface {
|
type Logger interface {
|
||||||
Infof(format string, v ...interface{})
|
Infof(format string, v ...interface{})
|
||||||
Errorf(format string, v ...interface{})
|
Errorf(format string, v ...interface{})
|
||||||
Eventf(format string, v ...interface{})
|
Eventf(color int, format string, v ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
type FSWatcher interface {
|
type FSWatcher interface {
|
||||||
@@ -48,15 +49,15 @@ func Start(cfg *config.Config, b *Bindings, sigCh chan os.Signal) chan struct{}
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case se := <-sigCh:
|
case se := <-sigCh:
|
||||||
b.Logger.Infof("Exiting program... (%s)\n", se)
|
b.Logger.Infof("Exiting program... (%s)", se)
|
||||||
exit <- struct{}{}
|
exit <- struct{}{}
|
||||||
case fe := <-fsEventCh:
|
case fe := <-fsEventCh:
|
||||||
if cfg.LogFS {
|
if cfg.LogFS {
|
||||||
b.Logger.Eventf("FS: %+v\n", fe)
|
b.Logger.Eventf(logging.ColorGreen, "FS: %+v", fe)
|
||||||
}
|
}
|
||||||
case pe := <-psEventCh:
|
case pe := <-psEventCh:
|
||||||
if cfg.LogPS {
|
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()
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func (pl procList) refresh(eventCh chan string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
uid = "???"
|
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 {
|
// if print {
|
||||||
// log.Printf("\x1b[31;1mCMD: UID=%-4s PID=%-6d | %s\x1b[0m\n", uid, pid, cmd)
|
// log.Printf("\x1b[31;1mCMD: UID=%-4s PID=%-6d | %s\x1b[0m\n", uid, pid, cmd)
|
||||||
// }
|
// }
|
||||||
|
|||||||
@@ -171,3 +171,87 @@ func mockProcStatusReader(stat []byte, err error) (restore func()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// refresh
|
// 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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user