PageRenderTime 48ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/plugins/file/file_output_test.go

https://gitlab.com/karouf/heka
Go | 376 lines | 294 code | 58 blank | 24 comment | 28 complexity | d58b431f46769888f88e98be9d839044 MD5 | raw file
  1. /***** BEGIN LICENSE BLOCK *****
  2. # This Source Code Form is subject to the terms of the Mozilla Public
  3. # License, v. 2.0. If a copy of the MPL was not distributed with this file,
  4. # You can obtain one at http://mozilla.org/MPL/2.0/.
  5. #
  6. # The Initial Developer of the Original Code is the Mozilla Foundation.
  7. # Portions created by the Initial Developer are Copyright (C) 2012-2015
  8. # the Initial Developer. All Rights Reserved.
  9. #
  10. # Contributor(s):
  11. # Rob Miller (rmiller@mozilla.com)
  12. # Mike Trinkala (trink@mozilla.com)
  13. #
  14. # ***** END LICENSE BLOCK *****/
  15. package file
  16. import (
  17. "fmt"
  18. "io/ioutil"
  19. "os"
  20. "os/user"
  21. "path/filepath"
  22. "runtime"
  23. "sync"
  24. "time"
  25. . "github.com/mozilla-services/heka/pipeline"
  26. pipeline_ts "github.com/mozilla-services/heka/pipeline/testsupport"
  27. "github.com/mozilla-services/heka/plugins"
  28. plugins_ts "github.com/mozilla-services/heka/plugins/testsupport"
  29. "github.com/rafrombrc/gomock/gomock"
  30. gs "github.com/rafrombrc/gospec/src/gospec"
  31. )
  32. func FileOutputSpec(c gs.Context) {
  33. t := new(pipeline_ts.SimpleT)
  34. ctrl := gomock.NewController(t)
  35. tmpFileName := fmt.Sprintf("fileoutput-test-%d", time.Now().UnixNano())
  36. tmpFilePath := filepath.Join(os.TempDir(), tmpFileName)
  37. defer func() {
  38. ctrl.Finish()
  39. os.Remove(tmpFilePath)
  40. }()
  41. oth := plugins_ts.NewOutputTestHelper(ctrl)
  42. var wg sync.WaitGroup
  43. inChan := make(chan *PipelinePack, 1)
  44. pConfig := NewPipelineConfig(nil)
  45. c.Specify("A FileOutput", func() {
  46. fileOutput := new(FileOutput)
  47. encoder := new(plugins.PayloadEncoder)
  48. encoder.Init(encoder.ConfigStruct())
  49. config := fileOutput.ConfigStruct().(*FileOutputConfig)
  50. config.Path = tmpFilePath
  51. msg := pipeline_ts.GetTestMessage()
  52. pack := NewPipelinePack(pConfig.InputRecycleChan())
  53. pack.Message = msg
  54. pack.QueueCursor = "queuecursor"
  55. errChan := make(chan error, 1)
  56. c.Specify("w/ ProtobufEncoder", func() {
  57. encoder := new(ProtobufEncoder)
  58. encoder.SetPipelineConfig(pConfig)
  59. encoder.Init(nil)
  60. oth.MockOutputRunner.EXPECT().Encoder().Return(encoder)
  61. c.Specify("uses framing", func() {
  62. oth.MockOutputRunner.EXPECT().SetUseFraming(true)
  63. err := fileOutput.Init(config)
  64. defer os.Remove(tmpFilePath)
  65. c.Assume(err, gs.IsNil)
  66. oth.MockOutputRunner.EXPECT().InChan().Return(inChan)
  67. wg.Add(1)
  68. go func() {
  69. err = fileOutput.Run(oth.MockOutputRunner, oth.MockHelper)
  70. c.Expect(err, gs.IsNil)
  71. wg.Done()
  72. }()
  73. close(inChan)
  74. wg.Wait()
  75. })
  76. c.Specify("but not if config says not to", func() {
  77. useFraming := false
  78. config.UseFraming = &useFraming
  79. err := fileOutput.Init(config)
  80. defer os.Remove(tmpFilePath)
  81. c.Assume(err, gs.IsNil)
  82. oth.MockOutputRunner.EXPECT().InChan().Return(inChan)
  83. wg.Add(1)
  84. go func() {
  85. err = fileOutput.Run(oth.MockOutputRunner, oth.MockHelper)
  86. c.Expect(err, gs.IsNil)
  87. wg.Done()
  88. }()
  89. close(inChan)
  90. wg.Wait()
  91. // We should fail if SetUseFraming is called since we didn't
  92. // EXPECT it.
  93. })
  94. })
  95. c.Specify("rotates files correctly", func() {
  96. config.Path = "%Y-%m-%d"
  97. config.RotationInterval = 24
  98. rotateChan := make(chan time.Time)
  99. closingChan := make(chan struct{})
  100. err := fileOutput.Init(config)
  101. c.Assume(err, gs.IsNil)
  102. defer fileOutput.file.Close()
  103. fileOutput.rotateChan = rotateChan
  104. fileOutput.closing = closingChan
  105. fileOutput.startRotateNotifier()
  106. committerChan := make(chan struct{})
  107. go func() {
  108. fileOutput.committer(oth.MockOutputRunner, errChan)
  109. close(committerChan)
  110. }()
  111. c.Assume(fileOutput.path, gs.Equals, time.Now().Format("2006-01-02"))
  112. futureDuration, _ := time.ParseDuration("24h")
  113. futureNow := time.Now().Add(futureDuration)
  114. rotateChan <- futureNow
  115. close(inChan)
  116. close(fileOutput.batchChan)
  117. <-committerChan
  118. c.Assume(fileOutput.path, gs.Equals, futureNow.Format("2006-01-02"))
  119. })
  120. c.Specify("processes incoming messages", func() {
  121. err := fileOutput.Init(config)
  122. c.Assume(err, gs.IsNil)
  123. fileOutput.file.Close()
  124. // Save for comparison.
  125. payload := fmt.Sprintf("%s\n", pack.Message.GetPayload())
  126. oth.MockOutputRunner.EXPECT().InChan().Return(inChan)
  127. oth.MockOutputRunner.EXPECT().Encode(pack).Return(encoder.Encode(pack))
  128. go fileOutput.receiver(oth.MockOutputRunner, errChan)
  129. inChan <- pack
  130. close(inChan)
  131. outBatch := <-fileOutput.batchChan
  132. c.Expect(string(outBatch.data), gs.Equals, payload)
  133. c.Expect(outBatch.cursor, gs.Equals, pack.QueueCursor)
  134. })
  135. c.Specify("commits to a file", func() {
  136. outStr := "Write me out to the log file"
  137. outBytes := []byte(outStr)
  138. batch := &outBatch{
  139. data: outBytes,
  140. cursor: pack.QueueCursor,
  141. }
  142. oth.MockOutputRunner.EXPECT().UpdateCursor(pack.QueueCursor)
  143. c.Specify("with default settings", func() {
  144. err := fileOutput.Init(config)
  145. c.Assume(err, gs.IsNil)
  146. // Start committer loop.
  147. go fileOutput.committer(oth.MockOutputRunner, errChan)
  148. // Feed and close the batchChan.
  149. go func() {
  150. fileOutput.batchChan <- batch
  151. _ = <-fileOutput.backChan // clear backChan to prevent blocking.
  152. close(fileOutput.batchChan)
  153. }()
  154. // Wait until we know processing has finished.
  155. <-fileOutput.closing
  156. tmpFile, err := os.Open(tmpFilePath)
  157. defer tmpFile.Close()
  158. c.Assume(err, gs.IsNil)
  159. contents, err := ioutil.ReadAll(tmpFile)
  160. c.Assume(err, gs.IsNil)
  161. c.Expect(string(contents), gs.Equals, outStr)
  162. })
  163. c.Specify("with different Perm settings", func() {
  164. config.Perm = "600"
  165. err := fileOutput.Init(config)
  166. c.Assume(err, gs.IsNil)
  167. // Start committer loop.
  168. go fileOutput.committer(oth.MockOutputRunner, errChan)
  169. // Feed and close the batchChan.
  170. go func() {
  171. fileOutput.batchChan <- batch
  172. _ = <-fileOutput.backChan // clear backChan to prevent blocking.
  173. close(fileOutput.batchChan)
  174. }()
  175. // Wait until we know processing has finished.
  176. <-fileOutput.closing
  177. tmpFile, err := os.Open(tmpFilePath)
  178. defer tmpFile.Close()
  179. c.Assume(err, gs.IsNil)
  180. fileInfo, err := tmpFile.Stat()
  181. c.Assume(err, gs.IsNil)
  182. fileMode := fileInfo.Mode()
  183. if runtime.GOOS == "windows" {
  184. c.Expect(fileMode.String(), pipeline_ts.StringContains, "-rw-rw-rw-")
  185. } else {
  186. // 7 consecutive dashes implies no perms for group or other.
  187. c.Expect(fileMode.String(), pipeline_ts.StringContains, "-------")
  188. }
  189. })
  190. })
  191. if runtime.GOOS != "windows" {
  192. if u, err := user.Current(); err != nil && u.Uid != "0" {
  193. c.Specify("Init halts if basedirectory is not writable", func() {
  194. tmpdir := filepath.Join(os.TempDir(), "tmpdir")
  195. err := os.MkdirAll(tmpdir, 0400)
  196. c.Assume(err, gs.IsNil)
  197. config.Path = filepath.Join(tmpdir, "out.txt")
  198. err = fileOutput.Init(config)
  199. c.Assume(err, gs.Not(gs.IsNil))
  200. os.RemoveAll(tmpdir)
  201. })
  202. }
  203. c.Specify("honors folder_perm setting", func() {
  204. config.FolderPerm = "750"
  205. subdir := filepath.Join(os.TempDir(), "subdir")
  206. config.Path = filepath.Join(subdir, "out.txt")
  207. err := fileOutput.Init(config)
  208. defer os.RemoveAll(subdir)
  209. c.Assume(err, gs.IsNil)
  210. fi, err := os.Stat(subdir)
  211. c.Expect(fi.IsDir(), gs.IsTrue)
  212. c.Expect(fi.Mode().Perm(), gs.Equals, os.FileMode(0750))
  213. })
  214. }
  215. c.Specify("that starts receiving w/ a flush interval", func() {
  216. config.FlushInterval = 100000000 // We'll trigger the timer manually.
  217. inChan := make(chan *PipelinePack)
  218. oth.MockOutputRunner.EXPECT().InChan().Return(inChan)
  219. timerChan := make(chan time.Time)
  220. msg2 := pipeline_ts.GetTestMessage()
  221. msg2.SetPayload("MESSAGE 2")
  222. pack2 := NewPipelinePack(pConfig.InputRecycleChan())
  223. pack2.Message = msg2
  224. recvWithConfig := func(config *FileOutputConfig) {
  225. err := fileOutput.Init(config)
  226. c.Assume(err, gs.IsNil)
  227. fileOutput.timerChan = timerChan
  228. go fileOutput.receiver(oth.MockOutputRunner, errChan)
  229. runtime.Gosched() // Yield so receiver will start.
  230. }
  231. cleanUp := func() {
  232. close(inChan)
  233. fileOutput.file.Close()
  234. }
  235. c.Specify("honors flush interval", func() {
  236. oth.MockOutputRunner.EXPECT().Encode(pack).Return(encoder.Encode(pack))
  237. recvWithConfig(config)
  238. defer cleanUp()
  239. inChan <- pack
  240. after := time.After(100 * time.Millisecond)
  241. select {
  242. case _ = <-fileOutput.batchChan:
  243. c.Expect("", gs.Equals, "fileOutput.batchChan should NOT have fired yet")
  244. case <-after:
  245. }
  246. timerChan <- time.Now()
  247. after = time.After(100 * time.Millisecond)
  248. select {
  249. case _ = <-fileOutput.batchChan:
  250. case <-after:
  251. c.Expect("", gs.Equals, "fileOutput.batchChan SHOULD have fired by now")
  252. }
  253. })
  254. c.Specify("honors flush interval AND flush count", func() {
  255. oth.MockOutputRunner.EXPECT().Encode(pack).Return(encoder.Encode(pack))
  256. oth.MockOutputRunner.EXPECT().Encode(pack2).Return(encoder.Encode(pack2))
  257. config.FlushCount = 2
  258. recvWithConfig(config)
  259. defer cleanUp()
  260. inChan <- pack
  261. after := time.After(100 * time.Millisecond)
  262. select {
  263. case <-fileOutput.batchChan:
  264. c.Expect("", gs.Equals, "fileOutput.batchChan should NOT have fired yet")
  265. case <-after:
  266. }
  267. timerChan <- time.Now()
  268. after = time.After(100 * time.Millisecond)
  269. select {
  270. case <-fileOutput.batchChan:
  271. c.Expect("", gs.Equals, "fileOutput.batchChan should NOT have fired yet")
  272. case <-after:
  273. }
  274. after = time.After(100 * time.Millisecond)
  275. inChan <- pack2
  276. select {
  277. case <-fileOutput.batchChan:
  278. case <-after:
  279. c.Expect("", gs.Equals, "fileOutput.batchChan SHOULD have fired by now")
  280. }
  281. })
  282. c.Specify("honors flush interval OR flush count", func() {
  283. oth.MockOutputRunner.EXPECT().Encode(gomock.Any()).Return(encoder.Encode(pack))
  284. config.FlushCount = 2
  285. config.FlushOperator = "OR"
  286. recvWithConfig(config)
  287. defer cleanUp()
  288. inChan <- pack
  289. after := time.After(100 * time.Millisecond)
  290. select {
  291. case <-fileOutput.batchChan:
  292. c.Expect("", gs.Equals, "fileOutput.batchChan should NOT have fired yet")
  293. case <-after:
  294. }
  295. c.Specify("when interval triggers first", func() {
  296. timerChan <- time.Now()
  297. after = time.After(100 * time.Millisecond)
  298. select {
  299. case <-fileOutput.batchChan:
  300. case <-after:
  301. c.Expect("", gs.Equals, "fileOutput.batchChan SHOULD have fired by now")
  302. }
  303. })
  304. c.Specify("when count triggers first", func() {
  305. out, err := encoder.Encode(pack2)
  306. oth.MockOutputRunner.EXPECT().Encode(gomock.Any()).Return(out, err)
  307. inChan <- pack2
  308. after = time.After(100 * time.Millisecond)
  309. select {
  310. case <-fileOutput.batchChan:
  311. case <-after:
  312. c.Expect("", gs.Equals, "fileOutput.batchChan SHOULD have fired by now")
  313. }
  314. })
  315. })
  316. })
  317. })
  318. }