okami.mom/ultraviolet
git clone https://git.nkp.pet/okami.mom/ultraviolet
5e21978
upstream
1package conformance 2 3import ( 4"os" 5"path/filepath" 6"strconv" 7"strings" 8"testing" 9) 10 11// corpusProgram decodes a saved fuzz entry the way the fuzz target would. 12func corpusProgram (t * testing.T ,path string ) (Program ,bool ) { 13t .Helper () 14raw ,err := os .ReadFile (path ) 15if err != nil { 16t .Fatalf ("read %s: %v" ,path ,err ) 17 } 18for _ ,line := range strings .Split (string (raw ),"\n" ) { 19line = strings .TrimSpace (line ) 20if ! strings .HasPrefix (line ,"[]byte(" ) { 21continue 22 } 23quoted := strings .TrimSuffix (strings .TrimPrefix (line ,"[]byte(" ),")" ) 24data ,err := strconv .Unquote (quoted ) 25if err != nil { 26continue 27 } 28return DecodeProgram ([]byte (data )),true 29 } 30return Program {},false 31} 32 33// TestCorpusDecodesStably pins what every saved entry decodes to. 34// 35// A saved entry is only a regression test for as long as it keeps performing 36// the operations it was minimised for. Choosing an operation by a count that 37// grows re-pointed every byte the moment a kind was added, so entries kept 38// passing while testing something else entirely. This is the alarm for that: 39// if it fires, the decoder's mapping moved, and the entries have to be 40// re-minted deliberately rather than left to drift. 41func TestCorpusDecodesStably (t * testing.T ) { 42golden := map [string ]string {} 43raw ,err := os .ReadFile ("testdata/corpus_decode.txt" ) 44if err != nil { 45t .Fatalf ("read golden: %v" ,err ) 46 } 47for _ ,line := range strings .Split (strings .TrimSpace (string (raw )),"\n" ) { 48if line == "" || strings .HasPrefix (line ,"#" ) { 49continue 50 } 51name ,ops ,found := strings .Cut (line ,"\t" ) 52if ! found { 53t .Fatalf ("malformed golden line %q" ,line ) 54 } 55golden [name ]= ops 56 } 57 58seeds ,err := filepath .Glob ("testdata/fuzz/*/*" ) 59if err != nil { 60t .Fatal (err ) 61 } 62seen := map [string ]bool {} 63for _ ,path := range seeds { 64p ,ok := corpusProgram (t ,path ) 65if ! ok { 66continue 67 } 68name := filepath .Join (filepath .Base (filepath .Dir (path )),filepath .Base (path )) 69var kinds []string 70for _ ,op := range p .Ops { 71kinds = append (kinds ,op .Kind .String ()) 72 } 73got := strings .Join (kinds ,"," ) 74seen [name ]= true 75want ,recorded := golden [name ] 76if ! recorded { 77t .Errorf ("%s is not in testdata/corpus_decode.txt; add it with:\n\t%s\t%s" ,name ,name ,got ) 78continue 79 } 80if got != want { 81t .Errorf ("%s decodes to a different program than it was minimised for\n got: %s\nwant: %s" ,name ,got ,want ) 82 } 83 } 84for name := range golden { 85if ! seen [name ] { 86t .Errorf ("%s is recorded in testdata/corpus_decode.txt but no longer exists" ,name ) 87 } 88 } 89} 90 91// TestOpTableCoversEveryKind keeps the table honest: a kind the table never 92// names is a kind the fuzzer can never generate, which is a silent hole in 93// coverage rather than a failure anyone would notice. 94func TestOpTableCoversEveryKind (t * testing.T ) { 95seen := map [OpKind ]int {} 96for _ ,kind := range opTable { 97seen [kind ]++ 98 } 99for kind := OpKind (0 );kind < opKindCount ;kind ++ { 100if seen [kind ]== 0 { 101t .Errorf ("%s is never reachable: no slot in opTable names it" ,kind ) 102 } 103 } 104if len (seen )!= int (opKindCount ) { 105t .Errorf ("opTable names %d kinds, but %d exist" ,len (seen ),opKindCount ) 106 } 107} 108 109// TestSeedsDecodeToTheProgramsTheyDescribe checks that the hand-written seeds 110// still mean what their comments say. 111// 112// A seed is a literal byte slice, so nothing connects it to the program it is 113// meant to express. Two silent divergences have already happened: the op bytes 114// were the enum's numeric values, which stopped selecting those ops when the 115// decoder moved to a slot table, and the resize heights were encoded against the 116// fullscreen low bound for the inline modes too, so every inline resize in them 117// asked for a different size than intended. Both left the seeds passing while 118// covering less than they claimed. 119func TestSeedsDecodeToTheProgramsTheyDescribe (t * testing.T ) { 120for i ,seed := range Seeds () { 121p := DecodeProgram (seed ) 122if len (p .Ops )== 0 { 123t .Errorf ("seed %d decodes to no operations at all" ,i ) 124continue 125 } 126 127// Every seed exists to reach a render; one that cannot is inert. 128renders := 0 129for _ ,op := range p .Ops { 130if op .Kind == OpRender || op .Kind == OpRedraw { 131renders ++ 132 } 133 } 134if renders == 0 { 135t .Errorf ("seed %d never renders, so nothing it sets up is ever tested: %v" ,i ,kindsOf (p )) 136 } 137 138// A seed that resizes has to land inside the bounds the decoder 139// promises, or it is exercising a clamp rather than the case it names. 140for _ ,op := range p .Ops { 141if op .Kind != OpResize { 142continue 143 } 144minH := MinResizeH 145if p .Inline { 146minH = 0 147 } 148if op .W < MinResizeW || op .W > MaxResizeW { 149t .Errorf ("seed %d resizes to width %d, outside [%d,%d]" ,i ,op .W ,MinResizeW ,MaxResizeW ) 150 } 151if op .H < minH || op .H > MaxResizeH { 152t .Errorf ("seed %d (inline=%v) resizes to height %d, outside [%d,%d]" , 153i ,p .Inline ,op .H ,minH ,MaxResizeH ) 154 } 155 } 156 } 157} 158 159func kindsOf (p Program ) []string { 160var out []string 161for _ ,op := range p .Ops { 162out = append (out ,op .Kind .String ()) 163 } 164return out 165} 166 167// TestCollapseThenDrawIsReachable checks that an op a collapsed frame cannot 168// express does not end the program. 169// 170// Drawing needs a row, so a frame collapsed to nothing cannot take a DrawLine. 171// That used to end decoding, which made everything after a collapse unreachable: 172// the fuzzer could shrink an inline frame to zero but never reach the redraw 173// that proves it recovered, and the collapse is the case inline erasing is most 174// likely to get wrong. 175func TestCollapseThenDrawIsReachable (t * testing.T ) { 176width := byte ((20 - MinResizeW )% (MaxResizeW - MinResizeW + 1 )) 177program := []byte { 17814 ,2 ,// 20x4 1794 ,// inline 180opByte (OpResize ),width ,0 ,// collapse to no rows 181opByte (OpDrawLine ),0 ,0 ,byte (len (corpusAlphabet )),// cannot be expressed 182opByte (OpResize ),width ,3 ,// grow back 183opByte (OpRender ),// has to still be reachable 184 } 185 186p := DecodeProgram (program ) 187 188var collapsed ,renderedAfter bool 189for _ ,op := range p .Ops { 190if op .Kind == OpResize && op .H == 0 { 191collapsed = true 192 } 193if collapsed && op .Kind == OpRender { 194renderedAfter = true 195 } 196 } 197if ! collapsed { 198t .Fatalf ("expected the program to collapse the frame, got %v" ,kindsOf (p )) 199 } 200if ! renderedAfter { 201t .Errorf ("decoding stopped at the collapsed frame, so its recovery cannot be fuzzed: %v" ,kindsOf (p )) 202 } 203}