Source file src/runtime/metrics/description_test.go

     1  // Copyright 2020 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 metrics_test
     6  
     7  import (
     8  	"bytes"
     9  	"flag"
    10  	"fmt"
    11  	"go/ast"
    12  	"go/doc"
    13  	"go/doc/comment"
    14  	"go/format"
    15  	"go/parser"
    16  	"go/token"
    17  	"internal/diff"
    18  	"os"
    19  	"regexp"
    20  	"runtime/metrics"
    21  	"sort"
    22  	"strings"
    23  	"testing"
    24  	_ "unsafe"
    25  )
    26  
    27  // Implemented in the runtime.
    28  //
    29  //go:linkname runtime_readMetricNames
    30  func runtime_readMetricNames() []string
    31  
    32  func TestNames(t *testing.T) {
    33  	// Note that this regexp is promised in the package docs for Description. Do not change.
    34  	r := regexp.MustCompile("^(?P<name>/[^:]+):(?P<unit>[^:*/]+(?:[*/][^:*/]+)*)$")
    35  	all := metrics.All()
    36  	for i, d := range all {
    37  		if !r.MatchString(d.Name) {
    38  			t.Errorf("name %q does not match regexp %#q", d.Name, r)
    39  		}
    40  		if i > 0 && all[i-1].Name >= all[i].Name {
    41  			t.Fatalf("allDesc not sorted: %s ≥ %s", all[i-1].Name, all[i].Name)
    42  		}
    43  	}
    44  
    45  	names := runtime_readMetricNames()
    46  	sort.Strings(names)
    47  	samples := make([]metrics.Sample, len(names))
    48  	for i, name := range names {
    49  		samples[i].Name = name
    50  	}
    51  	metrics.Read(samples)
    52  
    53  	for _, d := range all {
    54  		for len(samples) > 0 && samples[0].Name < d.Name {
    55  			t.Errorf("%s: reported by runtime but not listed in All", samples[0].Name)
    56  			samples = samples[1:]
    57  		}
    58  		if len(samples) == 0 || d.Name < samples[0].Name {
    59  			t.Errorf("%s: listed in All but not reported by runtime", d.Name)
    60  			continue
    61  		}
    62  		if samples[0].Value.Kind() != d.Kind {
    63  			t.Errorf("%s: runtime reports %v but All reports %v", d.Name, samples[0].Value.Kind(), d.Kind)
    64  		}
    65  		samples = samples[1:]
    66  	}
    67  }
    68  
    69  func wrap(prefix, text string, width int) string {
    70  	doc := &comment.Doc{Content: []comment.Block{&comment.Paragraph{Text: []comment.Text{comment.Plain(text)}}}}
    71  	pr := &comment.Printer{TextPrefix: prefix, TextWidth: width}
    72  	return string(pr.Text(doc))
    73  }
    74  
    75  func formatDesc(t *testing.T) string {
    76  	var b strings.Builder
    77  	for i, d := range metrics.All() {
    78  		if i > 0 {
    79  			fmt.Fprintf(&b, "\n")
    80  		}
    81  		fmt.Fprintf(&b, "%s\n", d.Name)
    82  		fmt.Fprintf(&b, "%s", wrap("\t", d.Description, 80-2*8))
    83  	}
    84  	return b.String()
    85  }
    86  
    87  var generate = flag.Bool("generate", false, "update doc.go for go generate")
    88  
    89  func TestDocs(t *testing.T) {
    90  	want := formatDesc(t)
    91  
    92  	src, err := os.ReadFile("doc.go")
    93  	if err != nil {
    94  		t.Fatal(err)
    95  	}
    96  	fset := token.NewFileSet()
    97  	f, err := parser.ParseFile(fset, "doc.go", src, parser.ParseComments)
    98  	if err != nil {
    99  		t.Fatal(err)
   100  	}
   101  	fdoc := f.Doc
   102  	if fdoc == nil {
   103  		t.Fatal("no doc comment in doc.go")
   104  	}
   105  	pkg, err := doc.NewFromFiles(fset, []*ast.File{f}, "runtime/metrics")
   106  	if err != nil {
   107  		t.Fatal(err)
   108  	}
   109  	if pkg.Doc == "" {
   110  		t.Fatal("doc.NewFromFiles lost doc comment")
   111  	}
   112  	doc := new(comment.Parser).Parse(pkg.Doc)
   113  	expectCode := false
   114  	foundCode := false
   115  	updated := false
   116  	for _, block := range doc.Content {
   117  		switch b := block.(type) {
   118  		case *comment.Heading:
   119  			expectCode = false
   120  			if b.Text[0] == comment.Plain("Supported metrics") {
   121  				expectCode = true
   122  			}
   123  		case *comment.Code:
   124  			if expectCode {
   125  				foundCode = true
   126  				if b.Text != want {
   127  					if !*generate {
   128  						t.Fatalf("doc comment out of date; use go generate to rebuild\n%s", diff.Diff("old", []byte(b.Text), "want", []byte(want)))
   129  					}
   130  					b.Text = want
   131  					updated = true
   132  				}
   133  			}
   134  		}
   135  	}
   136  
   137  	if !foundCode {
   138  		t.Fatalf("did not find Supported metrics list in doc.go")
   139  	}
   140  	if updated {
   141  		fmt.Fprintf(os.Stderr, "go test -generate: writing new doc.go\n")
   142  		var buf bytes.Buffer
   143  		buf.Write(src[:fdoc.Pos()-f.FileStart])
   144  		buf.WriteString("/*\n")
   145  		buf.Write(new(comment.Printer).Comment(doc))
   146  		buf.WriteString("*/")
   147  		buf.Write(src[fdoc.End()-f.FileStart:])
   148  		src, err := format.Source(buf.Bytes())
   149  		if err != nil {
   150  			t.Fatal(err)
   151  		}
   152  		if err := os.WriteFile("doc.go", src, 0666); err != nil {
   153  			t.Fatal(err)
   154  		}
   155  	} else if *generate {
   156  		fmt.Fprintf(os.Stderr, "go test -generate: doc.go already up-to-date\n")
   157  	}
   158  }
   159  

View as plain text