Files
moby/vendor/pgregory.net/rapid/combinators.go
Cory Snider d8730dc1d3 libnetwork/networkdb: add convergence test
Add a property-based test which asserts that a cluster of NetworkDB
nodes always eventually converges to a consistent state. As this test
takes a long time to run it is build-tagged to be excluded from CI.

Signed-off-by: Cory Snider <csnider@mirantis.com>
2025-07-15 18:07:09 -04:00

288 lines
6.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2019 Gregory Petrosyan <gregory.petrosyan@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
package rapid
import (
"fmt"
"math"
"strings"
)
const tryLabel = "try"
// Custom creates a generator which produces results of calling fn. In fn, values should be generated
// by calling other generators; it is invalid to return a value from fn without using any other generator.
// Custom is a primary way of creating user-defined generators.
func Custom[V any](fn func(*T) V) *Generator[V] {
return newGenerator[V](&customGen[V]{
fn: fn,
})
}
type customGen[V any] struct {
fn func(*T) V
}
func (g *customGen[V]) String() string {
var v V
return fmt.Sprintf("Custom(%T)", v)
}
func (g *customGen[V]) value(t *T) V {
return find(g.maybeValue, t, small)
}
func (g *customGen[V]) maybeValue(t *T) (V, bool) {
t = newT(t.tb, t.s, flags.debug, nil)
defer t.cleanup()
defer func() {
if r := recover(); r != nil {
if _, ok := r.(invalidData); !ok {
panic(r)
}
}
}()
return g.fn(t), true
}
// Deferred creates a generator which defers calling fn until attempting to produce a value. This allows
// to define recursive generators.
func Deferred[V any](fn func() *Generator[V]) *Generator[V] {
return newGenerator[V](&deferredGen[V]{
fn: fn,
})
}
type deferredGen[V any] struct {
g *Generator[V]
fn func() *Generator[V]
}
func (g *deferredGen[V]) String() string {
var v V
return fmt.Sprintf("Deferred(%T)", v)
}
func (g *deferredGen[V]) value(t *T) V {
if g.g == nil {
g.g = g.fn()
}
return g.g.value(t)
}
func filter[V any](g *Generator[V], fn func(V) bool) *Generator[V] {
return newGenerator[V](&filteredGen[V]{
g: g,
fn: fn,
})
}
type filteredGen[V any] struct {
g *Generator[V]
fn func(V) bool
}
func (g *filteredGen[V]) String() string {
return fmt.Sprintf("%v.Filter(...)", g.g)
}
func (g *filteredGen[V]) value(t *T) V {
return find(g.maybeValue, t, small)
}
func (g *filteredGen[V]) maybeValue(t *T) (V, bool) {
v := g.g.value(t)
if g.fn(v) {
return v, true
} else {
var zero V
return zero, false
}
}
func find[V any](gen func(*T) (V, bool), t *T, tries int) V {
for n := 0; n < tries; n++ {
i := t.s.beginGroup(tryLabel, false)
v, ok := gen(t)
t.s.endGroup(i, !ok)
if ok {
return v
}
}
panic(invalidData(fmt.Sprintf("failed to find suitable value in %d tries", tries)))
}
// Map creates a generator producing fn(u) for each u produced by g.
func Map[U any, V any](g *Generator[U], fn func(U) V) *Generator[V] {
return newGenerator[V](&mappedGen[U, V]{
g: g,
fn: fn,
})
}
type mappedGen[U any, V any] struct {
g *Generator[U]
fn func(U) V
}
func (g *mappedGen[U, V]) String() string {
return fmt.Sprintf("Map(%v, %T)", g.g, g.fn)
}
func (g *mappedGen[U, V]) value(t *T) V {
return g.fn(g.g.value(t))
}
// Just creates a generator which always produces the given value.
// Just(val) is a shorthand for [SampledFrom]([]V{val}).
func Just[V any](val V) *Generator[V] {
return SampledFrom([]V{val})
}
// SampledFrom creates a generator which produces values from the given slice.
// SampledFrom panics if slice is empty.
func SampledFrom[S ~[]E, E any](slice S) *Generator[E] {
assertf(len(slice) > 0, "slice should not be empty")
return newGenerator[E](&sampledGen[E]{
slice: slice,
})
}
type sampledGen[E any] struct {
slice []E
}
func (g *sampledGen[E]) String() string {
if len(g.slice) == 1 {
return fmt.Sprintf("Just(%v)", g.slice[0])
} else {
return fmt.Sprintf("SampledFrom(%v %T)", len(g.slice), g.slice[0])
}
}
func (g *sampledGen[E]) value(t *T) E {
i := genIndex(t.s, len(g.slice), true)
return g.slice[i]
}
// Permutation creates a generator which produces permutations of the given slice.
func Permutation[S ~[]E, E any](slice S) *Generator[S] {
return newGenerator[S](&permGen[S, E]{
slice: slice,
})
}
type permGen[S ~[]E, E any] struct {
slice S
}
func (g *permGen[S, E]) String() string {
var zero E
return fmt.Sprintf("Permutation(%v %T)", len(g.slice), zero)
}
func (g *permGen[S, E]) value(t *T) S {
s := append(S(nil), g.slice...)
n := len(s)
m := n - 1
if m < 0 {
m = 0
}
// shrink-friendly variant of FisherYates shuffle: shrinks to lower number of smaller distance swaps
repeat := newRepeat(0, m, math.MaxInt, "permute")
for i := 0; repeat.more(t.s); i++ {
j, _, _ := genUintRange(t.s, uint64(i), uint64(n-1), false)
s[i], s[j] = s[j], s[i]
}
return s
}
// OneOf creates a generator which produces each value by selecting one of gens and producing a value from it.
// OneOf panics if gens is empty.
func OneOf[V any](gens ...*Generator[V]) *Generator[V] {
assertf(len(gens) > 0, "at least one generator should be specified")
return newGenerator[V](&oneOfGen[V]{
gens: gens,
})
}
type oneOfGen[V any] struct {
gens []*Generator[V]
}
func (g *oneOfGen[V]) String() string {
strs := make([]string, len(g.gens))
for i, g := range g.gens {
strs[i] = g.String()
}
return fmt.Sprintf("OneOf(%v)", strings.Join(strs, ", "))
}
func (g *oneOfGen[V]) value(t *T) V {
i := genIndex(t.s, len(g.gens), true)
return g.gens[i].value(t)
}
// Ptr creates a *E generator. If allowNil is true, Ptr can return nil pointers.
func Ptr[E any](elem *Generator[E], allowNil bool) *Generator[*E] {
return newGenerator[*E](&ptrGen[E]{
elem: elem,
allowNil: allowNil,
})
}
type ptrGen[E any] struct {
elem *Generator[E]
allowNil bool
}
func (g *ptrGen[E]) String() string {
return fmt.Sprintf("Ptr(%v, allowNil=%v)", g.elem, g.allowNil)
}
func (g *ptrGen[E]) value(t *T) *E {
pNonNil := float64(1)
if g.allowNil {
pNonNil = 0.5
}
if flipBiasedCoin(t.s, pNonNil) {
e := g.elem.value(t)
return &e
} else {
return nil
}
}
func asAny[V any](g *Generator[V]) *Generator[any] {
return newGenerator[any](&asAnyGen[V]{
gen: g,
})
}
type asAnyGen[V any] struct {
gen *Generator[V]
}
func (g *asAnyGen[V]) String() string {
return fmt.Sprintf("%v.AsAny()", g.gen)
}
func (g *asAnyGen[V]) value(t *T) any {
return g.gen.value(t)
}