go/src/cmd/doc/doc_test.go

602 lines
17 KiB
Go
Raw Normal View History

// Copyright 2015 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 main
import (
"bytes"
"flag"
"regexp"
"runtime"
"strings"
"testing"
)
func maybeSkip(t *testing.T) {
if strings.HasPrefix(runtime.GOOS, "nacl") {
t.Skip("nacl does not have a full file tree")
}
if runtime.GOOS == "darwin" && strings.HasPrefix(runtime.GOARCH, "arm") {
t.Skip("darwin/arm does not have a full file tree")
}
}
type test struct {
name string
args []string // Arguments to "[go] doc".
yes []string // Regular expressions that should match.
no []string // Regular expressions that should not match.
}
const p = "cmd/doc/testdata"
var tests = []test{
// Sanity check.
{
"sanity check",
[]string{p},
[]string{`type ExportedType struct`},
nil,
},
// Package dump includes import, package statement.
{
"package clause",
[]string{p},
[]string{`package pkg.*cmd/doc/testdata`},
nil,
},
// Constants.
// Package dump
{
"full package",
[]string{p},
[]string{
`Package comment`,
cmd/doc: ensure summaries truly are only one line The documentation for doc says: > Doc prints the documentation comments associated with the item identified by its > arguments (a package, const, func, type, var, or method) followed by a one-line > summary of each of the first-level items "under" that item (package-level > declarations for a package, methods for a type, etc.). Certain variables (and constants, functions, and types) have value specifications that are multiple lines long. Prior to this change, doc would print out all of the lines necessary to display the value. This is inconsistent with the documented behavior, which guarantees a one-line summary for all first-level items. We fix this here by writing a general oneLineNode method that always returns a one-line summary (guaranteed!) of any input node. Packages like image/color/palette and unicode now become much more readable since large slices are now a single line. $ go doc image/color/palette <<< // Before: var Plan9 = []color.Color{ color.RGBA{0x00, 0x00, 0x00, 0xff}, color.RGBA{0x00, 0x00, 0x44, 0xff}, color.RGBA{0x00, 0x00, 0x88, 0xff}, ... // Hundreds of more lines! } var WebSafe = []color.Color{ color.RGBA{0x00, 0x00, 0x00, 0xff}, color.RGBA{0x00, 0x00, 0x33, 0xff}, color.RGBA{0x00, 0x00, 0x66, 0xff}, ... // Hundreds of more lines! } // After: var Plan9 = []color.Color{ ... } var WebSafe = []color.Color{ ... } >>> In order to test this, I ran `go doc` and `go doc -u` on all of the standard library packages and diff'd the output with and without the change to ensure that all differences were intended. Fixes #13072 Change-Id: Ida10b7796b7e4e174a929b55c60813a9eb7158fe Reviewed-on: https://go-review.googlesource.com/25420 Run-TryBot: Joe Tsai <thebrokentoaster@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rob Pike <r@golang.org>
2016-08-03 00:31:17 -07:00
`const ExportedConstant = 1`, // Simple constant.
`const ConstOne = 1`, // First entry in constant block.
`const ConstFive ...`, // From block starting with unexported constant.
`var ExportedVariable = 1`, // Simple variable.
`var VarOne = 1`, // First entry in variable block.
`func ExportedFunc\(a int\) bool`, // Function.
`func ReturnUnexported\(\) unexportedType`, // Function with unexported return type.
`type ExportedType struct{ ... }`, // Exported type.
`const ExportedTypedConstant ExportedType = iota`, // Typed constant.
`const ExportedTypedConstant_unexported unexportedType`, // Typed constant, exported for unexported type.
`const ConstLeft2 uint64 ...`, // Typed constant using unexported iota.
`const ConstGroup1 unexportedType = iota ...`, // Typed constant using unexported type.
`const ConstGroup4 ExportedType = ExportedType{}`, // Typed constant using exported type.
`const MultiLineConst = ...`, // Multi line constant.
`var MultiLineVar = map\[struct{ ... }\]struct{ ... }{ ... }`, // Multi line variable.
`func MultiLineFunc\(x interface{ ... }\) \(r struct{ ... }\)`, // Multi line function.
`var LongLine = newLongLine\(("someArgument[1-4]", ){4}...\)`, // Long list of arguments.
`type T1 = T2`, // Type alias
},
[]string{
`const internalConstant = 2`, // No internal constants.
`var internalVariable = 2`, // No internal variables.
`func internalFunc(a int) bool`, // No internal functions.
`Comment about exported constant`, // No comment for single constant.
`Comment about exported variable`, // No comment for single variable.
`Comment about block of constants.`, // No comment for constant block.
`Comment about block of variables.`, // No comment for variable block.
`Comment before ConstOne`, // No comment for first entry in constant block.
`Comment before VarOne`, // No comment for first entry in variable block.
`ConstTwo = 2`, // No second entry in constant block.
`VarTwo = 2`, // No second entry in variable block.
`VarFive = 5`, // From block starting with unexported variable.
`type unexportedType`, // No unexported type.
`unexportedTypedConstant`, // No unexported typed constant.
`\bField`, // No fields.
`Method`, // No methods.
`someArgument[5-8]`, // No truncated arguments.
`type T1 T2`, // Type alias does not display as type declaration.
},
},
// Package dump -u
{
"full package with u",
[]string{`-u`, p},
[]string{
`const ExportedConstant = 1`, // Simple constant.
`const internalConstant = 2`, // Internal constants.
`func internalFunc\(a int\) bool`, // Internal functions.
`func ReturnUnexported\(\) unexportedType`, // Function with unexported return type.
},
[]string{
`Comment about exported constant`, // No comment for simple constant.
`Comment about block of constants`, // No comment for constant block.
`Comment about internal function`, // No comment for internal function.
cmd/doc: ensure summaries truly are only one line The documentation for doc says: > Doc prints the documentation comments associated with the item identified by its > arguments (a package, const, func, type, var, or method) followed by a one-line > summary of each of the first-level items "under" that item (package-level > declarations for a package, methods for a type, etc.). Certain variables (and constants, functions, and types) have value specifications that are multiple lines long. Prior to this change, doc would print out all of the lines necessary to display the value. This is inconsistent with the documented behavior, which guarantees a one-line summary for all first-level items. We fix this here by writing a general oneLineNode method that always returns a one-line summary (guaranteed!) of any input node. Packages like image/color/palette and unicode now become much more readable since large slices are now a single line. $ go doc image/color/palette <<< // Before: var Plan9 = []color.Color{ color.RGBA{0x00, 0x00, 0x00, 0xff}, color.RGBA{0x00, 0x00, 0x44, 0xff}, color.RGBA{0x00, 0x00, 0x88, 0xff}, ... // Hundreds of more lines! } var WebSafe = []color.Color{ color.RGBA{0x00, 0x00, 0x00, 0xff}, color.RGBA{0x00, 0x00, 0x33, 0xff}, color.RGBA{0x00, 0x00, 0x66, 0xff}, ... // Hundreds of more lines! } // After: var Plan9 = []color.Color{ ... } var WebSafe = []color.Color{ ... } >>> In order to test this, I ran `go doc` and `go doc -u` on all of the standard library packages and diff'd the output with and without the change to ensure that all differences were intended. Fixes #13072 Change-Id: Ida10b7796b7e4e174a929b55c60813a9eb7158fe Reviewed-on: https://go-review.googlesource.com/25420 Run-TryBot: Joe Tsai <thebrokentoaster@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rob Pike <r@golang.org>
2016-08-03 00:31:17 -07:00
`MultiLine(String|Method|Field)`, // No data from multi line portions.
},
},
// Single constant.
{
"single constant",
[]string{p, `ExportedConstant`},
[]string{
`Comment about exported constant`, // Include comment.
`const ExportedConstant = 1`,
},
nil,
},
// Single constant -u.
{
"single constant with -u",
[]string{`-u`, p, `internalConstant`},
[]string{
`Comment about internal constant`, // Include comment.
`const internalConstant = 2`,
},
nil,
},
// Block of constants.
{
"block of constants",
[]string{p, `ConstTwo`},
[]string{
`Comment before ConstOne.\n.*ConstOne = 1`, // First...
`ConstTwo = 2.*Comment on line with ConstTwo`, // And second show up.
`Comment about block of constants`, // Comment does too.
},
[]string{
`constThree`, // No unexported constant.
},
},
// Block of constants -u.
{
"block of constants with -u",
[]string{"-u", p, `constThree`},
[]string{
`constThree = 3.*Comment on line with constThree`,
},
nil,
},
// Block of constants with carryover type from unexported field.
{
"block of constants with carryover type",
[]string{p, `ConstLeft2`},
[]string{
`ConstLeft2, constRight2 uint64`,
`constLeft3, ConstRight3`,
`ConstLeft4, ConstRight4`,
},
nil,
},
// Block of constants -u with carryover type from unexported field.
{
"block of constants with carryover type",
[]string{"-u", p, `ConstLeft2`},
[]string{
`_, _ uint64 = 2 \* iota, 1 << iota`,
`constLeft1, constRight1`,
`ConstLeft2, constRight2`,
`constLeft3, ConstRight3`,
`ConstLeft4, ConstRight4`,
},
nil,
},
// Single variable.
{
"single variable",
[]string{p, `ExportedVariable`},
[]string{
`ExportedVariable`, // Include comment.
`var ExportedVariable = 1`,
},
nil,
},
// Single variable -u.
{
"single variable with -u",
[]string{`-u`, p, `internalVariable`},
[]string{
`Comment about internal variable`, // Include comment.
`var internalVariable = 2`,
},
nil,
},
// Block of variables.
{
"block of variables",
[]string{p, `VarTwo`},
[]string{
`Comment before VarOne.\n.*VarOne = 1`, // First...
`VarTwo = 2.*Comment on line with VarTwo`, // And second show up.
`Comment about block of variables`, // Comment does too.
},
[]string{
`varThree= 3`, // No unexported variable.
},
},
// Block of variables -u.
{
"block of variables with -u",
[]string{"-u", p, `varThree`},
[]string{
`varThree = 3.*Comment on line with varThree`,
},
nil,
},
// Function.
{
"function",
[]string{p, `ExportedFunc`},
[]string{
`Comment about exported function`, // Include comment.
`func ExportedFunc\(a int\) bool`,
},
nil,
},
// Function -u.
{
"function with -u",
[]string{"-u", p, `internalFunc`},
[]string{
`Comment about internal function`, // Include comment.
`func internalFunc\(a int\) bool`,
},
nil,
},
// Type.
{
"type",
[]string{p, `ExportedType`},
[]string{
`Comment about exported type`, // Include comment.
`type ExportedType struct`, // Type definition.
`Comment before exported field.*\n.*ExportedField +int` +
`.*Comment on line with exported field.`,
`ExportedEmbeddedType.*Comment on line with exported embedded field.`,
`Has unexported fields`,
`func \(ExportedType\) ExportedMethod\(a int\) bool`,
`const ExportedTypedConstant ExportedType = iota`, // Must include associated constant.
`func ExportedTypeConstructor\(\) \*ExportedType`, // Must include constructor.
`io.Reader.*Comment on line with embedded Reader.`,
},
[]string{
`unexportedField`, // No unexported field.
`int.*embedded`, // No unexported embedded field.
`Comment about exported method.`, // No comment about exported method.
`unexportedMethod`, // No unexported method.
`unexportedTypedConstant`, // No unexported constant.
`error`, // No embedded error.
},
},
// Type T1 dump (alias).
{
"type T1",
[]string{p + ".T1"},
[]string{
`type T1 = T2`,
},
[]string{
`type T1 T2`,
`type ExportedType`,
},
},
// Type -u with unexported fields.
{
"type with unexported fields and -u",
[]string{"-u", p, `ExportedType`},
[]string{
`Comment about exported type`, // Include comment.
`type ExportedType struct`, // Type definition.
`Comment before exported field.*\n.*ExportedField +int`,
`unexportedField.*int.*Comment on line with unexported field.`,
`ExportedEmbeddedType.*Comment on line with exported embedded field.`,
`\*ExportedEmbeddedType.*Comment on line with exported embedded \*field.`,
`unexportedType.*Comment on line with unexported embedded field.`,
`\*unexportedType.*Comment on line with unexported embedded \*field.`,
`io.Reader.*Comment on line with embedded Reader.`,
`error.*Comment on line with embedded error.`,
`func \(ExportedType\) unexportedMethod\(a int\) bool`,
`unexportedTypedConstant`,
},
[]string{
`Has unexported fields`,
},
},
// Unexported type with -u.
{
"unexported type with -u",
[]string{"-u", p, `unexportedType`},
[]string{
`Comment about unexported type`, // Include comment.
`type unexportedType int`, // Type definition.
`func \(unexportedType\) ExportedMethod\(\) bool`,
`func \(unexportedType\) unexportedMethod\(\) bool`,
`ExportedTypedConstant_unexported unexportedType = iota`,
`const unexportedTypedConstant unexportedType = 1`,
},
nil,
},
// Interface.
{
"interface type",
[]string{p, `ExportedInterface`},
[]string{
`Comment about exported interface`, // Include comment.
`type ExportedInterface interface`, // Interface definition.
`Comment before exported method.*\n.*ExportedMethod\(\)` +
`.*Comment on line with exported method`,
`io.Reader.*Comment on line with embedded Reader.`,
`error.*Comment on line with embedded error.`,
`Has unexported methods`,
},
[]string{
`unexportedField`, // No unexported field.
`Comment about exported method`, // No comment about exported method.
`unexportedMethod`, // No unexported method.
`unexportedTypedConstant`, // No unexported constant.
},
},
// Interface -u with unexported methods.
{
"interface type with unexported methods and -u",
[]string{"-u", p, `ExportedInterface`},
[]string{
`Comment about exported interface`, // Include comment.
`type ExportedInterface interface`, // Interface definition.
`Comment before exported method.*\n.*ExportedMethod\(\)` +
`.*Comment on line with exported method`,
`unexportedMethod\(\).*Comment on line with unexported method.`,
`io.Reader.*Comment on line with embedded Reader.`,
`error.*Comment on line with embedded error.`,
},
[]string{
`Has unexported methods`,
},
},
// Interface method.
{
"interface method",
[]string{p, `ExportedInterface.ExportedMethod`},
[]string{
`Comment before exported method.*\n.*ExportedMethod\(\)` +
`.*Comment on line with exported method`,
},
[]string{
`Comment about exported interface.`,
},
},
// Method.
{
"method",
[]string{p, `ExportedType.ExportedMethod`},
[]string{
`func \(ExportedType\) ExportedMethod\(a int\) bool`,
`Comment about exported method.`,
},
nil,
},
// Method with -u.
{
"method with -u",
[]string{"-u", p, `ExportedType.unexportedMethod`},
[]string{
`func \(ExportedType\) unexportedMethod\(a int\) bool`,
`Comment about unexported method.`,
},
nil,
},
// Field.
{
"field",
[]string{p, `ExportedType.ExportedField`},
[]string{
`type ExportedType struct`,
`ExportedField int`,
`Comment before exported field.`,
`Comment on line with exported field.`,
`other fields elided`,
},
nil,
},
// Field with -u.
{
"method with -u",
[]string{"-u", p, `ExportedType.unexportedField`},
[]string{
`unexportedField int`,
`Comment on line with unexported field.`,
},
nil,
},
// Field of struct with only one field.
{
"single-field struct",
[]string{p, `ExportedStructOneField.OnlyField`},
[]string{`the only field`},
[]string{`other fields elided`},
},
// Case matching off.
{
"case matching off",
[]string{p, `casematch`},
[]string{
`CaseMatch`,
`Casematch`,
},
nil,
},
// Case matching on.
{
"case matching on",
[]string{"-c", p, `Casematch`},
[]string{
`Casematch`,
},
[]string{
`CaseMatch`,
},
},
}
func TestDoc(t *testing.T) {
maybeSkip(t)
for _, test := range tests {
var b bytes.Buffer
var flagSet flag.FlagSet
err := do(&b, &flagSet, test.args)
if err != nil {
t.Fatalf("%s: %s\n", test.name, err)
}
output := b.Bytes()
failed := false
for j, yes := range test.yes {
re, err := regexp.Compile(yes)
if err != nil {
t.Fatalf("%s.%d: compiling %#q: %s", test.name, j, yes, err)
}
if !re.Match(output) {
t.Errorf("%s.%d: no match for %s %#q", test.name, j, test.args, yes)
failed = true
}
}
for j, no := range test.no {
re, err := regexp.Compile(no)
if err != nil {
t.Fatalf("%s.%d: compiling %#q: %s", test.name, j, no, err)
}
if re.Match(output) {
t.Errorf("%s.%d: incorrect match for %s %#q", test.name, j, test.args, no)
failed = true
}
}
if failed {
t.Logf("\n%s", output)
}
}
}
// Test the code to try multiple packages. Our test case is
// go doc rand.Float64
// This needs to find math/rand.Float64; however crypto/rand, which doesn't
// have the symbol, usually appears first in the directory listing.
func TestMultiplePackages(t *testing.T) {
if testing.Short() {
t.Skip("scanning file system takes too long")
}
maybeSkip(t)
var b bytes.Buffer // We don't care about the output.
// Make sure crypto/rand does not have the symbol.
{
var flagSet flag.FlagSet
err := do(&b, &flagSet, []string{"crypto/rand.float64"})
if err == nil {
t.Errorf("expected error from crypto/rand.float64")
} else if !strings.Contains(err.Error(), "no symbol float64") {
t.Errorf("unexpected error %q from crypto/rand.float64", err)
}
}
// Make sure math/rand does have the symbol.
{
var flagSet flag.FlagSet
err := do(&b, &flagSet, []string{"math/rand.float64"})
if err != nil {
t.Errorf("unexpected error %q from math/rand.float64", err)
}
}
// Try the shorthand.
{
var flagSet flag.FlagSet
err := do(&b, &flagSet, []string{"rand.float64"})
if err != nil {
t.Errorf("unexpected error %q from rand.float64", err)
}
}
// Now try a missing symbol. We should see both packages in the error.
{
var flagSet flag.FlagSet
err := do(&b, &flagSet, []string{"rand.doesnotexit"})
if err == nil {
t.Errorf("expected error from rand.doesnotexit")
} else {
errStr := err.Error()
if !strings.Contains(errStr, "no symbol") {
t.Errorf("error %q should contain 'no symbol", errStr)
}
if !strings.Contains(errStr, "crypto/rand") {
t.Errorf("error %q should contain crypto/rand", errStr)
}
if !strings.Contains(errStr, "math/rand") {
t.Errorf("error %q should contain math/rand", errStr)
}
}
}
}
// Test the code to look up packages when given two args. First test case is
// go doc binary BigEndian
// This needs to find encoding/binary.BigEndian, which means
// finding the package encoding/binary given only "binary".
// Second case is
// go doc rand Float64
// which again needs to find math/rand and not give up after crypto/rand,
// which has no such function.
func TestTwoArgLookup(t *testing.T) {
if testing.Short() {
t.Skip("scanning file system takes too long")
}
maybeSkip(t)
var b bytes.Buffer // We don't care about the output.
{
var flagSet flag.FlagSet
err := do(&b, &flagSet, []string{"binary", "BigEndian"})
if err != nil {
t.Errorf("unexpected error %q from binary BigEndian", err)
}
}
{
var flagSet flag.FlagSet
err := do(&b, &flagSet, []string{"rand", "Float64"})
if err != nil {
t.Errorf("unexpected error %q from rand Float64", err)
}
}
}
type trimTest struct {
path string
prefix string
result string
ok bool
}
var trimTests = []trimTest{
{"", "", "", true},
{"/usr/gopher", "/usr/gopher", "/usr/gopher", true},
{"/usr/gopher/bar", "/usr/gopher", "bar", true},
{"/usr/gopherflakes", "/usr/gopher", "/usr/gopherflakes", false},
{"/usr/gopher/bar", "/usr/zot", "/usr/gopher/bar", false},
}
func TestTrim(t *testing.T) {
for _, test := range trimTests {
result, ok := trim(test.path, test.prefix)
if ok != test.ok {
t.Errorf("%s %s expected %t got %t", test.path, test.prefix, test.ok, ok)
continue
}
if result != test.result {
t.Errorf("%s %s expected %q got %q", test.path, test.prefix, test.result, result)
continue
}
}
}