From 071c3690e527de53047210909ae403d837d958bb Mon Sep 17 00:00:00 2001 From: Daniel Lipovetsky <3445370+dlipovetsky@users.noreply.github.com> Date: Wed, 30 Dec 2020 14:53:07 -0800 Subject: [PATCH] Adds 'outline' command to print the outline of specs/containers in a file (#754) * Adds 'outline' command to print the outline of specs/containers in a file Implements feature request in #753 * outline: Add support for nodot and alias import of the ginkgo package * 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. * outline: Add backpropagation of unfocus, propagation of inherited focus/pending properties * outline: Add instructions and script for creating/updating sample test results To make it easier to maintain the outline tests. * 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. * outline: Factor the back/propagation code to helper functions The top-level function needed to be shorter. * outline: Refactor deriving ginkgo identifier from call expression Again, the top-level function needed to be shorter. * outline: Move the exported outline code into its own file Keep the unexprted ginkgoNode code in a separate file. * 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. * outline: (fix) Derive the "text" of the By ginkgo call The "text" was being ignored by mistake. * 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. * 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/main.go | 6 + ginkgo/outline/_testdata/alias_test.go | 49 ++++ ginkgo/outline/_testdata/alias_test.go.csv | 13 ++ ginkgo/outline/_testdata/alias_test.go.json | 1 + ginkgo/outline/_testdata/create_result.sh | 17 ++ ginkgo/outline/_testdata/focused_test.go | 49 ++++ ginkgo/outline/_testdata/focused_test.go.csv | 13 ++ ginkgo/outline/_testdata/focused_test.go.json | 1 + ginkgo/outline/_testdata/mixed_test.go | 45 ++++ ginkgo/outline/_testdata/mixed_test.go.csv | 18 ++ ginkgo/outline/_testdata/mixed_test.go.json | 1 + .../outline/_testdata/nestedfocused_test.go | 36 +++ .../_testdata/nestedfocused_test.go.csv | 15 ++ .../_testdata/nestedfocused_test.go.json | 1 + ginkgo/outline/_testdata/nodot_test.go | 48 ++++ ginkgo/outline/_testdata/nodot_test.go.csv | 13 ++ ginkgo/outline/_testdata/nodot_test.go.json | 1 + ginkgo/outline/_testdata/normal_test.go | 49 ++++ ginkgo/outline/_testdata/normal_test.go.csv | 13 ++ ginkgo/outline/_testdata/normal_test.go.json | 1 + ginkgo/outline/_testdata/pending_test.go | 49 ++++ ginkgo/outline/_testdata/pending_test.go.csv | 13 ++ ginkgo/outline/_testdata/pending_test.go.json | 1 + ginkgo/outline/_testdata/suite_test.go | 13 ++ ginkgo/outline/_testdata/suite_test.go.csv | 1 + ginkgo/outline/_testdata/suite_test.go.json | 1 + ginkgo/outline/ginkgo.go | 209 ++++++++++++++++++ ginkgo/outline/import.go | 61 +++++ ginkgo/outline/outline.go | 102 +++++++++ ginkgo/outline/outline_suite_test.go | 13 ++ ginkgo/outline/outline_test.go | 59 +++++ ginkgo/outline_command.go | 77 +++++++ go.mod | 4 +- go.sum | 28 ++- 34 files changed, 1013 insertions(+), 8 deletions(-) create mode 100644 ginkgo/outline/_testdata/alias_test.go create mode 100644 ginkgo/outline/_testdata/alias_test.go.csv create mode 100644 ginkgo/outline/_testdata/alias_test.go.json create mode 100644 ginkgo/outline/_testdata/create_result.sh create mode 100644 ginkgo/outline/_testdata/focused_test.go create mode 100644 ginkgo/outline/_testdata/focused_test.go.csv create mode 100644 ginkgo/outline/_testdata/focused_test.go.json create mode 100644 ginkgo/outline/_testdata/mixed_test.go create mode 100644 ginkgo/outline/_testdata/mixed_test.go.csv create mode 100644 ginkgo/outline/_testdata/mixed_test.go.json create mode 100644 ginkgo/outline/_testdata/nestedfocused_test.go create mode 100644 ginkgo/outline/_testdata/nestedfocused_test.go.csv create mode 100644 ginkgo/outline/_testdata/nestedfocused_test.go.json create mode 100644 ginkgo/outline/_testdata/nodot_test.go create mode 100644 ginkgo/outline/_testdata/nodot_test.go.csv create mode 100644 ginkgo/outline/_testdata/nodot_test.go.json create mode 100644 ginkgo/outline/_testdata/normal_test.go create mode 100644 ginkgo/outline/_testdata/normal_test.go.csv create mode 100644 ginkgo/outline/_testdata/normal_test.go.json create mode 100644 ginkgo/outline/_testdata/pending_test.go create mode 100644 ginkgo/outline/_testdata/pending_test.go.csv create mode 100644 ginkgo/outline/_testdata/pending_test.go.json create mode 100644 ginkgo/outline/_testdata/suite_test.go create mode 100644 ginkgo/outline/_testdata/suite_test.go.csv create mode 100644 ginkgo/outline/_testdata/suite_test.go.json create mode 100644 ginkgo/outline/ginkgo.go create mode 100644 ginkgo/outline/import.go 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/alias_test.go b/ginkgo/outline/_testdata/alias_test.go new file mode 100644 index 000000000..bf9a29075 --- /dev/null +++ b/ginkgo/outline/_testdata/alias_test.go @@ -0,0 +1,49 @@ +package example_test + +import ( + fooginkgo "github.com/onsi/ginkgo" +) + +var _ = fooginkgo.Describe("NodotFixture", func() { + fooginkgo.Describe("normal", func() { + fooginkgo.It("normal", func() { + fooginkgo.By("normal") + fooginkgo.By("normal") + + }) + }) + + 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.go.csv b/ginkgo/outline/_testdata/alias_test.go.csv new file mode 100644 index 000000000..267f905a1 --- /dev/null +++ b/ginkgo/outline/_testdata/alias_test.go.csv @@ -0,0 +1,13 @@ +Name,Text,Start,End,Spec,Focused,Pending +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 new file mode 100644 index 000000000..885de0a80 --- /dev/null +++ b/ginkgo/outline/_testdata/alias_test.go.json @@ -0,0 +1 @@ +[{"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/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/_testdata/focused_test.go b/ginkgo/outline/_testdata/focused_test.go new file mode 100644 index 000000000..e9f5a5d52 --- /dev/null +++ b/ginkgo/outline/_testdata/focused_test.go @@ -0,0 +1,49 @@ +package example_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" +) + +var _ = Describe("unfocused", func() { + FDescribe("focused", func() { + It("focused", func() { + By("focused") + By("focused") + }) + }) + + 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.go.csv b/ginkgo/outline/_testdata/focused_test.go.csv new file mode 100644 index 000000000..d49522daf --- /dev/null +++ b/ginkgo/outline/_testdata/focused_test.go.csv @@ -0,0 +1,13 @@ +Name,Text,Start,End,Spec,Focused,Pending +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 new file mode 100644 index 000000000..2c042225d --- /dev/null +++ b/ginkgo/outline/_testdata/focused_test.go.json @@ -0,0 +1 @@ +[{"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 new file mode 100644 index 000000000..6e91303f2 --- /dev/null +++ b/ginkgo/outline/_testdata/mixed_test.go @@ -0,0 +1,45 @@ +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() { + 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 new file mode 100644 index 000000000..819c222a1 --- /dev/null +++ b/ginkgo/outline/_testdata/mixed_test.go.csv @@ -0,0 +1,18 @@ +Name,Text,Start,End,Spec,Focused,Pending +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 +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,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 new file mode 100644 index 000000000..9db2e1271 --- /dev/null +++ b/ginkgo/outline/_testdata/mixed_test.go.json @@ -0,0 +1 @@ +[{"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 new file mode 100644 index 000000000..905b1c864 --- /dev/null +++ b/ginkgo/outline/_testdata/nestedfocused_test.go @@ -0,0 +1,36 @@ +package example_test + +import ( + . "github.com/onsi/ginkgo" +) + +var _ = FDescribe("unfocused", func() { + FContext("unfocused", func() { + It("unfocused", func() { + By("unfocused") + By("unfocused") + }) + FIt("focused", func() { + By("focused") + By("focused") + }) + }) + + 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.go.csv b/ginkgo/outline/_testdata/nestedfocused_test.go.csv new file mode 100644 index 000000000..b91cfa0dd --- /dev/null +++ b/ginkgo/outline/_testdata/nestedfocused_test.go.csv @@ -0,0 +1,15 @@ +Name,Text,Start,End,Spec,Focused,Pending +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 new file mode 100644 index 000000000..86e0ba4a6 --- /dev/null +++ b/ginkgo/outline/_testdata/nestedfocused_test.go.json @@ -0,0 +1 @@ +[{"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 new file mode 100644 index 000000000..13525db75 --- /dev/null +++ b/ginkgo/outline/_testdata/nodot_test.go @@ -0,0 +1,48 @@ +package example_test + +import ( + "github.com/onsi/ginkgo" +) + +var _ = ginkgo.Describe("NodotFixture", func() { + ginkgo.Describe("normal", func() { + ginkgo.It("normal", func() { + ginkgo.By("normal") + ginkgo.By("normal") + }) + }) + + 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.go.csv b/ginkgo/outline/_testdata/nodot_test.go.csv new file mode 100644 index 000000000..eecd3b9d5 --- /dev/null +++ b/ginkgo/outline/_testdata/nodot_test.go.csv @@ -0,0 +1,13 @@ +Name,Text,Start,End,Spec,Focused,Pending +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 new file mode 100644 index 000000000..d476b820c --- /dev/null +++ b/ginkgo/outline/_testdata/nodot_test.go.json @@ -0,0 +1 @@ +[{"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 new file mode 100644 index 000000000..8d8758120 --- /dev/null +++ b/ginkgo/outline/_testdata/normal_test.go @@ -0,0 +1,49 @@ +package example_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" +) + +var _ = Describe("NormalFixture", func() { + Describe("normal", func() { + It("normal", func() { + By("step 1") + By("step 2") + }) + }) + + 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.go.csv b/ginkgo/outline/_testdata/normal_test.go.csv new file mode 100644 index 000000000..569f336cf --- /dev/null +++ b/ginkgo/outline/_testdata/normal_test.go.csv @@ -0,0 +1,13 @@ +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 diff --git a/ginkgo/outline/_testdata/normal_test.go.json b/ginkgo/outline/_testdata/normal_test.go.json new file mode 100644 index 000000000..43b97a2b9 --- /dev/null +++ b/ginkgo/outline/_testdata/normal_test.go.json @@ -0,0 +1 @@ +[{"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 new file mode 100644 index 000000000..b4dd53ee2 --- /dev/null +++ b/ginkgo/outline/_testdata/pending_test.go @@ -0,0 +1,49 @@ +package example_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" +) + +var _ = Describe("PendingFixture", func() { + PDescribe("pending", func() { + It("pending", func() { + By("pending") + By("pending") + }) + }) + + 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.go.csv b/ginkgo/outline/_testdata/pending_test.go.csv new file mode 100644 index 000000000..831c8d9da --- /dev/null +++ b/ginkgo/outline/_testdata/pending_test.go.csv @@ -0,0 +1,13 @@ +Name,Text,Start,End,Spec,Focused,Pending +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 new file mode 100644 index 000000000..0014d4a2c --- /dev/null +++ b/ginkgo/outline/_testdata/pending_test.go.json @@ -0,0 +1 @@ +[{"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/_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.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/ginkgo.go b/ginkgo/outline/ginkgo.go new file mode 100644 index 000000000..2256a2cca --- /dev/null +++ b/ginkgo/outline/ginkgo.go @@ -0,0 +1,209 @@ +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) +} + +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() { + 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": + n.Text = textOrAltFromCallExpr(ce, undefinedTextAlt) + 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/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 new file mode 100644 index 000000000..200afd39e --- /dev/null +++ b/ginkgo/outline/outline.go @@ -0,0 +1,102 @@ +package outline + +import ( + "encoding/json" + "fmt" + "go/ast" + "strings" + + "golang.org/x/tools/go/ast/inspector" +) + +const ( + // ginkgoImportPath is the well-known ginkgo import path + ginkgoImportPath = "github.com/onsi/ginkgo" +) + +// 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{}, + } + stack := []*ginkgoNode{&root} + + ispr := inspector.New([]*ast.File{src}) + ispr.Nodes([]ast.Node{(*ast.CallExpr)(nil)}, func(node ast.Node, push bool) bool { + if push { + // Pre-order traversal + 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] + parent.Nodes = append(parent.Nodes, gn) + + stack = append(stack, gn) + return true + } + // 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 + return true + } + stack = stack[0 : len(stack)-1] + return 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 +} + +type outline struct { + 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 { + 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") + + 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.Walk(pre, post) + } + 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..b5746c81c --- /dev/null +++ b/ginkgo/outline/outline_test.go @@ -0,0 +1,59 @@ +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))) + }, + // 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.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"), + Entry("suite setup", "suite_test.go", "suite_test.go.json", "suite_test.go.csv"), +) diff --git a/ginkgo/outline_command.go b/ginkgo/outline_command.go new file mode 100644 index 000000000..6b57bb095 --- /dev/null +++ b/ginkgo/outline_command.go @@ -0,0 +1,77 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "go/parser" + "go/token" + "os" + + "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', 'indent', '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 "indent": + _, oerr = fmt.Print(o.StringIndent(indentWidth)) + 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=