github.com/onsi/ginkgo@v1.16.6-0.20211118180735-4e1925ba4c95/internal/focus.go (about)

     1  package internal
     2  
     3  import (
     4  	"regexp"
     5  	"strings"
     6  
     7  	"github.com/onsi/ginkgo/types"
     8  )
     9  
    10  /*
    11  	If a container marked as focus has a descendant that is also marked as focus, Ginkgo's policy is to
    12  	unmark the container's focus.  This gives developers a more intuitive experience when debugging specs.
    13  	It is common to focus a container to just run a subset of specs, then identify the specific specs within the container to focus -
    14  	this policy allows the developer to simply focus those specific specs and not need to go back and turn the focus off of the container:
    15  
    16  	As a common example, consider:
    17  
    18  		FDescribe("something to debug", function() {
    19  			It("works", function() {...})
    20  			It("works", function() {...})
    21  			FIt("doesn't work", function() {...})
    22  			It("works", function() {...})
    23  		})
    24  
    25  	here the developer's intent is to focus in on the `"doesn't work"` spec and not to run the adjacent specs in the focused `"something to debug"` container.
    26  	The nested policy applied by this function enables this behavior.
    27  */
    28  func ApplyNestedFocusPolicyToTree(tree *TreeNode) {
    29  	var walkTree func(tree *TreeNode) bool
    30  	walkTree = func(tree *TreeNode) bool {
    31  		if tree.Node.MarkedPending {
    32  			return false
    33  		}
    34  		hasFocusedDescendant := false
    35  		for _, child := range tree.Children {
    36  			childHasFocus := walkTree(child)
    37  			hasFocusedDescendant = hasFocusedDescendant || childHasFocus
    38  		}
    39  		tree.Node.MarkedFocus = tree.Node.MarkedFocus && !hasFocusedDescendant
    40  		return tree.Node.MarkedFocus || hasFocusedDescendant
    41  	}
    42  
    43  	walkTree(tree)
    44  }
    45  
    46  /*
    47  	Ginkgo supports focussing specs using `FIt`, `FDescribe`, etc. - this is called "programmatic focus"
    48  	It also supports focussing specs using regular expressions on the command line (`-focus=`, `-skip=`) that match against spec text
    49  	and file filters (`-focus-files=`, `-skip-files=`) that match against code locations for nodes in specs.
    50  
    51  	If any of the CLI flags are provided they take precedence.  The file filters run first followed by the regex filters.
    52  
    53  	This function sets the `Skip` property on specs by applying Ginkgo's focus policy:
    54  	- If there are no CLI arguments and no programmatic focus, do nothing.
    55  	- If there are no CLI arguments but a spec somewhere has programmatic focus, skip any specs that have no programmatic focus.
    56  	- If there are CLI arguments parse them and skip any specs that either don't match the focus filters or do match the skip filters.
    57  
    58  	*Note:* specs with pending nodes are Skipped when created by NewSpec.
    59  */
    60  func ApplyFocusToSpecs(specs Specs, description string, suiteLabels Labels, suiteConfig types.SuiteConfig) (Specs, bool) {
    61  	focusString := strings.Join(suiteConfig.FocusStrings, "|")
    62  	skipString := strings.Join(suiteConfig.SkipStrings, "|")
    63  
    64  	hasFocusCLIFlags := focusString != "" || skipString != "" || len(suiteConfig.SkipFiles) > 0 || len(suiteConfig.FocusFiles) > 0 || suiteConfig.LabelFilter != ""
    65  
    66  	type SkipCheck func(spec Spec) bool
    67  
    68  	// by default, skip any specs marked pending
    69  	skipChecks := []SkipCheck{func(spec Spec) bool { return spec.Nodes.HasNodeMarkedPending() }}
    70  	hasProgrammaticFocus := false
    71  
    72  	if !hasFocusCLIFlags {
    73  		// check for programmatic focus
    74  		for _, spec := range specs {
    75  			if spec.Nodes.HasNodeMarkedFocus() && !spec.Nodes.HasNodeMarkedPending() {
    76  				skipChecks = append(skipChecks, func(spec Spec) bool { return !spec.Nodes.HasNodeMarkedFocus() })
    77  				hasProgrammaticFocus = true
    78  				break
    79  			}
    80  		}
    81  	}
    82  
    83  	if suiteConfig.LabelFilter != "" {
    84  		labelFilter, _ := types.ParseLabelFilter(suiteConfig.LabelFilter)
    85  		skipChecks = append(skipChecks, func(spec Spec) bool { 
    86  			return !labelFilter(UnionOfLabels(suiteLabels, spec.Nodes.UnionOfLabels())) 
    87  		})
    88  	}
    89  
    90  	if len(suiteConfig.FocusFiles) > 0 {
    91  		focusFilters, _ := types.ParseFileFilters(suiteConfig.FocusFiles)
    92  		skipChecks = append(skipChecks, func(spec Spec) bool { return !focusFilters.Matches(spec.Nodes.CodeLocations()) })
    93  	}
    94  
    95  	if len(suiteConfig.SkipFiles) > 0 {
    96  		skipFilters, _ := types.ParseFileFilters(suiteConfig.SkipFiles)
    97  		skipChecks = append(skipChecks, func(spec Spec) bool { return skipFilters.Matches(spec.Nodes.CodeLocations()) })
    98  	}
    99  
   100  	if focusString != "" {
   101  		// skip specs that don't match the focus string
   102  		re := regexp.MustCompile(focusString)
   103  		skipChecks = append(skipChecks, func(spec Spec) bool { return !re.MatchString(description + " " + spec.Text()) })
   104  	}
   105  
   106  	if skipString != "" {
   107  		// skip specs that match the skip string
   108  		re := regexp.MustCompile(skipString)
   109  		skipChecks = append(skipChecks, func(spec Spec) bool { return re.MatchString(description + " " + spec.Text()) })
   110  	}
   111  
   112  	// skip specs if shouldSkip() is true.  note that we do nothing if shouldSkip() is false to avoid overwriting skip status established by the node's pending status
   113  	processedSpecs := Specs{}
   114  	for _, spec := range specs {
   115  		for _, skipCheck := range skipChecks {
   116  			if skipCheck(spec) {
   117  				spec.Skip = true
   118  				break
   119  			}
   120  		}
   121  		processedSpecs = append(processedSpecs, spec)
   122  	}
   123  
   124  	return processedSpecs, hasProgrammaticFocus
   125  }