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 }