Skip to content

Commit

Permalink
Introduce GinkgoLabelFilter() and Label().MatchesLabelFilter() to mak…
Browse files Browse the repository at this point in the history
…e it possible to programatically match filters
  • Loading branch information
onsi committed Jan 30, 2023
1 parent ea4966e commit 2f6597c
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 1 deletion.
14 changes: 14 additions & 0 deletions core_dsl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 15 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions dsl/core/core_dsl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions integration/_fixtures/filter_fixture/filter_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
})
4 changes: 4 additions & 0 deletions internal/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down
16 changes: 16 additions & 0 deletions internal/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
11 changes: 11 additions & 0 deletions types/label_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
14 changes: 14 additions & 0 deletions types/label_filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -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())
})
})
})

0 comments on commit 2f6597c

Please sign in to comment.