@@ -19,168 +19,31 @@ package roundtrip
1919import (
2020 "reflect"
2121 "testing"
22-
23- "pgregory.net/rapid"
24-
25- "github.com/algorand/go-algorand/protocol"
2622)
2723
28- const defaultRandomCount = 100
29-
30- // CheckOption configures the behavior of Check.
31- type CheckOption interface {
32- apply (* checkConfig )
33- }
34-
35- type checkConfig struct {
36- randomCount * int
37- randomOpts []protocol.RandomizeObjectOption
38- rapidGen any // *rapid.Generator[A], stored as any to avoid type parameters
39- useRapid bool
40- skipNearZeros bool
41- }
42-
43- type randomCountOption int
44-
45- func (n randomCountOption ) apply (cfg * checkConfig ) {
46- count := int (n )
47- cfg .randomCount = & count
48- }
49-
50- type randomOptsOption []protocol.RandomizeObjectOption
51-
52- func (opts randomOptsOption ) apply (cfg * checkConfig ) {
53- cfg .randomOpts = append (cfg .randomOpts , opts ... )
54- }
55-
56- type rapidGenOption struct {
57- gen any
58- }
59-
60- func (r rapidGenOption ) apply (cfg * checkConfig ) {
61- cfg .rapidGen = r .gen
62- cfg .useRapid = true
63- }
64-
65- // Opts configures round-trip checking behavior.
66- // The first argument specifies the number of random test cases to generate.
67- // Additional protocol.RandomizeObjectOption arguments can be passed to customize randomization.
68- func Opts (count int , opts ... protocol.RandomizeObjectOption ) CheckOption {
69- return multiOption {randomCountOption (count ), randomOptsOption (opts )}
70- }
71-
72- // NoRandomCases disables RandomizeObject testing (but still runs NearZeros).
73- // Use this when RandomizeObject generates invalid values for constrained types.
74- // Combine with NoNearZeros() to disable all automatic testing.
75- func NoRandomCases () CheckOption { return randomCountOption (0 ) }
76-
77- type skipNearZerosOption struct {}
78-
79- func (skipNearZerosOption ) apply (cfg * checkConfig ) { cfg .skipNearZeros = true }
80-
81- // NoNearZeros disables NearZeros testing, only using RandomizeObject for random variants.
82- // Use this for non-struct types (maps, slices) where NearZeros doesn't apply.
83- func NoNearZeros () CheckOption { return skipNearZerosOption {} }
84-
85- // WithRapid specifies a rapid.Generator to use for property-based testing.
86- // If provided, rapid.Check will be used instead of protocol.RandomizeObject (runs 100 tests).
87- func WithRapid [A any ](gen * rapid.Generator [A ]) CheckOption {
88- return rapidGenOption {gen : gen }
89- }
90-
91- type multiOption []CheckOption
92-
93- func (m multiOption ) apply (cfg * checkConfig ) {
94- for _ , opt := range m {
95- opt .apply (cfg )
96- }
97- }
98-
9924// Check verifies that converting from A -> B -> A yields the original value.
100- // By default, tests the provided example, all NearZeros variants (one per field),
101- // and 100 randomly generated values using protocol.RandomizeObject.
102- // Use WithRapid to provide a custom rapid.Generator for property-based testing.
103- // Use NoRandomCases to disable RandomizeObject (still runs NearZeros).
104- // Use NoNearZeros to disable NearZeros (for non-struct types like maps).
105- // Use Opts to customize the number of random tests or pass RandomizeObjectOptions.
106- func Check [A any , B any ](t * testing.T , a A , toB func (A ) B , toA func (B ) A , opts ... CheckOption ) bool {
107- cfg := checkConfig {}
108- for _ , opt := range opts {
109- opt .apply (& cfg )
110- }
111-
112- // Test the provided example first
113- if ! checkOne (t , a , toB , toA ) {
114- t .Errorf ("Round-trip failed for provided example: %+v" , a )
115- return false
116- }
117-
118- // Use rapid property testing if generator provided
119- if cfg .useRapid {
120- gen , ok := cfg .rapidGen .(* rapid.Generator [A ])
121- if ! ok {
122- t .Errorf ("Invalid rapid generator type" )
123- return false
124- }
25+ // It tests the provided example value, then tests all NearZeros variants (setting one field at a time).
26+ // NearZeros is tested first because failures clearly identify which field is problematic.
27+ func Check [A any , B any ](t * testing.T , example A , toB func (A ) B , toA func (B ) A ) {
28+ t .Helper ()
12529
126- // Run rapid property tests (runs 100 tests by default)
127- // Note: rapid.Check controls the count, not us
128- passed := true
129- rapid .Check (t , func (t1 * rapid.T ) {
130- randA := gen .Draw (t1 , "value" )
131- if ! checkOne (t , randA , toB , toA ) {
132- t .Errorf ("Round-trip failed for rapid-generated value: %+v" , randA )
133- passed = false
134- }
135- })
136- return passed
30+ // Test the provided example
31+ if ! checkOne (t , example , toB , toA ) {
32+ t .Fatalf ("Round-trip failed for provided example: %+v" , example )
13733 }
13834
13935 // Test NearZeros (one test per field) - comprehensive and deterministic
140- // Skip if explicitly disabled
141- if ! cfg .skipNearZeros {
142- nearZeroValues := NearZeros (t , a )
143- for i , nzA := range nearZeroValues {
144- if ! checkOne (t , nzA , toB , toA ) {
145- t .Errorf ("Round-trip failed for NearZero variant %d: %+v" , i , nzA )
146- return false
147- }
36+ // This comes first because failures clearly show which field is the problem
37+ nearZeroValues := NearZeros (t , example )
38+ for i , nzA := range nearZeroValues {
39+ if ! checkOne (t , nzA , toB , toA ) {
40+ t .Fatalf ("Round-trip failed for NearZero variant %d: %+v" , i , nzA )
14841 }
14942 }
150-
151- // Determine random count for RandomizeObject testing
152- randomCount := defaultRandomCount
153- if cfg .randomCount != nil {
154- randomCount = * cfg .randomCount
155- }
156-
157- // Test with RandomizeObject for additional coverage
158- var template A
159- for i := 0 ; i < randomCount ; i ++ {
160- randObj , err := protocol .RandomizeObject (& template , cfg .randomOpts ... )
161- if err != nil {
162- t .Logf ("Failed to randomize object (variant %d): %v" , i , err )
163- continue
164- }
165-
166- // Type assert the result back to *A, then dereference
167- randPtr , ok := randObj .(* A )
168- if ! ok {
169- t .Errorf ("Type assertion failed for random variant %d" , i )
170- return false
171- }
172- randA := * randPtr
173-
174- if ! checkOne (t , randA , toB , toA ) {
175- t .Errorf ("Round-trip failed for random variant %d: %+v" , i , randA )
176- return false
177- }
178- }
179-
180- return true
18143}
18244
18345func checkOne [A any , B any ](t * testing.T , a A , toB func (A ) B , toA func (B ) A ) bool {
46+ t .Helper ()
18447 b := toB (a )
18548 a2 := toA (b )
18649 if ! reflect .DeepEqual (a , a2 ) {
0 commit comments