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  }