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()
}