pipeline_test.go 12 KB

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