Skip to content

Commit

Permalink
Merge pull request #7970 from mjibson/acceptance/rsg
Browse files Browse the repository at this point in the history
sql: add random syntax generator tests
  • Loading branch information
maddyblue committed Aug 27, 2016
2 parents 132f895 + b6e03fd commit dd83f64
Show file tree
Hide file tree
Showing 6 changed files with 1,040 additions and 0 deletions.
141 changes: 141 additions & 0 deletions internal/rsg/rsg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright 2016 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
//
// Author: Matt Jibson (mjibson@gmail.com)

package rsg

import (
"fmt"
"math/rand"
"strings"

"github.com/cockroachdb/cockroach/internal/rsg/yacc"
"github.com/cockroachdb/cockroach/util/syncutil"
)

// RSG is a random syntax generator.
type RSG struct {
lock syncutil.Mutex
src *rand.Rand
seen map[string]bool
prods map[string][]*yacc.ExpressionNode
}

// NewRSG creates a random syntax generator from the given random seed and
// yacc file.
func NewRSG(seed int64, y string) (*RSG, error) {
tree, err := yacc.Parse("", y)
if err != nil {
return nil, err
}
rsg := RSG{
src: rand.New(rand.NewSource(seed)),
seen: make(map[string]bool),
prods: make(map[string][]*yacc.ExpressionNode),
}
for _, prod := range tree.Productions {
rsg.prods[prod.Name] = prod.Expressions
}
return &rsg, nil
}

// Generate generates a unique random syntax from the root node. At most depth
// levels of token expansion are performed. An empty string is returned on
// error or if depth is exceeded. Generate is safe to call from mulitipule
// goroutines. If Generate is called more times than it can generate unique
// output, it will block forever.
func (r *RSG) Generate(root string, depth int) string {
for {
s := strings.Join(r.generate(root, depth), " ")
r.lock.Lock()
if !r.seen[s] {
r.seen[s] = true
} else {
s = ""
}
r.lock.Unlock()
if s != "" {
s = strings.Replace(s, "_LA", "", -1)
s = strings.Replace(s, " AS OF SYSTEM TIME \"string\"", "", -1)
return s
}
}
}

func (r *RSG) generate(root string, depth int) []string {
var ret []string
prods := r.prods[root]
if len(prods) == 0 {
return []string{root}
}
prod := prods[r.Intn(len(prods))]
for _, item := range prod.Items {
switch item.Typ {
case yacc.TypLiteral:
v := item.Value[1 : len(item.Value)-1]
ret = append(ret, v)
case yacc.TypToken:
var v []string
switch item.Value {
case "IDENT":
v = []string{"ident"}
case "c_expr":
v = r.generate(item.Value, 30)
case "SCONST":
v = []string{`'string'`}
case "ICONST":
v = []string{fmt.Sprint(r.Intn(1000) - 500)}
case "FCONST":
v = []string{fmt.Sprint(r.Float64())}
case "BCONST":
v = []string{`b'bytes'`}
case "substr_from":
v = []string{"FROM", `'string'`}
case "substr_for":
v = []string{"FOR", `'string'`}
case "overlay_placing":
v = []string{"PLACING", `'string'`}
default:
if depth == 0 {
return nil
}
v = r.generate(item.Value, depth-1)
}
if v == nil {
return nil
}
ret = append(ret, v...)
default:
panic("unknown item type")
}
}
return ret
}

// Intn returns a random int.
func (r *RSG) Intn(n int) int {
r.lock.Lock()
v := r.src.Intn(n)
r.lock.Unlock()
return v
}

// Float64 returns a random float.
func (r *RSG) Float64() float64 {
r.lock.Lock()
v := r.src.NormFloat64()
r.lock.Unlock()
return v
}
Loading

0 comments on commit dd83f64

Please sign in to comment.