parsing_test.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. package parser
  2. import (
  3. "bytes"
  4. "fmt"
  5. "html/template"
  6. "io"
  7. "io/ioutil"
  8. "os"
  9. "strings"
  10. "testing"
  11. "github.com/crowdsecurity/crowdsec/pkg/types"
  12. "github.com/davecgh/go-spew/spew"
  13. log "github.com/sirupsen/logrus"
  14. "gopkg.in/yaml.v2"
  15. )
  16. type TestFile struct {
  17. Lines []types.Event `yaml:"lines,omitempty"`
  18. Results []types.Event `yaml:"results,omitempty"`
  19. }
  20. var debug bool = false
  21. func TestParser(t *testing.T) {
  22. debug = true
  23. log.SetLevel(log.InfoLevel)
  24. var envSetting = os.Getenv("TEST_ONLY")
  25. pctx, err := prepTests()
  26. if err != nil {
  27. t.Fatalf("failed to load env : %s", err)
  28. }
  29. //Init the enricher
  30. if envSetting != "" {
  31. if err := testOneParser(pctx, envSetting, nil); err != nil {
  32. t.Fatalf("Test '%s' failed : %s", envSetting, err)
  33. }
  34. } else {
  35. fds, err := ioutil.ReadDir("./tests/")
  36. if err != nil {
  37. t.Fatalf("Unable to read test directory : %s", err)
  38. }
  39. for _, fd := range fds {
  40. if !fd.IsDir() {
  41. continue
  42. }
  43. fname := "./tests/" + fd.Name()
  44. log.Infof("Running test on %s", fname)
  45. if err := testOneParser(pctx, fname, nil); err != nil {
  46. t.Fatalf("Test '%s' failed : %s", fname, err)
  47. }
  48. }
  49. }
  50. }
  51. func BenchmarkParser(t *testing.B) {
  52. log.Printf("start bench !!!!")
  53. debug = false
  54. log.SetLevel(log.ErrorLevel)
  55. pctx, err := prepTests()
  56. if err != nil {
  57. t.Fatalf("failed to load env : %s", err)
  58. }
  59. var envSetting = os.Getenv("TEST_ONLY")
  60. if envSetting != "" {
  61. if err := testOneParser(pctx, envSetting, t); err != nil {
  62. t.Fatalf("Test '%s' failed : %s", envSetting, err)
  63. }
  64. } else {
  65. fds, err := ioutil.ReadDir("./tests/")
  66. if err != nil {
  67. t.Fatalf("Unable to read test directory : %s", err)
  68. }
  69. for _, fd := range fds {
  70. if !fd.IsDir() {
  71. continue
  72. }
  73. fname := "./tests/" + fd.Name()
  74. log.Infof("Running test on %s", fname)
  75. if err := testOneParser(pctx, fname, t); err != nil {
  76. t.Fatalf("Test '%s' failed : %s", fname, err)
  77. }
  78. }
  79. }
  80. }
  81. func testOneParser(pctx *UnixParserCtx, dir string, b *testing.B) error {
  82. var err error
  83. var pnodes []Node
  84. var parser_configs []Stagefile
  85. log.Warningf("testing %s", dir)
  86. parser_cfg_file := fmt.Sprintf("%s/parsers.yaml", dir)
  87. cfg, err := ioutil.ReadFile(parser_cfg_file)
  88. if err != nil {
  89. return fmt.Errorf("failed opening %s : %s", parser_cfg_file, err)
  90. }
  91. tmpl, err := template.New("test").Parse(string(cfg))
  92. if err != nil {
  93. return fmt.Errorf("failed to parse template %s : %s", cfg, err)
  94. }
  95. var out bytes.Buffer
  96. err = tmpl.Execute(&out, map[string]string{"TestDirectory": dir})
  97. if err != nil {
  98. panic(err)
  99. }
  100. if err := yaml.UnmarshalStrict(out.Bytes(), &parser_configs); err != nil {
  101. return fmt.Errorf("failed unmarshaling %s : %s", parser_cfg_file, err)
  102. }
  103. pnodes, err = LoadStages(parser_configs, pctx)
  104. if err != nil {
  105. return fmt.Errorf("unable to load parser config : %s", err)
  106. }
  107. //TBD: Load post overflows
  108. //func testFile(t *testing.T, file string, pctx UnixParserCtx, nodes []Node) bool {
  109. parser_test_file := fmt.Sprintf("%s/test.yaml", dir)
  110. tests := loadTestFile(parser_test_file)
  111. count := 1
  112. if b != nil {
  113. count = b.N
  114. b.ResetTimer()
  115. }
  116. for n := 0; n < count; n++ {
  117. if testFile(tests, *pctx, pnodes) != true {
  118. return fmt.Errorf("test failed !")
  119. }
  120. }
  121. return nil
  122. }
  123. //prepTests is going to do the initialisation of parser : it's going to load enrichment plugins and load the patterns. This is done here so that we don't redo it for each test
  124. func prepTests() (*UnixParserCtx, error) {
  125. var pctx *UnixParserCtx
  126. var p UnixParser
  127. //Load enrichment
  128. datadir := "../../data/"
  129. pplugins, err := Loadplugin(datadir)
  130. if err != nil {
  131. log.Fatalf("failed to load plugin geoip : %v", err)
  132. }
  133. ECTX = nil
  134. ECTX = append(ECTX, pplugins)
  135. log.Printf("Loaded -> %+v", ECTX)
  136. //Load the parser patterns
  137. cfgdir := "../../config/"
  138. /* this should be refactored to 2 lines :p */
  139. // Init the parser
  140. pctx, err = p.Init(map[string]interface{}{"patterns": cfgdir + string("/patterns/"), "data": "./tests/"})
  141. if err != nil {
  142. return nil, fmt.Errorf("failed to initialize parser : %v", err)
  143. }
  144. return pctx, nil
  145. }
  146. func loadTestFile(file string) []TestFile {
  147. yamlFile, err := os.Open(file)
  148. if err != nil {
  149. log.Fatalf("yamlFile.Get err #%v ", err)
  150. }
  151. dec := yaml.NewDecoder(yamlFile)
  152. dec.SetStrict(true)
  153. var testSet []TestFile
  154. for {
  155. tf := TestFile{}
  156. err := dec.Decode(&tf)
  157. if err != nil {
  158. if err == io.EOF {
  159. break
  160. }
  161. log.Fatalf("Failed to load testfile '%s' yaml error : %v", file, err)
  162. return nil
  163. }
  164. testSet = append(testSet, tf)
  165. }
  166. return testSet
  167. }
  168. func matchEvent(expected types.Event, out types.Event, debug bool) ([]string, bool) {
  169. var retInfo []string
  170. var valid bool
  171. expectMaps := []map[string]string{expected.Parsed, expected.Meta, expected.Enriched}
  172. outMaps := []map[string]string{out.Parsed, out.Meta, out.Enriched}
  173. outLabels := []string{"Parsed", "Meta", "Enriched"}
  174. //allow to check as well for stage and processed flags
  175. if expected.Stage != "" {
  176. if expected.Stage != out.Stage {
  177. if debug {
  178. retInfo = append(retInfo, fmt.Sprintf("mismatch stage %s != %s", expected.Stage, out.Stage))
  179. }
  180. valid = false
  181. goto checkFinished
  182. } else {
  183. valid = true
  184. if debug {
  185. retInfo = append(retInfo, fmt.Sprintf("ok stage %s == %s", expected.Stage, out.Stage))
  186. }
  187. }
  188. }
  189. if expected.Process != out.Process {
  190. if debug {
  191. retInfo = append(retInfo, fmt.Sprintf("mismatch process %t != %t", expected.Process, out.Process))
  192. }
  193. valid = false
  194. goto checkFinished
  195. } else {
  196. valid = true
  197. if debug {
  198. retInfo = append(retInfo, fmt.Sprintf("ok process %t == %t", expected.Process, out.Process))
  199. }
  200. }
  201. if expected.Whitelisted != out.Whitelisted {
  202. if debug {
  203. retInfo = append(retInfo, fmt.Sprintf("mismatch whitelist %t != %t", expected.Whitelisted, out.Whitelisted))
  204. }
  205. valid = false
  206. goto checkFinished
  207. } else {
  208. if debug {
  209. retInfo = append(retInfo, fmt.Sprintf("ok whitelist %t == %t", expected.Whitelisted, out.Whitelisted))
  210. }
  211. valid = true
  212. }
  213. for mapIdx := 0; mapIdx < len(expectMaps); mapIdx++ {
  214. for expKey, expVal := range expectMaps[mapIdx] {
  215. if outVal, ok := outMaps[mapIdx][expKey]; ok {
  216. if outVal == expVal { //ok entry
  217. if debug {
  218. retInfo = append(retInfo, fmt.Sprintf("ok %s[%s] %s == %s", outLabels[mapIdx], expKey, expVal, outVal))
  219. }
  220. valid = true
  221. } else { //mismatch entry
  222. if debug {
  223. retInfo = append(retInfo, fmt.Sprintf("mismatch %s[%s] %s != %s", outLabels[mapIdx], expKey, expVal, outVal))
  224. }
  225. valid = false
  226. goto checkFinished
  227. }
  228. } else { //missing entry
  229. if debug {
  230. retInfo = append(retInfo, fmt.Sprintf("missing entry %s[%s]", outLabels[mapIdx], expKey))
  231. }
  232. valid = false
  233. goto checkFinished
  234. }
  235. }
  236. }
  237. checkFinished:
  238. if valid {
  239. if debug {
  240. retInfo = append(retInfo, fmt.Sprintf("OK ! %s", strings.Join(retInfo, "/")))
  241. }
  242. } else {
  243. if debug {
  244. retInfo = append(retInfo, fmt.Sprintf("KO ! %s", strings.Join(retInfo, "/")))
  245. }
  246. }
  247. return retInfo, valid
  248. }
  249. func testSubSet(testSet TestFile, pctx UnixParserCtx, nodes []Node) (bool, error) {
  250. var results []types.Event
  251. for _, in := range testSet.Lines {
  252. out, err := Parse(pctx, in, nodes)
  253. if err != nil {
  254. log.Errorf("Failed to process %s : %v", spew.Sdump(in), err)
  255. }
  256. //log.Infof("Parser output : %s", spew.Sdump(out))
  257. results = append(results, out)
  258. }
  259. log.Infof("parsed %d lines", len(testSet.Lines))
  260. log.Infof("got %d results", len(results))
  261. /*
  262. check the results we got against the expected ones
  263. only the keys of the expected part are checked against result
  264. */
  265. if len(testSet.Results) == 0 && len(results) == 0 {
  266. log.Fatalf("No results, no tests, abort.")
  267. return false, fmt.Errorf("no tests, no results")
  268. }
  269. reCheck:
  270. failinfo := []string{}
  271. for ridx, result := range results {
  272. for eidx, expected := range testSet.Results {
  273. explain, match := matchEvent(expected, result, debug)
  274. if match == true {
  275. log.Infof("expected %d/%d matches result %d/%d", eidx, len(testSet.Results), ridx, len(results))
  276. if len(explain) > 0 {
  277. log.Printf("-> %s", explain[len(explain)-1])
  278. }
  279. //don't do this at home : delete current element from list and redo
  280. results[len(results)-1], results[ridx] = results[ridx], results[len(results)-1]
  281. results = results[:len(results)-1]
  282. testSet.Results[len(testSet.Results)-1], testSet.Results[eidx] = testSet.Results[eidx], testSet.Results[len(testSet.Results)-1]
  283. testSet.Results = testSet.Results[:len(testSet.Results)-1]
  284. goto reCheck
  285. } else {
  286. failinfo = append(failinfo, explain...)
  287. }
  288. }
  289. }
  290. if len(results) > 0 {
  291. log.Printf("Errors : %s", strings.Join(failinfo, " / "))
  292. return false, fmt.Errorf("leftover results : %+v", results)
  293. }
  294. if len(testSet.Results) > 0 {
  295. log.Printf("Errors : %s", strings.Join(failinfo, " / "))
  296. return false, fmt.Errorf("leftover expected results : %+v", testSet.Results)
  297. }
  298. return true, nil
  299. }
  300. func testFile(testSet []TestFile, pctx UnixParserCtx, nodes []Node) bool {
  301. log.Warningf("Going to process one test set")
  302. for _, tf := range testSet {
  303. //func testSubSet(testSet TestFile, pctx UnixParserCtx, nodes []Node) (bool, error) {
  304. testOk, err := testSubSet(tf, pctx, nodes)
  305. if err != nil {
  306. log.Fatalf("test failed : %s", err)
  307. }
  308. if !testOk {
  309. log.Fatalf("failed test : %+v", tf)
  310. }
  311. }
  312. return true
  313. }