pipeline_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. package hercules
  2. import (
  3. "errors"
  4. "io"
  5. "io/ioutil"
  6. "os"
  7. "path"
  8. "testing"
  9. "github.com/stretchr/testify/assert"
  10. "gopkg.in/src-d/go-git.v4"
  11. "gopkg.in/src-d/go-git.v4/plumbing"
  12. "gopkg.in/src-d/go-git.v4/plumbing/object"
  13. "gopkg.in/src-d/go-git.v4/storage/memory"
  14. "gopkg.in/src-d/hercules.v3/pb"
  15. )
  16. type testPipelineItem struct {
  17. Initialized bool
  18. DepsConsumed bool
  19. CommitMatches bool
  20. IndexMatches bool
  21. TestError bool
  22. }
  23. func (item *testPipelineItem) Name() string {
  24. return "Test"
  25. }
  26. func (item *testPipelineItem) Provides() []string {
  27. arr := [...]string{"test"}
  28. return arr[:]
  29. }
  30. func (item *testPipelineItem) Requires() []string {
  31. return []string{}
  32. }
  33. func (item *testPipelineItem) Configure(facts map[string]interface{}) {
  34. }
  35. func (item *testPipelineItem) ListConfigurationOptions() []ConfigurationOption {
  36. options := [...]ConfigurationOption{{
  37. Name: "TestOption",
  38. Description: "The option description.",
  39. Flag: "test-option",
  40. Type: IntConfigurationOption,
  41. Default: 10,
  42. }}
  43. return options[:]
  44. }
  45. func (item *testPipelineItem) Flag() string {
  46. return "mytest"
  47. }
  48. func (item *testPipelineItem) Features() []string {
  49. f := [...]string{"power"}
  50. return f[:]
  51. }
  52. func (item *testPipelineItem) Initialize(repository *git.Repository) {
  53. item.Initialized = repository != nil
  54. }
  55. func (item *testPipelineItem) Consume(deps map[string]interface{}) (map[string]interface{}, error) {
  56. if item.TestError {
  57. return nil, errors.New("error")
  58. }
  59. obj, exists := deps["commit"]
  60. item.DepsConsumed = exists
  61. if item.DepsConsumed {
  62. commit := obj.(*object.Commit)
  63. item.CommitMatches = commit.Hash == plumbing.NewHash(
  64. "af9ddc0db70f09f3f27b4b98e415592a7485171c")
  65. obj, item.DepsConsumed = deps["index"]
  66. if item.DepsConsumed {
  67. item.IndexMatches = obj.(int) == 0
  68. }
  69. }
  70. return map[string]interface{}{"test": item}, nil
  71. }
  72. func (item *testPipelineItem) Finalize() interface{} {
  73. return item
  74. }
  75. func (item *testPipelineItem) Serialize(result interface{}, binary bool, writer io.Writer) error {
  76. return nil
  77. }
  78. type dependingTestPipelineItem struct {
  79. DependencySatisfied bool
  80. TestNilConsumeReturn bool
  81. }
  82. func (item *dependingTestPipelineItem) Name() string {
  83. return "Test2"
  84. }
  85. func (item *dependingTestPipelineItem) Provides() []string {
  86. arr := [...]string{"test2"}
  87. return arr[:]
  88. }
  89. func (item *dependingTestPipelineItem) Requires() []string {
  90. arr := [...]string{"test"}
  91. return arr[:]
  92. }
  93. func (item *dependingTestPipelineItem) ListConfigurationOptions() []ConfigurationOption {
  94. options := [...]ConfigurationOption{{
  95. Name: "TestOption2",
  96. Description: "The option description.",
  97. Flag: "test-option2",
  98. Type: IntConfigurationOption,
  99. Default: 10,
  100. }}
  101. return options[:]
  102. }
  103. func (item *dependingTestPipelineItem) Configure(facts map[string]interface{}) {
  104. }
  105. func (item *dependingTestPipelineItem) Initialize(repository *git.Repository) {
  106. }
  107. func (item *dependingTestPipelineItem) Flag() string {
  108. return "depflag"
  109. }
  110. func (item *dependingTestPipelineItem) Consume(deps map[string]interface{}) (map[string]interface{}, error) {
  111. _, exists := deps["test"]
  112. item.DependencySatisfied = exists
  113. if !item.TestNilConsumeReturn {
  114. return map[string]interface{}{"test2": item}, nil
  115. }
  116. return nil, nil
  117. }
  118. func (item *dependingTestPipelineItem) Finalize() interface{} {
  119. return true
  120. }
  121. func (item *dependingTestPipelineItem) Serialize(result interface{}, binary bool, writer io.Writer) error {
  122. return nil
  123. }
  124. func TestPipelineFacts(t *testing.T) {
  125. pipeline := NewPipeline(testRepository)
  126. pipeline.SetFact("fact", "value")
  127. assert.Equal(t, pipeline.GetFact("fact"), "value")
  128. }
  129. func TestPipelineFeatures(t *testing.T) {
  130. pipeline := NewPipeline(testRepository)
  131. pipeline.SetFeature("feat")
  132. val, _ := pipeline.GetFeature("feat")
  133. assert.True(t, val)
  134. _, exists := pipeline.GetFeature("!")
  135. assert.False(t, exists)
  136. Registry.featureFlags.Set("777")
  137. defer func() {
  138. Registry.featureFlags = arrayFeatureFlags{Flags: []string{}, Choices: map[string]bool{}}
  139. }()
  140. pipeline.SetFeaturesFromFlags()
  141. _, exists = pipeline.GetFeature("777")
  142. assert.False(t, exists)
  143. assert.Panics(t, func() {
  144. pipeline.SetFeaturesFromFlags(
  145. &PipelineItemRegistry{}, &PipelineItemRegistry{})
  146. })
  147. }
  148. func TestPipelineRun(t *testing.T) {
  149. pipeline := NewPipeline(testRepository)
  150. item := &testPipelineItem{}
  151. pipeline.AddItem(item)
  152. pipeline.Initialize(map[string]interface{}{})
  153. assert.True(t, item.Initialized)
  154. commits := make([]*object.Commit, 1)
  155. commits[0], _ = testRepository.CommitObject(plumbing.NewHash(
  156. "af9ddc0db70f09f3f27b4b98e415592a7485171c"))
  157. result, err := pipeline.Run(commits)
  158. assert.Nil(t, err)
  159. assert.Equal(t, 2, len(result))
  160. assert.Equal(t, item, result[item].(*testPipelineItem))
  161. common := result[nil].(*CommonAnalysisResult)
  162. assert.Equal(t, common.BeginTime, int64(1481719092))
  163. assert.Equal(t, common.EndTime, int64(1481719092))
  164. assert.Equal(t, common.CommitsNumber, 1)
  165. assert.True(t, common.RunTime.Nanoseconds()/1e6 < 100)
  166. assert.True(t, item.DepsConsumed)
  167. assert.True(t, item.CommitMatches)
  168. assert.True(t, item.IndexMatches)
  169. pipeline.RemoveItem(item)
  170. result, err = pipeline.Run(commits)
  171. assert.Nil(t, err)
  172. assert.Equal(t, 1, len(result))
  173. }
  174. func TestPipelineOnProgress(t *testing.T) {
  175. pipeline := NewPipeline(testRepository)
  176. var progressOk1, progressOk2 bool
  177. onProgress := func(step int, total int) {
  178. if step == 0 && total == 1 {
  179. progressOk1 = true
  180. }
  181. if step == 1 && total == 1 && progressOk1 {
  182. progressOk2 = true
  183. }
  184. }
  185. pipeline.OnProgress = onProgress
  186. commits := make([]*object.Commit, 1)
  187. commits[0], _ = testRepository.CommitObject(plumbing.NewHash(
  188. "af9ddc0db70f09f3f27b4b98e415592a7485171c"))
  189. result, err := pipeline.Run(commits)
  190. assert.Nil(t, err)
  191. assert.Equal(t, 1, len(result))
  192. assert.True(t, progressOk1)
  193. assert.True(t, progressOk2)
  194. }
  195. func TestPipelineCommits(t *testing.T) {
  196. pipeline := NewPipeline(testRepository)
  197. commits := pipeline.Commits()
  198. assert.True(t, len(commits) >= 90)
  199. assert.Equal(t, commits[0].Hash, plumbing.NewHash(
  200. "cce947b98a050c6d356bc6ba95030254914027b1"))
  201. assert.Equal(t, commits[89].Hash, plumbing.NewHash(
  202. "6db8065cdb9bb0758f36a7e75fc72ab95f9e8145"))
  203. assert.NotEqual(t, commits[len(commits)-1], commits[len(commits)-2])
  204. }
  205. func TestLoadCommitsFromFile(t *testing.T) {
  206. tmp, err := ioutil.TempFile("", "hercules-test-")
  207. assert.Nil(t, err)
  208. tmp.WriteString("cce947b98a050c6d356bc6ba95030254914027b1\n6db8065cdb9bb0758f36a7e75fc72ab95f9e8145")
  209. tmp.Close()
  210. defer os.Remove(tmp.Name())
  211. commits, err := LoadCommitsFromFile(tmp.Name(), testRepository)
  212. assert.Nil(t, err)
  213. assert.Equal(t, len(commits), 2)
  214. assert.Equal(t, commits[0].Hash, plumbing.NewHash(
  215. "cce947b98a050c6d356bc6ba95030254914027b1"))
  216. assert.Equal(t, commits[1].Hash, plumbing.NewHash(
  217. "6db8065cdb9bb0758f36a7e75fc72ab95f9e8145"))
  218. commits, err = LoadCommitsFromFile("/WAT?xxx!", testRepository)
  219. assert.Nil(t, commits)
  220. assert.NotNil(t, err)
  221. tmp, err = ioutil.TempFile("", "hercules-test-")
  222. assert.Nil(t, err)
  223. tmp.WriteString("WAT")
  224. tmp.Close()
  225. defer os.Remove(tmp.Name())
  226. commits, err = LoadCommitsFromFile(tmp.Name(), testRepository)
  227. assert.Nil(t, commits)
  228. assert.NotNil(t, err)
  229. tmp, err = ioutil.TempFile("", "hercules-test-")
  230. assert.Nil(t, err)
  231. tmp.WriteString("ffffffffffffffffffffffffffffffffffffffff")
  232. tmp.Close()
  233. defer os.Remove(tmp.Name())
  234. commits, err = LoadCommitsFromFile(tmp.Name(), testRepository)
  235. assert.Nil(t, commits)
  236. assert.NotNil(t, err)
  237. }
  238. func TestPipelineDeps(t *testing.T) {
  239. pipeline := NewPipeline(testRepository)
  240. item1 := &dependingTestPipelineItem{}
  241. item2 := &testPipelineItem{}
  242. pipeline.AddItem(item1)
  243. pipeline.AddItem(item2)
  244. assert.Equal(t, pipeline.Len(), 2)
  245. pipeline.Initialize(map[string]interface{}{})
  246. commits := make([]*object.Commit, 1)
  247. commits[0], _ = testRepository.CommitObject(plumbing.NewHash(
  248. "af9ddc0db70f09f3f27b4b98e415592a7485171c"))
  249. result, err := pipeline.Run(commits)
  250. assert.Nil(t, err)
  251. assert.True(t, result[item1].(bool))
  252. assert.Equal(t, result[item2], item2)
  253. item1.TestNilConsumeReturn = true
  254. assert.Panics(t, func() { pipeline.Run(commits) })
  255. }
  256. func TestPipelineDeployFeatures(t *testing.T) {
  257. pipeline := NewPipeline(testRepository)
  258. pipeline.DeployItem(&testPipelineItem{})
  259. f, _ := pipeline.GetFeature("power")
  260. assert.True(t, f)
  261. }
  262. func TestPipelineError(t *testing.T) {
  263. pipeline := NewPipeline(testRepository)
  264. item := &testPipelineItem{}
  265. item.TestError = true
  266. pipeline.AddItem(item)
  267. pipeline.Initialize(map[string]interface{}{})
  268. commits := make([]*object.Commit, 1)
  269. commits[0], _ = testRepository.CommitObject(plumbing.NewHash(
  270. "af9ddc0db70f09f3f27b4b98e415592a7485171c"))
  271. result, err := pipeline.Run(commits)
  272. assert.Nil(t, result)
  273. assert.NotNil(t, err)
  274. }
  275. func TestPipelineSerialize(t *testing.T) {
  276. pipeline := NewPipeline(testRepository)
  277. pipeline.SetFeature(FeatureUast)
  278. pipeline.DeployItem(&BurndownAnalysis{})
  279. facts := map[string]interface{}{}
  280. facts["Pipeline.DryRun"] = true
  281. tmpdir, _ := ioutil.TempDir("", "hercules-")
  282. defer os.RemoveAll(tmpdir)
  283. dotpath := path.Join(tmpdir, "graph.dot")
  284. facts["Pipeline.DumpPath"] = dotpath
  285. pipeline.Initialize(facts)
  286. bdot, _ := ioutil.ReadFile(dotpath)
  287. dot := string(bdot)
  288. assert.Equal(t, `digraph Hercules {
  289. "6 BlobCache" -> "7 [blob_cache]"
  290. "0 DaysSinceStart" -> "3 [day]"
  291. "9 FileDiff" -> "11 [file_diff]"
  292. "15 FileDiffRefiner" -> "16 Burndown"
  293. "1 IdentityDetector" -> "4 [author]"
  294. "8 RenameAnalysis" -> "16 Burndown"
  295. "8 RenameAnalysis" -> "9 FileDiff"
  296. "8 RenameAnalysis" -> "10 UAST"
  297. "8 RenameAnalysis" -> "13 UASTChanges"
  298. "2 TreeDiff" -> "5 [changes]"
  299. "10 UAST" -> "12 [uasts]"
  300. "13 UASTChanges" -> "14 [changed_uasts]"
  301. "4 [author]" -> "16 Burndown"
  302. "7 [blob_cache]" -> "16 Burndown"
  303. "7 [blob_cache]" -> "9 FileDiff"
  304. "7 [blob_cache]" -> "8 RenameAnalysis"
  305. "7 [blob_cache]" -> "10 UAST"
  306. "14 [changed_uasts]" -> "15 FileDiffRefiner"
  307. "5 [changes]" -> "6 BlobCache"
  308. "5 [changes]" -> "8 RenameAnalysis"
  309. "3 [day]" -> "16 Burndown"
  310. "11 [file_diff]" -> "15 FileDiffRefiner"
  311. "12 [uasts]" -> "13 UASTChanges"
  312. }`, dot)
  313. }
  314. func TestPipelineSerializeNoUast(t *testing.T) {
  315. pipeline := NewPipeline(testRepository)
  316. // pipeline.SetFeature(FeatureUast)
  317. pipeline.DeployItem(&BurndownAnalysis{})
  318. facts := map[string]interface{}{}
  319. facts["Pipeline.DryRun"] = true
  320. tmpdir, _ := ioutil.TempDir("", "hercules-")
  321. defer os.RemoveAll(tmpdir)
  322. dotpath := path.Join(tmpdir, "graph.dot")
  323. facts["Pipeline.DumpPath"] = dotpath
  324. pipeline.Initialize(facts)
  325. bdot, _ := ioutil.ReadFile(dotpath)
  326. dot := string(bdot)
  327. assert.Equal(t, `digraph Hercules {
  328. "6 BlobCache" -> "7 [blob_cache]"
  329. "0 DaysSinceStart" -> "3 [day]"
  330. "9 FileDiff" -> "10 [file_diff]"
  331. "1 IdentityDetector" -> "4 [author]"
  332. "8 RenameAnalysis" -> "11 Burndown"
  333. "8 RenameAnalysis" -> "9 FileDiff"
  334. "2 TreeDiff" -> "5 [changes]"
  335. "4 [author]" -> "11 Burndown"
  336. "7 [blob_cache]" -> "11 Burndown"
  337. "7 [blob_cache]" -> "9 FileDiff"
  338. "7 [blob_cache]" -> "8 RenameAnalysis"
  339. "5 [changes]" -> "6 BlobCache"
  340. "5 [changes]" -> "8 RenameAnalysis"
  341. "3 [day]" -> "11 Burndown"
  342. "10 [file_diff]" -> "11 Burndown"
  343. }`, dot)
  344. }
  345. func TestCommonAnalysisResultMerge(t *testing.T) {
  346. c1 := CommonAnalysisResult{
  347. BeginTime: 1513620635, EndTime: 1513720635, CommitsNumber: 1, RunTime: 100}
  348. assert.Equal(t, c1.BeginTimeAsTime().Unix(), int64(1513620635))
  349. assert.Equal(t, c1.EndTimeAsTime().Unix(), int64(1513720635))
  350. c2 := CommonAnalysisResult{
  351. BeginTime: 1513620535, EndTime: 1513730635, CommitsNumber: 2, RunTime: 200}
  352. c1.Merge(&c2)
  353. assert.Equal(t, c1.BeginTime, int64(1513620535))
  354. assert.Equal(t, c1.EndTime, int64(1513730635))
  355. assert.Equal(t, c1.CommitsNumber, 3)
  356. assert.Equal(t, c1.RunTime.Nanoseconds(), int64(300))
  357. }
  358. func TestCommonAnalysisResultMetadata(t *testing.T) {
  359. c1 := &CommonAnalysisResult{
  360. BeginTime: 1513620635, EndTime: 1513720635, CommitsNumber: 1, RunTime: 100 * 1e6}
  361. meta := &pb.Metadata{}
  362. c1 = MetadataToCommonAnalysisResult(c1.FillMetadata(meta))
  363. assert.Equal(t, c1.BeginTimeAsTime().Unix(), int64(1513620635))
  364. assert.Equal(t, c1.EndTimeAsTime().Unix(), int64(1513720635))
  365. assert.Equal(t, c1.CommitsNumber, 1)
  366. assert.Equal(t, c1.RunTime.Nanoseconds(), int64(100*1e6))
  367. }
  368. func TestPipelineResolveIntegration(t *testing.T) {
  369. pipeline := NewPipeline(testRepository)
  370. pipeline.DeployItem(&BurndownAnalysis{})
  371. pipeline.DeployItem(&CouplesAnalysis{})
  372. pipeline.Initialize(nil)
  373. }
  374. func TestConfigurationOptionTypeString(t *testing.T) {
  375. opt := ConfigurationOptionType(0)
  376. assert.Equal(t, opt.String(), "")
  377. opt = ConfigurationOptionType(1)
  378. assert.Equal(t, opt.String(), "int")
  379. opt = ConfigurationOptionType(2)
  380. assert.Equal(t, opt.String(), "string")
  381. opt = ConfigurationOptionType(3)
  382. assert.Equal(t, opt.String(), "float")
  383. opt = ConfigurationOptionType(4)
  384. assert.Panics(t, func() { _ = opt.String() })
  385. }
  386. func TestConfigurationOptionFormatDefault(t *testing.T) {
  387. opt := ConfigurationOption{Type: StringConfigurationOption, Default: "ololo"}
  388. assert.Equal(t, opt.FormatDefault(), "\"ololo\"")
  389. opt = ConfigurationOption{Type: IntConfigurationOption, Default: 7}
  390. assert.Equal(t, opt.FormatDefault(), "7")
  391. opt = ConfigurationOption{Type: BoolConfigurationOption, Default: false}
  392. assert.Equal(t, opt.FormatDefault(), "false")
  393. opt = ConfigurationOption{Type: FloatConfigurationOption, Default: 0.5}
  394. assert.Equal(t, opt.FormatDefault(), "0.5")
  395. }
  396. func init() {
  397. cwd, err := os.Getwd()
  398. if err == nil {
  399. testRepository, err = git.PlainOpen(cwd)
  400. if err == nil {
  401. iter, err := testRepository.CommitObjects()
  402. if err == nil {
  403. commits := -1
  404. for ; err != io.EOF; _, err = iter.Next() {
  405. if err != nil {
  406. panic(err)
  407. }
  408. commits++
  409. if commits >= 100 {
  410. return
  411. }
  412. }
  413. }
  414. }
  415. }
  416. testRepository, _ = git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
  417. URL: "https://github.com/src-d/hercules",
  418. })
  419. }