github.com/rohankumardubey/nomad@v0.11.8/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/e2e/framework/provisioning"
    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       force default no-op provisioning
    21    -skipTests   skips all tests and only provisions
    22    -slow        include execution of slow test suites
    23    -suite       run specified test suite
    24    -showHelp    shows this help text
    25  
    26  Provisioning flags tell the test runner to pre-provision the cluster before
    27  running all tests. These flags can be passed to 'go test'. If no
    28  '-provision.*' flag is set, the test runner assumes the cluster has already
    29  been configured and uses the test environment's env vars to connect to the
    30  cluster.
    31  
    32    -provision.terraform=string   pass file generated by terraform output
    33    -provision.vagrant=string     provision to a single-node vagrant box
    34  
    35  Nomad version flags tell the provisioner to deploy a specific version of
    36  Nomad. These flags are all ignored if no '-provision.*' flag is set.
    37  Otherwise at most one should be set.
    38  
    39    -nomad.local_file=string  provision this specific local binary of Nomad
    40    -nomad.sha=string         provision this specific sha from S3
    41    -nomad.version=string     provision this version from releases.hashicorp.com
    42  
    43  TestSuites can request Constraints on the Framework.Environment so that tests
    44  are only run in the appropriate conditions. These environment flags provide
    45  the information for those constraints.
    46  
    47    -env=string           name of the environment
    48    -env.arch=string      cpu architecture of the targets
    49    -env.os=string        operating system of the targets
    50    -env.provider=string  cloud provider of the environment
    51    -env.tags=string      comma delimited list of tags for the environment
    52  
    53  `
    54  
    55  var fHelp = flag.Bool("showHelp", false, "print the help screen")
    56  var fLocal = flag.Bool("local", false,
    57  	"denotes execution is against a local environment, forcing default no-op provisioning")
    58  var fSlow = flag.Bool("slow", false, "toggles execution of slow test suites")
    59  var fForceRun = flag.Bool("forceRun", false,
    60  	"if set, skips all environment checks when filtering test suites")
    61  var fSkipTests = flag.Bool("skipTests", false, "skip all tests and only provision")
    62  var fSuite = flag.String("suite", "", "run specified test suite")
    63  
    64  // Provisioning flags
    65  var fProvisionVagrant = flag.String("provision.vagrant", "",
    66  	"run pre-provision to a single-node vagrant host")
    67  var fProvisionTerraform = flag.String("provision.terraform", "",
    68  	"run pre-provision from file generated by 'terraform output provisioning'")
    69  
    70  // Nomad version flags
    71  // TODO: these override each other. local_file > sha > version
    72  // but we should assert at most 1 is set.
    73  var fProvisionNomadLocalBinary = flag.String("nomad.local_file", "",
    74  	"provision this specific local binary of Nomad (ignored for no-op provisioning).")
    75  var fProvisionNomadSha = flag.String("nomad.sha", "",
    76  	"provision this specific sha of Nomad (ignored for no-op provisioning)")
    77  var fProvisionNomadVersion = flag.String("nomad.version", "",
    78  	"provision this specific release of Nomad (ignored for no-op provisioning)")
    79  
    80  // Environment flags
    81  // TODO:
    82  // if we have a provisioner, each target has its own environment. it'd
    83  // be nice if we could match that environment against the tests so that
    84  // we always avoid running tests that don't apply against the
    85  // environment, and then have these flags override that behavior.
    86  var fEnv = flag.String("env", "", "name of the environment executing against")
    87  var fProvider = flag.String("env.provider", "",
    88  	"cloud provider for which environment is executing against")
    89  var fOS = flag.String("env.os", "",
    90  	"operating system for which the environment is executing against")
    91  var fArch = flag.String("env.arch", "",
    92  	"cpu architecture for which the environment is executing against")
    93  var fTags = flag.String("env.tags", "",
    94  	"comma delimited list of tags associated with the environment")
    95  
    96  type Framework struct {
    97  	suites      []*TestSuite
    98  	provisioner provisioning.Provisioner
    99  	env         Environment
   100  
   101  	isLocalRun bool
   102  	slow       bool
   103  	force      bool
   104  	skipAll    bool
   105  	suite      string
   106  }
   107  
   108  // Environment contains information about the test target environment, used
   109  // to constrain the set of tests run. See the environment flags above.
   110  type Environment struct {
   111  	Name     string
   112  	Provider string
   113  	OS       string
   114  	Arch     string
   115  	Tags     map[string]struct{}
   116  }
   117  
   118  // New creates a Framework
   119  func New() *Framework {
   120  	flag.Parse()
   121  	if *fHelp {
   122  		log.Fatal(frameworkHelp)
   123  	}
   124  	env := Environment{
   125  		Name:     *fEnv,
   126  		Provider: *fProvider,
   127  		OS:       *fOS,
   128  		Arch:     *fArch,
   129  		Tags:     map[string]struct{}{},
   130  	}
   131  	for _, tag := range strings.Split(*fTags, ",") {
   132  		env.Tags[tag] = struct{}{}
   133  	}
   134  	return &Framework{
   135  		provisioner: provisioning.NewProvisioner(provisioning.ProvisionerConfig{
   136  			IsLocal:          *fLocal,
   137  			VagrantBox:       *fProvisionVagrant,
   138  			TerraformConfig:  *fProvisionTerraform,
   139  			NomadLocalBinary: *fProvisionNomadLocalBinary,
   140  			NomadSha:         *fProvisionNomadSha,
   141  			NomadVersion:     *fProvisionNomadVersion,
   142  		}),
   143  		env:        env,
   144  		isLocalRun: *fLocal,
   145  		slow:       *fSlow,
   146  		force:      *fForceRun,
   147  		skipAll:    *fSkipTests,
   148  		suite:      *fSuite,
   149  	}
   150  }
   151  
   152  // AddSuites adds a set of test suites to a Framework
   153  func (f *Framework) AddSuites(s ...*TestSuite) *Framework {
   154  	f.suites = append(f.suites, s...)
   155  	return f
   156  }
   157  
   158  var pkgSuites []*TestSuite
   159  
   160  // AddSuites adds a set of test suites to the package scoped Framework
   161  func AddSuites(s ...*TestSuite) {
   162  	pkgSuites = append(pkgSuites, s...)
   163  }
   164  
   165  // Run starts the test framework, running each TestSuite
   166  func (f *Framework) Run(t *testing.T) {
   167  	info, err := f.provisioner.SetupTestRun(t, provisioning.SetupOptions{})
   168  	if err != nil {
   169  		t.Fatalf("could not provision cluster: %v", err)
   170  	}
   171  	defer f.provisioner.TearDownTestRun(t, info.ID)
   172  
   173  	if f.skipAll {
   174  		t.Skip("Skipping all tests, -skipTests set")
   175  	}
   176  
   177  	for _, s := range f.suites {
   178  		t.Run(s.Component, func(t *testing.T) {
   179  			skip, err := f.runSuite(t, s)
   180  			if skip {
   181  				t.Skipf("skipping suite '%s': %v", s.Component, err)
   182  				return
   183  			}
   184  			if err != nil {
   185  				t.Errorf("error starting suite '%s': %v", s.Component, err)
   186  			}
   187  		})
   188  	}
   189  }
   190  
   191  // Run starts the package scoped Framework, running each TestSuite
   192  func Run(t *testing.T) {
   193  	f := New()
   194  	f.AddSuites(pkgSuites...)
   195  	f.Run(t)
   196  }
   197  
   198  // runSuite is called from Framework.Run inside of a sub test for each TestSuite.
   199  // If skip is returned as true, the test suite is skipped with the error text added
   200  // to the Skip reason
   201  // If skip is false and an error is returned, the test suite is failed.
   202  func (f *Framework) runSuite(t *testing.T, s *TestSuite) (skip bool, err error) {
   203  
   204  	// If -forceRun is set, skip all constraint checks
   205  	if !f.force {
   206  		// If this is a local run, check that the suite supports running locally
   207  		if !s.CanRunLocal && f.isLocalRun {
   208  			return true, fmt.Errorf("local run detected and suite cannot run locally")
   209  		}
   210  
   211  		// Check that constraints are met
   212  		if err := s.Constraints.matches(f.env); err != nil {
   213  			return true, fmt.Errorf("constraint failed: %v", err)
   214  		}
   215  
   216  		// Check the slow toggle and if the suite's slow flag is that same
   217  		if f.slow != s.Slow {
   218  			return true, fmt.Errorf("framework slow suite configuration is %v but suite is %v", f.slow, s.Slow)
   219  		}
   220  	}
   221  
   222  	// If -suite is set, skip any suite that is not the one specified.
   223  	if f.suite != "" && f.suite != s.Component {
   224  		return true, fmt.Errorf("only running suite %q", f.suite)
   225  	}
   226  
   227  	info, err := f.provisioner.SetupTestSuite(t, provisioning.SetupOptions{
   228  		Name:         s.Component,
   229  		ExpectConsul: s.Consul,
   230  		ExpectVault:  s.Vault,
   231  	})
   232  	if err != nil {
   233  		t.Fatalf("could not provision cluster: %v", err)
   234  	}
   235  	defer f.provisioner.TearDownTestSuite(t, info.ID)
   236  
   237  	for _, c := range s.Cases {
   238  		f.runCase(t, s, c)
   239  	}
   240  
   241  	return false, nil
   242  }
   243  
   244  func (f *Framework) runCase(t *testing.T, s *TestSuite, c TestCase) {
   245  
   246  	// The test name is set to the name of the implementing type, including package
   247  	name := fmt.Sprintf("%T", c)
   248  
   249  	// The ClusterInfo handle should be used by each TestCase to isolate
   250  	// job/task state created during the test.
   251  	info, err := f.provisioner.SetupTestCase(t, provisioning.SetupOptions{
   252  		Name:         name,
   253  		ExpectConsul: s.Consul,
   254  		ExpectVault:  s.Vault,
   255  	})
   256  	if err != nil {
   257  		t.Errorf("could not provision cluster for case: %v", err)
   258  	}
   259  	defer f.provisioner.TearDownTestCase(t, info.ID)
   260  	c.setClusterInfo(info)
   261  
   262  	// Each TestCase runs as a subtest of the TestSuite
   263  	t.Run(c.Name(), func(t *testing.T) {
   264  		// If the TestSuite has Parallel set, all cases run in parallel
   265  		if s.Parallel {
   266  			t.Parallel()
   267  		}
   268  
   269  		f := newF(t)
   270  
   271  		// Check if the case includes a before all function
   272  		if beforeAllTests, ok := c.(BeforeAllTests); ok {
   273  			beforeAllTests.BeforeAll(f)
   274  		}
   275  
   276  		// Check if the case includes an after all function at the end
   277  		defer func() {
   278  			if afterAllTests, ok := c.(AfterAllTests); ok {
   279  				afterAllTests.AfterAll(f)
   280  			}
   281  		}()
   282  
   283  		// Here we need to iterate through the methods of the case to find
   284  		// ones that are test functions
   285  		reflectC := reflect.TypeOf(c)
   286  		for i := 0; i < reflectC.NumMethod(); i++ {
   287  			method := reflectC.Method(i)
   288  			if ok := isTestMethod(method.Name); !ok {
   289  				continue
   290  			}
   291  			// Each test is run as its own sub test of the case
   292  			// Test cases are never parallel
   293  			t.Run(method.Name, func(t *testing.T) {
   294  
   295  				cF := newFFromParent(f, t)
   296  				if BeforeEachTest, ok := c.(BeforeEachTest); ok {
   297  					BeforeEachTest.BeforeEach(cF)
   298  				}
   299  				defer func() {
   300  					if afterEachTest, ok := c.(AfterEachTest); ok {
   301  						afterEachTest.AfterEach(cF)
   302  					}
   303  				}()
   304  
   305  				//Call the method
   306  				method.Func.Call([]reflect.Value{reflect.ValueOf(c), reflect.ValueOf(cF)})
   307  			})
   308  		}
   309  	})
   310  }
   311  
   312  func isTestMethod(m string) bool {
   313  	if !strings.HasPrefix(m, "Test") {
   314  		return false
   315  	}
   316  
   317  	// THINKING: adding flag to target a specific test or test regex?
   318  	return true
   319  }