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.
23 var coverSpec = flag.Bool("coverspec", false, "Run spec coverage tests")
25 // The global map of sentence coverage for the http2 spec.
26 var defaultSpecCoverage specCoverage
28 var loadSpecOnce sync.Once
31 if f, err := os.Open("testdata/draft-ietf-httpbis-http2.xml"); err != nil {
34 defaultSpecCoverage = readSpecCov(f)
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)
46 type specPart struct {
51 func (ss specPart) Less(oo specPart) bool {
52 atoi := func(s string) int {
53 n, err := strconv.Atoi(s)
59 a := strings.Split(ss.section, ".")
60 b := strings.Split(oo.section, ".")
65 x, y := atoi(a[0]), atoi(b[0])
78 type bySpecSection []specPart
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] }
84 type specCoverage struct {
85 coverage map[specPart]bool
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)
97 func (sc specCoverage) readSection(sec []int) {
99 buf = new(bytes.Buffer)
103 tk, err := sc.d.Token()
110 switch v := tk.(type) {
111 case xml.StartElement:
113 if err := sc.d.Skip(); err != nil {
116 if v.Name.Local == "section" {
121 switch v.Name.Local {
124 sc.readSection(append(sec, sub))
126 buf.Write(sc.readXRef(v))
134 if v.Name.Local == "section" {
135 sc.addSentences(joinSection(sec), buf.String())
142 func (sc specCoverage) readXRef(se xml.StartElement) []byte {
145 tk, err := sc.d.Token()
149 switch v := tk.(type) {
152 panic("unexpected CharData")
154 b = []byte(string(v))
156 if v.Name.Local != "xref" {
157 panic("expected </xref>")
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")))
173 panic(fmt.Sprintf("unknown attribute signature %q in %#v", sig, fmt.Sprintf("%#v", se)))
176 panic(fmt.Sprintf("unexpected tag %q", v))
181 var skipAnchor = map[string]bool{
186 var skipTitle = map[string]bool{
187 "Acknowledgements": true,
189 "Document Organization": true,
190 "Conventions and Terminology": true,
193 func skipElement(s xml.StartElement) bool {
194 switch s.Name.Local {
198 for _, attr := range s.Attr {
199 switch attr.Name.Local {
201 if skipAnchor[attr.Value] || strings.HasPrefix(attr.Value, "changes.since.") {
205 if skipTitle[attr.Value] {
214 func readSpecCov(r io.Reader) specCoverage {
216 coverage: map[specPart]bool{},
217 d: xml.NewDecoder(r)}
222 func (sc specCoverage) addSentences(sec string, sentence string) {
223 for _, s := range parseSentences(sentence) {
224 sc.coverage[specPart{sec, s}] = false
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))
234 sc.coverage[specPart{sec, s}] = true
239 var whitespaceRx = regexp.MustCompile(`\s+`)
241 func parseSentences(sens string) []string {
242 sens = strings.TrimSpace(sens)
246 ss := strings.Split(whitespaceRx.ReplaceAllString(sens, " "), ". ")
247 for i, s := range ss {
248 s = strings.TrimSpace(s)
249 if !strings.HasSuffix(s, ".") {
257 func TestSpecParseSentences(t *testing.T) {
262 {"Sentence 1. Sentence 2.",
267 {"Sentence 1. \nSentence 2.\tSentence 3.",
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)
283 func TestSpecCoverage(t *testing.T) {
288 loadSpecOnce.Do(loadSpec)
292 cv = defaultSpecCoverage.coverage
297 for sp, touched := range defaultSpecCoverage.coverage {
301 list = append(list, sp)
304 sort.Stable(bySpecSection(list))
306 if testing.Short() && len(list) > 5 {
310 for _, p := range list {
311 t.Errorf("\tSECTION %s: %s", p.section, p.sentence)
314 t.Logf("%d/%d (%d%%) sentences covered", complete, total, (complete/total)*100)
317 func attrSig(se xml.StartElement) string {
319 for _, attr := range se.Attr {
320 if attr.Name.Local == "fmt" {
321 names = append(names, "fmt-"+attr.Value)
323 names = append(names, attr.Name.Local)
327 return strings.Join(names, ",")
330 func attrValue(se xml.StartElement, attr string) string {
331 for _, a := range se.Attr {
332 if a.Name.Local == attr {
336 panic("unknown attribute " + attr)
339 func TestSpecPartLess(t *testing.T) {
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
350 for _, tt := range tests {
351 got := (specPart{tt.sec1, "foo"}).Less(specPart{tt.sec2, "foo"})
353 t.Errorf("Less(%q, %q) = %v; want %v", tt.sec1, tt.sec2, got, tt.want)