diff --git a/ginkgo/outline/_testdata/alias_test.go b/ginkgo/outline/_testdata/alias_test.go new file mode 100644 index 000000000..61237e4d0 --- /dev/null +++ b/ginkgo/outline/_testdata/alias_test.go @@ -0,0 +1,47 @@ +package example_test + +import ( + fooginkgo "github.com/onsi/ginkgo" +) + +var _ = fooginkgo.Describe("NodotFixture", func() { + fooginkgo.Describe("normal", func() { + fooginkgo.It("normal", func() { + + }) + }) + + fooginkgo.Context("normal", func() { + fooginkgo.It("normal", func() { + + }) + }) + + fooginkgo.When("normal", func() { + fooginkgo.It("normal", func() { + + }) + }) + + fooginkgo.It("normal", func() { + + }) + + fooginkgo.Specify("normal", func() { + + }) + + fooginkgo.Measure("normal", func(b Benchmarker) { + + }, 2) + + fooginkgo.DescribeTable("normal", + func() {}, + fooginkgo.Entry("normal"), + ) + + fooginkgo.DescribeTable("normal", + func() {}, + fooginkgo.Entry("normal"), + ) +}) diff --git a/ginkgo/outline/_testdata/alias_test_outline.csv b/ginkgo/outline/_testdata/alias_test_outline.csv new file mode 100644 index 000000000..10b7d49f7 --- /dev/null +++ b/ginkgo/outline/_testdata/alias_test_outline.csv @@ -0,0 +1,11 @@ +Name,Text,Start,End,Spec,Focused,Pending +Describe,NodotFixture,79,676,false,false,false +Describe,normal,124,205,false,false,false +It,normal,164,201,true,false,false +Context,normal,208,288,false,false,false +It,normal,247,284,true,false,false +When,normal,291,368,false,false,false +It,normal,327,364,true,false,false +It,normal,371,407,true,false,false +Specify,normal,410,451,true,false,false +Measure,normal,454,511,true,false,false diff --git a/ginkgo/outline/_testdata/alias_test_outline.json b/ginkgo/outline/_testdata/alias_test_outline.json new file mode 100644 index 000000000..34029e00f --- /dev/null +++ b/ginkgo/outline/_testdata/alias_test_outline.json @@ -0,0 +1 @@ +[{"name":"Describe","text":"NodotFixture","start":79,"end":676,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"Describe","text":"normal","start":124,"end":205,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":164,"end":201,"spec":true,"focused":false,"pending":false}]},{"name":"Context","text":"normal","start":208,"end":288,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":247,"end":284,"spec":true,"focused":false,"pending":false}]},{"name":"When","text":"normal","start":291,"end":368,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":327,"end":364,"spec":true,"focused":false,"pending":false}]},{"name":"It","text":"normal","start":371,"end":407,"spec":true,"focused":false,"pending":false},{"name":"Specify","text":"normal","start":410,"end":451,"spec":true,"focused":false,"pending":false},{"name":"Measure","text":"normal","start":454,"end":511,"spec":true,"focused":false,"pending":false}]}] diff --git a/ginkgo/outline/_testdata/nodot_test.go b/ginkgo/outline/_testdata/nodot_test.go new file mode 100644 index 000000000..51cc08ff4 --- /dev/null +++ b/ginkgo/outline/_testdata/nodot_test.go @@ -0,0 +1,47 @@ +package example_test + +import ( + "github.com/onsi/ginkgo" +) + +var _ = ginkgo.Describe("NodotFixture", func() { + ginkgo.Describe("normal", func() { + ginkgo.It("normal", func() { + + }) + }) + + ginkgo.Context("normal", func() { + ginkgo.It("normal", func() { + + }) + }) + + ginkgo.When("normal", func() { + ginkgo.It("normal", func() { + + }) + }) + + ginkgo.It("normal", func() { + + }) + + ginkgo.Specify("normal", func() { + + }) + + ginkgo.Measure("normal", func(b Benchmarker) { + + }, 2) + + ginkgo.DescribeTable("normal", + func() {}, + ginkgo.Entry("normal"), + ) + + ginkgo.DescribeTable("normal", + func() {}, + ginkgo.Entry("normal"), + ) +}) diff --git a/ginkgo/outline/_testdata/nodot_test_outline.csv b/ginkgo/outline/_testdata/nodot_test_outline.csv new file mode 100644 index 000000000..9d79c0c6d --- /dev/null +++ b/ginkgo/outline/_testdata/nodot_test_outline.csv @@ -0,0 +1,11 @@ +Name,Text,Start,End,Spec,Focused,Pending +Describe,NodotFixture,69,624,false,false,false +Describe,normal,111,186,false,false,false +It,normal,148,182,true,false,false +Context,normal,189,263,false,false,false +It,normal,225,259,true,false,false +When,normal,266,337,false,false,false +It,normal,299,333,true,false,false +It,normal,340,373,true,false,false +Specify,normal,376,414,true,false,false +Measure,normal,417,471,true,false,false diff --git a/ginkgo/outline/_testdata/nodot_test_outline.json b/ginkgo/outline/_testdata/nodot_test_outline.json new file mode 100644 index 000000000..cfbf35785 --- /dev/null +++ b/ginkgo/outline/_testdata/nodot_test_outline.json @@ -0,0 +1 @@ +[{"name":"Describe","text":"NodotFixture","start":69,"end":624,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"Describe","text":"normal","start":111,"end":186,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":148,"end":182,"spec":true,"focused":false,"pending":false}]},{"name":"Context","text":"normal","start":189,"end":263,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":225,"end":259,"spec":true,"focused":false,"pending":false}]},{"name":"When","text":"normal","start":266,"end":337,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":299,"end":333,"spec":true,"focused":false,"pending":false}]},{"name":"It","text":"normal","start":340,"end":373,"spec":true,"focused":false,"pending":false},{"name":"Specify","text":"normal","start":376,"end":414,"spec":true,"focused":false,"pending":false},{"name":"Measure","text":"normal","start":417,"end":471,"spec":true,"focused":false,"pending":false}]}] diff --git a/ginkgo/outline/import.go b/ginkgo/outline/import.go new file mode 100644 index 000000000..9b869cf46 --- /dev/null +++ b/ginkgo/outline/import.go @@ -0,0 +1,61 @@ +// Copyright 2013 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. + +// Most of the required functions were available in the +// "golang.org/x/tools/go/ast/astutil" package, but not exported. +// They were copied from https://github.com/golang/tools/blob/2b0845dc783e36ae26d683f4915a5840ef01ab0f/go/ast/astutil/imports.go + +package outline + +import ( + "go/ast" + "strconv" + "strings" +) + +// importNameForPackage returns the import name for the package. If the package +// is not imported, it returns false. Examples: +// How package is imported -> Import Name +// "import example.com/pkg/foo" -> "foo" +// "import fooalias example.com/pkg/foo" -> "fooalias" +// "import . example.com/pkg/foo" -> "." +func importNameForPackage(f *ast.File, path string) (string, bool) { + spec := importSpec(f, path) + if spec == nil { + return "", false + } + name := spec.Name.String() + if name == "" { + // If the package name is not explicitly specified, + // make an educated guess. This is not guaranteed to be correct. + lastSlash := strings.LastIndex(path, "/") + if lastSlash == -1 { + name = path + } else { + name = path[lastSlash+1:] + } + } + return name, true +} + +// importSpec returns the import spec if f imports path, +// or nil otherwise. +func importSpec(f *ast.File, path string) *ast.ImportSpec { + for _, s := range f.Imports { + if importPath(s) == path { + return s + } + } + return nil +} + +// importPath returns the unquoted import path of s, +// or "" if the path is not properly quoted. +func importPath(s *ast.ImportSpec) string { + t, err := strconv.Unquote(s.Path.Value) + if err != nil { + return "" + } + return t +} diff --git a/ginkgo/outline/outline.go b/ginkgo/outline/outline.go index 33c6f6333..c4fef57c4 100644 --- a/ginkgo/outline/outline.go +++ b/ginkgo/outline/outline.go @@ -12,6 +12,8 @@ import ( ) const ( + // ginkgoImportPath is the well-known ginkgo import path + ginkgoImportPath = "github.com/onsi/ginkgo" // undefinedTextAlt is used if the spec/container text cannot be derived undefinedTextAlt = "undefined" ) @@ -52,9 +54,28 @@ func (n *ginkgoNode) Walk(f walkFunc) { // ginkgoNodeFromCallExpr derives an outline entry from a go AST subtree // corresponding to a Ginkgo container or spec. -func ginkgoNodeFromCallExpr(ce *ast.CallExpr) (*ginkgoNode, bool) { - id, ok := ce.Fun.(*ast.Ident) - if !ok { +func ginkgoNodeFromCallExpr(ce *ast.CallExpr, ginkgoImportName string) (*ginkgoNode, bool) { + var id *ast.Ident + switch ex := ce.Fun.(type) { + case *ast.Ident: + if ginkgoImportName != "." { + return nil, false + } + id = ex + case *ast.SelectorExpr: + pkgID, ok := ex.X.(*ast.Ident) + if !ok { + return nil, false + } + // A package identifier is top-level, so Obj must be nil + if pkgID.Obj != nil { + return nil, false + } + if ginkgoImportName != pkgID.Name { + return nil, false + } + id = ex.Sel + default: return nil, false } @@ -62,8 +83,6 @@ func ginkgoNodeFromCallExpr(ce *ast.CallExpr) (*ginkgoNode, bool) { n.Name = id.Name n.Start = ce.Pos() n.End = ce.End() - // TODO: Handle nodot and alias imports of the ginkgo package. - // The below assumes dot imports . switch id.Name { case "It", "Measure", "Specify": n.Spec = true @@ -131,6 +150,10 @@ func textFromCallExpr(ce *ast.CallExpr) (string, bool) { // FromASTFile returns an outline for a Ginkgo test source file func FromASTFile(src *ast.File) (*outline, error) { + ginkgoImportName, ok := importNameForPackage(src, ginkgoImportPath) + if !ok { + return nil, fmt.Errorf("file does not import %s", ginkgoImportPath) + } root := ginkgoNode{ Nodes: []*ginkgoNode{}, } @@ -144,7 +167,7 @@ func FromASTFile(src *ast.File) (*outline, error) { // ast.CallExpr, this should never happen panic(fmt.Errorf("node starting at %d, ending at %d is not an *ast.CallExpr", node.Pos(), node.End())) } - gn, ok := ginkgoNodeFromCallExpr(ce) + gn, ok := ginkgoNodeFromCallExpr(ce, ginkgoImportName) if !ok { // Not a Ginkgo call, continue return true diff --git a/ginkgo/outline/outline_test.go b/ginkgo/outline/outline_test.go index 4d6b62d46..6036459a1 100644 --- a/ginkgo/outline/outline_test.go +++ b/ginkgo/outline/outline_test.go @@ -40,6 +40,8 @@ var _ = DescribeTable("Validate outline from file with", Expect(gotCSV).To(Equal(string(wantCSV))) }, + Entry("normal import of ginkgo package (no dot, no alias), normal container and specs", "nodot_test.go", "nodot_test_outline.json", "nodot_test_outline.csv"), + Entry("aliased import of ginkgo package, normal container and specs", "alias_test.go", "alias_test_outline.json", "alias_test_outline.csv"), Entry("normal containers and specs", "normal_test.go", "normal_test_outline.json", "normal_test_outline.csv"), Entry("focused containers and specs", "focused_test.go", "focused_test_outline.json", "focused_test_outline.csv"), Entry("pending containers and specs", "pending_test.go", "pending_test_outline.json", "pending_test_outline.csv"),