diff --git a/cmd/gemini/root.go b/cmd/gemini/root.go index 059d68a2..5209af90 100644 --- a/cmd/gemini/root.go +++ b/cmd/gemini/root.go @@ -159,7 +159,11 @@ func runJob(f testJob, schema *gemini.Schema, s *gemini.Session, mode string) { for _, table := range schema.Tables { for i := 0; i < concurrency; i++ { - p := gemini.PartitionRange{Min: minRange + i*maxRange, Max: maxRange + i*maxRange} + p := gemini.PartitionRange{ + Min: minRange + i*maxRange, + Max: maxRange + i*maxRange, + Rand: rand.New(rand.NewSource(int64(seed))), + } go f(workerCtx, &workers, schema, table, s, p, c, mode) } } @@ -266,7 +270,7 @@ func Job(ctx context.Context, wg *sync.WaitGroup, schema *gemini.Schema, table g case readMode: validationJob(schema, table, s, p, &testStatus) default: - ind := rand.Intn(100000) % 2 + ind := p.Rand.Intn(100000) % 2 if ind == 0 { mutationJob(schema, table, s, p, &testStatus) } else { diff --git a/datautils.go b/datautils.go index 292567a8..595e2330 100644 --- a/datautils.go +++ b/datautils.go @@ -11,55 +11,51 @@ import ( "github.com/segmentio/ksuid" ) -func randIntRange(min int, max int) int { - return rand.Intn(max-min) + min +func randIntRange(rnd *rand.Rand, min int, max int) int { + return rnd.Intn(max-min) + min } -func nonEmptyRandIntRange(min int, max int, def int) int { +func nonEmptyRandIntRange(rnd *rand.Rand, min int, max int, def int) int { if max > min && min > 0 { - return randIntRange(min, max) + return randIntRange(rnd, min, max) } - return randIntRange(1, def) + return randIntRange(rnd, 1, def) } -func randInt64Range(min int64, max int64) int64 { - return rand.Int63n(max-min) + min +func randInt64Range(rnd *rand.Rand, min int64, max int64) int64 { + return rnd.Int63n(max-min) + min } -func nonEmptyRandInt64Range(min int64, max int64, def int64) int64 { +func nonEmptyRandInt64Range(rnd *rand.Rand, min int64, max int64, def int64) int64 { if max > min && min > 0 { - return randInt64Range(min, max) + return randInt64Range(rnd, min, max) } - return randInt64Range(1, def) + return randInt64Range(rnd, 1, def) } -func randFloat32Range(min float32, max float32) float32 { - return rand.Float32() * (max - min) +func randFloat32Range(rnd *rand.Rand, min float32, max float32) float32 { + return rnd.Float32() * (max - min) } -func nonEmptyRandFloat32Range(min float32, max float32, def float32) float32 { +func nonEmptyRandFloat32Range(rnd *rand.Rand, min float32, max float32, def float32) float32 { if max > min && min > 0 { - return randFloat32Range(min, max) + return randFloat32Range(rnd, min, max) } - return randFloat32Range(1, def) + return randFloat32Range(rnd, 1, def) } -func randFloat64Range(min float64, max float64) float64 { - return rand.Float64() * (max - min) +func randFloat64Range(rnd *rand.Rand, min float64, max float64) float64 { + return rnd.Float64() * (max - min) } -func nonEmptyRandFloat64Range(min float64, max float64, def float64) float64 { +func nonEmptyRandFloat64Range(rnd *rand.Rand, min float64, max float64, def float64) float64 { if max > min && min > 0 { - return randFloat64Range(min, max) + return randFloat64Range(rnd, min, max) } - return randFloat64Range(1, def) + return randFloat64Range(rnd, 1, def) } -func randString(len int) string { - return nonEmptyRandStringWithTime(len, time.Now().UTC()) -} - -func randStringWithTime(len int, t time.Time) string { +func randStringWithTime(rnd *rand.Rand, len int, t time.Time) string { id, _ := ksuid.NewRandomWithTime(t) var buf strings.Builder @@ -70,41 +66,41 @@ func randStringWithTime(len int, t time.Time) string { // Pad some extra random data buff := make([]byte, len-buf.Len()) - rand.Read(buff) + rnd.Read(buff) buf.WriteString(base64.StdEncoding.EncodeToString(buff)) return buf.String()[:len] } -func nonEmptyRandStringWithTime(len int, t time.Time) string { +func nonEmptyRandStringWithTime(rnd *rand.Rand, len int, t time.Time) string { if len <= 0 { len = 1 } - return randStringWithTime(len, t) + return randStringWithTime(rnd, len, t) } -func randDate() string { - time := randTime() +func randDate(rnd *rand.Rand) string { + time := randTime(rnd) return time.Format("2006-01-02") } -func randTime() time.Time { +func randTime(rnd *rand.Rand) time.Time { min := time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC).Unix() max := time.Date(2024, 1, 0, 0, 0, 0, 0, time.UTC).Unix() - sec := rand.Int63n(max-min) + min + sec := rnd.Int63n(max-min) + min return time.Unix(sec, 0) } -func randTimeNewer(d time.Time) time.Time { +func randTimeNewer(rnd *rand.Rand, d time.Time) time.Time { min := time.Date(d.Year()+1, 1, 0, 0, 0, 0, 0, time.UTC).Unix() max := time.Date(2024, 1, 0, 0, 0, 0, 0, time.UTC).Unix() - sec := rand.Int63n(max-min+1) + min + sec := rnd.Int63n(max-min+1) + min return time.Unix(sec, 0) } -func randIpV4Address(v, pos int) string { +func randIpV4Address(rnd *rand.Rand, v, pos int) string { if pos < 0 || pos > 4 { panic(fmt.Sprintf("invalid position for the desired value of the IP part %d, 0-3 supported", pos)) } @@ -116,7 +112,7 @@ func randIpV4Address(v, pos int) string { if i == pos { blocks = append(blocks, strconv.Itoa(v)) } else { - blocks = append(blocks, strconv.Itoa(rand.Intn(255))) + blocks = append(blocks, strconv.Itoa(rnd.Intn(255))) } } return strings.Join(blocks, ".") diff --git a/datautils_test.go b/datautils_test.go index b1f2707e..c37b2b34 100644 --- a/datautils_test.go +++ b/datautils_test.go @@ -1,14 +1,17 @@ package gemini import ( + "math/rand" "testing" "testing/quick" "time" ) +var rnd = rand.New(rand.NewSource(time.Now().UnixNano())) + func TestNonEmptyRandRange(t *testing.T) { f := func(x, y int) bool { - r := nonEmptyRandIntRange(x, y, 10) + r := nonEmptyRandIntRange(rnd, x, y, 10) return r > 0 } if err := quick.Check(f, nil); err != nil { @@ -18,7 +21,7 @@ func TestNonEmptyRandRange(t *testing.T) { func TestNonEmptyRandRange64(t *testing.T) { f := func(x, y int) bool { - r := nonEmptyRandIntRange(x, y, 10) + r := nonEmptyRandIntRange(rnd, x, y, 10) return r > 0 } if err := quick.Check(f, nil); err != nil { @@ -28,7 +31,7 @@ func TestNonEmptyRandRange64(t *testing.T) { func TestNonEmptyRandFloat32Range(t *testing.T) { f := func(x, y float32) bool { - r := nonEmptyRandFloat32Range(x, y, 10) + r := nonEmptyRandFloat32Range(rnd, x, y, 10) return r > 0 } if err := quick.Check(f, nil); err != nil { @@ -38,7 +41,7 @@ func TestNonEmptyRandFloat32Range(t *testing.T) { func TestNonEmptyRandFloat64Range(t *testing.T) { f := func(x, y float64) bool { - r := nonEmptyRandFloat64Range(x, y, 10) + r := nonEmptyRandFloat64Range(rnd, x, y, 10) return r > 0 } if err := quick.Check(f, nil); err != nil { @@ -50,7 +53,7 @@ func TestNonEmptyRandString(t *testing.T) { // TODO: Figure out why this is so horribly slow... tt := time.Now() f := func(len int32) bool { - r := nonEmptyRandStringWithTime(int(len), tt) + r := nonEmptyRandStringWithTime(rnd, int(len), tt) return r != "" } cfg := &quick.Config{MaxCount: 10} @@ -64,7 +67,7 @@ var bench_r string func BenchmarkNonEmptyRandStringWithTime(b *testing.B) { tt := time.Now() for i := 0; i < b.N; i++ { - bench_r = nonEmptyRandStringWithTime(30, tt) + bench_r = nonEmptyRandStringWithTime(rnd, 30, tt) } } @@ -72,7 +75,7 @@ func BenchmarkNonEmptyRandStringWithTimeParallel(b *testing.B) { tt := time.Now() b.RunParallel(func(pb *testing.PB) { for pb.Next() { - bench_r = nonEmptyRandStringWithTime(30, tt) + bench_r = nonEmptyRandStringWithTime(rnd, 30, tt) } }) } @@ -81,7 +84,7 @@ var bench_rr int func BenchmarkNonEmptyRandRange(b *testing.B) { for i := 0; i < b.N; i++ { - bench_rr = nonEmptyRandIntRange(0, 50, 30) + bench_rr = nonEmptyRandIntRange(rnd, 0, 50, 30) } } @@ -89,6 +92,6 @@ var bench_rr64 int64 func BenchmarkNonEmptyRandRange64(b *testing.B) { for i := 0; i < b.N; i++ { - bench_rr64 = nonEmptyRandInt64Range(0, 50, 30) + bench_rr64 = nonEmptyRandInt64Range(rnd, 0, 50, 30) } } diff --git a/schema.go b/schema.go index ad87dfa6..4cbd9933 100644 --- a/schema.go +++ b/schema.go @@ -122,8 +122,9 @@ type Schema struct { } type PartitionRange struct { - Min int `default:0` - Max int `default:100` + Min int `default:0` + Max int `default:100` + Rand *rand.Rand } func (s *Schema) GetDropSchema() []string { @@ -150,38 +151,38 @@ func (st SimpleType) GenValue(p *PartitionRange) []interface{} { var val interface{} switch st { case TYPE_ASCII, TYPE_TEXT, TYPE_VARCHAR: - val = randStringWithTime(nonEmptyRandIntRange(p.Max, p.Max, 10), randTime()) + val = randStringWithTime(p.Rand, nonEmptyRandIntRange(p.Rand, p.Max, p.Max, 10), randTime(p.Rand)) case TYPE_BLOB: - val = hex.EncodeToString([]byte(randStringWithTime(nonEmptyRandIntRange(p.Max, p.Max, 10), randTime()))) + val = hex.EncodeToString([]byte(randStringWithTime(p.Rand, nonEmptyRandIntRange(p.Rand, p.Max, p.Max, 10), randTime(p.Rand)))) case TYPE_BIGINT: - val = rand.Int63() + val = p.Rand.Int63() case TYPE_BOOLEAN: - val = rand.Int()%2 == 0 + val = p.Rand.Int()%2 == 0 case TYPE_DATE: - val = randDate() + val = randDate(p.Rand) case TYPE_TIME, TYPE_TIMESTAMP: - val = randTime() + val = randTime(p.Rand) case TYPE_DECIMAL: - val = inf.NewDec(randInt64Range(int64(p.Min), int64(p.Max)), 3) + val = inf.NewDec(randInt64Range(p.Rand, int64(p.Min), int64(p.Max)), 3) case TYPE_DOUBLE: - val = randFloat64Range(float64(p.Min), float64(p.Max)) + val = randFloat64Range(p.Rand, float64(p.Min), float64(p.Max)) case TYPE_DURATION: - val = (time.Minute * time.Duration(randIntRange(p.Min, p.Max))).String() + val = (time.Minute * time.Duration(randIntRange(p.Rand, p.Min, p.Max))).String() case TYPE_FLOAT: - val = randFloat32Range(float32(p.Min), float32(p.Max)) + val = randFloat32Range(p.Rand, float32(p.Min), float32(p.Max)) case TYPE_INET: - val = net.ParseIP(randIpV4Address(rand.Intn(255), 2)) + val = net.ParseIP(randIpV4Address(p.Rand, p.Rand.Intn(255), 2)) case TYPE_INT: - val = nonEmptyRandIntRange(p.Min, p.Max, 10) + val = nonEmptyRandIntRange(p.Rand, p.Min, p.Max, 10) case TYPE_SMALLINT: - val = int16(nonEmptyRandIntRange(p.Min, p.Max, 10)) + val = int16(nonEmptyRandIntRange(p.Rand, p.Min, p.Max, 10)) case TYPE_TIMEUUID, TYPE_UUID: - r := gocql.UUIDFromTime(randTime()) + r := gocql.UUIDFromTime(randTime(p.Rand)) val = r.String() case TYPE_TINYINT: - val = int8(nonEmptyRandIntRange(p.Min, p.Max, 10)) + val = int8(nonEmptyRandIntRange(p.Rand, p.Min, p.Max, 10)) case TYPE_VARINT: - val = big.NewInt(randInt64Range(int64(p.Min), int64(p.Max))) + val = big.NewInt(randInt64Range(p.Rand, int64(p.Min), int64(p.Max))) default: panic(fmt.Sprintf("generate value: not supported type %s", st)) } @@ -197,77 +198,77 @@ func (st SimpleType) GenValueRange(p *PartitionRange) ([]interface{}, []interfac ) switch st { case TYPE_ASCII, TYPE_TEXT, TYPE_VARCHAR: - startTime := randTime() - start := nonEmptyRandIntRange(p.Min, p.Max, 10) - end := start + nonEmptyRandIntRange(p.Min, p.Max, 10) - left = nonEmptyRandStringWithTime(start, startTime) - right = nonEmptyRandStringWithTime(end, randTimeNewer(startTime)) + startTime := randTime(p.Rand) + start := nonEmptyRandIntRange(p.Rand, p.Min, p.Max, 10) + end := start + nonEmptyRandIntRange(p.Rand, p.Min, p.Max, 10) + left = nonEmptyRandStringWithTime(p.Rand, start, startTime) + right = nonEmptyRandStringWithTime(p.Rand, end, randTimeNewer(p.Rand, startTime)) case TYPE_BLOB: - startTime := randTime() - start := nonEmptyRandIntRange(p.Min, p.Max, 10) - end := start + nonEmptyRandIntRange(p.Min, p.Max, 10) - left = hex.EncodeToString([]byte(nonEmptyRandStringWithTime(start, startTime))) - right = hex.EncodeToString([]byte(nonEmptyRandStringWithTime(end, randTimeNewer(startTime)))) + startTime := randTime(p.Rand) + start := nonEmptyRandIntRange(p.Rand, p.Min, p.Max, 10) + end := start + nonEmptyRandIntRange(p.Rand, p.Min, p.Max, 10) + left = hex.EncodeToString([]byte(nonEmptyRandStringWithTime(p.Rand, start, startTime))) + right = hex.EncodeToString([]byte(nonEmptyRandStringWithTime(p.Rand, end, randTimeNewer(p.Rand, startTime)))) case TYPE_BIGINT: - start := nonEmptyRandInt64Range(int64(p.Min), int64(p.Max), 10) - end := start + nonEmptyRandInt64Range(int64(p.Min), int64(p.Max), 10) + start := nonEmptyRandInt64Range(p.Rand, int64(p.Min), int64(p.Max), 10) + end := start + nonEmptyRandInt64Range(p.Rand, int64(p.Min), int64(p.Max), 10) left = start right = end case TYPE_DATE, TYPE_TIME, TYPE_TIMESTAMP: - start := randTime() - end := randTimeNewer(start) + start := randTime(p.Rand) + end := randTimeNewer(p.Rand, start) left = start right = end case TYPE_DECIMAL: - start := nonEmptyRandInt64Range(int64(p.Min), int64(p.Max), 10) - end := start + nonEmptyRandInt64Range(int64(p.Min), int64(p.Max), 10) + start := nonEmptyRandInt64Range(p.Rand, int64(p.Min), int64(p.Max), 10) + end := start + nonEmptyRandInt64Range(p.Rand, int64(p.Min), int64(p.Max), 10) left = inf.NewDec(start, 3) right = inf.NewDec(end, 3) case TYPE_DOUBLE: - start := nonEmptyRandFloat64Range(float64(p.Min), float64(p.Max), 10) - end := start + nonEmptyRandFloat64Range(float64(p.Min), float64(p.Max), 10) + start := nonEmptyRandFloat64Range(p.Rand, float64(p.Min), float64(p.Max), 10) + end := start + nonEmptyRandFloat64Range(p.Rand, float64(p.Min), float64(p.Max), 10) left = start right = end case TYPE_DURATION: - start := time.Minute * time.Duration(nonEmptyRandIntRange(p.Min, p.Max, 10)) - end := start + time.Minute*time.Duration(nonEmptyRandIntRange(p.Min, p.Max, 10)) + start := time.Minute * time.Duration(nonEmptyRandIntRange(p.Rand, p.Min, p.Max, 10)) + end := start + time.Minute*time.Duration(nonEmptyRandIntRange(p.Rand, p.Min, p.Max, 10)) left = start right = end case TYPE_FLOAT: - start := nonEmptyRandFloat32Range(float32(p.Min), float32(p.Max), 10) - end := start + nonEmptyRandFloat32Range(float32(p.Min), float32(p.Max), 10) + start := nonEmptyRandFloat32Range(p.Rand, float32(p.Min), float32(p.Max), 10) + end := start + nonEmptyRandFloat32Range(p.Rand, float32(p.Min), float32(p.Max), 10) left = start right = end case TYPE_INET: - start := randIpV4Address(0, 3) - end := randIpV4Address(255, 3) + start := randIpV4Address(p.Rand, 0, 3) + end := randIpV4Address(p.Rand, 255, 3) left = net.ParseIP(start) right = net.ParseIP(end) case TYPE_INT: - start := nonEmptyRandIntRange(p.Min, p.Max, 10) - end := start + nonEmptyRandIntRange(p.Min, p.Max, 10) + start := nonEmptyRandIntRange(p.Rand, p.Min, p.Max, 10) + end := start + nonEmptyRandIntRange(p.Rand, p.Min, p.Max, 10) left = start right = end case TYPE_SMALLINT: - start := int16(nonEmptyRandIntRange(p.Min, p.Max, 10)) - end := start + int16(nonEmptyRandIntRange(p.Min, p.Max, 10)) + start := int16(nonEmptyRandIntRange(p.Rand, p.Min, p.Max, 10)) + end := start + int16(nonEmptyRandIntRange(p.Rand, p.Min, p.Max, 10)) left = start right = end case TYPE_TIMEUUID, TYPE_UUID: - start := randTime() - end := randTimeNewer(start) + start := randTime(p.Rand) + end := randTimeNewer(p.Rand, start) left = gocql.UUIDFromTime(start).String() right = gocql.UUIDFromTime(end).String() case TYPE_TINYINT: - start := int8(nonEmptyRandIntRange(p.Min, p.Max, 10)) - end := start + int8(nonEmptyRandIntRange(p.Min, p.Max, 10)) + start := int8(nonEmptyRandIntRange(p.Rand, p.Min, p.Max, 10)) + end := start + int8(nonEmptyRandIntRange(p.Rand, p.Min, p.Max, 10)) left = start right = end case TYPE_VARINT: end := &big.Int{} - start := big.NewInt(randInt64Range(int64(p.Min), int64(p.Max))) + start := big.NewInt(randInt64Range(p.Rand, int64(p.Min), int64(p.Max))) end.Set(start) - end = end.Add(start, big.NewInt(randInt64Range(int64(p.Min), int64(p.Max)))) + end = end.Add(start, big.NewInt(randInt64Range(p.Rand, int64(p.Min), int64(p.Max)))) left = start right = end default: @@ -519,11 +520,11 @@ func (s *Schema) GenDeleteRows(t Table, p *PartitionRange) (*Stmt, error) { } func (s *Schema) GenMutateStmt(t Table, p *PartitionRange) (*Stmt, error) { - switch n := rand.Intn(1000); n { + switch n := p.Rand.Intn(1000); n { case 10, 100: return s.GenDeleteRows(t, p) default: - switch n := rand.Intn(2); n { + switch n := p.Rand.Intn(2); n { case 0: if t.KnownIssues[KnownIssuesJsonWithTuples] { return s.GenInsertStmt(t, p) @@ -538,9 +539,9 @@ func (s *Schema) GenMutateStmt(t Table, p *PartitionRange) (*Stmt, error) { func (s *Schema) GenCheckStmt(t Table, p *PartitionRange) *Stmt { var n int if len(t.Indexes) > 0 { - n = rand.Intn(5) + n = p.Rand.Intn(5) } else { - n = rand.Intn(4) + n = p.Rand.Intn(4) } switch n { case 0: @@ -578,7 +579,7 @@ func (s *Schema) genMultiplePartitionQuery(t Table, p *PartitionRange) *Stmt { relations []string values []interface{} ) - pkNum := rand.Intn(len(t.PartitionKeys)) + pkNum := p.Rand.Intn(len(t.PartitionKeys)) if pkNum == 0 { pkNum = 1 } @@ -608,7 +609,7 @@ func (s *Schema) genClusteringRangeQuery(t Table, p *PartitionRange) *Stmt { } maxClusteringRels := 0 if len(t.ClusteringKeys) > 1 { - maxClusteringRels = rand.Intn(len(t.ClusteringKeys) - 1) + maxClusteringRels = p.Rand.Intn(len(t.ClusteringKeys) - 1) for i := 0; i < maxClusteringRels; i++ { relations = append(relations, fmt.Sprintf("%s = ?", t.ClusteringKeys[i].Name)) values = appendValue(t.ClusteringKeys[i].Type, p, values) @@ -630,7 +631,7 @@ func (s *Schema) genMultiplePartitionClusteringRangeQuery(t Table, p *PartitionR relations []string values []interface{} ) - pkNum := rand.Intn(len(t.PartitionKeys)) + pkNum := p.Rand.Intn(len(t.PartitionKeys)) if pkNum == 0 { pkNum = 1 } @@ -642,7 +643,7 @@ func (s *Schema) genMultiplePartitionClusteringRangeQuery(t Table, p *PartitionR } maxClusteringRels := 0 if len(t.ClusteringKeys) > 1 { - maxClusteringRels = rand.Intn(len(t.ClusteringKeys) - 1) + maxClusteringRels = p.Rand.Intn(len(t.ClusteringKeys) - 1) for i := 0; i < maxClusteringRels; i++ { relations = append(relations, fmt.Sprintf("%s = ?", t.ClusteringKeys[i].Name)) values = appendValue(t.ClusteringKeys[i].Type, p, values) @@ -668,7 +669,7 @@ func (s *Schema) genSingleIndexQuery(t Table, p *PartitionRange) *Stmt { if len(t.Indexes) == 0 { return nil } - pkNum := rand.Intn(len(t.PartitionKeys)) + pkNum := p.Rand.Intn(len(t.PartitionKeys)) if pkNum == 0 { pkNum = 1 } @@ -678,7 +679,7 @@ func (s *Schema) genSingleIndexQuery(t Table, p *PartitionRange) *Stmt { values = appendValue(pk.Type, p, values) } } - idx := rand.Intn(len(t.Indexes)) + idx := p.Rand.Intn(len(t.Indexes)) query := fmt.Sprintf("SELECT * FROM %s.%s WHERE %s AND %s=?", s.Keyspace.Name, t.Name, strings.Join(relations, " AND "), t.Indexes[idx].Column.Name) values = appendValue(t.Indexes[idx].Column.Type, p, nil) return &Stmt{