func TestWeightedAlgorithms(t *testing.T) { t.Parallel() wg := sync.WaitGroup{} testAlgorithmForWeight := func(id string, inst types.UpstreamBalancingAlgorithm) { isWeighted := !strings.HasPrefix(id, unweightedPrefix) upstreams := testutils.GetRandomUpstreams(5, 20) inst.Set(upstreams) var totalWeight uint32 weights := map[string]uint32{} for _, u := range upstreams { weights[u.Host] = u.Weight totalWeight += u.Weight } mapping := map[string]uint32{} urlsToTest := uint32(25000) for i := uint32(0); i < urlsToTest; i++ { url := testutils.GenerateMeAString(rand.Int63(), 5+rand.Int63n(100)) if res, err := inst.Get(url); err != nil { t.Errorf("Unexpected error when getting url %s from algorithm %s: %s", url, id, err) } else { mapping[res.Host]++ } } var deviation float64 expectedPercent := 100.0 / float64(len(upstreams)) for host, count := range mapping { if isWeighted { expectedPercent = float64(weights[host]) * 100.0 / float64(totalWeight) } actualPercent := float64(count) * 100 / float64(urlsToTest) deviation += math.Abs(actualPercent - expectedPercent) t.Logf("Algorithm %s, host %s: expected percent %f (weight %d out of %d) but got %f (%d out of %d); deviation %f%%\n", id, host, expectedPercent, weights[host], totalWeight, actualPercent, count, urlsToTest, math.Abs(actualPercent-expectedPercent)) } threshold := 3.0 avgDeviation := deviation / float64(len(upstreams)) if avgDeviation > threshold { t.Errorf("The average deviation for algorithm %s is above the threshold of %f%%: %f", id, threshold, avgDeviation) } wg.Done() } for id, constructor := range allAlgorithms { wg.Add(1) go testAlgorithmForWeight(id, constructor()) } wg.Wait() }
func consistenHashAlgorithmTest(t *testing.T, wg *sync.WaitGroup, id string) { inst := allAlgorithms[id]() upstreams := testutils.GetRandomUpstreams(10, 100) inst.Set(upstreams) urlsToTest := 3000 + rand.Intn(1000) mapping := map[string]string{} for i := 0; i < urlsToTest; i++ { url := testutils.GenerateMeAString(rand.Int63(), 5+rand.Int63n(100)) if res1, err := inst.Get(url); err != nil { t.Errorf("Unexpected error when getting url %s from algorithm %s: %s", url, id, err) } else if res2, err := inst.Get(url); err != nil { t.Errorf("Unexpected error when getting url %s from algorithm %s for the second time: %s", url, id, err) } else if !reflect.DeepEqual(res1, res2) { t.Errorf("The two results for url %s by algorithm %s are different: %#v and %#v", url, id, res1, res2) } else { mapping[url] = res1.Host } } oldWeght := getTotalWeight(upstreams) checkDeviation := func(newUpstreams []*types.UpstreamAddress) { inst.Set(newUpstreams) var sameCount float64 for url, oldHost := range mapping { if res, err := inst.Get(url); err != nil { t.Errorf("Unexpected error when getting url %s from algorithm %s: %s", url, id, err) } else if res.Host == oldHost { sameCount++ } } total := float64(len(mapping)) newWeght := getTotalWeight(newUpstreams) weightDiff := math.Abs(oldWeght - newWeght) if math.Abs(weightDiff/oldWeght-(total-sameCount)/total) > 0.25 { t.Errorf("[%s] Same count is %f of %f (count diff %f%%); upstreams are %d out of %d (weight diff %f-%f=%f or %f%%); deviation from expected: %f\n\n", id, sameCount, total, (total-sameCount)*100/total, len(newUpstreams), len(upstreams), oldWeght, newWeght, oldWeght-newWeght, weightDiff*100/oldWeght, math.Abs(weightDiff/oldWeght-(total-sameCount)/total)*100) } } // Add an extra server at the start newUpstream := testutils.GetUpstream(len(upstreams)) checkDeviation(append([]*types.UpstreamAddress{newUpstream}, upstreams...)) // Remove a random server randomServer := rand.Intn(len(upstreams)) checkDeviation(append(upstreams[:randomServer], upstreams[randomServer+1:]...)) // Add an extra server at that point checkDeviation(append(upstreams[:randomServer], append([]*types.UpstreamAddress{newUpstream}, upstreams[randomServer:]...)...)) // Add an extra server at the end checkDeviation(append(upstreams, newUpstream)) wg.Done() }
func TestPercentageCalculations(t *testing.T) { t.Parallel() r := New() r.Set(testutils.GetRandomUpstreams(1, 200)) var totalPercent float64 for _, b := range r.buckets { totalPercent += b.weightPercent } if math.Abs(totalPercent-1.0) > 0.000001 { t.Errorf("Bucket percentages do not combine to 1: %f", totalPercent) } }
// This test will probably only be useful if `go test -race` is used func TestRandomConcurrentUsage(t *testing.T) { t.Parallel() wg := sync.WaitGroup{} randomlyTestAlgorithm := func(id string, inst types.UpstreamBalancingAlgorithm) { inst.Set([]*types.UpstreamAddress{testutils.GetUpstream(rand.Int())}) // Prevent expected errors setters := 50 + rand.Intn(200) wg.Add(setters) for i := 0; i < setters; i++ { go func() { time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) inst.Set(testutils.GetRandomUpstreams(1, 100)) wg.Done() }() } getters := 100 + rand.Intn(500) wg.Add(getters) for i := 0; i < getters; i++ { go func() { time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) path := fmt.Sprintf("some/path/%d.jpn", rand.Int()) if _, err := inst.Get(path); err != nil { t.Errorf("Unexpected algorithm %s error for path %s: %s", id, path, err) } wg.Done() }() } } for id, constructor := range allAlgorithms { randomlyTestAlgorithm(id, constructor()) } wg.Wait() }