// Returns pods missing from summary. func podsMissingFromSummary(s stats.Summary, expectedPods sets.String) sets.String { expectedPods = sets.StringKeySet(expectedPods) for _, pod := range s.Pods { if expectedPods.Has(pod.PodRef.Name) { expectedPods.Delete(pod.PodRef.Name) } } return expectedPods }
// Munge is the workhorse the will actually make updates to the PR // The algorithm goes as: // - Initially, we set up approverSet // - Go through all comments after latest commit. If any approver said "/approve", add him to approverSet. // - For each file, we see if any approver of this file is in approverSet. // - An approver of a file is defined as: // - It's known that each dir has a list of approvers. (This might not hold true. For usability, current situation is enough.) // - Approver of a dir is also the approver of child dirs. // - We look at top N (default 3) level dir approvers. For example, for file "/a/b/c/d/e", we might search for approver from // "/", "/a/", "/a/b/" // - Iff all files has been approved, the bot will add "approved" label. func (h *ApprovalHandler) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } files, err := obj.ListFiles() if err != nil { glog.Errorf("failed to list files in this PR: %v", err) return } comments, err := getCommentsAfterLastModified(obj) if err != nil { glog.Errorf("failed to get comments in this PR: %v", err) return } approverSet := sets.String{} // from oldest to latest for i := len(comments) - 1; i >= 0; i-- { c := comments[i] if !mungerutil.IsValidUser(c.User) { continue } fields := strings.Fields(strings.TrimSpace(*c.Body)) if len(fields) == 1 && strings.ToLower(fields[0]) == "/approve" { approverSet.Insert(*c.User.Login) continue } if len(fields) == 2 && strings.ToLower(fields[0]) == "/approve" && strings.ToLower(fields[1]) == "cancel" { approverSet.Delete(*c.User.Login) } } for _, file := range files { if !h.hasApproval(*file.Filename, approverSet, maxDepth) { return } } obj.AddLabel(approvedLabel) }
func TestHammerController(t *testing.T) { // This test executes a bunch of requests through the fake source and // controller framework to make sure there's no locking/threading // errors. If an error happens, it should hang forever or trigger the // race detector. // source simulates an apiserver object endpoint. source := framework.NewFakeControllerSource() // Let's do threadsafe output to get predictable test results. outputSetLock := sync.Mutex{} // map of key to operations done on the key outputSet := map[string][]string{} recordFunc := func(eventType string, obj interface{}) { key, err := framework.DeletionHandlingMetaNamespaceKeyFunc(obj) if err != nil { t.Errorf("something wrong with key: %v", err) key = "oops something went wrong with the key" } // Record some output when items are deleted. outputSetLock.Lock() defer outputSetLock.Unlock() outputSet[key] = append(outputSet[key], eventType) } // Make a controller which just logs all the changes it gets. _, controller := framework.NewInformer( source, &api.Pod{}, time.Millisecond*100, framework.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { recordFunc("add", obj) }, UpdateFunc: func(oldObj, newObj interface{}) { recordFunc("update", newObj) }, DeleteFunc: func(obj interface{}) { recordFunc("delete", obj) }, }, ) if controller.HasSynced() { t.Errorf("Expected HasSynced() to return false before we started the controller") } // Run the controller and run it until we close stop. stop := make(chan struct{}) go controller.Run(stop) // Let's wait for the controller to do its initial sync time.Sleep(100 * time.Millisecond) if !controller.HasSynced() { t.Errorf("Expected HasSynced() to return true after the initial sync") } wg := sync.WaitGroup{} const threads = 3 wg.Add(threads) for i := 0; i < threads; i++ { go func() { defer wg.Done() // Let's add a few objects to the source. currentNames := sets.String{} rs := rand.NewSource(rand.Int63()) f := fuzz.New().NilChance(.5).NumElements(0, 2).RandSource(rs) r := rand.New(rs) // Mustn't use r and f concurrently! for i := 0; i < 100; i++ { var name string var isNew bool if currentNames.Len() == 0 || r.Intn(3) == 1 { f.Fuzz(&name) isNew = true } else { l := currentNames.List() name = l[r.Intn(len(l))] } pod := &api.Pod{} f.Fuzz(pod) pod.ObjectMeta.Name = name pod.ObjectMeta.Namespace = "default" // Add, update, or delete randomly. // Note that these pods are not valid-- the fake source doesn't // call validation or perform any other checking. if isNew { currentNames.Insert(name) source.Add(pod) continue } switch r.Intn(2) { case 0: currentNames.Insert(name) source.Modify(pod) case 1: currentNames.Delete(name) source.Delete(pod) } } }() } wg.Wait() // Let's wait for the controller to finish processing the things we just added. time.Sleep(100 * time.Millisecond) close(stop) outputSetLock.Lock() t.Logf("got: %#v", outputSet) }