diff --git a/core_dsl.go b/core_dsl.go index 47d2efee7..5e78a92f3 100644 --- a/core_dsl.go +++ b/core_dsl.go @@ -172,6 +172,20 @@ func GinkgoHelper() { types.MarkAsHelper(1) } +/* +GinkgoLabelFilter() returns the label filter configured for this suite via `--label-filter`. + +You can use this to manually check if a set of labels would satisfy the filter via: + + if (Label("cat", "dog").MatchesLabelFilter(GinkgoLabelFilter())) { + //... + } +*/ +func GinkgoLabelFilter() string { + suiteConfig, _ := GinkgoConfiguration() + return suiteConfig.LabelFilter +} + /* PauseOutputInterception() pauses Ginkgo's output interception. This is only relevant when running in parallel and output to stdout/stderr is being intercepted. You generally diff --git a/docs/index.md b/docs/index.md index b1da6ce94..92c52a457 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1362,7 +1362,7 @@ DescribeTable("Extracting the author's first and last name", You'll be notified with a clear message at runtime if the parameter types don't match the spec closure signature. #### Mental Model: Table Specs are just Syntactic Sugar -`DescribeTable` is simply providing syntactic sugar to convert its inputs into a set of standard Ginkgo nodes. During the [Tree Construction Phase](#mental-model-how-ginkgo-traverses-the-spec-hierarchy) `DescribeTable` is generating a single container node that contains one subject node per table entry. The description for the container node will be the description passed to `DescribeTable` and the descriptions for the subject nodes will be the descriptions passed to the `Entry`s. During the Run Phase, when specs run, each subject node will simply invoke the spec closure passed to `DescribeTable`, passing in the parameters associated with the `Entry`. +`DescribeTable` is simply providing syntactic sugar to convert its Ls into a set of standard Ginkgo nodes. During the [Tree Construction Phase](#mental-model-how-ginkgo-traverses-the-spec-hierarchy) `DescribeTable` is generating a single container node that contains one subject node per table entry. The description for the container node will be the description passed to `DescribeTable` and the descriptions for the subject nodes will be the descriptions passed to the `Entry`s. During the Run Phase, when specs run, each subject node will simply invoke the spec closure passed to `DescribeTable`, passing in the parameters associated with the `Entry`. To put it another way, the table test above is equivalent to: @@ -2493,6 +2493,20 @@ You can list the labels used in a given package using the `ginkgo labels` subcom You can iterate on different filters quickly with `ginkgo --dry-run -v --label-filter=FILTER`. This will cause Ginkgo to tell you which specs it will run for a given filter without actually running anything. +If you want to have finer-grained control within a test about what code to run/not-run depending on what labels match/don't match the filter you can perform a manual check against the label-filter passed into Ginkgo like so: + +```go +It("can save books remotely", Label("network", "slow", "library query") { + if Label("performance").MatchesLabelFilter(GinkgoLabelFilter()) { + exp := gmeasure.NewExperiment() + // perform some benchmarking with exp... + } + // rest of the saving books test +}) +``` + +here `GinkgoLabelFilter()` returns the configured label filter passed in via `--label-filter`. With a setup like this you could run `ginkgo --label-filter="network && !performance"` - this would select the `"can save books remotely"` spec but not run the benchmarking code in the spec. Of course, this could also have been modeled as a separate spec with the `performance` label. + Finally, in addition to specifying Labels on subject and container nodes you can also specify suite-wide labels by decorating the `RunSpecs` command with `Label`: ```go diff --git a/dsl/core/core_dsl.go b/dsl/core/core_dsl.go index b4287b773..8314d7d82 100644 --- a/dsl/core/core_dsl.go +++ b/dsl/core/core_dsl.go @@ -29,6 +29,7 @@ var GinkgoConfiguration = ginkgo.GinkgoConfiguration var GinkgoRandomSeed = ginkgo.GinkgoRandomSeed var GinkgoParallelProcess = ginkgo.GinkgoParallelProcess var GinkgoHelper = ginkgo.GinkgoHelper +var GinkgoLabelFilter = ginkgo.GinkgoLabelFilter var PauseOutputInterception = ginkgo.PauseOutputInterception var ResumeOutputInterception = ginkgo.ResumeOutputInterception var RunSpecs = ginkgo.RunSpecs diff --git a/integration/_fixtures/filter_fixture/filter_suite_test.go b/integration/_fixtures/filter_fixture/filter_suite_test.go index 94f12ecb0..ac6a5e782 100644 --- a/integration/_fixtures/filter_fixture/filter_suite_test.go +++ b/integration/_fixtures/filter_fixture/filter_suite_test.go @@ -11,3 +11,8 @@ func TestFilterFixture(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "FilterFixture Suite", Label("TopLevelLabel")) } + +var _ = BeforeEach(func() { + config, _ := GinkgoConfiguration() + Ω(GinkgoLabelFilter()).Should(Equal(config.LabelFilter)) +}) diff --git a/internal/node.go b/internal/node.go index 9bbd258c0..0869bffb3 100644 --- a/internal/node.go +++ b/internal/node.go @@ -93,6 +93,10 @@ type NodeTimeout time.Duration type SpecTimeout time.Duration type GracePeriod time.Duration +func (l Labels) MatchesLabelFilter(query string) bool { + return types.MustParseLabelFilter(query)(l) +} + func UnionOfLabels(labels ...Labels) Labels { out := Labels{} seen := map[string]bool{} diff --git a/internal/node_test.go b/internal/node_test.go index 0e5134c41..588053a76 100644 --- a/internal/node_test.go +++ b/internal/node_test.go @@ -1621,6 +1621,22 @@ var _ = Describe("Nodes", func() { }) }) + + Describe("Labels", func() { + It("can match against a filter", func() { + Ω(Label().MatchesLabelFilter("")).Should(BeTrue()) + Ω(Label("dog", "cat").MatchesLabelFilter("dog")).Should(BeTrue()) + Ω(Label("dog", "cat").MatchesLabelFilter("cat")).Should(BeTrue()) + Ω(Label("dog", "cat").MatchesLabelFilter("dog && cat")).Should(BeTrue()) + Ω(Label("dog", "cat").MatchesLabelFilter("dog || cat")).Should(BeTrue()) + Ω(Label("dog", "cat").MatchesLabelFilter("!fish")).Should(BeTrue()) + Ω(Label("dog", "cat").MatchesLabelFilter("fish")).Should(BeFalse()) + Ω(Label("dog", "cat").MatchesLabelFilter("!dog")).Should(BeFalse()) + Ω(func() { + Label("dog", "cat").MatchesLabelFilter("!") + }).Should(Panic()) + }) + }) }) var _ = Describe("Iteration Performance", Serial, Label("performance"), func() { diff --git a/types/label_filter.go b/types/label_filter.go index 0403f9e63..b0d3b651e 100644 --- a/types/label_filter.go +++ b/types/label_filter.go @@ -272,12 +272,23 @@ func tokenize(input string) func() (*treeNode, error) { } } +func MustParseLabelFilter(input string) LabelFilter { + filter, err := ParseLabelFilter(input) + if err != nil { + panic(err) + } + return filter +} + func ParseLabelFilter(input string) (LabelFilter, error) { if DEBUG_LABEL_FILTER_PARSING { fmt.Println("\n==============") fmt.Println("Input: ", input) fmt.Print("Tokens: ") } + if input == "" { + return func(_ []string) bool { return true }, nil + } nextToken := tokenize(input) root := &treeNode{token: lfTokenRoot} diff --git a/types/label_filter_test.go b/types/label_filter_test.go index 50ddf2b83..cca3d2166 100644 --- a/types/label_filter_test.go +++ b/types/label_filter_test.go @@ -68,6 +68,10 @@ var _ = Describe("LabelFilter", func() { } } }, + Entry("An empty label", "", + M("cat"), M("cat", "dog"), M("dog", "cat"), + M(), M("cow"), + ), Entry("A single label", "cat", M("cat"), M("cat", "dog"), M("dog", "cat"), NM(), NM("cow"), @@ -187,4 +191,14 @@ var _ = Describe("LabelFilter", func() { Entry(nil, "cow)", "", types.GinkgoErrors.InvalidLabel("cow)", cl)), Entry(nil, "cow/", "", types.GinkgoErrors.InvalidLabel("cow/", cl)), ) + + Describe("MustParseLabelFilter", func() { + It("panics if passed an invalid filter", func() { + Ω(types.MustParseLabelFilter("dog")([]string{"dog"})).Should(BeTrue()) + Ω(types.MustParseLabelFilter("dog")([]string{"cat"})).Should(BeFalse()) + Ω(func() { + types.MustParseLabelFilter("!") + }).Should(Panic()) + }) + }) })