From c386859a8674d24d4ff0fefc7ca5e8e11a657dcd Mon Sep 17 00:00:00 2001 From: Daniel Lipovetsky Date: Fri, 25 Dec 2020 13:02:29 -0800 Subject: [PATCH 01/13] Adds 'outline' command to print the outline of specs/containers in a file Implements feature request in #753 --- ginkgo/main.go | 6 + ginkgo/outline/_testdata/focused_test.go | 48 +++++ .../_testdata/focused_test_outline.csv | 11 + .../_testdata/focused_test_outline.json | 100 +++++++++ ginkgo/outline/_testdata/normal_test.go | 48 +++++ .../outline/_testdata/normal_test_outline.csv | 11 + .../_testdata/normal_test_outline.json | 100 +++++++++ ginkgo/outline/_testdata/pending_test.go | 48 +++++ .../_testdata/pending_test_outline.csv | 11 + .../_testdata/pending_test_outline.json | 100 +++++++++ ginkgo/outline/_testdata/suite_test.go | 13 ++ .../outline/_testdata/suite_test_outline.json | 1 + ginkgo/outline/outline.go | 194 ++++++++++++++++++ ginkgo/outline/outline_suite_test.go | 13 ++ ginkgo/outline/outline_test.go | 46 +++++ ginkgo/outline_command.go | 70 +++++++ go.mod | 4 +- go.sum | 28 ++- 18 files changed, 844 insertions(+), 8 deletions(-) create mode 100644 ginkgo/outline/_testdata/focused_test.go create mode 100644 ginkgo/outline/_testdata/focused_test_outline.csv create mode 100644 ginkgo/outline/_testdata/focused_test_outline.json create mode 100644 ginkgo/outline/_testdata/normal_test.go create mode 100644 ginkgo/outline/_testdata/normal_test_outline.csv create mode 100644 ginkgo/outline/_testdata/normal_test_outline.json create mode 100644 ginkgo/outline/_testdata/pending_test.go create mode 100644 ginkgo/outline/_testdata/pending_test_outline.csv create mode 100644 ginkgo/outline/_testdata/pending_test_outline.json create mode 100644 ginkgo/outline/_testdata/suite_test.go create mode 100644 ginkgo/outline/_testdata/suite_test_outline.json create mode 100644 ginkgo/outline/outline.go create mode 100644 ginkgo/outline/outline_suite_test.go create mode 100644 ginkgo/outline/outline_test.go create mode 100644 ginkgo/outline_command.go diff --git a/ginkgo/main.go b/ginkgo/main.go index f60c48a72..ac725bf40 100644 --- a/ginkgo/main.go +++ b/ginkgo/main.go @@ -111,6 +111,11 @@ will output an executable file named `package.test`. This can be run directly o ginkgo + +To print an outline of Ginkgo specs and containers in a file: + + gingko outline + To print out Ginkgo's version: ginkgo version @@ -172,6 +177,7 @@ func init() { Commands = append(Commands, BuildUnfocusCommand()) Commands = append(Commands, BuildVersionCommand()) Commands = append(Commands, BuildHelpCommand()) + Commands = append(Commands, BuildOutlineCommand()) } func main() { diff --git a/ginkgo/outline/_testdata/focused_test.go b/ginkgo/outline/_testdata/focused_test.go new file mode 100644 index 000000000..3e56b581f --- /dev/null +++ b/ginkgo/outline/_testdata/focused_test.go @@ -0,0 +1,48 @@ +package example_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" +) + +var _ = Describe("FocusedFixture", func() { + FDescribe("focused", func() { + It("focused", func() { + + }) + }) + + FContext("focused", func() { + It("focused", func() { + + }) + }) + + FWhen("focused", func() { + It("focused", func() { + + }) + }) + + FIt("focused", func() { + + }) + + FSpecify("focused", func() { + + }) + + FMeasure("focused", func(b Benchmarker) { + + }, 2) + + FDescribeTable("focused", + func() {}, + Entry("focused"), + ) + + DescribeTable("focused", + func() {}, + FEntry("focused"), + ) +}) diff --git a/ginkgo/outline/_testdata/focused_test_outline.csv b/ginkgo/outline/_testdata/focused_test_outline.csv new file mode 100644 index 000000000..49bddba88 --- /dev/null +++ b/ginkgo/outline/_testdata/focused_test_outline.csv @@ -0,0 +1,11 @@ +Name,Text,Start,End,Spec,Focused,Pending +Describe,FocusedFixture,116,596,false,false,false +FDescribe,focused,153,217,false,true,false +It,focused,185,213,true,false,false +FContext,focused,220,283,false,true,false +It,focused,251,279,true,false,false +FWhen,focused,286,346,false,true,false +It,focused,314,342,true,false,false +FIt,focused,349,377,true,true,false +FSpecify,focused,380,413,true,true,false +FMeasure,focused,416,465,true,true,false diff --git a/ginkgo/outline/_testdata/focused_test_outline.json b/ginkgo/outline/_testdata/focused_test_outline.json new file mode 100644 index 000000000..f265139b5 --- /dev/null +++ b/ginkgo/outline/_testdata/focused_test_outline.json @@ -0,0 +1,100 @@ +[ + { + "name": "Describe", + "text": "FocusedFixture", + "start": 116, + "end": 596, + "spec": false, + "focused": false, + "pending": false, + "nodes": [ + { + "name": "FDescribe", + "text": "focused", + "start": 153, + "end": 217, + "spec": false, + "focused": true, + "pending": false, + "nodes": [ + { + "name": "It", + "text": "focused", + "start": 185, + "end": 213, + "spec": true, + "focused": false, + "pending": false + } + ] + }, + { + "name": "FContext", + "text": "focused", + "start": 220, + "end": 283, + "spec": false, + "focused": true, + "pending": false, + "nodes": [ + { + "name": "It", + "text": "focused", + "start": 251, + "end": 279, + "spec": true, + "focused": false, + "pending": false + } + ] + }, + { + "name": "FWhen", + "text": "focused", + "start": 286, + "end": 346, + "spec": false, + "focused": true, + "pending": false, + "nodes": [ + { + "name": "It", + "text": "focused", + "start": 314, + "end": 342, + "spec": true, + "focused": false, + "pending": false + } + ] + }, + { + "name": "FIt", + "text": "focused", + "start": 349, + "end": 377, + "spec": true, + "focused": true, + "pending": false + }, + { + "name": "FSpecify", + "text": "focused", + "start": 380, + "end": 413, + "spec": true, + "focused": true, + "pending": false + }, + { + "name": "FMeasure", + "text": "focused", + "start": 416, + "end": 465, + "spec": true, + "focused": true, + "pending": false + } + ] + } +] diff --git a/ginkgo/outline/_testdata/normal_test.go b/ginkgo/outline/_testdata/normal_test.go new file mode 100644 index 000000000..39cdd67e0 --- /dev/null +++ b/ginkgo/outline/_testdata/normal_test.go @@ -0,0 +1,48 @@ +package example_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" +) + +var _ = Describe("NormalFixture", func() { + Describe("normal", func() { + It("normal", func() { + + }) + }) + + Context("normal", func() { + It("normal", func() { + + }) + }) + + When("normal", func() { + It("normal", func() { + + }) + }) + + It("normal", func() { + + }) + + Specify("normal", func() { + + }) + + Measure("normal", func(b Benchmarker) { + + }, 2) + + DescribeTable("normal", + func() {}, + Entry("normal"), + ) + + DescribeTable("normal", + func() {}, + Entry("normal"), + ) +}) diff --git a/ginkgo/outline/_testdata/normal_test_outline.csv b/ginkgo/outline/_testdata/normal_test_outline.csv new file mode 100644 index 000000000..7938056b5 --- /dev/null +++ b/ginkgo/outline/_testdata/normal_test_outline.csv @@ -0,0 +1,11 @@ +Name,Text,Start,End,Spec,Focused,Pending +Describe,NormalFixture,116,574,false,false,false +Describe,normal,152,213,false,false,false +It,normal,182,209,true,false,false +Context,normal,216,276,false,false,false +It,normal,245,272,true,false,false +When,normal,279,336,false,false,false +It,normal,305,332,true,false,false +It,normal,339,365,true,false,false +Specify,normal,368,399,true,false,false +Measure,normal,402,449,true,false,false diff --git a/ginkgo/outline/_testdata/normal_test_outline.json b/ginkgo/outline/_testdata/normal_test_outline.json new file mode 100644 index 000000000..e81637f24 --- /dev/null +++ b/ginkgo/outline/_testdata/normal_test_outline.json @@ -0,0 +1,100 @@ +[ + { + "name": "Describe", + "text": "NormalFixture", + "start": 116, + "end": 574, + "spec": false, + "focused": false, + "pending": false, + "nodes": [ + { + "name": "Describe", + "text": "normal", + "start": 152, + "end": 213, + "spec": false, + "focused": false, + "pending": false, + "nodes": [ + { + "name": "It", + "text": "normal", + "start": 182, + "end": 209, + "spec": true, + "focused": false, + "pending": false + } + ] + }, + { + "name": "Context", + "text": "normal", + "start": 216, + "end": 276, + "spec": false, + "focused": false, + "pending": false, + "nodes": [ + { + "name": "It", + "text": "normal", + "start": 245, + "end": 272, + "spec": true, + "focused": false, + "pending": false + } + ] + }, + { + "name": "When", + "text": "normal", + "start": 279, + "end": 336, + "spec": false, + "focused": false, + "pending": false, + "nodes": [ + { + "name": "It", + "text": "normal", + "start": 305, + "end": 332, + "spec": true, + "focused": false, + "pending": false + } + ] + }, + { + "name": "It", + "text": "normal", + "start": 339, + "end": 365, + "spec": true, + "focused": false, + "pending": false + }, + { + "name": "Specify", + "text": "normal", + "start": 368, + "end": 399, + "spec": true, + "focused": false, + "pending": false + }, + { + "name": "Measure", + "text": "normal", + "start": 402, + "end": 449, + "spec": true, + "focused": false, + "pending": false + } + ] + } +] diff --git a/ginkgo/outline/_testdata/pending_test.go b/ginkgo/outline/_testdata/pending_test.go new file mode 100644 index 000000000..babd0078c --- /dev/null +++ b/ginkgo/outline/_testdata/pending_test.go @@ -0,0 +1,48 @@ +package example_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" +) + +var _ = Describe("PendingFixture", func() { + PDescribe("pending", func() { + It("pending", func() { + + }) + }) + + PContext("pending", func() { + It("pending", func() { + + }) + }) + + PWhen("pending", func() { + It("pending", func() { + + }) + }) + + PIt("pending", func() { + + }) + + PSpecify("pending", func() { + + }) + + PMeasure("pending", func(b Benchmarker) { + + }, 2) + + PDescribeTable("pending", + func() {}, + Entry("pending"), + ) + + DescribeTable("pending", + func() {}, + PEntry("pending"), + ) +}) diff --git a/ginkgo/outline/_testdata/pending_test_outline.csv b/ginkgo/outline/_testdata/pending_test_outline.csv new file mode 100644 index 000000000..f88cdbd09 --- /dev/null +++ b/ginkgo/outline/_testdata/pending_test_outline.csv @@ -0,0 +1,11 @@ +Name,Text,Start,End,Spec,Focused,Pending +Describe,PendingFixture,116,596,false,false,false +PDescribe,pending,153,217,false,false,true +It,pending,185,213,true,false,true +PContext,pending,220,283,false,false,true +It,pending,251,279,true,false,true +PWhen,pending,286,346,false,false,true +It,pending,314,342,true,false,true +PIt,pending,349,377,true,false,true +PSpecify,pending,380,413,true,false,true +PMeasure,pending,416,465,true,false,true diff --git a/ginkgo/outline/_testdata/pending_test_outline.json b/ginkgo/outline/_testdata/pending_test_outline.json new file mode 100644 index 000000000..719b1c96d --- /dev/null +++ b/ginkgo/outline/_testdata/pending_test_outline.json @@ -0,0 +1,100 @@ +[ + { + "name": "Describe", + "text": "PendingFixture", + "start": 116, + "end": 596, + "spec": false, + "focused": false, + "pending": false, + "nodes": [ + { + "name": "PDescribe", + "text": "pending", + "start": 153, + "end": 217, + "spec": false, + "focused": false, + "pending": true, + "nodes": [ + { + "name": "It", + "text": "pending", + "start": 185, + "end": 213, + "spec": true, + "focused": false, + "pending": true + } + ] + }, + { + "name": "PContext", + "text": "pending", + "start": 220, + "end": 283, + "spec": false, + "focused": false, + "pending": true, + "nodes": [ + { + "name": "It", + "text": "pending", + "start": 251, + "end": 279, + "spec": true, + "focused": false, + "pending": true + } + ] + }, + { + "name": "PWhen", + "text": "pending", + "start": 286, + "end": 346, + "spec": false, + "focused": false, + "pending": true, + "nodes": [ + { + "name": "It", + "text": "pending", + "start": 314, + "end": 342, + "spec": true, + "focused": false, + "pending": true + } + ] + }, + { + "name": "PIt", + "text": "pending", + "start": 349, + "end": 377, + "spec": true, + "focused": false, + "pending": true + }, + { + "name": "PSpecify", + "text": "pending", + "start": 380, + "end": 413, + "spec": true, + "focused": false, + "pending": true + }, + { + "name": "PMeasure", + "text": "pending", + "start": 416, + "end": 465, + "spec": true, + "focused": false, + "pending": true + } + ] + } +] diff --git a/ginkgo/outline/_testdata/suite_test.go b/ginkgo/outline/_testdata/suite_test.go new file mode 100644 index 000000000..e28786a5a --- /dev/null +++ b/ginkgo/outline/_testdata/suite_test.go @@ -0,0 +1,13 @@ +package example_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestExample(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Example Suite") +} diff --git a/ginkgo/outline/_testdata/suite_test_outline.json b/ginkgo/outline/_testdata/suite_test_outline.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/ginkgo/outline/_testdata/suite_test_outline.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/ginkgo/outline/outline.go b/ginkgo/outline/outline.go new file mode 100644 index 000000000..33c6f6333 --- /dev/null +++ b/ginkgo/outline/outline.go @@ -0,0 +1,194 @@ +package outline + +import ( + "encoding/json" + "fmt" + "go/ast" + "go/token" + "strconv" + "strings" + + "golang.org/x/tools/go/ast/inspector" +) + +const ( + // undefinedTextAlt is used if the spec/container text cannot be derived + undefinedTextAlt = "undefined" +) + +// ginkgoMetadata holds useful bits of information for every entry in the outline +type ginkgoMetadata struct { + // Name is the spec or container function name, e.g. `Describe` or `It` + Name string `json:"name"` + + // Text is the `text` argument passed to specs, and some containers + Text string `json:"text"` + + // Start is the position of first character of the spec or container block + Start token.Pos `json:"start"` + + // End is the position of first character immediately after the spec or container block + End token.Pos `json:"end"` + + Spec bool `json:"spec"` + Focused bool `json:"focused"` + Pending bool `json:"pending"` +} + +// ginkgoNode is used to construct the outline as a tree +type ginkgoNode struct { + ginkgoMetadata + Nodes []*ginkgoNode `json:"nodes,omitempty"` +} + +type walkFunc func(n *ginkgoNode) + +func (n *ginkgoNode) Walk(f walkFunc) { + f(n) + for _, m := range n.Nodes { + m.Walk(f) + } +} + +// 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 { + return nil, false + } + + n := ginkgoNode{} + 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 + n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) + case "FIt", "FMeasure", "FSpecify": + n.Spec = true + n.Focused = true + n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) + case "PIt", "PMeasure", "PSpecify", "XIt", "XMeasure", "XSpecify": + n.Spec = true + n.Pending = true + n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) + case "Context", "Describe", "When": + n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) + case "FContext", "FDescribe", "FWhen": + n.Focused = true + n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) + case "PContext", "PDescribe", "PWhen", "XContext", "XDescribe", "XWhen": + n.Pending = true + n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) + case "By": + case "AfterEach", "BeforeEach": + case "JustAfterEach", "JustBeforeEach": + case "AfterSuite", "BeforeSuite": + case "SynchronizedAfterSuite", "SynchronizedBeforeSuite": + default: + return nil, false + } + return &n, true +} + +// textOrAltFromCallExpr tries to derive the "text" of a Ginkgo spec or +// container. If it cannot derive it, it returns the alt text. +func textOrAltFromCallExpr(ce *ast.CallExpr, alt string) string { + text, defined := textFromCallExpr(ce) + if !defined { + return alt + } + return text +} + +// textFromCallExpr tries to derive the "text" of a Ginkgo spec or container. If +// it cannot derive it, it returns false. +func textFromCallExpr(ce *ast.CallExpr) (string, bool) { + if len(ce.Args) < 1 { + return "", false + } + text, ok := ce.Args[0].(*ast.BasicLit) + if !ok { + return "", false + } + switch text.Kind { + case token.CHAR, token.STRING: + // For token.CHAR and token.STRING, Value is quoted + unquoted, err := strconv.Unquote(text.Value) + if err != nil { + // If unquoting fails, just use the raw Value + return text.Value, true + } + return unquoted, true + default: + return text.Value, true + } +} + +// FromASTFile returns an outline for a Ginkgo test source file +func FromASTFile(src *ast.File) (*outline, error) { + root := ginkgoNode{ + Nodes: []*ginkgoNode{}, + } + stack := []*ginkgoNode{&root} + + ispr := inspector.New([]*ast.File{src}) + ispr.Nodes([]ast.Node{(*ast.CallExpr)(nil)}, func(node ast.Node, push bool) bool { + ce, ok := node.(*ast.CallExpr) + if !ok { + // Because `Nodes` calls this function only when the node is an + // 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) + if !ok { + // Not a Ginkgo call, continue + return true + } + + // Visiting this node on the way down + if push { + parent := stack[len(stack)-1] + if parent.Pending { + gn.Pending = true + } + // TODO: Update focused based on ginkgo behavior: + // > Nested programmatically focused specs follow a simple rule: if + // > a leaf-node is marked focused, any of its ancestor nodes that + // > are marked focus will be unfocused. + parent.Nodes = append(parent.Nodes, gn) + + stack = append(stack, gn) + return true + } + // Visiting node on the way up + stack = stack[0 : len(stack)-1] + return true + }) + + return (*outline)(&root), nil +} + +type outline ginkgoNode + +func (o *outline) MarshalJSON() ([]byte, error) { + return json.Marshal(o.Nodes) +} + +// String returns a CSV-formatted outline. Spec or container are output in +// depth-first order. +func (o *outline) String() string { + var b strings.Builder + b.WriteString("Name,Text,Start,End,Spec,Focused,Pending\n") + f := func(n *ginkgoNode) { + b.WriteString(fmt.Sprintf("%s,%s,%d,%d,%t,%t,%t\n", n.Name, n.Text, n.Start, n.End, n.Spec, n.Focused, n.Pending)) + } + for _, n := range o.Nodes { + n.Walk(f) + } + return b.String() +} diff --git a/ginkgo/outline/outline_suite_test.go b/ginkgo/outline/outline_suite_test.go new file mode 100644 index 000000000..4cceae9f6 --- /dev/null +++ b/ginkgo/outline/outline_suite_test.go @@ -0,0 +1,13 @@ +package outline_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestOutline(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Outline Suite") +} diff --git a/ginkgo/outline/outline_test.go b/ginkgo/outline/outline_test.go new file mode 100644 index 000000000..4d6b62d46 --- /dev/null +++ b/ginkgo/outline/outline_test.go @@ -0,0 +1,46 @@ +package outline + +import ( + "encoding/json" + "go/parser" + "go/token" + "io/ioutil" + "log" + "path/filepath" + + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +var _ = DescribeTable("Validate outline from file with", + func(srcFilename, jsonOutlineFilename, csvOutlineFilename string) { + fset := token.NewFileSet() + astFile, err := parser.ParseFile(fset, filepath.Join("_testdata", srcFilename), nil, 0) + Expect(err).To(BeNil(), "error parsing source: %s", err) + + if err != nil { + log.Fatalf("error parsing source: %s", err) + } + + o, err := FromASTFile(astFile) + Expect(err).To(BeNil(), "error creating outline: %s", err) + + gotJSON, err := json.MarshalIndent(o, "", " ") + Expect(err).To(BeNil(), "error marshalling outline to json: %s", err) + + wantJSON, err := ioutil.ReadFile(filepath.Join("_testdata", jsonOutlineFilename)) + Expect(err).To(BeNil(), "error reading JSON outline fixture: %s", err) + + Expect(gotJSON).To(MatchJSON(wantJSON)) + + gotCSV := o.String() + + wantCSV, err := ioutil.ReadFile(filepath.Join("_testdata", csvOutlineFilename)) + Expect(err).To(BeNil(), "error reading CSV outline fixture: %s", err) + + Expect(gotCSV).To(Equal(string(wantCSV))) + }, + 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"), +) diff --git a/ginkgo/outline_command.go b/ginkgo/outline_command.go new file mode 100644 index 000000000..bc51514c4 --- /dev/null +++ b/ginkgo/outline_command.go @@ -0,0 +1,70 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "go/parser" + "go/token" + "os" + + "github.com/onsi/ginkgo/ginkgo/outline" +) + +func BuildOutlineCommand() *Command { + const defaultFormat = "csv" + var format string + flagSet := flag.NewFlagSet("outline", flag.ExitOnError) + flagSet.StringVar(&format, "format", defaultFormat, "Format of outline. Accepted: 'csv', 'json'") + return &Command{ + Name: "outline", + FlagSet: flagSet, + UsageCommand: "ginkgo outline ", + Usage: []string{ + "Outline of Ginkgo symbols for the file", + }, + Command: func(args []string, additionalArgs []string) { + outlineFile(args, format) + }, + } +} + +func outlineFile(args []string, format string) { + if len(args) != 1 { + println("usage: ginkgo outline ") + os.Exit(1) + } + + filename := args[0] + fset := token.NewFileSet() + + src, err := parser.ParseFile(fset, filename, nil, 0) + if err != nil { + println(fmt.Sprintf("error parsing source: %s", err)) + os.Exit(1) + } + + o, err := outline.FromASTFile(src) + if err != nil { + println(fmt.Sprintf("error creating outline: %s", err)) + os.Exit(1) + } + + var oerr error + switch format { + case "csv": + _, oerr = fmt.Print(o) + case "json": + b, err := json.Marshal(o) + if err != nil { + println(fmt.Sprintf("error marshalling to json: %s", err)) + } + _, oerr = fmt.Println(string(b)) + default: + complainAndQuit(fmt.Sprintf("format %s not accepted", format)) + } + if oerr != nil { + println(fmt.Sprintf("error writing outline: %s", oerr)) + os.Exit(1) + } +} diff --git a/go.mod b/go.mod index 1f7125228..f6a09193a 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ require ( github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/nxadm/tail v1.4.4 github.com/onsi/gomega v1.10.1 - golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 - golang.org/x/text v0.3.2 // indirect + golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f + golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e ) go 1.13 diff --git a/go.sum b/go.sum index e14753507..56b1f9db1 100644 --- a/go.sum +++ b/go.sum @@ -21,25 +21,41 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From bdc78da2aa5223470662d32709b09a4acc775a91 Mon Sep 17 00:00:00 2001 From: Daniel Lipovetsky Date: Sat, 26 Dec 2020 14:01:21 -0800 Subject: [PATCH 02/13] outline: Add support for nodot and alias import of the ginkgo package --- ginkgo/outline/_testdata/alias_test.go | 47 ++++++++++++++ .../outline/_testdata/alias_test_outline.csv | 11 ++++ .../outline/_testdata/alias_test_outline.json | 1 + ginkgo/outline/_testdata/nodot_test.go | 47 ++++++++++++++ .../outline/_testdata/nodot_test_outline.csv | 11 ++++ .../outline/_testdata/nodot_test_outline.json | 1 + ginkgo/outline/import.go | 61 +++++++++++++++++++ ginkgo/outline/outline.go | 35 +++++++++-- ginkgo/outline/outline_test.go | 2 + 9 files changed, 210 insertions(+), 6 deletions(-) create mode 100644 ginkgo/outline/_testdata/alias_test.go create mode 100644 ginkgo/outline/_testdata/alias_test_outline.csv create mode 100644 ginkgo/outline/_testdata/alias_test_outline.json create mode 100644 ginkgo/outline/_testdata/nodot_test.go create mode 100644 ginkgo/outline/_testdata/nodot_test_outline.csv create mode 100644 ginkgo/outline/_testdata/nodot_test_outline.json create mode 100644 ginkgo/outline/import.go 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"), From d6352fbb59764169734edfa91c1a0908258d5d52 Mon Sep 17 00:00:00 2001 From: Daniel Lipovetsky Date: Sat, 26 Dec 2020 16:39:58 -0800 Subject: [PATCH 03/13] outline: During a post-order traversal of an AST Node, do not derive its ginkgo metadata The post-order traversal needs to check whether the AST Node is a ginkgo node. That can be done by comparing the positions of the AST Node and the last visited ginkgo node. Deriving the ginkgo metadata is not necessary. --- ginkgo/outline/outline.go | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/ginkgo/outline/outline.go b/ginkgo/outline/outline.go index c4fef57c4..b050e2424 100644 --- a/ginkgo/outline/outline.go +++ b/ginkgo/outline/outline.go @@ -161,20 +161,20 @@ func FromASTFile(src *ast.File) (*outline, error) { ispr := inspector.New([]*ast.File{src}) ispr.Nodes([]ast.Node{(*ast.CallExpr)(nil)}, func(node ast.Node, push bool) bool { - ce, ok := node.(*ast.CallExpr) - if !ok { - // Because `Nodes` calls this function only when the node is an - // 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, ginkgoImportName) - if !ok { - // Not a Ginkgo call, continue - return true - } - - // Visiting this node on the way down if push { + // Visiting this node on the way down + ce, ok := node.(*ast.CallExpr) + if !ok { + // Because `Nodes` calls this function only when the node is an + // 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, ginkgoImportName) + if !ok { + // Node is not a Ginkgo spec or container, continue + return true + } + parent := stack[len(stack)-1] if parent.Pending { gn.Pending = true @@ -189,6 +189,11 @@ func FromASTFile(src *ast.File) (*outline, error) { return true } // Visiting node on the way up + lastVisitedGinkgoNode := stack[len(stack)-1] + if node.Pos() != lastVisitedGinkgoNode.Start || node.End() != lastVisitedGinkgoNode.End { + // Node is not a Ginkgo spec or container, so it was not pushed onto the stack, continue + return true + } stack = stack[0 : len(stack)-1] return true }) From a629f65ebcea3ff69e5c0f0ae6f13c875fe7407c Mon Sep 17 00:00:00 2001 From: Daniel Lipovetsky Date: Sat, 26 Dec 2020 21:06:49 -0800 Subject: [PATCH 04/13] outline: Add backpropagation of unfocus, propagation of inherited focus/pending properties --- ginkgo/outline/_testdata/focused_test.go | 2 +- .../_testdata/focused_test_outline.csv | 20 ++-- .../_testdata/focused_test_outline.json | 101 +----------------- ginkgo/outline/_testdata/mixed_test.go | 43 ++++++++ .../outline/_testdata/mixed_test_outline.csv | 14 +++ .../outline/_testdata/mixed_test_outline.json | 1 + .../outline/_testdata/nestedfocused_test.go | 34 ++++++ .../_testdata/nestedfocused_test_outline.csv | 11 ++ .../_testdata/nestedfocused_test_outline.json | 1 + ginkgo/outline/outline.go | 58 +++++++--- ginkgo/outline/outline_test.go | 2 + 11 files changed, 164 insertions(+), 123 deletions(-) create mode 100644 ginkgo/outline/_testdata/mixed_test.go create mode 100644 ginkgo/outline/_testdata/mixed_test_outline.csv create mode 100644 ginkgo/outline/_testdata/mixed_test_outline.json create mode 100644 ginkgo/outline/_testdata/nestedfocused_test.go create mode 100644 ginkgo/outline/_testdata/nestedfocused_test_outline.csv create mode 100644 ginkgo/outline/_testdata/nestedfocused_test_outline.json diff --git a/ginkgo/outline/_testdata/focused_test.go b/ginkgo/outline/_testdata/focused_test.go index 3e56b581f..7900d2167 100644 --- a/ginkgo/outline/_testdata/focused_test.go +++ b/ginkgo/outline/_testdata/focused_test.go @@ -5,7 +5,7 @@ import ( . "github.com/onsi/ginkgo/extensions/table" ) -var _ = Describe("FocusedFixture", func() { +var _ = Describe("unfocused", func() { FDescribe("focused", func() { It("focused", func() { diff --git a/ginkgo/outline/_testdata/focused_test_outline.csv b/ginkgo/outline/_testdata/focused_test_outline.csv index 49bddba88..3a8ef5702 100644 --- a/ginkgo/outline/_testdata/focused_test_outline.csv +++ b/ginkgo/outline/_testdata/focused_test_outline.csv @@ -1,11 +1,11 @@ Name,Text,Start,End,Spec,Focused,Pending -Describe,FocusedFixture,116,596,false,false,false -FDescribe,focused,153,217,false,true,false -It,focused,185,213,true,false,false -FContext,focused,220,283,false,true,false -It,focused,251,279,true,false,false -FWhen,focused,286,346,false,true,false -It,focused,314,342,true,false,false -FIt,focused,349,377,true,true,false -FSpecify,focused,380,413,true,true,false -FMeasure,focused,416,465,true,true,false +Describe,unfocused,116,591,false,false,false +FDescribe,focused,148,212,false,true,false +It,focused,180,208,true,true,false +FContext,focused,215,278,false,true,false +It,focused,246,274,true,true,false +FWhen,focused,281,341,false,true,false +It,focused,309,337,true,true,false +FIt,focused,344,372,true,true,false +FSpecify,focused,375,408,true,true,false +FMeasure,focused,411,460,true,true,false diff --git a/ginkgo/outline/_testdata/focused_test_outline.json b/ginkgo/outline/_testdata/focused_test_outline.json index f265139b5..2e0bb6dd8 100644 --- a/ginkgo/outline/_testdata/focused_test_outline.json +++ b/ginkgo/outline/_testdata/focused_test_outline.json @@ -1,100 +1 @@ -[ - { - "name": "Describe", - "text": "FocusedFixture", - "start": 116, - "end": 596, - "spec": false, - "focused": false, - "pending": false, - "nodes": [ - { - "name": "FDescribe", - "text": "focused", - "start": 153, - "end": 217, - "spec": false, - "focused": true, - "pending": false, - "nodes": [ - { - "name": "It", - "text": "focused", - "start": 185, - "end": 213, - "spec": true, - "focused": false, - "pending": false - } - ] - }, - { - "name": "FContext", - "text": "focused", - "start": 220, - "end": 283, - "spec": false, - "focused": true, - "pending": false, - "nodes": [ - { - "name": "It", - "text": "focused", - "start": 251, - "end": 279, - "spec": true, - "focused": false, - "pending": false - } - ] - }, - { - "name": "FWhen", - "text": "focused", - "start": 286, - "end": 346, - "spec": false, - "focused": true, - "pending": false, - "nodes": [ - { - "name": "It", - "text": "focused", - "start": 314, - "end": 342, - "spec": true, - "focused": false, - "pending": false - } - ] - }, - { - "name": "FIt", - "text": "focused", - "start": 349, - "end": 377, - "spec": true, - "focused": true, - "pending": false - }, - { - "name": "FSpecify", - "text": "focused", - "start": 380, - "end": 413, - "spec": true, - "focused": true, - "pending": false - }, - { - "name": "FMeasure", - "text": "focused", - "start": 416, - "end": 465, - "spec": true, - "focused": true, - "pending": false - } - ] - } -] +[{"name":"Describe","text":"unfocused","start":116,"end":591,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"FDescribe","text":"focused","start":148,"end":212,"spec":false,"focused":true,"pending":false,"nodes":[{"name":"It","text":"focused","start":180,"end":208,"spec":true,"focused":true,"pending":false}]},{"name":"FContext","text":"focused","start":215,"end":278,"spec":false,"focused":true,"pending":false,"nodes":[{"name":"It","text":"focused","start":246,"end":274,"spec":true,"focused":true,"pending":false}]},{"name":"FWhen","text":"focused","start":281,"end":341,"spec":false,"focused":true,"pending":false,"nodes":[{"name":"It","text":"focused","start":309,"end":337,"spec":true,"focused":true,"pending":false}]},{"name":"FIt","text":"focused","start":344,"end":372,"spec":true,"focused":true,"pending":false},{"name":"FSpecify","text":"focused","start":375,"end":408,"spec":true,"focused":true,"pending":false},{"name":"FMeasure","text":"focused","start":411,"end":460,"spec":true,"focused":true,"pending":false}]}] diff --git a/ginkgo/outline/_testdata/mixed_test.go b/ginkgo/outline/_testdata/mixed_test.go new file mode 100644 index 000000000..af538e01c --- /dev/null +++ b/ginkgo/outline/_testdata/mixed_test.go @@ -0,0 +1,43 @@ +package example_test + +import ( + . "github.com/onsi/ginkgo" +) + +var _ = FDescribe("unfocused", func() { + FContext("unfocused", func() { + It("unfocused", func() { + + }) + FIt("focused", func() { + + }) + }) + + Context("unfocused", func() { + FIt("focused", func() { + + }) + It("unfocused", func() { + + }) + }) + + FContext("focused", func() { + It("focused", func() { + + }) + It("focused", func() { + + }) + }) + + PContext("unfocused", func() { + FIt("unfocused", func() { + + }) + It("unfocused", func() { + + }) + }) +}) diff --git a/ginkgo/outline/_testdata/mixed_test_outline.csv b/ginkgo/outline/_testdata/mixed_test_outline.csv new file mode 100644 index 000000000..b66aa4d5e --- /dev/null +++ b/ginkgo/outline/_testdata/mixed_test_outline.csv @@ -0,0 +1,14 @@ +Name,Text,Start,End,Spec,Focused,Pending +FDescribe,unfocused,71,508,false,false,false +FContext,unfocused,104,203,false,false,false +It,unfocused,137,167,true,false,false +FIt,focused,170,199,true,true,false +Context,unfocused,206,304,false,false,false +FIt,focused,238,267,true,true,false +It,unfocused,270,300,true,false,false +FContext,focused,307,401,false,true,false +It,focused,338,366,true,true,false +It,focused,369,397,true,true,false +PContext,unfocused,404,505,false,false,true +FIt,unfocused,437,468,true,false,true +It,unfocused,471,501,true,false,true diff --git a/ginkgo/outline/_testdata/mixed_test_outline.json b/ginkgo/outline/_testdata/mixed_test_outline.json new file mode 100644 index 000000000..b24ff06c5 --- /dev/null +++ b/ginkgo/outline/_testdata/mixed_test_outline.json @@ -0,0 +1 @@ +[{"name":"FDescribe","text":"unfocused","start":71,"end":508,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"FContext","text":"unfocused","start":104,"end":203,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"unfocused","start":137,"end":167,"spec":true,"focused":false,"pending":false},{"name":"FIt","text":"focused","start":170,"end":199,"spec":true,"focused":true,"pending":false}]},{"name":"Context","text":"unfocused","start":206,"end":304,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"FIt","text":"focused","start":238,"end":267,"spec":true,"focused":true,"pending":false},{"name":"It","text":"unfocused","start":270,"end":300,"spec":true,"focused":false,"pending":false}]},{"name":"FContext","text":"focused","start":307,"end":401,"spec":false,"focused":true,"pending":false,"nodes":[{"name":"It","text":"focused","start":338,"end":366,"spec":true,"focused":true,"pending":false},{"name":"It","text":"focused","start":369,"end":397,"spec":true,"focused":true,"pending":false}]},{"name":"PContext","text":"unfocused","start":404,"end":505,"spec":false,"focused":false,"pending":true,"nodes":[{"name":"FIt","text":"unfocused","start":437,"end":468,"spec":true,"focused":false,"pending":true},{"name":"It","text":"unfocused","start":471,"end":501,"spec":true,"focused":false,"pending":true}]}]}] diff --git a/ginkgo/outline/_testdata/nestedfocused_test.go b/ginkgo/outline/_testdata/nestedfocused_test.go new file mode 100644 index 000000000..51c558202 --- /dev/null +++ b/ginkgo/outline/_testdata/nestedfocused_test.go @@ -0,0 +1,34 @@ +package example_test + +import ( + . "github.com/onsi/ginkgo" +) + +var _ = FDescribe("unfocused", func() { + FContext("unfocused", func() { + It("unfocused", func() { + + }) + FIt("focused", func() { + + }) + }) + + Context("unfocused", func() { + FIt("focused", func() { + + }) + It("unfocused", func() { + + }) + }) + + FContext("focused", func() { + It("focused", func() { + + }) + It("focused", func() { + + }) + }) +}) diff --git a/ginkgo/outline/_testdata/nestedfocused_test_outline.csv b/ginkgo/outline/_testdata/nestedfocused_test_outline.csv new file mode 100644 index 000000000..f6b4fd09f --- /dev/null +++ b/ginkgo/outline/_testdata/nestedfocused_test_outline.csv @@ -0,0 +1,11 @@ +Name,Text,Start,End,Spec,Focused,Pending +FDescribe,unfocused,71,404,false,false,false +FContext,unfocused,104,203,false,false,false +It,unfocused,137,167,true,false,false +FIt,focused,170,199,true,true,false +Context,unfocused,206,304,false,false,false +FIt,focused,238,267,true,true,false +It,unfocused,270,300,true,false,false +FContext,focused,307,401,false,true,false +It,focused,338,366,true,true,false +It,focused,369,397,true,true,false diff --git a/ginkgo/outline/_testdata/nestedfocused_test_outline.json b/ginkgo/outline/_testdata/nestedfocused_test_outline.json new file mode 100644 index 000000000..8e317e5ee --- /dev/null +++ b/ginkgo/outline/_testdata/nestedfocused_test_outline.json @@ -0,0 +1 @@ +[{"name":"FDescribe","text":"unfocused","start":71,"end":404,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"FContext","text":"unfocused","start":104,"end":203,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"unfocused","start":137,"end":167,"spec":true,"focused":false,"pending":false},{"name":"FIt","text":"focused","start":170,"end":199,"spec":true,"focused":true,"pending":false}]},{"name":"Context","text":"unfocused","start":206,"end":304,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"FIt","text":"focused","start":238,"end":267,"spec":true,"focused":true,"pending":false},{"name":"It","text":"unfocused","start":270,"end":300,"spec":true,"focused":false,"pending":false}]},{"name":"FContext","text":"focused","start":307,"end":401,"spec":false,"focused":true,"pending":false,"nodes":[{"name":"It","text":"focused","start":338,"end":366,"spec":true,"focused":true,"pending":false},{"name":"It","text":"focused","start":369,"end":397,"spec":true,"focused":true,"pending":false}]}]}] diff --git a/ginkgo/outline/outline.go b/ginkgo/outline/outline.go index b050e2424..13fe8ae19 100644 --- a/ginkgo/outline/outline.go +++ b/ginkgo/outline/outline.go @@ -45,13 +45,20 @@ type ginkgoNode struct { type walkFunc func(n *ginkgoNode) -func (n *ginkgoNode) Walk(f walkFunc) { +func (n *ginkgoNode) PreOrder(f walkFunc) { f(n) for _, m := range n.Nodes { - m.Walk(f) + m.PreOrder(f) } } +func (n *ginkgoNode) PostOrder(f walkFunc) { + for _, m := range n.Nodes { + m.PostOrder(f) + } + f(n) +} + // ginkgoNodeFromCallExpr derives an outline entry from a go AST subtree // corresponding to a Ginkgo container or spec. func ginkgoNodeFromCallExpr(ce *ast.CallExpr, ginkgoImportName string) (*ginkgoNode, bool) { @@ -162,7 +169,7 @@ func FromASTFile(src *ast.File) (*outline, error) { ispr := inspector.New([]*ast.File{src}) ispr.Nodes([]ast.Node{(*ast.CallExpr)(nil)}, func(node ast.Node, push bool) bool { if push { - // Visiting this node on the way down + // Pre-order traversal ce, ok := node.(*ast.CallExpr) if !ok { // Because `Nodes` calls this function only when the node is an @@ -176,19 +183,12 @@ func FromASTFile(src *ast.File) (*outline, error) { } parent := stack[len(stack)-1] - if parent.Pending { - gn.Pending = true - } - // TODO: Update focused based on ginkgo behavior: - // > Nested programmatically focused specs follow a simple rule: if - // > a leaf-node is marked focused, any of its ancestor nodes that - // > are marked focus will be unfocused. parent.Nodes = append(parent.Nodes, gn) stack = append(stack, gn) return true } - // Visiting node on the way up + // Post-order traversal lastVisitedGinkgoNode := stack[len(stack)-1] if node.Pos() != lastVisitedGinkgoNode.Start || node.End() != lastVisitedGinkgoNode.End { // Node is not a Ginkgo spec or container, so it was not pushed onto the stack, continue @@ -198,6 +198,40 @@ func FromASTFile(src *ast.File) (*outline, error) { return true }) + // Derive authoritative focus by applying this rule: + // > Nested programmatically focused specs follow a simple rule: if a + // > leaf-node is marked focused, any of its ancestor nodes that are marked + // > focus will be unfocused. + focusedSpecInSubtreeStack := []bool{} + root.PostOrder(func(n *ginkgoNode) { + if n.Spec { + focusedSpecInSubtreeStack = append(focusedSpecInSubtreeStack, n.Focused) + return + } + focusedSpecInSubtree := false + for range n.Nodes { + focusedSpecInSubtree = focusedSpecInSubtree || focusedSpecInSubtreeStack[len(focusedSpecInSubtreeStack)-1] + focusedSpecInSubtreeStack = focusedSpecInSubtreeStack[0 : len(focusedSpecInSubtreeStack)-1] + } + focusedSpecInSubtreeStack = append(focusedSpecInSubtreeStack, focusedSpecInSubtree) + if focusedSpecInSubtree { + n.Focused = false + } + }) + + // Propagate focus and pending. + root.PreOrder(func(n *ginkgoNode) { + for _, m := range n.Nodes { + if n.Pending { + m.Pending = true + m.Focused = false + } + if n.Focused && !m.Pending { + m.Focused = true + } + } + }) + return (*outline)(&root), nil } @@ -216,7 +250,7 @@ func (o *outline) String() string { b.WriteString(fmt.Sprintf("%s,%s,%d,%d,%t,%t,%t\n", n.Name, n.Text, n.Start, n.End, n.Spec, n.Focused, n.Pending)) } for _, n := range o.Nodes { - n.Walk(f) + n.PreOrder(f) } return b.String() } diff --git a/ginkgo/outline/outline_test.go b/ginkgo/outline/outline_test.go index 6036459a1..afb5fda65 100644 --- a/ginkgo/outline/outline_test.go +++ b/ginkgo/outline/outline_test.go @@ -45,4 +45,6 @@ var _ = DescribeTable("Validate outline from file with", 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"), + Entry("nested focused containers and specs", "nestedfocused_test.go", "nestedfocused_test_outline.json", "nestedfocused_test_outline.csv"), + Entry("mixed focused containers and specs", "mixed_test.go", "mixed_test_outline.json", "mixed_test_outline.csv"), ) From c85d63eda6d9b15b6e4074f7d8591c112cf611f2 Mon Sep 17 00:00:00 2001 From: Daniel Lipovetsky Date: Mon, 28 Dec 2020 16:41:10 -0800 Subject: [PATCH 05/13] outline: Add instructions and script for creating/updating sample test results To make it easier to maintain the outline tests. --- ginkgo/outline/_testdata/create_result.sh | 17 +++++++++++++++++ ginkgo/outline/outline_test.go | 8 ++++++++ 2 files changed, 25 insertions(+) create mode 100644 ginkgo/outline/_testdata/create_result.sh diff --git a/ginkgo/outline/_testdata/create_result.sh b/ginkgo/outline/_testdata/create_result.sh new file mode 100644 index 000000000..357690cf3 --- /dev/null +++ b/ginkgo/outline/_testdata/create_result.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -o errexit +set -o nounset + +GINKGO=${GINKGO:-ginkgo} + +input=${1:-""} +for format in "csv" "json"; do + set -o xtrace + output="$(dirname $input)/$(basename $input).$format" + tmp=$(mktemp ginkgo-outline-test.XXX) + if "$GINKGO" outline --format="$format" "$input" 1>"$tmp" + then mv "$tmp" "$output" + else rm "$tmp" + set +o xtrace + fi +done diff --git a/ginkgo/outline/outline_test.go b/ginkgo/outline/outline_test.go index afb5fda65..1181add89 100644 --- a/ginkgo/outline/outline_test.go +++ b/ginkgo/outline/outline_test.go @@ -40,6 +40,14 @@ var _ = DescribeTable("Validate outline from file with", Expect(gotCSV).To(Equal(string(wantCSV))) }, + // To add a test: + // 1. Create the input, e.g., `myspecialcase_test.go` + // 2. Create the sample CSV and JSON results: Run `bash ./_testdata/create_result.sh ./_testdata/myspecialcase_test.go` + // 3. Add an Entry below, by copying an existing one, and substituting `myspecialcase` where needed. + // To re-create the sample results for a test: + // 1. Run `bash ./_testdata/create_result.sh ./testdata/myspecialcase_test.go` + // To re-create the sample results for all tests: + // 1. Run `for name in ./_testdata/*_test.go; do bash ./_testdata/create_result.sh $name; done` 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"), From 245e360856f2d81fd48fcd9ab4778080a44b5a5f Mon Sep 17 00:00:00 2001 From: Daniel Lipovetsky Date: Mon, 28 Dec 2020 17:16:45 -0800 Subject: [PATCH 06/13] outline: Use script to re-create result samples for all tests The content has not changed; only the JSON formatting and the filenames have changed. --- ...ias_test_outline.csv => alias_test.go.csv} | 0 ...s_test_outline.json => alias_test.go.json} | 0 ...d_test_outline.csv => focused_test.go.csv} | 0 ...test_outline.json => focused_test.go.json} | 0 ...xed_test_outline.csv => mixed_test.go.csv} | 0 ...d_test_outline.json => mixed_test.go.json} | 0 ..._outline.csv => nestedfocused_test.go.csv} | 0 ...utline.json => nestedfocused_test.go.json} | 0 ...dot_test_outline.csv => nodot_test.go.csv} | 0 ...t_test_outline.json => nodot_test.go.json} | 0 ...al_test_outline.csv => normal_test.go.csv} | 0 ginkgo/outline/_testdata/normal_test.go.json | 1 + .../_testdata/normal_test_outline.json | 100 ------------------ ...g_test_outline.csv => pending_test.go.csv} | 0 ginkgo/outline/_testdata/pending_test.go.json | 1 + .../_testdata/pending_test_outline.json | 100 ------------------ ginkgo/outline/_testdata/suite_test.go.csv | 1 + ginkgo/outline/_testdata/suite_test.go.json | 1 + .../outline/_testdata/suite_test_outline.json | 1 - ginkgo/outline/outline_test.go | 14 +-- 20 files changed, 11 insertions(+), 208 deletions(-) rename ginkgo/outline/_testdata/{alias_test_outline.csv => alias_test.go.csv} (100%) rename ginkgo/outline/_testdata/{alias_test_outline.json => alias_test.go.json} (100%) rename ginkgo/outline/_testdata/{focused_test_outline.csv => focused_test.go.csv} (100%) rename ginkgo/outline/_testdata/{focused_test_outline.json => focused_test.go.json} (100%) rename ginkgo/outline/_testdata/{mixed_test_outline.csv => mixed_test.go.csv} (100%) rename ginkgo/outline/_testdata/{mixed_test_outline.json => mixed_test.go.json} (100%) rename ginkgo/outline/_testdata/{nestedfocused_test_outline.csv => nestedfocused_test.go.csv} (100%) rename ginkgo/outline/_testdata/{nestedfocused_test_outline.json => nestedfocused_test.go.json} (100%) rename ginkgo/outline/_testdata/{nodot_test_outline.csv => nodot_test.go.csv} (100%) rename ginkgo/outline/_testdata/{nodot_test_outline.json => nodot_test.go.json} (100%) rename ginkgo/outline/_testdata/{normal_test_outline.csv => normal_test.go.csv} (100%) create mode 100644 ginkgo/outline/_testdata/normal_test.go.json delete mode 100644 ginkgo/outline/_testdata/normal_test_outline.json rename ginkgo/outline/_testdata/{pending_test_outline.csv => pending_test.go.csv} (100%) create mode 100644 ginkgo/outline/_testdata/pending_test.go.json delete mode 100644 ginkgo/outline/_testdata/pending_test_outline.json create mode 100644 ginkgo/outline/_testdata/suite_test.go.csv create mode 100644 ginkgo/outline/_testdata/suite_test.go.json delete mode 100644 ginkgo/outline/_testdata/suite_test_outline.json diff --git a/ginkgo/outline/_testdata/alias_test_outline.csv b/ginkgo/outline/_testdata/alias_test.go.csv similarity index 100% rename from ginkgo/outline/_testdata/alias_test_outline.csv rename to ginkgo/outline/_testdata/alias_test.go.csv diff --git a/ginkgo/outline/_testdata/alias_test_outline.json b/ginkgo/outline/_testdata/alias_test.go.json similarity index 100% rename from ginkgo/outline/_testdata/alias_test_outline.json rename to ginkgo/outline/_testdata/alias_test.go.json diff --git a/ginkgo/outline/_testdata/focused_test_outline.csv b/ginkgo/outline/_testdata/focused_test.go.csv similarity index 100% rename from ginkgo/outline/_testdata/focused_test_outline.csv rename to ginkgo/outline/_testdata/focused_test.go.csv diff --git a/ginkgo/outline/_testdata/focused_test_outline.json b/ginkgo/outline/_testdata/focused_test.go.json similarity index 100% rename from ginkgo/outline/_testdata/focused_test_outline.json rename to ginkgo/outline/_testdata/focused_test.go.json diff --git a/ginkgo/outline/_testdata/mixed_test_outline.csv b/ginkgo/outline/_testdata/mixed_test.go.csv similarity index 100% rename from ginkgo/outline/_testdata/mixed_test_outline.csv rename to ginkgo/outline/_testdata/mixed_test.go.csv diff --git a/ginkgo/outline/_testdata/mixed_test_outline.json b/ginkgo/outline/_testdata/mixed_test.go.json similarity index 100% rename from ginkgo/outline/_testdata/mixed_test_outline.json rename to ginkgo/outline/_testdata/mixed_test.go.json diff --git a/ginkgo/outline/_testdata/nestedfocused_test_outline.csv b/ginkgo/outline/_testdata/nestedfocused_test.go.csv similarity index 100% rename from ginkgo/outline/_testdata/nestedfocused_test_outline.csv rename to ginkgo/outline/_testdata/nestedfocused_test.go.csv diff --git a/ginkgo/outline/_testdata/nestedfocused_test_outline.json b/ginkgo/outline/_testdata/nestedfocused_test.go.json similarity index 100% rename from ginkgo/outline/_testdata/nestedfocused_test_outline.json rename to ginkgo/outline/_testdata/nestedfocused_test.go.json diff --git a/ginkgo/outline/_testdata/nodot_test_outline.csv b/ginkgo/outline/_testdata/nodot_test.go.csv similarity index 100% rename from ginkgo/outline/_testdata/nodot_test_outline.csv rename to ginkgo/outline/_testdata/nodot_test.go.csv diff --git a/ginkgo/outline/_testdata/nodot_test_outline.json b/ginkgo/outline/_testdata/nodot_test.go.json similarity index 100% rename from ginkgo/outline/_testdata/nodot_test_outline.json rename to ginkgo/outline/_testdata/nodot_test.go.json diff --git a/ginkgo/outline/_testdata/normal_test_outline.csv b/ginkgo/outline/_testdata/normal_test.go.csv similarity index 100% rename from ginkgo/outline/_testdata/normal_test_outline.csv rename to ginkgo/outline/_testdata/normal_test.go.csv diff --git a/ginkgo/outline/_testdata/normal_test.go.json b/ginkgo/outline/_testdata/normal_test.go.json new file mode 100644 index 000000000..8a19d3f15 --- /dev/null +++ b/ginkgo/outline/_testdata/normal_test.go.json @@ -0,0 +1 @@ +[{"name":"Describe","text":"NormalFixture","start":116,"end":574,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"Describe","text":"normal","start":152,"end":213,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":182,"end":209,"spec":true,"focused":false,"pending":false}]},{"name":"Context","text":"normal","start":216,"end":276,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":245,"end":272,"spec":true,"focused":false,"pending":false}]},{"name":"When","text":"normal","start":279,"end":336,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":305,"end":332,"spec":true,"focused":false,"pending":false}]},{"name":"It","text":"normal","start":339,"end":365,"spec":true,"focused":false,"pending":false},{"name":"Specify","text":"normal","start":368,"end":399,"spec":true,"focused":false,"pending":false},{"name":"Measure","text":"normal","start":402,"end":449,"spec":true,"focused":false,"pending":false}]}] diff --git a/ginkgo/outline/_testdata/normal_test_outline.json b/ginkgo/outline/_testdata/normal_test_outline.json deleted file mode 100644 index e81637f24..000000000 --- a/ginkgo/outline/_testdata/normal_test_outline.json +++ /dev/null @@ -1,100 +0,0 @@ -[ - { - "name": "Describe", - "text": "NormalFixture", - "start": 116, - "end": 574, - "spec": false, - "focused": false, - "pending": false, - "nodes": [ - { - "name": "Describe", - "text": "normal", - "start": 152, - "end": 213, - "spec": false, - "focused": false, - "pending": false, - "nodes": [ - { - "name": "It", - "text": "normal", - "start": 182, - "end": 209, - "spec": true, - "focused": false, - "pending": false - } - ] - }, - { - "name": "Context", - "text": "normal", - "start": 216, - "end": 276, - "spec": false, - "focused": false, - "pending": false, - "nodes": [ - { - "name": "It", - "text": "normal", - "start": 245, - "end": 272, - "spec": true, - "focused": false, - "pending": false - } - ] - }, - { - "name": "When", - "text": "normal", - "start": 279, - "end": 336, - "spec": false, - "focused": false, - "pending": false, - "nodes": [ - { - "name": "It", - "text": "normal", - "start": 305, - "end": 332, - "spec": true, - "focused": false, - "pending": false - } - ] - }, - { - "name": "It", - "text": "normal", - "start": 339, - "end": 365, - "spec": true, - "focused": false, - "pending": false - }, - { - "name": "Specify", - "text": "normal", - "start": 368, - "end": 399, - "spec": true, - "focused": false, - "pending": false - }, - { - "name": "Measure", - "text": "normal", - "start": 402, - "end": 449, - "spec": true, - "focused": false, - "pending": false - } - ] - } -] diff --git a/ginkgo/outline/_testdata/pending_test_outline.csv b/ginkgo/outline/_testdata/pending_test.go.csv similarity index 100% rename from ginkgo/outline/_testdata/pending_test_outline.csv rename to ginkgo/outline/_testdata/pending_test.go.csv diff --git a/ginkgo/outline/_testdata/pending_test.go.json b/ginkgo/outline/_testdata/pending_test.go.json new file mode 100644 index 000000000..71f86819c --- /dev/null +++ b/ginkgo/outline/_testdata/pending_test.go.json @@ -0,0 +1 @@ +[{"name":"Describe","text":"PendingFixture","start":116,"end":596,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"PDescribe","text":"pending","start":153,"end":217,"spec":false,"focused":false,"pending":true,"nodes":[{"name":"It","text":"pending","start":185,"end":213,"spec":true,"focused":false,"pending":true}]},{"name":"PContext","text":"pending","start":220,"end":283,"spec":false,"focused":false,"pending":true,"nodes":[{"name":"It","text":"pending","start":251,"end":279,"spec":true,"focused":false,"pending":true}]},{"name":"PWhen","text":"pending","start":286,"end":346,"spec":false,"focused":false,"pending":true,"nodes":[{"name":"It","text":"pending","start":314,"end":342,"spec":true,"focused":false,"pending":true}]},{"name":"PIt","text":"pending","start":349,"end":377,"spec":true,"focused":false,"pending":true},{"name":"PSpecify","text":"pending","start":380,"end":413,"spec":true,"focused":false,"pending":true},{"name":"PMeasure","text":"pending","start":416,"end":465,"spec":true,"focused":false,"pending":true}]}] diff --git a/ginkgo/outline/_testdata/pending_test_outline.json b/ginkgo/outline/_testdata/pending_test_outline.json deleted file mode 100644 index 719b1c96d..000000000 --- a/ginkgo/outline/_testdata/pending_test_outline.json +++ /dev/null @@ -1,100 +0,0 @@ -[ - { - "name": "Describe", - "text": "PendingFixture", - "start": 116, - "end": 596, - "spec": false, - "focused": false, - "pending": false, - "nodes": [ - { - "name": "PDescribe", - "text": "pending", - "start": 153, - "end": 217, - "spec": false, - "focused": false, - "pending": true, - "nodes": [ - { - "name": "It", - "text": "pending", - "start": 185, - "end": 213, - "spec": true, - "focused": false, - "pending": true - } - ] - }, - { - "name": "PContext", - "text": "pending", - "start": 220, - "end": 283, - "spec": false, - "focused": false, - "pending": true, - "nodes": [ - { - "name": "It", - "text": "pending", - "start": 251, - "end": 279, - "spec": true, - "focused": false, - "pending": true - } - ] - }, - { - "name": "PWhen", - "text": "pending", - "start": 286, - "end": 346, - "spec": false, - "focused": false, - "pending": true, - "nodes": [ - { - "name": "It", - "text": "pending", - "start": 314, - "end": 342, - "spec": true, - "focused": false, - "pending": true - } - ] - }, - { - "name": "PIt", - "text": "pending", - "start": 349, - "end": 377, - "spec": true, - "focused": false, - "pending": true - }, - { - "name": "PSpecify", - "text": "pending", - "start": 380, - "end": 413, - "spec": true, - "focused": false, - "pending": true - }, - { - "name": "PMeasure", - "text": "pending", - "start": 416, - "end": 465, - "spec": true, - "focused": false, - "pending": true - } - ] - } -] diff --git a/ginkgo/outline/_testdata/suite_test.go.csv b/ginkgo/outline/_testdata/suite_test.go.csv new file mode 100644 index 000000000..c85a82705 --- /dev/null +++ b/ginkgo/outline/_testdata/suite_test.go.csv @@ -0,0 +1 @@ +Name,Text,Start,End,Spec,Focused,Pending diff --git a/ginkgo/outline/_testdata/suite_test.go.json b/ginkgo/outline/_testdata/suite_test.go.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/ginkgo/outline/_testdata/suite_test.go.json @@ -0,0 +1 @@ +[] diff --git a/ginkgo/outline/_testdata/suite_test_outline.json b/ginkgo/outline/_testdata/suite_test_outline.json deleted file mode 100644 index 0637a088a..000000000 --- a/ginkgo/outline/_testdata/suite_test_outline.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/ginkgo/outline/outline_test.go b/ginkgo/outline/outline_test.go index 1181add89..544e379b1 100644 --- a/ginkgo/outline/outline_test.go +++ b/ginkgo/outline/outline_test.go @@ -48,11 +48,11 @@ var _ = DescribeTable("Validate outline from file with", // 1. Run `bash ./_testdata/create_result.sh ./testdata/myspecialcase_test.go` // To re-create the sample results for all tests: // 1. Run `for name in ./_testdata/*_test.go; do bash ./_testdata/create_result.sh $name; done` - 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"), - Entry("nested focused containers and specs", "nestedfocused_test.go", "nestedfocused_test_outline.json", "nestedfocused_test_outline.csv"), - Entry("mixed focused containers and specs", "mixed_test.go", "mixed_test_outline.json", "mixed_test_outline.csv"), + Entry("normal import of ginkgo package (no dot, no alias), normal container and specs", "nodot_test.go", "nodot_test.go.json", "nodot_test.go.csv"), + Entry("aliased import of ginkgo package, normal container and specs", "alias_test.go", "alias_test.go.json", "alias_test.go.csv"), + Entry("normal containers and specs", "normal_test.go", "normal_test.go.json", "normal_test.go.csv"), + Entry("focused containers and specs", "focused_test.go", "focused_test.go.json", "focused_test.go.csv"), + Entry("pending containers and specs", "pending_test.go", "pending_test.go.json", "pending_test.go.csv"), + Entry("nested focused containers and specs", "nestedfocused_test.go", "nestedfocused_test.go.json", "nestedfocused_test.go.csv"), + Entry("mixed focused containers and specs", "mixed_test.go", "mixed_test.go.json", "mixed_test.go.csv"), ) From 144c847e45e335fe347ba638becba63817fe2dce Mon Sep 17 00:00:00 2001 From: Daniel Lipovetsky Date: Mon, 28 Dec 2020 19:19:13 -0800 Subject: [PATCH 07/13] outline: Factor the back/propagation code to helper functions The top-level function needed to be shorter. --- ginkgo/outline/outline.go | 79 +++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/ginkgo/outline/outline.go b/ginkgo/outline/outline.go index 13fe8ae19..2c1840875 100644 --- a/ginkgo/outline/outline.go +++ b/ginkgo/outline/outline.go @@ -59,6 +59,47 @@ func (n *ginkgoNode) PostOrder(f walkFunc) { f(n) } +// PropagateInheritedProperties propagates the Pending and Focused properties +// through the subtree rooted at n. +func (n *ginkgoNode) PropagateInheritedProperties() { + n.PreOrder(func(thisNode *ginkgoNode) { + for _, descendantNode := range thisNode.Nodes { + if thisNode.Pending { + descendantNode.Pending = true + descendantNode.Focused = false + } + if thisNode.Focused && !descendantNode.Pending { + descendantNode.Focused = true + } + } + }) +} + +// BackpropagateUnfocus propagates the Focused property through the subtree +// rooted at n. It applies the rule described in the Ginkgo docs: +// > Nested programmatically focused specs follow a simple rule: if a +// > leaf-node is marked focused, any of its ancestor nodes that are marked +// > focus will be unfocused. +func (n *ginkgoNode) BackpropagateUnfocus() { + focusedSpecInSubtreeStack := []bool{} + n.PostOrder(func(thisNode *ginkgoNode) { + if thisNode.Spec { + focusedSpecInSubtreeStack = append(focusedSpecInSubtreeStack, thisNode.Focused) + return + } + focusedSpecInSubtree := false + for range thisNode.Nodes { + focusedSpecInSubtree = focusedSpecInSubtree || focusedSpecInSubtreeStack[len(focusedSpecInSubtreeStack)-1] + focusedSpecInSubtreeStack = focusedSpecInSubtreeStack[0 : len(focusedSpecInSubtreeStack)-1] + } + focusedSpecInSubtreeStack = append(focusedSpecInSubtreeStack, focusedSpecInSubtree) + if focusedSpecInSubtree { + thisNode.Focused = false + } + }) + +} + // ginkgoNodeFromCallExpr derives an outline entry from a go AST subtree // corresponding to a Ginkgo container or spec. func ginkgoNodeFromCallExpr(ce *ast.CallExpr, ginkgoImportName string) (*ginkgoNode, bool) { @@ -198,39 +239,11 @@ func FromASTFile(src *ast.File) (*outline, error) { return true }) - // Derive authoritative focus by applying this rule: - // > Nested programmatically focused specs follow a simple rule: if a - // > leaf-node is marked focused, any of its ancestor nodes that are marked - // > focus will be unfocused. - focusedSpecInSubtreeStack := []bool{} - root.PostOrder(func(n *ginkgoNode) { - if n.Spec { - focusedSpecInSubtreeStack = append(focusedSpecInSubtreeStack, n.Focused) - return - } - focusedSpecInSubtree := false - for range n.Nodes { - focusedSpecInSubtree = focusedSpecInSubtree || focusedSpecInSubtreeStack[len(focusedSpecInSubtreeStack)-1] - focusedSpecInSubtreeStack = focusedSpecInSubtreeStack[0 : len(focusedSpecInSubtreeStack)-1] - } - focusedSpecInSubtreeStack = append(focusedSpecInSubtreeStack, focusedSpecInSubtree) - if focusedSpecInSubtree { - n.Focused = false - } - }) - - // Propagate focus and pending. - root.PreOrder(func(n *ginkgoNode) { - for _, m := range n.Nodes { - if n.Pending { - m.Pending = true - m.Focused = false - } - if n.Focused && !m.Pending { - m.Focused = true - } - } - }) + // Derive the final focused property for all nodes. This must be done + // _before_ propagating the inherited focused property. + root.BackpropagateUnfocus() + // Now, propagate inherited properties, including focused and pending. + root.PropagateInheritedProperties() return (*outline)(&root), nil } From a717abcbbc67e1ac00eded4197ea45ebabe09b07 Mon Sep 17 00:00:00 2001 From: Daniel Lipovetsky Date: Mon, 28 Dec 2020 19:34:05 -0800 Subject: [PATCH 08/13] outline: Refactor deriving ginkgo identifier from call expression Again, the top-level function needed to be shorter. --- ginkgo/outline/outline.go | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/ginkgo/outline/outline.go b/ginkgo/outline/outline.go index 2c1840875..2d153fc33 100644 --- a/ginkgo/outline/outline.go +++ b/ginkgo/outline/outline.go @@ -100,38 +100,48 @@ func (n *ginkgoNode) BackpropagateUnfocus() { } -// ginkgoNodeFromCallExpr derives an outline entry from a go AST subtree -// corresponding to a Ginkgo container or spec. -func ginkgoNodeFromCallExpr(ce *ast.CallExpr, ginkgoImportName string) (*ginkgoNode, bool) { - var id *ast.Ident +func ginkgoIdentNameFromCallExpr(ce *ast.CallExpr, ginkgoImportName string) (string, bool) { switch ex := ce.Fun.(type) { case *ast.Ident: if ginkgoImportName != "." { - return nil, false + return "", false } - id = ex + return ex.Name, true case *ast.SelectorExpr: pkgID, ok := ex.X.(*ast.Ident) if !ok { - return nil, false + return "", false } // A package identifier is top-level, so Obj must be nil if pkgID.Obj != nil { - return nil, false + return "", false } if ginkgoImportName != pkgID.Name { - return nil, false + return "", false + } + if ex.Sel == nil { + return "", false } - id = ex.Sel + return ex.Sel.Name, true default: + return "", false + } +} + +// ginkgoNodeFromCallExpr derives an outline entry from a go AST subtree +// corresponding to a Ginkgo container or spec. +func ginkgoNodeFromCallExpr(ce *ast.CallExpr, ginkgoImportName string) (*ginkgoNode, bool) { + identName, ok := ginkgoIdentNameFromCallExpr(ce, ginkgoImportName) + if !ok { return nil, false } n := ginkgoNode{} - n.Name = id.Name + n.Name = identName n.Start = ce.Pos() n.End = ce.End() - switch id.Name { + + switch identName { case "It", "Measure", "Specify": n.Spec = true n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) From 380e88f71cd99808b46d1ceeaa624ff2a0dcbee5 Mon Sep 17 00:00:00 2001 From: Daniel Lipovetsky Date: Mon, 28 Dec 2020 20:21:44 -0800 Subject: [PATCH 09/13] outline: Move the exported outline code into its own file Keep the unexprted ginkgoNode code in a separate file. --- ginkgo/outline/ginkgo.go | 200 ++++++++++++++++++++++++++++++++++++++ ginkgo/outline/outline.go | 192 ------------------------------------ 2 files changed, 200 insertions(+), 192 deletions(-) create mode 100644 ginkgo/outline/ginkgo.go diff --git a/ginkgo/outline/ginkgo.go b/ginkgo/outline/ginkgo.go new file mode 100644 index 000000000..64efcd17e --- /dev/null +++ b/ginkgo/outline/ginkgo.go @@ -0,0 +1,200 @@ +package outline + +import ( + "go/ast" + "go/token" + "strconv" +) + +const ( + // undefinedTextAlt is used if the spec/container text cannot be derived + undefinedTextAlt = "undefined" +) + +// ginkgoMetadata holds useful bits of information for every entry in the outline +type ginkgoMetadata struct { + // Name is the spec or container function name, e.g. `Describe` or `It` + Name string `json:"name"` + + // Text is the `text` argument passed to specs, and some containers + Text string `json:"text"` + + // Start is the position of first character of the spec or container block + Start token.Pos `json:"start"` + + // End is the position of first character immediately after the spec or container block + End token.Pos `json:"end"` + + Spec bool `json:"spec"` + Focused bool `json:"focused"` + Pending bool `json:"pending"` +} + +// ginkgoNode is used to construct the outline as a tree +type ginkgoNode struct { + ginkgoMetadata + Nodes []*ginkgoNode `json:"nodes,omitempty"` +} + +type walkFunc func(n *ginkgoNode) + +func (n *ginkgoNode) PreOrder(f walkFunc) { + f(n) + for _, m := range n.Nodes { + m.PreOrder(f) + } +} + +func (n *ginkgoNode) PostOrder(f walkFunc) { + for _, m := range n.Nodes { + m.PostOrder(f) + } + f(n) +} + +// PropagateInheritedProperties propagates the Pending and Focused properties +// through the subtree rooted at n. +func (n *ginkgoNode) PropagateInheritedProperties() { + n.PreOrder(func(thisNode *ginkgoNode) { + for _, descendantNode := range thisNode.Nodes { + if thisNode.Pending { + descendantNode.Pending = true + descendantNode.Focused = false + } + if thisNode.Focused && !descendantNode.Pending { + descendantNode.Focused = true + } + } + }) +} + +// BackpropagateUnfocus propagates the Focused property through the subtree +// rooted at n. It applies the rule described in the Ginkgo docs: +// > Nested programmatically focused specs follow a simple rule: if a +// > leaf-node is marked focused, any of its ancestor nodes that are marked +// > focus will be unfocused. +func (n *ginkgoNode) BackpropagateUnfocus() { + focusedSpecInSubtreeStack := []bool{} + n.PostOrder(func(thisNode *ginkgoNode) { + if thisNode.Spec { + focusedSpecInSubtreeStack = append(focusedSpecInSubtreeStack, thisNode.Focused) + return + } + focusedSpecInSubtree := false + for range thisNode.Nodes { + focusedSpecInSubtree = focusedSpecInSubtree || focusedSpecInSubtreeStack[len(focusedSpecInSubtreeStack)-1] + focusedSpecInSubtreeStack = focusedSpecInSubtreeStack[0 : len(focusedSpecInSubtreeStack)-1] + } + focusedSpecInSubtreeStack = append(focusedSpecInSubtreeStack, focusedSpecInSubtree) + if focusedSpecInSubtree { + thisNode.Focused = false + } + }) + +} + +func ginkgoIdentNameFromCallExpr(ce *ast.CallExpr, ginkgoImportName string) (string, bool) { + switch ex := ce.Fun.(type) { + case *ast.Ident: + if ginkgoImportName != "." { + return "", false + } + return ex.Name, true + case *ast.SelectorExpr: + pkgID, ok := ex.X.(*ast.Ident) + if !ok { + return "", false + } + // A package identifier is top-level, so Obj must be nil + if pkgID.Obj != nil { + return "", false + } + if ginkgoImportName != pkgID.Name { + return "", false + } + if ex.Sel == nil { + return "", false + } + return ex.Sel.Name, true + default: + return "", false + } +} + +// ginkgoNodeFromCallExpr derives an outline entry from a go AST subtree +// corresponding to a Ginkgo container or spec. +func ginkgoNodeFromCallExpr(ce *ast.CallExpr, ginkgoImportName string) (*ginkgoNode, bool) { + identName, ok := ginkgoIdentNameFromCallExpr(ce, ginkgoImportName) + if !ok { + return nil, false + } + + n := ginkgoNode{} + n.Name = identName + n.Start = ce.Pos() + n.End = ce.End() + + switch identName { + case "It", "Measure", "Specify": + n.Spec = true + n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) + case "FIt", "FMeasure", "FSpecify": + n.Spec = true + n.Focused = true + n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) + case "PIt", "PMeasure", "PSpecify", "XIt", "XMeasure", "XSpecify": + n.Spec = true + n.Pending = true + n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) + case "Context", "Describe", "When": + n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) + case "FContext", "FDescribe", "FWhen": + n.Focused = true + n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) + case "PContext", "PDescribe", "PWhen", "XContext", "XDescribe", "XWhen": + n.Pending = true + n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) + case "By": + case "AfterEach", "BeforeEach": + case "JustAfterEach", "JustBeforeEach": + case "AfterSuite", "BeforeSuite": + case "SynchronizedAfterSuite", "SynchronizedBeforeSuite": + default: + return nil, false + } + return &n, true +} + +// textOrAltFromCallExpr tries to derive the "text" of a Ginkgo spec or +// container. If it cannot derive it, it returns the alt text. +func textOrAltFromCallExpr(ce *ast.CallExpr, alt string) string { + text, defined := textFromCallExpr(ce) + if !defined { + return alt + } + return text +} + +// textFromCallExpr tries to derive the "text" of a Ginkgo spec or container. If +// it cannot derive it, it returns false. +func textFromCallExpr(ce *ast.CallExpr) (string, bool) { + if len(ce.Args) < 1 { + return "", false + } + text, ok := ce.Args[0].(*ast.BasicLit) + if !ok { + return "", false + } + switch text.Kind { + case token.CHAR, token.STRING: + // For token.CHAR and token.STRING, Value is quoted + unquoted, err := strconv.Unquote(text.Value) + if err != nil { + // If unquoting fails, just use the raw Value + return text.Value, true + } + return unquoted, true + default: + return text.Value, true + } +} diff --git a/ginkgo/outline/outline.go b/ginkgo/outline/outline.go index 2d153fc33..a64a8a2e3 100644 --- a/ginkgo/outline/outline.go +++ b/ginkgo/outline/outline.go @@ -4,8 +4,6 @@ import ( "encoding/json" "fmt" "go/ast" - "go/token" - "strconv" "strings" "golang.org/x/tools/go/ast/inspector" @@ -14,198 +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" ) -// ginkgoMetadata holds useful bits of information for every entry in the outline -type ginkgoMetadata struct { - // Name is the spec or container function name, e.g. `Describe` or `It` - Name string `json:"name"` - - // Text is the `text` argument passed to specs, and some containers - Text string `json:"text"` - - // Start is the position of first character of the spec or container block - Start token.Pos `json:"start"` - - // End is the position of first character immediately after the spec or container block - End token.Pos `json:"end"` - - Spec bool `json:"spec"` - Focused bool `json:"focused"` - Pending bool `json:"pending"` -} - -// ginkgoNode is used to construct the outline as a tree -type ginkgoNode struct { - ginkgoMetadata - Nodes []*ginkgoNode `json:"nodes,omitempty"` -} - -type walkFunc func(n *ginkgoNode) - -func (n *ginkgoNode) PreOrder(f walkFunc) { - f(n) - for _, m := range n.Nodes { - m.PreOrder(f) - } -} - -func (n *ginkgoNode) PostOrder(f walkFunc) { - for _, m := range n.Nodes { - m.PostOrder(f) - } - f(n) -} - -// PropagateInheritedProperties propagates the Pending and Focused properties -// through the subtree rooted at n. -func (n *ginkgoNode) PropagateInheritedProperties() { - n.PreOrder(func(thisNode *ginkgoNode) { - for _, descendantNode := range thisNode.Nodes { - if thisNode.Pending { - descendantNode.Pending = true - descendantNode.Focused = false - } - if thisNode.Focused && !descendantNode.Pending { - descendantNode.Focused = true - } - } - }) -} - -// BackpropagateUnfocus propagates the Focused property through the subtree -// rooted at n. It applies the rule described in the Ginkgo docs: -// > Nested programmatically focused specs follow a simple rule: if a -// > leaf-node is marked focused, any of its ancestor nodes that are marked -// > focus will be unfocused. -func (n *ginkgoNode) BackpropagateUnfocus() { - focusedSpecInSubtreeStack := []bool{} - n.PostOrder(func(thisNode *ginkgoNode) { - if thisNode.Spec { - focusedSpecInSubtreeStack = append(focusedSpecInSubtreeStack, thisNode.Focused) - return - } - focusedSpecInSubtree := false - for range thisNode.Nodes { - focusedSpecInSubtree = focusedSpecInSubtree || focusedSpecInSubtreeStack[len(focusedSpecInSubtreeStack)-1] - focusedSpecInSubtreeStack = focusedSpecInSubtreeStack[0 : len(focusedSpecInSubtreeStack)-1] - } - focusedSpecInSubtreeStack = append(focusedSpecInSubtreeStack, focusedSpecInSubtree) - if focusedSpecInSubtree { - thisNode.Focused = false - } - }) - -} - -func ginkgoIdentNameFromCallExpr(ce *ast.CallExpr, ginkgoImportName string) (string, bool) { - switch ex := ce.Fun.(type) { - case *ast.Ident: - if ginkgoImportName != "." { - return "", false - } - return ex.Name, true - case *ast.SelectorExpr: - pkgID, ok := ex.X.(*ast.Ident) - if !ok { - return "", false - } - // A package identifier is top-level, so Obj must be nil - if pkgID.Obj != nil { - return "", false - } - if ginkgoImportName != pkgID.Name { - return "", false - } - if ex.Sel == nil { - return "", false - } - return ex.Sel.Name, true - default: - return "", false - } -} - -// ginkgoNodeFromCallExpr derives an outline entry from a go AST subtree -// corresponding to a Ginkgo container or spec. -func ginkgoNodeFromCallExpr(ce *ast.CallExpr, ginkgoImportName string) (*ginkgoNode, bool) { - identName, ok := ginkgoIdentNameFromCallExpr(ce, ginkgoImportName) - if !ok { - return nil, false - } - - n := ginkgoNode{} - n.Name = identName - n.Start = ce.Pos() - n.End = ce.End() - - switch identName { - case "It", "Measure", "Specify": - n.Spec = true - n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) - case "FIt", "FMeasure", "FSpecify": - n.Spec = true - n.Focused = true - n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) - case "PIt", "PMeasure", "PSpecify", "XIt", "XMeasure", "XSpecify": - n.Spec = true - n.Pending = true - n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) - case "Context", "Describe", "When": - n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) - case "FContext", "FDescribe", "FWhen": - n.Focused = true - n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) - case "PContext", "PDescribe", "PWhen", "XContext", "XDescribe", "XWhen": - n.Pending = true - n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) - case "By": - case "AfterEach", "BeforeEach": - case "JustAfterEach", "JustBeforeEach": - case "AfterSuite", "BeforeSuite": - case "SynchronizedAfterSuite", "SynchronizedBeforeSuite": - default: - return nil, false - } - return &n, true -} - -// textOrAltFromCallExpr tries to derive the "text" of a Ginkgo spec or -// container. If it cannot derive it, it returns the alt text. -func textOrAltFromCallExpr(ce *ast.CallExpr, alt string) string { - text, defined := textFromCallExpr(ce) - if !defined { - return alt - } - return text -} - -// textFromCallExpr tries to derive the "text" of a Ginkgo spec or container. If -// it cannot derive it, it returns false. -func textFromCallExpr(ce *ast.CallExpr) (string, bool) { - if len(ce.Args) < 1 { - return "", false - } - text, ok := ce.Args[0].(*ast.BasicLit) - if !ok { - return "", false - } - switch text.Kind { - case token.CHAR, token.STRING: - // For token.CHAR and token.STRING, Value is quoted - unquoted, err := strconv.Unquote(text.Value) - if err != nil { - // If unquoting fails, just use the raw Value - return text.Value, true - } - return unquoted, true - default: - return text.Value, true - } -} - // FromASTFile returns an outline for a Ginkgo test source file func FromASTFile(src *ast.File) (*outline, error) { ginkgoImportName, ok := importNameForPackage(src, ginkgoImportPath) From 4317c54e7c71c31f2ab54ec17cdc36b073666b5f Mon Sep 17 00:00:00 2001 From: Daniel Lipovetsky Date: Mon, 28 Dec 2020 20:22:06 -0800 Subject: [PATCH 10/13] outline: Instead of aliasing ginkgoNode with outline, embed ginkgoNode in outline An advantage of embedding is that it does not require casting. The outline receivers does not change. --- ginkgo/outline/outline.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ginkgo/outline/outline.go b/ginkgo/outline/outline.go index a64a8a2e3..4eea9332f 100644 --- a/ginkgo/outline/outline.go +++ b/ginkgo/outline/outline.go @@ -63,10 +63,12 @@ func FromASTFile(src *ast.File) (*outline, error) { // Now, propagate inherited properties, including focused and pending. root.PropagateInheritedProperties() - return (*outline)(&root), nil + return &outline{root}, nil } -type outline ginkgoNode +type outline struct { + ginkgoNode +} func (o *outline) MarshalJSON() ([]byte, error) { return json.Marshal(o.Nodes) From 2ca52dac7c8a43bbab1a456be6e1b533eeb3ec41 Mon Sep 17 00:00:00 2001 From: Daniel Lipovetsky Date: Mon, 28 Dec 2020 20:35:10 -0800 Subject: [PATCH 11/13] outline: (fix) Derive the "text" of the By ginkgo call The "text" was being ignored by mistake. --- ginkgo/outline/_testdata/alias_test.go | 2 ++ ginkgo/outline/_testdata/alias_test.go.csv | 22 +++++++++-------- ginkgo/outline/_testdata/alias_test.go.json | 2 +- ginkgo/outline/_testdata/focused_test.go | 3 ++- ginkgo/outline/_testdata/focused_test.go.csv | 22 +++++++++-------- ginkgo/outline/_testdata/focused_test.go.json | 2 +- ginkgo/outline/_testdata/mixed_test.go | 6 +++-- ginkgo/outline/_testdata/mixed_test.go.csv | 12 ++++++---- ginkgo/outline/_testdata/mixed_test.go.json | 2 +- .../outline/_testdata/nestedfocused_test.go | 6 +++-- .../_testdata/nestedfocused_test.go.csv | 24 +++++++++++-------- .../_testdata/nestedfocused_test.go.json | 2 +- ginkgo/outline/_testdata/nodot_test.go | 3 ++- ginkgo/outline/_testdata/nodot_test.go.csv | 22 +++++++++-------- ginkgo/outline/_testdata/nodot_test.go.json | 2 +- ginkgo/outline/_testdata/normal_test.go | 3 ++- ginkgo/outline/_testdata/normal_test.go.csv | 22 +++++++++-------- ginkgo/outline/_testdata/normal_test.go.json | 2 +- ginkgo/outline/_testdata/pending_test.go | 3 ++- ginkgo/outline/_testdata/pending_test.go.csv | 22 +++++++++-------- ginkgo/outline/_testdata/pending_test.go.json | 2 +- ginkgo/outline/ginkgo.go | 1 + 22 files changed, 108 insertions(+), 79 deletions(-) diff --git a/ginkgo/outline/_testdata/alias_test.go b/ginkgo/outline/_testdata/alias_test.go index 61237e4d0..bf9a29075 100644 --- a/ginkgo/outline/_testdata/alias_test.go +++ b/ginkgo/outline/_testdata/alias_test.go @@ -7,6 +7,8 @@ import ( var _ = fooginkgo.Describe("NodotFixture", func() { fooginkgo.Describe("normal", func() { fooginkgo.It("normal", func() { + fooginkgo.By("normal") + fooginkgo.By("normal") }) }) diff --git a/ginkgo/outline/_testdata/alias_test.go.csv b/ginkgo/outline/_testdata/alias_test.go.csv index 10b7d49f7..267f905a1 100644 --- a/ginkgo/outline/_testdata/alias_test.go.csv +++ b/ginkgo/outline/_testdata/alias_test.go.csv @@ -1,11 +1,13 @@ 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 +Describe,NodotFixture,79,728,false,false,false +Describe,normal,124,257,false,false,false +It,normal,164,253,true,false,false +By,normal,199,221,false,false,false +By,normal,225,247,false,false,false +Context,normal,260,340,false,false,false +It,normal,299,336,true,false,false +When,normal,343,420,false,false,false +It,normal,379,416,true,false,false +It,normal,423,459,true,false,false +Specify,normal,462,503,true,false,false +Measure,normal,506,563,true,false,false diff --git a/ginkgo/outline/_testdata/alias_test.go.json b/ginkgo/outline/_testdata/alias_test.go.json index 34029e00f..885de0a80 100644 --- a/ginkgo/outline/_testdata/alias_test.go.json +++ b/ginkgo/outline/_testdata/alias_test.go.json @@ -1 +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}]}] +[{"name":"Describe","text":"NodotFixture","start":79,"end":728,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"Describe","text":"normal","start":124,"end":257,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":164,"end":253,"spec":true,"focused":false,"pending":false,"nodes":[{"name":"By","text":"normal","start":199,"end":221,"spec":false,"focused":false,"pending":false},{"name":"By","text":"normal","start":225,"end":247,"spec":false,"focused":false,"pending":false}]}]},{"name":"Context","text":"normal","start":260,"end":340,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":299,"end":336,"spec":true,"focused":false,"pending":false}]},{"name":"When","text":"normal","start":343,"end":420,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":379,"end":416,"spec":true,"focused":false,"pending":false}]},{"name":"It","text":"normal","start":423,"end":459,"spec":true,"focused":false,"pending":false},{"name":"Specify","text":"normal","start":462,"end":503,"spec":true,"focused":false,"pending":false},{"name":"Measure","text":"normal","start":506,"end":563,"spec":true,"focused":false,"pending":false}]}] diff --git a/ginkgo/outline/_testdata/focused_test.go b/ginkgo/outline/_testdata/focused_test.go index 7900d2167..e9f5a5d52 100644 --- a/ginkgo/outline/_testdata/focused_test.go +++ b/ginkgo/outline/_testdata/focused_test.go @@ -8,7 +8,8 @@ import ( var _ = Describe("unfocused", func() { FDescribe("focused", func() { It("focused", func() { - + By("focused") + By("focused") }) }) diff --git a/ginkgo/outline/_testdata/focused_test.go.csv b/ginkgo/outline/_testdata/focused_test.go.csv index 3a8ef5702..d49522daf 100644 --- a/ginkgo/outline/_testdata/focused_test.go.csv +++ b/ginkgo/outline/_testdata/focused_test.go.csv @@ -1,11 +1,13 @@ Name,Text,Start,End,Spec,Focused,Pending -Describe,unfocused,116,591,false,false,false -FDescribe,focused,148,212,false,true,false -It,focused,180,208,true,true,false -FContext,focused,215,278,false,true,false -It,focused,246,274,true,true,false -FWhen,focused,281,341,false,true,false -It,focused,309,337,true,true,false -FIt,focused,344,372,true,true,false -FSpecify,focused,375,408,true,true,false -FMeasure,focused,411,460,true,true,false +Describe,unfocused,116,624,false,false,false +FDescribe,focused,148,245,false,true,false +It,focused,180,241,true,true,false +By,focused,206,219,false,true,false +By,focused,223,236,false,true,false +FContext,focused,248,311,false,true,false +It,focused,279,307,true,true,false +FWhen,focused,314,374,false,true,false +It,focused,342,370,true,true,false +FIt,focused,377,405,true,true,false +FSpecify,focused,408,441,true,true,false +FMeasure,focused,444,493,true,true,false diff --git a/ginkgo/outline/_testdata/focused_test.go.json b/ginkgo/outline/_testdata/focused_test.go.json index 2e0bb6dd8..2c042225d 100644 --- a/ginkgo/outline/_testdata/focused_test.go.json +++ b/ginkgo/outline/_testdata/focused_test.go.json @@ -1 +1 @@ -[{"name":"Describe","text":"unfocused","start":116,"end":591,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"FDescribe","text":"focused","start":148,"end":212,"spec":false,"focused":true,"pending":false,"nodes":[{"name":"It","text":"focused","start":180,"end":208,"spec":true,"focused":true,"pending":false}]},{"name":"FContext","text":"focused","start":215,"end":278,"spec":false,"focused":true,"pending":false,"nodes":[{"name":"It","text":"focused","start":246,"end":274,"spec":true,"focused":true,"pending":false}]},{"name":"FWhen","text":"focused","start":281,"end":341,"spec":false,"focused":true,"pending":false,"nodes":[{"name":"It","text":"focused","start":309,"end":337,"spec":true,"focused":true,"pending":false}]},{"name":"FIt","text":"focused","start":344,"end":372,"spec":true,"focused":true,"pending":false},{"name":"FSpecify","text":"focused","start":375,"end":408,"spec":true,"focused":true,"pending":false},{"name":"FMeasure","text":"focused","start":411,"end":460,"spec":true,"focused":true,"pending":false}]}] +[{"name":"Describe","text":"unfocused","start":116,"end":624,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"FDescribe","text":"focused","start":148,"end":245,"spec":false,"focused":true,"pending":false,"nodes":[{"name":"It","text":"focused","start":180,"end":241,"spec":true,"focused":true,"pending":false,"nodes":[{"name":"By","text":"focused","start":206,"end":219,"spec":false,"focused":true,"pending":false},{"name":"By","text":"focused","start":223,"end":236,"spec":false,"focused":true,"pending":false}]}]},{"name":"FContext","text":"focused","start":248,"end":311,"spec":false,"focused":true,"pending":false,"nodes":[{"name":"It","text":"focused","start":279,"end":307,"spec":true,"focused":true,"pending":false}]},{"name":"FWhen","text":"focused","start":314,"end":374,"spec":false,"focused":true,"pending":false,"nodes":[{"name":"It","text":"focused","start":342,"end":370,"spec":true,"focused":true,"pending":false}]},{"name":"FIt","text":"focused","start":377,"end":405,"spec":true,"focused":true,"pending":false},{"name":"FSpecify","text":"focused","start":408,"end":441,"spec":true,"focused":true,"pending":false},{"name":"FMeasure","text":"focused","start":444,"end":493,"spec":true,"focused":true,"pending":false}]}] diff --git a/ginkgo/outline/_testdata/mixed_test.go b/ginkgo/outline/_testdata/mixed_test.go index af538e01c..6e91303f2 100644 --- a/ginkgo/outline/_testdata/mixed_test.go +++ b/ginkgo/outline/_testdata/mixed_test.go @@ -34,10 +34,12 @@ var _ = FDescribe("unfocused", func() { PContext("unfocused", func() { FIt("unfocused", func() { - + By("unfocused") + By("unfocused") }) It("unfocused", func() { - + By("unfocused") + By("unfocused") }) }) }) diff --git a/ginkgo/outline/_testdata/mixed_test.go.csv b/ginkgo/outline/_testdata/mixed_test.go.csv index b66aa4d5e..819c222a1 100644 --- a/ginkgo/outline/_testdata/mixed_test.go.csv +++ b/ginkgo/outline/_testdata/mixed_test.go.csv @@ -1,5 +1,5 @@ Name,Text,Start,End,Spec,Focused,Pending -FDescribe,unfocused,71,508,false,false,false +FDescribe,unfocused,71,582,false,false,false FContext,unfocused,104,203,false,false,false It,unfocused,137,167,true,false,false FIt,focused,170,199,true,true,false @@ -9,6 +9,10 @@ It,unfocused,270,300,true,false,false FContext,focused,307,401,false,true,false It,focused,338,366,true,true,false It,focused,369,397,true,true,false -PContext,unfocused,404,505,false,false,true -FIt,unfocused,437,468,true,false,true -It,unfocused,471,501,true,false,true +PContext,unfocused,404,579,false,false,true +FIt,unfocused,437,505,true,false,true +By,unfocused,466,481,false,false,true +By,unfocused,485,500,false,false,true +It,unfocused,508,575,true,false,true +By,unfocused,536,551,false,false,true +By,unfocused,555,570,false,false,true diff --git a/ginkgo/outline/_testdata/mixed_test.go.json b/ginkgo/outline/_testdata/mixed_test.go.json index b24ff06c5..9db2e1271 100644 --- a/ginkgo/outline/_testdata/mixed_test.go.json +++ b/ginkgo/outline/_testdata/mixed_test.go.json @@ -1 +1 @@ -[{"name":"FDescribe","text":"unfocused","start":71,"end":508,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"FContext","text":"unfocused","start":104,"end":203,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"unfocused","start":137,"end":167,"spec":true,"focused":false,"pending":false},{"name":"FIt","text":"focused","start":170,"end":199,"spec":true,"focused":true,"pending":false}]},{"name":"Context","text":"unfocused","start":206,"end":304,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"FIt","text":"focused","start":238,"end":267,"spec":true,"focused":true,"pending":false},{"name":"It","text":"unfocused","start":270,"end":300,"spec":true,"focused":false,"pending":false}]},{"name":"FContext","text":"focused","start":307,"end":401,"spec":false,"focused":true,"pending":false,"nodes":[{"name":"It","text":"focused","start":338,"end":366,"spec":true,"focused":true,"pending":false},{"name":"It","text":"focused","start":369,"end":397,"spec":true,"focused":true,"pending":false}]},{"name":"PContext","text":"unfocused","start":404,"end":505,"spec":false,"focused":false,"pending":true,"nodes":[{"name":"FIt","text":"unfocused","start":437,"end":468,"spec":true,"focused":false,"pending":true},{"name":"It","text":"unfocused","start":471,"end":501,"spec":true,"focused":false,"pending":true}]}]}] +[{"name":"FDescribe","text":"unfocused","start":71,"end":582,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"FContext","text":"unfocused","start":104,"end":203,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"unfocused","start":137,"end":167,"spec":true,"focused":false,"pending":false},{"name":"FIt","text":"focused","start":170,"end":199,"spec":true,"focused":true,"pending":false}]},{"name":"Context","text":"unfocused","start":206,"end":304,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"FIt","text":"focused","start":238,"end":267,"spec":true,"focused":true,"pending":false},{"name":"It","text":"unfocused","start":270,"end":300,"spec":true,"focused":false,"pending":false}]},{"name":"FContext","text":"focused","start":307,"end":401,"spec":false,"focused":true,"pending":false,"nodes":[{"name":"It","text":"focused","start":338,"end":366,"spec":true,"focused":true,"pending":false},{"name":"It","text":"focused","start":369,"end":397,"spec":true,"focused":true,"pending":false}]},{"name":"PContext","text":"unfocused","start":404,"end":579,"spec":false,"focused":false,"pending":true,"nodes":[{"name":"FIt","text":"unfocused","start":437,"end":505,"spec":true,"focused":false,"pending":true,"nodes":[{"name":"By","text":"unfocused","start":466,"end":481,"spec":false,"focused":false,"pending":true},{"name":"By","text":"unfocused","start":485,"end":500,"spec":false,"focused":false,"pending":true}]},{"name":"It","text":"unfocused","start":508,"end":575,"spec":true,"focused":false,"pending":true,"nodes":[{"name":"By","text":"unfocused","start":536,"end":551,"spec":false,"focused":false,"pending":true},{"name":"By","text":"unfocused","start":555,"end":570,"spec":false,"focused":false,"pending":true}]}]}]}] diff --git a/ginkgo/outline/_testdata/nestedfocused_test.go b/ginkgo/outline/_testdata/nestedfocused_test.go index 51c558202..905b1c864 100644 --- a/ginkgo/outline/_testdata/nestedfocused_test.go +++ b/ginkgo/outline/_testdata/nestedfocused_test.go @@ -7,10 +7,12 @@ import ( var _ = FDescribe("unfocused", func() { FContext("unfocused", func() { It("unfocused", func() { - + By("unfocused") + By("unfocused") }) FIt("focused", func() { - + By("focused") + By("focused") }) }) diff --git a/ginkgo/outline/_testdata/nestedfocused_test.go.csv b/ginkgo/outline/_testdata/nestedfocused_test.go.csv index f6b4fd09f..b91cfa0dd 100644 --- a/ginkgo/outline/_testdata/nestedfocused_test.go.csv +++ b/ginkgo/outline/_testdata/nestedfocused_test.go.csv @@ -1,11 +1,15 @@ Name,Text,Start,End,Spec,Focused,Pending -FDescribe,unfocused,71,404,false,false,false -FContext,unfocused,104,203,false,false,false -It,unfocused,137,167,true,false,false -FIt,focused,170,199,true,true,false -Context,unfocused,206,304,false,false,false -FIt,focused,238,267,true,true,false -It,unfocused,270,300,true,false,false -FContext,focused,307,401,false,true,false -It,focused,338,366,true,true,false -It,focused,369,397,true,true,false +FDescribe,unfocused,71,474,false,false,false +FContext,unfocused,104,273,false,false,false +It,unfocused,137,204,true,false,false +By,unfocused,165,180,false,false,false +By,unfocused,184,199,false,false,false +FIt,focused,207,269,true,true,false +By,focused,234,247,false,true,false +By,focused,251,264,false,true,false +Context,unfocused,276,374,false,false,false +FIt,focused,308,337,true,true,false +It,unfocused,340,370,true,false,false +FContext,focused,377,471,false,true,false +It,focused,408,436,true,true,false +It,focused,439,467,true,true,false diff --git a/ginkgo/outline/_testdata/nestedfocused_test.go.json b/ginkgo/outline/_testdata/nestedfocused_test.go.json index 8e317e5ee..86e0ba4a6 100644 --- a/ginkgo/outline/_testdata/nestedfocused_test.go.json +++ b/ginkgo/outline/_testdata/nestedfocused_test.go.json @@ -1 +1 @@ -[{"name":"FDescribe","text":"unfocused","start":71,"end":404,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"FContext","text":"unfocused","start":104,"end":203,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"unfocused","start":137,"end":167,"spec":true,"focused":false,"pending":false},{"name":"FIt","text":"focused","start":170,"end":199,"spec":true,"focused":true,"pending":false}]},{"name":"Context","text":"unfocused","start":206,"end":304,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"FIt","text":"focused","start":238,"end":267,"spec":true,"focused":true,"pending":false},{"name":"It","text":"unfocused","start":270,"end":300,"spec":true,"focused":false,"pending":false}]},{"name":"FContext","text":"focused","start":307,"end":401,"spec":false,"focused":true,"pending":false,"nodes":[{"name":"It","text":"focused","start":338,"end":366,"spec":true,"focused":true,"pending":false},{"name":"It","text":"focused","start":369,"end":397,"spec":true,"focused":true,"pending":false}]}]}] +[{"name":"FDescribe","text":"unfocused","start":71,"end":474,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"FContext","text":"unfocused","start":104,"end":273,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"unfocused","start":137,"end":204,"spec":true,"focused":false,"pending":false,"nodes":[{"name":"By","text":"unfocused","start":165,"end":180,"spec":false,"focused":false,"pending":false},{"name":"By","text":"unfocused","start":184,"end":199,"spec":false,"focused":false,"pending":false}]},{"name":"FIt","text":"focused","start":207,"end":269,"spec":true,"focused":true,"pending":false,"nodes":[{"name":"By","text":"focused","start":234,"end":247,"spec":false,"focused":true,"pending":false},{"name":"By","text":"focused","start":251,"end":264,"spec":false,"focused":true,"pending":false}]}]},{"name":"Context","text":"unfocused","start":276,"end":374,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"FIt","text":"focused","start":308,"end":337,"spec":true,"focused":true,"pending":false},{"name":"It","text":"unfocused","start":340,"end":370,"spec":true,"focused":false,"pending":false}]},{"name":"FContext","text":"focused","start":377,"end":471,"spec":false,"focused":true,"pending":false,"nodes":[{"name":"It","text":"focused","start":408,"end":436,"spec":true,"focused":true,"pending":false},{"name":"It","text":"focused","start":439,"end":467,"spec":true,"focused":true,"pending":false}]}]}] diff --git a/ginkgo/outline/_testdata/nodot_test.go b/ginkgo/outline/_testdata/nodot_test.go index 51cc08ff4..13525db75 100644 --- a/ginkgo/outline/_testdata/nodot_test.go +++ b/ginkgo/outline/_testdata/nodot_test.go @@ -7,7 +7,8 @@ import ( var _ = ginkgo.Describe("NodotFixture", func() { ginkgo.Describe("normal", func() { ginkgo.It("normal", func() { - + ginkgo.By("normal") + ginkgo.By("normal") }) }) diff --git a/ginkgo/outline/_testdata/nodot_test.go.csv b/ginkgo/outline/_testdata/nodot_test.go.csv index 9d79c0c6d..eecd3b9d5 100644 --- a/ginkgo/outline/_testdata/nodot_test.go.csv +++ b/ginkgo/outline/_testdata/nodot_test.go.csv @@ -1,11 +1,13 @@ 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 +Describe,NodotFixture,69,669,false,false,false +Describe,normal,111,231,false,false,false +It,normal,148,227,true,false,false +By,normal,180,199,false,false,false +By,normal,203,222,false,false,false +Context,normal,234,308,false,false,false +It,normal,270,304,true,false,false +When,normal,311,382,false,false,false +It,normal,344,378,true,false,false +It,normal,385,418,true,false,false +Specify,normal,421,459,true,false,false +Measure,normal,462,516,true,false,false diff --git a/ginkgo/outline/_testdata/nodot_test.go.json b/ginkgo/outline/_testdata/nodot_test.go.json index cfbf35785..d476b820c 100644 --- a/ginkgo/outline/_testdata/nodot_test.go.json +++ b/ginkgo/outline/_testdata/nodot_test.go.json @@ -1 +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}]}] +[{"name":"Describe","text":"NodotFixture","start":69,"end":669,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"Describe","text":"normal","start":111,"end":231,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":148,"end":227,"spec":true,"focused":false,"pending":false,"nodes":[{"name":"By","text":"normal","start":180,"end":199,"spec":false,"focused":false,"pending":false},{"name":"By","text":"normal","start":203,"end":222,"spec":false,"focused":false,"pending":false}]}]},{"name":"Context","text":"normal","start":234,"end":308,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":270,"end":304,"spec":true,"focused":false,"pending":false}]},{"name":"When","text":"normal","start":311,"end":382,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":344,"end":378,"spec":true,"focused":false,"pending":false}]},{"name":"It","text":"normal","start":385,"end":418,"spec":true,"focused":false,"pending":false},{"name":"Specify","text":"normal","start":421,"end":459,"spec":true,"focused":false,"pending":false},{"name":"Measure","text":"normal","start":462,"end":516,"spec":true,"focused":false,"pending":false}]}] diff --git a/ginkgo/outline/_testdata/normal_test.go b/ginkgo/outline/_testdata/normal_test.go index 39cdd67e0..8d8758120 100644 --- a/ginkgo/outline/_testdata/normal_test.go +++ b/ginkgo/outline/_testdata/normal_test.go @@ -8,7 +8,8 @@ import ( var _ = Describe("NormalFixture", func() { Describe("normal", func() { It("normal", func() { - + By("step 1") + By("step 2") }) }) diff --git a/ginkgo/outline/_testdata/normal_test.go.csv b/ginkgo/outline/_testdata/normal_test.go.csv index 7938056b5..569f336cf 100644 --- a/ginkgo/outline/_testdata/normal_test.go.csv +++ b/ginkgo/outline/_testdata/normal_test.go.csv @@ -1,11 +1,13 @@ Name,Text,Start,End,Spec,Focused,Pending -Describe,NormalFixture,116,574,false,false,false -Describe,normal,152,213,false,false,false -It,normal,182,209,true,false,false -Context,normal,216,276,false,false,false -It,normal,245,272,true,false,false -When,normal,279,336,false,false,false -It,normal,305,332,true,false,false -It,normal,339,365,true,false,false -Specify,normal,368,399,true,false,false -Measure,normal,402,449,true,false,false +Describe,NormalFixture,116,605,false,false,false +Describe,normal,152,244,false,false,false +It,normal,182,240,true,false,false +By,step 1,207,219,false,false,false +By,step 2,223,235,false,false,false +Context,normal,247,307,false,false,false +It,normal,276,303,true,false,false +When,normal,310,367,false,false,false +It,normal,336,363,true,false,false +It,normal,370,396,true,false,false +Specify,normal,399,430,true,false,false +Measure,normal,433,480,true,false,false diff --git a/ginkgo/outline/_testdata/normal_test.go.json b/ginkgo/outline/_testdata/normal_test.go.json index 8a19d3f15..43b97a2b9 100644 --- a/ginkgo/outline/_testdata/normal_test.go.json +++ b/ginkgo/outline/_testdata/normal_test.go.json @@ -1 +1 @@ -[{"name":"Describe","text":"NormalFixture","start":116,"end":574,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"Describe","text":"normal","start":152,"end":213,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":182,"end":209,"spec":true,"focused":false,"pending":false}]},{"name":"Context","text":"normal","start":216,"end":276,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":245,"end":272,"spec":true,"focused":false,"pending":false}]},{"name":"When","text":"normal","start":279,"end":336,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":305,"end":332,"spec":true,"focused":false,"pending":false}]},{"name":"It","text":"normal","start":339,"end":365,"spec":true,"focused":false,"pending":false},{"name":"Specify","text":"normal","start":368,"end":399,"spec":true,"focused":false,"pending":false},{"name":"Measure","text":"normal","start":402,"end":449,"spec":true,"focused":false,"pending":false}]}] +[{"name":"Describe","text":"NormalFixture","start":116,"end":605,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"Describe","text":"normal","start":152,"end":244,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":182,"end":240,"spec":true,"focused":false,"pending":false,"nodes":[{"name":"By","text":"step 1","start":207,"end":219,"spec":false,"focused":false,"pending":false},{"name":"By","text":"step 2","start":223,"end":235,"spec":false,"focused":false,"pending":false}]}]},{"name":"Context","text":"normal","start":247,"end":307,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":276,"end":303,"spec":true,"focused":false,"pending":false}]},{"name":"When","text":"normal","start":310,"end":367,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"It","text":"normal","start":336,"end":363,"spec":true,"focused":false,"pending":false}]},{"name":"It","text":"normal","start":370,"end":396,"spec":true,"focused":false,"pending":false},{"name":"Specify","text":"normal","start":399,"end":430,"spec":true,"focused":false,"pending":false},{"name":"Measure","text":"normal","start":433,"end":480,"spec":true,"focused":false,"pending":false}]}] diff --git a/ginkgo/outline/_testdata/pending_test.go b/ginkgo/outline/_testdata/pending_test.go index babd0078c..b4dd53ee2 100644 --- a/ginkgo/outline/_testdata/pending_test.go +++ b/ginkgo/outline/_testdata/pending_test.go @@ -8,7 +8,8 @@ import ( var _ = Describe("PendingFixture", func() { PDescribe("pending", func() { It("pending", func() { - + By("pending") + By("pending") }) }) diff --git a/ginkgo/outline/_testdata/pending_test.go.csv b/ginkgo/outline/_testdata/pending_test.go.csv index f88cdbd09..831c8d9da 100644 --- a/ginkgo/outline/_testdata/pending_test.go.csv +++ b/ginkgo/outline/_testdata/pending_test.go.csv @@ -1,11 +1,13 @@ Name,Text,Start,End,Spec,Focused,Pending -Describe,PendingFixture,116,596,false,false,false -PDescribe,pending,153,217,false,false,true -It,pending,185,213,true,false,true -PContext,pending,220,283,false,false,true -It,pending,251,279,true,false,true -PWhen,pending,286,346,false,false,true -It,pending,314,342,true,false,true -PIt,pending,349,377,true,false,true -PSpecify,pending,380,413,true,false,true -PMeasure,pending,416,465,true,false,true +Describe,PendingFixture,116,629,false,false,false +PDescribe,pending,153,250,false,false,true +It,pending,185,246,true,false,true +By,pending,211,224,false,false,true +By,pending,228,241,false,false,true +PContext,pending,253,316,false,false,true +It,pending,284,312,true,false,true +PWhen,pending,319,379,false,false,true +It,pending,347,375,true,false,true +PIt,pending,382,410,true,false,true +PSpecify,pending,413,446,true,false,true +PMeasure,pending,449,498,true,false,true diff --git a/ginkgo/outline/_testdata/pending_test.go.json b/ginkgo/outline/_testdata/pending_test.go.json index 71f86819c..0014d4a2c 100644 --- a/ginkgo/outline/_testdata/pending_test.go.json +++ b/ginkgo/outline/_testdata/pending_test.go.json @@ -1 +1 @@ -[{"name":"Describe","text":"PendingFixture","start":116,"end":596,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"PDescribe","text":"pending","start":153,"end":217,"spec":false,"focused":false,"pending":true,"nodes":[{"name":"It","text":"pending","start":185,"end":213,"spec":true,"focused":false,"pending":true}]},{"name":"PContext","text":"pending","start":220,"end":283,"spec":false,"focused":false,"pending":true,"nodes":[{"name":"It","text":"pending","start":251,"end":279,"spec":true,"focused":false,"pending":true}]},{"name":"PWhen","text":"pending","start":286,"end":346,"spec":false,"focused":false,"pending":true,"nodes":[{"name":"It","text":"pending","start":314,"end":342,"spec":true,"focused":false,"pending":true}]},{"name":"PIt","text":"pending","start":349,"end":377,"spec":true,"focused":false,"pending":true},{"name":"PSpecify","text":"pending","start":380,"end":413,"spec":true,"focused":false,"pending":true},{"name":"PMeasure","text":"pending","start":416,"end":465,"spec":true,"focused":false,"pending":true}]}] +[{"name":"Describe","text":"PendingFixture","start":116,"end":629,"spec":false,"focused":false,"pending":false,"nodes":[{"name":"PDescribe","text":"pending","start":153,"end":250,"spec":false,"focused":false,"pending":true,"nodes":[{"name":"It","text":"pending","start":185,"end":246,"spec":true,"focused":false,"pending":true,"nodes":[{"name":"By","text":"pending","start":211,"end":224,"spec":false,"focused":false,"pending":true},{"name":"By","text":"pending","start":228,"end":241,"spec":false,"focused":false,"pending":true}]}]},{"name":"PContext","text":"pending","start":253,"end":316,"spec":false,"focused":false,"pending":true,"nodes":[{"name":"It","text":"pending","start":284,"end":312,"spec":true,"focused":false,"pending":true}]},{"name":"PWhen","text":"pending","start":319,"end":379,"spec":false,"focused":false,"pending":true,"nodes":[{"name":"It","text":"pending","start":347,"end":375,"spec":true,"focused":false,"pending":true}]},{"name":"PIt","text":"pending","start":382,"end":410,"spec":true,"focused":false,"pending":true},{"name":"PSpecify","text":"pending","start":413,"end":446,"spec":true,"focused":false,"pending":true},{"name":"PMeasure","text":"pending","start":449,"end":498,"spec":true,"focused":false,"pending":true}]}] diff --git a/ginkgo/outline/ginkgo.go b/ginkgo/outline/ginkgo.go index 64efcd17e..a105e64b7 100644 --- a/ginkgo/outline/ginkgo.go +++ b/ginkgo/outline/ginkgo.go @@ -155,6 +155,7 @@ func ginkgoNodeFromCallExpr(ce *ast.CallExpr, ginkgoImportName string) (*ginkgoN n.Pending = true n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) case "By": + n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) case "AfterEach", "BeforeEach": case "JustAfterEach", "JustBeforeEach": case "AfterSuite", "BeforeSuite": From bc67def0df9783e31095fa8ae8a3168892c99913 Mon Sep 17 00:00:00 2001 From: Daniel Lipovetsky Date: Mon, 28 Dec 2020 21:27:20 -0800 Subject: [PATCH 12/13] outline: Test an outline of a "suite_test.go" file The test and its result samples were already present, but the test was not run. --- ginkgo/outline/outline_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ginkgo/outline/outline_test.go b/ginkgo/outline/outline_test.go index 544e379b1..b5746c81c 100644 --- a/ginkgo/outline/outline_test.go +++ b/ginkgo/outline/outline_test.go @@ -55,4 +55,5 @@ var _ = DescribeTable("Validate outline from file with", Entry("pending containers and specs", "pending_test.go", "pending_test.go.json", "pending_test.go.csv"), Entry("nested focused containers and specs", "nestedfocused_test.go", "nestedfocused_test.go.json", "nestedfocused_test.go.csv"), Entry("mixed focused containers and specs", "mixed_test.go", "mixed_test.go.json", "mixed_test.go.csv"), + Entry("suite setup", "suite_test.go", "suite_test.go.json", "suite_test.go.csv"), ) From 742968085369637a52d8816266edf64a74971d13 Mon Sep 17 00:00:00 2001 From: Daniel Lipovetsky Date: Mon, 28 Dec 2020 22:03:45 -0800 Subject: [PATCH 13/13] outline: Add "indent" format This is really just for fun. Here's an example: ``shell ginkgo outline -format=indent outline/_testdata/normal_test.go ``` ```shell Name,Text,Start,End,Spec,Focused,Pending Describe,NormalFixture,116,605,false,false,false Describe,normal,152,244,false,false,false It,normal,182,240,true,false,false By,step 1,207,219,false,false,false By,step 2,223,235,false,false,false Context,normal,247,307,false,false,false It,normal,276,303,true,false,false When,normal,310,367,false,false,false It,normal,336,363,true,false,false It,normal,370,396,true,false,false Specify,normal,399,430,true,false,false Measure,normal,433,480,true,false,false ``` It also happens to look nice piped through `column`: ```shell ginkgo outline -format=indent outline/_testdata/normal_test.go | column --table -s="," ``` ``` Name Text Start End Spec Focused Pending Describe NormalFixture 116 605 false false false Describe normal 152 244 false false false It normal 182 240 true false false By step 1 207 219 false false false By step 2 223 235 false false false Context normal 247 307 false false false It normal 276 303 true false false When normal 310 367 false false false It normal 336 363 true false false It normal 370 396 true false false Specify normal 399 430 true false false Measure normal 433 480 true false false ``` --- ginkgo/outline/ginkgo.go | 8 ++++++++ ginkgo/outline/outline.go | 17 +++++++++++++++-- ginkgo/outline_command.go | 9 ++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/ginkgo/outline/ginkgo.go b/ginkgo/outline/ginkgo.go index a105e64b7..2256a2cca 100644 --- a/ginkgo/outline/ginkgo.go +++ b/ginkgo/outline/ginkgo.go @@ -52,6 +52,14 @@ func (n *ginkgoNode) PostOrder(f walkFunc) { f(n) } +func (n *ginkgoNode) Walk(pre, post walkFunc) { + pre(n) + for _, m := range n.Nodes { + m.Walk(pre, post) + } + post(n) +} + // PropagateInheritedProperties propagates the Pending and Focused properties // through the subtree rooted at n. func (n *ginkgoNode) PropagateInheritedProperties() { diff --git a/ginkgo/outline/outline.go b/ginkgo/outline/outline.go index 4eea9332f..200afd39e 100644 --- a/ginkgo/outline/outline.go +++ b/ginkgo/outline/outline.go @@ -77,13 +77,26 @@ func (o *outline) MarshalJSON() ([]byte, error) { // String returns a CSV-formatted outline. Spec or container are output in // depth-first order. func (o *outline) String() string { + return o.StringIndent(0) +} + +// StringIndent returns a CSV-formated outline, but every line is indented by +// one 'width' of spaces for every level of nesting. +func (o *outline) StringIndent(width int) string { var b strings.Builder b.WriteString("Name,Text,Start,End,Spec,Focused,Pending\n") - f := func(n *ginkgoNode) { + + currentIndent := 0 + pre := func(n *ginkgoNode) { + b.WriteString(fmt.Sprintf("%*s", currentIndent, "")) b.WriteString(fmt.Sprintf("%s,%s,%d,%d,%t,%t,%t\n", n.Name, n.Text, n.Start, n.End, n.Spec, n.Focused, n.Pending)) + currentIndent += width + } + post := func(n *ginkgoNode) { + currentIndent -= width } for _, n := range o.Nodes { - n.PreOrder(f) + n.Walk(pre, post) } return b.String() } diff --git a/ginkgo/outline_command.go b/ginkgo/outline_command.go index bc51514c4..6b57bb095 100644 --- a/ginkgo/outline_command.go +++ b/ginkgo/outline_command.go @@ -11,11 +11,16 @@ import ( "github.com/onsi/ginkgo/ginkgo/outline" ) +const ( + // indentWidth is the width used by the 'indent' output + indentWidth = 4 +) + func BuildOutlineCommand() *Command { const defaultFormat = "csv" var format string flagSet := flag.NewFlagSet("outline", flag.ExitOnError) - flagSet.StringVar(&format, "format", defaultFormat, "Format of outline. Accepted: 'csv', 'json'") + flagSet.StringVar(&format, "format", defaultFormat, "Format of outline. Accepted: 'csv', 'indent', 'json'") return &Command{ Name: "outline", FlagSet: flagSet, @@ -54,6 +59,8 @@ func outlineFile(args []string, format string) { switch format { case "csv": _, oerr = fmt.Print(o) + case "indent": + _, oerr = fmt.Print(o.StringIndent(indentWidth)) case "json": b, err := json.Marshal(o) if err != nil {