pipeline_test.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  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.v6/internal/pb"
  15. "gopkg.in/src-d/hercules.v6/internal/test"
  16. )
  17. type testPipelineItem struct {
  18. Initialized bool
  19. DepsConsumed bool
  20. Forked bool
  21. Merged *bool
  22. CommitMatches bool
  23. IndexMatches bool
  24. MergeState *int
  25. TestError bool
  26. ConfigureRaises bool
  27. InitializeRaises bool
  28. }
  29. func (item *testPipelineItem) Name() string {
  30. return "Test"
  31. }
  32. func (item *testPipelineItem) Provides() []string {
  33. arr := [...]string{"test"}
  34. return arr[:]
  35. }
  36. func (item *testPipelineItem) Requires() []string {
  37. return []string{}
  38. }
  39. func (item *testPipelineItem) Configure(facts map[string]interface{}) error {
  40. if item.ConfigureRaises {
  41. return errors.New("test1")
  42. }
  43. return nil
  44. }
  45. func (item *testPipelineItem) ListConfigurationOptions() []ConfigurationOption {
  46. options := [...]ConfigurationOption{{
  47. Name: "TestOption",
  48. Description: "The option description.",
  49. Flag: "test-option",
  50. Type: IntConfigurationOption,
  51. Default: 10,
  52. }}
  53. return options[:]
  54. }
  55. func (item *testPipelineItem) Flag() string {
  56. return "mytest"
  57. }
  58. func (item *testPipelineItem) Description() string {
  59. return "description!"
  60. }
  61. func (item *testPipelineItem) Features() []string {
  62. f := [...]string{"power"}
  63. return f[:]
  64. }
  65. func (item *testPipelineItem) Initialize(repository *git.Repository) error {
  66. item.Initialized = repository != nil
  67. item.Merged = new(bool)
  68. item.MergeState = new(int)
  69. if item.InitializeRaises {
  70. return errors.New("test2")
  71. }
  72. return nil
  73. }
  74. func (item *testPipelineItem) Consume(deps map[string]interface{}) (map[string]interface{}, error) {
  75. if item.TestError {
  76. return nil, errors.New("error")
  77. }
  78. obj, exists := deps[DependencyCommit]
  79. item.DepsConsumed = exists
  80. if item.DepsConsumed {
  81. commit := obj.(*object.Commit)
  82. item.CommitMatches = commit.Hash == plumbing.NewHash(
  83. "af9ddc0db70f09f3f27b4b98e415592a7485171c")
  84. obj, item.DepsConsumed = deps[DependencyIndex]
  85. if item.DepsConsumed {
  86. item.IndexMatches = obj.(int) == 0
  87. }
  88. }
  89. obj, exists = deps[DependencyIsMerge]
  90. if exists {
  91. *item.MergeState++
  92. if obj.(bool) {
  93. *item.MergeState++
  94. }
  95. }
  96. return map[string]interface{}{"test": item}, nil
  97. }
  98. func (item *testPipelineItem) Fork(n int) []PipelineItem {
  99. result := make([]PipelineItem, n)
  100. for i := 0; i < n; i++ {
  101. result[i] = &testPipelineItem{Merged: item.Merged, MergeState: item.MergeState}
  102. }
  103. item.Forked = true
  104. return result
  105. }
  106. func (item *testPipelineItem) Merge(branches []PipelineItem) {
  107. *item.Merged = true
  108. }
  109. func (item *testPipelineItem) Finalize() interface{} {
  110. return item
  111. }
  112. func (item *testPipelineItem) Serialize(result interface{}, binary bool, writer io.Writer) error {
  113. return nil
  114. }
  115. type dependingTestPipelineItem struct {
  116. DependencySatisfied bool
  117. TestNilConsumeReturn bool
  118. }
  119. func (item *dependingTestPipelineItem) Name() string {
  120. return "Test2"
  121. }
  122. func (item *dependingTestPipelineItem) Provides() []string {
  123. arr := [...]string{"test2"}
  124. return arr[:]
  125. }
  126. func (item *dependingTestPipelineItem) Requires() []string {
  127. arr := [...]string{"test"}
  128. return arr[:]
  129. }
  130. func (item *dependingTestPipelineItem) ListConfigurationOptions() []ConfigurationOption {
  131. options := [...]ConfigurationOption{{
  132. Name: "TestOption2",
  133. Description: "The option description.",
  134. Flag: "test-option2",
  135. Type: IntConfigurationOption,
  136. Default: 10,
  137. }}
  138. return options[:]
  139. }
  140. func (item *dependingTestPipelineItem) Configure(facts map[string]interface{}) error {
  141. return nil
  142. }
  143. func (item *dependingTestPipelineItem) Initialize(repository *git.Repository) error {
  144. return nil
  145. }
  146. func (item *dependingTestPipelineItem) Flag() string {
  147. return "depflag"
  148. }
  149. func (item *dependingTestPipelineItem) Description() string {
  150. return "another description"
  151. }
  152. func (item *dependingTestPipelineItem) Consume(deps map[string]interface{}) (map[string]interface{}, error) {
  153. _, exists := deps["test"]
  154. item.DependencySatisfied = exists
  155. if !item.TestNilConsumeReturn {
  156. return map[string]interface{}{"test2": item}, nil
  157. }
  158. return nil, nil
  159. }
  160. func (item *dependingTestPipelineItem) Fork(n int) []PipelineItem {
  161. return make([]PipelineItem, n)
  162. }
  163. func (item *dependingTestPipelineItem) Merge(branches []PipelineItem) {
  164. }
  165. func (item *dependingTestPipelineItem) Finalize() interface{} {
  166. return true
  167. }
  168. func (item *dependingTestPipelineItem) Serialize(result interface{}, binary bool, writer io.Writer) error {
  169. return nil
  170. }
  171. func TestPipelineFacts(t *testing.T) {
  172. pipeline := NewPipeline(test.Repository)
  173. pipeline.SetFact("fact", "value")
  174. assert.Equal(t, pipeline.GetFact("fact"), "value")
  175. }
  176. func TestPipelineFeatures(t *testing.T) {
  177. pipeline := NewPipeline(test.Repository)
  178. pipeline.SetFeature("feat")
  179. val, _ := pipeline.GetFeature("feat")
  180. assert.True(t, val)
  181. _, exists := pipeline.GetFeature("!")
  182. assert.False(t, exists)
  183. Registry.featureFlags.Set("777")
  184. defer func() {
  185. Registry.featureFlags = arrayFeatureFlags{Flags: []string{}, Choices: map[string]bool{}}
  186. }()
  187. pipeline.SetFeaturesFromFlags()
  188. _, exists = pipeline.GetFeature("777")
  189. assert.False(t, exists)
  190. assert.Panics(t, func() {
  191. pipeline.SetFeaturesFromFlags(
  192. &PipelineItemRegistry{}, &PipelineItemRegistry{})
  193. })
  194. }
  195. func TestPipelineErrors(t *testing.T) {
  196. pipeline := NewPipeline(test.Repository)
  197. pipeline.SetFact("fact", "value")
  198. assert.Equal(t, pipeline.GetFact("fact"), "value")
  199. item := &testPipelineItem{}
  200. pipeline.AddItem(item)
  201. item.ConfigureRaises = true
  202. err := pipeline.Initialize(map[string]interface{}{})
  203. assert.NotNil(t, err)
  204. assert.Contains(t, err.Error(), "configure")
  205. assert.Contains(t, err.Error(), "test1")
  206. item.ConfigureRaises = false
  207. item.InitializeRaises = true
  208. err = pipeline.Initialize(map[string]interface{}{})
  209. assert.NotNil(t, err)
  210. assert.Contains(t, err.Error(), "initialize")
  211. assert.Contains(t, err.Error(), "test2")
  212. }
  213. func TestPipelineRun(t *testing.T) {
  214. pipeline := NewPipeline(test.Repository)
  215. item := &testPipelineItem{}
  216. pipeline.AddItem(item)
  217. assert.Nil(t, pipeline.Initialize(map[string]interface{}{}))
  218. assert.True(t, item.Initialized)
  219. commits := make([]*object.Commit, 1)
  220. commits[0], _ = test.Repository.CommitObject(plumbing.NewHash(
  221. "af9ddc0db70f09f3f27b4b98e415592a7485171c"))
  222. result, err := pipeline.Run(commits)
  223. assert.Nil(t, err)
  224. assert.Equal(t, 2, len(result))
  225. assert.Equal(t, item, result[item].(*testPipelineItem))
  226. common := result[nil].(*CommonAnalysisResult)
  227. assert.Equal(t, common.BeginTime, int64(1481719198))
  228. assert.Equal(t, common.EndTime, int64(1481719198))
  229. assert.Equal(t, common.CommitsNumber, 1)
  230. assert.True(t, common.RunTime.Nanoseconds()/1e6 < 100)
  231. assert.Len(t, common.RunTimePerItem, 1)
  232. for key, val := range common.RunTimePerItem {
  233. assert.True(t, val >= 0, key)
  234. }
  235. assert.True(t, item.DepsConsumed)
  236. assert.True(t, item.CommitMatches)
  237. assert.True(t, item.IndexMatches)
  238. assert.Equal(t, 1, *item.MergeState)
  239. assert.True(t, item.Forked)
  240. assert.False(t, *item.Merged)
  241. pipeline.RemoveItem(item)
  242. result, err = pipeline.Run(commits)
  243. assert.Nil(t, err)
  244. assert.Equal(t, 1, len(result))
  245. }
  246. func TestPipelineRunBranches(t *testing.T) {
  247. pipeline := NewPipeline(test.Repository)
  248. item := &testPipelineItem{}
  249. pipeline.AddItem(item)
  250. pipeline.Initialize(map[string]interface{}{})
  251. assert.True(t, item.Initialized)
  252. commits := make([]*object.Commit, 5)
  253. hashes := []string{
  254. "6db8065cdb9bb0758f36a7e75fc72ab95f9e8145",
  255. "f30daba81ff2bf0b3ba02a1e1441e74f8a4f6fee",
  256. "8a03b5620b1caa72ec9cb847ea88332621e2950a",
  257. "dd9dd084d5851d7dc4399fc7dbf3d8292831ebc5",
  258. "f4ed0405b14f006c0744029d87ddb3245607587a",
  259. }
  260. for i, h := range hashes {
  261. var err error
  262. commits[i], err = test.Repository.CommitObject(plumbing.NewHash(h))
  263. if err != nil {
  264. t.Fatal(err)
  265. }
  266. }
  267. result, err := pipeline.Run(commits)
  268. assert.Nil(t, err)
  269. assert.True(t, item.Forked)
  270. assert.True(t, *item.Merged)
  271. assert.Equal(t, 2, len(result))
  272. assert.Equal(t, item, result[item].(*testPipelineItem))
  273. common := result[nil].(*CommonAnalysisResult)
  274. assert.Equal(t, common.CommitsNumber, 5)
  275. assert.Equal(t, *item.MergeState, 8)
  276. }
  277. func TestPipelineOnProgress(t *testing.T) {
  278. pipeline := NewPipeline(test.Repository)
  279. progressOk := 0
  280. onProgress := func(step int, total int) {
  281. if step == 1 && total == 4 {
  282. progressOk++
  283. }
  284. if step == 2 && total == 4 {
  285. progressOk++
  286. }
  287. if step == 3 && total == 4 {
  288. progressOk++
  289. }
  290. if step == 4 && total == 4 {
  291. progressOk++
  292. }
  293. }
  294. pipeline.OnProgress = onProgress
  295. commits := make([]*object.Commit, 1)
  296. commits[0], _ = test.Repository.CommitObject(plumbing.NewHash(
  297. "af9ddc0db70f09f3f27b4b98e415592a7485171c"))
  298. result, err := pipeline.Run(commits)
  299. assert.Nil(t, err)
  300. assert.Equal(t, 1, len(result))
  301. assert.Equal(t, 4, progressOk)
  302. }
  303. func TestPipelineCommitsFull(t *testing.T) {
  304. pipeline := NewPipeline(test.Repository)
  305. commits, err := pipeline.Commits(false)
  306. assert.Nil(t, err)
  307. assert.True(t, len(commits) >= 100)
  308. hashMap := map[plumbing.Hash]bool{}
  309. for _, c := range commits {
  310. hashMap[c.Hash] = true
  311. }
  312. assert.Equal(t, len(commits), len(hashMap))
  313. assert.Contains(t, hashMap, plumbing.NewHash(
  314. "cce947b98a050c6d356bc6ba95030254914027b1"))
  315. assert.Contains(t, hashMap, plumbing.NewHash(
  316. "a3ee37f91f0d705ec9c41ae88426f0ae44b2fbc3"))
  317. }
  318. func TestPipelineCommitsFirstParent(t *testing.T) {
  319. pipeline := NewPipeline(test.Repository)
  320. commits, err := pipeline.Commits(true)
  321. assert.Nil(t, err)
  322. assert.True(t, len(commits) >= 100)
  323. hashMap := map[plumbing.Hash]bool{}
  324. for _, c := range commits {
  325. hashMap[c.Hash] = true
  326. }
  327. assert.Equal(t, len(commits), len(hashMap))
  328. assert.Contains(t, hashMap, plumbing.NewHash(
  329. "cce947b98a050c6d356bc6ba95030254914027b1"))
  330. assert.NotContains(t, hashMap, plumbing.NewHash(
  331. "a3ee37f91f0d705ec9c41ae88426f0ae44b2fbc3"))
  332. }
  333. func TestLoadCommitsFromFile(t *testing.T) {
  334. tmp, err := ioutil.TempFile("", "hercules-test-")
  335. assert.Nil(t, err)
  336. tmp.WriteString("cce947b98a050c6d356bc6ba95030254914027b1\n6db8065cdb9bb0758f36a7e75fc72ab95f9e8145")
  337. tmp.Close()
  338. defer os.Remove(tmp.Name())
  339. commits, err := LoadCommitsFromFile(tmp.Name(), test.Repository)
  340. assert.Nil(t, err)
  341. assert.Equal(t, len(commits), 2)
  342. assert.Equal(t, commits[0].Hash, plumbing.NewHash(
  343. "cce947b98a050c6d356bc6ba95030254914027b1"))
  344. assert.Equal(t, commits[1].Hash, plumbing.NewHash(
  345. "6db8065cdb9bb0758f36a7e75fc72ab95f9e8145"))
  346. commits, err = LoadCommitsFromFile("/WAT?xxx!", test.Repository)
  347. assert.Nil(t, commits)
  348. assert.NotNil(t, err)
  349. tmp, err = ioutil.TempFile("", "hercules-test-")
  350. assert.Nil(t, err)
  351. tmp.WriteString("WAT")
  352. tmp.Close()
  353. defer os.Remove(tmp.Name())
  354. commits, err = LoadCommitsFromFile(tmp.Name(), test.Repository)
  355. assert.Nil(t, commits)
  356. assert.NotNil(t, err)
  357. tmp, err = ioutil.TempFile("", "hercules-test-")
  358. assert.Nil(t, err)
  359. tmp.WriteString("ffffffffffffffffffffffffffffffffffffffff")
  360. tmp.Close()
  361. defer os.Remove(tmp.Name())
  362. commits, err = LoadCommitsFromFile(tmp.Name(), test.Repository)
  363. assert.Nil(t, commits)
  364. assert.NotNil(t, err)
  365. }
  366. func TestPipelineDeps(t *testing.T) {
  367. pipeline := NewPipeline(test.Repository)
  368. item1 := &dependingTestPipelineItem{}
  369. item2 := &testPipelineItem{}
  370. pipeline.AddItem(item1)
  371. pipeline.AddItem(item2)
  372. assert.Equal(t, pipeline.Len(), 2)
  373. pipeline.Initialize(map[string]interface{}{})
  374. commits := make([]*object.Commit, 1)
  375. commits[0], _ = test.Repository.CommitObject(plumbing.NewHash(
  376. "af9ddc0db70f09f3f27b4b98e415592a7485171c"))
  377. result, err := pipeline.Run(commits)
  378. assert.Nil(t, err)
  379. assert.True(t, result[item1].(bool))
  380. assert.Equal(t, result[item2], item2)
  381. item1.TestNilConsumeReturn = true
  382. assert.Panics(t, func() { pipeline.Run(commits) })
  383. }
  384. func TestPipelineDeployFeatures(t *testing.T) {
  385. pipeline := NewPipeline(test.Repository)
  386. pipeline.DeployItem(&testPipelineItem{})
  387. f, _ := pipeline.GetFeature("power")
  388. assert.True(t, f)
  389. }
  390. func TestPipelineError(t *testing.T) {
  391. pipeline := NewPipeline(test.Repository)
  392. item := &testPipelineItem{}
  393. item.TestError = true
  394. pipeline.AddItem(item)
  395. pipeline.Initialize(map[string]interface{}{})
  396. commits := make([]*object.Commit, 1)
  397. commits[0], _ = test.Repository.CommitObject(plumbing.NewHash(
  398. "af9ddc0db70f09f3f27b4b98e415592a7485171c"))
  399. result, err := pipeline.Run(commits)
  400. assert.Nil(t, result)
  401. assert.NotNil(t, err)
  402. }
  403. func TestCommonAnalysisResultMerge(t *testing.T) {
  404. c1 := CommonAnalysisResult{
  405. BeginTime: 1513620635, EndTime: 1513720635, CommitsNumber: 1, RunTime: 100,
  406. RunTimePerItem: map[string]float64{"one": 1, "two": 2}}
  407. assert.Equal(t, c1.BeginTimeAsTime().Unix(), int64(1513620635))
  408. assert.Equal(t, c1.EndTimeAsTime().Unix(), int64(1513720635))
  409. c2 := CommonAnalysisResult{
  410. BeginTime: 1513620535, EndTime: 1513730635, CommitsNumber: 2, RunTime: 200,
  411. RunTimePerItem: map[string]float64{"two": 4, "three": 8}}
  412. c1.Merge(&c2)
  413. assert.Equal(t, c1.BeginTime, int64(1513620535))
  414. assert.Equal(t, c1.EndTime, int64(1513730635))
  415. assert.Equal(t, c1.CommitsNumber, 3)
  416. assert.Equal(t, c1.RunTime.Nanoseconds(), int64(300))
  417. assert.Equal(t, c1.RunTimePerItem, map[string]float64{"one": 1, "two": 6, "three": 8})
  418. }
  419. func TestCommonAnalysisResultMetadata(t *testing.T) {
  420. c1 := &CommonAnalysisResult{
  421. BeginTime: 1513620635, EndTime: 1513720635, CommitsNumber: 1, RunTime: 100 * 1e6,
  422. RunTimePerItem: map[string]float64{"one": 1, "two": 2}}
  423. meta := &pb.Metadata{}
  424. c1 = MetadataToCommonAnalysisResult(c1.FillMetadata(meta))
  425. assert.Equal(t, c1.BeginTimeAsTime().Unix(), int64(1513620635))
  426. assert.Equal(t, c1.EndTimeAsTime().Unix(), int64(1513720635))
  427. assert.Equal(t, c1.CommitsNumber, 1)
  428. assert.Equal(t, c1.RunTime.Nanoseconds(), int64(100*1e6))
  429. assert.Equal(t, c1.RunTimePerItem, map[string]float64{"one": 1, "two": 2})
  430. }
  431. func TestConfigurationOptionTypeString(t *testing.T) {
  432. opt := ConfigurationOptionType(0)
  433. assert.Equal(t, opt.String(), "")
  434. opt = ConfigurationOptionType(1)
  435. assert.Equal(t, opt.String(), "int")
  436. opt = ConfigurationOptionType(2)
  437. assert.Equal(t, opt.String(), "string")
  438. opt = ConfigurationOptionType(3)
  439. assert.Equal(t, opt.String(), "float")
  440. opt = ConfigurationOptionType(4)
  441. assert.Equal(t, opt.String(), "string")
  442. opt = ConfigurationOptionType(5)
  443. assert.Panics(t, func() { _ = opt.String() })
  444. }
  445. func TestConfigurationOptionFormatDefault(t *testing.T) {
  446. opt := ConfigurationOption{Type: StringConfigurationOption, Default: "ololo"}
  447. assert.Equal(t, opt.FormatDefault(), "\"ololo\"")
  448. opt = ConfigurationOption{Type: IntConfigurationOption, Default: 7}
  449. assert.Equal(t, opt.FormatDefault(), "7")
  450. opt = ConfigurationOption{Type: BoolConfigurationOption, Default: false}
  451. assert.Equal(t, opt.FormatDefault(), "false")
  452. opt = ConfigurationOption{Type: FloatConfigurationOption, Default: 0.5}
  453. assert.Equal(t, opt.FormatDefault(), "0.5")
  454. }
  455. func TestPrepareRunPlanTiny(t *testing.T) {
  456. rootCommit, err := test.Repository.CommitObject(plumbing.NewHash(
  457. "cce947b98a050c6d356bc6ba95030254914027b1"))
  458. if err != nil {
  459. t.Fatal(err)
  460. }
  461. plan := prepareRunPlan([]*object.Commit{rootCommit})
  462. assert.Len(t, plan, 2)
  463. assert.Equal(t, runActionEmerge, plan[0].Action)
  464. assert.Equal(t, rootBranchIndex, plan[0].Items[0])
  465. assert.Equal(t, "cce947b98a050c6d356bc6ba95030254914027b1", plan[0].Commit.Hash.String())
  466. assert.Equal(t, runActionCommit, plan[1].Action)
  467. assert.Equal(t, rootBranchIndex, plan[1].Items[0])
  468. assert.Equal(t, "cce947b98a050c6d356bc6ba95030254914027b1", plan[1].Commit.Hash.String())
  469. }
  470. func TestPrepareRunPlanSmall(t *testing.T) {
  471. cit, err := test.Repository.Log(&git.LogOptions{From: plumbing.ZeroHash})
  472. if err != nil {
  473. panic(err)
  474. }
  475. defer cit.Close()
  476. var commits []*object.Commit
  477. timeCutoff := time.Date(2016, 12, 15, 0, 0, 0, 0, time.FixedZone("CET", 7200))
  478. cit.ForEach(func(commit *object.Commit) error {
  479. reliableTime := time.Date(commit.Author.When.Year(), commit.Author.When.Month(),
  480. commit.Author.When.Day(), commit.Author.When.Hour(), commit.Author.When.Minute(),
  481. commit.Author.When.Second(), 0, time.FixedZone("CET", 7200))
  482. if reliableTime.Before(timeCutoff) {
  483. commits = append(commits, commit)
  484. }
  485. return nil
  486. })
  487. plan := prepareRunPlan(commits)
  488. /*for _, p := range plan {
  489. if p.Commit != nil {
  490. fmt.Println(p.Action, p.Commit.Hash.String(), p.Items)
  491. } else {
  492. fmt.Println(p.Action, strings.Repeat(" ", 40), p.Items)
  493. }
  494. }*/
  495. // fork, merge and one artificial commit per branch
  496. assert.Len(t, plan, len(commits)+1)
  497. assert.Equal(t, runActionEmerge, plan[0].Action)
  498. assert.Equal(t, "cce947b98a050c6d356bc6ba95030254914027b1", plan[0].Commit.Hash.String())
  499. assert.Equal(t, rootBranchIndex, plan[0].Items[0])
  500. assert.Equal(t, runActionCommit, plan[1].Action)
  501. assert.Equal(t, rootBranchIndex, plan[1].Items[0])
  502. assert.Equal(t, "cce947b98a050c6d356bc6ba95030254914027b1", plan[1].Commit.Hash.String())
  503. assert.Equal(t, runActionCommit, plan[2].Action)
  504. assert.Equal(t, rootBranchIndex, plan[2].Items[0])
  505. assert.Equal(t, "a3ee37f91f0d705ec9c41ae88426f0ae44b2fbc3", plan[2].Commit.Hash.String())
  506. assert.Equal(t, runActionCommit, plan[10].Action)
  507. assert.Equal(t, rootBranchIndex, plan[10].Items[0])
  508. assert.Equal(t, "a28e9064c70618dc9d68e1401b889975e0680d11", plan[10].Commit.Hash.String())
  509. }
  510. func TestMergeDag(t *testing.T) {
  511. cit, err := test.Repository.Log(&git.LogOptions{From: plumbing.ZeroHash})
  512. if err != nil {
  513. panic(err)
  514. }
  515. defer cit.Close()
  516. var commits []*object.Commit
  517. timeCutoff := time.Date(2017, 8, 12, 0, 0, 0, 0, time.FixedZone("CET", 7200))
  518. cit.ForEach(func(commit *object.Commit) error {
  519. reliableTime := time.Date(commit.Author.When.Year(), commit.Author.When.Month(),
  520. commit.Author.When.Day(), commit.Author.When.Hour(), commit.Author.When.Minute(),
  521. commit.Author.When.Second(), 0, time.FixedZone("CET", 7200))
  522. if reliableTime.Before(timeCutoff) {
  523. commits = append(commits, commit)
  524. }
  525. return nil
  526. })
  527. hashes, dag := buildDag(commits)
  528. leaveRootComponent(hashes, dag)
  529. mergedDag, _ := mergeDag(hashes, dag)
  530. for key, vals := range mergedDag {
  531. if key != plumbing.NewHash("a28e9064c70618dc9d68e1401b889975e0680d11") &&
  532. key != plumbing.NewHash("db325a212d0bc99b470e000641d814745024bbd5") {
  533. assert.Len(t, vals, len(dag[key]), key.String())
  534. } else {
  535. mvals := map[string]bool{}
  536. for _, val := range vals {
  537. mvals[val.Hash.String()] = true
  538. }
  539. if key == plumbing.NewHash("a28e9064c70618dc9d68e1401b889975e0680d11") {
  540. assert.Contains(t, mvals, "db325a212d0bc99b470e000641d814745024bbd5")
  541. assert.Contains(t, mvals, "be9b61e09b08b98e64ed461a4004c9e2412f78ee")
  542. }
  543. if key == plumbing.NewHash("db325a212d0bc99b470e000641d814745024bbd5") {
  544. assert.Contains(t, mvals, "f30daba81ff2bf0b3ba02a1e1441e74f8a4f6fee")
  545. assert.Contains(t, mvals, "8a03b5620b1caa72ec9cb847ea88332621e2950a")
  546. }
  547. }
  548. }
  549. assert.Len(t, mergedDag, 8)
  550. assert.Contains(t, mergedDag, plumbing.NewHash("cce947b98a050c6d356bc6ba95030254914027b1"))
  551. assert.Contains(t, mergedDag, plumbing.NewHash("a3ee37f91f0d705ec9c41ae88426f0ae44b2fbc3"))
  552. assert.Contains(t, mergedDag, plumbing.NewHash("a28e9064c70618dc9d68e1401b889975e0680d11"))
  553. assert.Contains(t, mergedDag, plumbing.NewHash("be9b61e09b08b98e64ed461a4004c9e2412f78ee"))
  554. assert.Contains(t, mergedDag, plumbing.NewHash("db325a212d0bc99b470e000641d814745024bbd5"))
  555. assert.Contains(t, mergedDag, plumbing.NewHash("f30daba81ff2bf0b3ba02a1e1441e74f8a4f6fee"))
  556. assert.Contains(t, mergedDag, plumbing.NewHash("8a03b5620b1caa72ec9cb847ea88332621e2950a"))
  557. assert.Contains(t, mergedDag, plumbing.NewHash("dd9dd084d5851d7dc4399fc7dbf3d8292831ebc5"))
  558. queue := []plumbing.Hash{plumbing.NewHash("cce947b98a050c6d356bc6ba95030254914027b1")}
  559. visited := map[plumbing.Hash]bool{}
  560. for len(queue) > 0 {
  561. head := queue[len(queue)-1]
  562. queue = queue[:len(queue)-1]
  563. if visited[head] {
  564. continue
  565. }
  566. visited[head] = true
  567. for _, child := range mergedDag[head] {
  568. queue = append(queue, child.Hash)
  569. }
  570. }
  571. assert.Len(t, visited, 8)
  572. }
  573. func TestPrepareRunPlanBig(t *testing.T) {
  574. cases := [][7]int{
  575. {2017, 8, 9, 0, 0, 0, 0},
  576. {2017, 8, 10, 0, 0, 0, 0},
  577. {2017, 8, 24, 1, 1, 1, 1},
  578. {2017, 9, 19, 1 - 2, 1, 1, 1},
  579. {2017, 9, 23, 1 - 2, 1, 1, 1},
  580. {2017, 12, 8, 1, 1, 1, 1},
  581. {2017, 12, 9, 1, 1, 1, 1},
  582. {2017, 12, 10, 1, 1, 1, 1},
  583. {2017, 12, 11, 2, 2, 2, 2},
  584. {2017, 12, 19, 3, 3, 3, 3},
  585. {2017, 12, 27, 3, 3, 3, 3},
  586. {2018, 1, 10, 3, 3, 3, 3},
  587. {2018, 1, 16, 3, 3, 3, 3},
  588. {2018, 1, 18, 4, 5, 4, 4},
  589. {2018, 1, 23, 5, 5, 5, 5},
  590. {2018, 3, 12, 6, 6, 6, 6},
  591. {2018, 5, 13, 6, 6, 6, 6},
  592. {2018, 5, 16, 7, 7, 7, 7},
  593. }
  594. for _, testCase := range cases {
  595. func() {
  596. cit, err := test.Repository.Log(&git.LogOptions{From: plumbing.ZeroHash})
  597. if err != nil {
  598. panic(err)
  599. }
  600. defer cit.Close()
  601. var commits []*object.Commit
  602. timeCutoff := time.Date(
  603. testCase[0], time.Month(testCase[1]), testCase[2], 0, 0, 0, 0, time.FixedZone("CET", 7200))
  604. cit.ForEach(func(commit *object.Commit) error {
  605. reliableTime := time.Date(commit.Author.When.Year(), commit.Author.When.Month(),
  606. commit.Author.When.Day(), commit.Author.When.Hour(), commit.Author.When.Minute(),
  607. commit.Author.When.Second(), 0, time.FixedZone("CET", 7200))
  608. if reliableTime.Before(timeCutoff) {
  609. commits = append(commits, commit)
  610. }
  611. return nil
  612. })
  613. plan := prepareRunPlan(commits)
  614. /*for _, p := range plan {
  615. if p.Commit != nil {
  616. fmt.Println(p.Action, p.Commit.Hash.String(), p.Items)
  617. } else {
  618. fmt.Println(p.Action, strings.Repeat(" ", 40), p.Items)
  619. }
  620. }*/
  621. numCommits := 0
  622. numForks := 0
  623. numMerges := 0
  624. numDeletes := 0
  625. numEmerges := 0
  626. processed := map[plumbing.Hash]map[int]int{}
  627. for _, p := range plan {
  628. switch p.Action {
  629. case runActionCommit:
  630. branches := processed[p.Commit.Hash]
  631. if branches == nil {
  632. branches = map[int]int{}
  633. processed[p.Commit.Hash] = branches
  634. }
  635. branches[p.Items[0]]++
  636. for _, parent := range p.Commit.ParentHashes {
  637. assert.Contains(t, processed, parent)
  638. }
  639. numCommits++
  640. case runActionFork:
  641. numForks++
  642. case runActionMerge:
  643. counts := map[int]int{}
  644. for _, i := range p.Items {
  645. counts[i]++
  646. }
  647. for x, v := range counts {
  648. assert.Equal(t, 1, v, x)
  649. }
  650. numMerges++
  651. case runActionDelete:
  652. numDeletes++
  653. case runActionEmerge:
  654. numEmerges++
  655. }
  656. }
  657. for c, branches := range processed {
  658. for b, v := range branches {
  659. assert.Equal(t, 1, v, fmt.Sprint(c.String(), b))
  660. }
  661. }
  662. assert.Equal(t, numCommits, len(commits)+testCase[3], fmt.Sprintf("commits %v", testCase))
  663. assert.Equal(t, numForks, testCase[4], fmt.Sprintf("forks %v", testCase))
  664. assert.Equal(t, numMerges, testCase[5], fmt.Sprintf("merges %v", testCase))
  665. assert.Equal(t, numDeletes, testCase[6], fmt.Sprintf("deletes %v", testCase))
  666. assert.Equal(t, numEmerges, 1, fmt.Sprintf("emerges %v", testCase))
  667. }()
  668. }
  669. }