diff options
Diffstat (limited to '')
-rw-r--r-- | src/cmd/doc/doc_test.go | 1049 |
1 files changed, 1049 insertions, 0 deletions
diff --git a/src/cmd/doc/doc_test.go b/src/cmd/doc/doc_test.go new file mode 100644 index 0000000..39530e3 --- /dev/null +++ b/src/cmd/doc/doc_test.go @@ -0,0 +1,1049 @@ +// 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" + "path/filepath" + "regexp" + "runtime" + "strings" + "testing" +) + +func TestMain(m *testing.M) { + // Clear GOPATH so we don't access the user's own packages in the test. + buildCtx.GOPATH = "" + testGOPATH = true // force GOPATH mode; module test is in cmd/go/testdata/script/mod_doc.txt + + // Add $GOROOT/src/cmd/doc/testdata explicitly so we can access its contents in the test. + // Normally testdata directories are ignored, but sending it to dirs.scan directly is + // a hack that works around the check. + testdataDir, err := filepath.Abs("testdata") + if err != nil { + panic(err) + } + dirsInit( + Dir{importPath: "testdata", dir: testdataDir}, + Dir{importPath: "testdata/nested", dir: filepath.Join(testdataDir, "nested")}, + Dir{importPath: "testdata/nested/nested", dir: filepath.Join(testdataDir, "nested", "nested")}) + + os.Exit(m.Run()) +} + +func maybeSkip(t *testing.T) { + if runtime.GOOS == "ios" { + t.Skip("iOS does not have a full file tree") + } +} + +type isDotSlashTest struct { + str string + result bool +} + +var isDotSlashTests = []isDotSlashTest{ + {``, false}, + {`x`, false}, + {`...`, false}, + {`.../`, false}, + {`...\`, false}, + + {`.`, true}, + {`./`, true}, + {`.\`, true}, + {`./x`, true}, + {`.\x`, true}, + + {`..`, true}, + {`../`, true}, + {`..\`, true}, + {`../x`, true}, + {`..\x`, true}, +} + +func TestIsDotSlashPath(t *testing.T) { + for _, test := range isDotSlashTests { + if result := isDotSlash(test.str); result != test.result { + t.Errorf("isDotSlash(%q) = %t; expected %t", test.str, result, test.result) + } + } +} + +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 -all + { + "full package", + []string{"-all", p}, + []string{ + `package pkg .*import`, + `Package comment`, + `CONSTANTS`, + `Comment before ConstOne`, + `ConstOne = 1`, + `ConstTwo = 2 // Comment on line with ConstTwo`, + `ConstFive`, + `ConstSix`, + `Const block where first entry is unexported`, + `ConstLeft2, constRight2 uint64`, + `constLeft3, ConstRight3`, + `ConstLeft4, ConstRight4`, + `Duplicate = iota`, + `const CaseMatch = 1`, + `const Casematch = 2`, + `const ExportedConstant = 1`, + `const MultiLineConst = `, + `MultiLineString1`, + `VARIABLES`, + `Comment before VarOne`, + `VarOne = 1`, + `Comment about block of variables`, + `VarFive = 5`, + `var ExportedVariable = 1`, + `var ExportedVarOfUnExported unexportedType`, + `var LongLine = newLongLine\(`, + `var MultiLineVar = map\[struct {`, + `FUNCTIONS`, + `func ExportedFunc\(a int\) bool`, + `Comment about exported function`, + `func MultiLineFunc\(x interface`, + `func ReturnUnexported\(\) unexportedType`, + `TYPES`, + `type ExportedInterface interface`, + `type ExportedStructOneField struct`, + `type ExportedType struct`, + `Comment about exported type`, + `const ConstGroup4 ExportedType = ExportedType`, + `ExportedTypedConstant ExportedType = iota`, + `Constants tied to ExportedType`, + `func ExportedTypeConstructor\(\) \*ExportedType`, + `Comment about constructor for exported type`, + `func ReturnExported\(\) ExportedType`, + `func \(ExportedType\) ExportedMethod\(a int\) bool`, + `Comment about exported method`, + `type T1 = T2`, + `type T2 int`, + }, + []string{ + `constThree`, + `_, _ uint64 = 2 \* iota, 1 << iota`, + `constLeft1, constRight1`, + `duplicate`, + `varFour`, + `func internalFunc`, + `unexportedField`, + `func \(unexportedType\)`, + }, + }, + // Package with just the package declaration. Issue 31457. + { + "only package declaration", + []string{"-all", p + "/nested/empty"}, + []string{`package empty .*import`}, + nil, + }, + // Package dump -short + { + "full package with -short", + []string{`-short`, p}, + []string{ + `const ExportedConstant = 1`, // Simple constant. + `func ReturnUnexported\(\) unexportedType`, // Function with unexported return type. + }, + []string{ + `MultiLine(String|Method|Field)`, // No data from multi line portions. + }, + }, + // 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. + }, + }, + // Package dump -u -all + { + "full package", + []string{"-u", "-all", p}, + []string{ + `package pkg .*import`, + `Package comment`, + `CONSTANTS`, + `Comment before ConstOne`, + `ConstOne += 1`, + `ConstTwo += 2 // Comment on line with ConstTwo`, + `constThree = 3 // Comment on line with constThree`, + `ConstFive`, + `const internalConstant += 2`, + `Comment about internal constant`, + `VARIABLES`, + `Comment before VarOne`, + `VarOne += 1`, + `Comment about block of variables`, + `varFour += 4`, + `VarFive += 5`, + `varSix += 6`, + `var ExportedVariable = 1`, + `var LongLine = newLongLine\(`, + `var MultiLineVar = map\[struct {`, + `var internalVariable = 2`, + `Comment about internal variable`, + `FUNCTIONS`, + `func ExportedFunc\(a int\) bool`, + `Comment about exported function`, + `func MultiLineFunc\(x interface`, + `func internalFunc\(a int\) bool`, + `Comment about internal function`, + `func newLongLine\(ss .*string\)`, + `TYPES`, + `type ExportedType struct`, + `type T1 = T2`, + `type T2 int`, + `type unexportedType int`, + `Comment about unexported type`, + `ConstGroup1 unexportedType = iota`, + `ConstGroup2`, + `ConstGroup3`, + `ExportedTypedConstant_unexported unexportedType = iota`, + `Constants tied to unexportedType`, + `const unexportedTypedConstant unexportedType = 1`, + `func ReturnUnexported\(\) unexportedType`, + `func \(unexportedType\) ExportedMethod\(\) bool`, + `func \(unexportedType\) unexportedMethod\(\) bool`, + }, + nil, + }, + + // 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 -src. + { + "block of constants with -src", + []string{"-src", p, `ConstTwo`}, + []string{ + `Comment about block of constants`, // Top comment. + `ConstOne.*=.*1`, // Each constant seen. + `ConstTwo.*=.*2.*Comment on line with ConstTwo`, + `constThree`, // Even unexported constants. + }, + 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, + }, + // Function with -src. + { + "function with -src", + []string{"-src", p, `ExportedFunc`}, + []string{ + `Comment about exported function`, // Include comment. + `func ExportedFunc\(a int\) bool`, + `return true != false`, // Include body. + }, + 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 with -src. Will see unexported fields. + { + "type", + []string{"-src", p, `ExportedType`}, + []string{ + `Comment about exported type`, // Include comment. + `type ExportedType struct`, // Type definition. + `Comment before exported field`, + `ExportedField.*Comment on line with exported field`, + `ExportedEmbeddedType.*Comment on line with exported embedded field`, + `unexportedType.*Comment on line with unexported embedded field`, + `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{ + `Comment about exported method`, // No comment about exported method. + `unexportedMethod`, // No unexported method. + `unexportedTypedConstant`, // No unexported constant. + }, + }, + // Type -all. + { + "type", + []string{"-all", p, `ExportedType`}, + []string{ + `type ExportedType struct {`, // Type definition as source. + `Comment about exported type`, // Include comment afterwards. + `const ConstGroup4 ExportedType = ExportedType\{\}`, // Related constants. + `ExportedTypedConstant ExportedType = iota`, + `Constants tied to ExportedType`, + `func ExportedTypeConstructor\(\) \*ExportedType`, + `Comment about constructor for exported type.`, + `func ReturnExported\(\) ExportedType`, + `func \(ExportedType\) ExportedMethod\(a int\) bool`, + `Comment about exported method.`, + `func \(ExportedType\) Uncommented\(a int\) bool\n\n`, // Ensure line gap after method with no comment + }, + []string{ + `unexportedType`, + }, + }, + // 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`, + `\*qualified.ExportedEmbeddedType.*Comment on line with exported embedded \*selector.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`, + }, + }, + // Interface method at package level. + { + "interface method at package level", + []string{p, `ExportedMethod`}, + []string{ + `func \(ExportedType\) ExportedMethod\(a int\) bool`, + `Comment about exported method`, + }, + []string{ + `Comment before exported method.*\n.*ExportedMethod\(\)` + + `.*Comment on line with exported method`, + }, + }, + + // 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, + }, + // Method with -src. + { + "method with -src", + []string{"-src", p, `ExportedType.ExportedMethod`}, + []string{ + `func \(ExportedType\) ExportedMethod\(a int\) bool`, + `Comment about exported method`, + `return true != true`, + }, + 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`, + }, + }, + + // Merging comments with -src. + { + "merge comments with -src A", + []string{"-src", p + "/merge", `A`}, + []string{ + `A doc`, + `func A`, + `A comment`, + }, + []string{ + `Package A doc`, + `Package B doc`, + `B doc`, + `B comment`, + `B doc`, + }, + }, + { + "merge comments with -src B", + []string{"-src", p + "/merge", `B`}, + []string{ + `B doc`, + `func B`, + `B comment`, + }, + []string{ + `Package A doc`, + `Package B doc`, + `A doc`, + `A comment`, + `A doc`, + }, + }, + + // No dups with -u. Issue 21797. + { + "case matching on, no dups", + []string{"-u", p, `duplicate`}, + []string{ + `Duplicate`, + `duplicate`, + }, + []string{ + "\\)\n+const", // This will appear if the const decl appears twice. + }, + }, + { + "non-imported: pkg.sym", + []string{"nested.Foo"}, + []string{"Foo struct"}, + nil, + }, + { + "non-imported: pkg only", + []string{"nested"}, + []string{"Foo struct"}, + nil, + }, + { + "non-imported: pkg sym", + []string{"nested", "Foo"}, + []string{"Foo struct"}, + nil, + }, + { + "formatted doc on function", + []string{p, "ExportedFormattedDoc"}, + []string{ + `func ExportedFormattedDoc\(a int\) bool`, + ` Comment about exported function with formatting\. + + Example + + fmt\.Println\(FormattedDoc\(\)\) + + Text after pre-formatted block\.`, + }, + nil, + }, + { + "formatted doc on type field", + []string{p, "ExportedFormattedType.ExportedField"}, + []string{ + `type ExportedFormattedType struct`, + ` // Comment before exported field with formatting\. + //[ ] + // Example + //[ ] + // a\.ExportedField = 123 + //[ ] + // Text after pre-formatted block\.`, + `ExportedField int`, + }, + nil, + }, +} + +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 %v: %s\n", test.name, test.args, 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 bytes.Count(output, []byte("TYPES\n")) > 1 { + t.Fatalf("%s: repeating headers", test.name) + } + 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) + } + } + { + var flagSet flag.FlagSet + err := do(&b, &flagSet, []string{"bytes", "Foo"}) + if err == nil { + t.Errorf("expected error from bytes Foo") + } else if !strings.Contains(err.Error(), "no symbol Foo") { + t.Errorf("unexpected error %q from bytes Foo", err) + } + } + { + var flagSet flag.FlagSet + err := do(&b, &flagSet, []string{"nosuchpackage", "Foo"}) + if err == nil { + // actually present in the user's filesystem + } else if !strings.Contains(err.Error(), "no such package") { + t.Errorf("unexpected error %q from nosuchpackage Foo", err) + } + } +} + +// Test the code to look up packages when the first argument starts with "./". +// Our test case is in effect "cd src/text; doc ./template". This should get +// text/template but before Issue 23383 was fixed would give html/template. +func TestDotSlashLookup(t *testing.T) { + if testing.Short() { + t.Skip("scanning file system takes too long") + } + maybeSkip(t) + where, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + defer func() { + if err := os.Chdir(where); err != nil { + t.Fatal(err) + } + }() + if err := os.Chdir(filepath.Join(buildCtx.GOROOT, "src", "text")); err != nil { + t.Fatal(err) + } + var b bytes.Buffer + var flagSet flag.FlagSet + err = do(&b, &flagSet, []string{"./template"}) + if err != nil { + t.Errorf("unexpected error %q from ./template", err) + } + // The output should contain information about the text/template package. + const want = `package template // import "text/template"` + output := b.String() + if !strings.HasPrefix(output, want) { + t.Fatalf("wrong package: %.*q...", len(want), output) + } +} + +// Test that we don't print spurious package clauses +// when there should be no output at all. Issue 37969. +func TestNoPackageClauseWhenNoMatch(t *testing.T) { + maybeSkip(t) + var b bytes.Buffer + var flagSet flag.FlagSet + err := do(&b, &flagSet, []string{"template.ZZZ"}) + // Expect an error. + if err == nil { + t.Error("expect an error for template.zzz") + } + // And the output should not contain any package clauses. + const dontWant = `package template // import ` + output := b.String() + if strings.Contains(output, dontWant) { + t.Fatalf("improper package clause printed:\n%s", output) + } +} + +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 + } + } +} |