github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/util/constants.go (about) 1 // Copyright 2020 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package util 12 13 import ( 14 "fmt" 15 "math/rand" 16 "os" 17 18 "github.com/cockroachdb/cockroachdb-parser/pkg/util/buildutil" 19 "github.com/cockroachdb/cockroachdb-parser/pkg/util/randutil" 20 "github.com/cockroachdb/cockroachdb-parser/pkg/util/syncutil" 21 ) 22 23 // IsMetamorphicBuild returns whether this build is metamorphic. By build being 24 // "metamorphic" we mean that some magic constants in the codebase might get 25 // initialized to non-default value. 26 // A build will become metamorphic with metamorphicBuildProbability probability 27 // if 'crdb_test' build flag is specified (this is the case for all test 28 // targets). 29 func IsMetamorphicBuild() bool { 30 return metamorphicBuild 31 } 32 33 var metamorphicBuild bool 34 35 const ( 36 metamorphicBuildProbability = 0.8 37 metamorphicValueProbability = 0.75 38 metamorphicBoolProbability = 0.5 39 ) 40 41 // ConstantWithMetamorphicTestValue should be used to initialize "magic 42 // constants" that should be varied during test scenarios to check for bugs at 43 // boundary conditions. When metamorphicBuild is true, the test value will be 44 // used with metamorphicValueProbability probability. In all other cases, the 45 // production ("default") value will be used. 46 // The constant must be a "metamorphic variable": changing it cannot affect the 47 // output of any SQL DMLs. It can only affect the way in which the data is 48 // retrieved or processed, because otherwise the main test corpus would fail if 49 // this flag were enabled. 50 // 51 // An example of a "magic constant" that behaves this way is a batch size. Batch 52 // sizes tend to present testing problems, because often the logic that deals 53 // with what to do when a batch is finished is less likely to be exercised by 54 // simple unit tests that don't use enough data to fill up a batch. 55 // 56 // For example, instead of writing: 57 // 58 // const batchSize = 64 59 // 60 // you should write: 61 // 62 // var batchSize = util.ConstantWithMetamorphicTestValue("batch-size", 64, 1) 63 // 64 // This will often give your code a batch size of 1 in the crdb_test build 65 // configuration, increasing the amount of exercise the edge conditions get. 66 // 67 // The given name is used for logging. 68 func ConstantWithMetamorphicTestValue(name string, defaultValue, metamorphicValue int) int { 69 if metamorphicBuild { 70 rng.Lock() 71 defer rng.Unlock() 72 if rng.r.Float64() < metamorphicValueProbability { 73 logMetamorphicValue(name, metamorphicValue) 74 return metamorphicValue 75 } 76 } 77 return defaultValue 78 } 79 80 // rng is initialized to a rand.Rand if crdbTestBuild is enabled. 81 var rng struct { 82 r *rand.Rand 83 syncutil.Mutex 84 } 85 86 // DisableMetamorphicEnvVar can be used to disable metamorphic tests for 87 // sub-processes. If it exists and is set to something truthy as defined by 88 // strconv.ParseBool then metamorphic testing will not be enabled. 89 const DisableMetamorphicEnvVar = "COCKROACH_INTERNAL_DISABLE_METAMORPHIC_TESTING" 90 91 // Returns true iff the current process is eligible to enable metamorphic 92 // variables. When run under Bazel, checking if we are in the Go test wrapper 93 // ensures that metamorphic variables are not initialized and logged twice 94 // from both the wrapper and the main test process, as both will perform 95 // initialization of the test module and its dependencies. 96 func metamorphicEligible() bool { 97 if !buildutil.CrdbTestBuild { 98 return false 99 } 100 return true 101 } 102 103 func init() { 104 if metamorphicEligible() { 105 if !disableMetamorphicTesting { 106 rng.r, _ = randutil.NewTestRand() 107 metamorphicBuild = rng.r.Float64() < metamorphicBuildProbability 108 } 109 } 110 } 111 112 // ConstantWithMetamorphicTestRange is like ConstantWithMetamorphicTestValue 113 // except instead of returning a single metamorphic test value, it returns a 114 // random test value in the semi-open range [min, max). 115 // 116 // The given name is used for logging. 117 func ConstantWithMetamorphicTestRange(name string, defaultValue, min, max int) int { 118 if metamorphicBuild { 119 rng.Lock() 120 defer rng.Unlock() 121 if rng.r.Float64() < metamorphicValueProbability { 122 ret := min 123 if max > min { 124 ret = int(rng.r.Int31())%(max-min) + min 125 } 126 logMetamorphicValue(name, ret) 127 return ret 128 } 129 } 130 return defaultValue 131 } 132 133 // ConstantWithMetamorphicTestBool is like ConstantWithMetamorphicTestValue except 134 // it returns the non-default value half of the time (if running a metamorphic build). 135 // 136 // The given name is used for logging. 137 func ConstantWithMetamorphicTestBool(name string, defaultValue bool) bool { 138 return constantWithMetamorphicTestBoolInternal(name, defaultValue, true /* doLog */) 139 } 140 141 func constantWithMetamorphicTestBoolInternal(name string, defaultValue bool, doLog bool) bool { 142 if metamorphicBuild { 143 rng.Lock() 144 defer rng.Unlock() 145 if rng.r.Float64() < metamorphicBoolProbability { 146 ret := !defaultValue 147 if doLog { 148 logMetamorphicValue(name, ret) 149 } 150 return ret 151 } 152 } 153 return defaultValue 154 } 155 156 // ConstantWithMetamorphicTestBoolWithoutLogging is like ConstantWithMetamorphicTestBool 157 // except it does not log the value. This is necessary to work around this issue: 158 // https://github.com/cockroachdb/cockroachdb-parser/issues/106667 159 // TODO(test-eng): Remove this variant when the issue above is addressed. 160 func ConstantWithMetamorphicTestBoolWithoutLogging(name string, defaultValue bool) bool { 161 return constantWithMetamorphicTestBoolInternal(name, defaultValue, false /* doLog */) 162 } 163 164 // ConstantWithMetamorphicTestChoice is like ConstantWithMetamorphicTestValue except 165 // it returns a random choice (equally weighted) of the given values. The default 166 // value is included in the random choice. 167 // 168 // The given name is used for logging. 169 func ConstantWithMetamorphicTestChoice( 170 name string, defaultValue interface{}, otherValues ...interface{}, 171 ) interface{} { 172 if metamorphicBuild { 173 values := append([]interface{}{defaultValue}, otherValues...) 174 rng.Lock() 175 defer rng.Unlock() 176 value := values[rng.r.Int63n(int64(len(values)))] 177 logMetamorphicValue(name, value) 178 return value 179 } 180 return defaultValue 181 } 182 183 func logMetamorphicValue(name string, value interface{}) { 184 fmt.Fprintf(os.Stderr, "initialized metamorphic constant %q with value %v\n", name, value) 185 }