journalctl_test.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. package journalctlacquisition
  2. import (
  3. "os"
  4. "os/exec"
  5. "path/filepath"
  6. "runtime"
  7. "testing"
  8. "time"
  9. "github.com/crowdsecurity/crowdsec/pkg/cstest"
  10. "github.com/crowdsecurity/crowdsec/pkg/types"
  11. log "github.com/sirupsen/logrus"
  12. "github.com/sirupsen/logrus/hooks/test"
  13. "github.com/stretchr/testify/assert"
  14. "gopkg.in/tomb.v2"
  15. )
  16. func TestBadConfiguration(t *testing.T) {
  17. if runtime.GOOS == "windows" {
  18. t.Skip("Skipping test on windows")
  19. }
  20. tests := []struct {
  21. config string
  22. expectedErr string
  23. }{
  24. {
  25. config: `foobar: asd.log`,
  26. expectedErr: "line 1: field foobar not found in type journalctlacquisition.JournalCtlConfiguration",
  27. },
  28. {
  29. config: `
  30. mode: tail
  31. source: journalctl`,
  32. expectedErr: "journalctl_filter is required",
  33. },
  34. {
  35. config: `
  36. mode: cat
  37. source: journalctl
  38. journalctl_filter:
  39. - _UID=42`,
  40. expectedErr: "",
  41. },
  42. }
  43. subLogger := log.WithFields(log.Fields{
  44. "type": "journalctl",
  45. })
  46. for _, test := range tests {
  47. f := JournalCtlSource{}
  48. err := f.Configure([]byte(test.config), subLogger)
  49. cstest.AssertErrorContains(t, err, test.expectedErr)
  50. }
  51. }
  52. func TestConfigureDSN(t *testing.T) {
  53. if runtime.GOOS == "windows" {
  54. t.Skip("Skipping test on windows")
  55. }
  56. tests := []struct {
  57. dsn string
  58. expectedErr string
  59. }{
  60. {
  61. dsn: "asd://",
  62. expectedErr: "invalid DSN asd:// for journalctl source, must start with journalctl://",
  63. },
  64. {
  65. dsn: "journalctl://",
  66. expectedErr: "empty journalctl:// DSN",
  67. },
  68. {
  69. dsn: "journalctl://foobar=42",
  70. expectedErr: "unsupported key foobar in journalctl DSN",
  71. },
  72. {
  73. dsn: "journalctl://filters=%ZZ",
  74. expectedErr: "could not parse journalctl DSN : invalid URL escape \"%ZZ\"",
  75. },
  76. {
  77. dsn: "journalctl://filters=_UID=42?log_level=warn",
  78. expectedErr: "",
  79. },
  80. {
  81. dsn: "journalctl://filters=_UID=1000&log_level=foobar",
  82. expectedErr: "unknown level foobar: not a valid logrus Level:",
  83. },
  84. {
  85. dsn: "journalctl://filters=_UID=1000&log_level=warn&since=yesterday",
  86. expectedErr: "",
  87. },
  88. }
  89. subLogger := log.WithFields(log.Fields{
  90. "type": "journalctl",
  91. })
  92. for _, test := range tests {
  93. f := JournalCtlSource{}
  94. err := f.ConfigureByDSN(test.dsn, map[string]string{"type": "testtype"}, subLogger)
  95. cstest.AssertErrorContains(t, err, test.expectedErr)
  96. }
  97. }
  98. func TestOneShot(t *testing.T) {
  99. if runtime.GOOS == "windows" {
  100. t.Skip("Skipping test on windows")
  101. }
  102. tests := []struct {
  103. config string
  104. expectedErr string
  105. expectedOutput string
  106. expectedLines int
  107. logLevel log.Level
  108. }{
  109. {
  110. config: `
  111. source: journalctl
  112. mode: cat
  113. journalctl_filter:
  114. - "-_UID=42"`,
  115. expectedErr: "",
  116. expectedOutput: "journalctl: invalid option",
  117. logLevel: log.WarnLevel,
  118. expectedLines: 0,
  119. },
  120. {
  121. config: `
  122. source: journalctl
  123. mode: cat
  124. journalctl_filter:
  125. - _SYSTEMD_UNIT=ssh.service`,
  126. expectedErr: "",
  127. expectedOutput: "",
  128. logLevel: log.WarnLevel,
  129. expectedLines: 14,
  130. },
  131. }
  132. for _, ts := range tests {
  133. var logger *log.Logger
  134. var subLogger *log.Entry
  135. var hook *test.Hook
  136. if ts.expectedOutput != "" {
  137. logger, hook = test.NewNullLogger()
  138. logger.SetLevel(ts.logLevel)
  139. subLogger = logger.WithFields(log.Fields{
  140. "type": "journalctl",
  141. })
  142. } else {
  143. subLogger = log.WithFields(log.Fields{
  144. "type": "journalctl",
  145. })
  146. }
  147. tomb := tomb.Tomb{}
  148. out := make(chan types.Event)
  149. j := JournalCtlSource{}
  150. err := j.Configure([]byte(ts.config), subLogger)
  151. if err != nil {
  152. t.Fatalf("Unexpected error : %s", err)
  153. }
  154. actualLines := 0
  155. if ts.expectedLines != 0 {
  156. go func() {
  157. READLOOP:
  158. for {
  159. select {
  160. case <-out:
  161. actualLines++
  162. case <-time.After(1 * time.Second):
  163. break READLOOP
  164. }
  165. }
  166. }()
  167. }
  168. err = j.OneShotAcquisition(out, &tomb)
  169. cstest.AssertErrorContains(t, err, ts.expectedErr)
  170. if err != nil {
  171. continue
  172. }
  173. if ts.expectedLines != 0 {
  174. assert.Equal(t, ts.expectedLines, actualLines)
  175. }
  176. if ts.expectedOutput != "" {
  177. if hook.LastEntry() == nil {
  178. t.Fatalf("Expected log output '%s' but got nothing !", ts.expectedOutput)
  179. }
  180. assert.Contains(t, hook.LastEntry().Message, ts.expectedOutput)
  181. hook.Reset()
  182. }
  183. }
  184. }
  185. func TestStreaming(t *testing.T) {
  186. if runtime.GOOS == "windows" {
  187. t.Skip("Skipping test on windows")
  188. }
  189. tests := []struct {
  190. config string
  191. expectedErr string
  192. expectedOutput string
  193. expectedLines int
  194. logLevel log.Level
  195. }{
  196. {
  197. config: `
  198. source: journalctl
  199. mode: cat
  200. journalctl_filter:
  201. - _SYSTEMD_UNIT=ssh.service`,
  202. expectedErr: "",
  203. expectedOutput: "",
  204. logLevel: log.WarnLevel,
  205. expectedLines: 14,
  206. },
  207. }
  208. for _, ts := range tests {
  209. var logger *log.Logger
  210. var subLogger *log.Entry
  211. var hook *test.Hook
  212. if ts.expectedOutput != "" {
  213. logger, hook = test.NewNullLogger()
  214. logger.SetLevel(ts.logLevel)
  215. subLogger = logger.WithFields(log.Fields{
  216. "type": "journalctl",
  217. })
  218. } else {
  219. subLogger = log.WithFields(log.Fields{
  220. "type": "journalctl",
  221. })
  222. }
  223. tomb := tomb.Tomb{}
  224. out := make(chan types.Event)
  225. j := JournalCtlSource{}
  226. err := j.Configure([]byte(ts.config), subLogger)
  227. if err != nil {
  228. t.Fatalf("Unexpected error : %s", err)
  229. }
  230. actualLines := 0
  231. if ts.expectedLines != 0 {
  232. go func() {
  233. READLOOP:
  234. for {
  235. select {
  236. case <-out:
  237. actualLines++
  238. case <-time.After(1 * time.Second):
  239. break READLOOP
  240. }
  241. }
  242. }()
  243. }
  244. err = j.StreamingAcquisition(out, &tomb)
  245. cstest.AssertErrorContains(t, err, ts.expectedErr)
  246. if err != nil {
  247. continue
  248. }
  249. if ts.expectedLines != 0 {
  250. time.Sleep(1 * time.Second)
  251. assert.Equal(t, ts.expectedLines, actualLines)
  252. }
  253. tomb.Kill(nil)
  254. tomb.Wait()
  255. output, _ := exec.Command("pgrep", "-x", "journalctl").CombinedOutput()
  256. if string(output) != "" {
  257. t.Fatalf("Found a journalctl process after killing the tomb !")
  258. }
  259. if ts.expectedOutput != "" {
  260. if hook.LastEntry() == nil {
  261. t.Fatalf("Expected log output '%s' but got nothing !", ts.expectedOutput)
  262. }
  263. assert.Contains(t, hook.LastEntry().Message, ts.expectedOutput)
  264. hook.Reset()
  265. }
  266. }
  267. }
  268. func TestMain(m *testing.M) {
  269. if os.Getenv("USE_SYSTEM_JOURNALCTL") == "" {
  270. currentDir, _ := os.Getwd()
  271. fullPath := filepath.Join(currentDir, "test_files")
  272. os.Setenv("PATH", fullPath+":"+os.Getenv("PATH"))
  273. }
  274. os.Exit(m.Run())
  275. }