github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/testing/base.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package testing
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"reflect"
    10  	"runtime"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/juju/collections/set"
    15  	"github.com/juju/featureflag"
    16  	"github.com/juju/loggo"
    17  	"github.com/juju/os/v2/series"
    18  	"github.com/juju/testing"
    19  	jc "github.com/juju/testing/checkers"
    20  	"github.com/juju/utils/v3"
    21  	"github.com/juju/version/v2"
    22  	gc "gopkg.in/check.v1"
    23  
    24  	"github.com/juju/juju/core/arch"
    25  	"github.com/juju/juju/core/base"
    26  	"github.com/juju/juju/core/model"
    27  	coreos "github.com/juju/juju/core/os"
    28  	"github.com/juju/juju/core/os/ostype"
    29  	"github.com/juju/juju/juju/osenv"
    30  	"github.com/juju/juju/jujuclient"
    31  	jujuversion "github.com/juju/juju/version"
    32  	"github.com/juju/juju/wrench"
    33  )
    34  
    35  var logger = loggo.GetLogger("juju.testing")
    36  
    37  // JujuOSEnvSuite isolates the tests from Juju environment variables.
    38  // This is intended to be only used by existing suites, usually embedded in
    39  // BaseSuite and in FakeJujuXDGDataHomeSuite. Eventually the tests relying on
    40  // JujuOSEnvSuite will be converted to use the IsolationSuite in
    41  // github.com/juju/testing, and this suite will be removed.
    42  // Do not use JujuOSEnvSuite when writing new tests.
    43  type JujuOSEnvSuite struct {
    44  	oldHomeEnv          string
    45  	oldEnvironment      map[string]string
    46  	initialFeatureFlags string
    47  }
    48  
    49  func (s *JujuOSEnvSuite) SetUpTest(c *gc.C) {
    50  	s.oldEnvironment = make(map[string]string)
    51  	for _, name := range []string{
    52  		osenv.JujuXDGDataHomeEnvKey,
    53  		osenv.JujuControllerEnvKey,
    54  		osenv.JujuModelEnvKey,
    55  		osenv.JujuLoggingConfigEnvKey,
    56  		osenv.JujuFeatureFlagEnvKey,
    57  		osenv.JujuFeatures,
    58  		osenv.XDGDataHome,
    59  	} {
    60  		s.oldEnvironment[name] = os.Getenv(name)
    61  		os.Setenv(name, "")
    62  	}
    63  	s.oldHomeEnv = utils.Home()
    64  	os.Setenv(osenv.JujuXDGDataHomeEnvKey, c.MkDir())
    65  	err := utils.SetHome("")
    66  	c.Assert(err, jc.ErrorIsNil)
    67  
    68  	// Update the feature flag set to be the requested initial set.
    69  	// For tests, setting with the environment variable isolates us
    70  	// from a single resource that was hitting contention during parallel
    71  	// test runs.
    72  	os.Setenv(osenv.JujuFeatureFlagEnvKey, s.initialFeatureFlags)
    73  	featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey)
    74  }
    75  
    76  func (s *JujuOSEnvSuite) TearDownTest(c *gc.C) {
    77  	for name, value := range s.oldEnvironment {
    78  		os.Setenv(name, value)
    79  	}
    80  	err := utils.SetHome(s.oldHomeEnv)
    81  	c.Assert(err, jc.ErrorIsNil)
    82  }
    83  
    84  // SetModelAndController adds a controller, and a model in that controller,
    85  // and sets the controller as the current controller, and the model as the
    86  // current model.
    87  func (s *JujuOSEnvSuite) SetModelAndController(c *gc.C, controllerName, modelName string) {
    88  	store := jujuclient.NewFileClientStore()
    89  	err := store.AddController(controllerName, jujuclient.ControllerDetails{
    90  		ControllerUUID: "fake-uuid",
    91  	})
    92  	c.Assert(err, jc.ErrorIsNil)
    93  	err = store.SetCurrentController(controllerName)
    94  	c.Assert(err, jc.ErrorIsNil)
    95  	err = store.SetModels(controllerName, map[string]jujuclient.ModelDetails{
    96  		modelName: {
    97  			ModelUUID: "fake-model-uuid",
    98  			ModelType: model.IAAS,
    99  		},
   100  	})
   101  	c.Assert(err, jc.ErrorIsNil)
   102  	err = store.SetCurrentModel(controllerName, modelName)
   103  	c.Assert(err, jc.ErrorIsNil)
   104  }
   105  
   106  // SkipIfPPC64EL skips the test if the arch is PPC64EL and the
   107  // compiler is gccgo.
   108  func SkipIfPPC64EL(c *gc.C, bugID string) {
   109  	if runtime.Compiler == "gccgo" &&
   110  		arch.NormaliseArch(runtime.GOARCH) == arch.PPC64EL {
   111  		c.Skip(fmt.Sprintf("Test disabled on PPC64EL until fixed - see bug %s", bugID))
   112  	}
   113  }
   114  
   115  // SkipIfS390X skips the test if the arch is S390X.
   116  func SkipIfS390X(c *gc.C, bugID string) {
   117  	if arch.NormaliseArch(runtime.GOARCH) == arch.S390X {
   118  		c.Skip(fmt.Sprintf("Test disabled on S390X until fixed - see bug %s", bugID))
   119  	}
   120  }
   121  
   122  // SkipIfWindowsBug skips the test if the OS is Windows.
   123  func SkipIfWindowsBug(c *gc.C, bugID string) {
   124  	if runtime.GOOS == "windows" {
   125  		c.Skip(fmt.Sprintf("Test disabled on Windows until fixed - see bug %s", bugID))
   126  	}
   127  }
   128  
   129  // SkipUnlessControllerOS skips the test if the current OS is not a supported
   130  // controller OS.
   131  func SkipUnlessControllerOS(c *gc.C) {
   132  	if coreos.HostOS() != ostype.Ubuntu {
   133  		c.Skip("Test disabled for non-controller OS")
   134  	}
   135  }
   136  
   137  // SkipLXDNotSupported will skip tests if the host does not support LXD
   138  func SkipLXDNotSupported(c *gc.C) {
   139  	if coreos.HostOS() != ostype.Ubuntu {
   140  		c.Skip("Test disabled for non-LXD OS")
   141  	}
   142  }
   143  
   144  // SkipFlaky skips the test if there is an open bug for intermittent test failures
   145  func SkipFlaky(c *gc.C, bugID string) {
   146  	c.Skip(fmt.Sprintf("Test disabled until flakiness is fixed - see bug %s", bugID))
   147  }
   148  
   149  // SetInitialFeatureFlags sets the feature flags to be in effect for
   150  // the next call to SetUpTest.
   151  func (s *JujuOSEnvSuite) SetInitialFeatureFlags(flags ...string) {
   152  	s.initialFeatureFlags = strings.Join(flags, ",")
   153  }
   154  
   155  func (s *JujuOSEnvSuite) SetFeatureFlags(flag ...string) {
   156  	flags := strings.Join(flag, ",")
   157  	if err := os.Setenv(osenv.JujuFeatureFlagEnvKey, flags); err != nil {
   158  		panic(err)
   159  	}
   160  	logger.Debugf("setting feature flags: %s", flags)
   161  	featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey)
   162  }
   163  
   164  // BaseSuite provides required functionality for all test suites
   165  // when embedded in a gocheck suite type:
   166  // - logger redirect
   167  // - no outgoing network access
   168  // - protection of user's home directory
   169  // - scrubbing of env vars
   170  // TODO (frankban) 2014-06-09: switch to using IsolationSuite.
   171  // NOTE: there will be many tests that fail when you try to change
   172  // to the IsolationSuite that rely on external things in PATH.
   173  type BaseSuite struct {
   174  	oldLtsForTesting string
   175  	testing.CleanupSuite
   176  	testing.LoggingSuite
   177  	JujuOSEnvSuite
   178  	InitialLoggingConfig string
   179  }
   180  
   181  func (s *BaseSuite) SetUpSuite(c *gc.C) {
   182  	wrench.SetEnabled(false)
   183  	s.CleanupSuite.SetUpSuite(c)
   184  	s.LoggingSuite.SetUpSuite(c)
   185  	// JujuOSEnvSuite does not have a suite setup.
   186  	// LTS-dependent requires new entry upon new LTS release.
   187  	s.oldLtsForTesting = series.SetLatestLtsForTesting("xenial")
   188  }
   189  
   190  func (s *BaseSuite) TearDownSuite(c *gc.C) {
   191  	// JujuOSEnvSuite does not have a suite teardown.
   192  	_ = series.SetLatestLtsForTesting(s.oldLtsForTesting)
   193  	s.LoggingSuite.TearDownSuite(c)
   194  	s.CleanupSuite.TearDownSuite(c)
   195  }
   196  
   197  func (s *BaseSuite) SetUpTest(c *gc.C) {
   198  	s.CleanupSuite.SetUpTest(c)
   199  	s.LoggingSuite.SetUpTest(c)
   200  	s.JujuOSEnvSuite.SetUpTest(c)
   201  	if s.InitialLoggingConfig != "" {
   202  		_ = loggo.ConfigureLoggers(s.InitialLoggingConfig)
   203  	}
   204  
   205  	// We do this to isolate invocations of bash from pulling in the
   206  	// ambient user environment, and potentially affecting the tests.
   207  	// We can't always just use IsolationSuite because we still need
   208  	// PATH and possibly a couple other envars.
   209  	s.PatchEnvironment("BASH_ENV", "")
   210  }
   211  
   212  func (s *BaseSuite) TearDownTest(c *gc.C) {
   213  	s.JujuOSEnvSuite.TearDownTest(c)
   214  	s.LoggingSuite.TearDownTest(c)
   215  	s.CleanupSuite.TearDownTest(c)
   216  }
   217  
   218  // CheckString compares two strings. If they do not match then the spot
   219  // where they do not match is logged.
   220  func CheckString(c *gc.C, value, expected string) {
   221  	if !c.Check(value, gc.Equals, expected) {
   222  		diffStrings(c, value, expected)
   223  	}
   224  }
   225  
   226  func diffStrings(c *gc.C, value, expected string) {
   227  	// If only Go had a diff library.
   228  	vlines := strings.Split(value, "\n")
   229  	elines := strings.Split(expected, "\n")
   230  	vsize := len(vlines)
   231  	esize := len(elines)
   232  
   233  	if vsize < 2 || esize < 2 {
   234  		return
   235  	}
   236  
   237  	smaller := elines
   238  	if vsize < esize {
   239  		smaller = vlines
   240  	}
   241  
   242  	for i := range smaller {
   243  		vline := vlines[i]
   244  		eline := elines[i]
   245  		if vline != eline {
   246  			c.Logf("first mismatched line (%d/%d):", i, len(smaller))
   247  			c.Log("expected: " + eline)
   248  			c.Log("got:      " + vline)
   249  			break
   250  		}
   251  	}
   252  }
   253  
   254  // TestCleanup is used to allow DumpTestLogsAfter to take any test suite
   255  // that supports the standard cleanup function.
   256  type TestCleanup interface {
   257  	AddCleanup(func(*gc.C))
   258  }
   259  
   260  // DumpTestLogsAfter will write the test logs to stdout if the timeout
   261  // is reached.
   262  func DumpTestLogsAfter(timeout time.Duration, c *gc.C, cleaner TestCleanup) {
   263  	done := make(chan interface{})
   264  	go func() {
   265  		select {
   266  		case <-time.After(timeout):
   267  			fmt.Printf(c.GetTestLog())
   268  		case <-done:
   269  		}
   270  	}()
   271  	cleaner.AddCleanup(func(_ *gc.C) {
   272  		close(done)
   273  	})
   274  }
   275  
   276  // GetExportedFields return the exported fields of a struct.
   277  func GetExportedFields(arg interface{}) set.Strings {
   278  	t := reflect.TypeOf(arg)
   279  	result := set.NewStrings()
   280  
   281  	count := t.NumField()
   282  	for i := 0; i < count; i++ {
   283  		f := t.Field(i)
   284  		// empty PkgPath means exported field.
   285  		// see https://golang.org/pkg/reflect/#StructField
   286  		if f.PkgPath == "" {
   287  			result.Add(f.Name)
   288  		}
   289  	}
   290  
   291  	return result
   292  }
   293  
   294  // CurrentVersion returns the current Juju version, asserting on error.
   295  func CurrentVersion() version.Binary {
   296  	return version.Binary{
   297  		Number:  jujuversion.Current,
   298  		Arch:    arch.HostArch(),
   299  		Release: coreos.HostOSTypeName(),
   300  	}
   301  }
   302  
   303  // HostSeries returns series.HostSeries(), asserting on error.
   304  func HostBase(c *gc.C) base.Base {
   305  	hostBase, err := coreos.HostBase()
   306  	c.Assert(err, jc.ErrorIsNil)
   307  	return hostBase
   308  }