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

     1  package internal
     2  
     3  import (
     4  	"errors"
     5  	"math/rand"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"regexp"
    10  	"strings"
    11  
    12  	"github.com/onsi/ginkgo/types"
    13  )
    14  
    15  const TIMEOUT_ELAPSED_FAILURE_REASON = "Suite did not run because the timeout elapsed"
    16  const PRIOR_FAILURES_FAILURE_REASON = "Suite did not run because prior suites failed and --keep-going is not set"
    17  const EMPTY_SKIP_FAILURE_REASON = "Suite did not run go test reported that no test files were found"
    18  
    19  type TestSuiteState uint
    20  
    21  const (
    22  	TestSuiteStateInvalid TestSuiteState = iota
    23  
    24  	TestSuiteStateUncompiled
    25  	TestSuiteStateCompiled
    26  
    27  	TestSuiteStatePassed
    28  
    29  	TestSuiteStateSkippedDueToEmptyCompilation
    30  	TestSuiteStateSkippedByFilter
    31  	TestSuiteStateSkippedDueToPriorFailures
    32  
    33  	TestSuiteStateFailed
    34  	TestSuiteStateFailedDueToTimeout
    35  	TestSuiteStateFailedToCompile
    36  )
    37  
    38  var TestSuiteStateFailureStates = []TestSuiteState{TestSuiteStateFailed, TestSuiteStateFailedDueToTimeout, TestSuiteStateFailedToCompile}
    39  
    40  func (state TestSuiteState) Is(states ...TestSuiteState) bool {
    41  	for _, suiteState := range states {
    42  		if suiteState == state {
    43  			return true
    44  		}
    45  	}
    46  
    47  	return false
    48  }
    49  
    50  type TestSuite struct {
    51  	Path        string
    52  	PackageName string
    53  	IsGinkgo    bool
    54  
    55  	Precompiled        bool
    56  	PathToCompiledTest string
    57  	CompilationError   error
    58  
    59  	HasProgrammaticFocus bool
    60  	State                TestSuiteState
    61  }
    62  
    63  func (ts TestSuite) AbsPath() string {
    64  	path, _ := filepath.Abs(ts.Path)
    65  	return path
    66  }
    67  
    68  func (ts TestSuite) NamespacedName() string {
    69  	name := relPath(ts.Path)
    70  	name = strings.TrimLeft(name, "."+string(filepath.Separator))
    71  	name = strings.ReplaceAll(name, string(filepath.Separator), "_")
    72  	name = strings.ReplaceAll(name, " ", "_")
    73  	if name == "" {
    74  		return ts.PackageName
    75  	}
    76  	return name
    77  }
    78  
    79  type TestSuites []TestSuite
    80  
    81  func (ts TestSuites) AnyHaveProgrammaticFocus() bool {
    82  	for _, suite := range ts {
    83  		if suite.HasProgrammaticFocus {
    84  			return true
    85  		}
    86  	}
    87  
    88  	return false
    89  }
    90  
    91  func (ts TestSuites) ThatAreGinkgoSuites() TestSuites {
    92  	out := TestSuites{}
    93  	for _, suite := range ts {
    94  		if suite.IsGinkgo {
    95  			out = append(out, suite)
    96  		}
    97  	}
    98  	return out
    99  }
   100  
   101  func (ts TestSuites) CountWithState(states ...TestSuiteState) int {
   102  	n := 0
   103  	for _, suite := range ts {
   104  		if suite.State.Is(states...) {
   105  			n += 1
   106  		}
   107  	}
   108  
   109  	return n
   110  }
   111  
   112  func (ts TestSuites) WithState(states ...TestSuiteState) TestSuites {
   113  	out := TestSuites{}
   114  	for _, suite := range ts {
   115  		if suite.State.Is(states...) {
   116  			out = append(out, suite)
   117  		}
   118  	}
   119  
   120  	return out
   121  }
   122  
   123  func (ts TestSuites) WithoutState(states ...TestSuiteState) TestSuites {
   124  	out := TestSuites{}
   125  	for _, suite := range ts {
   126  		if !suite.State.Is(states...) {
   127  			out = append(out, suite)
   128  		}
   129  	}
   130  
   131  	return out
   132  }
   133  
   134  func (ts TestSuites) ShuffledCopy(seed int64) TestSuites {
   135  	out := make(TestSuites, len(ts))
   136  	permutation := rand.New(rand.NewSource(seed)).Perm(len(ts))
   137  	for i, j := range permutation {
   138  		out[i] = ts[j]
   139  	}
   140  	return out
   141  }
   142  
   143  func FindSuites(args []string, cliConfig types.CLIConfig, allowPrecompiled bool) TestSuites {
   144  	suites := TestSuites{}
   145  
   146  	if len(args) > 0 {
   147  		for _, arg := range args {
   148  			if allowPrecompiled {
   149  				suite, err := precompiledTestSuite(arg)
   150  				if err == nil {
   151  					suites = append(suites, suite)
   152  					continue
   153  				}
   154  			}
   155  			recurseForSuite := cliConfig.Recurse
   156  			if strings.HasSuffix(arg, "/...") && arg != "/..." {
   157  				arg = arg[:len(arg)-4]
   158  				recurseForSuite = true
   159  			}
   160  			suites = append(suites, suitesInDir(arg, recurseForSuite)...)
   161  		}
   162  	} else {
   163  		suites = suitesInDir(".", cliConfig.Recurse)
   164  	}
   165  
   166  	if cliConfig.SkipPackage != "" {
   167  		skipFilters := strings.Split(cliConfig.SkipPackage, ",")
   168  		for idx := range suites {
   169  			for _, skipFilter := range skipFilters {
   170  				if strings.Contains(suites[idx].Path, skipFilter) {
   171  					suites[idx].State = TestSuiteStateSkippedByFilter
   172  					break
   173  				}
   174  			}
   175  		}
   176  	}
   177  
   178  	return suites
   179  }
   180  
   181  func precompiledTestSuite(path string) (TestSuite, error) {
   182  	info, err := os.Stat(path)
   183  	if err != nil {
   184  		return TestSuite{}, err
   185  	}
   186  
   187  	if info.IsDir() {
   188  		return TestSuite{}, errors.New("this is a directory, not a file")
   189  	}
   190  
   191  	if filepath.Ext(path) != ".test" && filepath.Ext(path) != ".exe" {
   192  		return TestSuite{}, errors.New("this is not a .test binary")
   193  	}
   194  
   195  	if filepath.Ext(path) == ".test" && info.Mode()&0111 == 0 {
   196  		return TestSuite{}, errors.New("this is not executable")
   197  	}
   198  
   199  	dir := relPath(filepath.Dir(path))
   200  	packageName := strings.TrimSuffix(filepath.Base(path), ".exe")
   201  	packageName = strings.TrimSuffix(packageName, ".test")
   202  
   203  	path, err = filepath.Abs(path)
   204  	if err != nil {
   205  		return TestSuite{}, err
   206  	}
   207  
   208  	return TestSuite{
   209  		Path:               dir,
   210  		PackageName:        packageName,
   211  		IsGinkgo:           true,
   212  		Precompiled:        true,
   213  		PathToCompiledTest: path,
   214  		State:              TestSuiteStateCompiled,
   215  	}, nil
   216  }
   217  
   218  func suitesInDir(dir string, recurse bool) TestSuites {
   219  	suites := TestSuites{}
   220  
   221  	if path.Base(dir) == "vendor" {
   222  		return suites
   223  	}
   224  
   225  	files, _ := os.ReadDir(dir)
   226  	re := regexp.MustCompile(`^[^._].*_test\.go$`)
   227  	for _, file := range files {
   228  		if !file.IsDir() && re.Match([]byte(file.Name())) {
   229  			suite := TestSuite{
   230  				Path:        relPath(dir),
   231  				PackageName: packageNameForSuite(dir),
   232  				IsGinkgo:    filesHaveGinkgoSuite(dir, files),
   233  				State:       TestSuiteStateUncompiled,
   234  			}
   235  			suites = append(suites, suite)
   236  			break
   237  		}
   238  	}
   239  
   240  	if recurse {
   241  		re = regexp.MustCompile(`^[._]`)
   242  		for _, file := range files {
   243  			if file.IsDir() && !re.Match([]byte(file.Name())) {
   244  				suites = append(suites, suitesInDir(dir+"/"+file.Name(), recurse)...)
   245  			}
   246  		}
   247  	}
   248  
   249  	return suites
   250  }
   251  
   252  func relPath(dir string) string {
   253  	dir, _ = filepath.Abs(dir)
   254  	cwd, _ := os.Getwd()
   255  	dir, _ = filepath.Rel(cwd, filepath.Clean(dir))
   256  
   257  	if string(dir[0]) != "." {
   258  		dir = "." + string(filepath.Separator) + dir
   259  	}
   260  
   261  	return dir
   262  }
   263  
   264  func packageNameForSuite(dir string) string {
   265  	path, _ := filepath.Abs(dir)
   266  	return filepath.Base(path)
   267  }
   268  
   269  func filesHaveGinkgoSuite(dir string, files []os.DirEntry) bool {
   270  	reTestFile := regexp.MustCompile(`_test\.go$`)
   271  	reGinkgo := regexp.MustCompile(`package ginkgo|\/ginkgo"`)
   272  
   273  	for _, file := range files {
   274  		if !file.IsDir() && reTestFile.Match([]byte(file.Name())) {
   275  			contents, _ := os.ReadFile(dir + "/" + file.Name())
   276  			if reGinkgo.Match(contents) {
   277  				return true
   278  			}
   279  		}
   280  	}
   281  
   282  	return false
   283  }