pkg/genlib/generator_interface.go (1,039 lines of code) (raw):

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. package genlib import ( "bytes" "encoding/json" "errors" "fmt" "math" "regexp" "strconv" "strings" "sync" "testing" "time" "github.com/Pallinder/go-randomdata" "github.com/elastic/elastic-integration-corpus-generator-tool/pkg/genlib/config" "github.com/elastic/elastic-integration-corpus-generator-tool/pkg/genlib/fields" ) var timeNowToBind time.Time type ( Fields = fields.Fields Field = fields.Field Config = config.Config ConfigField = config.ConfigField ) const ( FieldTypeBool = "boolean" FieldTypeKeyword = "keyword" FieldTypeConstantKeyword = "constant_keyword" FieldTypeDate = "date" FieldTypeIP = "ip" FieldTypeDouble = "double" FieldTypeFloat = "float" FieldTypeHalfFloat = "half_float" FieldTypeScaledFloat = "scaled_float" FieldTypeInteger = "integer" FieldTypeLong = "long" FieldTypeUnsignedLong = "unsigned_long" FieldTypeObject = "object" FieldTypeNested = "nested" FieldTypeFlattened = "flattened" FieldTypeGeoPoint = "geo_point" FieldTypeDurationSpan = 1000 // milliseconds FieldTypeTimeLayout = "2006-01-02T15:04:05.999999Z07:00" ) var ( replacer = strings.NewReplacer(".*", "") fieldNormalizerRegex = regexp.MustCompile("[^a-zA-Z0-9]") keywordRegex = regexp.MustCompile("(\\.|-|_|\\s){1,1}") ) // This is the emit function for the custom template engine where we stream content directly to the output buffer and no need a return value type emitFNotReturn func(state *genState, buf *bytes.Buffer) error // emitF Typedef of the internal emit function type emitF func(state *genState) any type Generator interface { Emit(buf *bytes.Buffer) error Close() error } type genState struct { // event counter counter uint64 // total events totEvents uint64 // previous value cache; necessary for fuzziness, cardinality, etc. prevCache map[string]any // previous value cache for dup check; necessary for cardinality prevCacheForDup map[string]map[any]struct{} // previous cardinality value cache; necessary for cardinality prevCacheCardinality map[string][]any // internal buffer pool to decrease load on GC pool sync.Pool } func newGenState() *genState { return &genState{ prevCache: make(map[string]any), prevCacheForDup: make(map[string]map[any]struct{}), prevCacheCardinality: make(map[string][]any, 0), pool: sync.Pool{ New: func() any { return new(bytes.Buffer) }, }, } } func bindField(cfg Config, field Field, fieldMap map[string]any, withReturn bool) error { // Check for hardcoded field value if len(field.Value) > 0 { if withReturn { return bindStaticWithReturn(field, field.Value, fieldMap) } else { return bindStatic(field, field.Value, fieldMap) } } // Check config override of value fieldCfg, _ := cfg.GetField(field.Name) if fieldCfg.Value != nil { if withReturn { return bindStaticWithReturn(field, fieldCfg.Value, fieldMap) } else { return bindStatic(field, fieldCfg.Value, fieldMap) } } if fieldCfg.Cardinality > 0 { if withReturn { return bindCardinalityWithReturn(cfg, field, fieldMap) } else { return bindCardinality(cfg, field, fieldMap) } } if withReturn { return bindByTypeWithReturn(cfg, field, fieldMap) } else { return bindByType(cfg, field, fieldMap) } } // Check for dupes O(n) func isDupeByteSlice(va []bytes.Buffer, dst []byte) bool { var dupe bool for _, b := range va { if bytes.Equal(dst, b.Bytes()) { dupe = true break } } return dupe } func isDupeAny(va map[any]struct{}, dst any) bool { _, ok := va[dst] return ok } // Check for dupes O(n) func isDupeInterface(va []any, dst any) bool { var dupe bool for _, b := range va { if b == dst { dupe = true break } } return dupe } func bindByType(cfg Config, field Field, fieldMap map[string]any) (err error) { fieldCfg, _ := cfg.GetField(field.Name) switch field.Type { case FieldTypeDate: err = bindNearTime(fieldCfg, field, fieldMap) case FieldTypeIP: err = bindIP(field, fieldMap) case FieldTypeDouble, FieldTypeFloat, FieldTypeHalfFloat, FieldTypeScaledFloat: err = bindDouble(fieldCfg, field, fieldMap) case FieldTypeInteger, FieldTypeLong, FieldTypeUnsignedLong: // TODO: generate > 63 bit values for unsigned_long err = bindLong(fieldCfg, field, fieldMap) case FieldTypeConstantKeyword: err = bindConstantKeyword(field, fieldMap) case FieldTypeKeyword: err = bindKeyword(fieldCfg, field, fieldMap) case FieldTypeBool: err = bindBool(field, fieldMap) case FieldTypeObject, FieldTypeNested, FieldTypeFlattened: err = bindObject(cfg, fieldCfg, field, fieldMap) case FieldTypeGeoPoint: err = bindGeoPoint(field, fieldMap) default: err = bindWordN(field, 25, fieldMap) } return } func bindByTypeWithReturn(cfg Config, field Field, fieldMap map[string]any) (err error) { fieldCfg, _ := cfg.GetField(field.Name) switch field.Type { case FieldTypeDate: err = bindNearTimeWithReturn(fieldCfg, field, fieldMap) case FieldTypeIP: err = bindIPWithReturn(field, fieldMap) case FieldTypeDouble, FieldTypeFloat, FieldTypeHalfFloat, FieldTypeScaledFloat: err = bindDoubleWithReturn(fieldCfg, field, fieldMap) case FieldTypeInteger, FieldTypeLong, FieldTypeUnsignedLong: // TODO: generate > 63 bit values for unsigned_long err = bindLongWithReturn(fieldCfg, field, fieldMap) case FieldTypeConstantKeyword: err = bindConstantKeywordWithReturn(field, fieldMap) case FieldTypeKeyword: err = bindKeywordWithReturn(fieldCfg, field, fieldMap) case FieldTypeBool: err = bindBoolWithReturn(field, fieldMap) case FieldTypeObject, FieldTypeNested, FieldTypeFlattened: err = bindObjectWithReturn(cfg, fieldCfg, field, fieldMap) case FieldTypeGeoPoint: err = bindGeoPointWithReturn(field, fieldMap) default: err = bindWordNWithReturn(field, 25, fieldMap) } return } func makeFloatFunc(fieldCfg ConfigField, field Field) func() float64 { minValue, _ := fieldCfg.Range.MinAsFloat64() maxValue, err := fieldCfg.Range.MaxAsFloat64() // maxValue not set, let's set it to 0 for the sake of the switch above if err != nil { maxValue = 0 } var dummyFunc func() float64 switch { case maxValue > 0: dummyFunc = func() float64 { return minValue + customRand.Float64()*(maxValue-minValue) } case len(field.Example) == 0: dummyFunc = func() float64 { return customRand.Float64() * 10 } default: totDigit := len(field.Example) max := math.Pow10(totDigit) dummyFunc = func() float64 { return customRand.Float64() * max } } return dummyFunc } func makeIntFunc(fieldCfg ConfigField, field Field) func() int64 { minValue, _ := fieldCfg.Range.MinAsInt64() maxValue, err := fieldCfg.Range.MaxAsInt64() // maxValue not set, let's set it to 0 for the sake of the switch above if err != nil { maxValue = 0 } var dummyFunc func() int64 switch { case maxValue > 0: dummyFunc = func() int64 { return customRand.Int63n(maxValue-minValue) + minValue } case len(field.Example) == 0: dummyFunc = func() int64 { return customRand.Int63n(10) } default: totDigit := len(field.Example) max := int64(math.Pow10(totDigit)) dummyFunc = func() int64 { return customRand.Int63n(max) } } return dummyFunc } func bindObject(cfg Config, fieldCfg ConfigField, field Field, fieldMap map[string]any) error { if len(field.ObjectType) > 0 { field.Type = field.ObjectType } else { field.Type = FieldTypeKeyword } objectRootFieldName := replacer.Replace(field.Name) if len(fieldCfg.ObjectKeys) > 0 { for _, objectsKey := range fieldCfg.ObjectKeys { field.Name = objectRootFieldName + "." + objectsKey if err := bindField(cfg, field, fieldMap, false); err != nil { return err } } return nil } return bindDynamicObject(cfg, field, fieldMap) } func bindDynamicObject(cfg Config, field Field, fieldMap map[string]any) error { // Temporary fieldMap which we pass to the bind function, // then extract the generated emitFunction for use in the stub. dynMap := make(map[string]any) if err := bindField(cfg, field, dynMap, false); err != nil { return err } stub := makeDynamicStub(dynMap[field.Name]) fieldMap[field.Name] = stub return nil } func genNounsN(n int, buf *bytes.Buffer) { for i := 0; i < n-1; i++ { buf.WriteString(randomdata.Noun()) buf.WriteByte(' ') } // randomdata.Adjective() + randomdata.Noun() -> 364 * 527 (~190k) different values buf.WriteString(randomdata.Adjective()) buf.WriteString(randomdata.Noun()) } func genNounsNWithReturn(n int) string { value := "" for i := 0; i < n-1; i++ { value += randomdata.Noun() + " " } // randomdata.Adjective() + randomdata.Noun() -> 364 * 527 (~190k) different values value += randomdata.Adjective() value += randomdata.Noun() return value } func randGeoPoint() (int, int, int, int) { lat := customRand.Intn(181) - 90 var latD int if lat != -90 && lat != 90 { latD = customRand.Intn(100) } var longD int long := customRand.Intn(361) - 180 if long != -180 && long != 180 { longD = customRand.Intn(100) } return lat, latD, long, longD } func bindConstantKeyword(field Field, fieldMap map[string]any) error { var emitFNotReturn emitFNotReturn emitFNotReturn = func(state *genState, buf *bytes.Buffer) error { value, ok := state.prevCache[field.Name].(string) if !ok { // randomdata.Adjective() + randomdata.Noun() -> 364 * 527 (~190k) different values value = randomdata.Adjective() + randomdata.Noun() state.prevCache[field.Name] = value } buf.WriteString(value) return nil } fieldMap[field.Name] = emitFNotReturn return nil } func bindKeyword(fieldCfg ConfigField, field Field, fieldMap map[string]any) error { if len(fieldCfg.Enum) > 0 { var emitFNotReturn emitFNotReturn emitFNotReturn = func(state *genState, buf *bytes.Buffer) error { idx := customRand.Intn(len(fieldCfg.Enum)) buf.WriteString(fieldCfg.Enum[idx]) return nil } fieldMap[field.Name] = emitFNotReturn } else if len(field.Example) > 0 { totWords, joiner := totWordsAndJoiner(field.Example) return bindJoinRand(field, totWords, joiner, fieldMap) } else { var emitFNotReturn emitFNotReturn emitFNotReturn = func(state *genState, buf *bytes.Buffer) error { // randomdata.Adjective() + randomdata.Noun() -> 364 * 527 (~190k) different values buf.WriteString(randomdata.Adjective() + randomdata.Noun()) return nil } fieldMap[field.Name] = emitFNotReturn } return nil } func totWordsAndJoiner(fieldExample string) (int, string) { totWords := len(keywordRegex.Split(fieldExample, -1)) var joiner string if strings.Contains(fieldExample, ".") { joiner = "." } else if strings.Contains(fieldExample, "-") { joiner = "-" } else if strings.Contains(fieldExample, "_") { joiner = "_" } else if strings.Contains(fieldExample, " ") { joiner = " " } return totWords, joiner } func bindJoinRand(field Field, N int, joiner string, fieldMap map[string]any) error { var emitFNotReturn emitFNotReturn emitFNotReturn = func(state *genState, buf *bytes.Buffer) error { for i := 0; i < N-1; i++ { buf.WriteString(randomdata.Noun()) buf.WriteString(joiner) } // randomdata.Adjective() + randomdata.Noun() -> 364 * 527 (~190k) different values buf.WriteString(randomdata.Adjective()) buf.WriteString(randomdata.Noun()) return nil } fieldMap[field.Name] = emitFNotReturn return nil } func bindStatic(field Field, v any, fieldMap map[string]any) error { vstr, err := json.Marshal(v) if err != nil { return err } var emitFNotReturn emitFNotReturn emitFNotReturn = func(state *genState, buf *bytes.Buffer) error { buf.Write(vstr) return nil } fieldMap[field.Name] = emitFNotReturn return nil } func bindBool(field Field, fieldMap map[string]any) error { var emitFNotReturn emitFNotReturn emitFNotReturn = func(state *genState, buf *bytes.Buffer) error { switch customRand.Int() % 2 { case 0: buf.WriteString("false") case 1: buf.WriteString("true") } return nil } fieldMap[field.Name] = emitFNotReturn return nil } func bindGeoPoint(field Field, fieldMap map[string]any) error { var emitFNotReturn emitFNotReturn emitFNotReturn = func(state *genState, buf *bytes.Buffer) error { lat, latD, long, longD := randGeoPoint() _, err := fmt.Fprintf(buf, "%d.%d,%d.%d", lat, latD, long, longD) return err } fieldMap[field.Name] = emitFNotReturn return nil } func bindWordN(field Field, n int, fieldMap map[string]any) error { var emitFNotReturn emitFNotReturn emitFNotReturn = func(state *genState, buf *bytes.Buffer) error { genNounsN(customRand.Intn(n), buf) return nil } fieldMap[field.Name] = emitFNotReturn return nil } func bindNearTime(fieldCfg ConfigField, field Field, fieldMap map[string]any) error { if err := fieldCfg.ValidForDateField(); err != nil { return err } var emitFNotReturn emitFNotReturn emitFNotReturn = func(state *genState, buf *bytes.Buffer) error { newTime := nearTime(fieldCfg, state) buf.WriteString(newTime.Format(FieldTypeTimeLayout)) return nil } fieldMap[field.Name] = emitFNotReturn return nil } func nearTime(fieldCfg ConfigField, state *genState) time.Time { var offset time.Duration from, errFrom := fieldCfg.Range.FromAsTime() to, errTo := fieldCfg.Range.ToAsTime() if errFrom == nil && errTo == nil { timeNowToBind = from fieldCfg.Period = to.UTC().Sub(from.UTC()) } if errFrom == nil && errTo != nil { if from.UTC().After(timeNowToBind.UTC()) { fieldCfg.Period = from.UTC().Sub(timeNowToBind.UTC()) } else { fieldCfg.Period = timeNowToBind.UTC().Sub(from.UTC()) } } if errFrom != nil && errTo == nil { if to.UTC().After(timeNowToBind.UTC()) { fieldCfg.Period = to.UTC().Sub(timeNowToBind.UTC()) } else { fieldCfg.Period = timeNowToBind.UTC().Sub(to.UTC()) } } if fieldCfg.Period > 0 && state.totEvents > 0 { offset = time.Duration((fieldCfg.Period.Nanoseconds() / int64(state.totEvents)) * int64(state.counter)) } else if fieldCfg.Period < 0 && state.totEvents > 0 { offset = time.Duration((fieldCfg.Period.Nanoseconds() / int64(state.totEvents)) * (int64(state.totEvents - state.counter))) } else { offset = time.Duration(customRand.Intn(FieldTypeDurationSpan)) * time.Millisecond } newTime := timeNowToBind.Add(offset) if state.totEvents <= 0 { timeNowToBind = newTime } return newTime } func bindIP(field Field, fieldMap map[string]any) error { var emitFNotReturn emitFNotReturn emitFNotReturn = func(state *genState, buf *bytes.Buffer) error { i0, i1, i2, i3 := randIP() _, err := fmt.Fprintf(buf, "%d.%d.%d.%d", i0, i1, i2, i3) return err } fieldMap[field.Name] = emitFNotReturn return nil } func fuzzyInt(previous int64, fuzziness, min, max float64) int64 { lowerBound := float64(previous) * (1 - fuzziness) higherBound := float64(previous) * (1 + fuzziness) lowerBound = math.Max(lowerBound, min) higherBound = math.Min(higherBound, max) return customRand.Int63n(int64(math.Ceil(higherBound-lowerBound))) + int64(lowerBound) } func fuzzyIntCounter(previous int64, fuzziness float64) int64 { lowerBound := float64(previous) higherBound := float64(previous) * (1 + fuzziness) return customRand.Int63n(int64(math.Ceil(higherBound-lowerBound))) + int64(lowerBound) } func bindLong(fieldCfg ConfigField, field Field, fieldMap map[string]any) error { if err := fieldCfg.ValidCounter(); err != nil { return err } if fieldCfg.Counter { var emitFNotReturn emitFNotReturn emitFNotReturn = func(state *genState, buf *bytes.Buffer) error { previous := int64(1) var dummyInt int64 var dummyFunc func() int64 if previousDummyInt, ok := state.prevCache[field.Name].(int64); ok { previous = previousDummyInt } if fieldCfg.Fuzziness <= 0 { dummyFunc = makeIntCounterFunc(previous, field) dummyInt = dummyFunc() } else { dummyInt = fuzzyIntCounter(previous, fieldCfg.Fuzziness) } state.prevCache[field.Name] = dummyInt v := make([]byte, 0, 32) v = strconv.AppendInt(v, dummyInt, 10) buf.Write(v) return nil } fieldMap[field.Name] = emitFNotReturn return nil } dummyFunc := makeIntFunc(fieldCfg, field) if fieldCfg.Fuzziness <= 0 { var emitFNotReturn emitFNotReturn emitFNotReturn = func(state *genState, buf *bytes.Buffer) error { v := make([]byte, 0, 32) v = strconv.AppendInt(v, dummyFunc(), 10) buf.Write(v) return nil } fieldMap[field.Name] = emitFNotReturn return nil } min, _ := fieldCfg.Range.MinAsFloat64() max, _ := fieldCfg.Range.MaxAsFloat64() var emitFNotReturn emitFNotReturn emitFNotReturn = func(state *genState, buf *bytes.Buffer) error { var dummyInt int64 if previousDummyInt, ok := state.prevCache[field.Name].(int64); ok { if previousDummyInt == 0 { previousDummyInt = 1 } dummyInt = fuzzyInt(previousDummyInt, fieldCfg.Fuzziness, min, max) } else { dummyInt = dummyFunc() } state.prevCache[field.Name] = dummyInt v := make([]byte, 0, 32) v = strconv.AppendInt(v, dummyInt, 10) buf.Write(v) return nil } fieldMap[field.Name] = emitFNotReturn return nil } func fuzzyFloat(previous, fuzziness, min, max float64) float64 { lowerBound := previous * (1 - fuzziness) higherBound := previous * (1 + fuzziness) lowerBound = math.Max(lowerBound, min) higherBound = math.Min(higherBound, max) return lowerBound + customRand.Float64()*(higherBound-lowerBound) } func fuzzyFloatCounter(previous, fuzziness float64) float64 { lowerBound := previous higherBound := previous * (1 + fuzziness) return lowerBound + customRand.Float64()*(higherBound-lowerBound) } func bindDouble(fieldCfg ConfigField, field Field, fieldMap map[string]any) error { if err := fieldCfg.ValidCounter(); err != nil { return err } if fieldCfg.Counter { var emitFNotReturn emitFNotReturn emitFNotReturn = func(state *genState, buf *bytes.Buffer) error { previous := float64(1) var dummyFloat float64 var dummyFunc func() float64 if previousDummyFloat, ok := state.prevCache[field.Name].(float64); ok { previous = previousDummyFloat } if fieldCfg.Fuzziness <= 0 { dummyFunc = makeFloatCounterFunc(previous, field) dummyFloat = dummyFunc() } else { dummyFloat = fuzzyFloatCounter(previous, fieldCfg.Fuzziness) } state.prevCache[field.Name] = dummyFloat _, err := fmt.Fprintf(buf, "%f", dummyFloat) return err } fieldMap[field.Name] = emitFNotReturn return nil } dummyFunc := makeFloatFunc(fieldCfg, field) if fieldCfg.Fuzziness <= 0 { var emitFNotReturn emitFNotReturn emitFNotReturn = func(state *genState, buf *bytes.Buffer) error { dummyFloat := dummyFunc() _, err := fmt.Fprintf(buf, "%f", dummyFloat) return err } fieldMap[field.Name] = emitFNotReturn return nil } min, _ := fieldCfg.Range.MinAsFloat64() max, _ := fieldCfg.Range.MaxAsFloat64() var emitFNotReturn emitFNotReturn emitFNotReturn = func(state *genState, buf *bytes.Buffer) error { var dummyFloat float64 if previousDummyFloat, ok := state.prevCache[field.Name].(float64); ok { dummyFloat = fuzzyFloat(previousDummyFloat, fieldCfg.Fuzziness, min, max) } else { dummyFloat = dummyFunc() } state.prevCache[field.Name] = dummyFloat _, err := fmt.Fprintf(buf, "%f", dummyFloat) return err } fieldMap[field.Name] = emitFNotReturn return nil } func bindCardinality(cfg Config, field Field, fieldMap map[string]any) error { fieldCfg, _ := cfg.GetField(field.Name) cardinality := fieldCfg.Cardinality if strings.HasSuffix(field.Name, ".*") { field.Name = replacer.Replace(field.Name) } // Go ahead and bind the original field if err := bindByType(cfg, field, fieldMap); err != nil { return err } // We will wrap the function we just generated boundF, ok := fieldMap[field.Name].(emitFNotReturn) if !ok { return errors.New("cannot bind cardinality") } var emitFNotReturn emitFNotReturn emitFNotReturn = func(state *genState, buf *bytes.Buffer) error { // Have we rolled over once? If not, generate a value and cache it. if len(state.prevCacheCardinality[field.Name]) < cardinality { // Do college try dupe detection on value; // Allow dupe if no unique value in nTries. nTries := 11 // "These go to 11." var tmp bytes.Buffer var value []byte for i := 0; i < nTries; i++ { tmp.Reset() if err := boundF(state, &tmp); err != nil { return err } value = tmp.Bytes() if !isDupeAny(state.prevCacheForDup[field.Name], string(value)) { break } } state.prevCacheForDup[field.Name][string(value)] = struct{}{} state.prevCacheCardinality[field.Name] = append(state.prevCacheCardinality[field.Name], value) } idx := int(state.counter % uint64(cardinality)) // Safety check; should be a noop if idx >= len(state.prevCacheCardinality[field.Name]) { idx = len(state.prevCacheCardinality[field.Name]) - 1 } choice := state.prevCacheCardinality[field.Name][idx].([]byte) buf.Write(choice) return nil } fieldMap[field.Name] = emitFNotReturn return nil } func makeDynamicStub(boundF any) emitFNotReturn { return func(state *genState, buf *bytes.Buffer) error { v := state.pool.Get() tmp := v.(*bytes.Buffer) tmp.Reset() defer state.pool.Put(tmp) // Fire the bound function, write into temp buffer err := boundF.(emitFNotReturn)(state, tmp) if err != nil { return err } // If bound function did not write for some reason; abort if tmp.Len() == 0 { return nil } // ok, formatted as expected, swap it out the payload buf.Write(tmp.Bytes()) return nil } } func makeDynamicStubWithReturn(boundF any) emitF { return func(state *genState) any { return boundF.(emitF)(state) } } func bindConstantKeywordWithReturn(field Field, fieldMap map[string]any) error { var emitF emitF emitF = func(state *genState) any { value, ok := state.prevCache[field.Name].(string) if !ok { // randomdata.Adjective() + randomdata.Noun() -> 364 * 527 (~190k) different values value = randomdata.Adjective() + randomdata.Noun() state.prevCache[field.Name] = value } return value } fieldMap[field.Name] = emitF return nil } func bindKeywordWithReturn(fieldCfg ConfigField, field Field, fieldMap map[string]any) error { if len(fieldCfg.Enum) > 0 { var emitF emitF emitF = func(state *genState) any { idx := customRand.Intn(len(fieldCfg.Enum)) return fieldCfg.Enum[idx] } fieldMap[field.Name] = emitF } else if len(field.Example) > 0 { totWords, joiner := totWordsAndJoiner(field.Example) return bindJoinRandWithReturn(field, totWords, joiner, fieldMap) } else { var emitF emitF emitF = func(state *genState) any { // randomdata.Adjective() + randomdata.Noun() -> 364 * 527 (~190k) different values return randomdata.Adjective() + randomdata.Noun() } fieldMap[field.Name] = emitF } return nil } func bindJoinRandWithReturn(field Field, N int, joiner string, fieldMap map[string]any) error { var emitF emitF emitF = func(state *genState) any { value := "" for i := 0; i < N-1; i++ { value += randomdata.Noun() + joiner } // randomdata.Adjective() + randomdata.Noun() -> 364 * 527 (~190k) different values value += randomdata.Adjective() value += randomdata.Noun() return value } fieldMap[field.Name] = emitF return nil } func bindStaticWithReturn(field Field, v any, fieldMap map[string]any) error { var emitF emitF emitF = func(state *genState) any { return v } fieldMap[field.Name] = emitF return nil } func bindBoolWithReturn(field Field, fieldMap map[string]any) error { var emitF emitF emitF = func(state *genState) any { switch customRand.Int() % 2 { case 0: return false default: return true } } fieldMap[field.Name] = emitF return nil } func bindGeoPointWithReturn(field Field, fieldMap map[string]any) error { var emitF emitF emitF = func(state *genState) any { lat, latD, long, longD := randGeoPoint() return fmt.Sprintf("%d.%d,%d.%d", lat, latD, long, longD) } fieldMap[field.Name] = emitF return nil } func bindWordNWithReturn(field Field, n int, fieldMap map[string]any) error { var emitF emitF emitF = func(state *genState) any { return genNounsNWithReturn(customRand.Intn(n)) } fieldMap[field.Name] = emitF return nil } func bindNearTimeWithReturn(fieldCfg ConfigField, field Field, fieldMap map[string]any) error { if err := fieldCfg.ValidForDateField(); err != nil { return err } var emitF emitF emitF = func(state *genState) any { return nearTime(fieldCfg, state) } fieldMap[field.Name] = emitF return nil } func bindIPWithReturn(field Field, fieldMap map[string]any) error { var emitF emitF emitF = func(state *genState) any { i0, i1, i2, i3 := randIP() return fmt.Sprintf("%d.%d.%d.%d", i0, i1, i2, i3) } fieldMap[field.Name] = emitF return nil } func randIP() (int, int, int, int) { i0 := customRand.Intn(255) i1 := customRand.Intn(255) i2 := customRand.Intn(255) i3 := customRand.Intn(255) return i0, i1, i2, i3 } func bindLongWithReturn(fieldCfg ConfigField, field Field, fieldMap map[string]any) error { if err := fieldCfg.ValidCounter(); err != nil { return err } if err := fieldCfg.ValidateCounterResetStrategy(); err != nil { return err } if err := fieldCfg.ValidateCounterResetAfterN(); err != nil { return err } if err := fieldCfg.ValidateCounterResetProbabilistic(); err != nil { return err } if len(fieldCfg.Enum) > 0 { var emitF emitF idx := customRand.Intn(len(fieldCfg.Enum)) f, err := strconv.ParseInt(fieldCfg.Enum[idx], 10, 64) if err != nil { return fmt.Errorf("field %s enum value is not a long: %w", fieldCfg.Name, err) } emitF = func(state *genState) any { return f } fieldMap[field.Name] = emitF return nil } if fieldCfg.Counter { var emitF emitF emitF = func(state *genState) any { previous := int64(1) var dummyInt int64 var dummyFunc func() int64 if previousDummyInt, ok := state.prevCache[field.Name].(int64); ok { previous = previousDummyInt } if fieldCfg.Fuzziness <= 0 { dummyFunc = makeIntCounterFunc(previous, field) dummyInt = dummyFunc() } else { dummyInt = fuzzyIntCounter(previous, fieldCfg.Fuzziness) } if fieldCfg.CounterReset != nil { switch fieldCfg.CounterReset.Strategy { case config.CounterResetStrategyRandom: // 50% chance to reset if customRand.Intn(2) == 0 { dummyInt = 0 } case config.CounterResetStrategyProbabilistic: // Probability% chance to reset if customRand.Intn(100) < int(*fieldCfg.CounterReset.Probability) { dummyInt = 0 } case config.CounterResetStrategyAfterN: // Reset after N if state.counter%*fieldCfg.CounterReset.ResetAfterN == 0 { dummyInt = 0 } } } state.prevCache[field.Name] = dummyInt return dummyInt } fieldMap[field.Name] = emitF return nil } dummyFunc := makeIntFunc(fieldCfg, field) if fieldCfg.Fuzziness <= 0 { var emitF emitF emitF = func(state *genState) any { return dummyFunc() } fieldMap[field.Name] = emitF return nil } min, _ := fieldCfg.Range.MinAsFloat64() max, _ := fieldCfg.Range.MaxAsFloat64() var emitF emitF emitF = func(state *genState) any { var dummyInt int64 if previousDummyInt, ok := state.prevCache[field.Name].(int64); ok { if previousDummyInt == 0 { previousDummyInt = 1 } dummyInt = fuzzyInt(previousDummyInt, fieldCfg.Fuzziness, min, max) } else { dummyInt = dummyFunc() } state.prevCache[field.Name] = dummyInt return dummyInt } fieldMap[field.Name] = emitF return nil } func makeIntCounterFunc(previousDummyInt int64, field Field) func() int64 { var dummyFunc func() int64 switch { case len(field.Example) == 0: dummyFunc = func() int64 { return previousDummyInt + customRand.Int63n(10) } default: totDigit := len(field.Example) max := int64(math.Pow10(totDigit)) dummyFunc = func() int64 { return previousDummyInt + customRand.Int63n(max) } } return dummyFunc } func makeFloatCounterFunc(previousDummyFloat float64, field Field) func() float64 { var dummyFunc func() float64 switch { case len(field.Example) == 0: dummyFunc = func() float64 { return previousDummyFloat + customRand.Float64()*10 } default: totDigit := len(field.Example) max := math.Pow10(totDigit) dummyFunc = func() float64 { return previousDummyFloat + customRand.Float64()*max } } return dummyFunc } func bindDoubleWithReturn(fieldCfg ConfigField, field Field, fieldMap map[string]any) error { if err := fieldCfg.ValidCounter(); err != nil { return err } if err := fieldCfg.ValidateCounterResetStrategy(); err != nil { return err } if err := fieldCfg.ValidateCounterResetAfterN(); err != nil { return err } if err := fieldCfg.ValidateCounterResetProbabilistic(); err != nil { return err } if len(fieldCfg.Enum) > 0 { var emitF emitF idx := customRand.Intn(len(fieldCfg.Enum)) f, err := strconv.ParseFloat(fieldCfg.Enum[idx], 64) if err != nil { return fmt.Errorf("field %s enum value is not a double: %w", fieldCfg.Name, err) } emitF = func(state *genState) any { return f } fieldMap[field.Name] = emitF return nil } if fieldCfg.Counter { var emitF emitF emitF = func(state *genState) any { previous := float64(1) var dummyFloat float64 var dummyFunc func() float64 if previousDummyFloat, ok := state.prevCache[field.Name].(float64); ok { previous = previousDummyFloat } if fieldCfg.Fuzziness <= 0 { dummyFunc = makeFloatCounterFunc(previous, field) dummyFloat = dummyFunc() } else { dummyFloat = fuzzyFloatCounter(previous, fieldCfg.Fuzziness) } if fieldCfg.CounterReset != nil { switch fieldCfg.CounterReset.Strategy { case config.CounterResetStrategyRandom: // 50% chance to reset if customRand.Intn(2) == 0 { dummyFloat = 0 } case config.CounterResetStrategyProbabilistic: // Probability% chance to reset if customRand.Intn(100) < int(*fieldCfg.CounterReset.Probability) { dummyFloat = 0 } case config.CounterResetStrategyAfterN: // Reset after N if state.counter%*fieldCfg.CounterReset.ResetAfterN == 0 { dummyFloat = 0 } } } state.prevCache[field.Name] = dummyFloat return dummyFloat } fieldMap[field.Name] = emitF return nil } dummyFunc := makeFloatFunc(fieldCfg, field) if fieldCfg.Fuzziness <= 0 { var emitF emitF emitF = func(state *genState) any { return dummyFunc() } fieldMap[field.Name] = emitF return nil } min, _ := fieldCfg.Range.MinAsFloat64() max, _ := fieldCfg.Range.MaxAsFloat64() var emitF emitF emitF = func(state *genState) any { var dummyFloat float64 if previousDummyFloat, ok := state.prevCache[field.Name].(float64); ok { dummyFloat = fuzzyFloat(previousDummyFloat, fieldCfg.Fuzziness, min, max) } else { dummyFloat = dummyFunc() } state.prevCache[field.Name] = dummyFloat return dummyFloat } fieldMap[field.Name] = emitF return nil } func bindCardinalityWithReturn(cfg Config, field Field, fieldMap map[string]any) error { fieldCfg, _ := cfg.GetField(field.Name) cardinality := fieldCfg.Cardinality if strings.HasSuffix(field.Name, ".*") { field.Name = replacer.Replace(field.Name) } // Go ahead and bind the original field if err := bindByTypeWithReturn(cfg, field, fieldMap); err != nil { return err } // We will wrap the function we just generated boundFWithReturn := fieldMap[field.Name].(emitF) var emitF emitF emitF = func(state *genState) any { var value any // Have we rolled over once? If not, generate a value and cache it. if len(state.prevCacheCardinality[field.Name]) < cardinality { // Do college try dupe detection on value; // Allow dupe if no unique value in nTries. nTries := 11 // "These go to 11." for i := 0; i < nTries; i++ { value = boundFWithReturn(state) if !isDupeAny(state.prevCacheForDup[field.Name], value) { break } } state.prevCacheForDup[field.Name][value] = struct{}{} state.prevCacheCardinality[field.Name] = append(state.prevCacheCardinality[field.Name], value) } idx := int(state.counter % uint64(cardinality)) // Safety check; should be a noop if idx >= len(state.prevCacheCardinality[field.Name]) { idx = len(state.prevCacheCardinality[field.Name]) - 1 } choice := state.prevCacheCardinality[field.Name][idx] return choice } fieldMap[field.Name] = emitF return nil } func bindObjectWithReturn(cfg Config, fieldCfg ConfigField, field Field, fieldMap map[string]any) error { if len(field.ObjectType) > 0 { field.Type = field.ObjectType } else { field.Type = FieldTypeKeyword } objectRootFieldName := replacer.Replace(field.Name) if len(fieldCfg.ObjectKeys) > 0 { for _, objectsKey := range fieldCfg.ObjectKeys { field.Name = objectRootFieldName + "." + objectsKey if err := bindField(cfg, field, fieldMap, true); err != nil { return err } } return nil } return bindDynamicObjectWithReturn(cfg, field, fieldMap) } func bindDynamicObjectWithReturn(cfg Config, field Field, fieldMap map[string]any) error { // Temporary fieldMap which we pass to the bind function, // then extract the generated emitFunction for use in the stub. dynMap := make(map[string]any) if err := bindField(cfg, field, dynMap, true); err != nil { return err } stub := makeDynamicStubWithReturn(dynMap[field.Name]) fieldMap[field.Name] = stub return nil } func unmarshalJSONT[T any](t *testing.T, data []byte) map[string]T { m := make(map[string]T) if err := json.Unmarshal(data, &m); err != nil { t.Fatal(err) } return m }