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 }