golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/http2/z_spec_test.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package http2 6 7 import ( 8 "bytes" 9 "encoding/xml" 10 "flag" 11 "fmt" 12 "io" 13 "os" 14 "reflect" 15 "regexp" 16 "sort" 17 "strconv" 18 "strings" 19 "sync" 20 "testing" 21 ) 22 23 var coverSpec = flag.Bool("coverspec", false, "Run spec coverage tests") 24 25 // The global map of sentence coverage for the http2 spec. 26 var defaultSpecCoverage specCoverage 27 28 var loadSpecOnce sync.Once 29 30 func loadSpec() { 31 if f, err := os.Open("testdata/draft-ietf-httpbis-http2.xml"); err != nil { 32 panic(err) 33 } else { 34 defaultSpecCoverage = readSpecCov(f) 35 f.Close() 36 } 37 } 38 39 // covers marks all sentences for section sec in defaultSpecCoverage. Sentences not 40 // "covered" will be included in report outputted by TestSpecCoverage. 41 func covers(sec, sentences string) { 42 loadSpecOnce.Do(loadSpec) 43 defaultSpecCoverage.cover(sec, sentences) 44 } 45 46 type specPart struct { 47 section string 48 sentence string 49 } 50 51 func (ss specPart) Less(oo specPart) bool { 52 atoi := func(s string) int { 53 n, err := strconv.Atoi(s) 54 if err != nil { 55 panic(err) 56 } 57 return n 58 } 59 a := strings.Split(ss.section, ".") 60 b := strings.Split(oo.section, ".") 61 for len(a) > 0 { 62 if len(b) == 0 { 63 return false 64 } 65 x, y := atoi(a[0]), atoi(b[0]) 66 if x == y { 67 a, b = a[1:], b[1:] 68 continue 69 } 70 return x < y 71 } 72 if len(b) > 0 { 73 return true 74 } 75 return false 76 } 77 78 type bySpecSection []specPart 79 80 func (a bySpecSection) Len() int { return len(a) } 81 func (a bySpecSection) Less(i, j int) bool { return a[i].Less(a[j]) } 82 func (a bySpecSection) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 83 84 type specCoverage struct { 85 coverage map[specPart]bool 86 d *xml.Decoder 87 } 88 89 func joinSection(sec []int) string { 90 s := fmt.Sprintf("%d", sec[0]) 91 for _, n := range sec[1:] { 92 s = fmt.Sprintf("%s.%d", s, n) 93 } 94 return s 95 } 96 97 func (sc specCoverage) readSection(sec []int) { 98 var ( 99 buf = new(bytes.Buffer) 100 sub = 0 101 ) 102 for { 103 tk, err := sc.d.Token() 104 if err != nil { 105 if err == io.EOF { 106 return 107 } 108 panic(err) 109 } 110 switch v := tk.(type) { 111 case xml.StartElement: 112 if skipElement(v) { 113 if err := sc.d.Skip(); err != nil { 114 panic(err) 115 } 116 if v.Name.Local == "section" { 117 sub++ 118 } 119 break 120 } 121 switch v.Name.Local { 122 case "section": 123 sub++ 124 sc.readSection(append(sec, sub)) 125 case "xref": 126 buf.Write(sc.readXRef(v)) 127 } 128 case xml.CharData: 129 if len(sec) == 0 { 130 break 131 } 132 buf.Write(v) 133 case xml.EndElement: 134 if v.Name.Local == "section" { 135 sc.addSentences(joinSection(sec), buf.String()) 136 return 137 } 138 } 139 } 140 } 141 142 func (sc specCoverage) readXRef(se xml.StartElement) []byte { 143 var b []byte 144 for { 145 tk, err := sc.d.Token() 146 if err != nil { 147 panic(err) 148 } 149 switch v := tk.(type) { 150 case xml.CharData: 151 if b != nil { 152 panic("unexpected CharData") 153 } 154 b = []byte(string(v)) 155 case xml.EndElement: 156 if v.Name.Local != "xref" { 157 panic("expected </xref>") 158 } 159 if b != nil { 160 return b 161 } 162 sig := attrSig(se) 163 switch sig { 164 case "target": 165 return []byte(fmt.Sprintf("[%s]", attrValue(se, "target"))) 166 case "fmt-of,rel,target", "fmt-,,rel,target": 167 return []byte(fmt.Sprintf("[%s, %s]", attrValue(se, "target"), attrValue(se, "rel"))) 168 case "fmt-of,sec,target", "fmt-,,sec,target": 169 return []byte(fmt.Sprintf("[section %s of %s]", attrValue(se, "sec"), attrValue(se, "target"))) 170 case "fmt-of,rel,sec,target": 171 return []byte(fmt.Sprintf("[section %s of %s, %s]", attrValue(se, "sec"), attrValue(se, "target"), attrValue(se, "rel"))) 172 default: 173 panic(fmt.Sprintf("unknown attribute signature %q in %#v", sig, fmt.Sprintf("%#v", se))) 174 } 175 default: 176 panic(fmt.Sprintf("unexpected tag %q", v)) 177 } 178 } 179 } 180 181 var skipAnchor = map[string]bool{ 182 "intro": true, 183 "Overview": true, 184 } 185 186 var skipTitle = map[string]bool{ 187 "Acknowledgements": true, 188 "Change Log": true, 189 "Document Organization": true, 190 "Conventions and Terminology": true, 191 } 192 193 func skipElement(s xml.StartElement) bool { 194 switch s.Name.Local { 195 case "artwork": 196 return true 197 case "section": 198 for _, attr := range s.Attr { 199 switch attr.Name.Local { 200 case "anchor": 201 if skipAnchor[attr.Value] || strings.HasPrefix(attr.Value, "changes.since.") { 202 return true 203 } 204 case "title": 205 if skipTitle[attr.Value] { 206 return true 207 } 208 } 209 } 210 } 211 return false 212 } 213 214 func readSpecCov(r io.Reader) specCoverage { 215 sc := specCoverage{ 216 coverage: map[specPart]bool{}, 217 d: xml.NewDecoder(r)} 218 sc.readSection(nil) 219 return sc 220 } 221 222 func (sc specCoverage) addSentences(sec string, sentence string) { 223 for _, s := range parseSentences(sentence) { 224 sc.coverage[specPart{sec, s}] = false 225 } 226 } 227 228 func (sc specCoverage) cover(sec string, sentence string) { 229 for _, s := range parseSentences(sentence) { 230 p := specPart{sec, s} 231 if _, ok := sc.coverage[p]; !ok { 232 panic(fmt.Sprintf("Not found in spec: %q, %q", sec, s)) 233 } 234 sc.coverage[specPart{sec, s}] = true 235 } 236 237 } 238 239 var whitespaceRx = regexp.MustCompile(`\s+`) 240 241 func parseSentences(sens string) []string { 242 sens = strings.TrimSpace(sens) 243 if sens == "" { 244 return nil 245 } 246 ss := strings.Split(whitespaceRx.ReplaceAllString(sens, " "), ". ") 247 for i, s := range ss { 248 s = strings.TrimSpace(s) 249 if !strings.HasSuffix(s, ".") { 250 s += "." 251 } 252 ss[i] = s 253 } 254 return ss 255 } 256 257 func TestSpecParseSentences(t *testing.T) { 258 tests := []struct { 259 ss string 260 want []string 261 }{ 262 {"Sentence 1. Sentence 2.", 263 []string{ 264 "Sentence 1.", 265 "Sentence 2.", 266 }}, 267 {"Sentence 1. \nSentence 2.\tSentence 3.", 268 []string{ 269 "Sentence 1.", 270 "Sentence 2.", 271 "Sentence 3.", 272 }}, 273 } 274 275 for i, tt := range tests { 276 got := parseSentences(tt.ss) 277 if !reflect.DeepEqual(got, tt.want) { 278 t.Errorf("%d: got = %q, want %q", i, got, tt.want) 279 } 280 } 281 } 282 283 func TestSpecCoverage(t *testing.T) { 284 if !*coverSpec { 285 t.Skip() 286 } 287 288 loadSpecOnce.Do(loadSpec) 289 290 var ( 291 list []specPart 292 cv = defaultSpecCoverage.coverage 293 total = len(cv) 294 complete = 0 295 ) 296 297 for sp, touched := range defaultSpecCoverage.coverage { 298 if touched { 299 complete++ 300 } else { 301 list = append(list, sp) 302 } 303 } 304 sort.Stable(bySpecSection(list)) 305 306 if testing.Short() && len(list) > 5 { 307 list = list[:5] 308 } 309 310 for _, p := range list { 311 t.Errorf("\tSECTION %s: %s", p.section, p.sentence) 312 } 313 314 t.Logf("%d/%d (%d%%) sentences covered", complete, total, (complete/total)*100) 315 } 316 317 func attrSig(se xml.StartElement) string { 318 var names []string 319 for _, attr := range se.Attr { 320 if attr.Name.Local == "fmt" { 321 names = append(names, "fmt-"+attr.Value) 322 } else { 323 names = append(names, attr.Name.Local) 324 } 325 } 326 sort.Strings(names) 327 return strings.Join(names, ",") 328 } 329 330 func attrValue(se xml.StartElement, attr string) string { 331 for _, a := range se.Attr { 332 if a.Name.Local == attr { 333 return a.Value 334 } 335 } 336 panic("unknown attribute " + attr) 337 } 338 339 func TestSpecPartLess(t *testing.T) { 340 tests := []struct { 341 sec1, sec2 string 342 want bool 343 }{ 344 {"6.2.1", "6.2", false}, 345 {"6.2", "6.2.1", true}, 346 {"6.10", "6.10.1", true}, 347 {"6.10", "6.1.1", false}, // 10, not 1 348 {"6.1", "6.1", false}, // equal, so not less 349 } 350 for _, tt := range tests { 351 got := (specPart{tt.sec1, "foo"}).Less(specPart{tt.sec2, "foo"}) 352 if got != tt.want { 353 t.Errorf("Less(%q, %q) = %v; want %v", tt.sec1, tt.sec2, got, tt.want) 354 } 355 } 356 }