...
Run Format

Source file src/cmd/doc/doc_test.go

Documentation: cmd/doc

  // 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`,
  			`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.
  			`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)
  			}
  		}
  	}
  }
  
  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
  		}
  	}
  }
  

View as plain text