diff --git a/cmd/root.go b/cmd/root.go index dcb8565..c01e489 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,10 +4,14 @@ import ( "fmt" "log" "os" + "os/signal" "strings" + "syscall" "github.com/dominicbreuker/pspy/internal/config" - "github.com/dominicbreuker/pspy/internal/logger" + "github.com/dominicbreuker/pspy/internal/inotify" + "github.com/dominicbreuker/pspy/internal/logging" + "github.com/dominicbreuker/pspy/internal/process" "github.com/dominicbreuker/pspy/internal/pspy" "github.com/spf13/cobra" ) @@ -71,9 +75,19 @@ func root(cmd *cobra.Command, args []string) { LogPS: logPS, LogFS: logFS, } - logger := logger.NewLogger() - pspy.Start(cfg, logger) - // pspy.Watch(rDirs, dirs, logPS, logFS) + logger := logging.NewLogger() + iw := inotify.NewInotifyWatcher() + pscan := process.NewProcfsScanner() + + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) + + exit, err := pspy.Start(cfg, logger, iw, pscan, sigCh) + if err != nil { + os.Exit(1) + } + <-exit + os.Exit(0) } func Execute() { diff --git a/internal/inotify/inotify.go b/internal/inotify/inotify.go index 9fbcec8..90a3d55 100644 --- a/internal/inotify/inotify.go +++ b/internal/inotify/inotify.go @@ -6,6 +6,18 @@ import ( "golang.org/x/sys/unix" ) +type InotifyWatcher struct{} + +func NewInotifyWatcher() *InotifyWatcher { + return &InotifyWatcher{} +} + +func (i *InotifyWatcher) Setup(rdirs, dirs []string) (chan struct{}, chan string, error) { + triggerCh := make(chan struct{}) + fsEventCh := make(chan string) + return triggerCh, fsEventCh, nil +} + type Inotify struct { fd int watchers map[int]*watcher diff --git a/internal/logger/logger.go b/internal/logging/logging.go similarity index 86% rename from internal/logger/logger.go rename to internal/logging/logging.go index 7e4aca9..188cb0c 100644 --- a/internal/logger/logger.go +++ b/internal/logging/logging.go @@ -1,18 +1,16 @@ -package logger +package logging import ( "log" "os" ) -// Logger is the logger used to print to the command line type Logger struct { infoLogger *log.Logger errorLogger *log.Logger eventLogger *log.Logger } -// NewLogger creates a new logger instance func NewLogger() *Logger { return &Logger{ infoLogger: log.New(os.Stdout, "", 0), diff --git a/internal/logger/logger_test.go b/internal/logging/logging_test.go similarity index 98% rename from internal/logger/logger_test.go rename to internal/logging/logging_test.go index 8d19451..dca8fa0 100644 --- a/internal/logger/logger_test.go +++ b/internal/logging/logging_test.go @@ -1,4 +1,4 @@ -package logger +package logging import ( "bytes" diff --git a/internal/process/process.go b/internal/process/process.go index 75077cb..8fae7d6 100644 --- a/internal/process/process.go +++ b/internal/process/process.go @@ -6,8 +6,20 @@ import ( "log" "strconv" "strings" + "time" ) +type ProcfsScanner struct{} + +func NewProcfsScanner() *ProcfsScanner { + return &ProcfsScanner{} +} + +func (p *ProcfsScanner) Setup(triggerCh chan struct{}, interval time.Duration) (chan string, error) { + psEventCh := make(chan string) + return psEventCh, nil +} + type ProcList map[int]string func NewProcList() *ProcList { diff --git a/internal/pspy/pspy.go b/internal/pspy/pspy.go index 54d9df9..79303ab 100644 --- a/internal/pspy/pspy.go +++ b/internal/pspy/pspy.go @@ -1,61 +1,66 @@ package pspy import ( + "errors" "fmt" "log" "os" - "os/signal" - "syscall" "time" "github.com/dominicbreuker/pspy/internal/config" "github.com/dominicbreuker/pspy/internal/inotify" - "github.com/dominicbreuker/pspy/internal/logger" "github.com/dominicbreuker/pspy/internal/process" "github.com/dominicbreuker/pspy/internal/walker" ) -func Start(cfg config.Config, logger *logger.Logger) { - fmt.Printf("Config: %+v\n", cfg) +type Logger interface { + Infof(format string, v ...interface{}) + Errorf(format string, v ...interface{}) + Eventf(format string, v ...interface{}) +} - triggerCh, fsEventCh, err := setupInotify(cfg.RDirs, cfg.Dirs) +type InotifyWatcher interface { + Setup(rdirs, dirs []string) (chan struct{}, chan string, error) +} + +type ProcfsScanner interface { + Setup(triggerCh chan struct{}, interval time.Duration) (chan string, error) +} + +func Start(cfg config.Config, logger Logger, inotify InotifyWatcher, pscan ProcfsScanner, sigCh chan os.Signal) (chan struct{}, error) { + logger.Infof("Config: %+v\n", cfg) + + triggerCh, fsEventCh, err := inotify.Setup(cfg.RDirs, cfg.Dirs) if err != nil { logger.Errorf("Can't set up inotify watchers: %v\n", err) + return nil, errors.New("inotify error") } - psEventCh, err := setupProcfsScanner(triggerCh, 100*time.Millisecond) + psEventCh, err := pscan.Setup(triggerCh, 100*time.Millisecond) if err != nil { logger.Errorf("Can't set up procfs scanner: %+v\n", err) + return nil, errors.New("procfs scanner error") } - sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) + exit := make(chan struct{}) - for { - select { - case se := <-sigCh: - logger.Infof("Exiting program... (%s)\n", se) - os.Exit(0) - case fe := <-fsEventCh: - if cfg.LogFS { - logger.Eventf("FS: %+v\n", fe) - } - case pe := <-psEventCh: - if cfg.LogPS { - logger.Eventf("CMD: %+v\n", pe) + go func() { + for { + select { + case se := <-sigCh: + logger.Infof("Exiting program... (%s)\n", se) + exit <- struct{}{} + case fe := <-fsEventCh: + if cfg.LogFS { + logger.Eventf("FS: %+v\n", fe) + } + case pe := <-psEventCh: + if cfg.LogPS { + logger.Eventf("CMD: %+v\n", pe) + } } } - } -} - -func setupInotify(rdirs, dirs []string) (chan struct{}, chan string, error) { - triggerCh := make(chan struct{}) - fsEventCh := make(chan string) - return triggerCh, fsEventCh, nil -} - -func setupProcfsScanner(triggerCh chan struct{}, interval time.Duration) (chan string, error) { - psEventCh := make(chan string) - return psEventCh, nil + }() + return exit, nil } const MaxInt = int(^uint(0) >> 1) diff --git a/internal/pspy/pspy_test.go b/internal/pspy/pspy_test.go new file mode 100644 index 0000000..dc9f0f2 --- /dev/null +++ b/internal/pspy/pspy_test.go @@ -0,0 +1,133 @@ +package pspy + +import ( + "fmt" + "os" + "syscall" + "testing" + "time" + + "github.com/dominicbreuker/pspy/internal/config" +) + +func TestStart(t *testing.T) { + cfg := config.Config{ + RDirs: []string{"rdir"}, + Dirs: []string{"dir"}, + LogFS: true, + LogPS: true, + } + mockLogger := newMockLogger() + mockIW := newMockInotifyWatcher(nil) + mockPS := newMockProcfsScanner(nil) + sigCh := make(chan os.Signal) + + exit, err := Start(cfg, mockLogger, mockIW, mockPS, sigCh) + if err != nil { + t.Fatalf("Unexpcted error: %v", err) + } + mockIW.fsEventCh <- "some fs event" + expectMsg(t, mockLogger.Event, "FS: some fs event\n") + + mockPS.psEventCh <- "some ps event" + expectMsg(t, mockLogger.Event, "CMD: some ps event\n") + + sigCh <- syscall.SIGINT + expectExit(t, exit) +} + +func expectMsg(t *testing.T, ch chan string, msg string) { + select { + case received := <-ch: + if received != msg { + t.Fatalf("Wanted to receive %s but got %s", msg, received) + } + case <-time.After(500 * time.Millisecond): + t.Fatalf("Did not receive message in time. Wanted: %s", msg) + } +} + +func expectExit(t *testing.T, ch chan struct{}) { + select { + case <-ch: + return + case <-time.After(500 * time.Millisecond): + t.Fatalf("Did not receive exit signal in time") + } +} + +// ##### Mocks ##### + +// Logger + +type mockLogger struct { + Info chan string + Error chan string + Event chan string +} + +func newMockLogger() *mockLogger { + return &mockLogger{ + Info: make(chan string, 10), + Error: make(chan string, 10), + Event: make(chan string, 10), + } +} + +func (l *mockLogger) Infof(format string, v ...interface{}) { + l.Info <- fmt.Sprintf(format, v...) +} + +func (l *mockLogger) Errorf(format string, v ...interface{}) { + l.Error <- fmt.Sprintf(format, v...) +} + +func (l *mockLogger) Eventf(format string, v ...interface{}) { + l.Event <- fmt.Sprintf(format, v...) +} + +// InotfiyWatcher + +type mockInotifyWatcher struct { + triggerCh chan struct{} + fsEventCh chan string + setupErr error +} + +func newMockInotifyWatcher(setupErr error) *mockInotifyWatcher { + return &mockInotifyWatcher{ + triggerCh: make(chan struct{}), + fsEventCh: make(chan string), + setupErr: setupErr, + } +} + +func (i *mockInotifyWatcher) Setup(rdirs, dirs []string) (chan struct{}, chan string, error) { + if i.setupErr != nil { + return nil, nil, i.setupErr + } + return i.triggerCh, i.fsEventCh, nil +} + +// ProcfsScanner + +type mockProcfsScanner struct { + triggerCh chan struct{} + interval time.Duration + psEventCh chan string + setupErr error +} + +func newMockProcfsScanner(setupErr error) *mockProcfsScanner { + return &mockProcfsScanner{ + psEventCh: make(chan string), + setupErr: setupErr, + } +} + +func (p *mockProcfsScanner) Setup(triggerCh chan struct{}, interval time.Duration) (chan string, error) { + if p.setupErr != nil { + return nil, p.setupErr + } + return p.psEventCh, nil +}