github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/util/json/random.go (about)

     1  // Copyright 2017 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 json
    12  
    13  import (
    14  	"bytes"
    15  	"encoding/json"
    16  	"fmt"
    17  	"math/rand"
    18  
    19  	"github.com/cockroachdb/cockroachdb-parser/pkg/util/randutil"
    20  )
    21  
    22  // Some issues will only be revealed if we have duplicate strings, so we
    23  // include a pool of common strings that we occasionally pull from rather than
    24  // generating a completely random string.
    25  var staticStrings = []string{
    26  	"a",
    27  	"b",
    28  	"c",
    29  	"foo",
    30  	"bar",
    31  	"baz",
    32  	"foobar",
    33  }
    34  
    35  type randConfig struct {
    36  	maxLen     int
    37  	complexity int
    38  	escapeProb float32
    39  }
    40  
    41  // RandOption is an option to control generation of random json.
    42  type RandOption interface {
    43  	apply(*randConfig)
    44  }
    45  
    46  type randFuncOpt func(cfg *randConfig)
    47  
    48  func (fn randFuncOpt) apply(cfg *randConfig) {
    49  	fn(cfg)
    50  }
    51  
    52  // WithMaxStrLen returns an option to set maximum length of random JSON string objects.
    53  func WithMaxStrLen(l int) RandOption {
    54  	return randFuncOpt(func(cfg *randConfig) {
    55  		cfg.maxLen = l
    56  	})
    57  }
    58  
    59  // WithComplexity returns an option to set maximum complexity of JSON objects.
    60  func WithComplexity(c int) RandOption {
    61  	return randFuncOpt(func(cfg *randConfig) {
    62  		cfg.complexity = c
    63  	})
    64  }
    65  
    66  // WithEscapeProb returns an option that configures the probability
    67  // of producing escaped character in JSON.
    68  // Setting to 0 produces JSON strings consisting of printable characters only.
    69  func WithEscapeProb(p float32) RandOption {
    70  	return randFuncOpt(func(cfg *randConfig) {
    71  		cfg.escapeProb = p
    72  	})
    73  }
    74  
    75  // defaultRandConfig is the default configuration for generating
    76  // random JSON objects.
    77  var defaultRandConfig = func() randConfig {
    78  	return randConfig{
    79  		maxLen:     defaultRandStrLen,
    80  		complexity: 20,
    81  		escapeProb: 0.01, // ~100 chars in our alphabet, one of which is \.
    82  	}
    83  }()
    84  
    85  // objectKeyConfig is the configuration for generating object keys.
    86  // Object keys are usually short, and are alphanumeric.
    87  var objectKeyConfig = func() randConfig {
    88  	return randConfig{
    89  		maxLen:     defaultRandStrLen,
    90  		escapeProb: 0,
    91  	}
    92  }()
    93  
    94  // RandGen generates a random JSON value configured with specified options.
    95  func RandGen(rng *rand.Rand, opts ...RandOption) (JSON, error) {
    96  	cfg := defaultRandConfig
    97  	for _, opt := range opts {
    98  		opt.apply(&cfg)
    99  	}
   100  	return MakeJSON(doRandomJSON(rng, cfg))
   101  }
   102  
   103  // Random generates a random JSON value.
   104  func Random(complexity int, rng *rand.Rand) (JSON, error) {
   105  	cfg := randConfig{
   106  		maxLen:     defaultRandStrLen,
   107  		complexity: complexity,
   108  	}
   109  	return MakeJSON(doRandomJSON(rng, cfg))
   110  }
   111  
   112  const defaultRandStrLen = 10
   113  const hexAlphabetUpperAndLower = "0123456789abcdefABCDEF"
   114  const escapeAlphabet = `"\/'bfnrtu`
   115  
   116  func randomJSONString(rng *rand.Rand, cfg randConfig) string {
   117  	if cfg.maxLen <= defaultRandStrLen && rng.Intn(2) == 0 {
   118  		return staticStrings[rng.Intn(len(staticStrings))]
   119  	}
   120  
   121  	// generate string of this size, biased to produce larger strings.
   122  	l := rng.Intn(cfg.maxLen-cfg.maxLen/4) + cfg.maxLen>>2
   123  	if cfg.escapeProb == 0 {
   124  		return randutil.RandString(rng, l, randutil.PrintableKeyAlphabet)
   125  	}
   126  
   127  	result := make([]byte, 0)
   128  
   129  	for i := 0; i < l; i++ {
   130  		if rng.Float32() < cfg.escapeProb {
   131  			c := escapeAlphabet[rng.Intn(len(escapeAlphabet))]
   132  			result = append(result, '\\', c)
   133  			i++
   134  			if c == 'u' {
   135  				// Generate random unicode sequence.
   136  				result = append(result, []byte(randutil.RandString(rng, 4, hexAlphabetUpperAndLower))...)
   137  				i += 4
   138  			}
   139  		} else {
   140  			c := byte(rng.Intn(0x7f-0x20) + 0x20)
   141  			if c == '\\' {
   142  				// Retry -- escape handled above.
   143  				i--
   144  				continue
   145  			}
   146  			result = append(result, c)
   147  		}
   148  	}
   149  	return string(result)
   150  }
   151  
   152  func randomJSONNumber(rng *rand.Rand) interface{} {
   153  	return json.Number(fmt.Sprintf("%v", rng.ExpFloat64()))
   154  }
   155  
   156  func doRandomJSON(rng *rand.Rand, cfg randConfig) interface{} {
   157  	if cfg.complexity <= 0 || rng.Intn(cfg.complexity) == 0 {
   158  		switch rng.Intn(5) {
   159  		case 0:
   160  			return randomJSONString(rng, cfg)
   161  		case 1:
   162  			return randomJSONNumber(rng)
   163  		case 2:
   164  			return true
   165  		case 3:
   166  			return false
   167  		case 4:
   168  			return nil
   169  		}
   170  	}
   171  	cfg.complexity--
   172  	switch rng.Intn(3) {
   173  	case 0:
   174  		result := make([]interface{}, 0)
   175  		for cfg.complexity > 0 {
   176  			amount := 1 + rng.Intn(cfg.complexity)
   177  			cfg.complexity -= amount
   178  			result = append(result, doRandomJSON(rng, cfg))
   179  		}
   180  		return result
   181  	case 1:
   182  		result := make(map[string]interface{})
   183  		for cfg.complexity > 0 {
   184  			amount := 1 + rng.Intn(cfg.complexity)
   185  			cfg.complexity -= amount
   186  			result[randomJSONString(rng, objectKeyConfig)] = doRandomJSON(rng, cfg)
   187  		}
   188  		return result
   189  	default:
   190  		j, _ := MakeJSON(doRandomJSON(rng, cfg))
   191  		encoding, _ := EncodeJSON(nil, j)
   192  		encoded, _ := newEncodedFromRoot(encoding)
   193  		return encoded
   194  	}
   195  }
   196  
   197  // AsStringWithErrorChance returns string representation of JSON object,
   198  // but allows up to specified chance that the returned string will contain
   199  // errors -- i.e. the string should not be parse-able back into JSON.
   200  func AsStringWithErrorChance(j JSON, rng *rand.Rand, p float32) string {
   201  	ej := &errJSON{
   202  		JSON:    j,
   203  		errProb: p,
   204  		rng:     rng,
   205  	}
   206  	return asString(ej)
   207  }
   208  
   209  // garbage is array of strings that will be appended (or prepended) to the
   210  // JSON string to produce invalid JSON string.
   211  var garbage = []string{
   212  	" [", " ]", " []", "[1]", " {", " }", "{}", `{"garbage": "in"}`, "-", " garbage", " 0", `" 0"`,
   213  }
   214  
   215  // numericGarbage are numeric garbage tokens that will be added
   216  // to the number string to make it invalid.
   217  var numericGarbage = []string{
   218  	"0", // okay to append, but prepending should produce an error
   219  	"=", "-", "e", "E", "e+", "E+",
   220  }
   221  
   222  type errJSON struct {
   223  	JSON
   224  	errProb float32
   225  	rng     *rand.Rand
   226  }
   227  
   228  func (j *errJSON) injectErr() bool {
   229  	r := j.rng.Float32() < j.errProb
   230  	j.errProb /= 2 // decay
   231  	return r
   232  }
   233  
   234  func (j *errJSON) writeGarbage(pile []string, buf *bytes.Buffer) {
   235  	if j.injectErr() {
   236  		buf.WriteString(pile[j.rng.Intn(len(pile))])
   237  	}
   238  }
   239  
   240  func (j *errJSON) errJSON(other JSON) JSON {
   241  	return &errJSON{
   242  		JSON:    other,
   243  		errProb: j.errProb,
   244  		rng:     j.rng,
   245  	}
   246  }
   247  
   248  // Format implements JSON, and overrides underlying JSON object
   249  // implementation in order to inject errors.
   250  func (j *errJSON) Format(buf *bytes.Buffer) {
   251  	j.writeGarbage(garbage, buf) // Possibly prefix with garbage data.
   252  	defer func() {
   253  		j.writeGarbage(garbage, buf) // Possibly append garbage data.
   254  	}()
   255  
   256  	switch t := j.JSON.(type) {
   257  	default:
   258  		j.JSON.Format(buf)
   259  	case jsonObject:
   260  		if j.injectErr() {
   261  			buf.WriteByte('[') // Oops, array instead of object.
   262  		} else {
   263  			buf.WriteByte('{')
   264  		}
   265  		for i := range t {
   266  			if i != 0 {
   267  				buf.WriteString(", ")
   268  			}
   269  			// Skip element (which results in trailing or extra ",")
   270  			// if injectErr is true.
   271  			if !j.injectErr() {
   272  				ek := j.errJSON(t[i].k)
   273  				ev := j.errJSON(t[i].v)
   274  				buf.WriteString(asString(ek))
   275  				buf.WriteString(": ")
   276  				ev.Format(buf)
   277  			}
   278  		}
   279  		if j.injectErr() {
   280  			buf.WriteByte(']') // Oops, array instead of object.
   281  		} else {
   282  			buf.WriteByte('}')
   283  		}
   284  	case jsonArray:
   285  		if j.injectErr() {
   286  			buf.WriteByte('{') // Oops, object instead of array.
   287  		} else {
   288  			buf.WriteByte('[')
   289  		}
   290  		for i := range t {
   291  			if i != 0 {
   292  				buf.WriteString(", ")
   293  			}
   294  			// Skip element (which results in trailing or extra ",")
   295  			// if injectErr is true.
   296  			if !j.injectErr() {
   297  				j.errJSON(t[i]).Format(buf)
   298  			}
   299  		}
   300  		if j.injectErr() {
   301  			buf.WriteByte('}') // Oops, object instead of array.
   302  		} else {
   303  			buf.WriteByte(']')
   304  		}
   305  	case jsonString:
   306  		t.Format(buf)
   307  		if j.injectErr() {
   308  			// Drop terminating quote.
   309  			buf.Truncate(1)
   310  		}
   311  	case jsonNumber:
   312  		if j.injectErr() {
   313  			n := j.rng.Int31n(3) // 0 -> prefix, 1 -> prefix & suffix, 2 -> suffix only
   314  			if n <= 1 {
   315  				j.writeGarbage(numericGarbage, buf) // Prefix
   316  			}
   317  			t.Format(buf)
   318  			if n >= 1 {
   319  				j.writeGarbage(numericGarbage, buf)
   320  			}
   321  		} else {
   322  			t.Format(buf)
   323  		}
   324  	case jsonNull:
   325  		if j.injectErr() {
   326  			buf.WriteString("nil")
   327  		} else {
   328  			buf.WriteString("null")
   329  		}
   330  	case jsonTrue:
   331  		if j.injectErr() {
   332  			buf.WriteString("truish")
   333  		} else {
   334  			buf.WriteString("true")
   335  		}
   336  	case jsonFalse:
   337  		if j.injectErr() {
   338  			buf.WriteString("falsy")
   339  		} else {
   340  			buf.WriteString("false")
   341  		}
   342  	}
   343  }