aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2020-01-17 19:30:03 +0100
committerDmitry Vyukov <dvyukov@google.com>2020-01-18 21:02:24 +0100
commitde577addbf322eaa76ab50478593df7b11f197ff (patch)
tree77e5da928957cf405c8330d212f96e8f2f05d43c
parent22535fecd5b37c6a14b92b17e548c8061ef77924 (diff)
prog: tune flags generation/mutation
Tune flags to generate more sane values over insane values based on examination of results for common cases.
-rw-r--r--prog/rand.go93
-rw-r--r--prog/rand_test.go56
-rw-r--r--prog/types.go2
3 files changed, 119 insertions, 32 deletions
diff --git a/prog/rand.go b/prog/rand.go
index 70a936870..bb3c81789 100644
--- a/prog/rand.go
+++ b/prog/rand.go
@@ -173,39 +173,70 @@ func (r *randGen) randPageCount() (n uint64) {
}
// Change a flag value or generate a new one.
-func (r *randGen) flags(vv []uint64, bitmask bool, oldVal uint64) (v uint64) {
- v = oldVal
- if r.oneOf(5) {
- // Ignore the old value sometimes.
- v = 0
- }
- switch {
- case (bitmask && r.nOutOf(7, 10)) || (!bitmask && r.nOutOf(1, 5)):
- // Try flipping randomly chosen flags.
- // Prioritized when bitmask == true.
- for stop := false; !stop; stop = r.oneOf(3) {
- flag := vv[r.rand(len(vv))]
- if r.oneOf(5) {
- // Try choosing adjacent bit values in case we forgot
- // to add all relevant flags to the descriptions.
- if r.bin() {
- flag >>= 1
- } else {
- flag <<= 1
- }
+// If you are changing this function, run TestFlags and examine effect of results.
+func (r *randGen) flags(vv []uint64, bitmask bool, oldVal uint64) uint64 {
+ // Get these simpler cases out of the way first.
+ // Once in a while we want to return completely random values,
+ // or 0 which is frequently special.
+ if r.oneOf(100) {
+ return r.rand64()
+ }
+ if r.oneOf(50) {
+ return 0
+ }
+ if !bitmask && oldVal != 0 && r.oneOf(100) {
+ // Slightly increment/decrement the old value.
+ // This is especially important during mutation when len(vv) == 1,
+ // otherwise in that case we produce almost no randomness
+ // (the value is always mutated to 0).
+ inc := uint64(1)
+ if r.bin() {
+ inc = ^uint64(0)
+ }
+ v := oldVal + inc
+ for r.bin() {
+ v += inc
+ }
+ return v
+ }
+ if len(vv) == 1 {
+ // This usually means that value or 0,
+ // at least that's our best (and only) bet.
+ if r.bin() {
+ return 0
+ }
+ return vv[0]
+ }
+ if !bitmask && !r.oneOf(10) {
+ // Enumeration, so just choose one of the values.
+ return vv[r.rand(len(vv))]
+ }
+ if r.oneOf(len(vv) + 4) {
+ return 0
+ }
+ // Flip rand bits. Do this for non-bitmask sometimes
+ // because we may have detected bitmask incorrectly for complex cases
+ // (e.g. part of the vlaue is bitmask and another is not).
+ v := oldVal
+ if v != 0 && r.oneOf(10) {
+ v = 0 // Ignore the old value sometimes.
+ }
+ // We don't want to return 0 here, because we already given 0
+ // fixed probability above (otherwise we get 0 too frequently).
+ for v == 0 || r.nOutOf(2, 3) {
+ flag := vv[r.rand(len(vv))]
+ if r.oneOf(20) {
+ // Try choosing adjacent bit values in case we forgot
+ // to add all relevant flags to the descriptions.
+ if r.bin() {
+ flag >>= 1
+ } else {
+ flag <<= 1
}
- v ^= flag
- }
- case (bitmask && r.nOutOf(2, 3)) || (!bitmask && r.nOutOf(7, 8)):
- // Chose a random flag.
- // Prioritized when bitmask == false.
- v = vv[r.rand(len(vv))]
- case r.bin():
- v = 0
- default:
- v = r.rand64()
+ }
+ v ^= flag
}
- return
+ return v
}
func (r *randGen) filename(s *state, typ *BufferType) string {
diff --git a/prog/rand_test.go b/prog/rand_test.go
index a17d60853..d308bf890 100644
--- a/prog/rand_test.go
+++ b/prog/rand_test.go
@@ -4,7 +4,10 @@
package prog
import (
+ "bytes"
+ "fmt"
"math/rand"
+ "sort"
"testing"
)
@@ -113,3 +116,56 @@ func TestSizeGenerateConstArg(t *testing.T) {
})
}
}
+
+func TestFlags(t *testing.T) {
+ // This test does not test anything, it just prints resulting
+ // distribution of values for different scenarios.
+ tests := []struct {
+ vv []uint64
+ bitmask bool
+ old uint64
+ }{
+ {[]uint64{0, 1, 2, 3}, false, 0},
+ {[]uint64{0, 1, 2, 3}, false, 2},
+ {[]uint64{1, 2, 3, 4}, false, 0},
+ {[]uint64{1, 2, 3, 4}, false, 2},
+ {[]uint64{1, 2, 4, 8}, true, 0},
+ {[]uint64{1, 2, 4, 8}, true, 2},
+ {[]uint64{7}, false, 0},
+ {[]uint64{7}, false, 7},
+ {[]uint64{1, 2}, true, 0},
+ {[]uint64{1, 2}, true, 2},
+ }
+ target, rs, _ := initRandomTargetTest(t, "test", "64")
+ r := newRand(target, rs)
+ for _, test := range tests {
+ results := make(map[uint64]uint64)
+ const throws = 1e4
+ for i := 0; i < throws; i++ {
+ var v uint64
+ for {
+ v = r.flags(test.vv, test.bitmask, test.old)
+ if test.old == 0 || test.old != v {
+ break
+ }
+ }
+ if v > 100 {
+ v = 999 // to not print all possible random values we generated
+ }
+ results[v]++
+ }
+ var sorted [][2]uint64
+ for v, c := range results {
+ sorted = append(sorted, [2]uint64{v, c})
+ }
+ sort.Slice(sorted, func(i, j int) bool {
+ return sorted[i][0] < sorted[j][0]
+ })
+ buf := new(bytes.Buffer)
+ for _, p := range sorted {
+ fmt.Fprintf(buf, "%v\t%v\n", p[0], p[1])
+ }
+ t.Logf("test: vv=%+v bitmask=%v old=%v\nvalue\ttimes (out of %v)\n%v",
+ test.vv, test.bitmask, test.old, throws, buf.String())
+ }
+}
diff --git a/prog/types.go b/prog/types.go
index 68a1330c0..bebf0aaea 100644
--- a/prog/types.go
+++ b/prog/types.go
@@ -304,7 +304,7 @@ func (t *IntType) isDefaultArg(arg Arg) bool {
type FlagsType struct {
IntTypeCommon
- Vals []uint64
+ Vals []uint64 // compiler ensures that it's not empty
BitMask bool
}