github.com/smithx10/nomad@v0.9.1-rc1/e2e/framework/framework.go (about)

     1  package framework
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"reflect"
     7  	"strings"
     8  	"testing"
     9  )
    10  
    11  var fEnv = flag.String("env", "", "name of the environment executing against")
    12  var fProvider = flag.String("env.provider", "", "cloud provider for which environment is executing against")
    13  var fOS = flag.String("env.os", "", "operating system for which the environment is executing against")
    14  var fArch = flag.String("env.arch", "", "cpu architecture for which the environment is executing against")
    15  var fTags = flag.String("env.tags", "", "comma delimited list of tags associated with the environment")
    16  var fLocal = flag.Bool("local", false, "denotes execution is against a local environment")
    17  var fSlow = flag.Bool("slow", false, "toggles execution of slow test suites")
    18  var fForceRun = flag.Bool("forceRun", false, "if set, skips all environment checks when filtering test suites")
    19  
    20  var pkgFramework = New()
    21  
    22  type Framework struct {
    23  	suites      []*TestSuite
    24  	provisioner Provisioner
    25  	env         Environment
    26  
    27  	isLocalRun bool
    28  	slow       bool
    29  	force      bool
    30  }
    31  
    32  // Environment includes information about the target environment the test
    33  // framework is targeting. During 'go run', these fields are populated by
    34  // the following flags:
    35  //
    36  //		-env <string>		"name of the environment executing against"
    37  //		-env.provider <string>	"cloud provider for which the environment is executing against"
    38  //		-env.os <string>		"operating system of environment"
    39  //		-env.arch <string>	"cpu architecture of environment"
    40  //		-env.tags <string>	"comma delimited list of environment tags"
    41  //
    42  // These flags are not needed when executing locally
    43  type Environment struct {
    44  	Name     string
    45  	Provider string
    46  	OS       string
    47  	Arch     string
    48  	Tags     map[string]struct{}
    49  }
    50  
    51  // New creates a Framework
    52  func New() *Framework {
    53  	env := Environment{
    54  		Name:     *fEnv,
    55  		Provider: *fProvider,
    56  		OS:       *fOS,
    57  		Arch:     *fArch,
    58  		Tags:     map[string]struct{}{},
    59  	}
    60  	for _, tag := range strings.Split(*fTags, ",") {
    61  		env.Tags[tag] = struct{}{}
    62  	}
    63  	return &Framework{
    64  		provisioner: DefaultProvisioner,
    65  		env:         env,
    66  		isLocalRun:  *fLocal,
    67  		slow:        *fSlow,
    68  		force:       *fForceRun,
    69  	}
    70  }
    71  
    72  // AddSuites adds a set of test suites to a Framework
    73  func (f *Framework) AddSuites(s ...*TestSuite) *Framework {
    74  	f.suites = append(f.suites, s...)
    75  	return f
    76  }
    77  
    78  // AddSuites adds a set of test suites to the package scoped Framework
    79  func AddSuites(s ...*TestSuite) *Framework {
    80  	pkgFramework.AddSuites(s...)
    81  	return pkgFramework
    82  }
    83  
    84  // Run starts the test framework, running each TestSuite
    85  func (f *Framework) Run(t *testing.T) {
    86  	for _, s := range f.suites {
    87  		t.Run(s.Component, func(t *testing.T) {
    88  			skip, err := f.runSuite(t, s)
    89  			if skip {
    90  				t.Skipf("skipping suite '%s': %v", s.Component, err)
    91  				return
    92  			}
    93  			if err != nil {
    94  				t.Errorf("error starting suite '%s': %v", s.Component, err)
    95  			}
    96  		})
    97  	}
    98  
    99  }
   100  
   101  // Run starts the package scoped Framework, running each TestSuite
   102  func Run(t *testing.T) {
   103  	pkgFramework.Run(t)
   104  }
   105  
   106  // runSuite is called from Framework.Run inside of a sub test for each TestSuite.
   107  // If skip is returned as true, the test suite is skipped with the error text added
   108  // to the Skip reason
   109  // If skip is false and an error is returned, the test suite is failed.
   110  func (f *Framework) runSuite(t *testing.T, s *TestSuite) (skip bool, err error) {
   111  
   112  	// If -forceRun is set, skip all constraint checks
   113  	if !f.force {
   114  		// If this is a local run, check that the suite supports running locally
   115  		if !s.CanRunLocal && f.isLocalRun {
   116  			return true, fmt.Errorf("local run detected and suite cannot run locally")
   117  		}
   118  
   119  		// Check that constraints are met
   120  		if err := s.Constraints.matches(f.env); err != nil {
   121  			return true, fmt.Errorf("constraint failed: %v", err)
   122  		}
   123  
   124  		// Check the slow toggle and if the suite's slow flag is that same
   125  		if f.slow != s.Slow {
   126  			return true, fmt.Errorf("framework slow suite configuration is %v but suite is %v", f.slow, s.Slow)
   127  		}
   128  	}
   129  
   130  	for _, c := range s.Cases {
   131  		// The test name is set to the name of the implementing type, including package
   132  		name := fmt.Sprintf("%T", c)
   133  
   134  		// Each TestCase is provisioned a Nomad cluster to test against.
   135  		// This varies by the provisioner implementation used, currently
   136  		// the default behavior is for every Test case to use a single shared
   137  		// Nomad cluster.
   138  		info, err := f.provisioner.ProvisionCluster(ProvisionerOptions{
   139  			Name:         name,
   140  			ExpectConsul: s.Consul,
   141  			ExpectVault:  s.Vault,
   142  		})
   143  		if err != nil {
   144  			return false, fmt.Errorf("could not provision cluster for case: %v", err)
   145  		}
   146  		defer f.provisioner.DestroyCluster(info.ID)
   147  		c.setClusterInfo(info)
   148  
   149  		// Each TestCase runs as a subtest of the TestSuite
   150  		t.Run(c.Name(), func(t *testing.T) {
   151  			// If the TestSuite has Parallel set, all cases run in parallel
   152  			if s.Parallel {
   153  				t.Parallel()
   154  			}
   155  
   156  			f := newF(t)
   157  
   158  			// Check if the case includes a before all function
   159  			if beforeAllTests, ok := c.(BeforeAllTests); ok {
   160  				beforeAllTests.BeforeAll(f)
   161  			}
   162  
   163  			// Check if the case includes an after all function at the end
   164  			defer func() {
   165  				if afterAllTests, ok := c.(AfterAllTests); ok {
   166  					afterAllTests.AfterAll(f)
   167  				}
   168  			}()
   169  
   170  			// Here we need to iterate through the methods of the case to find
   171  			// ones that are test functions
   172  			reflectC := reflect.TypeOf(c)
   173  			for i := 0; i < reflectC.NumMethod(); i++ {
   174  				method := reflectC.Method(i)
   175  				if ok := isTestMethod(method.Name); !ok {
   176  					continue
   177  				}
   178  				// Each test is run as its own sub test of the case
   179  				// Test cases are never parallel
   180  				t.Run(method.Name, func(t *testing.T) {
   181  
   182  					cF := newFFromParent(f, t)
   183  					if BeforeEachTest, ok := c.(BeforeEachTest); ok {
   184  						BeforeEachTest.BeforeEach(cF)
   185  					}
   186  					defer func() {
   187  						if afterEachTest, ok := c.(AfterEachTest); ok {
   188  							afterEachTest.AfterEach(cF)
   189  						}
   190  					}()
   191  
   192  					//Call the method
   193  					method.Func.Call([]reflect.Value{reflect.ValueOf(c), reflect.ValueOf(cF)})
   194  				})
   195  			}
   196  		})
   197  
   198  	}
   199  
   200  	return false, nil
   201  }
   202  
   203  func isTestMethod(m string) bool {
   204  	if !strings.HasPrefix(m, "Test") {
   205  		return false
   206  	}
   207  	// THINKING: adding flag to target a specific test or test regex?
   208  	return true
   209  }