--- /dev/null
+// 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"
+ "os"
+ "os/exec"
+ "regexp"
+ "testing"
+)
+
+const (
+ dataDir = "testdata"
+ binary = "testdoc"
+)
+
+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.
+ {
+ "fmt",
+ []string{`fmt`},
+ []string{`type Formatter interface`},
+ 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`,
+ `const ExportedConstant = 1`, // Simple constant.
+ `ConstOne = 1`, // First entry in constant block.
+ `const ExportedVariable = 1`, // Simple variable.
+ `VarOne = 1`, // First entry in variable block.
+ `func ExportedFunc\(a int\) bool`, // Function.
+ `type ExportedType struct { ... }`, // Exported type.
+ `const ExportedTypedConstant ExportedType = iota`, // Typed constant.
+ `const ExportedTypedConstant_unexported unexportedType`, // Typed constant, exported for unexported type.
+ },
+ []string{
+ `const internalConstant = 2`, // No internal constants.
+ `const 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.
+ `type unexportedType`, // No unexported type.
+ `unexportedTypedConstant`, // No unexported typed constant.
+ `Field`, // No fields.
+ `Method`, // No methods.
+ },
+ },
+ // 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.
+ },
+ []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.
+ },
+ },
+
+ // 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,
+ },
+
+ // Single variable.
+ {
+ "single variable",
+ []string{p, `ExportedVariable`},
+ []string{
+ `ExportedVariable`, // Include comment.
+ `const ExportedVariable = 1`,
+ },
+ nil,
+ },
+ // Single variable -u.
+ {
+ "single variable with -u",
+ []string{`-u`, p, `internalVariable`},
+ []string{
+ `Comment about internal variable`, // Include comment.
+ `const 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`,
+ `Has unexported fields`,
+ `func \(ExportedType\) ExportedMethod\(a int\) bool`,
+ `const ExportedTypedConstant ExportedType = iota`, // Must include associated constant.
+ },
+ []string{
+ `unexportedField`, // No unexported field.
+ `Comment about exported method.`, // No comment about exported method.
+ `unexportedMethod`, // No unexported method.
+ `unexportedTypedConstant`, // No unexported constant.
+ },
+ },
+ // 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.`,
+ `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,
+ },
+
+ // 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,
+ },
+
+ // 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) {
+ 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)
+ }
+ }
+}
+
+// run runs the command, but calls t.Fatal if there is an error.
+func run(c *exec.Cmd, t *testing.T) []byte {
+ output, err := c.CombinedOutput()
+ if err != nil {
+ os.Stdout.Write(output)
+ t.Fatal(err)
+ }
+ return output
+}
"flag"
"fmt"
"go/build"
+ "io"
"log"
"os"
"path"
)
var (
- unexported = flag.Bool("u", false, "show unexported symbols as well as exported")
- matchCase = flag.Bool("c", false, "symbol matching honors case (paths not affected)")
+ unexported bool // -u flag
+ matchCase bool // -c flag
)
// usage is a replacement usage function for the flags package.
func main() {
log.SetFlags(0)
log.SetPrefix("doc: ")
- flag.Usage = usage
- flag.Parse()
- buildPackage, userPath, symbol := parseArgs()
+ err := do(os.Stdout, flag.CommandLine, os.Args[1:])
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+// do is the workhorse, broken out of main to make testing easier.
+func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) {
+ flagSet.Usage = usage
+ unexported = false
+ matchCase = false
+ flagSet.BoolVar(&unexported, "u", false, "show unexported symbols as well as exported")
+ flagSet.BoolVar(&matchCase, "c", false, "symbol matching honors case (paths not affected)")
+ flagSet.Parse(args)
+ buildPackage, userPath, symbol := parseArgs(flagSet.Args())
symbol, method := parseSymbol(symbol)
- pkg := parsePackage(buildPackage, userPath)
+ pkg := parsePackage(writer, buildPackage, userPath)
+ defer func() {
+ pkg.flush()
+ e := recover()
+ if e == nil {
+ return
+ }
+ pkgError, ok := e.(PackageError)
+ if ok {
+ err = pkgError
+ return
+ }
+ panic(e)
+ }()
switch {
case symbol == "":
pkg.packageDoc()
default:
pkg.methodDoc(symbol, method)
}
+ return nil
}
// parseArgs analyzes the arguments (if any) and returns the package
// the path (or "" if it's the current package) and the symbol
// (possibly with a .method) within that package.
// parseSymbol is used to analyze the symbol itself.
-func parseArgs() (*build.Package, string, string) {
- switch flag.NArg() {
+func parseArgs(args []string) (*build.Package, string, string) {
+ switch len(args) {
default:
usage()
case 0:
// Done below.
case 2:
// Package must be importable.
- pkg, err := build.Import(flag.Arg(0), "", build.ImportComment)
+ pkg, err := build.Import(args[0], "", build.ImportComment)
if err != nil {
- log.Fatal(err)
+ log.Fatalf("%s", err)
}
- return pkg, flag.Arg(0), flag.Arg(1)
+ return pkg, args[0], args[1]
}
// Usual case: one argument.
- arg := flag.Arg(0)
+ arg := args[0]
// If it contains slashes, it begins with a package path.
// First, is it a complete package path as it is? If so, we are done.
// This avoids confusion over package paths that have other
// If the unexported flag (-u) is true, isExported returns true because
// it means that we treat the name as if it is exported.
func isExported(name string) bool {
- return *unexported || isUpper(name)
+ return unexported || isUpper(name)
}
// isUpper reports whether the name starts with an upper case letter.
"go/format"
"go/parser"
"go/token"
+ "io"
"log"
"os"
"unicode"
)
type Package struct {
- name string // Package name, json for encoding/json.
- userPath string // String the user used to find this package.
- pkg *ast.Package // Parsed package.
- file *ast.File // Merged from all files in the package
- doc *doc.Package
- build *build.Package
- fs *token.FileSet // Needed for printing.
- buf bytes.Buffer
+ writer io.Writer // Destination for output.
+ name string // Package name, json for encoding/json.
+ userPath string // String the user used to find this package.
+ unexported bool
+ matchCase bool
+ pkg *ast.Package // Parsed package.
+ file *ast.File // Merged from all files in the package
+ doc *doc.Package
+ build *build.Package
+ fs *token.FileSet // Needed for printing.
+ buf bytes.Buffer
+}
+
+type PackageError string // type returned by pkg.Fatalf.
+
+func (p PackageError) Error() string {
+ return string(p)
+}
+
+// pkg.Fatalf is like log.Fatalf, but panics so it can be recovered in the
+// main do function, so it doesn't cause an exit. Allows testing to work
+// without running a subprocess. The log prefix will be added when
+// logged in main; it is not added here.
+func (pkg *Package) Fatalf(format string, args ...interface{}) {
+ panic(PackageError(fmt.Sprintf(format, args...)))
}
// parsePackage turns the build package we found into a parsed package
// we can then use to generate documentation.
-func parsePackage(pkg *build.Package, userPath string) *Package {
+func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Package {
fs := token.NewFileSet()
// include tells parser.ParseDir which files to include.
// That means the file must be in the build package's GoFiles or CgoFiles
}
// Make sure they are all in one package.
if len(pkgs) != 1 {
- log.Fatalf("multiple packages directory %s", pkg.Dir)
+ log.Fatalf("multiple packages in directory %s", pkg.Dir)
}
astPkg := pkgs[pkg.Name]
}
return &Package{
+ writer: writer,
name: pkg.Name,
userPath: userPath,
pkg: astPkg,
}
func (pkg *Package) flush() {
- _, err := os.Stdout.Write(pkg.buf.Bytes())
+ _, err := pkg.writer.Write(pkg.buf.Bytes())
if err != nil {
log.Fatal(err)
}
// trimUnexportedElems modifies spec in place to elide unexported fields from
// structs and methods from interfaces (unless the unexported flag is set).
func trimUnexportedElems(spec *ast.TypeSpec) {
- if *unexported {
+ if unexported {
return
}
switch typ := spec.Type.(type) {
if symbol == "" {
return false
}
- log.Fatalf("symbol %s is not a type in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath)
+ pkg.Fatalf("symbol %s is not a type in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath)
}
found := false
for _, typ := range types {
func (pkg *Package) methodDoc(symbol, method string) {
defer pkg.flush()
if !pkg.printMethodDoc(symbol, method) {
- log.Fatalf("no method %s.%s in package %s installed in %q", symbol, method, pkg.name, pkg.build.ImportPath)
+ pkg.Fatalf("no method %s.%s in package %s installed in %q", symbol, method, pkg.name, pkg.build.ImportPath)
}
}
if !isExported(program) {
return false
}
- if *matchCase {
+ if matchCase {
return user == program
}
for _, u := range user {
--- /dev/null
+// 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 comment.
+package pkg
+
+// Constants
+
+// Comment about exported constant.
+const ExportedConstant = 1
+
+// Comment about internal constant.
+const internalConstant = 2
+
+// Comment about block of constants.
+const (
+ // Comment before ConstOne.
+ ConstOne = 1
+ ConstTwo = 2 // Comment on line with ConstTwo.
+ constThree = 3 // Comment on line with constThree.
+)
+
+// Variables
+
+// Comment about exported variable.
+const ExportedVariable = 1
+
+// Comment about internal variable.
+const internalVariable = 2
+
+// Comment about block of variables.
+const (
+ // Comment before VarOne.
+ VarOne = 1
+ VarTwo = 2 // Comment on line with VarTwo.
+ varThree = 3 // Comment on line with varThree.
+)
+
+// Comment about exported function.
+func ExportedFunc(a int) bool
+
+// Comment about internal function.
+func internalFunc(a int) bool
+
+// Comment about exported type.
+type ExportedType struct {
+ // Comment before exported field.
+ ExportedField int
+ unexportedField int // Comment on line with unexported field.
+}
+
+// Comment about exported method.
+func (ExportedType) ExportedMethod(a int) bool {
+ return true
+}
+
+// Comment about unexported method.
+func (ExportedType) unexportedMethod(a int) bool {
+ return true
+}
+
+// Constants tied to ExportedType. (The type is a struct so this isn't valid Go,
+// but it parses and that's all we need.)
+const (
+ ExportedTypedConstant ExportedType = iota
+)
+
+const unexportedTypedConstant ExportedType = 1 // In a separate section to test -u.
+
+// Comment about unexported type.
+type unexportedType int
+
+func (unexportedType) ExportedMethod() bool {
+ return true
+}
+
+func (unexportedType) unexportedMethod() bool {
+ return true
+}
+
+// Constants tied to unexportedType.
+const (
+ ExportedTypedConstant_unexported unexportedType = iota
+)
+
+const unexportedTypedConstant unexportedType = 1 // In a separate section to test -u.
+
+// For case matching.
+const CaseMatch = 1
+const Casematch = 2