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  }