pipeline_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. package core
  2. import (
  3. "errors"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  7. "os"
  8. "testing"
  9. "time"
  10. "github.com/stretchr/testify/assert"
  11. "gopkg.in/src-d/go-git.v4"
  12. "gopkg.in/src-d/go-git.v4/plumbing"
  13. "gopkg.in/src-d/go-git.v4/plumbing/object"
  14. "gopkg.in/src-d/hercules.v4/internal/pb"
  15. "gopkg.in/src-d/hercules.v4/internal/test"
  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. type dependingTestPipelineItem struct {
  80. DependencySatisfied bool
  81. TestNilConsumeReturn bool
  82. }
  83. func (item *dependingTestPipelineItem) Name() string {
  84. return "Test2"
  85. }
  86. func (item *dependingTestPipelineItem) Provides() []string {
  87. arr := [...]string{"test2"}
  88. return arr[:]
  89. }
  90. func (item *dependingTestPipelineItem) Requires() []string {
  91. arr := [...]string{"test"}
  92. return arr[:]
  93. }
  94. func (item *dependingTestPipelineItem) ListConfigurationOptions() []ConfigurationOption {
  95. options := [...]ConfigurationOption{{
  96. Name: "TestOption2",
  97. Description: "The option description.",
  98. Flag: "test-option2",
  99. Type: IntConfigurationOption,
  100. Default: 10,
  101. }}
  102. return options[:]
  103. }
  104. func (item *dependingTestPipelineItem) Configure(facts map[string]interface{}) {
  105. }
  106. func (item *dependingTestPipelineItem) Initialize(repository *git.Repository) {
  107. }
  108. func (item *dependingTestPipelineItem) Flag() string {
  109. return "depflag"
  110. }
  111. func (item *dependingTestPipelineItem) Consume(deps map[string]interface{}) (map[string]interface{}, error) {
  112. _, exists := deps["test"]
  113. item.DependencySatisfied = exists
  114. if !item.TestNilConsumeReturn {
  115. return map[string]interface{}{"test2": item}, nil
  116. }
  117. return nil, nil
  118. }
  119. func (item *dependingTestPipelineItem) Finalize() interface{} {
  120. return true
  121. }
  122. func (item *dependingTestPipelineItem) Serialize(result interface{}, binary bool, writer io.Writer) error {
  123. return nil
  124. }
  125. func TestPipelineFacts(t *testing.T) {
  126. pipeline := NewPipeline(test.Repository)
  127. pipeline.SetFact("fact", "value")
  128. assert.Equal(t, pipeline.GetFact("fact"), "value")
  129. }
  130. func TestPipelineFeatures(t *testing.T) {
  131. pipeline := NewPipeline(test.Repository)
  132. pipeline.SetFeature("feat")
  133. val, _ := pipeline.GetFeature("feat")
  134. assert.True(t, val)
  135. _, exists := pipeline.GetFeature("!")
  136. assert.False(t, exists)
  137. Registry.featureFlags.Set("777")
  138. defer func() {
  139. Registry.featureFlags = arrayFeatureFlags{Flags: []string{}, Choices: map[string]bool{}}
  140. }()
  141. pipeline.SetFeaturesFromFlags()
  142. _, exists = pipeline.GetFeature("777")
  143. assert.False(t, exists)
  144. assert.Panics(t, func() {
  145. pipeline.SetFeaturesFromFlags(
  146. &PipelineItemRegistry{}, &PipelineItemRegistry{})
  147. })
  148. }
  149. func TestPipelineRun(t *testing.T) {
  150. pipeline := NewPipeline(test.Repository)
  151. item := &testPipelineItem{}
  152. pipeline.AddItem(item)
  153. pipeline.Initialize(map[string]interface{}{})
  154. assert.True(t, item.Initialized)
  155. commits := make([]*object.Commit, 1)
  156. commits[0], _ = test.Repository.CommitObject(plumbing.NewHash(
  157. "af9ddc0db70f09f3f27b4b98e415592a7485171c"))
  158. result, err := pipeline.Run(commits)
  159. assert.Nil(t, err)
  160. assert.Equal(t, 2, len(result))
  161. assert.Equal(t, item, result[item].(*testPipelineItem))
  162. common := result[nil].(*CommonAnalysisResult)
  163. assert.Equal(t, common.BeginTime, int64(1481719092))
  164. assert.Equal(t, common.EndTime, int64(1481719092))
  165. assert.Equal(t, common.CommitsNumber, 1)
  166. assert.True(t, common.RunTime.Nanoseconds()/1e6 < 100)
  167. assert.True(t, item.DepsConsumed)
  168. assert.True(t, item.CommitMatches)
  169. assert.True(t, item.IndexMatches)
  170. pipeline.RemoveItem(item)
  171. result, err = pipeline.Run(commits)
  172. assert.Nil(t, err)
  173. assert.Equal(t, 1, len(result))
  174. }
  175. func TestPipelineOnProgress(t *testing.T) {
  176. pipeline := NewPipeline(test.Repository)
  177. var progressOk1, progressOk2 bool
  178. onProgress := func(step int, total int) {
  179. if step == 0 && total == 1 {
  180. progressOk1 = true
  181. }
  182. if step == 1 && total == 1 && progressOk1 {
  183. progressOk2 = true
  184. }
  185. }
  186. pipeline.OnProgress = onProgress
  187. commits := make([]*object.Commit, 1)
  188. commits[0], _ = test.Repository.CommitObject(plumbing.NewHash(
  189. "af9ddc0db70f09f3f27b4b98e415592a7485171c"))
  190. result, err := pipeline.Run(commits)
  191. assert.Nil(t, err)
  192. assert.Equal(t, 1, len(result))
  193. assert.True(t, progressOk1)
  194. assert.True(t, progressOk2)
  195. }
  196. func TestPipelineCommits(t *testing.T) {
  197. pipeline := NewPipeline(test.Repository)
  198. commits := pipeline.Commits()
  199. assert.True(t, len(commits) >= 90)
  200. assert.Equal(t, commits[0].Hash, plumbing.NewHash(
  201. "cce947b98a050c6d356bc6ba95030254914027b1"))
  202. assert.Equal(t, commits[89].Hash, plumbing.NewHash(
  203. "6db8065cdb9bb0758f36a7e75fc72ab95f9e8145"))
  204. assert.NotEqual(t, commits[len(commits)-1], commits[len(commits)-2])
  205. }
  206. func TestLoadCommitsFromFile(t *testing.T) {
  207. tmp, err := ioutil.TempFile("", "hercules-test-")
  208. assert.Nil(t, err)
  209. tmp.WriteString("cce947b98a050c6d356bc6ba95030254914027b1\n6db8065cdb9bb0758f36a7e75fc72ab95f9e8145")
  210. tmp.Close()
  211. defer os.Remove(tmp.Name())
  212. commits, err := LoadCommitsFromFile(tmp.Name(), test.Repository)
  213. assert.Nil(t, err)
  214. assert.Equal(t, len(commits), 2)
  215. assert.Equal(t, commits[0].Hash, plumbing.NewHash(
  216. "cce947b98a050c6d356bc6ba95030254914027b1"))
  217. assert.Equal(t, commits[1].Hash, plumbing.NewHash(
  218. "6db8065cdb9bb0758f36a7e75fc72ab95f9e8145"))
  219. commits, err = LoadCommitsFromFile("/WAT?xxx!", test.Repository)
  220. assert.Nil(t, commits)
  221. assert.NotNil(t, err)
  222. tmp, err = ioutil.TempFile("", "hercules-test-")
  223. assert.Nil(t, err)
  224. tmp.WriteString("WAT")
  225. tmp.Close()
  226. defer os.Remove(tmp.Name())
  227. commits, err = LoadCommitsFromFile(tmp.Name(), test.Repository)
  228. assert.Nil(t, commits)
  229. assert.NotNil(t, err)
  230. tmp, err = ioutil.TempFile("", "hercules-test-")
  231. assert.Nil(t, err)
  232. tmp.WriteString("ffffffffffffffffffffffffffffffffffffffff")
  233. tmp.Close()
  234. defer os.Remove(tmp.Name())
  235. commits, err = LoadCommitsFromFile(tmp.Name(), test.Repository)
  236. assert.Nil(t, commits)
  237. assert.NotNil(t, err)
  238. }
  239. func TestPipelineDeps(t *testing.T) {
  240. pipeline := NewPipeline(test.Repository)
  241. item1 := &dependingTestPipelineItem{}
  242. item2 := &testPipelineItem{}
  243. pipeline.AddItem(item1)
  244. pipeline.AddItem(item2)
  245. assert.Equal(t, pipeline.Len(), 2)
  246. pipeline.Initialize(map[string]interface{}{})
  247. commits := make([]*object.Commit, 1)
  248. commits[0], _ = test.Repository.CommitObject(plumbing.NewHash(
  249. "af9ddc0db70f09f3f27b4b98e415592a7485171c"))
  250. result, err := pipeline.Run(commits)
  251. assert.Nil(t, err)
  252. assert.True(t, result[item1].(bool))
  253. assert.Equal(t, result[item2], item2)
  254. item1.TestNilConsumeReturn = true
  255. assert.Panics(t, func() { pipeline.Run(commits) })
  256. }
  257. func TestPipelineDeployFeatures(t *testing.T) {
  258. pipeline := NewPipeline(test.Repository)
  259. pipeline.DeployItem(&testPipelineItem{})
  260. f, _ := pipeline.GetFeature("power")
  261. assert.True(t, f)
  262. }
  263. func TestPipelineError(t *testing.T) {
  264. pipeline := NewPipeline(test.Repository)
  265. item := &testPipelineItem{}
  266. item.TestError = true
  267. pipeline.AddItem(item)
  268. pipeline.Initialize(map[string]interface{}{})
  269. commits := make([]*object.Commit, 1)
  270. commits[0], _ = test.Repository.CommitObject(plumbing.NewHash(
  271. "af9ddc0db70f09f3f27b4b98e415592a7485171c"))
  272. result, err := pipeline.Run(commits)
  273. assert.Nil(t, result)
  274. assert.NotNil(t, err)
  275. }
  276. func TestCommonAnalysisResultMerge(t *testing.T) {
  277. c1 := CommonAnalysisResult{
  278. BeginTime: 1513620635, EndTime: 1513720635, CommitsNumber: 1, RunTime: 100}
  279. assert.Equal(t, c1.BeginTimeAsTime().Unix(), int64(1513620635))
  280. assert.Equal(t, c1.EndTimeAsTime().Unix(), int64(1513720635))
  281. c2 := CommonAnalysisResult{
  282. BeginTime: 1513620535, EndTime: 1513730635, CommitsNumber: 2, RunTime: 200}
  283. c1.Merge(&c2)
  284. assert.Equal(t, c1.BeginTime, int64(1513620535))
  285. assert.Equal(t, c1.EndTime, int64(1513730635))
  286. assert.Equal(t, c1.CommitsNumber, 3)
  287. assert.Equal(t, c1.RunTime.Nanoseconds(), int64(300))
  288. }
  289. func TestCommonAnalysisResultMetadata(t *testing.T) {
  290. c1 := &CommonAnalysisResult{
  291. BeginTime: 1513620635, EndTime: 1513720635, CommitsNumber: 1, RunTime: 100 * 1e6}
  292. meta := &pb.Metadata{}
  293. c1 = MetadataToCommonAnalysisResult(c1.FillMetadata(meta))
  294. assert.Equal(t, c1.BeginTimeAsTime().Unix(), int64(1513620635))
  295. assert.Equal(t, c1.EndTimeAsTime().Unix(), int64(1513720635))
  296. assert.Equal(t, c1.CommitsNumber, 1)
  297. assert.Equal(t, c1.RunTime.Nanoseconds(), int64(100*1e6))
  298. }
  299. func TestConfigurationOptionTypeString(t *testing.T) {
  300. opt := ConfigurationOptionType(0)
  301. assert.Equal(t, opt.String(), "")
  302. opt = ConfigurationOptionType(1)
  303. assert.Equal(t, opt.String(), "int")
  304. opt = ConfigurationOptionType(2)
  305. assert.Equal(t, opt.String(), "string")
  306. opt = ConfigurationOptionType(3)
  307. assert.Equal(t, opt.String(), "float")
  308. opt = ConfigurationOptionType(4)
  309. assert.Equal(t, opt.String(), "string")
  310. opt = ConfigurationOptionType(5)
  311. assert.Panics(t, func() { _ = opt.String() })
  312. }
  313. func TestConfigurationOptionFormatDefault(t *testing.T) {
  314. opt := ConfigurationOption{Type: StringConfigurationOption, Default: "ololo"}
  315. assert.Equal(t, opt.FormatDefault(), "\"ololo\"")
  316. opt = ConfigurationOption{Type: IntConfigurationOption, Default: 7}
  317. assert.Equal(t, opt.FormatDefault(), "7")
  318. opt = ConfigurationOption{Type: BoolConfigurationOption, Default: false}
  319. assert.Equal(t, opt.FormatDefault(), "false")
  320. opt = ConfigurationOption{Type: FloatConfigurationOption, Default: 0.5}
  321. assert.Equal(t, opt.FormatDefault(), "0.5")
  322. }
  323. func TestPrepareRunPlanTiny(t *testing.T) {
  324. rootCommit, err := test.Repository.CommitObject(plumbing.NewHash(
  325. "cce947b98a050c6d356bc6ba95030254914027b1"))
  326. if err != nil {
  327. t.Fatal(err)
  328. }
  329. plan := prepareRunPlan([]*object.Commit{rootCommit})
  330. assert.Len(t, plan, 1)
  331. assert.Equal(t, runActionCommit, plan[0].Action)
  332. assert.Equal(t, 0, plan[0].Items[0])
  333. assert.Equal(t, "cce947b98a050c6d356bc6ba95030254914027b1", plan[0].Commit.Hash.String())
  334. }
  335. func TestPrepareRunPlanSmall(t *testing.T) {
  336. cit, err := test.Repository.Log(&git.LogOptions{From: plumbing.ZeroHash})
  337. if err != nil {
  338. panic(err)
  339. }
  340. defer cit.Close()
  341. var commits []*object.Commit
  342. timeCutoff := time.Date(2016, 12, 15, 0, 0, 0, 0, time.FixedZone("CET", 7200))
  343. cit.ForEach(func(commit *object.Commit) error {
  344. reliableTime := time.Date(commit.Author.When.Year(), commit.Author.When.Month(),
  345. commit.Author.When.Day(), commit.Author.When.Hour(), commit.Author.When.Minute(),
  346. commit.Author.When.Second(), 0, time.FixedZone("CET", 7200))
  347. if reliableTime.Before(timeCutoff) {
  348. commits = append(commits, commit)
  349. }
  350. return nil
  351. })
  352. plan := prepareRunPlan(commits)
  353. /*for _, p := range plan {
  354. if p.Commit != nil {
  355. fmt.Println(p.Action, p.Commit.Hash.String(), p.Items)
  356. } else {
  357. fmt.Println(p.Action, strings.Repeat(" ", 40), p.Items)
  358. }
  359. }*/
  360. // fork, merge and one artificial commit per branch
  361. assert.Len(t, plan, len(commits))
  362. assert.Equal(t, runActionCommit, plan[0].Action)
  363. assert.Equal(t, 0, plan[0].Items[0])
  364. assert.Equal(t, "cce947b98a050c6d356bc6ba95030254914027b1", plan[0].Commit.Hash.String())
  365. assert.Equal(t, runActionCommit, plan[1].Action)
  366. assert.Equal(t, 0, plan[1].Items[0])
  367. assert.Equal(t, "a3ee37f91f0d705ec9c41ae88426f0ae44b2fbc3", plan[1].Commit.Hash.String())
  368. assert.Equal(t, runActionCommit, plan[9].Action)
  369. assert.Equal(t, 0, plan[9].Items[0])
  370. assert.Equal(t, "a28e9064c70618dc9d68e1401b889975e0680d11", plan[9].Commit.Hash.String())
  371. }
  372. func TestPrepareRunPlanBig(t *testing.T) {
  373. cases := [][7]int {
  374. {2017, 8, 9, 0, 0, 0, 0},
  375. {2017, 8, 10, 0, 0, 0, 0},
  376. {2017, 8, 24, 1, 1, 1, 1},
  377. {2017, 9, 19, 1-2, 1, 1, 1},
  378. {2017, 9, 23, 1-2, 1, 1, 1},
  379. {2017, 12, 8, 1, 1, 1, 1},
  380. {2017, 12, 9, 1, 1, 1, 1},
  381. {2017, 12, 10, 1, 1, 1, 1},
  382. {2017, 12, 11, 2, 2, 2, 2},
  383. {2017, 12, 19, 4, 4, 4, 4},
  384. {2017, 12, 27, 4, 4, 4, 4},
  385. {2018, 1, 10, 4, 4, 4, 4},
  386. {2018, 1, 16, 4, 4, 4, 4},
  387. {2018, 1, 18, 7, 6, 7, 7},
  388. {2018, 1, 23, 8, 6, 8, 8},
  389. {2018, 3, 12, 9, 7, 9, 9},
  390. {2018, 5, 13, 9, 7, 9, 9},
  391. {2018, 5, 16, 13, 9, 13, 13},
  392. }
  393. for _, testCase := range cases {
  394. cit, err := test.Repository.Log(&git.LogOptions{From: plumbing.ZeroHash})
  395. if err != nil {
  396. panic(err)
  397. }
  398. defer cit.Close()
  399. var commits []*object.Commit
  400. timeCutoff := time.Date(
  401. testCase[0], time.Month(testCase[1]), testCase[2], 0, 0, 0, 0, time.FixedZone("CET", 7200))
  402. cit.ForEach(func(commit *object.Commit) error {
  403. reliableTime := time.Date(commit.Author.When.Year(), commit.Author.When.Month(),
  404. commit.Author.When.Day(), commit.Author.When.Hour(), commit.Author.When.Minute(),
  405. commit.Author.When.Second(), 0, time.FixedZone("CET", 7200))
  406. if reliableTime.Before(timeCutoff) {
  407. commits = append(commits, commit)
  408. }
  409. return nil
  410. })
  411. plan := prepareRunPlan(commits)
  412. /*for _, p := range plan {
  413. if p.Commit != nil {
  414. fmt.Println(p.Action, p.Commit.Hash.String(), p.Items)
  415. } else {
  416. fmt.Println(p.Action, strings.Repeat(" ", 40), p.Items)
  417. }
  418. }*/
  419. numCommits := 0
  420. numForks := 0
  421. numMerges := 0
  422. numDeletes := 0
  423. for _, p := range plan {
  424. switch p.Action {
  425. case runActionCommit:
  426. numCommits++
  427. case runActionFork:
  428. numForks++
  429. case runActionMerge:
  430. numMerges++
  431. case runActionDelete:
  432. numDeletes++
  433. }
  434. }
  435. assert.Equal(t, numCommits, len(commits)+testCase[3], fmt.Sprintf("commits %v", testCase))
  436. assert.Equal(t, numForks, testCase[4], fmt.Sprintf("forks %v", testCase))
  437. assert.Equal(t, numMerges, testCase[5], fmt.Sprintf("merges %v", testCase))
  438. assert.Equal(t, numMerges, testCase[6], fmt.Sprintf("deletes %v", testCase))
  439. }
  440. }