1
2
3
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
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
40
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},
348 {"6.1", "6.1", false},
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 }
357
View as plain text