// Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package metrics_test import ( "bytes" "flag" "fmt" "go/ast" "go/doc" "go/doc/comment" "go/format" "go/parser" "go/token" "internal/diff" "os" "regexp" "runtime/metrics" "slices" "strings" "testing" _ "unsafe" ) // Implemented in the runtime. // //go:linkname runtime_readMetricNames func runtime_readMetricNames() []string func TestNames(t *testing.T) { // Note that this regexp is promised in the package docs for Description. Do not change. r := regexp.MustCompile("^(?P/[^:]+):(?P[^:*/]+(?:[*/][^:*/]+)*)$") all := metrics.All() for i, d := range all { if !r.MatchString(d.Name) { t.Errorf("name %q does not match regexp %#q", d.Name, r) } if i > 0 && all[i-1].Name >= all[i].Name { t.Fatalf("allDesc not sorted: %s ≥ %s", all[i-1].Name, all[i].Name) } } names := runtime_readMetricNames() slices.Sort(names) samples := make([]metrics.Sample, len(names)) for i, name := range names { samples[i].Name = name } metrics.Read(samples) for _, d := range all { for len(samples) > 0 && samples[0].Name < d.Name { t.Errorf("%s: reported by runtime but not listed in All", samples[0].Name) samples = samples[1:] } if len(samples) == 0 || d.Name < samples[0].Name { t.Errorf("%s: listed in All but not reported by runtime", d.Name) continue } if samples[0].Value.Kind() != d.Kind { t.Errorf("%s: runtime reports %v but All reports %v", d.Name, samples[0].Value.Kind(), d.Kind) } samples = samples[1:] } } func wrap(prefix, text string, width int) string { doc := &comment.Doc{Content: []comment.Block{&comment.Paragraph{Text: []comment.Text{comment.Plain(text)}}}} pr := &comment.Printer{TextPrefix: prefix, TextWidth: width} return string(pr.Text(doc)) } func formatDesc(t *testing.T) string { var b strings.Builder for i, d := range metrics.All() { if i > 0 { fmt.Fprintf(&b, "\n") } fmt.Fprintf(&b, "%s\n", d.Name) fmt.Fprintf(&b, "%s", wrap("\t", d.Description, 80-2*8)) } return b.String() } var generate = flag.Bool("generate", false, "update doc.go for go generate") func TestDocs(t *testing.T) { want := formatDesc(t) src, err := os.ReadFile("doc.go") if err != nil { t.Fatal(err) } fset := token.NewFileSet() f, err := parser.ParseFile(fset, "doc.go", src, parser.ParseComments) if err != nil { t.Fatal(err) } fdoc := f.Doc if fdoc == nil { t.Fatal("no doc comment in doc.go") } pkg, err := doc.NewFromFiles(fset, []*ast.File{f}, "runtime/metrics") if err != nil { t.Fatal(err) } if pkg.Doc == "" { t.Fatal("doc.NewFromFiles lost doc comment") } doc := new(comment.Parser).Parse(pkg.Doc) expectCode := false foundCode := false updated := false for _, block := range doc.Content { switch b := block.(type) { case *comment.Heading: expectCode = false if b.Text[0] == comment.Plain("Supported metrics") { expectCode = true } case *comment.Code: if expectCode { foundCode = true if b.Text != want { if !*generate { t.Fatalf("doc comment out of date; use go generate to rebuild\n%s", diff.Diff("old", []byte(b.Text), "want", []byte(want))) } b.Text = want updated = true } } } } if !foundCode { t.Fatalf("did not find Supported metrics list in doc.go") } if updated { fmt.Fprintf(os.Stderr, "go test -generate: writing new doc.go\n") var buf bytes.Buffer buf.Write(src[:fdoc.Pos()-f.FileStart]) buf.WriteString("/*\n") buf.Write(new(comment.Printer).Comment(doc)) buf.WriteString("*/") buf.Write(src[fdoc.End()-f.FileStart:]) src, err := format.Source(buf.Bytes()) if err != nil { t.Fatal(err) } if err := os.WriteFile("doc.go", src, 0666); err != nil { t.Fatal(err) } } else if *generate { fmt.Fprintf(os.Stderr, "go test -generate: doc.go already up-to-date\n") } }