فهرست منبع

Add PeopleMatrix marge and cover corner cases

Vadim Markovtsev 7 سال پیش
والد
کامیت
7f38391ae4
2فایلهای تغییر یافته به همراه276 افزوده شده و 58 حذف شده
  1. 114 46
      burndown.go
  2. 162 12
      burndown_test.go

+ 114 - 46
burndown.go

@@ -318,9 +318,7 @@ func (analyser *BurndownAnalysis) MergeResults(
 	r1, r2 interface{}, c1, c2 *CommonAnalysisResult) interface{} {
 	bar1 := r1.(BurndownResult)
 	bar2 := r2.(BurndownResult)
-	merged := BurndownResult{
-		FileHistories: map[string][][]int64{},
-	}
+	merged := BurndownResult{}
 	if bar1.sampling < bar2.sampling {
 		merged.sampling = bar1.sampling
 	} else {
@@ -352,48 +350,114 @@ func (analyser *BurndownAnalysis) MergeResults(
 	for name, ptrs := range people {
 		merged.reversedPeopleDict[ptrs[0]] = name
 	}
-	merged.GlobalHistory = mergeMatrices(
-		bar1.GlobalHistory, bar2.GlobalHistory,
-		bar1.granularity, bar1.sampling,
-		bar2.granularity, bar2.sampling,
-		c1, c2)
 	var wg sync.WaitGroup
-	for key, fh1 := range bar1.FileHistories {
-		if fh2, exists := bar2.FileHistories[key]; exists {
-			wg.Add(1)
-			go func(fh1, fh2 [][]int64) {
-				defer wg.Done()
-				merged.FileHistories[key] = mergeMatrices(
-					fh1, fh2, bar1.granularity, bar1.sampling, bar2.granularity, bar2.sampling, c1, c2)
-			}(fh1, fh2)
-		} else {
-			merged.FileHistories[key] = fh1
+	if len(bar1.GlobalHistory) > 0 || len(bar2.GlobalHistory) > 0 {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			merged.GlobalHistory = mergeMatrices(
+				bar1.GlobalHistory, bar2.GlobalHistory,
+				bar1.granularity, bar1.sampling,
+				bar2.granularity, bar2.sampling,
+				c1, c2)
+		}()
+	}
+	if len(bar1.FileHistories) > 0 || len(bar2.FileHistories) > 0 {
+		merged.FileHistories = map[string][][]int64{}
+		historyMutex := sync.Mutex{}
+		for key, fh1 := range bar1.FileHistories {
+			if fh2, exists := bar2.FileHistories[key]; exists {
+				wg.Add(1)
+				go func(fh1, fh2 [][]int64, key string) {
+					defer wg.Done()
+					historyMutex.Lock()
+					defer historyMutex.Unlock()
+					merged.FileHistories[key] = mergeMatrices(
+						fh1, fh2, bar1.granularity, bar1.sampling, bar2.granularity, bar2.sampling, c1, c2)
+				}(fh1, fh2, key)
+			} else {
+				historyMutex.Lock()
+				merged.FileHistories[key] = fh1
+				historyMutex.Unlock()
+			}
 		}
-	}
-	for key, fh2 := range bar2.FileHistories {
-		if _, exists := bar1.FileHistories[key]; !exists {
-			merged.FileHistories[key] = fh2
+		for key, fh2 := range bar2.FileHistories {
+			if _, exists := bar1.FileHistories[key]; !exists {
+				historyMutex.Lock()
+				merged.FileHistories[key] = fh2
+				historyMutex.Unlock()
+			}
 		}
 	}
-	merged.PeopleHistories = make([][][]int64, len(merged.reversedPeopleDict))
-	for i, key := range merged.reversedPeopleDict {
-		ptrs := people[key]
-		if ptrs[1] < 0 {
-			merged.PeopleHistories[i] = bar2.PeopleHistories[ptrs[2]]
-		} else if ptrs[2] < 0 {
-			merged.PeopleHistories[i] = bar1.PeopleHistories[ptrs[1]]
-		} else {
-			wg.Add(1)
-			go func(i int) {
-				defer wg.Done()
-				merged.PeopleHistories[i] = mergeMatrices(
-					bar1.PeopleHistories[ptrs[1]], bar2.PeopleHistories[ptrs[2]],
-					bar1.granularity, bar1.sampling,
-					bar2.granularity, bar2.sampling,
-					c1, c2,
-				)
-			}(i)
+	if len(merged.reversedPeopleDict) > 0 {
+		merged.PeopleHistories = make([][][]int64, len(merged.reversedPeopleDict))
+		for i, key := range merged.reversedPeopleDict {
+			ptrs := people[key]
+			if ptrs[1] < 0 {
+				if len(bar2.PeopleHistories) > 0 {
+					merged.PeopleHistories[i] = bar2.PeopleHistories[ptrs[2]]
+				}
+			} else if ptrs[2] < 0 {
+				if len(bar1.PeopleHistories) > 0 {
+					merged.PeopleHistories[i] = bar1.PeopleHistories[ptrs[1]]
+				}
+			} else {
+				wg.Add(1)
+				go func(i int) {
+					defer wg.Done()
+					var m1, m2 [][]int64
+					if len(bar1.PeopleHistories) > 0 {
+						m1 = bar1.PeopleHistories[ptrs[1]]
+					}
+					if len(bar2.PeopleHistories) > 0 {
+						m2 = bar2.PeopleHistories[ptrs[2]]
+					}
+					merged.PeopleHistories[i] = mergeMatrices(
+						m1, m2,
+						bar1.granularity, bar1.sampling,
+						bar2.granularity, bar2.sampling,
+						c1, c2,
+					)
+				}(i)
+			}
 		}
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			if len(bar2.PeopleMatrix) == 0 {
+				merged.PeopleMatrix = bar1.PeopleMatrix
+				// extend the matrix in both directions
+				for i := 0; i < len(merged.PeopleMatrix); i++ {
+					for j := len(bar1.reversedPeopleDict); j < len(merged.reversedPeopleDict); j++ {
+						merged.PeopleMatrix[i] = append(merged.PeopleMatrix[i], 0)
+					}
+				}
+				for i := len(bar1.reversedPeopleDict); i < len(merged.reversedPeopleDict); i++ {
+					merged.PeopleMatrix = append(
+						merged.PeopleMatrix, make([]int64, len(merged.reversedPeopleDict)+2))
+				}
+			} else {
+				merged.PeopleMatrix = make([][]int64, len(merged.reversedPeopleDict))
+				for i := range merged.PeopleMatrix {
+					merged.PeopleMatrix[i] = make([]int64, len(merged.reversedPeopleDict)+2)
+				}
+				for i, key := range bar1.reversedPeopleDict {
+					mi := people[key][0] // index in merged.reversedPeopleDict
+					copy(merged.PeopleMatrix[mi][:2], bar1.PeopleMatrix[i][:2])
+					for j, val := range bar1.PeopleMatrix[i][2:] {
+						merged.PeopleMatrix[mi][2+people[bar1.reversedPeopleDict[j]][0]] = val
+					}
+				}
+				for i, key := range bar2.reversedPeopleDict {
+					mi := people[key][0] // index in merged.reversedPeopleDict
+					merged.PeopleMatrix[mi][0] += bar2.PeopleMatrix[i][0]
+					merged.PeopleMatrix[mi][1] += bar2.PeopleMatrix[i][1]
+					for j, val := range bar2.PeopleMatrix[i][2:] {
+						merged.PeopleMatrix[mi][2+people[bar2.reversedPeopleDict[j]][0]] += val
+					}
+				}
+			}
+		}()
 	}
 	wg.Wait()
 	return merged
@@ -421,17 +485,21 @@ func mergeMatrices(m1, m2 [][]int64, granularity1, sampling1, granularity2, samp
 	for i := range daily {
 		daily[i] = make([]float32, size+sampling)
 	}
-	addBurndownMatrix(m1, granularity1, sampling1, daily,
-		int(c1.BeginTime-commonMerged.BeginTime)/(3600*24))
-	addBurndownMatrix(m2, granularity2, sampling2, daily,
-		int(c2.BeginTime-commonMerged.BeginTime)/(3600*24))
+	if len(m1) > 0 {
+		addBurndownMatrix(m1, granularity1, sampling1, daily,
+			int(c1.BeginTime-commonMerged.BeginTime)/(3600*24))
+	}
+	if len(m2) > 0 {
+		addBurndownMatrix(m2, granularity2, sampling2, daily,
+			int(c2.BeginTime-commonMerged.BeginTime)/(3600*24))
+	}
 
 	// convert daily to [][]in(t64
 	result := make([][]int64, (size+sampling-1)/sampling)
 	for i := range result {
 		result[i] = make([]int64, (size+granularity-1)/granularity)
-		sampledIndex := i*sampling
-		if i == len(result) - 1 {
+		sampledIndex := i * sampling
+		if i == len(result)-1 {
 			sampledIndex = size - 1
 		}
 		for j := 0; j < len(result[i]); j++ {

+ 162 - 12
burndown_test.go

@@ -589,12 +589,12 @@ func TestBurndownAddMatrix(t *testing.T) {
 	})
 	// yaml.PrintMatrix(os.Stdout, added, 0, "test", true)
 	/*
-		"test": |-
-	  10  0  0
-	  18  2  0
-	  12 14  0
-	  10 12  6
-	   8  9 13
+			"test": |-
+		  10  0  0
+		  18  2  0
+		  12 14  0
+		  10 12  6
+		   8  9 13
 	*/
 	addBurndownMatrix(added, 5, 3, daily, 1)
 	for i := range daily[0] {
@@ -668,12 +668,12 @@ func TestBurndownAddMatrixCrazy(t *testing.T) {
 	}
 	// yaml.PrintMatrix(os.Stdout, added, 0, "test", true)
 	/*
-		"test": |-
-	  10  0  0
-	  9  2  0
-	  8 16  0
-	  7 12  6
-	  6  9 13
+			"test": |-
+		  10  0  0
+		  9  2  0
+		  8 16  0
+		  7 12  6
+		  6  9 13
 	*/
 	addBurndownMatrix(added, 5, 3, daily, 0)
 	/*for _, row := range daily {
@@ -752,6 +752,16 @@ func TestBurndownMergeGlobalHistory(t *testing.T) {
 	res1.FileHistories["file2"] = res1.GlobalHistory
 	res1.PeopleHistories = append(res1.PeopleHistories, res1.GlobalHistory)
 	res1.PeopleHistories = append(res1.PeopleHistories, res1.GlobalHistory)
+	res1.PeopleMatrix = append(res1.PeopleMatrix, make([]int64, 4))
+	res1.PeopleMatrix = append(res1.PeopleMatrix, make([]int64, 4))
+	res1.PeopleMatrix[0][0] = 10
+	res1.PeopleMatrix[0][1] = 20
+	res1.PeopleMatrix[0][2] = 30
+	res1.PeopleMatrix[0][3] = 40
+	res1.PeopleMatrix[1][0] = 50
+	res1.PeopleMatrix[1][1] = 60
+	res1.PeopleMatrix[1][2] = 70
+	res1.PeopleMatrix[1][3] = 80
 	people2 := [...]string{"two", "three"}
 	res2 := BurndownResult{
 		GlobalHistory:      [][]int64{},
@@ -792,6 +802,16 @@ func TestBurndownMergeGlobalHistory(t *testing.T) {
 	res2.FileHistories["file3"] = res2.GlobalHistory
 	res2.PeopleHistories = append(res2.PeopleHistories, res2.GlobalHistory)
 	res2.PeopleHistories = append(res2.PeopleHistories, res2.GlobalHistory)
+	res2.PeopleMatrix = append(res2.PeopleMatrix, make([]int64, 4))
+	res2.PeopleMatrix = append(res2.PeopleMatrix, make([]int64, 4))
+	res2.PeopleMatrix[0][0] = 100
+	res2.PeopleMatrix[0][1] = 200
+	res2.PeopleMatrix[0][2] = 300
+	res2.PeopleMatrix[0][3] = 400
+	res2.PeopleMatrix[1][0] = 500
+	res2.PeopleMatrix[1][1] = 600
+	res2.PeopleMatrix[1][2] = 700
+	res2.PeopleMatrix[1][3] = 800
 	burndown := BurndownAnalysis{}
 	merged := burndown.MergeResults(res1, res2, &c1, &c2).(BurndownResult)
 	assert.Equal(t, merged.granularity, 19)
@@ -807,4 +827,134 @@ func TestBurndownMergeGlobalHistory(t *testing.T) {
 	assert.Equal(t, merged.PeopleHistories[0], res1.GlobalHistory)
 	assert.Equal(t, merged.PeopleHistories[1], merged.GlobalHistory)
 	assert.Equal(t, merged.PeopleHistories[2], res2.GlobalHistory)
+	assert.Len(t, merged.PeopleMatrix, 3)
+	for _, row := range merged.PeopleMatrix {
+		assert.Len(t, row, 5)
+	}
+	assert.Equal(t, merged.PeopleMatrix[0][0], int64(10))
+	assert.Equal(t, merged.PeopleMatrix[0][1], int64(20))
+	assert.Equal(t, merged.PeopleMatrix[0][2], int64(30))
+	assert.Equal(t, merged.PeopleMatrix[0][3], int64(40))
+	assert.Equal(t, merged.PeopleMatrix[0][4], int64(0))
+
+	assert.Equal(t, merged.PeopleMatrix[1][0], int64(150))
+	assert.Equal(t, merged.PeopleMatrix[1][1], int64(260))
+	assert.Equal(t, merged.PeopleMatrix[1][2], int64(70))
+	assert.Equal(t, merged.PeopleMatrix[1][3], int64(380))
+	assert.Equal(t, merged.PeopleMatrix[1][4], int64(400))
+
+	assert.Equal(t, merged.PeopleMatrix[2][0], int64(500))
+	assert.Equal(t, merged.PeopleMatrix[2][1], int64(600))
+	assert.Equal(t, merged.PeopleMatrix[2][2], int64(0))
+	assert.Equal(t, merged.PeopleMatrix[2][3], int64(700))
+	assert.Equal(t, merged.PeopleMatrix[2][4], int64(800))
+}
+
+func TestBurndownMergeNils(t *testing.T) {
+	res1 := BurndownResult{
+		GlobalHistory:      [][]int64{},
+		FileHistories:      map[string][][]int64{},
+		PeopleHistories:    [][][]int64{},
+		PeopleMatrix:       [][]int64{},
+		reversedPeopleDict: []string{},
+		sampling:           15,
+		granularity:        20,
+	}
+	c1 := CommonAnalysisResult{
+		BeginTime:     600566400, // 1989 Jan 12
+		EndTime:       604713600, // 1989 March 1
+		CommitsNumber: 10,
+		RunTime:       100000,
+	}
+	res2 := BurndownResult{
+		GlobalHistory:      nil,
+		FileHistories:      nil,
+		PeopleHistories:    nil,
+		PeopleMatrix:       nil,
+		reversedPeopleDict: nil,
+		sampling:           14,
+		granularity:        19,
+	}
+	c2 := CommonAnalysisResult{
+		BeginTime:     601084800, // 1989 Jan 18
+		EndTime:       605923200, // 1989 March 15
+		CommitsNumber: 10,
+		RunTime:       100000,
+	}
+	burndown := BurndownAnalysis{}
+	merged := burndown.MergeResults(res1, res2, &c1, &c2).(BurndownResult)
+	assert.Equal(t, merged.granularity, 19)
+	assert.Equal(t, merged.sampling, 14)
+	assert.Nil(t, merged.GlobalHistory)
+	assert.Nil(t, merged.FileHistories)
+	assert.Nil(t, merged.PeopleHistories)
+	assert.Nil(t, merged.PeopleMatrix)
+
+	res2.GlobalHistory = make([][]int64, 56/14 /* 4 samples */)
+	for i := range res2.GlobalHistory {
+		res2.GlobalHistory[i] = make([]int64, 56/19+1 /* 3 bands */)
+		switch i {
+		case 0:
+			res2.GlobalHistory[i][0] = 900
+		case 1:
+			res2.GlobalHistory[i][0] = 1100
+			res2.GlobalHistory[i][1] = 400
+		case 2:
+			res2.GlobalHistory[i][0] = 900
+			res2.GlobalHistory[i][1] = 750
+			res2.GlobalHistory[i][2] = 100
+		case 3:
+			res2.GlobalHistory[i][0] = 800
+			res2.GlobalHistory[i][1] = 600
+			res2.GlobalHistory[i][2] = 600
+		}
+	}
+	people1 := [...]string{"one", "two"}
+	res1.reversedPeopleDict = people1[:]
+	res1.PeopleMatrix = append(res1.PeopleMatrix, make([]int64, 4))
+	res1.PeopleMatrix = append(res1.PeopleMatrix, make([]int64, 4))
+	res1.PeopleMatrix[0][0] = 10
+	res1.PeopleMatrix[0][1] = 20
+	res1.PeopleMatrix[0][2] = 30
+	res1.PeopleMatrix[0][3] = 40
+	res1.PeopleMatrix[1][0] = 50
+	res1.PeopleMatrix[1][1] = 60
+	res1.PeopleMatrix[1][2] = 70
+	res1.PeopleMatrix[1][3] = 80
+	people2 := [...]string{"two", "three"}
+	res2.reversedPeopleDict = people2[:]
+	merged = burndown.MergeResults(res1, res2, &c1, &c2).(BurndownResult)
+	mgh := [5][4]int64{
+		{0, 0, 0, 0},
+		{578, 0, 0, 0},
+		{798, 546, 0, 0},
+		{664, 884, 222, 0},
+		{547, 663, 610, 178},
+	}
+	mgh2 := [...][]int64{
+		mgh[0][:], mgh[1][:], mgh[2][:], mgh[3][:], mgh[4][:],
+	}
+	mgh3 := mgh2[:]
+	assert.Equal(t, mgh3, merged.GlobalHistory)
+	assert.Len(t, merged.PeopleMatrix, 3)
+	for _, row := range merged.PeopleMatrix {
+		assert.Len(t, row, 5)
+	}
+	assert.Equal(t, merged.PeopleMatrix[0][0], int64(10))
+	assert.Equal(t, merged.PeopleMatrix[0][1], int64(20))
+	assert.Equal(t, merged.PeopleMatrix[0][2], int64(30))
+	assert.Equal(t, merged.PeopleMatrix[0][3], int64(40))
+	assert.Equal(t, merged.PeopleMatrix[0][4], int64(0))
+
+	assert.Equal(t, merged.PeopleMatrix[1][0], int64(50))
+	assert.Equal(t, merged.PeopleMatrix[1][1], int64(60))
+	assert.Equal(t, merged.PeopleMatrix[1][2], int64(70))
+	assert.Equal(t, merged.PeopleMatrix[1][3], int64(80))
+	assert.Equal(t, merged.PeopleMatrix[1][4], int64(0))
+
+	assert.Equal(t, merged.PeopleMatrix[2][0], int64(0))
+	assert.Equal(t, merged.PeopleMatrix[2][1], int64(0))
+	assert.Equal(t, merged.PeopleMatrix[2][2], int64(0))
+	assert.Equal(t, merged.PeopleMatrix[2][3], int64(0))
+	assert.Equal(t, merged.PeopleMatrix[2][4], int64(0))
 }