]> Cypherpunks repositories - gostls13.git/commitdiff
sort: fix TestAdversary
authorTom Levy <tomlevy93@gmail.com>
Thu, 24 Aug 2017 01:44:04 +0000 (13:44 +1200)
committerIan Lance Taylor <iant@golang.org>
Fri, 25 Aug 2017 05:58:57 +0000 (05:58 +0000)
There are some major problems with TestAdversary (based on "A Killer
Adversary for Quicksort"[1] by M. D. McIlroy). See #21581 for details.

Rewrite the test to closely match the version in the paper so it can
be verified as correct by virtue of similarity.

The only major difference between this new version and the version in
the paper is that this version swaps the values directly instead of
permuting an array of indices because we don't need to recover the
original permutation.

This new version also counts the number of calls to Less() and fails
the test if there are too many.

Fixes #21581.

[1]: http://www.cs.dartmouth.edu/~doug/mdmspe.pdf

Change-Id: Ia94b5b6d288b8fa3805a5fa27661cebbc5bad9a7
Reviewed-on: https://go-review.googlesource.com/58330
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
src/sort/sort_test.go

index 45713a28cc4735fa1fa008b03aa7cb11fb666c2f..092135e588d00db73ef6f5978b462d33f3d51c83 100644 (file)
@@ -458,49 +458,69 @@ func TestStableBM(t *testing.T) {
 // This is based on the "antiquicksort" implementation by M. Douglas McIlroy.
 // See http://www.cs.dartmouth.edu/~doug/mdmspe.pdf for more info.
 type adversaryTestingData struct {
-       data      []int
-       keys      map[int]int
-       candidate int
+       t         *testing.T
+       data      []int // item values, initialized to special gas value and changed by Less
+       maxcmp    int   // number of comparisons allowed
+       ncmp      int   // number of comparisons (calls to Less)
+       nsolid    int   // number of elements that have been set to non-gas values
+       candidate int   // guess at current pivot
+       gas       int   // special value for unset elements, higher than everything else
 }
 
 func (d *adversaryTestingData) Len() int { return len(d.data) }
 
 func (d *adversaryTestingData) Less(i, j int) bool {
-       if _, present := d.keys[i]; !present {
-               if _, present := d.keys[j]; !present {
-                       if i == d.candidate {
-                               d.keys[i] = len(d.keys)
-                       } else {
-                               d.keys[j] = len(d.keys)
-                       }
+       if d.ncmp >= d.maxcmp {
+               d.t.Fatalf("used %d comparisons sorting adversary data with size %d", d.ncmp, len(d.data))
+       }
+       d.ncmp++
+
+       if d.data[i] == d.gas && d.data[j] == d.gas {
+               if i == d.candidate {
+                       // freeze i
+                       d.data[i] = d.nsolid
+                       d.nsolid++
+               } else {
+                       // freeze j
+                       d.data[j] = d.nsolid
+                       d.nsolid++
                }
        }
 
-       if _, present := d.keys[i]; !present {
+       if d.data[i] == d.gas {
                d.candidate = i
-               return false
-       }
-       if _, present := d.keys[j]; !present {
+       } else if d.data[j] == d.gas {
                d.candidate = j
-               return true
        }
 
-       return d.keys[i] >= d.keys[j]
+       return d.data[i] < d.data[j]
 }
 
 func (d *adversaryTestingData) Swap(i, j int) {
        d.data[i], d.data[j] = d.data[j], d.data[i]
 }
 
-func TestAdversary(t *testing.T) {
-       const size = 100
+func newAdversaryTestingData(t *testing.T, size int, maxcmp int) *adversaryTestingData {
+       gas := size - 1
        data := make([]int, size)
        for i := 0; i < size; i++ {
-               data[i] = i
+               data[i] = gas
        }
+       return &adversaryTestingData{t: t, data: data, maxcmp: maxcmp, gas: gas}
+}
 
-       d := &adversaryTestingData{data, make(map[int]int), 0}
+func TestAdversary(t *testing.T) {
+       const size = 10000            // large enough to distinguish between O(n^2) and O(n*log(n))
+       maxcmp := size * lg(size) * 4 // the factor 4 was found by trial and error
+       d := newAdversaryTestingData(t, size, maxcmp)
        Sort(d) // This should degenerate to heapsort.
+       // Check data is fully populated and sorted.
+       for i, v := range d.data {
+               if v != i {
+                       t.Errorf("adversary data not fully sorted")
+                       t.FailNow()
+               }
+       }
 }
 
 func TestStableInts(t *testing.T) {