github.com/abhinav/git-pr@v0.6.1-0.20171029234004-54218d68c11b/pr/walk_test.go (about)

     1  package pr_test
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/abhinav/git-pr/pr"
     9  	"github.com/abhinav/git-pr/pr/prtest"
    10  
    11  	"github.com/golang/mock/gomock"
    12  	"github.com/google/go-github/github"
    13  	"github.com/stretchr/testify/assert"
    14  )
    15  
    16  type mockChildren struct {
    17  	ctrl *gomock.Controller
    18  }
    19  
    20  func newMockChildren(ctrl *gomock.Controller) *mockChildren {
    21  	return &mockChildren{ctrl: ctrl}
    22  }
    23  
    24  func (m *mockChildren) Expect(pr interface{}) *gomock.Call {
    25  	return m.ctrl.RecordCall(m, "Call", pr)
    26  }
    27  
    28  func (m *mockChildren) Call(pr *github.PullRequest) (out []*github.PullRequest, err error) {
    29  	results := m.ctrl.Call(m, "Call", pr)
    30  	out, _ = results[0].([]*github.PullRequest)
    31  	err, _ = results[1].(error)
    32  	return
    33  }
    34  
    35  func TestWalk(t *testing.T) {
    36  	type children map[int][]int
    37  
    38  	type visit struct {
    39  		// Whether the children of this node should be visited.
    40  		VisitChildren bool
    41  
    42  		// If non-nil, this visit will fail with an error.
    43  		Error error
    44  
    45  		// If non-nil, this visit will panic with the given value.
    46  		Panic interface{}
    47  	}
    48  
    49  	type visits map[int]visit
    50  
    51  	concurrency := []int{0, 1, 2, 4, 8}
    52  
    53  	tests := []struct {
    54  		Desc  string
    55  		Pulls []int
    56  
    57  		Children children
    58  		Visits   map[int]visit
    59  
    60  		WantErr []string
    61  	}{
    62  		{Desc: "empty", Pulls: []int{}},
    63  		{
    64  			Desc:     "single",
    65  			Pulls:    []int{1},
    66  			Children: children{1: {}},
    67  			Visits:   visits{1: {VisitChildren: true}},
    68  		},
    69  		{
    70  			Desc:    "single error",
    71  			Pulls:   []int{1},
    72  			Visits:  visits{1: {Error: errors.New("great sadness")}},
    73  			WantErr: []string{"great sadness"},
    74  		},
    75  		{
    76  			Desc:  "single panic error",
    77  			Pulls: []int{1},
    78  			Visits: visits{
    79  				1: {Panic: errors.New("great sadness")},
    80  			},
    81  			WantErr: []string{"great sadness"},
    82  		},
    83  		{
    84  			Desc:  "single panic",
    85  			Pulls: []int{1},
    86  			Visits: visits{
    87  				1: {Panic: "great sadness"},
    88  			},
    89  			WantErr: []string{"panic: great sadness"},
    90  		},
    91  		{
    92  			Desc:   "single no children",
    93  			Pulls:  []int{1},
    94  			Visits: visits{1: {}},
    95  		},
    96  		{
    97  			Desc:  "single hop",
    98  			Pulls: []int{1, 2, 3},
    99  			Children: children{
   100  				1: {4, 5},
   101  				2: {},
   102  				4: {},
   103  				5: {},
   104  			},
   105  			Visits: visits{
   106  				1: {VisitChildren: true},
   107  				2: {VisitChildren: true},
   108  				3: {},
   109  				4: {VisitChildren: true},
   110  				5: {VisitChildren: true},
   111  			},
   112  		},
   113  		{
   114  			Desc:  "multi hop",
   115  			Pulls: []int{1, 2},
   116  			Children: children{
   117  				1:  {3},
   118  				2:  {4, 5},
   119  				3:  {},
   120  				4:  {6},
   121  				5:  {7},
   122  				6:  {8, 9},
   123  				7:  {},
   124  				8:  {10},
   125  				9:  {},
   126  				10: {11},
   127  				11: {},
   128  			},
   129  			Visits: visits{
   130  				1:  {VisitChildren: true},
   131  				2:  {VisitChildren: true},
   132  				3:  {VisitChildren: true},
   133  				4:  {VisitChildren: true},
   134  				5:  {VisitChildren: true},
   135  				6:  {VisitChildren: true},
   136  				7:  {VisitChildren: true},
   137  				8:  {VisitChildren: true},
   138  				9:  {VisitChildren: true},
   139  				10: {VisitChildren: true},
   140  				11: {VisitChildren: true},
   141  			},
   142  		},
   143  		{
   144  			Desc:  "multi hop errors",
   145  			Pulls: []int{1, 2},
   146  			Children: children{
   147  				1: {3},
   148  				2: {4, 5},
   149  				4: {6},
   150  				5: {7},
   151  				7: {},
   152  			},
   153  			Visits: visits{
   154  				1: {VisitChildren: true},
   155  				2: {VisitChildren: true},
   156  				3: {VisitChildren: true, Error: errors.New("something went wrong")},
   157  				4: {VisitChildren: true},
   158  				5: {VisitChildren: true},
   159  				6: {VisitChildren: true, Error: errors.New("great sadness")},
   160  				7: {VisitChildren: true},
   161  			},
   162  			WantErr: []string{"great sadness", "something went wrong"},
   163  		},
   164  		{
   165  			Desc:  "channel overflow",
   166  			Pulls: []int{1, 21},
   167  			Children: children{
   168  				1: {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20},
   169  				2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}, 9: {}, 10: {},
   170  				11: {}, 12: {}, 13: {}, 14: {}, 15: {}, 16: {}, 17: {}, 18: {}, 19: {}, 20: {},
   171  				21: {22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40},
   172  				22: {}, 23: {}, 24: {}, 25: {}, 26: {}, 27: {}, 28: {}, 29: {}, 30: {},
   173  				31: {}, 32: {}, 33: {}, 34: {}, 35: {}, 36: {}, 37: {}, 38: {}, 39: {}, 40: {},
   174  			},
   175  			Visits: visits{
   176  				1:  {VisitChildren: true},
   177  				2:  {VisitChildren: true},
   178  				3:  {VisitChildren: true},
   179  				4:  {VisitChildren: true},
   180  				5:  {VisitChildren: true},
   181  				6:  {VisitChildren: true},
   182  				7:  {VisitChildren: true},
   183  				8:  {VisitChildren: true},
   184  				9:  {VisitChildren: true},
   185  				10: {VisitChildren: true},
   186  				11: {VisitChildren: true},
   187  				12: {VisitChildren: true},
   188  				13: {VisitChildren: true},
   189  				14: {VisitChildren: true},
   190  				15: {VisitChildren: true},
   191  				16: {VisitChildren: true},
   192  				17: {VisitChildren: true},
   193  				18: {VisitChildren: true},
   194  				19: {VisitChildren: true},
   195  				20: {VisitChildren: true},
   196  				21: {VisitChildren: true},
   197  				22: {VisitChildren: true},
   198  				23: {VisitChildren: true},
   199  				24: {VisitChildren: true},
   200  				25: {VisitChildren: true},
   201  				26: {VisitChildren: true},
   202  				27: {VisitChildren: true},
   203  				28: {VisitChildren: true},
   204  				29: {VisitChildren: true},
   205  				30: {VisitChildren: true},
   206  				31: {VisitChildren: true},
   207  				32: {VisitChildren: true},
   208  				33: {VisitChildren: true},
   209  				34: {VisitChildren: true},
   210  				35: {VisitChildren: true},
   211  				36: {VisitChildren: true},
   212  				37: {VisitChildren: true},
   213  				38: {VisitChildren: true},
   214  				39: {VisitChildren: true},
   215  				40: {VisitChildren: true},
   216  			},
   217  		},
   218  	}
   219  
   220  	for _, conc := range concurrency {
   221  		for _, tt := range tests {
   222  			name := fmt.Sprintf("concurrency=%v/%v", conc, tt.Desc)
   223  			t.Run(name, func(t *testing.T) {
   224  				ctrl := gomock.NewController(t)
   225  				defer ctrl.Finish()
   226  
   227  				getChildren := newMockChildren(ctrl)
   228  				for parent, children := range tt.Children {
   229  					getChildren.
   230  						Expect(prMatcher{Number: parent}).
   231  						Return(fakePullRequests(children), nil)
   232  				}
   233  
   234  				visitor := prtest.NewMockVisitor(ctrl)
   235  				for num, visit := range tt.Visits {
   236  					var v pr.Visitor
   237  					if visit.VisitChildren {
   238  						v = visitor
   239  					}
   240  
   241  					call := visitor.EXPECT().Visit(prMatcher{Number: num})
   242  					switch {
   243  					case visit.Error != nil:
   244  						call.Return(v, visit.Error)
   245  					case visit.Panic != nil:
   246  						p := visit.Panic
   247  						call.Do(func(*github.PullRequest) { panic(p) }).Return(v, nil)
   248  					default:
   249  						call.Return(v, nil)
   250  					}
   251  				}
   252  
   253  				cfg := pr.WalkConfig{
   254  					Children:    getChildren.Call,
   255  					Concurrency: conc,
   256  				}
   257  				err := pr.Walk(cfg, fakePullRequests(tt.Pulls), visitor)
   258  
   259  				if len(tt.WantErr) > 0 {
   260  					if !assert.Error(t, err) {
   261  						return
   262  					}
   263  
   264  					for _, msg := range tt.WantErr {
   265  						assert.Contains(t, err.Error(), msg)
   266  					}
   267  					return
   268  				}
   269  
   270  				assert.NoError(t, err)
   271  			})
   272  		}
   273  	}
   274  }
   275  
   276  type prMatcher struct {
   277  	Number int
   278  }
   279  
   280  var _ gomock.Matcher = prMatcher{}
   281  
   282  func (m prMatcher) String() string {
   283  	return fmt.Sprintf("pull request #%v", m.Number)
   284  }
   285  
   286  func (m prMatcher) Matches(x interface{}) bool {
   287  	pr, ok := x.(*github.PullRequest)
   288  	if !ok {
   289  		return false
   290  	}
   291  
   292  	return pr.GetNumber() == m.Number
   293  }
   294  
   295  func fakePullRequests(nums []int) []*github.PullRequest {
   296  	prs := make([]*github.PullRequest, len(nums))
   297  	for i, n := range nums {
   298  		prs[i] = &github.PullRequest{Number: github.Int(n)}
   299  	}
   300  	return prs
   301  }