package acquisition import ( "fmt" "os" "strings" "testing" "time" "github.com/crowdsecurity/crowdsec/pkg/types" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" tomb "gopkg.in/tomb.v2" ) func TestAcquisCat(t *testing.T) { tests := []struct { cfg DataSourceCfg //tombState config_error string read_error string tomb_error string lines int }{ { //missing filename(s) cfg: DataSourceCfg{ Mode: CAT_MODE, }, config_error: "no filename or filenames", }, { //forbiden file cfg: DataSourceCfg{ Mode: CAT_MODE, Filename: "/etc/shadow", }, config_error: "unable to open /etc/shadow : permission denied", }, { //bad regexp cfg: DataSourceCfg{ Filename: "[a-", Mode: CAT_MODE, }, config_error: "while globbing [a-: syntax error in pattern", }, { //inexisting file cfg: DataSourceCfg{ Filename: "/does/not/exists", Mode: CAT_MODE, }, config_error: "no files to read for [/does/not/exists]", }, { //ok file cfg: DataSourceCfg{ Filename: "./tests/test.log", Mode: CAT_MODE, }, lines: 1, }, { //invalid gz cfg: DataSourceCfg{ Filename: "./tests/badlog.gz", Mode: CAT_MODE, }, lines: 0, tomb_error: "failed to read gz ./tests/badlog.gz: EOF", }, { //good gz cfg: DataSourceCfg{ Filename: "./tests/test.log.gz", Mode: CAT_MODE, }, lines: 1, }, } for tidx, test := range tests { fileSrc := new(FileSource) err := fileSrc.Configure(test.cfg) if test.config_error != "" { assert.Contains(t, fmt.Sprintf("%s", err), test.config_error) log.Infof("expected config error ok : %s", test.config_error) continue } else { if err != nil { t.Fatalf("%d/%d unexpected config error %s", tidx, len(tests), err) } } out := make(chan types.Event) tomb := tomb.Tomb{} count := 0 err = fileSrc.StartReading(out, &tomb) if test.read_error != "" { assert.Contains(t, fmt.Sprintf("%s", err), test.read_error) log.Infof("expected read error ok : %s", test.read_error) continue } else { if err != nil { t.Fatalf("%d/%d unexpected read error %s", tidx, len(tests), err) } } READLOOP: for { select { case <-out: count++ case <-time.After(1 * time.Second): break READLOOP } } if count != test.lines { t.Fatalf("%d/%d expected %d line read, got %d", tidx, len(tests), test.lines, count) } if test.tomb_error != "" { assert.Contains(t, fmt.Sprintf("%s", tomb.Err()), test.tomb_error) log.Infof("expected tomb error ok : %s", test.read_error) continue } else { if tomb.Err() != nil { t.Fatalf("%d/%d unexpected tomb error %s", tidx, len(tests), tomb.Err()) } } } } func TestTailKill(t *testing.T) { cfg := DataSourceCfg{ Filename: "./tests/test.log", Mode: TAIL_MODE, } fileSrc := new(FileSource) err := fileSrc.Configure(cfg) if err != nil { t.Fatalf("unexpected config error %s", err) } out := make(chan types.Event) tb := tomb.Tomb{} err = fileSrc.StartReading(out, &tb) if err != nil { t.Fatalf("unexpected read error %s", err) } time.Sleep(1 * time.Second) if tb.Err() != tomb.ErrStillAlive { t.Fatalf("unexpected tomb error %s (should be alive)", tb.Err()) } //kill it :> tb.Kill(nil) time.Sleep(1 * time.Second) if tb.Err() != nil { t.Fatalf("unexpected tomb error %s (should be dead)", tb.Err()) } } func TestTailKillBis(t *testing.T) { cfg := DataSourceCfg{ Filename: "./tests/test.log", Mode: TAIL_MODE, } fileSrc := new(FileSource) err := fileSrc.Configure(cfg) if err != nil { t.Fatalf("unexpected config error %s", err) } out := make(chan types.Event) tb := tomb.Tomb{} err = fileSrc.StartReading(out, &tb) if err != nil { t.Fatalf("unexpected read error %s", err) } time.Sleep(1 * time.Second) if tb.Err() != tomb.ErrStillAlive { t.Fatalf("unexpected tomb error %s (should be alive)", tb.Err()) } //kill the underlying tomb of tailer fileSrc.tails[0].Kill(fmt.Errorf("ratata")) time.Sleep(1 * time.Second) //it can be two errors : if !strings.Contains(fmt.Sprintf("%s", tb.Err()), "dead reader for ./tests/test.log") && !strings.Contains(fmt.Sprintf("%s", tb.Err()), "tail for ./tests/test.log is empty") { t.Fatalf("unexpected error : %s", tb.Err()) } } func TestTailRuntime(t *testing.T) { //log.SetLevel(log.TraceLevel) cfg := DataSourceCfg{ Filename: "./tests/test.log", Mode: TAIL_MODE, } fileSrc := new(FileSource) err := fileSrc.Configure(cfg) if err != nil { t.Fatalf("unexpected config error %s", err) } out := make(chan types.Event) tb := tomb.Tomb{} count := 0 err = fileSrc.StartReading(out, &tb) if err != nil { t.Fatalf("unexpected read error %s", err) } time.Sleep(1 * time.Second) //write data f, err := os.OpenFile(cfg.Filename, os.O_APPEND|os.O_WRONLY, 0644) if err != nil { t.Fatal(err) } for i := 0; i < 5; i++ { _, err := f.WriteString(fmt.Sprintf("ratata%d\n", i)) if err != nil { t.Fatal(err) } } f.Close() READLOOP: for { select { case <-out: count++ case <-time.After(1 * time.Second): break READLOOP } } if count != 5 { t.Fatalf("expected %d line read, got %d", 5, count) } if tb.Err() != tomb.ErrStillAlive { t.Fatalf("unexpected tomb error %s", tb.Err()) } /*reset the file*/ f, err = os.OpenFile(cfg.Filename, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { t.Fatal(err) } _, err = f.WriteString("one log line\n") if err != nil { t.Fatal(err) } f.Close() } func TestAcquisTail(t *testing.T) { tests := []struct { cfg DataSourceCfg //tombState config_error string read_error string tomb_error string lines int }{ { //missing filename(s) cfg: DataSourceCfg{ Mode: TAIL_MODE, }, config_error: "no filename or filenames", }, { //forbiden file cfg: DataSourceCfg{ Mode: TAIL_MODE, Filename: "/etc/shadow", }, config_error: "unable to open /etc/shadow : permission denied", }, { //bad regexp cfg: DataSourceCfg{ Filename: "[a-", Mode: TAIL_MODE, }, config_error: "while globbing [a-: syntax error in pattern", }, { //inexisting file cfg: DataSourceCfg{ Filename: "/does/not/exists", Mode: TAIL_MODE, }, config_error: "no files to read for [/does/not/exists]", }, { //ok file cfg: DataSourceCfg{ Filename: "./tests/test.log", Mode: TAIL_MODE, }, lines: 0, tomb_error: "still alive", }, { //invalid gz cfg: DataSourceCfg{ Filename: "./tests/badlog.gz", Mode: TAIL_MODE, }, lines: 0, tomb_error: "still alive", }, { //good gz cfg: DataSourceCfg{ Filename: "./tests/test.log.gz", Mode: TAIL_MODE, }, lines: 0, tomb_error: "still alive", }, } for tidx, test := range tests { fileSrc := new(FileSource) err := fileSrc.Configure(test.cfg) if test.config_error != "" { assert.Contains(t, fmt.Sprintf("%s", err), test.config_error) log.Infof("expected config error ok : %s", test.config_error) continue } else { if err != nil { t.Fatalf("%d/%d unexpected config error %s", tidx, len(tests), err) } } out := make(chan types.Event) tomb := tomb.Tomb{} count := 0 err = fileSrc.StartReading(out, &tomb) if test.read_error != "" { assert.Contains(t, fmt.Sprintf("%s", err), test.read_error) log.Infof("expected read error ok : %s", test.read_error) continue } else { if err != nil { t.Fatalf("%d/%d unexpected read error %s", tidx, len(tests), err) } } READLOOP: for { select { case <-out: count++ case <-time.After(1 * time.Second): break READLOOP } } if count != test.lines { t.Fatalf("%d/%d expected %d line read, got %d", tidx, len(tests), test.lines, count) } if test.tomb_error != "" { assert.Contains(t, fmt.Sprintf("%s", tomb.Err()), test.tomb_error) log.Infof("expected tomb error ok : %s", test.read_error) continue } else { if tomb.Err() != nil { t.Fatalf("%d/%d unexpected tomb error %s", tidx, len(tests), tomb.Err()) } } } }