pipeline_test.go 14 KB

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