summaryrefslogtreecommitdiff
path: root/pkgtools/pkglint/files/fuzzer_test.go
blob: 6a995bae2deef6fb8b3dc1948dd64feecb453311 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package pkglint

import (
	"gopkg.in/check.v1"
	"math/rand"
	"time"
)

// Fuzzer generates random strings.
// The structure of the strings is configurable.
type Fuzzer struct {
	seed  int64
	rnd   *rand.Rand
	stock []struct {
		r      rune
		weight int
	}
	total int    // The sum of all stock weights
	last  string //
	ok    bool   // Whether the last string was processed correctly
}

// NewFuzzer returns a fuzzer.
// If no seed is passed, a random seed is chosen.
// To reproduce a previous run, pass the seed from that run as the parameter.
func NewFuzzer(seed ...int64) *Fuzzer {
	var actualSeed int64
	if len(seed) > 0 {
		actualSeed = seed[0]
	} else {
		actualSeed = time.Now().UnixNano()
	}
	return &Fuzzer{seed: actualSeed, rnd: rand.New(rand.NewSource(actualSeed))}
}

// Char randomly generates a character from the given set.
// Each character has the given weight.
func (f *Fuzzer) Char(set string, weight int) {
	for _, r := range set {
		f.addChar(r, weight)
	}
}

// Range randomly generates a character from the given range.
// Each character has the given weight.
func (f *Fuzzer) Range(minIncl, maxIncl rune, weight int) {
	for r := minIncl; r <= maxIncl; r++ {
		f.addChar(r, weight)
	}
}

func (f *Fuzzer) addChar(r rune, weight int) {
	f.stock = append(f.stock, struct {
		r      rune
		weight int
	}{r, weight})
	f.total += weight
}

func (f *Fuzzer) Generate(length int) string {
	rs := make([]rune, length)
	for i := 0; i < length; i++ {
		rs[i] = f.randomChar()
	}
	f.last = string(rs)
	return f.last
}

func (f *Fuzzer) randomChar() rune {
	i := int(f.rnd.Int31n(int32(f.total)))
	for _, entry := range f.stock {
		i -= entry.weight
		if i < 0 {
			return entry.r
		}
	}
	panic("Out of stock")
}

// CheckOk is typically used in a defer statement and is run after all
// the tests to check whether they have been marked as ok.
func (f *Fuzzer) CheckOk() {
	if !f.ok {
		panic(sprintf("Fuzzing failed with seed %d, last generated value: %s", f.seed, f.last))
	}
}

// Ok marks the current string as processed correctly.
func (f *Fuzzer) Ok() { f.ok = true }

func (s *Suite) Test_Fuzzer__out_of_stock(c *check.C) {
	fuzzer := NewFuzzer(0)
	fuzzer.total = 1 // Intentionally damage the fuzzer to achieve full code coverage.

	c.Check(
		func() { fuzzer.Generate(1) },
		check.Panics,
		"Out of stock")
}