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