github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/e2e/framework/framework.go (about)

     1  package framework
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"log"
     7  	"reflect"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/hashicorp/nomad/ci"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  const frameworkHelp = `
    16  Usage: go test -v ./e2e [options]
    17  
    18  These flags are coarse overrides for the test environment.
    19  
    20    -forceRun    skip all environment checks when filtering test suites
    21    -local       denotes execution is against a local environment
    22    -slow        include execution of slow test suites
    23    -suite       run specified test suite
    24    -showHelp    shows this help text
    25  
    26  TestSuites can request Constraints on the Framework.Environment so that tests
    27  are only run in the appropriate conditions. These environment flags provide
    28  the information for those constraints.
    29  
    30    -env=string           name of the environment
    31    -env.arch=string      cpu architecture of the targets
    32    -env.os=string        operating system of the targets
    33    -env.provider=string  cloud provider of the environment
    34    -env.tags=string      comma delimited list of tags for the environment
    35  
    36  `
    37  
    38  var fHelp = flag.Bool("showHelp", false, "print the help screen")
    39  var fLocal = flag.Bool("local", false,
    40  	"denotes execution is against a local environment")
    41  var fSlow = flag.Bool("slow", false, "toggles execution of slow test suites")
    42  var fForceRun = flag.Bool("forceRun", false,
    43  	"if set, skips all environment checks when filtering test suites")
    44  var fSuite = flag.String("suite", "", "run specified test suite")
    45  
    46  // Environment flags
    47  // TODO:
    48  // It would be nice if we could match the target environment against
    49  // the tests automatically so that we always avoid running tests that
    50  // don't apply, and then have these flags override that behavior.
    51  var fEnv = flag.String("env", "", "name of the environment executing against")
    52  var fProvider = flag.String("env.provider", "",
    53  	"cloud provider for which environment is executing against")
    54  var fOS = flag.String("env.os", "",
    55  	"operating system for which the environment is executing against")
    56  var fArch = flag.String("env.arch", "",
    57  	"cpu architecture for which the environment is executing against")
    58  var fTags = flag.String("env.tags", "",
    59  	"comma delimited list of tags associated with the environment")
    60  
    61  type Framework struct {
    62  	suites      []*TestSuite
    63  	provisioner Provisioner
    64  	env         Environment
    65  
    66  	isLocalRun bool
    67  	slow       bool
    68  	force      bool
    69  	suite      string
    70  }
    71  
    72  // Environment contains information about the test target environment, used
    73  // to constrain the set of tests run. See the environment flags above.
    74  type Environment struct {
    75  	Name     string
    76  	Provider string
    77  	OS       string
    78  	Arch     string
    79  	Tags     map[string]struct{}
    80  }
    81  
    82  // New creates a Framework
    83  func New() *Framework {
    84  	flag.Parse()
    85  	if *fHelp {
    86  		log.Fatal(frameworkHelp)
    87  	}
    88  	env := Environment{
    89  		Name:     *fEnv,
    90  		Provider: *fProvider,
    91  		OS:       *fOS,
    92  		Arch:     *fArch,
    93  		Tags:     map[string]struct{}{},
    94  	}
    95  	for _, tag := range strings.Split(*fTags, ",") {
    96  		env.Tags[tag] = struct{}{}
    97  	}
    98  	return &Framework{
    99  		provisioner: DefaultProvisioner,
   100  		env:         env,
   101  		isLocalRun:  *fLocal,
   102  		slow:        *fSlow,
   103  		force:       *fForceRun,
   104  		suite:       *fSuite,
   105  	}
   106  }
   107  
   108  // AddSuites adds a set of test suites to a Framework
   109  func (f *Framework) AddSuites(s ...*TestSuite) *Framework {
   110  	f.suites = append(f.suites, s...)
   111  	return f
   112  }
   113  
   114  var pkgSuites []*TestSuite
   115  
   116  // AddSuites adds a set of test suites to the package scoped Framework
   117  func AddSuites(s ...*TestSuite) {
   118  	pkgSuites = append(pkgSuites, s...)
   119  }
   120  
   121  // Run starts the test framework, running each TestSuite
   122  func (f *Framework) Run(t *testing.T) {
   123  	info, err := f.provisioner.SetupTestRun(t, SetupOptions{})
   124  	if err != nil {
   125  		require.NoError(t, err, "could not provision cluster")
   126  	}
   127  	defer f.provisioner.TearDownTestRun(t, info.ID)
   128  
   129  	for _, s := range f.suites {
   130  		t.Run(s.Component, func(t *testing.T) {
   131  			skip, err := f.runSuite(t, s)
   132  			if skip {
   133  				t.Skipf("skipping suite '%s': %v", s.Component, err)
   134  				return
   135  			}
   136  			if err != nil {
   137  				t.Errorf("error starting suite '%s': %v", s.Component, err)
   138  			}
   139  		})
   140  	}
   141  }
   142  
   143  // Run starts the package scoped Framework, running each TestSuite
   144  func Run(t *testing.T) {
   145  	f := New()
   146  	f.AddSuites(pkgSuites...)
   147  	f.Run(t)
   148  }
   149  
   150  // runSuite is called from Framework.Run inside of a sub test for each TestSuite.
   151  // If skip is returned as true, the test suite is skipped with the error text added
   152  // to the Skip reason
   153  // If skip is false and an error is returned, the test suite is failed.
   154  func (f *Framework) runSuite(t *testing.T, s *TestSuite) (skip bool, err error) {
   155  
   156  	// If -forceRun is set, skip all constraint checks
   157  	if !f.force {
   158  		// If this is a local run, check that the suite supports running locally
   159  		if !s.CanRunLocal && f.isLocalRun {
   160  			return true, fmt.Errorf("local run detected and suite cannot run locally")
   161  		}
   162  
   163  		// Check that constraints are met
   164  		if err := s.Constraints.matches(f.env); err != nil {
   165  			return true, fmt.Errorf("constraint failed: %v", err)
   166  		}
   167  
   168  		// Check the slow toggle and if the suite's slow flag is that same
   169  		if f.slow != s.Slow {
   170  			return true, fmt.Errorf("framework slow suite configuration is %v but suite is %v", f.slow, s.Slow)
   171  		}
   172  	}
   173  
   174  	// If -suite is set, skip any suite that is not the one specified.
   175  	if f.suite != "" && f.suite != s.Component {
   176  		return true, fmt.Errorf("only running suite %q", f.suite)
   177  	}
   178  
   179  	info, err := f.provisioner.SetupTestSuite(t, SetupOptions{
   180  		Name:         s.Component,
   181  		ExpectConsul: s.Consul,
   182  		ExpectVault:  s.Vault,
   183  	})
   184  	require.NoError(t, err, "could not provision cluster")
   185  	defer f.provisioner.TearDownTestSuite(t, info.ID)
   186  
   187  	for _, c := range s.Cases {
   188  		f.runCase(t, s, c)
   189  	}
   190  
   191  	return false, nil
   192  }
   193  
   194  func (f *Framework) runCase(t *testing.T, s *TestSuite, c TestCase) {
   195  
   196  	// The test name is set to the name of the implementing type, including package
   197  	name := fmt.Sprintf("%T", c)
   198  
   199  	// The ClusterInfo handle should be used by each TestCase to isolate
   200  	// job/task state created during the test.
   201  	info, err := f.provisioner.SetupTestCase(t, SetupOptions{
   202  		Name:         name,
   203  		ExpectConsul: s.Consul,
   204  		ExpectVault:  s.Vault,
   205  	})
   206  	if err != nil {
   207  		t.Errorf("could not provision cluster for case: %v", err)
   208  	}
   209  	defer f.provisioner.TearDownTestCase(t, info.ID)
   210  	c.setClusterInfo(info)
   211  
   212  	// Each TestCase runs as a subtest of the TestSuite
   213  	t.Run(c.Name(), func(t *testing.T) {
   214  		// If the TestSuite has Parallel set, all cases run in parallel
   215  		if s.Parallel {
   216  			ci.Parallel(t)
   217  		}
   218  
   219  		f := newF(t)
   220  
   221  		// Check if the case includes a before all function
   222  		if beforeAllTests, ok := c.(BeforeAllTests); ok {
   223  			beforeAllTests.BeforeAll(f)
   224  		}
   225  
   226  		// Check if the case includes an after all function at the end
   227  		defer func() {
   228  			if afterAllTests, ok := c.(AfterAllTests); ok {
   229  				afterAllTests.AfterAll(f)
   230  			}
   231  		}()
   232  
   233  		// Here we need to iterate through the methods of the case to find
   234  		// ones that are test functions
   235  		reflectC := reflect.TypeOf(c)
   236  		for i := 0; i < reflectC.NumMethod(); i++ {
   237  			method := reflectC.Method(i)
   238  			if ok := isTestMethod(method.Name); !ok {
   239  				continue
   240  			}
   241  			// Each test is run as its own sub test of the case
   242  			// Test cases are never parallel
   243  			t.Run(method.Name, func(t *testing.T) {
   244  
   245  				cF := newFFromParent(f, t)
   246  				if BeforeEachTest, ok := c.(BeforeEachTest); ok {
   247  					BeforeEachTest.BeforeEach(cF)
   248  				}
   249  				defer func() {
   250  					if afterEachTest, ok := c.(AfterEachTest); ok {
   251  						afterEachTest.AfterEach(cF)
   252  					}
   253  				}()
   254  
   255  				//Call the method
   256  				method.Func.Call([]reflect.Value{reflect.ValueOf(c), reflect.ValueOf(cF)})
   257  			})
   258  		}
   259  	})
   260  }
   261  
   262  func isTestMethod(m string) bool {
   263  	return strings.HasPrefix(m, "Test")
   264  }