// 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" "go/build" "internal/testenv" "log" "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 // Set GOROOT in case runtime.GOROOT is wrong (for example, if the test was // built with -trimpath). dirsInit would identify it using 'go env GOROOT', // but we can't be sure that the 'go' in $PATH is the right one either. buildCtx.GOROOT = testenv.GOROOT(nil) build.Default.GOROOT = testenv.GOROOT(nil) // 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 `type SimpleConstraint interface{ ... }`, `type TildeConstraint interface{ ... }`, `type StructConstraint interface{ ... }`, }, []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. `ignore:directive`, // Directives should be dropped. }, }, // 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`, `type SimpleConstraint interface {`, `type TildeConstraint interface {`, `type StructConstraint interface {`, }, []string{ `constThree`, `_, _ uint64 = 2 \* iota, 1 << iota`, `constLeft1, constRight1`, `duplicate`, `varFour`, `func internalFunc`, `unexportedField`, `func \(unexportedType\)`, `ignore:directive`, }, }, // 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. `ignore:directive`, }, }, // 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`, }, []string{ `ignore:directive`, }, }, // 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.*//\n.*// // Code block showing how to use ExportedMethod\n.*// func DoSomething\(\) error {\n.*// ExportedMethod\(\)\n.*// return nil\n.*// }\n.*//.*\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.*//\n.*// // Code block showing how to use ExportedMethod\n.*// func DoSomething\(\) error {\n.*// ExportedMethod\(\)\n.*// return nil\n.*// }\n.*//.*\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.*//\n.*// // Code block showing how to use ExportedMethod\n.*// func DoSomething\(\) error {\n.*// ExportedMethod\(\)\n.*// return nil\n.*// }\n.*//.*\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`, }, []string{"ignore:directive"}, }, { "formatted doc on entire type", []string{p, "ExportedFormattedType"}, []string{ `type ExportedFormattedType struct`, ` // Comment before exported field with formatting\. // // Example // // a\.ExportedField = 123 // // Text after pre-formatted block\.`, `ExportedField int`, }, []string{"ignore:directive"}, }, { "formatted doc on entire type with -all", []string{"-all", p, "ExportedFormattedType"}, []string{ `type ExportedFormattedType struct`, ` // Comment before exported field with formatting\. // // Example // // a\.ExportedField = 123 // // Text after pre-formatted block\.`, `ExportedField int`, }, []string{"ignore:directive"}, }, } func TestDoc(t *testing.T) { maybeSkip(t) defer log.SetOutput(log.Writer()) for _, test := range tests { var b bytes.Buffer var flagSet flag.FlagSet var logbuf bytes.Buffer log.SetOutput(&logbuf) err := do(&b, &flagSet, test.args) if err != nil { t.Fatalf("%s %v: %s\n", test.name, test.args, err) } if logbuf.Len() > 0 { t.Errorf("%s %v: unexpected log messages:\n%s", test.name, test.args, logbuf.Bytes()) } 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 strings.Builder 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 strings.Builder 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 } } }