Documentation ¶
Overview ¶
Package rapid implements utilities for property-based testing.
Rapid checks that properties you define hold for a large number of automatically generated test cases. If a failure is found, rapid fails the current test and presents an automatically minimized version of the failing test case.
Here is what a trivial test using rapid looks like:
package rapid_test import ( "net" "testing" "pgregory.net/rapid" ) func TestParseValidIPv4(t *testing.T) { const ipv4re = `(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])` + `\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])` + `\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])` + `\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])` rapid.Check(t, func(t *rapid.T) { addr := rapid.StringMatching(ipv4re).Draw(t, "addr").(string) ip := net.ParseIP(addr) if ip == nil || ip.String() != addr { t.Fatalf("parsed %q into %v", addr, ip) } }) }
Index ¶
- func Check(t *testing.T, prop func(*T))
- func MakeCheck(prop func(*T)) func(*testing.T)
- func Run(m StateMachine) func(*T)
- type Generator
- func ArrayOf(count int, elem *Generator) *Generator
- func Bool() *Generator
- func Byte() *Generator
- func ByteMax(max byte) *Generator
- func ByteMin(min byte) *Generator
- func ByteRange(min byte, max byte) *Generator
- func Custom(fn interface{}) *Generator
- func Float32() *Generator
- func Float32Max(max float32) *Generator
- func Float32Min(min float32) *Generator
- func Float32Range(min float32, max float32) *Generator
- func Float64() *Generator
- func Float64Max(max float64) *Generator
- func Float64Min(min float64) *Generator
- func Float64Range(min float64, max float64) *Generator
- func Int() *Generator
- func Int16() *Generator
- func Int16Max(max int16) *Generator
- func Int16Min(min int16) *Generator
- func Int16Range(min int16, max int16) *Generator
- func Int32() *Generator
- func Int32Max(max int32) *Generator
- func Int32Min(min int32) *Generator
- func Int32Range(min int32, max int32) *Generator
- func Int64() *Generator
- func Int64Max(max int64) *Generator
- func Int64Min(min int64) *Generator
- func Int64Range(min int64, max int64) *Generator
- func Int8() *Generator
- func Int8Max(max int8) *Generator
- func Int8Min(min int8) *Generator
- func Int8Range(min int8, max int8) *Generator
- func IntMax(max int) *Generator
- func IntMin(min int) *Generator
- func IntRange(min int, max int) *Generator
- func Just(val interface{}) *Generator
- func MapOf(key *Generator, val *Generator) *Generator
- func MapOfN(key *Generator, val *Generator, minLen int, maxLen int) *Generator
- func MapOfNValues(val *Generator, minLen int, maxLen int, keyFn interface{}) *Generator
- func MapOfValues(val *Generator, keyFn interface{}) *Generator
- func OneOf(gens ...*Generator) *Generator
- func Ptr(elem *Generator, allowNil bool) *Generator
- func Rune() *Generator
- func RuneFrom(runes []rune, tables ...*unicode.RangeTable) *Generator
- func SampledFrom(slice interface{}) *Generator
- func SliceOf(elem *Generator) *Generator
- func SliceOfBytesMatching(expr string) *Generator
- func SliceOfDistinct(elem *Generator, keyFn interface{}) *Generator
- func SliceOfN(elem *Generator, minLen int, maxLen int) *Generator
- func SliceOfNDistinct(elem *Generator, minLen int, maxLen int, keyFn interface{}) *Generator
- func String() *Generator
- func StringMatching(expr string) *Generator
- func StringN(minRunes int, maxRunes int, maxLen int) *Generator
- func StringOf(elem *Generator) *Generator
- func StringOfN(elem *Generator, minElems int, maxElems int, maxLen int) *Generator
- func Uint() *Generator
- func Uint16() *Generator
- func Uint16Max(max uint16) *Generator
- func Uint16Min(min uint16) *Generator
- func Uint16Range(min uint16, max uint16) *Generator
- func Uint32() *Generator
- func Uint32Max(max uint32) *Generator
- func Uint32Min(min uint32) *Generator
- func Uint32Range(min uint32, max uint32) *Generator
- func Uint64() *Generator
- func Uint64Max(max uint64) *Generator
- func Uint64Min(min uint64) *Generator
- func Uint64Range(min uint64, max uint64) *Generator
- func Uint8() *Generator
- func Uint8Max(max uint8) *Generator
- func Uint8Min(min uint8) *Generator
- func Uint8Range(min uint8, max uint8) *Generator
- func UintMax(max uint) *Generator
- func UintMin(min uint) *Generator
- func UintRange(min uint, max uint) *Generator
- func Uintptr() *Generator
- func UintptrMax(max uintptr) *Generator
- func UintptrMin(min uintptr) *Generator
- func UintptrRange(min uintptr, max uintptr) *Generator
- type StateMachine
- type T
- func (t *T) Error(args ...interface{})
- func (t *T) Errorf(format string, args ...interface{})
- func (t *T) Fail()
- func (t *T) FailNow()
- func (t *T) Failed() bool
- func (t *T) Fatal(args ...interface{})
- func (t *T) Fatalf(format string, args ...interface{})
- func (t *T) Log(args ...interface{})
- func (t *T) Logf(format string, args ...interface{})
- func (t *T) Skip(args ...interface{})
- func (t *T) SkipNow()
- func (t *T) Skipf(format string, args ...interface{})
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Check ¶
Check fails the current test if rapid can find a test case which falsifies prop.
Property is falsified in case of a panic or a call to (*T).Fatalf, (*T).Fatal, (*T).Errorf, (*T).Error, (*T).FailNow or (*T).Fail.
Example (ParseDate) ¶
Rename to TestParseDate(t *testing.T) to make an actual (failing) test.
package main import ( "fmt" "strconv" "testing" "pgregory.net/rapid" ) // ParseDate parses dates in the YYYY-MM-DD format. func ParseDate(s string) (int, int, int, error) { if len(s) != 10 { return 0, 0, 0, fmt.Errorf("%q has wrong length: %v instead of 10", s, len(s)) } if s[4] != '-' || s[7] != '-' { return 0, 0, 0, fmt.Errorf("'-' separators expected in %q", s) } y, err := strconv.Atoi(s[0:4]) if err != nil { return 0, 0, 0, fmt.Errorf("failed to parse year: %v", err) } m, err := strconv.Atoi(s[6:7]) if err != nil { return 0, 0, 0, fmt.Errorf("failed to parse month: %v", err) } d, err := strconv.Atoi(s[8:10]) if err != nil { return 0, 0, 0, fmt.Errorf("failed to parse day: %v", err) } return y, m, d, nil } func testParseDate(t *rapid.T) { y := rapid.IntRange(0, 9999).Draw(t, "y").(int) m := rapid.IntRange(1, 12).Draw(t, "m").(int) d := rapid.IntRange(1, 31).Draw(t, "d").(int) s := fmt.Sprintf("%04d-%02d-%02d", y, m, d) y_, m_, d_, err := ParseDate(s) if err != nil { t.Fatalf("failed to parse date %q: %v", s, err) } if y_ != y || m_ != m || d_ != d { t.Fatalf("got back wrong date: (%d, %d, %d)", y_, m_, d_) } } // Rename to TestParseDate(t *testing.T) to make an actual (failing) test. func main() { var t *testing.T rapid.Check(t, testParseDate) }
Output:
func MakeCheck ¶
MakeCheck is a convenience function for defining subtests suitable for (*testing.T).Run. It allows you to write this:
t.Run("subtest name", rapid.MakeCheck(func(t *rapid.T) { // test code }))
instead of this:
t.Run("subtest name", func(t *testing.T) { rapid.Check(t, func(t *rapid.T) { // test code }) })
func Run ¶ added in v0.3.1
func Run(m StateMachine) func(*T)
Run is a convenience function for defining "state machine" tests, to be run by Check or MakeCheck.
State machine test is a pattern for testing stateful systems that looks like this:
m := new(StateMachineType) m.Init(t) // optional defer m.Cleanup() // optional m.Check(t) for { m.RandomAction(t) m.Check(t) }
Run synthesizes such test from the type of m, which must be a pointer. Note that for each test case, new state machine instance is created via reflection; any data inside m is ignored.
Example (Queue) ¶
Rename to TestQueue(t *testing.T) to make an actual (failing) test.
package main import ( "testing" "pgregory.net/rapid" ) // Queue implements integer queue with a fixed maximum size. type Queue struct { buf []int in int out int } func NewQueue(n int) *Queue { return &Queue{ buf: make([]int, n+1), } } // Precondition: Size() > 0. func (q *Queue) Get() int { i := q.buf[q.out] q.out = (q.out + 1) % len(q.buf) return i } // Precondition: Size() < n. func (q *Queue) Put(i int) { q.buf[q.in] = i q.in = (q.in + 1) % len(q.buf) } func (q *Queue) Size() int { return (q.in - q.out) % len(q.buf) } // queueMachine is a description of a rapid state machine for testing Queue type queueMachine struct { q *Queue // queue being tested n int // maximum queue size state []int // model of the queue } // Init is an action for initializing a queueMachine instance. func (m *queueMachine) Init(t *rapid.T) { n := rapid.IntRange(1, 1000).Draw(t, "n").(int) m.q = NewQueue(n) m.n = n } // Get is a conditional action which removes an item from the queue. func (m *queueMachine) Get(t *rapid.T) { if m.q.Size() == 0 { t.Skip("queue empty") } i := m.q.Get() if i != m.state[0] { t.Fatalf("got invalid value: %v vs expected %v", i, m.state[0]) } m.state = m.state[1:] } // Put is a conditional action which adds an items to the queue. func (m *queueMachine) Put(t *rapid.T) { if m.q.Size() == m.n { t.Skip("queue full") } i := rapid.Int().Draw(t, "i").(int) m.q.Put(i) m.state = append(m.state, i) } // Check runs after every action and verifies that all required invariants hold. func (m *queueMachine) Check(t *rapid.T) { if m.q.Size() != len(m.state) { t.Fatalf("queue size mismatch: %v vs expected %v", m.q.Size(), len(m.state)) } } // Rename to TestQueue(t *testing.T) to make an actual (failing) test. func main() { var t *testing.T rapid.Check(t, rapid.Run(&queueMachine{})) }
Output:
Types ¶
type Generator ¶
type Generator struct {
// contains filtered or unexported fields
}
func ArrayOf ¶
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.ArrayOf(5, rapid.Int()) for i := 0; i < 5; i++ { fmt.Println(gen.Example(i)) } }
Output: [-3 1303 184 7 236258] [-186981 -59881619 0 -1 168442] [4 441488606 -4008258 -2 297] [-2 -5863986 22973756520 -15 766316951] [43 -3513 16 141395 -9223372036854775808]
func Custom ¶
func Custom(fn interface{}) *Generator
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { type point struct { x int y int } gen := rapid.Custom(func(t *rapid.T) point { return point{ x: rapid.Int().Draw(t, "x").(int), y: rapid.Int().Draw(t, "y").(int), } }) for i := 0; i < 5; i++ { fmt.Println(gen.Example(i)) } }
Output: {-3 1303} {-186981 -59881619} {4 441488606} {-2 -5863986} {43 -3513}
func Float32Max ¶
func Float32Min ¶
func Float32Range ¶
func Float64Max ¶
func Float64Min ¶
func Float64Range ¶
func Int16Range ¶
func Int32Range ¶
func Int64Range ¶
func Just ¶
func Just(val interface{}) *Generator
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.Just(42) for i := 0; i < 5; i++ { fmt.Println(gen.Example(i)) } }
Output: 42 42 42 42 42
func MapOf ¶
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.MapOf(rapid.Int(), rapid.StringMatching(`[a-z]+`)) for i := 0; i < 5; i++ { fmt.Println(gen.Example(i)) } }
Output: map[1:nhlgqwasbggbaociac 561860:r] map[-3752:pizpv -3:bacuabp 0:bi] map[-33086515648293:gewf -264276:b -1313:a -258:v -4:b -2:fdhbzcz 4:ubfsdbowrja 1775:tcozav 8334:lvcprss 376914:braigey] map[-350:h 590:coaaamcasnapgaad] map[]
func MapOfN ¶
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.MapOfN(rapid.Int(), rapid.StringMatching(`[a-z]+`), 5, 5) for i := 0; i < 5; i++ { fmt.Println(gen.Example(i)) } }
Output: map[-130450326583:bd -2983:bbdbcs 1:nhlgqwasbggbaociac 31:kmdnpmcbuagzr 561860:r] map[-82024404:d -3752:pizpv -3:bacuabp 0:bi 179745:rzkneb] map[-33086515648293:gewf -258:v 4:ubfsdbowrja 1775:tcozav 8334:lvcprss] map[-4280678227:j -25651:aafmd -3308:o -350:h 590:coaaamcasnapgaad] map[-9614404661322:gsb -378:y 2:paai 4629136912:otg 1476419818092:qign]
func MapOfNValues ¶
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.MapOfNValues(rapid.StringMatching(`[a-z]+`), 5, 5, func(s string) int { return len(s) }) for i := 0; i < 5; i++ { fmt.Println(gen.Example(i)) } }
Output: map[1:s 2:dr 3:anc 7:xguehfc 11:sbggbaociac] map[1:b 2:bp 4:ydag 5:jarxz 6:ebzkwa] map[1:j 3:gjl 5:eeeqa 7:stcozav 9:fxmcadagf] map[2:ub 8:waraafmd 10:bfiqcaxazu 16:rjgqimcasnapgaad 17:gckfbljafcedhcvfc] map[1:k 2:ay 3:wzb 4:dign 7:faabhcb]
func MapOfValues ¶
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.MapOfValues(rapid.StringMatching(`[a-z]+`), func(s string) int { return len(s) }) for i := 0; i < 5; i++ { fmt.Println(gen.Example(i)) } }
Output: map[2:dr 7:xguehfc 11:sbggbaociac] map[2:bp 5:jarxz 6:ebzkwa] map[1:j 2:aj 3:gjl 4:vayt 5:eeeqa 6:riacaa 7:stcozav 8:mfdhbzcz 9:fxmcadagf 10:bgsbraigey 15:gxongygnxqlovib] map[2:ub 8:waraafmd 10:bfiqcaxazu 16:rjgqimcasnapgaad 17:gckfbljafcedhcvfc] map[]
func OneOf ¶
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.OneOf(rapid.Int32Range(1, 10), rapid.Float32Range(100, 1000)) for i := 0; i < 5; i++ { fmt.Println(gen.Example(i)) } }
Output: 997.0737 10 475.3125 2 9
func Ptr ¶
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.Ptr(rapid.Int(), true) for i := 0; i < 5; i++ { v := gen.Example(i).(*int) if v == nil { fmt.Println("<nil>") } else { fmt.Println("(*int)", *v) } } }
Output: (*int) 1 (*int) -3 <nil> (*int) 590 <nil>
func Rune ¶
func Rune() *Generator
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.Rune() for i := 0; i < 25; i++ { if i%5 == 0 { fmt.Println() } else { fmt.Print(" ") } fmt.Printf("%q", gen.Example(i)) } }
Output: '\\' '\ufeff' '?' '~' '-' '0' '$' '!' '`' '\ue05d' '"' '&' '#' '\u0604' 'A' '&' '茞' '@' '#' '|' '⊙' '𝩔' '$' '҈' '\r'
func RuneFrom ¶
func RuneFrom(runes []rune, tables ...*unicode.RangeTable) *Generator
Example ¶
package main import ( "fmt" "unicode" "pgregory.net/rapid" ) func main() { gens := []*rapid.Generator{ rapid.RuneFrom([]rune{'A', 'B', 'C'}), rapid.RuneFrom(nil, unicode.Cyrillic, unicode.Greek), rapid.RuneFrom([]rune{'⌘'}, &unicode.RangeTable{ R32: []unicode.Range32{{0x1F600, 0x1F64F, 1}}, }), } for _, gen := range gens { for i := 0; i < 5; i++ { if i > 0 { fmt.Print(" ") } fmt.Printf("%q", gen.Example(i)) } fmt.Println() } }
Output: 'A' 'A' 'A' 'B' 'A' 'Ͱ' 'Ѥ' 'Ͱ' 'ͱ' 'Ϳ' '😀' '⌘' '😀' '😁' '😋'
func SampledFrom ¶
func SampledFrom(slice interface{}) *Generator
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.SampledFrom([]int{1, 2, 3}) for i := 0; i < 5; i++ { fmt.Println(gen.Example(i)) } }
Output: 2 3 2 3 1
func SliceOf ¶
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.SliceOf(rapid.Int()) for i := 0; i < 5; i++ { fmt.Println(gen.Example(i)) } }
Output: [1 -1902 7 -236 14 -433 -1572631 -1 4219826 -50 1414 -3890044391133 -9223372036854775808 5755498240 -10 680558 10 -80458281 0 -27] [-3 -2 -1 -3 -2172865589 -5 -2 -2503553836720] [4 308 -2 21 -5843 3 1 78 6129321692 -59] [590 -131 -15 -769 16 -1 14668 14 -1 -58784] []
func SliceOfBytesMatching ¶
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.SliceOfBytesMatching(`[CAGT]+`) for i := 0; i < 5; i++ { fmt.Printf("%q\n", gen.Example(i)) } }
Output: "CCTTGAGAGCGATACGGAAG" "GCAGAACT" "AACCGTCGAG" "GGGAAAAGAT" "AGTG"
func SliceOfDistinct ¶
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.SliceOfDistinct(rapid.IntMin(0), func(i int) int { return i % 2 }) for i := 0; i < 5; i++ { fmt.Println(gen.Example(i)) } }
Output: [1] [2 1] [4 1] [590] []
func SliceOfN ¶
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.SliceOfN(rapid.Int(), 5, 5) for i := 0; i < 5; i++ { fmt.Println(gen.Example(i)) } }
Output: [1 -1902 7 -236 14] [-3 -2 -1 -3 -2172865589] [4 308 -2 21 -5843] [590 -131 -15 -769 16] [4629136912 270 141395 -129322425838843911 -7]
func SliceOfNDistinct ¶
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.SliceOfNDistinct(rapid.IntMin(0), 2, 2, func(i int) int { return i % 2 }) for i := 0; i < 5; i++ { fmt.Println(gen.Example(i)) } }
Output: [4219826 49] [2 1] [4 1] [0 58783] [4629136912 141395]
func String ¶
func String() *Generator
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.String() for i := 0; i < 5; i++ { fmt.Printf("%q\n", gen.Example(i)) } }
Output: "\\߾⃝!/?Ⱥ֍" "\u2006𑨷" "?﹩\u0603ᾢ" ".*%:<%৲" ""
func StringMatching ¶
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.StringMatching(`\(?([0-9]{3})\)?([ .-]?)([0-9]{3})([ .-]?)([0-9]{4})`) for i := 0; i < 5; i++ { fmt.Printf("%q\n", gen.Example(i)) } }
Output: "(532) 649-9610" "901)-5783983" "914.444.1575" "(316 696.3584" "816)0861080"
func StringN ¶
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.StringN(5, 5, -1) for i := 0; i < 5; i++ { fmt.Printf("%q\n", gen.Example(i)) } }
Output: "\\߾⃝!/" "\u2006𑨷%\v\ufeff" "?﹩\u0603ᾢÉ" ".*%:<" ":?\"~¤"
func StringOf ¶
Example ¶
package main import ( "fmt" "unicode" "pgregory.net/rapid" ) func main() { gen := rapid.StringOf(rapid.RuneFrom(nil, unicode.Tibetan)) for i := 0; i < 5; i++ { fmt.Printf("%q\n", gen.Example(i)) } }
Output: "༁༭༇ཬ༆༐༖ༀྸ༁༆༎ༀ༁ཱི༂༨ༀ༂" "༂༁ༀ༂༴ༀ༁ྵ" "ༀ༴༁༅ན༃༁༎ྼ༄༽" "༎༂༎ༀༀༀཌྷ༂ༀྥ" ""
func StringOfN ¶
Example ¶
package main import ( "fmt" "pgregory.net/rapid" ) func main() { gen := rapid.StringOfN(rapid.ByteRange(65, 90), 5, 5, -1) for i := 0; i < 5; i++ { fmt.Printf("%q\n", gen.Example(i)) } }
Output: "AXYHC" "ESAAC" "AUGWT" "BRIOX" "LYATZ"
func Uint16Range ¶
func Uint32Range ¶
func Uint64Range ¶
func Uint8Range ¶
func UintptrMax ¶
func UintptrMin ¶
func UintptrRange ¶
type StateMachine ¶
type StateMachine interface { // Check is ran after every action and should contain invariant checks. // // Other public methods are treated as follows: // - Init(t *rapid.T), if present, is ran at the beginning of each test case // to initialize the state machine instance; // - Cleanup(), if present, is called at the end of each test case; // - All other public methods should have a form ActionName(t *rapid.T) // and are used as possible actions. At least one action has to be specified. // Check(*T) }
type T ¶
type T struct {
// contains filtered or unexported fields
}
func (*T) Error ¶
func (t *T) Error(args ...interface{})
Error is equivalent to Log followed by Fail.
func (*T) Fatal ¶
func (t *T) Fatal(args ...interface{})
Fatal is equivalent to Log followed by FailNow.
func (*T) Skip ¶
func (t *T) Skip(args ...interface{})
Skip is equivalent to Log followed by SkipNow.
func (*T) SkipNow ¶
func (t *T) SkipNow()
SkipNow marks the current test case as invalid (except state machine tests, where it marks current action as non-applicable instead). If too many test cases are skipped, rapid will mark the test as failing due to inability to generate enough valid test cases.
Prefer Filter to SkipNow, and prefer generators that always produce valid test cases to Filter.