github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/bisect/minimize/slice_test.go (about)

     1  // Copyright 2023 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package minimize
     5  
     6  import (
     7  	"fmt"
     8  	"math"
     9  	"math/rand"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/google/syzkaller/pkg/testutil"
    14  	"github.com/stretchr/testify/assert"
    15  )
    16  
    17  func TestBisectSliceToZero(t *testing.T) {
    18  	t.Parallel()
    19  	array := make([]int, 100)
    20  	ret, err := Slice(Config[int]{
    21  		Pred: func(arr []int) (bool, error) {
    22  			// No elements are needed.
    23  			return true, nil
    24  		},
    25  		Logf: t.Logf,
    26  	}, array)
    27  	assert.NoError(t, err)
    28  	assert.Len(t, ret, 0)
    29  }
    30  
    31  func TestBisectSliceFull(t *testing.T) {
    32  	t.Parallel()
    33  	array := make([]int, 100)
    34  	ret, err := Slice(Config[int]{
    35  		Pred: func(arr []int) (bool, error) {
    36  			// All elements are needed.
    37  			return false, nil
    38  		},
    39  		Logf: t.Logf,
    40  	}, array)
    41  	assert.NoError(t, err)
    42  	assert.Equal(t, ret, array)
    43  }
    44  
    45  func TestBisectSliceWithFixed(t *testing.T) {
    46  	t.Parallel()
    47  	array := make([]int, 100)
    48  	for i := 0; i < 100; i++ {
    49  		array[i] = i
    50  	}
    51  	ret, err := SliceWithFixed(Config[int]{
    52  		Pred: func(arr []int) (bool, error) {
    53  			// Let's ensure that there are always fixed elements.
    54  			values := map[int]bool{}
    55  			for _, v := range arr {
    56  				values[v] = true
    57  			}
    58  			if !values[0] || !values[20] || !values[40] {
    59  				t.Fatal("predicate step without fixed elements")
    60  			}
    61  			// And also require the elements 25 and 50.
    62  			return values[25] && values[50], nil
    63  		},
    64  		Logf: t.Logf,
    65  	}, array, func(elem int) bool {
    66  		// Let's keep these 3 elements.
    67  		return elem == 0 || elem == 20 || elem == 40
    68  	})
    69  	assert.NoError(t, err)
    70  	assert.Equal(t, []int{0, 20, 25, 40, 50}, ret)
    71  }
    72  
    73  func TestBisectRandomSlice(t *testing.T) {
    74  	t.Parallel()
    75  	r := rand.New(testutil.RandSource(t))
    76  	for i := 0; i < testutil.IterCount(); i++ {
    77  		// Create an array of random size and set the elements that must remain to non-zero values.
    78  		size := r.Intn(50)
    79  		subset := r.Intn(size + 1)
    80  		array := make([]int, size)
    81  		for _, j := range r.Perm(size)[:subset] {
    82  			array[j] = j + 1
    83  		}
    84  		var expect []int
    85  		for _, j := range array {
    86  			if j > 0 {
    87  				expect = append(expect, j)
    88  			}
    89  		}
    90  		predCalls := 0
    91  		ret, err := Slice(Config[int]{
    92  			Pred: func(arr []int) (bool, error) {
    93  				predCalls++
    94  				// All elements of the subarray must be present.
    95  				nonZero := 0
    96  				for _, x := range arr {
    97  					if x > 0 {
    98  						nonZero++
    99  					}
   100  				}
   101  				return nonZero == subset, nil
   102  			},
   103  			Logf: t.Logf,
   104  		}, array)
   105  		assert.NoError(t, err)
   106  		assert.EqualValues(t, expect, ret)
   107  		// Ensure we don't make too many predicate calls.
   108  		maxCalls := 3 + 2*subset*(1+int(math.Floor(math.Log2(float64(size)))))
   109  		assert.LessOrEqual(t, predCalls, maxCalls)
   110  	}
   111  }
   112  
   113  func BenchmarkSplits(b *testing.B) {
   114  	for _, guilty := range []int{1, 2, 3, 4} {
   115  		b.Run(fmt.Sprintf("%d_guilty", guilty), func(b *testing.B) {
   116  			var sum int
   117  			for i := 0; i < b.N; i++ {
   118  				sum += runMinimize(guilty)
   119  			}
   120  			b.ReportMetric(float64(sum)/float64(b.N), "remaining-elements")
   121  		})
   122  	}
   123  }
   124  
   125  func runMinimize(guilty int) int {
   126  	const size = 300
   127  	const steps = 5
   128  
   129  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   130  	array := make([]int, size)
   131  	for _, j := range r.Perm(size)[:guilty] {
   132  		array[j] = 1
   133  	}
   134  
   135  	ret, _ := Slice(Config[int]{
   136  		MaxSteps: steps,
   137  		Pred: func(arr []int) (bool, error) {
   138  			nonZero := 0
   139  			for _, x := range arr {
   140  				if x > 0 {
   141  					nonZero++
   142  				}
   143  			}
   144  			return nonZero == guilty, nil
   145  		},
   146  	}, array)
   147  	return len(ret)
   148  }